ESP32を使った環境測定システム(気温・湿度・気圧)


はじめに

気温・湿度・気圧センサーのBME280と気温・湿度センサーのAHT25を使って,室内と室外の環境をモニターするシステムを作りました. センサーで測定したデータはESP32で定期的にサーバーに送られて,Webブラウザで表示します.

昔購入したBME280が部品箱で埃をかぶっていましたが,ESP32が電池で思いのほか長時間駆動できることが分かったので作ってみました. 室内と室外の両方で測定したいと思いましたが,昔送料込みで$3.2だったBME280が値上がりしていたので,気圧は測定できないけれど廉価なAHT25を買い足しました. このような環境モニター(weather station)はESP32の工作例としてありがちであまり興味がなかったのですが,実際に使ってみるとエアコンの効き具合や就寝時の気温の変化などが分かっておもしろいと思いました.

センサーから収集したデータを蓄積して表示するにはサーバーが必要になります. いくつかそれに適したクラウドサービスもあるようですが,柔軟性などを考慮して自宅で24時間稼働しているLinuxサーバーを使うことにしました. Pythonで書いたCGIスクリプトでデータの収集と表示を行います.

センサー部

前述のとおりマイコンにはESP32を使用し,センサーにはBME280とAHT25を使用しました. マイコンもセンサーも動作可能電圧が広いので(BME280は1.8-3.6V,AHT25は2.2-5.5V),単三電池2本で直接駆動します. 回路はとても簡単なので,回路図は描かずに下記の配線対応表を用意しました. ESPはESP32で,SENはBME280とAHT25を表します(どちらのセンサーもVCC,GND,SCL,SDAの4つの端子があります). PINは,ファームウェア書き込み時にテストクリップを挟むために短いリード線を取り付けます. センサーのVCCは直接電源につながずに,ESP32のIO23から供給します. 配線表には書かれていませんが,ESP32のVCCには0.1μFのパスコンを付け,ENは10kΩでプルアップします. またESP32のピーク電流に対応するために,電池ボックスには1000μFの電解コンデンサーを取り付けます.

ESP:IO23 SEN:VCC
ESP:GND SEN:GND
ESP:IO22 SEN:SCL
ESP:IO21 SEN:SDA
ESP:GND GND
ESP:3V3 VCC
ESP:EN PIN:EN
ESP:IO0 PIN:IO0
ESP:TXD PIN:TXD
ESP:RXD PIN:RXD

野外に置くセンサーは防水ケースに入れました. センサーの部分だけ穴を開けて,防水のためにまわりをホットメルトで固めました.

ケースに入れて完成した状態を下に示します.

屋外用(BME280使用)
屋内用(AHT25使用)

ファームウェアはArduino IDEで開発しました. 起動すると,まずセンサーで計測を行い,Wi-Fiで結果をサーバーに送って,10分間ディープスリープします(スリープ時間は設定用モードで変更可能). バッテリー電圧も,以前行ったのと同様に非公開関数のrom_phy_get_vdd33()を使って測定します. 10分毎に動作するようにしたので,単三電池2本だと1年も持たないかもしれません. 気温が低い屋外で使用した場合アルカリ電池の容量は3割ほど低下するようなので,動作時間はさらに短くなるかもしれません.

ESP32は磁気センサーを内蔵しています. 磁石を近づけて本機をリセットすると設定用モードで起動します. SoftAPでアクセスポイントになるため,SSIDを"ESP32",パスワードを"12345678"としてスマホやタブレットで接続して,ブラウザで"http://192.168.0.1/"にアクセスします. するとフォーム画面が表示されて,任意のkeyとvalueをESP32に登録することができます. 本機では計測したデータをサーバーにアップロードするために,下記のkeyを設定する必要があります.

key 内容
WAIT 測定間隔(単位は分で実数)
UURL アップロード用CGIのURL(例. "http://example.com/cgi-bin/wst.cgi")
SSID Wi-Fi接続のSSID
PASS Wi-Fi接続のパスワード
CHAN WiFi接続のチャンネル

ESP32が搭載しているULPコプロセッサにも興味があり,スリープ中にULPでセンサーを動かすことも考えましたが,今回はサンプリングの頻度は必要ないので使いませんでした.

サーバー部

センサーで収集したデータを蓄積したり,またそれらをWebブラウザで表示するためのCGIをPythonで書きました. 1つのCGIスクリプトで,収集と表示のどちらにも対応します. つまり,POSTメソッドでデータが送信された場合はデータの蓄積を行い,それ以外の場合は計測結果をグラフにしてWebページを返します. Apache2では,デフォルトでCGIスクリプトは/usr/bin/cgi-bin/に置くことになっているので,このwst.cgiというスクリプトをそこに置きます. さらに,wst.lck (ロックファイル),wst.log (ログファイル),wst.dat (データファイル)という3つの空ファイルをtouchコマンドなどで同じディレクトリに作成して,CGIスクリプトから変更できるように所有者をwww-dataにしておきます. ログファイルには過去の全ての計測結果が含まれており,データファイルにはWebブラウザにグラフを表示するのに必要なだけの計測結果が含まれています. これらの2つのファイルに複数のプロセスから同時に書き込みを行わないために,ロックファイルを使って排他制御を行います.

グラフの描画にはmatplotlibを使いましたが,かなり遅くてページの表示に4秒程度かかります. 表示する情報は,1日と1週間における気温・湿度・気圧・電池電圧の変化で,合計8つのグラフを生成します. これらのグラフはbase64エンコーディングでHTMLページに埋め込むため,1回のHTTPアクセスでページ描画が完了します.

電池動作のための最適化

数ヶ月使用しましたが,電池が1ヶ月も持たずに何度か交換が必要になりました. そこで電池の持ちをよくするためにいくつかの改善を行いました. ESP32は無線使用時の消費電流が大きく,センサーの消費電流などはそれに比べれば無視できるほど少ないので,WiFiの使用時間をいかに減らすかが重要になります. 色々試した結果,以下の変更により大きな効果が見られました:

現状で,無線LANの接続に200ms,HTTP転送に500ms程度で,WiFiの使用時間は700ms程度となっています. HTTPの転送が一番のボトルネックなので色々と試しましたが改善できませんでした. データの転送自体ではなく,WiFiClient::connect()内でネットワークソケットを作りselect()するあたりで400ms程度の時間がかかっているようです. なお,時間がかかるのはWiFi接続後の最初のHTTPセッションだけで,複数回HTTPによる転送を行っても2回目以降は30ms程度しかかかりません.

これ以上電池の持ちをよくするには,手っ取り早いのはデータの送信間隔を10分よりも長くすることです. DHCPサーバーやDNSサーバーを使わない場合も試しましたが,大きな効果は見られませんでした. 今回は全て自宅内のサーバーと通信していますが,外部サーバーとの通信だとさらに遅延が大きくなる可能性があります.

表示例

Webブラウザでの表示例

上のスクリーンショットは,Webブラウザで測定結果を表示した例です. 測定したのは冬場ですが,エアコンを消して寝ると屋内の気温が指数関数的減衰をして,湿度は逆に増加していく様子が分かります. 屋内の測定は,センサーを置く場所によっても結果がかなり変わります. サーキュレーターの置き場所を変えるとその効果が分かり役に立ちました. 屋外のセンサーは,雨が降り始めると湿度が急に上がります. 2つのセンサーを同じ場所に置いた場合は,気温の差は最大で0.2ポイントでとても近い値が得られましたが,湿度は2ポイント程度の差が生じました.

ファームウェア・CGIスクリプト


[Back]
2021-12 製作
2021-12-24 ページ作成
T. Nakagawa