RaspberryPi
raspbian
co2
MH-Z19

窓を開けて新鮮な空気をいれよう!Raspberry Pi でCO2 濃度を測ろう

English version of this document is available here.

始めに

その会議が眠いのはテーマや議事運営が眠いからというのはもちろんそうなのかもしれませんが、二酸化炭素濃度が上がっていることも原因なのかもしれません。
CO2濃度が2500ppmをこえたあたりから急激に思考力が低下するなんて報告もありますが、人間のCO2排出量って結構すごくて、会議室に数人あつまっただけでかなりCO2濃度は高くなります。冬の閉め切った室内でちょっと灯油ストーブとかつけるとすぐに凄いCO2濃度になったりします。

建設的な会議を進めるために、まずは現状のCO2 濃度を把握してみませんか?

MH-Z19センサー

CO2センサーは non-dispersive infrared (NDIR) といって、赤外線の吸収率がCO2濃度によって変わることを利用して測るのだそうです、かっこいい。 こういうセンサーではスウェーデンの SenseAir 社の K-30 がポピュラーです。使っていらっしゃる方の報告がいくつもありますし、Raspberry Pi でつかうためのアプリケーショノート も通販屋さんから出てるので繋いですぐに使える手軽さと安心感が魅力ですが、安い通販で買ってもかなり高いです

なんか安いセンサーがないかなと探していて見つけたのがこちらです。送料込みで20$代は安いです。ちなみにポチった頃(先月末)よりさらに値段が下がってる気が...

センサー外観

届いたセンサーはあのずっしりとたくましい K-30 とちがってもの凄くちっちゃくて軽いです。開発元サイトをみると15g だそうです。本当に動くのか、ケースの中にちゃんと物が入っているのか心配になるレベルの軽さです ^^;;;
金属光沢がありますが、樹脂性のケースにメッキがしてあるようです

スクリーンショット 2015-12-22 19.02.07.png

接続

どうやって使うんだろうと思って mh-z19 と Raspberry とかで検索してもめぼしいものがでてきません。ますます不安になります。とはいえ、みつけたドキュメントを見ていると PWM(!) と UART のインターフェースがあるようなので、下図のように普通にRaspberry Pi のUART のGPIOと4線でつなぎます。

※2018.09.04 近況
これを書いた2015年末は検索しても本当に寂しい状況だったのですが今は随分賑やかになってるみたいで、ちょっと最近の状況とあってないですね ^^/
PWM にびっくりマークを付けたのはそれまで知らなくて「デジタルデータをわざわざ擬似アナログで送るの?」っていう驚きだったんですけど、個人的な感想なので蛇足だったです

mz-h19_ブレッドボード.jpg

事前準備(UARTを空ける)

以前、ToCoStick で Raspberry Pi 同士を SLIP で繋ぐにも書きましたが、Raspberry Pi は起動時に自動的に uart でコンソールを開いてくれている(のでシリアルでバッグができます)ので、uart が塞がっています。
そこで uart を空けるために、事前に下記のように /etc/inittab の末尾の、ttuUSB0 で tty を開く行をコメントアウトしてから起動する必用があります

/etc/inittab
#Spawn a getty on Raspberry Pi serial line
# T1:23:respawn:/sbin/getty -L ttyUSB0 115200 vt100 ※これをコメントアウト

jessie の補足

※2016.07.08 補足
この辺りが jessie で変わっていて、inittab で止めておかなくても systemctl で tty を止めたり再開したり出来るようになってました

sudo systemctl stop serial-getty@ttyAMA0.service
sudo systemctl start serial-getty@ttyAMA0.service

RaspberryPi 3 の補足

※2016.11.24 補足
Raspberry Pi 3 だと 下記2点変更が必要です

  • デフォルトで UART が無効(SoC が2個持ってる内の簡単な方が BT に map?)になっているので、raspi-config で UART を enable にするか、/boot/config.txtenable_uart=1 を追加するかして UART を有効にする スクリーンショット 2016-11-24 10.06.40.png スクリーンショット 2016-11-24 10.07.07.png スクリーンショット 2016-11-24 10.07.20.png

またはこんな感じ↓

スクリーンショット 2016-11-24 10.29.15.png

  • tty が ttyAMA0 じゃなくて ttyS0 になっているので、下記のように tty を開け閉めをし、
sudo systemctl stop serial-getty@ttyS0.service
sudo systemctl start serial-getty@ttyS0.service

/dev/ttyAMA0 ではなく /dev/ttyS0 に読み書きする

def mh_z19():
#    ser = serial.Serial('/dev/ttyAMA0',
    serial_dev = '/dev/ttyS0'
    ser = serial.Serial(serial_dev,
                        baudrate=9600,
                        bytesize=serial.EIGHTBITS,
                        parity=serial.PARITY_NONE,
                        stopbits=serial.STOPBITS_ONE,
                        timeout=1.0)

コード

そして Gas consentration reading のコマンドを投げます。Return Value の Byte2 * 256 + Byte3 が CO2 濃度になるとのことなので、下記のようにシリアルを開いてコマンドなげて結果を読むだけの簡単なコードであっさり動きました。マジックナンバーだらけのコードでどうもすみません ^^;;;

mh_z19.py
# http://eleparts.co.kr/data/design/product_file/SENSOR/gas/MH-Z19_CO2%20Manual%20V2.pdf
import serial
import time

def mh_z19():
  ser = serial.Serial('/dev/ttyAMA0',
                      baudrate=9600,
                      bytesize=serial.EIGHTBITS,
                      parity=serial.PARITY_NONE,
                      stopbits=serial.STOPBITS_ONE,
                      timeout=1.0)
  while 1:
    result=ser.write("\xff\x01\x86\x00\x00\x00\x00\x00\x79")
    s=ser.read(9)
    if s[0] == "\xff" and s[1] == "\x86":
      return {'co2': ord(s[2])*256 + ord(s[3])}
    break

if __name__ == '__main__':
  value = mh_z19()
  print "co2=", value["co2"]

※2016.2.12 遅ればせながらこちらにgistを御用意いたしました
※2018.9.13 pre-require モジュールのインストール、設定、MONITOR™への送信など、必要なものをまとめてこちらに用意しました
これまで用意してなくて大変申し訳ありませんでした
※2018.11.06 遅ればせながらパッケージを pypi にご用意いたしました。本当に必要なのはこれだと気がつくのに3年も掛かってしまい我ながら情けないです ^^;;;

jessie の補足

※2016.07.08 補足
上の補足の通り,jessie だと systemctl コマンドで tty を開け閉めできるので

import subprocess

stop_getty = 'sudo systemctl stop serial-getty@ttyAMA0.service'
start_getty = 'sudo systemctl start serial-getty@ttyAMA0.service'


if __name__ == '__main__':
  subprocess.call(stop_getty, stdout=subprocess.PIPE, shell=True)
  value = mh_z19()
  subprocess.call(start_getty, stdout=subprocess.PIPE, shell=True)

みたいに、UART 読む時だけ tty を閉めてしまうのでもいいのかもしれません

RaspberryPi 3 の補足

※2016.11.24 補足
起動時に RPi3 かどうかチェックして、使う tty を切り替える処理を入れたバージョンをgithubに用意いたしました
中で import してる RPi のモデルチェックモジュール getpimodel.py もgithubに用意してあります
これはこれで他にも使い所がありそうなので、別途 package を作ろうとおもってます(futur works)

getrpimodel は、他にもいろいろ使い所がありそうでしたので大変僭越ではございますがpypi のパッケージにさせていだだきました。pip でインストールしていただければ恐縮でございます

pip install getrpimodel

パッケージのソースはご参考までにこちらにご用意させていただきました

表示

測ったデータはサーバに POST して(mqttが本手なんでしょうけど 1883通してくれないネットワークが結構あるんですよね) Chart.js でグラフにすると(websocket が本手なんでしょうけど 9000通してくれないネットワークが沢山あるのでポーリングで更新)見やすくて便利です。
スクリーンショット 2015-12-22 17.53.21.png

ついでに温湿度も測って飽差も計算してつけてしまうと会議室だけじゃなくてビニールハウスのトマト栽培のCO2施肥管理でもつかえそうです

※2018.09.04 補足
こちらも、MONITORとしてサービスを公開しておりますのでよろしければお時間のある時にでも御覧いただけますことが叶えば至福の至りに存じます
使っていただけたりしようものならもう大感謝で言葉もないです!

謝辞

Qiita の Raspberry Pi アドベント、ぼーっとしてたら速攻で埋まってしまって登録できず、立て逃げみたいな事になってしまいまして誠に申し訳ありませんでした
登録された方の書かれてた記事がとても参考になり、ありがたく勉強させて頂きました事をこの場を借りてお礼もうしあげさせていただきます

future works

私の所に届いた個体はちょっと高めに出ている気がします。校正が必用みたいです
※2016.02.18 屋外で400ppmで校正しました。こちらもコマンド一発なので簡単でした
あと、時定数というか応答時間というかこれが結構長いです

※2018.09.05 訂正
応答時間が長いんじゃなくて、プレヒート中は正確な値がとれない、ですね。馬鹿な事をもうしまして申し訳ありませんでした。訂正させていただければ幸いです。訂正が遅れましたことも申し訳ありませんでした。

references