Introduction: Simulate Simple Passcode / RFID Door Lock System
In this post, I am going to use DumbDisplay to simulate a simple passcode / RDIF door lock system using Arduino UNO.
Instead of installing an actual door:
- DumbDisplay is used to simulate door locking / unlocking, simular to the effect of my previous post Blink Test with Colored Image, with Arduino Nano.
- To simulate the "action" of opening and closing, a LED is attached for the purpose. It not only shows the state of the door, when the door open / close, it flashes a bit, simulating the "action" of opening / closing.
Neverthess, there are still some actual things:
- Keypad is used for entering passcode. You can refer to my previous post Reading 4x4 Keypad with Arduino UNO.
- RFID Reader (RC522) module is attached.
Moreover, the passcode and RFID set are persisted to the EEPROM of the Arduino UNO, making it "safe" to reboot the system.
Note that only Keypad is required; RFID reader and LED are optional.
Step 1: Connections
For Keypad:
- Connect PIN 8 of Arduino UNO to R1 of Keypad.
- Connect PIN 7 of Arduino UNO to R2 of Keypad.
- Connect PIN 6 of Arduino UNO to R3 of Keypad.
- Connect PIN 5 of Arduino UNO to R4 of Keypad.
- Connect PIN 4 of Arduino UNO to C1 of Keypad.
- Connect PIN 3 of Arduino UNO to C2 of Keypad.
- Connect PIN 2 of Arduino UNO to C3 of Keypad.
Optionally for RFID Reader (RC522) module:
- Connect 3.3V of Arduino UNO to 3.3V of RC522. It is 3.3V!
- Connect a Ground pin of Arduino UNO to GND of RC522.
- Connect PIN 9 of Arduino UNO to RST of RC522.
- Connect PIN 10 of Arduino UNO to SDA of RC522.
- Connect PIN 11 of Arduino UNO to MOSI of RC522.
- Connect PIN 12 of Arduino UNO to MISO of RC522.
- Connect PIN 13 of Arduino UNO to SCL of RC522.
Optionally for LED:
- Connect PIN8 of Arduino UNO to the long leg of the LED
- Connect a Ground pin of Arduino UNO the short leg of the LED, via a 330Ω resistor.
Step 2: The Sketch
#define PIN_R1 8
#define PIN_R2 7
#define PIN_R3 6
#define PIN_R4 5
#define PIN_C1 4
#define PIN_C2 3
#define PIN_C3 2
#define SS_PIN 10
#define RST_PIN 9
#define SIM_DOOR_LED_PIN A5
#define STORE_IN_EEPROM
#define TONE_KEY 1500
#define TONE_RFID 1400
#define TONE_UNLOCK 1000
#define TONE_LOCK 800
#define TONE_FAIL 500
#include "dumbdisplay.h"
DumbDisplay dumbdisplay(new DDInputOutput(115200));
LcdDDLayer *lcd;
GraphicalDDLayer *graphical;
SimpleToolDDTunnel *tunnel_unlocked;
SimpleToolDDTunnel *tunnel_locked;
DDConnectVersionTracker cvTracker;
const char Keys[4][4] = {
{ '1', '2', '3', 'A' },
{ '4', '5', '6', 'B' },
{ '7', '8', '9', 'C' },
{ '*', '0', '#', 'D' }
};
void setupForReadingRow(int r) {
digitalWrite(PIN_R1, r != 0);
digitalWrite(PIN_R2, r != 1);
digitalWrite(PIN_R3, r != 2);
digitalWrite(PIN_R4, r != 3);
}
int readColumnOfKeyPress() {
int c0 = digitalRead(PIN_C1);
int c1 = digitalRead(PIN_C2);
int c2 = digitalRead(PIN_C3);
#if defined(PIN_C3)
int c3 = digitalRead(PIN_C3);
#else
int c3 = 1;
#endif
int c = -1;
if (c0 == 0) {
c = 0;
} else if (c1 == 0) {
c = 1;
} else if (c2 == 0) {
c = 2;
} else if (c3 == 0) {
c = 3;
}
return c;
}
long lastKeyMillis = 0;
char getAKeyPress() {
long nowMillis = millis();
if ((nowMillis - lastKeyMillis) < 300) {
return 0;
}
for (int r = 0; r < 4; r++) {
setupForReadingRow(r);
int c = readColumnOfKeyPress();
if (c != -1) {
char key = Keys[r][c];
lastKeyMillis = nowMillis;
return key;
}
}
return 0;
}
const int MaxComboSize = 4;
struct Combo {
int combinationSize = 0;
char combinations[MaxComboSize]; // maximum 4 chars
};
char ComboBuffer[MaxComboSize + 1];
char* getCombo(const Combo& combo) {
int i = 0;
for (; i < combo.combinationSize; i++) {
ComboBuffer[i] = combo.combinations[i];
}
for (; i < MaxComboSize; i++) {
ComboBuffer[i] = '_';
}
ComboBuffer[MaxComboSize] = 0;
return ComboBuffer;
}
void addComboKey(Combo &combo, char key) {
if (combo.combinationSize == MaxComboSize) {
memmove(combo.combinations, combo.combinations + 1, combo.combinationSize - 1);
combo.combinationSize--;
}
combo.combinations[combo.combinationSize++] = key;
}
void resetCombo(Combo &combo) {
combo.combinationSize = 0;
}
bool matchCombos(const Combo& combo1, const Combo& combo2) {
if (combo1.combinationSize != combo2.combinationSize) {
return false;
}
for (int i = 0; i < combo1.combinationSize; i++) {
if (combo1.combinations[i] != combo2.combinations[i]) {
return false;
}
}
return true;
}
void copyCombos(const Combo& fromCombo, Combo& toCombo) {
toCombo.combinationSize = fromCombo.combinationSize;
memcpy(toCombo.combinations, fromCombo.combinations, toCombo.combinationSize);
}
Combo lockCombo;
Combo enteringCombo;
Combo newCombo;
bool allReady = false;
bool locked = false;
bool renewing = false;
long lastShownMillis = 0;
#if defined (SS_PIN)
#include <SPI.h>
#include <MFRC522.h>
class RFIDControl
{
public:
RFIDControl(uint8_t ssPin, uint8_t rstPin): mfrc522(ssPin, rstPin) {
noRFIDMillis = millis();
}
public:
void init() {
SPI.begin();
mfrc522.PCD_Init();
}
const char* readRFID() {
long nowMillis = millis();
if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
char buffer[16];
for (byte i = 0; i < mfrc522.uid.size; i++) {
sprintf(buffer + 2 * i, "%02x", mfrc522.uid.uidByte[i]);
}
bool accept = noRFIDMillis > 0 && (nowMillis - noRFIDMillis) >= 500;
//accept = true;
noRFIDMillis = 0;
if (accept) {
rfidRead = buffer;
return rfidRead.c_str();
} else {
return NULL;
}
} else {
if (noRFIDMillis <= 0) {
noRFIDMillis = nowMillis;
}
return NULL;
}
}
bool matchRFIDPass(const char* rfid) {
const char* csRFIDPass = rfidPass.c_str();
for (int i = 0;; i++) {
char p = csRFIDPass[i];
char c = rfid[i];
if (p == 0 && c == 0) {
break;
}
if (p == 0 || c == 0 || p != c) {
return false;
}
}
return true;
}
public:
String rfidPass;
private:
MFRC522 mfrc522;
String rfidRead;
long noRFIDMillis;
};
RFIDControl rfidControl(SS_PIN, RST_PIN);
#endif
#if defined(STORE_IN_EEPROM)
#include <EEPROM.h>
class EEPROMStorage {
public:
void readCombo(Combo& combo) {
int offset = 0;
// check header first
for (uint8_t i = 0; i < 10; i++) {
uint8_t r = EEPROM.read(offset + i);
if (r != i) {
return;
}
}
combo.combinationSize = EEPROM.read(offset + 10);
for (int i = 0; i < MaxComboSize; i++) {
combo.combinations[i] = EEPROM.read(offset + 11 + i);
}
}
void writeCombo(const Combo& combo) {
int offset = 0;
// write some header
for (uint8_t i = 0; i < 10; i++) {
EEPROM.write(offset + i, i);
}
EEPROM.write(offset + 10, combo.combinationSize);
for (int i = 0; i < MaxComboSize; i++) {
EEPROM.write(offset + 11 + i, combo.combinations[i]);
}
}
void readRFID(String& rfid) {
int offset = 30;
// check header first
for (uint8_t i = 0; i < 10; i++) {
uint8_t r = EEPROM.read(offset + i);
if (r != i) {
return;
}
}
uint8_t count = EEPROM.read(offset + 10);
char buffer[count + 1];
for (int i = 0; i < count; i++) {
buffer[i] = EEPROM.read(offset + 11 + i);
}
buffer[count] = 0;
rfid = String(buffer);
}
void writeRFID(const char* rfid) {
int offset = 30;
// write some header
for (uint8_t i = 0; i < 10; i++) {
EEPROM.write(offset + i, i);
}
int len = strlen(rfid);
EEPROM.write(offset + 10, len);
for (int i = 0; i < len; i++) {
EEPROM.write(offset + 11 + i, rfid[i]);
}
}
};
EEPROMStorage eepromStorage;
#endif
#if defined(SIM_DOOR_LED_PIN)
class DoorHandler {
public:
DoorHandler(uint8_t ledPin): ledPin(ledPin) {
toggleCounter = 0;
}
// doorState -- 0: not changed; 1: opened; 2: closed
void handleDoor(int doorState) {
if (doorState == 1) {
if (!opened) {
//dumbdisplay.writeComment("OPEN");
toggleCounter = 3;
//toggleMillis = 0;
}
opened = true;
} else if (doorState == 2) {
if (opened) {
//dumbdisplay.writeComment("CLOSE");
toggleCounter = 3;
//toggleMillis = 0;
}
opened = false;
} else {
if (toggleCounter > 0) {
long nowMillis = millis();
if ((toggleMillis - nowMillis) <= 0) {
if (toggleCounter % 2 == 0) {
digitalWrite(ledPin, opened ? 0 : 1);
} else {
digitalWrite(ledPin, opened ? 1 : 0);
}
if (--toggleCounter > 0) {
toggleMillis = nowMillis + 150;
} else {
toggleMillis = 0;
}
}
}
}
}
private:
uint8_t ledPin;
bool opened;
long toggleMillis;
int toggleCounter;
};
DoorHandler doorHandler(SIM_DOOR_LED_PIN);
#endif
void onTried(bool matched, bool warnIfNoMatch) {
if (matched) {
dumbdisplay.tone(TONE_UNLOCK, 200);
locked = false;
renewing = false;
} else if (warnIfNoMatch) {
dumbdisplay.tone(TONE_FAIL, 100);
dumbdisplay.tone(TONE_LOCK, 100);
dumbdisplay.tone(TONE_FAIL, 200);
}
}
void onNew() {
dumbdisplay.tone(TONE_LOCK, 100);
dumbdisplay.tone(TONE_UNLOCK, 100);
locked = true;
renewing = false;
}
void setup() {
pinMode(PIN_R1, OUTPUT);
pinMode(PIN_R2, OUTPUT);
pinMode(PIN_R3, OUTPUT);
pinMode(PIN_R4, OUTPUT);
pinMode(PIN_C1, INPUT_PULLUP);
pinMode(PIN_C2, INPUT_PULLUP);
pinMode(PIN_C3, INPUT_PULLUP);
#if defined(PIN_C3)
pinMode(PIN_C3, INPUT_PULLUP);
#endif
#if defined(SIM_DOOR_LED_PIN)
pinMode(SIM_DOOR_LED_PIN, OUTPUT);
#endif
#if defined (SS_PIN)
rfidControl.init();
#endif
dumbdisplay.recordLayerSetupCommands();
// create a LCD layer for display some info about the picture shown
lcd = dumbdisplay.createLcdLayer(16, 3);
//lcd->backgroundColor(DD_RGB_COLOR(0xee, 0xee, 0xee));
//lcd->pixelColor("darkblue");
lcd->border(2, "black");
lcd->writeCenteredLine("... ...");
// create a graphical layer for drawing the web images to
graphical = dumbdisplay.createGraphicalLayer(200, 300);
graphical->padding(0);
graphical->margin(2);
graphical->border(2, "darkgreen");
graphical->enableFeedback();
// auto "pin" the two layers vertically, one above the other
dumbdisplay.configAutoPin(DD_AP_VERT);
dumbdisplay.playbackLayerSetupCommands("rc-lock");
// create tunnels for downloading web images ... and save to your phone
// tunnel_unlocked = dumbdisplay.createImageDownloadTunnel("https://raw.githubusercontent.com/trevorwslee/Arduino-DumbDisplay/master/screenshots/lock-unlocked.png", "lock-unlocked.png", false);
// tunnel_locked = dumbdisplay.createImageDownloadTunnel("https://raw.githubusercontent.com/trevorwslee/Arduino-DumbDisplay/master/screenshots/lock-locked.png", "lock-locked.png", false);
tunnel_unlocked = dumbdisplay.createImageDownloadTunnel("https://${DDSS}/lock-unlocked.png", "lock-unlocked.png", false);
tunnel_locked = dumbdisplay.createImageDownloadTunnel("https://${DDSS}/lock-locked.png", "lock-locked.png", false);
#if defined(STORE_IN_EEPROM)
eepromStorage.readCombo(lockCombo);
#if defined (SS_PIN)
eepromStorage.readRFID(rfidControl.rfidPass);
dumbdisplay.writeComment("STORED RFID: " + rfidControl.rfidPass);
#endif
#endif
}
void loop() {
// get result whether web image downloaded .. 0: downloading; 1: downloaded ok; -1: failed to download
int lockedResult = tunnel_locked->checkResult();
int unlockedResult = tunnel_unlocked->checkResult();
const DDFeedback* feedback = graphical->getFeedback();
bool refresh = false;
bool wasLocked = locked;
if (!allReady) {
// not all ready ... if not refreshed for a while (1000 ms), refresh
long now = millis();
long diff = now - lastShownMillis;
if (diff > 1000) {
locked = !locked;
refresh = true;
}
} else {
// if all ready ... see if double clicked ... if so, handle and refresh
if (feedback != NULL) {
if (feedback->type == DOUBLECLICK) {
if (locked) {
// if locked ... check if combo matches ... if so, unlock
onTried((matchCombos(lockCombo, enteringCombo)), true);
} else {
// if not locked ... lock
dumbdisplay.tone(TONE_LOCK, 200);
locked = true;
resetCombo(enteringCombo);
}
refresh = true;
}
}
char key = getAKeyPress();
if (key != 0) {
// a key on keypad pressed
dumbdisplay.tone(TONE_KEY, 100);
if (locked) {
// already locked ... handled entering of combo
if (key == '*') {
// if key is '*', clear combo being entered
resetCombo(enteringCombo);
} else if (key != '#') {
// if key is not '#', add the key to what combo being entered
addComboKey(enteringCombo, key);
}
// try the combo
onTried((matchCombos(lockCombo, enteringCombo)), key == '#');
} else {
// not locked ... handle if changing combo
if (key == '*') {
// if key is '*', start changing combo
renewing = true;
resetCombo(newCombo);
dumbdisplay.tone(TONE_UNLOCK, 200);
dumbdisplay.tone(TONE_KEY, 100);
} else {
if (renewing) {
if (key == '#') {
// if changing combo and key pressed is '#', use the new combo
copyCombos(newCombo, lockCombo);
resetCombo(enteringCombo);
resetCombo(newCombo);
onNew();
#if defined(STORE_IN_EEPROM)
eepromStorage.writeCombo(lockCombo);
#endif
} else {
// add the key to the new combo being entered
addComboKey(newCombo, key);
}
}
}
}
refresh = true;
}
}
#if defined (SS_PIN)
const char* rfid = rfidControl.readRFID();
if (rfid != NULL) {
// got RFID
dumbdisplay.writeComment("RFID: " + String(rfid));
dumbdisplay.tone(TONE_RFID, 100);
if (allReady) {
if (locked) {
// if already locked ... try to match the RFID, to see if can unlock
onTried(rfidControl.matchRFIDPass(rfid), true);
} else {
if (renewing) {
// if renewing combo and got RFID ... use the RFID as new RFID pass
rfidControl.rfidPass = String(rfid);
onNew();
#if defined(STORE_IN_EEPROM)
eepromStorage.writeRFID(rfid);
#endif
}
}
refresh = true;
}
}
#endif
#if defined(SIM_DOOR_LED_PIN)
int doorState = 0; // door state not changed
if (wasLocked && !locked) {
doorState = 1; // door should open now
} else if (!wasLocked && locked) {
doorState = 2; // door should close now
}
doorHandler.handleDoor(doorState);
#endif
if (!refresh) {
// check if reconnected ... if so, refresh anyway
if (cvTracker.checkChanged(dumbdisplay)) {
refresh = true;
}
}
if (!refresh) {
return;
}
allReady = lockedResult == 1 && unlockedResult == 1;
int result;
const char* imageFileName;
if (locked) {
imageFileName = "lock-locked.png";
result = lockedResult;
} else {
imageFileName = "lock-unlocked.png";
result = unlockedResult;
}
if (result == 1) {
String status1;
String status2;
if (allReady) {
status1 = "🔒 " + String(getCombo(lockCombo));
if (locked) {
status2 = "🔑 " + String(getCombo(enteringCombo));
lcd->backgroundColor(DD_RGB_COLOR(0xff, 0xdd, 0xdd));
lcd->pixelColor("darkred");
} else {
if (renewing) {
status2 = "🆕 " + String(getCombo(newCombo));
lcd->backgroundColor(DD_RGB_COLOR(0xdd, 0xdd, 0xff));
lcd->pixelColor("darkblue");
} else {
status2 = "";
lcd->backgroundColor(DD_RGB_COLOR(0xdd, 0xff, 0xdd));
lcd->pixelColor("darkgreen");
}
}
} else {
status1 = "not";
status2 = "ready";
}
graphical->drawImageFile(imageFileName);
lcd->clear();
lcd->writeCenteredLine(locked ? "Lock locked" : "Lock unlocked");
lcd->writeCenteredLine(status1, 1);
lcd->writeCenteredLine(status2, 2);
} else if (result == 0) {
// downloading
graphical->clear();
graphical->setCursor(0, 10);
graphical->println("... ...");
graphical->println(imageFileName);
graphical->println("... ...");
lcd->writeCenteredLine("...");
} else if (result == -1) {
graphical->clear();
graphical->setCursor(0, 10);
graphical->println("XXX failed to download XXX");
lcd->writeCenteredLine("failed");
}
lastShownMillis = millis();
}
As mentioned above, only Keypad is required. You can not install RFID Reader and / or LED.
- If you do not have RFID Reader installed, you can disable it by commenting out the line
#define SS_PIN 10
- If you do not have LED installed, you can disable it by commenting out the line
#define SIM_DOOR_LED_PIN A5
Step 3: Upload the Sketch and Run It
Upload the sketch to your Arduino UNO. Then plug your Arduino UNO to your phone (with DumbDisplay app instlled). Make connection and try it out.
Step 4: Using the Keypad -- Starting Off
It should starts off with no passcode / RFID set.
Step 5: Opening by Double Click
You are free to unlock it (the door) as you wish. Simply double click the lock.
Step 6: Using the Keypad -- New Passcode
You can set new passcode only when the door is unlocked. Press the * key on the keypad to start entering new passcode.
Now, key in the new passcode, then press the # key.
As soon as you pressed the # key, new passcode is set, and the door is locked.
Note that new passcode is stored in your Arduino UNO's EEPROM as well. This way, even you reboot your Arduino UNO, the passcode will still be effective.
Step 7: Using the Keypad -- Open
As soon as you entered the correct passcode, the door will be opened for you.
Step 8: Using RFID Card
Using RFID card is easy; simply swipe your RFID card over the RFID Reader.
- If the door is locked, swiping the correct card will open the door.
- If the door is unlocked, swiping will not do anything.
If you want to setup a new RFID card:
- While door is unlocked, press the * key on the keypad.
- Swipe new RFID card. This will immediately make the new card the only accepted card. Note that the new RFID will be saved to your Arduino UNO's EEPROM as well.
Step 9: Enjoy!
Hope you will have fun with this simulated door lock system! Enjoy!
Peace be with you. Jesus loves you. May God bless you!