S2: SDK2 en Python vs C++ — Comparativa Técnica¶
Objetivos¶
- Comparar la API de SDK2 en Python (
unitree_sdk2_python) y C++ (unitree_sdk2) - Identificar escenarios donde cada lenguaje es la elección correcta
- Configurar el entorno Python en el sandbox y en el PC2 del B2
- Escribir el equivalente Python de un programa C++ de SDK2 típico
Arquitectura de los bindings¶
Unitree publica dos SDKs oficiales que comparten el mismo protocolo DDS:
C++ unitree_sdk2 → DDS nativo (CycloneDDS C++)
Python unitree_sdk2_python → DDS via pybind11 / cyclone-dds-python
Ambos serializan los mismos tipos IDL (unitree_go, unitree_hg) para mensajes DDS. El protocolo en la red es idéntico; solo cambia la API del cliente.
Tabla comparativa¶
| Criterio | C++ SDK2 | Python SDK2 |
|---|---|---|
| Latencia de publicación | ~0.05 ms | ~0.2 ms (overhead GIL + pybind) |
| Frecuencia máxima fiable | 500 Hz (LowCmd a motores) | ~200 Hz (práctico para scripts) |
| Tiempo de arranque | Rápido (binario compilado) | Lento en cold start (~1 s) |
| Iteración / prototipado | Lento (recompila cada cambio) | Rápido (edit → run) |
| Acceso completo al SDK2 | Sí (todas las features) | Sí (parity ≥ SDK2 v1.2) |
| Integración con NumPy | No directa | Nativa |
| Facilidad en el curso | Compleja (CMake, headers) | Directa (pip install) |
Conclusión para el curso: usaremos Python en todos los ejercicios del sandbox. C++ es relevante si necesitas control de motores a 500 Hz en producción (ej. controlador de locomoción personalizado).
Setup del entorno Python¶
En el sandbox (ya preinstalado)¶
# Verificar instalación
python3 -c "import unitree_sdk2py; print('OK')"
# Ver versión y módulos disponibles
python3 -c "import unitree_sdk2py; help(unitree_sdk2py)"
En el PC2 del B2 / laptop del lab (instalación manual)¶
# Requisitos: Ubuntu 22.04/24.04, Python 3.10+
pip3 install unitree_sdk2_python
# Instalar CycloneDDS (dependency)
pip3 install cyclone-dds
# Configurar interfaz de red (reemplazar eth0 con tu interfaz)
export CYCLONEDDS_URI='<CycloneDDS><Domain><General><NetworkInterfaceAddress>eth0</NetworkInterfaceAddress></General></Domain></CycloneDDS>'
API Python: estructura de módulos¶
unitree_sdk2py/
├── b2/
│ ├── loco_client.py # LocoClient — locomotion (B2 específico)
│ └── sport_client.py # SportClient — behaviors
├── h1/
│ ├── loco_client.py # H1-2 locomotion
│ └── hand_client.py # H1-2 dexterous hands
├── go2/
│ ├── loco_client.py # Go2 locomotion
│ └── sport_client.py
├── core/
│ ├── channel.py # ChannelPublisher / ChannelSubscriber
│ ├── channel_factory.py # ChannelFactory.Instance()
│ └── sdk2_msg/ # Auto-generated IDL Python types
└── utils/
└── crc.py # CRC para LowCmd
Equivalencia Python / C++¶
C++ (referencia)¶
#include "unitree/robot/b2/loco_client.hpp"
int main() {
ChannelFactory::Instance()->Init(0, "eth0");
LocoClient loco_client;
loco_client.Init();
loco_client.Move(0.5f, 0.0f, 0.0f); // vx, vy, vyaw
return 0;
}
Python equivalente¶
#!/usr/bin/env python3
"""
Equivalente Python del ejemplo C++ de locomotion.
Sandbox: usa sdk2_bridge_sim en lugar del import real.
"""
import time
# ── Simulación (sandbox) ────────────────────────────
from sdk2_bridge_sim import LocoClientSim as LocoClient # (1)!
# ── Robot real (PC2 o laptop en la red del B2) ──────
# from unitree_sdk2py.core.channel import ChannelFactory
# from unitree_sdk2py.b2.loco_client import LocoClient
# ChannelFactory.Instance().Init(0, "eth0")
def main():
loco = LocoClient()
loco.Init()
print("Moviéndose hacia adelante...")
loco.Move(0.5, 0.0, 0.0) # vx=0.5 m/s, vy=0, vyaw=0 # (2)!
time.sleep(2.0)
print("Rotando...")
loco.Move(0.0, 0.0, 0.5) # vx=0, vy=0, vyaw=0.5 rad/s # (3)!
time.sleep(2.0)
loco.StopMove()
print("Detenido.")
if __name__ == '__main__':
main()
- Swapping this one line transitions the exact same code to real hardware
LocoClient.Move(vx, vy, vyaw)— holonomic, units: m/s and rad/s- B2 supports lateral movement (vy ≠ 0) unlike differential robots
SDK2 de bajo nivel en Python: LowCmd / LowState¶
Para control directo de motores (sin pasar por LocoClient):
#!/usr/bin/env python3
"""Control de bajo nivel: leer LowState, publicar LowCmd."""
import time
from unitree_sdk2py.core.channel import ChannelFactory, ChannelSubscriber, ChannelPublisher
from unitree_sdk2py.idl.unitree_go.msg.dds_ import LowState_, LowCmd_
from unitree_sdk2py.utils.crc import CRC
# NOTA: Este ejercicio solo funciona en robot real.
# El LowCmd a 500 Hz puede causar movimientos bruscos — ¡usar con precaución!
latest_state: LowState_ = None
def state_handler(msg: LowState_):
global latest_state
latest_state = msg
def main():
ChannelFactory.Instance().Init(0, "eth0")
sub = ChannelSubscriber("rt/lowstate", LowState_)
sub.Init(state_handler, 10)
pub = ChannelPublisher("rt/lowcmd", LowCmd_)
pub.Init()
time.sleep(0.5) # Esperar primer mensaje
if latest_state:
for i, motor in enumerate(latest_state.motor_state):
print(f"Motor {i:02d}: q={motor.q:.3f} rad, dq={motor.dq:.3f} rad/s")
Solo en robot real
La publicación en rt/lowcmd afecta directamente a los motores. No existe en sim_lite. Úsalo solo durante la sesión de robot real con el B2 en modo seguro.
Ejercicios¶
Ejercicio 1 — Medir latencia¶
Escribe un script que publique una marca de tiempo en un tópico y la consuma inmediatamente, midiendo el round-trip. Compara Python vs la latencia teórica de C++ (0.05 ms).
Ejercicio 2 — LocoClient move en simulación¶
Usa sdk2_bridge_sim para mover el robot describiendo un cuadrado de 1 m de lado. Mide el tiempo de cada lado con time.time().
Ejercicio 3 — Introspección de tipos IDL¶
Ejecuta python3 -c "from unitree_sdk2py.idl.unitree_go.msg.dds_ import LowState_; print(LowState_.__annotations__)". Enumera los campos del mensaje y compara con la documentación oficial.
Ejercicio 4 — Comparativa de frecuencia¶
Suscríbete a sportmodestate (a través de sdk2_bridge_sim) y mide cuántos mensajes recibes por segundo. ¿Cuánto añade el wrapper Python comparado con los 500 Hz nativos?
Ejercicio 5 — Port a B2 vs Go2¶
Examina unitree_sdk2py/b2/loco_client.py y unitree_sdk2py/go2/loco_client.py. ¿Qué métodos existen en B2 pero no en Go2? (Pista: capacidades de carga del B2.)
Pasa al robot real¶
Checklist para robot real
- Conectar laptop/gateway a la red del B2 (WiFi
Unitree_B2_XXXXo Ethernet) - Verificar conectividad:
ping 192.168.123.1 - Configurar interfaz DDS:
export CYCLONEDDS_URI='...' - Cambiar el import de
sdk2_bridge_simal real - Probar
ChannelFactory.Instance().Init(0)antes de cualquier comando de movimiento