L3aP Protocol

Legible encoding for addressable packets documentation

For python:

For javascript:

Premise

Packets are human interprettable/enterable:

S10c0:00:cf000000\n

The protocol is configured using a yaml/json/toml file.

Purpose

The L3aP protocol is designed to:

Configuration

The json file has the following root schema:

Version

Version is a semvar version with major, minor and patch numbers. This version relates to the L3aP protocol itself. Its purpose is to determine compatability. In leap packages, major and minor versions should always match the leap protocol spec, patch versions may not.

Category

Category characters determine how the receiving application should interpret a packet.

The L3aP protocol defines six categories by default. However, an application may define more categories when needed.

Separator, Compound, End characters

These configuration items define the utf-8 characters used in encoded packets. They may be changed to suit a users application if they meet the following rules:

Data

The data field is a heirachical structure of user defined data.

"data" : [
  { "sensor" : { "addr": "8000", "data": [
    { "imu": { "addr": "00A0", "data": [
      { "accel": { "data": [
        { "x": { "type": "float" } }, 
        { "y": { "type": "float" } }, 
        { "z": { "type": "float" } }
      ]}},
      { "gyros": { "data": [
        { "x": { "type": "float" } },
        { "y": { "type": "float" } },
        { "z": { "type": "float" } }
      ]}},
    ]}},
    { "temperature": { "addr": "00C0", "type": "float" } },
    { "barometer": { "type": "float" } }
  ]}},
  { "timestamp_ms": { "addr": "9000", "type": "u64" }}
] 

Results in the following address mapping:

path 16-bit address type
sensor 0x8000 -
sensor/imu 0x80A0 -
sensor/imu/accel 0x80A1 -
sensor/imu/accel/x 0x80A2 float
sensor/imu/accel/y 0x80A3 float
sensor/imu/accel/z 0x80A4 float
sensor/imu/gyros 0x80A5 -
sensor/imu/gyros/x 0x80A6 float
sensor/imu/gyros/y 0x80A7 float
sensor/imu/gyros/z 0x80A8 float
sensor/temperature 0x80C0 float
sensor/barometer 0x80C1 float
timestamp_ms 0x9000 unsigned integer 64-bit

Addresses:

Items

Valid types:

For enumerations, the item names are user defined - these get encoded as integers. An enumeration can have no more than 256 entries.

None types can be sent when the path itself is the command. For example, an application may have a path of control/mode/disable which does not require an accompanying payload.

Packets

A typical packet looks like:

S13e7:00:cf000000\n

Where:

Category

The first character of a packet is always a category character. This specifies how the packet should be interpretted.

L3aP defines the following catergories by default:

Category Default encoding Description
set S Sets a data value, a response of nak or ack are recommended (though optional)
ack/nak A/N Responds to set packets.
They should contain the same address(es) as the set packet, but no payload data
get G Requests a data value once, a response of pub with all contained data is expected.
<ul><li>If the requested path has no children:
The response payload should contain only one item.
(eg: path sensor/imu/accel/x carries only one data item in the payload)</li><li>If the requested path has children:
The response payload should contain multiple items.
(e.g. path sensor/imu from above carries six items in the payload)</li></ul>
sub B Is the same as get but expects the data to be sent continously
The publishing rate is at the discretion of the sending application.
pub P Is used for sending application data values

It is not necessary that an application implements all categories (eg. ack and nak can easily be omitted in many cases)

Address

Addresses are encoded as a four character big-endian hexidecimal string.

Separators

Separator characters go between addresses and data values. The default separator character is :

Data

Encoding of data depends on the data type. All data gets encoded into some big endian hexidecimal string. Data typing is inferred from the address and configuration, it is not carried in the packet itself.

Type Encoding
Unsigned integers 1 character per 4-bits, zero padded
Signed integers 2’s complement, 1 character per 4-bits, zero padded
Float IEEE 754 binary 32, 8 characters
Double IEEE 754 binary 64, 16 characters
Bool Single character, only ‘0’ or ‘1’ is valid
Enumeration 2 characters to represent an unsigned 8-bit integer - enumerations are limited to 256 values
None Not applicable

End

Packets terminate with an end character. The default end character is \n.

Compound

A typical compound packet looks like:

P13e7:00:cf000000|8100:132a|8210:a8903456:b499ff00\n

Instead of an end charactor, packets are combined using a compound character.

The advantage of compound packets is to ensure sets of data are processed together.

The default compound character is |.

Configuration Files

L3aP can be configured using yaml, json or toml files.

Yaml files are probably the easiest to maintain.

Example yaml file

separator: ":"
compound: "|"
end: "\n"

version:
  major: 1
  minor: 0
  patch: 0

category:
  get: "G"
  set: "S"
  ack: "A"
  nak: "N"
  sub: "B"
  pub: "P"

data:
  - item-1:
      addr: "0000"
      data:
        - child-1:
            data:
            - grand-child-1: { type: "u8" }
            - grand-child-2: { type: "float" }
        - child-2: { type: "string" }
  - item-2: { addr: "2000", type: "none" }

Example json file

{
  "separator": ":",
  "compound": "|",
  "end": "\n",

  "version": {
    "major": 1,
    "minor": 0,
    "patch": 0
  },
  
  "category": {
    "get": "G",
    "set": "S",
    "ack": "A",
    "nak": "N",
    "sub": "B",
    "pub": "P"
  },
  
  "data": [
    { "item-1": { "addr": "0000", "data": [
      { "child-1": { "data": [
        { "grand-child-1": { "type": "u8"  } },
        { "grand-child-2": { "type": "float"  } }
      ] } },
      { "child-2": { "type": "none" } }
    ] } },
    { "item-2": { "addr": "2000", "type":  "none" } }
  ]
}

Example toml file

separator = ":"
compound = "|"
end = "\n"

[version]
  major = 1
  minor = 0
  patch = 0

[category]
  get = "G"
  set = "S"
  ack = "A"
  nak = "N"
  sub = "B"
  pub = "P"

[[data]]
  [data.item-1]
    addr = "0000"
    [[data.item-1.data]]
      [data.item-1.data.child-1]
        [[data.item-1.data.child-1.data]]
          grand-child-1 = { type = "u8" }
        [[data.item-1.data.child-1.data]]
          grand-child-2 = { type = "float" }
    [[data.item-1.data]]
      child-2 = { type = "none" }
[[data]]
  item-2 = { addr = "2000", type = "none" }