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.
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:
- Node.js to install the required dependencies
- A code editor for... writing code! Tip: Visual Studio Code supports the new Ionic VS Code Extension
- Android Studio to build the Android app
- XCode to build the iOS app (only available on macOS)
Create a new app¶
To create a new project, we simply use the Ionic CLI. For this, first install the CLI globally:
Then you can create a new project with the ionic start
command:
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:
Then add the Android platform using the following command:
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:
Then add the iOS platform using the following command:
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:
- 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 withrequestPermissions()
. - Add the
deviceScanned
listener: We need to add a listener for thedeviceScanned
event before we start scanning for devices. This listener will be called whenever a BLE device is found. - Start scanning for devices: To start scanning for devices, we call
startScan()
. As soon as a device is found, thedeviceScanned
event is emitted. - Stop scanning for devices: After we found the heart rate sensor, we stop scanning for devices. This is important to save battery life.
- Connect to the device: To connect to the heart rate sensor, we call
connect()
with the device ID. - 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()
. - Add the
characteristicChanged
listener: We need to add a listener for thecharacteristicChanged
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. - 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 is0000180D-0000-1000-8000-00805F9B34FB
, and the characteristic ID for the heart rate measurement is00002A37-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:
To run your app on iOS, use the following command:
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.