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
Controllerfacade,WatlowManager, streaming, all sinks, the sync facade, and every CLI are implemented and covered by 579 passing tests acrossasyncio,asyncio+uvloop, andtrio. The core has been exercised against a live EZ-ZONE PM3 (Standard Bus) and a Series SD over Modbus RTU; the Modbus RTU side has codec / client / integration coverage plus captured PM3 and SD fixture goldens. 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 identicalReading/LoopState/DeviceInfomodels. - 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 — soread_parameter("setpoint")lowers to either protocol from one shared registry. - Typed end to end.
Unit.FAHRENHEIT,Capability.HAS_COOLING, frozen-dataclass responses,py.typed,mypy --strictclean. - Typed errors.
WatlowErrorroot with structuredErrorContext; every Standard Bus error code (0x81/0x83/0x84) and every Modbus exception (0x01–0x0B) maps to a distinct exception. - Safety gates. Every
PERSISTENTwrite — including setpoint, PID, baud, address, and protocol-mode changes — requiresconfirm=True. - Multi-device.
WatlowManagerruns 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,SqliteSinkin core;ParquetSinkandPostgresSinkbehind extras. - Swappable transports.
SerialTransportfor hardware,FakeTransportfor tests, fixture-backed transports for regression goldens. - Sync or async. Async core on
anyio; complete sync facade atwatlowlib.syncvia a blocking portal — every async method has a sync parity. - CLI tooling.
watlow-read,watlow-discover,watlow-raw,watlow-decode,watlow-configure, and thewatlow-diagreverse-engineering namespace. - Lean core.
pip install watlowlibpulls inanyio,anyserial, andanymodbus— 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,
parameters=["process_value", "setpoint"],
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 --port /dev/ttyUSB0 --protocol both # probe + identify
watlow-read --port /dev/ttyUSB0 --protocol auto -p process_value
watlow-decode "55 FF 06 00 10 ..." # offline Std Bus decode
watlow-raw --port /dev/ttyUSB0 stdbus --service read --class 4 --member 1
watlow-configure change-protocol-mode /dev/ttyUSB0 --target modbus_rtu --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://watlowlib.graysonbellamy.dev/. Useful entry points:
- Async quickstart / Sync quickstart
- Controllers and capabilities
- Commands and safety tiers / Safety
- Parameter registry
- Streaming and acquisition / Logging
- Standard Bus reference / Standard Bus findings / Modbus RTU mapping
- Testing —
FakeTransport, fixtures, hardware tiers - Architecture (design doc)
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.
Metadata
Release files for watlowlib 0.7.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| watlowlib-0.7.1.tar.gz | 397.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| watlowlib-0.7.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 739.3 kB
Release files / watlowlib-0.7.1.tar.gz
| Download URL | watlowlib-0.7.1.tar.gz |
|---|---|
| Size | 397.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6d3844402870abef6a68b895c9f057233096e2100007a9101323964abf89401c
|
|
BLAKE2b-256 checksum How to use checksums |
1b85790340293050de004109a70eff69ce416b842f3b07c820feab1db4031ee2
|
| 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 Oct 1, 2026.
Transparency logRelease files / watlowlib-0.7.1-py3-none-any.whl
| Download URL | watlowlib-0.7.1-py3-none-any.whl |
|---|---|
| Size | 342.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9054c81a57924fe467b120832f8c4aee53c6a977ff6b32fbf22640b9dddf7736
|
|
BLAKE2b-256 checksum How to use checksums |
8357d9073c9c126a409d53cfd50427ab04d654b04e646e819d54800c2e164f6c
|
| 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 Oct 1, 2026.
Transparency log