diff --git a/Changelog.txt b/Changelog.txt
index 7bd9a5e..913bcfa 100644
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -1,3 +1,15 @@
+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
diff --git a/Doku/JC-Display_UART-Protokoll.md b/Doku/JC-Display_UART-Protokoll.md
index f2f4a06..27dea7b 100644
--- a/Doku/JC-Display_UART-Protokoll.md
+++ b/Doku/JC-Display_UART-Protokoll.md
@@ -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 —
diff --git a/Dual-PID.ino b/Dual-PID.ino
index 1aee607..058d94d 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.3.3";
+String version = "5.3.4";
String versionHersteller = "Thomas Müller";
String versionHerstellerMail =
"thomas@mueller.black";
@@ -857,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;
@@ -4050,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 &&
@@ -4124,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;
}
@@ -7158,7 +7175,7 @@ static const char brewControlCX_Fields[] PROGMEM = R"rawliteral(
-
+
@@ -7316,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}", " " + cxStatus + "");
- } else {
- cxFieldsHtml.replace("{CX_STATUS_MSG}", " Kalte Extraktion derzeit möglich.");
+ String cxStatus;
+ // Waagen-Hinweis zuerst: er gilt unabhaengig von der Temperatur-Freigabe und
+ // erklaert, warum das Zielgewicht oben wirkungslos ist.
+ if (!coldExtractionScaleUsable()) {
+ cxStatus += " Keine Waage aktiv/verbunden: Zielgewicht und "
+ "Stillstands-Erkennung greifen nicht. Der Bezug endet erst nach der maximalen "
+ "Bezugsdauer - diesen Wert dann als Dosierung verstehen.";
}
+ String reason = coldExtractionBlockReason();
+ if (reason.length() > 0) {
+ cxStatus += " " + reason + "";
+ } else {
+ cxStatus += " Kalte Extraktion derzeit möglich.";
+ }
+ cxFieldsHtml.replace("{CX_STATUS_MSG}", cxStatus);
} else {
cxFieldsHtml.replace("{CX_STATUS_MSG}", "");
}
@@ -14996,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';
}
@@ -16041,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)";
@@ -16288,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";
diff --git a/JC_Display_Firmware/Changelog.txt b/JC_Display_Firmware/Changelog.txt
index 4509fbe..d7c4d93 100644
--- a/JC_Display_Firmware/Changelog.txt
+++ b/JC_Display_Firmware/Changelog.txt
@@ -1,3 +1,12 @@
+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
diff --git a/JC_Display_Firmware/config.h b/JC_Display_Firmware/config.h
index e1b5ffe..1ae7412 100644
--- a/JC_Display_Firmware/config.h
+++ b/JC_Display_Firmware/config.h
@@ -65,7 +65,7 @@
// -------------------------------------------------------------------------------------
// Firmware
// -------------------------------------------------------------------------------------
-#define DISPLAY_FW_VERSION "1.1.1" // Firmware-Stand der P4-Display-Steuerung (Info-Seite)
+#define DISPLAY_FW_VERSION "1.1.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 e80f410..7af9225 100644
--- a/JC_Display_Firmware/ui.cpp
+++ b/JC_Display_Firmware/ui.cpp
@@ -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[256]; // der Text ist ~205 Zeichen lang (das ° zaehlt als 2 Bytes)
+ 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 {