From dc62f99b7d336e57e2c109b0eb1652a39fef6d32 Mon Sep 17 00:00:00 2001 From: Yurii Date: Mon, 14 Oct 2024 19:54:26 +0300 Subject: [PATCH] feat: added polling of min modulation and max boiler power; added sensor for current boiler power --- src/HaHelper.h | 46 +++++++++++++++++++++++++++++++++ src/MqttTask.h | 2 ++ src/OpenThermTask.h | 48 +++++++++++++++++++++++++++++++++-- src/Settings.h | 3 +++ src/utils.h | 2 ++ src_data/locales/en.json | 3 +++ src_data/locales/ru.json | 3 +++ src_data/pages/dashboard.html | 10 ++++++++ 8 files changed, 115 insertions(+), 2 deletions(-) diff --git a/src/HaHelper.h b/src/HaHelper.h index 8583d40..d7c9161 100644 --- a/src/HaHelper.h +++ b/src/HaHelper.h @@ -872,6 +872,52 @@ public: return this->publish(this->getTopic(FPSTR(HA_ENTITY_BINARY_SENSOR), F("diagnostic")).c_str(), doc); } + bool publishSensorMaxPower(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("max_power")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("max_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("Max 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.maxPower|float(0)|round(2) }}"); + doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc.shrinkToFit(); + + return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("max_power")).c_str(), doc); + } + + bool publishSensorCurrentPower(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("current_power")); + doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("current_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.currentPower|float(0)|round(2) }}"); + doc[FPSTR(HA_EXPIRE_AFTER)] = 120; + doc.shrinkToFit(); + + return this->publish(this->getTopic(FPSTR(HA_ENTITY_SENSOR), F("current_power")).c_str(), doc); + } + bool publishSensorFaultCode(bool enabledByDefault = true) { JsonDocument doc; doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); diff --git a/src/MqttTask.h b/src/MqttTask.h index 49e3761..795b4d8 100644 --- a/src/MqttTask.h +++ b/src/MqttTask.h @@ -357,6 +357,8 @@ protected: // sensors this->haHelper->publishSensorModulation(false); this->haHelper->publishSensorPressure(settings.system.unitSystem, false); + this->haHelper->publishSensorMaxPower(false); + this->haHelper->publishSensorCurrentPower(); this->haHelper->publishSensorFaultCode(); this->haHelper->publishSensorDiagnosticCode(); this->haHelper->publishSensorRssi(false); diff --git a/src/OpenThermTask.h b/src/OpenThermTask.h index 1ea18ee..4b4dbf1 100644 --- a/src/OpenThermTask.h +++ b/src/OpenThermTask.h @@ -253,6 +253,19 @@ protected: // These parameters will be updated every minute if (millis() - this->prevUpdateNonEssentialVars > 60000) { + if (this->updateMinModulationLevel()) { + Log.straceln(FPSTR(L_OT), F("Min modulation: %u%%, boiler max power: %u kW"), vars.parameters.minModulation, vars.sensors.maxPower); + + if (vars.parameters.minModulation > settings.heating.maxModulation) { + settings.heating.maxModulation = vars.parameters.minModulation; + fsSettings.update(); + Log.snoticeln(FPSTR(L_OT_DHW), F("Updated min modulation: %hhu"), settings.heating.maxModulation); + } + + } else { + Log.swarningln(FPSTR(L_OT), F("Failed get min modulation & max power")); + } + if (!heatingEnabled && settings.opentherm.modulationSyncWithHeating) { if (this->setMaxModulationLevel(0)) { Log.snoticeln(FPSTR(L_OT_HEATING), F("Set max modulation 0% (off)")); @@ -363,11 +376,22 @@ protected: // Get current modulation level (if necessary) - if (vars.states.flame) { - this->updateModulationLevel(); + if (vars.states.flame && this->updateModulationLevel()) { + vars.sensors.currentPower = vars.sensors.maxPower > 0 + ? vars.sensors.maxPower * (vars.sensors.modulation / 100) + : 0; + + Log.sverboseln( + FPSTR(L_OT_DHW), + F("Current modulation level: %.2f%%, power: %.2f of %hhu kW"), + vars.sensors.modulation, + vars.sensors.currentPower, + vars.sensors.maxPower + ); } else { vars.sensors.modulation = 0; + vars.sensors.currentPower = 0; } // Update DHW sensors (if necessary) @@ -1045,6 +1069,26 @@ protected: return true; } + bool updateMinModulationLevel() { + unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( + OpenThermRequestType::READ_DATA, + OpenThermMessageID::MaxCapacityMinModLevel, + 0 + )); + + if (!CustomOpenTherm::isValidResponse(response)) { + return false; + } + + byte minModulation = response & 0xFF; + byte maxPower = (response & 0xFFFF) >> 8; + + vars.parameters.minModulation = minModulation; + vars.sensors.maxPower = maxPower; + + return true; + } + bool updatePressure() { unsigned long response = this->instance->sendRequest(CustomOpenTherm::buildRequest( OpenThermRequestType::READ_DATA, diff --git a/src/Settings.h b/src/Settings.h index 16bea24..9f95d85 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -174,6 +174,8 @@ struct Variables { float modulation = 0.0f; float pressure = 0.0f; float dhwFlowRate = 0.0f; + byte maxPower = 0; + float currentPower = 0.0f; byte faultCode = 0; unsigned short diagnosticCode = 0; int8_t rssi = 0; @@ -210,6 +212,7 @@ struct Variables { unsigned long extPumpLastEnableTime = 0; byte dhwMinTemp = DEFAULT_DHW_MIN_TEMP; byte dhwMaxTemp = DEFAULT_DHW_MAX_TEMP; + byte minModulation = 0; byte maxModulation = 0; uint8_t slaveMemberId = 0; uint8_t slaveFlags = 0; diff --git a/src/utils.h b/src/utils.h index 9076af6..e7915b9 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1546,6 +1546,8 @@ void varsToJson(const Variables& src, JsonVariant dst) { 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"]["maxPower"] = src.sensors.maxPower; + dst["sensors"]["currentPower"] = roundd(src.sensors.currentPower, 2); dst["sensors"]["faultCode"] = src.sensors.faultCode; dst["sensors"]["diagnosticCode"] = src.sensors.diagnosticCode; dst["sensors"]["rssi"] = src.sensors.rssi; diff --git a/src_data/locales/en.json b/src_data/locales/en.json index d96d5a5..512e839 100644 --- a/src_data/locales/en.json +++ b/src_data/locales/en.json @@ -9,6 +9,7 @@ "releases": "Releases" }, "dbm": "dBm", + "kw": "kW", "button": { "upgrade": "Upgrade", @@ -99,6 +100,8 @@ "modulation": "Modulation", "pressure": "Pressure", "dhwFlowRate": "DHW flow rate", + "maxPower": "Max power", + "currentPower": "Current power", "faultCode": "Fault code", "diagCode": "Diagnostic code", "indoorTemp": "Indoor temp", diff --git a/src_data/locales/ru.json b/src_data/locales/ru.json index 6ab08de..99a470a 100644 --- a/src_data/locales/ru.json +++ b/src_data/locales/ru.json @@ -9,6 +9,7 @@ "releases": "Релизы" }, "dbm": "дБм", + "kw": "кВт", "button": { "upgrade": "Обновить", @@ -99,6 +100,8 @@ "modulation": "Уровень модуляции", "pressure": "Давление", "dhwFlowRate": "Расход ГВС", + "maxPower": "Макс. мощность", + "currentPower": "Текущая мощность", "faultCode": "Код ошибки", "diagCode": "Диагностический код", "indoorTemp": "Внутренняя темп.", diff --git a/src_data/pages/dashboard.html b/src_data/pages/dashboard.html index b9e70b2..450fef9 100644 --- a/src_data/pages/dashboard.html +++ b/src_data/pages/dashboard.html @@ -158,6 +158,14 @@ dashboard.state.dhwFlowRate /min + + dashboard.state.maxPower + kw + + + dashboard.state.currentPower + kw + dashboard.state.faultCode @@ -434,6 +442,8 @@ setValue('#ot-modulation', result.sensors.modulation); setValue('#ot-pressure', result.sensors.pressure); setValue('#ot-dhw-flow-rate', result.sensors.dhwFlowRate); + setValue('#ot-max-power', result.sensors.maxPower); + setValue('#ot-current-power', result.sensors.currentPower); setValue( '#ot-fault-code', result.sensors.faultCode