Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f840173a9 | ||
|
|
6371b22683 | ||
|
|
de2d0635b2 | ||
|
|
801a659b0c |
@@ -1,3 +1,22 @@
|
|||||||
|
Version 5.2.0:
|
||||||
|
- Feed-Forward bei Bezug (Störgrößenaufschaltung) für den Wasserkessel: Während eines Bezugs wird der
|
||||||
|
Heizung sofort ein einstellbarer Leistungs-Boost (0–100 % der Fenstergröße, 0 % = aus) aufgeschaltet,
|
||||||
|
statt auf den vom Sensor gemeldeten Temperaturabfall zu warten. Die PID regelt nur noch den Restfehler;
|
||||||
|
der Temperatur-Dip während des Shots wird deutlich kleiner. Einstellung auf der /PID-Seite
|
||||||
|
(„Feed-Forward bei Bezug"), gespeichert im EEPROM (bewusst kein Profilfeld, da maschinenabhängig).
|
||||||
|
- Optional: I-Anteil der Wasser-PID während des Bezugs einfrieren. Verhindert das Aufladen des
|
||||||
|
Integral-Anteils während des Shots und damit das Überschwingen nach Bezugsende.
|
||||||
|
- Der Boost wirkt nur bei echten Bezügen (Hardware-Schalter oder Software-Start), nicht bei
|
||||||
|
Spülen/Wartungsmodus/AutoTune. Sicherheitslogik unverändert: Sensorfehler, Übertemperatur-Abschaltung
|
||||||
|
und „Heizen oberhalb Setpoint verhindern" haben weiterhin Vorrang; Deckelung auf die Fenstergröße.
|
||||||
|
- Die Heizleistungs-Telemetrie (dutyW, u. a. für das Verlaufs-Chart des Touch-Displays) zeigt den
|
||||||
|
Boost mit an - damit ist die Wirkung direkt im Chart kalibrierbar.
|
||||||
|
|
||||||
|
Version 5.1.2:
|
||||||
|
- Profil-Seite (Web-UI): Die neue „Aktives Profil"-Anzeige stand linksbündig außerhalb des
|
||||||
|
Karten-Layouts. Sie nutzt jetzt den status-message-Stil der Seite (zentriert, max. 600 px,
|
||||||
|
dezenter Rahmen) und fügt sich damit in das bestehende Layout ein; Profilname zusätzlich fett.
|
||||||
|
|
||||||
Version 5.1.1:
|
Version 5.1.1:
|
||||||
- Aktives Profil wird jetzt geführt und angezeigt: Die Steuerung merkt sich das zuletzt geladene bzw.
|
- Aktives Profil wird jetzt geführt und angezeigt: Die Steuerung merkt sich das zuletzt geladene bzw.
|
||||||
gespeicherte Profil dauerhaft im EEPROM (überlebt Neustarts). Web-UI: Anzeige auf dem Dashboard über der
|
gespeicherte Profil dauerhaft im EEPROM (überlebt Neustarts). Web-UI: Anzeige auf dem Dashboard über der
|
||||||
|
|||||||
+137
-15
@@ -369,7 +369,7 @@ Adafruit_SH1106G display = Adafruit_SH1106G(128, 64, &Wire);
|
|||||||
* Firmware-Informationen
|
* Firmware-Informationen
|
||||||
************************************************************************************/
|
************************************************************************************/
|
||||||
|
|
||||||
String version = "5.1.1";
|
String version = "5.2.0";
|
||||||
String versionHersteller = "Thomas Müller";
|
String versionHersteller = "Thomas Mü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>";
|
||||||
@@ -656,6 +656,17 @@ unsigned long windowStartTimeDampf; // Startzeit des aktuellen Fensters für D
|
|||||||
PID pidDampf(&InputDampf, &OutputDampf, &SetpointDampf, KpDampf, KiDampf, KdDampf, DIRECT);
|
PID pidDampf(&InputDampf, &OutputDampf, &SetpointDampf, KpDampf, KiDampf, KdDampf, DIRECT);
|
||||||
PID pidWasser(&InputWasser, &OutputWasser, &SetpointWasser, KpWasser, KiWasser, KdWasser, DIRECT);
|
PID pidWasser(&InputWasser, &OutputWasser, &SetpointWasser, KpWasser, KiWasser, KdWasser, DIRECT);
|
||||||
|
|
||||||
|
// Feed-Forward (Störgrößenaufschaltung) bei Bezug: der Bezug ist eine bekannte Störung
|
||||||
|
// (kaltes Wasser strömt in den Kessel). Statt auf den Temperaturabfall zu warten, wird
|
||||||
|
// der Heizleistung während des Bezugs sofort ein fester Boost aufgeschaltet; die PID
|
||||||
|
// regelt nur noch den Restfehler. Optional wird der I-Anteil während des Bezugs
|
||||||
|
// eingefroren, damit er sich nicht auflädt (verhindert Überschwingen nach dem Shot).
|
||||||
|
uint8_t ffBoostPercentWasser = 0; // Zusatz-Heizleistung während Bezug in % der Fenstergröße (0 = deaktiviert)
|
||||||
|
bool ffIFreezeWasser = false; // I-Anteil der Wasser-PID während des Bezugs einfrieren
|
||||||
|
bool ffPidIFrozenWasser = false; // Laufzeitzustand: Ki ist aktuell auf 0 gesetzt
|
||||||
|
const uint8_t defaultFfBoostPercentWasser = 0;
|
||||||
|
const bool defaultFfIFreezeWasser = false;
|
||||||
|
|
||||||
// Variablen für Sicherheitsabschaltung
|
// Variablen für Sicherheitsabschaltung
|
||||||
double maxTempWasser = 110.0; // Standardwert, wie bisher hardcoded
|
double maxTempWasser = 110.0; // Standardwert, wie bisher hardcoded
|
||||||
double maxTempDampf = 180.0; // Standardwert, wie bisher hardcoded
|
double maxTempDampf = 180.0; // Standardwert, wie bisher hardcoded
|
||||||
@@ -944,7 +955,9 @@ const int EEPROM_ADDR_POWERON_STANDBY = 910; // 1 Byte (bool) -> Ende 9
|
|||||||
const int EEPROM_ADDR_DISPLAY_TYPE = 911; // 1 Byte (uint8_t) -> Ende 911
|
const int EEPROM_ADDR_DISPLAY_TYPE = 911; // 1 Byte (uint8_t) -> Ende 911
|
||||||
const int EEPROM_ADDR_ACTIVE_PROFILE_NAME = 912; // 31 Bytes (char[31], nullterminiert) -> Ende 942
|
const int EEPROM_ADDR_ACTIVE_PROFILE_NAME = 912; // 31 Bytes (char[31], nullterminiert) -> Ende 942
|
||||||
const int EEPROM_ADDR_ACTIVE_PROFILE_DIRTY = 943; // 1 Byte (bool) -> Ende 943
|
const int EEPROM_ADDR_ACTIVE_PROFILE_DIRTY = 943; // 1 Byte (bool) -> Ende 943
|
||||||
// Naechste freie Adresse: 944 (Innerhalb EEPROM_SIZE=1024)
|
const int EEPROM_ADDR_FF_BOOST_PERCENT_WASSER = 944; // 1 Byte (uint8_t) -> Ende 944
|
||||||
|
const int EEPROM_ADDR_FF_IFREEZE_WASSER = 945; // 1 Byte (bool) -> Ende 945
|
||||||
|
// Naechste freie Adresse: 946 (Innerhalb EEPROM_SIZE=1024)
|
||||||
|
|
||||||
/************************************************************************************
|
/************************************************************************************
|
||||||
* Eco-Mode Variablen
|
* Eco-Mode Variablen
|
||||||
@@ -2871,6 +2884,8 @@ void resetToDefaults() {
|
|||||||
EEPROM.put(EEPROM_ADDR_BOOST_DAMPF_ACTIVE, defaultBoostDampfActive);
|
EEPROM.put(EEPROM_ADDR_BOOST_DAMPF_ACTIVE, defaultBoostDampfActive);
|
||||||
EEPROM.put(EEPROM_ADDR_PREVENTHEAT_WASSER, defaultPreventHeatAboveSetpointWasser);
|
EEPROM.put(EEPROM_ADDR_PREVENTHEAT_WASSER, defaultPreventHeatAboveSetpointWasser);
|
||||||
EEPROM.put(EEPROM_ADDR_PREVENTHEAT_DAMPF, defaultPreventHeatAboveSetpointDampf);
|
EEPROM.put(EEPROM_ADDR_PREVENTHEAT_DAMPF, defaultPreventHeatAboveSetpointDampf);
|
||||||
|
EEPROM.put(EEPROM_ADDR_FF_BOOST_PERCENT_WASSER, defaultFfBoostPercentWasser);
|
||||||
|
EEPROM.put(EEPROM_ADDR_FF_IFREEZE_WASSER, (uint8_t)(defaultFfIFreezeWasser ? 1 : 0));
|
||||||
EEPROM.put(EEPROM_ADDR_MAX_TEMP_WASSER, maxTempWasser); // Sollte ggf. eigene Defaults haben
|
EEPROM.put(EEPROM_ADDR_MAX_TEMP_WASSER, maxTempWasser); // Sollte ggf. eigene Defaults haben
|
||||||
EEPROM.put(EEPROM_ADDR_MAX_TEMP_DAMPF, maxTempDampf); // Sollte ggf. eigene Defaults haben
|
EEPROM.put(EEPROM_ADDR_MAX_TEMP_DAMPF, maxTempDampf); // Sollte ggf. eigene Defaults haben
|
||||||
|
|
||||||
@@ -3861,6 +3876,46 @@ void applyFlowGuardPumpControl(unsigned long currentShotTimeMillis, float netWei
|
|||||||
digitalWrite(PUMP_PIN, pumpOn ? HIGH : LOW);
|
digitalWrite(PUMP_PIN, pumpOn ? HIGH : LOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/************************************************************************************
|
||||||
|
* Feed-Forward bei Bezug (Wasserkessel)
|
||||||
|
************************************************************************************/
|
||||||
|
|
||||||
|
// Setzt die Wasser-PID-Tunings und berücksichtigt dabei einen eingefrorenen I-Anteil.
|
||||||
|
// Alle Stellen, die die Wasser-Tunings anwenden, müssen diese Funktion nutzen, damit
|
||||||
|
// der Freeze-Zustand bei Änderungen während eines Bezugs erhalten bleibt.
|
||||||
|
void applyPidWasserTunings() {
|
||||||
|
pidWasser.SetTunings(KpWasser, ffPidIFrozenWasser ? 0.0 : KiWasser, KdWasser);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Friert den I-Anteil der Wasser-PID ein bzw. gibt ihn wieder frei (nur bei Wechsel).
|
||||||
|
// Bei Ki=0 bleibt die aufsummierte I-Komponente in der PID-Library erhalten und wird
|
||||||
|
// nach dem Auftauen unverändert weiterverwendet.
|
||||||
|
void setPidWasserIntegralFrozen(bool frozen) {
|
||||||
|
if (frozen == ffPidIFrozenWasser) return;
|
||||||
|
ffPidIFrozenWasser = frozen;
|
||||||
|
applyPidWasserTunings();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feed-Forward nur während eines echten Bezugs (Hardware- oder Software-Shot),
|
||||||
|
// nicht im Wartungsmodus und nicht während AutoTune.
|
||||||
|
bool ffBoostWasserActiveNow() {
|
||||||
|
return ffBoostPercentWasser > 0 && (shotActive || shotSoftwareActive) &&
|
||||||
|
!wartungsModusAktiv && !autoTuneWasserActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Effektive Einschaltzeit im aktuellen Fenster: PID-Output + Feed-Forward-Boost,
|
||||||
|
// gedeckelt auf die Fenstergröße. Zustandslos, damit SSR-Ansteuerung und
|
||||||
|
// Duty-Telemetrie (Chart!) denselben Wert sehen.
|
||||||
|
unsigned long effectiveOutputWasserMs() {
|
||||||
|
double out = OutputWasser;
|
||||||
|
if (ffBoostWasserActiveNow()) {
|
||||||
|
out += ((double)ffBoostPercentWasser / 100.0) * (double)windowSizeWasser;
|
||||||
|
}
|
||||||
|
if (out > (double)windowSizeWasser) out = (double)windowSizeWasser;
|
||||||
|
if (out < 0.0) out = 0.0;
|
||||||
|
return (unsigned long)out;
|
||||||
|
}
|
||||||
|
|
||||||
/************************************************************************************
|
/************************************************************************************
|
||||||
* Stoppt den Brühvorgang (Pumpe/Ventil) und verarbeitet Shot-Daten.
|
* Stoppt den Brühvorgang (Pumpe/Ventil) und verarbeitet Shot-Daten.
|
||||||
* Wird von updateShotTimer() oder aus loop() (BrewByTime/Weight) aufgerufen.
|
* Wird von updateShotTimer() oder aus loop() (BrewByTime/Weight) aufgerufen.
|
||||||
@@ -4178,6 +4233,13 @@ void setup() {
|
|||||||
EEPROM.get(EEPROM_ADDR_BOOST_DAMPF_ACTIVE, boostDampfActive);
|
EEPROM.get(EEPROM_ADDR_BOOST_DAMPF_ACTIVE, boostDampfActive);
|
||||||
EEPROM.get(EEPROM_ADDR_PREVENTHEAT_WASSER, preventHeatAboveSetpointWasser);
|
EEPROM.get(EEPROM_ADDR_PREVENTHEAT_WASSER, preventHeatAboveSetpointWasser);
|
||||||
EEPROM.get(EEPROM_ADDR_PREVENTHEAT_DAMPF, preventHeatAboveSetpointDampf);
|
EEPROM.get(EEPROM_ADDR_PREVENTHEAT_DAMPF, preventHeatAboveSetpointDampf);
|
||||||
|
uint8_t storedFfBoostPercent = defaultFfBoostPercentWasser;
|
||||||
|
EEPROM.get(EEPROM_ADDR_FF_BOOST_PERCENT_WASSER, storedFfBoostPercent);
|
||||||
|
if (storedFfBoostPercent > 100) storedFfBoostPercent = defaultFfBoostPercentWasser; // u. a. unprogrammierte 0xFF
|
||||||
|
ffBoostPercentWasser = storedFfBoostPercent;
|
||||||
|
uint8_t storedFfIFreeze = 0;
|
||||||
|
EEPROM.get(EEPROM_ADDR_FF_IFREEZE_WASSER, storedFfIFreeze);
|
||||||
|
ffIFreezeWasser = (storedFfIFreeze == 1);
|
||||||
EEPROM.get(EEPROM_ADDR_WINDOWSIZE_WASSER, windowSizeWasser);
|
EEPROM.get(EEPROM_ADDR_WINDOWSIZE_WASSER, windowSizeWasser);
|
||||||
EEPROM.get(EEPROM_ADDR_WINDOWSIZE_DAMPF, windowSizeDampf);
|
EEPROM.get(EEPROM_ADDR_WINDOWSIZE_DAMPF, windowSizeDampf);
|
||||||
uint8_t storedOffsetCompensationEnabled = 0;
|
uint8_t storedOffsetCompensationEnabled = 0;
|
||||||
@@ -4504,7 +4566,7 @@ void setup() {
|
|||||||
}
|
}
|
||||||
pidWasser.SetOutputLimits(0, windowSizeWasser);
|
pidWasser.SetOutputLimits(0, windowSizeWasser);
|
||||||
pidDampf.SetOutputLimits(0, windowSizeDampf);
|
pidDampf.SetOutputLimits(0, windowSizeDampf);
|
||||||
pidWasser.SetTunings(KpWasser, KiWasser, KdWasser);
|
applyPidWasserTunings();
|
||||||
pidDampf.SetTunings(KpDampf, KiDampf, KdDampf);
|
pidDampf.SetTunings(KpDampf, KiDampf, KdDampf);
|
||||||
pidWasser.SetMode(AUTOMATIC);
|
pidWasser.SetMode(AUTOMATIC);
|
||||||
pidDampf.SetMode(AUTOMATIC);
|
pidDampf.SetMode(AUTOMATIC);
|
||||||
@@ -4866,6 +4928,11 @@ void loop() {
|
|||||||
}
|
}
|
||||||
yield();
|
yield();
|
||||||
|
|
||||||
|
// Feed-Forward: I-Anteil der Wasser-PID während des Bezugs einfrieren/freigeben.
|
||||||
|
// Zentral hier verwaltet (statt an Start-/Stopp-Stellen), damit der Zustand auch
|
||||||
|
// nach Software-Shots, Einstellungsänderungen mid-shot etc. immer konsistent ist.
|
||||||
|
setPidWasserIntegralFrozen(ffIFreezeWasser && (shotActive || shotSoftwareActive) && !wartungsModusAktiv);
|
||||||
|
|
||||||
// --- Angepasste Sicherheitsueberpruefung Wasser ---
|
// --- Angepasste Sicherheitsueberpruefung Wasser ---
|
||||||
if (InputWasser < 0.0 || isnan(InputWasser)) {
|
if (InputWasser < 0.0 || isnan(InputWasser)) {
|
||||||
wasserSensorError = true;
|
wasserSensorError = true;
|
||||||
@@ -4964,7 +5031,8 @@ void loop() {
|
|||||||
// Wasser SSR
|
// Wasser SSR
|
||||||
if (wasserSensorError || wasserSafetyShutdown || standbyModeActive || displayOtaActive) { digitalWrite(SSR_WASSER_PIN, LOW); } else {
|
if (wasserSensorError || wasserSafetyShutdown || standbyModeActive || displayOtaActive) { digitalWrite(SSR_WASSER_PIN, LOW); } else {
|
||||||
if (now - windowStartTimeWasser > windowSizeWasser) { windowStartTimeWasser = now; }
|
if (now - windowStartTimeWasser > windowSizeWasser) { windowStartTimeWasser = now; }
|
||||||
bool pidWantsHeat = (OutputWasser > 0) && (now < (windowStartTimeWasser + (unsigned long)OutputWasser));
|
unsigned long effOutWasser = effectiveOutputWasserMs(); // PID-Output + Feed-Forward-Boost bei Bezug
|
||||||
|
bool pidWantsHeat = (effOutWasser > 0) && (now < (windowStartTimeWasser + effOutWasser));
|
||||||
bool preventOverheatActive = (preventHeatAboveSetpointWasser && (InputWasser > SetpointWasser)) || (ecoModeAktiv && (InputWasser > SetpointWasser));
|
bool preventOverheatActive = (preventHeatAboveSetpointWasser && (InputWasser > SetpointWasser)) || (ecoModeAktiv && (InputWasser > SetpointWasser));
|
||||||
if (pidWantsHeat && !preventOverheatActive) { digitalWrite(SSR_WASSER_PIN, HIGH); } else { digitalWrite(SSR_WASSER_PIN, LOW); }
|
if (pidWantsHeat && !preventOverheatActive) { digitalWrite(SSR_WASSER_PIN, HIGH); } else { digitalWrite(SSR_WASSER_PIN, LOW); }
|
||||||
}
|
}
|
||||||
@@ -6406,7 +6474,7 @@ void handleAutoTune(AsyncWebServerRequest *request) {
|
|||||||
KdWasser = newKd;
|
KdWasser = newKd;
|
||||||
|
|
||||||
// WICHTIG: PID-Tunings sofort anwenden
|
// WICHTIG: PID-Tunings sofort anwenden
|
||||||
pidWasser.SetTunings(KpWasser, KiWasser, KdWasser);
|
applyPidWasserTunings();
|
||||||
|
|
||||||
// Neue Werte im EEPROM speichern
|
// Neue Werte im EEPROM speichern
|
||||||
EEPROM.put(EEPROM_ADDR_KP_WASSER, KpWasser);
|
EEPROM.put(EEPROM_ADDR_KP_WASSER, KpWasser);
|
||||||
@@ -8717,8 +8785,32 @@ static const char rootChunk_PIDWasser_Window[] PROGMEM = R"rawliteral('>
|
|||||||
<label for='windowSizeWasser'>Zeitfenster (ms):</label>
|
<label for='windowSizeWasser'>Zeitfenster (ms):</label>
|
||||||
<input type='number' step='1' id='windowSizeWasser' name='windowSizeWasser' value=')rawliteral"; // Step auf 1 geändert für ms, Endet vor Window Size Wert
|
<input type='number' step='1' id='windowSizeWasser' name='windowSizeWasser' value=')rawliteral"; // Step auf 1 geändert für ms, Endet vor Window Size Wert
|
||||||
|
|
||||||
|
// --- Feed-Forward bei Bezug (Wasser) Sektion ---
|
||||||
|
static const char rootChunk_FFWasser_Start[] PROGMEM = R"rawliteral('>
|
||||||
|
<br><br> <h3>Feed-Forward bei Bezug (Wasser)</h3>
|
||||||
|
<p style="font-size:0.9em; color: #ccc; margin-bottom:15px;">
|
||||||
|
Während des Bezugs strömt kaltes Wasser in den Kessel. Der Boost schaltet der Heizung<br>
|
||||||
|
sofort zusätzliche Leistung auf, statt auf den Temperaturabfall zu warten (Feed-Forward).<br>
|
||||||
|
Richtwert: benötigte Leistung [W] = Flow [g/s] · 4,2 · Temperaturhub [K] — daraus Prozent<br>
|
||||||
|
der Heizleistung bestimmen. <b>0 % = deaktiviert.</b> Wirkt nur während eines Bezugs,<br>
|
||||||
|
nicht bei Spülen/Wartung. „Heizen oberhalb Setpoint verhindern“ hat weiterhin Vorrang.
|
||||||
|
</p>
|
||||||
|
<label for='ffBoostWasser'>Boost bei Bezug (% Heizleistung):</label>
|
||||||
|
<input type='number' step='1' min='0' max='100' id='ffBoostWasser' name='ffBoostWasser' value=')rawliteral"; // Endet vor Boost-Prozent-Wert
|
||||||
|
static const char rootChunk_FFWasser_IFreeze_Start[] PROGMEM = R"rawliteral('>
|
||||||
|
<div class="toggle-switch-container">
|
||||||
|
<span class="toggle-switch-label-text">I-Anteil während Bezug einfrieren:</span>
|
||||||
|
<label class="toggle-switch">
|
||||||
|
<input type="checkbox" id="ffIFreezeWasser" name="ffIFreezeWasser" value="1")rawliteral"; // Endet VOR checked
|
||||||
|
static const char rootChunk_FFWasser_IFreeze_End[] PROGMEM = R"rawliteral(>
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<small class="toggle-description">Verhindert das Aufladen des I-Anteils während des Bezugs (kein Überschwingen nach dem Shot)</small>
|
||||||
|
)rawliteral";
|
||||||
|
|
||||||
// --- PID Dampf Sektion ---
|
// --- PID Dampf Sektion ---
|
||||||
static const char rootChunk_PIDDampf_Start[] PROGMEM = R"rawliteral('>
|
static const char rootChunk_PIDDampf_Start[] PROGMEM = R"rawliteral(
|
||||||
<br><br> <h3>PID-Parameter: Dampf / Thermoblock</h3>
|
<br><br> <h3>PID-Parameter: Dampf / Thermoblock</h3>
|
||||||
<label for='kpDampf'>Kp:</label>
|
<label for='kpDampf'>Kp:</label>
|
||||||
<input type='number' step='0.01' id='kpDampf' name='kpDampf' value=')rawliteral"; // Endet vor Kp Dampf Wert
|
<input type='number' step='0.01' id='kpDampf' name='kpDampf' value=')rawliteral"; // Endet vor Kp Dampf Wert
|
||||||
@@ -8842,6 +8934,14 @@ void handlePidSettings(AsyncWebServerRequest *request) {
|
|||||||
response->print(buffer);
|
response->print(buffer);
|
||||||
yield();
|
yield();
|
||||||
|
|
||||||
|
response->write(rootChunk_FFWasser_Start, strlen_P(rootChunk_FFWasser_Start));
|
||||||
|
snprintf(buffer, sizeof(buffer), "%u", (unsigned int)ffBoostPercentWasser);
|
||||||
|
response->print(buffer);
|
||||||
|
response->write(rootChunk_FFWasser_IFreeze_Start, strlen_P(rootChunk_FFWasser_IFreeze_Start));
|
||||||
|
if (ffIFreezeWasser) response->print(F(" checked"));
|
||||||
|
response->write(rootChunk_FFWasser_IFreeze_End, strlen_P(rootChunk_FFWasser_IFreeze_End));
|
||||||
|
yield();
|
||||||
|
|
||||||
response->write(rootChunk_PIDDampf_Start, strlen_P(rootChunk_PIDDampf_Start));
|
response->write(rootChunk_PIDDampf_Start, strlen_P(rootChunk_PIDDampf_Start));
|
||||||
snprintf(buffer, sizeof(buffer), "%.2f", KpDampf);
|
snprintf(buffer, sizeof(buffer), "%.2f", KpDampf);
|
||||||
response->print(buffer);
|
response->print(buffer);
|
||||||
@@ -9067,15 +9167,34 @@ void handleSettingsUpdate(AsyncWebServerRequest *request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Feed-Forward bei Bezug (kein Profilfeld: maschinenabhängig, nicht rezeptabhängig)
|
||||||
|
bool ffChanged = false;
|
||||||
|
if (request->hasArg(F("ffBoostWasser"))) {
|
||||||
|
long val = request->arg(F("ffBoostWasser")).toInt();
|
||||||
|
if (val >= 0 && val <= 100 && (uint8_t)val != ffBoostPercentWasser) {
|
||||||
|
ffBoostPercentWasser = (uint8_t)val;
|
||||||
|
EEPROM.put(EEPROM_ADDR_FF_BOOST_PERCENT_WASSER, ffBoostPercentWasser);
|
||||||
|
ffChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool newFfIFreeze = request->hasArg(F("ffIFreezeWasser"));
|
||||||
|
if (newFfIFreeze != ffIFreezeWasser) {
|
||||||
|
ffIFreezeWasser = newFfIFreeze;
|
||||||
|
EEPROM.put(EEPROM_ADDR_FF_IFREEZE_WASSER, (uint8_t)(ffIFreezeWasser ? 1 : 0));
|
||||||
|
ffChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
// --- EEPROM speichern und PID aktualisieren ---
|
// --- EEPROM speichern und PID aktualisieren ---
|
||||||
if (valueChanged) { // Nur speichern, wenn sich mindestens ein Wert geändert hat
|
if (valueChanged || ffChanged) { // Nur speichern, wenn sich mindestens ein Wert geändert hat
|
||||||
EEPROM.commit();
|
EEPROM.commit();
|
||||||
|
}
|
||||||
|
if (valueChanged) {
|
||||||
markProfileDirty(); // Setpoints/Offsets/PID/Boost/MaxTemp/Window sind Profilfelder
|
markProfileDirty(); // Setpoints/Offsets/PID/Boost/MaxTemp/Window sind Profilfelder
|
||||||
}
|
}
|
||||||
|
|
||||||
// PID-Parameter an die Regler übergeben
|
// PID-Parameter an die Regler übergeben
|
||||||
pidDampf.SetTunings(KpDampf, KiDampf, KdDampf);
|
pidDampf.SetTunings(KpDampf, KiDampf, KdDampf);
|
||||||
pidWasser.SetTunings(KpWasser, KiWasser, KdWasser);
|
applyPidWasserTunings();
|
||||||
|
|
||||||
// Zurück zur Hauptseite (bereits vorhanden)
|
// Zurück zur Hauptseite (bereits vorhanden)
|
||||||
AsyncWebServerResponse *resp = request->beginResponse(303);
|
AsyncWebServerResponse *resp = request->beginResponse(303);
|
||||||
@@ -11489,7 +11608,7 @@ void applyProfileSettings(const TemperatureProfile& profileData) {
|
|||||||
EEPROM.commit();
|
EEPROM.commit();
|
||||||
|
|
||||||
// --- PID-Regler sofort aktualisieren ---
|
// --- PID-Regler sofort aktualisieren ---
|
||||||
pidWasser.SetTunings(KpWasser, KiWasser, KdWasser); pidDampf.SetTunings(KpDampf, KiDampf, KdDampf);
|
applyPidWasserTunings(); pidDampf.SetTunings(KpDampf, KiDampf, KdDampf);
|
||||||
pidWasser.SetOutputLimits(0, windowSizeWasser); pidDampf.SetOutputLimits(0, windowSizeDampf);
|
pidWasser.SetOutputLimits(0, windowSizeWasser); pidDampf.SetOutputLimits(0, windowSizeDampf);
|
||||||
// Serial.println(" PID controllers reconfigured with loaded settings.");
|
// Serial.println(" PID controllers reconfigured with loaded settings.");
|
||||||
|
|
||||||
@@ -11831,14 +11950,17 @@ void handleProfilesPage(AsyncWebServerRequest *request) {
|
|||||||
profileStatusMessage = ""; // Nachricht entfernen, nachdem sie angezeigt wurde
|
profileStatusMessage = ""; // Nachricht entfernen, nachdem sie angezeigt wurde
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aktives Profil anzeigen (zuletzt geladen/gespeichert; Stern = danach geändert)
|
// Aktives Profil anzeigen (zuletzt geladen/gespeichert; Stern = danach geändert).
|
||||||
|
// Im status-message-Stil der Seite: zentriert, max-width 600px - fuegt sich damit in
|
||||||
|
// das Karten-Layout ein statt linksbuendig aus der Reihe zu tanzen.
|
||||||
{
|
{
|
||||||
String activeDiv = "<div style=\"margin-bottom:12px;opacity:0.85;\">Aktives Profil: ";
|
String activeDiv = "<div class=\"status-message\" style=\"background-color: rgba(255,255,255,0.06); border-color: rgba(255,255,255,0.15);\">Aktives Profil: <b>";
|
||||||
if (activeProfileName.length() > 0) {
|
if (activeProfileName.length() > 0) {
|
||||||
activeDiv += htmlEscape(activeProfileName);
|
activeDiv += htmlEscape(activeProfileName);
|
||||||
|
activeDiv += "</b>";
|
||||||
if (activeProfileDirty) { activeDiv += " ✱ (geändert)"; }
|
if (activeProfileDirty) { activeDiv += " ✱ (geändert)"; }
|
||||||
} else {
|
} else {
|
||||||
activeDiv += "—";
|
activeDiv += "—</b>";
|
||||||
}
|
}
|
||||||
activeDiv += "</div>";
|
activeDiv += "</div>";
|
||||||
response->print(activeDiv);
|
response->print(activeDiv);
|
||||||
@@ -14862,7 +14984,7 @@ bool executeDashboardAction(const String& action, const String& value,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pidNeedsUpdate) {
|
if (pidNeedsUpdate) {
|
||||||
pidWasser.SetTunings(KpWasser, KiWasser, KdWasser);
|
applyPidWasserTunings();
|
||||||
pidDampf.SetTunings(KpDampf, KiDampf, KdDampf);
|
pidDampf.SetTunings(KpDampf, KiDampf, KdDampf);
|
||||||
// Serial.println("PID-Parameter nach Dashboard-Aktion aktualisiert.");
|
// Serial.println("PID-Parameter nach Dashboard-Aktion aktualisiert.");
|
||||||
}
|
}
|
||||||
@@ -15555,7 +15677,7 @@ static void buildTouchUartStateJson(char *buffer, size_t bufferSize) {
|
|||||||
offset += snprintf(buffer + offset, bufferSize - offset, "\"xSwitchLongAction\":%u,", (unsigned int)xSwitchLongAction);
|
offset += snprintf(buffer + offset, bufferSize - offset, "\"xSwitchLongAction\":%u,", (unsigned int)xSwitchLongAction);
|
||||||
|
|
||||||
// --- v2: Heizleistung (Duty in %) aus PID-Output / Fenstergroesse ---
|
// --- v2: Heizleistung (Duty in %) aus PID-Output / Fenstergroesse ---
|
||||||
double dutyW = (windowSizeWasser > 0) ? (OutputWasser / (double)windowSizeWasser) * 100.0 : 0.0;
|
double dutyW = (windowSizeWasser > 0) ? ((double)effectiveOutputWasserMs() / (double)windowSizeWasser) * 100.0 : 0.0; // inkl. Feed-Forward-Boost
|
||||||
double dutyD = (windowSizeDampf > 0) ? (OutputDampf / (double)windowSizeDampf) * 100.0 : 0.0;
|
double dutyD = (windowSizeDampf > 0) ? (OutputDampf / (double)windowSizeDampf) * 100.0 : 0.0;
|
||||||
dtostrf(dutyW, 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"dutyW\":%s,", floatBuf);
|
dtostrf(dutyW, 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"dutyW\":%s,", floatBuf);
|
||||||
dtostrf(dutyD, 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"dutyD\":%s,", floatBuf);
|
dtostrf(dutyD, 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"dutyD\":%s,", floatBuf);
|
||||||
@@ -16397,7 +16519,7 @@ static bool touchUartHandleCommandLine(const String& line) {
|
|||||||
}
|
}
|
||||||
EEPROM.commit();
|
EEPROM.commit();
|
||||||
markProfileDirty(); // PID-Werte sind Profilfelder
|
markProfileDirty(); // PID-Werte sind Profilfelder
|
||||||
if (changedW) { pidWasser.SetTunings(KpWasser, KiWasser, KdWasser); }
|
if (changedW) { applyPidWasserTunings(); }
|
||||||
if (changedD) { pidDampf.SetTunings(KpDampf, KiDampf, KdDampf); }
|
if (changedD) { pidDampf.SetTunings(KpDampf, KiDampf, KdDampf); }
|
||||||
touchUartSendAck(id, true, "PID gespeichert");
|
touchUartSendAck(id, true, "PID gespeichert");
|
||||||
touchUartSendState();
|
touchUartSendState();
|
||||||
|
|||||||
Reference in New Issue
Block a user