arduino-audio-tools
All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Modules Pages
ESPNowStream.h
1#pragma once
2#include <WiFi.h>
3#include <esp_now.h>
4#include <esp_wifi.h>
5
6#include "AudioTools/CoreAudio/AudioBasic/StrView.h"
7#include "AudioTools/CoreAudio/BaseStream.h"
8#include "AudioTools/Concurrency/RTOS.h"
9
10namespace audio_tools {
11
12// forward declarations
13class ESPNowStream;
14static ESPNowStream *ESPNowStreamSelf = nullptr;
15
22 wifi_mode_t wifi_mode = WIFI_STA;
23 const char *mac_address = nullptr;
24 int channel = 0;
25 const char *ssid = nullptr;
26 const char *password = nullptr;
27 bool use_send_ack = true; // we wait for
28 uint16_t delay_after_failed_write_ms = 2000;
29 uint16_t buffer_size = ESP_NOW_MAX_DATA_LEN;
30 uint16_t buffer_count = 400;
31 int write_retry_count = -1; // -1 endless
32#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
33 void (*recveive_cb)(const esp_now_recv_info *info, const uint8_t *data,
34 int data_len) = nullptr;
35#else
36 void (*recveive_cb)(const uint8_t *mac_addr, const uint8_t *data,
37 int data_len) = nullptr;
38#endif
40 const char *primary_master_key = nullptr;
41 const char *local_master_key = nullptr;
43 wifi_phy_rate_t rate = WIFI_PHY_RATE_2M_S;
44};
45
52class ESPNowStream : public BaseStream {
53 public:
54 ESPNowStream() { ESPNowStreamSelf = this; };
55
57 if (xSemaphore != nullptr) vSemaphoreDelete(xSemaphore);
58 }
59
60 ESPNowStreamConfig defaultConfig() {
61 ESPNowStreamConfig result;
62 return result;
63 }
64
66 const char *macAddress() {
67 static const char *result = WiFi.macAddress().c_str();
68 return result;
69 }
70
72 void setSendCallback(esp_now_send_cb_t cb) { send = cb; }
73
76 void setReceiveCallback(esp_now_recv_cb_t cb) { receive = cb; }
77
79 bool begin() { return begin(cfg); }
80
83 this->cfg = cfg;
84 WiFi.mode(cfg.wifi_mode);
85 // set mac address
86 if (cfg.mac_address != nullptr) {
87 LOGI("setting mac %s", cfg.mac_address);
88 byte mac[ESP_NOW_KEY_LEN];
89 str2mac(cfg.mac_address, mac);
90 if (esp_wifi_set_mac((wifi_interface_t)getInterface(), mac) != ESP_OK) {
91 LOGE("Could not set mac address");
92 return false;
93 }
94 delay(500); // On some boards calling macAddress to early leads to a race
95 // condition.
96 // checking if address has been updated
97 const char *addr = macAddress();
98 if (strcmp(addr, cfg.mac_address) != 0) {
99 LOGE("Wrong mac address: %s", addr);
100 return false;
101 }
102 }
103
104 if (WiFi.status() != WL_CONNECTED && cfg.ssid != nullptr &&
105 cfg.password != nullptr) {
106 WiFi.begin(cfg.ssid, cfg.password);
107 while (WiFi.status() != WL_CONNECTED) {
108 Serial.print('.');
109 delay(1000);
110 }
111 }
112
113#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
114 LOGI("Setting ESP-NEW rate");
115 if (esp_wifi_config_espnow_rate(getInterface(), cfg.rate) != ESP_OK) {
116 LOGW("Could not set rate");
117 }
118#endif
119
120 Serial.println();
121 Serial.print("mac: ");
122 Serial.println(WiFi.macAddress());
123 return setup();
124 }
125
127 void end() {
128 if (is_init){
129 if (esp_now_deinit() != ESP_OK) {
130 LOGE("esp_now_deinit");
131 }
132 if (buffer.size()>0) buffer.resize(0);
133 is_init = false;
134 }
135 }
136
138 bool addPeer(esp_now_peer_info_t &peer) {
139 if (!is_init) {
140 LOGE("addPeer before begin");
141 return false;
142 }
143 esp_err_t result = esp_now_add_peer(&peer);
144 if (result == ESP_OK) {
145 LOGI("addPeer: %s", mac2str(peer.peer_addr));
146 } else {
147 LOGE("addPeer: %d", result);
148 }
149 return result == ESP_OK;
150 }
151
153 template <size_t size>
154 bool addPeers(const char *(&array)[size]) {
155 bool result = true;
156 for (int j = 0; j < size; j++) {
157 const char *peer = array[j];
158 if (peer != nullptr) {
159 if (!addPeer(peer)) {
160 result = false;
161 }
162 }
163 }
164 return result;
165 }
166
168 bool addPeer(const char *address) {
169 esp_now_peer_info_t peer;
170 peer.channel = cfg.channel;
171 peer.ifidx = getInterface();
172 peer.encrypt = false;
173
174 if (StrView(address).equals(cfg.mac_address)) {
175 LOGW("Did not add own address as peer");
176 return true;
177 }
178
179 if (isEncrypted()) {
180 peer.encrypt = true;
181 strncpy((char *)peer.lmk, cfg.local_master_key, 16);
182 }
183
184 if (!str2mac(address, peer.peer_addr)) {
185 LOGE("addPeer - Invalid address: %s", address);
186 return false;
187 }
188 return addPeer(peer);
189 }
190
192 size_t write(const uint8_t *data, size_t len) override {
193 setupSemaphore();
194 int open = len;
195 size_t result = 0;
196 int retry_count = cfg.write_retry_count;
197 while (open > 0) {
198 resetAvailableToWrite();
199 // wait for confirmation
200 if (cfg.use_send_ack) {
201 xSemaphoreTake(xSemaphore, portMAX_DELAY);
202 }
203 size_t send_len = min(open, ESP_NOW_MAX_DATA_LEN);
204 esp_err_t rc = esp_now_send(nullptr, data + result, send_len);
205 // check status
206 if (rc == ESP_OK) {
207 open -= send_len;
208 result += send_len;
209 retry_count = 0;
210 } else {
211 LOGW("Write failed - retrying again");
212 retry_count++;
213 if (retry_count-- < 0 ) {
214 LOGE("Write error after %d retries", cfg.write_retry_count);
215 // break loop
216 return 0;
217 }
218 delay(cfg.delay_after_failed_write_ms);
219 }
220 }
221 return result;
222 }
223
225 size_t readBytes(uint8_t *data, size_t len) override {
226 if (buffer.size()==0) return 0;
227 return buffer.readArray(data, len);
228 }
229
230 int available() override {
231 if (!buffer) return 0;
232 return buffer.size() == 0? 0 : buffer.available();
233 }
234
235 int availableForWrite() override {
236 if (!buffer) return 0;
237 return cfg.use_send_ack ? available_to_write : cfg.buffer_size;
238 }
239
242 int size = buffer.size();
243 // prevent div by 0
244 if (size==0) return 0.0;
245 // calculate percent
246 return 100.0 * buffer.available() / size;
247 }
248
249 protected:
251 BufferRTOS<uint8_t> buffer{0};
252 esp_now_recv_cb_t receive = default_recv_cb;
253 esp_now_send_cb_t send = default_send_cb;
254 volatile size_t available_to_write = 0;
255 bool is_init = false;
256 SemaphoreHandle_t xSemaphore = nullptr;
257
258 inline void setupSemaphore() {
259 // use semaphore for confirmations
260 if (cfg.use_send_ack && xSemaphore==nullptr) {
261 xSemaphore = xSemaphoreCreateBinary();
262 xSemaphoreGive(xSemaphore);
263 }
264 }
265
266 inline void setupReceiveBuffer() {
267 // setup receive buffer
268 if (!buffer) {
269 LOGI("setupReceiveBuffer: %d", cfg.buffer_size * cfg.buffer_count);
270 buffer.resize(cfg.buffer_size * cfg.buffer_count);
271 }
272 }
273
274 inline void resetAvailableToWrite() {
275 if (cfg.use_send_ack) {
276 available_to_write = 0;
277 }
278 }
279
280 bool isEncrypted() {
281 return cfg.primary_master_key != nullptr && cfg.local_master_key != nullptr;
282 }
283
284 wifi_interface_t getInterface() {
285 // define wifi_interface_t
286 wifi_interface_t result;
287 switch (cfg.wifi_mode) {
288 case WIFI_STA:
289 result = (wifi_interface_t)ESP_IF_WIFI_STA;
290 break;
291 case WIFI_AP:
292 result = (wifi_interface_t)ESP_IF_WIFI_AP;
293 break;
294 default:
295 result = (wifi_interface_t)0;
296 break;
297 }
298 return result;
299 }
300
302 bool setup() {
303 esp_err_t result = esp_now_init();
304 if (result == ESP_OK) {
305 LOGI("esp_now_init: %s", macAddress());
306 } else {
307 LOGE("esp_now_init: %d", result);
308 }
309
310 // encryption is optional
311 if (isEncrypted()) {
312 esp_now_set_pmk((uint8_t *)cfg.primary_master_key);
313 }
314
315 if (cfg.recveive_cb != nullptr) {
316 esp_now_register_recv_cb(cfg.recveive_cb);
317 } else {
318 esp_now_register_recv_cb(receive);
319 }
320 if (cfg.use_send_ack) {
321 esp_now_register_send_cb(send);
322 }
323 available_to_write = cfg.buffer_size;
324 is_init = result == ESP_OK;
325 return is_init;
326 }
327
328 bool str2mac(const char *mac, uint8_t *values) {
329 sscanf(mac, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &values[0], &values[1],
330 &values[2], &values[3], &values[4], &values[5]);
331 return strlen(mac) == 17;
332 }
333
334 const char *mac2str(const uint8_t *array) {
335 static char macStr[18];
336 memset(macStr, 0, 18);
337 snprintf(macStr, 18, "%02x:%02x:%02x:%02x:%02x:%02x", array[0], array[1],
338 array[2], array[3], array[4], array[5]);
339 return (const char *)macStr;
340 }
341
342 static int bufferAvailableForWrite() {
343 return ESPNowStreamSelf->buffer.availableForWrite();
344 }
345
346#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
347 static void default_recv_cb(const esp_now_recv_info *info,
348 const uint8_t *data, int data_len)
349#else
350 static void default_recv_cb(const uint8_t *mac_addr, const uint8_t *data,
351 int data_len)
352#endif
353 {
354 LOGD("rec_cb: %d", data_len);
355 // make sure that the receive buffer is available - moved from begin to make
356 // sure that it is only allocated when needed
357 ESPNowStreamSelf->setupReceiveBuffer();
358 // blocking write
359 size_t result = ESPNowStreamSelf->buffer.writeArray(data, data_len);
360 if (result != data_len) {
361 LOGE("writeArray %d -> %d", data_len, result);
362 }
363 }
364
365 static void default_send_cb(const uint8_t *mac_addr,
366 esp_now_send_status_t status) {
367 static uint8_t first_mac[ESP_NOW_KEY_LEN] = {0};
368 // we use the first confirming mac_addr for further confirmations and ignore
369 // others
370 if (first_mac[0] == 0) {
371 strncpy((char *)first_mac, (char *)mac_addr, ESP_NOW_KEY_LEN);
372 }
373 LOGD("default_send_cb - %s -> %s", ESPNowStreamSelf->mac2str(mac_addr),
374 status == ESP_NOW_SEND_SUCCESS ? "+" : "-");
375
376 // ignore others
377 if (strncmp((char *)mac_addr, (char *)first_mac, ESP_NOW_KEY_LEN) == 0) {
378 ESPNowStreamSelf->available_to_write = ESPNowStreamSelf->cfg.buffer_size;
379 if (status == ESP_NOW_SEND_SUCCESS) {
380 xSemaphoreGive(ESPNowStreamSelf->xSemaphore);
381 } else {
382 LOGE("Send Error!");
383 }
384 }
385 }
386};
387
388} // namespace audio_tools
Base class for all Streams. It relies on write(const uint8_t *buffer, size_t size) and readBytes(uint...
Definition BaseStream.h:36
Buffer implementation which is using a FreeRTOS StreamBuffer. The default allocator uses psram is ava...
Definition BufferRTOS.h:29
int available() override
provides the number of entries that are available to read
Definition BufferRTOS.h:136
int availableForWrite() override
provides the number of entries that are available to write
Definition BufferRTOS.h:141
int writeArray(const T data[], int len)
Fills the buffer data.
Definition BufferRTOS.h:94
bool resize(size_t size)
Re-Allocats the memory and the queue.
Definition BufferRTOS.h:50
int readArray(T data[], int len)
reads multiple values
Definition BufferRTOS.h:76
ESPNow as Arduino Stream.
Definition ESPNowStream.h:52
void setSendCallback(esp_now_send_cb_t cb)
Defines an alternative send callback.
Definition ESPNowStream.h:72
size_t readBytes(uint8_t *data, size_t len) override
Reeds the data from the peers.
Definition ESPNowStream.h:225
bool begin()
Initialization of ESPNow.
Definition ESPNowStream.h:79
bool addPeer(const char *address)
Adds a peer to which we can send info or from which we can receive info.
Definition ESPNowStream.h:168
size_t write(const uint8_t *data, size_t len) override
Writes the data - sends it to all the peers.
Definition ESPNowStream.h:192
bool addPeers(const char *(&array)[size])
Adds an array of.
Definition ESPNowStream.h:154
void setReceiveCallback(esp_now_recv_cb_t cb)
Definition ESPNowStream.h:76
const char * macAddress()
Returns the mac address of the current ESP32.
Definition ESPNowStream.h:66
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:138
void end()
DeInitialization.
Definition ESPNowStream.h:127
float getBufferPercent()
provides how much the receive buffer is filled (in percent)
Definition ESPNowStream.h:241
bool begin(ESPNowStreamConfig cfg)
Initialization of ESPNow incl WIFI.
Definition ESPNowStream.h:82
bool setup()
Initialization.
Definition ESPNowStream.h:302
A simple wrapper to provide string functions on existing allocated char*. If the underlying char* is ...
Definition StrView.h:28
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
Configuration for ESP-NOW protocolö.W.
Definition ESPNowStream.h:21
const char * primary_master_key
to encrypt set primary_master_key and local_master_key to 16 byte strings
Definition ESPNowStream.h:40
wifi_phy_rate_t rate
esp-now bit rate
Definition ESPNowStream.h:43