Rust CLI and library for Avery Weigh-Tronix scales speaking the SCP-01/NCI protocol over serial or Ethernet.
The implementation has been validated against these devices:
| Model | Capacity | Connection | Status Format |
|---|---|---|---|
| NCI 6720-30 | 15 kg / 30 lb | USB Serial | ASCII (S00, S10, S20) |
| NCI / Avery Weigh-Tronix 7820-50 | 25 kg / 50 lb | USB Serial adapter | Binary status bytes |
Observed device-specific behavior on that unit:
- Serial parity must be
even. The CLI now defaults to--parity even. weightreplies use uppercase units such asLB.statusreplies may be standalone ASCII frames such asS00.weightmay also reply with a standalone ASCII status frame such asS10instead of a weight payload when the load is unstable.- Unsupported commands reply with framed
?responses likeLF ? CR ETX.
- Connected via a USB-to-serial adapter (
/dev/ttyUSB0). - Uses binary SCP-01 status bytes (not ASCII status codes like the 6720-30).
- Same default serial settings:
9600baud, even parity,7data bits,1stop bit.
- Rust toolchain (stable)
- just task runner —
cargo install just - (Optional) cargo-nextest for faster tests —
cargo install cargo-nextest
git clone git@github.com:ye/scale-bridge.git
cd scale-bridge
just buildjust test # run all workspace tests
just test-crate scale-bridge-scp01 # run tests for a single crate
just fuzz # run fuzz tests for the SCP-01 parserjust fmt # check formatting
just lint # clippy — deny all warnings
just ci # full CI check: fmt → lint → testInstall the release binary, man page, and shell completions system-wide:
just install # builds release + installs to /usr/localOr install to a custom prefix:
PREFIX="$HOME/.local" ./install.shTo uninstall:
just uninstallOnce built or installed, connect your scale via USB serial and run:
# one-shot weight reading
scale-bridge --serial-port /dev/ttyUSB0 weight
# continuous streaming
scale-bridge --serial-port /dev/ttyUSB0 weight --watch
# check scale status
scale-bridge --serial-port /dev/ttyUSB0 statusFor development and testing without a physical scale:
just mock weight
just mock status
just mock weight --watchUse --verbose to see raw wire traffic. This is essential for debugging protocol issues or adding support for new scale models:
# level 1: debug wire logs (hex bytes on tx/rx)
scale-bridge --serial-port /dev/ttyUSB0 --verbose 1 weight
# level 2: trace-level detail
scale-bridge --serial-port /dev/ttyUSB0 --verbose 2 weight --watchExample output at --verbose 1:
DEBUG tx: 57 0D
DEBUG rx: 0A 30 30 31 2E 33 34 4C 42 0D 0A 53 30 30 0D 03
That response decodes to 1.34 lb with ASCII status S00 (stable, non-zero reading).
When filing a bug or adding support for a new scale model, include the raw --verbose 1 output so the exact bytes on the wire are visible.
| Recipe | Description |
|---|---|
just build |
Build all crates (debug) |
just release |
Build release binary |
just test |
Run all tests |
just test-crate <name> |
Test a single crate |
just fuzz |
Run fuzz tests (SCP-01 parser) |
just lint |
Clippy with -D warnings |
just fmt |
Check formatting |
just fmt-fix |
Fix formatting in place |
just ci |
Full CI: fmt → lint → test |
just mock <args> |
Run CLI in mock mode |
just install |
Install to /usr/local |
just uninstall |
Uninstall |
just docs |
Generate and open rustdoc |
just generate |
Generate man page + shell completions |
just man |
Preview the man page |
just size |
Show release binary size |
just clean |
Clean build artifacts |
A hooks/pre-push script is included in the repository. It runs cargo fmt --check and cargo clippy before every push, blocking if either fails.
To install it, symlink or copy it into .git/hooks:
# symlink (recommended — picks up changes automatically)
ln -sf ../../hooks/pre-push .git/hooks/pre-push
# or copy
cp hooks/pre-push .git/hooks/pre-push
chmod +x .git/hooks/pre-pushVerified working on both tested models:
weightstatuszero
Verified unsupported on the tested NCI 6720-30:
taremetrologyaboutdiagnostic
Unsupported commands were observed returning the framed ? response on the wire.
- The tested unit uses ASCII status codes in weight responses and standalone status frames.
S00means the reading is stable and non-zero.S10means the weight is not ready because the load is unstable or changing.S20means the scale is at zero. On the tested unit, this matches the illuminated zero indicator on the LCD.zerois recognized by the scale, but it may not actually zero the displayed weight if the current load is outside the scale's allowed zero window.- In testing, issuing
zerowith about1.32 lbpresent returned a status reply and the follow-up weight remained non-zero. - Treat this as device behavior or configuration/calibration state, not a transport or parser failure.
Representative raw responses observed on the tested unit:
- Stable non-zero reading with
S00:LF 002.98LB CR LF S00 CR ETX - Unstable dynamic load with
S10:LF S10 CR ETX - Zero reading with
S20:LF 000.00LB CR LF S20 CR ETX
The CLI defaults match the most common NCI/AWT scale configuration:
--baud 9600--parity even7 data bits1 stop bit
Use the Windows executable from a PowerShell prompt:
.\scale-bridge.exe --serial-port COM3 weightUseful notes:
- Windows serial ports are typically named
COM3,COM4, and so on. - USB serial adapters usually appear in Device Manager under
Ports (COM & LPT). - If the scale is connected through a USB-to-serial adapter, note the assigned
COMnumber and pass it to--serial-port. - The same default serial settings still apply:
9600baud, even parity,7data bits,1stop bit.
Example HTTPS server invocation from PowerShell:
.\scale-bridge.exe --serial-port COM3 serve --https-port 8443 --bind 127.0.0.1 --cert .\cert.pem --key .\key.pemUse the macOS executable from Terminal:
./scale-bridge --serial-port /dev/cu.usbserial-0001 weightUseful notes:
- USB serial devices on macOS commonly appear as
/dev/cu.usbserial-*,/dev/cu.usbmodem*,/dev/tty.usbserial-*, or/dev/tty.usbmodem*. - Prefer the
/dev/cu.*device for initiating outbound serial connections from the CLI. - To inspect available serial devices before and after plugging in the adapter, run
ls /dev/cu.* /dev/tty.*. - The same default serial settings still apply:
9600baud, even parity,7data bits,1stop bit.
Example HTTPS server invocation on macOS:
./scale-bridge --serial-port /dev/cu.usbserial-0001 serve --https-port 8443 --bind 127.0.0.1 --cert ./cert.pem --key ./key.pemTagged releases use GitHub Actions to build downloadable archives for Linux, macOS, and Windows.
- Create and push a version tag such as
v0.2.0:
git tag v0.2.0
git push origin v0.2.0- The
Releaseworkflow in.github/workflows/release.ymlruns on tags matchingv*. - It builds release binaries for Linux, macOS, and Windows, packages them, and attaches the archives to the GitHub Release page for that tag.
- Each archive includes
scale-bridge,scale-bridge-generate, andREADME.md.
The CLI has two levels of help:
scale-bridge --helpshows top-level flags such as--serial-port,--baud,--parity,--host, and--tcp-port.scale-bridge <subcommand> --helpshows options specific to that subcommand.
For the HTTPS server, use:
scale-bridge serve --helpThat is where listener-specific options such as --https-port and --bind are documented.
Enable wire logging with --verbose 1 or higher:
./target/debug/scale-bridge --serial-port /dev/ttyUSB0 --verbose 1 weightExample observed traffic on the tested unit:
DEBUG tx: 57 0D
DEBUG rx: 0A 30 30 31 2E 33 34 4C 42 0D 0A 53 30 30 0D 03
That response decodes to 1.34 lb with ASCII status S00, meaning a stable non-zero reading.