spi

RFID

Funktionsweise

Bei RFID, abgekürzt für Radio Frequency Identification, handelt es sich um eine Technologie, die es ermöglicht automatisch und kontaktlos mit einer Karte entsprechende Informationen auszutauschen. Dies wird zum Beispiel bei weit bekannten NFC Tags verwendet, jedoch auch für Anwendungen wie das kontaktlose Zahlen per Kreditkarte.

Ein grosser Vorteil ist hierbei, dass die jeweiligen Karten/Tags, auch Transponder genannt, so klein wie ein Reiskorn sein können und keine eigene Stromversorgung benötigen. Im CrowPi ist die Komponente MFRC522 verbaut, welche ein hochfrequentes elektronisches Wechselfeld bei 13.56 MHz erzeugt, um damit einerseits die Karte mit Strom zu versorgen und andererseits mit dieser zu kommunizieren. Somit können beispielsweise die beiden beigelegten Tags des CrowPi ausgelesen und beschrieben werden. Dies funktioniert dabei mit beliebigen Daten.

Die Technologie dahinter ist sehr komplex und die entsprechenden Informationen dazu sind auf mehrere Standards aufgeteilt, hauptsächlich ISO-14443 für den allgemeinen Aufbau und die Kommunikation, sowie die entsprechenden Spezifikationen von NXP Semiconductors, dem Hersteller des Lese- und Schreibgeräts, sowie von den meisten RFID Karten. Die Einhaltung dieser Standards ist hierbei sehr wichtig, da eine RFID Karte bei falschen Schreiboperationen auch permanent beschädigt werden kann. Im Fachjargon wird das Lese- und Schreibgerät “Proximity Coupling Device” (PCD) genannt, während die verschiedenen Karten “Proximity Integrated Circuit Card” (PICC) heissen.

Es besteht hier jedoch kein Grund zur Sorge, da sich diese Komponente um all die komplexen Aufgaben im Hintergrund kümmert und eine einfache sowie zuverlässige Schnittstelle bietet, um mit solchen RFID Karten zu interagieren. So bietet RfidComponent die Möglichkeit an, Karten zu erkennen und ein beliebiges Java-Objekt darauf zu speichern oder wieder zu lesen.

Voraussetzungen

DIP Switches

Für diese Komponente werden keine spezifischen DIP-Switches benötigt, sodass diese in der Standardkonfiguration belassen werden können:

ON(links)12345678ON(rechts)12345678

Verwendung

Nachfolgend wird die Verwendung der Klasse com.pi4j.crowpi.components.RfidComponent Javadoc beschrieben.

Konstruktoren

KonstruktorBemerkung
RfidComponent(com.pi4j.context.Context pi4j)Initialisiert einen RFID Reader/Writer mit der Standard Reset-Pin und SPI Adresse sowie SPI Bandbreite für den CrowPi.
RfidComponent(com.pi4j.context.Context pi4j, int spiChannel, int spiBaud)Initialisiert einen RFID Reader/Writer mit einer benutzerdefinierten SPI Adresse und Bandbreite ohne Reset-Pin.
RfidComponent(com.pi4j.context.Context pi4j, Integer gpioResetPin, int spiChannel, int spiBaud)Initialisiert einen RFID Reader/Writer mit einem benutzerdefinierten Reset-Pin, SPI-Adresse sowie Bandbreite.

Methoden

MethodeBemerkung
boolean isNewCardPresent()Gibt einen boolschen Wert zurück, ob eine neue Karte erkannt wurde, welche noch nicht gelesen wurde.
boolean isAnyCardPresent()Gibt einen boolschen Wert zurück, ob irgendeine Karte erkannt wurde. Diese Methode erkennt im Gegensatz zu isNewCardPresent() auch bereits gelesene Karten.
RfidCard initializeCard()Erzeugt eine neue Instanz von RfidCard, welche anschliessend zur Interaktion mit der Karte verwendet werden kann. Erfordert dass isNewCardPresent oder isAnyCardPresent vorher aufgerufen wurde und true zurückgab.
void uninitializeCard()Muss zwingend nach dem Abschliessen aller Aktionen mit einer Karte aufgerufen werden, um die Kommunikation mit der Karte sauber zu beenden und neue Karten erkennen zu können.
void onCardDetected(EventHandler<RfidCard> handler)Definiert einen Event Handler, welcher jedes Mal aufgerufen wird wenn eine neue Karte erkennt wird. Als erster Parameter wird hierbei die erkannte Karte übergeben.
void waitForNewCard(EventHandler<RfidCard> handler)Gleiche Funktionsweise wie onCardDetected, blockiert jedoch den aktuellen Thread und wartet bis eine neue Karte erkannt wird, um dann einmalig einen Handler aufzurufen. Diese Methode deaktiviert den aktuellen Event Listener für onCardDetected.
void waitForAnyCard(EventHandler<RfidCard> handler)Gleiche Funktionsweise wie waitForNewCard, akzeptiert jedoch auch eine Karte welche bereits gelesen oder beschrieben wurde und führt auf dieser die Aktion durch. Diese Methode deaktiviert den aktuellen Event Listener für onCardDetected.
void reset()Setzt die Komponente auf den Ausgangszustand zurück, was bei allfälligen Problemen Abhilfe schaffen kann. Normalerweise nicht erforderlich für den normalen Betrieb.

Karten-Methoden

Die Klasse com.pi4j.crowpi.components.internal.rfid.RfidCard Javadoc welche als Rückgabewert bei initializeCard() oder als Parameter bei den Event Handlern onCardDetected(), waitForNewCard() sowie waitForAnyCard verwendet wird, bietet verschiedene Methoden um mit der erkannten Karte zu interagieren. Wichtig zu beachten ist, dass nach Ausführung eines Event Handlers automatisch uninitializeCard() aufgerufen wird, sodass sich die erkannte Karte nicht weiter nutzen lässt.

MethodeBemerkung
String getSerial()Gibt die Seriennummer der aktuellen Karte als String zurück.
int getCapacity()Gibt die maximale Kapazität der aktuellen Karte in Bytes zurück.
void writeObject(Object data)Schreibt das übergebene Objekt in die Karte. Wird dieser Befehl mehrfach aufgerufen, so wird dieses jeweils überschrieben. Das Objekt muss das Interface Serializable implementieren. Wirft eine RfidException falls die Operation nicht erfolgreich war.
Object readObject()Liest das auf der Karte gespeicherte Objekt aus und gibt dieses als generischen Object-Typ zurück. Wirft eine RfidException falls die Operation nicht erfolgreich war.
T readObject(Class<T> type)Gleiche Funktionsweise wie readObject(), aber gibt das Objekt gleich mit dem gewünschten Typ zurück. Wurde zum Beispiel mit writeObject vorher ein Animal gespeichert, so gibt readObject(Animal.class) direkt die Animal-Instanz zurück.

Beispielapplikation

Die nachfolgende Beispielapplikation definiert eine Klasse namens Person, um von einer beliebigen Person den Vornamen und Nachnamen, die Adresse sowie das Geburtsdatum zusammen mit einer zufällig generierten ID zu speichern. Nun werden zuerst zwei Personen generiert und in den Variablen personA sowie personB gespeichert, welche beide über verschiedene Daten verfügen.

Anschliessend wird mit der waitForNewCard Methode die Anwendung so lange blockiert, bis eine neue Karte vom RFID Gerät erkannt wurde. Sobald dies der Fall ist, wird die übergebene Funktion ausgeführt, welche die Instanz personA von der Person-Klasse auf der Karte mit writeObject speichert. Es kann jedes beliebige Java-Objekt gespeichert werden, solange dieses das Serializable mit implements Serializable implementiert. Hierbei gilt zu beachten, dass diese Methode eine RfidException werfen kann, welche abgefangen werden muss. Diese tritt auf, wenn die Karte nicht ordnungsgemäss beschrieben werden konnte.

Nachdem die erste Karte beschrieben wurde, wird der gleiche Prozess für die zweite Karte wiederholt. Da mit waitForNewCard grundsätzlich nur neue Karten erkannt werden und eine Karte nach erfolgter Interaktion in einen Schlafzustand geht, kann hier garantiert werden, dass nicht sofort die zweite Person auf die gleiche Karte geschrieben wird, sondern sich eine neue Karte annähern oder die bestehende Karte kurzfristig entfernt werden muss.

Sobald beide Karten beschrieben wurden, wird ein Event Handler mit onCardDetected registriert, welcher asynchron jedes Mal aufgerufen wird, wenn eine neue Karte erkannt wurde. Da der RFID-Standard ein Verfahren gegen Kollisionen besitzt, können sich sogar mehrere Karten gleichzeitig auf dem Lese- und Schreibgerät befinden. Bei jeder ermittelten Karte wird mit readObject(Person.class) versucht, eine vorher gespeicherte Instanz der Person-Klasse auszulesen und in die Variable person zu speichern. Wenn dies gelingt, so wird die Person auf der Konsole ausgegeben. Auch hier muss eine allfällige RfidException aufgefangen werden, welche zum Beispiel auftritt, wenn die Daten nicht gelesen werden können oder korrupt sind.

Nach der Registrierung des Event Handlers schläft die Applikation für 30 Sekunden, um genug Zeit zu geben, die zwei verwendeten Karten auszuprobieren. Nach Ablauf der Zeit wird der Event Handler wieder sauber entfernt und die Applikation beendet sich.

Pfad zum Codebeispiel: src/main/java/com/pi4j/crowpi/applications/RfidApp.java
Auf GitHub ansehen
package com.pi4j.crowpi.applications;

import com.pi4j.context.Context;
import com.pi4j.crowpi.Application;
import com.pi4j.crowpi.components.RfidComponent;
import com.pi4j.crowpi.components.exceptions.RfidException;

import java.io.Serializable;
import java.time.LocalDate;
import java.util.UUID;

/**
 * This example first waits for the user to approach two different RFID cards and writes a different instance of a {@link Person} class into
 * each card. After doing so, the application registers an event listener which reads any approached cards and outputs the read and
 * deserialized {@link Person} class. The example will then sleep for 30 seconds to give the user some time to test the two tags or approach
 * other tags before shutting down cleanly by de-registering the event listener.
 */
public class RfidApp implements Application {
    @Override
    public void execute(Context pi4j) {
        // Initialize RFID component
        final var rfid = new RfidComponent(pi4j);

        // Generate two persons which will be written to tags
        // Values taken from fakenamegenerator.com, these are not real identities :-)
        final var personA = new Person(
            "Robert", "Parson",
            "21 Southern Street, Bohemia, NY 11716",
            LocalDate.of(1948, 2, 23)
        );
        final var personB = new Person(
            "Lisa", "Knee",
            "1944 Veltri Drive, Old Harbor, AK 99643",
            LocalDate.of(2000, 8, 11)
        );

        // Wait for the user to approach a first card and write person A
        System.out.println("Please approach a first card to write person A");
        rfid.waitForNewCard(card -> {
            try {
                card.writeObject(personA);
                System.out.println("Person A was written to card " + card.getSerial());
            } catch (RfidException e) {
                System.out.println("Could not write card for person A: " + e.getMessage());
            }
        });

        // Wait for the user to approach a second card and write person B
        System.out.println("Please approach a second card to write person B");
        rfid.waitForNewCard(card -> {
            try {
                card.writeObject(personB);
                System.out.println("Person B was written to card " + card.getSerial());
            } catch (RfidException e) {
                System.out.println("Could not write card for person B: " + e.getMessage());
            }
        });

        // Register event listener to detect card in proximity
        rfid.onCardDetected(card -> {
            // Print serial number and capacity of approached card
            System.out.println("Detected card with serial " + card.getSerial() + " and capacity of " + card.getCapacity() + " bytes");

            // Read `Person` object from card and print it
            try {
                final var person = card.readObject(Person.class);
                System.out.println("Read person from card: " + person);
            } catch (RfidException e) {
                System.out.println("Could not read person from card: " + e.getMessage());
            }
        });

        // Sleep for 30 seconds to give the user some time to approach various cards
        System.out.println("Waiting 30 seconds for new RFID cards, try switching between the previously written cards...");
        sleep(30000);

        // Cleanup by unregistering the event handler
        rfid.onCardDetected(null);
    }

    /**
     * This class represents a person with a random unique identifier (UUID), first name, last name, address and date of birth.
     * There is no magic involved here, except the implementation of the {@link Serializable} interface which is needed.
     * You can read and write any kind of class from/to a RFID card as long as it is serializable.
     */
    private static final class Person implements Serializable {
        private final UUID uuid;
        private final String firstName;
        private final String lastName;
        private final String address;
        private final LocalDate dateOfBirth;

        public Person(String firstName, String lastName, String address, LocalDate dateOfBirth) {
            // Generate a random UUID for this person
            this.uuid = UUID.randomUUID();

            // Store all other attributes in this class
            this.firstName = firstName;
            this.lastName = lastName;
            this.address = address;
            this.dateOfBirth = dateOfBirth;
        }

        public UUID getUuid() {
            return uuid;
        }

        public String getFirstName() {
            return firstName;
        }

        public String getLastName() {
            return lastName;
        }

        public String getAddress() {
            return address;
        }

        public LocalDate getDateOfBirth() {
            return dateOfBirth;
        }

        /**
         * Generate a nice description for this object when converted to a string which contains all attributes
         *
         * @return Human-readable string describing this person
         */
        @Override
        public String toString() {
            return getUuid() + ": " + getFirstName() + " " + getLastName() + " @ " + getAddress() + ", born " + getDateOfBirth();
        }
    }
}

Weitere Möglichkeiten

  • Das Beispiel zu einer Art Zutrittskontrolle ausbauen und beispielsweise nur Personen mit einem bestimmten Attribut zulassen.

  • Bei der Erkennung von Personen könnte auf dem LCD Display ein kleiner Begrüssungstext angezeigt werden.

  • Statt dem Speichern von Personen könnten auch andere Daten auf einer Karte abgelegt werden, zum Beispiel eine (nicht sehr sichere) Implementation einer Bank wo auf jeder Karte der Inhaber sowie der aktuelle Kontostand gespeichert wird. Hierfür wäre die Methode waitForAnyCard praktisch, um eine bereits aufgelegte Karte erneut zu beschreiben.