Files
Dual-PID/Doku/JC-Display_UART-Protokoll.md
T
raw-designsandClaude Fable 5 d97e4c34d5 feat(P4): Profil-Schnellwahl im Header + Anzeige des aktiven Profils
Neuer Profil-Chip im Header (alle Seiten): zeigt das zuletzt geladene
bzw. gespeicherte Profil aus den neuen S3-State-Feldern profile/profDirty
(S3 ab 5.1.1), „*" = seitdem geänderte Einstellungen. Antippen öffnet
ein Schnellwahl-Overlay mit allen Profilen (aktives mit Häkchen und
Akzentfarbe); ein Tap lädt direkt per loadProfile. Profile-Seite
markiert das aktive Profil in der Liste. Bei älterer S3-Firmware bleibt
der Chip unsichtbar. Version 1.0.13, Protokoll-Doku ergänzt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 20:55:10 +02:00

28 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

Persistente Toggles

action value Wirkung
toggleBoostW "true"/"false" Boost Wasser (EEPROM)
toggleBoostD "true"/"false" Boost Dampf (EEPROM)
togglePI "true"/"false" Pre-Infusion (EEPROM)
toggleBBT "true"/"false" Brew-by-Time (EEPROM)
toggleBBW "true"/"false" Brew-by-Weight (EEPROM)
toggleLight "true"/"false" Beleuchtung (SSR + EEPROM)

Waage

action value Wirkung
tareScale Tarieren (nur I2C/HX711; ESP-NOW nicht über UI)
toggleScaleMode "true"/"false" Waage-Anzeige-/Wiegemodus (temporär)

Profil

action value Wirkung
loadProfile Profilname Profil laden (identisch zu op:"loadProfile")

Sperre bei Reinigungsassistent: Solange cleaningAssistantActive, weisen startEco, toggleMaintenance, startShot, stopShot, startSteam, stopSteam, flush, steamFlush mit success:false und entsprechender Meldung ab. Das Display sollte diese Aktionen dann ausgegraut darstellen (Status maintenance).

Unbekannte Aktion: liefert message:"Unbekannte Aktion." und success:false.


7. Status-Schlüssel (statusKey)

Aus getTouchUartStatus(). Für sprach-unabhängige UI-Logik/Icons auf dem P4 verwenden (statt statusText zu parsen):

ready, error, standby, maintenance, tuning, brewing, eco, fastheatup, heating, ready_eco_pending.


8. Geplante Erweiterungen — Protokoll v2

Für ein vollwertiges 480×800-Touch-UI fehlen v1 noch einige Daten/Funktionen. Geplant:

8.1 Neue Telemetrie-Felder im state

  • [erledigt A.1] Netzwerk: wifiConnected, apMode, ssid, ip, rssi.
  • [erledigt A.1] PID: aktuelle Kp/Ki/Kd Wasser & Dampf (read-only).
  • [erledigt A.1] AutoTune: autoTuneW / autoTuneD (laufend ja/nein).
  • [erledigt] AutoTune-Ergebnis: autoTuneWStatus / autoTuneDStatus (Erfolg/Abbruch + Grund; siehe §8.1-Tabelle). P4-Dashboard blendet das Ergebnis nach Lauf-Ende ~30 s als Banner ein; S3-Web /PID-Tuning zeigt es persistent.
  • [erledigt A.1] Heizleistung: Duty Wasser & Dampf (dutyW/dutyD, für Balkenanzeige).
  • [offen] Flow-Rate: flowRate (g/s) als Live-Zahl (kein Chart) — bei Bedarf später.
  • [offen] AutoTune-Fortschritt: numerischer Fortschritt (Peak-Count) — derzeit nur Lauf-Flag + Ergebnis-/Abbruchstatus.

Kein Chart am Display (Entscheidung): Es wird keine Verlaufs-/Historiendaten- Übertragung benötigt. Damit entfällt der aufwändigste Teil (großer Ringpuffer, Chunking).

8.2 Neue Kommandos (op)

op Status Zweck
hello erledigt Handshake: P4 meldet sich, S3 antwortet mit type:"hello" + firmwareVersion, protocolVersion, capabilities[], danach state + ack.
getProfileDetails erledigt Profil-Inhalt (profile) → Antwort type:"profileDetails" mit allen Feldern (Setpoints, Offsets, Kp/Ki/Kd, Boost, Window, Eco, Brew, Piezo, FastHeatUp).
setPid erledigt Felder kpW/kiW/kdW/kpD/kiD/kdD (alle optional) → EEPROM + Live-SetTunings.
startAutotune erledigt target:water|steam (auch wasser/dampf). Spiegelt handleAutoTune* (Guard: nur wenn keins läuft, Wartungsmodus wird zurückgesetzt).
stopAutotune erledigt Bricht laufendes AutoTune Wasser und Dampf ab.
scanWifi erledigt Startet einen asynchronen WLAN-Scan. Sofort ack; wenn fertig, pusht die S3 type:"wifiNetworks" (Array {ssid,rssi,enc}, max. 20). Timeout 15 s.
setWifi erledigt Felder ssid (Pflicht) + password (optional, offenes Netz = leer). Speichert in WiFiConfig (EEPROM) und ruft WiFi.begin(...) nicht-blockierend auf; vorhandene IP-Einstellungen bleiben erhalten. Fortschritt über die wifi*-Felder im state.
getUsageStats erledigt Nutzungsstatistik anfordern → Antwort type:"usageStats". Aggregierte Kennzahlen aus calculateShotStatistics() (today/week/month, yesterday/lastWeek/lastMonth, total, avgDur, avgPerDay) plus shots[] = letzte ~12 Einzelbezüge (dt, d=Dauer s, w=Gewicht g bzw. -1). Es wird nur das Dateiende von /Nutzungsstatistik.csv gelesen, nie die ganze Datei (P4-Heap schonen).
getNetworkInfo entfällt WLAN-Details sind bereits im state enthalten — eigener Op unnötig.

Antwort auf scanWifi (Beispiel, eigene Push-Nachricht sobald der Scan fertig ist):

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