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

9.4 KiB
Raw Blame History

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 的 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。

频率

环节 频率 出处
控制周期 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)

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,无需联网。

运行

# 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。