Files
Dual-PID/Doku/JC-Display_UART-Protokoll.md
T
raw-designsandClaude Opus 5 b6155acafe feat: Cold Extraction warnt bei fehlender Waage (S3 5.3.4 / P4 1.1.2)
Ohne aktive und verbundene Waage greifen weder das Zielgewicht noch die
Stillstands-Erkennung — der Bezug endet dann erst nach der maximalen Bezugsdauer,
die damit faktisch zur Dosierung wird. Bisher passierte diese Degradierung
stillschweigend.

Die Funktion bleibt ohne Waage bewusst nutzbar (Zeitsteuerung als Rückfallebene),
damit ein kurzzeitig abgemeldeter HX711 die kalte Extraktion nicht komplett
blockiert. Angezeigt wird der Zustand jetzt an vier Stellen: dauerhafter
Warnhinweis auf /Brew-Control (Zielgewicht-Feld zusätzlich als "nur mit Waage
wirksam" beschriftet), Hinweiszeile unter dem Dashboard-Schalter, Statustext
"Cold Extraction bereit (ohne Waage: Zeitsteuerung)" und eine einmalige Meldung
beim Aktivieren.

P4: Statuszeile zeigt "ohne Waage, Ende nach Zeit" in Warnfarbe statt des blauen
Normalhinweises; Erklärzeile am Schalter und Parameter-Anzeige auf der Brew-Seite
ergänzen den Hinweis und nennen bei aktivem Modus ohne Waage die Zeit statt des
Zielgewichts. Neuer cxNotice-Code 4 (additiv, Protokoll bleibt v2).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 02:05:50 +02:00

32 KiB
Raw Blame History

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

// 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

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 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 (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
cxPreInf float Vorbenetzung in s (für die Phasenleiste des kalten Bezugs)
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-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):

{"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 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).