【VBA】FelicaのUIDを読み取りExcelに出力する

私の勤務先は職員証がFelicaカードになっています。この職員証を使用し,勤怠打刻や各ゲートの入退管理をしています。
たまたま,Sony製FelicaリーダーのパソリPC-S300が手元に余っていたので,この職員証の情報を記録するような仕組みを作れないと思い試作しました。

目次

コード本体

VBAで作成しています。Windows標準APIを使用するため,新しく何かをインストールする必要がありません。

Option Explicit

' *********************
'  API宣言
' *********************
Private Declare PtrSafe Function SCardEstablishContext Lib "winscard.dll" ( _
        ByVal dwScope As Long, _
        ByVal pvReserved1 As Long, _
        ByVal pvReserved2 As Long, _
        ByRef phContext As LongPtr) As LongPtr

Private Declare PtrSafe Function SCardListReaders Lib "winscard.dll" Alias "SCardListReadersA" ( _
        ByVal hContext As LongPtr, _
        ByVal mzGroup As String, _
        ByVal ReaderList As String, _
        ByRef pcchReaders As Long) As LongPtr

Private Declare PtrSafe Function SCardConnectA Lib "winscard.dll" ( _
        ByVal hContext As LongPtr, _
        ByVal szReaderName As String, _
        ByVal dwShareMode As Long, _
        ByVal dwPrefProtocol As Long, _
        ByRef hCard As LongPtr, _
        ByRef activeProtocol As Long) As LongPtr

Private Declare PtrSafe Function SCardTransmit Lib "winscard.dll" ( _
        ByVal hCard As LongPtr, _
        ByRef pioSendRequest As Any, _
        ByRef sendbuff As Byte, _
        ByVal SendBuffLen As Long, _
        ByRef pioRecvRequest As Any, _
        ByRef RecvBuff As Byte, _
        ByRef RecvBuffLen As Long) As LongPtr

Private Declare PtrSafe Function SCardDisconnect Lib "winscard.dll" ( _
        ByVal hCard As LongPtr, _
        ByVal Disposition As Long) As LongPtr

Private Declare PtrSafe Function SCardReleaseContext Lib "winscard.dll" ( _
        ByVal hContext As LongPtr) As LongPtr

' *********************
' 定数の定義
' *********************
Private Const SCARD_SCOPE_USER As Long = 0
Private Const SCARD_SHARE_SHARED As Long = 2
Private Const SCARD_PROTOCOL_T1 As Long = 2
Private Const SCARD_LEAVE_CARD As Long = 0
Private Const MAX_READER_NAME As Long = 256
Private Const MAX_RECEIVE_BUFFER As Long = 255

' *********************
' エラーメッセージの定義
' *********************
Private Const ERR_INIT_FAILED As String = "ERROR:初期化処理に失敗しました。"
Private Const ERR_NO_READER As String = "ERROR:カードリーダが見つかりません。"
Private Const ERR_NO_CARD As String = "ERROR:正しいカードをセットしてください。"
Private Const ERR_TRANSMIT As String = "ERROR:ID取得エラー(Transmit:{0})"
Private Const ERR_READ_ABNORMAL As String = "ERROR:ID取得エラー(読込異常:{0},{1})"
Private Const ERR_DISCONNECT As String = "ERROR:切断エラー {0}"

' *********************
' 関数本体
' *********************
Public Function ReadUID() As String
    Dim hContext As LongPtr
    Dim readerName As String
    Dim result As String
    
    ' コンテキストを確立
    result = EstablishContext(hContext)
    If Left(result, 5) = "ERROR" Then
        ReadUID = result
        Exit Function
    End If
    
    ' カードリーダーを取得
    result = GetReader(hContext, readerName)
    If Left(result, 5) = "ERROR" Then
        ReadUID = result
        Exit Function
    End If
    
    ' カードに接続してUIDを読み取る
    result = ConnectAndReadCard(hContext, readerName)
    
    ' コンテキストを解放
    Call SCardReleaseContext(hContext)
    
    ReadUID = result
End Function

Private Function EstablishContext(ByRef hContext As LongPtr) As String
    Dim ret As LongPtr
    
    ret = SCardEstablishContext(SCARD_SCOPE_USER, 0, 0, hContext)
    If ret <> 0 Then
        EstablishContext = ERR_INIT_FAILED
    Else
        EstablishContext = "SUCCESS"
    End If
End Function

Private Function GetReader(ByVal hContext As LongPtr, ByRef readerName As String) As String
    Dim ret As LongPtr
    Dim pcchReaders As Long
    Dim mszReaders As String
    Dim readerArray() As String
    
    pcchReaders = MAX_READER_NAME
    
    ' リーダー名称取得
    mszReaders = String$(pcchReaders, vbNullChar)
    ret = SCardListReaders(hContext, vbNullString, mszReaders, pcchReaders)
    If ret <> 0 Then
        GetReader = ERR_NO_READER
        Exit Function
    End If
    
    readerArray = Split(mszReaders, vbNullChar)
    readerName = readerArray(0)
    
    GetReader = "SUCCESS"
End Function

Private Function ConnectAndReadCard(ByVal hContext As LongPtr, ByVal readerName As String) As String
    Dim hCard As LongPtr
    Dim activeProtocol As Long
    Dim ret As LongPtr
    Dim sendBuffer(4) As Byte
    Dim recvBuffer(MAX_RECEIVE_BUFFER - 1) As Byte
    Dim recvLen As Long
    Dim i As Long
    Dim cardData As String
    
    ' カード接続
    ret = SCardConnectA(hContext, readerName, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T1, hCard, activeProtocol)
    If ret <> 0 Then
        ConnectAndReadCard = ERR_NO_CARD
        Exit Function
    End If
    
    ' 送信バッファ設定
    sendBuffer(0) = &HFF: sendBuffer(1) = &HCA: sendBuffer(2) = &H0: sendBuffer(3) = &H0: sendBuffer(4) = &H0
    
    ' データ受信
    recvLen = MAX_RECEIVE_BUFFER
    ret = SCardTransmit(hCard, ByVal 0&, sendBuffer(0), 5, ByVal 0&, recvBuffer(0), recvLen)
    If ret <> 0 Then
        ConnectAndReadCard = Replace(ERR_TRANSMIT, "{0}", Hex(ret))
        GoTo DisconnectCard
    End If
    
    ' データチェック
    If recvBuffer(recvLen - 2) <> &H90 Then
        ConnectAndReadCard = Replace(Replace(ERR_READ_ABNORMAL, "{0}", Hex(recvBuffer(0))), "{1}", Hex(recvBuffer(1)))
        GoTo DisconnectCard
    End If
    
    ' UIDの取得
    cardData = ""
    For i = 0 To 7
        If Chr(recvBuffer(i)) <> Space(1) Then
            cardData = cardData & Right("0" & Hex(recvBuffer(i)), 2)
        End If
    Next i
    
    ConnectAndReadCard = cardData

DisconnectCard:
    ' カード切断
    ret = SCardDisconnect(hCard, SCARD_LEAVE_CARD)
    If ret <> 0 Then
        ConnectAndReadCard = Replace(ERR_DISCONNECT, "{0}", Hex(ret))
    End If
End Function

参考情報

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメント一覧 (3件)

  • 管理人様。

    初めてご連絡させていただきます。株式会社TKアジャイルの高柳と申します。

    最近、ディープAIを調査しておりまして、たまたま当社ブログで公開しているコードをAIで盗用チェック確認したところ、貴サイトがヒットしましたので、念のためご連絡させていただいた次第です。
    もちろん、AIの盗用チェックが100%とは思っておりませんし、当社サイトの内容と貴社サイトの内容が100%一致していないことも承知しています。(パッとみた感じでは、学生レベルでも可能な書き換え内容のようには見えますが。。。)

    つきましては、本記事につきまして、もし仮に当社記事をインスパイアされたのであれば、その旨を明記していただくか、あるいは当該記事の非公開をご検討いただけないでしょうか?

    HP等をご覧になればお分かりかと思いますが、当社ではエクセル関連事業を本業(ビジネス)として展開しておりますので、管理人さまのご対応によりましては、当社コンプライアンス規定に基づき、発信者開示・内容証明郵便送付の処置を行った上、改善がない場合は法的処理の検討となりますことをあらかじめご理解ください。(当社は穏便解決を望んでおります。念のため)

    以上、善処のご検討をお願いします

    • 株式会社TKアジャイル
      高柳様

      コメントいただきありがとうございます。
      ご連絡いただきました件、拝見いたしました。

      まず初めに、当方では当該記事の作成にあたり、複数の公開情報を参考にしながら独自に検討・実装を行っており、ご指摘の貴社記事のみを特段参照したものではないことを申し添えます。

      また、当該記事におけるコード記述、機能実装、エラーハンドリング、全体構成等において、貴社記事と明確な相違点が存在することも確認しております。
      具体的には、以下の点が異なっております。

      ・当方記事では、各API呼び出し(SCardEstablishContext、SCardConnect等)を個別関数化し、モジュール分割と再利用性を重視した設計を行っている
      ・エラー発生時の処理について、エラーコードに応じたメッセージの詳細分岐を追加している
      ・送信・受信バッファの処理手順について、別途ラップ関数化することで汎用性を向上させている
      ・コードコメントおよびロジック説明を、オリジナルの記述で行っている
      ・UID抽出処理においても、異なる変数管理手法(カードデータ文字列生成ロジックの違い)を採用している

      これらの違いから、当方記事は独自に創作されたものであり、著作権法上保護されるべき「独自の表現」を不正に利用したものではないと認識しております。

      もっとも、当方記事の作成過程において貴社記事の内容も参考にさせていただいております。
      その点につきましては、発信者様へのリスペクトが十分でなかったことを反省しております。

      したがいまして、当方記事内に、貴社サイトを参考にした旨の明記を加える対応をさせていただきたく存じます。

TKアジャイル 高柳 へ返信する コメントをキャンセル

目次