How to use ACR35 NFC Reader in Android

2020-08-01 08:44发布

问题:

I'm new in developing with ACR or NFC Reader, especially for Android. And recently I need to use an ACR35 and I've got the SDK and the example from this acs official website. And It works just fine as an example. And now I need to create an activity that will always be ready to check whether nfc card is tapped. But the problem is I don't know how to detect when the nfc card is tapped and I don't know what to do next, and I can't find the way out from the example as it detects nfc card when I touch the 'transmit' button, it doesn't do it automatically. Please give me with example code. Thanks for your answer.

回答1:

A bit late, but it might help someone. I have found following project on the git hub. It shows how to read tag id from the acr35 here .

I have been using acr35 for some time to read the tag id. From my experience is a buggy device on Android. I tested around 10 devices and it worked only on 3...

I read from it every second. It returns results from the last card even though it is not present there anymore. Therefore; I have to reset it twice per every successful read and it takes around 6 seconds to get the device in reading state again... Also be very careful with multithreading.

My implementation based on mentioned project - added simple locking to stop querying the card after successfull reading + full device reset, filternig same uuid which was read in very short time after it was read previously:

ACR3x class

import com.acs.audiojack.AudioJackReader;

import android.media.AudioManager;
import sk.tido.util.ByteHex;

import java.util.Date;

/**
 * This class allows control of the ACR35 reader sleep state and PICC commands
 */
public class Acr3x {

    private Acr3xTransmitter transmitter;
    private AudioManager mAudioManager;
    private AudioJackReader mReader;

    private boolean firstReset = true;  /** Is this the first reset of the reader? */

    /** APDU command for reading a card's UID */
    private final byte[] apdu = { (byte) 0xFF, (byte) 0xCA, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
    /** Timeout for APDU response (in <b>seconds</b>) */
    private final int timeout = 1;
    private int acr3xCardType = AudioJackReader.PICC_CARD_TYPE_ISO14443_TYPE_A
            | AudioJackReader.PICC_CARD_TYPE_ISO14443_TYPE_B
            | AudioJackReader.PICC_CARD_TYPE_FELICA_212KBPS
            | AudioJackReader.PICC_CARD_TYPE_FELICA_424KBPS
            | AudioJackReader.PICC_CARD_TYPE_AUTO_RATS;

    private int acr3xStartAudioLevel = 0;

    private Object locking = new Object();

    private String lastUuid = "";
    private Date lastUuidDate = new Date();


    public Acr3x(AudioManager mAudioManager){
        this.mAudioManager = mAudioManager;
    }

    public void start(final Acr3xNotifListener listener){
        Runnable r = new Runnable(){

            @Override
            public void run() {
                if(mReader == null){
                    mReader = new AudioJackReader(mAudioManager);
                }
                System.out.println("ACR35 reader start");

                acr3xStartAudioLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
                System.out.println("acr3x start audio stream level: " + acr3xStartAudioLevel);
                mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
                        mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);
                System.out.println("acr3x set audio stream level: " + mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));

                mReader.start();
                mReader.setSleepTimeout(30);
                mReader.setOnFirmwareVersionAvailableListener(new AudioJackReader.OnFirmwareVersionAvailableListener() {
                    @Override
                    public void onFirmwareVersionAvailable(AudioJackReader reader,
                        String firmwareVersion) {
                        System.out.println("acr3x firmware version: " + firmwareVersion);
                        if(listener != null){
                            listener.onFirmwareVersionAvailable(firmwareVersion);
                        }
                        Acr3x.this.read(listener);

                    }
                });
                mReader.reset(new AudioJackReader.OnResetCompleteListener(){

                    @Override
                    public void onResetComplete(AudioJackReader arg0) {
                        mReader.getFirmwareVersion();

                    }

                });
            }
        };

        Thread t = new Thread(r, "Acr3xInitThread");
        t.start();

    }


    /**
     * Sets the ACR35 reader to continuously poll for the presence of a card. If a card is found,
     * the UID will be returned to the Apache Cordova application
     *
     * @param callbackContext: the callback context provided by Cordova
     * @param cardType: the integer representing card type
     */
    public void read(final Acr3xNotifListener callbackContext){
        System.out.println("acr3x setting up for reading...");
        firstReset = true;

        /* Set the PICC response APDU callback */
        mReader.setOnPiccResponseApduAvailableListener
                (new AudioJackReader.OnPiccResponseApduAvailableListener() {
                    @Override
                    public void onPiccResponseApduAvailable(AudioJackReader reader,
                                                            byte[] responseApdu) {
                        /* Update the connection status of the transmitter */
                        transmitter.updateStatus(true);

                        /* Print out the UID */
                        String uuid = ByteHex.bytesToHex(responseApdu);

                        if(uuid.equalsIgnoreCase("0x9000")){
                            return;
                        }

                        if(uuid.endsWith("9000")){
                            uuid = uuid.substring(0, uuid.length() - 4);
                        }

                        if(uuid.equals(lastUuid)){ // na odfiltrovanie opakujucich sa uuid z citacky z predchadzajuceho citania
                            if(new Date().getTime() - lastUuidDate.getTime() < 3000){
                                return;
                            }
                        }

                        lastUuid = uuid;
                        lastUuidDate = new Date();

                        synchronized(locking){

                            System.out.println("acr3x uuid: " + uuid);

                            if(callbackContext != null){
                                callbackContext.onUUIDAavailable(uuid);
                            }
                            System.out.println("acr3x restarting reader");
                            transmitter.kill();
                            try {
                                locking.wait(2000);
                            } catch (InterruptedException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                        }

                        read(callbackContext);



                    }
                });

        /* Set the reset complete callback */
        mReader.setOnResetCompleteListener(new AudioJackReader.OnResetCompleteListener() {
            @Override
            public void onResetComplete(AudioJackReader reader) {
                System.out.println("acr3x reset complete");

                /* If this is the first reset, the ACR35 reader must be turned off and back on again
                   to work reliably... */
                Thread t = null;
                if(firstReset){  //firstReset
                    t = new Thread(new Runnable() {
                        public void run() {
                            try{
                                /* Set the reader asleep */
                                mReader.sleep();
                                /* Wait one second */
                                Thread.sleep(500);
                                /* Reset the reader */
                                mReader.reset();

                                firstReset = false;
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                                // TODO: add exception handling
                            }
                        }
                    });

                } else {
                    /* Create a new transmitter for the UID read command */
                    transmitter = new Acr3xTransmitter(mReader, mAudioManager, timeout,
                            apdu, acr3xCardType, locking);
                    t = new Thread(transmitter);
                }
                t.start();
            }
        });

        mReader.start();
        mReader.reset();
        System.out.println("acr3x setup complete");
    }

    public void stop(){
        if(transmitter != null){
            transmitter.kill();
        }

        System.out.println("acr3x restoring audio level: " + acr3xStartAudioLevel);
        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,acr3xStartAudioLevel, 0);
        System.out.println("acr3x set audio stream level: " + mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC));

        if(mReader != null){
            mReader.stop();
        }
    }

}

Transmitter class

    import com.acs.audiojack.AudioJackReader;

import android.media.AudioManager;

/**
 * This class sets up an independent thread for card polling, and is linked to the
 * <code>setOnPiccResponseApduAvailableListener</code> callback function
 */

public class Acr3xTransmitter implements Runnable {

    private AudioJackReader mReader;
    private AudioManager mAudioManager;
    //private CallbackContext mContext;

    private boolean killMe = false;          /** Stop the polling thread? */
    private int itersWithoutResponse = 0;    /** The number of iterations that have passed with no
                                                 response from the reader */
    private boolean readerConnected = true;  /** Is the reader currently connected? */

    private int cardType;
    private int timeout;
    private byte[] apdu;
    private Object locking;

    /**
     * @param mReader: AudioJack reader service
     * @param mAudioManager: system audio service
     * @param mContext: context for plugin results
     * @param timeout: time in <b>seconds</b> to wait for commands to complete
     * @param apdu: byte array containing the command to be sent
     * @param cardType: the integer representing card type
     */
    public Acr3xTransmitter(AudioJackReader mReader, AudioManager mAudioManager,
                       int timeout, byte[] apdu, int cardType, Object locking){
        this.mReader = mReader;
        this.mAudioManager = mAudioManager;
        this.timeout = timeout;
        this.apdu = apdu;
        this.cardType = cardType;
        this.locking = locking;
    }


    /**
     * Stops the polling thread
     */
    public void kill(){
        killMe = true;
    }


    /**
     * Updates the connection status of the reader (links to APDU response callback)
     */
    public void updateStatus(boolean status){
        readerConnected = status;
    }

    /**
     * Sends the APDU command for reading a card UID every second
     */
    @Override
    public void run() {
        try {
            /* Wait one second for stability */
            Thread.sleep(1000);

            while (!killMe) {
                synchronized(locking){

                    if(killMe){
                        continue;
                    }
                    /* If the reader is not connected, increment no. of iterations without response */
                    if(!readerConnected){
                        itersWithoutResponse++;
                    }
                    /* Else, reset the number of iterations without a response */
                    else{
                        itersWithoutResponse = 0;
                    }
                    /* Reset the connection state */
                    readerConnected = false;

                    if(itersWithoutResponse == 4) {
                        /* Communicate to the Cordova application that the reader is disconnected */
                        System.out.println("acr3x disconnected");
                        /* Kill this thread */
                        kill();
                    } else if(!mAudioManager.isWiredHeadsetOn()) {
                        System.out.println("acr3x not connected");
                        /* Kill this thread */
                        kill();
                    } else{
                        System.out.println("acr3x reading...");
                        /* Power on the PICC */
                        mReader.piccPowerOn(timeout, cardType);
                        /* Transmit the APDU */
                        mReader.piccTransmit(timeout, apdu);


                    }
                }
                /* Repeat every second */
                Thread.sleep(1000);
            }
            /* Power off the PICC */
            mReader.piccPowerOff();
            /* Set the reader asleep */
            mReader.sleep();
            /* Stop the reader service */
            mReader.stop();

            synchronized(locking){
                locking.notifyAll();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
            // TODO: add exception handling
        }
    }

}

listener interface:

public interface Acr3xNotifListener {

public void onUUIDAavailable(String uuid);
public void onFirmwareVersionAvailable(String firmwareVersion);

}



回答2:

First you call the Reset command to activate the device. As the device will fall asleep (after 4 seconds by default)* the trick is to keeping it alive.

You can achieve that by relaunching a new PowerOn command every time the previous PowerOn timedOut. Something like that

void powerOn () {
if (!mReader.piccPowerOn(mPiccTimeout, mPiccCardType)) {
   powerOn();
} else {
   askNfcForItsId();
}

Don't forget to call powerOn after you read the nfc otherwise it will fall asleep after the 1st nfc card.

*the reset timeout can be set between 4 and 20 seconds



标签: android nfc