> ## Documentation Index
> Fetch the complete documentation index at: https://docs.synheart.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# BLE Heart Rate Monitor

> Direct Bluetooth LE heart rate monitor support for real-time HR streaming

## Overview

Synheart Wear supports direct Bluetooth LE connections to any standard heart rate monitor. This enables real-time HR streaming without requiring a cloud API or vendor-specific SDK.

**Supported devices include:**

* **WHOOP** (Broadcast Heart Rate mode)
* **Polar** sensors (H10 chest strap, OH1 / Verity Sense optical arm bands)
* **Wahoo** TICKR, TICKR X
* **Garmin** HRM-Pro, HRM-Dual
* **Gym equipment** with BLE HR broadcast
* Any device implementing the standard **BLE Heart Rate Profile (0x180D)**

## How It Works

The BLE HRM provider connects to devices advertising the standard Bluetooth Heart Rate Service (`0x180D`), subscribes to the Heart Rate Measurement characteristic (`0x2A37`), and parses incoming data per the Bluetooth SIG specification.

```text theme={null}
Phone (iOS/Android)
    │
    │── BLE Scan (filter: 0x180D service)
    │
    ▼
BLE Heart Rate Monitor
    │
    │── Subscribe to HR Measurement (0x2A37)
    │
    ▼
Real-time HR samples → HeartRateSample stream
    │
    │── bpm, rr_intervals_ms, device_id, timestamp
    │
    ▼
Your App
```

## Platform Setup

### iOS

Add to `Info.plist`:

```xml theme={null}
<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to connect to heart rate monitors.</string>
```

### Android

Add to `AndroidManifest.xml`:

```xml theme={null}
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
    android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
```

**Note:** Android 12+ requires runtime permission requests for `BLUETOOTH_SCAN` and `BLUETOOTH_CONNECT`.

## Usage

<Tabs>
  <Tab title="Flutter/Dart">
    ```dart theme={null}
    import 'package:synheart_wear/synheart_wear.dart';

    final bleHrm = BleHrmProvider();

    // Scan for nearby HR monitors
    final devices = await bleHrm.scan(
      timeoutMs: 10000,
      namePrefix: 'WHOOP',  // optional filter
    );

    // Connect to a device
    await bleHrm.connect(
      deviceId: devices.first.deviceId,
      sessionId: 'my-session',  // optional
    );

    // Listen to heart rate samples
    bleHrm.onHeartRate.listen((sample) {
      print('BPM: ${sample.bpm}');
      if (sample.rrIntervalsMs != null) {
        print('RR: ${sample.rrIntervalsMs}');
      }
    });

    // Disconnect when done
    await bleHrm.disconnect();
    ```
  </Tab>

  <Tab title="Swift">
    ```swift theme={null}
    import SynheartWear

    let config = SynheartWearConfig(enabledAdapters: [.bleHrm])
    let synheartWear = SynheartWear(config: config)

    guard let bleHrm = synheartWear.bleHrm else { return }

    // Scan
    let devices = try await bleHrm.scan(timeoutMs: 10000, namePrefix: "WHOOP")

    // Connect
    try await bleHrm.connect(deviceId: devices.first.deviceId)

    // Stream
    for await sample in bleHrm.onHeartRate {
        print("BPM: \(sample.bpm)")
    }

    // Disconnect
    try await bleHrm.disconnect()
    ```
  </Tab>

  <Tab title="Kotlin">
    ```kotlin theme={null}
    import ai.synheart.wear.SynheartWear
    import ai.synheart.wear.models.DeviceAdapter

    val synheartWear = SynheartWear(
        context = this,
        config = SynheartWearConfig(
            enabledAdapters = setOf(DeviceAdapter.BLE_HRM)
        )
    )

    val bleHrm = synheartWear.bleHrm ?: return

    // Scan
    val devices = bleHrm.scan(timeoutMs = 10000, namePrefix = "WHOOP")

    // Connect
    bleHrm.connect(deviceId = devices.first().deviceId)

    // Collect
    bleHrm.heartRateFlow.collect { sample ->
        Log.d(TAG, "BPM: ${sample.bpm}")
    }

    // Disconnect
    bleHrm.disconnect()
    ```
  </Tab>
</Tabs>

## API Reference

### Methods

| Method                                          | Description                                | Returns              |
| ----------------------------------------------- | ------------------------------------------ | -------------------- |
| `scan(timeoutMs, namePrefix?)`                  | Scan for nearby BLE HR monitors            | `List<BleHrmDevice>` |
| `connect(deviceId, sessionId?, enableBattery?)` | Connect to a device and start HR streaming | `void`               |
| `disconnect()`                                  | Disconnect from the current device         | `void`               |
| `isConnected()`                                 | Check if a device is currently connected   | `bool`               |

### BleHrmDevice

Returned from `scan()`:

| Field      | Type      | Description            |
| ---------- | --------- | ---------------------- |
| `deviceId` | `String`  | BLE device UUID        |
| `name`     | `String?` | Device advertised name |
| `rssi`     | `int`     | Signal strength (dBm)  |

### HeartRateSample

Emitted on the heart rate stream:

| Field           | Type            | Description                              |
| --------------- | --------------- | ---------------------------------------- |
| `tsMs`          | `int`           | Phone receipt timestamp (ms since epoch) |
| `bpm`           | `int`           | Heart rate in beats per minute           |
| `source`        | `String`        | Always `"ble_hrm"`                       |
| `deviceId`      | `String`        | BLE device UUID                          |
| `deviceName`    | `String?`       | Device advertised name                   |
| `sessionId`     | `String?`       | Session tag (passed during connect)      |
| `rrIntervalsMs` | `List<double>?` | RR intervals in milliseconds             |

**Note:** `rrIntervalsMs` availability depends on the device. Polar chest straps typically include RR intervals; WHOOP Broadcast typically does not.

### Error Codes

| Code                | Meaning                                  |
| ------------------- | ---------------------------------------- |
| `PERMISSION_DENIED` | Bluetooth permission not granted         |
| `BLUETOOTH_OFF`     | Bluetooth adapter is disabled            |
| `DEVICE_NOT_FOUND`  | Device not found or connection timed out |
| `SUBSCRIBE_FAILED`  | Failed to subscribe to HR characteristic |
| `DISCONNECTED`      | Device disconnected unexpectedly         |

## WHOOP Broadcast Setup

To use WHOOP as a BLE heart rate monitor:

1. Open the **WHOOP app** on your phone
2. Go to **Device Settings** (tap your WHOOP device)
3. Enable **Broadcast Heart Rate**
4. Your WHOOP will now appear in BLE scans as a standard HR monitor

**Note:** WHOOP Broadcast HR provides BPM only (no RR intervals). For full WHOOP data (recovery, sleep, strain), use the WHOOP cloud provider instead.

## Reconnection

The BLE HRM provider automatically handles disconnections:

* **3 retry attempts** with exponential backoff (1s, 2s, 4s)
* After retries are exhausted, a `DISCONNECTED` error is emitted on the stream
* Your app can then prompt the user to reconnect

## Limitations

* **Single device**: Only one BLE HRM device can be connected at a time (v1)
* **Phone-side only**: Requires the phone to be in BLE range of the HR monitor
* **No HRV calculation**: Raw RR intervals are provided when available, but HRV calculation is not performed by the provider
* **Battery dependent**: Battery level monitoring is optional (`enableBattery: true` during connect)

## Architecture

The BLE HRM provider is implemented natively on each platform:

| Platform    | Implementation                                      | Streaming Pattern              |
| ----------- | --------------------------------------------------- | ------------------------------ |
| **iOS**     | CoreBluetooth (`CBCentralManager`)                  | `AsyncStream<HeartRateSample>` |
| **Android** | Android BLE (`BluetoothLeScanner`, `BluetoothGatt`) | `SharedFlow<HeartRateSample>`  |
| **Flutter** | Platform Channels (MethodChannel + EventChannel)    | `Stream<HeartRateSample>`      |

All platforms parse HR data identically per the Bluetooth SIG Heart Rate Profile specification (flags byte, uint8/uint16 BPM, optional RR intervals at 1/1024s resolution).
