6 minute read

ACS ACR122U USB NFC Reader
ACS ACR122U USB NFC Reader

A while back, we were using RFID readers made by ACS. They make a number of decent readers and we used one of the USB models, ACR1220U, with our bespoke Android tablet. To communicate to the reader, ACS provides libraries for Windows and Android. The Android library comes with a .jar file and some sample (but very limited) code. The demo showed how to connect to the reader, but didn’t have that much for getting information from the reader.

The ACS reader follows the SmartCard standards. Their library provides the low level access to the reader. It’s up to the consumer of the library to write the code that actual sends and receives data from the RFID reader. If you are working with a SmartCard reader, then you’ll need to know how to create APDU packets. An APDU, application protocol data unit, is the command packet that is sent from the reader to the card, and from the card to the reader. When you use this reader, your code is responsible for creating and sending the packets.

The logic that I used more or less follows this flow:

  • Reader signals the application that a card has been detected (or no longer detected)
  • Applicate tells the reader to a warm reset of the card
  • Application tells the reader to set the communications protocol for talking to the card
  • Application tells the reader to transmit the APDU command to get the unique identifier (UID) from the card
  • The response from the transmit command will have the UID from the card.

And that’s just to get the UID from a card. If you are trying to read a NDEF packet, then you have a lot more work to do. NDEF records are stored differently, depending on what kind of card that you are reading. If you are using RFID data from cards with the built-in NFC support that Android provides for hardware that comes with the device, you are benefiting by the low level code being handled for you.

For the last .5 decade, we’ve been using Xamarin for our Android coding. To use the ACS reader, I created a Xamarin wrapper library for their Java library. It basically takes and embeds the .jar file and provides a nice .NET API to their library. I then took their sample library and did a more or less straight port from Java to C#. If you grab that repo from Github, you’ll get the library and and the demo app. It was created with Visual Studio on Windows, but should work on the Mac. Should being the code word for “I didn’t do anything that was platform specific, but I didn’t test it on the Mac.”

The results of that wrapper/conversion are up on Github. You can grab it here. The .jar file that the library is compiled with is included in the repository. It is part of an API kit that should be downloaded from https://www.acs.com.hk/en/mobile-support/. The API kit includes the API documentation in HTML format.

The library does not have the APDU code to get the data from a RFID card. I do have that code, but at the moment it’s part of some code that I can’t share. I’ll pull the APDU code from our business code and post it later. In the meantime, this is the code from the OnStateChange event that gets assigned to the reader.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public void OnStateChange(int slotNum, int prevState, int currState)
{
    // If the previous state and the current state are outside the range of allowable
    // values, then assign min/max of the range
    if (prevState < Reader.CardUnknown || prevState > Reader.CardSpecific)
    {
        prevState = Reader.CardSpecific;
    }
    if (currState < Reader.CardUnknown || currState > Reader.CardSpecific)
    {
        currState = Reader.CardUnknown;
    }
    // Create output string
    string outputString = "Slot " + slotNum + ": "
            + stateStrings[prevState] + " -> "
            + stateStrings[currState];
    // Log the status change
    LogMsg(outputString);
    // We tapped the card
    if ((prevState == Reader.CardAbsent) && (currState == Reader.CardPresent))
    {
        // We have the right slot - opening the reader generates a spurious StateChange event
        if (slotNum == SlotNumber)
        {
            // read the card
            // The APDU (smart card application protocol data unit) byte array to get the UID from the card
            // Command  | Class | INS | P1 | P2 | Le
            // Get Data |   FF  | CA  | 00 | 00 | 00
            var command = new byte[] { 0xFF, 0xCA, 0x00, 0x00, 0x00 };
            // The byte array to contain the response
            var response = new byte[300];
            try
            {
                // In order to set the Get Data command to the card, we need to send a warm reset, followed by setting
                // the communications protocol.
                var atr = this.Power(slotNum, Reader.CardWarmReset);
                this.SetProtocol(slotNum, Reader.ProtocolT0 | Reader.ProtocolT1);
                // Send the command to the reader
                var responseLength = this.Transmit(slotNum,
                    command, command.Length, response,
                    response.Length);
                // We appear to be getting all 9 bytes of a 7 byte identifier. Since 9 would be considered a too
                // large value of 7, we drop the last 2 bytes
                if ((responseLength > ForcedIdSize) && (ForcedIdSize > 0))
                {
                    responseLength = ForcedIdSize;
                }
                // If we got a response, process it
                if (responseLength > 0)
                {
                    // Convert the byte array to a hex string
                    var s = ByteArrayToString(response, responseLength);
                    // Add the scan to the collection and notify any watchers
                    AddScan(s);
                    // Notify any watchers
                    LogMsg($"UID: {responseLength} {s}");
                }
            }
            catch (Java.Lang.Exception e)
            {
                LogMsg(e.Message);
            }
        }
    }
}

This is probably as a good point as any to menton that we no longer use the ACS readers. While their documentation is obtuse and technical support is non-existent, they do exactly what they are supposed to do. Which is fine, but it just didn’t mesh up with our needs.

Our use case is for scanning student and driver RFID tags for our Android tablet. We use an external RFID reader so that the students are scanned as they get on the bus. Our tablets are securely mounted in cradles, which tends to block RFID readers that are built in to the tablets. We had the following problems with our use of the ACS readers

  1. The ACS readers are NFC only. RFID covers a multitude of sins. NFC is just a subset of that. Our customers may have RFID cards that use different technologies, like HID PROX or EM400.
  2. The steps needed to get a UID were quick, but not fast. To get the UID. there needs to be several round trip conversations between the table, the reader, and the card. If a student getting on the bus didn’t hold the card long enough to the reader, we ended up missing some scans.
  3. The power draw was draining school bus batteries. The ACS readers are designed to draw power continuously from the USB port. Because the readers are mounted to the bus, they would be drawing power all the time. They are radios and there’s a power cost to run those radios.

These issues turned up during our initial deployment. We found a better solution with the Elatec RFID readers, but the ACS readers are a good solution. The ACS NFC readers are pretty much the industry standard for external FNC readers. If I was wiring up and external RFID reader that was connected to a device with AC power, an ACS reader would be the first thing I would consider for the project.

Comments