Raspberry Piを使用した二酸化炭素濃度による換気扇自動制御をためす

この記事は富士通株式会社FSWebユニット（旧株式会社富士通システムズウェブテクノロジー）が企画する「[いのべこ夏休みアドベントカレンダー 2021](https://qiita.com/jugemsan/items/6c8b7324b9390a557774)」の14日目の記事です。
記事の掲載内容は私自身の見解であり、所属する組織を代表するものではありません。

# はじめに
在宅勤務をするようになって1年半近くが経ちました。自宅で仕事もするようになると、より室内の環境が気になってきます。
最近、特に気にしているのが換気です。換気をしないと室内のCO2濃度が上昇して、集中力の低下や頭痛などの症状を引き起こすようで、こまめな換気を行いたいところですが、定期的に行うのは面倒です。だからといって換気を過剰にすれば良いかというとそうでもなく、空調効率が低下してエコでなかったりします。

このような問題を解決すべく、以前から興味があったRaspberry PiやIoTの技術の学習もかねて、CO2センサーを使用して換気扇を自動制御し、適度な換気をする仕組みを作ってみることにしました。

# 全体像、使ったもの
![overall.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/681603/73ccfb5a-5f2b-9325-a7a6-b18a5b0a7f43.png)
・シングルボードコンピュータ：Raspberry Pi Zero WH
　制御に使うコンピュータは、一度使ってみたかったという理由でRaspberry Pi(以下ラズパイ)を選択しました。ユーザが多く、Webの情報が豊富な点も良いです。ラズパイには複数モデルがありますが、今回は常時稼働させることや実行する処理も重くないということで低消費電力なZeroとしました。非常に小さいのに、単体でwifiやbluetoothも使用できるなど高機能なのがすごいです。

・CO2センサー：MH-Z19b
　https://www.winsen-sensor.com/sensors/co2-sensor/mh-z19b.html
　ラズパイと接続して使用している情報が豊富で、Amazonなどで容易に入手できるということでこの製品としました。事前調査でセンサー操作用のPythonライブラリが存在していることが分かり、開発言語は一旦Pythonとすることにしました。センサー自体は、NDIRという赤外線を用いて二酸化炭素の検出を行う方式のようで、精度も高そうです。そのせいか、購入価格も￥2,500ほどしました。

・スマートプラグ：tp-link HS105
　しくみの検討段階では、換気扇のON/OFFのためのAC100Vのスイッチ制御をどうするかが悩みでしたが、スマートプラグが使えるのではと思い立ちました。調べてみると、node.js用のライブラリが存在し、とりあえずラズパイから操作できそうであったこちらの製品にしました。価格も￥1,500程度と手ごろです。

・換気扇
　基本的にAC100Vで動くものであれば何でも良いですが、自宅のパイプファンタイプの換気扇Panasonic FY-08PDE9を接続しています。換気量は少な目ですが、低消費電力で電気シャッターなども備えている高性能な製品です。

# 使用ライブラリ(Python)
センサーやスマートプラグの制御に使用したPythonのライブラリを記載します。

・mh_z19
　https://github.com/UedaTakeyuki/mh-z19
　CO2センサー(MH-Z19b)からのデータ取得のために使用

・python-kasa
　https://github.com/python-kasa/python-kasa
　tp-linkのスマートプラグを操作するために使用。node.js用のライブラリが存在するのは知っていたのですが、python用のライブラリがないか検索してみたところ見つけました。
　Python3の非同期I/O(asyncio)使用を前提として作られているようです。

# 前提条件
次から記載する手順は、以下の前提としています。

・ラズパイはraspbian OSの導入を済ませ、wifiの接続設定も済んでいる状態とします。
　※リモートコンピュータから操作できるようにSSHの有効化も行っておくと良いです。

# 手順
## ①センサーの物理的な接続
まず、ラズパイのGPIOとCO2センサーを接続します。

**・センサーへのピンヘッダのはんだ付け**
センサーには、コネクタとそれに接続するケーブルが付属していましたが、ラズパイと直接接続できる形状ではないので、ジャンパー線で接続できるようにピンヘッダをセンサー基盤にはんだ付けします。

[センサー(左上)と付属のケーブル(右上)、取り付けるピンヘッダ(下)]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/681603/be5af34a-23eb-f8d4-f1ed-02d111cfc225.png)
[ピンヘッダを必要なサイズにカットしてセンサにはんだ付け]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/681603/b3999190-2e2f-4c06-66ef-7dfee251bc2d.png)
[はんだ付け後　右上は、ラズパイとの接続に使用するジャンパー線]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/681603/a2c1ea5d-da99-7191-43ba-6566fa60aec3.png)

**・センサーとラズパイの接続**
ジャンパー線を使用して以下の4本を接続します。

[センサー側]<->[ラズパイのGPIO]
　V+ <-> 5V(4番ピン)
　V- <-> 0V(6番ピン)
　Rxd <-> TxD(8番ピン)
　Txd <-> RxD(10番ピン)

※GPIOのピンのレイアウトはラズパイ上で以下のコマンドを実行すると表示できます。

```sh:
pi@raspberrypi:~ $ gpio readall
 +-----+-----+---------+------+---+-Pi ZeroW-+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 |     |     |    3.3v |      |   |  1 || 2  |   |      | 5v      |     |     |
 |   2 |   8 |   SDA.1 |   IN | 1 |  3 || 4  |   |      | 5v      |     |     |
 |   3 |   9 |   SCL.1 |   IN | 1 |  5 || 6  |   |      | 0v      |     |     |
 |   4 |   7 | GPIO. 7 |   IN | 1 |  7 || 8  | 1 | ALT5 | TxD     | 15  | 14  |
 |     |     |      0v |      |   |  9 || 10 | 1 | ALT5 | RxD     | 16  | 15  |
 |  17 |   0 | GPIO. 0 |   IN | 0 | 11 || 12 | 0 | IN   | GPIO. 1 | 1   | 18  |
 |  27 |   2 | GPIO. 2 |   IN | 0 | 13 || 14 |   |      | 0v      |     |     |
 |  22 |   3 | GPIO. 3 |   IN | 0 | 15 || 16 | 0 | IN   | GPIO. 4 | 4   | 23  |
 |     |     |    3.3v |      |   | 17 || 18 | 0 | IN   | GPIO. 5 | 5   | 24  |
 |  10 |  12 |    MOSI |   IN | 0 | 19 || 20 |   |      | 0v      |     |     |
 |   9 |  13 |    MISO |   IN | 0 | 21 || 22 | 0 | IN   | GPIO. 6 | 6   | 25  |
 |  11 |  14 |    SCLK |   IN | 0 | 23 || 24 | 1 | IN   | CE0     | 10  | 8   |
 |     |     |      0v |      |   | 25 || 26 | 1 | IN   | CE1     | 11  | 7   |
 |   0 |  30 |   SDA.0 |   IN | 1 | 27 || 28 | 1 | IN   | SCL.0   | 31  | 1   |
 |   5 |  21 | GPIO.21 |   IN | 1 | 29 || 30 |   |      | 0v      |     |     |
 |   6 |  22 | GPIO.22 |   IN | 1 | 31 || 32 | 0 | IN   | GPIO.26 | 26  | 12  |
 |  13 |  23 | GPIO.23 |   IN | 0 | 33 || 34 |   |      | 0v      |     |     |
 |  19 |  24 | GPIO.24 |   IN | 0 | 35 || 36 | 0 | IN   | GPIO.27 | 27  | 16  |
 |  26 |  25 | GPIO.25 |   IN | 0 | 37 || 38 | 0 | IN   | GPIO.28 | 28  | 20  |
 |     |     |      0v |      |   | 39 || 40 | 0 | IN   | GPIO.29 | 29  | 21  |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+-Pi ZeroW-+---+------+---------+-----+-----+
```

[接続完了]
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/681603/acbe6085-6dc4-ceed-e0c2-fba1199f8fcf.png)

##②センサーの接続設定
センサーはラズパイにシリアルインタフェースで接続しますが、それを有効化する必要があります。

```sh:
pi@raspberrypi:~ $ sudo raspi-config
```

上記コマンド実行により表示される画面にて、以下を順に選択します。

"3 Interface Options" を選択。
"P6 Serial Port" を選択。
Would you like a login shell to be accessible over serial? で "No" を選択。
Would you like the serial port hardware to be enabled? で "Yes" を選択。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/681603/51e6cf34-9612-e25c-e407-72d34794983e.png)
raspi-config 完了後に再起動します。

### ③Python実行環境の準備
Pythonを実行できるようにするためパッケージを導入します。

```sh:
pi@raspberrypi:~ $ sudo apt-get -y install python3-dev
pi@raspberrypi:~ $ sudo apt-get -y install python3-pip
```


### ④使用するPythonのライブラリ導入
今回使用するPythonのライブラリをpip3コマンドにより導入します。

```sh:
pi@raspberrypi:~ $ sudo pip3 install mh-z19
pi@raspberrypi:~ $ sudo pip3 install python-kasa --pre
```

### ⑤CO2センサーの動作確認
以下のコマンドを実行してCO2濃度値が取得できることを確認します。
※実行例は644ppmとなります。

```sh:
pi@raspberrypi:~ $ sudo python3 -m mh_z19
{"co2": 644}
```

### ⑥スマートプラグのセットアップとプラグのIPアドレス確認
スマートプラグのセットアップは、スマートフォンに専用アプリ(Kasa Smart)を導入して、画面に従うことで行えます。手順も簡単なのでここでは省略します。
セットアップが完了すると、スマホのアプリからもプラグのON/OFFが行えるようになり便利です。

次にプラグのIPアドレスを確認します。次のコマンドを実行します。IPアドレスを確認する理由は、プログラムからプラグのON/OFFを行う際の対象のプラグ指定に使用するためです。

```sh:
pi@raspberrypi:~ $ kasa
No host name given, trying discovery..
Discovering devices on 255.255.255.255 for 3 seconds
== 換気扇 - HS105(JP) ==
        Host: <※スマートプラグのIPアドレス※>
        Device state: OFF

        == Generic information ==
        Time:         2021-08-13 23:44:13
        Hardware:     2.0
        Software:     1.0.8 Build 210506 Rel.154949
        MAC (rssi):   **:**:**:**:**:** (-44)
        Location:     {'latitude': ******, 'longitude': ******}

        == Device specific information ==
        LED state: True
        On since: None
```

補足：プラグをコンセントから一時的に取り外した時など、割り当てられるIPアドレスが変わらないようにするため、WifiルータなどでDHCPのIPの固定化を行っておくと良いと思います。

### ⑦コーディング
プログラムでは、換気扇をONにする場合とOFFにする場合のしきい値(CO2濃度)を別々に設けることにしました。これは、しきい値の付近で頻繁に換気扇がON/OFFしないようにするためです。
スマートプラグ操作ライブラリの仕様のためPython3の非同期I/Oを使用していますが、それ以外の意味はありません。
プログラムを実行するとプラグのON/OFFの状態を取得し、ONの場合はOFFにするかの判定を、OFFの場合はONにするかを判定し、必要に応じてプラグのOFFまたはONを実行します。
処理後に、日時、CO2濃度値、プラグON/OFFの前後状態を画面にログとして出力します。


```python:co2swplug.py
import datetime
import asyncio
from kasa import SmartPlug
import mh_z19

PLUG_IP = "<プラグのIPアドレス>"
ON_THRESHOLD = 700 # ONしきい値(ppm)
OFF_THRESHOLD = 650 # OFFしきい値(ppm)

async def main():
    now = datetime.datetime.now() # ログ出力用の日時の取得
    now = "{0:%Y-%m-%d %H:%M:%S}".format(now) # ログ出力用の日時の整形

    co2val = mh_z19.read().get("co2") # CO2濃度取得
    p = SmartPlug(PLUG_IP) # プラグのオブジェクト作成
    await p.update() # プラグオブジェクトの初期化
    pstate = p.is_on # プラグがonか確認
    pstate_n = pstate # プラグの次の状態を現在の状態で初期設定(ログ出力用)

    if pstate: # プラグがonの場合
        if co2val <= OFF_THRESHOLD:
            await p.turn_off()
            pstate_n = False
    else: # プラグがoffの場合
        if co2val >= ON_THRESHOLD:
            await p.turn_on()
            pstate_n = True

    out = now + ',' + str(co2val) + ',' + str(pstate) + ',' + str(pstate_n) # ログ文字列生成
    print(out) # ログの画面出力

if __name__ == "__main__":
    asyncio.run(main())
```

以下のように実行します。

```sh:
pi@raspberrypi:~ $ sudo python3 co2swplug.py 
2021-08-13 00:36:50,665,False,False
```

### ⑧定期実行設定
作成したプログラムを、osのcronの機能を使用して定間隔で実行するようにします。

```sh:
sudo crontab -e
```

上記コマンドで末尾に以下の行を追加します。例では5分間隔で処理を繰り返します。

```sh:
*/5 * * * * echo `python3 /<プログラム配置先フォルダ>/co2swplug.py` >> /<ログ出力先フォルダ>/<ログファイル名>.log
```

以下のコマンドで追加されたことを確認します。

```sh:
sudo crontab -l
～省略～
*/5 * * * * echo `python3 /<プログラム配置先フォルダ>/co2swplug.py` >> /<ログ出力先フォルダ>/<ログファイル名>.log
```

# 効果確認
効果確認のため、取得したログをグラフ化しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/681603/7178a063-da50-5d2e-0665-60d2fd4c8772.png)
CO2濃度が下がるとプラグがoffとなり意図した動作ができていそうです。
しかし、プラグがonの時も濃度が上昇していくケースが多くありました。これは、換気扇の換気量が少ないもののためと思われますが、数値としてはおおむね1000ppm以内となっているので今のままで問題なさそうです。

# おまけ（消費電力）
今回のシステムの消費電力をワットチェッカーで計測してみました。
換気扇動作時でもトータルで4W程度と非常に低消費電力でした。

|  項目  |  消費電力  |
| ---- | ---- |
|  プラグ+換気扇(ON時)  |  2.9W  |
|  ラズパイ+CO2センサー  |  1.1W  |


# まとめ
今までの私の仕事はインフラ構築が主で、プログラムを作ることはほとんどなかったのですが、Pythonとそのライブラリを使うことで様々なデバイスを容易に操作できることが分かり、その便利さを実感しました。
また、ラズパイを中心に安価に必要な装置を揃えることができ、低い消費電力で動作させられるなど時代の進歩を感じました。

昨今のコロナ禍で、室内の密の指標として二酸化炭素濃度値が注目されていますが、このような仕組みをうまく使うことで効果的な対策が取れるのではとも思いました。
また色々試してみたいと思います。