OpenRVDAS provides two readers for acquiring data from Modbus devices:

Both readers support the same Modbus function types, register specification format, scan file format, and output modes. They differ only in their transport layer and associated connection parameters.


Modbus Concepts

Modbus is a widely used protocol for reading data from industrial sensors and controllers. A brief primer on the terms used throughout this page:

Slave / Unit ID — Each device on a Modbus bus has a numeric ID (1–247). A single reader can poll multiple slaves.

Function types — Modbus exposes four data tables, each addressed with a different function code:

Function type Data Read/Write
holding_registers 16-bit unsigned integers Read/Write
input_registers 16-bit unsigned integers Read-only
coils single bits (booleans) Read/Write
discrete_inputs single bits (booleans) Read-only

Register address — A zero-based integer identifying a specific register or coil within a slave’s data table.


ModBusTCPReader

ModBusTCPReader connects to a Modbus TCP server over Ethernet/IP. Install its dependency with:

pip install pyModbusTCP

Parameters

Parameter Default Description
host 'localhost' Hostname or IP address of the Modbus TCP server
port 502 TCP port of the Modbus server
registers (required if no scan_file) Register addresses to poll — see Register Specification
slave 1 Modbus slave/unit ID
function 'holding_registers' Modbus function type: 'holding_registers', 'input_registers', 'coils', or 'discrete_inputs'
scan_file None Path to a YAML scan file for multi-slave/multi-function polling — see Scan Files
interval 10 Seconds between consecutive read() calls
sep ' ' Separator between values in text output
encoding 'utf-8' Output encoding; set to None for raw bytes
encoding_errors 'ignore' Encoding error strategy: 'strict', 'replace', 'ignore', or 'backslashreplace'

Python Examples

Single slave, holding registers

from logger.readers.modbus_reader import ModBusTCPReader

# Read holding registers 0–4 and 10–14 from slave 1 every 5 seconds
reader = ModBusTCPReader(
    host='192.168.1.10',
    port=502,
    registers='0:4,10:14',
    slave=1,
    function='holding_registers',
    interval=5,
)

while True:
    records = reader.read()
    for record in records:
        print(record)

Coils

reader = ModBusTCPReader(
    host='192.168.1.10',
    registers='0:7',
    function='coils',
    interval=1,
)

Multi-slave via scan file

reader = ModBusTCPReader(
    host='192.168.1.10',
    scan_file='/opt/openrvdas/local/scan_config.yaml',
    interval=10,
)

Configuration File Examples

Single slave

readers:
  class: ModBusTCPReader
  kwargs:
    host: 192.168.1.10
    port: 502
    registers: "0:4,10:14"
    slave: 1
    function: holding_registers
    interval: 5

Full logger configuration

modbus->net+file:
  name: modbus->net+file
  readers:
    class: ModBusTCPReader
    kwargs:
      host: 192.168.1.10
      port: 502
      scan_file: /opt/openrvdas/local/scan_config.yaml
      interval: 10
  transforms:
    - class: TimestampTransform
    - class: PrefixTransform
      kwargs:
        prefix: modbus1
  writers:
    - class: LogfileWriter
      kwargs:
        filebase: /log/current/modbus1
    - class: UDPWriter
      kwargs:
        port: 6224

ModBusSerialReader

ModBusSerialReader connects to a Modbus RTU device over a serial port. Install its dependency with:

pip install "pymodbus[serial]"

Parameters

Parameter Default Description
port '/dev/ttyUSB0' Serial port device
baudrate 9600 Baud rate
parity 'N' Parity: 'N'one, 'E'ven, or 'O'dd
stopbits 1 Stop bits
bytesize 8 Data bits
timeout None Serial response timeout in seconds (minimum 1s; defaults to 2s)
registers (required if no scan_file) Register addresses to poll — see Register Specification
slave 1 Modbus slave/unit ID
function 'holding_registers' Modbus function type: 'holding_registers', 'input_registers', 'coils', or 'discrete_inputs'
scan_file None Path to a YAML scan file for multi-slave/multi-function polling — see Scan Files
interval 1.0 Seconds between consecutive read() calls (minimum 0.1)
sep ' ' Separator between values in text output
encoding 'utf-8' Output encoding; set to None for raw bytes
encoding_errors 'ignore' Encoding error strategy: 'strict', 'replace', 'ignore', or 'backslashreplace'

ModBusSerialReader also implements exponential backoff for serial connection retries, starting at the timeout value and growing up to 30 seconds between attempts.

Python Examples

Single slave, holding registers

from logger.readers.modbus_serial_reader import ModBusSerialReader

reader = ModBusSerialReader(
    port='/dev/ttyUSB0',
    baudrate=19200,
    registers='0:5,10:15',
    slave=1,
    function='holding_registers',
    interval=5,
)

while True:
    records = reader.read()
    for record in records:
        print(record)

Input registers with custom timeout

reader = ModBusSerialReader(
    port='/dev/ttyUSB0',
    baudrate=9600,
    timeout=3,
    registers='0:9',
    function='input_registers',
    interval=2,
)

Multi-slave via scan file

reader = ModBusSerialReader(
    port='/dev/ttyUSB0',
    baudrate=19200,
    scan_file='/opt/openrvdas/local/scan_config.yaml',
    interval=5,
)

Configuration File Examples

Single slave

readers:
  class: ModBusSerialReader
  kwargs:
    port: /dev/ttyUSB0
    baudrate: 19200
    registers: "0:5,10:15"
    slave: 1
    function: holding_registers
    interval: 5

Full logger configuration

modbus-serial->net+file:
  name: modbus-serial->net+file
  readers:
    class: ModBusSerialReader
    kwargs:
      port: /dev/ttyUSB0
      baudrate: 19200
      scan_file: /opt/openrvdas/local/scan_config.yaml
      interval: 5
      timeout: 3
  transforms:
    - class: TimestampTransform
    - class: PrefixTransform
      kwargs:
        prefix: modbus_serial1
  writers:
    - class: LogfileWriter
      kwargs:
        filebase: /log/current/modbus_serial1
    - class: UDPWriter
      kwargs:
        port: 6224

Register Specification

Both readers accept register addresses as a string or a list of tuples.

String format

A comma-separated list of individual addresses or ranges:

Spec Meaning
"5" Single register at address 5
"0:9" Registers 0 through 9 (10 registers)
"0:4,10:14" Registers 0–4 and registers 10–14
registers: "0:9"
registers: "0:4,10:14"
registers: "5"

List of tuples

Each tuple is (start_address, count):

registers = [(0, 5), (10, 5)]   # same as "0:4,10:14"
registers = [(0, 10)]           # same as "0:9"

This form is only available when instantiating the reader in Python, not in YAML configuration files.


Scan Files

Both readers accept a scan_file parameter pointing to a YAML file that specifies multiple slaves, function types, and register blocks in a single configuration. When scan_file is provided the registers, slave, and function parameters are ignored.

Format

polls:
  - slave: 1
    function: holding_registers
    registers: "0:9"
  - slave: 1
    function: input_registers
    registers: "0:4"
  - slave: 2
    function: holding_registers
    registers: "0:4,10:14"
  - slave: 3
    function: coils
    registers: "0:7"

Each entry in polls requires:

Key Description
slave Modbus slave/unit ID
registers Register specification string
function (optional) Modbus function type; defaults to holding_registers

read() returns a list with one entry per poll, in the order they appear in the file. If a poll fails, its entry in the list is None; other polls are unaffected.


Output Format

Both readers return a list from read(), with one element per configured poll (one poll for single-slave use, one per entry in a scan file).

Text mode (default)

When encoding is set (default 'utf-8'), each element is a string of space-separated values (or the separator specified by sep), prefixed with the slave ID:

slave 1: 1234 5678 910 1112
slave 2: 0 1 0 1 0 0 1 1

Failed polls produce None in the list rather than raising an exception.

Binary mode

When encoding=None, each element is a bytes object:

  • Registers — each value packed as a 16-bit unsigned big-endian integer.
  • Coils / discrete inputs — bits packed LSB-first into bytes (Modbus spec).
reader = ModBusTCPReader(
    host='192.168.1.10',
    registers='0:3',
    encoding=None,      # return raw bytes
)
records = reader.read()  # e.g. [b'\x04\xd2\x16\x2e\x00\x00\x00\x00']

See Also

Updated: