Files
DeepRoboticsLab-S10-proto/山猫S10-遥控指令.md
caowangrenbo 5e2017de69 Initial commit: S10 remote control module, demos, and docs
- s10_robot.py: protocol framing, mTLS client, heartbeat, status and control APIs
- demo_stand_rotate_lie.py: stand, rotate in place, lie down with safe recovery
- status_probe.py: read-only status subscription
- tests: protocol unit tests and local fake robot
- docs: remote control command reference
2026-10-29 20:31:30 +08:00

468 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 山猫 S10 遥控指令整理
> **资料依据**
> - 《山猫S10 软件开发指南》V1.0.2(2026-10-13)第 1 章「本体监控协议」
> - 《DeepRoboticsLab 2.5.4 备份与密钥分析》(2026-10-29)连接与加密实测部分
>
> **整理日期:**2026-10-29
>
> 本文只整理「遥控(手动/半自主)控制」所需的内容:连接方式、帧格式、控制类指令、必要的状态回读。
> 导航任务下发、CPU/电池等设备监控、ROS2 话题不在本文范围内。
---
## 0. 一分钟速查
| 功能 | Type | Command | 关键 Items | 响应 |
|---|---|---|---|---|
| 心跳(保活 + 开启状态上报) | `0x00100064` | `0x00000005` | 无 | 有 |
| 使用模式切换 | `0x00100002` | `0x00500002` | `Mode` | 有 |
| 运动状态转换(站/趴/阻尼) | `0x00100001` | `0x00200002` | `MotionParam` | 有 |
| 运动步态切换 | `0x00100001` | `0x00300002` | `GaitParam` | 有 |
| 运动控制(轴指令) | `0x00100001` | `0x00100002` | `X/Y/Z/Roll/Pitch/Yaw` | **无响应帧** |
| 运动控制(速度指令) | `0x00100001` | `0x00110002` | `X/Y/Z/Roll/Pitch/Yaw` | **无响应帧** |
| 自定义灯语 | `0x00100005` | `0x00200002` | `CustomMode`、`Led[]` | 有 |
| 自主充电 | `0x00100004` | `0x00100002` | `Charge` | 有 |
| 运动 SDK 模式 | `0x00100005` | `0x00300002` | `SDKEnable`、`Frequency` | 有 |
| 休眠模式设置 | `0x00100002` | `0x00400002` | `Sleep`、`Auto`、`Time` | 有 |
十进制换算(JSON 里必须写数字,不能写带空格的十六进制):
| 十六进制 | 十进制 | 十六进制 | 十进制 |
|---|---|---|---|
| `0x00100001` | 1048577 | `0x00000005` | 5 |
| `0x00100002` | 1048578 | `0x00100002` | 1048578 |
| `0x00100004` | 1048580 | `0x00110002` | 1114114 |
| `0x00100005` | 1048581 | `0x00200002` | 2097154 |
| `0x00100064` | 1048676 | `0x00300002` | 3145730 |
| `0x0010007f` | 1048703 | `0x00400002` | 4194306 |
| `0x00f00000` | 15728640 | `0x00500002` | 5242882 |
---
## 1. 连接与帧格式
### 1.1 连接参数
| 项目 | 值 |
|---|---|
| 机器人地址(实测) | `10.21.33.103` |
| TLS(TCP + 加密) | 端口 **30003** |
| DTLS(UDP + 加密) | 端口 **30004** |
| 角色 | 机器人为服务端,上位机/外部板卡为客户端 |
| 加密 | 默认开启;关闭需修改 `robotserver` 配置文件(联系技术支持),系统更新不会覆盖该配置 |
**安全机制是 mTLS 双向认证**(实测结论,不是可选的):
- 客户端必须提供 `client_chain.crt` + `client.key`,服务端用同一 CA 链验证;
- 不带客户端证书,服务端直接返回 `TLSV13_ALERT_CERTIFICATE_REQUIRED`;
- 明文 TCP 会被服务端用 TLS alert 拒绝(`15 03 03 00 02 02 16`);
- 实测协商结果:TLSv1.3 / `TLS_AES_256_GCM_SHA384`,服务端证书 `CN=server.local`,可用导出的 `root_ca.crt` 校验通过。
证书材料来自 App 的 `assets/certs/`,运行时复制到 `filesDir/certs/`:
| 文件 | 说明 |
|---|---|
| `client.key` | 客户端私钥,RSA 2048 / PKCS#8 / PEM,无口令 |
| `client_chain.crt` | 客户端证书链(client.local ← TLS CA ← Project CA ← Root CA) |
| `root_ca.crt` | 根 CA,有效期 2026-04-03 ~ 2036-03-31 |
> 客户端叶子证书有效期 2026-04-03 ~ 2027-04-03,过期后需重新获取。
> TCP 与 DTLS 复用同一套证书和同一个 TLS 上下文,不存在单独的对称密钥或 PSK。
### 1.2 协议帧(16 字节头 + ASDU)
| 偏移 | 长度 | 字段 | 说明 |
|---|---|---|---|
| 0 | 4 | 同步字符 | 固定 `EB 91 EB 90` |
| 4 | 2 | 长度 | ASDU 字节数,**小端**;单帧 ASDU ≤ 1000 字节,分包总长 ≤ 65535 |
| 6 | 2 | 报文 ID | 小端,请求/响应同值,0 → 65535 循环;分包须一致 |
| 8 | 1 | ASDU 格式 | `0x00` = XML,`0x01` = JSON |
| 9 | 1 | 当前包编号 | 从 0 递增,≤ 255 |
| 10 | 1 | 协议版本 | 固定 `0x01` |
| 11 | 5 | 预留 | 全 0 |
### 1.3 ASDU 结构
根节点固定为 `PatrolDevice`,四个字段:
| 字段 | 含义 |
|---|---|
| `Type` | 消息类型 |
| `Command` | 消息命令码 |
| `Time` | 本地时区发送时间,`YYYY-MM-DD HH:MM:SS`(实测响应带毫秒,如 `2026-10-29 19:50:18.403`,两种均可) |
| `Items` | 参数项 |
推荐 JSON(官方说明:JSON 处理更快、支持的消息类型更全)。
### 1.4 数值写法(容易踩坑)
官方文档示例里写的是 `"Type": 0x0010 0064` 这种**带空格的十六进制,这不是合法 JSON**。实际发送必须用整数:
```json
{"PatrolDevice":{"Type":1048676,"Command":5,"Time":"2026-01-01 00:00:00","Items":{}}}
```
---
## 2. 手动遥控操作时序
```
① 建立 mTLS 连接 (30003/TCP 或 30004/UDP)
│
② 心跳 0x00100064 / 0x00000005,频率 ≥ 1Hz(持续保活)
│ └─ 机器人开始向该 IP:端口 上报状态(2Hz/10Hz/1Hz)
│
③ 使用模式 0x00100002 / 0x00500002 Mode=0 常规(手动)
│
④ 运动状态 0x00100001 / 0x00200002 MotionParam=1 站立
│ └─ ASDU 下发站立完成后会自动进入 RL 控制状态
│
⑤ 步态 0x00100001 / 0x00300002 GaitParam=0x1001 基础
│ (必须在机器人完全停止后下发)
│
⑥ 轴指令 0x00100001 / 0x00100002 持续下发 X/Y/Yaw(建议 ≥10Hz)
│ (无响应帧,靠状态上报确认)
│
⑦ 结束:轴指令全 0 → 趴下 MotionParam=4,或直接断开
```
**导航(半自主)路线**与上面的差异:
```
③ Mode=1 导航模式
⑤ GaitParam=0x3002(平地导航)/ 0x3003(楼梯导航,须在进楼梯前切换)
⑥ 改用速度指令 0x00100001 / 0x00110002(单位 m/s、rad/s,仅导航模式生效,建议 10Hz)
```
**关键约束**
- 心跳必须持续发:连续 **2 秒** 未收到就关闭主动上报,需重新建立连接。
- 轴指令/速度指令要求 **2 秒内来自同一客户端**,否则返回 `0xE006`。
- 步态切换必须在机器人**完全停止**后下发,否则指令被缓存、停机后才执行。
- 站立后:走 **ASDU** 会自动进 RL 控制,可直接发速度/步态;走 **ROS2 话题**不会自动进,需要再补一条 RL 控制指令。
- 轴指令和速度指令**不返回响应帧**,只能靠状态上报判断是否生效。
---
## 3. 控制类指令详解
### 3.1 心跳指令
| Type | Command | 说明 |
|---|---|---|
| `0x00100064` | `0x00000005` | 心跳,Items 为空 |
```json
{"PatrolDevice":{"Type":1048676,"Command":5,"Time":"2026-01-01 00:00:00","Items":{}}}
```
- 建议 ≥ 1Hz。
- 机器人会向**持续发送心跳的 IP 和端口**主动上报实时状态与异常状态。
- 超时 2 秒关闭主动上报,超时后需要重新建立链接。
### 3.2 使用模式切换
| Type | Command | 说明 |
|---|---|---|
| `0x00100002` | `0x00500002` | 切换机器人使用模式 |
| 参数 | 类型 | 取值 |
|---|---|---|
| `Mode` | int | `0` 常规模式 / `1` 导航模式 / `2` 辅助模式 |
```json
{"PatrolDevice":{"Type":1048578,"Command":5242882,"Time":"2026-01-01 00:00:00","Items":{"Mode":0}}}
```
- 常规模式下支持轴指令;导航模式下支持导航任务。
- 切换结果需通过状态上报 `ControlUsageMode` 确认。
### 3.3 运动状态转换
| Type | Command | 说明 |
|---|---|---|
| `0x00100001` | `0x00200002` | 转换机器人运动状态 |
| 参数 | 类型 | 取值 |
|---|---|---|
| `MotionParam` | int | `-2` 软急停 / `0` 默认(初始、趴卧)/ `1` 站立 / `2` 关节阻尼 / `4` 趴下 / `5` 标零 / `17` RL 控制 / `0x1001` 阻尼趴下 |
```json
{"PatrolDevice":{"Type":1048577,"Command":2097154,"Time":"2026-01-01 00:00:00","Items":{"MotionParam":1}}}
```
> 「空闲」与「软急停」只能查询,**不能通过该指令下发转换**。
> 用途:`MotionParam=1` 站立、`4` 趴下是最常用的两条;`2` 关节阻尼可作为软急停后的安全态。
### 3.4 运动步态切换
| Type | Command | 说明 |
|---|---|---|
| `0x00100001` | `0x00300002` | 切换运动步态 |
| 参数 | 类型 | 取值 |
|---|---|---|
| `GaitParam` | int | `0x1001` 基础(常规)/ `0x1003` 楼梯(常规)/ `0x3002` 平地(导航)/ `0x3003` 楼梯(导航) |
```json
{"PatrolDevice":{"Type":1048577,"Command":3145730,"Time":"2026-01-01 00:00:00","Items":{"GaitParam":4097}}}
```
- 步态切换会自动切到对应运动模式,运动模式切换也会自动切到默认步态。
- 二次开发做自主导航时**必须**用导航专用步态:平地 `0x3002`,楼梯 `0x3003`,且楼梯步态要在**进楼梯前**切换,严禁进楼梯后再切。
- 必须停机后下发。
### 3.5 运动控制(轴指令)
| Type | Command | 说明 |
|---|---|---|
| `0x00100001` | `0x00100002` | 六自由度速度比例控制,**无响应帧** |
```json
{"PatrolDevice":{"Type":1048577,"Command":1048578,"Time":"2026-01-01 00:00:00","Items":{"X":0.0,"Y":0.0,"Z":0.0,"Roll":0.0,"Pitch":0.0,"Yaw":0.0}}}
```
| 参数 | 含义 | 单位 | 范围 |
|---|---|---|---|
| `X` | 前后速度 | %(相对最大速度比例) | [-1, 1] |
| `Y` | 左右速度 | % | [-1, 1] |
| `Z` | 高度方向速度 | % | [-1, 1] |
| `Roll` | 翻滚角速度 | % | [-1, 1] |
| `Pitch` | 俯仰角速度 | % | [-1, 1] |
| `Yaw` | 偏航角速度 | % | [-1, 1] |
- 仅常规模式和辅助模式可用(导航模式请用速度指令)。
- **基础步态只响应 X、Y、Yaw**;Z、Roll、Pitch、Yaw 的额外自由度仅在特殊步态下有效。
- 正方向按右手坐标系;X 正为前进、Y 正为左移、Yaw 正为逆时针。
- 需要连续下发维持运动;停止时发全 0。
### 3.6 运动控制(速度指令)
| Type | Command | 说明 |
|---|---|---|
| `0x00100001` | `0x00110002` | 真实物理量速度控制,**无响应帧** |
```json
{"PatrolDevice":{"Type":1048577,"Command":1114114,"Time":"2026-01-01 00:00:00","Items":{"X":0.0,"Y":0.0,"Z":0.0,"Roll":0.0,"Pitch":0.0,"Yaw":0.0}}}
```
| 参数 | 含义 | 单位 | 范围 |
|---|---|---|---|
| `X` | 前后速度 | m/s | ±1.67 |
| `Y` | 左右速度 | m/s | ±0.4 |
| `Z` | 高度方向速度 | m/s | — |
| `Roll` | 翻滚角速度 | rad/s | — |
| `Pitch` | 俯仰角速度 | rad/s | — |
| `Yaw` | 偏航角速度 | rad/s | ±1.0 |
- 字段与轴指令完全相同,差别是**不做速度换算、原样下发执行端**,单位为物理量。
- **仅导航模式生效**,建议 10Hz 下发。
### 3.7 自定义灯语
| Type | Command | 说明 |
|---|---|---|
| `0x00100005` | `0x00200002` | 分别设置头灯、尾灯的灯光模式/颜色/占空比 |
| 参数 | 类型 | 取值 |
|---|---|---|
| `CustomMode` | bool | `true` 开启自定义 / `false` 关闭 |
| `Led[0]` | object | 头灯 |
| `Led[1]` | object | 尾灯 |
| `Led[].Type` | int | `0` 常亮 / `1` 填充式流水 / `2` 移动式流水 / `3` 渐变式流水 / `4` 呼吸 / `5` 单色闪烁 |
| `Led[].Color` | array\<int\> | `0` 黑 / `1` 白 / `2` 绿 / `3` 蓝 |
| `Led[].Cycle` | int | 占空比,单位 s;仅呼吸、闪烁模式使用 |
```json
{"PatrolDevice":{"Type":1048581,"Command":2097154,"Time":"2026-01-01 00:00:00","Items":{"CustomMode":true,"Led":[{"Type":0,"Color":[1],"Cycle":1},{"Type":0,"Color":[2],"Cycle":1}]}}}
```
> 官方原文的 JSON 示例缺逗号、`"Led"` 后直接跟数组,不是合法 JSON;上面是修正后的可直接发送版本。
### 3.8 自主充电
| Type | Command | 说明 |
|---|---|---|
| `0x00100004` | `0x00100002` | 控制自主充电 |
| 参数 | 类型 | 取值 |
|---|---|---|
| `Charge` | int | `0` 结束充电 / `1` 开始充电 / `2` 清除充电状态、恢复空闲 |
```json
{"PatrolDevice":{"Type":1048580,"Command":1048578,"Time":"2026-01-01 00:00:00","Items":{"Charge":1}}}
```
> `Charge=2` 会强制切断充电电流、状态归零并清除相关错误记录,**有风险,慎用**。
### 3.9 运动 SDK 模式
| Type | Command | 说明 |
|---|---|---|
| `0x00100005` | `0x00300002` | 进入/退出 SDK 运动控制模式 |
| 参数 | 类型 | 取值 |
|---|---|---|
| `SDKEnable` | bool | `true` 开启 / `false` 关闭 |
| `Frequency` | int | 关节话题数据发布频率,[1, 200] 且能整除 1000 |
```json
{"PatrolDevice":{"Type":1048581,"Command":3145730,"Time":"2026-01-01 00:00:00.000","Items":{"SDKEnable":true,"Frequency":100}}}
```
### 3.10 休眠模式设置
| Type | Command | 说明 |
|---|---|---|
| `0x00100002` | `0x00400002` | 设置休眠/自动休眠 |
| 参数 | 类型 | 取值 |
|---|---|---|
| `Sleep` | bool | `true` 进入休眠 / `false` 唤醒 |
| `Auto` | bool | 到时间是否自动休眠 |
| `Time` | uint(min) | 心跳断联后等待多久休眠 |
```json
{"PatrolDevice":{"Type":1048578,"Command":4194306,"Time":"2026-01-01 00:00:00.000","Items":{"Sleep":false,"Auto":true,"Time":5}}}
```
---
## 4. 遥控必须回读的状态
心跳建立后机器人主动上报。上报地址 = 最近发心跳的 IP:端口。
| 上报 | Type | Command | 频率 | 遥控关注点 |
|---|---|---|---|---|
| 基础状态 | `0x00100064` | `0x00f00000` | 2Hz | `MotionState`、`Gait`、`ControlUsageMode`、`HES`、`Charge` |
| 运控状态 | `0x00100001` | `0x00f00000` | 10Hz | 实际速度、姿态、关节角,用于闭环 |
| 设备状态 | `0x00100002` | `0x00f00000` | 2Hz | 电池、电机温度、外设使能 |
| 异常状态 | `0x0010007f` | `0x00f00000` | 2Hz | 故障列表,状态变化时额外上报 |
| 导航状态 | `0x00100003` | `0x00f00000` | 1Hz | 导航任务状态、定位状态 |
### 4.1 基础状态 `BasicStatus`
| 字段 | 含义 | 取值 |
|---|---|---|
| `MotionState` | 运动状态 | `-2` 软急停 / `0` 默认 / `1` 站立 / `2` 关节阻尼 / `4` 趴下 / `5` 标零 / `17` RL 控制 / `0x1001` 阻尼趴下 |
| `Gait` | 步态 | `0x1001` 基础 / `0x1002` 高台 / `0x3002` 平地(导航)/ `0x3003` 楼梯(导航) |
| `HES` | 硬急停 | `0` 未触发 / `1` 已触发 |
| `ControlUsageMode` | 使用模式 | `0` 常规 / `1` 导航 / `2` 辅助 |
| `PowerManagement` | 电源模式 | `0` 常规 / `1` 单电池 |
| `Sleep` | 休眠 | `0` 非休眠 / `1` 休眠 / `2` 进入休眠中 |
| `Model` | 型号 | 如 `CD1` |
| `Version` | 版本 | `STD` 标准 / `PRO` 增强 |
| `Charge` | 充电状态 | `0x0000` 空闲 / `0x0001` 前往电桩 / `0x0002` 充电中 / `0x0003` 退出电桩 / `0x0004` 异常 / `0x0005` 在桩未充电 / `0x0006` 线充 |
> 注意:步态上报里有 `0x1002` 高台,但《运动步态切换》指令表没列出 `0x1002`;下发时以指令表为准。
### 4.2 运控状态 `MotionStatus`(10Hz,闭环用)
| 字段 | 含义 | 单位 |
|---|---|---|
| `LinearX` / `LinearY` | 自身坐标系 x/y 线速度 | m/s |
| `AngularZ` | yaw 角速度 | rad/s |
| `Roll` / `Pitch` / `Yaw` | 姿态角 | rad |
| `OmegaX/Y/Z` | IMU 三轴角速度 | rad/s |
| `AccX/Y/Z` | 三轴加速度 | m/s² |
| `Height` | 机身高度 | m |
| `Payload` | 负载 | kg |
| `RemainMile` | 预测剩余里程 | km |
| `Gait` / `MotionState` | 当前步态 / 运动状态 | — |
| `MotorStatus.Joint` | 16 关节角度数组 | rad |
`MotorStatus.Joint` 顺序(16 个):LeftFrontHipX、LeftFrontHipY、LeftFrontKnee、LeftFrontWheel、RightFrontHipX、RightFrontHipY、RightFrontKnee、RightFrontWheel、LeftBackHipX、LeftBackHipY、LeftBackKnee、LeftBackWheel、RightBackHipX、RightBackHipY、RightBackKnee、RightBackWheel。
### 4.3 异常状态 `ErrorList`
每组含 `Code`、`Name`、`Severities`(`3` WARN / `4` ERROR / `5` FATAL)、`Resources`、`Source`、`Timestamp`、`Type`(`1` 开始/等级变化,`2` 消除)。遥控常见故障码:
| 码 | 含义 | 码 | 含义 |
|---|---|---|---|
| `0x8001` | 电机温度过高 | `0x8101` | 电池电量低 |
| `0x8002` | 驱动器温度过高 | `0x8108` | 电池温度过高 |
| `0x8014` | 关节位置超限 | `0x810A` | 放电电流过大 |
| `0x8016` | 关节数据无效 | `0x8201` | IMU 发布超时/无更新 |
| `0x8006` | 驱动器通信超时 | `0x8204` | IMU 数据无效 |
| `0x8301` | CPU 温度过高 | `0x8304` | 可用内存过低 |
---
## 5. 通用返回与错误码
除轴指令、速度指令外,所有控制指令都会返回同 Type/Command 的通用状态响应:
```json
{"PatrolDevice":{"Type":1048676,"Command":5,"Time":"2026-10-29 19:50:18.403","Items":{"ErrorCode":0,"ErrorMessage":"Success"}}}
```
| ErrorCode | ErrorMessage | 含义 |
|---|---|---|
| `0x0000` | Success | 成功 |
| `0xE001` | Unsupported data format | 数据格式不支持(非 XML/JSON) |
| `0xE002` | Data parsing failed | 解析失败(缺 PatrolDevice/Items/Type/Command) |
| `0xE003` | Unsupported protocol type | 找不到 Type+Command 对应协议 |
| `0xE004` | Missing necessary fields | 缺少必要字段 |
| `0xE005` | Mismatched data type | 字段类型不匹配 |
| `0xE006` | Mismatched request client | 请求客户端不匹配(如 2 秒内轴指令来自不同客户端) |
| `0xE007` | No operation permission | 无操作权限 |
| `0xE008` | Not allowed operation | 不允许的操作 |
| `0xE009` | Operation failure | 操作失败 |
| `0xE00A` | Unsupported function | 不支持的功能(如标准版不支持导航) |
| `0xE00B` | Internal error | 内部错误 |
> 实测返回里 `Items` 可能只带 `ErrorCode` 而不带 `ErrorMessage`,解析时两者都要兼容。
---
## 6. 注意事项汇总
1. **心跳是前提**:≥1Hz,2 秒断流即关闭上报并需重连。
2. **mTLS 是强制的**:无客户端证书连不上;明文 TCP 直接被拒。
3. **JSON 数值必须写整数**,不能用文档示例里的 `0x0010 0064` 带空格写法。
4. **轴指令/速度指令没有响应帧**,必须靠状态上报验证;且 2 秒内必须同一客户端。
5. **轴指令 vs 速度指令**:轴指令 = [-1,1] 比例、常规/辅助模式;速度指令 = 物理量、仅导航模式。
6. **步态切换要停机**,否则被缓存延后执行。
7. **楼梯步态先进后退**:必须在进入楼梯前切到 `0x3003`,严禁楼梯上切换。
8. **基础步态只有 X/Y/Yaw 有效**,Z/Roll/Pitch 需要特殊步态。
9. **站立后模式差异**:ASDU 站立自动进 RL 控制;ROS2 话题站立需手动补 RL 控制。
10. **`Charge=2` 有风险**:强制断电、状态归零、清错误记录。
11. **导航功能仅 PRO 版**,标准版会返回 `0xE00A`。
12. 报文 ID 请求/响应同值,便于把异步上报与请求响应区分开。
---
## 7. 配合 `send_adsu.py` 使用
`send_adsu.py` 已实现帧封装 + mTLS 连接,`--type/--command` 接受任意进制写法,`Items` 用 JSON 传入。
```bash
# 心跳
python3 send_adsu.py --type 0x00100064 --command 0x00000005 --items-json '{}'
# 常规模式
python3 send_adsu.py --type 0x00100002 --command 0x00500002 --items-json '{"Mode":0}'
# 站立
python3 send_adsu.py --type 0x00100001 --command 0x00200002 --items-json '{"MotionParam":1}'
# 基础步态
python3 send_adsu.py --type 0x00100001 --command 0x00300002 --items-json '{"GaitParam":4097}'
# 前进(轴指令,无响应,需脚本循环维持)
python3 send_adsu.py --type 0x00100001 --command 0x00100002 \
--items-json '{"X":0.3,"Y":0.0,"Z":0.0,"Roll":0.0,"Pitch":0.0,"Yaw":0.0}'
# 导航模式下的速度指令(m/s、rad/s)
python3 send_adsu.py --type 0x00100001 --command 0x00110002 \
--items-json '{"X":0.5,"Y":0.0,"Z":0.0,"Roll":0.0,"Pitch":0.0,"Yaw":0.0}'
# 趴下
python3 send_adsu.py --type 0x00100001 --command 0x00200002 --items-json '{"MotionParam":4}'
```
脚本当前默认**不校验证书链**(根 CA 缺 keyUsage 扩展,Python 3.13+ 严格校验会拒绝);已用 `openssl s_client` 单独确认服务端证书可信。需要严格校验时加 `--verify`,但部分 Python 版本会失败。
> 做遥控闭环时不要每条指令新建连接:轴指令要求 2 秒内同一客户端,应在同一个 TLS 会话里循环发送,并同时读状态上报。