【SwitchBot】CO2 濃度に応じて自動で換気扇を回す

## はじめに
二酸化炭素濃度（以降、CO2 濃度）が高くなると、人体に良くない影響が出ると言われています。具体的には、CO2 濃度が 1,000ppm 以上で知的活動（問題解決能力や意思決定能力）の低下が現れ始め、濃度が上がるにつれ心拍数や血圧に変化が生じ、息苦しさや頭痛、眠気、倦怠感などを感じるようになるそうです（運転中に眠くなるのは車内の CO2 濃度による影響が大きそうですね。**換気、大事**）。また建築基準法では、室内環境を維持するための空気環境条件として、CO2 濃度を 1,000ppm 以下に抑えるようにと定められています。
以上のような健康被害を考慮し、室内の CO2 濃度を測定して CO2 濃度が高くなったら自動で換気扇を回し、室内の CO2 濃度を下げるような仕組みを構築しました。流れとしては、Raspberry Pi で CO2 濃度を取得し、CO2 濃度が高くなったら SwitchBotAPI を利用して換気扇を起動し、CO2 濃度が低くなったら換気扇を停止します。

![ダウンロード.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1115291/0233dc4d-0eee-0afe-e255-bbe3fa9741fa.gif)

## 準備したもの
* [Raspberry Pi 4 Model B 4GB](https://amzn.to/3xzQ1aO)
* [CO2-mini](https://amzn.to/3IyQkZX)
* [SwitchBot スマートリモコン ハブミニ](https://amzn.to/3Ei6Pab)
* [SwitchBot スイッチボット スイッチ × 2](https://amzn.to/3EgoTkQ)

## 環境
* Raspberry Pi 4 Model B
    * CentOS Stream 8
        * Python 3.11.0
            * CO2Meter 2.3

## 導入

### Raspberry Pi のセットアップ
Raspberry Pi のセットアップについてはまとめてみたので参考にしてみて下さい。

https://qiita.com/Pumila/items/4f73263025ffbc427e90

### ライブラリのインストール
USB デバイスから情報を取得するためには root 権限が必要になります。以降の作業は root で行うと楽でしょう。まずは必要なライブラリのインストールです。

```console
pip3 install git+https://github.com/heinemml/CO2Meter requests
```

### CO2 濃度の測定
Raspberry Pi と CO2-mini を接続し、CO2 濃度が測定できるか確かめてみましょう（この時、CO2-mini を接続した状態で Raspberry Pi を起動することをおすすめします。認識されるデバイス番号が特定しづらいため）。適当にディレクトリを作り（/root/co2meter など）、以下のソースコードを保存して下さい。

```python:chkco2.py
#!/usr/bin/env python3
from CO2Meter import *
import time

# CO2-mini が認識されているデバイスの設定
try:
    sensor = CO2Meter("/dev/hidraw0")
except Exception as e:
    sensor = CO2Meter("/dev/hidraw1")

while True:
    time.sleep(5)
    data = sensor.get_data()
    current = data["co2"]
    print(current)
```

実行した結果、正しく CO2 濃度が表示されていれば問題ありません。

```console
[root@localhost co2meter]# python3 chkco2.py 
1505
```

USB デバイスの設定についてですが、他に USB デバイスを挿入していたりすると、"hidraw0" ではなく、"hidraw1" だったり "hidraw2" になったりします。またUSB の抜き差しをしたり、長時間運用していると "hidraw0" から "hidraw1" に変わることがあったため、try-except 文を用いています。デバイスが認識できないと以下のようなエラーが吐かれます。認識しない場合、再起動すると認識することがあります。試してみて下さい。
`fcntl.ioctl(self._file, HIDIOCSFEATURE_9, bytearray(set_report))
OSError: [Errno 25] Inappropriate ioctl for device`

<font color="red">2023/03/28 追記</font>

デバイスが認識されなくなる問題について、再起動すると認識するようになると言いましたが、再起動しなくても再認識させる方法が見つかったので、追記しておきます。
まず始めに、デバイスが認識されなくなると以下のようなエラーが吐かれます。
```console:hidraw error
[root@localhost co2meter]# ./chkco2.py 
Traceback (most recent call last):
  File "/root/co2meter/./chkco2.py", line 6, in <module>
    sensor = CO2Meter("/dev/hidraw0")
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/CO2Meter.py", line 46, in __init__
    fcntl.ioctl(self._file, HIDIOCSFEATURE_9, bytearray(set_report))
OSError: [Errno 25] Inappropriate ioctl for device

During handling of the above exception, another exception occurred:
```
これに対して、`/dev/hidraw0` ファイルを削除し、再度 USB を挿し直すと `/dev/hidraw0` として認識してくれます。`rm /dev/hidraw*` とコマンドを実行し、0 番以降も全て削除しても問題なかったので、全て削除しても大丈夫かもしれません。

### SwitchBot の設定
SwichBot アプリ（[iOS](https://apps.apple.com/us/app/switchbot/id1087374760), [Android](https://play.google.com/store/apps/details?id=com.theswitchbot.switchbot&hl=en)）から SwitchBot スイッチデバイスの登録をして下さい。[近くのボット] から登録ができるはずです（詳細は割愛します）。この時、名前を変更しておくと後々便利です。次にスイッチデバイスを選択し、設定からクラウドサービスの有効化を行って下さい。
<img width="200" alt="IMG_0053.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1115291/21c740e3-8f1d-85c6-0a44-7e1b5ffed598.jpeg"><img width="200" alt="IMG_0054.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1115291/a9172637-86a8-2c59-e92d-f6c407765c16.jpeg">

#### 開発者トークンの取得
SwitchBot デバイスにアクセスするために必要な開発者トークンの取得を行います。SwitchBot アプリの [プロフィール] より [設定] を開きます。[アプリバージョン] の項目を10回連続でタップすると、[開発者向けオプション] が表示されます。

<img width="200" alt="IMG_0055.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1115291/2f219f4b-fce3-5487-f98b-028234d8e2ca.jpeg"><img width="200" alt="IMG_0056.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1115291/04ae617a-1f16-2beb-9c6f-1d79e273d80a.jpeg"><img width="200" alt="IMG_0057.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1115291/7a56dec1-7a15-c7be-21e9-1457f79c0832.jpeg">

[開発者向けオプション] を開き [トークンを取得] をタップします。すると、トークンが表示されるのでコピーし PC に送って下さい。

<img width="200" alt="IMG_0058.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1115291/fd4cf50e-b2c3-c83b-9a7d-520b280428e8.jpeg"><img width="200" alt="IMG_0059.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1115291/24381e1e-5dbf-5c2d-6e98-ab500b45e754.jpeg">

#### デバイス ID の取得
続いて、SwitchBot デバイスのデバイス ID を取得していきます。以下のコマンドの "YourAccessToken" 部分を先程取得したトークンに置き換えて実行して下さい。
```bash:デバイス ID の取得
/usr/bin/curl -s -X GET "https://api.switch-bot.com/v1.0/devices"  -H "Authorization:YourAccessToken" |sed 's/{\"deviceId\"/\n{\"deviceId\"/g'
```
実行すると以下のようにデバイス ID が表示されます。
```console:Result
{"statusCode":100,"body":{"deviceList":[
{"deviceId":"C02CF2","deviceName":"CO2OFF","deviceType":"Bot","enableCloudService":true,"hubDeviceId":"F144D9"},
{"deviceId":"C27E36","deviceName":"温湿度計 07","deviceType":"Meter","enableCloudService":true,"hubDeviceId":"F144D9"},
{"deviceId":"C7E384","deviceName":"カーテン D3","deviceType":"Curtain","hubDeviceId":"000000","curtainDevicesIds":["E76E3C","C7E384"],"calibrate":true,"group":true,"master":false,"openDirection":"right"},
{"deviceId":"C9DFFF","deviceName":"CO2ON","deviceType":"Bot","enableCloudService":true,"hubDeviceId":"F144D9"},
{"deviceId":"D371D0","deviceName":"除湿機","deviceType":"Bot","enableCloudService":true,"hubDeviceId":"F144D9"},
{"deviceId":"E76E3C","deviceName":"カーテン CC","deviceType":"Curtain","enableCloudService":true,"hubDeviceId":"F144D9","curtainDevicesIds":["E76E3C","C7E384"],"calibrate":true,"group":true,"master":true,"openDirection":"left"},
{"deviceId":"EE93FD","deviceName":"リモートボタン 6A","deviceType":"Remote","enableCloudService":false,"hubDeviceId":"000000"},
{"deviceId":"F144D9","deviceName":"ハブミニ 7A","deviceType":"Hub Mini","hubDeviceId":"000000"}],"infraredRemoteList":[]},"message":"success"}
```
今回欲しいデバイス ID は "CO2ON" の "C9DFFF" と "CO2OFF" の "C02CF2" になります。メモしておきましょう。これで、SwichBot デバイスを API で制御する準備ができました。

#### SwitchBot デバイスの操作
続いて、SwitchBotAPI を実行します。ソースコードは以下の通りです。

```python:switchbot.py
import requests
import json
# 取得したトークンに置き換えて下さい。
ACCESS_TOKEN="YourAccessToken"
API_BASE_URL="https://api.switch-bot.com"

# Switch ON
def device_control1():
# 取得したデバイス ID に置き換えて下さい。
    DEVICEID="C9DFFF"
    headers = {
        'Content-Type': 'application/json; charset: utf8',
        'Authorization': ACCESS_TOKEN
    }
    url = API_BASE_URL + "/v1.0/devices/" + DEVICEID + "/commands"
    body = {
        "command":"turnOn",
        "parameter":"default",
        "commandType":"command"
    }
    ddd = json.dumps(body)
    res = requests.post(url, data=ddd, headers=headers)

# Switch OFF
def device_control2():
# 取得したデバイス ID に置き換えて下さい。
    DEVICEID="C02CF2"
    headers = {
        'Content-Type': 'application/json; charset: utf8',
        'Authorization': ACCESS_TOKEN
    }
    url = API_BASE_URL + "/v1.0/devices/" + DEVICEID + "/commands"
    body = {
        "command":"turnOn",
        "parameter":"default",
        "commandType":"command"
    }
    ddd = json.dumps(body)
    res = requests.post(url, data=ddd, headers=headers)

if __name__ == '__main__':
    device_control1()
    device_control2()
```
デバイスごとに設定するパラメータ（command, parameter, commandType）については、
SwitchBotAPI の [README](https://github.com/OpenWonderLabs/SwitchBotAPI#command-set-for-physical-devices) に記載されています。参考にしてみて下さい。実行してスイッチデバイスがきちんと動けば問題ありません。

### 本実装
スイッチデバイスが動くことが確認できたので、続いて室内の CO2 濃度に応じて自動で換気扇をつけるコードの実装に移ります。
```python:co2.py
#!/usr/bin/env python3
from CO2Meter import *
import time
import switchbot
import subprocess
import requests

# CO2-mini が認識されているデバイスの設定
try:
    sensor = CO2Meter("/dev/hidraw0")
except Exception as e:
    sensor = CO2Meter("/dev/hidraw1")
time.sleep(5)

def main():
# 換気扇を ON にしたかどうか
    triggered = False
# スイッチデバイスが反応しなかった時に LINE に通知する
    line = True
    count_on  = 0
    count_off = 0
# 起動時にとりあえず換気扇を OFF にする
    switchbot.device_control2()
    while True:
        try:
            data = sensor.get_data()
# CO2 濃度の測定エラーが発生したら、換気扇を OFF にし LINE に通知する
        except Exception as e:
            switchbot.device_control2()
            send_notify()
            quit()
        current = data["co2"]
# CO2 濃度がしきい値を超えたら換気扇を ON にする
        if current >= 1500 and not triggered:
            count_off = 0
            switchbot.device_control1()
            triggered = True
# スイッチデバイスが制御できなかった時の対策
        if current >= 1500 and triggered:
            cuount_on = count_on + 1
            if count_on > 2:
                switchbot.device_control1()
# CO2 濃度がしきい値を下回ったら換気扇を OFF にする
        if current <= 700 and triggered:
            count_on = 0
            switchbot.device_control2()
            triggered = False
            line = True
# スイッチデバイスが制御できなかった時の対策
        if current <= 600 and not triggered:
            count_off = count_off + 1
            if count_off > 2 and count_off < 4:
                switchbot.device_control2()
# スイッチデバイスに何らかのエラーが起きたことを通知
        if current >= 2000 and line:
            send_notify()
            line = False
        time.sleep(60)

# 無くてもいい
def send_notify():
# 自分の LINE のアクセストークンに置き換えて下さい
    LINE_TOKEN = "YourAccessToken"
    api_url = 'https://notify-api.line.me/api/notify'
    headers = {'Authorization': f'Bearer {LINE_TOKEN}'}
    message = "CO2 Service Stopped!"
    data = {'message': f'{message}'}
    requests.post(api_url,headers=headers,data=data)

main()
```
CO2 濃度が 1,500ppm を超えたら換気扇をオンにし、700ppm を下回ったら換気扇をオフにします。スイッチデバイスが何らかのエラーで動かないことがあったため、スイッチデバイスが制御できなかった時の対策を加えています。また、CO2 濃度が 2,000ppm を超えたらスイッチデバイスに何らかのエラー（電池切れなど）があったとし、LINE に通知する設定にしています。LINE のアクセストークンについては以下記事にまとめているので参考にして下さい。

https://qiita.com/Pumila/items/db71cf169748e0724d5a#line-notify-%E3%81%AE%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E7%99%BA%E8%A1%8C

運用した結果、室内の CO2 濃度は以下のようにコントロールできるようになりました。

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1115291/5b529402-23d2-67c8-e3ba-7cfe971d03a6.png)

## service への登録
常時稼働させておくために、サービスとして登録しておきましょう。登録するためには root 権限が必要になります。まずは、以下ディレクトリに移動してください。
```console
[root@localhost ~]# cd /etc/systemd/system/
```
その後、以下のコードを `co2.service` として保存して下さい。
```bash:co2.service
[Unit]
Description=co2

[Install]
WantedBy=multi-user.target

[Service]
User=root
# 実行するコードのディレクトリに置き換えて下さい
ExecStart=/root/co2meter/co2.py
Restart=always
```
最後にサーバ起動時にサービスも自動起動してくれるように設定します。
```console:サービスの自動起動
[root@localhost system]# systemctl start co2.service
[root@localhost system]# systemctl enable co2.service 
Created symlink /etc/systemd/system/multi-user.target.wants/co2.service → /etc/systemd/system/co2.service.
```
以下コマンドを実行して `Active: active (running)` と表示されていれば問題ありません。
```console:サービスの状態
[root@localhost system]# systemctl status co2
● co2.service - co2
   Loaded: loaded (/etc/systemd/system/co2.service; enabled; vendor preset: disabled)
   Active: active (running) since Sun 2023-02-19 19:53:55 JST; 1s ago
 Main PID: 6219 (python3)
    Tasks: 2 (limit: 23888)
   CGroup: /system.slice/co2.service
           └─6219 python3 /root/co2meter/co2.py

Feb 19 19:53:55 localhost.localdomain systemd[1]: Started co2.
```

## 24時間換気でいいのでは
建築基準法が改正され、2003年以降に建てられた物件には24時間換気システムの設置が義務付けられています。実際に24時間換気システムを利用した場合、 CO2 濃度は以下のように 1,200ppm あたりに維持されていることが分かります。わざわざお金をかけてまで SwitchBot で CO2 濃度を管理する必要はなさそうですね。どうしても24時間換気システムを使いたくない場合や、そもそも設置されていない場合、CO2 濃度を低水準（1,000ppm 以下など）で維持したい場合などを除き、本システムを利用する必要はなさそうですね。
![キャプチャ1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1115291/9da1bf5f-a3a1-90d1-3479-440f2df35ae9.png)

## 最後に
~~24時間換気システムで十分なのでは。~~ なんとなく CO2-mini を購入し CO2 濃度を監視していた時に、CO2 濃度に応じて自動で換気扇をつけるの、スマートでかっこいいんじゃないかとか思ってました（現に旧住居では24時間換気システムがありませんでした）。とりあえず今後は 1,200ppm をしきい値として、SwitchBot システムと24時間換気システムを併用して運用してみようかなと思います。

## 参考
[部屋のCO2濃度が上昇したら自動で換気扇をつける](https://miyasakura.hatenablog.com/entry/2018/08/13/120629)
[【SwitchBotAPI 使い方】PythonでSwitchBotやリモコン家電を操作する方法](https://chasuke.com/switchbotapi/)
[CO2センサを部屋につけてログを取る](https://qiita.com/sgrk/items/366beb3d08f792097f46)
[CO2濃度変化及び温熱環境が作業性と生理心理量に及ぼす影響](https://www.jstage.jst.go.jp/article/shasetaikai/2018.8/0/2018.8_169/_pdf)
[室内環境中における二酸化炭素の吸入曝露によるヒトへの影響](https://www.jstage.jst.go.jp/article/siej/21/2/21_113/_pdf/-char/en)