MQTT-Display-LaserCutter/Implementation-Plan.md

275 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Implementation Plan Laser Cutter MQTT Display
> Dieses Dokument beschreibt die Entwicklungsreihenfolge des Projekts.
> Tasks werden während der Entwicklung mit `[x]` abgehakt.
---
## Phase 1 Projekt-Setup & Konfiguration
- [x] **1.1** `platformio.ini` mit allen benötigten Bibliotheken erweitern
- `MD_Parola`, `MD_MAX72XX`
- `PubSubClient`
- `WiFiManager` (tzapu/WiFiManager)
- `ESPAsyncWebServer` + `AsyncTCP`
- `ArduinoJson`
- `ElegantOTA`
- `Preferences` (ESP32 built-in, kein Extra-Eintrag nötig)
- [x] **1.2** `include/config.h` erstellen zentrale Pin-Definitionen und Konstanten
- GPIO-Pins (MAX7219 SPI, Laser-Signal)
- Anzahl der Module (8), Zonen-Aufteilung
- Standard-Werte (Gratiszeit 20s, MQTT Port 1883, etc.)
- MQTT-Topic-Konstanten
- [x] **1.3** Build prüfen (leeres Projekt kompiliert fehlerfrei)
- [x] **1.4** Test Verdrahtung Dot-Matrix-Display
- GYMAX7219-Module gemäß Pinbelegung angeschlossen (MOSI GPIO 23, CLK GPIO 18, CS GPIO 5)
- Hardware-Typ `GENERIC_HW` (verifiziert; physische 90° Verdrehung per Software CCW kompensiert)
- Externes 5 V-Netzteil erforderlich (~0,5 A / ~2,5 W gemessen; USB-Port reicht nicht)
- SPI-Taktrate auf 1 MHz reduziert für stabile Ansteuerung aller 8 Module
- Modul 1 oben-links, Modul 4 oben-rechts, Modul 5 unten-links, Modul 8 unten-rechts
- Zone 0 (0-idx 03) = obere Reihe, Zone 1 (0-idx 47) = untere Reihe
- [x] **1.5** Test Verdrahtung Potentialfreier Schalter (Phase 1: Push Button)
- Push Button an GPIO 4 und GND angeschlossen (INPUT_PULLUP, kein externer Widerstand nötig)
- Pegel verifiziert: Button offen = HIGH, Button gedrückt = LOW → Polarität: LOW_ACTIVE
- Debounce 50 ms funktioniert
---
## Phase 2 NVS Persistenz (`Settings`)
- [x] **2.1** `include/settings.h` + `src/settings.cpp` erstellen
Speichert und lädt alle Konfigurationswerte über ESP32 `Preferences` (NVS):
- MQTT Broker IP, Port, User, Passwort
- Gratiszeit (0120 s)
- Signal-Polarität (`LOW_ACTIVE` / `HIGH_ACTIVE`)
- Akkumulierte Laserzeit (float, Minuten)
- Globale Instanz `settings`, `settings.begin()` in `main.cpp`
- [x] **2.2** Unit-Test (Serial-Output): Werte schreiben, ESP32 neu starten, Werte lesen und verifizieren
- `test_sketches/test_nvs.cpp` erstellt, `test-nvs` Environment in `platformio.ini`
- Round-Trip-Test + Persistenz-Check über Neustart
---
## Phase 3 WiFi & WiFiManager
- [x] **3.1** WiFiManager einbinden
- `include/wifi_connector.h` + `src/wifi_connector.cpp` erstellt
- AP-Name: `LaserCutter-Setup` (aus `config.h`)
- Captive-Portal-Seite für WLAN-Credentials
- Timeout: 120 s, danach `ESP.restart()`
- [x] **3.2** Verbindungsstatus-Callback implementieren (für spätere Fehleranzeige)
- `WifiStatusCallback`-Typ + `onStatusChange()` Methode
- Status-Enum: `DISCONNECTED`, `AP_ACTIVE`, `CONNECTING`, `CONNECTED`
- Globale Instanz `wifiConnector`, `wifiConnector.begin()` + `.loop()` in `main.cpp`
- [x] **3.3** Test: Erstverbindung mit neuem AP, gespeichertes WLAN nach Neustart automatisch verbinden
- `test_sketches/test_wifi.cpp` erstellt, `test-wifi` Environment in `platformio.ini`
- BOOT-Taste (GPIO 0, 3 s) löscht Credentials und erzwingt Portal
- Erstverbindung via Captive Portal ✅, Auto-Reconnect nach Neustart ✅
---
## Phase 4 Dot-Matrix-Display (`DisplayManager`)
- [x] **4.1** `include/display_manager.h` + `src/display_manager.cpp` erstellen
Implementierung mit rohem `MD_MAX72XX` (nicht MD_Parola) für vollständige Rotationskontrolle:
- Zone 0 (Module 03, obere Reihe): Laserzeit in Minuten
- Zone 1 (Module 47, untere Reihe): Countdown / Statusmeldungen
- `rotateCCW()` kompensiert physische 90°-CW-Verdrehung der Module
- 17 Zeichen-Bitmaps definiert (09, Sonderzeichen, Buchstaben)
- [x] **4.2** Methoden implementiert:
- `showLaserTime(float minutes)` obere Zeile (4 Formate je Wertebereich)
- `showCountdown(int seconds)` untere Zeile, rechtsbündig (während Gratiszeit)
- `showIdle()` ` --` in unterer Zeile wenn kein Countdown aktiv
- `showStatus(const char* msg)` max. 4-Zeichen-Statusmeldung (untere Zeile)
- `setBrightness()`, `allLedsOn()`, `allLedsOff()`, `clear()`, `printToSerial()`
- `update()` in `loop()` aufrufen (no-op, Interface für künftige Animationen)
- `main.cpp` integriert: `display.begin()`, `display.showIdle()`, `display.update()`
- [x] **4.3** Test: `test_sketches/test_display_manager.cpp`, Environment `test-display-mgr`
- Alle LEDs EIN/AUS ✅
- `showLaserTime()` alle 12 Grenzwerte (0.0 9999.0 min) ✅
- `showCountdown()` 5→0 ✅
- `showIdle()`` --`
- `showStatus()` mit „Err ", „AP ", „WiFi", „ oF" ✅
- Realistischer Loop: Laserzeit steigt, Countdown 20→0, dann Idle ✅
---
## Phase 5 Laser-Signaldetektion & Zeit-Tracking (`LaserTracker`)
- [x] **5.1** `include/laser_tracker.h` + `src/laser_tracker.cpp` erstellt
- Globale Instanz `laserTracker`, in `main.cpp` integriert
- [x] **5.2** GPIO-Eingang konfiguriert:
- `INPUT_PULLUP` auf GPIO 4
- Software-Debounce 50 ms (`LASER_DEBOUNCE_MS` aus `config.h`)
- Polarität aus `settings.get().signalPolarity` (LOW_ACTIVE / HIGH_ACTIVE)
- [x] **5.3** Session-Logik implementiert:
- `onSessionStart()` intern → `_sessionActive = true`, Timer setzen
- `onSessionEnd()` intern → Netto-Sekunden berechnen, `_totalMinutesBase` addieren, NVS via `settings.saveTotalMinutes()`
- [x] **5.4** Gratiszeit-Logik:
- Countdown läuft ab Session-Start
- `getSessionSeconds()` gibt erst nach Ablauf der Gratiszeit > 0 zurück
- `getCountdownRemaining()` → verbleibende Sekunden (0 wenn abgelaufen / inaktiv)
- `getTotalMinutes()` = NVS-Basis + laufende Session-Netto-Minuten (Live-Anzeige)
- [x] **5.5** Öffentliche Getter:
- `getTotalMinutes()` → float (Live)
- `getSessionSeconds()` → int (Netto ohne Gratiszeit)
- `getCountdownRemaining()` → int (verbleibende Gratiszeit)
- `isActive()` → bool
- `getLastSessionSeconds()` → int (letzte abgeschlossene Session, für MQTT)
- `resetTotal()` → Gesamtzeit auf 0 + NVS-Speicherung
- [x] **5.6** Test: `test_sketches/test_laser_tracker.cpp`, Environment `test-laser-tracker`
- Button GPIO 4 simuliert Laser-Signal
- BOOT-Taste (GPIO 0, 3 s) löscht Gesamtzeit
- Boot-Output: NVS-Werte geladen, Display zeigt Basiswert + Idle ✅
- Manuelle Verifikation: Button drücken → Countdown, Netto-Zeit, Session-Ende → NVS
---
## Phase 6 MQTT Client (`MqttClient`)
- [x] **6.1** `include/mqtt_client.h` + `src/mqtt_client.cpp` erstellen
Wrapper um `PubSubClient`
- [x] **6.2** Verbindungsaufbau mit Credentials aus `Settings` (TLS automatisch bei Port 8883)
- [x] **6.3** Publish-Methode `publishSession(sessionSec, totalMin, gratisSec)`:
- Topic: `lasercutter/session`
- JSON mit `session_s`, `total_min`, `gratiszeit_s`, `ts`
- Wird von `LaserTracker` beim Session-Ende aufgerufen
- [x] **6.4** Publish-Methode `publishHeartbeat(totalMin, ipStr, uptimeSec)`:
- Topic: `lasercutter/status` (retained)
- Heartbeat alle 30 Sekunden
- LWT `{"online":false}` bei Verbindungsverlust
- [x] **6.5** Subscribe auf `lasercutter/reset`:
- Payload `"1"`, `{"reset":true}` oder `{"reset":1}``LaserTracker::resetTotal()`
- QoS 1
- [ ] **6.6** Reconnect-Logik (Non-blocking, max. alle 10 Sekunden versuchen)
- [ ] **6.7** Test: Session-Daten mit MQTT Explorer empfangen, Reset auslösen und verifizieren
⚠️ Offen: MQTT-Error Modul 5 bei Verbindungsverlust, Reconnect nach WiFi-Ausfall
---
## Phase 7 Webinterface (`WebServer`)
- [x] **7.1** `include/web_server.h` + `src/web_server.cpp` erstellen
Basierend auf `ESPAsyncWebServer`
- [x] **7.2** Route `GET /` Statusseite (HTML):
- Session-Minuten, Gesamtzeit, letzter Burst
- WLAN-Status (IP), MQTT-Status (verbunden/getrennt)
- Broker-Info, Gratiszeit
- Auto-Reload alle 10 s
- [x] **7.3** Route `GET /config` Konfigurationsformular (HTML):
- MQTT Broker IP, Port, User, Passwort
- Gratiszeit (Slider 0120 s)
- Signal-Polarität (Radio-Button)
- [x] **7.4** Route `POST /config` Konfiguration speichern (via `Settings`, NVS)
Redirect + Hinweis „Neustart für MQTT-Änderungen"
- [x] **7.5** Route `POST /reset` Session zurücksetzen (`laserTracker.resetSession()`)
Gesamtzeit (NVS) bleibt erhalten; Wartungsreset nur per BOOT-Taste oder MQTT
- [x] **7.6** ElegantOTA unter `/update` einbinden
Flag `ELEGANTOTA_USE_ASYNC_WEBSERVER=1` in `platformio.ini`
- [ ] **7.7** Test: Alle Seiten im Browser aufrufen, Konfiguration ändern und nach Neustart prüfen
---
## Phase 8 Integration & Hauptprogramm (`main.cpp`)
- [ ] **8.1** `main.cpp` aufräumen und alle Module initialisieren:
- Reihenfolge: `Settings``DisplayManager``WiFiManager``MqttClient``WebServer``LaserTracker`
- [ ] **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
- [ ] **8.4** WLAN/MQTT-Fehlerzustand auf Display spiegeln
- [ ] **8.5** Watchdog Timer aktivieren (ESP32 WDT, 30 s)
---
## Phase 9 OTA & Stabilisierung
- [ ] **9.1** ElegantOTA in `main.cpp` initialisieren und in `loop()` einbinden
- [ ] **9.2** Serielles Logging vereinheitlichen (Log-Level: INFO / DEBUG per `#define`)
- [ ] **9.3** Langzeit-Test: 24h Betrieb, Speicherleck-Check via Serial Monitor (`ESP.getFreeHeap()`)
- [ ] **9.4** Edge-Cases testen:
- Laser aktiv, WLAN wird getrennt → Display läuft weiter
- MQTT-Broker nicht erreichbar → Reconnect
- Reset während aktiver Session
- Neustart nach akkumulierter Zeit → Zeit korrekt aus NVS geladen
---
## Phase 10 Dokumentation & Abschluss
- [ ] **10.1** README.md finalisieren (Screenshots, Schaltplan-Skizze ggf. ergänzen)
- [ ] **10.2** `platformio.ini` mit finalen Library-Versionen dokumentieren
- [ ] **10.3** MQTT-Topics in README.md validieren (mit realem Broker testen)
- [ ] **10.4** Optionaler Schaltplan (Fritzing / ASCII) für Laser-Signal-Anschluss (Optokoppler)
---
## Abhängigkeitsdiagramm (Reihenfolge)
```
Phase 1 (Setup)
└── Phase 2 (NVS/Settings)
├── Phase 3 (WiFi)
├── Phase 4 (Display)
└── Phase 5 (LaserTracker)
└── Phase 6 (MQTT)
└── Phase 7 (Webinterface)
└── Phase 8 (Integration)
└── Phase 9 (OTA/Stabilisierung)
└── Phase 10 (Doku)
```
---
## Verwendete MQTT-Topics (Überblick)
| Topic | Richtung | QoS | Auslöser |
|------------------------|-----------|-----|-------------------------------|
| `lasercutter/session` | Publish | 1 | Session-Ende (Laser inaktiv) |
| `lasercutter/status` | Publish | 0 | Heartbeat alle 60 s |
| `lasercutter/reset` | Subscribe | 1 | Externer Reset-Befehl |
---
*Erstellt: 22. Februar 2026*