A full-featured command-line controller for the Unitree Go2 (and experimental G1) robots. Communicates directly over WebRTC via the robot's built-in data channel, with no middleware required.
Work in progress. Expect rough edges and breaking changes. Contributions and bug reports are welcome.
- Interactive REPL - tab-completed shell for live robot control
- Single-shot CLI - scriptable one-liner commands (
z4rtc.py stand_up) - 149 commands - locomotion, poses, tricks, configuration, services, sensors
- 50 subscribable topics - IMU, motor state, battery, GNSS, odometry, and more
- 40 USLAM sub-commands - mapping, navigation, patrol, localization
- Video streaming - H.264 live view or recording via ffplay/mpv/vlc
- Photo capture - single frame grab with auto-open (feh/eog/xdg-open)
- Audio I/O - listen to robot mic, upload audio files, megaphone broadcast
- LiDAR viewer - live 3D point cloud in browser (Three.js + WebSocket)
- SLAM & navigation - lidar control, mapping, obstacle avoidance, patrol routes
- GPT streaming - real-time GPT responses with token-by-token display
- Pet mode - AI personality, pet background images, natural language commands
- Map download - retrieve map.pcd, map.pgm, map.txt from the robot
- Cloud mode - connect via Unitree cloud (email/password auth + TURN)
- G1 support - experimental humanoid mode with arm control
- Auto-discovery - finds robots on the LAN via UDP multicast
pip install -r requirements.txt# Debian/Ubuntu
sudo apt install ffmpeg mpv alsa-utils
# ffplay (part of ffmpeg), mpv, or vlc needed for video
# aplay needed for audio listening
# feh, eog, or xdg-open for photo viewingNo build step. Just make the script executable:
chmod +x z4rtc.pyOr run it directly with python3 z4rtc.py.
| Flag | Arguments | Description |
|---|---|---|
--ip |
<addr> |
Robot IP address (auto-discovers if omitted) |
--g1 |
G1 humanoid robot mode | |
--user |
<email> |
Cloud login email |
--pass |
<password> |
Cloud login password |
--sn |
<serial> |
Robot serial number (cloud, optional) |
--debug |
Verbose debug output |
./z4rtc.py # Auto-discover robot and open REPL
./z4rtc.py --ip 192.168.1.100 # Connect to a known IPOnce connected, type any command at the go2> prompt. Tab completion is supported.
./z4rtc.py stand_up # Stand the robot up
./z4rtc.py --ip 10.0.0.15 move 0.5 0 0 # Walk forward at 0.5 m/s
./z4rtc.py sub low_state 5 # Subscribe `low_state` topic for 5 seconds
./z4rtc.py video_show # Open live video stream
./z4rtc.py photo snapshot.jpg # Capture a photo
./z4rtc.py light_color red # Solid red LED
./z4rtc.py light_color blue 5 500 # Blue flashing for 5sIf no --ip flag is given, auto-discovery runs silently before the command.
./z4rtc.py --user you@email.com --pass yourpass # Auto-detect robot
./z4rtc.py --user you@email.com --pass yourpass --sn ABC123 # Specific robot./z4rtc.py --g1 --ip 10.0.0.15 wave| Command | Description |
|---|---|
help |
Full command reference |
apis |
All API commands with topics and payloads |
subs |
All subscribable topics |
topics |
All topic names |
| Command | Arguments | Description |
|---|---|---|
move |
<vx> <vy> <vyaw> |
Set velocity (m/s, m/s, rad/s) |
euler |
<roll> <pitch> <yaw> |
Set body orientation (radians) |
body_height |
<meters> |
Adjust body height |
foot_raise_height |
<meters> |
Adjust step height |
switch_gait |
0-4 |
Gait (idle/trot/trot_run/climb/rev_climb) |
speed_level |
-1/0/1 |
Slow / normal / fast |
continuous_gait |
0/1 |
Continuous gait mode |
economic_gait |
0/1 |
Energy-saving gait |
pose |
0/1 |
Lock current pose |
stand_up, stand_down, balance_stand, sit, rise_sit, recovery_stand, damp, stop_move
hello, stretch, wiggle_hips, wallow, content, heart, scrape, trigger,
dance1, dance2, dance3, dance4, front_flip, back_flip, left_flip, right_flip,
front_jump, front_pounce, moon_walk, bound, lead_follow, standup, cross_walk,
static_walk, trot_run, one_sided_step
| Command | Arguments | Description |
|---|---|---|
video_show |
Live video stream (ffplay/mpv/vlc) | |
video_save |
[file] [secs] |
Record video to file |
photo |
[file] |
Capture single frame (auto-opens viewer) |
audio_listen |
Stream robot mic to speakers | |
audio_save |
[file] [secs] |
Record robot audio |
audio_upload |
<file> |
Upload WAV/MP3 to robot |
megaphone_play |
<file> |
Broadcast audio through robot speakers |
audio_play |
<id> |
Play uploaded audio by ID |
audio_volume_set |
0-10 |
Set volume |
audio_play_mode_set |
<mode> |
Play mode (single_cycle/no_cycle/list_loop) |
| Command | Arguments | Description |
|---|---|---|
light_color |
<color> [time] [flash_cycle] |
LED color (white/red/yellow/blue/green/cyan/purple) |
light_set |
0-10 |
Set brightness |
light_get |
Get current light config |
Examples: light_color red (constant red), light_color blue 5 500 (blue flashing for 5s)
| Command | Arguments | Description |
|---|---|---|
lidar_on |
Enable lidar | |
lidar_off |
Disable lidar | |
lidar_show |
Live 3D LiDAR point cloud in browser | |
lidar_stop |
Stop LiDAR viewer | |
slam_start |
Start SLAM mapping | |
slam_stop |
Stop SLAM mapping | |
obstacles_on |
Enable obstacle avoidance | |
obstacles_off |
Disable obstacle avoidance | |
get_map_file |
Download map files (map.pcd/pgm/txt) | |
uslam_cmd |
<cmd> |
USLAM command (40 sub-commands) |
USLAM sub-commands include mapping, localization, navigation, and patrol control.
Run ./z4rtc.py help to see all 40 sub-commands with descriptions.
| Command | Arguments | Description |
|---|---|---|
config_get |
<name> |
Read a config key |
config_set |
<json> |
Write a config key |
config_delete |
<name> |
Delete a config key |
config_meta |
<name> |
Get config metadata |
service_list |
List running services | |
service_start |
<name> |
Start a service |
service_stop |
<name> |
Stop a service |
motion_switch |
<mode> |
Switch motion mode (Go2: mcf, Go2-W: ai-w) |
traffic_save |
on/off |
Toggle traffic saving |
| Command | Description |
|---|---|
run_hw_version |
Get hardware version |
run_pkg_version |
Get firmware version |
run_self_test |
Run self-diagnostic |
run_ip_address |
Get robot IP |
run_rf_power |
Get RC RF power |
run_calibrate_save |
Save joint calibration |
run_get_sn |
Get robot serial number |
run_start_sport |
Start sport mode |
run_error_code |
Get basic service error code |
run_test_success |
Run success diagnostic |
run_test_error |
Run error diagnostic |
| Command | Arguments | Description |
|---|---|---|
pet_switch |
Toggle pet mode | |
pet_ask |
<text> |
Natural language command |
pet_background |
[file] |
Get pet environment image (base64 JPEG) |
pet_role_list |
List available AI personalities | |
gpt_req |
<text> |
Streaming GPT request |
| Command | Arguments | Description |
|---|---|---|
joy |
<lx> <ly> <rx> <ry> [keys] |
Virtual joystick input (requires switch_joystick 1) |
Keys: R1=1 L1=2 Start=4 Select=8 R2=16 L2=32 A=256 B=512 X=1024 Y=2048
| Command | Arguments | Description |
|---|---|---|
msg |
<topic> [json] |
Fire-and-forget message |
req |
<topic> [json] |
Request with auto-subscribe |
raw |
<json> |
Send arbitrary JSON directly (no envelope) |
rtc_inner_req |
[json] |
Bridge internal request |
msg and req wrap your payload in a {"type", "topic", "data"} envelope automatically.
raw bypasses this and sends your JSON as-is — useful when the standard envelope doesn't
match what the robot expects, without needing to add a new command for every edge case.
msg rt/utlidar/switch on # Wrapped: {"type":"msg","topic":"...","data":"on"}
req rt/api/sport/request '{"api_id": 1001}' # Wrapped: {"type":"req","topic":"...","data":{...}}
raw '{"type":"req","topic":"rt/api/sport/request","data":{"header":{"identity":{"api_id":1001}}}}'
raw '{"type":"rtc_inner_req","topic":"","data":{"req_type":"custom","instruction":"test"}}'| Command | Arguments | Description |
|---|---|---|
sub |
<topic> [seconds] |
Subscribe to topic (default 10s) |
unsub |
<topic> |
Unsubscribe |
quiet |
Toggle message display (interactive only) |
Popular topics: low_state, sport_state, multiple_state, robot_pose, bms_state, wireless_controller
Run ./z4rtc.py subs for the full list of 50 subscribable topics.
z4rtc finds your robot automatically via UDP multicast (231.1.1.1 and 239.255.1.1).
If multiple robots are found, use --ip to specify which one.
Single-file, zero-middleware WebRTC controller:
- ICE/WebRTC - uses
aiortcwith monkey-patched shared ICE credentials - SDP exchange - port 9991 (fw >= 1.1.11) with port 8081 fallback
- H.264 video - Annex B stream piped to external player or saved via ffmpeg
- Encryption - RSA + AES-ECB for SDP; AES-GCM for cloud telemetry
- Protocol - binary-framed JSON over WebRTC data channel with chunk reassembly
- REPL - readline with tab completion; restores terminal state on exit
See LICENSE for details.
Copyright (c) 2026 Ziggy (Elia Yehuda) - @z4ziggy