sartoriuslib¶
Top-level re-exports. Every name listed here is importable directly
from the package: from sartoriuslib import open_device, Balance,
Reading, ....
sartoriuslib ¶
sartoriuslib — Python library for Sartorius balances.
Supports both wire protocols the hardware speaks:
- xBPI: binary, length-prefixed, checksum-protected, SBN-addressed.
- SBI: ASCII command/response and autoprint.
The public API is semantic and protocol-neutral — a caller asks for
poll(), tare(), status(); the session dispatches the xBPI or SBI
variant selected at open time. Both protocols decode into the same frozen
:class:Reading / :class:BalanceStatus / :class:DeviceInfo models.
Core API is async (built on anyio); a sync facade is available at
:mod:sartoriuslib.sync for scripts, notebooks, and REPL use.
The public surface tracks the cross-library unified API
(UNIFIED_API_HANDOFF.md): open_device(...),
:class:SartoriusManager, :func:find_devices, :class:DiscoveryResult
(per-probe row) plus the sartorius-typed :class:SartoriusDiscoveryResult
subclass, :class:Sample with the §C timestamp contract,
:class:DeviceResult with success() / failure() factories,
:class:PollSourceAdapter, :class:Recording, and
:func:sartoriuslib.units.to_pint.
See docs/design.md for the architectural design.
AcquisitionSummary
dataclass
¶
AcquisitionSummary(
started_at,
finished_at=None,
samples_emitted=0,
samples_late=0,
max_drift_ms=0.0,
target_total_samples=None,
)
Mutable acquisition totals owned by recorder / pipe drivers.
The recorder is the sole writer — counters update in place during the run so progress polling (TUIs, dashboards) works without a separate API. Consumers MUST treat the summary as read-only; mutating it from the consumer side is a contract violation that will produce wrong totals on shutdown.
finished_at is None while the producer is running and is
set on context-manager exit.
Unified spec §M: every sibling library follows the same mutability rule; the field set is library-specific.
Attributes:
| Name | Type | Description |
|---|---|---|
started_at |
datetime
|
Wall-clock at the first scheduled tick. |
finished_at |
datetime | None
|
Wall-clock at producer shutdown ( |
samples_emitted |
int
|
Count of per-tick batches actually pushed
onto the receive stream for recorder summaries, or
individual samples handed to the sink for |
samples_late |
int
|
Count of ticks that missed their target slot (producer overran the previous tick, or overflow policy dropped the batch). |
max_drift_ms |
float
|
Largest observed positive drift of an emitted batch relative to its absolute target, in milliseconds. |
target_total_samples |
int | None
|
Number of scheduled ticks for finite
duration recorder runs, or |
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.
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
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.
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.
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.
DetectionResult
dataclass
¶
Outcome of :func:detect_protocol.
Attributes:
| Name | Type | Description |
|---|---|---|
protocol |
ProtocolKind
|
The resolved :class: |
autoprint_active |
bool
|
|
pending_lines |
tuple[bytes, ...]
|
Complete CRLF-terminated SBI lines consumed during
the sniff that the caller may want to re-queue on the live
client. Empty unless |
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.
DeviceResult
dataclass
¶
Bases: Generic[T_co]
Per-device result container — value or error, never both.
The protocol that produced the failure is available via
result.error.context.protocol when the error carries context;
keeping it off the result keeps the success-path representation
clean and aligns with the ecosystem DeviceResult shape used by
:mod:alicatlib and :mod:watlowlib.
Use the :meth:success / :meth:failure classmethod factories at
call sites that branch on success/failure — they make the intent
obvious. The keyword-construction path (DeviceResult(value=v,
error=None)) stays valid for internal call sites that already
know both fields.
failure
staticmethod
¶
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). |
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. |
ErrorContext
dataclass
¶
ErrorContext(
command_name=None,
command_bytes=None,
opcode=None,
sbi_token=None,
raw_response=None,
protocol=None,
port=None,
model=None,
family=None,
sbn_address=None,
elapsed_s=None,
extra=_empty_extra(),
)
Structured context attached to every :class:SartoriusError.
Fields are best-effort — missing data is None rather than raising.
extra accepts any Mapping and is always frozen into a read-only
:class:types.MappingProxyType at construction so the shared empty
sentinel can never be mutated through error.context.extra[k] = v.
address
property
¶
Unified cross-library accessor for the device address.
For sartoriuslib this is the xBPI SBN address (sbn_address).
Consumers that work across sibling libraries (alicatlib, watlowlib,
nidaqlib) read ctx.address uniformly; sartorius-internal code
keeps using sbn_address because it carries protocol-layer
semantics.
merged ¶
Return a new context with updates overlaid. Unknown keys go to extra.
Source code in src/sartoriuslib/errors.py
ErrorPolicy ¶
Bases: Enum
How the manager surfaces per-device failures.
Under :attr:RAISE, the manager collects every balance's result
and — if any call failed — raises an :class:ExceptionGroup
containing the per-device exceptions after the task group joins.
Under :attr:RETURN, each balance produces a :class:DeviceResult
and the caller inspects .error per entry.
FirmwareVersion
dataclass
¶
Immutable firmware version. Ordering is tuple-lexicographic.
Exact numbering conventions differ per family; see design doc §16 Q5.
InvalidParameterIndexError ¶
Bases: SartoriusConfigurationError
Parameter-table index is out of range for this device.
Source code in src/sartoriuslib/errors.py
InvalidSbnError ¶
Bases: SartoriusConfigurationError
SBN bus address is invalid.
Source code in src/sartoriuslib/errors.py
OverflowPolicy ¶
Bases: Enum
What record() does when the receive-stream buffer is full.
The producer runs on an absolute-target schedule; the consumer drains at its own pace. Slow consumers create backpressure — this knob picks how the recorder responds.
BLOCK
class-attribute
instance-attribute
¶
Await the slow consumer. Default. Silent drops are surprising
in a data-acquisition setting, so the recorder blocks the producer
rather than quietly discarding samples. The effective sample rate
drops to the consumer's drain rate; samples_late accrues once
the consumer catches up and the producer can check its schedule.
DROP_NEWEST
class-attribute
instance-attribute
¶
Drop the sample that was about to be enqueued. Counted as late.
DROP_OLDEST
class-attribute
instance-attribute
¶
Evict the oldest queued batch, then enqueue. Counted as late.
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.
PollSource ¶
Bases: Protocol
Minimal shape the recorder needs from its dispatcher.
:class:~sartoriuslib.manager.SartoriusManager satisfies this: its
poll(names) returns a Mapping[str, DeviceResult[Reading]].
Using a Protocol keeps :func:record testable against a lightweight
stub without pulling in the whole manager + transport stack.
poll
async
¶
Poll every named balance (or all under management) concurrently.
Must return a mapping keyed by the manager-assigned device name.
Successful polls carry the :class:Reading as .value;
failed ones carry the :class:~sartoriuslib.errors.SartoriusError
as .error (per :class:~sartoriuslib.manager.ErrorPolicy.RETURN).
Source code in src/sartoriuslib/streaming/recorder.py
PollSourceAdapter ¶
Wrap one :class:Balance as a :class:PollSource for :func:record.
Construction takes a name (the manager-style identifier the
sample carries downstream) and the :class:Balance to poll. Every
:meth:poll invocation returns either a single-entry mapping
containing the poll outcome wrapped in :class:DeviceResult, or an
empty mapping when names is supplied and does not include this
adapter's name.
Usage::
adapter = PollSourceAdapter("bal1", balance)
async with record(adapter, rate_hz=10) as recording:
async for batch in recording.stream:
...
Source code in src/sartoriuslib/streaming/poll_source.py
poll
async
¶
Poll the wrapped balance and return a one-entry result mapping.
When names is supplied and excludes :attr:name, returns
an empty mapping (the consumer asked for a different device).
Otherwise returns {name: DeviceResult.success(reading)} on
success or {name: DeviceResult.failure(error)} on a typed
sartoriuslib failure.
Source code in src/sartoriuslib/streaming/poll_source.py
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.
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.
ProtocolKind ¶
Bases: StrEnum
Which wire protocol is active on a session.
AUTO is only valid at open_device call time; by the time a
session exists, AUTO has resolved to XBPI or SBI.
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
Recording
dataclass
¶
The context-manager payload returned by :func:record.
Bundles the per-tick stream, the live :class:AcquisitionSummary,
and the configured / observed rates so consumers can poll progress
without reaching into recorder internals. Cross-library spec §M:
every sibling library yields Recording[T] from its record
CM; T is what the recorder actually emits per tick (for
sartoriuslib that's Mapping[str, Sample]).
Attributes:
| Name | Type | Description |
|---|---|---|
stream |
StreamT
|
The async iterator the recorder publishes per-tick
payloads into. Drain by |
summary |
AcquisitionSummary
|
Live :class: |
rate_hz |
float
|
Configured cadence the recorder is running at, as
passed to :func: |
observed_rate_hz |
float | None
|
Rolling mean inter-frame rate over the last
10 SBI autoprint frames. |
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.
Sample
dataclass
¶
Sample(
device,
reading,
t_mono_ns,
t_utc,
requested_at,
received_at,
latency_s,
protocol,
t_midpoint_mono_ns=None,
metadata=_empty_metadata(),
error=None,
)
One balance poll with full timing provenance.
Attributes:
| Name | Type | Description |
|---|---|---|
device |
str
|
The manager-assigned name (from |
reading |
Reading | None
|
The :class: |
t_mono_ns |
int
|
Canonical monotonic acquisition timestamp in nanoseconds since OS boot. The midpoint of the request / response monotonic timestamps for request/response polling; the receive-side monotonic for SBI autoprint frames. This is the join key downstream tooling correlates against sibling-library samples. |
t_utc |
datetime
|
Wall-clock acquisition instant (UTC, tz-aware) for the
same moment :attr: |
t_midpoint_mono_ns |
int | None
|
Optional integration-window midpoint in
monotonic nanoseconds. |
requested_at |
datetime
|
Wall-clock |
received_at |
datetime
|
Wall-clock |
latency_s |
float
|
|
protocol |
ProtocolKind | None
|
Which wire protocol produced this sample.
Duplicates |
metadata |
Mapping[str, str]
|
Free-form per-sample annotations. Populated by
|
error |
SartoriusError | None
|
The :class: |
SartoriusAutoprintActiveError ¶
Bases: SartoriusProtocolError
SBI autoprint is active, so command/reply traffic is not reliable.
Source code in src/sartoriuslib/errors.py
SartoriusCapabilityError ¶
Bases: SartoriusError
Command is not available on this device / firmware / family.
Source code in src/sartoriuslib/errors.py
SartoriusCapabilityWarning ¶
Bases: UserWarning
Emitted when a command's family/capability priors do not match the device.
In non-strict mode (the default) the library attempts the command anyway and updates availability from the device's response. See design doc §6.1.
SartoriusCommandRejectedError ¶
Bases: SartoriusProtocolError
The device returned an xBPI subtype 0x01 / SBI refusal response.
Source code in src/sartoriuslib/errors.py
SartoriusConfigurationError ¶
Bases: SartoriusError
Configuration-level error (bad args, wrong confirm flag, etc.).
Source code in src/sartoriuslib/errors.py
SartoriusConfirmationRequiredError ¶
Bases: SartoriusConfigurationError
A PERSISTENT / DANGEROUS command was attempted without confirm=True.
Source code in src/sartoriuslib/errors.py
SartoriusConnectionError ¶
Bases: SartoriusTransportError
Could not open / lost the connection to the balance.
Source code in src/sartoriuslib/errors.py
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. |
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). |
SartoriusError ¶
Bases: Exception
Base class for every :mod:sartoriuslib exception.
Carries a typed :class:ErrorContext. The message is the human-readable
summary; the context is the machine-readable detail.
Source code in src/sartoriuslib/errors.py
with_context ¶
Return a copy of this error with its context updated.
Useful when an inner layer raises and an outer layer wants to enrich
the context (for instance adding port or elapsed_s).
Source code in src/sartoriuslib/errors.py
SartoriusFirmwareError ¶
Bases: SartoriusCapabilityError
Command is outside the supported firmware window.
Source code in src/sartoriuslib/errors.py
SartoriusFrameError ¶
Bases: SartoriusProtocolError
Bad checksum, bad length, malformed TLV, etc.
Source code in src/sartoriuslib/errors.py
SartoriusIndexOutOfRangeError ¶
Bases: SartoriusCapabilityError, SartoriusCommandRejectedError
Device returned xBPI err 0x10 (index out of range).
Source code in src/sartoriuslib/errors.py
SartoriusManager ¶
Coordinator for many balances across one or more serial ports.
Operations run concurrently across different physical ports (via
:func:anyio.create_task_group) and serialise on the same-port
client lock. Per-balance failures are surfaced per
:attr:error_policy:
- :attr:
ErrorPolicy.RAISE: the manager still collects results from every balance, then raises an :class:ExceptionGroupif any failed. - :attr:
ErrorPolicy.RETURN: the mapping's values carry :class:DeviceResultcontainers with.valueor.error.
Usage::
async with SartoriusManager() as mgr:
await mgr.add("bal1", "/dev/ttyUSB0")
await mgr.add("bal2", "/dev/ttyUSB1")
readings = await mgr.poll()
Source code in src/sartoriuslib/manager.py
add
async
¶
add(
name,
source,
*,
protocol=ProtocolKind.XBPI,
serial_settings=None,
timeout=1.0,
src_sbn=1,
dst_sbn=9,
strict=False,
identify=True,
)
Register and open a balance under name.
The source discriminates lifecycle ownership:
- :class:
Balance— pre-built (via :func:open_deviceoutside the manager). The manager only tracks the name mapping; it does not take lifecycle ownership. str— serial port path ("/dev/ttyUSB0","COM3"). The manager creates a :class:SerialTransport, canonicalises the port key, and shares the transport across balances on the same bus. Mixing xBPI and SBI sessions on a shared physical port is refused; one serial link has one active protocol.- :class:
Transport— duck-typed transport. The manager invokes :func:open_deviceagainst it but does not take transport ownership.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Unique manager-level identifier. |
required |
source
|
Balance | str | Transport
|
One of the three lifecycle shapes above. |
required |
protocol
|
ProtocolKind
|
Which wire protocol to speak (per
:func: |
XBPI
|
serial_settings
|
SerialSettings | None
|
Override default serial framing. Only
honoured when |
None
|
timeout
|
float
|
Per-call default timeout. |
1.0
|
src_sbn
|
int
|
Host xBPI bus address. |
1
|
dst_sbn
|
int
|
Balance xBPI bus address. |
9
|
strict
|
bool
|
Strict prior gating (see design §6.1). |
False
|
identify
|
bool
|
Run identify on open and cache :class: |
True
|
Returns:
| Type | Description |
|---|---|
Balance
|
The opened :class: |
Raises:
| Type | Description |
|---|---|
SartoriusValidationError
|
|
SartoriusConnectionError
|
Manager is closed. |
Source code in src/sartoriuslib/manager.py
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 | |
close
async
¶
Tear down every managed balance and port (LIFO).
Source code in src/sartoriuslib/manager.py
execute
async
¶
Dispatch a per-device Command across the requested names.
requests_by_name chooses both which balances participate and
what arguments each gets — supporting the common case of
"same command, different argument per balance".
Source code in src/sartoriuslib/manager.py
get ¶
Return the balance registered under name.
Source code in src/sartoriuslib/manager.py
poll
async
¶
Poll every (or named) balance concurrently across ports.
Returns a mapping from balance name to :class:DeviceResult
even under :attr:ErrorPolicy.RAISE — but under that policy,
any failed balance's error is re-raised as an
:class:ExceptionGroup after all balances have completed.
Source code in src/sartoriuslib/manager.py
remove
async
¶
Unregister and close the balance named name.
If name was the last balance on a shared port, the
transport for that port is closed too. A pre-built
:class:Balance source is only dropped from the manager's
registry — the caller retains lifecycle ownership.
Source code in src/sartoriuslib/manager.py
SartoriusMissingArgsError ¶
Bases: SartoriusCapabilityError, SartoriusCommandRejectedError
Device returned xBPI err 0x07 (invalid or missing args).
Source code in src/sartoriuslib/errors.py
SartoriusOperationNotApplicableError ¶
Bases: SartoriusCapabilityError, SartoriusCommandRejectedError
Device returned xBPI err 0x06 (operation not applicable).
Source code in src/sartoriuslib/errors.py
SartoriusParseError ¶
Bases: SartoriusProtocolError
Unknown xBPI subtype or unparseable SBI line.
Source code in src/sartoriuslib/errors.py
SartoriusProtocolError ¶
Bases: SartoriusError
Protocol-level error (framing, parsing, device refusal).
Source code in src/sartoriuslib/errors.py
SartoriusProtocolUnsupportedError ¶
Bases: SartoriusProtocolError
Command has no variant defined for the active protocol.
Source code in src/sartoriuslib/errors.py
SartoriusSinkDependencyError ¶
Bases: SartoriusSinkError, SartoriusConfigurationError
A sink's optional backing library is not installed.
Raised when the user instantiates (or calls open() on) a sink
whose extras have not been installed — e.g. ParquetSink without
sartoriuslib[parquet] or PostgresSink without
sartoriuslib[postgres]. The message names the exact extra to
install so the remediation is copy-pasteable.
Multi-inherits :class:SartoriusConfigurationError because callers
that already branch on configuration errors (missing extras being a
configuration problem from their perspective) keep working without
changes.
Source code in src/sartoriuslib/errors.py
SartoriusSinkError ¶
Bases: SartoriusError
Base class for errors raised by sinks (CSV, JSONL, SQLite, Parquet, Postgres).
Source code in src/sartoriuslib/errors.py
SartoriusSinkSchemaError ¶
Bases: SartoriusSinkError
A batch's shape is incompatible with the sink's locked schema.
Raised when a sink has locked its schema on the first batch (or validated against an existing table) and a subsequent batch carries rows whose shape can't be reconciled — for example, a Postgres target table that's missing a required column.
Dropping unknown optional columns is handled by a per-sink WARN log and does not raise.
Source code in src/sartoriuslib/errors.py
SartoriusSinkWriteError ¶
Bases: SartoriusSinkError
The backing store rejected a write.
Wraps the underlying driver exception (sqlite3, asyncpg, pyarrow)
so downstream error handlers don't need to import optional
dependencies. The original exception is preserved via
raise ... from original so tracebacks remain intact.
Source code in src/sartoriuslib/errors.py
SartoriusTimeoutError ¶
Bases: SartoriusTransportError
A transport read or write timed out.
Source code in src/sartoriuslib/errors.py
SartoriusTransientTransportError ¶
Bases: SartoriusTransportError
Transport-layer hiccup that is safe to retry without reopening.
Raised in the cold-open window when the device is still settling and
a read returns 0 bytes (transport layer) or the first frame arrives
short of MIN_FRAME_SIZE (protocol layer underrun). Callers may
retry the same operation up to 3 times before escalating to
:class:SartoriusConnectionError; :func:sartoriuslib.open_device
swallows up to 3 inside the first identify so cold-open is invisible
to most callers.
Source code in src/sartoriuslib/errors.py
SartoriusTransportError ¶
Bases: SartoriusError
I/O-layer error from the serial transport.
Source code in src/sartoriuslib/errors.py
SartoriusUnsupportedCommandError ¶
Bases: SartoriusCapabilityError, SartoriusCommandRejectedError
Device returned xBPI err 0x04 (unsupported/unknown opcode).
Source code in src/sartoriuslib/errors.py
SartoriusValidationError ¶
Bases: SartoriusConfigurationError
Request validation failed before I/O.
Source code in src/sartoriuslib/errors.py
SartoriusValueOutOfRangeError ¶
Bases: SartoriusCapabilityError, SartoriusCommandRejectedError
Device returned xBPI err 0x03 (value out of range).
Source code in src/sartoriuslib/errors.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.
Sign ¶
Bases: StrEnum
Sign of a measurement as encoded on the wire.
StreamingSession ¶
StreamingSession(
balance,
*,
rate_hz=None,
mode="poll",
temporary_autoprint=False,
confirm=False,
timeout=None,
)
Async context manager + iterator for one balance.
mode="poll" performs request/response polling at an absolute cadence.
mode="autoprint" consumes already-enabled SBI autoprint lines and
fails on entry if no line is available within timeout. The
temporary_autoprint=True path is reserved for a future persistent
SBI parameter-write flow and currently raises :class:NotImplementedError.
Source code in src/sartoriuslib/streaming/stream_session.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.
Unit ¶
Bases: StrEnum
Physical unit of a measurement / display unit.
Values are the short symbol (or short tag for units without a
single canonical symbol) so str(Unit.G) == "g" for log lines
and CSV columns.
Membership covers the 24-entry p07 display-unit table plus
:attr:UNKNOWN for forward-compat on measurement-frame decoding.
CT_AU
class-attribute
instance-attribute
¶
Austrian carat — non-metric; used in p07 idx 17.
PARTS_PER_POUND
class-attribute
instance-attribute
¶
Parts per pound (p07 ptplb).
USERDEF
class-attribute
instance-attribute
¶
User-defined unit (p07 idx 1). A multiplier + label live in a separate register not yet located; the balance displays it scaled from grams.
UnknownUnitError ¶
Bases: SartoriusConfigurationError
The unit code is not recognised.
Source code in src/sartoriuslib/errors.py
detect_protocol
async
¶
detect_protocol(
transport,
*,
timeout=_DEFAULT_PROBE_TIMEOUT,
sniff_window=_DEFAULT_SNIFF_WINDOW,
src_sbn=HOST_SBN_DEFAULT,
dst_sbn=BALANCE_SBN_DEFAULT,
)
Detect xBPI vs SBI on an already-open transport.
Runs the conservative sequence from design §4.3 in order — drain → passive sniff → xBPI probe → SBI probe → fail. Each probe writes at most one frame. The transport's serial settings are never changed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
transport
|
Transport
|
An open :class: |
required |
timeout
|
float
|
Per-probe timeout for the xBPI and SBI identity probes. |
_DEFAULT_PROBE_TIMEOUT
|
sniff_window
|
float
|
Passive listen window for SBI autoprint, in seconds. |
_DEFAULT_SNIFF_WINDOW
|
src_sbn
|
int
|
Source SBN for the xBPI probe frame ( |
HOST_SBN_DEFAULT
|
dst_sbn
|
int
|
Destination SBN for the xBPI probe frame ( |
BALANCE_SBN_DEFAULT
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
DetectionResult
|
class: |
DetectionResult
|
|
|
DetectionResult
|
carries the sniffed bytes for re-queue. |
Raises:
| Type | Description |
|---|---|
SartoriusError
|
No xBPI or SBI device responded — neither the
passive sniff nor either probe produced a recognisable reply.
Hard transport faults (e.g. the port closed mid-detect)
propagate as :class: |
Source code in src/sartoriuslib/protocol/detect.py
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 155 156 157 158 159 160 161 162 163 | |
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
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 | |
record
async
¶
record(
source,
*,
rate_hz,
duration=None,
names=None,
overflow=OverflowPolicy.BLOCK,
buffer_size=64,
)
Record polled samples into a receive stream at an absolute cadence.
Usage::
async with record(mgr, rate_hz=10, duration=60) as recording:
async for batch in recording.stream:
process(batch)
print(recording.summary.samples_emitted)
The CM yields a :class:Recording whose :attr:Recording.stream
is an async iterator of per-tick sample batches. Each batch is a
Mapping[name, Sample] — one entry per device that
participated on that tick. Successful polls produce a
:class:Sample carrying a :class:Reading; failed polls produce
a :class:Sample with reading=None and error set. The
bundled :attr:Recording.summary updates live during the run and
finalises (finished_at set) on CM exit.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source
|
PollSource
|
Any :class: |
required |
rate_hz
|
float
|
Target cadence. Absolute targets are computed
|
required |
duration
|
float | None
|
Total acquisition duration in seconds. Finite runs
schedule |
None
|
names
|
Sequence[str] | None
|
Subset of device names to poll per tick. |
None
|
overflow
|
OverflowPolicy
|
Backpressure policy when the receive-stream buffer
is full. See :class: |
BLOCK
|
buffer_size
|
int
|
Receive-stream capacity, in per-tick batches. |
64
|
Yields:
| Name | Type | Description |
|---|---|---|
A |
AsyncGenerator[Recording[Mapping[str, Sample], AsyncIterator[Mapping[str, Sample]]]]
|
class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/sartoriuslib/streaming/recorder.py
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 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 | |
sample_to_row ¶
Flatten a :class:Sample into a single row dict for tabular sinks.
Schema layout (stable across samples; design §10, unified spec §C):
device— manager-assigned name.t_mono_ns— canonical monotonic acquisition timestamp.t_utc— wall-clock acquisition instant (ISO 8601).t_midpoint_mono_ns— integration-window midpoint (Nonefor poll/autoprint samples).requested_at/received_at— ISO 8601 I/O provenance.latency_s— poll round-trip time, seconds.- reading fields — from :meth:
Reading.as_dict:value,unit,sign,stable,overload,underload,decimals,sequence,protocol,raw. On error samples (reading is None) these all appear asNone. error_type— fully qualified exception class on a failed sample, otherwiseNone.error_message—str(error)on a failed sample, otherwiseNone.
Reading.protocol is the authoritative protocol column on
success rows; on error rows the row's protocol column falls
back to :attr:Sample.protocol (populated by the manager from
the session's active protocol) so sinks never see a missing
column.
Source code in src/sartoriuslib/sinks/base.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.
Source code in src/sartoriuslib/devices/discovery.py
to_pint ¶
Return a pint-compatible unit string for unit, or None.
Accepts a :class:Unit enum member, the matching string value
("g", "kg", ...), or None. Unknown strings and units
pint cannot model return None — never raise.
Example::
>>> from sartoriuslib.units import to_pint
>>> to_pint(Unit.G)
'gram'
>>> to_pint("kg")
'kilogram'
>>> to_pint("tl.hk") # Hong Kong tael — pint can't model
>>> to_pint(None)