Skip to content

How to Build a Heart Rate Monitor with Capacitor

Capacitor makes building a cross-platform app with one codebase easier than ever before. Today we will use the Ionic Framework and Capacitor to create a simple heart rate monitor app for Android and iOS in just 15 minutes. For this, we will use the Capacitor Bluetooth Low Energy plugin to connect to a heart rate sensor.

The heart rate sensor we use in this tutorial is the Polar H9 Heart Rate Sensor, but any other Bluetooth heart rate sensor should work as well. You can find the complete app code referenced in this guide on GitHub.

Capacitor Heart Rate Monitor App

Capacitor Heart Rate Monitor App

Basics

Bluetooth Low Energy

First, let's talk about Bluetooth Low Energy (BLE) and how it works. BLE is a wireless communication technology that is designed to reduce power consumption while maintaining a similar communication range to classic Bluetooth. BLE is used in many devices, such as heart rate sensors, fitness trackers, smartwatches, and more.

Centrals and Peripherals

BLE devices are divided into two categories: peripherals and centrals. Peripherals are devices that provide data, such as heart rate sensors, while centrals are devices that consume data, such as smartphones. In our case, the heart rate sensor is the peripheral, and the smartphone is the central.

Services and Characteristics

BLE communication is based on the concept of services and characteristics. A service is a collection of characteristics, and a characteristic is a piece of data that can be read, written, or notified. For example, a heart rate sensor has a service that contains a characteristic for the heart rate measurement. Each service and characteristic is represented by a unique UUID (Universally Unique Identifier). The UUIDs are standardized and defined by the Bluetooth SIG (Special Interest Group).

Prerequisites

Before we start, download and install the following tools to ensure an optimal developer experience:

Create a new app

To create a new project, we simply use the Ionic CLI. For this, first install the CLI globally:

npm install -g @ionic/cli

Then you can create a new project with the ionic start command:

npx ionic start heart-rate-monitor-app blank --type=angular --capacitor

In this case, the app is called heart-rate-monitor-app, the starter template is blank, and the project type for the purposes of this guide is Angular. You can also choose Vue or React, for example. Additionally, we enable the Capacitor integration with --capacitor.

Once everything is ready, you should see this output:

Your Ionic app is ready! Follow these next steps:

- Go to your new project: cd .\heart-rate-monitor-app
- Run ionic serve within the app directory to see your app in the browser
- Run ionic capacitor add to add a native iOS or Android project using Capacitor
- Generate your app icon and splash screens using cordova-res --skip-config --copy
- Explore the Ionic docs for components, tutorials, and more: https://ion.link/docs
- Building an enterprise app? Ionic has Enterprise Support and Features: https://ion.link/enterprise-edition

Add the Android platform

If you want to build the app for Android, you need to add the Android platform. For this, first install the @capacitor/android package:

npm install @capacitor/android

Then add the Android platform using the following command:

npx cap add android

Add the iOS platform

If you want to build the app for iOS, you need to add the iOS platform. For this, first install the @capacitor/ios package:

npm install @capacitor/ios

Then add the iOS platform using the following command:

npx cap add ios

Implement the Heart Rate Monitor

Install the Bluetooth Low Energy plugin

In order to connect to the heart rate sensor, we need to install the Capacitor Bluetooth Low Energy plugin. Please note that the plugin is currently only available to Insiders. See Getting started with Insiders and follow the instructions to install the plugin.

On Android, this plugin requires the following permissions be added to your AndroidManifest.xml before or after the application tag:

<!-- Needed only if your app looks for Bluetooth devices.  -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<!-- Needed only if your app communicates with already-paired Bluetooth devices. -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Needed only if your app uses Bluetooth scan results to derive physical location. -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Needed only if your app uses the foreground service. -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

You can read more about Bluetooth permissions in the Android documentation.

You also need to add the following service inside the application tag in your AndroidManifest.xml (usually android/app/src/main/AndroidManifest.xml):

<service android:name="io.capawesome.capacitorjs.plugins.bluetoothle.BluetoothLowEnergyService" android:foregroundServiceType="connectedDevice" />

On iOS, add the NSBluetoothPeripheralUsageDescription and NSBluetoothAlwaysUsageDescription keys to the Info.plist file (usually ios/App/App/Info.plist), which tells the user why the app needs access to Bluetooth peripherals:

<key>NSBluetoothAlwaysUsageDescription</key>
<string>The app needs access to Bluetooth peripherals to communicate with Bluetooth devices.</string>

The plugin is now ready to use.

Build the page

Let's start with the actual implementation. Since the app already has a blank page called home, we can use this page to display the heart rate. The page consists of a title and the current heart rate. For reason of simplicity, there will be no interactive elements. The app will try to establish a connection to the heart rate sensor via BLE directly after the page is loaded and display the current heart rate.

The following code goes to your src/app/home/home.page.ts file:

import { Component, OnInit, signal } from '@angular/core';
import { Capacitor } from '@capacitor/core';
import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy';
import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton } from '@ionic/angular/standalone';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
  standalone: true,
  imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonButton],
})
export class HomePage implements OnInit {
  public heartRate = signal<undefined | number>(undefined);

  constructor() {}

  public async ngOnInit() {
    // 1. Initialize the Bluetooth Low Energy plugin
    if (Capacitor.getPlatform() === 'ios') {
      await BluetoothLowEnergy.initialize();
    } else {
      await BluetoothLowEnergy.requestPermissions();
    }
    // 2. Add a listener for the `deviceScanned` event
    await BluetoothLowEnergy.addListener('deviceScanned', async (event) => {
      if (event.name?.startsWith('Polar H9')) {
        // 4. Stop scanning for devices
        void BluetoothLowEnergy.stopScan();
        // 5. Connect to the device
        await BluetoothLowEnergy.connect({
          deviceId: event.id,
        });
        // 6. Discover services
        await BluetoothLowEnergy.discoverServices({
          deviceId: event.id,
        });
        // 7. Add a listener for the `characteristicChanged` event
        await BluetoothLowEnergy.addListener('characteristicChanged', (event) => {
          let byteArray = new Uint8Array(event.value);
          let firstBitValue = byteArray[0] & 0x01;
          if (firstBitValue === 0) {
            this.heartRate.set(byteArray[1]);
          } else {
            this.heartRate.set((byteArray[1] << 8) | byteArray[2]);
          }
        });
        // 8. Start notifications for the Heart Rate Measurement characteristic
        await BluetoothLowEnergy.startCharacteristicNotifications({
          deviceId: event.id,
          serviceId: '0000180D-0000-1000-8000-00805F9B34FB',
          characteristicId: '00002A37-0000-1000-8000-00805F9B34FB',
        });
      }
    });
    // 3. Start scanning for devices
    await BluetoothLowEnergy.startScan();
  }
}

Let's break down the code:

  1. Initialize the Bluetooth Low Energy plugin: First, we need to initialize the plugin. For iOS, we need to call initialize(), and for Android, we need to request the necessary permissions with requestPermissions().
  2. Add the deviceScanned listener: We need to add a listener for the deviceScanned event before we start scanning for devices. This listener will be called whenever a BLE device is found.
  3. Start scanning for devices: To start scanning for devices, we call startScan(). As soon as a device is found, the deviceScanned event is emitted.
  4. Stop scanning for devices: After we found the heart rate sensor, we stop scanning for devices. This is important to save battery life.
  5. Connect to the device: To connect to the heart rate sensor, we call connect() with the device ID.
  6. Discover services: After connecting to the device, we need to discover the services. This is necessary to find the service that contains the heart rate measurement characteristic. For this, we call discoverServices().
  7. Add the characteristicChanged listener: We need to add a listener for the characteristicChanged event to receive the heart rate measurements. This event is emitted whenever the heart rate measurement characteristic changes. The value of the characteristic is a byte array that contains the heart rate measurement. Depending on the first bit of the first byte, the heart rate is either a single byte or two bytes. If the heart rate is a single byte, the heart rate is the second byte. If the heart rate is two bytes, the heart rate is the second byte shifted left by 8 bits and combined with the third byte.
  8. Start notifications for the Heart Rate Measurement characteristic: To receive the heart rate measurements, we need to start notifications for the heart rate measurement characteristic. For this, we call startCharacteristicNotifications(). The service ID and characteristic ID are standardized and defined by the Bluetooth SIG. The service ID for the heart rate service is 0000180D-0000-1000-8000-00805F9B34FB, and the characteristic ID for the heart rate measurement is 00002A37-0000-1000-8000-00805F9B34FB.

Next, we need to create the HTML and CSS for the page. As mentioned earlier, we will keep it simple and only display a title and the current heart rate. The following code goes to your src/app/home/home.page.html file:

<ion-header>
  <ion-toolbar>
    <ion-title>
      Heart Rate Monitor
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <div class="title">Current heart rate</div>
  <div class="body">{{ heartRate() ?? '--' }}</div>
</ion-content>

The following code goes to your src/app/home/home.page.scss file:

ion-content {
  text-align: center;
}

.title {
  font-size: 32px;
  margin-top: 36px;
}

.body {
  font-size: 64px;
  font-weight: bold;
  margin-top: 48px;
}

That's it! Now everything is set up to build and run the app. 🎉

Run the app

To run your app on Android, use the following command:

npx ionic cap run android

To run your app on iOS, use the following command:

npx ionic cap run ios

Conclusion

In this guide, we learned how to build a simple heart rate monitor app with Capacitor. We used the Capacitor Bluetooth Low Energy plugin to connect to a heart rate sensor and receive the heart rate measurements. If you have any questions, just create a discussion in the GitHub repository. Make sure you follow Capawesome on X so you don't miss any future updates.