これまでESP8266ベースでBME280やSHT31などのセンサーを使ってベランダの気温や湿度をBlynkとThing Speakでモニタリングしていましたが、高コスパなESP32ベースのM5Atom Lite+ENV II(SHT30|BMP280)に更新しました。
最近は寒いせいかモバイルバッテリーのヘタレが原因なのか、Deep Sleepを使って30分に1回の計測間隔にしていても数日で動かなくなっていたため、モバイルバッテリーも5,000mAhの大容量なものに変えて、ケースも刷新しています。
※2021年1月28日追記
バッテリーを変えても2日程度で止まってしまう現象が発生したため、原因が他にありそうです。しばらく部屋で動かして原因を調査します。
※2021年1月30日追記
2日程度で止まってしまう原因が判明しました。
WiFiとBlynkの接続にBlynk.begin()
を使っていましたが、このコードはWiFiルーターやBlynkサーバーに繋がらないと永遠にリトライします。
そして、何らかの問題でずっと繋がらない状態が発生していました。
この状態に陥るとWiFiの通信処理をずっと行うためにバッテリーの消耗が激しくなり、バッテリー切れを起こします。
この問題への対処として、Blynk.begin()
の代わりにWiFiの接続にはWiFi.begin()
、Blynkの接続にはBlynk.connect()
を使用してそれぞれをタイムアウトに対応させ、タイムアウト時にESP.restart()
でESP32をリセットするようにしました。
※2021年5月9日追記
最終的な対応についてはこちらの記事にまとめました。
使ったもの(ハードウェア)
- M5Stack ATOM Lite 968円(税込)
- M5Stack用環境センサユニット ver.2(ENV II)572円(税込)
- HIDISC HD-MBTC5000GFWH 秋葉原の店頭でバルク品を500円(税込)で入手
- ダイソーで売っていたマグネット式小物入れ
モバイルバッテリーの中には、消費電流が極端に少ないと電力供給を止めてしまう製品があるので、Deep Sleepを使ってマイコンを間欠動作させるときは注意する必要があります。
設置場所は柱の影になる隣のビル方面にある鉄柵で、直射日光が当たりにくい場所にしています。
小物入れの口は下に向けているとはいえ全開放状態なので、台風でも生き残ることができるのかかなり不安です。
ソフトウェア
構成
ATOM Lite
Arduinoを使ってプログラミングをしました。
プログラムの特徴
- Deep Sleepを用いて間欠動作することで消費電力を抑制
- NTPを用いた正確な時間管理
- Deep Sleep中も変数の値を保持するRTC_DATA_ATTRの利用
- BlynkのWebHookを用いたThinSpeakへのデータ送信
- WiFi接続時のタイムアウトによるESP32のリセット ※2021年1月30日追加
- Blynk接続時のタイムアウトによるESP32のリセット ※2021年1月30日追加
開発環境
使用プラットフォームパッケージ(ボードマネージャー)
- esp32 by Espressif Systems バージョン1.0.5-rc4
使用ライブラリ(ライブラリマネージャー)
- Adafruit SHT31 Library by Adafruit バージョン2.0.0
- Adafruit BMP280 Library by Adafruit バージョン2.1.0
- Time by Michael Margolls バージョン1.6.0
- Blynk by Volodymyr Shymanskyy バージョンバージョン0.6.1
スケッチ ※2021年1月30日更新 WiFiとBlynkの接続問題対応版
#include <Adafruit_SHT31.h> #include <Adafruit_BMP280.h> #define BLYNK_PRINT Serial #include <WiFi.h> #include <BlynkSimpleEsp32.h> #define WIFI_SSID "2.4GHzのWiFiのSSIDで置き換えます" #define WIFI_PASS "WiFiのパスワードで置き換えます" #define AUTH_TOKEN "BlynkのAuth Tokenで置き換えます" #define MESURE_PERIOD 30 // 計測間隔(分単位) // グローバル変数 time_t now = 0; // 現在の時間 RTC_DATA_ATTR time_t last_time = 0; // 前回の計測時間(UNIXタイム) // センサー計測値の送信処理 void sendSensor() { Adafruit_SHT31 sht31 = Adafruit_SHT31(); // SHT3x用オブジェクト Adafruit_BMP280 bmp; // BMP280用オブジェクト float temperature, humidity, pressure = NAN; // 温度、湿度、気圧の変数 // 計測処理 sht31.begin(0x44); // SHT3xの初期化 bmp.begin(0x76); // BMP280の初期化 while (isnan(temperature) || isnan(humidity) || isnan(pressure)) { // 変数に値が入るまでループする delay(100); temperature = sht31.readTemperature(); // 温度の取得 humidity = sht31.readHumidity(); // 湿度の取得 pressure = bmp.readPressure() / 100.0; // 気圧の取得 } // Blynkへの送信処理 Blynk.config(AUTH_TOKEN, BLYNK_DEFAULT_DOMAIN, BLYNK_DEFAULT_PORT); // Blynk接続設定 if (Blynk.connect(10000) != true ) { // Blynkサーバーへの接続(10秒のタイムアウト) Serial.println("\n========== Blynk connecting timeout! ==========\n"); ESP.restart(); // リセット } Blynk.virtualWrite(V1, temperature); // 温度を送信 Blynk.virtualWrite(V2, humidity); // 湿度を送信 Blynk.virtualWrite(V3, pressure); // 気圧を送信 Blynk.virtualWrite(V5, temperature, humidity, pressure); // WebHookデータを送信 struct tm *t; t = localtime(&now); char dateTime[17]; sprintf(dateTime, "%04d/%02d/%02d %02d:%02d", t->tm_year+1900, t->tm_mon+1, t->tm_mday, t->tm_hour, t->tm_min); WidgetLCD lcd(V4); lcd.clear(); lcd.print(0, 0, dateTime); // LCDに計測時刻を送信 // ログ出力 Serial.printf("Temperature : %4.1fC\n", temperature); Serial.printf("Hteumidity : %4.1f%%\n", humidity); Serial.printf("Pressure : %4.1fhPa\n", pressure); Serial.printf("Time : %s\n\n", dateTime); delay(10000); // Blynkに送信し終えるまでの待ち時間(10秒) } // WiFiの接続処理 void initWiFi() { WiFi.mode(WIFI_STA); Serial.print("\nConnecting to " WIFI_SSID); WiFi.begin(WIFI_SSID, WIFI_PASS); // WiFiに接続 int i = 1; while (WiFi.status() != WL_CONNECTED) { // WiFiが繋がるまでループ if (i > 20) { // 10秒のタイムアウト Serial.println("\n========== WiFi connecting timeout! ==========\n"); ESP.restart(); // リセット } delay(500); // 500ミリ秒待つ Serial.print("."); // ログに「.」を表示 i++; } Serial.println("\nWiFi connected"); // ログに「WiFi connected」を表示 Serial.print("IP address: "); // ログにIPアドレスを表示 Serial.println(WiFi.localIP()); } // 時間の初期化処理 void initTime() { configTzTime("JST-9", "ntp.nict.jp"); // NTPの初期化 while (now < 1546268400L) { // 現在時刻が2019-01-01 00:00:00以降になるまでループ delay(500); // 500ミリ秒待つ now = time(nullptr); // 現在時刻の取得 } if (last_time == 0) { // 初回のとき(1回もディープスリープしていないとき) last_time = now - MESURE_PERIOD * 60; // 現在時刻から計測間隔を引いて前回の時刻にセット } } void setup() { Serial.begin(115200); Wire.begin(26, 32); // SDA:26、SCL:32でI2Cを初期化 initWiFi(); // WiFiの接続処理 initTime(); // 時間の初期化処理 // ディープスリープから起きる時間の計算 time_t wake_time = last_time + MESURE_PERIOD * 60; // 起きる時刻 if (now >= wake_time) { // 現在時刻が起きる時刻より大きいとき sendSensor(); // センサー計測値の送信処理 last_time = wake_time; // 前回の時刻に起きる時刻をセット wake_time = last_time + MESURE_PERIOD * 60; // 次に起きる時刻を計算 } else { Serial.println("It's early."); // 起きる時刻が早かったとき } // ディープスリープ処理 now = time(nullptr); // 現在時刻の取得 time_t diff = wake_time - now; // 次に起きる時刻と現在時刻の差を計算 esp_sleep_enable_timer_wakeup(diff * 1000000UL); // 差をタイマーにセット Serial.printf("Go to sleep for %d seconds.\n\n", diff); // ログに差を出力 Serial.flush(); // ログの出力を待つ esp_deep_sleep_start(); // ディープスリープを実行 } void loop() { }
Blynkアプリの設定
動作画面
温度Gaugeの設定
湿度Gaugeの設定
気圧Labelの設定
SuperChart(グラフ)の設定
計測時刻表示用LCDの設定
WebHookの設定
グレイで塗りつぶしているところにはThingSpeakの"Write API Key"を埋め込みます。
ThingSpeakのチャンネル設定
WebHookの設定に合わせて、Field1を温度、Field2を湿度、Field3を気圧に設定しています。
ThingSpeak Read APIの利用
ThingSpeak Read APIを利用すると、サーバーサイドプログラムを使わずにフロントエンド(ブラウザ)だけでThingSpeakからデーターを取得することができます。
このブログのサイドバーで表示している「ベランダの値(SHT30|BMP280)」はこのAPIを利用して表示しています。
<div id="veranda_time">時刻取得中...</div> <div id="veranda_temperature">温度取得中...</div> <div id="veranda_humidity">湿度取得中...</div> <div id="veranda_pressure">気圧...</div> <script type="text/javascript">// <![CDATA[ getVerandaData(); var ret_interval_getRoomData = setInterval(getVerandaData, 60000); function visibilityChangeHandlerGetVerandaData() { if (document.hidden) { clearInterval(ret_interval_getRoomData); } } document.addEventListener('visibilitychange', visibilityChangeHandlerGetVerandaData, false); function getVerandaData() { var readAPIKey = "ThingSpeakのRead API Keyで置き換えます"; var channelID = "ThingSpeakのChannel IDで置き換えます"; var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status === 200) { var json = JSON.parse(this.responseText); var t = parseFloat(json.feeds[0].field1); var h = parseFloat(json.feeds[0].field2); var p = parseFloat(json.feeds[0].field3); var dt = new Date(json.feeds[0].created_at); var str = dt.toLocaleString(); document.getElementById("veranda_temperature").innerHTML = " 温度:" + t.toFixed(1) + "℃"; document.getElementById("veranda_humidity").innerHTML = " 湿度:" + h.toFixed(1) + "%"; document.getElementById("veranda_pressure").innerHTML = " 気圧:" + p.toFixed(0) + "hPa"; document.getElementById("veranda_time").innerHTML = str.slice(0,-3) + "現在"; } } } xhr.open("GET", "https://api.thingspeak.com/channels/" + channelID + "/feeds.json?api_key=" + readAPIKey + "&results=1", true); xhr.send(); } // ]]></script>
このソースコードをJavaScriptが動くウェブサイトに貼り付けると、最新の温度・湿度・気圧・計測時刻を表示してくれます。
おわり