8.0 KiB
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.rs和stream.rs读取 response 起始符时使用u16::from_be_bytes([header[0], header[1]]) - 如果真实设备返回字节序为
55 AA,这里需要改为小端;如果真实返回AA 55,当前逻辑正确
2. Transport Layer
文件:src/transport.rs
已完成:
SerialTransporttraitSerialPortTransport- 串口 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
已完成:
DeviceStateEskinDeviceInnerEskinDevicetraitopen/closestart_stream/stop_streamread_sample/read_event- 同步
read_register/write_register ensure_open- 共享 channel:
Arc<ChannelManager> - 共享 transport:
SharedSerialTransport create_stream_runtimeshared_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
已完成:
DeviceCommandDeviceEventChannelManager- sample/cmd/event 三类 channel
send_sample/recv_samplesend_cmd/recv_cmdsend_event/recv_eventdropped_count/reset_dropped_count- sample drop policy:
DropNewestDropOldest
设计决策:
dropped_samples只统计 sample drop,不统计 command/event- channel timeout 和 disconnected 分别映射为:
SdkError::TimeoutSdkError::ChannelClosed
- sample channel 满时根据 drop policy 处理,不作为硬错误
5. Stream Layer
文件:src/stream.rs
已完成:
StreamModeStreamConfigStreamControllerStreamRuntimeStreamWorker- worker thread 生命周期:
start()spawn workerstop()stop flag + join
SampleCollectortraitNoopSampleCollectorPollingSampleCollector骨架- polling collector 已具备同步
read_register能力 - polling collector 当前会尝试读取:
REG_COMBINED_FORCEREG_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.rsunused import:transport::{self, ...}中的selfStreamWorker::new参数transport未使用
6. Register Layer
文件:src/register.rs
已完成:
- 寄存器地址常量
RegisterSpecRegisterAccessRegisterValueTypeDEVICE_INFO_REGISTERSRegisterMaptraitEskinRegisterMapparse_distribution_force
暂未完成:
distribution_registerparse_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::new 的 transport 参数。
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.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()发StreamStartedStreamRuntime::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
当前风险点
- response 起始符字节序仍需真实设备帧确认。
- stream worker 和 device 同步 read/write 共享同一 transport,虽然 mutex 安全,但业务上仍可能抢响应。
PollingSampleCollector已读取 raw bytes,但还未构建 sample。register.rs的parse_device_info和distribution_register仍是todo!()。StreamStarted/StreamStopped事件存在重复来源,需要统一入口。