Sending Bytes

Generally, the technology allows you to send 51 bytes. If you lower your spreading factor (that is, increase the data rate), you can occasionally send even more data. However, the more bytes you send, the more airtime it takes to transmit the data packet, and the sooner you'll hit your maximum allotted time. So, don’t ask yourself how many bytes you can possibly send. Rather, ask how few could do the job.

Sending Big Numbers

Want to know how to send ranges larger than 255?

Index

If the possible values you need to support don’t start at zero and you know the minimum value, start by indexing on that number.

For example, imagine we’d expect values between 3400 and 3600.

On the device we’d encode this as:

int myVal = 3450;
int myBase = 3400;
byte payload[] = { myVal - myBase };

And in the application payload functions use this:

var myBase = 3400;
decoded.myVal = bytes[0] + myBase;

The other way around, in the application encoder payload function we would have:

var myVal = 3450;
var myBase = 3400;
var bytes = [myVal - myBase];

And on the device, decode this with:

int myBase = 3400;
int myVal = payload[0] + myBase;

As you can see, as long as the minimum value is known and the range of our value is 256 or less, we can still use a single byte without breaking a sweat.

Round

Now, what if the range is larger than 256? The next question is: do you need to know the exact value? If your sensor has a range of 400 and an error margin of 2, you wouldn’t lose any meaning by rounding the value. Both 299 and 300 would round to the same value if it was divided by the error margin of 2 so this would be 150, which is fine as it is below 255.. On the device we’d encode this as:

int myVal = 300;
int errorMargin = 2
byte payload[] = { round(myVal / errorMargin) };

And in the application payload functions do the reverse :

var errorMargin = 2;
decoded.myVal = bytes[0] * errorMargin;
Use Words

A word is generally two bytes. By using words, you get a huge range of values: up to 65536 (2562). An example of a word is found in the int data type. Also, Arduino comes with highByte() and lowByte() commands you can use to extract the left and right byte from a word. This makes it really easy to encode and decode.

For example, to encode a data packet for Arduino, you might use this:

int myVal = 20000;
byte payload[2];
payload[0] = highByte(myVal);
payload[1] = lowByte(myVal);

To decode that same payload, you’d use this:

decoded.myVal = (bytes[0] << 8) + bytes[1];

In the first line of code here, you see (bytes[0] << 8). The << command indicates that the designated number of bytes should be shifted to the left. In this instance, the 8 bits of the first byte are shifted eight  positions to the left. We’ll talk more about bit shifting next.

Encode (payload functions):  

var myVal = 20000;
var bytes = [];
bytes[0] = (myVal & 0xFF00) >> 8;
bytes[1] = (myVal & 0x00FF);

Never seen '&' used this way before? This is a Bitwise AND. Used this way, the right side of the expression will act as a mask to zero-out one byte so we can work with only the other.

Decode (Arduino):

int myVal = (payload[0] << 8) + payload[1];

Shifting Bits

If the range of expected values is larger than 65536, we can use the same trick. The only difference is that we have to manually shift bits when we encode on Arduino, for example, just like we did in the payload function.

Let’s say we need to encode a long value, which uses 4 bytes, for a range of up to 4294967296 values.

Encode (Arduino):

long lng = 200000L;
byte payload[4];
payload[0] = (int) ((lng & 0xFF000000) >> 24 );
payload[1] = (int) ((lng & 0x00FF0000) >> 16 );
payload[2] = (int) ((lng & 0x0000FF00) >> 8  );
payload[3] = (int) ((lng & 0X000000FF)       );

Decode (payload functions):

decoded.myVal = (bytes[0] << 24)
             + (bytes[1] << 16)
             + (bytes[2] << 8)
              + (bytes[3]);
Sending Negative Numbers

To tell the difference between -100 and 100 you'll need a signed data type. These set the highest (left-most) bit to 1 to indicate it’s a negative number. This means, however, that, in a word, for example, only 15 of the 16 bits are available for the actual number, limiting the range from 65536 to 32768.

  • Index, Round and Shift: The data types we have used so far are all signed, which means all of the tricks work just as well for negative values. Just be aware of the maximum value.
  • Unsigned Data Types:  If you don’t expect negative numbers and need a larger range, explicitly use unsigned int or unsigned long data types.

Sending Decimals

So far, we have only dealt with round numbers. What if you need more precision? The answer is very similar to how we indexed or rounded large numbers. Simply multiply and divide the value as you encode and decode it. For example,

Encode (Arduino):

float myVal = 1.22;
byte payload[1];
payload[0] = round(myVal * 100);

Decode (payload functions):

decoded.myVal = bytes[0] / 100;

Encode (payload functions):

bytes[0] = Math.round(1.22 * 100);

Decode (Arduino):

float myVal = payload[0] / 100.00;

Note

The example above uses “100.00”, not simply “100”. If both are integers, Arduino/C/C++ will do the math using integers as well, resulting in 1 instead of 1.22.



Sending Multiple Numbers

Often, you’ll want to send multiple values in a single message. Start by encoding each individual number to a buffer of bytes, and then combine them into a single buffer.

Encode (Arduino):

byte payloadA[] = { 0xF0 };
byte payloadB[] = { 0xF0, 0x0F };
byte payloadC[] = { 0xF0, 0x0F, 0xFF };
int sizeofPayloadA = sizeof(payloadA);
int sizeofPayloadB = sizeof(payloadB);
int sizeofPayloadC = sizeof(payloadC);  
byte payload[sizeofPayloadA + sizeofPayloadB + sizeofPayloadC];  
memcpy(payload, payloadA, sizeofPayloadA);
memcpy(payload + sizeofPayloadA, payloadB, sizeofPayloadB);
memcpy(payload + sizeofPayloadA + sizeofPayloadB, payloadC, sizeofPayloadC);

You might wonder why memcpy() accepts payload + sizeOfPayloadA,since they seem to be categorically different. To understand why this works, think of it as an instruction to copy to the payload buffer, but only after moving the point it will copy to, with the length of the payloads we added so far.

decoded.myValA = bytes.slice(0, 2);
decoded.myValB = bytes.slice(2, 5);
// Decode both byte arrays as we did before

Encode (payload function)

var bytes = bytesA.concat(bytesB);

Decode (Arduino):

var payloadA[2];
var payloadB[3];
memcpy(payloadA)

Sending Text

The approach to sending text is simple: don’t. Text uses a lot of bytes. Unicode defines more than 128,000 characters, which would require three bytes per character to encode! There are rarely good reasons to use text instead of numbers, apart from transmitting text input submitted by users.

However, if there is no getting around it, here is how to encode a string:

var myVal =
"Hello";
var l = myVal.length();
byte payload[l + 1];
myVal.getBytes(payload, l + 1);

And here is how to decode a string:

decoded.myVal =
String.fromCharCode.apply(null, bytes);

Last modified: Tuesday, October 11, 2022, 3:58 PM