using System; using System.Collections.Generic; using System.Text; using Aisu.SIM1.Face.Models; using Aisu.SIM1.Face.Protocol; using UnityEngine; namespace Aisu.SIM1.Face.Core { /// /// FaceModule 键盘功能测试脚本,用于不依赖 UI 面板快速验证串口与业务接口。 /// public sealed class FaceModuleKeyboardTester : MonoBehaviour { [Header("Manager 引用")] // 场景中的 FaceModuleManager,不填时自动查找或创建。 public FaceModuleManager Manager; [Header("串口参数(A/Q/W)")] // 是否使用 Mock 模组。 public bool UseMock; // 串口名称;示例 COM3,按当前设备实际串口修改。 public string PortName = "COM3"; // 串口波特率。 public int BaudRate = 115200; // 打开串口后等待 READY 的超时时间。 public int ReadyTimeoutMs = 3000; [Header("人脸检测/验证参数(S/Z/X)")] // 验证超时时间,单位秒。 public byte VerifyTimeout = 10; // 最大识别次数。 public byte MaxRecognitionTimes = 30; // 是否开启自动验证。 public bool AutoVerifyEnable = true; [Header("注册用户参数(E/R/T/F)")] // 注册用户名。 public string EnrollUserName = "TestUser"; // 注册用户是否为管理员。 public bool EnrollAsAdmin; // 注册方向。 public FaceDirection EnrollDirection = FaceDirection.Middle; // 注册超时时间,单位秒。 public byte EnrollTimeout = 20; // 集成录入是否使用单帧模式。 public bool IntegratedSingleFrame; // 集成录入是否查重。 public bool IntegratedDuplicateCheck = true; [Header("用户管理参数(D/M/G/H/J)")] // 查询 / 删除使用的用户 ID。 public int UserId = 1; // 是否使用 GET_ALL_USERID2 查询所有用户。 public bool UseGetAllUserId2; [Header("二维码参数(C)")] // 扫码超时时间,单位秒。 public byte QrTimeout = 10; [Header("图片抓拍参数(V/B/N)")] // 大图 DPI。 public ImageDpi LargeImageDpi = ImageDpi.Dpi480x640; [Header("照片注册参数(P/O)")] // 照片或特征文件路径。 public string PhotoRegisterFilePath; // 照片注册 BioType。 public BioType PhotoBioType = BioType.NormalPhoto; // 照片注册用户名。 public string PhotoUserName = "PhotoUser"; // 照片注册是否查重。 public bool PhotoDuplicateCheck = true; // 指定 ID 照片注册使用的用户 ID。 public int PhotoUserId = 100; [Header("特征参数(L/K)")] // 特征类型。 public FeatureType FeatureType = FeatureType.ColorFace; // 写入特征文件路径。 public string FeatureFilePath; // 写入特征使用的用户名。 public string FeatureUserName = "FeatureUser"; [Header("高级参数(1/2/3)")] // RGB 识别阈值 0-4。 [Range(0, 4)] public byte RgbLevel = 2; // 是否启用查重。 public bool DuplicateCheckEnable = true; // Demo Mode 开关。 public bool DemoModeEnable; [Header("UVC 参数(U/I/Y)")] // USB Type。 public byte UsbType = 0x20; // UVC 码率。 public byte UvcBitrateMbps = 8; // MJPG 质量。 [Range(10, 100)] public byte MjpgQuality = 80; // 图像镜像。 public bool UvcMirror; // 180 度旋转。 public bool UvcRotate180; // 人脸框显示。 public bool FaceLocationVisible = true; [Header("加密参数(4/5/6)")] // Release Key,16 bytes Hex,例如:00 01 02 ... public string ReleaseKeyHex; // Debug Key,16 bytes Hex,例如:00 01 02 ... public string DebugKeyHex; // Seed,4 bytes Hex。 public string SeedHex; // 加密模式。 public byte EncryptionMode; [Header("固件升级参数(7)")] // 固件升级按键说明。 public string FirmwareKey = "7 启动固件升级"; [Header("串口指令解析(Space)")] // 待解析的 TX 串口 Hex 指令,按空格键后只解析为中文说明,不会发送到串口。 [TextArea(2, 6)] public string TxSerialCommandHex = "EF AA 10 00 00 10"; // 待解析的 RX 串口 Hex 内容,可填模组返回的 REPLY / NOTE。 [TextArea(2, 6)] public string RxSerialCommandHex = "EF AA 01 00 01 00 00"; // 兼容旧字段,留空 TX / RX 时会解析这里的内容。 [HideInInspector] public string SerialCommandHex; private Action m_readyHandler; // READY 事件回调。 private Action m_rawLogHandler; // 原始日志事件回调。 private Action m_errorHandler; // 错误事件回调。 private Action m_faceStateHandler; // 人脸状态事件回调。 [Header("按键说明")] // A 打开串口,Q 关闭串口,W 等待 READY / GET STATUS 探测。 public string ConnectionKeys = "A 打开串口,Q 关闭串口,W 等待 READY / 主动探测"; // S 检测人脸,Z 自动验证,X 停止/RESET。 public string VerifyKeys = "S 检测人脸,Z 自动验证,X RESET"; // E 单帧注册,R 交互注册,T 集成注册,F 取消注册。 public string EnrollKeys = "E 单帧注册,R 交互注册,T 集成注册,F 取消注册"; // D 查询所有用户 ID,G 查询用户,H 删除用户,J 删除全部用户。 public string UserKeys = "D 查询所有用户 ID,M 查询所有用户信息,G 查询用户,H 删除用户,J 删除全部用户"; // C 扫码,V 普通抓拍,B 人脸抓拍,N 大图抓拍。 public string MediaKeys = "C 扫码,V 普通抓拍,B 人脸抓拍,N 大图抓拍"; // P 照片注册,O 指定 ID 照片注册,L 读特征,K 写特征。 public string AdvancedKeys = "P 照片注册,O 指定 ID 照片注册,L 读特征,K 写特征"; // U 读 UVC,I 设置 UVC,Y 人脸框,数字 1/2/3 分别设置 RGB/查重/Demo。 public string SettingKeys = "U 读 UVC,I 设置 UVC,Y 人脸框,1 RGB,2 查重,3 Demo"; // 数字 4/5/6/7 分别设置 Release Key、Debug Key、Init Encryption、固件升级。 public string SystemKeys = "4 Release Key,5 Debug Key,6 初始化加密,7 固件升级,Space 解析串口指令"; #region Unity API /// /// 初始化测试脚本。 /// private void Awake() { EnsureManager(); BindLogs(); } private void OnDestroy() { UnbindLogs(); } /// /// 每帧检测键盘输入并触发测试功能。 /// private void Update() { if (Input.GetKeyDown(KeyCode.A)) OpenSerial(); if (Input.GetKeyDown(KeyCode.Q)) CloseSerial(); if (Input.GetKeyDown(KeyCode.W)) WaitReadyOrProbe(); if (Input.GetKeyDown(KeyCode.S)) VerifyFace(); if (Input.GetKeyDown(KeyCode.Z)) AutoVerify(); if (Input.GetKeyDown(KeyCode.X)) ResetModule(); if (Input.GetKeyDown(KeyCode.E)) EnrollSingle(); if (Input.GetKeyDown(KeyCode.R)) Enroll(); if (Input.GetKeyDown(KeyCode.T)) EnrollIntegrated(); if (Input.GetKeyDown(KeyCode.F)) FaceReset(); if (Input.GetKeyDown(KeyCode.D)) GetAllUserIds(); if (Input.GetKeyDown(KeyCode.M)) GetAllUserInfos(); if (Input.GetKeyDown(KeyCode.G)) GetUserInfo(); if (Input.GetKeyDown(KeyCode.H)) DeleteUser(); if (Input.GetKeyDown(KeyCode.J)) DeleteAllUsers(); if (Input.GetKeyDown(KeyCode.C)) ScanQrCode(); if (Input.GetKeyDown(KeyCode.V)) SnapImage(); if (Input.GetKeyDown(KeyCode.B)) SnapFaceImage(); if (Input.GetKeyDown(KeyCode.N)) SnapLargeImage(); if (Input.GetKeyDown(KeyCode.P)) EnrollWithPhoto(); if (Input.GetKeyDown(KeyCode.O)) EnrollWithPhotoAndId(); if (Input.GetKeyDown(KeyCode.L)) ReadFeature(); if (Input.GetKeyDown(KeyCode.K)) WriteFeature(); if (Input.GetKeyDown(KeyCode.U)) ReadUvc(); if (Input.GetKeyDown(KeyCode.I)) SetUvc(); if (Input.GetKeyDown(KeyCode.Y)) SetFaceLocationVisible(); if (Input.GetKeyDown(KeyCode.Alpha1)) SetRgbLevel(); if (Input.GetKeyDown(KeyCode.Alpha2)) SetDuplicateCheck(); if (Input.GetKeyDown(KeyCode.Alpha3)) SetDemoMode(); if (Input.GetKeyDown(KeyCode.Alpha4)) SetReleaseKey(); if (Input.GetKeyDown(KeyCode.Alpha5)) SetDebugKey(); if (Input.GetKeyDown(KeyCode.Alpha6)) InitEncryption(); if (Input.GetKeyDown(KeyCode.Alpha7)) UpgradeFirmware(); if (Input.GetKeyDown(KeyCode.Space)) ParseSerialCommand(); } #endregion /// /// 确保 Manager 存在。 /// private void EnsureManager() { if (Manager != null) { return; } Manager = FindObjectOfType(); if (Manager == null) { Manager = new GameObject("FaceModuleManager").AddComponent(); } } /// /// 绑定日志事件。 /// private void BindLogs() { UnbindLogs(); m_readyHandler = () => Log("收到 READY,模组已就绪"); m_rawLogHandler = message => Log("原始数据:" + message); m_errorHandler = (code, message) => LogError("错误码:" + code + ",原因:" + message); m_faceStateHandler = state => Log("人脸状态:" + FaceProtocolEncoding.FaceStateMessage(state.State)); Manager.OnReady += m_readyHandler; Manager.OnRawLog += m_rawLogHandler; Manager.OnError += m_errorHandler; Manager.OnFaceStateUpdated += m_faceStateHandler; } private void UnbindLogs() { if (Manager == null) { return; } if (m_readyHandler != null) { Manager.OnReady -= m_readyHandler; } if (m_rawLogHandler != null) { Manager.OnRawLog -= m_rawLogHandler; } if (m_errorHandler != null) { Manager.OnError -= m_errorHandler; } if (m_faceStateHandler != null) { Manager.OnFaceStateUpdated -= m_faceStateHandler; } m_readyHandler = null; m_rawLogHandler = null; m_errorHandler = null; m_faceStateHandler = null; } /// /// 打开串口。 /// public async void OpenSerial() { try { Manager.Configure(UseMock, PortName, BaudRate); Manager.Open(); Log("串口已打开:" + PortName + ",波特率:" + BaudRate); await Manager.WaitReadyAsync(ReadyTimeoutMs); Log("已收到 READY。"); } catch (Exception ex) { Log("未收到 READY,开始使用 GET STATUS 主动探测。原因:" + ex.Message); await ProbeReadyAfterOpen(); } } /// /// 关闭串口。 /// public async void CloseSerial() { try { await Manager.CloseAsync(); Log("串口已关闭。"); } catch (Exception ex) { LogError("关闭串口失败:" + ex.Message); } } /// /// 等待 READY 或主动探测。 /// public async void WaitReadyOrProbe() { try { await Manager.WaitReadyAsync(ReadyTimeoutMs); Log("已收到 READY。"); } catch { await ProbeReadyAfterOpen(); } } /// /// 使用 GET STATUS 主动探测 READY。 /// private async System.Threading.Tasks.Task ProbeReadyAfterOpen() { try { var status = await Manager.ProbeReadyByStatusAsync(); Log("主动探测成功,当前模组状态:" + status); } catch (Exception ex) { LogError("主动探测失败:" + ex.Message); } } /// /// 检测人脸 / 普通验证。 /// public async void VerifyFace() { try { var result = await Manager.Verify.VerifyAsync(VerifyTimeout, MaxRecognitionTimes); Log("人脸验证完成:成功=" + result.Success + ",用户ID=" + result.UserId + ",姓名=" + result.UserName + ",管理员=" + result.IsAdmin); } catch (Exception ex) { LogError("人脸验证失败:" + ex.Message); } } /// /// 自动验证。 /// public async void AutoVerify() { try { if (AutoVerifyEnable) { await Manager.StartAutoVerifyAsync(VerifyTimeout); Log("自动验证已开启,识别结果通过事件回调。"); return; } await Manager.StopAutoVerifyAsync(VerifyTimeout); Log("自动验证已关闭。"); } catch (Exception ex) { LogError("自动验证失败:" + ex.Message); } } /// /// RESET。 /// public async void ResetModule() { try { await Manager.StopFaceOperationAsync(); Log("已停止当前人脸流程。"); } catch (Exception ex) { LogError("RESET 失败:" + ex.Message); } } /// /// FACE RESET。 /// public async void FaceReset() { try { await Manager.Basic.FaceResetAsync(); Log("FACE RESET 成功。"); } catch (Exception ex) { LogError("FACE RESET 失败:" + ex.Message); } } /// /// 单帧注册。 /// public async void EnrollSingle() { try { var result = await Manager.Enrollment.EnrollSingleAsync(EnrollAsAdmin, EnrollUserName, EnrollTimeout); Log("单帧注册完成:成功=" + result.Success + ",用户ID=" + result.UserId + ",方向=" + result.Direction); await ConfirmEnrollResult(result); } catch (Exception ex) { LogError("单帧注册失败:" + ex.Message); } } /// /// 交互注册。 /// public async void Enroll() { try { var result = await Manager.Enrollment.EnrollAsync(EnrollAsAdmin, EnrollUserName, EnrollDirection, EnrollTimeout); Log("交互注册完成:成功=" + result.Success + ",用户ID=" + result.UserId + ",方向=" + result.Direction); await ConfirmEnrollResult(result); } catch (Exception ex) { LogError("交互注册失败:" + ex.Message); } } /// /// 集成注册。 /// public async void EnrollIntegrated() { try { var result = await Manager.Enrollment.EnrollIntegratedAsync(EnrollAsAdmin, EnrollUserName, EnrollDirection, EnrollTimeout, IntegratedSingleFrame, IntegratedDuplicateCheck); Log("集成注册完成:成功=" + result.Success + ",用户ID=" + result.UserId + ",方向=" + result.Direction); await ConfirmEnrollResult(result); } catch (Exception ex) { LogError("集成注册失败:" + ex.Message); } } /// /// 注册成功后查询用户信息,确认姓名和用户 ID 已经写入模组。 /// private async System.Threading.Tasks.Task ConfirmEnrollResult(FaceEnrollResult result) { if (!result.Success || result.UserId <= 0) { return; } try { var info = await Manager.Users.GetUserInfoAsync(result.UserId); Log("注册确认成功:ID=" + info.UserId + ",姓名=" + info.UserName + ",管理员=" + info.IsAdmin); } catch (Exception ex) { LogError("注册已返回成功,但查询用户确认失败:" + ex.Message); } } /// /// 查询所有用户 ID。 /// public async void GetAllUserIds() { try { var ids = UseGetAllUserId2 ? await Manager.Users.GetAllUserIds2Async() : await Manager.Users.GetAllUserIdsAsync(); Log("用户 ID 列表:" + string.Join(",", ids)); } catch (Exception ex) { LogError("查询用户 ID 列表失败:" + ex.Message); } } /// /// 查询所有用户信息。 /// public async void GetAllUserInfos() { try { var infos = UseGetAllUserId2 ? await Manager.Users.GetAllUserInfos2Async() : await Manager.Users.GetAllUserInfosAsync(); var builder = new StringBuilder(); builder.AppendLine("用户信息列表,共 " + infos.Count + " 个:"); for (var i = 0; i < infos.Count; i++) { builder.AppendLine("ID=" + infos[i].UserId + ",姓名=" + infos[i].UserName + ",管理员=" + infos[i].IsAdmin); } Log(builder.ToString().TrimEnd()); } catch (Exception ex) { LogError("查询所有用户信息失败:" + ex.Message); } } /// /// 查询用户信息。 /// public async void GetUserInfo() { try { var info = await Manager.Users.GetUserInfoAsync(UserId); Log("用户信息:ID=" + info.UserId + ",姓名=" + info.UserName + ",管理员=" + info.IsAdmin); } catch (Exception ex) { LogError("查询用户信息失败:" + ex.Message); } } /// /// 删除用户。 /// public async void DeleteUser() { try { await Manager.Users.DeleteUserAsync(UserId); Log("删除用户成功,用户ID:" + UserId); } catch (Exception ex) { LogError("删除用户失败:" + ex.Message); } } /// /// 删除全部用户。 /// public async void DeleteAllUsers() { try { await Manager.Users.DeleteAllAsync(); Log("删除全部用户成功。"); } catch (Exception ex) { LogError("删除全部用户失败:" + ex.Message); } } /// /// 扫描二维码。 /// public async void ScanQrCode() { try { Log("二维码内容:" + await Manager.QrCode.ScanQrCodeAsync(QrTimeout)); } catch (Exception ex) { LogError("扫描二维码失败:" + ex.Message); } } /// /// 抓拍普通图片。 /// public async void SnapImage() { try { LogImageResult("普通图片", await Manager.Images.SnapUploadImageAsync()); } catch (Exception ex) { LogError("抓拍普通图片失败:" + ex.Message); } } /// /// 抓拍人脸图片。 /// public async void SnapFaceImage() { try { LogImageResult("人脸图片", await Manager.Images.SnapUploadFaceImageAsync()); } catch (Exception ex) { LogError("抓拍人脸图片失败:" + ex.Message); } } /// /// 抓拍大图。 /// public async void SnapLargeImage() { try { LogImageResult("大图", await Manager.Images.SnapUploadLargeImageAsync(LargeImageDpi)); } catch (Exception ex) { LogError("抓拍大图失败:" + ex.Message); } } /// /// 照片注册。 /// public async void EnrollWithPhoto() { try { var bytes = System.IO.File.ReadAllBytes(PhotoRegisterFilePath); var result = await Manager.Images.EnrollWithPhotoAsync(bytes, PhotoBioType, PhotoUserName, PhotoDuplicateCheck, LogProgress); Log("照片注册完成:成功=" + result.Success + ",用户ID=" + result.UserId); } catch (Exception ex) { LogError("照片注册失败:" + ex.Message); } } /// /// 指定 ID 照片注册。 /// public async void EnrollWithPhotoAndId() { try { var bytes = System.IO.File.ReadAllBytes(PhotoRegisterFilePath); var result = await Manager.Images.EnrollWithPhotoAndIdAsync(PhotoUserId, bytes, PhotoBioType, PhotoUserName, PhotoDuplicateCheck, LogProgress); Log("指定 ID 照片注册完成:成功=" + result.Success + ",用户ID=" + result.UserId); } catch (Exception ex) { LogError("指定 ID 照片注册失败:" + ex.Message); } } /// /// 读取特征。 /// public async void ReadFeature() { try { var bytes = await Manager.Features.ReadFeatureAsync(UserId, FeatureType); Log("读取特征完成,长度:" + bytes.Length + " bytes"); } catch (Exception ex) { LogError("读取特征失败:" + ex.Message); } } /// /// 写入特征。 /// public async void WriteFeature() { try { var bytes = System.IO.File.ReadAllBytes(FeatureFilePath); var result = await Manager.Features.WriteFeatureAsync(bytes, FeatureType, FeatureUserName, LogProgress); Log("写入特征完成:成功=" + result.Success + ",用户ID=" + result.UserId); } catch (Exception ex) { LogError("写入特征失败:" + ex.Message); } } /// /// 读取 UVC 参数。 /// public async void ReadUvc() { try { var p = await Manager.Uvc.ReadUsbUvcParametersAsync(); Log("UVC 参数:USB=0x" + p.UsbType.ToString("X2") + ",码率=" + p.BitrateMbps + ",质量=" + p.MjpgQuality + ",镜像=" + p.Mirror + ",旋转180=" + p.Rotate180); } catch (Exception ex) { LogError("读取 UVC 参数失败:" + ex.Message); } } /// /// 设置 UVC 参数。 /// public async void SetUvc() { try { await Manager.Uvc.SetUsbUvcParametersAsync(new UsbUvcParameters { UsbType = UsbType, BitrateMbps = UvcBitrateMbps, MjpgQuality = MjpgQuality, Mirror = UvcMirror, Rotate180 = UvcRotate180 }); Log("设置 UVC 参数成功。"); } catch (Exception ex) { LogError("设置 UVC 参数失败:" + ex.Message); } } /// /// 设置人脸框显示。 /// public async void SetFaceLocationVisible() { try { await Manager.Uvc.SetFaceLocationDisplayAsync(FaceLocationVisible); Log("设置人脸框显示成功,当前值:" + FaceLocationVisible); } catch (Exception ex) { LogError("设置人脸框显示失败:" + ex.Message); } } /// /// 设置 RGB_LEVEL。 /// public async void SetRgbLevel() { try { await Manager.Uvc.SetRgbLevelAsync(RgbLevel); Log("设置 RGB_LEVEL 成功,当前值:" + RgbLevel); } catch (Exception ex) { LogError("设置 RGB_LEVEL 失败:" + ex.Message); } } /// /// 设置查重。 /// public async void SetDuplicateCheck() { try { await Manager.Uvc.SetDuplicateCheckAsync(DuplicateCheckEnable); Log("设置查重成功,启用查重:" + DuplicateCheckEnable); } catch (Exception ex) { LogError("设置查重失败:" + ex.Message); } } /// /// 设置 Demo Mode。 /// public async void SetDemoMode() { try { await Manager.Basic.SetDemoModeAsync(DemoModeEnable); Log("设置 Demo Mode 成功,当前值:" + DemoModeEnable); } catch (Exception ex) { LogError("设置 Demo Mode 失败:" + ex.Message); } } /// /// 设置 Release Key。 /// public async void SetReleaseKey() { try { await Manager.Encryption.SetReleaseEncryptionKeyAsync(ParseHex(ReleaseKeyHex, 16)); Log("设置 Release Key 成功。"); } catch (Exception ex) { LogError("设置 Release Key 失败:" + ex.Message); } } /// /// 设置 Debug Key。 /// public async void SetDebugKey() { try { await Manager.Encryption.SetDebugEncryptionKeyAsync(ParseHex(DebugKeyHex, 16)); Log("设置 Debug Key 成功。"); } catch (Exception ex) { LogError("设置 Debug Key 失败:" + ex.Message); } } /// /// 初始化加密。 /// public async void InitEncryption() { try { await Manager.Encryption.InitEncryptionAsync(ParseHex(SeedHex, 4), EncryptionMode); Log("初始化加密成功。"); } catch (Exception ex) { LogError("初始化加密失败:" + ex.Message); } } /// /// 启动固件升级。 /// public async void UpgradeFirmware() { try { await Manager.Basic.UpgradeFirmwareAsync(); Log("固件升级命令已发送。"); } catch (Exception ex) { LogError("固件升级命令发送失败:" + ex.Message); } } /// /// 将 Inspector 输入的串口 Hex 指令解析为中文说明。 /// public void ParseSerialCommand() { try { var hasTx = !string.IsNullOrWhiteSpace(TxSerialCommandHex); var hasRx = !string.IsNullOrWhiteSpace(RxSerialCommandHex); if (!hasTx && !hasRx && !string.IsNullOrWhiteSpace(SerialCommandHex)) { Log("兼容旧字段解析:\n" + DescribeSerialPackets(ParseHexFlexible(SerialCommandHex))); return; } var builder = new StringBuilder(); if (hasTx) { builder.AppendLine("TX 内容解析:"); builder.AppendLine(DescribeSerialPackets(ParseHexFlexible(TxSerialCommandHex))); } if (hasRx) { if (builder.Length > 0) { builder.AppendLine(); } builder.AppendLine("RX 内容解析:"); builder.AppendLine(DescribeSerialPackets(ParseHexFlexible(RxSerialCommandHex))); } Log(builder.Length > 0 ? builder.ToString().TrimEnd() : "请先填写 TX 或 RX 串口 Hex 内容。"); } catch (Exception ex) { LogError("串口指令解析失败:" + ex.Message); } } /// /// 解析一段串口 bytes 中的所有完整协议包。 /// private static string DescribeSerialPackets(byte[] bytes) { if (bytes == null || bytes.Length == 0) { return "串口指令为空。"; } var builder = new StringBuilder(); var offset = 0; var index = 1; while (offset < bytes.Length) { if (offset + FaceModuleConstants.HeaderLength + FaceModuleConstants.ChecksumLength > bytes.Length) { builder.AppendLine("第 " + index + " 包:剩余字节不足一个完整包头。"); break; } if (bytes[offset] != FaceModuleConstants.SyncWordHigh || bytes[offset + 1] != FaceModuleConstants.SyncWordLow) { builder.AppendLine("第 " + index + " 包:同步头错误,应为 EF AA,当前位置为 " + ToHex(bytes, offset, Math.Min(2, bytes.Length - offset)) + "。"); break; } var msgId = bytes[offset + 2]; var size = (ushort)((bytes[offset + 3] << 8) | bytes[offset + 4]); var packetLength = FaceModuleConstants.HeaderLength + size + FaceModuleConstants.ChecksumLength; if (offset + packetLength > bytes.Length) { builder.AppendLine("第 " + index + " 包:声明 DATA 长度为 " + size + ",但输入字节不完整。"); break; } var data = new byte[size]; if (size > 0) { Buffer.BlockCopy(bytes, offset + FaceModuleConstants.HeaderLength, data, 0, size); } var checksum = bytes[offset + packetLength - 1]; var expected = FacePacketBuilder.CalculateChecksum(msgId, size, data); builder.AppendLine("第 " + index + " 包:"); builder.AppendLine("类型:" + DescribeMessageType(msgId)); builder.AppendLine("MID/MsgID:0x" + msgId.ToString("X2")); builder.AppendLine("DATA 长度:" + size); builder.AppendLine("DATA:" + (data.Length > 0 ? ToHex(data, 0, data.Length) : "无")); builder.AppendLine("校验:" + (checksum == expected ? "正确" : "错误,应为 0x" + expected.ToString("X2"))); builder.AppendLine(DescribePacketData(msgId, data)); offset += packetLength; index++; } return builder.ToString().TrimEnd(); } /// /// 解析协议包类型。 /// private static string DescribeMessageType(byte msgId) { if (msgId == (byte)FaceMessageType.Reply) { return "模组回复 REPLY"; } if (msgId == (byte)FaceMessageType.Note) { return "模组主动通知 NOTE"; } return "主控发送命令:" + DescribeCommand((FaceCommandId)msgId); } /// /// 解析协议包 DATA。 /// private static string DescribePacketData(byte msgId, byte[] data) { if (msgId == (byte)FaceMessageType.Reply) { return DescribeReplyData(data); } if (msgId == (byte)FaceMessageType.Note) { return DescribeNoteData(data); } return DescribeCommandData((FaceCommandId)msgId, data); } /// /// 解析 REPLY 数据。 /// private static string DescribeReplyData(byte[] data) { if (data == null || data.Length < 2) { return "回复解析:DATA 长度不足,至少需要 MID + RESULT。"; } var command = (FaceCommandId)data[0]; var result = (FaceResultCode)data[1]; var payload = Slice(data, 2, data.Length - 2); var builder = new StringBuilder(); builder.AppendLine("回复命令:" + DescribeCommand(command)); builder.AppendLine("结果码:0x" + ((byte)result).ToString("X2") + "," + FaceProtocolEncoding.ResultMessage(result)); builder.Append("回复内容:" + DescribeReplyPayload(command, payload)); return builder.ToString(); } /// /// 解析 NOTE 数据。 /// private static string DescribeNoteData(byte[] data) { if (data == null || data.Length < 1) { return "通知解析:DATA 长度不足,至少需要 NID。"; } var noteId = (FaceNoteId)data[0]; var payload = Slice(data, 1, data.Length - 1); switch (noteId) { case FaceNoteId.Ready: return "通知内容:READY,模组已就绪。"; case FaceNoteId.FaceState: return "通知内容:人脸状态," + DescribeFaceStatePayload(payload); case FaceNoteId.QrData: return "通知内容:二维码数据," + Encoding.UTF8.GetString(payload); default: return "通知内容:" + noteId + ",原始 DATA=" + ToHex(payload, 0, payload.Length); } } /// /// 解析主控命令 DATA。 /// private static string DescribeCommandData(FaceCommandId command, byte[] data) { switch (command) { case FaceCommandId.Reset: case FaceCommandId.GetStatus: case FaceCommandId.FaceReset: case FaceCommandId.GetVersion: case FaceCommandId.GetSn: case FaceCommandId.ReadUsbUvcParameters: case FaceCommandId.UpgradeFirmware: return "命令内容:" + DescribeCommand(command) + ",无参数。"; case FaceCommandId.Verify: return data != null && data.Length == 2 ? DescribeAutoVerifyData(data) : DescribeVerifyData(data); case FaceCommandId.Enroll: case FaceCommandId.EnrollSingle: return DescribeEnrollData(command, data, false); case FaceCommandId.EnrollIntegrated: return DescribeEnrollData(command, data, true); case FaceCommandId.DeleteUser: case FaceCommandId.GetUserInfo: return "命令内容:" + DescribeCommand(command) + ",用户ID=" + ReadUInt16Safe(data, 0); case FaceCommandId.DeleteAll: return "命令内容:删除全部用户。"; case FaceCommandId.GetAllUserId: case FaceCommandId.GetAllUserId2: return "命令内容:" + DescribeCommand(command) + ",页索引=" + ReadByteSafe(data, 0); case FaceCommandId.ScanQrCode: return "命令内容:扫描二维码,超时=" + ReadByteSafe(data, 1) + " 秒。"; case FaceCommandId.SnapUploadLargeImage: return "命令内容:抓拍大图,DPI=" + ReadByteSafe(data, 0) + "。"; case FaceCommandId.SetUsbUvcParameters: return DescribeSetUvcData(data); case FaceCommandId.SetFaceLocationDisplay: return "命令内容:设置人脸框显示=" + (ReadByteSafe(data, 0) != 0) + "。"; case FaceCommandId.RgbLevel: return "命令内容:设置 RGB_LEVEL=" + ReadByteSafe(data, 0) + "。"; case FaceCommandId.DuplicateCheck: return data == null || data.Length == 0 ? "命令内容:查询查重状态。" : "命令内容:设置查重=" + (ReadByteSafe(data, 0) == 0) + "。"; case FaceCommandId.DemoMode: return "命令内容:设置 Demo Mode=" + (ReadByteSafe(data, 0) != 0) + "。"; case FaceCommandId.ReadFeature: return "命令内容:读取特征,用户ID=" + ReadUInt16Safe(data, 2) + ",特征类型=" + ReadByteSafe(data, 4) + "。"; case FaceCommandId.WriteFeature: return DescribeWriteFeatureInitData(data); case FaceCommandId.EnrollWithPhoto: case FaceCommandId.EnrollWithPhotoAndId: return DescribePhotoEnrollInitData(command, data); default: return "命令内容:" + DescribeCommand(command) + ",原始 DATA=" + (data == null || data.Length == 0 ? "无" : ToHex(data, 0, data.Length)); } } /// /// 解析 VERIFY 命令参数。 /// private static string DescribeVerifyData(byte[] data) { return "命令内容:人脸验证,立即检测=" + ReadByteSafe(data, 0) + ",超时=" + ReadByteSafe(data, 1) + " 秒,最大识别次数=" + ReadByteSafe(data, 2) + "。"; } /// /// 解析 AUTO_VERIFY 命令参数。 /// private static string DescribeAutoVerifyData(byte[] data) { return "命令内容:自动验证,启用=" + (ReadByteSafe(data, 0) != 0) + ",超时=" + ReadByteSafe(data, 1) + " 秒。"; } /// /// 解析录入命令参数。 /// private static string DescribeEnrollData(FaceCommandId command, byte[] data, bool integrated) { var name = data != null && data.Length > 1 ? FaceProtocolEncoding.DecodeUserName(Slice(data, 1, Math.Min(FaceModuleConstants.UserNameSize, data.Length - 1))) : string.Empty; var directionOffset = 1 + FaceModuleConstants.UserNameSize; var builder = new StringBuilder(); builder.Append("命令内容:" + DescribeCommand(command)); builder.Append(",管理员=" + (ReadByteSafe(data, 0) != 0)); builder.Append(",用户名=" + name); builder.Append(",方向=" + (FaceDirection)ReadByteSafe(data, directionOffset)); builder.Append(",超时=" + ReadByteSafe(data, directionOffset + 1) + " 秒"); if (integrated) { builder.Append(",单帧=" + (ReadByteSafe(data, directionOffset + 2) != 0)); builder.Append(",查重=" + (ReadByteSafe(data, directionOffset + 3) == 0)); } builder.Append("。"); return builder.ToString(); } /// /// 解析设置 UVC 参数。 /// private static string DescribeSetUvcData(byte[] data) { var attributes = ReadByteSafe(data, 3); return "命令内容:设置 UVC,USB=0x" + ReadByteSafe(data, 0).ToString("X2") + ",码率=" + ReadByteSafe(data, 1) + "Mbps,质量=" + ReadByteSafe(data, 2) + ",镜像=" + ((attributes & 0x01) != 0) + ",旋转180=" + ((attributes & 0x02) != 0) + "。"; } /// /// 解析照片注册初始化参数。 /// private static string DescribePhotoEnrollInitData(FaceCommandId command, byte[] data) { var hasId = command == FaceCommandId.EnrollWithPhotoAndId; var fileSizeOffset = hasId ? 4 : 2; var nameLengthOffset = fileSizeOffset + 5; var builder = new StringBuilder(); builder.Append("命令内容:" + DescribeCommand(command)); if (hasId) { builder.Append(",指定用户ID=" + ReadUInt16Safe(data, 2)); } builder.Append(",文件长度=" + ReadUInt32Safe(data, fileSizeOffset)); builder.Append(",BioType=" + ReadByteSafe(data, fileSizeOffset + 4)); builder.Append(",用户名=" + ReadVariableNameSafe(data, nameLengthOffset)); var duplicateOffset = nameLengthOffset + 1 + ReadByteSafe(data, nameLengthOffset); builder.Append(",查重=" + (ReadByteSafe(data, duplicateOffset) == 0) + "。"); return builder.ToString(); } /// /// 解析写特征初始化参数。 /// private static string DescribeWriteFeatureInitData(byte[] data) { return "命令内容:写入特征,特征类型=" + ReadByteSafe(data, 2) + ",用户名=" + ReadVariableNameSafe(data, 3) + "。"; } /// /// 解析 REPLY 业务数据。 /// private static string DescribeReplyPayload(FaceCommandId command, byte[] payload) { switch (command) { case FaceCommandId.GetStatus: return "模组状态=" + (FaceModuleStatus)ReadByteSafe(payload, 0) + "。"; case FaceCommandId.Verify: case FaceCommandId.GetUserInfo: return "用户ID=" + ReadUInt16Safe(payload, 0) + ",用户名=" + FaceProtocolEncoding.DecodeUserName(Slice(payload, 2, Math.Min(FaceModuleConstants.UserNameSize, Math.Max(0, payload.Length - 2)))) + ",管理员=" + (ReadByteSafe(payload, 34) != 0) + "。"; case FaceCommandId.Enroll: case FaceCommandId.EnrollSingle: case FaceCommandId.EnrollIntegrated: case FaceCommandId.EnrollWithPhoto: case FaceCommandId.EnrollWithPhotoAndId: case FaceCommandId.WriteFeature: return "用户ID=" + ReadUInt16Safe(payload, 0) + ",方向/附加值=" + ReadByteSafe(payload, 2) + "。"; case FaceCommandId.GetAllUserId: return DescribeUserIdList(payload, false); case FaceCommandId.GetAllUserId2: return DescribeUserIdList(payload, true); case FaceCommandId.ReadUsbUvcParameters: return DescribeSetUvcData(payload); default: return payload == null || payload.Length == 0 ? "无。" : ToHex(payload, 0, payload.Length); } } /// /// 解析用户 ID 列表。 /// private static string DescribeUserIdList(byte[] payload, bool twoByteCount) { var count = twoByteCount ? ReadUInt16Safe(payload, 0) : ReadByteSafe(payload, 0); var offset = twoByteCount ? 2 : 1; var ids = new List(); for (var i = 0; i < count && offset + 1 < payload.Length; i++, offset += 2) { ids.Add(ReadUInt16Safe(payload, offset).ToString()); } return "用户数量=" + count + ",用户ID列表=" + string.Join(",", ids) + "。"; } /// /// 解析人脸状态通知。 /// private static string DescribeFaceStatePayload(byte[] payload) { if (payload == null || payload.Length < 2) { return "状态数据不足。"; } var state = (FaceStateCode)ReadUInt16Safe(payload, 0); return FaceProtocolEncoding.FaceStateMessage(state); } /// /// 返回命令中文名称。 /// private static string DescribeCommand(FaceCommandId command) { switch (command) { case FaceCommandId.Reset: return "复位模组"; case FaceCommandId.GetStatus: return "获取状态"; case FaceCommandId.Verify: return "人脸验证 / 自动验证"; case FaceCommandId.Enroll: return "交互式人脸录入"; case FaceCommandId.EnrollSingle: return "单帧人脸录入"; case FaceCommandId.DeleteUser: return "删除用户"; case FaceCommandId.DeleteAll: return "删除全部用户"; case FaceCommandId.GetUserInfo: return "查询用户信息"; case FaceCommandId.FaceReset: return "取消当前人脸流程"; case FaceCommandId.GetAllUserId: return "查询所有用户 ID"; case FaceCommandId.GetAllUserId2: return "查询所有用户 ID 扩展"; case FaceCommandId.EnrollIntegrated: return "集成式人脸录入"; case FaceCommandId.GetVersion: return "获取版本"; case FaceCommandId.InitEncryption: return "初始化加密"; case FaceCommandId.SetReleaseEncKey: return "设置 Release Key"; case FaceCommandId.SetDebugEncKey: return "设置 Debug Key"; case FaceCommandId.ScanQrCode: return "扫描二维码"; case FaceCommandId.SnapUploadImage: return "抓拍普通图片"; case FaceCommandId.SnapUploadFaceImage: return "抓拍人脸图片"; case FaceCommandId.EnrollSnapFaceImage: return "抓拍人脸后注册"; case FaceCommandId.SnapUploadLargeImage: return "抓拍大图"; case FaceCommandId.GetSn: return "获取设备 SN"; case FaceCommandId.ReadUsbUvcParameters: return "读取 UVC 参数"; case FaceCommandId.SetUsbUvcParameters: return "设置 UVC 参数"; case FaceCommandId.SetFaceLocationDisplay: return "设置人脸框显示"; case FaceCommandId.RgbLevel: return "设置 RGB_LEVEL"; case FaceCommandId.EnrollWithPhotoAndId: return "指定 ID 照片注册"; case FaceCommandId.UpgradeFirmware: return "固件升级"; case FaceCommandId.EnrollWithPhoto: return "照片注册"; case FaceCommandId.ReadFeature: return "读取特征"; case FaceCommandId.WriteFeature: return "写入特征"; case FaceCommandId.DuplicateCheck: return "查重设置"; case FaceCommandId.DemoMode: return "Demo Mode"; default: return "未知命令 0x" + ((byte)command).ToString("X2"); } } /// /// 输出图片结果。 /// private void LogImageResult(string name, FaceImageResult result) { Log(name + "抓拍完成:字节数=" + result.Bytes.Length + ",用户ID=" + result.UserId + ",贴图是否生成=" + (result.Texture != null)); } /// /// 输出进度。 /// private void LogProgress(int sent, int total) { Log("发送进度:" + sent + "/" + total); } /// /// 解析固定长度 Hex。 /// private static byte[] ParseHex(string text, int expectedLength) { var parts = text.Split(new[] { ' ', '-', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); if (parts.Length != expectedLength) { throw new ArgumentException("Hex 长度必须为 " + expectedLength + " bytes"); } var bytes = new byte[parts.Length]; for (var i = 0; i < parts.Length; i++) { bytes[i] = Convert.ToByte(parts[i], 16); } return bytes; } /// /// 解析任意长度 Hex 字符串。 /// private static byte[] ParseHexFlexible(string text) { if (string.IsNullOrWhiteSpace(text)) { return Array.Empty(); } var normalized = text.Replace("0x", string.Empty).Replace("0X", string.Empty).Replace("TX", " ").Replace("tx", " ").Replace("Rx", " ").Replace("RX", " ").Replace("rx", " "); var parts = normalized.Split(new[] { ' ', '-', ',', ':', ':', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 1 && parts[0].Length > 2 && IsHexText(parts[0])) { var compact = parts[0]; if (compact.Length % 2 != 0) { throw new ArgumentException("连续 Hex 字符数量必须为偶数。"); } var compactBytes = new byte[compact.Length / 2]; for (var i = 0; i < compactBytes.Length; i++) { compactBytes[i] = Convert.ToByte(compact.Substring(i * 2, 2), 16); } return compactBytes; } var result = new List(); for (var i = 0; i < parts.Length; i++) { if (!IsHexByteText(parts[i])) { continue; } result.Add(Convert.ToByte(parts[i], 16)); } if (result.Count == 0) { throw new ArgumentException("没有找到有效 Hex 字节。"); } return result.ToArray(); } /// /// 判断字符串是否全部为 Hex 字符。 /// private static bool IsHexText(string text) { if (string.IsNullOrEmpty(text)) { return false; } for (var i = 0; i < text.Length; i++) { if (!IsHexChar(text[i])) { return false; } } return true; } /// /// 判断字符串是否为一个 Hex byte。 /// private static bool IsHexByteText(string text) { return text != null && text.Length == 2 && IsHexText(text); } /// /// 判断字符是否为 Hex 字符。 /// private static bool IsHexChar(char value) { return value >= '0' && value <= '9' || value >= 'a' && value <= 'f' || value >= 'A' && value <= 'F'; } /// /// 截取 byte 数组。 /// private static byte[] Slice(byte[] bytes, int offset, int length) { if (bytes == null || offset < 0 || length <= 0 || offset >= bytes.Length) { return Array.Empty(); } length = Math.Min(length, bytes.Length - offset); var result = new byte[length]; Buffer.BlockCopy(bytes, offset, result, 0, length); return result; } /// /// 安全读取 1 byte。 /// private static byte ReadByteSafe(byte[] bytes, int offset) { return bytes != null && offset >= 0 && offset < bytes.Length ? bytes[offset] : (byte)0; } /// /// 安全读取 UInt16。 /// private static int ReadUInt16Safe(byte[] bytes, int offset) { return bytes != null && offset >= 0 && offset + 1 < bytes.Length ? (bytes[offset] << 8) | bytes[offset + 1] : 0; } /// /// 安全读取 UInt32。 /// private static uint ReadUInt32Safe(byte[] bytes, int offset) { if (bytes == null || offset < 0 || offset + 3 >= bytes.Length) { return 0; } return (uint)((bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]); } /// /// 安全读取变长 UTF-8 名称。 /// private static string ReadVariableNameSafe(byte[] bytes, int lengthOffset) { var length = ReadByteSafe(bytes, lengthOffset); return FaceProtocolEncoding.DecodeUserName(Slice(bytes, lengthOffset + 1, length)); } /// /// 转换 Hex 字符串。 /// private static string ToHex(byte[] bytes, int offset, int length) { if (bytes == null || length <= 0) { return string.Empty; } var builder = new StringBuilder(); var end = Math.Min(bytes.Length, offset + length); for (var i = offset; i < end; i++) { if (builder.Length > 0) { builder.Append(' '); } builder.Append(bytes[i].ToString("X2")); } return builder.ToString(); } /// /// 输出普通日志。 /// private void Log(string message) { Debug.Log("[FaceModule键盘测试] " + message); } /// /// 输出错误日志。 /// private void LogError(string message) { Debug.LogError("[FaceModule键盘测试] " + message); } } }