From 9b0a7a48723834fff58d687fc37f04f174bc11aa Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 18 May 2025 11:46:28 +0200 Subject: [PATCH 1/5] AsyncTCP 3.4.1 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 40c2c13c4..27e03f791 100644 --- a/platformio.ini +++ b/platformio.ini @@ -101,7 +101,7 @@ build_type = release board_build.filesystem = littlefs lib_deps = bblanchon/ArduinoJson @ 7.4.1 - ESP32Async/AsyncTCP @ 3.4.0 + ESP32Async/AsyncTCP @ 3.4.1 ESP32Async/ESPAsyncWebServer @ 3.7.7 https://github.com/emsesp/EMS-ESP-Modules.git @ 1.0.7 From 9ddc587334696713117139ec4da7d8e6868f779a Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 18 May 2025 11:46:45 +0200 Subject: [PATCH 2/5] add comment --- src/core/system.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/system.cpp b/src/core/system.cpp index 253700003..559905a01 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -598,6 +598,7 @@ void System::loop() { } // send MQTT info topic appended with the version information as JSON, as a retained flag +// this is only done once when the connection is established void System::send_info_mqtt() { static uint8_t _connection = 0; uint8_t connection = (ethernet_connected() ? 1 : 0) + ((WiFi.status() == WL_CONNECTED) ? 2 : 0) + (ntp_connected_ ? 4 : 0) + (has_ipv6_ ? 8 : 0); @@ -616,7 +617,7 @@ void System::send_info_mqtt() { char time_string[25]; time_t now = time(nullptr) - uuid::get_uptime_sec(); strftime(time_string, 25, "%FT%T%z", localtime(&now)); - doc["boot time"] = time_string; + doc["bootTime"] = time_string; } #ifndef EMSESP_STANDALONE @@ -655,7 +656,7 @@ void System::send_info_mqtt() { #endif } #endif - Mqtt::queue_publish_retain(F_(info), doc.as(), true); // topic called "info" and it's Retained + Mqtt::queue_publish_retain(F_(info), doc.as()); // topic called "info" and it's Retained } // create the json for heartbeat From 5c473c2b3d44c77a417835d3d6349f08288d1c82 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 18 May 2025 11:47:04 +0200 Subject: [PATCH 3/5] shower active is shown in HA on EMS-ESP boot --- src/core/shower.cpp | 43 +++++++++++++++++++++++++------------------ src/core/shower.h | 1 + 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/core/shower.cpp b/src/core/shower.cpp index 6905b50cf..d7529e807 100644 --- a/src/core/shower.cpp +++ b/src/core/shower.cpp @@ -50,10 +50,6 @@ void Shower::start() { }, FL_(coldshot_cmd), CommandFlag::ADMIN_ONLY); - - if (shower_timer_) { - set_shower_state(false, true); // turns shower to off and creates HA topic if not already done - } } void Shower::loop() { @@ -61,6 +57,17 @@ void Shower::loop() { return; } + // if haven't done already send the MQTT topic shower_active with the current state + // which creates the HA Discovery topic + static bool mqtt_sent_ = false; + if (!mqtt_sent_) { + if (Mqtt::connected()) { + create_ha_discovery(); + set_shower_state(shower_state_, true); // force publish initial state + mqtt_sent_ = true; + } + } + auto time_now = uuid::get_uptime_sec(); // in sec // if already in cold mode, ignore all this logic until we're out of the cold blast @@ -161,9 +168,7 @@ void Shower::shower_alert_stop() { } } -// send status of shower to MQTT topic called shower_active - which is determined by the state parameter -// and creates the HA config topic if HA enabled -// force is used by EMSESP::publish_all_loop() +// sets the state and publishes the state to the MQTT topic shower_active void Shower::set_shower_state(bool state, bool force) { // sets the state shower_state_ = state; @@ -173,22 +178,26 @@ void Shower::set_shower_state(bool state, bool force) { if ((shower_state_ == old_shower_state_) && !force) { return; } - old_shower_state_ = shower_state_; // copy current state + old_shower_state_ = shower_state_; - // always publish as a string + // always publish as a string - see https://github.com/emsesp/EMS-ESP/issues/369 + // and with retain flag set so HA will pick it up when EMS-ESP reboots char s[12]; - Mqtt::queue_publish("shower_active", Helpers::render_boolean(s, shower_state_)); // https://github.com/emsesp/EMS-ESP/issues/369 + Mqtt::queue_publish_retain("shower_active", Helpers::render_boolean(s, shower_state_)); +} - // send out HA MQTT Discovery config topic - if ((Mqtt::ha_enabled()) && (!ha_configdone_ || force)) { +// send status of shower to MQTT topic called shower_active - which is determined by the state parameter +// and creates the HA config topic if HA enabled +void Shower::create_ha_discovery() { + // first create the HA MQTT Discovery config topic + // this has to be done before the state is published + if (Mqtt::ha_enabled() && !ha_configdone_) { JsonDocument doc; char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; char str[70]; char stat_t[50]; - // // shower active - // doc["name"] = "Shower Active"; if (Mqtt::entity_format() == Mqtt::entityFormat::MULTI_SHORT) { @@ -196,8 +205,8 @@ void Shower::set_shower_state(bool state, bool force) { } else { snprintf(str, sizeof(str), "shower_active"); // v3.4 compatible } - doc["uniq_id"] = str; - doc["object_id"] = str; + doc["uniq_id"] = str; + doc["obj_id"] = str; snprintf(stat_t, sizeof(stat_t), "%s/shower_active", Mqtt::base().c_str()); doc["stat_t"] = stat_t; @@ -209,9 +218,7 @@ void Shower::set_shower_state(bool state, bool force) { snprintf(topic, sizeof(topic), "binary_sensor/%s/shower_active/config", Mqtt::basename().c_str()); ha_configdone_ = Mqtt::queue_ha(topic, doc.as()); // publish the config payload with retain flag - // // shower duration - // doc.clear(); snprintf(str, sizeof(str), "%s_shower_duration", Mqtt::basename().c_str()); diff --git a/src/core/shower.h b/src/core/shower.h index f266e709c..82c99c694 100644 --- a/src/core/shower.h +++ b/src/core/shower.h @@ -29,6 +29,7 @@ class Shower { void loop(); void set_shower_state(bool state, bool force = false); + void create_ha_discovery(); // commands static bool command_coldshot(const char * value, const int8_t id); From 47f21019a050c7a9d8c5c87a1b55467471a8a0b2 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 18 May 2025 11:47:22 +0200 Subject: [PATCH 4/5] clean-up publish retain functions --- src/core/mqtt.cpp | 127 ++++++++++++++++++++++++++-------------------- src/core/mqtt.h | 10 ++-- 2 files changed, 79 insertions(+), 58 deletions(-) diff --git a/src/core/mqtt.cpp b/src/core/mqtt.cpp index 3eff811e8..188fffd8e 100644 --- a/src/core/mqtt.cpp +++ b/src/core/mqtt.cpp @@ -516,7 +516,7 @@ void Mqtt::on_connect() { resubscribe(); // publish to the last will topic (see Mqtt::start() function) to say we're alive - queue_publish_retain("status", "online", true); // retain: https://github.com/emsesp/EMS-ESP32/discussions/2086 + queue_publish_retain("status", "online"); // retain: https://github.com/emsesp/EMS-ESP32/discussions/2086 } // Home Assistant Discovery - the main master Device called EMS-ESP @@ -656,56 +656,67 @@ bool Mqtt::queue_message(const uint8_t operation, const std::string & topic, con return (packet_id != 0); } -// add MQTT message to queue, payload is a string -bool Mqtt::queue_publish_message(const std::string & topic, const std::string & payload, const bool retain) { - return queue_message(Operation::PUBLISH, topic, payload, retain); -} - // add MQTT subscribe message to queue void Mqtt::queue_subscribe_message(const std::string & topic) { - queue_message(Operation::SUBSCRIBE, topic, "", false); // no payload + queue_message(Operation::SUBSCRIBE, topic, "", false); // no payload, no retain } // add MQTT unsubscribe message to queue void Mqtt::queue_unsubscribe_message(const std::string & topic) { - queue_message(Operation::UNSUBSCRIBE, topic, "", false); // no payload + queue_message(Operation::UNSUBSCRIBE, topic, "", false); // no payload, no retain } -// MQTT Publish, using a user's retain flag +// internal function to add MQTT message to queue, payload is a string +bool Mqtt::queue_publish_message(const std::string & topic, const std::string & payload, const bool retain) { + return queue_message(Operation::PUBLISH, topic, payload, retain); +} + +// MQTT Publish, using the user's retain flag bool Mqtt::queue_publish(const std::string & topic, const std::string & payload) { return queue_publish_message(topic, payload, mqtt_retain_); } -// MQTT Publish, using a user's retain flag - except for char * strings payload +// MQTT Publish, using the user's retain flag - except for char * strings payload bool Mqtt::queue_publish(const char * topic, const char * payload) { return queue_publish_message(topic, payload, mqtt_retain_); } -// MQTT Publish, using a user's retain flag - std::string payload +// MQTT Publish, using the user's retain flag - std::string payload bool Mqtt::queue_publish(const char * topic, const std::string & payload) { return queue_publish_message(topic, payload, mqtt_retain_); } bool Mqtt::queue_publish(const char * topic, const JsonObjectConst payload) { - return queue_publish_retain(topic, payload, mqtt_retain_); + return queue_publish(topic, payload, mqtt_retain_); } -// publish json doc, only if its not empty +// MQTT Publish, for json doc, only if its not empty - with retain flag set bool Mqtt::queue_publish(const std::string & topic, const JsonObjectConst payload) { - return queue_publish_retain(topic, payload, mqtt_retain_); + return queue_publish_retain(topic, payload); } -// MQTT Publish, using a specific retain flag, topic is a flash string, forcing retain flag -bool Mqtt::queue_publish_retain(const char * topic, const std::string & payload, const bool retain) { - return queue_publish_message(topic, payload, retain); +// MQTT Publish, topic and payload are strings, forcing retain flag +bool Mqtt::queue_publish_retain(const char * topic, const std::string & payload) { + return queue_publish_message(topic, payload, true); } -// publish json doc, only if its not empty, using the retain flag -bool Mqtt::queue_publish_retain(const std::string & topic, const JsonObjectConst payload, const bool retain) { - return queue_publish_retain(topic.c_str(), payload, retain); +// publish json doc, only if its not empty, forcing retain flag +bool Mqtt::queue_publish_retain(const std::string & topic, const JsonObjectConst payload) { + return queue_publish(topic.c_str(), payload, true); } -bool Mqtt::queue_publish_retain(const char * topic, const JsonObjectConst payload, const bool retain) { +// publish json doc, only if its not empty, forcing retain flag +bool Mqtt::queue_publish_retain(const char * topic, const JsonObjectConst payload) { + return queue_publish(topic, payload, true); +} + +// MQTT Publish, for char * strings payload, forcing retain flag +bool Mqtt::queue_publish_retain(const char * topic, const char * payload) { + return queue_publish_message(topic, payload, true); +} + +// converts json payload to string, uses any retain flag +bool Mqtt::queue_publish(const char * topic, const JsonObjectConst payload, const bool retain) { if (payload.size()) { std::string payload_text; payload_text.reserve(measureJson(payload) + 1); @@ -1381,6 +1392,11 @@ void Mqtt::add_ha_sections_to_doc(const char * name, const char * cond1, const char * cond2, const char * negcond) { + // only for HA + if (discovery_type() != discoveryType::HOMEASSISTANT) { + return; + } + // adds dev section to HA Discovery config if (name != nullptr) { JsonObject dev = config["dev"].to(); @@ -1398,49 +1414,50 @@ void Mqtt::add_ha_sections_to_doc(const char * name, free(cap_name); } + // skip availability section if no conditions set + if (!cond1 && !cond2 && !negcond) { + return; + } + // adds "availability" section to HA Discovery config JsonArray avty = config["avty"].to(); JsonDocument avty_json; - char tpl[150]; - - // make local copy of state, as the pointer will get derefenced + // make local copy of state, as the pointer will get de-referenced char state[50]; strlcpy(state, state_t, sizeof(state)); - // skip conditional Jinja2 templates if not home assistant - if (discovery_type() == discoveryType::HOMEASSISTANT) { - const char * tpl_draft = "{{'online' if %s else 'offline'}}"; + char tpl[150]; + const char * tpl_draft = "{{'online' if %s else 'offline'}}"; - // condition 1 - if (cond1 != nullptr) { - avty_json.clear(); - avty_json["t"] = state; - snprintf(tpl, sizeof(tpl), tpl_draft, cond1); - avty_json["val_tpl"] = tpl; - avty.add(avty_json); // returns 0 if no mem - } - - // condition 2 - if (cond2 != nullptr) { - avty_json.clear(); - avty_json["t"] = state; - snprintf(tpl, sizeof(tpl), tpl_draft, cond2); - avty_json["val_tpl"] = tpl; - avty.add(avty_json); // returns 0 if no mem - } - - // negative condition - if (negcond != nullptr) { - avty_json.clear(); - avty_json["t"] = state; - snprintf(tpl, sizeof(tpl), "{{'offline' if %s else 'online'}}", negcond); - avty_json["val_tpl"] = tpl; - avty.add(avty_json); // returns 0 if no mem - } - - config["avty_mode"] = "all"; + // condition 1 + if (cond1 != nullptr) { + avty_json.clear(); + avty_json["t"] = state; + snprintf(tpl, sizeof(tpl), tpl_draft, cond1); + avty_json["val_tpl"] = tpl; + avty.add(avty_json); // returns 0 if no mem } + + // condition 2 + if (cond2 != nullptr) { + avty_json.clear(); + avty_json["t"] = state; + snprintf(tpl, sizeof(tpl), tpl_draft, cond2); + avty_json["val_tpl"] = tpl; + avty.add(avty_json); // returns 0 if no mem + } + + // negative condition + if (negcond != nullptr) { + avty_json.clear(); + avty_json["t"] = state; + snprintf(tpl, sizeof(tpl), "{{'offline' if %s else 'online'}}", negcond); + avty_json["val_tpl"] = tpl; + avty.add(avty_json); // returns 0 if no mem + } + + config["avty_mode"] = "all"; } void Mqtt::add_ha_bool(JsonDocument & config) { diff --git a/src/core/mqtt.h b/src/core/mqtt.h index 89d572b96..f6565cbdb 100644 --- a/src/core/mqtt.h +++ b/src/core/mqtt.h @@ -72,12 +72,16 @@ class Mqtt { static bool queue_publish(const std::string & topic, const std::string & payload); static bool queue_publish(const char * topic, const char * payload); + static bool queue_publish(const char * topic, const JsonObjectConst payload, const bool retain); static bool queue_publish(const std::string & topic, const JsonObjectConst payload); static bool queue_publish(const char * topic, const JsonObjectConst payload); static bool queue_publish(const char * topic, const std::string & payload); - static bool queue_publish_retain(const std::string & topic, const JsonObjectConst payload, const bool retain); - static bool queue_publish_retain(const char * topic, const std::string & payload, const bool retain); - static bool queue_publish_retain(const char * topic, const JsonObjectConst payload, const bool retain); + + static bool queue_publish_retain(const std::string & topic, const JsonObjectConst payload); + static bool queue_publish_retain(const char * topic, const std::string & payload); + static bool queue_publish_retain(const char * topic, const JsonObjectConst payload); + static bool queue_publish_retain(const char * topic, const char * payload); + static bool queue_ha(const char * topic, const JsonObjectConst payload); static bool queue_remove_topic(const char * topic); From ab099c45b841c3ddf0759f819bfae2e72e87e387 Mon Sep 17 00:00:00 2001 From: proddy Date: Sun, 18 May 2025 11:48:13 +0200 Subject: [PATCH 5/5] updated --- CHANGELOG_LATEST.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG_LATEST.md b/CHANGELOG_LATEST.md index 8e854164d..ab783ed08 100644 --- a/CHANGELOG_LATEST.md +++ b/CHANGELOG_LATEST.md @@ -25,6 +25,7 @@ For more details go to [docs.emsesp.org](https://docs.emsesp.org/). - FB100 controls the hc, not the master [#2510](https://github.com/emsesp/EMS-ESP32/issues/2510) - IPM DHW module, [#2524](https://github.com/emsesp/EMS-ESP32/issues/2524) - charge optimization [#2543](https://github.com/emsesp/EMS-ESP32/issues/2543) +- shower active state retained, shows correctly in HA ## Changed