Overview
We built a Bluetooth presence detection backend for CoSounds to test whether timer-based occupancy tracking could be replaced with continuous room-level scanning. The prototype combined NFC tap gating, Raspberry Pi 3 scanning, PostgreSQL triggers, and pg_cron, then was tested with a small number of registered devices in a controlled environment.
The Problem
The CoSounds prototype used a 2-hour timer after each NFC tap to infer presence. Timers detect entry; they do not detect departure. Students who left early remained marked as present, skewing the collective utility optimization toward preferences of users no longer in the room. Students who stayed beyond the timeout were silently disconnected, requiring manual re-tap to resume tracking. The system operated on occupancy assumptions rather than occupancy data.
If 15 students were recorded as present but only 8 remained, the ML pipeline aggregated soundscape preferences for 7 phantom users.
Constraints
- —Session records created only when a student has performed a deliberate physical action (NFC tap)
- —No Bluetooth pairing dialog, app install, or manual check-out permitted
- —Session state must remain consistent if the Django API server restarts between sequential status writes
- —Hardware fixed at Raspberry Pi 3 with Bluetooth Classic; BLE unavailable during development phase
- —Transient disconnections under 15 minutes must not terminate active sessions
Key Decisions
Gate session creation behind an NFC tap
A scanner running continuously would detect every registered device at proximity, including students who never meant to check in. That's passive tracking, and it's not acceptable in a study space. Requiring the tap flag before session creation means the system cannot create a record for a student who has not performed a deliberate physical action in that visit.
The one-time consumption model, where the flag resets immediately after the session record is written, prevents duplicate sessions from multiple scan cycles. This satisfies the no passive tracking constraint structurally rather than by policy enforcement.
Alternatives considered
- ·GPS tracking
- ·Surveillance cameras
- ·Manual check-in/check-out
Two-Tier Disconnect Detection
Students leaving briefly — a bathroom break or a coffee run; should not be required to tap in again on return. A single-threshold model that ends the session immediately on disconnect treats a brief absence the same as a permanent departure.
The 30-second scan timeout confirms the device has genuinely left Bluetooth range rather than missed a single scan cycle. The 15-minute grace period then holds the session open after that confirmed disconnect. If the student returns within 15 minutes, the session resumes automatically with no re-tap required. If they do not return within 15 minutes, the session ends and a new tap is required to start fresh.
-- pg_cron job: expire grace-period sessions every minute
SELECT cron.schedule(
'expire-grace-sessions',
'* * * * *',
$$
UPDATE sessions
SET status = 'ended', ended_at = now()
WHERE status = 'grace'
AND grace_started_at < now() - interval '15 minutes';
$$
);Alternatives considered
- ·Single fixed timeout with no grace period
- ·Exponential backoff retry on missed scans
- ·Manual check-out as the only session termination path
Results
Tested in a controlled environment with a small number of registered devices. Four defined scenarios validated:
- ✓Standard check-in/check-out: clean session, accurate timestamps
- ✓Brief disconnection (bathroom, coffee run): 15-minute grace period holds the session
- ✓Extended absence: grace period expires, trigger closes session, re-tap opens a new one
- ✓Multiple simultaneous users: independent tracking, no conflicts
4
Scenarios
all passed
10s
Scan Interval
continuous monitoring
0
Phantom Sessions
zero false check-ins
Zero phantom sessions created without an explicit NFC tap across all test runs. Integration with the CoSounds ML pipeline is the next step, pending production hardening and extended scale testing.
Limitation
The main limitation is MAC address randomization. Modern smartphones randomize their Bluetooth MAC address across scan cycles. Static device registration, which this system depends on; works for laptops but fails for phones.
A production version would need a native mobile app to expose a stable identifier during onboarding. Safari also does not expose the Bluetooth Web API, which removes browser-based registration as an option for iOS users. Both problems are solvable. Neither is solved in this prototype.