mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 07:49:52 +03:00
Merge pull request #2557 from proddy/dev
mqtt tidy up and shower active fixed for HA
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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<JsonObject>();
|
||||
@@ -1398,18 +1414,20 @@ 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<JsonArray>();
|
||||
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) {
|
||||
char tpl[150];
|
||||
const char * tpl_draft = "{{'online' if %s else 'offline'}}";
|
||||
|
||||
// condition 1
|
||||
@@ -1440,7 +1458,6 @@ void Mqtt::add_ha_sections_to_doc(const char * name,
|
||||
}
|
||||
|
||||
config["avty_mode"] = "all";
|
||||
}
|
||||
}
|
||||
|
||||
void Mqtt::add_ha_bool(JsonDocument & config) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
@@ -197,7 +206,7 @@ void Shower::set_shower_state(bool state, bool force) {
|
||||
snprintf(str, sizeof(str), "shower_active"); // v3.4 compatible
|
||||
}
|
||||
doc["uniq_id"] = str;
|
||||
doc["object_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<JsonObject>()); // publish the config payload with retain flag
|
||||
|
||||
//
|
||||
// shower duration
|
||||
//
|
||||
doc.clear();
|
||||
|
||||
snprintf(str, sizeof(str), "%s_shower_duration", Mqtt::basename().c_str());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<JsonObject>(), true); // topic called "info" and it's Retained
|
||||
Mqtt::queue_publish_retain(F_(info), doc.as<JsonObject>()); // topic called "info" and it's Retained
|
||||
}
|
||||
|
||||
// create the json for heartbeat
|
||||
|
||||
Reference in New Issue
Block a user