Cold Extraction bekommt in beiden Oberflächen einen eigenen Menüpunkt statt eines angehängten Blocks auf der Brew-Control-Seite. Die Parameter sind damit erstmals auch am Touch-Display änderbar. S3: neue Seite /Cold-Extraction, gegliedert in Grundeinstellung, Vorbenetzung, Hauptbezug, Bezugsende und Pumpenschutz, jeder Abschnitt mit kurzer Begründung. /Brew-Control verweist nur noch darauf. Das UART-Kommando saveBrew akzeptiert jetzt die cx-Felder, und das State-JSON liefert die restlichen Parameter mit, damit das Display seine Felder vorbelegen kann. Wichtig: Der Speicher-Handler von /Brew-Control wertet die cx-Felder nicht mehr aus. Er tat es bisher, und nach dem Verschieben der Checkbox hätte ein Speichern dort die Freischaltung abgeschaltet, weil das Feld im Formular fehlt. P4: neue Seite "Cold Extraction" mit allen Parametern; der Modus-Schalter ist von der Temperatur-Seite dorthin umgezogen, damit es nur eine Stelle gibt. Die Brew-Control- Seite ist neu gegliedert: jede Funktion in einer eigenen Karte mit Titel und einer Zeile, die erklärt was sie tut, statt einer durchlaufenden Liste unter Zwischenüberschriften. Neues Hilfsmakro group_card() für beide Seiten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
33 KiB
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.\rwird empfangsseitig ignoriert. - Max. Zeilenlänge (RX):
TOUCH_UART_MAX_LINE_LEN= 1024 Byte. Längere Zeilen werden verworfen und mit einemerror("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,escapedStatusTextetc.):\,",\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äßigping(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
- Leerlauf:
- 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 |
protocolVersionist 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
// 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 |
Für das Bedienelement zählt cxArmable, nicht cxAllowed: Der Modus schaltet die
Wasserheizung ab und muss deshalb auch im Standby und bei noch warmem Kessel scharf zu
stellen sein — sonst heizt die Maschine nach dem Aufwecken an, bevor man an den Schalter
kommt. cxAllowed gilt nur für den einzelnen Bezug; ist der Modus scharf und cxAllowed
false, sollte die UI „Modus aktiv, Bezug noch gesperrt" zeigen.
Ein Aufwecken direkt in die kalte Extraktion sendet startColdExtraction vor
deactivateStandby — in dieser Reihenfolge, damit der Wasserkreis gar nicht erst anheizt.
Die Aktionen werden abgewiesen (ok:false + message), solange die Bedingungen nicht
erfüllt sind. Der Modus ist
persistent (EEPROM) und überlebt Standby und Neustart; er endet nur durch explizites
Ausschalten, durch Entzug der Freischaltung auf /Brew-Control oder durch einen Werksreset.
Das P4-UI sollte cxActive deshalb dauerhaft sichtbar spiegeln — sonst wundert sich der
Benutzer, warum die Maschine nicht heizt. Ein laufender Bezug wird nie abgebrochen,
stopColdExtraction schlägt dann fehl.
Die Parameter der kalten Extraktion sind ab S3 5.4.0 über saveBrew einstellbar (zusätzlich
zur Web-Seite /Cold-Extraction). Zulässige Felder: cxEnabled (bool), cxMaxTemp,
cxPreInf, cxPreInfDuty, cxPulseMs, cxDuty, cxTarget, cxMaxSecs, cxPumpMaxRun,
cxPumpRest. Es werden nur die tatsächlich gesendeten Felder übernommen; Grenzwerte prüft
die S3. Die aktuellen Werte stehen alle im state, damit das Display seine Eingabefelder
vorbelegen kann.
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, weisenstartEco,toggleMaintenance,startShot,stopShot,startSteam,stopSteam,flush,steamFlushmitsuccess:falseund entsprechender Meldung ab. Das Display sollte diese Aktionen dann ausgegraut darstellen (Statusmaintenance).
Unbekannte Aktion: liefert
message:"Unbekannte Aktion."undsuccess: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 | Bezug jetzt möglich (Wasserkessel kalt genug, kein Standby/Wartung/Tuning) |
cxArmable |
bool | Modus umschaltbar — auch im Standby und bei warmem Kessel (ab S3 5.3.5) |
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 |
cxPreInf |
float | Vorbenetzung in s (auch für die Phasenleiste des kalten Bezugs) |
cxPreInfDuty |
float | Pumpenleistung Vorbenetzung in % (ab S3 5.4.0) |
cxDuty |
float | Pumpenleistung Hauptbezug in % (ab S3 5.4.0) |
cxPulseMs |
uint16 | Pulsperiode in ms (ab S3 5.4.0) |
cxPumpMaxRun |
float | Pumpenschutz: max. kumulierte Laufzeit in s (ab S3 5.4.0) |
cxPumpRest |
float | Pumpenschutz: Zwangspause in s (ab S3 5.4.0) |
cxMaxSecs |
float | Sicherheits-Timeout des Bezugs in s |
cxBlock |
uint8 | Sperrgrund (siehe unten), 0 = frei |
cxNotice |
uint8 | transiente Meldung, 0 = keine (läuft nach ~6 s selbst ab) |
cxBlock: 0 frei, 1 nicht freigeschaltet, 2 Standby, 3 Wartungsmodus,
4 Reinigungsassistent, 5 PID-Tuning, 6 Sensorfehler, 7 Wasserkessel zu warm.
cxNotice: 0 keine, 1 Start abgewiesen (Grund steht in cxBlock), 2 Bezug wegen
fehlenden Zulaufs beendet, 3 Umschalten während eines Bezugs abgelehnt, 4 ohne Waage
aktiviert (ab S3 5.3.4).
Ohne Waage (scaleEnabled/scaleConnected false) läuft die kalte Extraktion rein
zeitgesteuert: cxTarget ist wirkungslos und die Stillstands-Erkennung ist inaktiv, es
bleibt cxMaxSecs als Ende. Das ist Absicht (ein kurz abgemeldeter HX711 soll die Funktion
nicht blockieren) und kein Sperrgrund — das Display sollte es aber dauerhaft anzeigen.
Bewusst Codes statt Klartext: Die State-Zeile muss unter dem Zeilenlimit des P4 bleiben
(PROTO_RX_LINE_MAX; sie liegt real bereits bei ~2,1 KB), und die Texte gehören ins Display —
dieselbe Trennung wie bei statusKey/statusText.
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-Tuningzeigt 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):{"type":"wifiNetworks","networks":[ {"ssid":"MeinWLAN","rssi":-52,"enc":true}, {"ssid":"Gast","rssi":-71,"enc":false} ]}
Antwort auf
getUsageStats(Beispiel):{"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):{"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 P4getUsageStatsund 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(einTemperatureProfile) 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
protocolVersionwird 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
hellodieprotocolVersionder 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_DISPLAYabhä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 keingetStrings/setLanguagenö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 angezeigtenstatusText.- 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); beienabled=0sofortiger 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)
- [erledigt]
action-Stringliste ausexecuteDashboardAction()dokumentiert (§6.1). - [erledigt A.1] Flag-Entkopplung von
ENABLE_DISPLAYdokumentiert/abgesichert (§9); Defines kommentiert,TOUCH_UART_PROTOCOL_VERSION-> 2, Puffer -> 3000. - [erledigt A.1] v2-Telemetriefelder ergänzt: Netzwerk, PID, Duty, AutoTune-Status (§8.1).
- [erledigt A.1] Op
helloimplementiert (§8.2). - [erledigt] Weitere Ops:
getProfileDetails,setPid,startAutotune/stopAutotune(§8.2). - [offen, optional] CRC + ggf. Puffer-Feinjustage (§8.3) — bewusst nicht als toter, ungetesteter Code eingebaut; bei Bedarf später.
- [offen, durch dich]
TOUCH_UART_ENABLEDauf 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.
- [erledigt] UART-Client: Zeilen-RX/TX, JSON-Parsing,
state-> UI-Modell,hello-Handshake, Heartbeat, Link-Timeout (protocol_client.*). - [erledigt] Dashboard: Temps/Soll/Duty, Status-Banner (nach
statusKey), Verbindungs-/WLAN-Anzeige, Bezugs-Timer, Bedien-Buttons -> Kommandos (ui.*). - [erledigt] Senderoutinen für
setPid,startAutotune/stopAutotune,getProfileDetailsim Client vorhanden. - [erledigt] Board-Bringup auf Basis der Hersteller-Portierung verdrahtet
(
display_hal.cpp, SchalterJC_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). - [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. - [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).