From e71f3868fdabe76230215f3570472cb86593c342 Mon Sep 17 00:00:00 2001 From: Yurii Date: Sat, 9 Nov 2024 17:10:26 +0300 Subject: [PATCH 01/42] refactor: dynamic sensors --- lib/BufferedWebServer/BufferedWebServer.h | 2 +- lib/CustomOpenTherm/CustomOpenTherm.h | 60 - lib/HomeAssistantHelper/HomeAssistantHelper.h | 34 +- lib/HomeAssistantHelper/strings.h | 3 + lib/MqttWriter/MqttWriter.h | 2 +- platformio.ini | 10 +- secrets.default.ini | 5 +- src/HaHelper.h | 1681 +++++++---------- src/MainTask.h | 137 +- src/MqttTask.h | 314 ++- src/OpenThermTask.h | 1333 ++++++++----- src/PortalTask.h | 180 +- src/RegulatorTask.h | 117 +- src/Sensors.h | 424 +++++ src/SensorsTask.h | 881 +++++---- src/Settings.h | 284 ++- src/defines.h | 41 +- src/main.cpp | 94 +- src/strings.h | 11 +- src/utils.h | 932 ++++----- src_data/locales/en.json | 183 +- src_data/locales/ru.json | 193 +- src_data/pages/dashboard.html | 360 ++-- src_data/pages/index.html | 3 +- src_data/pages/sensors.html | 283 +++ src_data/pages/settings.html | 205 +- src_data/scripts/utils.js | 180 +- 27 files changed, 4615 insertions(+), 3337 deletions(-) create mode 100644 src/Sensors.h create mode 100644 src_data/pages/sensors.html diff --git a/lib/BufferedWebServer/BufferedWebServer.h b/lib/BufferedWebServer/BufferedWebServer.h index 8c04c14..1ba3881 100644 --- a/lib/BufferedWebServer/BufferedWebServer.h +++ b/lib/BufferedWebServer/BufferedWebServer.h @@ -10,7 +10,7 @@ class BufferedWebServer { free(this->buffer); } - void send(int code, const char* contentType, JsonDocument& content, bool pretty = false) { + void send(int code, const char* contentType, const JsonVariantConst content, bool pretty = false) { #ifdef ARDUINO_ARCH_ESP8266 if (!this->webServer->chunkedResponseModeStart(code, contentType)) { this->webServer->send(505, F("text/html"), F("HTTP1.1 required")); diff --git a/lib/CustomOpenTherm/CustomOpenTherm.h b/lib/CustomOpenTherm/CustomOpenTherm.h index 600f59a..4ae34d8 100644 --- a/lib/CustomOpenTherm/CustomOpenTherm.h +++ b/lib/CustomOpenTherm/CustomOpenTherm.h @@ -98,66 +98,6 @@ class CustomOpenTherm : public OpenTherm { )); } - bool setHeatingCh1Temp(float temperature) { - unsigned long response = this->sendRequest(buildRequest( - OpenThermMessageType::WRITE_DATA, - OpenThermMessageID::TSet, - temperatureToData(temperature) - )); - - return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::TSet); - } - - bool setHeatingCh2Temp(float temperature) { - unsigned long response = this->sendRequest(buildRequest( - OpenThermMessageType::WRITE_DATA, - OpenThermMessageID::TsetCH2, - temperatureToData(temperature) - )); - - return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::TsetCH2); - } - - bool setDhwTemp(float temperature) { - unsigned long response = this->sendRequest(buildRequest( - OpenThermMessageType::WRITE_DATA, - OpenThermMessageID::TdhwSet, - temperatureToData(temperature) - )); - - return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::TdhwSet); - } - - bool setRoomSetpoint(float temperature) { - unsigned long response = this->sendRequest(buildRequest( - OpenThermMessageType::WRITE_DATA, - OpenThermMessageID::TrSet, - temperatureToData(temperature) - )); - - return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::TrSet); - } - - bool setRoomSetpointCh2(float temperature) { - unsigned long response = this->sendRequest(buildRequest( - OpenThermMessageType::WRITE_DATA, - OpenThermMessageID::TrSetCH2, - temperatureToData(temperature) - )); - - return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::TrSetCH2); - } - - bool setRoomTemp(float temperature) { - unsigned long response = this->sendRequest(buildRequest( - OpenThermMessageType::WRITE_DATA, - OpenThermMessageID::Tr, - temperatureToData(temperature) - )); - - return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::Tr); - } - bool sendBoilerReset() { unsigned int data = 1; data <<= 8; diff --git a/lib/HomeAssistantHelper/HomeAssistantHelper.h b/lib/HomeAssistantHelper/HomeAssistantHelper.h index c27f028..b18fe6d 100644 --- a/lib/HomeAssistantHelper/HomeAssistantHelper.h +++ b/lib/HomeAssistantHelper/HomeAssistantHelper.h @@ -100,8 +100,8 @@ class HomeAssistantHelper { return result; } - template - String getTopic(T category, T name, char nameSeparator = '/') { + template + String makeConfigTopic(CT category, NT name, char nameSeparator = '/') { String topic = ""; topic.concat(this->prefix); topic.concat('/'); @@ -115,16 +115,40 @@ class HomeAssistantHelper { } template - String getDeviceTopic(T value, char separator = '/') { + String getDeviceTopic(T value, char dpvSeparator = '/') { String topic = ""; topic.concat(this->devicePrefix); - topic.concat(separator); + topic.concat(dpvSeparator); topic.concat(value); return topic; } + template + String getDeviceTopic(CT category, NT name, char dpcSeparator = '/', char cnSeparator = '/') { + String topic = ""; + topic.concat(this->devicePrefix); + topic.concat(dpcSeparator); + topic.concat(category); + topic.concat(cnSeparator); + topic.concat(name); + return topic; + } + + template + String getDeviceTopic(CT category, NT name, ST suffix, char dpcSeparator = '/', char cnSeparator = '/', char nsSeparator = '/') { + String topic = ""; + topic.concat(this->devicePrefix); + topic.concat(dpcSeparator); + topic.concat(category); + topic.concat(cnSeparator); + topic.concat(name); + topic.concat(nsSeparator); + topic.concat(suffix); + return topic; + } + template - String getObjectId(T value, char separator = '_') { + String getObjectIdWithPrefix(T value, char separator = '_') { String topic = ""; topic.concat(this->devicePrefix); topic.concat(separator); diff --git a/lib/HomeAssistantHelper/strings.h b/lib/HomeAssistantHelper/strings.h index b9ed038..e98237d 100644 --- a/lib/HomeAssistantHelper/strings.h +++ b/lib/HomeAssistantHelper/strings.h @@ -25,6 +25,8 @@ 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"; const char HA_STATE_TOPIC[] PROGMEM = "state_topic"; const char HA_VALUE_TEMPLATE[] PROGMEM = "value_template"; const char HA_OPTIONS[] PROGMEM = "options"; @@ -45,6 +47,7 @@ const char HA_STATE_OFF[] PROGMEM = "state_off"; const char HA_PAYLOAD_ON[] PROGMEM = "payload_on"; const char HA_PAYLOAD_OFF[] PROGMEM = "payload_off"; const char HA_STATE_CLASS[] PROGMEM = "state_class"; +const char HA_STATE_CLASS_MEASUREMENT[] PROGMEM = "measurement"; const char HA_EXPIRE_AFTER[] PROGMEM = "expire_after"; const char HA_CURRENT_TEMPERATURE_TOPIC[] PROGMEM = "current_temperature_topic"; const char HA_CURRENT_TEMPERATURE_TEMPLATE[] PROGMEM = "current_temperature_template"; diff --git a/lib/MqttWriter/MqttWriter.h b/lib/MqttWriter/MqttWriter.h index 03c54df..9c0fa45 100644 --- a/lib/MqttWriter/MqttWriter.h +++ b/lib/MqttWriter/MqttWriter.h @@ -77,7 +77,7 @@ class MqttWriter { #endif } - bool publish(const char* topic, JsonDocument& doc, bool retained = false) { + bool publish(const char* topic, const JsonVariantConst doc, bool retained = false) { if (!this->client->connected()) { this->bufferPos = 0; return false; diff --git a/platformio.ini b/platformio.ini index dbd28d2..835d884 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,7 +14,7 @@ extra_configs = secrets.default.ini core_dir = .pio [env] -version = 1.4.5 +version = 1.5.0-alpha framework = arduino lib_deps = bblanchon/ArduinoJson@^7.1.0 @@ -38,9 +38,9 @@ build_flags = ;-D DEBUG_ESP_CORE -D DEBUG_ESP_WIFI -D DEBUG_ESP_HTTP_SERVER -D DEBUG_ESP_PORT=Serial -D BUILD_VERSION='"${this.version}"' -D BUILD_ENV='"$PIOENV"' - -D DEFAULT_SERIAL_ENABLE=${secrets.serial_enable} + -D DEFAULT_SERIAL_ENABLED=${secrets.serial_enabled} -D DEFAULT_SERIAL_BAUD=${secrets.serial_baud} - -D DEFAULT_TELNET_ENABLE=${secrets.telnet_enable} + -D DEFAULT_TELNET_ENABLED=${secrets.telnet_enabled} -D DEFAULT_TELNET_PORT=${secrets.telnet_port} -D DEFAULT_LOG_LEVEL=${secrets.log_level} -D DEFAULT_HOSTNAME='"${secrets.hostname}"' @@ -50,6 +50,7 @@ build_flags = -D DEFAULT_STA_PASSWORD='"${secrets.sta_password}"' -D DEFAULT_PORTAL_LOGIN='"${secrets.portal_login}"' -D DEFAULT_PORTAL_PASSWORD='"${secrets.portal_password}"' + -D DEFAULT_MQTT_ENABLED=${secrets.mqtt_enabled} -D DEFAULT_MQTT_SERVER='"${secrets.mqtt_server}"' -D DEFAULT_MQTT_PORT=${secrets.mqtt_port} -D DEFAULT_MQTT_USER='"${secrets.mqtt_user}"' @@ -57,7 +58,8 @@ build_flags = -D DEFAULT_MQTT_PREFIX='"${secrets.mqtt_prefix}"' upload_speed = 921600 monitor_speed = 115200 -monitor_filters = direct +;monitor_filters = direct +monitor_filters = esp32_exception_decoder board_build.flash_mode = dio board_build.filesystem = littlefs diff --git a/secrets.default.ini b/secrets.default.ini index e19d0a4..f478977 100644 --- a/secrets.default.ini +++ b/secrets.default.ini @@ -1,9 +1,9 @@ [secrets] build_type = release -serial_enable = true +serial_enabled = true serial_baud = 115200 -telnet_enable = true +telnet_enabled = true telnet_port = 23 log_level = 5 hostname = opentherm @@ -17,6 +17,7 @@ sta_password = portal_login = admin portal_password = admin +mqtt_enabled = false mqtt_server = mqtt_port = 1883 mqtt_user = diff --git a/src/HaHelper.h b/src/HaHelper.h index 1a71f3e..50755e6 100644 --- a/src/HaHelper.h +++ b/src/HaHelper.h @@ -5,36 +5,403 @@ class HaHelper : public HomeAssistantHelper { public: static const byte TEMP_SOURCE_HEATING = 0; static const byte TEMP_SOURCE_INDOOR = 1; + static const char AVAILABILITY_OT_CONN[]; + static const char AVAILABILITY_SENSOR_CONN[]; + + void setExpireAfter(unsigned short value) { + this->expireAfter = value; + } + + auto getExpireAfter() { + return this->expireAfter; + } + + bool publishDynamicSensor(Sensors::Settings& sSensor, Sensors::ValueType vType = Sensors::ValueType::PRIMARY, UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { + JsonDocument doc; + + // set device class & unit of measurement + switch (sSensor.purpose) { + case Sensors::Purpose::OUTDOOR_TEMP: + case Sensors::Purpose::INDOOR_TEMP: + case Sensors::Purpose::HEATING_TEMP: + case Sensors::Purpose::HEATING_RETURN_TEMP: + case Sensors::Purpose::DHW_TEMP: + case Sensors::Purpose::DHW_RETURN_TEMP: + case Sensors::Purpose::EXHAUST_TEMP: + case Sensors::Purpose::TEMPERATURE: + doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); + if (unit == UnitSystem::METRIC) { + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C); + + if (sSensor.type == Sensors::Type::MANUAL) { + doc[FPSTR(HA_MIN)] = -99; + doc[FPSTR(HA_MAX)] = 99; + } + + } else if (unit == UnitSystem::IMPERIAL) { + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); + + if (sSensor.type == Sensors::Type::MANUAL) { + doc[FPSTR(HA_MIN)] = -147; + doc[FPSTR(HA_MAX)] = 211; + } + } + break; + + case Sensors::Purpose::DHW_FLOW_RATE: + doc[FPSTR(HA_DEVICE_CLASS)] = F("volume_flow_rate"); + if (unit == UnitSystem::METRIC) { + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C); + + } else if (unit == UnitSystem::IMPERIAL) { + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); + } + break; + + case Sensors::Purpose::MODULATION_LEVEL: + doc[FPSTR(HA_DEVICE_CLASS)] = F("power_factor"); + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("%"); + break; + + case Sensors::Purpose::CURRENT_POWER: + doc[FPSTR(HA_DEVICE_CLASS)] = F("power"); + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("kW"); + break; + + case Sensors::Purpose::PRESSURE: + doc[FPSTR(HA_DEVICE_CLASS)] = F("pressure"); + if (unit == UnitSystem::METRIC) { + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("bar"); + + } else if (unit == UnitSystem::IMPERIAL) { + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("psi"); + } + break; + + case Sensors::Purpose::HUMIDITY: + doc[FPSTR(HA_DEVICE_CLASS)] = F("humidity"); + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = "%"; + break; + + default: + break; + } + + // set icon + switch (sSensor.purpose) { + case Sensors::Purpose::OUTDOOR_TEMP: + doc[FPSTR(HA_ICON)] = F("mdi:home-thermometer-outline"); + break; + + case Sensors::Purpose::INDOOR_TEMP: + doc[FPSTR(HA_ICON)] = F("mdi:home-thermometer"); + break; + + case Sensors::Purpose::HEATING_TEMP: + doc[FPSTR(HA_ICON)] = F("mdi:radiator"); + break; + + case Sensors::Purpose::HEATING_RETURN_TEMP: + doc[FPSTR(HA_ICON)] = F("mdi:heating-coil"); + break; + + case Sensors::Purpose::DHW_TEMP: + doc[FPSTR(HA_ICON)] = F("mdi:faucet"); + break; + + case Sensors::Purpose::DHW_RETURN_TEMP: + doc[FPSTR(HA_ICON)] = F("mdi:heating-coil"); + break; + + case Sensors::Purpose::EXHAUST_TEMP: + doc[FPSTR(HA_ICON)] = F("mdi:smoke"); + break; + + case Sensors::Purpose::TEMPERATURE: + doc[FPSTR(HA_ICON)] = F("mdi:thermometer-lines"); + break; + + case Sensors::Purpose::DHW_FLOW_RATE: + doc[FPSTR(HA_ICON)] = F("mdi:faucet"); + break; + + case Sensors::Purpose::MODULATION_LEVEL: + doc[FPSTR(HA_ICON)] = F("mdi:fire-circle"); + break; + + case Sensors::Purpose::CURRENT_POWER: + doc[FPSTR(HA_ICON)] = F("mdi:chart-bar"); + break; + + case Sensors::Purpose::PRESSURE: + doc[FPSTR(HA_ICON)] = F("mdi:gauge"); + break; + + case Sensors::Purpose::HUMIDITY: + doc[FPSTR(HA_ICON)] = F("mdi:water-percent"); + break; + + default: + break; + } + + String objId = Sensors::makeObjectId(sSensor.name); + + // state topic + doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("sensors"), objId.c_str()); + + // set device class, name, value template for bluetooth sensors + // or name & value template for another sensors + String sName = sSensor.name; + + if (sSensor.type == Sensors::Type::BLUETOOTH) { + // available state topic + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = doc[FPSTR(HA_STATE_TOPIC)]; + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_SENSOR_CONN; + + switch (vType) { + case Sensors::ValueType::TEMPERATURE: + objId = Sensors::makeObjectIdWithSuffix(sSensor.name, F("temp")); + sName += F(" temperature"); + + doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); + if (unit == UnitSystem::METRIC) { + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C); + + } else if (unit == UnitSystem::IMPERIAL) { + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); + } + doc[FPSTR(HA_NAME)] = sName; + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperature|float(0)|round(2) }}"); + break; + + case Sensors::ValueType::HUMIDITY: + objId = Sensors::makeObjectIdWithSuffix(sSensor.name, F("humidity")); + sName += F(" humidity"); + + doc[FPSTR(HA_DEVICE_CLASS)] = F("humidity"); + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = "%"; + doc[FPSTR(HA_NAME)] = sName; + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.humidity|float(0)|round(2) }}"); + break; + + case Sensors::ValueType::BATTERY: + objId = Sensors::makeObjectIdWithSuffix(sSensor.name, F("battery")); + sName += F(" battery"); + + doc[FPSTR(HA_DEVICE_CLASS)] = F("battery"); + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = "%"; + doc[FPSTR(HA_NAME)] = sName; + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.battery|float(0)|round(2) }}"); + break; + + case Sensors::ValueType::RSSI: + objId = Sensors::makeObjectIdWithSuffix(sSensor.name, F("rssi")); + sName += F(" RSSI"); + + doc[FPSTR(HA_DEVICE_CLASS)] = F("signal_strength"); + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("dBm"); + doc[FPSTR(HA_NAME)] = sName; + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.rssi|float(0)|round(2) }}"); + break; + + default: + return false; + } + + } else if (sSensor.type == Sensors::Type::MANUAL) { + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); + doc[FPSTR(HA_MODE)] = "box"; + + doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("sensors"), objId.c_str(), F("set")); + doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"value\": {{ value }}}"); + + doc[FPSTR(HA_NAME)] = sName; + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.value|float(0)|round(2) }}"); + + } else { + // available state topic + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = doc[FPSTR(HA_STATE_TOPIC)]; + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_SENSOR_CONN; + + doc[FPSTR(HA_NAME)] = sName; + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.value|float(0)|round(2) }}"); + } + + sName.clear(); + + // object id's + { + String objIdWithPrefix = this->getObjectIdWithPrefix(objId.c_str()); + doc[FPSTR(HA_UNIQUE_ID)] = objIdWithPrefix; + doc[FPSTR(HA_OBJECT_ID)] = objIdWithPrefix; + } + + String configTopic = this->makeConfigTopic( + sSensor.type == Sensors::Type::MANUAL ? FPSTR(HA_ENTITY_NUMBER) : FPSTR(HA_ENTITY_SENSOR), + objId.c_str() + ); + objId.clear(); + + doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); + doc[FPSTR(HA_STATE_CLASS)] = FPSTR(HA_STATE_CLASS_MEASUREMENT); + doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; + doc.shrinkToFit(); + + return this->publish(configTopic.c_str(), doc); + } + + bool deleteDynamicSensor(Sensors::Settings& sSensor, Sensors::ValueType vType = Sensors::ValueType::PRIMARY) { + String objId = Sensors::makeObjectId(sSensor.name); + + if (sSensor.type == Sensors::Type::BLUETOOTH) { + switch (vType) { + case Sensors::ValueType::TEMPERATURE: + objId = Sensors::makeObjectIdWithSuffix(sSensor.name, F("temp")); + break; + + case Sensors::ValueType::HUMIDITY: + objId = Sensors::makeObjectIdWithSuffix(sSensor.name, F("humidity")); + break; + + case Sensors::ValueType::BATTERY: + objId = Sensors::makeObjectIdWithSuffix(sSensor.name, F("battery")); + break; + + case Sensors::ValueType::RSSI: + objId = Sensors::makeObjectIdWithSuffix(sSensor.name, F("rssi")); + break; + + default: + return false; + } + } + + String configTopic = this->makeConfigTopic( + sSensor.type == Sensors::Type::MANUAL ? FPSTR(HA_ENTITY_NUMBER) : FPSTR(HA_ENTITY_SENSOR), + objId.c_str() + ); + objId.clear(); + + return this->publish(configTopic.c_str()); + } + + bool publishConnectionDynamicSensor(Sensors::Settings& sSensor, bool enabledByDefault = true) { + JsonDocument doc; + String objId = Sensors::makeObjectIdWithSuffix(sSensor.name, F("connected")); + + // object id's + { + String objIdWithPrefix = this->getObjectIdWithPrefix(objId.c_str()); + doc[FPSTR(HA_UNIQUE_ID)] = objIdWithPrefix; + doc[FPSTR(HA_OBJECT_ID)] = objIdWithPrefix; + } + + // state topic + { + String parentObjId = Sensors::makeObjectId(sSensor.name); + String stateTopic = this->getDeviceTopic(F("sensors"), parentObjId.c_str()); + doc[FPSTR(HA_STATE_TOPIC)] = stateTopic; + } - bool publishSwitchHeating(bool enabledByDefault = true) { + // sensor name + { + String sName = sSensor.name; + sName.trim(); + sName += F(" connected"); + + doc[FPSTR(HA_NAME)] = sName; + } + + String configTopic = this->makeConfigTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), objId.c_str()); + objId.clear(); + + + doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); + doc[FPSTR(HA_DEVICE_CLASS)] = F("connectivity"); + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.connected, 'ON', 'OFF') }}"); + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; + doc.shrinkToFit(); + + return this->publish(configTopic.c_str(), doc); + } + + bool deleteConnectionDynamicSensor(Sensors::Settings& sSensor) { + String objId = Sensors::makeObjectIdWithSuffix(sSensor.name, F("connected")); + String configTopic = this->makeConfigTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), objId.c_str()); + objId.clear(); + + return this->publish(configTopic.c_str()); + } + + bool publishSignalQualityDynamicSensor(Sensors::Settings& sSensor, bool enabledByDefault = true) { JsonDocument doc; + String objId = Sensors::makeObjectIdWithSuffix(sSensor.name, F("signal_quality")); + + // object id's + { + String objIdWithPrefix = this->getObjectIdWithPrefix(objId.c_str()); + doc[FPSTR(HA_UNIQUE_ID)] = objIdWithPrefix; + doc[FPSTR(HA_OBJECT_ID)] = objIdWithPrefix; + } + + // state topic + { + String parentObjId = Sensors::makeObjectId(sSensor.name); + String stateTopic = this->getDeviceTopic(F("sensors"), parentObjId.c_str()); + doc[FPSTR(HA_STATE_TOPIC)] = stateTopic; + } + + // sensor name + { + String sName = sSensor.name; + sName.trim(); + sName += F(" signal quality"); + + doc[FPSTR(HA_NAME)] = sName; + } + + String configTopic = this->makeConfigTopic(FPSTR(HA_ENTITY_SENSOR), objId.c_str()); + objId.clear(); + + doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); - doc[FPSTR(HA_NAME)] = F("Heating"); - doc[FPSTR(HA_ICON)] = F("mdi:radiator"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); - doc[FPSTR(HA_STATE_ON)] = true; - doc[FPSTR(HA_STATE_OFF)] = false; - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.heating.enable }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); - doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"heating\": {\"enable\" : true}}"); - doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"heating\": {\"enable\" : false}}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + 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); + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("%"); + doc[FPSTR(HA_ICON)] = F("mdi:signal"); + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.signalQuality|float(0)|round(0) }}"); + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("heating")).c_str(), doc); + return this->publish(configTopic.c_str(), doc); } + bool deleteSignalQualityDynamicSensor(Sensors::Settings& sSensor) { + JsonDocument doc; + String objId = Sensors::makeObjectIdWithSuffix(sSensor.name, F("signal_quality")); + String configTopic = this->makeConfigTopic(FPSTR(HA_ENTITY_SENSOR), objId.c_str()); + objId.clear(); + + return this->publish(configTopic.c_str()); + } + + bool publishSwitchHeatingTurbo(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating_turbo")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating_turbo")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("heating_turbo")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("heating_turbo")); + 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"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); @@ -44,51 +411,19 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"heating\": {\"turbo\" : true}}"); doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"heating\": {\"turbo\" : false}}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("heating_turbo")).c_str(), doc); - } - - bool publishInputHeatingTarget(UnitSystem unit = UnitSystem::METRIC, byte minTemp = 20, byte maxTemp = 90, bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating_target")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating_target")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); - - if (unit == UnitSystem::METRIC) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C); - - } else if (unit == UnitSystem::IMPERIAL) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); - } - - doc[FPSTR(HA_NAME)] = F("Heating target"); - doc[FPSTR(HA_ICON)] = F("mdi:radiator"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.heating.target|float(0)|round(1) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); - doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"target\" : {{ value }}}}"); - doc[FPSTR(HA_MIN)] = minTemp; - doc[FPSTR(HA_MAX)] = maxTemp; - doc[FPSTR(HA_STEP)] = 0.5f; - doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("heating_target")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SWITCH), F("heating_turbo")).c_str(), doc); } bool publishInputHeatingHysteresis(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating_hysteresis")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating_hysteresis")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("heating_hysteresis")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("heating_hysteresis")); + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); if (unit == UnitSystem::METRIC) { @@ -108,19 +443,19 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_MAX)] = 15; doc[FPSTR(HA_STEP)] = 0.01f; doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("heating_hysteresis")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("heating_hysteresis")).c_str(), doc); } bool publishInputHeatingTurboFactor(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating_turbo_factor")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating_turbo_factor")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("heating_turbo_factor")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("heating_turbo_factor")); + 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"); doc[FPSTR(HA_ICON)] = F("mdi:multiplication-box"); @@ -132,18 +467,19 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_MAX)] = 10; doc[FPSTR(HA_STEP)] = 0.01f; doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("heating_turbo_factor")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("heating_turbo_factor")).c_str(), doc); } + bool publishInputHeatingMinTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating_min_temp")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating_min_temp")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("heating_min_temp")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("heating_min_temp")); + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); if (unit == UnitSystem::METRIC) { @@ -165,19 +501,19 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"minTemp\" : {{ value }}}}"); doc[FPSTR(HA_STEP)] = 1; doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("heating_min_temp")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("heating_min_temp")).c_str(), doc); } bool publishInputHeatingMaxTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating_max_temp")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating_max_temp")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("heating_max_temp")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("heating_max_temp")); + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); if (unit == UnitSystem::METRIC) { @@ -199,222 +535,20 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"maxTemp\" : {{ value }}}}"); doc[FPSTR(HA_STEP)] = 1; doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("heating_max_temp")).c_str(), doc); - } - - - bool publishSensorHeatingSetpoint(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating_setpoint")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating_setpoint")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - - if (unit == UnitSystem::METRIC) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C); - - } else if (unit == UnitSystem::IMPERIAL) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); - } - - doc[FPSTR(HA_NAME)] = F("Heating setpoint"); - doc[FPSTR(HA_ICON)] = F("mdi:coolant-temperature"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.parameters.heatingSetpoint|float(0)|round(1) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("heating_setpoint")).c_str(), doc); - } - - bool publishSensorBoilerHeatingMinTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}"); - doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("boiler_heating_min_temp")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("boiler_heating_min_temp")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - - if (unit == UnitSystem::METRIC) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C); - - } else if (unit == UnitSystem::IMPERIAL) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); - } - - doc[FPSTR(HA_NAME)] = F("Boiler heating min temp"); - doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-down"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.parameters.heatingMinTemp|int(0) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("boiler_heating_min_temp")).c_str(), doc); - } - - bool publishSensorBoilerHeatingMaxTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}"); - doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("boiler_heating_max_temp")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("boiler_heating_max_temp")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - - if (unit == UnitSystem::METRIC) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C); - - } else if (unit == UnitSystem::IMPERIAL) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); - } - - doc[FPSTR(HA_NAME)] = F("Boiler heating max temp"); - doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-up"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.parameters.heatingMaxTemp|int(0) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("boiler_heating_max_temp")).c_str(), doc); - } - - - bool publishSwitchDhw(bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("dhw")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("dhw")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); - doc[FPSTR(HA_NAME)] = F("DHW"); - doc[FPSTR(HA_ICON)] = F("mdi:water-pump"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); - doc[FPSTR(HA_STATE_ON)] = true; - doc[FPSTR(HA_STATE_OFF)] = false; - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.dhw.enable }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); - doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"dhw\": {\"enable\" : true}}"); - doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"dhw\": {\"enable\" : false}}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("dhw")).c_str(), doc); - } - - bool publishInputDhwTarget(UnitSystem unit = UnitSystem::METRIC, byte minTemp = 40, byte maxTemp = 60, bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("dhw_target")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("dhw_target")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); - - if (unit == UnitSystem::METRIC) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C); - - } else if (unit == UnitSystem::IMPERIAL) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); - } - - doc[FPSTR(HA_NAME)] = F("DHW target"); - doc[FPSTR(HA_ICON)] = F("mdi:water-pump"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.dhw.target|float(0)|round(1) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); - doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}"); - doc[FPSTR(HA_MIN)] = minTemp; - doc[FPSTR(HA_MAX)] = maxTemp > minTemp ? maxTemp : minTemp; - doc[FPSTR(HA_STEP)] = 1; - doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("dhw_target")).c_str(), doc); - } - - bool publishSensorBoilerDhwMinTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}"); - doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("boiler_dhw_min_temp")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("boiler_dhw_min_temp")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - - if (unit == UnitSystem::METRIC) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C); - - } else if (unit == UnitSystem::IMPERIAL) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); - } - - doc[FPSTR(HA_NAME)] = F("Boiler DHW min temp"); - doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-down"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.parameters.dhwMinTemp|int(0) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("boiler_dhw_min_temp")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("heating_max_temp")).c_str(), doc); } - bool publishSensorBoilerDhwMaxTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}"); - doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("boiler_dhw_max_temp")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("boiler_dhw_max_temp")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - - if (unit == UnitSystem::METRIC) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C); - - } else if (unit == UnitSystem::IMPERIAL) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); - } - - doc[FPSTR(HA_NAME)] = F("Boiler DHW max temp"); - doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-up"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.parameters.dhwMaxTemp|int(0) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("boiler_dhw_max_temp")).c_str(), doc); - } bool publishInputDhwMinTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("dhw_min_temp")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("dhw_min_temp")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("dhw_min_temp")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("dhw_min_temp")); + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); if (unit == UnitSystem::METRIC) { @@ -436,19 +570,19 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"minTemp\" : {{ value }}}}"); doc[FPSTR(HA_STEP)] = 1; doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("dhw_min_temp")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("dhw_min_temp")).c_str(), doc); } bool publishInputDhwMaxTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("dhw_max_temp")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("dhw_max_temp")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("dhw_max_temp")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("dhw_max_temp")); + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); if (unit == UnitSystem::METRIC) { @@ -470,10 +604,10 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"maxTemp\" : {{ value }}}}"); doc[FPSTR(HA_STEP)] = 1; doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("dhw_max_temp")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("dhw_max_temp")).c_str(), doc); } @@ -481,31 +615,31 @@ class HaHelper : public HomeAssistantHelper { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("pid")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("pid")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("pid")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("pid")); + 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"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); doc[FPSTR(HA_STATE_ON)] = true; doc[FPSTR(HA_STATE_OFF)] = false; - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.enable }}"); + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.enabled }}"); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"pid\": {\"enable\" : true}}"); doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"pid\": {\"enable\" : false}}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("pid")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SWITCH), F("pid")).c_str(), doc); } bool publishInputPidFactorP(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("pid_p")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("pid_p")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("pid_p")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("pid_p")); + 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"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); @@ -516,19 +650,19 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_MAX)] = 1000; doc[FPSTR(HA_STEP)] = 0.1f; doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("pid_p_factor")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("pid_p_factor")).c_str(), doc); } bool publishInputPidFactorI(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("pid_i")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("pid_i")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("pid_i")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("pid_i")); + 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"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); @@ -539,19 +673,19 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_MAX)] = 100; doc[FPSTR(HA_STEP)] = 0.001f; doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("pid_i_factor")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("pid_i_factor")).c_str(), doc); } bool publishInputPidFactorD(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("pid_d")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("pid_d")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("pid_d")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("pid_d")); + 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"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); @@ -562,19 +696,19 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_MAX)] = 100000; doc[FPSTR(HA_STEP)] = 1; doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("pid_d_factor")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("pid_d_factor")).c_str(), doc); } bool publishInputPidDt(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("pid_dt")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("pid_dt")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("pid_dt")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("pid_dt")); + 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"); doc[FPSTR(HA_NAME)] = F("PID DT"); @@ -587,19 +721,19 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_MAX)] = 1800; doc[FPSTR(HA_STEP)] = 1; doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("pid_dt")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("pid_dt")).c_str(), doc); } bool publishInputPidMinTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("pid_min_temp")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("pid_min_temp")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("pid_min_temp")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("pid_min_temp")); + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); if (unit == UnitSystem::METRIC) { @@ -621,19 +755,19 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"minTemp\" : {{ value }}}}"); doc[FPSTR(HA_STEP)] = 1; doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("pid_min_temp")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("pid_min_temp")).c_str(), doc); } bool publishInputPidMaxTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("pid_max_temp")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("pid_max_temp")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("pid_max_temp")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("pid_max_temp")); + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); if (unit == UnitSystem::METRIC) { @@ -655,10 +789,10 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"maxTemp\" : {{ value }}}}"); doc[FPSTR(HA_STEP)] = 1; doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("pid_max_temp")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("pid_max_temp")).c_str(), doc); } @@ -666,31 +800,31 @@ class HaHelper : public HomeAssistantHelper { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("equitherm")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("equitherm")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("equitherm")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("equitherm")); + 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"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); doc[FPSTR(HA_STATE_ON)] = true; doc[FPSTR(HA_STATE_OFF)] = false; - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.enable }}"); + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.enabled }}"); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"equitherm\": {\"enable\" : true}}"); doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"equitherm\": {\"enable\" : false}}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("equitherm")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SWITCH), F("equitherm")).c_str(), doc); } bool publishInputEquithermFactorN(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("equitherm_n")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("equitherm_n")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("equitherm_n")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("equitherm_n")); + 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"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); @@ -701,19 +835,19 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_MAX)] = 10; doc[FPSTR(HA_STEP)] = 0.001f; doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_n_factor")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_n_factor")).c_str(), doc); } bool publishInputEquithermFactorK(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("equitherm_k")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("equitherm_k")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("equitherm_k")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("equitherm_k")); + 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"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); @@ -724,10 +858,10 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_MAX)] = 10; doc[FPSTR(HA_STEP)] = 0.01f; doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_k_factor")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_k_factor")).c_str(), doc); } bool publishInputEquithermFactorT(bool enabledByDefault = true) { @@ -737,9 +871,9 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.pid.enable, 'offline', 'online') }}"); doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("equitherm_t")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("equitherm_t")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("equitherm_t")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("equitherm_t")); + 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"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); @@ -750,19 +884,19 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_MAX)] = 10; doc[FPSTR(HA_STEP)] = 0.01f; doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_t_factor")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_t_factor")).c_str(), doc); } - bool publishStateStatus(bool enabledByDefault = true) { + bool publishStatusState(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("status")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("status")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("status")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("status")); + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("problem"); doc[FPSTR(HA_NAME)] = F("Status"); doc[FPSTR(HA_ICON)] = F("mdi:list-status"); @@ -771,766 +905,260 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_EXPIRE_AFTER)] = 60; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("status")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("status")).c_str(), doc); } - bool publishStateEmergency(bool enabledByDefault = true) { + bool publishEmergencyState(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("emergency")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("emergency")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("emergency")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("emergency")); + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("problem"); - doc[FPSTR(HA_NAME)] = F("Emergency mode"); + doc[FPSTR(HA_NAME)] = F("Emergency"); doc[FPSTR(HA_ICON)] = F("mdi:alert-rhombus-outline"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.emergency, 'ON', 'OFF') }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.master.emergency.state, 'ON', 'OFF') }}"); + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("emergency")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("emergency")).c_str(), doc); } - bool publishStateOtStatus(bool enabledByDefault = true) { + bool publishOpenthermConnectedState(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("ot_status")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("ot_status")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("ot_status")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("ot_status")); + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("connectivity"); doc[FPSTR(HA_NAME)] = F("Opentherm status"); doc[FPSTR(HA_ICON)] = F("mdi:list-status"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'ON', 'OFF') }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.slave.connected, 'ON', 'OFF') }}"); + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("ot_status")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("ot_status")).c_str(), doc); } - bool publishStateHeating(bool enabledByDefault = true) { + bool publishHeatingState(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}"); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_OT_CONN; doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("heating")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("heating")); + //doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("running"); doc[FPSTR(HA_NAME)] = F("Heating"); doc[FPSTR(HA_ICON)] = F("mdi:radiator"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.heating, 'ON', 'OFF') }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.slave.heating.active, 'ON', 'OFF') }}"); + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("heating")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("heating")).c_str(), doc); } - bool publishStateDhw(bool enabledByDefault = true) { + bool publishDhwState(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}"); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_OT_CONN; doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("dhw")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("dhw")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("dhw")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("dhw")); + //doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("running"); doc[FPSTR(HA_NAME)] = F("DHW"); - doc[FPSTR(HA_ICON)] = F("mdi:water-pump"); + doc[FPSTR(HA_ICON)] = F("mdi:faucet"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.dhw, 'ON', 'OFF') }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.slave.dhw.active, 'ON', 'OFF') }}"); + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("dhw")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("dhw")).c_str(), doc); } - bool publishStateFlame(bool enabledByDefault = true) { + bool publishFlameState(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}"); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_OT_CONN; doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("flame")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("flame")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("flame")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("flame")); + //doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("running"); doc[FPSTR(HA_NAME)] = F("Flame"); - doc[FPSTR(HA_ICON)] = F("mdi:fire"); + doc[FPSTR(HA_ICON)] = F("mdi:gas-burner"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.flame, 'ON', 'OFF') }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.slave.flame, 'ON', 'OFF') }}"); + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("flame")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("flame")).c_str(), doc); } - bool publishStateFault(bool enabledByDefault = true) { + bool publishFaultState(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}"); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_OT_CONN; doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("fault")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("fault")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("fault")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("fault")); + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("problem"); doc[FPSTR(HA_NAME)] = F("Fault"); - doc[FPSTR(HA_ICON)] = F("mdi:water-boiler-alert"); + doc[FPSTR(HA_ICON)] = F("mdi:alert-remove-outline"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.fault, 'ON', 'OFF') }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.slave.fault.active, 'ON', 'OFF') }}"); + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("fault")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("fault")).c_str(), doc); } - bool publishStateDiagnostic(bool enabledByDefault = true) { + bool publishDiagState(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}"); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_OT_CONN; doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("diagnostic")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("diagnostic")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC)); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC)); + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("problem"); doc[FPSTR(HA_NAME)] = F("Diagnostic"); doc[FPSTR(HA_ICON)] = F("mdi:account-wrench"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.diagnostic, 'ON', 'OFF') }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.slave.diag.active, 'ON', 'OFF') }}"); + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("diagnostic")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC)).c_str(), doc); } - bool publishStateExtPump(bool enabledByDefault = true) { + bool publishExternalPumpState(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("ext_pump")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("ext_pump")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("ext_pump")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("ext_pump")); + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("running"); doc[FPSTR(HA_NAME)] = F("External pump"); doc[FPSTR(HA_ICON)] = F("mdi:pump"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.externalPump, 'ON', 'OFF') }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.master.externalPump.state, 'ON', 'OFF') }}"); + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("ext_pump")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("ext_pump")).c_str(), doc); } - - bool publishSensorModulation(bool enabledByDefault = true) { + bool publishFaultCode(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}"); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.opentherm.connected and value_json.fault.active, 'online', 'offline') }}"); doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("modulation_level")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("modulation_level")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("power_factor"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("%"); - doc[FPSTR(HA_NAME)] = F("Modulation level"); - doc[FPSTR(HA_ICON)] = F("mdi:fire-circle"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("fault_code")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("fault_code")); + 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"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.sensors.modulation|float(0)|round(0) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ \"%02d (0x%02X)\"|format(value_json.slave.fault.code, value_json.slave.fault.code) }}"); + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("modulation")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SENSOR), F("fault_code")).c_str(), doc); } - bool publishSensorPressure(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { + bool publishDiagCode(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}"); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.opentherm.connected and value_json.fault.active or value_json.diag.active, 'online', 'offline') }}"); doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("pressure")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("pressure")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("pressure"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - - if (unit == UnitSystem::METRIC) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("bar"); - - } else if (unit == UnitSystem::IMPERIAL) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("psi"); - } - - doc[FPSTR(HA_NAME)] = F("Pressure"); - doc[FPSTR(HA_ICON)] = F("mdi:gauge"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("diagnostic_code")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("diagnostic_code")); + 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"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.sensors.pressure|float(0)|round(2) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ \"%02d (0x%02X)\"|format(value_json.slave.diag.code, value_json.slave.diag.code) }}"); + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("pressure")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SENSOR), F("diagnostic_code")).c_str(), doc); } - bool publishSensorDhwFlowRate(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { + bool publishNetworkRssi(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}"); - doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); + doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("dhw_flow_rate")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("dhw_flow_rate")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("volume_flow_rate"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - - if (unit == UnitSystem::METRIC) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("L/min"); - - } else if (unit == UnitSystem::IMPERIAL) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("gal/min"); - } - - doc[FPSTR(HA_NAME)] = F("DHW flow rate"); - doc[FPSTR(HA_ICON)] = F("mdi:water-pump"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("rssi")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("rssi")); + 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); + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("dBm"); + doc[FPSTR(HA_NAME)] = F("RSSI"); + doc[FPSTR(HA_ICON)] = F("mdi:signal"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.sensors.dhwFlowRate|float(0)|round(2) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.master.network.rssi|float(0)|round(1) }}"); + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("dhw_flow_rate")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SENSOR), F("rssi")).c_str(), doc); } - bool publishSensorPower(bool enabledByDefault = true) { + bool publishUptime(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}"); - doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); + doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("power")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("power")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("power"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("kW"); - doc[FPSTR(HA_NAME)] = F("Current power"); - doc[FPSTR(HA_ICON)] = F("mdi:chart-bar"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("uptime")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("uptime")); + 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"); + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("s"); + doc[FPSTR(HA_NAME)] = F("Uptime"); + doc[FPSTR(HA_ICON)] = F("mdi:clock-start"); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.sensors.power|float(0)|round(2) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.master.uptime|int(0) }}"); + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("power")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SENSOR), F("uptime")).c_str(), doc); } - bool publishSensorFaultCode(bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus and value_json.states.fault, 'online', 'offline') }}"); - doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("fault_code")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("fault_code")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_NAME)] = F("Fault code"); - doc[FPSTR(HA_ICON)] = F("mdi:chat-alert-outline"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ \"%02d (0x%02X)\"|format(value_json.sensors.faultCode, value_json.sensors.faultCode) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("fault_code")).c_str(), doc); - } - - bool publishSensorDiagnosticCode(bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus and value_json.states.fault or value_json.states.diagnostic, 'online', 'offline') }}"); - doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("diagnostic_code")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("diagnostic_code")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_NAME)] = F("Diagnostic code"); - doc[FPSTR(HA_ICON)] = F("mdi:chat-alert-outline"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ \"%02d (0x%02X)\"|format(value_json.sensors.diagnosticCode, value_json.sensors.diagnosticCode) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("diagnostic_code")).c_str(), doc); - } - - bool publishSensorRssi(bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("rssi")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("rssi")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("signal_strength"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("dBm"); - doc[FPSTR(HA_NAME)] = F("RSSI"); - doc[FPSTR(HA_ICON)] = F("mdi:signal"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.sensors.rssi|float(0)|round(1) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("rssi")).c_str(), doc); - } - - bool publishSensorUptime(bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("uptime")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("uptime")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("duration"); - doc[FPSTR(HA_STATE_CLASS)] = F("total_increasing"); - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("s"); - doc[FPSTR(HA_NAME)] = F("Uptime"); - doc[FPSTR(HA_ICON)] = F("mdi:clock-start"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.sensors.uptime|int(0) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("uptime")).c_str(), doc); - } - - bool publishOutdoorSensorConnected(bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("outdoor_sensor_connected")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("outdoor_sensor_connected")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("connectivity"); - doc[FPSTR(HA_NAME)] = F("Outdoor sensor connected"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.sensors.outdoor.connected, 'ON', 'OFF') }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("outdoor_sensor_connected")).c_str(), doc); - } - - bool publishOutdoorSensorRssi(bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("outdoor_sensor_rssi")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("outdoor_sensor_rssi")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("signal_strength"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("dBm"); - doc[FPSTR(HA_NAME)] = F("Outdoor sensor RSSI"); - doc[FPSTR(HA_ICON)] = F("mdi:signal"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.sensors.outdoor.rssi|float(0)|round(1) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("outdoor_sensor_rssi")).c_str(), doc); - } - - bool publishOutdoorSensorBattery(bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("outdoor_sensor_battery")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("outdoor_sensor_battery")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("battery"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR("%"); - doc[FPSTR(HA_NAME)] = F("Outdoor sensor battery"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.sensors.outdoor.battery|float(0)|round(1) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("outdoor_sensor_battery")).c_str(), doc); - } - - bool publishOutdoorSensorHumidity(bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("outdoor_sensor_humidity")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("outdoor_sensor_humidity")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("humidity"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR("%"); - doc[FPSTR(HA_NAME)] = F("Outdoor sensor humidity"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.sensors.outdoor.humidity|float(0)|round(1) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("outdoor_sensor_humidity")).c_str(), doc); - } - - bool publishIndoorSensorConnected(bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("indoor_sensor_connected")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("indoor_sensor_connected")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("connectivity"); - doc[FPSTR(HA_NAME)] = F("Indoor sensor connected"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.sensors.indoor.connected, 'ON', 'OFF') }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("indoor_sensor_connected")).c_str(), doc); - } - - bool publishIndoorSensorRssi(bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("indoor_sensor_rssi")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("indoor_sensor_rssi")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("signal_strength"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("dBm"); - doc[FPSTR(HA_NAME)] = F("Indoor sensor RSSI"); - doc[FPSTR(HA_ICON)] = F("mdi:signal"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.sensors.indoor.rssi|float(0)|round(1) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("indoor_sensor_rssi")).c_str(), doc); - } - - bool publishIndoorSensorBattery(bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("indoor_sensor_battery")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("indoor_sensor_battery")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("battery"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR("%"); - doc[FPSTR(HA_NAME)] = F("Indoor sensor battery"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.sensors.indoor.battery|float(0)|round(1) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("indoor_sensor_battery")).c_str(), doc); - } - - bool publishIndoorSensorHumidity(bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("indoor_sensor_humidity")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("indoor_sensor_humidity")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("humidity"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR("%"); - doc[FPSTR(HA_NAME)] = F("Indoor sensor humidity"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.sensors.indoor.humidity|float(0)|round(1) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("indoor_sensor_humidity")).c_str(), doc); - } - - - bool publishInputIndoorTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("indoor_temp")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("indoor_temp")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); - - if (unit == UnitSystem::METRIC) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C); - doc[FPSTR(HA_MIN)] = -99; - doc[FPSTR(HA_MAX)] = 99; - - } else if (unit == UnitSystem::IMPERIAL) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); - doc[FPSTR(HA_MIN)] = -147; - doc[FPSTR(HA_MAX)] = 211; - } - - doc[FPSTR(HA_NAME)] = F("Indoor temperature"); - doc[FPSTR(HA_ICON)] = F("mdi:home-thermometer"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.indoor|float(0)|round(1) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set")); - doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"temperatures\": {\"indoor\":{{ value }}}}"); - doc[FPSTR(HA_STEP)] = 0.01f; - doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("indoor_temp")).c_str(), doc); - } - - bool publishSensorIndoorTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("indoor_temp")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("indoor_temp")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - - if (unit == UnitSystem::METRIC) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C); - - } else if (unit == UnitSystem::IMPERIAL) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); - } - - doc[FPSTR(HA_NAME)] = F("Indoor temperature"); - doc[FPSTR(HA_ICON)] = F("mdi:home-thermometer"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.indoor|float(0)|round(1) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("indoor_temp")).c_str(), doc); - } - - bool publishInputOutdoorTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("outdoor_temp")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("outdoor_temp")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); - - if (unit == UnitSystem::METRIC) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C); - doc[FPSTR(HA_MIN)] = -99; - doc[FPSTR(HA_MAX)] = 99; - - } else if (unit == UnitSystem::IMPERIAL) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); - doc[FPSTR(HA_MIN)] = -147; - doc[FPSTR(HA_MAX)] = 211; - } - - doc[FPSTR(HA_NAME)] = F("Outdoor temperature"); - doc[FPSTR(HA_ICON)] = F("mdi:home-thermometer-outline"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.outdoor|float(0)|round(1) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set")); - doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"temperatures\": {\"outdoor\":{{ value }}}}"); - doc[FPSTR(HA_STEP)] = 0.01f; - doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("outdoor_temp")).c_str(), doc); - } - - bool publishSensorOutdoorTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { + + bool publishClimateHeating(UnitSystem unit = UnitSystem::METRIC, byte minTemp = 20, byte maxTemp = 90, bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("outdoor_temp")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("outdoor_temp")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - - if (unit == UnitSystem::METRIC) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C); - - } else if (unit == UnitSystem::IMPERIAL) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); - } - - doc[FPSTR(HA_NAME)] = F("Outdoor temperature"); - doc[FPSTR(HA_ICON)] = F("mdi:home-thermometer-outline"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.outdoor|float(0)|round(1) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("outdoor_temp")).c_str(), doc); - } - - bool publishSensorHeatingTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}"); - doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating_temp")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating_temp")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - - if (unit == UnitSystem::METRIC) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C); - - } else if (unit == UnitSystem::IMPERIAL) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); - } - - doc[FPSTR(HA_NAME)] = F("Heating temperature"); - doc[FPSTR(HA_ICON)] = F("mdi:radiator"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.heating|float(0)|round(2) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("heating_temp")).c_str(), doc); - } - - bool publishSensorHeatingReturnTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}"); - doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating_return_temp")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating_return_temp")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - - if (unit == UnitSystem::METRIC) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C); - - } else if (unit == UnitSystem::IMPERIAL) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); - } - - doc[FPSTR(HA_NAME)] = F("Heating return temperature"); - doc[FPSTR(HA_ICON)] = F("mdi:radiator"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.heatingReturn|float(0)|round(2) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("heating_return_temp")).c_str(), doc); - } - - bool publishSensorDhwTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}"); - doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("dhw_temp")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("dhw_temp")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - - if (unit == UnitSystem::METRIC) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C); - - } else if (unit == UnitSystem::IMPERIAL) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); - } - - doc[FPSTR(HA_NAME)] = F("DHW temperature"); - doc[FPSTR(HA_ICON)] = F("mdi:water-pump"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.dhw|float(0)|round(2) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("dhw_temp")).c_str(), doc); - } - - bool publishSensorExhaustTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.otStatus, 'online', 'offline') }}"); - doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("exhaust_temp")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("exhaust_temp")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); - doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); - doc[FPSTR(HA_STATE_CLASS)] = F("measurement"); - - if (unit == UnitSystem::METRIC) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C); - - } else if (unit == UnitSystem::IMPERIAL) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); - } - - doc[FPSTR(HA_NAME)] = F("Exhaust temperature"); - doc[FPSTR(HA_ICON)] = F("mdi:smoke"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.exhaust|float(0)|round(2) }}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("exhaust_temp")).c_str(), doc); - } - - - bool publishClimateHeating(UnitSystem unit = UnitSystem::METRIC, byte minTemp = 20, byte maxTemp = 90, byte currentTempSource = HaHelper::TEMP_SOURCE_HEATING, bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("heating")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("heating")); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("heating")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("heating")); doc[FPSTR(HA_NAME)] = F("Heating"); doc[FPSTR(HA_ICON)] = F("mdi:radiator"); - if (currentTempSource == HaHelper::TEMP_SOURCE_HEATING || currentTempSource == HaHelper::TEMP_SOURCE_INDOOR) { - doc[FPSTR(HA_CURRENT_TEMPERATURE_TOPIC)] = this->getDeviceTopic(F("state")); - } - - if (currentTempSource == HaHelper::TEMP_SOURCE_HEATING) { - doc[FPSTR(HA_CURRENT_TEMPERATURE_TEMPLATE)] = F("{{ value_json.temperatures.heating|float(0)|round(2) }}"); - - } else if (currentTempSource == HaHelper::TEMP_SOURCE_INDOOR) { - doc[FPSTR(HA_CURRENT_TEMPERATURE_TEMPLATE)] = F("{{ value_json.temperatures.indoor|float(0)|round(2) }}"); - } + doc[FPSTR(HA_CURRENT_TEMPERATURE_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_CURRENT_TEMPERATURE_TEMPLATE)] = F("{{ iif(value_json.master.heating.indoorTempControl, value_json.master.heating.indoorTemp, value_json.master.heating.currentTemp, 0)|float(0)|round(2) }}"); doc[FPSTR(HA_TEMPERATURE_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_TEMPERATURE_COMMAND_TEMPLATE)] = F("{\"heating\": {\"target\" : {{ value }}}}"); @@ -1554,7 +1182,7 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_MODES)][1] = F("heat"); doc[FPSTR(HA_ACTION_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_ACTION_TEMPLATE)] = F("{{ iif(value_json.states.heating, 'heating', 'idle') }}"); + doc[FPSTR(HA_ACTION_TEMPLATE)] = F("{{ iif(value_json.master.heating.enabled, iif(value_json.slave.heating.active, 'heating', 'idle'), 'off') }}"); doc[FPSTR(HA_PRESET_MODE_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_PRESET_MODE_COMMAND_TEMPLATE)] = F("{% if value == 'boost' %}{\"heating\": {\"turbo\" : true}}" @@ -1566,23 +1194,23 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_MIN_TEMP)] = minTemp; doc[FPSTR(HA_MAX_TEMP)] = maxTemp; doc[FPSTR(HA_TEMP_STEP)] = 0.5f; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_CLIMATE), F("heating"), '_').c_str(), doc); + 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) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("dhw")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("dhw")); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("dhw")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("dhw")); doc[FPSTR(HA_NAME)] = F("DHW"); - doc[FPSTR(HA_ICON)] = F("mdi:water-pump"); + doc[FPSTR(HA_ICON)] = F("mdi:faucet"); doc[FPSTR(HA_CURRENT_TEMPERATURE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_CURRENT_TEMPERATURE_TEMPLATE)] = F("{{ value_json.temperatures.dhw|float(0)|round(1) }}"); + doc[FPSTR(HA_CURRENT_TEMPERATURE_TEMPLATE)] = F("{{ value_json.master.dhw.currentTemp|float(0)|round(1) }}"); doc[FPSTR(HA_TEMPERATURE_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_TEMPERATURE_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}"); @@ -1606,128 +1234,107 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_MODES)][1] = F("heat"); doc[FPSTR(HA_ACTION_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_ACTION_TEMPLATE)] = F("{{ iif(value_json.states.dhw, 'heating', 'idle') }}"); + doc[FPSTR(HA_ACTION_TEMPLATE)] = F("{{ iif(value_json.master.dhw.enabled, iif(value_json.slave.dhw.active, 'heating', 'idle'), 'off') }}"); doc[FPSTR(HA_MIN_TEMP)] = minTemp; doc[FPSTR(HA_MAX_TEMP)] = maxTemp; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_CLIMATE), F("dhw"), '_').c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_CLIMATE), F("dhw"), '_').c_str(), doc); } - bool publishButtonRestart(bool enabledByDefault = true) { + bool publishRestartButton(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("restart")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("restart")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("restart")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("restart")); + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("restart"); doc[FPSTR(HA_NAME)] = F("Restart"); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set")); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"actions\": {\"restart\": true}}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_BUTTON), F("restart")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_BUTTON), F("restart")).c_str(), doc); } - bool publishButtonResetFault(bool enabledByDefault = true) { + bool publishResetFaultButton(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.fault, 'online', 'offline') }}"); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.fault.active, 'online', 'offline') }}"); doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("reset_fault")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("reset_fault")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("reset_fault")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("reset_fault")); + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("restart"); doc[FPSTR(HA_NAME)] = F("Reset fault"); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set")); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"actions\": {\"resetFault\": true}}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_BUTTON), F("reset_fault")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_BUTTON), F("reset_fault")).c_str(), doc); } - bool publishButtonResetDiagnostic(bool enabledByDefault = true) { + bool publishResetDiagButton(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.states.diagnostic, 'online', 'offline') }}"); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.diag.active, 'online', 'offline') }}"); doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("reset_diagnostic")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("reset_diagnostic")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); + doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("reset_diagnostic")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("reset_diagnostic")); + doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("restart"); doc[FPSTR(HA_NAME)] = F("Reset diagnostic"); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set")); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"actions\": {\"resetDiagnostic\": true}}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_BUTTON), F("reset_diagnostic")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_BUTTON), F("reset_diagnostic")).c_str(), doc); } - bool deleteInputOutdoorTemp() { - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("outdoor_temp")).c_str()); - } - - bool deleteSensorOutdoorTemp() { - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("outdoor_temp")).c_str()); - } - - bool deleteInputIndoorTemp() { - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("indoor_temp")).c_str()); - } - - bool deleteSensorIndoorTemp() { - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("indoor_temp")).c_str()); + template + bool deleteEntities(CT category) { + return this->publish(this->makeConfigTopic(category).c_str()); } bool deleteSwitchDhw() { - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("dhw")).c_str()); - } - - bool deleteSensorBoilerDhwMinTemp() { - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("boiler_dhw_min_temp")).c_str()); - } - - bool deleteSensorBoilerDhwMaxTemp() { - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("boiler_dhw_max_temp")).c_str()); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SWITCH), F("dhw")).c_str()); } bool deleteInputDhwMinTemp() { - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("dhw_min_temp")).c_str()); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("dhw_min_temp")).c_str()); } bool deleteInputDhwMaxTemp() { - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("dhw_max_temp")).c_str()); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("dhw_max_temp")).c_str()); } - bool deleteStateDhw() { - return this->publish(this->getTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("dhw")).c_str()); + bool deleteDhwState() { + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("dhw")).c_str()); } - - bool deleteSensorDhwTemp() { - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("dhw_temp")).c_str()); - } - + bool deleteInputDhwTarget() { - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("dhw_target")).c_str()); - } - - bool deleteSensorDhwFlowRate() { - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("dhw_flow_rate")).c_str()); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("dhw_target")).c_str()); } bool deleteClimateDhw() { - return this->publish(this->getTopic(FPSTR(HA_ENTITY_CLIMATE), F("dhw"), '_').c_str()); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_CLIMATE), F("dhw"), '_').c_str()); } + + protected: + unsigned short expireAfter = 300u; }; + +const char HaHelper::AVAILABILITY_OT_CONN[] = "{{ iif(value_json.slave.connected, 'online', 'offline') }}"; +const char HaHelper::AVAILABILITY_SENSOR_CONN[] = "{{ iif(value_json.connected, 'online', 'offline') }}"; \ No newline at end of file diff --git a/src/MainTask.h b/src/MainTask.h index f592927..08314b7 100644 --- a/src/MainTask.h +++ b/src/MainTask.h @@ -5,7 +5,7 @@ using namespace NetworkUtils; extern NetworkMgr* network; extern MqttTask* tMqtt; extern OpenThermTask* tOt; -extern FileData fsSettings, fsNetworkSettings; +extern FileData fsNetworkSettings, fsSettings, fsSensorsSettings; extern ESPTelnetStream* telnetStream; @@ -60,12 +60,16 @@ class MainTask : public Task { void loop() { network->loop(); + if (fsNetworkSettings.tick() == FD_WRITE) { + Log.sinfoln(FPSTR(L_NETWORK_SETTINGS), F("Updated")); + } + if (fsSettings.tick() == FD_WRITE) { Log.sinfoln(FPSTR(L_SETTINGS), F("Updated")); } - if (fsNetworkSettings.tick() == FD_WRITE) { - Log.sinfoln(FPSTR(L_NETWORK_SETTINGS), F("Updated")); + if (fsSensorsSettings.tick() == FD_WRITE) { + Log.sinfoln(FPSTR(L_SENSORS_SETTINGS), F("Updated")); } if (vars.actions.restart) { @@ -75,6 +79,9 @@ class MainTask : public Task { // save settings fsSettings.updateNow(); + // save sensors settings + fsSensorsSettings.updateNow(); + // force save network settings if (fsNetworkSettings.updateNow() == FD_FILE_ERR && LittleFS.begin()) { fsNetworkSettings.write(); @@ -83,8 +90,9 @@ class MainTask : public Task { Log.sinfoln(FPSTR(L_MAIN), F("Restart signal received. Restart after 10 sec.")); } - vars.states.mqtt = tMqtt->isConnected(); - vars.sensors.rssi = network->isConnected() ? WiFi.RSSI() : 0; + vars.mqtt.connected = tMqtt->isConnected(); + vars.network.connected = network->isConnected(); + vars.network.rssi = network->isConnected() ? WiFi.RSSI() : 0; if (settings.system.logLevel >= TinyLogger::Level::SILENT && settings.system.logLevel <= TinyLogger::Level::VERBOSE) { if (Log.getLevel() != settings.system.logLevel) { @@ -98,20 +106,14 @@ class MainTask : public Task { this->telnetStarted = true; } - if (settings.mqtt.enable && !tMqtt->isEnabled()) { + if (settings.mqtt.enabled && !tMqtt->isEnabled()) { tMqtt->enable(); - } else if (!settings.mqtt.enable && tMqtt->isEnabled()) { + } else if (!settings.mqtt.enabled && tMqtt->isEnabled()) { tMqtt->disable(); } - if (settings.sensors.indoor.type == SensorType::MANUAL) { - vars.sensors.indoor.connected = !settings.mqtt.enable || vars.states.mqtt; - } - - if (settings.sensors.outdoor.type == SensorType::MANUAL) { - vars.sensors.outdoor.connected = !settings.mqtt.enable || vars.states.mqtt; - } + Sensors::setConnectionStatusByType(Sensors::Type::MANUAL, !settings.mqtt.enabled || vars.mqtt.connected, false); } else { if (this->telnetStarted) { @@ -123,13 +125,7 @@ class MainTask : public Task { tMqtt->disable(); } - if (settings.sensors.indoor.type == SensorType::MANUAL) { - vars.sensors.indoor.connected = false; - } - - if (settings.sensors.outdoor.type == SensorType::MANUAL) { - vars.sensors.outdoor.connected = false; - } + Sensors::setConnectionStatusByType(Sensors::Type::MANUAL, false, false); } this->yield(); @@ -209,18 +205,21 @@ class MainTask : public Task { uint8_t emergencyFlags = 0b00000000; // set outdoor sensor flag - if (settings.equitherm.enable && !vars.sensors.outdoor.connected) { - emergencyFlags |= 0b00000001; - } - - // set indoor sensor flag - if (!settings.equitherm.enable && settings.pid.enable && !vars.sensors.indoor.connected) { - emergencyFlags |= 0b00000010; + if (settings.equitherm.enabled) { + if (!Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP)) { + emergencyFlags |= 0b00000001; + } } - // set indoor sensor flag for OT native heating control - if (settings.opentherm.nativeHeatingControl && !vars.sensors.indoor.connected) { - emergencyFlags |= 0b00000100; + // set indoor sensor flags + if (!Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::OUTDOOR_TEMP)) { + if (!settings.equitherm.enabled && settings.pid.enabled) { + emergencyFlags |= 0b00000010; + } + + if (settings.opentherm.nativeHeatingControl) { + emergencyFlags |= 0b00000100; + } } // if any flags is true @@ -230,10 +229,10 @@ class MainTask : public Task { this->emergencyDetected = true; this->emergencyFlipTime = millis(); - } else if (this->emergencyDetected && !vars.states.emergency) { + } else if (this->emergencyDetected && !vars.emergency.state) { // enable emergency if (millis() - this->emergencyFlipTime > (settings.emergency.tresholdTime * 1000)) { - vars.states.emergency = true; + vars.emergency.state = true; Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode enabled (%hhu)"), emergencyFlags); } } @@ -244,10 +243,10 @@ class MainTask : public Task { this->emergencyDetected = false; this->emergencyFlipTime = millis(); - } else if (!this->emergencyDetected && vars.states.emergency) { + } else if (!this->emergencyDetected && vars.emergency.state) { // disable emergency if (millis() - this->emergencyFlipTime > (settings.emergency.tresholdTime * 1000)) { - vars.states.emergency = false; + vars.emergency.state = false; Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode disabled")); } } @@ -286,15 +285,15 @@ class MainTask : public Task { errors[errCount++] = 2; } - if (!vars.states.otStatus) { + if (!vars.slave.connected) { errors[errCount++] = 3; } - if (vars.states.fault) { + if (vars.slave.fault.active) { errors[errCount++] = 4; } - if (vars.states.emergency) { + if (vars.emergency.state) { errors[errCount++] = 5; } @@ -342,7 +341,7 @@ class MainTask : public Task { static unsigned long outputChangedTs = 0; // input - if (settings.cascadeControl.input.enable) { + if (settings.cascadeControl.input.enabled) { if (settings.cascadeControl.input.gpio != configuredInputGpio) { if (configuredInputGpio != GPIO_IS_NOT_CONFIGURED) { pinMode(configuredInputGpio, OUTPUT); @@ -393,7 +392,7 @@ class MainTask : public Task { } } - if (!settings.cascadeControl.input.enable || configuredInputGpio == GPIO_IS_NOT_CONFIGURED) { + if (!settings.cascadeControl.input.enabled || configuredInputGpio == GPIO_IS_NOT_CONFIGURED) { if (!vars.cascadeControl.input) { vars.cascadeControl.input = true; @@ -407,7 +406,7 @@ class MainTask : public Task { // output - if (settings.cascadeControl.output.enable) { + if (settings.cascadeControl.output.enabled) { if (settings.cascadeControl.output.gpio != configuredOutputGpio) { if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) { pinMode(configuredOutputGpio, OUTPUT); @@ -437,13 +436,13 @@ class MainTask : public Task { if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) { bool value = false; - if (settings.cascadeControl.output.onFault && vars.states.fault) { + if (settings.cascadeControl.output.onFault && vars.slave.fault.active) { value = true; - } else if (settings.cascadeControl.output.onLossConnection && !vars.states.otStatus) { + } else if (settings.cascadeControl.output.onLossConnection && !vars.slave.connected) { value = true; - } else if (settings.cascadeControl.output.onEnabledHeating && settings.heating.enable && vars.cascadeControl.input) { + } else if (settings.cascadeControl.output.onEnabledHeating && settings.heating.enabled && vars.cascadeControl.input) { value = true; } @@ -475,7 +474,7 @@ class MainTask : public Task { } } - if (!settings.cascadeControl.output.enable || configuredOutputGpio == GPIO_IS_NOT_CONFIGURED) { + if (!settings.cascadeControl.output.enabled || configuredOutputGpio == GPIO_IS_NOT_CONFIGURED) { if (vars.cascadeControl.output) { vars.cascadeControl.output = false; @@ -516,75 +515,75 @@ class MainTask : public Task { } if (configuredGpio == GPIO_IS_NOT_CONFIGURED) { - if (vars.states.externalPump) { - vars.states.externalPump = false; - vars.parameters.extPumpLastEnableTime = millis(); + if (vars.externalPump.state) { + vars.externalPump.state = false; + vars.externalPump.lastEnableTime = millis(); - Log.sinfoln("EXTPUMP", F("Disabled: use = off")); + Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: use = off")); } return; } - if (!vars.states.heating && this->heatingEnabled) { + if (!vars.master.heating.enabled && this->heatingEnabled) { this->heatingEnabled = false; this->heatingDisabledTime = millis(); - } else if (vars.states.heating && !this->heatingEnabled) { + } else if (vars.master.heating.enabled && !this->heatingEnabled) { this->heatingEnabled = true; } if (!settings.externalPump.use) { - if (vars.states.externalPump) { + if (vars.externalPump.state) { digitalWrite(configuredGpio, LOW); - vars.states.externalPump = false; - vars.parameters.extPumpLastEnableTime = millis(); + vars.externalPump.state = false; + vars.externalPump.lastEnableTime = millis(); - Log.sinfoln("EXTPUMP", F("Disabled: use = off")); + Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: use = off")); } return; } - if (vars.states.externalPump && !this->heatingEnabled) { + if (vars.externalPump.state && !this->heatingEnabled) { if (this->extPumpStartReason == MainTask::PumpStartReason::HEATING && millis() - this->heatingDisabledTime > (settings.externalPump.postCirculationTime * 1000u)) { digitalWrite(configuredGpio, LOW); - vars.states.externalPump = false; - vars.parameters.extPumpLastEnableTime = millis(); + vars.externalPump.state = false; + vars.externalPump.lastEnableTime = millis(); - Log.sinfoln("EXTPUMP", F("Disabled: expired post circulation time")); + 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); - vars.states.externalPump = false; - vars.parameters.extPumpLastEnableTime = millis(); + vars.externalPump.state = false; + vars.externalPump.lastEnableTime = millis(); - Log.sinfoln("EXTPUMP", F("Disabled: expired anti stuck time")); + Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: expired anti stuck time")); } - } else if (vars.states.externalPump && this->heatingEnabled && this->extPumpStartReason == MainTask::PumpStartReason::ANTISTUCK) { + } else if (vars.externalPump.state && this->heatingEnabled && this->extPumpStartReason == MainTask::PumpStartReason::ANTISTUCK) { this->extPumpStartReason = MainTask::PumpStartReason::HEATING; - } else if (!vars.states.externalPump && this->heatingEnabled) { - vars.states.externalPump = true; + } else if (!vars.externalPump.state && this->heatingEnabled) { + vars.externalPump.state = true; this->externalPumpStartTime = millis(); this->extPumpStartReason = MainTask::PumpStartReason::HEATING; digitalWrite(configuredGpio, HIGH); - Log.sinfoln("EXTPUMP", F("Enabled: heating on")); + Log.sinfoln(FPSTR(L_EXTPUMP), F("Enabled: heating on")); - } else if (!vars.states.externalPump && (vars.parameters.extPumpLastEnableTime == 0 || millis() - vars.parameters.extPumpLastEnableTime >= (settings.externalPump.antiStuckInterval * 1000ul))) { - vars.states.externalPump = true; + } else if (!vars.externalPump.state && (vars.externalPump.lastEnableTime == 0 || millis() - vars.externalPump.lastEnableTime >= (settings.externalPump.antiStuckInterval * 1000ul))) { + vars.externalPump.state = true; this->externalPumpStartTime = millis(); this->extPumpStartReason = MainTask::PumpStartReason::ANTISTUCK; digitalWrite(configuredGpio, HIGH); - Log.sinfoln("EXTPUMP", F("Enabled: anti stuck")); + Log.sinfoln(FPSTR(L_EXTPUMP), F("Enabled: anti stuck")); } } }; \ No newline at end of file diff --git a/src/MqttTask.h b/src/MqttTask.h index cd81fcf..c910f14 100644 --- a/src/MqttTask.h +++ b/src/MqttTask.h @@ -1,3 +1,4 @@ +#include #include #include #include @@ -61,10 +62,18 @@ class MqttTask : public Task { this->prevPubSettingsTime = 0; } + inline void resetPublishedSensorTime(uint8_t sensorId) { + this->prevPubSensorTime[sensorId] = 0; + } + inline void resetPublishedVarsTime() { this->prevPubVarsTime = 0; } + inline void rebuildHaEntity(uint8_t sensorId, Sensors::Settings& prevSettings) { + this->queueRebuildingHaEntities[sensorId] = prevSettings; + } + protected: MqttWiFiClient* wifiClient = nullptr; MqttClient* client = nullptr; @@ -72,12 +81,14 @@ class MqttTask : public Task { MqttWriter* writer = nullptr; UnitSystem currentUnitSystem = UnitSystem::METRIC; bool currentHomeAssistantDiscovery = false; + std::unordered_map queueRebuildingHaEntities; unsigned short readyForSendTime = 30000; unsigned long lastReconnectTime = 0; unsigned long connectedTime = 0; unsigned long disconnectedTime = 0; unsigned long prevPubVarsTime = 0; unsigned long prevPubSettingsTime = 0; + std::unordered_map prevPubSensorTime; bool connected = false; bool newConnection = false; @@ -173,11 +184,6 @@ class MqttTask : public Task { } void loop() { - if (settings.mqtt.interval > 120) { - settings.mqtt.interval = 5; - fsSettings.update(); - } - if (this->connected && !this->client->connected()) { this->connected = false; this->onDisconnect(); @@ -226,6 +232,28 @@ class MqttTask : public Task { this->prevPubSettingsTime = millis(); } + // publish sensors + for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { + if (!Sensors::hasEnabledAndValid(sensorId)) { + continue; + } + + auto& rSensor = Sensors::results[sensorId]; + bool needUpdate = false; + if (millis() - this->prevPubSensorTime[sensorId] > ((this->haHelper->getExpireAfter() - 10) * 1000u)) { + needUpdate = true; + + } else if (rSensor.activityTime >= this->prevPubSensorTime[sensorId]) { + auto estimated = rSensor.activityTime - this->prevPubSensorTime[sensorId]; + needUpdate = estimated > 1000u; + } + + if (this->newConnection || needUpdate) { + this->publishSensor(sensorId); + this->prevPubSensorTime[sensorId] = millis(); + } + } + // publish ha entities if not published if (settings.mqtt.homeAssistantDiscovery) { if (this->newConnection || !this->currentHomeAssistantDiscovery || this->currentUnitSystem != settings.system.unitSystem) { @@ -239,6 +267,79 @@ class MqttTask : public Task { this->publishNonStaticHaEntities(); } + + for (auto& [sensorId, prevSettings] : this->queueRebuildingHaEntities) { + Log.sinfoln(FPSTR(L_MQTT_HA), F("Rebuilding config for sensor #%hhu '%s'"), sensorId, prevSettings.name); + + // delete old config + if (strlen(prevSettings.name) && prevSettings.enabled) { + switch (prevSettings.type) { + case Sensors::Type::BLUETOOTH: + this->haHelper->deleteConnectionDynamicSensor(prevSettings); + this->haHelper->deleteSignalQualityDynamicSensor(prevSettings); + this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::TEMPERATURE); + this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::HUMIDITY); + this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::BATTERY); + this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::RSSI); + break; + + case Sensors::Type::DALLAS_TEMP: + this->haHelper->deleteConnectionDynamicSensor(prevSettings); + this->haHelper->deleteSignalQualityDynamicSensor(prevSettings); + this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::TEMPERATURE); + break; + + case Sensors::Type::MANUAL: { + String topic = this->haHelper->getDeviceTopic( + F("sensors"), + Sensors::makeObjectId(prevSettings.name), + F("set") + ); + this->client->unsubscribe(topic.c_str()); + } + + default: + this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::PRIMARY); + } + } + + if (!Sensors::hasEnabledAndValid(sensorId)) { + continue; + } + + // make new config + auto& sSettings = Sensors::settings[sensorId]; + switch (sSettings.type) { + case Sensors::Type::BLUETOOTH: + this->haHelper->publishConnectionDynamicSensor(sSettings); + this->haHelper->publishSignalQualityDynamicSensor(sSettings, false); + this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::TEMPERATURE, settings.system.unitSystem); + this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::HUMIDITY, settings.system.unitSystem); + this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::BATTERY, settings.system.unitSystem); + this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::RSSI, settings.system.unitSystem, false); + break; + + case Sensors::Type::DALLAS_TEMP: + this->haHelper->publishConnectionDynamicSensor(sSettings); + this->haHelper->publishSignalQualityDynamicSensor(sSettings, false); + this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::TEMPERATURE, settings.system.unitSystem); + break; + + case Sensors::Type::MANUAL: { + String topic = this->haHelper->getDeviceTopic( + F("sensors"), + Sensors::makeObjectId(prevSettings.name), + F("set") + ); + this->client->subscribe(topic.c_str()); + } + + default: + this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::PRIMARY, settings.system.unitSystem); + } + } + this->queueRebuildingHaEntities.clear(); + } else if (this->currentHomeAssistantDiscovery) { this->currentHomeAssistantDiscovery = false; } @@ -303,33 +404,51 @@ class MqttTask : public Task { doc.shrinkToFit(); if (this->haHelper->getDeviceTopic("state/set").equals(topic)) { - this->writer->publish(this->haHelper->getDeviceTopic("state/set").c_str(), nullptr, 0, true); + this->writer->publish(topic, nullptr, 0, true); if (jsonToVars(doc, vars)) { this->resetPublishedVarsTime(); } } else if (this->haHelper->getDeviceTopic("settings/set").equals(topic)) { - this->writer->publish(this->haHelper->getDeviceTopic("settings/set").c_str(), nullptr, 0, true); + this->writer->publish(topic, nullptr, 0, true); if (safeJsonToSettings(doc, settings)) { this->resetPublishedSettingsTime(); fsSettings.update(); } + + } else { + this->writer->publish(topic, nullptr, 0, true); + + String _topic = topic; + String sensorsTopic = this->haHelper->getDeviceTopic("sensors/"); + unsigned short stLength = sensorsTopic.length(); + + if (_topic.startsWith(sensorsTopic) && _topic.endsWith("/set")) { + if (_topic.length() > stLength + 4) { + String name = _topic.substring(stLength, _topic.indexOf('/', stLength)); + int16_t id = Sensors::getIdByObjectId(name.c_str()); + + if (id == -1) { + return; + } + + if (jsonToSensorResult(id, doc)) { + this->resetPublishedSensorTime(id); + } + } + } } } void publishHaEntities() { // heating - this->haHelper->publishSwitchHeating(false); this->haHelper->publishSwitchHeatingTurbo(false); this->haHelper->publishInputHeatingHysteresis(settings.system.unitSystem); this->haHelper->publishInputHeatingTurboFactor(false); this->haHelper->publishInputHeatingMinTemp(settings.system.unitSystem); this->haHelper->publishInputHeatingMaxTemp(settings.system.unitSystem); - this->haHelper->publishSensorHeatingSetpoint(settings.system.unitSystem, false); - this->haHelper->publishSensorBoilerHeatingMinTemp(settings.system.unitSystem, false); - this->haHelper->publishSensorBoilerHeatingMaxTemp(settings.system.unitSystem, false); // pid this->haHelper->publishSwitchPid(); @@ -347,103 +466,98 @@ class MqttTask : public Task { this->haHelper->publishInputEquithermFactorT(false); // states - this->haHelper->publishStateStatus(); - this->haHelper->publishStateEmergency(); - this->haHelper->publishStateOtStatus(); - this->haHelper->publishStateHeating(); - this->haHelper->publishStateFlame(); - this->haHelper->publishStateFault(); - this->haHelper->publishStateDiagnostic(); - this->haHelper->publishStateExtPump(false); + this->haHelper->publishStatusState(); + this->haHelper->publishEmergencyState(); + this->haHelper->publishOpenthermConnectedState(); + this->haHelper->publishHeatingState(); + this->haHelper->publishFlameState(); + this->haHelper->publishFaultState(); + this->haHelper->publishDiagState(); + this->haHelper->publishExternalPumpState(false); // sensors - this->haHelper->publishSensorModulation(); - this->haHelper->publishSensorPressure(settings.system.unitSystem, false); - this->haHelper->publishSensorPower(); - this->haHelper->publishSensorFaultCode(); - this->haHelper->publishSensorDiagnosticCode(); - this->haHelper->publishSensorRssi(false); - this->haHelper->publishSensorUptime(false); - this->haHelper->publishOutdoorSensorConnected(); - this->haHelper->publishOutdoorSensorRssi(false); - this->haHelper->publishOutdoorSensorBattery(false); - this->haHelper->publishOutdoorSensorHumidity(false); - this->haHelper->publishIndoorSensorConnected(); - this->haHelper->publishIndoorSensorRssi(false); - this->haHelper->publishIndoorSensorBattery(false); - this->haHelper->publishIndoorSensorHumidity(false); - - // temperatures - this->haHelper->publishSensorHeatingTemp(settings.system.unitSystem); - this->haHelper->publishSensorHeatingReturnTemp(settings.system.unitSystem, false); - this->haHelper->publishSensorExhaustTemp(settings.system.unitSystem, false); + this->haHelper->publishFaultCode(); + this->haHelper->publishDiagCode(); + this->haHelper->publishNetworkRssi(false); + this->haHelper->publishUptime(false); // buttons - this->haHelper->publishButtonRestart(false); - this->haHelper->publishButtonResetFault(); - this->haHelper->publishButtonResetDiagnostic(); + this->haHelper->publishRestartButton(false); + this->haHelper->publishResetFaultButton(); + this->haHelper->publishResetDiagButton(); + + // dynamic sensors + for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { + if (!Sensors::hasEnabledAndValid(sensorId)) { + continue; + } + + auto& sSettings = Sensors::settings[sensorId]; + switch (sSettings.type) { + case Sensors::Type::BLUETOOTH: + this->haHelper->publishConnectionDynamicSensor(sSettings); + this->haHelper->publishSignalQualityDynamicSensor(sSettings, false); + this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::TEMPERATURE, settings.system.unitSystem); + this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::HUMIDITY, settings.system.unitSystem); + this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::BATTERY, settings.system.unitSystem); + this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::RSSI, settings.system.unitSystem, false); + break; + + case Sensors::Type::DALLAS_TEMP: + this->haHelper->publishConnectionDynamicSensor(sSettings); + this->haHelper->publishSignalQualityDynamicSensor(sSettings, false); + this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::TEMPERATURE, settings.system.unitSystem); + break; + + case Sensors::Type::MANUAL: { + String topic = this->haHelper->getDeviceTopic( + F("sensors"), + Sensors::makeObjectId(sSettings.name), + F("set") + ); + this->client->subscribe(topic.c_str()); + } + + default: + this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::PRIMARY, settings.system.unitSystem); + } + } } bool publishNonStaticHaEntities(bool force = false) { static byte _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0; - static bool _noRegulators, _editableOutdoorTemp, _editableIndoorTemp, _dhwPresent = false; + static bool _indoorTempControl, _dhwPresent = false; bool published = false; - bool noRegulators = !settings.opentherm.nativeHeatingControl && !settings.pid.enable && !settings.equitherm.enable; - byte heatingMinTemp = 0; - byte heatingMaxTemp = 0; - bool editableOutdoorTemp = settings.sensors.outdoor.type == SensorType::MANUAL; - bool editableIndoorTemp = settings.sensors.indoor.type == SensorType::MANUAL; - - if (noRegulators) { - heatingMinTemp = settings.heating.minTemp; - heatingMaxTemp = settings.heating.maxTemp; - - } else { - heatingMinTemp = convertTemp(THERMOSTAT_INDOOR_MIN_TEMP, UnitSystem::METRIC, settings.system.unitSystem); - heatingMaxTemp = convertTemp(THERMOSTAT_INDOOR_MAX_TEMP, UnitSystem::METRIC, settings.system.unitSystem); - } - if (force || _dhwPresent != settings.opentherm.dhwPresent) { _dhwPresent = settings.opentherm.dhwPresent; if (_dhwPresent) { - this->haHelper->publishSwitchDhw(false); - this->haHelper->publishSensorBoilerDhwMinTemp(settings.system.unitSystem, false); - this->haHelper->publishSensorBoilerDhwMaxTemp(settings.system.unitSystem, false); this->haHelper->publishInputDhwMinTemp(settings.system.unitSystem); this->haHelper->publishInputDhwMaxTemp(settings.system.unitSystem); - this->haHelper->publishStateDhw(); - this->haHelper->publishSensorDhwTemp(settings.system.unitSystem); - this->haHelper->publishSensorDhwFlowRate(settings.system.unitSystem); + this->haHelper->publishDhwState(); } else { this->haHelper->deleteSwitchDhw(); - this->haHelper->deleteSensorBoilerDhwMinTemp(); - this->haHelper->deleteSensorBoilerDhwMaxTemp(); this->haHelper->deleteInputDhwMinTemp(); this->haHelper->deleteInputDhwMaxTemp(); - this->haHelper->deleteStateDhw(); - this->haHelper->deleteSensorDhwTemp(); + this->haHelper->deleteDhwState(); this->haHelper->deleteInputDhwTarget(); this->haHelper->deleteClimateDhw(); - this->haHelper->deleteSensorDhwFlowRate(); } published = true; } - if (force || _noRegulators != noRegulators || _heatingMinTemp != heatingMinTemp || _heatingMaxTemp != heatingMaxTemp) { - _heatingMinTemp = heatingMinTemp; - _heatingMaxTemp = heatingMaxTemp; - _noRegulators = noRegulators; + if (force || _indoorTempControl != vars.master.heating.indoorTempControl || _heatingMinTemp != vars.master.heating.minTemp || _heatingMaxTemp != vars.master.heating.maxTemp) { + _heatingMinTemp = vars.master.heating.minTemp; + _heatingMaxTemp = vars.master.heating.maxTemp; + _indoorTempControl = vars.master.heating.indoorTempControl; - this->haHelper->publishInputHeatingTarget(settings.system.unitSystem, heatingMinTemp, heatingMaxTemp, false); this->haHelper->publishClimateHeating( settings.system.unitSystem, - heatingMinTemp, - heatingMaxTemp, - noRegulators ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR + vars.master.heating.minTemp, + vars.master.heating.maxTemp ); published = true; @@ -453,49 +567,39 @@ class MqttTask : public Task { _dhwMinTemp = settings.dhw.minTemp; _dhwMaxTemp = settings.dhw.maxTemp; - this->haHelper->publishInputDhwTarget(settings.system.unitSystem, settings.dhw.minTemp, settings.dhw.maxTemp, false); this->haHelper->publishClimateDhw(settings.system.unitSystem, settings.dhw.minTemp, settings.dhw.maxTemp); published = true; } - if (force || _editableOutdoorTemp != editableOutdoorTemp) { - _editableOutdoorTemp = editableOutdoorTemp; + return published; + } - if (editableOutdoorTemp) { - this->haHelper->deleteSensorOutdoorTemp(); - this->haHelper->publishInputOutdoorTemp(settings.system.unitSystem); - } else { - this->haHelper->deleteInputOutdoorTemp(); - this->haHelper->publishSensorOutdoorTemp(settings.system.unitSystem); - } + bool publishSettings(const char* topic) { + JsonDocument doc; + safeSettingsToJson(settings, doc); + doc.shrinkToFit(); - published = true; - } + return this->writer->publish(topic, doc, true); + } - if (force || _editableIndoorTemp != editableIndoorTemp) { - _editableIndoorTemp = editableIndoorTemp; + bool publishSensor(uint8_t sensorId) { + auto& sSettings = Sensors::settings[sensorId]; - if (editableIndoorTemp) { - this->haHelper->deleteSensorIndoorTemp(); - this->haHelper->publishInputIndoorTemp(settings.system.unitSystem); - } else { - this->haHelper->deleteInputIndoorTemp(); - this->haHelper->publishSensorIndoorTemp(settings.system.unitSystem); - } + if (!Sensors::isValidSensorId(sensorId)) { + return false; - published = true; + } else if (!strlen(sSettings.name)) { + return false; } - return published; - } - - bool publishSettings(const char* topic) { JsonDocument doc; - safeSettingsToJson(settings, doc); + sensorResultToJson(sensorId, doc); doc.shrinkToFit(); - return this->writer->publish(topic, doc, true); + String objId = Sensors::makeObjectId(sSettings.name); + String topic = this->haHelper->getDeviceTopic(F("sensors"), objId); + return this->writer->publish(topic.c_str(), doc, true); } bool publishVariables(const char* topic) { diff --git a/src/OpenThermTask.h b/src/OpenThermTask.h index 542e80b..3c4c484 100644 --- a/src/OpenThermTask.h +++ b/src/OpenThermTask.h @@ -11,22 +11,22 @@ class OpenThermTask : public Task { protected: const unsigned short readyTime = 60000; - const unsigned short dhwSetTempInterval = 60000; const unsigned short heatingSetTempInterval = 60000; + const unsigned short dhwSetTempInterval = 60000; + const unsigned short ch2SetTempInterval = 60000; const unsigned int initializingInterval = 3600000; CustomOpenTherm* instance = nullptr; unsigned long instanceCreatedTime = 0; byte instanceInGpio = 0; byte instanceOutGpio = 0; - bool isInitialized = false; + bool initialized = false; unsigned long initializedTime = 0; - unsigned int initializedMemberIdCode = 0; unsigned long lastSuccessResponse = 0; unsigned long prevUpdateNonEssentialVars = 0; - unsigned long dhwSetTempTime = 0; unsigned long heatingSetTempTime = 0; - bool heatingBlocking = false; + unsigned long dhwSetTempTime = 0; + unsigned long ch2SetTempTime = 0; byte configuredRxLedGpio = GPIO_IS_NOT_CONFIGURED; #if defined(ARDUINO_ARCH_ESP32) @@ -44,11 +44,12 @@ class OpenThermTask : public Task { #endif void setup() { + // Convert defaults at start if (settings.system.unitSystem != UnitSystem::METRIC) { - vars.parameters.heatingMinTemp = convertTemp(vars.parameters.heatingMinTemp, UnitSystem::METRIC, settings.system.unitSystem); - vars.parameters.heatingMaxTemp = convertTemp(vars.parameters.heatingMaxTemp, UnitSystem::METRIC, settings.system.unitSystem); - vars.parameters.dhwMinTemp = convertTemp(vars.parameters.dhwMinTemp, UnitSystem::METRIC, settings.system.unitSystem); - vars.parameters.dhwMaxTemp = convertTemp(vars.parameters.dhwMaxTemp, UnitSystem::METRIC, settings.system.unitSystem); + vars.slave.heating.minTemp = convertTemp(vars.slave.heating.minTemp, UnitSystem::METRIC, settings.system.unitSystem); + vars.slave.heating.maxTemp = convertTemp(vars.slave.heating.maxTemp, UnitSystem::METRIC, settings.system.unitSystem); + vars.slave.dhw.minTemp = convertTemp(vars.slave.dhw.minTemp, UnitSystem::METRIC, settings.system.unitSystem); + vars.slave.dhw.maxTemp = convertTemp(vars.slave.dhw.maxTemp, UnitSystem::METRIC, settings.system.unitSystem); } // delete instance @@ -75,7 +76,7 @@ class OpenThermTask : public Task { this->instanceCreatedTime = millis(); this->instanceInGpio = settings.opentherm.inGpio; this->instanceOutGpio = settings.opentherm.outGpio; - this->isInitialized = false; + this->initialized = false; Log.sinfoln(FPSTR(L_OT), F("Started. GPIO IN: %hhu, GPIO OUT: %hhu"), settings.opentherm.inGpio, settings.opentherm.outGpio); @@ -105,14 +106,19 @@ class OpenThermTask : public Task { } void loop() { - static float currentHeatingTemp = 0.0f; - static float currentDhwTemp = 0.0f; - if (this->instanceInGpio != settings.opentherm.inGpio || this->instanceOutGpio != settings.opentherm.outGpio) { this->setup(); - } else if (this->initializedMemberIdCode != settings.opentherm.memberIdCode || millis() - this->initializedTime > this->initializingInterval) { - this->isInitialized = false; + } else if (vars.master.memberId != settings.opentherm.memberId || vars.master.flags != settings.opentherm.flags) { + this->initialized = false; + vars.master.memberId = settings.opentherm.memberId; + vars.master.flags = settings.opentherm.flags; + vars.master.protocolVersion = 2.2f; + vars.master.appVersion = 0x3F; + vars.master.type = 0x01; + + } else if (millis() - this->initializedTime > this->initializingInterval) { + this->initialized = false; } if (this->instance == nullptr) { @@ -136,16 +142,26 @@ class OpenThermTask : public Task { } } - bool heatingEnabled = (vars.states.emergency || settings.heating.enable) + // Heating settings + vars.master.heating.enabled = this->isReady() + && (settings.heating.enabled || vars.emergency.state) && vars.cascadeControl.input - && this->isReady() - && !this->heatingBlocking; - bool heatingCh2Enabled = settings.opentherm.heatingCh2Enabled; + && !vars.master.heating.blocking; + + // DHW settings + vars.master.dhw.enabled = settings.opentherm.dhwPresent && settings.dhw.enabled; + vars.master.dhw.targetTemp = settings.dhw.target; + + // CH2 settings + vars.master.ch2.enabled = settings.opentherm.heatingCh2Enabled + || (settings.opentherm.heatingCh1ToCh2 && vars.master.heating.enabled) + || (settings.opentherm.dhwToCh2 && settings.opentherm.dhwPresent && settings.dhw.enabled); + if (settings.opentherm.heatingCh1ToCh2) { - heatingCh2Enabled = heatingEnabled; + vars.master.ch2.targetTemp = vars.master.heating.targetTemp; } else if (settings.opentherm.dhwToCh2) { - heatingCh2Enabled = settings.opentherm.dhwPresent && settings.dhw.enable; + vars.master.ch2.targetTemp = vars.master.dhw.targetTemp; } // Set boiler status LB @@ -159,11 +175,11 @@ class OpenThermTask : public Task { } unsigned long response = this->instance->setBoilerStatus( - heatingEnabled, - settings.opentherm.dhwPresent && settings.dhw.enable, + vars.master.heating.enabled, + vars.master.dhw.enabled, false, settings.opentherm.nativeHeatingControl, - heatingCh2Enabled, + vars.master.ch2.enabled, settings.opentherm.summerWinterMode, settings.opentherm.dhwBlocking, statusLb @@ -177,108 +193,131 @@ class OpenThermTask : public Task { ); } - if (!vars.states.otStatus && millis() - this->lastSuccessResponse < 1150) { + if (!vars.slave.connected && millis() - this->lastSuccessResponse < 1150) { Log.sinfoln(FPSTR(L_OT), F("Connected")); - vars.states.otStatus = true; + vars.slave.connected = true; - } else if (vars.states.otStatus && millis() - this->lastSuccessResponse > 1150) { + } else if (vars.slave.connected && millis() - this->lastSuccessResponse > 1150) { Log.swarningln(FPSTR(L_OT), F("Disconnected")); - if (settings.sensors.outdoor.type == SensorType::BOILER_OUTDOOR) { - vars.sensors.outdoor.connected = false; - } + // Mark sensors as disconnected + Sensors::setConnectionStatusByType(Sensors::Type::OT_OUTDOOR_TEMP, false); + Sensors::setConnectionStatusByType(Sensors::Type::OT_HEATING_TEMP, false); + Sensors::setConnectionStatusByType(Sensors::Type::OT_HEATING_RETURN_TEMP, false); + Sensors::setConnectionStatusByType(Sensors::Type::OT_DHW_TEMP, false); + Sensors::setConnectionStatusByType(Sensors::Type::OT_DHW_TEMP2, false); + Sensors::setConnectionStatusByType(Sensors::Type::OT_DHW_FLOW_RATE, false); + Sensors::setConnectionStatusByType(Sensors::Type::OT_CH2_TEMP, false); + Sensors::setConnectionStatusByType(Sensors::Type::OT_EXHAUST_TEMP, false); + Sensors::setConnectionStatusByType(Sensors::Type::OT_HEAT_EXCHANGER_TEMP, false); + Sensors::setConnectionStatusByType(Sensors::Type::OT_PRESSURE, false); + Sensors::setConnectionStatusByType(Sensors::Type::OT_MODULATION_LEVEL, false); + Sensors::setConnectionStatusByType(Sensors::Type::OT_CURRENT_POWER, false); - if (settings.sensors.indoor.type == SensorType::BOILER_RETURN) { - vars.sensors.indoor.connected = false; - } - - vars.states.otStatus = false; - this->isInitialized = false; + this->initialized = false; + vars.slave.connected = false; } // If boiler is disconnected, no need try setting other OT stuff - if (!vars.states.otStatus) { - vars.states.heating = false; - vars.states.dhw = false; - vars.states.flame = false; - vars.states.fault = false; - vars.states.diagnostic = false; + if (!vars.slave.connected) { + vars.slave.heating.enabled = false; + vars.slave.heating.active = false; + vars.slave.dhw.enabled = false; + vars.slave.dhw.active = false; + vars.slave.flame = false; + vars.slave.fault.active = false; + vars.slave.fault.code = 0; + vars.slave.diag.active = false; + vars.slave.diag.code = 0; return; } - if (!this->isInitialized) { + if (!this->initialized) { Log.sinfoln(FPSTR(L_OT), F("Initializing...")); - this->isInitialized = true; + this->initialized = true; this->initializedTime = millis(); - this->initializedMemberIdCode = settings.opentherm.memberIdCode; this->initialize(); } - if (vars.parameters.heatingEnabled != heatingEnabled) { + if (vars.master.heating.enabled != vars.slave.heating.enabled) { this->prevUpdateNonEssentialVars = 0; - vars.parameters.heatingEnabled = heatingEnabled; - Log.sinfoln(FPSTR(L_OT_HEATING), "%s", heatingEnabled ? F("Enabled") : F("Disabled")); + vars.slave.heating.enabled = vars.master.heating.enabled; + Log.sinfoln(FPSTR(L_OT_HEATING), "%s", vars.master.heating.enabled ? F("Enabled") : F("Disabled")); } - vars.states.heating = CustomOpenTherm::isCentralHeatingActive(response); - vars.states.dhw = settings.opentherm.dhwPresent ? CustomOpenTherm::isHotWaterActive(response) : false; - vars.states.flame = CustomOpenTherm::isFlameOn(response); - vars.states.fault = CustomOpenTherm::isFault(response); - vars.states.diagnostic = CustomOpenTherm::isDiagnostic(response); + if (vars.master.dhw.enabled != vars.slave.dhw.enabled) { + this->prevUpdateNonEssentialVars = 0; + vars.slave.dhw.enabled = vars.master.heating.enabled; + Log.sinfoln(FPSTR(L_OT_DHW), "%s", vars.master.heating.enabled ? F("Enabled") : F("Disabled")); + } + + vars.slave.heating.active = CustomOpenTherm::isCentralHeatingActive(response); + vars.slave.dhw.active = settings.opentherm.dhwPresent ? CustomOpenTherm::isHotWaterActive(response) : false; + vars.slave.flame = CustomOpenTherm::isFlameOn(response); + vars.slave.fault.active = CustomOpenTherm::isFault(response); + vars.slave.diag.active = CustomOpenTherm::isDiagnostic(response); Log.snoticeln( - FPSTR(L_OT), - F("Received boiler status. Heating: %hhu; DHW: %hhu; flame: %hhu; fault: %hhu; diag: %hhu"), - vars.states.heating, vars.states.dhw, vars.states.flame, vars.states.fault, vars.states.diagnostic + FPSTR(L_OT), F("Received boiler status. Heating: %hhu; DHW: %hhu; flame: %hhu; fault: %hhu; diag: %hhu"), + vars.slave.heating.active, vars.slave.dhw.active, + vars.slave.flame, vars.slave.fault.active, vars.slave.diag.active ); // These parameters will be updated every minute if (millis() - this->prevUpdateNonEssentialVars > 60000) { if (this->updateMinModulationLevel()) { Log.snoticeln( - FPSTR(L_OT), - F("Received min modulation: %hhu%%, max power: %hhu kW"), - vars.parameters.minModulation, - vars.parameters.maxPower + FPSTR(L_OT), F("Received min modulation: %hhu%%, max power: %.2f kW"), + vars.slave.modulation.min, vars.slave.power.max ); - if (settings.opentherm.maxModulation < vars.parameters.minModulation) { - settings.opentherm.maxModulation = vars.parameters.minModulation; + if (settings.opentherm.maxModulation < vars.slave.modulation.min) { + settings.opentherm.maxModulation = vars.slave.modulation.min; fsSettings.update(); - Log.swarningln(FPSTR(L_SETTINGS_OT), F("Updated min modulation: %hhu%%"), settings.opentherm.maxModulation); + + Log.swarningln( + FPSTR(L_SETTINGS_OT), F("Updated min modulation: %hhu%%"), + settings.opentherm.maxModulation + ); } - if (fabsf(settings.opentherm.maxPower) < 0.1f && vars.parameters.maxPower > 0) { - settings.opentherm.maxPower = vars.parameters.maxPower; - - if (vars.parameters.minModulation > 0) { - settings.opentherm.minPower = (vars.parameters.minModulation / 100.0f) * vars.parameters.maxPower; - } + if (fabsf(settings.opentherm.maxPower) < 0.1f && vars.slave.power.max > 0.1f) { + settings.opentherm.maxPower = vars.slave.power.max; + settings.opentherm.minPower = vars.slave.power.min; fsSettings.update(); - Log.swarningln(FPSTR(L_SETTINGS_OT), F("Updated max power: %.2f kW"), settings.opentherm.maxPower); + Log.swarningln( + FPSTR(L_SETTINGS_OT), F("Updated power, min: %.2f kW, max: %.2f kW"), + settings.opentherm.minPower, settings.opentherm.maxPower + ); } } else { Log.swarningln(FPSTR(L_OT), F("Failed receive min modulation and max power")); } - if (!heatingEnabled && settings.opentherm.modulationSyncWithHeating) { + if (!vars.master.heating.enabled && settings.opentherm.modulationSyncWithHeating) { if (this->setMaxModulationLevel(0)) { - Log.snoticeln(FPSTR(L_OT), F("Set max modulation: 0% (off)")); + Log.snoticeln(FPSTR(L_OT), F("Set max modulation: 0% (response: %hhu%%)"), vars.slave.modulation.max); } else { - Log.swarningln(FPSTR(L_OT), F("Failed set max modulation: 0% (off)")); + Log.swarningln(FPSTR(L_OT), F("Failed set max modulation: 0% (response: %hhu%%)"), vars.slave.modulation.max); } } else { if (this->setMaxModulationLevel(settings.opentherm.maxModulation)) { - Log.snoticeln(FPSTR(L_OT), F("Set max modulation: %hhu%%"), settings.opentherm.maxModulation); + Log.snoticeln( + FPSTR(L_OT), F("Set max modulation: %hhu%% (response: %hhu%%)"), + settings.opentherm.maxModulation, vars.slave.modulation.max + ); } else { - Log.swarningln(FPSTR(L_OT), F("Failed set max modulation: %hhu%%"), settings.opentherm.maxModulation); + Log.swarningln( + FPSTR(L_OT), F("Failed set max modulation: %hhu%% (response: %hhu%%)"), + settings.opentherm.maxModulation, vars.slave.modulation.max + ); } } @@ -286,270 +325,460 @@ class OpenThermTask : public Task { // Get DHW min/max temp (if necessary) if (settings.opentherm.dhwPresent && settings.opentherm.getMinMaxTemp) { if (this->updateMinMaxDhwTemp()) { + uint8_t convertedMinTemp = convertTemp( + vars.slave.dhw.minTemp, + settings.opentherm.unitSystem, + settings.system.unitSystem + ); + + uint8_t convertedMaxTemp = convertTemp( + vars.slave.dhw.maxTemp, + settings.opentherm.unitSystem, + settings.system.unitSystem + ); + Log.snoticeln( - FPSTR(L_OT_DHW), - F("Received min temp: %hhu, max temp: %hhu"), - vars.parameters.dhwMinTemp, - vars.parameters.dhwMaxTemp + FPSTR(L_OT_DHW), F("Received min temp: %hhu (converted: %hhu), max temp: %hhu (converted: %hhu)"), + vars.slave.dhw.minTemp, convertedMinTemp, vars.slave.dhw.maxTemp, convertedMaxTemp ); - if (settings.dhw.minTemp < vars.parameters.dhwMinTemp) { - settings.dhw.minTemp = vars.parameters.dhwMinTemp; + if (settings.dhw.minTemp < convertedMinTemp) { + settings.dhw.minTemp = convertedMinTemp; fsSettings.update(); + Log.swarningln(FPSTR(L_SETTINGS_DHW), F("Updated min temp: %hhu"), settings.dhw.minTemp); } - if (settings.dhw.maxTemp > vars.parameters.dhwMaxTemp) { - settings.dhw.maxTemp = vars.parameters.dhwMaxTemp; + if (settings.dhw.maxTemp > convertedMaxTemp) { + settings.dhw.maxTemp = convertedMaxTemp; fsSettings.update(); + Log.swarningln(FPSTR(L_SETTINGS_DHW), F("Updated max temp: %hhu"), settings.dhw.maxTemp); } } else { - vars.parameters.dhwMinTemp = convertTemp(DEFAULT_DHW_MIN_TEMP, UnitSystem::METRIC, settings.system.unitSystem); - vars.parameters.dhwMaxTemp = convertTemp(DEFAULT_DHW_MAX_TEMP, UnitSystem::METRIC, settings.system.unitSystem); - Log.swarningln(FPSTR(L_OT_DHW), F("Failed receive min/max temp")); } + } - if (settings.dhw.minTemp >= settings.dhw.maxTemp) { - settings.dhw.minTemp = vars.parameters.dhwMinTemp; - settings.dhw.maxTemp = vars.parameters.dhwMaxTemp; - fsSettings.update(); - } + if (settings.dhw.minTemp >= settings.dhw.maxTemp) { + settings.dhw.minTemp = convertTemp(DEFAULT_DHW_MIN_TEMP, UnitSystem::METRIC, settings.system.unitSystem); + settings.dhw.maxTemp = convertTemp(DEFAULT_DHW_MAX_TEMP, UnitSystem::METRIC, settings.system.unitSystem); + fsSettings.update(); } // Get heating min/max temp if (settings.opentherm.getMinMaxTemp) { if (this->updateMinMaxHeatingTemp()) { + uint8_t convertedMinTemp = convertTemp( + vars.slave.heating.minTemp, + settings.opentherm.unitSystem, + settings.system.unitSystem + ); + + uint8_t convertedMaxTemp = convertTemp( + vars.slave.heating.maxTemp, + settings.opentherm.unitSystem, + settings.system.unitSystem + ); + Log.snoticeln( - FPSTR(L_OT_HEATING), - F("Received min temp: %hhu, max temp: %hhu"), - vars.parameters.heatingMinTemp, - vars.parameters.heatingMaxTemp + FPSTR(L_OT_HEATING), F("Received min temp: %hhu (converted: %hhu), max temp: %hhu (converted: %hhu)"), + vars.slave.heating.minTemp, convertedMinTemp, vars.slave.heating.maxTemp, convertedMaxTemp ); - if (settings.heating.minTemp < vars.parameters.heatingMinTemp) { - settings.heating.minTemp = vars.parameters.heatingMinTemp; + if (settings.heating.minTemp < convertedMinTemp) { + settings.heating.minTemp = convertedMinTemp; fsSettings.update(); + Log.swarningln(FPSTR(L_SETTINGS_HEATING), F("Updated min temp: %hhu"), settings.heating.minTemp); } - if (settings.heating.maxTemp > vars.parameters.heatingMaxTemp) { - settings.heating.maxTemp = vars.parameters.heatingMaxTemp; + if (settings.heating.maxTemp > convertedMaxTemp) { + settings.heating.maxTemp = convertedMaxTemp; fsSettings.update(); + Log.swarningln(FPSTR(L_SETTINGS_HEATING), F("Updated max temp: %hhu"), settings.heating.maxTemp); } } else { - vars.parameters.heatingMinTemp = convertTemp(DEFAULT_HEATING_MIN_TEMP, UnitSystem::METRIC, settings.system.unitSystem); - vars.parameters.heatingMaxTemp = convertTemp(DEFAULT_HEATING_MAX_TEMP, UnitSystem::METRIC, settings.system.unitSystem); - Log.swarningln(FPSTR(L_OT_HEATING), F("Failed receive min/max temp")); } } if (settings.heating.minTemp >= settings.heating.maxTemp) { - settings.heating.minTemp = vars.parameters.heatingMinTemp; - settings.heating.maxTemp = vars.parameters.heatingMaxTemp; + settings.heating.minTemp = convertTemp(DEFAULT_HEATING_MIN_TEMP, UnitSystem::METRIC, settings.system.unitSystem);; + settings.heating.maxTemp = convertTemp(DEFAULT_HEATING_MAX_TEMP, UnitSystem::METRIC, settings.system.unitSystem);; fsSettings.update(); } // Get fault code (if necessary) - if (vars.states.fault) { + if (vars.slave.fault.active) { if (this->updateFaultCode()) { Log.snoticeln( - FPSTR(L_OT), - F("Received fault code: %hhu (0x%02X)"), - vars.sensors.faultCode, - vars.sensors.faultCode + FPSTR(L_OT), F("Received fault code: %hhu (0x%02X)"), + vars.slave.fault.code, vars.slave.fault.code ); } else { - vars.sensors.faultCode = 0; - Log.swarningln(FPSTR(L_OT), F("Failed receive fault code")); } - } else if (vars.sensors.faultCode != 0) { - vars.sensors.faultCode = 0; + } else if (vars.slave.fault.code != 0) { + vars.slave.fault.code = 0; } // Get diagnostic code (if necessary) - if (vars.states.fault || vars.states.diagnostic) { + if (vars.slave.fault.active || vars.slave.diag.active) { if (this->updateDiagCode()) { Log.snoticeln( - FPSTR(L_OT), - F("Received diag code: %hu (0x%02X)"), - vars.sensors.diagnosticCode, - vars.sensors.diagnosticCode + FPSTR(L_OT), F("Received diag code: %hu (0x%02X)"), + vars.slave.diag.code, vars.slave.diag.code ); } else { - vars.sensors.diagnosticCode = 0; - Log.swarningln(FPSTR(L_OT), F("Failed receive diag code")); } - } else if (vars.sensors.diagnosticCode != 0) { - vars.sensors.diagnosticCode = 0; + } else if (vars.slave.diag.code != 0) { + vars.slave.diag.code = 0; } - // If filtering is disabled, then it is enough to - // update these parameters once a minute - if (!settings.opentherm.filterNumValues.enable) { - // Get outdoor temp (if necessary) - if (settings.sensors.outdoor.type == SensorType::BOILER_OUTDOOR) { - if (this->updateOutdoorTemp()) { - if (!vars.sensors.outdoor.connected) { - vars.sensors.outdoor.connected = true; - } + this->prevUpdateNonEssentialVars = millis(); + } + - Log.snoticeln(FPSTR(L_OT), F("Received outdoor temp: %.2f"), vars.temperatures.outdoor); + // Update modulation level + if ( + Sensors::getAmountByType(Sensors::Type::OT_MODULATION_LEVEL) || + Sensors::getAmountByType(Sensors::Type::OT_CURRENT_POWER) + ) { + float power = 0.0f; + bool result = false; - } else { - if (vars.sensors.outdoor.connected) { - vars.sensors.outdoor.connected = false; - } + if (vars.slave.flame) { + result = this->updateModulationLevel(); - Log.swarningln(FPSTR(L_OT), F("Failed receive outdoor temp")); + if (result) { + if (settings.opentherm.maxPower > 0.1f) { + float modulatedPower = settings.opentherm.maxPower - settings.opentherm.minPower; + power = settings.opentherm.minPower + (modulatedPower / 100.0f * vars.slave.modulation.current); } + + Log.snoticeln( + FPSTR(L_OT), F("Received modulation level: %.2f%%, power: %.2f of %.2f kW (min: %.2f kW)"), + vars.slave.modulation.current, power, settings.opentherm.maxPower, settings.opentherm.minPower + ); + + } else { + Log.swarningln(FPSTR(L_OT), F("Failed receive modulation level")); } - - // Get pressure - if (this->updatePressure()) { - Log.snoticeln(FPSTR(L_OT), F("Received pressure: %.2f"), vars.sensors.pressure); + } + + // Modulation level sensors + Sensors::setValueByType( + Sensors::Type::OT_MODULATION_LEVEL, vars.slave.modulation.current, + Sensors::ValueType::PRIMARY, true, true + ); + + // Power sensors + Sensors::setValueByType( + Sensors::Type::OT_CURRENT_POWER, power, + Sensors::ValueType::PRIMARY, true, true + ); + } + + // Update DHW temp + if (Sensors::getAmountByType(Sensors::Type::OT_DHW_TEMP)) { + float convertedDhwTemp = 0.0f; + bool result = false; + + if (settings.opentherm.dhwPresent) { + result = this->updateDhwTemp(); + + if (result) { + convertedDhwTemp = convertTemp( + vars.slave.dhw.currentTemp, + settings.opentherm.unitSystem, + settings.system.unitSystem + ); + + Log.snoticeln( + FPSTR(L_OT_DHW), F("Received temp: %.2f (converted: %.2f)"), + vars.slave.dhw.currentTemp, convertedDhwTemp + ); } else { - Log.swarningln(FPSTR(L_OT), F("Failed receive pressure")); + Log.swarningln(FPSTR(L_OT_DHW), F("Failed receive temp")); } } - this->prevUpdateNonEssentialVars = millis(); + Sensors::setValueByType( + Sensors::Type::OT_DHW_TEMP, convertedDhwTemp, + Sensors::ValueType::PRIMARY, true, true + ); } + // Update DHW temp 2 + if (Sensors::getAmountByType(Sensors::Type::OT_DHW_TEMP2)) { + float convertedDhwTemp2 = 0.0f; + bool result = false; - // Get current modulation level (if necessary) - if (vars.states.flame) { - if (this->updateModulationLevel()) { - if (settings.opentherm.maxPower > 0.1f) { - float modulatedPower = settings.opentherm.maxPower - settings.opentherm.minPower; - vars.sensors.power = settings.opentherm.minPower + (modulatedPower / 100.0f * vars.sensors.modulation); + if (settings.opentherm.dhwPresent) { + result = this->updateDhwTemp2(); + + if (result) { + convertedDhwTemp2 = convertTemp( + vars.slave.dhw.currentTemp2, + settings.opentherm.unitSystem, + settings.system.unitSystem + ); + + Log.snoticeln( + FPSTR(L_OT_DHW), F("Received temp 2: %.2f (converted: %.2f)"), + vars.slave.dhw.currentTemp2, convertedDhwTemp2 + ); } else { - vars.sensors.power = 0.0f; + Log.swarningln(FPSTR(L_OT_DHW), F("Failed receive temp 2")); } - + } + + Sensors::setValueByType( + Sensors::Type::OT_DHW_TEMP2, convertedDhwTemp2, + Sensors::ValueType::PRIMARY, true, true + ); + } + + // Update DHW flow rate + if (Sensors::getAmountByType(Sensors::Type::OT_DHW_FLOW_RATE)) { + float convertedDhwFlowRate = 0.0f; + bool result = false; + + if (settings.opentherm.dhwPresent) { + result = this->updateDhwFlowRate(); + + if (result) { + convertedDhwFlowRate = convertVolume( + vars.slave.dhw.flowRate, + settings.opentherm.unitSystem, + settings.system.unitSystem + ); + + Log.snoticeln( + FPSTR(L_OT_DHW), F("Received flow rate: %.2f (converted: %.2f)"), + vars.slave.dhw.flowRate, convertedDhwFlowRate + ); + + } else { + Log.swarningln(FPSTR(L_OT_DHW), F("Failed receive flow rate")); + } + } + + Sensors::setValueByType( + Sensors::Type::OT_DHW_FLOW_RATE, convertedDhwFlowRate, + Sensors::ValueType::PRIMARY, true, true + ); + } + + // Update heating temp + if (Sensors::getAmountByType(Sensors::Type::OT_HEATING_TEMP)) { + float convertedHeatingTemp = 0.0f; + bool result = this->updateHeatingTemp(); + + if (result) { + convertedHeatingTemp = convertTemp( + vars.slave.heating.currentTemp, + settings.opentherm.unitSystem, + settings.system.unitSystem + ); + Log.snoticeln( - FPSTR(L_OT), - F("Received modulation level: %.2f%%, power: %.2f of %.2f kW (min: %.2f kW)"), - vars.sensors.modulation, - vars.sensors.power, - settings.opentherm.maxPower, - settings.opentherm.minPower + FPSTR(L_OT_HEATING), F("Received temp: %.2f"), + vars.slave.heating.currentTemp, convertedHeatingTemp ); } else { - Log.swarningln(FPSTR(L_OT), F("Failed receive modulation level")); + Log.swarningln(FPSTR(L_OT_HEATING), F("Failed receive temp")); } - } else { - vars.sensors.modulation = 0; - vars.sensors.power = 0; + Sensors::setValueByType( + Sensors::Type::OT_HEATING_TEMP, convertedHeatingTemp, + Sensors::ValueType::PRIMARY, true, true + ); } - // Update DHW sensors (if necessary) - if (settings.opentherm.dhwPresent) { - if (this->updateDhwTemp()) { - Log.snoticeln(FPSTR(L_OT_DHW), F("Received temp: %.2f"), vars.temperatures.dhw); + // Update heating return temp + if (Sensors::getAmountByType(Sensors::Type::OT_HEATING_RETURN_TEMP)) { + float convertedHeatingReturnTemp = 0.0f; + bool result = this->updateHeatingReturnTemp(); - } else { - Log.swarningln(FPSTR(L_OT_DHW), F("Failed receive temp")); - } + if (result) { + convertedHeatingReturnTemp = convertTemp( + vars.slave.heating.returnTemp, + settings.opentherm.unitSystem, + settings.system.unitSystem + ); - if (this->updateDhwFlowRate()) { - Log.snoticeln(FPSTR(L_OT_DHW), F("Received flow rate: %.2f"), vars.sensors.dhwFlowRate); + Log.snoticeln( + FPSTR(L_OT_HEATING), F("Received return temp: %.2f (converted: %.2f)"), + vars.slave.heating.returnTemp, convertedHeatingReturnTemp + ); } else { - Log.swarningln(FPSTR(L_OT_DHW), F("Failed receive flow rate")); + Log.swarningln(FPSTR(L_OT_HEATING), F("Failed receive return temp")); } - } else { - vars.temperatures.dhw = 0.0f; - vars.sensors.dhwFlowRate = 0.0f; + Sensors::setValueByType( + Sensors::Type::OT_HEATING_RETURN_TEMP, convertedHeatingReturnTemp, + Sensors::ValueType::PRIMARY, true, true + ); } - // Get current heating temp - if (this->updateHeatingTemp()) { - Log.snoticeln(FPSTR(L_OT_HEATING), F("Received temp: %.2f"), vars.temperatures.heating); + // Update CH2 temp + if (Sensors::getAmountByType(Sensors::Type::OT_CH2_TEMP)) { + float convertedCh2Temp = 0.0f; + bool result = false; - } else { - Log.swarningln(FPSTR(L_OT_HEATING), F("Failed receive temp")); - } + if (vars.master.ch2.enabled && !settings.opentherm.nativeHeatingControl) { + result = this->updateCh2Temp(); - // Get heating return temp - if (this->updateHeatingReturnTemp()) { - if (settings.sensors.indoor.type == SensorType::BOILER_RETURN) { - vars.temperatures.indoor = settings.sensors.outdoor.offset + vars.temperatures.heatingReturn; + if (result) { + convertedCh2Temp = convertTemp( + vars.slave.ch2.currentTemp, + settings.opentherm.unitSystem, + settings.system.unitSystem + ); - if (!vars.sensors.outdoor.connected) { - vars.sensors.indoor.connected = true; + Log.snoticeln( + FPSTR(L_OT_CH2), F("Received temp: %.2f (converted: %.2f)"), + vars.slave.ch2.currentTemp, convertedCh2Temp + ); + + } else { + Log.swarningln(FPSTR(L_OT_CH2), F("Failed receive temp")); } } - Log.snoticeln(FPSTR(L_OT_HEATING), F("Received return temp: %.2f"), vars.temperatures.heatingReturn); + Sensors::setValueByType( + Sensors::Type::OT_CH2_TEMP, convertedCh2Temp, + Sensors::ValueType::PRIMARY, true, true + ); + } - } else { - if (settings.sensors.indoor.type == SensorType::BOILER_RETURN && vars.sensors.outdoor.connected) { - vars.sensors.indoor.connected = false; + // Update exhaust temp + if (Sensors::getAmountByType(Sensors::Type::OT_EXHAUST_TEMP)) { + float convertedExhaustTemp = 0.0f; + bool result = this->updateExhaustTemp(); + + if (result) { + convertedExhaustTemp = convertTemp( + vars.slave.exhaustTemp, + settings.opentherm.unitSystem, + settings.system.unitSystem + ); + + Log.snoticeln( + FPSTR(L_OT), F("Received exhaust temp: %.2f (converted: %.2f)"), + vars.slave.exhaustTemp, convertedExhaustTemp + ); + + } else { + Log.swarningln(FPSTR(L_OT), F("Failed receive exhaust temp")); } - Log.swarningln(FPSTR(L_OT_HEATING), F("Failed receive return temp")); + Sensors::setValueByType( + Sensors::Type::OT_EXHAUST_TEMP, convertedExhaustTemp, + Sensors::ValueType::PRIMARY, true, true + ); } - // Get exhaust temp - if (this->updateExhaustTemp()) { - Log.snoticeln(FPSTR(L_OT), F("Received exhaust temp: %.2f"), vars.temperatures.exhaust); + // Update heat exchanger temp + if (Sensors::getAmountByType(Sensors::Type::OT_HEAT_EXCHANGER_TEMP)) { + float convertedHeatExchTemp = 0.0f; + bool result = this->updateHeatExchangerTemp(); - } else { - Log.swarningln(FPSTR(L_OT), F("Failed receive exhaust temp")); + if (result) { + convertedHeatExchTemp = convertTemp( + vars.slave.heatExchangerTemp, + settings.opentherm.unitSystem, + settings.system.unitSystem + ); + + Log.snoticeln( + FPSTR(L_OT), F("Received heat exchanger temp: %.2f (converted: %.2f)"), + vars.slave.heatExchangerTemp, convertedHeatExchTemp + ); + + } else { + Log.swarningln(FPSTR(L_OT), F("Failed receive heat exchanger temp")); + } + + Sensors::setValueByType( + Sensors::Type::OT_HEAT_EXCHANGER_TEMP, convertedHeatExchTemp, + Sensors::ValueType::PRIMARY, true, true + ); } - // If filtering is enabled, these parameters - // must be updated every time. - if (settings.opentherm.filterNumValues.enable) { - // Get outdoor temp (if necessary) - if (settings.sensors.outdoor.type == SensorType::BOILER_OUTDOOR) { - if (this->updateOutdoorTemp()) { - if (!vars.sensors.outdoor.connected) { - vars.sensors.outdoor.connected = true; - } + // Update outdoor temp + if (Sensors::getAmountByType(Sensors::Type::OT_OUTDOOR_TEMP)) { + bool result = this->updateOutdoorTemp(); + float convertedOutdoorTemp = 0.0f; - Log.snoticeln(FPSTR(L_OT), F("Received outdoor temp: %.2f"), vars.temperatures.outdoor); + if (result) { + convertedOutdoorTemp = convertTemp( + vars.slave.heating.outdoorTemp, + settings.opentherm.unitSystem, + settings.system.unitSystem + ); - } else { - if (vars.sensors.outdoor.connected) { - vars.sensors.outdoor.connected = false; - } + Log.snoticeln( + FPSTR(L_OT), F("Received outdoor temp: %.2f (converted: %.2f)"), + vars.slave.heating.outdoorTemp, convertedOutdoorTemp + ); - Log.swarningln(FPSTR(L_OT), F("Failed receive outdoor temp")); - } + } else { + Log.swarningln(FPSTR(L_OT), F("Failed receive outdoor temp")); } - - // Get pressure - if (this->updatePressure()) { - Log.snoticeln(FPSTR(L_OT), F("Received pressure: %.2f"), vars.sensors.pressure); + + Sensors::setValueByType( + Sensors::Type::OT_OUTDOOR_TEMP, convertedOutdoorTemp, + Sensors::ValueType::PRIMARY, true, true + ); + } + + // Update pressure + if (Sensors::getAmountByType(Sensors::Type::OT_PRESSURE)) { + float convertedPressure = 0.0f; + bool result = this->updatePressure(); + + if (result) { + convertedPressure = convertPressure( + vars.slave.pressure, + settings.opentherm.unitSystem, + settings.system.unitSystem + ); + + Log.snoticeln( + FPSTR(L_OT), F("Received pressure: %.2f (converted: %.2f)"), + vars.slave.pressure, convertedPressure + ); } else { Log.swarningln(FPSTR(L_OT), F("Failed receive pressure")); } + + Sensors::setValueByType( + Sensors::Type::OT_PRESSURE, convertedPressure, + Sensors::ValueType::PRIMARY, true, true + ); } // Fault reset action if (vars.actions.resetFault) { - if (vars.states.fault) { + if (vars.slave.fault.active) { if (this->instance->sendBoilerReset()) { Log.sinfoln(FPSTR(L_OT), F("Boiler fault reset successfully")); @@ -563,7 +792,7 @@ class OpenThermTask : public Task { // Diag reset action if (vars.actions.resetDiagnostic) { - if (vars.states.diagnostic) { + if (vars.slave.diag.active) { if (this->instance->sendServiceReset()) { Log.sinfoln(FPSTR(L_OT), F("Boiler diagnostic reset successfully")); @@ -577,158 +806,202 @@ class OpenThermTask : public Task { // Update DHW temp - if (settings.opentherm.dhwPresent && settings.dhw.enable && (this->needSetDhwTemp() || fabs(settings.dhw.target - currentDhwTemp) > 0.0001f)) { - float convertedTemp = convertTemp(settings.dhw.target, settings.system.unitSystem, settings.opentherm.unitSystem); - Log.sinfoln(FPSTR(L_OT_DHW), F("Set temp: %.2f (converted: %.2f)"), settings.dhw.target, convertedTemp); + if (vars.master.dhw.enabled) { + // Converted target dhw temp + float convertedTemp = convertTemp( + vars.master.dhw.targetTemp, + settings.system.unitSystem, + settings.opentherm.unitSystem + ); // Set DHW temp - if (this->instance->setDhwTemp(convertedTemp)) { - currentDhwTemp = settings.dhw.target; - this->dhwSetTempTime = millis(); + if (this->needSetDhwTemp(convertedTemp)) { + if (this->setDhwTemp(convertedTemp)) { + this->dhwSetTempTime = millis(); - } else { - Log.swarningln(FPSTR(L_OT_DHW), F("Failed set temp")); - } + Log.sinfoln( + FPSTR(L_OT_DHW), F("Set temp: %.2f (converted: %.2f, response: %.2f)"), + vars.master.dhw.targetTemp, convertedTemp, vars.slave.dhw.targetTemp + ); - // Set DHW temp to CH2 - if (settings.opentherm.dhwToCh2) { - if (!this->instance->setHeatingCh2Temp(convertedTemp)) { - Log.swarningln(FPSTR(L_OT_DHW), F("Failed set CH2 temp")); + } else { + Log.swarningln(FPSTR(L_OT_DHW), F("Failed set temp")); } } } - // Native heating control if (settings.opentherm.nativeHeatingControl) { - // Set current indoor temp - float indoorTemp = 0.0f; - float convertedTemp = 0.0f; + // Converted current indoor temp + float convertedTemp = convertTemp(vars.master.heating.indoorTemp, settings.system.unitSystem, settings.opentherm.unitSystem); - if (vars.sensors.indoor.connected) { - indoorTemp = vars.temperatures.indoor; - convertedTemp = convertTemp(indoorTemp, settings.system.unitSystem, settings.opentherm.unitSystem); + // Set current indoor temp + if (this->setRoomTemp(convertedTemp)) { + Log.sinfoln( + FPSTR(L_OT_HEATING), F("Set current indoor temp: %.2f (converted: %.2f, response: %.2f)"), + vars.master.heating.indoorTemp, convertedTemp, vars.slave.heating.indoorTemp + ); + + } else { + Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set current indoor temp")); } - Log.sinfoln(FPSTR(L_OT_HEATING), F("Set current indoor temp: %.2f (converted: %.2f)"), indoorTemp, convertedTemp); - if (!this->instance->setRoomTemp(convertedTemp)) { - Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set current indoor temp")); + // Set current CH2 indoor temp + if (settings.opentherm.heatingCh1ToCh2) { + if (this->setRoomTempCh2(convertedTemp)) { + Log.sinfoln( + FPSTR(L_OT_HEATING), F("Set current CH2 indoor temp: %.2f (converted: %.2f, response: %.2f)"), + vars.master.heating.indoorTemp, convertedTemp, vars.slave.ch2.indoorTemp + ); + + } else { + Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set current CH2 indoor temp")); + } } - // Set target indoor temp - if (this->needSetHeatingTemp() || fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001f) { - convertedTemp = convertTemp(vars.parameters.heatingSetpoint, settings.system.unitSystem, settings.opentherm.unitSystem); - Log.sinfoln(FPSTR(L_OT_HEATING), F("Set target indoor temp: %.2f (converted: %.2f)"), vars.parameters.heatingSetpoint, convertedTemp); - if (this->instance->setRoomSetpoint(convertedTemp)) { - currentHeatingTemp = vars.parameters.heatingSetpoint; + // Converted target indoor temp + convertedTemp = convertTemp(vars.master.heating.targetTemp, settings.system.unitSystem, settings.opentherm.unitSystem); + + // Set target indoor temp + if (this->needSetHeatingTemp(convertedTemp)) { + if (this->setRoomSetpoint(convertedTemp)) { this->heatingSetTempTime = millis(); + Log.sinfoln( + FPSTR(L_OT_HEATING), F("Set target indoor temp: %.2f (converted: %.2f, response: %.2f)"), + vars.master.heating.targetTemp, convertedTemp, vars.slave.heating.targetTemp + ); + } else { Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set target indoor temp")); } + } - // Set target temp to CH2 - if (settings.opentherm.heatingCh1ToCh2) { - if (!this->instance->setRoomSetpointCh2(convertedTemp)) { - Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set target indoor temp to CH2")); - } + // Set target CH2 temp + if (settings.opentherm.heatingCh1ToCh2 && this->needSetCh2Temp(convertedTemp)) { + if (this->setRoomSetpointCh2(convertedTemp)) { + this->ch2SetTempTime = millis(); + + Log.sinfoln( + FPSTR(L_OT_HEATING), F("Set target CH2 indoor temp: %.2f (converted: %.2f, response: %.2f)"), + vars.master.heating.targetTemp, convertedTemp, vars.slave.ch2.targetTemp + ); + + } else { + Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set target CH2 indoor temp")); } } + } - } else { - // Update heating temp - if (heatingEnabled && (this->needSetHeatingTemp() || fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001f)) { - float convertedTemp = convertTemp(vars.parameters.heatingSetpoint, settings.system.unitSystem, settings.opentherm.unitSystem); - Log.sinfoln(FPSTR(L_OT_HEATING), F("Set temp: %.2f (converted: %.2f)"), vars.parameters.heatingSetpoint, convertedTemp); + // Normal heating control + if (!settings.opentherm.nativeHeatingControl && vars.master.heating.enabled) { + // Converted target heating temp + float convertedTemp = convertTemp(vars.master.heating.targetTemp, settings.system.unitSystem, settings.opentherm.unitSystem); + if (this->needSetHeatingTemp(convertedTemp)) { // Set max heating temp if (this->setMaxHeatingTemp(convertedTemp)) { - currentHeatingTemp = vars.parameters.heatingSetpoint; - this->heatingSetTempTime = millis(); + Log.sinfoln( + FPSTR(L_OT_HEATING), F("Set max heating temp: %.2f (converted: %.2f)"), + vars.master.heating.targetTemp, convertedTemp + ); } else { Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set max heating temp")); } - // Set heating temp - if (this->instance->setHeatingCh1Temp(convertedTemp)) { - currentHeatingTemp = vars.parameters.heatingSetpoint; + // Set target heating temp + if (this->setHeatingTemp(convertedTemp)) { this->heatingSetTempTime = millis(); - } else { - Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set CH1 temp")); - } + Log.sinfoln( + FPSTR(L_OT_HEATING), F("Set target temp: %.2f (converted: %.2f, response: %.2f)"), + vars.master.heating.targetTemp, convertedTemp, vars.slave.heating.targetTemp + ); - // Set heating temp to CH2 - if (settings.opentherm.heatingCh1ToCh2) { - if (!this->instance->setHeatingCh2Temp(convertedTemp)) { - Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set CH2 temp")); - } + } else { + Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set target temp")); } } } - // Hysteresis - // Only if enabled PID or/and Equitherm or Native heating control via OT - bool useHyst = false; - if (settings.heating.hysteresis > 0.01f && vars.sensors.indoor.connected) { - useHyst = settings.equitherm.enable || settings.pid.enable || settings.opentherm.nativeHeatingControl; - } + // Set CH2 temp + if (vars.master.ch2.enabled && !settings.opentherm.nativeHeatingControl) { + // Converted target CH2 temp + float convertedTemp = convertTemp( + vars.master.ch2.targetTemp, + settings.system.unitSystem, + settings.opentherm.unitSystem + ); - if (useHyst) { - if (!this->heatingBlocking && vars.temperatures.indoor - settings.heating.target + 0.0001f >= settings.heating.hysteresis) { - this->heatingBlocking = true; + if (this->needSetCh2Temp(convertedTemp)) { + if (this->setCh2Temp(convertedTemp)) { + this->ch2SetTempTime = millis(); - } else if (this->heatingBlocking && vars.temperatures.indoor - settings.heating.target - 0.0001f <= -(settings.heating.hysteresis)) { - this->heatingBlocking = false; - } + Log.sinfoln( + FPSTR(L_OT_CH2), F("Set temp: %.2f (converted: %.2f, response: %.2f)"), + vars.master.ch2.targetTemp, convertedTemp, vars.slave.ch2.targetTemp + ); - } else if (this->heatingBlocking) { - this->heatingBlocking = false; + } else { + Log.swarningln(FPSTR(L_OT_CH2), F("Failed set temp")); + } + } } } void initialize() { // Not all boilers support these, only try once when the boiler becomes connected if (this->updateSlaveVersion()) { - Log.snoticeln(FPSTR(L_OT), F("Received slave version: %u, type: %u"), vars.parameters.slaveVersion, vars.parameters.slaveType); + Log.snoticeln( + FPSTR(L_OT), F("Received slave app version: %u, type: %u"), + vars.slave.appVersion, vars.slave.type + ); } else { Log.swarningln(FPSTR(L_OT), F("Failed receive slave version")); } - // 0x013F - if (this->setMasterVersion(0x3F, 0x01)) { - Log.snoticeln(FPSTR(L_OT), F("Set master version: %u, type: %u"), vars.parameters.masterVersion, vars.parameters.masterType); + if (this->setMasterVersion(vars.master.appVersion, vars.master.type)) { + Log.snoticeln( + FPSTR(L_OT), F("Set master version: %u, type: %u"), + vars.master.appVersion, vars.master.type + ); } else { Log.swarningln(FPSTR(L_OT), F("Failed set master version")); } if (this->updateSlaveOtVersion()) { - Log.snoticeln(FPSTR(L_OT), F("Received slave OT version: %f"), vars.parameters.slaveOtVersion); + Log.snoticeln(FPSTR(L_OT), F("Received slave OT version: %f"), vars.slave.protocolVersion); } else { Log.swarningln(FPSTR(L_OT), F("Failed receive slave OT version")); } - if (this->setMasterOtVersion(2.2f)) { - Log.snoticeln(FPSTR(L_OT), F("Set master OT version: %f"), vars.parameters.masterOtVersion); + if (this->setMasterOtVersion(vars.master.protocolVersion)) { + Log.snoticeln(FPSTR(L_OT), F("Set master OT version: %f"), vars.master.protocolVersion); } else { Log.swarningln(FPSTR(L_OT), F("Failed set master OT version")); } if (this->updateSlaveConfig()) { - Log.snoticeln(FPSTR(L_OT), F("Received slave member id: %u, flags: %u"), vars.parameters.slaveMemberId, vars.parameters.slaveFlags); + Log.snoticeln( + FPSTR(L_OT), F("Received slave member id: %u, flags: %u"), + vars.slave.memberId, vars.slave.flags + ); } else { Log.swarningln(FPSTR(L_OT), F("Failed receive slave config")); } - if (this->setMasterConfig(settings.opentherm.memberIdCode & 0xFF, (settings.opentherm.memberIdCode & 0xFFFF) >> 8)) { - Log.snoticeln(FPSTR(L_OT), F("Set master member id: %u, flags: %u"), vars.parameters.masterMemberId, vars.parameters.masterFlags); + if (this->setMasterConfig(vars.master.memberId, vars.master.flags)) { + Log.snoticeln( + FPSTR(L_OT), F("Set master member id: %u, flags: %u"), + vars.master.memberId, vars.master.flags + ); } else { Log.swarningln(FPSTR(L_OT), F("Failed set master config")); @@ -739,12 +1012,178 @@ class OpenThermTask : public Task { return millis() - this->instanceCreatedTime > this->readyTime; } - bool needSetDhwTemp() { - return millis() - this->dhwSetTempTime > this->dhwSetTempInterval; + bool needSetDhwTemp(const float target) { + return millis() - this->dhwSetTempTime > this->dhwSetTempInterval + || fabsf(target - vars.slave.dhw.targetTemp) > 0.001f; + } + + bool needSetHeatingTemp(const float target) { + return millis() - this->heatingSetTempTime > this->heatingSetTempInterval + || fabsf(target - vars.slave.heating.targetTemp) > 0.001f; } - bool needSetHeatingTemp() { - return millis() - this->heatingSetTempTime > this->heatingSetTempInterval; + bool needSetCh2Temp(const float target) { + return millis() - this->ch2SetTempTime > this->ch2SetTempInterval + || fabsf(target - vars.slave.ch2.targetTemp) > 0.001f; + } + + bool setHeatingTemp(const float temperature) { + const unsigned int request = CustomOpenTherm::temperatureToData(temperature); + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + OpenThermMessageType::WRITE_DATA, + OpenThermMessageID::TSet, + request + )); + + if (!CustomOpenTherm::isValidResponse(response)) { + return false; + + } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::TSet)) { + return false; + } + + vars.slave.heating.targetTemp = CustomOpenTherm::getFloat(response); + + return CustomOpenTherm::getUInt(response) == request; + } + + bool setCh2Temp(const float temperature) { + const unsigned int request = CustomOpenTherm::temperatureToData(temperature); + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + OpenThermMessageType::WRITE_DATA, + OpenThermMessageID::TsetCH2, + request + )); + + if (!CustomOpenTherm::isValidResponse(response)) { + return false; + + } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::TsetCH2)) { + return false; + } + + vars.slave.ch2.targetTemp = CustomOpenTherm::getFloat(response); + + return CustomOpenTherm::getUInt(response) == request; + } + + bool setDhwTemp(const float temperature) { + const unsigned int request = CustomOpenTherm::temperatureToData(temperature); + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + OpenThermMessageType::WRITE_DATA, + OpenThermMessageID::TdhwSet, + request + )); + + if (!CustomOpenTherm::isValidResponse(response)) { + return false; + + } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::TdhwSet)) { + return false; + } + + vars.slave.dhw.targetTemp = CustomOpenTherm::getFloat(response); + + return CustomOpenTherm::getUInt(response) == request; + } + + bool setRoomSetpoint(const float temperature) { + const unsigned int request = CustomOpenTherm::temperatureToData(temperature); + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + OpenThermMessageType::WRITE_DATA, + OpenThermMessageID::TrSet, + request + )); + + if (!CustomOpenTherm::isValidResponse(response)) { + return false; + + } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::TrSet)) { + return false; + } + + vars.slave.heating.targetTemp = CustomOpenTherm::getFloat(response); + + return CustomOpenTherm::getUInt(response) == request; + } + + bool setRoomSetpointCh2(const float temperature) { + const unsigned int request = CustomOpenTherm::temperatureToData(temperature); + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + OpenThermMessageType::WRITE_DATA, + OpenThermMessageID::TrSetCH2, + request + )); + + if (!CustomOpenTherm::isValidResponse(response)) { + return false; + + } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::TrSetCH2)) { + return false; + } + + vars.slave.ch2.targetTemp = CustomOpenTherm::getFloat(response); + + return CustomOpenTherm::getUInt(response) == request; + } + + bool setRoomTemp(float temperature) { + const unsigned int request = CustomOpenTherm::temperatureToData(temperature); + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + OpenThermMessageType::WRITE_DATA, + OpenThermMessageID::Tr, + request + )); + + if (!CustomOpenTherm::isValidResponse(response)) { + return false; + + } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::Tr)) { + return false; + } + + vars.slave.heating.indoorTemp = CustomOpenTherm::getFloat(response); + + return CustomOpenTherm::getUInt(response) == request; + } + + bool setRoomTempCh2(float temperature) { + const unsigned int request = CustomOpenTherm::temperatureToData(temperature); + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + OpenThermMessageType::WRITE_DATA, + OpenThermMessageID::TrCH2, + request + )); + + if (!CustomOpenTherm::isValidResponse(response)) { + return false; + + } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::TrCH2)) { + return false; + } + + vars.slave.ch2.indoorTemp = CustomOpenTherm::getFloat(response); + + return CustomOpenTherm::getUInt(response) == request; + } + + bool updateCh2Temp() { + unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + OpenThermRequestType::READ_DATA, + OpenThermMessageID::TflowCH2, + 0 + )); + + if (!CustomOpenTherm::isValidResponse(response)) { + return false; + + } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::TflowCH2)) { + return false; + } + + vars.slave.ch2.currentTemp = CustomOpenTherm::getFloat(response); + + return true; } bool updateSlaveConfig() { @@ -761,8 +1200,8 @@ class OpenThermTask : public Task { return false; } - vars.parameters.slaveMemberId = response & 0xFF; - vars.parameters.slaveFlags = (response & 0xFFFF) >> 8; + vars.slave.memberId = response & 0xFF; + vars.slave.flags = (response & 0xFFFF) >> 8; /*uint8_t flags = (response & 0xFFFF) >> 8; Log.straceln( @@ -794,38 +1233,41 @@ class OpenThermTask : public Task { * @return true * @return false */ - bool setMasterConfig(uint8_t id, uint8_t flags, bool force = false) { - //uint8_t configId = settings.opentherm.memberIdCode & 0xFF; - //uint8_t configFlags = (settings.opentherm.memberIdCode & 0xFFFF) >> 8; - - vars.parameters.masterMemberId = (force || id || settings.opentherm.memberIdCode > 65535) - ? id - : vars.parameters.slaveMemberId; - - vars.parameters.masterFlags = (force || flags || settings.opentherm.memberIdCode > 65535) - ? flags - : vars.parameters.slaveFlags; + bool setMasterConfig(const uint8_t id, const uint8_t flags, const bool force = false) { + const uint8_t rMemberId = (force || id > 0) ? id : vars.slave.memberId; + const uint8_t rFlags = (force || flags > 0) ? flags : vars.slave.flags; + const unsigned int request = (unsigned int) rMemberId | (unsigned int) rFlags << 8; - unsigned int request = (unsigned int) vars.parameters.masterMemberId | (unsigned int) vars.parameters.masterFlags << 8; // if empty request if (!request) { return true; } - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermRequestType::WRITE_DATA, OpenThermMessageID::MConfigMMemberIDcode, request )); - return CustomOpenTherm::isValidResponse(response) && CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::MConfigMMemberIDcode); + if (!CustomOpenTherm::isValidResponse(response)) { + return false; + + } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::MConfigMMemberIDcode)) { + return false; + } + + //uint8_t rMemberId = response & 0xFF; + //uint8_t rFlags = (response & 0xFFFF) >> 8; + + return CustomOpenTherm::getUInt(response) == request; } - bool setMaxModulationLevel(byte value) { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + bool setMaxModulationLevel(const uint8_t value) { + const unsigned int request = CustomOpenTherm::toFloat(value); + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermRequestType::WRITE_DATA, OpenThermMessageID::MaxRelModLevelSetting, - CustomOpenTherm::toFloat(value) + request )); if (!CustomOpenTherm::isValidResponse(response)) { @@ -835,12 +1277,13 @@ class OpenThermTask : public Task { return false; } - vars.parameters.maxModulation = CustomOpenTherm::getFloat(response); - return true; + vars.slave.modulation.max = CustomOpenTherm::getFloat(response); + + return CustomOpenTherm::getUInt(response) == request; } bool updateSlaveOtVersion() { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermRequestType::READ_DATA, OpenThermMessageID::OpenThermVersionSlave, 0 @@ -853,15 +1296,17 @@ class OpenThermTask : public Task { return false; } - vars.parameters.slaveOtVersion = CustomOpenTherm::getFloat(response); + vars.slave.protocolVersion = CustomOpenTherm::getFloat(response); + return true; } - bool setMasterOtVersion(float version) { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + bool setMasterOtVersion(const float version) { + const unsigned int request = CustomOpenTherm::toFloat(version); + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermRequestType::WRITE_DATA, OpenThermMessageID::OpenThermVersionMaster, - CustomOpenTherm::toFloat(version) + request )); if (!CustomOpenTherm::isValidResponse(response)) { @@ -871,13 +1316,11 @@ class OpenThermTask : public Task { return false; } - vars.parameters.masterOtVersion = CustomOpenTherm::getFloat(response); - - return true; + return CustomOpenTherm::getUInt(response) == request; } bool updateSlaveVersion() { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermRequestType::READ_DATA, OpenThermMessageID::SlaveVersion, 0 @@ -890,14 +1333,14 @@ class OpenThermTask : public Task { return false; } - vars.parameters.slaveVersion = response & 0xFF; - vars.parameters.slaveType = (response & 0xFFFF) >> 8; + vars.slave.appVersion = response & 0xFF; + vars.slave.type = (response & 0xFFFF) >> 8; return true; } - bool setMasterVersion(uint8_t version, uint8_t type) { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + bool setMasterVersion(const uint8_t version, const uint8_t type) { + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermRequestType::WRITE_DATA, OpenThermMessageID::MasterVersion, (unsigned int) version | (unsigned int) type << 8 @@ -910,14 +1353,14 @@ class OpenThermTask : public Task { return false; } - vars.parameters.masterVersion = response & 0xFF; - vars.parameters.masterType = (response & 0xFFFF) >> 8; + uint8_t rVersion = response & 0xFF; + uint8_t rType = (response & 0xFFFF) >> 8; - return true; + return rVersion == version && rType == type; } bool updateMinMaxDhwTemp() { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermRequestType::READ_DATA, OpenThermMessageID::TdhwSetUBTdhwSetLB, 0 @@ -930,12 +1373,12 @@ class OpenThermTask : public Task { return false; } - byte minTemp = response & 0xFF; - byte maxTemp = (response & 0xFFFF) >> 8; + uint8_t minTemp = response & 0xFF; + uint8_t maxTemp = (response & 0xFFFF) >> 8; if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) { - vars.parameters.dhwMinTemp = convertTemp(minTemp, settings.opentherm.unitSystem, settings.system.unitSystem); - vars.parameters.dhwMaxTemp = convertTemp(maxTemp, settings.opentherm.unitSystem, settings.system.unitSystem); + vars.slave.dhw.minTemp = minTemp; + vars.slave.dhw.maxTemp = maxTemp; return true; } @@ -944,7 +1387,7 @@ class OpenThermTask : public Task { } bool updateMinMaxHeatingTemp() { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermRequestType::READ_DATA, OpenThermMessageID::MaxTSetUBMaxTSetLB, 0 @@ -957,30 +1400,39 @@ class OpenThermTask : public Task { return false; } - byte minTemp = response & 0xFF; - byte maxTemp = (response & 0xFFFF) >> 8; + uint8_t minTemp = response & 0xFF; + uint8_t maxTemp = (response & 0xFFFF) >> 8; if (minTemp >= 0 && maxTemp > 0 && maxTemp > minTemp) { - vars.parameters.heatingMinTemp = convertTemp(minTemp, settings.opentherm.unitSystem, settings.system.unitSystem); - vars.parameters.heatingMaxTemp = convertTemp(maxTemp, settings.opentherm.unitSystem, settings.system.unitSystem); + vars.slave.heating.minTemp = minTemp; + vars.slave.heating.maxTemp = maxTemp; + return true; } return false; } - bool setMaxHeatingTemp(byte value) { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + bool setMaxHeatingTemp(const uint8_t temperature) { + const unsigned int request = CustomOpenTherm::temperatureToData(temperature); + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermMessageType::WRITE_DATA, OpenThermMessageID::MaxTSet, - CustomOpenTherm::temperatureToData(value) + request )); - return CustomOpenTherm::isValidResponse(response) && CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::MaxTSet); + if (!CustomOpenTherm::isValidResponse(response)) { + return false; + + } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::MaxTSet)) { + return false; + } + + return CustomOpenTherm::getUInt(response) == request; } bool updateOutdoorTemp() { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermRequestType::READ_DATA, OpenThermMessageID::Toutside, 0 @@ -992,25 +1444,14 @@ class OpenThermTask : public Task { } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::Toutside)) { return false; } - - float value = settings.sensors.outdoor.offset + convertTemp( - CustomOpenTherm::getFloat(response), - settings.opentherm.unitSystem, - settings.system.unitSystem - ); - if (settings.opentherm.filterNumValues.enable && fabs(vars.temperatures.outdoor) >= 0.1f) { - vars.temperatures.outdoor += (value - vars.temperatures.outdoor) * settings.opentherm.filterNumValues.factor; - - } else { - vars.temperatures.outdoor = value; - } + vars.slave.heating.outdoorTemp = CustomOpenTherm::getFloat(response); return true; } bool updateExhaustTemp() { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermRequestType::READ_DATA, OpenThermMessageID::Texhaust, 0 @@ -1028,24 +1469,37 @@ class OpenThermTask : public Task { return false; } - value = convertTemp( - value, - settings.opentherm.unitSystem, - settings.system.unitSystem - ); + vars.slave.exhaustTemp = value; - if (settings.opentherm.filterNumValues.enable && fabs(vars.temperatures.exhaust) >= 0.1f) { - vars.temperatures.exhaust += (value - vars.temperatures.exhaust) * settings.opentherm.filterNumValues.factor; - - } else { - vars.temperatures.exhaust = value; + return true; + } + + bool updateHeatExchangerTemp() { + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + OpenThermRequestType::READ_DATA, + OpenThermMessageID::TboilerHeatExchanger, + 0 + )); + + if (!CustomOpenTherm::isValidResponse(response)) { + return false; + + } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::TboilerHeatExchanger)) { + return false; } + float value = (float) CustomOpenTherm::getInt(response); + if (value <= 0) { + return false; + } + + vars.slave.heatExchangerTemp = value; + return true; } bool updateHeatingTemp() { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermMessageType::READ_DATA, OpenThermMessageID::Tboiler, 0 @@ -1063,24 +1517,13 @@ class OpenThermTask : public Task { return false; } - value = convertTemp( - value, - settings.opentherm.unitSystem, - settings.system.unitSystem - ); - - if (settings.opentherm.filterNumValues.enable && fabs(vars.temperatures.heating) >= 0.1f) { - vars.temperatures.heating += (value - vars.temperatures.heating) * settings.opentherm.filterNumValues.factor; - - } else { - vars.temperatures.heating = value; - } + vars.slave.heating.currentTemp = value; return true; } bool updateHeatingReturnTemp() { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermMessageType::READ_DATA, OpenThermMessageID::Tret, 0 @@ -1093,26 +1536,14 @@ class OpenThermTask : public Task { return false; } - float value = convertTemp( - CustomOpenTherm::getFloat(response), - settings.opentherm.unitSystem, - settings.system.unitSystem - ); - - if (settings.opentherm.filterNumValues.enable && fabs(vars.temperatures.heatingReturn) >= 0.1f) { - vars.temperatures.heatingReturn += (value - vars.temperatures.heatingReturn) * settings.opentherm.filterNumValues.factor; - - } else { - vars.temperatures.heatingReturn = value; - } + vars.slave.heating.returnTemp = CustomOpenTherm::getFloat(response); - return true; } bool updateDhwTemp() { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermMessageType::READ_DATA, OpenThermMessageID::Tdhw, 0 @@ -1130,24 +1561,37 @@ class OpenThermTask : public Task { return false; } - value = convertTemp( - value, - settings.opentherm.unitSystem, - settings.system.unitSystem - ); + vars.slave.dhw.currentTemp = value; - if (settings.opentherm.filterNumValues.enable && fabs(vars.temperatures.dhw) >= 0.1f) { - vars.temperatures.dhw += (value - vars.temperatures.dhw) * settings.opentherm.filterNumValues.factor; - - } else { - vars.temperatures.dhw = value; + return true; + } + + bool updateDhwTemp2() { + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + OpenThermMessageType::READ_DATA, + OpenThermMessageID::Tdhw2, + 0 + )); + + if (!CustomOpenTherm::isValidResponse(response)) { + return false; + + } else if (!CustomOpenTherm::isValidResponseId(response, OpenThermMessageID::Tdhw2)) { + return false; + } + + float value = CustomOpenTherm::getFloat(response); + if (value <= 0) { + return false; } + vars.slave.dhw.currentTemp2 = value; + return true; } bool updateDhwFlowRate() { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermMessageType::READ_DATA, OpenThermMessageID::DHWFlowRate, 0 @@ -1165,31 +1609,19 @@ class OpenThermTask : public Task { return false; } - // correction - value = value * settings.opentherm.dhwFlowRateFactor; - // no minuscule values // some boilers send a response of 0.06 when there is no flow if (value < 0.1f) { value = 0.0f; } - // protocol declares a maximum of 16 l/m - //if (value > convertVolume(16.0f, UnitSystem::METRIC, settings.opentherm.unitSystem)) { - // value = 0.0f; - //} - - vars.sensors.dhwFlowRate = convertVolume( - value, - settings.opentherm.unitSystem, - settings.system.unitSystem - ); + vars.slave.dhw.flowRate = value; return true; } bool updateFaultCode() { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermRequestType::READ_DATA, OpenThermMessageID::ASFflags, 0 @@ -1202,12 +1634,13 @@ class OpenThermTask : public Task { return false; } - vars.sensors.faultCode = response & 0xFF; + vars.slave.fault.code = response & 0xFF; + return true; } bool updateDiagCode() { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermRequestType::READ_DATA, OpenThermMessageID::OEMDiagnosticCode, 0 @@ -1220,12 +1653,13 @@ class OpenThermTask : public Task { return false; } - vars.sensors.diagnosticCode = CustomOpenTherm::getUInt(response); + vars.slave.diag.code = CustomOpenTherm::getUInt(response); + return true; } bool updateModulationLevel() { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermRequestType::READ_DATA, OpenThermMessageID::RelModLevel, 0 @@ -1243,18 +1677,13 @@ class OpenThermTask : public Task { return false; } - if (settings.opentherm.filterNumValues.enable && fabs(vars.sensors.modulation) >= 0.1f) { - vars.sensors.modulation += (value - vars.sensors.modulation) * settings.opentherm.filterNumValues.factor; - - } else { - vars.sensors.modulation = value; - } + vars.slave.modulation.current = value; return true; } bool updateMinModulationLevel() { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermRequestType::READ_DATA, OpenThermMessageID::MaxCapacityMinModLevel, 0 @@ -1267,14 +1696,17 @@ class OpenThermTask : public Task { return false; } - vars.parameters.minModulation = response & 0xFF; - vars.parameters.maxPower = (response & 0xFFFF) >> 8; + vars.slave.modulation.min = response & 0xFF; + vars.slave.power.max = (response & 0xFFFF) >> 8; + vars.slave.power.min = vars.slave.modulation.min > 0 && vars.slave.power.max > 0.1f + ? (vars.slave.modulation.min * 0.01f) * vars.slave.power.max + : 0.0f; return true; } bool updatePressure() { - unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + const unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermRequestType::READ_DATA, OpenThermMessageID::CHPressure, 0 @@ -1292,26 +1724,7 @@ class OpenThermTask : public Task { return false; } - // correction - value = value * settings.opentherm.pressureFactor; - - // protocol declares a maximum of 5 bar - //if (value > convertPressure(5.0f, UnitSystem::METRIC, settings.opentherm.unitSystem)) { - // value = 0.0f; - //} - - value = convertPressure( - value, - settings.opentherm.unitSystem, - settings.system.unitSystem - ); - - if (settings.opentherm.filterNumValues.enable && fabs(vars.sensors.pressure) >= 0.1f) { - vars.sensors.pressure += (value - vars.sensors.pressure) * settings.opentherm.filterNumValues.factor; - - } else { - vars.sensors.pressure = value; - } + vars.slave.pressure = value; return true; } diff --git a/src/PortalTask.h b/src/PortalTask.h index 6309760..323885e 100644 --- a/src/PortalTask.h +++ b/src/PortalTask.h @@ -17,7 +17,7 @@ using WebServer = ESP8266WebServer; using namespace NetworkUtils; extern NetworkMgr* network; -extern FileData fsSettings, fsNetworkSettings; +extern FileData fsNetworkSettings, fsSettings, fsSensorsSettings; extern MqttTask* tMqtt; @@ -140,6 +140,18 @@ class PortalTask : public LeanTask { }); this->webServer->addHandler(settingsPage); + // sensors page + auto sensorsPage = (new StaticPage("/sensors.html", &LittleFS, "/pages/sensors.html", PORTAL_CACHE)) + ->setBeforeSendCallback([this]() { + if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) { + this->webServer->requestAuthentication(DIGEST_AUTH); + return false; + } + + return true; + }); + this->webServer->addHandler(sensorsPage); + // upgrade page auto upgradePage = (new StaticPage("/upgrade.html", &LittleFS, "/pages/upgrade.html", PORTAL_CACHE)) ->setBeforeSendCallback([this]() { @@ -194,17 +206,19 @@ class PortalTask : public LeanTask { } } - JsonDocument networkSettingsDoc; - networkSettingsToJson(networkSettings, networkSettingsDoc); - networkSettingsDoc.shrinkToFit(); + JsonDocument doc; - JsonDocument settingsDoc; - settingsToJson(settings, settingsDoc); - settingsDoc.shrinkToFit(); + auto networkDoc = doc["network"].to(); + networkSettingsToJson(networkSettings, networkDoc); + + auto settingskDoc = doc["settings"].to(); + settingsToJson(settings, settingskDoc); + + for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { + auto sensorSettingskDoc = doc["sensors"][sensorId].to(); + sensorSettingsToJson(sensorId, Sensors::settings[sensorId], sensorSettingskDoc); + } - JsonDocument doc; - doc["network"] = networkSettingsDoc; - doc["settings"] = settingsDoc; doc.shrinkToFit(); this->webServer->sendHeader(F("Content-Disposition"), F("attachment; filename=\"backup.json\"")); @@ -240,13 +254,13 @@ class PortalTask : public LeanTask { } bool changed = false; - if (doc["settings"] && jsonToSettings(doc["settings"], settings)) { + if (!doc["settings"].isNull() && jsonToSettings(doc["settings"], settings)) { vars.actions.restart = true; fsSettings.update(); changed = true; } - if (doc["network"] && jsonToNetworkSettings(doc["network"], networkSettings)) { + if (!doc["network"].isNull() && jsonToNetworkSettings(doc["network"], networkSettings)) { fsNetworkSettings.update(); network->setHostname(networkSettings.hostname) ->setStaCredentials(networkSettings.sta.ssid, networkSettings.sta.password, networkSettings.sta.channel) @@ -262,6 +276,19 @@ class PortalTask : public LeanTask { changed = true; } + if (!doc["sensors"].isNull()) { + for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { + if (doc["sensors"][sensorId].isNull()) { + continue; + } + + auto sensorSettingsDoc = doc["sensors"][sensorId].to(); + if (jsonToSensorSettings(sensorId, sensorSettingsDoc, Sensors::settings[sensorId])){ + changed = true; + } + } + } + doc.clear(); doc.shrinkToFit(); @@ -446,6 +473,135 @@ class PortalTask : public LeanTask { }); + // sensors list + this->webServer->on("/api/sensors", HTTP_GET, [this]() { + if (this->isAuthRequired()) { + if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { + return this->webServer->send(401); + } + } + + bool detailed = false; + if (this->webServer->hasArg("detailed")) { + detailed = this->webServer->arg("detailed").toInt() > 0; + } + + JsonDocument doc; + for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { + if (detailed) { + auto& sSensor = Sensors::settings[sensorId]; + doc[sensorId]["name"] = sSensor.name; + doc[sensorId]["purpose"] = static_cast(sSensor.purpose); + sensorResultToJson(sensorId, doc[sensorId]); + + } else { + doc[sensorId] = Sensors::settings[sensorId].name; + } + } + + doc.shrinkToFit(); + this->bufferedWebServer->send(200, "application/json", doc); + }); + + // sensor settings + this->webServer->on("/api/sensor", HTTP_GET, [this]() { + if (this->isAuthRequired()) { + if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { + return this->webServer->send(401); + } + } + + if (!this->webServer->hasArg("id")) { + return this->webServer->send(400); + } + + auto id = this->webServer->arg("id"); + if (!isDigit(id.c_str())) { + return this->webServer->send(400); + } + + uint8_t sensorId = id.toInt(); + id.clear(); + if (!Sensors::isValidSensorId(sensorId)) { + return this->webServer->send(404); + } + + JsonDocument doc; + sensorSettingsToJson(sensorId, Sensors::settings[sensorId], doc); + doc.shrinkToFit(); + this->bufferedWebServer->send(200, "application/json", doc); + }); + + this->webServer->on("/api/sensor", HTTP_POST, [this]() { + if (this->isAuthRequired()) { + if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { + return this->webServer->send(401); + } + } + + #ifdef ARDUINO_ARCH_ESP8266 + if (!this->webServer->hasArg("id") || this->webServer->args() != 1) { + return this->webServer->send(400); + } + #else + if (!this->webServer->hasArg("id") || this->webServer->args() != 2) { + return this->webServer->send(400); + } + #endif + + auto id = this->webServer->arg("id"); + if (!isDigit(id.c_str())) { + return this->webServer->send(400); + } + + uint8_t sensorId = id.toInt(); + id.clear(); + if (!Sensors::isValidSensorId(sensorId)) { + return this->webServer->send(404); + } + + auto plain = this->webServer->arg(1); + Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/sensor/?id=%hhu %d bytes: %s"), sensorId, plain.length(), plain.c_str()); + + if (plain.length() < 5) { + return this->webServer->send(406); + + } else if (plain.length() > 1024) { + return this->webServer->send(413); + } + + bool changed = false; + auto prevSettings = Sensors::settings[sensorId]; + { + JsonDocument doc; + DeserializationError dErr = deserializeJson(doc, plain); + plain.clear(); + + if (dErr != DeserializationError::Ok || doc.isNull() || !doc.size()) { + return this->webServer->send(400); + } + + if (jsonToSensorSettings(sensorId, doc, Sensors::settings[sensorId])) { + changed = true; + } + } + + { + JsonDocument doc; + auto& sSettings = Sensors::settings[sensorId]; + sensorSettingsToJson(sensorId, sSettings, doc); + doc.shrinkToFit(); + + this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc); + } + + if (changed) { + tMqtt->rebuildHaEntity(sensorId, prevSettings); + fsSensorsSettings.update(); + } + }); + + // vars this->webServer->on("/api/vars", HTTP_GET, [this]() { JsonDocument doc; diff --git a/src/RegulatorTask.h b/src/RegulatorTask.h index c26a66b..3fbbb0f 100644 --- a/src/RegulatorTask.h +++ b/src/RegulatorTask.h @@ -10,9 +10,12 @@ class RegulatorTask : public LeanTask { RegulatorTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {} protected: - float prevHeatingTarget = 0; - float prevEtResult = 0; - float prevPidResult = 0; + float prevHeatingTarget = 0.0f; + float prevEtResult = 0.0f; + float prevPidResult = 0.0f; + + bool indoorSensorsConnected = false; + //bool outdoorSensorsConnected = false; #if defined(ARDUINO_ARCH_ESP32) const char* getTaskName() override { @@ -29,20 +32,50 @@ class RegulatorTask : public LeanTask { #endif void loop() { - if (!settings.pid.enable && fabs(pidRegulator.integral) > 0.01f) { + this->indoorSensorsConnected = Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP); + //this->outdoorSensorsConnected = Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::OUTDOOR_TEMP); + + if (settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.nativeHeatingControl) { + vars.master.heating.indoorTempControl = true; + vars.master.heating.minTemp = THERMOSTAT_INDOOR_MIN_TEMP; + vars.master.heating.maxTemp = THERMOSTAT_INDOOR_MAX_TEMP; + + } else { + vars.master.heating.indoorTempControl = false; + vars.master.heating.minTemp = settings.heating.minTemp; + vars.master.heating.maxTemp = settings.heating.maxTemp; + } + + if (!settings.pid.enabled && fabsf(pidRegulator.integral) > 0.01f) { pidRegulator.integral = 0.0f; Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset")); } + this->turbo(); + this->hysteresis(); + + vars.master.heating.targetTemp = constrain( + this->getHeatingSetpoint(), + vars.master.heating.minTemp, + vars.master.heating.maxTemp + ); + + Sensors::setValueByType( + Sensors::Type::HEATING_SETPOINT_TEMP, vars.master.heating.targetTemp, + Sensors::ValueType::PRIMARY, true, true + ); + } + + void turbo() { if (settings.heating.turbo) { - if (!settings.heating.enable || vars.states.emergency || !vars.sensors.indoor.connected) { + if (!settings.heating.enabled || vars.emergency.state || !this->indoorSensorsConnected) { settings.heating.turbo = false; - } else if (!settings.pid.enable && !settings.equitherm.enable) { + } else if (!settings.pid.enabled && !settings.equitherm.enabled) { settings.heating.turbo = false; - } else if (fabs(settings.heating.target - vars.temperatures.indoor) <= 1.0f) { + } else if (fabsf(settings.heating.target - vars.master.heating.indoorTemp) <= 1.0f) { settings.heating.turbo = false; } @@ -50,45 +83,58 @@ class RegulatorTask : public LeanTask { Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled")); } } + } + void hysteresis() { + bool useHyst = false; + if (settings.heating.hysteresis > 0.01f && this->indoorSensorsConnected) { + useHyst = settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.nativeHeatingControl; + } - float newTemp = vars.states.emergency - ? settings.emergency.target - : this->getNormalModeTemp(); + if (useHyst) { + if (!vars.master.heating.blocking && vars.master.heating.indoorTemp - settings.heating.target + 0.0001f >= settings.heating.hysteresis) { + vars.master.heating.blocking = true; - // Limits - newTemp = constrain( - newTemp, - !settings.opentherm.nativeHeatingControl ? settings.heating.minTemp : THERMOSTAT_INDOOR_MIN_TEMP, - !settings.opentherm.nativeHeatingControl ? settings.heating.maxTemp : THERMOSTAT_INDOOR_MAX_TEMP - ); + } else if (vars.master.heating.blocking && vars.master.heating.indoorTemp - settings.heating.target - 0.0001f <= -(settings.heating.hysteresis)) { + vars.master.heating.blocking = false; + } - if (fabs(vars.parameters.heatingSetpoint - newTemp) > 0.09f) { - vars.parameters.heatingSetpoint = newTemp; + } else if (vars.master.heating.blocking) { + vars.master.heating.blocking = false; } } - float getNormalModeTemp() { + float getHeatingSetpoint() { float newTemp = 0; - if (fabs(prevHeatingTarget - settings.heating.target) > 0.0001f) { + if (fabsf(prevHeatingTarget - settings.heating.target) > 0.0001f) { prevHeatingTarget = settings.heating.target; Log.sinfoln(FPSTR(L_REGULATOR), F("New target: %.2f"), settings.heating.target); - /*if (settings.pid.enable) { + /*if (settings.pid.enabled) { pidRegulator.integral = 0.0f; Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset")); }*/ } + if (vars.emergency.state) { + return settings.emergency.target; + + } else if (settings.opentherm.nativeHeatingControl) { + return settings.heating.target; + + } else if (!settings.equitherm.enabled && !settings.pid.enabled) { + return settings.heating.target; + } + // if use equitherm - if (settings.equitherm.enable) { + if (settings.equitherm.enabled) { unsigned short minTemp = settings.heating.minTemp; unsigned short maxTemp = settings.heating.maxTemp; float targetTemp = settings.heating.target; - float indoorTemp = vars.temperatures.indoor; - float outdoorTemp = vars.temperatures.outdoor; + float indoorTemp = vars.master.heating.indoorTemp; + float outdoorTemp = vars.master.heating.outdoorTemp; if (settings.system.unitSystem == UnitSystem::IMPERIAL) { minTemp = f2c(minTemp); @@ -98,7 +144,7 @@ class RegulatorTask : public LeanTask { outdoorTemp = f2c(outdoorTemp); } - if (!vars.sensors.indoor.connected || settings.pid.enable) { + if (!this->indoorSensorsConnected || settings.pid.enabled) { etRegulator.Kt = 0.0f; etRegulator.indoorTemp = 0.0f; @@ -118,7 +164,7 @@ class RegulatorTask : public LeanTask { etResult = c2f(etResult); } - if (fabs(prevEtResult - etResult) > 0.09f) { + if (fabsf(prevEtResult - etResult) > 0.09f) { prevEtResult = etResult; newTemp += etResult; @@ -130,18 +176,18 @@ class RegulatorTask : public LeanTask { } // if use pid - if (settings.pid.enable) { + if (settings.pid.enabled) { //if (vars.parameters.heatingEnabled) { - if (settings.heating.enable && vars.sensors.indoor.connected) { + if (settings.heating.enabled && this->indoorSensorsConnected) { pidRegulator.Kp = settings.heating.turbo ? 0.0f : settings.pid.p_factor; pidRegulator.Kd = settings.pid.d_factor; pidRegulator.setLimits(settings.pid.minTemp, settings.pid.maxTemp); pidRegulator.setDt(settings.pid.dt * 1000u); - pidRegulator.input = vars.temperatures.indoor; + pidRegulator.input = vars.master.heating.indoorTemp; pidRegulator.setpoint = settings.heating.target; - if (fabs(pidRegulator.Ki - settings.pid.i_factor) >= 0.0001f) { + if (fabsf(pidRegulator.Ki - settings.pid.i_factor) >= 0.0001f) { pidRegulator.Ki = settings.pid.i_factor; pidRegulator.integral = 0.0f; pidRegulator.getResultNow(); @@ -150,7 +196,7 @@ class RegulatorTask : public LeanTask { } float pidResult = pidRegulator.getResultTimer(); - if (fabs(prevPidResult - pidResult) > 0.09f) { + if (fabsf(prevPidResult - pidResult) > 0.09f) { prevPidResult = pidResult; newTemp += pidResult; @@ -167,19 +213,14 @@ class RegulatorTask : public LeanTask { } // Turbo mode - if (settings.heating.turbo && (settings.equitherm.enable || settings.pid.enable)) { + if (settings.heating.turbo && (settings.equitherm.enabled || settings.pid.enabled)) { newTemp += constrain( - settings.heating.target - vars.temperatures.indoor, + settings.heating.target - vars.master.heating.indoorTemp, -3.0f, 3.0f ) * settings.heating.turboFactor; } - // default temp, manual mode - if (!settings.equitherm.enable && !settings.pid.enable) { - newTemp = settings.heating.target; - } - return newTemp; } }; diff --git a/src/Sensors.h b/src/Sensors.h new file mode 100644 index 0000000..4c6735c --- /dev/null +++ b/src/Sensors.h @@ -0,0 +1,424 @@ +#pragma once + +class Sensors { +protected: + static uint8_t maxSensors; + +public: + enum class Type : uint8_t { + OT_OUTDOOR_TEMP = 0, + OT_HEATING_TEMP = 1, + OT_HEATING_RETURN_TEMP = 2, + OT_DHW_TEMP = 3, + OT_DHW_TEMP2 = 4, + OT_DHW_FLOW_RATE = 5, + OT_CH2_TEMP = 6, + OT_EXHAUST_TEMP = 7, + OT_HEAT_EXCHANGER_TEMP = 8, + OT_PRESSURE = 9, + OT_MODULATION_LEVEL = 10, + OT_CURRENT_POWER = 11, + + NTC_10K_TEMP = 50, + DALLAS_TEMP = 51, + BLUETOOTH = 52, + + HEATING_SETPOINT_TEMP = 253, + MANUAL = 254, + NOT_CONFIGURED = 255 + }; + + enum class Purpose : uint8_t { + OUTDOOR_TEMP = 0, + INDOOR_TEMP = 1, + HEATING_TEMP = 2, + HEATING_RETURN_TEMP = 3, + DHW_TEMP = 4, + DHW_RETURN_TEMP = 5, + DHW_FLOW_RATE = 6, + EXHAUST_TEMP = 7, + MODULATION_LEVEL = 8, + CURRENT_POWER = 9, + + PRESSURE = 252, + HUMIDITY = 253, + TEMPERATURE = 254, + NOT_CONFIGURED = 255 + }; + + enum class ValueType : uint8_t { + PRIMARY = 0, + TEMPERATURE = 0, + HUMIDITY = 1, + BATTERY = 2, + RSSI = 3 + }; + + typedef struct { + bool enabled = false; + char name[33]; + Purpose purpose = Purpose::NOT_CONFIGURED; + Type type = Type::NOT_CONFIGURED; + uint8_t gpio = GPIO_IS_NOT_CONFIGURED; + uint8_t address[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + float offset = 0.0f; + float factor = 1.0f; + bool filtering = false; + float filteringFactor = 0.15f; + } Settings; + + typedef struct { + bool connected = false; + unsigned long activityTime = 0; + uint8_t signalQuality = 0; + //float raw[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + float values[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + } Result; + + + static Settings* settings; + static Result* results; + + static inline void setMaxSensors(uint8_t value) { + maxSensors = value; + } + + static inline uint8_t getMaxSensors() { + return maxSensors; + } + + static uint8_t getMaxSensorId() { + uint8_t maxSensors = getMaxSensors(); + return maxSensors > 1 ? (maxSensors - 1) : 0; + } + + static inline bool isValidSensorId(const uint8_t id) { + return id >= 0 && id <= getMaxSensorId(); + } + + static inline bool isValidValueId(const uint8_t id) { + return id >= (uint8_t) ValueType::TEMPERATURE && id <= (uint8_t) ValueType::RSSI; + } + + static bool hasEnabledAndValid(const uint8_t id) { + if (!isValidSensorId(id) || !settings[id].enabled) { + return false; + } + + if (settings[id].type == Type::NOT_CONFIGURED || settings[id].purpose == Purpose::NOT_CONFIGURED) { + return false; + } + + return true; + } + + static uint8_t getAmountByType(Type type) { + if (settings == nullptr) { + return 0; + } + + uint8_t amount = 0; + for (uint8_t id = 0; id < getMaxSensorId(); id++) { + if (settings[id].type == type) { + amount++; + } + } + + return amount; + } + + static int16_t getIdByName(const char* name) { + if (settings == nullptr) { + return 0; + } + + for (uint8_t id = 0; id < getMaxSensorId(); id++) { + if (strcmp(settings[id].name, name) == 0) { + return id; + } + } + + return -1; + } + + static int16_t getIdByObjectId(const char* objectId) { + if (settings == nullptr) { + return 0; + } + + for (uint8_t id = 0; id < getMaxSensorId(); id++) { + String _objectId = Sensors::makeObjectId(settings[id].name); + if (strcmp(_objectId.c_str(), objectId) == 0) { + return id; + } + } + + return -1; + } + + static bool setValueById(const uint8_t sensorId, float value, const ValueType valueType, const bool updateActivityTime = false, const bool markConnected = false) { + if (settings == nullptr || results == nullptr) { + return false; + } + + uint8_t valueId = (uint8_t) valueType; + if (!isValidSensorId(sensorId) || !isValidValueId(valueId)) { + return false; + } + + auto& sSensor = settings[sensorId]; + auto& rSensor = results[sensorId]; + + float compensatedValue = value; + if (valueType == ValueType::PRIMARY) { + if (fabsf(sSensor.factor) > 0.001f) { + compensatedValue *= sSensor.factor; + } + + if (fabsf(sSensor.offset) > 0.001f) { + compensatedValue += sSensor.offset; + } + + } else if (valueType == ValueType::RSSI) { + if (sSensor.type == Type::BLUETOOTH) { + rSensor.signalQuality = Sensors::bluetoothRssiToQuality(value); + } + } + + if (sSensor.filtering && fabs(rSensor.values[valueId]) >= 0.1f) { + rSensor.values[valueId] += (compensatedValue - rSensor.values[valueId]) * sSensor.filteringFactor; + + } else { + rSensor.values[valueId] = compensatedValue; + } + + if (updateActivityTime) { + rSensor.activityTime = millis(); + } + + if (markConnected) { + if (!rSensor.connected) { + rSensor.connected = true; + + Log.snoticeln( + FPSTR(L_SENSORS), F("#%hhu '%s' new status: CONNECTED"), + sensorId, sSensor.name + ); + } + } + + Log.snoticeln( + FPSTR(L_SENSORS), F("#%hhu '%s' new value %hhu: %.2f, compensated: %.2f, raw: %.2f"), + sensorId, sSensor.name, valueId, rSensor.values[valueId], compensatedValue, value + ); + + return true; + } + + static uint8_t setValueByType(Type type, float value, const ValueType valueType, const bool updateActivityTime = false, const bool markConnected = false) { + if (settings == nullptr) { + return 0; + } + + uint8_t updated = 0; + + // read sensors data for current instance + for (uint8_t sensorId = 0; sensorId < getMaxSensorId(); sensorId++) { + auto& sSensor = settings[sensorId]; + + // only target & valid sensors + if (!sSensor.enabled || sSensor.type != type) { + continue; + } + + if (setValueById(sensorId, value, valueType, updateActivityTime, markConnected)) { + updated++; + } + } + + return updated; + } + + static bool getConnectionStatusById(const uint8_t sensorId) { + if (settings == nullptr || results == nullptr) { + return false; + } + + if (!isValidSensorId(sensorId)) { + return false; + } + + return results[sensorId].connected; + } + + static bool setConnectionStatusById(const uint8_t sensorId, const bool status, const bool updateActivityTime = true) { + if (settings == nullptr || results == nullptr) { + return false; + } + + if (!isValidSensorId(sensorId)) { + return false; + } + + auto& sSensor = settings[sensorId]; + auto& rSensor = results[sensorId]; + + if (rSensor.connected != status) { + Log.snoticeln( + FPSTR(L_SENSORS), F("#%hhu '%s' new status: %s"), + sensorId, sSensor.name, status ? F("CONNECTED") : F("DISCONNECTED") + ); + + rSensor.connected = status; + } + + if (updateActivityTime) { + rSensor.activityTime = millis(); + } + + return true; + } + + static uint8_t setConnectionStatusByType(Type type, const bool status, const bool updateActivityTime = true) { + if (settings == nullptr) { + return 0; + } + + uint8_t updated = 0; + + // read sensors data for current instance + for (uint8_t sensorId = 0; sensorId < getMaxSensorId(); sensorId++) { + auto& sSensor = settings[sensorId]; + + // only target & valid sensors + if (!sSensor.enabled || sSensor.type != type) { + continue; + } + + if (setConnectionStatusById(sensorId, status, updateActivityTime)) { + updated++; + } + } + + return updated; + } + + static float getMeanValueByPurpose(Purpose purpose, const ValueType valueType, bool onlyConnected = true) { + if (settings == nullptr || results == nullptr) { + return 0; + } + + uint8_t valueId = (uint8_t) valueType; + if (!isValidValueId(valueId)) { + return false; + } + + float value = 0.0f; + uint8_t amount = 0; + + for (uint8_t id = 0; id < getMaxSensorId(); id++) { + auto& sSensor = settings[id]; + auto& rSensor = results[id]; + + if (sSensor.purpose == purpose && (!onlyConnected || rSensor.connected)) { + value += rSensor.values[valueId]; + amount++; + } + } + + if (!amount) { + return 0.0f; + + } else if (amount == 1) { + return value; + + } else { + return value / amount; + } + } + + static bool existsConnectedSensorsByPurpose(Purpose purpose) { + if (settings == nullptr || results == nullptr) { + return 0; + } + + for (uint8_t id = 0; id < getMaxSensorId(); id++) { + if (settings[id].purpose == purpose && results[id].connected) { + return true; + } + } + + return false; + } + + template + static String cleanName(T value, char space = ' ') { + String clean = value; + + // only valid symbols + for (uint8_t pos = 0; pos < clean.length(); pos++) { + char symbol = clean.charAt(pos); + + // 0..9 + if (symbol >= 48 && symbol <= 57) { + continue; + } + + // A..Z + if (symbol >= 65 && symbol <= 90) { + continue; + } + + // a..z + if (symbol >= 97 && symbol <= 122) { + continue; + } + + // _- + if (symbol == 95 || symbol == 45 || symbol == space) { + continue; + } + + clean.setCharAt(pos, space); + } + + clean.trim(); + + return clean; + } + + template + static String makeObjectId(T value, char separator = '_') { + auto objId = cleanName(value); + objId.toLowerCase(); + objId.replace(' ', separator); + + return objId; + } + + template + static auto makeObjectIdWithSuffix(TV value, TS suffix, char separator = '_') { + auto objId = makeObjectId(value, separator); + objId += separator; + objId += suffix; + + return objId; + } + + template + static auto makeObjectIdWithPrefix(TV value, TP prefix, char separator = '_') { + String objId = prefix; + objId += separator; + objId += makeObjectId(value, separator); + + return objId; + } + + static uint8_t bluetoothRssiToQuality(int rssi) { + return constrain(map(rssi, -110, -50, 0, 100), 0, 100);; + } +}; + +uint8_t Sensors::maxSensors = 0; +Sensors::Settings* Sensors::settings = nullptr; +Sensors::Result* Sensors::results = nullptr; \ No newline at end of file diff --git a/src/SensorsTask.h b/src/SensorsTask.h index 0f9d81c..88abbc2 100644 --- a/src/SensorsTask.h +++ b/src/SensorsTask.h @@ -1,3 +1,4 @@ +#include #include #include @@ -5,50 +6,40 @@ #include #endif +extern FileData fsSensorsSettings; + class SensorsTask : public LeanTask { public: SensorsTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) { - this->oneWireOutdoorSensor = new OneWire(); - this->outdoorSensor = new DallasTemperature(this->oneWireOutdoorSensor); - this->outdoorSensor->setWaitForConversion(false); - - this->oneWireIndoorSensor = new OneWire(); - this->indoorSensor = new DallasTemperature(this->oneWireIndoorSensor); - this->indoorSensor->setWaitForConversion(false); + this->owInstances.reserve(2); + this->dallasInstances.reserve(2); + this->dallasSearchTime.reserve(2); + this->dallasPolling.reserve(2); + this->dallasLastPollingTime.reserve(2); } ~SensorsTask() { - delete this->outdoorSensor; - delete this->oneWireOutdoorSensor; - delete this->indoorSensor; - delete this->oneWireIndoorSensor; + this->dallasInstances.clear(); + this->owInstances.clear(); + this->dallasSearchTime.clear(); + this->dallasPolling.clear(); + this->dallasLastPollingTime.clear(); } protected: - OneWire* oneWireOutdoorSensor = nullptr; - OneWire* oneWireIndoorSensor = nullptr; - - DallasTemperature* outdoorSensor = nullptr; - DallasTemperature* indoorSensor = nullptr; - - bool initOutdoorSensor = false; - unsigned long initOutdoorSensorTime = 0; - unsigned long startOutdoorConversionTime = 0; - float filteredOutdoorTemp = 0; - float prevFilteredOutdoorTemp = 0; - - bool initIndoorSensor = false; - unsigned long initIndoorSensorTime = 0; - unsigned long startIndoorConversionTime = 0; - float filteredIndoorTemp = 0; - float prevFilteredIndoorTemp = 0; + const unsigned int disconnectedTimeout = 120000; + const unsigned short dallasSearchInterval = 60000; + const unsigned short dallasPollingInterval = 10000; + const unsigned short globalPollingInterval = 15000; + + std::unordered_map owInstances; + std::unordered_map dallasInstances; + std::unordered_map dallasSearchTime; + std::unordered_map dallasPolling; + std::unordered_map dallasLastPollingTime; + unsigned long globalLastPollingTime = 0; #if defined(ARDUINO_ARCH_ESP32) - #if USE_BLE - unsigned long outdoorConnectedTime = 0; - unsigned long indoorConnectedTime = 0; - #endif - const char* getTaskName() override { return "Sensors"; } @@ -68,102 +59,359 @@ class SensorsTask : public LeanTask { #endif void loop() { - #if USE_BLE - if (!NimBLEDevice::getInitialized() && millis() > 5000) { - Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Init BLE")); - BLEDevice::init(""); - NimBLEDevice::setPower(ESP_PWR_LVL_P9); + if (isPollingDallasSensors()) { + pollingDallasSensors(false); } - #endif - if (settings.sensors.outdoor.type == SensorType::DS18B20 && GPIO_IS_VALID(settings.sensors.outdoor.gpio)) { - outdoorDallasSensor(); + if (millis() - this->globalLastPollingTime > this->globalPollingInterval) { + makeDallasInstances(); + cleanDallasInstances(); + searchDallasSensors(); + fillingAddressesDallasSensors(); + pollingDallasSensors(); + pollingNtcSensors(); + pollingBleSensors(); + + this->globalLastPollingTime = millis(); } - #if USE_BLE - else if (settings.sensors.outdoor.type == SensorType::BLUETOOTH) { - bool connected = this->bluetoothSensor( - BLEAddress(settings.sensors.outdoor.bleAddress), - &vars.sensors.outdoor.rssi, - &this->filteredOutdoorTemp, - &vars.sensors.outdoor.humidity, - &vars.sensors.outdoor.battery - ); - if (connected) { - this->outdoorConnectedTime = millis(); - vars.sensors.outdoor.connected = true; + updateConnectionStatus(); + updateMasterValues(); + } + + void updateMasterValues() { + vars.master.heating.outdoorTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::OUTDOOR_TEMP, Sensors::ValueType::PRIMARY); + vars.master.heating.indoorTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::INDOOR_TEMP, Sensors::ValueType::PRIMARY); - } else if (millis() - this->outdoorConnectedTime > 60000) { - vars.sensors.outdoor.connected = false; + vars.master.heating.currentTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::HEATING_TEMP, Sensors::ValueType::PRIMARY); + vars.master.heating.returnTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::HEATING_RETURN_TEMP, Sensors::ValueType::PRIMARY); + + vars.master.dhw.currentTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::DHW_TEMP, Sensors::ValueType::PRIMARY); + vars.master.dhw.returnTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::DHW_RETURN_TEMP, Sensors::ValueType::PRIMARY); + } + + void makeDallasInstances() { + for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { + auto& sSensor = Sensors::settings[sensorId]; + + if (!sSensor.enabled || sSensor.type != Sensors::Type::DALLAS_TEMP || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) { + continue; + + } else if (this->dallasInstances.count(sSensor.gpio)) { + // no need to make instances + continue; } + + auto& owInstance = this->owInstances[sSensor.gpio]; + owInstance.begin(sSensor.gpio); + owInstance.reset(); + + this->dallasSearchTime[sSensor.gpio] = 0; + this->dallasPolling[sSensor.gpio] = false; + this->dallasLastPollingTime[sSensor.gpio] = 0; + + auto& instance = this->dallasInstances[sSensor.gpio]; + instance.setOneWire(&owInstance); + instance.setWaitForConversion(false); + + Log.sinfoln(FPSTR(L_SENSORS_DALLAS), F("Started on GPIO %hhu"), sSensor.gpio); } - #endif + } + + void cleanDallasInstances() { + for (auto& [gpio, instance] : this->dallasInstances) { + bool instanceUsed = false; + + for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { + auto& sSensor = Sensors::settings[sensorId]; + + if (!sSensor.enabled || sSensor.type != Sensors::Type::DALLAS_TEMP || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) { + continue; + } - if (settings.sensors.indoor.type == SensorType::DS18B20 && GPIO_IS_VALID(settings.sensors.indoor.gpio)) { - indoorDallasSensor(); + if (Sensors::settings[sensorId].gpio == gpio) { + instanceUsed = true; + break; + } + } + + if (!instanceUsed) {; + this->dallasInstances.erase(gpio); + this->owInstances.erase(gpio); + this->dallasSearchTime.erase(gpio); + this->dallasPolling.erase(gpio); + this->dallasLastPollingTime.erase(gpio); + + Log.sinfoln(FPSTR(L_SENSORS_DALLAS), F("Stopped on GPIO %hhu"), gpio); + continue; + } } - #if USE_BLE - else if (settings.sensors.indoor.type == SensorType::BLUETOOTH) { - bool connected = this->bluetoothSensor( - BLEAddress(settings.sensors.indoor.bleAddress), - &vars.sensors.indoor.rssi, - &this->filteredIndoorTemp, - &vars.sensors.indoor.humidity, - &vars.sensors.indoor.battery - ); + } + + void searchDallasSensors() { + // search sensors on bus + for (auto& [gpio, instance] : this->dallasInstances) { + // do not search if polling! + if (this->dallasPolling[gpio]) { + continue; + } - if (connected) { - this->indoorConnectedTime = millis(); - vars.sensors.indoor.connected = true; + if (millis() - this->dallasSearchTime[gpio] > this->dallasSearchInterval) { + this->dallasSearchTime[gpio] = millis(); + instance.begin(); - } else if (millis() - this->indoorConnectedTime > 60000) { - vars.sensors.indoor.connected = false; + Log.straceln( + FPSTR(L_SENSORS_DALLAS), + F("GPIO %hhu, devices on bus: %hhu, DS18* devices: %hhu"), + gpio, instance.getDeviceCount(), instance.getDS18Count() + ); } } - #endif + } - // convert - if (fabs(this->prevFilteredOutdoorTemp - this->filteredOutdoorTemp) >= 0.1f) { - float newTemp = settings.sensors.outdoor.offset; - if (settings.system.unitSystem == UnitSystem::METRIC) { - newTemp += this->filteredOutdoorTemp; + void fillingAddressesDallasSensors() { + // check & filling sensors address + for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { + auto& sSensor = Sensors::settings[sensorId]; + + if (!sSensor.enabled || sSensor.type != Sensors::Type::DALLAS_TEMP || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) { + continue; + + } else if (!this->dallasInstances.count(sSensor.gpio)) { + continue; + } - } else if (settings.system.unitSystem == UnitSystem::IMPERIAL) { - newTemp += c2f(this->filteredOutdoorTemp); + // do nothing if address not empty + if (!isEmptyAddress(sSensor.address)) { + continue; } - if (fabs(vars.temperatures.outdoor - newTemp) > 0.099f) { - vars.temperatures.outdoor = newTemp; - Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("New temp: %f"), vars.temperatures.outdoor); + // do nothing if polling + if (this->dallasPolling[sSensor.gpio]) { + continue; + } + + auto& instance = this->dallasInstances[sSensor.gpio]; + DeviceAddress devAddr; + for (uint8_t devId = 0; devId < instance.getDeviceCount(); devId++) { + if (!instance.getAddress(devAddr, devId)) { + continue; + } + + bool freeAddress = true; + + // checking address usage + for (uint8_t checkingSensorId = 0; checkingSensorId <= Sensors::getMaxSensorId(); checkingSensorId++) { + auto& sCheckingSensor = Sensors::settings[checkingSensorId]; + if (sCheckingSensor.type != Sensors::Type::DALLAS_TEMP || checkingSensorId == sensorId) { + continue; + } + + if (sCheckingSensor.gpio != sSensor.gpio || isEmptyAddress(sCheckingSensor.address)) { + continue; + } + + if (isEqualAddress(sCheckingSensor.address, devAddr)) { + freeAddress = false; + break; + } + } + + // address already in use + if (!freeAddress) { + continue; + } + + // set address + for (uint8_t i = 0; i < 8; i++) { + sSensor.address[i] = devAddr[i]; + } + + fsSensorsSettings.update(); + Log.straceln( + FPSTR(L_SENSORS_DALLAS), F("GPIO %hhu, sensor #%hhu '%s', set address: %hhX:%hhX:%hhX:%hhX:%hhX:%hhX:%hhX:%hhX"), + sSensor.gpio, sensorId, sSensor.name, + sSensor.address[0], sSensor.address[1], sSensor.address[2], sSensor.address[3], + sSensor.address[4], sSensor.address[5], sSensor.address[6], sSensor.address[7] + ); + + break; } + } + } - this->prevFilteredOutdoorTemp = this->filteredOutdoorTemp; + bool isPollingDallasSensors() { + for (auto& [gpio, instance] : this->dallasInstances) { + if (this->dallasPolling.count(gpio) && this->dallasPolling[gpio]) { + return true; + } } - if (fabs(this->prevFilteredIndoorTemp - this->filteredIndoorTemp) > 0.1f) { - float newTemp = settings.sensors.indoor.offset; - if (settings.system.unitSystem == UnitSystem::METRIC) { - newTemp += this->filteredIndoorTemp; + return false; + } + + void pollingDallasSensors(bool newPolling = true) { + for (auto& [gpio, instance] : this->dallasInstances) { + unsigned long ts = millis(); + + if (this->dallasPolling[gpio]) { + auto minPollingTime = instance.millisToWaitForConversion(12); + unsigned long estimatePollingTime = ts - this->dallasLastPollingTime[gpio]; - } else if (settings.system.unitSystem == UnitSystem::IMPERIAL) { - newTemp += c2f(this->filteredIndoorTemp); + // check conversion time + if (estimatePollingTime < minPollingTime) { + continue; + } + + // check conversion + bool conversionComplete = instance.isConversionComplete(); + if (!conversionComplete) { + if (estimatePollingTime > (minPollingTime * 2)) { + this->dallasPolling[gpio] = false; + + Log.swarningln(FPSTR(L_SENSORS_DALLAS), F("GPIO %hhu, timeout receiving data"), gpio); + } + + continue; + } + + // read sensors data for current instance + for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { + auto& sSensor = Sensors::settings[sensorId]; + + // only target & valid sensors + if (!sSensor.enabled || sSensor.type != Sensors::Type::DALLAS_TEMP || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) { + continue; + + } else if (sSensor.gpio != gpio || isEmptyAddress(sSensor.address)) { + continue; + } + + float value = instance.getTempC(sSensor.address); + if (value == DEVICE_DISCONNECTED_C) { + Log.swarningln( + FPSTR(L_SENSORS_DALLAS), F("GPIO %hhu, sensor #%hhu '%s': failed receiving data"), + sSensor.gpio, sensorId, sSensor.name + ); + + continue; + } + + Log.straceln( + FPSTR(L_SENSORS_DALLAS), F("GPIO %hhu, sensor #%hhu '%s', received data: %.2f"), + sSensor.gpio, sensorId, sSensor.name, value + ); + + // set sensor value + Sensors::setValueById(sensorId, value, Sensors::ValueType::TEMPERATURE, true, true); + } + + // reset polling flag + this->dallasPolling[gpio] = false; + + } else if (newPolling) { + auto estimateLastPollingTime = ts - this->dallasLastPollingTime[gpio]; + + // check last polling time + if (estimateLastPollingTime < this->dallasPollingInterval) { + continue; + } + + // check sensors on bus + if (!instance.getDeviceCount()) { + continue; + } + + // start polling + instance.setResolution(12); + instance.requestTemperatures(); + this->dallasPolling[gpio] = true; + this->dallasLastPollingTime[gpio] = ts; + + Log.straceln(FPSTR(L_SENSORS_DALLAS), F("GPIO %hhu, polling..."), gpio); } + } + } - if (fabs(vars.temperatures.indoor - newTemp) > 0.099f) { - vars.temperatures.indoor = newTemp; - Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("New temp: %f"), vars.temperatures.indoor); + void pollingBleSensors() { + #if USE_BLE + if (!NimBLEDevice::getInitialized() && millis() > 5000) { + Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Initialized")); + BLEDevice::init(""); + NimBLEDevice::setPower(ESP_PWR_LVL_P9); + } + + 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; } - this->prevFilteredIndoorTemp = this->filteredIndoorTemp; + connectToBleDevice(sensorId); } + #endif } -#if USE_BLE - bool bluetoothSensor(const BLEAddress& address, int8_t* const pRssi, float* const pTemperature, float* const pHumidity = nullptr, float* const pBattery = nullptr) { + void pollingNtcSensors() { + for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { + auto& sSensor = Sensors::settings[sensorId]; + + if (!sSensor.enabled || sSensor.type != Sensors::Type::NTC_10K_TEMP || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) { + continue; + } + + const auto value = analogReadMilliVolts(sSensor.gpio); + if (value < DEFAULT_NTC_VLOW_TRESHOLD) { + if (Sensors::getConnectionStatusById(sensorId)) { + Sensors::setConnectionStatusById(sensorId, false, false); + } + + Log.swarningln( + FPSTR(L_SENSORS_NTC), F("GPIO %hhu, sensor #%hhu '%s', voltage too low: %.2f"), + sSensor.gpio, sensorId, sSensor.name, (value / 1000.0f) + ); + + continue; + } + + const float sensorResistance = value > 0.001f + ? DEFAULT_NTC_REF_RESISTANCE / (DEFAULT_NTC_VREF / (float) value - 1.0f) + : 0.0f; + const float rawTemp = 1.0f / ( + 1.0f / (DEFAULT_NTC_NOMINAL_TEMP + 273.15f) + + log(sensorResistance / DEFAULT_NTC_NOMINAL_RESISTANCE) / DEFAULT_NTC_BETA_FACTOR + ) - 273.15f; + + Log.straceln( + FPSTR(L_SENSORS_NTC), F("GPIO %hhu, sensor #%hhu '%s', raw temp: %.2f, raw voltage: %.3f, raw resistance: %.2f"), + sSensor.gpio, sensorId, sSensor.name, rawTemp, (value / 1000.0f), sensorResistance + ); + + // set temp + Sensors::setValueById(sensorId, rawTemp, Sensors::ValueType::TEMPERATURE, true, true); + } + } + + bool connectToBleDevice(const uint8_t sensorId) { + #if USE_BLE if (!NimBLEDevice::getInitialized()) { return false; } + auto& sSensor = Sensors::settings[sensorId]; + auto& rSensor = Sensors::results[sensorId]; + + if (!sSensor.enabled || sSensor.type != Sensors::Type::BLUETOOTH || sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) { + return false; + } + + uint8_t addr[6] = { + sSensor.address[0], sSensor.address[1], sSensor.address[2], + sSensor.address[3], sSensor.address[4], sSensor.address[5] + }; + const NimBLEAddress address = NimBLEAddress(addr); + NimBLEClient* pClient = nullptr; pClient = NimBLEDevice::getClientByPeerAddress(address); @@ -181,18 +429,28 @@ class SensorsTask : public LeanTask { } if(pClient->isConnected()) { - *pRssi = pClient->getRssi(); + if (!rSensor.connected) { + rSensor.connected = true; + } + return true; } if (!pClient->connect(address)) { - Log.swarningln(FPSTR(L_SENSORS_BLE), F("Device %s: failed connecting"), address.toString().c_str()); + Log.swarningln( + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed connecting to %s"), + sensorId, sSensor.name, address.toString().c_str() + ); NimBLEDevice::deleteClient(pClient); return false; } - Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Device %s: connected"), address.toString().c_str()); + Log.sinfoln( + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': connected to %s"), + sensorId, sSensor.name, address.toString().c_str() + ); + NimBLERemoteService* pService = nullptr; NimBLERemoteCharacteristic* pChar = nullptr; @@ -201,21 +459,16 @@ class SensorsTask : public LeanTask { pService = pClient->getService(serviceUuid); if (!pService) { Log.straceln( - FPSTR(L_SENSORS_BLE), - F("Device %s: failed to find env service (%s)"), - address.toString().c_str(), - serviceUuid.toString().c_str() + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to find env service (%s) on device %s"), + sensorId, sSensor.name, serviceUuid.toString().c_str(), address.toString().c_str() ); } else { Log.straceln( - FPSTR(L_SENSORS_BLE), - F("Device %s: found env service (%s)"), - address.toString().c_str(), - serviceUuid.toString().c_str() + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found env service (%s) on device %s"), + sensorId, sSensor.name, serviceUuid.toString().c_str(), address.toString().c_str() ); - // 0x2A6E - Notify temperature x0.01C (pvvx) bool tempNotifyCreated = false; if (!tempNotifyCreated) { @@ -224,13 +477,11 @@ class SensorsTask : public LeanTask { if (pChar && pChar->canNotify()) { Log.straceln( - FPSTR(L_SENSORS_BLE), - F("Device %s: found temperature char (%s) in env service"), - address.toString().c_str(), - charUuid.toString().c_str() + 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(), address.toString().c_str() ); - tempNotifyCreated = pChar->subscribe(true, [pTemperature](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) { + tempNotifyCreated = pChar->subscribe(true, [sensorId](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) { if (pChar == nullptr) { return; } @@ -245,48 +496,47 @@ class SensorsTask : public LeanTask { return; } + auto& sSensor = Sensors::settings[sensorId]; + if (length != 2) { Log.swarningln( FPSTR(L_SENSORS_BLE), - F("Device %s: invalid notification data at temperature char (%s)"), - pClient->getPeerAddress().toString().c_str(), - pChar->getUUID().toString().c_str() + F("Sensor #%hhu '%s': invalid notification data at temp char (%s) on device %s"), + sensorId, + sSensor.name, + pChar->getUUID().toString().c_str(), + pClient->getPeerAddress().toString().c_str() ); + return; } float rawTemp = ((pData[0] | (pData[1] << 8)) * 0.01f); Log.straceln( - FPSTR(L_SENSORS_INDOOR), - F("Device %s: raw temp %f"), - pClient->getPeerAddress().toString().c_str(), - rawTemp + FPSTR(L_SENSORS_BLE), + F("Sensor #%hhu '%s': received temp: %.2f"), + sensorId, sSensor.name, rawTemp ); - if (fabs(*pTemperature) < 0.1f) { - *pTemperature = rawTemp; + // set temp + Sensors::setValueById(sensorId, rawTemp, Sensors::ValueType::TEMPERATURE, true, true); - } else { - *pTemperature += (rawTemp - (*pTemperature)) * EXT_SENSORS_FILTER_K; - } - - *pTemperature = floor((*pTemperature) * 100) / 100; + // update rssi + Sensors::setValueById(sensorId, pClient->getRssi(), Sensors::ValueType::RSSI, false, false); }); if (tempNotifyCreated) { Log.straceln( - FPSTR(L_SENSORS_BLE), - F("Device %s: subscribed to temperature char (%s) in env service"), - address.toString().c_str(), - charUuid.toString().c_str() + 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(), address.toString().c_str() ); } else { Log.swarningln( - FPSTR(L_SENSORS_BLE), - F("Device %s: failed to subscribe to temperature char (%s) in env service"), - address.toString().c_str(), - charUuid.toString().c_str() + 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(), address.toString().c_str() ); } } @@ -300,13 +550,11 @@ class SensorsTask : public LeanTask { if (pChar && pChar->canNotify()) { Log.straceln( - FPSTR(L_SENSORS_BLE), - F("Device %s: found temperature char (%s) in env service"), - address.toString().c_str(), - charUuid.toString().c_str() + 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(), address.toString().c_str() ); - tempNotifyCreated = pChar->subscribe(true, [pTemperature](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) { + tempNotifyCreated = pChar->subscribe(true, [sensorId](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) { if (pChar == nullptr) { return; } @@ -321,48 +569,47 @@ class SensorsTask : public LeanTask { return; } + auto& sSensor = Sensors::settings[sensorId]; + if (length != 2) { Log.swarningln( FPSTR(L_SENSORS_BLE), - F("Device %s: invalid notification data at temperature char (%s)"), - pClient->getPeerAddress().toString().c_str(), - pChar->getUUID().toString().c_str() + F("Sensor #%hhu '%s': invalid notification data at temp char (%s) on device %s"), + sensorId, + sSensor.name, + pChar->getUUID().toString().c_str(), + pClient->getPeerAddress().toString().c_str() ); + return; } float rawTemp = ((pData[0] | (pData[1] << 8)) * 0.1f); Log.straceln( - FPSTR(L_SENSORS_INDOOR), - F("Device %s: raw temp %f"), - pClient->getPeerAddress().toString().c_str(), - rawTemp + FPSTR(L_SENSORS_BLE), + F("Sensor #%hhu '%s': received temp: %.2f"), + sensorId, sSensor.name, rawTemp ); - if (fabs(*pTemperature) < 0.1f) { - *pTemperature = rawTemp; + // set temp + Sensors::setValueById(sensorId, rawTemp, Sensors::ValueType::TEMPERATURE, true, true); - } else { - *pTemperature += (rawTemp - (*pTemperature)) * EXT_SENSORS_FILTER_K; - } - - *pTemperature = floor((*pTemperature) * 100) / 100; + // update rssi + Sensors::setValueById(sensorId, pClient->getRssi(), Sensors::ValueType::RSSI, false, false); }); if (tempNotifyCreated) { Log.straceln( - FPSTR(L_SENSORS_BLE), - F("Device %s: subscribed to temperature char (%s) in env service"), - address.toString().c_str(), - charUuid.toString().c_str() + 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(), address.toString().c_str() ); } else { Log.swarningln( - FPSTR(L_SENSORS_BLE), - F("Device %s: failed to subscribe to temperature char (%s) in env service"), - address.toString().c_str(), - charUuid.toString().c_str() + 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(), address.toString().c_str() ); } } @@ -370,9 +617,8 @@ class SensorsTask : public LeanTask { if (!tempNotifyCreated) { Log.swarningln( - FPSTR(L_SENSORS_BLE), - F("Device %s: not found supported temperature chars in env service"), - address.toString().c_str() + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found supported temp chars in env service on device %s"), + sensorId, sSensor.name, address.toString().c_str() ); pClient->disconnect(); @@ -381,7 +627,7 @@ class SensorsTask : public LeanTask { // 0x2A6F - Notify about humidity x0.01% (pvvx) - if (pHumidity != nullptr) { + { bool humidityNotifyCreated = false; if (!humidityNotifyCreated) { NimBLEUUID charUuid((uint16_t) 0x2A6F); @@ -389,13 +635,11 @@ class SensorsTask : public LeanTask { if (pChar && pChar->canNotify()) { Log.straceln( - FPSTR(L_SENSORS_BLE), - F("Device %s: found humidity char (%s) in env service"), - address.toString().c_str(), - charUuid.toString().c_str() + 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(), address.toString().c_str() ); - humidityNotifyCreated = pChar->subscribe(true, [pHumidity](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) { + humidityNotifyCreated = pChar->subscribe(true, [sensorId](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) { if (pChar == nullptr) { return; } @@ -410,48 +654,47 @@ class SensorsTask : public LeanTask { return; } + auto& sSensor = Sensors::settings[sensorId]; + if (length != 2) { Log.swarningln( FPSTR(L_SENSORS_BLE), - F("Device %s: invalid notification data at humidity char (%s)"), - pClient->getPeerAddress().toString().c_str(), - pChar->getUUID().toString().c_str() + F("Sensor #%hhu '%s': invalid notification data at humidity char (%s) on device %s"), + sensorId, + sSensor.name, + pChar->getUUID().toString().c_str(), + pClient->getPeerAddress().toString().c_str() ); + return; } float rawHumidity = ((pData[0] | (pData[1] << 8)) * 0.01f); Log.straceln( - FPSTR(L_SENSORS_INDOOR), - F("Device %s: raw humidity %f"), - pClient->getPeerAddress().toString().c_str(), - rawHumidity + FPSTR(L_SENSORS_BLE), + F("Sensor #%hhu '%s': received humidity: %.2f"), + sensorId, sSensor.name, rawHumidity ); - if (fabs(*pHumidity) < 0.1f) { - *pHumidity = rawHumidity; - - } else { - *pHumidity += (rawHumidity - (*pHumidity)) * EXT_SENSORS_FILTER_K; - } + // set humidity + Sensors::setValueById(sensorId, rawHumidity, Sensors::ValueType::HUMIDITY, true, true); - *pHumidity = floor((*pHumidity) * 100) / 100; + // update rssi + Sensors::setValueById(sensorId, pClient->getRssi(), Sensors::ValueType::RSSI, false, false); }); if (humidityNotifyCreated) { Log.straceln( - FPSTR(L_SENSORS_BLE), - F("Device %s: subscribed to humidity char (%s) in env service"), - address.toString().c_str(), - charUuid.toString().c_str() + 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(), address.toString().c_str() ); } else { Log.swarningln( - FPSTR(L_SENSORS_BLE), - F("Device %s: failed to subscribe to humidity char (%s) in env service"), - address.toString().c_str(), - charUuid.toString().c_str() + 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(), address.toString().c_str() ); } } @@ -459,9 +702,8 @@ class SensorsTask : public LeanTask { if (!humidityNotifyCreated) { Log.swarningln( - FPSTR(L_SENSORS_BLE), - F("Device %s: not found supported humidity chars in env service"), - address.toString().c_str() + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found supported humidity chars in env service on device %s"), + sensorId, sSensor.name, address.toString().c_str() ); } } @@ -469,23 +711,19 @@ class SensorsTask : public LeanTask { // Battery Service (0x180F) - if (pBattery != nullptr) { + { NimBLEUUID serviceUuid((uint16_t) 0x180F); pService = pClient->getService(serviceUuid); if (!pService) { Log.straceln( - FPSTR(L_SENSORS_BLE), - F("Device %s: failed to find battery service (%s)"), - address.toString().c_str(), - serviceUuid.toString().c_str() + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to find battery service (%s) on device %s"), + sensorId, sSensor.name, serviceUuid.toString().c_str(), address.toString().c_str() ); } else { Log.straceln( - FPSTR(L_SENSORS_BLE), - F("Device %s: found battery service (%s)"), - address.toString().c_str(), - serviceUuid.toString().c_str() + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found battery service (%s) on device %s"), + sensorId, sSensor.name, serviceUuid.toString().c_str(), address.toString().c_str() ); // 0x2A19 - Notify the battery charge level 0..99% (pvvx) @@ -496,13 +734,11 @@ class SensorsTask : public LeanTask { if (pChar && pChar->canNotify()) { Log.straceln( - FPSTR(L_SENSORS_BLE), - F("Device %s: found battery char (%s) in battery service"), - address.toString().c_str(), - charUuid.toString().c_str() + 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(), address.toString().c_str() ); - batteryNotifyCreated = pChar->subscribe(true, [pBattery](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) { + batteryNotifyCreated = pChar->subscribe(true, [sensorId](NimBLERemoteCharacteristic* pChar, uint8_t* pData, size_t length, bool isNotify) { if (pChar == nullptr) { return; } @@ -517,48 +753,47 @@ class SensorsTask : public LeanTask { return; } + auto& sSensor = Sensors::settings[sensorId]; + if (length != 1) { Log.swarningln( FPSTR(L_SENSORS_BLE), - F("Device %s: invalid notification data at battery char (%s)"), - pClient->getPeerAddress().toString().c_str(), - pChar->getUUID().toString().c_str() + F("Sensor #%hhu '%s': invalid notification data at battery char (%s) on device %s"), + sensorId, + sSensor.name, + pChar->getUUID().toString().c_str(), + pClient->getPeerAddress().toString().c_str() ); + return; } uint8_t rawBattery = pData[0]; Log.straceln( - FPSTR(L_SENSORS_INDOOR), - F("Device %s: raw battery %hhu"), - pClient->getPeerAddress().toString().c_str(), - rawBattery + FPSTR(L_SENSORS_BLE), + F("Sensor #%hhu '%s': received battery: %.2f"), + sensorId, sSensor.name, rawBattery ); - if (fabs(*pBattery) < 0.1f) { - *pBattery = rawBattery; - - } else { - *pBattery += (rawBattery - (*pBattery)) * EXT_SENSORS_FILTER_K; - } - - *pBattery = floor((*pBattery) * 100) / 100; + // set battery + Sensors::setValueById(sensorId, rawBattery, Sensors::ValueType::BATTERY, true, true); + + // update rssi + Sensors::setValueById(sensorId, pClient->getRssi(), Sensors::ValueType::RSSI, false, false); }); if (batteryNotifyCreated) { Log.straceln( - FPSTR(L_SENSORS_BLE), - F("Device %s: subscribed to battery char (%s) in battery service"), - address.toString().c_str(), - charUuid.toString().c_str() + 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(), address.toString().c_str() ); } else { Log.swarningln( - FPSTR(L_SENSORS_BLE), - F("Device %s: failed to subscribe to battery char (%s) in battery service"), - address.toString().c_str(), - charUuid.toString().c_str() + 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(), address.toString().c_str() ); } } @@ -566,173 +801,65 @@ class SensorsTask : public LeanTask { if (!batteryNotifyCreated) { Log.swarningln( - FPSTR(L_SENSORS_BLE), - F("Device %s: not found supported battery chars in battery service"), - address.toString().c_str() + FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found supported battery chars in battery service on device %s"), + sensorId, sSensor.name, address.toString().c_str() ); } } } return true; + #else + return false; + #endif } -#endif - - void outdoorDallasSensor() { - if (!this->initOutdoorSensor) { - if (this->initOutdoorSensorTime && millis() - this->initOutdoorSensorTime < EXT_SENSORS_INTERVAL * 10) { - return; - } - - Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("Starting on GPIO %hhu..."), settings.sensors.outdoor.gpio); - - this->oneWireOutdoorSensor->begin(settings.sensors.outdoor.gpio); - this->oneWireOutdoorSensor->reset(); - this->outdoorSensor->begin(); - this->initOutdoorSensorTime = millis(); - - Log.straceln( - FPSTR(L_SENSORS_OUTDOOR), - F("Devices on bus: %hhu, DS18* devices: %hhu"), - this->outdoorSensor->getDeviceCount(), - this->outdoorSensor->getDS18Count() - ); - - if (this->outdoorSensor->getDeviceCount() > 0) { - this->initOutdoorSensor = true; - this->outdoorSensor->setResolution(12); - this->outdoorSensor->requestTemperatures(); - this->startOutdoorConversionTime = millis(); - - Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("Started")); - - } else { - if (vars.sensors.outdoor.connected) { - vars.sensors.outdoor.connected = false; - } - - return; - } - } - - unsigned long estimateConversionTime = millis() - this->startOutdoorConversionTime; - if (estimateConversionTime < this->outdoorSensor->millisToWaitForConversion()) { - return; - } - - bool completed = this->outdoorSensor->isConversionComplete(); - if (!completed && estimateConversionTime >= 1000) { - this->initOutdoorSensor = false; - - Log.serrorln(FPSTR(L_SENSORS_OUTDOOR), F("Could not read temperature data (no response)")); - } - if (!completed) { - return; - } - - float rawTemp = this->outdoorSensor->getTempCByIndex(0); - if (rawTemp == DEVICE_DISCONNECTED_C) { - this->initOutdoorSensor = false; + void updateConnectionStatus() { + for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { + auto& sSensor = Sensors::settings[sensorId]; + auto& rSensor = Sensors::results[sensorId]; - Log.serrorln(FPSTR(L_SENSORS_OUTDOOR), F("Could not read temperature data (not connected)")); + if (rSensor.connected && !sSensor.enabled) { + rSensor.connected = false; - } else { - Log.straceln(FPSTR(L_SENSORS_OUTDOOR), F("Raw temp: %f"), rawTemp); + } else if (rSensor.connected && sSensor.type == Sensors::Type::NOT_CONFIGURED) { + rSensor.connected = false; - if (!vars.sensors.outdoor.connected) { - vars.sensors.outdoor.connected = true; - } + } else if (rSensor.connected && sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) { + rSensor.connected = false; - if (fabs(this->filteredOutdoorTemp) < 0.1f) { - this->filteredOutdoorTemp = rawTemp; - - } else { - this->filteredOutdoorTemp += (rawTemp - this->filteredOutdoorTemp) * EXT_SENSORS_FILTER_K; - } + } else if (sSensor.type != Sensors::Type::MANUAL && rSensor.connected && (millis() - rSensor.activityTime) > this->disconnectedTimeout) { + rSensor.connected = false; - this->filteredOutdoorTemp = floor(this->filteredOutdoorTemp * 100) / 100; - this->outdoorSensor->requestTemperatures(); - this->startOutdoorConversionTime = millis(); + }/* else if (!rSensor.connected) { + rSensor.connected = true; + }*/ } } - void indoorDallasSensor() { - if (!this->initIndoorSensor) { - if (this->initIndoorSensorTime && millis() - this->initIndoorSensorTime < EXT_SENSORS_INTERVAL * 10) { - return; - } - - Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("Starting on GPIO %hhu..."), settings.sensors.indoor.gpio); - - this->oneWireIndoorSensor->begin(settings.sensors.indoor.gpio); - this->oneWireIndoorSensor->reset(); - this->indoorSensor->begin(); - this->initIndoorSensorTime = millis(); - - Log.straceln( - FPSTR(L_SENSORS_INDOOR), - F("Devices on bus: %hhu, DS18* devices: %hhu"), - this->indoorSensor->getDeviceCount(), - this->indoorSensor->getDS18Count() - ); - - if (this->indoorSensor->getDeviceCount() > 0) { - this->initIndoorSensor = true; - this->indoorSensor->setResolution(12); - this->indoorSensor->requestTemperatures(); - this->startIndoorConversionTime = millis(); + static bool isEqualAddress(const uint8_t *addr1, const uint8_t *addr2, const uint8_t length = 8) { + bool result = true; - Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("Started")); - - } else { - if (vars.sensors.indoor.connected) { - vars.sensors.indoor.connected = false; - } - - return; + for (uint8_t i = 0; i < length; i++) { + if (addr1[i] != addr2[i]) { + result = false; + break; } } - unsigned long estimateConversionTime = millis() - this->startIndoorConversionTime; - if (estimateConversionTime < this->indoorSensor->millisToWaitForConversion()) { - return; - } - - bool completed = this->indoorSensor->isConversionComplete(); - if (!completed && estimateConversionTime >= 1000) { - this->initIndoorSensor = false; - - Log.serrorln(FPSTR(L_SENSORS_INDOOR), F("Could not read temperature data (no response)")); - } - - if (!completed) { - return; - } - - float rawTemp = this->indoorSensor->getTempCByIndex(0); - if (rawTemp == DEVICE_DISCONNECTED_C) { - this->initIndoorSensor = false; - - Log.serrorln(FPSTR(L_SENSORS_INDOOR), F("Could not read temperature data (not connected)")); - - } else { - Log.straceln(FPSTR(L_SENSORS_INDOOR), F("Raw temp: %f"), rawTemp); - - if (!vars.sensors.indoor.connected) { - vars.sensors.indoor.connected = true; - } + return result; + } - if (fabs(this->filteredIndoorTemp) < 0.1f) { - this->filteredIndoorTemp = rawTemp; + static bool isEmptyAddress(const uint8_t *addr, const uint8_t length = 8) { + bool result = true; - } else { - this->filteredIndoorTemp += (rawTemp - this->filteredIndoorTemp) * EXT_SENSORS_FILTER_K; + for (uint8_t i = 0; i < length; i++) { + if (addr[i] != 0) { + result = false; + break; } - - this->filteredIndoorTemp = floor(this->filteredIndoorTemp * 100) / 100; - this->indoorSensor->requestTemperatures(); - this->startIndoorConversionTime = millis(); } + + return result; } }; \ No newline at end of file diff --git a/src/Settings.h b/src/Settings.h index 7cf5650..7bb9dc5 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -27,12 +27,12 @@ struct Settings { uint8_t logLevel = DEFAULT_LOG_LEVEL; struct { - bool enable = DEFAULT_SERIAL_ENABLE; + bool enabled = DEFAULT_SERIAL_ENABLED; unsigned int baudrate = DEFAULT_SERIAL_BAUD; } serial; struct { - bool enable = DEFAULT_TELNET_ENABLE; + bool enabled = DEFAULT_TELNET_ENABLED; unsigned short port = DEFAULT_TELNET_PORT; } telnet; @@ -51,12 +51,12 @@ struct Settings { byte inGpio = DEFAULT_OT_IN_GPIO; byte outGpio = DEFAULT_OT_OUT_GPIO; byte rxLedGpio = DEFAULT_OT_RX_LED_GPIO; - unsigned int memberIdCode = 0; + uint8_t memberId = 0; + uint8_t flags = 0; uint8_t maxModulation = 100; - float pressureFactor = 1.0f; - float dhwFlowRateFactor = 1.0f; float minPower = 0.0f; float maxPower = 0.0f; + bool dhwPresent = true; bool summerWinterMode = false; bool heatingCh2Enabled = true; @@ -67,15 +67,10 @@ struct Settings { bool getMinMaxTemp = true; bool nativeHeatingControl = false; bool immergasFix = false; - - struct { - bool enable = false; - float factor = 0.1f; - } filterNumValues; } opentherm; struct { - bool enable = false; + bool enabled = DEFAULT_MQTT_ENABLED; char server[81] = DEFAULT_MQTT_SERVER; unsigned short port = DEFAULT_MQTT_PORT; char user[33] = DEFAULT_MQTT_USER; @@ -91,7 +86,7 @@ struct Settings { } emergency; struct { - bool enable = true; + bool enabled = true; bool turbo = false; float target = DEFAULT_HEATING_TARGET_TEMP; float hysteresis = 0.5f; @@ -101,14 +96,14 @@ struct Settings { } heating; struct { - bool enable = true; + bool enabled = true; float target = DEFAULT_DHW_TARGET_TEMP; byte minTemp = DEFAULT_DHW_MIN_TEMP; byte maxTemp = DEFAULT_DHW_MAX_TEMP; } dhw; struct { - bool enable = false; + bool enabled = false; float p_factor = 2.0f; float i_factor = 0.0055f; float d_factor = 0.0f; @@ -118,28 +113,12 @@ struct Settings { } pid; struct { - bool enable = false; + bool enabled = false; float n_factor = 0.7f; float k_factor = 3.0f; float t_factor = 2.0f; } equitherm; - struct { - struct { - SensorType type = SensorType::BOILER_OUTDOOR; - byte gpio = DEFAULT_SENSOR_OUTDOOR_GPIO; - uint8_t bleAddress[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - float offset = 0.0f; - } outdoor; - - struct { - SensorType type = SensorType::MANUAL; - byte gpio = DEFAULT_SENSOR_INDOOR_GPIO; - uint8_t bleAddress[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - float offset = 0.0f; - } indoor; - } sensors; - struct { bool use = false; byte gpio = DEFAULT_EXT_PUMP_GPIO; @@ -150,14 +129,14 @@ struct Settings { struct { struct { - bool enable = false; + bool enabled = false; byte gpio = GPIO_IS_NOT_CONFIGURED; byte invertState = false; unsigned short thresholdTime = 60; } input; struct { - bool enable = false; + bool enabled = false; byte gpio = GPIO_IS_NOT_CONFIGURED; byte invertState = false; unsigned short thresholdTime = 60; @@ -170,51 +149,95 @@ struct Settings { char validationValue[8] = SETTINGS_VALID_VALUE; } settings; -struct Variables { - struct { - bool otStatus = false; - bool emergency = false; - bool heating = false; - bool dhw = false; - bool flame = false; - bool fault = false; - bool diagnostic = false; - bool externalPump = false; - bool mqtt = false; - } states; +Sensors::Settings sensorsSettings[SENSORS_AMOUNT] = { + { + false, + "Indoor temp", + Sensors::Purpose::OUTDOOR_TEMP, + Sensors::Type::DALLAS_TEMP, + DEFAULT_SENSOR_OUTDOOR_GPIO + }, + { + false, + "Outdoor temp", + Sensors::Purpose::INDOOR_TEMP, + Sensors::Type::DALLAS_TEMP, + DEFAULT_SENSOR_INDOOR_GPIO + }, + { + true, + "Heating temp", + Sensors::Purpose::HEATING_TEMP, + Sensors::Type::OT_HEATING_TEMP, + }, + { + true, + "Heating return temp", + Sensors::Purpose::HEATING_RETURN_TEMP, + Sensors::Type::OT_HEATING_RETURN_TEMP, + }, + { + true, + "Heating setpoint temp", + Sensors::Purpose::TEMPERATURE, + Sensors::Type::HEATING_SETPOINT_TEMP, + }, + { + true, + "DHW temp", + Sensors::Purpose::DHW_TEMP, + Sensors::Type::OT_DHW_TEMP, + }, + { + true, + "DHW flow rate", + Sensors::Purpose::DHW_FLOW_RATE, + Sensors::Type::OT_DHW_FLOW_RATE, + }, + { + true, + "Exhaust temp", + Sensors::Purpose::EXHAUST_TEMP, + Sensors::Type::OT_EXHAUST_TEMP, + }, + { + true, + "Pressure", + Sensors::Purpose::PRESSURE, + Sensors::Type::OT_PRESSURE, + }, + { + true, + "Modulation level", + Sensors::Purpose::MODULATION_LEVEL, + Sensors::Type::OT_MODULATION_LEVEL, + }, + { + true, + "Power", + Sensors::Purpose::CURRENT_POWER, + Sensors::Type::OT_CURRENT_POWER, + } +}; +struct Variables { struct { - float modulation = 0.0f; - float pressure = 0.0f; - float dhwFlowRate = 0.0f; - float power = 0.0f; - byte faultCode = 0; - unsigned short diagnosticCode = 0; + bool connected = false; int8_t rssi = 0; + } network; - struct { - bool connected = false; - int8_t rssi = 0; - float battery = 0.0f; - float humidity = 0.0f; - } outdoor; + struct { + bool connected = false; + } mqtt; - struct { - bool connected = false; - int8_t rssi = 0; - float battery = 0.0f; - float humidity = 0.0f; - } indoor; - } sensors; + struct { + bool state = false; + } emergency; struct { - float indoor = 0.0f; - float outdoor = 0.0f; - float heating = 0.0f; - float heatingReturn = 0.0f; - float dhw = 0.0f; - float exhaust = 0.0f; - } temperatures; + bool state = false; + unsigned long lastEnableTime = 0; + } externalPump; struct { bool input = false; @@ -222,27 +245,104 @@ struct Variables { } cascadeControl; struct { - bool heatingEnabled = false; - byte heatingMinTemp = DEFAULT_HEATING_MIN_TEMP; - byte heatingMaxTemp = DEFAULT_HEATING_MAX_TEMP; - float heatingSetpoint = 0; - unsigned long extPumpLastEnableTime = 0; - byte dhwMinTemp = DEFAULT_DHW_MIN_TEMP; - byte dhwMaxTemp = DEFAULT_DHW_MAX_TEMP; - byte minModulation = 0; - byte maxModulation = 0; - uint8_t maxPower = 0; - uint8_t slaveMemberId = 0; - uint8_t slaveFlags = 0; - uint8_t slaveType = 0; - uint8_t slaveVersion = 0; - float slaveOtVersion = 0.0f; - uint8_t masterMemberId = 0; - uint8_t masterFlags = 0; - uint8_t masterType = 0; - uint8_t masterVersion = 0; - float masterOtVersion = 0; - } parameters; + uint8_t memberId = 0; + uint8_t flags = 0; + uint8_t type = 0; + uint8_t appVersion = 0; + float protocolVersion = 0.0f; + + struct { + bool blocking = false; + bool enabled = false; + bool indoorTempControl = false; + float targetTemp = 0.0f; + float currentTemp = 0.0f; + float returnTemp = 0.0f; + float indoorTemp = 0.0f; + float outdoorTemp = 0.0f; + float minTemp = 0.0f; + float maxTemp = 0.0f; + } heating; + + struct { + bool enabled = false; + float targetTemp = 0.0f; + float currentTemp = 0.0f; + float returnTemp = 0.0f; + } dhw; + + struct { + bool enabled = false; + float targetTemp = 0.0f; + } ch2; + } master; + + struct { + uint8_t memberId = 0; + uint8_t flags = 0; + uint8_t type = 0; + uint8_t appVersion = 0; + float protocolVersion = 0.0f; + + bool connected = false; + bool flame = false; + float pressure = 0.0f; + float exhaustTemp = 0.0f; + float heatExchangerTemp = 0.0f; + + struct { + bool active = false; + uint8_t code = 0; + } fault; + + struct { + bool active = false; + uint16_t code = 0; + } diag; + + struct { + uint8_t current = 0; + uint8_t min = 0; + uint8_t max = 100; + } modulation; + + struct { + float current = 0.0f; + float min = 0.0f; + float max = 0.0f; + } power; + + struct { + bool active = false; + bool enabled = false; + float targetTemp = 0.0f; + float currentTemp = 0.0f; + float returnTemp = 0.0f; + float indoorTemp = 0.0f; + float outdoorTemp = 0.0f; + uint8_t minTemp = DEFAULT_HEATING_MIN_TEMP; + uint8_t maxTemp = DEFAULT_HEATING_MAX_TEMP; + } heating; + + struct { + bool active = false; + bool enabled = false; + float targetTemp = 0.0f; + float currentTemp = 0.0f; + float currentTemp2 = 0.0f; + float returnTemp = 0.0f; + float flowRate = 0.0f; + uint8_t minTemp = DEFAULT_DHW_MIN_TEMP; + uint8_t maxTemp = DEFAULT_DHW_MAX_TEMP; + } dhw; + + struct { + bool enabled = false; + float targetTemp = 0.0f; + float currentTemp = 0.0f; + float indoorTemp = 0.0f; + } ch2; + } slave; struct { bool restart = false; diff --git a/src/defines.h b/src/defines.h index 599e509..35dd000 100644 --- a/src/defines.h +++ b/src/defines.h @@ -2,10 +2,6 @@ #define PROJECT_REPO "https://github.com/Laxilef/OTGateway" #define MQTT_RECONNECT_INTERVAL 15000 - -#define EXT_SENSORS_INTERVAL 5000 -#define EXT_SENSORS_FILTER_K 0.15 - #define CONFIG_URL "http://%s/" #define SETTINGS_VALID_VALUE "stvalid" // only 8 chars! #define GPIO_IS_NOT_CONFIGURED 0xff @@ -22,6 +18,13 @@ #define THERMOSTAT_INDOOR_MIN_TEMP 5 #define THERMOSTAT_INDOOR_MAX_TEMP 30 +#define DEFAULT_NTC_NOMINAL_RESISTANCE 10000.0f +#define DEFAULT_NTC_NOMINAL_TEMP 25.0f +#define DEFAULT_NTC_REF_RESISTANCE 10000.0f +#define DEFAULT_NTC_BETA_FACTOR 3950.0f +#define DEFAULT_NTC_VREF 3300.0f +#define DEFAULT_NTC_VLOW_TRESHOLD 25.0f + #ifndef BUILD_VERSION #define BUILD_VERSION "0.0.0" #endif @@ -30,16 +33,16 @@ #define BUILD_ENV "undefined" #endif -#ifndef DEFAULT_SERIAL_ENABLE - #define DEFAULT_SERIAL_ENABLE true +#ifndef DEFAULT_SERIAL_ENABLED + #define DEFAULT_SERIAL_ENABLED true #endif #ifndef DEFAULT_SERIAL_BAUD #define DEFAULT_SERIAL_BAUD 115200 #endif -#ifndef DEFAULT_TELNET_ENABLE - #define DEFAULT_TELNET_ENABLE true +#ifndef DEFAULT_TELNET_ENABLED + #define DEFAULT_TELNET_ENABLED true #endif #ifndef DEFAULT_TELNET_PORT @@ -86,6 +89,10 @@ #define DEFAULT_PORTAL_PASSWORD "" #endif +#ifndef DEFAULT_MQTT_ENABLED + #define DEFAULT_MQTT_ENABLED false +#endif + #ifndef DEFAULT_MQTT_SERVER #define DEFAULT_MQTT_SERVER "" #endif @@ -130,6 +137,10 @@ #define DEFAULT_SENSOR_INDOOR_GPIO GPIO_IS_NOT_CONFIGURED #endif +#ifndef SENSORS_AMOUNT + #define SENSORS_AMOUNT 20 +#endif + #ifndef DEFAULT_EXT_PUMP_GPIO #define DEFAULT_EXT_PUMP_GPIO GPIO_IS_NOT_CONFIGURED #endif @@ -146,17 +157,9 @@ #define GPIO_IS_VALID(gpioNum) (gpioNum != GPIO_IS_NOT_CONFIGURED && GPIO_IS_VALID_GPIO(gpioNum)) -enum class SensorType : byte { - BOILER_OUTDOOR = 0, - BOILER_RETURN = 4, - MANUAL = 1, - DS18B20 = 2, - BLUETOOTH = 3 -}; - -enum class UnitSystem : byte { - METRIC, - IMPERIAL +enum class UnitSystem : uint8_t { + METRIC = 0, + IMPERIAL = 1 }; char buffer[255]; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index c5c2f94..6e2663d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,13 +1,15 @@ #include -#include "defines.h" -#include "strings.h" -#include "CrashRecorder.h" #include #include #include #include #include #include + +#include "defines.h" +#include "strings.h" +#include "CrashRecorder.h" +#include "Sensors.h" #include "Settings.h" #include "utils.h" @@ -31,10 +33,13 @@ using namespace NetworkUtils; // Vars -FileData fsNetworkSettings(&LittleFS, "/network.conf", 'n', &networkSettings, sizeof(networkSettings), 1000); -FileData fsSettings(&LittleFS, "/settings.conf", 's', &settings, sizeof(settings), 60000); ESPTelnetStream* telnetStream = nullptr; NetworkMgr* network = nullptr; +Sensors::Result sensorsResults[SENSORS_AMOUNT]; + +FileData fsNetworkSettings(&LittleFS, "/network.conf", 'n', &networkSettings, sizeof(networkSettings), 1000); +FileData fsSettings(&LittleFS, "/settings.conf", 's', &settings, sizeof(settings), 60000); +FileData fsSensorsSettings(&LittleFS, "/sensors.conf", 'e', &sensorsSettings, sizeof(sensorsSettings), 60000); // Tasks MqttTask* tMqtt; @@ -47,6 +52,9 @@ MainTask* tMain; void setup() { CrashRecorder::init(); + Sensors::setMaxSensors(SENSORS_AMOUNT); + Sensors::settings = sensorsSettings; + Sensors::results = sensorsResults; LittleFS.begin(); Log.setLevel(TinyLogger::Level::VERBOSE); @@ -64,10 +72,14 @@ void setup() { }); Serial.begin(115200); + #if ARDUINO_USB_MODE + Serial.setTxBufferSize(512); + #endif Log.addStream(&Serial); Log.print("\n\n\r"); - // network settings + // + // Network settings switch (fsNetworkSettings.read()) { case FD_FS_ERR: Log.swarningln(FPSTR(L_NETWORK_SETTINGS), F("Filesystem error, load default")); @@ -86,7 +98,27 @@ void setup() { break; } - // settings + network = (new NetworkMgr) + ->setHostname(networkSettings.hostname) + ->setStaCredentials( + strlen(networkSettings.sta.ssid) ? networkSettings.sta.ssid : nullptr, + strlen(networkSettings.sta.password) ? networkSettings.sta.password : nullptr, + networkSettings.sta.channel + )->setApCredentials( + strlen(networkSettings.ap.ssid) ? networkSettings.ap.ssid : nullptr, + strlen(networkSettings.ap.password) ? networkSettings.ap.password : nullptr, + networkSettings.ap.channel + ) + ->setUseDhcp(networkSettings.useDhcp) + ->setStaticConfig( + networkSettings.staticConfig.ip, + networkSettings.staticConfig.gateway, + networkSettings.staticConfig.subnet, + networkSettings.staticConfig.dns + ); + + // + // Settings switch (fsSettings.read()) { case FD_FS_ERR: Log.swarningln(FPSTR(L_SETTINGS), F("Filesystem error, load default")); @@ -112,8 +144,8 @@ void setup() { break; } - // logs - if (!settings.system.serial.enable) { + // Logs settings + if (!settings.system.serial.enabled) { Serial.end(); Log.clearStreams(); @@ -125,7 +157,7 @@ void setup() { Log.addStream(&Serial); } - if (settings.system.telnet.enable) { + if (settings.system.telnet.enabled) { telnetStream = new ESPTelnetStream; telnetStream->setKeepAliveInterval(500); Log.addStream(telnetStream); @@ -135,34 +167,34 @@ void setup() { Log.setLevel(static_cast(settings.system.logLevel)); } - // network - network = (new NetworkMgr) - ->setHostname(networkSettings.hostname) - ->setStaCredentials( - strlen(networkSettings.sta.ssid) ? networkSettings.sta.ssid : nullptr, - strlen(networkSettings.sta.password) ? networkSettings.sta.password : nullptr, - networkSettings.sta.channel - )->setApCredentials( - strlen(networkSettings.ap.ssid) ? networkSettings.ap.ssid : nullptr, - strlen(networkSettings.ap.password) ? networkSettings.ap.password : nullptr, - networkSettings.ap.channel - ) - ->setUseDhcp(networkSettings.useDhcp) - ->setStaticConfig( - networkSettings.staticConfig.ip, - networkSettings.staticConfig.gateway, - networkSettings.staticConfig.subnet, - networkSettings.staticConfig.dns - ); + // + // Sensors settings + switch (fsSensorsSettings.read()) { + case FD_FS_ERR: + Log.swarningln(FPSTR(L_SENSORS), F("Filesystem error, load default")); + break; + case FD_FILE_ERR: + Log.swarningln(FPSTR(L_SENSORS), F("Bad data, load default")); + break; + case FD_WRITE: + Log.sinfoln(FPSTR(L_SENSORS), F("Not found, load default")); + break; + case FD_ADD: + case FD_READ: + Log.sinfoln(FPSTR(L_SENSORS), F("Loaded")); + default: + break; + } - // tasks + // + // Make tasks tMqtt = new MqttTask(false, 500); Scheduler.start(tMqtt); tOt = new OpenThermTask(true, 750); Scheduler.start(tOt); - tSensors = new SensorsTask(true, EXT_SENSORS_INTERVAL); + tSensors = new SensorsTask(true, 1000); Scheduler.start(tSensors); tRegulator = new RegulatorTask(true, 10000); diff --git a/src/strings.h b/src/strings.h index ba5e06b..60776c6 100644 --- a/src/strings.h +++ b/src/strings.h @@ -15,15 +15,20 @@ const char L_PORTAL_CAPTIVE[] PROGMEM = "PORTAL.CAPTIVE"; const char L_PORTAL_OTA[] PROGMEM = "PORTAL.OTA"; const char L_MAIN[] PROGMEM = "MAIN"; const char L_MQTT[] PROGMEM = "MQTT"; +const char L_MQTT_HA[] PROGMEM = "MQTT.HA"; const char L_MQTT_MSG[] PROGMEM = "MQTT.MSG"; const char L_OT[] PROGMEM = "OT"; const char L_OT_DHW[] PROGMEM = "OT.DHW"; const char L_OT_HEATING[] PROGMEM = "OT.HEATING"; -const char L_SENSORS_OUTDOOR[] PROGMEM = "SENSORS.OUTDOOR"; -const char L_SENSORS_INDOOR[] PROGMEM = "SENSORS.INDOOR"; +const char L_OT_CH2[] PROGMEM = "OT.CH2"; +const char L_SENSORS[] PROGMEM = "SENSORS"; +const char L_SENSORS_SETTINGS[] PROGMEM = "SENSORS.SETTINGS"; +const char L_SENSORS_DALLAS[] PROGMEM = "SENSORS.DALLAS"; +const char L_SENSORS_NTC[] PROGMEM = "SENSORS.NTC"; const char L_SENSORS_BLE[] PROGMEM = "SENSORS.BLE"; const char L_REGULATOR[] PROGMEM = "REGULATOR"; const char L_REGULATOR_PID[] PROGMEM = "REGULATOR.PID"; const char L_REGULATOR_EQUITHERM[] PROGMEM = "REGULATOR.EQUITHERM"; const char L_CASCADE_INPUT[] PROGMEM = "CASCADE.INPUT"; -const char L_CASCADE_OUTPUT[] PROGMEM = "CASCADE.OUTPUT"; \ No newline at end of file +const char L_CASCADE_OUTPUT[] PROGMEM = "CASCADE.OUTPUT"; +const char L_EXTPUMP[] PROGMEM = "EXTPUMP"; \ No newline at end of file diff --git a/src/utils.h b/src/utils.h index 092139b..9f7f700 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,5 +1,11 @@ #include +inline bool isDigit(const char* ptr) { + char* endPtr; + strtol(ptr, &endPtr, 10); + return *endPtr == 0; +} + inline float liter2gallon(float value) { return value / 4.546091879f; } @@ -11,7 +17,7 @@ inline float gallon2liter(float value) { float convertVolume(float value, const UnitSystem unitFrom, const UnitSystem unitTo) { if (unitFrom == UnitSystem::METRIC && unitTo == UnitSystem::IMPERIAL) { value = liter2gallon(value); - + } else if (unitFrom == UnitSystem::IMPERIAL && unitTo == UnitSystem::METRIC) { value = gallon2liter(value); } @@ -30,7 +36,7 @@ inline float psi2bar(float value) { float convertPressure(float value, const UnitSystem unitFrom, const UnitSystem unitTo) { if (unitFrom == UnitSystem::METRIC && unitTo == UnitSystem::IMPERIAL) { value = bar2psi(value); - + } else if (unitFrom == UnitSystem::IMPERIAL && unitTo == UnitSystem::METRIC) { value = psi2bar(value); } @@ -49,7 +55,7 @@ inline float f2c(float value) { float convertTemp(float value, const UnitSystem unitFrom, const UnitSystem unitTo) { if (unitFrom == UnitSystem::METRIC && unitTo == UnitSystem::IMPERIAL) { value = c2f(value); - + } else if (unitFrom == UnitSystem::IMPERIAL && unitTo == UnitSystem::METRIC) { value = f2c(value); } @@ -61,7 +67,7 @@ inline bool isValidTemp(const float value, UnitSystem unit, const float min = 0. return value >= convertTemp(min, minMaxUnit, unit) && value <= convertTemp(max, minMaxUnit, unit); } -double roundd(double value, uint8_t decimals = 2) { +float roundf(float value, uint8_t decimals = 2) { if (decimals == 0) { return (int)(value + 0.5); @@ -69,7 +75,7 @@ double roundd(double value, uint8_t decimals = 2) { return 0.0; } - double multiplier = pow10(decimals); + float multiplier = pow10(decimals); value += 0.5 / multiplier * (value < 0 ? -1 : 1); return (int)(value * multiplier) / multiplier; } @@ -86,26 +92,26 @@ inline size_t getTotalHeap() { size_t getFreeHeap(bool getMinValue = false) { #if defined(ARDUINO_ARCH_ESP32) - return getMinValue ? ESP.getMinFreeHeap() : ESP.getFreeHeap(); - + return getMinValue ? ESP.getMinFreeHeap() : ESP.getFreeHeap(); + #elif defined(ARDUINO_ARCH_ESP8266) - static size_t minValue = 0; - size_t value = ESP.getFreeHeap(); - - if (value < minValue || minValue == 0) { - minValue = value; - } + static size_t minValue = 0; + size_t value = ESP.getFreeHeap(); - return getMinValue ? minValue : value; + if (value < minValue || minValue == 0) { + minValue = value; + } + + return getMinValue ? minValue : value; #else - return 0; + return 0; #endif } size_t getMaxFreeBlockHeap(bool getMinValue = false) { static size_t minValue = 0; size_t value = 0; - + #if defined(ARDUINO_ARCH_ESP32) value = ESP.getMaxAllocHeap(); @@ -134,7 +140,7 @@ String getResetReason() { #if defined(ARDUINO_ARCH_ESP8266) value = ESP.getResetReason(); #elif defined(ARDUINO_ARCH_ESP32) - switch(esp_reset_reason()) { + switch (esp_reset_reason()) { case ESP_RST_POWERON: value = F("Reset due to power-on event"); break; @@ -189,14 +195,14 @@ String getResetReason() { } template -void arr2str(String &str, T arr[], size_t length) { +void arr2str(String& str, T arr[], size_t length) { char buffer[12]; for (size_t i = 0; i < length; i++) { auto addr = arr[i]; if (!addr) { continue; } - + sprintf(buffer, "0x%08X ", addr); str.concat(buffer); } @@ -229,7 +235,7 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { if (!src["hostname"].isNull()) { String value = src["hostname"].as(); - if (value.length() < sizeof(dst.hostname)) { + if (value.length() < sizeof(dst.hostname) && !value.equals(dst.hostname)) { strcpy(dst.hostname, value.c_str()); changed = true; } @@ -246,7 +252,7 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { if (!src["staticConfig"]["ip"].isNull()) { String value = src["staticConfig"]["ip"].as(); - if (value.length() < sizeof(dst.staticConfig.ip)) { + if (value.length() < sizeof(dst.staticConfig.ip) && !value.equals(dst.staticConfig.ip)) { strcpy(dst.staticConfig.ip, value.c_str()); changed = true; } @@ -255,7 +261,7 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { if (!src["staticConfig"]["gateway"].isNull()) { String value = src["staticConfig"]["gateway"].as(); - if (value.length() < sizeof(dst.staticConfig.gateway)) { + if (value.length() < sizeof(dst.staticConfig.gateway) && !value.equals(dst.staticConfig.gateway)) { strcpy(dst.staticConfig.gateway, value.c_str()); changed = true; } @@ -264,7 +270,7 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { if (!src["staticConfig"]["subnet"].isNull()) { String value = src["staticConfig"]["subnet"].as(); - if (value.length() < sizeof(dst.staticConfig.subnet)) { + if (value.length() < sizeof(dst.staticConfig.subnet) && !value.equals(dst.staticConfig.subnet)) { strcpy(dst.staticConfig.subnet, value.c_str()); changed = true; } @@ -273,7 +279,7 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { if (!src["staticConfig"]["dns"].isNull()) { String value = src["staticConfig"]["dns"].as(); - if (value.length() < sizeof(dst.staticConfig.dns)) { + if (value.length() < sizeof(dst.staticConfig.dns) && !value.equals(dst.staticConfig.dns)) { strcpy(dst.staticConfig.dns, value.c_str()); changed = true; } @@ -284,7 +290,7 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { if (!src["ap"]["ssid"].isNull()) { String value = src["ap"]["ssid"].as(); - if (value.length() < sizeof(dst.ap.ssid)) { + if (value.length() < sizeof(dst.ap.ssid) && !value.equals(dst.ap.ssid)) { strcpy(dst.ap.ssid, value.c_str()); changed = true; } @@ -293,7 +299,7 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { if (!src["ap"]["password"].isNull()) { String value = src["ap"]["password"].as(); - if (value.length() < sizeof(dst.ap.password)) { + if (value.length() < sizeof(dst.ap.password) && !value.equals(dst.ap.password)) { strcpy(dst.ap.password, value.c_str()); changed = true; } @@ -313,7 +319,7 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { if (!src["sta"]["ssid"].isNull()) { String value = src["sta"]["ssid"].as(); - if (value.length() < sizeof(dst.sta.ssid)) { + if (value.length() < sizeof(dst.sta.ssid) && !value.equals(dst.sta.ssid)) { strcpy(dst.sta.ssid, value.c_str()); changed = true; } @@ -322,7 +328,7 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { if (!src["sta"]["password"].isNull()) { String value = src["sta"]["password"].as(); - if (value.length() < sizeof(dst.sta.password)) { + if (value.length() < sizeof(dst.sta.password) && !value.equals(dst.sta.password)) { strcpy(dst.sta.password, value.c_str()); changed = true; } @@ -343,27 +349,26 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) { if (!safe) { dst["system"]["logLevel"] = static_cast(src.system.logLevel); - dst["system"]["serial"]["enable"] = src.system.serial.enable; + dst["system"]["serial"]["enable"] = src.system.serial.enabled; dst["system"]["serial"]["baudrate"] = src.system.serial.baudrate; - dst["system"]["telnet"]["enable"] = src.system.telnet.enable; + dst["system"]["telnet"]["enable"] = src.system.telnet.enabled; dst["system"]["telnet"]["port"] = src.system.telnet.port; - dst["system"]["unitSystem"] = static_cast(src.system.unitSystem); + dst["system"]["unitSystem"] = static_cast(src.system.unitSystem); dst["system"]["statusLedGpio"] = src.system.statusLedGpio; dst["portal"]["auth"] = src.portal.auth; dst["portal"]["login"] = src.portal.login; dst["portal"]["password"] = src.portal.password; - dst["opentherm"]["unitSystem"] = static_cast(src.opentherm.unitSystem); + dst["opentherm"]["unitSystem"] = static_cast(src.opentherm.unitSystem); dst["opentherm"]["inGpio"] = src.opentherm.inGpio; dst["opentherm"]["outGpio"] = src.opentherm.outGpio; dst["opentherm"]["rxLedGpio"] = src.opentherm.rxLedGpio; - dst["opentherm"]["memberIdCode"] = src.opentherm.memberIdCode; + dst["opentherm"]["memberId"] = src.opentherm.memberId; + dst["opentherm"]["flags"] = src.opentherm.flags; dst["opentherm"]["maxModulation"] = src.opentherm.maxModulation; - dst["opentherm"]["pressureFactor"] = roundd(src.opentherm.pressureFactor, 2); - dst["opentherm"]["dhwFlowRateFactor"] = roundd(src.opentherm.dhwFlowRateFactor, 2); - dst["opentherm"]["minPower"] = roundd(src.opentherm.minPower, 2); - dst["opentherm"]["maxPower"] = roundd(src.opentherm.maxPower, 2); + dst["opentherm"]["minPower"] = roundf(src.opentherm.minPower, 2); + dst["opentherm"]["maxPower"] = roundf(src.opentherm.maxPower, 2); dst["opentherm"]["dhwPresent"] = src.opentherm.dhwPresent; dst["opentherm"]["summerWinterMode"] = src.opentherm.summerWinterMode; dst["opentherm"]["heatingCh2Enabled"] = src.opentherm.heatingCh2Enabled; @@ -374,10 +379,8 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) { dst["opentherm"]["getMinMaxTemp"] = src.opentherm.getMinMaxTemp; dst["opentherm"]["nativeHeatingControl"] = src.opentherm.nativeHeatingControl; dst["opentherm"]["immergasFix"] = src.opentherm.immergasFix; - dst["opentherm"]["filterNumValues"]["enable"] = src.opentherm.filterNumValues.enable; - dst["opentherm"]["filterNumValues"]["factor"] = roundd(src.opentherm.filterNumValues.factor, 2); - dst["mqtt"]["enable"] = src.mqtt.enable; + dst["mqtt"]["enable"] = src.mqtt.enabled; dst["mqtt"]["server"] = src.mqtt.server; dst["mqtt"]["port"] = src.mqtt.port; dst["mqtt"]["user"] = src.mqtt.user; @@ -386,82 +389,49 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) { dst["mqtt"]["interval"] = src.mqtt.interval; dst["mqtt"]["homeAssistantDiscovery"] = src.mqtt.homeAssistantDiscovery; - dst["emergency"]["target"] = roundd(src.emergency.target, 2); + dst["emergency"]["target"] = roundf(src.emergency.target, 2); dst["emergency"]["tresholdTime"] = src.emergency.tresholdTime; } - dst["heating"]["enable"] = src.heating.enable; + dst["heating"]["enable"] = src.heating.enabled; dst["heating"]["turbo"] = src.heating.turbo; - dst["heating"]["target"] = roundd(src.heating.target, 2); - dst["heating"]["hysteresis"] = roundd(src.heating.hysteresis, 2); - dst["heating"]["turboFactor"] = roundd(src.heating.turboFactor, 2); + dst["heating"]["target"] = roundf(src.heating.target, 2); + dst["heating"]["hysteresis"] = roundf(src.heating.hysteresis, 3); + dst["heating"]["turboFactor"] = roundf(src.heating.turboFactor, 3); dst["heating"]["minTemp"] = src.heating.minTemp; dst["heating"]["maxTemp"] = src.heating.maxTemp; - dst["dhw"]["enable"] = src.dhw.enable; - dst["dhw"]["target"] = roundd(src.dhw.target, 1); + dst["dhw"]["enable"] = src.dhw.enabled; + dst["dhw"]["target"] = roundf(src.dhw.target, 1); dst["dhw"]["minTemp"] = src.dhw.minTemp; dst["dhw"]["maxTemp"] = src.dhw.maxTemp; - dst["equitherm"]["enable"] = src.equitherm.enable; - dst["equitherm"]["n_factor"] = roundd(src.equitherm.n_factor, 3); - dst["equitherm"]["k_factor"] = roundd(src.equitherm.k_factor, 3); - dst["equitherm"]["t_factor"] = roundd(src.equitherm.t_factor, 3); + dst["equitherm"]["enable"] = src.equitherm.enabled; + dst["equitherm"]["n_factor"] = roundf(src.equitherm.n_factor, 3); + dst["equitherm"]["k_factor"] = roundf(src.equitherm.k_factor, 3); + dst["equitherm"]["t_factor"] = roundf(src.equitherm.t_factor, 3); - dst["pid"]["enable"] = src.pid.enable; - dst["pid"]["p_factor"] = roundd(src.pid.p_factor, 3); - dst["pid"]["i_factor"] = roundd(src.pid.i_factor, 4); - dst["pid"]["d_factor"] = roundd(src.pid.d_factor, 1); + dst["pid"]["enable"] = src.pid.enabled; + dst["pid"]["p_factor"] = roundf(src.pid.p_factor, 3); + dst["pid"]["i_factor"] = roundf(src.pid.i_factor, 4); + dst["pid"]["d_factor"] = roundf(src.pid.d_factor, 1); dst["pid"]["dt"] = src.pid.dt; dst["pid"]["minTemp"] = src.pid.minTemp; dst["pid"]["maxTemp"] = src.pid.maxTemp; - dst["sensors"]["outdoor"]["type"] = static_cast(src.sensors.outdoor.type); - dst["sensors"]["outdoor"]["gpio"] = src.sensors.outdoor.gpio; - - char bleAddress[18]; - sprintf( - bleAddress, - "%02x:%02x:%02x:%02x:%02x:%02x", - src.sensors.outdoor.bleAddress[0], - src.sensors.outdoor.bleAddress[1], - src.sensors.outdoor.bleAddress[2], - src.sensors.outdoor.bleAddress[3], - src.sensors.outdoor.bleAddress[4], - src.sensors.outdoor.bleAddress[5] - ); - dst["sensors"]["outdoor"]["bleAddress"] = String(bleAddress); - dst["sensors"]["outdoor"]["offset"] = roundd(src.sensors.outdoor.offset, 2); - - dst["sensors"]["indoor"]["type"] = static_cast(src.sensors.indoor.type); - dst["sensors"]["indoor"]["gpio"] = src.sensors.indoor.gpio; - - sprintf( - bleAddress, - "%02x:%02x:%02x:%02x:%02x:%02x", - src.sensors.indoor.bleAddress[0], - src.sensors.indoor.bleAddress[1], - src.sensors.indoor.bleAddress[2], - src.sensors.indoor.bleAddress[3], - src.sensors.indoor.bleAddress[4], - src.sensors.indoor.bleAddress[5] - ); - dst["sensors"]["indoor"]["bleAddress"] = String(bleAddress); - dst["sensors"]["indoor"]["offset"] = roundd(src.sensors.indoor.offset, 2); - if (!safe) { dst["externalPump"]["use"] = src.externalPump.use; dst["externalPump"]["gpio"] = src.externalPump.gpio; - dst["externalPump"]["postCirculationTime"] = roundd(src.externalPump.postCirculationTime / 60, 0); - dst["externalPump"]["antiStuckInterval"] = roundd(src.externalPump.antiStuckInterval / 86400, 0); - dst["externalPump"]["antiStuckTime"] = roundd(src.externalPump.antiStuckTime / 60, 0); + dst["externalPump"]["postCirculationTime"] = roundf(src.externalPump.postCirculationTime / 60, 0); + dst["externalPump"]["antiStuckInterval"] = roundf(src.externalPump.antiStuckInterval / 86400, 0); + dst["externalPump"]["antiStuckTime"] = roundf(src.externalPump.antiStuckTime / 60, 0); - dst["cascadeControl"]["input"]["enable"] = src.cascadeControl.input.enable; + dst["cascadeControl"]["input"]["enable"] = src.cascadeControl.input.enabled; dst["cascadeControl"]["input"]["gpio"] = src.cascadeControl.input.gpio; dst["cascadeControl"]["input"]["invertState"] = src.cascadeControl.input.invertState; dst["cascadeControl"]["input"]["thresholdTime"] = src.cascadeControl.input.thresholdTime; - dst["cascadeControl"]["output"]["enable"] = src.cascadeControl.output.enable; + dst["cascadeControl"]["output"]["enable"] = src.cascadeControl.output.enabled; dst["cascadeControl"]["output"]["gpio"] = src.cascadeControl.output.gpio; dst["cascadeControl"]["output"]["invertState"] = src.cascadeControl.output.invertState; dst["cascadeControl"]["output"]["thresholdTime"] = src.cascadeControl.output.thresholdTime; @@ -492,8 +462,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (src["system"]["serial"]["enable"].is()) { bool value = src["system"]["serial"]["enable"].as(); - if (value != dst.system.serial.enable) { - dst.system.serial.enable = value; + if (value != dst.system.serial.enabled) { + dst.system.serial.enabled = value; changed = true; } } @@ -511,9 +481,9 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (src["system"]["telnet"]["enable"].is()) { bool value = src["system"]["telnet"]["enable"].as(); - - if (value != dst.system.telnet.enable) { - dst.system.telnet.enable = value; + + if (value != dst.system.telnet.enabled) { + dst.system.telnet.enabled = value; changed = true; } } @@ -528,18 +498,18 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } if (!src["system"]["unitSystem"].isNull()) { - byte value = src["system"]["unitSystem"].as(); + uint8_t value = src["system"]["unitSystem"].as(); UnitSystem prevUnitSystem = dst.system.unitSystem; switch (value) { - case static_cast(UnitSystem::METRIC): + case static_cast(UnitSystem::METRIC): if (dst.system.unitSystem != UnitSystem::METRIC) { dst.system.unitSystem = UnitSystem::METRIC; changed = true; } break; - case static_cast(UnitSystem::IMPERIAL): + case static_cast(UnitSystem::IMPERIAL): if (dst.system.unitSystem != UnitSystem::IMPERIAL) { dst.system.unitSystem = UnitSystem::IMPERIAL; changed = true; @@ -570,7 +540,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false dst.system.statusLedGpio = GPIO_IS_NOT_CONFIGURED; changed = true; } - + } else { unsigned char value = src["system"]["statusLedGpio"].as(); @@ -595,7 +565,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["portal"]["login"].isNull()) { String value = src["portal"]["login"].as(); - if (value.length() < sizeof(dst.portal.login) && !String(dst.portal.login).equals(value)) { + if (value.length() < sizeof(dst.portal.login) && !value.equals(dst.portal.login)) { strcpy(dst.portal.login, value.c_str()); changed = true; } @@ -604,7 +574,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["portal"]["password"].isNull()) { String value = src["portal"]["password"].as(); - if (value.length() < sizeof(dst.portal.password) && !String(dst.portal.password).equals(value)) { + if (value.length() < sizeof(dst.portal.password) && !value.equals(dst.portal.password)) { strcpy(dst.portal.password, value.c_str()); changed = true; } @@ -613,17 +583,17 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // opentherm if (!src["opentherm"]["unitSystem"].isNull()) { - byte value = src["opentherm"]["unitSystem"].as(); + uint8_t value = src["opentherm"]["unitSystem"].as(); switch (value) { - case static_cast(UnitSystem::METRIC): + case static_cast(UnitSystem::METRIC): if (dst.opentherm.unitSystem != UnitSystem::METRIC) { dst.opentherm.unitSystem = UnitSystem::METRIC; changed = true; } break; - case static_cast(UnitSystem::IMPERIAL): + case static_cast(UnitSystem::IMPERIAL): if (dst.opentherm.unitSystem != UnitSystem::IMPERIAL) { dst.opentherm.unitSystem = UnitSystem::IMPERIAL; changed = true; @@ -641,7 +611,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false dst.opentherm.inGpio = GPIO_IS_NOT_CONFIGURED; changed = true; } - + } else { unsigned char value = src["opentherm"]["inGpio"].as(); @@ -651,14 +621,14 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } } - + if (!src["opentherm"]["outGpio"].isNull()) { if (src["opentherm"]["outGpio"].is() && src["opentherm"]["outGpio"].as().size() == 0) { if (dst.opentherm.outGpio != GPIO_IS_NOT_CONFIGURED) { dst.opentherm.outGpio = GPIO_IS_NOT_CONFIGURED; changed = true; } - + } else { unsigned char value = src["opentherm"]["outGpio"].as(); @@ -675,7 +645,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false dst.opentherm.rxLedGpio = GPIO_IS_NOT_CONFIGURED; changed = true; } - + } else { unsigned char value = src["opentherm"]["rxLedGpio"].as(); @@ -686,38 +656,29 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["opentherm"]["memberIdCode"].isNull()) { - unsigned int value = src["opentherm"]["memberIdCode"].as(); - - if (value >= 0 && value < 65536 && value != dst.opentherm.memberIdCode) { - dst.opentherm.memberIdCode = value; - changed = true; - } - } - - if (!src["opentherm"]["maxModulation"].isNull()) { - unsigned char value = src["opentherm"]["maxModulation"].as(); + if (!src["opentherm"]["memberId"].isNull()) { + auto value = src["opentherm"]["memberId"].as(); - if (value > 0 && value <= 100 && value != dst.opentherm.maxModulation) { - dst.opentherm.maxModulation = value; + if (value != dst.opentherm.memberId) { + dst.opentherm.memberId = value; changed = true; } } - if (!src["opentherm"]["pressureFactor"].isNull()) { - float value = src["opentherm"]["pressureFactor"].as(); + if (!src["opentherm"]["flags"].isNull()) { + auto value = src["opentherm"]["flags"].as(); - if (value > 0 && value <= 100 && fabs(value - dst.opentherm.pressureFactor) > 0.0001f) { - dst.opentherm.pressureFactor = roundd(value, 2); + if (value != dst.opentherm.flags) { + dst.opentherm.flags = value; changed = true; } } - if (!src["opentherm"]["dhwFlowRateFactor"].isNull()) { - float value = src["opentherm"]["dhwFlowRateFactor"].as(); + if (!src["opentherm"]["maxModulation"].isNull()) { + unsigned char value = src["opentherm"]["maxModulation"].as(); - if (value > 0 && value <= 100 && fabs(value - dst.opentherm.dhwFlowRateFactor) > 0.0001f) { - dst.opentherm.dhwFlowRateFactor = roundd(value, 2); + if (value > 0 && value <= 100 && value != dst.opentherm.maxModulation) { + dst.opentherm.maxModulation = value; changed = true; } } @@ -725,8 +686,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["opentherm"]["minPower"].isNull()) { float value = src["opentherm"]["minPower"].as(); - if (value >= 0 && value <= 1000 && fabs(value - dst.opentherm.minPower) > 0.0001f) { - dst.opentherm.minPower = roundd(value, 2); + if (value >= 0 && value <= 1000 && fabsf(value - dst.opentherm.minPower) > 0.0001f) { + dst.opentherm.minPower = roundf(value, 2); changed = true; } } @@ -734,26 +695,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["opentherm"]["maxPower"].isNull()) { float value = src["opentherm"]["maxPower"].as(); - if (value >= 0 && value <= 1000 && fabs(value - dst.opentherm.maxPower) > 0.0001f) { - dst.opentherm.maxPower = roundd(value, 2); - changed = true; - } - } - - if (src["opentherm"]["filterNumValues"]["enable"].is()) { - bool value = src["opentherm"]["filterNumValues"]["enable"].as(); - - if (value != dst.opentherm.filterNumValues.enable) { - dst.opentherm.filterNumValues.enable = value; - changed = true; - } - } - - if (!src["opentherm"]["filterNumValues"]["factor"].isNull()) { - float value = src["opentherm"]["filterNumValues"]["factor"].as(); - - if (value > 0 && value <= 1 && fabs(value - dst.opentherm.filterNumValues.factor) > 0.0001f) { - dst.opentherm.filterNumValues.factor = roundd(value, 2); + if (value >= 0 && value <= 1000 && fabsf(value - dst.opentherm.maxPower) > 0.0001f) { + dst.opentherm.maxPower = roundf(value, 2); changed = true; } } @@ -855,8 +798,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false dst.opentherm.nativeHeatingControl = value; if (value) { - dst.equitherm.enable = false; - dst.pid.enable = false; + dst.equitherm.enabled = false; + dst.pid.enabled = false; } changed = true; @@ -877,16 +820,16 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (src["mqtt"]["enable"].is()) { bool value = src["mqtt"]["enable"].as(); - if (value != dst.mqtt.enable) { - dst.mqtt.enable = value; + if (value != dst.mqtt.enabled) { + dst.mqtt.enabled = value; changed = true; } } - + if (!src["mqtt"]["server"].isNull()) { String value = src["mqtt"]["server"].as(); - if (value.length() < sizeof(dst.mqtt.server) && !String(dst.mqtt.server).equals(value)) { + if (value.length() < sizeof(dst.mqtt.server) && !value.equals(dst.mqtt.server)) { strcpy(dst.mqtt.server, value.c_str()); changed = true; } @@ -904,7 +847,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["mqtt"]["user"].isNull()) { String value = src["mqtt"]["user"].as(); - if (value.length() < sizeof(dst.mqtt.user) && !String(dst.mqtt.user).equals(value)) { + if (value.length() < sizeof(dst.mqtt.user) && !value.equals(dst.mqtt.user)) { strcpy(dst.mqtt.user, value.c_str()); changed = true; } @@ -913,7 +856,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["mqtt"]["password"].isNull()) { String value = src["mqtt"]["password"].as(); - if (value.length() < sizeof(dst.mqtt.password) && !String(dst.mqtt.password).equals(value)) { + if (value.length() < sizeof(dst.mqtt.password) && !value.equals(dst.mqtt.password)) { strcpy(dst.mqtt.password, value.c_str()); changed = true; } @@ -922,7 +865,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["mqtt"]["prefix"].isNull()) { String value = src["mqtt"]["prefix"].as(); - if (value.length() < sizeof(dst.mqtt.prefix) && !String(dst.mqtt.prefix).equals(value)) { + if (value.length() < sizeof(dst.mqtt.prefix) && !value.equals(dst.mqtt.prefix)) { strcpy(dst.mqtt.prefix, value.c_str()); changed = true; } @@ -964,13 +907,13 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false bool value = src["equitherm"]["enable"].as(); if (!dst.opentherm.nativeHeatingControl) { - if (value != dst.equitherm.enable) { - dst.equitherm.enable = value; + if (value != dst.equitherm.enabled) { + dst.equitherm.enabled = value; changed = true; } - - } else if (dst.equitherm.enable) { - dst.equitherm.enable = false; + + } else if (dst.equitherm.enabled) { + dst.equitherm.enabled = false; changed = true; } } @@ -978,8 +921,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["equitherm"]["n_factor"].isNull()) { float value = src["equitherm"]["n_factor"].as(); - if (value > 0 && value <= 10 && fabs(value - dst.equitherm.n_factor) > 0.0001f) { - dst.equitherm.n_factor = roundd(value, 3); + if (value > 0 && value <= 10 && fabsf(value - dst.equitherm.n_factor) > 0.0001f) { + dst.equitherm.n_factor = roundf(value, 3); changed = true; } } @@ -987,8 +930,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["equitherm"]["k_factor"].isNull()) { float value = src["equitherm"]["k_factor"].as(); - if (value >= 0 && value <= 10 && fabs(value - dst.equitherm.k_factor) > 0.0001f) { - dst.equitherm.k_factor = roundd(value, 3); + if (value >= 0 && value <= 10 && fabsf(value - dst.equitherm.k_factor) > 0.0001f) { + dst.equitherm.k_factor = roundf(value, 3); changed = true; } } @@ -996,8 +939,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["equitherm"]["t_factor"].isNull()) { float value = src["equitherm"]["t_factor"].as(); - if (value >= 0 && value <= 10 && fabs(value - dst.equitherm.t_factor) > 0.0001f) { - dst.equitherm.t_factor = roundd(value, 3); + if (value >= 0 && value <= 10 && fabsf(value - dst.equitherm.t_factor) > 0.0001f) { + dst.equitherm.t_factor = roundf(value, 3); changed = true; } } @@ -1006,15 +949,15 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // pid if (src["pid"]["enable"].is()) { bool value = src["pid"]["enable"].as(); - + if (!dst.opentherm.nativeHeatingControl) { - if (value != dst.pid.enable) { - dst.pid.enable = value; + if (value != dst.pid.enabled) { + dst.pid.enabled = value; changed = true; } - } else if (dst.pid.enable) { - dst.pid.enable = false; + } else if (dst.pid.enabled) { + dst.pid.enabled = false; changed = true; } } @@ -1022,8 +965,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["pid"]["p_factor"].isNull()) { float value = src["pid"]["p_factor"].as(); - if (value > 0 && value <= 1000 && fabs(value - dst.pid.p_factor) > 0.0001f) { - dst.pid.p_factor = roundd(value, 3); + if (value > 0 && value <= 1000 && fabsf(value - dst.pid.p_factor) > 0.0001f) { + dst.pid.p_factor = roundf(value, 3); changed = true; } } @@ -1031,8 +974,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["pid"]["i_factor"].isNull()) { float value = src["pid"]["i_factor"].as(); - if (value >= 0 && value <= 100 && fabs(value - dst.pid.i_factor) > 0.0001f) { - dst.pid.i_factor = roundd(value, 4); + if (value >= 0 && value <= 100 && fabsf(value - dst.pid.i_factor) > 0.0001f) { + dst.pid.i_factor = roundf(value, 4); changed = true; } } @@ -1040,8 +983,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["pid"]["d_factor"].isNull()) { float value = src["pid"]["d_factor"].as(); - if (value >= 0 && value <= 100000 && fabs(value - dst.pid.d_factor) > 0.0001f) { - dst.pid.d_factor = roundd(value, 1); + if (value >= 0 && value <= 100000 && fabsf(value - dst.pid.d_factor) > 0.0001f) { + dst.pid.d_factor = roundf(value, 1); changed = true; } } @@ -1058,7 +1001,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["pid"]["minTemp"].isNull()) { short value = src["pid"]["minTemp"].as(); - if (isValidTemp(value, dst.system.unitSystem, dst.equitherm.enable ? -99.9f : 0.0f) && value != dst.pid.minTemp) { + if (isValidTemp(value, dst.system.unitSystem, dst.equitherm.enabled ? -99.9f : 0.0f) && value != dst.pid.minTemp) { dst.pid.minTemp = value; changed = true; } @@ -1083,8 +1026,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (src["heating"]["enable"].is()) { bool value = src["heating"]["enable"].as(); - if (value != dst.heating.enable) { - dst.heating.enable = value; + if (value != dst.heating.enabled) { + dst.heating.enabled = value; changed = true; } } @@ -1101,8 +1044,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["heating"]["hysteresis"].isNull()) { float value = src["heating"]["hysteresis"].as(); - if (value >= 0.0f && value <= 15.0f && fabs(value - dst.heating.hysteresis) > 0.0001f) { - dst.heating.hysteresis = roundd(value, 2); + if (value >= 0.0f && value <= 15.0f && fabsf(value - dst.heating.hysteresis) > 0.0001f) { + dst.heating.hysteresis = roundf(value, 2); changed = true; } } @@ -1110,8 +1053,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["heating"]["turboFactor"].isNull()) { float value = src["heating"]["turboFactor"].as(); - if (value >= 1.5f && value <= 10.0f && fabs(value - dst.heating.turboFactor) > 0.0001f) { - dst.heating.turboFactor = roundd(value, 2); + if (value >= 1.5f && value <= 10.0f && fabsf(value - dst.heating.turboFactor) > 0.0001f) { + dst.heating.turboFactor = roundf(value, 3); changed = true; } } @@ -1119,7 +1062,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["heating"]["minTemp"].isNull()) { unsigned char value = src["heating"]["minTemp"].as(); - if (value != dst.heating.minTemp && value >= vars.parameters.heatingMinTemp && value < vars.parameters.heatingMaxTemp && value != dst.heating.minTemp) { + if (value != dst.heating.minTemp && value >= vars.slave.heating.minTemp && value < vars.slave.heating.maxTemp && value != dst.heating.minTemp) { dst.heating.minTemp = value; changed = true; } @@ -1128,7 +1071,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["heating"]["maxTemp"].isNull()) { unsigned char value = src["heating"]["maxTemp"].as(); - if (value != dst.heating.maxTemp && value > vars.parameters.heatingMinTemp && value <= vars.parameters.heatingMaxTemp && value != dst.heating.maxTemp) { + if (value != dst.heating.maxTemp && value > vars.slave.heating.minTemp && value <= vars.slave.heating.maxTemp && value != dst.heating.maxTemp) { dst.heating.maxTemp = value; changed = true; } @@ -1144,8 +1087,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (src["dhw"]["enable"].is()) { bool value = src["dhw"]["enable"].as(); - if (value != dst.dhw.enable) { - dst.dhw.enable = value; + if (value != dst.dhw.enabled) { + dst.dhw.enabled = value; changed = true; } } @@ -1153,7 +1096,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["dhw"]["minTemp"].isNull()) { unsigned char value = src["dhw"]["minTemp"].as(); - if (value >= vars.parameters.dhwMinTemp && value != dst.dhw.minTemp) { + if (value >= vars.slave.dhw.minTemp && value < vars.slave.dhw.maxTemp && value != dst.dhw.minTemp) { dst.dhw.minTemp = value; changed = true; } @@ -1162,7 +1105,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["dhw"]["maxTemp"].isNull()) { unsigned char value = src["dhw"]["maxTemp"].as(); - if (value > vars.parameters.dhwMinTemp && value != dst.dhw.maxTemp) { + if (value > vars.slave.dhw.minTemp && value <= vars.slave.dhw.maxTemp && value != dst.dhw.maxTemp) { dst.dhw.maxTemp = value; changed = true; } @@ -1173,167 +1116,6 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false changed = true; } - // sensors - if (!src["sensors"]["outdoor"]["type"].isNull()) { - byte value = src["sensors"]["outdoor"]["type"].as(); - - switch (value) { - case static_cast(SensorType::BOILER_OUTDOOR): - if (dst.sensors.outdoor.type != SensorType::BOILER_OUTDOOR) { - dst.sensors.outdoor.type = SensorType::BOILER_OUTDOOR; - changed = true; - } - break; - - case static_cast(SensorType::MANUAL): - if (dst.sensors.outdoor.type != SensorType::MANUAL) { - dst.sensors.outdoor.type = SensorType::MANUAL; - changed = true; - } - break; - - case static_cast(SensorType::DS18B20): - if (dst.sensors.outdoor.type != SensorType::DS18B20) { - dst.sensors.outdoor.type = SensorType::DS18B20; - changed = true; - } - break; - - #if USE_BLE - case static_cast(SensorType::BLUETOOTH): - if (dst.sensors.outdoor.type != SensorType::BLUETOOTH) { - dst.sensors.outdoor.type = SensorType::BLUETOOTH; - changed = true; - } - break; - #endif - - default: - break; - } - } - - if (!src["sensors"]["outdoor"]["gpio"].isNull()) { - if (src["sensors"]["outdoor"]["gpio"].is() && src["sensors"]["outdoor"]["gpio"].as().size() == 0) { - if (dst.sensors.outdoor.gpio != GPIO_IS_NOT_CONFIGURED) { - dst.sensors.outdoor.gpio = GPIO_IS_NOT_CONFIGURED; - changed = true; - } - - } else { - unsigned char value = src["sensors"]["outdoor"]["gpio"].as(); - - if (GPIO_IS_VALID(value) && value != dst.sensors.outdoor.gpio) { - dst.sensors.outdoor.gpio = value; - changed = true; - } - } - } - - #if USE_BLE - if (!src["sensors"]["outdoor"]["bleAddress"].isNull()) { - String value = src["sensors"]["outdoor"]["bleAddress"].as(); - int tmp[6]; - if(sscanf(value.c_str(), "%02x:%02x:%02x:%02x:%02x:%02x", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5]) == 6) { - for(uint8_t i = 0; i < 6; i++) { - if (dst.sensors.outdoor.bleAddress[i] != (uint8_t) tmp[i]) { - dst.sensors.outdoor.bleAddress[i] = (uint8_t) tmp[i]; - changed = true; - } - } - } - } - #endif - - if (!src["sensors"]["outdoor"]["offset"].isNull()) { - float value = src["sensors"]["outdoor"]["offset"].as(); - - if (value >= -20.0f && value <= 20.0f && fabs(value - dst.sensors.outdoor.offset) > 0.0001f) { - dst.sensors.outdoor.offset = roundd(value, 2); - changed = true; - } - } - - if (!src["sensors"]["indoor"]["type"].isNull()) { - byte value = src["sensors"]["indoor"]["type"].as(); - - switch (value) { - case static_cast(SensorType::BOILER_RETURN): - if (dst.sensors.indoor.type != SensorType::BOILER_RETURN) { - dst.sensors.indoor.type = SensorType::BOILER_RETURN; - changed = true; - } - break; - - case static_cast(SensorType::MANUAL): - if (dst.sensors.indoor.type != SensorType::MANUAL) { - dst.sensors.indoor.type = SensorType::MANUAL; - changed = true; - } - break; - - case static_cast(SensorType::DS18B20): - if (dst.sensors.indoor.type != SensorType::DS18B20) { - dst.sensors.indoor.type = SensorType::DS18B20; - changed = true; - } - break; - - #if USE_BLE - case static_cast(SensorType::BLUETOOTH): - if (dst.sensors.indoor.type != SensorType::BLUETOOTH) { - dst.sensors.indoor.type = SensorType::BLUETOOTH; - changed = true; - } - break; - #endif - - default: - break; - } - } - - if (!src["sensors"]["indoor"]["gpio"].isNull()) { - if (src["sensors"]["indoor"]["gpio"].is() && src["sensors"]["indoor"]["gpio"].as().size() == 0) { - if (dst.sensors.indoor.gpio != GPIO_IS_NOT_CONFIGURED) { - dst.sensors.indoor.gpio = GPIO_IS_NOT_CONFIGURED; - changed = true; - } - - } else { - unsigned char value = src["sensors"]["indoor"]["gpio"].as(); - - if (GPIO_IS_VALID(value) && value != dst.sensors.indoor.gpio) { - dst.sensors.indoor.gpio = value; - changed = true; - } - } - } - - #if USE_BLE - if (!src["sensors"]["indoor"]["bleAddress"].isNull()) { - String value = src["sensors"]["indoor"]["bleAddress"].as(); - int tmp[6]; - if(sscanf(value.c_str(), "%02x:%02x:%02x:%02x:%02x:%02x", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5]) == 6) { - for(uint8_t i = 0; i < 6; i++) { - if (dst.sensors.indoor.bleAddress[i] != (uint8_t) tmp[i]) { - dst.sensors.indoor.bleAddress[i] = (uint8_t) tmp[i]; - changed = true; - } - } - } - } - #endif - - if (!src["sensors"]["indoor"]["offset"].isNull()) { - float value = src["sensors"]["indoor"]["offset"].as(); - - if (value >= -20.0f && value <= 20.0f && fabs(value - dst.sensors.indoor.offset) > 0.0001f) { - dst.sensors.indoor.offset = roundd(value, 2); - changed = true; - } - } - if (!safe) { // external pump @@ -1352,7 +1134,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false dst.externalPump.gpio = GPIO_IS_NOT_CONFIGURED; changed = true; } - + } else { unsigned char value = src["externalPump"]["gpio"].as(); @@ -1407,8 +1189,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (src["cascadeControl"]["input"]["enable"].is()) { bool value = src["cascadeControl"]["input"]["enable"].as(); - if (value != dst.cascadeControl.input.enable) { - dst.cascadeControl.input.enable = value; + if (value != dst.cascadeControl.input.enabled) { + dst.cascadeControl.input.enabled = value; changed = true; } } @@ -1419,7 +1201,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false dst.cascadeControl.input.gpio = GPIO_IS_NOT_CONFIGURED; changed = true; } - + } else { unsigned char value = src["cascadeControl"]["input"]["gpio"].as(); @@ -1453,8 +1235,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (src["cascadeControl"]["output"]["enable"].is()) { bool value = src["cascadeControl"]["output"]["enable"].as(); - if (value != dst.cascadeControl.output.enable) { - dst.cascadeControl.output.enable = value; + if (value != dst.cascadeControl.output.enabled) { + dst.cascadeControl.output.enabled = value; changed = true; } } @@ -1465,7 +1247,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false dst.cascadeControl.output.gpio = GPIO_IS_NOT_CONFIGURED; changed = true; } - + } else { unsigned char value = src["cascadeControl"]["output"]["gpio"].as(); @@ -1544,8 +1326,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false ); } - if (fabs(dst.emergency.target - value) > 0.0001f) { - dst.emergency.target = roundd(value, 2); + if (fabsf(dst.emergency.target - value) > 0.0001f) { + dst.emergency.target = roundf(value, 2); changed = true; } } @@ -1553,25 +1335,26 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // force check heating target { float value = !src["heating"]["target"].isNull() ? src["heating"]["target"].as() : dst.heating.target; - bool noRegulators = !dst.opentherm.nativeHeatingControl && !dst.equitherm.enable && !dst.pid.enable; bool valid = isValidTemp( value, dst.system.unitSystem, - noRegulators ? dst.heating.minTemp : THERMOSTAT_INDOOR_MIN_TEMP, - noRegulators ? dst.heating.maxTemp : THERMOSTAT_INDOOR_MAX_TEMP, - noRegulators ? dst.system.unitSystem : UnitSystem::METRIC + vars.master.heating.minTemp, + vars.master.heating.maxTemp, + dst.system.unitSystem ); if (!valid) { value = convertTemp( - noRegulators ? DEFAULT_HEATING_TARGET_TEMP : THERMOSTAT_INDOOR_DEFAULT_TEMP, + vars.master.heating.indoorTempControl + ? THERMOSTAT_INDOOR_DEFAULT_TEMP + : DEFAULT_HEATING_TARGET_TEMP, UnitSystem::METRIC, dst.system.unitSystem ); } - if (fabs(dst.heating.target - value) > 0.0001f) { - dst.heating.target = roundd(value, 2); + if (fabsf(dst.heating.target - value) > 0.0001f) { + dst.heating.target = roundf(value, 2); changed = true; } } @@ -1591,7 +1374,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false value = convertTemp(DEFAULT_DHW_TARGET_TEMP, UnitSystem::METRIC, dst.system.unitSystem); } - if (fabs(dst.dhw.target - value) > 0.0001f) { + if (fabsf(dst.dhw.target - value) > 0.0001f) { dst.dhw.target = value; changed = true; } @@ -1604,84 +1387,355 @@ inline bool safeJsonToSettings(const JsonVariantConst src, Settings& dst) { return jsonToSettings(src, dst, true); } -void varsToJson(const Variables& src, JsonVariant dst) { - dst["states"]["otStatus"] = src.states.otStatus; - dst["states"]["emergency"] = src.states.emergency; - dst["states"]["heating"] = src.states.heating; - dst["states"]["dhw"] = src.states.dhw; - dst["states"]["flame"] = src.states.flame; - dst["states"]["fault"] = src.states.fault; - dst["states"]["diagnostic"] = src.states.diagnostic; - dst["states"]["externalPump"] = src.states.externalPump; - dst["states"]["mqtt"] = src.states.mqtt; - - dst["sensors"]["modulation"] = roundd(src.sensors.modulation, 2); - dst["sensors"]["pressure"] = roundd(src.sensors.pressure, 2); - dst["sensors"]["dhwFlowRate"] = roundd(src.sensors.dhwFlowRate, 2); - dst["sensors"]["power"] = roundd(src.sensors.power, 2); - dst["sensors"]["faultCode"] = src.sensors.faultCode; - dst["sensors"]["diagnosticCode"] = src.sensors.diagnosticCode; - dst["sensors"]["rssi"] = src.sensors.rssi; - dst["sensors"]["uptime"] = millis() / 1000ul; - dst["sensors"]["outdoor"]["connected"] = src.sensors.outdoor.connected; - dst["sensors"]["outdoor"]["rssi"] = src.sensors.outdoor.rssi; - dst["sensors"]["outdoor"]["battery"] = roundd(src.sensors.outdoor.battery, 2); - dst["sensors"]["outdoor"]["humidity"] = roundd(src.sensors.outdoor.humidity, 2); - dst["sensors"]["indoor"]["connected"] = src.sensors.indoor.connected; - dst["sensors"]["indoor"]["rssi"] = src.sensors.indoor.rssi; - dst["sensors"]["indoor"]["battery"] = roundd(src.sensors.indoor.battery, 2); - dst["sensors"]["indoor"]["humidity"] = roundd(src.sensors.indoor.humidity, 2); - - dst["temperatures"]["indoor"] = roundd(src.temperatures.indoor, 2); - dst["temperatures"]["outdoor"] = roundd(src.temperatures.outdoor, 2); - dst["temperatures"]["heating"] = roundd(src.temperatures.heating, 2); - dst["temperatures"]["heatingReturn"] = roundd(src.temperatures.heatingReturn, 2); - dst["temperatures"]["dhw"] = roundd(src.temperatures.dhw, 2); - dst["temperatures"]["exhaust"] = roundd(src.temperatures.exhaust, 2); - - dst["cascadeControl"]["input"] = src.cascadeControl.input; - dst["cascadeControl"]["output"] = src.cascadeControl.output; - - dst["parameters"]["heatingEnabled"] = src.parameters.heatingEnabled; - dst["parameters"]["heatingMinTemp"] = src.parameters.heatingMinTemp; - dst["parameters"]["heatingMaxTemp"] = src.parameters.heatingMaxTemp; - dst["parameters"]["heatingSetpoint"] = roundd(src.parameters.heatingSetpoint, 2); - dst["parameters"]["dhwMinTemp"] = src.parameters.dhwMinTemp; - dst["parameters"]["dhwMaxTemp"] = src.parameters.dhwMaxTemp; - - dst["parameters"]["slaveMemberId"] = src.parameters.slaveMemberId; - dst["parameters"]["slaveFlags"] = src.parameters.slaveFlags; - dst["parameters"]["slaveType"] = src.parameters.slaveType; - dst["parameters"]["slaveVersion"] = src.parameters.slaveVersion; - dst["parameters"]["slaveOtVersion"] = src.parameters.slaveOtVersion; +void sensorSettingsToJson(const uint8_t sensorId, const Sensors::Settings& src, JsonVariant dst) { + dst["id"] = sensorId; + dst["enabled"] = src.enabled; + dst["name"] = src.name; + dst["purpose"] = static_cast(src.purpose); + dst["type"] = static_cast(src.type); + dst["gpio"] = src.gpio; + + if (src.type == Sensors::Type::DALLAS_TEMP) { + char addr[24]; + sprintf( + addr, + "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + src.address[0], src.address[1], src.address[2], src.address[3], + src.address[4], src.address[5], src.address[6], src.address[7] + ); + dst["address"] = String(addr); + + } else if (src.type == Sensors::Type::BLUETOOTH) { + char addr[18]; + sprintf( + addr, + "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + src.address[0], src.address[1], src.address[2], + src.address[3], src.address[4], src.address[5] + ); + dst["address"] = String(addr); + + } else { + dst["address"] = ""; + } + + dst["offset"] = roundf(src.offset, 3); + dst["factor"] = roundf(src.factor, 3); + dst["filtering"] = src.filtering; + dst["filteringFactor"] = roundf(src.filteringFactor, 3); } -bool jsonToVars(const JsonVariantConst src, Variables& dst) { +bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Sensors::Settings& dst) { + if (sensorId > Sensors::getMaxSensorId()) { + return false; + } + bool changed = false; - // temperatures - if (!src["temperatures"]["indoor"].isNull()) { - float value = src["temperatures"]["indoor"].as(); + // enabled + if (src["enabled"].is()) { + auto value = src["enabled"].as(); + + if (value != dst.enabled) { + dst.enabled = value; + changed = true; + } + } + + // name + if (!src["name"].isNull()) { + String value = Sensors::cleanName(src["name"].as()); + + if (value.length() < sizeof(dst.name) && !value.equals(dst.name)) { + strcpy(dst.name, value.c_str()); + changed = true; + } + } + + // purpose + if (!src["purpose"].isNull()) { + uint8_t value = src["purpose"].as(); + + switch (value) { + case static_cast(Sensors::Purpose::OUTDOOR_TEMP): + case static_cast(Sensors::Purpose::INDOOR_TEMP): + case static_cast(Sensors::Purpose::HEATING_TEMP): + case static_cast(Sensors::Purpose::HEATING_RETURN_TEMP): + case static_cast(Sensors::Purpose::DHW_TEMP): + case static_cast(Sensors::Purpose::DHW_RETURN_TEMP): + case static_cast(Sensors::Purpose::DHW_FLOW_RATE): + case static_cast(Sensors::Purpose::EXHAUST_TEMP): + case static_cast(Sensors::Purpose::MODULATION_LEVEL): + case static_cast(Sensors::Purpose::CURRENT_POWER): + case static_cast(Sensors::Purpose::PRESSURE): + case static_cast(Sensors::Purpose::HUMIDITY): + case static_cast(Sensors::Purpose::TEMPERATURE): + case static_cast(Sensors::Purpose::NOT_CONFIGURED): + if (static_cast(dst.purpose) != value) { + dst.purpose = static_cast(value); + changed = true; + } + break; + + default: + break; + } + } + + // type + if (!src["type"].isNull()) { + uint8_t value = src["type"].as(); + + switch (value) { + case static_cast(Sensors::Type::OT_OUTDOOR_TEMP): + case static_cast(Sensors::Type::OT_HEATING_TEMP): + case static_cast(Sensors::Type::OT_HEATING_RETURN_TEMP): + case static_cast(Sensors::Type::OT_DHW_TEMP): + case static_cast(Sensors::Type::OT_DHW_TEMP2): + case static_cast(Sensors::Type::OT_DHW_FLOW_RATE): + case static_cast(Sensors::Type::OT_CH2_TEMP): + case static_cast(Sensors::Type::OT_EXHAUST_TEMP): + case static_cast(Sensors::Type::OT_HEAT_EXCHANGER_TEMP): + case static_cast(Sensors::Type::OT_PRESSURE): + case static_cast(Sensors::Type::OT_MODULATION_LEVEL): + case static_cast(Sensors::Type::OT_CURRENT_POWER): + case static_cast(Sensors::Type::NTC_10K_TEMP): + case static_cast(Sensors::Type::DALLAS_TEMP): + case static_cast(Sensors::Type::BLUETOOTH): + case static_cast(Sensors::Type::HEATING_SETPOINT_TEMP): + case static_cast(Sensors::Type::MANUAL): + case static_cast(Sensors::Type::NOT_CONFIGURED): + if (static_cast(dst.type) != value) { + dst.type = static_cast(value); + changed = true; + } + break; + + default: + break; + } + } + + // gpio + if (!src["gpio"].isNull()) { + if (dst.type != Sensors::Type::DALLAS_TEMP && dst.type == Sensors::Type::BLUETOOTH && dst.type == Sensors::Type::NTC_10K_TEMP) { + if (dst.gpio != GPIO_IS_NOT_CONFIGURED) { + dst.gpio = GPIO_IS_NOT_CONFIGURED; + changed = true; + } + + } else if (src["gpio"].is() && src["gpio"].as().size() == 0) { + if (dst.gpio != GPIO_IS_NOT_CONFIGURED) { + dst.gpio = GPIO_IS_NOT_CONFIGURED; + changed = true; + } + + } else { + unsigned char value = src["gpio"].as(); - if (settings.sensors.indoor.type == SensorType::MANUAL && isValidTemp(value, settings.system.unitSystem, -99.9f, 99.9f)) { - if (fabs(value - dst.temperatures.indoor) > 0.0001f) { - dst.temperatures.indoor = roundd(value, 2); + if (GPIO_IS_VALID(value) && value != dst.gpio) { + dst.gpio = value; changed = true; } } } - if (!src["temperatures"]["outdoor"].isNull()) { - float value = src["temperatures"]["outdoor"].as(); + // address + if (!src["address"].isNull()) { + String value = src["address"].as(); + + if (dst.type == Sensors::Type::DALLAS_TEMP) { + uint8_t tmp[8]; + int parsed = sscanf( + value.c_str(), + "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &tmp[0], &tmp[1], &tmp[2], &tmp[3], + &tmp[4], &tmp[5], &tmp[6], &tmp[7] + ); - if (settings.sensors.outdoor.type == SensorType::MANUAL && isValidTemp(value, settings.system.unitSystem, -99.9f, 99.9f)) { - if (fabs(value - dst.temperatures.outdoor) > 0.0001f) { - dst.temperatures.outdoor = roundd(value, 2); - changed = true; + if (parsed == 8) { + for (uint8_t i = 0; i < 8; i++) { + if (dst.address[i] != tmp[i]) { + dst.address[i] = tmp[i]; + changed = true; + } + } + } + + } else if (dst.type == Sensors::Type::BLUETOOTH) { + uint8_t tmp[6]; + int parsed = sscanf( + value.c_str(), + "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + &tmp[0], &tmp[1], &tmp[2], + &tmp[3], &tmp[4], &tmp[5] + ); + + if (parsed == 6) { + for (uint8_t i = 0; i < 6; i++) { + if (dst.address[i] != tmp[i]) { + dst.address[i] = tmp[i]; + changed = true; + } + } } } } + // offset + if (!src["offset"].isNull()) { + float value = src["offset"].as(); + + if (value >= -20.0f && value <= 20.0f && fabsf(value - dst.offset) > 0.0001f) { + dst.offset = roundf(value, 2); + changed = true; + } + } + + // factor + if (!src["factor"].isNull()) { + float value = src["factor"].as(); + + if (value > 0.09f && value <= 10.0f && fabsf(value - dst.factor) > 0.0001f) { + dst.factor = roundf(value, 3); + changed = true; + } + } + + // filtering + if (src["filtering"].is()) { + auto value = src["filtering"].as(); + + if (value != dst.filtering) { + dst.filtering = value; + changed = true; + } + } + + // filtering factor + if (!src["filteringFactor"].isNull()) { + float value = src["filteringFactor"].as(); + + if (value > 0 && value <= 1 && fabsf(value - dst.filteringFactor) > 0.0001f) { + dst.filteringFactor = roundf(value, 3); + changed = true; + } + } + + return changed; +} + +void sensorResultToJson(const uint8_t sensorId, JsonVariant dst) { + if (!Sensors::isValidSensorId(sensorId)) { + return; + } + + auto& sSensor = Sensors::settings[sensorId]; + auto& rSensor = Sensors::results[sensorId]; + + //dst["id"] = sensorId; + dst["connected"] = rSensor.connected; + dst["signalQuality"] = rSensor.signalQuality; + + if (sSensor.type == Sensors::Type::BLUETOOTH) { + dst["temperature"] = rSensor.values[static_cast(Sensors::ValueType::TEMPERATURE)]; + dst["humidity"] = rSensor.values[static_cast(Sensors::ValueType::HUMIDITY)]; + dst["battery"] = rSensor.values[static_cast(Sensors::ValueType::BATTERY)]; + dst["rssi"] = rSensor.values[static_cast(Sensors::ValueType::RSSI)]; + + } else { + dst["value"] = rSensor.values[static_cast(Sensors::ValueType::PRIMARY)]; + } +} + +bool jsonToSensorResult(const uint8_t sensorId, const JsonVariantConst src) { + if (!Sensors::isValidSensorId(sensorId)) { + return false; + } + + auto& sSensor = Sensors::settings[sensorId]; + if (!sSensor.enabled || sSensor.type != Sensors::Type::MANUAL) { + return false; + } + + auto& dst = Sensors::results[sensorId]; + bool changed = false; + + // value + if (!src["value"].isNull()) { + float value = src["value"].as(); + + uint8_t vType = static_cast(Sensors::ValueType::PRIMARY); + if (fabsf(value - dst.values[vType]) > 0.0001f) { + dst.values[vType] = roundf(value, 2); + changed = true; + } + } + + return changed; +} + +void varsToJson(const Variables& src, JsonVariant dst) { + dst["slave"]["memberId"] = src.slave.memberId; + dst["slave"]["flags"] = src.slave.flags; + dst["slave"]["type"] = src.slave.type; + dst["slave"]["appVersion"] = src.slave.appVersion; + dst["slave"]["protocolVersion"] = src.slave.appVersion; + dst["slave"]["connected"] = src.slave.connected; + dst["slave"]["flame"] = src.slave.flame; + + dst["slave"]["modulation"]["min"] = src.slave.modulation.min; + dst["slave"]["modulation"]["max"] = src.slave.modulation.max; + + dst["slave"]["power"]["min"] = roundf(src.slave.power.min, 2); + dst["slave"]["power"]["max"] = roundf(src.slave.power.max, 2); + + dst["slave"]["heating"]["active"] = src.slave.heating.active; + dst["slave"]["heating"]["minTemp"] = src.slave.heating.minTemp; + dst["slave"]["heating"]["maxTemp"] = src.slave.heating.maxTemp; + + dst["slave"]["dhw"]["active"] = src.slave.dhw.active; + dst["slave"]["dhw"]["minTemp"] = src.slave.dhw.minTemp; + dst["slave"]["dhw"]["maxTemp"] = src.slave.dhw.maxTemp; + + dst["slave"]["fault"]["active"] = src.slave.fault.active; + dst["slave"]["fault"]["code"] = src.slave.fault.code; + + dst["slave"]["diag"]["active"] = src.slave.diag.active; + dst["slave"]["diag"]["code"] = src.slave.diag.code; + + dst["master"]["heating"]["enabled"] = src.master.heating.enabled; + dst["master"]["heating"]["blocking"] = src.master.heating.blocking; + dst["master"]["heating"]["indoorTempControl"] = src.master.heating.indoorTempControl; + dst["master"]["heating"]["targetTemp"] = roundf(src.master.heating.targetTemp, 2); + dst["master"]["heating"]["currentTemp"] = roundf(src.master.heating.currentTemp, 2); + dst["master"]["heating"]["returnTemp"] = roundf(src.master.heating.returnTemp, 2); + dst["master"]["heating"]["indoorTemp"] = roundf(src.master.heating.indoorTemp, 2); + dst["master"]["heating"]["outdoorTemp"] = roundf(src.master.heating.outdoorTemp, 2); + dst["master"]["heating"]["minTemp"] = roundf(src.master.heating.minTemp, 2); + dst["master"]["heating"]["maxTemp"] = roundf(src.master.heating.maxTemp, 2); + + dst["master"]["dhw"]["enabled"] = src.master.dhw.enabled; + dst["master"]["dhw"]["targetTemp"] = roundf(src.master.dhw.targetTemp, 2); + dst["master"]["dhw"]["currentTemp"] = roundf(src.master.dhw.currentTemp, 2); + dst["master"]["dhw"]["returnTemp"] = roundf(src.master.dhw.returnTemp, 2); + dst["master"]["dhw"]["minTemp"] = settings.dhw.minTemp; + dst["master"]["dhw"]["maxTemp"] = settings.dhw.maxTemp; + + dst["master"]["network"]["connected"] = src.network.connected; + dst["master"]["mqtt"]["connected"] = src.mqtt.connected; + dst["master"]["emergency"]["state"] = src.emergency.state; + dst["master"]["externalPump"]["state"] = src.externalPump.state; + + dst["master"]["cascadeControl"]["input"] = src.cascadeControl.input; + dst["master"]["cascadeControl"]["output"] = src.cascadeControl.output; + + dst["master"]["uptime"] = millis() / 1000ul; +} + +bool jsonToVars(const JsonVariantConst src, Variables& dst) { + bool changed = false; + // actions if (src["actions"]["restart"].is() && src["actions"]["restart"].as()) { dst.actions.restart = true; diff --git a/src_data/locales/en.json b/src_data/locales/en.json index f7b48d6..7251746 100644 --- a/src_data/locales/en.json +++ b/src_data/locales/en.json @@ -74,8 +74,9 @@ "section": { "control": "Control", - "states": "States and sensors", - "otDiag": "OpenTherm diagnostic" + "states": "States", + "sensors": "Sensors", + "diag": "OpenTherm diagnostic" }, "thermostat": { @@ -86,39 +87,35 @@ "turbo": "Turbo mode" }, - "state": { - "ot": "OpenTherm connected", - "mqtt": "MQTT connected", - "emergency": "Emergency", - "heating": "Heating", - "dhw": "DHW", - "flame": "Flame", - "fault": "Fault", - "diag": "Diagnostic", - "extpump": "External pump", - "outdoorSensorConnected": "Outdoor sensor connected", - "outdoorSensorRssi": "Outdoor sensor RSSI", - "outdoorSensorHumidity": "Outdoor sensor humidity", - "outdoorSensorBattery": "Outdoor sensor battery", - "indoorSensorConnected": "Indoor sensor connected", - "cascadeControlInput": "Cascade control (input)", - "cascadeControlOutput": "Cascade control (output)", - "indoorSensorRssi": "Indoor sensor RSSI", - "indoorSensorHumidity": "Indoor sensor humidity", - "indoorSensorBattery": "Indoor sensor battery", - "modulation": "Modulation", - "pressure": "Pressure", - "dhwFlowRate": "DHW flow rate", - "power": "Current power", - "faultCode": "Fault code", - "diagCode": "Diagnostic code", - "indoorTemp": "Indoor temp", - "outdoorTemp": "Outdoor temp", - "heatingTemp": "Heating temp", - "heatingSetpointTemp": "Heating setpoint temp", - "heatingReturnTemp": "Heating return temp", - "dhwTemp": "DHW temp", - "exhaustTemp": "Exhaust temp" + "states": { + "mNetworkConnected": "Network connection", + "mMqttConnected": "MQTT connection", + "mEmergencyState": "Emergency mode", + "mExtPumpState": "External pump", + "mCascadeControlInput": "Cascade control (input)", + "mCascadeControlOutput": "Cascade control (output)", + + "sConnected": "OpenTherm connection", + "sFlame": "Flame", + "sFaultActive": "Fault", + "sFaultCode": "Faul code", + "sDiagActive": "Diagnostic", + "sDiagCode": "Diagnostic code", + + "mHeatEnabled": "Heating enabled", + "mHeatBlocking": "Heating blocked", + "sHeatActive": "Heating active", + "mHeatTargetTemp": "Heating setpoint temp", + "mHeatCurrTemp": "Heating current temp", + "mHeatRetTemp": "Heating return temp", + "mHeatIndoorTemp": "Heating, indoor temp", + "mHeatOutdoorTemp": "Heating, outdoor temp", + + "mDhwEnabled": "DHW enabled", + "sDhwActive": "DHW active", + "mDhwTargetTemp": "DHW setpoint temp", + "mDhwCurrTemp": "DHW current temp", + "mDhwRetTemp": "DHW return temp" } }, @@ -161,6 +158,76 @@ } }, + "sensors": { + "title": "Sensors settings - OpenTherm Gateway", + "name": "Sensors settings", + + "enabled": "Enabled", + "sensorName": { + "title": "Sensor name", + "note": "May only contain: a-z, A-Z, 0-9, _ and space" + }, + "purpose": "Purpose", + "purposes": { + "outdoorTemp": "Outdoor temperature", + "indoorTemp": "Indoor temperature", + "heatTemp": "Heating, temperature", + "heatRetTemp": "Heating, return temperature", + "dhwTemp": "DHW, temperature", + "dhwRetTemp": "DHW, return temperature", + "dhwFlowRate": "DHW, flow rate", + "exhaustTemp": "Exhaust temperature", + "modLevel": "Modulation level (in percents)", + "currentPower": "Current power (in kWt)", + "pressure": "Pressure", + "humidity": "Humidity", + "temperature": "Temperature", + "notConfigured": "Not configured" + }, + "type": "Type/source", + "types": { + "otOutdoorTemp": "OpenTherm, outdoor temp", + "otHeatTemp": "OpenTherm, heating, temp", + "otHeatRetTemp": "OpenTherm, heating, return temp", + "otDhwTemp": "OpenTherm, DHW, temperature", + "otDhwTemp2": "OpenTherm, DHW, temperature 2", + "otDhwFlowRate": "OpenTherm, DHW, flow rate", + "otCh2Temp": "OpenTherm, channel 2, temp", + "otExhaustTemp": "OpenTherm, exhaust temp", + "otHeatExchangerTemp": "OpenTherm, heat exchanger temp", + "otPressure": "OpenTherm, pressure", + "otModLevel": "OpenTherm, modulation level", + "otCurrentPower": "OpenTherm, current power", + "ntcTemp": "NTC sensor", + "dallasTemp": "DALLAS sensor", + "bluetooth": "BLE sensor", + "heatSetpointTemp": "Heating, setpoint temp", + "manual": "Manual via MQTT/API", + "notConfigured": "Not configured" + }, + "gpio": "GPIO", + "address": { + "title": "Sensor address", + "note": "For auto detection of DALLAS sensors leave it at default, for BLE devices need a MAC address" + }, + "correction": { + "desc": "Correction of values", + "offset": "Compensation (offset)", + "factor": "Multiplier" + }, + "filtering": { + "desc": "Filtering values", + "enabled": { + "title": "Enabled filtering", + "note": "It can be useful if there is a lot of sharp noise on the charts. The filter used is \"Running Average\"." + }, + "factor": { + "title": "Filtration factor", + "note": "The lower the value, the smoother and longer the change in numeric values." + } + } + }, + "settings": { "title": "Settings - OpenTherm Gateway", "name": "Settings", @@ -176,8 +243,6 @@ "pid": "PID settings", "ot": "OpenTherm settings", "mqtt": "MQTT settings", - "outdorSensor": "Outdoor sensor settings", - "indoorSensor": "Indoor sensor settings", "extPump": "External pump settings", "cascadeControl": "Cascade control settings" }, @@ -207,11 +272,11 @@ "statusLedGpio": "Status LED GPIO", "logLevel": "Log level", "serial": { - "enable": "Enable Serial port", + "enable": "Enabled Serial port", "baud": "Serial port baud rate" }, "telnet": { - "enable": "Enable Telnet", + "enable": "Enabled Telnet", "port": { "title": "Telnet port", "note": "Default: 23" @@ -256,16 +321,9 @@ "inGpio": "In GPIO", "outGpio": "Out GPIO", "ledGpio": "RX LED GPIO", - "memberIdCode": "Master MemberID code", + "memberId": "Master member ID", + "flags": "Master flags", "maxMod": "Max modulation level", - "pressureFactor": { - "title": "Coeff. pressure correction", - "note": "If the pressure displayed is X10 from the real one, set the 0.1." - }, - "dhwFlowRateFactor": { - "title": "Coeff. DHW flow rate correction", - "note": "If the DHW flow rate displayed is X10 from the real one, set the 0.1." - }, "minPower": { "title": "Min boiler power (kW)", "note": "This value is at 0-1% boiler modulation level. Typically found in the boiler specification as \"minimum useful heat output\"." @@ -274,17 +332,6 @@ "title": "Max boiler power (kW)", "note": "0 - try detect automatically. Typically found in the boiler specification as \"maximum useful heat output\"." }, - "fnv": { - "desc": "Filtering numeric values", - "enable": { - "title": "Enable filtering", - "note": "It can be useful if there is a lot of sharp noise on the charts. The filter used is \"Running Average\"." - }, - "factor": { - "title": "Filtration coeff.", - "note": "The lower the value, the smoother and longer the change in numeric values." - } - }, "options": { "desc": "Options", @@ -315,20 +362,6 @@ "interval": "Publish interval (sec)" }, - "tempSensor": { - "source": { - "type": "Source type", - "boilerOutdoor": "From boiler via OpenTherm", - "boilerReturn": "Return heat carrier temp via OpenTherm", - "manual": "Manual via MQTT/API", - "ext": "External (DS18B20)", - "ble": "BLE device" - }, - "gpio": "GPIO", - "offset": "Temp offset (calibration)", - "bleAddress": "BLE device MAC address" - }, - "extPump": { "use": "Use external pump", "gpio": "Relay GPIO", @@ -340,14 +373,14 @@ "cascadeControl": { "input": { "desc": "Can be used to turn on the heating only if another boiler is faulty. The other boiler controller must change the state of the GPIO input in the event of a fault.", - "enable": "Enable input", + "enable": "Enabled input", "gpio": "GPIO", "invertState": "Invert GPIO state", "thresholdTime": "State change threshold time (sec)" }, "output": { "desc": "Can be used to switch on another boiler via relay.", - "enable": "Enable output", + "enable": "Enabled output", "gpio": "GPIO", "invertState": "Invert GPIO state", "thresholdTime": "State change threshold time (sec)", diff --git a/src_data/locales/ru.json b/src_data/locales/ru.json index 174f8b6..9d594c6 100644 --- a/src_data/locales/ru.json +++ b/src_data/locales/ru.json @@ -74,8 +74,9 @@ "section": { "control": "Управление", - "states": "Состояние и сенсоры", - "otDiag": "Диагностика OpenTherm" + "states": "Состояние", + "sensors": "Сенсоры", + "diag": "Диагностика OpenTherm" }, "thermostat": { @@ -86,39 +87,35 @@ "turbo": "Турбо" }, - "state": { - "ot": "OpenTherm подключение", - "mqtt": "MQTT подключение", - "emergency": "Аварийный режим", - "heating": "Отопление", - "dhw": "ГВС", - "flame": "Пламя", - "fault": "Ошибка", - "diag": "Диагностика", - "extpump": "Внешний насос", - "outdoorSensorConnected": "Датчик наруж. темп.", - "outdoorSensorRssi": "RSSI датчика наруж. темп.", - "outdoorSensorHumidity": "Влажность с наруж. датчика темп.", - "outdoorSensorBattery": "Заряд наруж. датчика темп.", - "indoorSensorConnected": "Датчик внутр. темп.", - "cascadeControlInput": "Каскадное управление (вход)", - "cascadeControlOutput": "Каскадное управление (выход)", - "indoorSensorRssi": "RSSI датчика внутр. темп.", - "indoorSensorHumidity": "Влажность с внутр. датчика темп.", - "indoorSensorBattery": "Заряд внутр. датчика темп.", - "modulation": "Уровень модуляции", - "pressure": "Давление", - "dhwFlowRate": "Расход ГВС", - "power": "Текущая мощность", - "faultCode": "Код ошибки", - "diagCode": "Диагностический код", - "indoorTemp": "Внутренняя темп.", - "outdoorTemp": "Наружная темп.", - "heatingTemp": "Темп. отопления", - "heatingSetpointTemp": "Уставка темп. отопления", - "heatingReturnTemp": "Темп. обратки отопления", - "dhwTemp": "Темп. ГВС", - "exhaustTemp": "Темп. выхлопных газов" + "states": { + "mNetworkConnected": "Подключение к сети", + "mMqttConnected": "Подключение к MQTT", + "mEmergencyState": "Аварийный режим", + "mExtPumpState": "Внешний насос", + "mCascadeControlInput": "Каскадное управление (вход)", + "mCascadeControlOutput": "Каскадное управление (выход)", + + "sConnected": "Подключение к OpenTherm", + "sFlame": "Пламя", + "sFaultActive": "Ошибка", + "sFaultCode": "Код ошибки", + "sDiagActive": "Диагностика", + "sDiagCode": "Диагностический код", + + "mHeatEnabled": "Отопление", + "mHeatBlocking": "Блокировка отопления", + "sHeatActive": "Активность отопления", + "mHeatTargetTemp": "Отопление, целевая температура", + "mHeatCurrTemp": "Отопление, текущая температура", + "mHeatRetTemp": "Отопление, температура обратки", + "mHeatIndoorTemp": "Отопление, внутренняя темп.", + "mHeatOutdoorTemp": "Отопление, наружная темп.", + + "mDhwEnabled": "ГВС", + "sDhwActive": "Активность ГВС", + "mDhwTargetTemp": "ГВС, целевая температура", + "mDhwCurrTemp": "ГВС, текущая температура", + "mDhwRetTemp": "ГВС, температура обратки" } }, @@ -161,6 +158,76 @@ } }, + "sensors": { + "title": "Настройки сенсоров - OpenTherm Gateway", + "name": "Настройки сенсоров", + + "enabled": "Включить и использовать", + "sensorName": { + "title": "Имя сенсора", + "note": "Может содержать только: a-z, A-Z, 0-9, _ и пробел" + }, + "purpose": "Назначение", + "purposes": { + "outdoorTemp": "Внешняя температура", + "indoorTemp": "Внутреняя температура", + "heatTemp": "Отопление, температура", + "heatRetTemp": "Отопление, температура обратки", + "dhwTemp": "ГВС, температура", + "dhwRetTemp": "ГВС, температура обратки", + "dhwFlowRate": "ГВС, расход/скорость потока", + "exhaustTemp": "Температура выхлопных газов", + "modLevel": "Уровень модуляции (в процентах)", + "currentPower": "Текущая мощность (в кВт)", + "pressure": "Давление", + "humidity": "Влажность", + "temperature": "Температура", + "notConfigured": "Не сконфигурировано" + }, + "type": "Тип/источник", + "types": { + "otOutdoorTemp": "OpenTherm, внешняя температура", + "otHeatTemp": "OpenTherm, отопление, температура", + "otHeatRetTemp": "OpenTherm, отопление, температура обратки", + "otDhwTemp": "OpenTherm, ГВС, температура", + "otDhwTemp2": "OpenTherm, ГВС, температура 2", + "otDhwFlowRate": "OpenTherm, ГВС, расход/скорость потока", + "otCh2Temp": "OpenTherm, канал 2, температура", + "otExhaustTemp": "OpenTherm, температура выхлопных газов", + "otHeatExchangerTemp": "OpenTherm, температура теплообменника", + "otPressure": "OpenTherm, давление", + "otModLevel": "OpenTherm, уровень модуляции", + "otCurrentPower": "OpenTherm, текущая мощность", + "ntcTemp": "NTC датчик", + "dallasTemp": "DALLAS датчик", + "bluetooth": "BLE датчик", + "heatSetpointTemp": "Отопление, температура уставки", + "manual": "Вручную через MQTT/API", + "notConfigured": "Не сконфигурировано" + }, + "gpio": "GPIO датчика", + "address": { + "title": "Адрес датчика", + "note": "Для DALLAS датчиков оставьте по умолчанию для автоопределения, для BLE устройств необходимо указать MAC адрес" + }, + "correction": { + "desc": "Коррекция показаний", + "offset": "Компенсация (смещение)", + "factor": "Множитель" + }, + "filtering": { + "desc": "Фильтрация показаний", + "enabled": { + "title": "Включить фильтрацию", + "note": "Может быть полезно, если на графиках много резкого шума. В качестве фильтра используется \"бегущее среднее\"." + }, + "factor": { + "title": "Коэфф. фильтрации", + "note": "Чем меньше коэф., тем плавнее и дольше изменение числовых значений." + } + } + }, + "settings": { "title": "Настройки - OpenTherm Gateway", "name": "Настройки", @@ -176,8 +243,6 @@ "pid": "Настройки ПИД", "ot": "Настройки OpenTherm", "mqtt": "Настройки MQTT", - "outdorSensor": "Настройки наружного датчика температуры", - "indoorSensor": "Настройки внутреннего датчика температуры", "extPump": "Настройки дополнительного насоса", "cascadeControl": "Настройки каскадного управления" }, @@ -231,21 +296,7 @@ "title": "Целевая температура", "note": "Важно: Целевая температура в помещении, если включена ОТ опция «Передать управление отоплением котлу».
Во всех остальных случаях целевая температура теплоносителя." }, - "treshold": "Пороговое время включения (сек)", - - "events": { - "desc": "События", - "network": "При отключении сети", - "mqtt": "При отключении MQTT", - "indoorSensorDisconnect": "При потере связи с датчиком внутренней темп.", - "outdoorSensorDisconnect": "При потере связи с датчиком наружной темп." - }, - - "regulators": { - "desc": "Используемые регуляторы", - "equitherm": "ПЗА (требуется внешний (DS18B20) или подключенный к котлу датчик наружной температуры)", - "pid": "ПИД (требуется внешний (DS18B20) датчик внутренней температуры)" - } + "treshold": "Пороговое время включения (сек)" }, "equitherm": { @@ -270,16 +321,9 @@ "inGpio": "Вход GPIO", "outGpio": "Выход GPIO", "ledGpio": "RX LED GPIO", - "memberIdCode": "Master MemberID код", + "memberId": "Master member ID", + "flags": "Master flags", "maxMod": "Макс. уровень модуляции", - "pressureFactor": { - "title": "Коэфф. коррекции давления", - "note": "Если давление отображается Х10 от реального, установите значение 0.1." - }, - "dhwFlowRateFactor": { - "title": "Коэфф. коррекции потока ГВС", - "note": "Если поток ГВС отображается Х10 от реального, установите значение 0.1." - }, "minPower": { "title": "Мин. мощность котла (кВт)", "note": "Это значение соответствует уровню модуляции котла 0–1%. Обычно можно найти в спецификации котла как \"минимальная полезная тепловая мощность\"." @@ -288,17 +332,6 @@ "title": "Макс. мощность котла (кВт)", "note": "0 - попробовать определить автоматически. Обычно можно найти в спецификации котла как \"максимальная полезная тепловая мощность\"." }, - "fnv": { - "desc": "Фильтрация числовых значений", - "enable": { - "title": "Включить фильтрацию", - "note": "Может быть полезно, если на графиках много резкого шума. В качестве фильтра используется \"бегущее среднее\"." - }, - "factor": { - "title": "Коэфф. фильтрации", - "note": "Чем меньше коэф., тем плавнее и дольше изменение числовых значений." - } - }, "options": { "desc": "Опции", @@ -329,20 +362,6 @@ "interval": "Интервал публикации (сек)" }, - "tempSensor": { - "source": { - "type": "Источник данных", - "boilerOutdoor": "От котла через OpenTherm", - "boilerReturn": "Температура обратки через OpenTherm", - "manual": "Вручную через MQTT/API", - "ext": "Внешний датчик (DS18B20)", - "ble": "BLE устройство" - }, - "gpio": "GPIO", - "offset": "Смещение температуры (калибровка)", - "bleAddress": "MAC адрес BLE устройства" - }, - "extPump": { "use": "Использовать доп. насос", "gpio": "GPIO реле", @@ -366,7 +385,7 @@ "invertState": "Инвертировать состояние GPIO", "thresholdTime": "Пороговое время изменения состояния (сек)", "events": { - "title": "События", + "desc": "События", "onFault": "Если состояние fault (ошибки) активно", "onLossConnection": "Если соединение по OpenTherm потеряно", "onEnabledHeating": "Если отопление включено" diff --git a/src_data/pages/dashboard.html b/src_data/pages/dashboard.html index 554327e..7cfa2f5 100644 --- a/src_data/pages/dashboard.html +++ b/src_data/pages/dashboard.html @@ -42,31 +42,31 @@

dashboard.name

dashboard.thermostat.heating
-
-
dashboard.thermostat.temp.current:
+
+
dashboard.thermostat.temp.current:
-
-
+
+
- - + + - - + +
dashboard.thermostat.dhw
-
-
dashboard.thermostat.temp.current:
+
+
dashboard.thermostat.temp.current:
-
-
+
+
- - + +
@@ -79,132 +79,112 @@

dashboard.name

- - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - + + - - + + + + - - + + - - + + - - + + - - + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + - - + + - - + + - - + + - - + + - - + +
dashboard.state.otdashboard.states.mNetworkConnected
dashboard.state.mqttdashboard.states.mMqttConnected
dashboard.state.emergencydashboard.states.mEmergencyState
dashboard.state.heatingdashboard.states.mExtPumpState
dashboard.state.dhwdashboard.states.mCascadeControlInput
dashboard.state.flame
dashboard.state.fault
dashboard.state.diag
dashboard.state.extpump
dashboard.state.outdoorSensorConnected
dashboard.state.outdoorSensorRssi dbm
dashboard.state.outdoorSensorHumidity %
dashboard.state.outdoorSensorBattery %dashboard.states.mCascadeControlOutput
dashboard.state.indoorSensorConnecteddashboard.states.sConnected
dashboard.state.cascadeControlInputdashboard.states.sFlame
dashboard.state.cascadeControlOutputdashboard.states.sFaultActive
dashboard.state.indoorSensorRssi dbmdashboard.states.sFaultCode
dashboard.state.indoorSensorHumidity %dashboard.states.sDiagActive
dashboard.state.indoorSensorBattery %dashboard.states.sDiagCode
dashboard.state.modulation %dashboard.states.mHeatEnabled
dashboard.state.pressure dashboard.states.mHeatBlocking
dashboard.state.dhwFlowRate /mindashboard.states.sHeatActive
dashboard.state.power kwdashboard.states.mHeatTargetTemp
dashboard.state.faultCodedashboard.states.mHeatCurrTemp
dashboard.state.diagCodedashboard.states.mHeatRetTemp
dashboard.state.indoorTemp dashboard.states.mHeatIndoorTemp
dashboard.state.outdoorTemp dashboard.states.mHeatOutdoorTemp
dashboard.state.heatingTemp dashboard.states.mDhwEnabled
dashboard.state.heatingSetpointTemp dashboard.states.sDhwActive
dashboard.state.heatingReturnTemp dashboard.states.mDhwTargetTemp
dashboard.state.dhwTemp dashboard.states.mDhwCurrTemp
dashboard.state.exhaustTemp dashboard.states.mDhwRetTemp
@@ -213,15 +193,17 @@

dashboard.name


- dashboard.section.otDiag -
Vendor:          
-Member ID:       
-Flags:           
-Type:            
-Version:         
-OT version:      
-Heating limits:  ... 
-DHW limits:      ... 
+ dashboard.section.diag +
Vendor:             
+Member ID:          
+Flags:              
+Type:               
+AppVersion:         
+OT version:         
+Modulation limits:  ... %
+Power limits:       ... kW
+Heating limits:     ... 
+DHW limits:         ... 
@@ -259,7 +241,7 @@

dashboard.name

const lang = new Lang(document.getElementById('lang')); lang.build(); - document.querySelector('#thermostat-heating-minus').addEventListener('click', (event) => { + document.querySelector('#tHeatActionMinus').addEventListener('click', (event) => { if (!prevSettings) { return; } @@ -278,10 +260,10 @@

dashboard.name

newSettings.heating.target = minTemp; } - setValue('#thermostat-heating-target', newSettings.heating.target); + setValue('#tHeatTargetTemp', newSettings.heating.target); }); - document.querySelector('#thermostat-heating-plus').addEventListener('click', (event) => { + document.querySelector('#tHeatActionPlus').addEventListener('click', (event) => { if (!prevSettings) { return; } @@ -300,10 +282,10 @@

dashboard.name

newSettings.heating.target = maxTemp; } - setValue('#thermostat-heating-target', newSettings.heating.target); + setValue('#tHeatTargetTemp', newSettings.heating.target); }); - document.querySelector('#thermostat-dhw-minus').addEventListener('click', (event) => { + document.querySelector('#tDhwActionMinus').addEventListener('click', (event) => { if (!prevSettings) { return; } @@ -315,10 +297,10 @@

dashboard.name

newSettings.dhw.target = prevSettings.dhw.minTemp; } - setValue('#thermostat-dhw-target', newSettings.dhw.target); + setValue('#tDhwTargetTemp', newSettings.dhw.target); }); - document.querySelector('#thermostat-dhw-plus').addEventListener('click', (event) => { + document.querySelector('#tDhwActionPlus').addEventListener('click', (event) => { if (!prevSettings) { return; } @@ -330,22 +312,22 @@

dashboard.name

newSettings.dhw.target = prevSettings.dhw.maxTemp; } - setValue('#thermostat-dhw-target', newSettings.dhw.target); + setValue('#tDhwTargetTemp', newSettings.dhw.target); }); - document.querySelector('#thermostat-heating-enabled').addEventListener('change', (event) => { + document.querySelector('#tHeatEnabled').addEventListener('change', (event) => { modifiedTime = Date.now(); - newSettings.heating.enable = event.currentTarget.checked; + newSettings.heating.enabled = event.currentTarget.checked; }); - document.querySelector('#thermostat-heating-turbo').addEventListener('change', (event) => { + document.querySelector('#tHeatTurbo').addEventListener('change', (event) => { modifiedTime = Date.now(); newSettings.heating.turbo = event.currentTarget.checked; }); - document.querySelector('#thermostat-dhw-enabled').addEventListener('change', (event) => { + document.querySelector('#tDhwEnabled').addEventListener('change', (event) => { modifiedTime = Date.now(); - newSettings.dhw.enable = event.currentTarget.checked; + newSettings.dhw.enabled = event.currentTarget.checked; }); setTimeout(async function onLoadPage() { @@ -361,10 +343,10 @@

dashboard.name

// settings try { let modified = prevSettings && ( - (prevSettings.heating.enable != newSettings.heating.enable) + (prevSettings.heating.enabled != newSettings.heating.enabled) || (prevSettings.heating.turbo != newSettings.heating.turbo) || (prevSettings.heating.target != newSettings.heating.target) - || (prevSettings.opentherm.dhwPresent && prevSettings.dhw.enable != newSettings.dhw.enable) + || (prevSettings.opentherm.dhwPresent && prevSettings.dhw.enabled != newSettings.dhw.enabled) || (prevSettings.opentherm.dhwPresent && prevSettings.dhw.target != newSettings.dhw.target) ); @@ -384,12 +366,12 @@

dashboard.name

} const result = await response.json(); - noRegulators = !result.opentherm.nativeHeatingControl && !result.equitherm.enable && !result.pid.enable; + noRegulators = !result.opentherm.nativeHeatingControl && !result.equitherm.enabled && !result.pid.enabled; prevSettings = result; - newSettings.heating.enable = result.heating.enable; + newSettings.heating.enabled = result.heating.enabled; newSettings.heating.turbo = result.heating.turbo; newSettings.heating.target = result.heating.target; - newSettings.dhw.enable = result.dhw.enable; + newSettings.dhw.enabled = result.dhw.enabled; newSettings.dhw.target = result.dhw.target; if (result.opentherm.dhwPresent) { @@ -398,16 +380,16 @@

dashboard.name

hide('#thermostat-dhw'); } - setCheckboxValue('#thermostat-heating-enabled', result.heating.enable); - setCheckboxValue('#thermostat-heating-turbo', result.heating.turbo); - setValue('#thermostat-heating-target', result.heating.target); + setCheckboxValue('#tHeatEnabled', result.heating.enabled); + setCheckboxValue('#tHeatTurbo', result.heating.turbo); + setValue('#tHeatTargetTemp', result.heating.target); - setCheckboxValue('#thermostat-dhw-enabled', result.dhw.enable); - setValue('#thermostat-dhw-target', result.dhw.target); + setCheckboxValue('#tDhwEnabled', result.dhw.enabled); + setValue('#tDhwTargetTemp', result.dhw.target); - setValue('.temp-unit', temperatureUnit(result.system.unitSystem)); - setValue('.pressure-unit', pressureUnit(result.system.unitSystem)); - setValue('.volume-unit', volumeUnit(result.system.unitSystem)); + setValue('.tempUnit', temperatureUnit(result.system.unitSystem)); + setValue('.pressureUnit', pressureUnit(result.system.unitSystem)); + setValue('.volumeUnit', volumeUnit(result.system.unitSystem)); } catch (error) { console.log(error); @@ -421,67 +403,83 @@

dashboard.name

} const result = await response.json(); - setValue('#thermostat-heating-current', noRegulators ? result.temperatures.heating : result.temperatures.indoor); - setValue('#thermostat-dhw-current', result.temperatures.dhw); - - setState('#ot-connected', result.states.otStatus); - setState('#mqtt-connected', result.states.mqtt); - setState('#ot-emergency', result.states.emergency); - setState('#ot-heating', result.states.heating); - setState('#ot-dhw', result.states.dhw); - setState('#ot-flame', result.states.flame); - setState('#ot-fault', result.states.fault); - setState('#ot-diagnostic', result.states.diagnostic); - setState('#ot-external-pump', result.states.externalPump); - setState('#outdoor-sensor-connected', result.sensors.outdoor.connected); - setState('#indoor-sensor-connected', result.sensors.indoor.connected); - setState('#cc-input', result.cascadeControl.input); - setState('#cc-output', result.cascadeControl.output); - - setValue('#outdoor-sensor-rssi', result.sensors.outdoor.rssi); - setValue('#outdoor-sensor-humidity', result.sensors.outdoor.humidity); - setValue('#outdoor-sensor-battery', result.sensors.outdoor.battery); - setValue('#indoor-sensor-rssi', result.sensors.indoor.rssi); - setValue('#indoor-sensor-humidity', result.sensors.indoor.humidity); - setValue('#indoor-sensor-battery', result.sensors.indoor.battery); - - setValue('#ot-modulation', result.sensors.modulation); - setValue('#ot-pressure', result.sensors.pressure); - setValue('#ot-dhw-flow-rate', result.sensors.dhwFlowRate); - setValue('#ot-power', result.sensors.power); + + // Graph + setValue('#tHeatCurrentTemp', result.master.indoorTempControl + ? result.master.heating.indoorTemp + : result.master.heating.currentTemp + ); + setValue('#tDhwCurrentTemp', result.master.dhw.currentTemp); + + + // SLAVE + setValue('.sMemberId', result.slave.memberId); + setValue('.sVendor', memberIdToVendor(result.slave.memberId)); + setValue('.sFlags', result.slave.flags); + setValue('.sType', result.slave.type); + setValue('.sAppVersion', result.slave.appVersion); + setValue('.sProtocolVersion', result.slave.protocolVersion); + + setState('.sConnected', result.slave.connected); + setState('.sFlame', result.slave.flame); + + setValue('.sModMin', result.slave.modulation.min); + setValue('.sModMax', result.slave.modulation.max); + + setValue('.sPowerMin', result.slave.power.min); + setValue('.sPowerMax', result.slave.power.max); + + setState('.sHeatActive', result.slave.heating.active); + setValue('.sHeatMinTemp', result.slave.heating.minTemp); + setValue('.sHeatMaxTemp', result.slave.heating.maxTemp); + + setState('.sDhwActive', result.slave.dhw.active); + setValue('.sDhwMinTemp', result.slave.dhw.minTemp); + setValue('.sDhwMaxTemp', result.slave.dhw.maxTemp); + + setState('.sFaultActive', result.slave.fault.active); setValue( - '#ot-fault-code', - result.sensors.faultCode - ? (result.sensors.faultCode + " (0x" + dec2hex(result.sensors.faultCode) + ")") + '.sFaultCode', + result.slave.fault.active + ? (result.slave.fault.code + " (0x" + dec2hex(result.slave.fault.code) + ")") : "-" ); + + setState('.sDiagActive', result.slave.diag.active); setValue( - '#ot-diag-code', - result.sensors.diagnosticCode - ? (result.sensors.diagnosticCode + " (0x" + dec2hex(result.sensors.diagnosticCode) + ")") + '.sDiagCode', + result.slave.diag.active + ? (result.slave.diag.code + " (0x" + dec2hex(result.slave.diag.code) + ")") : "-" ); - setValue('#indoor-temp', result.temperatures.indoor); - setValue('#outdoor-temp', result.temperatures.outdoor); - setValue('#heating-temp', result.temperatures.heating); - setValue('#heating-return-temp', result.temperatures.heatingReturn); - setValue('#dhw-temp', result.temperatures.dhw); - setValue('#exhaust-temp', result.temperatures.exhaust); - - setValue('#heating-min-temp', result.parameters.heatingMinTemp); - setValue('#heating-max-temp', result.parameters.heatingMaxTemp); - setValue('#heating-setpoint-temp', result.parameters.heatingSetpoint); - setValue('#dhw-min-temp', result.parameters.dhwMinTemp); - setValue('#dhw-max-temp', result.parameters.dhwMaxTemp); - - setValue('#slave-member-id', result.parameters.slaveMemberId); - setValue('#slave-vendor', memberIdToVendor(result.parameters.slaveMemberId)); - - setValue('#slave-flags', result.parameters.slaveFlags); - setValue('#slave-type', result.parameters.slaveType); - setValue('#slave-version', result.parameters.slaveVersion); - setValue('#slave-ot-version', result.parameters.slaveOtVersion); + + // MASTER + setState('.mHeatEnabled', result.master.heating.enabled); + setState('.mHeatBlocking', result.master.heating.blocking); + setState('.mHeatIndoorTempControl', result.master.heating.indoorTempControl); + setValue('.mHeatTargetTemp', result.master.heating.targetTemp); + setValue('.mHeatCurrTemp', result.master.heating.currentTemp); + setValue('.mHeatRetTemp', result.master.heating.returnTemp); + setValue('.mHeatIndoorTemp', result.master.heating.indoorTemp); + setValue('.mHeatOutdoorTemp', result.master.heating.outdoorTemp); + setValue('.mHeatMinTemp', result.master.heating.minTemp); + setValue('.mHeatMaxTemp', result.master.heating.maxTemp); + + setState('.mDhwEnabled', result.master.dhw.enabled); + setValue('.mDhwTargetTemp', result.master.dhw.targetTemp); + setValue('.mDhwCurrTemp', result.master.dhw.currentTemp); + setValue('.mDhwRetTemp', result.master.dhw.returnTemp); + setValue('.mDhwMinTemp', result.master.dhw.minTemp); + setValue('.mDhwMaxTemp', result.master.dhw.maxTemp); + + setState('.mNetworkConnected', result.master.network.connected); + setState('.mMqttConnected', result.master.mqtt.connected); + setState('.mEmergencyState', result.master.emergency.state); + setState('.mExtPumpState', result.master.externalPump.state); + setState('.mCascadeControlInput', result.master.cascadeControl.input); + setState('.mCascadeControlOutput', result.master.cascadeControl.output); + setBusy('#dashboard-busy', '#dashboard-container', false); } catch (error) { diff --git a/src_data/pages/index.html b/src_data/pages/index.html index f23ba8e..f681058 100644 --- a/src_data/pages/index.html +++ b/src_data/pages/index.html @@ -139,9 +139,10 @@

index.section.system

-
+ diff --git a/src_data/pages/sensors.html b/src_data/pages/sensors.html new file mode 100644 index 0000000..9373ed4 --- /dev/null +++ b/src_data/pages/sensors.html @@ -0,0 +1,283 @@ + + + + + + sensors.title + + + + +
+ +
+ +
+
+
+

sensors.name

+

+
+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/src_data/pages/settings.html b/src_data/pages/settings.html index ca36dfe..ce45530 100644 --- a/src_data/pages/settings.html +++ b/src_data/pages/settings.html @@ -371,8 +371,13 @@

settings.name

+ + -
- -
- settings.ot.advanced -
-
- - - -
-
- -
- settings.ot.fnv.desc - - - - -
-
-
@@ -556,120 +523,6 @@

settings.name


-
- settings.section.outdorSensor -
-
- -
-
- -
- -
- settings.section.indoorSensor -
-
- -
-
- -
-
settings.section.extPump
@@ -722,7 +575,7 @@

settings.name

@@ -750,7 +603,7 @@

settings.name

@@ -815,13 +668,12 @@

settings.name

const lang = new Lang(document.getElementById('lang')); lang.build(); - const fillData = (data) => { // System setSelectValue('#system-log-level', data.system.logLevel); - setCheckboxValue('#system-serial-enable', data.system.serial.enable); + setCheckboxValue('#system-serial-enable', data.system.serial.enabled); setSelectValue('#system-serial-baudrate', data.system.serial.baudrate); - setCheckboxValue('#system-telnet-enable', data.system.telnet.enable); + setCheckboxValue('#system-telnet-enable', data.system.telnet.enabled); setInputValue('#system-telnet-port', data.system.telnet.port); setRadioValue('.system-unit-system', data.system.unitSystem); setInputValue('#system-status-led-gpio', data.system.statusLedGpio < 255 ? data.system.statusLedGpio : ''); @@ -838,10 +690,9 @@

settings.name

setInputValue('#opentherm-in-gpio', data.opentherm.inGpio < 255 ? data.opentherm.inGpio : ''); setInputValue('#opentherm-out-gpio', data.opentherm.outGpio < 255 ? data.opentherm.outGpio : ''); setInputValue('#opentherm-rx-led-gpio', data.opentherm.rxLedGpio < 255 ? data.opentherm.rxLedGpio : ''); - setInputValue('#opentherm-member-id-code', data.opentherm.memberIdCode); + setInputValue('#opentherm-member-id', data.opentherm.memberId); + setInputValue('#opentherm-flags', data.opentherm.flags); setInputValue('#opentherm-max-modulation', data.opentherm.maxModulation); - setInputValue('#opentherm-pressure-factor', data.opentherm.pressureFactor); - setInputValue('#opentherm-dhw-fr-factor', data.opentherm.dhwFlowRateFactor); setInputValue('#opentherm-min-power', data.opentherm.minPower); setInputValue('#opentherm-max-power', data.opentherm.maxPower); setCheckboxValue('#opentherm-dhw-present', data.opentherm.dhwPresent); @@ -854,12 +705,10 @@

settings.name

setCheckboxValue('#opentherm-get-min-max-temp', data.opentherm.getMinMaxTemp); setCheckboxValue('#opentherm-native-heating-control', data.opentherm.nativeHeatingControl); setCheckboxValue('#opentherm-immergas-fix', data.opentherm.immergasFix); - setCheckboxValue('#opentherm-fnv-enable', data.opentherm.filterNumValues.enable); - setInputValue('#opentherm-fnv-factor', data.opentherm.filterNumValues.factor); setBusy('#opentherm-settings-busy', '#opentherm-settings', false); // MQTT - setCheckboxValue('#mqtt-enable', data.mqtt.enable); + setCheckboxValue('#mqtt-enable', data.mqtt.enabled); setCheckboxValue('#mqtt-ha-discovery', data.mqtt.homeAssistantDiscovery); setInputValue('#mqtt-server', data.mqtt.server); setInputValue('#mqtt-port', data.mqtt.port); @@ -869,20 +718,6 @@

settings.name

setInputValue('#mqtt-interval', data.mqtt.interval); setBusy('#mqtt-settings-busy', '#mqtt-settings', false); - // Outdoor sensor - setRadioValue('.outdoor-sensor-type', data.sensors.outdoor.type); - setInputValue('#outdoor-sensor-gpio', data.sensors.outdoor.gpio < 255 ? data.sensors.outdoor.gpio : ''); - setInputValue('#outdoor-sensor-offset', data.sensors.outdoor.offset); - setInputValue('#outdoor-sensor-ble-addresss', data.sensors.outdoor.bleAddress); - setBusy('#outdoor-sensor-settings-busy', '#outdoor-sensor-settings', false); - - // Indoor sensor - setRadioValue('.indoor-sensor-type', data.sensors.indoor.type); - setInputValue('#indoor-sensor-gpio', data.sensors.indoor.gpio < 255 ? data.sensors.indoor.gpio : ''); - setInputValue('#indoor-sensor-offset', data.sensors.indoor.offset); - setInputValue('#indoor-sensor-ble-addresss', data.sensors.indoor.bleAddress); - setBusy('#indoor-sensor-settings-busy', '#indoor-sensor-settings', false); - // Extpump setCheckboxValue('#extpump-use', data.externalPump.use); setInputValue('#extpump-gpio', data.externalPump.gpio < 255 ? data.externalPump.gpio : ''); @@ -892,12 +727,12 @@

settings.name

setBusy('#extpump-settings-busy', '#extpump-settings', false); // Cascade control - setCheckboxValue('#cc-input-enable', data.cascadeControl.input.enable); + setCheckboxValue('#cc-input-enable', data.cascadeControl.input.enabled); setInputValue('#cc-input-gpio', data.cascadeControl.input.gpio < 255 ? data.cascadeControl.input.gpio : ''); setCheckboxValue('#cc-input-invert-state', data.cascadeControl.input.invertState); setInputValue('#cc-input-tt', data.cascadeControl.input.thresholdTime); - setCheckboxValue('#cc-output-enable', data.cascadeControl.output.enable); + setCheckboxValue('#cc-output-enable', data.cascadeControl.output.enabled); setInputValue('#cc-output-gpio', data.cascadeControl.output.gpio < 255 ? data.cascadeControl.output.gpio : ''); setCheckboxValue('#cc-output-invert-state', data.cascadeControl.output.invertState); setInputValue('#cc-output-tt', data.cascadeControl.output.thresholdTime); @@ -948,20 +783,20 @@

settings.name

setBusy('#emergency-settings-busy', '#emergency-settings', false); // Equitherm - setCheckboxValue('#equitherm-enable', data.equitherm.enable); + setCheckboxValue('#equitherm-enable', data.equitherm.enabled); setInputValue('#equitherm-n-factor', data.equitherm.n_factor); setInputValue('#equitherm-k-factor', data.equitherm.k_factor); setInputValue('#equitherm-t-factor', data.equitherm.t_factor); setBusy('#equitherm-settings-busy', '#equitherm-settings', false); // PID - setCheckboxValue('#pid-enable', data.pid.enable); + setCheckboxValue('#pid-enable', data.pid.enabled); setInputValue('#pid-p-factor', data.pid.p_factor); setInputValue('#pid-i-factor', data.pid.i_factor); setInputValue('#pid-d-factor', data.pid.d_factor); setInputValue('#pid-dt', data.pid.dt); setInputValue('#pid-min-temp', data.pid.minTemp, { - "min": data.equitherm.enable ? (data.system.unitSystem == 0 ? -100 : -146) : (data.system.unitSystem == 0 ? 0 : 32), + "min": data.equitherm.enabled ? (data.system.unitSystem == 0 ? -100 : -146) : (data.system.unitSystem == 0 ? 0 : 32), "max": (data.system.unitSystem == 0 ? 99 : 211) }); setInputValue('#pid-max-temp', data.pid.maxTemp, { @@ -989,8 +824,6 @@

settings.name

setupForm('#pid-settings', fillData); setupForm('#opentherm-settings', fillData); setupForm('#mqtt-settings', fillData, ['mqtt.user', 'mqtt.password', 'mqtt.prefix']); - setupForm('#outdoor-sensor-settings', fillData); - setupForm('#indoor-sensor-settings', fillData, ['sensors.indoor.bleAddress']); setupForm('#extpump-settings', fillData); setupForm('#cc-settings', fillData); diff --git a/src_data/scripts/utils.js b/src_data/scripts/utils.js index 4d380a0..01217ed 100644 --- a/src_data/scripts/utils.js +++ b/src_data/scripts/utils.js @@ -1,4 +1,4 @@ -function setupForm(formSelector, onResultCallback = null, noCastItems = []) { +const setupForm = (formSelector, onResultCallback = null, noCastItems = []) => { const form = document.querySelector(formSelector); if (!form) { return; @@ -10,13 +10,13 @@ function setupForm(formSelector, onResultCallback = null, noCastItems = []) { }) }); - const url = form.action; - let button = form.querySelector('button[type="submit"]'); - let defaultText; - form.addEventListener('submit', async (event) => { event.preventDefault(); + const url = form.action; + let button = form.querySelector('button[type="submit"]'); + let defaultText; + if (button) { defaultText = button.textContent; button.textContent = i18n("button.wait"); @@ -86,7 +86,7 @@ function setupForm(formSelector, onResultCallback = null, noCastItems = []) { }); } -function setupNetworkScanForm(formSelector, tableSelector) { +const setupNetworkScanForm = (formSelector, tableSelector) => { const form = document.querySelector(formSelector); if (!form) { console.error("form not found"); @@ -132,7 +132,7 @@ function setupNetworkScanForm(formSelector, tableSelector) { let row = tbody.insertRow(-1); row.classList.add("network"); row.setAttribute('data-ssid', result[i].hidden ? '' : result[i].ssid); - row.onclick = function () { + row.onclick = () => { const input = document.querySelector('input#sta-ssid'); const ssid = this.getAttribute('data-ssid'); if (!input || !ssid) { @@ -246,7 +246,7 @@ function setupNetworkScanForm(formSelector, tableSelector) { onSubmitFn(); } -function setupRestoreBackupForm(formSelector) { +const setupRestoreBackupForm = (formSelector) => { const form = document.querySelector(formSelector); if (!form) { return; @@ -266,7 +266,7 @@ function setupRestoreBackupForm(formSelector) { button.setAttribute('aria-busy', true); } - const onSuccess = (response) => { + const onSuccess = () => { if (button) { button.textContent = i18n('button.restored'); button.classList.add('success'); @@ -280,7 +280,7 @@ function setupRestoreBackupForm(formSelector) { } }; - const onFailed = (response) => { + const onFailed = () => { if (button) { button.textContent = i18n('button.error'); button.classList.add('failed'); @@ -302,35 +302,79 @@ function setupRestoreBackupForm(formSelector) { let reader = new FileReader(); reader.readAsText(files[0]); - reader.onload = async function () { + reader.onload = async (event) => { try { - let response = await fetch(url, { - method: 'POST', - cache: 'no-cache', - headers: { - 'Content-Type': 'application/json' - }, - body: reader.result - }); - - if (response.ok) { - onSuccess(response); + const data = JSON.parse(event.target.result); + console.log("Backup: ", data); + + if (data.network != undefined) { + let response = await fetch(url, { + method: 'POST', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data.network) + }); + + if (!response.ok) { + onFailed(); + return; + } + } - } else { - onFailed(response); + if (data.settings != undefined) { + let response = await fetch(url, { + method: 'POST', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data.settings) + }); + + if (!response.ok) { + onFailed(); + return; + } } + if (data.sensors != undefined) { + for (const sensorId in data.sensors) { + const payload = { + "sensors": {} + }; + payload["sensors"][sensorId] = data.sensors[sensorId]; + + const response = await fetch(url, { + method: 'POST', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(payload) + }); + + if (!response.ok) { + onFailed(); + return; + } + } + } + + onSuccess(); + } catch (err) { - onFailed(false); + onFailed(); } }; - reader.onerror = function () { + reader.onerror = () => { console.log(reader.error); }; }); } -function setupUpgradeForm(formSelector) { +const setupUpgradeForm = (formSelector) => { const form = document.querySelector(formSelector); if (!form) { return; @@ -471,19 +515,23 @@ function setupUpgradeForm(formSelector) { } -function setBusy(busySelector, contentSelector, value) { +const setBusy = (busySelector, contentSelector, value, parent = undefined) => { if (!value) { - hide(busySelector); - show(contentSelector); + hide(busySelector, parent); + show(contentSelector, parent); } else { - show(busySelector); - hide(contentSelector); + show(busySelector, parent); + hide(contentSelector, parent); } } -function setState(selector, value) { - let item = document.querySelector(selector); +const setState = (selector, value, parent = undefined) => { + if (parent == undefined) { + parent = document; + } + + let item = parent.querySelector(selector); if (!item) { return; } @@ -491,8 +539,12 @@ function setState(selector, value) { item.setAttribute('aria-invalid', !value); } -function setValue(selector, value) { - let items = document.querySelectorAll(selector); +const setValue = (selector, value, parent = undefined) => { + if (parent == undefined) { + parent = document; + } + + let items = parent.querySelectorAll(selector); if (!items.length) { return; } @@ -502,8 +554,12 @@ function setValue(selector, value) { } } -function setCheckboxValue(selector, value) { - let item = document.querySelector(selector); +const setCheckboxValue = (selector, value, parent = undefined) => { + if (parent == undefined) { + parent = document; + } + + let item = parent.querySelector(selector); if (!item) { return; } @@ -511,8 +567,12 @@ function setCheckboxValue(selector, value) { item.checked = value; } -function setRadioValue(selector, value) { - let items = document.querySelectorAll(selector); +const setRadioValue = (selector, value, parent = undefined) => { + if (parent == undefined) { + parent = document; + } + + let items = parent.querySelectorAll(selector); if (!items.length) { return; } @@ -522,8 +582,12 @@ function setRadioValue(selector, value) { } } -function setInputValue(selector, value, attrs = {}) { - let items = document.querySelectorAll(selector); +const setInputValue = (selector, value, attrs = {}, parent = undefined) => { + if (parent == undefined) { + parent = document; + } + + let items = parent.querySelectorAll(selector); if (!items.length) { return; } @@ -539,8 +603,12 @@ function setInputValue(selector, value, attrs = {}) { } } -function setSelectValue(selector, value) { - let item = document.querySelector(selector); +const setSelectValue = (selector, value, parent = undefined) => { + if (parent == undefined) { + parent = document; + } + + let item = parent.querySelector(selector); if (!item) { return; } @@ -550,8 +618,12 @@ function setSelectValue(selector, value) { } } -function show(selector) { - let items = document.querySelectorAll(selector); +const show = (selector, parent = undefined) => { + if (parent == undefined) { + parent = document; + } + + let items = parent.querySelectorAll(selector); if (!items.length) { return; } @@ -563,8 +635,12 @@ function show(selector) { } } -function hide(selector) { - let items = document.querySelectorAll(selector); +const hide = (selector, parent = undefined) => { + if (parent == undefined) { + parent = document; + } + + let items = parent.querySelectorAll(selector); if (!items.length) { return; } @@ -582,28 +658,28 @@ function unit2str(unitSystem, units = {}, defaultValue = '?') { : defaultValue; } -function temperatureUnit(unitSystem) { +const temperatureUnit = (unitSystem) => { return unit2str(unitSystem, { 0: "°C", 1: "°F" }); } -function pressureUnit(unitSystem) { +const pressureUnit = (unitSystem) => { return unit2str(unitSystem, { 0: "bar", 1: "psi" }); } -function volumeUnit(unitSystem) { +const volumeUnit = (unitSystem) => { return unit2str(unitSystem, { 0: "L", 1: "gal" }); } -function memberIdToVendor(memberId) { +const memberIdToVendor = (memberId) => { // https://github.com/Jeroen88/EasyOpenTherm/blob/main/src/EasyOpenTherm.h // https://github.com/Evgen2/SmartTherm/blob/v0.7/src/Web.cpp const vendorList = { From fcf7d61ca59493d88c67706464d31bf4c8c02171 Mon Sep 17 00:00:00 2001 From: Yurii Date: Sat, 9 Nov 2024 17:41:39 +0300 Subject: [PATCH 02/42] fix: polling ntc on esp8266 fixed --- src/SensorsTask.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/SensorsTask.h b/src/SensorsTask.h index 88abbc2..1f1208f 100644 --- a/src/SensorsTask.h +++ b/src/SensorsTask.h @@ -361,7 +361,12 @@ class SensorsTask : public LeanTask { continue; } + #ifdef ARDUINO_ARCH_ESP32 const auto value = analogReadMilliVolts(sSensor.gpio); + #else + const auto value = analogRead(sSensor.gpio) / 1023 * DEFAULT_NTC_VREF; + #endif + if (value < DEFAULT_NTC_VLOW_TRESHOLD) { if (Sensors::getConnectionStatusById(sensorId)) { Sensors::setConnectionStatusById(sensorId, false, false); From 5aea80e63069162dd723dbb1c2f55c8a8dcfbc72 Mon Sep 17 00:00:00 2001 From: Yurii Date: Sun, 10 Nov 2024 01:30:28 +0300 Subject: [PATCH 03/42] fix: fix typos --- src/Settings.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Settings.h b/src/Settings.h index 7bb9dc5..35fcf36 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -152,14 +152,14 @@ struct Settings { Sensors::Settings sensorsSettings[SENSORS_AMOUNT] = { { false, - "Indoor temp", + "Outdoor temp", Sensors::Purpose::OUTDOOR_TEMP, Sensors::Type::DALLAS_TEMP, DEFAULT_SENSOR_OUTDOOR_GPIO }, { false, - "Outdoor temp", + "Indoor temp", Sensors::Purpose::INDOOR_TEMP, Sensors::Type::DALLAS_TEMP, DEFAULT_SENSOR_INDOOR_GPIO From 6a9bd9673a43d9b806b4240bf9f068f154c8a282 Mon Sep 17 00:00:00 2001 From: Yurii Date: Sun, 10 Nov 2024 01:32:42 +0300 Subject: [PATCH 04/42] fix: ``Sensors::cleanDallasInstances()`` fixed --- src/SensorsTask.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/SensorsTask.h b/src/SensorsTask.h index 1f1208f..6eef520 100644 --- a/src/SensorsTask.h +++ b/src/SensorsTask.h @@ -119,7 +119,10 @@ class SensorsTask : public LeanTask { } void cleanDallasInstances() { - for (auto& [gpio, instance] : this->dallasInstances) { + // for (auto& [gpio, instance] : this->dallasInstances) { + auto it = this->dallasInstances.begin(); + while (it != this->dallasInstances.end()) { + auto gpio = it->first; bool instanceUsed = false; for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { @@ -135,8 +138,8 @@ class SensorsTask : public LeanTask { } } - if (!instanceUsed) {; - this->dallasInstances.erase(gpio); + if (!instanceUsed) { + it = this->dallasInstances.erase(it); this->owInstances.erase(gpio); this->dallasSearchTime.erase(gpio); this->dallasPolling.erase(gpio); @@ -145,6 +148,8 @@ class SensorsTask : public LeanTask { Log.sinfoln(FPSTR(L_SENSORS_DALLAS), F("Stopped on GPIO %hhu"), gpio); continue; } + + it++; } } From fda18cdb130ddda4d1427f6df4d09ed34d87ab67 Mon Sep 17 00:00:00 2001 From: Yurii Date: Mon, 11 Nov 2024 02:41:39 +0300 Subject: [PATCH 05/42] refactor: memory optimization for esp8266 --- lib/BufferedWebServer/BufferedWebServer.h | 3 +- lib/HomeAssistantHelper/HomeAssistantHelper.h | 2 +- lib/HomeAssistantHelper/strings.h | 2 + lib/WebServerHandlers/DynamicPage.h | 2 +- lib/WebServerHandlers/StaticPage.h | 9 +- lib/WebServerHandlers/UpgradeHandler.h | 6 +- platformio.ini | 4 +- src/HaHelper.h | 381 ++++---- src/MainTask.h | 10 +- src/MqttTask.h | 40 +- src/OpenThermTask.h | 4 +- src/PortalTask.h | 368 ++++---- src/SensorsTask.h | 12 +- src/Settings.h | 2 +- src/main.cpp | 3 + src/strings.h | 217 ++++- src/utils.h | 813 +++++++++--------- 17 files changed, 1067 insertions(+), 811 deletions(-) diff --git a/lib/BufferedWebServer/BufferedWebServer.h b/lib/BufferedWebServer/BufferedWebServer.h index 1ba3881..a991556 100644 --- a/lib/BufferedWebServer/BufferedWebServer.h +++ b/lib/BufferedWebServer/BufferedWebServer.h @@ -10,7 +10,8 @@ class BufferedWebServer { free(this->buffer); } - void send(int code, const char* contentType, const JsonVariantConst content, bool pretty = false) { + template + void send(int code, T contentType, const JsonVariantConst content, bool pretty = false) { #ifdef ARDUINO_ARCH_ESP8266 if (!this->webServer->chunkedResponseModeStart(code, contentType)) { this->webServer->send(505, F("text/html"), F("HTTP1.1 required")); diff --git a/lib/HomeAssistantHelper/HomeAssistantHelper.h b/lib/HomeAssistantHelper/HomeAssistantHelper.h index b18fe6d..c7417c8 100644 --- a/lib/HomeAssistantHelper/HomeAssistantHelper.h +++ b/lib/HomeAssistantHelper/HomeAssistantHelper.h @@ -110,7 +110,7 @@ class HomeAssistantHelper { topic.concat(this->devicePrefix); topic.concat(nameSeparator); topic.concat(name); - topic.concat("/config"); + topic.concat(F("/config")); return topic; } diff --git a/lib/HomeAssistantHelper/strings.h b/lib/HomeAssistantHelper/strings.h index e98237d..8ac68da 100644 --- a/lib/HomeAssistantHelper/strings.h +++ b/lib/HomeAssistantHelper/strings.h @@ -37,11 +37,13 @@ const char HA_DEVICE_CLASS[] PROGMEM = "device_class"; const char HA_UNIT_OF_MEASUREMENT[] PROGMEM = "unit_of_measurement"; const char HA_UNIT_OF_MEASUREMENT_C[] PROGMEM = "°C"; const char HA_UNIT_OF_MEASUREMENT_F[] PROGMEM = "°F"; +const char HA_UNIT_OF_MEASUREMENT_PERCENT[] PROGMEM = "%"; const char HA_ICON[] PROGMEM = "icon"; const char HA_MIN[] PROGMEM = "min"; const char HA_MAX[] PROGMEM = "max"; const char HA_STEP[] PROGMEM = "step"; const char HA_MODE[] PROGMEM = "mode"; +const char HA_MODE_BOX[] PROGMEM = "box"; const char HA_STATE_ON[] PROGMEM = "state_on"; const char HA_STATE_OFF[] PROGMEM = "state_off"; const char HA_PAYLOAD_ON[] PROGMEM = "payload_on"; diff --git a/lib/WebServerHandlers/DynamicPage.h b/lib/WebServerHandlers/DynamicPage.h index 4ad7182..8553a0d 100644 --- a/lib/WebServerHandlers/DynamicPage.h +++ b/lib/WebServerHandlers/DynamicPage.h @@ -61,7 +61,7 @@ class DynamicPage : public RequestHandler { } if (this->cacheHeader != nullptr) { - server.sendHeader("Cache-Control", this->cacheHeader); + server.sendHeader(F("Cache-Control"), this->cacheHeader); } #ifdef ARDUINO_ARCH_ESP8266 diff --git a/lib/WebServerHandlers/StaticPage.h b/lib/WebServerHandlers/StaticPage.h index 095635c..8b09721 100644 --- a/lib/WebServerHandlers/StaticPage.h +++ b/lib/WebServerHandlers/StaticPage.h @@ -8,7 +8,8 @@ class StaticPage : public RequestHandler { typedef std::function CanHandleCallback; typedef std::function BeforeSendCallback; - StaticPage(const char* uri, FS* fs, const char* path, const char* cacheHeader = nullptr) { + template + StaticPage(const char* uri, FS* fs, T path, const char* cacheHeader = nullptr) { this->uri = uri; this->fs = fs; this->path = path; @@ -55,7 +56,7 @@ class StaticPage : public RequestHandler { this->eTag = esp8266webserver::calcETag(*this->fs, this->path); } - if (server.header("If-None-Match").equals(this->eTag.c_str())) { + if (server.header(F("If-None-Match")).equals(this->eTag.c_str())) { server.send(304); return true; } @@ -80,12 +81,12 @@ class StaticPage : public RequestHandler { } if (this->cacheHeader != nullptr) { - server.sendHeader("Cache-Control", this->cacheHeader); + server.sendHeader(F("Cache-Control"), this->cacheHeader); } #if defined(ARDUINO_ARCH_ESP8266) if (server._eTagEnabled && this->eTag.length() > 0) { - server.sendHeader("ETag", this->eTag); + server.sendHeader(F("ETag"), this->eTag); } server.streamFile(file, F("text/html"), method); diff --git a/lib/WebServerHandlers/UpgradeHandler.h b/lib/WebServerHandlers/UpgradeHandler.h index 13012cf..14c7f93 100644 --- a/lib/WebServerHandlers/UpgradeHandler.h +++ b/lib/WebServerHandlers/UpgradeHandler.h @@ -93,10 +93,12 @@ class UpgradeHandler : public RequestHandler { void upload(WebServer& server, const String& uri, HTTPUpload& upload) override { UpgradeResult* result; - if (upload.name.equals("firmware")) { + if (upload.name.equals(F("firmware"))) { result = &this->firmwareResult; - } else if (upload.name.equals("filesystem")) { + + } else if (upload.name.equals(F("filesystem"))) { result = &this->filesystemResult; + } else { return; } diff --git a/platformio.ini b/platformio.ini index 835d884..9da4d56 100644 --- a/platformio.ini +++ b/platformio.ini @@ -59,7 +59,9 @@ build_flags = upload_speed = 921600 monitor_speed = 115200 ;monitor_filters = direct -monitor_filters = esp32_exception_decoder +monitor_filters = + esp32_exception_decoder + esp8266_exception_decoder board_build.flash_mode = dio board_build.filesystem = littlefs diff --git a/src/HaHelper.h b/src/HaHelper.h index 50755e6..149ebb4 100644 --- a/src/HaHelper.h +++ b/src/HaHelper.h @@ -8,6 +8,14 @@ class HaHelper : public HomeAssistantHelper { static const char AVAILABILITY_OT_CONN[]; static const char AVAILABILITY_SENSOR_CONN[]; + void updateCachedTopics() { + this->statusTopic = this->getDeviceTopic(F("status")); + this->stateTopic = this->getDeviceTopic(F("state")); + this->setStateTopic = this->getDeviceTopic(F("state/set")); + this->settingsTopic = this->getDeviceTopic(F("settings")); + this->setSettingsTopic = this->getDeviceTopic(F("settings/set")); + } + void setExpireAfter(unsigned short value) { this->expireAfter = value; } @@ -60,7 +68,7 @@ class HaHelper : public HomeAssistantHelper { case Sensors::Purpose::MODULATION_LEVEL: doc[FPSTR(HA_DEVICE_CLASS)] = F("power_factor"); - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("%"); + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_PERCENT); break; case Sensors::Purpose::CURRENT_POWER: @@ -80,7 +88,7 @@ class HaHelper : public HomeAssistantHelper { case Sensors::Purpose::HUMIDITY: doc[FPSTR(HA_DEVICE_CLASS)] = F("humidity"); - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = "%"; + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_PERCENT); break; default: @@ -180,7 +188,7 @@ class HaHelper : public HomeAssistantHelper { sName += F(" humidity"); doc[FPSTR(HA_DEVICE_CLASS)] = F("humidity"); - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = "%"; + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_PERCENT); doc[FPSTR(HA_NAME)] = sName; doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.humidity|float(0)|round(2) }}"); break; @@ -191,7 +199,7 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_DEVICE_CLASS)] = F("battery"); doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = "%"; + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_PERCENT); doc[FPSTR(HA_NAME)] = sName; doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.battery|float(0)|round(2) }}"); break; @@ -213,7 +221,7 @@ class HaHelper : public HomeAssistantHelper { } else if (sSensor.type == Sensors::Type::MANUAL) { doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); - doc[FPSTR(HA_MODE)] = "box"; + doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("sensors"), objId.c_str(), F("set")); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"value\": {{ value }}}"); @@ -245,7 +253,7 @@ class HaHelper : public HomeAssistantHelper { ); objId.clear(); - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_STATE_CLASS)] = FPSTR(HA_STATE_CLASS_MEASUREMENT); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; @@ -321,7 +329,7 @@ class HaHelper : public HomeAssistantHelper { objId.clear(); - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC); doc[FPSTR(HA_DEVICE_CLASS)] = F("connectivity"); @@ -371,12 +379,12 @@ class HaHelper : public HomeAssistantHelper { objId.clear(); - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; 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); - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("%"); + doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_PERCENT); doc[FPSTR(HA_ICON)] = F("mdi:signal"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.signalQuality|float(0)|round(0) }}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; @@ -397,18 +405,18 @@ class HaHelper : public HomeAssistantHelper { bool publishSwitchHeatingTurbo(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("heating_turbo")); + doc[FPSTR(HA_OBJECT_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"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_STATE_ON)] = true; doc[FPSTR(HA_STATE_OFF)] = false; doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.heating.turbo }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"heating\": {\"turbo\" : true}}"); doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"heating\": {\"turbo\" : false}}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; @@ -419,10 +427,10 @@ class HaHelper : public HomeAssistantHelper { bool publishInputHeatingHysteresis(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("heating_hysteresis")); + doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); @@ -435,14 +443,14 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_NAME)] = F("Heating hysteresis"); doc[FPSTR(HA_ICON)] = F("mdi:altimeter"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.heating.hysteresis|float(0)|round(2) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"hysteresis\" : {{ value }}}}"); doc[FPSTR(HA_MIN)] = 0; doc[FPSTR(HA_MAX)] = 15; doc[FPSTR(HA_STEP)] = 0.01f; - doc[FPSTR(HA_MODE)] = "box"; + doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -451,22 +459,22 @@ class HaHelper : public HomeAssistantHelper { bool publishInputHeatingTurboFactor(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("heating_turbo_factor")); + doc[FPSTR(HA_OBJECT_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"); doc[FPSTR(HA_ICON)] = F("mdi:multiplication-box"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.heating.turboFactor|float(0)|round(2) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"turboFactor\" : {{ value }}}}"); doc[FPSTR(HA_MIN)] = 1.5; doc[FPSTR(HA_MAX)] = 10; doc[FPSTR(HA_STEP)] = 0.01f; - doc[FPSTR(HA_MODE)] = "box"; + doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -475,10 +483,10 @@ class HaHelper : public HomeAssistantHelper { bool publishInputHeatingMinTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("heating_min_temp")); + doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); @@ -495,12 +503,12 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_NAME)] = F("Heating min temp"); doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-down"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.heating.minTemp|float(0)|round(1) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"minTemp\" : {{ value }}}}"); doc[FPSTR(HA_STEP)] = 1; - doc[FPSTR(HA_MODE)] = "box"; + doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -509,10 +517,10 @@ class HaHelper : public HomeAssistantHelper { bool publishInputHeatingMaxTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("heating_max_temp")); + doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); @@ -529,12 +537,12 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_NAME)] = F("Heating max temp"); doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-up"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.heating.maxTemp|float(0)|round(1) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"maxTemp\" : {{ value }}}}"); doc[FPSTR(HA_STEP)] = 1; - doc[FPSTR(HA_MODE)] = "box"; + doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -544,10 +552,10 @@ class HaHelper : public HomeAssistantHelper { bool publishInputDhwMinTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("dhw_min_temp")); + doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); @@ -564,12 +572,12 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_NAME)] = F("DHW min temp"); doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-down"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.dhw.minTemp|float(0)|round(1) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"minTemp\" : {{ value }}}}"); doc[FPSTR(HA_STEP)] = 1; - doc[FPSTR(HA_MODE)] = "box"; + doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -578,10 +586,10 @@ class HaHelper : public HomeAssistantHelper { bool publishInputDhwMaxTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("dhw_max_temp")); + doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); @@ -598,12 +606,12 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_NAME)] = F("DHW max temp"); doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-up"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.dhw.maxTemp|float(0)|round(1) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"maxTemp\" : {{ value }}}}"); doc[FPSTR(HA_STEP)] = 1; - doc[FPSTR(HA_MODE)] = "box"; + doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -613,20 +621,20 @@ class HaHelper : public HomeAssistantHelper { bool publishSwitchPid(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("pid")); + doc[FPSTR(HA_OBJECT_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"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_STATE_ON)] = true; doc[FPSTR(HA_STATE_OFF)] = false; doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.enabled }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); - doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"pid\": {\"enable\" : true}}"); - doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"pid\": {\"enable\" : false}}"); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); + doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"pid\": {\"enabled\" : true}}"); + doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"pid\": {\"enabled\" : false}}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -635,21 +643,21 @@ class HaHelper : public HomeAssistantHelper { bool publishInputPidFactorP(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("pid_p")); + doc[FPSTR(HA_OBJECT_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"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.p_factor|float(0)|round(3) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"p_factor\" : {{ value }}}}"); doc[FPSTR(HA_MIN)] = 0.1f; doc[FPSTR(HA_MAX)] = 1000; doc[FPSTR(HA_STEP)] = 0.1f; - doc[FPSTR(HA_MODE)] = "box"; + doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -658,21 +666,21 @@ class HaHelper : public HomeAssistantHelper { bool publishInputPidFactorI(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("pid_i")); + doc[FPSTR(HA_OBJECT_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"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.i_factor|float(0)|round(4) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"i_factor\" : {{ value }}}}"); doc[FPSTR(HA_MIN)] = 0; doc[FPSTR(HA_MAX)] = 100; doc[FPSTR(HA_STEP)] = 0.001f; - doc[FPSTR(HA_MODE)] = "box"; + doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -681,21 +689,21 @@ class HaHelper : public HomeAssistantHelper { bool publishInputPidFactorD(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("pid_d")); + doc[FPSTR(HA_OBJECT_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"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.d_factor|float(0)|round(3) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"d_factor\" : {{ value }}}}"); doc[FPSTR(HA_MIN)] = 0; doc[FPSTR(HA_MAX)] = 100000; doc[FPSTR(HA_STEP)] = 1; - doc[FPSTR(HA_MODE)] = "box"; + doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -704,23 +712,23 @@ class HaHelper : public HomeAssistantHelper { bool publishInputPidDt(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("pid_dt")); + doc[FPSTR(HA_OBJECT_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"); doc[FPSTR(HA_NAME)] = F("PID DT"); doc[FPSTR(HA_ICON)] = F("mdi:timer-cog-outline"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.dt|int(0) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"dt\" : {{ value }}}}"); doc[FPSTR(HA_MIN)] = 30; doc[FPSTR(HA_MAX)] = 1800; doc[FPSTR(HA_STEP)] = 1; - doc[FPSTR(HA_MODE)] = "box"; + doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -729,10 +737,10 @@ class HaHelper : public HomeAssistantHelper { bool publishInputPidMinTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("pid_min_temp")); + doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); @@ -749,12 +757,12 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_NAME)] = F("PID min temp"); doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-down"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.minTemp|float(0)|round(1) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"minTemp\" : {{ value }}}}"); doc[FPSTR(HA_STEP)] = 1; - doc[FPSTR(HA_MODE)] = "box"; + doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -763,10 +771,10 @@ class HaHelper : public HomeAssistantHelper { bool publishInputPidMaxTemp(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("pid_max_temp")); + doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature"); @@ -783,12 +791,12 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_NAME)] = F("PID max temp"); doc[FPSTR(HA_ICON)] = F("mdi:thermometer-chevron-up"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.maxTemp|float(0)|round(1) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"maxTemp\" : {{ value }}}}"); doc[FPSTR(HA_STEP)] = 1; - doc[FPSTR(HA_MODE)] = "box"; + doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -798,20 +806,20 @@ class HaHelper : public HomeAssistantHelper { bool publishSwitchEquitherm(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("equitherm")); + doc[FPSTR(HA_OBJECT_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"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_STATE_ON)] = true; doc[FPSTR(HA_STATE_OFF)] = false; doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.enabled }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); - doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"equitherm\": {\"enable\" : true}}"); - doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"equitherm\": {\"enable\" : false}}"); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); + doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"equitherm\": {\"enabled\" : true}}"); + doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"equitherm\": {\"enabled\" : false}}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -820,21 +828,21 @@ class HaHelper : public HomeAssistantHelper { bool publishInputEquithermFactorN(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("equitherm_n")); + doc[FPSTR(HA_OBJECT_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"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.n_factor|float(0)|round(3) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"n_factor\" : {{ value }}}}"); doc[FPSTR(HA_MIN)] = 0.001f; doc[FPSTR(HA_MAX)] = 10; doc[FPSTR(HA_STEP)] = 0.001f; - doc[FPSTR(HA_MODE)] = "box"; + doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -843,21 +851,21 @@ class HaHelper : public HomeAssistantHelper { bool publishInputEquithermFactorK(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("equitherm_k")); + doc[FPSTR(HA_OBJECT_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"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.k_factor|float(0)|round(2) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"k_factor\" : {{ value }}}}"); doc[FPSTR(HA_MIN)] = 0; doc[FPSTR(HA_MAX)] = 10; doc[FPSTR(HA_STEP)] = 0.01f; - doc[FPSTR(HA_MODE)] = "box"; + doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -866,24 +874,24 @@ class HaHelper : public HomeAssistantHelper { bool publishInputEquithermFactorT(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("settings")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.pid.enable, 'offline', 'online') }}"); + doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->settingsTopic.c_str(); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.pid.enabled, 'offline', 'online') }}"); 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)] = this->getObjectIdWithPrefix(F("equitherm_t")); + doc[FPSTR(HA_OBJECT_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"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.t_factor|float(0)|round(2) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"t_factor\" : {{ value }}}}"); doc[FPSTR(HA_MIN)] = 0; doc[FPSTR(HA_MAX)] = 10; doc[FPSTR(HA_STEP)] = 0.01f; - doc[FPSTR(HA_MODE)] = "box"; + doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -895,12 +903,12 @@ class HaHelper : public HomeAssistantHelper { JsonDocument doc; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("status")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("status")); + doc[FPSTR(HA_OBJECT_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"); doc[FPSTR(HA_ICON)] = F("mdi:list-status"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("status")); + doc[FPSTR(HA_STATE_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value == 'online', 'OFF', 'ON') }}"); doc[FPSTR(HA_EXPIRE_AFTER)] = 60; doc.shrinkToFit(); @@ -910,15 +918,15 @@ class HaHelper : public HomeAssistantHelper { bool publishEmergencyState(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("emergency")); + doc[FPSTR(HA_OBJECT_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"); doc[FPSTR(HA_ICON)] = F("mdi:alert-rhombus-outline"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_STATE_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.master.emergency.state, 'ON', 'OFF') }}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -928,15 +936,15 @@ class HaHelper : public HomeAssistantHelper { bool publishOpenthermConnectedState(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("ot_status")); + doc[FPSTR(HA_OBJECT_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"); doc[FPSTR(HA_ICON)] = F("mdi:list-status"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_STATE_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.slave.connected, 'ON', 'OFF') }}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -946,18 +954,18 @@ class HaHelper : public HomeAssistantHelper { bool publishHeatingState(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_OT_CONN; 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)] = this->getObjectIdWithPrefix(F("heating")); + doc[FPSTR(HA_OBJECT_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"); doc[FPSTR(HA_ICON)] = F("mdi:radiator"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_STATE_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.slave.heating.active, 'ON', 'OFF') }}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -967,18 +975,18 @@ class HaHelper : public HomeAssistantHelper { bool publishDhwState(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_OT_CONN; 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)] = this->getObjectIdWithPrefix(F("dhw")); + doc[FPSTR(HA_OBJECT_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"); doc[FPSTR(HA_ICON)] = F("mdi:faucet"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_STATE_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.slave.dhw.active, 'ON', 'OFF') }}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -988,18 +996,18 @@ class HaHelper : public HomeAssistantHelper { bool publishFlameState(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_OT_CONN; 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)] = this->getObjectIdWithPrefix(F("flame")); + doc[FPSTR(HA_OBJECT_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"); doc[FPSTR(HA_ICON)] = F("mdi:gas-burner"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_STATE_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.slave.flame, 'ON', 'OFF') }}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -1009,18 +1017,18 @@ class HaHelper : public HomeAssistantHelper { bool publishFaultState(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_OT_CONN; 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)] = this->getObjectIdWithPrefix(F("fault")); + doc[FPSTR(HA_OBJECT_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"); doc[FPSTR(HA_ICON)] = F("mdi:alert-remove-outline"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_STATE_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.slave.fault.active, 'ON', 'OFF') }}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -1030,18 +1038,18 @@ class HaHelper : public HomeAssistantHelper { bool publishDiagState(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = AVAILABILITY_OT_CONN; 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)] = this->getObjectIdWithPrefix(FPSTR(HA_ENTITY_CATEGORY_DIAGNOSTIC)); + doc[FPSTR(HA_OBJECT_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"); doc[FPSTR(HA_ICON)] = F("mdi:account-wrench"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_STATE_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.slave.diag.active, 'ON', 'OFF') }}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -1051,15 +1059,15 @@ class HaHelper : public HomeAssistantHelper { bool publishExternalPumpState(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("ext_pump")); + doc[FPSTR(HA_OBJECT_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"); doc[FPSTR(HA_ICON)] = F("mdi:pump"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_STATE_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.master.externalPump.state, 'ON', 'OFF') }}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -1069,17 +1077,17 @@ class HaHelper : public HomeAssistantHelper { bool publishFaultCode(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.opentherm.connected and value_json.fault.active, 'online', 'offline') }}"); + doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->stateTopic.c_str(); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.slave.connected and value_json.slave.fault.active, 'online', 'offline') }}"); 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)] = this->getObjectIdWithPrefix(F("fault_code")); + doc[FPSTR(HA_OBJECT_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"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_STATE_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ \"%02d (0x%02X)\"|format(value_json.slave.fault.code, value_json.slave.fault.code) }}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -1089,17 +1097,17 @@ class HaHelper : public HomeAssistantHelper { bool publishDiagCode(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.opentherm.connected and value_json.fault.active or value_json.diag.active, 'online', 'offline') }}"); + doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->stateTopic.c_str(); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.slave.connected and value_json.slave.fault.active or value_json.slave.diag.active, 'online', 'offline') }}"); 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)] = this->getObjectIdWithPrefix(F("diagnostic_code")); + doc[FPSTR(HA_OBJECT_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"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_STATE_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ \"%02d (0x%02X)\"|format(value_json.slave.diag.code, value_json.slave.diag.code) }}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -1109,17 +1117,17 @@ class HaHelper : public HomeAssistantHelper { bool publishNetworkRssi(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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("rssi")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("rssi")); + doc[FPSTR(HA_OBJECT_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); doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("dBm"); doc[FPSTR(HA_NAME)] = F("RSSI"); doc[FPSTR(HA_ICON)] = F("mdi:signal"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_STATE_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.master.network.rssi|float(0)|round(1) }}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -1129,17 +1137,17 @@ class HaHelper : public HomeAssistantHelper { bool publishUptime(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("uptime")); + doc[FPSTR(HA_OBJECT_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"); doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = F("s"); doc[FPSTR(HA_NAME)] = F("Uptime"); doc[FPSTR(HA_ICON)] = F("mdi:clock-start"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_STATE_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.master.uptime|int(0) }}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -1150,20 +1158,20 @@ class HaHelper : public HomeAssistantHelper { bool publishClimateHeating(UnitSystem unit = UnitSystem::METRIC, byte minTemp = 20, byte maxTemp = 90, bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("heating")); + doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_NAME)] = F("Heating"); doc[FPSTR(HA_ICON)] = F("mdi:radiator"); - doc[FPSTR(HA_CURRENT_TEMPERATURE_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_CURRENT_TEMPERATURE_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_CURRENT_TEMPERATURE_TEMPLATE)] = F("{{ iif(value_json.master.heating.indoorTempControl, value_json.master.heating.indoorTemp, value_json.master.heating.currentTemp, 0)|float(0)|round(2) }}"); - doc[FPSTR(HA_TEMPERATURE_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_TEMPERATURE_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_TEMPERATURE_COMMAND_TEMPLATE)] = F("{\"heating\": {\"target\" : {{ value }}}}"); - doc[FPSTR(HA_TEMPERATURE_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_TEMPERATURE_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_TEMPERATURE_STATE_TEMPLATE)] = F("{{ value_json.heating.target|float(0)|round(1) }}"); if (unit == UnitSystem::METRIC) { @@ -1173,21 +1181,21 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_TEMPERATURE_UNIT)] = "F"; } - doc[FPSTR(HA_MODE_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); - doc[FPSTR(HA_MODE_COMMAND_TEMPLATE)] = F("{% if value == 'heat' %}{\"heating\": {\"enable\" : true}}" - "{% elif value == 'off' %}{\"heating\": {\"enable\" : false}}{% endif %}"); - doc[FPSTR(HA_MODE_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); - doc[FPSTR(HA_MODE_STATE_TEMPLATE)] = F("{{ iif(value_json.heating.enable, 'heat', 'off') }}"); + doc[FPSTR(HA_MODE_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); + doc[FPSTR(HA_MODE_COMMAND_TEMPLATE)] = F("{% if value == 'heat' %}{\"heating\": {\"enabled\" : true}}" + "{% elif value == 'off' %}{\"heating\": {\"enabled\" : false}}{% endif %}"); + doc[FPSTR(HA_MODE_STATE_TOPIC)] = this->settingsTopic.c_str(); + doc[FPSTR(HA_MODE_STATE_TEMPLATE)] = F("{{ iif(value_json.heating.enabled, 'heat', 'off') }}"); doc[FPSTR(HA_MODES)][0] = F("off"); doc[FPSTR(HA_MODES)][1] = F("heat"); - doc[FPSTR(HA_ACTION_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_ACTION_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_ACTION_TEMPLATE)] = F("{{ iif(value_json.master.heating.enabled, iif(value_json.slave.heating.active, 'heating', 'idle'), 'off') }}"); - doc[FPSTR(HA_PRESET_MODE_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_PRESET_MODE_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_PRESET_MODE_COMMAND_TEMPLATE)] = F("{% if value == 'boost' %}{\"heating\": {\"turbo\" : true}}" "{% elif value == 'none' %}{\"heating\": {\"turbo\" : false}}{% endif %}"); - doc[FPSTR(HA_PRESET_MODE_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_PRESET_MODE_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_PRESET_MODE_VALUE_TEMPLATE)] = F("{{ iif(value_json.heating.turbo, 'boost', 'none') }}"); doc[FPSTR(HA_PRESET_MODES)][0] = F("boost"); @@ -1202,20 +1210,20 @@ class HaHelper : public HomeAssistantHelper { bool publishClimateDhw(UnitSystem unit = UnitSystem::METRIC, byte minTemp = 40, byte maxTemp = 60, bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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)] = this->getObjectIdWithPrefix(F("dhw")); + doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_NAME)] = F("DHW"); doc[FPSTR(HA_ICON)] = F("mdi:faucet"); - doc[FPSTR(HA_CURRENT_TEMPERATURE_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_CURRENT_TEMPERATURE_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_CURRENT_TEMPERATURE_TEMPLATE)] = F("{{ value_json.master.dhw.currentTemp|float(0)|round(1) }}"); - doc[FPSTR(HA_TEMPERATURE_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); + doc[FPSTR(HA_TEMPERATURE_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_TEMPERATURE_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}"); - doc[FPSTR(HA_TEMPERATURE_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); + doc[FPSTR(HA_TEMPERATURE_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_TEMPERATURE_STATE_TEMPLATE)] = F("{{ value_json.dhw.target|float(0)|round(1) }}"); if (unit == UnitSystem::METRIC) { @@ -1225,15 +1233,15 @@ class HaHelper : public HomeAssistantHelper { doc[FPSTR(HA_TEMPERATURE_UNIT)] = "F"; } - doc[FPSTR(HA_MODE_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); - doc[FPSTR(HA_MODE_COMMAND_TEMPLATE)] = F("{% if value == 'heat' %}{\"dhw\": {\"enable\" : true}}" - "{% elif value == 'off' %}{\"dhw\": {\"enable\" : false}}{% endif %}"); - doc[FPSTR(HA_MODE_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); - doc[FPSTR(HA_MODE_STATE_TEMPLATE)] = F("{{ iif(value_json.dhw.enable, 'heat', 'off') }}"); + doc[FPSTR(HA_MODE_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); + doc[FPSTR(HA_MODE_COMMAND_TEMPLATE)] = F("{% if value == 'heat' %}{\"dhw\": {\"enabled\" : true}}" + "{% elif value == 'off' %}{\"dhw\": {\"enabled\" : false}}{% endif %}"); + doc[FPSTR(HA_MODE_STATE_TOPIC)] = this->settingsTopic.c_str(); + doc[FPSTR(HA_MODE_STATE_TEMPLATE)] = F("{{ iif(value_json.dhw.enabled, 'heat', 'off') }}"); doc[FPSTR(HA_MODES)][0] = F("off"); doc[FPSTR(HA_MODES)][1] = F("heat"); - doc[FPSTR(HA_ACTION_TOPIC)] = this->getDeviceTopic(F("state")); + doc[FPSTR(HA_ACTION_TOPIC)] = this->stateTopic.c_str(); doc[FPSTR(HA_ACTION_TEMPLATE)] = F("{{ iif(value_json.master.dhw.enabled, iif(value_json.slave.dhw.active, 'heating', 'idle'), 'off') }}"); doc[FPSTR(HA_MIN_TEMP)] = minTemp; @@ -1247,14 +1255,14 @@ class HaHelper : public HomeAssistantHelper { bool publishRestartButton(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + 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("restart")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectIdWithPrefix(F("restart")); + doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("restart"); doc[FPSTR(HA_NAME)] = F("Restart"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setStateTopic.c_str(); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"actions\": {\"restart\": true}}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -1264,17 +1272,17 @@ class HaHelper : public HomeAssistantHelper { bool publishResetFaultButton(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.fault.active, 'online', 'offline') }}"); + doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->stateTopic.c_str(); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.slave.fault.active, 'online', 'offline') }}"); 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)] = this->getObjectIdWithPrefix(F("reset_fault")); + doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("restart"); doc[FPSTR(HA_NAME)] = F("Reset fault"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setStateTopic.c_str(); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"actions\": {\"resetFault\": true}}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -1284,17 +1292,17 @@ class HaHelper : public HomeAssistantHelper { bool publishResetDiagButton(bool enabledByDefault = true) { JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.diag.active, 'online', 'offline') }}"); + doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->stateTopic.c_str(); + doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.slave.diag.active, 'online', 'offline') }}"); 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)] = this->getObjectIdWithPrefix(F("reset_diagnostic")); + doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_DEVICE_CLASS)] = F("restart"); doc[FPSTR(HA_NAME)] = F("Reset diagnostic"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set")); + doc[FPSTR(HA_COMMAND_TOPIC)] = this->setStateTopic.c_str(); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"actions\": {\"resetDiagnostic\": true}}"); doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); @@ -1334,6 +1342,7 @@ class HaHelper : public HomeAssistantHelper { protected: unsigned short expireAfter = 300u; + String statusTopic, stateTopic, setStateTopic, settingsTopic, setSettingsTopic; }; const char HaHelper::AVAILABILITY_OT_CONN[] = "{{ iif(value_json.slave.connected, 'online', 'offline') }}"; diff --git a/src/MainTask.h b/src/MainTask.h index 08314b7..7c238f6 100644 --- a/src/MainTask.h +++ b/src/MainTask.h @@ -517,7 +517,7 @@ class MainTask : public Task { if (configuredGpio == GPIO_IS_NOT_CONFIGURED) { if (vars.externalPump.state) { vars.externalPump.state = false; - vars.externalPump.lastEnableTime = millis(); + vars.externalPump.lastEnabledTime = millis(); Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: use = off")); } @@ -538,7 +538,7 @@ class MainTask : public Task { digitalWrite(configuredGpio, LOW); vars.externalPump.state = false; - vars.externalPump.lastEnableTime = millis(); + vars.externalPump.lastEnabledTime = millis(); Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: use = off")); } @@ -551,7 +551,7 @@ class MainTask : public Task { digitalWrite(configuredGpio, LOW); vars.externalPump.state = false; - vars.externalPump.lastEnableTime = millis(); + vars.externalPump.lastEnabledTime = millis(); Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: expired post circulation time")); @@ -559,7 +559,7 @@ class MainTask : public Task { digitalWrite(configuredGpio, LOW); vars.externalPump.state = false; - vars.externalPump.lastEnableTime = millis(); + vars.externalPump.lastEnabledTime = millis(); Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: expired anti stuck time")); } @@ -576,7 +576,7 @@ class MainTask : public Task { Log.sinfoln(FPSTR(L_EXTPUMP), F("Enabled: heating on")); - } else if (!vars.externalPump.state && (vars.externalPump.lastEnableTime == 0 || millis() - vars.externalPump.lastEnableTime >= (settings.externalPump.antiStuckInterval * 1000ul))) { + } else if (!vars.externalPump.state && (vars.externalPump.lastEnabledTime == 0 || millis() - vars.externalPump.lastEnabledTime >= (settings.externalPump.antiStuckInterval * 1000lu))) { vars.externalPump.state = true; this->externalPumpStartTime = millis(); this->extPumpStartReason = MainTask::PumpStartReason::ANTISTUCK; diff --git a/src/MqttTask.h b/src/MqttTask.h index c910f14..f42e26c 100644 --- a/src/MqttTask.h +++ b/src/MqttTask.h @@ -192,6 +192,7 @@ class MqttTask : public Task { Log.sinfoln(FPSTR(L_MQTT), F("Connecting to %s:%u..."), settings.mqtt.server, settings.mqtt.port); this->haHelper->setDevicePrefix(settings.mqtt.prefix); + this->haHelper->updateCachedTopics(); this->client->stop(); this->client->setId(networkSettings.hostname); this->client->setUsernamePassword(settings.mqtt.user, settings.mqtt.password); @@ -221,14 +222,14 @@ class MqttTask : public Task { // publish variables and status if (this->newConnection || millis() - this->prevPubVarsTime > (settings.mqtt.interval * 1000u)) { - this->writer->publish(this->haHelper->getDeviceTopic("status").c_str(), "online", false); - this->publishVariables(this->haHelper->getDeviceTopic("state").c_str()); + this->writer->publish(this->haHelper->getDeviceTopic(F("status")).c_str(), "online", false); + this->publishVariables(this->haHelper->getDeviceTopic(F("state")).c_str()); this->prevPubVarsTime = millis(); } // publish settings if (this->newConnection || millis() - this->prevPubSettingsTime > (settings.mqtt.interval * 10000u)) { - this->publishSettings(this->haHelper->getDeviceTopic("settings").c_str()); + this->publishSettings(this->haHelper->getDeviceTopic(F("settings")).c_str()); this->prevPubSettingsTime = millis(); } @@ -292,7 +293,7 @@ class MqttTask : public Task { case Sensors::Type::MANUAL: { String topic = this->haHelper->getDeviceTopic( F("sensors"), - Sensors::makeObjectId(prevSettings.name), + Sensors::makeObjectId(prevSettings.name).c_str(), F("set") ); this->client->unsubscribe(topic.c_str()); @@ -328,7 +329,7 @@ class MqttTask : public Task { case Sensors::Type::MANUAL: { String topic = this->haHelper->getDeviceTopic( F("sensors"), - Sensors::makeObjectId(prevSettings.name), + Sensors::makeObjectId(prevSettings.name).c_str(), F("set") ); this->client->subscribe(topic.c_str()); @@ -355,15 +356,15 @@ class MqttTask : public Task { unsigned long downtime = (millis() - this->disconnectedTime) / 1000; Log.sinfoln(FPSTR(L_MQTT), F("Connected (downtime: %u s.)"), downtime); - this->client->subscribe(this->haHelper->getDeviceTopic("settings/set").c_str()); - this->client->subscribe(this->haHelper->getDeviceTopic("state/set").c_str()); + this->client->subscribe(this->haHelper->getDeviceTopic(F("settings/set")).c_str()); + this->client->subscribe(this->haHelper->getDeviceTopic(F("state/set")).c_str()); } void onDisconnect() { this->disconnectedTime = millis(); unsigned long uptime = (millis() - this->connectedTime) / 1000; - Log.swarningln(FPSTR(L_MQTT), F("Disconnected (reason: %d uptime: %u s.)"), this->client->connectError(), uptime); + Log.swarningln(FPSTR(L_MQTT), F("Disconnected (reason: %d uptime: %lu s.)"), this->client->connectError(), uptime); } void onMessage(const char* topic, uint8_t* payload, size_t length) { @@ -380,12 +381,12 @@ class MqttTask : public Task { } else if (payload[i] == 13) { continue; } else if (payload[i] == 10) { - Log.print("\r\n> "); + Log.print(F("\r\n> ")); } else { Log.print((char) payload[i]); } } - Log.print("\r\n\n"); + Log.print(F("\r\n\n")); Log.flush(); Log.unlock(); } @@ -403,14 +404,14 @@ class MqttTask : public Task { } doc.shrinkToFit(); - if (this->haHelper->getDeviceTopic("state/set").equals(topic)) { + if (this->haHelper->getDeviceTopic(F("state/set")).equals(topic)) { this->writer->publish(topic, nullptr, 0, true); if (jsonToVars(doc, vars)) { this->resetPublishedVarsTime(); } - } else if (this->haHelper->getDeviceTopic("settings/set").equals(topic)) { + } else if (this->haHelper->getDeviceTopic(F("settings/set")).equals(topic)) { this->writer->publish(topic, nullptr, 0, true); if (safeJsonToSettings(doc, settings)) { @@ -422,10 +423,10 @@ class MqttTask : public Task { this->writer->publish(topic, nullptr, 0, true); String _topic = topic; - String sensorsTopic = this->haHelper->getDeviceTopic("sensors/"); - unsigned short stLength = sensorsTopic.length(); + String sensorsTopic = this->haHelper->getDeviceTopic(F("sensors/")); + auto stLength = sensorsTopic.length(); - if (_topic.startsWith(sensorsTopic) && _topic.endsWith("/set")) { + if (_topic.startsWith(sensorsTopic) && _topic.endsWith(F("/set"))) { if (_topic.length() > stLength + 4) { String name = _topic.substring(stLength, _topic.indexOf('/', stLength)); int16_t id = Sensors::getIdByObjectId(name.c_str()); @@ -512,7 +513,7 @@ class MqttTask : public Task { case Sensors::Type::MANUAL: { String topic = this->haHelper->getDeviceTopic( F("sensors"), - Sensors::makeObjectId(sSettings.name), + Sensors::makeObjectId(sSettings.name).c_str(), F("set") ); this->client->subscribe(topic.c_str()); @@ -597,8 +598,11 @@ class MqttTask : public Task { sensorResultToJson(sensorId, doc); doc.shrinkToFit(); - String objId = Sensors::makeObjectId(sSettings.name); - String topic = this->haHelper->getDeviceTopic(F("sensors"), objId); + String topic = this->haHelper->getDeviceTopic( + F("sensors"), + Sensors::makeObjectId(sSettings.name).c_str() + ); + return this->writer->publish(topic.c_str(), doc, true); } diff --git a/src/OpenThermTask.h b/src/OpenThermTask.h index 3c4c484..6a66c81 100644 --- a/src/OpenThermTask.h +++ b/src/OpenThermTask.h @@ -244,13 +244,13 @@ class OpenThermTask : public Task { if (vars.master.heating.enabled != vars.slave.heating.enabled) { this->prevUpdateNonEssentialVars = 0; vars.slave.heating.enabled = vars.master.heating.enabled; - Log.sinfoln(FPSTR(L_OT_HEATING), "%s", vars.master.heating.enabled ? F("Enabled") : F("Disabled")); + Log.sinfoln(FPSTR(L_OT_HEATING), vars.master.heating.enabled ? F("Enabled") : F("Disabled")); } if (vars.master.dhw.enabled != vars.slave.dhw.enabled) { this->prevUpdateNonEssentialVars = 0; vars.slave.dhw.enabled = vars.master.heating.enabled; - Log.sinfoln(FPSTR(L_OT_DHW), "%s", vars.master.heating.enabled ? F("Enabled") : F("Disabled")); + Log.sinfoln(FPSTR(L_OT_DHW), vars.master.heating.enabled ? F("Enabled") : F("Disabled")); } vars.slave.heating.active = CustomOpenTherm::isCentralHeatingActive(response); diff --git a/src/PortalTask.h b/src/PortalTask.h index 323885e..baed4de 100644 --- a/src/PortalTask.h +++ b/src/PortalTask.h @@ -88,10 +88,10 @@ class PortalTask : public LeanTask { return result; }); this->webServer->addHandler(indexPage);*/ - this->webServer->addHandler(new StaticPage("/", &LittleFS, "/pages/index.html", PORTAL_CACHE)); + this->webServer->addHandler(new StaticPage("/", &LittleFS, F("/pages/index.html"), PORTAL_CACHE)); // dashboard page - auto dashboardPage = (new StaticPage("/dashboard.html", &LittleFS, "/pages/dashboard.html", PORTAL_CACHE)) + auto dashboardPage = (new StaticPage("/dashboard.html", &LittleFS, F("/pages/dashboard.html"), PORTAL_CACHE)) ->setBeforeSendCallback([this]() { if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) { this->webServer->requestAuthentication(DIGEST_AUTH); @@ -103,7 +103,7 @@ class PortalTask : public LeanTask { this->webServer->addHandler(dashboardPage); // restart - this->webServer->on("/restart.html", HTTP_GET, [this]() { + this->webServer->on(F("/restart.html"), HTTP_GET, [this]() { if (this->isAuthRequired()) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { this->webServer->send(401); @@ -112,12 +112,12 @@ class PortalTask : public LeanTask { } vars.actions.restart = true; - this->webServer->sendHeader("Location", "/"); + this->webServer->sendHeader(F("Location"), "/"); this->webServer->send(302); }); // network settings page - auto networkPage = (new StaticPage("/network.html", &LittleFS, "/pages/network.html", PORTAL_CACHE)) + auto networkPage = (new StaticPage("/network.html", &LittleFS, F("/pages/network.html"), PORTAL_CACHE)) ->setBeforeSendCallback([this]() { if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) { this->webServer->requestAuthentication(DIGEST_AUTH); @@ -129,7 +129,7 @@ class PortalTask : public LeanTask { this->webServer->addHandler(networkPage); // settings page - auto settingsPage = (new StaticPage("/settings.html", &LittleFS, "/pages/settings.html", PORTAL_CACHE)) + auto settingsPage = (new StaticPage("/settings.html", &LittleFS, F("/pages/settings.html"), PORTAL_CACHE)) ->setBeforeSendCallback([this]() { if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) { this->webServer->requestAuthentication(DIGEST_AUTH); @@ -141,7 +141,7 @@ class PortalTask : public LeanTask { this->webServer->addHandler(settingsPage); // sensors page - auto sensorsPage = (new StaticPage("/sensors.html", &LittleFS, "/pages/sensors.html", PORTAL_CACHE)) + auto sensorsPage = (new StaticPage("/sensors.html", &LittleFS, F("/pages/sensors.html"), PORTAL_CACHE)) ->setBeforeSendCallback([this]() { if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) { this->webServer->requestAuthentication(DIGEST_AUTH); @@ -153,7 +153,7 @@ class PortalTask : public LeanTask { this->webServer->addHandler(sensorsPage); // upgrade page - auto upgradePage = (new StaticPage("/upgrade.html", &LittleFS, "/pages/upgrade.html", PORTAL_CACHE)) + auto upgradePage = (new StaticPage("/upgrade.html", &LittleFS, F("/pages/upgrade.html"), PORTAL_CACHE)) ->setBeforeSendCallback([this]() { if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) { this->webServer->requestAuthentication(DIGEST_AUTH); @@ -167,7 +167,7 @@ class PortalTask : public LeanTask { // OTA auto upgradeHandler = (new UpgradeHandler("/api/upgrade"))->setCanUploadCallback([this](const String& uri) { if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) { - this->webServer->sendHeader("Connection", "close"); + this->webServer->sendHeader(F("Connection"), F("close")); this->webServer->send(401); return false; } @@ -184,22 +184,22 @@ class PortalTask : public LeanTask { status = 400; } - String response = "{\"firmware\": {\"status\": "; + String response = F("{\"firmware\": {\"status\": "); response.concat((short int) fwResult.status); - response.concat(", \"error\": \""); + response.concat(F(", \"error\": \"")); response.concat(fwResult.error); - response.concat("\"}, \"filesystem\": {\"status\": "); + response.concat(F("\"}, \"filesystem\": {\"status\": ")); response.concat((short int) fsResult.status); - response.concat(", \"error\": \""); + response.concat(F(", \"error\": \"")); response.concat(fsResult.error); - response.concat("\"}}"); - this->webServer->send(status, "application/json", response); + response.concat(F("\"}}")); + this->webServer->send(status, F("application/json"), response); }); this->webServer->addHandler(upgradeHandler); // backup - this->webServer->on("/api/backup/save", HTTP_GET, [this]() { + this->webServer->on(F("/api/backup/save"), HTTP_GET, [this]() { if (this->isAuthRequired()) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { return this->webServer->send(401); @@ -208,24 +208,24 @@ class PortalTask : public LeanTask { JsonDocument doc; - auto networkDoc = doc["network"].to(); + auto networkDoc = doc[FPSTR(S_NETWORK)].to(); networkSettingsToJson(networkSettings, networkDoc); - auto settingskDoc = doc["settings"].to(); - settingsToJson(settings, settingskDoc); + auto settingsDoc = doc[FPSTR(S_SETTINGS)].to(); + settingsToJson(settings, settingsDoc); for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { - auto sensorSettingskDoc = doc["sensors"][sensorId].to(); - sensorSettingsToJson(sensorId, Sensors::settings[sensorId], sensorSettingskDoc); + auto sensorsettingsDoc = doc[FPSTR(S_SENSORS)][sensorId].to(); + sensorSettingsToJson(sensorId, Sensors::settings[sensorId], sensorsettingsDoc); } doc.shrinkToFit(); this->webServer->sendHeader(F("Content-Disposition"), F("attachment; filename=\"backup.json\"")); - this->bufferedWebServer->send(200, "application/json", doc); + this->bufferedWebServer->send(200, F("application/json"), doc); }); - this->webServer->on("/api/backup/restore", HTTP_POST, [this]() { + this->webServer->on(F("/api/backup/restore"), HTTP_POST, [this]() { if (this->isAuthRequired()) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { return this->webServer->send(401); @@ -254,13 +254,13 @@ class PortalTask : public LeanTask { } bool changed = false; - if (!doc["settings"].isNull() && jsonToSettings(doc["settings"], settings)) { + if (!doc[FPSTR(S_SETTINGS)].isNull() && jsonToSettings(doc[FPSTR(S_SETTINGS)], settings)) { vars.actions.restart = true; fsSettings.update(); changed = true; } - if (!doc["network"].isNull() && jsonToNetworkSettings(doc["network"], networkSettings)) { + if (!doc[FPSTR(S_NETWORK)].isNull() && jsonToNetworkSettings(doc[FPSTR(S_NETWORK)], networkSettings)) { fsNetworkSettings.update(); network->setHostname(networkSettings.hostname) ->setStaCredentials(networkSettings.sta.ssid, networkSettings.sta.password, networkSettings.sta.channel) @@ -276,13 +276,13 @@ class PortalTask : public LeanTask { changed = true; } - if (!doc["sensors"].isNull()) { + if (!doc[FPSTR(S_SENSORS)].isNull()) { for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { - if (doc["sensors"][sensorId].isNull()) { + if (doc[FPSTR(S_SENSORS)][sensorId].isNull()) { continue; } - auto sensorSettingsDoc = doc["sensors"][sensorId].to(); + auto sensorSettingsDoc = doc[FPSTR(S_SENSORS)][sensorId].to(); if (jsonToSensorSettings(sensorId, sensorSettingsDoc, Sensors::settings[sensorId])){ changed = true; } @@ -296,7 +296,7 @@ class PortalTask : public LeanTask { }); // network - this->webServer->on("/api/network/settings", HTTP_GET, [this]() { + this->webServer->on(F("/api/network/settings"), HTTP_GET, [this]() { if (this->isAuthRequired()) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { return this->webServer->send(401); @@ -307,10 +307,10 @@ class PortalTask : public LeanTask { networkSettingsToJson(networkSettings, doc); doc.shrinkToFit(); - this->bufferedWebServer->send(200, "application/json", doc); + this->bufferedWebServer->send(200, F("application/json"), doc); }); - this->webServer->on("/api/network/settings", HTTP_POST, [this]() { + this->webServer->on(F("/api/network/settings"), HTTP_POST, [this]() { if (this->isAuthRequired()) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { return this->webServer->send(401); @@ -345,7 +345,7 @@ class PortalTask : public LeanTask { networkSettingsToJson(networkSettings, doc); doc.shrinkToFit(); - this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc); + this->bufferedWebServer->send(changed ? 201 : 200, F("application/json"), doc); if (changed) { doc.clear(); @@ -366,7 +366,7 @@ class PortalTask : public LeanTask { } }); - this->webServer->on("/api/network/scan", HTTP_GET, [this]() { + this->webServer->on(F("/api/network/scan"), HTTP_GET, [this]() { if (this->isAuthRequired()) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { this->webServer->send(401); @@ -391,28 +391,28 @@ class PortalTask : public LeanTask { JsonDocument doc; for (short int i = 0; i < apCount; i++) { String ssid = WiFi.SSID(i); - doc[i]["ssid"] = ssid; - doc[i]["bssid"] = WiFi.BSSIDstr(i); - doc[i]["signalQuality"] = NetworkMgr::rssiToSignalQuality(WiFi.RSSI(i)); - doc[i]["channel"] = WiFi.channel(i); - doc[i]["hidden"] = !ssid.length(); + doc[i][FPSTR(S_SSID)] = ssid; + doc[i][FPSTR(S_BSSID)] = WiFi.BSSIDstr(i); + doc[i][FPSTR(S_SIGNAL_QUALITY)] = NetworkMgr::rssiToSignalQuality(WiFi.RSSI(i)); + doc[i][FPSTR(S_CHANNEL)] = WiFi.channel(i); + doc[i][FPSTR(S_HIDDEN)] = !ssid.length(); #ifdef ARDUINO_ARCH_ESP8266 const bss_info* info = WiFi.getScanInfoByIndex(i); - doc[i]["auth"] = info->authmode; + doc[i][FPSTR(S_AUTH)] = info->authmode; #else - doc[i]["auth"] = WiFi.encryptionType(i); + doc[i][FPSTR(S_AUTH)] = WiFi.encryptionType(i); #endif } doc.shrinkToFit(); - this->bufferedWebServer->send(200, "application/json", doc); + this->bufferedWebServer->send(200, F("application/json"), doc); WiFi.scanDelete(); }); // settings - this->webServer->on("/api/settings", HTTP_GET, [this]() { + this->webServer->on(F("/api/settings"), HTTP_GET, [this]() { if (this->isAuthRequired()) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { return this->webServer->send(401); @@ -423,10 +423,10 @@ class PortalTask : public LeanTask { settingsToJson(settings, doc); doc.shrinkToFit(); - this->bufferedWebServer->send(200, "application/json", doc); + this->bufferedWebServer->send(200, F("application/json"), doc); }); - this->webServer->on("/api/settings", HTTP_POST, [this]() { + this->webServer->on(F("/api/settings"), HTTP_POST, [this]() { if (this->isAuthRequired()) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { return this->webServer->send(401); @@ -461,7 +461,7 @@ class PortalTask : public LeanTask { settingsToJson(settings, doc); doc.shrinkToFit(); - this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc); + this->bufferedWebServer->send(changed ? 201 : 200, F("application/json"), doc); if (changed) { doc.clear(); @@ -474,7 +474,7 @@ class PortalTask : public LeanTask { // sensors list - this->webServer->on("/api/sensors", HTTP_GET, [this]() { + this->webServer->on(F("/api/sensors"), HTTP_GET, [this]() { if (this->isAuthRequired()) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { return this->webServer->send(401); @@ -482,16 +482,16 @@ class PortalTask : public LeanTask { } bool detailed = false; - if (this->webServer->hasArg("detailed")) { - detailed = this->webServer->arg("detailed").toInt() > 0; + if (this->webServer->hasArg(F("detailed"))) { + detailed = this->webServer->arg(F("detailed")).toInt() > 0; } JsonDocument doc; for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { if (detailed) { auto& sSensor = Sensors::settings[sensorId]; - doc[sensorId]["name"] = sSensor.name; - doc[sensorId]["purpose"] = static_cast(sSensor.purpose); + doc[sensorId][FPSTR(S_NAME)] = sSensor.name; + doc[sensorId][FPSTR(S_PURPOSE)] = static_cast(sSensor.purpose); sensorResultToJson(sensorId, doc[sensorId]); } else { @@ -500,22 +500,22 @@ class PortalTask : public LeanTask { } doc.shrinkToFit(); - this->bufferedWebServer->send(200, "application/json", doc); + this->bufferedWebServer->send(200, F("application/json"), doc); }); // sensor settings - this->webServer->on("/api/sensor", HTTP_GET, [this]() { + this->webServer->on(F("/api/sensor"), HTTP_GET, [this]() { if (this->isAuthRequired()) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { return this->webServer->send(401); } } - if (!this->webServer->hasArg("id")) { + if (!this->webServer->hasArg(F("id"))) { return this->webServer->send(400); } - auto id = this->webServer->arg("id"); + auto id = this->webServer->arg(F("id")); if (!isDigit(id.c_str())) { return this->webServer->send(400); } @@ -529,10 +529,10 @@ class PortalTask : public LeanTask { JsonDocument doc; sensorSettingsToJson(sensorId, Sensors::settings[sensorId], doc); doc.shrinkToFit(); - this->bufferedWebServer->send(200, "application/json", doc); + this->bufferedWebServer->send(200, F("application/json"), doc); }); - this->webServer->on("/api/sensor", HTTP_POST, [this]() { + this->webServer->on(F("/api/sensor"), HTTP_POST, [this]() { if (this->isAuthRequired()) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { return this->webServer->send(401); @@ -540,16 +540,16 @@ class PortalTask : public LeanTask { } #ifdef ARDUINO_ARCH_ESP8266 - if (!this->webServer->hasArg("id") || this->webServer->args() != 1) { + if (!this->webServer->hasArg(F("id")) || this->webServer->args() != 1) { return this->webServer->send(400); } #else - if (!this->webServer->hasArg("id") || this->webServer->args() != 2) { + if (!this->webServer->hasArg(F("id")) || this->webServer->args() != 2) { return this->webServer->send(400); } #endif - auto id = this->webServer->arg("id"); + auto id = this->webServer->arg(F("id")); if (!isDigit(id.c_str())) { return this->webServer->send(400); } @@ -592,7 +592,7 @@ class PortalTask : public LeanTask { sensorSettingsToJson(sensorId, sSettings, doc); doc.shrinkToFit(); - this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc); + this->bufferedWebServer->send(changed ? 201 : 200, F("application/json"), doc); } if (changed) { @@ -603,15 +603,15 @@ class PortalTask : public LeanTask { // vars - this->webServer->on("/api/vars", HTTP_GET, [this]() { + this->webServer->on(F("/api/vars"), HTTP_GET, [this]() { JsonDocument doc; varsToJson(vars, doc); doc.shrinkToFit(); - this->bufferedWebServer->send(200, "application/json", doc); + this->bufferedWebServer->send(200, F("application/json"), doc); }); - this->webServer->on("/api/vars", HTTP_POST, [this]() { + this->webServer->on(F("/api/vars"), HTTP_POST, [this]() { if (this->isAuthRequired()) { if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) { return this->webServer->send(401); @@ -646,7 +646,7 @@ class PortalTask : public LeanTask { varsToJson(vars, doc); doc.shrinkToFit(); - this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc); + this->bufferedWebServer->send(changed ? 201 : 200, F("application/json"), doc); if (changed) { doc.clear(); @@ -656,78 +656,138 @@ class PortalTask : public LeanTask { } }); - this->webServer->on("/api/info", HTTP_GET, [this]() { + this->webServer->on(F("/api/info"), HTTP_GET, [this]() { bool isConnected = network->isConnected(); JsonDocument doc; - doc["system"]["resetReason"] = getResetReason(); - doc["system"]["uptime"] = millis() / 1000ul; - - doc["network"]["hostname"] = networkSettings.hostname; - doc["network"]["mac"] = network->getStaMac(); - doc["network"]["connected"] = isConnected; - doc["network"]["ssid"] = network->getStaSsid(); - doc["network"]["signalQuality"] = isConnected ? NetworkMgr::rssiToSignalQuality(network->getRssi()) : 0; - doc["network"]["channel"] = isConnected ? network->getStaChannel() : 0; - doc["network"]["ip"] = isConnected ? network->getStaIp().toString() : ""; - doc["network"]["subnet"] = isConnected ? network->getStaSubnet().toString() : ""; - doc["network"]["gateway"] = isConnected ? network->getStaGateway().toString() : ""; - doc["network"]["dns"] = isConnected ? network->getStaDns().toString() : ""; - - doc["build"]["version"] = BUILD_VERSION; - doc["build"]["date"] = __DATE__ " " __TIME__; - doc["build"]["env"] = BUILD_ENV; - - doc["heap"]["total"] = getTotalHeap(); - doc["heap"]["free"] = getFreeHeap(); - doc["heap"]["minFree"] = getFreeHeap(true); - doc["heap"]["maxFreeBlock"] = getMaxFreeBlockHeap(); - doc["heap"]["minMaxFreeBlock"] = getMaxFreeBlockHeap(true); + + auto docSystem = doc[FPSTR(S_SYSTEM)].to(); + docSystem[FPSTR(S_RESET_REASON)] = getResetReason(); + docSystem[FPSTR(S_UPTIME)] = millis() / 1000; + + auto docNetwork = doc[FPSTR(S_NETWORK)].to(); + docNetwork[FPSTR(S_HOSTNAME)] = networkSettings.hostname; + docNetwork[FPSTR(S_MAC)] = network->getStaMac(); + docNetwork[FPSTR(S_CONNECTED)] = isConnected; + docNetwork[FPSTR(S_SSID)] = network->getStaSsid(); + docNetwork[FPSTR(S_SIGNAL_QUALITY)] = isConnected ? NetworkMgr::rssiToSignalQuality(network->getRssi()) : 0; + docNetwork[FPSTR(S_CHANNEL)] = isConnected ? network->getStaChannel() : 0; + docNetwork[FPSTR(S_IP)] = isConnected ? network->getStaIp().toString() : ""; + docNetwork[FPSTR(S_SUBNET)] = isConnected ? network->getStaSubnet().toString() : ""; + docNetwork[FPSTR(S_GATEWAY)] = isConnected ? network->getStaGateway().toString() : ""; + docNetwork[FPSTR(S_DNS)] = isConnected ? network->getStaDns().toString() : ""; + + auto docBuild = doc[FPSTR(S_BUILD)].to(); + docBuild[FPSTR(S_VERSION)] = BUILD_VERSION; + docBuild[FPSTR(S_DATE)] = __DATE__ " " __TIME__; + docBuild[FPSTR(S_ENV)] = BUILD_ENV; + #ifdef ARDUINO_ARCH_ESP8266 + docBuild[FPSTR(S_CORE)] = ESP.getCoreVersion(); + docBuild[FPSTR(S_SDK)] = ESP.getSdkVersion(); + #elif ARDUINO_ARCH_ESP32 + docBuild[FPSTR(S_CORE)] = ESP.getCoreVersion(); + docBuild[FPSTR(S_SDK)] = ESP.getSdkVersion(); + #else + docBuild[FPSTR(S_CORE)] = 0; + docBuild[FPSTR(S_SDK)] = 0; + #endif + + auto docHeap = doc[FPSTR(S_HEAP)].to(); + docHeap[FPSTR(S_TOTAL)] = getTotalHeap(); + docHeap[FPSTR(S_FREE)] = getFreeHeap(); + docHeap[FPSTR(S_MIN_FREE)] = getFreeHeap(true); + docHeap[FPSTR(S_MAX_FREE_BLOCK)] = getMaxFreeBlockHeap(); + docHeap[FPSTR(S_MIN_MAX_FREE_BLOCK)] = getMaxFreeBlockHeap(true); + auto docChip = doc[FPSTR(S_CHIP)].to(); + #ifdef ARDUINO_ARCH_ESP8266 + docChip[FPSTR(S_MODEL)] = esp_is_8285() ? F("ESP8285") : F("ESP8266"); + docChip[FPSTR(S_REV)] = 0; + docChip[FPSTR(S_CORES)] = 1; + docChip[FPSTR(S_FREQ)] = ESP.getCpuFreqMHz(); + #elif ARDUINO_ARCH_ESP32 + docChip[FPSTR(S_MODEL)] = ESP.getChipModel(); + docChip[FPSTR(S_REV)] = ESP.getChipRevision(); + docChip[FPSTR(S_CORES)] = ESP.getChipCores(); + docChip[FPSTR(S_FREQ)] = ESP.getCpuFreqMHz(); + #else + docChip[FPSTR(S_MODEL)] = 0; + docChip[FPSTR(S_REV)] = 0; + docChip[FPSTR(S_CORES)] = 0; + docChip[FPSTR(S_FREQ)] = 0; + #endif + + auto docFlash = doc[FPSTR(S_FLASH)].to(); #ifdef ARDUINO_ARCH_ESP8266 - doc["build"]["core"] = ESP.getCoreVersion(); - doc["build"]["sdk"] = ESP.getSdkVersion(); - doc["chip"]["model"] = esp_is_8285() ? "ESP8285" : "ESP8266"; - doc["chip"]["rev"] = 0; - doc["chip"]["cores"] = 1; - doc["chip"]["freq"] = ESP.getCpuFreqMHz(); - doc["flash"]["size"] = ESP.getFlashChipSize(); - doc["flash"]["realSize"] = ESP.getFlashChipRealSize(); + docFlash[FPSTR(S_SIZE)] = ESP.getFlashChipSize(); + docFlash[FPSTR(S_REAL_SIZE)] = ESP.getFlashChipRealSize(); #elif ARDUINO_ARCH_ESP32 - doc["build"]["core"] = ESP.getCoreVersion(); - doc["build"]["sdk"] = ESP.getSdkVersion(); - doc["chip"]["model"] = ESP.getChipModel(); - doc["chip"]["rev"] = ESP.getChipRevision(); - doc["chip"]["cores"] = ESP.getChipCores(); - doc["chip"]["freq"] = ESP.getCpuFreqMHz(); - doc["flash"]["size"] = ESP.getFlashChipSize(); - doc["flash"]["realSize"] = doc["flash"]["size"]; + docFlash[FPSTR(S_SIZE)] = ESP.getFlashChipSize(); + docFlash[FPSTR(S_REAL_SIZE)] = docFlash[FPSTR(S_SIZE)]; #else - doc["build"]["core"] = 0; - doc["build"]["sdk"] = 0; - doc["chip"]["model"] = 0; - doc["chip"]["rev"] = 0; - doc["chip"]["cores"] = 0; - doc["chip"]["freq"] = 0; - doc["flash"]["size"] = 0; - doc["flash"]["realSize"] = 0; + docFlash[FPSTR(S_SIZE)] = 0; + docFlash[FPSTR(S_REAL_SIZE)] = 0; #endif doc.shrinkToFit(); - this->bufferedWebServer->send(200, "application/json", doc); + this->bufferedWebServer->send(200, F("application/json"), doc); }); - this->webServer->on("/api/debug", HTTP_GET, [this]() { + this->webServer->on(F("/api/debug"), HTTP_GET, [this]() { JsonDocument doc; - doc["build"]["version"] = BUILD_VERSION; - doc["build"]["date"] = __DATE__ " " __TIME__; - doc["build"]["env"] = BUILD_ENV; - doc["heap"]["total"] = getTotalHeap(); - doc["heap"]["free"] = getFreeHeap(); - doc["heap"]["minFree"] = getFreeHeap(true); - doc["heap"]["maxFreeBlock"] = getMaxFreeBlockHeap(); - doc["heap"]["minMaxFreeBlock"] = getMaxFreeBlockHeap(true); + + auto docBuild = doc[FPSTR(S_BUILD)].to(); + docBuild[FPSTR(S_VERSION)] = BUILD_VERSION; + docBuild[FPSTR(S_DATE)] = __DATE__ " " __TIME__; + docBuild[FPSTR(S_ENV)] = BUILD_ENV; + #ifdef ARDUINO_ARCH_ESP8266 + docBuild[FPSTR(S_CORE)] = ESP.getCoreVersion(); + docBuild[FPSTR(S_SDK)] = ESP.getSdkVersion(); + #elif ARDUINO_ARCH_ESP32 + docBuild[FPSTR(S_CORE)] = ESP.getCoreVersion(); + docBuild[FPSTR(S_SDK)] = ESP.getSdkVersion(); + #else + docBuild[FPSTR(S_CORE)] = 0; + docBuild[FPSTR(S_SDK)] = 0; + #endif + + auto docHeap = doc[FPSTR(S_HEAP)].to(); + docHeap[FPSTR(S_TOTAL)] = getTotalHeap(); + docHeap[FPSTR(S_FREE)] = getFreeHeap(); + docHeap[FPSTR(S_MIN_FREE)] = getFreeHeap(true); + docHeap[FPSTR(S_MAX_FREE_BLOCK)] = getMaxFreeBlockHeap(); + docHeap[FPSTR(S_MIN_MAX_FREE_BLOCK)] = getMaxFreeBlockHeap(true); + + auto docChip = doc[FPSTR(S_CHIP)].to(); + #ifdef ARDUINO_ARCH_ESP8266 + docChip[FPSTR(S_MODEL)] = esp_is_8285() ? F("ESP8285") : F("ESP8266"); + docChip[FPSTR(S_REV)] = 0; + docChip[FPSTR(S_CORES)] = 1; + docChip[FPSTR(S_FREQ)] = ESP.getCpuFreqMHz(); + #elif ARDUINO_ARCH_ESP32 + docChip[FPSTR(S_MODEL)] = ESP.getChipModel(); + docChip[FPSTR(S_REV)] = ESP.getChipRevision(); + docChip[FPSTR(S_CORES)] = ESP.getChipCores(); + docChip[FPSTR(S_FREQ)] = ESP.getCpuFreqMHz(); + #else + docChip[FPSTR(S_MODEL)] = 0; + docChip[FPSTR(S_REV)] = 0; + docChip[FPSTR(S_CORES)] = 0; + docChip[FPSTR(S_FREQ)] = 0; + #endif + + auto docFlash = doc[FPSTR(S_FLASH)].to(); + #ifdef ARDUINO_ARCH_ESP8266 + docFlash[FPSTR(S_SIZE)] = ESP.getFlashChipSize(); + docFlash[FPSTR(S_REAL_SIZE)] = ESP.getFlashChipRealSize(); + #elif ARDUINO_ARCH_ESP32 + docFlash[FPSTR(S_SIZE)] = ESP.getFlashChipSize(); + docFlash[FPSTR(S_REAL_SIZE)] = docFlash[FPSTR(S_SIZE)]; + #else + docFlash[FPSTR(S_SIZE)] = 0; + docFlash[FPSTR(S_REAL_SIZE)] = 0; + #endif #if defined(ARDUINO_ARCH_ESP32) auto reason = esp_reset_reason(); @@ -738,58 +798,30 @@ class PortalTask : public LeanTask { #else if (false) { #endif - doc["crash"]["reason"] = getResetReason(); - doc["crash"]["core"] = CrashRecorder::ext.core; - doc["crash"]["heap"] = CrashRecorder::ext.heap; - doc["crash"]["uptime"] = CrashRecorder::ext.uptime; + auto docCrash = doc[FPSTR(S_CRASH)].to(); + docCrash[FPSTR(S_REASON)] = getResetReason(); + docCrash[FPSTR(S_CORE)] = CrashRecorder::ext.core; + docCrash[FPSTR(S_HEAP)] = CrashRecorder::ext.heap; + docCrash[FPSTR(S_UPTIME)] = CrashRecorder::ext.uptime; if (CrashRecorder::backtrace.length > 0 && CrashRecorder::backtrace.length <= CrashRecorder::backtraceMaxLength) { String backtraceStr; arr2str(backtraceStr, CrashRecorder::backtrace.data, CrashRecorder::backtrace.length); - doc["crash"]["backtrace"]["data"] = backtraceStr; - doc["crash"]["backtrace"]["continues"] = CrashRecorder::backtrace.continues; + docCrash[FPSTR(S_BACKTRACE)][FPSTR(S_DATA)] = backtraceStr; + docCrash[FPSTR(S_BACKTRACE)][FPSTR(S_CONTINUES)] = CrashRecorder::backtrace.continues; } if (CrashRecorder::epc.length > 0 && CrashRecorder::epc.length <= CrashRecorder::epcMaxLength) { String epcStr; arr2str(epcStr, CrashRecorder::epc.data, CrashRecorder::epc.length); - doc["crash"]["epc"] = epcStr; + docCrash[FPSTR(S_EPC)] = epcStr; } } - #ifdef ARDUINO_ARCH_ESP8266 - doc["build"]["core"] = ESP.getCoreVersion(); - doc["build"]["sdk"] = ESP.getSdkVersion(); - doc["chip"]["model"] = esp_is_8285() ? "ESP8285" : "ESP8266"; - doc["chip"]["rev"] = 0; - doc["chip"]["cores"] = 1; - doc["chip"]["freq"] = ESP.getCpuFreqMHz(); - doc["flash"]["size"] = ESP.getFlashChipSize(); - doc["flash"]["realSize"] = ESP.getFlashChipRealSize(); - #elif ARDUINO_ARCH_ESP32 - doc["build"]["core"] = ESP.getCoreVersion(); - doc["build"]["sdk"] = ESP.getSdkVersion(); - doc["chip"]["model"] = ESP.getChipModel(); - doc["chip"]["rev"] = ESP.getChipRevision(); - doc["chip"]["cores"] = ESP.getChipCores(); - doc["chip"]["freq"] = ESP.getCpuFreqMHz(); - doc["flash"]["size"] = ESP.getFlashChipSize(); - doc["flash"]["realSize"] = doc["flash"]["size"]; - #else - doc["build"]["core"] = 0; - doc["build"]["sdk"] = 0; - doc["chip"]["model"] = 0; - doc["chip"]["rev"] = 0; - doc["chip"]["cores"] = 0; - doc["chip"]["freq"] = 0; - doc["flash"]["size"] = 0; - doc["flash"]["realSize"] = 0; - #endif - doc.shrinkToFit(); this->webServer->sendHeader(F("Content-Disposition"), F("attachment; filename=\"debug.json\"")); - this->bufferedWebServer->send(200, "application/json", doc, true); + this->bufferedWebServer->send(200, F("application/json"), doc, true); }); @@ -798,14 +830,14 @@ class PortalTask : public LeanTask { Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Page not found, uri: %s"), this->webServer->uri().c_str()); const String uri = this->webServer->uri(); - if (uri.equals("/")) { - this->webServer->send(200, "text/plain", F("The file system is not flashed!")); + if (uri.equals(F("/"))) { + this->webServer->send(200, F("text/plain"), F("The file system is not flashed!")); } else if (network->isApEnabled()) { this->onCaptivePortal(); } else { - this->webServer->send(404, "text/plain", F("Page not found")); + this->webServer->send(404, F("text/plain"), F("Page not found")); } }); @@ -877,26 +909,28 @@ class PortalTask : public LeanTask { void onCaptivePortal() { const String uri = this->webServer->uri(); - if (uri.equals("/connecttest.txt")) { + if (uri.equals(F("/connecttest.txt"))) { this->webServer->sendHeader(F("Location"), F("http://logout.net")); this->webServer->send(302); Log.straceln(FPSTR(L_PORTAL_CAPTIVE), F("Redirect to http://logout.net with 302 code")); - } else if (uri.equals("/wpad.dat")) { + } else if (uri.equals(F("/wpad.dat"))) { this->webServer->send(404); Log.straceln(FPSTR(L_PORTAL_CAPTIVE), F("Send empty page with 404 code")); - } else if (uri.equals("/success.txt")) { + } else if (uri.equals(F("/success.txt"))) { this->webServer->send(200); Log.straceln(FPSTR(L_PORTAL_CAPTIVE), F("Send empty page with 200 code")); } else { - String portalUrl = "http://" + network->getApIp().toString() + '/'; + String portalUrl = F("http://"); + portalUrl += network->getApIp().toString(); + portalUrl += '/'; - this->webServer->sendHeader("Location", portalUrl.c_str()); + this->webServer->sendHeader(F("Location"), portalUrl.c_str()); this->webServer->send(302); Log.straceln(FPSTR(L_PORTAL_CAPTIVE), F("Redirect to portal page with 302 code")); diff --git a/src/SensorsTask.h b/src/SensorsTask.h index 6eef520..432adfa 100644 --- a/src/SensorsTask.h +++ b/src/SensorsTask.h @@ -61,16 +61,26 @@ class SensorsTask : public LeanTask { void loop() { if (isPollingDallasSensors()) { pollingDallasSensors(false); + this->yield(); } if (millis() - this->globalLastPollingTime > this->globalPollingInterval) { - makeDallasInstances(); cleanDallasInstances(); + makeDallasInstances(); + this->yield(); + searchDallasSensors(); fillingAddressesDallasSensors(); + this->yield(); + pollingDallasSensors(); + this->yield(); + pollingNtcSensors(); + this->yield(); + pollingBleSensors(); + this->yield(); this->globalLastPollingTime = millis(); } diff --git a/src/Settings.h b/src/Settings.h index 35fcf36..7b3f59b 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -236,7 +236,7 @@ struct Variables { struct { bool state = false; - unsigned long lastEnableTime = 0; + unsigned long lastEnabledTime = 0; } externalPump; struct { diff --git a/src/main.cpp b/src/main.cpp index 6e2663d..b504948 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,6 @@ +#define ARDUINOJSON_USE_DOUBLE 0 +#define ARDUINOJSON_USE_LONG_LONG 0 + #include #include #include diff --git a/src/strings.h b/src/strings.h index 60776c6..54c874d 100644 --- a/src/strings.h +++ b/src/strings.h @@ -3,32 +3,191 @@ #define PROGMEM #endif -const char L_SETTINGS[] PROGMEM = "SETTINGS"; -const char L_SETTINGS_OT[] PROGMEM = "SETTINGS.OT"; -const char L_SETTINGS_DHW[] PROGMEM = "SETTINGS.DHW"; -const char L_SETTINGS_HEATING[] PROGMEM = "SETTINGS.HEATING"; -const char L_NETWORK[] PROGMEM = "NETWORK"; -const char L_NETWORK_SETTINGS[] PROGMEM = "NETWORK.SETTINGS"; -const char L_PORTAL_WEBSERVER[] PROGMEM = "PORTAL.WEBSERVER"; -const char L_PORTAL_DNSSERVER[] PROGMEM = "PORTAL.DNSSERVER"; -const char L_PORTAL_CAPTIVE[] PROGMEM = "PORTAL.CAPTIVE"; -const char L_PORTAL_OTA[] PROGMEM = "PORTAL.OTA"; -const char L_MAIN[] PROGMEM = "MAIN"; -const char L_MQTT[] PROGMEM = "MQTT"; -const char L_MQTT_HA[] PROGMEM = "MQTT.HA"; -const char L_MQTT_MSG[] PROGMEM = "MQTT.MSG"; -const char L_OT[] PROGMEM = "OT"; -const char L_OT_DHW[] PROGMEM = "OT.DHW"; -const char L_OT_HEATING[] PROGMEM = "OT.HEATING"; -const char L_OT_CH2[] PROGMEM = "OT.CH2"; -const char L_SENSORS[] PROGMEM = "SENSORS"; -const char L_SENSORS_SETTINGS[] PROGMEM = "SENSORS.SETTINGS"; -const char L_SENSORS_DALLAS[] PROGMEM = "SENSORS.DALLAS"; -const char L_SENSORS_NTC[] PROGMEM = "SENSORS.NTC"; -const char L_SENSORS_BLE[] PROGMEM = "SENSORS.BLE"; -const char L_REGULATOR[] PROGMEM = "REGULATOR"; -const char L_REGULATOR_PID[] PROGMEM = "REGULATOR.PID"; -const char L_REGULATOR_EQUITHERM[] PROGMEM = "REGULATOR.EQUITHERM"; -const char L_CASCADE_INPUT[] PROGMEM = "CASCADE.INPUT"; -const char L_CASCADE_OUTPUT[] PROGMEM = "CASCADE.OUTPUT"; -const char L_EXTPUMP[] PROGMEM = "EXTPUMP"; \ No newline at end of file +const char L_SETTINGS[] PROGMEM = "SETTINGS"; +const char L_SETTINGS_OT[] PROGMEM = "SETTINGS.OT"; +const char L_SETTINGS_DHW[] PROGMEM = "SETTINGS.DHW"; +const char L_SETTINGS_HEATING[] PROGMEM = "SETTINGS.HEATING"; +const char L_NETWORK[] PROGMEM = "NETWORK"; +const char L_NETWORK_SETTINGS[] PROGMEM = "NETWORK.SETTINGS"; +const char L_PORTAL_WEBSERVER[] PROGMEM = "PORTAL.WEBSERVER"; +const char L_PORTAL_DNSSERVER[] PROGMEM = "PORTAL.DNSSERVER"; +const char L_PORTAL_CAPTIVE[] PROGMEM = "PORTAL.CAPTIVE"; +const char L_PORTAL_OTA[] PROGMEM = "PORTAL.OTA"; +const char L_MAIN[] PROGMEM = "MAIN"; +const char L_MQTT[] PROGMEM = "MQTT"; +const char L_MQTT_HA[] PROGMEM = "MQTT.HA"; +const char L_MQTT_MSG[] PROGMEM = "MQTT.MSG"; +const char L_OT[] PROGMEM = "OT"; +const char L_OT_DHW[] PROGMEM = "OT.DHW"; +const char L_OT_HEATING[] PROGMEM = "OT.HEATING"; +const char L_OT_CH2[] PROGMEM = "OT.CH2"; +const char L_SENSORS[] PROGMEM = "SENSORS"; +const char L_SENSORS_SETTINGS[] PROGMEM = "SENSORS.SETTINGS"; +const char L_SENSORS_DALLAS[] PROGMEM = "SENSORS.DALLAS"; +const char L_SENSORS_NTC[] PROGMEM = "SENSORS.NTC"; +const char L_SENSORS_BLE[] PROGMEM = "SENSORS.BLE"; +const char L_REGULATOR[] PROGMEM = "REGULATOR"; +const char L_REGULATOR_PID[] PROGMEM = "REGULATOR.PID"; +const char L_REGULATOR_EQUITHERM[] PROGMEM = "REGULATOR.EQUITHERM"; +const char L_CASCADE_INPUT[] PROGMEM = "CASCADE.INPUT"; +const char L_CASCADE_OUTPUT[] PROGMEM = "CASCADE.OUTPUT"; +const char L_EXTPUMP[] PROGMEM = "EXTPUMP"; + + +const char S_ACTIONS[] PROGMEM = "actions"; +const char S_ACTIVE[] PROGMEM = "active"; +const char S_ADDRESS[] PROGMEM = "address"; +const char S_ANTI_STUCK_INTERVAL[] PROGMEM = "antiStuckInterval"; +const char S_ANTI_STUCK_TIME[] PROGMEM = "antiStuckTime"; +const char S_AP[] PROGMEM = "ap"; +const char S_APP_VERSION[] PROGMEM = "appVersion"; +const char S_AUTH[] PROGMEM = "auth"; +const char S_BACKTRACE[] PROGMEM = "backtrace"; +const char S_BATTERY[] PROGMEM = "battery"; +const char S_BAUDRATE[] PROGMEM = "baudrate"; +const char S_BLOCKING[] PROGMEM = "blocking"; +const char S_BSSID[] PROGMEM = "bssid"; +const char S_BUILD[] PROGMEM = "build"; +const char S_CASCADE_CONTROL[] PROGMEM = "cascadeControl"; +const char S_CHANNEL[] PROGMEM = "channel"; +const char S_CHIP[] PROGMEM = "chip"; +const char S_CODE[] PROGMEM = "code"; +const char S_CONNECTED[] PROGMEM = "connected"; +const char S_CONTINUES[] PROGMEM = "continues"; +const char S_CORE[] PROGMEM = "core"; +const char S_CORES[] PROGMEM = "cores"; +const char S_CRASH[] PROGMEM = "crash"; +const char S_CURRENT_TEMP[] PROGMEM = "currentTemp"; +const char S_DATA[] PROGMEM = "data"; +const char S_DATE[] PROGMEM = "date"; +const char S_DHW[] PROGMEM = "dhw"; +const char S_DHW_BLOCKING[] PROGMEM = "dhwBlocking"; +const char S_DHW_PRESENT[] PROGMEM = "dhwPresent"; +const char S_DHW_TO_CH2[] PROGMEM = "dhwToCh2"; +const char S_DIAG[] PROGMEM = "diag"; +const char S_DNS[] PROGMEM = "dns"; +const char S_DT[] PROGMEM = "dt"; +const char S_D_FACTOR[] PROGMEM = "d_factor"; +const char S_EMERGENCY[] PROGMEM = "emergency"; +const char S_ENABLED[] PROGMEM = "enabled"; +const char S_ENV[] PROGMEM = "env"; +const char S_EPC[] PROGMEM = "epc"; +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_FILTERING[] PROGMEM = "filtering"; +const char S_FILTERING_FACTOR[] PROGMEM = "filteringFactor"; +const char S_FLAGS[] PROGMEM = "flags"; +const char S_FLAME[] PROGMEM = "flame"; +const char S_FLASH[] PROGMEM = "flash"; +const char S_FREE[] PROGMEM = "free"; +const char S_FREQ[] PROGMEM = "freq"; +const char S_GATEWAY[] PROGMEM = "gateway"; +const char S_GET_MIN_MAX_TEMP[] PROGMEM = "getMinMaxTemp"; +const char S_GPIO[] PROGMEM = "gpio"; +const char S_HEAP[] PROGMEM = "heap"; +const char S_HEATING[] PROGMEM = "heating"; +const char S_HEATING_CH1_TO_CH2[] PROGMEM = "heatingCh1ToCh2"; +const char S_HEATING_CH2_ENABLED[] PROGMEM = "heatingCh2Enabled"; +const char S_HIDDEN[] PROGMEM = "hidden"; +const char S_HOME_ASSISTANT_DISCOVERY[] PROGMEM = "homeAssistantDiscovery"; +const char S_HOSTNAME[] PROGMEM = "hostname"; +const char S_HUMIDITY[] PROGMEM = "humidity"; +const char S_HYSTERESIS[] PROGMEM = "hysteresis"; +const char S_ID[] PROGMEM = "id"; +const char S_IMMERGAS_FIX[] PROGMEM = "immergasFix"; +const char S_INDOOR_TEMP[] PROGMEM = "indoorTemp"; +const char S_INDOOR_TEMP_CONTROL[] PROGMEM = "indoorTempControl"; +const char S_IN_GPIO[] PROGMEM = "inGpio"; +const char S_INPUT[] PROGMEM = "input"; +const char S_INTERVAL[] PROGMEM = "interval"; +const char S_INVERT_STATE[] PROGMEM = "invertState"; +const char S_IP[] PROGMEM = "ip"; +const char S_I_FACTOR[] PROGMEM = "i_factor"; +const char S_K_FACTOR[] PROGMEM = "k_factor"; +const char S_LOGIN[] PROGMEM = "login"; +const char S_LOG_LEVEL[] PROGMEM = "logLevel"; +const char S_MAC[] PROGMEM = "mac"; +const char S_MASTER[] PROGMEM = "master"; +const char S_MAX[] PROGMEM = "max"; +const char S_MAX_FREE_BLOCK[] PROGMEM = "maxFreeBlock"; +const char S_MAX_MODULATION[] PROGMEM = "maxModulation"; +const char S_MAX_POWER[] PROGMEM = "maxPower"; +const char S_MAX_TEMP[] PROGMEM = "maxTemp"; +const char S_MEMBER_ID[] PROGMEM = "memberId"; +const char S_MIN[] PROGMEM = "min"; +const char S_MIN_FREE[] PROGMEM = "minFree"; +const char S_MIN_MAX_FREE_BLOCK[] PROGMEM = "minMaxFreeBlock"; +const char S_MIN_POWER[] PROGMEM = "minPower"; +const char S_MIN_TEMP[] PROGMEM = "minTemp"; +const char S_MODEL[] PROGMEM = "model"; +const char S_MODULATION[] PROGMEM = "modulation"; +const char S_MODULATION_SYNC_WITH_HEATING[] PROGMEM = "modulationSyncWithHeating"; +const char S_MQTT[] PROGMEM = "mqtt"; +const char S_NAME[] PROGMEM = "name"; +const char S_NATIVE_HEATING_CONTROL[] PROGMEM = "nativeHeatingControl"; +const char S_NETWORK[] PROGMEM = "network"; +const char S_N_FACTOR[] PROGMEM = "n_factor"; +const char S_OFFSET[] PROGMEM = "offset"; +const char S_ON_ENABLED_HEATING[] PROGMEM = "onEnabledHeating"; +const char S_ON_FAULT[] PROGMEM = "onFault"; +const char S_ON_LOSS_CONNECTION[] PROGMEM = "onLossConnection"; +const char S_OPENTHERM[] PROGMEM = "opentherm"; +const char S_OUTDOOR_TEMP[] PROGMEM = "outdoorTemp"; +const char S_OUT_GPIO[] PROGMEM = "outGpio"; +const char S_OUTPUT[] PROGMEM = "output"; +const char S_PASSWORD[] PROGMEM = "password"; +const char S_PID[] PROGMEM = "pid"; +const char S_PORT[] PROGMEM = "port"; +const char S_PORTAL[] PROGMEM = "portal"; +const char S_POST_CIRCULATION_TIME[] PROGMEM = "postCirculationTime"; +const char S_POWER[] PROGMEM = "power"; +const char S_PREFIX[] PROGMEM = "prefix"; +const char S_PROTOCOL_VERSION[] PROGMEM = "protocolVersion"; +const char S_PURPOSE[] PROGMEM = "purpose"; +const char S_P_FACTOR[] PROGMEM = "p_factor"; +const char S_REAL_SIZE[] PROGMEM = "realSize"; +const char S_REASON[] PROGMEM = "reason"; +const char S_RESET_DIAGNOSTIC[] PROGMEM = "resetDiagnostic"; +const char S_RESET_FAULT[] PROGMEM = "resetFault"; +const char S_RESET_REASON[] PROGMEM = "resetReason"; +const char S_RESTART[] PROGMEM = "restart"; +const char S_RETURN_TEMP[] PROGMEM = "returnTemp"; +const char S_REV[] PROGMEM = "rev"; +const char S_RSSI[] PROGMEM = "rssi"; +const char S_RX_LED_GPIO[] PROGMEM = "rxLedGpio"; +const char S_SDK[] PROGMEM = "sdk"; +const char S_SENSORS[] PROGMEM = "sensors"; +const char S_SERIAL[] PROGMEM = "serial"; +const char S_SERVER[] PROGMEM = "server"; +const char S_SETTINGS[] PROGMEM = "settings"; +const char S_SIGNAL_QUALITY[] PROGMEM = "signalQuality"; +const char S_SIZE[] PROGMEM = "size"; +const char S_SLAVE[] PROGMEM = "slave"; +const char S_SSID[] PROGMEM = "ssid"; +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_SUBNET[] PROGMEM = "subnet"; +const char S_SUMMER_WINTER_MODE[] PROGMEM = "summerWinterMode"; +const char S_SYSTEM[] PROGMEM = "system"; +const char S_TARGET[] PROGMEM = "target"; +const char S_TARGET_TEMP[] PROGMEM = "targetTemp"; +const char S_TELNET[] PROGMEM = "telnet"; +const char S_TEMPERATURE[] PROGMEM = "temperature"; +const char S_THRESHOLD_TIME[] PROGMEM = "thresholdTime"; +const char S_TOTAL[] PROGMEM = "total"; +const char S_TRESHOLD_TIME[] PROGMEM = "tresholdTime"; +const char S_TURBO[] PROGMEM = "turbo"; +const char S_TURBO_FACTOR[] PROGMEM = "turboFactor"; +const char S_TYPE[] PROGMEM = "type"; +const char S_T_FACTOR[] PROGMEM = "t_factor"; +const char S_UNIT_SYSTEM[] PROGMEM = "unitSystem"; +const char S_UPTIME[] PROGMEM = "uptime"; +const char S_USE[] PROGMEM = "use"; +const char S_USE_DHCP[] PROGMEM = "useDhcp"; +const char S_USER[] PROGMEM = "user"; +const char S_VALUE[] PROGMEM = "value"; +const char S_VERSION[] PROGMEM = "version"; diff --git a/src/utils.h b/src/utils.h index 9f7f700..0eac8c7 100644 --- a/src/utils.h +++ b/src/utils.h @@ -69,14 +69,14 @@ inline bool isValidTemp(const float value, UnitSystem unit, const float min = 0. float roundf(float value, uint8_t decimals = 2) { if (decimals == 0) { - return (int)(value + 0.5); + return (int)(value + 0.5f); - } else if (abs(value) < 0.00000001) { - return 0.0; + } else if (abs(value) < 0.00000001f) { + return 0.0f; } float multiplier = pow10(decimals); - value += 0.5 / multiplier * (value < 0 ? -1 : 1); + value += 0.5f / multiplier * (value < 0.0f ? -1.0f : 1.0f); return (int)(value * multiplier) / multiplier; } @@ -211,29 +211,29 @@ void arr2str(String& str, T arr[], size_t length) { } void networkSettingsToJson(const NetworkSettings& src, JsonVariant dst) { - dst["hostname"] = src.hostname; + dst[FPSTR(S_HOSTNAME)] = src.hostname; - dst["useDhcp"] = src.useDhcp; - dst["staticConfig"]["ip"] = src.staticConfig.ip; - dst["staticConfig"]["gateway"] = src.staticConfig.gateway; - dst["staticConfig"]["subnet"] = src.staticConfig.subnet; - dst["staticConfig"]["dns"] = src.staticConfig.dns; + dst[FPSTR(S_USE_DHCP)] = src.useDhcp; + dst[FPSTR(S_STATIC_CONFIG)][FPSTR(S_IP)] = src.staticConfig.ip; + dst[FPSTR(S_STATIC_CONFIG)][FPSTR(S_GATEWAY)] = src.staticConfig.gateway; + dst[FPSTR(S_STATIC_CONFIG)][FPSTR(S_SUBNET)] = src.staticConfig.subnet; + dst[FPSTR(S_STATIC_CONFIG)][FPSTR(S_DNS)] = src.staticConfig.dns; - dst["ap"]["ssid"] = src.ap.ssid; - dst["ap"]["password"] = src.ap.password; - dst["ap"]["channel"] = src.ap.channel; + dst[FPSTR(S_AP)][FPSTR(S_SSID)] = src.ap.ssid; + dst[FPSTR(S_AP)][FPSTR(S_PASSWORD)] = src.ap.password; + dst[FPSTR(S_AP)][FPSTR(S_CHANNEL)] = src.ap.channel; - dst["sta"]["ssid"] = src.sta.ssid; - dst["sta"]["password"] = src.sta.password; - dst["sta"]["channel"] = src.sta.channel; + dst[FPSTR(S_STA)][FPSTR(S_SSID)] = src.sta.ssid; + dst[FPSTR(S_STA)][FPSTR(S_PASSWORD)] = src.sta.password; + dst[FPSTR(S_STA)][FPSTR(S_CHANNEL)] = src.sta.channel; } bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { bool changed = false; // hostname - if (!src["hostname"].isNull()) { - String value = src["hostname"].as(); + if (!src[FPSTR(S_HOSTNAME)].isNull()) { + String value = src[FPSTR(S_HOSTNAME)].as(); if (value.length() < sizeof(dst.hostname) && !value.equals(dst.hostname)) { strcpy(dst.hostname, value.c_str()); @@ -242,15 +242,15 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { } // use dhcp - if (src["useDhcp"].is()) { - dst.useDhcp = src["useDhcp"].as(); + if (src[FPSTR(S_USE_DHCP)].is()) { + dst.useDhcp = src[FPSTR(S_USE_DHCP)].as(); changed = true; } // static config - if (!src["staticConfig"]["ip"].isNull()) { - String value = src["staticConfig"]["ip"].as(); + if (!src[FPSTR(S_STATIC_CONFIG)][FPSTR(S_IP)].isNull()) { + String value = src[FPSTR(S_STATIC_CONFIG)][FPSTR(S_IP)].as(); if (value.length() < sizeof(dst.staticConfig.ip) && !value.equals(dst.staticConfig.ip)) { strcpy(dst.staticConfig.ip, value.c_str()); @@ -258,8 +258,8 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { } } - if (!src["staticConfig"]["gateway"].isNull()) { - String value = src["staticConfig"]["gateway"].as(); + if (!src[FPSTR(S_STATIC_CONFIG)][FPSTR(S_GATEWAY)].isNull()) { + String value = src[FPSTR(S_STATIC_CONFIG)][FPSTR(S_GATEWAY)].as(); if (value.length() < sizeof(dst.staticConfig.gateway) && !value.equals(dst.staticConfig.gateway)) { strcpy(dst.staticConfig.gateway, value.c_str()); @@ -267,8 +267,8 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { } } - if (!src["staticConfig"]["subnet"].isNull()) { - String value = src["staticConfig"]["subnet"].as(); + if (!src[FPSTR(S_STATIC_CONFIG)][FPSTR(S_SUBNET)].isNull()) { + String value = src[FPSTR(S_STATIC_CONFIG)][FPSTR(S_SUBNET)].as(); if (value.length() < sizeof(dst.staticConfig.subnet) && !value.equals(dst.staticConfig.subnet)) { strcpy(dst.staticConfig.subnet, value.c_str()); @@ -276,8 +276,8 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { } } - if (!src["staticConfig"]["dns"].isNull()) { - String value = src["staticConfig"]["dns"].as(); + if (!src[FPSTR(S_STATIC_CONFIG)][FPSTR(S_DNS)].isNull()) { + String value = src[FPSTR(S_STATIC_CONFIG)][FPSTR(S_DNS)].as(); if (value.length() < sizeof(dst.staticConfig.dns) && !value.equals(dst.staticConfig.dns)) { strcpy(dst.staticConfig.dns, value.c_str()); @@ -287,8 +287,8 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { // ap - if (!src["ap"]["ssid"].isNull()) { - String value = src["ap"]["ssid"].as(); + if (!src[FPSTR(S_AP)][FPSTR(S_SSID)].isNull()) { + String value = src[FPSTR(S_AP)][FPSTR(S_SSID)].as(); if (value.length() < sizeof(dst.ap.ssid) && !value.equals(dst.ap.ssid)) { strcpy(dst.ap.ssid, value.c_str()); @@ -296,8 +296,8 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { } } - if (!src["ap"]["password"].isNull()) { - String value = src["ap"]["password"].as(); + if (!src[FPSTR(S_AP)][FPSTR(S_PASSWORD)].isNull()) { + String value = src[FPSTR(S_AP)][FPSTR(S_PASSWORD)].as(); if (value.length() < sizeof(dst.ap.password) && !value.equals(dst.ap.password)) { strcpy(dst.ap.password, value.c_str()); @@ -305,8 +305,8 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { } } - if (!src["ap"]["channel"].isNull()) { - unsigned char value = src["ap"]["channel"].as(); + if (!src[FPSTR(S_AP)][FPSTR(S_CHANNEL)].isNull()) { + unsigned char value = src[FPSTR(S_AP)][FPSTR(S_CHANNEL)].as(); if (value >= 0 && value < 12) { dst.ap.channel = value; @@ -316,8 +316,8 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { // sta - if (!src["sta"]["ssid"].isNull()) { - String value = src["sta"]["ssid"].as(); + if (!src[FPSTR(S_STA)][FPSTR(S_SSID)].isNull()) { + String value = src[FPSTR(S_STA)][FPSTR(S_SSID)].as(); if (value.length() < sizeof(dst.sta.ssid) && !value.equals(dst.sta.ssid)) { strcpy(dst.sta.ssid, value.c_str()); @@ -325,8 +325,8 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { } } - if (!src["sta"]["password"].isNull()) { - String value = src["sta"]["password"].as(); + if (!src[FPSTR(S_STA)][FPSTR(S_PASSWORD)].isNull()) { + String value = src[FPSTR(S_STA)][FPSTR(S_PASSWORD)].as(); if (value.length() < sizeof(dst.sta.password) && !value.equals(dst.sta.password)) { strcpy(dst.sta.password, value.c_str()); @@ -334,8 +334,8 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { } } - if (!src["sta"]["channel"].isNull()) { - unsigned char value = src["sta"]["channel"].as(); + if (!src[FPSTR(S_STA)][FPSTR(S_CHANNEL)].isNull()) { + unsigned char value = src[FPSTR(S_STA)][FPSTR(S_CHANNEL)].as(); if (value >= 0 && value < 12) { dst.sta.channel = value; @@ -348,96 +348,113 @@ bool jsonToNetworkSettings(const JsonVariantConst src, NetworkSettings& dst) { void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) { if (!safe) { - dst["system"]["logLevel"] = static_cast(src.system.logLevel); - dst["system"]["serial"]["enable"] = src.system.serial.enabled; - dst["system"]["serial"]["baudrate"] = src.system.serial.baudrate; - dst["system"]["telnet"]["enable"] = src.system.telnet.enabled; - dst["system"]["telnet"]["port"] = src.system.telnet.port; - dst["system"]["unitSystem"] = static_cast(src.system.unitSystem); - dst["system"]["statusLedGpio"] = src.system.statusLedGpio; - - dst["portal"]["auth"] = src.portal.auth; - dst["portal"]["login"] = src.portal.login; - dst["portal"]["password"] = src.portal.password; - - dst["opentherm"]["unitSystem"] = static_cast(src.opentherm.unitSystem); - dst["opentherm"]["inGpio"] = src.opentherm.inGpio; - dst["opentherm"]["outGpio"] = src.opentherm.outGpio; - dst["opentherm"]["rxLedGpio"] = src.opentherm.rxLedGpio; - dst["opentherm"]["memberId"] = src.opentherm.memberId; - dst["opentherm"]["flags"] = src.opentherm.flags; - dst["opentherm"]["maxModulation"] = src.opentherm.maxModulation; - dst["opentherm"]["minPower"] = roundf(src.opentherm.minPower, 2); - dst["opentherm"]["maxPower"] = roundf(src.opentherm.maxPower, 2); - dst["opentherm"]["dhwPresent"] = src.opentherm.dhwPresent; - dst["opentherm"]["summerWinterMode"] = src.opentherm.summerWinterMode; - dst["opentherm"]["heatingCh2Enabled"] = src.opentherm.heatingCh2Enabled; - dst["opentherm"]["heatingCh1ToCh2"] = src.opentherm.heatingCh1ToCh2; - dst["opentherm"]["dhwToCh2"] = src.opentherm.dhwToCh2; - dst["opentherm"]["dhwBlocking"] = src.opentherm.dhwBlocking; - dst["opentherm"]["modulationSyncWithHeating"] = src.opentherm.modulationSyncWithHeating; - dst["opentherm"]["getMinMaxTemp"] = src.opentherm.getMinMaxTemp; - dst["opentherm"]["nativeHeatingControl"] = src.opentherm.nativeHeatingControl; - dst["opentherm"]["immergasFix"] = src.opentherm.immergasFix; - - dst["mqtt"]["enable"] = src.mqtt.enabled; - dst["mqtt"]["server"] = src.mqtt.server; - dst["mqtt"]["port"] = src.mqtt.port; - dst["mqtt"]["user"] = src.mqtt.user; - dst["mqtt"]["password"] = src.mqtt.password; - dst["mqtt"]["prefix"] = src.mqtt.prefix; - dst["mqtt"]["interval"] = src.mqtt.interval; - dst["mqtt"]["homeAssistantDiscovery"] = src.mqtt.homeAssistantDiscovery; - - dst["emergency"]["target"] = roundf(src.emergency.target, 2); - dst["emergency"]["tresholdTime"] = src.emergency.tresholdTime; - } - - dst["heating"]["enable"] = src.heating.enabled; - dst["heating"]["turbo"] = src.heating.turbo; - dst["heating"]["target"] = roundf(src.heating.target, 2); - dst["heating"]["hysteresis"] = roundf(src.heating.hysteresis, 3); - dst["heating"]["turboFactor"] = roundf(src.heating.turboFactor, 3); - dst["heating"]["minTemp"] = src.heating.minTemp; - dst["heating"]["maxTemp"] = src.heating.maxTemp; - - dst["dhw"]["enable"] = src.dhw.enabled; - dst["dhw"]["target"] = roundf(src.dhw.target, 1); - dst["dhw"]["minTemp"] = src.dhw.minTemp; - dst["dhw"]["maxTemp"] = src.dhw.maxTemp; - - dst["equitherm"]["enable"] = src.equitherm.enabled; - dst["equitherm"]["n_factor"] = roundf(src.equitherm.n_factor, 3); - dst["equitherm"]["k_factor"] = roundf(src.equitherm.k_factor, 3); - dst["equitherm"]["t_factor"] = roundf(src.equitherm.t_factor, 3); - - dst["pid"]["enable"] = src.pid.enabled; - dst["pid"]["p_factor"] = roundf(src.pid.p_factor, 3); - dst["pid"]["i_factor"] = roundf(src.pid.i_factor, 4); - dst["pid"]["d_factor"] = roundf(src.pid.d_factor, 1); - dst["pid"]["dt"] = src.pid.dt; - dst["pid"]["minTemp"] = src.pid.minTemp; - dst["pid"]["maxTemp"] = src.pid.maxTemp; + auto system = dst[FPSTR(S_SYSTEM)].to(); + system[FPSTR(S_LOG_LEVEL)] = static_cast(src.system.logLevel); + + auto serial = system[FPSTR(S_SERIAL)].to(); + serial[FPSTR(S_ENABLED)] = src.system.serial.enabled; + serial[FPSTR(S_BAUDRATE)] = src.system.serial.baudrate; + + auto telnet = system[FPSTR(S_TELNET)].to(); + telnet[FPSTR(S_ENABLED)] = src.system.telnet.enabled; + telnet[FPSTR(S_PORT)] = src.system.telnet.port; + + system[FPSTR(S_UNIT_SYSTEM)] = static_cast(src.system.unitSystem); + system[FPSTR(S_STATUS_LED_GPIO)] = src.system.statusLedGpio; + + auto portal = dst[FPSTR(S_PORTAL)].to(); + portal[FPSTR(S_AUTH)] = src.portal.auth; + portal[FPSTR(S_LOGIN)] = src.portal.login; + portal[FPSTR(S_PASSWORD)] = src.portal.password; + + auto opentherm = dst[FPSTR(S_OPENTHERM)].to(); + opentherm[FPSTR(S_UNIT_SYSTEM)] = static_cast(src.opentherm.unitSystem); + opentherm[FPSTR(S_IN_GPIO)] = src.opentherm.inGpio; + opentherm[FPSTR(S_OUT_GPIO)] = src.opentherm.outGpio; + opentherm[FPSTR(S_RX_LED_GPIO)] = src.opentherm.rxLedGpio; + opentherm[FPSTR(S_MEMBER_ID)] = src.opentherm.memberId; + opentherm[FPSTR(S_FLAGS)] = src.opentherm.flags; + opentherm[FPSTR(S_MAX_MODULATION)] = src.opentherm.maxModulation; + opentherm[FPSTR(S_MIN_POWER)] = roundf(src.opentherm.minPower, 2); + opentherm[FPSTR(S_MAX_POWER)] = roundf(src.opentherm.maxPower, 2); + opentherm[FPSTR(S_DHW_PRESENT)] = src.opentherm.dhwPresent; + opentherm[FPSTR(S_SUMMER_WINTER_MODE)] = src.opentherm.summerWinterMode; + opentherm[FPSTR(S_HEATING_CH2_ENABLED)] = src.opentherm.heatingCh2Enabled; + opentherm[FPSTR(S_HEATING_CH1_TO_CH2)] = src.opentherm.heatingCh1ToCh2; + opentherm[FPSTR(S_DHW_TO_CH2)] = src.opentherm.dhwToCh2; + opentherm[FPSTR(S_DHW_BLOCKING)] = src.opentherm.dhwBlocking; + opentherm[FPSTR(S_MODULATION_SYNC_WITH_HEATING)] = src.opentherm.modulationSyncWithHeating; + opentherm[FPSTR(S_GET_MIN_MAX_TEMP)] = src.opentherm.getMinMaxTemp; + opentherm[FPSTR(S_NATIVE_HEATING_CONTROL)] = src.opentherm.nativeHeatingControl; + opentherm[FPSTR(S_IMMERGAS_FIX)] = src.opentherm.immergasFix; + + auto mqtt = dst[FPSTR(S_MQTT)].to(); + mqtt[FPSTR(S_ENABLED)] = src.mqtt.enabled; + mqtt[FPSTR(S_SERVER)] = src.mqtt.server; + mqtt[FPSTR(S_PORT)] = src.mqtt.port; + mqtt[FPSTR(S_USER)] = src.mqtt.user; + mqtt[FPSTR(S_PASSWORD)] = src.mqtt.password; + mqtt[FPSTR(S_PREFIX)] = src.mqtt.prefix; + mqtt[FPSTR(S_INTERVAL)] = src.mqtt.interval; + mqtt[FPSTR(S_HOME_ASSISTANT_DISCOVERY)] = src.mqtt.homeAssistantDiscovery; + + auto emergency = dst[FPSTR(S_EMERGENCY)].to(); + emergency[FPSTR(S_TARGET)] = roundf(src.emergency.target, 2); + emergency[FPSTR(S_TRESHOLD_TIME)] = src.emergency.tresholdTime; + } + + auto heating = dst[FPSTR(S_HEATING)].to(); + heating[FPSTR(S_ENABLED)] = src.heating.enabled; + heating[FPSTR(S_TURBO)] = src.heating.turbo; + heating[FPSTR(S_TARGET)] = roundf(src.heating.target, 2); + heating[FPSTR(S_HYSTERESIS)] = roundf(src.heating.hysteresis, 3); + heating[FPSTR(S_TURBO_FACTOR)] = roundf(src.heating.turboFactor, 3); + heating[FPSTR(S_MIN_TEMP)] = src.heating.minTemp; + heating[FPSTR(S_MAX_TEMP)] = src.heating.maxTemp; + + auto dhw = dst[FPSTR(S_DHW)].to(); + dhw[FPSTR(S_ENABLED)] = src.dhw.enabled; + dhw[FPSTR(S_TARGET)] = roundf(src.dhw.target, 1); + dhw[FPSTR(S_MIN_TEMP)] = src.dhw.minTemp; + dhw[FPSTR(S_MAX_TEMP)] = src.dhw.maxTemp; + + auto equitherm = dst[FPSTR(S_EQUITHERM)].to(); + equitherm[FPSTR(S_ENABLED)] = src.equitherm.enabled; + equitherm[FPSTR(S_N_FACTOR)] = roundf(src.equitherm.n_factor, 3); + equitherm[FPSTR(S_K_FACTOR)] = roundf(src.equitherm.k_factor, 3); + equitherm[FPSTR(S_T_FACTOR)] = roundf(src.equitherm.t_factor, 3); + + auto pid = dst[FPSTR(S_PID)].to(); + pid[FPSTR(S_ENABLED)] = src.pid.enabled; + pid[FPSTR(S_P_FACTOR)] = roundf(src.pid.p_factor, 3); + pid[FPSTR(S_I_FACTOR)] = roundf(src.pid.i_factor, 4); + pid[FPSTR(S_D_FACTOR)] = roundf(src.pid.d_factor, 1); + pid[FPSTR(S_DT)] = src.pid.dt; + pid[FPSTR(S_MIN_TEMP)] = src.pid.minTemp; + pid[FPSTR(S_MAX_TEMP)] = src.pid.maxTemp; if (!safe) { - dst["externalPump"]["use"] = src.externalPump.use; - dst["externalPump"]["gpio"] = src.externalPump.gpio; - dst["externalPump"]["postCirculationTime"] = roundf(src.externalPump.postCirculationTime / 60, 0); - dst["externalPump"]["antiStuckInterval"] = roundf(src.externalPump.antiStuckInterval / 86400, 0); - dst["externalPump"]["antiStuckTime"] = roundf(src.externalPump.antiStuckTime / 60, 0); - - dst["cascadeControl"]["input"]["enable"] = src.cascadeControl.input.enabled; - dst["cascadeControl"]["input"]["gpio"] = src.cascadeControl.input.gpio; - dst["cascadeControl"]["input"]["invertState"] = src.cascadeControl.input.invertState; - dst["cascadeControl"]["input"]["thresholdTime"] = src.cascadeControl.input.thresholdTime; - - dst["cascadeControl"]["output"]["enable"] = src.cascadeControl.output.enabled; - dst["cascadeControl"]["output"]["gpio"] = src.cascadeControl.output.gpio; - dst["cascadeControl"]["output"]["invertState"] = src.cascadeControl.output.invertState; - dst["cascadeControl"]["output"]["thresholdTime"] = src.cascadeControl.output.thresholdTime; - dst["cascadeControl"]["output"]["onFault"] = src.cascadeControl.output.onFault; - dst["cascadeControl"]["output"]["onLossConnection"] = src.cascadeControl.output.onLossConnection; - dst["cascadeControl"]["output"]["onEnabledHeating"] = src.cascadeControl.output.onEnabledHeating; + 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_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); + + auto cascadeControlInput = dst[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_INPUT)].to(); + cascadeControlInput[FPSTR(S_ENABLED)] = src.cascadeControl.input.enabled; + cascadeControlInput[FPSTR(S_GPIO)] = src.cascadeControl.input.gpio; + cascadeControlInput[FPSTR(S_INVERT_STATE)] = src.cascadeControl.input.invertState; + cascadeControlInput[FPSTR(S_THRESHOLD_TIME)] = src.cascadeControl.input.thresholdTime; + + auto cascadeControlOutput = dst[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_OUTPUT)].to(); + cascadeControlOutput[FPSTR(S_ENABLED)] = src.cascadeControl.output.enabled; + cascadeControlOutput[FPSTR(S_GPIO)] = src.cascadeControl.output.gpio; + cascadeControlOutput[FPSTR(S_INVERT_STATE)] = src.cascadeControl.output.invertState; + cascadeControlOutput[FPSTR(S_THRESHOLD_TIME)] = src.cascadeControl.output.thresholdTime; + cascadeControlOutput[FPSTR(S_ON_FAULT)] = src.cascadeControl.output.onFault; + cascadeControlOutput[FPSTR(S_ON_LOSS_CONNECTION)] = src.cascadeControl.output.onLossConnection; + cascadeControlOutput[FPSTR(S_ON_ENABLED_HEATING)] = src.cascadeControl.output.onEnabledHeating; } } @@ -450,8 +467,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!safe) { // system - if (!src["system"]["logLevel"].isNull()) { - uint8_t value = src["system"]["logLevel"].as(); + if (!src[FPSTR(S_SYSTEM)][FPSTR(S_LOG_LEVEL)].isNull()) { + uint8_t value = src[FPSTR(S_SYSTEM)][FPSTR(S_LOG_LEVEL)].as(); if (value != dst.system.logLevel && value >= TinyLogger::Level::SILENT && value <= TinyLogger::Level::VERBOSE) { dst.system.logLevel = value; @@ -459,8 +476,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["system"]["serial"]["enable"].is()) { - bool value = src["system"]["serial"]["enable"].as(); + if (src[FPSTR(S_SYSTEM)][FPSTR(S_SERIAL)][FPSTR(S_ENABLED)].is()) { + bool value = src[FPSTR(S_SYSTEM)][FPSTR(S_SERIAL)][FPSTR(S_ENABLED)].as(); if (value != dst.system.serial.enabled) { dst.system.serial.enabled = value; @@ -468,8 +485,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["system"]["serial"]["baudrate"].isNull()) { - unsigned int value = src["system"]["serial"]["baudrate"].as(); + if (!src[FPSTR(S_SYSTEM)][FPSTR(S_SERIAL)][FPSTR(S_BAUDRATE)].isNull()) { + unsigned int value = src[FPSTR(S_SYSTEM)][FPSTR(S_SERIAL)][FPSTR(S_BAUDRATE)].as(); if (value == 9600 || value == 19200 || value == 38400 || value == 57600 || value == 74880 || value == 115200) { if (value != dst.system.serial.baudrate) { @@ -479,8 +496,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["system"]["telnet"]["enable"].is()) { - bool value = src["system"]["telnet"]["enable"].as(); + if (src[FPSTR(S_SYSTEM)][FPSTR(S_TELNET)][FPSTR(S_ENABLED)].is()) { + bool value = src[FPSTR(S_SYSTEM)][FPSTR(S_TELNET)][FPSTR(S_ENABLED)].as(); if (value != dst.system.telnet.enabled) { dst.system.telnet.enabled = value; @@ -488,8 +505,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["system"]["telnet"]["port"].isNull()) { - unsigned short value = src["system"]["telnet"]["port"].as(); + if (!src[FPSTR(S_SYSTEM)][FPSTR(S_TELNET)][FPSTR(S_PORT)].isNull()) { + unsigned short value = src[FPSTR(S_SYSTEM)][FPSTR(S_TELNET)][FPSTR(S_PORT)].as(); if (value > 0 && value <= 65535 && value != dst.system.telnet.port) { dst.system.telnet.port = value; @@ -497,8 +514,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["system"]["unitSystem"].isNull()) { - uint8_t value = src["system"]["unitSystem"].as(); + if (!src[FPSTR(S_SYSTEM)][FPSTR(S_UNIT_SYSTEM)].isNull()) { + uint8_t value = src[FPSTR(S_SYSTEM)][FPSTR(S_UNIT_SYSTEM)].as(); UnitSystem prevUnitSystem = dst.system.unitSystem; switch (value) { @@ -534,15 +551,15 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["system"]["statusLedGpio"].isNull()) { - if (src["system"]["statusLedGpio"].is() && src["system"]["statusLedGpio"].as().size() == 0) { + if (!src[FPSTR(S_SYSTEM)][FPSTR(S_STATUS_LED_GPIO)].isNull()) { + if (src[FPSTR(S_SYSTEM)][FPSTR(S_STATUS_LED_GPIO)].is() && src[FPSTR(S_SYSTEM)][FPSTR(S_STATUS_LED_GPIO)].as().size() == 0) { if (dst.system.statusLedGpio != GPIO_IS_NOT_CONFIGURED) { dst.system.statusLedGpio = GPIO_IS_NOT_CONFIGURED; changed = true; } } else { - unsigned char value = src["system"]["statusLedGpio"].as(); + unsigned char value = src[FPSTR(S_SYSTEM)][FPSTR(S_STATUS_LED_GPIO)].as(); if (GPIO_IS_VALID(value) && value != dst.system.statusLedGpio) { dst.system.statusLedGpio = value; @@ -553,8 +570,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // portal - if (src["portal"]["auth"].is()) { - bool value = src["portal"]["auth"].as(); + if (src[FPSTR(S_PORTAL)][FPSTR(S_AUTH)].is()) { + bool value = src[FPSTR(S_PORTAL)][FPSTR(S_AUTH)].as(); if (value != dst.portal.auth) { dst.portal.auth = value; @@ -562,8 +579,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["portal"]["login"].isNull()) { - String value = src["portal"]["login"].as(); + if (!src[FPSTR(S_PORTAL)][FPSTR(S_LOGIN)].isNull()) { + String value = src[FPSTR(S_PORTAL)][FPSTR(S_LOGIN)].as(); if (value.length() < sizeof(dst.portal.login) && !value.equals(dst.portal.login)) { strcpy(dst.portal.login, value.c_str()); @@ -571,8 +588,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["portal"]["password"].isNull()) { - String value = src["portal"]["password"].as(); + if (!src[FPSTR(S_PORTAL)][FPSTR(S_PASSWORD)].isNull()) { + String value = src[FPSTR(S_PORTAL)][FPSTR(S_PASSWORD)].as(); if (value.length() < sizeof(dst.portal.password) && !value.equals(dst.portal.password)) { strcpy(dst.portal.password, value.c_str()); @@ -582,8 +599,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // opentherm - if (!src["opentherm"]["unitSystem"].isNull()) { - uint8_t value = src["opentherm"]["unitSystem"].as(); + if (!src[FPSTR(S_OPENTHERM)][FPSTR(S_UNIT_SYSTEM)].isNull()) { + uint8_t value = src[FPSTR(S_OPENTHERM)][FPSTR(S_UNIT_SYSTEM)].as(); switch (value) { case static_cast(UnitSystem::METRIC): @@ -605,15 +622,15 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["opentherm"]["inGpio"].isNull()) { - if (src["opentherm"]["inGpio"].is() && src["opentherm"]["inGpio"].as().size() == 0) { + if (!src[FPSTR(S_OPENTHERM)][FPSTR(S_IN_GPIO)].isNull()) { + if (src[FPSTR(S_OPENTHERM)][FPSTR(S_IN_GPIO)].is() && src[FPSTR(S_OPENTHERM)][FPSTR(S_IN_GPIO)].as().size() == 0) { if (dst.opentherm.inGpio != GPIO_IS_NOT_CONFIGURED) { dst.opentherm.inGpio = GPIO_IS_NOT_CONFIGURED; changed = true; } } else { - unsigned char value = src["opentherm"]["inGpio"].as(); + unsigned char value = src[FPSTR(S_OPENTHERM)][FPSTR(S_IN_GPIO)].as(); if (GPIO_IS_VALID(value) && value != dst.opentherm.inGpio) { dst.opentherm.inGpio = value; @@ -622,15 +639,15 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["opentherm"]["outGpio"].isNull()) { - if (src["opentherm"]["outGpio"].is() && src["opentherm"]["outGpio"].as().size() == 0) { + if (!src[FPSTR(S_OPENTHERM)][FPSTR(S_OUT_GPIO)].isNull()) { + if (src[FPSTR(S_OPENTHERM)][FPSTR(S_OUT_GPIO)].is() && src[FPSTR(S_OPENTHERM)][FPSTR(S_OUT_GPIO)].as().size() == 0) { if (dst.opentherm.outGpio != GPIO_IS_NOT_CONFIGURED) { dst.opentherm.outGpio = GPIO_IS_NOT_CONFIGURED; changed = true; } } else { - unsigned char value = src["opentherm"]["outGpio"].as(); + unsigned char value = src[FPSTR(S_OPENTHERM)][FPSTR(S_OUT_GPIO)].as(); if (GPIO_IS_VALID(value) && value != dst.opentherm.outGpio) { dst.opentherm.outGpio = value; @@ -639,15 +656,15 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["opentherm"]["rxLedGpio"].isNull()) { - if (src["opentherm"]["rxLedGpio"].is() && src["opentherm"]["rxLedGpio"].as().size() == 0) { + if (!src[FPSTR(S_OPENTHERM)][FPSTR(S_RX_LED_GPIO)].isNull()) { + if (src[FPSTR(S_OPENTHERM)][FPSTR(S_RX_LED_GPIO)].is() && src[FPSTR(S_OPENTHERM)][FPSTR(S_RX_LED_GPIO)].as().size() == 0) { if (dst.opentherm.rxLedGpio != GPIO_IS_NOT_CONFIGURED) { dst.opentherm.rxLedGpio = GPIO_IS_NOT_CONFIGURED; changed = true; } } else { - unsigned char value = src["opentherm"]["rxLedGpio"].as(); + unsigned char value = src[FPSTR(S_OPENTHERM)][FPSTR(S_RX_LED_GPIO)].as(); if (GPIO_IS_VALID(value) && value != dst.opentherm.rxLedGpio) { dst.opentherm.rxLedGpio = value; @@ -656,8 +673,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["opentherm"]["memberId"].isNull()) { - auto value = src["opentherm"]["memberId"].as(); + if (!src[FPSTR(S_OPENTHERM)][FPSTR(S_MEMBER_ID)].isNull()) { + auto value = src[FPSTR(S_OPENTHERM)][FPSTR(S_MEMBER_ID)].as(); if (value != dst.opentherm.memberId) { dst.opentherm.memberId = value; @@ -665,8 +682,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["opentherm"]["flags"].isNull()) { - auto value = src["opentherm"]["flags"].as(); + if (!src[FPSTR(S_OPENTHERM)][FPSTR(S_FLAGS)].isNull()) { + auto value = src[FPSTR(S_OPENTHERM)][FPSTR(S_FLAGS)].as(); if (value != dst.opentherm.flags) { dst.opentherm.flags = value; @@ -674,8 +691,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["opentherm"]["maxModulation"].isNull()) { - unsigned char value = src["opentherm"]["maxModulation"].as(); + if (!src[FPSTR(S_OPENTHERM)][FPSTR(S_MAX_MODULATION)].isNull()) { + unsigned char value = src[FPSTR(S_OPENTHERM)][FPSTR(S_MAX_MODULATION)].as(); if (value > 0 && value <= 100 && value != dst.opentherm.maxModulation) { dst.opentherm.maxModulation = value; @@ -683,8 +700,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["opentherm"]["minPower"].isNull()) { - float value = src["opentherm"]["minPower"].as(); + if (!src[FPSTR(S_OPENTHERM)][FPSTR(S_MIN_POWER)].isNull()) { + float value = src[FPSTR(S_OPENTHERM)][FPSTR(S_MIN_POWER)].as(); if (value >= 0 && value <= 1000 && fabsf(value - dst.opentherm.minPower) > 0.0001f) { dst.opentherm.minPower = roundf(value, 2); @@ -692,8 +709,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["opentherm"]["maxPower"].isNull()) { - float value = src["opentherm"]["maxPower"].as(); + if (!src[FPSTR(S_OPENTHERM)][FPSTR(S_MAX_POWER)].isNull()) { + float value = src[FPSTR(S_OPENTHERM)][FPSTR(S_MAX_POWER)].as(); if (value >= 0 && value <= 1000 && fabsf(value - dst.opentherm.maxPower) > 0.0001f) { dst.opentherm.maxPower = roundf(value, 2); @@ -701,8 +718,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["opentherm"]["dhwPresent"].is()) { - bool value = src["opentherm"]["dhwPresent"].as(); + if (src[FPSTR(S_OPENTHERM)][FPSTR(S_DHW_PRESENT)].is()) { + bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_DHW_PRESENT)].as(); if (value != dst.opentherm.dhwPresent) { dst.opentherm.dhwPresent = value; @@ -710,8 +727,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["opentherm"]["summerWinterMode"].is()) { - bool value = src["opentherm"]["summerWinterMode"].as(); + if (src[FPSTR(S_OPENTHERM)][FPSTR(S_SUMMER_WINTER_MODE)].is()) { + bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_SUMMER_WINTER_MODE)].as(); if (value != dst.opentherm.summerWinterMode) { dst.opentherm.summerWinterMode = value; @@ -719,8 +736,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["opentherm"]["heatingCh2Enabled"].is()) { - bool value = src["opentherm"]["heatingCh2Enabled"].as(); + if (src[FPSTR(S_OPENTHERM)][FPSTR(S_HEATING_CH2_ENABLED)].is()) { + bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_HEATING_CH2_ENABLED)].as(); if (value != dst.opentherm.heatingCh2Enabled) { dst.opentherm.heatingCh2Enabled = value; @@ -734,8 +751,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["opentherm"]["heatingCh1ToCh2"].is()) { - bool value = src["opentherm"]["heatingCh1ToCh2"].as(); + if (src[FPSTR(S_OPENTHERM)][FPSTR(S_HEATING_CH1_TO_CH2)].is()) { + bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_HEATING_CH1_TO_CH2)].as(); if (value != dst.opentherm.heatingCh1ToCh2) { dst.opentherm.heatingCh1ToCh2 = value; @@ -749,8 +766,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["opentherm"]["dhwToCh2"].is()) { - bool value = src["opentherm"]["dhwToCh2"].as(); + if (src[FPSTR(S_OPENTHERM)][FPSTR(S_DHW_TO_CH2)].is()) { + bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_DHW_TO_CH2)].as(); if (value != dst.opentherm.dhwToCh2) { dst.opentherm.dhwToCh2 = value; @@ -764,8 +781,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["opentherm"]["dhwBlocking"].is()) { - bool value = src["opentherm"]["dhwBlocking"].as(); + if (src[FPSTR(S_OPENTHERM)][FPSTR(S_DHW_BLOCKING)].is()) { + bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_DHW_BLOCKING)].as(); if (value != dst.opentherm.dhwBlocking) { dst.opentherm.dhwBlocking = value; @@ -773,8 +790,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["opentherm"]["modulationSyncWithHeating"].is()) { - bool value = src["opentherm"]["modulationSyncWithHeating"].as(); + if (src[FPSTR(S_OPENTHERM)][FPSTR(S_MODULATION_SYNC_WITH_HEATING)].is()) { + bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_MODULATION_SYNC_WITH_HEATING)].as(); if (value != dst.opentherm.modulationSyncWithHeating) { dst.opentherm.modulationSyncWithHeating = value; @@ -782,8 +799,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["opentherm"]["getMinMaxTemp"].is()) { - bool value = src["opentherm"]["getMinMaxTemp"].as(); + if (src[FPSTR(S_OPENTHERM)][FPSTR(S_GET_MIN_MAX_TEMP)].is()) { + bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_GET_MIN_MAX_TEMP)].as(); if (value != dst.opentherm.getMinMaxTemp) { dst.opentherm.getMinMaxTemp = value; @@ -791,8 +808,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["opentherm"]["nativeHeatingControl"].is()) { - bool value = src["opentherm"]["nativeHeatingControl"].as(); + if (src[FPSTR(S_OPENTHERM)][FPSTR(S_NATIVE_HEATING_CONTROL)].is()) { + bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_NATIVE_HEATING_CONTROL)].as(); if (value != dst.opentherm.nativeHeatingControl) { dst.opentherm.nativeHeatingControl = value; @@ -806,8 +823,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["opentherm"]["immergasFix"].is()) { - bool value = src["opentherm"]["immergasFix"].as(); + if (src[FPSTR(S_OPENTHERM)][FPSTR(S_IMMERGAS_FIX)].is()) { + bool value = src[FPSTR(S_OPENTHERM)][FPSTR(S_IMMERGAS_FIX)].as(); if (value != dst.opentherm.immergasFix) { dst.opentherm.immergasFix = value; @@ -817,8 +834,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // mqtt - if (src["mqtt"]["enable"].is()) { - bool value = src["mqtt"]["enable"].as(); + if (src[FPSTR(S_MQTT)][FPSTR(S_ENABLED)].is()) { + bool value = src[FPSTR(S_MQTT)][FPSTR(S_ENABLED)].as(); if (value != dst.mqtt.enabled) { dst.mqtt.enabled = value; @@ -826,8 +843,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["mqtt"]["server"].isNull()) { - String value = src["mqtt"]["server"].as(); + if (!src[FPSTR(S_MQTT)][FPSTR(S_SERVER)].isNull()) { + String value = src[FPSTR(S_MQTT)][FPSTR(S_SERVER)].as(); if (value.length() < sizeof(dst.mqtt.server) && !value.equals(dst.mqtt.server)) { strcpy(dst.mqtt.server, value.c_str()); @@ -835,8 +852,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["mqtt"]["port"].isNull()) { - unsigned short value = src["mqtt"]["port"].as(); + if (!src[FPSTR(S_MQTT)][FPSTR(S_PORT)].isNull()) { + unsigned short value = src[FPSTR(S_MQTT)][FPSTR(S_PORT)].as(); if (value > 0 && value <= 65535 && value != dst.mqtt.port) { dst.mqtt.port = value; @@ -844,8 +861,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["mqtt"]["user"].isNull()) { - String value = src["mqtt"]["user"].as(); + if (!src[FPSTR(S_MQTT)][FPSTR(S_USER)].isNull()) { + String value = src[FPSTR(S_MQTT)][FPSTR(S_USER)].as(); if (value.length() < sizeof(dst.mqtt.user) && !value.equals(dst.mqtt.user)) { strcpy(dst.mqtt.user, value.c_str()); @@ -853,8 +870,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["mqtt"]["password"].isNull()) { - String value = src["mqtt"]["password"].as(); + if (!src[FPSTR(S_MQTT)][FPSTR(S_PASSWORD)].isNull()) { + String value = src[FPSTR(S_MQTT)][FPSTR(S_PASSWORD)].as(); if (value.length() < sizeof(dst.mqtt.password) && !value.equals(dst.mqtt.password)) { strcpy(dst.mqtt.password, value.c_str()); @@ -862,8 +879,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["mqtt"]["prefix"].isNull()) { - String value = src["mqtt"]["prefix"].as(); + if (!src[FPSTR(S_MQTT)][FPSTR(S_PREFIX)].isNull()) { + String value = src[FPSTR(S_MQTT)][FPSTR(S_PREFIX)].as(); if (value.length() < sizeof(dst.mqtt.prefix) && !value.equals(dst.mqtt.prefix)) { strcpy(dst.mqtt.prefix, value.c_str()); @@ -871,8 +888,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["mqtt"]["interval"].isNull()) { - unsigned short value = src["mqtt"]["interval"].as(); + if (!src[FPSTR(S_MQTT)][FPSTR(S_INTERVAL)].isNull()) { + unsigned short value = src[FPSTR(S_MQTT)][FPSTR(S_INTERVAL)].as(); if (value >= 3 && value <= 60 && value != dst.mqtt.interval) { dst.mqtt.interval = value; @@ -880,8 +897,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["mqtt"]["homeAssistantDiscovery"].is()) { - bool value = src["mqtt"]["homeAssistantDiscovery"].as(); + if (src[FPSTR(S_MQTT)][FPSTR(S_HOME_ASSISTANT_DISCOVERY)].is()) { + bool value = src[FPSTR(S_MQTT)][FPSTR(S_HOME_ASSISTANT_DISCOVERY)].as(); if (value != dst.mqtt.homeAssistantDiscovery) { dst.mqtt.homeAssistantDiscovery = value; @@ -891,8 +908,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // emergency - if (!src["emergency"]["tresholdTime"].isNull()) { - unsigned short value = src["emergency"]["tresholdTime"].as(); + if (!src[FPSTR(S_EMERGENCY)][FPSTR(S_TRESHOLD_TIME)].isNull()) { + unsigned short value = src[FPSTR(S_EMERGENCY)][FPSTR(S_TRESHOLD_TIME)].as(); if (value >= 60 && value <= 1800 && value != dst.emergency.tresholdTime) { dst.emergency.tresholdTime = value; @@ -903,8 +920,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // equitherm - if (src["equitherm"]["enable"].is()) { - bool value = src["equitherm"]["enable"].as(); + if (src[FPSTR(S_EQUITHERM)][FPSTR(S_ENABLED)].is()) { + bool value = src[FPSTR(S_EQUITHERM)][FPSTR(S_ENABLED)].as(); if (!dst.opentherm.nativeHeatingControl) { if (value != dst.equitherm.enabled) { @@ -918,8 +935,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["equitherm"]["n_factor"].isNull()) { - float value = src["equitherm"]["n_factor"].as(); + if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_N_FACTOR)].isNull()) { + float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_N_FACTOR)].as(); if (value > 0 && value <= 10 && fabsf(value - dst.equitherm.n_factor) > 0.0001f) { dst.equitherm.n_factor = roundf(value, 3); @@ -927,8 +944,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["equitherm"]["k_factor"].isNull()) { - float value = src["equitherm"]["k_factor"].as(); + if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_K_FACTOR)].isNull()) { + float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_K_FACTOR)].as(); if (value >= 0 && value <= 10 && fabsf(value - dst.equitherm.k_factor) > 0.0001f) { dst.equitherm.k_factor = roundf(value, 3); @@ -936,8 +953,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["equitherm"]["t_factor"].isNull()) { - float value = src["equitherm"]["t_factor"].as(); + if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_T_FACTOR)].isNull()) { + float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_T_FACTOR)].as(); if (value >= 0 && value <= 10 && fabsf(value - dst.equitherm.t_factor) > 0.0001f) { dst.equitherm.t_factor = roundf(value, 3); @@ -947,8 +964,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // pid - if (src["pid"]["enable"].is()) { - bool value = src["pid"]["enable"].as(); + if (src[FPSTR(S_PID)][FPSTR(S_ENABLED)].is()) { + bool value = src[FPSTR(S_PID)][FPSTR(S_ENABLED)].as(); if (!dst.opentherm.nativeHeatingControl) { if (value != dst.pid.enabled) { @@ -962,8 +979,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["pid"]["p_factor"].isNull()) { - float value = src["pid"]["p_factor"].as(); + if (!src[FPSTR(S_PID)][FPSTR(S_P_FACTOR)].isNull()) { + float value = src[FPSTR(S_PID)][FPSTR(S_P_FACTOR)].as(); if (value > 0 && value <= 1000 && fabsf(value - dst.pid.p_factor) > 0.0001f) { dst.pid.p_factor = roundf(value, 3); @@ -971,8 +988,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["pid"]["i_factor"].isNull()) { - float value = src["pid"]["i_factor"].as(); + if (!src[FPSTR(S_PID)][FPSTR(S_I_FACTOR)].isNull()) { + float value = src[FPSTR(S_PID)][FPSTR(S_I_FACTOR)].as(); if (value >= 0 && value <= 100 && fabsf(value - dst.pid.i_factor) > 0.0001f) { dst.pid.i_factor = roundf(value, 4); @@ -980,8 +997,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["pid"]["d_factor"].isNull()) { - float value = src["pid"]["d_factor"].as(); + if (!src[FPSTR(S_PID)][FPSTR(S_D_FACTOR)].isNull()) { + float value = src[FPSTR(S_PID)][FPSTR(S_D_FACTOR)].as(); if (value >= 0 && value <= 100000 && fabsf(value - dst.pid.d_factor) > 0.0001f) { dst.pid.d_factor = roundf(value, 1); @@ -989,8 +1006,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["pid"]["dt"].isNull()) { - unsigned short value = src["pid"]["dt"].as(); + if (!src[FPSTR(S_PID)][FPSTR(S_DT)].isNull()) { + unsigned short value = src[FPSTR(S_PID)][FPSTR(S_DT)].as(); if (value >= 30 && value <= 1800 && value != dst.pid.dt) { dst.pid.dt = value; @@ -998,8 +1015,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["pid"]["minTemp"].isNull()) { - short value = src["pid"]["minTemp"].as(); + if (!src[FPSTR(S_PID)][FPSTR(S_MIN_TEMP)].isNull()) { + short value = src[FPSTR(S_PID)][FPSTR(S_MIN_TEMP)].as(); if (isValidTemp(value, dst.system.unitSystem, dst.equitherm.enabled ? -99.9f : 0.0f) && value != dst.pid.minTemp) { dst.pid.minTemp = value; @@ -1007,8 +1024,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["pid"]["maxTemp"].isNull()) { - short value = src["pid"]["maxTemp"].as(); + if (!src[FPSTR(S_PID)][FPSTR(S_MAX_TEMP)].isNull()) { + short value = src[FPSTR(S_PID)][FPSTR(S_MAX_TEMP)].as(); if (isValidTemp(value, dst.system.unitSystem) && value != dst.pid.maxTemp) { dst.pid.maxTemp = value; @@ -1023,8 +1040,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // heating - if (src["heating"]["enable"].is()) { - bool value = src["heating"]["enable"].as(); + if (src[FPSTR(S_HEATING)][FPSTR(S_ENABLED)].is()) { + bool value = src[FPSTR(S_HEATING)][FPSTR(S_ENABLED)].as(); if (value != dst.heating.enabled) { dst.heating.enabled = value; @@ -1032,8 +1049,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["heating"]["turbo"].is()) { - bool value = src["heating"]["turbo"].as(); + if (src[FPSTR(S_HEATING)][FPSTR(S_TURBO)].is()) { + bool value = src[FPSTR(S_HEATING)][FPSTR(S_TURBO)].as(); if (value != dst.heating.turbo) { dst.heating.turbo = value; @@ -1041,8 +1058,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["heating"]["hysteresis"].isNull()) { - float value = src["heating"]["hysteresis"].as(); + if (!src[FPSTR(S_HEATING)][FPSTR(S_HYSTERESIS)].isNull()) { + float value = src[FPSTR(S_HEATING)][FPSTR(S_HYSTERESIS)].as(); if (value >= 0.0f && value <= 15.0f && fabsf(value - dst.heating.hysteresis) > 0.0001f) { dst.heating.hysteresis = roundf(value, 2); @@ -1050,8 +1067,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["heating"]["turboFactor"].isNull()) { - float value = src["heating"]["turboFactor"].as(); + if (!src[FPSTR(S_HEATING)][FPSTR(S_TURBO_FACTOR)].isNull()) { + float value = src[FPSTR(S_HEATING)][FPSTR(S_TURBO_FACTOR)].as(); if (value >= 1.5f && value <= 10.0f && fabsf(value - dst.heating.turboFactor) > 0.0001f) { dst.heating.turboFactor = roundf(value, 3); @@ -1059,8 +1076,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["heating"]["minTemp"].isNull()) { - unsigned char value = src["heating"]["minTemp"].as(); + 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) { dst.heating.minTemp = value; @@ -1068,8 +1085,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["heating"]["maxTemp"].isNull()) { - unsigned char value = src["heating"]["maxTemp"].as(); + 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) { dst.heating.maxTemp = value; @@ -1084,8 +1101,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // dhw - if (src["dhw"]["enable"].is()) { - bool value = src["dhw"]["enable"].as(); + if (src[FPSTR(S_DHW)][FPSTR(S_ENABLED)].is()) { + bool value = src[FPSTR(S_DHW)][FPSTR(S_ENABLED)].as(); if (value != dst.dhw.enabled) { dst.dhw.enabled = value; @@ -1093,8 +1110,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["dhw"]["minTemp"].isNull()) { - unsigned char value = src["dhw"]["minTemp"].as(); + if (!src[FPSTR(S_DHW)][FPSTR(S_MIN_TEMP)].isNull()) { + unsigned char value = src[FPSTR(S_DHW)][FPSTR(S_MIN_TEMP)].as(); if (value >= vars.slave.dhw.minTemp && value < vars.slave.dhw.maxTemp && value != dst.dhw.minTemp) { dst.dhw.minTemp = value; @@ -1102,8 +1119,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["dhw"]["maxTemp"].isNull()) { - unsigned char value = src["dhw"]["maxTemp"].as(); + if (!src[FPSTR(S_DHW)][FPSTR(S_MAX_TEMP)].isNull()) { + unsigned char value = src[FPSTR(S_DHW)][FPSTR(S_MAX_TEMP)].as(); if (value > vars.slave.dhw.minTemp && value <= vars.slave.dhw.maxTemp && value != dst.dhw.maxTemp) { dst.dhw.maxTemp = value; @@ -1119,8 +1136,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!safe) { // external pump - if (src["externalPump"]["use"].is()) { - bool value = src["externalPump"]["use"].as(); + if (src[FPSTR(S_EXTERNAL_PUMP)][FPSTR(S_USE)].is()) { + bool value = src[FPSTR(S_EXTERNAL_PUMP)][FPSTR(S_USE)].as(); if (value != dst.externalPump.use) { dst.externalPump.use = value; @@ -1128,15 +1145,15 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["externalPump"]["gpio"].isNull()) { - if (src["externalPump"]["gpio"].is() && src["externalPump"]["gpio"].as().size() == 0) { + if (!src[FPSTR(S_EXTERNAL_PUMP)][FPSTR(S_GPIO)].isNull()) { + if (src[FPSTR(S_EXTERNAL_PUMP)][FPSTR(S_GPIO)].is() && src[FPSTR(S_EXTERNAL_PUMP)][FPSTR(S_GPIO)].as().size() == 0) { if (dst.externalPump.gpio != GPIO_IS_NOT_CONFIGURED) { dst.externalPump.gpio = GPIO_IS_NOT_CONFIGURED; changed = true; } } else { - unsigned char value = src["externalPump"]["gpio"].as(); + unsigned char value = src[FPSTR(S_EXTERNAL_PUMP)][FPSTR(S_GPIO)].as(); if (GPIO_IS_VALID(value) && value != dst.externalPump.gpio) { dst.externalPump.gpio = value; @@ -1145,8 +1162,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["externalPump"]["postCirculationTime"].isNull()) { - unsigned short value = src["externalPump"]["postCirculationTime"].as(); + 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(); if (value >= 0 && value <= 120) { value = value * 60; @@ -1158,8 +1175,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["externalPump"]["antiStuckInterval"].isNull()) { - unsigned int value = src["externalPump"]["antiStuckInterval"].as(); + if (!src[FPSTR(S_EXTERNAL_PUMP)][FPSTR(S_ANTI_STUCK_INTERVAL)].isNull()) { + unsigned int value = src[FPSTR(S_EXTERNAL_PUMP)][FPSTR(S_ANTI_STUCK_INTERVAL)].as(); if (value >= 0 && value <= 366) { value = value * 86400; @@ -1171,8 +1188,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["externalPump"]["antiStuckTime"].isNull()) { - unsigned short value = src["externalPump"]["antiStuckTime"].as(); + if (!src[FPSTR(S_EXTERNAL_PUMP)][FPSTR(S_ANTI_STUCK_TIME)].isNull()) { + unsigned short value = src[FPSTR(S_EXTERNAL_PUMP)][FPSTR(S_ANTI_STUCK_TIME)].as(); if (value >= 0 && value <= 20) { value = value * 60; @@ -1186,8 +1203,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // cascade control - if (src["cascadeControl"]["input"]["enable"].is()) { - bool value = src["cascadeControl"]["input"]["enable"].as(); + if (src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_INPUT)][FPSTR(S_ENABLED)].is()) { + bool value = src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_INPUT)][FPSTR(S_ENABLED)].as(); if (value != dst.cascadeControl.input.enabled) { dst.cascadeControl.input.enabled = value; @@ -1195,15 +1212,15 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["cascadeControl"]["input"]["gpio"].isNull()) { - if (src["cascadeControl"]["input"]["gpio"].is() && src["cascadeControl"]["input"]["gpio"].as().size() == 0) { + if (!src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_INPUT)][FPSTR(S_GPIO)].isNull()) { + if (src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_INPUT)][FPSTR(S_GPIO)].is() && src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_INPUT)][FPSTR(S_GPIO)].as().size() == 0) { if (dst.cascadeControl.input.gpio != GPIO_IS_NOT_CONFIGURED) { dst.cascadeControl.input.gpio = GPIO_IS_NOT_CONFIGURED; changed = true; } } else { - unsigned char value = src["cascadeControl"]["input"]["gpio"].as(); + unsigned char value = src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_INPUT)][FPSTR(S_GPIO)].as(); if (GPIO_IS_VALID(value) && value != dst.cascadeControl.input.gpio) { dst.cascadeControl.input.gpio = value; @@ -1212,8 +1229,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["cascadeControl"]["input"]["invertState"].is()) { - bool value = src["cascadeControl"]["input"]["invertState"].as(); + if (src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_INPUT)][FPSTR(S_INVERT_STATE)].is()) { + bool value = src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_INPUT)][FPSTR(S_INVERT_STATE)].as(); if (value != dst.cascadeControl.input.invertState) { dst.cascadeControl.input.invertState = value; @@ -1221,8 +1238,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["cascadeControl"]["input"]["thresholdTime"].isNull()) { - unsigned short value = src["cascadeControl"]["input"]["thresholdTime"].as(); + if (!src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_INPUT)][FPSTR(S_THRESHOLD_TIME)].isNull()) { + unsigned short value = src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_INPUT)][FPSTR(S_THRESHOLD_TIME)].as(); if (value >= 5 && value <= 600) { if (value != dst.cascadeControl.input.thresholdTime) { @@ -1232,8 +1249,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["cascadeControl"]["output"]["enable"].is()) { - bool value = src["cascadeControl"]["output"]["enable"].as(); + if (src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_OUTPUT)][FPSTR(S_ENABLED)].is()) { + bool value = src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_OUTPUT)][FPSTR(S_ENABLED)].as(); if (value != dst.cascadeControl.output.enabled) { dst.cascadeControl.output.enabled = value; @@ -1241,15 +1258,15 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["cascadeControl"]["output"]["gpio"].isNull()) { - if (src["cascadeControl"]["output"]["gpio"].is() && src["cascadeControl"]["output"]["gpio"].as().size() == 0) { + if (!src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_OUTPUT)][FPSTR(S_GPIO)].isNull()) { + if (src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_OUTPUT)][FPSTR(S_GPIO)].is() && src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_OUTPUT)][FPSTR(S_GPIO)].as().size() == 0) { if (dst.cascadeControl.output.gpio != GPIO_IS_NOT_CONFIGURED) { dst.cascadeControl.output.gpio = GPIO_IS_NOT_CONFIGURED; changed = true; } } else { - unsigned char value = src["cascadeControl"]["output"]["gpio"].as(); + unsigned char value = src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_OUTPUT)][FPSTR(S_GPIO)].as(); if (GPIO_IS_VALID(value) && value != dst.cascadeControl.output.gpio) { dst.cascadeControl.output.gpio = value; @@ -1258,8 +1275,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["cascadeControl"]["output"]["invertState"].is()) { - bool value = src["cascadeControl"]["output"]["invertState"].as(); + if (src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_OUTPUT)][FPSTR(S_INVERT_STATE)].is()) { + bool value = src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_OUTPUT)][FPSTR(S_INVERT_STATE)].as(); if (value != dst.cascadeControl.output.invertState) { dst.cascadeControl.output.invertState = value; @@ -1267,8 +1284,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["cascadeControl"]["output"]["thresholdTime"].isNull()) { - unsigned short value = src["cascadeControl"]["output"]["thresholdTime"].as(); + if (!src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_OUTPUT)][FPSTR(S_THRESHOLD_TIME)].isNull()) { + unsigned short value = src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_OUTPUT)][FPSTR(S_THRESHOLD_TIME)].as(); if (value >= 5 && value <= 600) { if (value != dst.cascadeControl.output.thresholdTime) { @@ -1278,8 +1295,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["cascadeControl"]["output"]["onFault"].is()) { - bool value = src["cascadeControl"]["output"]["onFault"].as(); + if (src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_OUTPUT)][FPSTR(S_ON_FAULT)].is()) { + bool value = src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_OUTPUT)][FPSTR(S_ON_FAULT)].as(); if (value != dst.cascadeControl.output.onFault) { dst.cascadeControl.output.onFault = value; @@ -1287,8 +1304,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["cascadeControl"]["output"]["onLossConnection"].is()) { - bool value = src["cascadeControl"]["output"]["onLossConnection"].as(); + if (src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_OUTPUT)][FPSTR(S_ON_LOSS_CONNECTION)].is()) { + bool value = src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_OUTPUT)][FPSTR(S_ON_LOSS_CONNECTION)].as(); if (value != dst.cascadeControl.output.onLossConnection) { dst.cascadeControl.output.onLossConnection = value; @@ -1296,8 +1313,8 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (src["cascadeControl"]["output"]["onEnabledHeating"].is()) { - bool value = src["cascadeControl"]["output"]["onEnabledHeating"].as(); + if (src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_OUTPUT)][FPSTR(S_ON_ENABLED_HEATING)].is()) { + bool value = src[FPSTR(S_CASCADE_CONTROL)][FPSTR(S_OUTPUT)][FPSTR(S_ON_ENABLED_HEATING)].as(); if (value != dst.cascadeControl.output.onEnabledHeating) { dst.cascadeControl.output.onEnabledHeating = value; @@ -1308,7 +1325,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // force check emergency target { - float value = !src["emergency"]["target"].isNull() ? src["emergency"]["target"].as() : dst.emergency.target; + float value = !src[FPSTR(S_EMERGENCY)][FPSTR(S_TARGET)].isNull() ? src[FPSTR(S_EMERGENCY)][FPSTR(S_TARGET)].as() : dst.emergency.target; bool noRegulators = !dst.opentherm.nativeHeatingControl; bool valid = isValidTemp( value, @@ -1334,7 +1351,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // force check heating target { - float value = !src["heating"]["target"].isNull() ? src["heating"]["target"].as() : dst.heating.target; + float value = !src[FPSTR(S_HEATING)][FPSTR(S_TARGET)].isNull() ? src[FPSTR(S_HEATING)][FPSTR(S_TARGET)].as() : dst.heating.target; bool valid = isValidTemp( value, dst.system.unitSystem, @@ -1361,7 +1378,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // force check dhw target { - float value = !src["dhw"]["target"].isNull() ? src["dhw"]["target"].as() : dst.dhw.target; + float value = !src[FPSTR(S_DHW)][FPSTR(S_TARGET)].isNull() ? src[FPSTR(S_DHW)][FPSTR(S_TARGET)].as() : dst.dhw.target; bool valid = isValidTemp( value, dst.system.unitSystem, @@ -1388,41 +1405,41 @@ inline bool safeJsonToSettings(const JsonVariantConst src, Settings& dst) { } void sensorSettingsToJson(const uint8_t sensorId, const Sensors::Settings& src, JsonVariant dst) { - dst["id"] = sensorId; - dst["enabled"] = src.enabled; - dst["name"] = src.name; - dst["purpose"] = static_cast(src.purpose); - dst["type"] = static_cast(src.type); - dst["gpio"] = src.gpio; + dst[FPSTR(S_ID)] = sensorId; + dst[FPSTR(S_ENABLED)] = src.enabled; + dst[FPSTR(S_NAME)] = src.name; + dst[FPSTR(S_PURPOSE)] = static_cast(src.purpose); + dst[FPSTR(S_TYPE)] = static_cast(src.type); + dst[FPSTR(S_GPIO)] = src.gpio; if (src.type == Sensors::Type::DALLAS_TEMP) { char addr[24]; - sprintf( + sprintf_P( addr, - "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + PSTR("%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx"), src.address[0], src.address[1], src.address[2], src.address[3], src.address[4], src.address[5], src.address[6], src.address[7] ); - dst["address"] = String(addr); + dst[FPSTR(S_ADDRESS)] = String(addr); } else if (src.type == Sensors::Type::BLUETOOTH) { char addr[18]; - sprintf( + sprintf_P( addr, - "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", + PSTR("%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx"), src.address[0], src.address[1], src.address[2], src.address[3], src.address[4], src.address[5] ); - dst["address"] = String(addr); + dst[FPSTR(S_ADDRESS)] = String(addr); } else { - dst["address"] = ""; + dst[FPSTR(S_ADDRESS)] = ""; } - dst["offset"] = roundf(src.offset, 3); - dst["factor"] = roundf(src.factor, 3); - dst["filtering"] = src.filtering; - dst["filteringFactor"] = roundf(src.filteringFactor, 3); + dst[FPSTR(S_OFFSET)] = roundf(src.offset, 3); + dst[FPSTR(S_FACTOR)] = roundf(src.factor, 3); + dst[FPSTR(S_FILTERING)] = src.filtering; + dst[FPSTR(S_FILTERING_FACTOR)] = roundf(src.filteringFactor, 3); } bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Sensors::Settings& dst) { @@ -1433,8 +1450,8 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se bool changed = false; // enabled - if (src["enabled"].is()) { - auto value = src["enabled"].as(); + if (src[FPSTR(S_ENABLED)].is()) { + auto value = src[FPSTR(S_ENABLED)].as(); if (value != dst.enabled) { dst.enabled = value; @@ -1443,8 +1460,8 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se } // name - if (!src["name"].isNull()) { - String value = Sensors::cleanName(src["name"].as()); + if (!src[FPSTR(S_NAME)].isNull()) { + String value = Sensors::cleanName(src[FPSTR(S_NAME)].as()); if (value.length() < sizeof(dst.name) && !value.equals(dst.name)) { strcpy(dst.name, value.c_str()); @@ -1453,8 +1470,8 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se } // purpose - if (!src["purpose"].isNull()) { - uint8_t value = src["purpose"].as(); + if (!src[FPSTR(S_PURPOSE)].isNull()) { + uint8_t value = src[FPSTR(S_PURPOSE)].as(); switch (value) { case static_cast(Sensors::Purpose::OUTDOOR_TEMP): @@ -1483,8 +1500,8 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se } // type - if (!src["type"].isNull()) { - uint8_t value = src["type"].as(); + if (!src[FPSTR(S_TYPE)].isNull()) { + uint8_t value = src[FPSTR(S_TYPE)].as(); switch (value) { case static_cast(Sensors::Type::OT_OUTDOOR_TEMP): @@ -1517,21 +1534,21 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se } // gpio - if (!src["gpio"].isNull()) { + 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.gpio != GPIO_IS_NOT_CONFIGURED) { dst.gpio = GPIO_IS_NOT_CONFIGURED; changed = true; } - } else if (src["gpio"].is() && src["gpio"].as().size() == 0) { + } else if (src[FPSTR(S_GPIO)].is() && src[FPSTR(S_GPIO)].as().size() == 0) { if (dst.gpio != GPIO_IS_NOT_CONFIGURED) { dst.gpio = GPIO_IS_NOT_CONFIGURED; changed = true; } } else { - unsigned char value = src["gpio"].as(); + unsigned char value = src[FPSTR(S_GPIO)].as(); if (GPIO_IS_VALID(value) && value != dst.gpio) { dst.gpio = value; @@ -1541,8 +1558,8 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se } // address - if (!src["address"].isNull()) { - String value = src["address"].as(); + if (!src[FPSTR(S_ADDRESS)].isNull()) { + String value = src[FPSTR(S_ADDRESS)].as(); if (dst.type == Sensors::Type::DALLAS_TEMP) { uint8_t tmp[8]; @@ -1583,8 +1600,8 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se } // offset - if (!src["offset"].isNull()) { - float value = src["offset"].as(); + if (!src[FPSTR(S_OFFSET)].isNull()) { + float value = src[FPSTR(S_OFFSET)].as(); if (value >= -20.0f && value <= 20.0f && fabsf(value - dst.offset) > 0.0001f) { dst.offset = roundf(value, 2); @@ -1593,8 +1610,8 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se } // factor - if (!src["factor"].isNull()) { - float value = src["factor"].as(); + if (!src[FPSTR(S_FACTOR)].isNull()) { + float value = src[FPSTR(S_FACTOR)].as(); if (value > 0.09f && value <= 10.0f && fabsf(value - dst.factor) > 0.0001f) { dst.factor = roundf(value, 3); @@ -1603,8 +1620,8 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se } // filtering - if (src["filtering"].is()) { - auto value = src["filtering"].as(); + if (src[FPSTR(S_FILTERING)].is()) { + auto value = src[FPSTR(S_FILTERING)].as(); if (value != dst.filtering) { dst.filtering = value; @@ -1613,8 +1630,8 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se } // filtering factor - if (!src["filteringFactor"].isNull()) { - float value = src["filteringFactor"].as(); + if (!src[FPSTR(S_FILTERING_FACTOR)].isNull()) { + float value = src[FPSTR(S_FILTERING_FACTOR)].as(); if (value > 0 && value <= 1 && fabsf(value - dst.filteringFactor) > 0.0001f) { dst.filteringFactor = roundf(value, 3); @@ -1633,18 +1650,18 @@ void sensorResultToJson(const uint8_t sensorId, JsonVariant dst) { auto& sSensor = Sensors::settings[sensorId]; auto& rSensor = Sensors::results[sensorId]; - //dst["id"] = sensorId; - dst["connected"] = rSensor.connected; - dst["signalQuality"] = rSensor.signalQuality; + //dst[FPSTR(S_ID)] = sensorId; + dst[FPSTR(S_CONNECTED)] = rSensor.connected; + dst[FPSTR(S_SIGNAL_QUALITY)] = rSensor.signalQuality; if (sSensor.type == Sensors::Type::BLUETOOTH) { - dst["temperature"] = rSensor.values[static_cast(Sensors::ValueType::TEMPERATURE)]; - dst["humidity"] = rSensor.values[static_cast(Sensors::ValueType::HUMIDITY)]; - dst["battery"] = rSensor.values[static_cast(Sensors::ValueType::BATTERY)]; - dst["rssi"] = rSensor.values[static_cast(Sensors::ValueType::RSSI)]; + dst[FPSTR(S_TEMPERATURE)] = rSensor.values[static_cast(Sensors::ValueType::TEMPERATURE)]; + dst[FPSTR(S_HUMIDITY)] = rSensor.values[static_cast(Sensors::ValueType::HUMIDITY)]; + dst[FPSTR(S_BATTERY)] = rSensor.values[static_cast(Sensors::ValueType::BATTERY)]; + dst[FPSTR(S_RSSI)] = rSensor.values[static_cast(Sensors::ValueType::RSSI)]; } else { - dst["value"] = rSensor.values[static_cast(Sensors::ValueType::PRIMARY)]; + dst[FPSTR(S_VALUE)] = rSensor.values[static_cast(Sensors::ValueType::PRIMARY)]; } } @@ -1662,8 +1679,8 @@ bool jsonToSensorResult(const uint8_t sensorId, const JsonVariantConst src) { bool changed = false; // value - if (!src["value"].isNull()) { - float value = src["value"].as(); + if (!src[FPSTR(S_VALUE)].isNull()) { + float value = src[FPSTR(S_VALUE)].as(); uint8_t vType = static_cast(Sensors::ValueType::PRIMARY); if (fabsf(value - dst.values[vType]) > 0.0001f) { @@ -1676,76 +1693,88 @@ bool jsonToSensorResult(const uint8_t sensorId, const JsonVariantConst src) { } void varsToJson(const Variables& src, JsonVariant dst) { - dst["slave"]["memberId"] = src.slave.memberId; - dst["slave"]["flags"] = src.slave.flags; - dst["slave"]["type"] = src.slave.type; - dst["slave"]["appVersion"] = src.slave.appVersion; - dst["slave"]["protocolVersion"] = src.slave.appVersion; - dst["slave"]["connected"] = src.slave.connected; - dst["slave"]["flame"] = src.slave.flame; - - dst["slave"]["modulation"]["min"] = src.slave.modulation.min; - dst["slave"]["modulation"]["max"] = src.slave.modulation.max; - - dst["slave"]["power"]["min"] = roundf(src.slave.power.min, 2); - dst["slave"]["power"]["max"] = roundf(src.slave.power.max, 2); - - dst["slave"]["heating"]["active"] = src.slave.heating.active; - dst["slave"]["heating"]["minTemp"] = src.slave.heating.minTemp; - dst["slave"]["heating"]["maxTemp"] = src.slave.heating.maxTemp; - - dst["slave"]["dhw"]["active"] = src.slave.dhw.active; - dst["slave"]["dhw"]["minTemp"] = src.slave.dhw.minTemp; - dst["slave"]["dhw"]["maxTemp"] = src.slave.dhw.maxTemp; - - dst["slave"]["fault"]["active"] = src.slave.fault.active; - dst["slave"]["fault"]["code"] = src.slave.fault.code; - - dst["slave"]["diag"]["active"] = src.slave.diag.active; - dst["slave"]["diag"]["code"] = src.slave.diag.code; - - dst["master"]["heating"]["enabled"] = src.master.heating.enabled; - dst["master"]["heating"]["blocking"] = src.master.heating.blocking; - dst["master"]["heating"]["indoorTempControl"] = src.master.heating.indoorTempControl; - dst["master"]["heating"]["targetTemp"] = roundf(src.master.heating.targetTemp, 2); - dst["master"]["heating"]["currentTemp"] = roundf(src.master.heating.currentTemp, 2); - dst["master"]["heating"]["returnTemp"] = roundf(src.master.heating.returnTemp, 2); - dst["master"]["heating"]["indoorTemp"] = roundf(src.master.heating.indoorTemp, 2); - dst["master"]["heating"]["outdoorTemp"] = roundf(src.master.heating.outdoorTemp, 2); - dst["master"]["heating"]["minTemp"] = roundf(src.master.heating.minTemp, 2); - dst["master"]["heating"]["maxTemp"] = roundf(src.master.heating.maxTemp, 2); - - dst["master"]["dhw"]["enabled"] = src.master.dhw.enabled; - dst["master"]["dhw"]["targetTemp"] = roundf(src.master.dhw.targetTemp, 2); - dst["master"]["dhw"]["currentTemp"] = roundf(src.master.dhw.currentTemp, 2); - dst["master"]["dhw"]["returnTemp"] = roundf(src.master.dhw.returnTemp, 2); - dst["master"]["dhw"]["minTemp"] = settings.dhw.minTemp; - dst["master"]["dhw"]["maxTemp"] = settings.dhw.maxTemp; - - dst["master"]["network"]["connected"] = src.network.connected; - dst["master"]["mqtt"]["connected"] = src.mqtt.connected; - dst["master"]["emergency"]["state"] = src.emergency.state; - dst["master"]["externalPump"]["state"] = src.externalPump.state; - - dst["master"]["cascadeControl"]["input"] = src.cascadeControl.input; - dst["master"]["cascadeControl"]["output"] = src.cascadeControl.output; - - dst["master"]["uptime"] = millis() / 1000ul; + auto slave = dst[FPSTR(S_SLAVE)].to(); + slave[FPSTR(S_MEMBER_ID)] = src.slave.memberId; + slave[FPSTR(S_FLAGS)] = src.slave.flags; + slave[FPSTR(S_TYPE)] = src.slave.type; + slave[FPSTR(S_APP_VERSION)] = src.slave.appVersion; + slave[FPSTR(S_PROTOCOL_VERSION)] = src.slave.appVersion; + slave[FPSTR(S_CONNECTED)] = src.slave.connected; + slave[FPSTR(S_FLAME)] = src.slave.flame; + + auto sModulation = slave[FPSTR(S_MODULATION)].to(); + sModulation[FPSTR(S_MIN)] = src.slave.modulation.min; + sModulation[FPSTR(S_MAX)] = src.slave.modulation.max; + + auto sPower = slave[FPSTR(S_POWER)].to(); + sPower[FPSTR(S_MIN)] = roundf(src.slave.power.min, 2); + sPower[FPSTR(S_MAX)] = roundf(src.slave.power.max, 2); + + auto sHeating = slave[FPSTR(S_HEATING)].to(); + sHeating[FPSTR(S_ACTIVE)] = src.slave.heating.active; + sHeating[FPSTR(S_MIN_TEMP)] = src.slave.heating.minTemp; + sHeating[FPSTR(S_MAX_TEMP)] = src.slave.heating.maxTemp; + + auto sDhw = slave[FPSTR(S_DHW)].to(); + sDhw[FPSTR(S_ACTIVE)] = src.slave.dhw.active; + sDhw[FPSTR(S_MIN_TEMP)] = src.slave.dhw.minTemp; + sDhw[FPSTR(S_MAX_TEMP)] = src.slave.dhw.maxTemp; + + auto sFault = slave[FPSTR(S_FAULT)].to(); + sFault[FPSTR(S_ACTIVE)] = src.slave.fault.active; + sFault[FPSTR(S_CODE)] = src.slave.fault.code; + + auto sDiag = slave[FPSTR(S_DIAG)].to(); + sDiag[FPSTR(S_ACTIVE)] = src.slave.diag.active; + sDiag[FPSTR(S_CODE)] = src.slave.diag.code; + + + auto master = dst[FPSTR(S_MASTER)].to(); + auto mHeating = master[FPSTR(S_HEATING)].to(); + mHeating[FPSTR(S_ENABLED)] = src.master.heating.enabled; + mHeating[FPSTR(S_BLOCKING)] = src.master.heating.blocking; + mHeating[FPSTR(S_INDOOR_TEMP_CONTROL)] = src.master.heating.indoorTempControl; + mHeating[FPSTR(S_TARGET_TEMP)] = roundf(src.master.heating.targetTemp, 2); + mHeating[FPSTR(S_CURRENT_TEMP)] = roundf(src.master.heating.currentTemp, 2); + mHeating[FPSTR(S_RETURN_TEMP)] = roundf(src.master.heating.returnTemp, 2); + mHeating[FPSTR(S_INDOOR_TEMP)] = roundf(src.master.heating.indoorTemp, 2); + mHeating[FPSTR(S_OUTDOOR_TEMP)] = roundf(src.master.heating.outdoorTemp, 2); + mHeating[FPSTR(S_MIN_TEMP)] = roundf(src.master.heating.minTemp, 2); + mHeating[FPSTR(S_MAX_TEMP)] = roundf(src.master.heating.maxTemp, 2); + + auto mDhw = master[FPSTR(S_DHW)].to(); + mDhw[FPSTR(S_ENABLED)] = src.master.dhw.enabled; + mDhw[FPSTR(S_TARGET_TEMP)] = roundf(src.master.dhw.targetTemp, 2); + mDhw[FPSTR(S_CURRENT_TEMP)] = roundf(src.master.dhw.currentTemp, 2); + mDhw[FPSTR(S_RETURN_TEMP)] = roundf(src.master.dhw.returnTemp, 2); + mDhw[FPSTR(S_MIN_TEMP)] = settings.dhw.minTemp; + mDhw[FPSTR(S_MAX_TEMP)] = settings.dhw.maxTemp; + + master[FPSTR(S_NETWORK)][FPSTR(S_CONNECTED)] = src.network.connected; + master[FPSTR(S_MQTT)][FPSTR(S_CONNECTED)] = src.mqtt.connected; + master[FPSTR(S_EMERGENCY)][FPSTR(S_STATE)] = src.emergency.state; + master[FPSTR(S_EXTERNAL_PUMP)][FPSTR(S_STATE)] = src.externalPump.state; + + auto mCascadeControl = master[FPSTR(S_CASCADE_CONTROL)].to(); + mCascadeControl[FPSTR(S_INPUT)] = src.cascadeControl.input; + mCascadeControl[FPSTR(S_OUTPUT)] = src.cascadeControl.output; + + master[FPSTR(S_UPTIME)] = millis() / 1000; } bool jsonToVars(const JsonVariantConst src, Variables& dst) { bool changed = false; // actions - if (src["actions"]["restart"].is() && src["actions"]["restart"].as()) { + if (src[FPSTR(S_ACTIONS)][FPSTR(S_RESTART)].is() && src[FPSTR(S_ACTIONS)][FPSTR(S_RESTART)].as()) { dst.actions.restart = true; } - if (src["actions"]["resetFault"].is() && src["actions"]["resetFault"].as()) { + if (src[FPSTR(S_ACTIONS)][FPSTR(S_RESET_FAULT)].is() && src[FPSTR(S_ACTIONS)][FPSTR(S_RESET_FAULT)].as()) { dst.actions.resetFault = true; } - if (src["actions"]["resetDiagnostic"].is() && src["actions"]["resetDiagnostic"].as()) { + if (src[FPSTR(S_ACTIONS)][FPSTR(S_RESET_DIAGNOSTIC)].is() && src[FPSTR(S_ACTIONS)][FPSTR(S_RESET_DIAGNOSTIC)].as()) { dst.actions.resetDiagnostic = true; } From ed5020854692656e573de1b07ca5d651334a8c1f Mon Sep 17 00:00:00 2001 From: Yurii Date: Mon, 11 Nov 2024 09:29:19 +0300 Subject: [PATCH 06/42] feat: added will msg for mqtt broker --- src/MqttTask.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/MqttTask.h b/src/MqttTask.h index f42e26c..b201ace 100644 --- a/src/MqttTask.h +++ b/src/MqttTask.h @@ -196,6 +196,11 @@ class MqttTask : public Task { this->client->stop(); this->client->setId(networkSettings.hostname); this->client->setUsernamePassword(settings.mqtt.user, settings.mqtt.password); + + this->client->beginWill(this->haHelper->getDeviceTopic(F("status")).c_str(), 7, true, 1); + this->client->print(F("offline")); + this->client->endWill(); + this->client->connect(settings.mqtt.server, settings.mqtt.port); this->lastReconnectTime = millis(); this->yield(); From 99d82657c01030b074713a8c974afa93afc4c3f6 Mon Sep 17 00:00:00 2001 From: Yurii Date: Mon, 11 Nov 2024 12:54:23 +0300 Subject: [PATCH 07/42] fix: restore settings on portal fixed --- src_data/scripts/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src_data/scripts/utils.js b/src_data/scripts/utils.js index 01217ed..5bdc88d 100644 --- a/src_data/scripts/utils.js +++ b/src_data/scripts/utils.js @@ -314,7 +314,7 @@ const setupRestoreBackupForm = (formSelector) => { headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data.network) + body: JSON.stringify({"network": data.network}) }); if (!response.ok) { @@ -330,7 +330,7 @@ const setupRestoreBackupForm = (formSelector) => { headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data.settings) + body: JSON.stringify({"settings": data.settings}) }); if (!response.ok) { From e04462811bda65b49ef8f9386b22e03445915481 Mon Sep 17 00:00:00 2001 From: Yurii Date: Mon, 11 Nov 2024 12:56:18 +0300 Subject: [PATCH 08/42] refactor: improved NTC polling --- src/SensorsTask.h | 4 ++-- src/defines.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/SensorsTask.h b/src/SensorsTask.h index 432adfa..cfc1452 100644 --- a/src/SensorsTask.h +++ b/src/SensorsTask.h @@ -382,13 +382,13 @@ class SensorsTask : public LeanTask { const auto value = analogRead(sSensor.gpio) / 1023 * DEFAULT_NTC_VREF; #endif - if (value < DEFAULT_NTC_VLOW_TRESHOLD) { + if (value < DEFAULT_NTC_VLOW_TRESHOLD || value > DEFAULT_NTC_VHIGH_TRESHOLD) { if (Sensors::getConnectionStatusById(sensorId)) { Sensors::setConnectionStatusById(sensorId, false, false); } Log.swarningln( - FPSTR(L_SENSORS_NTC), F("GPIO %hhu, sensor #%hhu '%s', voltage too low: %.2f"), + FPSTR(L_SENSORS_NTC), F("GPIO %hhu, sensor #%hhu '%s', voltage is out of threshold: %.3f"), sSensor.gpio, sensorId, sSensor.name, (value / 1000.0f) ); diff --git a/src/defines.h b/src/defines.h index 35dd000..67f7741 100644 --- a/src/defines.h +++ b/src/defines.h @@ -24,6 +24,7 @@ #define DEFAULT_NTC_BETA_FACTOR 3950.0f #define DEFAULT_NTC_VREF 3300.0f #define DEFAULT_NTC_VLOW_TRESHOLD 25.0f +#define DEFAULT_NTC_VHIGH_TRESHOLD 3298.0f #ifndef BUILD_VERSION #define BUILD_VERSION "0.0.0" From 2d74d0c0ad19db6fa005a8fde44be7f930da4d4f Mon Sep 17 00:00:00 2001 From: Yurii Date: Mon, 11 Nov 2024 12:59:35 +0300 Subject: [PATCH 09/42] refactor: gpio 17 (A0) is valid for ESP8266 --- src/defines.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/defines.h b/src/defines.h index 67f7741..8167589 100644 --- a/src/defines.h +++ b/src/defines.h @@ -153,7 +153,7 @@ #ifdef ARDUINO_ARCH_ESP32 #include #elif !defined(GPIO_IS_VALID_GPIO) - #define GPIO_IS_VALID_GPIO(gpioNum) (gpioNum >= 0 && gpioNum <= 16) + #define GPIO_IS_VALID_GPIO(gpioNum) (gpioNum >= 0 && gpioNum <= 17) #endif #define GPIO_IS_VALID(gpioNum) (gpioNum != GPIO_IS_NOT_CONFIGURED && GPIO_IS_VALID_GPIO(gpioNum)) From ddc9cf7c902b0f91bec333edadbe58acc291309b Mon Sep 17 00:00:00 2001 From: Yurii Date: Mon, 11 Nov 2024 13:32:36 +0300 Subject: [PATCH 10/42] fix: fix typos in settings page --- src_data/pages/settings.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src_data/pages/settings.html b/src_data/pages/settings.html index ce45530..e06eb7e 100644 --- a/src_data/pages/settings.html +++ b/src_data/pages/settings.html @@ -95,12 +95,12 @@

settings.name

settings.section.diag @@ -245,7 +245,7 @@

settings.name