isaac_ros_unitree_g1_teleop_bringup#

Source code available on GitHub.

Unitree G1 teleoperation with an XR headset

Overview#

Top-level launch file for Unitree G1 teleoperation combining AGILE locomotion, bimanual inverse kinematics, and finger control. Supports XR teleoperation through Isaac Teleop and RViz interactive markers.

The application supports both simulation and real hardware.

Tutorial — Unitree G1 XR Teleop#

This tutorial walks through running whole-body XR teleoperation on the Unitree G1 humanoid robot. The application combines AGILE locomotion, bimanual inverse kinematics, and finger control, all driven by an XR headset.

You first run the application in MuJoCo simulation, then deploy on real hardware.

Prerequisites#

Note

NVIDIA tested and qualified this tutorial on Jetson AGX Thor and x86_64 for both simulation and real robot deployment.

Set Up Development Environment#

  1. Set up your development environment by following the instructions in getting started.

  2. (Optional) Install dependencies for any sensors you want to use by following the sensor-specific guides.

    Note

    We strongly recommend installing all sensor dependencies before starting any quickstarts. Some sensor dependencies require restarting the development environment during installation, which will interrupt the quickstart process.

Set Up Hardware#

  1. Set up the Intel RealSense camera by following the RealSense setup tutorial. For Docker environments, the RealSense setup tutorial adds the realsense Docker image key and rebuilds the Isaac ROS environment with isaac-ros activate --build-local.

  2. Set up the Unitree G1 with the host machine connected to it by following these guides:

Build isaac_ros_unitree_g1_teleop_bringup#

If you built your container with the gr00t_workflow Docker layer, you can skip step 1.

  1. Install and build isaac_ros_unitree_g1_teleop_bringup:

    1. Activate the Isaac ROS environment:

      isaac-ros activate
      
    2. Install the prebuilt Debian package:

      sudo apt-get update
      
      sudo apt-get install -y ros-jazzy-isaac-ros-unitree-g1-teleop-bringup
      
    3. Install mujoco_ros2_control:

      sudo apt-get install -y ros-jazzy-mujoco-ros2-control
      source /opt/ros/jazzy/setup.bash
      

Prepare CloudXR#

  1. Whitelist firewall ports for CloudXR.

    CloudXR requires certain network ports to be open. Depending on your firewall configuration, you may need to open them manually. For Quest and PICO headsets (WebXR Client), at the minimum, the CloudXR runtime and WebSocket Secure proxy ports must be open. If ufw is enabled, run:

    sudo ufw allow 47998/udp
    sudo ufw allow 49100,48322/tcp
    

    If you are running the WebXR client from source and ufw is enabled, also open the web server ports:

    sudo ufw allow 8080,8443/tcp
    

Launch the Application#

  1. Launch the teleop application:

    ros2 launch isaac_ros_unitree_g1_teleop_bringup unitree_g1_teleop.launch.py \
        hardware_type:=mujoco \
        input_mode:=teleop \
        cloudxr_accept_eula:=true
    

    This opens the MuJoCo viewer with the G1 robot. The virtual gantry holds the robot upright during startup. Press G to toggle the gantry on/off, and use [ / ] to shorten or lengthen the rope. It is recommended to disable the gantry once the robot is stable. The robot may oscillate at startup and will only stabilize once the controller is active.

    Note

    blend_ratio defaults to 0.0. After the controller is active, enable the policy with ros2 param set /safety_controller blend_ratio 1.0.

  2. Connect the XR headset to the teleop server. In your headset’s browser, open the CloudXR web client and follow the headset connection guide.

    Note

    If the CloudXR runtime is running on Jetson, make sure to set the Video Codec to H.264, otherwise the headset will fail to connect.

    Warning

    The world frame of the headset is defined as the position of the headset and controllers at the moment of connection. Stand still and face the robot before connecting to establish a consistent world frame. To reset the world frame, disconnect and reconnect the headset while stationary.

  3. With the controllers in your hands, start moving them. Verify that the robot’s arms track your movements in the MuJoCo viewer.

    Unitree G1 teleoperation in MuJoCo simulation

Warning

When operating real robot hardware, stop the robot (set blend_ratio to 0.0) before disconnecting the teleop application.

If the XR headset disconnects at any point during teleoperation, the G1’s hands hold their last commanded position.

Controller Reference#

The PICO 4 Ultra headset and Meta Quest 3 include two handheld controllers. The following table summarizes what each input does during teleoperation:

Input

Action

Left joystick

Move the robot: up = forward, down = backward, left = strafe left, right = strafe right

Right joystick — left / right

Rotate the robot in place (yaw)

Controller motion (6-DOF)

The end-effector pose tracks the physical controller; moving and rotating the controller moves the robot’s hand correspondingly

Triggers (each controller has two)

Open and close the finger joints of the tri-finger hand

API#

Usage#

ros2 launch isaac_ros_unitree_g1_teleop_bringup unitree_g1_teleop.launch.py

Launch Arguments#

Launch Argument

Type

Default

Description

hardware_type

string

mujoco

Hardware platform. Options: mujoco (simulation), real (physical robot).

input_mode

string

teleop

Input source. Options: teleop (XR device through CloudXR), markers (RViz interactive markers).

network_interface

string

eno1

Network interface for G1 communication. Only used when hardware_type:=real.

enable_viewer

bool

true

Enable MuJoCo GUI viewer. Only used when hardware_type:=mujoco.

use_rviz

bool

false

Enable RViz visualization. The launch automatically sets this to true when input_mode:=markers.

use_foxglove

bool

false

Start Foxglove bridge for remote monitoring.

ROS Topics#

Topics depend on the input_mode launch argument. In teleop mode:

ROS Topic

Interface

Description

/xr_teleop/ee_poses

geometry_msgs/PoseArray

End-effector wrist poses from XR controllers

/xr_teleop/root_twist

geometry_msgs/TwistStamped

Root velocity command from XR controllers

/xr_teleop/head_pose

geometry_msgs/PoseStamped

Head pose from the XR headset

/xr_teleop/finger_joints

sensor_msgs/JointState

Retargeted G1 TriHand finger joint angles from XR controller inputs

/xr_teleop/controller_data

std_msgs/ByteMultiArray

Raw controller state encoded as msgpack (button, trigger, thumbstick, and pose data)

In markers mode:

ROS Topic

Interface

Description

/ik_controller/reference_pose

geometry_msgs/PoseArray

End-effector poses published by the RViz interactive marker node

ROS Parameters#

Parameter

Node

Type

Default

Description

blend_ratio

/safety_controller

double

0.0

Policy activation level (0.0–1.0). Dynamically adjustable at runtime.

Troubleshooting#

ros2_control_node Times Out Waiting for the Robot State#

On real hardware, ros2_control_node may fail to activate the G1 hardware interface with output similar to the following:

[ros2_control_node-1] [ERROR] [UnitreeG1SystemInterface]: Timeout waiting for robot state
[ros2_control_node-1] [ERROR] [resource_manager]: Failed to 'activate' hardware 'UnitreeG1System'
[ros2_control_node-1] terminate called after throwing an instance of 'std::runtime_error'
[ros2_control_node-1]   what(): Failed to set the initial state of the component : UnitreeG1System to active

This error indicates that the host did not receive the initial robot state from the G1. Recover the connection as follows:

  1. Stop all Isaac ROS containers.

  2. Reboot both the G1 and the host computer (for example, Jetson AGX Thor).

  3. On the host, rerun the network setup script and select the network interface that is physically connected to the G1:

    ${ISAAC_ROS_WS}/src/isaac_ros_robots/isaac_ros_robots_tools/scripts/setup_network.py
    
  4. Relaunch the workflow. If the timeout persists, stop all containers and run setup_network.py again. Establishing the connection might require more than one attempt.

Test Without an XR Headset (Interactive Markers Mode)#

If the XR headset is unavailable or you want to isolate whether an issue is with XR or the robot itself, launch with input_mode:=markers:

ros2 launch isaac_ros_unitree_g1_teleop_bringup unitree_g1_teleop.launch.py \
    input_mode:=markers

RViz opens automatically with six-degrees-of-freedom (6-DOF) interactive markers for each wrist. The /ik_controller/reference_pose topic replaces the /xr_teleop/ee_poses topic in this mode.

  1. Publish to /cmd_vel to start the controller:

    ros2 topic pub --rate 10 /cmd_vel geometry_msgs/msg/Twist \
        "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"
    
  2. In the RViz Displays panel, find the IK Target Marker display and set its Interactive Markers Namespace to /ik_controller_marker. You can then drag the wrist markers to command the arms.

Remote Monitoring with Foxglove#

To visualize through Foxglove, add use_foxglove:=true to any launch command to start the Foxglove bridge:

ros2 launch isaac_ros_unitree_g1_teleop_bringup unitree_g1_teleop.launch.py \
    use_foxglove:=true

Refer to Foxglove Setup for instructions on connecting Foxglove Studio.