当前位置: 首页 > news >正文

随笔20250530 C# 整合 IC卡读写技术解析与实现

以下是一个完整、最简化的 FeliCa 读取整合示例(无需 SDK,基于 PCSC NuGet 包),你可以直接运行这个控制台程序,验证能否识别 RC-S300 并读取卡片 UID:


🧪 示例说明

  • 📦 使用 NuGet 包 PCSC

  • 🎯 功能:初始化读卡器,读取插卡的 UID(部分 FeliCa 卡支持)

  • ✅ 仅依赖 Windows 驱动(无需 SDK)


🛠 使用方式

1. 创建控制台项目

dotnet new console -n FelicaReaderExample
cd FelicaReaderExample

2. 安装依赖包

dotnet add package PCSC

 

直接选第一个宝贝!!! 

3. 替换 Program.cs

将下载的 FelicaReaderExample.cs 文件内容,覆盖 Program.cs

或直接复制粘贴内容。

4. 运行程序

插入 FeliCa 卡到 RC-S300,然后运行:

dotnet run

✅ 成功输出示例

找到读卡器:Sony RC-S300
读取成功,卡片 UID: 01-23-45-67-89-AB-CD

FelicaReaderService.cs 

using System;
using System.Linq;
using System.Threading.Tasks;
using PCSC;
using PCSC.Utils;namespace StarMauiPrinter.Services
{/// <summary>/// 提供对 FeliCa 卡(如 ICOCA)的读取功能,仅获取 UID(IDm)/// </summary>public class FelicaReaderService{/// <summary>/// 读取 FeliCa 卡的唯一识别码(IDm / UID)/// </summary>/// <returns>UID 字符串 或 错误信息</returns>public async Task<string?> ReadCardUidAsync(){try{// 1. 建立与 PC/SC 子系统的连接上下文using var context = ContextFactory.Instance.Establish(SCardScope.System);// 2. 获取所有已连接的智能卡读卡器列表var readerNames = context.GetReaders();if (readerNames == null || readerNames.Length == 0)return "未检测到任何读卡器。";var readerName = readerNames[0];// 3. 使用第一个读卡器进行连接using var reader = new SCardReader(context);var result = reader.Connect(readerName, SCardShareMode.Shared, SCardProtocol.Any);if (result != SCardError.Success)return $"连接失败: {SCardHelper.StringifyError(result)}";// 4. 构造 APDU 指令读取 UID(IDm)// 标准 PC/SC 指令: FF CA 00 00 00var command = new byte[] { 0xFF, 0xCA, 0x00, 0x00, 0x00 };var receivePci = new SCardPCI();var sendPci = SCardPCI.GetPci(reader.ActiveProtocol);var receiveBuffer = new byte[256];// 5. 发送指令并接收响应var transmitResult = reader.Transmit(sendPci,        // 协议控制结构command,        // 发送的指令receivePci,     // 接收的控制结构ref receiveBuffer // 输出缓冲区);if (transmitResult != SCardError.Success)return $"发送失败: {SCardHelper.StringifyError(transmitResult)}";// 6. 解析返回的 UID(通常为 8 字节的 IDm)var uid = receiveBuffer.TakeWhile(b => b != 0x00).ToArray();if (uid.Length == 0)return "未读取到卡片 UID";return $"UID: {BitConverter.ToString(uid)}";}catch (Exception ex){return $"异常: {ex.Message}";}}}
}

 FelicaReaderTest.razor

@page "/felicareadertest"
@inject StarMauiPrinter.Services.FelicaReaderService FelicaReaderService<h3>ICOCA UID を取得する</h3><button class="btn btn-primary" @onclick="ReadUid">取得</button>
<p>@uid</p>@code {private string? uid;private async Task ReadUid(){try{uid = await FelicaReaderService.ReadCardUidAsync();}catch (Exception ex){uid = $"読み取りに失敗しました: {ex.Message}";}}
}
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
using PCSC;
using PCSC.Utils;namespace StarMauiPrinter.Services
{/// <summary>/// Felica读卡器服务类,用于读取ICOCA等Felica格式的IC卡信息/// </summary>public class FelicaReaderService{#region 常量定义/// <summary>/// 获取UID的APDU命令/// </summary>private static readonly byte[] GET_UID_COMMAND = { 0xFF, 0xCA, 0x00, 0x00, 0x00 };/// <summary>/// 接收缓冲区大小/// </summary>private const int RECEIVE_BUFFER_SIZE = 256;/// <summary>/// 最大重试次数/// </summary>private const int MAX_RETRY_COUNT = 3;/// <summary>/// 重试延迟基数(毫秒)/// </summary>private const int RETRY_DELAY_BASE = 500;#endregion#region 公共方法/// <summary>/// 异步读取卡片UID(带重试机制)/// </summary>/// <returns>返回UID字符串,如果失败则返回错误信息</returns>public async Task<string> ReadCardUidAsync(){return await ExecuteWithRetryAsync(async () =>{// 方案1:直接创建SCardContext实例using var context = new SCardContext();context.Establish(SCardScope.System);// 获取可用读卡器var readerName = GetFirstAvailableReader(context);// 连接读卡器并读取UIDusing var reader = new SCardReader(context);ConnectToCard(reader, readerName);var uid = ReadCardUid(reader);return FormatUid(uid);});}/// <summary>/// 简化版:异步读取ICOCA卡的详细信息/// </summary>/// <returns>返回包含UID、余额和基本信息的字符串</returns>public async Task<string> ReadIcocaDetailsAsync(){return await ExecuteWithRetryAsync(async () =>{using var context = new SCardContext();context.Establish(SCardScope.System);var readerName = GetFirstAvailableReader(context);using var reader = new SCardReader(context);ConnectToCard(reader, readerName);// 读取基本UID信息var uid = ReadCardUid(reader);var uidString = FormatUid(uid);// 尝试读取余额var balance = TryReadBalance(reader);// 尝试读取最近一次交易var lastTransaction = TryReadLastTransaction(reader);return $"ICOCA卡信息:\n" +$"UID: {uidString}\n" +$"余额: {balance}\n" +$"最近交易: {lastTransaction}";});}/// <summary>/// 尝试读取余额(修复版)/// </summary>private string TryReadBalance(SCardReader reader){try{// ICOCA余额读取命令var command = new byte[] { 0xFF, 0xCA, 0x00, 0x01, 0x04, 0x8B, 0x00, 0x83, 0x00 };var response = new byte[64];// 修复:正确使用ref参数var result = reader.Transmit(command, ref response);// 检查命令是否成功执行if (result == SCardError.Success){// 检查响应长度var actualLength = response.Length;if (actualLength >= 4){// 正确解析余额数据var balance = (response[1] << 8) | response[0];  // Little-endianif (balance > 0 && balance < 50000){return $"¥{balance}";}// 尝试其他可能的位置for (int i = 0; i < actualLength - 1; i++){var testBalance = (response[i + 1] << 8) | response[i];if (testBalance > 0 && testBalance < 50000){return $"¥{testBalance}";}}}}else{return $"命令失败: {result}";}}catch (Exception ex){return $"读取失败: {ex.Message}";}return "无法读取";}/// <summary>/// 尝试读取最近一次交易(修复版)/// </summary>private string TryReadLastTransaction(SCardReader reader){try{// 交易记录读取命令var command = new byte[] { 0xFF, 0xCA, 0x00, 0x02, 0x04, 0x8C, 0x00, 0x80, 0x00 };var response = new byte[64];// 修复:正确使用ref参数var result = reader.Transmit(command, ref response);if (result == SCardError.Success && response.Length >= 8){var amount = (response[5] << 8) | response[4];var type = response[0] == 0x05 ? "乘车" : "其他";if (amount > 0 && amount < 10000) // 合理的交易金额范围{return $"{type} ¥{amount}";}}}catch (Exception){// 忽略错误}return "无记录";}/// <summary>/// 检查读卡器状态/// </summary>/// <returns>返回读卡器状态信息</returns>public async Task<string> CheckReaderStatusAsync(){return await Task.Run(() =>{try{// 修复:直接创建SCardContext实例using var context = new SCardContext();context.Establish(SCardScope.System);var readerNames = context.GetReaders();if (readerNames.Length == 0)return "状态: 未检测到任何读卡器";var statusInfo = new List<string>();foreach (var readerName in readerNames){try{// 检查读卡器中是否有卡var cardStatus = CheckCardPresence(context, readerName);statusInfo.Add($"读卡器: {readerName} - 状态: {cardStatus}");}catch (Exception ex){statusInfo.Add($"读卡器: {readerName} - 状态: 检查失败 ({ex.Message})");}}return string.Join("\n", statusInfo);}catch (Exception ex){return $"检查状态时发生异常: {ex.Message}";}});}/// <summary>/// 测试读卡器连接/// </summary>/// <returns>连接测试结果</returns>public async Task<string> TestReaderConnectionAsync(){return await Task.Run(() =>{try{using var context = ContextFactory.Instance.Establish(SCardScope.System);var readerNames = context.GetReaders();if (readerNames.Length == 0)return "测试失败: 未检测到任何读卡器";var readerName = readerNames[0];using var reader = new SCardReader(context);var result = reader.Connect(readerName, SCardShareMode.Shared, SCardProtocol.Any);if (result == SCardError.Success){var protocol = reader.ActiveProtocol;return $"连接测试成功!\n读卡器: {readerName}\n协议: {protocol}";}else{return $"连接测试失败: {SCardHelper.StringifyError(result)}";}}catch (Exception ex){return $"连接测试异常: {ex.Message}";}});}#endregion#region 私有方法/// <summary>/// 获取第一个可用的读卡器/// </summary>/// <param name="context">PC/SC上下文</param>/// <returns>读卡器名称</returns>/// <exception cref="InvalidOperationException">当没有可用读卡器时抛出</exception>private string GetFirstAvailableReader(SCardContext context){var readerNames = context.GetReaders();if (readerNames.Length == 0)throw new InvalidOperationException("未检测到任何读卡器,请确认读卡器已正确连接");return readerNames[0];}/// <summary>/// 连接到卡片/// </summary>/// <param name="reader">读卡器实例</param>/// <param name="readerName">读卡器名称</param>/// <exception cref="InvalidOperationException">当连接失败时抛出</exception>private void ConnectToCard(SCardReader reader, string readerName){var result = reader.Connect(readerName, SCardShareMode.Shared, SCardProtocol.Any);if (result != SCardError.Success){throw new InvalidOperationException($"连接到读卡器失败: {SCardHelper.StringifyError(result)}。" +"请确认卡片已正确放置在读卡器上");}}/// <summary>/// 读取卡片UID/// </summary>/// <param name="reader">读卡器实例</param>/// <returns>UID字节数组</returns>/// <exception cref="InvalidOperationException">当读取失败时抛出</exception>private byte[] ReadCardUid(SCardReader reader){var receivePci = new SCardPCI();var sendPci = SCardPCI.GetPci(reader.ActiveProtocol);var receiveBuffer = new byte[RECEIVE_BUFFER_SIZE];var transmitResult = reader.Transmit(sendPci,GET_UID_COMMAND,receivePci,ref receiveBuffer);if (transmitResult != SCardError.Success){throw new InvalidOperationException($"发送UID读取命令失败: {SCardHelper.StringifyError(transmitResult)}");}// 提取有效的UID数据(去除填充的0x00)var uid = receiveBuffer.TakeWhile(b => b != 0x00).ToArray();if (uid.Length == 0 || uid.Length < 4){throw new InvalidOperationException("读取到的UID无效或长度不足");}return uid;}/// <summary>/// 尝试读取额外的卡片信息/// </summary>/// <param name="reader">读卡器实例</param>/// <returns>额外信息字符串</returns>private string TryReadAdditionalCardInfo(SCardReader reader){try{// 尝试读取更多信息的示例命令var infoCommand = new byte[] { 0xFF, 0xB0, 0x00, 0x00, 0x10 };var receivePci = new SCardPCI();var sendPci = SCardPCI.GetPci(reader.ActiveProtocol);var receiveBuffer = new byte[RECEIVE_BUFFER_SIZE];var result = reader.Transmit(sendPci,infoCommand,receivePci,ref receiveBuffer);if (result == SCardError.Success){var responseData = receiveBuffer.TakeWhile(b => b != 0x00).ToArray();if (responseData.Length > 0){return $"附加信息: {BitConverter.ToString(responseData)}";}}return "附加信息: 无法读取或不支持";}catch (Exception ex){return $"附加信息: 读取异常 ({ex.Message})";}}/// <summary>/// 检查指定读卡器中是否有卡片/// </summary>/// <param name="context">PC/SC上下文</param>/// <param name="readerName">读卡器名称</param>/// <returns>卡片状态描述</returns>private string CheckCardPresence(SCardContext context, string readerName){try{using var reader = new SCardReader(context);var connectResult = reader.Connect(readerName, SCardShareMode.Shared, SCardProtocol.Any);if (connectResult == SCardError.Success){return "有卡 (已连接)";}else if (connectResult == SCardError.NoSmartcard){return "无卡";}else{return $"未知状态 ({SCardHelper.StringifyError(connectResult)})";}}catch (Exception){return "检查失败";}}/// <summary>/// 格式化UID为可读字符串/// </summary>/// <param name="uid">UID字节数组</param>/// <returns>格式化的UID字符串</returns>private string FormatUid(byte[] uid){if (uid == null || uid.Length == 0)return "UID: 无效";var uidString = BitConverter.ToString(uid).Replace("-", ":");return $"UID: {uidString} (长度: {uid.Length} 字节)";}/// <summary>/// 执行带重试机制的异步操作/// </summary>/// <param name="operation">要执行的操作</param>/// <returns>操作结果</returns>private async Task<string> ExecuteWithRetryAsync(Func<Task<string>> operation){Exception lastException = null;for (int attempt = 1; attempt <= MAX_RETRY_COUNT; attempt++){try{return await operation();}catch (Exception ex){lastException = ex;if (attempt == MAX_RETRY_COUNT)break;// 在重试之间添加递增延迟await Task.Delay(RETRY_DELAY_BASE * attempt);}}// 如果所有重试都失败,返回错误信息return $"操作失败 (重试 {MAX_RETRY_COUNT} 次): {lastException?.Message ?? "未知错误"}";}#endregion#region 资源清理/// <summary>/// 清理资源/// </summary>public void Dispose(){// 强制垃圾回收,释放未管理资源GC.Collect();}#endregion}#region 扩展类和数据结构/// <summary>/// ICOCA卡信息结构/// </summary>public class IcocaCardInfo{public string Uid { get; set; } = string.Empty;public int Balance { get; set; }public DateTime LastUsed { get; set; }public List<TransactionRecord> History { get; set; } = new List<TransactionRecord>();}/// <summary>/// 交易记录结构/// </summary>public class TransactionRecord{public DateTime Date { get; set; }public string Type { get; set; } = string.Empty;public int Amount { get; set; }public string Station { get; set; } = string.Empty;}#endregion
}

 


using System;
using System.Linq;
using System.Threading.Tasks;
using PCSC;
using PCSC.Utils;namespace StarMauiPrinter.Services
{public class FelicaReaderService{/// <summary>/// 获取卡的 IDm(UID)、System Code,并根据 IDm 前缀推断卡类型/// </summary>public async Task<string?> GetCardInfoAsync(){try{using var context = ContextFactory.Instance.Establish(SCardScope.System);var readers = context.GetReaders();if (readers == null || readers.Length == 0)return "未检测到读卡器";var readerName = readers[0];using var reader = new SCardReader(context);var conn = reader.Connect(readerName, SCardShareMode.Shared, SCardProtocol.Any);if (conn != SCardError.Success)return $"连接失败: {SCardHelper.StringifyError(conn)}";var sendPci = SCardPCI.GetPci(reader.ActiveProtocol);var recvPci = new SCardPCI();var buffer = new byte[256];// 发送 Polling 命令以获取卡信息var polling = new byte[] {0xFF, 0x00, 0x00, 0x00, 0x04,0xD4, 0x04, 0x00, 0x01};var result = reader.Transmit(sendPci, polling, recvPci, ref buffer);if (result != SCardError.Success)return $"轮询失败: {SCardHelper.StringifyError(result)}";var response = buffer.TakeWhile(b => b != 0x00).ToArray();if (response.Length < 18)return "响应数据不足,无法解析卡片信息";var idm = response.Skip(9).Take(8).ToArray();var idmStr = BitConverter.ToString(idm);var idPrefix = $"{idm[0]:X2}-{idm[1]:X2}";var systemCode = response.Length >= 27 ? response.Skip(25).Take(2).ToArray() : new byte[] { 0x00, 0x00 };var sysCodeStr = $"{systemCode[0]:X2}{systemCode[1]:X2}";// 根据 IDm 前缀 和 System Code 判断卡种string cardType = sysCodeStr switch{"0003" => idPrefix switch{"01-01" or "02-01" => "Suica","03-01" => "PASMO","07-01" or "07-02" => "ICOCA",_ => "未知交通卡"},"FE00" or "FE01" => "Edy","8B01" => "WAON",_ => $"未知卡片(SystemCode: {sysCodeStr}, IDm前缀: {idPrefix})"};return $"卡类型: {cardType}\nIDm: {idmStr}\nSystem Code: {sysCodeStr}";}catch (Exception ex){return $"异常: {ex.Message}";}}}
}

相关文章:

  • 《java创世手记》---java基础篇(上)
  • Paraformer语音模型:一种语音模型加速方法
  • π0-FAST-针对VLA模型的高效动作token化技术-2025.1.16-开源
  • MySQL + CloudCanal + Iceberg + StarRocks 构建全栈数据服务
  • 【Netty系列】核心概念
  • 如何从ISO镜像直接制作Docker容器基础镜像
  • 怎么在window上打开ubuntu虚拟机?
  • 深度学习复习笔记
  • 循环神经网络(RNN):为什么它能处理时序数据?它真的能减轻过拟合吗?
  • 文字转图片的字符画生成工具
  • 打卡day41
  • 【Ant Design】解决树形组件面板收起问题
  • C++题解(34) 2025年顺德区中小学生程序设计展示活动(初中组C++)U560289 字符串排序(一)和 U560136 字符串排(二)题解
  • 现代密码学 | 高级加密标准(AES)
  • Syslog 全面介绍及在 C 语言中的应用
  • 主流电商平台的反爬机制解析
  • Idea使用springAI搭建MCP项目
  • 使用摄像头推流+VLC软件拉流
  • 跟我学c++中级篇——动态库的资源处理
  • 气体放电管(GDT)选型时需要注意的事项
  • 做网站建设哪家便宜/成都网络运营推广
  • 营销型高端网站建设价格/小红书笔记关键词排名优化
  • 互利互通网站建设/成都网站建设软件
  • angularjs 做团购网站/兴安盟新百度县seo快速排名
  • 伴奏网站防盗是怎么做的/百度识图搜索图片来源
  • 让别人做的网站不给源代码/dw软件怎么制作网页