# 睿尔曼 RM75 测试项目架构说明

本文说明当前 RealMan/RM75-6F 测试代码的分层、模块职责和设计取舍。

## 总体结构

```text
linkerhand-cpp-sdk/
├── CMakeLists.txt
├── CMakePresets.json
├── examples/
│   ├── realman_minimal_demo.cpp
│   ├── realman_slow_motion_demo.cpp
│   ├── realman_joint_pose_config_demo.cpp
│   └── realman_metrics_test.cpp
├── tests/
│   ├── realman_hardware/
│   │   ├── realman_hw_test_common.hpp
│   │   ├── test_realman_connection.cpp
│   │   ├── test_realman_joint_telemetry.cpp
│   │   ├── test_realman_force_sensor.cpp
│   │   ├── test_realman_controller_state.cpp
│   │   ├── test_realman_motion_tracking.cpp
│   │   ├── test_realman_force_retreat.cpp
│   │   └── test_realman_force_retreat_hold.cpp
│   └── rm75_acceptance/
│       ├── test_rm75_acceptance_preflight.cpp
│       ├── test_rm75_acceptance_communication.cpp
│       └── README.md
├── docs/
│   ├── REALMAN_CLION_SETUP.md
│   ├── REALMAN_METRICS_TEST_README.md
│   └── REALMAN_TEST_ARCHITECTURE.md
└── reports/
    └── realman_hardware_tests/
```

## 分层职责

| 层级 | 目录/文件 | 职责 |
| --- | --- | --- |
| 构建层 | `CMakeLists.txt`, `CMakePresets.json` | 发现 RM_API2 SDK，导入 `libapi_cpp.so`，暴露 CLion target 和 CTest target |
| Demo 层 | `examples/` | 给使用者提供最小连接、慢速运动、源码调参、综合指标测试 |
| 测试基础设施 | `tests/realman_hardware/realman_hw_test_common.hpp` | 统一连接参数、SDK 生命周期、断言输出、Markdown 报告路径 |
| 独立硬件模块测试 | `tests/realman_hardware/test_*.cpp` | 每个模块独立 `main()`，分别测试连接、关节、控制器、力传感器、运动、力退让 |
| 文档验收层 | `tests/rm75_acceptance/` | 按 RM75-6F 测试文档组织验收项，自动执行只读项目，手动项目写入 README |
| 报告层 | `reports/realman_hardware_tests/` | 每个测试生成 latest 和带时间戳的 Markdown/CSV 报告 |

## 为什么每个测试都是独立可执行文件

机械臂不是普通纯软件模块，它有真实状态、网络连接、运动风险和 SDK 全局初始化状态。把每个测试做成独立 `main()` 有几个好处：

- 单个模块失败不会污染后续模块。
- 每个模块都独立执行 `rm_init`、`rm_create_robot_arm`、`rm_delete_robot_arm`、`rm_destroy/rm_destory`。
- 在 CLion 里可以像单元测试一样单独运行某个 target。
- CTest 可以通过 `RESOURCE_LOCK realman_robot` 防止多个测试并发抢同一台机械臂。
- 报告天然按模块拆分，便于定位是通信、关节、力传感器还是运动问题。

## 为什么区分只读测试和运动/手动测试

RM75-6F 测试文档要求不能绕过安全确认。项目里把测试分成三类：

| 类型 | 示例 target | 默认是否可自动运行 | 原因 |
| --- | --- | --- | --- |
| 只读测试 | `rm75_acceptance_preflight`, `realman_hw_test_force_sensor` | 是 | 只调用 `rm_get_*`，不会移动机械臂或修改控制器参数 |
| 低速运动测试 | `realman_hw_test_motion_tracking`, `realman_slow_motion_demo` | 否，需要用户明确要求 | 会发送运动指令，必须确认工作空间和急停 |
| 手动/外部设备测试 | 载荷、急停、重复定位、真实接触力控 | 否 | 需要人现场观察、治具、载荷、测量设备或监护 |

这样做的核心原因是：自动化测试应该提高效率，但不能替代物理安全确认。

## 为什么 `rm75_acceptance` 不直接复用所有硬件测试

`realman_hardware` 是“模块测试”，关注某一个硬件能力是否正常。

`rm75_acceptance` 是“验收流程”，关注测试文档里的阶段和数据字段是否被覆盖。

两者不是重复关系：

- `realman_hardware` 适合调试单个故障点。
- `rm75_acceptance` 适合给验收人员一个按文档组织的入口和报告。
- 验收层会复用公共基础设施，但不把所有模块串成强耦合流程。

## 为什么报告同时有 Markdown 和 CSV

Markdown 用于人读：

- 每个断言 PASS/FAIL。
- 每个模块的关键状态摘要。
- 适合直接发给测试人员或写入验收记录。

CSV 用于机器分析：

- `rm75_acceptance_communication` 按测试文档保留固定字段。
- 后续可以导入 Excel、Python、数据库或绘图工具。
- `latest.csv` 方便固定路径读取，带时间戳 CSV 方便追溯历史。

## 为什么 `err_len=1,codes=[0]` 不判失败

实测 RM75 控制器在 `rm_get_current_arm_state` 和 `rm_get_arm_all_state` 中可能返回：

```text
err_len=1, codes=[0]
```

这表示错误列表里只有一个零码占位，不是实际故障。代码里统一使用 `errListClear()`，只要错误码数组没有非零值，就视为无故障。这样可以避免把 SDK 的占位返回误判为验收失败。

## 为什么力退让持续模式使用绝对目标位姿

早期使用 `rm_movel_offset` 做连续退让时，当前控制器曾出现 `error=-7`。现在的持续模式流程是：

```text
读取当前 TCP 位姿
读取工作坐标系外力
根据外力方向和大小计算小步退让
生成 current_pose + delta 的绝对目标位姿
调用 rm_movel(target_pose, ...)
```

这样写的原因：

- 方向计算和数据来源都在工作坐标系语义下，减少坐标混用。
- 绝对目标位姿比连续 offset 在当前固件上更稳定。
- 每一步都能重新读取当前状态，便于遇到错误立即停止。
- 单步距离、速度和累计距离都有上限，方便人工测试。

## 为什么参数写在源码常量里

`realman_hw_test_force_retreat_hold.cpp` 里的阈值、速度、步长是 `constexpr` 常量。这样对初学者更直观：

- 打开一个文件就能看到所有关键参数。
- CLion 修改后重新编译即可验证。
- 避免命令行参数传错导致危险动作。
- 参数自检 `validateConfig()` 会在运动前拦截明显危险配置。

后续如果要做大量实验，可以再把这些常量抽成配置文件；当前阶段优先保证简单、可读、可控。

## 推荐开发流程

1. 修改代码前先确认目标模块。
2. 只读逻辑优先跑：

   ```bash
   cmake --build --preset rm75-acceptance-readonly-debug
   ctest --test-dir cmake-build-debug --output-on-failure --label-regex rm75
   ```

3. 修改运动逻辑时先构建，不自动运行：

   ```bash
   cmake --build --preset realman-hw-force-retreat-hold-debug
   ```

4. 运行任何运动测试前，确认工作空间、急停、载荷和人工监护。
