TinyRobotics
Loading...
Searching...
No Matches
ESPNowStream.h
1#pragma once
2#include <WiFi.h>
3#include <esp_now.h>
4#include <esp_wifi.h>
5
6#include "TinyRobotics/utils/BaseStream.h"
7#include "TinyRobotics/concurrency/RTOS/BufferRTOS.h"
8
9#define LOGE TRLogger.error
10#define LOGW TRLogger.warn
11#define LOGI TRLogger.info
12#define LOGD TRLogger.debug
13
14// propose max data length based on esp-idf version
15#ifdef ESP_NOW_MAX_DATA_LEN_V2
16// 1470
17#define MY_ESP_NOW_MAX_LEN ESP_NOW_MAX_DATA_LEN_V2
18#else
19// 240
20#define MY_ESP_NOW_MAX_LEN ESP_NOW_MAX_DATA_LEN
21#endif
22
23// calculate buffer count: 96000 bytes should be enough for most use cases and
24// should not cause memory issues on the receiver side. With 240 bytes per
25// packet, this results in 400 packets.
26#define MY_ESP_NOW_BUFFER_SIZE (240 * 400)
27#define MY_ESP_NOW_BUFFER_COUNT (MY_ESP_NOW_BUFFER_SIZE / MY_ESP_NOW_MAX_LEN)
28
29namespace tinyrobotics {
30
31// forward declarations
32class ESPNowStream;
33static ESPNowStream* ESPNowStreamSelf = nullptr;
34static const char* BROADCAST_MAC_STR = "FF:FF:FF:FF:FF:FF";
35static const uint8_t BROADCAST_MAC[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
36
37/**
38 * @brief Configuration for ESP-NOW protocolö.W
39 * @author Phil Schatzmann
40
41 */
42struct ESPNowStreamConfig {
43 /// WiFi mode (station or access point). Default: WIFI_STA
45 /// MAC address to use for the ESP-NOW interface (nullptr for default).
46 /// Default: nullptr
47 const char* mac_address = nullptr;
48 /// Size of each ESP-NOW packet buffer (bytes). Default: 1470 or 240 depending
49 /// on esp-idf version
51 /// Number of packet buffers allocated. Default: 65 or 400 depending on
52 /// esp-idf version
54 /// WiFi channel to use (0 for auto). Default: 0
55 int channel = 0;
56 /// WiFi SSID for connection (optional). Default: nullptr
57 const char* ssid = nullptr;
58 /// WiFi password for connection (optional). Default: nullptr
59 const char* password = nullptr;
60#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
61 /// Set the OUI (Organization Identifier) in the vendor-specific element for
62 /// ESPNOW.
63 uint32_t oui = 0;
64#endif
65 /// Use send acknowledgments to prevent buffer overflow. Default: true
66 bool use_send_ack = false; // we wait for
67 /// Delay after failed write (ms). Default: 2000
68 uint16_t delay_after_failed_write_ms = 2000;
69 // enable long_range mode: increases range but reduces throughput. Default:
70 // false
71 bool use_long_range = false;
72 /// Number of write retries (-1 for endless). Default: 1
73 int write_retry_count = 1; // -1 endless
74#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
75 /// Receive callback for ESP-NOW (esp-idf >= 5.0.0). Default: nullptr
76 void (*recveive_cb)(const esp_now_recv_info* info, const uint8_t* data,
77 int data_len) = nullptr;
78#else
79 /// Receive callback for ESP-NOW (esp-idf < 5.0.0). Default: nullptr
80 void (*recveive_cb)(const uint8_t* mac_addr, const uint8_t* data,
81 int data_len) = nullptr;
82#endif
83 /// Primary master key for encryption (16 bytes, optional). Default: nullptr
84 const char* primary_master_key = nullptr;
85 /// Local master key for encryption (16 bytes, optional). Default: nullptr
86 const char* local_master_key = nullptr;
87 /// ESP-NOW PHY mode. Default: WIFI_PHY_MODE_11G
89 /// ESP-NOW bit rate. Default: WIFI_PHY_RATE_6M
91 /// Buffer fill threshold (percent) to start reading. Default: 0
93 /// Timeout for ACK semaphore (ms). Default: portMAX_DELAY
95 /// Delay after updating mac
96 uint16_t delay_after_updating_mac_ms = 500;
97};
98
99/**
100 * @class ESPNowStream
101 * @ingroup communication
102 * @brief ESPNow as Arduino Stream. When use_send_ack is true we prevent any
103 * buffer overflows by blocking writes until the previous packet has been
104 * confirmed. When no acknowledgments are used, you might need to throttle the
105 * send speed to prevent any buffer overflow on the receiver side.
106 *
107 * @note If multiple receivers are in range, only the first one which sends an
108 * acknowledgment will be used as coordinator.
109 *
110 * @author Phil Schatzmann
111 */
112class ESPNowStream : public BaseStream {
113 public:
114 ESPNowStream() { ESPNowStreamSelf = this; };
115
116 ~ESPNowStream() {
117 if (xSemaphore != nullptr) vSemaphoreDelete(xSemaphore);
118 }
119
120 ESPNowStreamConfig defaultConfig() {
121 ESPNowStreamConfig result;
122 return result;
123 }
124
125 /// Returns the mac address of the current ESP32
126 const char* macAddress() {
127 static String mac_str = WiFi.macAddress();
128 return mac_str.c_str();
129 }
130
131 /// Defines an alternative send callback
132 void setSendCallback(esp_now_send_cb_t cb) { send = cb; }
133
134 /// Defines the Receive Callback - Deactivates the readBytes and available()
135 /// methods!
136 void setReceiveCallback(esp_now_recv_cb_t cb) { receive = cb; }
137
138 /// Initialization of ESPNow
139 bool begin() { return begin(cfg); }
140
141 /// Initialization of ESPNow incl WIFI
142 virtual bool begin(ESPNowStreamConfig cfg) {
143 this->cfg = cfg;
144 if (WiFi.getMode() == WIFI_MODE_NULL) {
145 WiFi.mode(cfg.wifi_mode);
146 } else {
147 cfg.wifi_mode = WiFi.getMode();
148 }
149
150 if (!setupMAC()) return false;
151
152 if (!setupWiFi()) return false;
153
154 WiFi.enableLongRange(cfg.use_long_range);
155
156#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
157 LOGI("Setting ESP-NEW rate");
158 if (esp_wifi_config_espnow_rate(getInterface(), cfg.rate) != ESP_OK) {
159 LOGW("Could not set rate");
160 }
161#endif
162 if (cfg.channel > 0) {
164 }
165 Serial.print("mac: ");
166 Serial.println(WiFi.macAddress());
167 Serial.print("channel: ");
168 Serial.println(getChannel());
169 return setup();
170 }
171
172 /// DeInitialization
173 virtual void end() {
174 if (is_init) {
175 if (esp_now_deinit() != ESP_OK) {
176 LOGE("esp_now_deinit");
177 }
178 if (buffer.size() > 0) buffer.resize(0);
179 is_init = false;
180 has_peers = false;
181 read_ready = false;
182 is_broadcast = false;
183 }
184 }
185
186 /// Defines the WiFi Channel
187 void setChannel(uint8_t ch) {
188 WiFi.setChannel(ch, WIFI_SECOND_CHAN_NONE);
189 cfg.channel = ch;
190 }
191
192 /// Provies the WiFi Channel
193 uint8_t getChannel() {
194 uint32_t ch = WiFi.channel();
195 return (uint8_t)ch ^ 0xff;
196 }
197
198 /// Adds a peer to which we can send info or from which we can receive info
199 bool addPeer(esp_now_peer_info_t& peer) {
200 if (!is_init) {
201 LOGE("addPeer before begin");
202 return false;
203 }
204 if (memcmp(BROADCAST_MAC, peer.peer_addr, 6) == 0) {
205 LOGI("Using broadcast");
206 is_broadcast = true;
207 }
208 esp_err_t result = esp_now_add_peer(&peer);
209 if (result == ESP_OK) {
210 LOGI("addPeer: %s", mac2str(peer.peer_addr));
211#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
212 esp_now_rate_config_t rate_config = {.phymode = cfg.phymode,
213 .rate = cfg.rate,
214 .ersu = false,
215 .dcm = false};
216 result = esp_now_set_peer_rate_config(peer.peer_addr, &rate_config);
217 if (result != ESP_OK) {
218 LOGW("Could not set the ESP-NOW PHY rate (%d) %s.", result,
219 esp_err_to_name(result));
220 }
221#endif
222 has_peers = result == ESP_OK;
223 } else {
224 LOGE("addPeer: %d", result);
225 }
226 return result == ESP_OK;
227 }
228
229 /// Adds a peer to which we can send info or from which we can receive info
230 bool addPeer(const uint8_t* address) {
231 esp_now_peer_info_t peer;
232 memcpy(peer.peer_addr, address, ESP_NOW_ETH_ALEN);
233
234 peer.channel = cfg.channel;
235 peer.ifidx = getInterface();
236 peer.encrypt = false;
237
238 if (isEncrypted()) {
239 peer.encrypt = true;
240 strncpy((char*)peer.lmk, cfg.local_master_key, 16);
241 }
242 return addPeer(peer);
243 }
244
245 /// Adds a peer to which we can send info or from which we can receive info
246 bool addPeer(const char* address) {
247 if (strcmp(address, cfg.mac_address)==0) {
248 LOGW("Did not add own address as peer");
249 return true;
250 }
251
252 uint8_t mac[] = {0, 0, 0, 0, 0, 0};
253 if (!str2mac(address, (uint8_t*)&mac)) {
254 LOGE("addPeer - Invalid address: %s", address);
255 return false;
256 }
257 return addPeer((const uint8_t*)&mac);
258 }
259
260 /// Adds an array of peers
261 template <size_t size>
262 bool addPeers(const char* (&array)[size]) {
263 bool result = true;
264 for (int j = 0; j < size; j++) {
265 const char* peer = array[j];
266 if (peer != nullptr) {
267 if (!addPeer(peer)) {
268 result = false;
269 }
270 }
271 }
272 return result;
273 }
274
275 /// Adds an array of peers
276 template <size_t N>
277 bool addPeers(const uint8_t (&array)[N][6]) {
278 bool result = true;
279 for (int j = 0; j < N; j++) {
280 const uint8_t* peer = array[j];
281 if (peer != nullptr) {
282 if (!addPeer(peer)) {
283 result = false;
284 }
285 }
286 }
287 return result;
288 }
289
290 /// Adds the broadcast peer (FF:FF:FF:FF:FF:FF) to send to all devices in
291 /// range. Note: Broadcast does not support acknowledgments
292 bool addBroadcastPeer() { return addPeer(BROADCAST_MAC); }
293
294 /// Deletes all registered peers
295 bool clearPeers() {
296 esp_now_peer_info_t peer;
297 uint8_t breakout_counter = 0;
298 while ((esp_now_fetch_peer(true, &peer) == ESP_OK) &&
299 (breakout_counter < ESP_NOW_MAX_TOTAL_PEER_NUM + 1)) {
300 esp_now_del_peer(peer.peer_addr);
301 breakout_counter++;
302 }
303
304 if (breakout_counter == ESP_NOW_MAX_TOTAL_PEER_NUM + 1) {
305 LOGE("Not all Peers seems to be removed.");
306 }
307 // return true when all peers are removed.
308 return breakout_counter <= ESP_NOW_MAX_TOTAL_PEER_NUM;
309 }
310
311 /// Writes the data - sends it to all registered peers
312 size_t write(const uint8_t* data, size_t len) override {
313 // nullptr means send to all registered peers
314 return write((const uint8_t*)nullptr, data, len);
315 }
316
317 /// Writes the data - sends it to all the indicated peer mac address string
318 size_t write(const char* peer, const uint8_t* data, size_t len) {
319 uint8_t mac[6];
320 if (!str2mac(peer, mac)) {
321 LOGE("write: invalid mac address %s", peer);
322 return 0;
323 }
324 return write(mac, data, len);
325 }
326
327 /// Writes the data - sends it to all the peers
328 size_t write(const uint8_t* peer, const uint8_t* data, size_t len) {
329 // initialization: setup semaphore
330 setupSemaphore();
331
332 // initialization: if no peers registered and peer is nullptr, add broadcast
333 if (!has_peers && peer == nullptr) {
335 }
336
337 size_t total_sent = 0;
338 size_t remaining = len;
339
340 while (remaining > 0) {
341 size_t chunk_size = min(remaining, (size_t)MY_ESP_NOW_MAX_LEN);
342 int retry_count = 0;
343
344 bool success =
345 sendPacket(data + total_sent, chunk_size, retry_count, peer);
346
347 if (success) {
348 // Chunk sent successfully
349 total_sent += chunk_size;
350 remaining -= chunk_size;
351 } else {
352 // Max retries exceeded for this chunk
353 LOGE(
354 "write: failed to send chunk after %d attempts (sent %zu/%zu "
355 "bytes)",
356 retry_count, total_sent, len);
357 // Return bytes successfully sent so far (may be 0 if first chunk
358 // failed)
359 return total_sent;
360 }
361 }
362 return total_sent;
363 }
364
365 /// Reeds the data from the peers
366 size_t readBytes(uint8_t* data, size_t len) override {
367 if (!read_ready) return 0;
368 if (buffer.size() == 0) return 0;
369 return buffer.readArray(data, len);
370 }
371
372 int available() override {
373 if (!buffer) return 0;
374 if (!read_ready) return 0;
375 return buffer.size() == 0 ? 0 : buffer.available();
376 }
377
378 int availableForWrite() override {
379 if (!buffer) return 0;
380 return cfg.use_send_ack ? available_to_write : cfg.buffer_size;
381 }
382
383 /// provides how much the receive buffer is filled (in percent)
384 float getBufferPercent() {
385 int size = buffer.size();
386 // prevent div by 0
387 if (size == 0) return 0.0;
388 // calculate percent
389 return 100.0 * buffer.available() / size;
390 }
391
392 /// provides access to the receive buffer
393 BufferRTOS<uint8_t>& getBuffer() { return buffer; }
394
395 /// time when we were able to send or receive the last packet successfully
396 uint32_t getLastIoSuccessTime() const { return last_io_success_time; }
397
398 protected:
400 BufferRTOS<uint8_t> buffer{0};
401 esp_now_recv_cb_t receive = default_recv_cb;
402 esp_now_send_cb_t send = default_send_cb;
403 volatile size_t available_to_write = 0;
404 volatile bool last_send_success = true;
405 bool is_init = false;
406 SemaphoreHandle_t xSemaphore = nullptr;
407 bool has_peers = false;
408 bool read_ready = false;
409 bool is_broadcast = false;
410 uint32_t last_io_success_time = 0;
411
412 bool setupMAC() {
413 // set mac address
414 if (cfg.mac_address != nullptr) {
415 LOGI("setting mac %s", cfg.mac_address);
416 byte mac[ESP_NOW_KEY_LEN];
417 str2mac(cfg.mac_address, mac);
418 if (esp_wifi_set_mac((wifi_interface_t)getInterface(), mac) != ESP_OK) {
419 LOGE("Could not set mac address");
420 return false;
421 }
422
423 // On some boards calling macAddress to early leads to a race condition.
424 delay(cfg.delay_after_updating_mac_ms);
425
426 // checking if address has been updated
427 const char* addr = macAddress();
428 if (strcmp(addr, cfg.mac_address) != 0) {
429 LOGE("Wrong mac address: %s", addr);
430 return false;
431 }
432 }
433 return true;
434 }
435
436 bool setupWiFi() {
437 if (WiFi.status() != WL_CONNECTED) {
438 // start only when not connected and we have ssid and password
439 if (cfg.ssid != nullptr && cfg.password != nullptr) {
440 LOGI("Logging into WiFi: %s", cfg.ssid);
441 WiFi.begin(cfg.ssid, cfg.password);
442 while (WiFi.status() != WL_CONNECTED) {
443 Serial.print('.');
444 delay(1000);
445 }
446 }
447 Serial.println();
448 }
449
450 // in AP mode we neeed to be logged in!
451 if (WiFi.getMode() == WIFI_AP && WiFi.status() != WL_CONNECTED) {
452 LOGE("You did not start Wifi or did not provide ssid and password");
453 return false;
454 }
455
456 return true;
457 }
458
459 inline void setupSemaphore() {
460 // use semaphore for confirmations
461 if (cfg.use_send_ack && xSemaphore == nullptr) {
462 xSemaphore = xSemaphoreCreateBinary();
463 xSemaphoreGive(xSemaphore);
464 }
465 }
466
467 inline void setupReceiveBuffer() {
468 // setup receive buffer
469 if (!buffer) {
470 LOGI("setupReceiveBuffer: %d", cfg.buffer_size * cfg.buffer_count);
471 buffer.resize(cfg.buffer_size * cfg.buffer_count);
472 }
473 }
474
475 inline void resetAvailableToWrite() {
476 if (cfg.use_send_ack) {
477 available_to_write = 0;
478 }
479 }
480
481 /// Sends a single packet with retry logic
482 virtual bool sendPacket(const uint8_t* data, size_t len, int& retry_count,
483 const uint8_t* destination = nullptr) {
484
485 const uint8_t* target = destination;
486 if (target == nullptr && is_broadcast) {
487 target = BROADCAST_MAC;
488 }
489
490 while (true) {
491 resetAvailableToWrite();
492
493 // Wait for previous send to complete (if using ACKs)
494 if (cfg.use_send_ack) {
495 TickType_t ticks = (cfg.ack_semaphore_timeout_ms == portMAX_DELAY)
496 ? portMAX_DELAY
497 : pdMS_TO_TICKS(cfg.ack_semaphore_timeout_ms);
498 if (xSemaphoreTake(xSemaphore, ticks) != pdTRUE) {
499 // Timeout waiting for previous send - check retry limit BEFORE
500 // incrementing
501 if (cfg.write_retry_count >= 0 &&
502 retry_count >= cfg.write_retry_count) {
503 LOGE("Timeout waiting for ACK semaphore after %d retries",
504 retry_count);
505 return false;
506 }
507 retry_count++;
508 LOGW("ACK semaphore timeout (attempt %d)", retry_count);
509 delay(cfg.delay_after_failed_write_ms);
510 continue;
511 }
512 }
513
514 // Try to queue the packet
515 esp_err_t rc = esp_now_send(target, data, len);
516
517 if (rc == ESP_OK) {
518 // Packet queued - wait for transmission result
519 if (handleTransmissionResult(retry_count)) {
520 return true; // Success
521 }
522 // Transmission failed - check if we've exceeded the limit
523 // handleTransmissionResult returns false both when limit is reached
524 // and when we should retry, so check the limit here
525 if (cfg.write_retry_count >= 0 &&
526 retry_count >= cfg.write_retry_count) {
527 return false; // Give up - limit reached
528 }
529 // Continue to retry
530 } else {
531 // Failed to queue - callback will NOT be called
532 if (cfg.use_send_ack) {
533 xSemaphoreGive(xSemaphore); // Give back semaphore
534 }
535
536 // Check limit BEFORE incrementing
537 if (cfg.write_retry_count >= 0 &&
538 retry_count >= cfg.write_retry_count) {
539 LOGE("esp_now_send queue error (rc=%d/0x%04X) after %d retries", rc,
540 rc, retry_count);
541 return false;
542 }
543
544 retry_count++;
545 LOGW("esp_now_send failed (rc=%d/0x%04X) - retrying (attempt %d)", rc,
546 rc, retry_count);
547 delay(cfg.delay_after_failed_write_ms);
548 }
549 }
550 }
551
552 /// Handles the result of packet transmission (after queuing)
553 bool handleTransmissionResult(int& retry_count) {
554
555 if (cfg.use_send_ack) {
556 // Wait for callback to signal result
557 TickType_t ticks = (cfg.ack_semaphore_timeout_ms == portMAX_DELAY)
558 ? portMAX_DELAY
559 : pdMS_TO_TICKS(cfg.ack_semaphore_timeout_ms);
560 if (xSemaphoreTake(xSemaphore, ticks) != pdTRUE) {
561 // Callback never came - check limit BEFORE incrementing
562 if (cfg.write_retry_count >= 0 &&
563 retry_count >= cfg.write_retry_count) {
564 LOGE("Transmission callback timeout after %d retries", retry_count);
565 return false;
566 }
567 retry_count++;
568 LOGW("Transmission callback timeout (attempt %d)", retry_count);
569 delay(cfg.delay_after_failed_write_ms);
570 return false; // Retry
571 }
572
573 // Got callback - check result
574 if (last_send_success) {
575 xSemaphoreGive(xSemaphore); // Release for next send
576 return true;
577 } else {
578 xSemaphoreGive(xSemaphore); // Release for retry
579
580 // Check limit BEFORE incrementing
581 if (cfg.write_retry_count >= 0 &&
582 retry_count >= cfg.write_retry_count) {
583 LOGE("Transmission failed after %d retries", retry_count);
584 return false;
585 }
586
587 retry_count++;
588 LOGI("Transmission failed - retrying (attempt %d)", retry_count);
589 delay(cfg.delay_after_failed_write_ms);
590 return false; // Retry
591 }
592 }
593 return true; // No ACK mode - assume success
594 }
595
596 /// Handles errors when queuing packets
597 bool handleQueueError(esp_err_t rc, int& retry_count) {
598
599 // esp_now_send failed to queue - callback will NOT be called
600 // Give back the semaphore we took earlier
601 if (cfg.use_send_ack) {
602 xSemaphoreGive(xSemaphore);
603 }
604
605 retry_count++;
606 LOGW("esp_now_send failed to queue (rc=%d/0x%04X) - retrying (attempt %d)",
607 rc, rc, retry_count);
608
609 if (cfg.write_retry_count >= 0 && retry_count > cfg.write_retry_count) {
610 LOGE("Send queue error after %d retries", retry_count);
611 return false;
612 }
613
614 delay(cfg.delay_after_failed_write_ms);
615 return true; // Continue retrying
616 }
617
618 bool isEncrypted() {
619 return cfg.primary_master_key != nullptr && cfg.local_master_key != nullptr;
620 }
621
622 wifi_interface_t getInterface() {
623 // define wifi_interface_t
624 wifi_interface_t result;
625 switch (cfg.wifi_mode) {
626 case WIFI_STA:
627 result = (wifi_interface_t)ESP_IF_WIFI_STA;
628 break;
629 case WIFI_AP:
630 result = (wifi_interface_t)ESP_IF_WIFI_AP;
631 break;
632 default:
633 result = (wifi_interface_t)0;
634 break;
635 }
636 return result;
637 }
638
639 /// Initialization
640 bool setup() {
641 esp_err_t result = esp_now_init();
642 if (result == ESP_OK) {
643 LOGI("esp_now_init: %s", macAddress());
644 } else {
645 LOGE("esp_now_init: %d", result);
646 }
647
648#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
649 if (cfg.oui) {
650 esp_now_set_user_oui((uint8_t*)cfg.oui);
651 }
652#endif
653
654 // encryption is optional
655 if (isEncrypted()) {
656 esp_now_set_pmk((uint8_t*)cfg.primary_master_key);
657 }
658
659 if (cfg.recveive_cb != nullptr) {
660 esp_now_register_recv_cb(cfg.recveive_cb);
661 } else {
662 esp_now_register_recv_cb(receive);
663 }
664 if (cfg.use_send_ack) {
665 esp_now_register_send_cb(send);
666 }
667 available_to_write = cfg.buffer_size;
668 is_init = result == ESP_OK;
669 return is_init;
670 }
671
672 bool str2mac(const char* mac, uint8_t* values) {
673 sscanf(mac, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &values[0], &values[1],
674 &values[2], &values[3], &values[4], &values[5]);
675 return strlen(mac) == 17;
676 }
677
678 const char* mac2str(const uint8_t* array) {
679 static char macStr[18];
680 memset(macStr, 0, 18);
681 snprintf(macStr, 18, "%02x:%02x:%02x:%02x:%02x:%02x", array[0], array[1],
682 array[2], array[3], array[4], array[5]);
683 return (const char*)macStr;
684 }
685
686 static int bufferAvailableForWrite() {
687 return ESPNowStreamSelf->buffer.availableForWrite();
688 }
689
690 virtual void handle_recv_cb(const uint8_t* mac_addr, const uint8_t* data,
691 int data_len, bool broadcast, uint8_t rssi) {
692 LOGD("rec_cb: %d", data_len);
693 // make sure that the receive buffer is available - moved from begin to
694 // make sure that it is only allocated when needed
695 setupReceiveBuffer();
696
697 // update last io time
698 last_io_success_time = millis();
699
700 // blocking write
701 size_t result = buffer.writeArray(data, data_len);
702 if (result != data_len) {
703 LOGE("writeArray %d -> %d", data_len, result);
704 }
705 // manage ready state
706 if (read_ready == false) {
707 if (cfg.start_read_threshold_percent == 0) {
708 read_ready = true;
709 } else {
710 read_ready = getBufferPercent() >= cfg.start_read_threshold_percent;
711 }
712 }
713 }
714
715#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
716 static void default_recv_cb(const esp_now_recv_info* info,
717 const uint8_t* data, int data_len) {
718 const bool broadcast =
721 info->rx_ctrl->rssi);
722 }
723#else
724 static void default_recv_cb(const uint8_t* mac_addr, const uint8_t* data,
725 int data_len) {
726 ESPNowStreamSelf->handle_recv_cb(mac_addr, data, data_len, false, 255);
727 }
728#endif
729
730 virtual void handle_send_cb(const uint8_t* mac_addr,
731 esp_now_send_status_t status) {
732 static uint8_t first_mac[ESP_NOW_KEY_LEN] = {0};
733 // we use the first confirming mac_addr for further confirmations and
734 // ignore others
735 if (first_mac[0] == 0) {
736 strncpy((char*)first_mac, (char*)mac_addr, ESP_NOW_KEY_LEN);
737 }
738 LOGD("default_send_cb - %s -> %s", this->mac2str(mac_addr),
739 status == ESP_NOW_SEND_SUCCESS ? "+" : "-");
740
741 // ignore others
742 if (strncmp((char*)mac_addr, (char*)first_mac, ESP_NOW_KEY_LEN) == 0) {
743 this->available_to_write = this->cfg.buffer_size;
744
745 // Track send success/failure
746 this->last_send_success = (status == ESP_NOW_SEND_SUCCESS);
747
748 if (status == ESP_NOW_SEND_SUCCESS) {
749 last_io_success_time = millis();
750 } else {
751 LOGI(
752 "Send Error to %s! Status: %d (Possible causes: out of range, "
753 "receiver busy/offline, channel mismatch, or buffer full)",
754 this->mac2str(mac_addr), status);
755 }
756
757 // Release semaphore to allow write to check status and retry if needed
758 xSemaphoreGive(this->xSemaphore);
759 }
760 }
761
762#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0)
763 static void default_send_cb(const wifi_tx_info_t* tx_info,
766#else
767 static void default_send_cb(const uint8_t* mac_addr,
768 esp_now_send_status_t status) {
769#endif
770 ESPNowStreamSelf->handle_send_cb(mac_addr, status);
771 }
772};
773
774} // namespace audio_tools
#define LOGI
Definition: ESPNowStream.h:11
#define MY_ESP_NOW_MAX_LEN
Definition: ESPNowStream.h:20
#define LOGD
Definition: ESPNowStream.h:12
#define MY_ESP_NOW_BUFFER_SIZE
Definition: ESPNowStream.h:26
#define MY_ESP_NOW_BUFFER_COUNT
Definition: ESPNowStream.h:27
#define LOGW
Definition: ESPNowStream.h:10
#define LOGE
Definition: ESPNowStream.h:9
Base class for all Streams. It relies on write(const uint8_t *buffer, size_t size) and readBytes(uint...
Definition: BaseStream.h:20
ESPNow as Arduino Stream. When use_send_ack is true we prevent any buffer overflows by blocking write...
Definition: ESPNowStream.h:112
BufferRTOS< uint8_t > & getBuffer()
provides access to the receive buffer
Definition: ESPNowStream.h:393
bool handleTransmissionResult(int &retry_count)
Handles the result of packet transmission (after queuing)
Definition: ESPNowStream.h:553
bool handleQueueError(esp_err_t rc, int &retry_count)
Handles errors when queuing packets.
Definition: ESPNowStream.h:597
bool clearPeers()
Deletes all registered peers.
Definition: ESPNowStream.h:295
void setSendCallback(esp_now_send_cb_t cb)
Defines an alternative send callback.
Definition: ESPNowStream.h:132
bool addPeers(const uint8_t(&array)[N][6])
Adds an array of peers.
Definition: ESPNowStream.h:277
size_t readBytes(uint8_t *data, size_t len) override
Reeds the data from the peers.
Definition: ESPNowStream.h:366
bool begin()
Initialization of ESPNow.
Definition: ESPNowStream.h:139
uint32_t getLastIoSuccessTime() const
time when we were able to send or receive the last packet successfully
Definition: ESPNowStream.h:396
bool addPeer(const char *address)
Adds a peer to which we can send info or from which we can receive info.
Definition: ESPNowStream.h:246
void setChannel(uint8_t ch)
Defines the WiFi Channel.
Definition: ESPNowStream.h:187
size_t write(const uint8_t *data, size_t len) override
Writes the data - sends it to all registered peers.
Definition: ESPNowStream.h:312
bool addPeers(const char *(&array)[size])
Adds an array of peers.
Definition: ESPNowStream.h:262
virtual bool begin(ESPNowStreamConfig cfg)
Initialization of ESPNow incl WIFI.
Definition: ESPNowStream.h:142
void setReceiveCallback(esp_now_recv_cb_t cb)
Definition: ESPNowStream.h:136
const char * macAddress()
Returns the mac address of the current ESP32.
Definition: ESPNowStream.h:126
bool addPeer(const uint8_t *address)
Adds a peer to which we can send info or from which we can receive info.
Definition: ESPNowStream.h:230
bool addPeer(esp_now_peer_info_t &peer)
Adds a peer to which we can send info or from which we can receive info.
Definition: ESPNowStream.h:199
uint8_t getChannel()
Provies the WiFi Channel.
Definition: ESPNowStream.h:193
virtual bool sendPacket(const uint8_t *data, size_t len, int &retry_count, const uint8_t *destination=nullptr)
Sends a single packet with retry logic.
Definition: ESPNowStream.h:482
bool addBroadcastPeer()
Definition: ESPNowStream.h:292
size_t write(const char *peer, const uint8_t *data, size_t len)
Writes the data - sends it to all the indicated peer mac address string.
Definition: ESPNowStream.h:318
float getBufferPercent()
provides how much the receive buffer is filled (in percent)
Definition: ESPNowStream.h:384
size_t write(const uint8_t *peer, const uint8_t *data, size_t len)
Writes the data - sends it to all the peers.
Definition: ESPNowStream.h:328
virtual void end()
DeInitialization.
Definition: ESPNowStream.h:173
bool setup()
Initialization.
Definition: ESPNowStream.h:640
Configuration for ESP-NOW protocolö.W.
Definition: ESPNowStream.h:42
const char * mac_address
Definition: ESPNowStream.h:47
uint16_t delay_after_failed_write_ms
Delay after failed write (ms). Default: 2000.
Definition: ESPNowStream.h:68
wifi_mode_t wifi_mode
WiFi mode (station or access point). Default: WIFI_STA.
Definition: ESPNowStream.h:44
const char * local_master_key
Local master key for encryption (16 bytes, optional). Default: nullptr.
Definition: ESPNowStream.h:86
const char * primary_master_key
Primary master key for encryption (16 bytes, optional). Default: nullptr.
Definition: ESPNowStream.h:84
uint32_t ack_semaphore_timeout_ms
Timeout for ACK semaphore (ms). Default: portMAX_DELAY.
Definition: ESPNowStream.h:94
uint8_t start_read_threshold_percent
Buffer fill threshold (percent) to start reading. Default: 0.
Definition: ESPNowStream.h:92
const char * ssid
WiFi SSID for connection (optional). Default: nullptr.
Definition: ESPNowStream.h:57
uint16_t delay_after_updating_mac_ms
Delay after updating mac.
Definition: ESPNowStream.h:96
const char * password
WiFi password for connection (optional). Default: nullptr.
Definition: ESPNowStream.h:59
uint16_t buffer_count
Definition: ESPNowStream.h:53
int write_retry_count
Number of write retries (-1 for endless). Default: 1.
Definition: ESPNowStream.h:73
uint16_t buffer_size
Definition: ESPNowStream.h:50
bool use_send_ack
Use send acknowledgments to prevent buffer overflow. Default: true.
Definition: ESPNowStream.h:66
wifi_phy_rate_t rate
ESP-NOW bit rate. Default: WIFI_PHY_RATE_6M.
Definition: ESPNowStream.h:90
int channel
WiFi channel to use (0 for auto). Default: 0.
Definition: ESPNowStream.h:55
wifi_phy_mode_t phymode
ESP-NOW PHY mode. Default: WIFI_PHY_MODE_11G.
Definition: ESPNowStream.h:88