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  -> CentralReceiverBLEService

Participant 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.