Sending different arrays from different sender via esp-now. Dynamic arrays

Hello all,
I want to send data from two sender esp32 boards via esp now and it will be received by one receiver board. Both sender send data from two different types of sensors (strain gauges and thermo couple) and the number of sensors varies as as well. For example: Sender 1: 3 strain gauges and 4 thermo couples Sender 2: 2 Strain gauges and 2 thermo couples.

The amount of sensors will vary from task to task. The data from each sensor type is being stored in an array and sent to the receiver. This works well but only if the amount of sensors match for Sender 1 and 2.

My problem is the following. The sender structure has to match the receiver structure.

 #define NUMBER_OF_SENSORSSG 2
  #define NUMBER_OF_SENSORSTC 0
  #define NUMBER_OF_SENSORS 2

// Define data structure
typedef struct struct_message {
  int id = 1; // must be unique for each sender board
int NUMBER_OF_SENSORSSG=2;
  int32_t* strainNow;
  float* ThermoNow;
} struct_message;
// Create structured data object
struct_message myData;

I declare the length of the arrays by defining macros at the beginning.

struct_message myData;

// Create a structure to hold the readings from each board
struct_message board1;
struct_message board2;
struct_message board3;

// Create an array with all the structures
struct_message boardsStruct[3] = {board1, board2, board3};

void loop(){
 myData.strainNow = (int32_t*)malloc(myData.NUMBER_OF_SENSORSSG * sizeof(int32_t));


  for (byte x = 0; x < NUMBER_OF_SENSORSSG; x++) {
    myMux.setPort(x);
    while (!nau[x]->available()) {
      delay(1);
    }
    load[x] = nau[x]->read();
    myData.strainNow[x]=load[x];
   Serial.print("strain value ");  Serial.print(x);Serial.print(": ");Serial.print(load[x]); Serial.print("\t");
  }
Serial.println();
  // Send message via ESP-NOW
  esp_err_t resultTC = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
   
  if (resultTC == ESP_OK) {
    Serial.println("Sent with success");
  }
  else {
    Serial.println("Error sending the data");
  }

I used the callback function OnDataRecv very similar to the tutorial from random nerds tutorial. The receiver structure is obviously the same as the sender.

// Structure example to receive data
// Must match the sender structure
typedef struct struct_message {
  int id;
int NUMBER_OF_SENSORSSG;
 int32_t* strainNow;
  float* ThermoNow;
}struct_message;




// Create a struct_message called myData
struct_message myData;



// Create a structure to hold the readings from each board
struct_message board1;
struct_message board2;
struct_message board3;

// Create an array with all the structures
struct_message boardsStruct[3] = {board1, board2, board3};


// callback function that will be executed when data is received
void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) {
  char macStr[18];
  Serial.print("Packet received from: ");
  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.println(macStr);
 myData.strainNow = (int32_t*)malloc(myData.NUMBER_OF_SENSORSSG * sizeof(int32_t));
 memcpy(myData.strainNow, incomingData + sizeof(myData), myData.NUMBER_OF_SENSORSSG * sizeof(int32_t));// Update the corresponding board's data in boardsStruct
 boardsStruct[myData.id - 1] = myData;

Can someone help me out on how to send the information of the length of each array with the package being sent like with a pointer *strainNow or anything like that.

I tried the command malloc Sender:

typedef struct struct_message {
 int id; // must be unique for each sender board
 int32_t strain;
 int NUMBER_OF_SENSORSS = 2;
 int NUMBER_OF_SENSORST = 0;
 int32_t* strainNow; // Assuming a maximum size for demonstration
 float* ThermoNow; // Adjust size as needed
} struct_message;
 myData.strainNow = (int32_t*)malloc(myData.NUMBER_OF_SENSORSSG * sizeof(int32_t));

typedef struct struct_message {
 int id;
 int32_t strain;
 int NUMBER_OF_SENSORSSG; 
 int NUMBER_OF_SENSORSTC; 
 int32_t* strainNow;
 float* ThermoNow;
} struct_message;
struct_message board1;
struct_message board2;
struct_message board3;

struct_message boardsStruct[3] = {board1, board2, board3};
struct_message myData;

void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) {
 memcpy(&myData, incomingData, sizeof(myData));
 myData.strainNow = (int32_t*)malloc(myData.NUMBER_OF_SENSORSSG * sizeof(int32_t));
 memcpy(myData.strainNow, incomingData + sizeof(myData), myData.NUMBER_OF_SENSORSS G* sizeof(int32_t));


I was able to send it but on the receiving side it got the array size right but not the actual data. Thanks for any suggestions.

Simply use memcpy() to copy the data from the receive buffer into the correct structure.

That will require a flag in the incoming packet, used to identify the correct structure.

Hi @stonehentsch ,

Welcome to the forum..

Them pointers in your structure is going to give you some troubles as you see..

~q

thanks for your input. I am not a programmer so I don't fully understand what you mean by flag.
On the receiving side I memcpy is used in the OnDataRecv function to store the incomingData into the myData which is defined earlier in the receiving code as an object of the structure struct_message, correct ? How would that flag look like. Right now the length of the array strainNow is correct but the data within the array is just wrong.

Thanks. I am sure I missed something. When I name the int NUMBER_OF_SENSORSSG=2; in the structure at the sender side different but let the strainNow array being filled in the void loop with the length defined with the macro #define NUMBER_OF_SENSORS 2 and serial.print the myData.strainNow it contains the correct data and size at the sender side. it also has the right byte size.

Anything you like. It could be the first byte of the message buffer, for example.

Using malloc() is a bad idea on a microprocessor, especially if you are not an experienced programmer. You should declare one structure of the correct size for each of the possibilities.

 myData.strainNow = (int32_t*)malloc(myData.NUMBER_OF_SENSORSSG * sizeof(int32_t));

i useint id=1; in each structure to identify the sender. But wouldnt the structure of the receiving side not have to be matching the sender side? how would the receiver know what structure it receives ? Do I have to write two different OnDataRecv functions as well. Sorry for the noob questions

thanks. I begin to realize that malloc is a dead end. My work around solution right now is that I define the correct array size in the sender side.
Let's say Sender 1: #define NUMBER_OF_SENSORSSG 2 therefore myData.strainNow is an filled with two entries.
Sender 2: #define NUMBER_OF_SENSORSSG 4therefore myData.strainNow is an filled with four entries. on the receiver side I #define NUMBER_OF_SENSORSSG 4` and just discharge the two last entries in myData.strainNow. I am sure this is not the best practise

It would make more sense to use different IDs for each structure.

But wouldnt the structure of the receiving side not have to be matching the sender side?

Of course the TX and RX structures have to match, but the TX/RX buffer is just a linear array of bytes.

Pointers "*" don't contain the value but rather the memory address that the values reside in.
When you send this to the other board it's not a valid memory address, throws you into the twilight zone "undefined behavior" and you don't want to go there..

is there a reason you need the pointers??

or maybe a something like this..

#define MAX_STRAIN 5
#define MAX_THERM 5

typedef struct __attribute__((__packed__))  struct_message {
 int     id; // must be unique for each sender board
 int32_t strain;
 int     NUMBER_OF_SENSORSS = 2;
 int     NUMBER_OF_SENSORST = 0;
 int32_t strainNow[MAX_STRAIN]; 
 float   ThermoNow[MAX_THERM];
};

Should transmit better..

good luck.. ~q

Thanks. Would I have to define MAX_STRAIN and MAX_THERM at the receiver side as well as a macro at the beginning ? that would be a problem since the values vary from sender to sender . or will I use int NUMBER_OF_SENSORSS and populate strainNow with those ? but that would give me an error since the size is not defined in the code.

I do not need pointers at all it was just my attempt to populate the dynamic array strainNow at the receiving end using malloc().

Yes, the structure needs to always be the same size, going to have extra space in it, just ignore extra

kind of thought NUMBER_OF_SENSORS was the actual count of how many from this board.. if not you need to add it to the structure..

show me what you mean..

good, better if you don't go there..
every malloc() must be followed by a free()..

~q

This beastie here has some packets..
Every packet gets a header, sometimes all i send is a header..
Work in progress, something to look at..

~q

I will post the relevant sender code and the way I worked around my problem :

#include <Wire.h>
#include <SparkFun_I2C_Mux_Arduino_Library.h>  //Click here to get the library: http://librarymanager/All#SparkFun_I2C_Mux
#include <Adafruit_NAU7802.h>

#include "Adafruit_MCP9601.h"
#include <WiFi.h>
#include <esp_now.h>
int ledPin = LED_BUILTIN;  //Status LED connected to digital pin 13
#define I2C_ADDRESS (0x67)


//Create instances:
Adafruit_NAU7802 **nau;
Adafruit_MCP9601 **mcp;
QWIICMUX myMux;

  // ---------------------------------------------------------------------------------------------- Number of Sensors -----------------------------------------------------------------------------------------------------



  #define NUMBER_OF_SENSORSSG 2
  #define NUMBER_OF_SENSORSTC 0


// Receiver MAC Address 
uint8_t broadcastAddress[] = {0xFC, 0xB4, 0x67, 0xD0, 0x65, 0xFC};

// Define data structure
typedef struct struct_message {
  int id = 2; // must be unique for each sender board
  int NUMBER_OF_SENSORSSG1=2;
  int32_t strainNow[NUMBER_OF_SENSORSSG];
  float ThermoNow[NUMBER_OF_SENSORSTC];
} struct_message;


// Create structured data object
struct_message myData;

// Create peer interface
esp_now_peer_info_t peerInfo;

// callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status)
{
  Serial.print("Last Packet Send Status: ");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}



uint8_t status;
int32_t load[NUMBER_OF_SENSORSSG];

float Hot_Junction[NUMBER_OF_SENSORSTC];
float Cold_Junction[NUMBER_OF_SENSORSTC];



void setup() {

  Serial.begin(115200);

  Wire.begin();


  if (myMux.begin(0x70) == false) {
    Serial.println("Mux 1 not detected. Freezing...");
    while (1)
      ;
  }
  Serial.println("Mux 1 detected");


      // Take 10 readings to flush out readings
      for (uint8_t i = 0; i < 10; i++) {
        while (!nau[x]->available()) delay(1);
        nau[x]->read();
      }
      while (!nau[x]->calibrate(NAU7802_CALMOD_INTERNAL)) {
        Serial.println("Failed to calibrate internal offset, retrying!");
        delay(1000);
      }
      Serial.println("Calibrated internal offset");

      while (!nau[x]->calibrate(NAU7802_CALMOD_OFFSET)) {
        Serial.println("Failed to calibrate system offset, retrying!");
        delay(1000);
      }
      Serial.println("Caibrlated system offset");
      Serial.println(nau[x]->read());
      Serial.println(F("------------------------------"));
    }
  }

//--------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------


// Set device as a Wi-Fi Station
  WiFi.mode(WIFI_STA);

  // Init ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }


  // Once ESPNow is successfully Init, we will register for Send CB to
  // get the status of Trasnmitted packet
  esp_now_register_send_cb(OnDataSent);

  // Register peer
  memcpy(peerInfo.peer_addr, broadcastAddress, 6);
  peerInfo.channel = 0;  
  peerInfo.encrypt = false;

  if (esp_now_add_peer(&peerInfo) != ESP_OK) {
    Serial.println("Failed to add peer");
    return;
  }



}

void loop() {

  myMux.begin(0x70);

  for (byte x = 0; x < NUMBER_OF_SENSORSTC; x++) {
    myMux.setPort(x);
    Hot_Junction[x] = mcp[x]->readThermocouple();
    Cold_Junction[x] = mcp[x]->readAmbient();
 //   Serial.print(mcp[x]->readThermocouple()); Serial.print("\t");
 //   Serial.print("Cold Junction: ");Serial.print(x+1); Serial.print(": ");

 //   Serial.print(mcp[x]->readAmbient());

    myData.ThermoNow[x]= Hot_Junction[x];
    Serial.print("Hot Junction: "); Serial.print(x+1); Serial.print(": ");Serial.print(myData.ThermoNow[x]); Serial.print("\t");
  }


     // Send message via ESP-NOW
  esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
   
  if (result == ESP_OK) {
    Serial.println("Sent with success");
  }
  else {
    Serial.println("Error sending the data");
  }


Serial.println();


  myMux.begin(0x71);

  for (byte x = 0; x < NUMBER_OF_SENSORSSG; x++) {
    myMux.setPort(x);
    while (!nau[x]->available()) {
      delay(1);
    }
    load[x] = nau[x]->read();
    myData.strainNow[x]=load[x];
   Serial.print("strain value ");  Serial.print(x);Serial.print(": ");Serial.print(load[x]); Serial.print("\t");
  }
Serial.println();
  // Send message via ESP-NOW
  esp_err_t resultTC = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
   
  if (resultTC == ESP_OK) {
    Serial.println("Sent with success");
  }
  else {
    Serial.println("Error sending the data");
  }
 delay(1000);

}

The macro #define NUMBER_OF_SENSORSSG 2 on at the sender side defines the length of the array strainNow. It will be populated with the data from 2 sensors. This is all on the sender side. int NUMBER_OF_SENSORSSG1=2; within the structure gives the information to the receiver the size of the strainNow array.

On the receiver side:
I defined a macro #define NUMBER_OF_SENSORSSG 4 which is defines the maximum size of sensors. That is the case for sender 2 which has 4 sensors.
From sender 1 the receiver therefore receives the two entries of strainNow with the correct values and the two more entries which are not correct since they don't match the number of sensors. I later populate the strainNow array after using memcpy to copy the incoming data to the new structure object myData.

` if (myData.id==1) {
for (byte x = 0; x < myData.NUMBER_OF_SENSORSSG1; x++) {

boardsStruct[myData.id - 1].strainNow[x] = myData.strainNow[x];
`
this gives me an array strainNow which is always size 4 regardless of the sender but is then being filled with the right data and in case of sender 1 the two remaining entries of strainNow is being filled with garbage data. I just discharge it later before being used. This is obviously not best practise.

  #define NUMBER_OF_SENSORSSG 4
  #define NUMBER_OF_SENSORSTC 0




// Structure example to receive data
// Must match the sender structure
typedef struct struct_message {
  int id;
  int NUMBER_OF_SENSORSSG1;
 int32_t strainNow[NUMBER_OF_SENSORSSG];
  float ThermoNow[NUMBER_OF_SENSORSTC];
}struct_message;




// Create a struct_message called myData
struct_message myData;



// Create a structure to hold the readings from each board
struct_message board1;
struct_message board2;
struct_message board3;

// Create an array with all the structures
struct_message boardsStruct[3] = {board1, board2, board3};


// callback function that will be executed when data is received
void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) {
  char macStr[18];
  Serial.print("Packet received from: ");
  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.println(macStr);
  if (myData.id==1) {
  for (byte x = 0; x < myData.NUMBER_OF_SENSORSSG1; x++) {
   
  boardsStruct[myData.id - 1].strainNow[x] = myData.strainNow[x];
  Serial.print("strain value ");  Serial.print(x);Serial.print(": ");Serial.print(boardsStruct[myData.id - 1].strainNow[x]); Serial.print("\t");
 // Serial.print("strain value ");  Serial.print(x);Serial.print(": ");Serial.print(boardsStruct[myData.id - 1].strainNow[x]); Serial.print("\t");

}
  }

  Serial.println(myData.id);
 

   if (myData.id==2) {
  for (byte x = 0; x < myData.NUMBER_OF_SENSORSSG1; x++) {
   
  boardsStruct[myData.id - 1].strainNow[x] = myData.strainNow[x];
  Serial.print("strain value ");  Serial.print(x);Serial.print(": ");Serial.print(boardsStruct[myData.id - 1].strainNow[x]); Serial.print("\t");
  }
   }
   Serial.println();

  memcpy(&myData, incomingData, sizeof(myData));

sorry I should have been more clear on the subject

your structures are not the same size..
they always have to be the max that the board with the most of whatever would need..
and then maybe a few more..
~q

correct. They have the max size of sensors #define NUMBER_OF_SENSORSSG 4 on the receiver side. Therefore the myData.strainNow array on the receiver side will be filled with data from the sensor and in case of sender 1 two more entries with garbage data. which will later in the process be discharged

` if (myData.id==1) {
for (byte x = 0; x < myData.NUMBER_OF_SENSORSSG1; x++) {

boardsStruct[myData.id - 1].strainNow[x] = myData.strainNow[x];

This will populate the array on the receiver side with the right data.

in case of sender two sender two myData.NUMBER_OF_SENSORSSG1; has been defined as 4 in the sender structure

   if (myData.id==2) {
  for (byte x = 0; x < myData.NUMBER_OF_SENSORSSG1; x++) {
   
  boardsStruct[myData.id - 1].strainNow[x] = myData.strainNow[x];
  Serial.print("strain value ");  Serial.print(x);Serial.print(": ");Serial.print(boardsStruct[myData.id - 1].strainNow[x]); Serial.print("\t");
  }

The memcpy is at the end of recv data??
But yeah, if you don't mind the garbage should be ok..

~q

thanks, would you have any suggestions on how I can make the code any better ? Is there a way to write a unique structure for each sender and a way for the receiver to identify the sender and therefore automatically get the correct array size ? would that involve a second callback function ? that is called separately for each sender ?

Yes, as I outlined above. It is a standard approach.