From c2ee30d1abcbe009a54b1f71109fe9183cceb91b Mon Sep 17 00:00:00 2001 From: zedward <17852402+z-edward@users.noreply.github.com> Date: Fri, 19 Sep 2025 21:00:11 +0300 Subject: [PATCH 01/17] fix: added ``nodemcu_32_160mhz`` env, deadband usage in PID fixed (#176) * Small fixes: CPU limit for odemcu_32, deadbamd usage in PID * PR review fixes * Fix: remove spaces --------- Co-authored-by: Edward Zhuravlov --- platformio.ini | 4 ++++ src/RegulatorTask.h | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index f683bc5..7be7151 100644 --- a/platformio.ini +++ b/platformio.ini @@ -294,6 +294,10 @@ build_flags = check_tool = ${esp32_defaults.check_tool} check_flags = ${esp32_defaults.check_flags} +[env:nodemcu_32_160mhz] +extends = env:nodemcu_32 +board_build.f_cpu = 160000000L ; set frequency to 160MHz + [env:d1_mini32] platform = ${esp32_defaults.platform} platform_packages = ${esp32_defaults.platform_packages} diff --git a/src/RegulatorTask.h b/src/RegulatorTask.h index 443aee9..f768a54 100644 --- a/src/RegulatorTask.h +++ b/src/RegulatorTask.h @@ -213,7 +213,8 @@ protected: }*/ float error = pidRegulator.setpoint - pidRegulator.input; - bool hasDeadband = (error > -(settings.pid.deadband.thresholdHigh)) + bool hasDeadband = settings.pid.deadband.enabled + && (error > -(settings.pid.deadband.thresholdHigh)) && (error < settings.pid.deadband.thresholdLow); if (hasDeadband) { From 502f73db0edd2581a70023c61a9bf1a7d825b66b Mon Sep 17 00:00:00 2001 From: Yurii Date: Fri, 19 Sep 2025 21:06:17 +0300 Subject: [PATCH 02/17] chore: bump pioarduino/platform-espressif32 from 3.3.0 to 3.3.1 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 7be7151..b40be67 100644 --- a/platformio.ini +++ b/platformio.ini @@ -92,7 +92,7 @@ check_flags = ${env.check_flags} ;platform_packages = ; framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.5 ; framework-arduinoespressif32-libs @ https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.1/esp32-arduino-libs-idf-release_v5.1-33fbade6.zip -platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.30-2/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.31/platform-espressif32.zip platform_packages = ${env.platform_packages} board_build.partitions = esp32_partitions.csv lib_deps = From 062c6714d1fdb177433e15f11fa828a6139c10d4 Mon Sep 17 00:00:00 2001 From: Yurii Date: Fri, 19 Sep 2025 21:09:55 +0300 Subject: [PATCH 03/17] chore: bump dependencies --- platformio.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platformio.ini b/platformio.ini index b40be67..18ff224 100644 --- a/platformio.ini +++ b/platformio.ini @@ -17,12 +17,12 @@ core_dir = .pio version = 1.5.6 framework = arduino lib_deps = - bblanchon/ArduinoJson@^7.3.0 + bblanchon/ArduinoJson@^7.4.2 ;ihormelnyk/OpenTherm Library@^1.1.5 https://github.com/Laxilef/opentherm_library#esp32_timer arduino-libraries/ArduinoMqttClient@^0.1.8 - lennarthennigs/ESP Telnet@^2.2 - gyverlibs/FileData@^1.0.2 + lennarthennigs/ESP Telnet@^2.2.3 + gyverlibs/FileData@^1.0.3 gyverlibs/GyverPID@^3.3.2 gyverlibs/GyverBlinker@^1.1.1 https://github.com/pstolarz/Arduino-Temperature-Control-Library.git#OneWireNg @@ -98,7 +98,7 @@ board_build.partitions = esp32_partitions.csv lib_deps = ${env.lib_deps} laxilef/ESP32Scheduler@^1.0.1 -nimble_lib = h2zero/NimBLE-Arduino@^2.1.0 +nimble_lib = h2zero/NimBLE-Arduino@^2.3.6 lib_ignore = extra_scripts = post:tools/esp32.py From f3ba43adbd11cd4e6b508479d901f55a2c92e376 Mon Sep 17 00:00:00 2001 From: Yurii Date: Fri, 19 Sep 2025 21:14:38 +0300 Subject: [PATCH 04/17] docs: fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a1d9634..67c662b 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ All available information and instructions can be found in the wiki: * [Leds on board](https://github.com/Laxilef/OTGateway/wiki/OT-adapters#leds-on-board) ## 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 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), [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. From 28a5218b7c3b5e45d0547225c2df8fe7c498c597 Mon Sep 17 00:00:00 2001 From: Yurii Date: Tue, 23 Sep 2025 04:02:40 +0300 Subject: [PATCH 05/17] fix: various fixes Found by PVS-Studio --- src/Sensors.h | 2 +- src/SensorsTask.h | 42 +++++++++++++++++++++--------------------- src/Settings.h | 8 ++++---- src/utils.h | 8 ++++---- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/Sensors.h b/src/Sensors.h index 7b0cc61..d481816 100644 --- a/src/Sensors.h +++ b/src/Sensors.h @@ -334,7 +334,7 @@ public: uint8_t valueId = (uint8_t) valueType; if (!isValidValueId(valueId)) { - return false; + return 0; } float value = 0.0f; diff --git a/src/SensorsTask.h b/src/SensorsTask.h index cba050e..135d20f 100644 --- a/src/SensorsTask.h +++ b/src/SensorsTask.h @@ -385,7 +385,7 @@ protected: continue; } - const float sensorResistance = value > 0.001f + const float sensorResistance = value > 1 ? DEFAULT_NTC_REF_RESISTANCE / (DEFAULT_NTC_VREF / (float) value - 1.0f) : 0.0f; const float rawTemp = 1.0f / ( @@ -577,7 +577,7 @@ protected: bool subscribeToBleDevice(const uint8_t sensorId, NimBLEClient* pClient) { auto& sSensor = Sensors::settings[sensorId]; - auto pAddress = pClient->getPeerAddress().toString().c_str(); + auto pAddress = pClient->getPeerAddress().toString(); NimBLERemoteService* pService = nullptr; NimBLERemoteCharacteristic* pChar = nullptr; @@ -588,13 +588,13 @@ protected: if (!pService) { Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to find env service (%s) on device %s"), - sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress + sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress.c_str() ); } else { Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found env service (%s) on device %s"), - sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress + sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress.c_str() ); // 0x2A6E - Notify temperature x0.01C (pvvx) @@ -606,7 +606,7 @@ protected: if (pChar && (pChar->canNotify() || pChar->canIndicate())) { Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found temp char (%s) in env service on device %s"), - sensorId, sSensor.name, charUuid.toString().c_str(), pAddress + sensorId, sSensor.name, charUuid.toString().c_str(), pAddress.c_str() ); pChar->unsubscribe(); @@ -661,14 +661,14 @@ protected: Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to temp char (%s) in env service on device %s"), sensorId, sSensor.name, - charUuid.toString().c_str(), pAddress + charUuid.toString().c_str(), pAddress.c_str() ); } else { Log.swarningln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to temp char (%s) in env service on device %s"), sensorId, sSensor.name, - charUuid.toString().c_str(), pAddress + charUuid.toString().c_str(), pAddress.c_str() ); } } @@ -683,7 +683,7 @@ protected: if (pChar && (pChar->canNotify() || pChar->canIndicate())) { Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found temp char (%s) in env service on device %s"), - sensorId, sSensor.name, charUuid.toString().c_str(), pAddress + sensorId, sSensor.name, charUuid.toString().c_str(), pAddress.c_str() ); pChar->unsubscribe(); @@ -738,14 +738,14 @@ protected: Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to temp char (%s) in env service on device %s"), sensorId, sSensor.name, - charUuid.toString().c_str(), pAddress + charUuid.toString().c_str(), pAddress.c_str() ); } else { Log.swarningln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to temp char (%s) in env service on device %s"), sensorId, sSensor.name, - charUuid.toString().c_str(), pAddress + charUuid.toString().c_str(), pAddress.c_str() ); } } @@ -754,7 +754,7 @@ protected: if (!tempNotifyCreated) { Log.swarningln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found supported temp chars in env service on device %s"), - sensorId, sSensor.name, pAddress + sensorId, sSensor.name, pAddress.c_str() ); pClient->disconnect(); @@ -772,7 +772,7 @@ protected: if (pChar && (pChar->canNotify() || pChar->canIndicate())) { Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found humidity char (%s) in env service on device %s"), - sensorId, sSensor.name, charUuid.toString().c_str(), pAddress + sensorId, sSensor.name, charUuid.toString().c_str(), pAddress.c_str() ); pChar->unsubscribe(); @@ -827,14 +827,14 @@ protected: Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to humidity char (%s) in env service on device %s"), sensorId, sSensor.name, - charUuid.toString().c_str(), pAddress + charUuid.toString().c_str(), pAddress.c_str() ); } else { Log.swarningln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to humidity char (%s) in env service on device %s"), sensorId, sSensor.name, - charUuid.toString().c_str(), pAddress + charUuid.toString().c_str(), pAddress.c_str() ); } } @@ -843,7 +843,7 @@ protected: if (!humidityNotifyCreated) { Log.swarningln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found supported humidity chars in env service on device %s"), - sensorId, sSensor.name, pAddress + sensorId, sSensor.name, pAddress.c_str() ); } } @@ -857,13 +857,13 @@ protected: if (!pService) { Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to find battery service (%s) on device %s"), - sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress + sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress.c_str() ); } else { Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found battery service (%s) on device %s"), - sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress + sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress.c_str() ); // 0x2A19 - Notify the battery charge level 0..99% (pvvx) @@ -875,7 +875,7 @@ protected: if (pChar && (pChar->canNotify() || pChar->canIndicate())) { Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found battery char (%s) in battery service on device %s"), - sensorId, sSensor.name, charUuid.toString().c_str(), pAddress + sensorId, sSensor.name, charUuid.toString().c_str(), pAddress.c_str() ); pChar->unsubscribe(); @@ -930,14 +930,14 @@ protected: Log.straceln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to battery char (%s) in battery service on device %s"), sensorId, sSensor.name, - charUuid.toString().c_str(), pAddress + charUuid.toString().c_str(), pAddress.c_str() ); } else { Log.swarningln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to battery char (%s) in battery service on device %s"), sensorId, sSensor.name, - charUuid.toString().c_str(), pAddress + charUuid.toString().c_str(), pAddress.c_str() ); } } @@ -946,7 +946,7 @@ protected: if (!batteryNotifyCreated) { Log.swarningln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found supported battery chars in battery service on device %s"), - sensorId, sSensor.name, pAddress + sensorId, sSensor.name, pAddress.c_str() ); } } diff --git a/src/Settings.h b/src/Settings.h index af71a46..17cdad1 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -170,15 +170,15 @@ struct Settings { struct { struct { bool enabled = false; - byte gpio = GPIO_IS_NOT_CONFIGURED; - byte invertState = false; + uint8_t gpio = GPIO_IS_NOT_CONFIGURED; + bool invertState = false; unsigned short thresholdTime = 60; } input; struct { bool enabled = false; - byte gpio = GPIO_IS_NOT_CONFIGURED; - byte invertState = false; + uint8_t gpio = GPIO_IS_NOT_CONFIGURED; + bool invertState = false; unsigned short thresholdTime = 60; bool onFault = true; bool onLossConnection = true; diff --git a/src/utils.h b/src/utils.h index 4913357..ac4ffa2 100644 --- a/src/utils.h +++ b/src/utils.h @@ -72,7 +72,7 @@ time_t mkgmtime(const struct tm *ptm) { inline bool isDigit(const char* ptr) { char* endPtr; - strtol(ptr, &endPtr, 10); + auto tmp = strtol(ptr, &endPtr, 10); return *endPtr == 0; } @@ -1324,7 +1324,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src[FPSTR(S_HEATING)][FPSTR(S_MIN_TEMP)].isNull()) { unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_MIN_TEMP)].as(); - if (value != dst.heating.minTemp && value >= vars.slave.heating.minTemp && value < vars.slave.heating.maxTemp && value != dst.heating.minTemp) { + if (value != dst.heating.minTemp && value >= vars.slave.heating.minTemp && value < vars.slave.heating.maxTemp && value != dst.heating.maxTemp) { dst.heating.minTemp = value; changed = true; } @@ -1333,7 +1333,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src[FPSTR(S_HEATING)][FPSTR(S_MAX_TEMP)].isNull()) { unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_MAX_TEMP)].as(); - if (value != dst.heating.maxTemp && value > vars.slave.heating.minTemp && value <= vars.slave.heating.maxTemp && value != dst.heating.maxTemp) { + if (value != dst.heating.maxTemp && value > vars.slave.heating.minTemp && value <= vars.slave.heating.maxTemp && value != dst.heating.minTemp) { dst.heating.maxTemp = value; changed = true; } @@ -1894,7 +1894,7 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se // gpio if (!src[FPSTR(S_GPIO)].isNull()) { - if (dst.type != Sensors::Type::DALLAS_TEMP && dst.type == Sensors::Type::BLUETOOTH && dst.type == Sensors::Type::NTC_10K_TEMP) { + if (dst.type != Sensors::Type::DALLAS_TEMP && dst.type != Sensors::Type::NTC_10K_TEMP) { if (dst.gpio != GPIO_IS_NOT_CONFIGURED) { dst.gpio = GPIO_IS_NOT_CONFIGURED; changed = true; From 14826c10cd5c71e2dc0e5b2e7645791d5c930c05 Mon Sep 17 00:00:00 2001 From: Yurii Date: Tue, 23 Sep 2025 04:03:36 +0300 Subject: [PATCH 06/17] chore: ``byte`` to ``uint8_t`` --- lib/CustomOpenTherm/CustomOpenTherm.h | 11 +++++------ lib/NetworkUtils/NetworkMgr.h | 12 ++++++------ src/HaHelper.h | 8 ++++---- src/MqttTask.h | 2 +- src/OpenThermTask.h | 8 ++++---- src/Settings.h | 22 +++++++++++----------- 6 files changed, 31 insertions(+), 32 deletions(-) diff --git a/lib/CustomOpenTherm/CustomOpenTherm.h b/lib/CustomOpenTherm/CustomOpenTherm.h index 82f59c1..6fd6db1 100644 --- a/lib/CustomOpenTherm/CustomOpenTherm.h +++ b/lib/CustomOpenTherm/CustomOpenTherm.h @@ -4,8 +4,8 @@ class CustomOpenTherm : public OpenTherm { public: typedef std::function DelayCallback; - typedef std::function BeforeSendRequestCallback; - typedef std::function AfterSendRequestCallback; + typedef std::function BeforeSendRequestCallback; + typedef std::function AfterSendRequestCallback; CustomOpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false, bool alwaysReceive = false) : OpenTherm(inPin, outPin, isSlave, alwaysReceive) {} ~CustomOpenTherm() {} @@ -106,15 +106,14 @@ public: return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::RemoteRequest); } - static bool isCh2Active(unsigned long response) - { + static bool isCh2Active(unsigned long response) { return response & 0x20; } static bool isValidResponseId(unsigned long response, OpenThermMessageID id) { - byte responseId = (response >> 16) & 0xFF; + uint8_t responseId = (response >> 16) & 0xFF; - return (byte)id == responseId; + return (uint8_t)id == responseId; } static uint8_t getResponseMessageTypeId(unsigned long response) { diff --git a/lib/NetworkUtils/NetworkMgr.h b/lib/NetworkUtils/NetworkMgr.h index 30de4c7..aadf3dd 100644 --- a/lib/NetworkUtils/NetworkMgr.h +++ b/lib/NetworkUtils/NetworkMgr.h @@ -35,7 +35,7 @@ namespace NetworkUtils { return this; } - NetworkMgr* setApCredentials(const char* ssid, const char* password = nullptr, byte channel = 0) { + NetworkMgr* setApCredentials(const char* ssid, const char* password = nullptr, uint8_t channel = 0) { this->apName = ssid; this->apPassword = password; this->apChannel = channel; @@ -43,7 +43,7 @@ namespace NetworkUtils { return this; } - NetworkMgr* setStaCredentials(const char* ssid = nullptr, const char* password = nullptr, byte channel = 0) { + NetworkMgr* setStaCredentials(const char* ssid = nullptr, const char* password = nullptr, uint8_t channel = 0) { this->staSsid = ssid; this->staPassword = password; this->staChannel = channel; @@ -140,7 +140,7 @@ namespace NetworkUtils { return this->staPassword; } - byte getStaChannel() { + uint8_t getStaChannel() { return this->staChannel; } @@ -377,7 +377,7 @@ namespace NetworkUtils { } } - static byte rssiToSignalQuality(short int rssi) { + static uint8_t rssiToSignalQuality(short int rssi) { return constrain(map(rssi, -100, -50, 0, 100), 0, 100); } @@ -397,11 +397,11 @@ namespace NetworkUtils { const char* hostname = "esp"; const char* apName = "ESP"; const char* apPassword = nullptr; - byte apChannel = 1; + uint8_t apChannel = 1; const char* staSsid = nullptr; const char* staPassword = nullptr; - byte staChannel = 0; + uint8_t staChannel = 0; bool useDhcp = true; IPAddress staticIp; diff --git a/src/HaHelper.h b/src/HaHelper.h index 689d3ec..69ba800 100644 --- a/src/HaHelper.h +++ b/src/HaHelper.h @@ -3,8 +3,8 @@ class HaHelper : public HomeAssistantHelper { public: - static const byte TEMP_SOURCE_HEATING = 0; - static const byte TEMP_SOURCE_INDOOR = 1; + static const uint8_t TEMP_SOURCE_HEATING = 0; + static const uint8_t TEMP_SOURCE_INDOOR = 1; static const char AVAILABILITY_OT_CONN[]; static const char AVAILABILITY_SENSOR_CONN[]; @@ -1170,7 +1170,7 @@ public: } - bool publishClimateHeating(UnitSystem unit = UnitSystem::METRIC, byte minTemp = 20, byte maxTemp = 90, bool enabledByDefault = true) { + bool publishClimateHeating(UnitSystem unit = UnitSystem::METRIC, uint8_t minTemp = 20, uint8_t maxTemp = 90, bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; @@ -1222,7 +1222,7 @@ public: return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_CLIMATE), F("heating"), '_').c_str(), doc); } - bool publishClimateDhw(UnitSystem unit = UnitSystem::METRIC, byte minTemp = 40, byte maxTemp = 60, bool enabledByDefault = true) { + bool publishClimateDhw(UnitSystem unit = UnitSystem::METRIC, uint8_t minTemp = 40, uint8_t maxTemp = 60, bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; diff --git a/src/MqttTask.h b/src/MqttTask.h index da29d0a..7578f72 100644 --- a/src/MqttTask.h +++ b/src/MqttTask.h @@ -557,7 +557,7 @@ protected: } bool publishNonStaticHaEntities(bool force = false) { - static byte _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0; + static uint8_t _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0; static bool _indoorTempControl, _dhwSupport = false; bool published = false; diff --git a/src/OpenThermTask.h b/src/OpenThermTask.h index 4b7543b..fd5e1d8 100644 --- a/src/OpenThermTask.h +++ b/src/OpenThermTask.h @@ -19,8 +19,8 @@ protected: CustomOpenTherm* instance = nullptr; unsigned long instanceCreatedTime = 0; - byte instanceInGpio = 0; - byte instanceOutGpio = 0; + uint8_t instanceInGpio = 0; + uint8_t instanceOutGpio = 0; bool initialized = false; unsigned long connectedTime = 0; unsigned long disconnectedTime = 0; @@ -31,7 +31,7 @@ protected: unsigned long heatingSetTempTime = 0; unsigned long dhwSetTempTime = 0; unsigned long ch2SetTempTime = 0; - byte configuredRxLedGpio = GPIO_IS_NOT_CONFIGURED; + uint8_t configuredRxLedGpio = GPIO_IS_NOT_CONFIGURED; #if defined(ARDUINO_ARCH_ESP32) const char* getTaskName() override { @@ -90,7 +90,7 @@ protected: Log.sinfoln(FPSTR(L_OT), F("Started. GPIO IN: %hhu, GPIO OUT: %hhu"), settings.opentherm.inGpio, settings.opentherm.outGpio); - this->instance->setAfterSendRequestCallback([this](unsigned long request, unsigned long response, OpenThermResponseStatus status, byte attempt) { + this->instance->setAfterSendRequestCallback([this](unsigned long request, unsigned long response, OpenThermResponseStatus status, uint8_t attempt) { Log.sverboseln( FPSTR(L_OT), F("ID: %4d Request: %8lx Response: %8lx Msg type: %s Attempt: %2d Status: %s"), diff --git a/src/Settings.h b/src/Settings.h index 17cdad1..1fded9f 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -12,13 +12,13 @@ struct NetworkSettings { struct { char ssid[33] = DEFAULT_AP_SSID; char password[65] = DEFAULT_AP_PASSWORD; - byte channel = 6; + uint8_t channel = 6; } ap; struct { char ssid[33] = DEFAULT_STA_SSID; char password[65] = DEFAULT_STA_PASSWORD; - byte channel = 0; + uint8_t channel = 0; } sta; } networkSettings; @@ -42,7 +42,7 @@ struct Settings { } ntp; UnitSystem unitSystem = UnitSystem::METRIC; - byte statusLedGpio = DEFAULT_STATUS_LED_GPIO; + uint8_t statusLedGpio = DEFAULT_STATUS_LED_GPIO; } system; struct { @@ -54,9 +54,9 @@ struct Settings { struct { UnitSystem unitSystem = UnitSystem::METRIC; - byte inGpio = DEFAULT_OT_IN_GPIO; - byte outGpio = DEFAULT_OT_OUT_GPIO; - byte rxLedGpio = DEFAULT_OT_RX_LED_GPIO; + uint8_t inGpio = DEFAULT_OT_IN_GPIO; + uint8_t outGpio = DEFAULT_OT_OUT_GPIO; + uint8_t rxLedGpio = DEFAULT_OT_RX_LED_GPIO; uint8_t memberId = 0; uint8_t flags = 0; float minPower = 0.0f; @@ -105,8 +105,8 @@ struct Settings { float target = DEFAULT_HEATING_TARGET_TEMP; float hysteresis = 0.5f; float turboFactor = 7.5f; - byte minTemp = DEFAULT_HEATING_MIN_TEMP; - byte maxTemp = DEFAULT_HEATING_MAX_TEMP; + uint8_t minTemp = DEFAULT_HEATING_MIN_TEMP; + uint8_t maxTemp = DEFAULT_HEATING_MAX_TEMP; uint8_t maxModulation = 100; struct { @@ -123,8 +123,8 @@ struct Settings { struct { bool enabled = true; float target = DEFAULT_DHW_TARGET_TEMP; - byte minTemp = DEFAULT_DHW_MIN_TEMP; - byte maxTemp = DEFAULT_DHW_MAX_TEMP; + uint8_t minTemp = DEFAULT_DHW_MIN_TEMP; + uint8_t maxTemp = DEFAULT_DHW_MAX_TEMP; uint8_t maxModulation = 100; struct { @@ -161,7 +161,7 @@ struct Settings { struct { bool use = false; - byte gpio = DEFAULT_EXT_PUMP_GPIO; + uint8_t gpio = DEFAULT_EXT_PUMP_GPIO; unsigned short postCirculationTime = 600; unsigned int antiStuckInterval = 2592000; unsigned short antiStuckTime = 300; From d3e7a13e1f5b6a757fab1af3f81a7bbd23cad51c Mon Sep 17 00:00:00 2001 From: Yurii Date: Fri, 3 Oct 2025 20:54:42 +0300 Subject: [PATCH 07/17] fix: rollback NimBLE-Arduino from 2.3.6 to 2.3.3 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 18ff224..46f8a3c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -98,7 +98,7 @@ board_build.partitions = esp32_partitions.csv lib_deps = ${env.lib_deps} laxilef/ESP32Scheduler@^1.0.1 -nimble_lib = h2zero/NimBLE-Arduino@^2.3.6 +nimble_lib = h2zero/NimBLE-Arduino@2.3.3 lib_ignore = extra_scripts = post:tools/esp32.py From 9d77256051eddda962cf485afc0e432f2d496ef1 Mon Sep 17 00:00:00 2001 From: Yurii Date: Fri, 3 Oct 2025 20:56:04 +0300 Subject: [PATCH 08/17] refactor: not initialize Bluetooth if BLE sensors are not used --- src/SensorsTask.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/SensorsTask.h b/src/SensorsTask.h index 135d20f..8390f9e 100644 --- a/src/SensorsTask.h +++ b/src/SensorsTask.h @@ -405,7 +405,6 @@ protected: #if USE_BLE void cleanBleInstances() { - #if USE_BLE if (!NimBLEDevice::isInitialized()) { return; } @@ -442,10 +441,13 @@ protected: NimBLEDevice::deleteClient(client); } } - #endif } void pollingBleSensors() { + if (!Sensors::getAmountByType(Sensors::Type::BLUETOOTH, true)) { + return; + } + if (!NimBLEDevice::isInitialized() && millis() > 5000) { Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Initialized")); BLEDevice::init(""); From d4addf887f9b070f587e490bf4b8da3dda45d859 Mon Sep 17 00:00:00 2001 From: Yurii Date: Thu, 9 Oct 2025 08:13:33 +0300 Subject: [PATCH 09/17] refactor: improved work with BLE sensors --- src/SensorsTask.h | 154 +++++++++++++++++++++++----------------------- 1 file changed, 78 insertions(+), 76 deletions(-) diff --git a/src/SensorsTask.h b/src/SensorsTask.h index 8390f9e..1031536 100644 --- a/src/SensorsTask.h +++ b/src/SensorsTask.h @@ -8,6 +8,45 @@ extern FileData fsSensorsSettings; +#if USE_BLE +class BluetoothClientCallbacks : public NimBLEClientCallbacks { +public: + BluetoothClientCallbacks(uint8_t sensorId) : sensorId(sensorId) {} + + void onConnect(NimBLEClient* pClient) { + auto& sSensor = Sensors::settings[this->sensorId]; + + Log.sinfoln( + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': connected to %s"), + sensorId, sSensor.name, pClient->getPeerAddress().toString().c_str() + ); + } + + void onDisconnect(NimBLEClient* pClient, int reason) { + auto& sSensor = Sensors::settings[this->sensorId]; + + Log.sinfoln( + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': disconnected, reason %i"), + sensorId, sSensor.name, reason + ); + } + + void onConnectFail(NimBLEClient* pClient, int reason) { + auto& sSensor = Sensors::settings[this->sensorId]; + + Log.sinfoln( + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to connect, reason %i"), + sensorId, sSensor.name, reason + ); + + pClient->cancelConnect(); + } + +protected: + uint8_t sensorId; +}; +#endif + class SensorsTask : public LeanTask { public: SensorsTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) { @@ -41,6 +80,7 @@ protected: std::unordered_map dallasPolling; std::unordered_map dallasLastPollingTime; #if USE_BLE + std::unordered_map bleClients; std::unordered_map bleSubscribed; std::unordered_map bleLastSetDtTime; #endif @@ -409,36 +449,18 @@ protected: return; } - for (auto client : NimBLEDevice::getConnectedClients()) { - auto address = client->getPeerAddress(); - bool used = false; + for (auto& [sensorId, pClient]: this->bleClients) { + auto& sSensor = Sensors::settings[sensorId]; - for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { - auto& sSensor = Sensors::settings[sensorId]; - - if (!sSensor.enabled || sSensor.type != Sensors::Type::BLUETOOTH || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) { - continue; - } - - auto pAddress = address.getVal(); - uint8_t addr[] = { - pAddress[5], pAddress[4], pAddress[3], - pAddress[2], pAddress[1], pAddress[0] - }; - - if (isEqualAddress(addr, sSensor.address, sizeof(addr))) { - used = true; - break; - } - } - - if (!used) { + if (!sSensor.enabled || sSensor.type != Sensors::Type::BLUETOOTH || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) { Log.sinfoln( - FPSTR(L_SENSORS_BLE), F("Deleted unused client connected to %s"), - address.toString().c_str() + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s', deleted unused client, address: %s"), + sensorId, sSensor.name, + pClient->getPeerAddress().toString().c_str() ); - NimBLEDevice::deleteClient(client); + NimBLEDevice::deleteClient(pClient); + pClient = nullptr; } } } @@ -462,38 +484,27 @@ protected: continue; } - auto client = this->getBleClient(sensorId); - if (client == nullptr) { + auto pClient = this->getBleClient(sensorId); + if (pClient == nullptr) { continue; } - if (!client->isConnected()) { + if (!pClient->isConnected()) { this->bleSubscribed[sensorId] = false; this->bleLastSetDtTime[sensorId] = 0; - if (client->connect()) { - Log.sinfoln( - FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': connected to %s"), - sensorId, sSensor.name, client->getPeerAddress().toString().c_str() - ); + pClient->connect(true, true, true); - } else { - Log.swarningln( - FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed connecting to %s"), - sensorId, sSensor.name, client->getPeerAddress().toString().c_str() - ); - - continue; - } + continue; } if (!this->bleSubscribed[sensorId]) { - if (this->subscribeToBleDevice(sensorId, client)) { + if (this->subscribeToBleDevice(sensorId, pClient)) { this->bleSubscribed[sensorId] = true; } else { this->bleSubscribed[sensorId] = false; - client->disconnect(); + pClient->disconnect(); continue; } } @@ -505,7 +516,7 @@ protected: struct tm ti; if (getLocalTime(&ti)) { - if (this->setDateOnBleSensor(client, &ti)) { + if (this->setDateOnBleSensor(pClient, &ti)) { Log.sinfoln( FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s', successfully set date: %02d.%02d.%04d %02d:%02d:%02d"), sensorId, sSensor.name, @@ -538,41 +549,32 @@ protected: return nullptr; } - uint8_t addr[6] = { - sSensor.address[0], sSensor.address[1], sSensor.address[2], - sSensor.address[3], sSensor.address[4], sSensor.address[5] - }; - const auto address = NimBLEAddress(addr, 0); - - NimBLEClient* pClient = NimBLEDevice::getClientByPeerAddress(address); + const auto address = NimBLEAddress(sSensor.address, 0); + if (address.isNull()) { + return nullptr; + } + + if (this->bleClients[sensorId] && this->bleClients[sensorId] != nullptr) { + return this->bleClients[sensorId]; + } + + auto pClient = NimBLEDevice::createClient(address); if (pClient == nullptr) { - pClient = NimBLEDevice::getDisconnectedClient(); + return nullptr; } - if (pClient == nullptr) { - if (NimBLEDevice::getCreatedClientCount() >= NIMBLE_MAX_CONNECTIONS) { - return nullptr; - } + /** + * Set initial connection parameters: + * These settings are safe for 3 clients to connect reliably, can go faster if you have less + * connections. Timeout should be a multiple of the interval, minimum is 100ms. + * Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 1000 * 10ms = 10000ms timeout + */ + pClient->setConnectionParams(12, 12, 0, 1000); + pClient->setConnectTimeout(10000); + pClient->setSelfDelete(false, false); + pClient->setClientCallbacks(new BluetoothClientCallbacks(sensorId), true); - pClient = NimBLEDevice::createClient(); - if (pClient == nullptr) { - return nullptr; - } - - /** - * Set initial connection parameters: - * These settings are safe for 3 clients to connect reliably, can go faster if you have less - * connections. Timeout should be a multiple of the interval, minimum is 100ms. - * Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 1000 * 10ms = 10000ms timeout - */ - pClient->setConnectionParams(12, 12, 0, 1000); - pClient->setConnectTimeout(5000); - pClient->setSelfDelete(false, true); - } - - if (!pClient->isConnected()) { - pClient->setPeerAddress(address); - } + this->bleClients[sensorId] = pClient; return pClient; } From 1375d8c7a59d469dad0ad218e9e42fcd32ee1287 Mon Sep 17 00:00:00 2001 From: Yurii Date: Thu, 9 Oct 2025 08:16:04 +0300 Subject: [PATCH 10/17] chore: bump pioarduino/platform-espressif32 from 3.3.1 to 3.3.2 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 46f8a3c..2cdeed2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -92,7 +92,7 @@ check_flags = ${env.check_flags} ;platform_packages = ; framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.5 ; framework-arduinoespressif32-libs @ https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.1/esp32-arduino-libs-idf-release_v5.1-33fbade6.zip -platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.31/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.32/platform-espressif32.zip platform_packages = ${env.platform_packages} board_build.partitions = esp32_partitions.csv lib_deps = From dda79151f3e4872ac61f8e123b468300d31c4673 Mon Sep 17 00:00:00 2001 From: Douwe <61123717+dhoeben@users.noreply.github.com> Date: Thu, 9 Oct 2025 07:19:55 +0200 Subject: [PATCH 11/17] feat: added Dutch locale #184 * Create nl.json * Update nl.json --- src_data/locales/nl.json | 464 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 464 insertions(+) create mode 100644 src_data/locales/nl.json diff --git a/src_data/locales/nl.json b/src_data/locales/nl.json new file mode 100644 index 0000000..9464def --- /dev/null +++ b/src_data/locales/nl.json @@ -0,0 +1,464 @@ +{ + "values": { + "logo": "OpenTherm Gateway", + "nav": { + "license": "Licentie", + "source": "Broncode", + "help": "Help", + "issues": "Problemen & vragen", + "releases": "Releases" + }, + "dbm": "dBm", + "kw": "kW", + "time": { + "days": "d.", + "hours": "u.", + "min": "min.", + "sec": "sec." + }, + "button": { + "upgrade": "Upgraden", + "restart": "Herstarten", + "save": "Opslaan", + "saved": "Opgeslagen", + "refresh": "Vernieuwen", + "restore": "Herstellen", + "restored": "Hersteld", + "backup": "Back-up", + "wait": "Even wachten...", + "uploading": "Uploaden...", + "success": "Succes", + "error": "Fout" + }, + "index": { + "title": "OpenTherm Gateway", + "section": { + "network": "Netwerk", + "system": "Systeem" + }, + "system": { + "build": { + "title": "Build", + "version": "Versie", + "date": "Datum", + "core": "Core", + "sdk": "SDK" + }, + "uptime": "Uptime", + "memory": { + "title": "Vrij geheugen", + "maxFreeBlock": "max. vrij blok", + "min": "min" + }, + "board": "Board", + "chip": { + "model": "Chip", + "cores": "Kernen", + "freq": "frequentie" + }, + "flash": { + "size": "Flash-grootte", + "realSize": "werkelijke grootte" + }, + "lastResetReason": "Reden laatste herstart" + } + }, + "dashboard": { + "name": "Dashboard", + "title": "Dashboard - OpenTherm Gateway", + "section": { + "control": "Bediening", + "states": "Statussen", + "sensors": "Sensoren", + "diag": "OpenTherm diagnose" + }, + "thermostat": { + "heating": "Verwarming", + "dhw": "Warm water", + "temp.current": "Huidig", + "enable": "Inschakelen", + "turbo": "Turbomodus" + }, + "notify": { + "fault": { + "title": "Ketelstoring is actief!", + "note": "Het wordt aanbevolen de ketel te inspecteren en de documentatie te raadplegen om de storingscode te interpreteren:" + }, + "diag": { + "title": "Keteldiagnose is actief!", + "note": "Heeft uw ketel misschien een inspectie nodig? Het wordt aanbevolen de documentatie te raadplegen om de diagnosecode te interpreteren:" + }, + "reset": "Probeer te resetten" + }, + "states": { + "mNetworkConnected": "Netwerkverbinding", + "mMqttConnected": "MQTT-verbinding", + "mEmergencyState": "Noodmodus", + "mExtPumpState": "Externe pomp", + "mCascadeControlInput": "Cascaderegeling (ingang)", + "mCascadeControlOutput": "Cascaderegeling (uitgang)", + "sConnected": "OpenTherm-verbinding", + "sFlame": "Vlam", + "sCooling": "Koeling", + "sFaultActive": "Storing", + "sFaultCode": "Storingscode", + "sDiagActive": "Diagnose", + "sDiagCode": "Diagnosecode", + "mHeatEnabled": "Verwarming ingeschakeld", + "mHeatBlocking": "Verwarming geblokkeerd", + "mHeatOverheat": "Verwarming oververhit", + "sHeatActive": "Verwarming actief", + "mHeatSetpointTemp": "Insteltemperatuur verwarming", + "mHeatTargetTemp": "Doeltemperatuur verwarming", + "mHeatCurrTemp": "Huidige temperatuur verwarming", + "mHeatRetTemp": "Retourtemperatuur verwarming", + "mHeatIndoorTemp": "Verwarming, binnentemperatuur", + "mHeatOutdoorTemp": "Verwarming, buitentemperatuur", + "mDhwEnabled": "Warm water ingeschakeld", + "mDhwOverheat": "Warm water oververhit", + "sDhwActive": "Warm water actief", + "mDhwTargetTemp": "Doeltemperatuur warm water", + "mDhwCurrTemp": "Huidige temperatuur warm water", + "mDhwRetTemp": "Retourtemperatuur warm water" + }, + "sensors": { + "values": { + "temp": "Temperatuur", + "humidity": "Luchtvochtigheid", + "battery": "Batterij", + "rssi": "RSSI" + } + } + }, + "network": { + "title": "Netwerk - OpenTherm Gateway", + "name": "Netwerkinstellingen", + "section": { + "static": "Statische instellingen", + "availableNetworks": "Beschikbare netwerken", + "staSettings": "WiFi-instellingen", + "apSettings": "AP-instellingen" + }, + "scan": { + "pos": "#", + "info": "Info" + }, + "wifi": { + "ssid": "SSID", + "password": "Wachtwoord", + "channel": "Kanaal", + "signal": "Signaal", + "connected": "Verbonden" + }, + "params": { + "hostname": "Hostnaam", + "dhcp": "Gebruik DHCP", + "mac": "MAC", + "ip": "IP", + "subnet": "Subnet", + "gateway": "Gateway", + "dns": "DNS" + }, + "sta": { + "channel.note": "zet op 0 voor automatische selectie" + } + }, + "sensors": { + "title": "Sensorinstellingen - OpenTherm Gateway", + "name": "Sensorinstellingen", + "enabled": "Ingeschakeld", + "sensorName": { + "title": "Sensornaam", + "note": "Mag alleen bevatten: a-z, A-Z, 0-9, _ en spatie" + }, + "purpose": "Doel", + "purposes": { + "outdoorTemp": "Buitentemperatuur", + "indoorTemp": "Binnentemperatuur", + "heatTemp": "Verwarming, temperatuur", + "heatRetTemp": "Verwarming, retourtemperatuur", + "dhwTemp": "Warm water, temperatuur", + "dhwRetTemp": "Warm water, retourtemperatuur", + "dhwFlowRate": "Warm water, doorstroomsnelheid", + "exhaustTemp": "Rookgastemperatuur", + "modLevel": "Modulatieniveau (in procenten)", + "number": "Getal (ruw)", + "powerFactor": "Vermogen (in procent)", + "power": "Vermogen (in kWt)", + "fanSpeed": "Ventilatorsnelheid", + "co2": "CO2", + "pressure": "Druk", + "humidity": "Luchtvochtigheid", + "temperature": "Temperatuur", + "notConfigured": "Niet geconfigureerd" + }, + "type": "Type/bron", + "types": { + "otOutdoorTemp": "OpenTherm, buitentemp.", + "otHeatTemp": "OpenTherm, verwarming, temp.", + "otHeatRetTemp": "OpenTherm, verwarming, retourtemp.", + "otDhwTemp": "OpenTherm, warm water, temperatuur", + "otDhwTemp2": "OpenTherm, warm water, temperatuur 2", + "otDhwFlowRate": "OpenTherm, warm water, doorstroomsnelheid", + "otCh2Temp": "OpenTherm, kanaal 2, temp.", + "otExhaustTemp": "OpenTherm, rookgastemp.", + "otHeatExchangerTemp": "OpenTherm, warmtewisselaar temp.", + "otPressure": "OpenTherm, druk", + "otModLevel": "OpenTherm, modulatieniveau", + "otCurrentPower": "OpenTherm, huidig vermogen", + "otExhaustCo2": "OpenTherm, rookgas CO2", + "otExhaustFanSpeed": "OpenTherm, rookgasventilator snelheid", + "otSupplyFanSpeed": "OpenTherm, toevoerventilator snelheid", + "otSolarStorageTemp": "OpenTherm, zonneboiler opslagtemp.", + "otSolarCollectorTemp": "OpenTherm, zonnecollector temp.", + "otFanSpeedSetpoint": "OpenTherm, instelpunt ventilatorsnelheid", + "otFanSpeedCurrent": "OpenTherm, huidige ventilatorsnelheid", + "otBurnerStarts": "OpenTherm, aantal branderstarts", + "otDhwBurnerStarts": "OpenTherm, aantal branderstarts (warm water)", + "otHeatingPumpStarts": "OpenTherm, aantal pompstarts (verwarming)", + "otDhwPumpStarts": "OpenTherm, aantal pompstarts (warm water)", + "otBurnerHours": "OpenTherm, aantal branderuren", + "otDhwBurnerHours": "OpenTherm, aantal branderuren (warm water)", + "otHeatingPumpHours": "OpenTherm, aantal pompuren (verwarming)", + "otDhwPumpHours": "OpenTherm, aantal pompuren (warm water)", + "ntcTemp": "NTC-sensor", + "dallasTemp": "DALLAS-sensor", + "bluetooth": "BLE-sensor", + "heatSetpointTemp": "Verwarming, insteltemperatuur", + "manual": "Handmatig via MQTT/API", + "notConfigured": "Niet geconfigureerd" + }, + "gpio": "GPIO", + "address": { + "title": "Sensoradres", + "note": "Laat leeg voor automatische detectie van DALLAS-sensoren. Voor BLE-apparaten is een MAC-adres vereist." + }, + "correction": { + "desc": "Correctie van waarden", + "offset": "Compensatie (offset)", + "factor": "Vermenigvuldiger" + }, + "filtering": { + "desc": "Filteren van waarden", + "enabled": { + "title": "Filteren ingeschakeld", + "note": "Kan handig zijn bij veel scherpe ruis in de grafieken. Het gebruikte filter is \"Voortschrijdend gemiddelde\"." + }, + "factor": { + "title": "Filterfactor", + "note": "Hoe lager de waarde, hoe vloeiender en langer de verandering in numerieke waarden." + } + } + }, + "settings": { + "title": "Instellingen - OpenTherm Gateway", + "name": "Instellingen", + "section": { + "portal": "Portaalinstellingen", + "system": "Systeeminstellingen", + "diag": "Diagnose", + "heating": "Verwarmingsinstellingen", + "dhw": "Warmwaterinstellingen", + "emergency": "Instellingen noodmodus", + "equitherm": "Equitherm-instellingen", + "pid": "PID-instellingen", + "ot": "OpenTherm-instellingen", + "mqtt": "MQTT-instellingen", + "extPump": "Instellingen externe pomp", + "cascadeControl": "Instellingen cascaderegeling" + }, + "enable": "Inschakelen", + "note": { + "restart": "Na het wijzigen van deze instellingen moet het apparaat opnieuw worden opgestart om de wijzigingen door te voeren.", + "blankNotUse": "leeg - niet gebruiken", + "bleDevice": "BLE-apparaat kan alleen worden gebruikt met sommige ESP32-boards met BLE-ondersteuning!" + }, + "temp": { + "min": "Minimumtemperatuur", + "max": "Maximumtemperatuur" + }, + "maxModulation": "Max. modulatieniveau", + "ohProtection": { + "title": "Oververhittingsbeveiliging", + "desc": "Let op: Deze functie kan handig zijn als de ingebouwde oververhittingsbeveiliging van de ketel niet of niet correct werkt en de warmtedrager kookt. Om uit te schakelen, stel 0 in als hoge en lage temperatuur.", + "highTemp": { + "title": "Drempelwaarde hoge temperatuur", + "note": "Drempelwaarde waarbij de brander geforceerd wordt uitgeschakeld" + }, + "lowTemp": { + "title": "Drempelwaarde lage temperatuur", + "note": "Drempelwaarde waarbij de brander weer ingeschakeld kan worden" + } + }, + "freezeProtection": { + "title": "Vorstbeveiliging", + "desc": "De verwarming wordt geforceerd ingeschakeld als de temperatuur van de warmtedrager of de binnentemperatuur onder de Lage temperatuur daalt gedurende de Wachttijd.", + "lowTemp": "Drempelwaarde lage temperatuur", + "thresholdTime": "Wachttijd (sec)" + }, + "portal": { + "login": "Gebruikersnaam", + "password": "Wachtwoord", + "auth": "Authenticatie vereisen", + "mdns": "Gebruik mDNS" + }, + "system": { + "unit": "Eenheidssysteem", + "metric": "Metrisch (celsius, liters, bar)", + "imperial": "Imperiaal (fahrenheit, gallons, psi)", + "statusLedGpio": "Status LED GPIO", + "logLevel": "Logniveau", + "serial": { + "enable": "Seriële poort ingeschakeld", + "baud": "Baudrate seriële poort" + }, + "telnet": { + "enable": "Telnet ingeschakeld", + "port": { + "title": "Telnet-poort", + "note": "Standaard: 23" + } + }, + "ntp": { + "server": "NTP-server", + "timezone": "Tijdzone", + "timezonePresets": "Selecteer voorinstelling..." + } + }, + "heating": { + "hyst": "Hysterese (in graden)", + "turboFactor": "Turbomodus coëff." + }, + "emergency": { + "desc": "Noodmodus wordt automatisch geactiveerd wanneer «PID» of «Equitherm» het instelpunt van de warmtedrager niet kan berekenen:
- als «Equitherm» is ingeschakeld en de buitentemperatuursensor is losgekoppeld;
- als «PID» of OT-optie «Natuurlijke verwarmingsregeling» is ingeschakeld en de binnentemperatuursensor is losgekoppeld.
Let op: Bij een netwerk- of MQTT-storing krijgen sensoren van het type «Handmatig via MQTT/API» de status ONVERBONDEN.", + "target": { + "title": "Doeltemperatuur", + "note": "Belangrijk: Doel binnentemperatuur als OT-optie «Natuurlijke verwarmingsregeling» is ingeschakeld.
In alle andere gevallen, de doel warmtedragertemperatuur." + }, + "treshold": "Drempeltijd (sec)" + }, + "equitherm": { + "n": "N-factor", + "k": "K-factor", + "t": { + "title": "T-factor", + "note": "Niet gebruikt als PID is ingeschakeld" + } + }, + "pid": { + "p": "P-factor", + "i": "I-factor", + "d": "D-factor", + "dt": "DT in seconden", + "limits": { + "title": "Limieten", + "note": "Belangrijk: Bij gelijktijdig gebruik van «Equitherm» en «PID» beperken de min- en maxtemperaturen de invloed op de resulterende «Equitherm»-temperatuur.
Dus, als de min-temperatuur is ingesteld op -15 en de max-temperatuur op 15, zal het uiteindelijke instelpunt van de warmtedrager liggen tussen equitherm_resultaat - 15 en equitherm_resultaat + 15." + }, + "deadband": { + "title": "Deadband", + "note": "Deadband is een bereik rond de doeltemperatuur waarbinnen de PID-regeling minder actief wordt. Binnen dit bereik kan het algoritme de intensiteit verminderen of aanpassingen pauzeren om overreactie op kleine schommelingen te voorkomen.

Bijvoorbeeld, met een doeltemperatuur van 22°, een onderdrempel van 1.0 en een bovendrempel van 0.5, werkt de deadband tussen 21° en 22.5°. Als de I-coëfficiënt 0.0005 is en de I-vermenigvuldiger 0.05, dan wordt de I-coëfficiënt binnen de deadband: 0.0005 * 0.05 = 0.000025", + "p_multiplier": "Vermenigvuldiger voor P-factor", + "i_multiplier": "Vermenigvuldiger voor I-factor", + "d_multiplier": "Vermenigvuldiger voor D-factor", + "thresholdHigh": "Bovendrempel", + "thresholdLow": "Onderdrempel" + } + }, + "ot": { + "advanced": "Geavanceerde instellingen", + "inGpio": "In GPIO", + "outGpio": "Uit GPIO", + "ledGpio": "RX LED GPIO", + "memberId": "Master member ID", + "flags": "Master flags", + "minPower": { + "title": "Min. ketelvermogen (kW)", + "note": "Deze waarde is bij 0-1% modulatieniveau van de ketel. Typisch te vinden in de ketelspecificatie als \"minimum nuttig warmtevermogen\"." + }, + "maxPower": { + "title": "Max. ketelvermogen (kW)", + "note": "0 - probeer automatisch te detecteren. Typisch te vinden in de ketelspecificatie als \"maximum nuttig warmtevermogen\"." + }, + "options": { + "title": "Opties (aanvullende instellingen)", + "desc": "Opties kunnen de logica van de ketel veranderen. Niet alle opties zijn gedocumenteerd in het protocol, dus dezelfde optie kan verschillende effecten hebben op verschillende ketels.
Let op: Het is niet nodig om iets te veranderen als alles goed werkt.", + "dhwSupport": "Warm water ondersteuning", + "coolingSupport": "Koeling ondersteuning", + "summerWinterMode": "Zomer/wintermodus", + "heatingStateToSummerWinterMode": "Verwarmingsstatus als zomer/wintermodus", + "ch2AlwaysEnabled": "CH2 altijd ingeschakeld", + "heatingToCh2": "Dupliceer verwarming naar CH2", + "dhwToCh2": "Dupliceer warm water naar CH2", + "dhwBlocking": "Blokkering warm water", + "dhwStateAsDhwBlocking": "Status warm water als blokkering warm water", + "maxTempSyncWithTargetTemp": "Synchroniseer max. verwarmingstemp. met doeltemp.", + "getMinMaxTemp": "Haal min/max temp. op van ketel", + "ignoreDiagState": "Negeer diagnosestatus", + "autoFaultReset": "Automatische storingsreset (niet aanbevolen!)", + "autoDiagReset": "Automatische diagnosereset (niet aanbevolen!)", + "setDateAndTime": "Stel datum & tijd in op ketel", + "immergasFix": "Fix voor Immergas-ketels" + }, + "nativeHeating": { + "title": "Natuurlijke verwarmingsregeling (ketel)", + "note": "Werkt ALLEEN als de ketel de gewenste kamertemperatuur vereist en zelf de temperatuur van de warmtedrager regelt. Niet compatibel met PID- en Equitherm-regelaars in de firmware." + } + }, + "mqtt": { + "homeAssistantDiscovery": "Home Assistant Discovery", + "server": "Server", + "port": "Poort", + "user": "Gebruiker", + "password": "Wachtwoord", + "prefix": "Prefix", + "interval": "Publicatie-interval (sec)" + }, + "extPump": { + "use": "Gebruik externe pomp", + "gpio": "Relais GPIO", + "postCirculationTime": "Nacirculatietijd (min)", + "antiStuckInterval": "Anti-vastloopinterval (dagen)", + "antiStuckTime": "Anti-vastlooptijd (min)" + }, + "cascadeControl": { + "input": { + "desc": "Kan worden gebruikt om de verwarming alleen in te schakelen als een andere ketel defect is. De besturing van de andere ketel moet de status van de GPIO-ingang wijzigen in geval van een storing.", + "enable": "Ingang ingeschakeld", + "gpio": "GPIO", + "invertState": "Inverteer GPIO-status", + "thresholdTime": "Drempeltijd statuswijziging (sec)" + }, + "output": { + "desc": "Kan worden gebruikt om een andere ketel in te schakelen via een relais.", + "enable": "Uitgang ingeschakeld", + "gpio": "GPIO", + "invertState": "Inverteer GPIO-status", + "thresholdTime": "Drempeltijd statuswijziging (sec)", + "events": { + "desc": "Gebeurtenissen", + "onFault": "Als de storingsstatus actief is", + "onLossConnection": "Als de verbinding via Opentherm is verbroken", + "onEnabledHeating": "Als de verwarming is ingeschakeld" + } + } + } + }, + "upgrade": { + "title": "Upgrade - OpenTherm Gateway", + "name": "Upgrade", + "section": { + "backupAndRestore": "Back-up & herstel", + "backupAndRestore.desc": "In deze sectie kunt u een back-up van ALLE instellingen opslaan en herstellen.", + "upgrade": "Upgrade", + "upgrade.desc": "In deze sectie kunt u de firmware en het bestandssysteem van uw apparaat upgraden.
De nieuwste releases kunnen worden gedownload van de Releases-pagina van de projectrepository." + }, + "note": { + "disclaimer1": "Na een succesvolle upgrade van het bestandssysteem worden ALLE instellingen teruggezet naar de standaardwaarden! Sla een back-up op voordat u gaat upgraden.", + "disclaimer2": "Na een succesvolle upgrade zal het apparaat automatisch herstarten na 15 seconden." + }, + "settingsFile": "Instellingenbestand", + "fw": "Firmware", + "fs": "Bestandssysteem" + } + } +} From cc5bbb7a87f029bd7d0370288497a1295f5ea480 Mon Sep 17 00:00:00 2001 From: Yurii Date: Thu, 9 Oct 2025 08:22:07 +0300 Subject: [PATCH 12/17] refactor: refactoring after #184 --- src_data/pages/dashboard.html | 1 + src_data/pages/index.html | 1 + src_data/pages/network.html | 1 + src_data/pages/sensors.html | 1 + src_data/pages/settings.html | 1 + src_data/pages/upgrade.html | 1 + 6 files changed, 6 insertions(+) diff --git a/src_data/pages/dashboard.html b/src_data/pages/dashboard.html index 4a85a83..1d446f9 100644 --- a/src_data/pages/dashboard.html +++ b/src_data/pages/dashboard.html @@ -23,6 +23,7 @@ + diff --git a/src_data/pages/index.html b/src_data/pages/index.html index 0958a62..c309190 100644 --- a/src_data/pages/index.html +++ b/src_data/pages/index.html @@ -23,6 +23,7 @@ + diff --git a/src_data/pages/network.html b/src_data/pages/network.html index 1a90b04..7204437 100644 --- a/src_data/pages/network.html +++ b/src_data/pages/network.html @@ -23,6 +23,7 @@ + diff --git a/src_data/pages/sensors.html b/src_data/pages/sensors.html index 0f5d0da..c36dbb7 100644 --- a/src_data/pages/sensors.html +++ b/src_data/pages/sensors.html @@ -23,6 +23,7 @@ + diff --git a/src_data/pages/settings.html b/src_data/pages/settings.html index 49f6327..cec0087 100644 --- a/src_data/pages/settings.html +++ b/src_data/pages/settings.html @@ -23,6 +23,7 @@ + diff --git a/src_data/pages/upgrade.html b/src_data/pages/upgrade.html index c84e7eb..5cf0c85 100644 --- a/src_data/pages/upgrade.html +++ b/src_data/pages/upgrade.html @@ -23,6 +23,7 @@ + From 78b5a12e9091a7cecf0147f6c8a35eeec407cbfc Mon Sep 17 00:00:00 2001 From: Yurii Date: Thu, 9 Oct 2025 22:57:04 +0300 Subject: [PATCH 13/17] feat: setting the cooling setpoint if cooling support is enabled --- src/OpenThermTask.h | 42 +++++++++++++++++++++++++++++++++-- src/Settings.h | 6 ++++- src/strings.h | 1 + src/utils.h | 5 ++++- src_data/locales/cn.json | 3 ++- src_data/locales/en.json | 3 ++- src_data/locales/it.json | 3 ++- src_data/locales/nl.json | 3 ++- src_data/locales/ru.json | 3 ++- src_data/pages/dashboard.html | 13 ++++++++--- 10 files changed, 70 insertions(+), 12 deletions(-) diff --git a/src/OpenThermTask.h b/src/OpenThermTask.h index fd5e1d8..1dbb93b 100644 --- a/src/OpenThermTask.h +++ b/src/OpenThermTask.h @@ -236,7 +236,7 @@ protected: vars.slave.heating.active = CustomOpenTherm::isCentralHeatingActive(response); 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.cooling.active = CustomOpenTherm::isCoolingActive(response); vars.slave.ch2.active = CustomOpenTherm::isCh2Active(response); vars.slave.fault.active = CustomOpenTherm::isFault(response); @@ -250,7 +250,7 @@ protected: Log.snoticeln( 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.ch2.active, vars.slave.fault.active, vars.slave.diag.active + vars.slave.flame, vars.slave.cooling.active, vars.slave.ch2.active, vars.slave.fault.active, vars.slave.diag.active ); } @@ -318,6 +318,8 @@ protected: vars.slave.dhw.enabled = false; vars.slave.dhw.active = false; vars.slave.flame = false; + vars.slave.cooling.active = false; + vars.slave.cooling.setpoint = 0; vars.slave.fault.active = false; vars.slave.fault.code = 0; vars.slave.diag.active = false; @@ -688,6 +690,22 @@ protected: this->prevUpdateNonEssentialVars = millis(); } + // Set cooling setpoint = heating max modulation + if (settings.opentherm.options.coolingSupport) { + if (this->setCoolingSetpoint(settings.heating.maxModulation)) { + Log.snoticeln( + FPSTR(L_OT), F("Set cooling setpoint: %hhu%% (response: %hhu%%)"), + settings.heating.maxModulation, vars.slave.cooling.setpoint + ); + + } else { + Log.swarningln( + FPSTR(L_OT), F("Failed set cooling setpoint: %hhu%% (response: %hhu%%)"), + settings.heating.maxModulation, vars.slave.cooling.setpoint + ); + } + } + // Set max modulation level uint8_t targetMaxModulation = vars.slave.modulation.max; if (vars.slave.heating.active) { @@ -1568,6 +1586,26 @@ protected: return CustomOpenTherm::getUInt(response) == request; } + bool setCoolingSetpoint(const uint8_t value) { + const unsigned int request = CustomOpenTherm::toFloat(value); + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + OpenThermRequestType::WRITE_DATA, + OpenThermMessageID::CoolingControl, + request + )); + + if (!CustomOpenTherm::isValidResponse(response)) { + return false; + + } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::CoolingControl)) { + return false; + } + + vars.slave.cooling.setpoint = CustomOpenTherm::getFloat(response); + + return CustomOpenTherm::getUInt(response) == request; + } + bool setMaxModulationLevel(const uint8_t value) { const unsigned int request = CustomOpenTherm::toFloat(value); const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( diff --git a/src/Settings.h b/src/Settings.h index 1fded9f..80ab827 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -329,10 +329,14 @@ struct Variables { bool connected = false; bool flame = false; - bool cooling = false; float pressure = 0.0f; float heatExchangerTemp = 0.0f; + struct { + bool active = false; + uint8_t setpoint = 0; + } cooling; + struct { bool active = false; uint8_t code = 0; diff --git a/src/strings.h b/src/strings.h index 74e2eaf..268d73e 100644 --- a/src/strings.h +++ b/src/strings.h @@ -190,6 +190,7 @@ const char S_STA[] PROGMEM = "sta"; const char S_STATE[] PROGMEM = "state"; const char S_STATIC_CONFIG[] PROGMEM = "staticConfig"; const char S_STATUS_LED_GPIO[] PROGMEM = "statusLedGpio"; +const char S_SETPOINT[] PROGMEM = "setpoint"; const char S_SETPOINT_TEMP[] PROGMEM = "setpointTemp"; const char S_SUBNET[] PROGMEM = "subnet"; const char S_SUMMER_WINTER_MODE[] PROGMEM = "summerWinterMode"; diff --git a/src/utils.h b/src/utils.h index ac4ffa2..85db0cc 100644 --- a/src/utils.h +++ b/src/utils.h @@ -2060,7 +2060,10 @@ void varsToJson(const Variables& src, JsonVariant dst) { slave[FPSTR(S_PROTOCOL_VERSION)] = src.slave.appVersion; slave[FPSTR(S_CONNECTED)] = src.slave.connected; slave[FPSTR(S_FLAME)] = src.slave.flame; - slave[FPSTR(S_COOLING)] = src.slave.cooling; + + auto sCooling = slave[FPSTR(S_COOLING)].to(); + sCooling[FPSTR(S_ACTIVE)] = src.slave.cooling.active; + sCooling[FPSTR(S_SETPOINT)] = src.slave.cooling.setpoint; auto sModulation = slave[FPSTR(S_MODULATION)].to(); sModulation[FPSTR(S_MIN)] = src.slave.modulation.min; diff --git a/src_data/locales/cn.json b/src_data/locales/cn.json index 51ffb01..abf02f8 100644 --- a/src_data/locales/cn.json +++ b/src_data/locales/cn.json @@ -109,7 +109,8 @@ "sConnected": "OpenTherm 通讯状态", "sFlame": "火焰", - "sCooling": "制冷", + "sCoolingActive": "制冷", + "sCoolingSetpoint": "冷却设定点", "sFaultActive": "报警状态", "sFaultCode": "报警代码", "sDiagActive": "诊断状态", diff --git a/src_data/locales/en.json b/src_data/locales/en.json index c801561..c3f880d 100644 --- a/src_data/locales/en.json +++ b/src_data/locales/en.json @@ -109,7 +109,8 @@ "sConnected": "OpenTherm connection", "sFlame": "Flame", - "sCooling": "Cooling", + "sCoolingActive": "Cooling", + "sCoolingSetpoint": "Cooling setpoint", "sFaultActive": "Fault", "sFaultCode": "Fault code", "sDiagActive": "Diagnostic", diff --git a/src_data/locales/it.json b/src_data/locales/it.json index 9a13be8..454fb8e 100644 --- a/src_data/locales/it.json +++ b/src_data/locales/it.json @@ -109,7 +109,8 @@ "sConnected": "Connessione OpenTherm", "sFlame": "Fiamma", - "sCooling": "Raffrescamento", + "sCoolingActive": "Raffrescamento", + "sCoolingSetpoint": "Raffrescamento setpoint", "sFaultActive": "Anomalia", "sFaultCode": "Codice anomalia", "sDiagActive": "Diagnostica", diff --git a/src_data/locales/nl.json b/src_data/locales/nl.json index 9464def..70cacd3 100644 --- a/src_data/locales/nl.json +++ b/src_data/locales/nl.json @@ -99,7 +99,8 @@ "mCascadeControlOutput": "Cascaderegeling (uitgang)", "sConnected": "OpenTherm-verbinding", "sFlame": "Vlam", - "sCooling": "Koeling", + "sCoolingActive": "Koeling", + "sCoolingSetpoint": "Koelinstelpunt", "sFaultActive": "Storing", "sFaultCode": "Storingscode", "sDiagActive": "Diagnose", diff --git a/src_data/locales/ru.json b/src_data/locales/ru.json index 28055f7..b712c3c 100644 --- a/src_data/locales/ru.json +++ b/src_data/locales/ru.json @@ -109,7 +109,8 @@ "sConnected": "Подключение к OpenTherm", "sFlame": "Пламя", - "sCooling": "Охлаждение", + "sCoolingActive": "Охлаждение", + "sCoolingSetpoint": "Охлаждение, уставка", "sFaultActive": "Ошибка", "sFaultCode": "Код ошибки", "sDiagActive": "Диагностика", diff --git a/src_data/pages/dashboard.html b/src_data/pages/dashboard.html index 1d446f9..f01b72b 100644 --- a/src_data/pages/dashboard.html +++ b/src_data/pages/dashboard.html @@ -154,9 +154,14 @@ dashboard.states.sFlame + - dashboard.states.sCooling - + dashboard.states.sCoolingActive + + + + dashboard.states.sCoolingSetpoint + % @@ -558,7 +563,9 @@ result.slave.connected ? "green" : "red" ); setState('.sFlame', result.slave.flame); - setState('.sCooling', result.slave.cooling); + + setState('.sCoolingActive', result.slave.cooling.active); + setValue('.sCoolingSetpoint', result.slave.cooling.setpoint); setValue('.sModMin', result.slave.modulation.min); setValue('.sModMax', result.slave.modulation.max); From cf81ff585618c9da8614b28133a75f4eefbcee13 Mon Sep 17 00:00:00 2001 From: Yurii Date: Fri, 10 Oct 2025 05:35:57 +0300 Subject: [PATCH 14/17] refactor: improved work with BLE sensors --- src/SensorsTask.h | 53 +++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/src/SensorsTask.h b/src/SensorsTask.h index 1031536..0a8801b 100644 --- a/src/SensorsTask.h +++ b/src/SensorsTask.h @@ -450,13 +450,17 @@ protected: } for (auto& [sensorId, pClient]: this->bleClients) { - auto& sSensor = Sensors::settings[sensorId]; + if (pClient == nullptr) { + continue; + } - if (!sSensor.enabled || sSensor.type != Sensors::Type::BLUETOOTH || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) { + auto& sSensor = Sensors::settings[sensorId]; + const auto sAddress = NimBLEAddress(sSensor.address, 0); + + if (sAddress.isNull() || !sSensor.enabled || sSensor.type != Sensors::Type::BLUETOOTH || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) { Log.sinfoln( - FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s', deleted unused client, address: %s"), - sensorId, sSensor.name, - pClient->getPeerAddress().toString().c_str() + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s', deleted unused client"), + sensorId, sSensor.name ); NimBLEDevice::deleteClient(pClient); @@ -484,16 +488,36 @@ protected: continue; } + const auto address = NimBLEAddress(sSensor.address, 0); + if (address.isNull()) { + continue; + } + auto pClient = this->getBleClient(sensorId); if (pClient == nullptr) { continue; } + if (pClient->getPeerAddress() != address) { + if (pClient->isConnected()) { + if (!pClient->disconnect()) { + continue; + } + } + + pClient->setPeerAddress(address); + } + if (!pClient->isConnected()) { this->bleSubscribed[sensorId] = false; this->bleLastSetDtTime[sensorId] = 0; - pClient->connect(true, true, true); + if (pClient->connect(false, true, true)) { + Log.sinfoln( + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': trying connecting to %s..."), + sensorId, sSensor.name, pClient->getPeerAddress().toString().c_str() + ); + } continue; } @@ -548,29 +572,18 @@ protected: if (!sSensor.enabled || sSensor.type != Sensors::Type::BLUETOOTH || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) { return nullptr; } - - const auto address = NimBLEAddress(sSensor.address, 0); - if (address.isNull()) { - return nullptr; - } if (this->bleClients[sensorId] && this->bleClients[sensorId] != nullptr) { return this->bleClients[sensorId]; } - auto pClient = NimBLEDevice::createClient(address); + auto pClient = NimBLEDevice::createClient(); if (pClient == nullptr) { return nullptr; } - /** - * Set initial connection parameters: - * These settings are safe for 3 clients to connect reliably, can go faster if you have less - * connections. Timeout should be a multiple of the interval, minimum is 100ms. - * Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 1000 * 10ms = 10000ms timeout - */ - pClient->setConnectionParams(12, 12, 0, 1000); - pClient->setConnectTimeout(10000); + //pClient->setConnectionParams(BLE_GAP_CONN_ITVL_MS(10), BLE_GAP_CONN_ITVL_MS(100), 10, 150); + pClient->setConnectTimeout(30000); pClient->setSelfDelete(false, false); pClient->setClientCallbacks(new BluetoothClientCallbacks(sensorId), true); From 56671999882c046846785ddd63ab9a44ed2c3d46 Mon Sep 17 00:00:00 2001 From: Yurii Date: Tue, 14 Oct 2025 07:38:30 +0300 Subject: [PATCH 15/17] refactor: reset BLE/DALLAS address if not valid --- src/utils.h | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/utils.h b/src/utils.h index 85db0cc..5824c2f 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1930,12 +1930,20 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se ); if (parsed == 8) { - for (uint8_t i = 0; i < 8; i++) { + for (uint8_t i = 0; i < parsed; i++) { if (dst.address[i] != tmp[i]) { dst.address[i] = tmp[i]; changed = true; } } + + } else { + // reset + for (uint8_t i = 0; i < sizeof(dst.address); i++) { + dst.address[i] = 0x00; + } + + changed = true; } } else if (dst.type == Sensors::Type::BLUETOOTH) { @@ -1948,12 +1956,20 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se ); if (parsed == 6) { - for (uint8_t i = 0; i < 6; i++) { + for (uint8_t i = 0; i < parsed; i++) { if (dst.address[i] != tmp[i]) { dst.address[i] = tmp[i]; changed = true; } } + + } else { + // reset + for (uint8_t i = 0; i < sizeof(dst.address); i++) { + dst.address[i] = 0x00; + } + + changed = true; } } } From b9010643f063ecb257b6c64dc1e9b9c3400e0ba0 Mon Sep 17 00:00:00 2001 From: Yurii Date: Fri, 31 Oct 2025 21:08:35 +0300 Subject: [PATCH 16/17] feat: added GPIO inversion setting for extpump --- src/MainTask.h | 42 ++++++++++++++++++++++++++++++------ src/Settings.h | 1 + src/utils.h | 10 +++++++++ src_data/locales/cn.json | 1 + src_data/locales/en.json | 1 + src_data/locales/it.json | 1 + src_data/locales/nl.json | 1 + src_data/locales/ru.json | 1 + src_data/pages/settings.html | 6 ++++++ 9 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/MainTask.h b/src/MainTask.h index af02b60..8d746e0 100644 --- a/src/MainTask.h +++ b/src/MainTask.h @@ -609,7 +609,12 @@ protected: if (GPIO_IS_VALID(settings.externalPump.gpio)) { configuredGpio = settings.externalPump.gpio; pinMode(configuredGpio, OUTPUT); - digitalWrite(configuredGpio, LOW); + digitalWrite( + configuredGpio, + settings.externalPump.invertState + ? HIGH + : LOW + ); } else if (configuredGpio != GPIO_IS_NOT_CONFIGURED) { configuredGpio = GPIO_IS_NOT_CONFIGURED; @@ -637,7 +642,12 @@ protected: if (!settings.externalPump.use) { if (vars.externalPump.state) { - digitalWrite(configuredGpio, LOW); + digitalWrite( + configuredGpio, + settings.externalPump.invertState + ? HIGH + : LOW + ); vars.externalPump.state = false; vars.externalPump.lastEnabledTime = millis(); @@ -650,7 +660,12 @@ protected: if (vars.externalPump.state && !this->heatingEnabled) { if (this->extPumpStartReason == MainTask::PumpStartReason::HEATING && millis() - this->heatingDisabledTime > (settings.externalPump.postCirculationTime * 1000u)) { - digitalWrite(configuredGpio, LOW); + digitalWrite( + configuredGpio, + settings.externalPump.invertState + ? HIGH + : LOW + ); vars.externalPump.state = false; vars.externalPump.lastEnabledTime = millis(); @@ -658,7 +673,12 @@ protected: Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: expired post circulation time")); } else if (this->extPumpStartReason == MainTask::PumpStartReason::ANTISTUCK && millis() - this->externalPumpStartTime >= (settings.externalPump.antiStuckTime * 1000u)) { - digitalWrite(configuredGpio, LOW); + digitalWrite( + configuredGpio, + settings.externalPump.invertState + ? HIGH + : LOW + ); vars.externalPump.state = false; vars.externalPump.lastEnabledTime = millis(); @@ -674,7 +694,12 @@ protected: this->externalPumpStartTime = millis(); this->extPumpStartReason = MainTask::PumpStartReason::HEATING; - digitalWrite(configuredGpio, HIGH); + digitalWrite( + configuredGpio, + settings.externalPump.invertState + ? LOW + : HIGH + ); Log.sinfoln(FPSTR(L_EXTPUMP), F("Enabled: heating on")); @@ -683,7 +708,12 @@ protected: this->externalPumpStartTime = millis(); this->extPumpStartReason = MainTask::PumpStartReason::ANTISTUCK; - digitalWrite(configuredGpio, HIGH); + digitalWrite( + configuredGpio, + settings.externalPump.invertState + ? LOW + : HIGH + ); Log.sinfoln(FPSTR(L_EXTPUMP), F("Enabled: anti stuck")); } diff --git a/src/Settings.h b/src/Settings.h index 80ab827..ecb4d15 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -162,6 +162,7 @@ struct Settings { struct { bool use = false; uint8_t gpio = DEFAULT_EXT_PUMP_GPIO; + bool invertState = false; unsigned short postCirculationTime = 600; unsigned int antiStuckInterval = 2592000; unsigned short antiStuckTime = 300; diff --git a/src/utils.h b/src/utils.h index 5824c2f..8442dbf 100644 --- a/src/utils.h +++ b/src/utils.h @@ -542,6 +542,7 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) { auto externalPump = dst[FPSTR(S_EXTERNAL_PUMP)].to(); externalPump[FPSTR(S_USE)] = src.externalPump.use; externalPump[FPSTR(S_GPIO)] = src.externalPump.gpio; + externalPump[FPSTR(S_INVERT_STATE)] = src.externalPump.invertState; externalPump[FPSTR(S_POST_CIRCULATION_TIME)] = roundf(src.externalPump.postCirculationTime / 60, 0); externalPump[FPSTR(S_ANTI_STUCK_INTERVAL)] = roundf(src.externalPump.antiStuckInterval / 86400, 0); externalPump[FPSTR(S_ANTI_STUCK_TIME)] = roundf(src.externalPump.antiStuckTime / 60, 0); @@ -1492,6 +1493,15 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } + if (src[FPSTR(S_EXTERNAL_PUMP)][FPSTR(S_INVERT_STATE)].is()) { + bool value = src[FPSTR(S_EXTERNAL_PUMP)][FPSTR(S_INVERT_STATE)].as(); + + if (value != dst.externalPump.invertState) { + dst.externalPump.invertState = value; + changed = true; + } + } + if (!src[FPSTR(S_EXTERNAL_PUMP)][FPSTR(S_POST_CIRCULATION_TIME)].isNull()) { unsigned short value = src[FPSTR(S_EXTERNAL_PUMP)][FPSTR(S_POST_CIRCULATION_TIME)].as(); diff --git a/src_data/locales/cn.json b/src_data/locales/cn.json index abf02f8..10a3a6b 100644 --- a/src_data/locales/cn.json +++ b/src_data/locales/cn.json @@ -455,6 +455,7 @@ "extPump": { "use": "使用外置循环泵", "gpio": "继电器 GPIO引脚", + "invertState": "切换 GPIO 状态", "postCirculationTime": "后循环时间 (分钟)", "antiStuckInterval": "防卡死间隔时间(天)", "antiStuckTime": "防卡死运行时长(分钟)" diff --git a/src_data/locales/en.json b/src_data/locales/en.json index c3f880d..24ac153 100644 --- a/src_data/locales/en.json +++ b/src_data/locales/en.json @@ -455,6 +455,7 @@ "extPump": { "use": "Use external pump", "gpio": "Relay GPIO", + "invertState": "Invert GPIO state", "postCirculationTime": "Post circulation time (min)", "antiStuckInterval": "Anti stuck interval (days)", "antiStuckTime": "Anti stuck time (min)" diff --git a/src_data/locales/it.json b/src_data/locales/it.json index 454fb8e..2e4f15c 100644 --- a/src_data/locales/it.json +++ b/src_data/locales/it.json @@ -455,6 +455,7 @@ "extPump": { "use": "Usa pompa/circolatore esterno", "gpio": "GPIO relè", + "invertState": "Inverti stato GPIO", "postCirculationTime": "Tempo di post circolazione (min)", "antiStuckInterval": "Intervallo antiblocco (days)", "antiStuckTime": "Tempo antiblocco (min)" diff --git a/src_data/locales/nl.json b/src_data/locales/nl.json index 70cacd3..65ff5a3 100644 --- a/src_data/locales/nl.json +++ b/src_data/locales/nl.json @@ -417,6 +417,7 @@ "extPump": { "use": "Gebruik externe pomp", "gpio": "Relais GPIO", + "invertState": "Inverteer GPIO-status", "postCirculationTime": "Nacirculatietijd (min)", "antiStuckInterval": "Anti-vastloopinterval (dagen)", "antiStuckTime": "Anti-vastlooptijd (min)" diff --git a/src_data/locales/ru.json b/src_data/locales/ru.json index b712c3c..7a6e627 100644 --- a/src_data/locales/ru.json +++ b/src_data/locales/ru.json @@ -455,6 +455,7 @@ "extPump": { "use": "Использовать доп. насос", "gpio": "GPIO реле", + "invertState": "Инвертировать состояние GPIO", "postCirculationTime": "Время постциркуляции (в минутах)", "antiStuckInterval": "Интервал защиты от блокировки (в днях)", "antiStuckTime": "Время работы насоса (в минутах)" diff --git a/src_data/pages/settings.html b/src_data/pages/settings.html index cec0087..f5c4494 100644 --- a/src_data/pages/settings.html +++ b/src_data/pages/settings.html @@ -731,6 +731,11 @@ settings.extPump.use + +
@@ -928,6 +933,7 @@ // Extpump setCheckboxValue("[name='externalPump[use]']", data.externalPump.use); setInputValue("[name='externalPump[gpio]']", data.externalPump.gpio < 255 ? data.externalPump.gpio : ''); + setCheckboxValue("[name='externalPump[invertState]']", data.externalPump.invertState); setInputValue("[name='externalPump[postCirculationTime]']", data.externalPump.postCirculationTime); setInputValue("[name='externalPump[antiStuckInterval]']", data.externalPump.antiStuckInterval); setInputValue("[name='externalPump[antiStuckTime]']", data.externalPump.antiStuckTime); From d4603aa0de7dab240fad4fd3b895effa7ece4d86 Mon Sep 17 00:00:00 2001 From: Yurii Date: Sun, 2 Nov 2025 23:43:02 +0300 Subject: [PATCH 17/17] fix: compatibility with HA 2025.10+ fixed #189 --- lib/HomeAssistantHelper/strings.h | 2 +- src/HaHelper.h | 78 +++++++++++++++---------------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/lib/HomeAssistantHelper/strings.h b/lib/HomeAssistantHelper/strings.h index da99954..6c0eb3b 100644 --- a/lib/HomeAssistantHelper/strings.h +++ b/lib/HomeAssistantHelper/strings.h @@ -12,6 +12,7 @@ const char HA_ENTITY_SELECT[] PROGMEM = "select"; const char HA_ENTITY_SENSOR[] PROGMEM = "sensor"; const char HA_ENTITY_SWITCH[] PROGMEM = "switch"; +const char HA_DEFAULT_ENTITY_ID[] PROGMEM = "def_ent_id"; const char HA_DEVICE[] PROGMEM = "device"; const char HA_IDENTIFIERS[] PROGMEM = "identifiers"; const char HA_SW_VERSION[] PROGMEM = "sw_version"; @@ -23,7 +24,6 @@ const char HA_COMMAND_TOPIC[] PROGMEM = "command_topic"; const char HA_COMMAND_TEMPLATE[] PROGMEM = "command_template"; const char HA_ENABLED_BY_DEFAULT[] PROGMEM = "enabled_by_default"; const char HA_UNIQUE_ID[] PROGMEM = "unique_id"; -const char HA_OBJECT_ID[] PROGMEM = "object_id"; const char HA_ENTITY_CATEGORY[] PROGMEM = "entity_category"; const char HA_ENTITY_CATEGORY_DIAGNOSTIC[] PROGMEM = "diagnostic"; const char HA_ENTITY_CATEGORY_CONFIG[] PROGMEM = "config"; diff --git a/src/HaHelper.h b/src/HaHelper.h index 69ba800..311bcbb 100644 --- a/src/HaHelper.h +++ b/src/HaHelper.h @@ -262,7 +262,7 @@ public: // object id's doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(objId.c_str()); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; const String& configTopic = this->makeConfigTopic( sSensor.type == Sensors::Type::MANUAL ? FPSTR(HA_ENTITY_NUMBER) : FPSTR(HA_ENTITY_SENSOR), @@ -324,7 +324,7 @@ public: // object id's doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(objId.c_str()); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; // state topic doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic( @@ -371,7 +371,7 @@ public: // object id's doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(objId.c_str()); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; // state topic doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic( @@ -422,7 +422,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("heating_turbo")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_NAME)] = F("Turbo heating"); doc[FPSTR(HA_ICON)] = F("mdi:rocket-launch-outline"); @@ -444,7 +444,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("heating_hysteresis")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = FPSTR(S_TEMPERATURE); @@ -476,7 +476,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("heating_turbo_factor")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("power_factor"); doc[FPSTR(HA_NAME)] = F("Heating turbo factor"); @@ -500,7 +500,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("heating_min_temp")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = FPSTR(S_TEMPERATURE); @@ -534,7 +534,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("heating_max_temp")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = FPSTR(S_TEMPERATURE); @@ -569,7 +569,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("dhw_min_temp")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = FPSTR(S_TEMPERATURE); @@ -603,7 +603,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("dhw_max_temp")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = FPSTR(S_TEMPERATURE); @@ -638,7 +638,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("pid")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_NAME)] = F("PID"); doc[FPSTR(HA_ICON)] = F("mdi:chart-bar-stacked"); @@ -660,7 +660,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("pid_p")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_NAME)] = F("PID factor P"); doc[FPSTR(HA_ICON)] = F("mdi:alpha-p-circle-outline"); @@ -683,7 +683,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("pid_i")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_NAME)] = F("PID factor I"); doc[FPSTR(HA_ICON)] = F("mdi:alpha-i-circle-outline"); @@ -706,7 +706,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("pid_d")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_NAME)] = F("PID factor D"); doc[FPSTR(HA_ICON)] = F("mdi:alpha-d-circle-outline"); @@ -729,7 +729,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("pid_dt")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("duration"); doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("s"); @@ -754,7 +754,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("pid_min_temp")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = FPSTR(S_TEMPERATURE); @@ -788,7 +788,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("pid_max_temp")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = FPSTR(S_TEMPERATURE); @@ -823,7 +823,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("equitherm")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_NAME)] = F("Equitherm"); doc[FPSTR(HA_ICON)] = F("mdi:sun-snowflake-variant"); @@ -845,7 +845,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("equitherm_n")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_NAME)] = F("Equitherm factor N"); doc[FPSTR(HA_ICON)] = F("mdi:alpha-n-circle-outline"); @@ -868,7 +868,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("equitherm_k")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_NAME)] = F("Equitherm factor K"); doc[FPSTR(HA_ICON)] = F("mdi:alpha-k-circle-outline"); @@ -894,7 +894,7 @@ public: doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("equitherm_t")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_NAME)] = F("Equitherm factor T"); doc[FPSTR(HA_ICON)] = F("mdi:alpha-t-circle-outline"); @@ -917,7 +917,7 @@ public: JsonDocument doc; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("status")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("problem"); doc[FPSTR(HA_NAME)] = F("Status"); @@ -935,7 +935,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("emergency")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("problem"); doc[FPSTR(HA_NAME)] = F("Emergency"); @@ -953,7 +953,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("ot_status")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("connectivity"); doc[FPSTR(HA_NAME)] = F("Opentherm status"); @@ -974,7 +974,7 @@ public: doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("heating")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; //doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("running"); doc[FPSTR(HA_NAME)] = F("Heating"); @@ -995,7 +995,7 @@ public: doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("dhw")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; //doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("running"); doc[FPSTR(HA_NAME)] = F("DHW"); @@ -1016,7 +1016,7 @@ public: doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("flame")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; //doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("running"); doc[FPSTR(HA_NAME)] = F("Flame"); @@ -1037,7 +1037,7 @@ public: doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("fault")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("problem"); doc[FPSTR(HA_NAME)] = F("Fault"); @@ -1058,7 +1058,7 @@ public: doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC)); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("problem"); doc[FPSTR(HA_NAME)] = F("Diagnostic"); @@ -1076,7 +1076,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("ext_pump")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("running"); doc[FPSTR(HA_NAME)] = F("External pump"); @@ -1097,7 +1097,7 @@ public: doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("fault_code")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_NAME)] = F("Fault code"); doc[FPSTR(HA_ICON)] = F("mdi:cog-box"); @@ -1117,7 +1117,7 @@ public: doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("diagnostic_code")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_NAME)] = F("Diagnostic code"); doc[FPSTR(HA_ICON)] = F("mdi:information-box"); @@ -1134,7 +1134,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(FPSTR(S_RSSI)); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("signal_strength"); doc[FPSTR(HA_STATE_CLASS)] = FPSTR(HA_STATE_CLASS_MEASUREMENT); @@ -1154,7 +1154,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("uptime")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("duration"); doc[FPSTR(HA_STATE_CLASS)] = F("total_increasing"); @@ -1175,7 +1175,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("heating")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_NAME)] = F("Heating"); doc[FPSTR(HA_ICON)] = F("mdi:radiator"); @@ -1227,7 +1227,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("dhw")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_NAME)] = F("DHW"); doc[FPSTR(HA_ICON)] = F("mdi:faucet"); @@ -1272,7 +1272,7 @@ public: doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(FPSTR(S_RESTART)); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = FPSTR(S_RESTART); doc[FPSTR(HA_NAME)] = F("Restart"); @@ -1292,7 +1292,7 @@ public: doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("reset_fault")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = FPSTR(S_RESTART); doc[FPSTR(HA_NAME)] = F("Reset fault"); @@ -1312,7 +1312,7 @@ public: doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("reset_diagnostic")); - doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; + doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = FPSTR(S_RESTART); doc[FPSTR(HA_NAME)] = F("Reset diagnostic");