知的好奇心 for IoT

IoT関連の知的好奇心を探求するブログです

ベランダの温湿度・気圧計をM5Atom Lite+ENV II(SHT30|BMP280)に更新した

これまで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日追記

最終的な対応についてはこちらの記事にまとめました。

 刷新したベランダの温湿度・気圧モニタリング設備f:id:IntellectualCuriosity:20210123131511j:plain

使ったもの(ハードウェア)

モバイルバッテリーの中には、消費電流が極端に少ないと電力供給を止めてしまう製品があるので、Deep Sleepを使ってマイコンを間欠動作させるときは注意する必要があります。

f:id:IntellectualCuriosity:20210123135033j:plain

ケースにラップを巻いて、逆さまにしてベランダの鉄柵にペタッと貼り付け

設置場所は柱の影になる隣のビル方面にある鉄柵で、直射日光が当たりにくい場所にしています。

小物入れの口は下に向けているとはいえ全開放状態なので、台風でも生き残ることができるのかかなり不安です。

 

ソフトウェア

構成

f:id:IntellectualCuriosity:20210123151613p:plain

構成図

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のWiFiSSIDで置き換えます"
#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アプリの設定

動作画面

f:id:IntellectualCuriosity:20210123174951j:plain

温度Gaugeの設定

f:id:IntellectualCuriosity:20210123180045j:plain

湿度Gaugeの設定

f:id:IntellectualCuriosity:20210123180606j:plain

気圧Labelの設定

f:id:IntellectualCuriosity:20210123181048p:plain

SuperChart(グラフ)の設定

f:id:IntellectualCuriosity:20210123182808j:plain

計測時刻表示用LCDの設定

f:id:IntellectualCuriosity:20210123184015j:plain

WebHookの設定

f:id:IntellectualCuriosity:20210123184844p:plain

グレイで塗りつぶしているところにはThingSpeakの"Write API Key"を埋め込みます。


ThingSpeakのチャンネル設定

f:id:IntellectualCuriosity:20210123194044p:plain

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が動くウェブサイトに貼り付けると、最新の温度・湿度・気圧・計測時刻を表示してくれます。

 

おわり