feat(safety): 错误位上报 1002/3;IMU 断流判定;移除硬件假 IMU

- MotionState 增加 error_code,新增 invicta_msgs/error_code.h 共用位定义;SafeController 写入、remote 展开成协议 ErrorList(component 暂为 0)
- InvictaInterface 增加 IMU 断流/恢复判定(imu_timeout_s / imu_recover_s),covariance=-1 视为不可用;RL 丢失 IMU 走正常趴下
- invicta_hardware 不再发布硬编码单位四元数 IMU(含错误的 covariance[0]=0)
- 更新 README 与 docs/TODO.md:记录已完成项、反馈帧 CANID 1~4 已确认、[joint_limits] 重复维护入待办
This commit is contained in:
2026-10-23 23:32:57 +08:00
parent 944774eab3
commit 7ba2bba450
21 changed files with 303 additions and 126 deletions
+32 -39
View File
@@ -1,15 +1,16 @@
# TODO
本文件记录**当前会话中确认但本期不做**的事项。按优先级分组,每项尽量写清「为什么」和「验收方式」,
避免下次重新推导。已完成的部分见各包 README。
避免下次重新推导。已完成的部分见各包 README 与文末清单。
## P0:安全链路(真机跑 RL 前必须处理)
- [ ] **异常数据接入 deploy 的保护链路**
硬件已在 `/joint_diagnostics` 提供温度/状态字/电压(10 Hz),但 deploy 侧仍是常量:
`robot_interface.h` 的 `GetMotorTemperture()` 返回 30 ℃、`GetDriverStatusWord()` 返回全 1,
所以 `SafeController` 的过温检查和驱动报错检查恒不触发。
做法:`InvictaInterface` 订阅 `/joint_diagnostics` 并覆盖这两个虚函数。
硬件已在 `/joint_diagnostics` 提供温度/状态字/电压(10 Hz),但 deploy 侧仍未接:
`InvictaInterface` 没有订阅该话题,`GetMotorTemperture()` 走基类常量 30 ℃;
`GetDriverStatusWord()` 虽已重写为返回成员 `driver_status_`,但该成员只被初始化成全 1、无人更新,
所以 `SafeController` 的过温检查与驱动报错检查依然恒不触发。
做法:`InvictaInterface` 订阅 `/joint_diagnostics`,按关节名填 `driver_status_`、覆盖 `GetMotorTemperture()`。
注意:协议 Byte9 的 bit0 是「使能中」,而 `IsDriverStatusNormal()` 判断 `status_word != 1` 报错,
失能但健康的电机回 `0x00` 会误判;建议在接口层归一化成「无故障位就填 1」。
验收:人为把某个关节温度改成 101 ℃,deploy 进入 `safe_control_mode=2` 并切 `kJointDamping`。
@@ -18,32 +19,13 @@
诊断话题 10 Hz,`SafeController` 读缓存值。若 hardware 进程死掉,温度会停在最后一帧的「正常」。
需要在接口层记录诊断时间戳,超时(如 1 s)按异常处理。
验收:杀掉 hardware 节点后 deploy 进入安全状态。
- [ ] **异常上报 1002/3 的数据源**
remote 的 `ErrorDoc` 恒为空列表,因为 `MotionState.msg` 没有错误字段。
建议给 `MotionState` 加 `uint32 error_code` + `uint16 error_component`(位域与
`SafeController::robot_error_state_` 对齐,component 用 M20 槽号),remote 再映射成
`0x8001/0x8002/0x8020/0x8012` 等协议错误码。
验收:模拟驱动器故障,App 收到非空 `ErrorList` 且 component 指向正确关节。
- [ ] **IMU 数据源缺失**
`/imu/data` 目前发的是单位四元数、`orientation_covariance[0] = 0`(声称有效),
但协议里没有任何 IMU 字段,硬件桥也没有读 IMU。
按本项目自己的约定,拿不到姿态时应把 `orientation_covariance[0]` 置 `-1`;
改之前要确认 deploy 的 `[rl].require_imu` 会不会因此拒绝进 RL。
(对照:IMU 的同类问题已解,走 `IsImuHealthy()` 的断流判定——本条可照抄那套「时间戳 + 超时 + 恢复去抖」。)
## P1:标定与配置一致性(上真机前)
- [x] **`dir`/`offset` 的施加方已定:hardware**(2026-10-22)
raw↔model 的换算统一由 `invicta_hardware` 独家承担(`[[legs]].swing/wheel` 的 `dir`/`offset_deg`),
话题上收发都是模型帧;deploy 的 `InvictaInterface` 退化为纯透传,`invicta_deploy.toml` 不再有 `dir`/`offset_deg`。
这样 `[joint_limits]`(模型帧)与被限幅的指令同帧,`/joint_states` 也是模型角。
遗留:`[joint_limits]` 仍在两份 TOML 里重复维护(deploy 用一份做状态机自检、hardware 用一份做下发夹紧),
后续可考虑合并或生成。
- [ ] **反馈帧仲裁 ID 需真机确认**
`ReadHardware` 按「反馈使用关节自身 CANID」实现(手册只写了从站 CANID 1~6)。
上电后 `candump can0` 核对;若不是 1~4,需要改匹配逻辑。
- [ ] **`[joint_limits]` 在两份 TOML 里重复维护**
`invicta_deploy.toml` 用一份做状态机自检,`invicta_hardware.toml` 用一份做下发夹紧。
两份手改容易漂移,且改限位时要同步改两处。可考虑合并成单一来源(hardware 读 deploy 的,或从一处生成另一处)。
- [ ] **`lie_height = 0` 与位置限位矛盾**
`invicta_deploy.toml` 里 `lie_height = 0.00` 反解 `θ = acos(0) = 90° = 1.571 rad`,
@@ -58,7 +40,8 @@
如果当初是估的,建议按实测重新定。
- [ ] **位置指令越界目前静默夹紧**
力矩和 kp/kd 越界会告警,位置不会。策略动作顶到限位时可能值得提示一次(注意别刷屏)。
`invicta_hardware_node.cpp` 的 `PackRunBlocks` 里力矩和 kp/kd 越界会告警,位置不会。
策略动作顶到限位时可能值得提示一次(`ROS_WARN_THROTTLE`,注意别刷屏)。
## P2:协议状态上报的剩余占位
@@ -67,6 +50,7 @@
- [ ] `Charge / HES / Sleep`:没有充电状态、硬急停信号与休眠管理。
- [ ] `HipY` 的正负号未与真实 App/M20 对照:如果 App 只是显示数值没问题,若渲染姿态需要核对。
- [ ] 未实现指令:照明灯、休眠设置/查询、自主充电、速度指令(导航模式)、导航任务(Pro)。
- [ ] 错误上报的 `component` 恒为 0(见文末遗留):要定位到具体关节,需在 deploy 侧把错误位与关节号关联。
## P3:工程与清理
@@ -74,16 +58,25 @@
deploy 已改为「等订阅者再发」且 hardware 在 `kMotorControl` 未使能时会补发使能,功能上已安全;
但 hardware 循环线程处理 `kErrorReset` 的使能连发会阻塞约 80 ms,期间可能观察不到 `kEnable`。
若要严格逐帧落地,可把连发改成分段非阻塞发送。
- [ ] `[motor.gains].bench_*` 已被指令里的 kp/kd 取代,确认是否删除。
- [ ] 构建镜像 `localhost/custom-ros-noetic` 缺 toml++,目前靠挂载外部安装树 + `CMAKE_PREFIX_PATH`。
可考虑在镜像里装 toml++,或在仓库内 vendored。
- [ ] `[motor.gains].bench_*` 已被指令里的 kp/kd 取代(现仅被加载、无消费方),确认是否删除。
- [ ] 真机联调清单:`ip link set canX ... fd on` 权限、`roslaunch` 启动顺序、双路差分驱动方向。
## 已完成(本会话)
## 已完成
- [x] SocketCAN FD 收发 + 智身模组协议编解码(使能/失能/运行、12 字节反馈解码)
- [x] 温度读回 → `/joint_diagnostics`(默认 10 Hz,可配置)→ remote 填 1002/5
- [x] 1002/4 的 8 → 16 槽映射(swing→HipY、wheel→Wheel,HipX/Knee 占位 0)
- [x] 力矩/速度/位置限幅,kp/kd 越界夹紧并告警;限幅与归一化边界分离 + 加载期校验
- [x] 指令看门狗(100 ms 无指令转阻尼)
- [x] 容器内构建 + 冒烟测试;修复启动时序丢失与订阅回调阻塞
- [x] **SocketCAN FD 收发 + 智身模组协议编解码**(使能/失能/运行、12 字节反馈解码)
- [x] **温度读回 → `/joint_diagnostics`**(默认 10 Hz,可配置)→ remote 填 1002/5
- [x] **1002/4 的 8 → 16 槽映射**(swing→HipY、wheel→Wheel,HipX/Knee 占位 0)
- [x] **力矩/速度/位置限幅,kp/kd 越界夹紧并告警**;限幅与归一化边界分离 + 加载期校验
- [x] **指令看门狗**(100 ms 无指令转阻尼)
- [x] **`dir`/`offset` 的施加方已定:hardware**(2026-10-22)
raw↔model 换算由 `invicta_hardware` 独家承担(`[[legs]].swing/wheel` 的 `dir`/`offset_deg`),话题上收发都是模型帧;
deploy 的 `InvictaInterface` 退化为纯透传,`invicta_deploy.toml` 不再有 `dir`/`offset_deg`。
- [x] **反馈帧仲裁 ID 已确认为 1~4**(2026-10-23):`ReadHardware` 按关节自身 CANID 匹配,与实测一致,无需改动。
- [x] **toml++ 改为 vendored**(`third_party/tomlplusplus`),不再依赖镜像或 `CMAKE_PREFIX_PATH`。
- [x] **容器内构建 + 冒烟测试**(`scripts/smoke_test.sh`);修复启动时序丢失与订阅回调阻塞。
- [x] **异常上报 1002/3 的数据源**:`MotionState.error_code` + 共用位定义 `invicta_msgs/error_code.h`;
`SafeController` 每周期写 `UserCommand::robot_error_code`,remote 经 `RobotErrorList()` 展开成协议 `ErrorList`。
遗留:`component` 恒填 0(未按关节定位),已单列在 P2。
- [x] **IMU 数据源缺失**:`invicta_hardware` 不再发假 IMU(删掉单位四元数 + `covariance[0]=0`);
deploy 把 `orientation_covariance[0]==-1` 视为不可用,并做断流/恢复判定(`imu_timeout_s`/`imu_recover_s`),
RL 丢失 IMU 走「正常趴下」而非就地垮下。实际数据源 `odin_imu_bridge` 不在本仓库,契约见 `invicta_deploy/README.md`。