Back to Projects

Attendance System

An attendance and tracking system using RFID.

PythonWebRFID

Introduction

For security reasons, this project does not have a public repository available. The motivation to this project was creating a good attendance system for my high school's FIRST robotics team. I felt like attendance was lacking and thought it would be funny to give people "points" for showing up. I would regularly have fun with this and update it via a website but then decided to formalize this into something more.

Identification Cards

Since we had a ID-card maker on hand, I decided to put it to good use. Using the Python Pillow library, I was able to write a script to generate student's ID cards which would later be printed. Inside these cards was an RFID chip which means I had to be smart about transfering data. Funny story about the original ID cards, I will touch upon this later.

Accounts

Now I needed an account system to keep track of members. Each member had a numerical id which would be transfered via RFID. Of course, each student would also have an email and password entry. There is also a position field (member, captain, etc) and a score. This score is what is mostly considered when looking at attendance. Every minute present will result in one score being added to the student. On its own, score is not very helpful. So each student's attendance/score is also kept track of for each day of the month. Via the web panel, administrators are able to see this attendance history for each student. Upon checking in for the day, this history is created via

document["attendance"].append({
    "date": datetime.now().strftime("%x"),
    "in": int(time.time()),
    "out": None

API Design

My API was written in Flask and is quite expansive. There is no point in listing out every endpoint and what they do, but for example, here is the endpoint code for api/check_in:

@views.route("api/check_in", methods=["POST"])
def check_in():
    data = request.get_json(silent=True)
    id = data.get("id")
    checked_in = users.check_in(id)
    return jsonify({"status": 200, "response": "Checked in" if checked_in else "Checked out"})

This is the API endpoint that will be called when students check-in or check-out. The users that you see is also a MongoDB collections object. That object specifically is where accounts are managed. While still pretty interesting, this is just average web-server development.

Handling RFID

This part was a challenge. Before I took on this project, our ID cards were simply our name and a QR code which pointed to a string of our name. Yes, very bad. First of all, if two people had the same name, the system would break. Secondly, this was a very easy system to spoof, I could simply check-in as someone else and all I had to do was know their name. The solution to the first problem was to use the prefix of our school emails since they were unique but that would still not solve the first problem. The first iteration of this project did in fact use the email prefixes and a QR code, instead of RFID, there was a camera which would feed into the Raspberry Pi running an OpenCV script to read the QR code. The best solution was to use RFID chips instead. Despite being still possible, it would be much too annoying to spoof this system. We had disabled copying on all the cards (there is specific metadata that allows you to do this) so students could not see that information their chips sent over. Anyway, I ended up using a MIFARE Classic NFC reader/writer which was connected to a Raspberry Pi 5 running Ubuntu via a PN532 NFC module. This is initialized via

i2c = busio.I2C(board.SCL, board.SDA)
pn532 = PN532_I2C(i2c, debug=False)
pn532.SAM_configuration()

We also want to be communicating with the right RFID tags/cards so we need to add a little check to make sure; this is as simple as making sure len(uid) == 4. To actually write the data onto the card (which was another script the adminstrators could easily run), we prepare block 4 of the data with

BLOCK = 4
data = input_string.encode("utf-8")
padded_data = data.ljust(16, b'\x00')

Now when it came to reading the data on the card, one issue was the system being "spammed". If held to the sensor for too long, it would trigger multiple times. To prevent this, upon a successful read, we add a 0.5 second delay via

while pn532.read_passive_target(timeout=0.5):
    ...
    time.sleep(0.1)

Now, we could read and write data and communicate with the web server to retrieve account information. The next step was to actually give students a proper interface.

The Kiosk Interface

So members could actually use this, I connected the Raspberry Pi to a monitor along with the RFID reader. I could simply have the website up on the monitor and have it automatically refresh the page every x seconds, showing a green checkmark next to students currently marked as present. This was a good idea, but I wanted more responsiveness. I wanted the page to refresh only upon a successful check-in or check-out. I decided to use a library called eel. This allowed me to launch an instance of Chromium in kiosk mode via

eel_thread = threading.Thread(
    target=eel.start,
    args=("index.html",),
    kwargs={"mode": "/snap/bin/chromium", "cmdline_args": ["--kiosk", ...]},
    daemon=True

and let me communicate with my python script which was handling the RFID card reader and expose them to my client-side JavaScript code. For instance,

@eel.expose
def set_status(status, name):
    eel.changeStatus(status, name)

is how I would expose a function to JS and

eel.expose(reloadPage);
function reloadPage() { window.location.reload(); }

is how I might recieve a Python function from the JavaScript side. So upon a successful scan, I could show a banner on the web page telling the user they have checked-in or checked-out and then after some seconds, reset the page. This provided a functional and aesthetic interface for members.

Extra Security

There is some extra security that I did not mention: The web server's API is securied with generated API keys tied to each administrators account. The web server ran on the staff WiFi network, meaning it was for the most part, inaccessable to students (I was given permission to deploy this on the staff network). The ID cards had a built in expiration date of one year (this could be overridden). So members would have to renew their cards once an adademic year. Since the lab closes at 5:30 pm, the cards are ineffective after that time. If a student is currently present by that time and does not check out, they will not recieve attendance points for that day. The robotics lab was already a secure room where regular students were not allowed. Everyone in the room was already quite honest and trustworthy so many additional security measures were simply not needed.