feat: add FFI layer, protocol tests, mock transport, README

- FFI: eskin_open/close/read_register/write_register for C/C++/Python
- Protocol: encode/decode tests with golden bytes verification
- Stream: implement PollingSampleCollector producing FingerSample
- Register: add parse_combined_forces/parse_module_errors
- Transport: add MockSerialTransport for testing
- Include: add C header file eskin_ffi.h
- Examples: C++ and Python usage examples
- README: full usage guide for Rust/C++/Python
- Exclude docs/ from repo (internal only)
This commit is contained in:
lenn
2026-05-06 00:54:44 +08:00
parent 60f9ad15e7
commit a7b7192341
13 changed files with 721 additions and 1915 deletions

View File

@@ -0,0 +1,86 @@
import ctypes
from ctypes import (
Structure, POINTER, c_void_p, c_char_p, c_uint8, c_uint16,
c_uint32, c_uint64, c_int16, c_bool
)
class EskinSdkVersion(Structure):
_fields_ = [
("major", c_uint16),
("minor", c_uint16),
("patch", c_uint16),
]
class EskinDevice:
def __init__(self, lib_path: str):
self._lib = ctypes.CDLL(lib_path)
self._setup_functions()
self._handle = None
def _setup_functions(self):
lib = self._lib
lib.eskin_version.restype = EskinSdkVersion
lib.eskin_version.argtypes = []
lib.eskin_open.restype = c_void_p
lib.eskin_open.argtypes = [c_char_p, c_void_p]
lib.eskin_close.restype = c_uint32
lib.eskin_close.argtypes = [c_void_p]
lib.eskin_read_register.restype = c_uint32
lib.eskin_read_register.argtypes = [
c_void_p, c_uint32, c_uint16,
POINTER(c_uint8), c_uint32, POINTER(c_uint32)
]
lib.eskin_write_register.restype = c_uint32
lib.eskin_write_register.argtypes = [
c_void_p, c_uint32, POINTER(c_uint8), c_uint16, POINTER(c_uint16)
]
def version(self) -> tuple:
v = self._lib.eskin_version()
return (v.major, v.minor, v.patch)
def open(self, path: str):
handle = self._lib.eskin_open(path.encode("utf-8"), None)
if not handle:
raise RuntimeError(f"Failed to open device: {path}")
self._handle = handle
def close(self):
if self._handle:
self._lib.eskin_close(self._handle)
self._handle = None
def read_register(self, addr: int, length: int) -> bytes:
"""读寄存器,返回原始字节"""
buf = (c_uint8 * 256)()
actual = c_uint32(0)
err = self._lib.eskin_read_register(
self._handle, addr, length, buf, len(buf), ctypes.byref(actual)
)
if err != 0:
raise RuntimeError(f"read_register failed: error={err}")
return bytes(buf[:actual.value])
def write_register(self, addr: int, data: bytes) -> int:
"""写寄存器,返回设备确认的字节数"""
arr = (c_uint8 * len(data))(*data)
ret = c_uint16(0)
err = self._lib.eskin_write_register(
self._handle, addr, arr, len(data), ctypes.byref(ret)
)
if err != 0:
raise RuntimeError(f"write_register failed: error={err}")
return ret.value
def __enter__(self):
return self
def __exit__(self, *args):
self.close()