ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
mqtt_backend_esp32.cpp
Go to the documentation of this file.
2
3#ifdef USE_MQTT
4#ifdef USE_ESP32
5
6#include <string>
8#include "esphome/core/log.h"
10
11#ifdef USE_MQTT_CERT_BUNDLE
12#include "esp_crt_bundle.h"
13#endif
14
15namespace esphome::mqtt {
16
17ESPHOME_LOG_TAG(TAG, "mqtt");
18
20 mqtt_cfg_.broker.address.hostname = this->host_.c_str();
21 mqtt_cfg_.broker.address.port = this->port_;
22 mqtt_cfg_.session.keepalive = this->keep_alive_;
23 mqtt_cfg_.session.disable_clean_session = !this->clean_session_;
24
25 if (!this->username_.empty()) {
26 mqtt_cfg_.credentials.username = this->username_.c_str();
27 if (!this->password_.empty()) {
28 mqtt_cfg_.credentials.authentication.password = this->password_.c_str();
29 }
30 }
31
32 if (!this->lwt_topic_.empty()) {
33 mqtt_cfg_.session.last_will.topic = this->lwt_topic_.c_str();
34 this->mqtt_cfg_.session.last_will.qos = this->lwt_qos_;
35 this->mqtt_cfg_.session.last_will.retain = this->lwt_retain_;
36
37 if (!this->lwt_message_.empty()) {
38 mqtt_cfg_.session.last_will.msg = this->lwt_message_.c_str();
39 mqtt_cfg_.session.last_will.msg_len = this->lwt_message_.size();
40 }
41 }
42
43 if (!this->client_id_.empty()) {
44 mqtt_cfg_.credentials.client_id = this->client_id_.c_str();
45 }
46#ifdef USE_MQTT_TLS
47 mqtt_cfg_.broker.verification.skip_cert_common_name_check = this->skip_cert_cn_check_;
48 mqtt_cfg_.broker.verification.certificate = this->ca_certificate_;
49#ifdef USE_MQTT_CERT_BUNDLE
50 mqtt_cfg_.broker.verification.crt_bundle_attach = esp_crt_bundle_attach;
51#endif
52 // Validation only accepts the client certificate together with its key
53 mqtt_cfg_.credentials.authentication.certificate = this->cl_certificate_;
54 mqtt_cfg_.credentials.authentication.key = this->cl_key_;
55#endif
56 // Validation keeps TLS off plain ws, so only the TCP transport has two forms
57 switch (this->transport_) {
59 mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_WS;
60 break;
62 mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_WSS;
63 break;
65 default:
66#ifdef USE_MQTT_TLS
67 mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_SSL;
68#else
69 mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_TCP;
70#endif
71 break;
72 }
73 // Only set for the WebSocket transports
74 mqtt_cfg_.broker.address.path = this->ws_path_;
75
76 auto *mqtt_client = esp_mqtt_client_init(&mqtt_cfg_);
77 if (mqtt_client) {
78 handler_.reset(mqtt_client);
79 is_initalized_ = true;
80 esp_mqtt_client_register_event(mqtt_client, MQTT_EVENT_ANY, mqtt_event_handler, this);
81#if defined(USE_MQTT_IDF_ENQUEUE)
82 // Create the task only after MQTT client is initialized successfully
83 // Use larger stack size when TLS is enabled
84#ifdef USE_MQTT_TLS
85 constexpr size_t stack_size = TASK_STACK_SIZE_TLS;
86#else
87 constexpr size_t stack_size = TASK_STACK_SIZE;
88#endif
89 xTaskCreate(esphome_mqtt_task, "esphome_mqtt", stack_size, (void *) this, TASK_PRIORITY, &this->task_handle_);
90 if (this->task_handle_ == nullptr) {
91 ESP_LOGE(TAG, "Failed to create MQTT task");
92 // Clean up MQTT client since we can't start the async task
93 handler_.reset();
94 is_initalized_ = false;
95 return false;
96 }
97 // Set the task handle so the queue can notify it
98 this->mqtt_queue_.set_task_to_notify(this->task_handle_);
99#endif
100 return true;
101 } else {
102 ESP_LOGE(TAG, "Failed to init client");
103 return false;
104 }
105}
106
108 // process new events
109 // handle only 1 message per loop iteration
110 Event *event = this->mqtt_event_queue_.pop();
111 if (event != nullptr) {
112 this->mqtt_event_handler_(*event);
113 this->mqtt_event_pool_.release(event);
114 }
115
116 // Log dropped inbound events (check is cheap - single atomic load in common case)
117 uint16_t inbound_dropped = this->mqtt_event_queue_.get_and_reset_dropped_count();
118 if (inbound_dropped > 0) {
119 ESP_LOGW(TAG, "Dropped %u inbound MQTT events", inbound_dropped);
120 }
121
122#if defined(USE_MQTT_IDF_ENQUEUE)
123 // Periodically log dropped messages to avoid blocking during spikes.
124 // During high load, many messages can be dropped in quick succession.
125 // Logging each drop immediately would flood the logs and potentially
126 // cause more drops if MQTT logging is enabled (cascade effect).
127 // Instead, we accumulate the count and log a summary periodically.
128 // IMPORTANT: Don't move this to the scheduler - if drops are due to memory
129 // pressure, the scheduler's heap allocations would make things worse.
131 // Handle rollover: (now - last_time) works correctly with unsigned arithmetic
132 // even when now < last_time due to rollover
133 if ((now - this->last_dropped_log_time_) >= DROP_LOG_INTERVAL_MS) {
134 uint16_t dropped = this->mqtt_queue_.get_and_reset_dropped_count();
135 if (dropped > 0) {
136 ESP_LOGW(TAG, "Dropped %u messages (%" PRIu32 "s)", dropped, DROP_LOG_INTERVAL_MS / 1000);
137 }
138 this->last_dropped_log_time_ = now;
139 }
140#endif
141}
142
144 ESP_LOGV(TAG, "Event dispatched from event loop event_id=%d", event.event_id);
145 switch (event.event_id) {
146 case MQTT_EVENT_BEFORE_CONNECT:
147 ESP_LOGV(TAG, "MQTT_EVENT_BEFORE_CONNECT");
148 break;
149
150 case MQTT_EVENT_CONNECTED:
151 ESP_LOGV(TAG, "MQTT_EVENT_CONNECTED");
152 this->is_connected_ = true;
153#if defined(USE_MQTT_IDF_ENQUEUE)
154 this->last_dropped_log_time_ = 0;
155 xTaskNotifyGive(this->task_handle_);
156#endif
157 this->on_connect_.call(event.session_present);
158 break;
159 case MQTT_EVENT_DISCONNECTED:
160 ESP_LOGV(TAG, "MQTT_EVENT_DISCONNECTED");
161 // TODO is there a way to get the disconnect reason?
162 this->is_connected_ = false;
163#if defined(USE_MQTT_IDF_ENQUEUE)
164 this->last_dropped_log_time_ = 0;
165 xTaskNotifyGive(this->task_handle_);
166#endif
168 break;
169
170 case MQTT_EVENT_SUBSCRIBED:
171 ESP_LOGV(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event.msg_id);
172 // hardcode QoS to 0. QoS is not used in this context but required to mirror the AsyncMqtt interface
173 this->on_subscribe_.call((int) event.msg_id, 0);
174 break;
175 case MQTT_EVENT_UNSUBSCRIBED:
176 ESP_LOGV(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event.msg_id);
177 this->on_unsubscribe_.call((int) event.msg_id);
178 break;
179 case MQTT_EVENT_PUBLISHED:
180 ESP_LOGV(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event.msg_id);
181 this->on_publish_.call((int) event.msg_id);
182 break;
183 case MQTT_EVENT_DATA: {
184 if (!event.topic.empty()) {
185 // When a single message arrives as multiple chunks, the topic will be empty
186 // on any but the first message, leading to event.topic being an empty string.
187 // To ensure handlers get the correct topic, cache the last seen topic to
188 // simulate always receiving the topic from underlying library
189 this->cached_topic_ = event.topic;
190 }
191 ESP_LOGV(TAG, "MQTT_EVENT_DATA %s", this->cached_topic_.c_str());
192 this->on_message_.call(this->cached_topic_.c_str(), event.data.data(), event.data.size(),
193 event.current_data_offset, event.total_data_len);
194 } break;
195 case MQTT_EVENT_ERROR:
196 ESP_LOGE(TAG, "MQTT_EVENT_ERROR");
197 if (event.error_handle.error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
198 ESP_LOGE(TAG, "Last esp-tls error: 0x%x, tls stack error: 0x%x, socket errno: %d (%s)",
199 event.error_handle.esp_tls_last_esp_err, event.error_handle.esp_tls_stack_err,
200 event.error_handle.esp_transport_sock_errno, strerror(event.error_handle.esp_transport_sock_errno));
201 } else if (event.error_handle.error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) {
202 ESP_LOGE(TAG, "Connection refused error: 0x%x", event.error_handle.connect_return_code);
203 } else {
204 ESP_LOGE(TAG, "Unknown error type: 0x%x", event.error_handle.error_type);
205 }
206 break;
207 default:
208 ESP_LOGV(TAG, "Other event id:%d", event.event_id);
209 break;
210 }
211}
212
214void MQTTBackendESP32::mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id,
215 void *event_data) {
216 MQTTBackendESP32 *instance = static_cast<MQTTBackendESP32 *>(handler_args);
217 // queue event to decouple processing from ESP-IDF MQTT task to main loop
218 if (instance) {
219 auto *event = instance->mqtt_event_pool_.allocate();
220 if (event == nullptr) {
221 // Pool exhausted, drop event (counted via queue's dropped counter)
222 instance->mqtt_event_queue_.increment_dropped_count();
223 return;
224 }
225 event->populate(*static_cast<esp_mqtt_event_t *>(event_data));
226 // Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
227 // allocate() returned non-null, the queue cannot be full.
228 instance->mqtt_event_queue_.push(event);
229
230 // Wake main loop immediately to process MQTT event
232 }
233}
234
235#if defined(USE_MQTT_IDF_ENQUEUE)
237 MQTTBackendESP32 *this_mqtt = (MQTTBackendESP32 *) params;
238
239 while (true) {
240 // Wait for notification indefinitely
241 ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
242
243 // Process all queued items
244 struct QueueElement *elem;
245 while ((elem = this_mqtt->mqtt_queue_.pop()) != nullptr) {
246 if (this_mqtt->is_connected_) {
247 switch (elem->type) {
249 esp_mqtt_client_subscribe(this_mqtt->handler_.get(), elem->topic, elem->qos);
250 break;
251
253 esp_mqtt_client_unsubscribe(this_mqtt->handler_.get(), elem->topic);
254 break;
255
257 esp_mqtt_client_publish(this_mqtt->handler_.get(), elem->topic, elem->payload, elem->payload_len, elem->qos,
258 elem->retain);
259 break;
260
261 default:
262 ESP_LOGE(TAG, "Invalid operation type from MQTT queue");
263 break;
264 }
265 }
266 this_mqtt->mqtt_outbound_pool_.release(elem);
267 }
268 }
269}
270
271bool MQTTBackendESP32::enqueue_(MqttQueueTypeT type, const char *topic, int qos, bool retain, const char *payload,
272 size_t len) {
273 auto *elem = this->mqtt_outbound_pool_.allocate();
274
275 if (!elem) {
276 // Queue is full - increment counter but don't log immediately.
277 // Logging here can cause a cascade effect: if MQTT logging is enabled,
278 // each dropped message would generate a log message, which could itself
279 // be sent via MQTT, causing more drops and more logs in a feedback loop
280 // that eventually triggers a watchdog reset. Instead, we log periodically
281 // in loop() to prevent blocking the event loop during spikes.
282 this->mqtt_queue_.increment_dropped_count();
283 return false;
284 }
285
286 elem->type = type;
287 elem->qos = qos;
288 elem->retain = retain;
289
290 // Use the helper to allocate and copy data
291 if (!elem->set_data(topic, payload, len)) {
292 // Allocation failed, return elem to pool
293 this->mqtt_outbound_pool_.release(elem);
294 // Increment counter without logging to avoid cascade effect during memory pressure
295 this->mqtt_queue_.increment_dropped_count();
296 return false;
297 }
298
299 // Push to queue - always succeeds since we allocated from the pool
300 this->mqtt_queue_.push(elem);
301 return true;
302}
303#endif // USE_MQTT_IDF_ENQUEUE
304
305} // namespace esphome::mqtt
306#endif // USE_ESP32
307#endif
void wake_loop_threadsafe()
Wake the main event loop from another thread or callback.
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.
EventPool< Event, MQTT_EVENT_QUEUE_LENGTH - 1 > mqtt_event_pool_
EventPool< struct QueueElement, MQTT_QUEUE_LENGTH - 1 > mqtt_outbound_pool_
CallbackManager< on_connect_callback_t > on_connect_
CallbackManager< on_disconnect_callback_t > on_disconnect_
CallbackManager< on_message_callback_t > on_message_
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
static - Dispatch event to instance method
CallbackManager< on_subscribe_callback_t > on_subscribe_
static void esphome_mqtt_task(void *params)
bool enqueue_(MqttQueueTypeT type, const char *topic, int qos=0, bool retain=false, const char *payload=NULL, size_t len=0)
static constexpr size_t TASK_STACK_SIZE_TLS
CallbackManager< on_unsubscribe_callback_t > on_unsubscribe_
esp_mqtt_client_config_t mqtt_cfg_
static constexpr size_t TASK_STACK_SIZE
CallbackManager< on_publish_user_callback_t > on_publish_
static constexpr uint32_t DROP_LOG_INTERVAL_MS
NotifyingLockFreeQueue< struct QueueElement, MQTT_QUEUE_LENGTH > mqtt_queue_
void mqtt_event_handler_(const Event &event)
static constexpr ssize_t TASK_PRIORITY
LockFreeQueue< Event, MQTT_EVENT_QUEUE_LENGTH > mqtt_event_queue_
uint8_t type
ESPHOME_LOG_TAG(TAG, "alarm_control_panel")
const void size_t len
Definition hal.h:68
Application App
Global storage of Application pointer - only one Application can exist.
uint8_t event_id
Definition tt21100.cpp:3