Kalte Extraktion ohne Kessel-Bypass: Da kaltes Wasser nur bei kaltem Wasserkessel am Puck ankommt, wird die Funktion über eine Temperaturschwelle freigegeben (Standard 30 °C, 10–40 °C einstellbar). Solange der Modus aktiv ist, heizt der Wasserkreis nicht (SSR gesperrt, Stellgröße und dutyW-Telemetrie auf 0, I-Anteil eingefroren, Feed-Forward-Boost aus); der Dampfkreis bleibt unberührt. Die Freigabe wird vor jedem Bezug neu geprüft, weil der weiter heizende Dampfkessel den Wasserkessel über die Zeit über die Schwelle bringen kann. Ein abgewiesener Start meldet den Grund auf OLED, Touch-Display und Web-Dashboard; ein laufender Bezug wird nie abgebrochen. 5 °C Hysterese, Sensorfehler sperrt. Fluss über Pumpen-Pulsung nach einer Vorbenetzung mit voller Leistung; Ende über Zielgewicht oder Timeout. Brew-by-Time/Weight und FlowGuard sind während einer kalten Extraktion außer Kraft, da FlowGuard sonst um denselben Pumpen-Ausgang konkurrieren würde. Pumpenschutz über kumulierte Einschaltzeit mit Zwangspause plus Stillstands-Erkennung bei aktiver Waage. Kalte Bezüge werden im CSV-Log mit einem vierten Feld "C" gekennzeichnet, zählen in allen Stückzahlen mit, bleiben aber aus der durchschnittlichen Bezugsdauer heraus. Neue additive State-Felder cx* im UART-JSON (Protokoll bleibt v2) und neuer statusKey "coldextraction". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
578 lines
31 KiB
Markdown
578 lines
31 KiB
Markdown
# 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 |
|
||
| `profile` | string | zuletzt geladenes/gespeichertes Profil, leer = keines (additiv seit S3 5.1.1) |
|
||
| `profDirty` | bool | profil-relevante Einstellungen seit dem Laden geändert (additiv seit S3 5.1.1) |
|
||
| `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
|
||
~13399–13863). 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 |
|
||
|
||
**Cold Extraction (kalte Extraktion, ab S3 5.3.0)**
|
||
|
||
| `action` | `value` | Wirkung |
|
||
|---|---|---|
|
||
| `startColdExtraction` | — | Modus aktivieren (Wasserkreis heizt nicht, Dampf bleibt möglich) |
|
||
| `stopColdExtraction` | — | Modus beenden |
|
||
| `toggleColdExtraction` | — | Modus umschalten |
|
||
|
||
Alle drei Aktionen werden abgewiesen (`ok:false` + `message`), solange die Bedingungen nicht
|
||
erfüllt sind — Hauptfall ist ein zu warmer Wasserkessel (`cxAllowed:false`). Der Modus wird
|
||
**nicht** persistiert und ist nach jedem Neustart aus; Standby beendet ihn. Ein laufender
|
||
Bezug wird nie abgebrochen, `stopColdExtraction` schlägt dann fehl.
|
||
|
||
Die Parameter der kalten Extraktion (Schwelle, Vorbenetzung, Pulsperiode, Pumpenleistung,
|
||
Zielgewicht, Timeout, Pumpenschutz) sind bewusst **nicht** über `saveBrew` erreichbar, sondern
|
||
nur über die Web-Seite `/Brew-Control`. Das Display schaltet den Modus, konfiguriert ihn nicht.
|
||
|
||
**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`, `coldextraction` (ab S3 5.3.0).
|
||
|
||
### Cold-Extraction-Felder im `state` (ab S3 5.3.0, additiv — Protokoll bleibt v2)
|
||
|
||
| Feld | Typ | Bedeutung |
|
||
|---|---|---|
|
||
| `cxEnabled` | bool | Funktion in der Web-UI freigeschaltet (sonst Bedienelement ausblenden) |
|
||
| `cxActive` | bool | Modus scharf: Wasserkreis heizt nicht |
|
||
| `cxShot` | bool | Der laufende Bezug ist eine kalte Extraktion |
|
||
| `cxAllowed` | bool | Freigabe **jetzt** (Wasserkessel kalt genug, kein Standby/Wartung/Tuning) |
|
||
| `cxResting` | bool | Zwangspause des Pumpenschutzes läuft (Bezug läuft weiter) |
|
||
| `cxMaxTemp` | float | Freigabeschwelle in °C (für eigene Hinweistexte am Display) |
|
||
| `cxTarget` | float | Zielgewicht in g |
|
||
| `cxMaxSecs` | float | Sicherheits-Timeout des Bezugs in s |
|
||
| `cxMessage` | string | Sperr-/Abbruchmeldung, leer = keine (läuft nach ~6 s selbst ab) |
|
||
|
||
Während `cxShot` folgt die Phase weiterhin `piState`: `1` = Vorbenetzung, `3` = Hauptbezug
|
||
(gepulst). Eine Pause-Phase (`2`) gibt es bei der kalten Extraktion nicht.
|
||
|
||
In der Antwort auf `getUsageStats` trägt jeder Eintrag in `shots[]` zusätzlich `cx` (bool) —
|
||
kalte Bezüge zählen in `total`/`today`/`week` mit, sind aber in der durchschnittlichen
|
||
Bezugsdauer der S3-Statistik ausgenommen.
|
||
|
||
---
|
||
|
||
## 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).*
|