From 11ad4d8a0f373c987130b920ce6777186159ac0e Mon Sep 17 00:00:00 2001 From: Proddy Date: Wed, 22 Feb 2023 19:01:58 +0100 Subject: [PATCH] merge with https://github.com/emsesp/EMS-ESP32/pull/1077 --- interface/src/i18n/pl/index.ts | 4 +- src/analogsensor.cpp | 22 +++-- src/dallassensor.cpp | 21 +++-- src/emsdevice.cpp | 1 + src/mqtt.cpp | 145 +++++++++++++++++++++++---------- src/mqtt.h | 12 +-- src/shower.cpp | 7 +- 7 files changed, 142 insertions(+), 70 deletions(-) diff --git a/interface/src/i18n/pl/index.ts b/interface/src/i18n/pl/index.ts index 4e6cebcdf..faa58a60a 100644 --- a/interface/src/i18n/pl/index.ts +++ b/interface/src/i18n/pl/index.ts @@ -157,7 +157,7 @@ const pl: BaseTranslation = { CUSTOMIZATIONS_HELP_3: 'zablokuj akcje zapisu', CUSTOMIZATIONS_HELP_4: 'wyklucz z MQTT i API', CUSTOMIZATIONS_HELP_5: 'ukryj na pulpicie', - CUSTOMIZATIONS_HELP_6: 'remove from memory', // TODO translate + CUSTOMIZATIONS_HELP_6: 'usuń z pamięci', SELECT_DEVICE: 'wybierz urządzenie', SET_ALL: 'Ustaw wszystko jako', OPTIONS: 'Opcje', @@ -201,7 +201,7 @@ const pl: BaseTranslation = { CPU_FREQ: 'Taktowanie CPU', HEAP: 'HEAP (wolne / maksymalny przydział)', PSRAM: 'PSRAM (rozmiar / wolne)', - FLASH: 'Flash (rozmiar / taktowanie)', + FLASH: 'FLASH (rozmiar / taktowanie)', APPSIZE: 'Aplikacja (wykorzystane / wolne)', FILESYSTEM: 'System plików (wykorzystane / wolne)', BUFFER_SIZE: 'Maksymalna pojemność bufora (ilość wpisów)', diff --git a/src/analogsensor.cpp b/src/analogsensor.cpp index 158defe6c..a46cc0b7e 100644 --- a/src/analogsensor.cpp +++ b/src/analogsensor.cpp @@ -396,10 +396,9 @@ void AnalogSensor::remove_ha_topic(const uint8_t gpio) const { } LOG_DEBUG("Removing HA config for analog sensor GPIO %02d", gpio); - char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), gpio); - Mqtt::publish_ha(topic); + Mqtt::remove_topic(topic); } // send all sensor values as a JSON package to MQTT @@ -457,13 +456,16 @@ void AnalogSensor::publish_values(const bool force) { snprintf(stat_t, sizeof(stat_t), "%s/analogsensor_data", Mqtt::base().c_str()); // use base path config["stat_t"] = stat_t; - char str[50]; + char val_obj[50]; + char val_cond[65]; if (Mqtt::is_nested()) { - snprintf(str, sizeof(str), "{{value_json['%02d'].value}}", sensor.gpio()); + snprintf(val_obj, sizeof(val_obj), "value_json['%02d'].value", sensor.gpio()); + snprintf(val_cond, sizeof(val_cond), "value_json['%02d'] is defined", sensor.gpio()); } else { - snprintf(str, sizeof(str), "{{value_json['%s']}", sensor.name().c_str()); + snprintf(val_obj, sizeof(val_obj), "value_json['%s']", sensor.name().c_str()); + snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj); } - config["val_tpl"] = str; + config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + "}}"; char uniq_s[70]; if (Mqtt::entity_format() == 2) { @@ -475,8 +477,9 @@ void AnalogSensor::publish_values(const bool force) { config["object_id"] = uniq_s; config["uniq_id"] = uniq_s; // same as object_id - snprintf(str, sizeof(str), "%s", sensor.name().c_str()); - config["name"] = str; + char name[50]; + snprintf(name, sizeof(name), "%s", sensor.name().c_str()); + config["name"] = name; if (sensor.uom() != DeviceValueUOM::NONE) { config["unit_of_meas"] = EMSdevice::uom_to_string(sensor.uom()); @@ -486,6 +489,9 @@ void AnalogSensor::publish_values(const bool force) { JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp"); + // add "availability" section + Mqtt::add_avty_to_doc(stat_t, config.as(), val_cond); + char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; snprintf(topic, sizeof(topic), "sensor/%s/analogsensor_%02d/config", Mqtt::basename().c_str(), sensor.gpio()); diff --git a/src/dallassensor.cpp b/src/dallassensor.cpp index 1bd7d4848..7897355a6 100644 --- a/src/dallassensor.cpp +++ b/src/dallassensor.cpp @@ -462,7 +462,7 @@ void DallasSensor::remove_ha_topic(const std::string & id) { std::replace(sensorid.begin(), sensorid.end(), '-', '_'); char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; snprintf(topic, sizeof(topic), "sensor/%s/dallassensor_%s/config", Mqtt::basename().c_str(), sensorid.c_str()); - Mqtt::publish_ha(topic); + Mqtt::remove_topic(topic); } // send all dallas sensor values as a JSON package to MQTT @@ -516,13 +516,16 @@ void DallasSensor::publish_values(const bool force) { config["unit_of_meas"] = EMSdevice::uom_to_string(DeviceValueUOM::DEGREES); - char str[50]; + char val_obj[50]; + char val_cond[65]; if (Mqtt::is_nested()) { - snprintf(str, sizeof(str), "{{value_json['%s'].temp}}", sensor.id().c_str()); + snprintf(val_obj, sizeof(val_obj), "value_json['%s'].temp", sensor.id().c_str()); + snprintf(val_cond, sizeof(val_cond), "value_json['%s'] is defined", sensor.id().c_str()); } else { - snprintf(str, sizeof(str), "{{value_json['%s']}}", sensor.name().c_str()); + snprintf(val_obj, sizeof(val_obj), "value_json['%s']", sensor.name().c_str()); + snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj); } - config["val_tpl"] = str; + config["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + "}}"; char uniq_s[70]; if (Mqtt::entity_format() == 2) { @@ -534,13 +537,17 @@ void DallasSensor::publish_values(const bool force) { config["object_id"] = uniq_s; config["uniq_id"] = uniq_s; // same as object_id - snprintf(str, sizeof(str), "%s", sensor.name().c_str()); - config["name"] = str; + char name[50]; + snprintf(name, sizeof(name), "%s", sensor.name().c_str()); + config["name"] = name; JsonObject dev = config.createNestedObject("dev"); JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp"); + // add "availability" section + Mqtt::add_avty_to_doc(stat_t, config.as(), val_cond); + char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; // use '_' as HA doesn't like '-' in the topic name std::string sensorid = sensor.id(); diff --git a/src/emsdevice.cpp b/src/emsdevice.cpp index 07bf11c2a..55160099b 100644 --- a/src/emsdevice.cpp +++ b/src/emsdevice.cpp @@ -1670,6 +1670,7 @@ bool EMSdevice::generate_values(JsonObject & output, const uint8_t tag_filter, c // remove the Home Assistant configs for each device value/entity if its not visible or active or marked as read-only // this is called when an MQTT publish is done via an EMS Device in emsesp.cpp::publish_device_values() +// TODO remove topic remove on cold start void EMSdevice::mqtt_ha_entity_config_remove() { for (auto & dv : devicevalues_) { if (dv.has_state(DeviceValueState::DV_HA_CONFIG_CREATED) diff --git a/src/mqtt.cpp b/src/mqtt.cpp index add01af40..d16e37078 100644 --- a/src/mqtt.cpp +++ b/src/mqtt.cpp @@ -137,7 +137,7 @@ void Mqtt::loop() { uint32_t currentMillis = uuid::get_uptime(); - // publish top item from MQTT queue to stop flooding + // publish MQTT queue, but timed to avoid overloading the TCP pipe if ((uint32_t)(currentMillis - last_mqtt_poll_) > MQTT_PUBLISH_WAIT) { last_mqtt_poll_ = currentMillis; process_queue(); @@ -212,7 +212,7 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) { return; } - shell.printfln("MQTT queue (%d/%d messages):", mqtt_messages_.size(), MAX_MQTT_MESSAGES); + shell.printfln("MQTT queue (%d):", mqtt_messages_.size()); for (const auto & message : mqtt_messages_) { auto content = message.content_; @@ -677,31 +677,26 @@ void Mqtt::queue_message(const uint8_t operation, const std::string & topic, con // take the topic and prefix the base, unless its for HA std::shared_ptr message = std::make_shared(operation, topic, payload, retain); -#if defined(EMSESP_DEBUG) if (operation == Operation::PUBLISH) { if (message->payload.empty()) { - LOG_INFO("Adding to queue: (publish) topic='%s' empty payload", message->topic.c_str()); + LOG_DEBUG("Adding to queue: (publish) topic='%s' empty payload", message->topic.c_str()); } else { - LOG_INFO("Adding to queue: (publish) topic='%s' payload=%s", message->topic.c_str(), message->payload.c_str()); + LOG_DEBUG("Adding to queue: (publish) topic='%s' payload=%s", message->topic.c_str(), message->payload.c_str()); } } else { - LOG_INFO("Adding to queue: (subscribe) topic='%s'", message->topic.c_str()); + LOG_DEBUG("Adding to queue: (subscribe) topic='%s'", message->topic.c_str()); } -#endif - // TODO : to look at with @MichaelDvP ... - // 1. check heap instead of counting? - // 2. reduce the time to process the queue so it empties quicker? - // 3. if the queue is full, just exit and don't remove the last message? #ifndef EMSESP_STANDALONE - - if (mqtt_messages_.size() >= MAX_MQTT_MESSAGES) { + // TODO to look at with @MichaelDvP ... + // TODO also reduce the time to process the queue so it empties quicker? I changed MQTT_PUBLISH_WAIT from 100 to 75 + // anything below 65MB available free heap is dangerously low + if (ESP.getFreeHeap() < (65 * 1024)) { // mqtt_messages_.pop_front(); LOG_WARNING("Queue overflow"); mqtt_publish_fails_++; - return; // don't add + return; // TODO - don't add top queue. Check will this have negative side affects? } - #endif mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message)); @@ -770,15 +765,18 @@ void Mqtt::publish_retain(const char * topic, const JsonObject & payload, bool r } // publish empty payload to remove the topic -void Mqtt::publish_ha(const char * topic) { +void Mqtt::remove_topic(const char * topic) { if (!enabled()) { return; } - std::string fulltopic = Mqtt::discovery_prefix() + topic; - LOG_DEBUG("Publishing empty HA topic=%s", fulltopic.c_str()); - - queue_publish_message(fulltopic, "", true); // publish with retain to remove from broker + if (ha_enabled_) { + LOG_DEBUG("Publishing HA topic %s%s", Mqtt::discovery_prefix().c_str(), topic); + queue_publish_message(Mqtt::discovery_prefix() + topic, "", true); // publish with retain to remove from broker + } else { + LOG_DEBUG("Publishing topic %s", topic); + queue_publish_message(topic, "", true); // publish with retain to remove from broker + } } // publish a Home Assistant config topic and payload, with retain flag off. @@ -1076,7 +1074,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev // https://github.com/emsesp/EMS-ESP32/issues/196 if (remove) { LOG_DEBUG("Removing HA config for %s", uniq_id); - publish_ha(topic); + remove_topic(topic); return; } @@ -1089,6 +1087,8 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev const char * sc_ha = "stat_cla"; // state class const char * uom_ha = "unit_of_meas"; // unit of measure + char sample_val[30] = "0"; // sample, correct(!) entity value, used only to prevent warning/error in HA if real value is not published yet + // handle commands, which are device entities that are writable // we add the command topic parameter // note: there is no way to handle strings in HA so datetimes (e.g. set_datetime, set_holiday, set_wwswitchtime etc) are excluded @@ -1108,6 +1108,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev for (uint8_t i = 0; i < options_size; i++) { option_list.add(Helpers::translated_word(options[i])); } + snprintf(sample_val, sizeof(sample_val), "'%s'", Helpers::translated_word(options[0])); } else if (type != DeviceValueType::STRING && type != DeviceValueType::BOOL) { // Must be Numeric.... doc["mode"] = "box"; // auto, slider or box @@ -1124,6 +1125,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev if (dv_set_min != 0 || dv_set_max != 0) { doc["min"] = dv_set_min; doc["max"] = dv_set_max; + snprintf(sample_val, sizeof(sample_val), "%i", dv_set_min); } // set icons @@ -1151,7 +1153,8 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev char ha_name[70]; char * F_name = strdup(fullname); Helpers::CharToUpperUTF8(F_name); // capitalize first letter - if (tag != DeviceValueTAG::TAG_NONE) { + if (tag > DeviceValueTAG::TAG_HEARTBEAT) { + // exclude heartbeat tag snprintf(ha_name, sizeof(ha_name), "%s %s", EMSdevice::tag_to_string(tag), F_name); } else { snprintf(ha_name, sizeof(ha_name), "%s", F_name); // no tag @@ -1161,17 +1164,15 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev // value template // if its nested mqtt format then use the appended entity name, otherwise take the original name - char val_tpl[75]; - if (is_nested()) { - if (tag >= DeviceValueTAG::TAG_HC1) { - snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s.%s}}", EMSdevice::tag_to_mqtt(tag), entity); - } else { - snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", entity); - } + char val_obj[100]; + char val_cond[200]; + if (is_nested() && tag >= DeviceValueTAG::TAG_HC1) { + snprintf(val_obj, sizeof(val_obj), "value_json.%s.%s", EMSdevice::tag_to_mqtt(tag), entity); + snprintf(val_cond, sizeof(val_cond), "value_json.%s is defined and %s is defined", EMSdevice::tag_to_mqtt(tag), val_obj); } else { - snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", entity); + snprintf(val_obj, sizeof(val_obj), "value_json.%s", entity); + snprintf(val_cond, sizeof(val_cond), "%s is defined", val_obj); } - doc["val_tpl"] = val_tpl; // special case to handle booleans // applies to both Binary Sensor (read only) and a Switch (for a command) @@ -1180,6 +1181,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) { doc["pl_on"] = true; doc["pl_off"] = false; + snprintf(sample_val, sizeof(sample_val), "false"); } else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) { doc["pl_on"] = 1; doc["pl_off"] = 0; @@ -1187,8 +1189,8 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev char result[12]; doc["pl_on"] = Helpers::render_boolean(result, true); doc["pl_off"] = Helpers::render_boolean(result, false); + snprintf(sample_val, sizeof(sample_val), "'%s'", Helpers::render_boolean(result, false)); } - // doc[sc_ha] = F_(measurement); // not needed } else { // always set the uom, using the standards except for hours/minutes/seconds // using HA specific codes from https://github.com/home-assistant/core/blob/dev/homeassistant/const.py @@ -1202,6 +1204,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev doc[uom_ha] = EMSdevice::uom_to_string(uom); // default } } + doc["val_tpl"] = (std::string) "{{" + val_obj + " if " + val_cond + " else " + sample_val + "}}"; // this next section is adding the state class, device class and sometimes the icon // used for Sensor and Binary Sensor Entities in HA @@ -1298,6 +1301,9 @@ void Mqtt::publish_ha_sensor_config(uint8_t type, // EMSdev // add the dev json object to the end doc["dev"] = dev_json; + // add "availability" section + add_avty_to_doc(stat_t, doc.as(), val_cond); + publish_ha(topic, doc.as()); } @@ -1309,6 +1315,9 @@ void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp, char hc_mode_s[30]; char seltemp_s[30]; char currtemp_s[30]; + char hc_mode_cond[70]; + char seltemp_cond[70]; + char currtemp_cond[170]; char mode_str_tpl[400]; char name_s[10]; char uniq_id_s[60]; @@ -1319,31 +1328,39 @@ void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp, snprintf(topic, sizeof(topic), "climate/%s/thermostat_hc%d/config", mqtt_basename_.c_str(), hc_num); if (remove) { - publish_ha(topic); // publish empty payload with retain flag + remove_topic(topic); // publish empty payload with retain flag return; } if (Mqtt::is_nested()) { // nested format snprintf(hc_mode_s, sizeof(hc_mode_s), "value_json.hc%d.mode", hc_num); - snprintf(seltemp_s, sizeof(seltemp_s), "{{value_json.hc%d.seltemp}}", hc_num); + snprintf(hc_mode_cond, sizeof(hc_mode_cond), "value_json.hc%d is undefined or %s is undefined", hc_num, hc_mode_s); + snprintf(seltemp_s, sizeof(seltemp_s), "value_json.hc%d.seltemp", hc_num); + snprintf(seltemp_cond, sizeof(seltemp_cond), "value_json.hc%d is defined and %s is defined", hc_num, seltemp_s); + if (has_roomtemp) { - snprintf(currtemp_s, sizeof(currtemp_s), "{{value_json.hc%d.currtemp}}", hc_num); + snprintf(currtemp_s, sizeof(currtemp_s), "value_json.hc%d.currtemp", hc_num); + snprintf(currtemp_cond, sizeof(currtemp_cond), "value_json.hc%d is defined and %s is defined", hc_num, currtemp_s); } snprintf(topic_t, sizeof(topic_t), "~/%s", Mqtt::tag_to_topic(EMSdevice::DeviceType::THERMOSTAT, DeviceValueTAG::TAG_NONE).c_str()); } else { // single format snprintf(hc_mode_s, sizeof(hc_mode_s), "value_json.mode"); - snprintf(seltemp_s, sizeof(seltemp_s), "{{value_json.seltemp}}"); + snprintf(hc_mode_cond, sizeof(hc_mode_cond), "%s is undefined", hc_mode_s); + snprintf(seltemp_s, sizeof(seltemp_s), "value_json.seltemp"); + snprintf(seltemp_cond, sizeof(seltemp_cond), "%s is defined", seltemp_s); + if (has_roomtemp) { - snprintf(currtemp_s, sizeof(currtemp_s), "{{value_json.currtemp}}"); + snprintf(currtemp_s, sizeof(currtemp_s), "value_json.currtemp"); + snprintf(currtemp_cond, sizeof(currtemp_cond), "%s is defined", currtemp_s); } snprintf(topic_t, sizeof(topic_t), "~/%s", Mqtt::tag_to_topic(EMSdevice::DeviceType::THERMOSTAT, DeviceValueTAG::TAG_HC1 + hc_num - 1).c_str()); } snprintf(mode_str_tpl, sizeof(mode_str_tpl), - "{%%if %s=='manual'%%}heat{%%elif %s=='day'%%}heat{%%elif %s=='night'%%}off{%%elif %s=='off'%%}off{%%else%%}auto{%%endif%%}", + "{%%if %s%%}off{%%elif %s=='manual'%%}heat{%%elif %s=='day'%%}heat{%%elif %s=='night'%%}off{%%elif %s=='off'%%}off{%%else%%}auto{%%endif%%}", hc_mode_s, hc_mode_s, hc_mode_s, @@ -1360,7 +1377,7 @@ void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp, snprintf(temp_cmd_s, sizeof(temp_cmd_s), "~/thermostat/hc%d/seltemp", hc_num); snprintf(mode_cmd_s, sizeof(temp_cmd_s), "~/thermostat/hc%d/mode", hc_num); - StaticJsonDocument doc; + StaticJsonDocument doc; // doc is 787 typically so 1024 should be enough doc["~"] = mqtt_base_; doc["uniq_id"] = uniq_id_s; @@ -1370,17 +1387,17 @@ void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp, doc["mode_stat_tpl"] = mode_str_tpl; doc["temp_cmd_t"] = temp_cmd_s; doc["temp_stat_t"] = topic_t; - doc["temp_stat_tpl"] = seltemp_s; - doc["mode_cmd_t"] = mode_cmd_s; + doc["temp_stat_tpl"] = (std::string) "{{" + seltemp_s + " if " + seltemp_cond + " else 0}}"; if (has_roomtemp) { doc["curr_temp_t"] = topic_t; - doc["curr_temp_tpl"] = currtemp_s; + doc["curr_temp_tpl"] = (std::string) "{{" + currtemp_s + " if " + currtemp_cond + " else 0}}"; } - doc["min_temp"] = Helpers::render_value(min_s, min, 0, EMSESP::system_.fahrenheit() ? 2 : 0); - doc["max_temp"] = Helpers::render_value(max_s, max, 0, EMSESP::system_.fahrenheit() ? 2 : 0); - doc["temp_step"] = "0.5"; + doc["min_temp"] = Helpers::render_value(min_s, min, 0, EMSESP::system_.fahrenheit() ? 2 : 0); + doc["max_temp"] = Helpers::render_value(max_s, max, 0, EMSESP::system_.fahrenheit() ? 2 : 0); + doc["temp_step"] = "0.5"; + doc["mode_cmd_t"] = mode_cmd_s; // the HA climate component only responds to auto, heat and off JsonArray modes = doc.createNestedArray("modes"); @@ -1393,6 +1410,9 @@ void Mqtt::publish_ha_climate_config(const uint8_t tag, const bool has_roomtemp, JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp-thermostat"); + // add "availability" section + add_avty_to_doc(topic_t, doc.as(), seltemp_cond, has_roomtemp ? currtemp_cond : nullptr, hc_mode_cond); + publish_ha(topic, doc.as()); // publish the config payload with retain flag } @@ -1415,4 +1435,39 @@ std::string Mqtt::tag_to_topic(uint8_t device_type, uint8_t tag) { } } +// adds "availability" section to HA Discovery config +void Mqtt::add_avty_to_doc(const char * state_t, const JsonObject & doc, const char * cond1, const char * cond2, const char * negcond) { + const char * tpl_draft = "{{'online' if %s else 'offline'}}"; + char tpl[150]; + JsonArray avty = doc.createNestedArray("avty"); + + StaticJsonDocument<512> avty_json; + + snprintf(tpl, sizeof(tpl), "%s/status", mqtt_base_.c_str()); + avty_json["t"] = tpl; + snprintf(tpl, sizeof(tpl), tpl_draft, "value == 'online'"); + avty_json["val_tpl"] = tpl; + avty.add(avty_json); + + avty_json["t"] = state_t; + snprintf(tpl, sizeof(tpl), tpl_draft, cond1 == nullptr ? "value is defined" : cond1); + avty_json["val_tpl"] = tpl; + avty.add(avty_json); + + if (cond2 != nullptr) { + snprintf(tpl, sizeof(tpl), tpl_draft, cond2); + avty_json["val_tpl"] = tpl; + avty.add(avty_json); + } + + if (negcond != nullptr) { + snprintf(tpl, sizeof(tpl), "{{'offline' if %s else 'online'}}", negcond); + avty_json["val_tpl"] = tpl; + avty.add(avty_json); + } + + doc["avty_mode"] = "all"; +} + + } // namespace emsesp diff --git a/src/mqtt.h b/src/mqtt.h index 3911f3e22..24c6d63c9 100644 --- a/src/mqtt.h +++ b/src/mqtt.h @@ -29,9 +29,6 @@ using uuid::console::Shell; -// size of queue -static constexpr uint16_t MAX_MQTT_MESSAGES = 300; - namespace emsesp { using mqtt_sub_function_p = std::function; @@ -87,7 +84,7 @@ class Mqtt { static void publish_retain(const char * topic, const std::string & payload, bool retain); static void publish_retain(const char * topic, const JsonObject & payload, bool retain); static void publish_ha(const char * topic, const JsonObject & payload); - static void publish_ha(const char * topic); + static void remove_topic(const char * topic); static void publish_ha_sensor_config(DeviceValue & dv, const char * model, const char * brand, const bool remove, const bool create_device_config = false); static void publish_ha_sensor_config(uint8_t type, @@ -242,6 +239,9 @@ class Mqtt { static std::string tag_to_topic(uint8_t device_type, uint8_t tag); + static void + add_avty_to_doc(const char * state_t, const JsonObject & doc, const char * cond1 = nullptr, const char * cond2 = nullptr, const char * negcond = nullptr); + struct QueuedMqttMessage { const uint32_t id_; const std::shared_ptr content_; @@ -263,8 +263,8 @@ class Mqtt { static AsyncMqttClient * mqttClient_; static uint32_t mqtt_message_id_; - static constexpr uint32_t MQTT_PUBLISH_WAIT = 100; // delay between sending publishes, to account for large payloads - static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing + static constexpr uint32_t MQTT_PUBLISH_WAIT = 75; // delay in ms between sending publishes, to account for large payloads + static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing static void queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain); static void queue_publish_message(const std::string & topic, const std::string & payload, bool retain); diff --git a/src/shower.cpp b/src/shower.cpp index aa5318424..ecac7a3ed 100644 --- a/src/shower.cpp +++ b/src/shower.cpp @@ -168,8 +168,8 @@ void Shower::set_shower_state(bool state, bool force) { doc["stat_t"] = stat_t; if (EMSESP::system_.bool_format() == BOOL_FORMAT_TRUEFALSE) { - doc["pl_on"] = true; - doc["pl_off"] = false; + doc["pl_on"] = "true"; + doc["pl_off"] = "false"; } else if (EMSESP::system_.bool_format() == BOOL_FORMAT_10) { doc["pl_on"] = 1; doc["pl_off"] = 0; @@ -183,6 +183,9 @@ void Shower::set_shower_state(bool state, bool force) { JsonArray ids = dev.createNestedArray("ids"); ids.add("ems-esp"); + // add "availability" section + Mqtt::add_avty_to_doc(stat_t, doc.as()); + char topic[Mqtt::MQTT_TOPIC_MAX_SIZE]; snprintf(topic, sizeof(topic), "binary_sensor/%s/shower_active/config", Mqtt::basename().c_str()); Mqtt::publish_ha(topic, doc.as()); // publish the config payload with retain flag