KĂŒrzlich hatten wir bei Liip die Gelegenheit, mit der Bobst SA zusammenzuarbeiten. Das Unternehmen verkauft GerĂ€te, die authentifiziert werden mĂŒssen, und wollte seinen aktuellen Prozess verbessern, bei dem die Kunden die GerĂ€te mit einem speziellen Tool scannen und manuell eine Referenz im Webportal von Bobst angeben mĂŒssen. Beim neuen Verfahren werden jetzt RFID-Tags verwendet. Unser Ziel bestand darin, eine mobile App zu entwickeln, mit der diese Tags gescannt werden können und die sich automatisch mit dem Konto des Benutzers auf den Servern von Bobst verbindet, um diesem das richtige Zertifikat zuzuweisen. Mit der App kann Bobst dieses Zertifikat dann auf das GerĂ€t schreiben.
Technologien
Wie bei den meisten Apps, die wir bei Liip entwickeln, haben wir uns auch bei dieser dafĂŒr entschieden, beide Plattformen mit nativen Technologien zu entwickeln â die Android-App mit Kotlin (Kotlin Version 1.4.10, Ziel Android 11, API 30) und die iOS-App mit Swift (Swift 5, Ziel iOS 14).
Zumeist bevorzugen wir diese Technologien gegenĂŒber plattformĂŒbergreifenden Alternativen aus verschiedenen GrĂŒnden, unter anderem wegen der Leistung, des zuverlĂ€ssigen UI-Erlebnisses fĂŒr Benutzer beider Plattformen und der Wartbarkeit. In diesem Fall ist uns die Wahl sogar noch einfacher gefallen, da die NFC-Funktionen Teil der integrierten Systembibliotheken sind. Eine plattformĂŒbergreifende App wĂŒrde daher sowieso ĂberbrĂŒckungsmodule fĂŒr beide Plattformen erfordern.
RFID Standards
RFID steht fĂŒr Radiofrequenz-Identifikation, und einfache RFID-Tags benötigen nicht einmal eine Stromversorgung. RFID-GerĂ€te können nach verschiedenen Standards implementiert werden. Zu diesen gehört beispielsweise die ISO 15693-3, die die Tags von Bobst nutzen.
NFC oder Near Field Communication, st eine prĂ€zisere Untergruppe von RadiofrequenzgerĂ€ten. NFC-GerĂ€te können sowohl Tags als auch Reader sein, und sie können aktiv miteinander kommunizieren. Moderne Smartphones sind fast alle NFC-fĂ€hig. NFC-GerĂ€te kommunizieren standardmĂ€ssig ĂŒber NDEF (NFC Data Exchange Format), bei der Kommunikation mit RFID-ISO-Standards muss jedoch ein anderes Format verwendet werden, zum Beispiel: NFC Typ 2 (auch bekannt als Typ A) und Typ 4 (auch bekannt als Typ B) werden fĂŒr die Kommunikation mit dem RFID-ISO-Standard 14443 verwendet
.
In unserem Fall werden wir fĂŒr die Kommunikation mit unserem ISO 15693-Tag den NFC-Typ 5 (auch bekannt als Typ V) verwenden.
NFC-UnterstĂŒtzung fĂŒr Android seit Mai 2021
Typ | Beschreibung |
---|---|
NfcA | Bietet Zugriff auf NFC-A-Eigenschaften (ISO 14443-3A) und E/A-VorgÀnge. |
NfcB | Bietet Zugriff auf NFC-B-Eigenschaften (ISO 14443-3B) und E/A-VorgÀnge. |
NfcF | Bietet Zugriff auf NFC-F-Eigenschaften (JIS 6319-4) und E/A-VorgÀnge. |
NfcV | Bietet Zugriff auf NFC-V-Eigenschaften (ISO 15693) und E/A-VorgÀnge. |
IsoDep | Bietet Zugriff auf ISO-DEP-Eigenschaften (ISO 14443-4) und E/A-VorgÀnge. |
Ndef | Bietet Zugriff auf NDEF-Daten und VorgÀnge auf NFC-Tags, die als NDEF formatiert wurden. |
NdefFormatable | Bietet einen Formatierungsvorgang fĂŒr Tags, die NDEF-formatierbar sein können. |
MifareClassic | Bietet Zugriff auf MIFARE-Classic-Eigenschaften und E/A-VorgĂ€nge. â ïž Optional: Es gibt keine Garantie dafĂŒr, dass ein Android-Smartphone mit NFC diesen Typ unterstĂŒtzt. |
MifareUltralight | Bietet Zugriff auf MIFARE-Ultralight-Eigenschaften und E/A-VorgĂ€nge. â ïž Optional: Es gibt keine Garantie dafĂŒr, dass ein Android-Smartphone mit NFC diesen Typ unterstĂŒtzt. |
NFC-UnterstĂŒtzung fĂŒr iOS seit Mai 2021
Typ | Beschreibung |
---|---|
NFCISO7816Tag | Ein Interface zur Interaktion mit einem ISO 7816-Tag. |
NFCISO15693Tag | Ein Interface zur Interaktion mit einem ISO 15693-Tag. |
NFCFeliCaTag | Ein Interface zur Interaktion mit einem FeliCaâą-Tag. |
NFCMiFareTag | Ein Interface zur Interaktion mit einem MIFAREÂź-Tag. |
NFCNDEFTag | Ein Interface zur Interaktion mit einem NDEF-Tag. |
Implementierung auf beiden Plattformen
In den obigen Tabellen ist ersichtlich, dass sowohl Android als auch iOS die RFID-Chips von Bobst unterstĂŒtzen. Wir mussten fĂŒr Android den NfcV
-Typ und fĂŒr iOS den NFCISO15693Tag
-Typ verwenden.
FĂŒr beide Systeme musste zudem die entsprechende Berechtigung fĂŒr den Zugriff auf die NFC-Funktionen erteilt werden, aber sowohl Android- als auch iOS-Dokumente decken diesen Bereich im Detail ab.
Mit anderen Worten: Unsere Implementierung musste drei VorgĂ€nge unterstĂŒtzen:
- ein Tag in Reichweite identifizieren
- den ihalt des Tags lesen
- neuen Inhalt auf das Tag schreiben
Wir haben diese VorgÀnge wie folgt verwendet:
- Identifizieren und lesen: Abwarten, bis ein Tag identifiziert ist (das System erhÀlt dessen individuelle ID), und dann mit dieser individuellen ID einen Lesevorgang anfordern.
- Das Ergebnis des Lesevorgangs an den Server von Bobst senden, um ein Zertifikat zu erhalten (hier kein RFID-Vorgang).
- Identifizieren und schreiben: Ein schliessendes Tag identifizieren und sicherstellen, dass es die gleiche ID hat wie das zuvor gelesene (es ist dasselbe Tag). Danach das Zertifikat darauf schreiben.
Schritt 1: Ein Tag identifizieren
Mit beiden Systemen kann die ID eines in der NĂ€he befindlichen Tags auf einfache Weise ĂŒbermittelt werden. Sie unterscheiden sich jedoch leicht:
Bei iOS muss das System aufgefordert werden, die Identifizierung von RFID-Tags zu starten. An dieser Stelle wird eine kleine modale Ansicht angezeigt und das System sucht nach einem Tag. GrundsÀtzlich reichen die folgenden Angaben aus:
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()
}
Bei Android wird das System ĂŒber den Android-Intent-Mechanismus gesteuert. Das bedeutet, dass man die App registrieren muss, damit sie Scan-Intents des entsprechenden NFC-Typs akzeptiert. Danach erhĂ€lt die App eine entsprechende Meldung. Aus diesem Grund gibt es keine Starttaste. Wenn sich ein Tag in Reichweite befindet, wird die App, selbst wenn sie geschlossen ist, geöffnet und umgehend benachrichtigt!
Die NFC-FÀhigkeit im Verzeichnis der AktivitÀt registrieren, welche die Informationen empfangen soll:
<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"
/>
Die Filterliste wird in die Ressourcendatei eingefĂŒgt, auf die wir im Verzeichnis verwiesen haben, z. B. xml/filter_nfc/xml
(in unserem Fall verwenden wir einfach den Typ NfcV
, wie in der Tabelle oben):
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.NfcV</tech>
</tech-list>
</resources>
Our activity will be notified 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
}
}
Die beiden Plattformen unterscheiden sich deutlich in der Bedienung, funktionieren aber dennoch auf folgende Weise: Die obigen Schritte ergeben lediglich ein Tag-Objekt, das die ID des erkannten Tags enthĂ€lt. Nun mĂŒssen wir damit lesen oder schreiben, was wir brauchen.
Schritt 2: Ein Tag lesen
Bei iOS beinhaltet das System bereits eine Basisimplementierung der ISO 15693, was alles einfacher macht.
Die konkrete Implementierung hÀngt von den spezifischen Anforderungen ab. GrundsÀtzlich besteht die Idee aber darin, die generische Tag-ID in die richtige ISO-Implementierung, welche in die Core-NFC-Bibliothek integriert ist, zu konvertieren:
if case let NFCTag.iso15693(tagType5) = tag {
}
Und dann integrierte Funktionen wie tag.readMultipleBlocks()
zu verwenden. Diese Funktion akzeptiert eine Reihe von Argumenten, einschliesslich hilfreicher Request-Flags.
tag.readMultipleBlocks(requestFlags: [.highDataRate], blockRange: NSRange(location: 0, length: NfcService.MAXREADBLOCK)) { data, error in
... // check for error and get data using the variables above
}
An dieser Stelle besteht die einzige Schwierigkeit darin, herauszufinden, wie die Bytes angeordnet wurden. Bobst hat uns eine umfassende Dokumentation zur VerfĂŒgung gestellt, damit wir ihre Tags richtig lesen können. In unserem Fall waren sie in der Regel in Viererblöcken, die nach dem Lesen jeweils einzeln umgedreht werden mussten, angeordnet.
Bei Android bietet das System keine integrierte Implementierung der ISO-Standard-Appart-Funktionen zum Senden von unbearbeiteten Byte-Blöcken. Diese Rohblöcke beinhalten eine Befehlsanfrage fĂŒr das Tag, gegliedert nach ISO 15693 https://www.iso.org/standard/73602.html.
Der erste Schritt Àhnelt dem bei iOS: Ermittlung des unserem Tag entsprechenden Systemtyps
val nfcVTag = NfcV.get(tag) ?: return
Der nÀchste Teil ist allerdings ein bisschen komplizierter. Das 70 Seiten umfassende Dokument zur Beschreibung der ISO 15693 zeigt, dass unter allen möglichen Befehlen die von iOS integrierten readMultipleBlocks
tatsÀchlich durch Senden des Befehlscodes 0x23
ausgegeben werden.
Darin wird zudem mitgeteilt, dass zum Senden eines Befehls folgende Informationen erforderlich sind:
â Flags
â Befehlscode
â Felder fĂŒr obligatorische und optionale Parameter, je nach Befehl
In unserem Fall mĂŒssen die Flags so gesetzt werden, dass ein «adressierter» Befehl ausgegeben wird, d. h. der Befehl nimmt die Tag-ID als Argument und wird nur auf diesem bestimmten Tag ausgefĂŒhrt. GemĂ€ss Dokumentation entspricht dieses Flag dem sechsten Bit des Flag-Bytes. Zudem wird das High-Rate-Flag (zweites Bit) gesetzt. Unser gesamtes Flag-Byte ist 00100010, was 0x22
entspricht.
Folglich besteht unsere Android-Anfrage aus folgendem Byte-Array:
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()
Wir empfangen einfach die Antwort mit der transceive
-Methode und ĂŒberprĂŒfen das erste Byte zur Sicherstellung des Erfolgs.
nfcVTag.connect()
val responseBytes = nfcVTag.transceive(cmd)
if (responseBytes.first() != 0x00.toByte()) {
return NfcResponse.Error.Read
}
nfcVTag.close()
Das Response-Byte-Array konnte nun gemÀss der Bobst-Dokumentation geparst werden, um die richtigen Tag-Informationen zu extrahieren.
Schritt 3: Auf ein Tag schreiben
Das Schreiben auf ein Tag ist ungefÀhr derselbe Prozess wie das Lesen (d. h. ein einfacher bei iOS und ein komplexerer bei Android).
Bei iOS stellen wir alle Informationen zum Schreiben in einem Byte-Array zusammen. Leider waren die MehrfachschreibvorgÀnge bei den Bobst-Tags deaktiviert, weshalb wir die Bytes einzeln senden mussten. Wir haben einfach die NFC-Core-Funktion transceive
in einem Loop verwendet. Die Funktion ĂŒbernimmt wiederum alle notwendigen Flags
tag.writeSingleBlock(requestFlags: [.highDataRate, .address], blockNumber: UInt8(startBlock), dataBlock: dataBlock) { error in
... // Simply check for error and react accordingly
}
Bei Android
Wie zuvor mĂŒssen wir den entsprechenden Byte-Befehl senden und die transceive
-Funktion verwenden. Wie zuvor nutzt dieser Befehl einen Flag-Wert von 0x22
. GemÀss der ISO 15693-Dokumentation wird die writeSingleBlock
-Anfrage mit der Eingabe von 0x21
als Befehlsbyte gestellt. Wie zuvor mĂŒssen wir die Tag-ID eingeben, da wir einen «adressierten» Befehl ausfĂŒhren. Dann können wir die ID-Bytes und anschliessend alle Daten, die wir schreiben wollen, eingeben.
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()
Wie bei iOS mĂŒssen wir diese Funktion in einem Loop aufrufen und dabei den richtigen Block-Offset eingeben, um alle Blöcke nacheinander zu schreiben.
Schlussfolgerung
Die Benutzererfahrung unterscheidet sich zwischen Android und iOS zwar nur geringfĂŒgig, aber das Lesen und Schreiben auf ISO 15693-Tags ist bei Android deutlich komplexer.
Solche Divergenzen beobachten wir oft zwischen den beiden Plattformen: In der Regel bietet das iOS-Framework eine einfache Erfahrung fĂŒr Entwickler, weil es fĂŒr viele Funktionen Standardimplementierungen bereitstellt. Android hingegen lĂ€sst oft mehr Freiheiten bei der Implementierung, was mit zusĂ€tzlichem Aufwand zum Erreichen des gleichen Ergebnisses einhergeht.