From bd9234579392e5ba106243b3e4a2968e7f7420e3 Mon Sep 17 00:00:00 2001 From: MichaelDvP Date: Sun, 3 Sep 2023 17:53:48 +0200 Subject: [PATCH] boiler energy counter, stored in nvs --- lib/framework/RestartService.cpp | 5 ++ lib/framework/UploadFileService.cpp | 4 + src/devices/boiler.cpp | 114 +++++++++++++++++++++++++++- src/devices/boiler.h | 14 ++++ src/emsdevice.cpp | 9 ++- src/emsdevice.h | 16 ++++ src/emsesp.cpp | 2 + src/emsesp.h | 2 + src/locale_translations.h | 4 + src/system.cpp | 5 ++ src/system.h | 1 + 11 files changed, 173 insertions(+), 3 deletions(-) diff --git a/lib/framework/RestartService.cpp b/lib/framework/RestartService.cpp index afa42ef16..157f26dfb 100644 --- a/lib/framework/RestartService.cpp +++ b/lib/framework/RestartService.cpp @@ -1,6 +1,8 @@ #include #include +#include "../../src/emsesp_stub.hpp" + using namespace std::placeholders; // for `_1` etc RestartService::RestartService(AsyncWebServer * server, SecurityManager * securityManager) { @@ -11,6 +13,7 @@ RestartService::RestartService(AsyncWebServer * server, SecurityManager * securi } void RestartService::restart(AsyncWebServerRequest * request) { + emsesp::EMSESP::system_.store_boiler_energy(); request->onDisconnect(RestartService::restartNow); request->send(200); } @@ -19,6 +22,7 @@ void RestartService::partition(AsyncWebServerRequest * request) { const esp_partition_t * factory_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL); if (factory_partition) { esp_ota_set_boot_partition(factory_partition); + emsesp::EMSESP::system_.store_boiler_energy(); request->onDisconnect(RestartService::restartNow); request->send(200); return; @@ -35,6 +39,7 @@ void RestartService::partition(AsyncWebServerRequest * request) { return; } esp_ota_set_boot_partition(ota_partition); + emsesp::EMSESP::system_.store_boiler_energy(); request->onDisconnect(RestartService::restartNow); request->send(200); } diff --git a/lib/framework/UploadFileService.cpp b/lib/framework/UploadFileService.cpp index a96f67142..d6840d211 100644 --- a/lib/framework/UploadFileService.cpp +++ b/lib/framework/UploadFileService.cpp @@ -1,6 +1,8 @@ #include #include +#include "../../src/emsesp_stub.hpp" + using namespace std::placeholders; // for `_1` etc static bool is_firmware = false; @@ -112,6 +114,7 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) { // did we complete uploading a json file? if (request->_tempFile) { request->_tempFile.close(); // close the file handle as the upload is now done + emsesp::EMSESP::system_.store_boiler_energy(); request->onDisconnect(RestartService::restartNow); AsyncWebServerResponse * response = request->beginResponse(200); request->send(response); @@ -121,6 +124,7 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) { // check if it was a firmware upgrade // if no error, send the success response as a JSON if (is_firmware && !request->_tempObject) { + emsesp::EMSESP::system_.store_boiler_energy(); request->onDisconnect(RestartService::restartNow); AsyncWebServerResponse * response = request->beginResponse(200); request->send(response); diff --git a/src/devices/boiler.cpp b/src/devices/boiler.cpp index 62a3f5be6..48263d012 100644 --- a/src/devices/boiler.cpp +++ b/src/devices/boiler.cpp @@ -750,7 +750,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const register_device_value( DeviceValueTAG::TAG_BOILER_DATA_WW, &wwMaxPower_, DeviceValueType::UINT, FL_(wwMaxPower), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_ww_maxpower), 0, 254); register_device_value( - DeviceValueTAG::TAG_BOILER_DATA_WW, &wwMaxTemp_, DeviceValueType::UINT, FL_(wwMaxTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_ww_maxtemp), 0, 70); + DeviceValueTAG::TAG_BOILER_DATA_WW, &wwMaxTemp_, DeviceValueType::UINT, FL_(wwMaxTemp), DeviceValueUOM::DEGREES, MAKE_CF_CB(set_ww_maxtemp), 0, 80); register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW, &wwCircPump_, DeviceValueType::BOOL, @@ -849,6 +849,31 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const EMSESP::send_read_request(0x15, device_id); // read maintenance data on start (only published on change) EMSESP::send_read_request(0x1C, device_id); // read maintenance status on start (only published on change) EMSESP::send_read_request(0xC2, device_id); // read last errorcode on start (only published on errors) + + register_telegram_type(0x04, "UBAFactory", true, MAKE_PF_CB(process_UBAFactory)); + register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nomPower_, DeviceValueType::UINT, FL_(nomPower), DeviceValueUOM::KW, MAKE_CF_CB(set_nomPower)); + + if (model() != EMS_DEVICE_FLAG_HEATPUMP) { + register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgHeat_, DeviceValueType::ULONG, FL_(nrgHeat), DeviceValueUOM::KWH, MAKE_CF_CB(set_nrgHeat)); + register_device_value(DeviceValueTAG::TAG_DEVICE_DATA, &nrgWw_, DeviceValueType::ULONG, FL_(nrgWw), DeviceValueUOM::KWH, MAKE_CF_CB(set_nrgWw)); + + nrgHeatF_ = EMSESP::nvs_.getDouble(FL_(nrgHeat)[0], 0); + nrgWwF_ = EMSESP::nvs_.getDouble(FL_(nrgWw)[0], 0); + // update/publish the values + has_update(nrgHeat_, (uint32_t)nrgHeatF_); + has_update(nrgWw_, (uint32_t)nrgWwF_); + } +} + +void Boiler::store_energy() { + // only write if something is changed + if (nrgHeatF_ != EMSESP::nvs_.getDouble(FL_(nrgHeat)[0]) || nrgWwF_ != EMSESP::nvs_.getDouble(FL_(nrgWw)[0]) + || nomPower_ != EMSESP::nvs_.getUChar(FL_(nomPower)[0])) { + EMSESP::nvs_.putDouble(FL_(nrgHeat)[0], nrgHeatF_); + EMSESP::nvs_.putDouble(FL_(nrgWw)[0], nrgWwF_); + EMSESP::nvs_.putUChar(FL_(nomPower)[0], nomPower_); + LOG_DEBUG("energy values stored"); + } } // Check if hot tap water or heating is active @@ -894,6 +919,62 @@ void Boiler::check_active() { Mqtt::queue_publish(F_(tapwater_active), Helpers::render_boolean(s, b)); EMSESP::tap_water_active(b); // let EMS-ESP know, used in the Shower class } + + // calculate energy for boiler 0x08 from stored modulation an time in units of 0.01 Wh + if (model() != EMS_DEVICE_FLAG_HEATPUMP) { + // remember values from last call + static uint32_t powLastReadTime_ = uuid::get_uptime(); + static uint8_t heatBurnPow = 0; + static uint8_t wwBurnPow = 0; + static uint8_t lastSaveHour = 0; + if (nrgHeat_ > (uint32_t)nrgHeatF_) { + nrgHeatF_ = nrgHeat_; + } + if (nrgWw_ > (uint32_t)nrgWwF_) { + nrgWwF_ = nrgWw_; + } + // 0.01 Wh = 0.01 Ws / 3600 = (% * kW * ms) / 3600 + nrgHeatF_ += (double_t)(((uint32_t)heatBurnPow * nomPower_ * (uuid::get_uptime() - powLastReadTime_)) / 3600) / 100000UL; + nrgWwF_ += (double_t)(((uint32_t)wwBurnPow * nomPower_ * (uuid::get_uptime() - powLastReadTime_)) / 3600) / 100000UL; + has_update(nrgHeat_, (uint32_t)(nrgHeatF_)); + has_update(nrgWw_, (uint32_t)(nrgWwF_)); + // check for store values + time_t now = time(nullptr); + tm * tm_ = localtime(&now); + if (tm_->tm_hour != lastSaveHour) { + lastSaveHour = tm_->tm_hour; + store_energy(); + } else if (curBurnPow_ == 0 && (heatBurnPow + wwBurnPow) > 0) { // on burner switch off + store_energy(); + } + // store new modulation and time + heatBurnPow = heatingActive_ ? curBurnPow_ : 0; + wwBurnPow = tapwaterActive_ ? curBurnPow_ : 0; + powLastReadTime_ = uuid::get_uptime(); + } +} + +// 0x04 +// boiler(0x08) -W-> Me(0x0B), ?(0x04), data: 13 96 09 81 00 64 64 35 05 64 5A 22 00 00 00 00 00 00 00 00 B7 +// offset 4 - nominal Power kW, could be zero, 5 - min. Burner, 6 - max. Burner +void Boiler::process_UBAFactory(std::shared_ptr telegram) { + uint8_t nomPower = nomPower_; + if (!telegram->read_value(nomPower, 4)) { + return; + } + if (nomPower == 0 || nomPower == 255) { + nomPower = EMSESP::nvs_.getUChar(FL_(nomPower)[0], 0); + } + if (nomPower != nomPower_ || nomPower == 255) { + if (nomPower == 255) { + nomPower_ = nomPower = 0; + store_energy(); + has_update(&nomPower_); + } + has_update(nomPower_, nomPower); + toggle_fetch(telegram->type_id, false); // only read once + // LOG_DEBUG("nominal power set to %d", nomPower_); + } } // 0x18 @@ -2642,4 +2723,35 @@ bool Boiler::set_wwAltOpPrio(const char * value, const int8_t id) { return false; } +bool Boiler::set_nrgHeat(const char * value, const int8_t id) { + int v; + if (Helpers::value2number(value, v)) { + nrgHeatF_ = nrgHeat_ = v; + store_energy(); + return true; + } + return false; +} + +bool Boiler::set_nrgWw(const char * value, const int8_t id) { + int v; + if (Helpers::value2number(value, v)) { + nrgWwF_ = nrgWw_ = v; + store_energy(); + return true; + } + return false; +} + +bool Boiler::set_nomPower(const char * value, const int8_t id) { + int v; + if (Helpers::value2number(value, v)) { + nomPower_ = v > 0 ? v : nomPower_; + store_energy(); + has_update(&nomPower_); + return true; + } + return false; +} + } // namespace emsesp diff --git a/src/devices/boiler.h b/src/devices/boiler.h index 78b175cc4..c22d1896e 100644 --- a/src/devices/boiler.h +++ b/src/devices/boiler.h @@ -36,6 +36,7 @@ class Boiler : public EMSdevice { } void check_active(); + void store_energy(); uint8_t boilerState_ = EMS_VALUE_UINT_NOTSET; // Boiler state flag - FOR INTERNAL USE @@ -258,6 +259,13 @@ class Boiler : public EMSdevice { uint8_t keepWarmTemp_; uint8_t setReturnTemp_; + // special + double_t nrgHeatF_; + double_t nrgWwF_; + uint32_t nrgHeat_; + uint32_t nrgWw_; + uint8_t nomPower_; + /* // Hybrid heatpump with telegram 0xBB is readable and writeable in boiler and thermostat // thermostat always overwrites settings in boiler @@ -272,6 +280,7 @@ class Boiler : public EMSdevice { uint8_t tempDiffBoiler_; // relative temperature degrees */ + void process_UBAFactory(std::shared_ptr telegram); void process_UBAParameterWW(std::shared_ptr telegram); void process_UBAMonitorFast(std::shared_ptr telegram); void process_UBATotalUptime(std::shared_ptr telegram); @@ -468,6 +477,11 @@ class Boiler : public EMSdevice { bool set_delayBoiler(const char * value, const int8_t id); bool set_tempDiffBoiler(const char * value, const int8_t id); */ + + bool set_nrgHeat(const char * value, const int8_t id); + bool set_nrgWw(const char * value, const int8_t id); + bool set_nomPower(const char * value, const int8_t id); + }; } // namespace emsesp diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 9520499fc..5c8c7f0cd 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -931,7 +931,7 @@ void EMSdevice::generate_values_web(JsonObject & output) { } // handle INTs // add min and max values and steps, as integer values - else { + else if (dv.type != DeviceValueType::ULONG) { if (dv.numeric_operator > 0) { obj["s"] = (float)1 / dv.numeric_operator; } else if (dv.numeric_operator < 0) { @@ -1030,7 +1030,7 @@ void EMSdevice::generate_values_web_customization(JsonArray & output) { obj["m"] = dv.state >> 4; // send back the mask state. We're only interested in the high nibble obj["w"] = dv.has_cmd; // if writable - if (dv.has_cmd && (obj["v"].is() || obj["v"].is())) { + if (dv.has_cmd && dv.type != DeviceValueType::ULONG && (obj["v"].is() || obj["v"].is())) { // set the min and max values if there are any and if entity has a value int16_t dv_set_min; uint16_t dv_set_max; @@ -1767,6 +1767,11 @@ const char * EMSdevice::telegram_type_name(std::shared_ptr teleg bool EMSdevice::handle_telegram(std::shared_ptr telegram) { for (auto & tf : telegram_functions_) { if (tf.telegram_type_id_ == telegram->type_id) { + // for telegram desitnation only read telegram + if (telegram->dest == device_id_ && telegram->message_length > 0) { + tf.process_function_(telegram); + return true; + } // if the data block is empty and we have not received data before, assume that this telegram // is not recognized by the bus master. So remove it from the automatic fetch list if (telegram->message_length == 0 && telegram->offset == 0 && !tf.received_) { diff --git a/src/emsdevice.h b/src/emsdevice.h index 00ab27f1c..2ccfcdfdb 100644 --- a/src/emsdevice.h +++ b/src/emsdevice.h @@ -155,6 +155,22 @@ class EMSdevice { } } + inline void has_update(uint16_t & value, uint16_t newvalue) { + if (value != newvalue) { + value = newvalue; + has_update_ = true; + publish_value((void *)&value); + } + } + + inline void has_update(uint32_t & value, uint32_t newvalue) { + if (value != newvalue) { + value = newvalue; + has_update_ = true; + publish_value((void *)&value); + } + } + inline void has_enumupdate(std::shared_ptr telegram, uint8_t & value, const uint8_t index, int8_t s = 0) { if (telegram->read_enumvalue(value, index, s)) { has_update_ = true; diff --git a/src/emsesp.cpp b/src/emsesp.cpp index 67a591d11..354eebc5b 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -69,6 +69,7 @@ System EMSESP::system_; // core system services TemperatureSensor EMSESP::temperaturesensor_; // Temperature sensors AnalogSensor EMSESP::analogsensor_; // Analog sensors Shower EMSESP::shower_; // Shower logic +Preferences EMSESP::nvs_; // NV Storage // static/common variables uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; // for when log is TRACE. 0 means no trace set @@ -1485,6 +1486,7 @@ void EMSESP::start() { system_.system_restart(); }; + nvs_.begin("ems-esp"); webSettingsService.begin(); // load EMS-ESP Application settings... // do any system upgrades diff --git a/src/emsesp.h b/src/emsesp.h index e83287fde..1ce1e6261 100644 --- a/src/emsesp.h +++ b/src/emsesp.h @@ -36,6 +36,7 @@ #ifndef EMSESP_STANDALONE #include #endif +#include #include @@ -225,6 +226,7 @@ class EMSESP { static Shower shower_; static RxService rxservice_; static TxService txservice_; + static Preferences nvs_; // web controllers static ESP8266React esp8266React; diff --git a/src/locale_translations.h b/src/locale_translations.h index 4a9f2e314..a785b3827 100644 --- a/src/locale_translations.h +++ b/src/locale_translations.h @@ -500,6 +500,10 @@ MAKE_TRANSLATION(blockTerm, "blockterm", "config of block terminal", "Konfig. Sp MAKE_TRANSLATION(blockHyst, "blockhyst", "hyst. for boiler block", "Hysterese Sperrmodus", "Hysterese blokeerterminal", "Hysteres Blockeringsmodul", "tryb blokowania histerezy", "hystrese blokkeringsmodus", "hyst. Blocage chaudière", "kazan blok geçikmesi", "modalità blocco isteresi") MAKE_TRANSLATION(releaseWait, "releasewait", "boiler release wait time", "Wartezeit Kessel-Freigabe", "Wachttijd ketel vrijgave", "Väntetid Frisläppning", "czas oczekiwania na zwolnienie kotła", "kjele frigjøringsventetid", "temps attente libération chaudière", "kazan tahliyesi bekleme süresi", "tempo di attesa sblocco caldaia") +// energy +MAKE_TRANSLATION(nrgHeat, "nrgheat", "energy heating", "Energie Heizen", "", "", "", "", "", "", "") +MAKE_TRANSLATION(nrgWw, "nrgww", "energy dhw", "Energie Warmwasser", "", "", "", "", "", "", "") +MAKE_TRANSLATION(nomPower, "nompower", "nominal Power", "Brennerleistung", "", "", "", "", "", "", "") // HIU MAKE_TRANSLATION(netFlowTemp, "netflowtemp", "heat network flow temp", "System Vorlauftemperatur", "Netto aanvoertemperatuur", "", "", "", "", "", "temperatura di mandata della rete di riscaldamento") MAKE_TRANSLATION(cwFlowRate, "cwflowrate", "cold water flow rate", "Kaltwasser Durchfluss", "Stroomsnelheid koud water ", "", "", "", "", "", "portata acqua fredda") diff --git a/src/system.cpp b/src/system.cpp index a00c7e660..a0130aa70 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -231,9 +231,14 @@ bool System::command_watch(const char * value, const int8_t id) { return false; } +void System::store_boiler_energy() { + Command::call(EMSdevice::DeviceType::BOILER, "nompower", "-1"); // trigger a write +} + // restart EMS-ESP void System::system_restart() { LOG_INFO("Restarting EMS-ESP..."); + store_boiler_energy(); Shell::loop_all(); delay(1000); // wait a second #ifndef EMSESP_STANDALONE diff --git a/src/system.h b/src/system.h index 8d20cfb09..ce59895c8 100644 --- a/src/system.h +++ b/src/system.h @@ -66,6 +66,7 @@ class System { std::string reset_reason(uint8_t cpu) const; + void store_boiler_energy(); void system_restart(); void format(uuid::console::Shell & shell); void upload_status(bool in_progress);