Search code examples
vb.netnfcacr122

How do I read the UID of a tag (e.g. Mifare Classic) using the ACR122U Reader and PCSC library in VB.net


Right so I have the following code that works fine using the PCSC-sharp Iso7816 Library:

Imports PCSC
Imports PCSC.Iso7816
Imports PCSC.Utils
Imports System


Public Class TapMembershipCard

    Dim MembershipCardUID As String

    Sub ReadCard() Handles ReadCardBtn.Click
        Using context = New SCardContext()
            context.Establish(SCardScope.System)

            Dim readerNames = context.GetReaders()
            Dim sc As PCSC.SCardError

            Dim readerName = readerNames.FirstOrDefault()
            If readerName Is Nothing Then
                Return
            End If

            Using rfidReader = New SCardReader(context)
                sc = rfidReader.Connect(readerName, SCardShareMode.Shared, SCardProtocol.Any)
                If sc <> SCardError.Success Then
                    'THIS IS USUALLY WHERE THE PROGRAM WILL GET TO IF NO CARD HAS BEEN PRESENTED
                    Return
                End If

                PresentCard.Visibility = Visibility.Hidden

                Dim apdu = New CommandApdu(IsoCase.Case2Short, rfidReader.ActiveProtocol) With {
                    .CLA = &HFF,
                    .Instruction = InstructionCode.GetData,
                    .P1 = &H0,
                    .P2 = &H0,
                    .Le = 7
                }

                sc = rfidReader.BeginTransaction()
                If sc <> SCardError.Success Then
                    Console.WriteLine("Could not begin transaction.")
                    Return
                End If

                Console.WriteLine("Retrieving the UID .... ")

                Dim receivePci = New SCardPCI() ' IO returned protocol control information.
                Dim sendPci = SCardPCI.GetPci(rfidReader.ActiveProtocol)

                Dim receiveBuffer = New Byte(255) {}
                Dim command = apdu.ToArray()

                sc = rfidReader.Transmit(sendPci, command, receivePci, receiveBuffer)
                If sc <> SCardError.Success Then
                    Console.WriteLine("Error: " & SCardHelper.StringifyError(sc))
                End If

                Dim responseApdu = New ResponseApdu(receiveBuffer, IsoCase.Case2Short, rfidReader.ActiveProtocol)
                Console.Write("SW1: {0:X2}, SW2: {1:X2}" & vbCrLf & "Uid: {2}", responseApdu.SW1, responseApdu.SW2, If(responseApdu.HasData, BitConverter.ToString(responseApdu.GetData()), "No uid received"))

                rfidReader.EndTransaction(SCardReaderDisposition.Leave)
                rfidReader.Disconnect(SCardReaderDisposition.Reset)

                MembershipCardUID = BitConverter.ToString(responseApdu.GetData())
            End Using
        End Using
    End Sub
End Class

But ideally I would like to have it so that it constantly checks if a card is present instead of having to click the button once a card is presented. Also, can it be simplified in any way? I feel as though its a lot of code for just reading the UID.

I am willing to use any libraries or frameworks that are compatible with VB.net and also I'm happy for any ideas to be written in C# if it's preferred. FYI I'm using .NET 8.0 WPF Application. The reader is an ACR122U reader

I have tried to simply use recursion or call the sub again from within this IF statement:

sc = rfidReader.Connect(readerName, SCardShareMode.Shared, SCardProtocol.Any)
If sc <> SCardError.Success Then
    'THIS IS USUALLY WHERE THE PROGRAM WILL GET TO IF NO CARD HAS BEEN PRESENTED
    Return
End If

The issue is that none of the visual elements load / everything will freeze whilst it is constantly iterating until a card is presented.

Ideally I would like to be able to have some code that detects when a card is presented, then sends the ADPU command to retrieve the UID.


Solution

  • I ended up using the ACR122U Library Here's the documentation: Github + Website. It doesn't seem to be maintained but it is very helpful. Here's the code I ended up using (It is by no means perfect, its a work in progress. I have just managed to get the following working):

    Imports System
    Imports PCSC
    Imports PCSC.Iso7816
    Imports PCSC.Monitoring
    
    
    Public Class TapMembershipCard
    
        Private maxReadWriteLength As Integer = 50
        Private blockSize As Integer = 4
        Private startBlock As Integer = 4
        Private readbackDelayMilliseconds As Integer = 100
        Private cardReaderNames As String() = Nothing
        Private cardContext As ISCardContext = Nothing
        Private buzzerSet As Boolean = False
        Private buzzerOnOff As Boolean = False
    
        Public Event CardInserted As CardInsertedHandler
        Public Delegate Sub CardInsertedHandler(reader As ICardReader)
    
        Public Event CardRemoved As CardRemovedHandler
        Public Delegate Sub CardRemovedHandler()
    
    
        Public Sub Init()
            buzzerOnOff = True
            maxReadWriteLength = 16
            blockSize = 16
            startBlock = 0
            readbackDelayMilliseconds = 10
    
            cardContext = ContextFactory.Instance.Establish(SCardScope.System)
    
            cardReaderNames = cardContext.GetReaders()
    
            Dim monitor = MonitorFactory.Instance.Create(SCardScope.System)
            AddHandler monitor.CardInserted, AddressOf Monitor_CardInserted
            AddHandler monitor.CardRemoved, AddressOf Monitor_CardRemoved
            monitor.Start(cardReaderNames)
        End Sub
    
        Private Sub Monitor_CardInserted(sender As Object, e As CardStatusEventArgs)
            Dim reader As ICardReader = Nothing
    
            Try
                reader = cardContext.ConnectReader(cardReaderNames(0), SCardShareMode.Shared, SCardProtocol.Any)
            Catch
            End Try
    
            If reader IsNot Nothing Then
                If Not buzzerSet Then
                    buzzerSet = True
                    SetBuzzer(reader, buzzerOnOff)
                End If
    
                RaiseEvent CardInserted(reader)
                GetUID(reader)
    
                Try
                    reader.Disconnect(SCardReaderDisposition.Leave)
                Catch
                End Try
            End If
        End Sub
    
        Public Sub SetBuzzer(reader As ICardReader, [on] As Boolean)
            Dim ret As Byte() = New Byte(1) {}
    
            reader.Transmit(New Byte() {&HFF, &H0, &H52, If([on], &HFF, &H0), &H0}, ret)
        End Sub
    
    
        Private Sub Monitor_CardRemoved(sender As Object, e As CardStatusEventArgs)
            RaiseEvent CardRemoved()
        End Sub
    
        Public Sub GetUID(reader As ICardReader)
            Dim uid As Byte() = New Byte(9) {}
    
            reader.Transmit(New Byte() {&HFF, &HCA, &H0, &H0, &H0}, uid)
    
            Array.Resize(uid, 7)
        End Sub
    End Class