Azuki Digital

tutorial programming Mar 2, 2023

Using Host Card Emulation with Adafruit PN532 Reader

Olli profile image

Writen by Olli

header image for post of hce

This article will go through how to set up host card emulation (HCE) on android to communicate with the Adafruit PN532 NFC shield. This article will go through the code required on the android side and then the code needed to set up the NFC reader.

It is assumed that you have already connected the Adafruit PN532 NFC shield to a compatible Arduino board and that you also know how to upload code to the Arduino. Instructions for how to do this can be found on the Adafruit website.

Adafruit PN532 RFID/NFC Breakout and Shield

Host Card Emulation

Android Host Card Emulation (HCE) is a feature of the Android operating system that allows an Android device to emulate a smart card. With HCE, an Android device can be used to make payments at contactless payment terminals, authenticate with online services, and access other secure systems, just like a physical smart card.

To use HCE, an Android app must be developed that implements the necessary smart card functionality. The app can then communicate with a contactless payment terminal or other system using the NFC (Near Field Communication) protocol.

The reader and the app communicate using Application Protocol Data Unit (APDU) messages. An APDU message consists of a command header and a command data field, and may also include a response header and response data field. APDUs are used to send commands to a smart card and receive responses from the card.

Setting up the Android App

Firstly you need to tell the Android system that your app uses NFC and implements host card emulation. To do this you need to add the following to your AndroidManifest.xml before the application tag.


<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc.hce" android:required="true" />

You also need to add the following inside the application tag. This is to tell the Android system that your app has a HCE service that will run in the background. Feel free to change the service name from .MyHostApduService to anything else; it is just the name of the class you will create soon.


<service android:name=".MyHostApduService" android:exported="true"
        android:permission="android.permission.BIND_NFC_SERVICE">
  <intent-filter>
    <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
  </intent-filter>
  <meta-data android:name="android.nfc.cardemulation.host_apdu_service"
    android:resource="@xml/apduservice"/>
</service>

For the last bit of config, create the following file android/app/src/main/res/xml/apduservice.xml . Now copy the following into the file:


<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/servicedesc"
    android:requireDeviceUnlock="false">
    <aid-group android:description="@string/aiddescription"
        android:category="other">
        <aid-filter android:name="F0010203040506"/>
    </aid-group>
</host-apdu-service>

The <host-apdu-service> tag must contain an <android:description> attribute that contains a user-friendly description of the service. You can use the requireDeviceUnlock attribute to specify that the device is unlocked before you invoke this service to handle APDUs.

The <host-apdu-service> must contain one or more <aid-group> tags. Each <aid-group> tag is required to do the following:

  • Contain an android:description attribute that contains a user-friendly description of the AID group.
  • Have its android:category attribute set to indicate the category the AID group belongs to, such as the string constants defined by CATEGORY_PAYMENT or CATEGORY_OTHER.
  • Contain one or more <aid-filter> tags, each of which contains a single AID. Specify the AID in hexadecimal format, and make sure it contains an even number of characters.

This information can be found in more detail on the android developers documentation.

Host-based card emulation overview - Android Developers

The application ID (AID) is the ID the Arduino will request to the android system to call the HCE service. If multiple applications have the same AID on a device, the user will get a choice of which service they want to use and can also select a default.

The final part for setting up your Android app is to create the MyHostApduService class (or whatever you called it). Create the following file android/app/src/main/java/com/your-app-name/MyHostApduService.java and add the following code:


package com.your-app-name;

import android.nfc.cardemulation.HostApduService;
import android.os.Bundle;
import android.util.Log;

import java.util.Arrays;

public class MyHostApduService extends HostApduService {

    @Override
    public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) {
        Log.d("HCE", "processCommandApdu");
        Log.d("HCE", Arrays.toString(commandApdu));

        return "Hello".toByteArray();
    }

    @Override
    public void onDeactivated(int reason) {
        Log.d("HCE", "Deactivated: " + reason);
    }
}

This will create your Android service which will allow you to read messages from the Arduino and send messages back. This class extends HostApduService and the two main methods you will want to override are processCommandApdu and onDeactivated. The processCommandApdu method is where all incoming messages can be read and processed. This is also where you can send messages back to the Arduino. The onDeactivated method runs when the communication has ended. More information about the HostApduService class can be found at:

HostApduService - Android Developers

This concludes all the set up required for the Android side.

Setting up the Arduino

First, you will need to download the PN532 library code. If you are using the Arduino IDE, you can download it from within the IDE itself. Otherwise, you can download it from GitHub.

Adafruit PN532 RFID/NFC Breakout and Shield

Create a new Arduino file and add the following code:


#include <Wire.h>
#include <SPI.h>
#include <Adafruit_PN532.h>

#define PN532_IRQ   (2)
#define PN532_RESET (3)

Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET);

uint8_t SELECT_APDU[] = {
  0x00, /* CLA */
  0xA4, /* INS */
  0x04, /* P1  */
  0x00, /* P2  */
  0x07, /* Length of AID  */
  0xF0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* AID  */
  0x00  /* Le  */
};

void setup(void) {
  Serial.begin(115200);

  nfc.begin();

  uint32_t versiondata = nfc.getFirmwareVersion();
  if (! versiondata) {
    Serial.print("Didn't find PN53x board");
    while (1); // halt
  }

  Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX);
  Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC); Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC);

  // configure board to read RFID tags
  nfc.SAMConfig();

  nfc.begin();
}

This code will set up the Arduino and NFC shield and print out the firmware version if the shield has been connected properly. Make sure that the AID in the SELECT_APDU array matches the AID you set in the Android service config.

Now for the main loop we want to send the SELECT_APDU message to the Android device to invoke the HCE service.


void loop(void) {
  Serial.println("Listening...");
  if (nfc.inListPassiveTarget()) {
    Serial.println("Something's there...");

    uint8_t response[255];
    uint8_t responseLength = sizeof(response);

    bool success = nfc.inDataExchange(SELECT_APDU, sizeof(SELECT_APDU), response, &responseLength);

    if (success) {
      Serial.println("Sent");
      nfc.PrintHexChar(response, responseLength);
    } else {
      Serial.println("Not sent");
    }
  }
}

This will tell the reader to wait for a device to come in range and once it does, send the SELECT_APDU message. The Android system will then run the processCommandApdu function which will return a “Hello” message which will be printed out in the Arduino serial monitor.

This is everything you need to get the basic communication working between the Arduino and Android device. If you want to send more messages, you can do so after the SELECT_APDU message has been sent.