diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 09a58c558..52ed4be07 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -455,7 +455,7 @@ void EMSdevice::register_device_value(uint8_t tag, if ((entityCustomization.product_id == product_id()) && (entityCustomization.device_id == device_id())) { for (uint8_t entity_id : entityCustomization.entity_ids) { if (entity_id == dv_id) { - state = DeviceValueState::DV_DEFAULT; // it's on the list, exclude it by making it not visible + state = DeviceValueState::DV_DEFAULT; // it's on the exclude list, turn off active and visible flags break; } } @@ -757,12 +757,23 @@ void EMSdevice::generate_values_web(JsonObject & output) { } } +// reset all entities to being visible +// this is called before loading in the exclude entities list from the customization service +void EMSdevice::reset_exclude_entities() { + for (auto & dv : devicevalues_) { + dv.add_state(DeviceValueState::DV_VISIBLE); + } +} + // disable/exclude a device entity based on its unique id void EMSdevice::exclude_entity(uint8_t id) { for (auto & dv : devicevalues_) { if (dv.id == id) { +#if defined(EMSESP_USE_SERIAL) + Serial.print("exclude_entity() Removing state for device value: "); + Serial.println(read_flash_string(dv.full_name).c_str()); +#endif dv.remove_state(DeviceValueState::DV_VISIBLE); // this will remove from MQTT payloads and showing in web & console - dv.remove_state(DeviceValueState::DV_ACTIVE); // this will ensure it'll be removed from any HA MQTT /config topics return; } } @@ -1007,7 +1018,7 @@ bool EMSdevice::get_value_info(JsonObject & output, const char * cmd, const int8 } json["writeable"] = dv.has_cmd; - json["visible"] = dv.has_state(DeviceValue::DeviceValueState::DV_VISIBLE); + json["visible"] = dv.has_state(DeviceValueState::DV_VISIBLE); // if there is no value, mention it if (!json.containsKey(value)) { @@ -1042,6 +1053,7 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c for (auto & dv : devicevalues_) { // check if it exists, there is a value for the entity. Set the flag to ACTIVE + // not that this will override any previously removed states if (dv.hasValue()) { dv.add_state(DeviceValueState::DV_ACTIVE); } else { @@ -1210,21 +1222,34 @@ void EMSdevice::publish_mqtt_ha_entity_config() { // create the main device config if not already done, per device type bool create_device_config = !ha_config_done(); + // check the state of each of the device values for (auto & dv : devicevalues_) { if (dv.has_state(DeviceValueState::DV_ACTIVE)) { - // add it if not already done and if it's visible (not on the exclusion list) - // don't do this for commands (like reset) - if (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) && dv.has_state(DeviceValueState::DV_VISIBLE) && dv.type != DeviceValueType::CMD) { - Mqtt::publish_ha_sensor_config(dv, name(), brand_to_string(), false, create_device_config); - dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED); - if (create_device_config) { - create_device_config = false; + // entity has an active value (it means it contains a valid value) + + if (dv.has_state(DeviceValueState::DV_VISIBLE)) { + // visible + // if the HA config topic hasn't been created it, do it now (unless its a command like reset) + if (!dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) && dv.type != DeviceValueType::CMD) { + Mqtt::publish_ha_sensor_config(dv, name(), brand_to_string(), false, create_device_config); + dv.add_state(DeviceValueState::DV_HA_CONFIG_CREATED); + if (create_device_config) { + create_device_config = false; + } + } + } else { + // not visible. It must be on the entity exclusion list defined in the Customizations service + // if a HA config topic was created then remove it. This entity has become 'lost' + if (dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED)) { + Mqtt::publish_ha_sensor_config(dv, name(), brand_to_string(), true, create_device_config); // remove /config + dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED); } } } else { + // entity does not have an active value + // if a HA config topic was created then remove it. This entity has become 'lost' + // https://github.com/emsesp/EMS-ESP32/issues/196 if (dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED)) { - // if the HA config has already been created and now the value has gone dormant, delete the config - // https://github.com/emsesp/EMS-ESP32/issues/196 Mqtt::publish_ha_sensor_config(dv, name(), brand_to_string(), true, create_device_config); // remove /config dv.remove_state(DeviceValueState::DV_HA_CONFIG_CREATED); } diff --git a/src/emsdevice.h b/src/emsdevice.h index a88b9df5b..1ae6b8fce 100644 --- a/src/emsdevice.h +++ b/src/emsdevice.h @@ -184,6 +184,7 @@ class EMSdevice { void show_mqtt_handlers(uuid::console::Shell & shell); void list_device_entries(JsonObject & output); void exclude_entity(uint8_t entity_id); + void reset_exclude_entities(); using process_function_p = std::function)>; diff --git a/src/emsdevicevalue.cpp b/src/emsdevicevalue.cpp index 037829b21..a8a9bfd34 100644 --- a/src/emsdevicevalue.cpp +++ b/src/emsdevicevalue.cpp @@ -171,13 +171,6 @@ bool DeviceValue::hasValue() { break; } -#if defined(EMSESP_DEBUG) - // https://github.com/emsesp/EMS-ESP32/issues/196 - // if (has_state(DeviceValueState::DV_ACTIVE) && !has_value) { - // emsesp::EMSESP::logger().warning(F("[DEBUG] Lost device value %s"), short_name); - // } -#endif - return has_value; } diff --git a/src/emsdevicevalue.h b/src/emsdevicevalue.h index 9b7512e7f..a9b4b029c 100644 --- a/src/emsdevicevalue.h +++ b/src/emsdevicevalue.h @@ -109,9 +109,9 @@ class DeviceValue { // states of a device value enum DeviceValueState : uint8_t { DV_DEFAULT = 0, // 0 - does not yet have a value - DV_ACTIVE = (1 << 0), // 1 - has a valid value - DV_VISIBLE = (1 << 1), // 2 - shown on web and console, otherwise hidden - DV_HA_CONFIG_CREATED = (1 << 2) // 4 - set if the HA config has been created + DV_ACTIVE = (1 << 0), // 1 - has a validated real value + DV_VISIBLE = (1 << 1), // 2 - shown on web, console and on MQTT payload. Otherwise hidden + DV_HA_CONFIG_CREATED = (1 << 2) // 4 - set if the HA config topic has been created }; uint8_t device_type; // EMSdevice::DeviceType diff --git a/src/emsesp.cpp b/src/emsesp.cpp index 9de7169a6..b6ec5f953 100644 --- a/src/emsesp.cpp +++ b/src/emsesp.cpp @@ -625,11 +625,11 @@ void EMSESP::publish_device_values(uint8_t device_type) { json = doc.to(); need_publish |= emsdevice->generate_values(json, DeviceValueTAG::TAG_NONE, true, EMSdevice::OUTPUT_TARGET::MQTT); // nested } - } - // we want to create the /config topic after the data payload to prevent HA from throwing up a warning - if (Mqtt::ha_enabled()) { - emsdevice->publish_mqtt_ha_entity_config(); + // we want to create the /config topic after the data payload to prevent HA from throwing up a warning + if (Mqtt::ha_enabled()) { + emsdevice->publish_mqtt_ha_entity_config(); + } } } diff --git a/src/test/test.cpp b/src/test/test.cpp index 7d5029b1c..6523a773e 100644 --- a/src/test/test.cpp +++ b/src/test/test.cpp @@ -595,9 +595,41 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const EMSESP::system_.healthcheck(n); } + if (command == "exclude") { + shell.printfln(F("Testing exclude entities")); + + Mqtt::ha_enabled(true); + Mqtt::send_response(false); + + run_test("boiler"); + + shell.invoke_command("call boiler wwseltemp"); + shell.invoke_command("call system publish"); + + // toggle mode + for (const auto & emsdevice : EMSESP::emsdevices) { + if (emsdevice->unique_id() == 1) { // boiler + uint8_t entity_id = 47; // wwseltemp + emsdevice->exclude_entity(entity_id); + break; + } + } + + shell.invoke_command("call boiler wwseltemp"); + shell.invoke_command("call system publish"); + } + if (command == "dv2") { shell.printfln(F("Testing device value lost")); + Mqtt::ha_enabled(true); + Mqtt::send_response(false); + + run_test("boiler"); + + shell.invoke_command("call boiler wwseltemp"); + shell.invoke_command("call system publish"); + // Boiler -> Me, UBAParameterWW(0x33) // wwseltemp = goes from 52 degrees (0x34) to void (0xFF) // it should delete the HA config topic homeassistant/sensor/ems-esp/boiler_wwseltemp/config @@ -608,6 +640,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const } if (command == "api_values") { +#ifndef EMSESP_DEBUG shell.printfln(F("Testing API getting values")); Mqtt::ha_enabled(false); Mqtt::nested_format(1); @@ -626,6 +659,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & cmd, const EMSESP::webAPIService.webAPIService_get(&request); request.url("/api/boiler/values"); EMSESP::webAPIService.webAPIService_get(&request); +#endif } if (command == "mqtt_post") { diff --git a/src/test/test.h b/src/test/test.h index 58a2321de..f949b5f3b 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -35,7 +35,8 @@ namespace emsesp { // #define EMSESP_DEBUG_DEFAULT "boiler" // #define EMSESP_DEBUG_DEFAULT "mqtt2" // #define EMSESP_DEBUG_DEFAULT "mqtt_nested" -#define EMSESP_DEBUG_DEFAULT "ha" +// #define EMSESP_DEBUG_DEFAULT "ha" +#define EMSESP_DEBUG_DEFAULT "exclude" // #define EMSESP_DEBUG_DEFAULT "board_profile" // #define EMSESP_DEBUG_DEFAULT "shower_alert" // #define EMSESP_DEBUG_DEFAULT "310" diff --git a/src/web/WebCustomizationService.cpp b/src/web/WebCustomizationService.cpp index 0a2498150..a2a0103f0 100644 --- a/src/web/WebCustomizationService.cpp +++ b/src/web/WebCustomizationService.cpp @@ -216,13 +216,16 @@ void WebCustomizationService::exclude_entities(AsyncWebServerRequest * request, if (emsdevice) { uint8_t unique_device_id = json["id"]; if (emsdevice->unique_id() == unique_device_id) { - JsonArray entity_ids = json["entity_ids"]; + // first reset all the entity ids + emsdevice->reset_exclude_entities(); - std::vector temp; - for (JsonVariant id : entity_ids) { + // build a list of entities to exclude and then set the flag to non-visible + JsonArray entity_ids_json = json["entity_ids"]; + std::vector entity_ids; + for (JsonVariant id : entity_ids_json) { uint8_t entity_id = id.as(); emsdevice->exclude_entity(entity_id); // this will have immediate affect - temp.push_back(entity_id); + entity_ids.push_back(entity_id); } // Save the list to the customization file @@ -236,8 +239,8 @@ void WebCustomizationService::exclude_entities(AsyncWebServerRequest * request, if ((entityCustomization.product_id == product_id) && (entityCustomization.device_id == device_id)) { // already exists, clear the list and add the new values entityCustomization.entity_ids.clear(); - for (uint8_t i = 0; i < temp.size(); i++) { - entityCustomization.entity_ids.push_back(temp[i]); + for (uint8_t i = 0; i < entity_ids.size(); i++) { + entityCustomization.entity_ids.push_back(entity_ids[i]); } return StateUpdateResult::CHANGED; } @@ -246,8 +249,8 @@ void WebCustomizationService::exclude_entities(AsyncWebServerRequest * request, EntityCustomization new_entry; new_entry.product_id = product_id; new_entry.device_id = device_id; - for (uint8_t i = 0; i < temp.size(); i++) { - new_entry.entity_ids.push_back(temp[i]); + for (uint8_t i = 0; i < entity_ids.size(); i++) { + new_entry.entity_ids.push_back(entity_ids[i]); } settings.entityCustomizations.push_back(new_entry); return StateUpdateResult::CHANGED;