Raspberry Pi RFID Door lock

I don't really like keys, especially when I have my hands full. I find it easier to pull my wallet out of my back pocket, swipe it across the RFID reader and push open my front door. I have come up with the completely unoriginal and lame name of "DoorPi" for my keyless system.

Here is the main hardware you need to build your own DoorPi:

  • A Raspberry Pi (I have used a Model B, but you could use any Raspberry Pi with ethernet)
  • 5V 1A Power supply for Raspberry Pi
  • 12V Electric Door Striker
  • 12V Power supply for Electric Door Striker
  • RFID Reader (https://www.sparkfun.com/products/retired/8419)

I also added an RGB LED to display the state of the lock - ready, card read, card fail, card accepted.

This is version two of the DoorPi, the original version had no camera and no status LED. After some use I decided the status was definitely needed so that people would know the unit was operational.

I have taken the camera off the DoorPi, as I haven't come up with a way to put a glass or perspex screen on the front panel.

Breadboard image & parts list

I lost the original schematic, and this is one drawn up from looking at the board. It isn't very advanced, or complicated, but I have it working. Please let me know if I have done something stupid! The breadboard image links to a Fritzing file for you to download. If you haven't already, please support Fritzing by donating, even a small amount helps.

The parts I have used are all ones I had laying around, you can certainly use similar parts instead of these exact ones.

Parts used, in addition to the main hardware above:

  • 12v and 5v Power supply
  • 1x TIP120 Transistor
  • 3x resistors for the RGB LED, I have used 220Ω resistors
  • 1x 47kΩ resistor for the output of the Pi to the door striker
  • 1x RGB LED, common cathode
  • 1x 4N4001 diode

schematic thumbnail

 Photos of built add-on board and other hardware

all but striker

board top

Top of the add-on board.

 

board bottom

Bottom of the add-on board.

 

board side on

Side-on view of the add-on board. The connection to the Raspberry Pi has been made with two eight pin Arduino headers heatshrinked together for stability. They were used because their overall length was needed to sit securely on the Pi's header.

 

led full

Please note the LED has been ground down a little bit so that it makes a flat connection with the faceplate. Blu-tak was used to hold the LED in place while testing (you can still see some on the LED).

 

rfid bottom

Bottom of the RFID module, I added on some stripboard because I wanted the three connections to be next to each other, rather than on each side of the RFID unit.

 

outside panel

This is a blank faceplate (which is the same size as most light switches in Australia), left is the front faceplate, right is the mounting plate. Two holes have been added in the mounting plate. One for the camera (left), and one for the LED (right). Please note the lack of a hole in the faceplate for the camera, as I have not got the camera currently connected to the DoorPi. The LED is diffused through the front faceplate quite nicely, so a hole for it in the faceplate isn't needed.

 

power supply

A 240V to 12V 1.5A supply was used, with an automotive 12V to 5V. The DoorPi is powered by the microUSB connection, while the door striker is powered by the two exposed pins (partially obscured by the microUSB connector).

 

power supply plug

The 12V power supply is from an external HDD that I had spare. I have been buying external HDD drives, opening them up and then putting the HDD in my home server, so I have a few of these supplies laying around.

 

lightbox

This is how I have been taking photos in case anybody was wondering. It is an old chemical drum that has been cut open, some plastic for the floor to sit objects on, plastic on the ceiling in the drum to disperse light (ceiling plastic is hanging off the rubber bands and resting on the blue bulldog clip). The light sources are two battery powered LED lights from an automotive store. A sheet or two of white paper and you're ready to shoot.

 

Let me know if you would like more photos or clarification on how something was done.

Code

Overview

The DoorPi is running a LAMP stack for the RFID card administration, python for the RFID reading / striker / status LED operation and some PHP for taking photos, logging and making sure the card is valid.

I would have avoided using PHP, but I had trouble getting Python to talk to the MySQL database directly. Overall the code is not really secure from attacks, and could be improved quite a lot. Please take this into account if you do use the code verbatim.

I will not go through the instructions on installing Python, Apache, MySQL and PHP, as there are much better resources elsewhere on the internet.

Startup script

This code is placed in the file '/etc/rc.local' so that the Pi will read cards once it has finished booting up.

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
# Print the IP address
#_IP=$(hostname -I) || true
#if [ "$_IP" ]; then
#  printf "My IP address is %s\n" "$_IP"
#fi
cd /var/www
sudo ./readcard.py &
exit 0

Read RFID card script

The above code references the file 'readcard.py', this file is displayed below. It could be neatened up considerably, but once it worked I decided not to touch it again! Since it is a Python script and the layout matters, make sure the tabs are copied correctly if you copy and paste this code.

#!/usr/bin/python
# External module imports
import RPi.GPIO as GPIO
import subprocess
import serial
import time
import signal
import sys
def signal_handler(signal, frame):
        #Set used pins to safe
        GPIO.cleanup()
        sys.exit(0)
# Pin Definitons:
strikerPin =    23
inputPin =              10
redPin =        17
greenPin =      27
bluePin =       22
# Pin Setup:
GPIO.setmode(GPIO.BCM)
GPIO.setup(inputPin, GPIO.IN)
#GPIO.setwarnings(False)
# Pin Setup:
GPIO.setup(redPin,      GPIO.OUT)
GPIO.setup(greenPin,    GPIO.OUT)
GPIO.setup(bluePin,     GPIO.OUT)
#GPIO.setup(strikerPin,         GPIO.OUT, GPIO.PUD_DOWN)
GPIO.setup(strikerPin,  GPIO.OUT)
GPIO.output(redPin,     GPIO.HIGH)
GPIO.output(greenPin,   GPIO.HIGH)
GPIO.output(bluePin,    GPIO.HIGH)
GPIO.output(strikerPin, GPIO.LOW)
code = ''
serial = serial.Serial("/dev/ttyAMA0", baudrate=9600)
if __name__ == '__main__':
        try:
                while 1:
                        signal.signal(signal.SIGINT, signal_handler)
                        data = serial.read()
                        if data == '\r':
                                code = code.strip()
                                GPIO.output(bluePin,    GPIO.HIGH)
                                GPIO.output(greenPin,   GPIO.LOW)
                                GPIO.output(redPin,     GPIO.LOW)
                                ret = subprocess.check_output(["/usr/bin/php", "/var/www/doorpi.php", "%s" % code])
                                GPIO.output(bluePin,    GPIO.HIGH)
                                GPIO.output(greenPin,   GPIO.HIGH)
                                GPIO.output(redPin,     GPIO.HIGH)
                                code = '' # Reset the code var
                                if ret:
                                        GPIO.output(strikerPin, GPIO.HIGH)
                                        GPIO.output(redPin,     GPIO.LOW)
                                        GPIO.output(bluePin,    GPIO.LOW)
                                        GPIO.output(greenPin,   GPIO.HIGH)
                                        time.sleep(5)
                                        GPIO.output(greenPin,   GPIO.LOW)
                                        time.sleep(0.25)
                                        GPIO.output(greenPin,   GPIO.HIGH)
                                        time.sleep(0.25)
                                        GPIO.output(greenPin,   GPIO.LOW)
                                        time.sleep(0.25)
                                        GPIO.output(greenPin,   GPIO.HIGH)
                                        time.sleep(0.25)
                                        GPIO.output(greenPin,   GPIO.LOW)
                                        time.sleep(0.25)
                                        GPIO.output(greenPin,   GPIO.HIGH)
                                        time.sleep(0.25)
                                        GPIO.output(greenPin,   GPIO.LOW)
                                        time.sleep(0.25)
                                        GPIO.output(greenPin,   GPIO.HIGH)
                                        GPIO.output(strikerPin, GPIO.LOW)
                                        GPIO.output(redPin,     GPIO.HIGH)
                                        GPIO.output(bluePin,    GPIO.HIGH)
                                        serial.flushInput()
                                else:
                                        GPIO.output(greenPin,   GPIO.LOW)
                                        GPIO.output(bluePin,    GPIO.LOW)
                                        GPIO.output(redPin,     GPIO.LOW)
                                        time.sleep(0.25)
                                        GPIO.output(redPin,     GPIO.HIGH)
                                        time.sleep(0.25)
                                        GPIO.output(redPin,     GPIO.LOW)
                                        time.sleep(0.25)
                                        GPIO.output(redPin,     GPIO.HIGH)
                                        time.sleep(0.25)
                                        GPIO.output(redPin,     GPIO.LOW)
                                        time.sleep(0.25)
                                        GPIO.output(redPin,     GPIO.HIGH)
                                        time.sleep(0.25)
                                        GPIO.output(redPin,     GPIO.LOW)
                                        time.sleep(0.25)
                                        GPIO.output(redPin,     GPIO.HIGH)
                                        GPIO.output(greenPin,   GPIO.HIGH)
                                        GPIO.output(bluePin,    GPIO.HIGH)
                                        serial.flushInput()
                        else:
                                code = code + data
        except KeyboardInterrupt:
                pass
        except:
                pass
        finally:
                print('Safe exit')
                #Set used pins to safe
                GPIO.cleanup()
                sys.exit(0)

A few lines past the 'while' declaration in the code above there is a reference to the file 'doorpi.php', this script checks the card data in the database and returns true if it is valid, false if it is not valid. This file is displayed below.

<?php
/*
** This script receives string input from the readcard.py script.
** The string input is then checked in the DB to see if the output
** should be triggered to open the door.
*/
// setup DB connection
$mysqli = new mysqli("localhost", "doorpi", "d00rp1", "doorpi");
if ($mysqli->connect_errno) {
    syslog(LOG_ERR, "Failed to connect to MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error);
}
//$mysqli->autocommit(TRUE);
if(isset($argv[1]) && !empty($argv[1])) {
        $code = $argv[1];
} else {
        echo "\nUSAGE: doorpi.php *CARD_ID*\n\n";
        return;
}
// Check length is 12 chars long
$len = strlen($code);
if($len != 12) {
        $code = trim ($code, "\x02\x03\x12\x0A");
}
// Check the input is a hex string
if (preg_match('/([0-9]|[A-F]){12}/', $code)) {
        // Check the database for the card
        $q = "SELECT id, card, active FROM cards WHERE card='".$code."';";
        /* Select queries return a resultset */
        if ($result = $mysqli->query($q)) {
            $row = $result->fetch_array();
                if($result->num_rows == 1) {
                        if($row['active'] == 1) {
                                // Save to the database
                                $card_id =              $row['id'];
                                $timestamp =    date("Y-m-d h:i:s", time());
                                $photo_timestamp = time($timestamp);
                                $result = '1'; // Door is opened
                                $q = "INSERT INTO `logs` (`card_id`, `timestamp`, `result`, `photo`) VALUES ('".$card_id."', '".$timestamp."', '".$result."','photos/".$photo_timestamp.".jpg');";
                                $result = $mysqli->query($q);
                                // Take photo 
// Not currently used // exec('raspistill -t 1 -n -ex auto -v -w 1024 -h 768 -bm -o ./photos/'.$photo_timestamp.'.jpg'); echo TRUE;
return; } else { $card_id = $row['id']; $timestamp = date("Y-m-d h:i:s", time()); $photo_timestamp = time($timestamp); $result = '0'; // Door is not opened // Log the attempt $q = "INSERT INTO `logs` (`card_id`, `timestamp`, `result`, `photo`) VALUES ('".$card_id."', '".$timestamp."', '".$result."','./photos/".$photo_timestamp.".jpg');"; $result = $mysqli->query($q); // Take photo exec('raspistill -t 1 -n -ex auto -v -w 1024 -h 768 -bm -o ./photos/'.$photo_timestamp.'.jpg'); echo FALSE;
return; } } else { // Unknown card $timestamp = date("Y-m-d h:i:s", time()); $q = "INSERT INTO `logs` (`card_id`, `timestamp`, `result`) VALUES ('0', '".$timestamp."', '0');"; $result = $mysqli->query($q);
echo FALSE;
return; } } }
echo FALSE;
return;
?>

RFID Card Administration

I have written up some basic card administration software in PHP, but will not post it here as it needs a lot of work and is quite frankly crap.