Payload Best Practices
Introduction
Optimizing payload data is critical for your product. Plan in advance which data will be transmitted, its acquisition frequency and desired transmission rate, the binary encoding and encapsulation in the payload. A good design will help you:
- Keep power consumption low.
- Decrease data transmission and get cheaper data plans.
- Optimize spectrum availability for your devices.
Guidelines
- If your device has geolocation capabilities, use the built-in
GEO_W
serial command to encapsulate the geolocation in your payload. It takes 8 bytes, which will be added on top of your message payload. - When a message is enqueued in the payload, the current timestamp is stored and transmitted to the satellite, but it won't count in your payload data. The timestamp at which the payload has been queued will be available later via our portal or the API. In most cases it is not necessary to add a timestamp to your payload data.
- Optimize the number of fragments (and bursts) sent. Read carefully the Low Energy Guidelines (PDF) in Downloads, keep the following table in mind and follow the best practices below:
Generally speaking, it is more efficient to send messages less frequent but with more payload.
Adjust the message to the next smaller fragment number:
- If your message consists of 43 bytes, by optimizing the payload down to 39 bytes you would economize energy by sending 3 fragments instead of 4.
- If your message consists of 43 bytes, sending it would cost you the same amount of energy than sending 57 bytes of payload as in both cases the system will send 4 fragments.
Stay below 94 bytes of payload to avoid a second transmission burst (this will drastically increase your power consumption). Otherwise, use a maximum of the 160 bytes payload available per message to amortize the second transmission burst.
Power consumption profile of an 80 bytes message (a single burst of 6 fragments + ACK)
Power consumption profile of a 160 bytes message (burst of 6 fragments + ACK + burst of 4 fragments + ACK)
Optimize your data flow so you don't fill the Astronode S message queue up.
Encode your data wisely. Do not transmit text, csv or json, as it takes much more space than necessary. Design your message structure in a binary format and reduce the byte usage as much as you can.
Example of data encoding optimization
Imagine you are developing a weather station that will transmit temperature values. Your sensor has the specifications below:
- Temperature range: -40ºC to 85ºC
- Resolution: tenths of a degree (0.1ºC)
You have implemented a get_current_temp()
function that returns the current temperature of the sensor. You use a float
variable (4 bytes in your microcontroller) to store the value:
float temp;
// Current temperature is -12.7
temp = get_current_temp();
printf("%f", temp);
The current temperature is -12.7°C. We won't get into technical details here, but the hexadecimal representation of -12.7 float
value is 0xC14B3333
. You would need 4 bytes of payload to send this value straight away.
First optimization: integer conversion
It's normally good practice to convert decimal numbers to integers. As the resolution is tenths of a degree, we can use a multiplier of 10:
uint8_t payload[4];
float temp;
int32_t itemp;
// Current temperature is -12.7
temp = get_current_temp();
// Converted temperature is -127
itemp = temp * 10;
payload[0] = (itemp & 0xFF000000) >> 24;
payload[1] = (itemp & 0x00FF0000) >> 16;
payload[2] = (itemp & 0x0000FF00) >> 8;
payload[3] = (itemp & 0X000000FF);
printf("0x%02X %02X %02X %02X\n", payload[0], payload[1], payload[2], payload[3]);
You would still need 4 bytes to transmit -127: 0xFFFFFF81
using an int32_t
. As the expected integer data range would be -400 to 850, you could even use an int16_t
, so that would only take 2 bytes.
Second optimization: data normalization
On top of converting the decimal numbers to integers, we can normalize the values starting at 0, so we can use unsigned data types. As the normalized data range will be 0 to 1250 (using an offset of 400), you can use a uint16_t
data type (2 bytes, range 0 to 65535).
uint8_t payload[2];
float temp;
uint16_t itemp;
// Current temperature is -12.7
temp = get_current_temp();
// Converted temperature is 273
itemp = (40 + temp) * 10;
payload[0] = (itemp & 0xFF00) >> 8;
payload[1] = (itemp & 0x00FF);
printf("0x%02X %02X\n", payload[0], payload[1]);
You would only need 2 bytes to transmit 273: 0x0111
Third optimization: data rounding
Depending on the application, perhaps you can also get rid of decimal values. Rounded data will range from 0 to 125, you can use a uint8_t
data type (1 byte, range 0 to 255).
uint8_t payload;
float temp;
uint8_t itemp;
// Current temperature is -12.7
temp = get_current_temp();
// Converted temperature is 27
itemp = 40 + round(temp);
payload = itemp;
printf("0x%02X\n", payload);
You would only need 1 byte to transmit 27: 0x1B
Summary
In this case, using good optimization, you could transmit the same temperature data using 4 times fewer payload bytes. This means 4 times cheaper data bills.
Original temp | Int conversion | Norma-lization | Rounding | Data type | Range | Resolution | Converted temp | Encoded temp (hex) | Bytes |
---|---|---|---|---|---|---|---|---|---|
-12.7 | ❌ | ❌ | ❌ | float | -40.0 to 85.0 | 0.1ºC | -12.7 | 0xC14B3333 | 4 |
-12.7 | ✔️ | ❌ | ❌ | int32_t | -400 to 850 | 0.1ºC | -127 | 0xFFFFFF81 | 4 |
-12.7 | ✔️ | ❌ | ❌ | int16_t | -400 to 850 | 0.1°C | -127 | 0xFF81 | 2 |
-12.7 | ✔️ | ✔️ | ❌ | uint16_t | 0 to 1250 | 0.1ºC | 273 | 0x0111 | 2 |
-12.7 | ✔️ | ✔️ | ✔️ | uint8_t | 0 to 125 | 1ºC | 27 | 0x1B | 1 |