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() { RunAsync(async () => { if (m_manager != null) { await m_manager.CloseAsync(); } 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.StopFaceOperationAsync(); 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); } } }