Developer documentation
Heart Rate Sensor Integration
How heart-rate data reaches GROUP PULSE, and how to add new gateways without redesigning the application.
Architecture
HEART RATE SENSOR ↓ (Bluetooth GATT Heart Rate Service 0x180D) BLE GATEWAY gateway_type: PARTICIPANT_PHONE | CENTRAL_RECEIVER | DEMO ↓ GROUP PULSE SENSOR SERVICE HeartRateSensorService implementation ↓ HeartRateEvent (normalized) REAL-TIME BACKEND PulseEngine — validate, zone, stats, fan-out ↓ COACH LIVE DASHBOARD /coach
Layers above the gateway never know how a reading was captured. The only contract is HeartRateEvent. The UI must never talk to a sensor implementation directly.
HeartRateEvent format
interface HeartRateEvent {
event_id: string
timestamp: number // epoch ms
participant_id: string | null
sensor_id: string
gateway_id: string
gateway_type: 'PARTICIPANT_PHONE' | 'CENTRAL_RECEIVER' | 'DEMO'
class_id: string | null
bpm: number
connection_status: 'CONNECTED' | 'CONNECTING' | 'DISCONNECTED'
battery_level?: number | null
rr_intervals?: number[]
}Every implementation converts its raw input into this shape before ingest. Ingest validates participant, class, sensor and BPM range, derives the zone and percentage of max HR, updates live participant state and recomputes group statistics. Only affected components re-render — no page reloads.
Sensor service interface
interface HeartRateSensorService {
gatewayType; gatewayId
isSupported(): { supported, reason? }
connect(options?): Promise<SensorServiceStatus>
disconnect(): Promise<void>
getStatus(): SensorServiceStatus
subscribe(listener): () => void
unsubscribe(listener): void
}
createSensorService(gateway_type) // src/lib/pulse/services/factory.ts
DEMO -> DemoHeartRateSensorService
PARTICIPANT_PHONE -> ParticipantPhoneBLEService
CENTRAL_RECEIVER -> CentralReceiverBLEServiceParticipant phone mode
The participant signs in, taps Connect heart rate sensor, and the browser pairs with a GATT Heart Rate Service device via Web Bluetooth. Their phone is the gateway. After Join live class, readings carry user_id, participant_id, class_id, sensor_id and gateway_id.
Files: services/participant-phone-ble.ts, route /participant.
Central receiver mode
TABLET / MINI PC -> BLE RECEIVER -> GROUP PULSE GATEWAY -> REAL-TIME BACKEND -> COACH SCREEN
One receiver serves many sensors and pushes already-normalized events over WebSocket. Capacity comes from the class setting max_participants (default 15, expandable to 20 / 30 / 50) — the number 15 is never hard-coded.
⚠ PLACEHOLDER FOR FUTURE NATIVE BLE / HARDWARE INTEGRATION — CentralReceiverBLEService transport awaits real GROUP PULSE HUB firmware. Point it at the hub URL; nothing else changes.
Demo mode
DemoHeartRateSensorService simulates 15 sensors and 15 participants with gateway_type DEMO, a HIIT intensity curve, one scripted weak-signal sensor, one scripted disconnect + reconnect and occasional random dropouts. From the dashboard's point of view it is indistinguishable from real hardware — but the UI always labels it as simulated.
Sensor assignment
Each sensor stores sensor_id, device_name, manufacturer, gateway_id, participant_id, connection_status, last_seen and battery_level when available. A sensor cannot belong to two participants in the same class; a second claim fails with “Sensor already assigned to another participant.” Coaches assign manually, or participants claim their own sensor. Assignment is never inferred from signal proximity.
Connection quality & gateway heartbeat
fresh reading -> GOOD SIGNAL no reading > 5s -> WEAK SIGNAL / NO DATA no reading > 15s -> DISCONNECTED gateway silent > 12s -> GATEWAY OFFLINE (instructor notified)
All thresholds live in src/lib/pulse/config.ts.
Browser limitations
Web Bluetooth is not universal. It is unavailable on iOS Safari and Firefox, and requires a secure context plus a user gesture on Chromium browsers. GROUP PULSE detects API presence, secure context and radio availability, and offers “Use another device” or “Use demo mode” instead. A fake CONNECTED state for real hardware is never produced.
Future native BLE integration
⚠ PLACEHOLDER FOR FUTURE NATIVE BLE / HARDWARE INTEGRATION — A native wrapper (or the GROUP PULSE HUB API) can implement the same HeartRateSensorService interface and be registered in the factory. No dashboard, statistics or event changes required.
Privacy
Participant screens show only their own name, heart rate, zone and sensor status. Sensor IDs and assignment details are coach-only.