Files
eskin-finger-sdk/docs/PROGRESS.md
2026-05-05 16:48:16 +08:00

8.0 KiB
Raw Blame History

Eskin Finger SDK Progress

本文件记录当前代码骨架进度、已完成设计决策、已知问题和后续实现顺序。

当前状态

当前 SDK 已经形成如下分层:

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 帧长度校验

当前协议约定:

response frame = header + payload/status data + status(1B) + crc(1B)
crc 是最后 1 字节
status 是 crc 前 1 字节

注意:

  • FRAME_START_RESPONSE = 0xAA55
  • 当前 device.rsstream.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<Mutex<Box<dyn SerialTransport>>>

设计决策:

  • SerialTransport: Send,不要求 Sync
  • 跨线程共享通过 Arc<Mutex<...>> 完成
  • 串口 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
  • 共享 channelArc<ChannelManager>
  • 共享 transportSharedSerialTransport
  • 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

当前行为:

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 共享模型

当前使用:

Arc<Mutex<Box<dyn SerialTransport>>>

原因:

  • device 和 stream worker 需要共享同一个串口
  • 串口读写需要 &mut self
  • mutex 保证一次 request/response 不被其他线程打断

长期建议:

  • stream running 时,尽量由 worker 独占串口访问
  • 主线程通过 command channel 请求 worker 操作设备
  • 避免主线程同步 read_register 和 worker polling 同时抢 transport

Stream 职责拆分

当前拆分:

StreamRuntime
  管理 start/stop、worker handle、对外读取 sample/event

StreamWorker
  管理 loop、running flag、sleep、错误事件

SampleCollector
  管理一次采集,后续负责协议读写和 sample 构建

这是推荐方向。worker 不应该直接塞满协议和寄存器解析逻辑。

明确下一步

Step 1: 清理当前 warning

文件:src/stream.rs

处理:

  • 删除 unused import 中的 self
transport::{SerialTransport, SharedSerialTransport}
  • StreamWorker::new 当前参数 transport 未使用。二选一:
    • 删除 StreamWorker 中的 transport 参数,因为 collector 已持有 transport
    • 或者让 worker 持有 transport但不推荐职责重复

推荐:删除 StreamWorker::newtransport 参数。

Step 2: 完善 Register 解析接口

文件:src/register.rs

新增解析函数:

  • parse_combined_forces(raw: &[u8]) -> Result<Vec<CombinedForce>, SdkError>
  • parse_module_errors(raw: &[u8]) -> Result<Vec<ModuleError>, SdkError>

依据当前寄存器表:

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() 中:

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.rssrc/stream.rs

前置条件:

  • distribution_register(module) 能根据 SensorModule 返回地址和长度
  • 需要确认每个 module 的分布力长度来源

实现策略:

  • StreamConfig.read_distribution == false 时跳过
  • StreamConfig.modules 为空时默认读所有模块,或者默认不读;需要明确语义

Step 5: 统一 stream 状态入口

文件:src/device.rssrc/stream.rs

当前重复点:

  • device.start_stream()StreamStarted
  • StreamRuntime::start() 也发 StreamStarted

需要选择一个主入口:

推荐:

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.rsparse_device_infodistribution_register 仍是 todo!()
  5. StreamStarted/StreamStopped 事件存在重复来源,需要统一入口。