BusTap
Context: senior capstone, Tech 190A, San José State · Team of six · Status: working prototype, demonstrated live on hardware
My scope: the backend API and WebSocket layer, the web front end including the live map, and the in-vehicle electronics. The mechanical enclosure was designed by teammates.
Riders at a transit center often can’t tell which of several identical buses on the same route is leaving next. Drivers had no clean way to check in. BusTap connected the two: a driver taps an RFID card at the vehicle, and a live departures board updates for riders in real time.
The problem
Observed at Alum Rock Transit Center:
- Multiple buses on the same route park together during layovers, so riders can’t identify which one is actually departing.
- Drivers had no concise check-in system — the existing backend was old and awkward.
- The result is avoidable stress for riders and friction for staff.
The goal was transparency on both sides: give drivers a one-tap check-in, and give riders accurate, real-time departure information with live route tracking on a single page.
Architecture
┌────────────────────────────────┐
│ Raspberry Pi 4 (in-vehicle) │
│ ├─ RFID / NFC reader │ driver taps card
│ ├─ 16x2 LCD → status, next bus ID
│ ├─ LEDs → blue = valid, red = invalid
│ ├─ sound sensor → ambient noise
│ └─ USB speaker → announces next bus ID
└───────────────┬────────────────┘
│ POST { uid, driver_id, bus_id, route_id, stop_id }
▼
┌────────────────────────────────┐
│ REST API │
│ events table — every tap │
│ tags registry — uid → driver_id, bus_id,
│ first_seen, last_seen, tap_count
│ routes / stops tables │
└───────────────┬────────────────┘
│ WebSocket /ws
▼
┌────────────────────────────────┐
│ Web front end │
│ • Departures board │
│ • Driver dashboard │
│ • Live map │
└────────────────────────────────┘
Flow: RFID tap → reader POSTs the event → API writes to events and updates the tags
registry → WebSocket pushes to every connected client → departures board and route filter update
live.
Design decisions worth explaining
A tag registry separate from the event log. Every tap is appended to events, but the tags
table keeps a rolling summary per card UID — first_seen, last_seen, tap_count. That makes
“who tapped, how often, and when last” a single row read instead of an aggregate over the whole
event history. Append-only truth, plus a cheap materialized view of it.
WebSocket push rather than polling. A departures board is the case where polling is both wasteful and stale — poll slowly and the board lags, poll quickly and every rider’s phone hammers the API for data that usually hasn’t changed. Pushing on tap means the board changes at the moment the event happens, and idle clients cost nothing.
Route filtering server-shaped, client-applied. Events carry route_id and stop_id, so the
page’s route dropdown filters departures in real time without another round trip.
The live map is three open-source pieces doing one job:
- Leaflet for the interactive map — 42 KB, mobile-friendly.
- OpenStreetMap as the geographic database behind it.
- OSRM to solve the actual problem: route data exists only as an ordered list of stops, and drawing straight lines between them looks wrong. OSRM snaps the sequence to real roads, so the drawn route follows streets the bus actually takes.
That third piece is the non-obvious one — the gap between “I have stops” and “I have a route” is real, and it’s solved by routing engine, not by drawing code.
Accessibility and clarity were treated as requirements, not polish: multiple language options (the ridership this serves is genuinely multilingual), dark/light modes, and on-device LED status so a driver knows whether a tap registered without looking at a screen.
Hardware
Raspberry Pi 4 as controller, with an LCD, RFID/NFC reader, status LEDs (current-limited through series resistors), a sound sensor for ambient noise, and a USB speaker announcing the next bus ID.
The LED scheme mattered more than it looks: a driver taps a card and needs to know immediately whether it registered, without reading a screen. Blue for a valid read, red for invalid, decided at the device rather than after a round trip to the API — so the feedback is instant and still correct when the network is slow.
Enclosure — designed by teammates
Modeled in SolidWorks as a two-part lid and base:
- Snap-fit design for tool-less assembly
- Ventilation features for thermal dispersion
- A window for the LCD and openings for the LED indicators
- Mounting holes for flexible installation
- Internal layout optimized for wiring access and serviceability
This was deliberately a packaged prototype rather than a breadboard demo — the design question was how every component fits and interacts inside one enclosure a person would actually mount in a vehicle.
Demonstration
We built a simulation mode as a fallback for the presentation, so the departures board could be driven without live taps if the hardware misbehaved on the day.
We didn’t need it. The final demonstration ran end to end on real hardware — physical card tap, LCD and LED feedback at the device, API write, WebSocket push, and the departures board and live map updating in front of the room.
Building the fallback was still the right call. The cost was a few hours; the alternative was a capstone demo with a single point of failure and no recovery path.
Six-person capstone team (Group 6): Jude Gadingan, Qui Pham, Lucas Gabriel Da Cruz Leite, Phuong Thao Nguyen, Jordy Nguyen, Kevin Liz.