1547 lines
55 KiB
Markdown
1547 lines
55 KiB
Markdown
# Eskin Finger SDK — 架构设计文档
|
||
|
||
## 1. 概述
|
||
|
||
**Eskin Finger SDK** 是一款高性能压敏薄膜传感器数据采集 SDK,用于采集手套式压力传感器阵列的分布力数据(五指 + 掌心共 28 个传感器模组)。采用三层架构设计:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────┐
|
||
│ C++ Wrapper (RAII + Callback) │
|
||
│ cpp/include/eskin_finger_sdk.hpp │
|
||
│ cpp/src/eskin_finger_sdk.cpp │
|
||
├─────────────────────────────────────────────────┤
|
||
│ C ABI Layer (FFI) │
|
||
│ include/eskin_finger_sdk.h │
|
||
│ src/ffi/c_api.rs │
|
||
├─────────────────────────────────────────────────┤
|
||
│ Rust Core (Channel + Thread) │
|
||
│ src/lib.rs, device.rs, stream.rs │
|
||
│ src/channel.rs, types.rs, error.rs │
|
||
│ src/protocol.rs, src/transport.rs │
|
||
└─────────────────────────────────────────────────┘
|
||
```
|
||
|
||
- **Rust Core**:UART 通信、协议解析、数据采集线程、Channel 通信、错误管理
|
||
- **C ABI Layer**:通过 `#[repr(C)]` 和 `extern "C"` 提供稳定的 FFI 接口
|
||
- **C++ Wrapper**:RAII 封装、异常处理、回调机制
|
||
|
||
---
|
||
|
||
## 2. 通信协议概述
|
||
|
||
### 2.1 UART 物理层
|
||
|
||
| 参数 | 值 |
|
||
|------|-----|
|
||
| 波特率 | 115200 |
|
||
| 数据位 | 8 bit |
|
||
| 停止位 | 1 位 |
|
||
| 校验位 | NONE |
|
||
| 通信模式 | 请求-应答(主从模式) |
|
||
|
||
### 2.2 帧结构
|
||
|
||
#### 读请求帧
|
||
|
||
```
|
||
┌────────┬──────────┬────────────┬────────┬────────┬──────────┬──────────────┬───────────┬────────┐
|
||
│ 起始符 │ 数据长度 │ 设备地址 │ 预留 │功能码 │ 起始地址 │ 读取数据长度 │ CRC-8 │ │
|
||
│ 0x55AA │ 2B(LE) │ 1B │ 0x00 │ 0xFB │ 4B(LE) │ 2B(LE) │ 1B │ │
|
||
├────────┼──────────┼────────────┼────────┼────────┼──────────┼──────────────┼──────────┼────────┤
|
||
│data[0-1]│data[2-3]│ data[4] │data[5] │data[6] │data[7-10]│ data[11-12] │ data[13] │ │
|
||
└────────┴──────────┴────────────┴────────┴────────┴──────────┴──────────────┴──────────┘
|
||
```
|
||
|
||
#### 读应答帧
|
||
|
||
```
|
||
┌────────┬──────────┬────────────┬────────┬────────────┬──────────┬───────────┬──────────┬────────┬────────┐
|
||
│ 起始符 │ 数据长度 │ 设备地址 │ 预留 │ 功能码 │ 起始地址 │ 读取数据 │ CRC-8 │ 状态 │ │
|
||
│ 0xAA55 │ 2B(LE) │ 1B │ 0x00 │ 0x80+func │ 4B(LE) │ 2B(LE) │ 1B │ 1B │ │
|
||
├────────┼──────────┼────────────┼────────┼────────────┼──────────┼───────────┼──────────┼────────┤
|
||
│data[0-1]│data[2-3]│ data[4] │data[5] │ data[6] │data[7-10]│data[11-12]│data[N+13]│data[13]│ │
|
||
└────────┴──────────┴────────────┴────────┴────────────┴──────────┴───────────┴──────────┴────────┘
|
||
```
|
||
|
||
#### 写请求帧
|
||
|
||
```
|
||
┌────────┬──────────┬────────────┬────────┬────────┬──────────┬──────────────┬───────────┬────────┐
|
||
│ 起始符 │ 数据长度 │ 设备地址 │ 预留 │功能码 │ 起始地址 │ 写入数据长度 │ 写入数据 │ CRC-8 │
|
||
│ 0x55AA │ 2B(LE) │ 1B │ 0x00 │ 0x79 │ 4B(LE) │ 2B(LE) │ NB │ 1B │
|
||
├────────┼──────────┼────────────┼────────┼────────┼──────────┼──────────────┼──────────┼────────┤
|
||
│data[0-1]│data[2-3]│ data[4] │data[5] │data[6] │data[7-10]│ data[11-12] │ data[] │data[N+13]│
|
||
└────────┴──────────┴────────────┴────────┴────────┴──────────┴──────────────┴──────────┴────────┘
|
||
```
|
||
|
||
#### 写应答帧
|
||
|
||
```
|
||
┌────────┬──────────┬────────────┬────────┬────────────┬──────────┬───────────┬────────┬────────┐
|
||
│ 起始符 │ 数据长度 │ 设备地址 │ 预留 │ 功能码 │ 起始地址 │ 返回字节数 │ CRC-8 │ 状态 │
|
||
│ 0xAA55 │ 2B(LE) │ 1B │ 0x00 │ 0x80+func │ 4B(LE) │ 2B(LE) │ 1B │ 1B │
|
||
├────────┼──────────┼────────────┼────────┼────────────┼──────────┼───────────┼────────┼────────┤
|
||
│data[0-1]│data[2-3]│ data[4] │data[5] │ data[6] │data[7-10]│data[11-12]│data[14]│data[13]│
|
||
└────────┴──────────┴────────────┴────────┴────────────┴──────────┴───────────┴────────┴────────┘
|
||
```
|
||
|
||
### 2.3 状态码
|
||
|
||
| 值 | 含义 |
|
||
|----|------|
|
||
| `0x0000` | 正常(成功) |
|
||
| `0x0001` | 读取长度超限 |
|
||
| `0x0002` | 长度错误 |
|
||
| `0x0003` | 无效地址 |
|
||
| `0x0004` | 只读寄存器 |
|
||
|
||
### 2.4 功能码
|
||
|
||
| 功能码 | 方向 | 说明 |
|
||
|--------|------|------|
|
||
| `0xFB` | 请求 → | 读请求 |
|
||
| `0x79` | 请求 → | 写请求 |
|
||
| `0x80 + func` | ← 应答 | 读/写应答(`0xFF` = 读应答,`0xF9` = 写应答) |
|
||
|
||
---
|
||
|
||
## 3. 传感器模组布局
|
||
|
||
### 3.1 模组分布(28 个传感器模组)
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────┐
|
||
│ 传感器手套布局 │
|
||
│ │
|
||
│ 大拇指 食指 中指 无名指 小拇指 │
|
||
│ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ │
|
||
│ │指甲│ │指甲│ │指甲│ │指甲│ │指甲│ │
|
||
│ ├──┤ ├──┤ ├──┤ ├──┤ ├──┤ │
|
||
│ │指尖│ │指尖│ │指尖│ │指尖│ │指尖│ │
|
||
│ ├──┤ ├──┤ ├──┤ ├──┤ ├──┤ │
|
||
│ │中节│ │中节│ │中节│ │中节│ │中节│ │
|
||
│ ├──┤ ├──┤ ├──┤ ├──┤ ├──┤ │
|
||
│ │近节│ │近节│ │近节│ │近节│ │近节│ │
|
||
│ └──┘ └──┘ └──┘ └──┘ └──┘ │
|
||
│ │
|
||
│ ┌─────────────────┐ │
|
||
│ │ 掌心 1-8 │ │
|
||
│ │ (8个模组) │ │
|
||
│ └─────────────────┘ │
|
||
└──────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 3.2 模组编号(0-27)
|
||
|
||
| 编号 | 模组 | 编号 | 模组 |
|
||
|------|------|------|------|
|
||
| 0 | 大拇指-近节 | 14 | 无名指-近节 |
|
||
| 1 | 大拇指-中节 | 15 | 无名指-中节 |
|
||
| 2 | 大拇指-指尖 | 16 | 无名指-指尖 |
|
||
| 3 | 大拇指-指甲 | 17 | 无名指-指甲 |
|
||
| 4 | 食指-近节 | 18 | 小拇指-近节 |
|
||
| 5 | 食指-中节 | 19 | 小拇指-中节 |
|
||
| 6 | 食指-指尖 | 20 | 小拇指-指尖 |
|
||
| 7 | 食指-指甲 | 21 | 小拇指-指甲 |
|
||
| 8 | 中指-近节 | 22 | 掌心 1 |
|
||
| 9 | 中指-中节 | 23 | 掌心 2 |
|
||
| 10 | 中指-指尖 | 24 | 掌心 3 |
|
||
| 11 | 中指-指甲 | 25 | 掌心 4 |
|
||
| 12 | 无名指-近节 | 26 | 掌心 5 |
|
||
| 13 | 无名指-中节 | 27 | 掌心 6 |
|
||
|
||
> 注:掌心 7、掌心 8 为扩展编号 28-29(部分配置可能不包含)。
|
||
|
||
---
|
||
|
||
## 4. 寄存器地址映射
|
||
|
||
### 4.1 系统信息寄存器
|
||
|
||
| 地址 | 名称 | 权限 | 数据类型 | 说明 |
|
||
|------|------|------|---------|------|
|
||
| `0x0000-0x0001` | 系列号 | RO | `u32` | 设备唯一序列号 |
|
||
| `0x000F` | 固件版本 | RO | `u16` | `0x0000` = 系列号,`0x0001` = 软件版本号 |
|
||
| `0x0010` | 标定组 | RO | `u16` | 标定参数组,用于确认产品状态 |
|
||
| `0x0011` | 模组有效状态 | RO | `u16` | bit0-3: 大拇指近/中/指/甲,bit4-7: 食指近/中/指/甲 |
|
||
| `0x0012` | 传感器尺寸 L_LINE | RO | `u16` | 宽度阵列点数 |
|
||
| `0x0013` | 传感器尺寸 H_LINE | RO | `u16` | 长度阵列点数 |
|
||
| `0x0014` | 分布力点数(大拇指) | RO | `u16` | 分布力字节数 = 该值 × 3 |
|
||
| `0x0015` | 分布力点数(食指) | RO | `u16` | 同上 |
|
||
| `0x0016` | 分布力点数(中指) | RO | `u16` | 同上 |
|
||
| `0x0017` | 分布力点数(无名指) | RO | `u16` | 同上 |
|
||
| `0x0018` | 分布力点数(小拇指) | RO | `u16` | 同上 |
|
||
| `0x0023` | 当前模块位置 | RO | `u16` | 0-3: 大拇指,4-7: 食指,... 20-27: 掌心 |
|
||
|
||
### 4.2 配置寄存器
|
||
|
||
| 地址 | 名称 | 权限 | 数据类型 | 说明 |
|
||
|------|------|------|---------|------|
|
||
| `0x0030-0x0031` | 产品配置 1 | RW | `u32` | bit0: 自动回传使能(1=使能,0=不使能,默认0) |
|
||
| `0x0032-0x0033` | 产品配置 2 | RW | `u32` | 保留 |
|
||
| `0x0034-0x003F` | 大拇指分布力点数 | RO | `u16[6]` | 近节、中节、指尖、指甲各模组的分布力点数 |
|
||
| `0x0040-0x004B` | 食指分布力点数 | RO | `u16[6]` | 同上 |
|
||
| `0x004C-0x0057` | 中指分布力点数 | RO | `u16[6]` | 同上 |
|
||
| `0x0058-0x0059` | 无名指分布力点数 | RO | `u16[2]` | 近节、中节 |
|
||
| `0x005A-0x005B` | 无名指分布力点数 | RO | `u16[2]` | 指尖、指甲 |
|
||
| `0x005C-0x0067` | 掌心分布力点数 | RO | `u16[6]` | 掌心8-1(倒序) |
|
||
|
||
### 4.3 数据寄存器
|
||
|
||
| 地址 | 名称 | 权限 | 数据大小 | 说明 |
|
||
|------|------|------|---------|------|
|
||
| `0x0500-0x05A7` | 合力数据 | RO | 168B | 28个模组,每个 3×2B (fx,fy,fz 为 i16) |
|
||
| `0x0700-0x0737` | 模组错误码 | RO | 56B | 28个模组,每个 2B |
|
||
| `0x1000-0x11FF` | 大拇指分布力 | RO | 变长 | 三维分布力 (fx,fy,fz 每点 3B) |
|
||
| `0x1200-0x13FF` | 食指分布力 | RO | 变长 | 同上 |
|
||
| `0x1400-0x15FF` | 中指分布力 | RO | 变长 | 同上 |
|
||
| `0x1600-0x17FF` | 无名指分布力 | RO | 变长 | 同上 |
|
||
| `0x1800-0x19FF` | 小拇指分布力 | RO | 变长 | 同上 |
|
||
| `0x1A00-0x1BFF` | 掌心分布力(1-4) | RO | 变长 | 同上 |
|
||
| `0x1C00-0x1DFF` | 掌心分布力(5-8) | RO | 变长 | 同上 |
|
||
| `0x2000-0x2FFF` | 处理后数值 | RO | 变长 | 原始地址 + 0x3000 |
|
||
| `0x8000-0x8FFF` | 标定参数 | RW | 变长 | 每点 32 参数,最多 128 点 |
|
||
|
||
---
|
||
|
||
## 5. 目录结构
|
||
|
||
```
|
||
eskin-finger-sdk/
|
||
├── Cargo.toml # Rust crate 配置
|
||
├── CMakeLists.txt # C++ wrapper 构建配置
|
||
├── docs/
|
||
│ └── ARCHITECTURE.md # 本文档
|
||
├── include/
|
||
│ └── eskin_finger_sdk.h # C 头文件(公共 API)
|
||
├── cpp/
|
||
│ ├── include/
|
||
│ │ └── eskin_finger_sdk.hpp # C++ wrapper 头文件
|
||
│ └── src/
|
||
│ └── eskin_finger_sdk.cpp # C++ wrapper 实现
|
||
├── src/
|
||
│ ├── lib.rs # Rust 库入口
|
||
│ ├── main.rs # 示例/测试入口
|
||
│ ├── types.rs # 数据建模(传感器结构体、枚举)
|
||
│ ├── error.rs # 错误类型定义
|
||
│ ├── protocol.rs # UART 协议解析(帧编解码、CRC-8-ITU)
|
||
│ ├── transport.rs # 串口传输层(读写抽象)
|
||
│ ├── register.rs # 寄存器地址映射与读写封装
|
||
│ ├── device.rs # 设备管理核心
|
||
│ ├── channel.rs # Channel 设计与管理
|
||
│ ├── stream.rs # 数据流控制(分布力采集)
|
||
│ ├── config.rs # 设备配置管理
|
||
│ └── ffi/
|
||
│ ├── mod.rs # FFI 模块声明
|
||
│ └── c_api.rs # C ABI 实现
|
||
└── examples/
|
||
└── basic_usage.rs # Rust 使用示例
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Crate 依赖 (Cargo.toml)
|
||
|
||
```toml
|
||
[package]
|
||
name = "eskin-finger-sdk"
|
||
version = "0.1.0"
|
||
edition = "2024"
|
||
|
||
[lib]
|
||
name = "eskin_finger_sdk"
|
||
crate-type = ["lib", "cdylib", "staticlib"]
|
||
|
||
[dependencies]
|
||
# Channel 通信 — 高性能多生产者多消费者通道
|
||
crossbeam-channel = "0.5"
|
||
|
||
# 错误处理 — 结构化错误类型
|
||
thiserror = "2"
|
||
|
||
# 日志 — 可选的日志输出
|
||
log = "0.4"
|
||
|
||
# 日志前端
|
||
fern = "0.7"
|
||
|
||
# 时间处理 — 高精度时间戳
|
||
chrono = "0.4"
|
||
|
||
# 序列化 — 配置和数据的序列化支持
|
||
serde = { version = "1", features = ["derive"] }
|
||
serde_json = "1"
|
||
|
||
# 串口传输 — UART/USB-Serial 通信
|
||
serialport = "4"
|
||
|
||
# FFI 类型支持(C 类型兼容)
|
||
libc = "0.2"
|
||
|
||
# UUID — 设备序列号等唯一标识
|
||
uuid = { version = "1", features = ["v4"] }
|
||
|
||
[dev-dependencies]
|
||
# 测试工具
|
||
env_logger = "0.11"
|
||
```
|
||
|
||
### 依赖说明
|
||
|
||
| 依赖 | 用途 | 必要性 |
|
||
|------|------|--------|
|
||
| `crossbeam-channel` | 高性能 bounded/unbounded channel,支持 `try_send`、`recv_timeout` | **核心** |
|
||
| `thiserror` | 派生 `std::error::Error`,简化错误类型定义 | **核心** |
|
||
| `log` / `fern` | 日志抽象层 + 日志前端,方便调试 | 推荐 |
|
||
| `chrono` | 高精度时间戳(微秒/纳秒级) | 推荐 |
|
||
| `serde` / `serde_json` | 配置文件序列化、调试输出 | 推荐 |
|
||
| `serialport` | 串口打开、读写、清空缓冲区,承载设备 UART 协议 | **核心** |
|
||
| `libc` | FFI 类型兼容(`c_char`, `c_int` 等) | **FFI 必需** |
|
||
| `uuid` | 设备唯一标识符生成 | 可选 |
|
||
|
||
### 编译产物
|
||
|
||
```toml
|
||
crate-type = ["lib", "cdylib", "staticlib"]
|
||
```
|
||
|
||
- `lib`:Rust 内部使用
|
||
- `cdylib`:生成 `.dylib`/`.so`/`.dll`,供 C/C++ 动态链接
|
||
- `staticlib`:生成 `.a`/`.lib`,供 C/C++ 静态链接
|
||
|
||
---
|
||
|
||
## 7. 数据建模
|
||
|
||
### 7.1 核心数据类型
|
||
|
||
```rust
|
||
// src/types.rs
|
||
|
||
/// 三维力向量(分布力中的单个测力点)
|
||
#[repr(C)]
|
||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
||
pub struct Force3D {
|
||
pub fx: i16, // X 方向力 (原始值)
|
||
pub fy: i16, // Y 方向力 (原始值)
|
||
pub fz: i16, // Z 方向力 (原始值)
|
||
}
|
||
|
||
/// 三维力向量(浮点版本,用于合力)
|
||
#[repr(C)]
|
||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
||
pub struct Force3F {
|
||
pub fx: f32, // X 方向力 (N)
|
||
pub fy: f32, // Y 方向力 (N)
|
||
pub fz: f32, // Z 方向力 (N)
|
||
}
|
||
|
||
/// 传感器模组标识
|
||
#[repr(C)]
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||
pub enum SensorModule {
|
||
// 大拇指
|
||
ThumbProximal = 0, // 大拇指近节
|
||
ThumbMiddle = 1, // 大拇指中节
|
||
ThumbTip = 2, // 大拇指指尖
|
||
ThumbNail = 3, // 大拇指指甲
|
||
|
||
// 食指
|
||
IndexProximal = 4,
|
||
IndexMiddle = 5,
|
||
IndexTip = 6,
|
||
IndexNail = 7,
|
||
|
||
// 中指
|
||
MiddleProximal = 8,
|
||
MiddleMiddle = 9,
|
||
MiddleTip = 10,
|
||
MiddleNail = 11,
|
||
|
||
// 无名指
|
||
RingProximal = 12,
|
||
RingMiddle = 13,
|
||
RingTip = 14,
|
||
RingNail = 15,
|
||
|
||
// 小拇指
|
||
PinkyProximal = 16,
|
||
PinkyMiddle = 17,
|
||
PinkyTip = 18,
|
||
PinkyNail = 19,
|
||
|
||
// 掌心
|
||
Palm1 = 20,
|
||
Palm2 = 21,
|
||
Palm3 = 22,
|
||
Palm4 = 23,
|
||
Palm5 = 24,
|
||
Palm6 = 25,
|
||
Palm7 = 26,
|
||
Palm8 = 27,
|
||
}
|
||
|
||
/// 单个传感器模组的分布力数据
|
||
#[repr(C)]
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct DistributionForce {
|
||
pub module: SensorModule,
|
||
/// 分布力点数(每个点 3 字节: fx, fy, fz)
|
||
pub point_count: u16,
|
||
/// 分布力数据(原始 i8 值,长度 = point_count * 3)
|
||
pub points: Vec<Force3D>,
|
||
}
|
||
|
||
/// 单个传感器模组的合力数据
|
||
#[repr(C)]
|
||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||
pub struct CombinedForce {
|
||
pub module: SensorModule,
|
||
/// X 方向合力 (i16 原始值)
|
||
pub fx: i16,
|
||
/// Y 方向合力 (i16 原始值)
|
||
pub fy: i16,
|
||
/// Z 方向合力 (i16 原始值)
|
||
pub fz: i16,
|
||
}
|
||
|
||
/// 一帧完整的传感器采样数据
|
||
#[repr(C)]
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct FingerSample {
|
||
/// 时间戳(微秒,SDK 侧采集时间)
|
||
pub timestamp_us: u64,
|
||
/// 采样序列号(SDK 侧递增)
|
||
pub sequence: u32,
|
||
/// 28 个模组的合力数据
|
||
pub combined_forces: [CombinedForce; 28],
|
||
/// 28 个模组的分布力数据(可选,根据需求拉取)
|
||
pub distribution_forces: Vec<DistributionForce>,
|
||
/// 28 个模组的错误码(每个模组 2 字节)
|
||
pub module_errors: [u16; 28],
|
||
}
|
||
|
||
/// 单个测力点的简化数据(用于 C ABI 传递分布力)
|
||
#[repr(C)]
|
||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
||
pub struct ForcePoint {
|
||
pub fx: i8,
|
||
pub fy: i8,
|
||
pub fz: i8,
|
||
}
|
||
```
|
||
|
||
### 7.2 设备配置
|
||
|
||
```rust
|
||
// src/config.rs
|
||
|
||
/// 设备配置
|
||
#[repr(C)]
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct DeviceConfig {
|
||
/// 设备地址(默认 0x34)
|
||
pub device_addr: u8,
|
||
|
||
/// 是否启用自动回传模式
|
||
/// bit0: 1=使能, 0=不使能(默认0)
|
||
pub auto_transmit: bool,
|
||
|
||
/// 是否读取分布力数据(合力数据始终读取)
|
||
pub read_distribution: bool,
|
||
|
||
/// 样本丢弃策略
|
||
pub drop_policy: DropPolicy,
|
||
}
|
||
|
||
/// 样本丢弃策略
|
||
#[repr(C)]
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum DropPolicy {
|
||
/// 丢弃新数据 — 通道满时丢弃当前新样本,保留旧数据
|
||
DropNewest,
|
||
/// 丢弃旧数据 — 通道满时丢弃队列头部的旧数据,保留最新样本
|
||
DropOldest,
|
||
}
|
||
```
|
||
|
||
### 7.3 系统信息
|
||
|
||
```rust
|
||
/// 设备系统信息(从只读寄存器读取)
|
||
#[repr(C)]
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct DeviceInfo {
|
||
/// 系列号
|
||
pub serial_number: u32,
|
||
/// 固件版本
|
||
pub firmware_version: u16,
|
||
/// 标定组
|
||
pub calibration_group: u16,
|
||
/// 模组有效状态(bit 掩码)
|
||
pub module_active_status: u16,
|
||
/// 传感器宽度阵列点数 (L_LINE)
|
||
pub l_line: u16,
|
||
/// 传感器长度阵列点数 (H_LINE)
|
||
pub h_line: u16,
|
||
/// 产品配置 1
|
||
pub product_config_1: u32,
|
||
/// 产品配置 2
|
||
pub product_config_2: u32,
|
||
}
|
||
```
|
||
|
||
### 7.4 协议帧类型
|
||
|
||
```rust
|
||
// src/protocol.rs
|
||
|
||
/// 帧起始符
|
||
pub const FRAME_START_REQUEST: u16 = 0x55AA;
|
||
pub const FRAME_START_RESPONSE: u16 = 0xAA55;
|
||
|
||
/// 功能码
|
||
pub const FUNC_READ: u8 = 0xFB;
|
||
pub const FUNC_WRITE: u8 = 0x79;
|
||
pub const FUNC_RESPONSE_READ: u8 = 0xFF; // 0x80 + 0xFB
|
||
pub const FUNC_RESPONSE_WRITE: u8 = 0xF9; // 0x80 + 0x79
|
||
|
||
/// 设备状态码(应答帧中的 status 字段)
|
||
#[repr(C)]
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum DeviceStatus {
|
||
Success = 0x0000, // 正常
|
||
ReadLenExceeded = 0x0001, // 读取长度超限
|
||
LengthError = 0x0002, // 长度错误
|
||
InvalidAddress = 0x0003, // 无效地址
|
||
ReadOnlyRegister = 0x0004, // 只读寄存器
|
||
}
|
||
|
||
/// 读请求帧结构
|
||
pub struct ReadRequest {
|
||
pub device_addr: u8,
|
||
pub start_addr: u32,
|
||
pub read_byte_count: u16,
|
||
}
|
||
|
||
/// 写请求帧结构
|
||
pub struct WriteRequest {
|
||
pub device_addr: u8,
|
||
pub start_addr: u32,
|
||
pub data: Vec<u8>,
|
||
}
|
||
|
||
/// 读应答帧结构
|
||
pub struct ReadResponse {
|
||
pub device_addr: u8,
|
||
pub start_addr: u32,
|
||
pub data: Vec<u8>,
|
||
pub status: DeviceStatus,
|
||
}
|
||
|
||
/// 写应答帧结构
|
||
pub struct WriteResponse {
|
||
pub device_addr: u8,
|
||
pub start_addr: u32,
|
||
pub return_byte_count: u16,
|
||
pub status: DeviceStatus,
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 错误处理
|
||
|
||
### 8.1 SDK 错误码(C ABI)
|
||
|
||
```rust
|
||
// src/error.rs
|
||
|
||
/// C ABI 兼容的错误码
|
||
#[repr(C)]
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
pub enum SdkErrorCode {
|
||
Success = 0,
|
||
InvalidPointer = 1,
|
||
DeviceNotFound = 2,
|
||
DeviceAlreadyOpen = 3,
|
||
NotInitialized = 4,
|
||
AlreadyStreaming = 5,
|
||
NotStreaming = 6,
|
||
ConfigError = 7,
|
||
IoError = 8,
|
||
Timeout = 9,
|
||
ChannelClosed = 10,
|
||
InternalError = 11,
|
||
BufferOverflow = 12,
|
||
InvalidParameter = 13,
|
||
CrcError = 14,
|
||
FrameError = 15,
|
||
ProtocolError = 16,
|
||
DeviceError = 17, // 设备返回非零状态码
|
||
}
|
||
|
||
/// Rust 内部错误类型
|
||
#[derive(Debug, thiserror::Error)]
|
||
pub enum SdkError {
|
||
#[error("Device not found: {0}")]
|
||
DeviceNotFound(String),
|
||
|
||
#[error("Device already open")]
|
||
DeviceAlreadyOpen,
|
||
|
||
#[error("SDK not initialized")]
|
||
NotInitialized,
|
||
|
||
#[error("Already streaming")]
|
||
AlreadyStreaming,
|
||
|
||
#[error("Not streaming")]
|
||
NotStreaming,
|
||
|
||
#[error("Configuration error: {0}")]
|
||
ConfigError(String),
|
||
|
||
#[error("I/O error: {0}")]
|
||
IoError(#[from] std::io::Error),
|
||
|
||
#[error("Read timeout")]
|
||
Timeout,
|
||
|
||
#[error("Channel closed")]
|
||
ChannelClosed,
|
||
|
||
#[error("Internal error: {0}")]
|
||
InternalError(String),
|
||
|
||
#[error("Buffer overflow — dropped {0} samples")]
|
||
BufferOverflow(u64),
|
||
|
||
#[error("Invalid parameter: {0}")]
|
||
InvalidParameter(String),
|
||
|
||
#[error("CRC error: expected 0x{expected:02X}, got 0x{actual:02X}")]
|
||
CrcError { expected: u8, actual: u8 },
|
||
|
||
#[error("Frame error: {0}")]
|
||
FrameError(String),
|
||
|
||
#[error("Protocol error: {0}")]
|
||
ProtocolError(String),
|
||
|
||
#[error("Device error: status 0x{0:04X}")]
|
||
DeviceError(u16),
|
||
}
|
||
```
|
||
|
||
### 8.2 设备侧错误码
|
||
|
||
```rust
|
||
/// 设备应答帧中的状态码
|
||
impl DeviceStatus {
|
||
pub fn to_error(&self) -> Option<SdkError> {
|
||
match self {
|
||
DeviceStatus::Success => None,
|
||
DeviceStatus::ReadLenExceeded => Some(SdkError::DeviceError(0x0001)),
|
||
DeviceStatus::LengthError => Some(SdkError::DeviceError(0x0002)),
|
||
DeviceStatus::InvalidAddress => Some(SdkError::DeviceError(0x0003)),
|
||
DeviceStatus::ReadOnlyRegister => Some(SdkError::DeviceError(0x0004)),
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 9. CRC-8-ITU 校验
|
||
|
||
```rust
|
||
// src/protocol.rs
|
||
|
||
/// CRC-8-ITU 多项式: x^8 + x^2 + x + 1 (0x07)
|
||
/// 初始值: 0x00
|
||
pub fn crc8_itu(data: &[u8]) -> u8 {
|
||
let mut crc: u8 = 0x00;
|
||
for &byte in data {
|
||
crc ^= byte;
|
||
for _ in 0..8 {
|
||
if crc & 0x80 != 0 {
|
||
crc = (crc << 1) ^ 0x07;
|
||
} else {
|
||
crc <<= 1;
|
||
}
|
||
}
|
||
}
|
||
crc
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 10. Channel 设计
|
||
|
||
### 10.1 通道架构
|
||
|
||
```
|
||
┌──────────────────────┐
|
||
│ UART I/O Thread │
|
||
│ (串口数据采集线程) │
|
||
└──────┬───────┬───────┘
|
||
│ │
|
||
sample_tx event_tx
|
||
│ │
|
||
┌────────────▼─┐ ┌──▼───────────┐
|
||
│ Data Channel │ │ Event Channel │
|
||
│ (bounded) │ │ (bounded) │
|
||
│ capacity: 64 │ │ capacity: 64 │
|
||
└────────────┬─┘ └──┬────────────┘
|
||
│ │
|
||
sample_rx event_rx
|
||
│ │
|
||
┌────────────▼───────▼────────────┐
|
||
│ User-facing API │
|
||
│ read_sample() / callback │
|
||
└──────────────────────────────────┘
|
||
|
||
┌──────────────────────┐
|
||
│ User Thread │
|
||
└──────┬───────────────┘
|
||
│
|
||
cmd_tx
|
||
│
|
||
┌──────▼──────────────┐
|
||
│ Command Channel │
|
||
│ (bounded) │
|
||
│ capacity: 16 │
|
||
└──────┬───────────────┘
|
||
│
|
||
cmd_rx
|
||
┌──────▼──────────────┐
|
||
│ UART I/O Thread │
|
||
└──────────────────────┘
|
||
```
|
||
|
||
### 10.2 通道定义
|
||
|
||
```rust
|
||
// src/channel.rs
|
||
|
||
use crossbeam_channel::{bounded, Sender, Receiver};
|
||
use std::sync::atomic::{AtomicU64, Ordering};
|
||
|
||
/// 设备命令
|
||
#[derive(Debug, Clone)]
|
||
pub enum DeviceCommand {
|
||
StartStream,
|
||
StopStream,
|
||
SetConfig(DeviceConfig),
|
||
/// 读取指定寄存器
|
||
ReadRegister { addr: u32, length: u16 },
|
||
/// 写入指定寄存器
|
||
WriteRegister { addr: u32, data: Vec<u8> },
|
||
Shutdown,
|
||
}
|
||
|
||
/// 设备事件
|
||
#[derive(Debug, Clone)]
|
||
pub enum DeviceEvent {
|
||
Disconnected(String),
|
||
IoError(String),
|
||
ProtocolError(String),
|
||
ConfigApplied,
|
||
StreamStarted,
|
||
StreamStopped,
|
||
SampleDropped { count: u64 },
|
||
ModuleError { module: SensorModule, error_code: u16 },
|
||
}
|
||
|
||
/// 通道管理器
|
||
pub struct ChannelManager {
|
||
// 数据通道 — 传感器采样数据
|
||
pub sample_tx: Sender<FingerSample>,
|
||
pub sample_rx: Receiver<FingerSample>,
|
||
|
||
// 命令通道 — 控制命令
|
||
pub cmd_tx: Sender<DeviceCommand>,
|
||
pub cmd_rx: Receiver<DeviceCommand>,
|
||
|
||
// 事件通道 — 事件通知
|
||
pub event_tx: Sender<DeviceEvent>,
|
||
pub event_rx: Receiver<DeviceEvent>,
|
||
|
||
// 丢弃计数器
|
||
pub dropped_samples: AtomicU64,
|
||
|
||
// 丢弃策略
|
||
pub drop_policy: DropPolicy,
|
||
}
|
||
|
||
impl ChannelManager {
|
||
pub fn new(
|
||
sample_capacity: usize, // 建议 64(传感器帧率较低)
|
||
cmd_capacity: usize, // 建议 16
|
||
event_capacity: usize, // 建议 64
|
||
drop_policy: DropPolicy,
|
||
) -> Self {
|
||
let (sample_tx, sample_rx) = bounded(sample_capacity);
|
||
let (cmd_tx, cmd_rx) = bounded(cmd_capacity);
|
||
let (event_tx, event_rx) = bounded(event_capacity);
|
||
|
||
Self {
|
||
sample_tx, sample_rx,
|
||
cmd_tx, cmd_rx,
|
||
event_tx, event_rx,
|
||
dropped_samples: AtomicU64::new(0),
|
||
drop_policy,
|
||
}
|
||
}
|
||
|
||
/// 非阻塞发送样本,根据策略处理溢出
|
||
pub fn send_sample(&self, sample: FingerSample) {
|
||
match self.drop_policy {
|
||
DropPolicy::DropNewest => {
|
||
if self.sample_tx.try_send(sample).is_err() {
|
||
self.dropped_samples.fetch_add(1, Ordering::Relaxed);
|
||
}
|
||
}
|
||
DropPolicy::DropOldest => {
|
||
if let Err(crossbeam_channel::TrySendError::Full(_)) =
|
||
self.sample_tx.try_send(sample)
|
||
{
|
||
let _ = self.sample_rx.try_recv();
|
||
if self.sample_tx.try_send(sample).is_err() {
|
||
self.dropped_samples.fetch_add(1, Ordering::Relaxed);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 带超时的样本读取
|
||
pub fn recv_sample(&self, timeout_ms: u32) -> Result<FingerSample, SdkError> {
|
||
let timeout = std::time::Duration::from_millis(timeout_ms as u64);
|
||
self.sample_rx
|
||
.recv_timeout(timeout)
|
||
.map_err(|_| SdkError::Timeout)
|
||
}
|
||
|
||
/// 获取丢弃的样本数量
|
||
pub fn dropped_count(&self) -> u64 {
|
||
self.dropped_samples.load(Ordering::Relaxed)
|
||
}
|
||
}
|
||
```
|
||
|
||
### 10.3 通道容量配置
|
||
|
||
| 通道 | 容量 | 频率 | 说明 |
|
||
|------|------|------|------|
|
||
| 数据通道 (sample) | 64 | 低频 (~10-100Hz) | bounded,传感器帧率决定 |
|
||
| 命令通道 (cmd) | 16 | 低频 (~1Hz) | bounded,固定 |
|
||
| 事件通道 (event) | 64 | 低频 (~1Hz) | bounded,固定 |
|
||
|
||
---
|
||
|
||
## 11. 设备管理
|
||
|
||
### 11.1 设备状态机
|
||
|
||
```
|
||
┌──────────┐ init() ┌──────────────┐
|
||
│ Idle │ ──────────────►│ Initialized │
|
||
└──────────┘ └──────┬───────┘
|
||
▲ │
|
||
│ open(port)
|
||
│ │
|
||
│ ┌────▼───────┐
|
||
│ │ Connected │
|
||
│ └────┬───────┘
|
||
│ │
|
||
│ start_stream()
|
||
│ │
|
||
│ ┌────▼───────┐
|
||
│ │ Streaming │
|
||
│ └────┬───────┘
|
||
│ │
|
||
│ stop_stream()
|
||
│ │
|
||
│ ┌────▼───────┐
|
||
│ close() │ Connected │
|
||
│◄───────────────────────└────────────┘
|
||
│
|
||
shutdown()
|
||
```
|
||
|
||
### 11.2 核心设备结构
|
||
|
||
```rust
|
||
// src/device.rs
|
||
|
||
pub struct EskinDeviceInner {
|
||
pub info: DeviceInfo,
|
||
pub config: DeviceConfig,
|
||
pub channels: ChannelManager,
|
||
pub state: DeviceState,
|
||
pub transport: Box<dyn SerialTransport>,
|
||
pub stream_handle: Option<JoinHandle<()>>,
|
||
pub cmd_handle: Option<JoinHandle<()>>,
|
||
pub shutdown_flag: Arc<AtomicBool>,
|
||
pub sequence_counter: AtomicU32,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||
pub enum DeviceState {
|
||
Idle,
|
||
Initialized,
|
||
Connected,
|
||
Streaming,
|
||
Error,
|
||
}
|
||
```
|
||
|
||
### 11.3 传输层抽象
|
||
|
||
```rust
|
||
// src/transport.rs
|
||
|
||
/// 串口传输抽象(方便测试时 mock)
|
||
pub trait SerialTransport: Send + Sync {
|
||
/// 发送原始字节
|
||
fn write(&mut self, data: &[u8]) -> Result<usize, SdkError>;
|
||
/// 接收原始字节(带超时)
|
||
fn read(&mut self, buf: &mut [u8], timeout: Duration) -> Result<usize, SdkError>;
|
||
/// 清空接收缓冲区
|
||
fn flush_rx(&mut self) -> Result<(), SdkError>;
|
||
}
|
||
|
||
/// 真实串口实现
|
||
pub struct SerialPortTransport {
|
||
port: Box<dyn serialport::SerialPort>,
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 12. 数据采集流程
|
||
|
||
### 12.1 合力数据采集(标准模式)
|
||
|
||
```
|
||
用户调用 start_stream()
|
||
│
|
||
▼
|
||
┌──────────────────────────────────────────┐
|
||
│ UART I/O Thread 启动 │
|
||
│ │
|
||
│ loop { │
|
||
│ 1. 检查 shutdown_flag │
|
||
│ 2. 检查 cmd_rx 是否有新命令 │
|
||
│ 3. 发送读请求: 0x0500, 长度 168 字节 │
|
||
│ (28 模组 × 6 字节合力) │
|
||
│ 4. 接收读应答帧 │
|
||
│ 5. 校验 CRC-8-ITU │
|
||
│ 6. 解析 28 个 CombinedForce │
|
||
│ 7. 组装 FingerSample │
|
||
│ 8. sample_tx.try_send(sample) │
|
||
│ 9. 如果配置了 read_distribution: │
|
||
│ a. 依次发送读请求获取各指分布力 │
|
||
│ b. 填充 distribution_forces │
|
||
│ } │
|
||
└──────────────────────────────────────────┘
|
||
```
|
||
|
||
### 12.2 分布力数据采集
|
||
|
||
分布力数据按模组分段读取,每段地址范围不同:
|
||
|
||
| 传感器 | 地址范围 | 每点字节数 |
|
||
|--------|---------|-----------|
|
||
| 大拇指 | `0x1000-0x11FF` | 3 (fx, fy, fz) |
|
||
| 食指 | `0x1200-0x13FF` | 3 |
|
||
| 中指 | `0x1400-0x15FF` | 3 |
|
||
| 无名指 | `0x1600-0x17FF` | 3 |
|
||
| 小拇指 | `0x1800-0x19FF` | 3 |
|
||
| 掌心 1-4 | `0x1A00-0x1BFF` | 3 |
|
||
| 掌心 5-8 | `0x1C00-0x1DFF` | 3 |
|
||
|
||
> 分布力字节数 = point_count × 3,point_count 从 `0x0014-0x0018` 等寄存器读取。
|
||
|
||
### 12.3 命令处理流程
|
||
|
||
```
|
||
cmd_rx 接收到命令
|
||
│
|
||
▼
|
||
match command {
|
||
StartStream → 设置 streaming 标志,开始采集循环
|
||
StopStream → 清除 streaming 标志
|
||
SetConfig(cfg) → 写入产品配置寄存器 (0x0030)
|
||
ReadRegister → 发送读请求,返回结果
|
||
WriteRegister → 发送写请求,检查状态码
|
||
Shutdown → 设置 shutdown_flag,退出线程
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 13. C ABI 设计
|
||
|
||
### 13.1 Opaque Pointer 模式
|
||
|
||
```rust
|
||
// src/ffi/c_api.rs
|
||
|
||
/// 不透明指针,C 端只能通过 API 函数操作
|
||
#[repr(C)]
|
||
pub struct EskinDevice {
|
||
inner: Box<EskinDeviceInner>,
|
||
}
|
||
|
||
/// 全局 SDK 状态
|
||
static SDK_STATE: OnceLock<Mutex<SdkState>> = OnceLock::new();
|
||
|
||
struct SdkState {
|
||
initialized: bool,
|
||
}
|
||
```
|
||
|
||
### 13.2 C API 函数签名
|
||
|
||
```c
|
||
// include/eskin_finger_sdk.h
|
||
|
||
#ifndef ESKIN_FINGER_SDK_H
|
||
#define ESKIN_FINGER_SDK_H
|
||
|
||
#include <stdint.h>
|
||
#include <stddef.h>
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
// ─── 错误码 ──────────────────────────────────────────
|
||
typedef enum {
|
||
ESKIN_SUCCESS = 0,
|
||
ESKIN_INVALID_POINTER = 1,
|
||
ESKIN_DEVICE_NOT_FOUND = 2,
|
||
ESKIN_DEVICE_ALREADY_OPEN = 3,
|
||
ESKIN_NOT_INITIALIZED = 4,
|
||
ESKIN_ALREADY_STREAMING = 5,
|
||
ESKIN_NOT_STREAMING = 6,
|
||
ESKIN_CONFIG_ERROR = 7,
|
||
ESKIN_IO_ERROR = 8,
|
||
ESKIN_TIMEOUT = 9,
|
||
ESKIN_CHANNEL_CLOSED = 10,
|
||
ESKIN_INTERNAL_ERROR = 11,
|
||
ESKIN_BUFFER_OVERFLOW = 12,
|
||
ESKIN_INVALID_PARAMETER = 13,
|
||
ESKIN_CRC_ERROR = 14,
|
||
ESKIN_FRAME_ERROR = 15,
|
||
ESKIN_PROTOCOL_ERROR = 16,
|
||
ESKIN_DEVICE_ERROR = 17,
|
||
} EskinErrorCode;
|
||
|
||
// ─── 数据类型 ────────────────────────────────────────
|
||
typedef struct { int16_t fx, fy, fz; } EskinForce3D;
|
||
|
||
typedef struct {
|
||
uint8_t module_index; // 0-27
|
||
int16_t fx, fy, fz;
|
||
} EskinCombinedForce;
|
||
|
||
typedef struct {
|
||
uint8_t module_index;
|
||
uint16_t point_count;
|
||
EskinForce3D* points; // 长度 = point_count
|
||
} EskinDistributionForce;
|
||
|
||
typedef struct {
|
||
uint64_t timestamp_us;
|
||
uint32_t sequence;
|
||
EskinCombinedForce combined_forces[28];
|
||
EskinDistributionForce* distribution_forces; // 可选,NULL 表示不采集
|
||
uint16_t distribution_count;
|
||
uint16_t module_errors[28];
|
||
} EskinSample;
|
||
|
||
typedef struct {
|
||
uint32_t serial_number;
|
||
uint16_t firmware_version;
|
||
uint16_t calibration_group;
|
||
uint16_t module_active_status;
|
||
uint16_t l_line;
|
||
uint16_t h_line;
|
||
uint32_t product_config_1;
|
||
uint32_t product_config_2;
|
||
} EskinDeviceInfo;
|
||
|
||
typedef enum {
|
||
ESKIN_DROP_NEWEST = 0,
|
||
ESKIN_DROP_OLDEST = 1,
|
||
} EskinDropPolicy;
|
||
|
||
typedef struct {
|
||
uint8_t device_addr; // 默认 0x34
|
||
int auto_transmit; // 0=不使能, 1=使能
|
||
int read_distribution; // 0=只读合力, 1=也读分布力
|
||
EskinDropPolicy drop_policy;
|
||
} EskinConfig;
|
||
|
||
// ─── 设备管理 API ────────────────────────────────────
|
||
EskinErrorCode eskin_initialize(void);
|
||
EskinErrorCode eskin_shutdown(void);
|
||
|
||
EskinErrorCode eskin_open(const char* port_name, EskinDevice** out_device);
|
||
EskinErrorCode eskin_open_by_serial(const char* serial, EskinDevice** out_device);
|
||
void eskin_close(EskinDevice* device);
|
||
|
||
// ─── 设备信息 API ────────────────────────────────────
|
||
EskinErrorCode eskin_get_device_info(EskinDevice* device, EskinDeviceInfo* out_info);
|
||
|
||
// ─── 配置 API ────────────────────────────────────────
|
||
EskinErrorCode eskin_get_config(EskinDevice* device, EskinConfig* config);
|
||
EskinErrorCode eskin_set_config(EskinDevice* device, const EskinConfig* config);
|
||
|
||
// ─── 流控制 API ──────────────────────────────────────
|
||
EskinErrorCode eskin_start_stream(EskinDevice* device);
|
||
EskinErrorCode eskin_stop_stream(EskinDevice* device);
|
||
|
||
// ─── 数据读取 API ────────────────────────────────────
|
||
EskinErrorCode eskin_read_sample(EskinDevice* device, EskinSample* out_sample,
|
||
uint32_t timeout_ms);
|
||
uint64_t eskin_get_dropped_sample_count(EskinDevice* device);
|
||
|
||
// ─── 寄存器直接访问 API ─────────────────────────────
|
||
EskinErrorCode eskin_read_register(EskinDevice* device, uint32_t addr,
|
||
uint16_t length, uint8_t* out_data);
|
||
EskinErrorCode eskin_write_register(EskinDevice* device, uint32_t addr,
|
||
uint16_t length, const uint8_t* data);
|
||
|
||
// ─── 错误管理 API ────────────────────────────────────
|
||
const char* eskin_error_string(EskinErrorCode code);
|
||
EskinErrorCode eskin_get_last_error(EskinDevice* device);
|
||
|
||
// ─── 内存管理 API ────────────────────────────────────
|
||
void eskin_free_sample(EskinSample* sample);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif // ESKIN_FINGER_SDK_H
|
||
```
|
||
|
||
---
|
||
|
||
## 14. C++ Wrapper 设计
|
||
|
||
### 14.1 RAII 封装
|
||
|
||
```cpp
|
||
// cpp/include/eskin_finger_sdk.hpp
|
||
|
||
#pragma once
|
||
|
||
#include "eskin_finger_sdk.h"
|
||
#include <functional>
|
||
#include <stdexcept>
|
||
#include <string>
|
||
#include <thread>
|
||
#include <atomic>
|
||
#include <vector>
|
||
#include <array>
|
||
|
||
namespace eskin {
|
||
|
||
// ─── 异常类型 ────────────────────────────────────────
|
||
class EskinException : public std::runtime_error {
|
||
public:
|
||
EskinException(EskinErrorCode code, const std::string& msg)
|
||
: std::runtime_error(msg), code_(code) {}
|
||
EskinErrorCode code() const { return code_; }
|
||
private:
|
||
EskinErrorCode code_;
|
||
};
|
||
|
||
// ─── 数据类型 ────────────────────────────────────────
|
||
struct Force3D { int16_t fx, fy, fz; };
|
||
|
||
struct CombinedForce {
|
||
uint8_t module_index;
|
||
int16_t fx, fy, fz;
|
||
};
|
||
|
||
struct DistributionForce {
|
||
uint8_t module_index;
|
||
std::vector<Force3D> points;
|
||
};
|
||
|
||
struct Sample {
|
||
uint64_t timestamp_us;
|
||
uint32_t sequence;
|
||
std::array<CombinedForce, 28> combined_forces;
|
||
std::vector<DistributionForce> distribution_forces;
|
||
std::array<uint16_t, 28> module_errors;
|
||
};
|
||
|
||
struct DeviceInfo {
|
||
uint32_t serial_number;
|
||
uint16_t firmware_version;
|
||
uint16_t calibration_group;
|
||
uint16_t module_active_status;
|
||
uint16_t l_line;
|
||
uint16_t h_line;
|
||
};
|
||
|
||
// ─── 回调类型 ────────────────────────────────────────
|
||
using SampleCallback = std::function<void(const Sample&)>;
|
||
using ErrorCallback = std::function<void(EskinErrorCode, const std::string&)>;
|
||
|
||
// ─── RAII Device 封装 ────────────────────────────────
|
||
class Device {
|
||
public:
|
||
Device(const Device&) = delete;
|
||
Device& operator=(const Device&) = delete;
|
||
Device(Device&& other) noexcept;
|
||
Device& operator=(Device&& other) noexcept;
|
||
~Device();
|
||
|
||
// 设备信息
|
||
DeviceInfo getDeviceInfo();
|
||
|
||
// 配置
|
||
void setConfig(const EskinConfig& config);
|
||
EskinConfig getConfig();
|
||
|
||
// 流控制
|
||
void start();
|
||
void stop();
|
||
|
||
// 同步数据读取
|
||
Sample readSample(uint32_t timeout_ms = 1000);
|
||
uint64_t getDroppedSampleCount() const;
|
||
|
||
// 寄存器直接访问
|
||
std::vector<uint8_t> readRegister(uint32_t addr, uint16_t length);
|
||
void writeRegister(uint32_t addr, const std::vector<uint8_t>& data);
|
||
|
||
// 回调模式
|
||
void setCallback(SampleCallback on_sample);
|
||
void setErrorCallback(ErrorCallback on_error);
|
||
void startAsync();
|
||
void stopAsync();
|
||
|
||
private:
|
||
friend class Sdk;
|
||
explicit Device(EskinDevice* handle);
|
||
|
||
EskinDevice* handle_ = nullptr;
|
||
SampleCallback sample_callback_;
|
||
ErrorCallback error_callback_;
|
||
std::thread callback_thread_;
|
||
std::atomic<bool> callback_running_{false};
|
||
};
|
||
|
||
// ─── SDK 全局管理 ────────────────────────────────────
|
||
class Sdk {
|
||
public:
|
||
Sdk();
|
||
~Sdk();
|
||
|
||
Sdk(const Sdk&) = delete;
|
||
Sdk& operator=(const Sdk&) = delete;
|
||
|
||
Device open(const std::string& port_name);
|
||
Device openBySerial(const std::string& serial);
|
||
|
||
static std::string errorString(EskinErrorCode code);
|
||
|
||
private:
|
||
bool initialized_ = false;
|
||
};
|
||
|
||
} // namespace eskin
|
||
```
|
||
|
||
---
|
||
|
||
## 15. 数据流示意
|
||
|
||
### 15.1 同步读取模式(Pull)
|
||
|
||
```
|
||
用户线程 SDK 内部
|
||
│ │
|
||
│ eskin_read_sample() │
|
||
│────────────────────────────►│ sample_rx.recv_timeout()
|
||
│ │
|
||
│ 内部循环: │
|
||
│ 1. 发送 UART 读请求 │
|
||
│ 2. 接收应答,校验 CRC │
|
||
│ 3. 解析合力/分布力 │
|
||
│ 4. 组装 FingerSample │
|
||
│ 5. 通过 channel 传递 │
|
||
│ │
|
||
│◄────────────────────────────│ 返回 FingerSample 或 Timeout
|
||
│ │
|
||
│ eskin_get_dropped_count() │
|
||
│────────────────────────────►│ atomic load
|
||
│◄────────────────────────────│ 返回 u64
|
||
```
|
||
|
||
### 15.2 回调模式(Push)
|
||
|
||
```
|
||
SDK 内部 用户回调
|
||
│ │
|
||
│ UART I/O 线程采集并解析 │
|
||
│ sample_tx.try_send(sample) │
|
||
│ │
|
||
│ 回调线程 recv sample │
|
||
│────────────────────────────────►│ on_sample(sample)
|
||
│ │
|
||
│ 如果采集出错 │
|
||
│────────────────────────────────►│ on_error(code, msg)
|
||
```
|
||
|
||
---
|
||
|
||
## 16. 线程模型
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ Rust SDK 进程 │
|
||
│ │
|
||
│ ┌───────────────────┐ ┌───────────────────┐ │
|
||
│ │ UART I/O Thread │ │ Command Handler │ │
|
||
│ │ (per device) │ │ (same thread) │ │
|
||
│ │ │ │ │ │
|
||
│ │ - 串口读写 │ │ - 接收 cmd_rx │ │
|
||
│ │ - CRC 校验 │ │ - 执行寄存器操作 │ │
|
||
│ │ - 协议解析 │ │ - 写入设备 │ │
|
||
│ │ - 组装样本 │ │ │ │
|
||
│ │ - sample_tx │ │ │ │
|
||
│ │ - event_tx │ │ │ │
|
||
│ └───────┬───────────┘ └───────┬───────────┘ │
|
||
│ │ │ │
|
||
│ ▼ ▼ │
|
||
│ ┌───────────────────────────────────────┐ │
|
||
│ │ Channel Manager │ │
|
||
│ │ - sample channel (bounded 64) │ │
|
||
│ │ - cmd channel (bounded 16) │ │
|
||
│ │ - event channel (bounded 64) │ │
|
||
│ │ - dropped_samples (AtomicU64) │ │
|
||
│ └───────────────────┬───────────────────┘ │
|
||
│ │ │
|
||
│ ┌───────────────────▼───────────────────┐ │
|
||
│ │ User-facing API │ │
|
||
│ │ - C ABI (extern "C") │ │
|
||
│ │ - C++ Wrapper (RAII + callback) │ │
|
||
│ └───────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 17. 关键设计决策
|
||
|
||
| 决策 | 选择 | 理由 |
|
||
|------|------|------|
|
||
| Channel 库 | `crossbeam-channel` | 比 `std::sync::mpsc` 更高性能,支持 MPMC,有 `try_send`、`recv_timeout` |
|
||
| 数据通道容量 | 64 (bounded) | 传感器帧率较低 (~10-100Hz),64 帧缓冲足够 |
|
||
| 样本发送策略 | `try_send` (非阻塞) | 采集线程绝不阻塞,保证实时性 |
|
||
| 丢弃策略 | 可配置 (`DropNewest`/`DropOldest`) | 不同场景需求不同 |
|
||
| FFI 指针 | Opaque pointer (`EskinDevice*`) | 隐藏内部结构,ABI 稳定 |
|
||
| 内存管理 | Box + raw pointer | Rust 侧 Box 管理,C 侧只持有指针,`close()` 时归还 |
|
||
| 错误处理 | 错误码 + last_error | C ABI 用错误码,C++ wrapper 转为异常 |
|
||
| 时间戳 | `u64` 微秒 | SDK 侧采集时间,避免浮点精度问题 |
|
||
| CRC 校验 | CRC-8-ITU | 协议规定,多项式 0x07,初始值 0x00 |
|
||
| 传输抽象 | `SerialTransport` trait | 方便单元测试 mock,可扩展支持其他传输方式 |
|
||
| 分布力采集 | 可选配置 | 分布力数据量大,按需采集避免影响帧率 |
|
||
|
||
---
|
||
|
||
## 18. 指令示例
|
||
|
||
### 18.1 读取固件版本信息
|
||
|
||
```
|
||
请求: 55 AA 09 00 34 00 FB 00 00 00 00 10 00 76
|
||
├─起始─┤├─长度─┤├地址┤├─功能─┤├─地址──┤├─长度──┤├CRC┤
|
||
|
||
应答: AA 55 09 00 34 00 FF 00 00 00 00 02 00 xx xx CRC 00
|
||
```
|
||
|
||
### 18.2 读取大拇指指尖分布力点数
|
||
|
||
```
|
||
请求: 55 AA 09 00 34 00 FB 14 00 00 00 02 00 31
|
||
起始地址 = 0x0014, 读取 2 字节
|
||
```
|
||
|
||
### 18.3 读取食指指尖合力
|
||
|
||
```
|
||
请求: 55 AA 09 00 34 00 FB 00 1C 00 00 A8 00 35
|
||
起始地址 = 0x1C00, 读取 0xA8 = 168 字节
|
||
```
|
||
|
||
### 18.4 修改食指指尖 L_LINE 值
|
||
|
||
```
|
||
写请求: 55 AA 0A 00 34 00 79 14 00 00 00 01 00 03 A4
|
||
起始地址 = 0x0014, 写入 1 字节, 数据 = 0x03
|
||
```
|
||
|
||
---
|
||
|
||
## 19. 构建方式
|
||
|
||
### Rust(生成 C 共享库)
|
||
|
||
```bash
|
||
cargo build --release
|
||
# 产物: target/release/libeskin_finger_sdk.dylib (macOS)
|
||
# target/release/libeskin_finger_sdk.so (Linux)
|
||
# target/release/eskin_finger_sdk.dll (Windows)
|
||
```
|
||
|
||
### C++ Wrapper(CMake)
|
||
|
||
```bash
|
||
mkdir build && cd build
|
||
cmake ..
|
||
make
|
||
```
|
||
|
||
### Python 绑定(未来扩展)
|
||
|
||
可以通过 `cffi` 或 `pybind11` 直接加载 C 共享库,无需额外绑定层。
|
||
|
||
---
|
||
|
||
## 20. 当前 Rust Core 接口骨架
|
||
|
||
当前阶段只定义接口和数据边界,具体读写、CRC、解析、线程生命周期后续实现。
|
||
|
||
### 20.1 模块职责
|
||
|
||
| 模块 | 文件 | 职责 |
|
||
|------|------|------|
|
||
| 数据类型 | `src/types.rs` | 力向量、模组枚举、合力、分布力、采样帧、模组错误 |
|
||
| 配置 | `src/config.rs` | 设备地址、采样队列容量、超时、丢弃策略、设备信息 |
|
||
| 错误 | `src/error.rs` | SDK 错误枚举和 C ABI 错误码 |
|
||
| 传输层 | `src/transport.rs` | `SerialTransport` trait,屏蔽具体串口实现 |
|
||
| 协议层 | `src/protocol.rs` | 请求/应答帧结构、`ProtocolCodec` trait、CRC 接口 |
|
||
| 寄存器层 | `src/register.rs` | 地址常量、寄存器元信息、原始字节到业务类型的解析接口 |
|
||
| 通道层 | `src/channel.rs` | sample/command/event 三类 channel 和丢弃策略 |
|
||
| 数据流 | `src/stream.rs` | `StreamController` trait,定义轮询/自动回传模式 |
|
||
| 设备层 | `src/device.rs` | `EskinDevice` trait,聚合 transport、codec、channel |
|
||
| FFI 边界 | `src/ffi/mod.rs` | C ABI handle、版本和基础生命周期接口草案 |
|
||
|
||
### 20.2 核心接口
|
||
|
||
```rust
|
||
pub trait SerialTransport {
|
||
fn open(&mut self) -> Result<(), SdkError>;
|
||
fn close(&mut self) -> Result<(), SdkError>;
|
||
fn is_open(&self) -> bool;
|
||
fn write(&mut self, data: &[u8]) -> Result<usize, SdkError>;
|
||
fn read(&mut self, buf: &mut [u8], timeout: chrono::Duration) -> Result<usize, SdkError>;
|
||
fn flush_rx(&mut self) -> Result<(), SdkError>;
|
||
}
|
||
|
||
pub trait ProtocolCodec {
|
||
fn encode_read_request(&self, request: &ReadRequest) -> Result<Vec<u8>, SdkError>;
|
||
fn encode_write_request(&self, request: &WriteRequest) -> Result<Vec<u8>, SdkError>;
|
||
fn decode_read_response(&self, frame: &[u8]) -> Result<ReadResponse, SdkError>;
|
||
fn decode_write_response(&self, frame: &[u8]) -> Result<WriteResponse, SdkError>;
|
||
fn decode_stream_frame(&self, frame: &[u8]) -> Result<ProtocolFrame, SdkError>;
|
||
fn crc8(&self, data: &[u8]) -> u8;
|
||
}
|
||
|
||
pub trait RegisterMap {
|
||
fn device_info_registers(&self) -> &'static [RegisterSpec];
|
||
fn distribution_register(&self, module: SensorModule) -> Result<RegisterSpec, SdkError>;
|
||
fn parse_device_info(&self, raw: &[u8]) -> Result<DeviceInfo, SdkError>;
|
||
fn parse_distribution_force(
|
||
&self,
|
||
module: SensorModule,
|
||
raw: &[u8],
|
||
) -> Result<DistributionForce, SdkError>;
|
||
}
|
||
|
||
pub trait EskinDevice {
|
||
fn open(&mut self) -> Result<(), SdkError>;
|
||
fn close(&mut self) -> Result<(), SdkError>;
|
||
fn state(&self) -> DeviceState;
|
||
fn device_info(&self) -> Result<DeviceInfo, SdkError>;
|
||
fn apply_config(&mut self, config: DeviceConfig) -> Result<(), SdkError>;
|
||
fn start_stream(&mut self) -> Result<(), SdkError>;
|
||
fn stop_stream(&mut self) -> Result<(), SdkError>;
|
||
fn read_sample(&self, timeout_ms: u32) -> Result<FingerSample, SdkError>;
|
||
fn read_event(&self, timeout_ms: u32) -> Result<DeviceEvent, SdkError>;
|
||
fn read_register(&mut self, addr: u32, length: u16) -> Result<Vec<u8>, SdkError>;
|
||
fn write_register(&mut self, addr: u32, data: &[u8]) -> Result<u16, SdkError>;
|
||
}
|
||
```
|
||
|
||
### 20.3 数据流流程图
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
App[Rust/C/C++ App] --> Device[EskinDevice]
|
||
Device --> Channel[ChannelManager]
|
||
Device --> Codec[ProtocolCodec]
|
||
Device --> Register[RegisterMap]
|
||
Device --> Transport[SerialTransport]
|
||
Transport --> Serial[UART / USB-Serial]
|
||
Serial --> Hardware[Eskin Finger Device]
|
||
|
||
App -->|open| Device
|
||
Device -->|open port| Transport
|
||
Device -->|read device info registers| Register
|
||
Register -->|ReadRequest| Codec
|
||
Codec -->|frame bytes| Transport
|
||
Transport -->|response bytes| Codec
|
||
Codec -->|ReadResponse| Register
|
||
Register -->|DeviceInfo| Device
|
||
|
||
App -->|start_stream| Device
|
||
Device -->|DeviceCommand::StartStream| Channel
|
||
Device -->|poll or auto receive| Transport
|
||
Transport -->|raw frame| Codec
|
||
Codec -->|ProtocolFrame| Register
|
||
Register -->|FingerSample| Channel
|
||
Channel -->|sample_rx| App
|
||
Channel -->|event_rx| App
|
||
```
|
||
|
||
### 20.4 设备生命周期
|
||
|
||
```mermaid
|
||
stateDiagram-v2
|
||
[*] --> Closed
|
||
Closed --> Open: open()
|
||
Open --> Streaming: start_stream()
|
||
Streaming --> Open: stop_stream()
|
||
Open --> Closed: close()
|
||
Streaming --> Error: transport/protocol error
|
||
Open --> Error: transport/protocol error
|
||
Error --> Closed: close()
|
||
```
|
||
|
||
---
|
||
|
||
## 21. 安全性考虑
|
||
|
||
1. **空指针检查**:所有 C API 入口检查指针非空
|
||
2. **状态检查**:操作前检查设备状态(是否已连接、是否正在采集等)
|
||
3. **CRC 校验**:每帧数据必须通过 CRC-8-ITU 校验
|
||
4. **帧完整性**:检查起始符、数据长度与实际数据一致性
|
||
5. **线程安全**:使用 `AtomicBool`、`AtomicU64`、`AtomicU32` 管理共享状态
|
||
6. **资源泄漏**:C++ RAII 保证析构时释放;C API 提供 `eskin_close()`
|
||
7. **panic 安全**:Rust FFI 函数不 panic,所有 panic 用 `catch_unwind` 捕获
|
||
8. **超时保护**:所有串口读操作带超时,避免永久阻塞
|