From 530d2f46706bd89279f90c53cab6ad595eb615d6 Mon Sep 17 00:00:00 2001 From: MaPaLo76 <72209721+MaPaLo76@users.noreply.github.com> Date: Mon, 23 Feb 2026 20:50:04 +0100 Subject: [PATCH] feat(main): Phase 8 - WebServer, Watchdog, WLAN/MQTT-Fehlerstatus, Initialisierungsreihenfolge, Ceiling-Minuten pro Session --- Implementation-Plan.md | 10 +++++----- README.md | 9 +++++++-- include/laser_tracker.h | 1 + src/laser_tracker.cpp | 8 +++++++- src/main.cpp | 44 +++++++++++++++++++++++++++++++++-------- 5 files changed, 56 insertions(+), 16 deletions(-) diff --git a/Implementation-Plan.md b/Implementation-Plan.md index 76a571a..9706866 100644 --- a/Implementation-Plan.md +++ b/Implementation-Plan.md @@ -217,21 +217,21 @@ ## Phase 8 – Integration & Hauptprogramm (`main.cpp`) -- [ ] **8.1** `main.cpp` aufräumen und alle Module initialisieren: +- [x] **8.1** `main.cpp` aufräumen und alle Module initialisieren: - Reihenfolge: `Settings` → `DisplayManager` → `WiFiManager` → `MqttClient` → `WebServer` → `LaserTracker` -- [ ] **8.2** `loop()` implementieren: +- [x] **8.2** `loop()` implementieren: - `displayManager.update()` - `laserTracker.update()` (GPIO-Polling + Session-Logik) - `mqttClient.loop()` (Reconnect + Heartbeat) - `displayManager.showLaserTime(laserTracker.getTotalMinutes())` - `displayManager.showCountdown(...)` oder `showIdle()` oder `showError(...)` -- [ ] **8.3** Callback von `LaserTracker` → `MqttClient.publishSession(...)` verdrahten +- [x] **8.3** Callback von `LaserTracker` → `MqttClient.publishSession(...)` verdrahten -- [ ] **8.4** WLAN/MQTT-Fehlerzustand auf Display spiegeln +- [x] **8.4** WLAN/MQTT-Fehlerzustand auf Display spiegeln -- [ ] **8.5** Watchdog Timer aktivieren (ESP32 WDT, 30 s) +- [x] **8.5** Watchdog Timer aktivieren (ESP32 WDT, 30 s) --- diff --git a/README.md b/README.md index b113525..912396d 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ Es ist in **zwei Zonen** aufgeteilt, die unabhängig voneinander beschrieben wer | 4 | 4 | Unten links | **MQTT-Fehler** (`E` = kein Broker, leer = OK) | | 5–7 | 5–7 | Unten Mitte–rechts| **Countdown** (Gratiszeit in Sek.) oder `--` (Idle/Netto) | -- Summe Laser Zeit / Summe Session (Module 1–3) inkrementierten erst nach vollen **60 Netto-Sekunden** (harte Ganzzahl). +- Summe Session (Module 1–3) inkrementiert mit **jeder angefangenen Minute pro Session** (Ceiling). Beispiel: 10 s Netto = +1 Min, 65 s Netto = +2 Min (1 bei Sekunde 1, 2 ab Sekunde 61). - Die Netto-Zeit beginnt erst nach Ablauf der Gratiszeit (Laser muss länger als `gratisSeconds` aktiv bleiben). - Die Anzeige aktualisiert sich im `loop()` ohne Blocking-Delays. - Bibliotheken: `MD_MAX72XX` (direkte Puffer-Steuerung, keine MD_Parola) @@ -191,7 +191,12 @@ Zählt **jede Sekunde Laser-AN**, inklusive Gratiszeit. Eignet sich für Wartung ### Session-Minuten (Display) -Zählt nur die **Netto-Zeit** (nach Ablauf der Gratiszeit). Inkrementiert hart bei 60 / 120 / 180 ... Netto-Sekunden. Wird bei Neustart und `resetSession()` auf 0 zurückgesetzt. +Zählt nur die **Netto-Zeit** (nach Ablauf der Gratiszeit). Jede **angefangene Minute pro Session** wird aufgerundet gezählt (Ceiling). Beispiele: +- Session mit 10 s Netto → +1 Minute +- Session mit 65 s Netto → live +1 bei Sekunde 1–60, dann +2 ab Sekunde 61 +- Session mit 120 s Netto → live +1 bei Sek. 1–60, +2 ab Sek. 61–120 + +Wird bei Neustart und `resetSession()` auf 0 zurückgesetzt. ### Session-Sekunden diff --git a/include/laser_tracker.h b/include/laser_tracker.h index ef8b666..4251089 100644 --- a/include/laser_tracker.h +++ b/include/laser_tracker.h @@ -108,6 +108,7 @@ private: // ---- Zeitakkumulatoren -------------------------------------------------- int _sessionNetSec; // Netto-Sekunden der Session (RAM, 0 nach begin/resetSession) + int _sessionNetMin; // Aufgerundete Minuten je Session aufsummiert (Ceiling pro Session) float _totalMinutesBase; // NVS-Gesamtzeit (nach jedem Burst aktualisiert) int _lastSessionSec; // Netto-Sekunden der letzten abgeschlossenen Session bool _sessionEndPending; // true = Session gerade beendet, noch nicht consumed diff --git a/src/laser_tracker.cpp b/src/laser_tracker.cpp index d621caf..1627d3d 100644 --- a/src/laser_tracker.cpp +++ b/src/laser_tracker.cpp @@ -17,6 +17,7 @@ LaserTracker::LaserTracker() , _sessionStartMs(0) , _netStartMs(0) , _sessionNetSec(0) + , _sessionNetMin(0) , _totalMinutesBase(0.0f) , _lastSessionSec(0) , _sessionEndPending(false) @@ -90,6 +91,7 @@ void LaserTracker::onSessionEnd() { if (_state == SessionState::NET_COUNTING) { int netSec = (int)((millis() - _netStartMs) / 1000); _sessionNetSec += netSec; // Display: nur Netto (ohne Gratis) + _sessionNetMin += (netSec + 59) / 60; // Ceiling pro Session _lastSessionSec = netSec; _totalMinutesBase += totalSessionSec / 60.0f; // NVS: Netto + Gratis @@ -122,7 +124,10 @@ int LaserTracker::currentSessionNetSec() const { // --------------------------------------------------------------------------- int LaserTracker::getAllSessionsSumMinutes() const { - return (_sessionNetSec + currentSessionNetSec()) / 60; + // Pro Session aufgerundete Minuten + laufende Session (ebenfalls Ceiling) + int liveSec = currentSessionNetSec(); + int liveMin = (liveSec > 0) ? (liveSec + 59) / 60 : 0; + return _sessionNetMin + liveMin; } // --------------------------------------------------------------------------- @@ -156,6 +161,7 @@ int LaserTracker::getRunningSessionSeconds() const { // --------------------------------------------------------------------------- void LaserTracker::resetSession() { _sessionNetSec = 0; + _sessionNetMin = 0; Serial.println("[LaserTracker] Session zurueckgesetzt (NVS unveraendert)"); } diff --git a/src/main.cpp b/src/main.cpp index 392a30e..e3f0ebc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,41 +1,63 @@ #include +#include #include "config.h" #include "settings.h" #include "wifi_connector.h" #include "display_manager.h" #include "laser_tracker.h" #include "mqtt_client.h" +#include "web_server.h" + +#define WDT_TIMEOUT_SEC 30 void setup() { Serial.begin(SERIAL_BAUD_RATE); + + // 1. Einstellungen laden settings.begin(); LOG_I("MAIN", "LaserCutter Display gestartet"); LOG_I("MAIN", "Laser GPIO: %d, Display CS: %d", LASER_SIGNAL_PIN, DISPLAY_CS_PIN); settings.printToSerial(); + // 2. Display initialisieren display.begin(); display.printToSerial(); display.showWifiError(false); display.showMqttError(false); - display.showLaserTime(0.0f); // Session startet bei 0 + display.showLaserTime(0.0f); display.showIdle(); - laserTracker.begin(); - laserTracker.printToSerial(); - + // 3. WLAN verbinden wifiConnector.begin(); wifiConnector.printToSerial(); + // 4. MQTT-Client starten mqttClient.begin(); mqttClient.printToSerial(); + + // 5. Webserver starten (async, kein loop() nötig) + webServer.begin(); + + // 6. LaserTracker starten + laserTracker.begin(); + laserTracker.printToSerial(); + + // 7. Watchdog: 30 s Timeout, Panic bei Auslösung + esp_task_wdt_init(WDT_TIMEOUT_SEC, true); + esp_task_wdt_add(NULL); + LOG_I("MAIN", "Watchdog aktiv (%d s)", WDT_TIMEOUT_SEC); } void loop() { + // Watchdog zurücksetzen + esp_task_wdt_reset(); + + // Kern-Module aktualisieren laserTracker.loop(); wifiConnector.loop(); mqttClient.loop(); - // MQTT: Session-Publish nur wenn Netto-Zeit vorhanden (kein GRATIS-only) + // Session-Publish (nur wenn Netto-Zeit vorhanden, kein GRATIS-only) if (laserTracker.consumeSessionEnd()) { int lastSession = laserTracker.getLastSessionSeconds(); if (lastSession > 0) { @@ -43,13 +65,19 @@ void loop() { } } - // Display mit aktuellen Werten aktualisieren - display.showLaserTime((float)laserTracker.getAllSessionsSumMinutes()); // Module 1-3 + // Display: Modul 0 + 4 – Verbindungsstatus + display.showWifiError(!wifiConnector.isConnected()); + display.showMqttError(!mqttClient.isConnected()); + + // Display: Module 1-3 – Gesamtzeit + display.showLaserTime((float)laserTracker.getAllSessionsSumMinutes()); + + // Display: Module 5-7 – Betriebsstatus int countdown = laserTracker.getCountdownRemaining(); if (countdown > 0) { display.showCountdown(countdown); // GRATIS: Countdown } else if (laserTracker.isActive()) { - display.showSessionRing(laserTracker.getRunningSessionSeconds() % 60); // NET_COUNTING: Kreis + display.showSessionRing(laserTracker.getRunningSessionSeconds() % 60); // NET_COUNTING: Ring } else { display.showIdle(); // INACTIVE: -- }