Introduction: Internet of Things RFID Door Lock, NodeMCU (Arduino)

Welcome to the NodeMCU (IoT) RFID Door/Draw Lock, the idea of this project was to make a simple and secure way to lock draws & doors. In this instruction I use a NodeMCU microcontroller which connects to webserver and by using these components allow for remote management of RFID cards to unlock the draw. Furthermore, with the addition of a keypad adds another layer of security particularly from cloning and losing the RFID card.

Disclaimer

Every effort has been made to ensure that the information is correct in this guide. Furthermore Ive tried to make this system as secure as possible. However please use this lock at your own risk!

Code:

All the code for this project is stored on GitHub, its kept up to date and is an easy way to report any issues, bugs or even provide some comments on new features!

Palin Engineering GitHub

Supplies

Required

1. NodeMCU 1 (ESP8266) Microcontroller

2. RFID-RC522, RFID Reader

3. Simple 4x3 Matrix Keypad

4. Locking Solenoid. (Other electronic locking devices will work)

5. MCP23017: 16-Bit I2C I/O Expander with Serial Interface

6. 2SD313 NPN power transistor (Bit over kill for this application)

7. R1: 4.7K Resistor

8.R2: 4.7K Resistor

9. An Apache, MySQL and PHP web server, with an installed SSL (Lets Encrypt works great!)

  • There are plenty guides on setting this up on Raspberry Pi ,I am using MAMP (Mac Web server)

Step 1: Backend Web Server (Create a New MySQL Database and User)

For this project the simplest way to manage Users and Log activates was using a MySQL Database with a html GUI (Graphic User interface) for the admin. For the webserver I decided to use a simple and local self-hosted LAMP (Linux Apache MySQL PHP) server. Furthermore, I am using phpMyAdmin to manage the MySQL databases.

To get started creating a new database to store all the information as well as creating a new user to access the database. It’s to be noted that this user shouldn’t have root privileges and only access to reading and writing to the single database. This can be done in the usual way if you have experience in phpMyAdmin. In Summary:

  1. Create a New Database
  2. Create a New User that has read write access

See the photos for help!

Step 2: Backend Webserver (Creating a New Table)

Once the database is set up the next stage is to create the table to store the data. This table will have a few columns: ID (Primary key), Profile Name, RFID ID Tag and Personal Key code. See the MySQL code below:

Profile Table:

CREATE TABLE `Profile` (
  `ID` int(11) NOT NULL,
  `name` varchar(255) NOT NULL,
  `tag` varchar(255) NOT NULL,
  `keyCode` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
 
--
ALTER TABLE `Profile`
  ADD PRIMARY KEY (`ID`);
 
--
ALTER TABLE `Profile`
  MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=25;
COMMIT;

Log Table:

CREATE TABLE `log` (
  `ID` int(11) NOT NULL,
  `timeStamp` varchar(255) NOT NULL,
  `area` varchar(255) NOT NULL,
  `tag` varchar(255) NOT NULL,
  `Profile` varchar(255) DEFAULT NULL,
  `Access` varchar(45) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


ALTER TABLE `log`
  ADD PRIMARY KEY (`ID`),
  ADD UNIQUE KEY `ID_UNIQUE` (`ID`);

ALTER TABLE `log`
  MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=158;
COMMIT;


Step 3: Web Sever: Checking and Logging

To support the functionality of the NodeMCU two php scripts are going to be handling the requests to both validate RFID tags and logging failed/successful access. Firstly, check.php waits for the data from the NodeMCU via POST. Once received the data is processed a MySQL query is submitted and the RFID Tag & Key-code is searched. If a result appears then a success code is return back to the NodeMCU and it unlocks.

Finally, log.php. This script again connects to the database and waits for a POST from the NodeMCU with the RFID data. This data is then submitted along with the date & time to the MySQL database (Log Table).

check.php

<?php
//Creates new record as per request
    //Connect to database 
    $servername = "localhost";
    $username = "";
    $password = "";
    $dbname = "";
 
    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    // Check connection
    if ($conn->connect_error) {
        die("Database Connection failed: " . $conn->connect_error);
    }
 
    //Get current date and time
    date_default_timezone_set('Europe/London'); //Needed to set this due to webserver having different time zone.
    $d = date("Y-m-d");
    //echo " Date:".$d."<BR>";
    $t = date("H:i:s");
 
    if(!empty($_POST['tag']) && !empty($_POST['area']) && !empty($_POST['keyCode'])) //Checks to make sure all the data has been recived from the NodeMCU
    {
        $tag = $_POST['tag'];
        $area = $_POST['area'];
        $keyCode = $_POST['keyCode']; //Decare the varibles with the data from the NodeMCU
 
        $sql = "SELECT * FROM Profile WHERE tag = '$tag' AND keyCode = '$keyCode'";
        $res = mysqli_query($conn, $sql);
        $row = mysqli_fetch_array($res);
        
        $ID = $row['ID'];


        if ($conn->query($sql)->num_rows > 0) {
            echo "SS" .$row['name']; //Return Success Code to the NodeMCU & profile Name
            
            
        } else {
            $sqlx = "SELECT * FROM Profile WHERE tag = '$tag'";
            $resx = mysqli_query($conn, $sqlx);
            $rowx = mysqli_fetch_array($resx);

            if ($conn->query($sqlx)->num_rows > 0) 
            {
                echo "FF" .$rowx['name']; //Return Failed Code to the NodeMCU & profile name if known
            }else{
                echo "FF" .$rowx['name']; //Return Failed Code to the NodeMCU & profile name if known
            }   
        }
    }
 
 
    $conn->close();
?>

log.php

<?php
//Creates new record as per request
    //Connect to database
    $servername = "localhost";
    $username = "";
    $password = "";
    $dbname = "";
 
    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
   
    // Check connection
    if ($conn->connect_error) {
        die("Database Connection failed: " . $conn->connect_error);
    }
 
 
    if(!empty($_POST['tag']) && !empty($_POST['area']) && !empty($_POST['profile']) && !empty($_POST['access']))
    {
        $tag = $_POST['tag'];
        $area = $_POST['area'];
        $access = $_POST['access'];
        $profile = $_POST['profile'];
        $timeStamp = gmdate("d-m-Y H:i:s"); 
        
        $sql = "INSERT INTO log (timeStamp, tag, area, Profile, access)
        
        VALUES ('$timeStamp','$tag', '$area', '$profile', '$access')";
 
        if ($conn->query($sql) === TRUE) {
            echo "LOGGED OK" .$sql;
        } else {
            echo "LOGGED Error: " . $sql . "<br>" . $conn->error;
        }
    }
 
 
    $conn->close();
?>

Step 4: Arduino Wiring (Breadboarding)

The next stage is breadboarding the project. I used Fritzing to plan out the circuit.

Power Supply

A DC 5v, 1 Amp supply. This was chosen as it has plenty of current to supply the NodeMCU in normal operation as well as powering the solenoid lock when activated.

5V Power Supply ----- > NodeMCU / Solenoid

  • Ground -----> NodeMCU Ground
  • 5V ------> Vin NodeMCU & one wire of the solenoid.


RFID module (RFID-RC522). ONLY USE 3.3V!!!!

RFID -----> NodeMCU

  • 3.3V -----> 3.3v
  • RST -----> D2
  • IRQ -----> Not Connected
  • MISO -----> D6
  • MOSI -----> D7
  • SCK -----> D5
  • SDA -----> D4

16-Bit I2C I/O Expander (MCP23017)

The NodeMCU doesn't have enough pins to support the RFID modules, keypad and switching circuit for the solenoid lock. The I/O expander works by mapping the the additional digital pins over the I2C bus to the NodeMCU and increases the number of pins.

I/O Expander -----> NodeMCU / Keypad

  • Pin 9 -----> 3.3V
  • Pin 10 -----> Ground
  • Pin 12 -----> D1
  • Pin 13 -----> D2
  • Pin 15 -----> Ground
  • Pin 16 -----> Ground
  • Pin 17 -----> Ground
  • Pin 19 -----> 4.7k to 3.3V
  • Pin 21 -----> Keypad
  • Pin 22 -----> Keypad
  • Pin 23 -----> Keypad
  • Pin 24 -----> Keypad
  • Pin 25 -----> Keypad
  • Pin 26 -----> Keypad
  • Pin 27 -----> Keypad

Transistor Switch

Due to the current limit of the NodeMCU (around 20mA) we need to use switch to control the high current of the power supply. This is a simple method using the NodeMCU put the transistor into saturation and switches the collector and emitter. ITs to be noted that the solenoid lock is not polarised and therefore has not positive or negative legs.

NPN Transitor -----> NodeMCU / Solenoid Lock (See photo above)

  • Base -----> 4.7K to D8
  • Collector -----> Solenoid Lock
  • Emitter -----> Ground

Step 5: Arduino Programming

Firstly, a few libraries need adding to Arduino before the code below will work correctly.

  1. RFID library for RFID-RC522.
  2. HTTP Client this allows for the NodeMCU to POST data to the web server:
  3. Adafruit's MCP23017 Library this is used to interface with the I/O Expander over I2C
  4. MCP23017 4x3 Keyboard Library. This is used to interface the I/O expander with the 4x3 keyboard.

Operation of the Code

The Arduino code can be saved and uploaded to the NodeMCU in the normal way. The NodeMCU first connects to the network using the details and prints the IP address once it's connects. Next it attaches the RFID & Keypad. Finally the code waits for a tag to be scanned. Once scanned it waits for a key code to entered followed by #. All the information is printed in the serial. This can be used to populate the profiles database and allow access to new tags.

A few variables are needed to be edited to your configuration. Firstly the WiFi setting SSID and Password. Next is the IP of the web server this will be localhost but may change based your configuration. Next is the SHA1 Fingerprint to obtain this have a look at the photos above.

/// *********************** User Change Variables ************************

const char* ssid = ""; //Input SSID for the WIFI Network 
const char* password = ""; //Input Password for the WIFI Network 

const String room = ""; //Lock Description to uploaded to the logs
const String checkWebsite = "https://localhost/check.php";
const String logWebsite = "https://localhost/log.php";
const String sslSha1 = ""; //See Tutorial for details on obtaining this! Insert Link
const String MasterCard = "443142122"; //Define Master Card ID Number

#define SS_PIN  2 //RFID Pin Allocations
#define RST_PIN 0 //RFID Pin Allocations

#define buzzer 16 //Buzzer Pin
#define door 15 //Transistor Pin


/// *********************** User Change Variables ************************

Full Code

/// *********************** Included Libraries ************************
#include <ESP8266WiFi.h> // Pre-Installed 
#include <SPI.h> // Pre-Installed 
#include <Wire.h>// Pre-Installed 

#include <MFRC522.h> //RFID : <a href="https://github.com/miguelbalboa/rfid"> <a href="https://github.com/miguelbalboa/rfid" rel="nofollow"> https://github.com/miguelbalboa/rfid
</a>
</a>

#include <ESP8266HTTPClient.h> //NodeMCU Client <a href="https://github.com/esp8266/Arduino/tree/master/libraries/ESP8266HTTPClient"> <a href="https://github.com/miguelbalboa/rfid" rel="nofollow"> https://github.com/miguelbalboa/rfid
</a>
</a>

#include "Adafruit_MCP23017.h" //Adafruit's MCP23017 <a href="https://github.com/adafruit/Adafruit-MCP23017-Arduino-Library"> <a href="https://github.com/miguelbalboa/rfid" rel="nofollow"> https://github.com/miguelbalboa/rfid
</a>
</a>

#include <Keypad_MC17.h> //MCP23017 4x3 Keyboard Library <a href="https://github.com/joeyoung/arduino_keypads/tree/master/Keypad_MC17"> <a href="https://github.com/miguelbalboa/rfid" rel="nofollow"> https://github.com/miguelbalboa/rfid
</a>
</a>

#define I2CADDR 0x20 //MCP23017 I2C address
/// *********************** Included Libraries ************************

/// *********************** User Change Variables ************************

const char* ssid = ""; //Input SSID for the WIFI Network 
const char* password = ""; //Input Password for the WIFI Network 

const String room = ""; //Lock Description to uploaded to the logs
const String checkWebsite = "https://localhost/check.php";
const String logWebsite = "https://localhost/log.php";
const String sslSha1 = ""; //See Tutorial for details on obtaining this! Insert Link
const String MasterCard = "443142122"; //Define Master Card ID Number

#define SS_PIN  2 //RFID Pin Allocations
#define RST_PIN 0 //RFID Pin Allocations

#define buzzer 16 //Buzzer Pin
#define door 15 //Transistor Pin


/// *********************** User Change Variables ************************


/// ************************ KeyPad Stuff ************************
String code;

const byte ROWS = 4; //four rows
const byte COLS = 3; //three columns

char keys[ROWS][COLS] = {
  {'1', '2', '3'},
  {'4', '5', '6'},
  {'7', '8', '9'},
  {'*', '0', '#'}
}; //Keypad Definitions 

byte rowPins[ROWS] = {0, 1, 2, 3}; //Connect to the row pinouts of the keypad
byte colPins[COLS] = {4, 5, 6}; //Connect to the column pinouts of the keypad

Keypad_MC17 keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS, I2CADDR ); //Internalise keypad


Adafruit_MCP23017 mcp; //Internalise MCP23107 Chip

/// ************************ Variables ************************


byte readCard[4]; //Store the RFID Cards Unique ID

int ID = 0; //Store the RFID Cards Unique ID as a int to put in the database

String keyCode; //Store the keycode from the keypad


WiFiClient client; //Start the Wifi Client 

MFRC522 rfid(SS_PIN, RST_PIN); //Connect the RFID module to the NodeMCU
MFRC522::MIFARE_Key key;  

byte i;


void setup()
{
  Serial.begin(9600); //A Nice bowl of serial 

  pinMode(buzzer, OUTPUT); 

  delay(10);
  pinMode(door, OUTPUT);

  digitalWrite(door, LOW);

  SPI.begin();
  rfid.PCD_Init();

  for (byte i = 0; i < 6; i++)
  {
    key.keyByte[i] = 0xFF;
  }


  // Connect to WiFi network
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  int i = 0;
  while (WiFi.status() != WL_CONNECTED && i < 50)
  {
    i++;
    delay(500);
    Serial.print(".");
  }



  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print(WiFi.localIP());

  //***** Buzzer Alert ******
  delay(1000);
  digitalWrite(buzzer, HIGH);
  delay(200);
  digitalWrite(buzzer, LOW);
  delay(200);
  digitalWrite(buzzer, HIGH);
  delay(200);
  digitalWrite(buzzer, LOW);
  Serial.println("CONECTED");
  //***** Buzzer Alert ******
  
}



void loop()
{

  String sID, check, profile;


  getID();

  for ( uint8_t i = 0; i < 4; i++)
  { 
    sID = sID + readCard[i];
  }

  if (sID != "0000")
  {


    Serial.println("ID Tag: " + sID);

    for ( uint8_t i = 0; i < 4; i++)
    { //
      readCard[i] = 0;
    }

    if (sID == MasterCard)
    {
      Serial.println("Access Granted: Master Failsafe");
      access();

    }

    keyCode = getKeyCode();

    Serial.println("Got Keycode: " + keyCode);
    check = checkID(sID, room, keyCode);


    profile = check.substring(2);
    check = check.substring(0, 2);

    Serial.println("Check: " + check + " profile: " + profile);

    if (check == "SS")
    {
      Serial.println("Access Granted:" + check);
      access();
      logID(sID, room, profile, "GRANTED");
    } else {
      Serial.println("Access Denied: LOCKED" + check + "<- check");
      digitalWrite(buzzer, HIGH);
      delay(200);
      digitalWrite(buzzer, LOW);
      logID(sID, room, profile, "DENIED");
    }
    check = "";
    sID = "0000";
  }

}

void access() //Unlock the Door Subroutine
{
  digitalWrite(door, HIGH);

  for (int i = 0; i <= 15; i++) {
    digitalWrite(buzzer, HIGH);
    delay(75);
    digitalWrite(buzzer, LOW);
    delay(75);
  }
  digitalWrite(door, LOW);
}

int getID() //Get RFID tag Number
{
  if ( ! rfid.PICC_IsNewCardPresent()) { //If a new PICC placed to RFID reader continue
    return 0;
  }
  if ( ! rfid.PICC_ReadCardSerial()) {   //Since a PICC placed get Serial and continue
    return 0;
  }

  for ( uint8_t i = 0; i < 4; i++)
  {
    readCard[i] = rfid.uid.uidByte[i];
  }
  rfid.PICC_HaltA(); // Stop reading
  digitalWrite(buzzer, HIGH);
  delay(100);
  digitalWrite(buzzer, LOW);
  return 1;
}

String checkID(String ID, String area, String keyCode) //Post Request to webserver to check if the ID Card and Keycode is correct
{
  String postData, payload;


  //Post Data
  postData = "tag=" + ID + "&area=" + area + "&keyCode=" + keyCode;
  //Serial.println(postData);
  if ((WiFi.status() == WL_CONNECTED)) {


    HTTPClient http;


    // Use posttestserver.com
    http.begin(checkWebsite, sslSha1);
    http.addHeader("Content-Type", "application/x-www-form-urlencoded", false, true);


    // start connection and send HTTP header
    int httpCode = http.POST(postData);

    // httpCode will be negative on error
    if (httpCode > 0) {
      // HTTP header has been send and Server response header has been handled


      // file found at server
      if (httpCode == HTTP_CODE_OK) {
        payload = http.getString();
        Serial.println(payload);

      }
    } else {
      Serial.printf("[HTTPS] POST... failed, error: %s\n", http.errorToString(httpCode).c_str());
    }

    http.end();
  }

  return payload;
}



String logID(String ID, String area, String profile, String access) //Log the Tag
{
  String postData, payload;


  //Post Data
  if (profile == "")
  {
    profile = "UNKNOWN";
  }

  postData = "tag=" + ID + "&area=" + area + "&profile=" + profile + "&access=" + access;

  Serial.println(postData);
  if ((WiFi.status() == WL_CONNECTED)) {

    HTTPClient http;


    // Use posttestserver.com
    http.begin(logWebsite, sslSha1);
    http.addHeader("Content-Type", "application/x-www-form-urlencoded", false, true);


    // start connection and send HTTP header
    int httpCode = http.POST(postData);

    // httpCode will be negative on error
    if (httpCode > 0) {
      // HTTP header has been send and Server response header has been handled


      // file found at server
      if (httpCode == HTTP_CODE_OK) {
        payload = http.getString();
        Serial.println(payload);

      }
    } else {
      Serial.printf("[HTTPS] POST... failed, error: %s\n", http.errorToString(httpCode).c_str());
    }

    http.end();
  }
  return payload;
}

String getKeyCode() //Getting the keycode from the keypad
{
  keypad.begin( );
  char key;
  int check = 0;
  String code = "";

  do {
    key = keypad.getKey();
    switch (key) {
      case '*':
        check = 1;
        return code;
        break;

      default:
        if (key) {
          digitalWrite(buzzer, HIGH);
          delay(50);
          digitalWrite(buzzer, LOW);
          code = code + key;
        }
        break;
    }

    delay(100);

  } while (check != 1);
}

Step 6: Finishing Up

To make the circuit more stable and reliable the next stage in the prototyping process is PCB. I used the schematic and sent it off for production.

The raw PCB or printed circuit board is then populated and soldered and done!

If you have any question please leave a comment!

Arduino Contest 2020

Participated in the
Arduino Contest 2020