diff --git a/Changelog.txt b/Changelog.txt index ab956b9..2708f20 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,3 +1,20 @@ +Version 5.6.0: +- Die Abfrage „Espresso oder Cold Extraction" kommt jetzt bei JEDEM Weg aus dem Standby, also auch beim + Aufwecken über den Schalter. Bisher war sie eine reine Vorab-Abfrage des Touch-Displays und erschien nur + beim Antippen des Standby-Bildschirms - beim Schalter ist der Standby aber schon beendet, bevor das + Display überhaupt fragen könnte. Dieser Weg war damit nie abgedeckt. +- Dafür hält die Steuerung nach dem Aufwecken ein Auswahlfenster offen (20 s): Der Wasserkreis heizt in + dieser Zeit nicht, das Display zeigt die Auswahl mit Countdown. Ohne Entscheidung wird anschließend + normal geheizt - es geht also nichts verloren, wenn niemand hinsieht. +- Das Fenster öffnet nur mit angeschlossenem Touch-Display (ohne Display könnte niemand wählen), bei + freigeschalteter Funktion und aktivierter Einstellung „Abfrage beim Aufwecken", und nur wenn der + Cold-Extraction-Modus nicht ohnehin schon aktiv ist. Es schließt sofort bei einer Entscheidung, bei + Bezug/Spülen oder erneutem Standby. +- Neue Aktion „wakeEspresso": beendet den Standby (falls noch aktiv) und schließt das Auswahlfenster. + Das Display sendet sie statt „deactivateStandby", sobald die Auswahl relevant ist - sonst würde nach + dem Aufwecken über den Dialog gleich nochmal gefragt. +- Neue State-Felder „cxWakeChoice" (Fenster offen) und „cxWakeChoiceSec" (Restsekunden). + Version 5.5.0: - Neu: Beim Aufwecken aus dem Standby fragt das Touch-Display, ob normal geheizt (Espresso) oder kalt extrahiert werden soll. Damit lässt sich das bei jedem Aufwecken neu entscheiden, ohne vorher die diff --git a/Doku/JC-Display_UART-Protokoll.md b/Doku/JC-Display_UART-Protokoll.md index b30758b..53c633f 100644 --- a/Doku/JC-Display_UART-Protokoll.md +++ b/Doku/JC-Display_UART-Protokoll.md @@ -322,6 +322,7 @@ Alle gültigen `action`-Strings aus `executeDashboardAction()` (Stand v4.8.1, Ze | `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 @@ -407,6 +408,8 @@ Aus `getTouchUartStatus()`. Für sprach-unabhängige UI-Logik/Icons auf dem P4 v | `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) | diff --git a/Dual-PID.ino b/Dual-PID.ino index 2bbd5bb..33ebfcc 100644 --- a/Dual-PID.ino +++ b/Dual-PID.ino @@ -369,7 +369,7 @@ Adafruit_SH1106G display = Adafruit_SH1106G(128, 64, &Wire); * Firmware-Informationen ************************************************************************************/ -String version = "5.5.0"; +String version = "5.6.0"; String versionHersteller = "Thomas Müller"; String versionHerstellerMail = "thomas@mueller.black"; @@ -794,6 +794,15 @@ float coldExtractionPumpRestSec = 20.0f; // Dauer der Zwangspause // extrahiert werden soll. Nur wirksam bei angeschlossenem UART-Display und freigeschalteter // Funktion; abschaltbar, wenn man den Zwischenschritt nicht mehr will. bool coldExtractionAskOnWake = true; +// Auswahlfenster nach dem Aufwecken: Der Dialog am Display ist eine Vorab-Abfrage und kann +// den Schalter-Weg nicht abdecken - dort ist der Standby schon beendet, bevor das Display +// fragen koennte. Deshalb haelt die Steuerung den Wasserkreis nach dem Aufwecken kurz aus +// und wartet auf die Entscheidung; laeuft das Fenster ab, wird normal geheizt. +bool coldExtractionWakeChoicePending = false; +unsigned long coldExtractionWakeChoiceUntilMs = 0; +unsigned long coldExtractionWakeDecisionMs = 0; // Zeitpunkt der letzten expliziten Wahl +const unsigned long COLDEX_WAKE_CHOICE_MS = 20000UL; // Bedenkzeit +const unsigned long COLDEX_WAKE_DECISION_GRACE_MS = 5000UL; // frisch entschieden -> kein Fenster const bool defaultColdExtractionEnabled = false; const float defaultColdExtractionMaxTempC = 30.0f; @@ -5436,6 +5445,24 @@ void loop() { lastShotTime = millis(); resetOffsetCompensationReferences(); applySteamHeatDisabledOnStartupWakeIfConfigured(); + // Auswahlfenster oeffnen: gilt fuer JEDEN Weg aus dem Standby (Schalter, Touch, Web, + // Timer). Nur sinnvoll mit angeschlossenem Display - ohne das koennte niemand waehlen, + // und die Maschine wuerde nur unnoetig spaeter heizen. + if (coldExtractionEnabled && coldExtractionAskOnWake && touchUartClientActive && + !coldExtractionModeActive && coldExtractionArmable() && + (coldExtractionWakeDecisionMs == 0 || + (currentMillis - coldExtractionWakeDecisionMs) > COLDEX_WAKE_DECISION_GRACE_MS)) { + coldExtractionWakeChoicePending = true; + coldExtractionWakeChoiceUntilMs = currentMillis + COLDEX_WAKE_CHOICE_MS; + } + } + // Auswahlfenster beenden: Zeit abgelaufen, Standby, oder es wird ohnehin schon bezogen + if (coldExtractionWakeChoicePending) { + if (standbyModeActive || coldExtractionModeActive || shotActive || shotSoftwareActive || + flushActive || !coldExtractionEnabled || + (long)(currentMillis - coldExtractionWakeChoiceUntilMs) >= 0) { + coldExtractionWakeChoicePending = false; + } } if (standbyModeActive != lastStandbyModeActive) { syncFullyKioskWithStandby(standbyModeActive); @@ -5467,7 +5494,7 @@ void loop() { // nach Software-Shots, Einstellungsänderungen mid-shot etc. immer konsistent ist. // Bei Cold Extraction zusaetzlich einfrieren: der Wasserkreis heizt minutenlang nicht, // ohne Freeze wuerde der I-Anteil aufladen und nach dem Modus stark ueberschwingen. - setPidWasserIntegralFrozen(coldExtractionModeActive || + setPidWasserIntegralFrozen(coldExtractionModeActive || coldExtractionWakeChoicePending || (ffIFreezeWasser && (shotActive || shotSoftwareActive) && !wartungsModusAktiv)); // --- Angepasste Sicherheitsueberpruefung Wasser --- @@ -5488,12 +5515,12 @@ void loop() { } else { if (wasserSensorError) Serial.println("INFO: Wassertemperatursensor wieder OK."); wasserSensorError = false; if (wasserSafetyShutdown) Serial.println("INFO: Wassertemperatur wieder im sicheren Bereich."); wasserSafetyShutdown = false; - bool activateBoost = boostWasserActive && !wartungsModusAktiv && !ecoModeAktiv && !autoTuneWasserActive && !coldExtractionModeActive && (InputWasser < (SetpointWasser * 0.95)); + bool activateBoost = boostWasserActive && !wartungsModusAktiv && !ecoModeAktiv && !autoTuneWasserActive && !coldExtractionModeActive && !coldExtractionWakeChoicePending && (InputWasser < (SetpointWasser * 0.95)); if (activateBoost) { OutputWasser = windowSizeWasser; } else if (!autoTuneWasserActive) { pidWasser.Compute(); } // Cold Extraction: Stellgroesse hart auf 0 - der Wasserkreis heizt nicht. Die PID // laeuft weiter (D-Anteil/lastInput bleiben aktuell), nur der Output wird verworfen, // damit auch die dutyW-Telemetrie fuer das Display die Wahrheit zeigt. - if (coldExtractionModeActive) { OutputWasser = 0; } + if (coldExtractionModeActive || coldExtractionWakeChoicePending) { OutputWasser = 0; } } yield(); @@ -5570,7 +5597,7 @@ void loop() { recordResetCheckpoint(RESET_CP_LOOP_SSR); unsigned long now = millis(); // Wasser SSR - if (wasserSensorError || wasserSafetyShutdown || standbyModeActive || displayOtaActive || coldExtractionModeActive) { digitalWrite(SSR_WASSER_PIN, LOW); } else { + if (wasserSensorError || wasserSafetyShutdown || standbyModeActive || displayOtaActive || coldExtractionModeActive || coldExtractionWakeChoicePending) { digitalWrite(SSR_WASSER_PIN, LOW); } else { if (now - windowStartTimeWasser > windowSizeWasser) { windowStartTimeWasser = now; } unsigned long effOutWasser = effectiveOutputWasserMs(); // PID-Output + Feed-Forward-Boost bei Bezug bool pidWantsHeat = (effOutWasser > 0) && (now < (windowStartTimeWasser + effOutWasser)); @@ -15658,6 +15685,29 @@ bool executeDashboardAction(const String& action, const String& value, success = true; } } + // Gegenstueck zu startColdExtraction beim Aufwecken: "normal heizen" bewusst waehlen. + // Beendet den Standby, falls noch aktiv, und schliesst das Auswahlfenster - sonst + // wuerde nach dem Aufwecken noch einmal gefragt. + else if (action == "wakeEspresso") { + coldExtractionWakeDecisionMs = millis(); + coldExtractionWakeChoicePending = false; + if (standbyModeActive) { + standbyModeActive = false; + startupTime = millis(); + ecoForcedActive = false; + steamDelayOverridden = false; + EEPROM.get(EEPROM_ADDR_SETPOINT_WASSER, SetpointWasser); + EEPROM.get(EEPROM_ADDR_SETPOINT_DAMPF, SetpointDampf); + ecoModeAktiv = false; + ecoModeActivatedTime = 0; + lastShotTime = millis(); + applyLightOutput(); + message = "Espresso: Standby aufgehoben, Kessel heizt auf."; + } else { + message = "Espresso: Kessel heizt auf."; + } + success = true; + } else if (action == "activateStandby") { if (standbyModeActive) { message = "Standby ist bereits aktiv."; @@ -15787,6 +15837,9 @@ bool executeDashboardAction(const String& action, const String& value, } else { target = !coldExtractionModeActive; } + // Jede bewusste Wahl schliesst das Auswahlfenster nach dem Aufwecken + coldExtractionWakeDecisionMs = millis(); + coldExtractionWakeChoicePending = false; success = setColdExtractionMode(target, message); if (!success) { coldExtractionBlockMessage = message; @@ -16289,6 +16342,9 @@ void buildDashboardJson(char *buffer, size_t bufferSize) { } } statusKey = "brewing"; + } else if (coldExtractionWakeChoicePending) { + statusText = "Auswahl: Espresso oder Cold Extraction?"; + statusKey = "coldextraction"; } else if (coldExtractionModeActive) { statusText = coldExtractionScaleUsable() ? "Cold Extraction bereit (Wasserkreis heizt nicht)" @@ -16540,6 +16596,9 @@ static void getTouchUartStatus(String& statusText, String& statusKey) { statusText = "Bezug aktiv..."; } statusKey = "brewing"; + } else if (coldExtractionWakeChoicePending) { + statusText = "Auswahl: Espresso oder Cold Extraction?"; + statusKey = "coldextraction"; } else if (coldExtractionModeActive) { statusText = coldExtractionScaleUsable() ? "Cold Extraction bereit" : "Cold Extraction bereit (Zeitsteuerung)"; @@ -16741,6 +16800,16 @@ static void buildTouchUartStateJson(char *buffer, size_t bufferSize) { 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, "\"cxAskOnWake\":%s,", coldExtractionAskOnWake ? "true" : "false"); + // Offenes Auswahlfenster nach dem Aufwecken (+ Restsekunden fuer den Countdown am Display) + offset += snprintf(buffer + offset, bufferSize - offset, "\"cxWakeChoice\":%s,", coldExtractionWakeChoicePending ? "true" : "false"); + { + long cxWakeSec = 0; + if (coldExtractionWakeChoicePending) { + long remainMs = (long)(coldExtractionWakeChoiceUntilMs - millis()); + cxWakeSec = (remainMs > 0) ? (long)((remainMs + 999) / 1000) : 0; + } + offset += snprintf(buffer + offset, bufferSize - offset, "\"cxWakeChoiceSec\":%ld,", cxWakeSec); + } 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(coldExtractionPreInfusionSeconds, 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"cxPreInf\":%s,", floatBuf); diff --git a/JC_Display_Firmware/Changelog.txt b/JC_Display_Firmware/Changelog.txt index f6102bd..c379f6c 100644 --- a/JC_Display_Firmware/Changelog.txt +++ b/JC_Display_Firmware/Changelog.txt @@ -1,3 +1,14 @@ +Version 1.4.0: +- Die Auswahl „Espresso oder Cold Extraction" erscheint jetzt auch beim Aufwecken über den Schalter + (braucht S3-Firmware ab 5.6.0). Meldet die S3 ein offenes Auswahlfenster (cxWakeChoice), blendet das + Display den Dialog von sich aus ein - ohne Antippen - und zeigt die verbleibende Bedenkzeit als + Countdown. „Abbrechen" entfällt in diesem Fall, weil es nichts abzubrechen gibt: Läuft die Zeit ab, + heizt die Maschine normal auf. +- Der Espresso-Knopf sendet „wakeEspresso" statt „deactivateStandby", sobald die Auswahl relevant ist. + Damit wird die Entscheidung auf der S3 vermerkt und nicht direkt danach erneut gefragt. +- Der Cold-Extraction-Knopf beendet den Standby nur noch, wenn er überhaupt aktiv ist - im Auswahlfenster + nach dem Aufwecken schaltet er nur den Modus. + Version 1.3.0: - Der Aufweck-Dialog wird zur Auswahl: „Womit aufwecken?" mit den Knöpfen „Espresso" (heizt normal auf) und „Cold Extraction (Wasser bleibt kalt)". Braucht S3-Firmware ab 5.5.0. diff --git a/JC_Display_Firmware/config.h b/JC_Display_Firmware/config.h index de2946c..1cc578f 100644 --- a/JC_Display_Firmware/config.h +++ b/JC_Display_Firmware/config.h @@ -65,7 +65,7 @@ // ------------------------------------------------------------------------------------- // Firmware // ------------------------------------------------------------------------------------- -#define DISPLAY_FW_VERSION "1.3.0" // Firmware-Stand der P4-Display-Steuerung (Info-Seite) +#define DISPLAY_FW_VERSION "1.4.0" // Firmware-Stand der P4-Display-Steuerung (Info-Seite) // ------------------------------------------------------------------------------------- // Protokoll diff --git a/JC_Display_Firmware/machine_state.h b/JC_Display_Firmware/machine_state.h index 9dc0b0f..d3dac98 100644 --- a/JC_Display_Firmware/machine_state.h +++ b/JC_Display_Firmware/machine_state.h @@ -85,6 +85,8 @@ struct MachineState { float cxPumpMaxRun = 0; // Pumpenschutz: max. kumulierte Laufzeit in s float cxPumpRest = 0; // Pumpenschutz: Zwangspause in s bool cxAskOnWake = false; // Beim Aufwecken aus dem Standby fragen (seit S3 5.5.0) + bool cxWakeChoice = false; // Auswahlfenster nach dem Aufwecken offen (seit S3 5.6.0) + long cxWakeChoiceSec = 0; // Restsekunden der Bedenkzeit float cxMaxSecs = 0; // Sicherheits-Timeout des Bezugs in s // Sperrgrund: 0=frei, 1=nicht freigeschaltet, 2=Standby, 3=Wartung, 4=Reinigungsassistent, // 5=PID-Tuning, 6=Sensorfehler, 7=Wasserkessel zu warm diff --git a/JC_Display_Firmware/protocol_client.cpp b/JC_Display_Firmware/protocol_client.cpp index 8f6abd1..bd766a1 100644 --- a/JC_Display_Firmware/protocol_client.cpp +++ b/JC_Display_Firmware/protocol_client.cpp @@ -237,6 +237,8 @@ void ProtocolClient::applyState(const String& json) { s.cxPumpMaxRun = doc["cxPumpMaxRun"] | s.cxPumpMaxRun; s.cxPumpRest = doc["cxPumpRest"] | s.cxPumpRest; s.cxAskOnWake = doc["cxAskOnWake"] | false; + s.cxWakeChoice = doc["cxWakeChoice"] | false; + s.cxWakeChoiceSec = doc["cxWakeChoiceSec"] | (long)0; s.cxMaxSecs = doc["cxMaxSecs"] | s.cxMaxSecs; s.cxBlock = doc["cxBlock"] | (uint8_t)0; s.cxNotice = doc["cxNotice"] | (uint8_t)0; diff --git a/JC_Display_Firmware/ui.cpp b/JC_Display_Firmware/ui.cpp index 0399df0..f06baad 100644 --- a/JC_Display_Firmware/ui.cpp +++ b/JC_Display_Firmware/ui.cpp @@ -41,6 +41,7 @@ static lv_obj_t* g_wakeConfirm = nullptr; // Bestaetigungsdialog "Aus dem Stan static lv_obj_t* g_wakeColdBtn = nullptr; // "Cold Extraction" im Aufweck-Dialog static lv_obj_t* g_wakeMsg = nullptr; // Text des Aufweck-Dialogs (wechselt bei Abfrage-Modus) static lv_obj_t* g_wakeYesLbl = nullptr; // Beschriftung des Bestaetigen-Knopfs +static lv_obj_t* g_wakeNoBtn = nullptr; // "Abbrechen" (entfaellt beim Auswahlfenster nach dem Aufwecken) 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) @@ -607,7 +608,12 @@ static void wake_yes_cb(lv_event_t*) { // Aufwecken -> hell lassen, S3 bee wake_confirm_hide(); // g_wakeDialogOpen bleibt true, bis der Standby wirklich endet (ui_update) - sonst wuerde // 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) return; + // "wakeEspresso" beendet den Standby UND schliesst das Auswahlfenster der S3. Ohne das + // wuerde nach dem Aufwecken noch einmal gefragt bzw. der Wasserkreis bliebe kurz aus. + const bool choiceRelevant = g_state.cxWakeChoice || (g_state.cxEnabled && g_state.cxAskOnWake); + g_client->sendAction(choiceRelevant ? "wakeEspresso" : "deactivateStandby"); + showToast(g_state.standbyActive ? "Maschine wird aufgeweckt..." : "Espresso: Kessel heizt auf.", 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, @@ -616,8 +622,8 @@ 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); + if (g_state.standbyActive) g_client->sendAction("deactivateStandby"); + showToast("Cold Extraction aktiviert.", false); } } bool ui_wake_dialog_open() { return g_wakeDialogOpen; } // fuer die Helligkeitssteuerung in onState @@ -3349,6 +3355,7 @@ void ui_init(ProtocolClient* client) { lv_obj_t* wakeNo = th_button(wakeBtns, "Abbrechen", COL_CARD2, COL_TEXT); lv_obj_set_width(wakeNo, 150); lv_obj_add_event_cb(wakeNo, wake_no_cb, LV_EVENT_CLICKED, nullptr); + g_wakeNoBtn = wakeNo; lv_obj_t* wakeYes = th_accent_button(wakeBtns, "Aufwecken"); lv_obj_set_width(wakeYes, 150); lv_obj_add_event_cb(wakeYes, wake_yes_cb, LV_EVENT_CLICKED, nullptr); @@ -3555,7 +3562,9 @@ void ui_update(const MachineState& st) { } // Aufweck-Ablauf: nach beendetem Standby Dialog schliessen und Helligkeits-Flag loeschen // (auch wenn der Dialog schon ausgeblendet ist, weil "Aufwecken" das Flag bewusst haelt). - if (!st.standbyActive) { + // Bei offenem Auswahlfenster (cxWakeChoice) bleibt der Dialog stehen, obwohl der Standby + // schon beendet ist - genau dafuer ist er da. + if (!st.standbyActive && !st.cxWakeChoice) { g_wakeDialogOpen = false; if (g_wakeConfirm && !lv_obj_has_flag(g_wakeConfirm, LV_OBJ_FLAG_HIDDEN)) lv_obj_add_flag(g_wakeConfirm, LV_OBJ_FLAG_HIDDEN); @@ -3591,19 +3600,45 @@ void ui_update(const MachineState& st) { // Abschaltbar ueber die Einstellung cxAskOnWake, damit man sich den Zwischenschritt // sparen kann, wenn gerade keine kalten Bezuege anstehen. { - const bool askMode = st.cxEnabled && st.cxArmable && !st.cxActive && st.cxAskOnWake; + // Zwei Faelle: Vorab-Abfrage beim Antippen im Standby (askMode) und das von der S3 + // geoeffnete Auswahlfenster NACH dem Aufwecken (choiceOpen) - letzteres deckt auch + // das Aufwecken per Schalter ab, wo es keine Vorab-Abfrage geben kann. + const bool choiceOpen = st.cxWakeChoice; + const bool askMode = choiceOpen || + (st.cxEnabled && st.cxArmable && !st.cxActive && st.cxAskOnWake); if (g_wakeColdBtn) { if (askMode) lv_obj_remove_flag(g_wakeColdBtn, LV_OBJ_FLAG_HIDDEN); else lv_obj_add_flag(g_wakeColdBtn, LV_OBJ_FLAG_HIDDEN); } + // Im Auswahlfenster gibt es nichts abzubrechen - laeuft die Zeit ab, wird geheizt. + if (g_wakeNoBtn) { + if (choiceOpen) lv_obj_add_flag(g_wakeNoBtn, LV_OBJ_FLAG_HIDDEN); + else lv_obj_remove_flag(g_wakeNoBtn, LV_OBJ_FLAG_HIDDEN); + } if (g_wakeMsg) { - label_set_if_changed(g_wakeMsg, askMode - ? "Die Maschine befindet sich im Standby.\nWomit aufwecken?" - : "Die Maschine befindet sich im Standby.\nAus dem Standby aufwecken?"); + if (choiceOpen) { + char b[128]; + snprintf(b, sizeof(b), + "Womit weiter?\nEspresso heizt auf, Cold Extraction bleibt kalt.\n" + "Ohne Auswahl wird in %ld s geheizt.", st.cxWakeChoiceSec); + label_set_if_changed(g_wakeMsg, b); + } else { + label_set_if_changed(g_wakeMsg, askMode + ? "Die Maschine befindet sich im Standby.\nWomit aufwecken?" + : "Die Maschine befindet sich im Standby.\nAus dem Standby aufwecken?"); + } } if (g_wakeYesLbl) { label_set_if_changed(g_wakeYesLbl, askMode ? "Espresso" : "Aufwecken"); } + // Auswahlfenster von der S3 -> Dialog von sich aus zeigen (kein Antippen noetig) + if (choiceOpen && g_wakeConfirm && lv_obj_has_flag(g_wakeConfirm, LV_OBJ_FLAG_HIDDEN)) { + wake_confirm_show(); + } else if (!choiceOpen && !st.standbyActive && g_wakeConfirm && + !lv_obj_has_flag(g_wakeConfirm, LV_OBJ_FLAG_HIDDEN)) { + wake_confirm_hide(); // Fenster abgelaufen oder entschieden + g_wakeDialogOpen = false; + } } if (st.cxNotice == 2) { status_set(ST_COLDEX, true, LV_SYMBOL_WARNING " Cold Extraction beendet: kein Zulauf",