# RM JSON 机械臂控制子工程

这个目录只放睿尔曼 RM75 机械臂相关代码，和上层 LinkerHand O6 灵巧手代码分开维护。当前支持边界只覆盖“睿尔曼机械臂 + 灵心巧手 O6”的目标程序，不做其他机械臂品牌或其他灵巧手型号的泛化支持。

## 支持边界

- 机械臂：睿尔曼 RM75 / RM_API2。
- 灵巧手联动目标：灵心巧手 O6。
- 不支持范围：其他机械臂品牌、其他灵巧手型号、跨品牌驱动适配层。
- 保留 `interfaces/` 的目的只是隔离 core 和 RealMan SDK 头文件，不表示当前要支持多品牌机械臂。

## 目录结构

```text
RM/
├── CMakeLists.txt
├── README.md
├── config/
│   ├── sample_readonly.json
│   └── sample_movej_delta_dry_run.json
├── include/rm_control/
│   ├── common/：Result、Pose、RobotInfo 等通用类型
│   ├── interfaces/：IRobotArm 抽象机械臂接口
│   ├── config/：JSON 解析和命令结构
│   ├── core/：JsonCommandRunner、DragTeachRecorder 等流程编排
│   ├── modules/trajectory/：轨迹目录、CSV、C++ waypoint 转换
│   └── drivers/realman/：旧名称兼容头，不包含厂商 SDK
├── src/
│   ├── config/
│   ├── core/
│   ├── modules/trajectory/
│   ├── drivers/realman/
│   ├── main.cpp
│   ├── drag_teach_record_main.cpp
│   ├── trajectory_o6_grasp_main.cpp
│   └── trajectory_convert_main.cpp
└── tests/：不依赖真实硬件的单元与集成测试
```

真实 RM_API2 适配器位于仓库根目录 `include/flight_robot/realman/` 与
`src/realman/`；业务层不得包含 `rm_interface.h`。

## JSON 指令格式

顶层字段：

```json
{
  "connection": {
    "ip": "192.168.1.18",
    "port": 8080,
    "timeout_ms": 1000
  },
  "safety": {
    "dry_run": true,
    "allow_motion": false,
    "max_speed_percent": 10,
    "max_joint_delta_deg": 2.0,
    "max_cartesian_step_m": 0.02,
    "max_euler_delta_rad": 0.2
  },
  "commands": []
}
```

安全字段含义：

| 字段 | 含义 |
|---|---|
| `dry_run` | 为 `true` 时只校验运动指令，不发送 `rm_movej/rm_movel` |
| `allow_motion` | 为 `false` 时禁止真实运动 |
| `max_speed_percent` | 运动速度上限 |
| `max_joint_delta_deg` | 单次关节运动最大角度差 |
| `max_cartesian_step_m` | 单次直线运动最大 xyz 位移 |
| `max_euler_delta_rad` | 单次直线运动最大欧拉角变化 |

支持的 `commands`：

```json
{"type": "get_robot_info"}
```

```json
{"type": "get_current_arm_state"}
```

```json
{"type": "get_force_data"}
```

```json
{"type": "sleep_ms", "duration_ms": 500}
```

绝对关节目标，单位是度：

```json
{
  "type": "movej",
  "joints_deg": [0, 0, 0, 0, 0, 0, 0],
  "speed_percent": 5,
  "blocking": true
}
```

相对关节增量，单位是度。推荐初期用这个：

```json
{
  "type": "movej_delta",
  "delta_deg": [0, 1, 0, 0, 0, 0, 0],
  "speed_percent": 5,
  "blocking": true
}
```

绝对笛卡尔直线目标，位置单位是米，姿态单位是弧度：

```json
{
  "type": "movel",
  "pose": {
    "x": 0.30,
    "y": 0.00,
    "z": 0.25,
    "rx": 3.14,
    "ry": 0.00,
    "rz": 0.00
  },
  "speed_percent": 5,
  "blocking": true
}
```

## 编译

```bash
cd linkerhand-cpp-sdk
cmake -S RM -B RM/build -DCMAKE_BUILD_TYPE=Debug \
  -DREALMAN_RM_API2_ROOT=/path/to/RM_API2/C++
cmake --build RM/build -j
ctest --test-dir RM/build --output-on-failure
```

工程只从显式缓存变量或仓库内备份查找 SDK：

```text
REALMAN_RM_API2_ROOT
RM/../third_party/Robotic_Arm
```

如果 SDK 在其他位置：

```bash
cmake -S RM -B RM/build -DREALMAN_RM_API2_ROOT=/path/to/RM_API2/C++
```

## 运行

只校验 JSON，不连接 SDK：

```bash
./RM/build/rm_json_runner RM/config/sample_readonly.json --validate-only
```

执行只读指令，读取机器人信息、当前状态和六维力：

```bash
./RM/build/rm_json_runner RM/config/sample_readonly.json
```

校验一个相对关节运动，但不发送运动：

```bash
./RM/build/rm_json_runner RM/config/sample_movej_delta_dry_run.json
```

需要真实运动时，把 JSON 改成：

```json
"safety": {
  "dry_run": false,
  "allow_motion": true,
  "max_speed_percent": 10,
  "max_joint_delta_deg": 2.0
}
```

`rm_json_runner` 是既有程序，不满足本项目新增的完整 Live 双重授权门控；当前阶段只允许
执行 `--validate-only` 或明确 DryRun 配置，不应把它用于真实运动。

## 拖动示教轨迹

`DragTeachService` 提供以下状态机能力：

- `StartRecording` / `StopRecording`；
- `SaveRawTrajectory`；
- `MoveToTrajectoryOrigin`；
- `ReplayLastDragTrajectory`；
- `PauseReplay` / `ContinueReplay` / `StopReplay`。

默认命令只执行 DryRun 配置检查，不连接设备：

```bash
./RM/build/rm_drag_teach_record demo_pick_path 15 100
```

真实拖动必须显式增加 `--live`，并同时满足安全配置、RM75/O6 实际连接、急停、
控制器七轴限制、虚拟墙、自碰撞、力/力矩阈值、人工二次确认和轨迹校验。当前 RM 独立
入口尚未接入 O6 设备状态服务，因此即使传入 `--live` 也会 fail-closed；不得用命令行
布尔值伪造 O6 已连接。完整服务流程由 `MockRobotArm` 无硬件测试覆盖。

每次成功保存都在构建目录下创建新的唯一会话，例如：

```text
RM/build/runtime_data/trajectories/sessions/<清理后的名称>_<UTC毫秒>/
├── raw_trajectory.txt
├── synchronized_samples.csv
└── manifest.json
```

同步 CSV 使用 `std::chrono::steady_clock`，字段包含连续 `sample_index`、`timestamp_ms`、
`j1_deg..j7_deg`、TCP 位姿和 `source`。manifest 记录 RM75-6F 型号、SDK/固件版本、
保存时间、厂商点数、同步样本数、原始文件大小、SHA-256 和保存接口实际返回码。
厂商原始文件固化后移除写权限，既有会话和 manifest 均拒绝覆盖。

厂商原生回放接口没有速度参数，因此仅勾选“接受风险”仍不足以授权：调用方还必须
提供独立验证，证明实际回放速度不超过安全配置；缺少该事实时服务会拒绝 Replay/Continue。

## RM75 本地轨迹与 O6 夹握联动

`rm_trajectory_o6_grasp` 专用于以下受控流程：只读加载 RM75 厂商 JSONL/TXT 轨迹、严格解析
七个关节、生成推导时间戳并离线校验，随后在 Live 授权下低速移动到首点，并通过
`rm_send_project(project_type=1, only_save=0)` 交给控制器保存并执行。只有观察到指定程序
先进入运行态、再正常结束，并重新校验机械臂状态、六维力和 O6 readiness 后，程序才会
按照“转矩 FC16、速度 FC16、最后位置 FC16”的顺序发出六关节抓握命令，并通过
FC04 实际位置确认最终抓握姿态。
业务层不包含 RM_API2，O6 协议也不直接访问厂商 SDK。

先执行不会连接设备的 DryRun。`--sample-period-ms` 必须填写这份原始轨迹实际采集周期，
不能猜测；以下握姿数值只用于演示离线参数校验，不代表现场安全姿态：

```bash
./RM/build/rm_trajectory_o6_grasp \
  RM/data/trajectories/sourceTXT/123.txt \
  --sample-period-ms 100 \
  --grip-pose 140,100,55,55,55,55 \
  --grip-speed 0,0,0,0,0,0 \
  --grip-torque 0,0,0,0,0,0 \
  --safety-config RM/config/drag_teach_safety.json
```

DryRun 会输出轨迹点数、时长、文件字节数、SHA-256、七轴首末点和校验峰值，但不会连接
RM75 或 O6。默认安全配置把 O6 速度和转矩上限锁为 0，所以上述命令只能用于验证离线
调用链，不能作为真实抓握参数。真实运行前必须由现场人员确认采样周期、厂商整数单位比例
以及六个位置/速度/转矩值，并在安全配置中逐项审核：

- `hardware_execution_enabled`、急停确认、安全限制和工作空间确认；
- `trajectory_sample_period_confirmed` 与 `vendor_raw_units_per_degree_confirmed`；
- `vendor_raw_units_per_degree`，用于把 TXT 中的厂商整数显式转换为 degree；
- `max_speed_percent` 与命令行的首点/轨迹速度；
- `maximum_o6_speed_command` 与 `maximum_o6_torque_command`；
- `maximum_origin_move_delta_deg`，用于限制当前位置到轨迹首点的每轴最大距离；
- 七轴跃变、速度、加速度以及六维力/力矩上限。

Live 还会要求源轨迹是不可写的普通文件，并在移动前和上传前复核文件大小与 SHA-256。
Linux 现场确认文件内容无误后可显式移除写权限：

```bash
chmod a-w RM/data/trajectories/sourceTXT/123.txt
```

`--program-id` 必须是控制器中尚未占用的编号；程序会在首点运动前查询，并在上传前再次
查询，拒绝覆盖已有控制器工程。程序不会自动删除控制器中执行后保存的工程。

O6 readiness 会显式初始化工具端口 1、Modbus RTU 主站、115200 baud、24V 和有效超时，
再验证右手 `0x27/'R'`（或左手 `0x28/'L'`）、六自由度、0..255 的位置/转矩/速度、温度
不高于 70 摄氏度且六个故障码均为 0。RM_API2 v1.1.5 的多输入寄存器路径在当前控制器上
返回 `-2`，因此 O6 状态采用 FC04 单寄存器顺序读取；通信或写入失败后不自动重试。

只演示 O6 张开再抓握、完全不移动 RM75 时，可人工运行：

```bash
./RM/build/rm_o6_grip_demo --live-grip
```

演示程序保持 O6 供电，不触发上电标定；先验证安全张开，再以低速、低转矩抓握并逐项
读回位置。当前现场食指位置保持在已弯曲的 `0`，其余五个关节执行可见往返。

只有上述现场条件均已确认后，操作者才可手工增加 `--live`：

```bash
./RM/build/rm_trajectory_o6_grasp \
  RM/data/trajectories/sourceTXT/123.txt \
  --sample-period-ms <已确认的实际采样周期毫秒> \
  --origin-speed-percent 5 \
  --plan-speed-percent 5 \
  --program-id 1 \
  --o6-side right \
  --grip-pose <拇指弯曲>,<拇指横摆>,<食指>,<中指>,<无名指>,<小指> \
  --grip-speed <拇指弯曲>,<拇指横摆>,<食指>,<中指>,<无名指>,<小指> \
  --grip-torque <拇指弯曲>,<拇指横摆>,<食指>,<中指>,<无名指>,<小指> \
  --safety-config RM/config/drag_teach_safety.json \
  --live
```

Live 仍会在运动前显示文件校验值、首点、O6 身份和当前状态，并要求键盘二次确认；任一
设备读取、轨迹校验、首点距离、控制器运行状态或握姿读回不满足条件即 fail-closed。
SIGINT/SIGTERM 会请求 RM75 慢停，并阻止后续控制器上传或 O6 抓握。成功任务会在
`RM/build/runtime_data/trajectory_playback_reports/` 新建不可覆盖 JSON 报告；其中周期和
抖动字段只表示主机对控制器程序状态的轮询，不代表控制器内部逐点下发周期。
硬件程序只允许人工运行，自动化测试只使用 Mock。

把 CSV 转成 C++ TCP waypoint 表：

```bash
./RM/build/rm_trajectory_convert \
  RM/build/runtime_data/trajectories/sessions/SESSION_ID/synchronized_samples.csv
```

默认输出：

```text
RM/build/runtime_data/trajectories/converted/demo_pick_path_tcp_path.cpp
```

维护边界：

- `src/realman/realman_arm_adapter.cpp` 是 RM SDK 调用的唯一新实现边界。
- `core/drag_teach_service` 负责状态机、安全门控、采样、停止、校验和保存编排。
- `modules/trajectory` 负责唯一目录、CSV、SHA-256、manifest 和转换，不访问硬件。

## 后续维护边界

当前不要为其他机械臂品牌新增 `drivers/<vendor>/`，也不要把 JSON runner 改成多品牌选择器。后续如果继续做机械臂与灵心巧手联动，应优先围绕 RM75 和 O6 的安全 dry-run、只读状态检查、低速小步运动、日志和人工确认流程扩展。
