985 lines
37 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()
{
m_manager.Close();
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;
if (m_manager.IsAutoVerifyEnabled)
{
await m_manager.StopAutoVerifyAsync();
SetResult("自动验证已关闭");
return;
}
await m_manager.Basic.ResetAsync();
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;
if (m_manager.IsAutoVerifyEnabled)
{
await m_manager.StopAutoVerifyAsync();
SetResult("自动探测已停止");
return;
}
await m_manager.Basic.ResetAsync();
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";
}
}
}