RUMI

An Open Source, Low Cost, Servo-Actuated Quadruped
An accessible platform for learning quadruped locomotion.

Rumi is a 12-DoF open-source quadruped driven by off-the-shelf Dynamixel MX-64 servos. Locomotion and getup policies are trained in MuJoCo with PPO, then deployed at 50 Hz on a Raspberry Pi 4 — no simulator at runtime.

Abstract

Legged robots that learn to walk via reinforcement learning are typically demonstrated on hardware that costs tens of thousands of dollars and uses quasi-direct-drive actuators with rich torque feedback. Rumi asks a simpler question: how far can you get with low-cost servo motors, a Raspberry Pi, and a single low-end IMU?

We present a complete, open-source sim-to-real pipeline for a 12-DoF quadruped built on Dynamixel MX-64 servos. The robot is modeled in MuJoCo, its actuators are identified from chirp and step responses using CMA-ES, and two policies — velocity tracking and get-up — are trained with PPO in our rumi-mjlab environment. Trained MLP policies run on a Raspberry Pi 4 at a 50 Hz control loop, with observations assembled from joint encoders and a BNO-080 IMU. We share the URDF, hardware design, system-identification code, training code, and the deployment stack so that the platform can be reproduced and extended by others.

The Robot

Rumi is intentionally built from parts that anyone can buy. The actuators are 12 ROBOTIS Dynamixel MX-64 servos (three per leg) communicating over a shared 3-pin TTL bus. The on-board computer is a Raspberry Pi 4, the only proprioceptive sensors beyond joint encoders are a single BNO-080 IMU, and the chassis is 3D-printed using PLA material.

Actuators
12 × Dynamixel MX-64
3-pin TTL bus · PD control
Compute
Raspberry Pi 4
U2D2 USB-serial at 4 Mbaud
IMU
BNO-080
I²C, orientation + accel
Control rate
50 Hz
On-robot, no simulator
Degrees of freedom
12 (3 per leg)
Hip-roll, hip-pitch, knee
Actuator class
Low-cost servos
Off-the-shelf, hobby-grade bus

Approach

1. Build & model

The chassis is designed parametrically in CadQuery — a Python-based, code-defined CAD toolkit — so the entire mechanical design is reproducible from a script and diff-friendly in git. From there it is exported to URDF, ported to MuJoCo, and the masses, inertias, and joint limits are taken from the CAD model and refined against the real robot.

2. System identification

Servo actuators are the dominant source of the sim-to-real gap on this platform. We drive each motor with chirp and step signals, log torque and position, and fit a simulated actuator (P/D gains, friction, backlash) to the measured response using CMA-ES. The identified parameters are then plugged back into MuJoCo so the simulator behaves like the real motor.

3. RL training in rumi-mjlab

Training is built on top of mjlab — an Isaac-Lab-style API powered by MuJoCo-Warp. We adapt mjlab to register Rumi as a new robot and add two tasks, training each with PPO over 4096+ parallel environments:

Both policies are small MLPs designed to fit comfortably on the Pi.

4. Deployment at 50 Hz

The deployment stack runs the policy in pure PyTorch (CPU) at 50 Hz. The observation pipeline pulls joint state via GroupSyncRead over Dynamixel SDK and orientation from the BNO-080. The output is a target joint position vector, sent back to the motors with GroupSyncWrite. A USB latency-timer tweak is required to sustain the loop — the repo includes a one-shot script for it.

Video Results

Hardware

Forward locomotion
Velocity-tracking policy at ~0.5 m/s on the real robot.
Get-up from a 25 cm drop
Get-up policy recovers from a non-trivial initial pose.

Simulation

Velocity tracking in MuJoCo
PPO policy tracking a 0.5 m/s linear command.
Get-up in MuJoCo
Curriculum-trained policy reaching a target body height.

Limitations & Failure Modes

Not everything works. Two failure modes dominate on the real robot:

IMU-driven failure
Orientation estimate degrades, policy commits to a bad recovery.
Failed locomotion attempt
Early policy where actuator dynamics were not yet identified.

Both failure modes inform the next iteration: a better IMU + small contact sensors, and tighter actuator identification under load.

Build Your Own Rumi

Rumi is open source. Both the training code and the on-robot deployment stack are MIT-licensed and reproducible from off-the-shelf parts.

A typical reproduction path is: build the chassis from CAD, flash the motors with the IDs in the README, run the single-motor SysID notebook on one MX-64, transfer the parameters to all twelve, then train and deploy.

Star History

If RUMI is useful to you, a star on either repository helps others discover it.

Star history for rumi-open-source-quadruped and rumi-mjlab

How to Reproduce: System Identification

The single biggest source of sim-to-real gap on a servo platform is the actuator. To close it, we run a two-stage identification pipeline before any RL training is taken seriously.

Stage 1 — Single-motor rig

One MX-64 motor is mounted on a fixed bracket with a known pendulum load attached to the output horn (a printed arm with a machined mass at the tip). The motor is driven through a sequence of excitation signals while position, velocity, and commanded target are logged at the control loop rate:

The recorded traces are then replayed inside a single-joint MuJoCo model, and CMA-ES searches for the actuator parameters that minimize the joint position + velocity RMSE between sim and real:

armature      ≈ 0.0162 kg·m²     (range [0.0005, 0.5])
damping       ≈ 0.594  N·m·s/rad   (range [0.05,   5.0])
frictionloss  ≈ 0.001  N·m           (range [0.001,  1.0])

loss = RMSE(pos) + w · RMSE(vel),    with w = 0

A single shared triplet is fit across all joints — the MX-64s are nominally identical, and shared parameters keep the model honest. We set the velocity-loss weight w = 0 and optimize against position error only. Empirically, minimizing position error alone also drove the velocity error down, so the extra loss term added noise to the CMA-ES objective without improving the fit. Code lives in sim2real/sysid_load/ in the hardware repo (load_recorder.py, sysid_load.py, rollout_sysid.py).

Stage 2 — Full robot visual validation

The identified parameters are written back into rumi.xml and validated on the full 12-DoF robot. The robot is commanded through an IK-driven motion while MuJoCo replays the same commands, and the simulated and real trajectories are overlaid in a viser 3D viewer. Feet are anchored to a shared floor reference so the body can shift to match the centroid, keeping the visual comparison fair rather than dominated by base drift.

Anyone reproducing RUMI is encouraged to repeat both stages on their own hardware: even with the same servo, the friction and damping vary enough between units and over time that re-identifying is more reliable than copying our numbers.

Acknowledgements

RUMI's training environment is adapted from mjlab — an Isaac-Lab-style API powered by MuJoCo-Warp for RL and robotics research. Most of the heavy lifting for parallel simulation, PPO configuration, and the env/task abstractions comes from upstream mjlab; the contribution of rumi-mjlab is the robot registration, the Rumi-specific reward and observation design, and the curriculum used for the two tasks shown above. We thank the mjlab authors for releasing it openly.

The mechanical design is authored in CadQuery, an open-source Python-based parametric CAD framework. CadQuery makes the chassis fully scripted, version-controllable, and trivially forkable — if you change a leg length, the URDF and MuJoCo model regenerate from the same source. Thanks to the CadQuery maintainers for that workflow.

Citation

If Rumi is useful in your research, please cite the thesis:

@mastersthesis{rumi2026,
  title  = {Sim-to-Real Policy Transfer on a Low-Cost Servo Quadruped under Sensor and Actuator Constraints},
  author = {Vishwanath R},
  school = {Indian Institute of Technology Jodhpur},
  year   = {2026},
  note   = {M.Tech, Robotics and Mobility Systems}
}