diff --git a/lib/framework/RestartService.cpp b/lib/framework/RestartService.cpp index 157f26dfb..b767b9cf0 100644 --- a/lib/framework/RestartService.cpp +++ b/lib/framework/RestartService.cpp @@ -13,7 +13,7 @@ RestartService::RestartService(AsyncWebServer * server, SecurityManager * securi } void RestartService::restart(AsyncWebServerRequest * request) { - emsesp::EMSESP::system_.store_boiler_energy(); + emsesp::EMSESP::system_.store_nvs_values(); request->onDisconnect(RestartService::restartNow); request->send(200); } @@ -22,7 +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(); + emsesp::EMSESP::system_.store_nvs_values(); request->onDisconnect(RestartService::restartNow); request->send(200); return; @@ -39,7 +39,7 @@ void RestartService::partition(AsyncWebServerRequest * request) { return; } esp_ota_set_boot_partition(ota_partition); - emsesp::EMSESP::system_.store_boiler_energy(); + emsesp::EMSESP::system_.store_nvs_values(); request->onDisconnect(RestartService::restartNow); request->send(200); } diff --git a/lib/framework/UploadFileService.cpp b/lib/framework/UploadFileService.cpp index d6840d211..91428b8eb 100644 --- a/lib/framework/UploadFileService.cpp +++ b/lib/framework/UploadFileService.cpp @@ -114,7 +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(); + emsesp::EMSESP::system_.store_nvs_values(); request->onDisconnect(RestartService::restartNow); AsyncWebServerResponse * response = request->beginResponse(200); request->send(response); @@ -124,7 +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(); + emsesp::EMSESP::system_.store_nvs_values(); request->onDisconnect(RestartService::restartNow); AsyncWebServerResponse * response = request->beginResponse(200); request->send(response); diff --git a/src/analogsensor.cpp b/src/analogsensor.cpp index c071b1ffe..b8e3bdbd7 100644 --- a/src/analogsensor.cpp +++ b/src/analogsensor.cpp @@ -277,8 +277,8 @@ void AnalogSensor::measure() { } else if (!sensor.poll_) { // falling edge if (sensor.type() == AnalogType::COUNTER) { sensor.set_value(old_value + sensor.factor()); - EMSESP::nvs_.putDouble(sensor.name().c_str(), sensor.value()); - } else if (sensor.type() == AnalogType::RATE) { // dafault uom: Hz (1/sec) with factor 1 + // EMSESP::nvs_.putDouble(sensor.name().c_str(), sensor.value()); + } else if (sensor.type() == AnalogType::RATE) { // default uom: Hz (1/sec) with factor 1 sensor.set_value(sensor.factor() * 1000 / (sensor.polltime_ - sensor.last_polltime_)); } else if (sensor.type() == AnalogType::TIMER) { // default seconds with factor 1 sensor.set_value(sensor.factor() * (sensor.polltime_ - sensor.last_polltime_) / 1000); @@ -294,6 +294,25 @@ void AnalogSensor::measure() { } } } + // store counter-values only every hour to reduce flash wear + static uint8_t lastSaveHour = 0; + time_t now = time(nullptr); + tm * tm_ = localtime(&now); + if (tm_->tm_hour != lastSaveHour) { + lastSaveHour = tm_->tm_hour; + store_counters(); + } +} + +// store counters to NVS, called every hour, on restart and update +void AnalogSensor::store_counters() { + for (auto & sensor : sensors_) { + if (sensor.type() == AnalogType::COUNTER) { + if (sensor.value() != EMSESP::nvs_.getDouble(sensor.name().c_str())) { + EMSESP::nvs_.putDouble(sensor.name().c_str(), sensor.value()); + } + } + } } void AnalogSensor::loop() { diff --git a/src/analogsensor.h b/src/analogsensor.h index e1ecb67cf..cc931539b 100644 --- a/src/analogsensor.h +++ b/src/analogsensor.h @@ -158,6 +158,7 @@ class AnalogSensor { bool update(uint8_t gpio, const std::string & name, double offset, double factor, uint8_t uom, int8_t type, bool deleted = false); bool get_value_info(JsonObject & output, const char * cmd, const int8_t id) const; + void store_counters(); #if defined(EMSESP_TEST) void test(); diff --git a/src/devices/boiler.cpp b/src/devices/boiler.cpp index 48263d012..3264f3002 100644 --- a/src/devices/boiler.cpp +++ b/src/devices/boiler.cpp @@ -850,18 +850,30 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const 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_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)); 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); + nomPower_ = EMSESP::nvs_.getUChar(FL_(nomPower)[0], 0); + if (nrgHeatF_ < 0 || nrgHeatF_ >= EMS_VALUE_ULONG_NOTSET) { + nrgHeatF_ = 0; + } + if (nrgWwF_ < 0 || nrgWwF_ >= EMS_VALUE_ULONG_NOTSET) { + nrgWwF_ = 0; + } + if (nomPower_ == EMS_VALUE_UINT_NOTSET) { + nomPower_ = 0; + } + store_energy(); // update/publish the values has_update(nrgHeat_, (uint32_t)nrgHeatF_); has_update(nrgWw_, (uint32_t)nrgWwF_); + has_update(&nomPower_); } } @@ -927,13 +939,7 @@ void Boiler::check_active() { 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 + // resolution needed: 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_)); @@ -962,19 +968,10 @@ void Boiler::process_UBAFactory(std::shared_ptr telegram) { 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_); - } + if (nomPower > 0) { has_update(nomPower_, nomPower); - toggle_fetch(telegram->type_id, false); // only read once - // LOG_DEBUG("nominal power set to %d", nomPower_); } + toggle_fetch(telegram->type_id, false); // only read once } // 0x18 @@ -2723,35 +2720,43 @@ bool Boiler::set_wwAltOpPrio(const char * value, const int8_t id) { return false; } +// energy counters. Setting an invalid value does not update, but trigger a store. 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; + if (!Helpers::value2number(value, v)) { + return false; } - return false; + if (v >= 0 && v < EMS_VALUE_ULONG_NOTSET) { + nrgHeatF_ = v; + has_update(nrgHeat_, (uint32_t)nrgHeatF_); + } + store_energy(); + return true; } 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; + if (!Helpers::value2number(value, v)) { + return false; } - return false; + if (v >= 0 && v < EMS_VALUE_ULONG_NOTSET) { + nrgWwF_ = v; + has_update(nrgWw_, (uint32_t)nrgWwF_); + } + store_energy(); + return true; } 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; + if (!Helpers::value2number(value, v)) { + return false; } - return false; + if (v > 0 && v < EMS_VALUE_UINT_NOTSET) { + has_update(nomPower_, (uint8_t)v); + } + store_energy(); + return true; } } // namespace emsesp diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 5c8c7f0cd..41355e66e 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -488,7 +488,7 @@ void EMSdevice::add_device_value(uint8_t tag, // to b uint8_t uom, // unit of measure from DeviceValueUOM const cmd_function_p f, // command function pointer int16_t min, // min allowed value - uint16_t max // max allowed value + uint32_t max // max allowed value ) { // initialize the device value depending on it's type // ignoring DeviceValueType::CMD and DeviceValueType::TIME @@ -605,7 +605,7 @@ void EMSdevice::register_device_value(uint8_t tag, uint8_t uom, const cmd_function_p f, int16_t min, - uint16_t max) { + uint32_t max) { // create a multi-list from the options add_device_value(tag, value_p, type, nullptr, options_single, 0, name, uom, f, min, max); }; @@ -628,7 +628,7 @@ void EMSdevice::register_device_value(uint8_t tag, uint8_t uom, const cmd_function_p f, int16_t min, - uint16_t max) { + uint32_t max) { add_device_value(tag, value_p, type, nullptr, nullptr, numeric_operator, name, uom, f, min, max); } @@ -645,7 +645,7 @@ void EMSdevice::register_device_value(uint8_t tag, uint8_t uom, const cmd_function_p f, int16_t min, - uint16_t max) { + uint32_t max) { add_device_value(tag, value_p, type, nullptr, nullptr, 0, name, uom, f, min, max); }; @@ -660,7 +660,7 @@ void EMSdevice::register_device_value(uint8_t tag, uint8_t uom, const cmd_function_p f, int16_t min, - uint16_t max) { + uint32_t max) { add_device_value(tag, value_p, type, options, nullptr, 0, name, uom, f, min, max); } @@ -931,7 +931,7 @@ void EMSdevice::generate_values_web(JsonObject & output) { } // handle INTs // add min and max values and steps, as integer values - else if (dv.type != DeviceValueType::ULONG) { + else { if (dv.numeric_operator > 0) { obj["s"] = (float)1 / dv.numeric_operator; } else if (dv.numeric_operator < 0) { @@ -939,7 +939,7 @@ void EMSdevice::generate_values_web(JsonObject & output) { } int16_t dv_set_min; - uint16_t dv_set_max; + uint32_t dv_set_max; if (dv.get_min_max(dv_set_min, dv_set_max)) { obj["m"] = dv_set_min; obj["x"] = dv_set_max; @@ -1030,10 +1030,10 @@ 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 && dv.type != DeviceValueType::ULONG && (obj["v"].is() || obj["v"].is())) { + if (dv.has_cmd && (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; + uint32_t dv_set_max; if (dv.get_min_max(dv_set_min, dv_set_max)) { obj["mi"] = dv_set_min; obj["ma"] = dv_set_max; @@ -1059,7 +1059,7 @@ void EMSdevice::generate_values_web_customization(JsonArray & output) { }); } -void EMSdevice::set_climate_minmax(uint8_t tag, int16_t min, uint16_t max) { +void EMSdevice::set_climate_minmax(uint8_t tag, int16_t min, uint32_t max) { for (auto & dv : devicevalues_) { if (dv.tag == tag && (strcmp(dv.short_name, FL_(haclimate[0])) == 0)) { if (dv.min != min || dv.max != max) { @@ -1236,7 +1236,7 @@ void EMSdevice::dump_value_info() { // min/max range int16_t dv_set_min; - uint16_t dv_set_max; + uint32_t dv_set_max; if (dv.get_min_max(dv_set_min, dv_set_max)) { Serial.print(" (>="); Serial.print(dv_set_min); @@ -1473,7 +1473,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8 // set the min and max only for commands if (dv.has_cmd) { int16_t dv_set_min; - uint16_t dv_set_max; + uint32_t dv_set_max; if (dv.get_min_max(dv_set_min, dv_set_max)) { json["min"] = dv_set_min; json["max"] = dv_set_max; diff --git a/src/emsdevice.h b/src/emsdevice.h index 2ccfcdfdb..38ada1b7a 100644 --- a/src/emsdevice.h +++ b/src/emsdevice.h @@ -206,7 +206,7 @@ class EMSdevice { void list_device_entries(JsonObject & output) const; void add_handlers_ignored(const uint16_t handler); - void set_climate_minmax(uint8_t tag, int16_t min, uint16_t max); + void set_climate_minmax(uint8_t tag, int16_t min, uint32_t max); void setCustomEntity(const std::string & entity_id); void getCustomEntities(std::vector & entity_ids); @@ -232,7 +232,7 @@ class EMSdevice { uint8_t uom, const cmd_function_p f, int16_t min, - uint16_t max); + uint32_t max); void register_device_value(uint8_t tag, void * value_p, @@ -242,7 +242,7 @@ class EMSdevice { uint8_t uom, const cmd_function_p f, int16_t min, - uint16_t max); + uint32_t max); void register_device_value(uint8_t tag, void * value_p, uint8_t type, const char * const ** options, const char * const * name, uint8_t uom, const cmd_function_p f); @@ -265,7 +265,7 @@ class EMSdevice { uint8_t uom, const cmd_function_p f, int16_t min, - uint16_t max); + uint32_t max); // single list of options void register_device_value(uint8_t tag, @@ -285,14 +285,14 @@ class EMSdevice { uint8_t uom, const cmd_function_p f, int16_t min, - uint16_t max); + uint32_t max); // no options, optional function f void register_device_value(uint8_t tag, void * value_p, uint8_t type, const char * const * name, uint8_t uom, const cmd_function_p f = nullptr); // no options, with min/max void - register_device_value(uint8_t tag, void * value_p, uint8_t type, const char * const * name, uint8_t uom, const cmd_function_p f, int16_t min, uint16_t max); + register_device_value(uint8_t tag, void * value_p, uint8_t type, const char * const * name, uint8_t uom, const cmd_function_p f, int16_t min, uint32_t max); void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid) const; void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid) const; diff --git a/src/emsdevicevalue.cpp b/src/emsdevicevalue.cpp index 30e742f1b..0d0561f5c 100644 --- a/src/emsdevicevalue.cpp +++ b/src/emsdevicevalue.cpp @@ -36,7 +36,7 @@ DeviceValue::DeviceValue(uint8_t device_type, uint8_t uom, bool has_cmd, int16_t min, - uint16_t max, + uint32_t max, uint8_t state) : device_type(device_type) , tag(tag) @@ -261,7 +261,7 @@ bool DeviceValue::has_tag() const { // converts to signed int, which means rounding to an whole integer // returns false if there is no min/max needed // Types BOOL, ENUM, STRING and CMD are not used -bool DeviceValue::get_min_max(int16_t & dv_set_min, uint16_t & dv_set_max) { +bool DeviceValue::get_min_max(int16_t & dv_set_min, uint32_t & dv_set_max) { uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (uom == DeviceValueUOM::DEGREES) ? 2 : (uom == DeviceValueUOM::DEGREES_R) ? 1 : 0; // if we have individual limits set already, just do the conversion @@ -340,7 +340,7 @@ bool DeviceValue::get_custom_min(int16_t & val) { } // extract custom max from custom_fullname -bool DeviceValue::get_custom_max(uint16_t & val) { +bool DeviceValue::get_custom_max(uint32_t & val) { auto max_pos = custom_fullname.find('<'); bool has_max = (max_pos != std::string::npos); uint8_t fahrenheit = !EMSESP::system_.fahrenheit() ? 0 : (uom == DeviceValueUOM::DEGREES) ? 2 : (uom == DeviceValueUOM::DEGREES_R) ? 1 : 0; diff --git a/src/emsdevicevalue.h b/src/emsdevicevalue.h index 5ea551004..13bacba41 100644 --- a/src/emsdevicevalue.h +++ b/src/emsdevicevalue.h @@ -159,7 +159,7 @@ class DeviceValue { uint8_t uom; // DeviceValueUOM::* bool has_cmd; // true if there is a Console/MQTT command which matches the short_name int16_t min; // min range - uint16_t max; // max range + uint32_t max; // max range uint8_t state; // DeviceValueState::* DeviceValue(uint8_t device_type, @@ -175,16 +175,16 @@ class DeviceValue { uint8_t uom, bool has_cmd, int16_t min, - uint16_t max, + uint32_t max, uint8_t state); bool hasValue() const; bool has_tag() const; - bool get_min_max(int16_t & dv_set_min, uint16_t & dv_set_max); + bool get_min_max(int16_t & dv_set_min, uint32_t & dv_set_max); void set_custom_minmax(); bool get_custom_min(int16_t & val); - bool get_custom_max(uint16_t & val); + bool get_custom_max(uint32_t & val); std::string get_custom_fullname() const; std::string get_fullname() const; static std::string get_name(std::string & entity); diff --git a/src/mqtt.cpp b/src/mqtt.cpp index 7fc766964..f86a067f1 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -737,7 +737,7 @@ bool Mqtt::publish_ha_sensor_config(DeviceValue & dv, const char * model, const // calculate the min and max int16_t dv_set_min; - uint16_t dv_set_max; + uint32_t dv_set_max; (void)dv.get_min_max(dv_set_min, dv_set_max); // determine if we're creating the command topics which we use special HA configs @@ -788,7 +788,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev const char * const ** options, uint8_t options_size, const int16_t dv_set_min, - const int16_t dv_set_max, + const uint32_t dv_set_max, const int8_t num_op, const JsonObject & dev_json) { // ignore if name (fullname) is empty @@ -1117,7 +1117,7 @@ bool Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev return queue_ha(topic, doc.as()); } -bool Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp, const bool remove, const int16_t min, const uint16_t max) { +bool Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp, const bool remove, const int16_t min, const uint32_t max) { uint8_t hc_num = tag - DeviceValueTAG::TAG_HC1 + 1; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; diff --git a/src/mqtt.h b/src/mqtt.h index 8a02c0903..0048da7fb 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -87,12 +87,12 @@ class Mqtt { const char * const ** options, uint8_t options_size, const int16_t dv_set_min, - const int16_t dv_set_max, + const uint32_t dv_set_max, const int8_t num_op, const JsonObject & dev_json); static bool publish_system_ha_sensor_config(uint8_t type, const char * name, const char * entity, const uint8_t uom); - static bool publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp, const bool remove = false, const int16_t min = 5, const uint16_t max = 30); + static bool publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp, const bool remove = false, const int16_t min = 5, const uint32_t max = 30); static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_type); static void show_mqtt(uuid::console::Shell & shell); diff --git a/src/system.cpp b/src/system.cpp index a0130aa70..53a448b6e 100644 --- a/src/system.cpp +++ b/src/system.cpp @@ -231,14 +231,15 @@ bool System::command_watch(const char * value, const int8_t id) { return false; } -void System::store_boiler_energy() { +void System::store_nvs_values() { Command::call(EMSdevice::DeviceType::BOILER, "nompower", "-1"); // trigger a write + EMSESP::analogsensor_.store_counters(); } // restart EMS-ESP void System::system_restart() { LOG_INFO("Restarting EMS-ESP..."); - store_boiler_energy(); + store_nvs_values(); Shell::loop_all(); delay(1000); // wait a second #ifndef EMSESP_STANDALONE diff --git a/src/system.h b/src/system.h index ce59895c8..a4cb6d5f6 100644 --- a/src/system.h +++ b/src/system.h @@ -66,7 +66,7 @@ class System { std::string reset_reason(uint8_t cpu) const; - void store_boiler_energy(); + void store_nvs_values(); void system_restart(); void format(uuid::console::Shell & shell); void upload_status(bool in_progress); diff --git a/src/version.h b/src/version.h index def7251fc..713c08869 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define EMSESP_APP_VERSION "3.6.1-dev.0b" +#define EMSESP_APP_VERSION "3.6.1-dev.0c"