Files
Dual-PID/Doku/JC-Display_UART-Protokoll.md
T
raw-designs 453644816f Initiale Bereitstellung
Initiale Bereitstellung der aktuellen Version auf Gitea
2026-07-10 18:13:43 +02:00

538 lines
28 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.
# 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":<n>,...}`.
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
~1339913863). Aufruf vom Display als `{"op":"action","id":<n>,"action":"<name>","value":"<v>"}`.
`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 ~155184).*