Skip to content

modbus

tested on simulator

Tier Tier 1
Protocol Modbus RTU, Modbus TCP
Covers energy and power meters, PLCs, variable frequency drives, temperature controllers, solar inverters, industrial sensors and I/O modules
Hazard default medium
Conformance control
Tested with pymodbus server with a simulated fan controller, TCP and RTU framing

Estimated reach. Thousands of models. Modbus has been the default fieldbus for meters, drives and controllers since 1979 and is an open standard maintained by the Modbus Organization. Almost every vendor of energy meters, VFDs, PID controllers and inverters publishes a Modbus register map, and any device with a published map can be described in a device file without new code. Devices that only speak a vendor extension on top of Modbus are not covered.

The modbus driver reads and writes Modbus RTU devices on a serial line (usually RS-485) and Modbus TCP devices on the network, through pymodbus. Each channel's map names a register, its table, its number format and a linear scale, taken from the device's register map.

What it supports

  • All four tables, where coils and discrete inputs map to bool channels and holding and input registers map to number, bool and enum channels.
  • Formats uint16, int16, uint32, int32 and float32, with configurable word order and byte order.
  • Writes to coils (function 5) and holding registers (function 6, or 16 for 32-bit values and for devices that only accept 16).
  • Single bits of a register as read-only bool channels.
  • Read Device Identification (function 43 / 14) for identify() where the device supports it.

Setup

uv tool install --compile-bytecode "openhal-hub[modbus] @ git+https://github.com/sohan-bhat/OpenHAL"

For RTU you need a USB to RS-485 adapter and the bus wired A to A and B to B, with termination at both ends of longer runs. On Linux, add your user to the dialout group to open the port.

Parameters

ParameterDefaultMeaning
transportrequiredtcp or rtu. The shorthand modbus: rtu sets it
hostnoneTCP only. Address of the device or gateway
port502 for TCPTCP port number, or the serial device for RTU (/dev/ttyUSB0, COM3)
unit1Unit id, 1 to 247
baud9600RTU only
parityNRTU only. N, E or O
stopbits1RTU only
bytesize8RTU only
timeout1 sHow long to wait for each response
retries1Extra attempts after a timeout
word_orderbigDefault order of the two registers of 32-bit values. big sends the high word first
byte_orderbigDefault byte order inside each register. Only a few devices need little
always_write_multiplefalseUse function 16 even for single registers

Channel maps

read:
  voltage: { unit: V, map: { register: 0, kind: input, format: float32 } }
  temperature: { unit: degC, map: { register: 4, kind: input, format: int16, scale: 0.1 } }
  door_closed: { type: bool, map: { register: 0, kind: discrete } }
  alarm: { type: bool, map: { register: 30, bit: 3 } }
write:
  speed: { unit: percent, min: 0, max: 100, step: 0.1, map: { register: 10, scale: 0.1 } }
  enable: { type: bool, map: { register: 0, kind: coil } }
  mode: { type: enum, values: [stop, run], map: { register: 20, values: { stop: 0, run: 1 } } }
KeyDefaultMeaning
registerrequiredZero-based protocol address. Manuals that write 40011 or 30001 mean holding register 10 and input register 0
kindholdingcoil, discrete, holding or input
formatuint16uint16, int16, uint32, int32, float32
word_order, byte_orderfrom the parametersPer-channel override
scale, offset1, 0channel value = raw × scale + offset
bitnoneRead one bit (0 to 15) of a register as a bool. Read channels only
valuesnoneEnum word to raw value, required for enum channels
on, off1, 0Raw values for a bool channel on a register

Writes compute raw = (value - offset) / scale and round integer formats to the nearest whole number. The reading the driver returns shows the value it really sent, so 42.47 % with scale: 0.1 is sent as 425 and reported as 42.5 %. Set step on the channel to the register's resolution and the kernel rejects values that would be rounded.

Because changing one bit needs a read-modify-write that could race with the device or another master, bit channels can't be written.

Example

openhal/drivers/modbus/examples/sdm120-energy-meter.hal.yaml reads an Eastron SDM120 meter over RTU. The file has no write channels, so the file needs no confirmation.

Errors and health

  • No response within timeout, after retries, raises TimeoutError with a hint about the unit id, wiring, baud rate and parity.
  • Modbus exception responses raise DriverError naming the exception, for example illegal data address.
  • A NaN float, an enum raw value outside the map, or a value that doesn't fit the register format raises DriverError.
  • Because a TCP socket or a serial port stays open after the device behind it dies, health() reads the first mapped channel, and three failed transactions in a row also report the device as disconnected.

Limitations

  • Because scanning unit ids would send requests to devices nobody configured, the driver has no discovery.
  • The driver doesn't support strings, 64-bit values or file records.
  • Modbus devices don't describe their registers, so describe() only returns the unit id and any identification objects.
  • The driver doesn't coordinate with other masters, so don't run the driver on a bus with another master.

Hazard default and safe state

The default hazard is medium, because Modbus often drives motors, heaters and valves. Raise the hazard to high for drives and anything that can injure. Modbus has no emergency behavior of its own, so safe_state_support is file, and the hub applies the device file's safe_state through ordinary writes, which work only while the device responds. Wire hardware stops for anything dangerous.

Sources