Skip to content

sartoriuslib.commands

Declarative command specs and the per-protocol variant ABCs. See Commands for the catalogue and Design §4.2 for the layer's architecture.

Public surface

sartoriuslib.commands

Semantic command specs with per-protocol variants.

See design doc §4.2 (shape) and §6 (facade surface).

Command dataclass

Command(
    name,
    xbpi=None,
    sbi=None,
    family_hints=_NO_FAMILIES,
    capability_hints=NO_CAPABILITY,
    safety=SafetyTier.READ_ONLY,
    min_firmware=None,
    max_firmware=None,
    parameterized=False,
)

Declarative command spec.

Protocol mismatch (active protocol's variant is None) is the only hard pre-I/O gate that comes from this spec. family_hints and capability_hints are advisory priors consulted by the session — they upgrade to hard refusals only under strict=True (design doc §6.1).

parameterized flags commands whose request carries an application-level argument that selects a sub-resource (a sensor index, a parameter-table row, an area number, ...). Some firmwares answer xBPI 0x04 ("unsupported/unknown opcode") for an out-of-range argument value when they should answer 0x10 (index out of range) — the wire is technically incorrect but the device is what we have. The session translates 0x04 on a parameterized command into :class:SartoriusIndexOutOfRangeError (the semantic intent) and skips the :attr:Availability.UNSUPPORTED-sticky cache update so an out-of-range index for sensor 4 doesn't lock out sensors 0-3 for the rest of the session. Discovered on hardware day when probing temperature(4) poisoned temperature(0..3).

CommandContext dataclass

CommandContext(
    protocol,
    src_sbn=1,
    dst_sbn=9,
    firmware=None,
    family=BalanceFamily.UNKNOWN,
)

Context threaded through command encode/decode.

Variants stay pure functions of (ctx, request) -> bytes / (reply, ctx) -> Resp by receiving the small amount of session state they need through this struct: the active protocol, SBN addressing, and (when known) firmware and family.

SbiVariant

Bases: ABC

SBI variant of a command.

Declared alongside :class:XbpiVariant so :class:Command can carry both variant slots and the session can refuse pre-I/O when the active protocol has no variant.

expect_lines is the number of newline-terminated reply lines the variant expects. Control tokens like ESC T / ESC V set this to 0 because the device acknowledges silently.

decode abstractmethod

decode(reply, ctx)

Decode the SBI reply into the typed response.

Source code in src/sartoriuslib/commands/base.py
@abstractmethod
def decode(self, reply: SbiReply, ctx: CommandContext) -> Resp:
    """Decode the SBI reply into the typed response."""

encode abstractmethod

encode(ctx, request)

Encode request into the ASCII line(s) to send.

Source code in src/sartoriuslib/commands/base.py
@abstractmethod
def encode(self, ctx: CommandContext, request: Req) -> bytes:
    """Encode ``request`` into the ASCII line(s) to send."""

XbpiVariant

Bases: ABC

xBPI variant of a command.

Subclasses should set opcode as a class attribute and override :meth:encode and :meth:decode. Keep subclasses frozen (@dataclass(frozen=True, slots=True)) to preserve the "specs are immutable" invariant at the command layer.

decode abstractmethod

decode(reply, ctx)

Decode an already-validated reply frame into the typed response.

Source code in src/sartoriuslib/commands/base.py
@abstractmethod
def decode(self, reply: XbpiFrame, ctx: CommandContext) -> Resp:
    """Decode an already-validated reply frame into the typed response."""

encode abstractmethod

encode(ctx, request)

Encode request into full TX frame bytes (length-prefixed).

Source code in src/sartoriuslib/commands/base.py
@abstractmethod
def encode(self, ctx: CommandContext, request: Req) -> bytes:
    """Encode ``request`` into full TX frame bytes (length-prefixed)."""

Base — Command[Req, Resp], variants, context

sartoriuslib.commands.base

Command[Req, Resp] + XbpiVariant + SbiVariant + CommandContext.

One :class:Command carries at most one variant per protocol; either or both may be None. The session picks the variant matching its active protocol and dispatches through variant.encode / variant.decode.

Per design doc §4.2, per-protocol work lives on variant objects, not as methods bolted onto :class:Command. That keeps the opcode or SBI token co-located with the logic that uses it, makes "not implemented for this protocol" trivially expressible as None, and keeps the command dataclass pure metadata.

Command dataclass

Command(
    name,
    xbpi=None,
    sbi=None,
    family_hints=_NO_FAMILIES,
    capability_hints=NO_CAPABILITY,
    safety=SafetyTier.READ_ONLY,
    min_firmware=None,
    max_firmware=None,
    parameterized=False,
)

Declarative command spec.

Protocol mismatch (active protocol's variant is None) is the only hard pre-I/O gate that comes from this spec. family_hints and capability_hints are advisory priors consulted by the session — they upgrade to hard refusals only under strict=True (design doc §6.1).

parameterized flags commands whose request carries an application-level argument that selects a sub-resource (a sensor index, a parameter-table row, an area number, ...). Some firmwares answer xBPI 0x04 ("unsupported/unknown opcode") for an out-of-range argument value when they should answer 0x10 (index out of range) — the wire is technically incorrect but the device is what we have. The session translates 0x04 on a parameterized command into :class:SartoriusIndexOutOfRangeError (the semantic intent) and skips the :attr:Availability.UNSUPPORTED-sticky cache update so an out-of-range index for sensor 4 doesn't lock out sensors 0-3 for the rest of the session. Discovered on hardware day when probing temperature(4) poisoned temperature(0..3).

CommandContext dataclass

CommandContext(
    protocol,
    src_sbn=1,
    dst_sbn=9,
    firmware=None,
    family=BalanceFamily.UNKNOWN,
)

Context threaded through command encode/decode.

Variants stay pure functions of (ctx, request) -> bytes / (reply, ctx) -> Resp by receiving the small amount of session state they need through this struct: the active protocol, SBN addressing, and (when known) firmware and family.

SbiVariant

Bases: ABC

SBI variant of a command.

Declared alongside :class:XbpiVariant so :class:Command can carry both variant slots and the session can refuse pre-I/O when the active protocol has no variant.

expect_lines is the number of newline-terminated reply lines the variant expects. Control tokens like ESC T / ESC V set this to 0 because the device acknowledges silently.

decode abstractmethod

decode(reply, ctx)

Decode the SBI reply into the typed response.

Source code in src/sartoriuslib/commands/base.py
@abstractmethod
def decode(self, reply: SbiReply, ctx: CommandContext) -> Resp:
    """Decode the SBI reply into the typed response."""

encode abstractmethod

encode(ctx, request)

Encode request into the ASCII line(s) to send.

Source code in src/sartoriuslib/commands/base.py
@abstractmethod
def encode(self, ctx: CommandContext, request: Req) -> bytes:
    """Encode ``request`` into the ASCII line(s) to send."""

XbpiVariant

Bases: ABC

xBPI variant of a command.

Subclasses should set opcode as a class attribute and override :meth:encode and :meth:decode. Keep subclasses frozen (@dataclass(frozen=True, slots=True)) to preserve the "specs are immutable" invariant at the command layer.

decode abstractmethod

decode(reply, ctx)

Decode an already-validated reply frame into the typed response.

Source code in src/sartoriuslib/commands/base.py
@abstractmethod
def decode(self, reply: XbpiFrame, ctx: CommandContext) -> Resp:
    """Decode an already-validated reply frame into the typed response."""

encode abstractmethod

encode(ctx, request)

Encode request into full TX frame bytes (length-prefixed).

Source code in src/sartoriuslib/commands/base.py
@abstractmethod
def encode(self, ctx: CommandContext, request: Req) -> bytes:
    """Encode ``request`` into full TX frame bytes (length-prefixed)."""

Common request / response types

sartoriuslib.commands.common

Request/response dataclasses shared across command groups (stub).

Identity

sartoriuslib.commands.identity

Identity-read primitives composed by :meth:Balance.identify.

Each primitive decodes one opcode's reply into a typed value; the balance composes them into a :class:DeviceInfo.

xBPI opcodes per docs/protocol.md §7.1, §7.10:

  • 0x00 read_software_version → subtype 0x4A, 10-byte packed blob
  • 0x01 read_factory_number → subtype 0x45, 5-byte blob
  • 0x02 read_weigh_cell_model → subtype 0x54, 20-byte ASCII (null-padded)
  • 0x05 read_oem_text → subtype 0x50, 16-byte ASCII
  • 0x07 read_manufacturer → subtype 0x50, 16-byte ASCII
  • 0x71 read_sbn_address → subtype 0x21, 1 byte

IdentityRequest dataclass

IdentityRequest()

No-arg request for identity primitives.

Weight

sartoriuslib.commands.weight

Weight-read commands — net, gross, stored tare (std + hires variants).

xBPI opcodes per docs/protocol.md §7.4. All decode to the protocol-neutral :class:Reading dataclass via the shared :func:_measurement_reply_to_reading helper so the SBI variants produce semantically equivalent output.

hires variants (0x1F, 0x21) require a TLV-21 arg (1 = 10×, 2 = 100×). Standard-resolution reads (0x1E, 0x20, 0x22) take no args.

ReadWeightHiresRequest dataclass

ReadWeightHiresRequest(resolution=1)

High-resolution weight-read request.

resolution = 1 → 10× resolution (0.1 mg on a 1 mg balance). resolution = 2 → 100× resolution (0.01 mg).

ReadWeightRequest dataclass

ReadWeightRequest()

No-arg request for standard-resolution weight reads.

Tare / zero

sartoriuslib.commands.tare

Tare and zero commands — TARE (0x14) and ZERO (0x18).

Both return an xBPI ACK (subtype 0x00, empty body). Both are :attr:SafetyTier.STATEFUL — transient state change, no EEPROM write, runs freely without confirm=True (design §6.1). See docs/protocol.md §7.3.

TareRequest dataclass

TareRequest()

No-arg request; tare/zero take no parameters at the xBPI layer.

Status

sartoriuslib.commands.status

Status command — xBPI 0x30 (full 8-byte status block).

Decodes to the protocol-neutral :class:BalanceStatus dataclass. Derives :class:BalanceState from the state byte per docs/protocol.md §8.2: 0x82 overload, 0x84 underload, and bit 0x08 marks stable on both Cubis and non-Cubis families (just in different bytes — the parser already normalises).

StatusRequest dataclass

StatusRequest()

No-arg request for 0x30 read_balance_status_block.

Metrology

sartoriuslib.commands.metrology

Metrology commands — capacity, increment, temperature.

xBPI opcodes per docs/protocol.md §7.2 and §9:

  • 0x0C read_max → typed_float (Quantity), TLV-21 area arg
  • 0x0D read_increment → typed_float (Quantity), TLV-21 area arg
  • 0x76 read_temperature → typed_float (TemperatureReading), TLV-21 sensor arg

Capacity and increment are reported in the balance's current display unit; the opcode reply does not carry a self-describing unit byte (contrast the 8-byte measurement body's byte [6]). The typed results here therefore use :attr:Unit.UNKNOWN — callers who need a concrete unit read the display-unit parameter (p07) separately, or inspect Reading.unit from :meth:Balance.poll. This is more honest than guessing grams.

Temperature replies use the sentinel 7f ff ff ff for "sensor not installed" (docs/protocol.md §9). The :class:TemperatureReading decode maps that to celsius=None instead of NaN.

MetrologyRequest dataclass

MetrologyRequest(area=0)

Capacity / increment request.

area selects the weighing range on multi-range balances; single-range units (all currently captured) report the same value regardless. Defaults to 0 because that is the only area every balance is known to accept.

TemperatureRequest dataclass

TemperatureRequest(sensor=0)

Per-sensor temperature request (TLV-21 sensor index).

Parameters

sartoriuslib.commands.parameters

Parameter-table raw access — 0x55 read, 0x56 write.

Per docs/protocol.md §7.8 and §5.2 / §5.3:

  • 0x55 read_parameter: TX [0x55][TLV-21 idx], RX body contains two u8 TLVs (current, max). The subtype byte 0x21 doubles as the first TLV's tag, so decode prepends the subtype before parsing.
  • 0x56 write_parameter: TX [0x56][TLV-21 idx][TLV-21 val], RX is an xBPI ACK.

Writing is :attr:SafetyTier.PERSISTENT — the session refuses the call without confirm=True (design §6.1).

Typed accessors (Balance.get_filter_mode() / set_filter_mode()) build on these two primitives plus the :class:sartoriuslib.registry.parameters.ParameterSpec table.

ReadParameterRequest dataclass

ReadParameterRequest(index)

One parameter-table read at index.

WriteParameterRequest dataclass

WriteParameterRequest(index, value)

One parameter-table write: set index index to value.

Calibration

sartoriuslib.commands.calibration

Calibration commands — last cal record (0xB9) and internal adjust (0x28).

Per docs/protocol.md §7.7 and §7.12:

  • 0xB9 read_last_cal_record: subtype 0x51, 17-byte body. Layout in §7.12. Ignores TLV args. READ_ONLY.
  • 0x28 start_adjustment: TLV-21 arg selects cal type (e.g. 0x78 / 120 = internal adjust). Side-effecting, observable physically — DANGEROUS.

The CalRecord dataclass preserves the three-tier storage distinction from §7.12: :attr:CalRecord.temperature_celsius can be present while :attr:signature + :attr:counters are all-zero (the RAM metadata buffer was cleared by a cold boot but the temperature field lives in a separate backing location).

InternalAdjustRequest dataclass

InternalAdjustRequest(cal_type=INTERNAL_ADJUST_CAL_TYPE)

Start-adjustment request.

cal_type is the TLV-21 arg; defaults to :data:INTERNAL_ADJUST_CAL_TYPE.

LastCalRecordRequest dataclass

LastCalRecordRequest()

No-arg request for 0xB9.

System

sartoriuslib.commands.system

System commands — config counter, save/reload menu.

xBPI opcodes per docs/protocol.md §7.8 and §7.11:

  • 0xBA config_generation_counter: u8 register that increments on most runtime-config changes. The cache-invalidation signal that powers :class:Session's result cache. READ_ONLY.
  • 0x47 save_menu_to_eeprom: persist current menu to EEPROM. PERSISTENT — requires confirm=True.
  • 0x46 read_menu_from_eeprom: reload saved menu from EEPROM. PERSISTENT — requires confirm=True.

The 0xBA caveat from docs/protocol.md §10.1 and design doc §6.3: not every persistent write ticks the counter (p13 / p50 are known exceptions). The cache treats those writes as cache-invalidating regardless — see :class:ParameterSpec and the session's invalidation hooks.

SystemRequest dataclass

SystemRequest()

No-arg request shared by 0xBA / 0x47 / 0x46.

Raw

sartoriuslib.commands.raw

Raw protocol escape hatches for RE and advanced users.

Does not use the :class:Command spec machinery: the opcode is a per-call parameter, not a compile-time constant. Instead, the caller hands bytes to :meth:Balance.raw_xbpi, which routes through :meth:Session.execute_raw_xbpi. That method applies a single hard safety gate — the opcode must be on :data:SAFE_READ_ONLY_OPCODES or the caller must pass confirm=True. See design §6.1.

The SBI safe-list lives in :mod:sartoriuslib.protocol.sbi.tables because SBI tokens are also used by the line codec and raw CLI.