# JC4880P443C-I-W Touch-Display — UART-Protokoll & Architektur Status: Entwurf v1.0 der Doku · Firmware-Protokollversion: **1** (vorhanden, abgeschaltet) · Ziel-Protokollversion: **2** (geplante Erweiterungen) Diese Datei beschreibt die Anbindung des Touch-Displays **JC4880P443C-I-W** an die Siebträger-Steuerung (`Dual_PID_FastHeatUp.ino`). Sie dokumentiert den **bereits vorhandenen** UART-Code, bewertet ihn und legt die geplanten Erweiterungen fest. Es wird **noch kein Code geändert** — diese Doku ist die Grundlage dafür. --- ## 1. Architektur-Überblick Bewusste Designentscheidung (siehe Projektbesprechung): - Das **Webinterface bleibt auf der Hauptplatine** (ESP32-S3). Es bleibt unverändert die zentrale Steuerungs- und Konnektivitätsinstanz. - Das **JC-Display ist ein vollwertiges Touch-Bediengerät**, das über UART angebunden ist und denselben Funktionsumfang bietet wie das Webinterface — aber **kein** eigenes WLAN/Webserver betreibt. - Das **OLED (SH1106G) bleibt optional** und wird wie bisher über ein **Compile-Flag** gewählt (`ENABLE_DISPLAY`). Es ist unabhängig vom JC-Display. ``` +----------------------------+ UART (GPIO43/44) +---------------------------+ | Hauptplatine ESP32-S3 | <----------------------------------> | JC4880P443C-I-W | | Dual_PID_FastHeatUp.ino | JSON-Zeilen, 230400 Baud, 8N1 | ESP32-P4 (UI/LVGL) | | | | + ESP32-C6 (WLAN/BT)* | | - PID-Regelung (Wasser/ | S3 -> P4: state / ack / error | | | Dampf), SSRs, Sensoren | P4 -> S3: op-Kommandos | - Touch-Bedienung | | - Waage, Profile, EEPROM | | - Statusanzeige | | - WLAN + Webinterface | | - Einstellungen | | - OLED (optional) | | ST7701 / GT911 / ES8311 | +----------------------------+ +---------------------------+ * Der ESP32-C6 auf dem JC-Display (WLAN/BT) wird in diesem Projekt NICHT für die Maschinensteuerung benötigt. WLAN/Webinterface laufen weiterhin auf der S3. ``` ### Rollenverteilung | Aufgabe | S3 (Hauptplatine) | P4 (JC-Display) | |---|---|---| | Echtzeit-Regelung (PID, SSR, Sicherheit) | **ja** | nein | | Sensoren (NTC, MAX6675, Waage) | **ja** | nein | | Persistenz (EEPROM, FFat-Profile) | **ja** | nein | | WLAN + Webinterface | **ja** | nein | | Touch-UI / Bedienoberfläche | nein | **ja** | | OLED-Statusanzeige (optional) | **ja** (Compile-Flag) | — | | "Quelle der Wahrheit" für den Zustand | **ja** | spiegelt nur | **Wichtig:** Die S3 bleibt die alleinige Quelle der Wahrheit. Das P4-Display hält keinen eigenen, abweichenden Zustand, sondern stellt den von der S3 gepushten `state` dar und schickt Bedienwünsche als Kommandos. So bleiben Web-UI, OLED und Touch-Display konsistent. --- ## 2. Hardware-Anbindung | Parameter | Wert | Quelle im Code | |---|---|---| | UART-Instanz | `Serial1` | `HardwareSerial& touchUart = Serial1;` | | TX (S3 -> P4) | **GPIO43** | `#define TOUCH_UART_TX_PIN 43` | | RX (S3 <- P4) | **GPIO44** | `#define TOUCH_UART_RX_PIN 44` | | Baudrate | 230400 | `#define TOUCH_UART_BAUDRATE 230400` | | Format | 8N1 | `touchUart.begin(..., SERIAL_8N1, RX, TX)` | | Logikpegel | 3,3 V | ESP32-S3 / ESP32-P4 beide 3,3 V — **keine Pegelwandlung nötig** | ### Verkabelung (gekreuzt) ``` S3 GPIO43 (TX) -----> P4 RX S3 GPIO44 (RX) <----- P4 TX S3 GND ------ P4 GND (gemeinsame Masse zwingend) ``` > **Hinweis (bewusst akzeptiert):** GPIO43/44 sind beim ESP32-S3 zugleich die Standard- > Pins der seriellen Debug-Konsole (UART0). Solange das JC-Display dort angeschlossen ist, > steht die klassische serielle Debug-Ausgabe an diesen Pins nicht parallel zur Verfügung. > Debug/Logging erfolgt dann über USB-CDC bzw. das Webinterface. Diese Pins wurden > ausdrücklich beibehalten, weil sie auf der Platine herausgeführt sind. --- ## 3. Transport & Framing - **Zeilenbasiert:** Eine Nachricht = eine kompakte JSON-Zeile, abgeschlossen mit `\n`. `\r` wird empfangsseitig ignoriert. - **Max. Zeilenlänge (RX):** `TOUCH_UART_MAX_LINE_LEN` = 1024 Byte. Längere Zeilen werden verworfen und mit einem `error` (`"Zeile zu lang"`) quittiert (Overflow-Schutz). - **State-Puffer (TX):** `TOUCH_UART_JSON_BUFFER_SIZE` = 2400 Byte (`snprintf`-basiert). - **Encoding:** ASCII/UTF-8. Strings werden escaped (`jsonEscapeForUart`, `escapedStatusText` etc.): `\`, `"`, `\n`, `\r`, `\t`. - **Kein Binär-Frame, kein CRC in v1.** (CRC/Handshake siehe v2-Erweiterungen.) ### Nachrichtenrichtungen | Richtung | `type`/`op` | Bedeutung | |---|---|---| | **S3 -> P4** | `type:"state"` | Vollständiger Zustands-Push (Telemetrie) | | **S3 -> P4** | `type:"ack"` | Bestätigung eines Kommandos (`ok:true/false`, `message`) | | **S3 -> P4** | `type:"error"` | Fehler (Parsing, unbekannter op, Overflow) | | **S3 -> P4** | `type:"profiles"` | Liste der Profilnamen (Antwort auf `listProfiles`) | | **S3 -> P4** | `type:"usageStats"` | Nutzungsstatistik (Antwort auf `getUsageStats`) | | **P4 -> S3** | `op:"..."` | Kommando vom Display (siehe §6) | Jedes Kommando trägt eine optionale **`id`** (uint32), die in `ack`/`error` zurückgespiegelt wird (Request/Response-Korrelation). --- ## 4. Push-Verhalten (S3 -> P4) Implementiert in `touchUartTick()`: - **Client-Aktivierung:** Erst nachdem das Display ein Kommando gesendet hat, gilt es als aktiv (`touchUartClientActive`). Ohne aktiven Client wird **nicht** gepusht. - **Client-Timeout:** `TOUCH_UART_CLIENT_TIMEOUT_MS` = 15000 ms ohne Kommando -> inaktiv. -> Das Display muss regelmäßig `ping` (oder ein beliebiges Kommando) senden (Heartbeat). - **Adaptives Intervall:** - Leerlauf: `TOUCH_UART_INTERVAL_IDLE_MS` = 1000 ms - Aktiv (Bezug/Flush/Burst): `TOUCH_UART_INTERVAL_ACTIVE_MS` = 250 ms - **Force-Push** bei Zustandswechseln: Bezug, Flush, Dampfkreis (= Dampfbezug/Dampf-Flush), Tarier-Phase, Sensorfehler/Sicherheitsabschaltung, Reinigungsassistent, Standby (Details in §5). - **Burst (250 ms)** zusätzlich während: Bezug, Flush, Dampfkreis, Tarier-Phase, Reinigungsassistent. - **Burst** nach Bezugsende: `TOUCH_UART_SHOT_END_GRACE_MS` = 500 ms Nachlauf. **Empfehlung P4-Seite:** alle ~5 s `ping` senden (Heartbeat), nach jedem Bedienvorgang zusätzlich `getState` ist nicht nötig (die S3 pusht nach jedem Kommando automatisch `state`). --- ## 5. Telemetrie — `state`-Objekt (Protokoll v1, IST-Zustand) Erzeugt von `buildTouchUartStateJson()`. Alle Felder in einem flachen JSON-Objekt. | Feld | Typ | Bedeutung | |---|---|---| | `type` | string | immer `"state"` | | `protocolVersion` | int | aktuell `1` | | `firmwareVersion` | string | z. B. `"4.8.1"` | | `tempW` | float | Ist-Temperatur Wasser (angezeigt) | | `setW` | float | Soll-Temperatur Wasser | | `tempD` | float | Ist-Temperatur Dampf (angezeigt) | | `setD` | float | Soll-Temperatur Dampf | | `statusText` | string | Klartext-Status (DE), z. B. `"Bezug aktiv..."` | | `statusKey` | string | maschinenlesbarer Statusschlüssel (siehe §7) | | `shotActive` | bool | Bezug läuft | | `shotElapsedMs` | uint | vergangene Bezugszeit in ms | | `steamCircuitActive` | bool | Dampf-/Wasserkreis aktiv | | `steamFlushActive` | bool | Steam-Flush aktiv | | `flushActive` | bool | Flush aktiv | | `ecoActive` | bool | Eco-Modus aktiv | | `standbyActive` | bool | Standby aktiv | | `maintenanceActive` | bool | Wartungsmodus aktiv | | `steamHeatDisabled` | bool | Dampfheizung per Nutzer deaktiviert | | `lightOn` | bool | Beleuchtung an | | `piezoEnabled` | bool | Piezo-Summer aktiv | | `weight` | float | aktuelles Waagengewicht (UI) | | `targetWeight` | float | Ziel-Gewicht (Brew-by-Weight) | | `scaleEnabled` | bool | Waage aktiviert | | `scaleType` | uint | Waagentyp | | `scaleConnected` | bool | Waage verbunden | | `hx711CalFactor` | float | HX711-Kalibrierfaktor | | `hx711DisplaySmoothing` | bool | HX711-Anzeigeglättung | | `piEnabled` | bool | Pre-Infusion aktiv | | `bbtEnabled` | bool | Brew-by-Time aktiv | | `bbwEnabled` | bool | Brew-by-Weight aktiv | | `sbtEnabled` | bool | Steam-by-Time aktiv | | `bbtSecs` | float | Brew-by-Time Zielzeit (s) | | `sbtSecs` | float | Steam-by-Time Zielzeit (s) | | `piDurSecs` | float | Pre-Infusion Dauer (s) | | `piPauseSecs` | float | Pre-Infusion Pause (s) | | `bbwTarget` | float | Brew-by-Weight Zielgewicht (g) | | `bbwOffset` | float | Brew-by-Weight Offset (g) | | `caseSensorEnabled` | bool | Gehäusesensor aktiv | | `caseTempDashboard` | bool | Gehäusetemp. im Dashboard sichtbar (effektiv) | | `caseTempDashboardSetting` | bool | Einstellung Dashboard-Anzeige | | `caseTempDisplaySetting` | bool | Einstellung Display-Anzeige | | `caseSensorType` | uint | Typ des Gehäusesensors | | `caseOffset` | float | Offset Gehäusesensor | | `caseTemp` | float\|null | Gehäusetemperatur (`null` bei Fehler/inaktiv) | | `maintenanceInterval` | int | Wartungsintervall | | `maintenanceIntervalCounter` | uint | Wartungszähler | | `flushDurationSeconds` | uint | Flush-Dauer (s) | | `steamFlushDurationSeconds` | uint | Steam-Flush-Dauer (s) | | `xSwitchAction` | uint | X-Switch Kurz-Aktion | | `xSwitchLongAction` | uint | X-Switch Lang-Aktion | **v2-Felder (seit Phase A.1 implementiert):** | Feld | Typ | Bedeutung | |---|---|---| | `dutyW` | float | Heizleistung Wasser in % (`OutputWasser / windowSizeWasser * 100`) | | `dutyD` | float | Heizleistung Dampf in % | | `kpW` / `kiW` / `kdW` | float | PID-Parameter Wasser (read-only) | | `kpD` / `kiD` / `kdD` | float | PID-Parameter Dampf (read-only) | | `autoTuneW` | bool | AutoTune Wasser läuft | | `autoTuneD` | bool | AutoTune Dampf läuft | | `autoTuneWStatus` | uint8 | Ergebnis/Grund letzter Wasser-Lauf: 0=Idle, 1=läuft, 2=Erfolg, 3=Failsafe (>9 Peaks, evtl. ungenau), 4=degeneriert (keine Schwingung, Werte verworfen), 5=Sensorfehler, 6=Sicherheitsabschaltung, 7=manuell abgebrochen | | `autoTuneDStatus` | uint8 | Ergebnis/Grund letzter Dampf-Lauf (Codes wie `autoTuneWStatus`) | | `wifiConnected` | bool | STA verbunden **oder** AP-Modus aktiv | | `apMode` | bool | Access-Point-Modus aktiv | | `ip` | string | aktuelle IP (STA-IP, sonst softAP-IP) | | `ssid` | string | verbundene SSID bzw. AP-SSID | | `rssi` | int | WLAN-Signalstärke (dBm), `0` wenn nicht STA-verbunden | > `protocolVersion` ist mit diesen Feldern auf **2** angehoben. **v2.1-Felder (Timer-Kopplung, Sicherheit, Reinigungsassistent):** | Feld | Typ | Bedeutung | |---|---|---| | `steamElapsedMs` | uint | vergangene Dampfbezugszeit in ms (rückwirkende Timer-Kopplung, analog `shotElapsedMs`) | | `flushElapsedMs` | uint | vergangene Wasser-Flush-Zeit in ms (rückwirkende Timer-Kopplung) | | `scaleTaring` | bool | Tarier-Phase vor einem Brew-by-Weight-Bezug läuft (HX711); Display zeigt „Tariere…" | | `wasserSensorError` | bool | Wassersensor liefert ungültigen Wert | | `wasserSafetyShutdown` | bool | Wassertemperatur über Sicherheitslimit → Heizung aus | | `dampfSensorError` | bool | Dampfsensor liefert ungültigen Wert | | `dampfSafetyShutdown` | bool | Dampftemperatur über Sicherheitslimit → Heizung aus | | `cleaningActive` | bool | Reinigungsassistent läuft | | `cleaningWaiting` | bool | Reinigungsassistent heizt noch auf 93 °C vor | | `cleaningBrewPhase` | bool | aktuelle Phase: `true` = Bezug, `false` = Pause | | `cleaningCycle` | uint | aktueller Zyklus | | `cleaningCycles` | uint | Gesamtzahl der Zyklen | | `cleaningBrewSeconds` | uint | Bezugsdauer je Zyklus (s) | | `cleaningPauseSeconds` | uint | Pausendauer je Zyklus (s) | | `cleaningPhaseRemainSec` | int | verbleibende Sekunden der aktuellen Phase | > **Force-Push** zusätzlich bei: Dampfkreis start/stop (deckt Dampfbezug **und** Dampf-Flush ab), > Tarier-Phase start/stop, Sensorfehler/Sicherheitsabschaltung (Flanke), Reinigungsassistent start/stop, > Standby-Wechsel. **Burst (250 ms)** zusätzlich während: Dampfkreis, Tarier-Phase, Reinigungsassistent. --- ## 6. Kommando-Referenz (Protokoll v1, IST-Zustand) Verarbeitet in `touchUartHandleCommandLine()`. Alle Kommandos: `{"op":"...","id":,...}`. Antwort jeweils `ack`/`error`; bei zustandsändernden Ops folgt automatisch ein `state`-Push. | `op` | Zusatzfelder | Wirkung | Antwort | |---|---|---|---| | `ping` | — | Heartbeat | `ack` `"pong"` | | `getState` | — | erzwingt sofortigen State-Push | `state` + `ack` | | `listProfiles` | — | Profilnamen anfordern | `profiles` + `ack` | | `getUsageStats` | — | Nutzungsstatistik anfordern | `usageStats` + `ack` | | `action` | `action` (string), `value` (opt.) | ruft `executeDashboardAction()` auf — **dieselbe** Aktions-Engine wie das Web-Dashboard | `ack` + `state` | | `loadProfile` | `profile` (string) | Profil laden | `ack` + `state` | | `saveProfile` | `profile` (string) | aktuelle Einstellungen als Profil speichern | `ack` (+ `state`) | | `saveBrew` | `bbtEnabled`,`bbtSecs`,`bbwEnabled`,`bbwTarget`,`bbwOffset`,`piEnabled`,`piDurSecs`,`piPauseSecs`,`sbtEnabled`,`sbtSecs` (alle optional) | `applyBrewControlUpdate()` | `ack` + `state` | | `saveService` | `piezoEnabled`,`maintenanceInterval`,`flushDurationSeconds`,`steamFlushDurationSeconds`,`resetMaintenanceCounter` | `applyServiceSettingsUpdate()` | `ack` + `state` | | `saveSensor` | `section` (`case`/`scale`/`xswitch`) + jeweilige Felder (`caseSensorEnabled`, `scaleEnabled`, `scaleType`, `hx711CalFactor`, `xSwitchAction`, …) | `applySensorSettingsUpdate()` | `ack` + `state` | | `startCleaning` | `cycles`,`brewSeconds`,`pauseSeconds` (alle optional, sonst aktuelle Werte) | setzt die Parameter und startet den Reinigungsassistenten (Prüfung via `canStartCleaningAssistant()`; bei Ablehnung `ok:false` mit Begründung) | `ack` + `state` | | `stopCleaning` | — | bricht den laufenden Reinigungsassistenten ab | `ack` + `state` | ### Antwort-Envelopes ```jsonc // ack {"type":"ack","id":42,"ok":true,"message":"Brew gespeichert"} // error {"type":"error","id":42,"message":"op fehlt"} // profiles {"type":"profiles","id":7,"profiles":["Espresso","Lungo","Reinigung"]} ``` ### Die wichtigste Erweiterungsstelle: `action` `executeDashboardAction(action, value, success, message, settingsChanged, pidNeedsUpdate)` ist **die gemeinsame Aktions-Engine** von Web-Dashboard und Touch-Display. Jede Aktion, die das Webinterface auslöst (Standby, Eco, Licht, Flush, Bezug-Steuerung, Setpoints, Reinigungsassistent, …), ist damit ohne Mehraufwand auch übers Display verfügbar. ### 6.1 Vollständige `action`-Referenz Alle gültigen `action`-Strings aus `executeDashboardAction()` (Stand v4.8.1, Zeilen ~13399–13863). Aufruf vom Display als `{"op":"action","id":,"action":"","value":""}`. `value` ist immer ein **String**; die Engine konvertiert (`toFloat`/`toInt`, bzw. Vergleich `== "true"`). **Temperatur-Sollwerte** | `action` | `value` | Wirkung | |---|---|---| | `setTempW` | float `0..135` | Wasser-Sollwert (°C), persistiert in EEPROM | | `setTempD` | float `0..200` | Dampf-Sollwert (°C), persistiert in EEPROM | | `setTempDisplayMode` | int (`CLASSIC..RAILS`) | Temperatur-Anzeigemodus im Dashboard | **Betriebsmodi** | `action` | `value` | Wirkung | |---|---|---| | `startEco` | — | Eco-Modus aktivieren | | `stopEco` | — | Eco-Modus für diesen Zyklus beenden | | `activateStandby` | — | Standby aktivieren (Software) | | `deactivateStandby` | — | Standby aufheben (Software) | | `activateStandbyLegacy` | — | Standby-Override (schalterbasiert) reaktivieren | | `deactivateStandbyLegacy` | — | Standby per WebUI aufheben (nur bei aktivem Schalter) | | `toggleMaintenance` | `"true"`/`"false"` | Wartungsmodus ein/aus (temporär) | **Bezug / Dampf / Spülen** | `action` | `value` | Wirkung | |---|---|---| | `startShot` | — | Bezug starten (Software) | | `stopShot` | — | Bezug beenden | | `startSteam` | — | Dampfbezug starten | | `stopSteam` | — | Dampfbezug / Dampf-Flush beenden | | `flush` | — | Spülen starten (Dauer = `flushDurationSeconds`) | | `steamFlush` | — | Dampf-Flush starten (Dauer = `steamFlushDurationSeconds`) | | `overrideSteamDelay` | — | Dampfverzögerung temporär überspringen | | `disableSteamHeat` | — | Dampf-Heizung abschalten | | `enableSteamHeat` | — | Dampf-Heizung wieder aktivieren | **Persistente Toggles** | `action` | `value` | Wirkung | |---|---|---| | `toggleBoostW` | `"true"`/`"false"` | Boost Wasser (EEPROM) | | `toggleBoostD` | `"true"`/`"false"` | Boost Dampf (EEPROM) | | `togglePI` | `"true"`/`"false"` | Pre-Infusion (EEPROM) | | `toggleBBT` | `"true"`/`"false"` | Brew-by-Time (EEPROM) | | `toggleBBW` | `"true"`/`"false"` | Brew-by-Weight (EEPROM) | | `toggleLight` | `"true"`/`"false"` | Beleuchtung (SSR + EEPROM) | **Waage** | `action` | `value` | Wirkung | |---|---|---| | `tareScale` | — | Tarieren (nur I2C/HX711; ESP-NOW nicht über UI) | | `toggleScaleMode` | `"true"`/`"false"` | Waage-Anzeige-/Wiegemodus (temporär) | **Profil** | `action` | `value` | Wirkung | |---|---|---| | `loadProfile` | Profilname | Profil laden (identisch zu `op:"loadProfile"`) | > **Sperre bei Reinigungsassistent:** Solange `cleaningAssistantActive`, weisen > `startEco`, `toggleMaintenance`, `startShot`, `stopShot`, `startSteam`, `stopSteam`, > `flush`, `steamFlush` mit `success:false` und entsprechender Meldung ab. Das Display > sollte diese Aktionen dann ausgegraut darstellen (Status `maintenance`). > **Unbekannte Aktion:** liefert `message:"Unbekannte Aktion."` und `success:false`. --- ## 7. Status-Schlüssel (`statusKey`) Aus `getTouchUartStatus()`. Für sprach-unabhängige UI-Logik/Icons auf dem P4 verwenden (statt `statusText` zu parsen): `ready`, `error`, `standby`, `maintenance`, `tuning`, `brewing`, `eco`, `fastheatup`, `heating`, `ready_eco_pending`. --- ## 8. Geplante Erweiterungen — Protokoll v2 Für ein vollwertiges 480×800-Touch-UI fehlen v1 noch einige Daten/Funktionen. Geplant: ### 8.1 Neue Telemetrie-Felder im `state` - **[erledigt A.1] Netzwerk:** `wifiConnected`, `apMode`, `ssid`, `ip`, `rssi`. - **[erledigt A.1] PID:** aktuelle Kp/Ki/Kd Wasser & Dampf (read-only). - **[erledigt A.1] AutoTune:** `autoTuneW` / `autoTuneD` (laufend ja/nein). - **[erledigt] AutoTune-Ergebnis:** `autoTuneWStatus` / `autoTuneDStatus` (Erfolg/Abbruch + Grund; siehe §8.1-Tabelle). P4-Dashboard blendet das Ergebnis nach Lauf-Ende ~30 s als Banner ein; S3-Web `/PID-Tuning` zeigt es persistent. - **[erledigt A.1] Heizleistung:** Duty Wasser & Dampf (`dutyW`/`dutyD`, für Balkenanzeige). - **[offen] Flow-Rate:** `flowRate` (g/s) als **Live-Zahl** (kein Chart) — bei Bedarf später. - **[offen] AutoTune-Fortschritt:** numerischer Fortschritt (Peak-Count) — derzeit nur Lauf-Flag + Ergebnis-/Abbruchstatus. > **Kein Chart am Display** (Entscheidung): Es wird **keine** Verlaufs-/Historiendaten- > Übertragung benötigt. Damit entfällt der aufwändigste Teil (großer Ringpuffer, Chunking). ### 8.2 Neue Kommandos (`op`) | `op` | Status | Zweck | |---|---|---| | `hello` | **erledigt** | Handshake: P4 meldet sich, S3 antwortet mit `type:"hello"` + `firmwareVersion`, `protocolVersion`, `capabilities[]`, danach `state` + `ack`. | | `getProfileDetails` | **erledigt** | Profil-Inhalt (`profile`) → Antwort `type:"profileDetails"` mit allen Feldern (Setpoints, Offsets, Kp/Ki/Kd, Boost, Window, Eco, Brew, Piezo, FastHeatUp). | | `setPid` | **erledigt** | Felder `kpW/kiW/kdW/kpD/kiD/kdD` (alle optional) → EEPROM + Live-`SetTunings`. | | `startAutotune` | **erledigt** | `target`:`water`\|`steam` (auch `wasser`/`dampf`). Spiegelt `handleAutoTune*` (Guard: nur wenn keins läuft, Wartungsmodus wird zurückgesetzt). | | `stopAutotune` | **erledigt** | Bricht laufendes AutoTune Wasser **und** Dampf ab. | | `scanWifi` | **erledigt** | Startet einen **asynchronen** WLAN-Scan. Sofort `ack`; wenn fertig, pusht die S3 `type:"wifiNetworks"` (Array `{ssid,rssi,enc}`, max. 20). Timeout 15 s. | | `setWifi` | **erledigt** | Felder `ssid` (Pflicht) + `password` (optional, offenes Netz = leer). Speichert in `WiFiConfig` (EEPROM) und ruft `WiFi.begin(...)` **nicht-blockierend** auf; vorhandene IP-Einstellungen bleiben erhalten. Fortschritt über die `wifi*`-Felder im `state`. | | `getUsageStats` | **erledigt** | Nutzungsstatistik anfordern → Antwort `type:"usageStats"`. Aggregierte Kennzahlen aus `calculateShotStatistics()` (`today/week/month`, `yesterday/lastWeek/lastMonth`, `total`, `avgDur`, `avgPerDay`) plus `shots[]` = **letzte ~12** Einzelbezüge (`dt`, `d`=Dauer s, `w`=Gewicht g bzw. `-1`). Es wird nur das **Dateiende** von `/Nutzungsstatistik.csv` gelesen, nie die ganze Datei (P4-Heap schonen). | | `getNetworkInfo` | entfällt | WLAN-Details sind bereits im `state` enthalten — eigener Op unnötig. | > Antwort auf `scanWifi` (Beispiel, eigene Push-Nachricht sobald der Scan fertig ist): > ```jsonc > {"type":"wifiNetworks","networks":[ > {"ssid":"MeinWLAN","rssi":-52,"enc":true}, > {"ssid":"Gast","rssi":-71,"enc":false} > ]} > ``` > Antwort auf `getUsageStats` (Beispiel): > ```jsonc > {"type":"usageStats","id":7,"available":true,"total":340, > "today":3,"week":18,"month":72,"yesterday":5,"lastWeek":21,"lastMonth":80, > "avgDur":27.4,"avgPerDay":4.9, > "shots":[ > {"dt":"02.07. 08:14","d":27.5,"w":36.5}, > {"dt":"02.07. 07:41","d":25.0,"w":-1} > ]} > ``` > Antwort auf `hello` (Beispiel): > ```jsonc > {"type":"hello","protocolVersion":2,"firmwareVersion":"4.8.1", > "capabilities":["state","action","listProfiles","loadProfile","saveProfile","saveBrew","saveService","saveSensor","setPid","startAutotune","stopAutotune","getProfileDetails","scanWifi","setWifi","getUsageStats"]} > ``` > > Anzeige am P4: eigener Menüpunkt **„Statistik"** (`ui.cpp`, `PG_STATS`). Beim Öffnen der > Seite (und über einen „Aktualisieren"-Button) sendet der P4 `getUsageStats` und stellt die > Aggregate (Heute/Woche/Monat inkl. Vorperiode, Gesamt, mittlere Dauer, mittl. Bezüge/Tag) > sowie die letzten ~12 Einzelbezüge dar. ### 8.3 Robustheit / Transport - **Handshake + Capabilities** statt impliziter Aktivierung -> P4 kann sich an die tatsächliche Firmware-Version anpassen (Vorwärts-/Rückwärtskompatibilität). - **Optionale Prüfsumme** (z. B. CRC16 als Feld `crc`) gegen SSR-/EMV-Störungen auf der Maschine. Empfänger verwirft Zeilen mit falscher Prüfsumme. - **Puffergrößen:** Ohne Chart bleiben die Nutzdaten klein. `getProfileDetails` (ein `TemperatureProfile`) passt voraussichtlich in die bestehenden Limits (Zeile 1024 B, State 2400 B). Chunking ist damit **nicht** erforderlich; Limit nur bei Bedarf leicht anheben. ### 8.4 Versionierung - `protocolVersion` wird auf **2** angehoben, **sobald** ein nicht abwärtskompatibles Feld Pflicht wird. Reine *Ergänzungen* (neue optionale Felder/Ops) bleiben v1-kompatibel — der P4 ignoriert Unbekanntes. - Der P4 prüft beim `hello` die `protocolVersion` der S3 und schaltet Features entsprechend frei/aus. --- ## 9. OLED-Koexistenz (Compile-Flag) - OLED bleibt über `ENABLE_DISPLAY` (Compile-Flag) wählbar — unverändert. - Das JC-Display wird über `TOUCH_UART_ENABLED` (Compile-Flag) aktiviert. - **Geplante Änderung:** beide Flags strikt **unabhängig** halten, damit alle Kombinationen baubar sind: | `ENABLE_DISPLAY` | `TOUCH_UART_ENABLED` | Ergebnis | |---|---|---| | 0 | 0 | weder OLED noch Touch (Headless, nur Web) | | 1 | 0 | nur OLED (heutiger Standard) | | 0 | 1 | nur JC-Touch-Display | | 1 | 1 | OLED **und** JC-Touch parallel | > Aktuell ist sicherzustellen, dass kein UART-Code versehentlich von `ENABLE_DISPLAY` > abhängt und keine I2C-/Pin-Ressource doppelt belegt wird (OLED nutzt I2C GPIO17/18, > Touch nutzt UART GPIO43/44 — **kein Konflikt**). --- ## 10. Sprachen / UI-Texte - **Die App ist aktuell reines Deutsch** — es existiert keine i18n-/Sprachumschalt- Infrastruktur (geprüft: S3-Firmware und Web-Skripte haben nur fest verdrahtetes Deutsch, HTML durchgängig `lang="de"`). Damit ist **kein** `getStrings`/`setLanguage` nötig. - Der P4 hält seine **deutschen** UI-Texte lokal (statische Strings im Display-Projekt). - `statusKey` (sprachneutral, §7) bleibt trotzdem die saubere Grundlage für die UI-Logik (Icons/Zustände), unabhängig vom angezeigten `statusText`. - Projekt-Hinweis: Sollte später eine zweite Anwendungssprache eingeführt werden, ist sie laut Projektregel überall zu ergänzen — dann auch in den P4-Stringtabellen. --- ## 11. Bewertung des Bestands (Keep / Change / Delete) **Behalten (1:1):** Protokoll-Framing, Push-Engine (`touchUartTick`), RX-Parser (`touchUartRxTick`), Kommando-Dispatch (`touchUartHandleCommandLine`), JSON-Helfer (`jsonGetStringValue/UInt/Bool/Float/RawValue`, `jsonDecodeString`, `jsonEscapeForUart`), ACK/Error-Envelopes, Reset-Checkpoints (`RESET_CP_TOUCH_UART_RX/TX`). **Ändern/Erweitern:** Flag-Entkopplung (§9), Telemetrie-/Op-Erweiterungen (§8), Puffergrößen/Chunking, optionaler CRC + Handshake, `action`-Stringliste dokumentieren. **Löschen:** Nichts. **Fully-Kiosk bleibt erhalten** (Entscheidung) — als eigenständige, optionale Funktion neben dem JC-Display. Lastbewertung der S3: - Einziger Aufruf `sendFullyKioskLoadUrl()` wird **nur bei Standby-Zustandswechsel** getriggert (`if (standbyModeActive != lastStandbyModeActive)`), **nicht periodisch** (wenige Male pro Tag). - Blockierender HTTP-GET, aber mit **kurzem Timeout** (`timeoutMs`, Default 700 ms); bei `enabled=0` sofortiger Rückkehr ohne Netzwerkzugriff. - Übergänge passieren im Standby, also **außerhalb eines Bezugs** -> kein Einfluss auf die Regelung. **Keine relevante Dauerlast, keine Leistungsgrenze.** Koexistiert problemlos mit dem UART-Display. --- ## 12. Implementierungsplan ### Phase A — S3-Seite (Hauptplatine, `Dual_PID_FastHeatUp.ino`) 1. **[erledigt]** `action`-Stringliste aus `executeDashboardAction()` dokumentiert (§6.1). 2. **[erledigt A.1]** Flag-Entkopplung von `ENABLE_DISPLAY` dokumentiert/abgesichert (§9); Defines kommentiert, `TOUCH_UART_PROTOCOL_VERSION` -> 2, Puffer -> 3000. 3. **[erledigt A.1]** v2-Telemetriefelder ergänzt: Netzwerk, PID, Duty, AutoTune-Status (§8.1). 4. **[erledigt A.1]** Op `hello` implementiert (§8.2). 5. **[erledigt]** Weitere Ops: `getProfileDetails`, `setPid`, `startAutotune/stopAutotune` (§8.2). 6. **[offen, optional]** CRC + ggf. Puffer-Feinjustage (§8.3) — bewusst nicht als toter, ungetesteter Code eingebaut; bei Bedarf später. 7. **[offen, durch dich]** `TOUCH_UART_ENABLED` auf 1 setzen + Build verifizieren, **sobald** das JC-Display physisch an Serial1 (GPIO43/44) hängt. Bewusst noch **0**, damit die serielle Debug-Konsole (GPIO43/44) im aktuellen Betrieb erhalten bleibt. ### Phase B — P4-Seite (JC-Display, Projekt `JC_Display_Firmware/`) Grundgerüst angelegt (Arduino-ESP32 P4 + LVGL 9 + ArduinoJson 7). Aufteilung: `config.h`, `machine_state.h`, `protocol_client.*`, `ui.*`, `display_hal.*`, `.ino`, `README.md`. 1. **[erledigt]** UART-Client: Zeilen-RX/TX, JSON-Parsing, `state` -> UI-Modell, `hello`-Handshake, Heartbeat, Link-Timeout (`protocol_client.*`). 2. **[erledigt]** Dashboard: Temps/Soll/Duty, Status-Banner (nach `statusKey`), Verbindungs-/WLAN-Anzeige, Bezugs-Timer, Bedien-Buttons -> Kommandos (`ui.*`). 3. **[erledigt]** Senderoutinen für `setPid`, `startAutotune/stopAutotune`, `getProfileDetails` im Client vorhanden. 4. **[erledigt]** Board-Bringup auf Basis der **Hersteller-Portierung** verdrahtet (`display_hal.cpp`, Schalter `JC_USE_REAL_PANEL`): MIPI-DSI/ST7701, GT911 (I²C SDA=7/SCL=8), Backlight GPIO23, LCD-Reset GPIO5; LVGL-Port mit eigenem Task + Lock. Stub-Modus bleibt für Schreibtisch-Tests. Vendor-BSP-Dateien sind in den Sketch zu kopieren (siehe Projekt-README). 5. **[festgelegt]** UART-Pins am JC-Display: **P4 GPIO50 (TX) / GPIO51 (RX)** vom Expand-IO-Header JP1 (Schaltplan `4_USB&IO.png`). Gegenseite S3 unverändert GPIO43/44. 6. **[offen]** Weitere Screens: Einstellungen (Brew/Service/Sensor), Profil-Auswahl/-Editor, PID/AutoTune-Bedienung; Ausrichtung Hochformat (Rotation 0 in `pins_config.h`). ### Entschieden - **Sprachen:** App ist Deutsch-only, keine i18n -> P4 hält deutsche Strings lokal (§10). - **Chart am Display:** nicht benötigt -> kein `getChartData`/Chunking (§8). - **Fully-Kiosk:** bleibt als optionale Funktion erhalten; keine relevante S3-Last (§11). --- *Letzter Abgleich mit Code: `Dual_PID_FastHeatUp.ino` v4.8.1 — Abschnitt „Dashboard Code Block" (`buildTouchUartStateJson`, `touchUartHandleCommandLine`, `touchUartTick`, `touchUartRxTick`) sowie Defines `TOUCH_UART_*` (Zeilen ~155–184).*