本文整理 `linkerhand-cpp-sdk` 当前仓库中的通信协议、分层结构、实际使用情况，以及如何与外部设备尤其是机械臂联动。

内容优先级遵循：

- 头文件和工厂逻辑
- 示例和 README
- 文档说明

如果某项结论来自头文件声明而非 `.so` 源码实现，本文会明确标注为“推断”。

---

## 1. 总览

当前 SDK 对外暴露的通信类型枚举在 [include/Common.h](../include/Common.h) 中定义：

- `COMM_CAN_0`
- `COMM_CAN_1`
- `COMM_MODBUS`
- `COMM_ETHERCAT`

见 [include/Common.h](../include/Common.h)。

从公开说明看，仓库希望支持三类协议：

- CAN
- ModBus
- EtherCAT


---

## 2. 通信分层

### 2.1 对外 API 层

业务代码通常从 `LinkerHandApi` 进入，构造时传入型号、左右手和通信类型：

```cpp
LinkerHandApi hand(LINKER_HAND::L10, HAND_TYPE::RIGHT, COMM_CAN_0);
```

这一层是业务层最应该依赖的统一入口。

### 2.2 型号工厂层

`HandFactory` 负责根据：

- 手型号
- 左/右手 ID
- 通信方式

实例化具体型号实现类。

从 [include/HandFactory.h](../include/HandFactory.h) 当前逻辑可确认：

- `CAN0/CAN1`：O6、L6、L7、L10、L20、L21、L25 都有分发路径
- `COMM_MODBUS`：只有 `L10` 会走 `ModbusL10Hand`
- `COMM_ETHERCAT`：枚举构造器会把通道映射为 `"ethercat"`，然后仍分发到各型号 `LxHand`

这意味着：

- “是否支持某协议”，先看 `HandFactory`
- “是否真能跑通”，还要继续看底层总线工厂和对应实现

### 2.3 底层传输抽象层

仓库把通信抽象分成两类：

- `ICanBus`
  - 面向 `CAN` 风格的帧式通信
  - 接口是 `send(data, can_id)` / `recv(id)`
- `ICommunication`
  - 面向 `ModBus` 这类寄存器式通信
  - 接口是 `send(data, id, start_address, num)` / `recv(id, start_address, num)`

对应文件：

- [include/ICanBus.h](../include/ICanBus.h)
- [include/ICommunication.h](../include/ICommunication.h)

这两个抽象本身很关键，因为它解释了仓库当前对协议的建模方式：

- CAN 和 EtherCAT 在这个仓库里都被包装成“类似 CAN 帧”的总线接口
- ModBus 被单独建模成寄存器读写接口

---

## 3. 协议分类与当前使用情况

## 3.1 CAN

### 3.1.1 当前定位

CAN 是当前仓库里最直接、最明确、最成熟的通信主路径。

证据：

- `COMM_CAN_0` / `COMM_CAN_1` 为一级枚举入口
- `HandFactory` 对几乎所有型号都支持 CAN 分发
- Linux 下有 `CanBus`
- Windows 下有 `PCANBus`
- 示例和 README 也都默认按 CAN 使用

相关文件：

- [include/HandFactory.h](../include/HandFactory.h)
- [include/CanBusFactory.h](../include/CanBusFactory.h)
- [include/CanBus.h](../include/CanBus.h)
- [include/PCANBus.h](../include/PCANBus.h)

### 3.1.2 平台划分

Linux：

- 使用 `CanBus`
- 基于 Linux SocketCAN
- 需要系统里存在 `can0`、`can1` 等接口

Windows：

- 使用 `PCANBus`
- 依赖 `PCANBasic`

### 3.1.3 典型用法

默认构造：

```cpp
LinkerHandApi hand(LINKER_HAND::L10, HAND_TYPE::RIGHT);
```

显式指定 CAN1：

```cpp
LinkerHandApi hand(LINKER_HAND::L10, HAND_TYPE::RIGHT, COMM_CAN_1);
```

显式指定字符串通道：

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

### 3.1.4 适用型号

从工厂头文件可直接确认，以下型号存在 CAN 路径：

- O6
- L6
- L7
- L10
- L20
- L21
- L25

### 3.1.5 使用建议

- 如果你只是要把灵巧手跑起来，优先选 CAN
- Linux 上先确保 `can0/can1` 被正确配置
- 同一进程内多个对象同时用 CAN 时，要明确每个对象的通道和设备 ID

### 3.1.6 常见检查项

Linux 常见检查：

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

---

## 3.2 ModBus

### 3.2.1 当前定位

ModBus 在这个仓库里不是通用主路径，而是一个明显带条件限制的接入分支。

从 [include/HandFactory.h](../include/HandFactory.h) 可确认：

- `COMM_MODBUS` 只会在 `USE_RMAN` 打开时生效
- 当前只有 `L10` 会被分发到 `ModbusL10Hand`
- 其他型号走 `COMM_MODBUS` 会报错

从 [include/ModbusLinkerHandL10.h](../include/ModbusLinkerHandL10.h) 可确认：

- `ModbusL10Hand` 继承自 `IHand`
- 内部通信对象是 `Communication::ModBus`

### 3.2.2 与瑞尔曼关联的modbus

`ModBus` 类并不是一个纯串口/纯 TCP 客户端实现。它直接持有：

- `rm_robot_handle *robot_handle`
- `RM_Service robotic_arm`

见 [include/ModBus.h](../include/ModBus.h)。

这说明当前仓库里的 ModBus 路径，设计上是通过睿尔曼机械臂 SDK 做中转，而不是一个完全独立的通用 ModBus 栈。

### 3.2.3 当前可确认的支持范围


- `L10 + USE_RMAN + COMM_MODBUS`


### 3.2.4 使用方式

表面 API 用法是：

```cpp
LinkerHandApi hand(LINKER_HAND::L10, HAND_TYPE::RIGHT, COMM_MODBUS);
```


### 3.2.5 适合什么场景

适合：

- 灵巧手挂在睿尔曼机械臂末端
- 由机械臂控制器充当 ModBus 主站
- 想通过机械臂统一控制末端设备

不适合直接假设为：

- 任意型号都能直接串口 ModBus 接入
- 不依赖机械臂环境即可独立运行

---

## 3.3 EtherCAT

### 3.3.1 当前定位

仓库里存在 `EtherCAT` 类，也存在 `COMM_ETHERCAT` 枚举和 README 说明，所以“目标能力”上 EtherCAT 是被纳入设计范围的。

相关文件：

- [include/EtherCAT.h](../include/EtherCAT.h)
- [include/HandFactory.h](../include/HandFactory.h)
- [README.md](../README.md)

### 3.3.2 公开头文件能确认什么

从 [include/EtherCAT.h](../include/EtherCAT.h) 可确认：

- 它继承自 `ICanBus`
- 使用 `ecrt.h`
- 内部定义了 PDO 映射
- 包含位置、速度、力矩等目标/实际值字段

说明它的设计目标不是“把 EtherCAT 重新包装成 ModBus”，而是用一个与 `ICanBus` 兼容的接口去承载 EtherCAT 读写。



## 4. 支持矩阵

基于当前头文件，建议按下面理解：

| 协议 | 公开枚举入口 | 当前头文件可确认的型号支持 | 运行依赖 | 当前稳定性判断 |
|---|---|---|---|---|
| CAN | `COMM_CAN_0` / `COMM_CAN_1` | O6、L6、L7、L10、L20、L21、L25 | Linux SocketCAN 或 Windows PCAN | 最高 |
| ModBus | `COMM_MODBUS` | 仅 L10 | `USE_RMAN` + 睿尔曼机械臂 SDK | 条件可用 |
| EtherCAT | `COMM_ETHERCAT` | 工厂层面对多型号放行 | `USE_ETHERCAT` + EtherCAT 主站环境 | 需额外验证 |

---

## 5. 与机械臂通讯的几种方式

这里的“和机械臂实现通讯”至少有三种不同语义，实际工程里不要混淆。

## 5.1 方式 A：PC 直接控制机械手，机械臂单独控制

架构：

- 上位机进程 A 控制灵巧手
- 上位机进程 B 或同进程另一模块控制机械臂
- 两边在应用层做同步

特点：

- 结构最清晰
- 灵巧手协议和机械臂协议完全解耦
- 最适合调试

适用：

- 灵巧手走 CAN
- 机械臂走自身 TCP/IP SDK
- 需要先分别验证两边

## 5.2 方式 B：通过睿尔曼机械臂末端生态协议接入灵巧手

这是仓库里最值得关注的机械臂联动路径。

RM SDK 公开接口支持：

- `rm_set_rm_plus_mode`
- `rm_set_rm_plus_touch`
- `rm_get_rm_plus_base_info`
- `rm_get_rm_plus_state_info`

见 [third_party/Robotic_Arm/include/rm_interface.h](../third_party/Robotic_Arm/include/rm_interface.h)。

同时，RM 的实时 UDP 上报结构 `rm_realtime_arm_joint_state_t` 里直接带有：

- `rm_plus_state`
- `plus_base_info`
- `plus_state_info`

见 [third_party/Robotic_Arm/include/rm_define.h](../third_party/Robotic_Arm/include/rm_define.h)。

这意味着：

- 机械臂控制器可以把末端设备状态纳入统一实时上报
- 末端灵巧手的基础信息、实时位置、电流、触觉、力矩等都有统一结构可读

`rm_plus_base_info_t` 里明确包含：

- 是否支持力控 `force`
- 是否支持触觉 `touch`
- 触觉数量 `touch_num`

`rm_plus_state_info_t` 里明确包含：

- `pos`
- `speed`
- `angle`
- `current`
- `normal_force`
- `tangential_force`
- `touch_data`
- `force`

这条路径特别适合：

- 灵巧手作为机械臂末端执行器
- 需要机械臂和手的状态在同一条实时通道里统一采集

## 5.3 方式 C：通过机械臂的 Modbus RTU / TCP 主站能力访问末端设备

RM SDK 还公开了更底层的外设通信接口，包括：

- `rm_set_controller_rs485_mode`
- `rm_set_tool_rs485_mode`
- `rm_read_modbus_rtu_*`
- `rm_write_modbus_rtu_*`
- `rm_read_modbus_tcp_*`
- `rm_write_modbus_tcp_*`

见 [third_party/Robotic_Arm/include/rm_interface.h](../third_party/Robotic_Arm/include/rm_interface.h)。

其中关键点：

- 控制器端 RS485 可配置为 Modbus RTU 主站/从站
- 工具端 RS485 可配置为 `RTU 主站 / 灵巧手模式 / 夹爪模式`

这说明机械臂控制器本身就能承担末端总线主站角色。

这条路径适合：

- 你明确知道末端设备的寄存器协议
- 你希望通过机械臂统一做外设接入
- 你不想让 PC 额外直连末端设备

---

## 6. 推荐的机械臂联动方案

## 6.1 如果目标是“尽快跑通”

推荐：

- 灵巧手：CAN
- 机械臂：RM 自身 TCP/IP SDK
- 在上位机应用层做动作编排

原因：

- 调试边界清晰
- 出问题时容易定位是手、臂还是协同逻辑

## 6.2 如果目标是“机械臂统一管理末端设备”

推荐优先顺序：

1. 末端生态协议 `rm_plus`
2. 工具端 RS485 + Modbus RTU
3. 控制器端 Modbus TCP/RTU

原因：

- `rm_plus` 已经把“灵巧手是末端设备”这个语义内建进机械臂 SDK
- 还能直接得到触觉、力矩、状态等统一数据结构

## 6.3 如果目标是“仓库内直接复用现有 ModBus 手实现”

当前只能保守地按下面理解：

- 型号：L10
- 前提：`USE_RMAN`
- 路径：`LinkerHandApi(..., COMM_MODBUS)`

如果你的手不是 L10，当前仓库不建议直接假设可以复用这条路径。

---

## 7. 接入流程建议

## 7.1 CAN 直连灵巧手

1. 配置 `can0/can1`
2. 用 `LinkerHandApi(..., COMM_CAN_0)` 或字符串 `"can0"` 创建设备
3. 先读版本、状态、故障码
4. 再做动作和传感采集

## 7.2 通过机械臂末端生态协议接入

1. `rm_init(...)`
2. `rm_create_robot_arm(ip, port)`
3. 开启 `rm_plus` 模式
4. 如需要触觉，设置 `rm_set_rm_plus_touch(...)`
5. 读取 `rm_get_rm_plus_base_info(...)`
6. 周期性读取 `rm_get_rm_plus_state_info(...)` 或启用实时 UDP 推送

这个流程是从头文件能力推导出的推荐路径。

## 7.3 通过机械臂 Modbus 主站访问末端设备

1. 建立机械臂连接
2. 设置控制器端或工具端 RS485 模式
3. 准备 RTU/TCP 参数结构体
4. 用 `rm_read_modbus_*` / `rm_write_modbus_*` 读写设备
5. 在应用层把寄存器语义映射成手的业务语义

---

## 8. 当前仓库里的限制和坑

## 8.1 EtherCAT 头文件路径存在可疑点

`CanBusFactory` 当前 Linux 分支会直接返回 `CanBus`，导致 `EtherCAT` 分支逻辑看起来不可达。

建议：

- 不要只看 README 就认为 EtherCAT 已稳定可用
- 真要落地 EtherCAT，先单独修这一层

## 8.2 ModBus 不是“全型号通用”

当前工厂里 ModBus 只给了 `L10` 路径，且依赖 `USE_RMAN`。

## 8.3 字符串构造器与枚举构造器能力不完全一致

从 `HandFactory` 可见：

- 枚举构造器会把 `COMM_ETHERCAT` 映射成 `"ethercat"`
- 字符串构造器仅当字符串里包含 `"can"` 才走 CAN 路径
- 字符串 `"ethercat"` 当前没有对应分支

因此：

- 若你要尝试 EtherCAT，不应假设字符串重载一定能工作

## 8.4 大量实现封装在预编译库中

仓库很多核心行为在 `.so` 中，头文件只能说明：

- 有这个接口
- 有这条调用路径

但不能 100% 证明：

- 现场设备一定可用
- 某些组合一定完全实现

---

## 9. 实际选型建议

如果你的目标是：

- 单独控制手
- 快速验证功能
- 先把手跑通

选：

- CAN

如果你的目标是：

- 手挂在睿尔曼机械臂末端
- 希望机械臂统一管理末端设备
- 需要联合采集末端实时状态

优先选：

- `rm_plus` 末端生态协议

如果你的目标是：

- 明确基于寄存器协议接入末端
- 想复用机械臂的 RS485 / Modbus 能力

选：

- 工具端 RS485 + Modbus RTU
- 或控制器端 Modbus TCP/RTU

如果你的目标是：

- 工业现场总线统一化
- 已经有 EtherCAT 主站体系

可以评估：

- EtherCAT

但在当前仓库里应先补通信层验证，不建议直接投入业务。

---

## 10. 一页结论

- 现阶段最稳的主路径是 CAN。
- ModBus 在这个仓库里是“依赖睿尔曼机械臂 SDK 的 L10 特化路径”，不是全型号通用路径。
- EtherCAT 在设计上被纳入支持范围，但当前头文件分发逻辑存在疑点，落地前必须补验证。
- 如果要和机械臂联动，最值得优先考虑的是 RM 的 `rm_plus` 末端生态协议，因为它已经把末端设备状态、触觉、力矩和实时上报结构统一起来了。

