Files
huoyi_deploy/README.md
caowangrenbo 46bbb06978 feat(hardware): 标定归硬件;按 name 选关节 + kSetZero;toml++ 改 vendored
标定边界上移到 invicta_hardware:
- raw↔model 的 dir/offset 由 hardware 独家施加(话题收发都是模型帧),
  deploy 的 InvictaInterface 退化为透传,invicta_deploy.toml 不再有 dir/offset_deg。
  这样 [joint_limits](模型帧)与被限幅的指令同帧,/joint_states 也是模型角。

关节选择(部分接入 / 单腿调试):
- JointCommandArray 的 name 即本次要控制的关节子集(空 = 全部已配置关节)。
- 未选中的关节写「零力矩自由」块,而不是留全 0——按协议 (0,0,0,0,0) 会解码成
  P_MIN/V_MIN/T_MIN,其中 tau=T_MIN(摆臂 -100 N·m)是危险的前馈力矩。
- 新增 control_word kSetZero(6) -> 0xEF,只作用于选中的摆臂(轮毂不支持,跳过)。

调试脚本 invicta_hardware/scripts/hw_debug_test.py:
- 只起 hardware 节点调试:monitor/damping/angle/wheel/offset/zero 六种模式 + --joints。
- 限幅参数硬编码;发指令时默认回显模型角。

构建:toml++ 改为 vendored single-header(三个包各 third_party/tomlplusplus/toml++/toml.hpp),
   去掉 find_package(tomlplusplus) 与 -DTOML_HEADER_ONLY(单头自带 header-only)。

已在容器(localhost/custom-ros-noetic)中全新 catkin_make + smoke_test 验证通过。
2026-10-23 13:44:27 +08:00

201 lines
13 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.
# 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)
- 关节模组 CANFD 协议:[`docs/zhishen_joint_module_canfd.md`](docs/zhishen_joint_module_canfd.md)(智身模组手册 V1.2);文档索引见 [`docs/README.md`](docs/README.md)
> 代码源自 [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,厂家错误码、状态上报
│ config/invicta_remote.toml 节点参数
├── invicta_deploy/ RL 控制节点:状态机 + ONNX 推理 + 安全检查(不碰网络协议)
│ config/invicta_deploy.toml 节点参数(含机器人模型/策略)
└── invicta_hardware/ 关节/IMU 数据源 + 关节指令执行端(骨架,硬件 I/O 待实现)
config/invicta_hardware.toml 节点参数(含电机/CAN)
```
这样拆的依据:协议面是平台级接口(控制 + 上报 + 充电/休眠/导航),会跟厂家版本演进;控制面要求 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` 实测。
## 配置:每个节点一份自己的 TOML
原来的 `config/*.yaml`(ROS 参数表)换成 TOML,**粒度不变:每个节点一份,节点自己决定读什么**。
没有共享配置包,节点之间不通过配置文件耦合:
| 文件 | 归属 | 内容 |
|---|---|---|
| `invicta_deploy/config/invicta_deploy.toml` | RL 节点 | 关节表与标定、腿的运动学(IK)、时序与各状态增益、站立后自动进 RL、策略与步态、本节点话题/队列/控制周期 |
| `invicta_remote/config/invicta_remote.toml` | 协议节点 | UDP 端口、心跳超时、未实现项语义、本节点话题、四路状态上报频率 |
| `invicta_hardware/config/invicta_hardware.toml` | 硬件节点 | 关节表与硬件标定、每条腿挂的总线/CANID、**电机量程与 CAN 参数**、本节点话题与发布频率 |
launch 只传一个路径:`<param name="config_file" value="$(find <本包>)/config/<本包>.toml"/>`;
不传则默认取本包 `config/` 下的同名文件。TOML 解析用 toml++ 的 **single-header**(`toml.hpp`,
vendor 在三个包各自的 `third_party/tomlplusplus/toml++/` 下)。单头版本本身就是 header-only,
实现直接编进节点:**不需要 `find_package`、不需要系统装库、也不需要任何宏**,目标机无运行时 `.so` 依赖。
改动节奏:**改 TOML 不用重编**;改各节点的配置头(`deploy_config.h` / `remote_config.h` / `hardware_config.h`)会自动触发该节点重编。
两个 TOML 结构坑(已在文件里注释):① 数组表之后的裸键会归到最后一个元素,所以 `[joint_limits]`/`[leg]` 必须放在 `legs` 段之前;② 整数键要加引号(如 `"4097"`),roslaunch 不接受整数键。
## 关键约定:硬件约定 vs 模型约定(2 自由度腿)
本构型每条腿只有**摆臂 + 轮**两个关节(4 腿 × 2 = 8 关节),模型约定为:
- 摆臂角 θ=0 时摆臂竖直向下,θ 增大表示摆臂向外倒开;髋轴到轮轴的高度 h = L·cos(θ)。
- 站立:h = `[leg].stand_height`(默认 0.48 m,L=0.50 m → ±16.3°)。
- 趴下:h = `[leg].lie_height`(默认 0.10 m → ±78.5°),**前腿向前倒、后腿向后倒**(由 `[[legs]].splay_sign` 决定,与 M20 相反)。
- 轮关节指令 `kp` 恒为 0(速度/力矩模式,`wheel_kp_zero`)。
硬件约定(电机方向、机械零点)由 **`invicta_hardware`** 独家承担,写在它自己的
`config/invicta_hardware.toml` 的 `[[legs]]` 里,逐关节给:
```toml
[[legs]]
name = "fl"
swing_joint = "fl_swing"
wheel_joint = "fl_wheel"
swing = { dir = 1.0, offset_deg = 0.0, bus = "can0", canid = 1 } # 硬件:电机正方向 / 机械零点
wheel = { dir = 1.0, offset_deg = 0.0, bus = "can0", canid = 3 }
```
指令换算 `q_motor = (q_model − offset)·dir`、反馈 `q_model = q_motor·dir + offset`(速度/力矩只乘 `dir`)
都在该节点一处完成;话题上收发的都是**模型帧**,`invicta_deploy` 的 `invicta_interface` 只做透传。
deploy 侧的 `[[legs]]` 只保留模型姿态约定 `splay_sign`。
第一次上机先按默认值(`dir` 见配置、`offset_deg=0`)跑,用调试脚本或 idle 打印的关节角与实测对照,
再填 `dir`/`offset_deg`;也可用调试脚本的置零(`--mode zero`)把当前位姿写为零位后置 `offset_deg=0`。
## 步态 = 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`、`toml++` 均已 vendor,无需联网、无需系统装库。
### toml++(已 vendor 进仓库,无需安装)
`toml.hpp`(single-header,上游 https://github.com/marzer/tomlplusplus,v3.4.0)放在三个包各自的
`third_party/tomlplusplus/toml++/toml.hpp`。**单头版本本身就是 header-only("drop it in, there is no
step two"),不需要任何宏**,实现直接编进节点:
- **不需要 `find_package(tomlplusplus)`、不需要 `apt install`、不需要 `-DTOML_HEADER_ONLY`**,`catkin_make` 开箱即用;
- 目标机不需要 `libtomlplusplus.so`;
- 升级:下载新的 `toml.hpp` 覆盖三个包里的副本(保持同版本)即可,见各 `third_party/tomlplusplus/README.md`。
(早先的「系统安装 / 离线 .deb / 源码装到 /usr/local」三种方式已不再需要。)
## 运行
```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)容器里实测(8 关节 + 每节点自己的 TOML):
| 项目 | 结果 |
|---|---|
| 四个包 catkin_make | 干净构建通过(`rc=0`) |
| 三份配置加载 | deploy:`dof=8 legs=4 L=0.500 stand=0.480 pre=0.200 lie=0.000`;remote:UDP 30000、上报 2/10/2/2 Hz;hardware:8 关节、`fl: swing can0#1 / wheel can0#3`、电机 P65MAXS/W190S、CAN 2 路 @1M/5M |
| IK 目标角 | 站立 ±16.3°、收腿 ±66.4°、**趴下 ±90.0°(摆臂水平)**,前腿 +、后腿 −(与 M20 相反)✓ |
| 趴下指令实测 | 订阅 `/joint_commands` 采样,四个摆臂峰值均为 **1.5708 rad = 90.0°**,轮关节 0 ✓ |
| `/joint_states` | 200.000 Hz,8 个关节名与配置一致 |
| `/motion_state` | 20.000 Hz |
| 协议 | 心跳回 0;`MotionParam=1` → idle→standup;`MotionParam=4` → standup→liedown |
| 站立后自动进 RL 自检 | 生效(骨架发零位 → `[RL] 暂不进入 RL:关节 fl_swing 未到站立位姿`,正确地拦住) |
**未验证**:真实电机与 IMU(`invicta_hardware` 的 `ReadHardware/WriteHardware` 仍是 TODO)、实机上报频率、**新机器人几何与 `dir`/`offset_deg` 的实测标定**(TOML 里的 0.50 m / 0.48 m 等是待实测替换的占位值)。
## 与上游 M20 的差异
保留:状态机五状态、ONNX 策略与关节顺序/动作尺度、安全检查、厂家协议的头/JSON 结构与心跳、`eigen`/`onnxruntime`/`policy.onnx`。
删除或改写:**16 关节三关节腿 → 8 关节(摆臂 + 轮)2 自由度腿,IK 换成单连杆反解、新增 splay_sign 倒向**;**死参数(机身长宽/髋偏置/轮半径/力矩限制/通用策略参数)全部删除**;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。