M5Stackで家とオフィスの二酸化炭素濃度を計測してみた

## はじめに

自分は以前から肩こりに悩まされてきました。あと、きちんと休息と睡眠をとってるにも関わらず、いつも決まった時間に集中が落ちてくるので、それも改善したいと考えてました。

もしかしたら環境に原因があるのではと思って文献などを調べてみると、部屋の二酸化炭素濃度が高まると肩こりや頭痛が起こりやすくなり、さらに一定量を超えると脳の働きが悪くなって眠気も出てくるということが分かりました。

二酸化炭素濃度が見えるデバイスを作って、家とオフィスの二酸化炭素濃度を可視化したら、それらの問題をまとめて解決できるかもと考えたので、試しに作ってみました。

## 用意するもの

二酸化炭素濃度が見えるデバイスを作るため、二酸化炭素センサ **MH-Z19B**とマイコンボード M5Stack を使いました。


- M5Stack Basic
  - スイッチサイエンスで購入。¥4,490
- MH-Z19B 0-10000ppm
  - AliExpress で購入。¥2,000
- プロトワイヤ
  - M5Stack に付属するものを使用
- 2.54mm ピッチタイプのピンヘッダ
- 二酸化炭素センサ MH-Z19B

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/b443ba17-246e-12a3-7783-944c668d69fc.png)

- プロトワイヤ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/bb4b1c66-1d47-9580-28b3-94cccd78c9e8.png)

- ピンヘッダ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/6c4cff2a-41c5-0bf0-cc94-ba472264eca3.png)

MH-Z19B は中国の鄭州煒盛電子科技有限公司という会社が販売してる二酸化炭素センサです。NDIR（non-dispersive infrared）式ガスセンサ※の原理を使用して、空気中の二酸化炭素の存在を検出します。
他社製の二酸化炭素センサに比べるとかなりお手頃な価格なので、買った時は値段なりかなと考えてましたが、全く問題なく使えました。


> ※NDIR（non-dispersive infrared）式ガスセンサー……対象ガス（MH-Z19Bの場合は二酸化炭素）がセンサーから放出された特定波長の赤外線を吸収する現象を利用してガス濃度を検知するセンサーです。

仕様は以下のとおり。

- NDIR（non-dispersive infrared）式ガスセンサ
- 二酸化炭素濃度測定範囲：0-2000ppm / 0-5000ppm / 0-10000ppm
- 測定精度 0〜5％
- 動作電圧：4.5V〜5.5V
- 予熱時間 3 分

詳しい仕様は公式サイトにあるデータシートを確認してください。

https://www.winsen-sensor.com/products/ndir-co2-sensor/mh-z19b.html

## 事前準備
### Ambient

今回作成するプロダクトでは、二酸化炭素濃度の推移を見るため、送ったデーターをリアルタイムでグラフ化してくれる Web サービス Ambient を使います。

アカウントをお持ちでない方は、こちらのページからアカウントを作成してください。

https://ambidata.io/

## 配線

配線を始める前に MH-Z19B の裏面にピンヘッダをはんだづけして、プロトワイヤを挿し込めるようにしてください。

### ピンヘッダはんだ付け後の MH-Z19B
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/1bd75b7a-f334-bebb-83d3-4a08013cc9cf.png)

ピンヘッダのはんだ付けが終わったら、M5Stack と MH-Z19B をプロトワイヤで下記のようにつなぎます。

MH-Z19 は複数のインターフェースをサポートしてますが、今回は UART で M5Stack と MH-Z19B をつないでます。

| **M5Stack** | **MH-Z19B** |
| ----------- | ----------- |
| TX2（P17）    | RX          |
| RX2（P16）    | TX          |
| 5V          | Vin         |
| GND         | GND         |


M5Stack と MH-Z19B をプロトワイヤでつないだ状態


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/4943592b-51ae-bcb7-654e-1d16ac043fd4.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/07282a96-fe83-6c1b-fa97-e197c852d2a8.png)


## ファームウェア

M5Stack にファームウェアを書き込む方法は複数あります。今回はオンラインから書き込める M5Cloud を使いました。開発言語は MicroPython を使ってます。

M5Cloud のセットアップについては、[M5Stack入門01](https://booth.pm/ja/items/1316190)の**1 章の環境構築**を参考にしてください。

M5Cloud に M5Stack を登録するのが済んだら、新しく Project を作成して M5Stack のファームウェアを書き換えていきましょう。

M5Cloud のページの左から 2 番目の Project を選択してください。

### M5Cloud のプロジェクト一覧画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/a23cf88d-e9a9-797c-850a-d9fcd2341427.png)


Project language を Python、Device type を M5Stack にしてプロジェクトを新規作成してください。

M5Cloud の Project 作成画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/8aea3554-feae-4ce1-acbd-b1df006eacf4.png)

Project を作成したら、ライブラリを配置していきます。
Python/MicroPython 用の Ambient ライブラリ ambient.py を以下のページからダウンロードして、Project フォルダ直下にアップロードしてください。

https://github.com/AmbientDataInc/ambient-python-lib


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/4d7402fc-dc4d-cff1-9ca2-81b2aa29216c.png)


main.py

```python
from m5stack import *
from machine import UART
import time
import ambient

# 起動時の時刻を変数に入れる
lastTime = time.time()

# Ambientの初期設定
am = ambient.Ambient("AmbientのチャンネルIDに書き換えてください", "Ambientのリードキーに書き換えてください")

# LCDの前景色と背景色を黒に設定
lcd.setColor(lcd.BLACK, lcd.BLACK)

# 背景色で画面をクリアする
lcd.clear()

# LCDのフォントスタイルを設定
lcd.font(lcd.FONT_Default)

# UARTクラスはシリアル通信プロトコルを実装しています
# このクラスを使用すると、シリアルインタフェースを使用してM5Stackに接続されたデバイス（今回はMH-Z19）と通信できます
# txとrxにはそれぞれM5StackのGPIOピン番号を指定します
uart = UART(2, tx=17, rx=16)

# MH-Z19Bのデータシートに記載された設定を参考にして、通信方法を設定します
uart.init(9600, bits=8, parity=None, stop=1)

def getPPM():
    global uart
    
    # ガス濃度を読み込むのに使うコマンドをバイト配列で用意して、書き込みます
    b = bytearray(b'\xff\x01\x86\x00\x00\x00\x00\x00\x79')
    uart.write(b)
    
    # cに9バイトまでバイト配列を読み込みます
    c = bytearray(9)
    uart.readinto(c, 9)
    
    # 返ってきたバイト配列から二酸化炭素濃度の値を取り出します
    ppm = c[2]*256+c[3]
    
    return ppm

def getVentilationStatus(ppm):
    
    if ppm <= 410:
        status = "Waiting..."
    elif ppm <= 450:
        # 350～450ppm 過剰な換気(外気:330～400ppm)
        status = "Excess"
    elif ppm <= 700:
        # 450～700ppm 理想的な換気レベル
        status = "Good"
    elif ppm <= 1000:
        # 700～1000ppm 換気が不十分（室内では1000ppm以下に抑えることとされている）
        status = "Notice"
    elif ppm <= 1500:
         # 1000～1500ppm 悪い室内空気環境（学校環境では1500ppm以下が望ましいとされいる）
        status = "Caution"
    elif ppm <= 5000:
        # 1500～5000ppm これ以上の環境で労働をしてはいけない（労働安全基準法では、5000ppmが限度）
        status = "Warning"
    elif ppm <= 6000:
        # 5000ppm以上 疲労集中力の欠如
        status = "Danger"
    else:
        status = "Waiting..."

    return status


def savePPMToAmbient(ppm):
    global lastTime

    # 現在時刻を取得
    currentTime = time.time()

    # Ambientにデータ送ってから60秒以上経過していたら、再度データ送信を行う
    if 60 < (currentTime - lastTime):
        # MH-Z19Bは起動直後は正常な値を返さないので、正常な値が返ってきてない場合はログに残さない
        if (410 <= ppm) and (ppm <= 6000):
            
            # Ambient初期設定時に設定したチャンネルのd1に二酸化炭素濃度の数値を送信
            r = am.send({'d1': ppm})
            print(r.status_code)
            r.close()

            # Ambientにデータを送った最終時間を記録
            lastTime = currentTime

def displayPPM(ppm):
    # 二酸化炭素濃度をLCDに表示する
    lcd.font(lcd.FONT_DejaVu24)
    lcd.text(10,100, str(ppm) + " ppm", lcd.WHITE)

def displayVentilationStatus(ventilationStatus):
    lcd.font(lcd.FONT_DejaVu24)
    lcd.text(10,200, ventilationStatus, lcd.WHITE)

while True:
    # 二酸化炭素濃度を取得する
    ppm = getPPM()
    
    # 二酸化炭素濃度をLCDに表示
    displayPPM(ppm)
    
    # 換気ステータスを取得
    ventilationStatus = getVentilationStatus(ppm)
    
    # 換気ステータスをLCDに表示
    displayVentilationStatus(ventilationStatus)
    
    # Ambientにppmの数値を保存する
    savePPMToAmbient(ppm)
    
    # 指定の秒数 sleepする。ここでは10秒sleepさせてる
    time.sleep(10)
    
    # 背景色で画面をクリアする
    lcd.clear()
```


Ambient の設定の部分は、Ambient のサイトで自身のチャンネルを作って、その設定に書き換えてください。

## 完成

中央に二酸化炭素濃度（PPM = parts per million）を表示させてます。下段を見れば、その濃度がどのくらい良いかが分かるようになってます。
二酸化炭素濃度の数値は数秒おきに最新のものを画面に表示していて、Ambient には 60 秒毎にその数値を送ってます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/097af330-3a5c-f11b-eb8e-ac06e70bd39d.png)


## 注意点
- MH-Z19B は起動後に予熱しないと正しい数値が返ってきません。予熱には 30 秒〜3 分ほどかかるようです。正確な数値が出るまで、ゆっくり待ちましょう。

## オフィスの二酸化炭素濃度計測結果

窓のない高層ビル内なので高いと考えてましたが、予想に反して低い数値でした。グラフの数字は 30 分間の平均値を出してます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/316c417f-e0fe-2412-c82e-678a23bb1240.png)


朝 11:30 で 810ppm、お昼休みの時間が一番低くて 757ppm。
16:30 頃がピークでしたが、それでも室内の合格レベルである 1000ppm 以内でした。
人の数と連動して ppm が上がってるので、概ね正しい数値が出てると判断して良さそうです。

## 自宅の二酸化炭素濃度計測結果

人が少ないのでオフィスより低いと予想してましたが、想像以上に悪かったです。

帰宅時は 1600ppm（悪い室内空気環境、換気が不十分）、帰宅後数時間で 2000ppm（眠気を感じる人が出てくる濃度）まで上がってました。体感でも 2000ppm を超えたくらいから眠気出ました。毎日夜 22〜23 時頃に集中力が切れやすくなるのに納得がいきました。
10 分間換気を行って 600ppm まで落としたら眠気が吹き飛んだので、今後は冬場でも意識して換気を行っていこうと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/9ee8004f-84e2-3cef-5f9b-6a5ca938231a.png)

## 最後に
M5Stack で二酸化炭素濃度を計測する方法について紹介しました。

数値を計測して適切な頻度で換気を行うことで、肩こりや集中力の低下を改善できるかもしれませんので、ぜひ二酸化炭素計測装置を作って色々と試してみてください。