Author: Ghena Flaviu
Master: SRIC
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.
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.
Parts List:
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.
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 |
#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; }
<!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>
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.