Search code examples
androidkotlinnfc

How can I properly read a NFC Tag? What is a "NFC_READ_COMMAND" and where can I find it?


Disclaimer: I'm pretty much new in NFC stuff. The official documentation is confusing for me. This is a test app to learn about NFC

So, I have this activity code in an Android app. I waant the activity to read an NFC tag (SDK 32). I don't have the oportunity to test it because I don't know what is a NFC_READ_COMMAND, and therefore, I cannot compile it. I am also unsure on how to properly get the data, I want to show the data in the "text_view_nfc" TextView.

I cannot compile because I don't know what NFC_READ_COMMAND is.



import com.miguel_rp.nfc_gestor_test.ActividadGestoraDeNFCsBase
import android.app.Activity
import android.nfc.NfcAdapter
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.nfc.Tag
import android.nfc.tech.NfcA
import android.widget.TextView
import kotlin.jvm.internal.Intrinsics
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.miguel_rp.nfc_gestor_test.R

class creador_de_cartas : ActividadGestoraDeNFCsBase() {
    override var activity = this as Activity

    var text_view_nfc: TextView? = null
    private var nfcAdapter: NfcAdapter? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        this.setContentView(R.layout.activity_creador_de_cartas)
        text_view_nfc = findViewById(R.id.textViewContenido)
        this.nfcAdapter = NfcAdapter.getDefaultAdapter(this)

    }


    private fun enableForegroundDispatch(activity: AppCompatActivity, adapter: NfcAdapter?) {
        val intent = Intent(activity.applicationContext, activity.javaClass)
        intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
        val pendingIntent = PendingIntent.getActivity(activity.applicationContext, 0, intent, 0)
        val filters = arrayOfNulls<IntentFilter>(1)
        val techList = arrayOf<Array<String>>()
        filters[0] = IntentFilter()
        with(filters[0]) {
            this?.addAction(NfcAdapter.ACTION_NDEF_DISCOVERED)
            this?.addCategory(Intent.CATEGORY_DEFAULT)
            try {
                this?.addDataType("text/plain")
            } catch (ex: IntentFilter.MalformedMimeTypeException) {
                throw RuntimeException("e")
            }
        }
        adapter?.enableForegroundDispatch(activity, pendingIntent, filters, techList)
    }

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        var tagFromIntent: Tag? = intent?.getParcelableExtra(NfcAdapter.EXTRA_TAG)
        val nfc = NfcA.get(tagFromIntent)
        val atqa: ByteArray = nfc.getAtqa()
        val sak: Short = nfc.getSak()
        nfc.connect()
        val isConnected= nfc.isConnected()

        if(isConnected)
        {
            val receivedData:ByteArray= nfc.transceive(NFC_READ_COMMAND)

            //code to handle the received data

    }else{
        Log.e("ans", "Not connected")
    }
}

    override fun onResume() {
        super.onResume()
        enableForegroundDispatch(this, nfcAdapter)
    }

}

In case you are wondering, "ActividadGestoraDeNFCsBase" is basically a parent class that makes sure that NFC is always activated while using the app.

package com.miguel_rp.nfc_gestor_test

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.nfc.NfcAdapter
import android.provider.Settings
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import java.util.*

open class ActividadGestoraDeNFCsBase() : AppCompatActivity() {
open lateinit var activity: Activity


companion object {
lateinit var nfcAdapter: NfcAdapter;
var timer = Timer()
fun isNFCEnabled(context: Context, actividad: Activity){
if (!nfcAdapter.isEnabled()) {
actividad.runOnUiThread{
Toast.makeText(context, "Esta aplicación necesita tener el NFC activado", Toast.LENGTH_SHORT).show()
                }
val intent = Intent(Settings.ACTION_NFC_SETTINGS)
actividad.startActivity(intent)
            }
        }

    }

override fun onResume() {
super.onResume()
val task = object : TimerTask() {
override fun run() {
isNFCEnabled(applicationContext, activity)
            }
        }
timer.scheduleAtFixedRate(task, 0, 1000)
    }

override fun onPause() {
super.onPause()
timer.cancel()
timer = Timer()
    }
}

Just in case, I'm using this NFC cards: Spanish Amazon. Sorry for that


Solution

  • So a lot of NFC stuff is custom to the make and model of the NFC Tag, luckily your Amazon links say your using an NTAG215 which is a NFC Type2 Standard Tag.

    The datasheet for this Tag tells you all you need to know about what "NFC_READ_COMMAND" needs to be to read this card.

    The Tag can be read at the low level using the NfcA standard.

    The Wikipedia image gives a good overview of all the standards used in NFC.

    So from the datasheet Section "8.5 Memory organization" you can see that this tag stores data in a number of 4 byte pages with page addresses starting at zero 0x00h to 0x86h for the Ntag 215.

    Again from the datasheet some page addresses at the start and end of the range have specific functions and some are read only.

    As you can see that a lot in NFC is about creating and decoding arrays of bytes.

    Moving on in the datasheet to Section "10. NTAG commands" you can see the various commands that this Tag supports and their byte values.

    One of these is the Read Command of 0x30h, the read command takes a second arg that is the value of which page to read. (Note that the Java NfcA class handles adding the CRC value for you.

    The Read Command returns an array of bytes which will the bytes read of 4 pages (16 bytes) from the start address given or the NAK error code.

    So in your case a "NFC_READ_COMMAND" can be something like:-

    byteArrayOf(0x30.toByte(), 0x00.toByte())
    

    Which is to read (4 bytes) starting at page zero (these address contain data of the UID, Capability Container and other things)

    Also because it is NFC Type 2 compliant you can also use the higher level Ndef NFC data format standard to store and access data IF the data was stored using this format.

    Details of how Ndef Data would be stored on a Type 2 Tag is at http://apps4android.org/nfc-specifications/NFCForum-TS-Type-2-Tag_1.1.pdf

    UPDATE

    I have just reread the code example, and the guide you are using is weird (not totally wrong but a complicated and weird way to do things)

    It use the older and less functional/reliable enableForegroundDispatch to get notified when a Tag containing Ndef data is presented.

    But then assumes it's an NfcA tag that is storing the Ndef data (which in your case with an NTAG215 it is)

    IF a Tag was presented that also stored Ndef Data BUT was a NfcB Tag, your code would get a Null pointer exception.

    It would be much more logical instead of

    val nfc = NfcA.get(tagFromIntent)

    to do:-

    val ndef = Ndef.get(tagFromIntent)

    The you could use ndef.getNdefMessage() to get the data on the Tag without having to use low level commands.

    or as you are using enableForegroundDispatch get the Ndef data direct from the Intent.

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        ...
        if (NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action) {
            intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)?.also { rawMessages ->
                val messages: List<NdefMessage> = rawMessages.map { it as NdefMessage }
                // Process the messages array.
                ...
            }
        }
    }
    
    

    (Taken from https://developer.android.com/guide/topics/connectivity/nfc/nfc#obtain-info )