RadioHead
packingdata
Passing Sensor Data Between RadioHead nodes

People often ask about how to send data (such as numbers, sensor readings etc) from one RadioHead node to another. Although this issue is not specific to RadioHead, and more properly lies in the area of programming for networks, we will try to give some guidance here.

One reason for the uncertainty and confusion in this area, especially amongst beginners, is that there is no best way to do it. The best solution for your project may depend on the range of processors and data that you have to deal with. Also, it gets more difficult if you need to send several numbers in one packet, and/or deal with floating point numbers and/or different types of processors.

The principal cause of difficulty is that different microprocessors of the kind that run RadioHead may have different ways of representing binary data such as integers. Some processors are little-endian and some are big-endian in the way they represent multi-byte integers (https://en.wikipedia.org/wiki/Endianness). And different processors and maths libraries may represent floating point numbers in radically different ways: (https://en.wikipedia.org/wiki/Floating-point_arithmetic)

All the RadioHead examples show how to send and receive simple NUL terminated ASCII strings, and if thats all you want, refer to the examples folder in your RadioHead distribution. But your needs may be more complicated than that.

The essence of all engineering is compromise so it will be up to you to decide whats best for your particular needs. The main choices are:

  • Raw Binary
  • Network Order Binary
  • ASCII
Raw Binary

With this technique you just pack the raw binary numbers into the packet:

// Sending a single 16 bit unsigned integer
// in the transmitter:
...
uint16_t data = getsomevalue();
if (!driver.send((uint8_t*)&data, sizeof(data)))
{
...
// and in the receiver:
...
uint16_t data;
uint8_t datalen = sizeof(data);
if ( driver.recv((uint8_t*)&data, &datalen)
&& datalen == sizeof(data))
{
// Have the data, so do something with it
uint16_t xyz = data;
...

If you need to send more than one number at a time, its best to pack them into a structure

// Sending several 16 bit unsigned integers in a structure
// in a common header for your project:
typedef struct
{
uint16_t dataitem1;
uint16_t dataitem2;
} MyDataStruct;
...
// In the transmitter
...
MyDataStruct data;
data.dataitem1 = getsomevalue();
data.dataitem2 = getsomeothervalue();
if (!driver.send((uint8_t*)&data, sizeof(data)))
{
...
// in the receiver
MyDataStruct data;
uint8_t datalen = sizeof(data);
if ( driver.recv((uint8_t*)&data, &datalen)
&& datalen == sizeof(data))
{
// Have the data, so do something with it
uint16_t pqr = data.dataitem1;
uint16_t xyz = data.dataitem2;
....

The disadvantage with this simple technique becomes apparent if your transmitter and receiver have different endianness: the integers you receive will not be the same as the ones you sent (actually they are, but with the internal bytes swapped around, so they probably wont make sense to you). Endianness is not a problem if every data item you send is a just single byte (uint8_t or int8_t or char), or if the transmitter and receiver have the same endianness.

So you should only adopt this technique if:

  • You only send data items of a single byte each, or
  • You are absolutely sure (now and forever into the future) that you will only ever use the same processor endianness in the transmitter and receiver.
Network Order Binary

One solution to the issue of endianness in your processors is to always convert your data from the processor's native byte order to 'network byte order' before transmission and then convert it back to the receiver's native byte order on reception. You do this with the htons (host to network short) macro and friends. These functions may be a no-op on big-endian processors.

With this technique you convert every multi-byte number to and from network byte order (note that in most Arduino processors an integer is in fact a short, and is the same as int16_t. We prefer to use types that explicitly specify their size so we can be sure of applying the right conversions):

// Sending a single 16 bit unsigned integer
// in the transmitter:
...
uint16_t data = htons(getsomevalue());
if (!driver.send((uint8_t*)&data, sizeof(data)))
{
...
// and in the receiver:
...
uint16_t data;
uint8_t datalen = sizeof(data);
if ( driver.recv((uint8_t*)&data, &datalen)
&& datalen == sizeof(data))
{
// Have the data, so do something with it
uint16_t xyz = ntohs(data);
...

If you need to send more than one number at a time, its best to pack them into a structure

// Sending several 16 bit unsigned integers in a structure
// in a common header for your project:
typedef struct
{
uint16_t dataitem1;
uint16_t dataitem2;
} MyDataStruct;
...
// In the transmitter
...
MyDataStruct data;
data.dataitem1 = htons(getsomevalue());
data.dataitem2 = htons(getsomeothervalue());
if (!driver.send((uint8_t*)&data, sizeof(data)))
{
...
// in the receiver
MyDataStruct data;
uint8_t datalen = sizeof(data);
if ( driver.recv((uint8_t*)&data, &datalen)
&& datalen == sizeof(data))
{
// Have the data, so do something with it
uint16_t pqr = ntohs(data.dataitem1);
uint16_t xyz = ntohs(data.dataitem2);
....

This technique is quite general for integers but may not work if you want to send floating point number between transmitters and receivers that have different floating point number representations.

ASCII

In this technique, you transmit the printable ASCII equivalent of each floating point and then convert it back to a float in the receiver:

// In the transmitter
...
float data = getsomevalue();
uint8_t buf[15]; // Bigger than the biggest possible ASCII
snprintf(buf, sizeof(buf), "%f", data);
if (!driver.send(buf, strlen(buf) + 1)) // Include the trailing NUL
{
...
// In the receiver
...
float data;
uint8_t buf[15]; // Bigger than the biggest possible ASCII
uint8_t buflen = sizeof(buf);
if (driver.recv(buf, &buflen))
{
// Have the data, so do something with it
float data = atof(buf); // String to float
...
Conclusion:
  • This is just a basic introduction to the issues. You may need to extend your study into related C/C++ programming techniques.
  • You can extend these ideas to signed 16 bit (int16_t) and 32 bit (uint32_t, int32_t) numbers.
  • Things can be simple or complicated depending on the needs of your project.
  • We are not going to write your code for you: its up to you to take these examples and explanations and extend them to suit your needs.