3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WinFormsでPaSoRi RC-S300とNTAG 215 NFCカード読み込ませる

3
Posted at

はじめに

PaSoRi RC-S300を確定申告用に購入しました。

ドライバインストールでコケる

PaSoriのインストール失敗問題は結構あるようで、試行錯誤してもうまくいきませんでした。

image.png

このままだと「機器が壊れているのか」「環境(ドライバ/設定)が悪いのか」の切り分けができません。
そこでまずは、RC-S300 本体が PC/SC リーダーとして認識され、カードと通信できるか を最小構成で確認することにしました。

今回は手元にあった NTAG 215(NFCタグ) を使い、C#(WinForms)で UID が取得できるか を確かめるサンプルプログラムを作成します。

UID が読めれば「少なくともリーダーとカード間の基本的な通信はできている」ことが分かり、以降のトラブルシュートの土台になります。

環境

  • Windows 11 Pro
  • Visual Studio 2026
  • C#(.NET 10)
  • NTAG 215 NFCカード
  • PaSoRi RC-S300

実装

まずは Visual Studio で Windows Forms アプリ(.NET) の新規プロジェクトを作成します(.NET 10 を選択)。
PaSoRi RC-S300 は Windows 上では PC/SC(Smart Card) の仕組みで扱えるため、.NET から PC/SC API を呼び出すためのライブラリを NuGet で追加します。

NuGet では以下の 2 つをインストールします。

image.png

【補足】NuGet でインストールする PCSC / PCSC.Iso7816 とは

PCSC(pcsc-sharp)

PCSC は、Windows が標準で持っている PC/SC API(winscard.dll) を .NET から扱いやすくするためのラッパーライブラリです。
PaSoRi のような IC カードリーダーは、基本的に OS 側では スマートカードリーダー として扱われるので、PC/SC 経由で「リーダー一覧の取得」「リーダーへの接続」「カード状態/ATR の取得」「APDU の送受信」といった操作ができます。

今回のコードで PCSC 側が担当しているのはこのあたりです。

  • ContextFactory.Instance.Establish(...)
    → PC/SC リソースマネージャへ接続するための コンテキスト確立
  • context.GetReaders()
    → OS が認識している リーダー名一覧の取得
  • new SCardReader(context) / reader.Connect(...)
    リーダーへ接続
  • reader.Status(...)
    カード状態・プロトコル・ATR の取得
  • reader.Transmit(...)
    APDU の送受信(低レベル)

つまり PCSC は「PaSoRi を Windows のスマートカード基盤(PC/SC)越しに叩くための土台」です。


PCSC.Iso7816

PCSC.Iso7816 は、PCSC の上で ISO/IEC 7816 の APDU(コマンド/レスポンス) を組み立てたり解釈したりするための補助ライブラリです。

APDU は「カードに投げる命令(Command APDU)と、カードから返る応答(Response APDU)」の枠組みで、典型的には応答の末尾に SW1/SW2(ステータスワード) が付きます(例:90 00)。

ソースコード

Formにボタンを設置し、ボタンクリックイベントでNFCカードを読み込むようにしました。

private void button1_Click(object sender, EventArgs e)
{
    try
    {
        // 1) PC/SC のコンテキストを確立
        using var context = ContextFactory.Instance.Establish(SCardScope.System);

        // 2) OS が認識している PC/SC リーダー名の一覧を取得
        var readers = context.GetReaders();
        if (readers == null || readers.Length == 0)
        {
            Debug.WriteLine("PC/SC リーダーが見つかりません。");
            return;
        }

        // 3) RC-S300 を優先して選択(見つからなければ先頭のリーダーを使用)
        var readerName = readers.FirstOrDefault(r => r.Contains("RC-S300")) ?? readers[0];
        Debug.WriteLine($"Reader: {readerName}");

        // 4) リーダー操作用のオブジェクトを生成(コンテキストに紐づく)
        using var reader = new SCardReader(context);

        // 5) リーダーへ接続
        //    - Shared: 他アプリと共有してアクセス(Exclusive にすると占有)
        //    - Any   : プロトコルは自動選択(T=0 / T=1 など)
        var rc = reader.Connect(readerName, SCardShareMode.Shared, SCardProtocol.Any);
        if (rc != SCardError.Success)
        {
            Debug.WriteLine($"Connect failed: {SCardHelper.StringifyError(rc)}");
            return;
        }

        // 6) カードの状態・プロトコル・ATR を取得
        //    ATR(Answer To Reset) はカードが返す識別情報(カード種別の推定などに利用)
        rc = reader.Status(out var _, out var state, out var protocol, out var atr);
        if (rc != SCardError.Success)
        {
            Debug.WriteLine($"Status failed: {SCardHelper.StringifyError(rc)}");
            reader.Disconnect(SCardReaderDisposition.Leave);
            return;
        }

        Debug.WriteLine($"State   : {state}");
        Debug.WriteLine($"Protocol: {protocol}");
        Debug.WriteLine($"ATR     : {BitConverter.ToString(atr ?? Array.Empty<byte>())}");

        // 7) APDU を構築して送信(PC/SC 経由でカードとコマンド交換する)
#
        var apdu = new CommandApdu(IsoCase.Case2Short, protocol)
        {
            CLA = 0xFF,
            INS = 0xCA,
            P1 = 0x00,
            P2 = 0x00,
            Le = 0x00
        };

        // 8) プロトコルに対応する PCI(プロトコル制御情報)を取得して Transmit に渡す
        var sendPci = SCardPCI.GetPci(protocol);
        var receivePci = new SCardPCI();

        // 9) レスポンス格納バッファ(実装によっては固定長で返るため大きめに確保)
        var receiveBuffer = new byte[256];

        // 10) APDU を送信し、レスポンスを受信
        rc = reader.Transmit(sendPci, apdu.ToArray(), receivePci, ref receiveBuffer);
        if (rc != SCardError.Success)
        {
            Debug.WriteLine($"Transmit failed: {SCardHelper.StringifyError(rc)}");
            reader.Disconnect(SCardReaderDisposition.Leave);
            return;
        }

        // 11) ISO7816 ではレスポンス末尾に SW1/SW2(ステータスワード)が付く
        //     90 00 は一般的な成功コードの一つ
        const byte SW1_OK = 0x90;
        const byte SW2_OK = 0x00;

        // 12) 受信バッファから "90 00" の位置を探し、そこまでをデータ部として取り出す
        int swPos = -1;
        for (int i = 0; i <= receiveBuffer.Length - 2; i++)
        {
            if (receiveBuffer[i] == SW1_OK && receiveBuffer[i + 1] == SW2_OK)
            {
                swPos = i;
                break;
            }
        }

        if (swPos > 0)
        {
            // 13) データ部(SW1/SW2 の直前まで)を UID 相当として表示
            var data = receiveBuffer.Take(swPos).ToArray();
            Debug.WriteLine($"UID: {BitConverter.ToString(data)}");
            Debug.WriteLine("SW: 90-00");
        }
        else
        {
            // 14) 成功コードが見つからない場合は生データを出して調査に回す
            Debug.WriteLine($"Response(raw): {BitConverter.ToString(receiveBuffer)}");
        }

        // 15) 切断(Leave: カード状態を維持したまま離脱)
        reader.Disconnect(SCardReaderDisposition.Leave);
    }
    catch (Exception ex)
    {
        // 例外は環境差やドライバ状態でも起きるため、詳細をそのまま出力
        Debug.WriteLine(ex.ToString());
    }
}

結果

ボタンを押してカードをかざすと、Debug 出力に以下のようなログが出ました。

  • Reader: に RC-S300 が表示される(PC/SC からリーダーとして認識されている)
  • ATR が表示される(カードが反応していることの確認になる)
  • UID: が表示される(NTAG215 の UID を取得できた)

つまり 「Windows → PC/SC → RC-S300 → カード」 の経路で通信できていることが確認でき、少なくとも RC-S300 本体は正常に動作していると判断できました。
ドライバインストールでつまずいていても、このように PC/SC 経由で読めるなら、機器側の故障切り分けにも使えます。

なお、UID 取得は FF CA 00 00 00(Get Data - UID)を Transmit で送っています。カード種別やリーダー実装によってはこのコマンドが通らない場合もありますが、今回は NTAG 215 では問題なく UID を取得できました。

おわりに

今回は PaSoRi RC-S300 のドライバインストールでつまずいたため、まずは PC/SC 経由でリーダーとカードが通信できるか を確認する目的で、WinForms の最小サンプルを作成しました。
結果として NTAG 215 の UID を取得できたので、少なくとも RC-S300 本体と Windows 側の PC/SC 経路は動作している ことが確認でき、故障か環境問題かの切り分けが前に進みました。

あとは用途に応じて、読み取り対象を マイナンバーカード(ISO7816 APDU) に切り替えたり、交通系 IC のような FeliCa 系 を扱う場合は別の手順が必要になったりしますが、今回のコードは「まずリーダーが生きているか」を確かめる土台として使えるはずです。

同じようにセットアップで詰まっている方の、切り分けの一助になれば幸いです。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?