従業員の入退室管理のため、300枚はあろうかというMifareカードのUIDを読み取る必要があった。
良さげなフリーソフトが見つからなかったので、自分でコード書いてみたという話。
PCSC-sharpとは
Windows7以降に標準添付されているwinscard.dll
を .NETから簡単に使えるようにするラッパーライブラリ。【入手元】 https://github.com/danm-de/pcsc-sharp
winscard.dll
はスマートカード(ISO7816規格)をWindowsから操作するPC/SCというAPIの実装である。
PCSC-sharpに頼らず自分でDLLを叩きたいんだ!という人は、
@rheneさんの記事 https://qiita.com/rhene/items/725dfe4a6b6307731cbf
がとても参考になると思う。
NFCとは
RFIDと呼ばれる無線通信方式のひとつで、下表の通り複数の規格がある。
規格 | 日本における用途 |
---|---|
Type-A (Mifare) | Taspoなど |
Type-B | マイナンバーカード、住民基本台帳カード、運転免許証など |
Type-F (FeliCa) | Suica、楽天Edy、nanacoなどの電子マネー |
ISO/IEC 15693 | 物流の商品タグなど |
Type-AとType-Bは事実上の世界標準で、NFC Pay(クレジットカードのタッチ決済)にも使われている。
日本ではSuicaの自動改札で通信速度が必要だったという背景からFeliCaが採用され、その他電子マネーもこぞって追随したことからFeliCaが普及した。
入退室管理に使うカードではそこまでの性能は必要ないので、安価なMifareが良く使われている。
FeliCaはSONYが開発したものだが、技術力の高さゆえの過剰性能が災いしガラパゴス化した。
4K、8Kテレビは国内メーカーを助けるための開発ではあるが、同じ道を歩まないことを期待する。
ソフトウェアの作成手順
開発環境
ソフトウェア
- Visual Studio 2019(C#)
- Windows 10 Pro 1903
ハードウェア
- SONY ICカードリーダ/ライタ PaSoRi RC-S380(非接触型)
- DELL ノートパソコン Latitude 7390 内蔵NFCセンサ(非接触型)
- DELL ノートパソコン Latitude 7390 内蔵スマートカードスロット(接触型)
NuGetパッケージ
次のNuGetパッケージをプロジェクトに追加する。
画面構成
C# コード
冒頭にusing
ディレクティブを追加する。
usingPCSC;usingPCSC.Iso7816;usingPCSC.Exceptions;
アプリケーション起動時、接続されているカードリーダの名前をすべてコンボボックスに追加する。
カードリーダがひとつも接続されていなければアプリケーションを終了する。PCSC.Exceptions.NoServiceException
がスローされたら、ドライバがインストールされていないので、やはり終了する。
privatevoidFormMain_Load(objectsender,EventArgse){// リーダーの機器情報を取得try{using(varcontext=ContextFactory.Instance.Establish(SCardScope.System)){varreaderNames=context.GetReaders();if(readerNames==null||readerNames.Length==0){MessageBox.Show("スマートカードリーダが見つかりません","エラー",MessageBoxButtons.OK,MessageBoxIcon.Hand);Application.Exit();// スマートカードリーダが無ければ終了する}foreach(varreaderNameinreaderNames){comboBoxDevice.Items.Add(readerName);// カードリーダの名前をコンボボックスに追加}}}catch(NoServiceException){MessageBox.Show("スマートカードリソースマネージャが稼働していません","エラー",MessageBoxButtons.OK,MessageBoxIcon.Hand);Application.Exit();}comboBoxDevice.SelectedIndex=0;radioButtonNone.Checked=true;// データグリッドビューの初期設定dataGridView.AllowUserToResizeColumns=true;dataGridView.AllowUserToResizeRows=false;dataGridView.RowHeadersVisible=false;dataGridView.ClipboardCopyMode=DataGridViewClipboardCopyMode.EnableWithoutHeaderText;dataGridView.SelectionMode=DataGridViewSelectionMode.FullRowSelect;dataGridView.Columns.Add("seq","連番");dataGridView.Columns.Add("datetime","読み取り日時");dataGridView.Columns.Add("uid","UID/IDm");dataGridView.Columns["seq"].Width=80;dataGridView.Columns["datetime"].Width=200;dataGridView.Columns["uid"].Width=250;}
ボタンがクリックされたら読み取り動作をする。
読み取り用のAPDUコマンドを組み立て、送信し、結果を受信するという手順になる。
pcsc-sharp/Examples/ISO7816-4/Transmit/Program.csを参考にしている。
privatevoidbuttonRead_Click(objectsender,EventArgse){varreaderName=comboBoxDevice.Text;// 選択中のカードリーダ名を得るusing(varcontext=ContextFactory.Instance.Establish(SCardScope.System)){try{using(varrfidReader=context.ConnectReader(readerName,SCardShareMode.Shared,SCardProtocol.Any)){// APDUコマンドの作成varapdu=newCommandApdu(IsoCase.Case2Short,rfidReader.Protocol){CLA=0xFF,Instruction=InstructionCode.GetData,P1=0x00,P2=0x00,Le=0// We don't know the ID tag size};// 読み取りコマンド送信using(rfidReader.Transaction(SCardReaderDisposition.Leave)){varsendPci=SCardPCI.GetPci(rfidReader.Protocol);varreceivePci=newSCardPCI();// IO returned protocol control information.varreceiveBuffer=newbyte[256];varcommand=apdu.ToArray();varbytesReceived=rfidReader.Transmit(sendPci,// Protocol Control Information (T0, T1 or Raw)command,// command APDUcommand.Length,receivePci,// returning Protocol Control InformationreceiveBuffer,receiveBuffer.Length);// data buffervarresponseApdu=newResponseApdu(receiveBuffer,bytesReceived,IsoCase.Case2Short,rfidReader.Protocol);if(responseApdu.HasData){// バイナリ文字列の整形StringBuilderid=newStringBuilder(BitConverter.ToString(responseApdu.GetData()));if(radioButtonNone.Checked){id.Replace("-",string.Empty);}elseif(radioButtonSpace.Checked){id.Replace("-"," ");}// データグリッドビューに結果を追加し、連番をカウントアップするintseq=(int)numericUpDown.Value;DateTimedt=DateTime.Now;dataGridView.Rows.Add(seq,dt.ToString("yyyy/MM/dd HH:mm:ss"),id.ToString());numericUpDown.Value=++seq;}else{MessageBox.Show("このカードではIDを取得できません","エラー",MessageBoxButtons.OK,MessageBoxIcon.Warning);}}}}catch(RemovedCardException){MessageBox.Show("スマートカードが取り外されたため、通信できません","エラー",MessageBoxButtons.OK,MessageBoxIcon.Hand);}catch(PCSCExceptionex){MessageBox.Show(ex.Message,"スマートカードエラー",MessageBoxButtons.OK,MessageBoxIcon.Hand);}}}