In the context of the Web of Things (WoT), a Binding Template is a blueprint that gives guidance on how to implement a specific IoT protocol, data format or IoT platform. The Core Binding Templates specification explains the overall mechanism and requirements for any binding to follow. This document gives implementation guidelines regarding the Modbus protocol, which is a well-known cost-effective IoT protocol for communication between industrial control and automation devices.
More specifically, this document defines a set of vocabulary terms that can be used inside a Thing Description document, and associated rules which allow to describe WoT operations using the Modbus protocol over the network. Additionally, relevant examples are provided to showcase different vocabulary terms and the associated behavior.
This document is a work in progress
The Modbus is a data communication protocol originally developed by Modicon, which is now a part of Schneider Electric. The protocol was specifically designed for the remote management of the hardware devices in the industrial environment. For this reason, it has a low level of abstraction and it has built in bit handling capabilities oriented to the direct control of the relays and generic contact statuses.
All Modbus devices operate by exposing a certain set of coils (bits) and registers (group of 16 bits) over the network. These coils or registers can be read or written with a simple request-response mechanism. Very typically, registers are also combined to represent data that is larger than 16 bits, i.e. two registers can be used together to represent a 32 bit integer. Thus, a Modbus client can read a set of registers at once and obtain a larger value.
The physical layer can be RS485 differential bus which has less susceptibility to the EMC interference. This limits the usability of the protocol in short distance networks, typically within a kilometer. However, when Internet access is needed, it is encapsulated in TCP (called Modbus TCP) with Ethernet as the physical layer. Thanks to this encapsulation strategy, the Modbus protocol can reach remote nodes deployed in distant facilities over the Internet. Moreover, over the years, due to its simplicity and cost-effectiveness the Modbus protocol became a standard all over the world. This popularity, together with the advancement of the microcontroller/microprocessor, has led to a shift on how applications pack the information. Today it is usual to store and read any type of data in the Holdings Registers like bits, bytes, strings, floats etc.
Concretely, this document describes how WoT TDs can be used to describe devices that use the Modbus TCP protocol. Other versions of Modbus can be incorporated into this document in the future. In particular, the document explain how to create valid URLs and Forms for the different operations that the Modbus TCP protocol can perform. Developers are encouraged to use this document as an implementation guidelines and as a reference for the creation of their own binding implementations. The following sections will cover the URL format, the vocabulary and a list of Form examples.
Forms of a Thing Description instance with Modbus Binding complies with this specification if it follows the normative statements in and .
A JSON Schema [[?JSON-SCHEMA]] to validate Thing Description instances containing Modbus Binding is provided in the GitHub repository.
modbus+tcp://
,
modbus+ascii://
and modbus+rtu://
.
Since this document focuses on the Modbus TCP protocol, modbus+tcp://
is the only valid URL scheme at the
moment.
The following shows the typical structure of an URL of the Modbus TCP protocol:
modbus+tcp://{deviceAddress}:{port}/{unitID}/{address}?quantity={?quantity}
Where:
{deviceAddress}
is the IP address of the Modbus device{port}
is the port of the Modbus device{unitID}
is the unit ID of the Modbus device. See vocabulary{address}
is the address of the register/coil referenced in this URL. See vocabulary{quantity}
the amount of registers/coils referenced in this URL. See vocabularyVocabulary term | Description | Assignment | Type |
---|---|---|---|
modv:unitID |
The Unit ID is technically not needed for ModbusTCP, since the IP-address works as unique identifier, but for compatibility reasons it MUST be included | required | integer |
modv:address |
Specifies the starting address of the Modbus operations | required | integer |
modv:quantity |
Specifies the amount of either registers or coils to be read or written to | optional | integer |
Vocabulary term | Description | Assignment | Type |
---|---|---|---|
modv:pollingTime |
Modbus TCP maximum polling rate. The Modbus specification does not define a maximum or minimum allowed polling rate, however specific implementations might introduce such limits. Defined as integer of milliseconds. | optional | integer |
modv:timeout |
Modbus response maximum waiting time. Defines how much time the runtime should wait until it receives a reply from the device. | optional | integer |
modv:mostSignificantByte |
When true , it describes that the byte order of the data in the Modbus message is the most significant byte first (i.e., Big-Endian). When false , it describes the least significant byte first (i.e., Little-Endian). |
optional | boolean |
modv:mostSignificantWord |
When true , it describes that the word order of the data in the Modbus message is the most significant word first (i.e., no word swapping). When false , it describes the least significant word first (i.e. word swapping) |
optional | boolean |
modv:zeroBasedAddressing |
Modbus implementations can differ in the way addressing works, as the first coil/register can be either referred to as true or false . |
optional | boolean |
modv:entity |
A registry type to let the runtime automatically detect the right function code | optional | Entity |
modv:function |
Function Code sent by the master in every request. Specifying the desired interaction. | optional | Function |
modv:type |
Specifies the data type contained in the request or response payload. Users can define the specific data type using a sub type of xsd:decimal . |
optional | PayloadDataType |
The modv:mostSignificantByte
and modv:mostSignificantWord
properties within the Modbus
binding template define the byte and word order of data within Modbus messages. In Modbus
communication, the arrangement of bytes and words may vary between systems, and these properties provide a
declarative way to capture such configurations. For instance, in Big-Endian byte order (e.g., AABBCC), the most
significant byte comes first, and setting modv:mostSignificantByte
to true ensures the correct
interpretation of the data as AABBCC. Conversely, in Little-Endian byte order (e.g., CCBBAA), the least significant
byte comes first, therefore modv:mostSignificantByte
has to be set to false.
On the other hand, the modv:mostSignificantWord
property extends this functionality to message payloads
composed by more than two registers. For example, given a message payload composed by four registers (e.g., AABBCCDD),
with modv:mostSignificantWord
set to true let the client to correctly interpretate the data as AABBCCDD,
while setting the property to false will result in the interpretation of the data as CCDDAABB. For further examples, see
[[[#example-read-holding-32bit]]].
Note that while word swapping is not part of the Modbus specification, it is a practice observed in off-the-shelf devices.
Therefore, when creating a Thing Description (TD), users may need to account for such deviations by configuring the
hasMostSignificantWord
and modv:mostSignificantByte
properties accordingly.
modv:function
cannot (See the
[[[#example-read-coil-entity]]])
Value | Description |
---|---|
Coil |
Represent a modbus coil register. These entities can be read or written |
DiscreteInput |
Represent a modbus discrete input. These entities can only be read |
HoldingRegister |
Represent a modbus holding register. These entities can be read or written |
InputRegister |
Represent a modbus input register. These entities can only be read |
modv:function
, the value of modv:function
property should be ignored.
Value | Label | Code | Description |
---|---|---|---|
readCoil |
Read Single Coil | 1 | Read a single coil (i.e. boolean/bit access) value. Usually in the address range 00001-09999 |
readDeviceIdentification |
Read Device Identification | 43 | Read Device Identification for diagnostic purposes. Available in the majority of Modbus implementations. |
readDiscreteInput |
Read Discrete Inputs | 2 | Read Physical Discrete Inputs (bit access). Address range 10001-19999 |
readHoldingRegisters |
Read Multiple Holding Registers | 3 | Read Multiple Holding Registers (16 bit access). Address range 40001-49999 |
readInputRegisters |
Read Multiple Input Registers | 4 | Read Multiple Physical Input Registers (16 bits). Address range 30001-39999 |
writeMultipleCoils |
Write Multiple Coils | 15 | Write Multiple Physical Coils (internal bits). Address range 00001-09999 |
writeMultipleHoldingRegisters |
Write Multiple Holding Registers | 16 | Write Multiple Holding Registers (output registers, 16 bit). Address range 40001-49999 |
writeSingleCoil |
Write Single Coil | 5 | Write Single Physical Coil (internal bit). Address range 00001-09999 |
writeSingleHoldingRegister |
Write Single Holding Register | 6 | Write Single HoldingRegister (internal bits). Address range 40001-49999 |
The PayloadDataType
class within the Modbus Binding Template serves as value for the modv:type
property to specify
the expected data types of payload content in Modbus messages. It offers a set of terms taken from XML Schema [[XMLSCHEMA11-2-20120405]] to
cover the most common data types used in Modbus messages. Note that the PayloadDataType
entity is designed for describing established conventions in the Modbus ecosystem, further development might remove this
functionality or add new terms.
Currently, the PayloadDataType
class contains the following data types:
xsd:integer
xsd:boolean
xsd:string
xsd:float
xsd:decimal
xsd:byte
xsd:short
xsd:int
xsd:long
xsd:unsignedByte
xsd:unsignedShort
xsd:unsignedInt
xsd:unsignedLong
xsd:double
xsd:hexBinary
This section describes strategies and default values to employ protocol specific concepts within the WoT Interaction model.
The following table lists the default mappings between the protocol specific concepts and the WoT concepts. Please note that operations that are not
listed in the table are not supported by this binding template. For example, since the Modbus protocol is a request-response based protocol, the
subscribeevent
operation is not supported.
Operation | Default Binding |
---|---|
writeproperty |
"modv:function": "writeSingleCoil" |
invokeaction |
"modv:function": "writeSingleCoil" |
readallproperties |
"modv:function": "readHoldingRegisters" |
readmultipleproperties |
"modv:function": "readHoldingRegisters" |
writeallproperties |
"modv:function": "writeMultipleHoldingRegisters" |
writemultipleproperties |
"modv:function": "writeMultipleHoldingRegisters" |
Operation | Default | Comments |
---|---|---|
modv:quantity |
1
|
|
modv:zeroBaseAddressing |
false
|
|
modv:mostSignificantByte |
true
|
|
modv:mostSignificantWord |
true
|
|
modv:timeout |
infinite
|
Additional to the default mappings, users may decide to use other [[[#function]]]s for a specific operation. The following table lists examples of possible mappings for some operation types.
Operation | Possible Binding |
---|---|
writeproperty |
"modv:function":"writeMultipleCoils"
|
writeproperty |
"modv:function":"writeSingleHoldingRegister"
|
readproperty |
"modv:function":"readDeviceIdentification"
|
readproperty |
"modv:function":"readDiscreteInput"
|
observeproperty |
"modv:function":"readCoil" ;
"modv:pollingTime": 1000
|
{ "href": "modbus+tcp://127.0.0.1:60000/1", "op": [ "readproperty" ], "modv:function": "readCoil" }To describe forms with multiple operations types, the [[[#entity]]] keyword can be used to create a short description of the modbus endpoint.
{ "href": "modbus+tcp://127.0.0.1:60000/1/1", "op": [ "readproperty", "writeproperty" ], "modv:entity": "Coil" }A TD processor will intepred [[[#example-read-coil-entity]]] configuration as the following:
[{ "href": "modbus+tcp://127.0.0.1:60000/1/1", "op": [ "readproperty", ], "modv:function": "readCoil" }, { "href": "modbus+tcp://127.0.0.1:60000/1/1", "op": [ "writeproperty", ], "modv:function": "writeCoil" }]Reducing effectively the verbosity of a TD. Thanks to the expressiveness of the Modbus ontology users can describe also the total number of registries read or wrote in a WoT operation. [[[#example-read-holding]]] shows how to read or write 8 HoldingRegisters.
{ "href": "modbus+tcp://127.0.0.1:60000/1/40001?quantity=8", "op": [ "readproperty", "writeproperty" ], "modv:entity": "HoldingRegister" }When possible WoT consumers will use Modbus features to read the desired amount of data with a single protocol request. However, it may be possible to still specify a total length for Modbus operations that do not support reading or writing on a range of registers (see [[[#example-read-coil-range]]]). In these circumstances consumers will perform different requests to satisfy the configuration requirements.
{ "href": "modbus+tcp://127.0.0.1:60000/1/1?quantity=8", "op": [ "readproperty", "writeproperty" ], "modv:entity": "Coil" }When dealing with multi registers payloads, it is possible to specify further details about the data type sent on the wire in the payload request. The following example shows how to read a 32bit integer Little-Endian value stored in two HoldingRegisters.
{ "href": "modbus+tcp://127.0.0.1:60000/1/1?quantity=2", "op": [ "readproperty" ], "modv:entity": "HoldingRegister", "modv:type": "xsd:int" "modv:mostSignificantByte": false }Another notable configuration of a form using the Modbus vocabulary is the polling mechanism. Thanks to the keyword
pollingTime
the user can indicate the intervals for observing a particular
set of registers. Supposing that the device knows that the value of coil register 1 does change every
1000 ms, in [[[#example-polling]]], it suggest that the polling time should not be faster than 10 ms. WoT
consumers may still create requests faster than the specified time but it should be taken as a reasonable
default for observing a property.
{ "href": "modbus+tcp://127.0.0.1:60000/1/1", "op": [ "observeproperty" ], "modv:entity": "Coil", "modv:pollingTime": 1000 }Finally, [[[#full-td]]] shows a complete device described using Modbus ontology.
{ "@context": [ "https://www.w3.org/2019/wot/td/v1", { "modv": "https://w3c.github.io/wot-binding-templates/bindings/protocols/modbus/context.jsonld" } ], "title": "ModbusPLC", "description": "An industrial machine, retrofitted to be IoT capable.", "id": "uri:dev:ModbusTCPThing", "securityDefinitions": { "nosec_sc": { "scheme": "nosec" } }, "security": "nosec_sc", "base": "modbus+tcp://192.168.178.32:502/1/", "properties": { "limitSwitch1": { "title": "downLimitSwitch", "type": "boolean", "description": "Limit switch moving downwards", "forms": [ { "op": "readproperty", "href": "10003", "modv:function": "readDiscreteInput", "contentType": "application/octet-stream" } ] }, "limitSwitch2": { "title": "forwardLimitSwitch", "type": "boolean", "description": "Limit Switch moving forward", "forms": [ { "op": "readproperty", "href": "10002", "modv:function": "readDiscreteInput", "contentType": "application/octet-stream" } ] }, "moveDown": { "title": "moveDown", "type": "boolean", "description": "Down Motor Status (single coil). PLC output, can be written to control the motor.", "forms": [ { "op": [ "writeproperty", "observeproperty", "readproperty" ], "href": "6", "modv:entity": "Coil", "modv:pollingTime": 100, "contentType": "application/octet-stream" } ] }, "moveForward": { "title": "moveForward", "type": "boolean", "description": "Forward Motor Status (single coil). PLC output, can be written to control the motor.", "forms": [ { "op": [ "writeproperty", "observeproperty", "readproperty" ], "href": "3", "modv:entity": "Coil", "modv:pollingRate": 100, "contentType": "application/octet-stream" } ] } } }
The following describe the [[[#url]]] using [[[RFC2234]]] with the reference to [[[uri]]] specification.
MODBUS-URI = "modbus+tcp://" authority path-modbus [ "?quantity=" quantity ] path-modbus = "/" unitID "/" address unitID=1*DIGIT address=1*DIGIT quantity=1*DIGIT