Esp32 oder auch esp8266 Mikrocontroller eignen sich hervorragend für IOT-Anwendungen. Vielfach werden diese kleinen Module verwendet, um darauf die Firmware tasmota zu betreiben.

Der ESP32 und der ESP8266 sind beides Mikrocontroller mit integriertem WLAN, die häufig für IoT-Projekte genutzt werden. Der ESP8266 ist der ältere und kostengünstigere Chip, ideal für einfache Anwendungen, bei denen eine Internetverbindung benötigt wird. Er hat begrenzte Rechenleistung und weniger Speicher. Der ESP32 ist eine leistungsfähigere Weiterentwicklung, die neben WLAN auch Bluetooth-Unterstützung bietet. Er hat mehr Rechenleistung, Speicher und zusätzliche Features wie mehrere GPIO-Pins (für den Anschluss von Sensoren und Aktoren) und Hardware-Unterstützung für Verschlüsselung. Der ESP32 ist daher flexibler

Es geht aber auch noch einfacher. Mit der Arduino Entwicklungsumgebung lassen sich einfache Programme erstellen und auf den Mikrocontroller übertragen. Das ist insbesondere dann sinnvoll und hilfreich, wenn in der tasmota Software diese Funktionalität nicht abgebildet ist.

Aufgabenstellung und Motivation

Mit Hilfe eines RFID-Chips und einem Kartenleser Terminal soll eine Aktivität verknüpft werden. Dazu wird die eindeutige ID des RFID-Chips durch das Kartenlesegerät erfasst und durch den Mikrocontroller an einen MQTT-Server weitergeleitet werden. Die übertragene Information initiiert einen flow innerhalb des Node-RED Hubs.

Welche Aktivität mit dem Lesen des RFID Chips verbunden ist, obliegt der Logik innerhalb der Node-RED Umgebung.

Prototyp auf einem Breadboard

Zeiterfassung

Um aus diesem Grundmodul eine Zeiterfassung zu machen, benötigt es nur wenige Softwareschritte. In meinem Fall eine Datenbank mit einer Tabelle, in der ein Zeitstempel mit der ID des RFID-Chips abgelegt wird.

Programmierung der Hardware

Die wesentlichen Schritte in dem Programm für den Mikrocontroller sind folgende.

  • Initialisierung der Hardware inklusive des Kartenlesers
  • Verbindung mit dem WLAN herstellen
  • Verbindung mit dem MQTT-Server herstellen
  • In einer Endlosschleife läuft die Abfrage des Kartenlesers und sollte eine ID erkannt werden, so wird diese an den MQTT-SERVER übertragen
  • Innerhalb der Software können noch qualitätsverbessernde Aktivitäten und Reaktionen programmiert werden, um das Betriebsverhalten stabil und sicher zu gewährleisten. Beispielsweise Überprüfung der WLAN-Verbindung und der Übertragung und Kommunikation mit dem MQTT-Server

Weiterverarbeitung der Daten

Drei einfache Knoten benötigt es um die Daten zu übernehmen und in die Datenbank zu schreiben. Die unteren beiden Knoten dienen der Ausgabe im Dashboard. Die weitere Verarbeitung der Daten aus der Tabelle erfolgt entweder durch Export der Tabelle oder aber wie ich es gemacht hat mit einer ole-verbindung direkt in Excel und dann werden in Excel weitere Tools zur Datenaufbereitung verwendet.

Code für den ESP

/*
Version 1.0
27.08.2024
*/

#include <WiFi.h>
#include <MySQL_Connection.h>
#include <MySQL_Cursor.h>
#include <PubSubClient.h>
#include <esp_task_wdt.h>
#include <ESPmDNS.h>
#include <ESPping.h>
#include <SPI.h>
#include <MFRC522.h>

// Hier NUR EIN EINZIGES Profil aktivieren
#define ESP32WROOM32          //
//#define ESP32WROOMD1MINI        //
//#define ESP8266D1Mini           //

// DEBUG => True | FLASE
const boolean debug = true;

// Portzuweisungen abhängig von der HW
#ifdef ESP8266D1Mini
  #define STATUS_LED   21        // LED zur Anzeige der Bestätigung und Error-Codes
  #define RST_PIN  22            // RC522 Reset-Pin
  #define SS_PIN   5             // RC522 SDA-Pin
  const char* mDNSname = "ESP8266D1mini";
  const char* pinghost = "FQDN";
#endif

// Portzuweisungen abhängig von der HW
#ifdef ESP32WROOMD1MINI
  #define STATUS_LED   21        // LED zur Anzeige der Bestätigung und Error-Codes
  #define RST_PIN  22            // RC522 Reset-Pin
  #define SS_PIN   5             // RC522 SDA-Pin
  const char* mDNSname = "ESP32WROOM32D1mini";
  const char* pinghost = "FQDN";
#endif

// Portzuweisungen abhängig von der HW
#ifdef ESP32WROOM32
  #define STATUS_LED   17        // LED zur Anzeige der Bestätigung und Error-Codes
  #define RST_PIN  5             // RC522 Reset-Pin
  #define SS_PIN   21             // RC522 SDA-Pin
  const char* mDNSname = "ESP32WROOM32";
  const char* pinghost = "FQDN";
#endif

// WLAN-Zugangsdaten
const char* ssid = "*********";
const char* password = "******";

// MQTT-Broker-Zugangsdaten
const char* mqtt_server = "FQDN";
const int mqtt_port = 1883;                                        // Standard-MQTT-Port
const char* mqtt_user = "user";
const char* mqtt_password = "password";
const char* mqtt_topic = "zeiterfassung/rfid01/cardid";
//const char* mqtt_topic = "zeiterfassung/rfid01/TEST/cardid";

// RFID RC-522
MFRC522 mfrc522(SS_PIN, RST_PIN);  // Erstelle ein MFRC522-Objekt
// ***********************************************************************************************
// END OF DECLARATION
// ***********************************************************************************************

// ***********************************************************************************************
// MQTT-Client
// ***********************************************************************************************
WiFiClient wifiClient;
PubSubClient client(wifiClient);

// Der zu sendende Wert
int valueToSend = 0;

// ***********************************************************************************************
// RF-ID-Chip auslesen
// ***********************************************************************************************
String readRFID() {
  String rfidValue = "";
  // Suche nach neuen RFID-Tags
  if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
    // Konvertiere die UID des Tags in einen String
    for (byte i = 0; i < mfrc522.uid.size; i++) {
      rfidValue += (mfrc522.uid.uidByte[i] < 0x10 ? "0" : ""); // Führende Null hinzufügen, wenn nötig
      rfidValue += String(mfrc522.uid.uidByte[i], HEX);
    }
    
    // Halt die aktuelle Karte
    mfrc522.PICC_HaltA();
  }
  return rfidValue;
}
// ***********************************************************************************************

// ***********************************************************************************************
// Das ist die neue Version. Wenn die Verbindung zu MQTT nicht erfolgreich war, blinkt die LED 3x
// ***********************************************************************************************
void sendMQTTValue(String valueToSend) {
  // Verbindung zum MQTT-Broker aufbauen, wenn nicht verbunden
  if (!client.connected()) {
    if (debug) {Serial.println("sendMQTTValue: Client is NOT connected");}
    if (client.connect("ESP32Client", mqtt_user, mqtt_password)) {
      if (debug) {Serial.println("sendMQTTValue: Verbunden mit MQTT-Broker");}
    } else {
      if (debug) {Serial.println("sendMQTTValue: Verbindung mit MQTT-Broker fehlgeschlagen. Erneuter Versuch in 1 Sekunde...");}
      blinkLED(3);
      return;
    }
  }

  if (client.connected()) {
    if (debug) {Serial.println("sendMQTTValue: Client is connected");}
    
    // Sende den Wert mit QoS 1 (mindestens einmal zustellen)
    if (client.publish(mqtt_topic, valueToSend.c_str(), true)) {  // QoS 1 wird standardmäßig verwendet
      if (debug) {Serial.println(String("sendMQTTValue: Nachricht gesendet ") + valueToSend);}
      if (valueToSend != "no-card") {
        blinkLED(1);
      }  
    } else {
      if (debug) {Serial.println("sendMQTTValue: Nachricht senden fehlgeschlagen");}
      blinkLED(3);
    }
  }
}
// ***********************************************************************************************

// ***********************************************************************************************
// LED Blinken lassen
// ***********************************************************************************************
void blinkLED(int times) {
  for (int i = 0; i < times; i++) {
    digitalWrite(STATUS_LED, HIGH);
    delay(200); // LED ein für 200ms
    digitalWrite(STATUS_LED, LOW);
    delay(200); // LED aus für 200ms
  }
}
// ***********************************************************************************************

// ***********************************************************************************************
// prüfe die WLAN Verbindung, ggf. reconnect
// ***********************************************************************************************
boolean checkPing() {
  // Ping starten
  if (Ping.ping(pinghost)) {
    if (debug) {Serial.println("Ping erfolgreich!");}
    return true;
  } else {
    Serial.println("Ping fehlgeschlagen!");
    return false;
  } 
}
// ***********************************************************************************************

// ***********************************************************************************************
// prüfe die WLAN Verbindung, ggf. reconnect
// ***********************************************************************************************
void checkWiFiConnection() {
  // Prüfe, ob das WLAN noch verbunden ist
  if (WiFi.status() != WL_CONNECTED) {
    if (debug) {Serial.println("WLAN-Verbindung verloren! Versuche erneut zu verbinden...");}
      blinkLED(5);    
    // Versuche, die WLAN-Verbindung erneut herzustellen
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED) {
      delay(1000);
      if (debug) {Serial.println("Verbindung zum WLAN wird wiederhergestellt...");}
    }
    if (debug) {Serial.println("WLAN-Verbindung wiederhergestellt!");}
  }    
}
// ***********************************************************************************************

// ***********************************************************************************************
// MQTT Connection prüfen
// ***********************************************************************************************
void checkMQTTConnection() {
  // Prüfe, ob die MQTT-Verbindung noch aktiv ist
  if (!client.connected()) {
    
    if (debug) {Serial.println("MQTT-Verbindung verloren! Versuche erneut zu verbinden...");}
      // LED blinken lassen, um anzuzeigen, dass die MQTT-Verbindung verloren gegangen ist
      blinkLED(3);
    }

    // Versuche, die MQTT-Verbindung erneut herzustellen
    if (client.connect("ESP32Client", mqtt_user, mqtt_password)) {
      if (debug) {Serial.println("MQTT-Verbindung wiederhergestellt!");}
      
      // Optional: Initiale Nachrichten senden, falls nötig
      sendMQTTValue("reconnectedESP32");
      
    } else {
      if (debug) {Serial.println("Erneuter Verbindungsversuch fehlgeschlagen. Versuch in 1 Sekunde...");}
    }
  }
// ***********************************************************************************************


// ***********************************************************************************************
// Teste ob der RFID Leser verbunden ist
// ***********************************************************************************************
bool checkRFIDConnection() {
  // Ein einfacher Weg, um zu überprüfen, ob der Leser funktioniert, ist ein Selbsttest
  if (!mfrc522.PCD_PerformSelfTest()) {
    return false;  // Der Selbsttest ist fehlgeschlagen, der Leser ist nicht richtig verbunden
  }
  
  // Alternativ: Prüfe, ob der Leser auf Anfragen reagiert (z.B. Firmwareversion)
  byte firmware = mfrc522.PCD_ReadRegister(MFRC522::VersionReg);
  if (firmware == 0x00 || firmware == 0xFF) {
    return false;  // Ungültige Antwort, der Leser ist nicht verbunden oder defekt
  }
  return true;  // Der Leser ist korrekt verbunden und funktioniert
}
// ***********************************************************************************************

// ***********************************************************************************************
// Das ist das Hauptprogramm
// ***********************************************************************************************
// ***********************************************************************************************
// Setup and Loop
// ***********************************************************************************************
void setup() {
  Serial.begin(115200);
  // Initialisiere den Pin D5 als Ausgang
  pinMode(STATUS_LED, OUTPUT);

  // WIFI
  WiFi.begin(ssid, password);
  int retryCount = 0;
  const int maxRetries = 10;  // Maximal 10 Versuche

  while (WiFi.status() != WL_CONNECTED && retryCount < maxRetries) {
    blinkLED(5);  // Blinkt die LED 5 Mal, während auf eine Verbindung gewartet wird
    delay(2000);  // Warte 2 Sekunden zwischen den Versuchen

    retryCount++;  // Erhöhe den Zähler für die Verbindungsversuche

    if (debug) {
      Serial.print("SETUP: Verbindung zum WLAN wird hergestellt... Versuch ");
      Serial.println(retryCount);
    }
  }

  if (WiFi.status() == WL_CONNECTED) {
    if (debug) {
      Serial.println("WLAN: Verbunden mit dem WLAN");
      Serial.println("WLAN: IP-Addresse = " + WiFi.localIP().toString());
      //Serial.println(WiFi.localIP());
    }
    
  } else {
    if (debug) {Serial.println("SETUP: Verbindung zum WLAN fehlgeschlagen, Neustart...");}
    ESP.restart();  // Neustart des ESP32
  }

  // Initialize mDNS
  if (!MDNS.begin(mDNSname)) {   // Set the hostname 
    if (debug) {Serial.println("Error setting up MDNS responder!");}
    while(1) {
      delay(1000);
    }
  }
  if (debug) {
    Serial.print("mDNS: responder started, name: ");
    Serial.println(mDNSname);
  }
  
  client.setServer(mqtt_server, mqtt_port);
  if (debug) {Serial.println("SETUP: Verbunden mit dem MQTT");}
  if (client.connected()) Serial.println("SETUP: Ich bin immer noch verbunden, Initiale Verbindung erfolgreich");
  sendMQTTValue(mDNSname);
 
  //RFID-RC522
  SPI.begin();            // Initialisiere SPI-Bus
  mfrc522.PCD_Init();     // Initialisiere den MFRC522-Chip
  if (debug) {
    Serial.println("SETUP: RFID Reader gestartet!");
  }

  //WDT 
  esp_task_wdt_init(20, true); // Timeout nach 10 Sekunden
  esp_task_wdt_add(NULL);      // Fügt den aktuellen Task zum Watchdog Timer hinzu
}
// ***********************************************************************************************

// ***********************************************************************************************
// Endlosschleife
// ***********************************************************************************************
void loop() {
  static int counter   = 0; // Zähler für die Durchläufe
  static int WDT       = 0; // Zähler für WDT
  static int pcounter  = 0; // Zähler für WDT
  
  
  // RFID-Wert lesen
  String rfidValue = readRFID();

  // sende die gelesene ID an MQTT
  if (!rfidValue.isEmpty()) {
    //Sende Daten an den MQTT-Server
    sendMQTTValue(rfidValue);
    delay(1000);    // Warte 1 Sekunde um doppelte zu vermeiden
  }

 // Teste die WLAN Verbindung nur alle 100 Durchläufe
  if (++counter == 1000) {
    checkWiFiConnection();
    counter = 0; // Zähler zurücksetzen
  }

  //sende ein ping um zu prüfen ob Kommunikation möglich ist
  if (++pcounter == 1000) {
    if (!checkPing) {
      if (debug) {Serial.println("PING: nicht erfolgreich");}
    }
  }

  // WDT
  if (++WDT == 20) {
    esp_task_wdt_reset();
    WDT = 0; // Zähler zurücksetzen
  }

  // Teste den RFID-Reader

  // MQTT-Nachrichten empfangen (optional)
  //client.loop();

  delay(200);
}