Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cf375a2b4 | ||
|
|
e33de56edc |
@@ -1,3 +1,17 @@
|
||||
Version 5.3.6:
|
||||
- Cold Extraction: Die Vorbenetzung läuft nicht mehr mit voller Pumpenleistung, sondern gepulst mit
|
||||
eigener, niedriger Leistung (neue Einstellung „Pumpenleistung Vorbenetzung", Standard 10 %).
|
||||
Messung an der Maschine: Nach 7 s Vorbenetzung kommen heiß 1-2 Tropfen, kalt bei voller Leistung
|
||||
rund 10 g dünne Brühe ohne Crema - etwa zehnfacher Durchfluss trotz dreifacher Viskosität.
|
||||
Ursache ist nicht die Viskosität, sondern das Kaffeebett: Kaltes Mehl quillt nicht, ist schlecht
|
||||
benetzbar, es wandert kein Feinanteil und es lösen sich kaum Feststoffe, die das Bett verdichten.
|
||||
Volle Leistung auf den trockenen kalten Puck legt deshalb sofort Kanäle an, die sich - anders als
|
||||
bei heißem Bezug - nie wieder schließen; der restliche Bezug läuft daran vorbei statt zu extrahieren.
|
||||
- Standard der Pumpenleistung im Hauptbezug von 30 % auf 20 % gesenkt (gleicher Grund: das kalte Bett
|
||||
ist erheblich durchlässiger als ein heißes). Bestehende Installationen behalten ihren Wert.
|
||||
- Der Erklärtext auf /Brew-Control benennt den Zusammenhang jetzt und weist darauf hin, dass die
|
||||
Vorbenetzung deutlich schwächer eingestellt sein sollte als der Hauptbezug.
|
||||
|
||||
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
|
||||
|
||||
+51
-17
@@ -369,7 +369,7 @@ Adafruit_SH1106G display = Adafruit_SH1106G(128, 64, &Wire);
|
||||
* Firmware-Informationen
|
||||
************************************************************************************/
|
||||
|
||||
String version = "5.3.5";
|
||||
String version = "5.3.6";
|
||||
String versionHersteller = "Thomas Müller";
|
||||
String versionHerstellerMail =
|
||||
"<a href='mailto:thomas@mueller.black' class='info-link'>thomas@mueller.black</a>";
|
||||
@@ -782,9 +782,10 @@ bool flowGuardPumpPulseActive = false;
|
||||
************************************************************************************/
|
||||
bool coldExtractionEnabled = false; // Funktion in der Web-UI freigeschaltet
|
||||
float coldExtractionMaxTempC = 30.0f; // Freigabeschwelle Wasserkessel
|
||||
float coldExtractionPreInfusionSeconds = 10.0f; // Vorbenetzung (Pumpe laeuft ungepulst)
|
||||
float coldExtractionPreInfusionSeconds = 10.0f; // Vorbenetzung (gepulst, eigene Leistung)
|
||||
float coldExtractionPreInfDutyPercent = 10.0f; // Pumpenleistung waehrend der Vorbenetzung
|
||||
uint16_t coldExtractionPulsePeriodMs = 2000; // Pulsperiode der Pumpe im Hauptbezug
|
||||
float coldExtractionDutyPercent = 30.0f; // Pumpen-Duty im Hauptbezug
|
||||
float coldExtractionDutyPercent = 20.0f; // Pumpen-Duty im Hauptbezug
|
||||
float coldExtractionTargetGrams = 60.0f; // Zielgewicht (ca. 1:3 auf 18 g)
|
||||
float coldExtractionMaxSeconds = 300.0f; // Sicherheits-Timeout des Bezugs
|
||||
float coldExtractionPumpMaxRunSec = 60.0f; // kumulierte Pumpenlaufzeit bis zur Zwangspause
|
||||
@@ -792,13 +793,17 @@ float coldExtractionPumpRestSec = 20.0f; // Dauer der Zwangspause
|
||||
|
||||
const bool defaultColdExtractionEnabled = false;
|
||||
const float defaultColdExtractionMaxTempC = 30.0f;
|
||||
// 10 s statt der urspruenglichen 30 s: Die Pumpe laeuft in dieser Phase ungepulst auf
|
||||
// voller Leistung. Heiss kommt der erste Tropfen nach ~7 s, kalt (rund dreifache
|
||||
// Viskositaet) entsprechend spaeter - 30 s waeren laengst voller Bezug bei vollem Druck
|
||||
// gewesen, und die Haelfte des Pumpen-Laufzeitbudgets waere vor dem Hauptbezug verbraucht.
|
||||
const float defaultColdExtractionPreInfusionSeconds = 10.0f;
|
||||
// Die Vorbenetzung laeuft bewusst SEHR sanft. Messung an der Maschine: heiss kommen nach
|
||||
// 7 s Vorbenetzung 1-2 Tropfen, kalt bei voller Pumpenleistung rund 10 g duenne Bruehe -
|
||||
// etwa zehnfacher Durchfluss trotz dreifacher Viskositaet. Kaltes Mehl quillt nicht und ist
|
||||
// schlecht benetzbar; volle Leistung auf den trockenen Puck legt sofort Kanaele an, die sich
|
||||
// - anders als heiss - nie wieder zudruecken. Der restliche Bezug laeuft dann daran vorbei.
|
||||
const float defaultColdExtractionPreInfDutyPercent = 10.0f;
|
||||
const uint16_t defaultColdExtractionPulsePeriodMs = 2000;
|
||||
const float defaultColdExtractionDutyPercent = 30.0f;
|
||||
// 20 % statt 30 %: Das kalte Kaffeebett ist deutlich durchlaessiger als ein heisses
|
||||
// (kein Quellen, keine Feinanteil-Wanderung, keine geloesten Feststoffe, die es verdichten).
|
||||
const float defaultColdExtractionDutyPercent = 20.0f;
|
||||
const float defaultColdExtractionTargetGrams = 60.0f;
|
||||
const float defaultColdExtractionMaxSeconds = 300.0f;
|
||||
const float defaultColdExtractionPumpMaxRunSec = 60.0f;
|
||||
@@ -1067,7 +1072,8 @@ const int EEPROM_ADDR_COLDEX_MAX_SECONDS = 965; // 4 Bytes (float) -> Ende
|
||||
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_MODE_ACTIVE = 977; // 1 Byte (uint8_t) -> Ende 977
|
||||
// Naechste freie Adresse: 978 (Innerhalb EEPROM_SIZE=1024)
|
||||
const int EEPROM_ADDR_COLDEX_PREINF_DUTY = 978; // 4 Bytes (float) -> Ende 981
|
||||
// Naechste freie Adresse: 982 (Innerhalb EEPROM_SIZE=1024)
|
||||
|
||||
/************************************************************************************
|
||||
* Eco-Mode Variablen
|
||||
@@ -3054,6 +3060,7 @@ void resetToDefaults() {
|
||||
EEPROM.put(EEPROM_ADDR_COLDEX_PREINF_SEC, defaultColdExtractionPreInfusionSeconds);
|
||||
EEPROM.put(EEPROM_ADDR_COLDEX_PULSE_PERIOD_MS, defaultColdExtractionPulsePeriodMs);
|
||||
EEPROM.put(EEPROM_ADDR_COLDEX_DUTY, defaultColdExtractionDutyPercent);
|
||||
EEPROM.put(EEPROM_ADDR_COLDEX_PREINF_DUTY, defaultColdExtractionPreInfDutyPercent);
|
||||
EEPROM.put(EEPROM_ADDR_COLDEX_TARGET_GRAMS, defaultColdExtractionTargetGrams);
|
||||
EEPROM.put(EEPROM_ADDR_COLDEX_MAX_SECONDS, defaultColdExtractionMaxSeconds);
|
||||
EEPROM.put(EEPROM_ADDR_COLDEX_PUMP_MAX_RUN, defaultColdExtractionPumpMaxRunSec);
|
||||
@@ -3142,6 +3149,7 @@ void resetToDefaults() {
|
||||
coldExtractionPreInfusionSeconds = defaultColdExtractionPreInfusionSeconds;
|
||||
coldExtractionPulsePeriodMs = defaultColdExtractionPulsePeriodMs;
|
||||
coldExtractionDutyPercent = defaultColdExtractionDutyPercent;
|
||||
coldExtractionPreInfDutyPercent = defaultColdExtractionPreInfDutyPercent;
|
||||
coldExtractionTargetGrams = defaultColdExtractionTargetGrams;
|
||||
coldExtractionMaxSeconds = defaultColdExtractionMaxSeconds;
|
||||
coldExtractionPumpMaxRunSec = defaultColdExtractionPumpMaxRunSec;
|
||||
@@ -4230,10 +4238,12 @@ void applyColdExtractionPumpControl(unsigned long currentShotTimeMillis) {
|
||||
bool pumpOn;
|
||||
if (coldExtractionPumpResting) {
|
||||
pumpOn = false;
|
||||
} else if (currentPreInfusionState == PI_PRE_BREW) {
|
||||
pumpOn = true; // Vorbenetzung: volle Pumpenleistung
|
||||
} else {
|
||||
float duty = coldExtractionDutyPercent / 100.0f;
|
||||
// Vorbenetzung und Hauptbezug pulsen beide - nur mit unterschiedlicher Leistung.
|
||||
// Volle Leistung auf den trockenen kalten Puck legt Kanaele an, die sich mangels
|
||||
// Quellung nie wieder schliessen (siehe Kommentar bei den Defaults).
|
||||
float duty = ((currentPreInfusionState == PI_PRE_BREW) ? coldExtractionPreInfDutyPercent
|
||||
: coldExtractionDutyPercent) / 100.0f;
|
||||
duty = constrain(duty, COLDEX_DUTY_MIN / 100.0f, 1.0f);
|
||||
uint16_t periodMs = coldExtractionPulsePeriodMs;
|
||||
if (periodMs < COLDEX_PULSE_PERIOD_MIN_MS || periodMs > COLDEX_PULSE_PERIOD_MAX_MS) {
|
||||
@@ -4698,6 +4708,7 @@ void setup() {
|
||||
EEPROM.get(EEPROM_ADDR_COLDEX_PREINF_SEC, coldExtractionPreInfusionSeconds);
|
||||
EEPROM.get(EEPROM_ADDR_COLDEX_PULSE_PERIOD_MS, coldExtractionPulsePeriodMs);
|
||||
EEPROM.get(EEPROM_ADDR_COLDEX_DUTY, coldExtractionDutyPercent);
|
||||
EEPROM.get(EEPROM_ADDR_COLDEX_PREINF_DUTY, coldExtractionPreInfDutyPercent);
|
||||
EEPROM.get(EEPROM_ADDR_COLDEX_TARGET_GRAMS, coldExtractionTargetGrams);
|
||||
EEPROM.get(EEPROM_ADDR_COLDEX_MAX_SECONDS, coldExtractionMaxSeconds);
|
||||
EEPROM.get(EEPROM_ADDR_COLDEX_PUMP_MAX_RUN, coldExtractionPumpMaxRunSec);
|
||||
@@ -4714,6 +4725,10 @@ void setup() {
|
||||
if (!isfinite(coldExtractionDutyPercent) || coldExtractionDutyPercent < COLDEX_DUTY_MIN || coldExtractionDutyPercent > COLDEX_DUTY_MAX) {
|
||||
coldExtractionDutyPercent = defaultColdExtractionDutyPercent;
|
||||
}
|
||||
// Beim Update aus <= 5.3.5 steht hier 0xFFFFFFFF (NaN) -> Default greift automatisch
|
||||
if (!isfinite(coldExtractionPreInfDutyPercent) || coldExtractionPreInfDutyPercent < COLDEX_DUTY_MIN || coldExtractionPreInfDutyPercent > COLDEX_DUTY_MAX) {
|
||||
coldExtractionPreInfDutyPercent = defaultColdExtractionPreInfDutyPercent;
|
||||
}
|
||||
if (!isfinite(coldExtractionTargetGrams) || coldExtractionTargetGrams < COLDEX_TARGET_GRAMS_MIN || coldExtractionTargetGrams > COLDEX_TARGET_GRAMS_MAX) {
|
||||
coldExtractionTargetGrams = defaultColdExtractionTargetGrams;
|
||||
}
|
||||
@@ -7207,11 +7222,13 @@ static const char brewControlCX_ToggleContainerEnd[] PROGMEM = R"rawliteral(><sp
|
||||
static const char brewControlCX_Fields[] PROGMEM = R"rawliteral(
|
||||
<label for='cxMaxTemp'>Freigabe bis Wassertemperatur (°C):</label>
|
||||
<input type='number' min='10.0' max='40.0' step='0.5' id='cxMaxTemp' name='cxMaxTemp' value='{CX_MAX_TEMP}' required>
|
||||
<label for='cxPreInf'>Vorbenetzung (Sekunden, Pumpe läuft dabei durchgehend):</label>
|
||||
<label for='cxPreInf'>Vorbenetzung (Sekunden):</label>
|
||||
<input type='number' min='0.0' max='120.0' step='1.0' id='cxPreInf' name='cxPreInf' value='{CX_PREINF}' required>
|
||||
<label for='cxPreInfDuty'>Pumpenleistung Vorbenetzung (%):</label>
|
||||
<input type='number' min='5.0' max='100.0' step='1.0' id='cxPreInfDuty' name='cxPreInfDuty' value='{CX_PREINF_DUTY}' required>
|
||||
<label for='cxPulseMs'>Pulsperiode (ms):</label>
|
||||
<input type='number' min='200' max='5000' step='50' id='cxPulseMs' name='cxPulseMs' value='{CX_PULSE_MS}' required>
|
||||
<label for='cxDuty'>Pumpenleistung (%):</label>
|
||||
<label for='cxDuty'>Pumpenleistung Hauptbezug (%):</label>
|
||||
<input type='number' min='5.0' max='100.0' step='1.0' id='cxDuty' name='cxDuty' value='{CX_DUTY}' required>
|
||||
<label for='cxTarget'>Zielgewicht (Gramm, nur mit Waage wirksam):</label>
|
||||
<input type='number' min='10.0' max='300.0' step='1.0' id='cxTarget' name='cxTarget' value='{CX_TARGET}' required>
|
||||
@@ -7224,9 +7241,12 @@ static const char brewControlCX_Fields[] PROGMEM = R"rawliteral(
|
||||
<small class="toggle-description-input">(Die Maschine hat keinen Bypass um den Wasserkessel: Kaltes Wasser kommt nur bei kaltem
|
||||
Kessel am Puck an. Deshalb ist die kalte Extraktion nur unterhalb der Freigabetemperatur möglich, praktisch also
|
||||
aus dem kalten Zustand heraus. Solange der Modus aktiv ist, heizt der Wasserkreis nicht - der Dampfkreis bleibt
|
||||
unberührt und darf weiter heizen. Während der Vorbenetzung läuft die Pumpe durchgehend auf voller
|
||||
Leistung (kein Pulsen) - sinnvoll ist eine Dauer knapp unterhalb des ersten Tropfens; gepulst wird erst im
|
||||
Hauptbezug. Die Pumpenlaufzeit wird kumuliert gezählt; nach der eingestellten
|
||||
unberührt und darf weiter heizen. Vorbenetzung und Hauptbezug pulsen beide, nur mit unterschiedlicher
|
||||
Leistung: Kaltes Kaffeemehl quillt nicht und ist schlecht benetzbar - volle Leistung auf den trockenen Puck
|
||||
legt sofort Kanäle an, die sich (anders als bei heißem Bezug) nie wieder schließen, und der
|
||||
restliche Bezug läuft daran vorbei. Deshalb die Vorbenetzung deutlich schwächer einstellen als den
|
||||
Hauptbezug. Das kalte Bett ist insgesamt viel durchlässiger als ein heißes, niedrige Werte sind
|
||||
also normal. Die Pumpenlaufzeit wird kumuliert gezählt; nach der eingestellten
|
||||
Laufzeit legt die Steuerung eine Zwangspause ein. Der Modus selbst wird auf dem Dashboard bzw. am Touch-Display
|
||||
ein- und ausgeschaltet und bleibt über Standby und Neustart hinweg aktiv, bis er dort wieder ausgeschaltet
|
||||
wird - solange heizt der Wasserkreis nicht.)</small>
|
||||
@@ -7362,6 +7382,8 @@ void handleBrewControl(AsyncWebServerRequest *request) {
|
||||
cxFieldsHtml.replace("{CX_PULSE_MS}", buffer);
|
||||
snprintf(buffer, sizeof(buffer), "%.1f", coldExtractionDutyPercent);
|
||||
cxFieldsHtml.replace("{CX_DUTY}", buffer);
|
||||
snprintf(buffer, sizeof(buffer), "%.1f", coldExtractionPreInfDutyPercent);
|
||||
cxFieldsHtml.replace("{CX_PREINF_DUTY}", buffer);
|
||||
snprintf(buffer, sizeof(buffer), "%.1f", coldExtractionTargetGrams);
|
||||
cxFieldsHtml.replace("{CX_TARGET}", buffer);
|
||||
snprintf(buffer, sizeof(buffer), "%.1f", coldExtractionMaxSeconds);
|
||||
@@ -7460,6 +7482,8 @@ struct BrewControlUpdate {
|
||||
uint16_t cxPulseMs = 0;
|
||||
bool hasCXDuty = false;
|
||||
float cxDuty = 0.0f;
|
||||
bool hasCXPreInfDuty = false;
|
||||
float cxPreInfDuty = 0.0f;
|
||||
bool hasCXTarget = false;
|
||||
float cxTarget = 0.0f;
|
||||
bool hasCXMaxSecs = false;
|
||||
@@ -7625,6 +7649,14 @@ bool applyBrewControlUpdate(const BrewControlUpdate& update) {
|
||||
coldExtractionConfigChanged = true;
|
||||
}
|
||||
}
|
||||
if (update.hasCXPreInfDuty) {
|
||||
float v = update.cxPreInfDuty;
|
||||
if (isfinite(v) && v >= COLDEX_DUTY_MIN && v <= COLDEX_DUTY_MAX && fabs(v - coldExtractionPreInfDutyPercent) > 0.01f) {
|
||||
coldExtractionPreInfDutyPercent = v;
|
||||
EEPROM.put(EEPROM_ADDR_COLDEX_PREINF_DUTY, coldExtractionPreInfDutyPercent);
|
||||
coldExtractionConfigChanged = true;
|
||||
}
|
||||
}
|
||||
if (update.hasCXTarget) {
|
||||
float v = update.cxTarget;
|
||||
if (isfinite(v) && v >= COLDEX_TARGET_GRAMS_MIN && v <= COLDEX_TARGET_GRAMS_MAX && fabs(v - coldExtractionTargetGrams) > 0.01f) {
|
||||
@@ -7715,6 +7747,8 @@ void handleSaveBrewControl(AsyncWebServerRequest *request) {
|
||||
if (update.hasCXPulseMs) { update.cxPulseMs = (uint16_t)request->arg("cxPulseMs").toInt(); }
|
||||
update.hasCXDuty = request->hasArg("cxDuty");
|
||||
if (update.hasCXDuty) { update.cxDuty = request->arg("cxDuty").toFloat(); }
|
||||
update.hasCXPreInfDuty = request->hasArg("cxPreInfDuty");
|
||||
if (update.hasCXPreInfDuty) { update.cxPreInfDuty = request->arg("cxPreInfDuty").toFloat(); }
|
||||
update.hasCXTarget = request->hasArg("cxTarget");
|
||||
if (update.hasCXTarget) { update.cxTarget = request->arg("cxTarget").toFloat(); }
|
||||
update.hasCXMaxSecs = request->hasArg("cxMaxSecs");
|
||||
|
||||
Reference in New Issue
Block a user