Skip to main content

anymodbus

Async-native Modbus RTU client for Python, built on AnyIO and anyserial.

CI PyPI version Python versions License: MIT

Overview

anymodbus is a small, opinionated Modbus client (RTU and ASCII serial framing) built on AnyIO and anyserial. It is intentionally protocol-only and narrow in scope. It does not ship servers, TCP transport (planned for v0.3), or device-specific drivers — pymodbus is the right choice if you need any of those, and the two libraries can coexist in one project.

The cases anymodbus is built for:

  • AnyIO-native. Same code runs under asyncio, uvloop, or trio. pymodbus is asyncio-only.
  • Tx-side 3.5-char inter-frame gap. Enforced before sending. pymodbus relies on its concurrency lock plus OS scheduling and does not enforce a pre-tx idle gap.
  • Idempotent-only retries by default. Reads (FC 1-4) retry on transient transport errors; writes (FC 5/6/15/16) do not, unless you opt in. Protects against silent double-writes when a successful write's response is lost in transit.
  • Strict typing. mypy strict = true plus pyright typeCheckingMode = "strict". pymodbus uses partial-strict mypy and standard pyright.
  • Required baudrate and parity. No defaults — mismatched parity silently drops every frame, so making it explicit at the call site is worth the small ergonomic cost.
  • Transport-agnostic. Takes any anyio.abc.ByteStream, defaults to an anyserial.SerialPort. TCP support is planned in v0.3 with the same Bus API.

See docs/migration-from-pymodbus.md for an honest comparison.

Requirements

  • Python 3.13 or 3.14
  • anyio >= 4.13
  • anyserial >= 0.1.1

Installation

uv add anymodbus
# or
pip install anymodbus

Optional extras:

uv add "anymodbus[trio]"    # trio runtime

Usage

Async, one slave

import anyio
from anymodbus import open_modbus_rtu


async def main() -> None:
    async with await open_modbus_rtu("/dev/ttyUSB0", baudrate=19_200, parity="even") as bus:
        slave = bus.slave(address=1)
        regs = await slave.read_holding_registers(0x0040, count=2)
        print(regs)


anyio.run(main)

Sync

from anymodbus.sync import open_modbus_rtu

with open_modbus_rtu("/dev/ttyUSB0", baudrate=19_200, parity="even") as bus:
    slave = bus.slave(1)
    regs = slave.read_holding_registers(0, count=4, timeout=1.0)

Reading a 32-bit float across two registers

Word order varies by device — the Modbus spec doesn't standardize multi-register layout. The default is HIGH_LOW × big-endian-within-word, equivalent to struct.pack(">f", ...); pass word_order="low_high" for devices that store the LSW first.

from anymodbus import WordOrder

async with await open_modbus_rtu("/dev/ttyUSB0", baudrate=19_200, parity="even") as bus:
    slave = bus.slave(address=1)
    high_low_value = await slave.read_float(0x0040)  # default
    low_high_value = await slave.read_float(0x0044, word_order=WordOrder.LOW_HIGH)

Wrapping an existing serial port (with RS-485)

from anyserial import open_serial_port, SerialConfig, RS485Config, Parity
from anymodbus import Bus

port = await open_serial_port(
    "/dev/ttyUSB0",
    SerialConfig(
        baudrate=19_200,
        parity=Parity.EVEN,
        rs485=RS485Config(enabled=True, rts_on_send=True, rts_after_send=False),
    ),
)
async with Bus(port) as bus:
    regs = await bus.slave(1).read_holding_registers(0, count=4)

Modbus ASCII framing and a diagnostic loopback probe

from anymodbus import Framing, RegisterSource, open_modbus_ascii

# Classic 7E1 ASCII wire (data_bits=7); 8 also works.
async with await open_modbus_ascii(
    "/dev/ttyUSB0", baudrate=19_200, parity="even", data_bits=7
) as bus:
    slave = bus.slave(30)
    # Cheap, side-effect-free liveness probe (FC08 sub-0 loopback):
    assert await slave.diagnostic_loopback(b"\xab\xcd") == b"\xab\xcd"
    # Read a measurement published as input registers (FC04):
    o2 = await slave.read_float(0, source=RegisterSource.INPUT)

# Caller-owned stream (e.g. a port shared across modes): pick framing explicitly.
from anymodbus import Bus

bus = Bus(my_byte_stream, framing=Framing.ASCII)

Concurrent fan-out across multiple buses

import anyio
from anymodbus import open_modbus_rtu


async def poll_one(path: str, results: dict[str, tuple[int, ...]]) -> None:
    async with await open_modbus_rtu(path, baudrate=19_200, parity="even") as bus:
        results[path] = await bus.slave(1).read_holding_registers(0, count=4)


async def main() -> None:
    paths = ["/dev/ttyUSB0", "/dev/ttyUSB1", "/dev/ttyUSB2"]
    results: dict[str, tuple[int, ...]] = {}
    async with anyio.create_task_group() as tg:
        for p in paths:
            tg.start_soon(poll_one, p, results)
    for path, regs in results.items():
        print(path, regs)


anyio.run(main)

Testing without hardware

from anymodbus.testing import client_slave_pair

async with client_slave_pair(slave_address=1) as (bus, mock):
    mock.holding_registers[0:4] = [10, 20, 30, 40]
    regs = await bus.slave(1).read_holding_registers(0, count=4)
    assert regs == (10, 20, 30, 40)

Documentation

Full documentation will live at https://anymodbus.graysonbellamy.dev/. Starting points:

Contributing

Issues and PRs are welcome. To get a local checkout running:

git clone https://github.com/GraysonBellamy/anymodbus
cd anymodbus
uv sync --all-extras
uv run pre-commit install

Before opening a PR:

uv run pytest
uv run ruff check
uv run ruff format --check
uv run mypy
uv run pyright

Hardware-dependent tests are opt-in via pytest -m hardware with ANYMODBUS_TEST_PORT and ANYMODBUS_TEST_SLAVE_ADDRESS set.

License

MIT. See LICENSE.

Metadata

Release files for anymodbus 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for anymodbus 0.2.1
File Size Uploaded
anymodbus-0.2.1.tar.gz 113.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for anymodbus 0.2.1
File Interpreter ABI Platform
anymodbus-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 180.9 kB

Release files / anymodbus-0.2.1.tar.gz

Download URL anymodbus-0.2.1.tar.gz
Size 113.2 kB
Tags Source
SHA-256 checksum
How to use checksums
5c2ad75d98d43aea2d8cc8cbb7da88697a0b0ff0c4e47c18388394835138c247
BLAKE2b-256 checksum
How to use checksums
5c512f498c4c848a22bbe4b6ea6599ae4285c7e8241d957dac17c01acb299ebd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.

Transparency log

Release files / anymodbus-0.2.1-py3-none-any.whl

Download URL anymodbus-0.2.1-py3-none-any.whl
Size 67.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d4ede5257bba8737fe2a12f9c9c649f0b6658433c424e8c260de11447b19f617
BLAKE2b-256 checksum
How to use checksums
030db555a80c160435ddbb306ac816a289ba9ed60ca8b44861b1c9c116c2b2ce
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 28, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.0

2 release files

This release

0.2.1 This release

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page