Commands¶
Every Watlow command is one Command[Req, Resp]
spec — a frozen dataclass carrying metadata (name, family hints,
capability hints, safety tier, optional firmware bounds) plus at most
one variant per protocol. Per-protocol work lives on
StdBusVariant /
ModbusVariant objects, not as
methods bolted onto Command. The session selects the variant matching
the active protocol; if the selected variant is None, the call fails
pre-I/O with WatlowProtocolUnsupportedError. See Design
§5.
This page is a catalogue by module. Full API reference is at
api/commands.md; the
Controller facade methods are thin wrappers over
session.execute(COMMAND, request).
Anatomy of a command¶
from watlowlib.commands import Command, CommandContext
from watlowlib.commands.base import ModbusVariant, StdBusVariant
from watlowlib.devices.capability import Capability, SafetyTier
from watlowlib.registry.families import ControllerFamily
Key Command fields:
| Field | Purpose |
|---|---|
name |
Python-friendly identifier; used in error context and log events. |
stdbus |
StdBusVariant carrying class/member/instance + encode/decode, or None if no Std Bus binding. |
modbus |
ModbusVariant carrying register address + register count + encode/decode, or None if no Modbus binding. |
family_hints |
Advisory frozenset[ControllerFamily] prior; empty = no prior. |
capability_hints |
Required Capability bits; Capability.NONE = always attempt. |
safety |
SafetyTier: READ_ONLY, STATEFUL, PERSISTENT. |
min_firmware |
Optional firmware-version floor. |
Every command is dispatched via session.execute(spec, request). The
request is a typed per-command dataclass; the response is the typed
result the variant decodes. The session never juggles raw bytes at the
public surface.
Gate order¶
Session.execute() applies gates in a specific order before any I/O.
See Safety for the full table and Design
§5b for the rationale.
- Safety tier (hard) —
PERSISTENTrequiresconfirm=True. - Protocol (hard) — active-protocol variant must not be
None, elseWatlowProtocolUnsupportedError. - Firmware floor (hard, when
min_firmwareis set) — refuse pre-I/O withWatlowFirmwareError. - Capability priors (soft by default) — emit a one-shot
WatlowCapabilityWarningand attempt the command anyway. - Known-denied (hard once observed) — per-session availability cache short-circuits commands the device has already returned a "no such object/attribute/instance" code for.
- Execute, then update the availability cache from the device's response (see Design §5b).
Commands by module¶
Parameters — commands/parameters.py¶
The two workhorse commands. Every public-API method on
Controller lowers to one of these.
| Command | Std Bus | Modbus | Safety | Surface |
|---|---|---|---|---|
READ_PARAMETER |
class 03 / member 01 | input/holding registers (per spec) | derived from RWES | Controller.read_parameter(name, instance=1) |
WRITE_PARAMETER |
class 04 / member 01 | preset multiple regs | derived from RWES | Controller.write_parameter(name, value, confirm=True) |
Both are parameterised by ParameterSpec
from the registry. The spec carries the canonical
name, parameter id, data type (float / uint / enum), RWES flag (read /
write / EEPROM-saved / setpoint-bound), unit, and per-protocol
addressing (Std Bus class/member/instance, Modbus register address).
The RWES flag determines SafetyTier: anything that writes EEPROM
(W or E) is PERSISTENT.
The Controller facade exposes typed shortcuts that
compose READ_PARAMETER / WRITE_PARAMETER:
| Method | Underlying parameter | Tier |
|---|---|---|
Controller.read_pv(instance=1) |
process_value (4001) |
READ_ONLY |
Controller.read_setpoint(instance=1) |
setpoint (7001) |
READ_ONLY |
Controller.set_setpoint(value, confirm=True, instance=1) |
setpoint (7001) |
PERSISTENT |
Controller.read_parameter(name) |
any registered parameter | per-spec |
Controller.write_parameter(name, value, confirm=True) |
any registered parameter | per-spec |
Controller.poll(parameters) issues one read per requested parameter
in sequence, returning a list of Reading. Used by
the recorder.
Loop — commands/loop.py¶
Composite operations against a single control loop.
| Helper | What it does | Safety |
|---|---|---|
read_pid(session, instance=1) |
Reads heat / cool gains as one PidGains snapshot. |
READ_ONLY |
write_pid(session, gains, confirm=True, instance=1) |
Writes a PidGains — heat P/I/D and (when present) cool P/I/D. |
PERSISTENT |
read_output(session, instance=1) |
Reads output_power as a Reading. |
READ_ONLY |
Cool gains are skipped on single-output SKUs (Capability.HAS_COOLING
absent). PID round-trips on dual-output SKUs are subject to known
registry-range issues — see Troubleshooting.
Alarms — commands/alarms.py¶
| Helper | What it does | Safety |
|---|---|---|
read_alarms(session, instance=1) |
Reads alarm-status bits and decodes into an AlarmState (high / low / silenced). |
READ_ONLY |
Protocol-variant dispatch¶
The session selects the variant for the active protocol:
# Std Bus active: uses spec.stdbus
# Modbus active: uses spec.modbus
await ctl.write_parameter("setpoint", 75.0, confirm=True)
If the active variant is None for the active protocol, the call
fails pre-I/O with WatlowProtocolUnsupportedError. Switching
protocols is via maintenance.change_protocol_mode;
open_device(...) never flips protocol mode as a side effect.
Adding a command¶
- Define a request dataclass and (where useful) a response type in
the appropriate
commands/<module>.py. - Implement
StdBusVariant.encode/decodeand / orModbusVariant.encode_registers/decodefor the variants you can support. - Construct a
Command(name=..., stdbus=..., modbus=..., safety=..., capability_hints=...)at module top level. - Add a thin facade method on
Controller(or a composite helper incommands/<module>.py) that callsawait session.execute(SPEC, request). - Add unit tests against
FakeTransportand the relevant captured fixture.
See Design §5 for the spec / variant separation rationale.
See also¶
- Controllers —
Controllerfacade and capability flags. - Parameters — registry, parameter ids, units, RWES.
- Safety — gate order and
confirm=Truerules. - Standard Bus protocol and Modbus RTU — wire layers.
- Design §5 — full command surface and variant rationale.