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