sartoriuslib.devices¶
Device-layer reference: direct package re-exports (Session,
BalanceFamily, Capability, SafetyTier, …) plus the Balance
facade, public dataclasses (Reading, BalanceStatus, DeviceInfo, …),
factory helpers, and discovery helpers by submodule. See
Balances, Readings, and
Design §5–§7.
Public surface¶
sartoriuslib.devices ¶
Device-layer package exports.
The subpackage root keeps a deliberately small re-export set for capability
and session primitives. The :class:Balance facade, factory helpers, models,
and discovery helpers live in their own submodules and are re-exported from
top-level :mod:sartoriuslib.
Availability ¶
Bases: StrEnum
Derived state the session consults when dispatching a command.
See design doc §5.1, §6.1.1.
INAPPLICABLE
class-attribute
instance-attribute
¶
Device responded with xBPI 0x06. Retryable; state-dependent.
SUPPORTED
class-attribute
instance-attribute
¶
Directly confirmed by a successful call or probe.
UNKNOWN
class-attribute
instance-attribute
¶
Never exercised; priors may exist but no device observation yet.
UNSUPPORTED
class-attribute
instance-attribute
¶
Device responded with xBPI 0x04 / equivalent SBI refusal. Sticky per session.
BalanceFamily ¶
Bases: StrEnum
Classification from the model string returned by xBPI 0x02 or SBI identify.
- :attr:
CUBIS— MSE and related Cubis strings; full xBPI plus Cubis extensions. - :attr:
OEM_WEIGH_CELL— WZ/WZA; ships from the factory in SBI autoprint (1200-7-O-1) and requires a front-panel menu change to switch to xBPI. (MSE and BCE also ship in SBI by default — switching to xBPI is a front-panel menu change on every family.) - :attr:
BASIC_LAB— BCE*; MSE opcode subset, no Cubis extensions. - :attr:
UNKNOWN— anything we have not classified; every call becomes a live probe.
Capability ¶
Bases: Flag
Feature capabilities derived from family defaults + live probing.
Flag bitmap carries capabilities currently believed SUPPORTED. Full
tri/quad-state per capability lives in DeviceInfo.probe_report.
ProbeSource ¶
Bases: StrEnum
Where an :class:Availability value came from.
FAMILY_TABLE
class-attribute
instance-attribute
¶
Seeded prior from our captures.
LIVE_CALL
class-attribute
instance-attribute
¶
Updated by the device's response to a normal command.
TARGETED_PROBE
class-attribute
instance-attribute
¶
Explicit probe during identify() / discovery.
USER_OVERRIDE
class-attribute
instance-attribute
¶
Set explicitly by the caller.
SafetyTier ¶
Bases: IntEnum
Per-command safety tier. See design doc §6.1.
DANGEROUS
class-attribute
instance-attribute
¶
Baud/SBN change, reset, calibration init, protocol switch. Requires confirm=True.
PERSISTENT
class-attribute
instance-attribute
¶
Parameter writes, save menu, communication settings. Requires confirm=True.
READ_ONLY
class-attribute
instance-attribute
¶
Weight, status, identity, capacity, increment, temperature, parameter reads.
STATEFUL
class-attribute
instance-attribute
¶
Transient state change (tare, zero). No EEPROM write.
Session ¶
Session(
*,
xbpi_client=None,
sbi_client=None,
active_protocol,
family=BalanceFamily.UNKNOWN,
capabilities=NO_CAPABILITY,
firmware=None,
src_sbn=1,
dst_sbn=9,
strict=False,
default_timeout=1.0,
serial_settings=None,
)
One balance, one serial port. Enforces gates, serialises I/O.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
xbpi_client
|
XbpiProtocolClient | None
|
Client for xBPI dispatch, or |
None
|
active_protocol
|
ProtocolKind
|
Which protocol this session currently speaks. |
required |
family
|
BalanceFamily
|
Balance family discriminator (from |
UNKNOWN
|
capabilities
|
Capability
|
Bitmap of capabilities believed present. |
NO_CAPABILITY
|
firmware
|
FirmwareVersion | None
|
Firmware version, if known. |
None
|
src_sbn
|
int
|
Host SBN address for xBPI frames. |
1
|
dst_sbn
|
int
|
Balance SBN address for xBPI frames. |
9
|
strict
|
bool
|
If |
False
|
default_timeout
|
float
|
Per-call timeout when the caller passes
|
1.0
|
Source code in src/sartoriuslib/devices/session.py
recoverable_error_count
property
¶
Number of recoverable errors swallowed-and-retried since open.
Bumped by :func:sartoriuslib.open_device's cold-open identify
retry loop and by any other transparent retry path the session
runs. Resets to 0 only on a fresh :class:Session. Consumers
read it directly or via :class:DeviceSnapshot.recoverable_error_count.
sbi_autoprint_active
property
¶
Whether the SBI session has observed unsolicited autoprint output.
serial_settings
property
¶
Serial settings the transport was opened with, if known.
Set at construction time by :func:sartoriuslib.open_device and
updated after a successful :meth:Balance.configure_protocol.
None for sessions built from a pre-existing :class:Transport
whose framing the library did not control.
transport
property
¶
The underlying :class:Transport, regardless of active protocol.
Both protocol clients hold the same transport — return whichever
is wired. The constructor enforces that the client matching
active_protocol is non-None, so this always returns a real
:class:Transport.
availability_of ¶
Current availability for command_name (UNKNOWN if unseen).
cache_snapshot ¶
Copy of cache_key → counter_snapshot for test assertions.
The actual cached values are intentionally not surfaced — tests assert on the presence and counter pinning of an entry, not its decoded content.
Source code in src/sartoriuslib/devices/session.py
cached_execute
async
¶
Dispatch command with a 0xBA-keyed result cache.
Sessions without :attr:Capability.CONFIG_COUNTER fall through
to :meth:execute. When the capability is present, the session
re-reads the config counter before returning a cached value;
any change flushes the entry and the command re-runs.
cache_key is caller-supplied so a command that takes
arguments (capacity(area=N), read_parameter(idx)) can
cache separate entries per distinct call.
Source code in src/sartoriuslib/devices/session.py
check_state ¶
Raise :class:SartoriusConnectionError if the session is BROKEN.
Public alias for the internal gate run by every dispatch path,
so lifecycle helpers on :class:Balance can reuse the same
guard without poking at private attributes.
Source code in src/sartoriuslib/devices/session.py
close
async
¶
Close the underlying transport, if one is wired.
Idempotent — safe to call multiple times. The factory owns the transport's construction and hands it into the session via the protocol client; closing the session closes the transport.
Both clients hold the same transport, so close it once via the
session's :attr:transport accessor rather than once per client
slot — guards against a future caller that wires both clients
simultaneously.
Source code in src/sartoriuslib/devices/session.py
execute
async
¶
Dispatch command with full pre-I/O gating.
Gates fire in the design doc §6.1 order; each raise happens before any byte is sent, so a gate failure is observably equivalent to the call never leaving the host.
Source code in src/sartoriuslib/devices/session.py
execute_raw_sbi
async
¶
Send an arbitrary SBI command token and return the parsed reply.
Source code in src/sartoriuslib/devices/session.py
execute_raw_xbpi
async
¶
Send an arbitrary xBPI opcode and return the raw reply frame.
Bypasses the declarative :class:Command layer — the opcode is
a per-call parameter, so none of the prior / capability gating
applies. The one hard gate is a safe-list check: opcodes in
:data:sartoriuslib.commands.raw.SAFE_READ_ONLY_OPCODES run
freely; anything else requires confirm=True because the
library cannot know it is safe.
The availability cache is not updated — raw calls are opaque at the command-name level.
Source code in src/sartoriuslib/devices/session.py
invalidate_cache ¶
Drop one cached entry, or clear all when cache_key is None.
The :class:Balance facade calls this after writes whose
0xBA bump isn't guaranteed (the §6.3 caveat). Unknown
keys are a silent no-op — idempotent.
Source code in src/sartoriuslib/devices/session.py
mark_broken ¶
Transition the session to :attr:SessionState.BROKEN.
Called only from lifecycle operations
(Balance.configure_protocol) when a rollback fails. Once
broken, every subsequent dispatch refuses with
:class:SartoriusConnectionError.
Source code in src/sartoriuslib/devices/session.py
read_sbi_autoprint_reading
async
¶
Read the next valid SBI autoprint weight line without writing.
Source code in src/sartoriuslib/devices/session.py
read_sbi_line
async
¶
Read one unsolicited SBI line, used by autoprint streaming.
Source code in src/sartoriuslib/devices/session.py
record_recoverable_error ¶
Increment the recoverable-error counter.
Called from retry sites — :func:sartoriuslib.open_device's
cold-open swallow loop, future inline transient retries. Public
because the counter is observed by :class:DeviceSnapshot;
callers should not normally mutate it.
Source code in src/sartoriuslib/devices/session.py
refresh_sbi_autoprint_state
async
¶
Passively re-sniff whether SBI autoprint is currently active.
Source code in src/sartoriuslib/devices/session.py
replace_clients ¶
Swap protocol clients atomically — used by configure_protocol.
The host-side flip closes the old protocol client, reopens the
transport at new serial framing, builds a new client, and
verifies. On verification success this method installs the new
clients and the new active protocol; the availability cache,
prior warnings, and result cache are all
cleared because the command surface changes when the protocol
does (xBPI-only commands have no SBI variant and vice versa,
and any 0xBA-pinned cache entries belong to the old session).
Refuses to install ProtocolKind.AUTO — detection must
resolve to XBPI or SBI first. Refuses if the
corresponding client for active_protocol is missing.
Source code in src/sartoriuslib/devices/session.py
set_dst_sbn ¶
Update the destination SBN.
Used after :meth:Balance.write_sbn_address on multidrop links
where the new address must address the device going forward.
Source code in src/sartoriuslib/devices/session.py
update_identity ¶
Replace session-level identity state after a live identify call.
Called by :func:sartoriuslib.devices.factory.open_device after
running the identify commands, so subsequent prior gating sees
the discovered family and capabilities instead of the
placeholder UNKNOWN / empty values.
Each argument left as None keeps the existing value.
Source code in src/sartoriuslib/devices/session.py
Public dataclasses¶
sartoriuslib.devices.models ¶
Public frozen dataclasses returned by the :class:Balance facade.
See design doc §7. All types are immutable (frozen=True, slots=True) so
they are safe to share, pass across task boundaries, and log.
Reading and BalanceStatus are protocol-neutral: the xBPI and SBI
decoders both build the same shape. That is the whole point of the
dual-protocol API (design §4).
BalanceState ¶
Bases: StrEnum
High-level weighing state derived from the status block.
BalanceStatus
dataclass
¶
Status-block snapshot from xBPI 0x30 (or SBI equivalent).
adc_trusted and isocal_due are MSE-only signals; on WZA/BCE
they decode to None. raw_state and raw_status are the
untouched wire bytes (as integers for xBPI, strings for SBI where
applicable) so callers can cross-check against docs/protocol.md
§8.2 without re-decoding.
CalRecord
dataclass
¶
Last-calibration snapshot from 0xB9.
Layout per docs/protocol.md §7.12. The 17-byte RAM buffer is
cleared on cold boot, so :attr:temperature_celsius can be present
(the kernel maintains it separately) while :attr:signature and
:attr:counters are all-zero. Callers that just want "was there a
cal?" should check :attr:has_metadata.
has_metadata
property
¶
True if any metadata byte is non-zero.
All-zero :attr:signature + :attr:counters means the balance
has never recorded a cal in the current RAM buffer (post cold
boot). :attr:temperature_celsius can still be valid in that
state — see §7.12's three-tier storage note.
DeviceInfo
dataclass
¶
DeviceInfo(
manufacturer,
model,
serial,
factory_number,
software,
firmware,
family,
protocol,
capacity,
increment,
sbn,
serial_settings,
capabilities,
probe_report=_empty_probe_report(),
temperature_sensor_indices=None,
)
Identity snapshot produced by :meth:Balance.identify.
Populated at :func:open_device time when identify=True and
cached on the :class:Balance. Most fields are None for
balances we have not yet RE'd beyond the model-string classifier.
capacity and increment are populated by the metrology
probe and otherwise default to None. capabilities is
seeded from the family discriminator at identify time and
refined as commands probe the device.
temperature_sensor_indices is populated only when a caller
has explicitly run :meth:Balance.discover_temperature_sensors,
which probes the device at runtime and records exactly which
indices replied. None (the default) means "not yet probed" —
no assumption baked in. Some firmwares expose sparse indices
(the MSE1203S we tested replies at 0, 1, 3 and the
7f ff ff ff sentinel at 2), some expose contiguous, some
expose none at all; the device is the source of truth.
ParameterEntry
dataclass
¶
One parameter-table entry from 0x55.
current and max are the two u8 TLVs returned in the reply.
Callers normally route through the typed Balance.get_X() /
Balance.set_X() accessors which decode current through the
:class:sartoriuslib.registry.parameters.ParameterSpec table.
ProbeOutcome
dataclass
¶
One capability's current availability plus provenance.
See design §5.1: Availability is the derived state, while
ProbeOutcome is the observation record that produced it.
Quantity
dataclass
¶
Scalar value with its unit. Used for capacity, increment, etc.
Reading
dataclass
¶
Reading(
value,
unit,
sign,
stable,
overload,
underload,
decimals,
sequence,
status_flags,
protocol,
received_at,
monotonic_ns,
raw,
)
One decoded weight reading.
value is None on the off-scale sentinel; the measurement body
alone cannot disambiguate overload from underload, so callers that
need that distinction should invoke :meth:Balance.status.
stable comes from the universal measurement-frame flag bit
0x40 (design §7 note) — more portable across MSE/WZA/BCE than
the family-specific status-block state byte.
status_flags carries a bag of protocol-specific signals
("stable", "off_scale", and in long-frame reads
"isocal_due" / "adc_trusted") so power users can inspect
without reaching for the raw bytes.
__format__ ¶
Delegate format specs to :attr:value so f"{r:.4f}" works.
The empty spec falls back to :func:str (the frozen
dataclass default) so f"{r}" still prints the structured
repr. Off-scale readings (value is None) format as
"None" for any non-empty numeric spec rather than raising —
a stream of mixed valid/None readings is the common case during
a tare or zero settling window and crashing in a log-line
f-string would be a surprising failure mode.
Source code in src/sartoriuslib/devices/models.py
as_dict ¶
Flatten the reading into a row-shaped dict for tabular sinks.
Content-only — timing provenance (received_at,
monotonic_ns) lives on the surrounding
:class:~sartoriuslib.streaming.sample.Sample because sample-
level send/receive boundaries are the authoritative timeline
(design §10). Booleans render as 0 / 1 so SQLite picks
INTEGER affinity and CSV / JSONL round-trip cleanly through
every stdlib reader.
Source code in src/sartoriuslib/devices/models.py
TemperatureReading
dataclass
¶
One sensor's temperature read (xBPI 0x76).
:attr:celsius is None when the sensor index is not installed
— the balance returns the 7f ff ff ff sentinel in that case
(docs/protocol.md §9). :attr:sensor is the TLV-21 index the
caller passed; :attr:raw is the 5-byte typed-float body.
Family taxonomy¶
sartoriuslib.devices.kind ¶
Balance family taxonomy. See design doc §5.
BalanceFamily ¶
Bases: StrEnum
Classification from the model string returned by xBPI 0x02 or SBI identify.
- :attr:
CUBIS— MSE and related Cubis strings; full xBPI plus Cubis extensions. - :attr:
OEM_WEIGH_CELL— WZ/WZA; ships from the factory in SBI autoprint (1200-7-O-1) and requires a front-panel menu change to switch to xBPI. (MSE and BCE also ship in SBI by default — switching to xBPI is a front-panel menu change on every family.) - :attr:
BASIC_LAB— BCE*; MSE opcode subset, no Cubis extensions. - :attr:
UNKNOWN— anything we have not classified; every call becomes a live probe.
classify_family ¶
Classify a balance family by its model-string prefix.
Rules (design §5):
MSE*and related Cubis strings → :attr:BalanceFamily.CUBISWZ*/WZA*→ :attr:BalanceFamily.OEM_WEIGH_CELLBCE*→ :attr:BalanceFamily.BASIC_LAB- anything else → :attr:
BalanceFamily.UNKNOWN
Case-insensitive and whitespace-tolerant. Returns UNKNOWN for
empty input — every call becomes a live probe for unclassified
devices (design §5.1).
Source code in src/sartoriuslib/devices/kind.py
Capability flags + safety tier¶
sartoriuslib.devices.capability ¶
Capability flags, safety tiers, and probe/availability enums.
See design doc §5 (families + capabilities) and §6.1 (gates).
Availability ¶
Bases: StrEnum
Derived state the session consults when dispatching a command.
See design doc §5.1, §6.1.1.
INAPPLICABLE
class-attribute
instance-attribute
¶
Device responded with xBPI 0x06. Retryable; state-dependent.
SUPPORTED
class-attribute
instance-attribute
¶
Directly confirmed by a successful call or probe.
UNKNOWN
class-attribute
instance-attribute
¶
Never exercised; priors may exist but no device observation yet.
UNSUPPORTED
class-attribute
instance-attribute
¶
Device responded with xBPI 0x04 / equivalent SBI refusal. Sticky per session.
Capability ¶
Bases: Flag
Feature capabilities derived from family defaults + live probing.
Flag bitmap carries capabilities currently believed SUPPORTED. Full
tri/quad-state per capability lives in DeviceInfo.probe_report.
ProbeSource ¶
Bases: StrEnum
Where an :class:Availability value came from.
FAMILY_TABLE
class-attribute
instance-attribute
¶
Seeded prior from our captures.
LIVE_CALL
class-attribute
instance-attribute
¶
Updated by the device's response to a normal command.
TARGETED_PROBE
class-attribute
instance-attribute
¶
Explicit probe during identify() / discovery.
USER_OVERRIDE
class-attribute
instance-attribute
¶
Set explicitly by the caller.
SafetyTier ¶
Bases: IntEnum
Per-command safety tier. See design doc §6.1.
DANGEROUS
class-attribute
instance-attribute
¶
Baud/SBN change, reset, calibration init, protocol switch. Requires confirm=True.
PERSISTENT
class-attribute
instance-attribute
¶
Parameter writes, save menu, communication settings. Requires confirm=True.
READ_ONLY
class-attribute
instance-attribute
¶
Weight, status, identity, capacity, increment, temperature, parameter reads.
STATEFUL
class-attribute
instance-attribute
¶
Transient state change (tare, zero). No EEPROM write.
Balance facade¶
sartoriuslib.devices.balance ¶
The :class:Balance facade.
One protocol-neutral class — design §5 ("no family subclasses"). The
balance dispatches every call through :meth:Session.execute so all
gates run before any byte leaves the host, and runtime behaviour stays
identical whether the device is on xBPI or SBI.
Surfaces:
Weight & state:
:meth:poll, :meth:read_net, :meth:read_gross,
:meth:read_tare_value, :meth:tare, :meth:zero,
:meth:status, :meth:identify, :meth:raw_xbpi.
Metrology, parameters, cal, persistence:
:meth:capacity, :meth:increment, :meth:temperature,
:meth:read_parameter, :meth:write_parameter,
:meth:save_menu, :meth:reload_menu,
:meth:last_cal_record, :meth:internal_adjust.
Typed parameter pairs
:meth:get_filter_mode / :meth:set_filter_mode,
:meth:get_display_unit / :meth:set_display_unit,
:meth:get_auto_zero / :meth:set_auto_zero,
:meth:get_isocal_mode / :meth:set_isocal_mode,
:meth:get_tare_behavior / :meth:set_tare_behavior,
:meth:get_menu_access / :meth:set_menu_access.
Balance ¶
Protocol-neutral balance facade.
Construct via :func:sartoriuslib.open_device. Every method is a
thin wrapper around :meth:Session.execute (or
:meth:Session.cached_execute for repeat-read metrology /
parameter accessors); the session owns the I/O lock and runs the
pre-I/O safety / protocol / availability / prior gates
(design §6.1).
Source code in src/sartoriuslib/devices/balance.py
info
property
¶
Identity snapshot from the last :meth:identify call (or None).
Populated automatically by :func:open_device when
identify=True.
capacity
async
¶
Read the weighing capacity for area (xBPI 0x0C).
Cached by the session when :attr:Capability.CONFIG_COUNTER
is present — the balance's 0xBA counter bumps on display-
accuracy changes (p08) which is the only thing that would
move this value in practice.
The wire's typed-float reply does not carry a unit byte
(contrast the 8-byte measurement body's byte [6]). To return
a complete :class:Quantity we read the current display unit
(p07) and fold it in here. get_display_unit() is itself
cached on 0xBA via the parameter-table cache, so two
successive capacity() calls only re-read 0xBA (twice,
once per call) and not 0x0C or 0x55. If
get_display_unit() itself fails (e.g. the parameter table
is unreachable), the unit falls back to :attr:Unit.UNKNOWN
and the numeric value still returns — fail-open is more useful
than a hard error for a metadata read.
Source code in src/sartoriuslib/devices/balance.py
close
async
¶
configure_protocol
async
¶
configure_protocol(
target,
*,
baudrate=None,
parity=None,
stopbits=None,
timeout=None,
confirm=False,
)
Switch this balance's active wire protocol (and optionally framing).
DANGEROUS — requires confirm=True. The flip is purely
host-side: per docs/protocol.md §2.1 the device's protocol
mode changes via the front-panel menu, never via xBPI for the
PC-USB port (verified empirically 2026-04-25 on MSE1203S — the
PC-USB protocol selector is not in the xBPI parameter table at
all; only Device → PC-USB → Dat.Rec. on the front panel
flips it). On the 9-pin peripheral port, p35 write +
SAVE_MENU + cold boot does work programmatically, but most
users are on PC-USB.
This method reconciles the host with the user's front-panel
change by closing the current protocol client, reopening the
transport at the new serial framing (any None argument keeps
the existing value), and building the new client. It then
verifies via an identity probe and refreshes :attr:info from
the new protocol.
If the user has NOT actually flipped the front panel before
calling this method, the post-switch identity probe will fail
— typically with a :class:SartoriusFrameError ("bad marker
byte 0x20") when xBPI is requested but the wire is still
emitting SBI autoprint, or a timeout when SBI is requested
but the wire is still on xBPI. Treat that error as a strong
signal that the front-panel mode does not match target.
On any failure during the switch the method attempts to roll
back to the original serial framing. If that rollback also
fails the underlying :class:Session transitions to
:attr:SessionState.BROKEN and a
:class:SartoriusConnectionError is raised — the caller must
close this balance and re-open via
:func:sartoriuslib.open_device to recover.
Same-protocol no-op: when target equals the current
protocol and no framing override is supplied, the call returns
the cached :class:DeviceInfo (or runs :meth:identify if
none has been cached yet) without touching the transport.
Source code in src/sartoriuslib/devices/balance.py
1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 | |
discover_temperature_sensors
async
¶
Probe the device for installed temperature sensors at runtime.
Iterates indices 0..max_index calling :meth:temperature
on each. Records every index that replies — both real
readings (celsius is a float) and sentinel slots
(celsius is None because the firmware returned
7f ff ff ff). Stops early on
:class:SartoriusIndexOutOfRangeError (0x10), which the
device emits past the last valid index. A 0x03
value-out-of-range reply (how a WZ8202 reports an absent sensor
slot, instead of the sentinel) is treated as an empty slot and
skipped — probing continues to max_index so a sparse map is
not truncated at the first gap. Updates the cached
:class:DeviceInfo's
:attr:temperature_sensor_indices with the discovered tuple
and returns it.
Device-agnostic by design — no family table is consulted, so a balance we have never tested still produces an honest sensor map.
max_index is a safety cap, not a contract: 8 is the
default headroom (Cubis MSE captures stop at 3). Probing a
device with no sensors produces an empty tuple. Each probe
is one round-trip; the result is not cached on 0xBA
because per-call temperature reads are not cached either —
callers re-discover on demand.
Source code in src/sartoriuslib/devices/balance.py
get_auto_zero
async
¶
Read p06 (auto-zero tracking) as an :class:AutoZeroMode.
Source code in src/sartoriuslib/devices/balance.py
get_display_unit
async
¶
Read p07 (display unit) as a :class:Unit.
Source code in src/sartoriuslib/devices/balance.py
get_filter_mode
async
¶
Read p01 (filter mode) as a :class:FilterMode.
Source code in src/sartoriuslib/devices/balance.py
get_isocal_mode
async
¶
Read p15 (isoCAL mode) as an :class:IsoCalMode (Cubis only).
Source code in src/sartoriuslib/devices/balance.py
get_menu_access
async
¶
Read p40 (front-panel menu lock) as :class:MenuAccessMode.
Source code in src/sartoriuslib/devices/balance.py
get_tare_behavior
async
¶
Read p05 (tare-on-stability behaviour) as :class:TareBehavior.
Source code in src/sartoriuslib/devices/balance.py
identify
async
¶
Read every identity opcode and compose a :class:DeviceInfo.
Runs in sequence — the session's I/O lock keeps them serialised
on the wire. The session carries the serial framing it was
opened with (the device doesn't expose its own baud/parity), so
:class:DeviceInfo reports those settings directly; sessions
constructed without framing fall back to a placeholder.
After the textual identity primitives, the factory probes
capacity and increment via :meth:capacity / :meth:increment
and writes them onto the returned :class:DeviceInfo for
WZG-family balances where the metrology commands are known to
respond. Failures are swallowed — a balance that refuses
0x0C keeps the old behaviour of capacity=None.
Source code in src/sartoriuslib/devices/balance.py
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 | |
increment
async
¶
Read the display increment for area (xBPI 0x0D).
See :meth:capacity for the caching contract and the
composite-unit fold.
Source code in src/sartoriuslib/devices/balance.py
internal_adjust
async
¶
Start an internal adjustment (xBPI 0x28). DANGEROUS.
cal_type defaults to the canonical internal-adjust selector
(0x78) — see
:data:sartoriuslib.commands.calibration.INTERNAL_ADJUST_CAL_TYPE.
Callers can pass another value in the 0x70..0x7B range to
drive external cal / linearization variants per
docs/protocol.md §7.7.
Source code in src/sartoriuslib/devices/balance.py
last_cal_record
async
¶
Read the last-calibration snapshot (xBPI 0xB9, §7.12).
poll
async
¶
Read the live net weight at standard resolution.
Short-cut for :meth:read_net with no arguments. One-shot
request/response; to stream at a cadence use
:func:sartoriuslib.streaming.record.
Source code in src/sartoriuslib/devices/balance.py
raw_sbi
async
¶
Send an arbitrary SBI command and return parsed line replies.
Source code in src/sartoriuslib/devices/balance.py
raw_xbpi
async
¶
Send an arbitrary xBPI opcode and return the raw reply frame.
Opcodes in the built-in read-only safe-list
(:data:sartoriuslib.commands.raw.SAFE_READ_ONLY_OPCODES) run
freely; anything else requires confirm=True. Intended for
RE and one-off probes — typed commands are the preferred path
for everything the library already models.
Source code in src/sartoriuslib/devices/balance.py
read_gross
async
¶
Read the gross weight.
Source code in src/sartoriuslib/devices/balance.py
read_net
async
¶
Read the net weight.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hires
|
int
|
|
0
|
Source code in src/sartoriuslib/devices/balance.py
read_parameter
async
¶
Read one parameter-table entry (xBPI 0x55).
Returns the (current, max) u8 pair untouched; typed
accessors layer on top to decode via the
:class:sartoriuslib.registry.parameters.ParameterSpec table.
Cached on 0xBA.
Source code in src/sartoriuslib/devices/balance.py
read_tare_value
async
¶
Read the stored tare value (the reference, not a live operation).
refresh_sbi_autoprint_state
async
¶
Re-sniff whether an SBI session is currently in autoprint mode.
Use this after changing autoprint from the balance front panel during an open session. A quiet sniff clears autoprint mode so command/reply SBI APIs become available again; observed output keeps the session in consume-only autoprint mode.
Source code in src/sartoriuslib/devices/balance.py
reload_menu
async
¶
Reload the saved menu from EEPROM (xBPI 0x46).
save_menu
async
¶
Persist the current runtime menu to EEPROM (xBPI 0x47).
Source code in src/sartoriuslib/devices/balance.py
set_auto_zero
async
¶
Write p06. Accepts :class:AutoZeroMode, a string, or wire int.
Source code in src/sartoriuslib/devices/balance.py
set_baud_rate
async
¶
Send xBPI 0x5C and reopen the transport at the new baud.
DANGEROUS — requires confirm=True. wire_code is the
device-side encoding from docs/protocol.md §7.10
(0x00=9600, 0x01=19200, 0x02=38400, 0x03=57600);
baudrate is the matching host-side baud (the transport's
framing is reopened at this value). The library does not map
between the two automatically because the encoding is
documented as "different from p31 / p63" and we have no RE
captures verifying the mapping yet — the caller passes both so
the on-wire byte and the host framing stay explicit.
After the device-side ACK the transport is reopened, identity
is reprobed for verification, and the cached :class:DeviceInfo
is refreshed. Verification failure rolls the transport back to
the original baud; if rollback fails the session enters
:attr:SessionState.BROKEN.
SBI sessions are not supported here — the 0x5C opcode is
xBPI-only. Call :meth:configure_protocol first to switch to
xBPI if needed.
Source code in src/sartoriuslib/devices/balance.py
1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 | |
set_display_unit
async
¶
Write p07. Accepts :class:Unit, a fuzzy string, or wire code.
Source code in src/sartoriuslib/devices/balance.py
set_filter_mode
async
¶
Write p01. Accepts :class:FilterMode, a fuzzy string, or wire int.
Fuzzy strings ("stable" / "very stable" / "vs")
route through :func:resolve_filter_mode.
Source code in src/sartoriuslib/devices/balance.py
set_isocal_mode
async
¶
Write p15.
Source code in src/sartoriuslib/devices/balance.py
set_menu_access
async
¶
Write p40.
Source code in src/sartoriuslib/devices/balance.py
set_tare_behavior
async
¶
Write p05.
Source code in src/sartoriuslib/devices/balance.py
snapshot
async
¶
Return a cached identity + health snapshot — no I/O.
Builds the snapshot from :attr:info (the cached
:class:DeviceInfo) and the underlying :class:Session
counters. Safe to call any time, including from a hot path —
the cost is the dataclass construction.
family, capabilities, protocol are sourced from the
session (so they reflect the live identity even when info
is None). mode is reserved for a future mode-tracking
hook; today it is always None (the snapshot is no-I/O by
contract, so it cannot probe).
Source code in src/sartoriuslib/devices/balance.py
status
async
¶
stream ¶
Create a per-balance streaming session.
mode="poll" is the default and requires rate_hz.
mode="autoprint" consumes existing SBI autoprint output without
changing device settings. temporary_autoprint=True is reserved
for the future "enable on entry, restore on exit" SBI parameter flow
and currently raises :class:NotImplementedError.
Source code in src/sartoriuslib/devices/balance.py
tare
async
¶
temperature
async
¶
Read one temperature sensor (xBPI 0x76).
Returns a :class:TemperatureReading with celsius=None
when sensor is not installed (balance returns the
7f ff ff ff sentinel per docs/protocol.md §9).
Not cached — temperature changes continuously and callers expect a fresh read every call.
Sensor indexing is device-specific — some firmwares are
contiguous, some are sparse with reserved slots (the MSE1203S
we tested has sensors at 0/1/3 and a sentinel at 2).
Use :meth:discover_temperature_sensors to enumerate.
READ_TEMPERATURE is :attr:Command.parameterized, so an
out-of-range index raises
:class:SartoriusIndexOutOfRangeError without poisoning the
availability cache for in-range indices.
Source code in src/sartoriuslib/devices/balance.py
write_parameter
async
¶
Write one parameter-table entry (xBPI 0x56). PERSISTENT.
Requires confirm=True. Invalidates the cached entry for
index afterwards — conservative so the §6.3 caveat rows
(p13 / p50, whose writes don't bump 0xBA) stay
consistent.
Source code in src/sartoriuslib/devices/balance.py
write_sbn_address
async
¶
Send xBPI 0x72 to change the balance's SBN address.
DANGEROUS — requires confirm=True. Returns the value
read back via 0x71 after the write so the caller can
verify. The session's dst_sbn is left unchanged by default
because the balance accepts dst_sbn=0x09 regardless of its
configured SBN on a direct point-to-point link
(docs/protocol.md §2.2). Pass update_session_dst=True
on multidrop links where the new SBN must address the device
going forward.
Source code in src/sartoriuslib/devices/balance.py
DeviceSnapshot
dataclass
¶
DeviceSnapshot(
name,
model,
firmware,
serial,
connected,
last_error,
recoverable_error_count,
captured_at,
)
Cross-library identity + health snapshot.
Built from cached state — :meth:Balance.snapshot never performs
I/O. Sibling libraries (alicat, watlow, nidaq) expose the same
base shape per unified spec §H so multi-adapter consumers can
render every device's snapshot uniformly.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Device identifier (manager-style name; model fallback when the balance is not under a manager). |
model |
str | None
|
Cached model string, or |
firmware |
str | None
|
Cached firmware version string, or |
serial |
str | None
|
Cached serial / factory-number string, or |
connected |
bool
|
Whether the underlying session is operational. |
last_error |
ErrorContext | None
|
Last error context the session attached to a
failure, or |
recoverable_error_count |
int
|
How many transient errors the session has retried through transparently since open. |
captured_at |
datetime
|
Wall-clock instant the snapshot was taken (UTC, tz-aware). |
SartoriusDeviceSnapshot
dataclass
¶
SartoriusDeviceSnapshot(
name,
model,
firmware,
serial,
connected,
last_error,
recoverable_error_count,
captured_at,
family,
capabilities,
protocol,
mode,
)
Bases: DeviceSnapshot
Sartorius-typed snapshot extras.
Adds the family classification, capability bitmap, active protocol,
and last-observed mode (None if mode has never been observed
on this session — :meth:Balance.snapshot does not probe to find
out, by design).
Attributes:
| Name | Type | Description |
|---|---|---|
family |
BalanceFamily | None
|
Cached :class: |
capabilities |
Capability
|
Bitmap of capabilities the session believes the balance has. |
protocol |
ProtocolKind
|
Active wire protocol on the underlying session. |
mode |
str | None
|
Last-observed application mode if the session has tracked
one. |
Session¶
sartoriuslib.devices.session ¶
Session: I/O lock, safety gates, availability cache, prior checks.
One :class:Session wraps one balance on one serial port. It is the
single dispatch point between the :class:Balance facade and the
protocol clients. Every :meth:Session.execute call walks the
gates in the design-doc §6.1 order, then runs the command via the
active protocol's client, then updates the per-command availability
cache per §6.1.1.
What lives here
- Active-protocol selection (xBPI vs SBI).
- Safety-tier gate (PERSISTENT / DANGEROUS need confirm=True).
- Protocol gate (command's variant for the active protocol must be set).
- Availability gate (commands known UNSUPPORTED short-circuit pre-I/O).
- Prior gate (family / capability hints; soft by default, hard under strict).
- Per-call availability update (success → SUPPORTED, 0x04 → UNSUPPORTED,
0x06 → INAPPLICABLE, else unchanged).
What does not live here - Transport I/O: delegated to the protocol client. - Device identification: the factory pre-computes family / capabilities / firmware and hands them in at construction.
Result cache
:meth:cached_execute fronts results keyed on command + caller-
supplied cache_key and xBPI's 0xBA config counter
(docs/protocol.md §7.11 and design §6.3). Before returning a
cached value, the session re-reads 0xBA; a mismatch flushes the
entry. Writes invalidate the affected key explicitly — the §6.3
caveat says p13 / p50 writes don't bump the counter, so the
cache stays correct only if the :class:Balance facade clears
those entries on its own.
Sessions without :attr:`Capability.CONFIG_COUNTER` (WZA, SBI) fall
back to un-cached dispatch transparently — ``cached_execute``
behaves identically to :meth:`execute` in that mode.
Session ¶
Session(
*,
xbpi_client=None,
sbi_client=None,
active_protocol,
family=BalanceFamily.UNKNOWN,
capabilities=NO_CAPABILITY,
firmware=None,
src_sbn=1,
dst_sbn=9,
strict=False,
default_timeout=1.0,
serial_settings=None,
)
One balance, one serial port. Enforces gates, serialises I/O.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
xbpi_client
|
XbpiProtocolClient | None
|
Client for xBPI dispatch, or |
None
|
active_protocol
|
ProtocolKind
|
Which protocol this session currently speaks. |
required |
family
|
BalanceFamily
|
Balance family discriminator (from |
UNKNOWN
|
capabilities
|
Capability
|
Bitmap of capabilities believed present. |
NO_CAPABILITY
|
firmware
|
FirmwareVersion | None
|
Firmware version, if known. |
None
|
src_sbn
|
int
|
Host SBN address for xBPI frames. |
1
|
dst_sbn
|
int
|
Balance SBN address for xBPI frames. |
9
|
strict
|
bool
|
If |
False
|
default_timeout
|
float
|
Per-call timeout when the caller passes
|
1.0
|
Source code in src/sartoriuslib/devices/session.py
recoverable_error_count
property
¶
Number of recoverable errors swallowed-and-retried since open.
Bumped by :func:sartoriuslib.open_device's cold-open identify
retry loop and by any other transparent retry path the session
runs. Resets to 0 only on a fresh :class:Session. Consumers
read it directly or via :class:DeviceSnapshot.recoverable_error_count.
sbi_autoprint_active
property
¶
Whether the SBI session has observed unsolicited autoprint output.
serial_settings
property
¶
Serial settings the transport was opened with, if known.
Set at construction time by :func:sartoriuslib.open_device and
updated after a successful :meth:Balance.configure_protocol.
None for sessions built from a pre-existing :class:Transport
whose framing the library did not control.
transport
property
¶
The underlying :class:Transport, regardless of active protocol.
Both protocol clients hold the same transport — return whichever
is wired. The constructor enforces that the client matching
active_protocol is non-None, so this always returns a real
:class:Transport.
availability_of ¶
Current availability for command_name (UNKNOWN if unseen).
cache_snapshot ¶
Copy of cache_key → counter_snapshot for test assertions.
The actual cached values are intentionally not surfaced — tests assert on the presence and counter pinning of an entry, not its decoded content.
Source code in src/sartoriuslib/devices/session.py
cached_execute
async
¶
Dispatch command with a 0xBA-keyed result cache.
Sessions without :attr:Capability.CONFIG_COUNTER fall through
to :meth:execute. When the capability is present, the session
re-reads the config counter before returning a cached value;
any change flushes the entry and the command re-runs.
cache_key is caller-supplied so a command that takes
arguments (capacity(area=N), read_parameter(idx)) can
cache separate entries per distinct call.
Source code in src/sartoriuslib/devices/session.py
check_state ¶
Raise :class:SartoriusConnectionError if the session is BROKEN.
Public alias for the internal gate run by every dispatch path,
so lifecycle helpers on :class:Balance can reuse the same
guard without poking at private attributes.
Source code in src/sartoriuslib/devices/session.py
close
async
¶
Close the underlying transport, if one is wired.
Idempotent — safe to call multiple times. The factory owns the transport's construction and hands it into the session via the protocol client; closing the session closes the transport.
Both clients hold the same transport, so close it once via the
session's :attr:transport accessor rather than once per client
slot — guards against a future caller that wires both clients
simultaneously.
Source code in src/sartoriuslib/devices/session.py
execute
async
¶
Dispatch command with full pre-I/O gating.
Gates fire in the design doc §6.1 order; each raise happens before any byte is sent, so a gate failure is observably equivalent to the call never leaving the host.
Source code in src/sartoriuslib/devices/session.py
execute_raw_sbi
async
¶
Send an arbitrary SBI command token and return the parsed reply.
Source code in src/sartoriuslib/devices/session.py
execute_raw_xbpi
async
¶
Send an arbitrary xBPI opcode and return the raw reply frame.
Bypasses the declarative :class:Command layer — the opcode is
a per-call parameter, so none of the prior / capability gating
applies. The one hard gate is a safe-list check: opcodes in
:data:sartoriuslib.commands.raw.SAFE_READ_ONLY_OPCODES run
freely; anything else requires confirm=True because the
library cannot know it is safe.
The availability cache is not updated — raw calls are opaque at the command-name level.
Source code in src/sartoriuslib/devices/session.py
invalidate_cache ¶
Drop one cached entry, or clear all when cache_key is None.
The :class:Balance facade calls this after writes whose
0xBA bump isn't guaranteed (the §6.3 caveat). Unknown
keys are a silent no-op — idempotent.
Source code in src/sartoriuslib/devices/session.py
mark_broken ¶
Transition the session to :attr:SessionState.BROKEN.
Called only from lifecycle operations
(Balance.configure_protocol) when a rollback fails. Once
broken, every subsequent dispatch refuses with
:class:SartoriusConnectionError.
Source code in src/sartoriuslib/devices/session.py
read_sbi_autoprint_reading
async
¶
Read the next valid SBI autoprint weight line without writing.
Source code in src/sartoriuslib/devices/session.py
read_sbi_line
async
¶
Read one unsolicited SBI line, used by autoprint streaming.
Source code in src/sartoriuslib/devices/session.py
record_recoverable_error ¶
Increment the recoverable-error counter.
Called from retry sites — :func:sartoriuslib.open_device's
cold-open swallow loop, future inline transient retries. Public
because the counter is observed by :class:DeviceSnapshot;
callers should not normally mutate it.
Source code in src/sartoriuslib/devices/session.py
refresh_sbi_autoprint_state
async
¶
Passively re-sniff whether SBI autoprint is currently active.
Source code in src/sartoriuslib/devices/session.py
replace_clients ¶
Swap protocol clients atomically — used by configure_protocol.
The host-side flip closes the old protocol client, reopens the
transport at new serial framing, builds a new client, and
verifies. On verification success this method installs the new
clients and the new active protocol; the availability cache,
prior warnings, and result cache are all
cleared because the command surface changes when the protocol
does (xBPI-only commands have no SBI variant and vice versa,
and any 0xBA-pinned cache entries belong to the old session).
Refuses to install ProtocolKind.AUTO — detection must
resolve to XBPI or SBI first. Refuses if the
corresponding client for active_protocol is missing.
Source code in src/sartoriuslib/devices/session.py
set_dst_sbn ¶
Update the destination SBN.
Used after :meth:Balance.write_sbn_address on multidrop links
where the new address must address the device going forward.
Source code in src/sartoriuslib/devices/session.py
update_identity ¶
Replace session-level identity state after a live identify call.
Called by :func:sartoriuslib.devices.factory.open_device after
running the identify commands, so subsequent prior gating sees
the discovered family and capabilities instead of the
placeholder UNKNOWN / empty values.
Each argument left as None keeps the existing value.
Source code in src/sartoriuslib/devices/session.py
SessionState ¶
Bases: Enum
Lifecycle state of a :class:Session.
OPERATIONAL is the normal state — commands dispatch freely.
BROKEN is entered when an atomic lifecycle operation
(Balance.configure_protocol) cannot reconcile the transport
with the device's new state. A BROKEN session refuses every
subsequent :meth:execute with :class:SartoriusConnectionError;
the caller must construct a fresh session (typically via
:func:sartoriuslib.open_device) to recover.
Factory — open_device / open_balance¶
sartoriuslib.devices.factory ¶
open_device — primary async entry point for the library.
Supports forced xBPI, forced SBI, and ProtocolKind.AUTO opens.
open_device is the canonical name for cross-library uniformity
with alicatlib, watlowlib, and nidaqlib (unified spec §A).
The factory owns the transport's construction and wires it through
the xBPI protocol client and the :class:Session into the returned
:class:Balance. The balance's async-context-manager exit closes the
transport. If any step between open and balance-construction fails,
the factory closes the transport before propagating the exception.
Cold-open USB races where the device drops the first byte or two of
its reply surface as :class:SartoriusTransientTransportError (unified
spec §F). :func:open_device swallows up to three such transients
during the first identify with a 50 ms backoff so consumers do not
need to know about cold-open at all. Post-open transients still
surface to callers — the only invisibly-retried window is the open
itself.
open_device
async
¶
open_device(
port,
*,
protocol=ProtocolKind.XBPI,
serial_settings=None,
timeout=1.0,
src_sbn=1,
dst_sbn=9,
strict=False,
identify=True,
)
Open a serial port, wire up the protocol stack, and return a :class:Balance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
port
|
str | Transport
|
Serial-port path (e.g. |
required |
protocol
|
ProtocolKind
|
Which wire protocol to speak. :attr: |
XBPI
|
serial_settings
|
SerialSettings | None
|
Override the default 8-O-1 @ 9600 baud
configuration. Ignored when |
None
|
timeout
|
float
|
Per-call default timeout for both transport I/O and
:class: |
1.0
|
src_sbn
|
int
|
Host xBPI bus address (default |
1
|
dst_sbn
|
int
|
Balance xBPI bus address (default |
9
|
strict
|
bool
|
If |
False
|
identify
|
bool
|
Run the identify commands on open and cache
:class: |
True
|
Raises:
| Type | Description |
|---|---|
SartoriusError
|
|
SartoriusConnectionError
|
Transport failed to open. |
Returns:
| Name | Type | Description |
|---|---|---|
A |
Balance
|
class: |
Balance
|
closes the transport. |
Source code in src/sartoriuslib/devices/factory.py
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | |
Discovery¶
sartoriuslib.devices.discovery ¶
Port discovery + :class:DiscoveryResult + :func:find_devices.
The unified spec (UNIFIED_API_HANDOFF.md §B) defines a cross-library
DiscoveryResult base shape that every sibling adapter (alicat,
watlow, nidaq, sartorius) returns one row of per probe attempt.
Sartorius-specific framing extras (parity, stopbits, SBI autoprint
state) live on :class:SartoriusDiscoveryResult — a subclass that
satisfies list[DiscoveryResult] for the cross-lib contract while
preserving sartoriuslib-specific metadata callers depend on.
Two layers, two helpers:
- :func:
discover_port— open one port at one serial-settings configuration, run the conservative :func:sartoriuslib.protocol.detect_protocolprobe, return a :class:SartoriusDiscoveryResult. The caller picks the framing. - :func:
find_devices— sweep a set of baudrates against one or more ports (default: every port :func:anyserial.list_serial_portsenumerates) and return one :class:SartoriusDiscoveryResultper probe attempt. Callers wanting a per-port best-hit answer use :func:summarize_discoveryto fold attempts into :class:DiscoverySummaryrows.
Both helpers are READ-ONLY — neither writes anything beyond what
:func:detect_protocol already sends (an xBPI READ_MODEL and
optionally ESC x1_ / ESC P). No tare, no zero, no autoprint
toggling.
Design reference: docs/design.md §4.3, §16 Q2.
DiscoveryResult
dataclass
¶
Outcome of one probe attempt — the cross-library base shape.
The unified spec (§B) pins these fields across sibling libraries.
Use :class:SartoriusDiscoveryResult (a subclass) for the typed
sartorius-specific extras; treat this base shape as the lowest
common denominator multi-adapter consumers can rely on.
Attributes:
| Name | Type | Description |
|---|---|---|
ok |
bool
|
|
port |
str
|
The port label (path or pre-built transport's label). |
address |
str | int | None
|
SBN address for xBPI hits, |
baudrate |
int | None
|
Effective baudrate during the probe; |
protocol |
ProtocolKind | None
|
Resolved :class: |
device_info |
DeviceInfo | None
|
Identity snapshot from a successful probe.
|
error |
SartoriusError | None
|
The :class: |
elapsed_s |
float
|
Probe wall-clock duration in seconds. |
DiscoverySummary
dataclass
¶
Per-port roll-up of one or more :class:SartoriusDiscoveryResult probes.
Returned by :func:summarize_discovery. The lowest-cost ergonomic
"give me one row per port" shape for callers (Setup-editor Discover
dialog, sarto-discover print output) that don't want to fold
multi-baud attempts themselves.
Attributes:
| Name | Type | Description |
|---|---|---|
port |
str
|
The port label. |
ok |
bool
|
|
baudrate |
int | None
|
First successful baudrate on a hit; the last attempted
baudrate on a miss; |
protocol |
ProtocolKind | None
|
Resolved :class: |
autoprint_active |
bool
|
Carried from the winning probe on hits. |
error |
SartoriusError | None
|
First non- |
elapsed_s |
float
|
Sum of every per-probe elapsed time for the port. |
SartoriusDiscoveryResult
dataclass
¶
SartoriusDiscoveryResult(
ok,
port,
address,
baudrate,
protocol,
device_info,
error,
elapsed_s,
parity="O",
stopbits=1,
autoprint_active=False,
pending_lines=tuple(),
)
Bases: DiscoveryResult
Sartorius-typed probe result with serial-framing + autoprint extras.
Adds the per-probe framing details and SBI autoprint state that the
cross-lib base shape doesn't carry. Consumers reading the unified
surface use DiscoveryResult fields uniformly; sartoriuslib
callers (capa Discover dialog, sarto-discover) read the
subclass extras directly.
Attributes:
| Name | Type | Description |
|---|---|---|
parity |
str
|
Effective parity during the probe. |
stopbits |
int
|
Effective stop bits during the probe (1, 1.5, 2). |
autoprint_active |
bool
|
|
pending_lines |
tuple[bytes, ...]
|
CRLF-terminated SBI lines consumed during the sniff that the caller may want to re-queue when opening a live SBI client (so the first autoprint sample is not lost). |
discover_port
async
¶
discover_port(
port,
*,
serial_settings=None,
timeout=1.0,
sniff_window=0.25,
src_sbn=1,
dst_sbn=9,
)
Open port at serial_settings and run the conservative detect.
Returns a :class:SartoriusDiscoveryResult capturing the chosen
framing and the detector's verdict. The transport is closed before
returning. Failures during detect_protocol (no responsive
device, hard transport faults) surface in the result's error
field rather than being raised — discovery is meant to be safe to
call against unknown ports without crashing the caller. Port-open
failures (busy port, missing device) likewise return a non-ok
result rather than raising.
Source code in src/sartoriuslib/devices/discovery.py
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 | |
find_devices
async
¶
Probe local serial ports for Sartorius balances, sweeping baudrates.
Returns one :class:SartoriusDiscoveryResult per probe attempt —
one port × one baudrate. Callers wanting a per-port best-hit
answer fold the list via :func:summarize_discovery.
For each port in ports (or every port
:func:anyserial.list_serial_ports enumerates when ports is
None), call :func:discover_port once per baudrate in
baudrates (or :data:DEFAULT_DISCOVERY_BAUDRATES when None)
until either:
- a probe reports
ok=True(first hit wins for that port — the sweep short-circuits remaining bauds), or - a probe's port-open failure short-circuits the port (other bauds would fail the same way), or
- every baud has been tried without a hit.
The function is read-only and never raises: it only calls
:func:discover_port, which only sends the conservative
READ_MODEL / ESC x1_ / ESC P probes, and captures every
exception into a non-ok result. No tare, no zero, no autoprint
toggling.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ports
|
Sequence[str] | None
|
Explicit ports to probe. |
None
|
baudrates
|
Sequence[int] | None
|
Baudrates to sweep per port, in the order to try
them. |
None
|
per_probe_timeout_s
|
float
|
Per-probe :func: |
0.5
|
sniff_window_s
|
float
|
Per-probe passive SBI autoprint sniff window. |
0.25
|
Returns:
| Name | Type | Description |
|---|---|---|
One |
list[SartoriusDiscoveryResult]
|
class: |
list[SartoriusDiscoveryResult]
|
port-then-baud order. |
Source code in src/sartoriuslib/devices/discovery.py
summarize_discovery ¶
Fold per-probe results into one :class:DiscoverySummary per port.
Port order is preserved (first-appearance wins). For each port the
summary picks the first ok probe as the winning row; if no probe
succeeded the port's last probe contributes the failure reason.