ESP_Now doesn't send to second address

I am using ESP_NOW to send speed and direction commands to a radio controlled tugboat and a radio controlled car. If I send the commands individually, just one mac address, it works fine.

I'm now trying to send the commands to both at the same time, one sending ESP32 to two receive ESP32. It will send the commands to the first address but not the second. In my code I have the addresses as an array, uint8_t broadcastAddress1[], and uint8_t broadcastAddress2[] , if I just swap the 1 and 2 in those the the receiver that wasn't working is now but the other one is not, again sends to the first address but not the second. I get the following messages on the serial monitor.

Packet from: FC:E8:C0:7C:A2:40 send status: Delivery Success
Error sending data

All 3 ESP32's are ESP32 Dev Module

My code is below, and thanks for any help
John

[code]
/* Started with sipPuffToyCar_Mod example
    added NRF24L01 communications
    with corrections from forum
    Rev 3 added steering state to receiver
    Rev 4 Added sip/puff delay on steering
    Rev 5 Changed to ESP_NOW
    Rev 6 Added crawler controlS
          Can switch, by commenting out, tug and crawler
*/

// Include Libraries
#include <esp_now.h>
#include <WiFi.h>
#include <SafeString.h>

// MAC Address of responder - edit as required
uint8_t broadcastAddress1[] = {0xFC, 0xE8, 0xC0, 0x7C, 0xA2, 0x40};  // Tug MAC address
uint8_t broadcastAddress2[] = {0xFC, 0xE8, 0xC0, 0x7B, 0x09, 0x88};  // Crawler MAC address

cSF(drvText_SS, 24);
cSF(strText_SS, 24);

#define SENSOR_PIN 34

#define redLED 22
#define grnLED 23

#define SIP_THRESHOLD 2000 //analog val, 450
#define PUFF_THRESHOLD 3000 //analog val

#define POLL_INTERVAL 10 //ms

#define SHORT_ACTION_MIN 100 //ms
#define SHORT_ACTION_MAX 750 //ms
#define ACTION_SPACE_MIN 50 //ms
#define ACTION_SPACE_MAX 750 //ms

uint32_t sipStartTime;
uint8_t sipStarted = 0;
uint32_t lastSipTime;
uint32_t lastSipDuration;

uint32_t puffStartTime;
uint8_t puffStarted = 0;
uint32_t lastPuffTime;
uint32_t lastPuffDuration;

uint8_t ThrottleOutValue = 0;
uint8_t ThrottleTrimValue = 0;
uint8_t SteeringOutValue = 135;
uint8_t SteeringTrimValue = 0;

uint32_t SteeringDelay = 0;
uint32_t previousMillis = 0;
uint32_t StartSteeringDelay = 50;
uint8_t SteeringPause = 0;
uint32_t SteeringPauseDelay = 500;
uint32_t SteeringPauseStart = 0;

enum DRIVE_STATE {FORWARD, REVERSE, IDLE}; // Enumeration, FORWARD - 1, REVERSE = 2, IDLE = 3
char *DRIVE_STATE_STRS[] = {"forward", "reverse", "idle"}; // ? Defines words so that when Drive state = 1 Serial monitor will display Forward, etc
uint8_t driveState = IDLE;

// Define a data structure
typedef struct struct_message {
  int steering;
  int drive;
} struct_message;
 
// Create a structured object
struct_message myData;

// Peer info
esp_now_peer_info_t peerInfo;
 
// Callback function called when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  char macStr[18];
  Serial.print("Packet from: ");
  // Copies the sender mac address to a string
  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]);
  Serial.print(macStr);
  Serial.print(" send status:\t");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");}

//
void setDrive(uint8_t state) {
 // pinMode(FORWARD_PIN, OUTPUT);
 // pinMode(REVERSE_PIN, OUTPUT);

  if (driveState == state)
    return;

  if (state == FORWARD) {
    myData.drive = 1;
    digitalWrite(grnLED, HIGH);
    digitalWrite(redLED, LOW);
    
   // digitalWrite(FORWARD_PIN, HIGH);

    //Serial.print (" Forward Pause = ");
    //Serial.print (SteeringPause);
    //Serial.print (" Time =  ");
    //Serial.println (SteeringPauseStart);
  }
  else if (state == REVERSE) {
    myData.drive = 2;
    digitalWrite(grnLED, LOW);
    digitalWrite(redLED, HIGH);
    
    //digitalWrite (REVERSE_PIN, HIGH);

    //Serial.print (" Reverse Pause = ");
    //Serial.print (SteeringPause);
    //Serial.print (" Time =  ");
    //Serial.println (SteeringPauseStart);
  }
  else {
    myData.drive = 0;
    digitalWrite(grnLED, LOW);
    digitalWrite(redLED, LOW);
   // digitalWrite (FORWARD_PIN, LOW);
   // digitalWrite (REVERSE_PIN, LOW);
  }
  driveState = state;
}

//
void toggleReverse() {
  if (driveState == REVERSE)
    return;

  else if (driveState == IDLE)
    setDrive(REVERSE);
  else
    setDrive(IDLE);
}


void toggleForward() {
  if (driveState == FORWARD)
    return;

  else if (driveState == IDLE)
    setDrive(FORWARD);
  else
    setDrive(IDLE);
}

enum STEERING_STATE {LEFT, RIGHT, CENTER};
char *STEERING_STATE_STRS[] = {"left", "right", "center"};
uint8_t steeringState = CENTER;

void setSteering(uint8_t state) {

  //pinMode(LEFT_PIN, OUTPUT);
  //pinMode(RIGHT_PIN, OUTPUT);

  if (steeringState == state)
    return;

  if (state == LEFT) {
    myData.steering = 1;
    //  && (millis() - SteeringPauseStart) > SteeringPauseDelay
    // This line was added to the If condition statement above
  //  Serial.print (" Left Pause = ");
  //  Serial.print (SteeringPause);
  //  Serial.print ("Pause Time =  ");
  //  Serial.println (millis() - SteeringPauseStart);

   // digitalWrite(LEFT_PIN, HIGH);
  }
  else if (state == RIGHT) {
    myData.steering = 2;
    //  && (millis() - SteeringPauseStart) > SteeringPauseDelay
    // This line was added to the If condition statement above
  //  Serial.print (" Right Pause = ");
  //  Serial.print (SteeringPause);
  //  Serial.print (" Pause Time =  ");
  //  Serial.println (millis() - SteeringPauseStart);

   // digitalWrite(RIGHT_PIN, HIGH);
  }
  else {
    myData.steering = 0;
  //  digitalWrite(LEFT_PIN, LOW);
  //  digitalWrite(RIGHT_PIN, LOW);
    SteeringPauseStart = millis();
    SteeringPause = 0;

  //  Serial.print (" Idle Pause = ");
  //  Serial.print (SteeringPause);
  //  Serial.print (" Pause Time =  ");
  //  Serial.println (millis() - SteeringPauseStart);
  }
  steeringState = state;
} // end void setSteering(uint8_t state)


void setup() {
  Serial.begin(115200);

  pinMode(redLED, OUTPUT);
  pinMode(grnLED, OUTPUT);
  
   // Set ESP32 as a Wi-Fi Station
  WiFi.mode(WIFI_STA);
 
  // Initilize ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }
  
   // Register the send callback
  esp_now_register_send_cb(OnDataSent);
  
 // register peer
  peerInfo.channel = 0;
  peerInfo.encrypt = false;
  // register first peer
  memcpy(peerInfo.peer_addr, broadcastAddress1, 6);
  if (esp_now_add_peer(&peerInfo) != ESP_OK){
    Serial.println("Failed to add peer");
    return;
  }
  // register second peer
  memcpy(peerInfo.peer_addr, broadcastAddress2, 6);
  if (esp_now_add_peer(&peerInfo) != ESP_OK){
    Serial.println("Failed to add peer");
    return;
  }

} // End ot Setup Loop


void loop() {
  // loop is doing this Constantly repeating
  // while (1) { // Constantly run While Loop

  int16_t val = analogRead(SENSOR_PIN); // Read Pressure Switch < 450 = SIP, > 550 = Puff
  // Serial.println(val);
  
  if (!sipStarted && val < SIP_THRESHOLD) {
    SteeringPause = 1;
    sipStarted = 1;
    sipStartTime = millis(); //Store Sip Start Time
  }
  else if (sipStarted && val > SIP_THRESHOLD) {// SIP has Stopped
    sipStarted = 0;
    uint32_t duration = millis() - sipStartTime;

    if (duration > SHORT_ACTION_MIN) {
      uint32_t prevLastSipTime = lastSipTime;
      uint32_t prevLastSipDuration = lastSipDuration;

      lastSipTime = millis();
      lastSipDuration = duration;

      uint32_t space = sipStartTime - prevLastSipTime;

      //two shorts in a row
      if (prevLastSipDuration < SHORT_ACTION_MAX &&
          lastSipDuration < SHORT_ACTION_MAX &&
          space > ACTION_SPACE_MIN && space < ACTION_SPACE_MAX) {
        toggleReverse();
      }
    }
  }

  if (!puffStarted && val > PUFF_THRESHOLD) {
    SteeringPause = 1;
    puffStarted = 1;
    puffStartTime = millis();
  }
  else if (puffStarted && val < PUFF_THRESHOLD) {
    puffStarted = 0;
    uint32_t duration = millis() - puffStartTime;
    if (duration > SHORT_ACTION_MIN) {
      uint32_t prevLastPuffTime = lastPuffTime;
      uint32_t prevLastPuffDuration = lastPuffDuration;

      lastPuffTime = millis();
      lastPuffDuration = duration;

      uint32_t space = puffStartTime - prevLastPuffTime;

      //two shorts in a row
      if (prevLastPuffDuration < SHORT_ACTION_MAX &&
          lastPuffDuration < SHORT_ACTION_MAX &&
          space > ACTION_SPACE_MIN && space < ACTION_SPACE_MAX) {
        toggleForward();
      }
    }
  }

  //update steering
  if (sipStarted && (millis() > (sipStartTime + SteeringPauseDelay)))
    setSteering(LEFT);
  else if (puffStarted && (millis() > (puffStartTime + SteeringPauseDelay)))
    setSteering(RIGHT);
  else
    setSteering(CENTER);

  // Set Throttle Output with Trim

 // ThrottleTrimValue = map(analogRead(ThrottleTrimInput), 0, 1024, 85, 135) - 110;

  if (DRIVE_STATE_STRS[driveState] == "forward") {
   // ThrottleOutValue = (240 - ThrottleTrimValue) ;
    //analogWrite(ThrottleOutput, ThrottleOutValue);
    //  Serial.print("  Throttle Out = ");
    //   Serial.print(ThrottleOutValue);
  }
  else if (DRIVE_STATE_STRS[driveState] == "reverse") {
   // ThrottleOutValue = (30  - ThrottleTrimValue);
   // analogWrite(ThrottleOutput, ThrottleOutValue);
    //   Serial.print("  Throttle Out = ");
    //   Serial.print(ThrottleOutValue);
  }
  else {
   // ThrottleOutValue = (135  - ThrottleTrimValue);
    //analogWrite(ThrottleOutput, ThrottleOutValue);
    //    Serial.print("  Throttle Out = ");
    //     Serial.print(ThrottleOutValue);
  }
  // Set Steeering Output with Trim

 // SteeringTrimValue = map(analogRead(SteeringTrimInput), 0, 1024, 85, 135) - 110;

  // Delay for Steering change

  unsigned long currentmillis = millis();
  int CenterStop ;

  if (currentmillis - previousMillis > StartSteeringDelay) {
    previousMillis = currentmillis;
    CenterStop = (135 + SteeringTrimValue);

    if ((STEERING_STATE_STRS[steeringState] == "right") && (SteeringOutValue < 230)) {

    //  SteeringOutValue = ((SteeringOutValue + 10) );
     // analogWrite(SteeringOutput, SteeringOutValue);
    }

    if ((STEERING_STATE_STRS[steeringState] == "left") && (SteeringOutValue > 40)) {
     // SteeringOutValue = ((SteeringOutValue - 10)  );
      //analogWrite(SteeringOutput, SteeringOutValue);
    }

    if (STEERING_STATE_STRS[steeringState] == "center") {
      if (SteeringOutValue - CenterStop > 2) {
       // SteeringOutValue = ((SteeringOutValue) - 5);
      //  analogWrite(SteeringOutput, SteeringOutValue);
      }

      if (SteeringOutValue - CenterStop < -2) {
       // SteeringOutValue = ((SteeringOutValue) + 5);
       // analogWrite(SteeringOutput, SteeringOutValue);
      }
    } // End of CENTER if
  } // End of Main Steering If loop
/*
       Serial.print("  Steering State =  ");
       Serial.print(myData.steering);
       Serial.print("\t");
       Serial.print("  Drive State =  ");
       Serial.println(myData.drive);
*/
  strText_SS = STEERING_STATE_STRS[steeringState];
 
// Send message via ESP-NOW
  esp_err_t result = esp_now_send(0, (uint8_t *) &myData, 
  sizeof(struct_message));
  
  if (result == ESP_OK) {
    Serial.println("Sent with success");
  }
  else {
    Serial.println("Error sending data");
  }
  
    delay(POLL_INTERVAL);

  // if (sipStarted == 1){
  //    Serial.println ("Sip Started");
  //   StartSteeringDelay = 0;
  // }
  //    else if (puffStarted ==1){
  //    Serial.println ("Puff Started");
  //    }
  //   else {}
  //     }
}
[/code]

ESP-NOW allows broadcasting to all peers (one message — everyone gets it), which can simplify sending messages to multiple devices. Have you looked into this?

I have looked into that but have not tried it, I will. I thought the way I am doing it was similar, instead of just broadcasting, this way sends the same message to specific devices.

Broadcasting may be a better way to do it but shouldn't this way work as well?

Thanks for the suggestion.

Yes, using NULL as first parameter should send the data to all of the peers that are added to the peer list

have you tried a simpler code to validate that all works well? something like (typed here, untested)

#include <esp_now.h>
#include <WiFi.h>

typedef struct __attribute__((packed, aligned(1))) struct_message {
    int32_t steering; // Steering value
    int32_t drive;    // Drive value
} struct_message;

struct_message myData;

// MAC Addresses of the peers
uint8_t broadcastAddress1[] = {0xFC, 0xE8, 0xC0, 0x7C, 0xA2, 0x40};  // Tug MAC address
uint8_t broadcastAddress2[] = {0xFC, 0xE8, 0xC0, 0x7B, 0x09, 0x88};  // Crawler MAC address

void setup() {
    Serial.begin(115200);
    WiFi.mode(WIFI_STA);
    WiFi.disconnect();

    if (esp_now_init() != ESP_OK) {
        Serial.println("Error initializing ESP-NOW"); Serial.flush();
        while (true) yield(); // die here
    }

    esp_now_peer_info_t peer1;
    memcpy(peer1.peer_addr, broadcastAddress1, 6);
    peer1.channel = 0; 
    peer1.encrypt = false;
    if (esp_now_add_peer(&peer1) != ESP_OK) {
        Serial.println("Failed to add Tug peer"); Serial.flush();
        while (true) yield(); // die here
    }

    // Add second peer (Crawler)
    esp_now_peer_info_t peer2;
    memcpy(peer2.peer_addr, broadcastAddress2, 6);
    peer2.channel = 0; // Use the current channel
    peer2.encrypt = false; // No encryption
    if (esp_now_add_peer(&peer2) != ESP_OK) {
        Serial.println("Failed to add Crawler peer"); Serial.flush();
        while (true) yield(); // die here
    }
}

void loop() {
    // Prepare data to send with random values
    myData.steering = random(-180, 180); 
    myData.drive = random(0, 5); 

    esp_err_t result = esp_now_send(NULL, (uint8_t *) &myData, sizeof(struct_message));
    if (result == ESP_OK) {
        Serial.print("Sent data to all peers (broadcast): Steering = ");
        Serial.print(myData.steering);
        Serial.print(", Drive = ");
        Serial.println(myData.drive);
    } else {
        Serial.println("Error sending data to peers");
    }

    delay(5000);
}

and write something simple on the peers to print the message

I downloaded your example, and the RandomNerd tutorial one I've been using. Your send and his are very similar, both give me the same error, they will not send to the second peer. I get the message "Peer interface invalid". I swapped mac addresses and now the one that wasn't initiated is and the one that was isn't.

As you mentioned earlier for my application using the broadcast mode would be more appropriate so I'll start looking into that. I will also keep trying to figure this mode out as it could be useful for future project, plus it frustrates me, and slightly irritates me, that I can't get something that should work to work.

I do greatly appreciate your help and any other thoughts or suggestions you may have.

Thanks
John

Seems there are three ways to send

  1. unicast to a specific peer, which has to be registered
  2. multicast to every registered peer, by specifying nullptr as the target peer
  3. broadcast to any ESP-Now-enabled device, which requires registering the all-0xFF MAC as a peer, and sending to that

I've never tried the second one. Also note that your devices may receive broadcasts not actually intended for you, so they need to handle them gracefully (verify the contents, don't blindly act upon the data, etc)

@kenb4 thanks for the comment. I have #1, unicast to a specified peer working to both targets.

For the multicast, in this statement;

"esp_err_t result = esp_now_send(nullptr , (uint8_t *) &myData,
sizeof(struct_message));"

I have tried "0" as in the original tutorial, "NULL" as @J-M-L suggested, and now "nullptr" all of them give me the same result, it doesn't send data to the second peer. I have swapped the MAC address and the same result.

It must be something outside of that statement, is it in how each peer is registered, as shown below?

// register second peer
  memcpy(peerInfo.peer_addr, broadcastAddress2, 6);
  if (esp_now_add_peer(&peerInfo) != ESP_OK){
    Serial.println("Failed to add peer");
    return;
  }

No surprise, since they should all compile to the same thing. I would argue that in C++, nullptr is most expressive, and best for various technical reasons (the keyword does not exist in C).

I setup a multicast to two devices, and it worked fine for me. I used the same registration code as you. Regarding the output

The order should be the result from esp_now_send first, then two separate callback messages. But maybe that's the order if the second one is failing?

What happens if you turn off one or both of the targets? I got the expected message order, but with the callback for each saying the send failed, as expected.

it is but can be tricky depending on how the function was implemented as nullptr is formally typed as a pointer and thus will match a pointer signature.

for example:

void f1(const char * ptr) {Serial.println("calling f1 with a pointer");}
void f1(const int value) {Serial.println("calling f1 with a number");}

void setup() {
  Serial.begin(115200);
  f1(0);
  f1(NULL);
  f1(nullptr);
}

void loop() {}

you'll see the first two printing "calling f1 with a number" whilst the latter will be "calling f1 with a pointer" and interestingly you should get a warning

warning: passing NULL to non-pointer argument 1 of 'void f1(int)' [-Wconversion-null]
   f1(NULL);
          ^

My recommendation would be to stick with what the documentation stated.

Disconnecting the first one was a good idea and I notice I have two messages from 2 different sections of the code. These are the same messages I was getting I just looked to see where they where coming from.

I get the same results if I set either ESP32 to be the first one. I get a message for the first one that says, "status = Delivery Success" when unit on, and "status = Delivery fail" when the unit is off. That message is in the "OndataSent" callback function.

For the second unit I get the message "Error sending data" that message is near the bottom of the sketch below the "Send message via ESP_NOW" comment.

Where as the message is being sent to a defined MAC Address I wouldn't think anything in the receiver sketch would need to be modified, would it?

Thanks so much for your help.

ESP32 is dual-core; usually core 1 is the "app" core, where setup and loop run; and core 0 is the "protocol" core, which runs the WiFi stack. esp_now_send and its return value and subsequent messages would be on core 1, and the callbacks run on core 0. They run independently.

So what should happen is:

  • "Sent with success" after firing off to the entire list of peers
  • For each target (the order may not be determinate?)
    • "Delivery Success" or
    • "Delivery Fail" if the device is powered off for example

Instead you're getting just

  • "Delivery Success" for the first
  • "Error sending data" for the esp_now_send because it couldn't initiate the send to the second?

What is the actual esp_err_t result for the failed esp_now_send? Supposedly the choices are

- ESP_OK : succeed
- ESP_ERR_ESPNOW_NOT_INIT : ESPNOW is not initialized
- ESP_ERR_ESPNOW_ARG : invalid argument
- ESP_ERR_ESPNOW_INTERNAL : internal error
- ESP_ERR_ESPNOW_NO_MEM : out of memory, when this happens, you can delay a while before sending the next data
- ESP_ERR_ESPNOW_NOT_FOUND : peer is not found
- ESP_ERR_ESPNOW_IF : current WiFi interface doesn't match that of peer

What happens if both the targets are powered off?

I've made some progress, doing some more research I found that there is a broadcast example in the Arduino IDE, if you go to, File/Examples/ESP32/ESPNow/Multi-Slave there are two sketches, Master and Slave. I downloaded the Master into one of my ESP32s and the Slave into the other two.

I did have to make one change to the Master, although the "CHANNEL" defaults to 1 they do define it in these sketches, but they had CHANNEL 3 in the Master and CHANNEL 1 in the Slave, I changed the Master to CHANNEL 1 and it broadcasts a incrementing number to both of the slave ESPs.

I have to do some more homework to understand the differences but the two that jump out at me are they use SSID to search the network the ESPs are on and they set the WiFi.Mode to WIFI_AP instead of WIFI_STA.

I'll now incorporate these changes into my sketches.

Once again I want to thank you for your help and comments.
John

@J-M-L and @kenb4 just a quick follow up. For my project I needed a smaller controller, so I swapped the ESP32 Dev Module for a Adafruit Feather S2. Just as a test, I didn't think it would make a difference I loaded the sketch to send data to two ESP32's into the Feather, and it works. I have no problem. I went back to the Dev Mod as the sender to both two Dev Mod and a Dev Mod with the Feather and I get the error that It doesn't send the data to the second unit.

You probably know more than I about the inner workings of the Dev Mod and the Feather. Would it be a different chip, memory allocation or something of that kind?

Thanks
John

The Feather S2 uses an ESP32-S2 chip (hence the name), which has a different architecture, memory allocation, and peripheral setup compared to the ESP32 used in the Dev Module. Not sure about the details...