M5StackでCO2モニターを作って、データ可視化と換気を促す通知するものを Azure IoT Central で作ってみた

# はじめに
以前にM5StackでCO2モニターを作って、直接Teamsに通知するものを作ったのですが、
[『M5StackでCO2モニターを作って、Teams通知で換気を促すものを作ってみた』](https://qiita.com/yukimatsu/items/71f2d77ac3cee85b3358)

* CO2濃度が高くなると通知が来るけど、その傾向が良く分からない
* テレワークが増えてくると、いま職場の状況がどうなのかが遠隔で分からない
* 換気を促す通知は来てるけど、いまいち上手く活用できてない（されてない）気がする

という辺りが少し課題かなと思っていました。
なので、今回は上記を解決する為、**「M5Stackで作ったCO2モニター」** と **「Azure IoT Central」** を連携させることで、

* データがグラフ化されて、なんとなく傾向が見えてくる
* リモートからでも、データが見ることができる
* グラフ／通知の両方が見えるようになり、もう少し活用できる（かも）

を実現したいと思います。
以前は、TeamsのWebhookを直接連携させていましたが、今回はAzure IoT Central経由になります。

# 作ったもの
* **M5Stackを使ったCO2モニター**
デバイスは、以前に作成したものと同様です（内部にCO2センサーが入っています）。
細かい作り方などは、以下の記事に記載してあります。
[『M5StackでCO2モニターを作って、Teams通知で換気を促すものを作ってみた』](https://qiita.com/yukimatsu/items/71f2d77ac3cee85b3358)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475457/85386de4-cf94-164c-4bc9-7db0b90c74e6.png)

* **ダッシュボード（Azure IoT Central）**
データが可視化されることで、直感的に分かりやすくなりました。
過去からの変化も分かるので、どういったタイミングで換気しようとか、どういったときに濃度が高くなりやすいのかの傾向も分かってくるので、換気するという具体的なアクションに繋がりやすくなると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475457/80017fba-63a2-6100-4ce1-39e6cce955c2.png)

* **Teamsへの通知**
換気を促すメッセージとともに、CO2濃度も通知されます。
一定の閾値以上になったら、通知が来るので、よりアクションを取りやすくなると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475457/369a5b57-f894-9f1e-31f8-9f59cfcfa741.png)

# 部材や使用するサービスなど
* [M5Stack](https://www.switch-science.com/catalog/3648/)
* [TVOC/eCO2 ガスセンサユニット（SGP30）](https://www.switch-science.com/catalog/6619/)
* [Base15 産業用プロト基板モジュール](https://www.switch-science.com/catalog/6545/)
* [UI Flow](https://flow.m5stack.com/)
* [Azure IoT Central](https://azure.microsoft.com/ja-jp/services/iot-central/)
* [Azure Logic Apps](https://docs.microsoft.com/ja-jp/azure/logic-apps/logic-apps-overview)
* [Teams](https://www.microsoft.com/ja-jp/microsoft-teams/group-chat-software)

# 1. M5Stack側
実際の開発は、Blockly で行っています。
主な処理は、以下の通りです。

* Azure IoT Central への接続処理
* タイマーで、定期的に Azure IoT Central にデータを投げる（コストを意識してあまり頻繁にならないように）
* M5Stack側は、データ更新頻度は短めで、一定値以上はCO2濃度の表示を色を変える（応答性を重視）

**M5Stackの画面**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475457/23d45a33-46b5-30c2-d60b-5b6b8c1bc627.png)

**Blockly側の実装**
実際には動かすときは、SSID/PASSWORD、スコープID／デバイスID／主キーの設定をして下さい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475457/5fb8fe68-03ee-9793-a9b7-34e5735c01fc.png)


当初は、タイマーの処理内でセンサからデータを取得するような作りとしていましたが、I2C BUS のエラーが結構な頻度で発生する為、このような作りにしてあります。通常処理とタイマー処理の両方でセンサにアクセスするのが良くない気がしていまsう。


**Python側の実装**
Blockly を UI Flow 上で、python に変換したものです。
実際には動かすときは、スコープID／デバイスID／主キーの設定をして下さい。

```python
from m5stack import *
from m5ui import *
from uiflow import *
import json

from IoTcloud.Azure import IoT_Central
import time
import unit

setScreenColor(0x222222)
tvoc_0 = unit.get(unit.TVOC, unit.PORTA)


threshold = None
TVOC = None
eco2 = None
H2 = None
Ethanol = None


CO2Monitor = M5TextBox(84, 16, "CO2 Monitor", lcd.FONT_DejaVu24, 0xFFFFFF, rotate=0)
ppm = M5TextBox(246, 200, "ppm", lcd.FONT_DejaVu24, 0xFFFFFF, rotate=0)
value = M5TextBox(92, 84, "000", lcd.FONT_DejaVu72, 0xFFFFFF, rotate=0)



@timerSch.event('timerSendToIoTCentral')
def ttimerSendToIoTCentral():
  global threshold, TVOC, eco2, H2, Ethanol
  azure.publish_D2C_message(str((json.dumps(({'TVOC':TVOC,'eCO2':eco2,'H2':H2,'Ethanol':Ethanol})))))
  pass


threshold = 1000
azure = IoT_Central(scope_id='', device_id='', device_key='')
azure.start()
timerSch.run('timerSendToIoTCentral', 30000, 0x00)
while True:
  TVOC = tvoc_0.TVOC
  eco2 = tvoc_0.eCO2
  H2 = tvoc_0.H2
  Ethanol = tvoc_0.Ethanol
  if eco2 < threshold:
    value.setColor(0xffffff)
  else:
    value.setColor(0xff0000)
  value.setText(str(eco2))
  wait(3)
  wait_ms(2)
```

# 2. Azure IoT Central側
詳しくは以下の記事を参考にして貰えるとAzure IoT Central のサービスを作成するところや設定方法などを詳しく説明しています。今回は、その差分になりそうなデバイステンプレート生成のところのみを記載します。
[M5StickCとAzure IoT Centralを連携してデータを可視化する② ～Azure IoT Central環境構築編～](https://qiita.com/yukimatsu/items/e35542e16792e7818720)

**[デバイス テンプレート] の定義**
今回は、センサで取得できる値を取得できるようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475457/fac9b504-a4f5-2081-70a8-1f2bb7f4715e.png)

# 3. Azure IoT Centralのダッシュボード
ダッシュボードは各自が見やすいように作ると良いと思いますが、今回は以下のようにしてあります。

* 現在のCO2濃度を数値で表示：「いま」の状態が一目で分かるように
* 過去１時間のCO2濃度をグラフで表示：直近１時間の傾向を見ることで換気の有無を判断しやすいように
* 過去１２時間の全センサをグラフで表示：その他のセンサ値を含めて傾向が分析しやすいように

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475457/be032f18-ead3-0499-3d97-0acce46da1c0.png)

# 4. Azure IoT Centralからの通知機能
[規則] を作成し、通知の条件やアクションを設定します。
今回は、[Webhook]を利用して、[Azure Logic Apps] にHTTPトリガーを発生させて、[Azure Logic Apps] 側で Teams へ通知するようにします。

**[規則] の作成**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475457/08015e4c-4ab8-c000-69f6-c32fda825df4.png)

**通知条件の設定**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475457/80509146-3ac1-426d-3e60-e7877a6c195c.png)

**条件は例はこちら**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475457/9020723d-e772-34ad-5901-f871947bc015.png)

Webhookを選択し、[Azure Logic Apps] のHTTPトリガーのURLを記載
実際の実装では、Azure Logic Apps 側の設定が終わった後に、HTTPトリガーのURLが発行されるので、Azure Logic Apps 側の設置が終わった後に、こちらの設定を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475457/51600442-0655-429b-c165-162b70e6c529.png)

# 5. Azure Logic Apps側
フローは、以下の通りです。
受信するJSON形式のデータをParseして、後の処理で利用しやすくしています。
HTTPトリガーより渡される [本文] に対して解析していますが、以下に参考のスキーマを記載しておきます。通知の処理では、CO2濃度の部分のみ抜き出して、Teams で通知する内容に入れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475457/790fe88a-2c02-53d7-2da9-b882ca3055d3.png)

**JSON解析用のスキーマ**
スキーマを作る際は、一旦、JSONをそのままTeamsに送信して、そのペイロードからスキーマを生成しました。
以下の公式ドキュメントも参考になるかもしれませんが、デバイステンプレートの作り方に依存するみたいなので、各自の環境で実際にペイロードを受信してみると良いと思います。
https://docs.microsoft.com/ja-jp/azure/iot-central/core/howto-configure-rules#payload

```json
{
    "properties": {
        "action": {
            "properties": {
                "displayName": {
                    "type": "string"
                },
                "id": {
                    "type": "string"
                },
                "rules": {
                    "items": {
                        "type": "string"
                    },
                    "type": "array"
                },
                "type": {
                    "type": "string"
                },
                "url": {
                    "type": "string"
                }
            },
            "type": "object"
        },
        "application": {
            "properties": {
                "displayName": {
                    "type": "string"
                },
                "host": {
                    "type": "string"
                },
                "id": {
                    "type": "string"
                },
                "subdomain": {
                    "type": "string"
                }
            },
            "type": "object"
        },
        "device": {
            "properties": {
                "approved": {
                    "type": "boolean"
                },
                "displayName": {
                    "type": "string"
                },
                "etag": {
                    "type": "string"
                },
                "id": {
                    "type": "string"
                },
                "instanceOf": {
                    "type": "string"
                },
                "provisioned": {
                    "type": "boolean"
                },
                "simulated": {
                    "type": "boolean"
                },
                "telemetry": {
                    "properties": {
                        "eCO2": {
                            "properties": {
                                "avg": {
                                    "type": "number"
                                }
                            },
                            "type": "object"
                        }
                    },
                    "type": "object"
                }
            },
            "type": "object"
        },
        "rule": {
            "properties": {
                "displayName": {
                    "type": "string"
                },
                "id": {
                    "type": "string"
                }
            },
            "type": "object"
        },
        "timestamp": {
            "type": "string"
        }
    },
    "type": "object"
}
```

**Teams のメッセージ**
こんな感じで換気を促すメッセージにしています。また、実際のCO2濃度も記載することで、換気の必要性も値を見て判断できます。Parseされたデータを扱える為、実装も簡単になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/475457/98a46110-2dc9-0c99-3294-139d595da9d8.png)

# まとめ
時系列データが可視化されることで、どういったタイミングでCO2濃度が上がりやすいのかなども分かりやすくなりました。人が近くに来るとすぐに数値が上がったり、設置する場所によってもそれなりに傾向があることが見えたりしました。
前に、換気を促す通知だけを行うものを作って運用していましたが、時系列的な変化が分かることで納得感を持ってアクションに繋がっているような気がします。
だんだん「換気も自動化しようよ」みたいな気分にもなりますが、温度・湿度を含めて、感じ方は人それぞれなので、そこの最後のアクションはまだまだ人間の判断の余地を残しておきたいところです。

# おまけ
本記事は、以下のキャンペーンに参加しています。

https://qiita.com/official-events/b7811ce1542be54e6efe