Merge pull request 'feat(S3): Aktives Profil führen und anzeigen — Grundlage für Profil-Schnellwahl am Display' (#7) from feat/aktives-profil-anzeige-s3 into main
This commit was merged in pull request #7.
This commit is contained in:
@@ -1,3 +1,15 @@
|
|||||||
|
Version 5.1.1:
|
||||||
|
- 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
|
||||||
|
Profil-Auswahl sowie auf der Profil-Seite (aktives Profil zusätzlich mit grünem Punkt in der Liste).
|
||||||
|
- „Geändert"-Erkennung: Werden nach dem Laden profil-relevante Einstellungen geändert (Solltemperaturen,
|
||||||
|
PID-Werte inkl. AutoTune-Ergebnis und AutoTune-Parameter, Boost, Eco, Dampfverzögerung, Fast-Heat-Up,
|
||||||
|
Brew-Control, Piezo), erscheint hinter dem Profilnamen „✱ (geändert)". Reine Anzeige-/Systemeinstellungen
|
||||||
|
(Anzeigemodus, Aufheizzeit, Licht, FlowGuard, Waage/Sensoren) lösen das bewusst nicht aus.
|
||||||
|
- Profil löschen setzt die Anzeige zurück, wenn das gelöschte Profil das aktive war; Werksreset ebenso.
|
||||||
|
- Neue additive State-Felder "profile" und "profDirty" im UART-JSON für das Touch-Display (Protokoll bleibt
|
||||||
|
kompatibel, ältere Display-Firmware ignoriert die Felder) - Grundlage für die Profil-Schnellwahl am Display.
|
||||||
|
|
||||||
Version 5.1.0:
|
Version 5.1.0:
|
||||||
- Display-Auswahl in die Web-UI (/Sensoren) verlegt: Ohne Display / OLED (SH1106, I2C) / UART-Touch-Display.
|
- Display-Auswahl in die Web-UI (/Sensoren) verlegt: Ohne Display / OLED (SH1106, I2C) / UART-Touch-Display.
|
||||||
Der Compile-Schalter ENABLE_DISPLAY entfällt, die Einstellung wird im EEPROM gespeichert und ohne Neustart
|
Der Compile-Schalter ENABLE_DISPLAY entfällt, die Einstellung wird im EEPROM gespeichert und ohne Neustart
|
||||||
|
|||||||
+146
-7
@@ -369,7 +369,7 @@ Adafruit_SH1106G display = Adafruit_SH1106G(128, 64, &Wire);
|
|||||||
* Firmware-Informationen
|
* Firmware-Informationen
|
||||||
************************************************************************************/
|
************************************************************************************/
|
||||||
|
|
||||||
String version = "5.1.0";
|
String version = "5.1.1";
|
||||||
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>";
|
||||||
@@ -942,7 +942,9 @@ const int EEPROM_ADDR_BACKLIGHT_ACTIVE_PERCENT = 908; // 1 Byte (uint8_t)
|
|||||||
const int EEPROM_ADDR_BACKLIGHT_STANDBY_CLOCK_PERCENT = 909; // 1 Byte (uint8_t) -> Ende 909
|
const int EEPROM_ADDR_BACKLIGHT_STANDBY_CLOCK_PERCENT = 909; // 1 Byte (uint8_t) -> Ende 909
|
||||||
const int EEPROM_ADDR_POWERON_STANDBY = 910; // 1 Byte (bool) -> Ende 910
|
const int EEPROM_ADDR_POWERON_STANDBY = 910; // 1 Byte (bool) -> Ende 910
|
||||||
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
|
||||||
// Naechste freie Adresse: 912 (Innerhalb EEPROM_SIZE=1024)
|
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
|
||||||
|
// Naechste freie Adresse: 944 (Innerhalb EEPROM_SIZE=1024)
|
||||||
|
|
||||||
/************************************************************************************
|
/************************************************************************************
|
||||||
* Eco-Mode Variablen
|
* Eco-Mode Variablen
|
||||||
@@ -1312,6 +1314,45 @@ char hostname[33] = "Dual-PID"; // Max 32 Zeichen + Nullterminator
|
|||||||
String profileStatusMessage = ""; // für Nachrichten auf der Profil-Seite
|
String profileStatusMessage = ""; // für Nachrichten auf der Profil-Seite
|
||||||
String standbyTimerStatusMessage = ""; // fuer Nachrichten auf der Timer-Seite
|
String standbyTimerStatusMessage = ""; // fuer Nachrichten auf der Timer-Seite
|
||||||
|
|
||||||
|
// Zuletzt geladenes/gespeichertes Profil (leer = keines). Wird im EEPROM gehalten,
|
||||||
|
// damit die Anzeige einen Neustart überlebt. activeProfileDirty markiert, dass nach
|
||||||
|
// dem Laden profil-relevante Einstellungen geändert wurden (Anzeige "geändert").
|
||||||
|
String activeProfileName = "";
|
||||||
|
bool activeProfileDirty = false;
|
||||||
|
|
||||||
|
// Aktives Profil + Dirty-Flag ins EEPROM schreiben (char[31], nullterminiert)
|
||||||
|
void persistActiveProfile() {
|
||||||
|
char nameBuf[31];
|
||||||
|
memset(nameBuf, 0, sizeof(nameBuf));
|
||||||
|
strncpy(nameBuf, activeProfileName.c_str(), sizeof(nameBuf) - 1);
|
||||||
|
for (size_t i = 0; i < sizeof(nameBuf); i++) {
|
||||||
|
EEPROM.write(EEPROM_ADDR_ACTIVE_PROFILE_NAME + i, (uint8_t)nameBuf[i]);
|
||||||
|
}
|
||||||
|
EEPROM.write(EEPROM_ADDR_ACTIVE_PROFILE_DIRTY, activeProfileDirty ? 1 : 0);
|
||||||
|
EEPROM.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setActiveProfile(const String& name) {
|
||||||
|
activeProfileName = name;
|
||||||
|
activeProfileDirty = false;
|
||||||
|
persistActiveProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearActiveProfile() {
|
||||||
|
activeProfileName = "";
|
||||||
|
activeProfileDirty = false;
|
||||||
|
persistActiveProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nach einer profil-relevanten Einstellungsänderung aufrufen: markiert das aktive
|
||||||
|
// Profil als "geändert". Ohne aktives Profil oder bei bereits gesetztem Flag ein No-Op.
|
||||||
|
void markProfileDirty() {
|
||||||
|
if (activeProfileName.length() == 0 || activeProfileDirty) return;
|
||||||
|
activeProfileDirty = true;
|
||||||
|
EEPROM.write(EEPROM_ADDR_ACTIVE_PROFILE_DIRTY, 1);
|
||||||
|
EEPROM.commit();
|
||||||
|
}
|
||||||
|
|
||||||
/************************************************************************************
|
/************************************************************************************
|
||||||
* WiFi Signal Bitmap (Display-Anzeige)
|
* WiFi Signal Bitmap (Display-Anzeige)
|
||||||
************************************************************************************/
|
************************************************************************************/
|
||||||
@@ -2925,6 +2966,9 @@ void resetToDefaults() {
|
|||||||
// Alle Änderungen ins EEPROM schreiben
|
// Alle Änderungen ins EEPROM schreiben
|
||||||
EEPROM.commit();
|
EEPROM.commit();
|
||||||
|
|
||||||
|
// Werksreset -> kein Profil mehr aktiv
|
||||||
|
clearActiveProfile();
|
||||||
|
|
||||||
// WICHTIG: Globale Variablen auch auf Default setzen
|
// WICHTIG: Globale Variablen auch auf Default setzen
|
||||||
SetpointWasser = defaultSetpointWasser; SetpointDampf = defaultSetpointDampf;
|
SetpointWasser = defaultSetpointWasser; SetpointDampf = defaultSetpointDampf;
|
||||||
OffsetWasser = defaultOffsetWasser; OffsetDampf = defaultOffsetDampf;
|
OffsetWasser = defaultOffsetWasser; OffsetDampf = defaultOffsetDampf;
|
||||||
@@ -4264,6 +4308,28 @@ void setup() {
|
|||||||
storedDisplayType = DEFAULT_DISPLAY_TYPE; // u. a. unprogrammierte 0xFF
|
storedDisplayType = DEFAULT_DISPLAY_TYPE; // u. a. unprogrammierte 0xFF
|
||||||
}
|
}
|
||||||
displayType = storedDisplayType;
|
displayType = storedDisplayType;
|
||||||
|
// Zuletzt geladenes Profil aus dem EEPROM holen. Unprogrammierter Bereich (0xFF)
|
||||||
|
// oder nicht druckbare Zeichen -> kein aktives Profil.
|
||||||
|
{
|
||||||
|
char nameBuf[31];
|
||||||
|
for (size_t i = 0; i < sizeof(nameBuf); i++) {
|
||||||
|
nameBuf[i] = (char)EEPROM.read(EEPROM_ADDR_ACTIVE_PROFILE_NAME + i);
|
||||||
|
}
|
||||||
|
nameBuf[sizeof(nameBuf) - 1] = '\0';
|
||||||
|
bool valid = true;
|
||||||
|
for (size_t i = 0; nameBuf[i] != '\0'; i++) {
|
||||||
|
// Steuerzeichen oder 0xFF (unprogrammiertes EEPROM) -> ungueltig.
|
||||||
|
// Bytes >= 0x80 bleiben erlaubt (UTF-8-Umlaute im Anzeigenamen).
|
||||||
|
if ((uint8_t)nameBuf[i] < 0x20 || (uint8_t)nameBuf[i] == 0xFF) { valid = false; break; }
|
||||||
|
}
|
||||||
|
if (valid && nameBuf[0] != '\0') {
|
||||||
|
activeProfileName = String(nameBuf);
|
||||||
|
activeProfileDirty = (EEPROM.read(EEPROM_ADDR_ACTIVE_PROFILE_DIRTY) == 1);
|
||||||
|
} else {
|
||||||
|
activeProfileName = "";
|
||||||
|
activeProfileDirty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
uint8_t storedScaleEnabled = 0;
|
uint8_t storedScaleEnabled = 0;
|
||||||
uint8_t storedScaleType = defaultScaleType;
|
uint8_t storedScaleType = defaultScaleType;
|
||||||
EEPROM.get(EEPROM_ADDR_SCALE_ENABLED, storedScaleEnabled);
|
EEPROM.get(EEPROM_ADDR_SCALE_ENABLED, storedScaleEnabled);
|
||||||
@@ -6353,6 +6419,7 @@ void handleAutoTune(AsyncWebServerRequest *request) {
|
|||||||
autoTuneWasserResultKi = (float)KiWasser;
|
autoTuneWasserResultKi = (float)KiWasser;
|
||||||
autoTuneWasserResultKd = (float)KdWasser;
|
autoTuneWasserResultKd = (float)KdWasser;
|
||||||
autoTuneWasserStatus = (peaks > 9) ? AT_FAILSAFE_PEAKS : AT_SUCCESS;
|
autoTuneWasserStatus = (peaks > 9) ? AT_FAILSAFE_PEAKS : AT_SUCCESS;
|
||||||
|
markProfileDirty(); // PID-Werte weichen jetzt vom geladenen Profil ab
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serial.println("AutoTune Wasser erfolgreich abgeschlossen.");
|
// Serial.println("AutoTune Wasser erfolgreich abgeschlossen.");
|
||||||
@@ -6411,6 +6478,7 @@ void handleAutoTune(AsyncWebServerRequest *request) {
|
|||||||
autoTuneDampfResultKi = (float)KiDampf;
|
autoTuneDampfResultKi = (float)KiDampf;
|
||||||
autoTuneDampfResultKd = (float)KdDampf;
|
autoTuneDampfResultKd = (float)KdDampf;
|
||||||
autoTuneDampfStatus = (peaks > 9) ? AT_FAILSAFE_PEAKS : AT_SUCCESS;
|
autoTuneDampfStatus = (peaks > 9) ? AT_FAILSAFE_PEAKS : AT_SUCCESS;
|
||||||
|
markProfileDirty(); // PID-Werte weichen jetzt vom geladenen Profil ab
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serial.println("AutoTune Dampf erfolgreich abgeschlossen.");
|
// Serial.println("AutoTune Dampf erfolgreich abgeschlossen.");
|
||||||
@@ -6707,7 +6775,6 @@ bool applyBrewControlUpdate(const BrewControlUpdate& update) {
|
|||||||
if (update.hasFlowGuardEnabled && update.flowGuardEnabled != flowGuardEnabled) {
|
if (update.hasFlowGuardEnabled && update.flowGuardEnabled != flowGuardEnabled) {
|
||||||
flowGuardEnabled = update.flowGuardEnabled;
|
flowGuardEnabled = update.flowGuardEnabled;
|
||||||
EEPROM.put(EEPROM_ADDR_FLOWGUARD_ENABLED, flowGuardEnabled);
|
EEPROM.put(EEPROM_ADDR_FLOWGUARD_ENABLED, flowGuardEnabled);
|
||||||
changed = true;
|
|
||||||
flowGuardConfigChanged = true;
|
flowGuardConfigChanged = true;
|
||||||
}
|
}
|
||||||
if (update.hasFlowGuardMinSecs) {
|
if (update.hasFlowGuardMinSecs) {
|
||||||
@@ -6715,7 +6782,6 @@ bool applyBrewControlUpdate(const BrewControlUpdate& update) {
|
|||||||
if (newFlowGuardMinSecs >= FLOW_GUARD_MIN_SECONDS_MIN && newFlowGuardMinSecs <= FLOW_GUARD_MIN_SECONDS_MAX && fabs(newFlowGuardMinSecs - flowGuardMinBrewSeconds) > 0.01f) {
|
if (newFlowGuardMinSecs >= FLOW_GUARD_MIN_SECONDS_MIN && newFlowGuardMinSecs <= FLOW_GUARD_MIN_SECONDS_MAX && fabs(newFlowGuardMinSecs - flowGuardMinBrewSeconds) > 0.01f) {
|
||||||
flowGuardMinBrewSeconds = newFlowGuardMinSecs;
|
flowGuardMinBrewSeconds = newFlowGuardMinSecs;
|
||||||
EEPROM.put(EEPROM_ADDR_FLOWGUARD_MIN_SECONDS, flowGuardMinBrewSeconds);
|
EEPROM.put(EEPROM_ADDR_FLOWGUARD_MIN_SECONDS, flowGuardMinBrewSeconds);
|
||||||
changed = true;
|
|
||||||
flowGuardConfigChanged = true;
|
flowGuardConfigChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6724,7 +6790,6 @@ bool applyBrewControlUpdate(const BrewControlUpdate& update) {
|
|||||||
if (newPulseMs >= FLOW_GUARD_PULSE_PERIOD_MIN_MS && newPulseMs <= FLOW_GUARD_PULSE_PERIOD_MAX_MS && newPulseMs != flowGuardPulsePeriodMs) {
|
if (newPulseMs >= FLOW_GUARD_PULSE_PERIOD_MIN_MS && newPulseMs <= FLOW_GUARD_PULSE_PERIOD_MAX_MS && newPulseMs != flowGuardPulsePeriodMs) {
|
||||||
flowGuardPulsePeriodMs = newPulseMs;
|
flowGuardPulsePeriodMs = newPulseMs;
|
||||||
EEPROM.put(EEPROM_ADDR_FLOWGUARD_PULSE_PERIOD_MS, flowGuardPulsePeriodMs);
|
EEPROM.put(EEPROM_ADDR_FLOWGUARD_PULSE_PERIOD_MS, flowGuardPulsePeriodMs);
|
||||||
changed = true;
|
|
||||||
flowGuardConfigChanged = true;
|
flowGuardConfigChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6733,7 +6798,6 @@ bool applyBrewControlUpdate(const BrewControlUpdate& update) {
|
|||||||
if (newFlowGuardMinDuty >= FLOW_GUARD_MIN_DUTY_MIN && newFlowGuardMinDuty <= FLOW_GUARD_MIN_DUTY_MAX && fabs(newFlowGuardMinDuty - flowGuardMinDutyPercent) > 0.01f) {
|
if (newFlowGuardMinDuty >= FLOW_GUARD_MIN_DUTY_MIN && newFlowGuardMinDuty <= FLOW_GUARD_MIN_DUTY_MAX && fabs(newFlowGuardMinDuty - flowGuardMinDutyPercent) > 0.01f) {
|
||||||
flowGuardMinDutyPercent = newFlowGuardMinDuty;
|
flowGuardMinDutyPercent = newFlowGuardMinDuty;
|
||||||
EEPROM.put(EEPROM_ADDR_FLOWGUARD_MIN_DUTY, flowGuardMinDutyPercent);
|
EEPROM.put(EEPROM_ADDR_FLOWGUARD_MIN_DUTY, flowGuardMinDutyPercent);
|
||||||
changed = true;
|
|
||||||
flowGuardConfigChanged = true;
|
flowGuardConfigChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6778,10 +6842,16 @@ bool applyBrewControlUpdate(const BrewControlUpdate& update) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "changed" umfasst hier nur noch profil-relevante Felder (BBT/BBW/PI/SBT);
|
||||||
|
// FlowGuard ist kein Bestandteil von TemperatureProfile und laeuft ueber
|
||||||
|
// flowGuardConfigChanged separat -> markProfileDirty() bleibt praezise.
|
||||||
if (changed) {
|
if (changed) {
|
||||||
|
markProfileDirty();
|
||||||
|
}
|
||||||
|
if (changed || flowGuardConfigChanged) {
|
||||||
EEPROM.commit();
|
EEPROM.commit();
|
||||||
}
|
}
|
||||||
return changed;
|
return changed || flowGuardConfigChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleSaveBrewControl(AsyncWebServerRequest *request) {
|
void handleSaveBrewControl(AsyncWebServerRequest *request) {
|
||||||
@@ -8507,6 +8577,7 @@ void handleFastHeatUpSettings(AsyncWebServerRequest *request) {
|
|||||||
|
|
||||||
EEPROM.put(EEPROM_ADDR_FASTHEATUP_DATA, fastHeatUpAktiv);
|
EEPROM.put(EEPROM_ADDR_FASTHEATUP_DATA, fastHeatUpAktiv);
|
||||||
EEPROM.commit();
|
EEPROM.commit();
|
||||||
|
markProfileDirty(); // fastHeatUpAktiv ist Profilfeld
|
||||||
|
|
||||||
AsyncWebServerResponse *resp = request->beginResponse(303);
|
AsyncWebServerResponse *resp = request->beginResponse(303);
|
||||||
resp->addHeader(F("Location"), F("/Fast-Heat-Up"));
|
resp->addHeader(F("Location"), F("/Fast-Heat-Up"));
|
||||||
@@ -8999,6 +9070,7 @@ void handleSettingsUpdate(AsyncWebServerRequest *request) {
|
|||||||
// --- EEPROM speichern und PID aktualisieren ---
|
// --- EEPROM speichern und PID aktualisieren ---
|
||||||
if (valueChanged) { // Nur speichern, wenn sich mindestens ein Wert geändert hat
|
if (valueChanged) { // Nur speichern, wenn sich mindestens ein Wert geändert hat
|
||||||
EEPROM.commit();
|
EEPROM.commit();
|
||||||
|
markProfileDirty(); // Setpoints/Offsets/PID/Boost/MaxTemp/Window sind Profilfelder
|
||||||
}
|
}
|
||||||
|
|
||||||
// PID-Parameter an die Regler übergeben
|
// PID-Parameter an die Regler übergeben
|
||||||
@@ -9386,6 +9458,7 @@ void handleUpdateAutotuneSettings(AsyncWebServerRequest *request) {
|
|||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
EEPROM.commit();
|
EEPROM.commit();
|
||||||
|
markProfileDirty(); // AutoTune-Parameter sind Profilfelder
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *response = request->beginResponse(303);
|
AsyncWebServerResponse *response = request->beginResponse(303);
|
||||||
@@ -9733,6 +9806,8 @@ void handleEcoUpdate(AsyncWebServerRequest *request) {
|
|||||||
EEPROM.put(EEPROM_ADDR_BACKLIGHT_ACTIVE_PERCENT, backlightActivePercent);
|
EEPROM.put(EEPROM_ADDR_BACKLIGHT_ACTIVE_PERCENT, backlightActivePercent);
|
||||||
EEPROM.put(EEPROM_ADDR_BACKLIGHT_STANDBY_CLOCK_PERCENT, backlightStandbyClockPercent);
|
EEPROM.put(EEPROM_ADDR_BACKLIGHT_STANDBY_CLOCK_PERCENT, backlightStandbyClockPercent);
|
||||||
EEPROM.commit();
|
EEPROM.commit();
|
||||||
|
// Eco/Dampfverzoegerung/Wake-Verhalten sind Profilfelder -> Profil als geaendert markieren
|
||||||
|
markProfileDirty();
|
||||||
|
|
||||||
// Wenn Eco deaktiviert, Setpoints zurückladen
|
// Wenn Eco deaktiviert, Setpoints zurückladen
|
||||||
if (ecoModeMinutes == 0 && !ecoSwitchActive) {
|
if (ecoModeMinutes == 0 && !ecoSwitchActive) {
|
||||||
@@ -9796,6 +9871,7 @@ void handleUpdatePiezoSettings(AsyncWebServerRequest *request) {
|
|||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
EEPROM.commit();
|
EEPROM.commit();
|
||||||
|
markProfileDirty(); // piezoEnabled ist Teil des Profils
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse *resp = request->beginResponse(303);
|
AsyncWebServerResponse *resp = request->beginResponse(303);
|
||||||
@@ -11271,6 +11347,8 @@ bool saveProfile(const String& profileName, const TemperatureProfile& profileDat
|
|||||||
|
|
||||||
if (bytesWritten == sizeof(TemperatureProfile)) {
|
if (bytesWritten == sizeof(TemperatureProfile)) {
|
||||||
// Serial.println(" Profile saved successfully (Binary Format).");
|
// Serial.println(" Profile saved successfully (Binary Format).");
|
||||||
|
// Gespeicherter Ist-Zustand entspricht ab jetzt diesem Profil -> als aktiv merken
|
||||||
|
setActiveProfile(displayName);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
// Serial.printf(" ERROR: Failed to write complete profile data! Bytes written: %d, Expected: %d\n", bytesWritten, sizeof(TemperatureProfile));
|
// Serial.printf(" ERROR: Failed to write complete profile data! Bytes written: %d, Expected: %d\n", bytesWritten, sizeof(TemperatureProfile));
|
||||||
@@ -11418,6 +11496,9 @@ void applyProfileSettings(const TemperatureProfile& profileData) {
|
|||||||
// Eco-Status zurücksetzen
|
// Eco-Status zurücksetzen
|
||||||
ecoModeAktiv = false; ecoModeActivatedTime = 0;
|
ecoModeAktiv = false; ecoModeActivatedTime = 0;
|
||||||
// Serial.println(" Eco mode status reset.");
|
// Serial.println(" Eco mode status reset.");
|
||||||
|
|
||||||
|
// Erfolgreich angewendet -> als aktives Profil merken (setzt Dirty-Flag zurück)
|
||||||
|
setActiveProfile(normalizeProfileDisplayName(String(profileData.profileName)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -11480,6 +11561,11 @@ bool deleteProfile(const String& profileName) {
|
|||||||
if (LittleFS.exists(filePath)) {
|
if (LittleFS.exists(filePath)) {
|
||||||
if (LittleFS.remove(filePath)) {
|
if (LittleFS.remove(filePath)) {
|
||||||
// Serial.println(" Profile deleted successfully.");
|
// Serial.println(" Profile deleted successfully.");
|
||||||
|
// War das geloeschte Profil das aktive -> Anzeige zuruecksetzen
|
||||||
|
if (activeProfileName.length() > 0 &&
|
||||||
|
sanitizeProfileName(activeProfileName) == sanitizedName) {
|
||||||
|
clearActiveProfile();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
// Serial.println(" ERROR: Failed to delete profile file.");
|
// Serial.println(" ERROR: Failed to delete profile file.");
|
||||||
@@ -11745,6 +11831,19 @@ 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)
|
||||||
|
{
|
||||||
|
String activeDiv = "<div style=\"margin-bottom:12px;opacity:0.85;\">Aktives Profil: ";
|
||||||
|
if (activeProfileName.length() > 0) {
|
||||||
|
activeDiv += htmlEscape(activeProfileName);
|
||||||
|
if (activeProfileDirty) { activeDiv += " ✱ (geändert)"; }
|
||||||
|
} else {
|
||||||
|
activeDiv += "—";
|
||||||
|
}
|
||||||
|
activeDiv += "</div>";
|
||||||
|
response->print(activeDiv);
|
||||||
|
}
|
||||||
|
|
||||||
// Profilliste generieren
|
// Profilliste generieren
|
||||||
response->print(FPSTR(profilesPageListStart)); // <ul>
|
response->print(FPSTR(profilesPageListStart)); // <ul>
|
||||||
if (profiles.empty()) {
|
if (profiles.empty()) {
|
||||||
@@ -11756,6 +11855,9 @@ void handleProfilesPage(AsyncWebServerRequest *request) {
|
|||||||
String escapedConfirmJs = htmlEscape(confirmJs);
|
String escapedConfirmJs = htmlEscape(confirmJs);
|
||||||
String listItem = FPSTR(profilesPageListItemStart);
|
String listItem = FPSTR(profilesPageListItemStart);
|
||||||
listItem += escapedName;
|
listItem += escapedName;
|
||||||
|
if (name == activeProfileName) {
|
||||||
|
listItem += F(" <span style=\"color:#4CAF50;\" title=\"aktives Profil\">●</span>");
|
||||||
|
}
|
||||||
listItem += F("</span><div class=\"profile-actions\">");
|
listItem += F("</span><div class=\"profile-actions\">");
|
||||||
listItem += F("<form action=\"/loadProfile\" method=\"POST\" class=\"profiles-form\"><input type=\"hidden\" name=\"profileName\" value=\"");
|
listItem += F("<form action=\"/loadProfile\" method=\"POST\" class=\"profiles-form\"><input type=\"hidden\" name=\"profileName\" value=\"");
|
||||||
listItem += escapedName;
|
listItem += escapedName;
|
||||||
@@ -13559,6 +13661,7 @@ static const char dashboardControlPanelHTML[] PROGMEM = R"rawliteral(
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Profile</h3>
|
<h3>Profile</h3>
|
||||||
|
<div id="active_profile_line" style="margin-bottom:8px;opacity:0.85;">Aktives Profil: <span id="active_profile_name">—</span></div>
|
||||||
<select id="profile_select">
|
<select id="profile_select">
|
||||||
<option value="">Profil wählen...</option>
|
<option value="">Profil wählen...</option>
|
||||||
{PROFILE_OPTIONS} </select>
|
{PROFILE_OPTIONS} </select>
|
||||||
@@ -13866,6 +13969,16 @@ static const char dashboardJavaScript[] PROGMEM = R"rawliteral(
|
|||||||
// Status Text
|
// Status Text
|
||||||
if (statusSpan) { statusSpan.textContent = data.statusText || 'Unbekannt'; statusSpan.classList.toggle('error', data.errorW || data.errorD || data.safetyW || data.safetyD); }
|
if (statusSpan) { statusSpan.textContent = data.statusText || 'Unbekannt'; statusSpan.classList.toggle('error', data.errorW || data.errorD || data.safetyW || data.safetyD); }
|
||||||
|
|
||||||
|
// Aktives Profil (zuletzt geladen/gespeichert; Stern = danach geänderte Einstellungen)
|
||||||
|
const activeProfileEl = document.getElementById('active_profile_name');
|
||||||
|
if (activeProfileEl) {
|
||||||
|
if (data.profile && data.profile.length > 0) {
|
||||||
|
activeProfileEl.textContent = data.profile + (data.profDirty ? ' ✱ (geändert)' : '');
|
||||||
|
} else {
|
||||||
|
activeProfileEl.textContent = '—';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (caseTempDisplay) {
|
if (caseTempDisplay) {
|
||||||
const showCaseTemp = (data.caseTempDashboard === true);
|
const showCaseTemp = (data.caseTempDashboard === true);
|
||||||
caseTempDisplay.style.display = showCaseTemp ? 'block' : 'none';
|
caseTempDisplay.style.display = showCaseTemp ? 'block' : 'none';
|
||||||
@@ -14739,6 +14852,14 @@ bool executeDashboardAction(const String& action, const String& value,
|
|||||||
// --- Abschlussaktionen ---
|
// --- Abschlussaktionen ---
|
||||||
if (settingsChanged) {
|
if (settingsChanged) {
|
||||||
EEPROM.commit();
|
EEPROM.commit();
|
||||||
|
// Profil-relevante Aenderung (Feld ist Teil von TemperatureProfile) -> aktives
|
||||||
|
// Profil als "geaendert" markieren. Reine Anzeige-/Systemeinstellungen
|
||||||
|
// (setTempDisplayMode, setHeatUpMinutes, toggleLight) zaehlen nicht.
|
||||||
|
if (action == "setTempW" || action == "setTempD" ||
|
||||||
|
action == "toggleBoostW" || action == "toggleBoostD" ||
|
||||||
|
action == "togglePI" || action == "toggleBBT" || action == "toggleBBW") {
|
||||||
|
markProfileDirty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (pidNeedsUpdate) {
|
if (pidNeedsUpdate) {
|
||||||
pidWasser.SetTunings(KpWasser, KiWasser, KdWasser);
|
pidWasser.SetTunings(KpWasser, KiWasser, KdWasser);
|
||||||
@@ -15031,6 +15152,15 @@ void buildDashboardJson(char *buffer, size_t bufferSize) {
|
|||||||
offset += snprintf(buffer + offset, bufferSize - offset, "\"statusText\":\"%s\",", statusText.c_str());
|
offset += snprintf(buffer + offset, bufferSize - offset, "\"statusText\":\"%s\",", statusText.c_str());
|
||||||
offset += snprintf(buffer + offset, bufferSize - offset, "\"statusKey\":\"%s\",", statusKey.c_str());
|
offset += snprintf(buffer + offset, bufferSize - offset, "\"statusKey\":\"%s\",", statusKey.c_str());
|
||||||
|
|
||||||
|
// Zuletzt geladenes Profil (leer = keines) + "geaendert"-Flag
|
||||||
|
{
|
||||||
|
String escapedProfile = activeProfileName;
|
||||||
|
escapedProfile.replace("\\", "\\\\");
|
||||||
|
escapedProfile.replace("\"", "\\\"");
|
||||||
|
offset += snprintf(buffer + offset, bufferSize - offset, "\"profile\":\"%s\",", escapedProfile.c_str());
|
||||||
|
offset += snprintf(buffer + offset, bufferSize - offset, "\"profDirty\":%s,", activeProfileDirty ? "true" : "false");
|
||||||
|
}
|
||||||
|
|
||||||
bool caseTempDashboardActive = (caseTempOnDashboard && caseSensorEnabled);
|
bool caseTempDashboardActive = (caseTempOnDashboard && caseSensorEnabled);
|
||||||
offset += snprintf(buffer + offset, bufferSize - offset, "\"caseTempDashboard\":%s,", caseTempDashboardActive ? "true" : "false");
|
offset += snprintf(buffer + offset, bufferSize - offset, "\"caseTempDashboard\":%s,", caseTempDashboardActive ? "true" : "false");
|
||||||
offset += snprintf(buffer + offset, bufferSize - offset, "\"caseType\":%u,", (unsigned int)caseSensorType);
|
offset += snprintf(buffer + offset, bufferSize - offset, "\"caseType\":%u,", (unsigned int)caseSensorType);
|
||||||
@@ -15323,6 +15453,14 @@ static void buildTouchUartStateJson(char *buffer, size_t bufferSize) {
|
|||||||
offset += snprintf(buffer + offset, bufferSize - offset, "\"steamHeatDisabled\":%s,", steamHeatDisabledByUser ? "true" : "false");
|
offset += snprintf(buffer + offset, bufferSize - offset, "\"steamHeatDisabled\":%s,", steamHeatDisabledByUser ? "true" : "false");
|
||||||
offset += snprintf(buffer + offset, bufferSize - offset, "\"lightOn\":%s,", lightOn ? "true" : "false");
|
offset += snprintf(buffer + offset, bufferSize - offset, "\"lightOn\":%s,", lightOn ? "true" : "false");
|
||||||
offset += snprintf(buffer + offset, bufferSize - offset, "\"piezoEnabled\":%s,", piezoEnabled ? "true" : "false");
|
offset += snprintf(buffer + offset, bufferSize - offset, "\"piezoEnabled\":%s,", piezoEnabled ? "true" : "false");
|
||||||
|
// Zuletzt geladenes Profil (leer = keines) + "geaendert"-Flag fuer die Anzeige am Display
|
||||||
|
{
|
||||||
|
String escapedProfile = activeProfileName;
|
||||||
|
escapedProfile.replace("\\", "\\\\");
|
||||||
|
escapedProfile.replace("\"", "\\\"");
|
||||||
|
offset += snprintf(buffer + offset, bufferSize - offset, "\"profile\":\"%s\",", escapedProfile.c_str());
|
||||||
|
offset += snprintf(buffer + offset, bufferSize - offset, "\"profDirty\":%s,", activeProfileDirty ? "true" : "false");
|
||||||
|
}
|
||||||
|
|
||||||
dtostrf(getWeightReadingForUi(), 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"weight\":%s,", floatBuf);
|
dtostrf(getWeightReadingForUi(), 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"weight\":%s,", floatBuf);
|
||||||
dtostrf(brewByWeightTargetGrams, 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"targetWeight\":%s,", floatBuf);
|
dtostrf(brewByWeightTargetGrams, 4, 1, floatBuf); offset += snprintf(buffer + offset, bufferSize - offset, "\"targetWeight\":%s,", floatBuf);
|
||||||
@@ -16258,6 +16396,7 @@ static bool touchUartHandleCommandLine(const String& line) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
EEPROM.commit();
|
EEPROM.commit();
|
||||||
|
markProfileDirty(); // PID-Werte sind Profilfelder
|
||||||
if (changedW) { pidWasser.SetTunings(KpWasser, KiWasser, KdWasser); }
|
if (changedW) { pidWasser.SetTunings(KpWasser, KiWasser, KdWasser); }
|
||||||
if (changedD) { pidDampf.SetTunings(KpDampf, KiDampf, KdDampf); }
|
if (changedD) { pidDampf.SetTunings(KpDampf, KiDampf, KdDampf); }
|
||||||
touchUartSendAck(id, true, "PID gespeichert");
|
touchUartSendAck(id, true, "PID gespeichert");
|
||||||
|
|||||||
Reference in New Issue
Block a user