# 宇树 G1 运动控制模式切换与 Sport 服务接口解析

> 原文链接：https://robotics.hellobike.com/wiki/g1-motion-mode-switching
> 频道：wiki | 更新日期：2026-10-10 | 标签：G1, SDK, motion_switcher, LocoClient, 模式切换, DDS
> 核心摘要：深入剖析宇树 G1 的 motion_switcher 控制权切换机制、LocoClient (sport) 状态机 API、RPC 消息交互原理及 CycloneDDS 依赖选型。

# 宇树 G1 运动控制模式切换与 Sport 服务接口解析

> 本文为宇树 G1 EDU 真机实操记录，固件/SDK 版本以文中标注为准。

## 1. 控制权管理：motion_switcher 机制

在宇树 G1 的官方 SDK（`unitree_sdk2py`）中，`motion_switcher` 是管理整机高层运动控制权归属的核心服务，负责在不同控制模式间进行仲裁切换。

### 1.1 服务定义与接口

- **服务名称**：`MOTION_SWITCHER_SERVICE_NAME = "motion_switcher"`
- **API 版本**：`1.0.0.1`
- **核心方法**：
  - `CheckMode()`（API ID 1001）：查询当前处于激活状态的高层运动模式。接口返回 `(code, dict|None)`，成功时返回字典，例如 `{'form': '0', 'name': 'ai'}`。
  - `SelectMode(nameOrAlias)`（API ID 1002）：请求切换至指定的控制模式。参数字典形如 `{"name": nameOrAlias}`。常用可选模式包括 `"ai"`、`"normal"` 和 `"advanced"`。
  - `ReleaseMode()`（API ID 1003）：释放当前占用的运动控制模式。

### 1.2 实操切换建议

在真机运行自主导航或高层运动控制前，应先通过 `CheckMode()` 核验当前模式是否为目标模式（如 `"ai"`）。若不是，需调用 `SelectMode("ai")` 并再次调用 `CheckMode()` 复核模式名称。由于 `SelectMode` 和 `ReleaseMode` 底层返回对象中不携带数据载荷，调用端必须显式回读状态来确认切权是否真正生效。

## 2. sport 服务与 LocoClient 运动接口

宇树 G1 将步态与全身运动状态机封装在 `sport` 服务中，在 Python SDK 中由 `LocoClient` 实现。源码常量中明确声明 `LOCO_SERVICE_NAME = "sport"`，因此两者在架构上指向同一底层服务。

### 2.1 状态机与核心 API

| API 名称 | API ID | 签名与返回值 | 功能说明 |
|---|---|---|---|
| `GetFsmId()` | 7001 | `() -> (code, data)` | 获取当前有限状态机（FSM）状态 ID，正常运行常态为 802 |
| `SetFsmId(id)` | 7101 | `(id: int) -> code` | 切换底盘有限状态机状态 |
| `SetVelocity(vx, vy, omega, duration)` | 7105 | `(float, float, float, float) -> code` | 下发速度指令，支持指定单次持续时长 |
| `SetArmTask(id)` | 7106 | `(id: int) -> code` | 触发手臂控制任务 |

### 2.2 便捷方法调用陷阱

SDK 在 `LocoClient` 封装了系列高层动作便捷方法：
- `Start()`：等价于 `SetFsmId(500)`
- `Squat2StandUp()`：等价于 `SetFsmId(706)`
- `Sit()`：等价于 `SetFsmId(3)`
- `Damp()`：等价于 `SetFsmId(1)`（阻尼模式）
- `StopMove()`：等价于 `SetVelocity(0, 0, 0)`

**工程注意事项**：
上述便捷函数在 SDK 内部实现中**直接返回 None**，并不返回底层 RPC 执行状态码 `code`。若上层业务系统根据返回值是否非零来判断成功与否，极易导致逻辑误判。正确做法是直接调用 `SetFsmId` / `SetVelocity` 检查返回的 `code`，或在调用便捷函数后通过 `GetFsmId()` 轮询状态机确认动作到位。

此外，真机落地测试显示，机器人执行 `Sit()` 坐下后，若现场由遥控器强制介入起立，容易因质心与脚掌接触面变化造成失稳跌倒。因此在地面运行逻辑中，应避免未受保护的自动坐下动作。

## 3. RPC 通信机制与 CycloneDDS 依赖排障

### 3.1 消息载荷结构

运动控制与切权 RPC 基于 CycloneDDS 话题通信，请求和响应格式定义于 `unitree_api.msg.dds_`：
- `Request_`：包含 `header`（请求头）、`parameter`（JSON 字符串）与 `binary`（字节流 `sequence[uint8]`）。
- `Response_`：包含 `header`、`data`（JSON 字符串）与 `binary`。

### 3.2 错误码 3102（RPC_ERR_CLIENT_SEND）根因与解决

在开发与联调过程中，客户端向机器人发起 RPC 请求时可能遭遇 `3102`（`RPC_ERR_CLIENT_SEND`）报错。经真机排查，该报错的核心原因是 Python 环境与底层 CycloneDDS C 语言动态链接库版本不兼容：
- 若安装了高版本或不匹配的 wheel 包（例如部分平台上的 `11.x` 版本），将导致 DDS 发送缓冲区序列化失败。
- **解决方案**：运行环境必须严格锁定并编译安装 CycloneDDS `0.10.2` 版本（包含对应的 C 库动态库与 `cyclonedds==0.10.2` Python 绑定），确保底层 `LD_LIBRARY_PATH` 优先加载对应动态库。在依赖版本正确时，官方 SDK 原生的 `uint8` 消息定义无需任何额外 hack 即可稳定通信。

## 原始参考来源 (Sources)

- [unitree_sdk2_python](https://github.com/unitreerobotics/unitree_sdk2_python)