ClassPulse: Killing the Attendance Register With WiFi, GPS, and a Raspberry Pi

Every student knows the ritual. The professor walks in. The attendance sheet starts its lap around the room. Someone three rows back marks themselves present even though they left 10 minutes ago. Someone else, who’s been sitting attentively for an hour, forgets to sign and gets marked absent.

The attendance register is one of the most consistently broken systems in education. It’s manual, gameable, and produces data that’s only as accurate as the least attentive student in the chain.

I built ClassPulse to fix this — automatically, without QR codes, without beacons, without any manual action from students at all.


The Core Idea: Attendance as Infrastructure

The insight behind ClassPulse is simple: being in class already produces a measurable digital signal. Students connect to the classroom WiFi. Their phones know their GPS location. You don’t need to ask them to do anything—you just need to listen to signals they’re already producing.

ClassPulse marks you present when all three conditions hold simultaneously:

  1. Your device is connected to the classroom WiFi network (not any WiFi — specifically the classroom SSID)
  2. Your GPS coordinates are within the classroom geofence (configurable radius, default 50m)
  3. You’ve maintained both conditions for at least 45 continuous minutes

No QR code scanning. No Bluetooth beacons. No NFC taps. No manual check-in. Just walk into class, and if you’re actually there, you get marked as present.


Architecture: Android + Raspberry Pi Server

ClassPulse has two components that need to work together tightly:

flowchart TD subgraph APP["Android App — Flutter"] T["30s Timer"] W{"Classroom WiFi\nSSID match?"} G{"GPS ≤ 50m\ngeofence?"} C["Increment\npresence counter"] RT["Reset counter"] T --> W W -->|No| RT W -->|Yes| G G -->|No| RT G -->|Yes| C end C -->|POST /api/presence| PI RT -->|POST absence| PI subgraph PI["Raspberry Pi — Flask"] R["/api/presence\nroute"] TH{"duration\n≥ 2700s?"} DB["SQLite\nattendance records"] R --> TH TH -->|Yes| DB TH -->|No| WAIT["Keep accumulating"] end DB --> DASH["Teacher Dashboard\n/dashboard — live view"]

The Android App (Flutter/Dart)

The student-facing app is built in Flutter, targeting Android 6.0+. Flutter was the right choice here: one codebase, Material 3 design system out of the box, and excellent support for the two Android APIs I needed most — WiFi state monitoring and background location services.

The Detection Loop:

On the app side, a background service runs a continuous detection loop:

// attendance_service.dart (simplified)
class AttendanceService {
  Timer? _ticker;

  void start() {
    _ticker = Timer.periodic(Duration(seconds: 30), (_) async {
      final ssid = await WiFiForIoTPlugin.getSSID();
      final position = await Geolocator.getCurrentPosition();

      final isOnClassroomWifi = ssid == _classroomSSID;
      final isInGeofence = _distanceTo(position, _classroomLocation) <= 50.0;

      if (isOnClassroomWifi && isInGeofence) {
        _continuousMinutes += 0.5;
        _reportToServer();
      } else {
        _continuousMinutes = 0; // Break in presence resets counter
        _reportAbsence();
      }
    });
  }
}

Every 30 seconds, the app checks WiFi SSID and GPS position. If both conditions pass, it increments a “presence counter.” If either breaks (student leaves, disconnects), the counter resets. After 45 continuous minutes, the server marks that student as PRESENT for the session.

Why 45 minutes? It’s long enough to prevent gaming (connecting to WiFi just to get marked present, then leaving), but short enough to handle classes that end early or run over.

The Raspberry Pi Server (Python + Flask)

The server is intentionally lightweight—designed to run on a Raspberry Pi sitting in the classroom connected to the local network. No cloud required.

# app.py (core routes, simplified)
from flask import Flask, request, jsonify
from datetime import datetime, timedelta

@app.route('/api/presence', methods=['POST'])
def report_presence():
    data = request.json
    student_id = data['student_id']
    session_id = data['session_id']
    
    key = f"{student_id}:{session_id}"
    presence_log[key]['last_seen'] = datetime.now()
    presence_log[key]['duration'] += 30  # seconds
    
    if presence_log[key]['duration'] >= PRESENT_THRESHOLD:
        mark_present(student_id, session_id)
    
    return jsonify({'status': 'ok'})

@app.route('/dashboard')
def dashboard():
    # Real-time view of who's present
    return render_template('dashboard.html', 
                           students=get_live_status())

The dashboard updates in near-real-time (polling every 10 seconds) and shows teachers the current live status of every registered student. Teachers can also export attendance data as JSON for their own records.


The GPS Geofencing Layer

WiFi-based attendance alone isn’t enough. A student could:

  • Mark themselves present by connecting to the classroom WiFi from outside the building
  • Use a WiFi repeater or VPN to spoof their network location

GPS geofencing closes this loophole. The classroom location is set once by an IT admin (a single tap on a map in the teacher dashboard), and the radius is configurable from 25m to 200m.

The geofence check uses the Haversine formula to compute distance from the phone’s GPS reading to the classroom centroid:

double _distanceTo(Position pos, LatLng classroom) {
  return Geolocator.distanceBetween(
    pos.latitude, pos.longitude,
    classroom.latitude, classroom.longitude,
  );
}

In practice, indoor GPS on phones has ~5–15m accuracy in most modern buildings. A 50m default radius gives enough tolerance for GPS drift while still being meaningful.


The Optional Camera Layer

For high-stakes sessions where even GPS + WiFi might not be sufficient (final exams, seminars), ClassPulse supports an optional IP camera layer using OpenCV.

When enabled, a camera feed is processed server-side:

  • Face detection counts the number of humans in the frame
  • Anomaly detection flags unusual patterns (e.g., 30 check-ins but only 15 faces detected)

This isn’t facial recognition (no identity mapping) — it’s just headcount verification. The goal is to flag sessions where the numbers don’t add up, not to build a surveillance system.

Any IP camera works, including a phone running an IP camera app.


The Setup Reality

Getting ClassPulse deployed in a real classroom is a 15-minute operation:

For IT Admin:

# On the Raspberry Pi
./setup_server_simplified.sh
sudo systemctl enable classpulse.service
sudo systemctl start classpulse.service
# Open http://PI_IP:5000/dashboard
# Set classroom GPS location — done.

For Students:

1. Install ClassPulse.apk
2. Register with student ID
3. Done.

After that, attendance happens automatically. No daily interaction needed from anyone.


What Makes This Different From Existing Systems

Feature QR Code System Biometric System ClassPulse
Student action required Scan QR every class Tap/scan every class Zero
Proxy attendance possible Yes (share QR) Difficult Very difficult
Hardware cost Low High Raspberry Pi (~$35)
Gaming resistance Low High Medium-High
Data exported Varies Varies JSON, real-time

The core differentiator is friction: zero for honest students, high for gamers. A student trying to fake attendance needs to: be on the right WiFi SSID, spoof their GPS to within 50m of the classroom, and maintain both for 45 minutes. That’s a meaningful barrier.


What’s Next

Push notifications. Alert a student who’s been connected for 42 minutes: “You’re about to miss the 45-minute threshold — looks like you left early. Heads up.”

Teacher-initiated sessions. Currently sessions are scheduled. A “start now” button in the teacher dashboard would allow ad-hoc session creation.

Tamper detection. GPS spoofing is possible with root access. Combining accelerometer data (are you moving like someone in a building?) with GPS would make spoofing significantly harder.

iOS support. Flutter makes the port tractable. iOS background location is more restricted than Android, but the core flow should work within Apple’s guidelines.

The Teacher Dashboard in Detail

The teacher dashboard (/dashboard) was designed for a single audience: a professor who is not technical and has 30 seconds to check if their class is full.

The view shows:

  • A live list of all registered students with a colour-coded status: PRESENT (green), PARTIAL (yellow — connected but under 45 min), ABSENT (red), NOT YET CHECKED IN (grey)
  • A running count: “23/31 students present”
  • Elapsed session time
  • A one-button Export JSON that downloads the full session log — student IDs, timestamps, GPS coordinates, connection duration, and final status

Teachers don’t need to touch the app again after the session is started. ClassPulse runs and finalises on its own.

For the system overview docs (SIMPLIFIED_SYSTEM_OVERVIEW.md, SYSTEM_ARCHITECTURE.md), the architecture is summarised as three layers:

Detection Layer:    Flutter app (WiFi SSID + GPS polling every 30s)
Communication Layer: Flask REST API over local WiFi (no internet needed)
Verification Layer:  Pi server (presence timer + geofence validator + optional CV)

All three layers are local. The system works in a building with no internet connection — which is actually a common scenario in exam halls.

The Deployment Docs: What IT Admins Get

The repo ships with a WIFI_DEPLOYMENT_GUIDE.md that walks through the full physical setup: which Raspberry Pi model to use (Pi 4, 4GB), how to configure hostapd if you want the Pi to also be the classroom access point (not just connect to one), how to set up classpulse.service to auto-start on boot, and how to configure the classroom GPS coordinates.

There’s also a TEACHER_QUICK_GUIDE.md — a single page, plain English: how to start a session, how to read the dashboard, how to export records, what to do if a student says they’re not being marked present.

The goal was a system where the IT admin does a 15-minute setup once and then teachers operate it forever without needing support.


ClassPulse is the kind of project I build because the problem is real and the fix is obvious — once you look at it through an engineering lens rather than an administrative one.

The attendance register had a good run. It’s time to retire it.

— Vasanth