ESPHome 2026.1.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
10#include "esphome/core/log.h"
11
12#include <esp_event.h>
13#include <esp_mac.h>
14#include <esp_netif.h>
15#include <esp_now.h>
16#include <esp_random.h>
17#include <esp_wifi.h>
18#include <cstring>
19#include <memory>
20
21#ifdef USE_WIFI
23#endif
24
25namespace esphome::espnow {
26
27static constexpr const char *TAG = "espnow";
28
29static const esp_err_t CONFIG_ESPNOW_WAKE_WINDOW = 50;
30static const esp_err_t CONFIG_ESPNOW_WAKE_INTERVAL = 100;
31
32ESPNowComponent *global_esp_now = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
33
34static const LogString *espnow_error_to_str(esp_err_t error) {
35 switch (error) {
36 case ESP_ERR_ESPNOW_FAILED:
37 return LOG_STR("ESPNow is in fail mode");
38 case ESP_ERR_ESPNOW_OWN_ADDRESS:
39 return LOG_STR("Message to your self");
40 case ESP_ERR_ESPNOW_DATA_SIZE:
41 return LOG_STR("Data size to large");
42 case ESP_ERR_ESPNOW_PEER_NOT_SET:
43 return LOG_STR("Peer address not set");
44 case ESP_ERR_ESPNOW_PEER_NOT_PAIRED:
45 return LOG_STR("Peer address not paired");
46 case ESP_ERR_ESPNOW_NOT_INIT:
47 return LOG_STR("Not init");
48 case ESP_ERR_ESPNOW_ARG:
49 return LOG_STR("Invalid argument");
50 case ESP_ERR_ESPNOW_INTERNAL:
51 return LOG_STR("Internal Error");
52 case ESP_ERR_ESPNOW_NO_MEM:
53 return LOG_STR("Our of memory");
54 case ESP_ERR_ESPNOW_NOT_FOUND:
55 return LOG_STR("Peer not found");
56 case ESP_ERR_ESPNOW_IF:
57 return LOG_STR("Interface does not match");
58 case ESP_OK:
59 return LOG_STR("OK");
60 case ESP_NOW_SEND_FAIL:
61 return LOG_STR("Failed");
62 default:
63 return LOG_STR("Unknown Error");
64 }
65}
66
67std::string peer_str(uint8_t *peer) {
68 if (peer == nullptr || peer[0] == 0) {
69 return "[Not Set]";
70 } else if (memcmp(peer, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) {
71 return "[Broadcast]";
72 } else if (memcmp(peer, ESPNOW_MULTICAST_ADDR, ESP_NOW_ETH_ALEN) == 0) {
73 return "[Multicast]";
74 } else {
75 return format_mac_address_pretty(peer);
76 }
77}
78
79#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
80void on_send_report(const esp_now_send_info_t *info, esp_now_send_status_t status)
81#else
82void on_send_report(const uint8_t *mac_addr, esp_now_send_status_t status)
83#endif
84{
85 // Allocate an event from the pool
87 if (packet == nullptr) {
88 // No events available - queue is full or we're out of memory
89 global_esp_now->receive_packet_queue_.increment_dropped_count();
90 return;
91 }
92
93// Load new packet data (replaces previous packet)
94#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
95 packet->load_sent_data(info->des_addr, status);
96#else
97 packet->load_sent_data(mac_addr, status);
98#endif
99
100 // Push the packet to the queue
102 // Push always because we're the only producer and the pool ensures we never exceed queue size
103
104 // Wake main loop immediately to process ESP-NOW send event instead of waiting for select() timeout
105#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
107#endif
108}
109
110void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size) {
111 // Allocate an event from the pool
113 if (packet == nullptr) {
114 // No events available - queue is full or we're out of memory
115 global_esp_now->receive_packet_queue_.increment_dropped_count();
116 return;
117 }
118
119 // Load new packet data (replaces previous packet)
120 packet->load_received_data(info, data, size);
121
122 // Push the packet to the queue
124 // Push always because we're the only producer and the pool ensures we never exceed queue size
125
126 // Wake main loop immediately to process ESP-NOW receive event instead of waiting for select() timeout
127#if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE)
129#endif
130}
131
133
135 uint32_t version = 0;
136 esp_now_get_version(&version);
137
138 ESP_LOGCONFIG(TAG, "espnow:");
139 if (this->is_disabled()) {
140 ESP_LOGCONFIG(TAG, " Disabled");
141 return;
142 }
143 ESP_LOGCONFIG(TAG,
144 " Own address: %s\n"
145 " Version: v%" PRIu32 "\n"
146 " Wi-Fi channel: %d",
147 format_mac_address_pretty(this->own_address_).c_str(), version, this->wifi_channel_);
148#ifdef USE_WIFI
149 ESP_LOGCONFIG(TAG, " Wi-Fi enabled: %s", YESNO(this->is_wifi_enabled()));
150#endif
151}
152
154#ifdef USE_WIFI
156#else
157 return false;
158#endif
159}
160
162#ifndef USE_WIFI
163 // Initialize LwIP stack for wake_loop_threadsafe() socket support
164 // When WiFi component is present, it handles esp_netif_init()
165 ESP_ERROR_CHECK(esp_netif_init());
166#endif
167
168 if (this->enable_on_boot_) {
169 this->enable_();
170 } else {
172 }
173}
174
176 if (this->state_ == ESPNOW_STATE_ENABLED)
177 return;
178
179 ESP_LOGD(TAG, "Enabling");
180 this->state_ = ESPNOW_STATE_OFF;
181
182 this->enable_();
183}
184
186 if (!this->is_wifi_enabled()) {
187 esp_event_loop_create_default();
188
189 wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
190
191 ESP_ERROR_CHECK(esp_wifi_init(&cfg));
192 ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
193 ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
194 ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
195 ESP_ERROR_CHECK(esp_wifi_start());
196 ESP_ERROR_CHECK(esp_wifi_disconnect());
197
198 this->apply_wifi_channel();
199 }
200 this->get_wifi_channel();
201
202 esp_err_t err = esp_now_init();
203 if (err != ESP_OK) {
204 ESP_LOGE(TAG, "esp_now_init failed: %s", esp_err_to_name(err));
205 this->mark_failed();
206 return;
207 }
208
209 err = esp_now_register_recv_cb(on_data_received);
210 if (err != ESP_OK) {
211 ESP_LOGE(TAG, "esp_now_register_recv_cb failed: %s", esp_err_to_name(err));
212 this->mark_failed();
213 return;
214 }
215
216 err = esp_now_register_send_cb(on_send_report);
217 if (err != ESP_OK) {
218 ESP_LOGE(TAG, "esp_now_register_recv_cb failed: %s", esp_err_to_name(err));
219 this->mark_failed();
220 return;
221 }
222
223 esp_wifi_get_mac(WIFI_IF_STA, this->own_address_);
224
225#ifdef USE_DEEP_SLEEP
226 esp_now_set_wake_window(CONFIG_ESPNOW_WAKE_WINDOW);
227 esp_wifi_connectionless_module_set_wake_interval(CONFIG_ESPNOW_WAKE_INTERVAL);
228#endif
229
231
232 for (auto peer : this->peers_) {
233 this->add_peer(peer.address);
234 }
235}
236
238 if (this->state_ == ESPNOW_STATE_DISABLED)
239 return;
240
241 ESP_LOGD(TAG, "Disabling");
243
244 esp_now_unregister_recv_cb();
245 esp_now_unregister_send_cb();
246
247 esp_err_t err = esp_now_deinit();
248 if (err != ESP_OK) {
249 ESP_LOGE(TAG, "esp_now_deinit failed! 0x%x", err);
250 }
251}
252
254 if (this->state_ == ESPNOW_STATE_DISABLED) {
255 ESP_LOGE(TAG, "Cannot set channel when ESPNOW disabled");
256 this->mark_failed();
257 return;
258 }
259
260 if (this->is_wifi_enabled()) {
261 ESP_LOGE(TAG, "Cannot set channel when Wi-Fi enabled");
262 this->mark_failed();
263 return;
264 }
265
266 ESP_LOGI(TAG, "Channel set to %d.", this->wifi_channel_);
267 esp_wifi_set_promiscuous(true);
268 esp_wifi_set_channel(this->wifi_channel_, WIFI_SECOND_CHAN_NONE);
269 esp_wifi_set_promiscuous(false);
270}
271
273#ifdef USE_WIFI
274 if (wifi::global_wifi_component != nullptr && wifi::global_wifi_component->is_connected()) {
275 int32_t new_channel = wifi::global_wifi_component->get_wifi_channel();
276 if (new_channel != this->wifi_channel_) {
277 ESP_LOGI(TAG, "Wifi Channel is changed from %d to %d.", this->wifi_channel_, new_channel);
278 this->wifi_channel_ = new_channel;
279 }
280 }
281#endif
282 // Process received packets
283 ESPNowPacket *packet = this->receive_packet_queue_.pop();
284 while (packet != nullptr) {
285 switch (packet->type_) {
287 const ESPNowRecvInfo info = packet->get_receive_info();
288 if (!esp_now_is_peer_exist(info.src_addr)) {
289 bool handled = false;
290 for (auto *handler : this->unknown_peer_handlers_) {
291 if (handler->on_unknown_peer(info, packet->packet_.receive.data, packet->packet_.receive.size)) {
292 handled = true;
293 break; // If a handler returns true, stop processing further handlers
294 }
295 }
296 if (!handled && this->auto_add_peer_) {
297 this->add_peer(info.src_addr);
298 }
299 }
300 // Intentionally left as if instead of else in case the peer is added above
301 if (esp_now_is_peer_exist(info.src_addr)) {
302#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
303 char hex_buf[format_hex_pretty_size(ESP_NOW_MAX_DATA_LEN)];
304 ESP_LOGV(TAG, "<<< [%s -> %s] %s", format_mac_address_pretty(info.src_addr).c_str(),
306 format_hex_pretty_to(hex_buf, packet->packet_.receive.data, packet->packet_.receive.size));
307#endif
308 if (memcmp(info.des_addr, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0) {
309 for (auto *handler : this->broadcasted_handlers_) {
310 if (handler->on_broadcasted(info, packet->packet_.receive.data, packet->packet_.receive.size))
311 break; // If a handler returns true, stop processing further handlers
312 }
313 } else {
314 for (auto *handler : this->received_handlers_) {
315 if (handler->on_received(info, packet->packet_.receive.data, packet->packet_.receive.size))
316 break; // If a handler returns true, stop processing further handlers
317 }
318 }
319 }
320 break;
321 }
322 case ESPNowPacket::SENT: {
323#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
324 ESP_LOGV(TAG, ">>> [%s] %s", format_mac_address_pretty(packet->packet_.sent.address).c_str(),
325 LOG_STR_ARG(espnow_error_to_str(packet->packet_.sent.status)));
326#endif
327 if (this->current_send_packet_ != nullptr) {
328 this->current_send_packet_->callback_(packet->packet_.sent.status);
329 this->send_packet_pool_.release(this->current_send_packet_);
330 this->current_send_packet_ = nullptr; // Reset current packet after sending
331 }
332 break;
333 }
334 default:
335 break;
336 }
337 // Return the packet to the pool
338 this->receive_packet_pool_.release(packet);
339 packet = this->receive_packet_queue_.pop();
340 }
341
342 // Process sending packet queue
343 if (this->current_send_packet_ == nullptr) {
344 this->send_();
345 }
346
347 // Log dropped received packets periodically
348 uint16_t received_dropped = this->receive_packet_queue_.get_and_reset_dropped_count();
349 if (received_dropped > 0) {
350 ESP_LOGW(TAG, "Dropped %u received packets due to buffer overflow", received_dropped);
351 }
352
353 // Log dropped send packets periodically
354 uint16_t send_dropped = this->send_packet_queue_.get_and_reset_dropped_count();
355 if (send_dropped > 0) {
356 ESP_LOGW(TAG, "Dropped %u send packets due to buffer overflow", send_dropped);
357 }
358}
359
361 wifi_second_chan_t dummy;
362 esp_wifi_get_channel(&this->wifi_channel_, &dummy);
363 return this->wifi_channel_;
364}
365
366esp_err_t ESPNowComponent::send(const uint8_t *peer_address, const uint8_t *payload, size_t size,
367 const send_callback_t &callback) {
368 if (this->state_ != ESPNOW_STATE_ENABLED) {
369 return ESP_ERR_ESPNOW_NOT_INIT;
370 } else if (this->is_failed()) {
371 return ESP_ERR_ESPNOW_FAILED;
372 } else if (peer_address == 0ULL) {
373 return ESP_ERR_ESPNOW_PEER_NOT_SET;
374 } else if (memcmp(peer_address, this->own_address_, ESP_NOW_ETH_ALEN) == 0) {
375 return ESP_ERR_ESPNOW_OWN_ADDRESS;
376 } else if (size > ESP_NOW_MAX_DATA_LEN) {
377 return ESP_ERR_ESPNOW_DATA_SIZE;
378 } else if (!esp_now_is_peer_exist(peer_address)) {
379 if (memcmp(peer_address, ESPNOW_BROADCAST_ADDR, ESP_NOW_ETH_ALEN) == 0 || this->auto_add_peer_) {
380 esp_err_t err = this->add_peer(peer_address);
381 if (err != ESP_OK) {
382 return err;
383 }
384 } else {
385 return ESP_ERR_ESPNOW_PEER_NOT_PAIRED;
386 }
387 }
388 // Allocate a packet from the pool
389 ESPNowSendPacket *packet = this->send_packet_pool_.allocate();
390 if (packet == nullptr) {
391 this->send_packet_queue_.increment_dropped_count();
392 ESP_LOGE(TAG, "Failed to allocate send packet from pool");
393 this->status_momentary_warning("send-packet-pool-full");
394 return ESP_ERR_ESPNOW_NO_MEM;
395 }
396 // Load the packet data
397 packet->load_data(peer_address, payload, size, callback);
398 // Push the packet to the send queue
399 this->send_packet_queue_.push(packet);
400 return ESP_OK;
401}
402
404 ESPNowSendPacket *packet = this->send_packet_queue_.pop();
405 if (packet == nullptr) {
406 return; // No packets to send
407 }
408
409 this->current_send_packet_ = packet;
410 esp_err_t err = esp_now_send(packet->address_, packet->data_, packet->size_);
411 if (err != ESP_OK) {
412 ESP_LOGE(TAG, "Failed to send packet to %s - %s", format_mac_address_pretty(packet->address_).c_str(),
413 LOG_STR_ARG(espnow_error_to_str(err)));
414 if (packet->callback_ != nullptr) {
415 packet->callback_(err);
416 }
417 this->status_momentary_warning("send-failed");
418 this->send_packet_pool_.release(packet);
419 this->current_send_packet_ = nullptr; // Reset current packet
420 return;
421 }
422}
423
424esp_err_t ESPNowComponent::add_peer(const uint8_t *peer) {
425 if (this->state_ != ESPNOW_STATE_ENABLED || this->is_failed()) {
426 return ESP_ERR_ESPNOW_NOT_INIT;
427 }
428
429 if (memcmp(peer, this->own_address_, ESP_NOW_ETH_ALEN) == 0) {
430 this->status_momentary_warning("peer-add-failed");
431 return ESP_ERR_INVALID_MAC;
432 }
433
434 if (!esp_now_is_peer_exist(peer)) {
435 esp_now_peer_info_t peer_info = {};
436 memset(&peer_info, 0, sizeof(esp_now_peer_info_t));
437 peer_info.ifidx = WIFI_IF_STA;
438 memcpy(peer_info.peer_addr, peer, ESP_NOW_ETH_ALEN);
439 esp_err_t err = esp_now_add_peer(&peer_info);
440
441 if (err != ESP_OK) {
442 ESP_LOGE(TAG, "Failed to add peer %s - %s", format_mac_address_pretty(peer).c_str(),
443 LOG_STR_ARG(espnow_error_to_str(err)));
444 this->status_momentary_warning("peer-add-failed");
445 return err;
446 }
447 }
448 bool found = false;
449 for (auto &it : this->peers_) {
450 if (it == peer) {
451 found = true;
452 break;
453 }
454 }
455 if (!found) {
456 ESPNowPeer new_peer;
457 memcpy(new_peer.address, peer, ESP_NOW_ETH_ALEN);
458 this->peers_.push_back(new_peer);
459 }
460
461 return ESP_OK;
462}
463
464esp_err_t ESPNowComponent::del_peer(const uint8_t *peer) {
465 if (this->state_ != ESPNOW_STATE_ENABLED || this->is_failed()) {
466 return ESP_ERR_ESPNOW_NOT_INIT;
467 }
468 if (esp_now_is_peer_exist(peer)) {
469 esp_err_t err = esp_now_del_peer(peer);
470 if (err != ESP_OK) {
471 ESP_LOGE(TAG, "Failed to delete peer %s - %s", format_mac_address_pretty(peer).c_str(),
472 LOG_STR_ARG(espnow_error_to_str(err)));
473 this->status_momentary_warning("peer-del-failed");
474 return err;
475 }
476 }
477 for (auto it = this->peers_.begin(); it != this->peers_.end(); ++it) {
478 if (*it == peer) {
479 this->peers_.erase(it);
480 break;
481 }
482 }
483 return ESP_OK;
484}
485
486} // namespace esphome::espnow
487
488#endif // USE_ESP32
void wake_loop_threadsafe()
Wake the main event loop from a FreeRTOS task Thread-safe, can be called from task context to immedia...
virtual void mark_failed()
Mark this component as failed.
bool is_failed() const
void status_momentary_warning(const char *name, uint32_t length=5000)
Set warning status flag and automatically clear it after a timeout.
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_
EventPool< ESPNowSendPacket, MAX_ESP_NOW_SEND_QUEUE_SIZE > send_packet_pool_
std::vector< ESPNowPeer > peers_
friend void on_send_report(const esp_now_send_info_t *info, esp_now_send_status_t status)
std::vector< ESPNowBroadcastedHandler * > broadcasted_handlers_
friend void on_data_received(const esp_now_recv_info_t *info, const uint8_t *data, int size)
EventPool< ESPNowPacket, MAX_ESP_NOW_RECEIVE_QUEUE_SIZE > receive_packet_pool_
esp_err_t del_peer(const uint8_t *peer)
std::vector< ESPNowReceivedPacketHandler * > received_handlers_
LockFreeQueue< ESPNowPacket, MAX_ESP_NOW_RECEIVE_QUEUE_SIZE > receive_packet_queue_
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
std::string peer_str(uint8_t *peer)
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:331
std::string format_mac_address_pretty(const uint8_t *mac)
Definition helpers.cpp:283
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:735
Application App
Global storage of Application pointer - only one Application can exist.
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.