/

dorm keycard access

I have the firstest of first world problems. I am mildly inconvenienced by having to keep both my school-issued RFID card and my dorm key on my person to leave my room. Naturally, it was eating me up inside. Something simply had to be done.

Not only that, but it had been a bit since my last electronics project and was itching to work on something. After thinking about it, this seemed pretty doable.

Mechanics

I was starting fresh when it came to buying parts, so simplicity was key. I ended up buying just two things: a knock-off Arduino starter kit with an NFC reader and a metal gear servo. It comes out to about $35, but it has parts that I’ll presumably use for other projects. If I was willing to wait a few months for them to arrive, I probably could have cut the budget a fair amount, but I was impatient.

The janky mechanical design is truly the centerpiece of the project. The servo is attached with a 3D printed mount and double-sided tape to the door, just below the door handle. A small pulley mounted to it’s output shaft pulls a length of shoelace (we started with dental floss, but later upgraded), pulling the handle down. Zip ties and duct tape are used liberally to keep the string in place. The door’s weighting is such that when the handle’s turned, the door pops open slightly, letting you in.

I wanted the exterior of the door to be as inconspicuous as possible. While it came close, I couldn’t get the RC522 to read cards through our door. Now of course I could have chiseled out a recess in the back of the door to accommodate the reader, but the roommates didn’t like that idea. Instead we wrapped the cables around the edge of the door, mounting the RFID reader above the handle. Thanks to the Animal Crossing themed name tags on the door, it was pretty easy to hide.

Electronics

If it works, it’s good enough right? The Arduino is taped up to the door with a mess of jumper cables going to the servo and RFID reader. The code below has the pin layout.

Powering the contraption was a challenge. Batteries may have been an option, but I didn’t want to go through the work of trying to optimize its power consumption. Equally as janky as the mechanical design, we used an extension cord, USB power brick, USB extender and cable to reach the Arduino. Thankfully, the USB power was enough for servo motor, so we wouldn’t need any more cables.

Software

Continuing the theme of bodge jobs, the Arduino code is pretty thrown together. Every 50 milliseconds, the arduino polls the RFID reader. If a new card has appeared, it gets its UID, which is theoretically unique. If it matches one of the three that we have, the servo opens the door for a few seconds.

/*
 * 
 *
 * Pin Layout:
 * -------------------------
 * MFRC522      Arduino Uno
 * -------------------------
 * RST          D9
 * SDA(SS)      D10
 * MOSI         D11
 * MISO         D12
 * SCK          D13
 * GND          GND
 * VIN          3.3V
 * -------------------------
 * Servo        Arduino Uno
 * -------------------------
 * Black        GND
 * Red          5V
 * White        D6
 * -------------------------
 * Buzzer       Arduino Uno
 * -------------------------
 * Positive     5
 * Negative     GND
 * 
 */

const int NUM_USERS = 3;
const unsigned long ALLOWED_UIDS[NUM_USERS] =
{ ALLOWED UIDS (COMMA SEPARATED) };

const int DOOR_OPEN_SECONDS = 2;

#include <SPI.h>
#include <MFRC522.h>
#include <Servo.h>

#define RST_PIN 9
#define SS_PIN 10

#define SERVO_PIN 6
#define BUZZER_PIN 5

// Create servo instance
Servo doorServo; 

// Create MFRC522 instance 
MFRC522 mfrc522(SS_PIN, RST_PIN);

void (*resetFunc)(void) = 0;

void setup()
{
    // Start serial communication for debugging
    Serial.begin(9600);
    Serial.print("Initializing... ");

    SPI.begin();  // Init SPI bus
    mfrc522.PCD_Init();  // Init MFRC522
    delay(4);

    // Sets reader to maximum gain
    mfrc522.PCD_SetRegisterBitMask(mfrc522.RFCfgReg, (0x07 << 4));
    
    // Attach the servo to its pin
    doorServo.attach(SERVO_PIN);
 
    // Make sure the servo is set to the door close position
    closeDoor();

    // Set the buzzer pin the output
    pinMode(BUZZER_PIN, OUTPUT);
    Serial.println("Ready");
}

unsigned long loops = 0;
void loop()
{   
    // Check if a new card is present
    if (mfrc522.PICC_IsNewCardPresent())
    {
        // If so, get it's UID and make sure it's not -1
        unsigned long uid = getID();
        if (uid != -1)
        {
            Serial.print(uid);
            bool match = false;

            // Iterate through the list of allowed UIDs
            // and see if there's a match
            for (int i = 0; i < NUM_USERS; i++)
            {
                if (uid == ALLOWED_UIDS[i])
                {
                    match = true;
                }
            }
            if (match == true)
            {
                Serial.println(" UID recognized");

                // If there was a match, beep
                // the buzzer and open the door for some time
                openDoor();
                digitalWrite(BUZZER_PIN, HIGH);
                delay(50);
                digitalWrite(BUZZER_PIN, LOW);
                delay(DOOR_OPEN_SECONDS * 1000 - 50);
                closeDoor();
            }
            else
            {
                Serial.println(" UID not recognized");

                // Otherwise, don't open the door
                rejection();
            }
        }
    }

    delay(50);
}

unsigned long getID()  // Gets the UID of the card
{
    if (!mfrc522.PICC_ReadCardSerial())
    {
        return -1;
    }
    unsigned long hex_num;
    hex_num = mfrc522.uid.uidByte[0] << 24;
    hex_num += mfrc522.uid.uidByte[1] << 16;
    hex_num += mfrc522.uid.uidByte[2] << 8;
    hex_num += mfrc522.uid.uidByte[3];
    mfrc522.PICC_HaltA();
    return hex_num;
}

void closeDoor()  // Closes the door
{
    Serial.println("Door closing");
    doorServo.write(180);
}

void openDoor()  // Opens the door
{
    Serial.println("Door opening");
    doorServo.write(0);
}

// Long beep then a short beep to indicate incorrect card
void rejection()
{
    digitalWrite(BUZZER_PIN, HIGH);
    delay(200);
    digitalWrite(BUZZER_PIN, LOW);
    delay(100);
    digitalWrite(BUZZER_PIN, HIGH);
    delay(100);
    digitalWrite(BUZZER_PIN, LOW);
}

That’s pretty much it. A simple, fun, semi-useful first project for Washington. We’ll see if it holds up well enough that I’ll stop taking my keys with me.