Karte, Bitte! RFID zur Steuerung - MQTT Part V

Karte, Bitte! RFID zur Steuerung - MQTT Part V

Zeit für den Endspurt. In diesem letzten Blogpost der MQTT Reihe zeige ich dir wie du einen RFID-Reader anschließt um damit den LED-Streifen zu steuern.

Bearbeitungszeit: 1 - 3 Stunden

Lernziele:

  • RFID-Reader vom ESP32 (bzw. Arduino allgemein) ansprechen
  • Python Reaktion auf MQTT Topic mit mehreren Nachrichten
  • Auffrischung McLighting MQTT Kommandos

Die gesamte Blogreihe befasst sich mit der Frage, wie du Geräte zuhause per MQTT miteinander vernetzen kannst. Hier findest du nochmal die vorherigen Beiträge:

Teil 1 - MQTT auf dem Raspberry Pi: https://plantprogrammer.de/mqtt-auf-dem-raspberry-pi-mosquitto/
Teil 2 - MQTT-Kommunikation, aber sicher: TLS und Berechtigungen: https://plantprogrammer.de/mqtt-auf-dem-raspberry-pi-mosquitto-part-ii/
Teil 3 - Es Werde Licht mit McLighting
https://plantprogrammer.de/mqtt-auf-dem-raspberry-pi-mosquitto-part-iii/
Teil 4 - Heißkaltes Farbspiel
https://plantprogrammer.de/mqtt-auf-dem-raspberry-pi-mosquitto-part-iv/

Hardwarevoraussetzungen

Dieser Beitrag geht davon aus, dass der LED-Streifen aus Teil 3 und Raspberry Pi mit Mosquitto aus Teil 1 und Teil 2 vorhanden und installiert sind. Zusätzliche Hardware, die notwendig ist für diesen Teil:

* Es gibt die auch einzeln, der Stückpreis ist dann aber unverschämt hoch. Und mit RFID kann man coole Sachen machen :)

die abgebildeten Preise sind tagesaktuell am 26. April 2021

Als Amazon-Partner verdiene ich an qualifizierten Verkäufen, wenn du die Links verwendest und die Produkte kaufst. Das kostet nicht mehr als wenn du sie einfach so kaufst und hilft mir mehr Zeit für coole Tutorials aufzubringen. Wenn du das nicht möchtest, sind daneben jeweils die Links ohne Partnerprogramm gelistet.

Verkabelung

Achtung: RFID-Shields kommen in vielen verschiedenen Varianten und die Pin-Belegung kann selbst beim gleichen Lieferanten variieren. Dieses Mal ist es beim Verkabeln wichtig, dass du die tatsächliche Pin-Beschriftung mit der Tabelle unten abgleichst.

Schemazeichnung eines ESP32 DevBoards mit Kabelverbindung zu einem RFC522 RFID-Shields. (Verkabelung in beiliegender Tabelle.)
Abb. 1: Verkabelung des RFID-Shields mit dem ESP32 devkit.

RFID-Shield Pin ESP32 Pin Kabelfarbe im Bild
VCC 3V3 Rot
RST D22 Weiß
GND GND Schwarz
IRQ -- Nicht verbunden
MISO D19 Braun
MOSI D23 Orange
SCK D18 Gelb
NSS / SDA D21 Grün

Arduino IDE Paketinstallation und Test

Als erstes benötigst du das RFID-Arduino Paket. Öffne in der Arduino IDE den Menüpunkt "Manage Libraries" unter Tools. In der Bibliotheksverwaltung kannst du jetzt nach "mfrc" suchen. Installiere das Paket, es heißt MFRC522 by GithubCommunity, in Version 1.4.8 (neueste).

Um zu testen, ob die Verkabelung richtig war und um die Tag-IDs aufzuschreiben, kannst du das mitgelieferte Beispielprojekt benutzen. Suche im Menü File->Examples->MFRC522 und klicke auf das Beispielprojekt ReadNUID. Dieses Beispielprojekt liest RFID-Tags und gibt die Karten-ID auf dem seriellen Monitor aus. Die beiden Pin-Definition in Zeilen 34/35  musst du noch anpassen. Der SourceSelect-Pin bekommt den Wert 21 (s. Tabelle oben) und der ReSeT-Pin den Wert 22.

Verbinde den ESP mit deinem Computer. Wähle unter Tools->Port den richtigen Port aus (wie in den vergangenen Posts, ist es unter Windows üblicherweise COMX, unter OS X /dev/cu.usbserial-XXXX und unter Linux /dev/ttyUSBX). Zu guter Letzt, klicke den Upload-Button. Sobald das Programm auf dem ESP installiert ist und dieser neugestartet hat, öffne den seriellen Monitor (Die Baudrate sollte für diesen Beispielcode auf 9600 gestellt sein, Grund siehe Zeile 45).

Wenn du jetzt einen RFID-Tag in die Nähe des Readers bringst, sollte die Karten-UID im seriellen Monitor angezeigt werden

Abb. 2: Arduino serieller Monitor mit Ausgabe aus dem Beispielprojekt. Die Hex ID wird später wichtig.

Notiere so von allen RFID-Tags, die du nachher verwenden möchtest, die hexadezimalen Karten-IDs. Im Beispielbild (Abb. 2) wäre das zum Beispiel E4D5E158. Die Leerzeichen kannst du weglassen, weil wir nachher per MQTT eh nur die 8 Zeichen verschicken wollen.

Arduino Code

Die Kombination aus diesem Beispielprojekt und der Arduino-MQTT-Anbindung vom letzten Mal ist alles was wir brauchen, um das Detektieren von RFID-Tags per MQTT mitzuteilen. Wenn du dich nach den letzten Blogposts wohlfühlst, das selber zu versuchen, kannst du den Code aus Part 4 kopieren, alles was mit DHT und dem Messintervall zu tun hat löschen und alles im folgenden Codeblock an den richtigen Stellen einfügen. Keine Sorge, wenn es nicht klappt, ist der gesamte Code mit Erklärungen gleich darunter.

#include <SPI.h>
#include <MFRC522.h>

#define SS_PIN 21
#define RST_PIN 22

MFRC522 rfid(SS_PIN, RST_PIN); // Instance of the class
MFRC522::MIFARE_Key key;


void setup() {
  // ...
  // RFID setup
  SPI.begin(); // Init SPI bus
  rfid.PCD_Init(); // Init MFRC522 
  delay(10);
  for (byte i = 0; i < 6; i++) {
    key.keyByte[i] = 0xFF;
  } 
  Serial.println("RFID setup done");
}

void array_to_string(byte array[], unsigned int len, char buffer[])
{
  // helper function to convert RFID uuids to char[]
  for (unsigned int i = 0; i < len; i++)
  {
    byte nib1 = (array[i] >> 4) & 0x0F;
    byte nib2 = (array[i] >> 0) & 0x0F;
    buffer[i * 2 + 0] = nib1  < 0xA ? '0' + nib1  : 'A' + nib1  - 0xA;
    buffer[i * 2 + 1] = nib2  < 0xA ? '0' + nib2  : 'A' + nib2  - 0xA;
  }
  buffer[len * 2] = '\0';
}

void loop() {
  // ...
  // Reset the loop if no new card present on the sensor/reader. This saves the entire process when idle.
  if ( ! rfid.PICC_IsNewCardPresent())
      {return;}

  // Verify if the NUID has been read
  if ( ! rfid.PICC_ReadCardSerial())
      {return;}
    
  char str[rfid.uid.size * 2 + 1]; 
  array_to_string(rfid.uid.uidByte, rfid.uid.size, str);
  Serial.print("Card UID: ");
  Serial.println(str);
    
  message.retained = false;
  message.payload = str;
  message.payloadlen = strlen(str);
  client.publish(mqtt_topic_uid, message);
    
  // Halt PICC
  rfid.PICC_HaltA();
}

Im Wesentlichen besteht die Änderung aus vier Teilen:

  1. Includes und Definitionen
  2. Start der Bibliothek im setup
  3. Hilfsfunktion um die UID der Karte in einen String zu konvertieren
  4. Regelmäßiges Checken im loop ob eine Karte vorliegt und falls ja Senden der UID per MQTT.
#include <WiFiClientSecure.h>
#include <Countdown.h>
#include <IPStack.h>
#include <MQTTClient.h>
#include <SPI.h>
#include <MFRC522.h>


const char* CLIENT_CERT = "-----BEGIN CERTIFICATE-----\n"
"MIIC2zXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n"
// ...
"XXXXXXXXXXXXXXXXXXX\n"
"-----END CERTIFICATE-----";

const char* CLIENT_KEY = "-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEowXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n"
// ...
"XXXXXXXXXXXXXXXXXXX\n"
"-----END RSA PRIVATE KEY-----\n";

const char* CA_CERT = "-----BEGIN CERTIFICATE-----\n"
"MIIDRTXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n"
// ...
"XXXXXXXXXXXXXXXXXXX\n"
"-----END CERTIFICATE-----\n";

const char* ssid = ""; // Enter your WiFi name
const char* password =  ""; // Enter WiFi password

char* mqtt_host = ""; // IP address
uint16_t mqtt_port = 8883;
char* mqtt_user = "rfid";
char* mqtt_pw = "12345";
char* mqtt_topic_uid = "sensors/bathroom/rfid";
char* mqtt_clientID = "rfid_shield";

#define SS_PIN 21
#define RST_PIN 22

MFRC522 rfid(SS_PIN, RST_PIN); // Instance of the class
MFRC522::MIFARE_Key key;


WiFiClientSecure wifi;
IPStack ipstack(wifi);
MQTT::Client<IPStack, Countdown, 250, 1> client = MQTT::Client<IPStack, Countdown, 250, 1>(ipstack);
MQTT::Message message;

void setup(){
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
  Serial.println(WiFi.localIP());
  wifi.setCACert(CA_CERT);
  wifi.setCertificate(CLIENT_CERT);
  wifi.setPrivateKey(CLIENT_KEY);
  connectMQTT();

  // RFID setup
  SPI.begin(); // Init SPI bus
  rfid.PCD_Init(); // Init MFRC522 
  delay(10);
  for (byte i = 0; i < 6; i++) {
    key.keyByte[i] = 0xFF;
  } 
  Serial.println("RFID setup done");
} 

void connectMQTT()
{
  if(!client.isConnected()){
    char buffer[128];
    sprintf(buffer, "Connecting to: %s:%d", mqtt_host, mqtt_port);
    Serial.println(buffer);
    int rc = ipstack.connect(mqtt_host, mqtt_port);
    if (rc != 1)
    {
      Serial.print("rc from TCP connect is ");
      Serial.println(rc);
    }
    MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
    data.MQTTVersion = 3;
    data.clientID.cstring = mqtt_clientID;
    data.username.cstring = mqtt_user;
    data.password.cstring = mqtt_pw;
  
  
    sprintf(buffer, "Connecting as: %s", mqtt_clientID);
    Serial.println(buffer);
    rc = client.connect(data);
    if (rc != 0)
    {
      Serial.print("rc from MQTT connect is ");
      Serial.println(rc);
      return;
      
    }
    Serial.println("MQTT connected");
  }
}

void array_to_string(byte array[], unsigned int len, char buffer[])
{
  for (unsigned int i = 0; i < len; i++)
  {
    byte nib1 = (array[i] >> 4) & 0x0F;
    byte nib2 = (array[i] >> 0) & 0x0F;
    buffer[i * 2 + 0] = nib1  < 0xA ? '0' + nib1  : 'A' + nib1  - 0xA;
    buffer[i * 2 + 1] = nib2  < 0xA ? '0' + nib2  : 'A' + nib2  - 0xA;
  }
  buffer[len * 2] = '\0';
}

void loop()
{
  if (!client.isConnected())
    {
      connectMQTT();
    }
     // Reset the loop if no new card present on the sensor/reader. This saves the entire process when idle.
    if ( ! rfid.PICC_IsNewCardPresent())
      {return;}

    // Verify if the NUID has been readed
    if ( ! rfid.PICC_ReadCardSerial())
      {return;}
    
    char str[rfid.uid.size * 2 + 1]; 
    array_to_string(rfid.uid.uidByte, rfid.uid.size, str);
    Serial.print("Card UID: ");
    Serial.println(str);
    
    message.retained = false;
    message.payload = str;
    message.payloadlen = strlen(str);
    client.publish(mqtt_topic_uid, message);
    
    // Halt PICC
    rfid.PICC_HaltA();
}

In Zeile 5 & 6 wird die eben installierte MFRC522 Bibliothek geladen und die SPI-Bibliothek für die Kommunikation mit dem RFID-Reader.
Zeilen 9 - 35 sind wieder Konfiguration für WiFi und MQTT. Hier müssen MQTT user und pw einem existierenden Nutzer im Mosquitto entsprechen (für Hilfe s. Part II - Benutzerberechtigungen). Als Topic kannst du etwas eintragen, was dir als RFID-Topic sinnvoll erscheint (das brauchst du im nächsten Abschnitt nochmal, also merken). Und die Client-ID ist wie immer frei wählbar, sollte aber nicht mit einer bestehenden in deinem wachsenden Heimnetzwerk übereinstimmen.
Zeile 37 & 38 definieren den Pin an dem SourceSelect (NSS in der Tabelle) und ReSeT vom RFID-Reader angeschlossen sind. In diesem Fall wie in der Tabelle oben gezeigt 21 und 22.
Zeile 40 & 41 legen die notwendigen Variablen an um später mit dem RFID-Reader zu interagieren. rfid stellt die Verbindung und Kommunikation zur Verfügung und key legt fest, mit welchem (unsicheren nach heutigen Standards) Schlüssel die Daten auf der Karte (RFID-Tag) verschlüsselt werden sollen.
Zeilen 44 - 47 stellen entsprechend wie auch beim letzten Mal Variablen für die WiFi- und MQTT-Verbindung zur Verfügung.
In der ersten Hälfte der setup-Methode wird wie zuvor die WiFi- und dann die MQTT-Verbindung aufgebaut (Zeilen 50 - 59).
Danach wird in Zeile 62 die serielle Verbindung per SPI gestartet und gleich darauf in Zeile 63 für den RFID-Reader initialisiert. Die kurze Pause von 10 ms (Zeile 64) ist in der Dokumentation empfohlen.
Der Standard-Key, der keine Verschlüsselung bedeutet, wird in den Zeilen 65 - 67 auf 0xFFFFFFFFFFFF gesetzt also 6 bytes mit jeweils 255 als Wert.
connectMQTT() (Zeilen 71 - 102) ist identisch zum letzten Mal.
Die array_to_string-Methode in Zeile 104 - 114 sieht furchtbar kompliziert aus, ist aber ein Standard Codeschnipsel, der dafür sorgt, dass ein Byte-Array (erster Parameter), also eine Liste von Bytes wie zum Beispiel die Karten-ID in einen String-Buffer (dritter Parameter) geschrieben wird. Dafür schaut es sich für jedes Byte (Schleife läuft so oft wie der zweite Parameter angibt) die beiden Bytehälften separat an und sucht das entsprechende Zeichen 0-9 bzw. A-F. Dieses Zeichen wird dann an die entsprechende Stelle im Buffer geschrieben. Zum Schluss wird noch ein sogenanntes Nullbyte gesetzt ('\0'), damit das Programm für die Ausgabe weiß, dass der String hier zu Ende ist.
Im loop ist jetzt fast alles neu. Lediglich Zeilen 118 - 121 sind aus Part IV bekannt und stellen die MQTT-Verbindung neu her, falls sie mal abbricht.
Zeile 123/124 überspringt den Rest, wenn dem RFID-Reader keine neue Karte präsentiert wurde.
Wenn eine Karte detektiert wurde, wird in 127/128 überprüft ob die Seriennummer der Karte gelesen werden kann und überspringt den Rest, falls das nicht der Fall ist.
In Zeile 130 wird eine Variable str angelegt, die Platz für 2 Zeichen pro Byte der Karten-ID plus ein Nullbyte-Zeichen hat. Diese Variable ist unbeschrieben und weiß bisher nur wie viel Platz sie hat.
In Zeile 131 wird die array_to_string Methode aufgerufen. Als ersten Parameter übergibst du das ByteArray, das die Karten-ID abbildet, als Länge die Anzahl an Bytes (hier 4) und als Buffer die gerade erstellte Variable. Sobald die Methode durchgelaufen ist, steht in der Variable str die Karten-ID als String.
Zeilen 135 - 138 fügen jetzt diesen String in eine MQTT-Message ein (Zeile 136) und veröffentlichen diese dann unter dem oben angegebenen Topic.
Zuletzt wird der RFID-Karte mitgeteilt, dass sie anhalten soll. Das bedeutet, dass sie, solange sie in der Nähe des Readers bleibt, nicht noch einmal ausgelesen werden kann.

Achtung: Wenn du Karten mehrfach lesbar machen möchtest, bzw. eventuell detektieren möchtest, wann sie wieder weggenommen wird, reicht es nicht alleine die letzte Zeile zu streichen. Ich habe aber in einem anderen Blogpost schon einmal beschrieben wie das geht.

Wenn du den Code in der Arduino IDE fertig geschrieben (oder kopiert) hast und der ESP32 immer noch angeschlossen ist, kannst du jetzt auf Upload klicken und 1-2 Minuten warten.

Jetzt verbindet sich der ESP direkt mit deinem WiFi und fängt an detektierte Karten-IDs auf MQTT zu veröffentlichen. Du kannst also wie in Part II beschrieben testen ob die IDs ankommen, indem du auf dem Raspberry Pi folgende Zeile im Terminal eingibst:

mosquitto_sub -L "mqtt://plant:programmer@localhost/#" -d

Wobei plant ein gültiger Benutzer und programmer das Passwort sein sollte, das du beim Einrichten von Mosquitto verwendet hast (Dieser Benutzer benötigt Zugriffsrechte alles zu lesen). # bedeutet auf alle topics hören und -d ist der Debugmodus, der dir anzeigt zu welchem Topic eine Nachricht veröffentlicht wurde (wenn du das weglässt siehst du nur die Karten-ID).

Wenn das klappt, ist der schwierigste Teil getan und du hast dir eine Pause verdient. Weiter geht es im nächsten Abschnitt. Dort wird wie beim letzten Mal ein Pythonskript verwendet um die Karten-ID in eine Liste von Kommandos für den LED-Streifen zu übersetzen.

Etwas hat nicht geklappt und du musst auf Fehlersuche gehen? Hier ein paar typische Wege den Fehler einzugrenzen:

  1. Öffne den Seriellen Monitor wenn der ESP noch am PC hängt und schau nach, ob die Karten-ID dort ausgegeben wird (Achtung die Baudrate musst du jetzt gegebenenfalls wieder auf 115200 setzen). Wenn da nichts kommt, würde ich andere Karten ausprobieren um einen Defekt auszuschließen und die Verkabelung sowie die Zeilen 37 & 38 nochmal überprüfen.
  2. Die Karten-ID wird auf dem seriellen Monitor ausgegeben, aber kommt nicht im MQTT an? Überprüfe die MQTT Zugangsdaten und ob der Nutzer die Berechtigung hat auf das unter mqtt_topic_uid angegebene Topic zu schreiben.
  3. Es geht immer noch nicht? Schreib mir auf Twitter und ich schau mir das an und ergänze dann die Liste hier.

Anbindung an LEDs - Python Code

Wie beim letzten Mal, machen wir uns das Leben einfach. Wir speichern die Kommandos an den LED-Streifen nicht auf dem ESP, wo wir den Code bei jeder Änderung neu flashen müssten. Stattdessen geben wir dem ESP die Aufgabe, nur die ID der Karte an MQTT zu melden. Um die RFID-Karten-ID vom ESP32 in McLighting-Befehle umzusetzen brauchst du ein Pythonskript auf dem Raspberry Pi, das auf dem RFID-topic lauscht und auf dem LED-topic (hier: LEDstrip/in) schreibt. Lege eine Datei namens mqtt_rfid.py auf dem Raspberry Pi an und füge folgenden Code hinzu:

import time

import paho.mqtt.client as mqtt

LED_TOPIC = "LEDstrip1/in"
RFID_TOPIC = "sensors/bathroom/rfid"

commands = {
    b"E4D5E158": ["/12", "%42", "?70"],  # rainbow
    b"C636762B": ["/0", "#00F7F046", "%50"],  # Static yellowish white
}


def on_message_callback(client, userdata, message):
    topic = str(message._topic, encoding="UTF-8")
    value = message.payload
    if topic == RFID_TOPIC:
        # print(f"Card received {value}")
        payload = "/on"
        client.publish(LED_TOPIC, payload)
        for payload in commands.get(value, []):
            client.publish(LED_TOPIC, payload)
            time.sleep(.05)


def on_connect_callback(client, userdata, flags_dict, result):
    client.subscribe(RFID_TOPIC)


def connect(host: str, port: int, user: str = "", password: str = ""):
    if user:
        client.username_pw_set(user, password)
    client.on_connect = on_connect_callback
    client.on_message = on_message_callback
    client.connect(host, port)


if __name__ == "__main__":
    client = mqtt.Client("python_rfid2light")
    connect("localhost",  # host
            1883,  # port
            "plant",  # user
            "programmer"  # password
            )
    client.loop_forever()

Zeile 5 definiert das Topic, auf das der LED-Streifen hört (LEDstrip1/in, s. Part III) und Zeile 6 das Topic, auf dem die Karten-IDs veröffentlicht werden.
Zeilen 8 - 11 definieren ein Python Dictionary (erkennbar an den geschweiften Klammern). Das ist wie eine Liste, aber die Elemente (Value) die enthalten sind haben nicht einfach eine Position in der Liste, sondern einen Namen (Key). Als Key gibst du in Zeile 9 z.B. deine erste Karten-ID als Bytestring an (d.h. du muss Anführungszeichen mit einem b-Präfix benutzen, wie im Code gezeigt). Das muss so sein, damit man diesen Key nachher direkt mit dem MQTT-Payload vergleichen kann. Ein Doppelpunkt trennt Key und Value in Python Dictionaries. Die wenigsten LED-Konfigurationen kommen mit einem einzigen MQTT-Befehl zur Steuerung aus, deshalb habe ich den Code so vorbereitet, dass für jede gescannte Karten-ID mehrere Befehle an den LED-Streifen geschickt werden können. Dementsprechend ist der Value des Eintrags selber eine Liste (erkennbar an den eckigen Klammern) von Strings die alle per MQTT an den LED-Streifen geschickt werden sollen. In Zeile 9 ist es zum Beispiel die Regenbogen-Animation ("/12") mit der Helligkeit 42 von 255 ("%42") und dem Tempo 70 von 255 ("?70"). Farben können mit "#WWRRGGBB" (Weiß, Rot, Grün Blau) für Primär- "##WWRRGGBB" für Sekundär- und "###WWRRGGBB" für Tertiärfarbe gesetzt werden. Um nochmal nachzulesen, wie die anderen Kommandos heißen, schau nochmal in Part III nach.
Zeile 10 ist ein weiteres Beispiel mit einer anderen Karten-ID, statischer Farbe gelb-weiß und Helligkeit 50.

Wie beim letzten Mal wird in Zeile 14 eine callback-Funktion definiert, die beschreibt was passieren soll, wenn eine Nachricht auf subscribten Topics ankommt.
Wenn (Zeile 17) das Topic dem RFID-Topic (hier "sensors/bathroom/rfid") entspricht wird als erstes das payload "/on" definiert (Zeile 19) und an den LED-Streifen verschickt (Zeile 20).
Die Zeile 21 erlaubt es mehrere Kommandos zu verwenden und muss von innen nach außen gelesen werden. commands.get(value, []) versucht im commands-Dictionary mit dem Key der gerade empfangenen Karten-ID einen Value zu finden. Du weißt von oben schon, dass dieser Value falls der Key existiert als Element eine ganze Liste zurückgibt. Falls der Key nicht existiert wird mit den leeren eckigen Klammern eine leere Liste zurückgegeben. for payload in <liste>: geht jetzt die Liste elementweise durch und führt den eingerückten Code für jedes Element einmal aus, wobei der jeweils aktuelle Wert in der payload Variable steht. Das bedeutet auch, wenn der Key nicht existiert, wird die for-Schleife auch null mal ausgeführt. (Das bedeutet auch, dass der on Befehl selbst dann geschickt wird, wenn die Karte unbekannt ist. Du kannst also ungeachtet des aktuellen LED-Zustands mit jeder Karte, auch wenn sie nicht im commands-Dictionary enthalten ist, das Licht anschalten.
Nach jedem publish (Zeile 22) wird jetzt noch ein 50 Millisekunden Sleep  (Zeile 23) eingebaut, damit der ESP8266 am LED-Streifen auf jeden Fall Zeit hat, die MQTT Nachricht zu verarbeiten, bevor die nächste kommt.

Der Rest ist wie beim letzten Mal. In Zeile 26/27 wird auf das RFID-Topic subscribt, sobald die MQTT-Verbindung aufgebaut wurde. Zeile 30 - 35 kümmert sich um den Aufbau der Verbindung und verknüpft die Callback-Funktionen. Und Zeilen 38 - 45 starten den MQTT Client (Hier noch eine neue Client-ID eintragen und überprüfen ob user und password korrekt sind)

Geschafft. Wenn du dieses Skript jetzt auf dem Raspberry Pi ausführst (am besten im Hintergrund) kannst du Karten scannen und das Licht sollte reagieren.

# Skript im Vordergrund starten
python3 mqtt_rfid.py

# Skript im Hintergrund starten
python3 mqtt_rfid.py &

Achtung: Wenn du den Raspberry Pi per SSH steuerst, kann es sein, dass das Skript beendet wird, sobald deine Verbindung abbricht oder beendet wird. Für eine permanentere Lösung siehe Bonusteil am Ende.

Zusammenfassung

Wenn alles geklappt hat, hast du jetzt einen MQTT-Broker zuhause, einen LED-Streifen, den du per MQTT steuern kannst, Steuermöglichkeiten, wie zum Beispiel RFID oder Temperatursensoren und weißt, wie du die Kommunikation zwischen den Clients und dem Broker verschlüsselst und beschränkst. Damit hast du ein Set an Skripten und Arduino Code, den du je nachdem, was du als nächstes ausprobieren möchtest, nur ein bisschen abändern musst.

Es hat mir unfassbar große Freude bereitet, diese Reihe zu schreiben und ich hoffe dir macht das Basteln und nach und nach eigene Infrastruktur aufbauen genauso viel Freude. Wenn du dieser Reihe gefolgt bist oder in Anlehnung daran etwas eigenes gebaut hast, schick mir gerne einen Tweet oder eine E-Mail (info@). Ich bin gespannt zu sehen, was du daraus gemacht hast :D

Bonus-Exkurs: Systemd

Um Pythonskripte auf dem Raspberry Pi so einzurichten, dass sie auch nach einem Neustart automatisch wieder starten, nutze ich in der Regel systemd. Es gibt verschiedene Typen von systemd-Units, ich werde hier nur ganz kurz über services sprechen, also Programme die in der Regel automatisch gestartet und auf Wunsch bei Fehlern auch neugestartet werden können. Ein systemd Unit file könnte auf dem Raspberry Pi unter dem Pfad /etc/systemd/system/rfid2mqtt.service zum Beispiel so aussehen:

[Unit]
Description=MQTT client subscribing to RFID UIDs on one topic and publishing according LED commands on another.
Requires=network.target

[Service]
Type=simple
Restart=on-failure
ExecStart=/usr/bin/python3 /home/pi/rfid_mqtt.py


[Install]
WantedBy=default.target

Der Unit Block enthält generelle Informationen, wie eine menschenlesbare Beschreibung, was diese Unit tut und jegliche Abhängigkeiten. Da MQTT von einer bestehenden Netzwerkverbindung abhängt, starte ich den Service auch erst, wenn das Netzwerk bereitgestellt ist (->network.target). Theoretisch könnte man hier zusätzlich den mosquitto Service angeben, sodass dein Pythonskript erst startet wenn der Broker auf jeden Fall schon läuft. Ist aber nicht so kritisch, deshalb hab ich es weggelassen.

Der nächste Block definiert diese systemd-Unit als Service (in Näherung also als Programm das läuft). Es gibt auch hier verschiedene Typen, auf die ich nicht näher eingehe, simple bedeutet, dass das Programm gestartet wird und läuft bis es beendet wird. Restart=on-failure ist wichtig, damit falls das Pythonskript mal abstürzt, weil es die Verbindung verliert oder mit einem Payload nicht klarkommt oder so, automatisch neustartet. ExecStart gibt das Programm an, das ausgeführt werden soll, in diesem Fall Python3 (vollständiger Pfad) mit dem Skript als parameter (ebenfalls vollständiger Pfad)

Zuletzt muss systemd im Install Block wissen, welcher Startsequenz der Service zugeordnet werden soll. Oben bei Requires hast du schon gesehen, dass es ein network.target gibt, es gibt noch eine handvoll andere targets, wenn man keinen Grund hat, den Service an einer besonderen Stelle zu starten, reicht es üblicherweise, diesen dem default.target zuzweisen.