API reference
Auto-generated from the source. The top-level pymod package re-exports
every public symbol below — from pymod import Holding, AsyncClient, ...
just works.
Clients
- class pymod.AsyncClient(transport, *, unit_id=1, timeout_s=0.5, retry=None)[source]
Bases:
objectAsync Modbus client. Construct via the tcp, rtu, or rtu_over_tcp factories.
- Parameters:
transport (Transport)
unit_id (int)
timeout_s (float)
retry (RetryPolicy | None)
- classmethod tcp(host, port=502, *, unit_id=1, timeout_s=0.5, retry=None, pipeline=True, connect_timeout_s=3.0)[source]
Modbus TCP client. One auto-reconnecting connection per (host, port). Pipelining can be disabled per-PLC.
- classmethod rtu(port, baudrate=9600, *, bytesize=8, parity='N', stopbits=1, unit_id=1, timeout_s=0.5, retry=None, inter_frame_delay_s=None)[source]
Modbus RTU client over a serial port. Internal lock serializes the bus across callers.
- classmethod rtu_over_tcp(host, port, *, unit_id=1, timeout_s=0.5, retry=None, connect_timeout_s=3.0)[source]
RTU framing over a TCP socket — for serial-to-Ethernet gateways.
- async execute(pdu, *, unit_id=None, timeout_s=None, retry=None)[source]
Send a request PDU, return the response PDU.
The retry policy is applied to errors listed in policy.retry_on (default: ModbusTimeoutError, ModbusConnectionError). Other errors — notably ModbusExceptionResponse subclasses — are NOT retried, on the assumption that retrying an illegal address yields the same illegal address.
Per-call overrides default to the values supplied at client construction time.
- Parameters:
pdu (bytes)
unit_id (int | None)
timeout_s (float | None)
retry (RetryPolicy | None)
- Return type:
- async read(items, *, unit_id=None, timeout_s=None, retry=None)[source]
Issue a heterogeneous batch read.
Adjacent same-area ranges are coalesced into the minimum number of Modbus reads; per-FC PDU limits are honored by automatic splitting. Returns a list parallel to items — one item failing does not abort the batch.
- Parameters:
- Return type:
- async write(items, *, unit_id=None, timeout_s=None, retry=None)[source]
Issue a batch write. FC05/06 vs FC15/16 selected per item by value count. Per-item results parallel items.
- Parameters:
items (Sequence[WriteHolding | WriteCoils])
unit_id (int | None)
timeout_s (float | None)
retry (RetryPolicy | None)
- Return type:
- class pymod.Client(async_client, loop)[source]
Bases:
objectSynchronous facade over AsyncClient.
Owns a private daemon-thread event loop. Each sync call schedules the underlying coroutine on that loop and blocks the caller until it completes. The event loop is stopped on close().
- Parameters:
async_client (AsyncClient)
loop (_BackgroundLoop)
- classmethod tcp(host, port=502, *, unit_id=1, timeout_s=0.5, retry=None, pipeline=True, connect_timeout_s=3.0)[source]
- classmethod rtu(port, baudrate=9600, *, bytesize=8, parity='N', stopbits=1, unit_id=1, timeout_s=0.5, retry=None, inter_frame_delay_s=None)[source]
- classmethod rtu_over_tcp(host, port, *, unit_id=1, timeout_s=0.5, retry=None, connect_timeout_s=3.0)[source]
- execute(pdu, *, unit_id=None, timeout_s=None, retry=None)[source]
- Parameters:
pdu (bytes)
unit_id (int | None)
timeout_s (float | None)
retry (RetryPolicy | None)
- Return type:
- write(items, *, unit_id=None, timeout_s=None, retry=None)[source]
- Parameters:
items (Sequence[WriteHolding | WriteCoils])
unit_id (int | None)
timeout_s (float | None)
retry (RetryPolicy | None)
- Return type:
Read items
- class pymod.Holding(start, count, dtype='uint16', word_order='big', byte_order='big', bit_index=None, bit_indices=None, bit_numbering='lsb_first')[source]
Bases:
objectRead from holding registers (FC03), with typed decoding.
- Parameters:
- class pymod.Input(start, count, dtype='uint16', word_order='big', byte_order='big', bit_index=None, bit_indices=None, bit_numbering='lsb_first')[source]
Bases:
objectRead from input registers (FC04), with typed decoding.
- Parameters:
Write items
- class pymod.WriteHolding(start, values, dtype='uint16', word_order='big', byte_order='big')[source]
Bases:
objectWrite holding registers. Planner picks FC06 (single) vs FC16 (multiple).
- Parameters:
- class pymod.WriteCoils(start, values)[source]
Bases:
objectWrite coils. Planner picks FC05 (single) vs FC15 (multiple).
- class pymod.WriteResult(item, ok=True, error=None)[source]
Bases:
objectPer-item write result. Parallel to the input list passed to write().
- Parameters:
item (WriteHolding | WriteCoils)
ok (bool)
error (ModbusError | None)
- item: WriteHolding | WriteCoils
- error: ModbusError | None
Server
Retry policy
- class pymod.RetryPolicy(max_attempts=2, backoff_initial_s=0.05, backoff_factor=2.0, backoff_cap_s=1.0, retry_on=(<class 'pymod.errors.ModbusTimeoutError'>, <class 'pymod.errors.ModbusConnectionError'>))[source]
Bases:
objectPer-call retry policy.
Defaults match the user-stated requirement: 1 retry (= 2 total attempts), transport errors only. Modbus exception responses are NOT retried by default — the same illegal address will fail forever.
- Parameters:
- retry_on: tuple[type[ModbusError], ...]
Exceptions
- exception pymod.ModbusTransportError[source]
Bases:
ModbusErrorThe request did not produce a usable reply on the wire.
- exception pymod.ModbusTimeoutError[source]
Bases:
ModbusTransportErrorNo response within the configured timeout.
- exception pymod.ModbusConnectionError[source]
Bases:
ModbusTransportErrorTCP connection refused/dropped, or serial port unavailable.
- exception pymod.ModbusProtocolError[source]
Bases:
ModbusErrorA response was received but is malformed or out-of-spec.
- exception pymod.ModbusExceptionResponse(message='', code=None)[source]
Bases:
ModbusErrorSlave returned a valid Modbus exception response. Code in code.
- exception pymod.IllegalFunction(message='', code=None)[source]
Bases:
ModbusExceptionResponse
- exception pymod.IllegalDataAddress(message='', code=None)[source]
Bases:
ModbusExceptionResponse
- exception pymod.IllegalDataValue(message='', code=None)[source]
Bases:
ModbusExceptionResponse
- exception pymod.SlaveDeviceFailure(message='', code=None)[source]
Bases:
ModbusExceptionResponse
- exception pymod.SlaveDeviceBusy(message='', code=None)[source]
Bases:
ModbusExceptionResponse
- exception pymod.MemoryParityError(message='', code=None)[source]
Bases:
ModbusExceptionResponse
Bases:
ModbusExceptionResponse
Codec helpers
Lower-level encode/decode functions, useful for custom function-code codecs and integration tests.
Encode/decode int16, uint16, int32, uint32, float32 across registers.
Pure functions. Inputs are 16-bit register values (0..0xFFFF); outputs are typed Python values. word_order and byte_order together select the ABCD / CDAB / BADC / DCBA layout — see codec/_common.py.
- pymod.codec.values.regs_per_item(dtype)[source]
How many 16-bit registers are needed to represent one dtype item.
- pymod.codec.values.decode_registers(registers, dtype, *, word_order='big', byte_order='big')[source]
Decode a register block into a list of typed values.
The block size must be a multiple of regs_per_item(dtype).
- pymod.codec.values.encode_registers(values, dtype, *, word_order='big', byte_order='big')[source]
Encode a list of typed values into a register block.
Length of returned register list = len(values) * regs_per_item(dtype).
Extract bits from a register block.
Algorithm (per ADR 04):
Each register is two bytes, MSB-first per the Modbus spec.
Apply byte_order per-register, then word_order across registers, producing one big-endian byte string of count * 16 bits — equivalently one large unsigned integer.
Index into that integer per bit_numbering. The default is lsb_first: bit 0 is the LSB, equivalent to (value >> bit_index) & 1. msb_first flips so bit 0 is the most-significant bit of the assembled integer (vendors that label bits from the top).
- pymod.codec.bits.extract_bit(registers, bit_index, *, word_order='big', byte_order='big', bit_numbering='lsb_first')[source]
Return a single bit from a register block.
Protocol helpers
The pure-functions PDU layer. Most users never need these directly — they’re exposed for advanced cases (custom FCs, building a non-standard slave, etc.).
Per-function-code PDU codecs (pure, no I/O).
Each standard function code has an encode_* and decode_* pair operating on bytes. Exception responses (FC | 0x80) are detected via detect_exception and surfaced as the appropriate ModbusExceptionResponse subclass.
Custom function codes are supported via register_codec().
- Function codes covered:
FC01 read coils FC02 read discrete inputs FC03 read holding registers FC04 read input registers FC05 write single coil FC06 write single register FC15 write multiple coils FC16 write multiple registers
- pymod.protocol.pdu.detect_exception(pdu, expected_fc)[source]
If pdu is an exception response for expected_fc, return the corresponding ModbusExceptionResponse instance. If it is a normal response for expected_fc, return None. Anything else is a protocol error.
- Parameters:
- Return type:
ModbusExceptionResponse | None
- pymod.protocol.pdu.decode_write_single_coil(pdu)[source]
Returns (address, value). Slaves echo the request as the response.
- class pymod.protocol.pdu.CustomCodec(code, encode, decode)[source]
Bases:
objectCodec for a custom or vendor-specific function code.
encode produces the request PDU bytes (function code byte first). decode is called with the response PDU bytes (function code byte first) and must surface exceptions itself, typically by calling detect_exception(pdu, code).
- pymod.protocol.pdu.register_codec(codec)[source]
Register a codec for a non-standard function code.
Codes 0x01..0x10 are reserved for the standard FCs in this library. Codes with the exception bit (0x80) set are reserved for exception responses.
- Parameters:
codec (CustomCodec)
- Return type:
None
- pymod.protocol.pdu.get_codec(code)[source]
- Parameters:
code (int)
- Return type:
CustomCodec | None
- pymod.protocol.pdu.unregister_codec(code)[source]
Remove a previously registered codec. Mainly for tests.
- Parameters:
code (int)
- Return type:
None
- pymod.protocol.pdu.decode_request_read_coils(pdu)[source]
Returns
(start, count)for a FC01 request PDU.
- pymod.protocol.pdu.decode_request_write_single_coil(pdu)[source]
Returns
(address, value)for a FC05 request PDU.
- pymod.protocol.pdu.decode_request_write_single_register(pdu)[source]
Returns
(address, value)for a FC06 request PDU.
- pymod.protocol.pdu.decode_request_write_multiple_coils(pdu)[source]
Returns
(start, values)for a FC15 request PDU.
- pymod.protocol.pdu.decode_request_write_multiple_registers(pdu)[source]
Returns
(start, values)for a FC16 request PDU.
Logging helpers
Convenience helpers for pymod’s logging.
The library is silent by default — both pymod and pymod.wire start with NullHandler attached, so library users pay nothing unless they opt in.
This module provides a one-line configure() for users who want quick visibility, plus a JsonFormatter for structured output. Power users who already have a logging setup can simply attach their own handlers to the pymod and pymod.wire loggers and skip this module entirely.
Examples
import pymod.logging
pymod.logging.configure(level="INFO") # human
pymod.logging.configure(level="DEBUG", json=True) # JSON
pymod.logging.configure(level="INFO", wire_trace=True) # + hex dumps
- class pymod.logging.JsonFormatter(fmt=None, datefmt=None, style='%', validate=True, *, defaults=None)[source]
Bases:
FormatterOne log record per line, JSON-encoded. Suitable for log aggregators.
Initialize the formatter with specified format strings.
Initialize the formatter either with the specified format string, or a default as described above. Allow for specialized date formatting with the optional datefmt argument. If datefmt is omitted, you get an ISO8601-like (or RFC 3339-like) format.
Use a style parameter of ‘%’, ‘{’ or ‘$’ to specify that you want to use one of %-formatting,
str.format()({}) formatting orstring.Templateformatting in your format string.Changed in version 3.2: Added the
styleparameter.- format(record)[source]
Format the specified record as text.
The record’s attribute dictionary is used as the operand to a string formatting operation which yields the returned string. Before formatting the dictionary, a couple of preparatory steps are carried out. The message attribute of the record is computed using LogRecord.getMessage(). If the formatting string uses the time (as determined by a call to usesTime(), formatTime() is called to format the event time. If there is exception information, it is formatted using formatException() and appended to the message.
- pymod.logging.configure(*, level='INFO', json=False, wire_trace=False, stream=None)[source]
Attach a handler to the pymod logger.
- Parameters:
level (log level for the main pymod logger.)
json (emit structured JSON instead of human-readable lines.)
wire_trace (also enable the pymod.wire logger at DEBUG (hex dumps) – of every PDU sent and received).
stream (destination; defaults to
sys.stderr.)
- Return type:
None