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フォルダに投げれば設置完了です。

#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呼び出すとリセットされなくなるのかは謎です。







最近のコメント