https://github.com/HenriqueJoanoni/edusense-project
- Introduction
- Hardware
- Data, Data Storage, and Data Processing
- Security and Privacy
- The UI, User, and Testing
This project is a smart attendance management system designed to streamline student attendance tracking. It integrates a barcode-based identification system with a modern web platform to ensure accuracy and ease of use.
Backend: Built with Laravel, providing secure APIs, data management, and business logic.
Frontend: Developed in React, offering an intuitive and responsive admin panel for managing students, classes, and
attendance records.
IoT Layer: Powered by Python, running on barcode-enabled scanning device to capture student card data and send it
directly to
the system in real-time.
The system allows administrators and teachers to efficiently manage attendance records while reducing manual errors and improving reliability through automation.
- After cloning this repository, run
composer installto install the dependencies. - Create a
.envfile and set the environment variables as per.env.example. - Run
php artisan migrateto create the database tables. - Run
php artisan db:seedto seed the database with sample data. - Run
php artisan serveto start the server.
-
After cloning this repository, run
yarn installto install the dependencies. -
Run
yarn startto start the development server. -
Open
http://localhost:3000/in your browser.
- 1x Raspberry Pi 5
- 1x Buzzer
- 1x Red LED
- 1x Green LED
- 2x 330Ω Resistors
- 1x BreadBoard
- 1x 10K Potentiometer
- 1x 16x2 LCD Display (HD44780 compatible)
- 1x Barcode Scanner Module (e.g., ATOMIC 2D Barcode Scanner Module)
- 1x Membrane 4*4 button pad
- 8x 1kΩ Resistors
- Jumper Wires
- Power Supply for Raspberry Pi
- MicroSD Card with Raspberry Pi OS installed
- Ethernet Cable (for internet connection)
| Component | GPIO Pin(s) | Raspberry Pi 5 (Physical Pins) | Notes |
|---|---|---|---|
| LCD pin 1 (GND) | GND | Any ground rail | Common ground |
| LCD pin 2 (VCC / 5V) | +5V | Pin 2 or 4 | Powers LCD and backlight |
| LCD pin 3 | Potentiometer middle | – | Contrast adjust |
| LCD RS | GPIO4 | Pin 7 | Register Select |
| LCD EN | GPIO24 | Pin 18 | Enable signal |
| LCD D4 | GPIO23 | Pin 16 | Data bit 4 |
| LCD D5 | GPIO17 | Pin 11 | Data bit 5 |
| LCD D6 | GPIO18 | Pin 12 | Data bit 6 |
| LCD D7 | GPIO22 | Pin 15 | Data bit 7 |
| LCD LED+ | +5V | – | LCD backlight power |
| LCD LED– | GND | – | LCD backlight ground |
| Component | GPIO Pin(s) | Raspberry Pi 5 (Physical Pins) | Notes |
|---|---|---|---|
| Red LED | GPIO5 | Pin 29 | Use ~330 Ω resistor in series |
| Green LED | GPIO6 | Pin 31 | Use ~330 Ω resistor in series |
| Buzzer | GPIO13 | Pin 33 | Active buzzer or passive buzzer driver |
| Component | GPIO Pin(s) | Raspberry Pi 5 (Physical Pins) | Notes |
|---|---|---|---|
| Scanner VCC | 5V | Pin 2 or 4 | ATOMIC base spec supports 5 V |
| Scanner GND | GND | Any ground | Common ground with Pi |
| Scanner TX → Pi RX | GPIO15 (UART RX) | Pin 10 | Connect scanner TX to Pi RX |
| Scanner RX → Pi TX | GPIO14 (UART TX) | Pin 8 | Connect scanner RX to Pi TX |
- https://thepihut.com/products/atomic-barcode-qr-code-scanner-2-base
- https://docs.m5stack.com/en/atom/Atomic%20QRCode2%20Base
| Component | GPIO Pin(s) | Raspberry Pi 5 (Physical Pins) | Notes |
|---|---|---|---|
| Keypad Row 1 | GPIO12 | Pin 32 | Drive row 1 |
| Keypad Row 2 | GPIO16 | Pin 36 | Drive row 2 |
| Keypad Row 3 | GPIO25 | Pin 22 | Drive row 3 |
| Keypad Row 4 | GPIO26 | Pin 37 | Drive row 4 |
| Keypad Column 1 | GPIO7 | Pin 26 | Input column 1 |
| Keypad Column 2 | GPIO8 | Pin 24 | Input column 2 |
| Keypad Column 3 | GPIO9 | Pin 21 | Input column 3 |
| Keypad Column 4 | GPIO11 | Pin 23 | Input column 4 |
- https://nerdcave.xyz/raspberrypi/module-and-sensors/tutorial-4-keypad/
- https://docs.sunfounder.com/projects/davinci-kit/en/latest/python_pi5/pi5_2.1.5_keypad_python.html
- Raspberry Pi 5 will be connected to the internet via Ethernet
- Raspberry Pi will be powered using the official 27w power adapter
In Hardware folder
python -m venv venv
source venv/bin/activate
pip installs
pip install gpiozero adafruit-blinka adafruit-circuitpython-charlcd lgpio
This section describes what data the device sensors will gather, how that data is stored, and how it is processed in the Edusense automatic attendance system (Laravel API backend, React frontend, Python sensor clients). The deployment target is AWS with PubNub used for real-time event streaming to the frontend.
- Sensors and data captured
-
Primary sensors/devices
- Barcode/QR scanner
- What is captured: code value, scanner device id, timestamp.
- How it works: The scanner will send decoded code via USB keyboard emulation, camera-based scanners in Python to decode frames and emit an event.
- Typical frequency: event-driven on scan. Can scan several codes per second; again for attendance we expect one scan per student.
- Optional peripheral sensors
- Camera: for optional face capture (store face embeddings or image hashes only if consented). Frequency: on demand (when a reader event occurs) or continuous at a low frame rate if used for verification.
- Barcode/QR scanner
-
Third-party APIs
- PubNub: used as a real-time pub/sub channel between edge devices/backend and the frontend. Devices publish events to backend or directly to PubNub depending on architecture. PubNub is not used to store canonical records — it is a transport for realtime updates.
- Optional Student Information Systems (SIS) or LMS APIs: if integrating with a college SIS (e.g., to sync student lists or class schedules), those APIs are polled/synced via scheduled sync jobs. Frequency configurable ( hourly/daily).
- If camera-based face recognition uses cloud or third-party services (e.g., AWS Rekognition), these integrations must be explicitly configured and consented; details of that API (request/response fields) must be documented and GDPR/consent rules followed.
- Data model and persistent storage Primary choices
- Primary relational database: MySQL for canonical attendance data and user records. Laravel Eloquent models map to these tables.
Canonical relational schema (MySQL)
- id — BIGINT UNSIGNED, AUTO_INCREMENT, PRIMARY KEY
- student_name — VARCHAR(125)
- registration — VARCHAR(125)
- rfid_code — VARCHAR(125)
- qr_code — VARCHAR(125)
- course_id — INT
- user_id — INT
- FOREIGN KEY (course_id) → courses(id)
- FOREIGN KEY (user_id) → users(id)
- id — BIGINT UNSIGNED, AUTO_INCREMENT, PRIMARY KEY
- device_uuid — VARCHAR(255), UNIQUE
- label — VARCHAR(255)
- location — VARCHAR(255)
- last_seen_at — DATETIME, NULL
- config — JSON, NULL
- created_at — TIMESTAMP, NULL
- updated_at — TIMESTAMP, NULL
- id — BIGINT UNSIGNED, AUTO_INCREMENT, PRIMARY KEY
- year — INT
- semester — INT
- course_id — BIGINT UNSIGNED
- FOREIGN KEY (course_id) → courses(id)
- id — BIGINT UNSIGNED, AUTO_INCREMENT, PRIMARY KEY
- year — INT
- semester — INT
- course_id — BIGINT UNSIGNED
- FOREIGN KEY (course_id) → courses(id)
- id — BIGINT UNSIGNED, AUTO_INCREMENT, PRIMARY KEY
- device_id — BIGINT UNSIGNED
- sensor_type — VARCHAR(50)
- payload — JSON
- event_timestamp — DATETIME
- received_at — DATETIME
- processed — BOOLEAN, DEFAULT FALSE
- created_at — TIMESTAMP, NULL
- updated_at — TIMESTAMP, NULL
- FOREIGN KEY (device_id) → devices(id)
- id — BIGINT UNSIGNED, AUTO_INCREMENT, PRIMARY KEY
- device_id — BIGINT UNSIGNED
- ip — VARCHAR(45)
- firmware_version — VARCHAR(50)
- seen_at — DATETIME
- FOREIGN KEY (device_id) → devices(id)
Example JSON shape for a sensor_events.payload
"uid": "04A3F2C7",
"type": "mifare-classic",
"rssi": -58,
"raw_hex": "0x04A3F2C7",
"reader_port": 1,
"image_url": null
}
Reasoning: Keeping attendances normalized (with student_id and class_id) allows efficient relational queries and reporting. Raw events may be retained in S3 or a sensor_events table for debugging/auditing.
- Sample queries (MySQL)
-
- Mark attendance (insert)
INSERT INTO
attendances(student_id,class_id,device_id,sensor_type,sensor_event_id,recorded_at,source_timestamp,status,metadata,created_at,updated_at) VALUES (1234, 5678, 10, 'rfid', 98765, NOW(), '2025-10-25 08:05:32', 'present', '{"rssi": -52}', NOW(), NOW());
- Mark attendance (insert)
INSERT INTO
-
- Get a student's attendance for a course between dates
SELECT ar.*, c.course_code, s.first_name, s.last_name
FROM
attendancesar JOINclassesc ON ar.class_id = c.id JOINstudentss ON ar.student_id = s.id WHERE ar.student_id = 1234 AND c.start_time BETWEEN '2025-10-01' AND '2025-10-31' ORDER BY c.start_time;
- Get a student's attendance for a course between dates
SELECT ar.*, c.course_code, s.first_name, s.last_name
FROM
-
- Class attendance summary (count present / absent)
SELECT c.id, c.course_code,
COUNT(CASE WHEN ar.status = 'present' THEN 1 END) AS present_count,
COUNT(CASE WHEN ar.status != 'present' THEN 1 END) AS other_count
FROM
classesc LEFT JOINattendancesar ON ar.class_id = c.id WHERE c.id = 5678 GROUP BY c.id, c.course_code;
- Class attendance summary (count present / absent)
SELECT c.id, c.course_code,
COUNT(CASE WHEN ar.status = 'present' THEN 1 END) AS present_count,
COUNT(CASE WHEN ar.status != 'present' THEN 1 END) AS other_count
FROM
-
- Latest reading per device (for heartbeat monitoring)
SELECT d.device_uuid, d.label, d.last_seen_at
FROM
devicesd ORDER BY d.last_seen_at DESC LIMIT 100;
- Latest reading per device (for heartbeat monitoring)
SELECT d.device_uuid, d.label, d.last_seen_at
FROM
-
- Deduplicate multiple rapid reads (example to find duplicates in 5s window)
SELECT
JSON_UNQUOTE(JSON_EXTRACT(se.payload, '$.uid')) AS uid,
se.device_id,
COUNT(*) AS reads,
MIN(se.event_timestamp) AS first_read
FROM
sensor_eventsse WHERE se.event_timestamp BETWEEN '2025-10-25 08:00:00' AND '2025-10-25 09:00:00' GROUP BY uid, se.device_id, FLOOR(UNIX_TIMESTAMP(se.event_timestamp) / 5) HAVING reads > 1;
- Deduplicate multiple rapid reads (example to find duplicates in 5s window)
SELECT
JSON_UNQUOTE(JSON_EXTRACT(se.payload, '$.uid')) AS uid,
se.device_id,
COUNT(*) AS reads,
MIN(se.event_timestamp) AS first_read
FROM
- Processing pipeline and real-time flow
- Step 1: Device capture
- Python client (Raspberry Pi) reads sensor (Barcode/Camera), builds a JSON event with: device UUID, local timestamp, sensor_type, payload.
- The client either:
- a) POSTs event to Laravel API endpoint /api/v1/sensors/events with device API key + TLS, or
- b) Publishes event to PubNub channel (backend subscribed) for low-latency UI updates then backend persists.
- Step 2: Backend processing
- Laravel receives the event, authenticates device, stores raw event, then attempts to resolve student_id by matching UID or code.
- After student resolution, backend creates an attendances entry with metadata.
- Backend emits a PubNub message to the frontend to update real-time dashboards.
- Step 3: Post-processing and analytics
- Cron/queue workers perform aggregation, de-duplication, reconciliation against class schedules, generate reports, and sync with external SIS if configured.
Deduplication policy (example)
- Ignore repeated reads for the same student on the same device within a configurable window (default 30 seconds).
- If multiple devices detect the same tag within the same class timeslot, apply rule: prefer device assigned to class location, or first reported event.
- Cron jobs and scheduled processing We use Laravel's scheduler (artisan schedule) on the server to run recurring maintenance and analytics tasks. Example cron entries and Laravel commands:
Crontab line to run Laravel scheduler:
* * * * cd /path/to/project && php artisan schedule:run >> /dev/null 2>&1
Planned scheduled tasks (examples; describe frequency and purpose):
-
php artisan attendance:aggregate-daily
- Schedule: daily at 02:00 AM
- Purpose: aggregate previous day's attendance into summary tables, compute daily stats for dashboards, and store aggregated caches to speed up UI queries.
-
php artisan attendance:reconcile
- Schedule: every 5 minutes
- Purpose: process unprocessed sensor_events, resolve duplicates, create missing attendances, and mark sensor_events.processed = true.
-
php artisan device:heartbeat-check
- Schedule: every 5 minutes
- Purpose: mark devices offline/online based on last_seen_at and emit alerts for missing devices.
-
php artisan backup:upload --target=s3
- Schedule: daily at 03:00
- Purpose: dump new DB backups and upload to S3. Also trigger retention policy (keep 30 days).
-
php artisan analytics:run-weekly
- Schedule: weekly on Sunday at 04:00
- Purpose: run heavier analytics (attendance trends, no-show patterns), generate PDF/CSV reports for instructors and admins.
-
php artisan sync:sis
- Schedule: hourly (or configurable)
- Purpose: sync student rosters, class schedules and statuses from SIS/LMS. Only run if SIS integration is configured.
-
php artisan storage:prune-old-raw-events
- Schedule: daily at 01:00
- Purpose: purge or archive raw sensor events older than retention window (e.g., 90 days), or move them to cold storage.
-
php artisan security:rotate-keys
- Schedule: monthly
- Purpose: automated key rotation for device API keys (where supported) or to remind admins.
- Retention, backups, and compliance
- Retention:
- Attendance records: retained according to institutional policy (e.g., indefinite or X years).
- Raw sensor events and images: retained for a shorter period (e.g., 90 days), then archived to S3 Glacier or deleted, depending on privacy rules.
- Backups: daily RDS snapshots + nightly exports to S3. Laravel backup packages or native RDS snapshotting recommended.
- Access control and privacy:
- Transport: all device→backend calls use HTTPS/TLS.
- Devices authenticate with API keys or JWTs; keys stored in device config and rotated per policy.
- Images and biometric data must follow campus consent and legal requirements: ideally avoid storing raw faces; store hashes/embeddings only when explicitly consented.
- Real-time notifications (PubNub) usage
- PubNub channels per location/class: backend publishes attendance events to channels (e.g., edusense:location:room101).
- Frontend subscribes to channels to show live dashboards of who just arrived.
- PubNub is used for UI updates only; canonical storage stays in RDS or S3.
- Example of a full event flow (JSON) emitted by a device
"device_uuid": "device-abc-001", "device_label": "Door-1 - Room 101", "sensor_type": "rfid", "payload": { "uid":"04A3F2C7", "rssi":-58, "reader_port":1 }, "event_timestamp":"2025-10-25T08:05:32Z", "local_timestamp":"2025-10-25T08:05:32Z", "firmware_version":"1.2.0" }
Processing summary:
- Device client sends event → backend validates → store raw event → attempt to resolve student → create attendance_record → publish to PubNub → queue for analytics/aggregation → cron/worker jobs perform reconciliation and reporting.
- Implementation notes and best practices
- Debounce and deduplicate events at the edge (device) where possible to reduce network usage, but always re-check on the server to avoid race conditions.
- Keep canonical student list in relational DB and use indexed columns for fast lookups (index on students.student_number and students.id).
- Use background jobs (Laravel queues: Redis/SQS) to handle heavy processing (face recognition, large imports) so synchronous endpoints remain fast.
- Implement idempotency: events include a device_event_id or hash so duplicate POSTs do not create duplicate attendances.
- Rate limits: protect the API from malformed/spammed devices; implement per-device rate limits.
- Test scale: when many devices are active, raw event S3 + Athena or DynamoDB can be used for scalable ingestion and analytics. Start with RDS+S3 for small/medium institutions.
The security of the device will start with its protective case. This will prevent physical interference with the device.
Data will be sent between the Raspberry Pi and PubNub, and the AWS server that has been encrypted with appropriate standards to ensure network privacy. Data transfers to PubNub will be encrypted using PubNub's generated sets of keys.
Secure practices will also be enforced at the server level. All user input will be validated at the server before being acted on. This is to protect against:
- Incomplete information entering the system.
- SQL Injection attacks.
- Crashes occuring from unexpected inputs.
All user passwords will be hashed prior to storage and comparison.
Users will be logged in through tokens, then these tokens will be used to control users' access level and permissions, with deny by default being the standard.
This device is designed to be used by:
- Teachers
- Students
- School Administrators
Students will connect to the system through the scanner, by scanning their ID card, and their face to sign in.
They will be able to access their own personal and attendance data through the online portal.

Teachers will be able to access the attendance data their classes and students within their classes, and student
profiles.
Clicking on any student row will redirect a teacher to that student's page.

Administrators will have access to all student profiles, class information, and attendance data. They will be the users
able to edit and create class information and enrollment.

The success of the project can be determined by how effectively it scans and records attendance, and how quickly it achieves this. The scanner will need to be robust enough to work in a variety of brightness levels, positions, and with a wide sample of students of different features / complexions / accessories (such as glasses).
The project will be considered successful if
- The scanner is able to effectively scan and record students quickly and accurately.
- This data is easily accessible to teachers, students, and administrators and presented in an intuitive way.
- The system takes less time than traditional attendance methods.
- Users are able to use the system with minimal confusion.


