Skip to main content

Modbus

Pull

Synopsis​

Director reads the registers of one Modbus server: the coils, discrete inputs, holding registers, and input registers of a PLC, an energy meter, a drive, a sensor, or a gateway. It speaks Modbus/TCP, or the RTU or ASCII frame that some serial-to-Ethernet gateways carry over TCP. Nothing is installed on the server or beside it: Director connects as any Modbus master would, sends read requests, and writes what comes back as ECS records.

One device, one server. Monitoring forty meters takes forty devices. The device holds one connection to its server and sends its requests over it one at a time, waiting for each answer before sending the next. A Modbus server answers one request at a time, and many gateways drop a client that sends a second request before the first is answered.

What to read is declared by datasets. Modbus registers have no names: an address means something only to whoever wrote the server's register map. So the device's datasets are your own, each a table of typed fields read from one unit's registers of one kind, on the dataset's own interval. For example, a voltage at holding register 0 as an unsigned 16-bit number in tenths, a serial number in eight registers as text, or one bit of a status word.

The device dials the server and binds no port, so any number of Modbus devices, on one Director or several, can name port 502, and none of them blocks a listener. One Director owns each device: two would poll the server twice, and many Modbus servers take only one connection at a time.

note

The Modbus device needs Director 2.3.0 or later. With no Director or cluster at that version selected, the wizard shows Modbus devices are unavailable on directors or clusters below version 2.3.0 and the device cannot be created.

Schema​

- id: <numeric>
name: <string>
description: <string>
type: modbus
tags: <string[]>
pipelines: <pipeline[]>
status: <boolean>
properties:
address: <string>
port: <numeric>
transmission_mode: <string>
slave_id: <numeric>
timeout: <numeric>
busy_retries: <numeric>
busy_retries_wait: <numeric|string>
workarounds:
pause_after_connect: <numeric|string>
pause_between_requests: <numeric|string>
close_connection_after_gather: <boolean>
read_coils_starting_at_zero: <boolean>
string_register_location: <string>
one_request_per_field: <boolean>
max_bit_registers_per_request: <numeric>
max_word_registers_per_request: <numeric>
definitions: <definition[]>

Configuration​

The following fields are used to define the device:

Device​

FieldRequiredDefaultDescription
idY-Unique numeric identifier
nameY-Device name
descriptionN-Optional description
typeY-Must be modbus
tagsN-Optional tags
pipelinesN-Optional pre-processor pipelines
statusNtrueEnable/disable the device

Connection​

FieldRequiredDefaultDescription
addressY-Host name or IP address of the server. An IPv6 address can be written as it is or in brackets. A wildcard address (0.0.0.0, ::) is refused, and so is a port written into the address: the port is its own field.
portN502The server's port, from 1 to 65535
transmission_modeNtcpHow requests are framed on the connection: tcp (Modbus/TCP), rtu_over_tcp, or ascii_over_tcp. The spellings TCP, RTUoverTCP, ASCIIoverTCP, and auto are also accepted. See Transmission Modes.
slave_idN1The unit a dataset reads when it names none, from 0 to 255
timeoutN5Seconds a connect and each request wait for their answer, from 1 to 300
busy_retriesN0How many times a request that the server answered with the server device busy exception is sent again, from 0 to 10
busy_retries_waitN100Milliseconds waited before each such retry, from 0 to 60000. A duration such as 250ms or 2s is also accepted.

A device that fails these checks does not start, and its state is Configuration error; the message names the field. Examples are a missing or wildcard address, a port out of range, a serial transmission mode, or a value that is not a number. A number is read whole: NaN, an infinity, a fraction, or a number no 32-bit integer holds is refused, never converted. A switch is true or false, or text that says so; anything else is refused, never read as false.

Workarounds​

workarounds is an object of settings for servers that break the specification, or for a line that needs care. Each is off by default.

FieldRequiredDefaultDescription
workarounds.pause_after_connectN-Milliseconds (or a duration) waited after a connect before the first request, for slow servers. Up to a minute.
workarounds.pause_between_requestsN-Milliseconds (or a duration) waited between requests, counted from when the last one was answered, for slow serial lines. Up to a minute.
workarounds.close_connection_after_gatherNfalseClose the connection after every reading and connect again at the next, for servers or gateways that take few connections at once
workarounds.read_coils_starting_at_zeroNfalseStart the first request for coils at address 0, for servers that answer coil reads only from there. Left unapplied, with a warning, when the request would then exceed its maximum.
workarounds.string_register_locationNbothWhere a string keeps its characters in each register: both (a character in each byte), lower (the low byte only, 00XX), or upper (the high byte only, XX00)
workarounds.one_request_per_fieldNfalseSend a request for every field, for servers that do not answer a request covering two
workarounds.max_bit_registers_per_requestN2000Coils or discrete inputs one request reads, from 1 to 2000
workarounds.max_word_registers_per_requestN125Holding or input registers one request reads, from 1 to 125
note

Pauses add up over the requests of a reading. Keep their total below the dataset's interval.

Connection​

Transmission Modes​

  • tcp is Modbus/TCP: an MBAP header and the PDU, checked by TCP.
  • rtu_over_tcp is the serial RTU frame (unit ID, PDU, CRC-16) sent as it is over a TCP connection.
  • ascii_over_tcp is the serial ASCII frame (a colon, the message in hexadecimal, an LRC, CR LF) sent over a TCP connection.

A serial-to-Ethernet gateway takes one of two approaches. A gateway with a Modbus/TCP interface translates requests to the serial line itself, routing each one by the unit ID in its MBAP header, so it takes tcp. A gateway that tunnels the serial frame unchanged over TCP takes rtu_over_tcp or ascii_over_tcp, matching the frame on its line. The gateway's documentation says which kind it is.

No serial port

Director does not open a serial port such as /dev/ttyUSB0 or COM1: it is a central service and does not sit on a serial bus. For a server on a serial line, set address to the gateway that reaches it and transmission_mode to the mode the gateway speaks. The unit IDs on the line are the datasets' slave_id.

One Connection​

The device connects at its first reading and keeps the connection until something goes wrong. Requests take turns on it.

  • A connection is checked before every dataset, not once per reading. A read that fails for any reason other than an exception closes the connection, because what follows on it cannot be told apart from the tail of the failure. The next dataset connects again, and pause_after_connect applies again.
  • A connection the server closed while idle is found out by the first request of the next reading. That reading is made over a new connection at once, not lost. A timeout is not treated this way: a server that does not answer is not asked again while its timeout is still running.
  • A server that cannot be connected to ends the reading. The datasets still due would fail the same way, costing a timeout each; they are read at their next interval.

Exceptions​

A server that answers a request with an exception (illegal data address, illegal function, server device failure, server device busy) has understood the request and refused it. The connection is good; what is wrong is the request or the server's state. The dataset writes no record for that reading, since a record holding some of its registers and not others would mislead, the reason is logged, and the device stays Connected. Only server device busy is retried, as busy_retries sets.

Gateway exceptions are different. A serial gateway that answers gateway path unavailable (0x0A) or gateway target device failed to respond (0x0B) is saying the unit behind it is not there. The gateway answered, so its connection is kept, but the unit is what the dataset reads: the read fails and an error is logged. A device none of whose datasets could be read this way is in Connection error; a device whose other units answer stays Connected.

Datasets​

The device's definitions are its datasets. In the console they are the datasets and profiles assigned to the device in its Data collection; see Datasets and Profiles. In a configuration file each dataset is a definition named modbus_register_collector, the device's one dataset type:

DatasetDefinition IDKindOne record per
modbus_register_collector14150metricreading of the dataset's table
  • Any number of datasets. Each dataset is a table of its own, so a device reads as many as are assigned: for example, the meter's energy values, its status word, and the coils that switch its relays. Two datasets can name the same table; their values then sit under one name, and a field named alike in both should have one type.
  • Interval. A dataset is read every interval seconds, counted from the start of one reading to the start of the next. A reading that takes longer than its interval delays the next, which starts 100 ms after it ends. Datasets that fall due together are read one after the other over the same connection.
  • Pipelines. A dataset's records enter the pre-processing pipeline the dataset selects, or the device's when it selects none, so two datasets on one device can reach two pipelines.
  • Live capture names the dataset each record came from.
  • Nothing assigned is allowed. The device then only checks, every minute, that its server accepts a connection (closing it at once), so its connection state stays current, and writes nothing.
  • A dataset that cannot be read is left out, with a warning in the device's log that names it and says why. The rest of the device runs.

Dataset Properties​

FieldRequiredDefaultDescription
tableY-The name the dataset's fields sit under in a record (see Records): letters, digits, and underscores, at most 64, not starting with a digit. table, type, and slave_id are reserved.
intervalN60Seconds between readings, 1 or more
registerNholdingThe kind of register read: coil, discrete (discrete input), holding, or input. The longer names (holding_register, and so on) are also accepted.
slave_idNthe device'sThe unit read, from 0 to 255
byte_orderNABCDThe order of the bytes of a value. See Byte Order.
optimizationNnoneHow the fields are cut into requests: none, shrink, rearrange, or max_insert. See Optimizations.
optimization_max_register_fillN*-The most registers max_insert may read between two fields that no field asked for, from 1 to the most a request reads
labelsN-An object of names and values written on every record as ECS labels. At most 32; a name is letters, digits, underscores, and hyphens, and a value at most 256 characters.
pipeline_nameNthe device'sThe pre-processing pipeline the dataset's records enter

* Required when optimization is max_insert.

Byte Order​

byte_order spells the order of the bytes of a value of one or more registers as the letters of a 32-bit value, from its most significant byte:

ValueMeaning
ABCDBig endian: the registers as a Modbus server holds them, the high byte of each first
DCBALittle endian
BADCThe bytes of each register swapped
CDABThe registers swapped

The same names apply to 16- and 64-bit values. The aliases MSW-BE, MSW-LE, LSW-BE, LSW-LE, MSW, and LSW are also accepted.

Fields​

A dataset's inputs are its fields, at most 512 including omitted ones. A field whose value is written needs a name, unique in the table: letters, digits, and underscores, at most 64. A field that is only read (omit) needs no name. A dataset needs at least one field whose value is written.

FieldApplies toDescription
addressallRequired, from 0 to 65535: the register's address, or the bit's for a coil or a discrete input. For a field of several registers, the first.
typeholding, inputRequired. BIT (one bit of a register); INT8L, INT8H, UINT8L, UINT8H (the low or high byte); INT16, UINT16, INT32, UINT32, INT64, UINT64; FLOAT16, FLOAT32, FLOAT64 (IEEE 754); and STRING. 32-bit types take two registers, 64-bit types four.
lengthSTRINGRequired for a STRING, and only for it: the number of registers
bitBITThe bit of the register, from 0 (least significant) to 15
scalenumbersA factor the value is multiplied by: 0.1 turns tenths into units. Not for a STRING or a BIT.
outputallThe type the value is written as. For holding and input registers, INT64, UINT64, or FLOAT64; by default FLOAT64 when the field has a scale, otherwise the class of its type (INT64 for a signed number, UINT64 for an unsigned number or a bit, FLOAT64 for a float), and text for a STRING. For coils and discrete inputs, UINT16 (0 or 1, the default) or BOOL (false or true).
omitallRead the field's registers without writing its value. An omitted field keeps the fields around it in one request instead of two. Its type still says how many registers it covers.

A coil's or a discrete input's field takes no type, length, bit, or scale: only an address, a name, and an output.

A number that cannot be written as its output is left out of the record, and this is logged once for as long as it lasts. This covers a float that is NaN or infinite (JSON has neither), and a value outside the range of INT64 or UINT64. Numbers become integers by cutting off the fraction. For UINT64 output, the result depends on the field:

  • An unscaled signed integer type (INT8L, INT8H, INT16, INT32, INT64) is written as its two's complement. A negative reading becomes a large unsigned value, not a missing one.
  • A field with a scale, or a float type, is left out when its value is negative or too large for UINT64.

A record none of whose fields could be written is not written.

A STRING ends at its first zero byte; the registers may be longer than the text.

Requests​

A dataset's fields are read in requests of consecutive registers: function code 1 (coils), 2 (discrete inputs), 3 (holding registers), or 4 (input registers), each of at most 2000 bits or 125 registers, or less when the device's workarounds lower it. A dataset's requests are made in the order of their addresses.

  • Fields that share registers are read once. Several bits of one status register, or a wide field and a field inside it, take one request, and each field decodes its own bytes of the answer.
  • A field no request could hold is refused when the configuration is read, and the error names the field. An example is a STRING of 126 registers, or one longer than the lowered maximum. A request past address 65535 is also refused.

Optimizations​

optimization decides how the fields of a run become requests. A run is a stretch of registers that fields cover from one to the next, omitted fields included.

ValueBehavior
noneReads the layout as declared: each run is a request, omitted fields included, cut at a field boundary where it would exceed the maximum. A request of omitted fields alone is not made.
shrinkStrips the omitted fields at the ends of a run, so a request starts at its first field and ends at its last.
rearrangeFor a run that needs several requests, moves the cuts to where the requests read the fewest registers, with no more requests than shrink makes. The best cut is found exactly.
max_insertJoins the fields of every run, whatever lies between them, into requests that read no more than optimization_max_register_fill registers that no field asked for between two fields (omitted fields are ignored). It saves requests at the cost of registers, and it reads registers the server may not have: an illegal data address exception is the reason to lower the fill.

Readings​

  • At start the device connects and reads every dataset once, one after the other.
  • Each dataset on its own interval after that, from the start of one reading to the start of the next.
  • A dataset is read whole or not at all. If any of its requests fails, no record is written for that reading, and the reason is logged once for as long as it lasts: a repeat is held back for half an hour, and a recovery is logged.
  • Timestamps are the time the dataset's last request was answered.
  • On a stop the device ends its reading at once, mid-request, closes its connection, and flushes the queue of each stream it opened; only then is it stopped. A restarted device therefore never has its predecessor still writing beside it. A flush that takes longer than 15 seconds is logged as a warning, and the device waits for it all the same.

Records​

Every record is one ECS 8.11 document with the following fields:

FieldValue
@timestampThe reading's time
ecs.version8.11.0
event.modulemodbus
event.datasetmodbus.register
event.kindmetric
event.category, event.typehost, info
observer.nameThe device's name in the console
observer.hostnameThe Director's host name
observer.vendor, observer.productVirtualMetric, Modbus
server.address, server.portThe address the device is configured with, and its port
server.ipThe address, when it is an IP address
network.protocol, network.transportmodbus, tcp
labels.<name>The dataset's labels, when it has any
modbus.register.tableThe dataset's table
modbus.register.typecoil, discrete_input, holding_register, or input_register
modbus.register.slave_idThe unit read
modbus.register.<table>.<field>The value of each field that was written, as its output type

The values sit under the table's name, so two tables can name a field alike with different types without one mapping conflicting with the other. A field whose value could not be written is absent from that record.

Device State​

The connection state follows the server:

  • Connected while the server answers. An exception is an answer; a gateway's target device failed to respond is not.
  • Connection error while the server cannot be connected to, or none of the datasets due got an answer: every read failed with a timeout, a broken connection, or a gateway's path unavailable or target device failed to respond. A dataset refused with an ordinary exception still counts as answered (see Exceptions).
  • Configuration error for a configuration that cannot run, such as a bad address, port, transmission mode, or bound.

A dataset that fails while another is read is not an error of the connection. A reading that could not be written, or a failure inside one dataset, affects that dataset alone and does not change the device's state.

note

The Modbus device is not simulated. A dataset's records are the values of your own registers, a shape a generated sample cannot stand in for.

Examples​

Energy Meter​

Reading a meter's voltage, current, energy, and firmware every 10 seconds, with an omitted field keeping its neighbors in one request...

- id: 512400190
name: main-power-meter
type: modbus
status: true
properties:
address: "192.0.2.40"
busy_retries: 2
definitions:
- name: modbus_register_collector
status: true
properties:
table: energy
interval: 10
register: holding
optimization: shrink
labels:
machine: main-meter
location: main-building
inputs:
- name: voltage
properties: { address: 0, type: UINT16, scale: 0.1 }
- name: current
properties: { address: 1, type: INT32, scale: 0.001 }
- name: power_raw
properties: { address: 3, type: UINT32, omit: true }
- name: energy
properties: { address: 5, type: FLOAT32, scale: 0.001 }
- name: firmware
properties: { address: 20, type: STRING, length: 8 }

Status Bits and Coils​

Reading the bits of a status word, read once since they share a register, and two coils, one written as a boolean...

- id: 512400190
name: main-power-meter
type: modbus
status: true
properties:
address: "192.0.2.40"
definitions:
- name: modbus_register_collector
status: true
properties:
table: status
interval: 30
register: input
inputs:
- name: running
properties: { address: 100, type: BIT, bit: 0 }
- name: alarm
properties: { address: 100, type: BIT, bit: 7 }
- name: mode
properties: { address: 101, type: UINT8L }
- name: modbus_register_collector
status: true
properties:
table: relays
interval: 30
register: coil
inputs:
- name: motor1_run
properties: { address: 0 }
- name: motor1_jog
properties: { address: 1, output: BOOL }

Serial Line Through a Gateway​

Reading two drives on one serial line through a serial-to-Ethernet gateway that carries RTU frames, a dataset per unit...

- id: 512400191
name: line-gateway
type: modbus
status: true
properties:
address: "gateway-2.plant.example.com"
port: 4001
transmission_mode: rtu_over_tcp
timeout: 3
workarounds:
pause_after_connect: 500
pause_between_requests: 50
close_connection_after_gather: true
max_word_registers_per_request: 60
definitions:
- name: modbus_register_collector
status: true
properties:
table: drive_1
interval: 15
slave_id: 11
byte_order: CDAB
optimization: max_insert
optimization_max_register_fill: 8
inputs:
- name: speed_rpm
properties: { address: 100, type: INT16 }
- name: torque
properties: { address: 103, type: FLOAT32 }
- name: modbus_register_collector
status: true
properties:
table: drive_2
interval: 15
slave_id: 12
byte_order: CDAB
optimization: max_insert
optimization_max_register_fill: 8
inputs:
- name: speed_rpm
properties: { address: 100, type: INT16 }
- name: torque
properties: { address: 103, type: FLOAT32 }

Reachability Only​

With no dataset assigned, the device only checks every minute that the server accepts a connection, and writes nothing...

- id: 512400192
name: reachability-only
type: modbus
status: true
properties:
address: "192.0.2.41"