boiler energy counter, stored in nvs

This commit is contained in:
MichaelDvP
2023-09-03 17:53:48 +02:00
parent 4c1b66279d
commit bd92345793
11 changed files with 173 additions and 3 deletions

View File

@@ -1,6 +1,8 @@
#include <RestartService.h> #include <RestartService.h>
#include <esp_ota_ops.h> #include <esp_ota_ops.h>
#include "../../src/emsesp_stub.hpp"
using namespace std::placeholders; // for `_1` etc using namespace std::placeholders; // for `_1` etc
RestartService::RestartService(AsyncWebServer * server, SecurityManager * securityManager) { RestartService::RestartService(AsyncWebServer * server, SecurityManager * securityManager) {
@@ -11,6 +13,7 @@ RestartService::RestartService(AsyncWebServer * server, SecurityManager * securi
} }
void RestartService::restart(AsyncWebServerRequest * request) { void RestartService::restart(AsyncWebServerRequest * request) {
emsesp::EMSESP::system_.store_boiler_energy();
request->onDisconnect(RestartService::restartNow); request->onDisconnect(RestartService::restartNow);
request->send(200); 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); const esp_partition_t * factory_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL);
if (factory_partition) { if (factory_partition) {
esp_ota_set_boot_partition(factory_partition); esp_ota_set_boot_partition(factory_partition);
emsesp::EMSESP::system_.store_boiler_energy();
request->onDisconnect(RestartService::restartNow); request->onDisconnect(RestartService::restartNow);
request->send(200); request->send(200);
return; return;
@@ -35,6 +39,7 @@ void RestartService::partition(AsyncWebServerRequest * request) {
return; return;
} }
esp_ota_set_boot_partition(ota_partition); esp_ota_set_boot_partition(ota_partition);
emsesp::EMSESP::system_.store_boiler_energy();
request->onDisconnect(RestartService::restartNow); request->onDisconnect(RestartService::restartNow);
request->send(200); request->send(200);
} }

View File

@@ -1,6 +1,8 @@
#include <UploadFileService.h> #include <UploadFileService.h>
#include <esp_ota_ops.h> #include <esp_ota_ops.h>
#include "../../src/emsesp_stub.hpp"
using namespace std::placeholders; // for `_1` etc using namespace std::placeholders; // for `_1` etc
static bool is_firmware = false; static bool is_firmware = false;
@@ -112,6 +114,7 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
// did we complete uploading a json file? // did we complete uploading a json file?
if (request->_tempFile) { if (request->_tempFile) {
request->_tempFile.close(); // close the file handle as the upload is now done request->_tempFile.close(); // close the file handle as the upload is now done
emsesp::EMSESP::system_.store_boiler_energy();
request->onDisconnect(RestartService::restartNow); request->onDisconnect(RestartService::restartNow);
AsyncWebServerResponse * response = request->beginResponse(200); AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response); request->send(response);
@@ -121,6 +124,7 @@ void UploadFileService::uploadComplete(AsyncWebServerRequest * request) {
// check if it was a firmware upgrade // check if it was a firmware upgrade
// if no error, send the success response as a JSON // if no error, send the success response as a JSON
if (is_firmware && !request->_tempObject) { if (is_firmware && !request->_tempObject) {
emsesp::EMSESP::system_.store_boiler_energy();
request->onDisconnect(RestartService::restartNow); request->onDisconnect(RestartService::restartNow);
AsyncWebServerResponse * response = request->beginResponse(200); AsyncWebServerResponse * response = request->beginResponse(200);
request->send(response); request->send(response);

View File

@@ -750,7 +750,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
register_device_value( register_device_value(
DeviceValueTAG::TAG_BOILER_DATA_WW, &wwMaxPower_, DeviceValueType::UINT, FL_(wwMaxPower), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_ww_maxpower), 0, 254); DeviceValueTAG::TAG_BOILER_DATA_WW, &wwMaxPower_, DeviceValueType::UINT, FL_(wwMaxPower), DeviceValueUOM::PERCENT, MAKE_CF_CB(set_ww_maxpower), 0, 254);
register_device_value( 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, register_device_value(DeviceValueTAG::TAG_BOILER_DATA_WW,
&wwCircPump_, &wwCircPump_,
DeviceValueType::BOOL, 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(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(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) 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 // 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)); 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 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<const Telegram> 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 // 0x18
@@ -2642,4 +2723,35 @@ bool Boiler::set_wwAltOpPrio(const char * value, const int8_t id) {
return false; 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 } // namespace emsesp

View File

@@ -36,6 +36,7 @@ class Boiler : public EMSdevice {
} }
void check_active(); void check_active();
void store_energy();
uint8_t boilerState_ = EMS_VALUE_UINT_NOTSET; // Boiler state flag - FOR INTERNAL USE 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 keepWarmTemp_;
uint8_t setReturnTemp_; 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 // Hybrid heatpump with telegram 0xBB is readable and writeable in boiler and thermostat
// thermostat always overwrites settings in boiler // thermostat always overwrites settings in boiler
@@ -272,6 +280,7 @@ class Boiler : public EMSdevice {
uint8_t tempDiffBoiler_; // relative temperature degrees uint8_t tempDiffBoiler_; // relative temperature degrees
*/ */
void process_UBAFactory(std::shared_ptr<const Telegram> telegram);
void process_UBAParameterWW(std::shared_ptr<const Telegram> telegram); void process_UBAParameterWW(std::shared_ptr<const Telegram> telegram);
void process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram); void process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram);
void process_UBATotalUptime(std::shared_ptr<const Telegram> telegram); void process_UBATotalUptime(std::shared_ptr<const Telegram> telegram);
@@ -468,6 +477,11 @@ class Boiler : public EMSdevice {
bool set_delayBoiler(const char * value, const int8_t id); bool set_delayBoiler(const char * value, const int8_t id);
bool set_tempDiffBoiler(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 } // namespace emsesp

View File

@@ -931,7 +931,7 @@ void EMSdevice::generate_values_web(JsonObject & output) {
} }
// handle INTs // handle INTs
// add min and max values and steps, as integer values // add min and max values and steps, as integer values
else { else if (dv.type != DeviceValueType::ULONG) {
if (dv.numeric_operator > 0) { if (dv.numeric_operator > 0) {
obj["s"] = (float)1 / dv.numeric_operator; obj["s"] = (float)1 / dv.numeric_operator;
} else if (dv.numeric_operator < 0) { } 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["m"] = dv.state >> 4; // send back the mask state. We're only interested in the high nibble
obj["w"] = dv.has_cmd; // if writable obj["w"] = dv.has_cmd; // if writable
if (dv.has_cmd && (obj["v"].is<float>() || obj["v"].is<int>())) { if (dv.has_cmd && dv.type != DeviceValueType::ULONG && (obj["v"].is<float>() || obj["v"].is<int>())) {
// set the min and max values if there are any and if entity has a value // set the min and max values if there are any and if entity has a value
int16_t dv_set_min; int16_t dv_set_min;
uint16_t dv_set_max; uint16_t dv_set_max;
@@ -1767,6 +1767,11 @@ const char * EMSdevice::telegram_type_name(std::shared_ptr<const Telegram> teleg
bool EMSdevice::handle_telegram(std::shared_ptr<const Telegram> telegram) { bool EMSdevice::handle_telegram(std::shared_ptr<const Telegram> telegram) {
for (auto & tf : telegram_functions_) { for (auto & tf : telegram_functions_) {
if (tf.telegram_type_id_ == telegram->type_id) { 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 // 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 // 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_) { if (telegram->message_length == 0 && telegram->offset == 0 && !tf.received_) {

View File

@@ -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<const Telegram> telegram, uint8_t & value, const uint8_t index, int8_t s = 0) { inline void has_enumupdate(std::shared_ptr<const Telegram> telegram, uint8_t & value, const uint8_t index, int8_t s = 0) {
if (telegram->read_enumvalue(value, index, s)) { if (telegram->read_enumvalue(value, index, s)) {
has_update_ = true; has_update_ = true;

View File

@@ -69,6 +69,7 @@ System EMSESP::system_; // core system services
TemperatureSensor EMSESP::temperaturesensor_; // Temperature sensors TemperatureSensor EMSESP::temperaturesensor_; // Temperature sensors
AnalogSensor EMSESP::analogsensor_; // Analog sensors AnalogSensor EMSESP::analogsensor_; // Analog sensors
Shower EMSESP::shower_; // Shower logic Shower EMSESP::shower_; // Shower logic
Preferences EMSESP::nvs_; // NV Storage
// static/common variables // static/common variables
uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; // for when log is TRACE. 0 means no trace set 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(); system_.system_restart();
}; };
nvs_.begin("ems-esp");
webSettingsService.begin(); // load EMS-ESP Application settings... webSettingsService.begin(); // load EMS-ESP Application settings...
// do any system upgrades // do any system upgrades

View File

@@ -36,6 +36,7 @@
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
#include <uuid/telnet.h> #include <uuid/telnet.h>
#endif #endif
#include <Preferences.h>
#include <ESP8266React.h> #include <ESP8266React.h>
@@ -225,6 +226,7 @@ class EMSESP {
static Shower shower_; static Shower shower_;
static RxService rxservice_; static RxService rxservice_;
static TxService txservice_; static TxService txservice_;
static Preferences nvs_;
// web controllers // web controllers
static ESP8266React esp8266React; static ESP8266React esp8266React;

View File

@@ -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(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") 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 // HIU
MAKE_TRANSLATION(netFlowTemp, "netflowtemp", "heat network flow temp", "System Vorlauftemperatur", "Netto aanvoertemperatuur", "", "", "", "", "", "temperatura di mandata della rete di riscaldamento") 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") MAKE_TRANSLATION(cwFlowRate, "cwflowrate", "cold water flow rate", "Kaltwasser Durchfluss", "Stroomsnelheid koud water ", "", "", "", "", "", "portata acqua fredda")

View File

@@ -231,9 +231,14 @@ bool System::command_watch(const char * value, const int8_t id) {
return false; return false;
} }
void System::store_boiler_energy() {
Command::call(EMSdevice::DeviceType::BOILER, "nompower", "-1"); // trigger a write
}
// restart EMS-ESP // restart EMS-ESP
void System::system_restart() { void System::system_restart() {
LOG_INFO("Restarting EMS-ESP..."); LOG_INFO("Restarting EMS-ESP...");
store_boiler_energy();
Shell::loop_all(); Shell::loop_all();
delay(1000); // wait a second delay(1000); // wait a second
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE

View File

@@ -66,6 +66,7 @@ class System {
std::string reset_reason(uint8_t cpu) const; std::string reset_reason(uint8_t cpu) const;
void store_boiler_energy();
void system_restart(); void system_restart();
void format(uuid::console::Shell & shell); void format(uuid::console::Shell & shell);
void upload_status(bool in_progress); void upload_status(bool in_progress);