Skip to main content

Quick reference

Dense, scannable lookup for the most common commands, topics, and parameters. The cheat sheet you print or pin to a second monitor. Every link points at the page with the full details.

All commands below assume you've entered the workspace env (e.g. cd humanoid_control_ws && pixi shell) so ros2, colcon, and the Humanoid Control console scripts are on PATH. Looking for one-line aliases (pixi run launch-mujoco, pixi run build, …)? See How-to → Workspace shortcuts with pixi.

Launch invocations

Grouped by which machine they run on. See Concepts → Architecture → Deployment topology for the split rationale. Launches come from two repos:

  • Humanoid Control ships the Lite + Prime bringups (humanoid_bringup_lite, humanoid_bringup_prime), the description viewer (humanoid_bringup_lite), and the policy prepare-and-load launch (humanoid_control_policy).
  • pianist_ros2 ships the piano-task launches (pianist_bringup composes a Lite + piano MuJoCo scene; pianist_policy prepares the piano policy and runs the USB-MIDI driver).

Single-machine sim / dev (no robot, no tether)

# Drag joints in RViz — no controllers, no physics
ros2 launch humanoid_bringup_lite view_lite.launch.py

# MuJoCo sim — full controller stack, /clock from sim time
ros2 launch humanoid_bringup_lite mujoco.launch.py

# Lite + piano in MuJoCo (pianist_bringup composes the scene, spawns piano_state_bridge)
ros2 launch pianist_bringup mujoco.launch.py

# Calibrate the zero pose (writes ./calibration.yaml on Ctrl+C)
ros2 launch humanoid_bringup_lite calibrate.launch.py

Robot onboard computer (real bringup)

# Real Lite — both buses, two ros2_control blocks, gamepad + mode_manager
ros2 launch humanoid_bringup_lite real.launch.py

# Real Lite, no gamepad attached (drive the FSM via /humanoid_control/mode/* services)
ros2 launch humanoid_bringup_lite real.launch.py enable_gamepad:=false

# Gamepad enumerated as js1 (multiple controllers plugged into the Jetson)
ros2 launch humanoid_bringup_lite real.launch.py joy_dev:=/dev/input/js1

# Real Lite, no FSM (raw debug / calibration)
ros2 launch humanoid_bringup_lite real.launch.py enable_mode_manager:=false

real.launch.py boots the real-time control plane only — visualisers live on the operator workstation; the in-process policy is prepared and loaded on the robot below.

Robot onboard computer (prepare + load a policy)

# Prepare + load the in-process tracking policy (Humanoid Control → humanoid_control_policy):
# runs `prepare` (ONNX → .mcap + overlay), then loads rl_policy_controller
# inactive. START_LOCOMOTION (R1+A) activates it.
ros2 launch humanoid_control_policy lite_policy.launch.py \
wandb_run_path:=… wandb_checkpoint_name:=model.onnx

# Piano policy (pianist_ros2 → pianist_policy); task picked by ONNX task_type
ros2 launch pianist_policy piano_policy.launch.py \
wandb_run_path:=… motion_file:=/path/to/song

# USB-MIDI keyboard driver — publishes /piano/key_state (Float32MultiArray)
ros2 launch pianist_policy midi_keyboard_driver.launch.py

Operator workstation (host side of the tether)

# Live URDF + /lite/joint_states viewer (humanoid_bringup_lite)
ros2 launch humanoid_bringup_lite viz.launch.py # viser, http://0.0.0.0:8080
ros2 launch humanoid_bringup_lite viz.launch.py viewer:=rerun # native rerun window

Both machines must share ROS_DOMAIN_ID. Full surface: Launch args.

CAN bus setup

# One-time, after USB-to-CAN adapters plug in
sudo ip link set can0 down 2>/dev/null
sudo ip link set can0 up type can bitrate 1000000
sudo ip link set can1 down 2>/dev/null
sudo ip link set can1 up type can bitrate 1000000

# Check state (look for "UP" and "ERROR-ACTIVE")
ip -d link show can0
ip -d link show can1

Diagnostic CLIs

These are the hc (humanoid_control_cli) verbs — equivalent ros2 run humanoid_devices_robstride … invocations are listed in CLI tools.

# Scan an ID range; read-only, no Enable
hc bus discover --iface can0 --scan-to 32
hc bus discover --iface can1 --scan-to 32

# One-shot GetDeviceId / OperationStatus probe
hc bus ping --iface can0 --id 11

# Per-joint slider window (forward_command_controller frontend)
hc motor slider

# Live URDF + /lite/joint_states viewers (single-machine sim/dev shortcuts;
# on the tethered host, prefer `ros2 launch humanoid_bringup_lite viz.launch.py`)
hc viz viser # browser at :8080
hc viz rerun # native rerun window

Mode-FSM gamepad bindings (Xbox layout)

ButtonsIntentAllowed fromActivates
XDAMPany statedamping_controller
L1 + ALOAD_ADAMPINGstandby_controller_a (Pose A)
L1 + BLOAD_BDAMPINGstandby_controller_b (Pose B)
R1 + ASTART_LOCOMOTIONSTANDBY (gated on is_finished)rl_policy_controller
R1 + BSTART_REMOTESTANDBY (gated on is_finished)remote_policy_controller
BACKQUITZERO_TORQUE or DAMPING onlyrclcpp::shutdown()

L1+A and L1+B load different poses (standby_controller_a / standby_controller_b); the START combo picks the policy. Pose and policy are independent — from either pose, R1+A → LOCOMOTION or R1+B → REMOTE. You cannot switch A↔B directly (LOAD is admissible only from DAMPING); DAMP first, then load the other pose. See Concepts → Five-mode FSM.

Manual controller switching (no FSM)

These are interactive ros2 control calls:

# ZERO_TORQUE → DAMPING
ros2 control switch_controllers \
--deactivate zero_torque_controller \
--activate damping_controller

# DAMPING → STANDBY Pose A (motors will move to the ready pose over ~4 s;
# use standby_controller_b for Pose B)
ros2 control switch_controllers \
--deactivate damping_controller \
--activate standby_controller_a

# Back to safe
ros2 control switch_controllers \
--deactivate <whatever>_controller \
--activate zero_torque_controller

Always end a session with zero_torque_controller active before Ctrl+C-ing the launch.

Topics you actually echo

TopicTypeRateWhen
/lite/joint_statessensor_msgs/JointState50 Hz real / 200 Hz simalways (joint_state_broadcaster, remapped at bringup)
/imu/datasensor_msgs/Imusensor-ratealways; RELIABLE
/control_modehumanoid_control_msgs/ControlMode50 Hzalways (mode_manager)
/safety_statushumanoid_control_msgs/SafetyStatuson-change, latchedTRANSIENT_LOCAL; source field per bus
/standby_controller_a/statehumanoid_control_msgs/StandbyStateactive-onlyTRANSIENT_LOCAL; Pose A. Watch for is_finished:true before R1+A/R1+B
/standby_controller_b/statehumanoid_control_msgs/StandbyStateactive-onlyTRANSIENT_LOCAL; Pose B. Watch the instance matching the loaded pose
/remote_policy_controller/commandhumanoid_control_msgs/MITCommandsource ratewhen a System 1/2 source (gravity-comp, VLA) feeds remote_policy_controller
/piano/key_statestd_msgs/Float32MultiArraysensor / sim ratepiano runs only (RELIABLE + KEEP_LAST(1)); live key state, in-process key_pressed term
/joysensor_msgs/Joysensor-ratewhen enable_gamepad:=true (default)

Common one-liners

# Live controller state
ros2 control list_controllers

# Rate of /lite/joint_states (50 Hz real, 200 Hz MuJoCo)
ros2 topic hz /lite/joint_states

# Read the current pose (post-calibration, joint frame)
ros2 topic echo --once /lite/joint_states

# Safety status of every active bus
ros2 topic echo --once /safety_status

# Drive an FSM transition without a gamepad
ros2 service call /humanoid_control/mode/damp std_srvs/srv/Trigger
ros2 service call /humanoid_control/mode/load_a std_srvs/srv/Trigger # Pose A (load_b for Pose B)

# Fake a System 1/2 MITCommand publish (when remote_policy_controller is active in MuJoCo)
ros2 topic pub --once /remote_policy_controller/command \
humanoid_control_msgs/msg/MITCommand "{header: {stamp: now}, joint_names: [...], ...}"

Services for FSM transitions

std_srvs/Trigger services on /humanoid_control/mode/* mirror the gamepad intents — useful when there's no joystick attached.

ServiceEffect
/humanoid_control/mode/damp→ DAMPING from any state
/humanoid_control/mode/load_aDAMPING → STANDBY (Pose A, standby_controller_a)
/humanoid_control/mode/load_bDAMPING → STANDBY (Pose B, standby_controller_b)
/humanoid_control/mode/start_remoteSTANDBY → REMOTE (gated on is_finished)
/humanoid_control/mode/start_locomotionSTANDBY → LOCOMOTION (gated on is_finished)
/humanoid_control/mode/quitexit (only from ZERO_TORQUE or DAMPING)

The Lite joint table

14 actuated DOFs across two buses. Order is canonical (see Concepts → Frozen schemas).

IdxJointCAN idBusModel
0left_shoulder_pitch11can0rs-02
1left_shoulder_roll12can0rs-00
2left_shoulder_yaw13can0rs-00
3left_elbow_pitch14can0rs-00
4left_wrist_yaw15can0rs-05
5left_wrist_roll16can0rs-05
6left_wrist_pitch17can0rs-05
7right_shoulder_pitch21can1rs-02
8right_shoulder_roll22can1rs-00
9right_shoulder_yaw23can1rs-00
10right_elbow_pitch24can1rs-00
11right_wrist_yaw25can1rs-05
12right_wrist_roll26can1rs-05
13right_wrist_pitch27can1rs-05

Full table (effort / current limits / per-joint K/D) in Hardware specs → Joint table.

MIT-mode command convention

Every plugin (Robstride, Sito, MujocoSystem) computes torque as:

τ = K_p · (q_cmd − q) + K_d · (q̇_cmd − q̇) + τ_ff

Five command interfaces per joint: position, velocity, effort, stiffness, damping. Three state interfaces: position, velocity, effort. See Concepts → MIT command surface.

Frequent failure modes (one-liners)

SymptomFirst thing to check
ENOBUFS / Network is down warningsMotor power off → frames don't ACK → qdisc fills. Power the motors.
/lite/joint_states shows exactly 0.0 for every jointMotors un-Enabled (no power, or Enable frame dropped). Check /safety_status flags.
Launch dies with "joy_dev:=/dev/input/jsN does not exist"enable_gamepad:=true is the default and the bringup hard-fails when the resolved joystick path is missing. Plug a gamepad in, pass joy_dev:=<actual path> (the error message lists any other /dev/input/js* it found), or pass enable_gamepad:=false.
mode_manager rejects LOAD_A/LOAD_B from anywhere other than DAMPINGSend DAMP (X) first. See FSM table above.
ros2 topic echo /safety_status reports flags ≠ 0Check Concepts → Safety pipeline for the bit definitions.

Full guidance: Troubleshooting.