- Implementation-Plan.md: Phase 10 alle Tasks als erledigt markiert - README.md: Implementierungsstand-Tabelle aktualisiert (Phase 8-10 abgeschlossen)
397 lines
20 KiB
Markdown
397 lines
20 KiB
Markdown
# 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 0–3) = obere Reihe, Zone 1 (0-idx 4–7) = 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 (0–120 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 0–3, obere Reihe): Laserzeit in Minuten
|
||
- Zone 1 (Module 4–7, untere Reihe): Countdown / Statusmeldungen
|
||
- `rotateCCW()` kompensiert physische 90°-CW-Verdrehung der Module
|
||
- 17 Zeichen-Bitmaps definiert (0–9, 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
|
||
|
||
- [x] **5.7** `showSessionRing(int seconds)` – Kreisanzeige auf Modulen 5–7, 12-Uhr-Start, Uhrzeigersinn (Phase-5-Erweiterung)
|
||
- 60 LEDs auf 3 Modulen (je 8×8, aber nur die äußere Reihe vollständig umlaufend)
|
||
- Jede Sekunde NET_COUNTING: eine weitere LED zugeschaltet
|
||
- Nach 60 Sekunden: Kreis voll, Minutenanzeige (Module 1–3) inkrementiert
|
||
- Laser-AUS: Anzeige zurück auf `--`
|
||
- Implementierung in `display_manager.h/cpp`
|
||
- Aufruf in `main.cpp` während `NET_COUNTING` statt `showIdle()`
|
||
|
||
---
|
||
|
||
## 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, summeSession, gratisSec)`:
|
||
- Topic: `lasercutter/session`
|
||
- JSON-Felder (aktualisiert):
|
||
- `session_minutes` (int, ceiling: 62 s = 2 min)
|
||
- `session_seconds` (int, Rohwert Netto-Sekunden)
|
||
- `freetime_s` (int)
|
||
- `ip` (string)
|
||
- Kein `total_min`, kein `session_start_time` (NTP später)
|
||
- Wird von LaserTracker-Logik in main aufgerufen
|
||
|
||
- [x] **6.4** Publish-Methode `publishHeartbeat(totalMin, ipStr, uptimeSec)`:
|
||
- Topic: `lasercutter/status` (retained)
|
||
- Heartbeat alle 30 Sekunden
|
||
- LWT `{"online":false}` bei Verbindungsverlust
|
||
- JSON-Felder (aktualisiert):
|
||
- `online` (bool)
|
||
- `session_sum` (string, Summe Session in Minuten als string mit 2 Dezimalen)
|
||
- `machine_running_time_min` (string, Maschinenlaufzeit aus NVS)
|
||
- `ip` (string)
|
||
- `uptime_s` (int)
|
||
|
||
- [x] **6.5** Subscribe auf `lasercutter/reset`:
|
||
- Payload `"1"`, `{"reset":true}` oder `{"reset":1}` → `LaserTracker::resetTotal()` (NVS + RAM)
|
||
- Payload `{"reset_session":true}` → `LaserTracker::resetSessionSum()` (nur RAM, NVS bleibt)
|
||
- QoS 1
|
||
|
||
- [x] **6.6** Reconnect-Logik (Non-blocking, max. alle 10 Sekunden versuchen)
|
||
|
||
- [x] **6.7** Session-Publish bei Reset: `consumeSessionReset()` in `main.cpp` → ruft `publishSession()` mit akkumulierten Sekunden auf
|
||
- Identisches JSON-Format wie normales Session-Ende
|
||
|
||
---
|
||
|
||
## 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):
|
||
- Summe Session (Minuten, RAM), Maschinenlaufzeit gesamt (NVS)
|
||
- 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 0–120 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-Summe zurücksetzen (`laserTracker.resetSessionSum()`)
|
||
- Gesamtzeit (NVS) bleibt erhalten; Wartungsreset nur per MQTT `{"reset":true}` oder `resetTotal()`
|
||
- Bug fix: `resetSessionSum()` setzt laufende Session-Timer korrekt zurück (vorher: `getAllSessionsSumMinutes()` blieb > 0)
|
||
|
||
- [x] **7.6** ElegantOTA unter `/update` einbinden
|
||
Flag `ELEGANTOTA_USE_ASYNC_WEBSERVER=1` in `platformio.ini`
|
||
|
||
- [x] **7.7** Button-Layout der Statusseite überarbeitet:
|
||
- Alle 3 Buttons (Session zurücksetzen, Konfiguration, OTA Update) übereinander, gleich breit (`width:100%`)
|
||
- Alle Buttons einheitlich blau (`btn-primary`), `btn-danger` entfernt
|
||
- Flex-Layout mit `gap:.6rem` für gleichmäßigen Abstand
|
||
|
||
- [x] **7.8** Button-Layout der Konfigurationsseite vereinheitlicht:
|
||
- `Speichern & Zurück` und `Abbrechen` nun gleich breit (`width:100%` über `.btn`-Klasse)
|
||
- Identisches Flex-Spalten-Layout wie Statusseite (`display:flex;flex-direction:column;gap:.6rem;margin-top:1.5rem`)
|
||
- Farben beibehalten: Blau (`#3182ce`) = Speichern, Grau (`#718096`) = Abbrechen
|
||
|
||
---
|
||
|
||
## Phase 8 – Integration & Hauptprogramm (`main.cpp`)
|
||
|
||
- [x] **8.1** `main.cpp` aufräumen und alle Module initialisieren:
|
||
- Reihenfolge: `Settings` → `DisplayManager` → `LaserTracker` → `WiFiConnector` (non-blocking) → Watchdog
|
||
- MQTT + WebServer: Lazy-Init im `loop()` beim ersten WiFi-Connect
|
||
- **Änderung gegenüber ursprünglicher Planung:** LaserTracker startet vor WiFi (Priorität 1)
|
||
|
||
- [x] **8.2** `loop()` implementiert (non-blocking, kein `delay()` außer 20 ms am Ende):
|
||
- Priorität 1: `laserTracker.loop()` (immer zuerst)
|
||
- Priorität 2: `wifiConnector.loop()` (non-blocking)
|
||
- Lazy-Init: MQTT + WebServer beim ersten `wifiConnector.isConnected()`
|
||
- MQTT: `mqttClient.loop()`, Session-Publish nach `consumeSessionEnd/Reset()`
|
||
- Display rate-limited: W 500 ms-Blinker, Minuten-Update bei Änderung oder alle 60 s, Sekunden-Update alle 1 s, M nur bei Statuswechsel
|
||
|
||
- [x] **8.3** Callback von `LaserTracker` → `MqttClient.publishSession(...)` verdrahtet
|
||
|
||
- [x] **8.4** WLAN/MQTT-Fehlerzustand auf Display gespiegelt (rate-limited)
|
||
|
||
- [x] **8.5** Watchdog Timer aktivieren (ESP32 WDT, 30 s)
|
||
|
||
---
|
||
|
||
## Phase 9 – OTA & Stabilisierung ✅ abgeschlossen
|
||
|
||
- [x] **9.1** ElegantOTA via `web_server.cpp` eingebunden (kein separater `loop()`-Aufruf nötig)
|
||
- `ELEGANTOTA_USE_ASYNC_WEBSERVER=1` → vollständig async, kein `ElegantOTA.loop()` in `main.cpp`
|
||
- **Bugfix:** Include-Konflikt `ESPAsyncWebServer` ↔ `WiFiManager` (HTTP_GET/POST-Enum-Clash)
|
||
gelöst via PIMPL-Pattern: `web_server.h` nur Forward-Declaration `AsyncWebServer*`,
|
||
vollständiger `#include <ESPAsyncWebServer.h>` ausschließlich in `web_server.cpp`
|
||
|
||
- [x] **9.2** Serielles Logging vereinheitlichen: `LOG_D`-Makro in `config.h` ergänzt
|
||
- Aktiv wenn `CORE_DEBUG_LEVEL >= 3`, sonst no-op
|
||
|
||
- [x] **9.3** Speicherleck-Monitoring: `ESP.getFreeHeap()` alle 30 s via `LOG_D` in `loop()`
|
||
|
||
- [x] **9.4** NTP-Zeitsynchronisation + `session_start_time` im MQTT-Session-Payload
|
||
- `configTime(0, 0, "pool.ntp.org", "time.nist.gov")` in `wifi_connector.cpp` nach WLAN-Connect (auch Reconnect)
|
||
- `waitForNtp()` blockiert max. 5 s bis Sync bestätigt (`getLocalTime()`), loggt Ergebnis via `LOG_I`
|
||
- `LaserTracker::onSessionStart()` speichert `time(nullptr)` → `_sessionStartTime`
|
||
- `publishSession()` formatiert UTC-Zeitstempel als ISO-8601 (`strftime`, `gmtime`)
|
||
- Fallback `"unknown"` wenn NTP beim Session-Start noch nicht synchronisiert
|
||
- Verifiziert: `{"session_start_time":"2026-02-23T...Z"}` im MQTT-Payload bestätigt
|
||
|
||
- [x] **9.5** Edge-Cases manuell getestet (Integrations-Hardware-Test):
|
||
- Laser aktiv, WLAN getrennt → Display läuft weiter ✅
|
||
- MQTT-Broker nicht erreichbar → Reconnect ✅
|
||
- Reset während aktiver Session ✅
|
||
- Neustart nach akkumulierter Zeit → Zeit korrekt aus NVS geladen ✅
|
||
|
||
- [x] **9.6** Webinterface mit HTTP-Basic-Auth abgesichert
|
||
- `webUser` + `webPassword` in `Settings`-Struct + NVS-Keys in `config.h`
|
||
- `saveWebCredentials()` in `SettingsManager`
|
||
- `WebServerManager::requireAuth()` – prüft Auth, sendet 401 wenn nicht autorisiert
|
||
- Standard: `webUser = "admin"`, `webPassword = ""` (leer = kein Schutz, rückwärtskompatibel)
|
||
- Alle Routen (`/`, `/config`, `/reset`) + ElegantOTA (`ElegantOTA.setAuth()`) geschützt
|
||
- Passwort änderbar über `/config`-Formular (neuer Abschnitt "Web-Zugang")
|
||
- Forward-Declaration `AsyncWebServerRequest` in `web_server.h` ergänzt (Fix Compile-Fehler)
|
||
- **Bug fix:** `requestAuthentication()` sendet standardmäßig Digest Auth; überschrieben mit `false` für Basic Auth
|
||
- **Bug fix:** `webPassword[32]` zu `webPassword[64]` vergrößert (Passwort-Abschneiden bei 31 Zeichen)
|
||
- **Bug fix:** POST `/config` überschrieb Passwort immer mit leerem String; leeres Feld = unveraendert lassen; `clear_auth`-Checkbox für explizites Löschen
|
||
|
||
- [x] **9.7** `resetSession()` umbenannt in `resetSessionSum()` (klarere Semantik)
|
||
- `_sessionResetPending` Flag + `consumeSessionReset()` nach `consumeSessionEnd()`-Muster
|
||
- Vor Reset: akkumulierte Netto-Sekunden in `_lastSessionSec` gesichert
|
||
- `main.cpp`: `consumeSessionReset()` → `publishSession()` mit gesicherten Sekunden
|
||
- MQTT `{"reset_session":true}` löst `resetSessionSum()` aus
|
||
- Display zeigt nach Reset sofort 0 (alle Zeitakkumulatoren zurückgesetzt)
|
||
|
||
- [x] **9.8** WiFi-Architektur: vollständig non-blocking (Proposal B+D)
|
||
- **Problem:** `wifiConnector.begin()` blockierte bis zu 2 Minuten → LaserTracker startete nie
|
||
- `WiFiManager` als Pointer (`WiFiManager*`), lazy `new` in `begin()` (verhindert Crash durch globale Konstruktoren)
|
||
- `WiFi.mode(WIFI_STA)` vor Credentials-Prüfung; `_wm->getWiFiSSID(true)` statt `WiFi.SSID()` (erfordert gestarteten Stack)
|
||
- Credentials vorhanden: `WiFi.begin()` non-blocking, Ergebnis in `loop()` geprüft
|
||
- Keine/falsche Credentials: `setConfigPortalBlocking(false)` + `setConnectTimeout(1)` → Portal sofort, Verarbeitung via `_wm->process()` in `loop()`
|
||
- Nach 3 Fehlversuchen (je 15 s): `startConfigPortal()` → User kann neue Credentials eingeben
|
||
- `onConnect()`: `_wm->stopWebPortal()` vor AsyncWebServer-Start (verhindert Port-80-Konflikt)
|
||
- MQTT + WebServer: Lazy-Init erst bei erstem WiFi-Connect
|
||
- Display: rate-limited (W 500 ms-Blinker, Minuten bei Änderung/60 s, Sekunden 1 s, M bei Statuswechsel)
|
||
- Verifiziert: LaserTracker läuft sofort nach Neustart, unabhängig vom WiFi-Status ✅
|
||
|
||
- [x] **9.9** ArduinoOTA Integration + platformio.ini Refaktorierung
|
||
- `ArduinoOTA` in `web_server.cpp` integriert (ESP32 built-in, kein lib_deps-Eintrag nötig)
|
||
- Hostname: `lasercutter-display` → erscheint in Arduino IDE als Netzwerk-Port `lasercutter-display at <IP>`
|
||
- Passwort: `ArduinoOTA.setPassword(cfg.webPassword)` – gleiche Auth wie Webinterface, kein separates OTA-Passwort
|
||
- `WebServerManager::loop()` hinzugefügt → `ArduinoOTA.handle()` wird in `main.cpp` via `webServer.loop()` aufgerufen
|
||
- `platformio.ini` auf gemeinsamen `[env]`-Basisblock umgestellt (lib_deps + build_flags einmalig, alle Environments erben)
|
||
- Neues Environment `az-delivery-devkit-v4-ota`: `upload_protocol = espota`, IP konfigurierbar
|
||
- Neues Environment `az-delivery-devkit-v4-ota-http`: ElegantOTA HTTP-Fallback via `upload_ota.py` extra_script
|
||
- `upload_flags = --auth=...`: Kommentar + Anleitung für Passwort-Übergabe an espota (inkl. Umgebungsvariable-Option)
|
||
- OTA-Passwort-Authentifizierung verifiziert: Upload via `pio run -e az-delivery-devkit-v4-ota --target upload` erfolgreich ✅
|
||
- README.md: Abschnitt "Via WiFi (OTA)" mit vollständiger Anleitung ergänzt
|
||
|
||
---
|
||
|
||
## Phase 10 – Dokumentation & Abschluss ✅ abgeschlossen
|
||
|
||
- [x] **10.1** README.md finalisiert
|
||
- Alle Abschnitte vollständig: Hardware, Pinbelegung, Display, Laser-Signaldetektion, Zeit-Tracking, MQTT, Webinterface, Konfiguration, WiFi-Setup, OTA, Fehlerverhalten, Bibliotheken, Build & Flash
|
||
- Screenshots eingefügt (Hauptansicht, Konfiguration, OTA-Seite)
|
||
- Build & Flash: USB und Via WiFi (OTA) dokumentiert inkl. `upload_flags --auth` Anleitung
|
||
- Projektstruktur, Implementierungsstand-Tabelle, Commit-Konventionen dokumentiert
|
||
|
||
- [x] **10.2** `platformio.ini` mit finalem `[env]`-Basisblock konsolidiert
|
||
- Alle lib_deps einmalig im gemeinsamen Block, keine Duplizierung über Environments
|
||
- Alle Environments (`USB`, `OTA-espota`, `OTA-HTTP`, Test-Environments) vollständig
|
||
|
||
- [x] **10.3** MQTT-Topics verifiziert (realer Hardware-Test)
|
||
- `lasercutter/session` nach Laser-AUS ✅
|
||
- `lasercutter/status` Heartbeat alle 60 s ✅
|
||
- `lasercutter/reset` Payload `{"reset":true}` und `{"reset_session":true}` ✅
|
||
- TLS (Port 8883) automatisch aktiv wenn konfiguriert ✅
|
||
|
||
- [x] **10.4** Schaltbild in README.md als ASCII-Diagramm dokumentiert
|
||
- SPI-Ketten-Schaltbild (8 Module, 4×2-Anordnung) vollständig in README.md
|
||
|
||
---
|
||
|
||
## 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) oder Session-Reset |
|
||
| `lasercutter/status` | Publish | 0 | Heartbeat alle 60 s |
|
||
| `lasercutter/reset` | Subscribe | 1 | `{"reset":true}` = Gesamt-Reset (NVS+RAM), `{"reset_session":true}` = nur Session-Summe (RAM) |
|
||
|
||
### MQTT JSON-Formate
|
||
|
||
**`lasercutter/session`**
|
||
```json
|
||
{
|
||
"session_minutes": 2,
|
||
"session_seconds": 125,
|
||
"session_start_time": "2026-02-23T12:34:56Z",
|
||
"freetime_s": 20,
|
||
"ip": "192.168.1.100"
|
||
}
|
||
```
|
||
|
||
**`lasercutter/status`** (retained)
|
||
```json
|
||
{
|
||
"online": true,
|
||
"session_sum": "2.08",
|
||
"machine_running_time_min": "1234.75",
|
||
"ip": "192.168.1.100",
|
||
"uptime_s": 3600
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
*Erstellt: 22. Februar 2026*
|