974 lines
36 KiB
C#
974 lines
36 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Text;
|
||
using Aisu.SIM1.Face.Core;
|
||
using Aisu.SIM1.Face.Models;
|
||
using Aisu.SIM1.Face.Protocol;
|
||
using Aisu.SIM1.Face.UI.Common;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
|
||
namespace Aisu.SIM1.Face.UI.Pages
|
||
{
|
||
public sealed class FaceConnectPage : FaceModulePageBase
|
||
{
|
||
// Inspector 绑定字段。
|
||
[SerializeField] private InputField m_portInput;
|
||
// Inspector 绑定字段。
|
||
[SerializeField] private InputField m_baudRateInput;
|
||
// Inspector 绑定字段。
|
||
[SerializeField] private Toggle m_mockToggle;
|
||
// Inspector 绑定字段。
|
||
[SerializeField] private Text m_connectionText;
|
||
// Inspector 绑定字段。
|
||
[SerializeField] private Text m_readyText;
|
||
// Inspector 绑定字段。
|
||
[SerializeField] private Text m_moduleStatusText;
|
||
// Inspector 绑定字段。
|
||
[SerializeField] private Text m_licenseText;
|
||
// Inspector 绑定字段。
|
||
[SerializeField] private Text m_versionText;
|
||
// Inspector 绑定字段。
|
||
[SerializeField] private Text m_snText;
|
||
|
||
public override void Build(FaceModuleUiBuilder builder)
|
||
{
|
||
builder.Text(transform, "连接 / 基础状态", 30);
|
||
var form = builder.Section(transform, "通信配置");
|
||
m_portInput = builder.Input(form.transform, "串口号", "COM3");
|
||
m_baudRateInput = builder.Input(form.transform, "波特率", "115200");
|
||
m_mockToggle = builder.Toggle(form.transform, "Editor 使用 Mock 模式", IsEditorMockEnabled());
|
||
var row = builder.Row(form.transform);
|
||
builder.Button(row.transform, "打开连接", OpenConnection);
|
||
builder.Button(row.transform, "关闭连接", CloseConnection);
|
||
builder.Button(row.transform, "等待 READY", WaitReady);
|
||
|
||
var actions = builder.Section(transform, "基础命令");
|
||
var actionRow = builder.Row(actions.transform);
|
||
builder.Button(actionRow.transform, "RESET", ResetModule);
|
||
builder.Button(actionRow.transform, "GET STATUS", GetStatus);
|
||
builder.Button(actionRow.transform, "GET VERSION", GetVersion);
|
||
builder.Button(actionRow.transform, "GET SN", GetSn);
|
||
|
||
var info = builder.Section(transform, "状态");
|
||
m_connectionText = builder.Text(info.transform, "连接状态: 未连接");
|
||
m_readyText = builder.Text(info.transform, "READY: 未 READY");
|
||
m_moduleStatusText = builder.Text(info.transform, "模组状态: Invalid");
|
||
m_licenseText = builder.Text(info.transform, "授权: Mock 模式不需要授权");
|
||
m_versionText = builder.Text(info.transform, "版本: -");
|
||
m_snText = builder.Text(info.transform, "SN: -");
|
||
m_resultText = builder.Text(info.transform, "结果: -");
|
||
}
|
||
|
||
/// <summary>打开通信连接。</summary>
|
||
/// <summary>
|
||
/// 执行 OpenConnection 方法。
|
||
/// </summary>
|
||
public void OpenConnection()
|
||
{
|
||
RunAsync(async () =>
|
||
{
|
||
var useMock = m_mockToggle != null && m_mockToggle.isOn;
|
||
var baud = ReadInt(m_baudRateInput, 115200);
|
||
if (!useMock && !ValidateLicenseBeforeOpen())
|
||
{
|
||
return;
|
||
}
|
||
|
||
m_manager.Configure(useMock, ReadText(m_portInput), baud);
|
||
m_manager.Open();
|
||
|
||
if (useMock)
|
||
{
|
||
await m_manager.WaitReadyAsync();
|
||
RefreshState();
|
||
SetResult("Mock 模组已连接");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
await m_manager.WaitReadyAsync();
|
||
RefreshState();
|
||
SetResult("硬件连接成功,已收到 READY");
|
||
}
|
||
catch
|
||
{
|
||
try
|
||
{
|
||
var status = await m_manager.ProbeReadyByStatusAsync();
|
||
RefreshState();
|
||
m_moduleStatusText.text = "模组状态: " + status;
|
||
SetResult("硬件连接成功,未收到 READY NOTE,但 GET STATUS 探测成功");
|
||
}
|
||
catch
|
||
{
|
||
m_manager.Close();
|
||
RefreshState();
|
||
throw new TimeoutException("未收到硬件 READY,GET STATUS 也无响应,请检查串口、波特率、电源和接线");
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>关闭通信连接。</summary>
|
||
/// <summary>
|
||
/// 执行 CloseConnection 方法。
|
||
/// </summary>
|
||
public void CloseConnection()
|
||
{
|
||
RunAsync(async () =>
|
||
{
|
||
await m_manager.CloseAsync();
|
||
RefreshState();
|
||
SetResult("连接已关闭");
|
||
});
|
||
}
|
||
|
||
/// <summary>等待 READY NOTE。</summary>
|
||
/// <summary>
|
||
/// 执行 WaitReady 方法。
|
||
/// </summary>
|
||
public void WaitReady()
|
||
{
|
||
RunAsync(async () =>
|
||
{
|
||
await m_manager.WaitReadyAsync();
|
||
RefreshState();
|
||
SetResult("模组 READY");
|
||
});
|
||
}
|
||
|
||
/// <summary>复位模组。</summary>
|
||
/// <summary>
|
||
/// 执行 ResetModule 方法。
|
||
/// </summary>
|
||
public void ResetModule()
|
||
{
|
||
RunAsync(async () => { if (RequireConnected()) { await m_manager.Basic.ResetAsync(); SetResult("RESET 成功"); } });
|
||
}
|
||
|
||
/// <summary>获取模组状态。</summary>
|
||
/// <summary>
|
||
/// 执行 GetStatus 方法。
|
||
/// </summary>
|
||
public void GetStatus()
|
||
{
|
||
RunAsync(async () =>
|
||
{
|
||
if (!RequireConnected()) return;
|
||
var status = await m_manager.ProbeReadyByStatusAsync();
|
||
RefreshState();
|
||
m_moduleStatusText.text = "模组状态: " + status;
|
||
SetResult("状态获取成功");
|
||
});
|
||
}
|
||
|
||
/// <summary>获取版本。</summary>
|
||
/// <summary>
|
||
/// 执行 GetVersion 方法。
|
||
/// </summary>
|
||
public void GetVersion()
|
||
{
|
||
RunAsync(async () =>
|
||
{
|
||
if (!RequireConnected()) return;
|
||
m_versionText.text = "版本: " + await m_manager.Basic.GetVersionAsync();
|
||
SetResult("版本获取成功");
|
||
});
|
||
}
|
||
|
||
/// <summary>获取 SN。</summary>
|
||
/// <summary>
|
||
/// 执行 GetSn 方法。
|
||
/// </summary>
|
||
public void GetSn()
|
||
{
|
||
RunAsync(async () =>
|
||
{
|
||
if (!RequireConnected()) return;
|
||
m_snText.text = "SN: " + await m_manager.Basic.GetSnAsync();
|
||
SetResult("SN 获取成功");
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 RefreshState 方法。
|
||
/// </summary>
|
||
private void RefreshState()
|
||
{
|
||
var state = m_manager.IsReady ? "硬件已连接" : (m_manager.IsOpen ? "端口已打开,未 READY" : "未连接");
|
||
m_connectionText.text = "连接状态: " + state;
|
||
m_readyText.text = "READY: " + (m_manager.IsReady ? "READY" : "未 READY");
|
||
RefreshLicenseState(m_mockToggle != null && m_mockToggle.isOn);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 真实串口连接前检查授权。
|
||
/// </summary>
|
||
private bool ValidateLicenseBeforeOpen()
|
||
{
|
||
var path = FaceLicenseManager.GetDefaultLicensePath();
|
||
var status = FaceLicenseManager.LoadAndVerify(path);
|
||
if (status == FaceLicenseStatus.Valid)
|
||
{
|
||
RefreshLicenseState(false);
|
||
return true;
|
||
}
|
||
|
||
var message = "授权无效,无法打开真实串口。路径: " + path + ",原因: " + FaceLicenseManager.LastError;
|
||
if (m_licenseText != null)
|
||
{
|
||
m_licenseText.text = "授权: " + message;
|
||
}
|
||
|
||
SetResult(message);
|
||
return false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刷新授权状态显示。
|
||
/// </summary>
|
||
private void RefreshLicenseState(bool useMock)
|
||
{
|
||
if (m_licenseText == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (useMock)
|
||
{
|
||
m_licenseText.text = "授权: Mock 模式不需要授权";
|
||
return;
|
||
}
|
||
|
||
if (FaceLicenseManager.Status == FaceLicenseStatus.Valid && FaceLicenseManager.Payload != null)
|
||
{
|
||
m_licenseText.text = "授权: 有效,剩余 " + FaceLicenseVerifier.GetRemainingDays(FaceLicenseManager.Payload) + " 天";
|
||
return;
|
||
}
|
||
|
||
m_licenseText.text = "授权: 未验证,文件路径 " + FaceLicenseManager.GetDefaultLicensePath();
|
||
}
|
||
|
||
private bool IsEditorMockEnabled()
|
||
{
|
||
if (m_manager == null)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
return m_manager.Settings != null ? m_manager.Settings.UseMockInEditor : m_manager.Config.UseMockInEditor;
|
||
}
|
||
}
|
||
|
||
public sealed class FaceProtocolDebugPage : FaceModulePageBase
|
||
{
|
||
// Inspector 绑定字段。
|
||
[SerializeField] private InputField m_hexInput;
|
||
// Inspector 绑定字段。
|
||
[SerializeField] private Text m_parseText;
|
||
private readonly FacePacketParser m_parser = new FacePacketParser();
|
||
private FaceModuleManager m_boundManager; // 已订阅事件的 Manager。
|
||
|
||
public override void Build(FaceModuleUiBuilder builder)
|
||
{
|
||
builder.Text(transform, "协议调试", 30);
|
||
m_hexInput = builder.Input(transform, "EF AA 10 00 00 10", "EF AA 10 00 00 10");
|
||
var row = builder.Row(transform);
|
||
builder.Button(row.transform, "发送 Hex", SendHex);
|
||
builder.Button(row.transform, "清空输入", () => m_hexInput.text = string.Empty);
|
||
m_parseText = builder.Text(transform, "解析结果: -", 20, TextAnchor.UpperLeft);
|
||
m_resultText = builder.Text(transform, "结果: -");
|
||
}
|
||
|
||
public override void Init(FaceModuleManager manager, FaceModuleLogPanel logPanel, FaceModuleLoadingView loadingView)
|
||
{
|
||
UnbindManagerEvents();
|
||
base.Init(manager, logPanel, loadingView);
|
||
m_boundManager = m_manager;
|
||
m_manager.OnRawLog += ParseRaw;
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
UnbindManagerEvents();
|
||
}
|
||
|
||
/// <summary>发送原始 Hex 数据。</summary>
|
||
/// <summary>
|
||
/// 执行 SendHex 方法。
|
||
/// </summary>
|
||
public void SendHex()
|
||
{
|
||
try
|
||
{
|
||
if (!RequireConnected()) return;
|
||
var bytes = ParseHex(ReadText(m_hexInput));
|
||
m_manager.SendRawBytes(bytes);
|
||
SetResult("Hex 已发送");
|
||
}
|
||
catch (Exception ex) { SetError(ex); }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 ParseRaw 方法。
|
||
/// </summary>
|
||
private void ParseRaw(string line)
|
||
{
|
||
var index = line.IndexOf(' ');
|
||
if (index < 0) return;
|
||
try
|
||
{
|
||
var packets = m_parser.Push(ParseHex(line.Substring(index + 1)));
|
||
foreach (var packet in packets)
|
||
{
|
||
m_parseText.text = $"MsgID=0x{packet.MsgId:X2}\nSize={packet.Size}\nData={BitConverter.ToString(packet.Data)}\nChecksum=0x{packet.Checksum:X2}\nValid={packet.IsValid}";
|
||
}
|
||
}
|
||
catch { }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 ParseHex 方法。
|
||
/// </summary>
|
||
private static byte[] ParseHex(string text)
|
||
{
|
||
var parts = text.Split(new[] { ' ', '\t', '\r', '\n', '-' }, StringSplitOptions.RemoveEmptyEntries);
|
||
var bytes = new byte[parts.Length];
|
||
for (var i = 0; i < parts.Length; i++)
|
||
{
|
||
bytes[i] = Convert.ToByte(parts[i], 16);
|
||
}
|
||
|
||
return bytes;
|
||
}
|
||
|
||
private void UnbindManagerEvents()
|
||
{
|
||
if (m_boundManager != null)
|
||
{
|
||
m_boundManager.OnRawLog -= ParseRaw;
|
||
m_boundManager = null;
|
||
}
|
||
}
|
||
}
|
||
|
||
public sealed class FaceVerifyPage : FaceModulePageBase
|
||
{
|
||
// 内部状态字段。
|
||
private InputField m_timeoutInput;
|
||
// 内部状态字段。
|
||
private InputField m_maxTimesInput;
|
||
// 内部状态字段。
|
||
private Toggle m_autoToggle;
|
||
// 内部状态字段。
|
||
private Text m_faceStateText;
|
||
// 内部状态字段。
|
||
private Text m_poseText;
|
||
// 内部状态字段。
|
||
private Text m_boxText;
|
||
// 内部状态字段。
|
||
private Text m_userIdText;
|
||
// 内部状态字段。
|
||
private Text m_userNameText;
|
||
// 内部状态字段。
|
||
private Text m_adminText;
|
||
// 内部状态字段。
|
||
private Text m_unlockText;
|
||
private FaceModuleManager m_boundManager; // 已订阅事件的 Manager。
|
||
|
||
public override void Build(FaceModuleUiBuilder builder)
|
||
{
|
||
builder.Text(transform, "人脸验证", 30);
|
||
m_timeoutInput = builder.Input(transform, "超时时间", "10");
|
||
m_maxTimesInput = builder.Input(transform, "最大识别次数", "30");
|
||
m_autoToggle = builder.Toggle(transform, "自动探测验证", false);
|
||
var row = builder.Row(transform);
|
||
builder.Button(row.transform, "开始验证", StartVerify);
|
||
builder.Button(row.transform, "停止 / RESET", StopVerify);
|
||
m_faceStateText = builder.Text(transform, "人脸状态: -");
|
||
m_poseText = builder.Text(transform, "Yaw/Pitch/Roll: -");
|
||
m_boxText = builder.Text(transform, "人脸框: -");
|
||
m_userIdText = builder.Text(transform, "UserID: -");
|
||
m_userNameText = builder.Text(transform, "UserName: -");
|
||
m_adminText = builder.Text(transform, "Admin: -");
|
||
m_unlockText = builder.Text(transform, "UnlockStatus: -");
|
||
m_resultText = builder.Text(transform, "结果: -");
|
||
}
|
||
|
||
public override void Init(FaceModuleManager manager, FaceModuleLogPanel logPanel, FaceModuleLoadingView loadingView)
|
||
{
|
||
UnbindManagerEvents();
|
||
base.Init(manager, logPanel, loadingView);
|
||
m_boundManager = m_manager;
|
||
m_manager.OnFaceStateUpdated += OnFaceState;
|
||
m_manager.OnAutoVerifyResult += OnAutoVerifyResult;
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
UnbindManagerEvents();
|
||
}
|
||
|
||
/// <summary>开始验证或自动验证。</summary>
|
||
/// <summary>
|
||
/// 执行 StartVerify 方法。
|
||
/// </summary>
|
||
public void StartVerify()
|
||
{
|
||
RunAsync(async () =>
|
||
{
|
||
if (!RequireReady()) return;
|
||
if (m_autoToggle.isOn)
|
||
{
|
||
await m_manager.StartAutoVerifyAsync(ReadByte(m_timeoutInput, 10));
|
||
SetResult("自动验证已开启");
|
||
return;
|
||
}
|
||
|
||
var result = await m_manager.Verify.VerifyAsync(ReadByte(m_timeoutInput, 10), ReadByte(m_maxTimesInput, 30));
|
||
ShowResult(result);
|
||
});
|
||
}
|
||
|
||
/// <summary>停止验证。</summary>
|
||
/// <summary>
|
||
/// 执行 StopVerify 方法。
|
||
/// </summary>
|
||
public void StopVerify()
|
||
{
|
||
RunAsync(async () =>
|
||
{
|
||
if (!RequireConnected()) return;
|
||
await m_manager.StopFaceOperationAsync();
|
||
SetResult("已停止验证");
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 OnFaceState 方法。
|
||
/// </summary>
|
||
private void OnFaceState(FaceFaceStateNote state)
|
||
{
|
||
m_faceStateText.text = "人脸状态: " + FaceProtocolEncoding.FaceStateMessage(state.State);
|
||
m_poseText.text = $"Yaw/Pitch/Roll: {state.Yaw}/{state.Pitch}/{state.Roll}";
|
||
m_boxText.text = $"人脸框: L{state.Left} T{state.Top} R{state.Right} B{state.Bottom}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 ShowResult 方法。
|
||
/// </summary>
|
||
private void ShowResult(FaceVerifyResult result)
|
||
{
|
||
m_userIdText.text = "UserID: " + result.UserId;
|
||
m_userNameText.text = "UserName: " + result.UserName;
|
||
m_adminText.text = "Admin: " + result.IsAdmin;
|
||
m_unlockText.text = "UnlockStatus: " + result.UnlockStatus;
|
||
SetResult(result.Success ? "验证成功" : FaceProtocolEncoding.ResultMessage(result.ResultCode));
|
||
}
|
||
|
||
private void OnAutoVerifyResult(FaceAutoVerifyResult result)
|
||
{
|
||
ShowResult(result.VerifyResult);
|
||
}
|
||
|
||
private void UnbindManagerEvents()
|
||
{
|
||
if (m_boundManager != null)
|
||
{
|
||
m_boundManager.OnFaceStateUpdated -= OnFaceState;
|
||
m_boundManager.OnAutoVerifyResult -= OnAutoVerifyResult;
|
||
m_boundManager = null;
|
||
}
|
||
}
|
||
}
|
||
|
||
public sealed class FaceEnrollPage : FaceModulePageBase
|
||
{
|
||
// 内部状态字段。
|
||
private InputField m_nameInput;
|
||
// 内部状态字段。
|
||
private Toggle m_adminToggle;
|
||
// 内部状态字段。
|
||
private Dropdown m_modeDropdown;
|
||
// 内部状态字段。
|
||
private Dropdown m_directionDropdown;
|
||
// 内部状态字段。
|
||
private Dropdown m_enrollTypeDropdown;
|
||
// 内部状态字段。
|
||
private Toggle m_duplicateToggle;
|
||
// 内部状态字段。
|
||
private InputField m_timeoutInput;
|
||
// 内部状态字段。
|
||
private Text m_faceStateText;
|
||
// 内部状态字段。
|
||
private Text m_poseText;
|
||
// 内部状态字段。
|
||
private Text m_directionText;
|
||
// 内部状态字段。
|
||
private Text m_userIdText;
|
||
private FaceModuleManager m_boundManager; // 已订阅事件的 Manager。
|
||
private Action<FaceFaceStateNote> m_faceStateHandler; // 人脸状态事件回调。
|
||
|
||
public override void Build(FaceModuleUiBuilder builder)
|
||
{
|
||
builder.Text(transform, "人脸录入", 30);
|
||
m_nameInput = builder.Input(transform, "用户姓名", "User");
|
||
m_adminToggle = builder.Toggle(transform, "管理员", false);
|
||
m_modeDropdown = builder.Dropdown(transform, new[] { "交互录入 ENROLL", "单帧录入 ENROLL_SINGLE", "集成录入 ENROLL_ITG" });
|
||
m_directionDropdown = builder.Dropdown(transform, new[] { "正脸", "朝上", "朝下", "朝左", "朝右" });
|
||
m_enrollTypeDropdown = builder.Dropdown(transform, new[] { "交互", "单帧" });
|
||
m_duplicateToggle = builder.Toggle(transform, "允许重复录入", false);
|
||
m_timeoutInput = builder.Input(transform, "超时时间", "20");
|
||
var row = builder.Row(transform);
|
||
builder.Button(row.transform, "开始录入", StartEnroll);
|
||
builder.Button(row.transform, "取消录入", CancelEnroll);
|
||
m_faceStateText = builder.Text(transform, "人脸状态: -");
|
||
m_poseText = builder.Text(transform, "姿态: -");
|
||
m_directionText = builder.Text(transform, "已录入方向: -");
|
||
m_userIdText = builder.Text(transform, "最终 UserID: -");
|
||
m_resultText = builder.Text(transform, "ResultCode: -");
|
||
}
|
||
|
||
public override void Init(FaceModuleManager manager, FaceModuleLogPanel logPanel, FaceModuleLoadingView loadingView)
|
||
{
|
||
UnbindManagerEvents();
|
||
base.Init(manager, logPanel, loadingView);
|
||
m_boundManager = m_manager;
|
||
m_faceStateHandler = state =>
|
||
{
|
||
m_faceStateText.text = FaceProtocolEncoding.FaceStateMessage(state.State);
|
||
m_poseText.text = $"Yaw/Pitch/Roll: {state.Yaw}/{state.Pitch}/{state.Roll}";
|
||
};
|
||
m_manager.OnFaceStateUpdated += m_faceStateHandler;
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
UnbindManagerEvents();
|
||
}
|
||
|
||
/// <summary>开始录入。</summary>
|
||
/// <summary>
|
||
/// 执行 StartEnroll 方法。
|
||
/// </summary>
|
||
public void StartEnroll()
|
||
{
|
||
RunAsync(async () =>
|
||
{
|
||
if (!RequireReady()) return;
|
||
if (FaceProtocolEncoding.GetUserNameByteCount(ReadText(m_nameInput)) > FaceModuleConstants.UserNameSize)
|
||
{
|
||
SetResult("用户名 UTF-8 编码超过 32 bytes");
|
||
return;
|
||
}
|
||
|
||
await m_manager.EnsureExclusiveOperationReadyAsync("人脸录入");
|
||
|
||
FaceEnrollResult result;
|
||
if (m_modeDropdown.value == 1)
|
||
{
|
||
result = await m_manager.Enrollment.EnrollSingleAsync(m_adminToggle.isOn, ReadText(m_nameInput), ReadByte(m_timeoutInput, 20));
|
||
}
|
||
else if (m_modeDropdown.value == 2)
|
||
{
|
||
result = await m_manager.Enrollment.EnrollIntegratedAsync(m_adminToggle.isOn, ReadText(m_nameInput), Direction(), ReadByte(m_timeoutInput, 20), m_enrollTypeDropdown.value == 1, !m_duplicateToggle.isOn);
|
||
}
|
||
else
|
||
{
|
||
result = await m_manager.Enrollment.EnrollAsync(m_adminToggle.isOn, ReadText(m_nameInput), Direction(), ReadByte(m_timeoutInput, 20));
|
||
}
|
||
|
||
m_userIdText.text = "最终 UserID: " + result.UserId;
|
||
m_directionText.text = "已录入方向: " + DirectionText(result.Direction);
|
||
if (result.Success)
|
||
{
|
||
await ConfirmEnrollResult(result.UserId);
|
||
}
|
||
else
|
||
{
|
||
SetResult(FaceProtocolEncoding.ResultMessage(result.ResultCode));
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 录入成功后读取用户信息,确认模组已写入用户表。
|
||
/// </summary>
|
||
private async System.Threading.Tasks.Task ConfirmEnrollResult(int userId)
|
||
{
|
||
try
|
||
{
|
||
var info = await m_manager.Users.GetUserInfoAsync(userId);
|
||
SetResult("录入成功,已确认用户: ID=" + info.UserId + " 姓名=" + info.UserName + " 管理员=" + info.IsAdmin);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
SetResult("录入成功,但查询用户确认失败: " + ex.Message);
|
||
}
|
||
}
|
||
|
||
/// <summary>取消录入。</summary>
|
||
/// <summary>
|
||
/// 执行 CancelEnroll 方法。
|
||
/// </summary>
|
||
public void CancelEnroll()
|
||
{
|
||
RunAsync(async () => { if (RequireConnected()) { await m_manager.EnsureExclusiveOperationReadyAsync("取消录入"); await m_manager.Basic.FaceResetAsync(); SetResult("已取消录入"); } });
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 Direction 方法。
|
||
/// </summary>
|
||
private FaceDirection Direction()
|
||
{
|
||
switch (m_directionDropdown.value)
|
||
{
|
||
case 1: return FaceDirection.Up;
|
||
case 2: return FaceDirection.Down;
|
||
case 3: return FaceDirection.Left;
|
||
case 4: return FaceDirection.Right;
|
||
default: return FaceDirection.Middle;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 DirectionText 方法。
|
||
/// </summary>
|
||
private string DirectionText(FaceDirection direction)
|
||
{
|
||
return direction.ToString();
|
||
}
|
||
|
||
private void UnbindManagerEvents()
|
||
{
|
||
if (m_boundManager != null && m_faceStateHandler != null)
|
||
{
|
||
m_boundManager.OnFaceStateUpdated -= m_faceStateHandler;
|
||
}
|
||
|
||
m_boundManager = null;
|
||
m_faceStateHandler = null;
|
||
}
|
||
}
|
||
|
||
public sealed class FaceUserManagePage : FaceModulePageBase
|
||
{
|
||
// 内部状态字段。
|
||
private InputField m_userIdInput;
|
||
// 内部状态字段。
|
||
private Text m_listText;
|
||
// 内部状态字段。
|
||
private Text m_detailText;
|
||
// 内部状态字段。
|
||
private FaceModuleConfirmDialog m_confirmDialog;
|
||
|
||
public override void Build(FaceModuleUiBuilder builder)
|
||
{
|
||
builder.Text(transform, "用户管理", 30);
|
||
m_userIdInput = builder.Input(transform, "UserID", "1");
|
||
var row = builder.Row(transform);
|
||
builder.Button(row.transform, "查询用户", QueryUser);
|
||
builder.Button(row.transform, "删除用户", DeleteUser);
|
||
builder.Button(row.transform, "删除全部", DeleteAll);
|
||
var row2 = builder.Row(transform);
|
||
builder.Button(row2.transform, "获取所有 ID", QueryAll);
|
||
builder.Button(row2.transform, "获取所有 ID2", QueryAll2);
|
||
var scroll = builder.ScrollView(transform, out m_listText);
|
||
scroll.GetComponent<LayoutElement>().preferredHeight = 240;
|
||
m_detailText = builder.Text(transform, "用户详情: -");
|
||
m_resultText = builder.Text(transform, "结果: -");
|
||
}
|
||
|
||
public override void Init(FaceModuleManager manager, FaceModuleLogPanel logPanel, FaceModuleLoadingView loadingView)
|
||
{
|
||
base.Init(manager, logPanel, loadingView);
|
||
m_confirmDialog = FindObjectOfType<FaceModuleConfirmDialog>(true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 QueryUser 方法。
|
||
/// </summary>
|
||
public void QueryUser()
|
||
{
|
||
RunAsync(async () =>
|
||
{
|
||
if (!RequireReady()) return;
|
||
await m_manager.EnsureExclusiveOperationReadyAsync("查询用户");
|
||
var info = await m_manager.Users.GetUserInfoAsync(ReadInt(m_userIdInput, 0));
|
||
m_detailText.text = $"用户详情: ID={info.UserId} Name={info.UserName} Admin={info.IsAdmin}";
|
||
SetResult("查询成功");
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 DeleteUser 方法。
|
||
/// </summary>
|
||
public void DeleteUser()
|
||
{
|
||
RunAsync(async () =>
|
||
{
|
||
if (!RequireReady()) return;
|
||
await m_manager.EnsureExclusiveOperationReadyAsync("删除用户");
|
||
await m_manager.Users.DeleteUserAsync(ReadInt(m_userIdInput, 0));
|
||
SetResult("删除成功");
|
||
await RefreshIds(false);
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 DeleteAll 方法。
|
||
/// </summary>
|
||
public void DeleteAll()
|
||
{
|
||
if (m_confirmDialog != null)
|
||
{
|
||
m_confirmDialog.Show("确定删除全部用户?该操作不可撤销。", DeleteAllConfirmed);
|
||
return;
|
||
}
|
||
|
||
DeleteAllConfirmed();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 DeleteAllConfirmed 方法。
|
||
/// </summary>
|
||
private void DeleteAllConfirmed()
|
||
{
|
||
RunAsync(async () =>
|
||
{
|
||
if (!RequireReady()) return;
|
||
await m_manager.EnsureExclusiveOperationReadyAsync("删除全部用户");
|
||
await m_manager.Users.DeleteAllAsync();
|
||
SetResult("全部用户已删除");
|
||
await RefreshIds(false);
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 QueryAll 方法。
|
||
/// </summary>
|
||
public void QueryAll()
|
||
{
|
||
RunAsync(async () => { if (RequireReady()) await RefreshIds(false); });
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 QueryAll2 方法。
|
||
/// </summary>
|
||
public void QueryAll2()
|
||
{
|
||
RunAsync(async () => { if (RequireReady()) await RefreshIds(true); });
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 RefreshIds 方法。
|
||
/// </summary>
|
||
private async System.Threading.Tasks.Task RefreshIds(bool useV2)
|
||
{
|
||
await m_manager.EnsureExclusiveOperationReadyAsync("刷新用户列表");
|
||
var ids = useV2 ? await m_manager.Users.GetAllUserIds2Async() : await m_manager.Users.GetAllUserIdsAsync();
|
||
m_listText.text = string.Join("\n", ids.ConvertAll(id => "UserID: " + id));
|
||
SetResult("用户列表已刷新");
|
||
}
|
||
}
|
||
|
||
public sealed class FaceQrCodePage : FaceModulePageBase
|
||
{
|
||
// 内部状态字段。
|
||
private InputField m_timeoutInput;
|
||
// 内部状态字段。
|
||
private Text m_qrText;
|
||
private FaceModuleManager m_boundManager; // 已订阅事件的 Manager。
|
||
|
||
public override void Build(FaceModuleUiBuilder builder)
|
||
{
|
||
builder.Text(transform, "二维码", 30);
|
||
m_timeoutInput = builder.Input(transform, "扫码超时时间", "10");
|
||
var row = builder.Row(transform);
|
||
builder.Button(row.transform, "开始扫码", Scan);
|
||
builder.Button(row.transform, "自动探测二维码", AutoScan);
|
||
builder.Button(row.transform, "停止", Stop);
|
||
builder.Button(row.transform, "复制内容", Copy);
|
||
m_qrText = builder.Text(transform, "二维码内容: -", 22, TextAnchor.UpperLeft);
|
||
m_resultText = builder.Text(transform, "扫码状态: -");
|
||
}
|
||
|
||
public override void Init(FaceModuleManager manager, FaceModuleLogPanel logPanel, FaceModuleLoadingView loadingView)
|
||
{
|
||
UnbindManagerEvents();
|
||
base.Init(manager, logPanel, loadingView);
|
||
m_boundManager = m_manager;
|
||
m_manager.OnQrCodeReceived += OnQrCodeReceived;
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
UnbindManagerEvents();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 Scan 方法。
|
||
/// </summary>
|
||
public void Scan()
|
||
{
|
||
RunAsync(async () =>
|
||
{
|
||
if (!RequireReady()) return;
|
||
await m_manager.EnsureExclusiveOperationReadyAsync("扫码");
|
||
var qr = await m_manager.QrCode.ScanQrCodeAsync(ReadByte(m_timeoutInput, 10));
|
||
m_qrText.text = "二维码内容: " + qr;
|
||
SetResult(string.IsNullOrEmpty(qr) ? "二维码为空" : "扫码成功");
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 AutoScan 方法。
|
||
/// </summary>
|
||
public void AutoScan()
|
||
{
|
||
RunAsync(async () => { if (RequireReady()) { await m_manager.StartAutoVerifyAsync(ReadByte(m_timeoutInput, 10)); SetResult("自动探测已启动"); } });
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 Stop 方法。
|
||
/// </summary>
|
||
public void Stop()
|
||
{
|
||
RunAsync(async () =>
|
||
{
|
||
if (!RequireConnected()) return;
|
||
await m_manager.StopFaceOperationAsync();
|
||
SetResult("已停止");
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 Copy 方法。
|
||
/// </summary>
|
||
public void Copy()
|
||
{
|
||
GUIUtility.systemCopyBuffer = m_qrText.text.Replace("二维码内容: ", string.Empty);
|
||
SetResult("已复制到剪贴板");
|
||
}
|
||
|
||
private void OnQrCodeReceived(string qr)
|
||
{
|
||
m_qrText.text = "二维码内容: " + qr;
|
||
}
|
||
|
||
private void UnbindManagerEvents()
|
||
{
|
||
if (m_boundManager != null)
|
||
{
|
||
m_boundManager.OnQrCodeReceived -= OnQrCodeReceived;
|
||
m_boundManager = null;
|
||
}
|
||
}
|
||
}
|
||
|
||
public sealed class FaceImagePage : FaceModulePageBase
|
||
{
|
||
// 内部状态字段。
|
||
private Dropdown m_dpiDropdown;
|
||
// 内部状态字段。
|
||
private Slider m_progress;
|
||
// 内部状态字段。
|
||
private Text m_sizeText;
|
||
// 内部状态字段。
|
||
private RawImage m_preview;
|
||
// 内部状态字段。
|
||
private Text m_userIdText;
|
||
// 内部状态字段。
|
||
private byte[] m_lastBytes;
|
||
// 内部状态字段。
|
||
private string m_lastType;
|
||
|
||
public override void Build(FaceModuleUiBuilder builder)
|
||
{
|
||
builder.Text(transform, "图片抓拍", 30);
|
||
m_dpiDropdown = builder.Dropdown(transform, new[] { "480x640", "600x800", "1200x1600" });
|
||
var row = builder.Row(transform);
|
||
builder.Button(row.transform, "抓拍普通图片", () => Snap("Normal"));
|
||
builder.Button(row.transform, "抓拍人脸图片", () => Snap("Face"));
|
||
builder.Button(row.transform, "抓拍大图", () => Snap("Large"));
|
||
builder.Button(row.transform, "保存图片", SaveImage);
|
||
builder.Button(row.transform, "清空图片", ClearImage);
|
||
m_progress = builder.Slider(transform);
|
||
m_sizeText = builder.Text(transform, "图片大小: -");
|
||
m_userIdText = builder.Text(transform, "本地匹配 UserID: -");
|
||
var previewGo = FaceModuleUiBuilder.Create("Preview", transform);
|
||
previewGo.AddComponent<LayoutElement>().preferredHeight = 360;
|
||
previewGo.AddComponent<Image>().color = Color.white;
|
||
m_preview = FaceModuleUiBuilder.Create("RawImage", previewGo.transform).AddComponent<RawImage>();
|
||
FaceModuleUiBuilder.Stretch(m_preview.rectTransform);
|
||
m_resultText = builder.Text(transform, "结果: -");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 Snap 方法。
|
||
/// </summary>
|
||
public void Snap(string type)
|
||
{
|
||
RunAsync(async () =>
|
||
{
|
||
if (!RequireReady()) return;
|
||
await m_manager.EnsureExclusiveOperationReadyAsync("图片抓拍");
|
||
FaceImageResult result;
|
||
if (type == "Face") result = await m_manager.Images.SnapUploadFaceImageAsync();
|
||
else if (type == "Large") result = await m_manager.Images.SnapUploadLargeImageAsync((ImageDpi)m_dpiDropdown.value);
|
||
else result = await m_manager.Images.SnapUploadImageAsync();
|
||
m_lastBytes = result.Bytes;
|
||
m_lastType = type;
|
||
m_preview.texture = result.Texture;
|
||
m_sizeText.text = "图片大小: " + (m_lastBytes == null ? 0 : m_lastBytes.Length) + " bytes";
|
||
m_userIdText.text = "本地匹配 UserID: " + result.UserId;
|
||
m_progress.value = 1;
|
||
SetResult(result.Bytes.Length > 0 ? "图片接收完成" : "抓拍命令已完成,未收到图片数据");
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 SaveImage 方法。
|
||
/// </summary>
|
||
public void SaveImage()
|
||
{
|
||
if (m_lastBytes == null || m_lastBytes.Length == 0)
|
||
{
|
||
SetResult("没有可保存的图片");
|
||
return;
|
||
}
|
||
|
||
var dir = Path.Combine(Application.persistentDataPath, "FaceModuleImages");
|
||
Directory.CreateDirectory(dir);
|
||
File.WriteAllBytes(Path.Combine(dir, DateTime.Now.ToString("yyyyMMdd_HHmmss_") + m_lastType + ImageExtension(m_lastBytes)), m_lastBytes);
|
||
SetResult("图片已保存: " + dir);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行 ClearImage 方法。
|
||
/// </summary>
|
||
public void ClearImage()
|
||
{
|
||
m_lastBytes = null;
|
||
m_preview.texture = null;
|
||
m_sizeText.text = "图片大小: -";
|
||
m_userIdText.text = "本地匹配 UserID: -";
|
||
m_progress.value = 0;
|
||
SetResult("已清空图片");
|
||
}
|
||
|
||
private static string ImageExtension(byte[] bytes)
|
||
{
|
||
if (bytes != null && bytes.Length >= 4 && bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47)
|
||
{
|
||
return ".png";
|
||
}
|
||
|
||
return ".jpg";
|
||
}
|
||
}
|
||
}
|