BME280(温度、湿度、気圧センサー)をRaspberryPiと接続し、読み出したデータをデータベース上(とりあえず慣れてるMySQL)に蓄積し、Grafanaで可視化するところまでの備忘録です。
データの取得を、Raspberry Pi と サーバのどちらからさせるか迷いました。しかし部屋に転がってたRaspberry Pi無印はとても非力です。なので、Raspberry Piは、BME280から測定値の読み出しのみ担当させ、サーバにデータベースへの登録をさせるように実装しました。
データのやりとりには、一番簡単なHTTPを使います。Raspberry PiはPortをListenし、接続が来たらセンサーからデータを読み取りHTTP風に送り返す事だけを行います。
RaspberryPi + BME280の接続
BME280というのは温度,湿度,気圧を測れるセンサーです。このチップが載ったボードが割と安価に売られています。スイッチサイエンスのページ
湿度測定が不要ならば、もっと安価なBMP280というチップもあります。しかし、折角買うのならば測定できるデータが多い方が楽しそうですよね。
さて、手元にあるBME280センサーボードは、3.3V, データの読み出しはI2C経由です。
RaspberryPiとBME280センサーボードの接続は特に悩むところはないと思います。最初に接続したジャンパーケーブルが不良品でRaspberryPi側からBME280が見えなくてしばし悩んだりしましたが、正常なジャンパーと交換すると問題無く認識されました。
センサーからデータを読み込みサンプルファイルが、スイッチサイエンスのgithubページに載っていますのでありがたく使わせていただきます。
※ サンプルコードはpython2.7用ですが、print文を修正すればpython3でも問題なく動きます。
RaspberryPi側で接続を待ち受ける機能の実装
非力なRaspberryPi無印にさせる仕事は以下の3点。
- TCPポートをListen
- リクエストがあったらBME280からデータを読み出す
- 読み出したデータをHTTPでレスポンス
リソースが有り余っているのならなにか軽めのHTTPdを起動するところですが、搭載メモリは512Mバイト。リソースは節約しなくちゃいけません。
こんな時はtcpserverかinetdでListenさせて、スクリプトでHTTPレスポンスを返答する感じですかね。今回はインストールされていたxinetdを使いました。
/etc/xinet.d/bme280
として、以下のファイルを設置。Listen Portは8081番にしました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ cat /etc/xinetd.d/bme280 service bme280 { type = unlisted id = bme280 socket_type = stream user = root bind = 0.0.0.0 port = 8081 wait = no protocol = tcp disable = no server = /root/bin/bme280.sh #server_args } |
ファイルを配置したらxinetdサービスをリスタートしておきましょう。
1 |
$ systemctl restart xinetd |
次はHTTP風レスポンスを返すスクリプトです。
スクリプト作成のポイントは、ヘッダにContent-Lengthを含める事です。これが無い場合、Clientは、いつまで接続を継続したらよいのかわからずデータを待ってしまいます。そのタイミングでサーバ側からコネクション切断を行うと、client側では Connection reset by peer といったエラーを吐いてしまいます。
それから echo の -n オプションも忘れがちなハマりポイントです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ cat /root/bin/bme280.sh #!/usr/bin/env bash CMD='/root/bin/bme280.py' RESULTS=`$CMD` LEN=$(echo -n "$RESULTS"|wc -c) echo -ne "HTTP/1.0 200 OK\r\n" echo -ne "Content-Type: Content-Type: text/plain\r\n" echo -ne "Content-Length: ${LEN}\r\n" echo -ne "\r\n" echo -ne $RESULTS echo -ne "\r\n" sleep 1 exit 0 |
スクリプトから呼び出している /root/bin/bme280.py は、スイッチサイエンスのサンプルの結果表示部分を修正したもので、気温、気圧、湿度をカンマで区切り1行で出力するようにしました。
これで準備は完了。念のため、意図したデータを返しているか確認します。
1 2 3 |
$ curl http://localhost:8081/ 25.22 , 996.69, 54.05 $ |
サーバ側でデータを取得するスクリプトの実装
サーバ側で動かすスクリプトに必要な機能は、以下の2つ。
- HTTPでRaspberryPiへ接続し、レスポンスを受け取る
- 受け取ったデータをデータベースへ保存する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#!/usr/bin/env python3 import sys import requests import mysql.connector as mydb def db_conn(): conn = mydb.connect( host='localhost', port='3306', user='xxxx', password='xxxx', database='room_env' ) return conn def insert_to_db(conn, val): cur = conn.cursor() sql = "INSERT INTO measure (temperature, pressure, humidity) values ('{}','{}','{}');".format(float(val[0]), f loat(val[1]), float(val[2])) cur.execute(sql) conn.commit() def main(): conn = db_conn() #url = 'http://192.168.0.198:8081/' url = 'http://192.168.0.229:8081/' r = requests.get(url) if r.status_code != 200: sys.exit() res = r.text.strip().split(',') insert_to_db(conn, res) if __name__ == "__main__": main() |
時系列データで、参照する際も単純なクエリーだけ。リレーショナルデータベースである必要はないのですが、既にサーバ上で稼働していて自分自身慣れているDBということでMySQLにデータを投入する事にしました。
と、その前にMySQLにデータベースとテーブルを作っておかなくてはなりません。ユーザも忘れずに。
1 2 3 4 5 6 7 8 9 10 11 |
CREATE DATABASE room_env; CREATE TABLE room_env.measure ( data_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, temperature DECIMAL(5,2), pressure DECIMAL(6,2), humidity DECIMAL(5,2) ); CREATE USER 'room'@'localhost' identified by 'xxxxx'; GRANT INSERT ON room_env.* TO 'room'@'localhost'; |
あとは、cronで毎分実行するように登録すれば完了です。
Grafanaで可視化する
Grafanaは様々なデータソースから時系列データを取り出し可視化してくれるツールです。今までもPrometheusで収集したノードのリソースを監視するダッシュボードとして利用してきました。
Grafanaは様々なデータソースを扱えます。もちろんMySQLも。ところでGrafanaがDBへ接続する際に、限定的な権限を持ったユーザで行うべきなので、Grafana用にMySQLのユーザを作成します。
1 2 |
CREATE USER 'grafana'@'localhost' identified by 'xxxxx'; GRANT SELECT ON room_env.* TO 'room'@'localhost'; |
必要な権限はSELECTだけなので、アクセスするデータベース内のテーブルに対してSELECT権限を付与しました。
MySQL側の準備が完了したので、GrafanaにMySQLのデータソースを設定します。
接続試験ができるので、問題が無い事を確認します。
次はいよいよダッシュボードを作っていきます。Create -> Dashboard で 空のパネルを配置したあとData source に MySQLを選択するとSQLを書くように促されます。
たとえば、気温データをプロットしたい場合のSQLはこんな感じ。
1 2 3 4 5 6 |
SELECT UNIX_TIMESTAMP(data_time) AS "time", temperature as "temperature" FROM measure WHERE $__timeFilter(data_time) ORDER BY data_time asc |
最初、気温と湿度を1枚のグラフにまとめようと考えていたのですが綺麗に収まりませんでした。2つのY軸を設定する事ができるようなのですが、うまくいかず・・・別々のグラフにして縦に並べることで妥協しました。
縦に並べると気温と湿度の相関がよくわかりますね。
RaspberryPiはオーバースペックではないのか?
RaspberryPi(無印)にBME280を接続して環境データ(気温、気圧、湿度)を収集し、そのデータをMySQLへ格納。格納したデータをGrafanaで可視化するところまで完成しました。
しばらく動かしていて特に不満や問題は感じませんでしたが、RaspberryPiの長所がこの環境データ収集の場合は短所になってしまうことに気づきました。
RaspberryPi上ではLinuxが稼働していて自由度がとても高いのが長所だと思っています。
その一方で、センサーの数値を読み出すだけの仕事に対して消費電力が大きめであること(無印はそれほど電力は使わないようですが)、突然の電源断でシステムの破損が発生する可能性があるという弱点は回避が難しいのです。
- TCP/IPで通信可能であること
- $ I^{2}C $経由でBME280とデータのやりとりが可能であること
- 突然の電源断に強い
ESP32という、この用途にぴったりなボードが部屋に転がっていました。
次はRaspberryPiをESP32で置き換えてみます。