We often get questions asking how to efficiently construct payloads for use with Chirp SDKs. You can find instructions on sending basic data types with Chirp here, but what about when you need to send slightly more complex data structures? The usable sound spectrum is very limited in bandwidth when compared to RF, so data should be packed as efficiently as possible for transmission - welcome Google’s Protocol Buffers.

Protocol Buffers are a language-neutral, platform-neutral and extensible way of serializing structured data for use with communications protocols. They are often selected for IoT projects as data can be efficiently packed into small messages. Protocol buffers generate much smaller payloads than alternatives such as JSON or XML, and offer easy to use classes with a structured schema.

This walkthrough will show you how to structure data for a simple sensor with protocol buffers, serialize, send, and then parse the received data with the Chirp Python SDK.


Dependencies

Protocol Buffers does require a compiler to translate the schema definitions into a usable Python module. To install on macOS:

brew install protobuf

Defining a schema

Next up, we need to define the structure of the data we want to transmit. This is done with a .proto file.

syntax = "proto2";

package tutorial;

message Reading {
  required int32 id = 1;
  required float temp = 2;
  optional float humidity = 3;
}

message Readings {
  repeated Reading reading = 1;
}

In this simple example, we just encode an ID, a temperature reading and an optional humidity reading. It is also possible to chain a list of readings to send.

Compile

To compile the schema into a usable Python module:

protoc --python_out=. ./sensor.proto

This will generate a file called sensor_pb2.py that can be used directly with the Chirp Python SDK.

Usage

Data will be serialized to a bytes instance which can be passed directly into the send method.

from chirpsdk import ChirpSDK
from sensor_pb2 import Reading

sdk = ChirpSDK()
sdk.start()

reading = Reading()
reading.id = 123
reading.temp = 32.7
reading.humidity = 50.6

payload = reading.SerializeToString()
sdk.send(payload)

Data can be parsed from the receive method like so

def on_received(self, payload, channel):
    if payload is None:
        print('Decode failed!')
    else:
        reading = Reading()
        data = reading.FromString(payload)
        print('Temp: %f Humidity: %f' % (data.temp, data.humidity))

Encoding this simple message with protocol buffers creates a payloads of only 12 bytes.

payload = {
    "id": 123,
    "temp": 32.7,
    "humidity": 50.6
}

Compare this with the JSON representation of this data, which results in a string of 43 bytes.

For such simple data payloads, you may consider writing your encoding scheme, for example

payload = 'i123t32.7h50.6'

Although this a poor example of data packing, you will notice the string length is 14 bytes, still more than the protocol buffers representation.


In conclusion, Protocol Buffers allows you to send more complex data structures with the ChirpSDK, with a highly optimised message size. It also offers a well-defined data structure that is easy to use across all of our supported platforms.

If you require more than 32 bytes for your Chirp data, please send a request to access our 192 byte beta protocol via https://developers.chirp.io/support.