We will be undergoing planned maintenance on Oct 7th 6:00AM UTC / Oct 7th 2:00AM ET

Inspiration

In big auditorium courses with no final exam, attendance is the grade. So the QR code on the projector gets photographed and posted in the group chat, the poll answer gets forwarded, and twenty people who are at home get marked present. The students who actually show up are the ones being treated unfairly.

A QR code is information, and information copies for free. Being physically in the room does not. So we built attendance around a physical signal that only reaches about ten metres: Bluetooth.

What it does

SmartClass marks students present automatically when their phone is physically inside the lecture hall, and away / returned as they move, live on the professor's dashboard, with nothing to scan or tap and no app to keep open.

  • A $5 ESP32 board in the room acts as a Bluetooth "room sign". It only needs power.
  • iPhones listen for the room sign and tell the server "I'm next to room 317". Android phones broadcast a rotating anonymous code that the board hears and reports.
  • The server is the only thing that decides: present after a few readings, away after seconds of silence, left, returned, late. It never trusts a single packet.
  • The dashboard shows a live count, per-student status, a seat-zone heatmap, a timeline of the whole class, an end-of-class summary with attendance percentages, and CSV export. Manual overrides stay, because a system that verifies devices should say so honestly.
  • Privacy by design: no names or student numbers ever go over Bluetooth, the Android code changes every minute so it can't be replayed, and presence is only recorded while an enrolled class is running.

How we built it

  • ESP32 firmware (Arduino, NimBLE): continuous Bluetooth scanning, per-token aggregation, HMAC-signed batches to the server, an iBeacon advertiser for iPhones, and a serial console to set Wi-Fi, room and server without reflashing. The BOOT button simulates "walked out of the room" for demos.
  • Backend (Node.js, TypeScript, Fastify, MongoDB Atlas): resolves rotating codes to students in O(1) by precomputing every student's code for the current minute, runs the attendance state machine, and pushes live snapshots to the dashboard over WebSockets. Engine tests cover every transition.
  • Dashboard (React): hero count, status tiles, student table, heatmap, timeline, summary and export, all driven by one live snapshot.
  • Phone app (Flutter): sign in once, then it works with the phone locked. On iOS it ranges the beacon on screen and stays alive in the background so it can report leaving and returning within seconds. A permission gate makes sure Location "Always" and Bluetooth are granted before anything else.
  • A Cloudflare tunnel gives the laptop-hosted backend a public address so phones work over cellular.

Challenges we ran into

  • iPhones can't broadcast in the background. Our first design had every phone broadcasting to the board. Apple hides an app's Bluetooth identity once it's backgrounded, so for iOS we reversed the flow: the board broadcasts, the phone listens. Both paths produce identical evidence on the server.
  • Campus Wi-Fi isolates devices. Phones couldn't reach the laptop at all. A public tunnel fixed it, and taught us the backend belongs in the cloud.
  • iOS permission quirks. The location library only answers "what's my permission?" when the permission changes, so restarting detection silently stalled for a minute. And iOS never offers "Always" in the first prompt; you must ask for "While Using" first and "Always" second. We ended up driving the prompts natively.
  • "Away" must come from Bluetooth, not from closing the app. Students won't keep an app open. Getting a locked iPhone to notice it had left, and harder, that it had come back, took a background keep-alive, a time-based "lost the beacon" rule, and an explicit "I'm leaving" signal for stop and sign-out.
  • Hardware surprises. The ESP32 crashed at boot until we learned Wi-Fi modem sleep must stay on for Bluetooth coexistence; the CH340 USB chip refused to flash at 921600 baud; a laptop with 4 GB of free disk fought every Xcode build.

Accomplishments that we're proud of

  • The full path works end to end on real hardware: iPhone → ESP32 beacon → server → dashboard, with a student going present, away, left and returned while the phone stayed locked in a pocket.
  • Attendance decisions that can't be forwarded to a friend: the phone has to physically hear the room.
  • A privacy story we can defend line by line: nothing identifying over the air, rotating codes, nothing recorded outside a running class.
  • A demo that fits on a desk: one button on the board simulates walking out and back.
  • A hardware-free simulator, so the dashboard can be shown even if the boards die.

What we learned

Bluetooth advertising is a beautifully simple sensor when you keep the board dumb and the server smart. Real devices behave nothing like the docs suggest, so we made the phone report its own status line to the server and debugged from logs instead of guessing. And "instant" has physics behind it: a phone has to miss a few broadcasts before it can honestly say you're gone.

What's next for SmartClass

  • One board per room, and several per hall, for a real seat heatmap and no room mix-ups.
  • Android background broadcasting as a foreground service (written, not yet field-tested).
  • Deploy the backend to a cloud host for a fixed address, and add a rolling code to the room beacon so it can't be cloned either.
  • A university pilot with an opt-in privacy notice; the design already collects nothing it doesn't need.
Share this project:

Updates

Submission history