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