Skip to main content

A small, backend-neutral Modbus connection abstraction (pymodbus / tmodbus).

Project description

modbus-connection

A small, backend-neutral Modbus connection abstraction.

The top-level modbus_connection package is a pure interface — the ModbusConnection / ModbusUnit Protocols, the shared WordOrder type, and a tiny exception hierarchy. It imports no Modbus library and no Home Assistant, so consumers can type against it without committing to a backend.

Two interchangeable backends implement that interface:

Backend Module Extra
pymodbus modbus_connection.pymodbus [pymodbus]
tmodbus modbus_connection.tmodbus [tmodbus]

The bare install pulls neither backend.

Why

One physical Modbus link addresses many units (1–247). Sharing a single, internally-serialized connection across many consumers is strictly better than each opening a competing socket. This package is the connection abstraction that makes that sharing possible while keeping the backend swappable: the Protocol never changes when the backend does.

Design

  • A connection is transient and owner-held. A backend connect function returns a live, already-connected instance — there is no connect() on the object.
  • Requests are serialized per connection — but by the backend library, not by this wrapper: pymodbus's transaction manager and tmodbus's smart transport each hold a lock for the full request/response cycle, so concurrent unit calls on one connection can't interleave.
  • The connection does not self-reconnect. On a drop it fires on_connection_lost (best-effort) and stops; recreating it is the owner's job.
  • Consumers receive a ModbusUnit (via connection.for_unit(unit_id)), a stateless per-unit handle with no lifecycle methods. Every method raises on failure — it never returns None.
  • The full 19-function-code Modbus surface is exposed, plus typed reads (read_uint16, read_float32, …) that own datatype + word/byte ordering. A backend that cannot implement a code raises NotImplementedError.

Install

pip install "modbus-connection[pymodbus]"   # pymodbus backend
pip install "modbus-connection[tmodbus]"    # tmodbus backend

Use

import asyncio
from modbus_connection.pymodbus import connect_tcp


async def main() -> None:
    conn = await connect_tcp("192.168.1.50", port=502)
    try:
        unit = conn.for_unit(1)
        outside_temp = await unit.read_int16(9)        # raw register, signed
        flow_setpoint = await unit.read_float32(40, word_order="big")
        pump_on = (await unit.read_coils(56, 1))[0]
        print(outside_temp, flow_setpoint, pump_on)
    finally:
        await conn.close()


asyncio.run(main())

Swapping to tmodbus is a one-line import change:

from modbus_connection.tmodbus import connect_tcp

Exceptions

Both backends raise the same neutral types:

  • ModbusError — base class.
  • ModbusConnectionError — link down / not connected / transport failure.
  • ModbusTimeoutError — request sent, no valid response in time.
  • ModbusExceptionError — device returned a Modbus exception response (.exception_code carries the raw code).

Testing

An in-memory mock backend ships as a pytest plugin (auto-registered via an entry point — no conftest wiring). It implements the same Protocols, so code typed against ModbusUnit runs against it unchanged.

async def test_reads_setpoint(mock_modbus_unit):
    mock_modbus_unit.holding[40] = 1234            # single value
    mock_modbus_unit.holding[2] = [0x0001, 0x86A0]  # list -> consecutive registers
    mock_modbus_unit.holding[9] = lambda: 7         # callable -> evaluated per read

    assert await mock_modbus_unit.read_uint16(40) == 1234
    assert await mock_modbus_unit.read_uint32(2) == 100000

Reads resolve against the per-space stores (holding, input, coils, discrete_inputs); writes mutate them and fire on_write callbacks, so a test can react to a write by mocking other registers:

def test_command_sets_ready(mock_modbus_unit):
    def respond(event):
        if event.address == 0:          # a command was written
            mock_modbus_unit.holding[100] = 1   # device flips its "ready" flag
    mock_modbus_unit.on_write(respond)

Fixtures: mock_modbus_connection (a MockModbusConnection) and mock_modbus_unit (its unit 1). MockModbusConnection / MockModbusUnit are also importable from modbus_connection.mock for direct construction.

Develop

uv sync --extra pymodbus
uv run pytest

Formatting/linting is ruff, enforced in CI. Install the commit hook with prek so code is formatted on commit:

uvx prek install          # set up the git hook
uvx prek run --all-files  # format + lint everything now

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

modbus_connection-1.0.0.tar.gz (21.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

modbus_connection-1.0.0-py3-none-any.whl (23.0 kB view details)

Uploaded Python 3

File details

Details for the file modbus_connection-1.0.0.tar.gz.

File metadata

  • Download URL: modbus_connection-1.0.0.tar.gz
  • Upload date:
  • Size: 21.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for modbus_connection-1.0.0.tar.gz
Algorithm Hash digest
SHA256 987b5d9853ed6d41a106ac72fee71377974aff9e70872269798d2c892827d554
MD5 b4dacf987639ede69a976a6a9f15d2fb
BLAKE2b-256 11eaf873eeb33961abcc470dfdf5d8a0999e1cc305e980d1df556cc5b628a889

See more details on using hashes here.

Provenance

The following attestation bundles were made for modbus_connection-1.0.0.tar.gz:

Publisher: publish.yml on home-assistant-libs/modbus-connection

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file modbus_connection-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for modbus_connection-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1661a5cc6a0d4e292d663ec2a187c7a32dd8c4599d9777df9b9e0c512daac242
MD5 cb00091d787c8825a046d42eb7f0cd52
BLAKE2b-256 f59713255d1849a780009776c2d42fffbbec94cf87ff69e0ffd7d479a252f470

See more details on using hashes here.

Provenance

The following attestation bundles were made for modbus_connection-1.0.0-py3-none-any.whl:

Publisher: publish.yml on home-assistant-libs/modbus-connection

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page