Modbus Connection reference
The complete API of the connection layer. Everything here is importable from the
top-level modbus_connection package unless stated otherwise.
ModbusConnection
Section titled “ModbusConnection”The abstract connection base class (modbus_connection.ModbusConnection). Each
backend module exports a concrete subclass under the same name:
modbus_connection.tmodbus.ModbusConnection and
modbus_connection.pymodbus.ModbusConnection. The constructor and API are
identical, so selecting a backend changes only the import.
ModbusConnection(params, *, timeout=None, message_spacing=None, connect_delay=None)| Parameter | Type | Meaning |
|---|---|---|
params |
ModbusTcpParams | ModbusUdpParams | ModbusTlsParams | ModbusSerialParams |
The transport to connect over. See the parameter dataclasses. |
timeout |
float | None, default None |
Per-request timeout in seconds. |
message_spacing |
float | None, default None |
Connection-wide minimum interval, in seconds, from the completion of one request to the start of the next. 0 disables spacing. Raises ValueError if negative. |
connect_delay |
float | None, default None |
Pause, in seconds, after the link is established before it is used. For devices that need a moment after connecting before they answer reliably. Concurrent connectors share one pause. |
Each tuning value is optional. A value given here joins the resolution with every unit requirement, and the largest wins. With nothing asked at all the connection uses a 10 second timeout, no message spacing and no connect delay.
Constructing a connection performs no I/O. The first unit operation connects on demand. See Connections and units for the ownership and lifecycle model.
Properties and methods
Section titled “Properties and methods”connected
Section titled “connected”bool. Whether the link is currently established. False before the first
request, after a drop, and after close().
connect()
Section titled “connect()”async. Establish the connection eagerly. A no-op if already connected.
Concurrent callers share a single in-flight connect attempt. Raises
ModbusConnectionError if the connection fails and
ClientClosedError if the connection was closed. You
rarely need it, because every unit operation connects first.
for_unit(unit_id)
Section titled “for_unit(unit_id)”Return this backend’s stateless ModbusUnit handle bound to
unit_id. Handles are cheap. Consumers receive a handle and never the
connection.
on_connection_lost(callback)
Section titled “on_connection_lost(callback)”Register a Callable[[], None] fired when the link drops. Returns an
unsubscribe callable. A connection is lost when the transport takes it away.
close() and disconnect() are the owner tearing it down, so neither fires
the callbacks.
disconnect()
Section titled “disconnect()”async. Drop the link. The next request establishes a new one. Use it to
recycle a link that is up but unusable, such as a peer that keeps the socket
open but stops answering. Unlike close(), the connection stays usable:
existing unit handles and components reconnect on their next request. A no-op
when there is no link. Waits up to half a second for the request in flight. A
request still running after that is cut and fails. Raises
ModbusConnectionError if tearing the old link down
fails. The link is dropped regardless.
close()
Section titled “close()”async. Close the connection permanently. After close(), connect() and
every unit operation raise ClientClosedError. Construct
a new connection to reconnect. The connection is marked closed first, then the
call waits up to half a second for the request in flight.
Parameter dataclasses
Section titled “Parameter dataclasses”All four are frozen, keyword-only dataclasses importable from
modbus_connection. See
Connection parameters
for usage guidance.
ModbusTcpParams
Section titled “ModbusTcpParams”| Field | Type | Default | Meaning |
|---|---|---|---|
host |
str |
required | Host name or IP address of the device. |
port |
int |
502 |
TCP port. |
framer |
"socket" | "rtu" | "ascii" | None |
None |
Deprecated; omit it. Passing any value warns, and any value but these three raises ValueError. "rtu" and "ascii" frame a serial line; use ModbusSerialParams with a socket:// device instead. "socket" is the only framing a Modbus TCP link has. Omitted, it reads back as "socket". |
ModbusUdpParams
Section titled “ModbusUdpParams”| Field | Type | Default | Meaning |
|---|---|---|---|
host |
str |
required | Host name or IP address of the device. |
port |
int |
502 |
UDP port. |
framer |
"socket" | "rtu" | "ascii" |
"socket" |
Wire framing. Any other value raises ValueError. |
ModbusTlsParams
Section titled “ModbusTlsParams”| Field | Type | Default | Meaning |
|---|---|---|---|
host |
str |
required | Host name or IP address of the device. |
port |
int |
802 |
TLS port. |
verify |
bool | str |
True |
True verifies the server certificate against the system trust store; a path verifies against a private CA file or directory; False disables verification. |
check_hostname |
bool |
True |
Whether to verify the certificate hostname. |
client_cert |
str | None |
None |
Path to the client certificate. |
client_key |
str | None |
None |
Path to the private key belonging to client_cert. |
client_key_password |
str | None |
None |
Password for client_key, if it is encrypted. |
sslctx |
ssl.SSLContext | None |
None |
TLS context overriding the other TLS options. |
create_ssl_context()
Section titled “create_ssl_context()”async. Return the supplied sslctx or build an ssl.SSLContext from the
other parameters. The backends call this for you when connecting.
ModbusSerialParams
Section titled “ModbusSerialParams”| Field | Type | Default | Meaning |
|---|---|---|---|
device |
str |
required | Serial port device path (e.g. /dev/ttyUSB0), or a URL: socket://host:port, rfc2217://host:port. |
baudrate |
int |
9600 |
Line speed in baud. |
bytesize |
7 | 8 |
8 |
Data bits per character. |
parity |
"N" | "E" | "O" |
"N" |
Parity: none, even, or odd. |
stopbits |
1 | 2 |
1 |
Stop bits per character. |
framer |
"rtu" | "ascii" |
"rtu" |
Serial framing. Any other value raises ValueError. |
endpoint
Section titled “endpoint”Every parameter dataclass has an endpoint property: a hashable tuple that
identifies the physical target the params point at, excluding link settings.
Two params objects with equal endpoints address the same device. The property
also works as a dictionary key for grouping shared connections:
| Class | Endpoint | Excluded settings |
|---|---|---|
ModbusTcpParams |
("tcp", host, port) |
none |
ModbusTcpParams, deprecated rtu or ascii framing |
("serial", f"socket://{host}:{port}") |
none |
ModbusUdpParams |
("udp", host, port) |
framer |
ModbusTlsParams |
("tcp", host, port) |
all TLS options |
ModbusSerialParams |
("serial", device) |
baudrate, bytesize, parity, stopbits, framer |
ModbusTlsParams shares the "tcp" transport tag on purpose. A TLS link and
a plain-TCP link to the same host and port target the same TCP endpoint, and
therefore the same device.
A deprecated serial framing over TCP
is the one case where framer changes the endpoint. Such a link is a serial
line, so it takes the serial endpoint, the same one ModbusSerialParams
gives for it. A gateway answering native Modbus TCP at that address is a
different service, and keeps the "tcp" endpoint.
A host is folded to lower case on construction, since DNS names and IPv6 hex
digits are case-insensitive. The serial device path is compared verbatim.
Aliases of the same port (a /dev/serial/by-id symlink versus
/dev/ttyUSB0) are not resolved.
Equal endpoints with unequal params signal conflicting configurations for one
device, for example two serial configs for /dev/ttyUSB0 at different baud
rates. A connection manager can detect and reject that:
if new_params.endpoint == existing_params.endpoint and new_params != existing_params: raise ValueError("conflicting settings for the same device")ModbusUnit
Section titled “ModbusUnit”A runtime-checkable Protocol representing one unit on a shared connection.
Obtain one from connection.for_unit(unit_id). The
mock unit and any other object with
these methods satisfies it too.
Every operation is async, connects on demand, and raises a subclass of
ModbusError on failure.
Register I/O
Section titled “Register I/O”| Method | Function code | Returns |
|---|---|---|
read_holding_registers(address, count) |
3 (0x03) | list[int] |
read_input_registers(address, count) |
4 (0x04) | list[int] |
write_register(address, value) |
6 (0x06) | None |
write_registers(address, values) |
16 (0x10) | None |
Coil and discrete-input I/O
Section titled “Coil and discrete-input I/O”| Method | Function code | Returns |
|---|---|---|
read_coils(address, count) |
1 (0x01) | list[bool] |
read_discrete_inputs(address, count) |
2 (0x02) | list[bool] |
write_coil(address, value) |
5 (0x05) | None |
write_coils(address, values) |
15 (0x0F) | None |
Diagnostic, file-record, and identification operations
Section titled “Diagnostic, file-record, and identification operations”| Method | Function code | Returns |
|---|---|---|
read_exception_status() |
7 (0x07) | int |
diagnostics(sub_function, data=0) |
8 (0x08) | int |
get_comm_event_counter() |
11 (0x0B) | tuple[bool, int]: ready flag, event count |
get_comm_event_log() |
12 (0x0C) | bytes |
report_server_id() |
17 (0x11) | bytes |
read_file_record(file, record, length) |
20 (0x14) | list[int] |
write_file_record(file, record, values) |
21 (0x15) | None |
mask_write_register(address, and_mask, or_mask) |
22 (0x16) | None |
read_write_registers(read_address, read_count, write_address, write_values) |
23 (0x17) | list[int] |
read_fifo_queue(address) |
24 (0x18) | list[int] |
read_device_identification() |
43 / 14 (0x2B / 0x0E) | dict[int, bytes] |
diagnostics() sends any sub-function code with one data word, and returns the
data word the device answers with. get_comm_event_counter() returns whether
the device is ready and its event count. The device is not ready while it is
still processing a program function.
Properties and non-I/O methods
Section titled “Properties and non-I/O methods”connected
Section titled “connected”bool. Whether the owning connection’s link is currently established.
set_message_spacing(seconds)
Section titled “set_message_spacing(seconds)”Keep the line quiet for seconds 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, to any unit. The setting belongs to the unit
ID and combines with connection-wide spacing by waiting for the longer interval.
Pass 0 to clear it. Raises ValueError if seconds is negative.
See Request spacing.
require_timeout(seconds)
Section titled “require_timeout(seconds)”Ask the link for a per-request timeout of at least seconds. The connection
runs with the largest value asked of it, so this never shortens another unit’s
timeout. Raising it above what a live link carries drops that link. Pass None
to withdraw the requirement. Raises ValueError if seconds is negative. See
Device requirements.
require_connect_delay(seconds)
Section titled “require_connect_delay(seconds)”Ask the link for a pause of at least seconds after it opens, resolved the same
way. It applies to the next connect. Pass None to withdraw the requirement.
Raises ValueError if seconds is negative.
on_connection_lost(callback)
Section titled “on_connection_lost(callback)”Register a callback fired when the connection’s link drops. Returns an unsubscribe callable. Equivalent to registering on the owning connection.
disconnect()
Section titled “disconnect()”async. Drop the owning connection’s link. The next request establishes a
new one. Equivalent to disconnect() on the owning connection,
for holders of a unit that do not hold the connection itself.
Encoding and decoding functions
Section titled “Encoding and decoding functions”Converters between register words and Python values. The modelling fields use them internally, and they are available for direct use. See Decoding what you read for examples.
WordOrder
Section titled “WordOrder”Literal["big", "little"], importable from modbus_connection. The order of
16-bit registers within a multi-register value. "big" (the common Modbus
convention) puts the most-significant word first. "little" puts the
least-significant word first.
modbus_connection.decode
Section titled “modbus_connection.decode”All decoders take words: list[int]; the multi-word numeric ones also take a
word_order keyword (default "big").
| Function | Registers | Returns |
|---|---|---|
decode_uint16(words) |
1 | int (unsigned) |
decode_int16(words) |
1 | int (signed) |
decode_uint32(words, *, word_order="big") |
2 | int |
decode_int32(words, *, word_order="big") |
2 | int |
decode_uint64(words, *, word_order="big") |
4 | int |
decode_int64(words, *, word_order="big") |
4 | int |
decode_int(words, *, signed, word_order="big") |
any | int of any width |
decode_float32(words, *, word_order="big") |
2 | float (IEEE-754 single) |
decode_float64(words, *, word_order="big") |
4 | float (IEEE-754 double) |
decode_string(words) |
any | str: null-padded ASCII, two characters per word |
decode_ipaddr(words) |
2 | ipaddress.IPv4Address |
decode_ipv6addr(words) |
8 | ipaddress.IPv6Address |
decode_eui48(words) |
3 | str: colon-separated EUI-48 / MAC address |
combine_words(words, *, word_order="big") |
any | int: the raw unsigned value |
modbus_connection.encode
Section titled “modbus_connection.encode”All encoders return list[int] register words. The integer encoders raise
OverflowError if the value does not fit the width.
| Function | Registers | Encodes |
|---|---|---|
encode_uint16(value) |
1 | an unsigned/signed 16-bit integer |
encode_int16(value) |
1 | a signed 16-bit integer |
encode_uint32(value, *, word_order="big") |
2 | a 32-bit integer |
encode_int32(value, *, word_order="big") |
2 | a signed 32-bit integer |
encode_uint64(value, *, word_order="big") |
4 | a 64-bit integer |
encode_int64(value, *, word_order="big") |
4 | a signed 64-bit integer |
encode_int(value, *, count, word_order="big") |
count |
an integer of any width |
encode_float32(value, *, word_order="big") |
2 | an IEEE-754 single-precision float |
encode_float64(value, *, word_order="big") |
4 | an IEEE-754 double-precision float |
encode_string(value, *, length) |
length |
an ASCII string, null-padded (two characters per word) |
split_words(raw, *, count, word_order="big") |
count |
an unsigned integer into raw words |
Exceptions
Section titled “Exceptions”Both backends map their errors onto the same neutral hierarchy.
except ModbusError catches everything, whichever backend produced the error.
Import the classes from the top-level package:
ModbusError├── ModbusConnectionError│ └── ClientClosedError (request on a close()d connection)├── ModbusTimeoutError (also a builtin TimeoutError)├── ModbusProtocolError│ └── ModbusDesyncError (reply answers a different exchange)└── ModbusExceptionError (.exception_code; .block set when a │ component update aborted) ├── IllegalFunctionError (code 1) ├── IllegalDataAddressError (code 2) ├── IllegalDataValueError (code 3) ├── ServerDeviceFailureError (code 4) ├── AcknowledgeError (code 5) ├── ServerDeviceBusyError (code 6) ├── MemoryParityError (code 8) ├── GatewayPathUnavailableError (code 10) └── GatewayTargetError (code 11)ModbusError
Section titled “ModbusError”The base class. Catch it to handle any Modbus failure uniformly:
try: values = await unit.read_holding_registers(0, 10)except ModbusError as err: log.warning("read failed: %s", err)ModbusConnectionError
Section titled “ModbusConnectionError”The link is down, not connected, or the transport failed. The connection is not discarded: the next request attempts to establish it again.
ClientClosedError
Section titled “ClientClosedError”A request or connect() was attempted on a connection after its owner called
close(). A closed connection never reconnects; the owner must construct a new
one.
ModbusTimeoutError
Section titled “ModbusTimeoutError”An operation timed out: a request got no valid response in time, or a connect
attempt did not complete in time. It also subclasses the builtin TimeoutError,
so except TimeoutError catches it too:
try: await unit.read_holding_registers(0, 1)except TimeoutError: # catches ModbusTimeoutError ...ModbusProtocolError
Section titled “ModbusProtocolError”A reply arrived but could not be used. Either the frame was corrupt (bad CRC/LRC, framing), or a well-formed reply answered a different request than the one sent. The latter is the signature of a bridge shared by several simultaneous clients.
ModbusDesyncError
Section titled “ModbusDesyncError”The reply answers a different exchange than the one in flight: a mismatched
header, or the wrong function code. Retrying would read the same offset. The
connection therefore disconnect()s before this raises, and the
next request opens a fresh link.
ModbusExceptionError
Section titled “ModbusExceptionError”The device returned a Modbus exception response: it understood the request but refused it. A code with a standard meaning raises the matching subclass, so callers can branch without magic numbers:
try: await unit.write_register(40, 99)except IllegalDataValueError: ... # the device rejected the valueexcept GatewayTargetError: ... # the bridge is fine; the device behind it is not answering| Subclass | Code | Meaning |
|---|---|---|
IllegalFunctionError |
1 | The device does not support the function. |
IllegalDataAddressError |
2 | The device does not serve the address. |
IllegalDataValueError |
3 | The device rejected a value in the request. |
ServerDeviceFailureError |
4 | The device failed performing the request. |
AcknowledgeError |
5 | Accepted, but the device needs time to process. |
ServerDeviceBusyError |
6 | The device is busy; retry later. |
MemoryParityError |
8 | Parity error in the device’s memory. |
GatewayPathUnavailableError |
10 | The gateway has no path to the target. |
GatewayTargetError |
11 | The gateway’s target device did not respond. |
.exception_code carries the code as an ExceptionCode IntEnum member when
it is a standard one, and a plain int otherwise. Existing
err.exception_code == 2 comparisons keep working. An unknown code raises the
base ModbusExceptionError. Each subclass constructs with its code implied, as
in IllegalDataAddressError(). This is useful for
arming the mock.
.block says where the refusal happened. For an exception response that
aborted a component update,
it is the refused ReadBlock(space, address, count). For a raw unit request it
is None.
BlockReadError (deprecated)
Section titled “BlockReadError (deprecated)”A deprecated alias of ModbusExceptionError. It is kept so existing
except BlockReadError handlers keep catching an aborted component update, and
so .space / .address / .count still read (from .block; None on a raw
request error). New code should catch the typed class and read .block.
Backend modules
Section titled “Backend modules”modbus_connection.tmodbus and modbus_connection.pymodbus each export:
ModbusConnectionis the concrete connection class described above. Constructing one with parameters the backend does not support raisesValueError(see Choosing a backend).TmodbusConnection/PymodbusConnectionandTmodbusUnit/PymodbusUnitare the old backend-specific names, kept for compatibility. New code importsModbusConnectionand types against the abstractmodbus_connection.ModbusConnectionand theModbusUnitProtocol.- The legacy factories
connect_tcp,connect_udp,connect_tls, andconnect_serialare kept for compatibility. Each builds the matching parameter dataclass from keyword arguments, also accepts the constructor’stimeout,message_spacing, andconnect_delay, constructs aModbusConnection, eagerlyconnect()s it, and returns it. New code constructsModbusConnectionwith a shared parameter object instead.