Skip to main content

Python library for Watlow temperature controllers (Standard Bus and Modbus RTU over serial).

Project description

watlowlib

Async-first Python driver for Watlow temperature controllers over RS-232 / EIA-485. Speaks both wire protocols Watlow controllers expose — Standard Bus (BACnet MS/TP outer framing + a small Watlow attribute service) and Modbus RTU (via the in-house anymodbus) — behind a single semantic Controller API that decodes to the same typed Reading either way.

Built as a sibling to alicatlib and sartoriuslib: the same async core, sync facade, multi-device manager, fake transport, acquisition helpers, and pluggable sinks.

Status: alpha. Both wire protocols, the Controller facade, WatlowManager, streaming, all sinks, the sync facade, and every CLI are implemented and covered by 466 tests across asyncio, asyncio+uvloop, and trio. The core has been exercised against a live EZ-ZONE PM3 (Standard Bus); the Modbus RTU side has full codec / client / integration coverage but limited bench mileage. Expect API churn until 1.0. See docs/design.md for the architectural reference.

Goals

  • One protocol-neutral API. Controller.read_pv(), read_setpoint(), set_setpoint(), read_parameter() — the same calls work over Standard Bus and Modbus RTU, decoding to identical Reading / LoopState / DeviceInfo models.
  • Auto-detect. open_device(..., protocol=ProtocolKind.AUTO) does Standard Bus probe → Modbus RTU probe → fail clearly. Read-only by construction; never sweeps opcodes or guesses bauds.
  • Cross-protocol parameter registry. The EZ-ZONE register list carries both the Standard Bus selector (class/member/instance) and the Modbus register addresses for every parameter — so read_parameter("setpoint") lowers to either protocol from one shared registry.
  • Typed end to end. TemperatureUnit.F, Capability.MULTI_LOOP, frozen-dataclass responses, py.typed, mypy --strict clean.
  • Typed errors. WatlowError root with structured ErrorContext; every Standard Bus error code (0x81 / 0x83 / 0x84) and every Modbus exception (0x010x0B) maps to a distinct exception.
  • Safety gates. PERSISTENT (RWE) and DANGEROUS (RWES / calibration / baud-change) operations require confirm=True.
  • Multi-device. WatlowManager runs many controllers concurrently — same-port requests serialize, different ports run in parallel.
  • Acquisition built in. record(...) drives one or many devices on an absolute-target cadence into pluggable sinks: InMemorySink, CsvSink, JsonlSink, SqliteSink in core; ParquetSink and PostgresSink behind extras.
  • Swappable transports. SerialTransport for hardware, FakeTransport for tests, fixture-backed transports for regression goldens.
  • Sync or async. Async core on anyio; complete sync facade at watlowlib.sync via a blocking portal — every async method has a sync parity.
  • CLI tooling. watlow-read, watlow-discover, watlow-raw, watlow-decode, watlow-configure, and the watlow-diag reverse-engineering namespace.
  • Lean core. pip install watlowlib pulls in anyio, anyserial, and anymodbus — nothing else.

Install

pip install watlowlib

# optional sinks
pip install 'watlowlib[parquet]'   # ParquetSink (pyarrow)
pip install 'watlowlib[postgres]'  # PostgresSink (asyncpg)

Requires Python 3.13+. Linux, macOS, BSD, and Windows are supported via anyserial. On Linux, the user running watlow-* needs read/write access to the serial device — usually by joining the dialout group.

Quickstart (async)

import anyio
from watlowlib import open_device, ProtocolKind

async def main() -> None:
    async with await open_device(
        "/dev/ttyUSB0",
        protocol=ProtocolKind.AUTO,
        address=1,
    ) as ctl:
        info = await ctl.identify()
        print(info.model, info.part_number, info.firmware)
        pv = await ctl.read_pv()
        print(pv.value, pv.unit)
        await ctl.set_setpoint(75.0, confirm=True)

anyio.run(main)

Older Watlow controllers (Series 96, F4, early EZ-ZONE) ship from the factory in Standard Bus; on those models Modbus RTU is the front-panel-opt-in mode. Newer EZ-ZONE firmware can ship in either mode depending on configuration. The ProtocolKind.AUTO detector tries Standard Bus first then Modbus RTU.

Quickstart (sync)

from watlowlib.sync import Watlow

with Watlow.open("/dev/ttyUSB0", address=1) as ctl:
    print(ctl.read_pv())
    ctl.set_setpoint(75.0, confirm=True)

Multi-device acquisition

import anyio
from watlowlib import WatlowManager
from watlowlib.streaming import record
from watlowlib.sinks import CsvSink, pipe

async def main() -> None:
    async with WatlowManager() as mgr:
        await mgr.add("oven", "/dev/ttyUSB0", address=1)
        await mgr.add("furnace", "/dev/ttyUSB0", address=2)
        async with (
            record(mgr, rate_hz=2, duration=3600) as stream,
            CsvSink("run.csv") as sink,
        ):
            await pipe(stream, sink)

anyio.run(main)

The recorder runs on an absolute target cadence (drift-free), batches samples per tick, and reports send/receive timing on every Sample.

Command-line tools

watlow-discover /dev/ttyUSB0                       # probe + identify
watlow-read /dev/ttyUSB0 --protocol auto           # one decoded poll
watlow-decode --stdbus 55 FF 06 00 10 ...          # offline frame decode
watlow-raw /dev/ttyUSB0 --param 4001 --confirm     # raw escape hatch
watlow-configure switch-protocol /dev/ttyUSB0 --confirm
watlow-diag snapshot /dev/ttyUSB0 --out diag.json  # reverse-engineering aids

All CLIs accept --fixture FILE to drive a scripted FakeTransport, so end-to-end tests and demos work without hardware.

Documentation

Full docs live at https://GraysonBellamy.github.io/watlowlib/. Useful entry points:

Development

uv for env and lock management, hatchling + hatch-vcs for builds, ruff for format and lint, mypy --strict and pyright for types, AnyIO's pytest plugin for the test suite (parametrised across asyncio, asyncio+uvloop, and trio).

uv sync --all-extras --dev
uv run pre-commit install
uv run pytest
uv run ruff format --check .
uv run ruff check .
uv run mypy

Hardware tests are gated behind WATLOWLIB_ENABLE_STATEFUL_TESTS=1 and WATLOWLIB_ENABLE_DESTRUCTIVE_TESTS=1 and require a connected controller.

See CONTRIBUTING.md for the workflow and SECURITY.md for the disclosure policy.

License

MIT. See LICENSE.

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

watlowlib-0.1.0.tar.gz (334.6 kB view details)

Uploaded Source

Built Distribution

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

watlowlib-0.1.0-py3-none-any.whl (302.1 kB view details)

Uploaded Python 3

File details

Details for the file watlowlib-0.1.0.tar.gz.

File metadata

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

File hashes

Hashes for watlowlib-0.1.0.tar.gz
Algorithm Hash digest
SHA256 a75f060d695c188676928cad908b4433d939b4ea33e087f02de8e45d1bec1f88
MD5 be2948907302b04b130a27b084062abd
BLAKE2b-256 de2a37c2a05ec9159e2aea55211519ee2536e27fd028e80ed2c4fb3b6446418f

See more details on using hashes here.

Provenance

The following attestation bundles were made for watlowlib-0.1.0.tar.gz:

Publisher: release.yml on GraysonBellamy/watlowlib

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

File details

Details for the file watlowlib-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: watlowlib-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 302.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for watlowlib-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 98db819328e44c873ec3d045f308be342a0f864c99a5fcdf71b6d538abfa957d
MD5 c0e6a152dd16b2d8d76968b1916ee441
BLAKE2b-256 d139fc930818e6abf0d1f74ac00e10da274d6e5b6e9f1e8a0ce66f98470d0d4d

See more details on using hashes here.

Provenance

The following attestation bundles were made for watlowlib-0.1.0-py3-none-any.whl:

Publisher: release.yml on GraysonBellamy/watlowlib

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