ModBus smartCORE module
Description
The "modbus" module primarily reads the ModBus registers of ModBus devices. These devices can be
- via a serial connection (ModBus/RTU)
- via TCP (ModBus/TCP)
be connected.
Interfaces & protocols used
- ModBus
JSON configuration
The following section describes the entire JSON configuration of the module and explains the individual parameters.
Example configuration
An example ModBus/TCP configuration is shown below:
{
"module": "ModBus",
"comment": "ModBus/TCP config",
"factory": "modbus",
"config": {
"version": 1,
"type": "modbus",
"enable": true,
"pollCycleInMilliseconds": 1000,
"transport": {
"type": "tcp",
"ipAddress": "192.168.0.1",
"ipPortNumber": 502,
"timeoutInMilliseconds": 200
},
"devices": [
{
"enable": true,
"deviceName": "Device Name",
"deviceAddress": 1,
"readChannels": [
{
"name": "Channel Name",
"address": 49152,
"registerType": "HoldingReg",
"registerFormat": "REAL",
"byteOrder": "ABCD",
"bitOffset": 0,
"unit": "",
"scale": 1.0,
"offset": 0.0
}
]
}
]
}
}
Global module parameters
| Parameter name | Required | Data type | Meaningful value range | Default | Description |
|---|---|---|---|---|---|
| version | YES | INT | 1 | - | ModBus protocol version |
| type | YES | STRING | "modbus" | - | Serial interface for communication |
| enable | YES | BOOL | true | - | Switch for production of data in smartCHANNELs |
| pollCycleInMilliseconds | YES | INT | 1 - | - | polling interval [ms] of ModBus accesses |
| transport | YES | JSON object | - | configuration of the ModBus transport layer (see below) | |
| devices | YES | JSON array of JSON objects | - | List of ModBus participants | |
| pollCycleInNanoseconds | No | INT | 1 - | 100000000 | Polling interval [ns]. If the key is present it takes precedence over pollCycleInMilliseconds. If neither is given, 100 ms are used |
| enableInternalDataReduction | No | BOOL | true, false | false | Attaches a data reduction filter to every channel the module creates (absTolerance 0.0, timeoutMs 60000): unchanged values are not written, but a value is written again after 60 s at the latest |
| autoGroup | No | BOOL | true, false | true | Groups adjacent registers of a device into register blocks that are read in one transaction (see Register blocks) |
| autoGroupAllowGaps | No | BOOL | true, false | false | Allows gaps inside a block: registers nobody asked for are read along instead of splitting the block |
| maxRegisterGroup | No | INT | 1 - 125 | 125 | Largest number of registers in one block. Larger values are capped at 125 with a warning - ModBus does not allow more in a single transaction |
| useF23withoutOutputs | No | BOOL | true, false | false | Uses function code 23 (Read/Write Multiple Registers) even when there is nothing to write (from smartCORE 2.12) |
| minimalChannelAlarmCount | No | INT | 1 - | 2 | Number of immediately consecutive failed attempts after which a register block is switched off (see Timeout and switching off) |
| retryAfterBlockFailS | No | INT | 0 - | 0 | Time [s] after which a register block that was switched off is tried again. 0 = no retry, the block stays off for this measurement (from smartCORE 2.12) |
| workloadAsChannel | No | BOOL | true, false | false | Creates a diagnostic channel carrying the bus workload |
| workloadChannelBuffersize | No | INT | 1 - | 100 | Buffer size of the workload channel in elements |
| workloadChannel | No | STRING or JSON object | Name or complete channel definition of the workload channel |
ModBus/TCP specific transport parameters ("transport")
| Parameter Name | Required | Data Type | Meaningful Value Range | Default | Description |
|---|---|---|---|---|---|
| type | YES | STRING | "tcp" | Selection ModBus/TCP | |
| ipAddress | YES | STRING | Valid IP address | IP address of the ModBus device | |
| ipPortNumber | YES | INT | Valid IP port number | IP port number of the ModBus device (should be set to 502 unless otherwise specified) | |
| timeoutInMilliseconds | YES | INT | 1 - | Timeout [ms] for TCP connection (should be set to 200 unless otherwise specified) |
ModBus/RTU specific transport parameters ("transport")
| Parameter name | Required | Data type | Meaningful value range | Default | Description |
|---|---|---|---|---|---|
| type | YES | STRING | "rtu" | Selection ModBus/RTU | |
| baudRate | YES | INT | Valid ModBus baud rate | Baud rate of the serial interface | |
| dataBits | YES | INT | 5 - 8 | Data bits of the serial interface | |
| parity | YES | STRING | "no", "even", "odd" | Parity of the serial interface | |
| stopBits | YES | DOUBLE | 1.0, 1.5, 2.0 | Stop bits of the serial interface | |
| portName | YES | STRING | Valid device file | Device file of the serial interface | |
| timeoutInMilliseconds | YES | INT | 1 - | Response timeout [ms] of the serial connection. It bounds how long the answer of a participant is waited for and is passed to libmodbus as modbus_set_response_timeout() | |
| deviceTimeoutInMilliseconds | No | INT | 0 - | 0 | Pause [ms] between two devices of a sweep, inserted after all register blocks of one device have been polled |
| readDelayMs | No | INT | 0 - | Overrides the frame silence t3.5 [ms] that is kept before every request. Without the key it is derived from the baud rate (see Frame silence) |
ModBus device parameters ("devices")
| Parameter Name | Required | Data Type | Meaningful Value Range | Default | Description |
|---|---|---|---|---|---|
| enable | YES | BOOL | true | Switch for activating the ModBus device | |
| deviceName | YES | STRING | Name of the ModBus device | ||
| deviceAddress | YES | INT | 1 - | Address of the ModBus device | |
| readChannels | YES | JSON Array of JSON objects | see below |
The keys autoGroup, autoGroupAllowGaps, maxRegisterGroup and useF23withoutOutputs
may also be given per device, where they override the global value.
ModBus channel configuration ("readChannels")
| Parameter name | Required | Data type | Meaningful value range | Default | Description |
|---|---|---|---|---|---|
| name | YES | STRING | name of the smartCHANNEL | ||
| address | YES | INT | Address of the ModBus register | ||
| registerType | YES | STRING | Valid ModBus register type | Type of ModBus register ("InputReg", "HoldingReg", "Coil", "Status", "0x17" (read access only)) | |
| registerFormat | YES | STRING | Valid ModBus register format | Format of the ModBus register ("I8", "I16", "I32", "I64", "UI8", "UI16", "UI32", "UI64", "REAL", "BOOL") | |
| byteOrder | YES | STRING | see below | Order of the bytes of the value, see Byte order | |
| bitOffset | YES | UINT | Bit offset of the value to be interpreted | ||
| unit | YES | STRING | Unit of the smartCHANNEL | ||
| scale | YES | DOUBLE | Scaling of the smartCHANNEL | ||
| offset | YES | DOUBLE | Offset of the smartCHANNEL | ||
| enable | No | BOOL | true, false | true | A channel set to false is neither created nor read |
| pollingIntervalMultiplicator | No | INT | 1 - | 1 | The register block of this channel is only read in every n-th polling cycle |
| registerGroup | No | INT | 0 - | Assigns the channel to a register block by hand. Channels carrying the same number form one block and are excluded from automatic grouping |
ModBus channel configuration ("readChannelGroups")
Alternatively, several channel configurations can be combined into a JSON array, so that the ModBus registers on which this group is based can be read in a single step can be read in a single step (if the register type is identical). This readChannelGroup array combined in this way can then be listed in the JSON array of the "readChannelGroups", e.g.
"readChannelGroups": [
[
{
"name": "Channel 1",
"address": 49152,
"registerType": "HoldingReg",
"registerFormat": "REAL",
"byteOrder": "ABCD",
"bitOffset": 0,
"unit": "",
"scale": 1.0,
"offset": 0.0
},
{
"name": "Channel 2",
"address": 49158,
"registerType": "HoldingReg",
"registerFormat": "REAL",
"byteOrder": "ABCD",
"bitOffset": 0,
"unit": "",
"scale": 1.0,
"offset": 0.0
}
]
]
Alternatively, "readChannels" or "readChannelGroups" can be specified.
Register blocks
The channels of a device are not read one by one. smartCORE groups them into register blocks: a block is one contiguous address range of one register type and is read in exactly one ModBus transaction. Every channel is fed by exactly one block.
Grouping is done by register type and address. The four ModBus address spaces (coil, discrete input, input register, holding register) are independent of each other - coil 4 and input register 4 are different things and never share a block. A new block begins as soon as
- the range would span more than
maxRegisterGroupregisters, or - the next register does not immediately follow the previous one and
autoGroupAllowGapsisfalse.
With autoGroupAllowGaps: true gaps are bridged: the block reads registers no channel needs
and saves a transaction in return. That pays off for small gaps and expensive accesses, not for
addresses far apart.
To decide the layout yourself, give registerGroup per channel: channels carrying the same
number form one block and are excluded from automatic grouping.
Byte order
The value of a channel is cut out of the buffer that was read by the same component the
fmProducer module uses, the SimpleBufferPicker. byteOrder therefore
accepts two notations:
- By name -
bigEndian(alsobig,be,motorola,msb,network) orlittleEndian(alsolittle,le,intel,lsb). Without a value, big endian applies. - As a mapping - a sequence of letters that puts the bytes of the value into the order in which they sit on the bus. The length of the sequence says how many bytes the value spans, the order of the letters how they are to be swapped.
This also answers the difference between "AB" and "BA": both describe a value made of two
bytes. "AB" takes them in the order they were read, "BA" swaps them. The same for four
bytes: "ABCD" unchanged, "CDAB" word-swapped, "DCBA" fully reversed - the four variants
ModBus devices use to store 32 bit values.
"BIT" is a special case for access to a single bit. A sequence that is neither a known name
nor a valid mapping disables the channel; the message names it.
Timing and error handling
Polling cycle
The bus is served by a thread of its own that reads all due register blocks one after another in
every polling cycle (pollCycleInMilliseconds or pollCycleInNanoseconds). A block with
pollingIntervalMultiplicator: n is only due in every n-th cycle; blocks sharing a multiplicator
are spread over different cycles so that they do not all fall into the same one.
A block that is not due in a cycle produces nothing. Up to smartCORE 2.11 the unchanged
contents of the block were written out again under the current time instead - a channel with
pollingIntervalMultiplicator: 5 delivered five times as many values as there were
measurements.
Timestamps of the measured values
Every channel carries the timestamp of the transaction its value came from: the moment the answer of the device arrived. All channels of one register block therefore carry the same timestamp. The moment the device formed the value or put it on the wire is not observable over ModBus - the arrival of the answer is the closest instant the protocol offers.
Up to smartCORE 2.11 the values carried the moment they were written into the smartCHANNEL, not the moment of the measurement - that is, the moment after all devices on the bus had been polled, possibly delayed by several seconds.
Frame silence on ModBus/RTU
Before every request the line is kept quiet for the duration of the frame silence t3.5 - the pause that separates one RTU message from the next. Its length follows from:
| Condition | Silence |
|---|---|
readDelayMs is set | the given value |
| Baud rate above 19200 (or unknown) | 1.75 ms - the fixed value of the MODBUS over Serial Line Specification V1.02; the computed one would shrink to 0.33 ms at 115200 baud and separate nothing |
| otherwise | 3.5 characters of 11 bits, that is 38500000 / baud rate microseconds - 4.01 ms at 9600 baud |
The pause sits in front of the request. Up to smartCORE 2.11 it followed a successful answer - and therefore did not happen after a timeout, which is the one case in which the line needs the silence most.
Two things are logged along the way, because they point to a system configured to run faster than its line: bytes of the previous request still sitting in the output queue when the next one starts ("the line is slower than the polling cycle assumes"), and data found unexpectedly in the input buffer. The latter is read and reported with its first bytes rather than discarded silently.
Timeout and switching off a register block
If a request receives no answer within timeoutInMilliseconds, the access counts as failed. The
register block counts such failures, and after minimalChannelAlarmCount failures in a row
it is switched off: it is no longer polled and its channels produce nothing. The message names
the device, the register address, the function code, the length of the streak and whether
another attempt is planned.
Not every fault counts towards that threshold:
| Answer of the device | Effect |
|---|---|
| No answer (timeout), Illegal Function (01), Illegal Data Address (02), Illegal Data Value (03), Server Failure (04), Negative Acknowledge (07), Memory Parity (08), Gateway Target Failed To Respond (0B) | counts towards switching off - the device or the configuration is permanently not in order |
| Acknowledge (05), Server Busy (06), Gateway Path Unavailable (0A) | does not count - the device is asking for patience |
| A damaged frame, e.g. a CRC error | does not count - an event, not a state; repeating the request is the right answer to it |
retryAfterBlockFailS decides whether a block that was switched off comes back: with 0 it
stays off for the duration of the measurement, with a value greater than zero it is tried again
after that time. If the attempt succeeds the block is in service again and the counter is back
at zero; if it fails, the waiting time starts over.
A device that occasionally disappears and comes back in the field needs retryAfterBlockFailS.
Without the key it is over after minimalChannelAlarmCount failed attempts, even if the device
is back on the bus two minutes later.
Module information
| Information | Value |
|---|---|
| Authors | optiMEAS GmbH |
| since smartCORE | 0.1 |
| Module type | Producer |
| Dependencies | NONE |