Flutter智能卡读写插件flutter_pcsc的使用

发布于 1周前 作者 phonegap100 来自 Flutter

Flutter智能卡读写插件flutter_pcsc的使用

flutter_pcsc 是一个用于在 Windows、macOS 和 Linux 上使用 PCSC 智能卡读卡器的 Flutter 插件。本文将介绍如何使用这个插件,并提供一个完整的示例 demo。

使用说明

前提条件

  • 一个 PCSC 智能卡读卡器。
  • 在 Linux 系统上,需要安装 pcscdlibpcsclite1

示例代码

以下是一个简单的示例,展示了如何使用 flutter_pcsc 插件与智能卡进行通信:

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter_pcsc/flutter_pcsc.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Plugin example app'),
        ),
        body: SmartCardReader(),
      ),
    );
  }
}

class SmartCardReader extends StatefulWidget {
  [@override](/user/override)
  _SmartCardReaderState createState() => _SmartCardReaderState();
}

class _SmartCardReaderState extends State<SmartCardReader> {
  List<String> messages = [];

  [@override](/user/override)
  void initState() {
    super.initState();
    readCard();
  }

  Future<void> readCard() async {
    int ctx;
    CardStruct card;
    try {
      ctx = await Pcsc.establishContext(PcscSCope.user);
      List<String> readers = await Pcsc.listReaders(ctx);

      if (readers.isEmpty) {
        setState(() {
          messages.add('Could not detect any reader');
        });
      } else {
        String reader = readers[0];
        setState(() {
          messages.add('Using reader: $reader');
        });

        card = await Pcsc.cardConnect(ctx, reader, PcscShare.shared, PcscProtocol.any);
        var response = await Pcsc.transmit(card, [0xFF, 0xCA, 0x00, 0x00, 0x00]);
        setState(() {
          messages.add('Response: ${response.toString()}');
        });

        await Pcsc.cardDisconnect(card.hCard, PcscDisposition.resetCard);
      }
    } catch (e) {
      setState(() {
        messages.add('Error: $e');
      });
    } finally {
      await Pcsc.releaseContext(ctx);
    }
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: messages.length,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(messages[index]),
        );
      },
    );
  }
}

macOS 应用注意事项

对于 macOS 应用程序,为了能够使用智能卡,需要在 DebugProfile.entitlementsRelease.entitlements 文件中设置以下权限:

<key>com.apple.security.smartcard</key>
<true/>

如果不正确设置,上下文将无法建立。

完整示例 Demo

以下是一个更完整的示例,包含错误处理和用户界面更新:

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter_pcsc/flutter_pcsc.dart';

enum MessageType { info, error }

class Message {
  final String content;
  final MessageType type;

  Message(this.type, this.content);

  static info(String content) => Message(MessageType.info, content);
  static error(String content) => Message(MessageType.error, content);
}

class MyAppBody extends StatefulWidget {
  const MyAppBody({Key? key}) : super(key: key);

  [@override](/user/override)
  _MyAppBodyState createState() => _MyAppBodyState();
}

class _MyAppBodyState extends State<MyAppBody> {
  static const List<int> getCardSerialNumberCommand = [0xFF, 0xCA, 0x00, 0x00, 0x00];
  final ScrollController _scrollController = ScrollController();
  final List<Message> messages = [];

  [@override](/user/override)
  void initState() {
    super.initState();
    getCardSerialNumber();
  }

  Future<void> getCardSerialNumber() async {
    int ctx;
    CardStruct? card;
    try {
      ctx = await Pcsc.establishContext(PcscSCope.user);
      List<String> readers = await Pcsc.listReaders(ctx);

      if (readers.isEmpty) {
        messages.add(Message.error('Could not detect any reader'));
      } else {
        String reader = readers[0];
        setState(() {
          messages.add(Message.info('Using reader: $reader'));
        });

        card = await Pcsc.cardConnect(ctx, reader, PcscShare.shared, PcscProtocol.any);
        var response = await Pcsc.transmit(card, getCardSerialNumberCommand);
        var sw = response.sublist(response.length - 2);
        var sn = response.sublist(0, response.length - 2);

        if (sw[0] != 0x90 || sw[1] != 0x00) {
          setState(() {
            messages.add(Message.error('Card returned an error: ${hexDump(sw)}'));
          });
        } else {
          setState(() {
            messages.add(Message.info('Card Serial Number is: ${hexDump(sn)}'));
            messages.add(Message.info('Done'));
          });
        }
      }
    } catch (e) {
      messages.add(Message.error(e.toString()));
    } finally {
      if (card != null) {
        try {
          await Pcsc.cardDisconnect(card.hCard, PcscDisposition.resetCard);
        } catch (e) {
          messages.add(Message.error(e.toString()));
        }
      }
      try {
        await Pcsc.releaseContext(ctx);
      } catch (e) {
        messages.add(Message.error(e.toString()));
      }
    }
  }

  static String hexDump(List<int> csn) {
    return csn.map((i) => i.toRadixString(16).padLeft(2, '0').toUpperCase()).join(' ');
  }

  void _scrollToBottom() {
    _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    TextStyle errorStyle = const TextStyle(color: Colors.red);
    WidgetsBinding.instance?.addPostFrameCallback((_) => _scrollToBottom());
    return Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
      Expanded(
        child: Column(children: [
          Expanded(
            child: ListView(
              controller: _scrollController,
              children: messages.map((e) => Text(e.content, style: e.type == MessageType.error ? errorStyle : null)).toList(),
            ),
          ),
          Container(
            margin: const EdgeInsets.all(10),
            child: ElevatedButton(
              onPressed: () async {
                messages.clear();
                await getCardSerialNumber();
              },
              child: const Text("Try again"),
            ),
          )
        ]),
      )
    ]);
  }
}

void main() {
  runApp(MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: const Text('Plugin example app'),
      ),
      body: MyAppBody(),
    ),
  ));
}

更多关于Flutter智能卡读写插件flutter_pcsc的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter智能卡读写插件flutter_pcsc的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用flutter_pcsc插件进行智能卡读写的示例代码。这个插件允许你与智能卡(如RFID卡、SIM卡等)进行通信。在使用这个插件之前,请确保你的开发环境已经配置好Flutter,并且你已经在pubspec.yaml文件中添加了flutter_pcsc依赖。

1. 添加依赖

首先,在你的pubspec.yaml文件中添加flutter_pcsc依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_pcsc: ^x.y.z  # 替换为最新版本号

然后运行flutter pub get来获取依赖。

2. 配置权限(Android)

android/app/src/main/AndroidManifest.xml中添加必要的权限:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.yourapp">

    <uses-permission android:name="android.permission.NFC" />
    <!-- 其他必要的权限 -->

    <application
        ... >
        ...
    </application>
</manifest>

注意:NFC权限可能不是必需的,具体取决于你的智能卡读写需求。

3. 使用flutter_pcsc插件

下面是一个简单的Flutter应用示例,展示了如何使用flutter_pcsc插件来列出可用的智能卡读取器并与智能卡通信。

import 'package:flutter/material.dart';
import 'package:flutter_pcsc/flutter_pcsc.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  List<SCardReader> _readers = [];
  String _cardData = '';

  @override
  void initState() {
    super.initState();
    _listReaders();
  }

  Future<void> _listReaders() async {
    try {
      var readers = await SCard.listReaders();
      setState(() {
        _readers = readers;
      });
      if (_readers.isNotEmpty) {
        // 连接到第一个可用的读取器
        var reader = _readers.first;
        var context = await SCard.establishContext();
        var handle = await context.connect(reader.name);
        
        // 等待卡片插入
        var atr = await handle.waitForCard();
        print('ATR: $atr');

        // 进行APDU命令(示例:选择MF)
        var commandApdu = Uint8List.fromList([0x00, 0xA4, 0x04, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x04, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x21, 0x00]);
        var responseApdu = await handle.transmit(commandApdu);
        print('Response APDU: ${responseApdu.map((e) => e.toRadixString(16).padLeft(2, '0')).join(' ')}');

        // 断开连接并释放上下文
        await handle.disconnect();
        await context.release();
      }
    } catch (e) {
      print('Error: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter PC/SC Example'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            children: [
              Text('Available Readers:'),
              SizedBox(height: 16),
              Expanded(
                child: ListView.builder(
                  itemCount: _readers.length,
                  itemBuilder: (context, index) {
                    return ListTile(
                      title: Text(_readers[index].name),
                    );
                  },
                ),
              ),
              SizedBox(height: 16),
              Text('Card Data: $_cardData'),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: _listReaders,
          tooltip: 'List Readers',
          child: Icon(Icons.refresh),
        ),
      ),
    );
  }
}

说明

  1. 初始化:在initState中调用_listReaders函数来列出可用的智能卡读取器。
  2. 列出读取器:使用SCard.listReaders()列出所有可用的智能卡读取器。
  3. 连接到读取器:使用SCard.establishContext()建立上下文,并使用context.connect(reader.name)连接到第一个可用的读取器。
  4. 等待卡片插入:使用handle.waitForCard()等待卡片插入,并获取卡片的ATR(Answer To Reset)。
  5. APDU命令:发送APDU命令与卡片通信。这里只是一个示例命令,用于选择MF(主文件)。
  6. 断开连接:使用handle.disconnect()断开与读取器的连接,并使用context.release()释放上下文。

注意:上述代码只是一个基本示例,实际应用中可能需要处理更多的错误情况和异常,并根据具体的智能卡协议发送适当的APDU命令。

回到顶部