This commit is contained in:
Lenn Louis
2026-05-04 22:42:00 +08:00
parent 985002c96d
commit 79f4055959
14 changed files with 1440 additions and 4 deletions

144
src/channel.rs Normal file
View 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
View 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
View 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
View 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
View 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;
}

View File

@@ -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
View 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
View 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
View 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
View 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)
}
}

View File

@@ -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,
}