読者です 読者をやめる 読者になる 読者になる

熊小屋日誌

Windows 10 UWPやXamarin, Python、mbed/NetMF/Arduino/Edison, Azureなどぼちぼちと。たまにPCや勉強会、セミナーなどの話題も

Windows 10 MobileでFelicaカードの読み取り~Edy・Suica編

Windows 10 Mobile NFC Felica

この記事は、Windows Phone / Windows 10 Mobile Advent Calendar 2015の3日目の記事です。

www.adventar.org

はじめに

11月30日にアクセサリーメーカのトリニティから、Windows 10 Mobileを搭載した端末のNuAns NEOが発表されて、同日に予約を開始しました。

検証中ながらもContinuumに対応予定など、これまでのWindows 10 Mobileから一歩進んだスペックが注目を浴びました。 特に個人的に気になったのがNFCの搭載です。海外ではLumiaシリーズなどでNFCを搭載したWindows Phone端末が発売されてきましたが、国内ではNuAns NEOが初めてNFCを搭載した端末となります。

この記事では、NFC搭載のWindows 10 Mobile端末でNFCタグ、特に日本でよく利用されているFelicaカードの読み方を紹介します。

WP8.1 / W10MでのNFCカード読み取り

Windows Phone 8ではProximity APIでNDEFをはじめ一定形式のデータ形式の読み書きがサポートされました。

Windows Phone 8.1からは、NXP PN547などの特定のNFCコントローラを搭載した端末において Windows.Devices.SmartCards名前空間のSmartCardConnectionクラスを使って、NFCカードとAPDU(Application Protocol Data Unit:コマンドとレスポンスで構成されるアプリケーションデータの単位)をやり取りできるようになっています。

特にWindows 10 Mobileからは、コマンドをNFCタグに直接送信できるようになりました。これにより、8.1までの方法では読み取りでエラーとなっていた一部のFelicaカード(特にEdy)からデータを読み取ることができるようになっています。

対応端末

Edy・SuicaなどFelicaカードからデータを読むためには、NXP PN547などの特定のNFCコントローラを搭載したWindows Phone 8.1端末が必要と書きましたが、端末が搭載するNFCコントローラの情報をどこで得られるのかまだ調べ切れていません。

ただし、NFC関連資料を読むと以下のLumiaは対応しているようです。私はLumia 830で動作を確認しています。

  • Lumia 730
  • Lumia 830
  • Lumia 640
  • Lumia 640 XML

f:id:kumar:20151202063954p:plain

Windows 10 MobileでのFelicaカードの読み取り方法

NFC搭載Windows 10 Mobile端末でのEdy/Suica読み取りのサンプルコードをGitHubのNFCFelicaReadSampleリポジトリ https://github.com/nobukuma/NFCFelicaReadSample.gitで公開しました。

このサンプルコードを使って、以下の処理について簡単に説明します。

  1. Felicaカードの検知・接続

  2. FelicaカードのPolling・IDm取得

  3. Felicaカードのデータ読み取り

NFC Smart Card Reader PC/SC Library

対応端末でNFCタグを読み取るとき、Windows.Devices.SmartCards名前空間のSmartCardConnectionクラスを使って、 コマンドAPDUを送信します。

このとき便利なのがNFC Smart Card Reader PC/SC Libraryです。

nfcsmartcardreader.codeplex.com

これは、Windows Phone 8.1からNFCタグにrawアクセスする方法を説明するライブラリとサンプルアプリで、 Apache License 2.0で配布されています。。

Windows 10からは、GitHubのMicrosoft/Windows-universal-samplesリポジトリに含まれる サンプル"Near field communication (NFC) sample"にPcscSdkプロジェクトとして含まれています。 こちらはMITライセンスです。

PcscSdkには、以下の処理が含まれています。

  • Mifare, FelicaなどNFCカード毎に、データの読み書き、コマンドの直接送受信を行うクラス(xxxAccessHandler)
  • Mifare, FelicaなどNFCカード毎に、データの読み書きなどを指示するコマンドARDPを作成するクラス(xxxCommand)
  • NFCタグ検出処理のユーティリティクラス

GitHubからNFCサンプルのソリューションを取得したら、PcscSdkプロジェクトを自分のプロジェクトにコピーして登録します。 また、PcscSdkプロジェクトへの参照を追加して、Package.appxmanifestの機能タブで近接通信にチェックを入れます。

f:id:kumar:20151202195803p:plain

Felicaカードの検知・接続

まず初めに、NFCタグリーダを探して、NFCタグの接近・離脱のイベントハンドラを設定します。

var deviceInfo = await SmartCardReaderUtils.GetFirstSmartCardReaderInfo(SmartCardReaderKind.Nfc);
if (deviceInfo == null)
{
    deviceInfo = await SmartCardReaderUtils.GetFirstSmartCardReaderInfo(SmartCardReaderKind.Any);
}

if (deviceInfo == null)
{
    await LogMessageAsync("NFCカードリーダがサポートされていません");
    return;
}

cardReader = await SmartCardReader.FromIdAsync(deviceInfo.Id);

cardReader.CardAdded += cardReader_CardAdded;
cardReader.CardRemoved += cardReader_CardRemoved;

カード接近のイベントハンドラでは、イベント引数から当該NFCタグにアクセスできるので、 ConnectAsyncメソッドで接続して、SmartCardConnectionオブジェクトを取得します。

その後、IccDetectionクラスを使って、当該NFCタグの種類(PC/SCデバイスクラス、PC/SCカード名)に 応じた処理を行います。

private async void cardReader_CardAdded(SmartCardReader sender, CardAddedEventArgs args)
{
    SmartCard smartCard = args.SmartCard;

    using (SmartCardConnection connection = await smartCard.ConnectAsync())
    {
        IccDetection cardIdentification = new IccDetection(smartCard, connection);
        await cardIdentification.DetectCardTypeAync();

        if (cardIdentification.PcscDeviceClass == Pcsc.Common.DeviceClass.StorageClass
            && cardIdentification.PcscCardName == CardName.FeliCa)
        {
            // Felicaカードの処理

上記のコードのように、Felicaカードを検出した場合、Storageクラスのカード名CardName.FeliCaで 判別することができます。

FelicaカードのPolling・IDm取得

PcscSDKのFelica.AccessHandlerクラスにSmartCardConnectionオブジェクトを指定して、 AccessHandlerオブジェクトを作成します。そのTransparentExchangeAsyncメソッドを使って、 FelicaのPollingコマンドを送信します。

TransparentExchangeAsyncメソッドは、引数に渡されたFelicaコマンドをデータとして そのまま持つTransparentExchangeコマンドのパケットを作成して、Felicaカードに送信します。

(※) FelicaのPollingコマンドの構造は、 Felica技術情報の 「FeliCaカード ユーザーズマニュアル 抜粋版」の"4.4.2 Polling"に記載されているので参考にしてください。

(※) TransparentExchangeコマンドのパケットは、PC/SC Workgroupから 入手できるドキュメント"Interoperability Specification for ICCs and Personal Computer Systems Part 3. Supplemental Document for Contactless ICCs"の"5 Transparent Exchange Command"章に記載されています。

Pollingに指定するシステムコードは、Felicaカードにあるシステムごとに異なります。 Edyなど共通領域を使うカードは0xFE00、Suicaなどのサイバネ規格ICカード乗車券は0x0003です。 システムコードは以下のサイトにまとめられています。

felicaAccess = new Felica.AccessHandler(connection);

byte systemCodeHigher = (byte)(systemCode >> 8);
byte systemCodeLower = (byte)(systemCode & 0x00ff);

byte[] commandData = new byte[] {
   0x00, 0x00, systemCodeHigher, systemCodeLower, 0x01, 0x0f,
};
commandData[0] = (byte)commandData.Length;

byte[] result = await felicaAccess.TransparentExchangeAsync(commandData);

byte[] idm = new byte[8];
Array.Copy(result, 2, idm, 0, idm.Length);

return idm;

(サンプルコードではFelicaReaderを継承したEDYReaderクラスで実装)

応答データの2バイト目から8バイト長のIDm(システムを識別する製造ID)を取得できます。 これは他のデータを読むときに指定するため、保存しておきます。

Felicaカードのデータ読み取り

PollingをしてFelicaカードのIDmを取得したら、FelicaコマンドのReadWithoutEncryptionで 必要なデータを読み取ります。ReadWithoutEncryptionでは、指定されたサービスコード、 ブロック番号リストを使ってコマンドパケットを作成・送信したのち、レスポンスのパケットを解析します。

public async Task<ReadWithoutEncryptionResponse> ReadWithoutEncryption(
    byte[] idm,
    UInt16 serviceCode,
    byte blockNumber,
    byte[] blockList)
{
    byte serviceCodeHigher = (byte)(serviceCode >> 8);
    byte serviceCodeLower = (byte)(serviceCode & 0x00ff);

    byte[] commandDataPrefix = new byte[] {
        0x00,
        0x06,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x01,
        serviceCodeLower, serviceCodeHigher,
        blockNumber,
        // block list
    };

    for (int i = 0; i < idm.Length; i++)
    {
        commandDataPrefix[i + 2] = idm[i];
    }

    byte[] commandData = new byte[commandDataPrefix.Length + blockList.Length];
    Array.Copy(commandDataPrefix, commandData, commandDataPrefix.Length);
    Array.Copy(blockList, 0, commandData, commandDataPrefix.Length, blockList.Length);
    commandData[0] = (byte)commandData.Length;

    byte[] result = await felicaAccess.TransparentExchangeAsync(commandData);
    return ReadWithoutEncryptionResponse.ParsePackage(result);
}

例えばEdyの利用履歴(サービスコード=0x170f)を1件取得する場合、ReadWithoutEncryptionの引数に指定する値は以下の通りです。

  • サービスコード = 0x170f

  • ブロック件数 = 1

  • ブロックリスト = 0x80 0x00

public async Task<UsageHistory> GetUsageHistory(byte[] idm)
{
    ReadWithoutEncryptionResponse result = await base.ReadWithoutEncryption(idm, 0x170f, 0x01, new byte[] { 0x80, 0x00, });
    return UsageHistory.GetUsageHistory(result.BlockData);
}

レスポンスの解析は、システムコードでも紹介した下記サイトにパケットの構造がまとめられているので、 それに従ってバイト配列からデータを取得します。

ICカードのフォーマット解析

実行例

サンプルコードでEdy、Suicaカードを読み込んだときの画面表示です。 時間の都合でEdyの利用履歴だけ各項目を解析して値を表示していますが、 それ以外のサービスはレスポンスのパケットデータをそのままダンプしています。

f:id:kumar:20151203000625p:plain,w250 f:id:kumar:20151203000633p:plain,w250

まとめ

Windows 10 Mobileになって、端末への制約があるものの、Felicaカードに Felicaコマンドを直接送信することで、支払い記録・乗車記録などのデータを 読み取ることができるようになりました。

Windows 10 Mobileには、NFCタグを活用したアプリがまだまだ少ない状況です。 今後は、Edyの支払い内容の管理アプリや、Suica等の乗車記録から交通費をまとめるアプリなど、 NFC対応アプリが増えるのではないかと期待しています。Pollingで得られるIDmを使って、 勉強会などの出席管理システムも作れそうです。

そのようなNFC対応アプリの開発にあたり、この記事がとっかかりになればと思います。