The unitree_sdk2 in 2026: A practitioner's map of Unitree's public SDK
Where the SDK actually lives, what's in it, what isn't, and the seven control surfaces you need to know before you ship code.
qtvue · SDK deep-dive
unitree-sdk2-deep-dive
TL;DR. The unitree_sdk2 is the official C++ and Python SDK across the full Unitree range (Go2, B2, G1, G1-D, R1, H1, H2, Arms). It speaks Cyclone DDS over UDP multicast on a local LAN, exposes seven control surfaces (low-level joint, high-level sport, arm, hand, lidar, audio, video), and ships with
unitree_ros2for the ROS 2 ecosystem. The official docs are fragmented — this article is the map we wish existed.
1. Where the SDK lives
The public SDK is split across four GitHub repos maintained by Unitree and Unitree-affiliated contributors:
| Repo | What it ships |
|---|---|
unitree_sdk2 | C++ core (builds on Ubuntu 20.04 / 22.04) |
unitree_sdk2_python | Python bindings (CPython 3.8+) |
unitree_ros2 | ROS 2 Humble / Jazzy DDS-XRCE bridges |
unitree_sim_isaaclab | Isaac Lab environment files + URDF |
All four are mirrored on a single internal CI we maintain; in practice, we clone them as a sparse checkout into our customer repos because the release tags drift between the C++ and Python repos.
Gotcha #1. The C++ repo is tagged
vX.Ybut the Python bindings are tagged independently. Av2.4.0C++ tag may ship withv2.3.1Python bindings. Pin both tags in yourrequirements.txt/CMakeLists.txtand treat them as a coupled pair.
2. Transport: Cyclone DDS over UDP multicast
The SDK assumes a direct Ethernet (or Wi-Fi) connection between your
control host and the robot's onboard compute. It uses Cyclone DDS
with UDP multicast on 239.255.0.1:8888 by default. Every state
stream (joint states, IMU, LiDAR, battery) and every command stream
(joint commands, locomotion goals, arm trajectory points) goes over
this one multicast group.
In production this has two practical consequences:
- You need a clean Layer 2. Multicast doesn't survive routers,
and many corporate switches drop multicast by default. We tell
customers to put the robot on its own VLAN with the control host,
with the switch port configured for
multicast-floodor with IGMP snooping properly enabled. - You can't run two SDK instances on the same VLAN. Two hosts sending locomotion commands at the same time will fight over the robot. This is the most common customer support issue we see. We always run the SDK on a dedicated NUC or Jetson, not on a shared workstation.
# Explicit DDS configuration — pin the interface, force IPv4, set QoS
import cyclonedds
from cyclonedds.domain import DomainParticipant
from cyclonedds.core import Qos, Policy
qos = Qos(
Policy.Reliability.BestEffort,
Policy.History.KeepLast(10),
Policy.Durability.Volatile,
)
# Tell Cyclone DDS which interface to use on multi-NIC hosts
cyclonedds.config_set("Local./Network/Interfaces", "eth0")
3. The seven control surfaces
When we say "the SDK" we mean a federated set of modules. Each one talks to a separate DDS topic and exposes its own message types:
┌──────────────────────────────────────────────────────┐
│ unitree_sdk2 surfaces │
├──────────┬───────────────┬───────────────────────────┤
│ LowState │ <sport> │ SportClient (high-level │
│ LowCmd │ <hand> │ locomotion, arm, hand, │
│ │ <arm> │ LiDAR, audio, video) │
├──────────┼───────────────┼───────────────────────────┤
│ MotorCmd │ <lowlevel> │ Direct joint torque / │
│ MotorState│ │ position / velocity PD │
├──────────┼───────────────┼───────────────────────────┤
│ ArmCommand│ <arm_sdk> │ 6+ DoF arm trajectory │
│ │ │ (g1, h1, z1) │
├──────────┼───────────────┼───────────────────────────┤
│ LidarScan│ <lidar> │ 4D LiDAR point cloud │
├──────────┼───────────────┼───────────────────────────┤
│ AudioData│ <audio> │ Microphone array + │
│ │ │ speaker playback │
├──────────┼───────────────┼───────────────────────────┤
│ VideoFrame│<videohub> │ RGB-D camera stream │
└──────────┴───────────────┴───────────────────────────┘
The most common mistake is mixing the high-level SportClient
calls with low-level MotorCmd writes in the same process. The
SDK doesn't enforce a single-writer invariant — both work — but
they fight at runtime and you'll see the robot twitch erratically.
We always isolate them: one process owns low-level joints (or none
does), one process owns high-level goals, one process owns the arm.
4. The Python bindings
The Python bindings wrap the C++ core with pybind11. They expose
the same message types as dataclasses and the same clients as
high-level Python classes. The trade-off is GC pressure — every
state callback allocates a new message, and Python's GC can stall
the 200 Hz control loop if you're not careful.
Two patterns that work:
# 1. Use the pre-allocated read buffer for hot paths
from unitree_sdk2py.core.channel import ChannelSubscriber
from unitree_sdk2py.idl.unitree_hg import LowState_
sub = ChannelSubscriber("rt/lowstate", LowState_)
sub.Init()
while not shutdown_event.is_set():
msg = sub.Read() # reuses an internal buffer
process(msg) # no per-frame allocation
time.sleep(0.005) # 200 Hz
# 2. For non-hot paths (telemetry), batch into lists
states = []
def cb(msg: LowState_):
states.append(msg.tick)
if len(states) >= 1000:
flush(states) # every ~5s at 200 Hz
states.clear()
5. The four footguns
These are the bugs we hit on real engagements. Each one has cost us or a customer at least a week of debugging.
Footgun 1 — Walk(true) doesn't fully enable sport mode.
You need to call BalanceStand(true) first, wait for the robot to
settle (~0.5s), then call Walk(true). Skip the balance step and
the robot tips over on its first Move().
Footgun 2 — StopMove() doesn't actually stop.StopMove() issues a single zero-velocity command. The high-level
controller interpolates to a stop over ~0.3s. If you're on a real-
time control loop, you need to also send Move(0, 0, 0) repeatedly
until the robot is stationary. Or just use SportClient.Damp() —
the SDK's emergency damping state.
Footgun 3 — Joint index mismatch between Go2 and G1.
Go2 has 12 joints in a quadruped arrangement (FR/FL/HR/HL × 3 each).
G1 has 23–43 joints in a humanoid arrangement. The LowCmd message
flattens joints into a single array; the index ordering is documented
per-platform. There is no shared kJointNames constant —
copy the order from your platform's URDF.
Footgun 4 — Multicast TTL leaks.
The default DDS config uses TTL=1. On a multi-switch setup this is
fine. But if you ever route traffic through a Linux bridge with
brctl, the bridge will reset the TTL on forwarding and you'll see
the multicast loop back. Symptom: the control host sees its own
commands. Fix: set TTL=64 in the DDS QoS or use a separate VLAN.
6. ROS 2 — the unitree_ros2 layer
unitree_ros2 wraps the SDK with proper ROS 2 topics and TF trees.
For most customers, this is the right entry point — you get RViz,
ros2 bag, ros2 topic echo, and the entire ROS 2 ecosystem for
free. The trade-off is one extra hop of latency (~2 ms) and the
need to build a Cyclone DDS / Fast DDS bridge correctly.
We have a working ros2 humble setup with cyclonedds configured
that most engagements start from. The unitree_ros2 repo includes
example launch files for each platform.
7. What we ship
For customer engagements, we wrap the SDK with our own safety layer:
- Heartbeat watchdog — if the control host misses three
consecutive state updates, the robot goes to
Damp(). - Velocity limiter — caps
Move(x, y, yaw)to the platform's published max × 0.8. - Geofence — uses LiDAR SLAM pose to enforce a polygon. If the robot leaves the polygon, it stops and awaits human approval.
- Emergency stop — physical E-stop button wired to a watchdog
GPIO that calls
Damp()directly, bypassing the SDK.
We open-source these patterns at github.com/UDGOK/qtvue under MIT.
Where to start
If you're picking up the SDK for the first time:
- Clone
unitree_sdk2_pythonand run theexample/sport_clientexample with your Go2 on the bench. - Switch to
unitree_ros2andros2 topic echo /lowstate. - Pick a target (autonomous patrol? manipulation? imitation
learning?) and read the matching
unitree_sim_isaaclabenv. - Reach out if you're stuck —
hello@qtvue.comor our intake form.
The SDK is good. It's not perfect, and the docs are sparse. But once you understand the seven surfaces and the four footguns, you can ship real work on top of it.
Written by
qtvue engineering
Engineering team
The qtvue engineering team works on Unitree integration projects every week. We wrote this guide because the official SDK docs are fragmented and Chinese-first, and we wanted a single English reference we could hand to new engineers.
Working on something Unitree-shaped?
Five-question form, three minutes, reply within 1 business day.
Keep reading.
All articles →qtvue · Sim-to-real
isaac-lab-sim-to-real
Isaac Lab + Unitree G1: training locomotion in a weekend
A reproducible workflow for going from a fresh Isaac Lab install to a sim-to-real transferred G1 policy in 48 hours. We share the URDF, the reward function, the domain randomization ranges, and what doesn't transfer.
qtvue · Imitation learning
lerobot-meets-unitree-go2
LeRobot meets Unitree Go2: 50 demonstrations to a walking policy
Imitation learning is the right tool for quadruped locomotion over rough terrain. We share the workflow that goes from 50 teleoperated demonstrations to a deployed Go2 policy in 6 hours.
qtvue · Security advisory
post-unipwn-2025
Post-UniPwn: What the 2025 Bluetooth exploit revealed about Unitree's security model
A researcher's walk-through of CVE-style disclosure, the G1 attack surface, what Unitree patched in firmware 1.5.2, and what fleet operators should still do in 2026.