Files
huoyi_deploy/README.md
T
caowangrenbo cf70b1a1d7 Initial commit: extract M20 sim2real into ROS1 invicta project
Source: DeepRoboticsLab/sdk_deploy (BSD-3-Clause), M20 sim2real part only.

Packages:
- invicta_msgs: JointCommandArray, MotionCommand, MotionState; standard messages
  (sensor_msgs/JointState, sensor_msgs/Imu) elsewhere; shared motion value defs.
- invicta_deploy: state machine (idle/standup/rl/joint-damping/lie-down), ONNX
  policy with per-gait models, auto RL entry after standup with readiness checks,
  safety loop, gait->model table; no protocol/network code.
- invicta_remote: vendor basic_server protocol (UDP + JSON, proto.md V1.2.0),
  header/MsgId, 0xE0xx error codes, motion/usage-mode/gait translation, telemetry
  1002/3,4,5,6 (2/10/2/2 Hz); publishes /motion_command, subscribes /motion_state.
- invicta_hardware: joint/IMU bridge skeleton (SocketCAN I/O is TODO), publishes
  sensor_msgs/JointState + sensor_msgs/Imu, consumes /joint_commands.

Verified on ROS Noetic (Ubuntu 20.04) in docker: clean catkin_make, 200 Hz joint
state / 50 Hz RL commands / 20 Hz state report, protocol conformance (MotionParam,
gait preconditions, error codes), telemetry push, and end-to-end switching of two
policy models with a real remote controller.
2026-10-21 00:46:29 +08:00

159 lines
9.4 KiB
Markdown
Raw 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.
# invicta(火蚁)RL sim2real 工程
从 DeepRobotics `sdk_deploy` 的 **M20 sim2real 部分**剥离出来的 ROS1(Noetic)工程,并按照厂家完整协议 `proto.md` 把**遥控/设备协议**与**RL 控制**拆成两个节点。
- 上游参考代码:`sdk_deploy/src/M20_sdk_deploy`(ROS2 / ament / drdds)
- 本工程:ROS1 / catkin / Noetic / Ubuntu 20.04
- 协议依据:`proto.md`(山猫 M20 basic_server V1.2.0,UDP + JSON)
> 代码源自 [DeepRoboticsLab/sdk_deploy](https://github.com/DeepRoboticsLab/sdk_deploy) 的 M20 sim2real 部分,遵循 BSD-3-Clause(见 `LICENSE`);已按 ROS1、厂家完整协议与本机器人形态重构。
## 目录与职责
```
invicta_ws/src/
├── invicta_msgs/ 消息与共享值域(只用 3 条自定义消息,其余复用 sensor_msgs)
├── invicta_remote/ 遥控/设备协议节点:UDP + JSON ↔ ROS,厂家错误码、状态上报(待实现)
├── invicta_deploy/ RL 控制节点:状态机 + ONNX 推理 + 安全检查(不碰网络协议)
└── invicta_hardware/ 关节/IMU 数据源 + 关节指令执行端(骨架,硬件 I/O 待实现)
```
这样拆的依据:协议面是平台级接口(控制 + 上报 + 充电/休眠/导航),会跟厂家版本演进;控制面要求 200 Hz 稳定运行、最怕重启。两者生命周期和变更节奏不同。
## 数据流
```
遥控 App ──UDP:30000 JSON──→ invicta_remote ──/motion_command──→ invicta_deploy
↑ │
└────────── /motion_state ───────────┘
│
invicta_hardware ──/joint_states (200 Hz)──→ invicta_deploy ──/joint_commands──→ invicta_hardware
──/imu/data (200 Hz)──────→ (200 Hz,RL 状态下 50 Hz)
```
## 话题与消息
| 话题 | 消息 | 频率 | 说明 |
|---|---|---|---|
| `/joint_states` | `sensor_msgs/JointState`(标准) | 200 Hz | 关节状态回馈 |
| `/imu/data` | `sensor_msgs/Imu`(标准) | 200 Hz | IMU 回报(四元数,弧度) |
| `/joint_commands` | `invicta_msgs/JointCommandArray`(自定义) | 200 Hz / RL 下 50 Hz | 关节指令(kp/kd/前馈/控制字) |
| `/motion_command` | `invicta_msgs/MotionCommand`(自定义) | 事件 + 轴指令帧 | 协议节点 → RL 节点 |
| `/motion_state` | `invicta_msgs/MotionState`(自定义) | 20 Hz | RL 节点 → 协议节点(真实状态) |
消息细节见 [`src/invicta_msgs/README.md`](src/invicta_msgs/README.md)。
## 频率
| 环节 | 频率 | 出处 |
|---|---|---|
| 控制周期 | 200 Hz(5 ms) | `control_period_ms: 5` |
| RL 策略 | 50 Hz(decimation 4) | `InvictaPolicyRunner::SetDecimation(4)`,`agent_timestep=0.02` |
| 关节状态 / IMU 上报 | 200 Hz | 与上游 M20 仿真发布一致;低于 100 Hz 明显退化,50 Hz 是功能下限 |
| 状态回报 `/motion_state` | 20 Hz | 协议里基础状态上报是 2 Hz,从这里取即可 |
| 协议轴指令 | 协议建议 ≥20 Hz | 实测老版 App 静杆时更低,动杆时待确认 |
上游厂商侧口径:Lite3 的数据面上限 200 Hz 且必须整除 1000(内部 1 kHz)。M20 实机的真实上报频率请在接上硬件后用 `rostopic hz` 实测。
## 关键约定:硬件约定 vs policy 约定
关节量有两套约定,**必须只施加一次**:
- 硬件约定:电机方向、编码器零点、单位(CAN 原始值)。
- policy 约定:与 `policy.onnx` 训练一致(rad / rad/s / Nm)。
默认由 `invicta_hardware` 承担换算,话题上流动的就是 policy 约定;因此 `invicta_deploy` 的 `calibration/joint_dir` 全 1、`joint_offset_deg` 全 0、`auto_calibrate=false`。若反过来让 RL 节点承担,则填 M20 的表并打开 `auto_calibrate`:
| | 值 |
|---|---|
| `joint_dir` | `[1,1,-1,1, 1,-1,1,-1, -1,1,-1,1, -1,-1,1,-1]` |
| `joint_offset_deg` | `[-25,-131,160,0, 25,-131,160,0, -25,131,-160,0, 25,131,-160,0]` |
两条 M20 遗留语义保持原样:轮关节(下标 `%4==3`)指令 `kp` 强制 0(`wheel_kp_zero`);关节顺序固定为 `fl/fr/hl/hr × hipx/hipy/knee/wheel`,与 `policy.onnx` 绑定。
## 步态 = RL 模型
厂家协议里的 `GaitParam` 不是简单参数,**每个步态对应一个 RL 模型**:
| GaitParam | 步态 | 当前 |
|---|---|---|
| `0x1001` 4097 | 基础(标准,平地) | ✅ 默认模型 |
| `0x3002` 12290 | 平地(敏捷) | ❌ 未提供模型,请求被拒绝(训练好后在 `gait/policy_map` 填路径) |
| `0x1003` 4099 | 楼梯(标准) | ❌ 同上 |
| `0x3003` 12291 | 楼梯(敏捷) | ❌ 同上 |
`invicta_deploy` 支持多模型:启动时把 `gait/policy_map` 里提供了路径的步态全部预加载,运行中按当前步态切换(切换时清空该模型的 `last_action`)。**只有真正加载成功的步态才接受切换**,`/motion_state.gait` 只报实际生效值,不会出现"显示切了但还在用旧模型"。
## 构建(Ubuntu 20.04 + ROS Noetic)
```bash
source /opt/ros/noetic/setup.bash
cd invicta_ws
catkin_make -j4 # 交叉编译:-DBUILD_PLATFORM=arm
source devel/setup.bash
```
依赖:`roscpp`、`roslib`、`sensor_msgs`、`std_msgs` + 自建 `invicta_msgs`。`eigen`、`onnxruntime 1.22`(x86/aarch64)、`policy.onnx` 已 vendor,无需联网。
## 运行
```bash
# 1) 硬件桥接(当前是骨架:发零位状态、不驱动电机)
roslaunch invicta_hardware invicta_hardware.launch
# 2) 协议节点(UDP 30000)
roslaunch invicta_remote invicta_remote.launch
# 3) RL 控制
roslaunch invicta_deploy invicta_deploy.launch
# 无硬件台架(状态是零位)时必须关掉自检,否则站立后不会自动进 RL:
# roslaunch invicta_deploy invicta_deploy.launch rl_require_pose:=false rl_require_imu:=false
# 无硬件自检(起 roscore + 三个节点,测话题/频率/协议/ONNX)
bash src/invicta_deploy/scripts/smoke_test.sh
# 无遥控器时模拟协议报文
python3 src/invicta_remote/scripts/protocol_udp_test.py --steer 0.3,0,0 --motion 1
```
遥控器(云卓 G20)在 App 里把目标地址设为本机 IP、端口 30000。老版 App 用 `2/22 MotionParam` 切状态,新版(≥ V1.5.11)用 `1101/12` 按键事件,两套都支持。
## 已验证到什么程度
在 `localhost/custom-ros-noetic:v1.0`(Ubuntu 20.04 + ROS Noetic)容器里实测:
| 项目 | 结果 |
|---|---|
| 四个包 catkin_make | 通过 |
| `ldd` 解析 `libonnxruntime.so.1` | 通过(RPATH 生效) |
| `/joint_states` | 200.001 Hz,关节名/顺序正确 |
| `/joint_commands` | 站立 200 Hz;RL 状态 50.001 Hz,策略输出正常(kp=80,kd=2/0.6) |
| `/motion_state` | 20.000 Hz,RL 时 `motion_state=17`、`gait=4097` |
| 协议:心跳 / 轴指令 / MotionParam | 回 0,状态机正确切换(空闲→站立→RL) |
| 状态上报 `1002/3,4,5,6` | 2 / 10 / 2 / 2 Hz;RL 时 `BasicStatus.MotionState=17`、切步态后 `Gait` 同步变化 |
| 协议:非 RL 下切步态 | 回 `0xE008 Not allowed operation`(57352) |
| 协议:楼梯步态 | 协议节点回 0(已接收),RL 节点拒绝并记录"模型尚未注册" |
| 协议:照明灯 / 导航模式 / 未知命令 | `0xE00A`(57354)/ `0xE00A` / `0xE003`(57347) |
| 手柄实测(真遥控器) | 心跳、摇杆、MotionParam、照明灯、使用模式报文均能收到并正确应答 |
**未验证**:真实电机与 IMU(`invicta_hardware` 的 `ReadHardware/WriteHardware` 还是 TODO)、实机上报频率、新机器人几何参数、状态上报(未实现)。
## 与上游 M20 的差异
保留:状态机五状态、ONNX 策略与关节顺序/动作尺度、安全检查、厂家协议的头/JSON 结构与心跳、`eigen`/`onnxruntime`/`policy.onnx`。
删除或改写:ROS2→ROS1;`drdds` 消息→标准消息 + 3 条自定义;电池订阅(上游死代码);键盘控制模式;`third_party/gamepad`(未被引用);Mujoco 仿真与 `M20_description`;Lite3 三个包;`backward.hpp` 死代码;M20 几何硬编码→ROS 参数;原厂控制字→平台无关控制字;**UDP/JSON 协议层从 RL 进程拆到 `invicta_remote`**。
## 已知缺口
1. **设备状态数据未接**(`1002/5` 的电池/温度/CPU/GPS):报文结构已按协议发出,但数值是占位 0,需要 `invicta_hardware` 提供(建议标准消息,如 `sensor_msgs/BatteryState`)。运控状态里的线速度/高度/剩余里程同理。
2. **遥控断连不停机**:协议层 3 s 判离线只登记/log;`UserCommand.time_stamp` 已写入但断连判断未接线,策略会沿用最后一帧指令。当前按"硬件急停兜底"处理。
3. **未实现功能**:照明灯、休眠、自主充电、导航模式/速度指令(均回 `0xE00A`)。
4. **关节温度与驱动器状态字缺失**:`sensor_msgs/JointState` 无对应字段,是相对 M20 少的保护项(详见 `invicta_msgs/README.md`)。
5. **中文日志**:`ROS_*` 文案在非 UTF-8 locale 下显示为 `?`(容器内实测),机器人上用 UTF-8 终端则正常。
6. **`charge/hes/sleep` 恒为 0**:需要硬件侧提供充电状态、硬急停信号与休眠管理。
## 版本管理
`third_party/eigen`(22 MB)与 `third_party/onnxruntime`(113 MB)是 vendor 的二进制/头文件,便于离线构建;嫌大可改为系统 Eigen + 单独分发 ORT。