Compare commits

...
Author SHA1 Message Date
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
thomas 4cc9ceea7c Merge pull request 'fix(S3): Cold Extraction brach waehrend der Vorbenetzung ab (5.3.3)' (#15) from fix/cold-extraction-vorbenetzung into main 2026-08-11 01:55:31 +02:00
raw-designsandClaude Opus 5 881acec3df fix(S3): Cold Extraction brach während der Vorbenetzung ab (5.3.3)
Die Stillstands-Erkennung lief bereits während der Vorbenetzung mit. In dieser
Phase sättigt der Puck aber erst, in der Tasse kommt naturgemäß nichts an — bei
einer Vorbenetzung länger als die 15 s Stillstands-Zeit wurde deshalb jeder kalte
Bezug mit "kein Zulauf" abgebrochen. Mit dem bisherigen Standard von 30 s wäre das
ausnahmslos passiert; die Funktion war mit aktiver Waage unbenutzbar. Die
Erkennung startet jetzt erst mit dem Hauptbezug.

Dazu der Standard der Vorbenetzung von 30 s auf 10 s: Die Pumpe läuft in dieser
Phase bewusst ungepulst auf voller Leistung. Heiß kommt der erste Tropfen nach
etwa 7 s, kalt (rund dreifache Viskosität) entsprechend später — 30 s wären längst
voller Bezug bei vollem Druck gewesen, und die Hälfte des Pumpen-Laufzeitbudgets
wäre vor dem eigentlichen Bezug verbraucht. Bestehende Installationen behalten
ihren gespeicherten Wert.

Die Beschriftung auf /Brew-Control sagt jetzt ausdrücklich, dass die Pumpe während
der Vorbenetzung durchläuft und erst im Hauptbezug gepulst wird.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 01:53:48 +02:00
thomas bc78a10505 Merge pull request 'fix(P4): Compiler-Warnungen des Display-Builds beseitigt (1.1.1)' (#14) from fix/p4-compiler-warnungen into main 2026-08-11 01:23:02 +02:00
raw-designsandClaude Opus 5 1f8cce48aa fix(P4): Compiler-Warnungen des Display-Builds beseitigt (1.1.1)
Von 68 Warnungen (arduino-cli --warnings all) auf eine einzige, die im
ESP32-Core-SDK steckt und nicht im Projekt behebbar ist.

- LV_FS_DEFAULT_DRIVE_LETTER heißt ab LVGL 9.3 LV_FS_DEFAULT_DRIVER_LETTER. Der
  alte Name funktionierte weiter, löste aber in lv_api_map_v9_1.h ein #warning in
  JEDER Übersetzungseinheit aus, die lvgl.h einbindet — allein rund 60 Warnungen
  pro Build, die alles andere zugedeckt haben. In lv_conf.h umbenannt.
- LV_PART_* und LV_STATE_* sind in LVGL 9 verschiedene Enum-Typen; die direkte
  |-Verknüpfung ist in C++20 abgekündigt. Neues Makro TH_SEL(part, state) in
  theme.h castet beide auf lv_style_selector_t (ohnehin uint32_t), 5 Stellen.
- Bug-Fix: Der Puffer der Cold-Extraction-Infozeile auf der Brew-Seite war mit 192
  Bytes zu klein für den ~205 Zeichen langen Text (das "°" zählt doppelt), die
  letzte Zeile wurde abgeschnitten. Jetzt 256 Bytes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 01:20:49 +02:00
thomas 1933a41e64 Merge pull request 'feat(P4): Cold Extraction am Touch-Display + RX-Zeilenlimit-Fix (1.1.0 / S3 5.3.2)' (#13) from feat/cold-extraction-p4 into main 2026-08-10 23:58:06 +02:00
8 changed files with 164 additions and 30 deletions
+28
View File
@@ -1,3 +1,31 @@
Version 5.3.4:
- Cold Extraction weist jetzt deutlich darauf hin, wenn keine Waage aktiv/verbunden ist. Ohne 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 das stillschweigend.
- Die Funktion bleibt ohne Waage bewusst nutzbar (Zeitsteuerung als Rückfallebene): Ein kurzzeitig
abgemeldeter HX711 soll die kalte Extraktion nicht komplett blockieren.
- Angezeigt wird das an vier Stellen: dauerhafter Warnhinweis auf /Brew-Control (dort ist das
Zielgewicht-Feld zusätzlich als „nur mit Waage wirksam" beschriftet), Hinweiszeile unter dem
Dashboard-Schalter, Statustext „Cold Extraction bereit (ohne Waage: Zeitsteuerung)" sowie eine
einmalige Meldung beim Aktivieren.
- Neuer Code im UART-Feld cxNotice: 4 = ohne Waage aktiviert (additiv, Protokoll bleibt v2).
Version 5.3.3:
- Bug-Fix (Cold Extraction, hätte die Funktion mit Waage komplett unbrauchbar gemacht): Die
Stillstands-Erkennung lief bereits während der Vorbenetzung mit. In dieser Phase sättigt der Puck
aber erst, es kommt naturgemäß nichts in der Tasse an - bei einer Vorbenetzung länger als die
15 s Stillstands-Zeit wurde deshalb JEDER kalte Bezug mit „kein Zulauf" abgebrochen. Mit dem
bisherigen Standard von 30 s Vorbenetzung wäre das immer passiert. Die Erkennung startet jetzt
erst mit dem Hauptbezug.
- Standard der Vorbenetzung von 30 s auf 10 s gesenkt. In dieser Phase läuft die Pumpe bewusst
ungepulst auf voller Leistung; bei heißem Bezug kommt der erste Tropfen nach etwa 7 s, kalt
(rund dreifache Viskosität) entsprechend später. 30 s wären also längst voller Bezug bei vollem
Druck gewesen, und die Hälfte des Pumpen-Laufzeitbudgets wäre vor dem eigentlichen Bezug
verbraucht. Bestehende Installationen behalten ihren gespeicherten Wert - dort ggf. auf
/Brew-Control nachziehen.
- Die Beschriftung auf /Brew-Control sagt jetzt ausdrücklich, dass die Pumpe während der
Vorbenetzung durchläuft und erst im Hauptbezug gepulst wird.
Version 5.3.2:
- Cold Extraction sendet den Sperrgrund jetzt als Code („cxBlock") statt als Klartext („cxMessage"),
dazu eine transiente Meldung als Code („cxNotice") und die Vorbenetzungsdauer („cxPreInf").
+7 -1
View File
@@ -396,7 +396,13 @@ Aus `getTouchUartStatus()`. Für sprach-unabhängige UI-Logik/Icons auf dem P4 v
`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.
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 —
+61 -17
View File
@@ -369,7 +369,7 @@ Adafruit_SH1106G display = Adafruit_SH1106G(128, 64, &Wire);
* Firmware-Informationen
************************************************************************************/
String version = "5.3.2";
String version = "5.3.4";
String versionHersteller = "Thomas M&uuml;ller";
String versionHerstellerMail =
"<a href='mailto:thomas@mueller.black' class='info-link'>thomas@mueller.black</a>";
@@ -782,7 +782,7 @@ bool flowGuardPumpPulseActive = false;
************************************************************************************/
bool coldExtractionEnabled = false; // Funktion in der Web-UI freigeschaltet
float coldExtractionMaxTempC = 30.0f; // Freigabeschwelle Wasserkessel
float coldExtractionPreInfusionSeconds = 30.0f; // Vorbenetzung (Pumpe laeuft ungepulst)
float coldExtractionPreInfusionSeconds = 10.0f; // Vorbenetzung (Pumpe laeuft ungepulst)
uint16_t coldExtractionPulsePeriodMs = 2000; // Pulsperiode der Pumpe im Hauptbezug
float coldExtractionDutyPercent = 30.0f; // Pumpen-Duty im Hauptbezug
float coldExtractionTargetGrams = 60.0f; // Zielgewicht (ca. 1:3 auf 18 g)
@@ -792,7 +792,11 @@ float coldExtractionPumpRestSec = 20.0f; // Dauer der Zwangspause
const bool defaultColdExtractionEnabled = false;
const float defaultColdExtractionMaxTempC = 30.0f;
const float defaultColdExtractionPreInfusionSeconds = 30.0f;
// 10 s statt der urspruenglichen 30 s: Die Pumpe laeuft in dieser Phase ungepulst auf
// voller Leistung. Heiss kommt der erste Tropfen nach ~7 s, kalt (rund dreifache
// Viskositaet) entsprechend spaeter - 30 s waeren laengst voller Bezug bei vollem Druck
// gewesen, und die Haelfte des Pumpen-Laufzeitbudgets waere vor dem Hauptbezug verbraucht.
const float defaultColdExtractionPreInfusionSeconds = 10.0f;
const uint16_t defaultColdExtractionPulsePeriodMs = 2000;
const float defaultColdExtractionDutyPercent = 30.0f;
const float defaultColdExtractionTargetGrams = 60.0f;
@@ -853,12 +857,13 @@ enum ColdExtractionBlock : uint8_t {
COLDEX_BLOCK_TOO_WARM = 7
};
// Transiente Meldung (cxNotice): 0=keine, 1=Start abgewiesen, 2=Bezug wegen fehlenden
// Zulaufs beendet, 3=Umschalten waehrend eines Bezugs abgelehnt
// Zulaufs beendet, 3=Umschalten waehrend eines Bezugs abgelehnt, 4=ohne Waage aktiviert
enum ColdExtractionNotice : uint8_t {
COLDEX_NOTICE_NONE = 0,
COLDEX_NOTICE_START_REJECTED = 1,
COLDEX_NOTICE_NO_FLOW = 2,
COLDEX_NOTICE_BUSY = 3
COLDEX_NOTICE_BUSY = 3,
COLDEX_NOTICE_NO_SCALE = 4
};
uint8_t coldExtractionNotice = COLDEX_NOTICE_NONE;
@@ -4046,6 +4051,13 @@ bool coldExtractionTemperatureOk() {
return (InputWasser < (double)limit);
}
// Steht eine nutzbare Waage bereit? Ohne sie laeuft die kalte Extraktion rein zeitgesteuert:
// Zielgewicht und Stillstands-Erkennung greifen nicht, es bleibt nur die maximale Bezugsdauer.
// Bewusst kein Sperrgrund - ein kurz abgemeldeter HX711 soll die Funktion nicht blockieren.
bool coldExtractionScaleUsable() {
return scaleEnabled && scaleConnected;
}
// Allokationsfreie Freigabepruefung - wird in den JSON-Pushes (2x/s) verwendet.
bool coldExtractionConditionsOk() {
return coldExtractionEnabled &&
@@ -4120,6 +4132,15 @@ bool setColdExtractionMode(bool enable, String& message) {
resetColdExtractionRuntime();
persistColdExtractionMode();
message = "Cold Extraction aktiviert - Wasserkreis heizt nicht (bleibt auch nach Standby/Neustart aktiv).";
if (!coldExtractionScaleUsable()) {
// Ohne Waage faellt die Dosierung auf die maximale Bezugsdauer zurueck - das
// muss der Benutzer wissen, sonst wundert er sich ueber das Ergebnis.
message += " Achtung: keine Waage - Zielgewicht und Stillstands-Erkennung greifen nicht,"
" der Bezug endet erst nach der maximalen Bezugsdauer.";
coldExtractionBlockMessage = "Keine Waage: Bezug endet nach Zeit, nicht nach Gewicht.";
coldExtractionBlockMessageUntilMs = millis() + COLDEX_BLOCK_MESSAGE_MS;
coldExtractionNotice = COLDEX_NOTICE_NO_SCALE;
}
return true;
}
@@ -4202,8 +4223,12 @@ bool coldExtractionStallDetected(float netWeight) {
coldExtractionStallSinceMs = 0;
return false;
}
if (coldExtractionPumpResting) {
coldExtractionStallSinceMs = 0; // Pause ist kein Stillstand
// Waehrend der Vorbenetzung saettigt der Puck erst - da kommt naturgemaess nichts in
// der Tasse an. Ohne diese Ausnahme wuerde die Erkennung jeden kalten Bezug abbrechen,
// sobald die Vorbenetzung laenger als COLDEX_STALL_TIMEOUT_MS dauert. Der Zaehler
// startet damit erst mit dem Hauptbezug.
if (coldExtractionPumpResting || currentPreInfusionState == PI_PRE_BREW) {
coldExtractionStallSinceMs = 0; // Pause/Vorbenetzung ist kein Stillstand
coldExtractionStallLastWeight = netWeight;
return false;
}
@@ -7144,13 +7169,13 @@ static const char brewControlCX_ToggleContainerEnd[] PROGMEM = R"rawliteral(><sp
static const char brewControlCX_Fields[] PROGMEM = R"rawliteral(
<label for='cxMaxTemp'>Freigabe bis Wassertemperatur (&deg;C):</label>
<input type='number' min='10.0' max='40.0' step='0.5' id='cxMaxTemp' name='cxMaxTemp' value='{CX_MAX_TEMP}' required>
<label for='cxPreInf'>Vorbenetzung (Sekunden):</label>
<label for='cxPreInf'>Vorbenetzung (Sekunden, Pumpe l&auml;uft dabei durchgehend):</label>
<input type='number' min='0.0' max='120.0' step='1.0' id='cxPreInf' name='cxPreInf' value='{CX_PREINF}' required>
<label for='cxPulseMs'>Pulsperiode (ms):</label>
<input type='number' min='200' max='5000' step='50' id='cxPulseMs' name='cxPulseMs' value='{CX_PULSE_MS}' required>
<label for='cxDuty'>Pumpenleistung (%):</label>
<input type='number' min='5.0' max='100.0' step='1.0' id='cxDuty' name='cxDuty' value='{CX_DUTY}' required>
<label for='cxTarget'>Zielgewicht (Gramm):</label>
<label for='cxTarget'>Zielgewicht (Gramm, nur mit Waage wirksam):</label>
<input type='number' min='10.0' max='300.0' step='1.0' id='cxTarget' name='cxTarget' value='{CX_TARGET}' required>
<label for='cxMaxSecs'>Maximale Bezugsdauer (Sekunden):</label>
<input type='number' min='30.0' max='900.0' step='5.0' id='cxMaxSecs' name='cxMaxSecs' value='{CX_MAX_SECS}' required>
@@ -7161,7 +7186,9 @@ static const char brewControlCX_Fields[] PROGMEM = R"rawliteral(
<small class="toggle-description-input">(Die Maschine hat keinen Bypass um den Wasserkessel: Kaltes Wasser kommt nur bei kaltem
Kessel am Puck an. Deshalb ist die kalte Extraktion nur unterhalb der Freigabetemperatur m&ouml;glich, praktisch also
aus dem kalten Zustand heraus. Solange der Modus aktiv ist, heizt der Wasserkreis nicht - der Dampfkreis bleibt
unber&uuml;hrt und darf weiter heizen. Die Pumpenlaufzeit wird kumuliert gez&auml;hlt; nach der eingestellten
unber&uuml;hrt und darf weiter heizen. W&auml;hrend der Vorbenetzung l&auml;uft die Pumpe durchgehend auf voller
Leistung (kein Pulsen) - sinnvoll ist eine Dauer knapp unterhalb des ersten Tropfens; gepulst wird erst im
Hauptbezug. Die Pumpenlaufzeit wird kumuliert gez&auml;hlt; nach der eingestellten
Laufzeit legt die Steuerung eine Zwangspause ein. Der Modus selbst wird auf dem Dashboard bzw. am Touch-Display
ein- und ausgeschaltet und bleibt &uuml;ber Standby und Neustart hinweg aktiv, bis er dort wieder ausgeschaltet
wird - solange heizt der Wasserkreis nicht.)</small>
@@ -7306,12 +7333,21 @@ void handleBrewControl(AsyncWebServerRequest *request) {
snprintf(buffer, sizeof(buffer), "%.1f", coldExtractionPumpRestSec);
cxFieldsHtml.replace("{CX_PUMP_REST}", buffer);
if (coldExtractionEnabled) {
String cxStatus = coldExtractionBlockReason();
if (cxStatus.length() > 0) {
cxFieldsHtml.replace("{CX_STATUS_MSG}", "<br><b><small style='color: #FFCC00;'>" + cxStatus + "</small></b>");
} else {
cxFieldsHtml.replace("{CX_STATUS_MSG}", "<br><b><small style='color: #66CC66;'>Kalte Extraktion derzeit m&ouml;glich.</small></b>");
String cxStatus;
// Waagen-Hinweis zuerst: er gilt unabhaengig von der Temperatur-Freigabe und
// erklaert, warum das Zielgewicht oben wirkungslos ist.
if (!coldExtractionScaleUsable()) {
cxStatus += "<br><b><small style='color: #FFCC00;'>Keine Waage aktiv/verbunden: Zielgewicht und "
"Stillstands-Erkennung greifen nicht. Der Bezug endet erst nach der maximalen "
"Bezugsdauer - diesen Wert dann als Dosierung verstehen.</small></b>";
}
String reason = coldExtractionBlockReason();
if (reason.length() > 0) {
cxStatus += "<br><b><small style='color: #FFCC00;'>" + reason + "</small></b>";
} else {
cxStatus += "<br><b><small style='color: #66CC66;'>Kalte Extraktion derzeit m&ouml;glich.</small></b>";
}
cxFieldsHtml.replace("{CX_STATUS_MSG}", cxStatus);
} else {
cxFieldsHtml.replace("{CX_STATUS_MSG}", "");
}
@@ -14986,6 +15022,11 @@ static const char dashboardJavaScript[] PROGMEM = R"rawliteral(
hintText = 'Cold Extraction derzeit nicht möglich.';
}
}
// Waagen-Hinweis gilt zusätzlich und unabhängig von der Temperatur-Freigabe
if (data.cxEnabled === true && (data.scaleEnabled !== true || data.scaleConnected !== true)) {
if (hintText) { hintText += ' '; }
hintText += 'Ohne Waage: Bezug endet nach Zeit, nicht nach Gewicht.';
}
coldexHint.textContent = hintText;
coldexHint.style.display = hintText ? '' : 'none';
}
@@ -16031,7 +16072,9 @@ void buildDashboardJson(char *buffer, size_t bufferSize) {
}
statusKey = "brewing";
} else if (coldExtractionModeActive) {
statusText = "Cold Extraction bereit (Wasserkreis heizt nicht)";
statusText = coldExtractionScaleUsable()
? "Cold Extraction bereit (Wasserkreis heizt nicht)"
: "Cold Extraction bereit (ohne Waage: Zeitsteuerung)";
statusKey = "coldextraction";
} else if (ecoModeAktiv && ecoSwitchActive) {
statusText = dynamicEcoActive ? "Eco+ Modus aktiv (Schalter)" : "Eco Modus aktiv (Schalter)";
@@ -16278,7 +16321,8 @@ static void getTouchUartStatus(String& statusText, String& statusKey) {
}
statusKey = "brewing";
} else if (coldExtractionModeActive) {
statusText = "Cold Extraction bereit";
statusText = coldExtractionScaleUsable() ? "Cold Extraction bereit"
: "Cold Extraction bereit (Zeitsteuerung)";
statusKey = "coldextraction";
} else if (ecoModeAktiv) {
statusText = dynamicEcoActive ? "Eco+ Modus aktiv" : "Eco Modus aktiv";
+24
View File
@@ -1,3 +1,27 @@
Version 1.1.2:
- Cold Extraction: Fehlt eine nutzbare Waage, weist das Display jetzt dauerhaft darauf hin.
Ohne Waage greifen weder Zielgewicht noch Stillstands-Erkennung, der Bezug endet erst nach der
maximalen Bezugsdauer (braucht S3-Firmware ab 5.3.4).
- Statuszeile: „Cold Extraction aktiv - ohne Waage, Ende nach Zeit" (gelb) statt des blauen
Normalhinweises; beim Aktivieren zusätzlich kurz die Meldung zum neuen cxNotice-Code 4.
- Erklärzeile unter dem Schalter und die Parameter-Anzeige auf der Brew-Seite ergänzen den
Hinweis; bei aktivem Modus ohne Waage nennt die Zeile die Zeit statt des Zielgewichts.
Version 1.1.1:
- Compiler-Warnungen des P4-Builds aufgeräumt (rein technisch, keine Funktionsänderung):
- „LV_FS_DEFAULT_DRIVE_LETTER is deprecated": Die Option heißt ab LVGL 9.3
LV_FS_DEFAULT_DRIVER_LETTER. Der alte Name funktionierte zwar noch, löste aber in
lv_api_map_v9_1.h ein #warning aus - und zwar in JEDER Übersetzungseinheit, die lvgl.h
einbindet. Das allein waren rund 60 Warnungen pro Build, die alles andere zugedeckt haben.
In lv_conf.h umbenannt.
- „bitwise operation between different enumeration types is deprecated": LV_PART_* und
LV_STATE_* sind in LVGL 9 zwei verschiedene Enum-Typen; sie direkt mit | zu verknüpfen ist
in C++20 abgekündigt. Neues Makro TH_SEL(part, state) in theme.h castet beide auf
lv_style_selector_t (ohnehin uint32_t). Betraf 5 Stellen in ui.cpp und theme.h.
- Bug-Fix: Der Puffer für die Cold-Extraction-Infozeile auf der Brew-Seite war mit 192 Bytes
zu klein - der Text ist rund 205 Zeichen lang (das „°" zählt als zwei Bytes), die letzte
Zeile wurde also abgeschnitten. Jetzt 256 Bytes.
Version 1.1.0:
- Cold Extraction am Display bedienbar (braucht S3-Firmware ab 5.3.2):
- Neuer Schalter „Cold Extraction" auf der Seite „Temperaturen" unter „Modi", darunter eine
+1 -1
View File
@@ -65,7 +65,7 @@
// -------------------------------------------------------------------------------------
// Firmware
// -------------------------------------------------------------------------------------
#define DISPLAY_FW_VERSION "1.1.0" // Firmware-Stand der P4-Display-Steuerung (Info-Seite)
#define DISPLAY_FW_VERSION "1.1.2" // Firmware-Stand der P4-Display-Steuerung (Info-Seite)
// -------------------------------------------------------------------------------------
// Protokoll
+4 -1
View File
@@ -727,7 +727,10 @@
/*File system interfaces for common APIs */
/*Setting a default driver letter allows skipping the driver prefix in filepaths*/
#define LV_FS_DEFAULT_DRIVE_LETTER '\0'
/* Ab LVGL 9.3 heisst die Option LV_FS_DEFAULT_DRIVER_LETTER. Der alte Name wird zwar noch
uebersetzt, loest aber in lv_api_map_v9_1.h ein #warning aus - und zwar in JEDER
Uebersetzungseinheit, die lvgl.h einbindet (~60 Warnungen pro Build). */
#define LV_FS_DEFAULT_DRIVER_LETTER '\0'
/*API for fopen, fread, etc*/
#define LV_USE_FS_STDIO 0
+6 -1
View File
@@ -25,6 +25,11 @@
#define COL_BORDER lv_color_hex(0x555555)
#define COL_INPUT_BG lv_color_hex(0x101010)
// Style-Selektor aus Part + State. LV_PART_* und LV_STATE_* sind in LVGL 9 zwei
// verschiedene Enum-Typen; sie direkt mit | zu verknuepfen ist in C++20 deprecated
// (-Wdeprecated-enum-enum-conversion). Der Zieltyp ist ohnehin uint32_t.
#define TH_SEL(part, state) ((lv_style_selector_t)(part) | (lv_style_selector_t)(state))
// ---- Karte (dunkel, abgerundet) ----
static inline lv_obj_t* th_card(lv_obj_t* parent) {
lv_obj_t* c = lv_obj_create(parent);
@@ -61,7 +66,7 @@ static inline lv_obj_t* th_textarea(lv_obj_t* parent, bool oneLine) {
lv_textarea_set_one_line(ta, oneLine);
lv_obj_set_style_bg_color(ta, COL_INPUT_BG, 0);
lv_obj_set_style_border_color(ta, COL_BORDER, 0);
lv_obj_set_style_border_color(ta, COL_ACCENT, LV_PART_MAIN | LV_STATE_FOCUSED);
lv_obj_set_style_border_color(ta, COL_ACCENT, TH_SEL(LV_PART_MAIN, LV_STATE_FOCUSED));
lv_obj_set_style_border_width(ta, 1, 0);
lv_obj_set_style_radius(ta, 8, 0);
lv_obj_set_style_text_color(ta, COL_TEXT, 0);
+33 -9
View File
@@ -389,7 +389,7 @@ static lv_obj_t* row(lv_obj_t* parent, const char* label) {
static lv_obj_t* add_switch(lv_obj_t* parent, const char* label) {
lv_obj_t* r = row(parent, label);
lv_obj_t* sw = lv_switch_create(r);
lv_obj_set_style_bg_color(sw, COL_ACCENT, LV_PART_INDICATOR | LV_STATE_CHECKED);
lv_obj_set_style_bg_color(sw, COL_ACCENT, TH_SEL(LV_PART_INDICATOR, LV_STATE_CHECKED));
return sw;
}
static lv_obj_t* add_num(lv_obj_t* parent, const char* label) {
@@ -2348,7 +2348,7 @@ static void build_wifi(lv_obj_t* p) {
// Aktionszeile: Scan + gewaehltes Netz
lv_obj_t* ar = row(p, nullptr);
g_btnScan = th_accent_button(ar, LV_SYMBOL_REFRESH " Netzwerke suchen");
lv_obj_set_style_bg_opa(g_btnScan, LV_OPA_40, LV_PART_MAIN | LV_STATE_DISABLED);
lv_obj_set_style_bg_opa(g_btnScan, LV_OPA_40, TH_SEL(LV_PART_MAIN, LV_STATE_DISABLED));
lv_obj_add_event_cb(g_btnScan, wifi_scan_cb, LV_EVENT_CLICKED, nullptr);
lblWifiSel = lv_label_create(ar);
lv_label_set_text(lblWifiSel, "Netz: -");
@@ -2585,13 +2585,13 @@ static void build_profiles(lv_obj_t* p) {
lv_obj_set_style_text_color(profDetailLbl, COL_TEXT_DIM, 0);
profLoadBtn = th_accent_button(dcard, "Profil laden");
lv_obj_set_width(profLoadBtn, LV_PCT(100));
lv_obj_set_style_bg_opa(profLoadBtn, LV_OPA_40, LV_PART_MAIN | LV_STATE_DISABLED);
lv_obj_set_style_bg_opa(profLoadBtn, LV_OPA_40, TH_SEL(LV_PART_MAIN, LV_STATE_DISABLED));
lv_obj_add_state(profLoadBtn, LV_STATE_DISABLED);
lv_obj_add_event_cb(profLoadBtn, prof_do_load_cb, LV_EVENT_CLICKED, nullptr);
profDelBtn = th_button(dcard, "Profil löschen", COL_DANGER, COL_TEXT);
lv_obj_set_width(profDelBtn, LV_PCT(100));
lv_obj_set_style_bg_opa(profDelBtn, LV_OPA_40, LV_PART_MAIN | LV_STATE_DISABLED);
lv_obj_set_style_bg_opa(profDelBtn, LV_OPA_40, TH_SEL(LV_PART_MAIN, LV_STATE_DISABLED));
lv_obj_add_state(profDelBtn, LV_STATE_DISABLED);
lv_obj_add_event_cb(profDelBtn, prof_delete_cb, LV_EVENT_CLICKED, nullptr);
}
@@ -3446,6 +3446,9 @@ void ui_update(const MachineState& st) {
// Cold-Extraction-Hinweis: erklaert dauerhaft, warum der Wasserkreis nicht heizt.
// Transiente Meldung (abgewiesener Start / Abbruch) hat Vorrang vor dem Dauerzustand.
// Ohne nutzbare Waage laeuft die kalte Extraktion rein zeitgesteuert (Zielgewicht und
// Stillstands-Erkennung greifen nicht) - das wird dauerhaft mit angezeigt.
const bool cxScaleOk = st.scaleEnabled && st.scaleConnected;
if (st.cxNotice == 2) {
status_set(ST_COLDEX, true, LV_SYMBOL_WARNING " Cold Extraction beendet: kein Zulauf",
COL_WARN, lv_color_hex(0x000000));
@@ -3456,6 +3459,9 @@ void ui_update(const MachineState& st) {
String t = "Cold Extraction nicht möglich - ";
t += coldex_block_text(st.cxBlock, st.cxMaxTemp, st.tempW);
status_set(ST_COLDEX, true, t.c_str(), COL_WARN, lv_color_hex(0x000000));
} else if (st.cxNotice == 4) {
status_set(ST_COLDEX, true, LV_SYMBOL_WARNING " Cold Extraction ohne Waage: Bezug endet nach Zeit",
COL_WARN, lv_color_hex(0x000000));
} else if (st.cxActive && st.cxResting) {
status_set(ST_COLDEX, true, "Cold Extraction: Pumpenpause (Pumpenschutz)",
COL_ACCENT, lv_color_hex(0x000000));
@@ -3463,6 +3469,11 @@ void ui_update(const MachineState& st) {
String t = "Cold Extraction aktiv, Bezug gesperrt - ";
t += coldex_block_text(st.cxBlock, st.cxMaxTemp, st.tempW);
status_set(ST_COLDEX, true, t.c_str(), COL_WARN, lv_color_hex(0x000000));
} else if (st.cxActive && !cxScaleOk) {
// Dauerhinweis, solange ohne Waage gefahren wird: das Zielgewicht greift nicht,
// der Bezug endet erst nach der maximalen Bezugsdauer.
status_set(ST_COLDEX, true, "Cold Extraction aktiv - ohne Waage, Ende nach Zeit",
COL_WARN, lv_color_hex(0x000000));
} else if (st.cxActive) {
status_set(ST_COLDEX, true, "Cold Extraction aktiv - Wasserkreis heizt nicht",
lv_color_hex(0x1976d2), COL_TEXT);
@@ -3845,13 +3856,19 @@ void ui_update(const MachineState& st) {
if (usable) lv_obj_remove_state(swColdEx, LV_STATE_DISABLED);
else lv_obj_add_state(swColdEx, LV_STATE_DISABLED);
if (lblColdExHint && st.cxEnabled) {
const bool scaleOk = st.scaleEnabled && st.scaleConnected;
String hint;
if (st.cxActive) {
hint = "Wasserkreis heizt nicht (bleibt über Standby und Neustart aktiv). ";
if (st.cxAllowed) {
char b[96];
snprintf(b, sizeof(b), "Bezug regulär starten - Ziel %.0f g, max %.0f s.",
(double)st.cxTarget, (double)st.cxMaxSecs);
if (scaleOk) {
snprintf(b, sizeof(b), "Bezug regulär starten - Ziel %.0f g, max %.0f s.",
(double)st.cxTarget, (double)st.cxMaxSecs);
} else {
snprintf(b, sizeof(b), "Bezug regulär starten - Ende nach %.0f s.",
(double)st.cxMaxSecs);
}
hint += b;
} else {
hint += coldex_block_text(st.cxBlock, st.cxMaxTemp, st.tempW);
@@ -3865,18 +3882,25 @@ void ui_update(const MachineState& st) {
(double)st.tempW, (double)st.cxMaxTemp);
hint = b;
}
if (!scaleOk) {
hint += " Ohne Waage: Zielgewicht und Stillstands-Erkennung greifen nicht, "
"der Bezug endet erst nach der maximalen Bezugsdauer.";
}
label_set_if_changed(lblColdExHint, hint.c_str());
}
}
if (lblBrewColdEx) {
if (st.cxEnabled) {
char b[192];
char b[320]; // Grundtext ~205 Zeichen (das ° zaehlt als 2 Bytes) + Waagen-Hinweis
snprintf(b, sizeof(b),
"Cold Extraction (nur Anzeige, Einstellung in der Web-UI):\n"
"Freigabe < %.1f °C | Vorbenetzung %.0f s | Ziel %.0f g | max. %.0f s\n"
"Geschaltet wird der Modus auf der Seite \"Temperaturen\" unter \"Modi\".",
"Geschaltet wird der Modus auf der Seite \"Temperaturen\" unter \"Modi\".%s",
(double)st.cxMaxTemp, (double)st.cxPreInfSec,
(double)st.cxTarget, (double)st.cxMaxSecs);
(double)st.cxTarget, (double)st.cxMaxSecs,
(st.scaleEnabled && st.scaleConnected)
? ""
: "\nOhne Waage ist das Zielgewicht wirkungslos - der Bezug endet nach der max. Dauer.");
label_set_if_changed(lblBrewColdEx, b);
lv_obj_remove_flag(lblBrewColdEx, LV_OBJ_FLAG_HIDDEN);
} else {