From c7b8ba0cef88ba91c764bf024dec3bd3a83541f6 Mon Sep 17 00:00:00 2001 From: su Date: Tue, 4 Aug 2026 15:38:28 +0800 Subject: [PATCH] Prepare DLL-only UPM package v0.1.0 --- .gitignore | 17 + CHANGELOG.md | 8 + Documentation~/README_UI.md | 11 + Documentation~/README_UI.md.meta | 7 + Documentation~/SDK_API_Reference.md | 265 ++++++++ Documentation~/SDK_API_Reference.md.meta | 7 + Documentation~/SDK_Configuration.md | 126 ++++ Documentation~/SDK_Configuration.md.meta | 7 + Documentation~/SDK_Mock_And_Testing.md | 26 + Documentation~/SDK_Mock_And_Testing.md.meta | 7 + Documentation~/SDK_QuickStart.md | 77 +++ Documentation~/SDK_QuickStart.md.meta | 7 + ...SI-M1人脸识别算法模组_Unity实现功能说明.md | 27 + ...人脸识别算法模组_Unity实现功能说明.md.meta | 7 + .../SI-M1人脸识别算法模组_文档功能总结.md | 606 ++++++++++++++++++ ...SI-M1人脸识别算法模组_文档功能总结.md.meta | 7 + Documentation~/串口指令测试说明.md | 437 +++++++++++++ Documentation~/串口指令测试说明.md.meta | 7 + Documentation~/接口文档与开发说明.md | 16 + Documentation~/接口文档与开发说明.md.meta | 7 + Editor/Aisu.SIM1.Face.Editor.dll | Bin 0 -> 18432 bytes Editor/Aisu.SIM1.Face.Editor.dll.meta | 23 + LICENSE.md | 5 + README.md | 70 ++ Runtime/Aisu.SIM1.Face.Runtime.dll | Bin 0 -> 89088 bytes Runtime/Aisu.SIM1.Face.Runtime.dll.meta | 23 + package.json | 18 + 27 files changed, 1818 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 Documentation~/README_UI.md create mode 100644 Documentation~/README_UI.md.meta create mode 100644 Documentation~/SDK_API_Reference.md create mode 100644 Documentation~/SDK_API_Reference.md.meta create mode 100644 Documentation~/SDK_Configuration.md create mode 100644 Documentation~/SDK_Configuration.md.meta create mode 100644 Documentation~/SDK_Mock_And_Testing.md create mode 100644 Documentation~/SDK_Mock_And_Testing.md.meta create mode 100644 Documentation~/SDK_QuickStart.md create mode 100644 Documentation~/SDK_QuickStart.md.meta create mode 100644 Documentation~/SI-M1人脸识别算法模组_Unity实现功能说明.md create mode 100644 Documentation~/SI-M1人脸识别算法模组_Unity实现功能说明.md.meta create mode 100644 Documentation~/SI-M1人脸识别算法模组_文档功能总结.md create mode 100644 Documentation~/SI-M1人脸识别算法模组_文档功能总结.md.meta create mode 100644 Documentation~/串口指令测试说明.md create mode 100644 Documentation~/串口指令测试说明.md.meta create mode 100644 Documentation~/接口文档与开发说明.md create mode 100644 Documentation~/接口文档与开发说明.md.meta create mode 100644 Editor/Aisu.SIM1.Face.Editor.dll create mode 100644 Editor/Aisu.SIM1.Face.Editor.dll.meta create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 Runtime/Aisu.SIM1.Face.Runtime.dll create mode 100644 Runtime/Aisu.SIM1.Face.Runtime.dll.meta create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..da2c31b --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Unity local/generated files +Library/ +Temp/ +Obj/ +Logs/ +UserSettings/ +.vs/ + +# Do not publish SDK source or debug symbols in this DLL-only package repo +*.cs +*.asmdef +*.pdb +*.mdb + +# OS/editor noise +.DS_Store +Thumbs.db diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9ab832e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +# Changelog + +## 0.1.0 + +- Initial UPM package migration for the SI-M1 face module Unity implementation. +- Added Runtime, Editor, Tests, Samples, and Documentation package structure. +- Migrated protocol, transport, command service, manager, tests, and demo sample files from `Assets/App`. +- Limited transport support to serial port and Editor Mock, and removed temporary Android bridge placeholders. diff --git a/Documentation~/README_UI.md b/Documentation~/README_UI.md new file mode 100644 index 0000000..450d602 --- /dev/null +++ b/Documentation~/README_UI.md @@ -0,0 +1,11 @@ +# Demo UI + +The public DLL-only UPM package does not include Demo UI source files or importable sample scenes. + +Use the SDK from your own Unity scene by creating a `FaceModuleManager` through: + +```text +AISU/SI-M1 Face SDK/Create Manager GameObject +``` + +For Demo UI source, sample scenes, or internal validation tests, use the private development SDK repository. diff --git a/Documentation~/README_UI.md.meta b/Documentation~/README_UI.md.meta new file mode 100644 index 0000000..f233b8a --- /dev/null +++ b/Documentation~/README_UI.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9ff986e707a1eab41a0b1913d6f34cac +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation~/SDK_API_Reference.md b/Documentation~/SDK_API_Reference.md new file mode 100644 index 0000000..47c5a03 --- /dev/null +++ b/Documentation~/SDK_API_Reference.md @@ -0,0 +1,265 @@ +# SI-M1 Face SDK 接口文档 + +本文列出 SDK 对业务层推荐使用的公开 API。命名空间统一为 `Aisu.SIM1.Face.*`。 + +## 1. 命名空间 + +| 命名空间 | 说明 | +|---|---| +| `Aisu.SIM1.Face.Core` | Manager、配置、回调适配、日志、主线程调度 | +| `Aisu.SIM1.Face.Commands` | 业务服务、命令服务、加密接口 | +| `Aisu.SIM1.Face.Protocol` | 协议枚举、封包、拆包、编码工具 | +| `Aisu.SIM1.Face.Transport` | 通信接口、串口、Mock | +| `Aisu.SIM1.Face.Models` | Reply、Note、用户、验证、录入、图片、UVC 等模型 | + +## 2. FaceModuleManager + +`FaceModuleManager` 是 SDK 推荐的统一入口。 + +```csharp +using Aisu.SIM1.Face.Core; +``` + +### 属性 + +| 属性 | 类型 | 说明 | +|---|---|---| +| `Basic` | `FaceBasicService` | 基础命令服务 | +| `Verify` | `FaceVerifyService` | 人脸验证服务 | +| `Enrollment` | `FaceEnrollmentService` | 人脸录入服务 | +| `Users` | `FaceUserService` | 用户管理服务 | +| `QrCode` | `FaceQrCodeService` | 二维码服务 | +| `Images` | `FaceImageService` | 图片抓拍与照片注册服务 | +| `Features` | `FaceFeatureService` | 特征读写服务 | +| `Uvc` | `FaceUvcService` | UVC 和高级参数服务 | +| `Encryption` | `FaceEncryptionService` | 加密相关服务 | +| `Callback` | `FaceModuleCallbackAdapter` | 回调式 API 适配 | +| `IsOpen` | `bool` | 通信是否打开 | +| `IsReady` | `bool` | 是否收到 READY | +| `CurrentStatus` | `FaceModuleStatus` | 当前模组状态 | +| `LastError` | `string` | 最近错误文本 | +| `CurrentCommand` | `string` | 当前命令名 | +| `Config` | `FaceModuleConfig` | 当前运行配置 | +| `Settings` | `FaceSdkSettings` | 当前绑定配置资产 | + +### 方法 + +| 方法 | 说明 | +|---|---| +| `ConfigureTransport(IFaceModuleTransport transport)` | 注入自定义通信层 | +| `Configure(bool useMockInEditor, string portName, int baudRate)` | 兼容旧代码的快速配置入口 | +| `ApplySettings(FaceSdkSettings sdkSettings)` | 应用 ScriptableObject 配置并重建 transport | +| `Open()` | 打开通信 | +| `Close()` | 关闭通信 | +| `WaitReadyAsync(int timeoutMs = 5000)` | 等待 READY NOTE | +| `ProbeReadyByStatusAsync()` | 主动查询状态并标记 READY | +| `SendRawBytes(byte[] bytes)` | 发送原始协议 bytes | +| `RefreshStatus()` | 异步刷新 `CurrentStatus` | + +### 事件 + +| 事件 | 说明 | +|---|---| +| `OnReady` | 首次 READY | +| `OnStatusChanged(FaceModuleStatus)` | 当前状态变化 | +| `OnReplyReceived(FaceReply)` | 收到 REPLY 包 | +| `OnNoteReceived(FaceNote)` | 收到 NOTE 包 | +| `OnFaceStateUpdated(FaceFaceStateNote)` | 收到人脸状态 NOTE | +| `OnQrCodeReceived(string)` | 收到二维码 NOTE | +| `OnFeatureReceived(byte[])` | 收到读取特征数据包 | +| `OnDataPacketReceived(FacePacket)` | 收到 ImageOrTemplate 数据包 | +| `OnError(FaceResultCode, string)` | 错误上报 | +| `OnRawLog(string)` | 原始 TX / RX 日志 | + +## 3. 基础服务 FaceBasicService + +访问路径: + +```csharp +manager.Basic +``` + +| 方法 | 返回 | 说明 | +|---|---|---| +| `ResetAsync()` | `Task` | 复位模组 | +| `FaceResetAsync()` | `Task` | 取消当前人脸流程 | +| `GetStatusAsync()` | `Task` | 获取模组状态 | +| `GetVersionAsync()` | `Task` | 获取固件版本 | +| `GetSnAsync()` | `Task` | 获取设备序列号 | +| `SetDemoModeAsync(bool enable)` | `Task` | 设置 Demo Mode | +| `UpgradeFirmwareAsync()` | `Task` | 启动固件升级命令 | + +## 4. 验证服务 FaceVerifyService + +访问路径: + +```csharp +manager.Verify +``` + +| 方法 | 返回 | 说明 | +|---|---|---| +| `VerifyAsync(byte timeout, byte maxRecognitionTimes = 30)` | `Task` | 单次人脸验证 | +| `AutoVerifyAsync(bool enable, byte timeout)` | `Task` | 启用或关闭自动验证 | + +## 5. 录入服务 FaceEnrollmentService + +访问路径: + +```csharp +manager.Enrollment +``` + +| 方法 | 返回 | 说明 | +|---|---|---| +| `EnrollAsync(bool admin, string userName, FaceDirection direction, byte timeout)` | `Task` | 交互式多方向录入 | +| `EnrollSingleAsync(bool admin, string userName, byte timeout)` | `Task` | 单帧录入 | +| `EnrollIntegratedAsync(bool admin, string userName, FaceDirection direction, byte timeout, bool singleFrame = false, bool duplicateCheck = true)` | `Task` | 集成式录入 | +| `EnrollSnapFaceImageAsync(bool admin, string userName)` | `Task` | 抓拍人脸后注册 | + +## 6. 用户服务 FaceUserService + +访问路径: + +```csharp +manager.Users +``` + +| 方法 | 返回 | 说明 | +|---|---|---| +| `DeleteUserAsync(int userId)` | `Task` | 删除指定用户 | +| `DeleteAllAsync()` | `Task` | 删除全部用户 | +| `GetUserInfoAsync(int userId)` | `Task` | 查询用户信息 | +| `GetAllUserIdsAsync()` | `Task>` | 查询用户 ID 列表 | +| `GetAllUserIds2Async()` | `Task>` | 使用扩展命令查询用户 ID 列表 | +| `GetAllUserInfosAsync()` | `Task>` | 查询全部用户详情 | +| `GetAllUserInfos2Async()` | `Task>` | 使用扩展命令查询全部用户详情 | + +## 7. 二维码服务 FaceQrCodeService + +访问路径: + +```csharp +manager.QrCode +``` + +| 方法 | 返回 | 说明 | +|---|---|---| +| `ScanQrCodeAsync(byte timeout)` | `Task` | 扫描二维码并返回内容 | + +## 8. 图片服务 FaceImageService + +访问路径: + +```csharp +manager.Images +``` + +| 方法 | 返回 | 说明 | +|---|---|---| +| `SnapUploadImageAsync()` | `Task` | 抓拍普通图片 | +| `SnapUploadFaceImageAsync()` | `Task` | 抓拍人脸图片 | +| `SnapUploadLargeImageAsync(ImageDpi dpi)` | `Task` | 抓拍大图 | +| `EnrollWithPhotoAsync(byte[] photoBytes, BioType type, string userName = null, bool duplicateCheck = true, Action onProgress = null)` | `Task` | 照片注册 | +| `EnrollWithPhotoAndIdAsync(int userId, byte[] photoBytes, BioType type, string userName = null, bool duplicateCheck = true, Action onProgress = null)` | `Task` | 指定用户 ID 照片注册 | + +## 9. 特征服务 FaceFeatureService + +访问路径: + +```csharp +manager.Features +``` + +| 方法 | 返回 | 说明 | +|---|---|---| +| `ReadFeatureAsync(int userId, FeatureType type)` | `Task` | 读取指定用户特征 | +| `WriteFeatureAsync(byte[] featureBytes, FeatureType type, string userName = null, Action onProgress = null)` | `Task` | 写入特征并注册 | + +## 10. UVC 和高级参数 FaceUvcService + +访问路径: + +```csharp +manager.Uvc +``` + +| 方法 | 返回 | 说明 | +|---|---|---| +| `ReadUsbUvcParametersAsync()` | `Task` | 读取 USB / UVC 参数 | +| `SetUsbUvcParametersAsync(UsbUvcParameters parameters)` | `Task` | 设置 USB / UVC 参数 | +| `SetFaceLocationDisplayAsync(bool visible)` | `Task` | 设置人脸框显示 | +| `SetRgbLevelAsync(byte level)` | `Task` | 设置 RGB 阈值 | +| `SetDuplicateCheckAsync(bool enableCheck)` | `Task` | 设置查重 | +| `ReadDuplicateCheckAsync()` | `Task` | 读取查重状态 | + +## 11. 加密服务 FaceEncryptionService + +访问路径: + +```csharp +manager.Encryption +``` + +| 方法 | 返回 | 说明 | +|---|---|---| +| `SetReleaseEncryptionKeyAsync(byte[] key16)` | `Task` | 设置 Release 加密密钥 | +| `SetDebugEncryptionKeyAsync(byte[] key16)` | `Task` | 设置 Debug 加密密钥 | +| `InitEncryptionAsync(byte[] seed4, byte mode)` | `Task` | 初始化加密流程 | +| `EnableEncryption(IFaceModuleCrypto crypto)` | `void` | 设置本地加密算法适配器 | +| `DisableEncryption()` | `void` | 关闭本地加密算法适配器 | + +`IFaceModuleCrypto`: + +```csharp +public interface IFaceModuleCrypto +{ + byte[] Encrypt(byte[] plainBytes); + byte[] Decrypt(byte[] encryptedBytes); + byte[] GenerateSessionKey(byte[] seed4, byte[] encKey16); +} +``` + +## 12. 回调式 API + +`FaceModuleManager.Callback` 适合 Unity Button、旧项目回调风格或不方便使用 `await` 的业务。 + +```csharp +manager.Callback.Verify( + timeout: 10, + success: result => Debug.Log(result.UserId), + error: message => Debug.LogError(message)); +``` + +回调特点: + +- 所有 API 统一使用 `success` 和 `error`。 +- 照片注册、特征写入支持 `progress(current, total)`。 +- 回调会通过 `FaceModuleMainThreadDispatcher` 回到 Unity 主线程。 + +## 13. 主要模型 + +| 类型 | 说明 | +|---|---| +| `FaceReply` | REPLY 包解析结果 | +| `FaceNote` | NOTE 包解析结果 | +| `FaceFaceStateNote` | 人脸状态 NOTE | +| `FaceUserInfo` | 用户 ID、姓名、管理员标记 | +| `FaceVerifyResult` | 验证结果、用户信息、开锁状态 | +| `FaceEnrollResult` | 录入结果、用户 ID、方向 | +| `FaceImageResult` | 图片 bytes、Texture2D、用户 ID | +| `UsbUvcParameters` | USB / UVC 参数 | +| `FaceCommandException` | 命令 REPLY 非成功时抛出的异常 | + +## 14. 协议和通信底层 + +业务层通常不需要直接使用底层 API。需要协议调试或自定义通信时可使用: + +| 类型 | 说明 | +|---|---| +| `FacePacketBuilder` | 封包 | +| `FacePacketParser` | 拆包 | +| `FaceProtocolEncoding` | 用户名、ID、结果文本等编码工具 | +| `IFaceModuleTransport` | 通信接口 | +| `SerialPortTransport` | 串口实现 | +| `MockFaceModuleTransport` | Mock 实现 | diff --git a/Documentation~/SDK_API_Reference.md.meta b/Documentation~/SDK_API_Reference.md.meta new file mode 100644 index 0000000..e1eb11d --- /dev/null +++ b/Documentation~/SDK_API_Reference.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a34f4830a06e4db9bd8cb1ec7a1b9334 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation~/SDK_Configuration.md b/Documentation~/SDK_Configuration.md new file mode 100644 index 0000000..87cb229 --- /dev/null +++ b/Documentation~/SDK_Configuration.md @@ -0,0 +1,126 @@ +# SI-M1 Face SDK 配置与 Editor 工具 + +SDK 使用 `FaceSdkSettings` 作为推荐配置方式。该配置是 `ScriptableObject`,可在项目中复用,并可同步到场景中的 `FaceModuleManager`。 + +## 1. 默认配置资产 + +默认路径: + +```text +Assets/AISU/SI-M1 Face SDK/FaceSdkSettings.asset +``` + +打开菜单 `AISU/SI-M1 Face SDK/Settings` 后,Editor 会加载该路径。若文件不存在,点击 `Load Default` 会创建默认资产。 + +## 2. FaceSdkSettings 字段 + +| 字段 | 类型 | 说明 | +|---|---|---| +| `TransportMode` | `FaceTransportMode` | 通信模式:`Auto`、`SerialPort`、`Mock` | +| `UseMockInEditor` | `bool` | Editor 下是否强制使用 Mock | +| `PortName` | `string` | Player / 运行时串口号 | +| `BaudRate` | `int` | Player / 运行时波特率 | +| `ReadBufferSize` | `int` | Player / 运行时读取缓冲区 | +| `UseEditorSerialDebugParameters` | `bool` | Editor 下连接真实串口时是否使用单独参数 | +| `EditorPortName` | `string` | Editor 调试串口号 | +| `EditorBaudRate` | `int` | Editor 调试波特率 | +| `EditorReadBufferSize` | `int` | Editor 调试读取缓冲区 | +| `MockResponseDelayFrames` | `int` | Mock 响应延迟帧数 | +| `DebugLogEnabled` | `bool` | 是否启用 SDK Debug 日志 | +| `RawLogEnabled` | `bool` | 是否派发原始 TX / RX 日志 | +| `LogPrefix` | `string` | 日志前缀 | + +## 3. TransportMode 选择 + +| 模式 | 行为 | +|---|---| +| `Auto` | Editor 根据 `UseMockInEditor` 选择 Mock 或串口;Player 使用串口 | +| `SerialPort` | 使用 `SerialPortTransport` | +| `Mock` | 使用 `MockFaceModuleTransport` | + +Editor 下有一个额外优先级: + +```text +UseMockInEditor = true 时,强制使用 Mock,不打开真实串口。 +``` + +因此 Editor 中如果需要连接真实串口,必须将 `UseMockInEditor` 设为 `false`。 + +## 4. Editor Serial Debug 参数 + +Editor 连接真实硬件时,建议开启: + +```text +UseEditorSerialDebugParameters = true +``` + +开启后,Editor 中串口使用: + +- `EditorPortName` +- `EditorBaudRate` +- `EditorReadBufferSize` + +Player 中仍使用: + +- `PortName` +- `BaudRate` +- `ReadBufferSize` + +这样可以避免 Editor 调试口和发布运行口互相覆盖。 + +## 5. Editor 工具按钮 + +配置窗口按钮: + +| 按钮 | 行为 | +|---|---| +| `Load Default` | 固定加载或创建默认路径的 `FaceSdkSettings.asset` | +| `Save` | 保存当前配置并同步场景中的 Manager | +| `Save and Create/Update Manager` | 保存配置后创建或更新场景 Manager | + +同步 Manager 时会写入: + +- `m_settings` +- `m_config.TransportMode` +- `m_config.UseMockInEditor` +- 串口参数 +- Editor 串口参数 +- Mock 延迟 +- Raw Log 开关 + +PlayMode 中同步配置时,会调用 `FaceModuleManager.ApplySettings(settings)`,立即重建当前 transport。 + +## 6. Manager 配置方式 + +推荐方式: + +```csharp +manager.ApplySettings(settings); +manager.Open(); +``` + +兼容方式: + +```csharp +manager.Configure(useMockInEditor: true, portName: "COM3", baudRate: 115200); +manager.Open(); +``` + +外部通信层注入: + +```csharp +manager.ConfigureTransport(customTransport); +manager.Open(); +``` + +## 7. 场景约束 + +每个场景建议只保留一个 `FaceModuleManager`。 + +多个 Manager 可能导致: + +- 多次打开同一串口。 +- 重复接收和派发事件。 +- UI 状态不一致。 + +`Create Manager GameObject` 菜单会校验 Manager 数量,并在存在多个 Manager 时停止创建。 diff --git a/Documentation~/SDK_Configuration.md.meta b/Documentation~/SDK_Configuration.md.meta new file mode 100644 index 0000000..706ab14 --- /dev/null +++ b/Documentation~/SDK_Configuration.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6b9d5a7b5c834a8e8a3315c0215f4c92 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation~/SDK_Mock_And_Testing.md b/Documentation~/SDK_Mock_And_Testing.md new file mode 100644 index 0000000..392a779 --- /dev/null +++ b/Documentation~/SDK_Mock_And_Testing.md @@ -0,0 +1,26 @@ +# Mock And Testing + +The public DLL-only package includes runtime mock support inside `Aisu.SIM1.Face.Runtime.dll`, but it does not include EditMode test source files. + +## Editor Mock + +For Editor integration without real hardware, enable mock mode in `FaceSdkSettings`: + +```text +Use Mock In Editor = true +``` + +When this option is enabled, the SDK uses the built-in mock transport in the Unity Editor instead of opening a real serial port. + +## Hardware Validation + +For real SI-M1 hardware validation: + +1. Set `Use Mock In Editor = false`. +2. Configure `Editor Port Name`, `Editor Baud Rate`, and `Editor Read Buffer Size`. +3. Create a `FaceModuleManager` in the scene. +4. Call `Open()`, then wait for `WaitReadyAsync()` or handle `OnReady`. + +## Automated Tests + +Internal EditMode tests are maintained in the private development SDK repository and are not distributed in this DLL-only public package. diff --git a/Documentation~/SDK_Mock_And_Testing.md.meta b/Documentation~/SDK_Mock_And_Testing.md.meta new file mode 100644 index 0000000..4b82812 --- /dev/null +++ b/Documentation~/SDK_Mock_And_Testing.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 901f8c16c47242409161a8c42704dc30 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation~/SDK_QuickStart.md b/Documentation~/SDK_QuickStart.md new file mode 100644 index 0000000..90def11 --- /dev/null +++ b/Documentation~/SDK_QuickStart.md @@ -0,0 +1,77 @@ +# SDK Quick Start + +This public UPM package is distributed as compiled DLLs and does not include SDK source code, Demo source code, or test source code. + +## 1. Install + +Add the package through Unity Package Manager, or add it to `Packages/manifest.json`: + +```json +"xin.aisu.si-m1.face": "https://github.com/your-org/si-m1-face-package.git#v0.1.0" +``` + +For local validation: + +```json +"xin.aisu.si-m1.face": "file:D:/Workspaces/SDK/si-m1-face-package" +``` + +## 2. Create Settings + +Open Unity menu: + +```text +AISU/SI-M1 Face SDK/Settings +``` + +The tool creates or loads the default settings asset: + +```text +Assets/AISU/SI-M1 Face SDK/FaceSdkSettings.asset +``` + +Configure serial port, baud rate, mock mode, and logging options. + +## 3. Create Manager + +Open Unity menu: + +```text +AISU/SI-M1 Face SDK/Create Manager GameObject +``` + +Keep one `FaceModuleManager` in the scene to avoid duplicate serial connections and duplicate event dispatch. + +## 4. Minimal Code + +```csharp +using Aisu.SIM1.Face.Core; +using UnityEngine; + +public sealed class FaceExample : MonoBehaviour +{ + [SerializeField] private FaceModuleManager manager; + + private async void Start() + { + manager.Open(); + await manager.WaitReadyAsync(); + + var version = await manager.Basic.GetVersionAsync(); + Debug.Log("SI-M1 version: " + version); + } +} +``` + +## 5. Runtime Modes + +| Scenario | Recommended Setting | +|---|---| +| Editor UI integration without hardware | `Use Mock In Editor = true` | +| Editor with real serial hardware | `Use Mock In Editor = false`, configure editor serial parameters | +| Windows Player | `Transport Mode = SerialPort` or `Auto`, configure runtime serial parameters | + +## Notes + +- This DLL-only release does not include importable Demo samples. +- Demo source and EditMode test source remain in the private development SDK repository. diff --git a/Documentation~/SDK_QuickStart.md.meta b/Documentation~/SDK_QuickStart.md.meta new file mode 100644 index 0000000..324fcb9 --- /dev/null +++ b/Documentation~/SDK_QuickStart.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8cd4879d6b9a4f4eb1e3c77778c0b3d1 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation~/SI-M1人脸识别算法模组_Unity实现功能说明.md b/Documentation~/SI-M1人脸识别算法模组_Unity实现功能说明.md new file mode 100644 index 0000000..3441d4e --- /dev/null +++ b/Documentation~/SI-M1人脸识别算法模组_Unity实现功能说明.md @@ -0,0 +1,27 @@ +# Unity Implementation Notes + +This public package provides the SI-M1 Unity integration as compiled DLLs. + +## Public Assemblies + +| Assembly | Purpose | +|---|---| +| `Aisu.SIM1.Face.Runtime.dll` | Runtime protocol, transports, manager, services, models, mock transport | +| `Aisu.SIM1.Face.Editor.dll` | Editor settings panel, diagnostics, manager creation tools | + +## Runtime Areas + +- Protocol packet building and parsing. +- Serial and mock transport abstraction. +- Command service and async request/response flow. +- Face enrollment, verification, user, image, feature, QR code, UVC, and encryption services. +- `FaceModuleManager` lifecycle and Unity scene integration. + +## Public Release Scope + +- SDK source files are not included. +- Demo source files are not included. +- Internal EditMode test source files are not included. +- Debug symbol `.pdb` files are not included. + +For private development structure and implementation source, use the internal SDK repository. diff --git a/Documentation~/SI-M1人脸识别算法模组_Unity实现功能说明.md.meta b/Documentation~/SI-M1人脸识别算法模组_Unity实现功能说明.md.meta new file mode 100644 index 0000000..e5ba6fe --- /dev/null +++ b/Documentation~/SI-M1人脸识别算法模组_Unity实现功能说明.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6cd812fde7d86244b82b86344e82770d +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation~/SI-M1人脸识别算法模组_文档功能总结.md b/Documentation~/SI-M1人脸识别算法模组_文档功能总结.md new file mode 100644 index 0000000..c959c45 --- /dev/null +++ b/Documentation~/SI-M1人脸识别算法模组_文档功能总结.md @@ -0,0 +1,606 @@ +# SI-M1 人脸识别算法模组通信协议 V2.63:文档功能总结 + +> 来源文档:`SI-M1人脸识别算法模组通信协议V2.63.pdf` +> 整理目的:把通信协议、功能命令、返回消息、业务流程整理成可读的功能说明,便于后续 Unity 对接实现。 + +--- + +## 1. 文档定位 + +这份文档不是“人脸识别算法原理说明”,而是 **SI-M1 人脸识别算法模组的主控通信协议说明**。 + +核心关系: + +```text +Unity / PC 主控程序 <----串口通信链路----> SI-M1 人脸识别算法模组 +``` + +模组处于 **从属设备** 地位: + +- 主控负责发送命令; +- 模组负责执行人脸识别、录入、删除、查询、抓拍、二维码识别、特征读写等动作; +- 模组通过 `REPLY`、`NOTE`、`ImageOrTemplate` 三类消息主动/被动返回结果。 + +--- + +## 2. 通信消息格式 + +所有消息统一使用以下帧格式: + +| 字段 | 长度 | 说明 | +|---|---:|---| +| SyncWord | 2 bytes | 固定同步头:`0xEF 0xAA` | +| MsgID | 1 byte | 消息 ID / 命令 ID / 返回类型 | +| Size | 2 bytes | Data 长度,高字节在前,单位 byte | +| Data | N bytes | 命令参数或返回数据,N 可为 0 | +| ParityCheck | 1 byte | 校验码 | + +### 2.1 校验规则 + +校验码计算方式: + +```text +ParityCheck = MsgID ^ SizeHigh ^ SizeLow ^ Data[0] ^ Data[1] ^ ... ^ Data[N-1] +``` + +注意: + +- 不包含 `SyncWord`; +- `Size` 是大端模式,即高八位在前; +- Data 为空时,只对 `MsgID + SizeHigh + SizeLow` 做 XOR; +- RESET 示例:`EF AA 10 00 00 10` + +--- + +## 3. 主控发送给模组的命令功能 + +### 3.1 基础控制类 + +| 功能 | 命令 | Code | Data | 说明 | +|---|---|---:|---|---| +| 复位 / 取消当前任务 | RESET | `0x10` | 无 | 取消录入、验证等正在执行的任务,使模组回到 Standby | +| 获取状态 | GET STATUS | `0x11` | 无 | 获取模组当前状态 | +| 清除录入状态 | FACE RESET | `0x23` | 无 | 终止录入流程并清除录入状态 | +| 获取版本 | GET VERSION | `0x30` | 无 | 获取软件版本信息 | +| 获取设备序列号 | MID_GET_SN | `0x93` | 无 | 获取设备唯一 SN,返回 32 bytes,前 8 bytes 有效 | +| Demo 模式 | DEMO MODE | `0xFE` | enable 1 byte | 进入演示模式,所有人可解锁,但仍做活体检测 | + +--- + +### 3.2 人脸验证类 + +| 功能 | 命令 | Code | Data | 说明 | +|---|---|---:|---|---| +| 人脸验证 | VERIFY | `0x12` | `pd_rightaway` + `timeout` + 可选 `Max_Recognition_times` | 启动一次人脸验证 | +| 低功耗自动探测验证 | AUTO_VERIFY | `0x12` | `at_verify` + `timeout` | AI-10 适用,检测到人脸或二维码后自动识别 | + +#### VERIFY 参数 + +| 字段 | 长度 | 说明 | +|---|---:|---| +| pd_rightaway | 1 byte | 文档中作为保留字段,默认 0 | +| timeout | 1 byte | 解锁超时时间,单位秒,最大 255 | +| Max_Recognition_times | 1 byte,可选 | 最大识别尝试次数,不发送时默认 30 | + +特殊注意: + +- 照片注册后,第一次识别成功会补全红外模板并保存到 Flash; +- 主控在收到 REPLY 应答或超时后,应延时约 2 秒再断电。 + +--- + +### 3.3 人脸录入类 + +| 功能 | 命令 | Code | Data | 说明 | +|---|---|---:|---|---| +| 交互录入 | ENROLL | `0x13` | admin + user_name + face_dir + timeout | 按指定方向录入人脸 | +| 单帧录入 | ENROLL_SINGLE | `0x1D` | admin + user_name + face_dir + timeout | 只需一张正脸即可注册 | +| 集成录入 | MID_ENROLL_ITG | `0x26` | admin + user_name + face_dir + enroll_type + enable_duplicate + timeout + reserved | ENROLL 的扩展版本,支持交互/单帧和重复录入策略 | +| 抓拍人脸后注册 | ENROLL_SNAPFACEIMAGE | `0x73` | admin + user_name + reserved + reserved | 配合 0x72 未搜索到本地用户时使用,不查重 | + +#### ENROLL 参数 + +| 字段 | 长度 | 说明 | +|---|---:|---| +| admin | 1 byte | 是否管理员,1 是,0 否 | +| user_name | 32 bytes | 用户姓名 | +| face_dir | 1 byte | 录入方向 | +| timeout | 1 byte | 录入超时时间,单位秒 | + +#### 人脸方向定义 + +| 方向 | Code | 说明 | +|---|---:|---| +| FACE_DIRECTION_UP | `0x10` | 朝上 | +| FACE_DIRECTION_DOWN | `0x08` | 朝下 | +| FACE_DIRECTION_LEFT | `0x04` | 朝左 | +| FACE_DIRECTION_RIGHT | `0x02` | 朝右 | +| FACE_DIRECTION_MIDDLE | `0x01` | 正脸 | +| FACE_DIRECTION_UNDEFINE | `0x00` | 未定义,默认正脸 | + +#### 交互录入说明 + +录入过程中: + +1. 主控发送指定方向录入命令; +2. 模组通过 NOTE 返回人脸状态、位置、姿态; +3. Unity 根据 NOTE 信息提示用户调整姿态; +4. 模组通过 REPLY 返回最终录入结果; +5. 录入中可通过 FACE RESET 终止; +6. 录入过程中突然断电,之前录入的人脸不会保存。 + +#### 录入角度说明 + +| 方向 | 推荐偏转角度 | +|---|---| +| 正脸 | 正对摄像头 | +| 向上 | 正脸向上偏转 5~55 度 | +| 向下 | 正脸向下偏转 5~55 度 | +| 向右 | 正脸向右偏转 8~60 度 | +| 向左 | 正脸向左偏转 8~60 度 | + +不建议使用 Roll 倾斜头部方式录入,应缓慢转头并保持偏转。 + +--- + +### 3.4 用户管理类 + +| 功能 | 命令 | Code | Data | 说明 | +|---|---|---:|---|---| +| 删除指定用户 | DELETE USER | `0x20` | user_id 高字节 + 低字节 | 删除一个已注册用户 | +| 删除全部用户 | DELETE ALL | `0x21` | 无 | 删除所有注册用户 | +| 查询用户信息 | GET USER INFO | `0x22` | user_id 高字节 + 低字节 | 返回用户 ID、姓名、管理员标志 | +| 获取所有用户 ID | MID_GET_ALL_USERID | `0x24` | alluserid_index | 每包最多 100 个用户 ID | +| 获取所有用户 ID 2 | MID_GET_ALL_USERID2 | `0x25` | alluserid_index | AI-10 适用,user_counts 为 2 bytes | + +--- + +### 3.5 加密通信类 + +| 功能 | 命令 | Code | Data | 说明 | +|---|---|---:|---|---| +| 初始化加密 | INIT ENCRYPTION | `0x50` | seed[4] + mode | 主控发送随机数 | +| 设置 Release 加密序列 | MID_SET_RELEASE_ENC_KEY | `0x52` | enc_key_number[16] | 设置正式协议加密序列 | +| 设置 Debug 加密序列 | MID_SET_DEBUG_ENC_KEY | `0x53` | enc_key_number[16] | 设置调试协议加密序列 | + +加密流程: + +1. 主控给模组上电; +2. 模组发送明文 READY; +3. 第一次使用需设置 16 bytes Release 加密序列; +4. 主控生成 4 bytes 随机数发送给模组; +5. 双方基于随机数和私有协议生成 16 bytes 会话密码; +6. 后续通信使用 AES/SMPL 加密; +7. 主控解密设备 ID 并确认设备身份; +8. 后续录入、验证等命令均按加密方式发送和接收。 + +> 实现注意:文档没有给出完整私有密钥派生算法和 AES/SMPL 细节,Unity 侧如需启用加密通信,需要供应商提供 SDK、算法源码或加密协议补充文档。 + +--- + +### 3.6 USB / 图像参数类 + +| 功能 | 命令 | Code | Data | 说明 | +|---|---|---:|---|---| +| 读取 USB UVC 参数 | READ_USB_UVC_PARAMETERS | `0xB0` | 无 | 读取传图参数 | +| 设置 USB UVC 参数 | SET_USB_UVC_PARAMETERS | `0xB1` | USB Type + UVC 码率 + MJPG 质量 + 图像属性 | 设置传图模式、码率、质量、镜像/倒转 | +| 设置人脸框显示 | SET_FACE_LOCATION_DISPLAY | `0xB5` | 0/1 | 设置是否显示人脸框 | + +USB Type 示例: + +| 值 | 含义 | +|---:|---| +| `0x11` | USB1.1 + Bulk | +| `0x20` | USB2.0 + Bulk | +| `0x91` | USB1.1 + ISOC | +| `0xA0` | USB2.0 + ISOC | + +图像属性: + +| Bit | 说明 | +|---|---| +| BIT0 | 1 启用镜像 | +| BIT1 | 1 启用 180 度倒转 | + +--- + +### 3.7 二维码识别类 + +| 功能 | 命令 | Code | Data | 说明 | +|---|---|---:|---|---| +| 扫描二维码 | MID_SCAN_QR_CODE | `0x70` | 0x00 + timeout | 读取二维码信息 | +| 自动探测二维码 | AUTO_VERIFY | `0x12` | at_verify + timeout | AI-10 低功耗自动探测模式下自动识别二维码 | + +二维码 NOTE: + +- `NID_QR_DATA_FLAGS = 15` 表示 QR 数据; +- 可识别二维码不大于 250 bytes ASCII; +- 不包含中文。 + +--- + +### 3.8 抓拍与图片上传类 + +| 功能 | 命令 | Code | 数据包规则 | 说明 | +|---|---|---:|---|---| +| 抓拍普通图片 | MID_SNAP&UPLOAD_IMAGE | `0x71` | 每包 1024 bytes,最后一包按实际长度 | 240×320 图片 | +| 抓拍人脸图片 | MID_SNAP&UPLOAD_FACEIMAGE | `0x72` | 每包 1024 bytes,最后一包按实际长度 | 320×320 人脸图,本地比对后上传 | +| 抓拍全视角大图 | MID_SNAP&UPLOAD_IMAGE_B | `0x74` | 每包 1024 bytes,支持 DPI 参数 | 480×640 / 600×800 / 1200×1600 | + +0x74 DPI 参数: + +| DPI | 分辨率 | +|---:|---| +| 0 | 480×640 | +| 1 | 600×800 | +| 2 | 1200×1600,仅 AI-10 有效 | + +--- + +### 3.9 照片 / 特征下发注册类 + +| 功能 | 命令 | Code | 说明 | +|---|---|---:|---| +| 照片/特征注册,指定 ID | MID_ENROLL_WITH_PHOTO&ID | `0xD7` | 文档建议优先使用 0xF7 | +| 照片/特征注册 | MID_ENROLL_WITH_PHOTO | `0xF7` | 推荐使用 | +| 设置彩色识别阈值 | RGB_LEVEL | `0xD4` | 彩色识别阈值 0~4,默认 2 | + +#### MID_ENROLL_WITH_PHOTO 0xF7 流程 + +1. 第一包:`Seq = 0`; +2. Data 包含: + - `00` + - `00` + - 照片/特征长度,4 bytes,大端; + - BioType; + - 可选姓名长度; + - 可选姓名字符串; + - 可选重复检查标志; +3. 模组返回 Result 和 Seq; +4. Result 成功后,主控从 `Seq = 1` 开始发送数据; +5. 每包数据最大 MTU ≤ 246 bytes; +6. 最后一包不足 246 bytes 时按实际长度发送; +7. 发送完成后,模组返回最终注册结果和 UserID。 + +#### BioType 定义 + +| 值 | 说明 | +|---:|---| +| 0 | 普通照片 | +| 1 | 加密照片 | +| 2 | 普通特征码:2048 bytes 彩色,或 4096 bytes 彩色+红外 | +| 3 | 压缩特征码:1024 bytes | + +特征文件长度: + +| 类型 | 文件总长度 | 内容 | +|---|---:|---| +| 彩色+红外特征 | 4100 bytes | 4096 bytes 特征码 + 4 bytes CRC32 | +| 彩色特征 | 2052 bytes | 2048 bytes 特征码 + 4 bytes CRC32 | +| 压缩特征 | 1028 bytes | 1024 bytes 特征码 + 4 bytes CRC32 | + +CRC32 初始向量值:`0xFFFFFFFF`。 + +--- + +### 3.10 特征读写类(AI-10) + +| 功能 | 命令 | Code | 说明 | +|---|---|---:|---| +| 读取特征 | MID_READ_FEATURE | `0xFA` | 按用户 ID 和特征类型读取模板 | +| 写入特征 | MID_WRITE_FEATURE | `0xFB` | 分包写入模板数据 | + +Feature Type: + +| 值 | 说明 | +|---:|---| +| 1 | 彩色人脸模板 | +| 2 | 彩色 + 红外人脸模板 | +| 3 | 掌纹掌静脉模板 | + +分包规则: + +- 每包 246 bytes; +- 从包序号 1 开始递增; +- 最后一包按实际剩余长度; +- 读取特征时先返回特征长度,再按包上传; +- 写入特征时先发送特征类型,再分包写入。 + +--- + +### 3.11 查重配置类 + +| 功能 | 命令 | Code | Data | 说明 | +|---|---|---:|---|---| +| 设置查重 | MID_DUPLICATE_CHECK | `0xFC` | Checkflag | 设置本地注册是否查重 | +| 查询查重状态 | MID_READ_DUPLICATE_CHECK | `0xFC` | 无 | 不带参数时查询当前查重状态 | + +Checkflag: + +| 值 | 文档说明 | +|---:|---| +| 0 | 查重 | +| 1 | 不查重 | + +说明: + +- 默认查重; +- 设置后立即生效并保存到 Flash; +- 重启后仍有效; +- SI-M1 V2.10 以上支持。 + +--- + +### 3.12 固件升级类 + +| 功能 | 命令 | Code | 说明 | +|---|---|---:|---| +| U 盘升级固件 | MID_UPGRADE_FW | `0xF6` | 启动通过 U 盘升级模组固件 | + +返回数据中包含升级进度百分比。 + +--- + +## 4. 模组返回给主控的消息 + +模组主要返回三类消息: + +| 类型 | MsgID | 说明 | +|---|---:|---| +| REPLY | `0x00` | 命令最终执行结果 | +| NOTE | `0x01` | 模组主动通知,例如 READY、人脸状态、二维码数据 | +| ImageOrTemplate | 业务 mid | 图片或特征模板数据流 | + +--- + +## 5. REPLY 消息 + +### 5.1 REPLY 格式 + +```text +SyncWord MsgID=0x00 Size Data Checksum +Data = mid + result + data[n] +``` + +| 字段 | 说明 | +|---|---| +| mid | 被回复的命令 ID | +| result | 命令执行结果 | +| data | 不同命令对应的返回数据 | + +### 5.2 常见 REPLY 返回数据 + +| mid | Code | 成功时返回 | +|---|---:|---| +| MID_GETSTATUS | `0x11` | status | +| MID_VERIFY | `0x12` | user_id + user_name + admin + unlockStatus | +| MID_ENROLL | `0x13` | user_id + face_direction | +| MID_ENROLL_SINGLE | `0x1D` | user_id + face_direction | +| MID_GETUSERINFO | `0x22` | user_id + user_name + admin | +| MID_GET_ALL_USERID | `0x24` | user_counts + users_id[] | +| MID_GET_VERSION | `0x30` | version data | +| MID_INIT_ENCRYPTION | `0x50` | device_id[20] | +| MID_GET_SN | `0x93` | Device_SN[32],前 8 bytes 有效 | +| MID_SCAN_QR_CODE | `0x70` | QR CODE | +| MID_SNAP_UPLOAD_IMAGE | `0x71` | result + photo_len 或图片数据 | +| MID_SNAP_UPLOAD_FACEIMAGE | `0x72` | result + photo_len + user_id 或图片数据 | +| MID_ENROLL_WITH_PHOTO | `0xF7` | seq + user_id | +| MID_READ_FEATURE | `0xFA` | result + seq + feature_len 或特征数据 | +| MID_WRITE_FEATURE | `0xFB` | result + seq + user_id | +| MID_READ_DUPLICATE_CHECK | `0xFC` | state + duplicate | + +--- + +## 6. Result 结果码 + +| Code | 名称 | 说明 | +|---:|---|---| +| 0 | MR_SUCCESS | 成功 | +| 1 | MR_REJECTED | 模组拒绝命令 | +| 2 | MR_ABORTED | 录入/验证算法终止 | +| 4 | MR_FAILED4_CAMERA | 相机打开失败 | +| 5 | MR_FAILED4_UNKNOWNREASON | 未知错误 | +| 6 | MR_FAILED4_INVALIDPARAM | 参数无效 | +| 7 | MR_FAILED4_NOMEMORY | 内存不足 | +| 8 | MR_FAILED4_UNKNOWNUSER | 没有已录入用户 | +| 9 | MR_FAILED4_MAXUSER | 超过最大用户数量 | +| 10 | MR_FAILED4_FACEENROLLED | 人脸已录入 | +| 12 | MR_FAILED4_LIVENESSCHECK | 活体检测失败 | +| 13 | MR_FAILED4_TIMEOUT | 录入或解锁超时 | +| 14 | MR_FAILED4_AUTHORIZATION | 加密芯片授权失败 | +| 19 | MR_FAILED4_READ_FILE | 读文件失败 | +| 20 | MR_FAILED4_WRITE_FILE | 写文件失败 | +| 21 | MR_FAILED4_NO_ENCRYPT | 通信协议未加密 | +| 23 | MR_FAILED4_NO_RGBIMAGE | RGB 图像没有 ready | +| 24 | MR_FAILED4_JPGPHOTO_LARGE | JPG 照片过大 | +| 25 | MR_FAILED4_JPGPHOTO_SMALL | JPG 照片过小 | +| 26 | MR_FAILED4_DETECT_QR | 识别过程中扫描并解码了二维码 | + +--- + +## 7. NOTE 消息 + +### 7.1 NOTE 格式 + +```text +SyncWord MsgID=0x01 Size Data Checksum +Data = nid + data[n] +``` + +### 7.2 NID 定义 + +| NID | Code | 说明 | +|---|---:|---| +| NID_READY | 0 | 模组已准备好 | +| NID_FACE_STATE | 1 | 返回人脸状态、位置、姿态 | +| NID_UNKNOWNERROR | 2 | 未知错误 | +| NID_OTA_DONE | 3 | OTA 完成 | +| NID_EYE_STATE | 4 | 眼睛状态 | +| NID_AUTO_VERIFY | 10 | 自动检测状态 | +| NID_QR_DATA_FLAGS | 15 | 二维码数据 | + +--- + +## 8. NID_FACE_STATE 人脸状态数据 + +数据结构: + +| 字段 | 类型 | 长度 | 说明 | +|---|---|---:|---| +| state | int16 | 2 bytes | 人脸状态 | +| left | int16 | 2 bytes | 人脸框左侧距离 | +| top | int16 | 2 bytes | 人脸框上方距离 | +| right | int16 | 2 bytes | 人脸框右侧距离 | +| bottom | int16 | 2 bytes | 人脸框下方距离 | +| yaw | int16 | 2 bytes | 左右转头角度,负为左转,正为右转 | +| pitch | int16 | 2 bytes | 上下抬头/低头,负为抬头,正为低头 | +| roll | int16 | 2 bytes | 歪头角度,负为右歪,正为左歪 | + +### 8.1 Face State Code + +| Code | 名称 | 说明 | +|---:|---|---| +| 0 | FACE_STATE_NORMAL | 人脸正常 | +| 1 | FACE_STATE_NOFACE | 未检测到人脸 | +| 2 | FACE_STATE_TOOUP | 太靠近上边沿 | +| 3 | FACE_STATE_TOODOWN | 太靠近下边沿 | +| 4 | FACE_STATE_TOOLEFT | 太靠近左边沿 | +| 5 | FACE_STATE_TOORIGHT | 太靠近右边沿 | +| 6 | FACE_STATE_FAR | 距离太远 | +| 7 | FACE_STATE_CLOSE | 距离太近 | +| 8 | FACE_STATE_EYEBROW_OCCLUSION | 眉毛遮挡 | +| 9 | FACE_STATE_EYE_OCCLUSION | 眼睛遮挡 | +| 10 | FACE_STATE_FACE_OCCLUSION | 脸部遮挡 | +| 11 | FACE_STATE_DIRECTION_ERROR | 录入方向错误 | +| 12 | FACE_STATE_EYE_CLOSE_STATUS_OPEN_EYE | 闭眼模式检测到睁眼 | +| 13 | FACE_STATE_EYE_CLOSE_STATUS | 闭眼状态 | +| 14 | FACE_STATE_EYE_CLOSE_UNKNOWN_STATUS | 无法判定睁闭眼 | +| 128 | PV_STATE_NORMAL | 检测到手掌信息 | + +--- + +## 9. 核心业务流程 + +### 9.1 主控接收消息流程 + +```text +串口接收字节流 + ↓ +查找同步头 EF AA + ↓ +读取 MsgID + ↓ +读取 Size 高低字节 + ↓ +读取 Data + ↓ +读取 Checksum 并校验 + ↓ +根据 MsgID 分发: + - 0x00:REPLY + - 0x01:NOTE + - 其他:ImageOrTemplate +``` + +### 9.2 一般命令处理流程 + +```text +主控发送命令 + ↓ +模组执行命令 + ↓ +期间可能持续返回 NOTE + ↓ +最终返回 REPLY + ↓ +主控根据 result 判断成功/失败 +``` + +如果超时或无结果: + +- 主控可发送 GET STATUS 查询状态; +- 如果模组仍 BUSY,可等待、取消或 RESET; +- 如果出现 ERROR/INVALID,应提示异常或重新上电。 + +### 9.3 上下电流程 + +```text +主控给模组上电 + ↓ +模组初始化 + ↓ +模组发送 NOTE:READY + ↓ +主控开始发送业务命令 + ↓ +模组处理并返回结果 + ↓ +主控确认无消息或超时后可断电 +``` + +### 9.4 录入流程 + +```text +发送 ENROLL / ENROLL_ITG + ↓ +模组返回 NOTE:FACE_STATE + ↓ +Unity 提示用户调整距离、方向、姿态 + ↓ +模组返回 REPLY:ENROLL + ↓ +成功:记录 user_id 和 face_direction +失败:显示 result 对应原因 +``` + +### 9.5 验证流程 + +```text +发送 VERIFY + ↓ +模组返回 NOTE:FACE_STATE + ↓ +Unity 实时显示检测状态 + ↓ +模组返回 REPLY:VERIFY + ↓ +成功:得到 user_id / user_name / admin +失败:显示错误原因 +``` + +### 9.6 照片下发注册流程 + +```text +发送 0xF7 第一包:Seq=0 + PhotoLen + BioType + 可选扩展参数 + ↓ +模组 ACK + ↓ +主控 Seq 从 1 开始分包发送照片/特征数据 + ↓ +每包 MTU ≤ 246 bytes + ↓ +模组逐包 ACK + ↓ +最后返回 UserID +``` + +--- + +## 10. 文档中的关键实现风险 + +| 风险 | 说明 | 建议 | +|---|---|---| +| 加密协议不完整 | 文档未给出私有密钥派生算法和 AES/SMPL 细节 | 向供应商索取 SDK 或补充协议 | +| 0x12 同时用于 VERIFY 和 AUTO_VERIFY | 同一 MsgID 下 Data 结构不同 | Unity API 层必须区分调用场景 | +| 0x71 / 0x72 / 0x74 返回数据有两阶段 | 第一阶段返回长度,第二阶段返回图片包 | 需要做图片接收状态机 | +| 0xF7 照片注册需要分包 | 每包 ≤246 bytes,需等待 ACK 后继续 | 必须实现可靠分包发送队列 | +| 图片包为二进制流 | Unity 需要缓存 byte[] 并转换 Texture2D | 注意主线程 UI 更新 | +| 中文姓名编码未明确 | 文档仅说明 user_name 32 bytes / NameString 长度 | 建议先按 ASCII/UTF-8 测试,最终以供应商确认为准 | +| UVC 视频预览与串口协议是两条链路 | 文档里有 USB UVC 参数,但协议数据只负责控制 | Unity 视频画面需要单独使用 UVC/USB 摄像头方案 | + +--- + +## 11. 一句话总结 + +这份文档定义了 **主控与 SI-M1 人脸识别模组之间的二进制通信协议**,主控通过固定帧格式发送录入、验证、删除、查询、抓拍、二维码、照片注册、特征读写、加密、UVC 参数、升级等命令;模组通过 REPLY 返回最终结果,通过 NOTE 主动上报状态,通过 ImageOrTemplate 分包上传图片或特征模板。 + diff --git a/Documentation~/SI-M1人脸识别算法模组_文档功能总结.md.meta b/Documentation~/SI-M1人脸识别算法模组_文档功能总结.md.meta new file mode 100644 index 0000000..2d23744 --- /dev/null +++ b/Documentation~/SI-M1人脸识别算法模组_文档功能总结.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b58b6a37fb32dba499120e6cd57a78cf +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation~/串口指令测试说明.md b/Documentation~/串口指令测试说明.md new file mode 100644 index 0000000..a8b8339 --- /dev/null +++ b/Documentation~/串口指令测试说明.md @@ -0,0 +1,437 @@ +# SI-M1 串口指令测试说明 + +本文档用于串口助手或底层串口程序直接测试 SI-M1 人脸识别模组协议。Unity 端同样按本文的封包格式发送命令。 + +## 1. 基础封包格式 + +主控发送: + +```text +EF AA MID SIZE_H SIZE_L DATA... CHECKSUM +``` + +字段说明: + +| 字段 | 长度 | 说明 | +| --- | --- | --- | +| `EF AA` | 2 bytes | 同步头 | +| `MID` | 1 byte | 命令 ID | +| `SIZE_H SIZE_L` | 2 bytes | DATA 长度,大端 | +| `DATA` | N bytes | 命令参数,可为空 | +| `CHECKSUM` | 1 byte | `MID ^ SIZE_H ^ SIZE_L ^ DATA[0] ^ ...` | + +模组回复: + +```text +EF AA 00 SIZE_H SIZE_L MID RESULT DATA... CHECKSUM +``` + +主动通知: + +```text +EF AA 01 SIZE_H SIZE_L NID DATA... CHECKSUM +``` + +常见结果码: + +| Result | 说明 | +| --- | --- | +| `00` | 成功 | +| `01` | 模组拒绝命令 | +| `02` | 操作终止 | +| `06` | 参数无效 | +| `08` | 未找到用户 | +| `09` | 用户已满 | +| `0A` | 人脸已录入 | +| `0C` | 活体检测失败 | +| `0D` | 超时 | + +## 2. 通用测试准备 + +1. 打开串口:默认 `115200`,8N1。 +2. 上电后先等模组主动发 READY:`EF AA 01 00 01 00 00`。 +3. 如果没有 READY,可发送 `GET STATUS` 确认设备是否在线。 +4. 中文用户名使用 UTF-8,定长 `user_name[32]` 不足补 `00`。 +5. 多字节数值均为大端,例如用户 ID `1` 为 `00 01`。 + +## 3. 基础接口 + +| 接口 | MID | DATA | 示例发送 Hex | 成功回复 DATA | +| --- | --- | --- | --- | --- | +| 复位模组 | `10` | 无 | `EF AA 10 00 00 10` | 无 | +| 获取状态 | `11` | 无 | `EF AA 11 00 00 11` | `status` | +| 取消当前人脸流程 | `23` | 无 | `EF AA 23 00 00 23` | 无 | +| 获取版本 | `30` | 无 | `EF AA 30 00 00 30` | 版本字符串 | +| 获取 SN | `93` | 无 | `EF AA 93 00 00 93` | `Device_SN[32]`,前 8 bytes 有效 | +| 固件升级 | `F6` | 无 | `EF AA F6 00 00 F6` | 无,后续等 OTA 通知 | + +状态值: + +| Status | 说明 | +| --- | --- | +| `00` | Standby 待机 | +| `01` | Busy 忙碌 | +| `02` | Error 错误 | +| `03` | Invalid 无效 | + +## 4. 人脸验证接口 + +### 4.1 普通验证 `VERIFY` + +DATA: + +```text +pd_rightaway timeout max_recognition_times +``` + +| 字段 | 示例 | 说明 | +| --- | --- | --- | +| `pd_rightaway` | `00` | 普通验证 | +| `timeout` | `0A` | 超时时间,单位秒 | +| `max_recognition_times` | `1E` | 最大识别次数 | + +示例发送: + +```text +EF AA 12 00 03 00 0A 1E 05 +``` + +成功回复 DATA: + +```text +user_id[2] user_name[32] admin unlock_status +``` + +### 4.2 自动验证 `AUTO_VERIFY` + +DATA: + +```text +at_verify timeout +``` + +| 字段 | 示例 | 说明 | +| --- | --- | --- | +| `at_verify` | `01` | `01` 开启,`00` 关闭 | +| `timeout` | `0A` | 超时时间,单位秒 | + +示例发送: + +```text +EF AA 12 00 02 01 0A 1B +``` + +## 5. 人脸录入接口 + +用户名字段: + +```text +user_name[32] +``` + +例如 `张三` 的 UTF-8 bytes 为: + +```text +E5 BC A0 E4 B8 89 +``` + +写入 `user_name[32]` 时格式为: + +```text +E5 BC A0 E4 B8 89 00 00 ... 共 32 bytes +``` + +方向值: + +| 值 | 说明 | +| --- | --- | +| `01` | 正脸 | +| `02` | 向右 | +| `04` | 向左 | +| `08` | 向下 | +| `10` | 向上 | + +### 5.1 交互录入 `ENROLL` + +DATA: + +```text +admin user_name[32] face_dir timeout +``` + +示例:非管理员、空用户名、正脸、20 秒: + +```text +EF AA 13 00 23 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +01 14 25 +``` + +成功回复 DATA: + +```text +user_id[2] face_direction +``` + +录入成功后建议立即发送 `GET USER INFO` 确认: + +```text +EF AA 22 00 02 00 01 21 +``` + +### 5.2 单帧录入 `ENROLL_SINGLE` + +DATA 同 `ENROLL`,MID 改为 `1D`。 + +示例:非管理员、空用户名、正脸、20 秒: + +```text +EF AA 1D 00 23 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +01 14 2B +``` + +### 5.3 集成录入 `ENROLL_ITG` + +Unity 当前 DATA 顺序: + +```text +admin user_name[32] face_dir timeout single_frame duplicate_flag reserved +``` + +| 字段 | 说明 | +| --- | --- | +| `single_frame` | `01` 单帧,`00` 交互 | +| `duplicate_flag` | `00` 查重,`01` 不查重 | +| `reserved` | 固定 `00` | + +成功回复 DATA: + +```text +user_id[2] face_direction +``` + +### 5.4 抓拍人脸后注册 `ENROLL_SNAPFACEIMAGE` + +DATA: + +```text +admin user_name[32] 00 00 +``` + +成功回复 DATA: + +```text +user_id[2] face_direction +``` + +## 6. 用户管理接口 + +| 接口 | MID | DATA | 示例发送 Hex | 成功回复 DATA | +| --- | --- | --- | --- | --- | +| 删除用户 | `20` | `user_id[2]` | `EF AA 20 00 02 00 01 23` | 无 | +| 删除全部用户 | `21` | 无 | `EF AA 21 00 00 21` | 无 | +| 查询用户信息 | `22` | `user_id[2]` | `EF AA 22 00 02 00 01 21` | `user_id[2] user_name[32] admin` | +| 查询所有用户 ID | `24` | `page_index` | `EF AA 24 00 01 00 25` | `count[1] user_id[]` | +| 查询所有用户 ID 2 | `25` | `page_index` | `EF AA 25 00 01 00 24` | `count[2] user_id[]` | + +分页说明: + +- `page_index` 从 `00` 开始。 +- 每页最多 100 个 ID。 +- 当返回数量小于 100 时,表示最后一页。 + +## 7. 二维码接口 + +DATA: + +```text +00 timeout +``` + +示例:扫描 10 秒: + +```text +EF AA 70 00 02 00 0A 78 +``` + +成功回复 DATA: + +```text +QR 字符串 bytes +``` + +模组也可能通过 NOTE `NID=0F` 主动上报二维码内容。 + +## 8. 图片抓拍接口 + +| 接口 | MID | DATA | 示例发送 Hex | 回复说明 | +| --- | --- | --- | --- | --- | +| 普通图片抓拍 | `71` | 无 | `EF AA 71 00 00 71` | 先回复图片长度,再用 MID `71` 分包上传图片 | +| 人脸图片抓拍 | `72` | 无 | `EF AA 72 00 00 72` | 先回复图片长度和用户 ID,再用 MID `72` 分包上传图片 | +| 大图抓拍 | `74` | `dpi` | `EF AA 74 00 01 00 75` | 先回复图片长度,再用 MID `74` 分包上传图片 | + +大图 DPI: + +| 值 | 说明 | +| --- | --- | +| `00` | 480x640 | +| `01` | 600x800 | +| `02` | 1200x1600 | + +图片 DATA 包由模组发送,Unity 按数据总长度拼接。 + +## 9. 照片注册接口 + +### 9.1 推荐照片注册 `ENROLL_WITH_PHOTO` + +初始化包 DATA: + +```text +00 00 file_size[4] bio_type name_len name_bytes duplicate_flag +``` + +| 字段 | 说明 | +| --- | --- | +| `file_size[4]` | 图片或特征文件总长度,大端 | +| `bio_type` | `00` 普通照片,`01` 加密照片,`02` 普通特征,`03` 压缩特征 | +| `name_len` | 用户名 UTF-8 byte 长度 | +| `name_bytes` | 用户名 UTF-8 bytes | +| `duplicate_flag` | `00` 查重,`01` 不查重 | + +示例:文件长度 1024,普通照片,姓名 `张三`,查重: + +```text +EF AA F7 00 0F 00 00 00 00 04 00 00 06 E5 BC A0 E4 B8 89 00 D6 +``` + +后续文件分包 DATA: + +```text +seq[2] file_bytes +``` + +说明: + +- `seq` 从 `00 01` 开始。 +- 每包最多 246 bytes。 +- 每包 MID 仍为 `F7`。 +- 最后一包发送完成后,成功回复 DATA 通常包含 `user_id[2]`。 + +### 9.2 指定 ID 照片注册 `ENROLL_WITH_PHOTO_AND_ID` + +初始化包 DATA: + +```text +00 00 user_id[2] file_size[4] bio_type name_len name_bytes duplicate_flag +``` + +后续文件分包规则同 `F7`,MID 为 `D7`。 + +## 10. 特征接口 + +### 10.1 读取特征 `READ_FEATURE` + +DATA: + +```text +00 00 user_id[2] feature_type +``` + +示例:读取用户 1 的彩色人脸特征: + +```text +EF AA FA 00 05 00 00 00 01 01 FF +``` + +特征类型: + +| 值 | 说明 | +| --- | --- | +| `01` | 彩色人脸 | +| `02` | 彩色加红外人脸 | +| `03` | 手掌 | + +回复说明: + +- 首个 REPLY DATA 返回特征总长度。 +- 后续模组用 MID `FA` 分包发送特征 bytes。 + +### 10.2 写入特征 `WRITE_FEATURE` + +初始化包 DATA: + +```text +00 00 feature_type name_len name_bytes +``` + +后续特征分包 DATA: + +```text +seq[2] feature_bytes +``` + +说明: + +- `seq` 从 `00 01` 开始。 +- 每包最多 246 bytes。 +- 每包 MID 仍为 `FB`。 +- 最后一包成功后回复 `user_id[2]`。 + +## 11. UVC 与高级设置接口 + +| 接口 | MID | DATA | 示例发送 Hex | 说明 | +| --- | --- | --- | --- | --- | +| 读取 UVC 参数 | `B0` | 无 | `EF AA B0 00 00 B0` | 返回 `usb_type bitrate quality attributes` | +| 设置 UVC 参数 | `B1` | `usb_type bitrate quality attributes` | `EF AA B1 00 04 20 08 50 00 CD` | 示例为 USB2.0、8Mbps、质量 80、无镜像旋转 | +| 设置人脸框显示 | `B5` | `visible` | `EF AA B5 00 01 01 B5` | `01` 显示,`00` 隐藏 | +| 设置 RGB 阈值 | `D4` | `level` | `EF AA D4 00 01 02 D7` | `level` 范围 0 到 4 | +| 设置查重 | `FC` | `check_flag` | `EF AA FC 00 01 00 FD` | `00` 查重,`01` 不查重 | +| 查询查重 | `FC` | 无 | `EF AA FC 00 00 FC` | 返回查重状态 | +| 设置 Demo Mode | `FE` | `enable` | `EF AA FE 00 01 01 FE` | `01` 开启,`00` 关闭 | + +UVC `attributes`: + +| bit | 说明 | +| --- | --- | +| bit0 | 镜像 | +| bit1 | 旋转 180 度 | + +## 12. 加密接口 + +| 接口 | MID | DATA | 示例发送 Hex | +| --- | --- | --- | --- | +| 初始化加密 | `50` | `seed[4] mode` | `EF AA 50 00 05 00 01 02 03 00 55` | +| 设置 Release Key | `52` | `key[16]` | `EF AA 52 00 10 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 42` | +| 设置 Debug Key | `53` | `key[16]` | `EF AA 53 00 10 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 43` | + +说明: + +- Key 必须为 16 bytes。 +- 初始化加密后,后续是否需要加密封包取决于模组固件配置和供应商协议细节。 + +## 13. 推荐人工测试顺序 + +1. `GET STATUS`:确认串口可通信。 +2. `GET VERSION` / `GET SN`:确认设备信息读取正常。 +3. `VERIFY`:无用户时应返回未找到用户或超时。 +4. `ENROLL_SINGLE` 或 `ENROLL`:录入一个测试用户。 +5. `GET ALL USER ID`:确认用户列表出现新 ID。 +6. `GET USER INFO`:确认用户名、管理员标记正确,中文姓名不应显示为 `?`。 +7. `VERIFY`:确认刚录入的人脸可以验证成功。 +8. `SNAP_UPLOAD_IMAGE`:确认图片分包可以完整拼接。 +9. `READ/WRITE FEATURE`、照片注册、UVC 参数、加密接口按需要继续验证。 + +## 14. 常见问题 + +| 现象 | 排查方向 | +| --- | --- | +| 串口打开但无回复 | 确认波特率、TX/RX 线序、供电、GND、是否收到 READY | +| 校验失败 | 重新计算 `CHECKSUM = MID ^ SIZE_H ^ SIZE_L ^ DATA...` | +| 中文姓名变成 `?` | 确认发送端使用 UTF-8,不要使用 ASCII | +| 录入成功但列表无用户 | 录入成功后立即发 `GET ALL USER ID` 和 `GET USER INFO` 确认 | +| 图片或特征不完整 | 串口助手需要保留二进制原始 bytes,不能按文本行读取 | +| 多包命令超时 | 分包 `seq` 从 `00 01` 开始,每包最大 246 bytes | + diff --git a/Documentation~/串口指令测试说明.md.meta b/Documentation~/串口指令测试说明.md.meta new file mode 100644 index 0000000..34fecba --- /dev/null +++ b/Documentation~/串口指令测试说明.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5cb8b30bc471e4c4298b2344b3cbf397 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation~/接口文档与开发说明.md b/Documentation~/接口文档与开发说明.md new file mode 100644 index 0000000..6907a25 --- /dev/null +++ b/Documentation~/接口文档与开发说明.md @@ -0,0 +1,16 @@ +# Interface And Development Notes + +This public package is distributed as compiled DLLs and does not include SDK source files, Demo source files, or internal test source files. + +Use these public entry points from your Unity project: + +- `Aisu.SIM1.Face.Core.FaceModuleManager` +- `Aisu.SIM1.Face.Core.FaceSdkSettings` +- `Aisu.SIM1.Face.Commands.*Service` +- `Aisu.SIM1.Face.Models.*` +- `Aisu.SIM1.Face.Protocol.*` +- `Aisu.SIM1.Face.Transport.IFaceModuleTransport` + +For quick setup, see `SDK_QuickStart.md`. +For API details, see `SDK_API_Reference.md`. +For serial command behavior, see `串口指令测试说明.md`. diff --git a/Documentation~/接口文档与开发说明.md.meta b/Documentation~/接口文档与开发说明.md.meta new file mode 100644 index 0000000..2b652bd --- /dev/null +++ b/Documentation~/接口文档与开发说明.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f3143ed1eba09314497d839bc2fb455a +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Aisu.SIM1.Face.Editor.dll b/Editor/Aisu.SIM1.Face.Editor.dll new file mode 100644 index 0000000000000000000000000000000000000000..c8d7bc563501112a8c0d975d8d874a8f79215940 GIT binary patch literal 18432 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

r=-&Z{bT4;vqV*HzMR-mN)i2yD5eFll3Mdo2| z1Z$Veu`K|MHUOXj5F0C`9K97#fI|P!BP8}rkT;3O9S|NuJb3|^bYauLMct{~RniJw zgdl=PI53svtq8Vk8p>#KK-(siQv{*LjVli>0wIPzr@%57`NsZ$7yXlK$K|syn?|vG zFb%;@wFz`bAfcP}@f9I&*c;HHd?cw+k}7xUKmh`#j1MvtRjt%~W>787pignt zCed5CJ*BGrP-jW!ppeZ;MO@$>O9Lp?_(qm;Iz=D(oKcm{pR7_4em$ z0;f4}Km`tP;2^_8@LMI_a31JD1pS*$SA~57gjF?ISfPa#tL;kOn6QYnw~Ef}MxCM; ztJDsGh9LmyV2A@~AkfT$4#L#|0WEyXdc z_y|v;-E)dm(#p~D1ntn1m#~tv5-#-?sVt~2Y@oAHTy8#yj?sXE*tyUdttuHPHPxGa zlSbXM%{b$k!-wn~?_{(5x@(hC zGb>(yz$y>MDRfFi4d|hT8_y|a`BPR5>hOFh`pse7L*_FqJr%)^g{;c;%WyormF3hQ}B_$nZGBQHCcOKFRPj!vhQtGCV|S zbpxe_H-{Gf)bN;f$}0svpN_xjn6cuQ!_KCoW9t%e>$o<~od0VGrnddE|KEG|5|MR7@cU8V)^^W*JqUH+ut2&OyNX}`JG$w zAt_ea-0G_sh?td;*ba(+sCmYIyO81 zy}nc>)=9kMnEgJaj`}`&#((RG@>Pp$t;9#x-%nlUJJj>}17!TNmSr5A`e>43#owd? zhiK^S2}=i_e6EmU+25oi>pN%@*G`6U)v+DRwkl|)kLqp4Zw9RERby*8W%Y<$TSGN{GJkL5(8o z+W_dXA@hs1RuR#%)=JUzep%upotB-b>eO3@Ot%!Fb$w9fy_X_7wU@07hW{W#y_Yik zb$csoXjgw*8CAQr-mb9}p-ug5*~(Rv^D)AShp!B;FusshUX}>aB1Q9GUvU$Dn~d5K zTIFi|@W}gV(yswnjew3kYMPZHG=L8>POcitl11YK^_DiYfDamvteAQ{8uvjtDje$m zQYluyU*YU;ZeVhNg5@5mxAKI(@%0O#x+@AYPcsW2eg?@tCM^6?6s5DilY!sn6_ape zm<}LYZ~L$wv9zHaS zb3u5LjO5<$H3e1}$^KeH25qJJwm*JKSd=O`s^84y^RU@UCBly;NpVg;FdRY8{cMg9 zg_v*BD`oV0F&iQr$^O0=8!Y>SVr)|Tj50N6Lv;AO+wpD@S!WWty^L2^z`{F_gTyLp zY-!(oL3_)BwweG*1=^c0g?Y@%!*7zcY`ICuwhadA`J=dw;)jZlZZfMKTTh@(#)Vy2 zu>`+9vZUSSfrQ`q$^P5a-rQc?_vaHA4o249`^DBLhPHloF~w-UuoIuT*^$DV0g+TE za?zQJF6ggWupQwAc-5wpUm(TIlwR}cY>oCL@%l#R%1ERW2j-)YN~bz&mQ`2xR0mfz z_w+6d2YWkn8ol|})j$}Z;auBTyJ)e{Ihp>l&Iy%vUe$Rs|ME#s`hqDB)!m2^nSX}~ z5166s`zJHxgy_P;pg9(!7Xj6WKO+%-FImNTcNVl1-?U-fnxgz^^wVKQEo+-M;Z8|O zEbQ=0d}p1!3Ag-~=EjaaheCgPW)yqeIC=IL<1b%~j_~iUw5(d!{FRm69|?FjK=Ob8 z{?`Iz0m=PsiV(xu_MkPcz^~Vgr<2@TT$c|^v7b7*{@ZV0Jj1W+wBy%vw&3jcCSWZ% z$Gr~W7QofO>F*KeYkyHqmxC6#L}O0eGt@G*8BjuN5N9q|<3uK%Xr&YO+v%hy{wmSf z1Dz;d7#?44G=27PT+382z>yl}4Q@jU(?d*bAQkMqGj4 zK%#T~gNQ};q|^5ckrQ>ocOk4i$T8I^)rB|-8^b^P!_Eoxwn7I!I{_M zX2*ai0=TZ?^w(=TS%##|vQp^3myde3#|G$Er;+JAZmv~qdys7eejAEx*obn|`QCoi hLJT<%d|*68{olV2vH-^8LdYD$l<|N6{{OSU{{!}!s6GGy literal 0 HcmV?d00001 diff --git a/Runtime/Aisu.SIM1.Face.Runtime.dll.meta b/Runtime/Aisu.SIM1.Face.Runtime.dll.meta new file mode 100644 index 0000000..9266a20 --- /dev/null +++ b/Runtime/Aisu.SIM1.Face.Runtime.dll.meta @@ -0,0 +1,23 @@ +fileFormatVersion: 2 +guid: 8cf50efc5a2c44d481a20dd7b5f0b001 +PluginImporter: + serializedVersion: 1 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + Any: + enabled: 1 + settings: {} + Editor: + enabled: 0 + settings: + DefaultValueInitialized: true + WindowsStoreApps: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json new file mode 100644 index 0000000..6a18721 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "xin.aisu.si-m1.face", + "displayName": "AISU SI-M1 Face SDK", + "version": "0.1.0", + "unity": "2021.3", + "description": "Unity UPM package for the AISU SI-M1 face recognition module protocol, transport, command services, and lifecycle manager. Distributed as compiled DLLs without source code.", + "author": { + "name": "AISU" + }, + "keywords": [ + "aisu", + "si-m1", + "face", + "recognition", + "serial", + "unity" + ] +}