Skip to content

Add native MT-SICS Serial scale support - #2223

Open
hermetheuscoffee wants to merge 1 commit into
artisan-roaster-scope:masterfrom
hermetheuscoffee:mtsics-serial-support
Open

hermetheuscoffee wants to merge 1 commit into
artisan-roaster-scope:masterfrom
hermetheuscoffee:mtsics-serial-support

Conversation

@hermetheuscoffee

Copy link
Copy Markdown

Proposal: Native MT-SICS Serial Scale Support for Artisan
For: Marko Luther / Artisan Roaster Scope
From: A user of Artisan's Batch Manager, based on a real-world integration with a Mettler Toledo BC60
Scope: Generic MT-SICS Level 0 support, not a single scale model
Summary
Artisan's Batch Manager scale integration (artisanlib/scale.py, artisanlib/acaia.py) currently supports exactly one scale family: Acaia, over Bluetooth or a serial link that still speaks Acaia's own protocol. That leaves out a large, mature category of scales: bench, shipping/parcel, and laboratory scales from Mettler Toledo and other manufacturers that speak MT-SICS (Mettler Toledo Standard Interface Command Set), a long-standing, publicly documented serial protocol, not a proprietary or single-vendor one.
This proposal adds a second scale backend, artisanlib/mtsics.py, implementing MT-SICS Level 0 (SI, SIR, Z) over a real serial/USB-CDC connection, registered as a new SUPPORTED_SCALES entry ('MT-SICS Serial'). It reuses Artisan's existing AsyncComm framework and mirrors AcaiaSerial's structure closely, so the diff is small and the scale-selection UI needs no changes at all — SUPPORTED_SCALES is already fully data-driven.
Attached: a patch (mtsics-support.patch) against commit 58be9351 (2026-08-25, tag matching release 4.2.1) adding artisanlib/mtsics.py and three small edits to artisanlib/scale.py.
Motivation
It feels like the Cosmo scales are never available for purchase, but I really wanted to use the Artisan Batch Manager feature! Thus, I connected two Mettler Toledo BC60 scales to Artisan's Batch Manager by writing a standalone Python bridge that emulates Acaia's Bluetooth protocol over a virtual serial port, translating from MT-SICS underneath. It works well after some iteration, but it's a workaround: an extra process to run, an extra thing to configure, and an extra thing that can silently drift out of sync with Artisan's own Acaia implementation. Anyone else wanting to use a Toledo, Ohaus, A&D, or other MT-SICS-speaking scale with Artisan is stuck writing the same kind of bridge, because Artisan itself only ever speaks Acaia.
MT-SICS is a genuinely generic target: it's Mettler Toledo's standard command set across most of their bench and industrial scale lines, and other manufacturers' scales support it or a close superset as an interoperability mode. Native support in Artisan benefits anyone with a compatible scale, not just BC60 owners.
Design
artisanlib/mtsics.py follows the same layering Artisan already uses for Acaia:
MTSICSProtocol — pure protocol logic: sends SIR on connect to start continuous streaming, sends Z for tare, parses incoming weight lines, and reports (weight_grams, stable) to a callback. No I/O of its own.
MTSICSAsync(AsyncComm) — the thinnest possible adapter onto Artisan's existing generic serial/network layer (artisanlib/async_comm.py). It only implements the one required hook, read_msg(), delegating to MTSICSProtocol. All connection lifecycle, reconnection, and threading is inherited from AsyncComm for free — no new infrastructure.
MTSICSSerial(Scale) — the Scale subclass Artisan's ScaleManager talks to, implementing scan() (lists serial ports via pyserial, same as AcaiaSerial), connect_scale(), disconnect_scale(), tare_scale(), and the capability getters (max_weight, readability, repeatability).

scale.py changes are three small, additive edits: one new tuple in SUPPORTED_SCALES, one new if model == 2: branch in ScaleManager._get_scale(), and widening two model in {0, 1} membership checks to {0, 1, 2} so the new option can actually be scanned/selected. I confirmed by reading artisanlib/devices.py that the scale-selection combo boxes and the "Port" vs. "Name" field labeling are already entirely generic over SUPPORTED_SCALES — no UI code changes are needed for the new option to appear and work correctly.

Protocol details
Sends SIR on connect (continuous streaming, MT-SICS Level 0), Z for tare — the same minimal Level 0 command subset Artisan would need for any MT-SICS scale.
Parses the standard response line "<echo> <S|D> <value> <unit>", converting g/kg/lb/oz to grams.
Also parses a combined lb:oz value format (e.g. "S D 12:07.50 lb:oz"), which is how a real BC60 reports weight when configured for US shipping units. This is harmless to keep for scales that don't use it — the regex simply won't match a plain decimal into that branch — but happy to drop it from the generic module and keep it BC60-specific if you'd rather keep this file protocol-pure.
Local stability inference
MT-SICS's own S/D stability flag, returned even by the continuous SIR command, is honest about whether the scale considers a reading settled, but some scales (shipping/parcel scales in particular, tuned for throughput over precision) rarely or never flag a reading S through that fast path. Rather than switch to the blocking single-shot S command (which would stall the stream waiting on each read), MTSICSProtocol layers a local fallback: a reading counts as stable if the scale says so, or if the value hasn't changed for at least 0.5 real wall-clock seconds.
That threshold is time-based, not sample-count-based, on purpose... an earlier version of this logic (in the standalone bridge this patch is derived from) used a fixed repeat-count instead, and it had a real, field-observed failure mode: at typical streaming rates two samples can land under 150ms apart, so a momentary pause mid-motion (e.g. while lifting a container off the platform rather than removing it in one clean motion) could produce two near-identical readings and get misclassified as genuinely settled while the object was still partway off the scale — corrupting Batch Manager's next tare/completion judgment. The time-based version in this patch has been running correctly in the field since, including through repeated deliberate slow-motion removal tests.
Validation performed
This isn't a paper design... the protocol and stability logic in mtsics.py are a direct port of a standalone bridge I've been running against a real BC60 and Artisan's Batch Manager for several sessions, including working through two real bugs along the way (both already fixed in the code this patch is based on, so this native version starts from the corrected logic rather than reproducing them):
A bucket-removal misrecognition (checkmark vs. re-weigh X) caused by exactly the sample-count-vs-time-based stability issue described above.
A read-batching bug (bursty, laggy readings) from an overly long serial read timeout, unrelated to actual scale speed — fixed by using a short per-read timeout, the same approach AcaiaSerial already uses (SERIAL_TIMEOUT = 0.1) and for the same documented reason.
At the code level in this patch: mtsics.py imports cleanly against a checkout of this commit under Python 3.12 with real PyQt6/pyserial installed; ruff check --select=F,E9 and py_compile both pass clean; ScaleManager._get_scale() correctly dispatches model 2 to a MTSICSSerial instance; and the parsing/stability logic has unit-level coverage carried over from the standalone bridge's test suite. I have not yet run this patched build against the live BC60 end-to-end (that's a natural next step before merging) — the standalone bridge it's derived from has, extensively.

Open questions for discussion
Baud rate. Acaia's serial baud is fixed; MT-SICS scales commonly expose baud as a site-configurable setting (e.g. Setup > Communications > Serial > Baud on a BC60). This patch hardcodes 9600 (a common factory default and the MT-SICS default) — worth discussing whether that should become a user-facing setting once a scale using a different baud shows up.
Capability defaults. MT-SICS Level 0 has no self-identification command analogous to Acaia's INFO/STATUS handshake, so max_weight/readability/repeatability are fixed conservative defaults (100 kg / 1 g / 20 g) rather than auto-detected. Open to whatever convention you'd prefer here — a config field, a per-scale profile, or leaving it as-is.
lb:oz parsing. Keep it in the generic module (harmless, only matches that specific unit token) or split it into a small BC60-specific override — your call on how protocol-pure you want this file.
Naming/label. 'MT-SICS Serial' in SUPPORTED_SCALES — happy to rename to whatever fits Artisan's existing naming conventions better.

Happy to adjust anything here, run more validation, or split this into a smaller first PR (e.g. dropping the lb:oz special case) if that's an easier starting point for review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant