alicatlib.commands¶
Declarative command specs plus request / response models. See Commands for the catalog and Design §5.4 for the spec architecture.
alicatlib.commands ¶
Command layer — one declarative :class:Command per Alicat command.
See docs/design.md §5.4.
AnalogOutputSource
dataclass
¶
AnalogOutputSource(
name="analog_output_source",
token="ASOCV",
response_mode=ResponseMode.LINE,
device_kinds=_ALL_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.ANALOG_OUTPUT,
min_firmware=_MIN_FIRMWARE_ASOCV,
max_firmware=None,
firmware_families=frozenset({FirmwareFamily.V10}),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[AnalogOutputSourceRequest, AnalogOutputSourceSetting]
ASOCV — analog-output-source query/set (V10 10v05+).
Wire shape (primer p. 22):
- Query:
<uid><prefix>ASOCV <channel>\r - Set:
<uid><prefix>ASOCV <channel> <value> [<unit_code>]\r
Response: <uid> <value> <unit_code> <unit_label> (4 fields).
When value is 0 / 1 (fixed-level sentinels) the primer
notes unit_code=1 and unit_label="---".
decode ¶
Parse 4-field reply into :class:AnalogOutputSourceSetting.
channel is request-echo; the device doesn't re-echo it so
the facade fills from the request.
Source code in src/alicatlib/commands/output.py
encode ¶
Emit ASOCV query or set bytes.
Source code in src/alicatlib/commands/output.py
AnalogOutputSourceRequest
dataclass
¶
Arguments for :data:ANALOG_OUTPUT_SOURCE.
Attributes:
| Name | Type | Description |
|---|---|---|
channel |
AnalogOutputChannel
|
:class: |
value |
int | None
|
Either a :class: |
unit_code |
int | None
|
Engineering-unit code the output should use (primer
Appendix B). Optional — |
AutoTare
dataclass
¶
AutoTare(
name="auto_tare",
token="ZCA",
response_mode=ResponseMode.LINE,
device_kinds=_CONTROLLER_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=_MIN_FIRMWARE_ZCA_ZCP,
max_firmware=None,
firmware_families=frozenset({FirmwareFamily.V10}),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[AutoTareRequest, AutoTareState]
ZCA — auto-tare-on-zero-setpoint query/set (controllers, 10v05+).
Wire shape (hardware-corrected; primer p. 18 is incomplete):
- Query:
<uid><prefix>ZCA\r - Enable:
<uid><prefix>ZCA 1 <delay>\r - Disable:
<uid><prefix>ZCA 0\r(no delay field — see §15.3)
Primer documents the set form as always carrying both slots,
with ZCA <uid> 0 0 disabling auto-tare. Hardware validation
(2026-04-17) confirmed on two 10v20 units that ZCA 0 0
rejects with ? — the device does not accept a zero delay
in the disable form. The wire-form probe also confirmed that the
shortest accepted disable form is ZCA 0 with no delay field
(ZCA 0 0.1 / ZCA 0 1 / ZCA 0 1.0 also work, but all
land in the same enabled=0 delay=0.0 state per the reply).
The encoder emits the bare-0 form for enable=False.
Response: <uid> <enable> <delay> (3 fields) for both enable
and disable paths.
decode ¶
Parse <uid> <enable> <delay> into :class:AutoTareState.
Source code in src/alicatlib/commands/tare.py
encode ¶
Emit ZCA query or set bytes.
Disable uses the shortest accepted form (ZCA 0) rather
than the primer's ZCA 0 0 — the latter rejects on real
10v20 (see §16.6.10 + class docstring).
Source code in src/alicatlib/commands/tare.py
AutoTareRequest
dataclass
¶
Arguments for :data:AUTO_TARE.
Attributes:
| Name | Type | Description |
|---|---|---|
enable |
bool | None
|
|
delay_s |
float | None
|
Settling delay in seconds before the device tares
after seeing a zero setpoint. Primer constrains this to
|
AverageTiming
dataclass
¶
AverageTiming(
name="average_timing",
token="DCA",
response_mode=ResponseMode.LINE,
device_kinds=_ALL_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=_MIN_FIRMWARE_V10_05,
max_firmware=None,
firmware_families=_V10_ONLY,
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[AverageTimingRequest, AverageTimingSetting]
DCA — per-statistic averaging-window query/set (V10 10v05+).
Wire shape (primer p. 18):
- Query:
<uid><prefix>DCA <stat>\r - Set:
<uid><prefix>DCA <stat> <averaging_ms>\r
Response: <uid> <stat> <averaging_ms> (3 fields).
decode ¶
Parse the DCA reply into :class:AverageTimingSetting.
Primer p. 18 documents <uid> <stat> <averaging_ms> (3
fields). Hardware validation (2026-04-17) on 10v20 firmware shows
the device omits the <stat> echo and replies with just
<uid> <averaging_ms> (2 fields). Accept both shapes; when
the 2-field form arrives, statistic_code is 0 and the
facade re-populates it from the request.
Source code in src/alicatlib/commands/data_readings.py
encode ¶
Emit the DCA query or set bytes.
Source code in src/alicatlib/commands/data_readings.py
AverageTimingRequest
dataclass
¶
Arguments for :data:AVERAGE_TIMING.
Attributes:
| Name | Type | Description |
|---|---|---|
statistic_code |
int
|
One of the primer's permitted averaging codes
(see :data: |
averaging_ms |
int | None
|
Rolling window in ms. |
BlinkDisplay
dataclass
¶
BlinkDisplay(
name="blink_display",
token="FFP",
response_mode=ResponseMode.LINE,
device_kinds=_ALL_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.DISPLAY,
min_firmware=_MIN_FIRMWARE_FFP,
max_firmware=None,
firmware_families=frozenset(
{FirmwareFamily.V8_V9, FirmwareFamily.V10}
),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[BlinkDisplayRequest, BlinkDisplayState]
FFP — blink display query/set (8v28+, DISPLAY capability).
Wire:
- Query:
<uid><prefix>FFP\r - Set:
<uid><prefix>FFP <duration>\r
Response: <uid> <0|1> — two fields. 1 = flashing,
0 = idle.
decode ¶
Parse <uid> <0|1> into :class:BlinkDisplayState.
Source code in src/alicatlib/commands/display.py
encode ¶
Emit FFP query or set bytes.
Source code in src/alicatlib/commands/display.py
BlinkDisplayRequest
dataclass
¶
Arguments for :data:BLINK_DISPLAY.
Attributes:
| Name | Type | Description |
|---|---|---|
duration_s |
int | None
|
Flash duration in seconds. |
CancelValveHold
dataclass
¶
CancelValveHold(
name="cancel_valve_hold",
token="C",
response_mode=ResponseMode.LINE,
device_kinds=_CONTROLLER_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=None,
max_firmware=None,
firmware_families=_ALL_CONTROLLER_FIRMWARE_FAMILIES,
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[CancelValveHoldRequest, 'ParsedFrame']
C — cancel valve hold, resume closed-loop control.
Wire: <uid><prefix>C\r. Response is a post-op data frame
without the :attr:StatusCode.HLD bit. Safe to issue even when
no hold is active (the primer documents a successful data-frame
reply in that case).
No primer firmware cutoff — the command is documented as universally available across V1_V7, V8_V9, V10. GP behaviour is not documented; the firmware-family gate excludes GP to stay conservative until a capture confirms.
CancelValveHoldRequest
dataclass
¶
Arguments for :data:CANCEL_VALVE_HOLD (empty — C takes no arguments).
Capability ¶
Bases: Flag
Device-level feature flags, discovered per-device at session startup.
:class:~alicatlib.devices.base.DeviceKind is too coarse to gate many
commands (a flow meter may or may not have a barometer; only some
devices have a secondary pressure sensor or remote-tare pin). These
capabilities are orthogonal to DeviceKind and are declared per
command via :attr:Command.required_capabilities.
See design §5.4 for the full rationale.
BAROMETER
class-attribute
instance-attribute
¶
Device reports a barometric pressure reading (FPF 15 returns
a plausible value with a real unit label). Probed via FPF 15.
Does NOT imply :attr:TAREABLE_ABSOLUTE_PRESSURE. Hardware validation
on 2026-04-17 established that flow-controller devices (MCR, MCP, …)
expose a firmware-computed barometer reading used internally for
abs/gauge pressure derivation, but do not have a process-port
absolute-pressure sensor that the PC command can re-zero. Four
devices (8v17 MCR-200, 8v30 MCR-500, 6v21 MCR-775, 7v09 MCP-50)
all probed BAROMETER positive via FPF 15 yet rejected or
silently ignored PC. See design §16.6.7 for the narrative.
TAREABLE_ABSOLUTE_PRESSURE
class-attribute
instance-attribute
¶
Device has a process-port absolute-pressure sensor whose zero
can be re-referenced against the current barometric reading via
PC (tare_absolute_pressure). Gated separately from
:attr:BAROMETER because the two properties dissociate in practice
(see the BAROMETER docstring).
No safe probe exists — test-writing PC would tare the device.
Users opt in via assume_capabilities=Capability.TAREABLE_ABSOLUTE_PRESSURE
on :func:~alicatlib.devices.factory.open_device when they know
their hardware supports it (typically pressure meters/controllers).
Command
dataclass
¶
Command(
name,
token,
response_mode,
device_kinds,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=None,
max_firmware=None,
firmware_families=frozenset(),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Declarative spec for an Alicat command.
Subclasses are frozen dataclass instances. The overridden
:meth:encode / :meth:decode methods are the command's wire format.
Everything else — firmware gating, capability gating, destructive /
experimental flags, multiline termination — is metadata that the
session reads before dispatching, so commands fail fast with typed
errors rather than silently producing a bad wire payload.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Canonical Python-friendly name (e.g. |
token |
str
|
Protocol token (e.g. |
response_mode |
ResponseMode
|
How the session should dispatch the I/O. |
device_kinds |
frozenset[DeviceKind]
|
Which :class: |
media |
Medium
|
Which :class: |
required_capabilities |
Capability
|
Capability bits the device must have. A
command run on a device missing one raises
:class: |
min_firmware |
/ max_firmware
|
Supported firmware range within a
family. Cross-family comparison raises |
firmware_families |
frozenset[FirmwareFamily]
|
Which families support this command —
monotonic gate: declare a family only when every
captured device in that family either implements the
command or is documented to. Empty means "any". For
commands whose availability varies per-device within a
family ( |
destructive |
bool
|
Requires explicit |
experimental |
bool
|
Emits a deprecation-style warning on use. |
case_sensitive |
bool
|
Suppress any hypothetical upstream lowercase
normalisation. Only needed for |
prefix_less |
bool
|
Command opts out of the unit-id prefix
(e.g. |
expected_lines |
int | None
|
Fixed row count for |
is_complete |
Callable[[Sequence[bytes]], bool] | None
|
Predicate that returns |
A :class:Command must declare at least one of expected_lines,
is_complete for every :attr:ResponseMode.LINES command — otherwise
reads fall through to the idle-timeout fallback every time, adding
roughly 100 ms of latency per invocation. A test pins this
invariant.
Commands ¶
Namespace for command spec singletons.
Usage::
from alicatlib.commands import Commands
await session.execute(Commands.GAS_SELECT, GasSelectRequest(gas="N2"))
DataFrameFormatQuery
dataclass
¶
DataFrameFormatQuery(
name="data_frame_format_query",
token="??D*",
response_mode=ResponseMode.LINES,
device_kinds=_ALL_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=None,
max_firmware=None,
firmware_families=frozenset(),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=True,
expected_lines=64,
is_complete=None,
)
Bases: Command[DataFrameFormatRequest, 'DataFrameFormat']
??D* — query the device's advertised data-frame layout.
decode ¶
Parse the advertised data-frame layout into :class:DataFrameFormat.
Source code in src/alicatlib/commands/system.py
encode ¶
Emit <unit_id><prefix>??D*\r.
Source code in src/alicatlib/commands/system.py
DataFrameFormatRequest
dataclass
¶
Arguments for :data:DATA_FRAME_FORMAT_QUERY — no user-provided fields.
DeadbandLimit
dataclass
¶
DeadbandLimit(
name="deadband_limit",
token="LCDB",
response_mode=ResponseMode.LINE,
device_kinds=_CONTROLLER_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=_MIN_FIRMWARE_DEADBAND,
max_firmware=None,
firmware_families=frozenset({FirmwareFamily.V10}),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[DeadbandLimitRequest, DeadbandSetting]
LCDB — deadband-limit query/set (10v05+).
Wire shape (primer p. 14):
- Query:
<uid><prefix>LCDB\r - Set:
<uid><prefix>LCDB <save> <deadband_limit>\r
Response: <uid> <current_deadband> <unit_code> <unit_label>
(4 fields).
decode ¶
Parse the 4-field reply into :class:DeadbandSetting.
Source code in src/alicatlib/commands/control.py
encode ¶
Emit the LCDB query or set bytes.
Source code in src/alicatlib/commands/control.py
DeadbandLimitRequest
dataclass
¶
Arguments for :data:DEADBAND_LIMIT.
Attributes:
| Name | Type | Description |
|---|---|---|
deadband |
float | None
|
Acceptable drift around the setpoint in the
controlled variable's engineering units. |
save |
bool | None
|
|
DecodeContext
dataclass
¶
DecodeContext(
unit_id,
firmware,
capabilities=Capability.NONE,
command_prefix=b"",
data_frame_format=None,
)
Per-call context threaded through encode / decode.
Built once per command by the session from cached device info, so commands never do I/O to figure out the prefix / firmware themselves.
Attributes:
| Name | Type | Description |
|---|---|---|
unit_id |
str
|
Single-letter |
firmware |
FirmwareVersion
|
Parsed device firmware; used by commands that alter their
wire format across firmware families (e.g. legacy setpoint
|
capabilities |
Capability
|
Feature flags discovered at session startup. The
session pre-checks |
command_prefix |
bytes
|
Bytes injected between |
data_frame_format |
DataFrameFormat | None
|
The cached |
EngineeringUnits
dataclass
¶
EngineeringUnits(
name="engineering_units",
token="DCU",
response_mode=ResponseMode.LINE,
device_kinds=_ALL_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=MIN_FIRMWARE_DCU,
max_firmware=None,
firmware_families=frozenset({FirmwareFamily.V10}),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[EngineeringUnitsRequest, UnitSetting]
DCU — engineering-units query / set.
Wire shape (primer-derived; hardware-correctable):
- Query:
<uid><prefix>DCU <stat> - Set:
<uid><prefix>DCU <stat> <unit> - Group:
<uid><prefix>DCU <stat> <unit> 1 - Special:
<uid><prefix>DCU <stat> <unit> 1 1
Response shape: <uid> <stat_code> <unit_code> <unit_label>.
decode ¶
Parse <uid> <unit_code> <unit_label> into :class:UnitSetting.
Verified against a V10 capture on 2026-04-17 (design §16.6) — the
device does not echo the requested statistic in the reply, so
statistic is left as :attr:Statistic.NONE and the facade
fills it from the request via :func:dataclasses.replace.
Source code in src/alicatlib/commands/units.py
encode ¶
Emit the DCU query / set bytes.
Source code in src/alicatlib/commands/units.py
EngineeringUnitsRequest
dataclass
¶
EngineeringUnitsRequest(
statistic,
unit=None,
apply_to_group=False,
override_special_rules=False,
)
Arguments for :data:ENGINEERING_UNITS.
Attributes:
| Name | Type | Description |
|---|---|---|
statistic |
Statistic | str
|
Statistic to query or set units on. Accepts the
:class: |
unit |
Unit | int | str | None
|
|
apply_to_group |
bool
|
When setting, broadcast the change to the statistic's group instead of the single statistic. |
override_special_rules |
bool
|
When setting, override any device-specific restrictions on the statistic / unit pair. Only meaningful on set; ignored in query form. |
FullScaleQuery
dataclass
¶
FullScaleQuery(
name="full_scale_query",
token="FPF",
response_mode=ResponseMode.LINE,
device_kinds=_ALL_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=None,
max_firmware=None,
firmware_families=frozenset(
{
FirmwareFamily.V1_V7,
FirmwareFamily.V8_V9,
FirmwareFamily.V10,
}
),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[FullScaleQueryRequest, FullScaleValue]
FPF — full-scale query for a single statistic.
Wire shape: <uid><prefix>FPF <stat>\r.
Response (primer-derived): <uid> <stat_code> <value> <unit_code> <unit_label>.
decode ¶
Parse <uid> <value> <unit_code> <unit_label> into :class:FullScaleValue.
Verified against a V10 capture on 2026-04-17 (design §16.6) — the
device does not echo the requested statistic in the reply, so
statistic is left as :attr:Statistic.NONE and the facade
fills it from the request via :func:dataclasses.replace.
Source code in src/alicatlib/commands/units.py
encode ¶
Emit <unit_id><prefix>FPF <stat_code>\r.
Source code in src/alicatlib/commands/units.py
FullScaleQueryRequest
dataclass
¶
Arguments for :data:FULL_SCALE_QUERY.
Attributes:
| Name | Type | Description |
|---|---|---|
statistic |
Statistic | str
|
Statistic whose full-scale value to query. |
GasList
dataclass
¶
GasList(
name="gas_list",
token="??G*",
response_mode=ResponseMode.LINES,
device_kinds=frozenset(
{DeviceKind.FLOW_METER, DeviceKind.FLOW_CONTROLLER}
),
media=Medium.GAS,
required_capabilities=Capability.NONE,
min_firmware=None,
max_firmware=None,
firmware_families=frozenset(),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=True,
expected_lines=_GAS_LIST_MAX_LINES,
is_complete=None,
)
Bases: Command[GasListRequest, dict[int, str]]
??G* — enumerate built-in and mixture gases on the device.
Returns {gas_code: raw_label}. Callers that want typed
:class:Gas members should cross-reference the code against
:func:alicatlib.registry.gas_registry.by_code; unknown codes
(custom mixtures, legacy compat slots) are preserved as raw
labels so diagnostics retain them.
decode ¶
Parse the multi-line gas list into {gas_code: label}.
Source code in src/alicatlib/commands/gas.py
encode ¶
Emit <unit_id><prefix>??G*\r.
GasSelect
dataclass
¶
GasSelect(
name="gas_select",
token="GS",
response_mode=ResponseMode.LINE,
device_kinds=frozenset(
{DeviceKind.FLOW_METER, DeviceKind.FLOW_CONTROLLER}
),
media=Medium.GAS,
required_capabilities=Capability.NONE,
min_firmware=MIN_FIRMWARE_GAS_SELECT,
max_firmware=None,
firmware_families=frozenset({FirmwareFamily.V10}),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[GasSelectRequest, GasState]
Active-gas command (GS, 10v05+).
Both get (GS) and set (GS <code> [save]) share a single command
spec — the request's gas field picks the mode. The facade routes
firmwares < 10v05 to :data:GAS_SELECT_LEGACY
(see :func:~alicatlib.commands._firmware_cutoffs.uses_modern_gas_select).
decode ¶
Parse the four-field GS reply into a typed :class:GasState.
Source code in src/alicatlib/commands/gas.py
encode ¶
Emit the wire bytes for a GS query or set command.
Source code in src/alicatlib/commands/gas.py
GasSelectLegacy
dataclass
¶
GasSelectLegacy(
name="gas_select_legacy",
token="G",
response_mode=ResponseMode.LINE,
device_kinds=frozenset(
{DeviceKind.FLOW_METER, DeviceKind.FLOW_CONTROLLER}
),
media=Medium.GAS,
required_capabilities=Capability.NONE,
min_firmware=None,
max_firmware=_MAX_FIRMWARE_GAS_SELECT_LEGACY_V10,
firmware_families=frozenset(
{
FirmwareFamily.GP,
FirmwareFamily.V1_V7,
FirmwareFamily.V8_V9,
FirmwareFamily.V10,
}
),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[GasSelectLegacyRequest, 'ParsedFrame']
Legacy set-gas command (G) for firmware older than V10 ≥ 10v05.
Per design §5.4, the device replies with a post-op data frame
rather than the modern 4-field <uid> <code> <short> <long>
reply. This command's decoder returns the raw
:class:ParsedFrame so the facade can stitch a :class:GasState
from (a) the gas code the caller sent — known at the facade but
not at the decoder — and (b) the frame's echoed unit id.
Gating: firmware_families lists every family the legacy path
applies to; max_firmware set to 10v04 inside
:attr:FirmwareFamily.V10 blocks V10 ≥ 10v05 specifically.
Because the session's range check is family-scoped (design §5.10),
the V10 upper bound does not leak into the gating decisions for
GP / V1_V7 / V8_V9.
decode ¶
Parse the post-set data frame against ctx.data_frame_format.
The session caches the format at startup; callers hitting this
decoder before the format has been probed get an
:class:AlicatParseError pointing at the missing probe — same
failure mode as :data:~alicatlib.commands.polling.POLL_DATA.
Source code in src/alicatlib/commands/gas.py
encode ¶
Emit <unit_id><prefix>G <gas_code>\r.
Source code in src/alicatlib/commands/gas.py
GasSelectLegacyRequest
dataclass
¶
Arguments for :data:GAS_SELECT_LEGACY.
Legacy G is set only — there is no query form and no save
flag. The facade (:meth:FlowMeter.gas) raises
:class:AlicatUnsupportedCommandError if a caller asks for a query
while the device is on firmware that only supports the legacy path;
likewise it raises :class:AlicatValidationError if save=True
is passed to a legacy dispatch.
Attributes:
| Name | Type | Description |
|---|---|---|
gas |
Gas | str
|
Gas to select. Accepts a :class: |
GasSelectRequest
dataclass
¶
Arguments for :data:GAS_SELECT.
Attributes:
| Name | Type | Description |
|---|---|---|
gas |
Gas | str | None
|
Gas to select. Accepts a :class: |
save |
bool | None
|
If |
GasState
dataclass
¶
Active-gas response.
Populated from a four-field reply: <unit_id> <code> <short> <long>.
Attributes:
| Name | Type | Description |
|---|---|---|
unit_id |
str
|
Echoed unit id ( |
code |
int
|
Numeric gas code (primer Appendix C). Redundant with
|
gas |
Gas
|
The typed :class: |
label |
str
|
Short primer label as echoed by the device (usually matches
|
long_name |
str
|
Long primer name as echoed by the device. |
HoldValves
dataclass
¶
HoldValves(
name="hold_valves",
token="HP",
response_mode=ResponseMode.LINE,
device_kinds=_CONTROLLER_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=_MIN_FIRMWARE_HOLD,
max_firmware=None,
firmware_families=_ALL_CONTROLLER_FIRMWARE_FAMILIES,
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[HoldValvesRequest, 'ParsedFrame']
HP — hold valve(s) at current position (5v07+).
Wire: <uid><prefix>HP\r. Response is a post-op data frame
with :attr:StatusCode.HLD active. Closed-loop control pauses
until :data:CANCEL_VALVE_HOLD is sent.
HoldValvesClosed
dataclass
¶
HoldValvesClosed(
name="hold_valves_closed",
token="HC",
response_mode=ResponseMode.LINE,
device_kinds=_CONTROLLER_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=_MIN_FIRMWARE_HOLD,
max_firmware=None,
firmware_families=_ALL_CONTROLLER_FIRMWARE_FAMILIES,
destructive=True,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[HoldValvesClosedRequest, 'ParsedFrame']
HC — hold valves closed (5v07+); destructive.
Wire: <uid><prefix>HC\r. Response is a post-op data frame with
:attr:StatusCode.HLD active; flow and closed-loop control both
stop. destructive=True forces explicit confirm=True on the
request.
HoldValvesClosedRequest
dataclass
¶
Arguments for :data:HOLD_VALVES_CLOSED.
Attributes:
| Name | Type | Description |
|---|---|---|
confirm |
bool
|
Must be |
HoldValvesRequest
dataclass
¶
Arguments for :data:HOLD_VALVES (empty — HP takes no arguments).
LockDisplay
dataclass
¶
LockDisplay(
name="lock_display",
token="L",
response_mode=ResponseMode.LINE,
device_kinds=_ALL_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.DISPLAY,
min_firmware=None,
max_firmware=None,
firmware_families=frozenset(),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[LockDisplayRequest, 'ParsedFrame']
L — lock front-panel display (DISPLAY capability).
Wire: <uid><prefix>L\r. Response is a post-op data frame with
:attr:StatusCode.LCK active. No primer firmware cutoff —
capability gate suffices.
LockDisplayRequest
dataclass
¶
Arguments for :data:LOCK_DISPLAY (empty — L takes no arguments).
LoopControlVariableCommand
dataclass
¶
LoopControlVariableCommand(
name="loop_control_variable",
token="LV",
response_mode=ResponseMode.LINE,
device_kinds=_CONTROLLER_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=_MIN_FIRMWARE_LV,
max_firmware=None,
firmware_families=frozenset(
{FirmwareFamily.V8_V9, FirmwareFamily.V10}
),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[LoopControlVariableRequest, LoopControlState]
LV — loop-control variable get/set.
Firmware-gated at 9v00 within :attr:FirmwareFamily.V8_V9
and available on every V10 release. Set form validates the
variable against the restricted eight-member subset via
:func:coerce_loop_control_variable; an ineligible
:class:Statistic raises :class:AlicatValidationError pre-I/O
rather than letting the device reject silently.
decode ¶
Parse <uid> <stat_code> into :class:LoopControlState.
Verified against a V10 capture on 2026-04-17 (design §16.6) — the
device replies with just <uid> <stat_code> (2 fields, no
human-readable label). The label is derived from the typed
variable's :class:Statistic display name.
Source code in src/alicatlib/commands/loop_control.py
encode ¶
Emit the LV query or set bytes.
Source code in src/alicatlib/commands/loop_control.py
LoopControlVariableRequest
dataclass
¶
Arguments for :data:LOOP_CONTROL_VARIABLE.
Attributes:
| Name | Type | Description |
|---|---|---|
variable |
LoopControlVariable | str | int | None
|
One of the eight LV-eligible statistics, accepted as
:class: |
ManufacturingInfoCommand
dataclass
¶
ManufacturingInfoCommand(
name="manufacturing_info",
token="??M*",
response_mode=ResponseMode.LINES,
device_kinds=_ALL_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=None,
max_firmware=None,
firmware_families=frozenset(
{
FirmwareFamily.GP,
FirmwareFamily.V1_V7,
FirmwareFamily.V8_V9,
FirmwareFamily.V10,
}
),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=True,
expected_lines=10,
is_complete=None,
)
Bases: Command[ManufacturingInfoRequest, ManufacturingInfo]
??M* — 10-line manufacturing-info table (8v28+, numeric families).
The session gates this command on firmware family and version before dispatching, so the encoder never reaches a GP or pre-8v28 device.
decode ¶
Parse the 10-line ??M* table into :class:ManufacturingInfo.
Source code in src/alicatlib/commands/system.py
encode ¶
Emit <unit_id><prefix>??M*\r.
Source code in src/alicatlib/commands/system.py
ManufacturingInfoRequest
dataclass
¶
Arguments for :data:MANUFACTURING_INFO — no user-provided fields.
PollData
dataclass
¶
PollData(
name="poll_data",
token="",
response_mode=ResponseMode.LINE,
device_kinds=frozenset(DeviceKind),
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=None,
max_firmware=None,
firmware_families=frozenset(),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=True,
expected_lines=None,
is_complete=None,
)
Bases: Command[PollRequest, 'ParsedFrame']
Poll the device's current data frame (primer A\r).
Encodes as just {unit_id}{prefix}\r — no token. Decodes against
the session-cached :class:DataFrameFormat carried on the
:class:DecodeContext.
The decode layer returns a :class:ParsedFrame rather than a
:class:DataFrame because timing belongs to the I/O layer, not the
pure decode step. The session's execute() wraps via
:meth:DataFrame.from_parsed before returning from the facade
(Device.poll()), so users never see a raw :class:ParsedFrame
unless they go through the session.execute(POLL_DATA, ...)
escape hatch. See design §5.6.
decode ¶
Parse the raw data frame against ctx.data_frame_format.
Source code in src/alicatlib/commands/polling.py
encode ¶
Emit the device's poll bytes — <unit_id><prefix>\r, no token.
Source code in src/alicatlib/commands/polling.py
PowerUpTare
dataclass
¶
PowerUpTare(
name="power_up_tare",
token="ZCP",
response_mode=ResponseMode.LINE,
device_kinds=_ALL_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=_MIN_FIRMWARE_ZCA_ZCP,
max_firmware=None,
firmware_families=frozenset({FirmwareFamily.V10}),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[PowerUpTareRequest, PowerUpTareState]
ZCP — power-up tare query/set (all devices, V10 10v05+).
Wire shape (primer p. 19):
- Query:
<uid><prefix>ZCP\r - Set:
<uid><prefix>ZCP <enable>\r
Response: <uid> <enable> (2 fields).
decode ¶
Parse <uid> <enable> into :class:PowerUpTareState.
Source code in src/alicatlib/commands/tare.py
encode ¶
Emit ZCP query or set bytes.
Source code in src/alicatlib/commands/tare.py
PowerUpTareRequest
dataclass
¶
Arguments for :data:POWER_UP_TARE.
Attributes:
| Name | Type | Description |
|---|---|---|
enable |
bool | None
|
|
RampRate
dataclass
¶
RampRate(
name="ramp_rate",
token="SR",
response_mode=ResponseMode.LINE,
device_kinds=_CONTROLLER_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=_MIN_FIRMWARE_RAMP_RATE,
max_firmware=None,
firmware_families=frozenset(
{
FirmwareFamily.V1_V7,
FirmwareFamily.V8_V9,
FirmwareFamily.V10,
}
),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[RampRateRequest, RampRateSetting]
SR — max ramp rate query/set (7v11+).
Wire shape:
- Query:
<uid><prefix>SR\r - Set:
<uid><prefix>SR <max_ramp> <time_unit_code>\r
Response (primer p. 15, hardware-correctable): 5 fields —
<uid> <max_ramp> <setpoint_unit_code> <time_value> <rate_unit_label>
(e.g. A 25.0 12 4 SCCM/s).
decode ¶
Parse the 5-field reply into :class:RampRateSetting.
Source code in src/alicatlib/commands/control.py
encode ¶
Emit the SR query or set bytes.
Source code in src/alicatlib/commands/control.py
RampRateRequest
dataclass
¶
Arguments for :data:RAMP_RATE.
Attributes:
| Name | Type | Description |
|---|---|---|
max_ramp |
float | None
|
Ramp step size in the current engineering units.
|
time_unit |
TimeUnit | None
|
Time base (:class: |
RequestData
dataclass
¶
RequestData(
name="request_data",
token="DV",
response_mode=ResponseMode.LINE,
device_kinds=frozenset(DeviceKind),
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=None,
max_firmware=None,
firmware_families=frozenset(
{
FirmwareFamily.V1_V7,
FirmwareFamily.V8_V9,
FirmwareFamily.V10,
}
),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[RequestDataRequest, tuple[float | None, ...]]
DV — request a caller-chosen subset of statistics.
Decoder returns a positional tuple of parsed values aligned with
:attr:RequestDataRequest.statistics. The -- wire sentinel (an
invalid statistic code passed through per-slot) maps to None.
The wire reply has no unit-id prefix — unique in the catalog and
load-bearing for the decoder. The facade
:meth:alicatlib.devices.base.Device.request wraps the tuple into a
:class:~alicatlib.devices.models.MeasurementSet with the correct
unit_id / averaging_ms / received_at mapping (same
pure-parse → timing-wrap split as :data:POLL_DATA).
decode ¶
Parse <val1> [val2...] into a positional tuple.
-- slots resolve to None; every other token parses as a
float. Unit-id prefix absence is the load-bearing wire property
— if a future firmware adds one, this decoder needs an update
(pin via a fresh fixture).
Source code in src/alicatlib/commands/polling.py
encode ¶
Emit <unit_id><prefix>DV <time_ms> <stat1> [stat2...]\r.
Validates pre-I/O per design §5.20 item 4: averaging in
1..9999 ms, 1..13 statistics.
Source code in src/alicatlib/commands/polling.py
RequestDataRequest
dataclass
¶
Arguments for :data:REQUEST_DATA.
Attributes:
| Name | Type | Description |
|---|---|---|
statistics |
Sequence[Statistic | str]
|
1–13 :class: |
averaging_ms |
int
|
Rolling averaging window in milliseconds (primer:
1–9999). The device rejects |
ResponseMode ¶
Bases: Enum
What the transport should do for a command after writing.
The :class:~alicatlib.devices.session.Session uses this to pick
write_only / query_line / query_lines without per-command
branching in the session code.
LINE
class-attribute
instance-attribute
¶
Single-line response terminated by CR. The common case.
LINES
class-attribute
instance-attribute
¶
Multiline table response. See :attr:Command.expected_lines /
:attr:Command.is_complete for the termination contract.
NONE
class-attribute
instance-attribute
¶
Write-only; no read. Example: @@ stop-stream.
STREAM
class-attribute
instance-attribute
¶
Enters streaming mode. Not a normal request/response command.
Setpoint
dataclass
¶
Setpoint(
name="setpoint",
token="LS",
response_mode=ResponseMode.LINE,
device_kinds=_CONTROLLER_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=MIN_FIRMWARE_SETPOINT_LS,
max_firmware=None,
firmware_families=frozenset(
{FirmwareFamily.V8_V9, FirmwareFamily.V10}
),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[SetpointRequest, SetpointState]
LS — modern setpoint get/set (V10 + V8_V9 ≥ 9v00).
Wire shape:
- Query:
<uid><prefix>LS\r - Set:
<uid><prefix>LS <value>\r
Response is a post-op data frame containing the updated Setpoint
field; the decoder returns the :class:ParsedFrame and the
facade converts to :class:SetpointState with facade-level timing
(same pattern as tare / legacy-gas).
Gated to :attr:DeviceKind.FLOW_CONTROLLER and
:attr:DeviceKind.PRESSURE_CONTROLLER — a plain meter has no
setpoint. Firmware gating handles V8_V9 < 9v00 → redirect to
:data:SETPOINT_LEGACY at the facade layer.
decode ¶
Parse the modern LS 5-field reply into :class:SetpointState.
Source code in src/alicatlib/commands/setpoint.py
encode ¶
Emit the LS query or set bytes.
Source code in src/alicatlib/commands/setpoint.py
SetpointLegacy
dataclass
¶
SetpointLegacy(
name="setpoint_legacy",
token="S",
response_mode=ResponseMode.LINE,
device_kinds=_CONTROLLER_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=None,
max_firmware=_MAX_FIRMWARE_SETPOINT_LEGACY_V8V9,
firmware_families=frozenset(
{FirmwareFamily.V1_V7, FirmwareFamily.V8_V9}
),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[SetpointLegacyRequest, 'ParsedFrame']
S — legacy setpoint set for pre-9v00 firmware.
Applies to :attr:FirmwareFamily.V1_V7 (all) and
:attr:FirmwareFamily.V8_V9 < 9v00. Session's family-scoped
max_firmware gate blocks V8_V9 ≥ 9v00 (redirect to
:data:SETPOINT); V1_V7 has no upper bound.
Response: post-op data frame, same shape as
:data:SETPOINT.
SetpointLegacyRequest
dataclass
¶
Arguments for :data:SETPOINT_LEGACY.
Legacy S is set-only — there is no query form on firmware
that predates LS. The facade rejects value is None pre-I/O
with :class:AlicatUnsupportedCommandError and routes query
intents to the modern :data:SETPOINT if the firmware supports
it.
SetpointRequest
dataclass
¶
Arguments for :data:SETPOINT.
Attributes:
| Name | Type | Description |
|---|---|---|
value |
float | None
|
Setpoint target in the device's current engineering
units. |
SetpointSource
dataclass
¶
SetpointSource(
name="setpoint_source",
token="LSS",
response_mode=ResponseMode.LINE,
device_kinds=_CONTROLLER_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=MIN_FIRMWARE_LSS,
max_firmware=None,
firmware_families=frozenset({FirmwareFamily.V10}),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[SetpointSourceRequest, SetpointSourceResult]
LSS — setpoint-source get/set.
Wire shape:
- Query:
<uid><prefix>LSS\r - Set:
<uid><prefix>LSS <mode>[ <save>]\r— mode ∈ {S, A, U}.
Response: <uid> <mode> (2 fields). The facade caches the
decoded mode on :attr:Session.setpoint_source so
:meth:FlowController.setpoint can detect the
LSS=A silently ignores serial setpoint failure mode.
decode ¶
Parse <uid> <mode> into :class:SetpointSourceResult.
Source code in src/alicatlib/commands/setpoint.py
encode ¶
Emit the LSS query or set bytes.
Source code in src/alicatlib/commands/setpoint.py
SetpointSourceRequest
dataclass
¶
Arguments for :data:SETPOINT_SOURCE.
Attributes:
| Name | Type | Description |
|---|---|---|
mode |
str | None
|
|
save |
bool | None
|
|
SetpointSourceResult
dataclass
¶
Typed response for :data:SETPOINT_SOURCE.
Decodes the primer's <uid> <mode> two-field reply. Keeping the
decoded mode as a str (rather than a dedicated enum) lets
the facade treat unknown modes as best-effort diagnostics without
fighting an enum-coerce failure — the mode is re-validated
against :data:SETPOINT_SOURCE_MODES on the facade set path.
StpNtpPressure
dataclass
¶
StpNtpPressure(
name="stp_ntp_pressure",
token="DCFRP",
response_mode=ResponseMode.LINE,
device_kinds=_FLOW_DEVICE_KINDS,
media=Medium.GAS,
required_capabilities=Capability.NONE,
min_firmware=_MIN_FIRMWARE_V10_05,
max_firmware=None,
firmware_families=_V10_ONLY,
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[StpNtpPressureRequest, StpNtpPressureSetting]
DCFRP — STP/NTP pressure reference query/set (V10 10v05+, mass-flow).
Wire shape (primer p. 18):
- Query:
<uid><prefix>DCFRP <S|N>\r - Set:
<uid><prefix>DCFRP <S|N> <unit_code> <pressure>\r
Response: <uid> <pressure> <unit_code> <unit_label> (4 fields).
decode ¶
Parse the 4-field reply into :class:StpNtpPressureSetting.
mode is carried on the request-echo convention — the device
doesn't re-echo S / N in the reply, so the facade
fills the returned dataclass's mode via
:func:dataclasses.replace (same pattern as DCU / FPF).
Source code in src/alicatlib/commands/data_readings.py
encode ¶
Emit DCFRP query or set bytes.
StpNtpPressureRequest
dataclass
¶
Arguments for :data:STP_NTP_PRESSURE.
Attributes:
| Name | Type | Description |
|---|---|---|
mode |
StpNtpMode
|
:class: |
pressure |
float | None
|
Reference pressure. |
unit_code |
int | None
|
Engineering-unit code for the pressure value.
|
StpNtpTemperature
dataclass
¶
StpNtpTemperature(
name="stp_ntp_temperature",
token="DCFRT",
response_mode=ResponseMode.LINE,
device_kinds=_FLOW_DEVICE_KINDS,
media=Medium.GAS,
required_capabilities=Capability.NONE,
min_firmware=_MIN_FIRMWARE_V10_05,
max_firmware=None,
firmware_families=_V10_ONLY,
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[StpNtpTemperatureRequest, StpNtpTemperatureSetting]
DCFRT — STP/NTP temperature reference query/set (V10 10v05+, mass-flow).
Wire + response shape: see :class:StpNtpPressure; substitute
DCFRT for DCFRP and temperature for pressure.
decode ¶
Parse the 4-field reply. Facade replaces mode from the request.
Source code in src/alicatlib/commands/data_readings.py
encode ¶
Emit DCFRT query or set bytes.
StpNtpTemperatureRequest
dataclass
¶
Arguments for :data:STP_NTP_TEMPERATURE.
Same shape as :class:StpNtpPressureRequest but for temperature.
StreamingRate
dataclass
¶
StreamingRate(
name="streaming_rate",
token="NCS",
response_mode=ResponseMode.LINE,
device_kinds=_ALL_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=MIN_FIRMWARE_NCS,
max_firmware=None,
firmware_families=frozenset({FirmwareFamily.V10}),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[StreamingRateRequest, StreamingRateResult]
NCS — query or set the streaming interval.
Wire shape (primer p. 22):
- Query:
<uid><prefix>NCS\r - Set:
<uid><prefix>NCS <interval_ms>\r
Response (primer-derived, hardware-correctable): <uid> <interval_ms>.
The primer says the device "confirms the rate"; a real capture may
refine the reply shape, which is a one-line regex change to the
decoder per design §15.3.
decode ¶
Parse <uid> <interval_ms> into :class:StreamingRateResult.
Two-field reply per primer p. 22 — the device echoes its unit id and the effective interval. Hardware-correctable per design §15.3; any observed extra field surfaces as a parse error pointing at the raw bytes.
Source code in src/alicatlib/commands/streaming.py
encode ¶
Emit the NCS query or set bytes.
rate_ms must be a non-negative int. 0 is a valid
device setting (as-fast-as-possible); None distinguishes
the query form.
Source code in src/alicatlib/commands/streaming.py
StreamingRateRequest
dataclass
¶
Arguments for :data:STREAMING_RATE.
Attributes:
| Name | Type | Description |
|---|---|---|
rate_ms |
int | None
|
Streaming interval in milliseconds. |
StreamingRateResult
dataclass
¶
Reply payload for :data:STREAMING_RATE.
Attributes:
| Name | Type | Description |
|---|---|---|
unit_id |
str
|
Echoed unit id from the device. |
rate_ms |
int
|
Current streaming interval, in milliseconds. |
TareAbsolutePressure
dataclass
¶
TareAbsolutePressure(
name="tare_absolute_pressure",
token="PC",
response_mode=ResponseMode.LINE,
device_kinds=_PRESSURE_AWARE_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.TAREABLE_ABSOLUTE_PRESSURE,
min_firmware=None,
max_firmware=None,
firmware_families=frozenset(
{
FirmwareFamily.V1_V7,
FirmwareFamily.V8_V9,
FirmwareFamily.V10,
}
),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[TareAbsolutePressureRequest, 'ParsedFrame']
PC — calibrate absolute pressure against the onboard barometer.
Gated on :attr:Capability.TAREABLE_ABSOLUTE_PRESSURE — NOT on
:attr:Capability.BAROMETER. Hardware validation on 2026-04-17 established
that flow-controller devices report a firmware-computed barometer
reading (so BAROMETER probes positive) but do not have a
process-port absolute-pressure sensor and therefore reject or
silently ignore PC. Four devices confirmed the pattern (8v17
MCR-200, 8v30 MCR-500, 6v21 MCR-775, 7v09 MCP-50); see design
§16.6.7 for the narrative and Capability.BAROMETER /
:attr:Capability.TAREABLE_ABSOLUTE_PRESSURE for the semantic split.
No safe probe for TAREABLE_ABSOLUTE_PRESSURE exists (probing
would tare the device). Users with a pressure meter/controller that
supports PC opt in via assume_capabilities on
:func:~alicatlib.devices.factory.open_device. Devices without the
capability raise :class:AlicatMissingHardwareError pre-I/O.
Precondition: the gauge pressure should be at atmosphere — the device uses its barometer reading as the reference.
TareAbsolutePressureRequest
dataclass
¶
Arguments for :data:TARE_ABSOLUTE_PRESSURE — no user-provided fields.
TareFlow
dataclass
¶
TareFlow(
name="tare_flow",
token="T",
response_mode=ResponseMode.LINE,
device_kinds=_FLOW_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=None,
max_firmware=None,
firmware_families=frozenset(
{
FirmwareFamily.V1_V7,
FirmwareFamily.V8_V9,
FirmwareFamily.V10,
}
),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[TareFlowRequest, 'ParsedFrame']
T — tare the flow reading.
Precondition (caller's responsibility): no gas is flowing through the device. The library cannot verify this — the facade emits an INFO log noting the precondition on every call.
decode ¶
TareFlowRequest
dataclass
¶
Arguments for :data:TARE_FLOW — no user-provided fields.
TareGaugePressure
dataclass
¶
TareGaugePressure(
name="tare_gauge_pressure",
token="TP",
response_mode=ResponseMode.LINE,
device_kinds=_PRESSURE_AWARE_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=None,
max_firmware=None,
firmware_families=frozenset(
{
FirmwareFamily.V1_V7,
FirmwareFamily.V8_V9,
FirmwareFamily.V10,
}
),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[TareGaugePressureRequest, 'ParsedFrame']
TP — tare the gauge-pressure reading.
Precondition (caller's responsibility): line depressurised to atmosphere. Applies to both flow devices (which carry a pressure transducer for compensation) and pressure devices.
decode ¶
TareGaugePressureRequest
dataclass
¶
Arguments for :data:TARE_GAUGE_PRESSURE — no user-provided fields.
TotalizerConfigCommand
dataclass
¶
TotalizerConfigCommand(
name="totalizer_config",
token="TC",
response_mode=ResponseMode.LINE,
device_kinds=_FLOW_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=_MIN_FIRMWARE_TC,
max_firmware=None,
firmware_families=_V10_ONLY,
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[TotalizerConfigRequest, TotalizerConfig]
TC — totalizer configuration query/set (V10 10v00+, flow devices).
Wire shape:
- Query:
<uid><prefix>TC <totalizer>\r - Disable:
<uid><prefix>TC <totalizer> 1\r - Set:
<uid><prefix>TC <totalizer> <flow_stat> <mode> <limit_mode> <digits> <decimal>\r
Response: <uid> <flow_stat> <mode> <limit_mode> <digits> <decimal>
(6 fields — primer's explicit field list). The totalizer id is the
caller's responsibility to track; the facade re-populates
:attr:TotalizerConfig.totalizer from the request.
decode ¶
Parse the TC reply into :class:TotalizerConfig.
Primer-derived shape: <uid> <stat> <mode> <limit_mode>
<digits> <decimal> — 6 fields; totalizer id not echoed.
Hardware validation (2026-04-17) on 10v20 firmware shows the
totalizer id IS echoed as the second token, producing 7 fields
(<uid> <totalizer_id> <stat> <mode> <limit_mode> <digits>
<decimal>). Accept both shapes; drop the echoed id when
present. The facade re-populates :attr:totalizer from the
request either way.
Source code in src/alicatlib/commands/totalizer.py
encode ¶
Emit the TC query or set bytes.
Source code in src/alicatlib/commands/totalizer.py
TotalizerConfigRequest
dataclass
¶
TotalizerConfigRequest(
totalizer,
flow_statistic_code=None,
mode=None,
limit_mode=None,
digits=None,
decimal_place=None,
)
Arguments for :data:TOTALIZER_CONFIG.
Attributes:
| Name | Type | Description |
|---|---|---|
totalizer |
TotalizerId
|
Which totalizer ( |
flow_statistic_code |
int | None
|
|
mode |
/ limit_mode / digits / decimal_place
|
Required whenever
|
TotalizerReset
dataclass
¶
TotalizerReset(
name="totalizer_reset",
token="T",
response_mode=ResponseMode.LINE,
device_kinds=_FLOW_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=_MIN_FIRMWARE_RESET,
max_firmware=None,
firmware_families=_V8_V9_V10,
destructive=True,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[TotalizerResetRequest, 'ParsedFrame']
T <n> — reset totalizer count (8v00+, flow devices). Destructive.
Wire: <uid><prefix>T <totalizer>\r — always with the
numeric argument. Primer's bare <uid>T\r is the flow-tare
command (see :data:TARE_FLOW); the two share a token and
collide at the wire level if the numeric arg is omitted.
Response: post-op data frame with the totalizer reset to zero.
TotalizerResetPeak
dataclass
¶
TotalizerResetPeak(
name="totalizer_reset_peak",
token="TP",
response_mode=ResponseMode.LINE,
device_kinds=_FLOW_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=_MIN_FIRMWARE_RESET,
max_firmware=None,
firmware_families=_V8_V9_V10,
destructive=True,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[TotalizerResetPeakRequest, 'ParsedFrame']
TP <n> — reset totalizer peak reading (8v00+). Destructive.
Wire: <uid><prefix>TP <totalizer>\r. Always emits the
numeric argument so the spec can never accidentally produce the
:data:TARE_GAUGE_PRESSURE wire form (bare <uid>TP\r).
TotalizerResetPeakRequest
dataclass
¶
Arguments for :data:TOTALIZER_RESET_PEAK.
Same shape as :class:TotalizerResetRequest; destructive.
TotalizerResetRequest
dataclass
¶
Arguments for :data:TOTALIZER_RESET.
Attributes:
| Name | Type | Description |
|---|---|---|
totalizer |
TotalizerId
|
Which totalizer to reset. Defaults to
:attr: |
confirm |
bool
|
Required |
TotalizerSave
dataclass
¶
TotalizerSave(
name="totalizer_save",
token="TCR",
response_mode=ResponseMode.LINE,
device_kinds=_FLOW_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=_MIN_FIRMWARE_TCR,
max_firmware=None,
firmware_families=_V10_ONLY,
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[TotalizerSaveRequest, TotalizerSaveState]
TCR — save-totalizer query/set (V10 10v05+, flow devices).
Wire shape:
- Query:
<uid><prefix>TCR\r - Set:
<uid><prefix>TCR <enable>\r
Response: <uid> <enable> (2 fields).
decode ¶
Parse <uid> <enable> into :class:TotalizerSaveState.
Source code in src/alicatlib/commands/totalizer.py
encode ¶
Emit TCR query or set bytes.
Source code in src/alicatlib/commands/totalizer.py
TotalizerSaveRequest
dataclass
¶
Arguments for :data:TOTALIZER_SAVE.
Attributes:
| Name | Type | Description |
|---|---|---|
enable |
bool | None
|
|
save |
bool | None
|
|
UnlockDisplay
dataclass
¶
UnlockDisplay(
name="unlock_display",
token="U",
response_mode=ResponseMode.LINE,
device_kinds=_ALL_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=None,
max_firmware=None,
firmware_families=frozenset(),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[UnlockDisplayRequest, 'ParsedFrame']
U — unlock front-panel display. Safety escape hatch.
Wire: <uid><prefix>U\r. Response is a post-op data frame
without the :attr:StatusCode.LCK bit.
Intentionally NOT gated on :attr:Capability.DISPLAY (unlike
:data:LOCK_DISPLAY): the point of this command is to recover a
device that got into a locked state. Hardware validation (2026-04-17)
found that on V1_V7 firmware, any command starting with AL<X>
(including ALS / ALSS / ALV that the library itself
firmware-gates pre-I/O) is parsed by the device as
"lock display with argument X" and sets the LCK status bit.
The library's firmware gates protect against this under normal
use, but third-party code or direct catalog-command execution can
still trip it — dev.unlock_display() must always be callable
as the escape. AU is confirmed safe on V1_V7 (7v09) / V8_V9 /
V10; on a device without a physical display it's a harmless
no-op (the primer makes no firmware-cutoff claim).
UserData
dataclass
¶
UserData(
name="user_data",
token="UD",
response_mode=ResponseMode.LINE,
device_kinds=_ALL_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=_MIN_FIRMWARE_UD,
max_firmware=None,
firmware_families=frozenset(
{FirmwareFamily.V8_V9, FirmwareFamily.V10}
),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[UserDataRequest, UserDataSetting]
UD — read or write one of the four 32-char user-data slots.
Wire shape:
- Read:
<uid><prefix>UD <slot>\r - Write:
<uid><prefix>UD <slot> <value>\r
Response: <uid> <slot> <value> where value may contain
spaces. The decoder joins tokens after the slot into a single
string, preserving whatever the device echoed verbatim.
decode ¶
Parse <uid> <slot> <value...> into :class:UserDataSetting.
value may contain spaces; the decoder re-joins everything
after the slot field with single spaces so round-trip writes
of multi-word strings behave predictably.
Source code in src/alicatlib/commands/user_data.py
encode ¶
Emit UD read or write bytes.
Source code in src/alicatlib/commands/user_data.py
UserDataRequest
dataclass
¶
Arguments for :data:USER_DATA.
Attributes:
| Name | Type | Description |
|---|---|---|
slot |
int
|
Which 32-char slot to read / write — |
value |
str | None
|
|
ValveDrive
dataclass
¶
ValveDrive(
name="valve_drive",
token="VD",
response_mode=ResponseMode.LINE,
device_kinds=_CONTROLLER_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=_MIN_FIRMWARE_VD,
max_firmware=None,
firmware_families=frozenset(
{FirmwareFamily.V8_V9, FirmwareFamily.V10}
),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[ValveDriveRequest, ValveDriveState]
VD — query valve drive state (8v18+).
Wire: <uid><prefix>VD\r. Response is 2–4 whitespace-separated
tokens: <uid> <pct1> [<pct2>] [<pct3>]. The decoder returns a
:class:ValveDriveState whose valves tuple carries all
reported percentages.
Column count reflects the physical valve count of the
controller, but users should gate by :attr:Capability.MULTI_VALVE
/ :attr:THIRD_VALVE rather than infer from the reply (design §9):
the capability flags are probed once at open_device and
survive firmware quirks that VD's column count does not.
decode ¶
Parse <uid> <pct1> [<pct2>] [<pct3>] into :class:ValveDriveState.
Source code in src/alicatlib/commands/valve.py
encode ¶
Emit <uid><prefix>VD\r.
ValveDriveRequest
dataclass
¶
Arguments for :data:VALVE_DRIVE (empty — VD takes no arguments).
VeCommand
dataclass
¶
VeCommand(
name="ve_query",
token="VE",
response_mode=ResponseMode.LINE,
device_kinds=_ALL_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=None,
max_firmware=None,
firmware_families=frozenset(),
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[VeRequest, VeResult]
Firmware-version query. Works on every family; anchor of identification.
decode ¶
Parse the firmware version (and optional date) out of a VE reply.
Source code in src/alicatlib/commands/system.py
encode ¶
Emit <unit_id><prefix>VE\r.
VeResult
dataclass
¶
Typed response for :data:VE_QUERY.
:func:alicatlib.protocol.parser.parse_ve_response is intentionally
tolerant (the VE response shape varies across firmware families),
so we surface both the parsed :class:FirmwareVersion and the
optional firmware date to let callers use whichever they need without
having to re-parse the raw bytes.
ZeroBand
dataclass
¶
ZeroBand(
name="zero_band",
token="DCZ",
response_mode=ResponseMode.LINE,
device_kinds=_ALL_DEVICE_KINDS,
media=_DEFAULT_COMMAND_MEDIA,
required_capabilities=Capability.NONE,
min_firmware=_MIN_FIRMWARE_V10_05,
max_firmware=None,
firmware_families=_V10_ONLY,
destructive=False,
experimental=False,
case_sensitive=False,
prefix_less=False,
expected_lines=None,
is_complete=None,
)
Bases: Command[ZeroBandRequest, ZeroBandSetting]
DCZ — zero-band query/set (V10 10v05+).
Wire shape (primer p. 14):
- Query:
<uid><prefix>DCZ\r - Set:
<uid><prefix>DCZ 0 <zero_band>\r(the literal0is the primer's placeholder for a statistic slot thatDCZdoes not actually use).
Response: <uid> 0 <zero_band> (3 fields).
decode ¶
Parse <uid> 0 <zero_band> into :class:ZeroBandSetting.
Source code in src/alicatlib/commands/data_readings.py
encode ¶
Emit DCZ query or set bytes.
Source code in src/alicatlib/commands/data_readings.py
ZeroBandRequest
dataclass
¶
Arguments for :data:ZERO_BAND.
Attributes:
| Name | Type | Description |
|---|---|---|
zero_band |
float | None
|
Zero-band threshold as a percent of full scale.
|