From 7d2b65ff3857a18d19690a4dfe124de853e2d56d Mon Sep 17 00:00:00 2001 From: su Date: Tue, 8 Sep 2026 17:55:28 +0800 Subject: [PATCH] feat: release card reader SDK v0.1.0 --- CHANGELOG.md | 19 + CHANGELOG.md.meta | 7 + Documentation~.meta | 8 + Documentation~/API.en.md | 136 + Documentation~/API.zh-CN.md | 136 + Documentation~/GettingStarted.en.md | 88 + Documentation~/GettingStarted.zh-CN.md | 88 + Documentation~/index.md | 17 + README.en.md | 157 + README.md | 8 + README.md.meta | 7 + README.zh-CN.md | 157 + Runtime.meta | 8 + Runtime/Plugins.meta | 8 + .../Aisu.CardReader.Card830T.Runtime.dll | Bin 0 -> 29696 bytes .../Aisu.CardReader.Card830T.Runtime.dll.meta | 32 + Samples~.meta | 8 + Samples~/Basic/README.en.md | 25 + Samples~/Basic/README.en.md.meta | 7 + Samples~/Basic/README.md | 28 + Samples~/Basic/README.md.meta | 7 + Samples~/Basic/Scenes/CardReaderSample.unity | 5126 +++++++++++++++++ .../Basic/Scenes/CardReaderSample.unity.meta | 7 + Samples~/Basic/Scripts/CardReaderSampleUI.cs | 234 + .../Basic/Scripts/CardReaderSampleUI.cs.meta | 11 + Samples~/Console Demo/README.en.md | 12 + Samples~/Console Demo/README.en.md.meta | 7 + Samples~/Console Demo/README.zh-CN.md | 12 + Samples~/Console Demo/README.zh-CN.md.meta | 7 + Samples~/Console Demo/Scenes.meta | 8 + .../Console Demo/Scenes/SampleScene.unity | 359 ++ .../Scenes/SampleScene.unity.meta | 7 + Samples~/Console Demo/Scripts.meta | 8 + .../Scripts/CardReaderConsoleTest.cs | 43 + .../Scripts/CardReaderConsoleTest.cs.meta | 11 + package.json | 25 + package.json.meta | 7 + 37 files changed, 6835 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 CHANGELOG.md.meta create mode 100644 Documentation~.meta create mode 100644 Documentation~/API.en.md create mode 100644 Documentation~/API.zh-CN.md create mode 100644 Documentation~/GettingStarted.en.md create mode 100644 Documentation~/GettingStarted.zh-CN.md create mode 100644 Documentation~/index.md create mode 100644 README.en.md create mode 100644 README.md create mode 100644 README.md.meta create mode 100644 README.zh-CN.md create mode 100644 Runtime.meta create mode 100644 Runtime/Plugins.meta create mode 100644 Runtime/Plugins/Aisu.CardReader.Card830T.Runtime.dll create mode 100644 Runtime/Plugins/Aisu.CardReader.Card830T.Runtime.dll.meta create mode 100644 Samples~.meta create mode 100644 Samples~/Basic/README.en.md create mode 100644 Samples~/Basic/README.en.md.meta create mode 100644 Samples~/Basic/README.md create mode 100644 Samples~/Basic/README.md.meta create mode 100644 Samples~/Basic/Scenes/CardReaderSample.unity create mode 100644 Samples~/Basic/Scenes/CardReaderSample.unity.meta create mode 100644 Samples~/Basic/Scripts/CardReaderSampleUI.cs create mode 100644 Samples~/Basic/Scripts/CardReaderSampleUI.cs.meta create mode 100644 Samples~/Console Demo/README.en.md create mode 100644 Samples~/Console Demo/README.en.md.meta create mode 100644 Samples~/Console Demo/README.zh-CN.md create mode 100644 Samples~/Console Demo/README.zh-CN.md.meta create mode 100644 Samples~/Console Demo/Scenes.meta create mode 100644 Samples~/Console Demo/Scenes/SampleScene.unity create mode 100644 Samples~/Console Demo/Scenes/SampleScene.unity.meta create mode 100644 Samples~/Console Demo/Scripts.meta create mode 100644 Samples~/Console Demo/Scripts/CardReaderConsoleTest.cs create mode 100644 Samples~/Console Demo/Scripts/CardReaderConsoleTest.cs.meta create mode 100644 package.json create mode 100644 package.json.meta diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..02d9d9e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,19 @@ +# Changelog + +## [0.1.0] + +### 中文 + +- 初始发布 AISU 830T Card Reader Unity Package。 +- 支持读卡器打开、关闭和串口参数配置。 +- 支持连接异常后的自动重连。 +- 支持接收卡片信息和十进制 `CardNumber`。 +- 提供 Unity 事件 API、Basic Sample 和 Console Demo。 + +### English + +- Initial release of the AISU 830T Card Reader Unity package. +- Reader open/close and serial configuration. +- Automatic reconnect after connection failures. +- Card reading with decimal `CardNumber` values. +- Unity event API, Basic Sample, and Console Demo. diff --git a/CHANGELOG.md.meta b/CHANGELOG.md.meta new file mode 100644 index 0000000..908ca53 --- /dev/null +++ b/CHANGELOG.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5dcfefc0d619a834e87b1e62740cfa3d +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation~.meta b/Documentation~.meta new file mode 100644 index 0000000..9d36471 --- /dev/null +++ b/Documentation~.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6837a6f6e2f24393a5f8b5ae32d59098 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation~/API.en.md b/Documentation~/API.en.md new file mode 100644 index 0000000..3728230 --- /dev/null +++ b/Documentation~/API.en.md @@ -0,0 +1,136 @@ +# API Reference + +This document covers the Unity-facing public API of AISU 830T Card Reader SDK v0.1.0. The namespace is `Aisu.CardReader`. + +## CardReaderManager + +`CardReaderManager` is the scene-level reader component. Add it to a GameObject and keep one instance in the scene. + +### Main properties + +```csharp +public static CardReaderManager Instance { get; } +public SerialDeviceOptions SerialOptions { get; set; } +public bool IsConnected { get; } +``` + +- `Instance`: the current scene Manager. +- `SerialOptions`: serial configuration used by the Inspector and by code. +- `IsConnected`: whether the reader is currently connected. + +### Main methods + +```csharp +public void OpenReader(); +public void CloseReader(); +``` + +`OpenReader()` starts the reader connection. `CloseReader()` closes the current connection and stops automatic reconnect; `OpenReader()` can be called again later. + +### Events + +```csharp +public UnityEvent OnConnected { get; } +public UnityEvent OnDisconnected { get; } +public UnityEvent OnStateChanged { get; } +public UnityEvent OnCardReceived { get; } +public UnityEvent OnError { get; } +``` + +Use `AddListener` and `RemoveListener` for event subscription. UnityEvents raised through the Manager are dispatched on the Unity main thread. + +## CardReaderApi + +`CardReaderApi` is the static access point for the current `CardReaderManager` and is convenient for application scripts. + +```csharp +public static bool IsConnected { get; } +public static void Open(); +public static void Close(); + +public static UnityEvent OnConnected { get; } +public static UnityEvent OnDisconnected { get; } +public static UnityEvent OnStateChanged { get; } +public static UnityEvent OnCardReceived { get; } +public static UnityEvent OnError { get; } +``` + +`Open()` starts the connection and `Close()` closes it. Calling either method or accessing an event without an active `CardReaderManager` throws `InvalidOperationException`. + +## SerialDeviceOptions + +`SerialDeviceOptions` is the configuration type of `CardReaderManager.SerialOptions`. The currently exposed fields are: + +| Field | Type | Default | Description | +| --- | --- | --- | --- | +| `PortName` | `string` | none | Windows COM-port name. | +| `BaudRate` | `int` | `9600` | Baud rate. | +| `Parity` | `Parity` | `None` | Parity mode. | +| `DataBits` | `int` | `8` | Number of data bits. | +| `StopBits` | `StopBits` | `One` | Stop-bit setting. | +| `Handshake` | `Handshake` | `None` | Handshake mode. | +| `ReadTimeout` | `int` | `100` | Read timeout in milliseconds. | +| `WriteTimeout` | `int` | `1000` | Write timeout in milliseconds. | +| `RtsEnable` | `bool` | `true` | Whether RTS is enabled. | +| `DtrEnable` | `bool` | `true` | Whether DTR is enabled. | +| `AutoReconnect` | `bool` | `true` | Whether to keep retrying after a connection failure. | +| `ReconnectInterval` | `int` | `1000` | Automatic reconnect interval in milliseconds. | +| `MaxPendingSendCount` | `int` | `256` | Pending-data capacity. | + +The actual COM port and UART settings depend on the connected reader. + +## CardInfo + +`CardInfo` is the card data supplied by `OnCardReceived`: + +```csharp +public CardType CardType { get; } +public byte CardTypeCode { get; } +public string CardNumber { get; } +``` + +- `CardType`: the currently mapped type is `CardType.EM4100`. +- `CardTypeCode`: the raw type code reported by the device. Unknown values are preserved. +- `CardNumber`: a decimal card-number string. + +### CardNumber rules + +`CardNumber` is a `string` containing the decimal card number. Its default minimum width is 10 digits: values shorter than 10 digits are left-padded with `0`, and values longer than 10 digits are not truncated. + +## CardType + +```csharp +public enum CardType +{ + Unknown = 0, + EM4100 = 0x02 +} +``` + +## Error events + +`OnError` provides `CardReaderErrorEventArgs`: + +```csharp +public CardReaderException Exception { get; } +``` + +`Exception` is the actual exception object. Common causes include a missing COM port, a port already in use, and a failed connection. Identical connection errors are not repeatedly emitted, while automatic reconnect continues when `AutoReconnect` is enabled. + +## Event usage + +Register listeners with `AddListener` in `OnEnable` and remove them with `RemoveListener` in `OnDisable`: + +```csharp +private void OnEnable() +{ + CardReaderApi.OnCardReceived.AddListener(OnCardReceived); +} + +private void OnDisable() +{ + CardReaderApi.OnCardReceived.RemoveListener(OnCardReceived); +} +``` + +`CardReaderManager` releases serial resources when it is destroyed or when the application exits. diff --git a/Documentation~/API.zh-CN.md b/Documentation~/API.zh-CN.md new file mode 100644 index 0000000..74809fe --- /dev/null +++ b/Documentation~/API.zh-CN.md @@ -0,0 +1,136 @@ +# API 参考 + +本文仅记录 AISU 830T Card Reader SDK v0.1.0 中面向 Unity 使用者的公开 API。命名空间为 `Aisu.CardReader`。 + +## CardReaderManager + +`CardReaderManager` 是场景级读卡器管理组件。将它添加到场景中的 GameObject,并保持一个实例。 + +### 主要属性 + +```csharp +public static CardReaderManager Instance { get; } +public SerialDeviceOptions SerialOptions { get; set; } +public bool IsConnected { get; } +``` + +- `Instance`:当前场景中的 Manager。 +- `SerialOptions`:Inspector 和代码使用的串口配置。 +- `IsConnected`:当前是否已连接。 + +### 主要方法 + +```csharp +public void OpenReader(); +public void CloseReader(); +``` + +`OpenReader()` 启动读卡器连接。`CloseReader()` 关闭当前连接并停止自动重连;之后可以再次调用 `OpenReader()`。 + +### 事件 + +```csharp +public UnityEvent OnConnected { get; } +public UnityEvent OnDisconnected { get; } +public UnityEvent OnStateChanged { get; } +public UnityEvent OnCardReceived { get; } +public UnityEvent OnError { get; } +``` + +事件使用 `AddListener` 和 `RemoveListener`。通过 Manager 接收的 UnityEvent 在 Unity 主线程派发。 + +## CardReaderApi + +`CardReaderApi` 是当前 `CardReaderManager` 的静态访问入口,适合在业务脚本中使用。 + +```csharp +public static bool IsConnected { get; } +public static void Open(); +public static void Close(); + +public static UnityEvent OnConnected { get; } +public static UnityEvent OnDisconnected { get; } +public static UnityEvent OnStateChanged { get; } +public static UnityEvent OnCardReceived { get; } +public static UnityEvent OnError { get; } +``` + +`Open()` 与 `Close()` 的行为分别是启动连接和关闭连接。没有活动的 `CardReaderManager` 时,调用方法或访问事件会抛出 `InvalidOperationException`。 + +## SerialDeviceOptions + +`SerialDeviceOptions` 是 `CardReaderManager.SerialOptions` 的配置类型。当前公开字段如下: + +| 字段 | 类型 | 默认值 | 说明 | +| --- | --- | --- | --- | +| `PortName` | `string` | 无 | Windows COM 端口名称。 | +| `BaudRate` | `int` | `9600` | 波特率。 | +| `Parity` | `Parity` | `None` | 校验方式。 | +| `DataBits` | `int` | `8` | 数据位。 | +| `StopBits` | `StopBits` | `One` | 停止位。 | +| `Handshake` | `Handshake` | `None` | 握手方式。 | +| `ReadTimeout` | `int` | `100` | 读超时,单位毫秒。 | +| `WriteTimeout` | `int` | `1000` | 写超时,单位毫秒。 | +| `RtsEnable` | `bool` | `true` | 是否启用 RTS。 | +| `DtrEnable` | `bool` | `true` | 是否启用 DTR。 | +| `AutoReconnect` | `bool` | `true` | 连接异常后是否继续自动重连。 | +| `ReconnectInterval` | `int` | `1000` | 自动重连间隔,单位毫秒。 | +| `MaxPendingSendCount` | `int` | `256` | 待发送数据容量。 | + +实际设备的 COM 口和 UART 参数可能不同,请以设备配置为准。 + +## CardInfo + +`CardInfo` 是 `OnCardReceived` 提供的卡片信息: + +```csharp +public CardType CardType { get; } +public byte CardTypeCode { get; } +public string CardNumber { get; } +``` + +- `CardType`:当前已知类型为 `CardType.EM4100`。 +- `CardTypeCode`:设备上报的原始类型码。未知类型不会丢失。 +- `CardNumber`:十进制卡号字符串。 + +### CardNumber 规则 + +`CardNumber` 的类型为 `string`,表示十进制卡号字符串。默认最小长度为 10 位:不足 10 位时左侧补 `0`,超过 10 位时不截断。 + +## CardType + +```csharp +public enum CardType +{ + Unknown = 0, + EM4100 = 0x02 +} +``` + +## 错误事件 + +`OnError` 提供 `CardReaderErrorEventArgs`: + +```csharp +public CardReaderException Exception { get; } +``` + +`Exception` 是真实异常对象。常见原因包括 COM 端口不存在、端口被其他程序占用和连接失败。相同的连接错误不会持续重复派发,但在 `AutoReconnect` 开启时 SDK 仍会继续尝试连接。 + +## 事件使用约定 + +推荐在 `OnEnable` 中使用 `AddListener` 注册,在 `OnDisable` 中使用 `RemoveListener` 解除。生命周期示例: + +```csharp +private void OnEnable() +{ + CardReaderApi.OnCardReceived.AddListener(OnCardReceived); +} + +private void OnDisable() +{ + CardReaderApi.OnCardReceived.RemoveListener(OnCardReceived); +} +``` + +`CardReaderManager` 销毁或应用退出时会自动释放串口资源。 diff --git a/Documentation~/GettingStarted.en.md b/Documentation~/GettingStarted.en.md new file mode 100644 index 0000000..74744b5 --- /dev/null +++ b/Documentation~/GettingStarted.en.md @@ -0,0 +1,88 @@ +# Getting Started + +This guide describes the actual usage flow for AISU 830T Card Reader SDK v0.1.0. + +## 1. Install the package + +In Unity Package Manager, choose **Add package from git URL...** and enter: + +```text +https://git.aisu.xin/aisu-sdk/card-reader-830t-package.git +``` + +The Package ID is `xin.aisu.card-reader-830t`. The minimum Unity version is 2021.3, and the current supported platform is Windows. + +## 2. Import the Basic Sample + +Open the package details, find the **Samples** section, and click **Import** for **Basic**. Open the imported `Scenes/CardReaderSample.unity` scene. + +## 3. Add CardReaderManager + +Create a GameObject in the scene and add the `CardReaderManager` component. Keep one Manager instance in the scene. + +## 4. Configure the serial port + +Configure `SerialOptions` in the Manager Inspector: + +- `PortName`: the actual Windows COM port, for example `COM3`. +- `BaudRate`, `Parity`, `DataBits`, and `StopBits`: use the reader's settings. +- Keep the other defaults, or configure `ReadTimeout`, `WriteTimeout`, `AutoReconnect`, `ReconnectInterval`, and `MaxPendingSendCount` as needed. + +## 5. Open and listen for events + +Register listeners with `AddListener`, then call `CardReaderApi.Open()`: + +```csharp +using Aisu.CardReader; +using UnityEngine; + +public sealed class CardReaderUsage : MonoBehaviour +{ + private void OnEnable() + { + CardReaderApi.OnConnected.AddListener(OnConnected); + CardReaderApi.OnCardReceived.AddListener(OnCardReceived); + CardReaderApi.OnError.AddListener(OnError); + } + + private void Start() + { + CardReaderApi.Open(); + } + + private static void OnConnected() + { + Debug.Log("Reader connected."); + } + + private static void OnCardReceived(CardInfo card) + { + Debug.Log($"{card.CardType}: {card.CardNumber}"); + } + + private static void OnError(CardReaderErrorEventArgs error) + { + Debug.LogError(error.Exception.Message); + } + + private void OnDisable() + { + CardReaderApi.OnConnected.RemoveListener(OnConnected); + CardReaderApi.OnCardReceived.RemoveListener(OnCardReceived); + CardReaderApi.OnError.RemoveListener(OnError); + CardReaderApi.Close(); + } +} +``` + +Once connected, present a card to the reader and handle `OnCardReceived`. `CardInfo.CardNumber` is a decimal string with a default minimum width of 10 digits; shorter values are left-padded with `0`, and longer values are not truncated. + +## 6. Close + +Call `CardReaderApi.Close()` to close the current connection and stop automatic reconnect. You can call `Open()` again later. + +You can also call `OpenReader()` and `CloseReader()` on the current Manager. + +## Lifetime + +`CardReaderManager` releases serial resources when it is destroyed or when the application exits. Register events in `OnEnable` and remove them with `RemoveListener` in `OnDisable`. Without an active Manager, `CardReaderApi.Open()`, `Close()`, and event access throw `InvalidOperationException`. diff --git a/Documentation~/GettingStarted.zh-CN.md b/Documentation~/GettingStarted.zh-CN.md new file mode 100644 index 0000000..0f56cb7 --- /dev/null +++ b/Documentation~/GettingStarted.zh-CN.md @@ -0,0 +1,88 @@ +# 快速开始 + +本文介绍 AISU 830T Card Reader SDK v0.1.0 的实际使用流程。 + +## 1. 安装 Package + +在 Unity Package Manager 选择 **Add package from git URL...**,输入: + +```text +https://git.aisu.xin/aisu-sdk/card-reader-830t-package.git +``` + +Package ID 为 `xin.aisu.card-reader-830t`,最低 Unity 版本为 2021.3,当前支持 Windows。 + +## 2. 导入 Basic Sample + +打开包详情中的 **Samples** 区域,点击 **Import** 导入 **Basic**。使用导入后的 `Scenes/CardReaderSample.unity`。 + +## 3. 添加 CardReaderManager + +在场景中创建一个 GameObject,添加 `CardReaderManager` 组件。场景中保持一个 Manager 实例。 + +## 4. 配置串口 + +在 Manager Inspector 中设置 `SerialOptions`: + +- `PortName`:实际 Windows COM 端口,例如 `COM3`。 +- `BaudRate`、`Parity`、`DataBits`、`StopBits`:按读卡器参数设置。 +- 其他字段保留默认值,或按项目需要配置 `ReadTimeout`、`WriteTimeout`、`AutoReconnect`、`ReconnectInterval` 和 `MaxPendingSendCount`。 + +## 5. Open 与事件监听 + +使用 `AddListener` 注册事件,并调用 `CardReaderApi.Open()`: + +```csharp +using Aisu.CardReader; +using UnityEngine; + +public sealed class CardReaderUsage : MonoBehaviour +{ + private void OnEnable() + { + CardReaderApi.OnConnected.AddListener(OnConnected); + CardReaderApi.OnCardReceived.AddListener(OnCardReceived); + CardReaderApi.OnError.AddListener(OnError); + } + + private void Start() + { + CardReaderApi.Open(); + } + + private static void OnConnected() + { + Debug.Log("Reader connected."); + } + + private static void OnCardReceived(CardInfo card) + { + Debug.Log($"{card.CardType}: {card.CardNumber}"); + } + + private static void OnError(CardReaderErrorEventArgs error) + { + Debug.LogError(error.Exception.Message); + } + + private void OnDisable() + { + CardReaderApi.OnConnected.RemoveListener(OnConnected); + CardReaderApi.OnCardReceived.RemoveListener(OnCardReceived); + CardReaderApi.OnError.RemoveListener(OnError); + CardReaderApi.Close(); + } +} +``` + +设备连接后,将卡片靠近读卡器即可收到 `OnCardReceived`。事件中的 `CardInfo.CardNumber` 是十进制字符串:默认最小 10 位,不足时左侧补 `0`,超过 10 位不截断。 + +## 6. Close + +调用 `CardReaderApi.Close()` 关闭当前连接并停止自动重连。之后可以再次调用 `Open()`。 + +也可以直接调用当前 Manager 的 `OpenReader()` 和 `CloseReader()`。 + +## 生命周期 + +`CardReaderManager` 被销毁或应用退出时会自动释放串口资源。事件建议在 `OnEnable` 注册、在 `OnDisable` 使用 `RemoveListener` 解除。没有活动 Manager 时调用 `CardReaderApi.Open()`、`Close()` 或访问事件会抛出 `InvalidOperationException`。 diff --git a/Documentation~/index.md b/Documentation~/index.md new file mode 100644 index 0000000..6d513b1 --- /dev/null +++ b/Documentation~/index.md @@ -0,0 +1,17 @@ +# AISU 830T Card Reader SDK Documentation + +## 中文 + +- [快速开始](GettingStarted.zh-CN.md) +- [API 参考](API.zh-CN.md) +- [Basic Sample](../Samples~/Basic/README.md) + +## English + +- [Getting Started](GettingStarted.en.md) +- [API Reference](API.en.md) +- [Basic Sample](../Samples~/Basic/README.en.md) + +SDK version: `0.1.0` + +Package ID: `xin.aisu.card-reader-830t` diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..fdca279 --- /dev/null +++ b/README.en.md @@ -0,0 +1,157 @@ +# AISU 830T Card Reader SDK + +AISU 830T Card Reader SDK is a Unity UPM package for connecting an 830T card reader and receiving card data reported by the device. + +## Supported environment + +- Unity 2021.3 or later +- AISU 830T Card Reader +- Package ID: `xin.aisu.card-reader-830t` + +Android, Wiegand, active card search/read/write commands, and ACK/NAK APIs are not supported in this release. + +## Installation + +In Unity Package Manager, choose **Add package from git URL...** and enter: + +```text +https://git.aisu.xin/aisu-sdk/card-reader-830t-package.git +``` + +You can also add the package from a local folder. After installation, import **Basic** or **Console Demo** from the package's **Samples** section. + +## Quick start + +Place one `CardReaderManager` in the scene, configure `SerialOptions` in the Inspector, and use `CardReaderApi`: + +```csharp +using Aisu.CardReader; +using UnityEngine; + +public sealed class CardReaderExample : MonoBehaviour +{ + private void OnEnable() + { + CardReaderApi.OnConnected.AddListener(OnConnected); + CardReaderApi.OnCardReceived.AddListener(OnCardReceived); + CardReaderApi.OnError.AddListener(OnError); + } + + private void Start() + { + CardReaderApi.Open(); + } + + private static void OnConnected() + { + Debug.Log("Card reader connected."); + } + + private static void OnCardReceived(CardInfo card) + { + Debug.Log($"{card.CardType} ({card.CardTypeCode:X2}): {card.CardNumber}"); + } + + private static void OnError(CardReaderErrorEventArgs error) + { + Debug.LogError(error.Exception.Message); + } + + private void OnDisable() + { + CardReaderApi.OnConnected.RemoveListener(OnConnected); + CardReaderApi.OnCardReceived.RemoveListener(OnCardReceived); + CardReaderApi.OnError.RemoveListener(OnError); + CardReaderApi.Close(); + } +} +``` + +## CardReaderManager + +`CardReaderManager` is the scene-level entry point. Keep one Manager instance in the scene. It exposes `SerialOptions`, `IsConnected`, `OpenReader()`, `CloseReader()`, and connection, disconnection, state, card, and error events. + +## Inspector serial settings + +`SerialOptions` uses `SerialDeviceOptions`. The currently exposed fields are: + +- `PortName`: Windows COM port, for example `COM3`. +- `BaudRate`: baud rate, default `9600`. +- `Parity`: parity mode, default `None`. +- `DataBits`: data bits, default `8`. +- `StopBits`: stop bits, default `One`. +- `Handshake`: handshake mode, default `None`. +- `ReadTimeout`: read timeout in milliseconds, default `100`. +- `WriteTimeout`: write timeout in milliseconds, default `1000`. +- `RtsEnable` / `DtrEnable`: RTS/DTR flags, enabled by default. +- `AutoReconnect`: whether to retry after a connection failure, enabled by default. +- `ReconnectInterval`: retry interval in milliseconds, default `1000`. +- `MaxPendingSendCount`: send queue capacity, default `256`. + +Configure the UART fields for the actual device. The Samples do not contain a fixed local COM port. + +## CardReaderApi and events + +`CardReaderApi` is the static access point for the current Manager. It provides: + +- `Open()`: starts the reader connection. +- `Close()`: closes the current connection and stops automatic reconnect; `Open()` can be called again later. +- `IsConnected`: current connection state. +- `OnConnected`, `OnDisconnected`, `OnStateChanged`, `OnCardReceived`, and `OnError`. + +Use `AddListener` to subscribe and `RemoveListener` to unsubscribe. Pair them in `OnEnable` and `OnDisable`. Accessing events or calling `Open`/`Close` without an active `CardReaderManager` throws `InvalidOperationException`. + +## CardInfo and CardNumber + +`CardInfo` contains: + +- `CardType`: the currently mapped type is `CardType.EM4100`. +- `CardTypeCode`: the raw type code reported by the device. +- `CardNumber`: a decimal card-number string of type `string`. + +`CardNumber` has a default minimum width of 10 digits. Values shorter than 10 digits are left-padded with `0`; longer values are not truncated. + +## Automatic reconnect and errors + +When `AutoReconnect` is enabled, the SDK continues retrying after a connection failure according to `ReconnectInterval`. `OnError` provides the actual `Exception`; common causes include a missing COM port, a port already in use, or a failed connection. Identical connection errors are not repeatedly emitted, while automatic reconnect continues. + +## Samples + +- **Basic**: full UI controls, COM-port selection, connection state, card information, and error log display. +- **Console Demo**: minimal API calls and console logging. + +## Frequently asked questions + +### No COM port is listed + +Confirm that the reader is connected and its driver is installed. Check the actual COM name in Windows Device Manager. The Basic Sample's **Refresh** button rescans available ports. + +### The COM port is already in use + +Close other serial tools or applications using the port, then select it again and call `Open()`. + +### The reader cannot connect + +Check `PortName`, baud rate, parity, data bits, and stop bits against the device settings. Also confirm that no other program owns the port. + +### How does automatic reconnect work? + +With `AutoReconnect` enabled, the SDK retries at `ReconnectInterval`. Calling `Close()` stops the current connection and automatic reconnect. + +### Are serial resources released when Unity exits? + +`CardReaderManager` releases the serial resources when it is destroyed or when the application exits. + +### How do I import the Basic Sample? + +After installing the package, open its details in Package Manager, find **Samples**, and click **Import** for **Basic**. A version directory in the imported path is standard Unity behavior. + +## Version + +This documentation describes SDK `v0.1.0`. + +More information: + +- [Getting Started](Documentation~/GettingStarted.en.md) +- [API reference](Documentation~/API.en.md) +- [Basic Sample guide](Samples~/Basic/README.en.md) diff --git a/README.md b/README.md new file mode 100644 index 0000000..8e56098 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# AISU 830T Card Reader SDK + +Unity package for integrating AISU 830T card readers. + +Language: + +- [中文](README.zh-CN.md) +- [English](README.en.md) diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 0000000..caa6244 --- /dev/null +++ b/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: df6324a2d5fda2947a4601402d564451 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.zh-CN.md b/README.zh-CN.md new file mode 100644 index 0000000..3ab4110 --- /dev/null +++ b/README.zh-CN.md @@ -0,0 +1,157 @@ +# AISU 830T Card Reader SDK + +AISU 830T Card Reader SDK 是一个面向 Unity 的 UPM Package,用于在 Windows 上连接 830T 读卡器,接收设备主动上报的卡片信息。 + +## 支持环境 + +- Unity 2021.3 或更高版本 +- AISU 830T Card Reader +- Package ID:`xin.aisu.card-reader-830t` + +当前不支持 Wiegand、主动寻卡/读卡/写卡命令或 ACK/NAK API。 + +## 安装 + +在 Unity Package Manager 中选择 **Add package from git URL...**,输入: + +```text +https://git.aisu.xin/aisu-sdk/card-reader-830t-package.git +``` + +也可以将本地 Package 目录添加到 Unity 项目。安装后,在包详情的 **Samples** 区域导入 **Basic** 或 **Console Demo**。 + +## 快速开始 + +在场景中放置一个 `CardReaderManager`,在 Inspector 中配置 `SerialOptions`,然后使用 `CardReaderApi`: + +```csharp +using Aisu.CardReader; +using UnityEngine; + +public sealed class CardReaderExample : MonoBehaviour +{ + private void OnEnable() + { + CardReaderApi.OnConnected.AddListener(OnConnected); + CardReaderApi.OnCardReceived.AddListener(OnCardReceived); + CardReaderApi.OnError.AddListener(OnError); + } + + private void Start() + { + CardReaderApi.Open(); + } + + private static void OnConnected() + { + Debug.Log("Card reader connected."); + } + + private static void OnCardReceived(CardInfo card) + { + Debug.Log($"{card.CardType} ({card.CardTypeCode:X2}): {card.CardNumber}"); + } + + private static void OnError(CardReaderErrorEventArgs error) + { + Debug.LogError(error.Exception.Message); + } + + private void OnDisable() + { + CardReaderApi.OnConnected.RemoveListener(OnConnected); + CardReaderApi.OnCardReceived.RemoveListener(OnCardReceived); + CardReaderApi.OnError.RemoveListener(OnError); + CardReaderApi.Close(); + } +} +``` + +## CardReaderManager + +`CardReaderManager` 是场景级入口。场景中应保持一个 Manager 实例。它提供 `SerialOptions`、`IsConnected`、`OpenReader()`、`CloseReader()` 以及连接、断开、状态、卡片和错误事件。 + +## Inspector 串口参数 + +`SerialOptions` 类型为 `SerialDeviceOptions`,当前公开字段如下: + +- `PortName`:Windows COM 端口,例如 `COM3`。 +- `BaudRate`:波特率,默认 `9600`。 +- `Parity`:校验方式,默认 `None`。 +- `DataBits`:数据位,默认 `8`。 +- `StopBits`:停止位,默认 `One`。 +- `Handshake`:握手方式,默认 `None`。 +- `ReadTimeout`:读超时毫秒数,默认 `100`。 +- `WriteTimeout`:写超时毫秒数,默认 `1000`。 +- `RtsEnable` / `DtrEnable`:RTS/DTR,默认开启。 +- `AutoReconnect`:连接异常后是否自动重连,默认开启。 +- `ReconnectInterval`:重连间隔毫秒数,默认 `1000`。 +- `MaxPendingSendCount`:发送队列容量,默认 `256`。 + +请根据实际设备参数配置 UART 字段。Sample 不包含固定的本地 COM 端口。 + +## CardReaderApi 与事件 + +`CardReaderApi` 是当前 Manager 的静态访问入口,提供: + +- `Open()`:启动 Reader 连接。 +- `Close()`:关闭当前连接并停止自动重连;之后可以再次 `Open()`。 +- `IsConnected`:当前连接状态。 +- `OnConnected`、`OnDisconnected`、`OnStateChanged`、`OnCardReceived`、`OnError`。 + +事件使用 `AddListener` 注册、`RemoveListener` 解除,建议在 `OnEnable` 和 `OnDisable` 中成对处理。没有活动的 `CardReaderManager` 时访问事件或调用 `Open`/`Close` 会抛出 `InvalidOperationException`。 + +## CardInfo 与 CardNumber + +`CardInfo` 包含: + +- `CardType`:当前已知类型为 `CardType.EM4100`。 +- `CardTypeCode`:设备上报的原始类型码。 +- `CardNumber`:类型为 `string` 的十进制卡号字符串。 + +`CardNumber` 默认最小长度为 10 位;不足 10 位时在左侧补 `0`,超过 10 位时不截断。 + +## 自动重连与错误处理 + +当 `AutoReconnect` 开启时,连接异常后 SDK 会按 `ReconnectInterval` 继续尝试连接。`OnError` 提供真实的 `Exception`,常见原因包括 COM 端口不存在、端口被占用或连接失败。相同的连接错误不会持续刷出重复事件,但 SDK 仍会继续自动重连。 + +## Samples + +- **Basic**:完整 UI 操作、COM 端口选择、连接状态、卡片信息和错误日志显示。 +- **Console Demo**:最小 API 调用和日志输出示例。 + +## 常见问题 + +### 找不到 COM 端口 + +确认设备已连接、驱动已安装,并在 Windows 设备管理器中查看实际 COM 名称。Basic Sample 的 **Refresh** 会重新扫描端口。 + +### COM 端口被占用 + +关闭其他串口工具或正在使用该端口的程序,然后重新选择端口并调用 `Open()`。 + +### 无法连接 Reader + +检查 `PortName`、波特率、Parity、DataBits 和 StopBits 是否与设备设置一致,并确认设备没有被其他程序占用。 + +### 自动重连如何工作 + +开启 `AutoReconnect` 后,SDK 会按 `ReconnectInterval` 重试。调用 `Close()` 会停止当前连接和自动重连。 + +### Unity 退出后是否释放串口 + +`CardReaderManager` 在销毁或应用退出时会自动释放串口资源。 + +### 如何导入 Basic Sample + +安装 Package 后,在 Package Manager 的包详情中找到 **Samples**,点击 **Import** 导入 **Basic**。导入路径带有版本目录是 Unity 的标准行为。 + +## 版本 + +当前文档对应 SDK `v0.1.0`。 + +更多内容: + +- [快速开始](Documentation~/GettingStarted.zh-CN.md) +- [API 文档](Documentation~/API.zh-CN.md) +- [Basic Sample 文档](Samples~/Basic/README.md) diff --git a/Runtime.meta b/Runtime.meta new file mode 100644 index 0000000..a57193d --- /dev/null +++ b/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 04b022d97a25bee4f9c23b88297ff5a4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Plugins.meta b/Runtime/Plugins.meta new file mode 100644 index 0000000..3576c37 --- /dev/null +++ b/Runtime/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 30b0694242524259a249d31bf2e082c5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Plugins/Aisu.CardReader.Card830T.Runtime.dll b/Runtime/Plugins/Aisu.CardReader.Card830T.Runtime.dll new file mode 100644 index 0000000000000000000000000000000000000000..1627a29cd89acb2eb4794113d64980f008dacb1c GIT binary patch literal 29696 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

=QEsCN_6eEcvoM)`VrKZ9uusSgp9fpcf4mLS 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 b*xzi2rvJR3A^mqp用于 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; } + } +} diff --git a/Samples~/Basic/Scripts/CardReaderSampleUI.cs.meta b/Samples~/Basic/Scripts/CardReaderSampleUI.cs.meta new file mode 100644 index 0000000..5375f18 --- /dev/null +++ b/Samples~/Basic/Scripts/CardReaderSampleUI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5d9c4f0bca3e4dbd8b2f7c4d9e1a6f20 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/Console Demo/README.en.md b/Samples~/Console Demo/README.en.md new file mode 100644 index 0000000..a20382f --- /dev/null +++ b/Samples~/Console Demo/README.en.md @@ -0,0 +1,12 @@ +# AISU 830T Console Demo + +Console Demo is a minimal logging sample that demonstrates `CardReaderApi` connection, disconnection, event subscription, and card-data output. + +## Usage + +1. Install `xin.aisu.card-reader-830t`. +2. Import **Console Demo** from the package's **Samples** section in Package Manager. +3. Open the imported `Scenes/SampleScene.unity` scene. +4. Set the actual COM port and UART settings in `CardReaderManager.SerialOptions`. +5. Run the scene. The sample uses `CardReaderApi.Open()` and writes connection, card, error, and disconnection messages to the Unity Console. +6. When the scene stops, the sample calls `CardReaderApi.Close()` and removes its listeners. diff --git a/Samples~/Console Demo/README.en.md.meta b/Samples~/Console Demo/README.en.md.meta new file mode 100644 index 0000000..99244c0 --- /dev/null +++ b/Samples~/Console Demo/README.en.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6d1f8a3c5b7e4290a2c4f6d8b1e3a750 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/Console Demo/README.zh-CN.md b/Samples~/Console Demo/README.zh-CN.md new file mode 100644 index 0000000..2cc102b --- /dev/null +++ b/Samples~/Console Demo/README.zh-CN.md @@ -0,0 +1,12 @@ +# AISU 830T Console Demo + +Console Demo 是最小 API 调用和日志输出示例,用于演示 `CardReaderApi` 的连接、断开、事件监听和卡片信息输出。 + +## 使用 + +1. 安装 `xin.aisu.card-reader-830t`。 +2. 在 Package Manager 的 **Samples** 区域导入 **Console Demo**。 +3. 打开导入后的 `Scenes/SampleScene.unity`。 +4. 在场景中的 `CardReaderManager.SerialOptions` 设置实际 COM 端口和 UART 参数。 +5. 运行场景。示例使用 `CardReaderApi.Open()`,并将连接、卡片、错误和断开信息输出到 Unity Console。 +6. 停止场景时示例调用 `CardReaderApi.Close()` 并解除监听。 diff --git a/Samples~/Console Demo/README.zh-CN.md.meta b/Samples~/Console Demo/README.zh-CN.md.meta new file mode 100644 index 0000000..a02a3fb --- /dev/null +++ b/Samples~/Console Demo/README.zh-CN.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2a7c9e4f1b6d4380a5e2c7f9b3d81460 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/Console Demo/Scenes.meta b/Samples~/Console Demo/Scenes.meta new file mode 100644 index 0000000..ae8a6db --- /dev/null +++ b/Samples~/Console Demo/Scenes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3a2787d9312860343befc5d46c9545de +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/Console Demo/Scenes/SampleScene.unity b/Samples~/Console Demo/Scenes/SampleScene.unity new file mode 100644 index 0000000..970dbe8 --- /dev/null +++ b/Samples~/Console Demo/Scenes/SampleScene.unity @@ -0,0 +1,359 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 705507994} + m_IndirectSpecularColor: {r: 0.18028378, g: 0.22571412, b: 0.30692285, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 500 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 500 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 2 + m_PVRDenoiserTypeDirect: 0 + m_PVRDenoiserTypeIndirect: 0 + m_PVRDenoiserTypeAO: 0 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 0 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &705507993 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 705507995} + - component: {fileID: 705507994} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &705507994 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 705507993} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 1 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &705507995 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 705507993} + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1 &963194225 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 963194228} + - component: {fileID: 963194227} + - component: {fileID: 963194226} + - component: {fileID: 963194230} + - component: {fileID: 963194229} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &963194226 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 +--- !u!20 &963194227 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &963194228 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &963194229 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8d870f68f8917954ba64b45d81cffc2c, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &963194230 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 963194225} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 23419227fdc74ce8aa78b6eb70b2b722, type: 3} + m_Name: + m_EditorClassIdentifier: + serialOptions: + PortName: + BaudRate: 9600 + Parity: 0 + DataBits: 8 + StopBits: 1 + Handshake: 0 + ReadTimeout: 100 + WriteTimeout: 1000 + RtsEnable: 1 + DtrEnable: 1 + AutoReconnect: 1 + ReconnectInterval: 1000 + MaxPendingSendCount: 256 + openOnStart: 0 + onConnected: + m_PersistentCalls: + m_Calls: [] + onDisconnected: + m_PersistentCalls: + m_Calls: [] + onStateChanged: + m_PersistentCalls: + m_Calls: [] + onCardReceived: + m_PersistentCalls: + m_Calls: [] + onError: + m_PersistentCalls: + m_Calls: [] diff --git a/Samples~/Console Demo/Scenes/SampleScene.unity.meta b/Samples~/Console Demo/Scenes/SampleScene.unity.meta new file mode 100644 index 0000000..b748593 --- /dev/null +++ b/Samples~/Console Demo/Scenes/SampleScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9fc0d4010bbf28b4594072e72b8655ab +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/Console Demo/Scripts.meta b/Samples~/Console Demo/Scripts.meta new file mode 100644 index 0000000..9dfff6e --- /dev/null +++ b/Samples~/Console Demo/Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fe930ff4a01082c46a7763a7047dc4f5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/Console Demo/Scripts/CardReaderConsoleTest.cs b/Samples~/Console Demo/Scripts/CardReaderConsoleTest.cs new file mode 100644 index 0000000..f5dcfd2 --- /dev/null +++ b/Samples~/Console Demo/Scripts/CardReaderConsoleTest.cs @@ -0,0 +1,43 @@ +using Aisu.CardReader; +using UnityEngine; + +public sealed class CardReaderConsoleTest : MonoBehaviour +{ + private void Start() + { + CardReaderApi.OnConnected.AddListener(OnConnected); + CardReaderApi.OnCardReceived.AddListener(OnCardReceived); + CardReaderApi.OnError.AddListener(OnError); + CardReaderApi.OnDisconnected.AddListener(OnDisconnected); + CardReaderApi.Open(); + } + + private static void OnConnected() + { + Debug.Log("On Connected."); + } + + private static void OnCardReceived(CardInfo card) + { + Debug.Log($"{card.CardType} ({card.CardTypeCode:X2}): {card.CardNumber}"); + } + + private static void OnError(CardReaderErrorEventArgs error) + { + Debug.LogError(error.Exception.Message); + } + + private static void OnDisconnected() + { + Debug.Log("On Disconnected."); + } + + private void OnDestroy() + { + CardReaderApi.OnConnected.RemoveListener(OnConnected); + CardReaderApi.OnCardReceived.RemoveListener(OnCardReceived); + CardReaderApi.OnError.RemoveListener(OnError); + CardReaderApi.OnDisconnected.RemoveListener(OnDisconnected); + CardReaderApi.Close(); + } +} diff --git a/Samples~/Console Demo/Scripts/CardReaderConsoleTest.cs.meta b/Samples~/Console Demo/Scripts/CardReaderConsoleTest.cs.meta new file mode 100644 index 0000000..898473a --- /dev/null +++ b/Samples~/Console Demo/Scripts/CardReaderConsoleTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8d870f68f8917954ba64b45d81cffc2c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json new file mode 100644 index 0000000..76336a8 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "xin.aisu.card-reader-830t", + "displayName": "AISU 830T Card Reader SDK", + "version": "0.1.0", + "unity": "2021.3", + "description": "AISU 830T card reader SDK for Unity on Windows.", + "samples": [ + { + "displayName": "Basic", + "description": "Basic Card Reader usage sample.", + "path": "Samples~/Basic" + }, + { + "displayName": "Console Demo", + "description": "Minimal console-style sample demonstrating the Card Reader SDK API.", + "path": "Samples~/Console Demo" + } + ], + "dependencies": { + "com.unity.ugui": "1.0.0" + }, + "author": { + "name": "AISU" + } +} diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 0000000..442c6c5 --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: bae067ad5ccd4f44e893d57f7f231c2a +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: