ESPHome 2026.1.0-dev
Loading...
Searching...
No Matches
mqtt_component.cpp
Go to the documentation of this file.
1#include "mqtt_component.h"
2
3#ifdef USE_MQTT
4
7#include "esphome/core/log.h"
9
10#include "mqtt_const.h"
11
12namespace esphome {
13namespace mqtt {
14
15static const char *const TAG = "mqtt.component";
16
17void MQTTComponent::set_qos(uint8_t qos) { this->qos_ = qos; }
18
19void MQTTComponent::set_subscribe_qos(uint8_t qos) { this->subscribe_qos_ = qos; }
20
21void MQTTComponent::set_retain(bool retain) { this->retain_ = retain; }
22
23std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const {
24 std::string sanitized_name = str_sanitize(App.get_name());
25 return discovery_info.prefix + "/" + this->component_type() + "/" + sanitized_name + "/" +
26 this->get_default_object_id_() + "/config";
27}
28
29std::string MQTTComponent::get_default_topic_for_(const std::string &suffix) const {
30 const std::string &topic_prefix = global_mqtt_client->get_topic_prefix();
31 if (topic_prefix.empty()) {
32 // If the topic_prefix is null, the default topic should be null
33 return "";
34 }
35
36 return topic_prefix + "/" + this->component_type() + "/" + this->get_default_object_id_() + "/" + suffix;
37}
38
41 return this->custom_state_topic_.str();
42 return this->get_default_topic_for_("state");
43}
44
47 return this->custom_command_topic_.str();
48 return this->get_default_topic_for_("command");
49}
50
51bool MQTTComponent::publish(const std::string &topic, const std::string &payload) {
52 if (topic.empty())
53 return false;
54 return global_mqtt_client->publish(topic, payload, this->qos_, this->retain_);
55}
56
57bool MQTTComponent::publish_json(const std::string &topic, const json::json_build_t &f) {
58 if (topic.empty())
59 return false;
60 return global_mqtt_client->publish_json(topic, f, this->qos_, this->retain_);
61}
62
65
66 if (discovery_info.clean) {
67 ESP_LOGV(TAG, "'%s': Cleaning discovery", this->friendly_name_().c_str());
68 return global_mqtt_client->publish(this->get_discovery_topic_(discovery_info), "", 0, this->qos_, true);
69 }
70
71 ESP_LOGV(TAG, "'%s': Sending discovery", this->friendly_name_().c_str());
72
73 // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
75 this->get_discovery_topic_(discovery_info),
76 [this](JsonObject root) {
78 config.state_topic = true;
79 config.command_topic = true;
80
81 this->send_discovery(root, config);
82 // Set subscription QoS (default is 0)
83 if (this->subscribe_qos_ != 0) {
84 root[MQTT_QOS] = this->subscribe_qos_;
85 }
86
87 // Fields from EntityBase
88 root[MQTT_NAME] = this->get_entity()->has_own_name() ? this->friendly_name_() : "";
89
90 if (this->is_disabled_by_default_())
91 root[MQTT_ENABLED_BY_DEFAULT] = false;
92 // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
93 const auto icon_ref = this->get_icon_ref_();
94 if (!icon_ref.empty()) {
95 root[MQTT_ICON] = icon_ref;
96 }
97 // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
98
99 const auto entity_category = this->get_entity()->get_entity_category();
100 switch (entity_category) {
102 break;
105 root[MQTT_ENTITY_CATEGORY] = entity_category == ENTITY_CATEGORY_CONFIG ? "config" : "diagnostic";
106 break;
107 }
108
109 if (config.state_topic)
110 root[MQTT_STATE_TOPIC] = this->get_state_topic_();
111 if (config.command_topic)
113 if (this->command_retain_)
114 root[MQTT_COMMAND_RETAIN] = true;
115
116 const Availability &avail =
117 this->availability_ == nullptr ? global_mqtt_client->get_availability() : *this->availability_;
118 if (!avail.topic.empty()) {
119 root[MQTT_AVAILABILITY_TOPIC] = avail.topic;
120 if (avail.payload_available != "online")
122 if (avail.payload_not_available != "offline")
124 }
125
128 char friendly_name_hash[9];
129 sprintf(friendly_name_hash, "%08" PRIx32, fnv1_hash(this->friendly_name_()));
130 friendly_name_hash[8] = 0; // ensure the hash-string ends with null
131 root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash;
132 } else {
133 // default to almost-unique ID. It's a hack but the only way to get that
134 // gorgeous device registry view.
135 root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_();
136 }
137
138 const std::string &node_name = App.get_name();
140 root[MQTT_OBJECT_ID] = node_name + "_" + this->get_default_object_id_();
141
142 const std::string &friendly_name_ref = App.get_friendly_name();
143 const std::string &node_friendly_name = friendly_name_ref.empty() ? node_name : friendly_name_ref;
144 std::string node_area = App.get_area();
145
146 JsonObject device_info = root[MQTT_DEVICE].to<JsonObject>();
147 const auto mac = get_mac_address();
148 device_info[MQTT_DEVICE_IDENTIFIERS] = mac;
149 device_info[MQTT_DEVICE_NAME] = node_friendly_name;
150#ifdef ESPHOME_PROJECT_NAME
151 device_info[MQTT_DEVICE_SW_VERSION] = ESPHOME_PROJECT_VERSION " (ESPHome " ESPHOME_VERSION ")";
152 const char *model = std::strchr(ESPHOME_PROJECT_NAME, '.');
153 device_info[MQTT_DEVICE_MODEL] = model == nullptr ? ESPHOME_BOARD : model + 1;
154 device_info[MQTT_DEVICE_MANUFACTURER] =
155 model == nullptr ? ESPHOME_PROJECT_NAME : std::string(ESPHOME_PROJECT_NAME, model - ESPHOME_PROJECT_NAME);
156#else
157 static const char ver_fmt[] PROGMEM = ESPHOME_VERSION " (config hash 0x%08" PRIx32 ")";
158#ifdef USE_ESP8266
159 char fmt_buf[sizeof(ver_fmt)];
160 strcpy_P(fmt_buf, ver_fmt);
161 const char *fmt = fmt_buf;
162#else
163 const char *fmt = ver_fmt;
164#endif
166 device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD;
167#if defined(USE_ESP8266) || defined(USE_ESP32)
168 device_info[MQTT_DEVICE_MANUFACTURER] = "Espressif";
169#elif defined(USE_RP2040)
170 device_info[MQTT_DEVICE_MANUFACTURER] = "Raspberry Pi";
171#elif defined(USE_BK72XX)
172 device_info[MQTT_DEVICE_MANUFACTURER] = "Beken";
173#elif defined(USE_RTL87XX)
174 device_info[MQTT_DEVICE_MANUFACTURER] = "Realtek";
175#elif defined(USE_HOST)
176 device_info[MQTT_DEVICE_MANUFACTURER] = "Host";
177#endif
178#endif
179 if (!node_area.empty()) {
180 device_info[MQTT_DEVICE_SUGGESTED_AREA] = node_area;
181 }
182
183 device_info[MQTT_DEVICE_CONNECTIONS][0][0] = "mac";
184 device_info[MQTT_DEVICE_CONNECTIONS][0][1] = mac;
185 },
186 this->qos_, discovery_info.retain);
187 // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
188}
189
190uint8_t MQTTComponent::get_qos() const { return this->qos_; }
191
192bool MQTTComponent::get_retain() const { return this->retain_; }
193
197
201
202void MQTTComponent::subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos) {
203 global_mqtt_client->subscribe(topic, std::move(callback), qos);
204}
205
206void MQTTComponent::subscribe_json(const std::string &topic, const mqtt_json_callback_t &callback, uint8_t qos) {
207 global_mqtt_client->subscribe_json(topic, callback, qos);
208}
209
211
214void MQTTComponent::set_custom_state_topic(const char *custom_state_topic) {
215 this->custom_state_topic_ = StringRef(custom_state_topic);
216 this->has_custom_state_topic_ = true;
217}
218void MQTTComponent::set_custom_command_topic(const char *custom_command_topic) {
219 this->custom_command_topic_ = StringRef(custom_command_topic);
220 this->has_custom_command_topic_ = true;
221}
222void MQTTComponent::set_command_retain(bool command_retain) { this->command_retain_ = command_retain; }
223
224void MQTTComponent::set_availability(std::string topic, std::string payload_available,
225 std::string payload_not_available) {
226 this->availability_ = make_unique<Availability>();
227 this->availability_->topic = std::move(topic);
228 this->availability_->payload_available = std::move(payload_available);
229 this->availability_->payload_not_available = std::move(payload_not_available);
230}
233 if (this->is_internal())
234 return;
235
236 this->setup();
237
239
240 if (!this->is_connected_())
241 return;
242
243 if (this->is_discovery_enabled()) {
244 if (!this->send_discovery_()) {
245 this->schedule_resend_state();
246 }
247 }
248 if (!this->send_initial_state()) {
249 this->schedule_resend_state();
250 }
251}
252
254 if (this->is_internal())
255 return;
256
257 this->loop();
258
259 if (!this->resend_state_ || !this->is_connected_()) {
260 return;
261 }
262
263 this->resend_state_ = false;
264 if (this->is_discovery_enabled()) {
265 if (!this->send_discovery_()) {
266 this->schedule_resend_state();
267 }
268 }
269 if (!this->send_initial_state()) {
270 this->schedule_resend_state();
271 }
272}
274 if (this->is_internal())
275 return;
276
277 this->dump_config();
278}
281
282// Pull these properties from EntityBase if not overridden
283std::string MQTTComponent::friendly_name_() const { return this->get_entity()->get_name(); }
287 if (this->has_custom_state_topic_) {
288 // If the custom state_topic is null, return true as it is internal and should not publish
289 // else, return false, as it is explicitly set to a topic, so it is not internal and should publish
290 return this->get_state_topic_().empty();
291 }
292
293 if (this->has_custom_command_topic_) {
294 // If the custom command_topic is null, return true as it is internal and should not publish
295 // else, return false, as it is explicitly set to a topic, so it is not internal and should publish
296 return this->get_command_topic_().empty();
297 }
298
299 // No custom topics have been set
300 if (this->get_default_topic_for_("").empty()) {
301 // If the default topic prefix is null, then the component, by default, is internal and should not publish
302 return true;
303 }
304
305 // Use ESPHome's component internal state if topic_prefix is not null with no custom state_topic or command_topic
306 return this->get_entity()->is_internal();
307}
308
309} // namespace mqtt
310} // namespace esphome
311
312#endif // USE_MQTT
const std::string & get_friendly_name() const
Get the friendly name of this Application set by pre_setup().
constexpr uint32_t get_config_hash()
Get the config hash as a 32-bit integer.
const char * get_area() const
Get the area of this Application set by pre_setup().
const std::string & get_name() const
Get the name of this Application set by pre_setup().
virtual void setup()
Where the component's initialization should happen.
virtual void dump_config()
virtual void loop()
This method will be called repeatedly.
bool has_own_name() const
Definition entity_base.h:33
bool is_internal() const
Definition entity_base.h:64
const StringRef & get_name() const
StringRef get_icon_ref() const
Definition entity_base.h:85
bool is_disabled_by_default() const
Definition entity_base.h:70
EntityCategory get_entity_category() const
Definition entity_base.h:74
StringRef is a reference to a string owned by something else.
Definition string_ref.h:22
std::string str() const
Definition string_ref.h:74
const std::string & get_topic_prefix() const
Get the topic prefix of this device, using default if necessary.
void subscribe_json(const std::string &topic, const mqtt_json_callback_t &callback, uint8_t qos=0)
Subscribe to a MQTT topic and automatically parse JSON payload.
void register_mqtt_component(MQTTComponent *component)
bool publish(const MQTTMessage &message)
Publish a MQTTMessage.
const MQTTDiscoveryInfo & get_discovery_info() const
Get Home Assistant discovery info.
void subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos=0)
Subscribe to an MQTT topic and call callback when a message is received.
const Availability & get_availability()
bool publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos=0, bool retain=false)
Construct and send a JSON MQTT message.
std::unique_ptr< Availability > availability_
bool is_disabled_by_default_() const
Get whether the underlying Entity is disabled by default.
MQTTComponent()
Constructs a MQTTComponent.
void set_custom_state_topic(const char *custom_state_topic)
Set a custom state topic. Set to "" for default behavior.
std::string get_default_object_id_() const
Generate the Home Assistant MQTT discovery object id by automatically transforming the friendly name.
void set_qos(uint8_t qos)
Set QOS for state messages.
void schedule_resend_state()
Internal method for the MQTT client base to schedule a resend of the state on reconnect.
bool publish(const std::string &topic, const std::string &payload)
Send a MQTT message.
void set_command_retain(bool command_retain)
Set whether command message should be retained.
bool send_discovery_()
Internal method to start sending discovery info, this will call send_discovery().
void set_subscribe_qos(uint8_t qos)
Set the QOS for subscribe messages (used in discovery).
void subscribe_json(const std::string &topic, const mqtt_json_callback_t &callback, uint8_t qos=0)
Subscribe to a MQTT topic and automatically parse JSON payload.
void call_setup() override
Override setup_ so that we can call send_discovery() when needed.
bool publish_json(const std::string &topic, const json::json_build_t &f)
Construct and send a JSON MQTT message.
std::string get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const
Helper method to get the discovery topic for this component.
std::string get_default_topic_for_(const std::string &suffix) const
Get this components state/command/... topic.
virtual std::string component_type() const =0
Override this method to return the component type (e.g. "light", "sensor", ...)
void set_retain(bool retain)
Set whether state message should be retained.
virtual const EntityBase * get_entity() const =0
Gets the Entity served by this MQTT component.
std::string get_state_topic_() const
Get the MQTT topic that new states will be shared to.
virtual void send_discovery(JsonObject root, SendDiscoveryConfig &config)=0
Send discovery info the Home Assistant, override this.
std::string friendly_name_() const
Get the friendly name of this MQTT component.
virtual bool send_initial_state()=0
void disable_discovery()
Disable discovery. Sets friendly name to "".
float get_setup_priority() const override
MQTT_COMPONENT setup priority.
void set_availability(std::string topic, std::string payload_available, std::string payload_not_available)
Set the Home Assistant availability data.
void set_custom_command_topic(const char *custom_command_topic)
Set a custom command topic. Set to "" for default behavior.
std::string get_command_topic_() const
Get the MQTT topic for listening to commands.
void subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos=0)
Subscribe to a MQTT topic.
StringRef get_icon_ref_() const
Get the icon field of this component as StringRef.
std::function< void(JsonObject)> json_build_t
Callback function typedef for building JsonObjects.
Definition json_util.h:46
constexpr const char *const MQTT_COMMAND_RETAIN
Definition mqtt_const.h:48
@ MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR
Definition mqtt_client.h:77
constexpr const char *const MQTT_COMMAND_TOPIC
Definition mqtt_const.h:50
@ MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR
Definition mqtt_client.h:71
constexpr const char *const MQTT_DEVICE_IDENTIFIERS
Definition mqtt_const.h:60
constexpr const char *const MQTT_PAYLOAD_AVAILABLE
Definition mqtt_const.h:138
constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE
Definition mqtt_const.h:149
constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA
Definition mqtt_const.h:64
std::function< void(const std::string &, JsonObject)> mqtt_json_callback_t
Definition mqtt_client.h:40
constexpr const char *const MQTT_DEVICE_SW_VERSION
Definition mqtt_const.h:65
constexpr const char *const MQTT_DEVICE_NAME
Definition mqtt_const.h:63
constexpr const char *const MQTT_UNIQUE_ID
Definition mqtt_const.h:263
std::function< void(const std::string &, const std::string &)> mqtt_callback_t
Callback for MQTT subscriptions.
Definition mqtt_client.h:39
constexpr const char *const MQTT_STATE_TOPIC
Definition mqtt_const.h:223
constexpr const char *const MQTT_QOS
Definition mqtt_const.h:186
MQTTClientComponent * global_mqtt_client
constexpr const char *const MQTT_ENTITY_CATEGORY
Definition mqtt_const.h:77
constexpr const char *const MQTT_DEVICE_MODEL
Definition mqtt_const.h:62
constexpr const char *const MQTT_NAME
Definition mqtt_const.h:122
constexpr const char *const MQTT_DEVICE_MANUFACTURER
Definition mqtt_const.h:61
constexpr const char *const MQTT_ENABLED_BY_DEFAULT
Definition mqtt_const.h:76
constexpr const char *const MQTT_AVAILABILITY_TOPIC
Definition mqtt_const.h:20
constexpr const char *const MQTT_DEVICE_CONNECTIONS
Definition mqtt_const.h:59
constexpr const char *const MQTT_ICON
Definition mqtt_const.h:101
constexpr const char *const MQTT_DEVICE
Definition mqtt_const.h:57
constexpr const char *const MQTT_OBJECT_ID
Definition mqtt_const.h:123
const float AFTER_CONNECTION
For components that should be initialized after a data connection (API/MQTT) is connected.
Definition component.cpp:89
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string str_sanitize(const std::string &str)
Sanitizes the input string by removing all characters but alphanumerics, dashes and underscores.
Definition helpers.cpp:199
uint32_t fnv1_hash(const char *str)
Calculate a FNV-1 hash of str.
Definition helpers.cpp:147
@ ENTITY_CATEGORY_NONE
Definition entity_base.h:20
@ ENTITY_CATEGORY_CONFIG
Definition entity_base.h:21
@ ENTITY_CATEGORY_DIAGNOSTIC
Definition entity_base.h:22
std::string str_sprintf(const char *fmt,...)
Definition helpers.cpp:220
std::string get_mac_address()
Get the device MAC address as a string, in lowercase hex notation.
Definition helpers.cpp:691
Application App
Global storage of Application pointer - only one Application can exist.
std::string str_snake_case(const std::string &str)
Convert the string to snake case (lowercase with underscores).
Definition helpers.cpp:192
Simple data struct for Home Assistant component availability.
Definition mqtt_client.h:62
std::string payload_not_available
Definition mqtt_client.h:65
std::string topic
Empty means disabled.
Definition mqtt_client.h:63
Internal struct for MQTT Home Assistant discovery.
Definition mqtt_client.h:84
MQTTDiscoveryUniqueIdGenerator unique_id_generator
Definition mqtt_client.h:89
std::string prefix
The Home Assistant discovery prefix. Empty means disabled.
Definition mqtt_client.h:85
MQTTDiscoveryObjectIdGenerator object_id_generator
Definition mqtt_client.h:90
bool retain
Whether to retain discovery messages.
Definition mqtt_client.h:86
Simple Helper struct used for Home Assistant MQTT send_discovery().
bool command_topic
If the command topic should be included. Default to true.
bool state_topic
If the state topic should be included. Defaults to true.
const uint8_t ESPHOME_WEBSERVER_INDEX_HTML[] PROGMEM
Definition web_server.h:27