mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 08:19:52 +03:00
Mqtt: remove all HA if not active, timeout QoS, option single2cmd
This commit is contained in:
@@ -183,29 +183,43 @@ const MqttSettingsForm: FC = () => {
|
|||||||
control={<Checkbox name="send_response" checked={data.send_response} onChange={updateFormValue} />}
|
control={<Checkbox name="send_response" checked={data.send_response} onChange={updateFormValue} />}
|
||||||
label="Publish command output to a 'response' topic"
|
label="Publish command output to a 'response' topic"
|
||||||
/>
|
/>
|
||||||
<BlockFormControlLabel
|
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||||
control={<Checkbox name="publish_single" checked={data.publish_single} onChange={updateFormValue} />}
|
<Grid item>
|
||||||
label="Publish single value topics on change"
|
<BlockFormControlLabel
|
||||||
/>
|
control={<Checkbox name="publish_single" checked={data.publish_single} onChange={updateFormValue} />}
|
||||||
<Grid item>
|
label="Publish single value topics on change"
|
||||||
<BlockFormControlLabel
|
|
||||||
control={<Checkbox name="ha_enabled" checked={data.ha_enabled} onChange={updateFormValue} />}
|
|
||||||
label="Enable MQTT Discovery (for Home Assistant, Domoticz)"
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
{data.ha_enabled && (
|
|
||||||
<Grid item xs={6}>
|
|
||||||
<ValidatedTextField
|
|
||||||
name="discovery_prefix"
|
|
||||||
label="Prefix for the Discovery topic"
|
|
||||||
fullWidth
|
|
||||||
variant="outlined"
|
|
||||||
value={data.discovery_prefix}
|
|
||||||
onChange={updateFormValue}
|
|
||||||
margin="normal"
|
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
{data.publish_single && (
|
||||||
|
<Grid item>
|
||||||
|
<BlockFormControlLabel
|
||||||
|
control={<Checkbox name="publish_single2cmd" checked={data.publish_single2cmd} onChange={updateFormValue} />}
|
||||||
|
label="publish to command topics (ioBroker)"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
<Grid container spacing={1} direction="row" justifyContent="flex-start" alignItems="flex-start">
|
||||||
|
<Grid item>
|
||||||
|
<BlockFormControlLabel
|
||||||
|
control={<Checkbox name="ha_enabled" checked={data.ha_enabled} onChange={updateFormValue} />}
|
||||||
|
label="Enable MQTT Discovery (for Home Assistant, Domoticz)"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
{data.ha_enabled && (
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<ValidatedTextField
|
||||||
|
name="discovery_prefix"
|
||||||
|
label="Prefix for the Discovery topic"
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
value={data.discovery_prefix}
|
||||||
|
onChange={updateFormValue}
|
||||||
|
margin="normal"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
<Typography sx={{ pt: 2 }} variant="h6" color="primary">
|
||||||
Publish Intervals (in seconds, 0=automatic)
|
Publish Intervals (in seconds, 0=automatic)
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -40,5 +40,6 @@ export interface MqttSettings {
|
|||||||
nested_format: number;
|
nested_format: number;
|
||||||
send_response: boolean;
|
send_response: boolean;
|
||||||
publish_single: boolean;
|
publish_single: boolean;
|
||||||
|
publish_single2cmd: boolean;
|
||||||
discovery_prefix: string;
|
discovery_prefix: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,6 +176,7 @@ void MqttSettings::read(MqttSettings & settings, JsonObject & root) {
|
|||||||
root["nested_format"] = settings.nested_format;
|
root["nested_format"] = settings.nested_format;
|
||||||
root["discovery_prefix"] = settings.discovery_prefix;
|
root["discovery_prefix"] = settings.discovery_prefix;
|
||||||
root["publish_single"] = settings.publish_single;
|
root["publish_single"] = settings.publish_single;
|
||||||
|
root["publish_single2cmd"] = settings.publish_single2cmd;
|
||||||
root["send_response"] = settings.send_response;
|
root["send_response"] = settings.send_response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,11 +204,12 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
|
|||||||
newSettings.publish_time_other = root["publish_time_other"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
newSettings.publish_time_other = root["publish_time_other"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
||||||
newSettings.publish_time_sensor = root["publish_time_sensor"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
newSettings.publish_time_sensor = root["publish_time_sensor"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
||||||
|
|
||||||
newSettings.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED;
|
newSettings.ha_enabled = root["ha_enabled"] | EMSESP_DEFAULT_HA_ENABLED;
|
||||||
newSettings.nested_format = root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT;
|
newSettings.nested_format = root["nested_format"] | EMSESP_DEFAULT_NESTED_FORMAT;
|
||||||
newSettings.discovery_prefix = root["discovery_prefix"] | EMSESP_DEFAULT_DISCOVERY_PREFIX;
|
newSettings.discovery_prefix = root["discovery_prefix"] | EMSESP_DEFAULT_DISCOVERY_PREFIX;
|
||||||
newSettings.publish_single = root["publish_single"] | EMSESP_DEFAULT_PUBLISH_SINGLE;
|
newSettings.publish_single = root["publish_single"] | EMSESP_DEFAULT_PUBLISH_SINGLE;
|
||||||
newSettings.send_response = root["send_response"] | EMSESP_DEFAULT_SEND_RESPONSE;
|
newSettings.publish_single2cmd = root["publish_single2cmd"] | EMSESP_DEFAULT_PUBLISH_SINGLE2CMD;
|
||||||
|
newSettings.send_response = root["send_response"] | EMSESP_DEFAULT_SEND_RESPONSE;
|
||||||
|
|
||||||
if (newSettings.enabled != settings.enabled) {
|
if (newSettings.enabled != settings.enabled) {
|
||||||
changed = true;
|
changed = true;
|
||||||
@@ -230,6 +232,10 @@ StateUpdateResult MqttSettings::update(JsonObject & root, MqttSettings & setting
|
|||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newSettings.publish_single2cmd != settings.publish_single2cmd) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (newSettings.send_response != settings.send_response) {
|
if (newSettings.send_response != settings.send_response) {
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ class MqttSettings {
|
|||||||
uint8_t nested_format;
|
uint8_t nested_format;
|
||||||
String discovery_prefix;
|
String discovery_prefix;
|
||||||
bool publish_single;
|
bool publish_single;
|
||||||
|
bool publish_single2cmd;
|
||||||
bool send_response;
|
bool send_response;
|
||||||
|
|
||||||
static void read(MqttSettings & settings, JsonObject & root);
|
static void read(MqttSettings & settings, JsonObject & root);
|
||||||
|
|||||||
@@ -395,7 +395,11 @@ bool DallasSensor::get_value_info(JsonObject & output, const char * cmd, const i
|
|||||||
void DallasSensor::publish_sensor(const Sensor & sensor) {
|
void DallasSensor::publish_sensor(const Sensor & sensor) {
|
||||||
if (Mqtt::publish_single()) {
|
if (Mqtt::publish_single()) {
|
||||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||||
snprintf(topic, sizeof(topic), "%s/%s", read_flash_string(F_(dallassensor)).c_str(), sensor.name().c_str());
|
if (Mqtt::publish_single2cmd()) {
|
||||||
|
snprintf(topic, sizeof(topic), "%s/%s", read_flash_string(F_(dallassensor)).c_str(), sensor.name().c_str());
|
||||||
|
} else {
|
||||||
|
snprintf(topic, sizeof(topic), "%s%s/%s", read_flash_string(F_(dallassensor)).c_str(), "_data", sensor.name().c_str());
|
||||||
|
}
|
||||||
char payload[10];
|
char payload[10];
|
||||||
Mqtt::publish(topic, Helpers::render_value(payload, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0));
|
Mqtt::publish(topic, Helpers::render_value(payload, sensor.temperature_c, 10, EMSESP::system_.fahrenheit() ? 2 : 0));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,10 +128,6 @@
|
|||||||
#define EMSESP_DEFAULT_BOOL_FORMAT 1 // on/off
|
#define EMSESP_DEFAULT_BOOL_FORMAT 1 // on/off
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef EMSESP_DEFAULT_HA_CLIMATE_FORMAT
|
|
||||||
#define EMSESP_DEFAULT_HA_CLIMATE_FORMAT 1 // current temp
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef EMSESP_DEFAULT_MQTT_QOS
|
#ifndef EMSESP_DEFAULT_MQTT_QOS
|
||||||
#define EMSESP_DEFAULT_MQTT_QOS 0
|
#define EMSESP_DEFAULT_MQTT_QOS 0
|
||||||
#endif
|
#endif
|
||||||
@@ -160,6 +156,10 @@
|
|||||||
#define EMSESP_DEFAULT_PUBLISH_SINGLE false
|
#define EMSESP_DEFAULT_PUBLISH_SINGLE false
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef EMSESP_DEFAULT_PUBLISH_SINGLE2CMD
|
||||||
|
#define EMSESP_DEFAULT_PUBLISH_SINGLE2CMD false
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef EMSESP_DEFAULT_SEND_RESPONSE
|
#ifndef EMSESP_DEFAULT_SEND_RESPONSE
|
||||||
#define EMSESP_DEFAULT_SEND_RESPONSE false
|
#define EMSESP_DEFAULT_SEND_RESPONSE false
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -533,16 +533,26 @@ void EMSdevice::publish_value(void * value_p) {
|
|||||||
for (auto & dv : devicevalues_) {
|
for (auto & dv : devicevalues_) {
|
||||||
if (dv.value_p == value_p && dv.has_state(DeviceValueState::DV_VISIBLE)) {
|
if (dv.value_p == value_p && dv.has_state(DeviceValueState::DV_VISIBLE)) {
|
||||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||||
if ((dv.tag >= DeviceValueTAG::TAG_HC1 && dv.tag <= DeviceValueTAG::TAG_HC8)
|
if (Mqtt::publish_single2cmd()) {
|
||||||
|| (dv.tag >= DeviceValueTAG::TAG_WWC1 && dv.tag <= DeviceValueTAG::TAG_WWC4)) {
|
if ((dv.tag >= DeviceValueTAG::TAG_HC1 && dv.tag <= DeviceValueTAG::TAG_WWC4)) {
|
||||||
|
snprintf(topic,
|
||||||
|
sizeof(topic),
|
||||||
|
"%s/%s/%s",
|
||||||
|
device_type_2_device_name(device_type_).c_str(),
|
||||||
|
tag_to_mqtt(dv.tag).c_str(),
|
||||||
|
read_flash_string(dv.short_name).c_str());
|
||||||
|
} else {
|
||||||
|
snprintf(topic, sizeof(topic), "%s/%s", device_type_2_device_name(device_type_).c_str(), read_flash_string(dv.short_name).c_str());
|
||||||
|
}
|
||||||
|
} else if (Mqtt::is_nested() && dv.tag >= DeviceValueTAG::TAG_HC1) {
|
||||||
snprintf(topic,
|
snprintf(topic,
|
||||||
sizeof(topic),
|
sizeof(topic),
|
||||||
"%s/%s/%s",
|
"%s/%s/%s",
|
||||||
device_type_2_device_name(device_type_).c_str(),
|
Mqtt::tag_to_topic(device_type_, dv.tag).c_str(),
|
||||||
tag_to_mqtt(dv.tag).c_str(),
|
tag_to_mqtt(dv.tag).c_str(),
|
||||||
read_flash_string(dv.short_name).c_str());
|
read_flash_string(dv.short_name).c_str());
|
||||||
} else {
|
} else {
|
||||||
snprintf(topic, sizeof(topic), "%s/%s", device_type_2_device_name(device_type_).c_str(), read_flash_string(dv.short_name).c_str());
|
snprintf(topic, sizeof(topic), "%s/%s", Mqtt::tag_to_topic(device_type_, dv.tag).c_str(), read_flash_string(dv.short_name).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
int8_t divider = (dv.options_size == 1) ? Helpers::atoint(read_flash_string(dv.options[0]).c_str()) : 0;
|
int8_t divider = (dv.options_size == 1) ? Helpers::atoint(read_flash_string(dv.options[0]).c_str()) : 0;
|
||||||
|
|||||||
122
src/mqtt.cpp
122
src/mqtt.cpp
@@ -41,6 +41,7 @@ uint8_t Mqtt::nested_format_;
|
|||||||
std::string Mqtt::discovery_prefix_;
|
std::string Mqtt::discovery_prefix_;
|
||||||
bool Mqtt::send_response_;
|
bool Mqtt::send_response_;
|
||||||
bool Mqtt::publish_single_;
|
bool Mqtt::publish_single_;
|
||||||
|
bool Mqtt::publish_single2cmd_;
|
||||||
|
|
||||||
std::deque<Mqtt::QueuedMqttMessage> Mqtt::mqtt_messages_;
|
std::deque<Mqtt::QueuedMqttMessage> Mqtt::mqtt_messages_;
|
||||||
std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_;
|
std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_;
|
||||||
@@ -264,6 +265,14 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) {
|
|||||||
LOG_DEBUG(F("Received topic `%s`"), topic);
|
LOG_DEBUG(F("Received topic `%s`"), topic);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
// remove HA topics if we don't use discovery
|
||||||
|
if (strncmp(topic, discovery_prefix().c_str(), discovery_prefix().size()) == 0) {
|
||||||
|
if (!ha_enabled_ && len) { // don't ping pong the empty message
|
||||||
|
queue_publish_message(topic, "", true);
|
||||||
|
LOG_DEBUG(F("Remove topic %s"), topic);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// check first againts any of our subscribed topics
|
// check first againts any of our subscribed topics
|
||||||
for (const auto & mf : mqtt_subfunctions_) {
|
for (const auto & mf : mqtt_subfunctions_) {
|
||||||
@@ -290,7 +299,7 @@ void Mqtt::on_message(const char * topic, const char * payload, size_t len) {
|
|||||||
// convert payload into a json doc
|
// convert payload into a json doc
|
||||||
// if the payload doesn't not contain the key 'value' or 'data', treat the whole payload as the 'value'
|
// if the payload doesn't not contain the key 'value' or 'data', treat the whole payload as the 'value'
|
||||||
if (len != 0) {
|
if (len != 0) {
|
||||||
DeserializationError error = deserializeJson(input_doc, message);
|
DeserializationError error = deserializeJson(input_doc, (const char *)message);
|
||||||
if ((!input_doc.containsKey("value") && !input_doc.containsKey("data")) || error) {
|
if ((!input_doc.containsKey("value") && !input_doc.containsKey("data")) || error) {
|
||||||
input_doc.clear();
|
input_doc.clear();
|
||||||
input_doc["value"] = (const char *)message; // always a string
|
input_doc["value"] = (const char *)message; // always a string
|
||||||
@@ -387,15 +396,16 @@ void Mqtt::reset_mqtt() {
|
|||||||
|
|
||||||
void Mqtt::load_settings() {
|
void Mqtt::load_settings() {
|
||||||
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & mqttSettings) {
|
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & mqttSettings) {
|
||||||
mqtt_base_ = mqttSettings.base.c_str(); // Convert String to std::string
|
mqtt_base_ = mqttSettings.base.c_str(); // Convert String to std::string
|
||||||
mqtt_qos_ = mqttSettings.mqtt_qos;
|
mqtt_qos_ = mqttSettings.mqtt_qos;
|
||||||
mqtt_retain_ = mqttSettings.mqtt_retain;
|
mqtt_retain_ = mqttSettings.mqtt_retain;
|
||||||
mqtt_enabled_ = mqttSettings.enabled;
|
mqtt_enabled_ = mqttSettings.enabled;
|
||||||
ha_enabled_ = mqttSettings.ha_enabled;
|
ha_enabled_ = mqttSettings.ha_enabled;
|
||||||
nested_format_ = mqttSettings.nested_format;
|
nested_format_ = mqttSettings.nested_format;
|
||||||
publish_single_ = mqttSettings.publish_single;
|
publish_single_ = mqttSettings.publish_single;
|
||||||
send_response_ = mqttSettings.send_response;
|
publish_single2cmd_ = mqttSettings.publish_single2cmd;
|
||||||
discovery_prefix_ = mqttSettings.discovery_prefix.c_str();
|
send_response_ = mqttSettings.send_response;
|
||||||
|
discovery_prefix_ = mqttSettings.discovery_prefix.c_str();
|
||||||
|
|
||||||
// convert to milliseconds
|
// convert to milliseconds
|
||||||
publish_time_boiler_ = mqttSettings.publish_time_boiler * 1000;
|
publish_time_boiler_ = mqttSettings.publish_time_boiler * 1000;
|
||||||
@@ -444,14 +454,6 @@ void Mqtt::start() {
|
|||||||
if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) {
|
if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) {
|
||||||
LOG_INFO(F("MQTT disconnected: Not authorized"));
|
LOG_INFO(F("MQTT disconnected: Not authorized"));
|
||||||
}
|
}
|
||||||
// remove message with pending ack
|
|
||||||
if (!mqtt_messages_.empty()) {
|
|
||||||
auto mqtt_message = mqtt_messages_.front();
|
|
||||||
if (mqtt_message.packet_id_ != 0) {
|
|
||||||
mqtt_messages_.pop_front();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// mqtt_messages_.clear();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// create will_topic with the base prefixed. It has to be static because asyncmqttclient destroys the reference
|
// create will_topic with the base prefixed. It has to be static because asyncmqttclient destroys the reference
|
||||||
@@ -570,9 +572,23 @@ void Mqtt::on_connect() {
|
|||||||
#endif
|
#endif
|
||||||
publish(F_(info), doc.as<JsonObject>()); // topic called "info"
|
publish(F_(info), doc.as<JsonObject>()); // topic called "info"
|
||||||
|
|
||||||
// create the EMS-ESP device in HA, which is MQTT retained
|
if (ha_enabled_) {
|
||||||
if (ha_enabled()) {
|
queue_unsubscribe_message(discovery_prefix_ + "/climate/" + mqtt_base_ + "/#");
|
||||||
ha_status();
|
queue_unsubscribe_message(discovery_prefix_ + "/sensor/" + mqtt_base_ + "/#");
|
||||||
|
queue_unsubscribe_message(discovery_prefix_ + "/binary_sensor/" + mqtt_base_ + "/#");
|
||||||
|
queue_unsubscribe_message(discovery_prefix_ + "/number/" + mqtt_base_ + "/#");
|
||||||
|
queue_unsubscribe_message(discovery_prefix_ + "/select/" + mqtt_base_ + "/#");
|
||||||
|
queue_unsubscribe_message(discovery_prefix_ + "/switch/" + mqtt_base_ + "/#");
|
||||||
|
EMSESP::reset_mqtt_ha(); // re-create all HA devices if there are any
|
||||||
|
ha_status(); // create the EMS-ESP device in HA, which is MQTT retained
|
||||||
|
} else {
|
||||||
|
queue_subscribe_message(discovery_prefix_ + "/climate/" + mqtt_base_ + "/#");
|
||||||
|
queue_subscribe_message(discovery_prefix_ + "/sensor/" + mqtt_base_ + "/#");
|
||||||
|
queue_subscribe_message(discovery_prefix_ + "/binary_sensor/" + mqtt_base_ + "/#");
|
||||||
|
queue_subscribe_message(discovery_prefix_ + "/number/" + mqtt_base_ + "/#");
|
||||||
|
queue_subscribe_message(discovery_prefix_ + "/select/" + mqtt_base_ + "/#");
|
||||||
|
queue_subscribe_message(discovery_prefix_ + "/switch/" + mqtt_base_ + "/#");
|
||||||
|
LOG_INFO(F("start removing topics %s/+/%s/#"), discovery_prefix_.c_str(), mqtt_base_.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// send initial MQTT messages for some of our services
|
// send initial MQTT messages for some of our services
|
||||||
@@ -582,8 +598,6 @@ void Mqtt::on_connect() {
|
|||||||
// re-subscribe to all custom registered MQTT topics
|
// re-subscribe to all custom registered MQTT topics
|
||||||
resubscribe();
|
resubscribe();
|
||||||
|
|
||||||
EMSESP::reset_mqtt_ha(); // re-create all HA devices if there are any
|
|
||||||
|
|
||||||
publish_retain(F("status"), "online", true); // say we're alive to the Last Will topic, with retain on
|
publish_retain(F("status"), "online", true); // say we're alive to the Last Will topic, with retain on
|
||||||
|
|
||||||
mqtt_publish_fails_ = 0; // reset fail count to 0
|
mqtt_publish_fails_ = 0; // reset fail count to 0
|
||||||
@@ -669,6 +683,7 @@ std::shared_ptr<const MqttMessage> Mqtt::queue_message(const uint8_t operation,
|
|||||||
if (mqtt_messages_.size() >= MAX_MQTT_MESSAGES) {
|
if (mqtt_messages_.size() >= MAX_MQTT_MESSAGES) {
|
||||||
mqtt_messages_.pop_front();
|
mqtt_messages_.pop_front();
|
||||||
LOG_WARNING(F("Queue overflow, removing one message"));
|
LOG_WARNING(F("Queue overflow, removing one message"));
|
||||||
|
mqtt_publish_fails_++;
|
||||||
}
|
}
|
||||||
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
|
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
|
||||||
|
|
||||||
@@ -688,6 +703,11 @@ std::shared_ptr<const MqttMessage> Mqtt::queue_subscribe_message(const std::stri
|
|||||||
return queue_message(Operation::SUBSCRIBE, topic, "", false); // no payload
|
return queue_message(Operation::SUBSCRIBE, topic, "", false); // no payload
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add MQTT unsubscribe message to queue
|
||||||
|
std::shared_ptr<const MqttMessage> Mqtt::queue_unsubscribe_message(const std::string & topic) {
|
||||||
|
return queue_message(Operation::UNSUBSCRIBE, topic, "", false); // no payload
|
||||||
|
}
|
||||||
|
|
||||||
// MQTT Publish, using a user's retain flag
|
// MQTT Publish, using a user's retain flag
|
||||||
void Mqtt::publish(const std::string & topic, const std::string & payload) {
|
void Mqtt::publish(const std::string & topic, const std::string & payload) {
|
||||||
queue_publish_message(topic, payload, mqtt_retain_);
|
queue_publish_message(topic, payload, mqtt_retain_);
|
||||||
@@ -712,11 +732,6 @@ void Mqtt::publish(const std::string & topic, const JsonObject & payload) {
|
|||||||
publish_retain(topic, payload, mqtt_retain_);
|
publish_retain(topic, payload, mqtt_retain_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// no payload
|
|
||||||
void Mqtt::publish(const std::string & topic) {
|
|
||||||
queue_publish_message(topic, "", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// MQTT Publish, using a specific retain flag, topic is a flash string, forcing retain flag
|
// MQTT Publish, using a specific retain flag, topic is a flash string, forcing retain flag
|
||||||
void Mqtt::publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain) {
|
void Mqtt::publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain) {
|
||||||
queue_publish_message(read_flash_string(topic), payload, retain);
|
queue_publish_message(read_flash_string(topic), payload, retain);
|
||||||
@@ -750,7 +765,7 @@ void Mqtt::publish_ha(const std::string & topic) {
|
|||||||
LOG_DEBUG(F("[DEBUG] Publishing empty HA topic=%s"), fulltopic.c_str());
|
LOG_DEBUG(F("[DEBUG] Publishing empty HA topic=%s"), fulltopic.c_str());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
publish(fulltopic);
|
queue_publish_message(fulltopic, "", true); // publish with retain to remove from broker
|
||||||
}
|
}
|
||||||
|
|
||||||
// publish a Home Assistant config topic and payload, with retain flag off.
|
// publish a Home Assistant config topic and payload, with retain flag off.
|
||||||
@@ -792,12 +807,29 @@ void Mqtt::process_queue() {
|
|||||||
snprintf(topic, MQTT_TOPIC_MAX_SIZE, "%s/%s", mqtt_base_.c_str(), message->topic.c_str());
|
snprintf(topic, MQTT_TOPIC_MAX_SIZE, "%s/%s", mqtt_base_.c_str(), message->topic.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if this has already been published and we're waiting for an ACK, don't publish again
|
||||||
|
// it will have a real packet ID
|
||||||
|
if (mqtt_message.packet_id_ > 0) {
|
||||||
|
#if defined(EMSESP_DEBUG)
|
||||||
|
LOG_DEBUG(F("[DEBUG] Waiting for QOS-ACK"));
|
||||||
|
#endif
|
||||||
|
// if we don't get the ack within 10 minutes, republish with new packet_id
|
||||||
|
if (uuid::get_uptime_sec() - last_publish_queue_ < 600) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last_publish_queue_ = uuid::get_uptime_sec();
|
||||||
|
|
||||||
// if we're subscribing...
|
// if we're subscribing...
|
||||||
if (message->operation == Operation::SUBSCRIBE) {
|
if (message->operation == Operation::SUBSCRIBE) {
|
||||||
LOG_DEBUG(F("Subscribing to topic '%s'"), topic);
|
LOG_DEBUG(F("Subscribing to topic '%s'"), topic);
|
||||||
uint16_t packet_id = mqttClient_->subscribe(topic, mqtt_qos_);
|
uint16_t packet_id = mqttClient_->subscribe(topic, mqtt_qos_);
|
||||||
if (!packet_id) {
|
if (!packet_id) {
|
||||||
|
if (++mqtt_messages_.front().retry_count_ < MQTT_PUBLISH_MAX_RETRY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
LOG_ERROR(F("Error subscribing to topic '%s'"), topic);
|
LOG_ERROR(F("Error subscribing to topic '%s'"), topic);
|
||||||
|
mqtt_publish_fails_++; // increment failure counter
|
||||||
}
|
}
|
||||||
|
|
||||||
mqtt_messages_.pop_front(); // remove the message from the queue
|
mqtt_messages_.pop_front(); // remove the message from the queue
|
||||||
@@ -805,12 +837,20 @@ void Mqtt::process_queue() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if this has already been published and we're waiting for an ACK, don't publish again
|
// if we're unsubscribing...
|
||||||
// it will have a real packet ID
|
if (message->operation == Operation::UNSUBSCRIBE) {
|
||||||
if (mqtt_message.packet_id_ > 0) {
|
LOG_DEBUG(F("Subscribing to topic '%s'"), topic);
|
||||||
#if defined(EMSESP_DEBUG)
|
uint16_t packet_id = mqttClient_->unsubscribe(topic);
|
||||||
LOG_DEBUG(F("[DEBUG] Waiting for QOS-ACK"));
|
if (!packet_id) {
|
||||||
#endif
|
if (++mqtt_messages_.front().retry_count_ < MQTT_PUBLISH_MAX_RETRY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG_ERROR(F("Error unsubscribing to topic '%s'"), topic);
|
||||||
|
mqtt_publish_fails_++; // increment failure counter
|
||||||
|
}
|
||||||
|
|
||||||
|
mqtt_messages_.pop_front(); // remove the message from the queue
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -992,8 +1032,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool have_tag = !EMSdevice::tag_to_string(tag).empty();
|
bool have_tag = !EMSdevice::tag_to_string(tag).empty();
|
||||||
bool is_nested = (nested_format_ == 1); // nested_format is 1 if nested, otherwise 2 for single topics
|
|
||||||
|
|
||||||
// build the payload
|
// build the payload
|
||||||
DynamicJsonDocument doc(EMSESP_JSON_SIZE_HA_CONFIG);
|
DynamicJsonDocument doc(EMSESP_JSON_SIZE_HA_CONFIG);
|
||||||
@@ -1069,7 +1108,7 @@ void Mqtt::publish_ha_sensor_config(uint8_t type,
|
|||||||
// value template
|
// value template
|
||||||
// if its nested mqtt format then use the appended entity name, otherwise take the original
|
// if its nested mqtt format then use the appended entity name, otherwise take the original
|
||||||
char val_tpl[75];
|
char val_tpl[75];
|
||||||
if (is_nested) {
|
if (is_nested()) {
|
||||||
snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", new_entity);
|
snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", new_entity);
|
||||||
} else {
|
} else {
|
||||||
snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", read_flash_string(entity).c_str());
|
snprintf(val_tpl, sizeof(val_tpl), "{{value_json.%s}}", read_flash_string(entity).c_str());
|
||||||
@@ -1193,10 +1232,11 @@ const std::string Mqtt::tag_to_topic(uint8_t device_type, uint8_t tag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if there is a tag add it
|
// if there is a tag add it
|
||||||
if ((EMSdevice::tag_to_mqtt(tag).empty()) || ((nested_format_ == 1) && (device_type != EMSdevice::DeviceType::BOILER))) {
|
if (!EMSdevice::tag_to_mqtt(tag).empty()
|
||||||
return EMSdevice::device_type_2_device_name(device_type) + "_data";
|
&& ((device_type == EMSdevice::DeviceType::BOILER && tag == DeviceValueTAG::TAG_DEVICE_DATA_WW) || (!is_nested() && tag >= DeviceValueTAG::TAG_HC1))) {
|
||||||
} else {
|
|
||||||
return EMSdevice::device_type_2_device_name(device_type) + "_data_" + EMSdevice::tag_to_mqtt(tag);
|
return EMSdevice::device_type_2_device_name(device_type) + "_data_" + EMSdevice::tag_to_mqtt(tag);
|
||||||
|
} else {
|
||||||
|
return EMSdevice::device_type_2_device_name(device_type) + "_data";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
39
src/mqtt.h
39
src/mqtt.h
@@ -29,9 +29,6 @@
|
|||||||
|
|
||||||
using uuid::console::Shell;
|
using uuid::console::Shell;
|
||||||
|
|
||||||
// time between HA publishes
|
|
||||||
#define MQTT_HA_PUBLISH_DELAY 50
|
|
||||||
|
|
||||||
// size of queue
|
// size of queue
|
||||||
#define MAX_MQTT_MESSAGES 300
|
#define MAX_MQTT_MESSAGES 300
|
||||||
|
|
||||||
@@ -70,16 +67,10 @@ class Mqtt {
|
|||||||
void set_publish_time_sensor(uint16_t publish_time);
|
void set_publish_time_sensor(uint16_t publish_time);
|
||||||
bool get_publish_onchange(uint8_t device_type);
|
bool get_publish_onchange(uint8_t device_type);
|
||||||
|
|
||||||
enum Operation { PUBLISH, SUBSCRIBE };
|
enum Operation : uint8_t { PUBLISH, SUBSCRIBE, UNSUBSCRIBE };
|
||||||
|
enum NestedFormat : uint8_t { NESTED = 1, SINGLE };
|
||||||
|
|
||||||
enum HA_Climate_Format : uint8_t {
|
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = FACTORY_MQTT_MAX_TOPIC_LENGTH; // fixed, not a user setting anymore
|
||||||
CURRENT = 1, // 1
|
|
||||||
SETPOINT, // 2
|
|
||||||
ZERO // 3
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 128; // note this should really match the user setting in mqttSettings.maxTopicLength
|
|
||||||
|
|
||||||
static void on_connect();
|
static void on_connect();
|
||||||
|
|
||||||
@@ -92,7 +83,6 @@ class Mqtt {
|
|||||||
static void publish(const std::string & topic, const JsonObject & payload);
|
static void publish(const std::string & topic, const JsonObject & payload);
|
||||||
static void publish(const __FlashStringHelper * topic, const JsonObject & payload);
|
static void publish(const __FlashStringHelper * topic, const JsonObject & payload);
|
||||||
static void publish(const __FlashStringHelper * topic, const std::string & payload);
|
static void publish(const __FlashStringHelper * topic, const std::string & payload);
|
||||||
static void publish(const std::string & topic);
|
|
||||||
static void publish_retain(const std::string & topic, const JsonObject & payload, bool retain);
|
static void publish_retain(const std::string & topic, const JsonObject & payload, bool retain);
|
||||||
static void publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain);
|
static void publish_retain(const __FlashStringHelper * topic, const std::string & payload, bool retain);
|
||||||
static void publish_retain(const __FlashStringHelper * topic, const JsonObject & payload, bool retain);
|
static void publish_retain(const __FlashStringHelper * topic, const JsonObject & payload, bool retain);
|
||||||
@@ -125,10 +115,6 @@ class Mqtt {
|
|||||||
|
|
||||||
static void ha_status();
|
static void ha_status();
|
||||||
|
|
||||||
void disconnect() {
|
|
||||||
mqttClient_->disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(EMSESP_DEBUG)
|
#if defined(EMSESP_DEBUG)
|
||||||
void incoming(const char * topic, const char * payload = ""); // for testing only
|
void incoming(const char * topic, const char * payload = ""); // for testing only
|
||||||
#endif
|
#endif
|
||||||
@@ -179,13 +165,10 @@ class Mqtt {
|
|||||||
|
|
||||||
static void reset_mqtt();
|
static void reset_mqtt();
|
||||||
|
|
||||||
// nested_format is 1 if nested, otherwise 2 for single topics
|
|
||||||
static uint8_t nested_format() {
|
|
||||||
return nested_format_;
|
|
||||||
}
|
|
||||||
static bool is_nested() {
|
static bool is_nested() {
|
||||||
return nested_format_ == 1;
|
return nested_format_ == NestedFormat::NESTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nested_format(uint8_t nested_format) {
|
static void nested_format(uint8_t nested_format) {
|
||||||
nested_format_ = nested_format;
|
nested_format_ = nested_format;
|
||||||
}
|
}
|
||||||
@@ -193,6 +176,11 @@ class Mqtt {
|
|||||||
static bool publish_single() {
|
static bool publish_single() {
|
||||||
return publish_single_;
|
return publish_single_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool publish_single2cmd() {
|
||||||
|
return publish_single2cmd_;
|
||||||
|
}
|
||||||
|
|
||||||
static void publish_single(bool publish_single) {
|
static void publish_single(bool publish_single) {
|
||||||
publish_single_ = publish_single;
|
publish_single_ = publish_single;
|
||||||
}
|
}
|
||||||
@@ -200,6 +188,7 @@ class Mqtt {
|
|||||||
static bool ha_enabled() {
|
static bool ha_enabled() {
|
||||||
return ha_enabled_;
|
return ha_enabled_;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ha_enabled(bool ha_enabled) {
|
static void ha_enabled(bool ha_enabled) {
|
||||||
ha_enabled_ = ha_enabled;
|
ha_enabled_ = ha_enabled;
|
||||||
}
|
}
|
||||||
@@ -207,6 +196,7 @@ class Mqtt {
|
|||||||
static bool send_response() {
|
static bool send_response() {
|
||||||
return send_response_;
|
return send_response_;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void send_response(bool send_response) {
|
static void send_response(bool send_response) {
|
||||||
send_response_ = send_response;
|
send_response_ = send_response;
|
||||||
}
|
}
|
||||||
@@ -232,7 +222,7 @@ class Mqtt {
|
|||||||
uint16_t packet_id_;
|
uint16_t packet_id_;
|
||||||
|
|
||||||
~QueuedMqttMessage() = default;
|
~QueuedMqttMessage() = default;
|
||||||
QueuedMqttMessage(uint16_t id, std::shared_ptr<MqttMessage> && content)
|
QueuedMqttMessage(uint32_t id, std::shared_ptr<MqttMessage> && content)
|
||||||
: id_(id)
|
: id_(id)
|
||||||
, content_(std::move(content)) {
|
, content_(std::move(content)) {
|
||||||
retry_count_ = 0;
|
retry_count_ = 0;
|
||||||
@@ -254,6 +244,7 @@ class Mqtt {
|
|||||||
static std::shared_ptr<const MqttMessage> queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain);
|
static std::shared_ptr<const MqttMessage> queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain);
|
||||||
static std::shared_ptr<const MqttMessage> queue_publish_message(const std::string & topic, const std::string & payload, bool retain);
|
static std::shared_ptr<const MqttMessage> queue_publish_message(const std::string & topic, const std::string & payload, bool retain);
|
||||||
static std::shared_ptr<const MqttMessage> queue_subscribe_message(const std::string & topic);
|
static std::shared_ptr<const MqttMessage> queue_subscribe_message(const std::string & topic);
|
||||||
|
static std::shared_ptr<const MqttMessage> queue_unsubscribe_message(const std::string & topic);
|
||||||
|
|
||||||
void on_publish(uint16_t packetId);
|
void on_publish(uint16_t packetId);
|
||||||
void on_message(const char * topic, const char * payload, size_t len);
|
void on_message(const char * topic, const char * payload, size_t len);
|
||||||
@@ -281,6 +272,7 @@ class Mqtt {
|
|||||||
uint32_t last_publish_mixer_ = 0;
|
uint32_t last_publish_mixer_ = 0;
|
||||||
uint32_t last_publish_other_ = 0;
|
uint32_t last_publish_other_ = 0;
|
||||||
uint32_t last_publish_sensor_ = 0;
|
uint32_t last_publish_sensor_ = 0;
|
||||||
|
uint32_t last_publish_queue_ = 0;
|
||||||
|
|
||||||
static bool connecting_;
|
static bool connecting_;
|
||||||
static bool initialized_;
|
static bool initialized_;
|
||||||
@@ -303,6 +295,7 @@ class Mqtt {
|
|||||||
static uint8_t nested_format_;
|
static uint8_t nested_format_;
|
||||||
static std::string discovery_prefix_;
|
static std::string discovery_prefix_;
|
||||||
static bool publish_single_;
|
static bool publish_single_;
|
||||||
|
static bool publish_single2cmd_;
|
||||||
static bool send_response_;
|
static bool send_response_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ void WebDataService::sensor_data(AsyncWebServerRequest * request) {
|
|||||||
obj["t"] = sensor.type();
|
obj["t"] = sensor.type();
|
||||||
|
|
||||||
if (sensor.type() != AnalogSensor::AnalogType::NOTUSED) {
|
if (sensor.type() != AnalogSensor::AnalogType::NOTUSED) {
|
||||||
obj["v"] = Helpers::round2(sensor.value(), 1); // is optional and is a float
|
obj["v"] = Helpers::round2(sensor.value(), 0); // is optional and is a float
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user