From 96f1e7db1e08b7a66baeb314ea012641ecfd075a Mon Sep 17 00:00:00 2001 From: lenn Date: Thu, 7 May 2026 10:00:04 +0800 Subject: [PATCH] feat: add EskinDeviceFunc FFI bindings and update Python/README - Add FFI wrappers for all EskinDeviceFunc trait methods: eskin_read_hdw_version, eskin_read_matrix_row/col, eskin_read_device_config1/2, eskin_write_device_config1/2, eskin_write_matrix_row/col - Extract sdk_error_to_code() helper for SdkError -> SdkErrorCode conversion - Update C header (include/eskin_ffi.h) with new function declarations - Update Python FFI bindings (example/python/eskin_ffi.py) with new methods - Update README with Python usage instructions and full FFI interface table --- README.md | 25 +++- example/python/eskin_ffi.py | 137 ++++++++++++++++++++- example/python/example.py | 51 ++++++++ include/eskin_ffi.h | 53 ++++++++ src/device.rs | 2 +- src/ffi/mod.rs | 235 ++++++++++++++++++++++++++++++++++-- src/main.rs | 5 +- 7 files changed, 490 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 6bc6890..65368d3 100644 --- a/README.md +++ b/README.md @@ -98,18 +98,30 @@ int main() { ## 使用 Python +### 构建 & 准备 + ```bash cargo build --release +# 将生成的 .so 文件复制到 python 示例目录下 +cp target/release/libeskin_finger_sdk.so example/python/ cd example/python -LD_LIBRARY_PATH=../../target/release python3 example.py +python3 example.py ``` +> **注意:** Python 示例默认从当前目录加载 `libeskin_finger_sdk.so`,请确保 `.so` 文件已复制到 `example/python/` 目录下,或修改 `example.py` 中的 `LIB_PATH` 指向正确的路径。 + ```python from eskin_ffi import EskinDevice -with EskinDevice("target/release/libeskin_finger_sdk.so") as dev: +with EskinDevice("libeskin_finger_sdk.so") as dev: dev.open("/dev/ttyUSB0") + # 读取硬件版本 + print(f"Hardware version: {dev.read_hdw_version()}") + + # 读取矩阵尺寸 + print(f"Matrix: {dev.read_matrix_row()} x {dev.read_matrix_col()}") + # 读寄存器 data = dev.read_register(0x0000, 4) print(f"Serial: {data.hex()}") @@ -129,6 +141,15 @@ with EskinDevice("target/release/libeskin_finger_sdk.so") as dev: | `eskin_close(handle)` | 关闭设备,释放 handle | | `eskin_read_register(handle, addr, length, buf, buf_len, actual_len)` | 读寄存器原始字节 | | `eskin_write_register(handle, addr, data, data_len, return_count)` | 写寄存器原始字节 | +| `eskin_read_hdw_version(handle, buf, buf_len, actual_len)` | 读取硬件版本号 | +| `eskin_read_matrix_row(handle, out)` | 读取矩阵行数 | +| `eskin_read_matrix_col(handle, out)` | 读取矩阵列数 | +| `eskin_read_device_config1(handle, out)` | 读取设备配置寄存器1 | +| `eskin_read_device_config2(handle, out)` | 读取设备配置寄存器2 | +| `eskin_write_device_config1(handle, enable, return_count)` | 写入设备配置寄存器1 | +| `eskin_write_device_config2(handle, enable, return_count)` | 写入设备配置寄存器2 | +| `eskin_write_matrix_row(handle, row, return_count)` | 写入矩阵行数 | +| `eskin_write_matrix_col(handle, col, return_count)` | 写入矩阵列数 | --- diff --git a/example/python/eskin_ffi.py b/example/python/eskin_ffi.py index 84fdc99..3e655da 100644 --- a/example/python/eskin_ffi.py +++ b/example/python/eskin_ffi.py @@ -1,9 +1,10 @@ import ctypes from ctypes import ( - Structure, POINTER, c_void_p, c_char_p, c_uint8, c_uint16, + Structure, POINTER, c_void_p, c_char, c_char_p, c_uint8, c_uint16, c_uint32, c_uint64, c_int16, c_bool ) +LIB_PATH = "./libeskin_finger_sdk.so" class EskinSdkVersion(Structure): _fields_ = [ @@ -14,8 +15,8 @@ class EskinSdkVersion(Structure): class EskinDevice: - def __init__(self, lib_path: str): - self._lib = ctypes.CDLL(lib_path) + def __init__(self): + self._lib = ctypes.CDLL(LIB_PATH) self._setup_functions() self._handle = None @@ -42,6 +43,53 @@ class EskinDevice: c_void_p, c_uint32, POINTER(c_uint8), c_uint16, POINTER(c_uint16) ] + # EskinDeviceFunc bindings + + lib.eskin_read_hdw_version.restype = c_uint32 + lib.eskin_read_hdw_version.argtypes = [ + c_void_p, POINTER(c_char), c_uint32, POINTER(c_uint32) + ] + + lib.eskin_read_matrix_row.restype = c_uint32 + lib.eskin_read_matrix_row.argtypes = [ + c_void_p, POINTER(c_uint8) + ] + + lib.eskin_read_matrix_col.restype = c_uint32 + lib.eskin_read_matrix_col.argtypes = [ + c_void_p, POINTER(c_uint8) + ] + + lib.eskin_read_device_config1.restype = c_uint32 + lib.eskin_read_device_config1.argtypes = [ + c_void_p, POINTER(c_uint8) + ] + + lib.eskin_read_device_config2.restype = c_uint32 + lib.eskin_read_device_config2.argtypes = [ + c_void_p, POINTER(c_uint8) + ] + + lib.eskin_write_device_config1.restype = c_uint32 + lib.eskin_write_device_config1.argtypes = [ + c_void_p, c_bool, POINTER(c_uint16) + ] + + lib.eskin_write_device_config2.restype = c_uint32 + lib.eskin_write_device_config2.argtypes = [ + c_void_p, c_bool, POINTER(c_uint16) + ] + + lib.eskin_write_matrix_row.restype = c_uint32 + lib.eskin_write_matrix_row.argtypes = [ + c_void_p, c_uint8, POINTER(c_uint16) + ] + + lib.eskin_write_matrix_col.restype = c_uint32 + lib.eskin_write_matrix_col.argtypes = [ + c_void_p, c_uint8, POINTER(c_uint16) + ] + def version(self) -> tuple: v = self._lib.eskin_version() return (v.major, v.minor, v.patch) @@ -79,6 +127,89 @@ class EskinDevice: raise RuntimeError(f"write_register failed: error={err}") return ret.value + def read_hdw_version(self) -> str: + """读取硬件版本号""" + buf = (c_char * 64)() + actual = c_uint32(0) + err = self._lib.eskin_read_hdw_version( + self._handle, buf, len(buf), ctypes.byref(actual) + ) + if err != 0: + raise RuntimeError(f"read_hdw_version failed: error={err}") + return buf[:actual.value].decode("utf-8") + + def read_matrix_row(self) -> int: + """读取矩阵行数""" + out = c_uint8(0) + err = self._lib.eskin_read_matrix_row(self._handle, ctypes.byref(out)) + if err != 0: + raise RuntimeError(f"read_matrix_row failed: error={err}") + return out.value + + def read_matrix_col(self) -> int: + """读取矩阵列数""" + out = c_uint8(0) + err = self._lib.eskin_read_matrix_col(self._handle, ctypes.byref(out)) + if err != 0: + raise RuntimeError(f"read_matrix_col failed: error={err}") + return out.value + + def read_device_config1(self) -> int: + """读取设备配置寄存器1""" + out = c_uint8(0) + err = self._lib.eskin_read_device_config1(self._handle, ctypes.byref(out)) + if err != 0: + raise RuntimeError(f"read_device_config1 failed: error={err}") + return out.value + + def read_device_config2(self) -> int: + """读取设备配置寄存器2""" + out = c_uint8(0) + err = self._lib.eskin_read_device_config2(self._handle, ctypes.byref(out)) + if err != 0: + raise RuntimeError(f"read_device_config2 failed: error={err}") + return out.value + + def write_device_config1(self, enable: bool) -> int: + """写入设备配置寄存器1""" + ret = c_uint16(0) + err = self._lib.eskin_write_device_config1( + self._handle, enable, ctypes.byref(ret) + ) + if err != 0: + raise RuntimeError(f"write_device_config1 failed: error={err}") + return ret.value + + def write_device_config2(self, enable: bool) -> int: + """写入设备配置寄存器2""" + ret = c_uint16(0) + err = self._lib.eskin_write_device_config2( + self._handle, enable, ctypes.byref(ret) + ) + if err != 0: + raise RuntimeError(f"write_device_config2 failed: error={err}") + return ret.value + + def write_matrix_row(self, row: int) -> int: + """写入矩阵行数""" + ret = c_uint16(0) + err = self._lib.eskin_write_matrix_row( + self._handle, row, ctypes.byref(ret) + ) + if err != 0: + raise RuntimeError(f"write_matrix_row failed: error={err}") + return ret.value + + def write_matrix_col(self, col: int) -> int: + """写入矩阵列数""" + ret = c_uint16(0) + err = self._lib.eskin_write_matrix_col( + self._handle, col, ctypes.byref(ret) + ) + if err != 0: + raise RuntimeError(f"write_matrix_col failed: error={err}") + return ret.value + def __enter__(self): return self diff --git a/example/python/example.py b/example/python/example.py index e69de29..60b2e7e 100644 --- a/example/python/example.py +++ b/example/python/example.py @@ -0,0 +1,51 @@ +from eskin_ffi import EskinDevice + +def main(): + dev = EskinDevice() + + # SDK 版本 + ver = dev.version() + print(f"ESkin SDK version: {ver[0]}.{ver[1]}.{ver[2]}") + + # 打开设备 + dev.open("/dev/ttyUSB0") + print("Device opened") + + try: + # 读取硬件版本 + hdw_ver = dev.read_hdw_version() + print(f"Hardware version: {hdw_ver}") + + # 读取矩阵尺寸 + row = dev.read_matrix_row() + col = dev.read_matrix_col() + print(f"Matrix size: {row} x {col}") + + # 读取设备配置 + cfg1 = dev.read_device_config1() + # cfg2 = dev.read_device_config2() + print(f"Device config1: 0x{cfg1:02X}") + # print(f"Device config2: 0x{cfg2:02X}") + + # 写入矩阵尺寸示例 + # ret = dev.write_matrix_row(16) + # print(f"Write matrix row: returned {ret} bytes") + # ret = dev.write_matrix_col(16) + # print(f"Write matrix col: returned {ret} bytes") + + # 写入设备配置示例 + # ret = dev.write_device_config1(True) + # print(f"Write device config1: returned {ret} bytes") + # ret = dev.write_device_config2(False) + # print(f"Write device config2: returned {ret} bytes") + + # 原始寄存器读写 + data = dev.read_register(0x1C00, 168) + print(f"Serial number: {data.hex().upper()}") + + finally: + dev.close() + print("Device closed") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/include/eskin_ffi.h b/include/eskin_ffi.h index 9ef949c..b2deb1f 100644 --- a/include/eskin_ffi.h +++ b/include/eskin_ffi.h @@ -60,6 +60,59 @@ EskinSdkErrorCode eskin_write_register( uint16_t* return_count ); +// Device function interfaces (EskinDeviceFunc) + +EskinSdkErrorCode eskin_read_hdw_version( + EskinDeviceHandle handle, + char* buf, + uint32_t buf_len, + uint32_t* actual_len +); + +EskinSdkErrorCode eskin_read_matrix_row( + EskinDeviceHandle handle, + uint8_t* out +); + +EskinSdkErrorCode eskin_read_matrix_col( + EskinDeviceHandle handle, + uint8_t* out +); + +EskinSdkErrorCode eskin_read_device_config1( + EskinDeviceHandle handle, + uint8_t* out +); + +EskinSdkErrorCode eskin_read_device_config2( + EskinDeviceHandle handle, + uint8_t* out +); + +EskinSdkErrorCode eskin_write_device_config1( + EskinDeviceHandle handle, + bool enable, + uint16_t* return_count +); + +EskinSdkErrorCode eskin_write_device_config2( + EskinDeviceHandle handle, + bool enable, + uint16_t* return_count +); + +EskinSdkErrorCode eskin_write_matrix_row( + EskinDeviceHandle handle, + uint8_t row, + uint16_t* return_count +); + +EskinSdkErrorCode eskin_write_matrix_col( + EskinDeviceHandle handle, + uint8_t col, + uint16_t* return_count +); + #ifdef __cplusplus } #endif diff --git a/src/device.rs b/src/device.rs index 2ded23d..dab3f58 100644 --- a/src/device.rs +++ b/src/device.rs @@ -211,7 +211,7 @@ impl EskinDeviceFunc for EskinDeviceInner { fn read_device_config2(&mut self) -> Result { let enabled = self.read_register(0x0018, 1) - .map_err(|_| SdkError::FrameError("read device config1 failed".into()))?; + .map_err(|_| SdkError::FrameError("read device config2 failed".into()))?; Ok(enabled[0]) } diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 3c9897c..2778445 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -1,6 +1,7 @@ use std::{ptr}; use std::ffi::{CStr, c_char}; use crate::device::EskinDevice; +use crate::device::EskinDeviceFunc; use crate::transport::SerialPortTransport; use crate::{config::DeviceConfig, device::EskinDeviceInner, error::SdkErrorCode}; @@ -31,6 +32,27 @@ struct DeviceWrapper { last_me_raw: Vec } +fn sdk_error_to_code(err: crate::error::SdkError) -> SdkErrorCode { + match err { + crate::error::SdkError::Timeout => SdkErrorCode::Timeout, + crate::error::SdkError::FrameError(_) => SdkErrorCode::FrameError, + crate::error::SdkError::CrcError { .. } => SdkErrorCode::CrcError, + crate::error::SdkError::DeviceError(_) => SdkErrorCode::DeviceError, + crate::error::SdkError::IoError(_) => SdkErrorCode::IoError, + crate::error::SdkError::NotInitialized => SdkErrorCode::NotInitialized, + crate::error::SdkError::AlreadyStreaming => SdkErrorCode::AlreadyStreaming, + crate::error::SdkError::NotStreaming => SdkErrorCode::NotStreaming, + crate::error::SdkError::DeviceNotFound(_) => SdkErrorCode::DeviceNotFound, + crate::error::SdkError::DeviceAlreadyOpen => SdkErrorCode::DeviceAlreadyOpen, + crate::error::SdkError::ConfigError(_) => SdkErrorCode::ConfigError, + crate::error::SdkError::ChannelClosed => SdkErrorCode::ChannelClosed, + crate::error::SdkError::InternalError(_) => SdkErrorCode::InternalError, + crate::error::SdkError::BufferOverflow(_) => SdkErrorCode::BufferOverflow, + crate::error::SdkError::InvalidParameter(_) => SdkErrorCode::InvalidParameter, + crate::error::SdkError::ProtocolError(_) => SdkErrorCode::ProtocolError, + } +} + #[unsafe(no_mangle)] pub extern "C" fn eskin_version() -> EskinSdkVersion { EskinSdkVersion { major: 0, minor: 1, patch: 0 } @@ -111,11 +133,7 @@ pub unsafe extern "C" fn eskin_read_register( let data = match wrapper.device.read_register(addr, length) { Ok(d) => d, - Err(crate::error::SdkError::Timeout) => return SdkErrorCode::Timeout, - Err(crate::error::SdkError::FrameError(_)) => return SdkErrorCode::FrameError, - Err(crate::error::SdkError::CrcError { .. }) => return SdkErrorCode::CrcError, - Err(crate::error::SdkError::DeviceError(_)) => return SdkErrorCode::DeviceError, - Err(_) => return SdkErrorCode::IoError, + Err(e) => return sdk_error_to_code(e), }; let copy_len = std::cmp::min(data.len(), buf_len as usize); @@ -148,10 +166,207 @@ pub unsafe extern "C" fn eskin_write_register( unsafe { *return_count = count }; SdkErrorCode::Success } - Err(crate::error::SdkError::Timeout) => SdkErrorCode::Timeout, - Err(crate::error::SdkError::FrameError(_)) => SdkErrorCode::FrameError, - Err(crate::error::SdkError::CrcError { .. }) => SdkErrorCode::CrcError, - Err(crate::error::SdkError::DeviceError(_)) => SdkErrorCode::DeviceError, - Err(_) => SdkErrorCode::IoError, + Err(e) => sdk_error_to_code(e), + } +} + +/// 读取硬件版本,写入 buf 中,以 null 结尾 +#[unsafe(no_mangle)] +pub unsafe extern "C" fn eskin_read_hdw_version( + handle: EskinDeviceHandle, + buf: *mut c_char, + buf_len: u32, + actual_len: *mut u32, +) -> SdkErrorCode { + if handle.is_null() || buf.is_null() || actual_len.is_null() { + return SdkErrorCode::InvalidPointer; + } + + let wrapper = unsafe { &mut *(handle as *mut DeviceWrapper) }; + + match wrapper.device.read_hdw_version() { + Ok(version) => { + let bytes = version.as_bytes(); + let copy_len = std::cmp::min(bytes.len(), (buf_len as usize).saturating_sub(1)); + unsafe { + ptr::copy_nonoverlapping(bytes.as_ptr(), buf as *mut u8, copy_len); + *buf.add(copy_len) = 0; // null terminator + *actual_len = bytes.len() as u32; + } + SdkErrorCode::Success + } + Err(e) => sdk_error_to_code(e), + } +} + +/// 读取矩阵行数 +#[unsafe(no_mangle)] +pub unsafe extern "C" fn eskin_read_matrix_row( + handle: EskinDeviceHandle, + out: *mut u8, +) -> SdkErrorCode { + if handle.is_null() || out.is_null() { + return SdkErrorCode::InvalidPointer; + } + + let wrapper = unsafe { &mut *(handle as *mut DeviceWrapper) }; + + match wrapper.device.read_matrix_row() { + Ok(row) => { + unsafe { *out = row }; + SdkErrorCode::Success + } + Err(e) => sdk_error_to_code(e), + } +} + +/// 读取矩阵列数 +#[unsafe(no_mangle)] +pub unsafe extern "C" fn eskin_read_matrix_col( + handle: EskinDeviceHandle, + out: *mut u8, +) -> SdkErrorCode { + if handle.is_null() || out.is_null() { + return SdkErrorCode::InvalidPointer; + } + + let wrapper = unsafe { &mut *(handle as *mut DeviceWrapper) }; + + match wrapper.device.read_matrix_col() { + Ok(col) => { + unsafe { *out = col }; + SdkErrorCode::Success + } + Err(e) => sdk_error_to_code(e), + } +} + +/// 读取设备配置寄存器1 +#[unsafe(no_mangle)] +pub unsafe extern "C" fn eskin_read_device_config1( + handle: EskinDeviceHandle, + out: *mut u8, +) -> SdkErrorCode { + if handle.is_null() || out.is_null() { + return SdkErrorCode::InvalidPointer; + } + + let wrapper = unsafe { &mut *(handle as *mut DeviceWrapper) }; + + match wrapper.device.read_device_config1() { + Ok(val) => { + unsafe { *out = val }; + SdkErrorCode::Success + } + Err(e) => sdk_error_to_code(e), + } +} + +/// 读取设备配置寄存器2 +#[unsafe(no_mangle)] +pub unsafe extern "C" fn eskin_read_device_config2( + handle: EskinDeviceHandle, + out: *mut u8, +) -> SdkErrorCode { + if handle.is_null() || out.is_null() { + return SdkErrorCode::InvalidPointer; + } + + let wrapper = unsafe { &mut *(handle as *mut DeviceWrapper) }; + + match wrapper.device.read_device_config2() { + Ok(val) => { + unsafe { *out = val }; + SdkErrorCode::Success + } + Err(e) => sdk_error_to_code(e), + } +} + +/// 写入设备配置寄存器1 +#[unsafe(no_mangle)] +pub unsafe extern "C" fn eskin_write_device_config1( + handle: EskinDeviceHandle, + enable: bool, + return_count: *mut u16, +) -> SdkErrorCode { + if handle.is_null() || return_count.is_null() { + return SdkErrorCode::InvalidPointer; + } + + let wrapper = unsafe { &mut *(handle as *mut DeviceWrapper) }; + + match wrapper.device.write_device_config1(enable) { + Ok(count) => { + unsafe { *return_count = count }; + SdkErrorCode::Success + } + Err(e) => sdk_error_to_code(e), + } +} + +/// 写入设备配置寄存器2 +#[unsafe(no_mangle)] +pub unsafe extern "C" fn eskin_write_device_config2( + handle: EskinDeviceHandle, + enable: bool, + return_count: *mut u16, +) -> SdkErrorCode { + if handle.is_null() || return_count.is_null() { + return SdkErrorCode::InvalidPointer; + } + + let wrapper = unsafe { &mut *(handle as *mut DeviceWrapper) }; + + match wrapper.device.write_device_config2(enable) { + Ok(count) => { + unsafe { *return_count = count }; + SdkErrorCode::Success + } + Err(e) => sdk_error_to_code(e), + } +} + +/// 写入矩阵行数 +#[unsafe(no_mangle)] +pub unsafe extern "C" fn eskin_write_matrix_row( + handle: EskinDeviceHandle, + row: u8, + return_count: *mut u16, +) -> SdkErrorCode { + if handle.is_null() || return_count.is_null() { + return SdkErrorCode::InvalidPointer; + } + + let wrapper = unsafe { &mut *(handle as *mut DeviceWrapper) }; + + match wrapper.device.write_matrix_row(row) { + Ok(count) => { + unsafe { *return_count = count }; + SdkErrorCode::Success + } + Err(e) => sdk_error_to_code(e), + } +} + +/// 写入矩阵列数 +#[unsafe(no_mangle)] +pub unsafe extern "C" fn eskin_write_matrix_col( + handle: EskinDeviceHandle, + col: u8, + return_count: *mut u16, +) -> SdkErrorCode { + if handle.is_null() || return_count.is_null() { + return SdkErrorCode::InvalidPointer; + } + + let wrapper = unsafe { &mut *(handle as *mut DeviceWrapper) }; + + match wrapper.device.write_matrix_col(col) { + Ok(count) => { + unsafe { *return_count = count }; + SdkErrorCode::Success + } + Err(e) => sdk_error_to_code(e), } } diff --git a/src/main.rs b/src/main.rs index a580f75..c528fda 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ -use eskin_finger_sdk::{config::DeviceConfig, device::{EskinDevice, EskinDeviceInner}, transport::SerialPortTransport}; +use eskin_finger_sdk::{config::DeviceConfig, device::{EskinDevice, EskinDeviceFunc, EskinDeviceInner}, transport::SerialPortTransport}; fn main() { - let transport = SerialPortTransport::new("COM10", 921600); + let transport = SerialPortTransport::new("/dev/ttyUSB0", 921600); let config = DeviceConfig::default(); let mut device = EskinDeviceInner::new(config, Box::new(transport)); device.open().unwrap(); @@ -15,6 +15,7 @@ fn main() { read_col(&mut device); read_config(&mut device); + device.read_device_config2().unwrap(); device.close().unwrap(); }