From d16f56d2801987c0c178b6ebb5ecf3d4de71e8d1 Mon Sep 17 00:00:00 2001 From: Yurii Date: Fri, 23 May 2025 00:42:29 +0300 Subject: [PATCH 01/12] fix: build for ESP32 C6 --- platformio.ini | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/platformio.ini b/platformio.ini index f2e2727..69016fb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -291,6 +291,11 @@ framework = arduino, espidf platform_packages = ${esp32_defaults.platform_packages} board = esp32-c6-devkitm-1 board_build.partitions = ${esp32_defaults.board_build.partitions} +board_build.embed_txtfiles = + managed_components/espressif__esp_insights/server_certs/https_server.crt + managed_components/espressif__esp_rainmaker/server_certs/rmaker_mqtt_server.crt + managed_components/espressif__esp_rainmaker/server_certs/rmaker_claim_service_server.crt + managed_components/espressif__esp_rainmaker/server_certs/rmaker_ota_server.crt lib_deps = ${esp32_defaults.lib_deps} lib_ignore = ${esp32_defaults.lib_ignore} From 14b1eac7326597c76baac7e7d04c025c61aca10b Mon Sep 17 00:00:00 2001 From: Yurii Date: Thu, 5 Jun 2025 05:40:09 +0300 Subject: [PATCH 02/12] chore: bump version to 1.5.5 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 69016fb..b574a42 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,7 +14,7 @@ extra_configs = secrets.default.ini core_dir = .pio [env] -version = 1.5.4 +version = 1.5.5 framework = arduino lib_deps = bblanchon/ArduinoJson@^7.3.0 From ba03c9cda3b86f03581194c21698857210caf7d8 Mon Sep 17 00:00:00 2001 From: Yurii Date: Sun, 8 Jun 2025 03:10:10 +0300 Subject: [PATCH 03/12] fix: last sensor was ignored --- src/Sensors.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Sensors.h b/src/Sensors.h index 4937d79..7b0cc61 100644 --- a/src/Sensors.h +++ b/src/Sensors.h @@ -138,7 +138,7 @@ public: } uint8_t amount = 0; - for (uint8_t id = 0; id < getMaxSensorId(); id++) { + for (uint8_t id = 0; id <= getMaxSensorId(); id++) { if (settings[id].type == type && (!onlyEnabled || settings[id].enabled)) { amount++; } @@ -152,7 +152,7 @@ public: return 0; } - for (uint8_t id = 0; id < getMaxSensorId(); id++) { + for (uint8_t id = 0; id <= getMaxSensorId(); id++) { if (strcmp(settings[id].name, name) == 0) { return id; } @@ -167,7 +167,7 @@ public: } String refObjectId; - for (uint8_t id = 0; id < getMaxSensorId(); id++) { + for (uint8_t id = 0; id <= getMaxSensorId(); id++) { Sensors::makeObjectId(refObjectId, settings[id].name); if (refObjectId.equals(objectId)) { return id; @@ -247,7 +247,7 @@ public: uint8_t updated = 0; // read sensors data for current instance - for (uint8_t sensorId = 0; sensorId < getMaxSensorId(); sensorId++) { + for (uint8_t sensorId = 0; sensorId <= getMaxSensorId(); sensorId++) { auto& sSensor = settings[sensorId]; // only target & valid sensors @@ -311,7 +311,7 @@ public: uint8_t updated = 0; // read sensors data for current instance - for (uint8_t sensorId = 0; sensorId < getMaxSensorId(); sensorId++) { + for (uint8_t sensorId = 0; sensorId <= getMaxSensorId(); sensorId++) { auto& sSensor = settings[sensorId]; // only target & valid sensors @@ -340,7 +340,7 @@ public: float value = 0.0f; uint8_t amount = 0; - for (uint8_t id = 0; id < getMaxSensorId(); id++) { + for (uint8_t id = 0; id <= getMaxSensorId(); id++) { auto& sSensor = settings[id]; auto& rSensor = results[id]; @@ -366,7 +366,7 @@ public: return 0; } - for (uint8_t id = 0; id < getMaxSensorId(); id++) { + for (uint8_t id = 0; id <= getMaxSensorId(); id++) { if (settings[id].purpose == purpose && results[id].connected) { return true; } From 1eee184887beedb48ca21acd8776d8f5b60d65b7 Mon Sep 17 00:00:00 2001 From: Yurii Date: Mon, 16 Jun 2025 01:04:07 +0300 Subject: [PATCH 04/12] feat: added OT option dhwStateAsDhwBlocking For Baxi Slim with Bertelli --- src/OpenThermTask.h | 8 +++++++- src/Settings.h | 1 + src/strings.h | 1 + src/utils.h | 10 ++++++++++ src_data/locales/en.json | 1 + src_data/locales/it.json | 1 + src_data/locales/ru.json | 1 + src_data/pages/settings.html | 6 ++++++ 8 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/OpenThermTask.h b/src/OpenThermTask.h index f37bc80..808fb81 100644 --- a/src/OpenThermTask.h +++ b/src/OpenThermTask.h @@ -205,6 +205,12 @@ protected: summerWinterMode = vars.master.heating.enabled == summerWinterMode; } + // DHW blocking + bool dhwBlocking = settings.opentherm.options.dhwBlocking; + if (settings.opentherm.options.dhwStateAsDhwBlocking) { + dhwBlocking = vars.master.dhw.enabled == dhwBlocking; + } + unsigned long response = this->instance->setBoilerStatus( vars.master.heating.enabled, vars.master.dhw.enabled, @@ -212,7 +218,7 @@ protected: settings.opentherm.options.nativeHeatingControl, vars.master.ch2.enabled, summerWinterMode, - settings.opentherm.options.dhwBlocking, + dhwBlocking, statusLb ); diff --git a/src/Settings.h b/src/Settings.h index f939fbf..dc5444b 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -71,6 +71,7 @@ struct Settings { bool heatingToCh2 = false; bool dhwToCh2 = false; bool dhwBlocking = false; + bool dhwStateAsDhwBlocking = false; bool maxTempSyncWithTargetTemp = true; bool getMinMaxTemp = true; bool ignoreDiagState = false; diff --git a/src/strings.h b/src/strings.h index cd0c96b..c5ef678 100644 --- a/src/strings.h +++ b/src/strings.h @@ -68,6 +68,7 @@ const char S_DATE[] PROGMEM = "date"; const char S_DEADBAND[] PROGMEM = "deadband"; const char S_DHW[] PROGMEM = "dhw"; const char S_DHW_BLOCKING[] PROGMEM = "dhwBlocking"; +const char S_DHW_STATE_AS_DHW_BLOCKING[] PROGMEM = "dhwStateAsDhwBlocking"; const char S_DHW_SUPPORT[] PROGMEM = "dhwSupport"; const char S_DHW_TO_CH2[] PROGMEM = "dhwToCh2"; const char S_DIAG[] PROGMEM = "diag"; diff --git a/src/utils.h b/src/utils.h index 614dd3b..2758f7a 100644 --- a/src/utils.h +++ b/src/utils.h @@ -461,6 +461,7 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) { otOptions[FPSTR(S_HEATING_TO_CH2)] = src.opentherm.options.heatingToCh2; otOptions[FPSTR(S_DHW_TO_CH2)] = src.opentherm.options.dhwToCh2; otOptions[FPSTR(S_DHW_BLOCKING)] = src.opentherm.options.dhwBlocking; + otOptions[FPSTR(S_DHW_STATE_AS_DHW_BLOCKING)] = src.opentherm.options.dhwStateAsDhwBlocking; otOptions[FPSTR(S_MAX_TEMP_SYNC_WITH_TARGET_TEMP)] = src.opentherm.options.maxTempSyncWithTargetTemp; otOptions[FPSTR(S_GET_MIN_MAX_TEMP)] = src.opentherm.options.getMinMaxTemp; otOptions[FPSTR(S_IGNORE_DIAG_STATE)] = src.opentherm.options.ignoreDiagState; @@ -923,6 +924,15 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } + if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_DHW_STATE_AS_DHW_BLOCKING)].is()) { + bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_DHW_STATE_AS_DHW_BLOCKING)].as(); + + if (value != dst.opentherm.options.dhwStateAsDhwBlocking) { + dst.opentherm.options.dhwStateAsDhwBlocking = value; + changed = true; + } + } + if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_MAX_TEMP_SYNC_WITH_TARGET_TEMP)].is()) { bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OPTIONS)][FPSTR(S_MAX_TEMP_SYNC_WITH_TARGET_TEMP)].as(); diff --git a/src_data/locales/en.json b/src_data/locales/en.json index 44785f1..d8bf9ff 100644 --- a/src_data/locales/en.json +++ b/src_data/locales/en.json @@ -404,6 +404,7 @@ "heatingToCh2": "Duplicate heating to CH2", "dhwToCh2": "Duplicate DHW to CH2", "dhwBlocking": "DHW blocking", + "dhwStateAsDhwBlocking": "DHW state as DHW blocking", "maxTempSyncWithTargetTemp": "Sync max heating temp with target temp", "getMinMaxTemp": "Get min/max temp from boiler", "ignoreDiagState": "Ignore diag state", diff --git a/src_data/locales/it.json b/src_data/locales/it.json index 72455f2..d96e208 100644 --- a/src_data/locales/it.json +++ b/src_data/locales/it.json @@ -404,6 +404,7 @@ "heatingToCh2": "Riproduci riscaldamento su CH2", "dhwToCh2": "Riproduci ACS su CH2", "dhwBlocking": "Bloccare ACS", + "dhwStateAsDhwBlocking": "Stato ACS come bloccare ACS", "maxTempSyncWithTargetTemp": "Sincronizza la temperatura massima di riscaldamento con la temperatura target", "getMinMaxTemp": "Prendi temp min/max dalla caldaia", "ignoreDiagState": "Ignora lo stato diagnostico", diff --git a/src_data/locales/ru.json b/src_data/locales/ru.json index 40aa7f1..702f2ef 100644 --- a/src_data/locales/ru.json +++ b/src_data/locales/ru.json @@ -404,6 +404,7 @@ "heatingToCh2": "Дублировать параметры отопления в канал 2", "dhwToCh2": "Дублировать параметры ГВС в канал 2", "dhwBlocking": "DHW blocking", + "dhwStateAsDhwBlocking": "DHW blocking в качестве состояния ГВС", "maxTempSyncWithTargetTemp": "Синхронизировать макс. темп. отопления с целевой темп.", "getMinMaxTemp": "Получать мин. и макс. температуру от котла", "ignoreDiagState": "Игнорировать состояние диагностики", diff --git a/src_data/pages/settings.html b/src_data/pages/settings.html index a1eccb5..1582369 100644 --- a/src_data/pages/settings.html +++ b/src_data/pages/settings.html @@ -526,6 +526,11 @@ settings.ot.options.dhwBlocking + + +
+ settings.overheat.title + +
+ + + +
+ + settings.overheat.desc +
+ +
+ @@ -236,6 +258,28 @@ +
+ settings.overheat.title + +
+ + + +
+ + settings.overheat.desc +
+ +
+ @@ -880,6 +924,8 @@ setInputValue("[name='heating[hysteresis]']", data.heating.hysteresis); setInputValue("[name='heating[turboFactor]']", data.heating.turboFactor); setInputValue("[name='heating[maxModulation]']", data.heating.maxModulation); + setInputValue("[name='heating[overheatHighTemp]']", data.heating.overheatHighTemp); + setInputValue("[name='heating[overheatLowTemp]']", data.heating.overheatLowTemp); setBusy('#heating-settings-busy', '#heating-settings', false); // DHW @@ -892,6 +938,8 @@ "max": data.system.unitSystem == 0 ? 100 : 212 }); setInputValue("[name='dhw[maxModulation]']", data.dhw.maxModulation); + setInputValue("[name='dhw[overheatHighTemp]']", data.dhw.overheatHighTemp); + setInputValue("[name='dhw[overheatLowTemp]']", data.dhw.overheatLowTemp); setBusy('#dhw-settings-busy', '#dhw-settings', false); // Emergency mode From 58b0c184487dcbcbe0594586399981c20a690401 Mon Sep 17 00:00:00 2001 From: Yurii Date: Fri, 20 Jun 2025 02:46:25 +0300 Subject: [PATCH 06/12] fix: added C/F temperature conversion in overheating protection --- src/OpenThermTask.h | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/OpenThermTask.h b/src/OpenThermTask.h index fc9336a..801e6e7 100644 --- a/src/OpenThermTask.h +++ b/src/OpenThermTask.h @@ -1320,11 +1320,15 @@ protected: // Heating overheat control if (settings.heating.overheatHighTemp > 0 && settings.heating.overheatLowTemp > 0) { - float highTemp = max({ - vars.slave.heating.currentTemp, - vars.slave.heating.returnTemp, - vars.slave.heatExchangerTemp - }); + float highTemp = convertTemp( + max({ + vars.slave.heating.currentTemp, + vars.slave.heating.returnTemp, + vars.slave.heatExchangerTemp + }), + settings.opentherm.unitSystem, + settings.system.unitSystem + ); if (vars.master.heating.overheat) { if ((float) settings.heating.overheatLowTemp - highTemp + 0.0001f >= 0.0f) { @@ -1353,14 +1357,18 @@ protected: // DHW overheat control if (settings.dhw.overheatHighTemp > 0 && settings.dhw.overheatLowTemp > 0) { - float highTemp = max({ - vars.slave.heating.currentTemp, - vars.slave.heating.returnTemp, - vars.slave.heatExchangerTemp, - vars.slave.dhw.currentTemp, - vars.slave.dhw.currentTemp2, - vars.slave.dhw.returnTemp - }); + float highTemp = convertTemp( + max({ + vars.slave.heating.currentTemp, + vars.slave.heating.returnTemp, + vars.slave.heatExchangerTemp, + vars.slave.dhw.currentTemp, + vars.slave.dhw.currentTemp2, + vars.slave.dhw.returnTemp + }), + settings.opentherm.unitSystem, + settings.system.unitSystem + ); if (vars.master.dhw.overheat) { if ((float) settings.dhw.overheatLowTemp - highTemp + 0.0001f >= 0.0f) { From f6cfdf326368107fb942a035b37f2d1fd5e83b0d Mon Sep 17 00:00:00 2001 From: Yurii Date: Fri, 27 Jun 2025 00:28:38 +0300 Subject: [PATCH 07/12] 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 From 5c4ad8cd07dd102fc2011c6255155d7f12fd4b71 Mon Sep 17 00:00:00 2001 From: Yurii Date: Fri, 27 Jun 2025 14:53:17 +0300 Subject: [PATCH 08/12] chore: upd readme --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a5f681b..a1d9634 100644 --- a/README.md +++ b/README.md @@ -71,5 +71,10 @@ All available information and instructions can be found in the wiki: * [Connection](https://github.com/Laxilef/OTGateway/wiki/OT-adapters#connection) * [Leds on board](https://github.com/Laxilef/OTGateway/wiki/OT-adapters#leds-on-board) -___ -This project is tested with BrowserStack. +## Gratitude +* To the developers of the libraries used: [OpenTherm Library](https://github.com/ihormelnyk/opentherm_library), [ESP8266Scheduler](https://github.com/nrwiersma/ESP8266Scheduler), [ArduinoJson](https://github.com/bblanchon/ArduinoJson), [NimBLE-Arduino](https://github.com/h2zero/NimBLE-Arduino), [ArduinoMqttClient](https://github.com/arduino-libraries/ArduinoMqttClient), [ESPTelnet](https://github.com/LennartHennigs/ESPTelnet), [FileData](https://github.com/GyverLibs/FileData), [GyverPID](https://github.com/GyverLibs/GyverPID), [GyverBlinker](https://github.com/GyverLibs/GyverBlinker), [FileData](https://github.com/GyverLibs/FileData), [OneWireNg](https://github.com/pstolarz/OneWireNg) & [OneWire](https://github.com/PaulStoffregen/OneWire) +* To the [PlatformIO](https://platformio.org/) Team +* To the team and contributors of the [pioarduino](https://github.com/pioarduino/platform-espressif32) project +* To the [BrowserStack](https://www.browserstack.com/) team. This project is tested with BrowserStack. +* To the [PVS-Studio](https://pvs-studio.com/pvs-studio/?utm_source=website&utm_medium=github&utm_campaign=open_source) - static analyzer for C, C++, C#, and Java code. +* And of course to the contributors for their contribution to the development of the project! \ No newline at end of file From 47696a072106f665b84d929ed1870b37cddd8a3c Mon Sep 17 00:00:00 2001 From: Yurii Date: Sat, 28 Jun 2025 20:16:19 +0300 Subject: [PATCH 09/12] refactor: added print to log channel 2 activity state #155 --- lib/CustomOpenTherm/CustomOpenTherm.h | 5 +++++ src/OpenThermTask.h | 5 +++-- src/Settings.h | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/CustomOpenTherm/CustomOpenTherm.h b/lib/CustomOpenTherm/CustomOpenTherm.h index 389cfe5..82f59c1 100644 --- a/lib/CustomOpenTherm/CustomOpenTherm.h +++ b/lib/CustomOpenTherm/CustomOpenTherm.h @@ -106,6 +106,11 @@ public: return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::RemoteRequest); } + static bool isCh2Active(unsigned long response) + { + return response & 0x20; + } + static bool isValidResponseId(unsigned long response, OpenThermMessageID id) { byte responseId = (response >> 16) & 0xFF; diff --git a/src/OpenThermTask.h b/src/OpenThermTask.h index e6fddae..21d9084 100644 --- a/src/OpenThermTask.h +++ b/src/OpenThermTask.h @@ -237,6 +237,7 @@ protected: vars.slave.dhw.active = settings.opentherm.options.dhwSupport ? CustomOpenTherm::isHotWaterActive(response) : false; vars.slave.flame = CustomOpenTherm::isFlameOn(response); vars.slave.cooling = CustomOpenTherm::isCoolingActive(response); + vars.slave.ch2.active = CustomOpenTherm::isCh2Active(response); vars.slave.fault.active = CustomOpenTherm::isFault(response); if (!settings.opentherm.options.ignoreDiagState) { @@ -247,9 +248,9 @@ protected: } Log.snoticeln( - FPSTR(L_OT), F("Received boiler status. Heating: %hhu; DHW: %hhu; flame: %hhu; cooling: %hhu; fault: %hhu; diag: %hhu"), + FPSTR(L_OT), F("Received boiler status. Heating: %hhu; DHW: %hhu; flame: %hhu; cooling: %hhu; channel 2: %hhu; fault: %hhu; diag: %hhu"), vars.slave.heating.active, vars.slave.dhw.active, - vars.slave.flame, vars.slave.cooling, vars.slave.fault.active, vars.slave.diag.active + vars.slave.flame, vars.slave.cooling, vars.slave.ch2.active, vars.slave.fault.active, vars.slave.diag.active ); } diff --git a/src/Settings.h b/src/Settings.h index 634d714..731e4e4 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -398,6 +398,7 @@ struct Variables { } dhw; struct { + bool active = false; bool enabled = false; float targetTemp = 0.0f; float currentTemp = 0.0f; From 94f6f147955e5c794f43bcbc2b63c965d1a6e1fb Mon Sep 17 00:00:00 2001 From: Yurii Date: Mon, 30 Jun 2025 02:38:00 +0300 Subject: [PATCH 10/12] refactor: fixed logo size on mobile devices --- src_data/styles/app.css | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src_data/styles/app.css b/src_data/styles/app.css index 7a5e002..8e7ffe5 100644 --- a/src_data/styles/app.css +++ b/src_data/styles/app.css @@ -3,6 +3,10 @@ --pico-block-spacing-vertical: calc(var(--pico-spacing) * 0.75); --pico-block-spacing-horizontal: calc(var(--pico-spacing) * 0.75); } + + .logo { + font-size: 1.2rem; + } } @media (min-width: 768px) { @@ -10,6 +14,10 @@ --pico-block-spacing-vertical: var(--pico-spacing); --pico-block-spacing-horizontal: var(--pico-spacing); } + + .logo { + font-size: 1.25rem; + } } @media (min-width: 1024px) { @@ -17,6 +25,10 @@ --pico-block-spacing-vertical: calc(var(--pico-spacing) * 1.25); --pico-block-spacing-horizontal: calc(var(--pico-spacing) * 1.25); } + + .logo { + font-size: 1.25rem; + } } @media (min-width: 1280px) { @@ -25,6 +37,10 @@ --pico-block-spacing-horizontal: calc(var(--pico-spacing) * 1.5); } + .logo { + font-size: 1.3rem; + } + .container { max-width: 1000px; } @@ -36,6 +52,10 @@ --pico-block-spacing-horizontal: calc(var(--pico-spacing) * 1.75); } + .logo { + font-size: 1.3rem; + } + .container { max-width: 1000px; } @@ -111,7 +131,7 @@ tr.network:hover { border-radius: var(--pico-border-radius); color: var(--pico-code-kbd-color); font-weight: bolder; - font-size: 1.3rem; + /*font-size: 1.3rem;*/ font-family: var(--pico-font-family-monospace); } From 792c6a666865c533fc0568d5506d39ff80749b53 Mon Sep 17 00:00:00 2001 From: Yurii Date: Mon, 30 Jun 2025 02:38:25 +0300 Subject: [PATCH 11/12] chore: upd gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 94be47d..584ce9a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .pio .vscode +.PVS-Studio build/* data/* managed_components/* From e63a0dc7eb6f4341be4738e791f4ec6bd7c80e4f Mon Sep 17 00:00:00 2001 From: Yurii Date: Mon, 30 Jun 2025 02:41:44 +0300 Subject: [PATCH 12/12] refactor: improved freeze protection & overheat protection; added desc for OT options --- src/MainTask.h | 75 +++++++---- src/OpenThermTask.h | 20 +-- src/Settings.h | 20 ++- src/strings.h | 7 +- src/utils.h | 76 ++++++----- src_data/locales/en.json | 17 ++- src_data/locales/it.json | 17 ++- src_data/locales/ru.json | 17 ++- src_data/pages/settings.html | 243 ++++++++++++++++++++--------------- 9 files changed, 299 insertions(+), 193 deletions(-) diff --git a/src/MainTask.h b/src/MainTask.h index c60c7b3..af02b60 100644 --- a/src/MainTask.h +++ b/src/MainTask.h @@ -29,6 +29,7 @@ protected: enum class PumpStartReason {NONE, HEATING, ANTISTUCK}; Blinker* blinker = nullptr; + unsigned long miscRunned = 0; unsigned long lastHeapInfo = 0; unsigned int minFreeHeap = 0; unsigned int minMaxFreeBlockHeap = 0; @@ -42,6 +43,8 @@ protected: bool telnetStarted = false; bool emergencyDetected = false; unsigned long emergencyFlipTime = 0; + bool freezeDetected = false; + unsigned long freezeDetectedTime = 0; #if defined(ARDUINO_ARCH_ESP32) const char* getTaskName() override { @@ -150,18 +153,16 @@ protected: Sensors::setConnectionStatusByType(Sensors::Type::MANUAL, false, false); } - this->yield(); - this->heating(); - this->emergency(); + this->yield(); + if (this->misc()) { + this->yield(); + } this->ledStatus(); - this->cascadeControl(); - this->externalPump(); - this->yield(); - // telnet if (this->telnetStarted) { + this->yield(); telnetStream->loop(); this->yield(); } @@ -180,14 +181,27 @@ protected: // heap info this->heap(); + } + bool misc() { + if (millis() - this->miscRunned < 1000) { + return false; + } - // restart + // restart if required if (this->restartSignalReceived && millis() - this->restartSignalReceivedTime > 15000) { this->restartSignalReceived = false; ESP.restart(); } + + this->heating(); + this->emergency(); + this->cascadeControl(); + this->externalPump(); + this->miscRunned = millis(); + + return true; } void heap() { @@ -230,15 +244,15 @@ protected: } void heating() { - // anti freeze protection + // freeze protection if (!settings.heating.enabled) { - float minTemp = 255.0f; + float lowTemp = 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; + if (value < lowTemp) { + lowTemp = value; } availableSensors++; @@ -246,8 +260,8 @@ protected: if (Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::HEATING_TEMP)) { auto value = Sensors::getMeanValueByPurpose(Sensors::Purpose::HEATING_TEMP, Sensors::ValueType::PRIMARY); - if (value < minTemp) { - minTemp = value; + if (value < lowTemp) { + lowTemp = value; } availableSensors++; @@ -255,23 +269,36 @@ protected: 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; + if (value < lowTemp) { + lowTemp = value; } availableSensors++; } - if (availableSensors && minTemp <= settings.heating.antiFreezeTemp) { - settings.heating.enabled = true; - fsSettings.update(); + if (availableSensors && lowTemp <= settings.heating.freezeProtection.lowTemp) { + if (!this->freezeDetected) { + this->freezeDetected = true; + this->freezeDetectedTime = millis(); - Log.sinfoln( - FPSTR(L_MAIN), - F("Heating turned on by anti freeze protection, current min temp: %.2f, threshold: %hhu"), - minTemp, settings.heating.antiFreezeTemp - ); + } else if (millis() - this->freezeDetectedTime > (settings.heating.freezeProtection.thresholdTime * 1000)) { + this->freezeDetected = false; + settings.heating.enabled = true; + fsSettings.update(); + + Log.sinfoln( + FPSTR(L_MAIN), + F("Heating turned on by freeze protection, current low temp: %.2f, threshold: %hhu"), + lowTemp, settings.heating.freezeProtection.lowTemp + ); + } + + } else if (this->freezeDetected) { + this->freezeDetected = false; } + + } else if (this->freezeDetected) { + this->freezeDetected = false; } } diff --git a/src/OpenThermTask.h b/src/OpenThermTask.h index 21d9084..4b7543b 100644 --- a/src/OpenThermTask.h +++ b/src/OpenThermTask.h @@ -1320,7 +1320,7 @@ protected: // Heating overheat control - if (settings.heating.overheatHighTemp > 0 && settings.heating.overheatLowTemp > 0) { + if (settings.heating.overheatProtection.highTemp > 0 && settings.heating.overheatProtection.lowTemp > 0) { float highTemp = convertTemp( max({ vars.slave.heating.currentTemp, @@ -1332,22 +1332,22 @@ protected: ); if (vars.master.heating.overheat) { - if ((float) settings.heating.overheatLowTemp - highTemp + 0.0001f >= 0.0f) { + if ((float) settings.heating.overheatProtection.lowTemp - highTemp + 0.0001f >= 0.0f) { vars.master.heating.overheat = false; Log.sinfoln( FPSTR(L_OT_HEATING), F("Overheating not detected. Current high temp: %.2f, threshold (low): %hhu"), - highTemp, settings.heating.overheatLowTemp + highTemp, settings.heating.overheatProtection.lowTemp ); } } else if (vars.slave.heating.active) { - if (highTemp - (float) settings.heating.overheatHighTemp + 0.0001f >= 0.0f) { + if (highTemp - (float) settings.heating.overheatProtection.highTemp + 0.0001f >= 0.0f) { vars.master.heating.overheat = true; Log.swarningln( FPSTR(L_OT_HEATING), F("Overheating detected! Current high temp: %.2f, threshold (high): %hhu"), - highTemp, settings.heating.overheatHighTemp + highTemp, settings.heating.overheatProtection.highTemp ); } } @@ -1357,7 +1357,7 @@ protected: } // DHW overheat control - if (settings.dhw.overheatHighTemp > 0 && settings.dhw.overheatLowTemp > 0) { + if (settings.dhw.overheatProtection.highTemp > 0 && settings.dhw.overheatProtection.lowTemp > 0) { float highTemp = convertTemp( max({ vars.slave.heating.currentTemp, @@ -1372,22 +1372,22 @@ protected: ); if (vars.master.dhw.overheat) { - if ((float) settings.dhw.overheatLowTemp - highTemp + 0.0001f >= 0.0f) { + if ((float) settings.dhw.overheatProtection.lowTemp - highTemp + 0.0001f >= 0.0f) { vars.master.dhw.overheat = false; Log.sinfoln( FPSTR(L_OT_DHW), F("Overheating not detected. Current high temp: %.2f, threshold (low): %hhu"), - highTemp, settings.dhw.overheatLowTemp + highTemp, settings.dhw.overheatProtection.lowTemp ); } } else if (vars.slave.dhw.active) { - if (highTemp - (float) settings.dhw.overheatHighTemp + 0.0001f >= 0.0f) { + if (highTemp - (float) settings.dhw.overheatProtection.highTemp + 0.0001f >= 0.0f) { vars.master.dhw.overheat = true; Log.swarningln( FPSTR(L_OT_DHW), F("Overheating detected! Current high temp: %.2f, threshold (high): %hhu"), - highTemp, settings.dhw.overheatHighTemp + highTemp, settings.dhw.overheatProtection.highTemp ); } } diff --git a/src/Settings.h b/src/Settings.h index 731e4e4..af71a46 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -108,9 +108,16 @@ struct Settings { byte minTemp = DEFAULT_HEATING_MIN_TEMP; byte maxTemp = DEFAULT_HEATING_MAX_TEMP; uint8_t maxModulation = 100; - uint8_t overheatHighTemp = 95; - uint8_t overheatLowTemp = 90; - uint8_t antiFreezeTemp = 10; + + struct { + uint8_t highTemp = 95; + uint8_t lowTemp = 90; + } overheatProtection; + + struct { + uint8_t lowTemp = 10; + unsigned short thresholdTime = 600; + } freezeProtection; } heating; struct { @@ -119,8 +126,11 @@ struct Settings { byte minTemp = DEFAULT_DHW_MIN_TEMP; byte maxTemp = DEFAULT_DHW_MAX_TEMP; uint8_t maxModulation = 100; - uint8_t overheatHighTemp = 95; - uint8_t overheatLowTemp = 90; + + struct { + uint8_t highTemp = 95; + uint8_t lowTemp = 90; + } overheatProtection; } dhw; struct { diff --git a/src/strings.h b/src/strings.h index 6876f9b..74e2eaf 100644 --- a/src/strings.h +++ b/src/strings.h @@ -44,7 +44,6 @@ 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"; @@ -85,6 +84,7 @@ const char S_EQUITHERM[] PROGMEM = "equitherm"; const char S_EXTERNAL_PUMP[] PROGMEM = "externalPump"; const char S_FACTOR[] PROGMEM = "factor"; const char S_FAULT[] PROGMEM = "fault"; +const char S_FREEZE_PROTECTION[] PROGMEM = "freezeProtection"; const char S_FILTERING[] PROGMEM = "filtering"; const char S_FILTERING_FACTOR[] PROGMEM = "filteringFactor"; const char S_FLAGS[] PROGMEM = "flags"; @@ -100,6 +100,7 @@ const char S_HEATING[] PROGMEM = "heating"; const char S_HEATING_TO_CH2[] PROGMEM = "heatingToCh2"; const char S_HEATING_STATE_TO_SUMMER_WINTER_MODE[] PROGMEM = "heatingStateToSummerWinterMode"; const char S_HIDDEN[] PROGMEM = "hidden"; +const char S_HIGH_TEMP[] PROGMEM = "highTemp"; const char S_HOME_ASSISTANT_DISCOVERY[] PROGMEM = "homeAssistantDiscovery"; const char S_HOSTNAME[] PROGMEM = "hostname"; const char S_HUMIDITY[] PROGMEM = "humidity"; @@ -119,6 +120,7 @@ const char S_I_MULTIPLIER[] PROGMEM = "i_multiplier"; const char S_K_FACTOR[] PROGMEM = "k_factor"; const char S_LOGIN[] PROGMEM = "login"; const char S_LOG_LEVEL[] PROGMEM = "logLevel"; +const char S_LOW_TEMP[] PROGMEM = "lowTemp"; const char S_MAC[] PROGMEM = "mac"; const char S_MASTER[] PROGMEM = "master"; const char S_MAX[] PROGMEM = "max"; @@ -152,8 +154,7 @@ const char S_OUTDOOR_TEMP[] PROGMEM = "outdoorTemp"; const char S_OUT_GPIO[] PROGMEM = "outGpio"; const char S_OUTPUT[] PROGMEM = "output"; const char S_OVERHEAT[] PROGMEM = "overheat"; -const char S_OVERHEAT_HIGH_TEMP[] PROGMEM = "overheatHighTemp"; -const char S_OVERHEAT_LOW_TEMP[] PROGMEM = "overheatLowTemp"; +const char S_OVERHEAT_PROTECTION[] PROGMEM = "overheatProtection"; const char S_PASSWORD[] PROGMEM = "password"; const char S_PID[] PROGMEM = "pid"; const char S_PORT[] PROGMEM = "port"; diff --git a/src/utils.h b/src/utils.h index 0835aba..4913357 100644 --- a/src/utils.h +++ b/src/utils.h @@ -495,9 +495,14 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) { heating[FPSTR(S_MIN_TEMP)] = src.heating.minTemp; heating[FPSTR(S_MAX_TEMP)] = src.heating.maxTemp; 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 heatingOverheatProtection = heating[FPSTR(S_OVERHEAT_PROTECTION)].to(); + heatingOverheatProtection[FPSTR(S_HIGH_TEMP)] = src.heating.overheatProtection.highTemp; + heatingOverheatProtection[FPSTR(S_LOW_TEMP)] = src.heating.overheatProtection.lowTemp; + + auto freezeProtection = heating[FPSTR(S_FREEZE_PROTECTION)].to(); + freezeProtection[FPSTR(S_LOW_TEMP)] = src.heating.freezeProtection.lowTemp; + freezeProtection[FPSTR(S_THRESHOLD_TIME)] = src.heating.freezeProtection.thresholdTime; auto dhw = dst[FPSTR(S_DHW)].to(); dhw[FPSTR(S_ENABLED)] = src.dhw.enabled; @@ -505,8 +510,10 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) { dhw[FPSTR(S_MIN_TEMP)] = src.dhw.minTemp; dhw[FPSTR(S_MAX_TEMP)] = src.dhw.maxTemp; dhw[FPSTR(S_MAX_MODULATION)] = src.dhw.maxModulation; - dhw[FPSTR(S_OVERHEAT_HIGH_TEMP)] = src.dhw.overheatHighTemp; - dhw[FPSTR(S_OVERHEAT_LOW_TEMP)] = src.dhw.overheatLowTemp; + + auto dhwOverheatProtection = dhw[FPSTR(S_OVERHEAT_PROTECTION)].to(); + dhwOverheatProtection[FPSTR(S_HIGH_TEMP)] = src.dhw.overheatProtection.highTemp; + dhwOverheatProtection[FPSTR(S_LOW_TEMP)] = src.dhw.overheatProtection.lowTemp; auto equitherm = dst[FPSTR(S_EQUITHERM)].to(); equitherm[FPSTR(S_ENABLED)] = src.equitherm.enabled; @@ -1347,38 +1354,49 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_HIGH_TEMP)].isNull()) { - unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_HIGH_TEMP)].as(); + if (!src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_HIGH_TEMP)].isNull()) { + unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_HIGH_TEMP)].as(); - if (isValidTemp(value, dst.system.unitSystem, 0.0f, 100.0f) && value != dst.heating.overheatHighTemp) { - dst.heating.overheatHighTemp = value; + if (isValidTemp(value, dst.system.unitSystem, 0.0f, 100.0f) && value != dst.heating.overheatProtection.highTemp) { + dst.heating.overheatProtection.highTemp = value; changed = true; } } - if (!src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_LOW_TEMP)].isNull()) { - unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_LOW_TEMP)].as(); + if (!src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_LOW_TEMP)].isNull()) { + unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_LOW_TEMP)].as(); - if (isValidTemp(value, dst.system.unitSystem, 0.0f, 99.0f) && value != dst.heating.overheatLowTemp) { - dst.heating.overheatLowTemp = value; + if (isValidTemp(value, dst.system.unitSystem, 0.0f, 99.0f) && value != dst.heating.overheatProtection.lowTemp) { + dst.heating.overheatProtection.lowTemp = value; changed = true; } } - if (dst.heating.overheatHighTemp < dst.heating.overheatLowTemp) { - dst.heating.overheatHighTemp = dst.heating.overheatLowTemp; + if (dst.heating.overheatProtection.highTemp < dst.heating.overheatProtection.lowTemp) { + dst.heating.overheatProtection.highTemp = dst.heating.overheatProtection.lowTemp; 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 (!src[FPSTR(S_HEATING)][FPSTR(S_FREEZE_PROTECTION)][FPSTR(S_LOW_TEMP)].isNull()) { + unsigned short value = src[FPSTR(S_HEATING)][FPSTR(S_FREEZE_PROTECTION)][FPSTR(S_LOW_TEMP)].as(); - if (isValidTemp(value, dst.system.unitSystem, 1, 30) && value != dst.heating.antiFreezeTemp) { - dst.heating.antiFreezeTemp = value; + if (isValidTemp(value, dst.system.unitSystem, 1, 30) && value != dst.heating.freezeProtection.lowTemp) { + dst.heating.freezeProtection.lowTemp = value; changed = true; } } + if (!src[FPSTR(S_HEATING)][FPSTR(S_FREEZE_PROTECTION)][FPSTR(S_THRESHOLD_TIME)].isNull()) { + unsigned short value = src[FPSTR(S_HEATING)][FPSTR(S_FREEZE_PROTECTION)][FPSTR(S_THRESHOLD_TIME)].as(); + + if (value >= 30 && value <= 1800) { + if (value != dst.heating.freezeProtection.thresholdTime) { + dst.heating.freezeProtection.thresholdTime = value; + changed = true; + } + } + } + // dhw if (src[FPSTR(S_DHW)][FPSTR(S_ENABLED)].is()) { @@ -1422,26 +1440,26 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_HIGH_TEMP)].isNull()) { - unsigned char value = src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_HIGH_TEMP)].as(); + if (!src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_HIGH_TEMP)].isNull()) { + unsigned char value = src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_HIGH_TEMP)].as(); - if (isValidTemp(value, dst.system.unitSystem, 0.0f, 100.0f) && value != dst.dhw.overheatHighTemp) { - dst.dhw.overheatHighTemp = value; + if (isValidTemp(value, dst.system.unitSystem, 0.0f, 100.0f) && value != dst.dhw.overheatProtection.highTemp) { + dst.dhw.overheatProtection.highTemp = value; changed = true; } } - if (!src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_LOW_TEMP)].isNull()) { - unsigned char value = src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_LOW_TEMP)].as(); + if (!src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_LOW_TEMP)].isNull()) { + unsigned char value = src[FPSTR(S_DHW)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_LOW_TEMP)].as(); - if (isValidTemp(value, dst.system.unitSystem, 0.0f, 99.0f) && value != dst.dhw.overheatLowTemp) { - dst.dhw.overheatLowTemp = value; + if (isValidTemp(value, dst.system.unitSystem, 0.0f, 99.0f) && value != dst.dhw.overheatProtection.lowTemp) { + dst.dhw.overheatProtection.lowTemp = value; changed = true; } } - if (dst.dhw.overheatHighTemp < dst.dhw.overheatLowTemp) { - dst.dhw.overheatHighTemp = dst.dhw.overheatLowTemp; + if (dst.dhw.overheatProtection.highTemp < dst.dhw.overheatProtection.lowTemp) { + dst.dhw.overheatProtection.highTemp = dst.dhw.overheatProtection.lowTemp; changed = true; } diff --git a/src_data/locales/en.json b/src_data/locales/en.json index b539321..c801561 100644 --- a/src_data/locales/en.json +++ b/src_data/locales/en.json @@ -304,7 +304,7 @@ "max": "Maximum temperature" }, "maxModulation": "Max modulation level", - "overheat": { + "ohProtection": { "title": "Overheating protection", "desc": "Note: This feature can be useful if the built-in boiler overheating protection does not work or does not work correctly and the heat carrier boils. To disable, set 0 as high and low temperature.", "highTemp": { @@ -316,6 +316,12 @@ "note": "Threshold at which the burner can be turned on again" } }, + "freezeProtection": { + "title": "Freeze protection", + "desc": "Heating will be forced to turn on if the heat carrier or indoor temperature drops below Low temperature during Waiting time.", + "lowTemp": "Low temperature threshold", + "thresholdTime": "Waiting time (sec)" + }, "portal": { "login": "Login", @@ -350,11 +356,7 @@ "heating": { "hyst": "Hysteresis (in degrees)", - "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" - } + "turboFactor": "Turbo mode coeff." }, "emergency": { @@ -413,7 +415,8 @@ }, "options": { - "desc": "Options", + "title": "Options (additional settings)", + "desc": "Options can change the logic of the boiler. Not all options are documented in the protocol, so the same option can have different effects on different boilers.
Note: There is no need to change anything if everything works well.", "dhwSupport": "DHW support", "coolingSupport": "Cooling support", "summerWinterMode": "Summer/winter mode", diff --git a/src_data/locales/it.json b/src_data/locales/it.json index 5a33e07..9a13be8 100644 --- a/src_data/locales/it.json +++ b/src_data/locales/it.json @@ -304,7 +304,7 @@ "max": "Temperatura massima" }, "maxModulation": "Max livello modulazione", - "overheat": { + "ohProtection": { "title": "Protezione contro il surriscaldamento", "desc": "Nota: questa funzione può essere utile se la protezione contro il surriscaldamento integrata nella caldaia non funziona o non funziona correttamente e il fluido termovettore bolle. Per disattivarla, impostare 0 come temperatura alta e bassa.", "highTemp": { @@ -316,6 +316,12 @@ "note": "Soglia alla quale il bruciatore può essere riacceso" } }, + "freezeProtection": { + "title": "Protezione antigelo", + "desc": "Il riscaldamento verrà attivato forzatamente se la temperatura del vettore di calore o interna scende al di sotto della temperatura minima durante il tempo di attesa.", + "lowTemp": "Soglia di temperatura minima", + "thresholdTime": "Tempo di attesa (sec)" + }, "portal": { "login": "Login", @@ -350,11 +356,7 @@ "heating": { "hyst": "Isteresi (in gradi)", - "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" - } + "turboFactor": "Turbo mode coeff." }, "emergency": { @@ -413,7 +415,8 @@ }, "options": { - "desc": "Opzioni", + "title": "Opzioni (impostazioni aggiuntive)", + "desc": "Le opzioni possono modificare la logica della caldaia. Non tutte le opzioni sono documentate nel protocollo, quindi la stessa opzione può avere effetti diversi su caldaie diverse.
Nota: Non è necessario modificare nulla se tutto funziona correttamente.", "dhwSupport": "Supporto ACS", "coolingSupport": "Supporto rafferscamento", "summerWinterMode": "Modalità Estate/inverno", diff --git a/src_data/locales/ru.json b/src_data/locales/ru.json index a7af639..28055f7 100644 --- a/src_data/locales/ru.json +++ b/src_data/locales/ru.json @@ -304,7 +304,7 @@ "max": "Макс. температура" }, "maxModulation": "Макс. уровень модуляции", - "overheat": { + "ohProtection": { "title": "Защита от перегрева", "desc": "Примечание: Эта функция может быть полезна, если встроенная защита от перегрева котла не срабатывает или срабатывает некорректно и теплоноситель закипает. Для отключения установите 0 в качестве верхнего и нижнего порога температуры.", "highTemp": { @@ -316,6 +316,12 @@ "note": "Порог, при котором горелка может быть включена снова" } }, + "freezeProtection": { + "title": "Защита от замерзания", + "desc": "Отопление будет принудительно включено, если темп. теплоносителя или внутренняя темп. опустится ниже нижнего порога в течение времени ожидания.", + "lowTemp": "Нижний порог температуры", + "thresholdTime": "Время ожидания (сек)" + }, "portal": { "login": "Логин", @@ -350,11 +356,7 @@ "heating": { "hyst": "Гистерезис (в градусах)", - "turboFactor": "Коэфф. турбо режима", - "antiFreezeTemp": { - "title": "Температура защиты от замерзания", - "note": "Отопление будет принудительно включено, если температура теплоносителя или внутренняя температура опустится ниже этого значения" - } + "turboFactor": "Коэфф. турбо режима" }, "emergency": { @@ -413,7 +415,8 @@ }, "options": { - "desc": "Опции", + "title": "Опции (дополнительные настройки)", + "desc": "Опции могут менять логику работы котла. Не все опции задокументированы в протоколе, поэтому одна и та же опция может иметь разный эффект на разных котлах.
Примечание: Нет необходимости что-то менять, если всё работает хорошо.", "dhwSupport": "Поддержка ГВС", "coolingSupport": "Поддержка охлаждения", "summerWinterMode": "Летний/зимний режим", diff --git a/src_data/pages/settings.html b/src_data/pages/settings.html index 37a596a..fe58d94 100644 --- a/src_data/pages/settings.html +++ b/src_data/pages/settings.html @@ -207,33 +207,49 @@ - +
- settings.overheat.title + settings.ohProtection.title
- settings.overheat.desc + settings.ohProtection.desc
-
+
+ +
+ settings.freezeProtection.title + +
+ + + +
+ + settings.freezeProtection.desc +
+ +
@@ -264,27 +280,29 @@ +
+
- settings.overheat.title + settings.ohProtection.title
- settings.overheat.desc + settings.ohProtection.desc
-
+
@@ -533,97 +551,107 @@ -
- settings.ot.options.desc +
+ settings.ot.options.title - +
+
+ settings.ot.options.desc +
- +
+ - + - + - + - + - + - + - + - + - + - + - + - + - + - + -
- -
+ +
+ + +
+ +
+ +
@@ -930,12 +958,19 @@ setInputValue("[name='heating[hysteresis]']", data.heating.hysteresis); setInputValue("[name='heating[turboFactor]']", data.heating.turboFactor); 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, { + setInputValue("[name='heating[overheatProtection][highTemp]']", data.heating.overheatProtection.highTemp, { + "min": 0, + "max": data.system.unitSystem == 0 ? 100 : 212 + }); + setInputValue("[name='heating[overheatProtection][lowTemp]']", data.heating.overheatProtection.lowTemp, { + "min": 0, + "max": data.system.unitSystem == 0 ? 99 : 211 + }); + setInputValue("[name='heating[freezeProtection][lowTemp]']", data.heating.freezeProtection.lowTemp, { "min": data.system.unitSystem == 0 ? 1 : 34, "max": data.system.unitSystem == 0 ? 30 : 86 }); + setInputValue("[name='heating[freezeProtection][thresholdTime]']", data.heating.freezeProtection.thresholdTime); setBusy('#heating-settings-busy', '#heating-settings', false); // DHW @@ -948,8 +983,14 @@ "max": data.system.unitSystem == 0 ? 100 : 212 }); setInputValue("[name='dhw[maxModulation]']", data.dhw.maxModulation); - setInputValue("[name='dhw[overheatHighTemp]']", data.dhw.overheatHighTemp); - setInputValue("[name='dhw[overheatLowTemp]']", data.dhw.overheatLowTemp); + setInputValue("[name='dhw[overheatProtection][highTemp]']", data.dhw.overheatProtection.highTemp, { + "min": 0, + "max": data.system.unitSystem == 0 ? 100 : 212 + }); + setInputValue("[name='dhw[overheatProtection][lowTemp]']", data.dhw.overheatProtection.lowTemp, { + "min": 0, + "max": data.system.unitSystem == 0 ? 99 : 211 + }); setBusy('#dhw-settings-busy', '#dhw-settings', false); // Emergency mode