feat: Cold Extraction im Standby scharf schaltbar (S3 5.3.5 / P4 1.1.3) #17

Merged
thomas merged 1 commits from feat/cold-extraction-im-standby into main 2026-08-11 03:01:10 +02:00
8 changed files with 132 additions and 15 deletions
+16
View File
@@ -1,3 +1,19 @@
Version 5.3.5:
- Cold Extraction lässt sich jetzt auch im Standby und bei noch warmem Kessel scharf schalten.
Bisher galt „Standby aktiv" und „Kessel zu warm" auch fürs Aktivieren - das war falsch herum
gedacht: Der Modus schaltet die Heizung AB, man kam aber erst an den Schalter, nachdem die
Maschine aufgeweckt war und bereits zu heizen begonnen hatte. Genau der Ablauf, den die
Funktion vermeiden soll.
- Scharfschalten und Beziehen sind jetzt zwei getrennte Bedingungen:
- Scharfschalten (neues State-Feld „cxArmable"): blockiert nur durch fehlende Freischaltung,
Wartungsmodus, Reinigungsassistent, PID-Tuning oder einen laufenden Bezug/Spülvorgang.
- Bezug (unverändert „cxAllowed"): zusätzlich kein Standby, Kessel unter der Freigabeschwelle,
gültiger Sensorwert.
Ist der Modus scharf, der Bezug aber noch gesperrt, sagen Web-UI und Display das jetzt
ausdrücklich („Modus aktiv, Bezug noch gesperrt: …").
- Wird bei warmem Kessel aktiviert, geht die Wasserheizung sofort aus und der Kessel kühlt ab;
die Rückmeldung weist darauf hin, dass Bezüge bis zum Unterschreiten der Schwelle gesperrt sind.
Version 5.3.4: Version 5.3.4:
- Cold Extraction weist jetzt deutlich darauf hin, wenn keine Waage aktiv/verbunden ist. Ohne Waage - 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 greifen weder das Zielgewicht noch die Stillstands-Erkennung - der Bezug endet dann erst nach der
+13 -3
View File
@@ -323,8 +323,17 @@ Alle gültigen `action`-Strings aus `executeDashboardAction()` (Stand v4.8.1, Ze
| `stopColdExtraction` | — | Modus beenden | | `stopColdExtraction` | — | Modus beenden |
| `toggleColdExtraction` | — | Modus umschalten | | `toggleColdExtraction` | — | Modus umschalten |
Alle drei Aktionen werden abgewiesen (`ok:false` + `message`), solange die Bedingungen nicht Für das Bedienelement zählt `cxArmable`, **nicht** `cxAllowed`: Der Modus schaltet die
erfüllt sind — Hauptfall ist ein zu warmer Wasserkessel (`cxAllowed:false`). Der Modus ist 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 **persistent** (EEPROM) und überlebt Standby und Neustart; er endet nur durch explizites
Ausschalten, durch Entzug der Freischaltung auf `/Brew-Control` oder durch einen Werksreset. 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 Das P4-UI sollte `cxActive` deshalb dauerhaft sichtbar spiegeln — sonst wundert sich der
@@ -383,7 +392,8 @@ Aus `getTouchUartStatus()`. Für sprach-unabhängige UI-Logik/Icons auf dem P4 v
| `cxEnabled` | bool | Funktion in der Web-UI freigeschaltet (sonst Bedienelement ausblenden) | | `cxEnabled` | bool | Funktion in der Web-UI freigeschaltet (sonst Bedienelement ausblenden) |
| `cxActive` | bool | Modus scharf: Wasserkreis heizt nicht | | `cxActive` | bool | Modus scharf: Wasserkreis heizt nicht |
| `cxShot` | bool | Der laufende Bezug ist eine kalte Extraktion | | `cxShot` | bool | Der laufende Bezug ist eine kalte Extraktion |
| `cxAllowed` | bool | Freigabe **jetzt** (Wasserkessel kalt genug, kein Standby/Wartung/Tuning) | | `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) | | `cxResting` | bool | Zwangspause des Pumpenschutzes läuft (Bezug läuft weiter) |
| `cxMaxTemp` | float | Freigabeschwelle in °C (für eigene Hinweistexte am Display) | | `cxMaxTemp` | float | Freigabeschwelle in °C (für eigene Hinweistexte am Display) |
| `cxTarget` | float | Zielgewicht in g | | `cxTarget` | float | Zielgewicht in g |
+55 -7
View File
@@ -369,7 +369,7 @@ Adafruit_SH1106G display = Adafruit_SH1106G(128, 64, &Wire);
* Firmware-Informationen * Firmware-Informationen
************************************************************************************/ ************************************************************************************/
String version = "5.3.4"; String version = "5.3.5";
String versionHersteller = "Thomas Müller"; String versionHersteller = "Thomas Müller";
String versionHerstellerMail = String versionHerstellerMail =
"<a href='mailto:thomas@mueller.black' class='info-link'>thomas@mueller.black</a>"; "<a href='mailto:thomas@mueller.black' class='info-link'>thomas@mueller.black</a>";
@@ -4119,7 +4119,7 @@ bool setColdExtractionMode(bool enable, String& message) {
} }
if (enable) { if (enable) {
String reason = coldExtractionBlockReason(); String reason = coldExtractionArmBlockReason();
if (reason.length() > 0) { if (reason.length() > 0) {
message = reason; message = reason;
return false; return false;
@@ -4132,6 +4132,13 @@ bool setColdExtractionMode(bool enable, String& message) {
resetColdExtractionRuntime(); resetColdExtractionRuntime();
persistColdExtractionMode(); persistColdExtractionMode();
message = "Cold Extraction aktiviert - Wasserkreis heizt nicht (bleibt auch nach Standby/Neustart aktiv)."; message = "Cold Extraction aktiviert - Wasserkreis heizt nicht (bleibt auch nach Standby/Neustart aktiv).";
if (standbyModeActive) {
message += " Nach dem Aufwecken bleibt der Wasserkreis aus.";
} else if (!coldExtractionTemperatureOk()) {
// Scharfschalten bei warmem Kessel ist erlaubt (Heizung geht sofort aus), ein
// Bezug aber erst, wenn der Kessel unter die Schwelle gefallen ist.
message += " Der Kessel ist noch zu warm - er kuehlt jetzt ab, Bezuege sind bis dahin gesperrt.";
}
if (!coldExtractionScaleUsable()) { if (!coldExtractionScaleUsable()) {
// Ohne Waage faellt die Dosierung auf die maximale Bezugsdauer zurueck - das // Ohne Waage faellt die Dosierung auf die maximale Bezugsdauer zurueck - das
// muss der Benutzer wissen, sonst wundert er sich ueber das Ergebnis. // muss der Benutzer wissen, sonst wundert er sich ueber das Ergebnis.
@@ -4160,6 +4167,37 @@ bool coldExtractionShotAllowed() {
return coldExtractionModeActive && coldExtractionConditionsOk(); return coldExtractionModeActive && coldExtractionConditionsOk();
} }
/************************************************************************************
* Scharfschalten vs. Beziehen - bewusst zwei verschiedene Bedingungen
************************************************************************************
* Der Modus schaltet die Wasserheizung AB. Das an Standby oder an die Kesseltemperatur
* zu koppeln waere falsch herum: Im Standby kaeme man gar nicht an den Schalter, und
* nach dem Aufwecken heizt die Maschine sofort los - man muesste erst aufheizen lassen,
* um danach auf das Abkuehlen zu warten. Genau der Fall, den die Funktion vermeiden soll.
* Deshalb: Scharfschalten ist im Standby und bei warmem Kessel erlaubt; ueber den
* einzelnen Bezug entscheidet weiterhin coldExtractionShotAllowed().
************************************************************************************/
bool coldExtractionArmable() {
return coldExtractionEnabled && !wartungsModusAktiv && !cleaningAssistantActive &&
!autoTuneWasserActive && !autoTuneDampfActive;
}
String coldExtractionArmBlockReason() {
if (!coldExtractionEnabled) {
return "Cold Extraction ist nicht freigeschaltet.";
}
if (wartungsModusAktiv) {
return "Cold Extraction nicht moeglich: Wartungsmodus aktiv.";
}
if (cleaningAssistantActive) {
return "Cold Extraction nicht moeglich: Reinigungsassistent aktiv.";
}
if (autoTuneWasserActive || autoTuneDampfActive) {
return "Cold Extraction nicht moeglich: PID-Tuning laeuft.";
}
return "";
}
// Pumpensteuerung des kalten Bezugs: Vorbenetzung ungepulst, danach Puls-Duty. // Pumpensteuerung des kalten Bezugs: Vorbenetzung ungepulst, danach Puls-Duty.
// Zusaetzlich Pumpenschutz ueber die kumulierte Einschaltzeit (Zwangspause) - die Olab // Zusaetzlich Pumpenschutz ueber die kumulierte Einschaltzeit (Zwangspause) - die Olab
// hat zwar einen Thermoschutz, ein Auslösen mitten im Bezug wuerde aber den Shot // hat zwar einen Thermoschutz, ein Auslösen mitten im Bezug wuerde aber den Shot
@@ -15011,15 +15049,21 @@ static const char dashboardJavaScript[] PROGMEM = R"rawliteral(
if (coldexRow) { coldexRow.style.display = (data.cxEnabled === true) ? '' : 'none'; } if (coldexRow) { coldexRow.style.display = (data.cxEnabled === true) ? '' : 'none'; }
if (coldexToggleEl) { if (coldexToggleEl) {
updateToggleState(coldexToggleEl, data.cxActive); updateToggleState(coldexToggleEl, data.cxActive);
coldexToggleEl.disabled = (data.cxEnabled !== true) || (data.cxActive !== true && data.cxAllowed !== true); // Umschalten haengt an cxArmable, NICHT an cxAllowed: Der Modus muss auch im
// Standby und bei noch warmem Kessel scharf zu stellen sein.
coldexToggleEl.disabled = (data.cxEnabled !== true) || (data.cxArmable !== true);
} }
if (coldexHint) { if (coldexHint) {
let hintText = ''; let hintText = '';
if (data.cxEnabled === true && data.cxAllowed !== true && data.cxActive !== true) { if (data.cxEnabled === true && data.cxAllowed !== true) {
if (typeof data.tempW === 'number' && typeof data.cxMaxTemp === 'number' && data.tempW >= data.cxMaxTemp) { // Gilt auch bei aktivem Modus: dann ist er scharf, aber der Bezug noch gesperrt
hintText = 'Wasserkessel zu warm (' + data.tempW.toFixed(1) + ' °C, benötigt < ' + data.cxMaxTemp.toFixed(1) + ' °C).'; const prefix = (data.cxActive === true) ? 'Modus aktiv, Bezug noch gesperrt: ' : '';
if (data.standbyActive === true) {
hintText = prefix + 'Standby aktiv - Bezug erst nach dem Aufwecken.';
} else if (typeof data.tempW === 'number' && typeof data.cxMaxTemp === 'number' && data.tempW >= data.cxMaxTemp) {
hintText = prefix + 'Wasserkessel zu warm (' + data.tempW.toFixed(1) + ' °C, benötigt < ' + data.cxMaxTemp.toFixed(1) + ' °C).';
} else { } else {
hintText = 'Cold Extraction derzeit nicht möglich.'; hintText = prefix + 'Bezug derzeit nicht möglich.';
} }
} }
// Waagen-Hinweis gilt zusätzlich und unabhängig von der Temperatur-Freigabe // Waagen-Hinweis gilt zusätzlich und unabhängig von der Temperatur-Freigabe
@@ -16191,6 +16235,8 @@ void buildDashboardJson(char *buffer, size_t bufferSize) {
offset += snprintf(buffer + offset, bufferSize - offset, "\"cxEnabled\":%s,", coldExtractionEnabled ? "true" : "false"); offset += snprintf(buffer + offset, bufferSize - offset, "\"cxEnabled\":%s,", coldExtractionEnabled ? "true" : "false");
offset += snprintf(buffer + offset, bufferSize - offset, "\"cxActive\":%s,", coldExtractionModeActive ? "true" : "false"); offset += snprintf(buffer + offset, bufferSize - offset, "\"cxActive\":%s,", coldExtractionModeActive ? "true" : "false");
offset += snprintf(buffer + offset, bufferSize - offset, "\"cxAllowed\":%s,", coldExtractionConditionsOk() ? "true" : "false"); offset += snprintf(buffer + offset, bufferSize - offset, "\"cxAllowed\":%s,", coldExtractionConditionsOk() ? "true" : "false");
// cxArmable = Modus umschaltbar (auch im Standby und bei warmem Kessel); cxAllowed = Bezug moeglich
offset += snprintf(buffer + offset, bufferSize - offset, "\"cxArmable\":%s,", coldExtractionArmable() ? "true" : "false");
dtostrf(coldExtractionMaxTempC, 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"cxMaxTemp\":%s,", floatBuf); dtostrf(coldExtractionMaxTempC, 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"cxMaxTemp\":%s,", floatBuf);
offset += snprintf(buffer + offset, bufferSize - offset, "\"scaleModeActive\":%s,", scaleModeActive ? "true" : "false"); offset += snprintf(buffer + offset, bufferSize - offset, "\"scaleModeActive\":%s,", scaleModeActive ? "true" : "false");
dtostrf(brewByTimeTargetSeconds, 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"bbtSecs\":%s,", floatBuf); dtostrf(brewByTimeTargetSeconds, 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"bbtSecs\":%s,", floatBuf);
@@ -16517,6 +16563,8 @@ static void buildTouchUartStateJson(char *buffer, size_t bufferSize) {
offset += snprintf(buffer + offset, bufferSize - offset, "\"cxActive\":%s,", coldExtractionModeActive ? "true" : "false"); offset += snprintf(buffer + offset, bufferSize - offset, "\"cxActive\":%s,", coldExtractionModeActive ? "true" : "false");
offset += snprintf(buffer + offset, bufferSize - offset, "\"cxShot\":%s,", coldExtractionShotActive ? "true" : "false"); offset += snprintf(buffer + offset, bufferSize - offset, "\"cxShot\":%s,", coldExtractionShotActive ? "true" : "false");
offset += snprintf(buffer + offset, bufferSize - offset, "\"cxAllowed\":%s,", coldExtractionConditionsOk() ? "true" : "false"); offset += snprintf(buffer + offset, bufferSize - offset, "\"cxAllowed\":%s,", coldExtractionConditionsOk() ? "true" : "false");
// cxArmable = Modus umschaltbar (auch im Standby und bei warmem Kessel); cxAllowed = Bezug moeglich
offset += snprintf(buffer + offset, bufferSize - offset, "\"cxArmable\":%s,", coldExtractionArmable() ? "true" : "false");
offset += snprintf(buffer + offset, bufferSize - offset, "\"cxResting\":%s,", coldExtractionPumpResting ? "true" : "false"); offset += snprintf(buffer + offset, bufferSize - offset, "\"cxResting\":%s,", coldExtractionPumpResting ? "true" : "false");
dtostrf(coldExtractionMaxTempC, 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"cxMaxTemp\":%s,", floatBuf); dtostrf(coldExtractionMaxTempC, 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"cxMaxTemp\":%s,", floatBuf);
dtostrf(coldExtractionTargetGrams, 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"cxTarget\":%s,", floatBuf); dtostrf(coldExtractionTargetGrams, 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"cxTarget\":%s,", floatBuf);
+12
View File
@@ -1,3 +1,15 @@
Version 1.1.3:
- Aufweck-Dialog hat einen dritten Knopf: „Aufwecken mit Cold Extraction" (braucht S3 ab 5.3.5).
Er stellt den Modus scharf und beendet erst danach den Standby - in dieser Reihenfolge, damit
der Wasserkreis gar nicht erst anheizt. Ohne diesen Weg musste man normal aufwecken (Maschine
heizt an) und den Modus danach umschalten. Der Knopf erscheint nur, wenn die Funktion
freigeschaltet und umschaltbar ist und der Modus nicht ohnehin schon läuft.
- Der Schalter „Cold Extraction" auf der Temperatur-Seite hängt jetzt am neuen State-Feld
cxArmable statt an cxAllowed: umschaltbar auch im Standby und bei warmem Kessel, gesperrt nur
während eines laufenden kalten Bezugs. Bei älterer S3-Firmware fällt das Feld auf cxEnabled
zurück, der Schalter bleibt also bedienbar.
- Erklärzeile im Standby: „Nach dem Aufwecken bleibt der Wasserkreis aus."
Version 1.1.2: Version 1.1.2:
- Cold Extraction: Fehlt eine nutzbare Waage, weist das Display jetzt dauerhaft darauf hin. - 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 Ohne Waage greifen weder Zielgewicht noch Stillstands-Erkennung, der Bezug endet erst nach der
+1 -1
View File
@@ -65,7 +65,7 @@
// ------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------
// Firmware // Firmware
// ------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------
#define DISPLAY_FW_VERSION "1.1.2" // Firmware-Stand der P4-Display-Steuerung (Info-Seite) #define DISPLAY_FW_VERSION "1.1.3" // Firmware-Stand der P4-Display-Steuerung (Info-Seite)
// ------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------
// Protokoll // Protokoll
+2 -1
View File
@@ -72,7 +72,8 @@ struct MachineState {
bool cxEnabled = false; // Funktion auf der S3 freigeschaltet (sonst Bedienelement ausblenden) bool cxEnabled = false; // Funktion auf der S3 freigeschaltet (sonst Bedienelement ausblenden)
bool cxActive = false; // Modus scharf: Wasserkreis heizt nicht (persistent, seit S3 5.3.1) bool cxActive = false; // Modus scharf: Wasserkreis heizt nicht (persistent, seit S3 5.3.1)
bool cxShot = false; // der laufende Bezug ist eine kalte Extraktion bool cxShot = false; // der laufende Bezug ist eine kalte Extraktion
bool cxAllowed = false; // Freigabe jetzt (Wasserkessel kalt genug, kein Standby/Wartung/Tuning) bool cxAllowed = false; // Bezug jetzt moeglich (Wasserkessel kalt genug, kein Standby/Wartung/Tuning)
bool cxArmable = false; // Modus umschaltbar - auch im Standby und bei warmem Kessel (seit S3 5.3.5)
bool cxResting = false; // Zwangspause des Pumpenschutzes laeuft (Bezug laeuft weiter) bool cxResting = false; // Zwangspause des Pumpenschutzes laeuft (Bezug laeuft weiter)
float cxMaxTemp = 0; // Freigabeschwelle in C float cxMaxTemp = 0; // Freigabeschwelle in C
float cxTarget = 0; // Zielgewicht in g float cxTarget = 0; // Zielgewicht in g
+2
View File
@@ -225,6 +225,8 @@ void ProtocolClient::applyState(const String& json) {
s.cxActive = doc["cxActive"] | false; s.cxActive = doc["cxActive"] | false;
s.cxShot = doc["cxShot"] | false; s.cxShot = doc["cxShot"] | false;
s.cxAllowed = doc["cxAllowed"] | false; s.cxAllowed = doc["cxAllowed"] | false;
// Fehlt bei S3-FW < 5.3.5 -> auf cxEnabled zurueckfallen, sonst waere der Schalter tot
s.cxArmable = doc["cxArmable"] | (doc["cxEnabled"] | false);
s.cxResting = doc["cxResting"] | false; s.cxResting = doc["cxResting"] | false;
s.cxMaxTemp = doc["cxMaxTemp"] | s.cxMaxTemp; s.cxMaxTemp = doc["cxMaxTemp"] | s.cxMaxTemp;
s.cxTarget = doc["cxTarget"] | s.cxTarget; s.cxTarget = doc["cxTarget"] | s.cxTarget;
+31 -3
View File
@@ -38,6 +38,7 @@ static lv_timer_t* g_toastTimer = nullptr;
static lv_obj_t* g_standbyScreen = nullptr; // Vollbild-Overlay im Standby (Uhr) static lv_obj_t* g_standbyScreen = nullptr; // Vollbild-Overlay im Standby (Uhr)
static lv_obj_t* g_standbyClock = nullptr; static lv_obj_t* g_standbyClock = nullptr;
static lv_obj_t* g_wakeConfirm = nullptr; // Bestaetigungsdialog "Aus dem Standby aufwecken?" static lv_obj_t* g_wakeConfirm = nullptr; // Bestaetigungsdialog "Aus dem Standby aufwecken?"
static lv_obj_t* g_wakeColdBtn = nullptr; // "Aufwecken mit Cold Extraction" (nur wenn freigeschaltet)
static bool g_wakeDialogOpen = false; // Dialog sichtbar -> Display hell schalten (lesbar) static bool g_wakeDialogOpen = false; // Dialog sichtbar -> Display hell schalten (lesbar)
static bool g_standbyShown = false; // Standby-Uhr-Overlay zuletzt als sichtbar geschaltet (fuer Ein-/Ausblend-Uebergang) static bool g_standbyShown = false; // Standby-Uhr-Overlay zuletzt als sichtbar geschaltet (fuer Ein-/Ausblend-Uebergang)
@@ -572,6 +573,17 @@ static void wake_yes_cb(lv_event_t*) { // Aufwecken -> hell lassen, S3 bee
// das Display in der Uebergangsphase kurz auf Standby-Helligkeit abdunkeln. // das Display in der Uebergangsphase kurz auf Standby-Helligkeit abdunkeln.
if (g_client) { g_client->sendAction("deactivateStandby"); showToast("Maschine wird aufgeweckt...", false); } if (g_client) { g_client->sendAction("deactivateStandby"); showToast("Maschine wird aufgeweckt...", false); }
} }
// Aufwecken direkt in die kalte Extraktion. Reihenfolge ist wichtig: erst den Modus scharf
// stellen, dann den Standby beenden - sonst heizt der Wasserkreis in der Zwischenzeit an,
// und genau das soll die Funktion ja verhindern.
static void wake_cold_cb(lv_event_t*) {
wake_confirm_hide();
if (g_client) {
g_client->sendAction("startColdExtraction");
g_client->sendAction("deactivateStandby");
showToast("Aufwecken mit Cold Extraction...", false);
}
}
bool ui_wake_dialog_open() { return g_wakeDialogOpen; } // fuer die Helligkeitssteuerung in onState bool ui_wake_dialog_open() { return g_wakeDialogOpen; } // fuer die Helligkeitssteuerung in onState
// ===================================================================================== // =====================================================================================
@@ -3224,6 +3236,13 @@ void ui_init(ProtocolClient* client) {
lv_obj_t* wakeYes = th_accent_button(wakeBtns, "Aufwecken"); lv_obj_t* wakeYes = th_accent_button(wakeBtns, "Aufwecken");
lv_obj_set_width(wakeYes, 150); lv_obj_set_width(wakeYes, 150);
lv_obj_add_event_cb(wakeYes, wake_yes_cb, LV_EVENT_CLICKED, nullptr); lv_obj_add_event_cb(wakeYes, wake_yes_cb, LV_EVENT_CLICKED, nullptr);
// Zweite Zeile: direkt in die kalte Extraktion aufwecken. Ohne diesen Weg muesste man
// erst normal aufwecken (Maschine heizt an) und den Modus danach umschalten.
// Nur sichtbar, wenn die S3 die Funktion freigeschaltet meldet (siehe ui_update).
g_wakeColdBtn = th_button(wakeCard, "Aufwecken mit Cold Extraction", COL_CARD2, COL_ACCENT);
lv_obj_set_width(g_wakeColdBtn, LV_PCT(100));
lv_obj_add_event_cb(g_wakeColdBtn, wake_cold_cb, LV_EVENT_CLICKED, nullptr);
lv_obj_add_flag(g_wakeColdBtn, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(g_wakeConfirm, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(g_wakeConfirm, LV_OBJ_FLAG_HIDDEN);
// OTA-Overlay (Vollbild, waehrend Firmware-Update) // OTA-Overlay (Vollbild, waehrend Firmware-Update)
@@ -3449,6 +3468,12 @@ void ui_update(const MachineState& st) {
// Ohne nutzbare Waage laeuft die kalte Extraktion rein zeitgesteuert (Zielgewicht und // Ohne nutzbare Waage laeuft die kalte Extraktion rein zeitgesteuert (Zielgewicht und
// Stillstands-Erkennung greifen nicht) - das wird dauerhaft mit angezeigt. // Stillstands-Erkennung greifen nicht) - das wird dauerhaft mit angezeigt.
const bool cxScaleOk = st.scaleEnabled && st.scaleConnected; const bool cxScaleOk = st.scaleEnabled && st.scaleConnected;
// "Aufwecken mit Cold Extraction" nur anbieten, wenn die Funktion freigeschaltet und
// umschaltbar ist und der Modus nicht ohnehin schon laeuft.
if (g_wakeColdBtn) {
if (st.cxEnabled && st.cxArmable && !st.cxActive) lv_obj_remove_flag(g_wakeColdBtn, LV_OBJ_FLAG_HIDDEN);
else lv_obj_add_flag(g_wakeColdBtn, LV_OBJ_FLAG_HIDDEN);
}
if (st.cxNotice == 2) { if (st.cxNotice == 2) {
status_set(ST_COLDEX, true, LV_SYMBOL_WARNING " Cold Extraction beendet: kein Zulauf", status_set(ST_COLDEX, true, LV_SYMBOL_WARNING " Cold Extraction beendet: kein Zulauf",
COL_WARN, lv_color_hex(0x000000)); COL_WARN, lv_color_hex(0x000000));
@@ -3851,8 +3876,9 @@ void ui_update(const MachineState& st) {
if (lblColdExHint) lv_obj_add_flag(lblColdExHint, LV_OBJ_FLAG_HIDDEN); if (lblColdExHint) lv_obj_add_flag(lblColdExHint, LV_OBJ_FLAG_HIDDEN);
} }
sw_set(swColdEx, st.cxActive); sw_set(swColdEx, st.cxActive);
// Einschalten nur bei Freigabe; Ausschalten immer - ausser waehrend eines kalten Bezugs // Umschalten haengt an cxArmable, NICHT an cxAllowed: Der Modus muss auch im Standby
bool usable = st.cxEnabled && (st.cxActive ? !st.cxShot : st.cxAllowed); // und bei warmem Kessel scharf zu stellen sein. Waehrend eines kalten Bezugs gesperrt.
bool usable = st.cxEnabled && st.cxArmable && !(st.cxActive && st.cxShot);
if (usable) lv_obj_remove_state(swColdEx, LV_STATE_DISABLED); if (usable) lv_obj_remove_state(swColdEx, LV_STATE_DISABLED);
else lv_obj_add_state(swColdEx, LV_STATE_DISABLED); else lv_obj_add_state(swColdEx, LV_STATE_DISABLED);
if (lblColdExHint && st.cxEnabled) { if (lblColdExHint && st.cxEnabled) {
@@ -3860,7 +3886,9 @@ void ui_update(const MachineState& st) {
String hint; String hint;
if (st.cxActive) { if (st.cxActive) {
hint = "Wasserkreis heizt nicht (bleibt über Standby und Neustart aktiv). "; hint = "Wasserkreis heizt nicht (bleibt über Standby und Neustart aktiv). ";
if (st.cxAllowed) { if (st.standbyActive) {
hint += "Nach dem Aufwecken bleibt der Wasserkreis aus.";
} else if (st.cxAllowed) {
char b[96]; char b[96];
if (scaleOk) { if (scaleOk) {
snprintf(b, sizeof(b), "Bezug regulär starten - Ziel %.0f g, max %.0f s.", snprintf(b, sizeof(b), "Bezug regulär starten - Ziel %.0f g, max %.0f s.",