Der Ordner hieß JC_Display_Firmware, bedient seit 1.6.0 aber auch das Waveshare 7inch DSI LCD (H) am ESP32-P4-Pico. Der Name führte in die Irre. - JC_Display_Firmware/ -> P4_Display_Firmware/ (samt Sketch, den Arduino gleichnamig zum Ordner verlangt) - Doku/JC-Display_UART-Protokoll.md -> Doku/P4-Display_UART-Protokoll.md, Verweise und Titel angepasst - Verweise in CLAUDE.md, README und Quelltextköpfen nachgezogen Nur Namen und Pfade, keine Logikänderung. Der Vendor-Ordner JC_Display_Firmware_7zoll/ behält seinen Namen, ebenso die Bezeichner JC_PANEL_TYPE/jc_board_bringup im Quelltext. Enthält außerdem die bereits im Arbeitsverzeichnis liegende, noch nicht committete Ergänzung des Git-Workflows in CLAUDE.md (PRs über die Gitea-API statt der hängenden tea-CLI). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018QoLDQeUh1dCQz7yYZVb4Y
621 lines
34 KiB
Markdown
621 lines
34 KiB
Markdown
# P4-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 **P4-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 P4-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 P4-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 (P4-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 P4-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 |
|
||
| `wakeEspresso` | — | Standby beenden (falls aktiv) und das Auswahlfenster schließen, also „normal heizen" wählen (ab S3 5.6.0) |
|
||
|
||
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`, `cxAskOnWake` (bool). 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`, 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 | **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) |
|
||
| `cxAskOnWake` | bool | Beim Aufwecken aus dem Standby nach Espresso/Cold Extraction fragen (ab S3 5.5.0) |
|
||
| `cxWakeChoice` | bool | Auswahlfenster nach dem Aufwecken offen — Wasserkreis heizt solange nicht (ab S3 5.6.0) |
|
||
| `cxWakeChoiceSec` | long | Restsekunden der Bedenkzeit |
|
||
| `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):
|
||
> ```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 P4-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 P4-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 P4-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 (P4-Display, Projekt `P4_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 P4-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).*
|