Quickstart#
The smallest useful program: open the port, ask the motor how it’s doing, print it.
use std::time::Duration;
use m0601::M0601;
fn main() -> m0601::Result<()> {
let mut motor = M0601::open("/dev/ttyUSB0", 0x01, Duration::from_millis(150))?;
match motor.query()? {
Some(fb) => println!(
"{:+} RPM, {:.1}°, {:?} °C, faults: {}",
fb.speed_rpm, fb.position_deg, fb.temp_c, fb.faults
),
None => println!("no reply — check 18 V power, wiring (brown → GND), A/B, --id"),
}
Ok(())
}query() sends a 0x74 feedback frame and parses the reply. It’s the only reply
that carries winding temperature, which is why fb.temp_c is populated here and
comes back None from a reply to a drive frame (Telemetry has the details).
Ok(None) versus Err#
The two arms above aren’t symmetric, and the distinction runs through the whole
crate. Ok(None) means the bus stayed silent — a wrong address, an unpowered motor,
a probe to an empty slot. That’s an ordinary outcome on RS485, not a failure, and
the driver never manufactures an error for it. Err, by contrast, always means the
port or the OS failed: the device vanished, permission was denied, a write returned
an I/O error.
So the idiomatic shape is: ?-propagate the real failures, and pattern-match the
Option for presence.
match M0601::open(port, 0x01, timeout) {
Err(e) if e.is_permission_denied() => {
eprintln!("add yourself to dialout: sudo usermod -aG dialout $USER");
}
Err(e) => eprintln!("open failed: {e}"),
Ok(motor) => { /* ... */ }
}is_permission_denied() is there precisely so you can give the dialout hint the
CLI gives, instead of surfacing a bare OS error to your users.
Telemetry trust and strict CRC#
By default, telemetry is never rejected on its checksum. Feedback::crc_ok is
informational — genuine replies normally have it true — but the driver hands you the
reading either way and lets you decide. What it does silently drop is a reply
carrying the wrong motor’s ID, which surfaces as Ok(None) rather than as one motor
reporting its neighbour’s speed. That guard matters more than the CRC on a shared bus;
see Telemetry and echo.
The reason the default is advisory rather than strict is caution, not observed failures: the hardware capture in the protocol reference shows genuine replies do carry a valid CRC-8/MAXIM (earlier reports to the contrary traced to echo contamination), but other firmware revisions may differ, and dropping a real reading is its own hazard. But where a corrupt frame is worse than a missing one — above all feeding an odometry integrator, which one bad position sample can throw off permanently — flip it:
// Set it on the bus BEFORE minting handles: the flag is copied into each
// M0601 at `motor()` time, exactly as the timeout is.
let bus = Bus::open("/dev/ttyUSB0", timeout)?.with_strict_crc(true);
let mut motor = bus.motor(0x01)?;
let mut other = bus.motor(0x02)?.with_strict_crc(false); // override one handle
other.set_strict_crc(true); // or flip it later
With strict CRC on, a reply whose checksum doesn’t verify becomes Ok(None) — the
same “no reply” outcome as silence, so none of your error handling changes shape. The
pure parser behind it is protocol::parse_feedback_strict, alongside the lenient
protocol::parse_feedback, if you decode frames yourself.