Struct to string?

I’m sending values from a RFM69 sender to a receiver.
Ad the moment it is done with “struct”:

typedef struct __attribute__((__packed__)) {
  int16_t nodeId;       // Store this nodeId
  float temp;             // Temperature reading
  int16_t vcc;           // Battery voltage
  byte tx;                 // Transmit level
  float lx;                  // Light level
  int16_t values[POINTS];
} Payload;
Payload theData;

The problem here is the struct needs to look exactly the same on the other side.
Thats not practical because every sender has other sensors/data.
I’m looking for a way to send that struct as a string:
val1,val2,val3,val4…

And on the other side it should not matter how many values.
That means splitting that string up on the other side.
If the first value is always the sender ID and i know in what
order the values are, i can work with them on the receiver.

I also tried it with “dtostrf” and “sprintf” but can’t get it to work.
Sender:

float temp = 5;
char str_temp[6];
char sendBuf[32];
byte sendLen;
void loop() {

  sensors.begin(); // Start Dallas Temperature library
  sensors.requestTemperatures(); // Get the temperature
  theData.temp = (sensors.getTempCByIndex(0)); // Read first sensor

  temp = (sensors.getTempCByIndex(0));

  dtostrf((sensors.getTempCByIndex(0)), 4, 2, str_temp);
  sprintf(sendBuf, "T: %s", str_temp);
  sendLen = strlen(sendBuf);
  radio.sendWithRetry(GATEWAYID, sendBuf, sendLen);

  Sleepy::loseSomeTime(5000);
}

Receiver:

void loop() {
  if (radio.receiveDone())
  {
    for (byte i = 0; i < radio.DATALEN; i++)
    Serial.print((char)radio.DATA[i]);
    }
    Serial.println();
  }
}

But that doesn’t work. I only receive -127.
Maybe because the receiver is 32 bit and i send from a 8 bit Arduino?
I know for int i have to use “int16_t” but float just works in the struct 8<>32bit.

If you need to transfer arbitrary data structures, there are various methods for doing that. ASN/BER is very compact, but JSON is better supported.

In any case, once a C++ program is compiled, all the names of structure elements disappear - they are just compiled in as numerical offsets into the structure. There's no magic way of sending a stucture - any structure - of unknown format over a stream and having the receiver reconstitute it.

Nice to know but doesn’t help me :frowning:

theData.temp = (sensors.getTempCByIndex(0)); // Read first sensor

temp = (sensors.getTempCByIndex(0));

dtostrf((sensors.getTempCByIndex(0)), 4, 2, str_temp);

(Whats) (with) (all) (the) (unnecessary) (parentheses) (?)

Have you printed the number of bytes you are sending? Have you printed the number of bytes you are receiving? Are they the same?

Have you printed the data you are sending? Is it reasonable?

How about defining a structure that contains a union of all the different sender configurations? The first element of the structure would be the Sender ID followed by the union. The structure would always be the same size, determined by the largest element of the union. But, that’s still probably more efficient than sending strings because numerical values would go as binary.

Greg

typedef struct {
  // Sender 1 sensor configuration
} sender1DataStruct_t;

typedef struct {
  // Sender 2 sensor configuration
} sender2DataStruct_t;

typedef struct {
  // Sender 3 sensor configuration
} sender3DataStruct_t;

typedef struct {
  uint8_t senderNumber;
  union {
    sender1DataStruct_t sender1Data;
    sender2DataStruct_t sender2Data;
    sender3DataStruct_t sender3Data;
  } senderUnion;
} genericSenderStruct_t;

I wish i would understand that stuff like others. I’m lost :confused:

As i said it works this way:
Sender:

/**************************************************************
* DATA STRUCTURE TO BE SENDE                                  *
**************************************************************/
typedef struct __attribute__((__packed__)) {
  uint16_t nodeId;          // Store this nodeId
  float temp;              // Temperature reading
  uint16_t vcc;             // Battery voltage
  byte tx;                 // Transmit level
  float lx;                // Light level
  uint16_t values[POINTS];
} Payload;
Payload theData;

/**************************************************************
* SEND PAYLOAD DATA                                           *
**************************************************************/
static void rfwrite() {
  radio.receiveDone();
  if (radio.sendWithRetry(GATEWAYID, (const void*)(&theData), sizeof(theData), RETRY_LIMIT, ACK_TIME)) {
    if (DEBUG) {
      Serial.print("ACK received! ");
      Serial.print("Sending ");
      Serial.print(sizeof(theData));
      Serial.print(" bytes...");
      Serial.print(", TX level: ");
      Serial.print(radio._transmitLevel,DEC); // current transmit level used by this sender
      Serial.print(", RSSI: ");
      Serial.println(radio.getAckRSSI(),DEC); // this sender's RSSI acked back from the receiving node (Gateway)
      Serial.flush();
    }
    radio.sleep(); // Put radio to sleep
    } else {
      if (DEBUG) {
        Serial.println("No ACK response!");
        Serial.flush();
      }
      Sleepy::loseSomeTime(RETRY_PERIOD * 1000); // If no ack received wait and try again
    }
}

void loop() {
  digitalWrite(ONE_WIRE_POWER, HIGH); // Turn DS18B20 on

  delay(5); // Allow 5ms for the sensor to be ready
 
  sensors.begin(); // Start Dallas Temperature library
  sensors.requestTemperatures(); // Get the temperature
  theData.temp = (sensors.getTempCByIndex(0)); // Read first sensor

  digitalWrite(ONE_WIRE_POWER, LOW); // Turn DS18B20 off

  theData.nodeId = NODEID;
  theData.tx = radio._transmitLevel; // current transmit level used by this sender
  theData.vcc = readVcc(); // Get battery voltage

  rfwrite(); // Send data via RF

  Sleepy::loseSomeTime(90000);
}

Receiver:

typedef struct __attribute__((__packed__)) {
  uint16_t nodeId;          // Store this nodeId
  float temp;              // Temperature reading
  uint16_t vcc;             // Battery voltage
  byte tx;                 // Transmit level
  float lx;                // Light level
  uint16_t values[POINTS];
} Payload;
Payload theData;

void loop() {
  if (radio.receiveDone()) {
    if (radio.DATALEN != sizeof(Payload)) {
      Serial.print("Invalid Payload received!");
    } else {
      theData = *(Payload*)radio.DATA; //assume radio.DATA actually contains our struct and not something else
      if (DEBUG) {
        Serial.print("Node:");
        Serial.print(theData.nodeId);
        Serial.print(", Temp:");
        Serial.print(theData.temp);
        Serial.print(", Lux:");
        Serial.print(theData.lx);
        Serial.print(", Vcc:");
        Serial.print(theData.vcc);
        Serial.print(", TX Level:");
        Serial.print(theData.tx);
        Serial.print(", RSSI:");
        Serial.print(radio.RSSI);
      }
    }

    if (radio.ACKRequested()) { // When a node requests an ACK, respond to the ACK
      radio.sendACK();
      Serial.print(" - ACK sent.");
    }
    
    Serial.println();
  }
}

I would also like to know this or would like to be able to send through RFM69 as a string name of the device sending a message.

sprintf() and dtostrf() work exactly as the documentation states, and make it quite easy to format numbers into character arrays (C-strings).

What sort of problems have you encountered with them?

PS: there is really no reason to send a float value from a sensor. Ints have all the range you normally need (4 orders of magnitude). Instead of a float, send an integer that is the float value*100, if you think you need to preserve a couple of decimal places.

I still don’t understand why you insist on sending strings. It’s inefficient. Remember there is a limit on RFM69 packet size. And, you say the technique using structures is working. Why not expand it to meet your needs rather than inventing a new way?

Suggestions:

* If your sender and receive units are different processor types (ESPECIALLY if one is 8-bit and the other is 32-bit), then make each element in the structure a multiple of 4 bytes long. That will assure that the elements are aligned along word boundaries for the 32-bit device. Also, get rid of the “packed” attribute in the structure definition. It may provide unpredictable results when TX and RX units are different processor architectures.

-- So, all integers should be type int32_t or uint32_t.

-- Type float is 4 bytes, so you’re probably OK. But, as has been mentioned already, you’re better off not using floats. For temperature, just send (int32_t) (temp* 10.0 + 0.5). The Dallas temp sensor won’t give you better than 0.1 accuracy / precision anyway.

-- Scale the light level into a uint32_t in a similar manner. In this case I’m assuming light levels are only positive, otherwise use an int32_t.

* If the different sensors provide different amounts/types of data, then use a union of structures inside the main structure as I suggested. Make the senderNumber a uint32_t (rather than a uint8_t as I originally suggested) to maintain word alignment. If you don’t understand structures and unions, buy a book on C programming or find an online tutorial.

* Why are you sending the TX power setting in the data packet? What good is that information to the RX?

* Why are you calling sensors.begin() every time through loop()? Shouldn’t that be done once in setup()?

Mr glasspoole I solved it by sending integers and using a switch statement on the other end to detect which device it is.
Also the receiver can read signal levels so no need to send them.

gfvalvo:
Also, get rid of the “packed” attribute in the structure definition.

I thought "Data structure alignment" is used if you have 8bit, 16bit and 32bit processors mixed?

Is there something wrong using "int32_t" on numbers from 0-255?
I was reading this: Data Types in Arduino
and thought always use the least possible data type.

gfvalvo:
sending strings. It's inefficient

Is that always the case?
I see a lot people using JSON.
Especial if they build a gateway and send the data to a server.

gfvalvo:
For temperature, just send (int32_t) (temp* 10.0 + 0.5).

Is this correct:

int t = (sensors.getTempCByIndex(0)*10);
  if (t >= 0) {
    theData.temp = (t + 0.5);
  } else {
    theData.temp = (t - 0.5);
  }

EDIT
No because 26.38 prints 26.3 instead of 26.4
EDIT

gfvalvo:
Why are you sending the TX power setting in the data packet? What good is that information to the RX?

It is not for the receiver it is information for me.

gfvalvo:
Why are you calling sensors.begin() every time through loop()? Shouldn’t that be done once in setup()?

After sleeping the whole Arduino and powering down the DS18B20 you need to do that.
The loop is running just once before the machine sleeps for 90 seconds.

aot2002:
Also the receiver can read signal levels so no need to send them.

The receiver can read the TX level of the sender?

Even if the other questions are not answered i got another one.

If i set everything in the struct to int32 I’m sending 16 bytes.
But it also works with int16 (sending 8 bytes).

Why does it work with int16? Its not a multiple of 4 bytes?
4 byte = 32 bit < so 16 is just the half

When mixing 8, 16, 32 and so on environments you need to take into account if processors are big endian or little endian. E.g. an int16 with the value 255 can either be stored as 0x00 followed by 0xFF or the other way around.

When sending it to an environment with another endianess, you need to swap the bytes.

You need to rephrase the question in reply #11. Show both versions of the struct as well so we can see what you mean.

Ok, the sender is a Pro Mini (8bit) and the receiver is a Teensy (32bit).

Sure this works:

typedef struct {
  uint32_t nodeId;    // Store this nodeId
  int32_t temp;        // Temperature reading
  uint32_t vcc;        // Battery voltage
  uint32_t tx;          // Transmit level
} Payload;
Payload theData;

But also this:

typedef struct {
  uint16_t nodeId;    // Store this nodeId
  int16_t temp;        // Temperature reading
  uint16_t vcc;        // Battery voltage
  uint16_t tx;          // Transmit level
} Payload;
Payload theData;

The second one is not a multiple of 4 bytes - is it?
4 byte = 32 bit < so 16 is just the half

The second one would be better since the sender is battery powered and
sending 8 bytes vs 16 bytes uses less battery.

It is a multiple, 4 times 2 bytes on an 8 or 16 bit processor.

Simple test is to e.g. use Serial.println(sizeof(Payload));

Yes the size is what i already wrote sending 16 bytes (int32) vs 8 bytes (int16).

I still don't get the math.
If 4 times 2 bytes is a multiple
then
8 times 1 byte is also - isn't it?

gfvalvo wrote:

So, all integers should be type int32_t or uint32_t.

but it also works with in16 and that means sending less...

I did see somebody doing this:

#define POINTS 5 // number of values to send at each transmission

typedef struct __attribute__((__packed__)) {
  long timestamp;
  uint8_t period;
  int16_t values[POINTS];
} Payload;
Payload theData;

It seems like then it also works with int8?
What does the POINTS do and where does the 5 come from?

I suggested using all int32_t to ensure proper alignment for more general struct definitions. I'm guessing the compiler placed your struct containing int16_t members starting on a word boundary in the 32 bit machine. So the alignment worked out. Try this:

typedef struct {
  uint8_t nodeId;    // Store this nodeId
  int32_t temp;        // Temperature reading
  uint16_t vcc;        // Battery voltage
  uint32_t tx;          // Transmit level
} Payload;
Payload theData;

Then do a sizeof() check on both the 8 and 32 bit processors.

MrGlasspoole:
Yes the size is what i already wrote sending 16 bytes (int32) vs 8 bytes (int16).

I still don’t get the math.
If 4 times 2 bytes is a multiple
then
8 times 1 byte is also - isn’t it?

Yep; but I don’t get the point. Or maybe better, I don’t understand your problem?

MrGlasspoole:

#define POINTS 5 // number of values to send at each transmission

What does the POINTS do and where does the 5 come from?

How must we know? Ask the one that wrote the code why it’s 5 :wink:

The moment i put an int8 in there i have "Invalid Payload".
So sender and receiver don't match.

sterretje:
Yep; but I don't get the point. Or maybe better, I don't understand your problem?

The problem is that I don't understand why it needs to be in16 or int32 and can't be int8.
If: 8 times 1 byte is also multiple.

sterretje:
How must we know? Ask the one that wrote the code why it's 5 :wink:

I did and never had an answer what this POINTS is good for.
I thought maybe it has to do with size.

POINTS is the size of the array in the struct. It provides a single place where you can modify it. Suppose you have the following code

int myArray[5];

void setup()
{
  ...
  ...
}

void loop()
{
  for(int cnt=0;cnt<5;cnt++)
  {
    myArray[cnt]=readSensor(cnt);
  }

  for(int cnt=0;cnt<5;cnt++)
  {
    Serial.println(myArray[cnt];
  }
}

Now you want to adjust the size of the array. With the above you have 3 places that you need to modify:
1)
the array declaration
2)
the for loop that reads the sensors
3)
the for loop that prints the values

There is always a risk that you forget one which will result in all kinds of issues.

By defining POINTS, you can use the below code

#define POINTS 5

int myArray[POINTS];

void setup()
{
  ...
  ...
}

void loop()
{
  for(int cnt=0;cnt<POINTS;cnt++)
  {
    myArray[cnt]=readSensor(cnt);
  }

  for(int cnt=0;cnt<POINTS;cnt++)
  {
    Serial.println(myArray[cnt];
  }
}

Now you only have to make one change if you want to change the size of the array and that is the definition of points.

It could have been called anything; maybe it refers to the number of measuring points and hence the author used POINTS.

Note: can’t help you with the radio part (why you get the error message).