ESP32で NTP時計+温湿度モニター+ZabbixSender を作った (AHT10, SSD1306 OLED)
秒針付きNTP時計と、AHT10センサーで取得した温湿度をOLED SSD1306 ディスプレイに表示するやつを作りました。
また、取得した温湿度はZabbixにも送りつけます。
接続
OLEDディスプレイの裏にあるチップ抵抗のジャンパの位置を変えると、I2Cのアドレスをデフォルトの0x3Cから0x3Dに変えることができます。小さくて地味に難しかった。
ESP32は21番がSDA, 22番がSCLです。OLEDもAHT10も3.3Vに繋ぎました。
全て並列に繋げばOKです
Zabbixサーバーの設定
ホストの作成
Configuration -> Hosts から 右上の Create Host をクリック
Host name は ESP32_clock_and_temp で、Groups は Templates/Modules にしました。
アイテムの作成
作成したホストのitemsをクリック
こんな感じに温度と湿度のアイテムを作ります
あとはZabbixのダッシュボードでグラフを表示させればOK
プログラム
汚いプログラム。半分ぐらいコピペとGPT-4oです。
使うライブラリはincludeのとこ見てください。ESP32ZabbixSender以外はPIOのLibrariesから検索すると出てきます。
ESP32ZabbixSenderはこれ。ただしなぜか名前がESP8266のままなので、ESP8266ZabbixSender.cpp, ESP8266ZabbixSender.hをESP32ZabbixSender.cpp, ESP32ZabbixSender.hにリネームする必要がありました。
PIOなら、それらをlibフォルダに投げれば設置完了です。
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 | #include <Arduino.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include <WiFi.h> #include <NTPClient.h> #include <WiFiUdp.h> #include <TimeLib.h> #include <Adafruit_AHTX0.h> #include <ESP32ZabbixSender.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SERVERADDR 192, 168, 100, 11 // Zabbix server Address #define ZABBIXPORT 10051 // Zabbix erver Port #define ZABBIXAGHOST "ESP32_clock_and_temp" // Zabbix item's host name // Create display object Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); Adafruit_SSD1306 display2(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); //Create AHT10 object Adafruit_AHTX0 aht; // WiFi credentials const char* ssid = "SSID"; // Replace with your SSID const char* password = "Pass"; // Replace with your Password // NTP Client setup WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, "pool.ntp.org", 32400, 1000); // JST offset 9時間. 最小1000msで更新, ただしtimeClient.update()で呼び出さないと更新はされない unsigned long lastSyncTime = 0; const unsigned long SyncTimeInterval = 1024000; // 1024s = 17m4s int NTP_disable_sleep = 0; int NTP_error_count = 0; // Temp && Hum get interval unsigned long lastGetTemp = 0; const unsigned long GetTempInterval = 30000; //30秒が前提になってるので変えないで // Zabbix ESP32ZabbixSender zSender; int tempCount = 0; int Zabbix_disable_sleep = 0; int Zabbix_error_count = 0; void setup() { // SSD1306 init Serial.begin(115200); display.begin(SSD1306_SWITCHCAPVCC, 0x3C); display.clearDisplay(); Serial.println("Display1 initialized"); display2.begin(SSD1306_SWITCHCAPVCC, 0x3D); display2.clearDisplay(); Serial.println("Display2 initialized"); // Connect to WiFi WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi..."); } Serial.println("Connected to WiFi"); // Start NTP client timeClient.begin(); timeClient.update(); Serial.println("NTP client started"); // AHT10 init if (!aht.begin()) { Serial.println("Failed to initialize AHT10 sensor!"); // esp_restart(); // 初期化失敗したらリスタートすれば行けるかと思ったけど, USBを物理的に繋ぎ直さないとダメだった } // Zabbix init zSender.Init(IPAddress(SERVERADDR), ZABBIXPORT, ZABBIXAGHOST); } void loop() { unsigned long currentTime = millis(); // ここから NTP同期 if (currentTime - lastSyncTime >= SyncTimeInterval - 10000){ // Wifi使う10秒前からスリープ解除(しないと失敗する) NTP_disable_sleep = 1; } if (currentTime - lastSyncTime >= SyncTimeInterval){ if(timeClient.update()){ Serial.println("Success to sync"); lastSyncTime = millis(); NTP_disable_sleep = 0; NTP_error_count = 0 ; } else{ Serial.println("failed to sync"); // よく失敗する. Wifi再接続するとたぶん成功する WiFi.disconnect(); // 同期処理らしい WiFi.begin(ssid, password); // 非同期だけど30秒待てば多分大丈夫 lastSyncTime = currentTime - SyncTimeInterval + 30000; // 30秒後にリトライ NTP_error_count++ ; } } // ここまで NTP同期 // ここから 時刻表示 unsigned long epochTime = timeClient.getEpochTime(); setTime(epochTime); // Set the TimeLib time to NTP time // Fetch and print current time details int yearValue = year(); int monthValue = month(); int dayValue = day(); int hourValue = hour(); int minuteValue = minute(); int secondValue = second(); int dayOfWeek = weekday(); // 1=Sunday, 2=Monday, ..., 7=Saturday // Create an array for the abbreviated day names const char* days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; String dayString = days[dayOfWeek - 1]; // Get the correct day string (adjust index) int reiwa_year = yearValue - 2018; // Clear display before writing new data display.clearDisplay(); // Set text color to white display.setTextColor(SSD1306_WHITE); // Set text color to white // First line display.setTextSize(2); display.setCursor(0, 0); display.printf("%04d/%02d/%02d", yearValue, monthValue, dayValue); // Second line display.setTextSize(4); display.setCursor(0, 20); display.printf("%02d:%02d", hourValue, minuteValue); // Third line display.setTextSize(2); display.setCursor(0, 50); display.printf("%02d %s R%d", secondValue, dayString, reiwa_year); // Update display display.display(); // ここまで 時刻表示 // ここから 温湿度表示 if (currentTime - lastGetTemp >= GetTempInterval) { // Get value by sensor sensors_event_t humidity, temp; aht.getEvent(&humidity, &temp); float temperature = temp.temperature; float humidity_value = humidity.relative_humidity; display2.clearDisplay(); display2.setTextColor(SSD1306_WHITE); display2.setTextSize(4); display2.setCursor(0, 0); display2.printf("%.1f", temperature); display2.setTextSize(2); display2.printf(" "); display2.setTextSize(3); display2.printf("C"); display2.setTextSize(4); display2.setCursor(0, 32); display2.printf("%.1f", humidity_value); display2.setTextSize(2); display2.printf(" "); display2.setTextSize(3); display2.printf("%%"); // NTPとZabbixのエラーカウントを右下にちっちゃく表示 display2.setTextSize(1); display2.setCursor(96, 56); display2.printf("%d %d", NTP_error_count, Zabbix_error_count); display2.display(); lastGetTemp = currentTime; tempCount++; // ここまで 温湿度表示 // ここから Zabbixにデータ送信 if (tempCount == 3){ // 気温湿度センサーから値を取得して画面更新するループが4回周るごとにZabbixにデータを送りつける. つまり2分に1回 zSender.ClearItem(); zSender.AddItem("temp", temperature); zSender.AddItem("hum", humidity_value); if (zSender.Send() == EXIT_SUCCESS) { tempCount = 0; Zabbix_disable_sleep = 0; Zabbix_error_count = 0 ; }else{ WiFi.disconnect(); WiFi.begin(ssid, password); // 例によって、失敗したらWifi再接続 tempCount = 2; // カウントを2に戻すことで30秒後に再試行 Zabbix_error_count++ ; } } // ここまで Zabbixにデータ送信 } if (tempCount == 2){ Zabbix_disable_sleep++ ; // データ送信10秒前にスリープ解除するためのやつ 後に処理書いてる } delay(50); //sleepするにしても最低限のdelayを入れないとウオッチドッグにリセットされる // やけに消費電力が大きかった(常時0.5Wぐらい)のでdelayではなくライトスリープ使う // 何も考えずに1秒スリープすると秒針が飛ぶので、処理時間を考慮してスリープ時間を決定する long sleepDuration = 1000 - (millis() - currentTime); // スリープ時間が負になったらやばいので if (sleepDuration > 0) { if (NTP_disable_sleep == 1 || Zabbix_disable_sleep >= 20){ // 10秒前にスリープ解除 delay(sleepDuration); // Serial.println("delay"); // デバッグ用 } else{ esp_sleep_enable_timer_wakeup(sleepDuration * 1000); // micro sec 単位らしい esp_light_sleep_start(); // Serial.println("sleep"); // デバッグ用 } } else{ // Serial.println("no sleep"); // デバッグ用 } } |
・時計のモニターは1秒で更新 (秒針動かすため)
・NTPサーバーと同期する時間は1024秒 (この秒数は変えてOK)
・温湿度は30秒ごとに取得 & モニター更新 (この秒数は変えないで)
・Zabbixサーバーに2分に1回送信
・delayじゃなくてlight sleepさせる(消費電力がかなり減った)
・AHT10の初期化はよく失敗する。リセットコマンド入れても無駄で、USBを物理的に差しなおさないとダメだった
・Wifi使う直前にsleep入ってるとこういう変なエラーが出て失敗するので、10秒前のループからsleepじゃなくてdelayに変更。
[WiFiUdp.cpp:172] beginPacket(): could not get host from dns: 11
[WiFiUdp.cpp:185] endPacket(): could not send data: 118
更に失敗したときはWifiを再接続して30秒後に再試行します。
・loop()内のどこかで1回はdelay(50); (最低限は1らしいけど効かなかったので50にした) を呼び出さないとウォッチドッグタイマ(WDT)にリセットされる
こんな出力でリセットがかかります
rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
参考
https://lang-ship.com/blog/work/esp32-freertos-l03-multitask
ESP32には2コアあって、コア名PRO_CPUは無線の処理担当でWDT有効、APP_CPUはloop()関数担当でWDT無効らしい。
ウォッチドッグタイマのリセット条件はdelay()関数を1以上で呼び出すこと。
なぜWDT無効なはずのloop()関数側でdelay呼び出すとリセットされなくなるのかは謎です。
最近のコメント