diff --git a/Changelog.txt b/Changelog.txt
index 9b4329f..c62e24c 100644
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -1,3 +1,20 @@
+Version 5.6.3:
+- Bug-Fix: Beim Aufwecken über das Touch-Display blieb die Auswahl „Espresso oder Cold Extraction"
+ manchmal aus, während sie über den Schalter kam. Ursache waren zwei unterschiedliche Bedingungen für
+ ein und dieselbe Sache: Das Display prüfte zum ANZEIGEN der Auswahl zusätzlich, ob der Modus gerade
+ scharfschaltbar ist (cxArmable) - zum UNTERDRÜCKEN der Rückfrage der S3 („wakeEspresso") aber nicht.
+ Fiel diese Bedingung weg, zeigte der Dialog nur noch „Aufwecken", schloss aber trotzdem das
+ Auswahlfenster der S3: Die Frage war auf beiden Seiten weg. Der Schalterweg hatte diese Lücke nie,
+ weil er allein an der S3 hängt. Behoben in der Display-Firmware 1.4.2.
+- Diagnose: /Cold-Extraction zeigt jetzt zusätzlich zum Live-Stand der Voraussetzungen auch das
+ LETZTE Aufwecken - über welchen Weg (Schalter/Timer/Web oder Display, dort mit getroffener Wahl)
+ und mit welchem Ergebnis (Fenster geöffnet, am Display bereits entschieden, Abfrage aus, kein
+ Display verbunden, Wartung/Reinigung/Tuning aktiv, nicht freigeschaltet). Hintergrund: Die
+ Live-Liste zeigt nur den jetzigen Stand - blieb die Frage aus, hatte sich die Lage bis zum
+ Nachsehen längst wieder geändert, und der Grund war nicht mehr zu ermitteln.
+- Die Entscheidung über das Auswahlfenster ist als Bedingungskette geschrieben, damit jeder Zweig
+ seinen Grund hinterlässt (vorher eine einzige verknüpfte Bedingung ohne Rückmeldung).
+
Version 5.6.2:
- Bug-Fix: Die Abfrage beim Aufwecken erschien nicht, sobald der Cold-Extraction-Modus noch aktiv war -
und da er persistent ist, war er das nach dem ersten kalten Bezug dauerhaft. Die Bedingung „Modus ist
diff --git a/Dual-PID.ino b/Dual-PID.ino
index 3b90f9b..41fa930 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.6.2";
+String version = "5.6.3";
String versionHersteller = "Thomas Müller";
String versionHerstellerMail =
"thomas@mueller.black";
@@ -804,6 +804,28 @@ unsigned long coldExtractionWakeDecisionMs = 0; // Zeitpunkt der letzten ex
const unsigned long COLDEX_WAKE_CHOICE_MS = 20000UL; // Bedenkzeit
const unsigned long COLDEX_WAKE_DECISION_GRACE_MS = 5000UL; // frisch entschieden -> kein Fenster
+// Diagnose des letzten Aufweckens: Bleibt die Abfrage aus, ist von aussen nicht erkennbar,
+// welche der Voraussetzungen gefehlt hat - die Live-Anzeige auf /Cold-Extraction zeigt nur
+// den JETZIGEN Stand, und der kann sich bis zum Nachsehen laengst wieder geaendert haben.
+// Deshalb wird das Ergebnis beim Aufwecken festgehalten. Reine Anzeige, keine Logik.
+#define COLDEX_WAKE_SRC_UNKNOWN 0 // Schalter, Timer, Web - alles ohne Display-Beteiligung
+#define COLDEX_WAKE_SRC_TOUCH 1 // Display: "Aufwecken" (ohne Auswahl im Dialog)
+#define COLDEX_WAKE_SRC_ESPRESSO 2 // Display: "Espresso" gewaehlt
+#define COLDEX_WAKE_SRC_COLD 3 // Display: "Cold Extraction" gewaehlt
+#define COLDEX_WAKE_RES_NONE 0 // seit dem Start noch nicht aufgeweckt
+#define COLDEX_WAKE_RES_OPENED 1 // Auswahlfenster geoeffnet
+#define COLDEX_WAKE_RES_DECIDED 2 // am Display bereits entschieden -> keine zweite Frage
+#define COLDEX_WAKE_RES_ASK_OFF 3 // Abfrage in den Einstellungen ausgeschaltet
+#define COLDEX_WAKE_RES_NO_DISP 4 // kein Touch-Display verbunden
+#define COLDEX_WAKE_RES_NOT_ARM 5 // Wartung / Reinigung / PID-Tuning aktiv
+#define COLDEX_WAKE_RES_DISABLED 6 // Funktion nicht freigeschaltet
+uint8_t coldExtractionWakeSource = COLDEX_WAKE_SRC_UNKNOWN; // Quelle des laufenden Aufweckens
+unsigned long coldExtractionWakeSourceMs = 0; // ... wann gemeldet (verfaellt)
+const unsigned long COLDEX_WAKE_SRC_VALID_MS = 3000UL; // danach gilt sie als veraltet
+uint8_t coldExtractionLastWakeSource = COLDEX_WAKE_SRC_UNKNOWN; // ... des letzten, fuer die Anzeige
+uint8_t coldExtractionLastWakeResult = COLDEX_WAKE_RES_NONE;
+unsigned long coldExtractionLastWakeMs = 0;
+
const bool defaultColdExtractionEnabled = false;
const float defaultColdExtractionMaxTempC = 30.0f;
const float defaultColdExtractionPreInfusionSeconds = 10.0f;
@@ -5452,12 +5474,34 @@ void loop() {
// die Abfrage muss in BEIDE Richtungen gehen - auch von "kalt" zurueck auf Espresso.
// Sonst bliebe man nach dem ersten kalten Bezug dauerhaft im kalten Modus haengen,
// ohne beim Aufwecken je wieder gefragt zu werden.
- if (coldExtractionEnabled && coldExtractionAskOnWake && touchUartClientActive &&
- coldExtractionArmable() &&
- (coldExtractionWakeDecisionMs == 0 ||
- (currentMillis - coldExtractionWakeDecisionMs) > COLDEX_WAKE_DECISION_GRACE_MS)) {
+ // Als Kette geschrieben, damit jeder Zweig seinen Grund hinterlaesst (Anzeige auf
+ // /Cold-Extraction). Ohne das bleibt ein ausgebliebenes Fenster nicht nachvollziehbar.
+ // Eine gemeldete Quelle gilt nur kurz: Wird der Modus im Standby ueber die
+ // Einstellseite scharfgestellt, ohne dass ein Aufwecken folgt, darf sie nicht
+ // spaeter am naechsten Aufwecken kleben.
+ coldExtractionLastWakeSource =
+ (coldExtractionWakeSourceMs != 0 &&
+ (currentMillis - coldExtractionWakeSourceMs) <= COLDEX_WAKE_SRC_VALID_MS)
+ ? coldExtractionWakeSource : COLDEX_WAKE_SRC_UNKNOWN;
+ coldExtractionWakeSource = COLDEX_WAKE_SRC_UNKNOWN;
+ coldExtractionWakeSourceMs = 0;
+ coldExtractionLastWakeMs = currentMillis;
+ if (!coldExtractionEnabled) {
+ coldExtractionLastWakeResult = COLDEX_WAKE_RES_DISABLED;
+ } else if (!coldExtractionAskOnWake) {
+ coldExtractionLastWakeResult = COLDEX_WAKE_RES_ASK_OFF;
+ } else if (!touchUartClientActive) {
+ coldExtractionLastWakeResult = COLDEX_WAKE_RES_NO_DISP;
+ } else if (!coldExtractionArmable()) {
+ coldExtractionLastWakeResult = COLDEX_WAKE_RES_NOT_ARM;
+ } else if (coldExtractionWakeDecisionMs != 0 &&
+ (currentMillis - coldExtractionWakeDecisionMs) <= COLDEX_WAKE_DECISION_GRACE_MS) {
+ // Am Display wurde gerade gewaehlt - eine zweite Frage waere nur laestig.
+ coldExtractionLastWakeResult = COLDEX_WAKE_RES_DECIDED;
+ } else {
coldExtractionWakeChoicePending = true;
coldExtractionWakeChoiceUntilMs = currentMillis + COLDEX_WAKE_CHOICE_MS;
+ coldExtractionLastWakeResult = COLDEX_WAKE_RES_OPENED;
}
}
// Gleiche Auswahl nach einem Kaltstart: Wer die Maschine am Netzschalter einschaltet,
@@ -5469,6 +5513,9 @@ void loop() {
coldExtractionBootChoiceDone = true;
coldExtractionWakeChoicePending = true;
coldExtractionWakeChoiceUntilMs = currentMillis + COLDEX_WAKE_CHOICE_MS;
+ coldExtractionLastWakeSource = COLDEX_WAKE_SRC_UNKNOWN;
+ coldExtractionLastWakeResult = COLDEX_WAKE_RES_OPENED;
+ coldExtractionLastWakeMs = currentMillis;
} else if (!coldExtractionBootChoiceDone && touchUartClientActive && standbyModeActive) {
// Start in den Standby hinein: Der Standby-Uebergang uebernimmt die Abfrage.
coldExtractionBootChoiceDone = true;
@@ -7868,6 +7915,33 @@ void handleColdExtraction(AsyncWebServerRequest *request) {
long remainSec = (remainMs > 0) ? ((remainMs + 999) / 1000) : 0;
diag += "Auswahlfenster ist gerade offen (noch " + String(remainSec) + " s).
";
}
+ // Letztes Aufwecken: Die Liste darueber zeigt nur den jetzigen Stand. Blieb die Frage
+ // aus, ist der Grund von damals hier festgehalten - sonst muesste man ihn raten.
+ if (coldExtractionLastWakeResult != COLDEX_WAKE_RES_NONE) {
+ String src;
+ switch (coldExtractionLastWakeSource) {
+ case COLDEX_WAKE_SRC_TOUCH: src = "Display (Aufwecken)"; break;
+ case COLDEX_WAKE_SRC_ESPRESSO: src = "Display (Espresso gewählt)"; break;
+ case COLDEX_WAKE_SRC_COLD: src = "Display (Cold Extraction gewählt)"; break;
+ default: src = "Schalter / Timer / Web"; break;
+ }
+ String res;
+ bool resOk = false;
+ switch (coldExtractionLastWakeResult) {
+ case COLDEX_WAKE_RES_OPENED: res = "Auswahlfenster geöffnet"; resOk = true; break;
+ case COLDEX_WAKE_RES_DECIDED: res = "am Display bereits entschieden"; resOk = true; break;
+ case COLDEX_WAKE_RES_ASK_OFF: res = "Abfrage ist ausgeschaltet"; break;
+ case COLDEX_WAKE_RES_NO_DISP: res = "kein Touch-Display verbunden"; break;
+ case COLDEX_WAKE_RES_NOT_ARM: res = "Wartung / Reinigung / PID-Tuning aktiv"; break;
+ case COLDEX_WAKE_RES_DISABLED: res = "Funktion nicht freigeschaltet"; break;
+ default: res = "unbekannt"; break;
+ }
+ unsigned long agoSec = (millis() - coldExtractionLastWakeMs) / 1000UL;
+ diag += "Letztes Aufwecken vor " + String(agoSec) + " s über " + src +
+ ": " + res + ".
";
+ }
diag += "";
cxFieldsHtml.replace("{CX_ASK_DIAG}", diag);
}
@@ -17442,6 +17516,23 @@ static bool touchUartHandleCommandLine(const String& line) {
String message = "";
bool settingsChanged = false;
bool pidNeedsUpdate = false;
+ // Herkunft des Aufweckens fuer die Diagnose festhalten - nur hier ist bekannt, dass
+ // die Aktion vom Touch-Display kommt und nicht aus der Web-UI. "Cold Extraction"
+ // schickt erst den Modus und dann das Aufwecken, deshalb darf das nachfolgende
+ // deactivateStandby die schon gesetzte Quelle nicht ueberschreiben.
+ if (standbyModeActive) {
+ if (action == "wakeEspresso") {
+ coldExtractionWakeSource = COLDEX_WAKE_SRC_ESPRESSO;
+ coldExtractionWakeSourceMs = millis();
+ } else if (action == "startColdExtraction") {
+ coldExtractionWakeSource = COLDEX_WAKE_SRC_COLD;
+ coldExtractionWakeSourceMs = millis();
+ } else if (action == "deactivateStandby" &&
+ coldExtractionWakeSource == COLDEX_WAKE_SRC_UNKNOWN) {
+ coldExtractionWakeSource = COLDEX_WAKE_SRC_TOUCH;
+ coldExtractionWakeSourceMs = millis();
+ }
+ }
executeDashboardAction(action, value, success, message, settingsChanged, pidNeedsUpdate);
touchUartSendAck(id, success, message);
touchUartSendState();
diff --git a/JC_Display_Firmware/Changelog.txt b/JC_Display_Firmware/Changelog.txt
index 1b764f6..c31f595 100644
--- a/JC_Display_Firmware/Changelog.txt
+++ b/JC_Display_Firmware/Changelog.txt
@@ -1,3 +1,14 @@
+Version 1.4.2:
+- Bug-Fix: Beim Aufwecken über Antippen erschien die Auswahl „Espresso oder Cold Extraction"
+ manchmal nicht. Der Dialog entschied nach einer anderen Bedingung, ob er die Auswahl ANZEIGT
+ (verlangte zusätzlich cxArmable), als er beim Absenden benutzte, um die Rückfrage der S3 zu
+ UNTERDRÜCKEN. In der Lücke dazwischen zeigte er nur „Aufwecken", sendete aber „wakeEspresso" -
+ und damit war die Frage auch auf der S3 erledigt. Beide Stellen benutzen jetzt dieselbe Funktion
+ wake_choice_offered().
+- Hat der Dialog die Auswahl nicht angeboten, sendet er wieder „deactivateStandby". Dann öffnet die
+ S3 ihr eigenes Auswahlfenster, und die Frage kommt trotzdem - derselbe Weg wie beim Aufwecken über
+ den Schalter. Die Auswahl kann damit nicht mehr ersatzlos entfallen.
+
Version 1.4.1:
- Passend zu S3 5.6.2: Die Auswahl beim Aufwecken erscheint auch dann, wenn der Cold-Extraction-Modus
bereits aktiv ist - sie geht in beide Richtungen. „Espresso" schaltet den Modus dann ab.
diff --git a/JC_Display_Firmware/config.h b/JC_Display_Firmware/config.h
index 82a6110..4c62eb7 100644
--- a/JC_Display_Firmware/config.h
+++ b/JC_Display_Firmware/config.h
@@ -65,7 +65,7 @@
// -------------------------------------------------------------------------------------
// Firmware
// -------------------------------------------------------------------------------------
-#define DISPLAY_FW_VERSION "1.4.1" // Firmware-Stand der P4-Display-Steuerung (Info-Seite)
+#define DISPLAY_FW_VERSION "1.4.2" // Firmware-Stand der P4-Display-Steuerung (Info-Seite)
// -------------------------------------------------------------------------------------
// Protokoll
diff --git a/JC_Display_Firmware/ui.cpp b/JC_Display_Firmware/ui.cpp
index 9861dd9..be43ad5 100644
--- a/JC_Display_Firmware/ui.cpp
+++ b/JC_Display_Firmware/ui.cpp
@@ -598,6 +598,16 @@ static void wake_confirm_show() {
lv_obj_move_foreground(g_wakeConfirm);
}
static void wake_confirm_hide() { if (g_wakeConfirm) lv_obj_add_flag(g_wakeConfirm, LV_OBJ_FLAG_HIDDEN); }
+// Einzige Quelle der Wahrheit dafuer, ob der Aufweck-Dialog die Auswahl "Espresso oder Cold
+// Extraction" wirklich anbietet. Wird an ZWEI Stellen gebraucht: beim Aufbau des Dialogs und
+// beim Absenden. Standen dort verschiedene Bedingungen, konnte der Dialog nur "Aufwecken"
+// zeigen, aber trotzdem "wakeEspresso" senden - und damit das Auswahlfenster der S3
+// schliessen. Dann war die Frage auf beiden Seiten weg (der Schalterweg hat diese Luecke
+// nicht, weil er allein am S3 haengt).
+static bool wake_choice_offered() {
+ return g_state.cxWakeChoice ||
+ (g_state.cxEnabled && g_state.cxArmable && g_state.cxAskOnWake);
+}
static void standby_screen_cb(lv_event_t*) { wake_confirm_show(); } // Tipp auf Standby-Uhr -> nachfragen
static void wake_no_cb(lv_event_t*) { // Abbrechen -> zurueck in die Standby-Helligkeit
wake_confirm_hide();
@@ -609,10 +619,11 @@ static void wake_yes_cb(lv_event_t*) { // Aufwecken -> hell lassen, S3 bee
// 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) 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");
+ // "wakeEspresso" beendet den Standby UND schliesst das Auswahlfenster der S3 - das ist
+ // nur dann richtig, wenn dieser Dialog die Auswahl gerade wirklich angeboten hat. Sonst
+ // "deactivateStandby": dann oeffnet die S3 ihr eigenes Auswahlfenster, und die Frage
+ // kommt trotzdem (gleicher Weg wie beim Aufwecken ueber den Schalter).
+ g_client->sendAction(wake_choice_offered() ? "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
@@ -3606,8 +3617,7 @@ void ui_update(const MachineState& st) {
// Kein "!st.cxActive": Der Modus ist persistent, die Auswahl muss in beide
// Richtungen gehen - sonst haengt man nach dem ersten kalten Bezug dauerhaft fest.
const bool choiceOpen = st.cxWakeChoice;
- const bool askMode = choiceOpen ||
- (st.cxEnabled && st.cxArmable && st.cxAskOnWake);
+ const bool askMode = wake_choice_offered(); // g_state == st (oben zugewiesen)
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);