/**
* RFID + Password Door Lock System with Admin Control & EEPROM Storage
* Built for Wokwi Simulator
* * Features:
* - Master Card (Blue Presets: 01:02:03:04) enters Admin Menu.
* - Enroll new user cards (Supports both 4-byte and 7-byte RFID cards/tags).
* - Revoke / Delete user cards.
* - Change the master keypad security passcode (4-6 digits, default is "1234").
* - Non-volatile EEPROM memory persistence for cards and custom passcode.
* - Multi-tone buzzer alerts and dual LED indicators.
* - Physical lock simulation via SG90 Servo Motor.
*/
#include <SPI.h>
#include <MFRC522.h>
#include <Keypad.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Servo.h>
#include <EEPROM.h>
// --- Pin Definitions ---
#define RST_PIN 9
#define SS_PIN 10
#define BUZZER_PIN A0
#define SERVO_PIN A1
#define GREEN_LED A2
#define RED_LED A3
// --- EEPROM Mapping Config ---
#define EEPROM_INIT_FLAG_ADDR 0
#define EEPROM_INIT_VALUE 0xBB // Sentinel value to verify if EEPROM has been initialized
#define EEPROM_PIN_ADDR 1 // Dynamic Password location (up to 6 chars + null-terminator)
#define EEPROM_DB_START 10 // Card database start address
#define MAX_SLOTS 10 // Support up to 10 stored RFID cards
#define SLOT_SIZE 8 // 1 byte for UID length, 7 bytes for UID array
// --- Master Card Setup ---
const byte MASTER_UID[] = {0x01, 0x02, 0x03, 0x04}; // Matches the Blue Card Wokwi preset
const byte MASTER_UID_LEN = 4;
// --- Keypad Map Configuration (4 Rows, 3 Columns mapped for Arduino Pin Budget) ---
const byte ROWS = 4;
const byte COLS = 3;
char keys[ROWS][COLS] = {
{'1','2','3'},
{'4','5','6'},
{'7','8','9'},
{'*','0','#'}
};
byte rowPins[ROWS] = {2, 3, 4, 5};
byte colPins[COLS] = {6, 7, 8};
// --- Instantiate Modules ---
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
MFRC522 mfrc522(SS_PIN, RST_PIN);
LiquidCrystal_I2C lcd(0x27, 16, 2);
Servo lockServo;
// --- System State Machine ---
enum SystemState {
STATE_LOCKED,
STATE_UNLOCKED,
STATE_ADMIN_MENU,
STATE_ADMIN_ADD,
STATE_ADMIN_DEL,
STATE_ADMIN_PIN
};
SystemState currentState = STATE_LOCKED;
unsigned long stateTimer = 0;
String pinInput = "";
void setup() {
Serial.begin(9600);
SPI.begin();
mfrc522.PCD_Init();
lcd.init();
lcd.backlight();
lockServo.attach(SERVO_PIN);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(GREEN_LED, OUTPUT);
pinMode(RED_LED, OUTPUT);
// Initialize standard variables on pristine simulated EEPROM
initEEPROM();
// Launch into the default secure standby state
enterStateLocked();
}
void loop() {
// 1. Process active RFID scans
bool cardScanned = false;
byte scannedUID[8];
byte scannedUIDLen = 0;
if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
scannedUIDLen = mfrc522.uid.size;
for (byte i = 0; i < scannedUIDLen; i++) {
scannedUID[i] = mfrc522.uid.uidByte[i];
}
cardScanned = true;
// Stop reading card to avoid repeating scans immediately
mfrc522.PICC_HaltA();
mfrc522.PCD_StopCrypto1();
}
// 2. Process keypad entry
char key = keypad.getKey();
if (key != NO_KEY) {
playBeep(80, 2000); // Key click audio feedback
}
// 3. Coordinate State-specific Actions
switch (currentState) {
case STATE_LOCKED:
if (key != NO_KEY) {
if (key >= '0' && key <= '9') {
if (pinInput.length() < 6) {
pinInput += key;
updateLockedDisplay();
}
} else if (key == '*') {
pinInput = "";
updateLockedDisplay();
} else if (key == '#') {
if (pinInput == getSavedPIN()) {
enterStateUnlocked();
} else {
triggerAccessDenied();
}
}
}
if (cardScanned) {
if (isMasterCard(scannedUID, scannedUIDLen)) {
enterStateAdminMenu();
} else if (isCardAuthorized(scannedUID, scannedUIDLen)) {
enterStateUnlocked();
} else {
triggerAccessDenied();
}
}
break;
case STATE_UNLOCKED:
// Re-lock after 3 seconds
if (millis() - stateTimer >= 3000) {
enterStateLocked();
}
break;
case STATE_ADMIN_MENU:
if (key != NO_KEY) {
if (key == '1') {
enterStateAdminAdd();
} else if (key == '2') {
enterStateAdminDel();
} else if (key == '3') {
enterStateAdminPin();
} else if (key == '*') {
enterStateLocked();
}
}
break;
case STATE_ADMIN_ADD:
if (key == '*') {
enterStateAdminMenu();
}
if (cardScanned) {
if (isMasterCard(scannedUID, scannedUIDLen)) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Cannot Add Master");
playAlertTone();
delay(2000);
enterStateAdminAdd();
} else if (addCard(scannedUID, scannedUIDLen)) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Card Registered!");
lcd.setCursor(0, 1);
lcd.print("Added to EEPROM");
playSuccessTone();
delay(2000);
enterStateAdminMenu();
} else {
lcd.clear();
lcd.setCursor(0, 0);
if (findCardSlot(scannedUID, scannedUIDLen) != -1) {
lcd.print("Card Already");
lcd.setCursor(0, 1);
lcd.print("Registered!");
} else {
lcd.print("Memory Full!");
lcd.setCursor(0, 1);
lcd.print("Max 10 Slots");
}
playAlertTone();
delay(2000);
enterStateAdminMenu();
}
}
break;
case STATE_ADMIN_DEL:
if (key == '*') {
enterStateAdminMenu();
}
if (cardScanned) {
if (isMasterCard(scannedUID, scannedUIDLen)) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Cannot Del Master");
playAlertTone();
delay(2000);
enterStateAdminDel();
} else if (deleteCard(scannedUID, scannedUIDLen)) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Card Revoked!");
lcd.setCursor(0, 1);
lcd.print("Access Deleted");
playDeleteTone();
delay(2000);
enterStateAdminMenu();
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Card Not Found!");
playAlertTone();
delay(2000);
enterStateAdminMenu();
}
}
break;
case STATE_ADMIN_PIN:
if (key != NO_KEY) {
if (key >= '0' && key <= '9') {
if (pinInput.length() < 6) {
pinInput += key;
updateAdminPinDisplay();
}
} else if (key == '*') {
enterStateAdminMenu();
} else if (key == '#') {
if (pinInput.length() >= 4) {
savePIN(pinInput);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("PIN Saved!");
playSuccessTone();
delay(2000);
enterStateAdminMenu();
} else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Too Short! >=4");
playAlertTone();
delay(2000);
enterStateAdminPin();
}
}
}
break;
}
}
// --- State Transitions ---
void enterStateLocked() {
currentState = STATE_LOCKED;
pinInput = "";
digitalWrite(GREEN_LED, LOW);
digitalWrite(RED_LED, HIGH);
lockServo.write(0); // Physical lock engagement position
updateLockedDisplay();
}
void enterStateUnlocked() {
currentState = STATE_UNLOCKED;
digitalWrite(GREEN_LED, HIGH);
digitalWrite(RED_LED, LOW);
lockServo.write(90); // Unlock door position
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Access Granted!");
lcd.setCursor(0, 1);
lcd.print("Door Unlocked");
playSuccessTone();
stateTimer = millis(); // Initialize locking delay timer
}
void enterStateAdminMenu() {
currentState = STATE_ADMIN_MENU;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("1:Add 2:Del 3:PIN");
lcd.setCursor(0, 1);
lcd.print("*:Exit Menu");
playAdminModeTone();
}
void enterStateAdminAdd() {
currentState = STATE_ADMIN_ADD;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Scan Card to Add");
lcd.setCursor(0, 1);
lcd.print("*:Cancel");
}
void enterStateAdminDel() {
currentState = STATE_ADMIN_DEL;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Scan Card to Del");
lcd.setCursor(0, 1);
lcd.print("*:Cancel");
}
void enterStateAdminPin() {
currentState = STATE_ADMIN_PIN;
pinInput = "";
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("New PIN (4-6dig):");
lcd.setCursor(0, 1);
lcd.print("*:Cancel #:Save");
}
// --- Display Helpers ---
void updateLockedDisplay() {
lcd.setCursor(0, 0);
lcd.print("Scan Card... ");
lcd.setCursor(0, 1);
lcd.print("PIN: ");
for (int i = 0; i < 6; i++) {
if (i < pinInput.length()) {
lcd.print('*');
} else {
lcd.print(' ');
}
}
lcd.print(" ");
}
void updateAdminPinDisplay() {
lcd.setCursor(0, 1);
lcd.print("PIN: ");
lcd.print(pinInput);
// Clear the remainder of the line
for (int i = pinInput.length(); i < 11; i++) {
lcd.print(' ');
}
}
void triggerAccessDenied() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Access Denied!");
lcd.setCursor(0, 1);
lcd.print("Wrong Card/Code");
// Warning flashing and warning alarm tone
digitalWrite(RED_LED, LOW);
for (int i = 0; i < 3; i++) {
digitalWrite(RED_LED, HIGH);
playBeep(200, 400); // Heavy warning tone
digitalWrite(RED_LED, LOW);
delay(100);
}
delay(1000);
enterStateLocked();
}
// --- Database & Memory Operations ---
void initEEPROM() {
if (EEPROM.read(EEPROM_INIT_FLAG_ADDR) != EEPROM_INIT_VALUE) {
EEPROM.write(EEPROM_INIT_FLAG_ADDR, EEPROM_INIT_VALUE);
// Write Default Code "1234" to memory block
String defaultPin = "1234";
for (int i = 0; i < defaultPin.length(); i++) {
EEPROM.write(EEPROM_PIN_ADDR + i, defaultPin[i]);
}
EEPROM.write(EEPROM_PIN_ADDR + defaultPin.length(), '\0');
// Clean-out registration database block
for (int slot = 0; slot < MAX_SLOTS; slot++) {
EEPROM.write(EEPROM_DB_START + (slot * SLOT_SIZE), 0); // 0 length = Empty
}
}
}
String getSavedPIN() {
String pin = "";
for (int i = 0; i < 6; i++) {
char c = EEPROM.read(EEPROM_PIN_ADDR + i);
if (c == '\0' || c == 0xFF) break;
pin += c;
}
return pin;
}
void savePIN(String newPin) {
for (int i = 0; i < newPin.length(); i++) {
EEPROM.write(EEPROM_PIN_ADDR + i, newPin[i]);
}
EEPROM.write(EEPROM_PIN_ADDR + newPin.length(), '\0');
}
int findCardSlot(byte *uid, byte len) {
for (int slot = 0; slot < MAX_SLOTS; slot++) {
int baseAddr = EEPROM_DB_START + (slot * SLOT_SIZE);
byte storedLen = EEPROM.read(baseAddr);
if (storedLen == len) {
bool isMatch = true;
for (int i = 0; i < len; i++) {
if (EEPROM.read(baseAddr + 1 + i) != uid[i]) {
isMatch = false;
break;
}
}
if (isMatch) return slot;
}
}
return -1; // Not found
}
bool addCard(byte *uid, byte len) {
if (findCardSlot(uid, len) != -1) return false; // Already present
for (int slot = 0; slot < MAX_SLOTS; slot++) {
int baseAddr = EEPROM_DB_START + (slot * SLOT_SIZE);
byte storedLen = EEPROM.read(baseAddr);
if (storedLen == 0) { // Found open memory slot
EEPROM.write(baseAddr, len);
for (int i = 0; i < len; i++) {
EEPROM.write(baseAddr + 1 + i, uid[i]);
}
return true;
}
}
return false; // Storage full
}
bool deleteCard(byte *uid, byte len) {
int slot = findCardSlot(uid, len);
if (slot == -1) return false; // Not registered
int baseAddr = EEPROM_DB_START + (slot * SLOT_SIZE);
EEPROM.write(baseAddr, 0); // Mark empty
return true;
}
bool isCardAuthorized(byte *uid, byte len) {
if (isMasterCard(uid, len)) return true;
return (findCardSlot(uid, len) != -1);
}
bool isMasterCard(byte *uid, byte len) {
if (len != MASTER_UID_LEN) return false;
for (byte i = 0; i < len; i++) {
if (uid[i] != MASTER_UID[i]) return false;
}
return true;
}
// --- Audio Generation ---
void playBeep(int duration, int frequency) {
tone(BUZZER_PIN, frequency, duration);
}
void playSuccessTone() {
tone(BUZZER_PIN, 1200, 150);
delay(150);
tone(BUZZER_PIN, 1800, 300);
}
void playAlertTone() {
tone(BUZZER_PIN, 400, 400);
delay(400);
}
void playDeleteTone() {
tone(BUZZER_PIN, 1500, 150);
delay(150);
tone(BUZZER_PIN, 900, 300);
}
void playAdminModeTone() {
tone(BUZZER_PIN, 1600, 100);
delay(100);
tone(BUZZER_PIN, 2000, 100);
}Loading
mfrc522
mfrc522