From 8687e122ca0784abd044cf61ebc5086b02925523 Mon Sep 17 00:00:00 2001 From: Yurii Date: Mon, 22 Apr 2024 08:18:59 +0300 Subject: [PATCH] feat: added native heating control by boiler; refactoring; emergency settings removed from HA --- README.md | 1 - lib/CustomOpenTherm/CustomOpenTherm.h | 20 + src/HaHelper.h | 173 +------ src/MqttTask.h | 125 ++--- src/OpenThermTask.h | 123 +++-- src/PortalTask.h | 9 + src/RegulatorTask.h | 189 ++------ src/SensorsTask.h | 10 +- src/Settings.h | 15 +- src/defines.h | 33 +- src/utils.h | 658 ++++++++++++++++---------- src_data/dashboard.html | 6 +- src_data/settings.html | 14 + 13 files changed, 663 insertions(+), 713 deletions(-) diff --git a/README.md b/README.md index 4225a2a..e398d47 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,6 @@ - The current temperature of the heat carrier (usually the return heat carrier) - Set heat carrier temperature (depending on the selected mode) - Current hot water temperature -- Auto tuning of PID and Equitherm parameters *(in development)* - [Home Assistant](https://www.home-assistant.io/) integration via MQTT. The ability to create any automation for the boiler! ![logo](/assets/ha.png) diff --git a/lib/CustomOpenTherm/CustomOpenTherm.h b/lib/CustomOpenTherm/CustomOpenTherm.h index b8b063f..e491326 100644 --- a/lib/CustomOpenTherm/CustomOpenTherm.h +++ b/lib/CustomOpenTherm/CustomOpenTherm.h @@ -126,6 +126,26 @@ public: return isValidResponse(response); } + bool setRoomSetpoint(float temperature) { + unsigned long response = this->sendRequest(buildRequest( + OpenThermMessageType::WRITE_DATA, + OpenThermMessageID::TrSet, + temperatureToData(temperature) + )); + + return isValidResponse(response); + } + + bool setRoomTemp(float temperature) { + unsigned long response = this->sendRequest(buildRequest( + OpenThermMessageType::WRITE_DATA, + OpenThermMessageID::Tr, + temperatureToData(temperature) + )); + + return isValidResponse(response); + } + bool sendBoilerReset() { unsigned int data = 1; data <<= 8; diff --git a/src/HaHelper.h b/src/HaHelper.h index a9928b4..3258f0e 100644 --- a/src/HaHelper.h +++ b/src/HaHelper.h @@ -6,107 +6,6 @@ public: static const byte TEMP_SOURCE_HEATING = 0; static const byte TEMP_SOURCE_INDOOR = 1; - bool publishSwitchEmergency(bool enabledByDefault = true) { - JsonDocument doc; - 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("config"); - doc[FPSTR(HA_NAME)] = F("Use emergency"); - 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.emergency.enable }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); - doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"emergency\": {\"enable\" : true}}"); - doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"emergency\": {\"enable\" : false}}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("emergency")).c_str(), doc); - } - - bool publishNumberEmergencyTarget(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("emergency_target")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("emergency_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); - doc[FPSTR(HA_MIN)] = 5; - doc[FPSTR(HA_MAX)] = 50; - - } else if (unit == UnitSystem::IMPERIAL) { - doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F); - doc[FPSTR(HA_MIN)] = 41; - doc[FPSTR(HA_MAX)] = 122; - } - - doc[FPSTR(HA_NAME)] = F("Emergency target temp"); - doc[FPSTR(HA_ICON)] = F("mdi:thermometer-alert"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.emergency.target|float(0)|round(1) }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); - doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"emergency\": {\"target\" : {{ value }}}}"); - doc[FPSTR(HA_STEP)] = 0.5; - doc[FPSTR(HA_MODE)] = "box"; - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("emergency_target")).c_str(), doc); - } - - bool publishSwitchEmergencyUseEquitherm(bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("settings")); - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.sensors.outdoor.type != 1, 'online', 'offline') }}"); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("emergency_use_equitherm")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("emergency_use_equitherm")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); - doc[FPSTR(HA_NAME)] = F("Use equitherm in emergency"); - doc[FPSTR(HA_ICON)] = F("mdi:snowflake-alert"); - 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.emergency.useEquitherm }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); - doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"emergency\": {\"useEquitherm\" : true}}"); - doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"emergency\": {\"useEquitherm\" : false}}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("emergency_use_equitherm")).c_str(), doc); - } - - bool publishSwitchEmergencyUsePid(bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("settings")); - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.sensors.indoor.type != 1, 'online', 'offline') }}"); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("emergency_use_pid")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("emergency_use_pid")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); - doc[FPSTR(HA_NAME)] = F("Use PID in emergency"); - doc[FPSTR(HA_ICON)] = F("mdi:snowflake-alert"); - 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.emergency.usePid }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); - doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"emergency\": {\"usePid\" : true}}"); - doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"emergency\": {\"usePid\" : false}}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("emergency_use_pid")).c_str(), doc); - } - - bool publishSwitchHeating(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); @@ -175,7 +74,7 @@ public: doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"target\" : {{ value }}}}"); doc[FPSTR(HA_MIN)] = minTemp; doc[FPSTR(HA_MAX)] = maxTemp; - doc[FPSTR(HA_STEP)] = 0.5; + doc[FPSTR(HA_STEP)] = 0.5f; doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc.shrinkToFit(); @@ -206,7 +105,7 @@ public: doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"hysteresis\" : {{ value }}}}"); doc[FPSTR(HA_MIN)] = 0; doc[FPSTR(HA_MAX)] = 5; - doc[FPSTR(HA_STEP)] = 0.1; + doc[FPSTR(HA_STEP)] = 0.1f; doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc.shrinkToFit(); @@ -234,7 +133,7 @@ public: 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|int(0) }}"); + doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.parameters.heatingSetpoint|float(0)|round(1) }}"); doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc.shrinkToFit(); @@ -433,7 +332,7 @@ public: 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|int(0) }}"); + 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; @@ -605,9 +504,9 @@ public: doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.p_factor|float(0)|round(3) }}"); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"p_factor\" : {{ value }}}}"); - doc[FPSTR(HA_MIN)] = 0.1; + doc[FPSTR(HA_MIN)] = 0.1f; doc[FPSTR(HA_MAX)] = 1000; - doc[FPSTR(HA_STEP)] = 0.1; + doc[FPSTR(HA_STEP)] = 0.1f; doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc.shrinkToFit(); @@ -628,7 +527,7 @@ public: doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"i_factor\" : {{ value }}}}"); doc[FPSTR(HA_MIN)] = 0; doc[FPSTR(HA_MAX)] = 100; - doc[FPSTR(HA_STEP)] = 0.001; + doc[FPSTR(HA_STEP)] = 0.001f; doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc.shrinkToFit(); @@ -779,9 +678,9 @@ public: doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.n_factor|float(0)|round(3) }}"); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"n_factor\" : {{ value }}}}"); - doc[FPSTR(HA_MIN)] = 0.001; + doc[FPSTR(HA_MIN)] = 0.001f; doc[FPSTR(HA_MAX)] = 10; - doc[FPSTR(HA_STEP)] = 0.001; + doc[FPSTR(HA_STEP)] = 0.001f; doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc.shrinkToFit(); @@ -802,7 +701,7 @@ public: doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"k_factor\" : {{ value }}}}"); doc[FPSTR(HA_MIN)] = 0; doc[FPSTR(HA_MAX)] = 10; - doc[FPSTR(HA_STEP)] = 0.01; + doc[FPSTR(HA_STEP)] = 0.01f; doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc.shrinkToFit(); @@ -825,7 +724,7 @@ public: doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"t_factor\" : {{ value }}}}"); doc[FPSTR(HA_MIN)] = 0; doc[FPSTR(HA_MAX)] = 10; - doc[FPSTR(HA_STEP)] = 0.01; + doc[FPSTR(HA_STEP)] = 0.01f; doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc.shrinkToFit(); @@ -834,48 +733,6 @@ public: } - bool publishSwitchTuning(bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("tuning")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("tuning")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); - doc[FPSTR(HA_NAME)] = F("Tuning"); - doc[FPSTR(HA_ICON)] = F("mdi:tune-vertical"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_STATE_ON)] = true; - doc[FPSTR(HA_STATE_OFF)] = false; - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.tuning.enable }}"); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set")); - doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"tuning\": {\"enable\" : true}}"); - doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"tuning\": {\"enable\" : false}}"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("tuning")).c_str(), doc); - } - - bool publishSelectTuningRegulator(bool enabledByDefault = true) { - JsonDocument doc; - doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); - doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set")); - doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"tuning\": {\"regulator\": {% if value == 'Equitherm' %}0{% elif value == 'PID' %}1{% endif %}}}"); - doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; - doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("tuning_regulator")); - doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("tuning_regulator")); - doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config"); - doc[FPSTR(HA_NAME)] = F("Tuning regulator"); - doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); - doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{% if value_json.tuning.regulator == 0 %}Equitherm{% elif value_json.tuning.regulator == 1 %}PID{% endif %}"); - doc[FPSTR(HA_OPTIONS)][0] = F("Equitherm"); - doc[FPSTR(HA_OPTIONS)][1] = F("PID"); - doc[FPSTR(HA_EXPIRE_AFTER)] = 120; - doc.shrinkToFit(); - - return this->publish(this->getTopic(FPSTR(HA_ENTITY_SELECT), F("tuning_regulator")).c_str(), doc); - } - - bool publishBinSensorStatus(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; @@ -1184,7 +1041,7 @@ public: 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.01; + doc[FPSTR(HA_STEP)] = 0.01f; doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc.shrinkToFit(); @@ -1243,7 +1100,7 @@ public: 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.01; + doc[FPSTR(HA_STEP)] = 0.01f; doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc.shrinkToFit(); @@ -1452,7 +1309,7 @@ public: doc[FPSTR(HA_MIN_TEMP)] = minTemp; doc[FPSTR(HA_MAX_TEMP)] = maxTemp; - doc[FPSTR(HA_TEMP_STEP)] = 0.5; + doc[FPSTR(HA_TEMP_STEP)] = 0.5f; doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc.shrinkToFit(); @@ -1475,7 +1332,7 @@ public: doc[FPSTR(HA_TEMPERATURE_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}"); doc[FPSTR(HA_TEMPERATURE_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); - doc[FPSTR(HA_TEMPERATURE_STATE_TEMPLATE)] = F("{{ value_json.dhw.target|int(0) }}"); + doc[FPSTR(HA_TEMPERATURE_STATE_TEMPLATE)] = F("{{ value_json.dhw.target|float(0)|round(1) }}"); if (unit == UnitSystem::METRIC) { doc[FPSTR(HA_TEMPERATURE_UNIT)] = "C"; diff --git a/src/MqttTask.h b/src/MqttTask.h index 4c639b6..1b650d1 100644 --- a/src/MqttTask.h +++ b/src/MqttTask.h @@ -53,16 +53,25 @@ public: Log.sinfoln(FPSTR(L_MQTT), F("Enabled")); } - bool isConnected() { + inline bool isConnected() { return this->connected; } + inline void resetPublishedSettingsTime() { + this->prevPubSettingsTime = 0; + } + + inline void resetPublishedVarsTime() { + this->prevPubVarsTime = 0; + } + protected: MqttWiFiClient* wifiClient = nullptr; MqttClient* client = nullptr; HaHelper* haHelper = nullptr; MqttWriter* writer = nullptr; UnitSystem currentUnitSystem = UnitSystem::METRIC; + bool currentHomeAssistantDiscovery = false; unsigned short readyForSendTime = 15000; unsigned long lastReconnectTime = 0; unsigned long connectedTime = 0; @@ -84,7 +93,7 @@ protected: return 2; } - bool isReadyForSend() { + inline bool isReadyForSend() { return millis() - this->connectedTime > this->readyForSendTime; } @@ -227,15 +236,24 @@ protected: } // publish ha entities if not published - if (this->newConnection || this->currentUnitSystem != settings.system.unitSystem) { - this->publishHaEntities(); - this->publishNonStaticHaEntities(true); - this->newConnection = false; - this->currentUnitSystem = settings.system.unitSystem; + if (settings.mqtt.homeAssistantDiscovery) { + if (this->newConnection || !this->currentHomeAssistantDiscovery || this->currentUnitSystem != settings.system.unitSystem) { + this->publishHaEntities(); + this->publishNonStaticHaEntities(true); + this->currentHomeAssistantDiscovery = true; + this->currentUnitSystem = settings.system.unitSystem; - } else { - // publish non static ha entities - this->publishNonStaticHaEntities(); + } else { + // publish non static ha entities + this->publishNonStaticHaEntities(); + } + + } else if (this->currentHomeAssistantDiscovery) { + this->currentHomeAssistantDiscovery = false; + } + + if (this->newConnection) { + this->newConnection = false; } } @@ -291,52 +309,26 @@ protected: Log.swarningln(FPSTR(L_MQTT_MSG), F("Not valid json")); return; } + doc.shrinkToFit(); if (this->haHelper->getDeviceTopic("state/set").equals(topic)) { this->writer->publish(this->haHelper->getDeviceTopic("state/set").c_str(), nullptr, 0, true); - this->updateVariables(doc); + + 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->updateSettings(doc); + + if (safeJsonToSettings(doc, settings)) { + this->resetPublishedSettingsTime(); + fsSettings.update(); + } } } - - bool updateSettings(JsonDocument& doc) { - bool changed = safeJsonToSettings(doc, settings); - doc.clear(); - doc.shrinkToFit(); - - if (changed) { - this->prevPubSettingsTime = 0; - fsSettings.update(); - return true; - } - - return false; - } - - bool updateVariables(JsonDocument& doc) { - bool changed = jsonToVars(doc, vars); - doc.clear(); - doc.shrinkToFit(); - - if (changed) { - this->prevPubVarsTime = 0; - return true; - } - - return false; - } - void publishHaEntities() { - // emergency - this->haHelper->publishSwitchEmergency(); - this->haHelper->publishNumberEmergencyTarget(settings.system.unitSystem); - this->haHelper->publishSwitchEmergencyUseEquitherm(); - this->haHelper->publishSwitchEmergencyUsePid(); - // heating this->haHelper->publishSwitchHeating(false); this->haHelper->publishSwitchHeatingTurbo(); @@ -363,10 +355,6 @@ protected: this->haHelper->publishNumberEquithermFactorK(); this->haHelper->publishNumberEquithermFactorT(); - // tuning - this->haHelper->publishSwitchTuning(); - this->haHelper->publishSelectTuningRegulator(); - // states this->haHelper->publishBinSensorStatus(); this->haHelper->publishBinSensorOtStatus(); @@ -396,26 +384,22 @@ protected: bool publishNonStaticHaEntities(bool force = false) { static byte _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0; - static bool _isStupidMode, _editableOutdoorTemp, _editableIndoorTemp, _dhwPresent = false; + static bool _noRegulators, _editableOutdoorTemp, _editableIndoorTemp, _dhwPresent = false; bool published = false; - bool isStupidMode = !settings.pid.enable && !settings.equitherm.enable; + 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 (isStupidMode) { + if (noRegulators) { heatingMinTemp = settings.heating.minTemp; heatingMaxTemp = settings.heating.maxTemp; - } else if (settings.system.unitSystem == UnitSystem::METRIC) { - heatingMinTemp = 5; - heatingMaxTemp = 30; - - } else if (settings.system.unitSystem == UnitSystem::IMPERIAL) { - heatingMinTemp = 41; - heatingMaxTemp = 86; + } 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) { @@ -447,32 +431,17 @@ protected: published = true; } - if (force || _heatingMinTemp != heatingMinTemp || _heatingMaxTemp != heatingMaxTemp) { - if (settings.heating.target < heatingMinTemp || settings.heating.target > heatingMaxTemp) { - settings.heating.target = constrain(settings.heating.target, heatingMinTemp, heatingMaxTemp); - } - + if (force || _noRegulators != noRegulators || _heatingMinTemp != heatingMinTemp || _heatingMaxTemp != heatingMaxTemp) { _heatingMinTemp = heatingMinTemp; _heatingMaxTemp = heatingMaxTemp; - _isStupidMode = isStupidMode; + _noRegulators = noRegulators; this->haHelper->publishNumberHeatingTarget(settings.system.unitSystem, heatingMinTemp, heatingMaxTemp, false); this->haHelper->publishClimateHeating( settings.system.unitSystem, heatingMinTemp, heatingMaxTemp, - isStupidMode ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR - ); - - published = true; - - } else if (_isStupidMode != isStupidMode) { - _isStupidMode = isStupidMode; - this->haHelper->publishClimateHeating( - settings.system.unitSystem, - heatingMinTemp, - heatingMaxTemp, - isStupidMode ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR + noRegulators ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR ); published = true; diff --git a/src/OpenThermTask.h b/src/OpenThermTask.h index 024abc5..cf3f400 100644 --- a/src/OpenThermTask.h +++ b/src/OpenThermTask.h @@ -101,7 +101,8 @@ protected: } void loop() { - static byte currentHeatingTemp, currentDhwTemp = 0; + static float currentHeatingTemp = 0.0f; + static float currentDhwTemp = 0.0f; if (this->instanceInGpio != settings.opentherm.inGpio || this->instanceOutGpio != settings.opentherm.outGpio) { this->setup(); @@ -351,18 +352,13 @@ protected: // Update DHW temp - byte newDhwTemp = settings.dhw.target; - if (settings.opentherm.dhwPresent && settings.dhw.enable && (this->needSetDhwTemp() || newDhwTemp != currentDhwTemp)) { - if (newDhwTemp < settings.dhw.minTemp || newDhwTemp > settings.dhw.maxTemp) { - newDhwTemp = constrain(newDhwTemp, settings.dhw.minTemp, settings.dhw.maxTemp); - } - - float convertedTemp = convertTemp(newDhwTemp, settings.system.unitSystem, settings.opentherm.unitSystem); - Log.sinfoln(FPSTR(L_OT_DHW), F("Set temp: %u (converted: %.2f)"), newDhwTemp, convertedTemp); + 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); // Set DHW temp if (this->instance->setDhwTemp(convertedTemp)) { - currentDhwTemp = newDhwTemp; + currentDhwTemp = settings.dhw.target; this->dhwSetTempTime = millis(); } else { @@ -372,57 +368,94 @@ protected: // 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")); + Log.swarningln(FPSTR(L_OT_DHW), F("Failed set CH2 temp")); } } } - // Update heating temp - if (heatingEnabled && (this->needSetHeatingTemp() || fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001)) { - float convertedTemp = convertTemp(vars.parameters.heatingSetpoint, settings.system.unitSystem, settings.opentherm.unitSystem); - Log.sinfoln(FPSTR(L_OT_HEATING), F("Set temp: %u (converted: %.2f)"), vars.parameters.heatingSetpoint, convertedTemp); + // Native heating control + if (settings.opentherm.nativeHeatingControl) { + // Set current indoor temp + float indoorTemp = 0.0f; + float convertedTemp = 0.0f; - // Set max heating temp - if (this->setMaxHeatingTemp(convertedTemp)) { - currentHeatingTemp = vars.parameters.heatingSetpoint; - this->heatingSetTempTime = millis(); - - } else { - Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set max heating temp")); + if (!vars.states.emergency || settings.sensors.indoor.type != SensorType::MANUAL) { + indoorTemp = vars.temperatures.indoor; + convertedTemp = convertTemp(indoorTemp, settings.system.unitSystem, settings.opentherm.unitSystem); } - // Set heating temp - if (this->instance->setHeatingCh1Temp(convertedTemp)) { - currentHeatingTemp = vars.parameters.heatingSetpoint; - this->heatingSetTempTime = millis(); - - } else { - Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set CH1 temp")); + Log.sinfoln(FPSTR(L_OT_HEATING), F("Set current indoor temp: %.2f (converted: %.2f)"), indoorTemp, convertedTemp); + if (!this->instance->setRoomTemp(convertedTemp)) { + Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set current indoor temp")); } - // Set heating temp to CH2 - if (settings.opentherm.heatingCh1ToCh2) { - if (!this->instance->setHeatingCh2Temp(convertedTemp)) { - Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set CH2 temp")); + // Set target indoor temp + if (this->needSetHeatingTemp() || fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001f) { + convertedTemp = convertTemp(vars.parameters.heatingSetpoint, settings.system.unitSystem, settings.opentherm.unitSystem); + Log.sinfoln(FPSTR(L_OT_HEATING), F("Set target indoor temp: %.2f (converted: %.2f)"), vars.parameters.heatingSetpoint, convertedTemp); + + if (this->instance->setRoomSetpoint(convertedTemp)) { + currentHeatingTemp = vars.parameters.heatingSetpoint; + this->heatingSetTempTime = millis(); + + } else { + Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set target indoor temp")); } } - } - - // Hysteresis - // Only if enabled PID or/and Equitherm - if (settings.heating.hysteresis > 0 && (!vars.states.emergency || settings.emergency.usePid) && (settings.equitherm.enable || settings.pid.enable)) { - float halfHyst = settings.heating.hysteresis / 2; - if (this->pump && vars.temperatures.indoor - settings.heating.target + 0.0001 >= halfHyst) { - this->pump = false; - - } else if (!this->pump && vars.temperatures.indoor - settings.heating.target - 0.0001 <= -(halfHyst)) { + // force enable pump + if (!this->pump) { this->pump = true; } - } else if (!this->pump) { - this->pump = true; + } 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 max heating temp + if (this->setMaxHeatingTemp(convertedTemp)) { + currentHeatingTemp = vars.parameters.heatingSetpoint; + this->heatingSetTempTime = millis(); + + } 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; + this->heatingSetTempTime = millis(); + + } 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")); + } + } + } + + + // Hysteresis + // Only if enabled PID or/and Equitherm + if (settings.heating.hysteresis > 0 && (!vars.states.emergency || settings.emergency.usePid) && (settings.equitherm.enable || settings.pid.enable)) { + float halfHyst = settings.heating.hysteresis / 2; + if (this->pump && vars.temperatures.indoor - settings.heating.target + 0.0001f >= halfHyst) { + this->pump = false; + + } else if (!this->pump && vars.temperatures.indoor - settings.heating.target - 0.0001f <= -(halfHyst)) { + this->pump = true; + } + + } else if (!this->pump) { + this->pump = true; + } } } diff --git a/src/PortalTask.h b/src/PortalTask.h index ed23039..1209022 100644 --- a/src/PortalTask.h +++ b/src/PortalTask.h @@ -427,7 +427,9 @@ protected: if (changed) { doc.clear(); doc.shrinkToFit(); + fsSettings.update(); + tMqtt->resetPublishedSettingsTime(); } }); @@ -477,6 +479,13 @@ protected: doc.shrinkToFit(); this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc); + + if (changed) { + doc.clear(); + doc.shrinkToFit(); + + tMqtt->resetPublishedVarsTime(); + } }); this->webServer->on("/api/info", HTTP_GET, [this]() { diff --git a/src/RegulatorTask.h b/src/RegulatorTask.h index 0309aae..9be6f75 100644 --- a/src/RegulatorTask.h +++ b/src/RegulatorTask.h @@ -1,10 +1,8 @@ #include #include -#include Equitherm etRegulator; GyverPID pidRegulator(0, 0, 0); -PIDtuner pidTuner; class RegulatorTask : public LeanTask { @@ -12,9 +10,6 @@ public: RegulatorTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {} protected: - bool tunerInit = false; - byte tunerState = 0; - byte tunerRegulator = 0; float prevHeatingTarget = 0; float prevEtResult = 0; float prevPidResult = 0; @@ -32,7 +27,7 @@ protected: } void loop() { - byte newTemp = vars.parameters.heatingSetpoint; + float newTemp = vars.parameters.heatingSetpoint; if (vars.states.emergency) { if (settings.heating.turbo) { @@ -41,74 +36,60 @@ protected: Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled")); } - newTemp = getEmergencyModeTemp(); + newTemp = this->getEmergencyModeTemp(); } else { - if (vars.tuning.enable || tunerInit) { - if (settings.heating.turbo) { - settings.heating.turbo = false; + if (settings.heating.turbo && (fabs(settings.heating.target - vars.temperatures.indoor) < 1 || !settings.heating.enable || (settings.equitherm.enable && settings.pid.enable))) { + settings.heating.turbo = false; - Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled")); - } - - newTemp = getTuningModeTemp(); - - if (newTemp == 0) { - vars.tuning.enable = false; - } + Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled")); } - if (!vars.tuning.enable) { - if (settings.heating.turbo && (fabs(settings.heating.target - vars.temperatures.indoor) < 1 || !settings.heating.enable || (settings.equitherm.enable && settings.pid.enable))) { - settings.heating.turbo = false; - - Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled")); - } - - newTemp = getNormalModeTemp(); - } + newTemp = this->getNormalModeTemp(); } // Limits - if (newTemp < settings.heating.minTemp || newTemp > settings.heating.maxTemp) { - newTemp = constrain(newTemp, settings.heating.minTemp, settings.heating.maxTemp); - } + newTemp = constrain( + newTemp, + !settings.opentherm.nativeHeatingControl ? settings.heating.minTemp : THERMOSTAT_INDOOR_MIN_TEMP, + !settings.opentherm.nativeHeatingControl ? settings.heating.maxTemp : THERMOSTAT_INDOOR_MAX_TEMP + ); - if (abs(vars.parameters.heatingSetpoint - newTemp) + 0.0001 >= 1) { + if (fabs(vars.parameters.heatingSetpoint - newTemp) > 0.4999f) { vars.parameters.heatingSetpoint = newTemp; } } - byte getEmergencyModeTemp() { + float getEmergencyModeTemp() { float newTemp = 0; // if use equitherm - if (settings.emergency.useEquitherm && settings.sensors.outdoor.type != SensorType::MANUAL) { + if (settings.emergency.useEquitherm) { float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp); - if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) { + if (fabs(prevEtResult - etResult) > 0.4999f) { prevEtResult = etResult; newTemp += etResult; - Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New emergency result: %hhu (%.2f)"), (uint8_t) round(etResult), etResult); + Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New emergency result: %.2f"), etResult); } else { newTemp += prevEtResult; } - } else if(settings.emergency.usePid && settings.sensors.indoor.type != SensorType::MANUAL) { + } else if(settings.emergency.usePid) { if (vars.parameters.heatingEnabled) { float pidResult = getPidTemp( settings.heating.minTemp, settings.heating.maxTemp ); - if (fabs(prevPidResult - pidResult) + 0.0001 >= 0.5) { + if (fabs(prevPidResult - pidResult) > 0.4999f) { prevPidResult = pidResult; newTemp += pidResult; - Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New emergency result: %hhu (%.2f)"), (uint8_t) round(pidResult), pidResult); + Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New emergency result: %.2f"), pidResult); } else { newTemp += prevPidResult; @@ -123,13 +104,13 @@ protected: newTemp = settings.emergency.target; } - return round(newTemp); + return newTemp; } - byte getNormalModeTemp() { + float getNormalModeTemp() { float newTemp = 0; - if (fabs(prevHeatingTarget - settings.heating.target) > 0.0001) { + if (fabs(prevHeatingTarget - settings.heating.target) > 0.0001f) { prevHeatingTarget = settings.heating.target; Log.sinfoln(FPSTR(L_REGULATOR), F("New target: %.2f"), settings.heating.target); @@ -143,11 +124,11 @@ protected: if (settings.equitherm.enable) { float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp); - if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) { + if (fabs(prevEtResult - etResult) > 0.4999f) { prevEtResult = etResult; newTemp += etResult; - Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New result: %hhu (%.2f)"), (uint8_t) round(etResult), etResult); + Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New result: %.2f"), etResult); } else { newTemp += prevEtResult; @@ -162,11 +143,11 @@ protected: settings.pid.maxTemp ); - if (fabs(prevPidResult - pidResult) + 0.0001 >= 0.5) { + if (fabs(prevPidResult - pidResult) > 0.4999f) { prevPidResult = pidResult; newTemp += pidResult; - Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New result: %hhd (%.2f)"), (int8_t) round(pidResult), pidResult); + Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New result: %.2f"), pidResult); Log.straceln(FPSTR(L_REGULATOR_PID), F("Integral: %.2f"), pidRegulator.integral); } else { @@ -176,7 +157,7 @@ protected: newTemp += prevPidResult; } - } else if (pidRegulator.integral != 0) { + } else if (fabs(pidRegulator.integral) > 0.0001f) { pidRegulator.integral = 0; Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset")); } @@ -186,97 +167,9 @@ protected: newTemp = settings.heating.target; } - newTemp = round(newTemp); return newTemp; } - byte getTuningModeTemp() { - if (tunerInit && (!vars.tuning.enable || vars.tuning.regulator != tunerRegulator)) { - if (tunerRegulator == 0) { - pidTuner.reset(); - } - - tunerInit = false; - tunerRegulator = 0; - tunerState = 0; - Log.sinfoln("REGULATOR.TUNING", F("Stopped")); - } - - if (!vars.tuning.enable) { - return 0; - } - - - if (vars.tuning.regulator == 0) { - // @TODO дописать - Log.sinfoln("REGULATOR.TUNING.EQUITHERM", F("Not implemented")); - return 0; - - } else if (vars.tuning.regulator == 1) { - // PID tuner - float defaultTemp = settings.equitherm.enable - ? getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp) - : settings.heating.target; - - if (tunerInit && pidTuner.getState() == 3) { - Log.sinfoln("REGULATOR.TUNING.PID", F("Finished")); - for (Stream* stream : Log.getStreams()) { - pidTuner.debugText(stream); - } - - pidTuner.reset(); - tunerInit = false; - tunerRegulator = 0; - tunerState = 0; - - if (pidTuner.getAccuracy() < 90) { - Log.swarningln("REGULATOR.TUNING.PID", F("Bad result, try again...")); - - } else { - settings.pid.p_factor = pidTuner.getPID_p(); - settings.pid.i_factor = pidTuner.getPID_i(); - settings.pid.d_factor = pidTuner.getPID_d(); - - return 0; - } - } - - if (!tunerInit) { - Log.sinfoln("REGULATOR.TUNING.PID", F("Start...")); - - float step; - if (vars.temperatures.indoor - vars.temperatures.outdoor > 10) { - step = ceil(vars.parameters.heatingSetpoint / vars.temperatures.indoor * 2); - } else { - step = 5.0f; - } - - float startTemp = step; - Log.sinfoln("REGULATOR.TUNING.PID", F("Started. Start value: %f, step: %f"), startTemp, step); - pidTuner.setParameters(NORMAL, startTemp, step, 20 * 60 * 1000, 0.15, 60 * 1000, 10000); - tunerInit = true; - tunerRegulator = 1; - } - - pidTuner.setInput(vars.temperatures.indoor); - pidTuner.compute(); - - if (tunerState > 0 && pidTuner.getState() != tunerState) { - Log.sinfoln("REGULATOR.TUNING.PID", F("Log:")); - for (Stream* stream : Log.getStreams()) { - pidTuner.debugText(stream); - } - - tunerState = pidTuner.getState(); - } - - return round(defaultTemp + pidTuner.getOutput()); - - } else { - return 0; - } - } - /** * @brief Get the Equitherm Temp * Calculations in degrees C, conversion occurs when using F @@ -357,32 +250,4 @@ protected: return pidRegulator.getResultTimer(); } - - float tuneEquithermN(float ratio, float currentTemp, float setTemp, unsigned int dirtyInterval = 60, unsigned int accurateInterval = 1800, float accurateStep = 0.01, float accurateStepAfter = 1) { - static uint32_t _prevIteration = millis(); - - if (fabs(currentTemp - setTemp) < accurateStepAfter) { - if (millis() - _prevIteration < (accurateInterval * 1000)) { - return ratio; - } - - if (currentTemp - setTemp > 0.1f) { - ratio -= accurateStep; - - } else if (currentTemp - setTemp < -0.1f) { - ratio += accurateStep; - } - - } else { - if (millis() - _prevIteration < (dirtyInterval * 1000)) { - return ratio; - } - - ratio = ratio * (setTemp / currentTemp); - } - - _prevIteration = millis(); - return ratio; - } - }; diff --git a/src/SensorsTask.h b/src/SensorsTask.h index 181e047..1ba999f 100644 --- a/src/SensorsTask.h +++ b/src/SensorsTask.h @@ -90,7 +90,7 @@ protected: newTemp += c2f(this->filteredOutdoorTemp); } - if (fabs(vars.temperatures.outdoor - newTemp) > 0.099) { + 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); } @@ -105,7 +105,7 @@ protected: newTemp += c2f(this->filteredIndoorTemp); } - if (fabs(vars.temperatures.indoor - newTemp) > 0.099) { + 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); } @@ -163,7 +163,7 @@ protected: return; } - float rawTemp = ((pData[0] | (pData[1] << 8)) * 0.01); + float rawTemp = ((pData[0] | (pData[1] << 8)) * 0.01f); Log.straceln(FPSTR(L_SENSORS_INDOOR), F("Raw temp: %f"), rawTemp); if (this->emptyIndoorTemp) { @@ -235,7 +235,7 @@ protected: return; } - Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("Starting on gpio %hhu..."), settings.sensors.outdoor.gpio); + Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("Starting on GPIO %hhu..."), settings.sensors.outdoor.gpio); this->oneWireOutdoorSensor->begin(settings.sensors.outdoor.gpio); this->oneWireOutdoorSensor->reset(); @@ -307,7 +307,7 @@ protected: return; } - Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("Starting on gpio %hhu..."), settings.sensors.indoor.gpio); + Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("Starting on GPIO %hhu..."), settings.sensors.indoor.gpio); this->oneWireIndoorSensor->begin(settings.sensors.indoor.gpio); this->oneWireIndoorSensor->reset(); diff --git a/src/Settings.h b/src/Settings.h index ff54095..e968845 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -60,6 +60,7 @@ struct Settings { bool dhwBlocking = false; bool modulationSyncWithHeating = false; bool getMinMaxTemp = true; + bool nativeHeatingControl = false; } opentherm; struct { @@ -70,11 +71,12 @@ struct Settings { char password[33] = DEFAULT_MQTT_PASSWORD; char prefix[33] = DEFAULT_MQTT_PREFIX; unsigned short interval = 5; + bool homeAssistantDiscovery = true; } mqtt; struct { bool enable = true; - float target = 40.0f; + float target = DEFAULT_HEATING_TARGET_TEMP; unsigned short tresholdTime = 120; bool useEquitherm = false; bool usePid = false; @@ -85,7 +87,7 @@ struct Settings { struct { bool enable = true; bool turbo = false; - float target = 40.0f; + float target = DEFAULT_HEATING_TARGET_TEMP; float hysteresis = 0.5f; byte minTemp = DEFAULT_HEATING_MIN_TEMP; byte maxTemp = DEFAULT_HEATING_MAX_TEMP; @@ -94,7 +96,7 @@ struct Settings { struct { bool enable = true; - byte target = 40; + float target = DEFAULT_DHW_TARGET_TEMP; byte minTemp = DEFAULT_DHW_MIN_TEMP; byte maxTemp = DEFAULT_DHW_MAX_TEMP; } dhw; @@ -143,11 +145,6 @@ struct Settings { } settings; struct Variables { - struct { - bool enable = false; - byte regulator = 0; - } tuning; - struct { bool otStatus = false; bool emergency = false; @@ -181,7 +178,7 @@ struct Variables { bool heatingEnabled = false; byte heatingMinTemp = DEFAULT_HEATING_MIN_TEMP; byte heatingMaxTemp = DEFAULT_HEATING_MAX_TEMP; - byte heatingSetpoint = 0; + float heatingSetpoint = 0; unsigned long extPumpLastEnableTime = 0; byte dhwMinTemp = DEFAULT_DHW_MIN_TEMP; byte dhwMaxTemp = DEFAULT_DHW_MAX_TEMP; diff --git a/src/defines.h b/src/defines.h index 292070e..ac40926 100644 --- a/src/defines.h +++ b/src/defines.h @@ -1,20 +1,27 @@ -#define PROJECT_NAME "OpenTherm Gateway" -#define PROJECT_VERSION "1.4.0-rc.23" -#define PROJECT_REPO "https://github.com/Laxilef/OTGateway" +#define PROJECT_NAME "OpenTherm Gateway" +#define PROJECT_VERSION "1.4.0-rc.23" +#define PROJECT_REPO "https://github.com/Laxilef/OTGateway" -#define MQTT_RECONNECT_INTERVAL 15000 +#define MQTT_RECONNECT_INTERVAL 15000 -#define EXT_SENSORS_INTERVAL 5000 -#define EXT_SENSORS_FILTER_K 0.15 +#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 CONFIG_URL "http://%s/" +#define SETTINGS_VALID_VALUE "stvalid" // only 8 chars! +#define GPIO_IS_NOT_CONFIGURED 0xff -#define GPIO_IS_NOT_CONFIGURED 0xff -#define DEFAULT_HEATING_MIN_TEMP 20 -#define DEFAULT_HEATING_MAX_TEMP 90 -#define DEFAULT_DHW_MIN_TEMP 30 -#define DEFAULT_DHW_MAX_TEMP 60 +#define DEFAULT_HEATING_TARGET_TEMP 40 +#define DEFAULT_HEATING_MIN_TEMP 20 +#define DEFAULT_HEATING_MAX_TEMP 90 + +#define DEFAULT_DHW_TARGET_TEMP 40 +#define DEFAULT_DHW_MIN_TEMP 30 +#define DEFAULT_DHW_MAX_TEMP 60 + +#define THERMOSTAT_INDOOR_DEFAULT_TEMP 20 +#define THERMOSTAT_INDOOR_MIN_TEMP 5 +#define THERMOSTAT_INDOOR_MAX_TEMP 30 #ifndef USE_SERIAL #define USE_SERIAL true diff --git a/src/utils.h b/src/utils.h index 7c5b4ee..637dea1 100644 --- a/src/utils.h +++ b/src/utils.h @@ -57,7 +57,7 @@ float convertTemp(float value, const UnitSystem unitFrom, const UnitSystem unitT return value; } -bool isValidTemp(const float value, UnitSystem unit, const float min = 0.1f, const float max = 99.9f, const UnitSystem minMaxUnit = UnitSystem::METRIC) { +inline bool isValidTemp(const float value, UnitSystem unit, const float min = 0.1f, const float max = 99.9f, const UnitSystem minMaxUnit = UnitSystem::METRIC) { return value >= convertTemp(min, minMaxUnit, unit) && value <= convertTemp(max, minMaxUnit, unit); } @@ -351,6 +351,7 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) { dst["opentherm"]["dhwBlocking"] = src.opentherm.dhwBlocking; dst["opentherm"]["modulationSyncWithHeating"] = src.opentherm.modulationSyncWithHeating; dst["opentherm"]["getMinMaxTemp"] = src.opentherm.getMinMaxTemp; + dst["opentherm"]["nativeHeatingControl"] = src.opentherm.nativeHeatingControl; dst["mqtt"]["enable"] = src.mqtt.enable; dst["mqtt"]["server"] = src.mqtt.server; @@ -359,15 +360,16 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) { dst["mqtt"]["password"] = src.mqtt.password; dst["mqtt"]["prefix"] = src.mqtt.prefix; dst["mqtt"]["interval"] = src.mqtt.interval; - } + dst["mqtt"]["homeAssistantDiscovery"] = src.mqtt.homeAssistantDiscovery; - dst["emergency"]["enable"] = src.emergency.enable; - dst["emergency"]["target"] = roundd(src.emergency.target, 2); - dst["emergency"]["tresholdTime"] = src.emergency.tresholdTime; - dst["emergency"]["useEquitherm"] = src.emergency.useEquitherm; - dst["emergency"]["usePid"] = src.emergency.usePid; - dst["emergency"]["onNetworkFault"] = src.emergency.onNetworkFault; - dst["emergency"]["onMqttFault"] = src.emergency.onMqttFault; + dst["emergency"]["enable"] = src.emergency.enable; + dst["emergency"]["target"] = roundd(src.emergency.target, 2); + dst["emergency"]["tresholdTime"] = src.emergency.tresholdTime; + dst["emergency"]["useEquitherm"] = src.emergency.useEquitherm; + dst["emergency"]["usePid"] = src.emergency.usePid; + dst["emergency"]["onNetworkFault"] = src.emergency.onNetworkFault; + dst["emergency"]["onMqttFault"] = src.emergency.onMqttFault; + } dst["heating"]["enable"] = src.heating.enable; dst["heating"]["turbo"] = src.heating.turbo; @@ -378,7 +380,7 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) { dst["heating"]["maxModulation"] = src.heating.maxModulation; dst["dhw"]["enable"] = src.dhw.enable; - dst["dhw"]["target"] = src.dhw.target; + dst["dhw"]["target"] = roundd(src.dhw.target, 1); dst["dhw"]["minTemp"] = src.dhw.minTemp; dst["dhw"]["maxTemp"] = src.dhw.maxTemp; @@ -425,7 +427,7 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) { } } -void safeSettingsToJson(const Settings& src, JsonVariant dst) { +inline void safeSettingsToJson(const Settings& src, JsonVariant dst) { settingsToJson(src, dst, true); } @@ -435,33 +437,47 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!safe) { // system if (src["system"]["debug"].is()) { - dst.system.debug = src["system"]["debug"].as(); - changed = true; + bool value = src["system"]["debug"].as(); + + if (value != dst.system.debug) { + dst.system.debug = value; + changed = true; + } } if (src["system"]["serial"]["enable"].is()) { - dst.system.serial.enable = src["system"]["serial"]["enable"].as(); - changed = true; + bool value = src["system"]["serial"]["enable"].as(); + + if (value != dst.system.serial.enable) { + dst.system.serial.enable = value; + changed = true; + } } if (!src["system"]["serial"]["baudrate"].isNull()) { unsigned int value = src["system"]["serial"]["baudrate"].as(); if (value == 9600 || value == 19200 || value == 38400 || value == 57600 || value == 74880 || value == 115200) { - dst.system.serial.baudrate = value; - changed = true; + if (value != dst.system.serial.baudrate) { + dst.system.serial.baudrate = value; + changed = true; + } } } if (src["system"]["telnet"]["enable"].is()) { - dst.system.telnet.enable = src["system"]["telnet"]["enable"].as(); - changed = true; + bool value = src["system"]["telnet"]["enable"].as(); + + if (value != dst.system.telnet.enable) { + dst.system.telnet.enable = value; + changed = true; + } } if (!src["system"]["telnet"]["port"].isNull()) { unsigned short value = src["system"]["telnet"]["port"].as(); - if (value > 0 && value <= 65535) { + if (value > 0 && value <= 65535 && value != dst.system.telnet.port) { dst.system.telnet.port = value; changed = true; } @@ -473,13 +489,17 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false switch (value) { case static_cast(UnitSystem::METRIC): - dst.system.unitSystem = UnitSystem::METRIC; - changed = true; + if (dst.system.unitSystem != UnitSystem::METRIC) { + dst.system.unitSystem = UnitSystem::METRIC; + changed = true; + } break; case static_cast(UnitSystem::IMPERIAL): - dst.system.unitSystem = UnitSystem::IMPERIAL; - changed = true; + if (dst.system.unitSystem != UnitSystem::IMPERIAL) { + dst.system.unitSystem = UnitSystem::IMPERIAL; + changed = true; + } break; default: @@ -510,7 +530,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } else { unsigned char value = src["system"]["statusLedGpio"].as(); - if (value >= 0 && value <= 254) { + if (GPIO_IS_VALID(value) && value != dst.system.statusLedGpio) { dst.system.statusLedGpio = value; changed = true; } @@ -520,14 +540,18 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // portal if (src["portal"]["auth"].is()) { - dst.portal.auth = src["portal"]["auth"].as(); - changed = true; + bool value = src["portal"]["auth"].as(); + + if (value != dst.portal.auth) { + dst.portal.auth = value; + changed = true; + } } if (!src["portal"]["login"].isNull()) { String value = src["portal"]["login"].as(); - if (value.length() < sizeof(dst.portal.login)) { + if (value.length() < sizeof(dst.portal.login) && !String(dst.portal.login).equals(value)) { strcpy(dst.portal.login, value.c_str()); changed = true; } @@ -536,7 +560,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)) { + if (value.length() < sizeof(dst.portal.password) && !String(dst.portal.password).equals(value)) { strcpy(dst.portal.password, value.c_str()); changed = true; } @@ -549,13 +573,17 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false switch (value) { case static_cast(UnitSystem::METRIC): - dst.opentherm.unitSystem = UnitSystem::METRIC; - changed = true; + if (dst.opentherm.unitSystem != UnitSystem::METRIC) { + dst.opentherm.unitSystem = UnitSystem::METRIC; + changed = true; + } break; case static_cast(UnitSystem::IMPERIAL): - dst.opentherm.unitSystem = UnitSystem::IMPERIAL; - changed = true; + if (dst.opentherm.unitSystem != UnitSystem::IMPERIAL) { + dst.opentherm.unitSystem = UnitSystem::IMPERIAL; + changed = true; + } break; default: @@ -573,7 +601,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } else { unsigned char value = src["opentherm"]["inGpio"].as(); - if (value >= 0 && value <= 254) { + if (GPIO_IS_VALID(value) && value != dst.opentherm.inGpio) { dst.opentherm.inGpio = value; changed = true; } @@ -590,7 +618,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } else { unsigned char value = src["opentherm"]["outGpio"].as(); - if (value >= 0 && value <= 254) { + if (GPIO_IS_VALID(value) && value != dst.opentherm.outGpio) { dst.opentherm.outGpio = value; changed = true; } @@ -607,7 +635,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } else { unsigned char value = src["opentherm"]["rxLedGpio"].as(); - if (value >= 0 && value <= 254) { + if (GPIO_IS_VALID(value) && value != dst.opentherm.rxLedGpio) { dst.opentherm.rxLedGpio = value; changed = true; } @@ -617,7 +645,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["opentherm"]["memberIdCode"].isNull()) { unsigned int value = src["opentherm"]["memberIdCode"].as(); - if (value >= 0 && value < 65536) { + if (value >= 0 && value < 65536 && value != dst.opentherm.memberIdCode) { dst.opentherm.memberIdCode = value; changed = true; } @@ -629,69 +657,118 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } if (src["opentherm"]["summerWinterMode"].is()) { - dst.opentherm.summerWinterMode = src["opentherm"]["summerWinterMode"].as(); - changed = true; + bool value = src["opentherm"]["summerWinterMode"].as(); + + if (value != dst.opentherm.summerWinterMode) { + dst.opentherm.summerWinterMode = value; + changed = true; + } } if (src["opentherm"]["heatingCh2Enabled"].is()) { - dst.opentherm.heatingCh2Enabled = src["opentherm"]["heatingCh2Enabled"].as(); + bool value = src["opentherm"]["heatingCh2Enabled"].as(); - if (dst.opentherm.heatingCh2Enabled) { - dst.opentherm.heatingCh1ToCh2 = false; - dst.opentherm.dhwToCh2 = false; + if (value != dst.opentherm.heatingCh2Enabled) { + dst.opentherm.heatingCh2Enabled = value; + + if (dst.opentherm.heatingCh2Enabled) { + dst.opentherm.heatingCh1ToCh2 = false; + dst.opentherm.dhwToCh2 = false; + } + + changed = true; } - - changed = true; } if (src["opentherm"]["heatingCh1ToCh2"].is()) { - dst.opentherm.heatingCh1ToCh2 = src["opentherm"]["heatingCh1ToCh2"].as(); + bool value = src["opentherm"]["heatingCh1ToCh2"].as(); - if (dst.opentherm.heatingCh1ToCh2) { - dst.opentherm.heatingCh2Enabled = false; - dst.opentherm.dhwToCh2 = false; + if (value != dst.opentherm.heatingCh1ToCh2) { + dst.opentherm.heatingCh1ToCh2 = value; + + if (dst.opentherm.heatingCh1ToCh2) { + dst.opentherm.heatingCh2Enabled = false; + dst.opentherm.dhwToCh2 = false; + } + + changed = true; } - - changed = true; } if (src["opentherm"]["dhwToCh2"].is()) { - dst.opentherm.dhwToCh2 = src["opentherm"]["dhwToCh2"].as(); + bool value = src["opentherm"]["dhwToCh2"].as(); - if (dst.opentherm.dhwToCh2) { - dst.opentherm.heatingCh2Enabled = false; - dst.opentherm.heatingCh1ToCh2 = false; + if (value != dst.opentherm.dhwToCh2) { + dst.opentherm.dhwToCh2 = value; + + if (dst.opentherm.dhwToCh2) { + dst.opentherm.heatingCh2Enabled = false; + dst.opentherm.heatingCh1ToCh2 = false; + } + + changed = true; } - - changed = true; } if (src["opentherm"]["dhwBlocking"].is()) { - dst.opentherm.dhwBlocking = src["opentherm"]["dhwBlocking"].as(); - changed = true; + bool value = src["opentherm"]["dhwBlocking"].as(); + + if (value != dst.opentherm.dhwBlocking) { + dst.opentherm.dhwBlocking = value; + changed = true; + } } if (src["opentherm"]["modulationSyncWithHeating"].is()) { - dst.opentherm.modulationSyncWithHeating = src["opentherm"]["modulationSyncWithHeating"].as(); - changed = true; + bool value = src["opentherm"]["modulationSyncWithHeating"].as(); + + if (value != dst.opentherm.modulationSyncWithHeating) { + dst.opentherm.modulationSyncWithHeating = value; + changed = true; + } } if (src["opentherm"]["getMinMaxTemp"].is()) { - dst.opentherm.getMinMaxTemp = src["opentherm"]["getMinMaxTemp"].as(); - changed = true; + bool value = src["opentherm"]["getMinMaxTemp"].as(); + + if (value != dst.opentherm.getMinMaxTemp) { + dst.opentherm.getMinMaxTemp = value; + changed = true; + } + } + + if (src["opentherm"]["nativeHeatingControl"].is()) { + bool value = src["opentherm"]["nativeHeatingControl"].as(); + + if (value != dst.opentherm.nativeHeatingControl) { + dst.opentherm.nativeHeatingControl = value; + + if (value) { + dst.emergency.useEquitherm = false; + dst.emergency.usePid = false; + dst.equitherm.enable = false; + dst.pid.enable = false; + } + + changed = true; + } } // mqtt if (src["mqtt"]["enable"].is()) { - dst.mqtt.enable = src["mqtt"]["enable"].as(); - changed = true; + bool value = src["mqtt"]["enable"].as(); + + if (value != dst.mqtt.enable) { + dst.mqtt.enable = value; + changed = true; + } } if (!src["mqtt"]["server"].isNull()) { String value = src["mqtt"]["server"].as(); - if (value.length() < sizeof(dst.mqtt.server)) { + if (value.length() < sizeof(dst.mqtt.server) && !String(dst.mqtt.server).equals(value)) { strcpy(dst.mqtt.server, value.c_str()); changed = true; } @@ -700,7 +777,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["mqtt"]["port"].isNull()) { unsigned short value = src["mqtt"]["port"].as(); - if (value > 0 && value <= 65535) { + if (value > 0 && value <= 65535 && value != dst.mqtt.port) { dst.mqtt.port = value; changed = true; } @@ -709,7 +786,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)) { + if (value.length() < sizeof(dst.mqtt.user) && !String(dst.mqtt.user).equals(value)) { strcpy(dst.mqtt.user, value.c_str()); changed = true; } @@ -718,7 +795,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)) { + if (value.length() < sizeof(dst.mqtt.password) && !String(dst.mqtt.password).equals(value)) { strcpy(dst.mqtt.password, value.c_str()); changed = true; } @@ -727,7 +804,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)) { + if (value.length() < sizeof(dst.mqtt.prefix) && !String(dst.mqtt.prefix).equals(value)) { strcpy(dst.mqtt.prefix, value.c_str()); changed = true; } @@ -736,115 +813,139 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["mqtt"]["interval"].isNull()) { unsigned short value = src["mqtt"]["interval"].as(); - if (value >= 3 && value <= 60) { + if (value >= 3 && value <= 60 && value != dst.mqtt.interval) { dst.mqtt.interval = value; changed = true; } } - } + + if (src["mqtt"]["homeAssistantDiscovery"].is()) { + bool value = src["mqtt"]["homeAssistantDiscovery"].as(); + + if (value != dst.mqtt.homeAssistantDiscovery) { + dst.mqtt.homeAssistantDiscovery = value; + changed = true; + } + } - // emergency - if (src["emergency"]["enable"].is()) { - dst.emergency.enable = src["emergency"]["enable"].as(); - changed = true; - } + // emergency + if (src["emergency"]["enable"].is()) { + bool value = src["emergency"]["enable"].as(); + + if (value != dst.emergency.enable) { + dst.emergency.enable = value; + changed = true; + } + } if (!src["emergency"]["tresholdTime"].isNull()) { unsigned short value = src["emergency"]["tresholdTime"].as(); - if (value >= 60 && value <= 1800) { + if (value >= 60 && value <= 1800 && value != dst.emergency.tresholdTime) { dst.emergency.tresholdTime = value; changed = true; } } - if (src["emergency"]["useEquitherm"].is()) { - if (dst.sensors.outdoor.type != SensorType::MANUAL) { - dst.emergency.useEquitherm = src["emergency"]["useEquitherm"].as(); + if (src["emergency"]["useEquitherm"].is()) { + bool value = src["emergency"]["useEquitherm"].as(); - } else { - dst.emergency.useEquitherm = false; + if (!dst.opentherm.nativeHeatingControl && dst.sensors.outdoor.type != SensorType::MANUAL) { + if (value != dst.emergency.useEquitherm) { + dst.emergency.useEquitherm = value; + changed = true; + } + + } else if (dst.emergency.useEquitherm) { + dst.emergency.useEquitherm = false; + changed = true; + } + + if (dst.emergency.useEquitherm && dst.emergency.usePid) { + dst.emergency.usePid = false; + changed = true; + } } - if (dst.emergency.useEquitherm && dst.emergency.usePid) { - dst.emergency.usePid = false; + if (src["emergency"]["usePid"].is()) { + bool value = src["emergency"]["usePid"].as(); + + if (!dst.opentherm.nativeHeatingControl && dst.sensors.indoor.type != SensorType::MANUAL) { + if (value != dst.emergency.usePid) { + dst.emergency.usePid = value; + changed = true; + } + + } else if (dst.emergency.usePid) { + dst.emergency.usePid = false; + changed = true; + } + + if (dst.emergency.usePid && dst.emergency.useEquitherm) { + dst.emergency.useEquitherm = false; + changed = true; + } } - changed = true; - } + if (src["emergency"]["onNetworkFault"].is()) { + bool value = src["emergency"]["onNetworkFault"].as(); - if (src["emergency"]["usePid"].is()) { - if (dst.sensors.indoor.type != SensorType::MANUAL) { - dst.emergency.usePid = src["emergency"]["usePid"].as(); - - } else { - dst.emergency.usePid = false; + if (value != dst.emergency.onNetworkFault) { + dst.emergency.onNetworkFault = value; + changed = true; + } } - if (dst.emergency.usePid && dst.emergency.useEquitherm) { - dst.emergency.useEquitherm = false; - } + if (src["emergency"]["onMqttFault"].is()) { + bool value = src["emergency"]["onMqttFault"].as(); - changed = true; - } - - if (src["emergency"]["onNetworkFault"].is()) { - dst.emergency.onNetworkFault = src["emergency"]["onNetworkFault"].as(); - changed = true; - } - - if (src["emergency"]["onMqttFault"].is()) { - dst.emergency.onMqttFault = src["emergency"]["onMqttFault"].as(); - changed = true; - } - - if (!src["emergency"]["target"].isNull()) { - double value = src["emergency"]["target"].as(); - bool noRegulators = (!dst.emergency.useEquitherm && !dst.emergency.usePid); - bool valid = isValidTemp( - value, - dst.system.unitSystem, - noRegulators ? dst.heating.minTemp : 5, - noRegulators ? dst.heating.maxTemp : 30, - noRegulators ? dst.system.unitSystem : UnitSystem::METRIC - ); - - if (valid) { - dst.emergency.target = roundd(value, 2); - changed = true; + if (value != dst.emergency.onMqttFault) { + dst.emergency.onMqttFault = value; + changed = true; + } } } // pid if (src["pid"]["enable"].is()) { - dst.pid.enable = src["pid"]["enable"].as(); - changed = true; + bool value = src["pid"]["enable"].as(); + + if (!dst.opentherm.nativeHeatingControl) { + if (value != dst.pid.enable) { + dst.pid.enable = value; + changed = true; + } + + } else if (dst.pid.enable) { + dst.pid.enable = false; + changed = true; + } } if (!src["pid"]["p_factor"].isNull()) { - double value = src["pid"]["p_factor"].as(); + float value = src["pid"]["p_factor"].as(); - if (value > 0 && value <= 1000) { + if (value > 0 && value <= 1000 && fabs(value - dst.pid.p_factor) > 0.0001f) { dst.pid.p_factor = roundd(value, 3); changed = true; } } if (!src["pid"]["i_factor"].isNull()) { - double value = src["pid"]["i_factor"].as(); + float value = src["pid"]["i_factor"].as(); - if (value >= 0 && value <= 100) { + if (value >= 0 && value <= 100 && fabs(value - dst.pid.i_factor) > 0.0001f) { dst.pid.i_factor = roundd(value, 4); changed = true; } } if (!src["pid"]["d_factor"].isNull()) { - double value = src["pid"]["d_factor"].as(); + float value = src["pid"]["d_factor"].as(); - if (value >= 0 && value <= 100000) { + if (value >= 0 && value <= 100000 && fabs(value - dst.pid.d_factor) > 0.0001f) { dst.pid.d_factor = roundd(value, 1); changed = true; } @@ -853,7 +954,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["pid"]["dt"].isNull()) { unsigned short value = src["pid"]["dt"].as(); - if (value >= 30 && value <= 600) { + if (value >= 30 && value <= 600 && value != dst.pid.dt) { dst.pid.dt = value; changed = true; } @@ -862,7 +963,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["pid"]["maxTemp"].isNull()) { unsigned char value = src["pid"]["maxTemp"].as(); - if (isValidTemp(value, dst.system.unitSystem) && value > dst.pid.minTemp) { + if (isValidTemp(value, dst.system.unitSystem) && value > dst.pid.minTemp && value != dst.pid.maxTemp) { dst.pid.maxTemp = value; changed = true; } @@ -871,7 +972,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["pid"]["minTemp"].isNull()) { unsigned char value = src["pid"]["minTemp"].as(); - if (isValidTemp(value, dst.system.unitSystem) && value < dst.pid.maxTemp) { + if (isValidTemp(value, dst.system.unitSystem) && value < dst.pid.maxTemp && value != dst.pid.minTemp) { dst.pid.minTemp = value; changed = true; } @@ -880,32 +981,42 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // equitherm if (src["equitherm"]["enable"].is()) { - dst.equitherm.enable = src["equitherm"]["enable"].as(); - changed = true; + bool value = src["equitherm"]["enable"].as(); + + if (!dst.opentherm.nativeHeatingControl) { + if (value != dst.equitherm.enable) { + dst.equitherm.enable = value; + changed = true; + } + + } else if (dst.equitherm.enable) { + dst.equitherm.enable = false; + changed = true; + } } if (!src["equitherm"]["n_factor"].isNull()) { - double value = src["equitherm"]["n_factor"].as(); + float value = src["equitherm"]["n_factor"].as(); - if (value > 0 && value <= 10) { + if (value > 0 && value <= 10 && fabs(value - dst.equitherm.n_factor) > 0.0001f) { dst.equitherm.n_factor = roundd(value, 3); changed = true; } } if (!src["equitherm"]["k_factor"].isNull()) { - double value = src["equitherm"]["k_factor"].as(); + float value = src["equitherm"]["k_factor"].as(); - if (value >= 0 && value <= 10) { + if (value >= 0 && value <= 10 && fabs(value - dst.equitherm.k_factor) > 0.0001f) { dst.equitherm.k_factor = roundd(value, 3); changed = true; } } if (!src["equitherm"]["t_factor"].isNull()) { - double value = src["equitherm"]["t_factor"].as(); + float value = src["equitherm"]["t_factor"].as(); - if (value >= 0 && value <= 10) { + if (value >= 0 && value <= 10 && fabs(value - dst.equitherm.t_factor) > 0.0001f) { dst.equitherm.t_factor = roundd(value, 3); changed = true; } @@ -914,36 +1025,27 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // heating if (src["heating"]["enable"].is()) { - dst.heating.enable = src["heating"]["enable"].as(); - changed = true; + bool value = src["heating"]["enable"].as(); + + if (value != dst.heating.enable) { + dst.heating.enable = value; + changed = true; + } } if (src["heating"]["turbo"].is()) { - dst.heating.turbo = src["heating"]["turbo"].as(); - changed = true; - } + bool value = src["heating"]["turbo"].as(); - if (!src["heating"]["target"].isNull()) { - double value = src["heating"]["target"].as(); - bool noRegulators = (!dst.equitherm.enable && !dst.pid.enable); - bool valid = isValidTemp( - value, - dst.system.unitSystem, - noRegulators ? dst.heating.minTemp : 5, - noRegulators ? dst.heating.maxTemp : 30, - noRegulators ? dst.system.unitSystem : UnitSystem::METRIC - ); - - if (valid) { - dst.heating.target = roundd(value, 2); + if (value != dst.heating.turbo) { + dst.heating.turbo = value; changed = true; } } if (!src["heating"]["hysteresis"].isNull()) { - double value = src["heating"]["hysteresis"].as(); + float value = src["heating"]["hysteresis"].as(); - if (value >= 0 && value <= 5) { + if (value >= 0 && value <= 5 && fabs(value - dst.heating.hysteresis) > 0.0001f) { dst.heating.hysteresis = roundd(value, 2); changed = true; } @@ -952,7 +1054,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 >= vars.parameters.heatingMinTemp && value <= vars.parameters.heatingMaxTemp) { + if (value != dst.heating.minTemp && value >= vars.parameters.heatingMinTemp && value < vars.parameters.heatingMaxTemp && value != dst.heating.minTemp) { dst.heating.minTemp = value; changed = true; } @@ -961,7 +1063,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 >= vars.parameters.heatingMinTemp && value <= vars.parameters.heatingMaxTemp) { + if (value != dst.heating.maxTemp && value > vars.parameters.heatingMinTemp && value <= vars.parameters.heatingMaxTemp && value != dst.heating.maxTemp) { dst.heating.maxTemp = value; changed = true; } @@ -970,7 +1072,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!src["heating"]["maxModulation"].isNull()) { unsigned char value = src["heating"]["maxModulation"].as(); - if (value > 0 && value <= 100) { + if (value > 0 && value <= 100 && value != dst.heating.maxModulation) { dst.heating.maxModulation = value; changed = true; } @@ -979,15 +1081,10 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false // dhw if (src["dhw"]["enable"].is()) { - dst.dhw.enable = src["dhw"]["enable"].as(); - changed = true; - } + bool value = src["dhw"]["enable"].as(); - if (!src["dhw"]["target"].isNull()) { - unsigned char value = src["dhw"]["target"].as(); - - if (isValidTemp(value, dst.system.unitSystem, dst.dhw.minTemp, dst.dhw.maxTemp, dst.system.unitSystem)) { - dst.dhw.target = value; + if (value != dst.dhw.enable) { + dst.dhw.enable = value; changed = true; } } @@ -995,7 +1092,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 <= vars.parameters.dhwMaxTemp) { + if (value >= vars.parameters.dhwMinTemp && value < vars.parameters.dhwMaxTemp && value != dst.dhw.minTemp) { dst.dhw.minTemp = value; changed = true; } @@ -1004,7 +1101,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 <= vars.parameters.dhwMaxTemp) { + if (value > vars.parameters.dhwMinTemp && value <= vars.parameters.dhwMaxTemp && value != dst.dhw.maxTemp) { dst.dhw.maxTemp = value; changed = true; } @@ -1017,19 +1114,25 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false switch (value) { case static_cast(SensorType::BOILER): - dst.sensors.outdoor.type = SensorType::BOILER; - changed = true; + if (dst.sensors.outdoor.type != SensorType::BOILER) { + dst.sensors.outdoor.type = SensorType::BOILER; + changed = true; + } break; case static_cast(SensorType::MANUAL): - dst.sensors.outdoor.type = SensorType::MANUAL; - dst.emergency.useEquitherm = false; - changed = true; + if (dst.sensors.outdoor.type != SensorType::MANUAL) { + dst.sensors.outdoor.type = SensorType::MANUAL; + dst.emergency.useEquitherm = false; + changed = true; + } break; case static_cast(SensorType::DS18B20): - dst.sensors.outdoor.type = SensorType::DS18B20; - changed = true; + if (dst.sensors.outdoor.type != SensorType::DS18B20) { + dst.sensors.outdoor.type = SensorType::DS18B20; + changed = true; + } break; default: @@ -1047,7 +1150,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } else { unsigned char value = src["sensors"]["outdoor"]["gpio"].as(); - if (value >= 0 && value <= 254) { + if (GPIO_IS_VALID(value) && value != dst.sensors.outdoor.gpio) { dst.sensors.outdoor.gpio = value; changed = true; } @@ -1055,9 +1158,9 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } if (!src["sensors"]["outdoor"]["offset"].isNull()) { - double value = src["sensors"]["outdoor"]["offset"].as(); + float value = src["sensors"]["outdoor"]["offset"].as(); - if (value >= -10 && value <= 10) { + if (value >= -10 && value <= 10 && fabs(value - dst.sensors.outdoor.offset) > 0.0001f) { dst.sensors.outdoor.offset = roundd(value, 2); changed = true; } @@ -1067,26 +1170,28 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false byte value = src["sensors"]["indoor"]["type"].as(); switch (value) { - case static_cast(SensorType::BOILER): - dst.sensors.indoor.type = SensorType::BOILER; - changed = true; - break; - case static_cast(SensorType::MANUAL): - dst.sensors.indoor.type = SensorType::MANUAL; - dst.emergency.usePid = false; - changed = true; + if (dst.sensors.indoor.type != SensorType::MANUAL) { + dst.sensors.indoor.type = SensorType::MANUAL; + dst.emergency.usePid = false; + dst.opentherm.nativeHeatingControl = false; + changed = true; + } break; case static_cast(SensorType::DS18B20): - dst.sensors.indoor.type = SensorType::DS18B20; - changed = true; + if (dst.sensors.indoor.type != SensorType::DS18B20) { + dst.sensors.indoor.type = SensorType::DS18B20; + changed = true; + } break; #if USE_BLE case static_cast(SensorType::BLUETOOTH): - dst.sensors.indoor.type = SensorType::BLUETOOTH; - changed = true; + if (dst.sensors.indoor.type != SensorType::BLUETOOTH) { + dst.sensors.indoor.type = SensorType::BLUETOOTH; + changed = true; + } break; #endif @@ -1105,7 +1210,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } else { unsigned char value = src["sensors"]["indoor"]["gpio"].as(); - if (value >= 0 && value <= 254) { + if (GPIO_IS_VALID(value) && value != dst.sensors.indoor.gpio) { dst.sensors.indoor.gpio = value; changed = true; } @@ -1118,18 +1223,19 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false 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++) { - dst.sensors.indoor.bleAddresss[i] = (uint8_t) tmp[i]; + if (dst.sensors.indoor.bleAddresss[i] != (uint8_t) tmp[i]) { + dst.sensors.indoor.bleAddresss[i] = (uint8_t) tmp[i]; + changed = true; + } } - - changed = true; } } #endif if (!src["sensors"]["indoor"]["offset"].isNull()) { - double value = src["sensors"]["indoor"]["offset"].as(); + float value = src["sensors"]["indoor"]["offset"].as(); - if (value >= -10 && value <= 10) { + if (value >= -10 && value <= 10 && fabs(value - dst.sensors.indoor.offset) > 0.0001f) { dst.sensors.indoor.offset = roundd(value, 2); changed = true; } @@ -1139,8 +1245,12 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false if (!safe) { // external pump if (src["externalPump"]["use"].is()) { - dst.externalPump.use = src["externalPump"]["use"].as(); - changed = true; + bool value = src["externalPump"]["use"].as(); + + if (value != dst.externalPump.use) { + dst.externalPump.use = value; + changed = true; + } } if (!src["externalPump"]["gpio"].isNull()) { @@ -1153,7 +1263,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false } else { unsigned char value = src["externalPump"]["gpio"].as(); - if (value >= 0 && value <= 254) { + if (GPIO_IS_VALID(value) && value != dst.externalPump.gpio) { dst.externalPump.gpio = value; changed = true; } @@ -1164,8 +1274,12 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false unsigned short value = src["externalPump"]["postCirculationTime"].as(); if (value >= 0 && value <= 120) { - dst.externalPump.postCirculationTime = value * 60; - changed = true; + value = value * 60; + + if (value != dst.externalPump.postCirculationTime) { + dst.externalPump.postCirculationTime = value; + changed = true; + } } } @@ -1173,8 +1287,12 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false unsigned int value = src["externalPump"]["antiStuckInterval"].as(); if (value >= 0 && value <= 366) { - dst.externalPump.antiStuckInterval = value * 86400; - changed = true; + value = value * 86400; + + if (value != dst.externalPump.antiStuckInterval) { + dst.externalPump.antiStuckInterval = value; + changed = true; + } } } @@ -1182,23 +1300,97 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false unsigned short value = src["externalPump"]["antiStuckTime"].as(); if (value >= 0 && value <= 20) { - dst.externalPump.antiStuckTime = value * 60; - changed = true; + value = value * 60; + + if (value != dst.externalPump.antiStuckTime) { + dst.externalPump.antiStuckTime = value; + changed = true; + } } } } + // force check emergency target + { + float value = !src["emergency"]["target"].isNull() ? src["emergency"]["target"].as() : dst.emergency.target; + bool noRegulators = !dst.opentherm.nativeHeatingControl && !dst.emergency.useEquitherm && !dst.emergency.usePid; + 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 + ); + + if (!valid) { + value = convertTemp( + noRegulators ? DEFAULT_HEATING_TARGET_TEMP : THERMOSTAT_INDOOR_DEFAULT_TEMP, + UnitSystem::METRIC, + dst.system.unitSystem + ); + } + + if (fabs(dst.emergency.target - value) > 0.0001f) { + dst.emergency.target = roundd(value, 2); + changed = true; + } + } + + // 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 + ); + + if (!valid) { + value = convertTemp( + noRegulators ? DEFAULT_HEATING_TARGET_TEMP : THERMOSTAT_INDOOR_DEFAULT_TEMP, + UnitSystem::METRIC, + dst.system.unitSystem + ); + } + + if (fabs(dst.heating.target - value) > 0.0001f) { + dst.heating.target = roundd(value, 2); + changed = true; + } + } + + // force check dhw target + { + float value = !src["dhw"]["target"].isNull() ? src["dhw"]["target"].as() : dst.dhw.target; + bool valid = isValidTemp( + value, + dst.system.unitSystem, + dst.dhw.minTemp, + dst.dhw.maxTemp, + dst.system.unitSystem + ); + + if (!valid) { + value = convertTemp(DEFAULT_DHW_TARGET_TEMP, UnitSystem::METRIC, dst.system.unitSystem); + } + + if (fabs(dst.dhw.target - value) > 0.0001f) { + dst.dhw.target = value; + changed = true; + } + } + return changed; } -bool safeJsonToSettings(const JsonVariantConst src, Settings& dst) { +inline bool safeJsonToSettings(const JsonVariantConst src, Settings& dst) { return jsonToSettings(src, dst, true); } void varsToJson(const Variables& src, JsonVariant dst) { - dst["tuning"]["enable"] = src.tuning.enable; - dst["tuning"]["regulator"] = src.tuning.regulator; - dst["states"]["otStatus"] = src.states.otStatus; dst["states"]["emergency"] = src.states.emergency; dst["states"]["heating"] = src.states.heating; @@ -1226,7 +1418,7 @@ void varsToJson(const Variables& src, JsonVariant dst) { dst["parameters"]["heatingEnabled"] = src.parameters.heatingEnabled; dst["parameters"]["heatingMinTemp"] = src.parameters.heatingMinTemp; dst["parameters"]["heatingMaxTemp"] = src.parameters.heatingMaxTemp; - dst["parameters"]["heatingSetpoint"] = src.parameters.heatingSetpoint; + dst["parameters"]["heatingSetpoint"] = roundd(src.parameters.heatingSetpoint, 2); dst["parameters"]["dhwMinTemp"] = src.parameters.dhwMinTemp; dst["parameters"]["dhwMaxTemp"] = src.parameters.dhwMaxTemp; @@ -1240,38 +1432,26 @@ void varsToJson(const Variables& src, JsonVariant dst) { bool jsonToVars(const JsonVariantConst src, Variables& dst) { bool changed = false; - // tuning - if (src["tuning"]["enable"].is()) { - dst.tuning.enable = src["tuning"]["enable"].as(); - changed = true; - } - - if (!src["tuning"]["regulator"].isNull()) { - unsigned char value = src["tuning"]["regulator"].as(); - - if (value >= 0 && value <= 1) { - dst.tuning.regulator = value; - changed = true; - } - } - - // temperatures if (!src["temperatures"]["indoor"].isNull()) { - double value = src["temperatures"]["indoor"].as(); + float value = src["temperatures"]["indoor"].as(); if (settings.sensors.indoor.type == SensorType::MANUAL && isValidTemp(value, settings.system.unitSystem, -99.9f, 99.9f)) { - dst.temperatures.indoor = roundd(value, 2); - changed = true; + if (fabs(value - dst.temperatures.indoor) > 0.0001f) { + dst.temperatures.indoor = roundd(value, 2); + changed = true; + } } } if (!src["temperatures"]["outdoor"].isNull()) { - double value = src["temperatures"]["outdoor"].as(); + float value = src["temperatures"]["outdoor"].as(); if (settings.sensors.outdoor.type == SensorType::MANUAL && isValidTemp(value, settings.system.unitSystem, -99.9f, 99.9f)) { - dst.temperatures.outdoor = roundd(value, 2); - changed = true; + if (fabs(value - dst.temperatures.outdoor) > 0.0001f) { + dst.temperatures.outdoor = roundd(value, 2); + changed = true; + } } } diff --git a/src_data/dashboard.html b/src_data/dashboard.html index 1fb212c..967fe81 100644 --- a/src_data/dashboard.html +++ b/src_data/dashboard.html @@ -250,7 +250,7 @@ return; } - newSettings.dhw.target -= 1; + newSettings.dhw.target -= 1.0; modifiedTime = Date.now(); if (newSettings.dhw.target < prevSettings.dhw.minTemp) { @@ -265,7 +265,7 @@ return; } - newSettings.dhw.target += 1; + newSettings.dhw.target += 1.0; modifiedTime = Date.now(); if (newSettings.dhw.target > prevSettings.dhw.maxTemp) { @@ -326,7 +326,7 @@ } const result = await response.json(); - noRegulators = !result.equitherm.enable && !result.pid.enable; + noRegulators = !result.opentherm.nativeHeatingControl && !result.equitherm.enable && !result.pid.enable; prevSettings = result; newSettings.heating.enable = result.heating.enable; newSettings.heating.turbo = result.heating.turbo; diff --git a/src_data/settings.html b/src_data/settings.html index 5c11336..e270292 100644 --- a/src_data/settings.html +++ b/src_data/settings.html @@ -435,6 +435,13 @@ Get min/max temp from boiler + +
+ @@ -454,6 +461,11 @@ Enable + +
@@ -677,10 +689,12 @@ setCheckboxValue('#opentherm-dhw-blocking', data.opentherm.dhwBlocking); setCheckboxValue('#opentherm-sync-modulation-with-heating', data.opentherm.modulationSyncWithHeating); setCheckboxValue('#opentherm-get-min-max-temp', data.opentherm.getMinMaxTemp); + setCheckboxValue('#opentherm-native-heating-control', data.opentherm.nativeHeatingControl); setBusy('#opentherm-settings-busy', '#opentherm-settings', false); // MQTT setCheckboxValue('#mqtt-enable', data.mqtt.enable); + setCheckboxValue('#mqtt-ha-discovery', data.mqtt.homeAssistantDiscovery); setInputValue('#mqtt-server', data.mqtt.server); setInputValue('#mqtt-port', data.mqtt.port); setInputValue('#mqtt-user', data.mqtt.user);