Compare commits

...
Author SHA1 Message Date
raw-designsandClaude Opus 5 c697e65a01 feat(S3): Cold Extraction überlebt Standby und Neustart (5.3.1)
Der Modus wird im EEPROM (Adresse 977) gespeichert und beim Start wieder
übernommen, sofern die Funktion auf /Brew-Control freigeschaltet ist. Standby
beendet ihn nicht mehr, sondern verwirft nur die Laufzeitdaten.

Grund: Wer nur kalte Bezüge machen will, musste den Modus nach jedem Standby
neu einschalten. Bis dahin heizte die Maschine bereits auf und war für die
nächste kalte Extraktion zu warm — man musste auf das Abkühlen warten, also
genau die Situation, die der Modus vermeiden soll.

Der Modus endet nur noch durch explizites Ausschalten, durch Entzug der
Freischaltung oder durch einen Werksreset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 21:31:59 +02:00
thomas d7a1a8dee2 Merge pull request 'feat(S3): Cold Extraction (kalte Extraktion) mit Temperatur-Freigabe (5.3.0)' (#11) from feat/cold-extraction into main 2026-08-10 21:04:17 +02:00
3 changed files with 43 additions and 15 deletions
+10
View File
@@ -1,3 +1,13 @@
Version 5.3.1:
- Cold Extraction ist jetzt persistent: Der Modus wird im EEPROM gespeichert und überlebt sowohl den
Standby als auch einen Neustart. Zuvor wurde er beim Standby beendet und war nach dem Aufwachen aus -
die Maschine heizte dann erst auf, obwohl der nächste kalte Bezug einen kalten Kessel braucht, und man
musste auf das Abkühlen warten. Wer nur kalte Bezüge machen will, schaltet den Modus damit einmal ein
und wieder aus, wenn er fertig ist.
- Der Modus fällt nur weg, wenn auf /Brew-Control die Freischaltung entzogen wird oder ein Werksreset
läuft. Solange er aktiv ist, heizt der Wasserkreis nicht - Dashboard, OLED („(COLD)" in der Wasserzeile)
und Touch-Display zeigen das durchgehend an.
Version 5.3.0: Version 5.3.0:
- Neu: Cold Extraction (kalte Extraktion). Die Maschine hat keinen Bypass um den Wasserkessel, kaltes Wasser - Neu: Cold Extraction (kalte Extraktion). Die Maschine hat keinen Bypass um den Wasserkessel, kaltes Wasser
kommt also nur bei kaltem Kessel am Puck an. Die Funktion wird deshalb über eine Temperaturschwelle kommt also nur bei kaltem Kessel am Puck an. Die Funktion wird deshalb über eine Temperaturschwelle
+6 -3
View File
@@ -324,9 +324,12 @@ Alle gültigen `action`-Strings aus `executeDashboardAction()` (Stand v4.8.1, Ze
| `toggleColdExtraction` | — | Modus umschalten | | `toggleColdExtraction` | — | Modus umschalten |
Alle drei Aktionen werden abgewiesen (`ok:false` + `message`), solange die Bedingungen nicht Alle drei Aktionen werden abgewiesen (`ok:false` + `message`), solange die Bedingungen nicht
erfüllt sind — Hauptfall ist ein zu warmer Wasserkessel (`cxAllowed:false`). Der Modus wird erfüllt sind — Hauptfall ist ein zu warmer Wasserkessel (`cxAllowed:false`). Der Modus ist
**nicht** persistiert und ist nach jedem Neustart aus; Standby beendet ihn. Ein laufender **persistent** (EEPROM) und überlebt Standby und Neustart; er endet nur durch explizites
Bezug wird nie abgebrochen, `stopColdExtraction` schlägt dann fehl. 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
Benutzer, warum die Maschine nicht heizt. Ein laufender Bezug wird nie abgebrochen,
`stopColdExtraction` schlägt dann fehl.
Die Parameter der kalten Extraktion (Schwelle, Vorbenetzung, Pulsperiode, Pumpenleistung, Die Parameter der kalten Extraktion (Schwelle, Vorbenetzung, Pulsperiode, Pumpenleistung,
Zielgewicht, Timeout, Pumpenschutz) sind bewusst **nicht** über `saveBrew` erreichbar, sondern Zielgewicht, Timeout, Pumpenschutz) sind bewusst **nicht** über `saveBrew` erreichbar, sondern
+26 -11
View File
@@ -369,7 +369,7 @@ Adafruit_SH1106G display = Adafruit_SH1106G(128, 64, &Wire);
* Firmware-Informationen * Firmware-Informationen
************************************************************************************/ ************************************************************************************/
String version = "5.3.0"; String version = "5.3.1";
String versionHersteller = "Thomas M&uuml;ller"; String versionHersteller = "Thomas M&uuml;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>";
@@ -1036,7 +1036,8 @@ const int EEPROM_ADDR_COLDEX_TARGET_GRAMS = 961; // 4 Bytes (float) -> Ende
const int EEPROM_ADDR_COLDEX_MAX_SECONDS = 965; // 4 Bytes (float) -> Ende 968 const int EEPROM_ADDR_COLDEX_MAX_SECONDS = 965; // 4 Bytes (float) -> Ende 968
const int EEPROM_ADDR_COLDEX_PUMP_MAX_RUN = 969; // 4 Bytes (float) -> Ende 972 const int EEPROM_ADDR_COLDEX_PUMP_MAX_RUN = 969; // 4 Bytes (float) -> Ende 972
const int EEPROM_ADDR_COLDEX_PUMP_REST = 973; // 4 Bytes (float) -> Ende 976 const int EEPROM_ADDR_COLDEX_PUMP_REST = 973; // 4 Bytes (float) -> Ende 976
// Naechste freie Adresse: 977 (Innerhalb EEPROM_SIZE=1024) const int EEPROM_ADDR_COLDEX_MODE_ACTIVE = 977; // 1 Byte (uint8_t) -> Ende 977
// Naechste freie Adresse: 978 (Innerhalb EEPROM_SIZE=1024)
/************************************************************************************ /************************************************************************************
* Eco-Mode Variablen * Eco-Mode Variablen
@@ -3027,6 +3028,7 @@ void resetToDefaults() {
EEPROM.put(EEPROM_ADDR_COLDEX_MAX_SECONDS, defaultColdExtractionMaxSeconds); EEPROM.put(EEPROM_ADDR_COLDEX_MAX_SECONDS, defaultColdExtractionMaxSeconds);
EEPROM.put(EEPROM_ADDR_COLDEX_PUMP_MAX_RUN, defaultColdExtractionPumpMaxRunSec); EEPROM.put(EEPROM_ADDR_COLDEX_PUMP_MAX_RUN, defaultColdExtractionPumpMaxRunSec);
EEPROM.put(EEPROM_ADDR_COLDEX_PUMP_REST, defaultColdExtractionPumpRestSec); EEPROM.put(EEPROM_ADDR_COLDEX_PUMP_REST, defaultColdExtractionPumpRestSec);
EEPROM.put(EEPROM_ADDR_COLDEX_MODE_ACTIVE, (uint8_t)0); // Werksreset: Modus immer aus
// Piezo auf Default im EEPROM schreiben // Piezo auf Default im EEPROM schreiben
EEPROM.put(EEPROM_ADDR_PIEZO_ENABLED, defaultPiezoEnabled); EEPROM.put(EEPROM_ADDR_PIEZO_ENABLED, defaultPiezoEnabled);
@@ -3988,6 +3990,12 @@ void applyFlowGuardPumpControl(unsigned long currentShotTimeMillis, float netWei
* Cold Extraction (kalte Extraktion) * Cold Extraction (kalte Extraktion)
************************************************************************************/ ************************************************************************************/
// Modus-Zustand ins EEPROM schreiben (persistent ueber Standby und Neustart hinweg).
void persistColdExtractionMode() {
EEPROM.put(EEPROM_ADDR_COLDEX_MODE_ACTIVE, (uint8_t)(coldExtractionModeActive ? 1 : 0));
EEPROM.commit();
}
void resetColdExtractionRuntime() { void resetColdExtractionRuntime() {
coldExtractionShotActive = false; coldExtractionShotActive = false;
coldExtractionPumpResting = false; coldExtractionPumpResting = false;
@@ -4072,7 +4080,8 @@ bool setColdExtractionMode(bool enable, String& message) {
} }
coldExtractionModeActive = true; coldExtractionModeActive = true;
resetColdExtractionRuntime(); resetColdExtractionRuntime();
message = "Cold Extraction aktiviert - Wasserkreis heizt nicht."; persistColdExtractionMode();
message = "Cold Extraction aktiviert - Wasserkreis heizt nicht (bleibt auch nach Standby/Neustart aktiv).";
return true; return true;
} }
@@ -4082,6 +4091,7 @@ bool setColdExtractionMode(bool enable, String& message) {
} }
coldExtractionModeActive = false; coldExtractionModeActive = false;
resetColdExtractionRuntime(); resetColdExtractionRuntime();
persistColdExtractionMode();
message = "Cold Extraction beendet - Wasserkreis heizt wieder."; message = "Cold Extraction beendet - Wasserkreis heizt wieder.";
return true; return true;
} }
@@ -4615,9 +4625,13 @@ void setup() {
if (!isfinite(coldExtractionPumpRestSec) || coldExtractionPumpRestSec < COLDEX_PUMP_REST_SEC_MIN || coldExtractionPumpRestSec > COLDEX_PUMP_REST_SEC_MAX) { if (!isfinite(coldExtractionPumpRestSec) || coldExtractionPumpRestSec < COLDEX_PUMP_REST_SEC_MIN || coldExtractionPumpRestSec > COLDEX_PUMP_REST_SEC_MAX) {
coldExtractionPumpRestSec = defaultColdExtractionPumpRestSec; coldExtractionPumpRestSec = defaultColdExtractionPumpRestSec;
} }
// Der Modus selbst wird bewusst NICHT persistiert: nach einem Neustart startet die // Der Modus ist bewusst persistent: Wer nur kalte Bezuege machen will, soll die
// Maschine immer im normalen Heizbetrieb. // Maschine nicht nach jedem Standby/Neustart erst aufheizen lassen, um sie danach
coldExtractionModeActive = false; // wieder abkuehlen zu muessen. Nur bei entzogener Freischaltung faellt er weg.
uint8_t storedColdExtractionMode = 0;
EEPROM.get(EEPROM_ADDR_COLDEX_MODE_ACTIVE, storedColdExtractionMode);
if (storedColdExtractionMode > 1) { storedColdExtractionMode = 0; }
coldExtractionModeActive = (storedColdExtractionMode == 1) && coldExtractionEnabled;
coldExtractionShotActive = false; coldExtractionShotActive = false;
// Piezo-Status laden // Piezo-Status laden
@@ -5234,11 +5248,10 @@ void loop() {
if (wartungsModusAktiv) { if (wartungsModusAktiv) {
wartungsModusAktiv = false; wartungsModusAktiv = false;
} }
// Cold Extraction beim Wechsel in den Standby beenden (analog Eco/Wartung) // Cold Extraction bleibt beim Standby bewusst aktiv (anders als Eco/Wartung): Die
if (coldExtractionModeActive) { // Maschine soll nach dem Aufwachen nicht erst aufheizen, nur um fuer den naechsten
coldExtractionModeActive = false; // kalten Bezug wieder abkuehlen zu muessen. Nur die Laufzeitdaten werden verworfen.
resetColdExtractionRuntime(); resetColdExtractionRuntime();
}
coldExtractionBlockMessage = ""; coldExtractionBlockMessage = "";
} else if (lastStandbyModeActive) { } else if (lastStandbyModeActive) {
startupTime = currentMillis; startupTime = currentMillis;
@@ -7109,7 +7122,8 @@ static const char brewControlCX_Fields[] PROGMEM = R"rawliteral(
aus dem kalten Zustand heraus. Solange der Modus aktiv ist, heizt der Wasserkreis nicht - der Dampfkreis bleibt 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. 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 Laufzeit legt die Steuerung eine Zwangspause ein. Der Modus selbst wird auf dem Dashboard bzw. am Touch-Display
ein- und ausgeschaltet.)</small> ein- und ausgeschaltet und bleibt &uuml;ber Standby und Neustart hinweg aktiv, bis er dort wieder ausgeschaltet
wird - solange heizt der Wasserkreis nicht.)</small>
{CX_STATUS_MSG} {CX_STATUS_MSG}
<br> <br>
)rawliteral"; )rawliteral";
@@ -7461,6 +7475,7 @@ bool applyBrewControlUpdate(const BrewControlUpdate& update) {
if (!coldExtractionEnabled && coldExtractionModeActive && !shotActive && !shotSoftwareActive) { if (!coldExtractionEnabled && coldExtractionModeActive && !shotActive && !shotSoftwareActive) {
coldExtractionModeActive = false; // Freischaltung entzogen -> Modus beenden coldExtractionModeActive = false; // Freischaltung entzogen -> Modus beenden
resetColdExtractionRuntime(); resetColdExtractionRuntime();
EEPROM.put(EEPROM_ADDR_COLDEX_MODE_ACTIVE, (uint8_t)0); // Commit erfolgt am Ende der Funktion
} }
} }
if (update.hasCXMaxTemp) { if (update.hasCXMaxTemp) {