PythonでI2Cセンサ(CCS811/BME280)を読んで色々表示してみた(Matplotlib, Dash)


CCS811/BME280 on Raspberry PI 4

CCS811とBME280をI2Cで接続して、値を読む。

  • CCS811: 等価CO2濃度と総揮発性ガス濃度を測定できる
  • BME280: 温度、湿度、気圧を測定できる

コードは以下にある。
https://github.com/nv-h/i2c_env_sensors

ついでにopenweathermapの予報データの表示と、気象庁のアメダスの観測データも同時に表示するようにしてみた。(2020/12/01更新)

Check I2C connection

i2c-toolsが必要。これで接続を確認する。
CCS811は0x5B(default)、BME280は0x77(default)にあるはず。うまく見えると以下のようになる。

$ sudo apt install i2c-tools
$ ls /dev/i2c*
/dev/i2c-1
$ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- 5b -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- 77

接続を確認できたら、ハードウェアIDを確認してちゃんと動いているか確認。

#!/bin/bash

CCS811="0x5b"
BME280="0x77"
I2C_BUS="1"

# CCS811
# https://cdn.sparkfun.com/assets/learn_tutorials/1/4/3/CCS811_Datasheet-DS000459.pdf

ccs811_hw_id=$(i2cget -y ${I2C_BUS} ${CCS811} 0x20)
ccs811_hw_version=$(i2cget -y ${I2C_BUS} ${CCS811} 0x21)
# ccs811_fw_boot_version=$(i2cget -y ${I2C_BUS} ${CCS811} 0x23 w)
# ccs811_fw_app_version=$(i2cget -y ${I2C_BUS} ${CCS811} 0x24 w)

echo "CCS811 HW_ID: ${ccs811_hw_id} HW Version ${ccs811_hw_version}"
# echo "FW Boot Version ${ccs811_fw_boot_version}"
# echo "FW app  Version ${ccs811_fw_app_version}"

# BME280
# https://cdn.sparkfun.com/assets/learn_tutorials/4/1/9/BST-BME280_DS001-10.pdf

bme280_hw_id=$(i2cget -y ${I2C_BUS} ${BME280} 0xd0)
echo "BME280 HW_ID: ${bme280_hw_id}"

Read the sensor values

I2Cのデバイスは/dev/i2c-1みたいな感じの場所にいるが、アクセスするのに管理者権限が必要なので、自分をi2cというグループに登録しておく。
こうしておくと管理者権限無しでI2Cデバイスにアクセスできる。

sudo usermod -aG i2c $USER

こんかいはPythonを使いたいのでPythonからI2Cデバイスにアクセスできるsmbus2をインストールする。

pip install smbus2

このリポジトリのコードを使うと以下のようにセンサの値を取得できる。

from bme280 import BME280

bme280 = BME280()
p, t, h = bme280.get()
print(f"{p:7.2f} hPa, {t:6.2f} C, {h:5.2f} %")

from ccs811 import CCS811

ccs811 = CCS811()
ccs811.compensate(h, t) # if needed
voc, co2 = ccs811.get() # May need to exec several times to get correct values
print(f"TVOC:{voc:4d} ppb, eCO2:{co2:4d} ppm")

Example

Command Line

example.pyにコマンドラインの例を示す。
このコードは、標準出力に1秒毎にセンサで取得した値を吐く。
たまにOSErrorになるが、気にしていない。あと、CO2濃度はしばらく経たないとちゃんとした値にならない。
データシートによると20分位は安定しないのと、48時間のエージングが必要らしい。

#!/usr/bin/env python

from time import sleep
from bme280 import BME280
from ccs811 import CCS811

ccs811 = CCS811()
bme280 = BME280()
p, t, h = bme280.get()
ccs811.compensate(h, t)

while(True):
    try:
        p, t, h = bme280.get()
        voc, co2 = ccs811.get()
        print(f"{p:7.2f} hPa, {t:6.2f} C, {h:5.2f} %, TVOC:{voc:4d} ppb, eCO2:{co2:4d} ppm")
        sleep(1)
    except OSError:
        # i2c bus somtimes cannot access
        continue
    except KeyboardInterrupt:
        break

GUI (matplotlib)

example_gui.pyにmatplotlibでのGUI表示の例を示す。
このコードでは0.2秒毎にグラフを更新している。特に意味はないが高速に動かすと動いている感が得られる。
(PythonでGUI画面とリアルタイムグラフ表示するの成果を流用)

  • 必要なパッケージ: pip install matplotlib numpy

GUI (dash)

save_csv.pyで1分ごとにデータ取得して、dash_from_csv.pyでそれをWeb UIで表示する。
表示にはdashを使用した。

  • 必要なパッケージ: pip install dash pandas numpy
  • 注意:
    • 温度: 基板の発熱があるのでセンサの値から-2℃しているが、環境によって変わると思う。
    • Time-zone: Asia/Tokyo (UTC -9 hours)に固定している。

無限にCSVファイルに追記することになるが、読み込みより表示のほうが大幅に負荷が大きいため表示データを1周間ごとに制限している。
さらにデータの個数は間引きして表示している。(1分間隔なんて高頻度に取得するなってことだけど、デモだとたくさん待たないといけないので...)

  • 知見
    • dashはコードを変更すると動的に表示が更新されて楽しい
    • Rasberry PIでdashは重すぎるかと思ったけど割とサクサク
    • pandas初めて触ったけどめっちゃ便利なので活用したい

もしOpenWeather MapのAPI keyを持っている場合、以下のようにAPI keyをセットすると予報データも同時に表示される。(このキャプチャ画像は古くて、今は最初に貼ったようにアメダスデータの表示と予報の表示にも対応している)

echo "API_key = {API key}" > ./openweathermap/api_key.py