Data Structure

All accessible data of a device is structured as a treeopen in new window. The nodes in the tree structure are called data objects and correspond to the JSON object definition.

Inner nodes in the structure are used to define the hierarchical structure of the data.

Actual data is stored in leaf nodes, called data items. The data items can contain any kind of measurements (e.g. temperature), device configuration (e.g. setpoint of a controller) or similar information.

Each data object in the tree is identified by a numeric ID and a name. The ID can be chosen by the firmware developer. The name is a short case-sensitive ASCII string containing only alphanumeric characters, dots or underscores without any whitespace characters.

The underscore is only used to specify the unit of a data item (also see below). No additional underscores are allowed in the name.

A dot is used to identify paths which are used internally by the implementation of the protocol itself (e.g. configuration of publication messages). Other usages of a dot in the data object names is not allowed.

The IDs must be unique per device. Except for internal data objects (behind in a path starting with a dot) also the name must be unique.

The IDs are used to access data objects in the binary protocol mode for reduced message size. They can also be used in the firmware to define the relations in the data structure. For all interactions with users and in the text-based mode, only the object names and paths are used.

Reserved IDs

The IDs 0x00 and 0x10-0x1F are reserved for special data objects that need to be known in advance. In addition to that, IDs starting from 0x8000 are reserved for control purposes and will be assigned in the future.

The following table shows the assigned IDs. Currently unassigned IDs might be defined in a future version of the protocol.

0x00Root object of a device
0x10Time_sUnix timestamp in seconds since Jan 01, 1970
0x17.nameEndpoint used by binary protocol to determine names from IDs
0x18MetadataURLURL to JSON file containing extended information about exposed data
0x1DDeviceIDAlphanumeric string (without spaces) to identify the device (should be unique per manufacturer, typical length 8 characters)
>=0x8000...Control data objects with fixed IDs

The IDs up to 0x17 consume only a single byte when encoded as CBOR, which minimizes space consumption for IDs that are used often. The MetadataURL is retrieved only once during startup, so it is acceptable to consume 2 bytes for its ID.

Example Data

Flat layout

Most simple valid data layout consists of key/value pairs without any hierarchy.

    "DeviceID": "ABCD1234",
    "Ambient_degC": 22.3,
    "HeaterEnable": true,
    "x-reset": null

A problem with this layout is that the semantics of the data is not entirely clear. For example it is not clear if Ambient_degC is a temperature setpoint that can be written to or if it is a read-only measurement value.

Grouped layout

The following example data structure of an MPPT charge controller will be used for further explanation of the protocol.

    "info": {                                                       // 0x01
        "MetadataURL": "", // 0x18 (fixed)
        "DeviceID": "ABC12345",                                     // 0x1D (fixed)
        "DeviceType": "MPPT 1210 HUS"                               // 0x30
    "meas": {                                                       // 0x02
        "Bat_V": 14.2,                                              // 0x40
        "Bat_A": 5.13,                                              // 0x41
        "Ambient_degC": 22                                          // 0x42
    "state": {                                                      // 0x03
        "ChargerState": 3,                                          // 0x60
        "ErrorFlags": 0                                             // 0x61
    "rec": {                                                        // 0x04
        "Time_s": 460677600,                                        // 0x10 (fixed)
        "BatChgDay_Wh": 1984,                                       // 0x70
        "AmbientMax_deg": 21.6                                      // 0x71
    "input": {                                                      // 0x05
        "EnableCharging": true                                      // 0x90
    "conf": {                                                       // 0x06
        "BatCharging_V": 14.4                                       // 0xA0
    "rpc": {                                                        // 0x0E
        "x-reset": null,                                            // 0xE0
        "x-auth": ["Password"]                                      // 0xE1
    "dfu": {                                                        // 0x0D
        "x-write": ["Offset_B", "Data"],                            // 0xF0
        "FlashSize_KiB": 128                                        // 0xF1
    "report": ["Time_s", "Bat_V", "Ambient_degC"],                  // 0x20
    "ctrl": {                                                       // 0x0C
        "bus-voltage": {                                            // 0xDC01
            "Margin_V": 0.7,                                        // 0x7000
            "AbsMax_v": 30                                          // 0x7001
    ".pub": {                                                       // 0x0F
        "info": {                                                   // 0x100
            "OnChange": true                                        // 0x101
        "report": {                                                 // 0x102
            "Period_s": 10                                          // 0x103
    ".name": {                                                      // 0x17 (fixed)
        "0x01": "info",
        "0x02": "meas",
        // ...
        "0x40": "Bat_V"
        // ...

The data objects are structured in different groups like info and conf as described below. By convention, data items (leaf nodes) use upper camel caseopen in new window for their names, inner objects to define a path use lower case names.

The rpc group provides functions that can be called. In order to distinguish functions from normal data objects, executable object names are lower case and prefixed with x-.

The .pub path is used to configure the automatic publication of messages, so it doesn't hold normal data objects. Such internal nodes are prefixed with a ., similar to hidden files or folders in computer file systems.

The data node report in above example is a so-called subset, which contains an array pointing at existing data items. It can be used to configure the content of statements for publication if data objects of different groups should be combined in a single message.

Nested layout

If there are multiple entities with similar data sets inside one device, the data can be further structured as shown below.

    // ...
    "meas": {
        "bat1": {
            "Bat_V": 14.2,
            "Bat_A": 5.13
        "bat2": {
            "Bat_V": 14.2,
            "Bat_A": 5.13

In order to keep implementations simple, not all devices may implement multiple levels of hierarchies.


The following groups for data objects are used for the ThingSet protocol by default:

info0x01deviceStatic device information (e.g. manufacturer, software version)read
meas0x02deviceMeasurement data (changes regularly)read
state0x03deviceEvent-based status informationread
rec0x04deviceRecorded (history-dependent) data (e.g. min/max values)read + reset
input0x05clientInput objects (e.g. actuators)read + write
conf0x06bothConfigurable settings, stored in non-volatile memory after changeread + write, partly protected
cal0x07bothFactory-calibrated settingsread + write, protected
rpc0x0EclientExecutable functionsexecute
dfu0x0DclientFunctions and data for device firmware upgraderead + execute

The data objects of meas, state and input groups are used for instantaneous data. Changes to input data objects are only stored in RAM, so they get lost after a reset of the device. In contrast to that, conf data is stored in non-volatile memory (e.g. flash or EEPROM) after a change. As non-volatile memory has a limited amount of write cycles, configuration data should not be changed continuously.

The rec data group is used for history-dependent data like error memory, energy counters or min/max values of certain measurements. In contrast to data of meas group, recorded data cannot be obtained through measurement after reset, so the current status has to be stored in non-volatile memory on a regular basis. Also the current timestamp Time_s is stored in the rec group, as it is essentially a counter that is incremented each second.

Factory calibration data objects are only accessible for the manufacturer after authentication.

Excecutable data means that they trigger a function call in the device firmware. Child objects of the executable object can be used internally to define parameters that can be passed to the function.

Data object IDs are stored as unsigned integers. The firmware developer should assign the lowest IDs to the most used data objects, as they consume less bytes during transfer (see CBOR representation of unsigned integers).


Only SI unitsopen in new window and derived units (e.g. kWh for energy instead of Ws) are allowed.

The unit is appended to the data object name using an underscore. In order to keep the data object name very compact, the unit is also used to identify the physical quantity of the value. So instead of "BatteryEnergy_kWh" a short name like "Bat_kWh" is preferred.

Some special characters have to be replaced according to the following table in order to be compatible with URIs (see RFC 3986, section 2.3open in new window):

°deg"Ambient_degC" for ambient temperature in °C
%pct"Humidity_pct" for relative humidity in %
/_"Veh_m_s" for vehicle speed in m/s
^(omitted)"Surface_m2" for surface area in m^2