ESPHome 2026.10.0-dev
Loading...
Searching...
No Matches
espnow_component.cpp
Go to the documentation of this file.
1#include "espnow_component.h"
2
3#ifdef USE_ESP32
4
5#include "espnow_err.h"
6
7#include <algorithm>
8#include <cinttypes>
9
12#include "esphome/core/log.h"
13
14#include <esp_event.h>
15#include <esp_mac.h>
16#include <esp_netif.h>
17#include <esp_now.h>
18#include <esp_random.h>
19#include <esp_wifi.h>
20#include <cstring>
21#include <memory>
22
23#ifdef USE_WIFI
25#endif
26
27namespace esphome::espnow {
28
29static constexpr const char *TAG = "espnow";
30
31ESPNowComponent *global_esp_now = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
32
33static const LogString *espnow_error_to_str(esp_err_t error) {
34 switch (error) {
35 case ESP_ERR_ESPNOW_FAILED:
36 return LOG_STR("ESPNow is in fail mode");
37 case ESP_ERR_ESPNOW_OWN_ADDRESS:
38 return LOG_STR("Message to your self");
39 case ESP_ERR_ESPNOW_DATA_SIZE:
40 return LOG_STR("Data size to large");
41 case ESP_ERR_ESPNOW_PEER_NOT_SET:
42 return LOG_STR("Peer address not set");
43 case ESP_ERR_ESPNOW_PEER_NOT_PAIRED:
44 return LOG_STR("Peer address not paired");
45 case ESP_ERR_ESPNOW_NOT_INIT:
46 return LOG_STR("Not init");
47 case ESP_ERR_ESPNOW_ARG:
48 return LOG_STR("Invalid argument");
49 case ESP_ERR_ESPNOW_INTERNAL:
50 return LOG_STR("Internal Error");
51 case ESP_ERR_ESPNOW_NO_MEM:
52 return LOG_STR("Our of memory");
53 case ESP_ERR_ESPNOW_NOT_FOUND:
54 return LOG_STR("Peer not found");
55 case ESP_ERR_ESPNOW_IF:
56 return LOG_STR("Interface does not match");
57 case ESP_OK:
58 return LOG_STR("OK");
59 case ESP_NOW_SEND_FAIL:
60 return LOG_STR("Failed");
61 default:
62 return LOG_STR("Unknown Error");
63 }
64}
65
66#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
67void on_send_report(const esp_now_send_info_t *info, esp_now_send_status_t status)
68#else
69void on_send_report(const uint8_t *mac_addr, esp_now_send_status_t status)
70#endif
71{
72 // Allocate an event from the pool
73 ESPNowPacket *packet = global_esp_now->receive_packet_pool_.allocate();
74 if (packet == nullptr) {
75 // No events available - queue is full or we're out of memory
76 global_esp_now->receive_packet_queue_.increment_dropped_count();
77 global_esp_now->enable_loop_soon_any_context();
78 return;
79 }
80
81// Load new packet data (replaces previous packet)
82#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
83 packet->load_sent_data(info->des_addr, status);
84#else
85 packet->load_sent_data(mac_addr, status);
86#endif
87
88 // Push the packet to the queue
89 global_esp_now->receive_packet_queue_.push(packet);
90 // Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
91 // allocate() returned non-null, the queue cannot be full.
92
93 // Re-enable and wake the main loop to process the ESP-NOW send event
94 global_esp_now->enable_loop_soon_any_context();
95}
96
97void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size) {
98 // Drop oversized frames before copying. ESP-NOW v2 peers (IDF >= 5.4 builds a
99 // v2 stack with no opt-out) can send up to ESP_NOW_MAX_DATA_LEN_V2 (1470 B),
100 // but the receive buffer only fits v2 frames with ``max_payload_size``; copying a
101 // larger frame would overflow packet_.receive.data.
102 if (size < 0 || size > ESPNOW_MAX_DATA_LEN) {
103 global_esp_now->receive_packet_queue_.increment_dropped_count();
104 global_esp_now->enable_loop_soon_any_context();
105 return;
106 }
107
108 // Allocate an event from the pool
109 ESPNowPacket *packet = global_esp_now->receive_packet_pool_.allocate();
110 if (packet == nullptr) {
111 // No events available - queue is full or we're out of memory
112 global_esp_now->receive_packet_queue_.increment_dropped_count();
113 global_esp_now->enable_loop_soon_any_context();
114 return;
115 }
116
117 // Load new packet data (replaces previous packet)
118 packet->load_received_data(info, data, size);
119
120 // Push the packet to the queue
121 global_esp_now->receive_packet_queue_.push(packet);
122 // Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
123 // allocate() returned non-null, the queue cannot be full.
124
125 // Re-enable and wake the main loop to process the ESP-NOW receive event
126 global_esp_now->enable_loop_soon_any_context();
127}
128
129ESPNowComponent::ESPNowComponent() { global_esp_now = this; }
130
131void ESPNowComponent::dump_config() {
132 ESP_LOGCONFIG(TAG, "espnow:");
133 // Only report driver details once enabled; with enable_on_boot: false the
134 // Wi-Fi driver is not initialized yet and esp_now_get_version() would crash,
135 // and after a failed enable_() the values would be meaningless.
136 if (this->state_ != ESPNOW_STATE_ENABLED) {
137 // OFF here means enable_() failed; the core logs the FAILED marker separately
138 ESP_LOGCONFIG(TAG, " %s", this->is_disabled() ? LOG_STR_LITERAL("Disabled") : LOG_STR_LITERAL("Not enabled"));
139 return;
140 }
141 uint32_t version = 0;
142 esp_now_get_version(&version);
143 char own_addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
144 format_mac_addr_upper(this->own_address_, own_addr_buf);
145 ESP_LOGCONFIG(TAG,
146 " Own address: %s\n"
147 " Version: v%" PRIu32 "\n"
148 " Wi-Fi channel: %d",
149 own_addr_buf, version, this->wifi_channel_);
150#ifdef USE_WIFI
151 ESP_LOGCONFIG(TAG, " Wi-Fi enabled: %s", YESNO(this->is_wifi_enabled()));
152#endif
153}
154
155bool ESPNowComponent::is_wifi_enabled() {
156#ifdef USE_WIFI
158#else
159 return false;
160#endif
161}
162
163void ESPNowComponent::setup() {
164#if defined(USE_WIFI) && defined(USE_WIFI_CONNECT_STATE_LISTENERS)
165 if (wifi::global_wifi_component != nullptr) {
167 }
168#endif
169 if (this->enable_on_boot_) {
170 this->enable_();
171 } else {
172 this->state_ = ESPNOW_STATE_DISABLED;
173 }
174}
175
176#if defined(USE_WIFI) && defined(USE_WIFI_CONNECT_STATE_LISTENERS)
177void ESPNowComponent::on_wifi_connect_state(StringRef ssid, std::span<const uint8_t, 6> bssid) {
178 if (ssid.empty()) {
179 return; // Disconnected; the channel is only meaningful while associated
180 }
181 uint8_t old_channel = this->wifi_channel_;
182 this->get_wifi_channel();
183 if (this->wifi_channel_ != old_channel) {
184 ESP_LOGI(TAG, "WiFi channel changed from %d to %d", old_channel, this->wifi_channel_);
185 }
186}
187#endif
188
189void ESPNowComponent::enable() {
190 if (this->state_ == ESPNOW_STATE_ENABLED)
191 return;
192
193 ESP_LOGD(TAG, "Enabling");
194 this->state_ = ESPNOW_STATE_OFF;
195
196 this->enable_();
197}
198
199void ESPNowComponent::enable_() {
200 if (!this->is_wifi_enabled()) {
201 wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
202
203 ESP_ERROR_CHECK(esp_wifi_init(&cfg));
204 ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
205 ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
206 ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
207 ESP_ERROR_CHECK(esp_wifi_start());
208 ESP_ERROR_CHECK(esp_wifi_disconnect());
209
210 this->apply_wifi_channel();
211 }
212 this->get_wifi_channel();
213
214 esp_err_t err = esp_now_init();
215 if (err != ESP_OK) {
216 ESP_LOGE(TAG, "esp_now_init failed: %s", esp_err_to_name(err));
217 this->mark_failed();
218 return;
219 }
220
222 if (err != ESP_OK) {
223 ESP_LOGE(TAG, "esp_now_register_recv_cb failed: %s", esp_err_to_name(err));
224 this->mark_failed();
225 return;
226 }
227
229 if (err != ESP_OK) {
230 ESP_LOGE(TAG, "esp_now_register_recv_cb failed: %s", esp_err_to_name(err));
231 this->mark_failed();
232 return;
233 }
234
235 esp_wifi_get_mac(WIFI_IF_STA, this->own_address_);
236
237 this->state_ = ESPNOW_STATE_ENABLED;
238
239 for (auto peer : this->peers_) {
240 this->add_peer(peer.address);
241 }
242}
243
244void ESPNowComponent::disable() {
245 if (this->state_ == ESPNOW_STATE_DISABLED)
246 return;
247
248 ESP_LOGD(TAG, "Disabling");
249 this->state_ = ESPNOW_STATE_DISABLED;
250
253
254 esp_err_t err = esp_now_deinit();
255 if (err != ESP_OK) {
256 ESP_LOGE(TAG, "esp_now_deinit failed! 0x%x", err);
257 }
258}
259
260void ESPNowComponent::apply_wifi_channel() {
261 if (this->state_ == ESPNOW_STATE_DISABLED) {
262 ESP_LOGE(TAG, "Cannot set channel when ESPNOW disabled");
263 this->mark_failed();
264 return;
265 }
266
267 if (this->is_wifi_enabled()) {
268 ESP_LOGE(TAG, "Cannot set channel when Wi-Fi enabled");
269 this->mark_failed();
270 return;
271 }
272
273 ESP_LOGI(TAG, "Channel set to %d.", this->wifi_channel_);
274 esp_wifi_set_promiscuous(true);
275 esp_wifi_set_channel(this->wifi_channel_, WIFI_SECOND_CHAN_NONE);
276 esp_wifi_set_promiscuous(false);
277}
278
279void ESPNowComponent::loop() {
280 // Process received packets
281 ESPNowPacket *packet = this->receive_packet_queue_.pop();
282 while (packet != nullptr) {
283 switch (packet->type_) {
285 const ESPNowRecvInfo info = packet->get_receive_info();
286 if (!esp_now_is_peer_exist(info.src_addr)) {
287 bool handled = false;
288 for (auto *handler : this->unknown_peer_handlers_) {
289 if (handler->on_unknown_peer(info, packet->packet_.receive.data, packet->packet_.receive.size)) {
290 handled = true;
291 break; // If a handler returns true, stop processing further handlers
292 }
293 }
294 if (!handled && this->auto_add_peer_) {
295 this->add_peer(info.src_addr);
296 }
297 }
298 // Intentionally left as if instead of else in case the peer is added above
299 if (esp_now_is_peer_exist(info.src_addr)) {
300#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
301 char src_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
302 char dst_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
303 // Cap the hex dump at a v1 frame: a full v2 frame would need a
304 // ~4.4 KB stack buffer.
305 char hex_buf[format_hex_pretty_size(ESP_NOW_MAX_DATA_LEN)];
306 format_mac_addr_upper(info.src_addr, src_buf);
307 format_mac_addr_upper(info.des_addr, dst_buf);
308 ESP_LOGV(TAG, "<<< [%s -> %s] %s", src_buf, dst_buf,
309 format_hex_pretty_to(hex_buf, packet->packet_.receive.data,
310 std::min<uint16_t>(packet->packet_.receive.size, ESP_NOW_MAX_DATA_LEN)));
311#endif
312 if (memcmp(info.des_addr, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) {
313 for (auto *handler : this->broadcast_handlers_) {
314 if (handler->on_broadcast(info, packet->packet_.receive.data, packet->packet_.receive.size))
315 break; // If a handler returns true, stop processing further handlers
316 }
317 } else {
318 for (auto *handler : this->receive_handlers_) {
319 if (handler->on_receive(info, packet->packet_.receive.data, packet->packet_.receive.size))
320 break; // If a handler returns true, stop processing further handlers
321 }
322 }
323 }
324 break;
325 }
326 case ESPNowPacket::SENT: {
327#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
328 char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
329 format_mac_addr_upper(packet->packet_.sent.address, addr_buf);
330 ESP_LOGV(TAG, ">>> [%s] %s", addr_buf, LOG_STR_ARG(espnow_error_to_str(packet->packet_.sent.status)));
331#endif
332 if (this->current_send_packet_ != nullptr) {
333 if (this->current_send_packet_->callback_ != nullptr) {
334 this->current_send_packet_->callback_(packet->packet_.sent.status);
335 }
336 this->send_packet_pool_.release(this->current_send_packet_);
337 this->current_send_packet_ = nullptr; // Reset current packet after sending
338 }
339 break;
340 }
341 default:
342 break;
343 }
344 // Return the packet to the pool
345 this->receive_packet_pool_.release(packet);
346 packet = this->receive_packet_queue_.pop();
347 }
348
349 // Process sending packet queue
350 if (this->current_send_packet_ == nullptr) {
351 this->send_();
352 }
353
354 // Log dropped received packets periodically
355 uint16_t received_dropped = this->receive_packet_queue_.get_and_reset_dropped_count();
356 if (received_dropped > 0) {
357 ESP_LOGW(TAG, "Dropped %u received packets (queue full or oversized frame)", received_dropped);
358 }
359
360 // Log dropped send packets periodically
361 uint16_t send_dropped = this->send_packet_queue_.get_and_reset_dropped_count();
362 if (send_dropped > 0) {
363 ESP_LOGW(TAG, "Dropped %u send packets (queue full)", send_dropped);
364 }
365
366 // Nothing left to do; sleep until a callback or send() re-enables the loop.
367 // A packet in flight (current_send_packet_) needs no loop time even when more
368 // packets are queued behind it: the send callback re-enables the loop when
369 // the result arrives, and the SENT event handler above starts the next send.
370 if (this->receive_packet_queue_.empty() &&
371 (this->current_send_packet_ != nullptr || this->send_packet_queue_.empty())) {
372 this->disable_loop();
373 }
374}
375
376uint8_t ESPNowComponent::get_wifi_channel() {
377 wifi_second_chan_t dummy;
378 esp_wifi_get_channel(&this->wifi_channel_, &dummy);
379 return this->wifi_channel_;
380}
381
382esp_err_t ESPNowComponent::send(const uint8_t *peer_address, const uint8_t *payload, size_t size,
383 const send_callback_t &callback) {
384 if (this->state_ != ESPNOW_STATE_ENABLED) {
385 return ESP_ERR_ESPNOW_NOT_INIT;
386 } else if (this->is_failed()) {
387 return ESP_ERR_ESPNOW_FAILED;
388 } else if (peer_address == 0ULL) {
389 return ESP_ERR_ESPNOW_PEER_NOT_SET;
390 } else if (memcmp(peer_address, this->own_address_, ESP_NOW_ETH_ALEN) == 0) {
391 return ESP_ERR_ESPNOW_OWN_ADDRESS;
392 } else if (size > ESPNOW_MAX_DATA_LEN) {
393 return ESP_ERR_ESPNOW_DATA_SIZE;
394 } else if (!esp_now_is_peer_exist(peer_address)) {
395 if (memcmp(peer_address, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0 || this->auto_add_peer_) {
396 esp_err_t err = this->add_peer(peer_address);
397 if (err != ESP_OK) {
398 return err;
399 }
400 } else {
401 return ESP_ERR_ESPNOW_PEER_NOT_PAIRED;
402 }
403 }
404 // Allocate a packet from the pool
405 ESPNowSendPacket *packet = this->send_packet_pool_.allocate();
406 if (packet == nullptr) {
407 this->send_packet_queue_.increment_dropped_count();
408 ESP_LOGE(TAG, "Failed to allocate send packet from pool");
409 this->status_momentary_warning("send-packet-pool-full");
410 return ESP_ERR_ESPNOW_NO_MEM;
411 }
412 // Load the packet data
413 packet->load_data(peer_address, payload, size, callback);
414 // Push the packet to the send queue
415 this->send_packet_queue_.push(packet);
416 // Loop may be disabled while idle; re-enable it to send the packet
417 // (any-context variant so callers off the main loop are safe too)
419 return ESP_OK;
420}
421
422void ESPNowComponent::send_() {
423 ESPNowSendPacket *packet = this->send_packet_queue_.pop();
424 if (packet == nullptr) {
425 return; // No packets to send
426 }
427
428 this->current_send_packet_ = packet;
429 esp_err_t err = esp_now_send(packet->address_, packet->data_, packet->size_);
430 if (err != ESP_OK) {
431 char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
432 format_mac_addr_upper(packet->address_, addr_buf);
433 ESP_LOGE(TAG, "Failed to send packet to %s - %s", addr_buf, LOG_STR_ARG(espnow_error_to_str(err)));
434 if (packet->callback_ != nullptr) {
435 packet->callback_(err);
436 }
437 this->status_momentary_warning("send-failed");
438 this->send_packet_pool_.release(packet);
439 this->current_send_packet_ = nullptr; // Reset current packet
440 return;
441 }
442}
443
444esp_err_t ESPNowComponent::add_peer(const uint8_t *peer) {
445 if (this->state_ != ESPNOW_STATE_ENABLED || this->is_failed()) {
446 return ESP_ERR_ESPNOW_NOT_INIT;
447 }
448
449 if (memcmp(peer, this->own_address_, ESP_NOW_ETH_ALEN) == 0) {
450 this->status_momentary_warning("peer-add-failed");
451 return ESP_ERR_INVALID_MAC;
452 }
453
454 if (!esp_now_is_peer_exist(peer)) {
455 esp_now_peer_info_t peer_info = {};
456 memset(&peer_info, 0, sizeof(esp_now_peer_info_t));
457 peer_info.ifidx = WIFI_IF_STA;
458 memcpy(peer_info.peer_addr, peer, ESP_NOW_ETH_ALEN);
459 esp_err_t err = esp_now_add_peer(&peer_info);
460
461 if (err != ESP_OK) {
462 char peer_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
463 format_mac_addr_upper(peer, peer_buf);
464 ESP_LOGE(TAG, "Failed to add peer %s - %s", peer_buf, LOG_STR_ARG(espnow_error_to_str(err)));
465 this->status_momentary_warning("peer-add-failed");
466 return err;
467 }
468 }
469 bool found = false;
470 for (auto &it : this->peers_) {
471 if (it == peer) {
472 found = true;
473 break;
474 }
475 }
476 if (!found) {
477 ESPNowPeer new_peer;
478 memcpy(new_peer.address, peer, ESP_NOW_ETH_ALEN);
479 this->peers_.push_back(new_peer);
480 }
481
482 return ESP_OK;
483}
484
485esp_err_t ESPNowComponent::del_peer(const uint8_t *peer) {
486 if (this->state_ != ESPNOW_STATE_ENABLED || this->is_failed()) {
487 return ESP_ERR_ESPNOW_NOT_INIT;
488 }
489 if (esp_now_is_peer_exist(peer)) {
490 esp_err_t err = esp_now_del_peer(peer);
491 if (err != ESP_OK) {
492 char peer_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
493 format_mac_addr_upper(peer, peer_buf);
494 ESP_LOGE(TAG, "Failed to delete peer %s - %s", peer_buf, LOG_STR_ARG(espnow_error_to_str(err)));
495 this->status_momentary_warning("peer-del-failed");
496 return err;
497 }
498 }
499 for (auto it = this->peers_.begin(); it != this->peers_.end(); ++it) {
500 if (*it == peer) {
501 this->peers_.erase(it);
502 break;
503 }
504 }
505 return ESP_OK;
506}
507
508} // namespace esphome::espnow
509
510#endif // USE_ESP32
void load_sent_data(const uint8_t *mac_addr, esp_now_send_status_t status)
void load_received_data(const esp_now_recv_info_t *info, const uint8_t *data, int size)
void add_connect_state_listener(WiFiConnectStateListener *listener)
Add a listener for WiFi connection state changes.
esp_err_t esp_now_del_peer(const uint8_t *peer_addr)
esp_err_t esp_now_get_version(uint32_t *version)
esp_err_t esp_now_unregister_recv_cb(void)
esp_err_t esp_now_unregister_send_cb(void)
esp_err_t esp_now_register_send_cb(esp_now_send_cb_t cb)
esp_err_t esp_now_deinit(void)
esp_err_t esp_now_add_peer(const esp_now_peer_info_t *peer)
esp_err_t esp_now_send(const uint8_t *peer_addr, const uint8_t *data, size_t len)
bool esp_now_is_peer_exist(const uint8_t *peer_addr)
esp_err_t esp_now_register_recv_cb(esp_now_recv_cb_t cb)
esp_err_t esp_now_init(void)
void on_send_report(const esp_now_send_info_t *info, esp_now_send_status_t status) void on_send_report(const uint8_t *mac_addr
void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size)
ESPNowComponent * global_esp_now
global_esp_now enable_loop_soon_any_context()
void esp_now_send_status_t status
std::function< void(esp_err_t)> send_callback_t
WiFiComponent * global_wifi_component
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
Definition helpers.cpp:425
uint16_t size
Definition helpers.cpp:25
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
Definition helpers.h:1438
char * format_mac_addr_upper(const uint8_t *mac, char *output)
Format MAC address as XX:XX:XX:XX:XX:XX (uppercase, colon separators)
Definition helpers.h:1505
static void uint32_t