Control stack for the Unitree G1 humanoid robot. Supports MuJoCo simulation (macOS/Linux) and onboard real robot deployment (G1 Jetson Orin). Runs ONNX neural network policies at 50 Hz with safety enforcement.
| Mode | Command | Description |
|---|---|---|
| sim | uv run sim |
MuJoCo simulation (GUI, Viser, or headless) |
| eval | uv run eval |
Accurate evaluation (1000 Hz physics, headless) |
| real | uv run real |
Onboard G1 deployment (C++ DDS backend) |
| mirror | uv run mirror |
Read-only DDS visualization of the real robot |
| replay | uv run replay |
Play back logged data (GUI, Viser, or summary/CSV) |
- IsaacLab — Velocity-tracking locomotion (arrow key / stick controls)
- BeyondMimic — Motion-tracking with trajectory playback and ONNX metadata gains
- Python 3.10
- uv package manager
- macOS (Apple Silicon) or Linux (x86_64 / aarch64)
- Real robot: Linux + C++ unitree_interface binding
# Install
uv sync
# Simulation with MuJoCo GUI (macOS needs mjpython)
uv run sim --gui --policy assets/policies/stance_29dof.onnx
# Simulation with web viewer
uv run sim --viser --policy assets/policies/beyondmimic_29dof.onnx
# Open http://localhost:8080
# Headless evaluation
uv run eval --steps 500 --policy assets/policies/stance_29dof.onnx
# Replay logged data
uv run replay logs/run_name/ --gui
uv run replay logs/run_name/ --viser --speed 0.5 --loop
# Run tests
uv run pytest tests/ -xThe --gantry flag runs a right shoulder pitch sinusoid while the robot hangs from a gantry. Used for sim2real comparison — the same test runs identically in sim and on real hardware.
# Sim with GUI viewer
uv run sim --gantry --gui --duration 40
# Sim with viser
uv run sim --gantry --viser --duration 40
# Real robot (on G1, after deploy — press Start when ready)
uv run real --gantry --duration 40
# Compare logged data
uv run python scripts/compare_sim2real.py logs/<sim_run>/ logs/<real_run>/The test sequence:
- Prepare (5s): Smooth blend from current pose to home position
- Hold: Waits for Start button (real) or Space (sim) before continuing
- Sinusoid: Right shoulder pitch sweeps through quarter ROM (negative direction only, 0.2 Hz)
All infrastructure is active: wireless E-stop (A button), gamepad, keyboard, data logging, video recording (--record).
| Node | IP |
|---|---|
| Motor control board | 192.168.123.161 |
| G1 PC (SSH) | 192.168.123.164 (user: unitree, pass: 123) |
| Dev machine | 192.168.123.100 (configure with ./scripts/setup_robot_network.sh) |
# Sync code and run preflight checks (auto-installs uv if needed)
./scripts/deploy_to_robot.sh
# SSH in and build C++ backend (first time only)
ssh unitree@192.168.123.164
cd ~/unitree_launcher
./scripts/build_cpp_backend.sh
# Run (press Start on wireless controller after prepare completes)
uv run real --policy assets/policies/stance_29dof.onnx| Button | Action |
|---|---|
| A | E-stop (software, checked every tick) |
| L2+B | Hardware damping mode (firmware-level, always works) |
| B | Stop policy (return to stance) |
| X | Resume / re-activate policy |
| Y | Reset policy (stop + reset + re-prepare) |
| Start | Start policy (also activates after prepare hold) |
| Select | Stop policy |
| Left stick | Forward/back (Y) and strafe (X) |
| Right stick X | Yaw rate |
| R1 + DPad Up/Down | Next / previous policy |
On real hardware, a 5-second prepare phase blends from the current pose to the default stance:
- Linear ramp (80% of duration) from current motor positions to home pose
- Policies warm up (ONNX session + observation history) during prepare
- Wireless A-button E-stop is active throughout
- At 90%, policy state is reset for clean activation
- After prepare, the robot holds pose until you press Start (wireless) or Space (keyboard)
- Use
--autoto skip the hold and activate the policy immediately
| Key | Action |
|---|---|
Space |
Toggle policy (start / stop, also activates after prepare hold) |
Backspace |
E-stop (latching) |
Enter |
Clear E-stop |
Delete |
Reset robot and policy |
Up / Down |
Forward / back velocity (±0.1) |
Left / Right |
Strafe velocity (±0.1) |
, / . |
Yaw rate (±0.1) |
/ |
Zero all velocity |
= / - |
Next / previous policy |
uv run sim --viser --policy path/to/policy.onnx
# Open http://localhost:8080Sidebar controls: Start/Stop, E-stop, Reset, policy selector, velocity sliders, telemetry panel (Hz, inference time, height, step count).
YAML configs in configs/. Each mode auto-selects its config — no -c needed unless overriding.
| Config | Auto-selected by | Description |
|---|---|---|
sim.yaml |
sim, eval, mirror |
Simulation defaults (500 Hz physics, domain ID 1) |
real.yaml |
real |
Onboard deployment (eth0, domain ID 0, tilt/frame-drop checks) |
unsafe.yaml |
--preset unsafe |
Disables tilt check and joint position limits |
Key settings:
control:
policy_frequency: 50 # Policy inference rate (Hz)
sim_frequency: 500 # MuJoCo physics rate (Hz)
kd_damp: 8.0 # Damping gain for safety/non-controlled joints
transition_steps: 5 # Steps to interpolate to policy starting pose
safety:
tilt_check: true # E-stop on >57° tilt
frame_drop_check: true # E-stop on >200ms frame drop- Activation: Cosine-interpolates from current position to
default_posovertransition_steps(default 5). Policywarmup()runs during transition (ONNX + obs history). - Return to stance: Instant (stance policy needs full authority immediately).
- BeyondMimic: Holds at first reference frame for 5 steps before advancing. ONNX metadata
start_timestep/end_timesteptrims unstable trajectory edges.
Build the image from the repo root:
docker build -f docker/Dockerfile -t unitree-launcher .Run directly:
Policy files are gitignored and not baked into the image — mount them with -v:
# Headless simulation
docker run --rm -v ./assets/policies:/app/assets/policies:ro \
unitree-launcher sim --policy assets/policies/stance_29dof.onnx --duration 10
# Headless evaluation (1000 Hz physics)
docker run --rm -v ./assets/policies:/app/assets/policies:ro \
unitree-launcher eval --steps 500 --policy assets/policies/stance_29dof.onnx
# Viser web viewer (open http://localhost:8080)
docker run --rm -p 8080:8080 -v ./assets/policies:/app/assets/policies:ro \
unitree-launcher sim --viser --play
# Mirror real robot via viser (Linux, host networking for DDS)
docker run --rm --network host unitree-launcher mirror --viser --interface eth0
# Real robot (Linux, host networking for DDS, C++ backend)
docker build -f docker/Dockerfile --build-arg BUILD_CPP_BACKEND=1 -t unitree-launcher-real .
docker run --rm --network host -v ./assets/policies:/app/assets/policies:ro \
unitree-launcher-real real --policy assets/policies/stance_29dof.onnx
# X11 GUI (Linux only)
xhost +local:docker
docker run --rm -e DISPLAY=$DISPLAY -e MUJOCO_GL=glx \
-v /tmp/.X11-unix:/tmp/.X11-unix -v ./assets/policies:/app/assets/policies:ro \
unitree-launcher sim --gui --policy assets/policies/stance_29dof.onnx| Profile | Services | Description |
|---|---|---|
headless |
sim-headless, eval |
EGL rendering, no display needed |
gui |
sim-gui |
X11 forwarding (Linux only) |
viser |
sim-viser, mirror |
Viser web viewer on port 8080 |
real |
real-robot |
Host networking + C++ backend |
test |
test |
pytest runner |
# Headless sim
docker compose -f docker/docker-compose.yml --profile headless run --rm sim-headless \
sim --policy assets/policies/stance_29dof.onnx --duration 10
# Evaluation
docker compose -f docker/docker-compose.yml --profile headless run --rm eval \
eval --steps 500 --policy assets/policies/stance_29dof.onnx
# Viser sim
docker compose -f docker/docker-compose.yml --profile viser run --rm sim-viser \
sim --viser --policy assets/policies/stance_29dof.onnx
# Tests
docker compose -f docker/docker-compose.yml --profile test run --rm testNote: X11 GUI forwarding requires Linux with an X server. macOS does not support X11 forwarding to Docker containers natively — use --viser instead.
src/unitree_launcher/
main.py # CLI entry point, viewer/headless runners
config.py # Joint constants, dataclasses, YAML loading
mirror.py # Mirror mode entry point (DDS → MuJoCo viewer)
replay.py # Replay mode entry point (logged data → viewer)
gantry.py # Elastic band + gantry simulation utilities
trajectory.py # Collision-aware IK trajectory planning
recording.py # MuJoCo video recording (MP4)
compat.py # unitree_sdk2py patches, cross-platform helpers
script_utils.py # Shared helpers for diagnostic scripts
control/
runtime.py # Step-based control loop, transitions, state machine
safety.py # Safety controller, E-stop, command clamping
gamepad.py # Gamepad monitor (E-stop via USB HID)
policy/
base.py # Policy ABC, action smoothing, warmup()
isaaclab_policy.py # IsaacLab velocity-tracking policy
beyondmimic_policy.py # BeyondMimic motion-tracking policy
hold_policy.py # Static PD hold at home pose
sinusoid_policy.py # Joint sinusoid for gantry testing
joint_mapper.py # Robot ↔ policy joint ordering
factory.py # Policy loading, gain overrides, preloading
robot/
base.py # RobotState, RobotCommand, RobotInterface ABC
sim_robot.py # MuJoCo simulation backend
real_robot.py # C++ unitree_interface backend (onboard)
mirror_robot.py # Read-only Python DDS backend
controller/
input.py # InputManager (merges all controllers)
keyboard.py # Keyboard input (GLFW keys)
wireless.py # Unitree wireless gamepad (real robot)
gamepad_input.py # USB HID gamepad (sim/real)
viser_input.py # Viser web UI input
estimation/
state_estimator.py # InEKF + contact detection + FK
inekf.py # Invariant Extended Kalman Filter
contact.py # Contact detection (GRF thresholding)
kinematics.py # Leg forward kinematics (Jacobian)
lie_group.py # SO(3)/SE(3) Lie group operations
datalog/
logger.py # HDF5/NPZ time-series logging
replay.py # Log loading, state reconstruction, CSV export
viz/
viser_viewer.py # Web-based 3D viewer
conversions.py # MuJoCo geom → trimesh
configs/ # YAML configuration presets
assets/robots/g1/ # MuJoCo XML models + meshes
assets/policies/ # ONNX policy files
scripts/ # Shell helpers (deploy, build, network setup)
tests/ # 505 automated tests
uv run pytest tests/ -x # All tests
uv run pytest tests/ -k transition # Specific tests
uv run pytest tests/ -m "not slow" # Skip slow testsAn InEKF state estimator fuses IMU predictions with contact-foot kinematics (leg Jacobian).
- Real mode: Always on — the estimator is the only source of base state.
- Sim mode: Opt-in with
--estimatorto validate estimator-in-the-loop before hardware. - Tuning: Add
--estimator-verbosefor diagnostic output. Seedocs/estimator_tuning.md.
Two estimation modes:
| Mode | Flag | Estimates | Default for |
|---|---|---|---|
| pos+vel | (default) | base_position, base_velocity |
Real and sim |
| pos+vel+imu | --estimate-imu |
Above + smoothed imu_quaternion, bias-corrected imu_angular_velocity |
Policies trained with filtered IMU |
Most policies are trained with raw IMU in Isaac Lab — use the default mode. Only add --estimate-imu for policies explicitly trained with filtered IMU inputs.
# Sim: test estimator against MuJoCo ground truth
uv run sim --estimator --policy assets/policies/beyondmimic_29dof.onnx
# Real: estimator is automatic, verbose for tuning
uv run real --estimator-verbose --policy assets/policies/stance_29dof.onnx
# Full estimation (pos+vel+imu) for policies that expect filtered IMU
uv run real --estimate-imu --policy assets/policies/filtered_imu_policy.onnx- Joint limits: Commands clamped to physical joint position/velocity/torque ranges
- Tilt detection: E-stop on >57° tilt from vertical (every tick)
- Frame drop: E-stop on >200ms control loop stall
- Wireless E-stop: A-button checked in
get_state()(tightest Python loop) - Hardware fallback: L2+B on wireless controller triggers firmware-level damping
- Exception handling: Any control loop exception triggers immediate E-stop
- E-stop latching: Persists until explicitly cleared
This project is an independent implementation inspired by the design and architecture of:
- RoboJuDo by HansZ8 — Plug-and-play deployment framework for humanoid robots (CC BY 4.0)
- unitree_cpp by GDDG08 — C++ binding for Unitree G1 motor control (CC BY 4.0)
Third-party licenses are in the licenses/ directory.