# L6 灵巧手应用开发手册

## 1. 适用范围

本文面向使用 `LinkerHand-CPP-SDK` 开发 `L6/O6` 灵巧手上位机应用的工程师，重点覆盖：

- 开发环境准备
- L6 关节与数据模型
- 典型控制流程
- 触觉、状态、故障数据读取
- 应用层开发建议

本文内容基于当前仓库中的公开头文件、示例程序和构建脚本整理，适合作为二次开发入口文档。

## 2. 项目内的 L6 开发入口

L6 的开发主要有两条路径：

| 路径 | 入口 | 适用场景 |
|---|---|---|
| 通用业务接口 | `LinkerHandApi` | 快速接入、动作控制、通用状态查询 |
| L6 型号专用接口 | `linkerhand::hand::L6Hand` | 需要重调零、加速度、设备信息等 L6 专有能力 |

关联文件：

- `include/LinkerHandApi.h`：统一 API 入口
- `include/HandFactory.h`：型号与通信工厂
- `include/IHand.h`：抽象接口与异常约束
- `include/LinkerHandL6.h`：L6/O6 专有能力定义
- `examples/toolset_example.cpp`：交互式示例
- `src/main.cpp`：最小示例

## 3. 开发环境准备

### 3.1 软件环境

- Linux
- `x86_64` 或 `aarch64`
- CMake `3.15+`
- GCC `7+` 或 Clang `5+`
- `pthread`

### 3.2 构建 SDK

```bash
mkdir build
cd build
cmake ..
make
```

也可以使用仓库脚本：

```bash
./script.sh
```

### 3.3 CAN 通信准备

L6 当前仓库中的常规接入方式以 CAN 为主。典型配置：

```bash
sudo modprobe can
sudo modprobe can_raw
sudo ip link set can0 type can bitrate 1000000
sudo ip link set can0 up
ip link show can0
```

如果设备接在 `can1`，对应替换接口名即可。

## 4. L6 运动与数据模型

### 4.1 关节顺序

`L6` 的位置控制数组长度固定为 `6`，关节顺序如下：

| 索引 | 含义 |
|---|---|
| `0` | 大拇指弯曲 |
| `1` | 大拇指横摆 |
| `2` | 食指弯曲 |
| `3` | 中指弯曲 |
| `4` | 无名指弯曲 |
| `5` | 小拇指弯曲 |

### 4.2 数值语义

- 位置范围：`0-255`
- 当前文档按仓库现有 API 说明采用如下语义：
  - `0`：更接近完全弯曲
  - `255`：更接近完全伸直

实际机械极限、零位、方向仍应以设备标定结果和设备手册为准。

### 4.3 L6 专有协议能力

从 `include/LinkerHandL6.h` 可见，L6 除基础控制外还定义了以下能力：

- 位置控制
- 速度设置
- 扭矩设置
- 加速度设置
- 重调零 `reHome()`
- 温度读取
- 故障码读取与清除
- 触觉矩阵读取
- 软件/硬件/机械版本读取
- 序列号读取
- 恢复出厂设置与参数保存

注意：这些能力并不都透出到 `LinkerHandApi`，部分需要直接使用 `L6Hand`。

## 5. 最小应用开发流程

### 5.1 使用统一 API 快速接入

```cpp
#include "LinkerHandApi.h"
#include <chrono>
#include <iostream>
#include <thread>

int main() {
    LinkerHandApi hand(LINKER_HAND::L6, HAND_TYPE::RIGHT, "can0", 1000000);

    std::vector<uint8_t> open_pose = {255, 255, 255, 255, 255, 255};
    std::vector<uint8_t> pinch_pose = {160, 180, 20, 255, 255, 255};

    hand.setSpeed(std::vector<uint8_t>(6, 180));
    hand.setTorque(std::vector<uint8_t>(6, 200));

    hand.fingerMove(open_pose);
    std::this_thread::sleep_for(std::chrono::milliseconds(800));

    hand.fingerMove(pinch_pose);
    std::this_thread::sleep_for(std::chrono::milliseconds(800));

    auto state = hand.getState();
    auto fault = hand.getFaultCode();

    std::cout << "joint count: " << state.size() << std::endl;
    std::cout << "fault count: " << fault.size() << std::endl;
    return 0;
}
```

推荐顺序：

1. 初始化对象
2. 设置速度和扭矩
3. 下发目标位姿
4. 等待动作完成
5. 读取状态/故障/温度/触觉数据

### 5.2 使用 L6 专有类获取更完整能力

```cpp
#include "LinkerHandL6.h"
#include <iostream>

int main() {
    linkerhand::hand::L6Hand hand(static_cast<uint32_t>(HAND_TYPE::RIGHT), "can0", 1000000);

    hand.setSpeed(std::vector<uint8_t>(6, 150));
    hand.setTorque(std::vector<uint8_t>(6, 180));
    hand.setAcceleration(std::vector<uint8_t>(6, 100));

    std::cout << "version: " << hand.getVersion() << std::endl;
    std::cout << "serial: " << hand.getSerialNumber() << std::endl;

    return 0;
}
```

当应用需要以下能力时，优先使用 `L6Hand`：

- `setAcceleration`
- `getAcceleration`
- `reHome`
- `getSerialNumber`
- `getHardwareVersion`
- `getMechanicalVersion`
- `factoryReset`
- `saveParameters`

## 6. 典型应用模式

### 6.1 预设动作控制

适合演示、产线联调、抓取动作库调用。可直接参考 `examples/toolset_example.cpp` 中的 `L6_POSE_1`。

建议做法：

- 将常用动作封装为固定长度的 `std::vector<uint8_t>(6)`
- 每个动作之间保留 `100-1000ms` 过渡时间
- 动作切换前检查故障码

### 6.2 状态轮询

适合闭环可视化、日志记录、简易监控。

推荐读取项：

- `getState()`
- `getTemperature()`
- `getFaultCode()`
- `getForce()`

轮询周期建议从 `50-200ms` 起步，根据总线和上位机实时性要求调整。

### 6.3 触觉驱动应用

L6 头文件中定义了拇指、食指、中指、无名指、小指以及掌部触觉相关帧。当前示例程序对 `getForce()` 的使用方式是按五指矩阵读取并打印。

适合场景：

- 抓取接触检测
- 滑移趋势判断
- 触碰事件触发
- 人机交互实验

开发建议：

- 先记录空载基线
- 用阈值做一层滤波与触发
- 不要直接把原始矩阵值作为动作切换唯一条件

### 6.4 故障恢复

推荐在每轮任务前后调用：

- `getFaultCode()`
- 必要时 `clearFaultCode()`

如果应用直接使用 `L6Hand`，可进一步封装：

- 故障码非零立即停止新动作下发
- 清故障后重新回到张开位
- 重复故障时提示人工检查通信、供电和机构

## 7. 接口使用约束

### 7.1 向量长度必须严格匹配

L6 常用数组长度均应按 `6` 个关节处理，包括：

- `fingerMove`
- `setSpeed`
- `setTorque`
- `setAcceleration`

不要把 L10/L20 的示例长度直接复用到 L6。

### 7.2 并非所有型号都支持所有接口

`IHand` 中大量方法默认会抛出 `UnsupportedFeatureException`。这意味着：

- 某个接口在 `LinkerHandApi` 中存在
- 不等于当前型号一定实现了该接口

对跨型号应用，建议统一加异常处理。

### 7.3 动作控制要保留缓冲时间

SDK 接口是命令式下发，不会自动等待动作完成。应用层需要自己控制：

- 动作间隔
- 超时
- 重试
- 状态回读

### 7.4 当前仓库中实现以预编译库为主

当前仓库公开了头文件与二进制库，很多实现细节位于 `lib/x86_64/` 和 `lib/aarch64/` 下的 `.so` 中。开发时应以：

- 头文件签名
- 示例代码
- 现有文档

作为第一依据。

## 8. 推荐的应用层封装方式

建议把应用分成三层：

1. 设备适配层：封装 `LinkerHandApi` 或 `L6Hand`
2. 动作层：定义张开、握持、捏取、释放等动作模板
3. 任务层：把动作与视觉、力控、机械臂流程连接

一个稳定的应用封装至少应包含：

- 初始化与连接检测
- 安全张开位
- 动作超时控制
- 故障查询与恢复
- 数据日志

## 9. 调试建议

建议先跑仓库自带示例，再做业务接入：

```bash
./script.sh
```

或：

```bash
mkdir build
cd build
cmake ..
make
./toolset_example
```

优先验证：

- CAN 接口是否正常
- `getVersion()` 是否有返回
- `fingerMove()` 是否能完成张开/闭合
- `getState()` 和 `getFaultCode()` 是否能持续读取

## 10. 相关文档

- `docs/API-Reference.md`
- `docs/TROUBLESHOOTING.md`
- `docs/FAQ.md`
- `examples/README.md`
