ESPHome 2026.6.4
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 <cinttypes>
8
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
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 return;
78 }
79
80// Load new packet data (replaces previous packet)
81#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
82 packet->load_sent_data(info->des_addr, status);
83#else
84 packet->load_sent_data(mac_addr, status);
85#endif
86
87 // Push the packet to the queue
89 // Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
90 // allocate() returned non-null, the queue cannot be full.
91
92 // Wake main loop immediately to process ESP-NOW send event
94}
95
96void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size) {
97 // Drop oversized frames before copying. ESP-NOW v2 peers (IDF >= 5.4 builds a
98 // v2 stack with no opt-out) can send up to ESP_NOW_MAX_DATA_LEN_V2 (1470 B),
99 // but our receive buffer is ESP_NOW_MAX_DATA_LEN (250 B); copying a larger
100 // frame would overflow packet_.receive.data.
101 if (size < 0 || size > ESP_NOW_MAX_DATA_LEN) {
102 global_esp_now->receive_packet_queue_.increment_dropped_count();
103 return;
104 }
105
106 // Allocate an event from the pool
108 if (packet == nullptr) {
109 // No events available - queue is full or we're out of memory
110 global_esp_now->receive_packet_queue_.increment_dropped_count();
111 return;
112 }
113
114 // Load new packet data (replaces previous packet)
115 packet->load_received_data(info, data, size);
116
117 // Push the packet to the queue
119 // Push always succeeds: pool is sized to queue capacity (SIZE-1), so if
120 // allocate() returned non-null, the queue cannot be full.
121
122 // Wake main loop immediately to process ESP-NOW receive event
124}
125
127
129 uint32_t version = 0;
130 esp_now_get_version(&version);
131
132 ESP_LOGCONFIG(TAG, "espnow:");
133 if (this->is_disabled()) {
134 ESP_LOGCONFIG(TAG, " Disabled");
135 return;
136 }
137 char own_addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
138 format_mac_addr_upper(this->own_address_, own_addr_buf);
139 ESP_LOGCONFIG(TAG,
140 " Own address: %s\n"
141 " Version: v%" PRIu32 "\n"
142 " Wi-Fi channel: %d",
143 own_addr_buf, version, this->wifi_channel_);
144#ifdef USE_WIFI
145 ESP_LOGCONFIG(TAG, " Wi-Fi enabled: %s", YESNO(this->is_wifi_enabled()));
146#endif
147}
148
150#ifdef USE_WIFI
152#else
153 return false;
154#endif
155}
156
158 if (this->enable_on_boot_) {
159 this->enable_();
160 } else {
162 }
163}
164
166 if (this->state_ == ESPNOW_STATE_ENABLED)
167 return;
168
169 ESP_LOGD(TAG, "Enabling");
170 this->state_ = ESPNOW_STATE_OFF;
171
172 this->enable_();
173}
174
176 if (!this->is_wifi_enabled()) {
177 wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
178
179 ESP_ERROR_CHECK(esp_wifi_init(&cfg));
180 ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
181 ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
182 ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
183 ESP_ERROR_CHECK(esp_wifi_start());
184 ESP_ERROR_CHECK(esp_wifi_disconnect());
185
186 this->apply_wifi_channel();
187 }
188 this->get_wifi_channel();
189
190 esp_err_t err = esp_now_init();
191 if (err != ESP_OK) {
192 ESP_LOGE(TAG, "esp_now_init failed: %s", esp_err_to_name(err));
193 this->mark_failed();
194 return;
195 }
196
197 err = esp_now_register_recv_cb(on_data_received);
198 if (err != ESP_OK) {
199 ESP_LOGE(TAG, "esp_now_register_recv_cb failed: %s", esp_err_to_name(err));
200 this->mark_failed();
201 return;
202 }
203
204 err = esp_now_register_send_cb(on_send_report);
205 if (err != ESP_OK) {
206 ESP_LOGE(TAG, "esp_now_register_recv_cb failed: %s", esp_err_to_name(err));
207 this->mark_failed();
208 return;
209 }
210
211 esp_wifi_get_mac(WIFI_IF_STA, this->own_address_);
212
214
215 for (auto peer : this->peers_) {
216 this->add_peer(peer.address);
217 }
218}
219
221 if (this->state_ == ESPNOW_STATE_DISABLED)
222 return;
223
224 ESP_LOGD(TAG, "Disabling");
226
227 esp_now_unregister_recv_cb();
228 esp_now_unregister_send_cb();
229
230 esp_err_t err = esp_now_deinit();
231 if (err != ESP_OK) {
232 ESP_LOGE(TAG, "esp_now_deinit failed! 0x%x", err);
233 }
234}
235
237 if (this->state_ == ESPNOW_STATE_DISABLED) {
238 ESP_LOGE(TAG, "Cannot set channel when ESPNOW disabled");
239 this->mark_failed();
240 return;
241 }
242
243 if (this->is_wifi_enabled()) {
244 ESP_LOGE(TAG, "Cannot set channel when Wi-Fi enabled");
245 this->mark_failed();
246 return;
247 }
248
249 ESP_LOGI(TAG, "Channel set to %d.", this->wifi_channel_);
250 esp_wifi_set_promiscuous(true);
251 esp_wifi_set_channel(this->wifi_channel_, WIFI_SECOND_CHAN_NONE);
252 esp_wifi_set_promiscuous(false);
253}
254
256#ifdef USE_WIFI
257 if (wifi::global_wifi_component != nullptr && wifi::global_wifi_component->is_connected()) {
258 int32_t new_channel = wifi::global_wifi_component->get_wifi_channel();
259 if (new_channel != this->wifi_channel_) {
260 ESP_LOGI(TAG, "Wifi Channel is changed from %d to %" PRId32 ".", this->wifi_channel_, new_channel);
261 this->wifi_channel_ = new_channel;
262 }
263 }
264#endif
265 // Process received packets
266 ESPNowPacket *packet = this->receive_packet_queue_.pop();
267 while (packet != nullptr) {
268 switch (packet->type_) {
270 const ESPNowRecvInfo info = packet->get_receive_info();
271 if (!esp_now_is_peer_exist(info.src_addr)) {
272 bool handled = false;
273 for (auto *handler : this->unknown_peer_handlers_) {
274 if (handler->on_unknown_peer(info, packet->packet_.receive.data, packet->packet_.receive.size)) {
275 handled = true;
276 break; // If a handler returns true, stop processing further handlers
277 }
278 }
279 if (!handled && this->auto_add_peer_) {
280 this->add_peer(info.src_addr);
281 }
282 }
283 // Intentionally left as if instead of else in case the peer is added above
284 if (esp_now_is_peer_exist(info.src_addr)) {
285#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
286 char src_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
287 char dst_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
288 char hex_buf[format_hex_pretty_size(ESP_NOW_MAX_DATA_LEN)];
289 format_mac_addr_upper(info.src_addr, src_buf);
290 format_mac_addr_upper(info.des_addr, dst_buf);
291 ESP_LOGV(TAG, "<<< [%s -> %s] %s", src_buf, dst_buf,
292 format_hex_pretty_to(hex_buf, packet->packet_.receive.data, packet->packet_.receive.size));
293#endif
294 if (memcmp(info.des_addr, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) {
295 for (auto *handler : this->broadcast_handlers_) {
296 if (handler->on_broadcast(info, packet->packet_.receive.data, packet->packet_.receive.size))
297 break; // If a handler returns true, stop processing further handlers
298 }
299 } else {
300 for (auto *handler : this->receive_handlers_) {
301 if (handler->on_receive(info, packet->packet_.receive.data, packet->packet_.receive.size))
302 break; // If a handler returns true, stop processing further handlers
303 }
304 }
305 }
306 break;
307 }
308 case ESPNowPacket::SENT: {
309#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
310 char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
311 format_mac_addr_upper(packet->packet_.sent.address, addr_buf);
312 ESP_LOGV(TAG, ">>> [%s] %s", addr_buf, LOG_STR_ARG(espnow_error_to_str(packet->packet_.sent.status)));
313#endif
314 if (this->current_send_packet_ != nullptr) {
315 if (this->current_send_packet_->callback_ != nullptr) {
316 this->current_send_packet_->callback_(packet->packet_.sent.status);
317 }
318 this->send_packet_pool_.release(this->current_send_packet_);
319 this->current_send_packet_ = nullptr; // Reset current packet after sending
320 }
321 break;
322 }
323 default:
324 break;
325 }
326 // Return the packet to the pool
327 this->receive_packet_pool_.release(packet);
328 packet = this->receive_packet_queue_.pop();
329 }
330
331 // Process sending packet queue
332 if (this->current_send_packet_ == nullptr) {
333 this->send_();
334 }
335
336 // Log dropped received packets periodically
337 uint16_t received_dropped = this->receive_packet_queue_.get_and_reset_dropped_count();
338 if (received_dropped > 0) {
339 ESP_LOGW(TAG, "Dropped %u received packets (queue full or oversized frame)", received_dropped);
340 }
341
342 // Log dropped send packets periodically
343 uint16_t send_dropped = this->send_packet_queue_.get_and_reset_dropped_count();
344 if (send_dropped > 0) {
345 ESP_LOGW(TAG, "Dropped %u send packets (queue full)", send_dropped);
346 }
347}
348
350 wifi_second_chan_t dummy;
351 esp_wifi_get_channel(&this->wifi_channel_, &dummy);
352 return this->wifi_channel_;
353}
354
355esp_err_t ESPNowComponent::send(const uint8_t *peer_address, const uint8_t *payload, size_t size,
356 const send_callback_t &callback) {
357 if (this->state_ != ESPNOW_STATE_ENABLED) {
358 return ESP_ERR_ESPNOW_NOT_INIT;
359 } else if (this->is_failed()) {
360 return ESP_ERR_ESPNOW_FAILED;
361 } else if (peer_address == 0ULL) {
362 return ESP_ERR_ESPNOW_PEER_NOT_SET;
363 } else if (memcmp(peer_address, this->own_address_, ESP_NOW_ETH_ALEN) == 0) {
364 return ESP_ERR_ESPNOW_OWN_ADDRESS;
365 } else if (size > ESP_NOW_MAX_DATA_LEN) {
366 return ESP_ERR_ESPNOW_DATA_SIZE;
367 } else if (!esp_now_is_peer_exist(peer_address)) {
368 if (memcmp(peer_address, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0 || this->auto_add_peer_) {
369 esp_err_t err = this->add_peer(peer_address);
370 if (err != ESP_OK) {
371 return err;
372 }
373 } else {
374 return ESP_ERR_ESPNOW_PEER_NOT_PAIRED;
375 }
376 }
377 // Allocate a packet from the pool
378 ESPNowSendPacket *packet = this->send_packet_pool_.allocate();
379 if (packet == nullptr) {
380 this->send_packet_queue_.increment_dropped_count();
381 ESP_LOGE(TAG, "Failed to allocate send packet from pool");
382 this->status_momentary_warning("send-packet-pool-full");
383 return ESP_ERR_ESPNOW_NO_MEM;
384 }
385 // Load the packet data
386 packet->load_data(peer_address, payload, size, callback);
387 // Push the packet to the send queue
388 this->send_packet_queue_.push(packet);
389 return ESP_OK;
390}
391
393 ESPNowSendPacket *packet = this->send_packet_queue_.pop();
394 if (packet == nullptr) {
395 return; // No packets to send
396 }
397
398 this->current_send_packet_ = packet;
399 esp_err_t err = esp_now_send(packet->address_, packet->data_, packet->size_);
400 if (err != ESP_OK) {
401 char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
402 format_mac_addr_upper(packet->address_, addr_buf);
403 ESP_LOGE(TAG, "Failed to send packet to %s - %s", addr_buf, LOG_STR_ARG(espnow_error_to_str(err)));
404 if (packet->callback_ != nullptr) {
405 packet->callback_(err);
406 }
407 this->status_momentary_warning("send-failed");
408 this->send_packet_pool_.release(packet);
409 this->current_send_packet_ = nullptr; // Reset current packet
410 return;
411 }
412}
413
414esp_err_t ESPNowComponent::add_peer(const uint8_t *peer) {
415 if (this->state_ != ESPNOW_STATE_ENABLED || this->is_failed()) {
416 return ESP_ERR_ESPNOW_NOT_INIT;
417 }
418
419 if (memcmp(peer, this->own_address_, ESP_NOW_ETH_ALEN) == 0) {
420 this->status_momentary_warning("peer-add-failed");
421 return ESP_ERR_INVALID_MAC;
422 }
423
424 if (!esp_now_is_peer_exist(peer)) {
425 esp_now_peer_info_t peer_info = {};
426 memset(&peer_info, 0, sizeof(esp_now_peer_info_t));
427 peer_info.ifidx = WIFI_IF_STA;
428 memcpy(peer_info.peer_addr, peer, ESP_NOW_ETH_ALEN);
429 esp_err_t err = esp_now_add_peer(&peer_info);
430
431 if (err != ESP_OK) {
432 char peer_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
433 format_mac_addr_upper(peer, peer_buf);
434 ESP_LOGE(TAG, "Failed to add peer %s - %s", peer_buf, LOG_STR_ARG(espnow_error_to_str(err)));
435 this->status_momentary_warning("peer-add-failed");
436 return err;
437 }
438 }
439 bool found = false;
440 for (auto &it : this->peers_) {
441 if (it == peer) {
442 found = true;
443 break;
444 }
445 }
446 if (!found) {
447 ESPNowPeer new_peer;
448 memcpy(new_peer.address, peer, ESP_NOW_ETH_ALEN);
449 this->peers_.push_back(new_peer);
450 }
451
452 return ESP_OK;
453}
454
455esp_err_t ESPNowComponent::del_peer(const uint8_t *peer) {
456 if (this->state_ != ESPNOW_STATE_ENABLED || this->is_failed()) {
457 return ESP_ERR_ESPNOW_NOT_INIT;
458 }
459 if (esp_now_is_peer_exist(peer)) {
460 esp_err_t err = esp_now_del_peer(peer);
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 delete peer %s - %s", peer_buf, LOG_STR_ARG(espnow_error_to_str(err)));
465 this->status_momentary_warning("peer-del-failed");
466 return err;
467 }
468 }
469 for (auto it = this->peers_.begin(); it != this->peers_.end(); ++it) {
470 if (*it == peer) {
471 this->peers_.erase(it);
472 break;
473 }
474 }
475 return ESP_OK;
476}
477
478} // namespace esphome::espnow
479
480#endif // USE_ESP32
void wake_loop_threadsafe()
Wake the main event loop from another thread or callback.
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:272
void status_momentary_warning(const char *name, uint32_t length=5000)
Set warning status flag and automatically clear it after a timeout.
std::vector< ESPNowBroadcastHandler * > broadcast_handlers_
esp_err_t send(const uint8_t *peer_address, const std::vector< uint8_t > &payload, const send_callback_t &callback=nullptr)
Queue a packet to be sent to a specific peer address.
LockFreeQueue< ESPNowSendPacket, MAX_ESP_NOW_SEND_QUEUE_SIZE > send_packet_queue_
void add_peer(peer_address_t address)
std::vector< ESPNowUnknownPeerHandler * > unknown_peer_handlers_
std::vector< ESPNowPeer > peers_
friend void on_send_report(const esp_now_send_info_t *info, esp_now_send_status_t status)
friend void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size)
esp_err_t del_peer(const uint8_t *peer)
std::vector< ESPNowReceivedPacketHandler * > receive_handlers_
EventPool< ESPNowPacket, MAX_ESP_NOW_RECEIVE_QUEUE_SIZE - 1 > receive_packet_pool_
LockFreeQueue< ESPNowPacket, MAX_ESP_NOW_RECEIVE_QUEUE_SIZE > receive_packet_queue_
EventPool< ESPNowSendPacket, MAX_ESP_NOW_SEND_QUEUE_SIZE - 1 > send_packet_pool_
uint8_t own_address_[ESP_NOW_ETH_ALEN]
void load_sent_data(const uint8_t *mac_addr, esp_now_send_status_t status)
struct esphome::espnow::ESPNowPacket::@85::received_data receive
void load_received_data(const esp_now_recv_info_t *info, const uint8_t *data, int size)
union esphome::espnow::ESPNowPacket::@85 packet_
esp_now_packet_type_t type_
const ESPNowRecvInfo & get_receive_info() const
struct esphome::espnow::ESPNowPacket::@85::sent_data sent
void load_data(const uint8_t *peer_address, const uint8_t *payload, size_t size, const send_callback_t &callback)
uint8_t address_[ESP_NOW_ETH_ALEN]
uint8_t data_[ESP_NOW_MAX_DATA_LEN]
@ ESPNOW_STATE_ENABLED
ESPNOW is enabled.
@ ESPNOW_STATE_OFF
Nothing has been initialized yet.
@ ESPNOW_STATE_DISABLED
ESPNOW is disabled.
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
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:340
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:1386
Application App
Global storage of Application pointer - only one Application can exist.
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:1453
static void uint32_t
uint8_t address[ESP_NOW_ETH_ALEN]
uint8_t des_addr[ESP_NOW_ETH_ALEN]
Destination address of ESPNOW packet.
uint8_t src_addr[ESP_NOW_ETH_ALEN]
Source address of ESPNOW packet.