Raspberry PiとESP32とMicroPythonで自宅の環境をモニタリングする

この記事は[FOLIO アドベントカレンダー 2024](https://adventar.org/calendars/10315)の6日目です。


# 前書き

今年6月に引っ越したので、「見せてもらおうか、高断熱住宅の実力とやらを!」ということで、環境モニタリングシステムを作りたいなと思った。せっかくなので単なる温度計ではなく、Raspberry Piを使ってデータベースに記録をとれるようにしたら面白いかな、と思い実行。

筆者は埋め込み/IoTど素人。同じようなことをやっている方は山ほどいるかと思いますが、今年のアドベントカレンダーは書くネタが何もなく……。素人なので記事の正確さには期待できません。メモ代わりなので、チュートリアルを求めている人には役に立たないかも。



# 構成

* SBC: Raspberry Pi 4 (のちに[Seeed XIAO ESP32C3](https://akizukidenshi.com/catalog/g/g117454/)に移行。後述)
* 温湿度センサー: [BME680](https://akizukidenshi.com/catalog/g/g114469/)
* CO2センサー: [MH-Z19C](https://akizukidenshi.com/catalog/g/g116142/)
* においセンサー: [TGS8100](https://akizukidenshi.com/catalog/g/g115562/)

全部秋月で買った。この手のものだとBME280が有名だが、なぜかガスも測定できるBME680のほうが安かったのでこちらを使った。

奇しくも全部インターフェースがばらばらだったので勉強になりました。



# ハードウェア設計(ラズパイバージョン)

![IMG_0035.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/231669/a04ffe60-7900-18b6-fd7e-23c743e903c0.jpeg)

* BME680は抵抗でプルアップ
* ブレッドボード上に直接ぶっさす
* ADCはMCP3425を使用…するはずだったのだが、結局使わないままESP32へ移行してしまった
* MH-Z19C、ブレッドボードに刺さりそうな感じの見た目のくせにまさかのピッチが合わなかった。悲しい
* ネット上ではMH-Z19Cを手動でキャリブレーションしている記事があったが、ドキュメントを読む限り自動でやってくれるっぽいので不要だと思いやっていない

# ハードウェアの動作確認

まず、Raspberry PiでI2Cを有効化する。GUIでやる記事ばかりだったのでわざわざディスプレイとキーボードを接続したが、`raspi-config`経由でも設定できた。SSHからできるこっちのほうが楽だと思う。

`i2cdetect -y 1`でI2Cが動いていることを確認。



# ソフトウェア設計

Raspberry Pi上でサーバー兼デーモンを立てる。デーモンスレッドが定期的にセンサーからデータを取得し、PostgreSQLに突っ込む。サーバーはHTTP経由でデータ読み取りのAPI&ちょっとしたUIを提供する。

* 使用言語は業務でも使っているScala。最近は趣味プロでもすっかりScalaばかりで書くようになってしまった。SBTにイライラすることを除けばとても良い言語である
* サーバーは素のJetty。最近はでかいフレームワークを使うより逆に心が落ち着くようになってきた
* GPIOにはpi4jを使用
* JSONはcirce
* DBはPostgreSQL。業務はMySQL(というかAurora)だが絶対PostgreSQLのほうが優れていると思う
* FlywayでDBスキーマ管理
* エフェクトライブラリーに:cat:。以前からZIOを愛そうと頑張っているのだが、意味不明なコンパイルエラーが多すぎて諦めている
* STTP
* フロントエンドにはTypeScriptとReact。ScalaJSを使おうとしたがいろいろ大変すぎてやめた
* グラフにhighcharts
* https://github.com/minebreaker/sandbox-pi

## 実装上嵌った点

* Pi4Jのバックエンドには`pigpio`を手動で指定する必要がある
  * ルートが必要。ファイルを使ったバックエンドはルート不要だが、こちらのほうがパフォーマンスが悪いらしい
  * リソースを閉じる必要があるのか謎。放置してもシャットダウン時に自動で開放していたので結局そちらにお任せしてしまった…
* Javaはビット操作が苦手
  * `ByteBuf`を使って頑張る必要がある
  * 特に嵌ったのはshortのシフト。intにキャストされた後シフトされるので、2バイトの範囲で操作が行われるわけではない
* UARTを使うためには`sudo systemctl stop serial-getty@serial0.service`を打つ必要がある。正直このコマンドが何なのかわかっていない。詳しい人教えてください

# Discordへの通知

デーモン上でセンサーの値を取得するついでに、取得した値が閾値以上/以下の場合、Discordに通知を送るようにした。ウェブフックURLを作ってHTTP POSTするだけなので楽ちん。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/231669/55c66402-c78d-533b-6dc7-4cf19b80fc88.png)

ただセンサーの精度が微妙なのか、湿度計(100均製)では問題がないときでも警告をしまくるという問題が発生した。また、家にいないときにも通知が来てうざい。あまりにもうるさいので現在は無効化している。


# 完成品

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/231669/a5f35fd2-5c07-0bee-5a4d-0d581d5b8ed1.png)

床暖房のおかげで冬でも超温かい。高断熱高気密万歳。

(でも正直こんなに暖かくはない。温度計読みより4度ほど高く表示されている気がする。逆に湿度は低すぎる)


# なぜRaspberry PiのGPIOを使うのをやめたのか?

直接的な動機は、他の部屋にもセンサーを設置したくなったから。昨今の円安でラズパイはとても気軽に買える値段ではなくなったため、部屋ごとに買うのは避けたい。そもそもオーバースペック気味でもある。

また、センサーの位置がラズパイと同じ場所になるのも避けたかった。我が家のラズパイはルーターなどとともに壁付けのボックスに押し込んであるのだが、センサーの位置としては理想的ではない。

そこで、安価に購入できるESP32で環境測定を行い、Raspberry Pi上で動かすサーバーにデータを送信して蓄積するという形にした。



# 設計(ESP32バージョン)

![IMG_20241127_212358.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/231669/b993f107-7704-2619-afb8-d61ff8180f86.jpeg)

* ラズパイとほぼ同じ
* I2CのプルアップはESP32内蔵のものを使用。安定していないからよくないという意見も見かけたが、まあ真剣なプロジェクトではないのでOK
* ADCもESP32内蔵を使用。こちらも正確でないらしいが別に正確な値は必要ないので許容
* エラーを表示するためにLEDを1つ追加した。Pythonで例外が発生するとチカチカする


# ソフトウェア

ESP32上で動作するJVMを探したが、あまりメジャーなものは見つけられなかった。この記事の作者はPythonが嫌いである。だがArduino言語を学ぶなんてもっと嫌だ。仕方がないのでMicroPythonを使うことにした。細かい処理はしたくないので、センサーの素の値を直接サーバーへPOSTするようにする。

まずesptoolをインストールする。

```
pip install esptool
esptool.py -h  # .pyが必要
esptool.py chip_info
```

ブートスイッチを押しながらUSBを接続すれば、ブートモードに入る。この状態でファームウェアを更新する。MicroPythonのサイトからESP32C3向けのファームを落としておく。

```
esptool.py --port /dev/cu.SLAB_USBtoUART erase_flash
esptool.py --chip esp32c3 --baud 921600 --before default_reset --after hard_reset --no-stub  write_flash --flash_mode dio --flash_freq 80m 0x0 ESP32_GENERIC_C3-20240222-v1.22.2.bin
```

上気のコマンドはほぼ[Wiki](https://wiki.seeedstudio.com/xiao_esp32c3_with_micropython/)に従ったもの。`port`はなくても1つしか見つからなければ自動で選んでくれた。`-z`を指定してなかったりちょくちょくなぜこのコマンドになっているのかよくわからない部分が多い…。

ちなみに、Windows上でCOMポートを調べるには、デバイスマネージャーから「ポート(COMとLTP)」から「USBシリアルデバイス(COM3)」みたいに表示されるのを確認すればよい。


## 開発ツールたち

続いて埋め込み向けのIDEであるThonnyをインストールする。正直あまり使い勝手は良くなかったので、VSCode+ampyを主に使っていた。ただしこちらもまとめて複数ファイルをアップロードできなかったりいろいろと満足のいかない部分は多かった。Thonnyは主にデバッグ用途でスタックトレースを見るために使用。世間の人はどうしているのか気になる。



# まとめ・所感・未解決問題

* たまにBME680の値がおかしくなる
  * 多分どこかのフラグを正しく読んでいないのだと思うが確認する気力はない
* WiFiのパスワードを安全に扱う方法はないんだろうか
  * Pythonソースに直書きは不安であるが、あまりよい解決策が思いつかない
* 昨今はツールやICも進歩していて、自分のような初心者でも簡単に使えるのだなとしみじみ感心した。みなさんもお手すきの時にやってみてはいかがだろうか