# L6 灵巧手关节开发 README

本文档面向 `linkerhand-cpp-sdk` 中 `L6` 灵巧手的关节功能开发、联调和二次封装。

目标是帮助开发者快速回答下面几个问题：

- `L6` 在 SDK 里怎么初始化
- 6 个关节的控制数据应该怎么组织
- 常用的关节开发接口有哪些
- 调试关节动作时推荐什么流程
- 如果要做更底层的 L6 能力开发，应该改哪里

## 1. 模型概览

`L6`/`O6` 是 6 关节型号，在当前 SDK 中由以下文件承载：

- 统一 API 入口：`include/LinkerHandApi.h`
- L6 专用实现：`include/LinkerHandL6.h`
- 公共枚举定义：`include/Common.h`

其中：

- `LinkerHandApi` 适合业务开发和上层应用集成
- `linkerhand::hand::L6Hand` 适合做 L6 专项能力开发、协议扩展和底层调试

## 2. L6 关节数据约定

从 `include/LinkerHandL6.h` 可知，L6 的核心关节控制帧按 `1-6` 号关节组织，长度固定为 `6`：

- 关节位置：`JOINT_POSITION`
- 力矩限制：`TORQUE_LIMIT`
- 关节速度：`JOINT_SPEED`
- 关节加速度：`JOINT_ACCELERATION`

这意味着所有面向 L6 的关节数组都应默认按 6 维组织，例如：

```cpp
std::vector<uint8_t> pose   = {128, 128, 128, 128, 128, 128};
std::vector<uint8_t> speed  = {120, 120, 120, 120, 120, 120};
std::vector<uint8_t> torque = {180, 180, 180, 180, 180, 180};
```

推荐把这 6 个槽位统一命名为：

```text
joint1, joint2, joint3, joint4, joint5, joint6
```

注意：

- 当前仓库头文件明确了 L6 是 6 关节控制，但没有在公开文档里给出每个关节与具体手指机构的一一对应关系
- 在没有机械图纸、固件协议表或标定记录之前，建议先按 `joint1 ~ joint6` 做协议层开发
- 实机联调时再建立 “关节编号 -> 手指/自由度名称” 的映射表，避免早期写死错误语义

## 3. 推荐开发分层

### 3.1 上层业务开发

如果你的目标是：

- 下发关节角度
- 读取关节状态
- 做抓握动作编排
- 在现有 SDK 上快速做应用

优先使用 `LinkerHandApi`。

示例：

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

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

    std::vector<uint8_t> speed  = {100, 100, 100, 100, 100, 100};
    std::vector<uint8_t> torque = {180, 180, 180, 180, 180, 180};
    std::vector<uint8_t> open   = {255, 255, 255, 255, 255, 255};
    std::vector<uint8_t> close  = {0, 0, 0, 0, 0, 0};

    hand.setSpeed(speed);
    hand.setTorque(torque);

    hand.fingerMove(open);
    std::this_thread::sleep_for(std::chrono::seconds(1));

    hand.fingerMove(close);
    std::this_thread::sleep_for(std::chrono::seconds(1));

    auto state = hand.getState();
    std::cout << "joint state size = " << state.size() << std::endl;

    return 0;
}
```

### 3.2 底层功能开发

如果你的目标是：

- 调整 L6 专用协议
- 增加加速度、回零、参数保存等底层能力
- 扩展设备信息、触觉、故障处理逻辑
- 直接排查 L6 驱动收发问题

优先查看并使用 `linkerhand::hand::L6Hand`。

`include/LinkerHandL6.h` 中已经暴露出以下 L6 专用能力：

- `setJointPositions`
- `setJointPositionArc`
- `setSpeed`
- `setTorque`
- `setAcceleration`
- `reHome`
- `getCurrentStatus`
- `getCurrentStatusArc`
- `getSpeed`
- `getTorque`
- `getAcceleration`
- `getFaultCode`
- `clearFaultCode`
- `getForce`
- `getTemperature`
- `getVersion`
- `getSerialNumber`
- `getHardwareVersion`
- `getMechanicalVersion`
- `factoryReset`
- `saveParameters`

这部分适合做 L6 关节驱动能力的主开发面。

## 4. 关节开发建议流程

建议按下面顺序推进，能明显降低实机风险。

### 第一步：先确认基础连通

确认以下信息一致：

- 手型：`LINKER_HAND::L6`
- 左右手：`HAND_TYPE::LEFT` 或 `HAND_TYPE::RIGHT`
- 通信口：默认构造使用 `COMM_CAN_0`
- 如果是指定 CAN 通道，使用带 `canChannel` 和 `baudrate` 的构造函数

例如：

```cpp
LinkerHandApi hand(LINKER_HAND::L6, HAND_TYPE::RIGHT, "can0", 1000000);
```

建议首次联调先读信息，不先运动：

```cpp
auto version = hand.getVersion();
auto temp = hand.getTemperature();
auto fault = hand.getFaultCode();
```

### 第二步：单关节试动

第一次动关节时，不要六轴同时大幅运动，建议：

- 速度设置为低值
- 力矩限制设置为中低值
- 一次只改变一个关节
- 每次动作后读取状态和故障码

示例：

```cpp
std::vector<uint8_t> pose = {128, 128, 128, 128, 128, 128};
std::vector<uint8_t> speed = {60, 60, 60, 60, 60, 60};
std::vector<uint8_t> torque = {100, 100, 100, 100, 100, 100};

hand.setSpeed(speed);
hand.setTorque(torque);

pose[0] = 150;
hand.fingerMove(pose);
```

### 第三步：建立关节语义映射

实机调试时，记录每个关节对应的实际运动效果，例如：

```text
joint1 -> 拇指弯曲
joint2 -> 食指弯曲
joint3 -> 中指弯曲
joint4 -> 无名指弯曲
joint5 -> 小指弯曲
joint6 -> 某个联动或姿态自由度
```

上面只是示例格式，不代表当前仓库已经确认的最终定义。

你应该在实机动作和结构设计图一致后，再把这张映射表固化到代码或文档中。

### 第四步：封装动作层

完成关节映射后，再继续封装：

- 张开
- 握拳
- 两指捏合
- 三指捏合
- 循环抓握
- 自定义姿态组

推荐做法是先保留底层 `joint1 ~ joint6` 数组，再在上层增加动作语义层，避免后面硬件版本调整时要大面积改代码。

## 5. 关节控制接口说明

### 5.1 位置控制

位置控制是最基础的开发入口：

```cpp
std::vector<uint8_t> pose = {128, 128, 128, 128, 128, 128};
hand.fingerMove(pose);
```

说明：

- 数组长度必须与 L6 关节数一致
- 每个元素通常是 `0~255` 的范围值
- 实际机械含义需要结合 L6 机构定义和标定结果确认

### 5.2 弧度控制

如果你希望从更接近运动学的方式开发，可使用弧度接口：

```cpp
std::vector<double> pose_arc = {0.0, 0.2, 0.2, 0.2, 0.2, 0.1};
hand.fingerMoveArc(pose_arc);
```

适用场景：

- 上层算法输出弧度值
- 需要统一不同型号的角度控制接口
- 后续要接逆运动学或手势规划模块

### 5.3 速度控制

```cpp
std::vector<uint8_t> speed = {80, 80, 80, 80, 80, 80};
hand.setSpeed(speed);
```

建议：

- 联调初期使用低速
- 只有在动作方向和关节映射确认后，再逐步提速

### 5.4 力矩控制

```cpp
std::vector<uint8_t> torque = {120, 120, 120, 120, 120, 120};
hand.setTorque(torque);
```

建议：

- 初期避免满力矩
- 抓取脆弱目标时，应单独下调相关关节力矩

### 5.5 状态读取

```cpp
auto state = hand.getState();
auto speed_now = hand.getSpeed();
auto torque_now = hand.getTorque();
auto temperature = hand.getTemperature();
auto fault = hand.getFaultCode();
```

推荐在每个动作阶段都保留状态回读，便于排查：

- 命令已发送但未执行
- 关节卡死
- 力矩不足
- 温度异常
- 驱动报错

## 6. L6 底层开发关注点

如果你准备继续开发 `L6Hand` 实现，建议重点关注下面几类内容。

### 6.1 协议帧定义

L6 的协议属性在 `include/LinkerHandL6.h` 的 `L6FrameProperty` 中定义。

这里是新增关节能力、读写设备参数、扩展触觉读取时最关键的入口之一。

### 6.2 接收线程

`L6Hand` 内部包含：

- `receiveThread`
- `receiveResponse()`
- 多类缓存数据，如 `joint_position`、`joint_speeds`、`error_codes`

如果后续要增强稳定性，优先检查：

- 收包线程退出条件
- 多线程读写锁粒度
- 状态缓存是否会被旧帧覆盖
- 异常 CAN 帧的过滤和日志

### 6.3 设备参数与持久化

L6 还包含以下维护类能力：

- `reHome()`
- `factoryReset()`
- `saveParameters()`

这类操作有潜在风险，建议只在以下情况下使用：

- 已完成关节方向和零点确认
- 已有明确的标定流程
- 能保证操作后立即复核状态

## 7. 实机调试建议

### 建议做的事

- 先单关节，再多关节
- 先低速低力矩，再逐步放开
- 每次动作后都读取 `fault code`
- 为每个 joint 建立独立测试用例
- 记录左右手差异
- 将“协议编号”和“机械语义”分开管理

### 不建议直接做的事

- 首次上电就六关节满量程动作
- 未确认零点就做连续动作组
- 没有限速限力矩就抓取硬物
- 没有故障读取就长时间循环运行

## 8. 推荐的 L6 开发目录约定

如果你接下来要继续扩展仓库，推荐按下面方式组织：

```text
docs/
  L6-Joint-Development-README.md

examples/
  l6_joint_basic.cpp
  l6_joint_debug.cpp
  l6_action_group.cpp

tests/
  unit/
    test_L6JointMapping.cpp
    test_L6CommandPacking.cpp
```

这样可以把：

- 文档
- 示例
- 测试

三部分都围绕 L6 独立起来，后续维护会轻松很多。

## 9. 开发起步模板

下面这段代码适合作为 L6 关节开发的最小起步模板：

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

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

    std::vector<uint8_t> speed = {60, 60, 60, 60, 60, 60};
    std::vector<uint8_t> torque = {100, 100, 100, 100, 100, 100};
    std::vector<uint8_t> pose = {128, 128, 128, 128, 128, 128};

    hand.setSpeed(speed);
    hand.setTorque(torque);

    for (size_t joint = 0; joint < pose.size(); ++joint) {
        auto test_pose = pose;
        test_pose[joint] = 160;
        hand.fingerMove(test_pose);
        std::this_thread::sleep_for(std::chrono::milliseconds(800));

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

        std::cout << "joint " << joint + 1
                  << " test, state size = " << state.size()
                  << ", fault size = " << fault.size() << std::endl;

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

    return 0;
}
```

## 10. 当前文档结论

基于当前仓库代码，可以确认以下事实：

- `L6` 是 SDK 已支持型号
- `L6` 的关节控制长度为 6
- 统一开发入口是 `LinkerHandApi`
- L6 专用深度开发入口是 `linkerhand::hand::L6Hand`
- 目前仓库里缺少一份专门面向 L6 的清晰开发文档
- 目前公开代码没有直接给出 L6 六个关节的机械语义映射，需要通过实机标定补齐

如果后续你要继续推进，我建议下一步直接补两样内容：

1. `examples/l6_joint_basic.cpp`
2. `tests/unit/test_L6CommandPacking.cpp`

这样文档、示例和测试就能形成完整闭环。
