MQTT-Display-LaserCutter/Feature-Requests.md

237 lines
16 KiB
Markdown
Raw Permalink 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.

# Feature Requests & Bug Fixes
> Dieses Dokument verfolgt laufende Bug Fixes und kleinere Feature Requests nach Abschluss der Hauptentwicklungsphasen (Implementation-Plan.md Phase 19).
> Größere Änderungen (neue Module, neue Architektur) werden weiterhin im `Implementation-Plan.md` dokumentiert.
> Wird ein offener Punkt erledigt, wird dieser von ## Offen nach ## Erledigt verschoben, mit Details zum Fix/Feature und Commit-Hash und mit der Version in der Überschrift ### Version X.Y.Z, dokumentiert.
---
## Format
```
- [ ] **FR-NNN** Kurzbeschreibung
- Details, Kontext, betroffene Dateien
- Commit: `<hash>` (wird nach Erledigung eingetragen)
```
Status: `[ ]` = offen · `[x]` = erledigt
---
## Offen
---
## Erledigt
### Version 1.6.1
- [x] **FR-017** Bug: Maschinenlaufzeit ist in HA nicht verfügbar (MQTT Discovery Sensor fehlt) ✅
- **Symptom**: Home Assistant zeigte die Maschinenlaufzeit (Gesamtbetriebszeit) nicht als eigenständigen Sensor an.
- **Ursache**: In `publishDiscovery()` fehlte ein dedizierter numerischer Discovery-Sensor für `total_minutes`. Der bestehende Sensor #4 "Gesamtzeit" formatiert den Wert als hh:mm-String (kein numerischer HA-Sensor, keine Langzeitstatistik möglich).
- **Fix**: Neuer Discovery-Sensor #6 "Maschinenlaufzeit" hinzugefügt: `value_template: {{ value_json.total_minutes }}`, `unit_of_measurement: min`, `state_class: total_increasing` → HA kann Statistiken und Diagramme erstellen. Anzahl publizierter Entities von 8 auf 9 erhöht.
- **Betroffene Dateien**: `src/mqtt_client.cpp`
- Commit: `1524a2a`
### Version 1.6.0
- [x] **FR-015** Feature: Sofortiger MQTT-Status-Publish nach jeder Statusänderung ✅
- **Motivation**: HA zeigte veraltete Werte bis zum nächsten Heartbeat. Änderungen durch Webinterface, MQTT-CMD oder lokale Buttons sind nun sofort in HA sichtbar.
- **Umsetzung**: `publishHeartbeat()` von `private` nach `public` verschoben; wird nach Display-Toggle, Session-Reset und Reboot-CMD sofort aufgerufen (zusätzlich zum 10s-Timer). Heartbeat-Intervall von 60s auf 10s reduziert. HA MQTT Discovery Switch: `state_on`/`state_off` ergänzt damit Display-Zustand korrekt angezeigt wird.
- **Betroffene Dateien**: `include/mqtt_client.h`, `src/web_server.cpp`, `src/mqtt_client.cpp`, `include/config.h`
- Commit: `fc5e169`
- Version: 1.6.0
- [x] **FR-016** Feature: Webinterface Live-Aktualisierung ohne Neuladen ✅
- **Motivation**: Nach einer Aktion im Webinterface blieb die Statusseite veraltet bis zum manuellen Reload.
- **Umsetzung**: Neuer WebSocket-Endpunkt `/status-ws` (`AsyncWebSocket`). `sendStatusWs()` wird am Ende von `publishHeartbeat()` aufgerufen deckt damit alle Auslöser ab (10s-Timer, Web-Aktionen, Laser-Statuswechsel). Statusseite und Config-Seite nutzen `/status-ws` für Live-DOM-Updates ohne Reload. Reboot-Button auf Config-Seite wird automatisch deaktiviert wenn Laser aktiv. Alle Action-Buttons (Reboot, WLAN-Reset, Maschinenlaufzeit-Reset) auf `fetch()` umgestellt.
- **Betroffene Dateien**: `include/web_server.h`, `src/web_server.cpp`, `src/mqtt_client.cpp`
- Commit: `fc5e169`
- Version: 1.6.0
### Version 1.5.2
- [x] **FR-014** Bug: Display Störung bei Relais Umschaltung ✅
- **Symptom**: Nach Relais-Umschaltung (Laser an/aus) friert das Display ein oder zeigt Störungen (z.B. Modul M2M4 leer, keine Sekundenanzeige)
- **Ursache**: EMV-Störung durch Relais → SPI-Leitungen fangen Spike auf → MAX7219-interne Register (Decode-Mode, Scan-Limit) werden korrumpiert
- **Fix**: Neue Methode `display.reinit()` in `DisplayManager` — setzt nur Kontroll-Register neu (kein `clear()`), danach wird der zuletzt angezeigte Zustand via `redraw()` sofort neu gezeichnet. State-Tracking in allen `show*()`-Methoden. `LaserTracker::onSessionStart()` und `onSessionEnd()` rufen `display.reinit()` auf.
- **Betroffene Dateien**: `include/display_manager.h`, `src/display_manager.cpp`, `src/laser_tracker.cpp`
- Commit: `6c8be70`
- Version: 1.5.2
---
### Version 1.5.1
- [x] **FR-013** Bug: `binary_sensor` Laser aktiv zeigt "In Betrieb" / "Außer Betrieb" statt "An" / "Aus" ✅
- **Symptom**: HA Tile-Card zeigt unter dem Sensor-Status "Außer Betrieb" statt "Aus"
- **Ursache**: `device_class: running` in der MQTT Discovery-Config erzeugt HA-spezifische Texte ("In Betrieb" / "Außer Betrieb")
- **Fix**: `device_class` aus der `publishDiscovery()`-binary_sensor-Config in `mqtt_client.cpp` entfernt → HA zeigt generisch "An" / "Aus"
- **Betroffene Dateien**: `src/mqtt_client.cpp`
- Commit: `db1fd0b`
- Version: 1.5.1
---
### Version 1.5.0
- [x] **FR-012** Feature: Home Assistant MQTT Discovery (automatische Device-Erkennung ohne configuration.yaml) ✅
- **Ziel**: Der ESP32 soll in Home Assistant als vollständiges Device mit allen Entities automatisch erkannt werden, ohne manuelle Einträge in `configuration.yaml`
- **Mechanismus**: [MQTT Discovery](https://www.home-assistant.io/integrations/mqtt/#mqtt-discovery) Gerät publiziert beim Connect einmalig JSON-Config-Nachrichten an `homeassistant/<typ>/lasercutter-display/<entity>/config`
- **Implementiert**:
- `MQTT_TOPIC_AVAILABILITY` + `MQTT_DISCOVERY_PREFIX` in `config.h`
- LWT auf `lasercutter/availability` (`offline`), nach Connect `online` (retained)
- `publishDiscovery()`: 8 retained Entities nach jedem (Re-)Connect
- Heartbeat: neue Felder `laser_active`, `session_minutes_sum`, `session_seconds`, `total_minutes` für HA `value_template`
- PubSubClient Buffer: 512 → 1024 Bytes
- **Entities in HA** (automatisch gruppiert):
- Steuerelemente: Display (switch)
- Sensoren: Laser aktiv, Laserzeit Aktuell, Laserzeit Summe
- Konfiguration: Neustart, Session zurücksetzen (buttons)
- Diagnose: Firmware
- Betroffene Dateien: `src/mqtt_client.cpp`, `include/mqtt_client.h`, `include/config.h`, `platformio.ini`
- Commit: `ae3e40f`
- Version: 1.5.0
---
### Version 1.4.1
- [x] **FR-011** Bug: PANIC/EXCEPTION durch Heap-Korruption bei MQTT-Verbindungsabbruch (TLS) ✅
- **Symptom**: ESP32 crasht mit `CORRUPT HEAP: Bad tail` + `assert failed: multi_heap_free` wenn der MQTT-Broker nicht erreichbar ist und eine bestehende TLS-Session unerwartet abbricht
- **Fehlermeldung**: `(-76) UNKNOWN ERROR CODE (004C)` = `MBEDTLS_ERR_NET_CONN_RESET`
- **Crash-Kette**:
1. Broker bricht TLS-Verbindung ab (Connection reset by peer)
2. `PubSubClient::connected()``WiFiClientSecure::connected()``available()`
3. `available()` erkennt EOF → ruft intern `stop()``stop_ssl_socket()` auf
4. `mbedtls_ssl_free()` wird auf einem bereits inkonsistenten SSL-Kontext aufgerufen
5. Heap-Corruption → `multi_heap_free` assert → PANIC
- **Backtrace-Frames**: `multi_heap_free``heap_caps_free``esp_mbedtls_mem_free``mbedtls_free``mbedtls_ssl_free``stop_ssl_socket``WiFiClientSecure::stop()``WiFiClientSecure::available()``WiFiClientSecure::connected()``PubSubClient::connected()``MqttClient::_taskLoop()`
- **Root Cause**: `WiFiClientSecure`-Objekt wird nach einem TLS-Verbindungsabbruch nicht neu erstellt — der interne mbedTLS-State ist korrupt, beim nächsten `connected()`-Aufruf crasht `mbedtls_ssl_free()`
- **Fix**: `_rebuildClient()` — zerstört und erstellt `WiFiClientSecure`, `WiFiClient` und `PubSubClient` vor jedem Reconnect-Versuch neu auf Core 0. mbedTLS startet damit immer mit sauberem Heap-Kontext. Broker/Port werden in `_broker`/`_port` gecacht.
- Betroffene Dateien: `src/mqtt_client.cpp`, `include/mqtt_client.h`
- Commit: `1ef0464`
- Version: 1.4.1
### Version 1.4.0
- [x] **FR-010** Feature: Webinterface-Redesign + MQTT-Steuerung ✅
- **`/` Laser Cutter Status** (öffentlich, kein Auth)
- Seitentitel `Laser Cutter Status`, bereinigte Tabelle: Laserzeit Summe, Laserzeit Aktuell (m:ss), Laserstatus (an/aus), Gratiszeit, Firmware
- Button `Summe Laserzeit zurücksetzen` (kein Auth, bewusst öffentlich)
- Button `💡 Display ausschalten/einschalten` Toggle via `fetch()` ohne Seitenwechsel, Route `POST /display-toggle` antwortet 204
- Button `🔒 Laser Cutter Setup & Status``/config`
- Auto-Refresh alle 10 s
- **`/config` Laser Cutter Setup & Status** (Auth erforderlich)
- H2: Laser Cutter Status Maschinenlaufzeit h:mm:ss, roter Reset-Button (`/reset-total`)
- H2: MQTT Broker, H2: Webzugang, WLAN-Abschnitt
- H2: Gerät Reboot-Button `🔄 ESP32 neu starten` (grau, bei aktivem Laser `disabled`)
- H2: Tools OTA Update, Log Console
- Button `← Laser Cutter Status` zurück auf `/`
- **MQTT-Steuerung**: `lasercutter/reset` ersetzt durch `lasercutter/cmd`
- `{"reset_session_nvs":true}` Session-Summe + NVS-Maschinenlaufzeit auf 0
- `{"reset_session_ram":true}` nur RAM-Session-Summe auf 0, NVS bleibt
- `{"display":true/false}` Display ein-/ausschalten
- `{"reboot":true}` ESP32 neu starten
- **Heartbeat `lasercutter/status`**: Feld `"display_on": true/false` hinzugefügt
- **`DisplayManager`**: `setEnabled()` / `isEnabled()` via MAX7219 SHUTDOWN-Modus
- Betroffene Dateien: `src/web_server.cpp`, `src/mqtt_client.cpp`, `include/config.h`, `include/display_manager.h`, `src/display_manager.cpp`
- Commit: `c61a67f`
- Version: 1.4.0
### Version 1.2.0
- [x] **FR-009** Bug: `session_start_time` bei nachgelieferten Sessions (Queue) falsch ✅
- Bei Offline-Puffer (Queue) wird `session_start_time` erst beim Publish aus `laserTracker.getSessionStartTime()` gelesen → zeigt immer die **aktuelle** Session-Startzeit, nicht die der gepufferten Session
- Beispiel: Session A (22:00) läuft offline, Session B (22:10) beendet, dann Reconnect → beide Sessions wurden mit Startzeit von Session B publiziert
- Fix: `SessionPayload`-Struct um `time_t startTime` erweitert; Startzeit schon in `publishSession()` via `laserTracker.getSessionStartTime()` in den Payload geschrieben
- Betroffene Dateien: `include/mqtt_client.h` (`SessionPayload`), `src/mqtt_client.cpp` (`publishSession`, `_doPublishSession`)
- Commit: `aae34fe`
- [x] **FR-007** Feature: Laufende Session-ID in MQTT Session-Payload ✅
- Jede Session erhält eine aufsteigende Ganzzahl (`session_id`), die im MQTT-Payload `MQTT_TOPIC_SESSION` mitgesendet wird
- Empfänger (z. B. Home Assistant, Node-RED) kann fehlende Sessions sofort erkennen: Lücke zwischen `session_id` 47 und 49 → Session 48 ging verloren
- Zähler wird im RAM gehalten (kein NVS nötig), startet bei 0 nach jedem Reboot — Lücke beim Reboot ist akzeptabel
- Zähler wird auf 0 zurückgesetzt bei `{"reset":true}` und `{"reset_session":true}` via MQTT
- Betroffene Dateien: `include/mqtt_client.h` (`SessionPayload`, `_sessionCounter`, `resetSessionCounter()`), `src/mqtt_client.cpp`
- Commit: `c40668f`
### Version 1.1.1
- [x] **FR-006** Bug: MQTT Session-Publish nicht zuverlässig bei Verbindungsausfall ✅
- **Bug A Race Condition**: `_pendingSession = false` vor `_client->publish()` → bei Abbruch gehen Session-Daten still verloren
- **Bug B Nur 1 Slot**: volatile Einzelslot überschreibt ältere Session bei mehreren Offline-Sessions
- Fix: `QueueHandle_t` mit 128 Slots; `xQueuePeek` + Dequeue erst nach erfolgreichem Publish → kein Datenverlust
- Betroffene Dateien: `src/mqtt_client.cpp`, `include/mqtt_client.h`
- Commit: `9650891`
### Version 1.1.0
- [x] **FR-008** Bug: Reset-Befehle setzen Maschinenlaufzeit (NVS) ungewollt zurück ✅
- **Bug A MQTT `{"reset":true}`** ruft `resetTotal()` auf → `settings.saveTotalMinutes(0.0f)` → Maschinenlaufzeit (Gesamtbetriebszeit) wird **unwiderruflich aus dem NVS gelöscht**
- Gewünscht: `{"reset":true}` soll nur die Session-Summe (Tages-/Wochenzähler) zurücksetzen, nicht die Gesamtbetriebszeit
- `resetTotal()` sollte gar nicht per MQTT erreichbar sein (oder einen separaten, expliziten Befehl erfordern, z.B. `{"reset_total":true}`)
- **Bug B Web-Button "Summe Laserzeit zurücksetzen"** ruft `resetSessionSum()` auf → NVS wird nicht angefasst, aber `_sessionNetSec = 0``getTotalMinutes()` sinkt sofort in der Anzeige (weil `_sessionNetSec`-Anteil verschwindet), obwohl der NVS-Wert korrekt bleibt. Täuscht einen Gesamt-Reset vor.
- Fix: `_totalMinutesBase` beim `resetSessionSum()` um den bereits akkumulierten `_sessionNetSec`-Anteil erhöhen, bevor `_sessionNetSec` auf 0 gesetzt wird → Kontinuität der Gesamtzeit sicherstellen
- Betroffene Dateien: `src/laser_tracker.cpp` (`resetSessionSum`, `resetTotal`), `src/mqtt_client.cpp` (MQTT-Reset-Handler)
- Commit: `c636add`
### Version 1.?.?
- [x] **FR-005** Bug: WDT-Crash + Display-/Browser-Freeze durch blockierenden TLS-Handshake ✅
- MQTT `reconnect()` mit TLS (Port 8883) blockiert `loop()` auf Core 1 bis zu 15 s
- Folge: Task-Watchdog (30 s) feuert wenn zwei Reconnect-Versuche in einem 30s-Fenster → Neustart mit `WATCHDOG (Task)`
- Zusätzlich: Display-Updates, WebServer-Responses und WiFi-Stack auf Core 1 eingefroren während TLS-Handshake
- Fix: `MqttClient` komplett auf FreeRTOS-Task (Core 0) ausgelagert
- `begin()` startet nur Task (`xTaskCreatePinnedToCore`, Stack 16 KB), kein Netzwerk-Zugriff auf Core 1
- `WiFiClientSecure`/mbedtls wird **ausschließlich auf Core 0** initialisiert und verwendet (Cross-Core-Heap-Korruption vermieden)
- `mqttClient.loop()` in `main.cpp` ist No-Op alle MQTT-Arbeit im Task
- `publishSession()` von Core 1 safe: setzt nur `volatile`-Flags, Task auf Core 0 führt den Publish aus
- Version: 1.1.0 → 1.1.1
- Commit: `a7c6edb` (Task auf Core 0), `b91b3ca` (Heap-Fix: Objekte per new auf Core 0)
### Version 1.?.?
- [x] **FR-002** Web Console serieller Monitor über Browser (WebSocket) ✅
- Route `/log` liefert HTML-Seite mit automatisch scrollendem Terminal (dunkles Theme)
- WebSocket-Endpunkt `/log-ws` via `AsyncWebSocket` im bestehenden ESPAsyncWebServer
- `LOG_I`/`LOG_E`/`LOG_D` in `config.h` formatieren in lokalen Puffer und rufen `webLogForward()` auf
- `webLogForward()` sendet per `ws.textAll()` läuft nativ in AsyncTCP, kein Konflikt
- "Log Console"-Button auf Statusseite / automatischer WebSocket-Reconnect nach Trennung
- Commit: `4dd4ce0`
### Version 1.?.?
- [x] **FR-004** Bug: `session_sum` im MQTT-Heartbeat falsch (Binärzahl statt Dezimal) ✅
- `serialized(String(getAllSessionsSumMinutes(), 2))``String(int, basis)` interpretiert 2. Argument als **Basis**, nicht Dezimalstellen
- Beispiel: 18 Min → `"10010"` (18 in Binär), 20 Min → `"10100"`
- Fix: `doc["session_sum"] = laserTracker.getAllSessionsSumMinutes()` direkt als JSON-Integer, kein `serialized()` nötig
- `machine_running_time_min` war korrekt: `String(float, 2)` = 2 Dezimalstellen anderer Overload
- Version: 1.0.1 → 1.0.2
- Commit: `83537e3`
### Version 1.?.?
- [x] **FR-003** Bug: NTP Zeitzone falsch (UTC statt CET/CEST) ✅
- `configTime(0, 0, ...)` lieferte UTC → Anzeige 1h zu früh (CET) / 2h zu früh (CEST)
- `wifi_connector.cpp`: `configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "pool.ntp.org", "time.nist.gov")` automatische Sommerzeit
- `mqtt_client.cpp`: `gmtime()``localtime()`, `Z`-Suffix entfernt Payload zeigt Lokalzeit
- Version: 1.0.0 → 1.0.1
- Commit: `3a31082`
### Version 1.?.?
- [x] **FR-001** Firmware-Version auf Webseite und im MQTT-Status-Payload
- Definition: `FIRMWARE_VERSION` als `build_flags` in `platformio.ini` `[env]`-Basisblock (Single Source of Truth)
- Fallback `#define FIRMWARE_VERSION "0.0.0"` in `config.h` (Arduino IDE ohne build_flags)
- Format: `1.0.0 (Feb 26 2026)` Version + C++-Makro `__DATE__` (Compile-Zeitpunkt)
- Web: Firmware-Zeile in der Statustabelle auf `/` + Footer auf `/config`
- MQTT: `"firmware_version": "1.0.0 (Feb 26 2026)"` im `lasercutter/status`-Payload
- Commit: `46a8c59`
---
*Erstellt: 26. Februar 2026*
*Zuletzt aktualisiert: 28. Februar 2026*