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