
Short answer: Microduck is not a ROS 2 robot. Its published stack is a Rust workspace of small services that speak JSON-RPC over Unix sockets. robotd owns the 50 Hz control loop and the only motor-bus writer; ONNX policies run inside that loop, while training happens off the robot in mjlab and MuJoCo Warp.
That design is the interesting part. The duck looks like a toy, but the software boundary between a client intent and a motor command is deliberate, documented and testable. This page is a map of the published architecture, not a claim that every design page is final. Where the official docs label a section as a draft, treat it as the current public design rather than a permanent product promise.
gamepad / browser / Python / robotctl
|
JSON-RPC over Unix socket
|
robotd ---- safety ---- 50 Hz control loop ---- motor bus
| |
| +---- camera, LiDAR, two IMUs, ToF
|
+---- ONNX policy runtime (walk, stand, skills)
configd updaterd btd padd mediad tofd
| | | | | |
identity releases BLE gamepad WebRTC depthThe client sends a request such as “walk forward”, “look there” or “run this skill”. It does not send a stream of raw joint positions. robotd applies the safety limits, runs the selected policy at 50 Hz and writes the resulting targets to the servo bus.
| Component | Published detail | Why it shapes the software |
|---|---|---|
| Main board | Rockchip RK3566-class board | Runs the Linux services and the policy runtime on the robot |
| Motors | 15 servos | The mechanism has many controlled joints, so bus and safety handling matter |
| Sensing | Camera, LiDAR, two IMUs | Perception and balance inputs arrive from more than one service |
| Depth | ToF sensor served by tofd | Gives the robot a separate depth path from the colour camera |
| Control rate | 50 Hz | The loop has 20 ms to read state, apply intent and write a command |
| Connectivity | Wi-Fi and Bluetooth | Adds remote clients without putting control on the network path |
The control loop runs locally because a network round trip is a terrible way to keep a biped upright. Remote clients are allowed to ask for an intent; they are not allowed to become the balance controller.
| Service | Owns | What it does |
|---|---|---|
robotd | Control loop and motor bus | Reads the robot, applies safety, runs the selected policy and writes the servos |
updaterd | Signed releases | Installs, health-checks, rolls back and pins software; it cannot command a motor |
configd | Identity and Wi-Fi | Stores the robot name and pairing data, and handles network provisioning |
btd | Bluetooth front door | Gives a phone or laptop a BLE control path |
padd | Gamepad | Turns controller reports into intents |
mediad | Camera and remote media | Runs the WebRTC/streaming side and relays the control lane |
tofd | Depth sensor | Serves time-of-flight depth frames to the rest of the stack |
The split is by ownership, not by fashion. Wi-Fi provisioning has to work when robotd is dead, so it cannot live in robotd. A policy load must not be able to rewrite the update system, so updaterd has no motor authority. btd is a transport, not a second source of configuration.
Every local client uses the same call vocabulary: the browser console, robotctl, a Python session and the gamepad path all ask the owning service for the same operations. A state subscription, a policy load and a robot.do request do not each invent a private protocol.
That gives the stack a useful property: a new client can be written against the documented calls without patching the daemons. It also gives the system a clear authority model. Only robotd writes motors; everything around it asks for intents or reads state.
The loop is one process, one serial bus and one 50 Hz cycle. A control tick roughly does five things:
The safety layer owns the only motor I/O. A deadman timeout, a fall verdict, a joint limit or a missing servo can therefore stop the robot without asking a client for permission. The architecture document is explicit that nothing above safety can write to a motor.
The published training stack exports policies to ONNX at 50 Hz. At runtime the daemon holds named slots for default behaviours and a table of skills for requested one-shots. The 61-value observation contract is shared across the current policy family, which is what makes a hot swap possible without restarting the control loop.
The observation is 48 proprioceptive values plus three command groups: a three-value twist, a four-value head pose and a six-value body pose. A policy that does not use one group zero-pads it rather than changing the shape. The normalizer is baked into the ONNX graph, so a hand-converted checkpoint is not equivalent to an official export.
Slots and skills are described in the policy manifest documentation. The practical install and reset workflow is on the Microduck policies page.
The robot runs policies; it does not train them. Training happens in microduck_rl, where mjlab supplies the learning environment, MuJoCo Warp provides simulation and PPO updates the weights. The published recipe includes:
a BAM actuator model for the Dynamixel XL330, including voltage behaviour and friction;
domain randomisation over battery voltage, voltage sag, command delay and friction;
optional backlash variants with gear play modelled at each joint;
a shared observation layout across tasks;
export through the official script so the normaliser travels inside the ONNX
.
The practical GPU requirement is for training, not for running a finished robot. A local CUDA machine or Hugging Face Jobs are both documented paths. The training guide covers the workflow, and the simulator guide covers the no-hardware entry point.
updaterd installs a signed release, restarts the relevant services and runs a health gate. If the new release fails the gate, the updater can return to the previous installed release. A release can also be pinned so an automatic check does not move it.
The design separates “software is healthy” from “a pack is low” or “a motor is hot”. Those values are reported, but they must not become the sole reason to roll back a release; otherwise a robot with a low battery would reject a good update and then judge the replacement on the same pack.
For a user, the safe update sequence is: let the connection drop, reconnect, run duckctl update status, and use duckctl update rollback if the robot does not become healthy. The troubleshooting guide has the full command sequence.
If you are writing an integration, start with the documentation index, then keep your code on the intent side of robotd. If you are writing a paper or a technical article, cite the official repository and design pages rather than treating this summary as the source of record.
Is Microduck based on ROS 2? No. The published robot stack is a Rust workspace whose services communicate through JSON-RPC over Unix sockets. It does not use ROS 2 as its runtime.
Can I run my own daemon on Microduck? The stack is open source and its IPC contract is documented, so additional clients and services can use the same calls. Only robotd should command the motor bus; bypassing that boundary removes the safety layer.
Why does Microduck use several daemons? Each process owns one failure domain. robotd controls motors, updaterd handles signed releases, configd owns identity and Wi-Fi, btd exposes Bluetooth, padd reads the gamepad, mediad streams media and tofd serves depth.
Can a Microduck update brick the robot? The updater installs signed releases, restarts into them, checks health and can roll back. The public design also keeps the update path unable to command motors. A power cut is still a physical failure case, but the update mechanism is built around recovery.
Where does policy training run? Training runs on a CUDA workstation or through Hugging Face Jobs using mjlab with MuJoCo Warp and PPO. The exported ONNX policy then runs on the robot at 50 Hz.