Packets Reference

Documentation of the communications packet data format

1. Introduction

This document describes the data formats available for packet component definitions. Packets are used in Device to Customer Cloud communication:

Edge Device --> Packet --> Customer Cloud

Packets use a binary data format. The Packet format allows for compact representation of device sensor data transferred over the network. Each packet consists of one or more components that contains the data. There are several component types available for the developer to choose from.

For Customer Cloud to Device communication, character string data is used (see the Communication Programming Reference:

Customer Cloud --> String Data --> Edge Device

2. Packets

2.1. Packet Definitions

The format of a packet is described by a packet definition. Packet definitions are stored in the OASIS Cloud, which is part of the RIoT Secure Platform.

Packet definitions are created in the Management Console on the Packets screen (see the Packet Editor UI documentation).

Packets can also be created and managed programmatically using the REST API (see the REST API Overview and the documentation below).

The packet definition is used by the IoT Server when converting binary data to JSON, before sending the data to the Customer Cloud.

2.2. Packet Properties

A packet has the following properties:

Packet PropertyDescription
NameName of the packet (used as key in JSON data sent to the Customer Cloud server)
IdentifierSingle character that uniquely identifies the component
ComponentsAn array of one or many component specifications

Packet JSON format:

{
  "name": "<packet name>"
  "identifier": "<packet identifier>",
  "component":
  [
    <components>
  ]
}

The JSON format is used when creating packets using the RIoT Secure REST API.

3. Components

A packet has one or more components. Each components represent a value, such as an integer. Depending on the application requirements, different components types can be used. A packet can contain multiple components.

3.1. Component Types

List of component types:

ComponentDescription
bitfielduse for flags, where multiple bits can be set
enumuse to select a single value from a range of values
rangeuse a range (min..max) to map into a set of bits
scalaruse a scale and offset to map a set of bits to raw data
integera signed or unsigned whole number (integer)
stringa NULL terminated string of characters

3.2. Component Format

Components share the following common properties:

Component PropertyDescription
NameName of the component
FormatComponent format (one of the Component Types)
ValueSpecific to the component type; defines the component value properties
OrderThe order of the component; an integer index starting at 0

Notes:

  • The “order” property is used when creating components programatically using the REST API
  • The “value” property is not used by string components

Component JSON format:

    {
      "name": "<component name>",
      "format": "<component format>",
      "value":  "<component value properties>"
      "order": "<zero-based index>"
    }

The JSON format is used when creating packets using the REST API.

3.3. Component Properties

The following are the properties of the different component types.

3.3.1. Component bitfield

The component-bitfield object allows the reservation of a specific number of bits to represent on/off values (boolean) with multiple conditions to be defined at the same time. Each entry utilizes a bit in the data packet, to maximize the usage the number entries should be the bitdepth defined.

Component bitfield properties:

Component PropertyDescription
NameName of the component (used as key in JSON data)
Bit DepthNumber of bits in the bitfield
ItemsAn array of bit descriptions

Bit description properties:

Item PropertyDescription
BitBit number (zero is first bit)
MeaningString name

Example component in JSON:

{
  "name": "Car Status Flags",
  "format": "bitfield",
  "value":
  {
    "bitdepth": 7,
    "items":
    [
      { "bit": 6, "meaning": "Driver Seated" },
      { "bit": 5, "meaning": "Head Lights On" },
      { "bit": 4, "meaning": "Parking Lights On" },
      { "bit": 3, "meaning": "Boot Door Open" },
      { "bit": 2, "meaning": "Right Front Door Open" },
      { "bit": 1, "meaning": "Left Front Door Open" },
      { "bit": 0, "meaning": "Fuel Cap On" }
    ]
  },
  "order": 0
}

In the above example, a value of 0 would have no bits set - so, all the conditions would be treated as FALSE. The bitfield has 127 possible values, when it has a value of 97 (64+32+1), 1100001 in binary, it would indicate that the driver is seated (64), the head lights are on (32) and the fuel cap is on (1).

3.3.2. Component enum

The component-enum object allows the selection of a single element from a list of values. The index acts as a lookup into a table of values, the number of possible values is 2n-1, where n represents the bitdepth defined which means not all possible values may be utilized.

Component enum properties:

Component PropertyDescription
NameName of the component (used as key in JSON data)
Bit DepthNumber of bits used to represent the number of items in the enum
ItemsAn array of item descriptions

Item description properties:

Item PropertyDescription
IndexIndex (zero is first item)
MeaningString name

Example component in JSON:

{
  "name": "Gear Position",
  "format": "enum",
  "value":
  {
    "bitdepth": 3,
    "items":
    [
      { "index": 0, "meaning": "P" },
      { "index": 1, "meaning": "R" },
      { "index": 2, "meaning": "N" },
      { "index": 3, "meaning": "D" },
      { "index": 4, "meaning": "1" },
      { "index": 5, "meaning": "2" },
    ]
  },
  "order": 0
}

In the above example, the bitdepth defines that up to seven items can be defined (23-1 = 7). When a value of 3 is provided, it will refer to the “Drive” position of the gear stick and no other values are distinct and not used.

3.3.3. Component range

The component-range object allows the reservation of a specific number of bits to store a range of values. The entire bit range is utilized to produce a value between 0 and 2n-1, where n represents the bitdepth defined. The provided value is then scaled across the range (maximum - minimum) of potential values over the number of possible values, and offset by the minimum value.

Component range properties:

Component PropertyDescription
NameName of the component (used as key in JSON data)
Bit DepthNumber of bits used to represent the value
MinimumMinimum value
MaximumMaximum value
UnitsString with the unit name (optional)

Example component in JSON:

{
  "name": "Temperature",
  "format": "range",
  "value":
  {
    "bitdepth": 8,
    "minimum": -40,
    "maximum": 100,
    "units": "degrees C"
  },
  "order": 0
}

The value is computed as follows:

value = (data * (maximum - minimum)/(2^bitdepth-1)) + minimum;

In the above example, the bitdepth defines that up 255 possible values can be defined (28-1 = 255). The value of 0 represents the minimum value of -40 degrees C, and a value of 255 represents the maximum value of 100 degrees C. Any value in between, is mapped on a linear scale, using the provided formula. A value of 127 would represent just under halfway between minimum and maximum, which is 29.725 degrees C.

The more bits reserved by the data type, the more refined and accurate the calculated value is.

3.3.4. Component scalar

The component-scalar object, similar to the component-range object allows the reservation of a specific number of bits to store a range of values. The entire bit range is utilized to produce a value between 0 and 2n-1, where n represents the bitdepth defined. The provided value is then scaled using a multiplier and then offset by a specific value. Instead of mapping to a range, the resolution of accuracy is defined by the multiplier.

Component scalar properties:

Component PropertyDescription
NameName of the component (used as key in JSON data)
Bit DepthNumber of bits used to represent the value
Sign BitBoolean setting indicating the use of a signed number
MultiplierScale factor for the value
OffsetOffset for the value
UnitsString with the unit name (optional)

Example component in JSON:

{
  "name": "Latitude",
  "format": "scalar",
  "value":
  {
    "bitdepth": 32,
    "signed": true,
    "multiplier": 0.0000001,
    "offset": 0,
    "units": "degrees"
  },
  "order": 0
}

In the above example, the bitdepth, reserving one-bit for sign of 32 defines a possible value between -2,147,483,648 and 2,147,483,647. When applying the multiplier provided, the provided value is then converted to a value between -214.7483648 and 214.7483647, more than sufficient to store a GPS latitude position. An advantage is that the value is effectively decimal point accurate and as such is identifiable from the provided value easily. Using component-range would improve accuracy when using the same bitdepth.

The more bits reserved by the data type, the more refined and accurate the calculated value is.

3.3.5. Component integer

The component-integer object allows the representation of a signed or unsigned whole number (integer).

Component integer properties:

Component PropertyDescription
NameName of the component (used as key in JSON data)
Bit DepthNumber of bits used to represent the value
Sign BitBoolean setting indicating the use of a signed number
UnitsString with the unit name (optional)

Example component in JSON:

{
  "name": "fix",
  "format": "integer",
  "value":
  {
    "bitdepth": 1,
    "signed": false,
    "units": ""
  },
  "order": 0
}

In the above example, the bitdepth defines that a single bit is used and that the possible value is an unsigned integer can have a value of 0 or 1.

3.3.6. Component string

The component-string object allows the representation of a NULL terminated string.

Component string properties:

Component PropertyDescription
NameName of the component (used as key in JSON data)

Example component in JSON:

{
  "name": "message",
  "format": "string",
  "order": 0
}

A string does not have the “value” field.

The string data is represented as an array of byte characters terminated by a NULL character. The length of the string is determined dynamically by the number of characters appearing before the NULL chartacter.

Example data layout (byte charcters):

Hello World\0

4. Bit Numbering and Byte Order

Component data is represented as binary data (an array of bytes).

4.1. Bit Numbering

The component data uses LSB 0-bit-numbering, meaning the LSB (least significant bit) is bit 0 and the MSB (most significant bit) is bit number n-1.

For example, an unsigned 8-bit integer component represents the decimal number 1 as follows:

    00000001
MSB ^      ^ LSB

Note that the edge application does not have to handle bit numbering explicitly, as it is abstracted by the system - the C++ bitwise operators work according to the LSB 0-bit-numbering scheme.

4.2. Byte Order

The byte order of component data is big-endian (also know as network order). That is, the most significant (MS) byte is followed by the least significant (LS) byte(s).

For example, the decimal number 1 (hex 0x0001, binary 0000000000000001) is represented by two bytes as follows:

MS byte -> 0x00 0x01 <- LS byte

The decimal number 256 (hex 0x0100, binary 0000000100000000) is represented by two bytes in the following order:

MS byte -> 0x01 0x00 <- LS byte

5. Example Application Firmware Code

The following example uses a packet named CounterPacket. The packet contains a single integer component named Counter:

5.1. Example Packet Structure

Packet structure:

Name: CounterPacket
Identifier: c
Components:
  Component:
    Name: Counter
    Format: integer
    Bit Depth: 16
    Sign Bit: unchecked/empty (unsigned integer)
    Units: empty

5.2. Example C++ Code

The following C++ code shows how to create packet data for the above packet definition:

Packet Example Code (C++)

#define  PACKET_SIZE 5             // byte size for packet

uint8_t  packet[PACKET_SIZE];      // packet data
uint16_t counter = 2048;           // example counter value
                                   // hex: 0x0800
                                   // bin: 0000 1000 0000 0000

packet[0] = '*';                   // * = high priority
packet[1] = 'c';                   // packet id
packet[2] = 2;                     // data length in bytes
packet[3] = 0xFF & (counter >> 8); // most significant byte
packet[4] = 0xFF & (counter);      // least significant byte

The above packet data array has the following byte values:

packet[0] ->  42   // '*' character
packet[1] -> 105   // 'i' character
packet[2] ->   2   // data length
packet[3] ->   8   // counter MS byte (0x08)
packet[4] ->   0   // counter LS byte (0x00)

It is essential to specify the correct packet identifier in the packet data; this identifier is used by the IoT Server to convert packet data to JSON before sending it to the Customer Cloud server.

To send the packet, use the queue library function (available in file riotuart.h):

Send Packet (C++)

riotUART.queue(packet, 0, PACKET_SIZE);

See the Device Communication Reference documentation for information about the function available in the riotuart.h library file.

6. Defining Packets Programmatically

The following is an example of how to define a packet using the RIoT Secure REST API. The example code is written in PHP.

Note the distinction between the packet definition and packet data. Packet definitions are created in the Management Console or via the REST API. Packet data is created in the application firmware, programmed in C++.

6.1. Packet Example

The following is an example of a packet definition in JSON format:

{
  "keys":
  {
    "name": "CounterPacket"
    "identifier": "c",
    "component":
    [
      {
        "name": "Counter",
        "format": "integer",
        "value":
        {
          "bitdepth": 8,
          "signed": false,
          "units": ""
        },
        "order": 0
      }
    ]
  }
}

Note the use of the top-level “keys” entry - this is required by the REST API.

6.2. Code Example (PHP)

The following example code make calls to the library available the for download in the REST API Developer Guide.

Example of functions for defining packets using PHP:

Packet Code Example

// Include the REST API library (modify path as needed)
require_once __DIR__ . "./rest-api/tools/lib/rest_api_lib.php";

// Create an empty packet
function create_empty_packet($name, $identifier)
{
  $packet = new stdClass();
  $packet->keys = new stdClass();
  $packet->keys->name = $name;
  $packet->keys->identifier = $identifier;

  return $packet;
}

// Create an integer component
function create_integer_component($name, $order, $bitdepth, $signed, $units)
{
  $component = new stdClass();
  $component->name = $name;
  $component->format = "integer";
  $component->order = $order;
  $component->value = new stdClass();
  $component->value->bitdepth = $bitdepth;
  $component->value->signed = $signed;
  $component->value->units = $units;

  return $component;
}

// Create a string component
function create_string_component($name, $order)
{
  $component = new stdClass();
  $component->name = $name;
  $component->format = "string";
  $component->order = $order;

  return $component;
}

// Packets are created using the rest_api_post library function
function post_packet($tenant_id, $packet_object)
{
  $json = json_encode($packet_object);
  $response = rest_api_post(
    "/tenant/{$tenant_id}/device",
    $json,
    "application/json");
  $packet_id = json_decode($response);

  return $packet_id;
}

The above functions are used to create packets in the OASIS Cloud.

Example of how to create a packet with an integer component:

Define Packet (one component)

$tenant_id = rest_api_tenant_get_id();
$packet = create_empty_packet("CounterPacket", "c");
$component = create_integer_component(
  "Counter", // name
  0,         // order,
  8,         // bitdepth,
  false,     // signed flag
  ""         // unit is unspecified
  );
$packet->keys->component =  [ $component ];
$packet_id = post_packet($tenant_id, $packet);
echo "Packet Created: " . $packet_id . PHP_EOL;

How to create a packet with an integer component and a string component:

Define Packet (two components)

$tenant_id = rest_api_tenant_get_id();
$packet = create_empty_packet("CounterPacket", "c");
$component_1 = create_integer_component(
  "Counter", // name
  0,         // order,
  32,        // bitdepth,
  false,     // signed flag
  ""         // unit is unspecified
  );
$component_2 = create_string_component(
  "Description", // name
  1              // order
  );
$packet->keys->component =  [ $component_1, $component_2 ];
$packet_id = post_packet($tenant_id, $packet);
echo "Packet Created: " . $packet_id . PHP_EOL;

Using REST API calls, packet definitions can be created, retrieved, updated and deleted.

Creating and managing packets via the RIoT Secure REST API is intended for experienced developers who are creating custom applications and tools.