Classroom Presence System, Technical
The models, logic, and API behind the system. Reference material.
System architecture & data flow
The models, logic, and API behind the system. Reference material.
%%{init: { "flowchart": { "htmlLabels": false, "diagramPadding": 16 } } }%%
flowchart TD
BS[Bell Schedule] --> CP[Class / Period]
CP --> REG["Registration:
Student + Face + Computer + RFID Tag"]
REG --> RFID["RFID System
Front door / Back door / Room"]
REG --> CAM["Camera System
Spatial presence, face-scanning system"]
RFID --> CORR[Correlation / Intelligence Engine]
CAM --> CORR
CP -. expected enrollment .-> CORR
BS -. attendance window .-> CORR
CORR --> PRES[Attendance / Presence State]
Target architecture: RFID and camera events meet in one correlation engine, gated by the bell schedule.
sequenceDiagram
participant Bell as Bell Schedule
participant Win as Attendance Window
participant Door as Door Sensors (RFID)
participant Cam as Camera System
participant Eng as Correlation Engine
Bell->>Win: Period starts -> window opens
loop While window is open
Door->>Eng: tag read (device event)
Cam->>Eng: face/spatial event (student event)
Eng->>Eng: correlate device + student signals
end
Bell->>Win: Period ends -> window closes
Note over Eng: Any tag/camera event outside the window is logged but NOT counted as attendance
Attendance window: taps only count while the period's window is open.
%%{init: { "htmlLabels": false, "flowchart": { "htmlLabels": false } } }%%
stateDiagram-v2
[*] --> ABSENT
ABSENT --> PRESENT: device + student seen in window
ABSENT --> TARDY: first seen after grace period
PRESENT --> DEVICE_PRESENT_STUDENT_AWAY: student signal drops
DEVICE_PRESENT_STUDENT_AWAY --> PRESENT: student signal returns
PRESENT --> LEFT_EARLY: both signals stop before window close
PRESENT --> DEVICE_ABSENT_STUDENT_PRESENT: device signal drops, student still seen
TARDY --> PRESENT: settles once both signals confirmed
[*] --> DEVICE_ONLY_NO_CONSENT: device seen, camera opted out
[*] --> TAMPER_DETECTED: tag reports tamper
[*] --> IGNORED_OUT_OF_WINDOW: read outside attendance window
Presence states: what each student can be in, each period.
%%{init: { "flowchart": { "htmlLabels": false, "diagramPadding": 16 } } }%%
flowchart LR
A[New student enrolled] --> B["Registration:
capture Face ID,
assign Computer,
issue RFID Tag"]
B --> C[Tag: ACTIVE]
C -->|Tag lost or damaged| D[Tag: RETIRED]
C -->|Tamper signal received| E[Tag: TAMPERED]
D --> F[Reissue new tag]
E --> F
F --> C
C -->|Student unenrolled/graduated| G["Tag: RETIRED
Computer unlinked"]
Registration and tag lifecycle: how a tag is issued, flagged, and replaced.
Technical details
About the technical design
How the RFID and camera signals get correlated into an attendance record, the most-developed of the three input tracks, for readers who want the mechanism and not just the pitch. QR follows the same event API once it's generalized.
System Architecture
For the RFID and camera pairing: two independent event sources feed a correlation engine that is also aware of the bell schedule and per-period enrollment. QR would feed the same engine as a third source once integrated.
RFID + Camera Target Architecture
This is the correlation design for the RFID and camera tracks specifically, the most-developed of the three inputs. QR follows the same event API once it's generalized in Cycle 1.
Attendance Window Logic
The bell schedule doesn't just define start and end times for tardy math. It defines a window during which reads count as attendance at all.
Presence State Model
For the RFID and camera pairing: two independent signals, device via RFID and student via camera, get correlated into one of these states per student per period.
| State | Device (RFID) | Student (Camera) | Meaning |
|---|---|---|---|
| PRESENT | seen | seen | Normal attendance |
| TARDY | first seen after window open plus grace period | seen | Arrived late |
| DEVICE_ONLY_NO_CONSENT | seen | opted out | Student declined face scanning by policy. Presence is based on the device signal alone and is not flagged as anomalous or incomplete. |
| DEVICE_PRESENT_STUDENT_AWAY | seen | not currently seen | Bathroom or temporary-exit scenario. Not counted as absent or left early. |
| LEFT_EARLY | seen, then both signals stop before window close | stopped before window close | Exited before period ended |
| DEVICE_ABSENT_STUDENT_PRESENT | not seen | seen | Student present without their registered device. Flagged for manual review (forgot laptop, borrowed one, or tag failure). |
| ABSENT | not seen | not seen | No signal at all during the window |
| TAMPER_DETECTED | tag reports tamper/invalid | n/a | Tag physically compromised. Excluded from attendance and flagged for tag reissue. |
| IGNORED_OUT_OF_WINDOW | seen | n/a | Read occurred outside the attendance window. Logged, not counted. |
Registration & Tag Lifecycle
Data the system needs to persist
No code yet for the full model. This is the entity list, not a schema.
- Students: id, name, enrolled periods
- Periods: id, name, start time, end time, expected enrollment
- Computers: id, assigned student
- RFID Tags: UID, linked computer, status (active/tampered/retired), issue date
- Registrations: student, face ID, computer, and tag, the four-way binding
- Doors: id, room, side (front/back)
- RFID Events: tag UID, door, timestamp
- Camera Events: student (via face match), timestamp, spatial zone; stub interface into the face-scanning system
- Attendance Windows: derived per period from the bell schedule
- Presence Records: computed state per student per period, the output of the correlation engine
API Endpoints (merged into OCS, PRs #96 and #97)
| Method | Path | Auth | What it does |
|---|---|---|---|
POST | /api/presence/event | Device key | Record a tap (source rfid) or a camera result (source camera) |
POST | /api/rfid/scan | Device key | Route the tap reader uses; forwards to /api/presence/event |
GET | /api/presence/rooms/{id}/taps/pending | Device key | Camera asks for taps from the last 15 seconds |
GET | /api/presence/rooms/{id}/status | Teacher / Admin | Each person's state now (the dashboard polls this every 2 s) |
GET, POST | /api/presence/rooms | Teacher / Admin | List or create rooms: a class, a club meeting, or an event |
GET, PUT, DELETE | /api/presence/rooms/{id} | Teacher / Admin | One room: name, schedule, members |
POST, DELETE | /api/presence/rooms/{id}/members/{uid} | Teacher / Admin | Add or remove a room member |
GET, POST | /api/presence/rooms/{id}/periods | Teacher / Admin | Current period; start a manual period (overrides the schedule) |
GET, POST | /api/presence/schedules | Teacher / Admin | Schedules stored in the database, editable on demand |
GET, PUT, DELETE | /api/presence/schedules/{id} | Teacher / Admin | One schedule and its blocks |
GET, POST, DELETE | /api/presence/schedules/overrides | Teacher / Admin | Special days: another schedule, or no periods, on one date |
GET, POST | /api/rfid/tags | Teacher / Admin | List cards; register a card to a person by uid |
DELETE | /api/rfid/tags/{tag_uid} | Teacher / Admin | Remove a card |
GET | /api/rfid/rooms/{id}/events | Teacher / Admin | Latest 200 taps in a room |
GET, POST | /api/export/presence, /api/export/import/presence | Admin | Backup and restore of every presence table during OCS upgrades |
POST | /api/authenticate | Password | OCS login; sets the session cookie |
Device key = the X-API-Key header the tap reader (and later the camera) sends (RFID_API_KEY on the server). Teacher / Admin = a normal OCS login. Example tap, the request the tap reader sends and what Flask returns:
POST /api/presence/event
X-API-Key: (device key)
{
"event_id": "3f2b8c1e-6a1d-4f7e-9b0a-2d5c7e9f1a44",
"source": "rfid",
"room_id": 1,
"occurred_at": "2026-10-12T15:44:07.123Z",
"tag_uid": "1234567890",
"device_id": "reader-1"
}
200 OK (trimmed)
{
"status": "accepted",
"duplicate": false,
"event": {
"uid": "vibham",
"user_name": "Vibha Mandayam",
"type": "enter",
"period": "Demo Period",
"occurred_at": "2026-10-12T15:44:07.123Z",
"received_at": "2026-10-12T15:44:07.410Z"
}
}
Errors: 401 bad or missing device key, 404 unregistered tag ("status": "unregistered"), 400 missing fields or a timestamp without a timezone. A resent event_id returns the original record with "duplicate": true.
Testing the API: api/presence.http
| # | Method | Request | Dashboard shows |
|---|---|---|---|
| 1 | POST | Log in | Teacher session |
| 2 | POST | Start demo period | Everyone: not yet arrived |
| 3 | POST | Tap | Present |
| 4 | POST | Tap again | Stepped Out |
| 5 | POST | Same tap, resent | Not counted twice |
| 6 | POST | Fake device key | 401 rejected |
| 7 | GET | Status | Same data as dashboard |
The same sequence as our Postman collection, now as the REST Client file api/presence.http in the OCS backend, the new testing process for OCS. It runs against local or deployed OCS, and the dashboard updates live.
Current Software Stack (RFID Track)
- Reader: Raspberry Pi 4 with a Waveshare PN532 NFC HAT (UART mode), reading S50 (MIFARE Classic 1K) cards at 13.56 MHz. Contact tap, not the UHF doorway design above. The same
reader.pyalso runs an RC522 board, and card numbers match between the two. - Backend: the presence API is merged into the shared OCS Flask backend (PRs #96 and #97). Staff routes require an OCS Teacher or Admin login; readers use the device key. Deployment to the OCS server is pending.
- Database: OCS's own database (MySQL in production); the presence tables are included in OCS's export/import, so upgrades that rebuild the database keep every card, period, and tap.
- Frontend: the live dashboard works on our local OCS today and moves to OCS Pages after the deploy. Next: students register their own cards from a My card page (built, PR after deploy).
Open Technical Questions
- Bag-orientation reliability: a tagged laptop inside a closed backpack has no guaranteed tag-to-reader orientation, and UHF read reliability drops off-axis. Not yet solved. See Research for candidate antenna designs that address this directly.
- Camera system interface: what the face-scanning system exposes (API, event stream, or file) still needs to be defined, including how it reports a student who has opted out.
- Tag mounting standard: a single mounting spot on the laptop (for example, the underside near a plastic vent) so tag type (on-metal versus standard) can be standardized across devices.