using System; using System.Collections.Generic; using System.IO.Ports; using UnityEngine; using UnityEngine.UI; namespace Aisu.CardReader.Samples { /// 用于 Windows 实机联调的最小 Unity UI 示例。 public sealed class CardReaderSampleUI : MonoBehaviour { [SerializeField] private Dropdown portDropdown; [SerializeField] private Button refreshButton; [SerializeField] private Button openButton; [SerializeField] private Button closeButton; [SerializeField] private Text stateText; [SerializeField] private Text portText; [SerializeField] private Text cardTypeText; [SerializeField] private Text cardNumberText; [SerializeField] private Text lastEventTimeText; [SerializeField] private Text errorText; [SerializeField] private Text logText; private readonly List portNames = new List(); private readonly List logLines = new List(); private CardReaderManager manager; private bool operationInProgress; private bool destroying; private bool apiSubscribed; private void Awake() { manager = CardReaderManager.Instance; SetText(stateText, "状态:未连接"); SetText(portText, "端口:-"); SetText(cardTypeText, "CardType:-"); SetText(cardNumberText, "CardNumber:-"); SetText(lastEventTimeText, "最后事件:-"); SetText(errorText, "错误:-"); SetText(logText, "日志:-"); } private void OnEnable() { refreshButton?.onClick.AddListener(RefreshPorts); openButton?.onClick.AddListener(OpenSelectedPort); closeButton?.onClick.AddListener(CloseReader); SubscribeApiEvents(); } private void Start() { // Manager 与 Sample 的 Awake 顺序不应影响事件订阅和首次端口扫描。 SubscribeApiEvents(); RefreshPorts(); } private void SubscribeApiEvents() { if (apiSubscribed) return; if (manager == null) manager = CardReaderManager.Instance; if (manager == null) return; CardReaderApi.OnConnected.AddListener(OnConnected); CardReaderApi.OnDisconnected.AddListener(OnDisconnected); CardReaderApi.OnStateChanged.AddListener(OnStateChanged); CardReaderApi.OnCardReceived.AddListener(OnCardReceived); CardReaderApi.OnError.AddListener(OnReaderError); apiSubscribed = true; } private void OnDisable() { refreshButton?.onClick.RemoveListener(RefreshPorts); openButton?.onClick.RemoveListener(OpenSelectedPort); closeButton?.onClick.RemoveListener(CloseReader); if (!apiSubscribed || manager == null) return; CardReaderApi.OnConnected.RemoveListener(OnConnected); CardReaderApi.OnDisconnected.RemoveListener(OnDisconnected); CardReaderApi.OnStateChanged.RemoveListener(OnStateChanged); CardReaderApi.OnCardReceived.RemoveListener(OnCardReceived); CardReaderApi.OnError.RemoveListener(OnReaderError); apiSubscribed = false; } public void RefreshPorts() { if (destroying || operationInProgress) return; var previousPort = GetSelectedPort(); portNames.Clear(); portNames.AddRange(SerialPort.GetPortNames()); portNames.Sort(StringComparer.OrdinalIgnoreCase); if (portDropdown != null) { var options = portNames.Count == 0 ? new List { "No Ports" } : new List(portNames); portDropdown.ClearOptions(); portDropdown.AddOptions(options); var selectedIndex = previousPort == null ? 0 : portNames.IndexOf(previousPort); portDropdown.value = selectedIndex < 0 ? 0 : selectedIndex; portDropdown.RefreshShownValue(); } SetText(portText, portNames.Count == 0 ? "端口:未发现串口" : "端口:" + GetSelectedPort()); if (portNames.Count == 0) SetStateText("未发现串口"); AppendLog(portNames.Count == 0 ? "未发现可用串口。" : "发现串口:" + string.Join(", ", portNames)); UpdateButtons(); } public void OpenSelectedPort() { if (destroying || operationInProgress || manager == null || portDropdown == null || portNames.Count == 0) return; operationInProgress = true; UpdateButtons(); try { var selectedPort = GetSelectedPort(); if (string.IsNullOrEmpty(selectedPort)) return; var current = manager.SerialOptions; manager.SerialOptions = new SerialDeviceOptions { PortName = selectedPort, BaudRate = current.BaudRate, Parity = current.Parity, DataBits = current.DataBits, StopBits = current.StopBits, Handshake = current.Handshake, RtsEnable = current.RtsEnable, DtrEnable = current.DtrEnable, ReadTimeout = current.ReadTimeout, WriteTimeout = current.WriteTimeout, AutoReconnect = current.AutoReconnect, ReconnectInterval = current.ReconnectInterval, MaxPendingSendCount = current.MaxPendingSendCount }; CardReaderApi.Open(); SetText(portText, "端口:" + selectedPort); AppendLog("已打开读卡器:" + selectedPort); } catch (Exception exception) { ShowError("连接失败", exception); } finally { operationInProgress = false; UpdateButtons(); } } public void CloseReader() { if (destroying || operationInProgress || manager == null) return; operationInProgress = true; try { CardReaderApi.Close(); AppendLog("已关闭读卡器。"); } catch (Exception exception) { ShowError("断开失败", exception); } finally { operationInProgress = false; UpdateButtons(); } } private void OnConnected() { SetStateText("Connected"); AppendLog("读卡器已连接。"); UpdateButtons(); } private void OnDisconnected() { SetStateText("Disconnected"); AppendLog("读卡器已断开。"); UpdateButtons(); } private void OnStateChanged(SerialDeviceState state) { SetStateText(state.ToString()); AppendLog("状态:" + state); UpdateButtons(); } private void OnCardReceived(CardInfo card) { SetText(cardTypeText, $"CardType:{card.CardType} (0x{card.CardTypeCode:X2})"); SetText(cardNumberText, $"CardNumber:{card.CardNumber}"); SetText(lastEventTimeText, $"最后事件:{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}"); AppendLog("收到卡片:" + card.CardNumber); } private void OnReaderError(CardReaderErrorEventArgs args) { ShowError("Transport/Core 错误", args.Exception); } private void OnDestroy() { destroying = true; } private void UpdateButtons() { var connected = manager != null && manager.IsConnected; if (refreshButton != null) refreshButton.interactable = !operationInProgress && !connected; if (openButton != null) openButton.interactable = !operationInProgress && !connected && portNames.Count > 0; if (closeButton != null) closeButton.interactable = !operationInProgress && connected; } private void ShowError(string message, Exception exception) { var text = exception == null ? message : message + ":" + FormatException(exception); SetText(errorText, "错误:" + text); AppendLog(text); Debug.LogError("[AISU 830T Sample] " + text, this); } private string GetSelectedPort() { if (portDropdown == null || portDropdown.value < 0 || portDropdown.value >= portNames.Count) return null; return portNames[portDropdown.value]; } private void SetStateText(string state) { SetText(stateText, "状态:" + state); } private void AppendLog(string message) { if (logText == null) return; logLines.Add(DateTime.Now.ToString("HH:mm:ss.fff") + " " + message); if (logLines.Count > 40) logLines.RemoveAt(0); SetText(logText, "日志:\n" + string.Join("\n", logLines)); } private static string FormatException(Exception exception) { var current = exception; var result = exception.GetType().Name + ": " + exception.Message; while (current.InnerException != null) { current = current.InnerException; result += " | " + current.GetType().Name + ": " + current.Message; } return result; } private static void SetText(Text target, string value) { if (target != null) target.text = value; } } }