Skip to content

Balances

Sartorius balances all do the same thing — weigh, tare, zero — and differ in how much extra they can do, not what they are. sartoriuslib exposes a single Balance class for every model. Family is a discriminator on DeviceInfo, capabilities are a flag bitmap, and family-specific behaviour is dispatched on capabilities, not by class hierarchy. See Design §5 for the full rationale.

One class, many families

open_device(...) always returns a Balance. The balance's DeviceInfo carries the protocol that opened it, the model string returned by xBPI 0x02 (or SBI x1_), the family classification, and the seeded + live-probed capability bitmap.

async with await open_device("/dev/ttyUSB0") as bal:
    info = bal.info  # populated when open_device(..., identify=True)
    print(info.model, info.family, info.protocol)
    print(info.capabilities)

Family classification

Family is decided by the model-string prefix:

Prefix Family Notes
MSE* BalanceFamily.CUBIS Cubis line. Full xBPI plus extended opcodes (0xBC module list, app modes, level sensor).
WZ* / WZA* BalanceFamily.OEM_WEIGH_CELL OEM weigh cells. Subset of the MSE opcode table.
BCE* BalanceFamily.BASIC_LAB Basic lab balances. MSE opcode subset, no Cubis extensions.
anything else BalanceFamily.UNKNOWN First-class case — no priors, every call becomes a live probe.

classify_family(model) is the helper. Classification is case-insensitive and whitespace-tolerant.

All families ship in SBI from the factory

Every Sartorius balance — MSE, WZA, BCE — ships from the factory in SBI, not xBPI. Switching to xBPI is a front-panel menu change on every family. WZA additionally defaults to autoprint at 1200-7-O-1; MSE and BCE default to SBI command/reply at the same baud as their xBPI side. See Troubleshooting for first-contact settings.

Capability flags

Capability is a Flag enum. Bits are seeded from a family-defaults table when identify() succeeds and then confirmed (or contradicted) by lightweight targeted probes and by actual command attempts over the life of the session.

Capability Source Meaning
XBPI_SUPPORT identify Balance itself supports xBPI (whether or not the active protocol is xBPI).
SBI_SUPPORT identify Balance itself supports SBI.
PROTOCOL_SWITCHING family table + probe Confirmed protocol switch available (e.g. WZA SBI→xBPI).
HIRES_WEIGHT xBPI 0x1F Sub-mg weight read.
PARAMETER_TABLE xBPI 0x55 Indexed parameter table. Size varies (70 vs 8).
CONFIG_COUNTER xBPI 0xBA Runtime-config invalidation counter (cache hint).
TEMPERATURE_SENSORS xBPI 0x76 One or more temperature sensors. Count varies 1/2/3.
CAL_RECORD xBPI 0xB9 Last calibration metadata.
INTERNAL_CAL xBPI 0x28 Internal calibration adjust.
EXTERNAL_CAL family + probe External calibration available.
ISOCAL parameter p15, status bit 0x10 isoCAL automatic adjustment.
EXTENDED_OPCODES xBPI 0xBC etc. Cubis-only module list and extended opcodes.
APP_MODES family table Count / density / percent application modes (Cubis).
LEVEL_SENSOR parameters p59/p60 Cubis level sensor.
BARGRAPH xBPI 0x2F Bargraph display.
AUTO_OUTPUT parameter p36 SBI autoprint (auto_wo / auto_w).
RAW_ADC xBPI 0x75 Raw ADC counts (BCE).

Capabilities are priors, not contracts

The reverse-engineering sample behind these tables is small — a handful of models, mostly in one firmware revision each. The library treats family tables and capability defaults as priors from observation, not protocol guarantees.

DeviceInfo.probe_report carries the full ProbeOutcome per capability:

  • Availability.UNKNOWN — never exercised; priors may exist.
  • Availability.SUPPORTED — directly confirmed.
  • Availability.UNSUPPORTED — device returned xBPI 0x04 / equivalent SBI refusal. Sticky per session.
  • Availability.INAPPLICABLE — device returned xBPI 0x06. State-dependent; retryable.

Plus a ProbeSource: FAMILY_TABLE (seeded prior), TARGETED_PROBE (explicit probe during identify()), LIVE_CALL (updated by a normal command's response), or USER_OVERRIDE.

By default, the session attempts commands whose priors don't match and updates the probe report on the response. Pre-I/O refusal happens only under strict=True or after an observed denial on the current session. See Safety and Design §6.1 for the gate-order rationale.

Identifying a balance

async with await open_device("/dev/ttyUSB0") as bal:
    info = await bal.identify()
    print(f"{info.manufacturer} {info.model}")
    print(f"  serial:    {info.serial}")
    print(f"  software:  {info.software}")
    print(f"  capacity:  {info.capacity}")
    print(f"  increment: {info.increment}")
    print(f"  family:    {info.family}")
    print(f"  caps:      {info.capabilities}")
    for cap, outcome in info.probe_report.items():
        print(f"  {cap.name:>20s}  {outcome.availability}  ({outcome.source})")

identify() runs on open_device(..., identify=True) (the default) and populates Balance.info. Re-running it forces a refresh — useful after a configure_protocol(...) switch or a parameter write that may flip a capability.

Discovery

discover_port(port) probes a single port and returns a DiscoveryResult regardless of outcome — protocol and model are populated only on success. The sarto-discover CLI sweeps multiple ports and serial settings.

See also

  • Commands — the command surface and gate order.
  • ReadingsReading, BalanceStatus, DeviceInfo shapes.
  • Streaming — xBPI cadenced poll and SBI autoprint modes.
  • Wire protocol — xBPI/SBI framing and opcode tables.
  • Design §5 — full taxonomy and runtime-verification model.