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

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.

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:

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:
| Action | Description |
|---|---|
| BackFlip | Perform a back flip (advanced) |
| Content | Contented/relaxed gesture |
| Dance1 | Dance routine 1 |
| Dance2 | Dance routine 2 |
| FrontFlip | Perform a front flip (advanced) |
| FrontJump | Jump forward |
| FrontPounce | Forward pounce |
| Heart | Heart gesture with legs |
| Hello | Greeting gesture |
| LeftFlip | Perform a left flip (advanced) |
| RecoveryStand | Recover from a fall and return to standing |
| RiseSit | Rise the body from sitting pose |
| Scrape | Scraping motion with the forelegs |
| Sit | Sit down |
| StandDown | Lower body to rest (safe stand down) |
| StandUp | Stand up from sitting/lying to a stable stance |
| Stretch | Full-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.

% 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.