ESP32 RFID scanner

Author: Ghena Flaviu
Master: SRIC

Project Description

This project implements a secure RFID-based access control system using an ESP32-WROOM-32 microcontroller and an RC522 RFID reader. The system grants or denies access based on scanned RFID cards, with visual feedback provided by green (access granted) and red (access denied) LEDs.

Concept

This project implements an IoT-based RFID access control system using an ESP32 microcontroller. It scans RFID cards, validates them against a predefined list, and provides visual feedback via LEDs. The system logs all access attempts to Firebase in real-time and sends Telegram notifications for unauthorized access. A web interface allows remote monitoring and manual control.

Key features include: Real-time access logging to Firebase Realtime Database [1] Remote monitoring and control through a web interface Instant Telegram notifications for unauthorized access attempts [2] Manual access control via web interface buttons Time-synchronized logging with NTP server

The system validates scanned cards against a predefined list of authorized cards, providing immediate LED feedback and updating the cloud database. Unauthorized access attempts trigger Telegram alerts to the system administrator.

Hardware Description

Parts List:

  • ESP32-WROOM-32: Main microcontroller for processing and connectivity.
  • RFID-RC522 Module: Reads RFID card UIDs.
  • Green LED: Indicates authorized access.
  • Red LED: Indicates denied access.
  • Resistors (2x): Current-limiting resistors for LEDs
  • Breadboard/Jumper Wires: For prototyping connections.
  • Power Supply: 3.3V for ESP32 and RC522.

ESP32-WROOM-32:
- Connects to WiFi for cloud communication.
- Interfaces with the RC522 via SPI (GPIO 5, 18, 19, 23).
- Controls LEDs via GPIO 25 (green) and 26 (red).

RFID-RC522:
- Communicates with ESP32 via SPI pins.
- Powered by 3.3V
- Antenna gain boosted for better sensitivity.

LEDs:
- Green LED: Activated for valid cards or manual “grant” commands.
- Red LED: Activated for invalid cards or manual “deny” commands.

Functionality Breakdown

Key Components

RFID Handling: - The MFRC522 library interfaces with the RFID reader
- Cards are identified by their UID and converted to decimal format
- Reader automatically resets after periods of inactivity

Access Control Logic: - Compares scanned card IDs against authorized list
- Provides immediate visual feedback via LEDs
- Green LED for authorized access, red for denied

Firebase Integration: - Stores all access events with timestamps
- Allows remote control via the ledControl path
- Uses anonymous authentication for security

Realtime Database rules

{
  "rules": {
    "rfidSystem": {
      "control": {
        ".read": true,
        ".write": true
      },
      "logs": {
        ".read": true,
        ".write": true
      },
      "ledControl": {
        ".read": true,
        ".write": true
      }
    }
  }
}

Telegram Notifications: - Sends instant alerts for all access attempts
- Differentiates between authorized and unauthorized access
- Includes system startup notification

Web Interface: - Provides real-time access logs display
- Allows manual access control via buttons
- Shows system connection status
- The system combines local hardware control with cloud connectivity for a comprehensive access control solution with remote monitoring capabilities.

Error Handling: - Auto-resets RFID reader if inactive.
- Reconnects to WiFi/Firebase if disconnected.

RC522 Pin ESP32 Pin
SDA GPIO 5
SCK GPIO 18
MOSI GPIO 23
MISO GPIO 19
IRQ Not connected
GND GND
RST GPIO 22
3.3V 3.3V
LED Pin ESP32 Pin
GREEN LED PIN GPIO 25
RED LED PIN GPIO 26

Code Breakdown

#include <SPI.h>
#include <MFRC522.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <Firebase_ESP_Client.h>
#include "addons/TokenHelper.h"
#include "addons/RTDBHelper.h"
#include <time.h>

// Configuration Section
#define WIFI_SSID "DIGI-x39P"
#define WIFI_PASSWORD "xxxx"
#define API_KEY "xxxx"
#define DATABASE_URL "https://iotproiectflaviu-default-rtdb.europe-west1.firebasedatabase.app"

// Telegram Bot Settings
#define BOT_TOKEN "7635387808:AAGwtP_Y6fJ0LEhLbCuXkeLQb5a-0iDDQgs"
#define CHAT_ID "2124464812"
const char* TELEGRAM_HOST = "api.telegram.org";

// Hardware Configuration
#define RST_PIN 22
#define SS_PIN 5
#define GREEN_LED_PIN 25
#define RED_LED_PIN 26
#define READ_TIMEOUT 300
#define LED_FEEDBACK_MS 1000

// System Objects
MFRC522 mfrc522(SS_PIN, RST_PIN);
FirebaseData fbdo;
FirebaseData ledControlStream;
FirebaseAuth auth;
FirebaseConfig config;

// Database Paths
String databasePath = "rfidSystem";
String logsPath = "/logs";
String ledControlPath = "/ledControl";

// Authorized Cards List
const unsigned long validCards[] = {2318660736, 240163822};
const int numValidCards = sizeof(validCards)/sizeof(validCards[0]);

// NTP Configuration
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 0;
const int daylightOffset_sec = 0;

// Function to send Telegram alerts
void sendTelegramAlert(unsigned long cardID, bool isAuthorized) {
  WiFiClientSecure client;
  client.setInsecure(); // Bypass SSL verification
  
  HTTPClient https;
  String url = "https://" + String(TELEGRAM_HOST) + "/bot" + String(BOT_TOKEN) + "/sendMessage";
  
  // Customize message based on access status
  String message = isAuthorized ? 
    "✅ Authorized access: Card " + String(cardID) :
    "🚨 Unauthorized access attempt: Card " + String(cardID);
  
  String payload = "chat_id=" + String(CHAT_ID) + "&text=" + message;

  if (https.begin(client, url)) {
    https.addHeader("Content-Type", "application/x-www-form-urlencoded");
    int httpCode = https.POST(payload);
    if (httpCode == HTTP_CODE_OK) {
      Serial.println("Telegram alert sent");
    } else {
      Serial.printf("Telegram error: %d\n", httpCode);
    }
    https.end();
  } else {
    Serial.println("Failed to connect to Telegram");
  }
}

// Initialize WiFi connection
void connectToWiFi() {
  Serial.print("Connecting to WiFi");
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print('.');
    // Blink both LEDs while connecting
    digitalWrite(GREEN_LED_PIN, !digitalRead(GREEN_LED_PIN));
    digitalWrite(RED_LED_PIN, !digitalRead(RED_LED_PIN));
    delay(300);
  }
  
  Serial.println("\nConnected with IP: " + WiFi.localIP().toString());
  // Quick green LED confirmation
  digitalWrite(GREEN_LED_PIN, HIGH);
  delay(500);
  digitalWrite(GREEN_LED_PIN, LOW);
  digitalWrite(RED_LED_PIN, LOW);
}

// Initialize time synchronization
void initTime() {
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  Serial.println("Waiting for NTP time sync...");
  
  time_t now;
  while ((now = time(nullptr)) < 8 * 3600 * 2) {
    Serial.print(".");
    delay(500);
  }
  
  Serial.println("\nCurrent time: " + String(ctime(&now)));
}

// Initialize Firebase connection
void initFirebase() {
  config.api_key = API_KEY;
  config.database_url = DATABASE_URL;
  config.token_status_callback = tokenStatusCallback;
  
  Firebase.reconnectWiFi(true);
  fbdo.setResponseSize(4096);
  
  // Anonymous authentication
  if (Firebase.signUp(&config, &auth, "", "")) {
    Serial.println("Anonymous signup successful");
  } else {
    Serial.println("Signup error: " + fbdo.errorReason());
  }
  
  Firebase.begin(&config, &auth);
  
  // Wait for Firebase connection
  unsigned long startTime = millis();
  while (!Firebase.ready() && millis() - startTime < 15000) {
    Serial.print(".");
    delay(300);
  }
  
  if (Firebase.ready()) {
    Serial.println("Firebase connected!");
    // Start listening for LED control commands
    if (!Firebase.RTDB.beginStream(&ledControlStream, (databasePath + ledControlPath).c_str())) {
      Serial.println("Stream begin error: " + ledControlStream.errorReason());
    }
  } else {
    Serial.println("Failed to connect to Firebase");
    // Blink red LED indefinitely on failure
    while(1) {
      digitalWrite(RED_LED_PIN, HIGH);
      delay(500);
      digitalWrite(RED_LED_PIN, LOW);
      delay(500);
    }
  }
}

// Handle LED control commands from Firebase
void handleLEDControl() {
  if (Firebase.RTDB.readStream(&ledControlStream)) {
    if (ledControlStream.streamAvailable()) {
      FirebaseJsonData jsonData;
      FirebaseJson *json = ledControlStream.jsonObjectPtr();
      
      if (json->get(jsonData, "command")) {
        String command = jsonData.stringValue;
        Serial.println("Received LED command: " + command);
        
        if (command == "grant") {
          digitalWrite(GREEN_LED_PIN, HIGH);
          digitalWrite(RED_LED_PIN, LOW);
          delay(LED_FEEDBACK_MS);
          digitalWrite(GREEN_LED_PIN, LOW);
          sendToFirebase(0, true, true); // Manual grant
          sendTelegramAlert(0, true);    // Notify manual grant
        } else if (command == "deny") {
          digitalWrite(RED_LED_PIN, HIGH);
          digitalWrite(GREEN_LED_PIN, LOW);
          delay(LED_FEEDBACK_MS);
          digitalWrite(RED_LED_PIN, LOW);
          sendToFirebase(0, false, true); // Manual deny
          sendTelegramAlert(0, false);    // Notify manual deny
        }
      }
    }
  }
}

// Send access event to Firebase
void sendToFirebase(unsigned long cardID, bool accessGranted, bool isManual) {
  if (Firebase.ready()) {
    time_t now;
    time(&now);
    
    String path = databasePath + logsPath + "/" + String(now);
    FirebaseJson json;
    
    // Prepare JSON data
    json.set("card_id", String(cardID));
    json.set("timestamp", now);
    json.set("access_granted", accessGranted);
    json.set("is_manual", isManual);
    
    if (Firebase.RTDB.setJSON(&fbdo, path.c_str(), &json)) {
      Serial.println("Data sent to Firebase");
    } else {
      Serial.println("Firebase error: " + fbdo.errorReason());
    }
  }
}

// Check if card is authorized
bool isCardValid(unsigned long cardID) {
  for (int i = 0; i < numValidCards; i++) {
    if (cardID == validCards[i]) return true;
  }
  return false;
}

// Reset RFID reader
void resetReader() {
  mfrc522.PCD_Reset();
  mfrc522.PCD_Init();
  mfrc522.PCD_SetAntennaGain(mfrc522.RxGain_48dB); // Increase sensitivity
  Serial.println("Reader was reset");
}

// Setup function - runs once at startup
void setup() {
  Serial.begin(115200);
  
  // Initialize hardware
  pinMode(GREEN_LED_PIN, OUTPUT);
  pinMode(RED_LED_PIN, OUTPUT);
  digitalWrite(GREEN_LED_PIN, LOW);
  digitalWrite(RED_LED_PIN, LOW);
  
  SPI.begin();
  resetReader(); // Initialize RFID reader
  
  // Connect to services
  connectToWiFi();
  initTime();
  initFirebase();
  
  // Send startup notification
  sendTelegramAlert(0, true); // System startup notification
  Serial.println("System ready");
}

// Main program loop
void loop() {
  static bool readerEnabled = true;
  static unsigned long lastReadTime = 0;
  
  // Handle incoming Firebase commands
  handleLEDControl();
  
  // Reset reader if inactive for too long
  if (millis() - lastReadTime > 3000 && readerEnabled) {
    readerEnabled = false;
    resetReader();
    lastReadTime = millis();
    readerEnabled = true;
  }
  
  // Skip if not time to read yet or reader disabled
  if (millis() - lastReadTime < READ_TIMEOUT || !readerEnabled) return;
  
  // Check for new card
  if (!mfrc522.PICC_IsNewCardPresent()) return;
  if (!mfrc522.PICC_ReadCardSerial()) {
    Serial.println("Failed to read card");
    return;
  }
  
  lastReadTime = millis();
  readerEnabled = false;
  
  // Read card ID
  unsigned long cardID = 0;
  Serial.print("Card UID:");
  
  for (byte i = 0; i < mfrc522.uid.size; i++) {
    cardID = (cardID << 8) | mfrc522.uid.uidByte[i];
    Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ");
    Serial.print(mfrc522.uid.uidByte[i], HEX);
  }
  
  Serial.print(" | Decimal: ");
  Serial.println(cardID);
  
  // Check access and provide feedback
  bool accessGranted = isCardValid(cardID);
  digitalWrite(GREEN_LED_PIN, accessGranted ? HIGH : LOW);
  digitalWrite(RED_LED_PIN, accessGranted ? LOW : HIGH);
  
  // Log event and send notifications
  sendToFirebase(cardID, accessGranted, false);
  sendTelegramAlert(cardID, accessGranted);
  
  // LED feedback duration
  delay(LED_FEEDBACK_MS);
  digitalWrite(GREEN_LED_PIN, LOW);
  digitalWrite(RED_LED_PIN, LOW);
  
  // Clean up RFID communication
  mfrc522.PICC_HaltA();
  mfrc522.PCD_StopCrypto1();
  readerEnabled = true;
}

Webpage

<!DOCTYPE html>
<html>
<head>
  <title>RFID Access Control</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js"></script>
  <script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-auth.js"></script>
  <script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-database.js"></script>
  <style>
    body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
    .card { background: #f9f9f9; border-radius: 8px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
    button { padding: 10px 15px; margin: 5px; border: none; border-radius: 4px; cursor: pointer; }
    .grant-btn { background-color: #4CAF50; color: white; }
    .deny-btn { background-color: #f44336; color: white; }
    table { width: 100%; border-collapse: collapse; margin-top: 20px; }
    th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; }
    .access-granted { color: #4CAF50; }
    .access-denied { color: #f44336; }
    .manual-access { font-style: italic; }
  </style>
</head>
<body>
  <div class="card">
    <h1>RFID Access Control System</h1>
    <div id="authStatus"></div>
    <div>
      <h2>Manual Control</h2>
      <button id="grantBtn" class="grant-btn">Grant Access</button>
      <button id="denyBtn" class="deny-btn">Deny Access</button>
    </div>
  </div>

  <div class="card">
    <h2>Access Logs</h2>
    <div id="connectionStatus"></div>
    <table>
      <thead>
        <tr>
          <th>Timestamp</th>
          <th>Card ID</th>
          <th>Access</th>
        </tr>
      </thead>
      <tbody id="logsBody"></tbody>
    </table>
  </div>

  <script>
    const firebaseConfig = {
      apiKey: "AIzaSyAK0KP-a7qqbuoqp-qyPt1e-8xuktIrHVo",
      authDomain: "iotproiectflaviu.firebaseapp.com",
      databaseURL: "https://iotproiectflaviu-default-rtdb.europe-west1.firebasedatabase.app"
    };

    firebase.initializeApp(firebaseConfig);
    const database = firebase.database();
    const ledControlRef = database.ref('rfidSystem/ledControl');
    const logsRef = database.ref('rfidSystem/logs');

    // Auth state
    firebase.auth().signInAnonymously()
      .then(() => {
        document.getElementById('authStatus').textContent = "Authenticated";
      })
      .catch(error => {
        document.getElementById('authStatus').innerHTML = 
          `<span style="color:red">Auth error: ${error.message}</span>`;
      });

    // Connection state
    database.ref('.info/connected').on('value', (snapshot) => {
      const isConnected = snapshot.val();
      document.getElementById('connectionStatus').innerHTML = isConnected ? 
        '<span style="color:green">Connected to Firebase</span>' : 
        '<span style="color:red">Disconnected</span>';
    });

    // Button handlers
    document.getElementById('grantBtn').addEventListener('click', () => {
      ledControlRef.set({ command: "grant", timestamp: Date.now() })
        .then(() => console.log("Grant command sent"))
        .catch(error => console.error("Error:", error));
    });

    document.getElementById('denyBtn').addEventListener('click', () => {
      ledControlRef.set({ command: "deny", timestamp: Date.now() })
        .then(() => console.log("Deny command sent"))
        .catch(error => console.error("Error:", error));
    });

    // Logs display
    logsRef.limitToLast(20).on('value', (snapshot) => {
      const logsBody = document.getElementById('logsBody');
      logsBody.innerHTML = '';
      
      if (!snapshot.exists()) {
        logsBody.innerHTML = '<tr><td colspan="3">No logs found</td></tr>';
        return;
      }

      const logs = [];
      snapshot.forEach(child => {
        logs.push({
          key: child.key,
          ...child.val()
        });
      });

      logs.sort((a, b) => b.timestamp - a.timestamp);
      
      logs.forEach(log => {
        const row = document.createElement('tr');
        if (log.is_manual) row.classList.add('manual-access');
        
        const timeCell = document.createElement('td');
        timeCell.textContent = new Date(log.timestamp * 1000).toLocaleString();
        row.appendChild(timeCell);
        
        const idCell = document.createElement('td');
        idCell.textContent = log.card_id === "0" ? "MANUAL" : log.card_id;
        row.appendChild(idCell);
        
        const accessCell = document.createElement('td');
        accessCell.textContent = log.access_granted ? 'Granted' : 'Denied';
        accessCell.className = log.access_granted ? 'access-granted' : 'access-denied';
        row.appendChild(accessCell);
        
        logsBody.appendChild(row);
      });
    });
  </script>
</body>
</html>

Libraries

  • SPI.h: Enables SPI communication for the RC522.
  • MFRC522.h: Interfaces with the RFID reader.
  • WiFi.h: Manages WiFi connectivity.
  • Firebase_ESP_Client.h: Handles Firebase Realtime Database operations.
  • WiFiClientSecure.h/HTTPClient.h: Sends Telegram alerts via HTTPS.
  • time.h: Syncs time via NTP for accurate timestamps.

Conclusions

This project demonstrates a scalable IoT access control system combining hardware (ESP32, RFID, LEDs) with cloud services (Firebase, Telegram). Key achievements: - Security: Real-time validation and alerts for unauthorized access. - Remote Monitoring: Web interface and Telegram notifications. - Reliability: Auto-reset functions and error handling. - Future enhancements could include adding biometric verification or integrating with physical locks.

Demo

References

iothings/proiecte/2025sric/rfidscanner.txt · Last modified: 2025/05/29 10:07 by flaviu.ghena
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0