Adapter Protocol (sMQTT)
1.1 Adapters
Adapters link OT-Device protocols and the MQTT-based messaging within the SICON.OS. Adapters can either run as software modules within the SICON.OS (“internal adapters”) or as distributed modules in the OT-devices themselves (“external adapters”).
1.2 Devices
Every adapter represents one or more devices in a hierarchical structure. The root device is called “main”.
Device Descriptions are used in 2 different formats:
IODD: for IO-Link devices
SDD: the proprietary SICON.OS device description format, can be used to
describe a non-IO-Link device completely
describe additional information for IO-Link devices that is not contained in the IODD
2. MQTT Protocol
2.1 General recommendations for MQTT clients
Since the development of this specification is still ongoing and requirements are expected to change somewhat, all MQTT clients should be implemented with a good level of tolerance towards incoming data. In particular:
Messages on unknown or unsupported subtopics should be ignored without further reaction
Messages with zero-length payload should be ignored without further reaction
Missing or incorrectly specified data in a message: whenever possible, an internal default value for that data should be used and the message processed anyway. If no reasonable default value can be defined for a required parameter, the message should be ignored.
When waiting for a certain type of message, there should be a way to exit this wait condition in case the message never arrives, usually a timeout with fallback to a previous or idle state.
2.2 Connection Settings
Unless otherwise noted, the following methods and settings will be used by all mqtt clients:
Connections to the broker are started with QoS = 0 and cleanSession = true
The keepAlive feature shall be used with the broker with a timeout of 10 seconds. This feature allows both broker and client to detect disconnections and react accordingly. Specifically, upon client disconnect the Last Will and Testament (LWT) shall be used as well.
2.3 Topic Trees
Overview of the topic trees used in SICON.OS
mqtt/status/+ – Status information of internal services
services/[recipient]/[sender]/# - Certain commands between services
adapterConfig/[recipient]/[sender]/+ - Management of adapters
adapter/[recipient]/[sender]/+ - Management of devices
device/[DevID]/+ - Public device data
2.4 JSON
All MQTT messages in this protocol use JSON in the message body.
2.5 Data Types
The values of parameter objects are always represented as strings in JSON. However within these strings, we use a set of data types derived from the IO-Link specification:
DataType Name | Format | Example |
BooleanT | Fixed values: “True” and “False”, length 8 bit | ”True” |
IntegerT | Signed integer as decimal, length 2 to 64 bit | ”-688” |
UIntegerT | Unsigned integer as decimal, length 2 to 64 bit | ”5322” |
Float32T | IEEE 754-1985 32-bit single precision As described in XML schema | ”-12.78e-2” |
TimeT | yyyy-mm-dd[Thh:mm:ss[.fff]] (where fff = fraction of a second, up to millisecond), length 64 bit | “2019-01-24T17:06:30” |
TimespanT | [+-]?PT\d+(\.\d{1,3})?S, length 64 bit | “-PT3.412S” |
StringT | ASCII format, 0-232 bytes | ”Hello world!” |
HexString | Stream of hex-bytes without any punctuation or spaces (2 characters per byte, letters are caps) Byte order is dependent on target device. | ”00AECF34” |
OctetStringT | String of hex-bytes in “0x…” format, comma-separated, see IODD-spec. | ”0x00, 0xAE, 0xCF, 0x34” |
The complex data types “ArrayT” and “RecordT” from the IODD specification are not used in this protocol. When they occur in a device, the data are represented by the individual members as subindices or the whole object as a HexString.
2.6 Data Formatting
In addition to a data type, data objects can have additional attributes that govern how the raw data from the OT devices are to be represented within SICON.OS and to the user:
Gradient, Offset: If present they are represented as Float32T and are used to scale the device data in the following way:
<Data format in gateway> = Gradient x <Raw data from device> + Offset
Resolution: Dec|Dec.1|Dec.2|Dec.3|Hex|Bin
Unit: to represent the physical unit of a value, given as a string. E.g. “unit”:”mbar”.
Data Formatting: allowed combinations of DataType, DisplayFormat, Gradient and Offset
2.7 Acknowledgment of message receipt
A number of mqtt messages, mainly the commands to control adapters, shall be acknowledged by the recipient. This is also used as a handshake to control sequential message flow.
Acknowledgment messages are formed using the following scheme when the mapper sends a request to an adapter:
Command topic: | adapter/[InterfaceID]_[SubinterfaceID]/[mapper]/[command] |
Acknowledgment topic: | adapter/[mapper]/[InterfaceID]_[SubinterfaceID]/[command] |
Acknowledgment payload: | {“Value”:”ack|nak”} |
In the remainder of this document, the term “requires an acknowledgment message” shall refer to using this scheme.
2.8 Return codes
The error codes for reading and writing stat data are defined based on the “additional code” values of ISDU responses in IO-Link. That means in most cases an adapter for an IO-Link-Master can just forward the value it has received from the IO-Link device.
Code (dec) | Meaning |
0 | OK |
17 | Index not available |
18 | Subindex not available |
35 | Access denied (index not writable or device is locked etc.) |
48 | Value out of range |
49 | Value greater than limit |
50 | Value less than limit |
51 | Value too long (size of data) |
52 | Value too short (size of data) |
53 | Function not available |
54 | Function temporarily not available |
65 | Parameter set inconsistent |
998 | Request timed out |
999 | General error / unknown cause |
2.9 Security
The broker requires a username:password from every client except for read/write to device/#-topics.
2.10 Status of internal mqtt clients
The internal mqtt clients in SICON.OS (mapper, mqtt2db, etc.) will post their status on a specific topic tree and use LWT; so a watchdog-type service can monitor for crashes and restart them.
Topic tree will look like this:
“mqtt/status/[clientName]”, message {“Status”:”on|off|disconnected”}
The mapper (clientName = Mapper) will – as an exception – set its status with retail=true.
3. Adapter Management
Every instance of an adapter is assigned a unique identification called “InterfaceID” within SICON.OS (known as AdapterInstanceID in the database). The InterfaceID is an integer value.
After an adapter has received its InterfaceID, it gets started by the backend. This causes the adapter to initiate the resetting of old device entries in the database as well as preregistering its “main” device.
The adapter then remains dormant until it is chosen by the user for full device registration.
The overall sequence with all possible steps is shown here:
3.1 Assigning the InterfaceID
3.1.1 Internal / External Adapters
The term “internal adapters” refers to a set of software modules that share the same mqtt client and run as services in SICON.OS.
The term “external adapters” refers to adapters that come with their own mqtt client and are separated from the internal set of adapters. They may be running on SICON.OS as well, but still communicate like external adapters. External adapters need to be preconfigured with the broker IP-address and port. A useful default value could be defined (e.g. 192.168.0.250).
An adapter that has not been assigned an InterfaceID yet shall make its presence known upon startup by publishing the regAdapter message.
regAdapter | (retain = true) | ||
Topic: | adapterConfig/idAssigner/[MACaddress]/regAdapter e.g. adapterConfig/idAssigner/01:02:03:04:05:06/regAdapter | ||
Payload example: | { "IPAddress":"1.2.3.4", "VendorID":234, "VendorName":"J. Schmalz GmbH", "VendorText":"J. Schmalz GmbH", "DeviceID":999998, "ProductID":"5768", "ProductName":"SICON.PLUG", "ProductText":"Plug Adapter", "FirmwareRevision":"1.00", "ArticleNumber":"214215291" } | ||
Attribute | Mult* | DataType | Description |
IPAddress | 1 | String | The IP address on which the adapter software is running |
VendorID | 1 | Integer |
|
VendorName | 0..1 | String |
|
VendorText | 0..1 | String |
|
DeviceID | 1 | Integer |
|
ProductID | 0..1 | String |
|
ProductName | 0..1 | String |
|
ProductText | 0..1 | String |
|
FirmwareRevision | 0..1 | String | Software version of the adapter |
ArticleNumber | 0..1 | String |
|
StopOnProblems | 0..1 | Boolean | If `true`, the adapter gets stopped directly if an error occurs while connecting it for the first time |
isSelfRegistering | 0..1 | Boolean |
|
(* “Mult” = multiplicity of the attribute)
According to chapter 2, this message is published with the mqtt option “retainFlag = true” so the broker saves this message as the current state in case the mapper comes online after the adapter. An LWT is not to be used here.
The ID-Assigner responds to this by sending an assignID message.
assignID | (retain = false) | ||
Topic: | adapterConfig/[MACaddress]/assignID Example: adapterConfig/01:02:03:04:05:06/assignID | ||
Payload example: | {"ID":15} | ||
Attribute | Mult | DataType | Description |
ID | 1 | Number | New InterfaceID for the adapter |
Upon reception, the adapter shall
Store the InterfaceID persistently so the next system startup can be done without assignment.
Clear the retained status-message on ”adapterConfig/idAssigner/[adapter MAC address]/regAdapter” (this is done by sending an empty message (size 0) with retainFlag = true to this topic)
Disconnect from the broker so a proper LWT can be set on the status topic (see next chapter).
3.2 Persistent adapter status
External adapters are in one of 4 different connection states and publish their current state persistently using the status message.
status | For internal adapters: retain = false For external adapters: retain = true | ||
Topic: | adapterConfig/configService/[InterfaceID]/status | ||
Payload example: | {"Status":"on"} | ||
Attribute | Mult | DataType | Description |
Status | 1 | String | “disconnected|off|reset|on” |
State | Description |
off | Initial state after starting the adapter, resulting state of stopAdapter Devices on subinterfaces are not registered and no data are sent out |
reset | Waiting for acknowledgement of resetDevices Devices on subinterfaces are not registered and no data are sent out |
on | Active state, devices on subinterfaces are registered and unregistered Data (dynamic, static, events, reporting) are sent out according to requests received |
disconnected | Last Will and Testament message, indicates network disconnection or software crash (KeepAlive interval exceeded) |
3.3 Starting and stopping adapters
Adapters are in the state “off” upon startup, i.e. they do not register devices with the mapper.
To switch the adapter between on and off states, the commands startAdapter and stopAdapter are used.
startAdapter | (retain = false) | ||
Topic: | adapterConfig/[InterfaceID]/configService/startAdapter | ||
Payload: | {} | ||
Attribute | Mult | DataType | Description |
- |
|
|
|
The adapter switches to state “reset” (without publishing this status) and sends out the “resetDevices” message as described in in 3.4.
stopAdapter | (retain = false) | ||
Topic: | adapterConfig/[InterfaceID]/configService/stopAdapter | ||
Payload: | {} | ||
Attribute | Mult | DataType | Description |
- |
|
|
|
The adapter shall unregister all its attached devices by sending disDevice for each of them. It also sets its status to “off” and publishes this as described in 3.2. While in the off-state, it shall not register any new devices.
3.4 Resetting devices
After receiving the startAdapter message, the adapter shall go to the state “reset” and send the resetDevices message to the mapper (“Status”:”reset” is not published though).
resetDevices | (retain = false) | ||
Topic: | adapterConfig/configService/[InterfaceID]/resetDevices | ||
Payload | {} | ||
Attribute | Mult | DataType | Description |
- |
|
|
|
Acknowledge resetDevices | (retain = false) | ||
Topic: | adapterConfig/[InterfaceID]/configService/resetDevices | ||
Payload | {“Value”:”ack|nak”} | ||
Attribute | Mult | DataType | Description |
Value- | 1 | String |
|
Upon reception of the “ack” response, the adapter shall go to status “on”, publish its status as described in 3.2 and preregister its main device as described in 4.1.
In case of “nak” response the adapter stays in the state reset and publishes this on the status topic. In reset state, no devices will be registered. In the reset state, the adapter keeps waiting for a new startAdapter command.
3.5 Status of first connection attempt
After successfully resetting devices, the adapter shall try to connect the main device and if StopOnProblems was true broadcast the result of this first connection attempt.
firstConnect | (retain = false) | ||
Topic: | adapterConfig/configService/[InterfaceID]/firstConnect | ||
Payload | {"Status":"success"} or {"Status":"failed", "Error":"some error text / description"} | ||
Attribute | Mult | DataType | Description |
Status | 1 | String | “success|failed” |
Error | 0..1 | String | description of the occured error |
SICON.OS reacts to this message and either sets the StopOnProblems flag to false (status=success) or deactivates the adapter in the database (status=failed).
3.6 Starting full device registration
startDevices | (retain = false) | ||
Topic: | adapterConfig/[InterfaceID]/configService/startDevices | ||
Payload: | {} | ||
Attribute | Mult | DataType | Description |
- |
|
|
|
When switched from off to on state, the adapter shall only pre-register its main device. Only upon receipt of startDevices shall further subinterfaces be registered as described in 4.1.; starting by registering the main device without the prereg-Attribute set.
4. Device Management
Every adapter is subdivided into one or more Subinterfaces. The SubinterfaceIDs are predefined by each particular adapter and shall adhere to the following regular expression ^[a-zA-Z0-9]{1,4}$.
Every adapter has to have a subinterface called “main”. In case of simple, non-modular devices “main” can be the only subinterface present.
For IO-Link ports, the SubinterfaceIDs are p1, p2, p3 etc. adhering to ^p[1-9]{1}[0-9]{0,2}$.
The mapper has 2 different sub-instances designed to deal with different types of devices:
[mapper] = mapper_iodd: for IO-Link devices. They can be mapped using an IODD
[mapper] = mapper_sdd: for non-IO-Link devices that come with an SDD file for data mapping
4.1 Registering devices
Adapters recognize any devices connected to their subinterfaces and subscribe to the mapper with command regDevice:
regDevice | (retain = false) | ||
Adapter topic: | adapter/[mapper]/[InterfaceID]_[SubinterfaceID]/regDevice | ||
Device topic: | device/[DevID]/regDevice or device/[DevID]/preregDevice | ||
Payload example: | {"VendorID":"234", "DeviceID":"100610", “PDbitLength”:{“ProcessDataIn”:16, “ProcessDataOut”:0}, "VendorName":"J. Schmalz GmbH", “VendorText”:”www.schmalz.com”, "ProductName":"VSi-D", "ProductID":"VSi-D", “ProductText”:”VSi V D M12-5”, "SerialNumber":"999000001", “HardwareRevision”:”02”, “FirmwareRevision”:”1.13”, “prereg”:false} | ||
Attribute | Mult* | DataType | Description |
VendorID | 1 | String | Vendor ID within the respective namespace (IODD or SDD), decimal formatted number |
DeviceID | 1 | String | Device ID, decimal formatted number |
PDbitLength | 0..1 | Object | Process data lengths of connected device |
PDbitLength.ProcessDataIn | 0..1 | Number | Length of input process data in bits |
PDbitLength.ProcessDataOut | 0..1 | Number | Length of output process data in bits |
VendorName | 0..1 | String | may be present but empty |
VendorText | 0..1 | String | may be present but empty |
ProductName | 0..1 | String | may be present but empty |
ProductText | 0..1 | String | may be present but empty |
ProductID | 0..1 | String | may be present but empty |
FirmwareRevision | 0..1 | String | may be present but empty |
HardwareRevision | 0..1 | String | may be present but empty |
SerialNumber | 0..1 | String | may be present but empty |
ParentSubinterface | 0..1 | String | SubinterfaceID of the hierarchical parent device |
prereg | 0..1 | Boolean | Defaults to false If true => only the main device is registered |
While processing the registration, the device takes different registration stati:
0 | self preregistered |
1 | preregistered w/o xDD |
2 | xDD provisioning in progress |
3 | parsing |
4 | preregistered with xDD |
5 | registration in progress |
6 | registered |
7 | disconnected |
These stati get broadcasted on the device-topic:, e.g.:
device/[DevID]/registration_status
{"OldRegStatusID":2,"NewRegStatusID":3,"AncestorMainDevID":8}
Once the device has been registered SICON.OS broadcasts a message on device/[DevID]/regDevice or device/[DevID]/preregDevice, forwarding all info about the device.
4.2 Unregistering devices
If a device gets disconnected from the interface of the adapter the adapter sends a disDevice to the mapper. This also means that internal adapters for IO-Link masters shall send disDevice for each port if they lose connection to the IO-Link master.
disDevice | (retain = false) | ||
Topic: | adapter/[mapper]/[InterfaceID]_[SubinterfaceID]/disDevice | ||
Payload example: | {"VendorID":"234", "DeviceID":"100610", "VendorName":"J. Schmalz GmbH", "ProductName":"VSi-D", "ProductID":"VSi-D", "SerialNumber":"999000001"} | ||
Attribute | Mult* | DataType | Description |
VendorID | 1 | String | Vendor ID, decimal formatted number |
DeviceID | 1 | String | Device ID, decimal formatted number |
PDbitLength | 0..1 | Object | Process data lengths of connected device |
PDbitLength.ProcessDataIn | 0..1 | Number | Length of input process data in bits |
PDbitLength.ProcessDataOut | 0..1 | Number | Length of output process data in bits |
VendorName | 0..1 | String | may be present but empty |
ProductName | 0..1 | String | may be present but empty |
ProductID | 0..1 | String | may be present but empty |
SerialNumber | 0..1 | String | may be present but empty |
The SICON.OS forwards this message also to device/[DevID]/disDevice.
The disconnection of a device shall cause all active processes (like sending reporting data) and states (like sending events when they occur) to be reset to their default / idle states. The adapter shall not store any information about previously connected devices.
4.3 Retriggering device registration
The adapter can be made to reinitialize a subinterface by using the restartSubinterface command:
restartSubinterface | (retain = false) | ||
Topic: | adapterConfig/[InterfaceID]/configService/restartSubinterface | ||
Payload example: | {"Subinterface":"p1”} | ||
Attribute | Mult* | DataType | Description |
Subinterface | 1 | String | SubinterfaceID |
The adapter shall treat this as if the connection to the device was lost and then reestablished. Thus, a disDevice message followed by a new regDevice shall be sent to the mapper.
4.4 Setting the device id
Certain messages from the adapters are subscribed directly by the services and therefore require a device-specific ID based on their unique “DevID” within the system. As soon as this “DevID” has been determined within the database, the mapper shall transmit the information to the adapter:
setDevID | (retain = false) | ||
Topic: | adapter/[InterfaceID]_[SubinterfaceID]/[mapper]/setDevID | ||
Payload example: | {"ID":15} | ||
Attribute | Mult* | DataType | Description |
ID | 1 | Number | The DevID for this particular device. |
The adapter shall acknowledge with an acknowledge message as described in 2.7.
The adapter shall store this DevID for each subinterface until either the device is unregistered, the adapter stopped or a new setDevID command has been received.
4.5 Setting the device location info
The mapper then informs the adapter about the set location info:
setDevLocation | (retain = false) | ||
Topic: | adapter/[InterfaceID]_[SubinterfaceID]/[mapper]/setDevLocation | ||
Payload example: | {"LocationTag":"UndefinedTag","LocationPos":"UndefinedPos","AssetID":"t2twcgg4k"} | ||
Attribute | Mult* | DataType | Description |
LocationTag | 1 | String | LocationTag |
LocationPos | 1 | String | LocationPos |
AssetID | 1 | String | AssetID |
The adapter shall acknowledge with an acknowledge message as described in 2.7.
4.6 Forwarding the assigned SDD
The SDD-Mapper forwards the assigned SDD file completely to the adapter, as there might be some additional info which is essential for the adapter:
setDevSDD | (retain = false) | ||
Topic: | adapter/[InterfaceID]_[SubinterfaceID]/mapper_sdd/setDevSDD | ||
Payload example: | {"base64data":"ewogICJEb2N1bWVudCI6IHsKICAgICJTRERSZXZpc2lvbiI6ICIxLjEiCiAgfSwKICAiU291cmNlI … TAxMDIsCiAgICAgICAgIk1vZGUiOiAiQVBQRUFSUyIKICAgICAgfQogICAgXQogIH0KfQ=="} | ||
Attribute | Mult* | DataType | Description |
base64data | 1 | String | base64-encoded SDD file content |
4.7 Date Update
Certain messages like dynamic data and events require a timestamp and thus all adapters shall receive date information from the mapper before these kind of messages are requested.
The mapper shall send a dateUpdate message with the server date in a raw millisecond format of 48-Bit length. This counts the milliseconds since 1st Jan 1970, 0:00 AM. The adapter shall respond with an acknowledge message when the time stamp has been updated.
dateUpdate | (retain = false) | ||
Topic: | adapter/[InterfaceID]_[SubinterfaceID]/[mapper]/dateUpdate | ||
Payload example: | {"ms":"015FE62C8C00"} | ||
Attribute | Mult | DataType | Description |
ms | 1 | String | Timestamp in milliseconds since 1st Jan 1970, 0:00 AM. Formatted as HexString of 12 characters (= 6 byte of data). |
The adapter shall acknowledge with an acknowledge message as described in 2.7.
This timestamp format shall then be used for all further time information, like the time stamp of dynamic data, reporting data and events.
If no dateUpdate message has been received, the adapter shall use one of these methods as timestamp:
If internal real-time clock is available: use own time
Otherwise: the time since its power-up.
5. Static Data
Static data are accessible as acyclic parameters in the IOT devices and generally represent data with a low rate of change. Typically this includes identification data and device settings. Static data are referenced by a numeric key called index. For IODD-Devices, the index is suffixed by a dot and a numeric subindex (range 0…255) where subindex 0 represents summary access to the whole object (all subindices are transmitted together).
5.1 Reading Static Data
5.1.1 Data Flow
Upon device registration, all static data (as found in the IODD or SDD) are read by the mapper using the getStatData message. The corresponding postStatData message is then received by the mapper and the values get stored in the database.
5.1.2 getStatData
getStatData | (retain = false) | ||
Topic: | adapter/[InterfaceID]_[SubinterfaceID]/[mapper]/getStatData | ||
Payload example: | {"16":{"DataType":"StringT","Gradient":null,"Offset":null,"Resolution":null,"Unit":null,"BitLength":512,"BitOffset":0},"17":{"DataType":"StringT","Gradient":null,"Offset":null,"Resolution":null,"Unit":null,"BitLength":512,"BitOffset":0}} | ||
Attribute | Mult* | DataType | Description |
<Index(.Subindex)> | 1..* | Object | Index(.Subindex) is used as reference to the statData object. If Subindex is missing, it is implied 0. If Subindex is 0, the whole index is read as HexString. |
In each <Index(.Subindex)> object: | |||
DataType | 1 | String | Data Types |
Gradient | 1 | String | Data Formatting |
Offset | 1 | String | Data Formatting |
Resolution | 1 | String | Data Formatting |
Unit | 1 | String |
|
BitLength | 1 | Number | Size in bits |
BitOffset | 1 | Number | Offset in bits |
Label | 0..1 | String | If the label has to be read per OPCUA on a nodepath, the nodepath has to be set here – otherwise the label itself |
Value | 0..1 | String | If the value has to be read per OPCUA on a nodepath, the nodepath has to be set here – otherwise the value itself |
DefaultValue | 0..1 | String |
|
Range | 0..1 | String | Allowed range of value, may be single values or ranges e.g. „1,2-5,7-12,13,15“ |
Access | 0..1 | String | Accessible for read and/or write-requests e.g. „rw“ |
Static data can also be requested by using the device-topic, in which case SICON.OS creates the full getStatData message described above automatically.
getStatData | (retain = false) | ||
Topic: | device/[DevID]/getStatData | ||
Payload example: | {"Indices":["16"]} | ||
Attribute | Mult* | DataType | Description |
Indices | 1 | Array | List of Index(.Subindex) to be read |
5.1.3 postStatData
postStatData | (retain = false) | |||||
Topic: | adapter/[mapper]/[InterfaceID]_[SubinterfaceID]/postStatData | |||||
Payload example: | {"16":{"Value":"J. Schmalz GmbH","DataType":"StringT","Gradient":null,"Offset":null,"Resolution":null,"Unit":null,"BitLength":512,"BitOffset":0},"17":{"Value":"http://www.schmalz.com ","DataType":"StringT","Gradient":null,"Offset":null,"Resolution":null,"Unit":null,"BitLength":512,"BitOffset":0} | |||||
Attribute | Mult* | DataType | Description | |||
<Index> or <Index(.Subindex)> | 1..* | Object | Index(.Subindex) is used as reference to the statData object. If Subindex is missing, it is implied 0. | |||
In each <Index(.Subindex)> object: | ||||||
|
|
|
| |||
Value | 1 | String | Result of read | |||
DataType | 1 | String | Data Types | |||
Gradient | 0..1 | String | Data Formatting | |||
Offset | 0..1 | String | Data Formatting | |||
Resolution | 0..1 | String | Data Formatting | |||
Unit | 0..1 | String |
| |||
BitLength | 0..1 | Number | Size in bits | |||
BitOffset | 0..1 | Number | Offset in bits | |||
Label | 0..1 | String |
| |||
DefaultValue | 0..1 | String |
| |||
Range | 0..1 | String |
| |||
Access | 0..1 | String |
| |||
The adapter is allowed to generate more than one response message to a getStatData request.
In case of an error while reading (usually due to a missing key or blocked access), the ISDU response codes from IOL are used. In IO-Link these codes are called “additional error code”, see chapter 5.2. The adapter shall react to each key request with either the data or an error code. This way, the mapper can monitor whether the response is finished or not:
respond with key + result as string
respond with key + error code (number instead of string) as negative response,
e.g. “13”:17 => key not found
The postStatData message is automatically forwarded to the device-topic by SICON.OS:
device/[DevID]/postStatData
5.2 Writing static data
5.2.1 Data Flow
Requests for writing static data (i.e. device settings) usually originate at an IT-service using a device/DevID topic. The MQTT-OT-IT-converter receives these requests and forwards them to the appropriate adapter-instance topic.
The response to a write request of static data is first sent to the MQTT-OT-IT-converter who updates the new value in the database and then forwards the response to the IT-services again.
5.2.2 IT request
putStatData (IT) | (retain = false) | ||
Topic: | device/[DevID]/putStatData | ||
Payload example: | { "24":"VSi-NPT", "79":"1" } | ||
Attribute | Mult* | DataType | Description |
<key> | 1..* | Object | <key> gives Index.Subindex <Value> is new value to be written |
5.2.3 Adapter request
Translated request from the MQTT-OT-IT-converter to the adapter:
putStatData | (retain = false) | ||
Topic: | adapter/[InterfaceID]_[SubinterfaceID]/[mapper]/putStatData | ||
Payload example: | { "24":{"value":"Test","BitLength":256,"DataType":"StringT","Gradient":null,"Offset":null,"Resolution":null}, "79":{"value":"1","BitLength":8,"DataType":"UintegerT","Gradient":null,"Offset":null,"Resolution":null} } | ||
Attribute | Mult* | DataType | Description |
<key> | 1..* | Object | <key> gives Index.Subindex representation |
For each key-Object |
| ||
<key>.”value” | 1 | String | New value to be written |
<key>.”DataType” | 1 | String | Data Types |
<key>.”BitLength” | 1 | Number | Size in bits |
<key>.”BitOffset” | 1 | Number | Offset in bits |
<key>.”Gradient” | 1 | Number | Data Formatting |
<key>.”Offset” | 1 | Number | Data Formatting |
<key>.”Resolution” | 1 | String | Data Formatting |
<key>.”Unit” | 1 | String |
|
<key>.”Range” | 1 | String |
|
5.2.4 Adapter response
wrStatData | (retain = false) | ||
Topic: | adapter/[mapper]/[InterfaceID]_[SubinterfaceID]/wrStatData | ||
Payload example: | {"24":{"value":"Test","result":0},"79":{"value":"1","result":0}} | ||
Attribute | Mult* | DataType | Description |
<key> | 1..* | Object | <key> gives Index.Subindex representation |
For each key-Object |
| ||
<key>.”value” | 1 | String | New value that was attempted to be written |
<key>.”result” | 1 | Number | Return code of the write attempt (see chap 5.3) |
In response, the adapter shall transmit an error code for each key, as well as echo the value that was written (or was attempted to be written).
The error code and value are grouped as a JSON object for each key. The error codes are the same as for IOL ISDU write access, “additional code” values, and thus the same as in the NFC app. Error code = 0 signifies “no error”.
The adapter may generate more than one response message but should try to use as few messages as possible to reduce network overhead. However, the mapper must be able to handle the case that each key arrives as a separate message and in different sequence than the request.
5.2.5 IT response
wrStatData | (retain = false) | ||
Topic: | device/[DevID]/wrStatData | ||
Payload example: | {"24":{"value":"Test","result":0},"79":{"value":"1","result":0}} | ||
Attribute | Mult* | DataType | Description |
<key> | 1..* | Object | <key> gives Index.Subindex representation |
For each key-Object |
| ||
<key>.”value” | 1 | String | New value that was attempted to be written |
<key>.”result” | 1 | Number | Return code of the write attempt (see chap 5.3) |
5.3 Updating static data
Certain static data can change during operation of the device. To get the data base updated to new values SICON.OS defines a mechanism of periodic transmission of certain static data indices by the adapter.
The mechanism is defined and triggered by the mapper during device registration:
startStatDataUpdater | (retain = false) | |||
Topic: | adapter/[InterfaceID]_[SubinterfaceID]/[mapper]/ startStatDataUpdater | |||
Payload example: | {"Interval":60000,"Indices":[{"idxStr":"36.0","DataType":"UIntegerT","Gradient":null,"Resolution":null,"Offset":null,"Unit":null,"BitLength":8,"BitOffset":0}]} | |||
Attribute | Mult* | DataType | Description | |
Interval | 1 | Number | Sampling rate in ms between stat data updates | |
Indices | 1 | Array | One entry for each Index/Subindex | |
idxStr | 1 | String | Index.Subindex representation | |
Value | 0..1 | String | If the value has to be read per OPCUA on a nodepath, the nodepath has to be set here | |
DataType | 1 | String | Data Types | |
BitLength | 0..1 | Number | Size in bits | |
BitOffset | 0..1 | Number | Offset in bits | |
Gradient | 0..1 | String | Data Formatting | |
Offset | 0..1 | String | Data Formatting | |
Resolution | 0..1 | String | Data Formatting | |
Unit | 0..1 | String |
| |
Range | 0..1 | String |
|
As a result, the adapter shall send a statDataUpdate message periodically according to the attributes received:
updateStatData | (retain = false) | ||
Topic: | adapter/[mapper]/[InterfaceID]_[SubinterfaceID]/ updateStatData | ||
Payload example: | [{"IdxStr":"36.0", "Value":"1"}] | ||
Attribute | Mult* | DataType | Description |
Array with one entry for each requested Index/Subindex | |||
idxStr | 1 | String | Index.Subindex representation |
Value | 1 | String | The actual value of this index |
6. Dynamic Data
6.1 Description of Dynamic Data
Dynamic data is cyclically transmitted data that usually corresponds to a device’s process data. As such, the transmission of dynamic data via MQTT is also done cyclically with a given sampling rate.
Dynamic data for a subinterface consists of 2 separate channels:
The input channel “ProcessDataIn” is the data flowing from the field device to the controller, e.g. sensor values, threshold bits etc.
The output channel “ProcessDataOut” is the data flowing from the controller to the field device, e.g. actor control, target values etc.
In the SICON system, the dynamic data mechanism is for observation of both channels of process data. For forcing output process data directly from SICON.OS, see Forcing output process data.
6.2 Requesting Dynamic Data
The process data are represented as individual objects of given data type. Both process data channels can be requested in the same message:
getDynData | (retain = false) | ||
Topic: | adapter/[InterfaceID]_[SubinterfaceID]/[mapper]/getDynData | ||
Payload example: | {"SamplingRate":0,"BufferSize":1,"Parameters":{ "InPartDetection":{ "PDChannel":"ProcessDataIn", "BitLength":1, "BitOffset":120, "Gradient":null, "Offset":null, "Resolution":null, "Unit":null, "DataType":"BooleanT”}, "OutPartDetectionOff":{ "PDChannel":"ProcessDataOut", "BitLength":1, "BitOffset":24, "Gradient":null, "Offset":null, "Resolution":null, "Unit":null, "DataType":"BooleanT"} }} | ||
Attribute | Mult* | DataType | Description |
SamplingRate | 0..1 | Number | Time in ms between 2 subsequent process data samples. Default: 0 |
BufferSize | 0..1 | Number | Amount of data points to be collected and sent in a single mqtt message. Default: 1 |
Parameters | 1 | Object | Contains the individual descriptions of the process data items |
For each entry in Parameters: |
| ||
<MQTTName> | 1 | String | Definition of an “MQTTName” for the described parameter. Constructed by Channename (“In”/”Out”) and Label |
PDChannel | 1 | String | “ProcessDataIn|ProcessDataOut” |
DataType | 1 | String | Data Types |
BitLength | 0..1 | Number | Size in bits |
BitOffset | 0 or 1 | Number | Offset in bits |
Gradient | 0..1 | String | Data Formatting |
Offset | 0..1 | String | Data Formatting |
Resolution | 0..1 | String | Data Formatting |
Unit | 1 | String | as defined in chapter 1.3 |
Range | 1 | String |
|
DefaultValue | 1 | String |
|
In response, the adapter shall send out dynamic data messages periodically:
PDin PDout | (retain = false) | ||
Topic: | device/[DevID]/PDin device/[DevID]/PDout | ||
Payload example: | (PDin:) {"InPartDetection”:{“Unit”:null, “Value”:”False”}, "ms":"01648D62F609", "SamplingRate":1000} (PDout:) {“OutPartDetectionOff”:{“Unit”:null, “Value”:”False”}, "ms":"01648D62F609", "SamplingRate":1000} | ||
Attribute | Mult* | DataType | Description |
SamplingRate | 1 | Number | The actual sampling rate that the adapter is using |
ms | 1 | String | Timestamp of the first sample in the data array (in HexString format) |
For each entry: |
|
|
|
<MQTTName> | 1..* | String | The “MQTTName” for the described parameter |
<MQTTName>.Unit | 1 | String | as defined in chapter 1.3 |
<MQTTName>.Value | 1 | String | The formatted value of the parameter as a string |
Remarks on SamplingRate:
A sampling rate of 0 causes the adapter to use its internally defined default sampling rate, typically 1000ms.
A sampling rate smaller than the adapter’s minimum sampling rate (but != 0) causes the adapter to use its internally defined minimum sampling rate.
Because the actually used sampling rate can thus differ from what was requested, the adapter shall include the actual sampling rate in the postDynData message.
Remarks on BufferSize:
For regular, slow updates of process data, a buffer size of 1 is used. This causes the adapter to send a single data point per message with its respective timestamp.
In order to log faster changes in data with reasonable network overhead, a buffer size > 1 can be chosen. In this case, the adapter collects the amount of samples internally and sends them out in a single message as an array. In this case the timestamp gives the time of the first sample.
6.3 Stopping dynamic data
To stop the adapter from sending periodic dynamic data messages of any kind (both process data channels), the mapper can send this message:
stopDynData | (retain = false) | ||
Topic: | adapter/[InterfaceID]_[SubinterfaceID]/[mapper]/stopDynData | ||
Payload example: | {} | ||
Attribute | Mult* | DataType | Description |
- |
|
|
|
The adapter shall respond with an acknowledge message.
6.4 Forcing output process data
The output process data of a device can be forced via mqtt messages. The data flow follows the pattern of “putStatData”, i.e. the request is first submitted to the MQTT-OT-IT-converter via
“device/[DevID]/putPDout”
putPDout (IT) | (retain = false) | ||
Topic: | device/[DevID]/putPDout | ||
Payload example: | { "Suction":"True", "SupplyPressure":"5.2" } | ||
Attribute | Mult* | DataType | Description |
<key> | 1..* | Object | <key> gives MQTTLabel <Value> is new value to be written |
Between mapper and adapter, topics based on InterfaceID and SubinterfaceID are used.
To simplify the use of this feature for the user, the adapter shall allow that only a certain subset of process data are transmitted. For this, the adapter shall store a copy of the full process data internally and insert the new data into it before transmitting to the device. The internal process data copy is initialized with all zeros upon registration of the device.
The topic used between mapper and adapter is
“adapter/[InterfaceID]_[SubinterfaceID]/[mapper]/putPDout”
and the message format is like that of “getDynData” with the following attributes (min.):
“BitLength” and “BitOffset”: Position of the data within PDout
“DataType”, “Gradient” – as for getDynData
“Value” formatted according to the above 3
Topic: | device/[DevID]/putPDout |
Message: | {“Suction”:{"BitLength":1,"BitOffset":8,"Gradient":"1","DataType":"BooleanT","Value":"True”}, "SupplyPressure":{"BitLength":7,"BitOffset":0,"Gradient":"0.1",”DataType":"UIntegerT",”Value”:”5.2”}} |
In case of PDout, the gradient works “backwards” as compared to the transmission of dynamic data, so in the example for the parameter SupplyPressure, the value of 5.2 would have to be divided by the gradient, resulting in “52” and sent as such on the specified OT-topic for PDout (lower 7 bits of MSB).
All the other data objects in PDout that are not updated with such a message, shall be left in their previous state before being sent to the device.
7. XConfig
7.1 Requesting XConfig
The ”XConfig” configures the X_x values, which will be send with Fehler: Verweis nicht gefunden and Events and has to be send before requesting reporting data or starting events.
setXConfig | (retain = false) | ||
Topic: | adapter/[InterfaceID]_[SubinterfaceID]/[mapper]/setXConfig | ||
Payload example: | {"parameters":{"X_1":{"DataType":"UIntegerT","Gradient":null,"Resolution":null,"Offset":null,"Unit":null,"BitLength":8,"BitOffset":0,"Range":"0,1,2,3,4,5-255","index":"36.0"}},"AutomaticReadForEvents":true,"AutomaticReadForReporting":true} | ||
Attribute | Mult* | DataType | Description |
AutomaticReadForEvents | 1 | Boolean | If the X-values should be read automatically by the adapter, when sending out an event |
AutomaticReadForReporting | 1 | Boolean | If the X-values should be read automatically by the adapter, when sending out reporting data |
parameters | 1 | Object | List of individual stat data parameters that shall be sent |
Objects in parameter list: |
| ||
“X_1”,”Y_2”,…”X_4” | 1..30 | Object |
|
Objects for each X-parameter: |
| ||
index | 1 | String | The stat data index to be sent out as Y_x, given in decimal “index.subindex” notation |
Unit | 1 | String | as defined in chapter 1.3 can be replaced by a node path ? |
DataType | 1 | String | Data Types |
BitLength | 0..1 | Number | Size in bits |
BitOffset | 0 or 1 | Number | Offset in bits |
Gradient | 0..1 | String | Data Formatting |
Offset | 0..1 | String | Data Formatting |
Resolution | 0..1 | String | Data Formatting |
Range | 1 | String |
|
8. Reporting data
8.1 Requesting reporting data
The ”reporting data” mechanism of the protocol is a cyclic or trigger-base read-out of a certain set of static data. After receiving a request for startReporting, the adapter shall monitor the trigger or the time and automatically read statData from the device and send it out over mqtt. There is only 1 trigger active in a subinterface at any given time and the reception of a new startReporting message shall cancel and overwrite the old trigger.
8.1.1 Triggers
There are several different trigger mechanisms to define when the reporting data are to be sent:Time-based trigger (“Type”:”time”): The first epc message is sent out right after receipt of startReporting and updates are sent periodically thereafter.
“SamplingRate”:<time in ms> - Update rate for the EPC data
Event-based trigger (“Type”:”event”): Waits for the occurance of a specific device event. Whenever the specified event code is received with the specified incidence, the reporting data shall be sent.
“Code”:<event_code> - decimal coded event number (as in section 8.2)
“Mode”:”SINGLESHOT|APPEARS|DISAPPEARS” – event incidence mode, see section 8.2. So the trigger could be defined to react on a disappearing event as well.
Self-triggered (“Type”:”self-triggered”): In this case the adapter has certain application knowledge built-in and reacts to an internal condition that shall trigger the sending of reporting data.
Setting up the trigger condition:
startReporting | (retain = false) | ||
Topic: | adapter/[InterfaceID]_[SubinterfaceID]/[mapper]/startReporting | ||
Payload example: | {"trigger":{"type":"event", "code":"35872", "mode":"APPEARS"}, "parameters":{"Y_2":{"index":"141.0","Gradient":null,"Resolution":null,"Offset":null,"Unit":null,"DataType":"UIntegerT","BitLength":32,"BitOffset":0,"MQTTName":"CounterCc2"},"Y_1":{"index":"140.0","Gradient":null,"Resolution":null,"Offset":null,"Unit":null,"DataType":"UIntegerT","BitLength":32,"BitOffset":0,"MQTTName":"CounterCc1"}} } | ||
Attribute | Mult* | DataType | Description |
trigger | 1 | Object | The trigger to start reporting |
Parameters of trigger-object: |
| ||
type | 1 | String | “time|event|self-triggered” |
SamplingRate | 0..1 | Number | Present if and only if Type == time |
code | 0..1 | String | Event code as decimally formatted string Present if and only if Type == event |
mode | 0..1 | String | Event incidence: “APPEARS|DISAPPEARS|SINGLESHOT” Present if and only if Type == event |
parameters | 1 | Object | List of individual stat data parameters that shall be sent |
Objects in parameter list: |
| ||
“Y_1”,”Y_2”,…”Y_30” | 1..30 | Object |
|
Objects for each Y-parameter: |
| ||
index | 1 | String | The stat data index to be sent out as Y_x, given in decimal “index.subindex” notation |
Unit | 1 | String | as defined in chapter 1.3 can be replaced by a node path ? |
DataType | 1 | String | Data Types |
BitLength | 0..1 | Number | Size in bits |
BitOffset | 0 or 1 | Number | Offset in bits |
Gradient | 0..1 | String | Data Formatting |
Offset | 0..1 | String | Data Formatting |
Resolution | 0..1 | String | Data Formatting |
MQTTName | 1 | String |
|
Range | 1 | String |
|
The adapter broadcasts the reporting data:
postReport | (retain = false) | ||
Topic: | device/[DevID]/postReport | ||
Payload example: | {"Y_2":"0","Y_1":"1","X_1":"1","X_2":"0"} | ||
Attribute | Mult* | DataType | Description |
ms | 1 | String | Timestamp of the first sample in the data array (in HexString format) |
<Y_x> | 1..30 | String | mapped entries for the reporting-DB with the values of the requested data |
<X_x> | 0..4 | String | mapped entries requested by command ‘setXConfig’ |
8.2 Stopping reporting data
To stop the sending of reporting data, the following message exchange is used:
stopreportingData | (retain = false) |
Topic: | adapter/[InterfaceID]_[SubinterfaceID]/[mapper]/stopReportingData |
Payload: | {} |
This shall be acknowledged by the adapter.
9. Events
9.1 Starting and stopping events
The sending of events can be switched on or off with these messages:
startEvents | (retain = false) |
Topic: | adapter/[InterfaceID]_[SubinterfaceID]/[mapper]/startEvents |
Payload: | {} |
stopEvents | (retain = false) |
Topic: | adapter/[InterfaceID]_[SubinterfaceID]/[mapper]/stopEvents |
Payload: | {} |
Both types of message shall be acknowledged by the adapter.
9.2 Sending Events
The adapters don’t have all the information about an event that is required in the frontend or on the IT-Service side. So an event message is first transmitted from adapter to mapper using an “adapter”-topic. The mapper enriches the message with information from the database (from the IODD and SDD) as well as an unique ID for each event and forwards it to the respective device-topic.
Event message are not acknowledged in any way by the recipient.
Event (OT) | (retain = false) | ||
Topic: | adapter/[mapper]/[InterfaceID]_[SubinterfaceID]/event | ||
Payload example: | {"code":16, "mode":"DISAPPEARS", "type":"Error", "timestamp":"2018-07-12T13:31:46.058Z", “XValues":{"X_1":"1","X_2":"0"}} | ||
Attribute | Mult* | DataType | Description |
timestamp | 1 | String | Timestamp in ISO format |
code | 1 | Number | Event code |
mode | 1 | String | “APPEARS|DISAPPEARS|SINGLESHOT” |
type | 1 | String | “Error|Warning|Notification” |
XValues | 1 | Object of Strings | mapped entries requested by command ‘setXConfig’ |
Event (IT) | (retain = false) | ||
Topic: | device/[DevID]/event | ||
Payload example: | {"code":16, "mode":"DISAPPEARS", "type":"Error", "timestamp":"2018-07-12T13:31:46.058Z", “XValues":{"X_1":"1","X_2":"0"}, "message":null, "timestampAppears":"2018-07-12T13:31:24.895Z", "DeltaCounter":null, "ID":6} | ||
Attribute | Mult* | DataType | Description |
timestamp | 1 | String | Timestamp in ISO format |
code | 1 | Number | Event code |
mode | 1 | String | “APPEARS|DISAPPEARS|SINGLESHOT” |
type | 1 | String | “Defect/Fault|Warning|Notification” |
message | 1 | String | Human-readable message text |
TimestampAppears | 0..1 | String | Timestamp when the event appeared in ISO format (only if mode=DISAPPEARS) |
DeltaCounter | 0..1 | Number | Delta of Counter between this event & appeared event (only if mode=DISAPPEARS) |
ID | 1 | Number | ID of event instance in database |
XValues | 1 | Object of Strings | mapped entries requested by command ‘setXConfig’ |
EventStrings | 1 | Object of Strings or null | Name, Remark, Description, Cause, Impact, Solution |
10. Commands
Devices often have some specific commands like “Reset to factory defaults”, “Sensor calibration”, Teach-In etc. These commands are coded as integer numbers and are defined in analogy to the SystemCommand of IO-Link.
An IT-Service can request a command on the device-topic, which gets translated to the adapter-topic by the MQTT-OT-IT-converter.
putCommand | (retain = false) | ||
Topic: | device/[DevID]/putCommand adapter/[InterfaceID]_[SubinterfaceID]/[mapper]/putCommand | ||
Payload example: | {"Value":168} | ||
Attribute | Mult* | DataType | Description |
Value | 1 | Number | Command code to be executed |
The result of the command is sent back from the adapter first to the MQTT-OT-IT-converter who forwards it to the device topic:
wrCommand | (retain = false) | ||
Topic: | adapter/[mapper]/[InterfaceID]_[SubinterfaceID]/wrCommand device/[DevID]/wrCommand | ||
Payload example: | {"Value":0} | ||
Attribute | Mult* | DataType | Description |
Value | 1 | Number | Result code (see 5.3) |
For IO-Link devices, “putCommand” is always an ISDU write to index 2, subindex 0, UIntegerT with bitlength 8 and the <command_code> as data. This is handled internally in the adapter.
11. Services
For messages between build in SICON.OS services there are topics following the pattern:
services/[reciever]/[sender]/[command]
Today the following topcis/messages exist:
wrCommand | (retain = false) |
Topic: | services/preregscanner/restapi/start |
Payload example: | {} |
Send when the Prereg-Scanner is started by the restapi.
wrCommand | (retain = false) |
Topic: | services/restapi/preregscanner/finished |
Payload example: | {} |
Send when the Prereg-Scanner finishes.
wrCommand | (retain = false) | ||
Topic: | services/preregscanner/restapi/start | ||
Payload example: | {} | ||
Attribute | Mult* | DataType | Description |
Value | 1 | Number | Result code (see 5.3) |
wrCommand | (retain = false) | ||
Topic: | services/preregscanner/restapi/start | ||
Payload example: | {} | ||
Attribute | Mult* | DataType | Description |
Value | 1 | Number | Result code (see 5.3) |
12. Appendix
12.1 Data Formatting: allowed combinations of DataType, DisplayFormat, Gradient and Offset
Quoted from the IODD specification version 1.1:
13. Document revision
13.11.2017 | First document |
21.01.2020 | All occurrances of “size” as message attribute renamed to “BitLength” getStatData/postStatData without the distinction Ident/Para/Observ/Diag |
15.12.2020 | Adaptation to actual messages |
|
|