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 :
- 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.
- 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).
- 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.