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 use a vendor extension on top of Modbus are not covered.

The modbus driver reads and writes Modbus RTU devices on serial lines, usually RS-485, and Modbus TCP devices, through pymodbus. Coils and discrete inputs map to bools, holding and input registers to numbers, bools and enums. Coil writes use function 5 and register writes function 6, or 16 for 32-bit values. identify() uses Read Device Identification, function 43 / 14, where supported.

Setup

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

RTU needs a USB to RS-485 adapter wired A to A and B to B, termination at both ends of long runs, and on Linux the dialout group.

Parameters

ParameterDefaultMeaning
transportrequiredtcp or rtu. modbus: rtu is shorthand
hostnoneTCP device or gateway address
port502 for TCPTCP port, or the RTU serial device such as /dev/ttyUSB0 or COM3
unit1Unit id, 1 to 247
baud9600RTU only
parityNRTU only. N, E or O
stopbits1RTU only
bytesize8RTU only
timeout1 sResponse deadline
retries1Extra attempts after a timeout
word_orderbigHigh word first for 32-bit values
byte_orderbigByte order in each register
always_write_multiplefalseFunction 16 for single registers too

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 address. Manual addresses 40011 and 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, 0value = raw × scale + offset
bitnoneBit 0 to 15 as a read-only bool
valuesnoneEnum word to raw value, required for enums
on, off1, 0Raw values for a bool on a register

Writes send raw = (value - offset) / scale, rounded for integer formats, and return the value sent, so with scale: 0.1 a write of 42.47 % sends 425 and reads back 42.5 %. Set step to the register resolution so the kernel blocks values that would round. Bit channels are read-only, since read-modify-write races other masters.

Example

openhal/drivers/modbus/examples/sdm120-energy-meter.hal.yaml reads an Eastron SDM120 meter over RTU.

Errors and health

  • A timeout after retries raises TimeoutError with a hint about unit id, wiring, baud rate and parity.
  • A Modbus exception, a NaN float, an unmapped enum value or a value too big for the format raises DriverError.
  • health() reads the first mapped channel. Three failed transactions in a row mark the device disconnected.

Limitations

  • No discovery, strings, 64-bit values or file records.
  • describe() returns only the unit id and identification objects.
  • One master per bus.

Hazard default and safe state

Medium, or high for drives and anything that can injure. safe_state_support is file, so safe_state goes out as ordinary writes while the device responds.

Sources