From f6cfdf326368107fb942a035b37f2d1fd5e83b0d Mon Sep 17 00:00:00 2001 From: Yurii Date: Fri, 27 Jun 2025 00:28:38 +0300 Subject: [PATCH] feat: added freeze protection parameter for heating, removed forced start of heating in emergency mode #157 --- src/MainTask.h | 47 ++++++++++++++++++++++++++++++++++++ src/OpenThermTask.h | 2 +- src/Settings.h | 1 + src/strings.h | 1 + src/utils.h | 10 ++++++++ src_data/locales/en.json | 6 ++++- src_data/locales/it.json | 6 ++++- src_data/locales/ru.json | 6 ++++- src_data/pages/settings.html | 15 +++++++++--- 9 files changed, 87 insertions(+), 7 deletions(-) diff --git a/src/MainTask.h b/src/MainTask.h index 65aba81..c60c7b3 100644 --- a/src/MainTask.h +++ b/src/MainTask.h @@ -152,6 +152,7 @@ protected: } this->yield(); + this->heating(); this->emergency(); this->ledStatus(); this->cascadeControl(); @@ -228,6 +229,52 @@ protected: } } + void heating() { + // anti freeze protection + if (!settings.heating.enabled) { + float minTemp = 255.0f; + uint8_t availableSensors = 0; + + if (Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP)) { + auto value = Sensors::getMeanValueByPurpose(Sensors::Purpose::INDOOR_TEMP, Sensors::ValueType::PRIMARY); + if (value < minTemp) { + minTemp = value; + } + + availableSensors++; + } + + if (Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::HEATING_TEMP)) { + auto value = Sensors::getMeanValueByPurpose(Sensors::Purpose::HEATING_TEMP, Sensors::ValueType::PRIMARY); + if (value < minTemp) { + minTemp = value; + } + + availableSensors++; + } + + if (Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::HEATING_RETURN_TEMP)) { + auto value = Sensors::getMeanValueByPurpose(Sensors::Purpose::HEATING_RETURN_TEMP, Sensors::ValueType::PRIMARY); + if (value < minTemp) { + minTemp = value; + } + + availableSensors++; + } + + if (availableSensors && minTemp <= settings.heating.antiFreezeTemp) { + settings.heating.enabled = true; + fsSettings.update(); + + Log.sinfoln( + FPSTR(L_MAIN), + F("Heating turned on by anti freeze protection, current min temp: %.2f, threshold: %hhu"), + minTemp, settings.heating.antiFreezeTemp + ); + } + } + } + void emergency() { // flags uint8_t emergencyFlags = 0b00000000; diff --git a/src/OpenThermTask.h b/src/OpenThermTask.h index 801e6e7..e6fddae 100644 --- a/src/OpenThermTask.h +++ b/src/OpenThermTask.h @@ -169,7 +169,7 @@ protected: // Heating settings vars.master.heating.enabled = this->isReady() - && (settings.heating.enabled || vars.emergency.state) + && settings.heating.enabled && vars.cascadeControl.input && !vars.master.heating.blocking && !vars.master.heating.overheat; diff --git a/src/Settings.h b/src/Settings.h index 99732dc..634d714 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -110,6 +110,7 @@ struct Settings { uint8_t maxModulation = 100; uint8_t overheatHighTemp = 95; uint8_t overheatLowTemp = 90; + uint8_t antiFreezeTemp = 10; } heating; struct { diff --git a/src/strings.h b/src/strings.h index bf36cf5..6876f9b 100644 --- a/src/strings.h +++ b/src/strings.h @@ -44,6 +44,7 @@ const char S_APP_VERSION[] PROGMEM = "appVersion"; const char S_AUTH[] PROGMEM = "auth"; const char S_AUTO_DIAG_RESET[] PROGMEM = "autoDiagReset"; const char S_AUTO_FAULT_RESET[] PROGMEM = "autoFaultReset"; +const char S_ANTI_FREEZE_TEMP[] PROGMEM = "antiFreezeTemp"; const char S_BACKTRACE[] PROGMEM = "backtrace"; const char S_BATTERY[] PROGMEM = "battery"; const char S_BAUDRATE[] PROGMEM = "baudrate"; diff --git a/src/utils.h b/src/utils.h index e6f1b74..0835aba 100644 --- a/src/utils.h +++ b/src/utils.h @@ -497,6 +497,7 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) { heating[FPSTR(S_MAX_MODULATION)] = src.heating.maxModulation; heating[FPSTR(S_OVERHEAT_HIGH_TEMP)] = src.heating.overheatHighTemp; heating[FPSTR(S_OVERHEAT_LOW_TEMP)] = src.heating.overheatLowTemp; + heating[FPSTR(S_ANTI_FREEZE_TEMP)] = src.heating.antiFreezeTemp; auto dhw = dst[FPSTR(S_DHW)].to(); dhw[FPSTR(S_ENABLED)] = src.dhw.enabled; @@ -1369,6 +1370,15 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false changed = true; } + if (!src[FPSTR(S_HEATING)][FPSTR(S_ANTI_FREEZE_TEMP)].isNull()) { + unsigned short value = src[FPSTR(S_HEATING)][FPSTR(S_ANTI_FREEZE_TEMP)].as(); + + if (isValidTemp(value, dst.system.unitSystem, 1, 30) && value != dst.heating.antiFreezeTemp) { + dst.heating.antiFreezeTemp = value; + changed = true; + } + } + // dhw if (src[FPSTR(S_DHW)][FPSTR(S_ENABLED)].is()) { diff --git a/src_data/locales/en.json b/src_data/locales/en.json index d554dc8..b539321 100644 --- a/src_data/locales/en.json +++ b/src_data/locales/en.json @@ -350,7 +350,11 @@ "heating": { "hyst": "Hysteresis (in degrees)", - "turboFactor": "Turbo mode coeff." + "turboFactor": "Turbo mode coeff.", + "antiFreezeTemp": { + "title": "Freeze protection temperature", + "note": "If the heat carrier or indoor temperature drops below this value, the heating will be forced to turn on" + } }, "emergency": { diff --git a/src_data/locales/it.json b/src_data/locales/it.json index 312108b..5a33e07 100644 --- a/src_data/locales/it.json +++ b/src_data/locales/it.json @@ -350,7 +350,11 @@ "heating": { "hyst": "Isteresi (in gradi)", - "turboFactor": "Turbo mode coeff." + "turboFactor": "Turbo mode coeff.", + "antiFreezeTemp": { + "title": "Temperatura di protezione antigelo", + "note": "Se la temperatura del fluido termovettore o interna scende al di sotto di questo valore, il riscaldamento verrà forzato ad accendersi" + } }, "emergency": { diff --git a/src_data/locales/ru.json b/src_data/locales/ru.json index a338a0f..a7af639 100644 --- a/src_data/locales/ru.json +++ b/src_data/locales/ru.json @@ -350,7 +350,11 @@ "heating": { "hyst": "Гистерезис (в градусах)", - "turboFactor": "Коэфф. турбо режима" + "turboFactor": "Коэфф. турбо режима", + "antiFreezeTemp": { + "title": "Температура защиты от замерзания", + "note": "Отопление будет принудительно включено, если температура теплоносителя или внутренняя температура опустится ниже этого значения" + } }, "emergency": { diff --git a/src_data/pages/settings.html b/src_data/pages/settings.html index cbca6f2..37a596a 100644 --- a/src_data/pages/settings.html +++ b/src_data/pages/settings.html @@ -207,6 +207,12 @@ + +
settings.overheat.title @@ -926,6 +932,10 @@ setInputValue("[name='heating[maxModulation]']", data.heating.maxModulation); setInputValue("[name='heating[overheatHighTemp]']", data.heating.overheatHighTemp); setInputValue("[name='heating[overheatLowTemp]']", data.heating.overheatLowTemp); + setInputValue("[name='heating[antiFreezeTemp]']", data.heating.antiFreezeTemp, { + "min": data.system.unitSystem == 0 ? 1 : 34, + "max": data.system.unitSystem == 0 ? 30 : 86 + }); setBusy('#heating-settings-busy', '#heating-settings', false); // DHW @@ -943,11 +953,10 @@ setBusy('#dhw-settings-busy', '#dhw-settings', false); // Emergency mode - setInputValue("[name='emergency[tresholdTime]']", data.emergency.tresholdTime); if (data.opentherm.options.nativeHeatingControl) { setInputValue("[name='emergency[target]']", data.emergency.target, { "min": data.system.unitSystem == 0 ? 5 : 41, - "max": data.system.unitSystem == 0 ? 40 : 86 + "max": data.system.unitSystem == 0 ? 40 : 104 }); } else { @@ -956,7 +965,7 @@ "max": data.heating.maxTemp, }); } - + setInputValue("[name='emergency[tresholdTime]']", data.emergency.tresholdTime); setBusy('#emergency-settings-busy', '#emergency-settings', false); // Equitherm