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; }
}
}