Rust control library for Damiao-family CAN motors, with first-class Python bindings.
v0.1.0 (in development) — single-arm control over Linux SocketCAN or native macOS gs_usb. Linux supports CAN-FD; macOS gs_usb supports classical CAN. See docs/can-fd.md and docs/gs-usb-macos.md.
The two reference implementations — cmjang/DM_Control_Python and enactic/openarm_can — are either a single-file script that fuses sends with receives, or a C++ tree with a binding crate that rebuilds on every release. This library is built as a Rust core with a thin PyO3 binding so it installs as a single wheel and runs in realtime control loops without GIL contention.
# Python (once 0.1.0 is published):
pip install can-motor-control
# Rust from GitHub while crates.io publishing is deferred:
[dependencies]
can-motor-control = { git = "https://github.com/TomCC7/can-motor-control", tag = "v0.0.1" }
damiao-codec = { git = "https://github.com/TomCC7/can-motor-control", package = "can-motor-damiao-codec", tag = "v0.0.1" }Rust users can depend on the public GitHub repository directly while crates.io
publication is deferred. Pin a tag or commit for reproducible builds. The
companion package names are prefixed (can-motor-codec,
can-motor-damiao-codec) for a future crates.io release, while their Rust
import crate names remain motor_codec and damiao_codec.
Build from source:
# Rust workspace
cargo build --workspace --release
# Python wheel
cd crates/can-motor-control-py
maturin build --release
pip install target/wheels/can_motor_control-*.whlcrates/
motor-codec/ # no_std, package can-motor-codec, import motor_codec
damiao-codec/ # no_std, package can-motor-damiao-codec, import damiao_codec
can-motor-control/ # std, native transport + Robot/Group/Motor + builder
can-motor-control-py/ # PyO3 bindings, built by maturin
configs/ # example TOML robot configs
examples/ # Python hardware bring-up ladder (00_-06_) + Rust example
tests/python/ # Python smoke + integration tests
docs/ # architecture + multi-vendor + CAN-FD notes
openspec/ # source of truth: proposals, designs, specs, tasks
Real hardware bring-up should be done by walking the examples/00_* ->
examples/06_* ladder in order. Each step adds exactly one kind of risk;
do not skip steps when bringing up an arm for the first time. Mock and
MockCanBus-driven tests prove the API surface and CI wiring, not
that real hardware will respond correctly.
| Python example | Rust example | Sends | Hardware required |
|---|---|---|---|
00_can_interface_check.py |
00_can_interface_check |
nothing | none (checks /sys/class/net) |
01_single_motor_enable_disable.py |
01_single_motor_enable_disable |
enable + disable | one powered motor |
02_read_single_motor.py |
-- | enable + live refresh-poll + disable | one powered motor |
03_single_motor_mit_hold.py |
03_single_motor_mit_hold |
bounded MIT hold | one powered motor |
04_single_motor_control_modes.py |
-- | one bounded mode/run | one powered motor |
05_gripper_opening_control.py |
05_gripper_opening_control |
normalized gripper opening (open, set_opening, close) |
one gripper motor |
05_gripper_control.py |
05_gripper_control |
raw gripper PosForce sequence, MIT, or PosVel | one gripper motor |
06_read_robot_motor.py |
-- | live OpenArm/OpenYam zero-torque reader | configured OpenArm or OpenYam single arm |
Each example prints its assumptions before sending anything and disables
on exit. Durations are hard-capped; live readers accept --mock for API-shape
checks, but mock execution does not validate hardware.
For Linux setup, see docs/socketcan-setup.md. For
macOS adapter selection and diagnostics, see
docs/gs-usb-macos.md.
Run Rust examples with cargo run -p can-motor-control --example <Rust example> -- ....
Hosted documentation is published from main to
https://tomcc7.github.io/can-motor-control/ by the GitHub Pages workflow.
A local docs site (Python API reference, guides, and hosted Rust rustdoc) is
built with MkDocs plus cargo doc. The Python API prose is generated from the
package's docstrings, so the native extension is built first. The environment
is managed by uv and pinned by uv.lock for
reproducibility (the root pyproject.toml is a dev-only environment, not a
published package).
# Serve with live reload at http://127.0.0.1:8000
make docs
# Or render a static site into ./site
make docs-buildmake docs-build runs scripts/build-docs.sh, which provisions the uv
environment, builds the MkDocs site, runs cargo doc --no-deps --workspace --locked,
and copies rustdoc into site/rustdoc/ for deployment. No separate virtualenv
or pip install step is needed — uv provisions everything from uv.lock.
Maintainers should follow the release runbook in
docs/release.md before publishing to PyPI or crates.io.
It covers registry setup, release-candidate dry-runs, crates.io publish order,
PyPI Trusted Publishing, and post-publish install checks.
The architecture, every requirement, and the implementation plan live under openspec/. New features should propose changes there first.
| Target | Hardware transport | Scope |
|---|---|---|
| Linux | SocketCanBus |
Classical CAN and CAN-FD |
| macOS | GsUsbBus |
gs_usb channel 0, classical CAN |
Each build exports only its native hardware transport. Both export
MockCanBus. The macOS wheel uses IOKit through Rust nusb; it requires no
Homebrew libusb, PyUSB, or Python gs_usb package.
Dual-licensed under Apache-2.0 OR MIT.