Skip to content

RX-00/sysID_go2

Repository files navigation

sysID_go2_docker_env

This repository provides a Dockerized ROS 2 Humble environment for collecting real-robot system identification data on a Unitree Go2 and then fitting robot parameters from those datasets.

The setup is built around two mounted repositories:

The container is designed to feel close to a local ROS install:

  • you edit code on the host
  • both repos are bind-mounted into the container
  • Python scripts can be run directly from the mounted source tree
  • C++ packages can be built inside the container without copying files around

In practice:

  • Python edits in sim2real-robot-identification can be run immediately
  • C++ edits in unitree-ros2-dls/ros2_ws need a rebuild with colcon build

We set the Kp and Kd gains to be the same as the deploy.yaml seen in the deploy code for both unitree_rl_mjlab and our MPC-Injection project:

stiffness: [20, 20, 40, 20, 20, 40, 20, 20, 40, 20, 20, 40]
damping:   [ 1,  1,  2,  1,  1,  2,  1,  1,  2,  1,  1,  2]
default_joint_pos: [-0.1,0.9,-1.8, 0.1,0.9,-1.8, -0.1,0.9,-1.8, 0.1,0.9,-1.8]
# Build the per-joint PD gain arrays like MjLab Go2 actuator config
# Hip joints:   Kp=20, Kd=1
# Thigh joints: Kp=20, Kd=1
# Calf joints:  Kp=40, Kd=2
default_kp_map = {'hip_joint': 20.0, 'thigh_joint': 20.0, 'calf_joint': 40.0}
default_kd_map = {'hip_joint': 1.0,  'thigh_joint': 1.0,  'calf_joint': 2.0}

Repository Layout

  • Dockerfile: builds the ROS 2 Humble image and installs the Python dependencies needed for collection and MuJoCo-based fitting
  • Makefile: starts or attaches to the long-lived interactive container
  • unitree-ros2-dls: Unitree DDS workspace plus the DLS HAL bridge
  • sim2real-robot-identification: collection scripts, datasets, MuJoCo sysid flow, and IsaacLab/PACE flow

How The Docker Environment Works

Dockerfile

The Dockerfile:

  • starts from osrf/ros:humble-desktop
  • installs the ROS 2 packages needed by the Unitree/CycloneDDS workflow
  • installs Python packages used by collection and MuJoCo fitting
  • auto-sources /opt/ros/humble/setup.bash
  • auto-sources unitree-ros2-dls/ros2_connect.bash in new shells once the Unitree workspace has been built

Makefile

The Makefile is what makes the container feel like a local development environment.

  • make build builds the Docker image
  • make run starts the interactive container with:
    • --network host
    • --privileged
    • X11 forwarding for MuJoCo rendering
    • bind mounts for both repos
    • a shared shell history file
    • the working directory set to /root/sim2real-robot-identification
  • make attach opens another shell in the already-running container

Because the repos are bind-mounted, any edits you make on the host are visible immediately in the container.

Why The Two Repos Must Stay Separate

Do not merge both repositories into one colcon workspace.

Both repos contain a dls2_interface package:

  • unitree-ros2-dls/ros2_ws/src/dls2_interface
  • sim2real-robot-identification/ros2_ws/src/dls2_interface

The correct layout is:

  • build and source only the ROS 2 workspace in unitree-ros2-dls
  • treat sim2real-robot-identification as a mounted Python project that imports dls2_interface from the sourced ROS environment

If you build both ROS workspaces, you can end up with duplicate message packages and confusing ROS environment resolution.

How The Two Repos Interact

1. unitree-ros2-dls is the hardware bridge

unitree-ros2-dls is responsible for:

  • sourcing the Unitree CycloneDDS workspace
  • building and sourcing its own ros2_ws
  • exposing DLS-compatible ROS topics
  • translating between Unitree low-level messages and DLS messages

Its main entry points are:

The HAL:

  • subscribes to Unitree /lowstate
  • republishes DLS /imu
  • republishes DLS /blind_state
  • subscribes to DLS /trajectory_generator
  • subscribes to DLS /control_signal
  • publishes Unitree /lowcmd

2. sim2real-robot-identification is the collection and fitting layer

sim2real-robot-identification is responsible for:

  • generating reference trajectories from the MuJoCo robot model
  • publishing desired joint trajectories on /trajectory_generator
  • recording measured joint states from /blind_state
  • saving datasets to datasets/<robot>/traj_<N>.pt
  • replaying datasets in MuJoCo
  • fitting actuator parameters with MuJoCo sysid
  • optionally running the IsaacLab/PACE identification path in a separate environment

For the Go2 workflow, the most important script is:

3. The message contract between the repos

The two repos communicate through the DLS message package, especially:

  • BlindState.msg
  • TrajectoryGenerator.msg

The collection script imports those messages from the sourced ROS environment built in unitree-ros2-dls.

4. Joint order remapping

This is one of the most important details in the whole setup.

Unitree low-level joint order is:

  • FR
  • FL
  • RR
  • RL

The DLS/sim2real side expects:

  • FL
  • FR
  • RL
  • RR

quadruped_hal.cpp performs that remapping in both directions so the Go2 collection script can stay in DLS order while the robot still receives the correct Unitree motor indices.

Prerequisites

Before running on a real Go2, make sure you have:

  • a Linux host with Docker installed
  • X11 available if you want MuJoCo rendering inside the container
  • an Ethernet connection from the host to the robot
  • the host Ethernet interface configured for the Unitree subnet
  • the robot safely suspended in the air for data collection

Recommended host network settings for the Ethernet interface connected to the robot:

  • IPv4 mode: manual
  • address: 192.168.123.99
  • netmask: 255.255.255.0

If DDS traffic is blocked, check your firewall rules for UDP traffic in the CycloneDDS range.

First-Time Setup

1. Initialize the Unitree submodules on the host

The Unitree repo relies on git submodules. From the host:

git -C unitree-ros2-dls submodule update --init --recursive

2. Build the Docker image

make build

3. Start the container

make run

This opens a shell in /root/sim2real-robot-identification.

4. Build the Unitree CycloneDDS workspace

Inside the container:

cd /root/unitree-ros2-dls/unitree_ros2/cyclonedds_ws
colcon build

After this succeeds, new shells in the container will auto-source ros2_connect.bash. If you later modify C++ code in unitree-ros2-dls/ros2_ws, rebuild that workspace manually with:

cd /root/unitree-ros2-dls/ros2_ws
colcon build

5. Configure the DDS network interface

Edit unitree-ros2-dls/ros2_connect.bash and replace the hardcoded interface name in CYCLONEDDS_URI with the Ethernet NIC connected to the robot.

For example, change:

<NetworkInterface name="enp98s0" priority="default" multicast="default" />

to your actual NIC name such as:

<NetworkInterface name="enp3s0" priority="default" multicast="default" />

6. Verify that DDS can see the robot

Inside the container:

source /root/unitree-ros2-dls/ros2_connect.bash
ros2 topic list

If the connection is correct, you should see Unitree topics such as /lf/lowstate.

Step-By-Step: Real Go2 System ID Run

This is the recommended operator sequence for a first real Go2 run.

1. Prepare the host and robot

  • connect the robot by Ethernet
  • set the host NIC to the Unitree subnet
  • suspend the robot safely so the legs can move freely
  • make sure the area around the robot is clear

2. Build and start the container

On the host:

make build
make run

3. Build and source the Unitree ROS 2 environment

Inside the container:

cd /root/unitree-ros2-dls/unitree_ros2/cyclonedds_ws
colcon build

source /root/unitree-ros2-dls/ros2_connect.bash

Before running that source command, make sure you already edited unitree-ros2-dls/ros2_connect.bash to use the Ethernet interface that is physically connected to the robot.

4. Verify ROS 2 connectivity

Inside the container:

ros2 topic list

You should see the Unitree low-level topics. If you do not, the problem is usually one of:

  • wrong Ethernet interface name
  • host IP configuration
  • firewall rules
  • robot not reachable on the cable

5. Open two container terminals

Keep the first shell open, then from the host start a second shell:

make attach

In both terminals, make sure the Unitree environment is sourced:

source /root/unitree-ros2-dls/ros2_connect.bash

6. Launch the Go2 HAL bridge

In terminal 1:

cd /root/unitree-ros2-dls
python3 launch_quadruped_hal.py

This is the process that bridges:

  • Unitree /lowstate to DLS /blind_state and /imu
  • DLS /trajectory_generator and /control_signal to Unitree /lowcmd

launch_quadruped_hal.py expects to find ros2_connect.bash relative to the current working directory, so do not launch it from /root/sim2real-robot-identification.

If you prefer to run the HAL manually instead of using the launcher script:

cd /root/unitree-ros2-dls
source ros2_connect.bash
source ros2_ws/install/setup.bash
./ros2_ws/install/quadruped_hal/bin/quadruped_hal

7. Launch the collection script for the real robot

Before running the collection script, edit two settings in sim2real-robot-identification/config.py and sim2real-robot-identification/run_collection_quadruped_ros2.py:

  • set robot = 'go2' in config.py
  • keep the Kp and Kd values in config.py set to the gains you want to use for both collection and fitting
  • set USE_MUJOCO_SIMULATION = False for the real robot
  • optionally set USE_MUJOCO_RENDER = False if you do not want the MuJoCo viewer window

In terminal 2:

cd /root/sim2real-robot-identification
python3 run_collection_quadruped_ros2.py

Notes:

  • the Go2 XML already contains the required down, sys_id_1, and sys_id_2 keyframes
  • run_collection_quadruped_ros2.py defaults to MuJoCo simulation until you change that flag in the file

8. Start data collection from the interactive console

When the collection script starts, use:

startCollection

Then choose:

trajectory

The script will generate the chirp-based joint motion, publish the desired trajectory on /trajectory_generator, and record the measured state coming back from /blind_state.

The console help/autocomplete text in the upstream repo is a little stale. The command you need for this workflow is still startCollection.

9. Find the saved dataset

Collected trajectories are saved in:

sim2real-robot-identification/datasets/go2/traj_<N>.pt

For a first run, the file is typically:

datasets/go2/traj_0.pt

10. Replay the dataset in MuJoCo

Inside the container:

cd /root/sim2real-robot-identification
python3 datasets/replay_dataset_quadruped.py

This replays the recorded joint positions in MuJoCo so you can visually inspect the trajectory.

11. Run MuJoCo system identification

Inside the container:

cd /root/sim2real-robot-identification
python3 sysid_mujoco/my_fit.py --dataset datasets/go2/traj_0.pt

This identifies per-joint:

  • damping
  • armature
  • frictionloss

The fit output is written under:

sim2real-robot-identification/sysid_mujoco/results/go2/<timestamp>/

Before running my_fit.py, keep config.py aligned with the dataset you collected:

  • robot should still be go2
  • Kp and Kd should still match the gains used during collection

12. Copy the identified parameters into the Go2 MuJoCo model

my_fit.py does not automatically overwrite the normal Go2 robot XML you use for simulation.

What it identifies are the per-joint mechanical parameters:

  • damping
  • armature
  • frictionloss

The generated sysid model is only an internal fitting model. It is not your final robot model, because the sysid pipeline converts the robot to a fixed-base version, removes some elements, rewrites actuators, and disables collisions.

So after step 11, the practical workflow is:

  1. Open the generated report:
sim2real-robot-identification/sysid_mujoco/results/go2/<timestamp>/report.html
  1. Read off the optimized values for each Go2 joint.

  2. Copy those values into the full Go2 MJCF used for normal simulation:

sim2real-robot-identification/robot_model/go2/go2.xml

In go2.xml, each actuated joint already has these attributes. For example:

<joint name="FL_hip_joint" class="abduction" damping="0.1531" armature="0.0010" frictionloss="0.2884"/>

After fitting, replace those numbers with the identified values from the report for every actuated joint:

  • FL_hip_joint
  • FL_thigh_joint
  • FL_calf_joint
  • FR_hip_joint
  • FR_thigh_joint
  • FR_calf_joint
  • RL_hip_joint
  • RL_thigh_joint
  • RL_calf_joint
  • RR_hip_joint
  • RR_thigh_joint
  • RR_calf_joint

Example:

<joint name="FL_hip_joint" class="abduction" damping="NEW_VALUE" armature="NEW_VALUE" frictionloss="NEW_VALUE"/>

Recommended practice:

  • keep the original go2.xml as your nominal model
  • save a calibrated copy such as go2_sysid.xml if you want to compare nominal vs identified parameters
  • if you make a calibrated copy, make sure the scene file or downstream code points to that calibrated XML

If you also use gym-quadruped, update the Go2 XML inside that repository too. gym-quadruped loads its own robot XML from its own package, so changing the XML in this repository does not automatically update the gym-quadruped copy.

Important:

  • my_fit.py identifies mechanical joint parameters, not new PD gains
  • the Kp and Kd used during collection are part of the fitting setup, but the values you copy back into the XML are damping, armature, and frictionloss

What Happens During Collection

The real-robot collection loop is:

  1. run_collection_quadruped_ros2.py loads the Go2 MuJoCo model
  2. it reads the sys_id_1 and sys_id_2 keyframes
  3. it generates a chirp-like joint position trajectory
  4. it publishes desired joint position, velocity, kp, and kd on /trajectory_generator
  5. quadruped_hal converts that command into Unitree low-level motor commands
  6. the Go2 executes the command and publishes /lowstate
  7. quadruped_hal remaps Unitree joint order into DLS order and republishes /blind_state
  8. the collection script records actual position and velocity plus the desired reference
  9. the trajectory is saved as a .pt dataset
  10. the MuJoCo sysid scripts use that dataset to fit actuator parameters

Notes On Fitting Paths

MuJoCo path

The Docker image in this repo is intended to support:

  • ROS 2 collection
  • dataset replay
  • MuJoCo-based fitting with sysid_mujoco/my_fit.py

IsaacLab / PACE path

The IsaacLab/PACE identification flow in sim2real-robot-identification/sysid_isaaclab is a separate environment and is not fully provided by this Docker image.

Use that path only if you also set up the upstream IsaacLab dependencies.

Practical Gotchas

  • Build and source only unitree-ros2-dls/ros2_ws. Do not build sim2real-robot-identification/ros2_ws as a second workspace.
  • The hardcoded NIC name inside unitree-ros2-dls/ros2_connect.bash must match the Ethernet interface connected to the robot.
  • make run sets ROS_DOMAIN_ID=100, but ros2_connect.bash later sets ROS_DOMAIN_ID=0, so 0 is the effective domain once you source the Unitree environment.
  • make run mounts the local repos directly, so host-side edits appear instantly inside the container.
  • The Go2 collection path assumes the robot is suspended off the ground.
  • If the MuJoCo viewer is not available, set USE_MUJOCO_RENDER=False in run_collection_quadruped_ros2.py.
  • If ros2 topic list does not show Unitree topics, check the NIC, host IP, cable, and firewall before debugging the Python scripts.

Summary

The intended workflow is:

  1. use the root Docker image and make targets for the runtime environment
  2. use unitree-ros2-dls as the only ROS 2 workspace you build and source
  3. run the Go2 HAL from unitree-ros2-dls
  4. run the collection and fitting scripts from sim2real-robot-identification
  5. save datasets from the real robot and fit the model parameters from those recordings

About

System ID setup for the Go2

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors