Hallo zusammen,
ich habe ein Projekt mit einem ESP32 dev kit V2 gestartet. Teil des Projektes sind eine RTC, ein 4 Relais Board und ein 1,3 zoll Oled Bildschirm. Leider funktioniert der OLED nach einigen Tagen nicht mehr bis ich den ESP resettet habe. Ich arbeite bereits mit Watchdogs jedoch ändert das nichts. Das Projekt muss dauerhaft laufen ohne den Resetbutton drücken zu müssen. Ich würde mich freuen wenn sich jemand von euch mit hoher Kompetenz im Bereich Arduino den Sketch anschauen könnte und ihn korrigiert. Bitte Nachricht an mich. Über alles weitere können wir dann sprechen.
LG Justin
Stell den Sketch hier rein, dann schaut bestimmt jemand drauf.
Nur Beschreibung bringt nichts, musst schon dein Sketch zeigen.
Das ist Absicht und sollte auch so gemacht werden.
Gehe in Deinen Post, indem Du unten auf den Blaidtift klickst.
Dann markierst Du den gesamten Code und klickst oben auf <code>
Speichern - fertig
#include <RTClib.h>
#include <Preferences.h>
#include <WiFi.h>
#include <WebServer.h>
#include <U8g2lib.h>
#include "esp_task_wdt.h"
#include <Wire.h>
TwoWire OLED_I2C = TwoWire(0); // Bus für Display
TwoWire RTC_I2C = TwoWire(1); // Bus für RTC
static bool allowDisplay = false; //
// --- OLED / I2C Absicherung ---
unsigned long oledLastSend = 0;
const unsigned long oledTimeout = 50; // ms, maximale Blockierzeit
bool oledError = false;
// --- Tages-Neustart Flag ---
bool dailyRebootDone = false; // verhindert mehrfachen Tages-Neustart
// -------------------------
// Trigger-Anzeige
// -------------------------
bool showTrigger = false;
unsigned long triggerDisplayStart = 0;
char triggerText[20] = "";
// -------------------------
// Preferences
// -------------------------
Preferences preferences;
// -------------------------
// RTC auf separatem I2C-Bus
// -------------------------
RTC_DS3231 rtc;
// -------------------------
// OLED Display
// -------------------------
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 32, 33);
static unsigned long lastDisplayTime = 0;
static bool displayOn = true;
// -------------------------
// Relais-Pins
// -------------------------
const int relayPins[3] = {25, 26, 27};
// -------------------------
// Taster-Pins (Stunde / Minute)
// -------------------------
const int buttonPins[3][2] = {
{4, 15}, // Morgens
{18, 5}, // Mittags
{16, 17} // Abends
};
const char* relayNames[3] = {"Morgens", "Mittags", "Abends"};
// --- External Watchdog ---
const int WDI_PIN = 14; // freier GPIO für MAX6369KA+T
// -------------------------
// Speicher der Zeiten
// -------------------------
int relayHours[3];
int relayMinutes[3];
unsigned long lastButtonPress[3][2] = {0};
const unsigned long holdInterval = 200;
// -------------------------
// Relaissteuerung
// -------------------------
int lastTriggerTime[3] = {-1, -1, -1}; // (kann später sogar weg)
int lastTriggerDay[3] = {-1, -1, -1}; // <-- FEHLTE
bool relayActive[3] = {false, false, false};
unsigned long relayOnTime[3] = {0,0,0};
// -------------------------
// Serial Timer
// -------------------------
unsigned long lastSerialUpdate = 0;
const unsigned long serialInterval = 5000;
unsigned long bootTime = 0; // für geplanten Wochen-Neustart
bool rebootDone = false; // Tages-Neustart Sperre
// -------------------------
// Hilfsfunktion: Zentrieren
// -------------------------
int centerX(const char* text) {
return (128 - u8g2.getStrWidth(text)) / 2;
}
// -------------------------
// Relais setzen
// -------------------------
void setRelay(int index, bool on) {
digitalWrite(relayPins[index], on ? LOW : HIGH);
}
// -------------------------
// Serial Tabelle
// -------------------------
void printTable(DateTime now) {
Serial.println(F("+----------+----------+----------+----------+"));
Serial.println(F("| Zeit | Morgens | Mittags | Abends |"));
Serial.println(F("+----------+----------+----------+----------+"));
Serial.print("| ");
char t[9];
sprintf(t, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
Serial.print(t);
Serial.print(" | ");
for (int i = 0; i < 3; i++) {
char b[6];
sprintf(b, "%02d:%02d", relayHours[i], relayMinutes[i]);
Serial.print(b);
Serial.print(" | ");
}
Serial.println();
Serial.println(F("+----------+----------+----------+----------+"));
}
// -------------------------
// HTML Generator
// -------------------------
String generateHTML() {
DateTime now = rtc.now();
char buf[20];
sprintf(buf, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
String timeStr = buf;
String html = "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
html += "<meta http-equiv='Cache-Control' content='no-store, no-cache, must-revalidate, max-age=0'>";
html += "<meta http-equiv='Pragma' content='no-cache'>";
html += "<meta http-equiv='Expires' content='0'>";
html += "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
html += "<title>Relais Zeiten</title>";
html += "<style>"
"body{background:#EAF3FF;font-family:Arial;margin:20px;}"
"h2{text-align:center;color:#005FCC;font-size:26px;}"
"table{width:100%;border-collapse:collapse;font-size:22px;}"
"td,th{padding:12px;border-bottom:2px solid #BBD6F2;}"
"input[type=number]{width:100%;font-size:22px;padding:10px;border-radius:10px;border:2px solid #7FB3FF;}"
"button{margin:16px auto;display:block;width:70%;padding:14px;font-size:22px;color:white;"
"background:linear-gradient(135deg,#007BFF,#005FCC);border:none;border-radius:16px;}"
"</style>";
html += "<script>"
"let h="+String(now.hour())+",m="+String(now.minute())+",s="+String(now.second())+";"
"setInterval(()=>{s++;if(s>59){s=0;m++;}if(m>59){m=0;h++;}if(h>23)h=0;"
"document.getElementById('clock').innerText=("
"(h<10?'0'+h:h)+':'+"
"(m<10?'0'+m:m)+':'+"
"(s<10?'0'+s:s));},1000);"
"function syncTime(){"
"const d=new Date();"
"const params="
"'y='+d.getFullYear()"
"+'&mo='+(d.getMonth()+1)"
"+'&d='+d.getDate()"
"+'&h='+d.getHours()"
"+'&m='+d.getMinutes()"
"+'&s='+d.getSeconds();"
"fetch('/sync?'+params)"
".then(r=>r.text())"
".then(()=>alert('Uhrzeit vom Handy übernommen!'));"
"}"
"function saveTimes(){"
"let params='';"
"for(let i=0;i<3;i++){"
"let hh=document.querySelector('[name=h'+i+']').value;"
"let mm=document.querySelector('[name=m'+i+']').value;"
"params+='h'+i+'='+hh+'&m'+i+'='+mm+'&';"
"}"
"fetch('/setTimes?'+params)"
".then(r=>r.text())"
".then(()=>{"
"alert('Futterzeit geändert!');"
"location.reload();"
"});"
"}"
"</script>";
html += "</head><body>";
html += "<h2>Fütterzeiten</h2>";
html += "<div style='font-size:24px;text-align:center;margin-bottom:20px;'>"
"Aktuelle Zeit: <span id='clock'>"+timeStr+"</span></div>";
html += "<button onclick='syncTime()'>Uhrzeit vom Handy übernehmen</button>";
html += "<form onsubmit='saveTimes(); return false;'>";
html += "<table><tr><th>Relais</th><th>Stunde</th><th>Minute</th></tr>";
for(int i=0;i<3;i++){
html += "<tr>";
html += "<td>" + String(relayNames[i]) + "</td>";
html += "<td><input type='number' name='h"+String(i)+"' min='0' max='23' value='"+String(relayHours[i])+"'></td>";
html += "<td><input type='number' name='m"+String(i)+"' min='0' max='59' value='"+String(relayMinutes[i])+"'></td>";
html += "</tr>";
}
html += "</table>";
html += "<button type='submit'>Änderungen speichern</button>";
html += "</form></body></html>";
return html;
}
// -------------------------
// Handler
// -------------------------
void handleRoot() {
server.send(200, "text/html", generateHTML());
}
void handleSyncTime() {
if (server.hasArg("y")) {
int y = server.arg("y").toInt();
int mo = server.arg("mo").toInt();
int d = server.arg("d").toInt();
int h = server.arg("h").toInt();
int m = server.arg("m").toInt();
int s = server.arg("s").toInt();
rtc.adjust(DateTime(y, mo, d, h, m, s));
Serial.println("RTC per Handy synchronisiert");
}
server.send(200,"text/plain","OK");
}
void handleSetTimes() {
unsigned long tNow = millis();
int firstChangedIndex = -1;
for (int i = 0; i < 3; i++) {
bool changed = false;
if (server.hasArg("h" + String(i))) {
int newH = server.arg("h" + String(i)).toInt();
if (newH != relayHours[i]) {
relayHours[i] = newH;
char key[4]; sprintf(key, "h%d", i);
preferences.putInt(key, relayHours[i]);
changed = true;
}
}
if (server.hasArg("m" + String(i))) {
int newM = server.arg("m" + String(i)).toInt();
if (newM != relayMinutes[i]) {
relayMinutes[i] = newM;
char key[4]; sprintf(key, "m%d", i);
preferences.putInt(key, relayMinutes[i]);
changed = true;
}
}
if(changed){
lastTriggerDay[i] = -1;
if(firstChangedIndex == -1)
firstChangedIndex = i; // ← FEHLTE
}
}
// 🔔 Anzeige nur EINMAL starten
if (firstChangedIndex != -1) {
showTrigger = true;
triggerDisplayStart = tNow;
sprintf(
triggerText,
"%s %02d:%02d",
relayNames[firstChangedIndex],
relayHours[firstChangedIndex],
relayMinutes[firstChangedIndex]
);
displayOn = true;
allowDisplay = true;
lastDisplayTime = tNow;
}
printTable(rtc.now());
server.send(200, "text/plain", "OK");
}
// --- External Watchdog Feed ---
unsigned long lastWdgPulse = 0;
const unsigned long wdgInterval = 2000; // 2000 ms = 2 s
void feedExternalWdg() {
unsigned long t = millis();
if (t - lastWdgPulse >= wdgInterval) {
// kurzer Pulse LOW->HIGH
digitalWrite(WDI_PIN, LOW);
delay(10); // 10 ms Puls reicht
digitalWrite(WDI_PIN, HIGH);
lastWdgPulse = t;
}
}
// -------------------------
// Setup
// -------------------------
void setup() {
Serial.begin(115200);
// --- I2C Bus Recovery (verhindert Boot-Freeze) ---
pinMode(32, INPUT_PULLUP); // SDA OLED
pinMode(33, INPUT_PULLUP); // SCL OLED
delay(50);
// --- WATCHDOG für Loop-Task, Idle-TWDT ignorieren ---
esp_task_wdt_config_t wdt_config = {};
wdt_config.timeout_ms = 20000; // 20 Sekunden Timeout
wdt_config.trigger_panic = true;
wdt_config.idle_core_mask = 0; // keine Überwachung der Idle-Tasks
esp_task_wdt_init(&wdt_config);
// Loop-Task überwachen
esp_task_wdt_add(xTaskGetCurrentTaskHandle());
// OLED
OLED_I2C.begin(32, 33);
Wire = OLED_I2C; // U8g2 intern auf Bus 0 umbiegen
u8g2.begin();
u8g2.setBusClock(50000); // I2C-Geschwindigkeit drosseln, um Deadlocks zu verhindern
u8g2.clearBuffer();
// Ersten Frame senden – mit WDT-Schutz
unsigned long oledStart = millis();
while (millis() - oledStart < oledTimeout) {
u8g2.sendBuffer(); // Display initialisieren
esp_task_wdt_reset(); // Loop-Watchdog füttern
yield(); // Task-Wechsel
}
// RTC auf separatem Bus
RTC_I2C.begin(21,22);
RTC_I2C.setTimeOut(50); // verhindert I²C Freeze
if (!rtc.begin(&RTC_I2C)) { Serial.println("RTC Fehler!"); while(1); }
if (rtc.lostPower()) rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// Relais
for(int i=0;i<3;i++){
pinMode(relayPins[i], OUTPUT);
setRelay(i, false);
}
// Taster
for(int i=0;i<3;i++){
pinMode(buttonPins[i][0], INPUT_PULLUP);
pinMode(buttonPins[i][1], INPUT_PULLUP);
}
// Watchdog Pin initialisieren
pinMode(WDI_PIN, OUTPUT);
digitalWrite(WDI_PIN, HIGH); // Idle-Zustand HIGH (Open-Drain)
// Preferences
preferences.begin("relayTimes", false);
char key[10];
for(int i=0;i<3;i++){
sprintf(key,"h%d",i);
relayHours[i] = preferences.getInt(key, i*6);
sprintf(key,"m%d",i);
relayMinutes[i] = preferences.getInt(key, 0);
}
printTable(rtc.now());
// WLAN Access Point starten
WiFi.softAP(apSSID, apPassword);
WiFi.softAPConfig(apIP, apIP, IPAddress(255,255,255,0)); // IP, Gateway, Subnet
Serial.print("AP gestartet: ");
Serial.println(WiFi.softAPIP());
server.on("/", handleRoot);
server.on("/setTimes", handleSetTimes);
server.on("/sync", handleSyncTime);
server.begin();
bootTime = millis();
lastDisplayTime = millis();
}
// -------------------------
// Loop
// -------------------------
void loop() {
server.handleClient();
delay(1); // zwingt Task-Wechsel
DateTime now = rtc.now();
int currentMinutes = now.hour()*60 + now.minute();
unsigned long tNow = millis();
// --- OLED Fehlerzähler ---
static uint8_t oledFailCount = 0; // zählt fehlerhafte sendBuffer-Versuche
const uint8_t oledFailLimit = 3; // ab wann Display deaktiviert wird
if (now.hour() != 3) {
}
// --- OLED Notabschaltung ---
if (oledFailCount >= oledFailLimit) {
displayOn = false;
u8g2.setPowerSave(1);
}
// --- TAGESWECHSEL-SICHERUNG ---
static int lastDayCheck = -1;
if(now.day() != lastDayCheck){
lastDayCheck = now.day();
for(int i=0;i<3;i++){
lastTriggerDay[i] = -1; // Tagesfreigabe
}
Serial.println("Neuer Tag – Trigger zurückgesetzt");
}
// --- Taster lesen ---
for(int i=0;i<3;i++){
for(int j=0;j<2;j++){
if(digitalRead(buttonPins[i][j]) == LOW) {
if(tNow - lastButtonPress[i][j] > holdInterval) {
if(j==0){
relayHours[i] = (relayHours[i] + 1) % 24;
char key[4]; sprintf(key,"h%d",i);
preferences.putInt(key, relayHours[i]);
lastTriggerDay[i] = -1;
} else {
relayMinutes[i] = (relayMinutes[i] + 1) % 60;
char key[4]; sprintf(key,"m%d",i);
preferences.putInt(key, relayMinutes[i]);
lastTriggerDay[i] = -1;
}
// Triggeranzeige starten
showTrigger = true;
triggerDisplayStart = tNow;
sprintf(triggerText, "%s %02d:%02d", relayNames[i], relayHours[i], relayMinutes[i]);
displayOn = true;
allowDisplay = true; // <--- hier setzen, nur Taster/Web erlaubt Display
lastDisplayTime = tNow;
}
} else {
lastButtonPress[i][j] = tNow;
}
}
}
// --- Relaissteuerung ---
for(int i=0;i<3;i++){
int target = relayHours[i]*60 + relayMinutes[i];
if(currentMinutes >= target &&
currentMinutes <= target &&
lastTriggerDay[i] != now.day() &&
!relayActive[i])
{
setRelay(i,true);
relayActive[i] = true;
relayOnTime[i] = tNow;
lastTriggerDay[i] = now.day(); // <-- TAG merken
Serial.print(relayNames[i]);
Serial.println(" Relais ausgelöst!");
}
if(relayActive[i] && tNow - relayOnTime[i] >= 200){
setRelay(i,false);
relayActive[i] = false;
}
}
// --- OLED Anzeige ---
static String lastTimeStr = "";
static String lastTriggerStr = "";
if (displayOn && allowDisplay) { // <--- nur wenn erlaubt
u8g2.setPowerSave(0);
// Aktuelle Zeit und Triggertext als Strings
char timeStr[6];
sprintf(timeStr, "%02d:%02d", now.hour(), now.minute());
String curTimeStr = String(timeStr);
String curTriggerStr = showTrigger ? String(triggerText) : "";
// Nur neu zeichnen, wenn sich Zeit oder Trigger geändert hat
if (curTimeStr != lastTimeStr || curTriggerStr != lastTriggerStr) {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_logisoso28_tf);
u8g2.drawStr(centerX(timeStr), 28, timeStr);
if (showTrigger) {
u8g2.setFont(u8g2_font_ncenB10_tr);
u8g2.drawStr(centerX(triggerText), 54, triggerText);
}
// --- sendBuffer mit Fehlerkontrolle ---
unsigned long oledStart = millis();
bool sent = false;
while (millis() - oledStart < oledTimeout) {
// versuche einmal zu senden
u8g2.sendBuffer();
sent = true; // erfolgreich?
break; // nur ein Versuch pro Loop
}
if (!sent) {
oledFailCount++; // Fehler hochzählen
} else if (oledFailCount > 0) {
// Fehlerzähler langsam reduzieren, damit das Display wieder aktiviert wird
oledFailCount--;
if (oledFailCount < oledFailLimit && !displayOn) {
displayOn = true; // wieder aktivieren
u8g2.setPowerSave(0);
}
}
// Letzten Zustand merken
lastTimeStr = curTimeStr;
lastTriggerStr = curTriggerStr;
}
}
// Trigger nach 4 Sekunden ausblenden
if(showTrigger && tNow - triggerDisplayStart >= 10000){
showTrigger = false;
}
// --- Serial Tabelle alle 5 Sekunden aktualisieren ---
if (millis() - lastSerialUpdate >= serialInterval) {
lastSerialUpdate = millis();
printTable(now);
}
// Display nach 10 Sekunden in Ruhemodus
if(millis() - lastDisplayTime > 10000 && displayOn){
u8g2.setPowerSave(1);
displayOn = false;
allowDisplay = false; // Verhindert, dass Display beim Schloss auslösen angeht
}
server.handleClient();
// --- Geplanter Tages-Neustart um 03:00 ---
if(now.hour() == 3 && now.minute() == 0 && !dailyRebootDone){
Serial.println("Tages-Neustart um 03:00");
unsigned long start = millis();
while(millis() - start < 200){
esp_task_wdt_reset(); // WDT füttern
yield(); // Task-Wechsel
}
dailyRebootDone = true;
ESP.restart();
}
// Reset Flag nach 03:01
if(now.hour() == 3 && now.minute() > 0){
dailyRebootDone = false;
}
// --- Geplanter Wochen-Neustart (1× pro Woche) ---
if (millis() - bootTime > 7UL * 24UL * 60UL * 60UL * 1000UL) {
Serial.println("Geplanter Wochen-Neustart");
}
// Feed external watchdog
feedExternalWdg();
yield();
esp_task_wdt_reset(); }
Danke dir :-)
Bekomme ich nicht kompiliert.
Dem fehlt mindestens folgende Zeile:
WebServer server(80);
und dann sind da noch so Dinge:
sketch_feb02b:334:3: error: 'esp_task_wdt_config_t' was not declared in this scope
esp_task_wdt_config_t wdt_config = {};
^~~~~~~~~~~~~~~~~~~~~
/tmp/arduino_modified_sketch_468964/sketch_feb02b.ino:334:3: note: suggested alternative: 'esp_task_wdt_init'
esp_task_wdt_config_t wdt_config = {};
^~~~~~~~~~~~~~~~~~~~~
esp_task_wdt_init
sketch_feb02b:335:3: error: 'wdt_config' was not declared in this scope
wdt_config.timeout_ms = 20000; // 20 Sekunden Timeout
^~~~~~~~~~
/tmp/arduino_modified_sketch_468964/sketch_feb02b.ino:335:3: note: suggested alternative: 'gpio_config'
wdt_config.timeout_ms = 20000; // 20 Sekunden Timeout
^~~~~~~~~~
gpio_config
sketch_feb02b:405:15: error: 'apSSID' was not declared in this scope
WiFi.softAP(apSSID, apPassword);
^~~~~~
sketch_feb02b:405:23: error: 'apPassword' was not declared in this scope
WiFi.softAP(apSSID, apPassword);
^~~~~~~~~~
sketch_feb02b:406:21: error: 'apIP' was not declared in this scope
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); // IP, Gateway, Subnet
^~~~
#include <RTClib.h>
#include <Preferences.h>
#include <WiFi.h>
#include <WebServer.h>
#include <U8g2lib.h>
#include "esp_task_wdt.h"
#include <Wire.h>
TwoWire OLED_I2C = TwoWire(0); // Bus für Display
TwoWire RTC_I2C = TwoWire(1); // Bus für RTC
static bool allowDisplay = false; // <--- NEU, erlaubt nur Taster/Web-Trigger
// --- OLED / I2C Absicherung ---
unsigned long oledLastSend = 0;
const unsigned long oledTimeout = 50; // ms, maximale Blockierzeit
bool oledError = false;
// --- Tages-Neustart Flag ---
bool dailyRebootDone = false; // verhindert mehrfachen Tages-Neustart
// -------------------------
// Trigger-Anzeige
// -------------------------
bool showTrigger = false;
unsigned long triggerDisplayStart = 0;
char triggerText[20] = "";
// -------------------------
// Preferences
// -------------------------
Preferences preferences;
// -------------------------
// RTC auf separatem I2C-Bus
// -------------------------
RTC_DS3231 rtc;
// -------------------------
// OLED Display
// -------------------------
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 32, 33);
static unsigned long lastDisplayTime = 0;
static bool displayOn = true;
// -------------------------
// Relais-Pins
// -------------------------
const int relayPins[3] = {25, 26, 27};
// -------------------------
// Taster-Pins (Stunde / Minute)
// -------------------------
const int buttonPins[3][2] = {
{4, 15}, // Morgens
{18, 5}, // Mittags
{16, 17} // Abends
};
const char* relayNames[3] = {"Morgens", "Mittags", "Abends"};
// --- External Watchdog ---
const int WDI_PIN = 14; // freier GPIO für MAX6369KA+T
// -------------------------
// Speicher der Zeiten
// -------------------------
int relayHours[3];
int relayMinutes[3];
unsigned long lastButtonPress[3][2] = {0};
const unsigned long holdInterval = 200;
// -------------------------
// Relaissteuerung
// -------------------------
int lastTriggerTime[3] = {-1, -1, -1}; // (kann später sogar weg)
int lastTriggerDay[3] = {-1, -1, -1}; // <-- FEHLTE
bool relayActive[3] = {false, false, false};
unsigned long relayOnTime[3] = {0,0,0};
// -------------------------
// Serial Timer
// -------------------------
unsigned long lastSerialUpdate = 0;
const unsigned long serialInterval = 5000;
unsigned long bootTime = 0; // für geplanten Wochen-Neustart
bool rebootDone = false; // Tages-Neustart Sperre
// -------------------------
// ESP AP IP-Adresse konfigurierbar
// -------------------------
IPAddress apIP(192,168,4,1); // <-- hier IP ändern für weiteres System
const char* apSSID = "Futterbox101";
const char* apPassword = "12345678";
WebServer server(80);
// -------------------------
// Hilfsfunktion: Zentrieren
// -------------------------
int centerX(const char* text) {
return (128 - u8g2.getStrWidth(text)) / 2;
}
// -------------------------
// Relais setzen
// -------------------------
void setRelay(int index, bool on) {
digitalWrite(relayPins[index], on ? LOW : HIGH);
}
// -------------------------
// Serial Tabelle
// -------------------------
void printTable(DateTime now) {
Serial.println(F("+----------+----------+----------+----------+"));
Serial.println(F("| Zeit | Morgens | Mittags | Abends |"));
Serial.println(F("+----------+----------+----------+----------+"));
Serial.print("| ");
char t[9];
sprintf(t, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
Serial.print(t);
Serial.print(" | ");
for (int i = 0; i < 3; i++) {
char b[6];
sprintf(b, "%02d:%02d", relayHours[i], relayMinutes[i]);
Serial.print(b);
Serial.print(" | ");
}
Serial.println();
Serial.println(F("+----------+----------+----------+----------+"));
}
// -------------------------
// HTML Generator
// -------------------------
String generateHTML() {
DateTime now = rtc.now();
char buf[20];
sprintf(buf, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
String timeStr = buf;
String html = "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
html += "<meta http-equiv='Cache-Control' content='no-store, no-cache, must-revalidate, max-age=0'>";
html += "<meta http-equiv='Pragma' content='no-cache'>";
html += "<meta http-equiv='Expires' content='0'>";
html += "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
html += "<title>Relais Zeiten</title>";
html += "<style>"
"body{background:#EAF3FF;font-family:Arial;margin:20px;}"
"h2{text-align:center;color:#005FCC;font-size:26px;}"
"table{width:100%;border-collapse:collapse;font-size:22px;}"
"td,th{padding:12px;border-bottom:2px solid #BBD6F2;}"
"input[type=number]{width:100%;font-size:22px;padding:10px;border-radius:10px;border:2px solid #7FB3FF;}"
"button{margin:16px auto;display:block;width:70%;padding:14px;font-size:22px;color:white;"
"background:linear-gradient(135deg,#007BFF,#005FCC);border:none;border-radius:16px;}"
"</style>";
html += "<script>"
"let h="+String(now.hour())+",m="+String(now.minute())+",s="+String(now.second())+";"
"setInterval(()=>{s++;if(s>59){s=0;m++;}if(m>59){m=0;h++;}if(h>23)h=0;"
"document.getElementById('clock').innerText=("
"(h<10?'0'+h:h)+':'+"
"(m<10?'0'+m:m)+':'+"
"(s<10?'0'+s:s));},1000);"
"function syncTime(){"
"const d=new Date();"
"const params="
"'y='+d.getFullYear()"
"+'&mo='+(d.getMonth()+1)"
"+'&d='+d.getDate()"
"+'&h='+d.getHours()"
"+'&m='+d.getMinutes()"
"+'&s='+d.getSeconds();"
"fetch('/sync?'+params)"
".then(r=>r.text())"
".then(()=>alert('Uhrzeit vom Handy übernommen!'));"
"}"
"function saveTimes(){"
"let params='';"
"for(let i=0;i<3;i++){"
"let hh=document.querySelector('[name=h'+i+']').value;"
"let mm=document.querySelector('[name=m'+i+']').value;"
"params+='h'+i+'='+hh+'&m'+i+'='+mm+'&';"
"}"
"fetch('/setTimes?'+params)"
".then(r=>r.text())"
".then(()=>{"
"alert('Futterzeit geändert!');"
"location.reload();"
"});"
"}"
"</script>";
html += "</head><body>";
html += "<h2>Fütterzeiten</h2>";
html += "<div style='font-size:24px;text-align:center;margin-bottom:20px;'>"
"Aktuelle Zeit: <span id='clock'>"+timeStr+"</span></div>";
html += "<button onclick='syncTime()'>Uhrzeit vom Handy übernehmen</button>";
html += "<form onsubmit='saveTimes(); return false;'>";
html += "<table><tr><th>Relais</th><th>Stunde</th><th>Minute</th></tr>";
for(int i=0;i<3;i++){
html += "<tr>";
html += "<td>" + String(relayNames[i]) + "</td>";
html += "<td><input type='number' name='h"+String(i)+"' min='0' max='23' value='"+String(relayHours[i])+"'></td>";
html += "<td><input type='number' name='m"+String(i)+"' min='0' max='59' value='"+String(relayMinutes[i])+"'></td>";
html += "</tr>";
}
html += "</table>";
html += "<button type='submit'>Änderungen speichern</button>";
html += "</form></body></html>";
return html;
}
// -------------------------
// Handler
// -------------------------
void handleRoot() {
server.send(200, "text/html", generateHTML());
}
void handleSyncTime() {
if (server.hasArg("y")) {
int y = server.arg("y").toInt();
int mo = server.arg("mo").toInt();
int d = server.arg("d").toInt();
int h = server.arg("h").toInt();
int m = server.arg("m").toInt();
int s = server.arg("s").toInt();
rtc.adjust(DateTime(y, mo, d, h, m, s));
Serial.println("RTC per Handy synchronisiert");
}
server.send(200,"text/plain","OK");
}
void handleSetTimes() {
unsigned long tNow = millis();
int firstChangedIndex = -1;
for (int i = 0; i < 3; i++) {
bool changed = false;
if (server.hasArg("h" + String(i))) {
int newH = server.arg("h" + String(i)).toInt();
if (newH != relayHours[i]) {
relayHours[i] = newH;
char key[4]; sprintf(key, "h%d", i);
preferences.putInt(key, relayHours[i]);
changed = true;
}
}
if (server.hasArg("m" + String(i))) {
int newM = server.arg("m" + String(i)).toInt();
if (newM != relayMinutes[i]) {
relayMinutes[i] = newM;
char key[4]; sprintf(key, "m%d", i);
preferences.putInt(key, relayMinutes[i]);
changed = true;
}
}
if(changed){
lastTriggerDay[i] = -1;
if(firstChangedIndex == -1)
firstChangedIndex = i; // ← FEHLTE
}
}
// 🔔 Anzeige nur EINMAL starten
if (firstChangedIndex != -1) {
showTrigger = true;
triggerDisplayStart = tNow;
sprintf(
triggerText,
"%s %02d:%02d",
relayNames[firstChangedIndex],
relayHours[firstChangedIndex],
relayMinutes[firstChangedIndex]
);
displayOn = true;
allowDisplay = true;
lastDisplayTime = tNow;
}
printTable(rtc.now());
server.send(200, "text/plain", "OK");
}
// --- External Watchdog Feed ---
unsigned long lastWdgPulse = 0;
const unsigned long wdgInterval = 2000; // 2000 ms = 2 s
void feedExternalWdg() {
unsigned long t = millis();
if (t - lastWdgPulse >= wdgInterval) {
// kurzer Pulse LOW->HIGH
digitalWrite(WDI_PIN, LOW);
delay(10); // 10 ms Puls reicht
digitalWrite(WDI_PIN, HIGH);
lastWdgPulse = t;
}
}
// -------------------------
// Setup
// -------------------------
void setup() {
Serial.begin(115200);
// --- I2C Bus Recovery (verhindert Boot-Freeze) ---
pinMode(32, INPUT_PULLUP); // SDA OLED
pinMode(33, INPUT_PULLUP); // SCL OLED
delay(50);
// --- WATCHDOG für Loop-Task, Idle-TWDT ignorieren ---
esp_task_wdt_config_t wdt_config = {};
wdt_config.timeout_ms = 20000; // 20 Sekunden Timeout
wdt_config.trigger_panic = true;
wdt_config.idle_core_mask = 0; // keine Überwachung der Idle-Tasks
esp_task_wdt_init(&wdt_config);
// Loop-Task überwachen
esp_task_wdt_add(xTaskGetCurrentTaskHandle());
// OLED
OLED_I2C.begin(32, 33);
Wire = OLED_I2C; // U8g2 intern auf Bus 0 umbiegen
u8g2.begin();
u8g2.setBusClock(50000); // I2C-Geschwindigkeit drosseln, um Deadlocks zu verhindern
u8g2.clearBuffer();
// Ersten Frame senden – mit WDT-Schutz
unsigned long oledStart = millis();
while (millis() - oledStart < oledTimeout) {
u8g2.sendBuffer(); // Display initialisieren
esp_task_wdt_reset(); // Loop-Watchdog füttern
yield(); // Task-Wechsel
}
// RTC auf separatem Bus
RTC_I2C.begin(21,22);
RTC_I2C.setTimeOut(50); // verhindert I²C Freeze
if (!rtc.begin(&RTC_I2C)) { Serial.println("RTC Fehler!"); while(1); }
if (rtc.lostPower()) rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// Relais
for(int i=0;i<3;i++){
pinMode(relayPins[i], OUTPUT);
setRelay(i, false);
}
// Taster
for(int i=0;i<3;i++){
pinMode(buttonPins[i][0], INPUT_PULLUP);
pinMode(buttonPins[i][1], INPUT_PULLUP);
}
// Watchdog Pin initialisieren
pinMode(WDI_PIN, OUTPUT);
digitalWrite(WDI_PIN, HIGH); // Idle-Zustand HIGH (Open-Drain)
// Preferences
preferences.begin("relayTimes", false);
char key[10];
for(int i=0;i<3;i++){
sprintf(key,"h%d",i);
relayHours[i] = preferences.getInt(key, i*6);
sprintf(key,"m%d",i);
relayMinutes[i] = preferences.getInt(key, 0);
}
printTable(rtc.now());
// WLAN Access Point starten
WiFi.softAP(apSSID, apPassword);
WiFi.softAPConfig(apIP, apIP, IPAddress(255,255,255,0)); // IP, Gateway, Subnet
Serial.print("AP gestartet: ");
Serial.println(WiFi.softAPIP());
server.on("/", handleRoot);
server.on("/setTimes", handleSetTimes);
server.on("/sync", handleSyncTime);
server.begin();
bootTime = millis();
lastDisplayTime = millis();
}
// -------------------------
// Loop
// -------------------------
void loop() {
server.handleClient();
delay(1); // zwingt Task-Wechsel
DateTime now = rtc.now();
int currentMinutes = now.hour()*60 + now.minute();
unsigned long tNow = millis();
// --- OLED Fehlerzähler ---
static uint8_t oledFailCount = 0; // zählt fehlerhafte sendBuffer-Versuche
const uint8_t oledFailLimit = 3; // ab wann Display deaktiviert wird
if (now.hour() != 3) {
}
// --- OLED Notabschaltung ---
if (oledFailCount >= oledFailLimit) {
displayOn = false;
u8g2.setPowerSave(1);
}
// --- TAGESWECHSEL-SICHERUNG ---
static int lastDayCheck = -1;
if(now.day() != lastDayCheck){
lastDayCheck = now.day();
for(int i=0;i<3;i++){
lastTriggerDay[i] = -1; // Tagesfreigabe
}
Serial.println("Neuer Tag – Trigger zurückgesetzt");
}
// --- Taster lesen ---
for(int i=0;i<3;i++){
for(int j=0;j<2;j++){
if(digitalRead(buttonPins[i][j]) == LOW) {
if(tNow - lastButtonPress[i][j] > holdInterval) {
if(j==0){
relayHours[i] = (relayHours[i] + 1) % 24;
char key[4]; sprintf(key,"h%d",i);
preferences.putInt(key, relayHours[i]);
lastTriggerDay[i] = -1;
} else {
relayMinutes[i] = (relayMinutes[i] + 1) % 60;
char key[4]; sprintf(key,"m%d",i);
preferences.putInt(key, relayMinutes[i]);
lastTriggerDay[i] = -1;
}
// Triggeranzeige starten
showTrigger = true;
triggerDisplayStart = tNow;
sprintf(triggerText, "%s %02d:%02d", relayNames[i], relayHours[i], relayMinutes[i]);
displayOn = true;
allowDisplay = true; // <--- hier setzen, nur Taster/Web erlaubt Display
lastDisplayTime = tNow;
}
} else {
lastButtonPress[i][j] = tNow;
}
}
}
// --- Relaissteuerung ---
for(int i=0;i<3;i++){
int target = relayHours[i]*60 + relayMinutes[i];
if(currentMinutes >= target &&
currentMinutes <= target &&
lastTriggerDay[i] != now.day() &&
!relayActive[i])
{
setRelay(i,true);
relayActive[i] = true;
relayOnTime[i] = tNow;
lastTriggerDay[i] = now.day(); // <-- TAG merken
Serial.print(relayNames[i]);
Serial.println(" Relais ausgelöst!");
}
if(relayActive[i] && tNow - relayOnTime[i] >= 200){
setRelay(i,false);
relayActive[i] = false;
}
}
// --- OLED Anzeige ---
static String lastTimeStr = "";
static String lastTriggerStr = "";
if (displayOn && allowDisplay) { // <--- nur wenn erlaubt
u8g2.setPowerSave(0);
// Aktuelle Zeit und Triggertext als Strings
char timeStr[6];
sprintf(timeStr, "%02d:%02d", now.hour(), now.minute());
String curTimeStr = String(timeStr);
String curTriggerStr = showTrigger ? String(triggerText) : "";
// Nur neu zeichnen, wenn sich Zeit oder Trigger geändert hat
if (curTimeStr != lastTimeStr || curTriggerStr != lastTriggerStr) {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_logisoso28_tf);
u8g2.drawStr(centerX(timeStr), 28, timeStr);
if (showTrigger) {
u8g2.setFont(u8g2_font_ncenB10_tr);
u8g2.drawStr(centerX(triggerText), 54, triggerText);
}
// --- sendBuffer mit Fehlerkontrolle ---
unsigned long oledStart = millis();
bool sent = false;
while (millis() - oledStart < oledTimeout) {
// versuche einmal zu senden
u8g2.sendBuffer();
sent = true; // erfolgreich?
break; // nur ein Versuch pro Loop
}
if (!sent) {
oledFailCount++; // Fehler hochzählen
} else if (oledFailCount > 0) {
// Fehlerzähler langsam reduzieren, damit das Display wieder aktiviert wird
oledFailCount--;
if (oledFailCount < oledFailLimit && !displayOn) {
displayOn = true; // wieder aktivieren
u8g2.setPowerSave(0);
}
}
// Letzten Zustand merken
lastTimeStr = curTimeStr;
lastTriggerStr = curTriggerStr;
}
}
// Trigger nach 4 Sekunden ausblenden
if(showTrigger && tNow - triggerDisplayStart >= 10000){
showTrigger = false;
}
// --- Serial Tabelle alle 5 Sekunden aktualisieren ---
if (millis() - lastSerialUpdate >= serialInterval) {
lastSerialUpdate = millis();
printTable(now);
}
// Display nach 10 Sekunden in Ruhemodus
if(millis() - lastDisplayTime > 10000 && displayOn){
u8g2.setPowerSave(1);
displayOn = false;
allowDisplay = false; // Verhindert, dass Display beim Schloss auslösen angeht
}
server.handleClient();
// --- Geplanter Tages-Neustart um 03:00 ---
if(now.hour() == 3 && now.minute() == 0 && !dailyRebootDone){
Serial.println("Tages-Neustart um 03:00");
unsigned long start = millis();
while(millis() - start < 200){
esp_task_wdt_reset(); // WDT füttern
yield(); // Task-Wechsel
}
dailyRebootDone = true;
ESP.restart();
}
// Reset Flag nach 03:01
if(now.hour() == 3 && now.minute() > 0){
dailyRebootDone = false;
}
// --- Geplanter Wochen-Neustart (1× pro Woche) ---
if (millis() - bootTime > 7UL * 24UL * 60UL * 60UL * 1000UL) {
Serial.println("Geplanter Wochen-Neustart");
}
// Feed external watchdog
feedExternalWdg();
yield();
esp_task_wdt_reset(); }
ich hatte sie entfernt. jetzt kompiliert er. Zumindest bei mir
Warum verwendest du 2 I2C Busse ?
Du kannst doch beides auf einem betreiben.
Und Input_Pullup für I2C benötigst du nicht, da die I2C Boards in der Regel Pullups drauf haben.
Wie lang sind deine I2C Leitungen ?
Zwei I2C-Busse nutze ich bewusst zur Entkopplung: Das SH1106-OLED kann bei Fehlern den Bus blockieren, die RTC muss aber immer zuverlässig laufen.
INPUT_PULLUP auf SDA/SCL ist nur für die Bus-Recovery beim Boot, nicht als I2C-Pullup-Ersatz.
Leitungen sind kurz (7 cm), OLED-Bus läuft zusätzlich mit reduzierter I2C-Clock.
Wenn das OLED abgestürzt ist - läuft dann der restliche ESP noch z.B. der Webserver, deine Tastenabfrage?
Zeig bitte mal Bilder von deinem System was du da zusammengebaut hast, inkl. aller Kabel/Komponenten.
Bei deinem Sketch gibts aus meiner Sicht einiges zu verbessern. Z.B. diese losgelösten Arrays bzw mehrdimensionalen Arrays in Strukturen geben. Dann braucht es keine Raterei mehr, ob i oder j ...
Der Sketch in #9 scheint nicht komplett zu sein.
Formatiere mal mit STRG-T und poste noch mal deinen GANZEN letzten Stand.
Damit man zum Verständnis nicht reverse-engineeren muss ... hast du da Diagramme die zeigen was dein Ding machen soll?
Doch ist er.
Allerdings warte ich mittlerweile bald 10 Miunten bis der das erste mal mit allen Prototypen durchkompiliert ist...
sie laufen weiter damit das system nicht entgültig stirbt und zumindest die Funktion weiter ausgeführt wird. Der Sketch war komplett. ich bin für Verbesserungen sehr empfänglich. anbei sende ich ein bild der Platine
oben links kommen 230V und du dann mittels Trafo auf einen DC DC Wandler?
oben links 12v dann mit stepdown auf 5V
dein (mit STRG-T formatierter) Sketch aus #9 wirft zwei Warnings:
C:\Daten\myrepository\Arduino\Forum no SVN\sketch_feb02a\sketch_feb02a.ino: In function 'void printTable(DateTime)':
C:\Daten\myrepository\Arduino\Forum no SVN\sketch_feb02a\sketch_feb02a.ino:126:14: warning: '%02d' directive writing between 2 and 3 bytes into a region of size between 1 and 3 [-Wformat-overflow=]
126 | sprintf(t, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
| ^~~~~~~~~~~~~~~~
C:\Daten\myrepository\Arduino\Forum no SVN\sketch_feb02a\sketch_feb02a.ino:126:14: note: directive argument in the range [0, 255]
C:\Daten\myrepository\Arduino\Forum no SVN\sketch_feb02a\sketch_feb02a.ino:126:10: note: 'sprintf' output between 9 and 12 bytes into a destination of size 9
126 | sprintf(t, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
| ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
C:\Daten\myrepository\Arduino\Forum no SVN\sketch_feb02a\sketch_feb02a.ino: In function 'void loop()':
C:\Daten\myrepository\Arduino\Forum no SVN\sketch_feb02a\sketch_feb02a.ino:513:22: warning: '%02d' directive writing between 2 and 3 bytes into a region of size between 2 and 3 [-Wformat-overflow=]
513 | sprintf(timeStr, "%02d:%02d", now.hour(), now.minute());
| ^~~~~~~~~~~
C:\Daten\myrepository\Arduino\Forum no SVN\sketch_feb02a\sketch_feb02a.ino:513:22: note: directive argument in the range [0, 255]
C:\Daten\myrepository\Arduino\Forum no SVN\sketch_feb02a\sketch_feb02a.ino:513:12: note: 'sprintf' output between 6 and 8 bytes into a destination of size 6
513 | sprintf(timeStr, "%02d:%02d", now.hour(), now.minute());
| ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
geize nicht so mit lokalen Buffer-Variablen und mach dein t bzw. timeStr groß genug (oder verwende eine andere Formatierung).
Noch was Merkwürdiges:
wenn du dein u8g2 objekt anlegst, übergibst du zwei Pins (32, 33)
Wenn du den OLED_I2C.begin() aufrufst, übergibst du 32, 33
Zusätzlich setzt du noch pinMode für 32,33
und dann kopierst du noch OLED2_I2C nach Wire
ich weis noch nicht wie man es korrekt macht, aber ob das so stimmt bezweifle ich.
Vielen Dank erstmal. Das schaue ich mir an. Compilierst du den sketch oder kontrollierst du mir einer anderen Möglichkeit. Warnings hab ich noch nie bekommen.
LG
Nur mal ne grundsätzliche Frage: Warum RTC?
Hast du kein Wlan in der Nähe? dann kannst du dir die genaue Uhrzeit über einen Timeserver holen.
