ESPHome 2026.8.0
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 uint32_t version = 0;
133 esp_now_get_version(&version);
134
135 ESP_LOGCONFIG(TAG, "espnow:");
136 if (this->is_disabled()) {
137 ESP_LOGCONFIG(TAG, " Disabled");
138 return;
139 }
140 char own_addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
141 format_mac_addr_upper(this->own_address_, own_addr_buf);
142 ESP_LOGCONFIG(TAG,
143 " Own address: %s\n"
144 " Version: v%" PRIu32 "\n"
145 " Wi-Fi channel: %d",
146 own_addr_buf, version, this->wifi_channel_);
147#ifdef USE_WIFI
148 ESP_LOGCONFIG(TAG, " Wi-Fi enabled: %s", YESNO(this->is_wifi_enabled()));
149#endif
150}
151
152bool ESPNowComponent::is_wifi_enabled() {
153#ifdef USE_WIFI
155#else
156 return false;
157#endif
158}
159
160void ESPNowComponent::setup() {
161#if defined(USE_WIFI) && defined(USE_WIFI_CONNECT_STATE_LISTENERS)
162 if (wifi::global_wifi_component != nullptr) {
164 }
165#endif
166 if (this->enable_on_boot_) {
167 this->enable_();
168 } else {
169 this->state_ = ESPNOW_STATE_DISABLED;
170 }
171}
172
173#if defined(USE_WIFI) && defined(USE_WIFI_CONNECT_STATE_LISTENERS)
174void ESPNowComponent::on_wifi_connect_state(StringRef ssid, std::span<const uint8_t, 6> bssid) {
175 if (ssid.empty()) {
176 return; // Disconnected; the channel is only meaningful while associated
177 }
178 uint8_t old_channel = this->wifi_channel_;
179 this->get_wifi_channel();
180 if (this->wifi_channel_ != old_channel) {
181 ESP_LOGI(TAG, "WiFi channel changed from %d to %d", old_channel, this->wifi_channel_);
182 }
183}
184#endif
185
186void ESPNowComponent::enable() {
187 if (this->state_ == ESPNOW_STATE_ENABLED)
188 return;
189
190 ESP_LOGD(TAG, "Enabling");
191 this->state_ = ESPNOW_STATE_OFF;
192
193 this->enable_();
194}
195
196void ESPNowComponent::enable_() {
197 if (!this->is_wifi_enabled()) {
198 wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
199
200 ESP_ERROR_CHECK(esp_wifi_init(&cfg));
201 ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
202 ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
203 ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
204 ESP_ERROR_CHECK(esp_wifi_start());
205 ESP_ERROR_CHECK(esp_wifi_disconnect());
206
207 this->apply_wifi_channel();
208 }
209 this->get_wifi_channel();
210
211 esp_err_t err = esp_now_init();
212 if (err != ESP_OK) {
213 ESP_LOGE(TAG, "esp_now_init failed: %s", esp_err_to_name(err));
214 this->mark_failed();
215 return;
216 }
217
218 err = esp_now_register_recv_cb(on_data_received);
219 if (err != ESP_OK) {
220 ESP_LOGE(TAG, "esp_now_register_recv_cb failed: %s", esp_err_to_name(err));
221 this->mark_failed();
222 return;
223 }
224
225 err = esp_now_register_send_cb(on_send_report);
226 if (err != ESP_OK) {
227 ESP_LOGE(TAG, "esp_now_register_recv_cb failed: %s", esp_err_to_name(err));
228 this->mark_failed();
229 return;
230 }
231
232 esp_wifi_get_mac(WIFI_IF_STA, this->own_address_);
233
234 this->state_ = ESPNOW_STATE_ENABLED;
235
236 for (auto peer : this->peers_) {
237 this->add_peer(peer.address);
238 }
239}
240
241void ESPNowComponent::disable() {
242 if (this->state_ == ESPNOW_STATE_DISABLED)
243 return;
244
245 ESP_LOGD(TAG, "Disabling");
246 this->state_ = ESPNOW_STATE_DISABLED;
247
248 esp_now_unregister_recv_cb();
249 esp_now_unregister_send_cb();
250
251 esp_err_t err = esp_now_deinit();
252 if (err != ESP_OK) {
253 ESP_LOGE(TAG, "esp_now_deinit failed! 0x%x", err);
254 }
255}
256
257void ESPNowComponent::apply_wifi_channel() {
258 if (this->state_ == ESPNOW_STATE_DISABLED) {
259 ESP_LOGE(TAG, "Cannot set channel when ESPNOW disabled");
260 this->mark_failed();
261 return;
262 }
263
264 if (this->is_wifi_enabled()) {
265 ESP_LOGE(TAG, "Cannot set channel when Wi-Fi enabled");
266 this->mark_failed();
267 return;
268 }
269
270 ESP_LOGI(TAG, "Channel set to %d.", this->wifi_channel_);
271 esp_wifi_set_promiscuous(true);
272 esp_wifi_set_channel(this->wifi_channel_, WIFI_SECOND_CHAN_NONE);
273 esp_wifi_set_promiscuous(false);
274}
275
276void ESPNowComponent::loop() {
277 // Process received packets
278 ESPNowPacket *packet = this->receive_packet_queue_.pop();
279 while (packet != nullptr) {
280 switch (packet->type_) {
282 const ESPNowRecvInfo info = packet->get_receive_info();
283 if (!esp_now_is_peer_exist(info.src_addr)) {
284 bool handled = false;
285 for (auto *handler : this->unknown_peer_handlers_) {
286 if (handler->on_unknown_peer(info, packet->packet_.receive.data, packet->packet_.receive.size)) {
287 handled = true;
288 break; // If a handler returns true, stop processing further handlers
289 }
290 }
291 if (!handled && this->auto_add_peer_) {
292 this->add_peer(info.src_addr);
293 }
294 }
295 // Intentionally left as if instead of else in case the peer is added above
296 if (esp_now_is_peer_exist(info.src_addr)) {
297#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
298 char src_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
299 char dst_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
300 // Cap the hex dump at a v1 frame: a full v2 frame would need a
301 // ~4.4 KB stack buffer.
302 char hex_buf[format_hex_pretty_size(ESP_NOW_MAX_DATA_LEN)];
303 format_mac_addr_upper(info.src_addr, src_buf);
304 format_mac_addr_upper(info.des_addr, dst_buf);
305 ESP_LOGV(TAG, "<<< [%s -> %s] %s", src_buf, dst_buf,
306 format_hex_pretty_to(hex_buf, packet->packet_.receive.data,
307 std::min<uint16_t>(packet->packet_.receive.size, ESP_NOW_MAX_DATA_LEN)));
308#endif
309 if (memcmp(info.des_addr, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) {
310 for (auto *handler : this->broadcast_handlers_) {
311 if (handler->on_broadcast(info, packet->packet_.receive.data, packet->packet_.receive.size))
312 break; // If a handler returns true, stop processing further handlers
313 }
314 } else {
315 for (auto *handler : this->receive_handlers_) {
316 if (handler->on_receive(info, packet->packet_.receive.data, packet->packet_.receive.size))
317 break; // If a handler returns true, stop processing further handlers
318 }
319 }
320 }
321 break;
322 }
323 case ESPNowPacket::SENT: {
324#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
325 char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
326 format_mac_addr_upper(packet->packet_.sent.address, addr_buf);
327 ESP_LOGV(TAG, ">>> [%s] %s", addr_buf, LOG_STR_ARG(espnow_error_to_str(packet->packet_.sent.status)));
328#endif
329 if (this->current_send_packet_ != nullptr) {
330 if (this->current_send_packet_->callback_ != nullptr) {
331 this->current_send_packet_->callback_(packet->packet_.sent.status);
332 }
333 this->send_packet_pool_.release(this->current_send_packet_);
334 this->current_send_packet_ = nullptr; // Reset current packet after sending
335 }
336 break;
337 }
338 default:
339 break;
340 }
341 // Return the packet to the pool
342 this->receive_packet_pool_.release(packet);
343 packet = this->receive_packet_queue_.pop();
344 }
345
346 // Process sending packet queue
347 if (this->current_send_packet_ == nullptr) {
348 this->send_();
349 }
350
351 // Log dropped received packets periodically
352 uint16_t received_dropped = this->receive_packet_queue_.get_and_reset_dropped_count();
353 if (received_dropped > 0) {
354 ESP_LOGW(TAG, "Dropped %u received packets (queue full or oversized frame)", received_dropped);
355 }
356
357 // Log dropped send packets periodically
358 uint16_t send_dropped = this->send_packet_queue_.get_and_reset_dropped_count();
359 if (send_dropped > 0) {
360 ESP_LOGW(TAG, "Dropped %u send packets (queue full)", send_dropped);
361 }
362
363 // Nothing left to do; sleep until a callback or send() re-enables the loop.
364 // A packet in flight (current_send_packet_) needs no loop time even when more
365 // packets are queued behind it: the send callback re-enables the loop when
366 // the result arrives, and the SENT event handler above starts the next send.
367 if (this->receive_packet_queue_.empty() &&
368 (this->current_send_packet_ != nullptr || this->send_packet_queue_.empty())) {
369 this->disable_loop();
370 }
371}
372
373uint8_t ESPNowComponent::get_wifi_channel() {
374 wifi_second_chan_t dummy;
375 esp_wifi_get_channel(&this->wifi_channel_, &dummy);
376 return this->wifi_channel_;
377}
378
379esp_err_t ESPNowComponent::send(const uint8_t *peer_address, const uint8_t *payload, size_t size,
380 const send_callback_t &callback) {
381 if (this->state_ != ESPNOW_STATE_ENABLED) {
382 return ESP_ERR_ESPNOW_NOT_INIT;
383 } else if (this->is_failed()) {
384 return ESP_ERR_ESPNOW_FAILED;
385 } else if (peer_address == 0ULL) {
386 return ESP_ERR_ESPNOW_PEER_NOT_SET;
387 } else if (memcmp(peer_address, this->own_address_, ESP_NOW_ETH_ALEN) == 0) {
388 return ESP_ERR_ESPNOW_OWN_ADDRESS;
389 } else if (size > ESPNOW_MAX_DATA_LEN) {
390 return ESP_ERR_ESPNOW_DATA_SIZE;
391 } else if (!esp_now_is_peer_exist(peer_address)) {
392 if (memcmp(peer_address, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0 || this->auto_add_peer_) {
393 esp_err_t err = this->add_peer(peer_address);
394 if (err != ESP_OK) {
395 return err;
396 }
397 } else {
398 return ESP_ERR_ESPNOW_PEER_NOT_PAIRED;
399 }
400 }
401 // Allocate a packet from the pool
402 ESPNowSendPacket *packet = this->send_packet_pool_.allocate();
403 if (packet == nullptr) {
404 this->send_packet_queue_.increment_dropped_count();
405 ESP_LOGE(TAG, "Failed to allocate send packet from pool");
406 this->status_momentary_warning("send-packet-pool-full");
407 return ESP_ERR_ESPNOW_NO_MEM;
408 }
409 // Load the packet data
410 packet->load_data(peer_address, payload, size, callback);
411 // Push the packet to the send queue
412 this->send_packet_queue_.push(packet);
413 // Loop may be disabled while idle; re-enable it to send the packet
414 // (any-context variant so callers off the main loop are safe too)
416 return ESP_OK;
417}
418
419void ESPNowComponent::send_() {
420 ESPNowSendPacket *packet = this->send_packet_queue_.pop();
421 if (packet == nullptr) {
422 return; // No packets to send
423 }
424
425 this->current_send_packet_ = packet;
426 esp_err_t err = esp_now_send(packet->address_, packet->data_, packet->size_);
427 if (err != ESP_OK) {
428 char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
429 format_mac_addr_upper(packet->address_, addr_buf);
430 ESP_LOGE(TAG, "Failed to send packet to %s - %s", addr_buf, LOG_STR_ARG(espnow_error_to_str(err)));
431 if (packet->callback_ != nullptr) {
432 packet->callback_(err);
433 }
434 this->status_momentary_warning("send-failed");
435 this->send_packet_pool_.release(packet);
436 this->current_send_packet_ = nullptr; // Reset current packet
437 return;
438 }
439}
440
441esp_err_t ESPNowComponent::add_peer(const uint8_t *peer) {
442 if (this->state_ != ESPNOW_STATE_ENABLED || this->is_failed()) {
443 return ESP_ERR_ESPNOW_NOT_INIT;
444 }
445
446 if (memcmp(peer, this->own_address_, ESP_NOW_ETH_ALEN) == 0) {
447 this->status_momentary_warning("peer-add-failed");
448 return ESP_ERR_INVALID_MAC;
449 }
450
451 if (!esp_now_is_peer_exist(peer)) {
452 esp_now_peer_info_t peer_info = {};
453 memset(&peer_info, 0, sizeof(esp_now_peer_info_t));
454 peer_info.ifidx = WIFI_IF_STA;
455 memcpy(peer_info.peer_addr, peer, ESP_NOW_ETH_ALEN);
456 esp_err_t err = esp_now_add_peer(&peer_info);
457
458 if (err != ESP_OK) {
459 char peer_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
460 format_mac_addr_upper(peer, peer_buf);
461 ESP_LOGE(TAG, "Failed to add peer %s - %s", peer_buf, LOG_STR_ARG(espnow_error_to_str(err)));
462 this->status_momentary_warning("peer-add-failed");
463 return err;
464 }
465 }
466 bool found = false;
467 for (auto &it : this->peers_) {
468 if (it == peer) {
469 found = true;
470 break;
471 }
472 }
473 if (!found) {
474 ESPNowPeer new_peer;
475 memcpy(new_peer.address, peer, ESP_NOW_ETH_ALEN);
476 this->peers_.push_back(new_peer);
477 }
478
479 return ESP_OK;
480}
481
482esp_err_t ESPNowComponent::del_peer(const uint8_t *peer) {
483 if (this->state_ != ESPNOW_STATE_ENABLED || this->is_failed()) {
484 return ESP_ERR_ESPNOW_NOT_INIT;
485 }
486 if (esp_now_is_peer_exist(peer)) {
487 esp_err_t err = esp_now_del_peer(peer);
488 if (err != ESP_OK) {
489 char peer_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
490 format_mac_addr_upper(peer, peer_buf);
491 ESP_LOGE(TAG, "Failed to delete peer %s - %s", peer_buf, LOG_STR_ARG(espnow_error_to_str(err)));
492 this->status_momentary_warning("peer-del-failed");
493 return err;
494 }
495 }
496 for (auto it = this->peers_.begin(); it != this->peers_.end(); ++it) {
497 if (*it == peer) {
498 this->peers_.erase(it);
499 break;
500 }
501 }
502 return ESP_OK;
503}
504
505} // namespace esphome::espnow
506
507#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.
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:406
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:1426
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:1493
static void uint32_t