# Eskin Finger SDK Progress 本文件记录当前代码骨架进度、已完成设计决策、已知问题和后续实现顺序。 ## 当前状态 当前 SDK 已经形成如下分层: ```text Device API -> Stream Runtime / Channel -> Register Access -> Protocol Codec -> Serial Transport -> Hardware ``` 当前 `cargo check` 可以通过,但还有 `stream.rs` 中的两个 warning 需要清理。 ## 已完成 ### 1. Protocol Layer 文件:`src/protocol.rs` 已完成: - 读请求编码:`encode_read_request` - 写请求编码:`encode_write_request` - 读应答解码:`decode_read_response` - 写应答解码:`decode_write_response` - stream frame 解码入口:`decode_stream_frame` - CRC 校验 - 设备状态码转换 - response 帧长度校验 当前协议约定: ```text response frame = header + payload/status data + status(1B) + crc(1B) crc 是最后 1 字节 status 是 crc 前 1 字节 ``` 注意: - `FRAME_START_RESPONSE = 0xAA55` - 当前 `device.rs` 和 `stream.rs` 读取 response 起始符时使用 `u16::from_be_bytes([header[0], header[1]])` - 如果真实设备返回字节序为 `55 AA`,这里需要改为小端;如果真实返回 `AA 55`,当前逻辑正确 ### 2. Transport Layer 文件:`src/transport.rs` 已完成: - `SerialTransport` trait - `SerialPortTransport` - 串口 open/close/is_open - write/read/flush_rx - timeout 转换 - serialport error 到 `SdkError` 的转换 - `SharedSerialTransport = Arc>>` 设计决策: - `SerialTransport: Send`,不要求 `Sync` - 跨线程共享通过 `Arc>` 完成 - 串口 request/response 应在同一把 mutex lock 内完成,避免多线程串帧 ### 3. Device Layer 文件:`src/device.rs` 已完成: - `DeviceState` - `EskinDeviceInner` - `EskinDevice` trait - `open/close` - `start_stream/stop_stream` - `read_sample/read_event` - 同步 `read_register/write_register` - `ensure_open` - 共享 channel:`Arc` - 共享 transport:`SharedSerialTransport` - `create_stream_runtime` - `shared_transport` 当前行为: - `read_register/write_register` 会: - 检查设备状态 - protocol encode request - lock transport - flush rx - write request - read full response frame - protocol decode response 注意: - `device.start_stream()` 和 `StreamRuntime::start()` 当前都会发送 `StreamStarted` - 后续需要明确 stream 状态由谁统一管理,避免重复事件 ### 4. Channel Layer 文件:`src/channel.rs` 已完成: - `DeviceCommand` - `DeviceEvent` - `ChannelManager` - sample/cmd/event 三类 channel - `send_sample/recv_sample` - `send_cmd/recv_cmd` - `send_event/recv_event` - `dropped_count/reset_dropped_count` - sample drop policy: - `DropNewest` - `DropOldest` 设计决策: - `dropped_samples` 只统计 sample drop,不统计 command/event - channel timeout 和 disconnected 分别映射为: - `SdkError::Timeout` - `SdkError::ChannelClosed` - sample channel 满时根据 drop policy 处理,不作为硬错误 ### 5. Stream Layer 文件:`src/stream.rs` 已完成: - `StreamMode` - `StreamConfig` - `StreamController` - `StreamRuntime` - `StreamWorker` - worker thread 生命周期: - `start()` spawn worker - `stop()` stop flag + join - `SampleCollector` trait - `NoopSampleCollector` - `PollingSampleCollector` 骨架 - polling collector 已具备同步 `read_register` 能力 - polling collector 当前会尝试读取: - `REG_COMBINED_FORCE` - `REG_MODULE_ERROR` 当前行为: ```text StreamRuntime::start() -> make_collector() -> spawn StreamWorker -> worker loop -> collector.collect_once() -> if Some(sample), send_sample(sample) ``` 当前 `PollingSampleCollector::collect_once()` 只读取 raw bytes,尚未解析为 `FingerSample`,因此返回 `Ok(None)`。 已知 warning: - `src/stream.rs` unused import: `transport::{self, ...}` 中的 `self` - `StreamWorker::new` 参数 `transport` 未使用 ### 6. Register Layer 文件:`src/register.rs` 已完成: - 寄存器地址常量 - `RegisterSpec` - `RegisterAccess` - `RegisterValueType` - `DEVICE_INFO_REGISTERS` - `RegisterMap` trait - `EskinRegisterMap` - `parse_distribution_force` 暂未完成: - `distribution_register` - `parse_device_info` - combined force 解析 - module error 解析 ## 当前主要设计决策 ### Transport 共享模型 当前使用: ```rust Arc>> ``` 原因: - device 和 stream worker 需要共享同一个串口 - 串口读写需要 `&mut self` - mutex 保证一次 request/response 不被其他线程打断 长期建议: - stream running 时,尽量由 worker 独占串口访问 - 主线程通过 command channel 请求 worker 操作设备 - 避免主线程同步 `read_register` 和 worker polling 同时抢 transport ### Stream 职责拆分 当前拆分: ```text StreamRuntime 管理 start/stop、worker handle、对外读取 sample/event StreamWorker 管理 loop、running flag、sleep、错误事件 SampleCollector 管理一次采集,后续负责协议读写和 sample 构建 ``` 这是推荐方向。worker 不应该直接塞满协议和寄存器解析逻辑。 ## 明确下一步 ### Step 1: 清理当前 warning 文件:`src/stream.rs` 处理: - 删除 unused import 中的 `self` ```rust transport::{SerialTransport, SharedSerialTransport} ``` - `StreamWorker::new` 当前参数 `transport` 未使用。二选一: - 删除 `StreamWorker` 中的 transport 参数,因为 collector 已持有 transport - 或者让 worker 持有 transport,但不推荐,职责重复 推荐:删除 `StreamWorker::new` 的 `transport` 参数。 ### Step 2: 完善 Register 解析接口 文件:`src/register.rs` 新增解析函数: - `parse_combined_forces(raw: &[u8]) -> Result, SdkError>` - `parse_module_errors(raw: &[u8]) -> Result, SdkError>` 依据当前寄存器表: ```text REG_COMBINED_FORCE = 0x0500 长度 168B = 28 modules * 6B 每个 module = fx:i16 + fy:i16 + fz:i16 REG_MODULE_ERROR = 0x0700 长度 56B = 28 modules * 2B 每个 module = error_code:u16 ``` ### Step 3: 让 PollingSampleCollector 产出 FingerSample 文件:`src/stream.rs` 在 `PollingSampleCollector::collect_once()` 中: ```text 1. sequence = next_sequence() 2. read REG_COMBINED_FORCE 3. read REG_MODULE_ERROR 4. register parse raw bytes 5. build FingerSample 6. return Ok(Some(sample)) ``` 先不处理 distribution force。 ### Step 4: 补 distribution force 文件:`src/register.rs`、`src/stream.rs` 前置条件: - `distribution_register(module)` 能根据 `SensorModule` 返回地址和长度 - 需要确认每个 module 的分布力长度来源 实现策略: - `StreamConfig.read_distribution == false` 时跳过 - `StreamConfig.modules` 为空时默认读所有模块,或者默认不读;需要明确语义 ### Step 5: 统一 stream 状态入口 文件:`src/device.rs`、`src/stream.rs` 当前重复点: - `device.start_stream()` 发 `StreamStarted` - `StreamRuntime::start()` 也发 `StreamStarted` 需要选择一个主入口: 推荐: ```text device.open() let mut stream = device.create_stream_runtime() stream.start(config) stream.next_sample() stream.stop() ``` 如果最终 SDK 希望用户只调用 `device.start_stream()`,则 `EskinDeviceInner` 需要持有 `StreamRuntime` 或 worker handle。 ### Step 6: 增加基础测试 建议先加这些测试: - protocol encode read request golden bytes - protocol encode write request golden bytes - CRC 校验 - register parse combined force - register parse module error - ChannelManager drop policy ## 当前风险点 1. response 起始符字节序仍需真实设备帧确认。 2. stream worker 和 device 同步 read/write 共享同一 transport,虽然 mutex 安全,但业务上仍可能抢响应。 3. `PollingSampleCollector` 已读取 raw bytes,但还未构建 sample。 4. `register.rs` 的 `parse_device_info` 和 `distribution_register` 仍是 `todo!()`。 5. `StreamStarted/StreamStopped` 事件存在重复来源,需要统一入口。