ESPHome 2026.3.2
Loading...
Searching...
No Matches
mqtt_client.cpp
Go to the documentation of this file.
1#include "mqtt_client.h"
2
3#ifdef USE_MQTT
4
5#include <utility>
10#include "esphome/core/log.h"
13#ifdef USE_LOGGER
15#endif
16#include "lwip/dns.h"
17#include "lwip/err.h"
18#include "mqtt_component.h"
19
20#ifdef USE_API
22#endif
23#ifdef USE_DASHBOARD_IMPORT
25#endif
26
27namespace esphome::mqtt {
28
29static const char *const TAG = "mqtt";
30
31// Maximum number of MQTT component resends per loop iteration.
32// Limits work to avoid triggering the task watchdog on reconnect.
33static constexpr uint8_t MAX_RESENDS_PER_LOOP = 8;
34
35// Disconnect reason strings indexed by MQTTClientDisconnectReason enum (0-8)
36PROGMEM_STRING_TABLE(MQTTDisconnectReasonStrings, "TCP disconnected", "Unacceptable Protocol Version",
37 "Identifier Rejected", "Server Unavailable", "Malformed Credentials", "Not Authorized",
38 "Not Enough Space", "TLS Bad Fingerprint", "DNS Resolve Error", "Unknown");
39
41 global_mqtt_client = this;
42 char mac_addr[MAC_ADDRESS_BUFFER_SIZE];
44 this->credentials_.client_id = make_name_with_suffix(App.get_name(), '-', mac_addr, MAC_ADDRESS_BUFFER_SIZE - 1);
45}
46
47// Connection
50 [this](const char *topic, const char *payload, size_t len, size_t index, size_t total) {
51 if (index == 0) {
52 this->payload_buffer_.clear();
53 this->payload_buffer_.reserve(total);
54 }
55
56 // append new payload, may contain incomplete MQTT message
57 this->payload_buffer_.append(payload, len);
58
59 // MQTT fully received
60 if (len + index == total) {
61 this->on_message(topic, this->payload_buffer_);
62 this->payload_buffer_.clear();
63 }
64 });
66 if (this->state_ == MQTT_CLIENT_DISABLED)
67 return;
69 this->disconnect_reason_ = reason;
70 });
71#ifdef USE_LOGGER
72 if (this->is_log_message_enabled() && logger::global_logger != nullptr) {
74 this, [](void *self, uint8_t level, const char *tag, const char *message, size_t message_len) {
75 static_cast<MQTTClientComponent *>(self)->on_log(level, tag, message, message_len);
76 });
77 }
78#endif
79
80 if (this->is_discovery_ip_enabled()) {
81 this->subscribe(
82 "esphome/discover", [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); },
83 2);
84
85 // Format topic on stack - subscribe() copies it
86 // "esphome/ping/" (13) + name (ESPHOME_DEVICE_NAME_MAX_LEN) + null (1)
87 constexpr size_t ping_topic_buffer_size = 13 + ESPHOME_DEVICE_NAME_MAX_LEN + 1;
88 char ping_topic[ping_topic_buffer_size];
89 buf_append_printf(ping_topic, sizeof(ping_topic), 0, "esphome/ping/%s", App.get_name().c_str());
90 this->subscribe(
91 ping_topic, [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2);
92 }
93
94 if (this->enable_on_boot_) {
95 this->enable();
96 }
97}
98
100 if (!this->is_connected() or !this->is_discovery_ip_enabled()) {
101 return;
102 }
103 // Format topic on stack to avoid heap allocation
104 // "esphome/discover/" (17) + name (ESPHOME_DEVICE_NAME_MAX_LEN) + null (1)
105 constexpr size_t topic_buffer_size = 17 + ESPHOME_DEVICE_NAME_MAX_LEN + 1;
106 char topic[topic_buffer_size];
107 buf_append_printf(topic, sizeof(topic), 0, "esphome/discover/%s", App.get_name().c_str());
108
109 // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson
110 this->publish_json(
111 topic,
112 [](JsonObject root) {
113 uint8_t index = 0;
114 for (auto &ip : network::get_ip_addresses()) {
115 if (ip.is_set()) {
116 char key[8]; // "ip" + up to 3 digits + null
117 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
118 if (index == 0) {
119 key[0] = 'i';
120 key[1] = 'p';
121 key[2] = '\0';
122 } else {
123 buf_append_printf(key, sizeof(key), 0, "ip%u", index);
124 }
125 ip.str_to(ip_buf);
126 root[key] = ip_buf;
127 index++;
128 }
129 }
130 root[ESPHOME_F("name")] = App.get_name();
131 if (!App.get_friendly_name().empty()) {
132 root[ESPHOME_F("friendly_name")] = App.get_friendly_name();
133 }
134#ifdef USE_API
135 root[ESPHOME_F("port")] = api::global_api_server->get_port();
136#endif
137 root[ESPHOME_F("version")] = ESPHOME_VERSION;
138 char mac_buf[MAC_ADDRESS_BUFFER_SIZE];
140 root[ESPHOME_F("mac")] = mac_buf;
141
142#ifdef USE_ESP8266
143 root[ESPHOME_F("platform")] = ESPHOME_F("ESP8266");
144#endif
145#ifdef USE_ESP32
146 root[ESPHOME_F("platform")] = ESPHOME_F("ESP32");
147#endif
148#ifdef USE_LIBRETINY
149 root[ESPHOME_F("platform")] = lt_cpu_get_model_name();
150#endif
151
152 root[ESPHOME_F("board")] = ESPHOME_BOARD;
153#if defined(USE_WIFI)
154 root[ESPHOME_F("network")] = ESPHOME_F("wifi");
155#elif defined(USE_ETHERNET)
156 root[ESPHOME_F("network")] = ESPHOME_F("ethernet");
157#endif
158
159#ifdef ESPHOME_PROJECT_NAME
160 root[ESPHOME_F("project_name")] = ESPHOME_PROJECT_NAME;
161 root[ESPHOME_F("project_version")] = ESPHOME_PROJECT_VERSION;
162#endif // ESPHOME_PROJECT_NAME
163
164#ifdef USE_DASHBOARD_IMPORT
165 root[ESPHOME_F("package_import_url")] = dashboard_import::get_package_import_url();
166#endif
167
168#ifdef USE_API_NOISE
169 root[api::global_api_server->get_noise_ctx().has_psk() ? ESPHOME_F("api_encryption")
170 : ESPHOME_F("api_encryption_supported")] =
171 ESPHOME_F("Noise_NNpsk0_25519_ChaChaPoly_SHA256");
172#endif
173 },
174 2, this->discovery_info_.retain);
175 // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks)
176}
177
178#ifdef USE_LOGGER
179void MQTTClientComponent::on_log(uint8_t level, const char *tag, const char *message, size_t message_len) {
180 (void) tag;
181 if (level <= this->log_level_ && this->is_connected()) {
182 this->publish(this->log_message_.topic.c_str(), message, message_len, this->log_message_.qos,
183 this->log_message_.retain);
184 }
185}
186#endif
187
189 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
190 // clang-format off
191 ESP_LOGCONFIG(TAG,
192 "MQTT:\n"
193 " Server Address: %s:%u (%s)\n"
194 " Username: " LOG_SECRET("'%s'") "\n"
195 " Client ID: " LOG_SECRET("'%s'") "\n"
196 " Clean Session: %s",
197 this->credentials_.address.c_str(), this->credentials_.port, this->ip_.str_to(ip_buf),
198 this->credentials_.username.c_str(), this->credentials_.client_id.c_str(),
199 YESNO(this->credentials_.clean_session));
200 // clang-format on
201 if (this->is_discovery_ip_enabled()) {
202 ESP_LOGCONFIG(TAG, " Discovery IP enabled");
203 }
204 if (!this->discovery_info_.prefix.empty()) {
205 ESP_LOGCONFIG(TAG,
206 " Discovery prefix: '%s'\n"
207 " Discovery retain: %s",
208 this->discovery_info_.prefix.c_str(), YESNO(this->discovery_info_.retain));
209 }
210 ESP_LOGCONFIG(TAG, " Topic Prefix: '%s'", this->topic_prefix_.c_str());
211 if (!this->log_message_.topic.empty()) {
212 ESP_LOGCONFIG(TAG, " Log Topic: '%s'", this->log_message_.topic.c_str());
213 }
214 if (!this->availability_.topic.empty()) {
215 ESP_LOGCONFIG(TAG, " Availability: '%s'", this->availability_.topic.c_str());
216 }
217}
219 return network::is_disabled() || this->state_ == MQTT_CLIENT_DISABLED || this->is_connected() ||
221}
222
224 for (auto &subscription : this->subscriptions_) {
225 subscription.subscribed = false;
226 subscription.resubscribe_timeout = 0;
227 }
228
229 this->status_set_warning();
230 this->dns_resolve_error_ = false;
231 this->dns_resolved_ = false;
232 ip_addr_t addr;
233 err_t err;
234 {
235 LwIPLock lock;
236#if USE_NETWORK_IPV6
237 err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr, MQTTClientComponent::dns_found_callback,
238 this, LWIP_DNS_ADDRTYPE_IPV6_IPV4);
239#else
240 err = dns_gethostbyname_addrtype(this->credentials_.address.c_str(), &addr, MQTTClientComponent::dns_found_callback,
241 this, LWIP_DNS_ADDRTYPE_IPV4);
242#endif /* USE_NETWORK_IPV6 */
243 }
244 switch (err) {
245 case ERR_OK: {
246 // Got IP immediately
247 this->dns_resolved_ = true;
248 this->ip_ = network::IPAddress(&addr);
249 this->start_connect_();
250 return;
251 }
252 case ERR_INPROGRESS: {
253 // wait for callback
254 ESP_LOGD(TAG, "Resolving broker IP address");
255 break;
256 }
257 default:
258 case ERR_ARG: {
259 // error
260 ESP_LOGW(TAG, "Error resolving broker IP address: %d", err);
261 break;
262 }
263 }
264
266 this->connect_begin_ = millis();
267}
269 if (!this->dns_resolved_ && millis() - this->connect_begin_ > 20000) {
270 this->dns_resolve_error_ = true;
271 }
272
273 if (this->dns_resolve_error_) {
274 ESP_LOGW(TAG, "Couldn't resolve IP address for '%s'", this->credentials_.address.c_str());
278 return;
279 }
280
281 if (!this->dns_resolved_) {
282 return;
283 }
284
285 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
286 ESP_LOGD(TAG, "Resolved broker IP address to %s", this->ip_.str_to(ip_buf));
287 this->start_connect_();
288}
289#if defined(USE_ESP8266) && LWIP_VERSION_MAJOR == 1
290void MQTTClientComponent::dns_found_callback(const char *name, ip_addr_t *ipaddr, void *callback_arg) {
291#else
292void MQTTClientComponent::dns_found_callback(const char *name, const ip_addr_t *ipaddr, void *callback_arg) {
293#endif
294 auto *a_this = (MQTTClientComponent *) callback_arg;
295 if (ipaddr == nullptr) {
296 a_this->dns_resolve_error_ = true;
297 } else {
298 a_this->ip_ = network::IPAddress(ipaddr);
299 a_this->dns_resolved_ = true;
300 }
301}
302
305 return;
306
307 ESP_LOGI(TAG, "Connecting");
308 // Force disconnect first
310
313 const char *username = nullptr;
314 if (!this->credentials_.username.empty())
315 username = this->credentials_.username.c_str();
316 const char *password = nullptr;
317 if (!this->credentials_.password.empty())
318 password = this->credentials_.password.c_str();
319
320 this->mqtt_backend_.set_credentials(username, password);
321
322 this->mqtt_backend_.set_server(this->credentials_.address.c_str(), this->credentials_.port);
323 if (!this->last_will_.topic.empty()) {
324 this->mqtt_backend_.set_will(this->last_will_.topic.c_str(), this->last_will_.qos, this->last_will_.retain,
325 this->last_will_.payload.c_str());
326 }
327
328 this->mqtt_backend_.connect();
330 this->connect_begin_ = millis();
331}
333 return this->state_ == MQTT_CLIENT_CONNECTED && this->mqtt_backend_.connected();
334}
335
337 if (!this->mqtt_backend_.connected()) {
338 if (millis() - this->connect_begin_ > 60000) {
340 this->start_dnslookup_();
341 }
342 return;
343 }
344
346 this->sent_birth_message_ = false;
347 this->status_clear_warning();
348 ESP_LOGI(TAG, "Connected");
349 // MQTT Client needs some time to be fully set up.
350 delay(100); // NOLINT
351
353 this->send_device_info_();
354
355 for (MQTTComponent *component : this->children_)
356 component->schedule_resend_state();
357}
358
360 // Call the backend loop first
362
363 if (this->disconnect_reason_.has_value()) {
364 const LogString *reason_s = MQTTDisconnectReasonStrings::get_log_str(
365 static_cast<uint8_t>(*this->disconnect_reason_), MQTTDisconnectReasonStrings::LAST_INDEX);
366 if (!network::is_connected()) {
367 reason_s = LOG_STR("WiFi disconnected");
368 }
369 ESP_LOGW(TAG, "Disconnected: %s", LOG_STR_ARG(reason_s));
370 this->disconnect_reason_.reset();
371 }
372
374
375 switch (this->state_) {
377 return; // Return to avoid a reboot when disabled
379 if (now - this->connect_begin_ > 5000) {
380 this->start_dnslookup_();
381 }
382 break;
384 this->check_dnslookup_();
385 break;
387 this->check_connected();
388 break;
390 if (!this->mqtt_backend_.connected()) {
392 ESP_LOGW(TAG, "Lost client connection");
393 this->start_dnslookup_();
394 } else {
395 if (!this->birth_message_.topic.empty() && !this->sent_birth_message_) {
396 this->sent_birth_message_ = this->publish(this->birth_message_);
397 }
398
399 this->last_connected_ = now;
401
402 // Process pending resends for all MQTT components centrally
403 // Limit work per loop iteration to avoid triggering task WDT on reconnect
404 {
405 uint8_t resend_count = 0;
406 for (MQTTComponent *component : this->children_) {
407 if (component->is_resend_pending()) {
408 component->process_resend();
409 if (++resend_count >= MAX_RESENDS_PER_LOOP)
410 break;
411 }
412 }
413 }
414 }
415 break;
416 }
417
418 if (millis() - this->last_connected_ > this->reboot_timeout_ && this->reboot_timeout_ != 0) {
419 ESP_LOGE(TAG, "Can't connect; restarting");
420 App.reboot();
421 }
422}
424
425// Subscribe
426bool MQTTClientComponent::subscribe_(const char *topic, uint8_t qos) {
427 if (!this->is_connected())
428 return false;
429
430 bool ret = this->mqtt_backend_.subscribe(topic, qos);
431 yield();
432
433 if (ret) {
434 ESP_LOGV(TAG, "subscribe(topic='%s')", topic);
435 } else {
436 delay(5);
437 ESP_LOGV(TAG, "Subscribe failed for topic='%s'. Will retry", topic);
438 this->status_momentary_warning("subscribe", 1000);
439 }
440 return ret != 0;
441}
442void MQTTClientComponent::resubscribe_subscription_(MQTTSubscription *sub) {
443 if (sub->subscribed)
444 return;
445
446 const uint32_t now = millis();
447 bool do_resub = sub->resubscribe_timeout == 0 || now - sub->resubscribe_timeout > 1000;
448
449 if (do_resub) {
450 sub->subscribed = this->subscribe_(sub->topic.c_str(), sub->qos);
451 sub->resubscribe_timeout = now;
452 }
453}
455 for (auto &subscription : this->subscriptions_) {
456 this->resubscribe_subscription_(&subscription);
457 }
458}
459
460void MQTTClientComponent::subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos) {
461 MQTTSubscription subscription{
462 .topic = topic,
463 .qos = qos,
464 .callback = std::move(callback),
465 .subscribed = false,
466 .resubscribe_timeout = 0,
467 };
468 this->resubscribe_subscription_(&subscription);
469 this->subscriptions_.push_back(subscription);
470}
471
472void MQTTClientComponent::subscribe_json(const std::string &topic, const mqtt_json_callback_t &callback, uint8_t qos) {
473 auto f = [callback](const std::string &topic, const std::string &payload) {
474 json::parse_json(payload, [topic, callback](JsonObject root) -> bool {
475 callback(topic, root);
476 return true;
477 });
478 };
479 MQTTSubscription subscription{
480 .topic = topic,
481 .qos = qos,
482 .callback = f,
483 .subscribed = false,
484 .resubscribe_timeout = 0,
485 };
486 this->resubscribe_subscription_(&subscription);
487 this->subscriptions_.push_back(subscription);
488}
489
490void MQTTClientComponent::unsubscribe(const std::string &topic) {
491 bool ret = this->mqtt_backend_.unsubscribe(topic.c_str());
492 yield();
493 if (ret) {
494 ESP_LOGV(TAG, "unsubscribe(topic='%s')", topic.c_str());
495 } else {
496 delay(5);
497 ESP_LOGV(TAG, "Unsubscribe failed for topic='%s'.", topic.c_str());
498 this->status_momentary_warning("unsubscribe", 1000);
499 }
500
501 auto it = subscriptions_.begin();
502 while (it != subscriptions_.end()) {
503 if (it->topic == topic) {
504 it = subscriptions_.erase(it);
505 } else {
506 ++it;
507 }
508 }
509}
510
511// Publish
512bool MQTTClientComponent::publish(const std::string &topic, const std::string &payload, uint8_t qos, bool retain) {
513 return this->publish(topic, payload.data(), payload.size(), qos, retain);
514}
515
516bool MQTTClientComponent::publish(const std::string &topic, const char *payload, size_t payload_length, uint8_t qos,
517 bool retain) {
518 return this->publish(topic.c_str(), payload, payload_length, qos, retain);
519}
520
521bool MQTTClientComponent::publish(const MQTTMessage &message) {
522 return this->publish(message.topic.c_str(), message.payload.c_str(), message.payload.length(), message.qos,
523 message.retain);
524}
525bool MQTTClientComponent::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos,
526 bool retain) {
527 return this->publish_json(topic.c_str(), f, qos, retain);
528}
529
530bool MQTTClientComponent::publish(const char *topic, const char *payload, size_t payload_length, uint8_t qos,
531 bool retain) {
532 if (!this->is_connected()) {
533 return false;
534 }
535 size_t topic_len = strlen(topic);
536 bool logging_topic = (topic_len == this->log_message_.topic.size()) &&
537 (memcmp(this->log_message_.topic.c_str(), topic, topic_len) == 0);
538 bool ret = this->mqtt_backend_.publish(topic, payload, payload_length, qos, retain);
539 delay(0);
540 if (!ret && !logging_topic && this->is_connected()) {
541 delay(0);
542 ret = this->mqtt_backend_.publish(topic, payload, payload_length, qos, retain);
543 delay(0);
544 }
545
546 if (!logging_topic) {
547 if (ret) {
548 ESP_LOGV(TAG, "Publish(topic='%s' retain=%d qos=%d)", topic, retain, qos);
549 ESP_LOGVV(TAG, "Publish payload (len=%u): '%.*s'", payload_length, static_cast<int>(payload_length), payload);
550 } else {
551 ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). Will retry", topic, payload_length);
552 this->status_momentary_warning("publish", 1000);
553 }
554 }
555 return ret != 0;
556}
557
558bool MQTTClientComponent::publish_json(const char *topic, const json::json_build_t &f, uint8_t qos, bool retain) {
559 auto message = json::build_json(f);
560 return this->publish(topic, message.c_str(), message.size(), qos, retain);
561}
562
564 if (this->state_ != MQTT_CLIENT_DISABLED)
565 return;
566 ESP_LOGD(TAG, "Enabling");
568 this->last_connected_ = millis();
569 this->start_dnslookup_();
570}
571
573 if (this->state_ == MQTT_CLIENT_DISABLED)
574 return;
575 ESP_LOGD(TAG, "Disabling");
577 this->on_shutdown();
578}
579
591static bool topic_match(const char *message, const char *subscription, bool is_normal, bool past_separator) {
592 // Reached end of both strings at the same time, this means we have a successful match
593 if (*message == '\0' && *subscription == '\0')
594 return true;
595
596 // Either the message or the subscribe are at the end. This means they don't match.
597 if (*message == '\0' || *subscription == '\0')
598 return false;
599
600 bool do_wildcards = is_normal || past_separator;
601
602 if (*subscription == '+' && do_wildcards) {
603 // single level wildcard
604 // consume + from subscription
605 subscription++;
606 // consume everything from message until '/' found or end of string
607 while (*message != '\0' && *message != '/') {
608 message++;
609 }
610 // after this, both pointers will point to a '/' or to the end of the string
611
612 return topic_match(message, subscription, is_normal, true);
613 }
614
615 if (*subscription == '#' && do_wildcards) {
616 // multilevel wildcard - MQTT mandates that this must be at end of subscribe topic
617 return true;
618 }
619
620 // this handles '/' and normal characters at the same time.
621 if (*message != *subscription)
622 return false;
623
624 past_separator = past_separator || *subscription == '/';
625
626 // consume characters
627 subscription++;
628 message++;
629
630 return topic_match(message, subscription, is_normal, past_separator);
631}
632
633static bool topic_match(const char *message, const char *subscription) {
634 return topic_match(message, subscription, *message != '\0' && *message != '$', false);
635}
636
637void MQTTClientComponent::on_message(const std::string &topic, const std::string &payload) {
638#ifdef USE_ESP8266
639 // IMPORTANT: This defer is REQUIRED to prevent stack overflow crashes on ESP8266.
640 //
641 // On ESP8266, this callback is invoked directly from the lwIP/AsyncTCP network stack
642 // which runs in the "sys" context with a very limited stack (~4KB). By the time we
643 // reach this function, the stack is already partially consumed by the network
644 // processing chain: tcp_input -> AsyncClient::_recv -> AsyncMqttClient::_onMessage -> here.
645 //
646 // MQTT subscription callbacks can trigger arbitrary user actions (automations, HTTP
647 // requests, sensor updates, etc.) which may have deep call stacks of their own.
648 // For example, an HTTP request action requires: DNS lookup -> TCP connect -> TLS
649 // handshake (if HTTPS) -> request formatting. This easily overflows the remaining
650 // system stack space, causing a LoadStoreAlignmentCause exception or silent corruption.
651 //
652 // By deferring to the main loop, we ensure callbacks execute with a fresh, full-size
653 // stack in the normal application context rather than the constrained network task.
654 //
655 // DO NOT REMOVE THIS DEFER without understanding the above. It may appear to work
656 // in simple tests but will cause crashes with complex automations.
657 this->defer([this, topic, payload]() {
658#endif
659 for (auto &subscription : this->subscriptions_) {
660 if (topic_match(topic.c_str(), subscription.topic.c_str()))
661 subscription.callback(topic, payload);
662 }
663#ifdef USE_ESP8266
664 });
665#endif
666}
667
668// Setters
670bool MQTTClientComponent::is_log_message_enabled() const { return !this->log_message_.topic.empty(); }
671void MQTTClientComponent::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
672void MQTTClientComponent::register_mqtt_component(MQTTComponent *component) { this->children_.push_back(component); }
673void MQTTClientComponent::set_log_level(int level) { this->log_level_ = level; }
674void MQTTClientComponent::set_keep_alive(uint16_t keep_alive_s) { this->mqtt_backend_.set_keep_alive(keep_alive_s); }
675void MQTTClientComponent::set_log_message_template(MQTTMessage &&message) { this->log_message_ = std::move(message); }
676const MQTTDiscoveryInfo &MQTTClientComponent::get_discovery_info() const { return this->discovery_info_; }
677void MQTTClientComponent::set_topic_prefix(const std::string &topic_prefix, const std::string &check_topic_prefix) {
678 if (App.is_name_add_mac_suffix_enabled() && (topic_prefix == check_topic_prefix)) {
679 char buf[ESPHOME_DEVICE_NAME_MAX_LEN + 1];
681 } else {
682 this->topic_prefix_ = topic_prefix;
683 }
684}
685const std::string &MQTTClientComponent::get_topic_prefix() const { return this->topic_prefix_; }
686void MQTTClientComponent::set_publish_nan_as_none(bool publish_nan_as_none) {
687 this->publish_nan_as_none_ = publish_nan_as_none;
688}
691 this->birth_message_.topic = "";
693}
695 this->shutdown_message_.topic = "";
697}
698bool MQTTClientComponent::is_discovery_enabled() const { return !this->discovery_info_.prefix.empty(); }
700const Availability &MQTTClientComponent::get_availability() { return this->availability_; }
702 if (this->birth_message_.topic.empty() || this->birth_message_.topic != this->last_will_.topic) {
703 this->availability_.topic = "";
704 return;
705 }
709}
710
712 this->last_will_ = std::move(message);
714}
715
717 this->birth_message_ = std::move(message);
719}
720
721void MQTTClientComponent::set_shutdown_message(MQTTMessage &&message) { this->shutdown_message_ = std::move(message); }
722
723void MQTTClientComponent::set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator,
724 MQTTDiscoveryObjectIdGenerator object_id_generator, bool retain,
725 bool discover_ip, bool clean) {
726 this->discovery_info_.prefix = std::move(prefix);
727 this->discovery_info_.discover_ip = discover_ip;
728 this->discovery_info_.unique_id_generator = unique_id_generator;
729 this->discovery_info_.object_id_generator = object_id_generator;
730 this->discovery_info_.retain = retain;
731 this->discovery_info_.clean = clean;
732}
733
735
737 this->discovery_info_ = MQTTDiscoveryInfo{
738 .prefix = "",
739 .retain = false,
740 .discover_ip = false,
741 .clean = false,
742 .unique_id_generator = MQTT_LEGACY_UNIQUE_ID_GENERATOR,
743 .object_id_generator = MQTT_NONE_OBJECT_ID_GENERATOR,
744 };
745}
747 if (!this->shutdown_message_.topic.empty()) {
748 yield();
749 this->publish(this->shutdown_message_);
750 yield();
751 }
753}
754
756 this->mqtt_backend_.set_on_connect(std::forward<mqtt_on_connect_callback_t>(callback));
757}
758
760 auto callback_copy = callback;
761 this->mqtt_backend_.set_on_disconnect(std::forward<mqtt_on_disconnect_callback_t>(callback));
762 this->on_disconnect_.add(std::move(callback_copy));
763}
764
765MQTTClientComponent *global_mqtt_client = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
766
767// MQTTMessageTrigger
768MQTTMessageTrigger::MQTTMessageTrigger(std::string topic) : topic_(std::move(topic)) {}
769void MQTTMessageTrigger::set_qos(uint8_t qos) { this->qos_ = qos; }
770void MQTTMessageTrigger::set_payload(const std::string &payload) { this->payload_ = payload; }
771void MQTTMessageTrigger::setup() {
773 this->topic_,
774 [this](const std::string &topic, const std::string &payload) {
775 if (this->payload_.has_value() && payload != *this->payload_) {
776 return;
777 }
778
779 this->trigger(payload);
780 },
781 this->qos_);
782}
783void MQTTMessageTrigger::dump_config() {
784 ESP_LOGCONFIG(TAG,
785 "MQTT Message Trigger:\n"
786 " Topic: '%s'\n"
787 " QoS: %u",
788 this->topic_.c_str(), this->qos_);
789}
790float MQTTMessageTrigger::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
791
792} // namespace esphome::mqtt
793
794#endif // USE_MQTT
const StringRef & get_name() const
Get the name of this Application set by pre_setup().
const StringRef & get_friendly_name() const
Get the friendly name of this Application set by pre_setup().
bool is_name_add_mac_suffix_enabled() const
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
ESPDEPRECATED("Use const char* overload instead. Removed in 2026.7.0", "2026.1.0") void defer(const std voi defer)(const char *name, std::function< void()> &&f)
Defer a callback to the next loop() call.
Definition component.h:501
void status_set_warning(const char *message=nullptr)
void status_momentary_warning(const char *name, uint32_t length=5000)
Set warning status flag and automatically clear it after a timeout.
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.", "2026.2.0") void set_retry(const std uint32_t uint8_t std::function< RetryResult(uint8_t)> && f
Definition component.h:395
void status_clear_warning()
Definition component.h:254
constexpr const char * c_str() const
Definition string_ref.h:73
constexpr bool empty() const
Definition string_ref.h:76
APINoiseContext & get_noise_ctx()
Definition api_server.h:75
uint16_t get_port() const
void add_log_callback(void *instance, void(*fn)(void *, uint8_t, const char *, const char *, size_t))
Register a log callback to receive log messages.
Definition logger.h:191
void set_keep_alive(uint16_t keep_alive) final
void set_on_message(std::function< on_message_callback_t > &&callback) final
void set_client_id(const char *client_id) final
void set_on_connect(std::function< on_connect_callback_t > &&callback) final
void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) final
bool subscribe(const char *topic, uint8_t qos) final
void set_server(network::IPAddress ip, uint16_t port) final
bool publish(const char *topic, const char *payload, size_t length, uint8_t qos, bool retain) final
void set_clean_session(bool clean_session) final
bool unsubscribe(const char *topic) final
void set_on_disconnect(std::function< on_disconnect_callback_t > &&callback) final
void set_credentials(const char *username, const char *password) final
void set_birth_message(MQTTMessage &&message)
Set the birth message.
void start_connect_()
Reconnect to the MQTT broker if not already connected.
void setup() override
Setup the MQTT client, registering a bunch of callbacks and attempting to connect.
void disable_discovery()
Globally disable Home Assistant discovery.
void recalculate_availability_()
Re-calculate the availability property.
void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, MQTTDiscoveryObjectIdGenerator object_id_generator, bool retain, bool discover_ip, bool clean=false)
Set the Home Assistant discovery info.
void set_reboot_timeout(uint32_t reboot_timeout)
float get_setup_priority() const override
MQTT client setup priority.
void disable_log_message()
Get the topic used for logging. Defaults to "<topic_prefix>/debug" and the value is cached for speed.
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)
void set_last_will(MQTTMessage &&message)
Set the last will testament message.
bool publish(const MQTTMessage &message)
Publish a MQTTMessage.
const MQTTDiscoveryInfo & get_discovery_info() const
Get Home Assistant discovery info.
void disable_birth_message()
Remove the birth message.
static void dns_found_callback(const char *name, ip_addr_t *ipaddr, void *callback_arg)
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.
MQTTMessage last_will_
The last will message.
void set_topic_prefix(const std::string &topic_prefix, const std::string &check_topic_prefix)
Set the topic prefix that will be prepended to all topics together with "/".
void set_shutdown_message(MQTTMessage &&message)
MQTTMessage birth_message_
The birth message (e.g.
std::vector< MQTTComponent * > children_
void set_on_connect(mqtt_on_connect_callback_t &&callback)
optional< MQTTClientDisconnectReason > disconnect_reason_
void unsubscribe(const std::string &topic)
Unsubscribe from an MQTT topic.
void set_publish_nan_as_none(bool publish_nan_as_none)
void on_message(const std::string &topic, const std::string &payload)
void set_log_message_template(MQTTMessage &&message)
Manually set the topic used for logging.
void resubscribe_subscription_(MQTTSubscription *sub)
void set_on_disconnect(mqtt_on_disconnect_callback_t &&callback)
bool subscribe_(const char *topic, uint8_t qos)
const Availability & get_availability()
void on_log(uint8_t level, const char *tag, const char *message, size_t message_len)
std::vector< MQTTSubscription > subscriptions_
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.
void disable_last_will()
Remove the last will testament message.
void set_keep_alive(uint16_t keep_alive_s)
Set the keep alive time in seconds, every 0.7*keep_alive a ping will be sent.
void loop() override
Reconnect if required.
MQTTDiscoveryInfo discovery_info_
The discovery info options for Home Assistant.
CallbackManager< MQTTBackend::on_disconnect_callback_t > on_disconnect_
Availability availability_
Caches availability.
MQTTMessageTrigger(std::string topic)
const Component * component
Definition component.cpp:37
const char * message
Definition component.cpp:38
in_addr ip_addr_t
Definition ip_address.h:22
PROGMEM_STRING_TABLE(AlarmControlPanelStateStrings, "DISARMED", "ARMED_HOME", "ARMED_AWAY", "ARMED_NIGHT", "ARMED_VACATION", "ARMED_CUSTOM_BYPASS", "PENDING", "ARMING", "DISARMING", "TRIGGERED", "UNKNOWN")
APIServer * global_api_server
const char * get_package_import_url()
std::function< void(JsonObject)> json_build_t
Callback function typedef for building JsonObjects.
Definition json_util.h:151
bool parse_json(const std::string &data, const json_parse_t &f)
Parse a JSON string and run the provided json parse function if it's valid.
Definition json_util.cpp:27
SerializationBuffer build_json(const json_build_t &f)
Build a JSON string with the provided json build function.
Definition json_util.cpp:18
Logger * global_logger
std::function< MQTTBackend::on_disconnect_callback_t > mqtt_on_disconnect_callback_t
Definition mqtt_client.h:32
MQTTDiscoveryObjectIdGenerator
available discovery object_id generators
Definition mqtt_client.h:74
@ MQTT_NONE_OBJECT_ID_GENERATOR
Definition mqtt_client.h:75
MQTTDiscoveryUniqueIdGenerator
available discovery unique_id generators
Definition mqtt_client.h:68
@ MQTT_LEGACY_UNIQUE_ID_GENERATOR
Definition mqtt_client.h:69
std::function< void(const std::string &, JsonObject)> mqtt_json_callback_t
Definition mqtt_client.h:39
std::function< void(const std::string &, const std::string &)> mqtt_callback_t
Callback for MQTT subscriptions.
Definition mqtt_client.h:38
MQTTClientComponent * global_mqtt_client
@ MQTT_CLIENT_DISCONNECTED
Definition mqtt_client.h:94
@ MQTT_CLIENT_RESOLVING_ADDRESS
Definition mqtt_client.h:95
std::function< MQTTBackend::on_connect_callback_t > mqtt_on_connect_callback_t
Callback for MQTT events.
Definition mqtt_client.h:31
ESPHOME_ALWAYS_INLINE bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition util.h:27
network::IPAddresses get_ip_addresses()
Definition util.cpp:23
bool is_disabled()
Return whether the network is disabled (only wifi for now)
Definition util.cpp:10
constexpr float AFTER_WIFI
For components that should be initialized after WiFi is connected.
Definition component.h:41
const char *const TAG
Definition spi.cpp:7
const char * tag
Definition log.h:74
std::string size_t len
Definition helpers.h:892
void HOT yield()
Definition core.cpp:25
char * str_sanitize_to(char *buffer, size_t buffer_size, const char *str)
Sanitize a string to buffer, keeping only alphanumerics, dashes, and underscores.
Definition helpers.cpp:210
void get_mac_address_into_buffer(std::span< char, MAC_ADDRESS_BUFFER_SIZE > buf)
Get the device MAC address into the given buffer, in lowercase hex notation.
Definition helpers.cpp:811
void HOT delay(uint32_t ms)
Definition core.cpp:28
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:26
Application App
Global storage of Application pointer - only one Application can exist.
std::string make_name_with_suffix(const char *name, size_t name_len, char sep, const char *suffix_ptr, size_t suffix_len)
Optimized string concatenation: name + separator + suffix (const char* overload) Uses a fixed stack b...
Definition helpers.cpp:281
std::string payload_not_available
Definition mqtt_client.h:64
std::string topic
Empty means disabled.
Definition mqtt_client.h:62
std::string address
The address of the server without port number.
Definition mqtt_client.h:52
bool clean_session
Whether the session will be cleaned or remembered between connects.
Definition mqtt_client.h:57
std::string client_id
The client ID. Will automatically be truncated to 23 characters.
Definition mqtt_client.h:56
MQTTDiscoveryUniqueIdGenerator unique_id_generator
Definition mqtt_client.h:88
bool discover_ip
Enable the Home Assistant device discovery.
Definition mqtt_client.h:86
std::string prefix
The Home Assistant discovery prefix. Empty means disabled.
Definition mqtt_client.h:84
MQTTDiscoveryObjectIdGenerator object_id_generator
Definition mqtt_client.h:89
bool retain
Whether to retain discovery messages.
Definition mqtt_client.h:85