ESP32 + ESP-NOW with auto-pairing (loop through peer list help)

Smarter? LOL.. (doubtful)

I dont have much ESP background/exposure yet.. mostly been old ESP8266 stuff.. with locally hosted site/data/gui stuff..
or some ESP32 MQTT stuff..

ESP-NOW is new me in general..

I read a bunch of tutorials (one on one, one to many, many to one.etc).. and most of them always 'hard-code' mac addresses (turned me off right away) LOL.

I wanted to learn more. and play around. (for me.. I need a 'goal' though.. otherwise random stuff I read/learn doesnt 'click' until I have a real world solution I know it can apply to)

So I just wanted to play around.. and understand things as I go.

The RNT auto-pairing tutorials was great. Opened up doors. I just needed to try and tweak things in a way "I" thought was easier/better/more productive for an end use project.

Thanks, that's also what I think (have not found the source code behind this function yet).

To call a specific Mac Addr by index the TO could just create a separate array of mac addresses and fill it using the given function.

As ESP NOW seems to limit the peer numbers to 20, it would take about 120 bytes.

@gfvalvo

Ahh.. ok.... I just sending a 'pointer'.. which loads/sets all that 'peers' data as the current 'peer_info'... is this accurate/correct?

Hmmm.. so do you think (would be better than wasting my time here/playing with these built in functions).. to just collect these mac address by myself/manually.. and save them in an array or something? So then I -can- use it the same way I outlined above perhaps?


uint8_t totalPeerList[] = {{0x08, 0xD1, 0xF9, 0xD0, 0x5D, 0x94},{0x08, 0xD1, 0xF9, 0xD0, 0x5D, 0x93},{0x08, 0xD1, 0xF9, 0xD0, 0x5D, 0x92}};

Something like this?

then perhaps used more like originally posted?

getPeerAddress(totalPeerList[1]);

So it can ultimately be used like:

Specific target/device: (mac address)
esp_err_t result = esp_now_send(getPeerAddress(totalPeerList[1]), (uint8_t *) &myData, sizeof(myData));

or

Broadcast:

uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF 0xFF, 0xFF};  -which will be a hard-coded/standalone address/variable
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));

@ec2021

HA!.. I think you might be right..(I think?) just collect the mac addresses myself (manually).. and use my own array? (right?)

I am OK with the device limit being 20.. (so no issues there) :slight_smile:

I guess now (new goal).. figure out to correct store/save this mac address (data) to an array

Do I need to (forward) think about any specific formatting? or var type stuff? (I come from a 'spoiled' web developer background.. where 'we' are spoiled so much we rarely have to worry about type/format stuff) :slight_smile:

Just create (char?) array.. with length/size or 120?.. and then save things however they come from the (native) appPeer() function?

thanks!

Why bother? What's wrong with using the provided function to iterate through the list? How often do expect to need to access the peer information?

EDIT:
Never mind. I see you need to use the MAC address in every call to esp_now_send().

1 Like

It will be (sorta) the main purpose.

(dumb) summary:

  • main/sender board (has bunch of buttons on it)
  • each button has a different 'purpose'..
  • when button clicked.. it will (randomly) select one of the mac address in the list.. and send a message to it.

So each time the button is clicked.. it grabs a mac address and does (whatever the purpose of the button is).. send that message to the 'receiver' board.

One of the buttons is for a 'broadcast' message as well (so that mac address will be hard-coded to ff, ff, ff, ff...etc)

Do you think the 'manual' approach is... dumb? (why? as in just silly 'overkill'?) I started down -this- path originally... but then was like 'DOH.. dont be stupid.. use the assets already available in the sketch'.. (which started me down this path/post of HOW to correctly get the full mac addresses from the peer/slave list)..

But my (incorrect) thinking of it as an 'array' was a waste of time. Not a fan of the always starts from top of list requirement still :frowning:


Oh.. just saw your last minute update:

Yes, you are correct you need the mac address to use the esp_now_send() command.

Do I need to pre-think of any special ways this needs to be done? (formatting? pre/post?) specific var types? Or just make a huge char array?

Having it stored in the array with the correct formatting:
{0x08, 0xD1, 0xF9, 0xD0, 0x5D, 0x94}
would help when being used in the end/function param.

Appreciate the replies!

Given that, it's probably worth having your own copy of the peer information that can be accessed randomly. I might create a std::vector of esp_now_peer_info_t objects and populate it as peers are added. A vector can be accessed by index and also allows element to be easily removed if a peer is deleted.

1 Like

The array would be something like this:

 uint8_t maclist[20][6];

It is a 2D array of bytes:

see https://www.w3schools.com/c/c_arrays_multi.php

maclist[7][0] would be the first number of the mac address with index 7.

index goes from 0 for the first to 19 for the last entry.

@gfvalvo: std::vector is a good solution but might require additional information for the TO.

1 Like

OH? I dont believe I haven even really edited these? These are all come from other (worked/validated?) tutorials.

And in my other tests.... using it as such.. did work/send messages..etc (parsed and turned on leds in the 'receiver' devices..etc)

I guess I'll have to look into that more then? (confused as to why it has been working as expected so far.. both broadcast and direct (hard-coded) mac addresses?

Thanks!


update:

OK.. looks like this part of the comment....was removed? (so it ok/legit then? ......correct?)

Yea, it's OK. I misread it.

1 Like

@ec2021

thank you for the example. (I wasnt even thinking about the original [hard-coded] address being arrays... I was just thinking each 'entry' into the array would be a mac address 'string') (eye roll)

So I'd still need to set the correct 'index'.. and then loop through to grab all values, and pass on to the esp_now_send()..

example:

esp_err_t result = esp_now_send(targetMacAddress, (uint8_t *) &myData, sizeof(myData));

I need to make sure the that 'targetMacAddress' I add as a param -is- an array.

am I mis-understanding anything (so far/again?) :slight_smile:

thanks

It's 11pm now ... so I'm just answering from my smartphone... which makes real assistance complicated.

The Terminator would say "I'll be back..."

Maybe another member (in a more appropriate time zone) will take over?

See you tomorrow...

1 Like

Not late here... (but I do need to take a break. This project, plus some MQTT issue trying to work out, and -real work- is all starting to combine into a..................headache!) LOL

Either way.. appreciate all the time/effort everyone has done.. not only for answers, but education/enlightenment.

Thanks!

1 Like

made one (little) step (before bed...lol)

added some arrays, and a loop right before the

esp_now_fetch_peer()
//static/hard-coded broadcast mac address
uint8_t broadcastMacAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

//placeholder array for incoming/auto-paired mac addresses
uint8_t totalPeerList[20][6];

then right above the fetch call,,.. I added a quick loop to populate the array:

//output peer list
      Serial.println("Current Peer List: ");
      //Serial.println("");

      if((pn.total_num) > 0){  
        int i = 0;
        int j = 0;
        
        //collect default mac address data
        Serial.print("MAC ADDRESS (DEFAULT) ");
        Serial.print(i+1);
        Serial.print(": ");
        
        for(j=0; j < 6; j++){
          //Serial.println("");        
          //Serial.print("MAC ADDRESS ");
          //Serial.print(j);
          //Serial.print(": ");
          Serial.print(slave.peer_addr[j], HEX);
          Serial.print(":");       

          //add to array
          totalPeerList[i][j] = 
        }     
         
        Serial.println();
        
        //original process below      
        boolean from_head = true;
        
        while (esp_now_fetch_peer(from_head, &slave) == ESP_OK){
          from_head = false;
     
          Serial.print("MAC ADDRESS (PARSED) ");
          Serial.print(i+1);
          Serial.print(": ");
          printMAC(slave.peer_addr);
          
          Serial.println("");
          i++;
        }      
      }
      return true;

which I am getting this output:

Current Peer List: 
MAC ADDRESS (DEFAULT) 0: 8:D1:F9:D0:5D:94:
MAC ADDRESS (DEFAULT) 1: 8:D1:F9:D0:CB:6C:
MAC ADDRESS (PARSED) 0: 08:d1:f9:d0:5d:94
MAC ADDRESS (PARSED) 1: 08:d1:f9:d0:cb:6c
  • I am not sure if these need to be (somehow) converted to HEX before populating the array? (or does it not matter?)
    ** I do see the trailing ':'..but that is just part of the output/display (debug) check.

I am saving 'default' values to the array but using:

Serial.print(slave.peer_addr[j], HEX);

for output to display it as HEX visually in serial monitor.

(not sure if that matters? or or how to populate the array as 0xXX, 0xXX, 0xXX...etc)

No, HEX is just a different representation of the binary content . The mac data are handed over to the different functions just as an array of unsigned byte (uint8_t).

You only get HEX to make it easier for humans to read and compare.

I'm not sure where and how you got the data printed in post #34, but the function you posted should throw an error when you try to compile it:

       //add to array
          totalPeerList[i][j] = 
        }     

I have made a simple "emulation" of the esp_now_fetch_peer function so that I can demo how to populate and print the list:

https://wokwi.com/projects/383917653106828289

Sketch:

/*

  Forum: https://forum.arduino.cc/t/esp32-esp-now-with-auto-pairing-loop-through-peer-list-help/1199296
  Wokwi:https://wokwi.com/projects/383917653106828289

  The following declarations and functions handle a mac address list

*/

#include "dummyESPFunctions.h"

constexpr int maxPeers {20};
uint8_t totalPeerList[maxPeers][6];
int noOfPeersInList = 0;
esp_now_peer_info_t slave;

void setup() {
  Serial.begin(115200);
  populateList();
  printList();
}

void loop() {
}

void populateList() {
  boolean from_head = true;
  int count = 0;
  while (esp_now_fetch_peer(from_head, &slave) == ESP_OK) {
    from_head = false;
    memcpy(totalPeerList[count],slave.peer_addr,6);
    count++;
  }
  noOfPeersInList = count;
}

void printList() {
  for (int i = 0; i<noOfPeersInList;i++){
   Serial.printf("MAC ADRESS %2d: ",i+1);    
   printMAC(totalPeerList[i]);
   Serial.println();
  }
}


// ---------------------------- esp_ now -------------------------
void printMAC(const uint8_t * mac_addr){
  char macStr[18];
  snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
           mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
  for (int i=0;i<18;i++){
    macStr[i] = toupper(macStr[i]);
  }
  Serial.print(macStr);
}

This is the simple fake of esp_now_fetch_peer; it is only for this demo purpose so no use in your applications ...

Fake esp_now_fetch_peer function
#pragma once

#define ESP_NOW_ETH_ALEN 20
#define ESP_NOW_KEY_LEN   8
#define wifi_interface_t  byte
#define ESP_ERR_ESPNOW_NOT_FOUND -1

typedef struct esp_now_peer_info {
    uint8_t peer_addr[ESP_NOW_ETH_ALEN];     
    uint8_t lmk[ESP_NOW_KEY_LEN];            
    uint8_t channel;                         
    wifi_interface_t ifidx;  
    bool encrypt;
    void *priv; 
} esp_now_peer_info_t;

constexpr int maxMacs = 8;
uint8_t macs[maxMacs][6] {
   {0x08,0xD1,0xF9,0xD0,0xCB,0x1C},
   {0x08,0xD1,0xF9,0xD0,0xCB,0x2C},
   {0x08,0xD1,0xF9,0xD0,0xCB,0x3C},
   {0x08,0xD1,0xF9,0xD0,0xCB,0x4C},
   {0x08,0xD1,0xF9,0xD0,0xDB,0x5C},
   {0x08,0xD1,0xF9,0xD0,0xDB,0x6C},
   {0x08,0xD1,0xF9,0xD0,0xDB,0x7C},
   {0x08,0xD1,0xF9,0xD0,0xDB,0x8C}
};


int macIndex = 3;

esp_err_t esp_now_fetch_peer(bool from_head, esp_now_peer_info_t *peer) {
   if (from_head) {
    macIndex = 0;
   }
   if (macIndex >= maxMacs) {
    return ESP_ERR_ESPNOW_NOT_FOUND;
   }
   memcpy(peer->peer_addr,macs[macIndex],6);
   macIndex++;
   return ESP_OK;
}

/*

  * @return
  *          - ESP_OK : succeed
  *          - ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized
  *          - ESP_ERR_ESPNOW_ARG : invalid argument
  *          - ESP_ERR_ESPNOW_NOT_FOUND : peer is not found

*/

IAt every place where you usually have to use a reference to e.g. slave.peer_addr you can insert totalPeerList[1] or whatever index applies. Be aware that the second index is not used!

In the sketch above I have also added a for-loop in printMAC() to get uppercase letters in the HEX data. Just looks nicer ...

BTW: You should definitely get yourself a C++ book ... it is really worth it ... :wink:

1 Like

Sorry.. before I read rest of your post.. I wanted to clairfy..

the output of the above sketch was BEFORE adding this:

 //add to array
totalPeerList[i][j] = 

just wanted to show the next step/route.. but wasnt clear if any sort of conversion was needed (or didnt matter.. just for us/visually)

I'll have to come back and re-read your post again. (so it sinks in) :slight_smile:
(different names being used makes me need to start over again to follow things)

thanks!

Here is a fully commented version of the demo sketch (not the fake esp_now_fetch_peer() :wink: )

/*

  Forum: https://forum.arduino.cc/t/esp32-esp-now-with-auto-pairing-loop-through-peer-list-help/1199296
  Wokwi: https://wokwi.com/projects/383925663410460673

  The following declarations and functions handle a mac address list

*/

#include "dummyESPFunctions.h"


// maxPeers is defined as a constant and holds the content of 20
// It will be used to define the number of peer MAC addresses that
// can be stored in the totalPeerList[]
constexpr int maxPeers {20};

// totalPeerList is a two-dimensional array with maxPeers rows and 6 columns
// for the MAC addresses that will be stored in this array
// MAC addresses consist of 6 bytes like e.g. {8,250,30,24,25,1}
// For easier reception by users they are usually written as HEX data
// but when used by ESPNOW it's just six adjacent memory "cells" 
//
// To understand the two-D array just think of it as a table (e.g. in a Calc Sheet):
// * The first index addresses the row (in C/C++ index has a range from 0..(n-1) if you have n rows)
// * The second index addresses the column (in C/C++ index has a range from 0..(m-1) if you have m columns)
//
// The short form "totalPeerList[5]" points to the starting "cell" of the sixth row (counted from zero!)
// You may write "totalPeerList[5][3]" if you want to address the forth element in the sixth row
// 
// Always remind yourself that C/C++ counts from zero in arrays!  
//
uint8_t totalPeerList[maxPeers][6];

// noOfPeersInList is a variable that shall hold the actual number of peers which
// were retrieved via esp_now_fetch_peer()
// It is set to zero so that - if we call printList() - it will not print invalid data
int noOfPeersInList = 0;

// We need a variable of type esp_now_peer_info_t to communicate with the ESPNOW library.
// The content is defined in
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_now.html
//
// In addition with further information this variable might be filled by ESPNOW functions 
// with the MAC address of paired peers. 
// 
// esp_now_peer_info_t declares a structure, that is a combination of data that is bundled 
// under one name. To access one of those data you use the name of the structure variable
// (here: slave) and add the name of the internal variable (e.g. peer_addr) with a "." in 
// between: slave.peer_addr is a valid name as well as slave.channel.
//
esp_now_peer_info_t slave;


// In setup() we call two functions that are defined after loop()
void setup() {
  Serial.begin(115200);
  populateList();
  printList();
}

// Nothing to be done here for demo purposes
void loop() {
}

// The function populateList() does not need any parameters
// It collects (if available) the MAC addresses of all paired 
// peers and copies them one by one in the totalPeerList[]
void populateList() {
  // from_head is set to true, so that in the very first call
  // of esp_now_fetch_peer() we get the very first entry from the
  // ESPNOW's internal list
  boolean from_head = true;
  // We set the variable count to zero. It is used 
  // a) as the index where the next entry shall be written in totalPeerList[]
  // b) as a counter for the number of peers we get from the function
  int count = 0;
  // We use the return value of the ESPNOW function to detect whether a 
  // call was succesful or not
  // If it was successful "slave" will hold a valid MAC address
  // and the sketch enters the while-loop
  //
  // otherwise it will skip the while-loop and commence with the 
  // statement after the closing curly bracket
  // So if we don't get a ESP_OK on the first call
  // noOfPeersInList will be set to zero (as count is still zero)
  // a call of printlist() will not show anything.
  //
  // If the while-loop is entered ...
  while (esp_now_fetch_peer(from_head, &slave) == ESP_OK) {
    // from_head is set to false in the first entry here
    // This way only the first call starts at the Top of the ESPNOW internal list
    // Every following call in while(esp_...) will search for the next entry in
    // that internal list
    from_head = false;
    // The function call in while() has filled the memory where the variable "slave"
    // is stored in memory with the content from the ESPNOW internal list.
    // We are only interested in the peer_addr part and therefore
    // use the standard function memcpy()
    // See https://en.cppreference.com/w/cpp/string/byte/memcpy
    // to copy 6 bytes from source (slave.peer_addr) to destination totalPeerList[count]
    //
    // On the first entry count is zero, so we fill totalPeerList[0
    //
    memcpy(totalPeerList[count],slave.peer_addr,6);
    // Now we increase count by one; after the first entry it becomes 1 now
    // That's nice: 
    // a) That is the number of peer MAC addresses we have stored now and
    // b) It points to the next row in totalPeerList[] where a further address may 
    //    be stored - if available -
    count++;
  }
  // After all is done count does its last duty: It hands over the number of 
  // stored MAC addresses to noOfPeersInList and ... passes away as typical for local variables ... ;-)
  // while noOfPeersInList - being a global variable - survives and saves the heritage of "count"
  // for further functions
  noOfPeersInList = count;
}

//
// printList() is just trying to print
// from 0 to noOfPeersInList-1
// it calls the separate function printMAC() handing over the address where the first cell out of
// six is that (hopefully) holds a valid MAC address
// Serial.printf() is a function available to ESP controllers that eases mixed printing of
// text and numbers
//
// It uses a format string (here "MAC ADRESS %2d: ") that includes possible text and placeholders
// starting with a "%" and formatting commands for decimal, floating point numbers etc. 
//
// The variable(s) which are associated with each placeholder are listed after the format
// string in the same order as their placeholders and separated by a comma
//
// see https://en.cppreference.com/w/cpp/io/c/fprintf
//
void printList() {
  for (int i = 0; i<noOfPeersInList;i++){
   Serial.printf("MAC ADRESS %2d: ",i+1);    
   printMAC(totalPeerList[i]);
   Serial.println();
  }
}

// printMAC() gets the pointer to the begin of a six byte array holding a MAC address
// It uses the standard function snprintf() to fill the char array macStr[18] with 
// the hexadecimal printed values of each single array cell.
// The additional for-loop goes through the macStr array character by character and
// converts the characters - if they are letters - to upper case

// ---------------------------- esp_ now -------------------------
void printMAC(const uint8_t * mac_addr){
  char macStr[18];
  snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
           mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
  for (int i=0;i<18;i++){
    macStr[i] = toupper(macStr[i]);
  }
  Serial.print(macStr);
}

Good luck!

While commenting I came across the issue that ESPNOW (or somebody else) might sometime in future increase the number of peers in the internal list. We have to avoid writing after the end of our array totalPeerList[]. That;s integrated in the following snippet (and I will add it on Wokwi in the sketch mentioned above):

// The function populateList() does not need any parameters
// It collects (if available) the MAC addresses of all paired 
// peers and copies them one by one in the totalPeerList[]
void populateList() {
  // from_head is set to true, so that in the very first call
  // of esp_now_fetch_peer() we get the very first entry from the
  // ESPNOW's internal list
  boolean from_head = true;
  // We set the variable count to zero. It is used 
  // a) as the index where the next entry shall be written in totalPeerList[]
  // b) as a counter for the number of peers we get from the function
  int count = 0;
  // We use the return value of the ESPNOW function to detect whether a 
  // call was succesful or not
  // If it was successful "slave" will hold a valid MAC address
  // and the sketch enters the while-loop
  //
  // otherwise it will skip the while-loop and commence with the 
  // statement after the closing curly bracket
  // So if we don't get a ESP_OK on the first call
  // noOfPeersInList will be set to zero (as count is still zero)
  // a call of printlist() will not show anything.
  //
  // If the while-loop is entered ...
  while (esp_now_fetch_peer(from_head, &slave) == ESP_OK) {
    // from_head is set to false in the first entry here
    // This way only the first call starts at the Top of the ESPNOW internal list
    // Every following call in while(esp_...) will search for the next entry in
    // that internal list
    from_head = false;
    // The function call in while() has filled the memory where the variable "slave"
    // is stored in memory with the content from the ESPNOW internal list.
    // We are only interested in the peer_addr part and therefore
    // use the standard function memcpy()
    // See https://en.cppreference.com/w/cpp/string/byte/memcpy
    // to copy 6 bytes from source (slave.peer_addr) to destination totalPeerList[count]
    //
    // On the first entry count is zero, so we fill totalPeerList[0
    //
    memcpy(totalPeerList[count],slave.peer_addr,6);
    // Now we increase count by one; after the first entry it becomes 1 now
    // That's nice: 
    // a) That is the number of peer MAC addresses we have stored now and
    // b) It points to the next row in totalPeerList[] where a further address may 
    //    be stored - if available -
    count++;

   // To be on the safe side this function should be integrated also:
  //  If count reaches the maximum number of peers we jump out of the while loop
  // to avoid writing data after the end of the array.
  // It's not necessary now but might become relevant e.g. if ESPNOW allows more than twenty
  // peers to pair
   if (count >= maxPeers) {
      break;
   }
  }
  // After all is done count does its last duty: It hands over the number of 
  // stored MAC addresses to noOfPeersInList and ... passes away as typical for local variables ... ;-)
  // while noOfPeersInList - being a global variable - survives and saves the heritage of "count"
  // for further functions
  noOfPeersInList = count;
}

1 Like

HA!.. man you are awesome!

I havent even had time (work break yet) to go through the initial post from today! :slight_smile:

I will def let you know once I get a chance to play with it if I have any questions.. :slight_smile:

I'm sure (either way) I'll have some questions on my old approach vs your new one (just for educational purpose...so I can learn/put things into perspective/compare..etc)

thanks!

+1

An alternate approach given that ESP32 supports the STL:

#include <esp_now.h>
#include <vector>

std::vector<esp_now_peer_info_t> peerList;
size_t peerCount;

void populateList() {
  bool fromHead = true;
  esp_now_peer_info_t peer;

  while (esp_now_fetch_peer(fromHead, &peer) == ESP_OK) {
    fromHead = false;
    peerList.push_back(peer);
  }
  peerCount = peerList.size();
}

Thanks, I know but thought the use of std::vector is harder to explain than a 2D array ...

std::vector is available for ESP32, I used it (and also std::list) to store the data of WiFi clients so that a sketch using AsyncTCP could send data to different clients and was able to reply to each separate client ...

vector and list require a deeper insight into C++ (e.g. the use of iterators).

But I agree, once you know how to use (and your controller supports) them it makes handling lists much easier ...

:slight_smile: