From e71f3868fdabe76230215f3570472cb86593c342 Mon Sep 17 00:00:00 2001 From: Yurii Date: Sat, 9 Nov 2024 17:10:26 +0300 Subject: [PATCH] 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 | 1657 +++++++---------- src/MainTask.h | 137 +- src/MqttTask.h | 316 ++-- src/OpenThermTask.h | 1387 +++++++++----- src/PortalTask.h | 182 +- src/RegulatorTask.h | 117 +- src/Sensors.h | 424 +++++ src/SensorsTask.h | 919 +++++---- src/Settings.h | 288 ++- src/defines.h | 41 +- src/main.cpp | 94 +- src/strings.h | 11 +- src/utils.h | 944 +++++----- src_data/locales/en.json | 183 +- src_data/locales/ru.json | 193 +- src_data/pages/dashboard.html | 374 ++-- 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, 4666 insertions(+), 3388 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 @@ public: 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 @@ public: )); } - 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 @@ public: 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 @@ public: } 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 @@ public: #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[]; - bool publishSwitchHeating(bool enabledByDefault = true) { + 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; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); + + // 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_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_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 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; + } + + // 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_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(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 @@ public: 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[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; 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.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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc.shrinkToFit(); - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("heating_max_temp")).c_str(), doc); + return this->publish(this->makeConfigTopic(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.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("boiler_dhw_min_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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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_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.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("modulation")).c_str(), doc); - } - - bool publishSensorPressure(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("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_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.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("pressure")).c_str(), doc); - } - - bool publishSensorDhwFlowRate(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_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_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.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("dhw_flow_rate")).c_str(), doc); - } - - bool publishSensorPower(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("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_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.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("power")).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_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:chat-alert-outline"); + doc[FPSTR(HA_ICON)] = F("mdi:cog-box"); 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[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("fault_code")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SENSOR), F("fault_code")).c_str(), doc); } - bool publishSensorDiagnosticCode(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 and value_json.states.fault or value_json.states.diagnostic, '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("diagnostic_code")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("diagnostic_code")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("diagnostic"); + 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:chat-alert-outline"); + doc[FPSTR(HA_ICON)] = F("mdi:information-box"); 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[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("diagnostic_code")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SENSOR), F("diagnostic_code")).c_str(), doc); } - bool publishSensorRssi(bool enabledByDefault = true) { + bool publishNetworkRssi(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_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)] = F("measurement"); + 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.rssi|float(0)|round(1) }}"); - 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("rssi")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SENSOR), F("rssi")).c_str(), doc); } - bool publishSensorUptime(bool enabledByDefault = true) { + bool publishUptime(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_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.uptime|int(0) }}"); - 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("uptime")).c_str(), doc); + return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SENSOR), F("uptime")).c_str(), doc); } - bool publishOutdoorSensorConnected(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_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) { - 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ protected: 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 @@ protected: // 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: uint8_t emergencyFlags = 0b00000000; // set outdoor sensor flag - if (settings.equitherm.enable && !vars.sensors.outdoor.connected) { - emergencyFlags |= 0b00000001; + if (settings.equitherm.enabled) { + if (!Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP)) { + emergencyFlags |= 0b00000001; + } } - // set indoor sensor flag - if (!settings.equitherm.enable && settings.pid.enable && !vars.sensors.indoor.connected) { - emergencyFlags |= 0b00000010; - } - - // 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: } } - 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 @@ protected: // 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 @@ protected: 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 @@ protected: } } - 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 @@ protected: } 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 @@ public: 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 @@ protected: 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 @@ protected: } 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: 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,40 +567,11 @@ protected: _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; - - if (editableOutdoorTemp) { - this->haHelper->deleteSensorOutdoorTemp(); - this->haHelper->publishInputOutdoorTemp(settings.system.unitSystem); - } else { - this->haHelper->deleteInputOutdoorTemp(); - this->haHelper->publishSensorOutdoorTemp(settings.system.unitSystem); - } - - published = true; - } - - if (force || _editableIndoorTemp != editableIndoorTemp) { - _editableIndoorTemp = editableIndoorTemp; - - if (editableIndoorTemp) { - this->haHelper->deleteSensorIndoorTemp(); - this->haHelper->publishInputIndoorTemp(settings.system.unitSystem); - } else { - this->haHelper->deleteInputIndoorTemp(); - this->haHelper->publishSensorIndoorTemp(settings.system.unitSystem); - } - - published = true; - } - return published; } @@ -498,6 +583,25 @@ protected: return this->writer->publish(topic, doc, true); } + bool publishSensor(uint8_t sensorId) { + auto& sSettings = Sensors::settings[sensorId]; + + if (!Sensors::isValidSensorId(sensorId)) { + return false; + + } else if (!strlen(sSettings.name)) { + return false; + } + + JsonDocument doc; + sensorResultToJson(sensorId, doc); + doc.shrinkToFit(); + + 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) { JsonDocument doc; varsToJson(vars, doc); 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 @@ public: 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 @@ protected: #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 @@ protected: 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 @@ protected: } 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 @@ protected: } } - 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 @@ protected: } 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 @@ protected: ); } - 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 @@ protected: // Get DHW min/max temp (if necessary) if (settings.opentherm.dhwPresent && settings.opentherm.getMinMaxTemp) { if (this->updateMinMaxDhwTemp()) { - Log.snoticeln( - FPSTR(L_OT_DHW), - F("Received min temp: %hhu, max temp: %hhu"), - vars.parameters.dhwMinTemp, - vars.parameters.dhwMaxTemp + uint8_t convertedMinTemp = convertTemp( + vars.slave.dhw.minTemp, + settings.opentherm.unitSystem, + settings.system.unitSystem ); - if (settings.dhw.minTemp < vars.parameters.dhwMinTemp) { - settings.dhw.minTemp = vars.parameters.dhwMinTemp; + 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 (converted: %hhu), max temp: %hhu (converted: %hhu)"), + vars.slave.dhw.minTemp, convertedMinTemp, vars.slave.dhw.maxTemp, convertedMaxTemp + ); + + 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()) { - Log.snoticeln( - FPSTR(L_OT_HEATING), - F("Received min temp: %hhu, max temp: %hhu"), - vars.parameters.heatingMinTemp, - vars.parameters.heatingMaxTemp + uint8_t convertedMinTemp = convertTemp( + vars.slave.heating.minTemp, + settings.opentherm.unitSystem, + settings.system.unitSystem ); - if (settings.heating.minTemp < vars.parameters.heatingMinTemp) { - settings.heating.minTemp = vars.parameters.heatingMinTemp; + 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 (converted: %hhu), max temp: %hhu (converted: %hhu)"), + vars.slave.heating.minTemp, convertedMinTemp, vars.slave.heating.maxTemp, convertedMaxTemp + ); + + 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; - } - - // 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; - } - - Log.snoticeln(FPSTR(L_OT), F("Received outdoor temp: %.2f"), vars.temperatures.outdoor); - - } else { - if (vars.sensors.outdoor.connected) { - vars.sensors.outdoor.connected = false; - } - - 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); - - } else { - Log.swarningln(FPSTR(L_OT), F("Failed receive pressure")); - } + } else if (vars.slave.diag.code != 0) { + vars.slave.diag.code = 0; } this->prevUpdateNonEssentialVars = millis(); } - // 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); + // 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; + + if (vars.slave.flame) { + result = this->updateModulationLevel(); + + 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 { - vars.sensors.power = 0.0f; + Log.swarningln(FPSTR(L_OT), F("Failed receive modulation level")); } - + } + + // 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_DHW), F("Failed receive temp")); + } + } + + 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; + + 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 { + 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(); + + if (result) { + convertedHeatingReturnTemp = convertTemp( + vars.slave.heating.returnTemp, + settings.opentherm.unitSystem, + settings.system.unitSystem + ); + + 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 temp")); + Log.swarningln(FPSTR(L_OT_HEATING), F("Failed receive return temp")); } - if (this->updateDhwFlowRate()) { - Log.snoticeln(FPSTR(L_OT_DHW), F("Received flow rate: %.2f"), vars.sensors.dhwFlowRate); - - } else { - Log.swarningln(FPSTR(L_OT_DHW), F("Failed receive flow rate")); - } - - } 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_HEATING), F("Received return temp: %.2f"), vars.temperatures.heatingReturn); - - } else { - if (settings.sensors.indoor.type == SensorType::BOILER_RETURN && vars.sensors.outdoor.connected) { - vars.sensors.indoor.connected = false; - } - - Log.swarningln(FPSTR(L_OT_HEATING), F("Failed receive return temp")); - } - - // Get exhaust temp - if (this->updateExhaustTemp()) { - Log.snoticeln(FPSTR(L_OT), F("Received exhaust temp: %.2f"), vars.temperatures.exhaust); - - } else { - Log.swarningln(FPSTR(L_OT), F("Failed receive exhaust temp")); - } - - // 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; - } - - Log.snoticeln(FPSTR(L_OT), F("Received outdoor temp: %.2f"), vars.temperatures.outdoor); + Log.snoticeln( + FPSTR(L_OT_CH2), F("Received temp: %.2f (converted: %.2f)"), + vars.slave.ch2.currentTemp, convertedCh2Temp + ); } else { - if (vars.sensors.outdoor.connected) { - vars.sensors.outdoor.connected = false; - } - - Log.swarningln(FPSTR(L_OT), F("Failed receive outdoor temp")); + Log.swarningln(FPSTR(L_OT_CH2), F("Failed receive temp")); } } - - // Get pressure - if (this->updatePressure()) { - Log.snoticeln(FPSTR(L_OT), F("Received pressure: %.2f"), vars.sensors.pressure); + + Sensors::setValueByType( + Sensors::Type::OT_CH2_TEMP, convertedCh2Temp, + Sensors::ValueType::PRIMARY, true, true + ); + } + + // 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")); + } + + Sensors::setValueByType( + Sensors::Type::OT_EXHAUST_TEMP, convertedExhaustTemp, + Sensors::ValueType::PRIMARY, true, true + ); + } + + // Update heat exchanger temp + if (Sensors::getAmountByType(Sensors::Type::OT_HEAT_EXCHANGER_TEMP)) { + float convertedHeatExchTemp = 0.0f; + bool result = this->updateHeatExchangerTemp(); + + 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 + ); + } + + // Update outdoor temp + if (Sensors::getAmountByType(Sensors::Type::OT_OUTDOOR_TEMP)) { + bool result = this->updateOutdoorTemp(); + float convertedOutdoorTemp = 0.0f; + + if (result) { + convertedOutdoorTemp = convertTemp( + vars.slave.heating.outdoorTemp, + settings.opentherm.unitSystem, + settings.system.unitSystem + ); + + Log.snoticeln( + FPSTR(L_OT), F("Received outdoor temp: %.2f (converted: %.2f)"), + vars.slave.heating.outdoorTemp, convertedOutdoorTemp + ); + + } else { + Log.swarningln(FPSTR(L_OT), F("Failed receive outdoor temp")); + } + + 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 @@ protected: // 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 @@ protected: // 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) { + // Converted current indoor temp + float convertedTemp = convertTemp(vars.master.heating.indoorTemp, settings.system.unitSystem, settings.opentherm.unitSystem); + // Set current indoor temp - float indoorTemp = 0.0f; - float convertedTemp = 0.0f; - - if (vars.sensors.indoor.connected) { - indoorTemp = vars.temperatures.indoor; - convertedTemp = convertTemp(indoorTemp, settings.system.unitSystem, settings.opentherm.unitSystem); - } - - Log.sinfoln(FPSTR(L_OT_HEATING), F("Set current indoor temp: %.2f (converted: %.2f)"), indoorTemp, convertedTemp); - if (!this->instance->setRoomTemp(convertedTemp)) { + 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")); } - // 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); + // 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 + ); - if (this->instance->setRoomSetpoint(convertedTemp)) { - currentHeatingTemp = vars.parameters.heatingSetpoint; + } else { + Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set current CH2 indoor temp")); + } + } + + + // 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")); - } - } } - } 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); + // 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")); + } + } + } + + // 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(); + Log.sinfoln( + FPSTR(L_OT_HEATING), F("Set target 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 CH1 temp")); - } - - // 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")); - } + 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 { + Log.swarningln(FPSTR(L_OT_CH2), F("Failed set temp")); + } } - - } else if (this->heatingBlocking) { - this->heatingBlocking = false; } } 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 @@ protected: 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() { - return millis() - this->heatingSetTempTime > this->heatingSetTempInterval; + bool needSetHeatingTemp(const float target) { + return millis() - this->heatingSetTempTime > this->heatingSetTempInterval + || fabsf(target - vars.slave.heating.targetTemp) > 0.001f; + } + + 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 @@ protected: 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 @@ protected: * @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; + 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; - 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; - - 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: } 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 @@ protected: 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 @@ protected: } 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: }); 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 @@ protected: } } - JsonDocument networkSettingsDoc; - networkSettingsToJson(networkSettings, networkSettingsDoc); - networkSettingsDoc.shrinkToFit(); - - JsonDocument settingsDoc; - settingsToJson(settings, settingsDoc); - settingsDoc.shrinkToFit(); - JsonDocument doc; - doc["network"] = networkSettingsDoc; - doc["settings"] = settingsDoc; + + 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); + } + doc.shrinkToFit(); this->webServer->sendHeader(F("Content-Disposition"), F("attachment; filename=\"backup.json\"")); @@ -240,13 +254,13 @@ protected: } 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 @@ protected: 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 @@ protected: }); + // 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 @@ public: 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 @@ protected: #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 @@ protected: 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 @@ protected: 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 @@ protected: etResult = c2f(etResult); } - if (fabs(prevEtResult - etResult) > 0.09f) { + if (fabsf(prevEtResult - etResult) > 0.09f) { prevEtResult = etResult; newTemp += etResult; @@ -130,18 +176,18 @@ protected: } // 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 @@ protected: } float pidResult = pidRegulator.getResultTimer(); - if (fabs(prevPidResult - pidResult) > 0.09f) { + if (fabsf(prevPidResult - pidResult) > 0.09f) { prevPidResult = pidResult; newTemp += pidResult; @@ -167,19 +213,14 @@ protected: } // 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; + const unsigned int disconnectedTimeout = 120000; + const unsigned short dallasSearchInterval = 60000; + const unsigned short dallasPollingInterval = 10000; + const unsigned short globalPollingInterval = 15000; - 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; + 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 @@ protected: #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); - } - #endif - - if (settings.sensors.outdoor.type == SensorType::DS18B20 && GPIO_IS_VALID(settings.sensors.outdoor.gpio)) { - outdoorDallasSensor(); - } - #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; - - } else if (millis() - this->outdoorConnectedTime > 60000) { - vars.sensors.outdoor.connected = false; - } - } - #endif - - if (settings.sensors.indoor.type == SensorType::DS18B20 && GPIO_IS_VALID(settings.sensors.indoor.gpio)) { - indoorDallasSensor(); - } - #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 - ); - - if (connected) { - this->indoorConnectedTime = millis(); - vars.sensors.indoor.connected = true; - - } else if (millis() - this->indoorConnectedTime > 60000) { - vars.sensors.indoor.connected = false; - } - } - #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; - - } else if (settings.system.unitSystem == UnitSystem::IMPERIAL) { - newTemp += c2f(this->filteredOutdoorTemp); - } - - 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); - } - - this->prevFilteredOutdoorTemp = this->filteredOutdoorTemp; + if (isPollingDallasSensors()) { + pollingDallasSensors(false); } - if (fabs(this->prevFilteredIndoorTemp - this->filteredIndoorTemp) > 0.1f) { - float newTemp = settings.sensors.indoor.offset; - if (settings.system.unitSystem == UnitSystem::METRIC) { - newTemp += this->filteredIndoorTemp; + if (millis() - this->globalLastPollingTime > this->globalPollingInterval) { + makeDallasInstances(); + cleanDallasInstances(); + searchDallasSensors(); + fillingAddressesDallasSensors(); + pollingDallasSensors(); + pollingNtcSensors(); + pollingBleSensors(); - } else if (settings.system.unitSystem == UnitSystem::IMPERIAL) { - newTemp += c2f(this->filteredIndoorTemp); + this->globalLastPollingTime = millis(); + } + + 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); + + 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; } - 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); - } + auto& owInstance = this->owInstances[sSensor.gpio]; + owInstance.begin(sSensor.gpio); + owInstance.reset(); - this->prevFilteredIndoorTemp = this->filteredIndoorTemp; + 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); } } -#if USE_BLE - bool bluetoothSensor(const BLEAddress& address, int8_t* const pRssi, float* const pTemperature, float* const pHumidity = nullptr, float* const pBattery = nullptr) { + 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 (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; + } + } + } + + void searchDallasSensors() { + // search sensors on bus + for (auto& [gpio, instance] : this->dallasInstances) { + // do not search if polling! + if (this->dallasPolling[gpio]) { + continue; + } + + if (millis() - this->dallasSearchTime[gpio] > this->dallasSearchInterval) { + this->dallasSearchTime[gpio] = millis(); + instance.begin(); + + Log.straceln( + FPSTR(L_SENSORS_DALLAS), + F("GPIO %hhu, devices on bus: %hhu, DS18* devices: %hhu"), + gpio, instance.getDeviceCount(), instance.getDS18Count() + ); + } + } + } + + 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; + } + + // do nothing if address not empty + if (!isEmptyAddress(sSensor.address)) { + continue; + } + + // 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; + } + } + } + + bool isPollingDallasSensors() { + for (auto& [gpio, instance] : this->dallasInstances) { + if (this->dallasPolling.count(gpio) && this->dallasPolling[gpio]) { + return true; + } + } + + 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]; + + // 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); + } + } + } + + 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; + } + + connectToBleDevice(sensorId); + } + #endif + } + + 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 @@ protected: } 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: 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 @@ protected: // 0x2A6F - Notify about humidity x0.01% (pvvx) - if (pHumidity != nullptr) { + { bool humidityNotifyCreated = false; if (!humidityNotifyCreated) { NimBLEUUID charUuid((uint16_t) 0x2A6F); @@ -389,13 +635,11 @@ protected: 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 @@ protected: 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; + // set humidity + Sensors::setValueById(sensorId, rawHumidity, Sensors::ValueType::HUMIDITY, true, true); - } else { - *pHumidity += (rawHumidity - (*pHumidity)) * EXT_SENSORS_FILTER_K; - } - - *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 @@ protected: 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 @@ protected: // 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 @@ protected: 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 @@ protected: 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 @@ protected: 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; - } + void updateConnectionStatus() { + for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) { + auto& sSensor = Sensors::settings[sensorId]; + auto& rSensor = Sensors::results[sensorId]; - Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("Starting on GPIO %hhu..."), settings.sensors.outdoor.gpio); + if (rSensor.connected && !sSensor.enabled) { + rSensor.connected = false; - this->oneWireOutdoorSensor->begin(settings.sensors.outdoor.gpio); - this->oneWireOutdoorSensor->reset(); - this->outdoorSensor->begin(); - this->initOutdoorSensorTime = millis(); + } else if (rSensor.connected && sSensor.type == Sensors::Type::NOT_CONFIGURED) { + rSensor.connected = false; - Log.straceln( - FPSTR(L_SENSORS_OUTDOOR), - F("Devices on bus: %hhu, DS18* devices: %hhu"), - this->outdoorSensor->getDeviceCount(), - this->outdoorSensor->getDS18Count() - ); + } else if (rSensor.connected && sSensor.purpose == Sensors::Purpose::NOT_CONFIGURED) { + rSensor.connected = false; - if (this->outdoorSensor->getDeviceCount() > 0) { - this->initOutdoorSensor = true; - this->outdoorSensor->setResolution(12); - this->outdoorSensor->requestTemperatures(); - this->startOutdoorConversionTime = millis(); + } else if (sSensor.type != Sensors::Type::MANUAL && rSensor.connected && (millis() - rSensor.activityTime) > this->disconnectedTimeout) { + rSensor.connected = false; - 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; - - Log.serrorln(FPSTR(L_SENSORS_OUTDOOR), F("Could not read temperature data (not connected)")); - - } else { - Log.straceln(FPSTR(L_SENSORS_OUTDOOR), F("Raw temp: %f"), rawTemp); - - if (!vars.sensors.outdoor.connected) { - vars.sensors.outdoor.connected = true; - } - - if (fabs(this->filteredOutdoorTemp) < 0.1f) { - this->filteredOutdoorTemp = rawTemp; - - } else { - this->filteredOutdoorTemp += (rawTemp - this->filteredOutdoorTemp) * EXT_SENSORS_FILTER_K; - } - - 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); + static bool isEqualAddress(const uint8_t *addr1, const uint8_t *addr2, const uint8_t length = 8) { + bool result = true; - 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(); - - 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; - } + return result; + } - bool completed = this->indoorSensor->isConversionComplete(); - if (!completed && estimateConversionTime >= 1000) { - this->initIndoorSensor = false; + static bool isEmptyAddress(const uint8_t *addr, const uint8_t length = 8) { + bool result = true; - 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; + for (uint8_t i = 0; i < length; i++) { + if (addr[i] != 0) { + result = false; + break; } - - if (fabs(this->filteredIndoorTemp) < 0.1f) { - this->filteredIndoorTemp = rawTemp; - - } else { - this->filteredIndoorTemp += (rawTemp - this->filteredIndoorTemp) * EXT_SENSORS_FILTER_K; - } - - 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; +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 { - 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; - - 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; - - struct { - bool connected = false; - int8_t rssi = 0; - float battery = 0.0f; - float humidity = 0.0f; - } outdoor; - - struct { - bool connected = false; - int8_t rssi = 0; - float battery = 0.0f; - float humidity = 0.0f; - } indoor; - } sensors; + } network; 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 connected = false; + } mqtt; + + struct { + bool state = false; + } emergency; + + struct { + 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(); - - #elif defined(ARDUINO_ARCH_ESP8266) - static size_t minValue = 0; - size_t value = ESP.getFreeHeap(); - - if (value < minValue || minValue == 0) { - minValue = value; - } + return getMinValue ? ESP.getMinFreeHeap() : ESP.getFreeHeap(); - return getMinValue ? minValue : value; + #elif defined(ARDUINO_ARCH_ESP8266) + static size_t minValue = 0; + size_t value = ESP.getFreeHeap(); + + 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,11 +656,20 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["opentherm"]["memberIdCode"].isNull()) { - unsigned int value = src["opentherm"]["memberIdCode"].as(); + if (!src["opentherm"]["memberId"].isNull()) { + auto value = src["opentherm"]["memberId"].as(); - if (value >= 0 && value < 65536 && value != dst.opentherm.memberIdCode) { - dst.opentherm.memberIdCode = value; + if (value != dst.opentherm.memberId) { + dst.opentherm.memberId = value; + changed = true; + } + } + + if (!src["opentherm"]["flags"].isNull()) { + auto value = src["opentherm"]["flags"].as(); + + if (value != dst.opentherm.flags) { + dst.opentherm.flags = value; changed = true; } } @@ -704,29 +683,11 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } } - if (!src["opentherm"]["pressureFactor"].isNull()) { - float value = src["opentherm"]["pressureFactor"].as(); - - if (value > 0 && value <= 100 && fabs(value - dst.opentherm.pressureFactor) > 0.0001f) { - dst.opentherm.pressureFactor = roundd(value, 2); - changed = true; - } - } - - if (!src["opentherm"]["dhwFlowRateFactor"].isNull()) { - float value = src["opentherm"]["dhwFlowRateFactor"].as(); - - if (value > 0 && value <= 100 && fabs(value - dst.opentherm.dhwFlowRateFactor) > 0.0001f) { - dst.opentherm.dhwFlowRateFactor = roundd(value, 2); - changed = true; - } - } - 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 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 jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Sensors::Settings& dst) { + if (sensorId > Sensors::getMaxSensorId()) { + return false; + } + + bool changed = false; + + // 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 (GPIO_IS_VALID(value) && value != dst.gpio) { + dst.gpio = value; + changed = true; + } + } + } + + // 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 (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["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["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["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["slave"]["modulation"]["min"] = src.slave.modulation.min; + dst["slave"]["modulation"]["max"] = src.slave.modulation.max; - 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["slave"]["power"]["min"] = roundf(src.slave.power.min, 2); + dst["slave"]["power"]["max"] = roundf(src.slave.power.max, 2); - dst["cascadeControl"]["input"] = src.cascadeControl.input; - dst["cascadeControl"]["output"] = src.cascadeControl.output; + 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["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["slave"]["dhw"]["active"] = src.slave.dhw.active; + dst["slave"]["dhw"]["minTemp"] = src.slave.dhw.minTemp; + dst["slave"]["dhw"]["maxTemp"] = src.slave.dhw.maxTemp; - 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; + 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; - // temperatures - if (!src["temperatures"]["indoor"].isNull()) { - float value = src["temperatures"]["indoor"].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); - changed = true; - } - } - } - - if (!src["temperatures"]["outdoor"].isNull()) { - float value = src["temperatures"]["outdoor"].as(); - - 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; - } - } - } - // 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.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.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.flamedashboard.states.mCascadeControlOutput
dashboard.states.sConnected
dashboard.state.faultdashboard.states.sFlame
dashboard.states.sFaultActive
dashboard.state.diagdashboard.states.sFaultCode
dashboard.state.extpumpdashboard.states.sDiagActive
dashboard.state.outdoorSensorConnecteddashboard.states.sDiagCode
dashboard.states.mHeatEnabled
dashboard.state.outdoorSensorRssi dbmdashboard.states.mHeatBlocking
dashboard.state.outdoorSensorHumidity %dashboard.states.sHeatActive
dashboard.state.outdoorSensorBattery %dashboard.states.mHeatTargetTemp
dashboard.state.indoorSensorConnecteddashboard.states.mHeatCurrTemp
dashboard.state.cascadeControlInputdashboard.states.mHeatRetTemp
dashboard.state.cascadeControlOutputdashboard.states.mHeatIndoorTemp
dashboard.state.indoorSensorRssi dbmdashboard.states.mHeatOutdoorTemp
dashboard.states.mDhwEnabled
dashboard.state.indoorSensorHumidity %dashboard.states.sDhwActive
dashboard.state.indoorSensorBattery %dashboard.states.mDhwTargetTemp
dashboard.state.modulation %dashboard.states.mDhwCurrTemp
dashboard.state.pressure
dashboard.state.dhwFlowRate /min
dashboard.state.power kw
dashboard.state.faultCode
dashboard.state.diagCode
dashboard.state.indoorTemp
dashboard.state.outdoorTemp
dashboard.state.heatingTemp
dashboard.state.heatingSetpointTemp
dashboard.state.heatingReturnTemp
dashboard.state.dhwTemp
dashboard.state.exhaustTemp dashboard.states.mDhwRetTemp
@@ -213,15 +193,17 @@
- 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ // 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 @@ } 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 @@ 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 @@ } 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); - setValue( - '#ot-fault-code', - result.sensors.faultCode - ? (result.sensors.faultCode + " (0x" + dec2hex(result.sensors.faultCode) + ")") - : "-" + + // 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-diag-code', - result.sensors.diagnosticCode - ? (result.sensors.diagnosticCode + " (0x" + dec2hex(result.sensors.diagnosticCode) + ")") + '.sFaultCode', + result.slave.fault.active + ? (result.slave.fault.code + " (0x" + dec2hex(result.slave.fault.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); + setState('.sDiagActive', result.slave.diag.active); + setValue( + '.sDiagCode', + result.slave.diag.active + ? (result.slave.diag.code + " (0x" + dec2hex(result.slave.diag.code) + ")") + : "-" + ); - 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)); + // 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); - 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); 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 @@ -
+ 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.ot.advanced -
-
- - - -
-
- -
- settings.ot.fnv.desc - - - - -
-
-
@@ -556,120 +523,6 @@
-
- settings.section.outdorSensor -
-
- -
-
- -
- -
- settings.section.indoorSensor -
-
- -
-
- -
-
settings.section.extPump
@@ -722,7 +575,7 @@ @@ -750,7 +603,7 @@ @@ -815,13 +668,12 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 - }); + 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) { - onSuccess(response); - - } else { - onFailed(response); + if (!response.ok) { + onFailed(); + return; + } } + 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 = {