fix: protocol frame parsing, add debug feature, refactor read logic
This commit is contained in:
@@ -7,6 +7,10 @@ edition = "2024"
|
|||||||
name = "eskin_finger_sdk"
|
name = "eskin_finger_sdk"
|
||||||
crate-type = ["lib", "cdylib", "staticlib"]
|
crate-type = ["lib", "cdylib", "staticlib"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
debug = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = "0.4.44"
|
chrono = "0.4.44"
|
||||||
crc = "3.4.0"
|
crc = "3.4.0"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ impl Default for DeviceConfig {
|
|||||||
sample_capacity: 1024,
|
sample_capacity: 1024,
|
||||||
command_capacity: 64,
|
command_capacity: 64,
|
||||||
event_capacity: 128,
|
event_capacity: 128,
|
||||||
read_timeout_ms: 100,
|
read_timeout_ms: 1000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use crate::{
|
|||||||
config::{DeviceConfig, DeviceInfo},
|
config::{DeviceConfig, DeviceInfo},
|
||||||
error::SdkError,
|
error::SdkError,
|
||||||
protocol::{
|
protocol::{
|
||||||
EskinProtocolCodec, FRAME_CRC_LEN, FRAME_START_RESPONSE, FRAME_STATUS_LEN, ProtocolCodec,
|
EskinProtocolCodec, FRAME_START_RESPONSE, ProtocolCodec,
|
||||||
ReadRequest, WriteRequest,
|
ReadRequest, WriteRequest,
|
||||||
},
|
},
|
||||||
stream::StreamRuntime,
|
stream::StreamRuntime,
|
||||||
@@ -73,9 +73,19 @@ impl EskinDeviceInner {
|
|||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
) -> Result<(), SdkError> {
|
) -> Result<(), SdkError> {
|
||||||
|
let deadline = std::time::Instant::now()
|
||||||
|
+ timeout
|
||||||
|
.to_std()
|
||||||
|
.map_err(|_| SdkError::InvalidParameter("timeout must be non-negative".into()))?;
|
||||||
|
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
while offset < buf.len() {
|
while offset < buf.len() {
|
||||||
let n = transport.read(&mut buf[offset..], timeout)?;
|
let remaining = deadline
|
||||||
|
.checked_duration_since(std::time::Instant::now())
|
||||||
|
.unwrap_or(std::time::Duration::from_millis(1));
|
||||||
|
debug_println!("[device] read_exact: need {} bytes, have {} so far, remaining timeout: {:?}", buf.len() - offset, offset, remaining);
|
||||||
|
let n = transport.read(&mut buf[offset..], Duration::from_std(remaining).unwrap())?;
|
||||||
|
debug_println!("[device] read_exact: got {} bytes: {:02X?}", n, &buf[offset..offset + n]);
|
||||||
|
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return Err(SdkError::Timeout);
|
return Err(SdkError::Timeout);
|
||||||
@@ -94,19 +104,25 @@ impl EskinDeviceInner {
|
|||||||
let timeout = Duration::milliseconds(self.config.read_timeout_ms as i64);
|
let timeout = Duration::milliseconds(self.config.read_timeout_ms as i64);
|
||||||
let mut header = [0u8; 4];
|
let mut header = [0u8; 4];
|
||||||
Self::read_exact_from_transport(transport, &mut header, timeout)?;
|
Self::read_exact_from_transport(transport, &mut header, timeout)?;
|
||||||
|
debug_println!("[device] recv header: {:02X?}", header);
|
||||||
|
|
||||||
let start = u16::from_be_bytes([header[0], header[1]]);
|
// let start = u16::from_be_bytes([header[0], header[1]]);
|
||||||
|
let start = [header[0], header[1]];
|
||||||
if start != FRAME_START_RESPONSE {
|
if start != FRAME_START_RESPONSE {
|
||||||
return Err(SdkError::FrameError(format!(
|
return Err(SdkError::FrameError(format!(
|
||||||
"invalid response start: 0x{start:04X}"
|
"invalid response start: 0x{start:02X?}"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
debug_println!("h2: {:02X}, h3: {:02X}", header[2], header[3]);
|
||||||
let data_len = u16::from_le_bytes([header[2], header[3]]) as usize;
|
let data_len = u16::from_le_bytes([header[2], header[3]]) as usize;
|
||||||
let total_len = 4 + data_len + FRAME_STATUS_LEN + FRAME_CRC_LEN;
|
debug_println!("data_len: {data_len}");
|
||||||
|
let total_len = 5 + data_len;
|
||||||
|
// let total_len = data_len + 1;
|
||||||
|
|
||||||
let mut frame = vec![0u8; total_len];
|
let mut frame = vec![0u8; total_len];
|
||||||
frame[..4].copy_from_slice(&header);
|
frame[..4].copy_from_slice(&header);
|
||||||
Self::read_exact_from_transport(transport, &mut frame[4..], timeout)?;
|
Self::read_exact_from_transport(transport, &mut frame[4..], timeout)?;
|
||||||
|
debug_println!("[device] recv frame: {:02X?}", frame);
|
||||||
|
|
||||||
Ok(frame)
|
Ok(frame)
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/lib.rs
13
src/lib.rs
@@ -1,3 +1,16 @@
|
|||||||
|
/// Debug-only print macro. Only outputs when compiled with `--features debug`.
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! debug_println {
|
||||||
|
($($arg:tt)*) => { println!($($arg)*) };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "debug"))]
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! debug_println {
|
||||||
|
($($arg:tt)*) => {};
|
||||||
|
}
|
||||||
|
|
||||||
pub mod channel;
|
pub mod channel;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod device;
|
pub mod device;
|
||||||
|
|||||||
55
src/main.rs
55
src/main.rs
@@ -1,13 +1,60 @@
|
|||||||
use eskin_finger_sdk::{config::DeviceConfig, device::{EskinDevice, EskinDeviceInner}, transport::SerialPortTransport};
|
use eskin_finger_sdk::{config::DeviceConfig, device::{EskinDevice, EskinDeviceInner}, transport::SerialPortTransport};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let transport = SerialPortTransport::new("/dev/ttyUSB0", 921600);
|
let transport = SerialPortTransport::new("COM10", 921600);
|
||||||
let config = DeviceConfig::default();
|
let config = DeviceConfig::default();
|
||||||
let mut device = EskinDeviceInner::new(config, Box::new(transport));
|
let mut device = EskinDeviceInner::new(config, Box::new(transport));
|
||||||
device.open().unwrap();
|
device.open().unwrap();
|
||||||
|
|
||||||
let data = device.read_register(0x1C00, 168).unwrap();
|
// let data = device.read_register(0x1C00, 168).unwrap();
|
||||||
println!("Serial: {:?}", data);
|
// print_payload_data(&data);
|
||||||
|
|
||||||
|
read_hdv(&mut device);
|
||||||
|
read_check_group(&mut device);
|
||||||
|
read_row(&mut device);
|
||||||
|
write_col(&mut device, &[0x08]);
|
||||||
|
read_col(&mut device);
|
||||||
|
read_config(&mut device);
|
||||||
|
|
||||||
device.close().unwrap();
|
device.close().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_hdv(device: &mut EskinDeviceInner) {
|
||||||
|
let series_id = device.read_register(0, 2).unwrap();
|
||||||
|
print_payload_data(&series_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_check_group(device: &mut EskinDeviceInner) {
|
||||||
|
let group = device.read_register(0x000F, 1).unwrap();
|
||||||
|
print_payload_data(&group);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn read_row(device: &mut EskinDeviceInner) {
|
||||||
|
let row = device.read_register(0x0015, 1).unwrap();
|
||||||
|
print_payload_data(&row);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_col(device: &mut EskinDeviceInner, col: &[u8]) {
|
||||||
|
device.write_register(0x0014, col).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_col(device: &mut EskinDeviceInner) {
|
||||||
|
let col = device.read_register(0x0014, 1).unwrap();
|
||||||
|
print_payload_data(&col);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_config(device: &mut EskinDeviceInner) {
|
||||||
|
let conf = device.read_register(0x0017, 1).unwrap();
|
||||||
|
print_payload_data(&conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_payload_data(data: &[u8]) {
|
||||||
|
for (i, chunk) in data.chunks(2).enumerate() {
|
||||||
|
if chunk.len() == 2 {
|
||||||
|
let val = u16::from_le_bytes([chunk[0], chunk[1]]);
|
||||||
|
println!(" [{:3}] [{:02X}] [{:02X}] => {}", i, chunk[0], chunk[1], val);
|
||||||
|
} else {
|
||||||
|
println!(" [{:3}] [{:02X}] (odd byte)", i, chunk[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ use serde::{Deserialize, Serialize};
|
|||||||
use crate::error::SdkError;
|
use crate::error::SdkError;
|
||||||
|
|
||||||
pub const FRAME_START_REQUEST: [u8; 2] = [0x55, 0xAA];
|
pub const FRAME_START_REQUEST: [u8; 2] = [0x55, 0xAA];
|
||||||
pub const FRAME_START_RESPONSE: u16 = 0xAA55;
|
pub const FRAME_START_RESPONSE: [u8; 2] = [0xAA, 0x55];
|
||||||
|
|
||||||
pub const FUNC_READ: u8 = 0xFB;
|
pub const FUNC_READ: u8 = 0xFB;
|
||||||
pub const FUNC_WRITE: u8 = 0x79;
|
pub const FUNC_WRITE: u8 = 0x79;
|
||||||
pub const FUNC_RESPONSE_READ: u8 = 0xFF;
|
pub const FUNC_RESPONSE_READ: u8 = 0xFB;
|
||||||
pub const FUNC_RESPONSE_WRITE: u8 = 0xF9;
|
pub const FUNC_RESPONSE_WRITE: u8 = 0xF9;
|
||||||
|
|
||||||
pub const FRAME_HEADER_LEN: usize = 13;
|
pub const FRAME_HEADER_LEN: usize = 13;
|
||||||
@@ -45,7 +45,7 @@ pub enum FrameFunction {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ProtocolFrame {
|
pub struct ProtocolFrame {
|
||||||
pub start: u16,
|
pub start: [u8; 2],
|
||||||
pub device_addr: u8,
|
pub device_addr: u8,
|
||||||
pub function: u8,
|
pub function: u8,
|
||||||
pub start_addr: u32,
|
pub start_addr: u32,
|
||||||
@@ -154,7 +154,7 @@ impl ProtocolCodec for EskinProtocolCodec {
|
|||||||
|
|
||||||
let crc = self.crc8(&frame);
|
let crc = self.crc8(&frame);
|
||||||
frame.push(crc);
|
frame.push(crc);
|
||||||
println!("send: {:02X?}", frame);
|
debug_println!("send: {:02X?}", frame);
|
||||||
Ok(frame)
|
Ok(frame)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,15 +197,17 @@ impl ProtocolCodec for EskinProtocolCodec {
|
|||||||
return Err(SdkError::FrameError("read response too short".into()));
|
return Err(SdkError::FrameError("read response too short".into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let start = Self::read_u16_le(frame, 0)?;
|
// let start = Self::read_u16_le(frame, 0)?;
|
||||||
|
let start = &frame[0..2];
|
||||||
if start != FRAME_START_RESPONSE {
|
if start != FRAME_START_RESPONSE {
|
||||||
return Err(SdkError::FrameError(format!(
|
return Err(SdkError::FrameError(format!(
|
||||||
"invalid response start: 0x{start:04X}"
|
"invalid response start: 0x{start:02X?}"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug_println!("get resp");
|
||||||
let data_len = Self::read_u16_le(frame, 2)? as usize;
|
let data_len = Self::read_u16_le(frame, 2)? as usize;
|
||||||
let expected_len = 2 + 2 + data_len + FRAME_STATUS_LEN + FRAME_CRC_LEN;
|
let expected_len = 2 + 2 + 1 + data_len;
|
||||||
|
|
||||||
if frame.len() != expected_len {
|
if frame.len() != expected_len {
|
||||||
return Err(SdkError::FrameError(format!(
|
return Err(SdkError::FrameError(format!(
|
||||||
@@ -234,13 +236,13 @@ impl ProtocolCodec for EskinProtocolCodec {
|
|||||||
|
|
||||||
let start_addr = Self::read_u32_le(frame, 7)?;
|
let start_addr = Self::read_u32_le(frame, 7)?;
|
||||||
let read_len = Self::read_u16_le(frame, 11)? as usize;
|
let read_len = Self::read_u16_le(frame, 11)? as usize;
|
||||||
if data_len != 9 + read_len {
|
if data_len != 10 + read_len {
|
||||||
return Err(SdkError::FrameError(format!(
|
return Err(SdkError::FrameError(format!(
|
||||||
"read response data length mismatch: header data_len {data_len}, payload len {read_len}"
|
"read response data length mismatch: header data_len {data_len}, payload len {read_len}"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let payload_start = 13;
|
let payload_start = 14;
|
||||||
let payload_end = payload_start + read_len;
|
let payload_end = payload_start + read_len;
|
||||||
|
|
||||||
let data = frame
|
let data = frame
|
||||||
@@ -248,7 +250,7 @@ impl ProtocolCodec for EskinProtocolCodec {
|
|||||||
.ok_or_else(|| SdkError::FrameError("read response payload missing".into()))?
|
.ok_or_else(|| SdkError::FrameError("read response payload missing".into()))?
|
||||||
.to_vec();
|
.to_vec();
|
||||||
|
|
||||||
let status_offset = 4 + data_len;
|
let status_offset = 13;
|
||||||
let status_raw = *frame
|
let status_raw = *frame
|
||||||
.get(status_offset)
|
.get(status_offset)
|
||||||
.ok_or_else(|| SdkError::FrameError("read response status missing".into()))?;
|
.ok_or_else(|| SdkError::FrameError("read response status missing".into()))?;
|
||||||
@@ -271,15 +273,15 @@ impl ProtocolCodec for EskinProtocolCodec {
|
|||||||
return Err(SdkError::FrameError("write response too short".into()));
|
return Err(SdkError::FrameError("write response too short".into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let start = Self::read_u16_le(frame, 0)?;
|
let start = &frame[..2];
|
||||||
if start != FRAME_START_RESPONSE {
|
if start != FRAME_START_RESPONSE {
|
||||||
return Err(SdkError::FrameError(format!(
|
return Err(SdkError::FrameError(format!(
|
||||||
"invalid response start: 0x{start:04X}"
|
"invalid response start: 0x{start:02X?}"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let data_len = Self::read_u16_le(frame, 2)? as usize;
|
let data_len = Self::read_u16_le(frame, 2)? as usize;
|
||||||
let expected_len = 2 + 2 + data_len + FRAME_STATUS_LEN + FRAME_CRC_LEN;
|
let expected_len = 2 + 2 + 1 + data_len;
|
||||||
|
|
||||||
if frame.len() != expected_len {
|
if frame.len() != expected_len {
|
||||||
return Err(SdkError::FrameError(format!(
|
return Err(SdkError::FrameError(format!(
|
||||||
@@ -307,13 +309,13 @@ impl ProtocolCodec for EskinProtocolCodec {
|
|||||||
|
|
||||||
let start_addr = Self::read_u32_le(frame, 7)?;
|
let start_addr = Self::read_u32_le(frame, 7)?;
|
||||||
let return_byte_count = Self::read_u16_le(frame, 11)?;
|
let return_byte_count = Self::read_u16_le(frame, 11)?;
|
||||||
if data_len != 9 {
|
if data_len != 10 {
|
||||||
return Err(SdkError::FrameError(format!(
|
return Err(SdkError::FrameError(format!(
|
||||||
"write response data length mismatch: expected 9, got {data_len}"
|
"write response data length mismatch: expected 9, got {data_len}"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let status_offset = 4 + data_len;
|
let status_offset = 13;
|
||||||
let status_raw = *frame
|
let status_raw = *frame
|
||||||
.get(status_offset)
|
.get(status_offset)
|
||||||
.ok_or_else(|| SdkError::FrameError("write response status missing".into()))?;
|
.ok_or_else(|| SdkError::FrameError("write response status missing".into()))?;
|
||||||
@@ -336,10 +338,10 @@ impl ProtocolCodec for EskinProtocolCodec {
|
|||||||
return Err(SdkError::FrameError("stream frame too short".into()));
|
return Err(SdkError::FrameError("stream frame too short".into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let start = Self::read_u16_le(frame, 0)?;
|
let start: [u8; 2] = frame[..2].try_into().unwrap();
|
||||||
if start != FRAME_START_RESPONSE {
|
if start != FRAME_START_RESPONSE {
|
||||||
return Err(SdkError::FrameError(format!(
|
return Err(SdkError::FrameError(format!(
|
||||||
"invalid stream frame start: 0x{start:04X}"
|
"invalid stream frame start: 0x{start:02X?}"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,7 +375,7 @@ impl ProtocolCodec for EskinProtocolCodec {
|
|||||||
.ok_or_else(|| SdkError::FrameError("stream payload missing".into()))?
|
.ok_or_else(|| SdkError::FrameError("stream payload missing".into()))?
|
||||||
.to_vec();
|
.to_vec();
|
||||||
|
|
||||||
let status_offset = 4 + data_len;
|
let status_offset = 13;
|
||||||
let status_raw = *frame
|
let status_raw = *frame
|
||||||
.get(status_offset)
|
.get(status_offset)
|
||||||
.ok_or_else(|| SdkError::FrameError("stream status missing".into()))?;
|
.ok_or_else(|| SdkError::FrameError("stream status missing".into()))?;
|
||||||
|
|||||||
@@ -238,9 +238,17 @@ impl PollingSampleCollector {
|
|||||||
buf: &mut [u8],
|
buf: &mut [u8],
|
||||||
timeout: Duration,
|
timeout: Duration,
|
||||||
) -> Result<(), SdkError> {
|
) -> Result<(), SdkError> {
|
||||||
|
let deadline = std::time::Instant::now()
|
||||||
|
+ timeout
|
||||||
|
.to_std()
|
||||||
|
.map_err(|_| SdkError::InvalidParameter("timeout must be non-negative".into()))?;
|
||||||
|
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
while offset < buf.len() {
|
while offset < buf.len() {
|
||||||
let n = transport.read(&mut buf[offset..], timeout)?;
|
let remaining = deadline
|
||||||
|
.checked_duration_since(std::time::Instant::now())
|
||||||
|
.unwrap_or(std::time::Duration::from_millis(1));
|
||||||
|
let n = transport.read(&mut buf[offset..], Duration::from_std(remaining).unwrap())?;
|
||||||
|
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return Err(SdkError::Timeout);
|
return Err(SdkError::Timeout);
|
||||||
@@ -259,11 +267,12 @@ impl PollingSampleCollector {
|
|||||||
|
|
||||||
let mut header = [0u8; 4];
|
let mut header = [0u8; 4];
|
||||||
Self::read_exact_from_transport(transport, &mut header, timeout)?;
|
Self::read_exact_from_transport(transport, &mut header, timeout)?;
|
||||||
|
debug_println!("[stream] recv header: {:02X?}", header);
|
||||||
|
|
||||||
let start = u16::from_be_bytes([header[0], header[1]]);
|
let start = [header[0], header[1]];
|
||||||
if start != FRAME_START_RESPONSE {
|
if start != FRAME_START_RESPONSE {
|
||||||
return Err(SdkError::FrameError(format!(
|
return Err(SdkError::FrameError(format!(
|
||||||
"invalid response start: 0x{start:04X}"
|
"invalid response start: 0x{start:02X?}"
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,6 +283,7 @@ impl PollingSampleCollector {
|
|||||||
frame[..4].copy_from_slice(&header);
|
frame[..4].copy_from_slice(&header);
|
||||||
|
|
||||||
Self::read_exact_from_transport(transport, &mut frame[4..], timeout)?;
|
Self::read_exact_from_transport(transport, &mut frame[4..], timeout)?;
|
||||||
|
debug_println!("[stream] recv frame: {:02X?}", frame);
|
||||||
|
|
||||||
Ok(frame)
|
Ok(frame)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user