Penpen7のブログ

Penpen7のエンジニアブログ

電子工作初心者がRaspberry Pi 4Bで温度・湿度・気圧・二酸化炭素濃度を計測してAmbientに送信してみる

はじめに

有給期間中に新しいことやりたいな〜と思っていたところ、手元にあるラズパイを使って電子工作すればいいのではとなったので挑戦してみました。
日常生活で換気やエアコンの電源を入れるタイミングをいつにしようか迷うことが多かったため、室内の気温・湿度・気圧・二酸化炭素濃度を測定して通知してくれるようなシステムを組んでみようと思っています。
うろ覚えの部分もあるため手順に抜け漏れもあるかと思いますが、ご容赦ください。
このページの最後に参考文献を書いているので、何か作業に詰まることがあれば参考にされると良いかもしれません。

用意したもの

  • Raspberry Pi 4B
    • 知人からいただいたので0円
  • 電子工作スタートキット
    • ラズパイで電子工作するのに必要な基本的なツールが入っている
    • 5,087円
  • はんだごて
    • BME680とピンをくっつけるために使用
    • 880円
  • BME680 複合センサーモジュール
    • 温度・湿度・気圧・有害ガス?を一挙に計測できます
    • 1,320円
  • MH-Z19C
    • 2,480円
    • 二酸化炭素濃度を計測するのに使用
  • 合計で大体1万円くらい

配線していく

BME680にピンをはんだ付けする

  • BME680に加えピンが同封されているので、はんだで繋ぎ合わせる

    • ピンが同封されているが、4ピン余分についている状態で届いたので、ハサミやニッパーなどで真ん中を切って4ピンにしておく
  • はんだ付けは中学生ぶりにやったのでうまくできた自信はなし

ブレッドボードを使って接続

  • スタートキットに付属しているブレッドボードを使って回路を組んでいく
    • 配線は以下を参考
      • BME680
      • MH-Z19C
  • ショートしないように注意
  • 回路図は間違えているかもしれないので、下の写真や参考URLを参照ください

うまく測定データを取得できなかったので、試行錯誤…

  • ブレッドボードではなくラズパイ本体にピンを刺してからはうまく動くようになった
  • プルアップ抵抗に関してはつけた方が良いという記事もあったが、特になくても動いていそう
  • ジャンパーケーブルを動かすと認識されたりされなくなったりしたので、断線しかけていると判断し交換してうまく動くようになった

ラズパイの設定

ラズパイにOSをインストールする

  • Raspberry Pi Imageのインストール

    • MacであればHomebrewを使ってRaspbery Pi Imagerを入れられる

      $ brew install --cask raspberry-pi-imager
      
  • SDカードにRaspbery Pi Imagerを使用してOSを書き込む

    • Imagerの設定でsshとWifiを有効にしてからイメージを書き込んでおくと、ラズパイを起動する際にディスプレイに繋ぐ必要がなくなるので楽ちん
  • 書き込みが終わったらSDカードを抜いてラズパイに入れ、電源を入れる

  • しばらく起動が終わるのを待ってから、手元のPCでラズパイにsshでログインする

    • デフォルトであればraspberrypi.localでホストが立っているはず
    • ユーザー名は適当に
    $ ssh naoki@raspberrypi.local
    

I2Cとシリアル通信の有効化

  • 以下のコマンドを実行する

    $ sudo raspi-config
    
    • Interface Optionsを矢印キーで選択、エンター
    • I2Cを選択し、エンター
    • Yesを選びエンター
    • I2Cが有効になったので、再度Interface Optionsに入り、Serial Portを有効化する
    • Noを選択、エンター
    • Yesを選択、エンター
    • 有効化した後は、Finishを選択し、エンター
    • 再起動が求められるので、Yesを選択しエンターを押す
      • 再起動する画面が表示されなければ設定画面を抜けた後に以下を実行

        sudo reboot
        

Wifiが自動的に切れないように設定

  • Wifiが切れることが多かったため、以下を参考にPower Management設定を変更

    $ sudo iwconfig wlan0 power off
    $ iwconfig wlan0
    $ sudo vim /etc/rc.local
    (以下を追記)
    # Wi-Fi power management OFF
    iwconfig wlan0 power off
    

権限変更

  • sudoをつけなくてもこれから作成するpythonコードを実行できるように権限を変更する
    • 参考

    • 使用しているユーザーをttyグループに追加し、ログインし直す

      • (新しいセッションでグループ追加が反映される
      # グループ表示
      $ groups 
      # ttyグループに追加
      $ sudo usermod -a -G tty naoki
      # ログインし直す
      
    • /dev/serial0のリンク先のグループ権限にread権限を追加

      $ ls -l /dev/serial0
      lrwxrwxrwx 1 root root 5 Jun 11 18:54 /dev/serial0 -> ttyS0
      $ ls -l /dev/ttyS0
      crw--w---- 1 root tty 4, 64 Jun 11 22:59 /dev/ttyS0
      $ sudo chmod g+r /dev/ttyS0
      $ ls -l /dev/ttyS0
      crw-rw---- 1 root tty 4, 64 Jun 11 22:59 /dev/ttyS0
      

コーディング

センサーからの各値をpythonで取り込めるようにしておく

  • pythonでvirtual environmentを作成・有効化

    $ cd ~
    $ sudo apt update
    $ sudo apt install python3-venv git vim
    $ python3 -m venv venv
    $ source ~/venv/bin/activate
    
  • pipで必要なモジュールをインストール

    $ sudo apt update
    $ sudo apt install python3-pip
    $ pip3 install mh_z19
    $ pip3 install bme680
    $ pip3 install git+https://github.com/TakehikoShimojima/ambient-python-lib.git
    
  • ~/main.pyを配置

    • 温度、湿度、気圧、ガス抵抗、二酸化炭素濃度を取得、不快指数を計算し、30秒ごとに画面に表示するコードになっている
    $ vim ~/main.py
    
    import mh_z19
    import bme680
    import time
    import os
    
    def read_co2_density():
        out = mh_z19.read(serial_console_untouched=True)
        return out["co2"]
    
    def init_sensor():
        sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY)
        sensor.set_humidity_oversample(bme680.OS_2X)
        sensor.set_pressure_oversample(bme680.OS_4X)
        sensor.set_temperature_oversample(bme680.OS_8X)
        sensor.set_filter(bme680.FILTER_SIZE_3)
        sensor.set_gas_status(bme680.ENABLE_GAS_MEAS)
        
        sensor.set_gas_heater_temperature(320)
        sensor.set_gas_heater_duration(150)
        sensor.select_gas_heater_profile(0)
        
        return sensor
    
    
    if __name__ == '__main__':
        sensor = init_sensor()
        try:
            while True:
                temperature = pressure = humidity = gas_resistance = co2_density = di = None
    
                if sensor.get_sensor_data():
                    temperature = sensor.data.temperature
                    pressure = sensor.data.pressure
                    humidity = sensor.data.humidity
                    co2_density = read_co2_density()
                    di = 0.81 * temperature + 0.01 * humidity * (0.99 * temperature - 14.3) + 46.3
    
                    if sensor.data.heat_stable:
                        gas_resistance = sensor.data.gas_resistance
                print(f"temperature: {temperature} C pressure: {pressure} hPa humidity: {humidity} % gas_resistance: {gas_resistance} ohms co2_density: {co2_density} ppm di: {di}") 
    
                time.sleep(30)
    
        except KeyboardInterrupt:
            pass
    
  • pythonで実行すると温度、湿度、気圧、ガス抵抗、二酸化炭素濃度、不快指数を表示するようになる

    $ python3 main.py
    

Ambientに送信して可視化する

  • AmbientとはIoTデータ可視化サービスで、手軽に測定値をグラフ化できる

  • 以下のページで登録する

  • チャネルを追加する

  • 作成されたチャネルIDとライトキーをメモしておく

  • チャネルの画面でグラフを追加する

  • 追加されたグラフのデータ設定を行う

  • 温度、湿度、気圧、二酸化炭素濃度、ガス抵抗、不快指数をプロットするための設定を行う

    • d1~d8というラベルをつけて最大8個まで値を送信・プロットできるので、どのグラフでd1~d8までのどの値を表示するか決めておく

    • 今回のpythonコードでは以下の対応

      ラベル 測定値
      d1 温度 (temperature)
      d2 湿度 (humidity)
      d3 気圧 (pressure)
      d4 ガス抵抗 (gas_resistance)
      d5 CO2濃度 (co2_density)
      d6 不快指数
      d7 なし
      d8 なし
    • 不快指数とは温度、湿度から計算できる夏の蒸し暑さを表した指標

  • 以下のように複数のグラフを表示しておくと一括で複数の値を確認できる

  • pythonのコードを以下のように修正し、ambientに測定データを送信するようにする

    $ vim ~/main.py
    
    import mh_z19
    import bme680
    import time
    import ambient
    import os
    
    def read_co2_density():
        out = mh_z19.read(serial_console_untouched=True)
        return out["co2"]
    
    def init_sensor():
        sensor = bme680.BME680(bme680.I2C_ADDR_SECONDARY)
        sensor.set_humidity_oversample(bme680.OS_2X)
        sensor.set_pressure_oversample(bme680.OS_4X)
        sensor.set_temperature_oversample(bme680.OS_8X)
        sensor.set_filter(bme680.FILTER_SIZE_3)
        sensor.set_gas_status(bme680.ENABLE_GAS_MEAS)
        
        sensor.set_gas_heater_temperature(320)
        sensor.set_gas_heater_duration(150)
        sensor.select_gas_heater_profile(0)
        
        return sensor
    
    
    if __name__ == '__main__':
        channel_id = os.environ["AMBIENT_CHANNEL_ID"]
        write_key = os.environ["AMBIENT_WRITE_KEY"]
        am = ambient.Ambient(channel_id, write_key)
        sensor = init_sensor()
        try:
            while True:
                temperature = pressure = humidity = gas_resistance = co2_density = di = None
    
                if sensor.get_sensor_data():
                    temperature = sensor.data.temperature
                    pressure = sensor.data.pressure
                    humidity = sensor.data.humidity
                    co2_density = read_co2_density()
                    di = 0.81 * temperature + 0.01 * humidity * (0.99 * temperature - 14.3) + 46.3
    
                    if sensor.data.heat_stable:
                        gas_resistance = sensor.data.gas_resistance
                print(f"temperature: {temperature} C pressure: {pressure} hPa humidity: {humidity} % gas_resistance: {gas_resistance} ohms co2_density: {co2_density} ppm di: {di}") 
                am.send({'d1': temperature, 'd2': humidity, 'd3': pressure, 'd4': gas_resistance, 'd5': co2_density, 'd6': di})
    
                time.sleep(30)
    
        except KeyboardInterrupt:
            pass
    
    --- main2.py    2024-06-21 20:54:56
    +++ main.py     2024-06-21 19:59:45
    @@ -1,6 +1,7 @@
     import mh_z19
     import bme680
     import time
    +import ambient
     import os
    
     def read_co2_density():
    @@ -23,6 +24,9 @@
    
    
     if __name__ == '__main__':
    +    channel_id = os.environ["AMBIENT_CHANNEL_ID"]
    +    write_key = os.environ["AMBIENT_WRITE_KEY"]
    +    am = ambient.Ambient(channel_id, write_key)
         sensor = init_sensor()
         try:
             while True:
    @@ -38,6 +42,7 @@
                     if sensor.data.heat_stable:
                         gas_resistance = sensor.data.gas_resistance
                 print(f"temperature: {temperature} C pressure: {pressure} hPa humidity: {humidity} % gas_resistance: {gas_resistance} ohms co2_density: {co2_density} ppm di: {di}")
    +            am.send({'d1': temperature, 'd2': humidity, 'd3': pressure, 'd4': gas_resistance, 'd5': co2_density, 'd6': di})
    
                 time.sleep(30)
    
  • あらかじめメモしておいたチャネルIDとライトキーを環境変数に入れておく

    $ export AMBIENT_CHANNEL_ID=XXXXX
    $ export AMBIENT_WRITE_KEY=XXXXX
    
  • 作成したpythonコードを実行する

    $ source ~/venv/bin/activate
    $ python3 ~/main.py
    
  • ambientで測定データをグラフ化して表示してくれるようになるはず

    https://x.com/penpen_77777/status/1800580891372343702

グラフを見てみて

今回可視化されたグラフを読み取ってみて以下のような発見がありました。

  • 不快指数もプロットしているので、部屋の快適具合が数値化されて便利
    • エアコンつける決心がつきやすい
  • 二酸化炭素濃度は窓閉め切ると徐々に上がって2000 ppmを超える
    • 2000 ppmは眠気が誘われるレベル
    • 寝ている間に数値が上がりやすい
  • 窓を開けると二酸化炭素濃度すぐに下がって呼吸しやすくなる
    • 少しでも換気するのは大事
  • お風呂あがりは自分の体の熱気で湿度が上がる

まとめ

ここまでラズパイを使って室内の気温などを計測する手順を説明しました。ぜひ、参考になれば幸いです。
部屋の状況が見れるだけでも部屋を快適にするための意思決定がしやすくなったり、自分の不調の原因の一端を知れたり便利になってきました。
他方、常時センサーのデータを送信するためにデーモン化したり、構築を楽にするためコンテナ化したり、より良い可視化ツールを使ったりと様々な改善策が考えられると思いますが、時間があるときに取り組んでいこうかなと思っています。

参考文献

© Penpen7