Files
acRealman_xr/docs/superpowers/specs/2026-07-27-rm75-placo-qp-ik-design.md
T

285 lines
13 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.
# RM75 Placo 单步 QP 逆解设计
日期:2026-07-27
状态:已批准,等待实施
## 1. 目标
`single_arm_velocity_teleop` 当前通过 `rm_movep_canfd` 调用睿尔曼控制器内部逆解的链路,替换为独立的 Placo QP 逆解:
1. 每个 90 Hz 控制周期读取最新实际关节角。
2. 将本周期工具 TCP 目标交给 Placo。
3. 每周期只调用一次 `solver.solve(True)`
4. 得到 7 个目标关节角后,通过 `rm_movej_canfd(..., follow=False)` 控制 RM75。
实现借鉴 XRoboToolkit 真机示例的控制理念,但不依赖或复制 XRoboToolkit 项目代码。首轮分别独立验证左臂和右臂 RM75;实现本身继续支持现有左臂、右臂和双臂启动方式。
## 2. 不在本次范围内
- 不采用上传示例中的 Pinocchio + OSQP 多轮迭代逆解实现。
- 不新增独立 ROS2 QP 求解节点。
- 不改变 XR 输入协议、控制器话题或左右臂节点名。
- 不关闭现有工作空间、圆柱、速度、超时或安全停止逻辑。
- 不由 Codex 连接或移动真实机械臂、夹爪。
- 不在本轮验证左右臂同时运行的双臂真机模式。
## 3. 选定方案
`xr_rm_teleop` 中新增轻量 `PlacoIkSolver`,由每个 `single_arm_velocity_teleop` 进程持有一个实例:
- 遥操作节点继续负责 XR 相对位姿、滤波、死区、工作空间和速度限制。
- `PlacoIkSolver` 负责 RM75 模型、工具 TCP/法兰变换以及单步 QP。
- `RealManAdapter` 负责唯一的厂商 SDK 连接、关节反馈缓存、关节目标下发、安全停止和工具控制。
- `MockRealManAdapter` 提供同一套关节反馈和关节目标接口,不导入厂商 SDK。
没有选择以下方案:
- 将 Placo 逻辑继续堆入已有的大型遥操作节点:改动集中,但职责更混乱且难以独立测试。
- 增加独立 QP ROS2 节点:隔离更强,但引入额外话题、时序和状态同步,对当前单机 90 Hz 控制没有必要。
## 4. 启动与连接生命周期
`launcher_ui.py` 不直接创建 Adapter。启动链路为:
```text
launcher_ui.py
-> arm_debug.launch.py (arm:=left/right/both)
-> 对应 single_arm_velocity_teleop 节点
-> 节点内部创建 PlacoIkSolver 和 Adapter
```
具体行为:
- `arm:=left use_mock:=false`:一个左臂节点、一个求解器、一个左臂 RealMan 连接。
- `arm:=right use_mock:=false`:一个右臂节点、一个求解器、一个右臂 RealMan 连接。
- `arm:=both use_mock:=false`:左右节点各自持有一个求解器,并各自连接对应 IP。
- `use_mock:=true`:创建 `MockRealManAdapter`,不加载厂商 SDK,不建立真机连接。
每个单臂节点只调用一次 `rm_create_robot_arm`。关节反馈、`rm_movej_canfd`、慢停止和工具控制复用同一个 SDK 句柄,不为反馈建立第二条连接,也不让一条连接控制两台机械臂。
## 5. RM75 模型与关节约束
将上传文件中的 `RM75-B.urdf` 及其网格作为 `xr_rm_teleop` 包资源安装,不携带上传示例的 Pinocchio、OSQP 或仿真控制代码。
模型约定:
- 固定基座:`base_link`
- 运动关节:按 `joint_1``joint_7` 顺序映射 SDK 的 7 个关节角。
- 末端法兰帧:`link_7`
- 节点和 Placo 内部统一使用弧度;Adapter 在 SDK 反馈/指令边界完成度与弧度转换。
Placo 启用 URDF 关节位置和速度限制。上传 URDF 中的位置范围与睿尔曼官方 RM75-B 范围一致:
```text
J1 ±178°, J2 ±130°, J3 ±178°, J4 ±135°,
J5 ±178°, J6 ±128°, J7 ±360°
```
旧的、当前未被调用的 `fun_peripheral.alg_init()` 自定义限位不作为 QP 限位来源。控制器侧现有 `configure_safety_limits`、关节最大速度和最大加速度设置继续保留。
参考:
- [睿尔曼 RM75-B 本体参数](https://develop.realman-robotics.com/robot/robotParameter/RM75OntologyParameters/)
- [XRoboToolkit DualArmURController](https://github.com/XR-Robotics/XRoboToolkit-Teleop-Sample-Python/blob/main/xrobotoolkit_teleop/hardware/dual_arm_ur_controller.py)
## 6. 工具 TCP 处理
URDF 只描述到 `link_7`,实际工具来自 `peripherals_rm75.yaml`。同一份工具配置有两个使用者:
```text
peripherals_rm75.yaml
├─ RealManAdapter:设置真实控制器工具坐标系和负载
└─ PlacoIkSolver:构造法兰到工具 TCP 的固定变换
```
当前选择为:
- 左臂 `scissorgripper: 2``minisci`,局部 Z 偏移 `+0.19 m`
- 右臂 `scissorgripper: 1``omnipic`,局部 Z 偏移 `+0.16 m`
实现读取完整的 `[x, y, z, qx, qy, qz, qw]`,不硬编码为世界坐标 Z 偏移。设:
- `B_T_F(q)`:Placo 由关节角计算的基座到法兰变换。
- `F_T_T`:YAML 给出的法兰到工具 TCP 固定变换。
- `B_T_T_target`:经过现有安全和速度限制后的目标工具 TCP。
正解和目标换算为:
```text
B_T_T(q) = B_T_F(q) * F_T_T
B_T_F_target = B_T_T_target * inverse(F_T_T)
```
`F_T_T` 及其逆矩阵在启动时预计算。每周期只执行少量固定尺寸矩阵运算,不重新读取 YAML、求逆或加载 URDF。工作空间、圆柱限制、调试位姿和误差验收均以工具 TCP 为准;只有 Placo frame task 使用换算后的法兰目标。
## 7. Placo 求解器
每个求解器包含:
- 一个 `placo.RobotWrapper`。Placo 0.9.4 会为模型加入 7 个虚拟浮动基座状态,
因此 `robot.state.q` 长度为 14,真实 RM75 关节固定映射为
`robot.state.q[7:14]`
- 一个 `placo.KinematicsSolver``dt = 1 / control_rate_hz`
- 一个作用于 `link_7` 的软约束完整位姿任务。
- 一个可操作度任务。
- 一个动能正则项。
- 启用的关节位置与速度限制。
RM75 基座实际固定,创建求解器后必须调用 `solver.mask_fbase(True)`,禁止 QP
通过移动虚拟基座减小末端误差。所有状态同步和结果提取只读写
`robot.state.q[7:14]`
初始权重沿用 XR 真机示例的最小配置:
```text
frame task: soft, 1.0
manipulability task: soft, 5e-2
kinetic energy regularizer: 1e-6
```
每个周期先用实际关节反馈覆盖 Placo 状态并更新运动学,再设置法兰目标,最后只调用一次 `solver.solve(True)`。这里的“一步”指一次外层 Placo 求解调用;QP 求解器完成该次优化所需的内部数值迭代不算额外控制周期。
Placo 0.9.4 在 RM75 全零 neutral 位形下会出现 QP `NaN`;左右臂现有实际
初始关节角的一步求解均能得到 7 个有限结果。因此全零位形不作为启动状态或
健康检查,必须等待首帧实际关节反馈后才能启用 QP。
## 8. 90 Hz 数据流
```text
XR 相对位姿
-> 现有死区、滤波、工作空间/圆柱限制
-> 现有线速度和角速度单周期限制
-> 目标工具 TCP
-> 换算目标法兰位姿
-> 读取 Adapter 最新实际关节角
-> 同步 Placo 状态
-> solver.solve(True) 一次
-> 校验 7 个目标关节角
-> rad 转 deg
-> rm_movej_canfd(..., follow=False)
```
`RealManAdapter` 连接后在后台连续调用 `rm_get_joint_degree()`,把最新 7 关节角和单调时钟时间戳存入线程安全缓存。控制定时器只复制缓存,不在 90 Hz 回调中等待关节查询。缓存锁只保护内存数据,不包围网络调用。
第一次有效反馈到达前不调用 `solver.solve(True)`,也不发送运动命令。首帧必须
包含 7 个有限关节角且未过期;收到后将度转换为弧度写入
`robot.state.q[7:14]`,更新运动学,把当前工具 TCP 设为初始目标,并以实际
关节角初始化 `last_valid_joint_target`。Mock 模式使用现有
`initial_joint_pose` 初始化 7 关节状态并立即提供同样的首帧有效反馈,再通过
同一 Placo 正解计算工具 TCP;原先仅用于笛卡尔 mock 的
`mock_initial_pose` 随旧控制链路移除。
`rm_movep_canfd` 不再位于遥操作运动链路中。
## 9. 异常与停止策略
启动时先校验 Placo、URDF、关节顺序和工具配置,成功后才连接真机。运行时分为两类异常。
### 9.1 沿用 XR 的 last-known-good 策略
第一帧有效关节反馈到达后,用实际关节角初始化 `last_valid_joint_target`
- QP 成功且输出通过校验:更新并发送新的 `last_valid_joint_target`
- QP 抛出异常、返回错误维数、`NaN/Inf`,或输出违反关节位置/单周期速度限制:不更新目标,继续发送上一组有效关节目标。
- 下一周期 QP 恢复:自动恢复目标更新,不要求重新按 Grip。
- 求解失败日志限频,避免日志影响控制周期。
不可达目标本身不视为求解异常;软约束任务继续在约束内每周期靠近一步。
### 9.2 输入、反馈或通信不可信时慢停止
以下情况不使用旧关节目标,沿用现有只发送一次慢停止并重置激活状态的逻辑:
- XR 指令超过现有 `command_timeout_sec`
- Grip 松开。
- 真实关节反馈没有首帧、过期、维数错误或包含 `NaN/Inf`
- SDK 关节指令发送失败。
- 四元数非法。
- 节点关闭。
关节反馈时效先复用现有 `command_timeout_sec=0.12`,避免增加含义相近的参数。若真机测量证明正常反馈无法稳定满足该阈值,再单独拆分反馈超时参数。
`configure_safety_limits` 保持启用;`move_to_initial_pose_on_connect` 的启动默认值保持 `false`
## 10. 依赖、Python 环境与配置
- 复用现有 `/home/robot/miniconda3/envs/xr` 环境及其中已经验证的
Placo `0.9.4`、Pin `3.7.0` 和 NumPy `2.2.6`,不新增 XRoboToolkit
项目依赖。
- `arm_debug.launch.py` 明确使用
`/home/robot/miniconda3/envs/xr/bin/python` 启动
`single_arm_velocity_teleop`ROS2 launch 和 `colcon` 仍使用系统
`/usr/bin/python3`
- 禁止升级 Placo,禁止向系统 Python、`pip --user` 或其他全局位置安装
Placo、Pinocchio、EigenPy 或 NumPy。构建不改用 Conda Python。
- launch 启动前校验 XR Python 路径存在;不存在时直接报错,不回退到可能
缺少 Placo 或版本不同的系统 Python。
- 真机模式继续按需导入睿尔曼 Python API2。
- Mock 模式依赖 Placo 和 RM75 模型,但不得导入或要求安装睿尔曼 SDK。
- 工具选择继续只由 `peripherals_rm75.yaml` 和现有 `peripheral_arm` 决定。
- 左、右、双臂 YAML 中与 QP 相关的共同配置保持一致;左右现有空间、映射和初始关节角保持各自配置。
- 将左右单臂 YAML 的 `move_to_initial_pose_on_connect` 默认值统一为 `false`,并同步 README;需要自动回初始位姿时必须由用户显式传 `true`
- 不新增“为以后准备”的插件接口、求解器工厂或额外 ROS 消息。
## 11. 验证与验收
### 11.1 自动验证
- 工具 TCP/法兰变换可往返,包含末端旋转后的局部 Z 偏移。
- RM75 URDF 能加载,且映射顺序严格为 `joint_1``joint_7`
- 使用 Placo 0.9.4 时固定虚拟基座,真实关节只映射
`robot.state.q[7:14]`
- 没有首帧有效关节反馈时不调用 QP、不发送关节目标;首帧到达后用实际关节角
初始化状态和 `last_valid_joint_target`
- 一次 QP 求解输出 7 个有限关节角并满足位置、单周期速度限制。
- 强制 QP 失败时继续使用上一组有效关节目标。
- 强制反馈过期时执行慢停止。
- Mock 模式不导入睿尔曼 SDK。
- 运行现有姿态控制测试:
```bash
pytest src/xr_rm_teleop/test/test_orientation_control.py
```
- 从工作空间根目录构建:
```bash
source /opt/ros/humble/setup.bash
colcon build --symlink-install
```
### 11.2 左右臂单独 Mock 验收
```bash
ros2 launch xr_rm_bringup arm_debug.launch.py arm:=left use_mock:=true
ros2 launch xr_rm_bringup arm_debug.launch.py arm:=right use_mock:=true
```
左臂和右臂必须分别独立启动并完成相同验收。每个单臂目标停止变化并保持 `0.5 s` 后:
- 工具 TCP 位置误差不超过 `5 mm`。
- 工具 TCP 姿态误差不超过 ``。
- 记录 Placo 单次求解耗时和控制周期超时情况。
- 左臂使用 `minisci +0.19 m` 工具变换,右臂使用 `omnipic +0.16 m` 工具变换。
90 Hz 的周期预算约为 `11.1 ms`。性能数据作为验证报告输出,不把易受机器负载影响的耗时阈值写成单元测试硬断言。
### 11.3 左右臂单独真机验收
Codex 分别提供 `launcher_ui.py` 左臂、右臂启动步骤和检查清单,不执行真机连接、运动或夹爪操作。用户在确认急停、障碍物、低速和初始姿态后,先只启动一侧完成验证,停止该侧节点后再验证另一侧。本轮不以 `arm:=both` 进行真机验收。两侧真机首次启动都必须保持 `move_to_initial_pose_on_connect:=false`。
## 12. 完成标准
满足以下条件才视为实现完成:
1. 遥操作运动链路不再调用 `rm_movep_canfd`。
2. 每个有效控制周期只有一次 Placo `solve(True)`。
3. 目标通过 7 个关节角和 `rm_movej_canfd` 下发。
4. 同一机械臂始终只有一个 RealMan SDK 连接。
5. 工具 TCP 偏移参与目标换算、正解和误差验收。
6. QP 失败使用上一组有效目标,输入/反馈/通信失败执行慢停止。
7. 指定构建、测试以及左臂、右臂各自的 mock 验收通过。
8. 分别提供左臂、右臂真机人工验证步骤,但不代替用户执行。
9. 遥操作节点由 launch 显式使用 XR Python 和 Placo 0.9.4,未升级或全局安装
数值依赖。