update
This commit is contained in:
144
src/channel.rs
Normal file
144
src/channel.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
use crate::{
|
||||
config::{DeviceConfig, DropPolicy},
|
||||
error::SdkError,
|
||||
types::{FingerSample, SensorModule},
|
||||
};
|
||||
use crossbeam_channel::{Receiver, Sender, bounded};
|
||||
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,
|
||||
cmd_capacity: usize,
|
||||
event_capacity: usize,
|
||||
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(captured)) =
|
||||
self.sample_tx.try_send(sample)
|
||||
{
|
||||
let _ = self.sample_rx.try_recv();
|
||||
if self.sample_tx.try_send(captured).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 send_cmd(&self, cmd: DeviceCommand) {
|
||||
match self.drop_policy {
|
||||
DropPolicy::DropNewest => {
|
||||
if self.cmd_tx.try_send(cmd).is_err() {
|
||||
self.dropped_samples.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
DropPolicy::DropOldest => {
|
||||
if let Err(crossbeam_channel::TrySendError::Full(captured)) =
|
||||
self.cmd_tx.try_send(cmd)
|
||||
{
|
||||
let _ = self.cmd_rx.try_recv();
|
||||
if self.cmd_tx.try_send(captured).is_err() {
|
||||
self.dropped_samples.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recv_cmd(&self, timeout_ms: u32) -> Result<DeviceCommand, SdkError> {
|
||||
let timeout = std::time::Duration::from_millis(timeout_ms as u64);
|
||||
self.cmd_rx
|
||||
.recv_timeout(timeout)
|
||||
.map_err(|_| SdkError::Timeout)
|
||||
}
|
||||
|
||||
pub fn dropped_count(&self) -> u64 {
|
||||
self.dropped_samples.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn send_event(&self, event: DeviceEvent) -> Result<(), SdkError> {
|
||||
self.event_tx
|
||||
.try_send(event)
|
||||
.map_err(|_| SdkError::ChannelClosed)
|
||||
}
|
||||
|
||||
pub fn recv_event(&self, timeout_ms: u32) -> Result<DeviceEvent, SdkError> {
|
||||
let timeout = std::time::Duration::from_millis(timeout_ms as u64);
|
||||
self.event_rx
|
||||
.recv_timeout(timeout)
|
||||
.map_err(|_| SdkError::Timeout)
|
||||
}
|
||||
}
|
||||
64
src/config.rs
Normal file
64
src/config.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DeviceConfig {
|
||||
pub device_addr: u8,
|
||||
pub auto_distribution: bool,
|
||||
pub read_distribution: bool,
|
||||
pub drop_policy: DropPolicy,
|
||||
pub sample_capacity: usize,
|
||||
pub command_capacity: usize,
|
||||
pub event_capacity: usize,
|
||||
pub read_timeout_ms: u32,
|
||||
}
|
||||
|
||||
impl Default for DeviceConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
device_addr: 0x34,
|
||||
auto_distribution: false,
|
||||
read_distribution: true,
|
||||
drop_policy: DropPolicy::DropOldest,
|
||||
sample_capacity: 1024,
|
||||
command_capacity: 64,
|
||||
event_capacity: 128,
|
||||
read_timeout_ms: 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Copy)]
|
||||
pub enum DropPolicy {
|
||||
DropNewest,
|
||||
DropOldest,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DeviceInfo {
|
||||
pub serial_number: u32,
|
||||
pub firmware_version: u16,
|
||||
pub calibration_group: u16,
|
||||
pub module_active_status: u16,
|
||||
pub l_line: u16,
|
||||
pub h_line: u16,
|
||||
pub product_config_1: u32,
|
||||
pub product_config_2: u32,
|
||||
}
|
||||
|
||||
impl Default for DeviceInfo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
serial_number: 0x0001,
|
||||
firmware_version: 0x01,
|
||||
calibration_group: 0,
|
||||
module_active_status: 0,
|
||||
l_line: 7,
|
||||
h_line: 12,
|
||||
product_config_1: 0,
|
||||
product_config_2: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
89
src/device.rs
Normal file
89
src/device.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use chrono::Duration;
|
||||
|
||||
use crate::{
|
||||
channel::{ChannelManager, DeviceEvent},
|
||||
config::{DeviceConfig, DeviceInfo},
|
||||
error::SdkError,
|
||||
protocol::{EskinProtocolCodec, ProtocolCodec},
|
||||
transport::SerialTransport,
|
||||
types::FingerSample,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DeviceState {
|
||||
Closed,
|
||||
Open,
|
||||
Streaming,
|
||||
Error,
|
||||
}
|
||||
|
||||
pub struct EskinDeviceInner {
|
||||
pub info: DeviceInfo,
|
||||
pub config: DeviceConfig,
|
||||
pub channels: ChannelManager,
|
||||
pub state: DeviceState,
|
||||
pub transport: Box<dyn SerialTransport>,
|
||||
pub codec: Box<dyn ProtocolCodec>,
|
||||
}
|
||||
|
||||
impl EskinDeviceInner {
|
||||
pub fn new(config: DeviceConfig, transport: Box<dyn SerialTransport>) -> Self {
|
||||
let channels = ChannelManager::new(
|
||||
config.sample_capacity,
|
||||
config.command_capacity,
|
||||
config.event_capacity,
|
||||
config.drop_policy,
|
||||
);
|
||||
|
||||
Self {
|
||||
info: DeviceInfo::default(),
|
||||
config,
|
||||
channels,
|
||||
state: DeviceState::Closed,
|
||||
transport,
|
||||
codec: Box::new(EskinProtocolCodec),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_exact_from_transport(
|
||||
&mut self,
|
||||
buf: &mut [u8],
|
||||
timeout: Duration,
|
||||
) -> Result<(), SdkError> {
|
||||
let mut offset = 0;
|
||||
while offset < buf.len() {
|
||||
let n = self.transport.read(&mut buf[offset..], timeout)?;
|
||||
|
||||
if n == 0 {
|
||||
return Err(SdkError::Timeout);
|
||||
}
|
||||
|
||||
offset += n;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_response_frame(&mut self) -> Result<Vec<u8>, SdkError> {
|
||||
let timeout = Duration::from_millis(self.config.read_timeout_ms as u64);
|
||||
let mut header = [0u8; 4];
|
||||
self.read_exact_from_transport(&mut header, timeout)?;
|
||||
}
|
||||
}
|
||||
|
||||
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 config(&self) -> &DeviceConfig;
|
||||
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>;
|
||||
}
|
||||
72
src/error.rs
Normal file
72
src/error.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
#[repr(C)]
|
||||
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,
|
||||
}
|
||||
|
||||
#[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),
|
||||
}
|
||||
19
src/ffi/mod.rs
Normal file
19
src/ffi/mod.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use crate::{config::DeviceConfig, error::SdkErrorCode};
|
||||
|
||||
pub type EskinDeviceHandle = *mut core::ffi::c_void;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct EskinSdkVersion {
|
||||
pub major: u16,
|
||||
pub minor: u16,
|
||||
pub patch: u16,
|
||||
}
|
||||
|
||||
pub trait CApi {
|
||||
fn version() -> EskinSdkVersion;
|
||||
fn open(path: *const libc::c_char, config: *const DeviceConfig) -> EskinDeviceHandle;
|
||||
fn close(handle: EskinDeviceHandle) -> SdkErrorCode;
|
||||
fn start_stream(handle: EskinDeviceHandle) -> SdkErrorCode;
|
||||
fn stop_stream(handle: EskinDeviceHandle) -> SdkErrorCode;
|
||||
}
|
||||
11
src/lib.rs
11
src/lib.rs
@@ -1 +1,10 @@
|
||||
pub mod types;
|
||||
pub mod channel;
|
||||
pub mod config;
|
||||
pub mod device;
|
||||
pub mod error;
|
||||
pub mod ffi;
|
||||
pub mod protocol;
|
||||
pub mod register;
|
||||
pub mod stream;
|
||||
pub mod transport;
|
||||
pub mod types;
|
||||
|
||||
396
src/protocol.rs
Normal file
396
src/protocol.rs
Normal file
@@ -0,0 +1,396 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::error::SdkError;
|
||||
|
||||
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;
|
||||
pub const FUNC_RESPONSE_WRITE: u8 = 0xF9;
|
||||
|
||||
pub const FRAME_HEADER_LEN: usize = 13;
|
||||
pub const FRAME_CRC_LEN: usize = 1;
|
||||
pub const FRAME_STATUS_LEN: usize = 1;
|
||||
pub const MIN_RESPONSE_FRAME_LEN: usize = FRAME_HEADER_LEN + FRAME_STATUS_LEN + FRAME_CRC_LEN;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum DeviceStatus {
|
||||
Success = 0x00,
|
||||
ReadLenExceeded = 0x01,
|
||||
LengthError = 0x02,
|
||||
InvalidAddress = 0x03,
|
||||
ReadOnlyRegister = 0x04,
|
||||
}
|
||||
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum FrameFunction {
|
||||
Read,
|
||||
Write,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ProtocolFrame {
|
||||
pub start: u16,
|
||||
pub device_addr: u8,
|
||||
pub function: u8,
|
||||
pub start_addr: u32,
|
||||
pub payload: Vec<u8>,
|
||||
pub status: Option<DeviceStatus>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
pub trait ProtocolCodec: Send + Sync {
|
||||
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;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct EskinProtocolCodec;
|
||||
|
||||
impl EskinProtocolCodec {
|
||||
fn status_from_u8(raw: u8) -> Result<DeviceStatus, SdkError> {
|
||||
match raw {
|
||||
0x00 => Ok(DeviceStatus::Success),
|
||||
0x01 => Ok(DeviceStatus::ReadLenExceeded),
|
||||
0x02 => Ok(DeviceStatus::LengthError),
|
||||
0x03 => Ok(DeviceStatus::InvalidAddress),
|
||||
0x04 => Ok(DeviceStatus::ReadOnlyRegister),
|
||||
other => Err(SdkError::DeviceError(other as u16)),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_crc(&self, frame: &[u8]) -> Result<(), SdkError> {
|
||||
if frame.len() < FRAME_CRC_LEN {
|
||||
return Err(SdkError::FrameError("frame too short for crc".into()));
|
||||
}
|
||||
|
||||
let expected = frame[frame.len() - 1];
|
||||
let actual = self.crc8(&frame[..frame.len() - 1]);
|
||||
if expected != actual {
|
||||
return Err(SdkError::CrcError { expected, actual });
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_u16_le(frame: &[u8], offset: usize) -> Result<u16, SdkError> {
|
||||
let bytes = frame
|
||||
.get(offset..offset + 2)
|
||||
.ok_or_else(|| SdkError::FrameError("missing u16 field".into()))?;
|
||||
|
||||
Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
|
||||
}
|
||||
|
||||
fn read_u32_le(frame: &[u8], offset: usize) -> Result<u32, SdkError> {
|
||||
let bytes = frame
|
||||
.get(offset..offset + 4)
|
||||
.ok_or_else(|| SdkError::FrameError("missing u32 field".into()))?;
|
||||
|
||||
Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolCodec for EskinProtocolCodec {
|
||||
fn encode_read_request(&self, request: &ReadRequest) -> Result<Vec<u8>, SdkError> {
|
||||
if request.read_byte_count == 0 {
|
||||
return Err(SdkError::InvalidParameter(
|
||||
"read_byte_count must be greater than 0".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let data_len: u16 = 9;
|
||||
let mut frame = Vec::with_capacity(14);
|
||||
frame.extend_from_slice(&FRAME_START_REQUEST.to_le_bytes());
|
||||
frame.extend_from_slice(&data_len.to_le_bytes());
|
||||
frame.push(request.device_addr);
|
||||
frame.push(0x00);
|
||||
frame.push(FUNC_READ);
|
||||
frame.extend_from_slice(&request.start_addr.to_le_bytes());
|
||||
frame.extend_from_slice(&request.read_byte_count.to_le_bytes());
|
||||
|
||||
let crc = self.crc8(&frame);
|
||||
frame.push(crc);
|
||||
|
||||
Ok(frame)
|
||||
}
|
||||
|
||||
fn encode_write_request(&self, request: &WriteRequest) -> Result<Vec<u8>, SdkError> {
|
||||
if request.data.is_empty() {
|
||||
return Err(SdkError::InvalidParameter(
|
||||
"write data must not be empty".into(),
|
||||
));
|
||||
}
|
||||
|
||||
if request.data.len() > u16::MAX as usize {
|
||||
return Err(SdkError::InvalidParameter(
|
||||
"write data length exceeds u16::MAX".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let write_len = request.data.len() as u16;
|
||||
let data_len = 9u16
|
||||
.checked_add(write_len)
|
||||
.ok_or_else(|| SdkError::InvalidParameter("write frame too large".into()))?;
|
||||
|
||||
let mut frame = Vec::with_capacity(14 + request.data.len());
|
||||
frame.extend_from_slice(&FRAME_START_REQUEST.to_le_bytes());
|
||||
frame.extend_from_slice(&data_len.to_le_bytes());
|
||||
frame.push(request.device_addr);
|
||||
frame.push(0x00);
|
||||
frame.push(FUNC_WRITE);
|
||||
frame.extend_from_slice(&request.start_addr.to_le_bytes());
|
||||
frame.extend_from_slice(&write_len.to_le_bytes());
|
||||
frame.extend_from_slice(&request.data);
|
||||
|
||||
let crc = self.crc8(&frame);
|
||||
frame.push(crc);
|
||||
|
||||
Ok(frame)
|
||||
}
|
||||
|
||||
fn decode_read_response(&self, frame: &[u8]) -> Result<ReadResponse, SdkError> {
|
||||
if frame.len() < MIN_RESPONSE_FRAME_LEN {
|
||||
return Err(SdkError::FrameError("read response too short".into()));
|
||||
}
|
||||
|
||||
let start = Self::read_u16_le(frame, 0)?;
|
||||
if start != FRAME_START_RESPONSE {
|
||||
return Err(SdkError::FrameError(format!(
|
||||
"invalid response start: 0x{start:04X}"
|
||||
)));
|
||||
}
|
||||
|
||||
let data_len = Self::read_u16_le(frame, 2)? as usize;
|
||||
let expected_len = 2 + 2 + data_len + FRAME_STATUS_LEN + FRAME_CRC_LEN;
|
||||
|
||||
if frame.len() != expected_len {
|
||||
return Err(SdkError::FrameError(format!(
|
||||
"read response length mismatch: expected {expected_len}, got {}",
|
||||
frame.len()
|
||||
)));
|
||||
}
|
||||
|
||||
self.validate_crc(frame)?;
|
||||
|
||||
let device_addr = frame[4];
|
||||
let reserved = frame[5];
|
||||
let function = frame[6];
|
||||
|
||||
if reserved != 0x00 {
|
||||
return Err(SdkError::FrameError(format!(
|
||||
"invalid reserved byte: 0x{reserved:02X}"
|
||||
)));
|
||||
}
|
||||
|
||||
if function != FUNC_RESPONSE_READ {
|
||||
return Err(SdkError::FrameError(format!(
|
||||
"invalid read response function: 0x{function:02X}"
|
||||
)));
|
||||
}
|
||||
|
||||
let start_addr = Self::read_u32_le(frame, 7)?;
|
||||
let read_len = Self::read_u16_le(frame, 11)? as usize;
|
||||
if data_len != 9 + read_len {
|
||||
return Err(SdkError::FrameError(format!(
|
||||
"read response data length mismatch: header data_len {data_len}, payload len {read_len}"
|
||||
)));
|
||||
}
|
||||
|
||||
let payload_start = 13;
|
||||
let payload_end = payload_start + read_len;
|
||||
|
||||
let data = frame
|
||||
.get(payload_start..payload_end)
|
||||
.ok_or_else(|| SdkError::FrameError("read response payload missing".into()))?
|
||||
.to_vec();
|
||||
|
||||
let status_offset = 4 + data_len;
|
||||
let status_raw = *frame
|
||||
.get(status_offset)
|
||||
.ok_or_else(|| SdkError::FrameError("read response status missing".into()))?;
|
||||
let status = Self::status_from_u8(status_raw)?;
|
||||
|
||||
if let Some(err) = status.to_error() {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
Ok(ReadResponse {
|
||||
device_addr,
|
||||
start_addr,
|
||||
data,
|
||||
status,
|
||||
})
|
||||
}
|
||||
|
||||
fn decode_write_response(&self, frame: &[u8]) -> Result<WriteResponse, SdkError> {
|
||||
if frame.len() < MIN_RESPONSE_FRAME_LEN {
|
||||
return Err(SdkError::FrameError("write response too short".into()));
|
||||
}
|
||||
|
||||
let start = Self::read_u16_le(frame, 0)?;
|
||||
if start != FRAME_START_RESPONSE {
|
||||
return Err(SdkError::FrameError(format!(
|
||||
"invalid response start: 0x{start:04X}"
|
||||
)));
|
||||
}
|
||||
|
||||
let data_len = Self::read_u16_le(frame, 2)? as usize;
|
||||
let expected_len = 2 + 2 + data_len + FRAME_STATUS_LEN + FRAME_CRC_LEN;
|
||||
|
||||
if frame.len() != expected_len {
|
||||
return Err(SdkError::FrameError(format!(
|
||||
"write response length mismatch: expected {expected_len}, got {}",
|
||||
frame.len()
|
||||
)));
|
||||
}
|
||||
|
||||
self.validate_crc(frame)?;
|
||||
let device_addr = frame[4];
|
||||
let reserved = frame[5];
|
||||
let function = frame[6];
|
||||
|
||||
if reserved != 0x00 {
|
||||
return Err(SdkError::FrameError(format!(
|
||||
"invalid reserved byte: 0x{reserved:02X}"
|
||||
)));
|
||||
}
|
||||
|
||||
if function != FUNC_RESPONSE_WRITE {
|
||||
return Err(SdkError::FrameError(format!(
|
||||
"invalid write response function: 0x{function:02X}"
|
||||
)));
|
||||
}
|
||||
|
||||
let start_addr = Self::read_u32_le(frame, 7)?;
|
||||
let return_byte_count = Self::read_u16_le(frame, 11)?;
|
||||
if data_len != 9 {
|
||||
return Err(SdkError::FrameError(format!(
|
||||
"write response data length mismatch: expected 9, got {data_len}"
|
||||
)));
|
||||
}
|
||||
|
||||
let status_offset = 4 + data_len;
|
||||
let status_raw = *frame
|
||||
.get(status_offset)
|
||||
.ok_or_else(|| SdkError::FrameError("write response status missing".into()))?;
|
||||
let status = Self::status_from_u8(status_raw)?;
|
||||
|
||||
if let Some(err) = status.to_error() {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
Ok(WriteResponse {
|
||||
device_addr,
|
||||
start_addr,
|
||||
return_byte_count,
|
||||
status,
|
||||
})
|
||||
}
|
||||
|
||||
fn decode_stream_frame(&self, frame: &[u8]) -> Result<ProtocolFrame, SdkError> {
|
||||
if frame.len() < MIN_RESPONSE_FRAME_LEN {
|
||||
return Err(SdkError::FrameError("stream frame too short".into()));
|
||||
}
|
||||
|
||||
let start = Self::read_u16_le(frame, 0)?;
|
||||
if start != FRAME_START_RESPONSE {
|
||||
return Err(SdkError::FrameError(format!(
|
||||
"invalid stream frame start: 0x{start:04X}"
|
||||
)));
|
||||
}
|
||||
|
||||
let data_len = Self::read_u16_le(frame, 2)? as usize;
|
||||
let expected_len = 2 + 2 + data_len + FRAME_STATUS_LEN + FRAME_CRC_LEN;
|
||||
|
||||
if frame.len() != expected_len {
|
||||
return Err(SdkError::FrameError(format!(
|
||||
"stream frame length mismatch: expected {expected_len}, got {}",
|
||||
frame.len()
|
||||
)));
|
||||
}
|
||||
|
||||
self.validate_crc(frame)?;
|
||||
|
||||
let device_addr = frame[4];
|
||||
let function = frame[6];
|
||||
let start_addr = Self::read_u32_le(frame, 7)?;
|
||||
let payload_len = Self::read_u16_le(frame, 11)? as usize;
|
||||
if data_len != 9 + payload_len {
|
||||
return Err(SdkError::FrameError(format!(
|
||||
"stream frame data length mismatch: header data_len {data_len}, payload len {payload_len}"
|
||||
)));
|
||||
}
|
||||
|
||||
let payload_start = 13;
|
||||
let payload_end = payload_start + payload_len;
|
||||
|
||||
let payload = frame
|
||||
.get(payload_start..payload_end)
|
||||
.ok_or_else(|| SdkError::FrameError("stream payload missing".into()))?
|
||||
.to_vec();
|
||||
|
||||
let status_offset = 4 + data_len;
|
||||
let status_raw = *frame
|
||||
.get(status_offset)
|
||||
.ok_or_else(|| SdkError::FrameError("stream status missing".into()))?;
|
||||
let status = Self::status_from_u8(status_raw)?;
|
||||
|
||||
Ok(ProtocolFrame {
|
||||
start,
|
||||
device_addr,
|
||||
function,
|
||||
start_addr,
|
||||
payload,
|
||||
status: Some(status),
|
||||
})
|
||||
}
|
||||
|
||||
fn crc8(&self, _data: &[u8]) -> u8 {
|
||||
const X25: crc::Crc<u8> = crc::Crc::<u8>::new(&crc::CRC_8_I_432_1);
|
||||
X25.checksum(_data)
|
||||
}
|
||||
}
|
||||
76
src/register.rs
Normal file
76
src/register.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use crate::{
|
||||
config::DeviceInfo,
|
||||
error::SdkError,
|
||||
types::{DistributionForce, SensorModule},
|
||||
};
|
||||
|
||||
pub const REG_SERIAL_NUMBER: u32 = 0x0000;
|
||||
pub const REG_FIRMWARE_VERSION: u32 = 0x000F;
|
||||
pub const REG_CALIBRATION_GROUP: u32 = 0x0010;
|
||||
pub const REG_MODULE_ACTIVE_STATUS: u32 = 0x0011;
|
||||
pub const REG_L_LINE: u32 = 0x0012;
|
||||
pub const REG_H_LINE: u32 = 0x0013;
|
||||
pub const REG_PRODUCT_CONFIG_1: u32 = 0x0030;
|
||||
pub const REG_PRODUCT_CONFIG_2: u32 = 0x0032;
|
||||
pub const REG_COMBINED_FORCE: u32 = 0x0500;
|
||||
pub const REG_MODULE_ERROR: u32 = 0x0700;
|
||||
pub const REG_DISTRIBUTION_FORCE_BASE: u32 = 0x1000;
|
||||
pub const REG_PROCESSED_VALUE_BASE: u32 = 0x2000;
|
||||
pub const REG_CALIBRATION_BASE: u32 = 0x8000;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RegisterAccess {
|
||||
ReadOnly,
|
||||
ReadWrite,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RegisterValueType {
|
||||
U16,
|
||||
U32,
|
||||
Bytes,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct RegisterSpec {
|
||||
pub addr: u32,
|
||||
pub len: u16,
|
||||
pub access: RegisterAccess,
|
||||
pub value_type: RegisterValueType,
|
||||
}
|
||||
|
||||
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>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct EskinRegisterMap;
|
||||
|
||||
impl RegisterMap for EskinRegisterMap {
|
||||
fn device_info_registers(&self) -> &'static [RegisterSpec] {
|
||||
todo!("device info register specs")
|
||||
}
|
||||
|
||||
fn distribution_register(&self, _module: SensorModule) -> Result<RegisterSpec, SdkError> {
|
||||
todo!("distribution register spec")
|
||||
}
|
||||
|
||||
fn parse_device_info(&self, _raw: &[u8]) -> Result<DeviceInfo, SdkError> {
|
||||
todo!("parse device info")
|
||||
}
|
||||
|
||||
fn parse_distribution_force(
|
||||
&self,
|
||||
_module: SensorModule,
|
||||
_raw: &[u8],
|
||||
) -> Result<DistributionForce, SdkError> {
|
||||
todo!("parse distribution force")
|
||||
}
|
||||
}
|
||||
38
src/stream.rs
Normal file
38
src/stream.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use crate::{
|
||||
channel::DeviceEvent,
|
||||
error::SdkError,
|
||||
types::{FingerSample, SensorModule},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum StreamMode {
|
||||
Polling,
|
||||
AutoDistribution,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StreamConfig {
|
||||
pub mode: StreamMode,
|
||||
pub read_distribution: bool,
|
||||
pub modules: Vec<SensorModule>,
|
||||
pub poll_interval_ms: u32,
|
||||
}
|
||||
|
||||
impl Default for StreamConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mode: StreamMode::Polling,
|
||||
read_distribution: true,
|
||||
modules: Vec::new(),
|
||||
poll_interval_ms: 10,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait StreamController: Send {
|
||||
fn start(&mut self, config: StreamConfig) -> Result<(), SdkError>;
|
||||
fn stop(&mut self) -> Result<(), SdkError>;
|
||||
fn is_running(&self) -> bool;
|
||||
fn next_sample(&self, timeout_ms: u32) -> Result<FingerSample, SdkError>;
|
||||
fn next_event(&self, timeout_ms: u32) -> Result<DeviceEvent, SdkError>;
|
||||
}
|
||||
110
src/transport.rs
Normal file
110
src/transport.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use crate::error::SdkError;
|
||||
use chrono::Duration;
|
||||
use serialport::{ClearBuffer, DataBits, FlowControl, Parity, StopBits};
|
||||
use std::io::ErrorKind;
|
||||
|
||||
pub trait SerialTransport: Send {
|
||||
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: Duration) -> Result<usize, SdkError>;
|
||||
fn flush_rx(&mut self) -> Result<(), SdkError>;
|
||||
}
|
||||
|
||||
pub struct SerialPortTransport {
|
||||
pub path: String,
|
||||
pub baud_rate: u32,
|
||||
pub port: Option<Box<dyn serialport::SerialPort>>,
|
||||
}
|
||||
|
||||
impl SerialPortTransport {
|
||||
pub fn new(path: impl Into<String>, baud_rate: u32) -> Self {
|
||||
Self {
|
||||
path: path.into(),
|
||||
baud_rate,
|
||||
port: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn port_mut(&mut self) -> Result<&mut Box<dyn serialport::SerialPort>, SdkError> {
|
||||
self.port
|
||||
.as_mut()
|
||||
.ok_or_else(|| SdkError::DeviceNotFound(self.path.clone()))
|
||||
}
|
||||
|
||||
fn timeout_to_std(timeout: Duration) -> Result<std::time::Duration, SdkError> {
|
||||
timeout
|
||||
.to_std()
|
||||
.map_err(|_| SdkError::InvalidParameter("timeout must be non-negative".into()))
|
||||
}
|
||||
|
||||
fn map_serial_error(error: serialport::Error) -> SdkError {
|
||||
SdkError::IoError(std::io::Error::new(ErrorKind::Other, error.to_string()))
|
||||
}
|
||||
|
||||
fn map_io_error(error: std::io::Error) -> SdkError {
|
||||
match error.kind() {
|
||||
ErrorKind::TimedOut | ErrorKind::WouldBlock => SdkError::Timeout,
|
||||
_ => SdkError::IoError(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SerialTransport for SerialPortTransport {
|
||||
fn open(&mut self) -> Result<(), SdkError> {
|
||||
if self.port.is_some() {
|
||||
return Err(SdkError::DeviceAlreadyOpen);
|
||||
}
|
||||
|
||||
let port = serialport::new(&self.path, self.baud_rate)
|
||||
.data_bits(DataBits::Eight)
|
||||
.stop_bits(StopBits::One)
|
||||
.parity(Parity::None)
|
||||
.flow_control(FlowControl::None)
|
||||
.open()
|
||||
.map_err(Self::map_serial_error)?;
|
||||
|
||||
self.port = Some(port);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn close(&mut self) -> Result<(), SdkError> {
|
||||
self.port.take();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_open(&self) -> bool {
|
||||
self.port.is_some()
|
||||
}
|
||||
|
||||
fn write(&mut self, data: &[u8]) -> Result<usize, SdkError> {
|
||||
if data.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let port = self.port_mut()?;
|
||||
let written = port.write(data).map_err(Self::map_io_error)?;
|
||||
port.flush().map_err(Self::map_io_error)?;
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
fn read(&mut self, buf: &mut [u8], timeout: Duration) -> Result<usize, SdkError> {
|
||||
if buf.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let timeout = Self::timeout_to_std(timeout)?;
|
||||
let port = self.port_mut()?;
|
||||
|
||||
port.set_timeout(timeout).map_err(Self::map_serial_error)?;
|
||||
port.read(buf).map_err(Self::map_io_error)
|
||||
}
|
||||
|
||||
fn flush_rx(&mut self) -> Result<(), SdkError> {
|
||||
self.port_mut()?
|
||||
.clear(ClearBuffer::Input)
|
||||
.map_err(Self::map_serial_error)
|
||||
}
|
||||
}
|
||||
97
src/types.rs
97
src/types.rs
@@ -0,0 +1,97 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
||||
pub struct Force3D {
|
||||
pub fx: i16,
|
||||
pub fy: i16,
|
||||
pub fz: i16,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
||||
pub struct Force3F {
|
||||
pub fx: f32,
|
||||
pub fy: f32,
|
||||
pub fz: f32,
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
pub const SENSOR_MODULE_COUNT: usize = 28;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DistributionForce {
|
||||
pub module: SensorModule,
|
||||
pub point_count: u16,
|
||||
pub points: Vec<ForcePoint>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct CombinedForce {
|
||||
pub module: SensorModule,
|
||||
pub force: Force3D,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FingerSample {
|
||||
pub timestamp_us: u64,
|
||||
pub sequence: u32,
|
||||
pub combined_forces: Vec<CombinedForce>,
|
||||
pub distribution_forces: Vec<DistributionForce>,
|
||||
pub module_errors: Vec<ModuleError>,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
||||
pub struct ModuleError {
|
||||
pub module: u8,
|
||||
pub error_code: u16,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
||||
pub struct ForcePoint {
|
||||
pub fx: i8,
|
||||
pub fy: i8,
|
||||
pub fz: i8,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user