Skip to main content

ModbusDriver

ModbusDriver is the Modbus transport that register-mapped instrument drivers sit on top of. It is a public part of the library so customers can build their own drivers for Modbus-attached instruments (temperature and process controllers, meters, PLCs) without wrapping pymodbus themselves. It is intentionally narrow: it opens, closes, and locks a Modbus TCP or RTU connection and exposes raw function-code I/O plus typed register encode and decode. The caller owns the register map (which address holds what).
Modbus is a register-and-coil protocol. A driver reads and writes numbered 16-bit registers and single-bit coils by address; there is no self-describing command set. ModbusDriver uses pymodbus under the hood and supports both Modbus TCP and Modbus RTU (serial).

When to reach for it

ModbusDriver addresses Modbus TCP and RTU (serial) devices, exposing raw function-code ops plus a typed codec. Reach for it when the register map is fixed in code, or when you want a standalone client addressing registers by number. For a comparison against the other transports, see Transports. For a config-driven device where the register map lives in a JSON file rather than driver code, use ModbusDevice instead. ModbusDevice composes ModbusDriver and adds semantic access by register alias, scaling, validation, and background polling.

Quickstart

The most common use of ModbusDriver is as a transport inside an instrument driver. Here it is on its own, talking to a Modbus TCP device directly:
Use RTUConnection for serial devices:

Register and data types

Modbus defines four address spaces. ModbusDriver names them with the RegisterType vocabulary, and the typed access path dispatches on it: Values wider than 16 bits span consecutive registers. DataType names the encoding, and register_count() reports the span:

Typed access

read_typed and write_typed handle the multi-register encode and decode, so callers work in native Python types rather than assembling 16-bit words:
Modbus itself does not specify how a multi-register value is ordered, so vendors differ. Three keyword flags cover the common permutations, all defaulting to False (big-endian, high word first):
Four rules the typed path enforces:
  • "input" and "discrete" are read-only. write_typed raises ValueError rather than issuing a doomed request.
  • Single-bit spaces require "bool". Passing any other data_type for "coil" or "discrete" raises ValueError.
  • Coil writes require an actual bool. There is no numeric coercion, so write_typed("coil", addr, 1, "bool") raises rather than silently treating 1 as True.
  • Register count follows from the data type. read_typed reads exactly the span register_count() reports, so callers never pass a count.
register_count, decode_registers, and encode_value are also available as static methods for callers that hold raw registers already and only need the codec.

Atomic multi-step sequences

Hold the transport lock across several ops to keep them atomic, for example selecting a page or bank register and then reading from it:
Note that a transport error inside the block closes the dead socket before re-raising, so the next op after the with reconnects rather than reusing it.

Configuration

ModbusDriver takes either a TCPConnection or an RTUConnection. Both carry the unit_id (the Modbus slave address) and the response timeout, which ModbusDriver exposes through the unit_id property.

TCPConnection

RTUConnection

Method reference

Error handling

Device-side failures carry the standard Modbus exception-code name, so the message identifies the protocol-level cause rather than just reporting a failure:
The pymodbus synchronous client does not reconnect on its own between operations. ModbusDriver closes the dead socket when an op fails with a transport error and re-raises, so the next call establishes a fresh connection. Application code still has to decide whether to retry; the transport does not retry for you.