Skip to content

modbus-connection

A small, backend-neutral Modbus connection abstraction — and a typed device-modelling framework on top of it.

modbus-connection is two things in one package:

  1. A connection abstraction: the abstract ModbusConnection class and the ModbusUnit Protocol. Your code types against these, not against a specific Modbus library. Two interchangeable backends implement them: tmodbus and pymodbus.
  2. An optional device-modelling framework (modbus_connection.model). It maps a device’s registers and coils to typed Python attributes. It reads the whole device, or one sub-system, in as few Modbus calls as possible.

The package imports no Modbus library at the top level and nothing from Home Assistant. You can build a device library once and let the consumer choose the backend.

Model a device once. Then construct, update, read, and write it:

import asyncio
from modbus_connection import ModbusTcpParams
from modbus_connection.model import Component, gauge, uint32, coil
from modbus_connection.tmodbus import ModbusConnection
class Meter(Component):
voltage = gauge(0, 0.1, unit="V") # scaled 16-bit register
"""Grid voltage."""
current = gauge(1, 0.1, unit="A")
"""Grid current."""
energy = uint32(2, unit="Wh") # 32-bit over two registers
"""Lifetime energy."""
relay = coil(0, writable=True)
"""Load relay."""
async def main() -> None:
conn = ModbusConnection(ModbusTcpParams(host="192.168.1.50", port=502))
try:
meter = Meter(conn.for_unit(1))
await meter.async_update() # one pooled read per space
print(meter.voltage, meter.current, meter.energy, meter.relay)
await meter.write("relay", True) # write a writable field
finally:
await conn.close()
asyncio.run(main())

ModbusConnection

The link to a Modbus network, shared by every unit on it. It is owner-held: construct it from a backend-neutral parameter object, and only the owner tears it down with close(). The first request connects on demand. A dropped link is re-established.

ModbusUnit

One device on that link, from connection.for_unit(unit_id). It carries the read and write operations for that unit id and holds no state. Hand this, not the connection, to a device library.

The backend serializes requests on one connection, so concurrent unit calls cannot interleave. Many consumers can share one connection instead of each opening a competing socket. This abstraction makes that sharing possible while keeping the backend swappable.

Read more about connections and units →