General Concept

The ThingSet protocol provides a consistent, standardized way to configure, monitor and control ressource-constrained devices via different communication interfaces. It specifies the higher layers (5 to 7) of the OSI (Open Systems Interconnection) modelopen in new window. The payload data is independent of the underlying lower layer protocol or interface, which can be CAN, USB, LoRa, WiFi, Bluetooth, UART (serial) or similar.

ISO/OSI layer setup

The underlying layers have to ensure encryption, reliable transfer, correct packet order (if messages are packetized) and error-checking of the transferred data.

A major feature of the ThingSet protocol is a seamless integration with other application layer protocols such as HTTP and CoAPopen in new window. Suggestions for implementing gateways to convert between ThingSet messages and HTTP/CoAP payload will be added in a future separate chapter.

Message Types

ThingSet defines three types of messages: Requests, responses and publication messages.

Request/response or client/server model

The communication between two specific devices uses a request/response messaging pattern. A connection can be established either directly (e.g. serial interface, USB, Bluetooth) or via a network or bus with several devices attached (e.g. CAN, Ethernet, WiFi, LoRa). In case of a network, each device/node has to be uniquely addressable.

Communication Channels

The device acts as the server and responds to the requests by a client. The client might be a display, a mobile phone application or a gateway.

The data transfer is always synchronous: The client sends a request, waits for the response (status code and requested data), processes the response and possibly starts with additional requests.

Publication messages

Monitoring data is not intended for only a single device, but could be interesting for several devices (e.g. data logger and display). Thus, the monitoring data can be exchanged via a publish/subscribe messaging pattern.

Publication messages are directly broadcast through the network. Unlike in MQTT, there is no intermediate broker to store the messages and published messages are not confirmed by recipients, so there is no guarantee if the message was received.

Protocol Modes

Similar to Modbus, the ThingSet protocol supports two different modes: A human-readable text mode and a binary mode.

In the text mode, payload data is encoded in JSON format (RFC 8259open in new window). This mode is recommended when using serial communication interfaces as the low layer protocol, as it can be easily used directly on a terminal.

The binary mode uses the CBOR (RFC 7049open in new window) instead of JSON for payload data encoding in order to reduce the protocol overhead for ressource-constrained devices or low bandwith communication protocols like CAN and LoRa.

A device may implement both variants of the protocol, but it is also allowed to support only the mode most suitable for the application.

Data Structure

All accessible data of a device is structured as a treeopen in new window. Internal nodes are used to define paths or endpoints for data access. Actual data is stored in the leaf nodes and can be any kind of measurements (e.g. temperature), device configuration (e.g. setpoint of a controller) or similar information.

Each node is identified by a unique node 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, an underscore, dots or dashes without any whitespace characters. The underscore is only used to specify the unit of the data (if applicable, also see below). No additional underscore is allowed in the name and it should be unique per device at least for nodes that are used in publication messages.

The numeric IDs are used to define the relations between nodes and to directly access data nodes in the binary protocol for reduced message size. For all interactions with users and in the text-based mode, only the node names and paths (consisting of multiple node names) are used.

Example

For explanation of the protocol, the following simplified data structure of an MPPT charge controller will be used:

{
    "info": {
        "DeviceType": "MPPT 1210 HUS",
    },
    "conf": {                                       // data stored in NVM
        "BatCharging_V": 14.4,
    },
    "input": {                                      // data stored in RAM
        "EnableCharging": true
    },
    "output": {
        "Bat_V": 14.1,
        "Bat_A": 5.13,
        "Ambient_degC": 22
    },
    "rec": {
        "BatChgDay_Wh": 1984,
    },
    "exec": {
        "reset": null,                              // void function
    },
    "auth": ["Password"],                           // function with 1 parameter
    "pub": {                                        // publication channels
        "serial": {
            "Enable": true,
            "Interval": 1.0,
            "IDs": ["Bat_V", "Bat_A"]               // array of node names
        },
        "can": {
            "Enable": false,
            "Interval": 0.1,
            "IDs": ["Bat_V"]
        },
        "mqtt": {
            "Enable": true,
            "Interval": 3600,
            "Topic": "chargers/device-id/pub",
            "IDs": ["Bat_V", "Bat_A"]
        }
    },
    "sub": {                                        // subscription channels
        "mqtt": {                                   // may have callback attached
            "Topic": "chargers/device-id/sub",
            "IDs": ["EnableCharging"]
        }
    }
}

The data nodes are structured in the main different categories info, conf, input, output and rec. By convention, data node names use upper camel caseopen in new window, inner nodes nodes use lower case names.

The exec category is special, as it provides functions that can be called. In order to distinguish functions from a normal data nodes, executable node names are lower case.

The pub node is used to configure different types of publication messages, so it doesn't hold normal data nodes.

Categories

The following categories for data nodes are used for the ThingSet protocol by default:

CategoryDescriptionAccess
infoDevice information (e.g. manufacturer, software version)read access
confConfigurable settings, stored in non-volatile memory after changeread/write access, expert settings may be password-protected
inputInput nodes (e.g. actuators)write access
outputOutput nodes (e.g. sensor data)read access
recRecorded (history-dependent) data (e.g. min/max values)read access, restricted write access to reset
calFactory-calibrated settingsread/write access, protected
execExecutable functionspartly access restricted

The nodes of input and output categories are used for instantaneous data. Changes to input nodes 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 continously.

The recorded data category is used for history-dependent data like error memory, energy counters or min/max values of certain measurements. In contrast to data of output category, recorded data cannot be obtained through measurement after reset, so the current state has to be stored in non-volatile memory on a regular basis.

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

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

Data node 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).

Units

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):

CharacterReplacementExample
°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

Function Codes

The first byte of a ThingSet message is either a text-mode identifier ('?', '=', '!', '+', '-', ':' and '#'), a binary request code or a binary status code. Received data with unknown first byte is ignored, so that other text output (e.g. debug print information) can be used in parallel to the ThingSet protocol on the same serial interface.

Requests

The protocol supports the typical CRUD operationsopen in new window. Request codes match with CoAP to allow transparent translation and routing between ThingSet and HTTP APIs or CoAP devices.

CodeText IDMethodDescription
0x01?GETRetrieve all data from a node
0x02+ or !POSTAppend data to a node or execute a function
0x04-DELETEDelete data from a node
0x05?FETCHRetrieve a subset of data from a node
0x07=iPATCHUpdate (overwrite) data of a node

The CoAP PUT and PATCH methods are not explicitly implemented. PUT is equivalent to an update of all sub-nodes of a resource using a PATCH request. PATCH requests for ThingSet are always idempotent, so only the iPATCH request code is supported. The two different text IDs for POST requests are synonyms. It is decided based on the type of the node if the request is understood as a function call or a request to create a resource.

Additional request codes may be introduced in the future. Codes 0x0A, 0x0D and 0x20-0x7F are reserved, as they represent the ASCII characters for readable text including LF and CR.

Responses

Response messages in binary format are identified by a first byte greater than or equal to 128 (0x80) containing a status code which shows if the request could be handled successfully. For status codes between 0x80 and 0x9F, the response was successful. If the status code is greater than or equal to 0xA0, an error occured.

The status codes are again aligned with CoAP response codes, but contain an offset so that there is no interference with ASCII characters (less than 0x80).

CodeCoAPHTTPDescriptionComment
0x812.01201CreatedAnswer to POST requests appending data
0x822.02204DeletedAnswer to DELETE request
0x832.03200ValidAnswer to POST requests to exec nodes
0x842.04204ChangedAnswer to PATCH requests
0x852.05200ContentAnswer to GET / FETCH requests
0xA04.00400Bad Request
0xA14.01401UnauthorizedAuthentication needed
0xA34.03403ForbiddenTrying to write read-only value
0xA44.04404Not Found
0xA54.05405Method Not AllowedIf e.g. DELETE is not allowed for that node
0xA84.08400Request Entity Incomplete
0xA94.09409ConflictConfiguration conflicts with other settings
0xAD4.13413Request Entity Too Large
0xAF4.15415Unsupported Content-FormatIf trying to assign a string to an int
0xC05.00500Internal Server Error
0xC15.01501Not Implemented

The text mode converts the the hexadecimal response code into a string without the 0x prefix. The binary mode uses the code directly as the first byte.

Publication messages

Publication messages are neither requests nor response messages, as they are sent without expecting a confirmation. Below table lists the message specifier in text and binary mode.

CodeText IDDescription
0x1F#Publication message

More details regarding the ThingSet protocol methods for data access will be explained in the next chapter.