Attendance System
An attendance and tracking system using RFID.
Introduction
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.
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.