CARVIEW |
In the Web of Things (WoT), a Binding is a blueprint that provides guidance toward implementing a specific IoT protocol, data format, or IoT platform. The WoT Thing Description specification explains the overall mechanism, and the WoT Binding Registry provides the formal requirements that any binding ought to follow. This document is a binding for the Modbus protocol, which is a well-known and 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 description of WoT operations using the Modbus protocol over the network. Relevant examples showcasing different vocabulary terms and the associated behaviors are also provided.
This document is a work in progress
Introduction
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 limited susceptibility to EMC interference. However, this limits the usability of the protocol to short distance networks, typically within a kilometer. When Internet access is needed, it is encapsulated in TCP (called Modbus TCP) with Ethernet as the physical layer. By using this encapsulation strategy, the Modbus protocol can reach remote nodes deployed in distant facilities over the Internet. Over the years, due to its simplicity and cost-effectiveness, the Modbus protocol has become a standard all over the world. This popularity, together with the advancement in microcontrollers and microprocessors, has led to a shift in how applications pack the information. Today, it is common to store and read any type of data in the Holdings Registers, including 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. Particularly, this document explains how to create valid URLs and Forms for the different operations that can be performed via the Modbus TCP protocol. Developers are encouraged to use this document as an implementation guideline 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.
URL format
Historically different URL schemes have been used within the Modbus community such asmodbus+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 vocabulary
For the full syntax see [[[#abnf]]].
Modbus Vocabulary
This section describes the vocabulary used in the Modbus protocol. A Modbus binding implementation should use the vocabulary defined in this section to describe the different configurations that can be used to exchange data between a Web of Things implementation and a Modbus device.URL terms
Vocabulary 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 |
Form terms
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 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 with four registers (e.g.,
AABBCCDD
), with modv:mostSignificantWord
set to true, the client can correctly
interpret the data as AABBCCDD
, while setting the property to false will result in the
interpretation being as CCDDAABB
. For more 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.
Entity
A more user-friendly property to specify [[[#function]]]. The client will then determine the right function code to be applied in the modbus request. Furthermore, it can be used in multi-operation forms whereasmodv: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.
Function
Function Code
class represents the value of the function field in a Modbus frame. The following
table lists the supported codes and their descriptions:
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 |
Payload DataType
The PayloadDataType
class within a Modbus binding instance 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; future 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
Mappings
This section describes strategies and default values to employ protocol specific concepts within the WoT Interaction model.
Default mappings
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. 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:zeroBasedAddressing |
false
|
|
modv:mostSignificantByte |
true
|
|
modv:mostSignificantWord |
true
|
|
modv:timeout |
infinite
|
Possible mappings
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
|
Examples
This section will present a set of examples showing how the terms defined in this document can be used to describe and configure a Form. The [[[#example-read-coil]]] shows the minimum set of terms needed to configure a single coil reading using Modbus. Notice that theunitID
is contained in the href
as the
first element of the path
segment.
{ "href": "modbus+tcp://127.0.0.1:60000/1", "op": [ "readproperty" ], "modv:function": "readCoil" }The [[[#entity]]] keyword can be used to describe forms with multiple operations types where the default operation will apply based on the entity and intended operation by the Consumer.
{ "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" }]Effectively reducing the verbosity of a TD. The expressiveness of the Modbus ontology allows users to also describe the total number of registers read or written 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 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 make multiple requests to satisfy their 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 form of configuration of a form using the Modbus vocabulary is the polling mechanism. With the keyword
pollingTime
, the user can indicate the intervals at which to observe a particular set of
registers. Supposing that the device is implemented in a way that the value of coil register 1 changes every 1000
ms, in [[[#example-polling]]], it suggests that the polling time should not be faster than 10 ms. WoT Consumers
may still submit 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" } ] } } }
Modbus URL ABNF syntax
The following describes the [[[#url]]] using [[[RFC2234]]] with 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