Skip to content
Robin Krämer edited this page Sep 29, 2026 · 2 revisions

The xtrem crate talks to GRAM XTREM / XTREM-S weighing modules over UDP. It implements protocol version 3.007.

An XTREM is an ADPD weighing module certified to OIML R76:2006 and EN45501:2015. It drives a load cell and reports the weight over a communication interface:

  • UART0 is always plain RS232C.
  • UART1 can optionally carry Wi-Fi 802.11, RS485 or wired Ethernet.

This crate only implements UDP over the network interface. RS232 and RS485 carry the same frame format over a different transport, and aren't implemented.

A hardware sealing switch locks writes to the legally relevant calibration registers, and enables a separate software-protection handshake. Both protect the module's legal-for-trade certification. This crate doesn't try to work around either one.

Pages

Page Read it when you want to…
XTREM protocol understand the frame format, the registers, and how payloads are decoded
XTREM bus and discovery open the UDP socket, find modules on a subnet, and give them unique device IDs
XTREM scale driver read weights from a control loop with XtremScale, and send tare and zero

Layers

Module Contents Does I/O?
protocol Frame encode/decode, DataAddress, Function, register value parsing, LRC no
transport XtremBus: one shared UDP socket, with incoming frames routed by ID_O yes
discovery discover, read_once, assign_device_id (all async) yes
devices the XtremDevice polling trait and the XtremScale driver yes

The crate root re-exports the common types: XtremBus, XtremBusConfig, XtremBusHandle, XtremScale, ScaleMode, Reading, XtremDevice, XtremError, XtremProbe, discover, Frame, DataAddress, Function, ProtocolError, Weight, WeighingRegister.

Weights come back as units::Mass (see Units). The receive task runs on the shared multi-threaded runtime common::get_async_runtime(), not on the EtherCAT HAL's runtime.

Quick start

use std::time::Duration;
use common::get_async_runtime;
use units::mass::gram;
use xtrem::{ScaleMode, XtremDevice, XtremScale, discovery};
use xtrem::transport::{XtremBus, XtremBusConfig};

let bus = XtremBus::open(XtremBusConfig {
    bind_addr: "0.0.0.0:5555".parse()?,             // register 0700h default: module → host
    broadcast_addr: "192.168.4.255:4444".parse()?,  // register 0701h default: host → module
    host_id: 0x00,
    verify_lrc: true,
    crlf: true,
})?;

// discover() is async; drive it from the shared runtime.
let probes = get_async_runtime().block_on(discovery::discover(&bus, Duration::from_secs(2)))?;
let mut scale = XtremScale::from_probe(&bus, &probes[0], ScaleMode::Poll);

// Non-blocking, one call each per tick of a synchronous loop.
loop {
    scale.send_next_request()?;
    scale.handle_response()?;
    if let Some(reading) = scale.reading {
        println!("{:.1} g net", reading.net.get::<gram>());
    }
    if let Some(err) = scale.take_error() {
        eprintln!("{err}");
    }
    std::thread::sleep(Duration::from_millis(10));
}

bind_addr must be 0.0.0.0. The module replies to the broadcast address, never to the requester's IP, so a socket bound to one interface address never receives anything. XtremBus::open refuses such an address. See Why 0.0.0.0.

Examples

The examples live in xtrem/examples/. The crate isn't part of a Cargo workspace, so run them from inside xtrem/ (the -p xtrem shown in some of their doc comments doesn't work):

cd xtrem
cargo run --example discover   -- --bind 0.0.0.0:5555 --broadcast 192.168.4.255:4444
cargo run --example assign_ids -- --bind 0.0.0.0:5555 --broadcast 192.168.4.255:4444 --dry-run
cargo run --example poll_multi -- --bind 0.0.0.0:5555 --broadcast 192.168.4.255:4444
Example What it does Extra flags
discover sweeps the subnet, prints every module, then polls the first one --no-lrc, --stream <ms>
assign_ids gives every module a unique device ID, one at a time over unicast --dry-run, --start-id <n> (default 2), --no-lrc
poll_multi polls every discovered module concurrently on one bus --no-lrc

discover defaults --bind and --broadcast to the factory ports (0.0.0.0:5555 and 255.255.255.255:4444). assign_ids and poll_multi require both flags.

Tests

cd xtrem && cargo test                          # protocol unit tests + tests/loopback.rs + doctest
cd xtrem && cargo test --test loopback <name>   # one loopback test
  • Protocol unit tests are anchored to byte sequences taken from the specification's worked examples and its §17 UDP capture, not to invented fixtures.
  • tests/loopback.rs runs a fake XTREM module on 127.0.0.1 that speaks the real wire format. It covers discovery, polling, streaming (including re-arming a stream that never starts), tare, Drop stopping the stream, and ignoring frames from other modules. None of it needs hardware.

When you change protocol or transport behaviour, extend the loopback suite. It catches timing, multi-device and cleanup bugs that frame-codec tests can't.

Clone this wiki locally