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

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 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.

URL format

Historically different URL schemes have been used within the Modbus community such as 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:

For the full syntax see [[[#abnf]]]

Modbus Vocabulary

This section describes the vocabulary used in the Modbus protocol. A protocol binding template implementation should use the vocabulary defined in this section to describe the different configuration that can be used to exchanged data between Web of Things.

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 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.

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 whereas 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
Notice that when used in conjunction with modv:function, the value of modv:function property should be ignored.

Function

Function Code class represent the value of the function field in a Modbus frame. The following table list the supported codes and their description:
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 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:

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 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"
The next table defines default values for other terms, regardless of their use in combination with one of the operations listed in the table above.
Operation Default Comments
modv:quantity 1
modv:zeroBaseAddressing 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 about how the terms defined in this document can be used to describe and configure a Form. The [[[#example-read-coil]]] shows the minimal set of terms to configure a single coil reading using Modbus. Notice that the unitID is contained in the href as the first element of the path.
            {
                "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"
                              }
                          ]
                      }
                  }
              }
              

Modbus URL ABNF syntax

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