Connections and units
The top-level modbus_connection package defines the abstract
ModbusConnection and the ModbusUnit Protocol. It imports no backend.
ModbusConnection
Section titled “ModbusConnection”One physical link to a Modbus network, shared by every unit id on it. Requests are serialized over that link, so two units never interleave frames.
Constructing a connection performs no I/O. Pick a backend and hand it a parameter object:
from modbus_connection import ModbusTcpParamsfrom modbus_connection.tmodbus import ModbusConnection
connection = ModbusConnection(ModbusTcpParams(host="192.168.1.50", port=502))The first request connects on demand. If the link drops, the next request
reconnects. Call connect() only when you need to establish the link eagerly.
Some links stay up but stop responding. A peer can keep the socket open and
stop answering. Some serial-to-network bridges do this. Such a link never drops
on its own. Call disconnect() to recycle it: the link is torn down, and the
next request establishes a fresh one. Unit handles and components keep working
across the recycle.
disconnect() and close() wait briefly for the request in flight, so a
request that is about to answer still delivers its result. The wait is bounded
at half a second.
Only the connection owner should retain this object and call close(). Closing
is permanent: later calls to connect() or unit operations raise
ClientClosedError.
ModbusUnit
Section titled “ModbusUnit”One device on that link. connection.for_unit(unit_id) returns a handle that
carries every read and write operation for that unit id. See
Modbus operations for the full set.
unit = connection.for_unit(1)values = await unit.read_holding_registers(9, 2)The handle is stateless and cheap, so call for_unit whenever you need a unit.
Give consumers a handle and keep the owning connection. A consumer with a
handle can talk to its own unit, and can disconnect() a wedged link, but
cannot close the connection out from under the owner.
Connection parameters
Section titled “Connection parameters”A connection is constructed from one of four frozen, keyword-only dataclasses,
importable from modbus_connection. The parameter object is shared and
backend-neutral. Code that gathers connection details (a config flow, a CLI)
does not need to know which backend will consume them:
from modbus_connection import ( ModbusSerialParams, ModbusTcpParams, ModbusTlsParams, ModbusUdpParams,)
ModbusTcpParams(host="192.168.1.50", port=502) # native Modbus TCPModbusUdpParams(host="192.168.1.50", port=502)ModbusSerialParams(device="/dev/ttyUSB0", framer="ascii", baudrate=9600)ModbusSerialParams(device="socket://192.168.1.50:502") # a serial line over TCPModbusTlsParams(host="192.168.1.50", port=802, verify="/path/to/ca.pem")framer selects the wire framing. Serial accepts rtu or ascii, and UDP
accepts socket (native Modbus), rtu, or ascii. TCP and TLS framing is
fixed. Not every backend carries every framing. See
Choosing a backend.
A serial line reached over the network
Section titled “A serial line reached over the network”RTU and ASCII frame a serial line. A box that puts such a line on the network is either a serial server or a Modbus gateway. Which one it is decides the parameters.
A serial server forwards the line byte for byte. The frames on the network
are the frames on the wire. This is a serial link on a socket transport, so it
is ModbusSerialParams with a URL as the device:
ModbusSerialParams(device="socket://192.168.1.50:8899")ModbusSerialParams(device="rfc2217://192.168.1.50:8899", baudrate=19200)A Modbus gateway terminates Modbus TCP and re-frames to RTU on the serial
side. The network carries native Modbus TCP, so this is ModbusTcpParams with
the default framing:
ModbusTcpParams(host="192.168.1.50", port=502)Both backends accept a URL as the serial device. Set baudrate to the speed
the box runs its line at. The client opens no local port, so the value
configures nothing there, but the client spaces frames by it. RTU separates
frames by 3.5 character times, which is 4 ms at 9600 and 2 ms at 19200. A box
forwarding bytes cannot add that gap, because it does not know where a frame
ends. rfc2217:// also negotiates the line settings with the box.
The reference lists every field and default. Timing is not a parameter. A device asks for the timing it needs through its unit, as described below.
ModbusTlsParams verifies the server certificate against the system trust
store by default. The options:
verify=Falsedisables verification (self-signed devices).verify="/path/to/ca"verifies against a private CA.check_hostname=Falseskips only the hostname check.client_cert/client_key/client_key_passwordenable mutual TLS.sslctxsupplies a ready-madessl.SSLContextthat overrides the other options.
Device requirements
Section titled “Device requirements”A device library receives a ModbusUnit. The library knows the device, so it
asks for the timing the device needs through the unit:
unit.require_timeout(5.0) # slow to answerunit.require_connect_delay(1.0) # needs a moment after the link opensBoth are floors. The connection runs with the largest value asked of it, by the
connection itself or by any unit on it. Pass None to withdraw a requirement.
Raising the timeout drops the link, and the next request opens one that carries
the new value.
Request spacing
Section titled “Request spacing”Some devices need the line quiet around their frames. Set the interval on the unit:
unit.set_message_spacing(0.05)The line then stays quiet for this interval before each request to this unit
and after each of its requests. The interval is measured from the completion of
the previous request on the connection, whichever unit it went to. Requests
between other units do not wait for it. Pass 0 to clear it.
The interval starts when a request completes, not at the last byte on the wire. A late or unsolicited frame does not restart it.
A gap the line needs before any frame, such as RS485 turnaround, belongs to the connection instead. The two combine by waiting for the longer interval.
Continue with Modbus operations to use a unit.