feat(main): Phase 8 - WebServer, Watchdog, WLAN/MQTT-Fehlerstatus, Initialisierungsreihenfolge, Ceiling-Minuten pro Session

This commit is contained in:
MaPaLo76 2026-02-23 20:50:04 +01:00
parent e273f1ea6c
commit 530d2f4670
5 changed files with 56 additions and 16 deletions

View File

@ -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)
---

View File

@ -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) |
| 57 | 57 | Unten Mitterechts| **Countdown** (Gratiszeit in Sek.) oder `--` (Idle/Netto) |
- Summe Laser Zeit / Summe Session (Module 13) inkrementierten erst nach vollen **60 Netto-Sekunden** (harte Ganzzahl).
- Summe Session (Module 13) 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 160, dann +2 ab Sekunde 61
- Session mit 120 s Netto → live +1 bei Sek. 160, +2 ab Sek. 61120
Wird bei Neustart und `resetSession()` auf 0 zurückgesetzt.
### Session-Sekunden

View File

@ -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

View File

@ -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)");
}

View File

@ -1,41 +1,63 @@
#include <Arduino.h>
#include <esp_task_wdt.h>
#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: --
}