Files
acRealman_xr/docs/superpowers/specs/2026-08-10-act-tomato-pick-data-collection-design.md

658 lines
28 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.
# 右臂番茄采摘 ACT 数据采集适配设计
## 背景与目标
当前项目已经通过 ROS2 Humble、PICO 手柄、Placo QP 和 RealMan Python API2
完成 RM75 遥操作。右臂遥操节点以名义 `90 Hz` 读取实际关节反馈,根据 PICO
相对位姿生成 TCP 目标,经过工作空间限制、QP、关节速度与加速度限制后,通过
`rm_movej_canfd` 下发最终关节目标。
本次变更在不重写现有遥操链路的前提下,增加一个独立的 ALOHA/ACT 风格数据采集
节点。第一阶段只采集右臂番茄采摘任务,每个 episode 覆盖从初始位姿出发、抓取
番茄、搬运至 RM75 下方收集篮并释放番茄的完整过程。
核心目标如下:
- 以右臂 7 个实际关节角和夹爪逻辑状态作为 `observations/qpos`
- 以实际成功下发或零阶保持的 7 个最终关节目标和夹爪目标作为 `action`
- 同步采集一台全局 D455 和一台右腕 D405 的 RGB 图像;
-`30 Hz` 形成同周期因果对齐的数据;
- 每个完整任务流式保存为一个 ALOHA/ACT 核心结构兼容的 HDF5 文件;
- 支持手柄开始、结束、丢弃、拒绝、质量检查、崩溃恢复和编号防覆盖;
- 保留足够的 PICO、TCP、QP、夹爪和时间戳调试数据,但不让采集节点进入机器人
控制链路。
参考实现为 ALOHA 官方仓库中的
[`record_episodes.py`](https://github.com/tonyzhaozh/aloha/blob/master/aloha_scripts/record_episodes.py)。
官方双臂数据使用 14 维状态和动作;本项目第一阶段采用右臂 `7+1=8` 维,因此只
保证 HDF5 核心组织方式兼容,不声称官方旧训练加载器可以不修改直接训练。
## 非目标
第一阶段明确不实现:
- 左臂或双臂 ACT 采集;
- ACT 训练代码、数据加载器、策略部署或自动完成判定;
- 深度图、红外图、点云、图像压缩或 ROS 图像话题;
- rosbag、中间格式、离线转换工具、GUI、声音或手柄震动反馈;
- 让 ACT 学习 A 键触发的回初始位姿运动;
- 修改现有工作空间限制、圆柱限制、速度限制、指令超时、安全停止或
`move_to_initial_pose_on_connect` 默认值;
- 新建重复的 ROS2 包或第二个 RealMan 连接。
后续训练时建议将 ACT `chunk_size` 设为 `60`,对应约 2 秒动作长度;该参数属于
训练配置,不写死在采集逻辑中。
## 现有控制链路与关键约束
现有 `single_arm_velocity_teleop` 在每个控制周期内依次完成:
```text
读取最新 RM75 反馈
→ 同步 Placo 状态
→ 读取 PICO 状态并生成 TCP 目标
→ 工作空间、位姿步长与速度限制
→ Placo QP
→ 关节速度与加速度限制
→ rm_movej_canfd 下发
→ 发布 joint_target 调试话题
```
当前 `joint_states`、TCP 调试话题和 `joint_target` 分别发布并各自取时间戳。如果
采集节点仅订阅这些分散话题,即使时间接近,也可能把第 100 个控制周期的
`q_actual` 与第 99 个控制周期的 `q_target` 拼在一起。这里的“第 100 个周期”是
控制序号,不是 `100 Hz`;现有控制频率仍是名义 `90 Hz`
此外,A 键回位调用的是一次阻塞式 `rm_movej(initial_joint_pose)`。轨迹由 RM75
控制器内部生成,项目不能获得每个控制周期的中间目标,也不经过当前 QP 和
`rm_movej_canfd` 链路,因此不能与遥操动作混用同一种标签语义。
## 总体架构
采用一个自定义原子控制采样消息和一个独立 ACT 采集节点:
```text
PICO 输入 + RM75 反馈
single_arm_velocity_teleop90 Hz
QP → 关节限速/限加速度 → RM75 下发
ActControlSample(同周期、同时间戳、同序号)
act_episode_recorder(每 3 个控制周期取 1 个)
├──────────────┐
↓ ↓
全局 D455 RGB 右腕 D405 RGB
└──────┬───────┘
30 Hz 流式写入临时 HDF5
裁剪 → 质量检查 → 保存/拒绝/丢弃
```
“原子”表示消息是一个逻辑上不可拆分的控制周期快照。订阅者要么收到该周期完整的
反馈、求解结果、最终动作和状态,要么该周期整体缺失;不会自行拼接多个异步话题。
职责边界如下:
- 遥操节点继续唯一负责 RM75 连接、反馈、QP、限位、动作下发和安全停止;
- 遥操节点只增加原子消息发布和夹爪逻辑状态记录,不读取相机、不写 HDF5;
- 采集节点只订阅控制/PICO 数据、独占两台相机并写文件,不连接或控制 RM75;
- 采集节点异常、退出或写盘过慢不得阻塞遥操发布或改变机器人动作;
- 相机由采集节点通过 `pyrealsense2` 直接打开,不再发布和重新订阅 ROS 图像。
原子消息使用本机低延迟、非阻塞的 best-effort QoS。采集节点检测任何需要保留的
控制周期丢失并拒绝 episode,而不是让 DDS 反压影响遥操控制。
## 原子控制采样消息
`xr_rm_interfaces` 中新增 `ActControlSample.msg`,发布话题为:
```text
/xr_rm/right_rm75/act_control_sample
```
消息至少表达以下内容:
| 类别 | 字段语义 |
|---|---|
| 周期标识 | ROS header、`control_seq`、控制周期单调时间戳 |
| 关节反馈 | `q_actual[7]`、当前 URDF 关节上下限、反馈接收单调时间戳、反馈年龄、反馈有效状态 |
| QP | QP 原始输出或失败时的保持目标 `q_qp_raw[7]`、是否尝试、成功状态、耗时 |
| 最终动作 | 经关节限速后的 `q_target[7]`、动作时间戳、是否当前周期成功下发 |
| TCP | 当前 TCP、PICO 映射前的原始目标 TCP、最终受限目标 TCP、命令速度 |
| PICO | 当前右手位姿、Grip、Trigger、A、B 和摇杆值 |
| 夹爪 | 请求目标、已确认逻辑状态、命令是否处理中、命令是否失败 |
| 控制状态 | `teleop_active``action_valid`、QP 回退、目标限位和控制故障状态 |
数值型关节字段在 ROS 消息中保持双精度,写入 HDF5 核心数据时显式转换成
`float32`。位姿使用位置加四元数,不保存完整 Placo 对象、Hessian、约束矩阵或
其他大体积求解器内部状态。
消息在每个实际执行的 90 Hz 控制回调中发布,包括 Grip 松开和安全停止状态:
- 当前周期成功发送动作时,`command_sent=true`
- 当前周期没有发送,但此前存在成功目标时,`q_target` 零阶保持上一个成功目标,
`command_sent=false``action_valid=true`
- 尚未形成任何有效目标或当前动作发送失败时,`action_valid=false`
- QP 失败但成功重发上次有效目标时,`qp_success=false``action_valid=true`
- 发送失败必须发布失败状态,并由采集节点拒绝当前 episode。
相机数据不放进该消息。相机时间戳和帧号由采集节点在同一主机的单调时钟域中补充。
## 夹爪数据语义
右臂使用 Modbus 电动夹爪。由于不同番茄尺寸会导致实际停止开度不同,而本项目只
关心抓取意图,第一阶段不读取或估算实际开度。
统一约定:
```text
0 = closed
1 = open
```
两类状态必须分开:
- `action[7]` 是目标状态,在 Trigger 产生开合请求的控制周期立即改变;
- `qpos[7]` 是已确认逻辑状态,只有 Modbus 命令正常返回后才改变;
- 命令失败时 `qpos[7]` 保持原值,并拒绝当前 episode;
- 采集前右臂夹爪必须成功初始化为完全打开 `1.0`,之后才允许把初始
`qpos[7]` 设为 `1`
- 不再使用原先含义不明确的 `0.75 → 0.15` 初始化序列。
结束一个有效番茄采摘 episode 前,操作者应先请求打开夹爪,等待日志/状态确认
逻辑状态已经变为 `open`,再松开 Grip 并按 B。结束时夹爪命令仍在执行,保存状态
最多等待 3 秒;失败或超时则拒绝。最终裁剪后的数据若没有包含已确认的打开状态,
同样拒绝,不能把保存后的成功状态回填到更早样本中。
## 相机配置与采集
第一阶段固定使用两台已确定序列号的 RealSense:
| ACT 名称 | 型号与位置 | 序列号 |
|---|---|---|
| `cam_high` | 全局 D455 | `234222303366` |
| `cam_right_wrist` | 右臂腕部 D405 | `412622272532` |
两路图像参数统一为:
```text
分辨率:640 × 480
帧率:30 FPS
格式:RGB uint8
HDF5 形状:(T, 480, 640, 3)
```
不采集深度、红外和点云,不使用 JPEG 压缩。采集线程直接请求 RealSense RGB8
避免为颜色通道转换引入 OpenCV 依赖。
每台相机使用独立采集线程和一个很小的 `deque` 帧缓冲。每帧保存:
- RealSense 帧号;
- RealSense 硬件时间戳;
- `wait_for_frames` 返回后立即读取的主机单调时间戳;
- RGB 数组。
两台设备的硬件时钟不能默认视为同一时钟域,因此正式对齐只使用同一主机的单调
时钟;硬件时间戳只用于发现设备重启、帧号跳变和采集异常。
采集节点独占相机。指定设备缺失、型号/序列号不匹配、流配置失败或已经被其他
进程占用时,预检失败并停留在 `IDLE`,不自动替换成其他相机。
## 30 Hz 采样与因果对齐
正式采样不使用独立的 30 Hz ROS 定时器。采集节点在首次有效 Grip 控制周期记录
`sample_origin_seq`,随后只选择:
```text
(control_seq - sample_origin_seq) % 3 == 0
```
因此 90 Hz 控制消息按 `0、3、6、9...` 的相对序号形成名义 30 Hz 数据,同时保证
第一个正式样本就是首次有效动作,而不是等待一个全局取模相位。
每个样本的定义为:
```text
observation[t]
= 当前控制周期开始时读取的 q_actual
+ 对每台相机选择主机时间戳不晚于该控制周期的最新帧
action[t]
= 同一控制周期经 QP、关节限速后成功发送的 q_target
或 Grip 暂停/QP 回退时明确定义的上次成功目标
```
采集节点收到消息时,相机缓冲中可能已经存在晚于控制周期的帧,因此不能简单取
“回调时最新帧”,必须按 `host_monotonic_ns <= control_monotonic_ns` 选择最近帧。
不存在满足条件且年龄不超过 50 ms 的帧时,当前 episode 拒绝。
不采用官方旧加载器中的 `action[t-1]` 补丁。HDF5 根属性写入:
```text
action_alignment = "same_step_causal"
```
控制、反馈、动作和图像源时间戳全部保存在 `/debug`,未来只有在真实延迟测量证明
存在稳定偏移时,才在训练加载器中调整;原始 HDF5 不进行不可逆移位。
## Episode 边界与手柄状态机
### 按键映射
- 右手 B,即右手 `secondary` 单击:开始准备或结束保存;
- 左手 Y,即左手 `secondary` 长按 1 秒:丢弃当前准备/录制;
- 右手 A,即右手 `primary`:继续保持现有右臂回初始位姿功能;
- Grip:继续只控制遥操离合,不作为“只在按下时才记录”的采集开关。
右手 B 在右手 Grip 按下时始终忽略,避免运动中误触开始或结束。左手 Y 仅在
`ARMED``RECORDING` 中长按有效,在 `IDLE` 中无作用,且永远不删除上一个已经
保存的 episode。
### 状态机
```text
IDLE
└─ Grip 松开时单击右手 B
├─ 预检失败 → IDLE
└─ 预检通过 → ARMED
ARMED
├─ 第一次 Grip 有效动作 → RECORDING
├─ 再次单击右手 B → 取消 → IDLE
├─ 长按左手 Y 1 秒 → DISCARDED → IDLE
└─ 按右手 A → 取消 → IDLE
RECORDING
├─ Grip 松开后单击右手 B → SAVING
├─ 长按左手 Y 1 秒 → DISCARDED → IDLE
├─ 按右手 A → REJECTED → IDLE
└─ 硬质量故障/60 秒上限/Ctrl+C → REJECTED → IDLE
SAVING
├─ 质量检查通过 → SAVED → IDLE
└─ 质量检查失败 → REJECTED → IDLE
```
`IDLE``ARMED``RECORDING``SAVING` 是运行状态;`SAVED``DISCARDED`
`REJECTED` 是短暂结果状态,发布一次结果并输出日志后回到 `IDLE``SAVING`
期间忽略 B/Y 录制按键,A 键仍属于原有遥操逻辑,但不会再进入已经结束的数据。
状态通过 `std_msgs/msg/String` 话题 `/act/recording_status` 和终端日志报告,不增加
新状态消息、声音或震动接口。
### 连续记录与 Grip 暂停
正式时间轴从 `ARMED` 后 Grip 按下且第一次
`action_valid=true、command_sent=true` 的控制周期开始。Grip 刚按下的建基准周期
尚未向 RM75 发送新的 CANFD 目标,因此不作为第一个训练样本。
录制过程中临时松开 Grip 时仍以 30 Hz 保存图像和 `qpos``action` 零阶保持上次
成功目标,并记录 `teleop_active=false`
- 松开后重新按 Grip:暂停区间保留,继续同一个 episode;
- 最后一次松开后按 B:将该次松开至 B 之间的纯操作等待数据裁掉,episode 结束在
最后一次 Grip 松开附近;
- 夹爪打开确认必须已经包含在裁剪终点之前,否则拒绝该 episode。
只在 Grip 按下时保存数据会丢失接近任务开始、暂停恢复和完整视觉上下文,因此不
采用该方案。
### Episode 是否包含 A 键回位
一个正式 episode 只包含:
```text
初始位姿、夹爪打开
→ 接近番茄
→ 闭合夹爪
→ 搬运至收集篮
→ 打开夹爪并确认成功
→ 松开 Grip
→ 按 B 结束
```
A 键的 `rm_movej(initial_joint_pose)` 必须在成功结束 episode 后执行,不写进
episode。这样 ACT 始终学习同一种逐周期 `q_target` 动作语义。未来实时推理若要
连续采摘,应由上层状态机执行:
```text
ACT 完成一次采摘 → 完成判定 → 固定 rm_movej 复位 → 下一次 ACT 采摘
```
若在 `RECORDING` 中误按 A,当前文件转入拒绝目录,原因写为:
```text
initial_pose_command_during_episode
```
拒绝数据不会拦截 A 键原有回位动作,也不会额外控制机器人。
## HDF5 核心结构
数据根目录和任务目录固定为:
```text
/home/robot/ACT_Data
/home/robot/ACT_Data/tomato_pick
```
正式文件核心结构:
```text
/observations/qpos float32 (T, 8)
/observations/images/cam_high uint8 (T, 480, 640, 3)
/observations/images/cam_right_wrist uint8 (T, 480, 640, 3)
/action float32 (T, 8)
/debug/...
```
`T` 是该次任务的实际样本数,不要求所有 episode 等长,不进行文件内 padding。
最短有效 episode 为 `60` 个样本,即 2 秒;最长为 `1800` 个样本,即 60 秒。
8 维字段顺序固定为:
```text
qpos[0:7] = RM75 实际反馈关节角,单位 rad
qpos[7] = 已确认夹爪逻辑状态,0 closed、1 open
action[0:7] = 最终成功下发或明确定义为保持的关节目标,单位 rad
action[7] = 夹爪请求目标,0 closed、1 open
```
根属性至少包括:
| 属性 | 值或语义 |
|---|---|
| `sim` | `false`,与 ALOHA 真实数据约定一致 |
| `task_name` | `tomato_pick` |
| `sample_rate_hz` | `30` |
| `action_alignment` | `same_step_causal` |
| `arm` | `right_rm75` |
| `episode_status` | `saved``rejected` |
| `camera_high_serial` | `234222303366` |
| `camera_right_wrist_serial` | `412622272532` |
| `joint_names` | 7 个 RM75 关节名和 `gripper` 的固定顺序 |
| `joint_lower_limits` | 来自当前 Placo/URDF 的 7 关节下限 |
| `joint_upper_limits` | 来自当前 Placo/URDF 的 7 关节上限 |
| `reject_reason` | 仅拒绝文件存在 |
| `interrupted` | 正常文件为 `false`Ctrl+C/异常恢复为 `true` |
图像不压缩,每帧使用一个 HDF5 chunk;数值数据使用可扩展的一维时间轴并分块
写入。按两路 `640×480×3×30` 计算,图像数据约为 3.3 GB/分钟,因此不能把完整
episode 先缓存到内存再一次性保存。
不创建 `/observations/qvel``/observations/effort`、压缩标记或 `compress_len`
也不使用零值、有限差分或其他伪数据填充缺失字段。后续训练加载器按存在的核心
字段读取。
## Debug 结构
自定义消息是运行时传输载体,进程退出后不会保留;HDF5 `/debug` 是永久诊断记录。
ACT 训练默认不读取该组。
建议使用以下精简结构,布尔状态以 `uint8` 保存:
```text
/debug/timestamps/control_monotonic_ns int64 (T,)
/debug/timestamps/feedback_monotonic_ns int64 (T,)
/debug/timestamps/action_monotonic_ns int64 (T,)
/debug/timestamps/cam_high_host_monotonic_ns int64 (T,)
/debug/timestamps/cam_wrist_host_monotonic_ns int64 (T,)
/debug/timestamps/cam_high_hardware_ms float64 (T,)
/debug/timestamps/cam_wrist_hardware_ms float64 (T,)
/debug/timestamps/cam_high_age_ms float32 (T,)
/debug/timestamps/cam_wrist_age_ms float32 (T,)
/debug/timestamps/inter_camera_skew_ms float32 (T,)
/debug/cameras/cam_high_frame_number uint64 (T,)
/debug/cameras/cam_wrist_frame_number uint64 (T,)
/debug/control/control_seq uint64 (T,)
/debug/control/teleop_active uint8 (T,)
/debug/control/action_valid uint8 (T,)
/debug/control/command_sent uint8 (T,)
/debug/control/target_clamped uint8 (T,)
/debug/control/control_fault uint8 (T,)
/debug/qp/raw_target float32 (T, 7)
/debug/qp/attempted uint8 (T,)
/debug/qp/success uint8 (T,)
/debug/qp/duration_ms float32 (T,)
/debug/tcp/current_pose float32 (T, 7)
/debug/tcp/raw_target_pose float32 (T, 7)
/debug/tcp/final_target_pose float32 (T, 7)
/debug/tcp/command_velocity float32 (T, 6)
/debug/pico/right_pose float32 (T, 7)
/debug/pico/right_inputs float32 (T, 6)
/debug/pico/left_secondary uint8 (T,)
/debug/gripper/target_open uint8 (T,)
/debug/gripper/state_open uint8 (T,)
/debug/gripper/command_pending uint8 (T,)
/debug/gripper/command_failed uint8 (T,)
```
位姿顺序统一为 `[x, y, z, qx, qy, qz, qw]`TCP 速度顺序统一为
`[vx, vy, vz, wx, wy, wz]``right_inputs` 顺序在文件属性中写明。当前周期没有发送
动作时,`action_monotonic_ns=-1`,并以 `command_sent=false` 消除歧义。
episode 根属性额外保存 QP 失败次数、失败占比、最长连续失败次数、目标限位次数、
相机帧率、丢帧率和最大时间偏差等汇总指标。
## 数据质量规则
### 开始前预检
Grip 松开时单击 B 后,采集节点检查:
- 输出目录存在或可以创建且可写;
- 可用空间不少于 4 GiB,约为 60 秒原始图像估算值的 1.2 倍;
- 两台指定相机均在线、已经连续预热 5 秒且当前帧率合格;
- 最近 `q_actual` 合法,反馈年龄不超过 50 ms,RM75 无掉使能或控制故障;
- 右臂夹爪初始化打开命令已经成功;
- 左右 PICO 话题均在现有手柄超时范围内保持新鲜;
- 没有第二个采集进程持有任务目录锁或相机设备;
- 启动组合是 `arm:=right use_mock:=false record_act:=true`
任一预检失败时输出明确原因并停留在 `IDLE`,不生成空文件,也不改变机器人状态。
### 硬拒绝条件
以下任一情况使当前 episode 进入 `REJECTED`
- 样本少于 60 或达到 60 秒上限;
- 控制周期序列缺失、有效平均采样率低于 27 Hz 或相邻样本间隔超过 100 ms;
- `qpos/action` 不是 `(T,8)`、包含 NaN/Inf、违反配置关节限制或夹爪值不是
`0/1`
- `q_actual` 年龄超过 50 ms,反馈超时、掉使能或出现控制故障;
- 最终关节动作发送失败,或消息表示的反馈和动作不属于同一控制周期;
- 夹爪命令失败、超时,或最终裁剪数据没有包含已确认的打开状态;
- 任一路相机平均帧率低于 27 FPS;
- 任一路相机硬件帧号丢失率超过 1%,或采样后的重复/跳帧比例超过 1%;
- 任一采样图像年龄超过 50 ms,或两路图像主机时间差超过 50 ms;
- 图像形状、数据类型或 RGB 通道约定错误;
- 录制中按 A
- HDF5 写入失败、磁盘空间不足或有界写入队列持续积压;
- Ctrl+C、采集节点异常退出或启动时恢复崩溃残留文件。
采集节点的拒绝只处理数据,不额外发送停止或运动命令。若原因来自控制故障,仍由
现有遥操安全链路执行原有安全停止。
### 允许但记录告警的情况
QP 求解偶发失败时,现有逻辑保留上次有效关节目标。只要该保持目标最终成功发送、
反馈和其他质量规则正常,就不自动拒绝 episode,而是保存:
- QP 失败样本数;
- 失败占比;
- 最长连续失败样本数;
- 每个样本的 `qp_success`
工作空间限位、TCP 步长限制或关节速度/加速度限制生效同样只记录,不自动拒绝。
这些限制是正常安全控制的一部分。
## 文件编号、保存、拒绝与恢复
正式编号只扫描任务目录根部的 `episode_<数字>.hdf5`,取最大编号加一:
```text
已有 episode_0.hdf5 ... episode_9.hdf5
重启后下一个正式文件仍为 episode_10.hdf5
```
不填补编号空洞,绝不覆盖已有正式文件。任务目录使用标准库文件锁保证同一时刻只有
一个采集进程分配编号和写入;临时文件与目标文件位于同一文件系统,检查通过后使用
不覆盖已有目标的原子发布方式。
文件生命周期:
```text
录制中:
/home/robot/ACT_Data/tomato_pick/episode_10.partial.hdf5
检查通过:
/home/robot/ACT_Data/tomato_pick/episode_10.hdf5
检查失败:
/home/robot/ACT_Data/tomato_pick/rejected/
episode_10_<reason>_<timestamp>.hdf5
```
- 只有正式保存成功才消耗编号;
- 手动长按 Y 丢弃时关闭并删除当前临时文件,不生成拒绝文件;
- 普通质量拒绝转入 `rejected/`,不消耗正式编号;
- Ctrl+C 时尽力关闭可读 HDF5,根属性写入 `interrupted=true`,文件名原因使用
`interrupted`
- 下次启动先处理遗留临时文件:可读文件转入 `rejected/` 并标记
`crash_recovered`,不可读文件改成带时间戳的 `.partial.hdf5` 保留;
- 拒绝原因同时写入文件名和根属性;
- 任一步出现目标文件冲突时停止保存并报警,不能覆盖或自动删除已有 episode。
写入采用单独工作线程和有界队列,ROS 回调只完成取样、对齐和入队。队列容量只需
覆盖短暂磁盘抖动,不能无限增长掩盖磁盘吞吐不足;持续积压时拒绝数据。
## 启动、配置与依赖
继续使用唯一入口 `xr_rm_bringup/launch/arm_debug.launch.py`,新增参数:
```text
record_act:=false
```
默认 `false`,现有 mock、单臂、双臂和 MuJoCo 启动行为不变。第一阶段唯一允许的
采集组合为:
```bash
ros2 launch xr_rm_bringup arm_debug.launch.py \
arm:=right use_mock:=false record_act:=true
```
`record_act:=true` 配合 `arm:=left|both``use_mock:=true` 时,在 launch 参数校验
阶段明确拒绝。ACT 采集节点退出不触发整个 launch 的 `Shutdown`,保证数据进程故障
不会终止遥操;终端必须清晰显示采集已经不可用。
新增一份专用 YAML,集中保存:
- 数据根目录和任务名;
- 两个相机序列号、分辨率和帧率;
- 30 Hz 采样率、2 秒最短时长和 60 秒最长时长;
- 相机、反馈、磁盘和时间对齐质量阈值;
- PICO 左右话题、原子采样话题和状态话题。
硬件相关阈值保留为配置项,核心 HDF5 字段顺序和 8 维语义固定,不为未来可能的
变体增加插件或通用框架。
采集节点继续由 `/home/robot/miniconda3/envs/xr/bin/python` 启动。复用该环境已有的
`pyrealsense2` 和 NumPy,只在该环境增加 HDF5 必需依赖 `h5py`;不修改系统 Python
不新增 Conda 环境,也不增加 OpenCV 依赖。依赖缺失时节点应在打开相机或创建文件前
给出明确错误。
## 最小代码范围
预计只修改或新增以下位置:
- `xr_rm_interfaces/msg/ActControlSample.msg`:原子消息;
- `xr_rm_interfaces/CMakeLists.txt``package.xml`:生成并导出消息;
- `xr_rm_teleop/xr_rm_teleop/single_arm_velocity_teleop.py`:发布原子样本、跟踪夹爪
请求/成功状态和 A 键事件;
- `xr_rm_teleop/xr_rm_teleop/fun_peripheral.py`:把请求的初始工具状态明确改为完全
打开;
- `xr_rm_teleop/xr_rm_teleop/act_episode_recorder.py`:独立采集节点;
- `xr_rm_teleop/setup.py``package.xml`:安装入口和 ROS 运行依赖声明;
- `xr_rm_bringup/config/act_tomato_pick.yaml`:采集与硬件参数;
- `xr_rm_bringup/config/peripherals_rm75.yaml`:只为右臂启用初始化打开;
- `xr_rm_bringup/launch/arm_debug.launch.py`:默认关闭的 `record_act` 启动分支;
- 现有测试目录中的最小相关测试。
不改左臂/双臂运动参数,不改变 `left_arm_teleop``right_arm_teleop` 节点名,不创建
第二个相机包、训练包或数据工具包。
## 测试与验收
### 自动化验证
测试全部使用 mock、假适配器、合成相机帧和临时目录,不连接真机、不移动机械臂、
不操作真实夹爪:
- 原子消息中的 `q_actual`、QP 输出和最终 `q_target` 来自同一控制周期;
- QP 失败时记录失败并保持上次目标,不自动拒绝;
- 发送失败、夹爪失败和 A 键误触触发拒绝;
- Trigger 请求立即改变 `action[7]`,成功返回后才改变 `qpos[7]`
- B/Y/Grip 的边沿、长按、忽略条件和所有状态转换正确;
- 从首次有效样本开始按控制序号每 3 个周期取 1 个;
- 相机只选择不晚于控制时间的最新帧,并能发现过期、偏斜和帧号异常;
- Grip 中途暂停保留,最终松开到 B 的等待段正确裁剪;
- HDF5 核心路径、形状、dtype、8 维顺序、根属性和 debug 字段正确;
- 变量长度、最短/最长限制、质量拒绝、手动丢弃和 Ctrl+C 正确;
- 编号从最大正式编号加一,拒绝不占号,已有文件不被覆盖;
- 可读和不可读崩溃残留分别按设计恢复;
- `record_act` 默认关闭,非法 arm/mock 组合被 launch 拒绝;
- mock 模式不会导入或调用 RealMan SDK、RealSense 或 HDF5 采集链路。
按照仓库要求,构建和测试从工作空间根目录执行:
```bash
cd /home/robot/WS_xr
source /opt/ros/humble/setup.bash
colcon build --symlink-install
pytest src/xr_rm_teleop/test/test_orientation_control.py
```
实施时还应运行新增测试及受影响的现有关节控制、初始位姿和外设配置测试。未看到
实际通过输出前不得声称验证通过。
### 真机手工验收
真机验收必须由用户明确授权并在现有安全检查完成后进行,至少验证:
1. 启动后右臂夹爪完全打开,状态未在命令成功前提前标记;
2. 两台相机序列号和画面角色正确,持续 30 FPS 左右;
3. B 开始、Grip 激活、B 结束、Y 丢弃和 A 误触拒绝符合状态机;
4. 正常采摘文件包含完整抓取、搬运和释放,不包含 A 键回位;
5. `qpos/action` 是有限的 `(T,8)` `float32`,图像是两路
`(T,480,640,3)` `uint8`
6. QP 短暂失败只增加 debug 计数,成功调整后仍可完成 episode;
7. 重启后编号继续递增,丢弃和拒绝不会覆盖或占用正式编号;
8. Ctrl+C、相机断流和磁盘不足产生带明确原因的拒绝文件;
9. ACT 采集节点退出后,遥操安全链路仍按现有行为运行。
## 后续训练与推理影响
本设计生成 ALOHA/ACT 风格的核心数据,但原始 ACT 代码通常把状态维度硬编码为
双臂 14 维,并可能假设固定 episode 长度。训练阶段需要单独适配:
- `state_dim=8`
- 两个相机名 `cam_high``cam_right_wrist`
- 变量长度 episode 的 padding 和 mask
- `chunk_size≈60`
- 不使用 `action[t-1]` 旧补丁;
- 忽略 `/debug`,除非用于筛选或诊断。
实时推理只负责从初始位姿执行一次采摘到释放。回初始位姿继续调用当前确定性的
`rm_movej`,由未来的上层任务状态机协调,避免让一个低层策略混合两种动作接口和
任务阶段。