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 void CloseSerial()
{
Manager.Close();
Log("串口已关闭。");
}
///
/// 等待 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
{
var result = await Manager.Verify.AutoVerifyAsync(AutoVerifyEnable, VerifyTimeout);
Log("自动验证完成:成功=" + result.Success + ",用户ID=" + result.UserId);
}
catch (Exception ex) { LogError("自动验证失败:" + ex.Message); }
}
///
/// RESET。
///
public async void ResetModule()
{
try { await Manager.Basic.ResetAsync(); Log("RESET 成功。"); }
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);
}
}
}