using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Threading.Tasks;
using Aisu.SIM1.Face.Core;
using Aisu.SIM1.Face.Models;
using Aisu.SIM1.Face.Protocol;
using UnityEngine;
using UnityEngine.UI;
namespace Aisu.SIM1.Face.Samples.FaceDetectionDemo
{
///
/// 人脸检测 Demo 主流程控制器。
///
public sealed class FaceDetectionDemoController : MonoBehaviour
{
[SerializeField] private Dropdown m_cameraDropdown; // 摄像头下拉框。
[SerializeField] private Dropdown m_serialDropdown; // 串口下拉框。
[SerializeField] private InputField m_serialInput; // 串口输入兜底。
[SerializeField] private InputField m_baudRateInput; // 波特率输入。
[SerializeField] private InputField m_userNameInput; // 用户名输入。
[SerializeField] private Toggle m_adminToggle; // 管理员开关。
[SerializeField] private Text m_statusText; // 状态文本。
[SerializeField] private Text m_resultText; // 结果文本。
private FaceModuleManager m_manager; // SDK Manager。
private FaceDetectionDemoCamera m_camera; // 摄像头预览。
private FaceDetectionDemoLogger m_logger; // 日志。
private FaceDetectionDemoUserList m_userList; // 用户列表。
///
/// 绑定 MVP UI。
///
public void Bind(
FaceModuleManager manager,
FaceDetectionDemoCamera camera,
FaceDetectionDemoLogger logger,
FaceDetectionDemoUserList userList,
Dropdown cameraDropdown,
Dropdown serialDropdown,
InputField serialInput,
InputField baudRateInput,
InputField userNameInput,
Toggle adminToggle,
Text statusText,
Text resultText)
{
m_manager = manager;
m_camera = camera;
m_logger = logger;
m_userList = userList;
m_cameraDropdown = cameraDropdown;
m_serialDropdown = serialDropdown;
m_serialInput = serialInput;
m_baudRateInput = baudRateInput;
m_userNameInput = userNameInput;
m_adminToggle = adminToggle;
m_statusText = statusText;
m_resultText = resultText;
if (m_manager != null)
{
m_manager.OnError += OnManagerError;
m_manager.OnReady += OnManagerReady;
m_manager.OnFaceStateUpdated += OnFaceStateUpdated;
m_manager.OnAutoVerifyResult += OnAutoVerifyResult;
}
if (m_serialDropdown != null)
{
m_serialDropdown.onValueChanged.AddListener(OnSerialDropdownChanged);
}
RefreshCameras();
RefreshSerialPorts();
RefreshStatus();
}
///
/// 刷新摄像头列表。
///
public void RefreshCameras()
{
if (m_camera == null)
{
SetDropdownOptions(m_cameraDropdown, Array.Empty(), "未发现摄像头");
LogInfo("摄像头数量: 0");
return;
}
LogInfo("正在刷新摄像头");
m_camera.GetDeviceNamesWhenAuthorized(names =>
{
SetDropdownOptions(m_cameraDropdown, names, "未发现摄像头");
LogInfo("摄像头数量: " + names.Length);
if (names.Length == 0)
{
LogInfo("Android 未枚举到摄像头时,请确认 USB 摄像头是否被系统 Camera API 识别;部分 UVC 设备需要原生插件读取");
}
});
}
///
/// 打开摄像头。
///
public void OpenCamera()
{
if (m_cameraDropdown == null || m_cameraDropdown.options == null || m_cameraDropdown.options.Count == 0)
{
LogError("未发现可打开的摄像头");
return;
}
var deviceName = SelectedDropdownText(m_cameraDropdown);
if (deviceName == "未发现摄像头")
{
LogError("未发现可打开的摄像头");
return;
}
m_camera?.Open(m_cameraDropdown.value);
RefreshStatus();
}
///
/// 销毁测试页面时释放连接和事件。
///
private void OnDestroy()
{
if (m_manager != null)
{
m_manager.OnError -= OnManagerError;
m_manager.OnReady -= OnManagerReady;
m_manager.OnFaceStateUpdated -= OnFaceStateUpdated;
m_manager.OnAutoVerifyResult -= OnAutoVerifyResult;
m_manager.Close();
}
if (m_serialDropdown != null)
{
m_serialDropdown.onValueChanged.RemoveListener(OnSerialDropdownChanged);
}
m_camera?.Close();
}
///
/// 关闭摄像头。
///
public void CloseCamera()
{
m_camera?.Close();
LogInfo("摄像头已关闭");
RefreshStatus();
}
///
/// 刷新串口列表。
///
public void RefreshSerialPorts()
{
string[] ports;
try
{
ports = SerialPort.GetPortNames();
}
catch (Exception ex)
{
ports = Array.Empty();
LogError("串口枚举失败: " + ex.Message);
}
SetDropdownOptions(m_serialDropdown, ports, "手动输入串口");
if (ports.Length > 0 && m_serialInput != null)
{
m_serialInput.text = ports[0];
}
LogInfo("串口数量: " + ports.Length);
}
///
/// 连接模组。
///
public void ConnectModule()
{
RunAsync(async () =>
{
if (m_manager == null)
{
LogError("FaceModuleManager 不存在");
return;
}
var portName = ReadPortName();
if (string.IsNullOrWhiteSpace(portName))
{
LogError("未选择串口");
return;
}
var baudRate = ReadInt(m_baudRateInput, 115200);
m_manager.Configure(false, portName, baudRate);
m_manager.Config.RawLogEnabled = false;
LogInfo("正在连接模组: " + portName + " @ " + baudRate);
m_manager.Open();
await WaitReadyAndProbeAsync();
LogInfo("模组连接成功,状态: " + m_manager.CurrentStatus);
RefreshStatus();
});
}
///
/// 断开模组。
///
public void DisconnectModule()
{
m_manager?.Close();
LogInfo("模组连接已关闭");
RefreshStatus();
}
///
/// 录入人脸。
///
public void EnrollFace()
{
RunAsync(async () =>
{
if (!RequireReady())
{
return;
}
var userName = string.IsNullOrWhiteSpace(m_userNameInput?.text) ? "User" : m_userNameInput.text.Trim();
await PrepareFaceOperationAsync("录入人脸");
LogInfo("开始录入人脸: " + userName);
var result = await EnrollSingleWithFallbackAsync(m_adminToggle != null && m_adminToggle.isOn, userName, 30);
if (result.Success)
{
SetResult("录入成功,UserID=" + result.UserId);
LogInfo("录入成功,UserID=" + result.UserId);
await RefreshUsersInternal();
}
else
{
var message = FaceProtocolEncoding.ResultMessage(result.ResultCode);
SetResult("录入失败: " + message);
LogError("录入失败: " + message);
}
});
}
///
/// 检测人脸。
///
public void VerifyFace()
{
RunAsync(async () =>
{
if (!RequireReady())
{
return;
}
await PrepareFaceOperationAsync("检测人脸");
LogInfo("开始检测人脸");
var result = await m_manager.Verify.VerifyAsync(30, 60);
if (result.Success)
{
var message = $"检测成功,UserID={result.UserId},姓名={result.UserName},管理员={result.IsAdmin}";
SetResult(message);
LogInfo(message);
}
else
{
var message = FaceProtocolEncoding.ResultMessage(result.ResultCode);
SetResult("检测失败: " + message);
LogError("检测失败: " + message);
}
});
}
///
/// 刷新用户列表。
///
public void RefreshUsers()
{
RunAsync(RefreshUsersInternal);
}
///
/// 删除指定用户。
///
public void DeleteUser(int userId)
{
RunAsync(async () =>
{
if (!RequireReady())
{
return;
}
await m_manager.Users.DeleteUserAsync(userId);
LogInfo("已删除用户: " + userId);
await RefreshUsersInternal();
});
}
private async Task RefreshUsersInternal()
{
if (!RequireReady())
{
return;
}
await EnsureModuleStandbyAsync("刷新用户列表");
var users = await GetAllUsersWithFallbackAsync();
m_userList?.SetUsers(users);
LogInfo("用户列表已刷新,数量: " + users.Count);
}
private bool RequireReady()
{
if (m_manager != null && m_manager.IsOpen && m_manager.IsReady)
{
return true;
}
LogError("模组未连接或未 READY");
RefreshStatus();
return false;
}
private void RefreshStatus()
{
if (m_statusText == null)
{
return;
}
var cameraStatus = m_camera != null && m_camera.IsOpen ? "摄像头已打开" : "摄像头未打开";
var moduleStatus = m_manager == null
? "模组不存在"
: (m_manager.IsReady ? "模组 READY" : (m_manager.IsOpen ? "模组已连接,未 READY" : "模组未连接"));
m_statusText.text = cameraStatus + " | " + moduleStatus;
}
private void OnFaceStateUpdated(FaceFaceStateNote state)
{
SetResult("人脸状态: " + FaceProtocolEncoding.FaceStateMessage(state.State));
}
private void OnAutoVerifyResult(FaceAutoVerifyResult result)
{
if (result.Success)
{
SetResult("自动检测成功,UserID=" + result.VerifyResult.UserId + ",姓名=" + result.VerifyResult.UserName);
return;
}
SetResult("自动检测结果: " + FaceProtocolEncoding.ResultMessage(result.ResultCode));
}
private void OnManagerError(FaceResultCode _, string message)
{
LogError("SDK 错误: " + message);
}
private void OnManagerReady()
{
LogInfo("模组 READY");
}
private async Task WaitReadyAndProbeAsync()
{
try
{
await m_manager.WaitReadyAsync(1500);
}
catch (TimeoutException)
{
LogInfo("未收到 READY,改用主动查询确认模组状态");
}
LogInfo("正在查询模组状态");
await m_manager.ProbeReadyByStatusAsync();
}
private async Task PrepareFaceOperationAsync(string operationName)
{
await EnsureModuleStandbyAsync(operationName);
await ResetFaceFlowIfPossibleAsync(operationName);
}
private async Task EnsureModuleStandbyAsync(string operationName)
{
if (m_manager.IsAutoVerifyEnabled)
{
await m_manager.EnsureExclusiveOperationReadyAsync(operationName);
return;
}
var status = await m_manager.Basic.GetStatusAsync();
if (status == FaceModuleStatus.Busy)
{
LogInfo(operationName + " 前模组忙碌,正在取消当前人脸流程");
await ResetFaceFlowIfPossibleAsync(operationName);
await Task.Delay(300);
status = await m_manager.Basic.GetStatusAsync();
}
if (status == FaceModuleStatus.Busy)
{
throw new InvalidOperationException(operationName + " 前模组仍处于忙碌状态");
}
if (status == FaceModuleStatus.Error || status == FaceModuleStatus.Invalid)
{
throw new InvalidOperationException(operationName + " 前模组状态异常: " + status);
}
}
private async Task ResetFaceFlowIfPossibleAsync(string operationName)
{
try
{
await m_manager.Basic.FaceResetAsync();
LogInfo(operationName + " 前已取消残留人脸流程");
}
catch (Exception ex)
{
LogInfo(operationName + " 前取消残留流程未完成: " + ex.Message);
}
}
private async Task EnrollSingleWithFallbackAsync(bool admin, string userName, byte timeout)
{
try
{
return await m_manager.Enrollment.EnrollSingleAsync(admin, userName, timeout);
}
catch (TimeoutException)
{
LogInfo("单帧录入超时,改用集成单帧录入重试");
await ResetFaceFlowIfPossibleAsync("录入重试");
return await m_manager.Enrollment.EnrollIntegratedAsync(admin, userName, FaceDirection.Middle, timeout, true, true);
}
}
private async Task> GetAllUsersWithFallbackAsync()
{
try
{
return await m_manager.Users.GetAllUserInfos2Async();
}
catch (TimeoutException)
{
LogInfo("扩展用户列表超时,改用普通用户列表查询");
return await m_manager.Users.GetAllUserInfosAsync();
}
}
private void OnSerialDropdownChanged(int _)
{
var selected = SelectedDropdownText(m_serialDropdown);
if (!string.IsNullOrWhiteSpace(selected) && selected != "手动输入串口" && m_serialInput != null)
{
m_serialInput.text = selected;
}
}
private string ReadPortName()
{
var selected = SelectedDropdownText(m_serialDropdown);
if (!string.IsNullOrWhiteSpace(selected) && selected != "手动输入串口")
{
return selected;
}
return m_serialInput != null ? m_serialInput.text.Trim() : string.Empty;
}
private static int ReadInt(InputField input, int fallback)
{
return input != null && int.TryParse(input.text, out var value) ? value : fallback;
}
private static string SelectedDropdownText(Dropdown dropdown)
{
if (dropdown == null || dropdown.options == null || dropdown.options.Count == 0)
{
return string.Empty;
}
return dropdown.options[Mathf.Clamp(dropdown.value, 0, dropdown.options.Count - 1)].text;
}
private static void SetDropdownOptions(Dropdown dropdown, IReadOnlyList values, string emptyText)
{
if (dropdown == null)
{
return;
}
dropdown.ClearOptions();
var options = new List();
if (values != null)
{
for (var i = 0; i < values.Count; i++)
{
if (!string.IsNullOrWhiteSpace(values[i]))
{
options.Add(values[i]);
}
}
}
if (options.Count == 0)
{
options.Add(emptyText);
}
dropdown.AddOptions(options);
dropdown.value = 0;
dropdown.RefreshShownValue();
}
private async void RunAsync(Func action)
{
try
{
await action();
RefreshStatus();
}
catch (Exception ex)
{
LogError(ex.Message);
SetResult("错误: " + ex.Message);
RefreshStatus();
}
}
private void SetResult(string message)
{
if (m_resultText != null)
{
m_resultText.text = message;
}
}
private void LogInfo(string message)
{
m_logger?.Info(message);
}
private void LogError(string message)
{
m_logger?.Error(message);
}
}
}