Heute stelle ich meine NFC-Stempeluhr auf Basis eines ESP8266 Mikrocontrollers vor, die eine einfache und effektive Zeiterfassungslösung für jeden bietet. Durch das Scannen von NFC-Tags können Mitarbeiter schnell und einfach ein- und ausstempeln, während die Daten automatisch in einer Datenbank gespeichert werden.
Inhalt
Zielsetzung
Das Ziel dieses Projekts NFC ESP8266 besteht darin, eine eigene NFC-basierte Stempeluhr auf Basis des ESP8266 Mikrocontrollers zu entwickeln. Durch das Scannen von NFC-Tags sollen Mitarbeiter/Kollegen einfach und schnell ein- und ausstempeln können. Die Daten werden automatisch an einen Server gesendet, in einer Datenbank gespeichert und können für die Zeiterfassung genutzt werden. Zusätzlich zur Hardware soll auch die Software für den ESP8266 Microcontroller erstellt werden.
Umsetzung
Ich habe für die Realisierung meiner NFC-basierten Stempeluhr auf Basis des ESP8266 Mikrocontrollers eine eigene Platine entworfen und hergestellt. Dies ermöglichte mir eine optimale Anordnung der Komponenten und eine saubere Verdrahtung. Allerdings ist es nicht notwendig, eine eigene Platine herzustellen, um dieses NFC ESP8266 Projekt umzusetzen. Es gibt viele Alternativen wie z.B. Lochrasterplatinen oder Prototyping-Shields, die es Ihnen ermöglichen, die Hardwarekomponenten auf einfache Weise zu verbinden. Letztendlich hängt die Wahl der Platine davon ab, wie anspruchsvoll das Projekt ist und welche Komponenten verwendet werden.
Benötigte Komponenten für NFC ESP8266
- ESP8266 Microcontroller WEMOS D1 Mini AliExpress
- NFC-Lesegerät RFID-RC522 (je nach Hersteller unterschiedliche Pinbelegung) AliExpress
- LED DIP WS2812 (1) AliExpress
- Buzzer 5V Summer (1)
- 2N3904 Transistor (1) AliExpress
- 1k ohm Widerstände (2) AliExpress
- 4,7k ohm Widerstand (1) AliExpress
- 5V Stromversorgung über USB Handy Ladegerät
- Gehäuse für die Hardware (optional)
Schaltplan
Als erstes musste ich den Schaltplan erstellen. Hierfür verwendete ich das das kostenlose Layout-Tool Fritzing verwenden. Ich platzierte die Komponenten einschließlich des ESP8266 Mikrocontrollers, des NFC-Lesegeräts, der NEO LED WS2812, des Summers und des Jumpers auf dem virtuellen Breadboard. Anschließend verknüpfte ich die Komponenten miteinander, um den Schaltplan zu erstellen.
In meinem Schaltplan verwende ich das RFID RC522 Modul von Funduino. Das Pinout von anderen Herstellern dieser fertigen Module kann variieren. In meinem Fall habe ich noch einen weiteren Anbieter gefunden, der lediglich die Abfolge der Pins geändert hat.
Als Stromversorgung für mein Projekt verwende ich die USB-Schnittstelle, die auf dem ESP8266 Wemos D1 bereits integriert ist. Darüber kann ich die Spannungen 5V und 3,3 Volt für meine Schaltung entnehmen.
Über einen Jumper-Schalter möchte ich den ESP8266 in zwei Modi versetzen können. Einmal, um die Kommunikation zum WLan am Aufstellort herzustellen und um den ESP8266 dann in seinen Arbeitsbetrieb zu versetzten.
Die NEO LED WS2812 und der Summer dienen der Benutzerinteraktion, indem sie den Benutzer über erfolgreiche oder fehlgeschlagene Aktionen informieren.
Hinweis: In meinem Schaltplan ist eine RGB LED verbaut. Da zum Zeitpunkt der Erstellung ich keinen NEO LED WS2812 Plan zur Verfügung hatte, der das DIP Lochmuster der WS2812 darstellt.
Platine
Nachdem ich das Design abgeschlossen hatte, habe ich die Platine über die Plattform Aisler herstellen lassen. Aisler ist ein Online-Service, der benutzerdefinierte Platinenherstellung anbietet. Ich habe das Design meiner Platine hochgeladen und konnte innerhalb weniger Tage eine professionell gefertigte Platine erhalten.
Die Verwendung von Aisler bot mir eine effiziente Möglichkeit, meine Platine herzustellen, ohne dass ich eigene Ausrüstung oder Erfahrung mit der Herstellung von Platinen haben musste. Die fertige Platine passte perfekt zu meinem Design und konnte nahtlos in das Projekt integriert werden.
Insgesamt war die Verwendung von Fritzing und Aisler eine großartige Möglichkeit, meine eigene Platine für das NFC Stempeluhr ESP8266 DIY Projekt zu erstellen und sicherzustellen, dass das Projekt auch für mich als Laie umgesetzt werden konnte.
Die passende Platine kannst du hier finden und gleich herstellen lassen.
Software
Die Software für das NFC Stempeluhr ESP8266 DIY Projekt wurde mit der Arduino Integrated Development Environment (IDE) erstellt. Arduino ist eine Open-Source-Plattform, die häufig für die Entwicklung von Mikrocontroller-basierten Projekten wie diesem verwendet wird.
In der Arduino IDE konnte ich den ESP8266 Mikrocontroller programmieren und die verschiedenen Komponenten des Projekts steuern. Ich habe die notwendigen Bibliotheken und Sketche verwendet, um sicherzustellen, dass die NFC-Lese- und Schreibfunktionen sowie die LED und der Summer korrekt funktionieren.
Darüber hinaus habe ich auch die WLAN-Verbindung des ESP8266 programmiert, die es ermöglicht, die Stempeluhr über das lokale Netzwerk zu verbinden und die erfassten Arbeitszeiten auf einem Server zu speichern.
Konfigurationsmodus über Jumper-Schalter
Um die WLAN-Verbindung des NFC Stempeluhr ESP8266 DIY Projekts zu konfigurieren, wurde ein spezieller Konfigurationsmodus implementiert. Der Konfigurationsmodus wird gestartet, indem ein physischer Jumper-Schalter auf der Platine betätigt wird.
Sobald der Konfigurationsmodus gestartet ist, wird der ESP8266 zu einem Access Point (AP) und erzeugt ein WLAN-Signal. Dieses WLAN-Signal kann vom Benutzer erkannt werden, der sich dann mit dem Access Point verbindet.
Nachdem der Benutzer mit dem Access Point verbunden ist, kann er eine spezielle Webseite aufrufen, auf der er die SSID und das Passwort des vorhandenen WLAN-Netzwerks eingeben kann, mit dem er sich verbinden möchte.
Sobald der Benutzer die entsprechenden Informationen eingegeben hat, speichert der ESP8266 die WLAN-Verbindungsinformationen und verwendet sie bei zukünftigen Verbindungen mit dem Netzwerk.
Durch die Verwendung dieses Konfigurationsmodus können Benutzer die WLAN-Verbindung des NFC Stempeluhr ESP8266 DIY Projekts auf einfache Weise konfigurieren, ohne dass sie zusätzliche Hardware oder komplexe Software-Konfigurationen benötigen.
Normalbetrieb
Im Normalbetrieb erfasst die NFC Stempeluhr ESP8266 DIY die Stempelzeit von Benutzern, die ihre NFC-Karte oder ihr NFC-Tag an die Stempeluhr halten. Die NFC-Lesefunktion wird von einem NFC-Lesemodul bereitgestellt, das an den ESP8266 Mikrokontroller angeschlossen ist.
Wenn ein Benutzer seine NFC-Karte oder seinen NFC-Tag an die Stempeluhr hält, erkennt das NFC-Lesemodul die Karte oder den Tag und liest die darauf gespeicherten Informationen. Die Stempeluhr sendet die gelesenen Informationen an den Server. Dieser vergleicht die Daten mit einer Liste von Benutzern und speichert den Zeitpunkt der Aktion als Arbeitszeit in einer Datenbank.
Die URL des Servers und ein paar wenige Parameter lassen sich über die Oberfläche einstellen.
Netzwerk Konfigurationsparameter
- ssid -> Name des WLan Netzwerks
- pass ->Passwort zum WLan Netzwerk
- IP -> eigene IP Adresse
- gateway -> Netzwerkdateway
- subnet -> Subnetzmaske
- DNS -> Adresse des DNS-Servers
Dynamische Parameter
- mac -> MAC-Adresse der NFC Stempeluhr
- card -> ID der Karte, die an das NFC Lesegerät gehalten wurde
- pass -> Ein Schlüssel der in MD5 übertragen wird zur Berechtigung der Anfrage
- url -> Anfrage URL Server.
Die Stempeluhr gibt außerdem Feedback an den Benutzer durch die Verwendung einer LED und eines Summers.
#include <SPI.h> #include <MFRC522.h> #include <Adafruit_NeoPixel.h> #include <EEPROM.h> #include <ESP8266WiFi.h> #include <ESP8266WebServer.h> #include <ESP8266HTTPClient.h> #include <MD5Builder.h> // System Status int configMode = 0; // Einstellung int systemStatus = 0; // 0 = OK , 1 = Error, 2 = Notice int statusCount = 0; // Zähler für LEDs // Webserver const char* config_mode_ssid = "NFC-CardClock"; // Enter SSID here const char* config_mode_password = "start123"; //Enter Password here IPAddress config_mode_local_ip(192,168,1,1); IPAddress config_mode_gateway(192,168,1,1); IPAddress config_mode_subnet(255,255,255,0); ESP8266WebServer server(80); typedef struct{ char ssid[32]; char pass[64]; IPAddress ip; IPAddress gateway; IPAddress subnet; IPAddress dns; char url[400]; char urlpost[400]; char urlpass[100]; } network; network user_network; struct URL { String protocol = ""; String host = ""; String port = ""; String path = ""; } url; // Prüft ob alle notwendigen eingegebenen Werte übertragen wurden. byte user_checkup; // Buzzer int freqDeep=2200; // Hz int freqNormal=2400; // Hz int freqHeight=2550; // Hz int buzzPin=16; // Button int buttonPin=4; // NeoPixel int neoLedPin=5; Adafruit_NeoPixel pixels = Adafruit_NeoPixel(1, neoLedPin, NEO_RGB + NEO_KHZ400); MFRC522 mfrc522(15, 0); MD5Builder _md5; void setup() { // Terminal Serial.begin(115200); delay(10); yield(); // Neoleds pixels.begin(); pixels.clear(); pixels.setPixelColor(0, pixels.Color(255, 255, 0)); pixels.show(); delay(10); Serial.println(); Serial.println("Init System"); Serial.println(); // Card Reader SPI.begin(); // Init SPI bus mfrc522.PCD_Init(); // Init MFRC522 Serial.println("MFRC522 Card Reader details"); mfrc522.PCD_DumpVersionToSerial(); // Show details of PCD - MFRC522 Card Reader details Serial.println(); // EEPROM EEPROM.begin(sizeof(user_network)); EEPROM.get(0, user_network); EEPROM.commit(); // Prüfen ob Config Mode pinMode(buttonPin, INPUT_PULLUP); configMode = digitalRead(buttonPin); // CONFIG MODE if(configMode == 0){ Serial.println("Config Mode -> Soft-AP"); String ssid_config_mode = config_mode_ssid + String( WiFi.macAddress() ); WiFi.softAPConfig(config_mode_local_ip, config_mode_gateway, config_mode_subnet); Serial.println(WiFi.softAP(ssid_config_mode, config_mode_password) ? "Ready" : "Failed!"); server.on("/", handle_Config_Mode_OnConnect); server.on("/config", handle_Config_Mode_Config); server.on("/clearconfig", handle_Config_Mode_Clear_Config); // NORMAL MODE } else { // ACCESS POINT vergessen. WiFi.softAPdisconnect(true); WiFi.disconnect(); Serial.println("Normal Mode -> wLan Network"); delay(200); // Konfiguration aus EEPROM // für Server verwenden Serial.print("Connecting to "); Serial.println(user_network.ssid); // eigene IP // DNS Server // Gateway // Subnet WiFi.config(user_network.ip, user_network.dns, user_network.gateway, user_network.subnet); // Verbindung zum wLan aufbauen. WiFi.begin(user_network.ssid, user_network.pass); delay(500); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(500); } Serial.println("WiFi connected"); WiFi.setAutoReconnect(true); WiFi.persistent(true); server.on("/", handle_Normal_Mode_OnConnect); } server.onNotFound(handle_NotFound); server.begin(); Serial.println("HTTP server started"); soundSystemStart(); } void loop() { delay(50); statusCount++; systemStatusControll(); // Webserver Client bedienen server.handleClient(); // Ab hier ConfigMode // im config mode keine Karten annehmen if(configMode == 0){ systemStatus = 2; // Look for new cards if ( mfrc522.PICC_IsNewCardPresent()) { // Select one of the cards if ( mfrc522.PICC_ReadCardSerial()) { soundWrong(); } } return; } // ********************* // Ab hier Normalbetrieb // ********************* // Look for new cards if ( mfrc522.PICC_IsNewCardPresent()) { // Select one of the cards if ( mfrc522.PICC_ReadCardSerial()) { String cardId = ""; for (byte i = 0; i < mfrc522.uid.size; i++) { // Abstand zwischen HEX-Zahlen und führende Null bei Byte < 16 cardId += String(mfrc522.uid.uidByte[i] < 0x10 ? "0" : ""); cardId += String(mfrc522.uid.uidByte[i], HEX); } int result = httpRequest(cardId); // 200 Kommen // 202 Gehen // 204 Kein User gefunden if( result == 200 ){ soundWork(); } else if(result == 202 ){ soundGoHome(); } else { soundWrong(); } } } } int httpRequest(String cardId){ WiFiClientSecure client; HTTPClient http; // Daten an Server senden Serial.println("URL:"); Serial.println( user_network.url ); int output = 0; int attempts = 0; parseURL(user_network.url, &url); Serial.println("Host:"); Serial.println( url.host ); IPAddress IPresult; IPAddress testIpAdress; if(testIpAdress.fromString(url.host)){ Serial.println("Host is IP Adress:"); Serial.println( url.host ); Serial.println("No DNS needed"); } else { while ( ( output = WiFi.hostByName( url.host.c_str(), IPresult, (uint32_t) 2000 ) ) != 1 && ( attempts < 5) ){ delay(1000); attempts++; } if(output != 1){ Serial.println("Host konnte nicht ermittelt werden. DNS fehlgeschlagen."); return -1; } Serial.println("IP from DNS:"); Serial.println( ipToString( IPresult) ); } http.begin(client, user_network.url ); http.addHeader("Content-Type", "application/x-www-form-urlencoded"); // Create String for Post String post = user_network.urlpost; post.replace("{pass}", user_network.urlpass); post.replace("{card}", cardId); post.replace("{mac}", String( WiFi.macAddress() ) ); Serial.println("Post:"); Serial.println(post); int httpCode = http.POST(post); Serial.println("Response httpCode:"); Serial.println(httpCode); http.end(); return httpCode; } void systemStatusControll(){ // Alles OK if(systemStatus == 0){ if(statusCount == 75){ long rssi = 0; switch (WiFi.status()){ case WL_NO_SSID_AVAIL: Serial.println("Configured SSID cannot be reached"); break; case WL_CONNECTED: Serial.println("Connection successfully established"); rssi = WiFi.RSSI(); Serial.println( String( WiFi.macAddress() ) + " RSSI: " + rssi ); break; case WL_CONNECT_FAILED: Serial.println("Connection failed"); break; } pixels.setPixelColor(0, pixels.Color(0, 0, 255)); pixels.show(); statusCount = 0; } // Error } else if( systemStatus == 1){ if(statusCount == 3){ pixels.setPixelColor(0, pixels.Color(255, 0, 0)); pixels.show(); statusCount = 0; } // Notice } else if( systemStatus == 2){ if(statusCount == 3){ pixels.setPixelColor(0, pixels.Color(255, 255, 0)); pixels.show(); statusCount = 0; } } delay(30); pixels.clear(); pixels.show(); } void soundOK() { pixels.setPixelColor(0, pixels.Color(0, 255, 0)); pixels.show(); tone(buzzPin, freqNormal, 350); delay(350); noTone(buzzPin); pinMode(buzzPin, INPUT); delay(1000); } void soundSystemStart() { pixels.setPixelColor(0, pixels.Color(0, 0, 255)); pixels.show(); tone(buzzPin, freqNormal, 150); delay(200); tone(buzzPin, freqNormal, 150); delay(150); noTone(buzzPin); pinMode(buzzPin, INPUT); delay(1000); } void soundWork() { pixels.setPixelColor(0, pixels.Color(0, 255, 0)); pixels.show(); tone(buzzPin, freqNormal, 250); delay(250); noTone(buzzPin); pinMode(buzzPin, INPUT); delay(1000); } void soundGoHome() { pixels.setPixelColor(0, pixels.Color(0, 255, 0)); pixels.show(); tone(buzzPin, freqNormal, 250); delay(300); noTone(buzzPin); delay(150); tone(buzzPin, freqNormal, 250); delay(250); noTone(buzzPin); pinMode(buzzPin, INPUT); delay(1000); } void soundWrong() { pixels.setPixelColor(0, pixels.Color(255, 0, 0)); pixels.show(); tone(buzzPin, freqNormal, 2000); delay(1000); noTone(buzzPin); pinMode(buzzPin, INPUT); delay(1000); } void handle_NotFound(){ server.send(404, "text/plain", "Not found"); } void handle_Normal_Mode_OnConnect() { long rssi = WiFi.RSSI(); Serial.println( String( WiFi.macAddress() )+ " RSSI: " + rssi ); server.send(200, "text/html", String( WiFi.macAddress()) + " RSSI: " + rssi ); } void handle_Config_Mode_OnConnect() { server.send(200, "text/html", sendHtmlConfigMode("")); } void handle_Config_Mode_Clear_Config() { // EEPROM löschen ->ganze konfig weg!!! for (int i = 0 ; i < sizeof(user_network) ; i++) { EEPROM.write(i, 0); } EEPROM.commit(); Serial.println("Konfig gelöscht. Speicher überschrieben."); server.send(200, "text/html", sendHtmlConfigMode("")); } void handle_Config_Mode_Config(){ // INIT checkup user_checkup = 0; for (int i = 0; i < server.args(); i++) { if(server.argName(i) == "ssid"){ server.arg(i).toCharArray(user_network.ssid, 32); bitSet(user_checkup, 0); Serial.println(server.argName(i)); Serial.println(server.arg(i)); } if(server.argName(i) == "pass"){ server.arg(i).toCharArray(user_network.pass, 64); bitSet(user_checkup, 1); Serial.println(server.argName(i)); Serial.println("*********"); } if(server.argName(i) == "ip"){ user_network.ip.fromString(server.arg(i)); bitSet(user_checkup, 2); Serial.println(server.argName(i)); Serial.println(server.arg(i)); } if(server.argName(i) == "gateway"){ user_network.gateway.fromString(server.arg(i)); bitSet(user_checkup, 3); Serial.println(server.argName(i)); Serial.println(server.arg(i)); } if(server.argName(i) == "subnet"){ user_network.subnet.fromString(server.arg(i)); bitSet(user_checkup, 4); Serial.println(server.argName(i)); Serial.println(server.arg(i)); } if(server.argName(i) == "dns"){ user_network.dns.fromString(server.arg(i)); bitSet(user_checkup, 5); Serial.println(server.argName(i)); Serial.println(server.arg(i)); } if(server.argName(i) == "url"){ server.arg(i).toCharArray(user_network.url, 400); bitSet(user_checkup, 6); Serial.println(server.argName(i)); Serial.println(server.arg(i)); } if(server.argName(i) == "urlpost"){ server.arg(i).toCharArray(user_network.urlpost, 400); bitSet(user_checkup, 7); Serial.println(server.argName(i)); Serial.println(server.arg(i)); } if(server.argName(i) == "urlpass"){ // MD5 umwandeln vor dem abspeichern String stringMD5 = md5(server.arg(i)); stringMD5.toCharArray(user_network.urlpass, 100); Serial.println(server.argName(i)); Serial.println("*********"); } } // Prüfen ob alle Daten // Übertagen wurden // 8 bit 11111111 = 255 if(user_checkup == 255){ Serial.println( "Länge der Daten: "+ String( sizeof(user_network) ) ); // EEPROM löschen for (int i = 0 ; i < sizeof(user_network) ; i++) { EEPROM.write(i, 0); } EEPROM.commit(); // Write to EEPROM EEPROM.put(0, user_network); EEPROM.commit(); server.send(200, "text/html", sendHtmlConfigMode("<span style=\"color: green;\">Daten gesetzt.</span>")); return; } server.send(200, "text/html", sendHtmlConfigMode("")); } String sendHtmlConfigMode(String message){ // read EEPROM EEPROM.get(0, user_network); String s = "<!DOCTYPE html>"; s += "<html>\n"; s += "<head>"; s += "<meta charset=\"utf-8\"/>"; s += "</head>"; s += "<body>"; s += "<h1>Config \"NFC-CardClock\" => " + String(WiFi.macAddress()) + "</h1>\n"; s += "<b>" + message + "</b><br />"; s += "<form method=\"post\" action=\"/config\">"; s += "<h2>ssid</h2>\n"; s += "<input type=\"text\" name=\"ssid\" value=\""+ String(user_network.ssid) +"\" required></input><br />"; s += "<h2>pass</h2>\n"; s += "<input type=\"password\" name=\"pass\" value=\"\" required></input><br />"; s += "<h2>ip</h2>\n"; s += "<input type=\"text\" name=\"ip\" value=\"" + ipToString(user_network.ip) + "\" pattern=\"^([0-9]{1,3}\\.){3}[0-9]{1,3}$\" required></input><br />"; s += "<h2>gateway</h2>\n"; s += "<input type=\"text\" name=\"gateway\" value=\""+ipToString(user_network.gateway)+"\" pattern=\"^([0-9]{1,3}\\.){3}[0-9]{1,3}$\" required></input><br />"; s += "<h2>subnet</h2>\n"; s += "<input type=\"text\" name=\"subnet\" value=\""+ipToString(user_network.subnet)+"\" pattern=\"^([0-9]{1,3}\\.){3}[0-9]{1,3}$\" required></input><br />"; s += "<h2>DNS</h2>\n"; s += "<input type=\"text\" name=\"dns\" value=\""+ipToString(user_network.dns)+"\" pattern=\"^([0-9]{1,3}\\.){3}[0-9]{1,3}$\" required></input><br />"; s += "<h2>URL-Server</h2>\n"; s += "<p>Über die Variablen {mac}, {card} und/oder {pass} lassen sich Werde in der URL dynamisch einsetzen. Maximal können 400 Zeichen.<br><br>Bei Verwendung von {pass} wird das Passwort mit MD5 umgewandelt. Maximal können für {pass} 100 Zeichen verwendet werden.</p>\n"; s += "<input type=\"text\" name=\"url\" value=\""+ String(user_network.url) +"\" maxlength=\"400\" required></input><br />"; s += "<h3>POST</h3>\n"; s += "<input type=\"text\" name=\"urlpost\" value=\""+ String(user_network.urlpost) +"\" maxlength=\"400\" required></input><br />"; s += "<h3>{pass}</h3>\n"; s += "<input type=\"text\" name=\"urlpass\" value=\"\" maxlength=\"100\"></input><br />"; s += "<br /><input type=\"submit\" value=\"save\"><br />"; s += "</form></body><html>"; return s; } String md5(String str) { _md5.begin(); _md5.add(String(str)); _md5.calculate(); return _md5.toString(); } String ipToString(IPAddress ip){ String s=""; for (int i=0; i<4; i++) s += i ? "." + String(ip[i]) : String(ip[i]); return s; } void parseURL(String urlString, URL* url) { // Assume a valid URL enum URLParseState {PROTOCOL, SEPERATOR, HOST, PORT, PATH} state = PROTOCOL; url->protocol = ""; url->host = ""; url->port = ""; url->path = "/"; for (int i = 0; i < urlString.length(); i++) { switch(state) { case PROTOCOL: if (urlString[i] == ':') state = SEPERATOR; else url->protocol += urlString[i]; break; case SEPERATOR: if (urlString[i] != '/') { state = HOST; url->host += urlString[i]; } break; case HOST: if (urlString[i] == ':') state = PORT; else { if (urlString[i] == '/') state = PATH; else url->host += urlString[i]; } break; case PORT: if (urlString[i] == '/') state = PATH; else url->port += urlString[i]; break; case PATH: url->path += urlString[i]; } } }
Zusammengebaut
Resümee
Das Projekt der NFC Stempeluhr ESP8266 DIY hat sich in den letzten zwei Jahren erfolgreich im Feldeinsatz bewährt. Die Kombination aus NFC-Lesemodul und ESP8266 Mikrocontroller ermöglicht eine einfache und effiziente Erfassung von Arbeitszeiten.
Es besteht jedoch immer die Möglichkeit, das Projekt weiter zu verbessern. Durch den Einsatz einer externen Antenne kann die Verbindungsqualität zum Wlan verbessert werden.
Eine HTTPS-Verschlüsselung bei der Übertragung der Daten an den Server kann die Sicherheit erhöhen und die Daten vor unerlaubtem Zugriff schützen.
Die Speicherung der Stempelzeiten bei Netzwerkausfall kann ebenfalls nützliche sein.
Insgesamt hat die NFC Stempeluhr ESP8266 DIY in den letzten Jahren ihre Nützlichkeit und Effektivität bewiesen und bietet weiterhin Raum für Verbesserungen und Anpassungen.