Skip to content

Unitree Go2 - A Quadruped Robot Platform

Go2 Robot

Introduction

The Go2 robot represents a significant advancement in quadruped robotics, bringing together cutting-edge technology and practical applications at affordable cost.

Go2 Robot Front

The really nice thing about the robot is that it can be used as a development platform. Either for full-scale ROS development in the expensive EDU version of the robot, or with limited high-level control for the AIR and PRO versions using WebRTC.

Software

There is an Android app that allows you to operate the robot over WiFi. You can either connect to a local AP the robot provides, or connect the robot to your own network.

App

While the app works well, it is quite restricted and does not allow you to do much beyond using the dog as a toy.

Fortunately, the people from the roboverse provide a number of ways to extend control over the robot.

This repo lets you control many features of the robot using Python: legion1581/unitree_webrtc_connect

You can even use ROS2 with this repo, as long as you are happy that you
only have high-level control of the robot, i.e., you can't individually control the joints in real-time. abizovnuralem/go2_ros2_sdk

Playing around

Based on the repo from legion1581, I created a fork where I put in my experiments: juhasch/go2_webrtc_connect

Notable changes:

  • It doesn't require a patched version of aioice anymore
  • Nicer logging of what is happening
  • Helper class for simpler robot actions
  • Native Lidar decoding without webasm
  • More examples
  • A few simple apps:
    • Visualize Lidar and video in return
    • Control the robot using a gamepad controller
    • Control the robot using hand gestures

Installation

Using a virtual Python environment is highly recommended. I myself use uv.

% uv venv
% git clone git+https://github.com/juhasch/go2_webrtc_connect.git
% uv pip install .
% ./venv/bin/activate

Finding the robot IP

The first challenge is to find the robot on the network. You can use the app to find the robot's network information:

App Network Info

In my case, I have the robot connected to my local network.

Alternatively, you can use the go2-scanner command to scan the network for the robot.

% go2-scanner
==================================================
Unitree Go2 Robot Discovery
==================================================
Discovering devices on the network...
Discovered device: B42DXXXXXXXXXXXX at 192.168.0.197

Discovery Results:
------------------------------
Found 1 device(s):
  • Serial Number: B42DXXXXXXXXXXXX
    IP Address: 192.168.0.197

Checking connectivity

Use the ping command to check if the robot is reachable. The next step is to check if the robot is reachable via WebRTC.

% ping 192.168.0.197
PING 192.168.0.197 (192.168.0.197): 56 data bytes
64 bytes from 192.168.0.197: icmp_seq=0 ttl=64 time=9.875 ms
64 bytes from 192.168.0.197: icmp_seq=1 ttl=64 time=7.469 ms

% curl 192.168.0.197:9991/con_notify
eyJkYXRhMSI6Ikl3V1k ... ifQ==%

Running examples

Lowstate

First, let's try the lowstate example. Here you get a lot of information about the robot's state. Running it --once will only run it once and then exit, otherwise it will run continuously.

% export ROBOT_IP=192.168.0.197
% python examples/data_channel/lowstate/lowstate.py --once
Go2 Robot Low State Monitoring
Press Ctrl+C to stop
==============================
🔌 Initializing robot connection...
🔗 Connecting to robot...
🕒 WebRTC connection        : 🟡 started       (11:18:14)
🕒 Signaling State          : 🟡 have-local-offer (11:18:14)
🕒 ICE Gathering State      : 🟡 gathering     (11:18:14)
🕒 ICE Gathering State      : 🟢 complete      (11:18:14)
🕒 ICE Connection State     : 🔵 checking      (11:18:15)
🕒 Peer Connection State    : 🔵 connecting    (11:18:15)
🕒 Signaling State          : 🟢 stable        (11:18:15)
🕒 ICE Connection State     : 🟢 completed     (11:18:15)
🕒 Peer Connection State    : 🟢 connected     (11:18:15)
🕒 Data Channel Verification: ✅ OK            (11:18:15)
✅ Connected to robot successfully!
📋 Current motion mode: mcf
📡 Starting low state monitoring...
✅ Monitoring low state data (Ctrl+C to stop)
================================================================================
📊 COMPREHENSIVE ROBOT LOW STATE DATA
================================================================================

🧭 IMU STATE DATA:
----------------------------------------
   Roll/Pitch/Yaw:    [-1.960, -0.265, 5.804] deg

⚙️  MOTOR STATES (12 motors):
------------------------------------------------------------
Motor       Pos(rad)    Vel(r/s)    Torque(Nm)    Temp(°C)
--------  ----------  ----------  ------------  ----------
FR_hip       -0.0378           0             0          21
FR_thigh      0.7157           0             0          21
FR_calf      -1.4193           0             0          23
FL_hip        0.004            0             0          21
FL_thigh      0.7278           0             0          22
FL_calf      -1.5054           0             0          23
RR_hip        0.0013           0             0          22
RR_thigh      0.7543           0             0          22
RR_calf      -1.4517           0             0          23
RL_hip        0.1197           0             0          23
RL_thigh      0.6755           0             0          21
RL_calf      -1.4154           0             0          23

🦶 FOOT FORCE SENSORS:
----------------------------------------
Foot           Force(N)  Status
-----------  ----------  ---------
Front Right          90  ✅ Contact
Front Left          110  ✅ Contact
Rear Right           88  ✅ Contact
Rear Left            81  ✅ Contact

🔋 BATTERY MANAGEMENT SYSTEM:
----------------------------------------
   State of Charge: 99.0 %
   Other BMS data:
     version_high: 1
     version_low: 18
     current: -1890
     cycle: 15
     bq_ntc: [24, 23]
     mcu_ntc: [26, 24]

🔍 ADDITIONAL DATA:
----------------------------------------
   temperature_ntc1: 28
   power_v: 31.766182
🕒 Signaling State          : ⚫ closed        (11:18:16)
🕒 ICE Connection State     : ⚫ closed        (11:18:16)
🕒 Peer Connection State    : ⚫ closed        (11:18:16)
🕒 WebRTC connection        : 🔴 disconnected  (11:18:16)
✅ WebRTC connection closed successfully
Done.

Robot Odometry

You can get odometry data from the robot:

% python examples/data_channel/robot_odometry/robot_odometry.py --once
Go2 Robot Odometry Monitoring
Press Ctrl+C to stop
==============================
Connecting to Go2 robot...
🕒 WebRTC connection        : 🟡 started       (11:23:21)
🕒 Signaling State          : 🟡 have-local-offer (11:23:21)
🕒 ICE Gathering State      : 🟡 gathering     (11:23:21)
🕒 ICE Gathering State      : 🟢 complete      (11:23:21)
🕒 ICE Connection State     : 🔵 checking      (11:23:22)
🕒 Peer Connection State    : 🔵 connecting    (11:23:22)
🕒 Signaling State          : 🟢 stable        (11:23:22)
🕒 ICE Connection State     : 🟢 completed     (11:23:22)
🕒 Peer Connection State    : 🟢 connected     (11:23:22)
INFO:root:Validation succeed
🕒 Data Channel Verification: ✅ OK            (11:23:22)
Connected successfully!
Subscribing to robot odometry data...
📡 Starting robot odometry monitoring...
✅ Monitoring robot odometry data (Ctrl+C to stop)
==================================================
Go2 Robot Odometry (ROBOTODOM)
==============================
Timestamp: 1757496202.341720342

Position:
  X: -0.009455 m
  Y: -0.058710 m
  Z: 0.311473 m

Orientation:
  Quaternion - X: -0.014030
  Quaternion - Y: -0.000331
  Quaternion - Z: -0.134194
  Quaternion - W: 0.990856
==============================
🕒 Signaling State          : ⚫ closed        (11:23:22)
🕒 ICE Connection State     : ⚫ closed        (11:23:22)
🕒 Peer Connection State    : ⚫ closed        (11:23:22)
🕒 WebRTC connection        : 🔴 disconnected  (11:23:22)
WebRTC connection closed successfully
Done.

VUI - The front LED

Next, we can try the vui example. This example demonstrates how to control the robot's front LED brightness, colors, and flashing patterns.

% python examples/data_channel/vui/vui.py
🔌 Initializing robot connection...
🔗 Connecting to robot...
🕒 WebRTC connection        : 🟡 started       (11:25:57)
🕒 Signaling State          : 🟡 have-local-offer (11:25:57)
🕒 ICE Gathering State      : 🟡 gathering     (11:25:57)
🕒 ICE Gathering State      : 🟢 complete      (11:25:57)
🕒 ICE Connection State     : 🔵 checking      (11:25:58)
🕒 Peer Connection State    : 🔵 connecting    (11:25:58)
🕒 Signaling State          : 🟢 stable        (11:25:58)
🕒 ICE Connection State     : 🟢 completed     (11:25:58)
🕒 Peer Connection State    : 🟢 connected     (11:25:58)
INFO:root:Validation succeed
🕒 Data Channel Verification: ✅ OK            (11:25:58)
✅ Connected to robot successfully!
📊 Setting up state monitoring...
✅ State monitoring enabled
🤖 State #1: Mode=0, Progress=0, Gait=0, Height=0.320m, Roll=-0.027, Pitch=-0.005, Yaw=-0.480
📋 Current motion mode: mcf
💡 Starting VUI demonstration...

Fetching the current brightness level...
Current brightness level: 0

Increasing brightness from 0 to 10...
Brightness level: 0/10
Brightness level: 1/10
Brightness level: 2/10
Brightness level: 3/10
Brightness level: 4/10
Brightness level: 5/10
Brightness level: 6/10
Brightness level: 7/10
Brightness level: 8/10
Brightness level: 9/10
🤖 State #149: Mode=0, Progress=0, Gait=0, Height=0.320m, Roll=-0.027, Pitch=-0.005, Yaw=-0.491
Brightness level: 10/10

Decreasing brightness from 10 to 0...
Brightness level: 10/10
Brightness level: 9/10
Brightness level: 8/10
Brightness level: 7/10
Brightness level: 6/10
Brightness level: 5/10
Brightness level: 4/10
Brightness level: 3/10
Brightness level: 2/10
Brightness level: 1/10
Brightness level: 0/10

Changing LED color to purple for 5 seconds...
🤖 State #285: Mode=0, Progress=0, Gait=0, Height=0.320m, Roll=-0.027, Pitch=-0.005, Yaw=-0.501

Changing LED color to cyan with flash (cycle: 1000ms)...
🤖 State #413: Mode=0, Progress=0, Gait=0, Height=0.320m, Roll=-0.026, Pitch=-0.005, Yaw=-0.511
💡 VUI demonstration completed!
🕒 Signaling State          : ⚫ closed        (11:26:23)
🕒 ICE Connection State     : ⚫ closed        (11:26:23)
🕒 Peer Connection State    : ⚫ closed        (11:26:23)
🕒 WebRTC connection        : 🔴 disconnected  (11:26:23)
✅ WebRTC connection closed successfully

Actions

There are a number of actions that can be performed by the robot. Here is a mostly complete list:

ActionDescription
BackFlipPerform a back flip (advanced)
ContentContented/relaxed gesture
Dance1Dance routine 1
Dance2Dance routine 2
FrontFlipPerform a front flip (advanced)
FrontJumpJump forward
FrontPounceForward pounce
HeartHeart gesture with legs
HelloGreeting gesture
LeftFlipPerform a left flip (advanced)
RecoveryStandRecover from a fall and return to standing
RiseSitRise the body from sitting pose
ScrapeScraping motion with the forelegs
SitSit down
StandDownLower body to rest (safe stand down)
StandUpStand up from sitting/lying to a stable stance
StretchFull-body stretch sequence

Let's try two actions: StandDown and StandUp.

StandDown
% python examples/data_channel/stand_down.py
🔌 Initializing robot connection...
🔗 Connecting to robot...
🕒 WebRTC connection        : 🟡 started       (11:35:04)
🕒 Signaling State          : 🟡 have-local-offer (11:35:04)
🕒 ICE Gathering State      : 🟡 gathering     (11:35:04)
🕒 ICE Gathering State      : 🟢 complete      (11:35:04)
🕒 ICE Connection State     : 🔵 checking      (11:35:04)
🕒 Peer Connection State    : 🔵 connecting    (11:35:04)
🕒 Signaling State          : 🟢 stable        (11:35:04)
🕒 ICE Connection State     : 🟢 completed     (11:35:04)
🕒 Peer Connection State    : 🟢 connected     (11:35:05)
INFO:root:Validation succeed
🕒 Data Channel Verification: ✅ OK            (11:35:05)
✅ Connected to robot successfully!
📊 Setting up state monitoring...
✅ State monitoring enabled
🤖 State #1: Mode=0, Progress=0, Gait=0, Height=0.327m, Roll=-0.016, Pitch=0.045, Yaw=-2.020
📋 Current motion mode: mcf
🛏️  Starting lay down demonstration...
🎯 Executing command: StandDown
🤖 State #46: Mode=0, Progress=0, Gait=0, Height=0.302m, Roll=-0.024, Pitch=0.051, Yaw=-2.034
🤖 State #49: Mode=0, Progress=0, Gait=0, Height=0.283m, Roll=-0.025, Pitch=0.046, Yaw=-2.047
🤖 State #51: Mode=0, Progress=0, Gait=0, Height=0.270m, Roll=-0.022, Pitch=0.045, Yaw=-2.058
🤖 State #53: Mode=0, Progress=0, Gait=0, Height=0.258m, Roll=-0.021, Pitch=0.042, Yaw=-2.069
🤖 State #56: Mode=0, Progress=0, Gait=0, Height=0.236m, Roll=-0.022, Pitch=0.037, Yaw=-2.082
🤖 State #58: Mode=0, Progress=0, Gait=0, Height=0.221m, Roll=-0.019, Pitch=0.031, Yaw=-2.092
🤖 State #61: Mode=0, Progress=0, Gait=0, Height=0.199m, Roll=-0.016, Pitch=0.024, Yaw=-2.106
🤖 State #64: Mode=0, Progress=0, Gait=0, Height=0.176m, Roll=-0.014, Pitch=0.016, Yaw=-2.116
🤖 State #67: Mode=0, Progress=0, Gait=0, Height=0.153m, Roll=-0.011, Pitch=0.000, Yaw=-2.127
🤖 State #69: Mode=0, Progress=0, Gait=0, Height=0.141m, Roll=-0.013, Pitch=-0.012, Yaw=-2.132
🤖 State #71: Mode=0, Progress=0, Gait=0, Height=0.128m, Roll=-0.014, Pitch=-0.023, Yaw=-2.136
🤖 State #73: Mode=0, Progress=0, Gait=0, Height=0.115m, Roll=-0.012, Pitch=-0.034, Yaw=-2.140
🤖 State #76: Mode=0, Progress=0, Gait=0, Height=0.095m, Roll=-0.008, Pitch=-0.044, Yaw=-2.147
🤖 State #79: Mode=0, Progress=0, Gait=0, Height=0.079m, Roll=-0.002, Pitch=-0.064, Yaw=-2.153
🤖 State #80: Mode=0, Progress=0, Gait=0, Height=0.076m, Roll=0.005, Pitch=-0.080, Yaw=-2.156
🤖 State #81: Mode=0, Progress=0, Gait=0, Height=0.076m, Roll=0.001, Pitch=-0.099, Yaw=-2.161
✅ Command StandDown executed successfully
✅ Robot is now laying down!
🛏️  Lay down demonstration completed!
🕒 Signaling State          : ⚫ closed        (11:35:10)
🕒 ICE Connection State     : ⚫ closed        (11:35:10)
🕒 Peer Connection State    : ⚫ closed        (11:35:10)
🕒 WebRTC connection        : 🔴 disconnected  (11:35:10)
✅ WebRTC connection closed successfully

The actual code here is very simple:

await robot.execute_command("StandDown", wait_time=3)
StandUp
% python examples/data_channel/stand_up.py
🔌 Initializing robot connection...
🔗 Connecting to robot...
🕒 WebRTC connection        : 🟡 started       (11:35:16)
🕒 Signaling State          : 🟡 have-local-offer (11:35:16)
🕒 ICE Gathering State      : 🟡 gathering     (11:35:16)
🕒 ICE Gathering State      : 🟢 complete      (11:35:16)
🕒 ICE Connection State     : 🔵 checking      (11:35:16)
🕒 Peer Connection State    : 🔵 connecting    (11:35:16)
🕒 Signaling State          : 🟢 stable        (11:35:16)
🕒 ICE Connection State     : 🟢 completed     (11:35:16)
🕒 Peer Connection State    : 🟢 connected     (11:35:16)
INFO:root:Validation succeed
🕒 Data Channel Verification: ✅ OK            (11:35:16)
✅ Connected to robot successfully!
📊 Setting up state monitoring...
✅ State monitoring enabled
🤖 State #1: Mode=0, Progress=0, Gait=0, Height=0.076m, Roll=0.000, Pitch=-0.105, Yaw=-2.171
📋 Current motion mode: mcf
🦵 Starting stand up demonstration...
🎯 Executing command: StandUp
🤖 State #56: Mode=0, Progress=0, Gait=0, Height=0.083m, Roll=0.004, Pitch=-0.092, Yaw=-2.168
🤖 State #60: Mode=0, Progress=0, Gait=0, Height=0.157m, Roll=0.005, Pitch=-0.107, Yaw=-2.170
🤖 State #62: Mode=0, Progress=0, Gait=0, Height=0.202m, Roll=0.006, Pitch=-0.094, Yaw=-2.171
🤖 State #63: Mode=0, Progress=0, Gait=0, Height=0.222m, Roll=0.007, Pitch=-0.081, Yaw=-2.171
🤖 State #64: Mode=0, Progress=0, Gait=0, Height=0.241m, Roll=0.008, Pitch=-0.069, Yaw=-2.171
🤖 State #66: Mode=0, Progress=0, Gait=0, Height=0.277m, Roll=0.007, Pitch=-0.052, Yaw=-2.172
🤖 State #68: Mode=0, Progress=0, Gait=0, Height=0.311m, Roll=0.006, Pitch=-0.040, Yaw=-2.171
🤖 State #71: Mode=0, Progress=0, Gait=0, Height=0.328m, Roll=0.003, Pitch=-0.026, Yaw=-2.169
✅ Command StandUp executed successfully
✅ Robot is now standing up!
🦵 Stand up demonstration completed!
🕒 Signaling State          : ⚫ closed        (11:35:22)
🕒 ICE Connection State     : ⚫ closed        (11:35:22)
🕒 Peer Connection State    : ⚫ closed        (11:35:22)
🕒 WebRTC connection        : 🔴 disconnected  (11:35:22)
✅ WebRTC connection closed successfully

Moving the robot

The robot can be moved in standard "mcf" mode using the Move command:

await robot.execute_command("Move", {"x": 0.5, "y": 0.0, "z": 0.0}, wait_time=2.0)

There is also a "sports" mode, but that's a topic for another time.

Audio

This example records audio from the robot's microphone and saves it to a WAV file.

% python examples/audio/save_audio/save_audio_to_file.py
🕒 WebRTC connection        : 🟡 started       (18:05:01)
🕒 Signaling State          : 🟡 have-local-offer (18:05:01)
🕒 ICE Gathering State      : 🟡 gathering     (18:05:01)
🕒 ICE Gathering State      : 🟢 complete      (18:05:01)
🕒 ICE Connection State     : 🔵 checking      (18:05:01)
🕒 Peer Connection State    : 🔵 connecting    (18:05:01)
🕒 Signaling State          : 🟢 stable        (18:05:01)
🕒 ICE Connection State     : 🟢 completed     (18:05:01)
🕒 Peer Connection State    : 🟢 connected     (18:05:01)
INFO:root:Validation succeed
🕒 Data Channel Verification: ✅ OK            (18:05:01)
Starting audio recording for 5 seconds...
Audio recording complete, saved to output.wav
🕒 Signaling State          : ⚫ closed        (18:05:06)
🕒 ICE Connection State     : ⚫ closed        (18:05:06)
🕒 Peer Connection State    : ⚫ closed        (18:05:06)
🕒 WebRTC connection        : 🔴 disconnected  (18:05:06)

Playback of the audio file is also possible:

% python examples/audio/audio_player/webrtc_audio_player.py
🔌 Initializing robot connection...
🔗 Connecting to robot...
🕒 WebRTC connection        : 🟡 started       (18:18:43)
🕒 Signaling State          : 🟡 have-local-offer (18:18:43)
🕒 ICE Gathering State      : 🟡 gathering     (18:18:43)
🕒 ICE Gathering State      : 🟢 complete      (18:18:43)
🕒 ICE Connection State     : 🔵 checking      (18:18:44)
🕒 Peer Connection State    : 🔵 connecting    (18:18:44)
🕒 Signaling State          : 🟢 stable        (18:18:44)
🕒 ICE Connection State     : 🟢 completed     (18:18:44)
🕒 Peer Connection State    : 🟢 connected     (18:18:44)
INFO:root:Validation succeed
🕒 Data Channel Verification: ✅ OK            (18:18:44)
✅ Connected to robot successfully!
📋 Current motion mode: mcf
Starting audio playback of file: dog-barking.wav (UUID: a9f36e51-6a95-44f7-aef1-bbc708942b4f)
Playback command sent. Exiting.
🕒 Signaling State          : ⚫ closed        (18:18:45)
🕒 ICE Connection State     : ⚫ closed        (18:18:45)
🕒 Peer Connection State    : ⚫ closed        (18:18:45)
🕒 WebRTC connection        : 🔴 disconnected  (18:18:45)
✅ WebRTC connection closed successfully

Video

This example displays the robot's video stream in a window using OpenCV. You can quit the program by pressing the 'q' key.

Robot Image

% python examples/video/camera_stream/display_video_channel.py
🕒 WebRTC connection        : 🟡 started       (18:24:30)
🕒 Signaling State          : 🟡 have-local-offer (18:24:30)
🕒 ICE Gathering State      : 🟡 gathering     (18:24:30)
🕒 ICE Gathering State      : 🟢 complete      (18:24:30)
🕒 ICE Connection State     : 🔵 checking      (18:24:30)
🕒 Peer Connection State    : 🔵 connecting    (18:24:30)
🕒 Signaling State          : 🟢 stable        (18:24:30)
🕒 ICE Connection State     : 🟢 completed     (18:24:30)
🕒 Peer Connection State    : 🟢 connected     (18:24:30)
INFO:root:Validation succeed
🕒 Data Channel Verification: ✅ OK            (18:24:30)
Exiting...
Shutting down...
Disconnecting WebRTC...
🕒 Signaling State          : ⚫ closed        (18:24:46)
🕒 ICE Connection State     : ⚫ closed        (18:24:46)
🕒 Peer Connection State    : ⚫ closed        (18:24:46)
🕒 WebRTC connection        : 🔴 disconnected  (18:24:46)
WebRTC disconnected
Cleanup complete

Lidar

There is a lot to be said about the LiDAR, but I will leave that for another blog post.