RFID and mobile apps: iOS vs Android

  • Basile Perrenoud

Nous avons crĂ©Ă© une application mobile capable de lire et d’écrire des tags RFID. Les systĂšmes iOS et Android supportent tous deux ces opĂ©rations, mais comme souvent, leur approche n’est pas exactement identique.

En savoir plus sur le service Mobile de notre agence digitale.

RĂ©cemment, chez Liip, nous avons eu l’opportunitĂ© de travailler avec Bobst SA. Cette sociĂ©tĂ©, qui vend des machines demandant une authentification, souhaitait optimiser ses processus actuels. Auparavant, les client·e·s devaient utiliser un outil pour scanner la machine et saisir manuellement une rĂ©fĂ©rence dans le portail web de Bobst SA. Leur nouveau processus utilise dĂ©sormais des tags RFID. Notre but Ă©tait de crĂ©er une application mobile capable de scanner ces tags et de se connecter automatiquement au compte de l’utilisateur·rice sur les serveurs de Bobst afin de lui octroyer le certificat correct. L’application permettrait ensuite la reprise du certificat sur la machine.

Technologies

Tout comme pour la plupart des applications que nous crĂ©ons chez Liip, nous avons choisi de travailler avec des technologies natives pour les deux plateformes. Nous avons crĂ©Ă© l’application Android en utilisant Kotlin (Kotlin version 1.4.10, cible Android 11 – API 30) et l’application iOS en utilisant Swift (Swift 5, cible iOS 14).
Nous prĂ©fĂ©rons en gĂ©nĂ©ral ces technologies aux options multi-plateformes pour diverses raisons, y compris la performance, l’expĂ©rience UI pour les utilisateur·rice·s des deux plateformes et la maintenabilitĂ©. Dans ce cas, le choix Ă©tait d’autant plus Ă©vident que les capacitĂ©s NFC font partie des bibliothĂšques numĂ©riques intĂ©grĂ©es de ces systĂšmes. Une application de multi-plateforme aurait dans tous les cas requis des modules de passerelle pour les deux plateformes.

Standards RFID

Le sigle RFID vient de l’abrĂ©viation anglaise pour radio frequency identification. Les tags RFID les plus simples fonctionnent mĂȘme sans apport d’énergie. Les appareils RFID peuvent ĂȘtre implĂ©mentĂ©s selon diffĂ©rents standards, et notamment selon la norme ISO 15693-3, utilisĂ©e par les tags Bobst.
La technologie NFC, ou Near field communication, est une sous-catĂ©gorie plus prĂ©cise des appareils de radio-identification. Les appareils NFC peuvent ĂȘtre Ă  la fois tags et lecteurs, et sont en mesure de communiquer activement ensemble. Presque tous les tĂ©lĂ©phones mobiles modernes sont munis des capacitĂ©s NFC. De maniĂšre gĂ©nĂ©rale, les appareils NFC communiquent entre eux par NDEF (NFC Data Exchange Format). Cependant, lors de la communication selon les normes ISO en RFID, il faut utiliser d’autres formats: les NFC type 2 (= type A) et type 4 (= type B) sont utilisĂ©s pour communiquer avec la norme ISO 14443 en RFID.
Dans notre cas, nous utiliserons le NFC type 5 (= type V) pour communiquer avec notre tag ISO 15693.

Support NFC pour Android, Ă©tat en mai 2021

Type Description
NfcA Permet d’accĂ©der aux propriĂ©tĂ©s NFC-A (ISO 14443-3A) et aux opĂ©rations I/O.
NfcB Permet d’accĂ©der aux propriĂ©tĂ©s NFC-A (ISO 14443-3A) et aux opĂ©rations I/O.
NfcF Permet d’accĂ©der aux propriĂ©tĂ©s NFC-F (JIS 6319-4) et aux opĂ©rations I/O.
NfcV Permet d’accĂ©der aux propriĂ©tĂ©s NFC-V (ISO 15693) et aux opĂ©rations I/O.
IsoDep Permet d’accĂ©der aux propriĂ©tĂ©s ISO-DEP (ISO 14443-4) et aux opĂ©rations I/O.
Ndef Provides access to NDEF data and operations on NFC tags that have been formatted as NDEF.
NdefFormatable Permet d’accĂ©der aux donnĂ©es et opĂ©rations NDEF sur les tags NFC qui ont Ă©tĂ© formatĂ©s comme NDEF.
MifareClassic Permet d’accĂ©der aux propriĂ©tĂ©s MIFARE Classic et aux opĂ©rations I/O. ⚠ Optio: il n’y a aucune garantie qu’un tĂ©lĂ©phone Android avec NFC supporte ce type.
MifareUltralight Permet d’accĂ©der aux propriĂ©tĂ©s MIFARE Ultralight et aux opĂ©rations I/O. ⚠ Option : il n’y a aucune garantie qu’un tĂ©lĂ©phone Android avec NFC supporte ce type.

Support NFC pour iOS, Ă©tat en mai 2021

Type Description
NFCISO7816Tag Interface pour l’interaction avec un tag ISO 7816.
NFCISO15693Tag Interface pour l’interaction avec un tag ISO 15693.
NFCFeliCaTag Interface pour l’interaction avec un tag FeliCaℱ.
NFCMiFareTag Interface pour l’interaction avec un tag MIFARE¼.
NFCNDEFTag Interface pour l’interaction avec un tag NDEF.

Implémentation sur les deux plateformes

ConsidĂ©rant les deux tableaux ci-dessus, nous constatons qu’Android et iOS supportent tous deux les puces RFID de Bobst. Nous avons utilisĂ© le type Android NfcV et le type iOS NFCISO15693Tag.

De plus, les deux systĂšmes requiĂšrent l’autorisation adĂ©quate pour accĂ©der aux capacitĂ©s NFC. Les documents Android et iOS abordent ce sujet en dĂ©tail.

En bref, notre implémentation devait supporter trois opérations :

  • Identifier un tag Ă  proximitĂ©
  • Lire le contenu du tag
  • Écrire un nouveau contenu sur le tag

Nous avons utilisé ces opérations de la maniÚre suivante :

  1. Identifier et lire : attendre l’identification d’un tag (le systĂšme reçoit son identifiant unique), puis utiliser cet identifiant unique pour demander une opĂ©ration de lecture.
  2. Envoyer le rĂ©sultat de l’opĂ©ration de lecture au serveur Bobst pour recevoir un certificat (pas d’opĂ©ration RFID dans ce cas).
  3. Identifier et Ă©crire : identifier une nouvelle fois un tag proche et s’assurer que l’identifiant est le mĂȘme que celui lu au prĂ©alable (il s’agit du mĂȘme tag). Écrire ensuite le certificat.

Étape 1 : identifier un tag

Les deux systĂšmes offrent une façon simple, mais quelque peu diffĂ©rente, de recevoir l’identification d’un tag proche :

Pour iOS, nous devons demander au systĂšme de commencer l’identification de tags RFID. À ce stade, l’utilisateur·rice a une vue modale, et le systĂšme attend de trouver un tag. En principe, les lignes suivantes suffisent.

func startScan() {
    guard  NFCTagReaderSession.readingAvailable else {
        delegate?.onScanningNotSupported()
        return
    }
    nfcSession = NFCTagReaderSession(pollingOption: NFCTagReaderSession.PollingOption.iso15693, delegate: self, queue: **nil**)
    nfcSession?.alertMessage = ... // whatever alert message such as "hold the phone near the tag"
    nfcSession?.begin()
}

func stopScan() {
    nfcSession?.invalidate()
}

Pour Android, le systĂšme fonctionne grĂące au mĂ©canisme d'Intent d'Android. Cela signifie que nous devons enregistrer l’application pour accepter les scan intents du type NFC adĂ©quat. AprĂšs exĂ©cution, l’application reçoit une notification. Par consĂ©quent, il n’y a pas besoin de cliquer sur un bouton pour dĂ©marrer. MĂȘme lorsque l’application est fermĂ©e, si un tag est proche, l’application s’ouvre et reçoit immĂ©diatement une notification !

Enregistrement de la capacitĂ© NFC dans le manifeste, dans l’activitĂ© qui reçoit l’information :

<intent-filter>
    <action android:name="android.nfc.action.TECH_DISCOVERED" />
</intent-filter>
<meta-data
    android:name="android.nfc.action.TECH_DISCOVERED"
    android:resource="@xml/filter_nfc"
/>

Ajouter la liste de filtres dans le fichier de ressources que nous avons référencé dans le manifeste, tel que xml/filter_nfc/xml (dans notre cas, nous saisissons simplement NfcV, comme dans le tableau ci-dessus) :

<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.NfcV</tech>
    </tech-list>
</resources>

Notre activité sera notifiée via onNewIntent:

override fun onNewIntent(intent: Intent) {
  super.onNewIntent(intent)

  if (NfcAdapter.ACTION_TECH_DISCOVERED == intent.action) {
    val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
    viewModel.scanned(tag)  // Or whatever else
  }
}

Les deux plateformes offrent clairement une expĂ©rience diffĂ©rente aux utilisateur·rice·s, mais fonctionnent toutes deux de la façon suivante : les prĂ©cĂ©dentes Ă©tapes rĂ©sultent en un objet de tag contenant l’identifiant du tag dĂ©tectĂ©. Maintenant, nous devons l’utiliser pour lire ou Ă©crire ce dont nous avons besoin.

Étape 2 : lire un tag

Pour iOS, le systÚme contient déjà une implémentation basique de la norme ISO 15693, ce qui nous simplifie les choses.

L’implĂ©mentation exacte va dĂ©pendre des besoins spĂ©cifiques. L’idĂ©e de base est de convertir l’identifiant gĂ©nĂ©rique du tag en une implĂ©mentation ISO correcte intĂ©grĂ©e dans la bibliothĂšque de base NFC :

if case let NFCTag.iso15693(tagType5) = tag {
}

Puis, nous utilisons des fonctions intĂ©grĂ©es telles que tag.readMultipleBlocks(). Cette fonction accepte un certain nombre d’arguments, y compris les drapeaux de demande pratiques.

tag.readMultipleBlocks(requestFlags: [.highDataRate], blockRange: NSRange(location: 0, length: NfcService.MAXREADBLOCK)) { data, error in
... // check for error and get data using the variables above
}

À ce stade, l’unique difficultĂ© est de dĂ©terminer dans quel ordre les octets ont Ă©tĂ© organisĂ©s. La sociĂ©tĂ© Bobst nous a fourni une documentation prĂ©cise afin de nous permettre de lire leurs tags correctement. Dans notre cas, ils Ă©taient organisĂ©s en blocs de quatre devant ĂȘtre inversĂ©s individuellement aprĂšs lecture.

Pour Android, le systĂšme ne fournit pas d’implĂ©mentation intĂ©grĂ©e de la norme ISO, hormis la fonction d’envoi de blocs bruts d’octets. Ces blocs bruts contiennent une demande de commande pour le tag, organisĂ©e selon la norme ISO 15693 https://www.iso.org/standard/73602.html

La premiĂšre Ă©tape est trĂšs similaire Ă  celle d’iOS : obtenir le type de systĂšme correspondant Ă  notre tag.

val nfcVTag = NfcV.get(tag) ?: return

L’étape suivante est plus dĂ©licate. Le document de 70 pages dĂ©crivant la norme ISO 15693 nous indique, entre autres commandes, que la fonction readMultipleBlocks intĂ©grĂ©e dans la version iOS est en rĂ©alitĂ© dĂ©clenchĂ©e par l’envoi du code de commande 0x23.

Il nous précise également que pour envoyer une commande, nous devons envoyer les informations suivantes :
— drapeaux
— code de commande
— champs de paramĂ©trage obligatoires et facultatifs, selon la commande

Dans notre cas, nous devons dĂ©finir les drapeaux Ă  Ă©mettre et la commande « Addressed », ce qui signifie que la commande utilise l’identifiant du tag comme un argument et exĂ©cute uniquement ce tag spĂ©cifique. La documentation indique que ce drapeau Ă©quivaut au 6e bit de l’octet du drapeau. Nous allons Ă©galement activer un drapeau Ă  grand dĂ©bit (2e bit). L’ensemble total d’octets du drapeau est 00100010, ce qui se traduit par 0x22.

Notre demande Android correspondra donc au tableau d’octets suivant :

val offset = 0 // offset of first block to read
val blocks = 32 // number of blocks to read
val cmd = mutableListOf<Byte>().apply {
  add(0x22.toByte()) // flags: addressed (= UID field present) + high data rate
  add(0x23.toByte()) // command: READ MULTIPLE BLOCKS
  addAll(tag.id.toList()) // tag UID
  add((offset and 0x0ff).toByte()) // first block number. add 0xff to ensure we send two bytes
  add((blocks - 1 and 0x0ff).toByte()) // number of blocks (-1 as 0x00 means one block). add 0xff to ensure we send two bytes
}.toByteArray()

Nous recevons simplement la réponse par la méthode de transceive et contrÎlons le premier octet pour nous assurer que cela fonctionne.

nfcVTag.connect()
val responseBytes = nfcVTag.transceive(cmd)
if (responseBytes.first() != 0x00.toByte()) {
  return NfcResponse.Error.Read
}
nfcVTag.close()

Le tableau d’octets de la rĂ©ponse peut dĂ©sormais ĂȘtre analysĂ© selon la documentation de Bobst afin de nous permettre d’extraire l’information de tag correcte.

Étape 3 : Ă©crire sur un tag

Pour Ă©crire sur un tag, le processus est presque le mĂȘme que pour le lire (plus simple pour iOS, plus complexe pour Android).

Pour iOS, nous compilons toutes les informations que nous devrons Ă©crire sur un tableau d’octets. Malheureusement, la fonction d’écriture multiple Ă©tait dĂ©sactivĂ©e sur les tags de Bobst, raison pour laquelle nous avons dĂ» envoyer les octets individuellement. Nous avons simplement utilisĂ© la fonction de base NFC de transceive en boucle. LĂ  encore, la fonction active tous les drapeaux nĂ©cessaires.

tag.writeSingleBlock(requestFlags: [.highDataRate, .address], blockNumber: UInt8(startBlock), dataBlock: dataBlock) { error in
    ... // Simply check for error and react accordingly
}

Pour Android
Comme prĂ©cĂ©demment, nous devons envoyer la commande d’octet appropriĂ©e et utiliser la fonction de transceive. La commande utilise lĂ  aussi la valeur 0x22. La documentation de la norme ISO 15693 stipule que la demande writeSingleBlock est Ă©mise via 0x21 comme commande d’octet. Nous devons Ă©galement activer l’identifiant du tag puisque nous effectuons une commande « Addressed ». Nous pouvons ensuite activer les octets identifiants suivis de toutes les donnĂ©es que nous souhaitons Ă©crire.

private fun createCommand(tag: Tag, blockOffset: Int, blockData: List<Byte>) = mutableListOf<Byte>().apply {
  add(0x22.toByte()) // flags: addressed (= UID field present) + high data rate : 00100010
  add(0x21.toByte()) // command: WRITE SINGLE BLOCK (multi read not supported)
  addAll(tag.id.toList()) // tag UID
  add((blockOffset and 0x0ff).toByte()) // first block number. add 0xff to ensure we send two bytes
  addAll(blockData) // The bytes for certificate and reference,
}.toByteArray()

Comme pour iOS, nous aurons besoin d’utiliser cette fonction en boucle en activant le bloc correct en compensation afin d’écrire tous les blocs individuellement.

Conclusion

Tandis que l’expĂ©rience utilisateur ne varie que trĂšs peu entre Android et iOS, l’implĂ©mentation de la lecture et de l’écriture des tags selon la norme ISO 15693 est nettement plus complexe pour la version Android.

Nous constatons souvent ce genre de diffĂ©rence entre les deux plateformes : le framework d’iOS est souvent plus simple d’utilisation pour les dĂ©veloppeur·euse·s puisque de nombreuses fonctionnalitĂ©s sont implĂ©mentĂ©es par dĂ©faut. Le framework d’Android en revanche, en laissant davantage de libertĂ© en termes d’implĂ©mentation, nĂ©cessite une charge de travail supĂ©rieure pour obtenir le mĂȘme rĂ©sultat.


Qu’en penses-tu?