Andre Bandarra's Blog

Fitness with Web Bluetooth

PM 5 Monitor

With the launch of Chrome 56, web applications are now able to access Bluetooth Low Energy devices directly from the browser, without the need to install a plugin or a native application. This opens the opportunity to create types of web applications that were only available to native platforms.

For a great introduction on how to implement applications on the browser using Web Bluetooth, check François Beaufort's "Interact with Bluetooth Devices on the Web" article.

Building a Rowing Monitor

Many fitness tracking applications track exercises by connecting to Bluetooth enabled devices, such as Health Monitors and Treadmills. Due to the lack of Bluetooth connectivity on the browser, those applications are, in most cases, developed using native platforms. With Web Bluetooth now being available, it becomes possible to connect and track exercises in real-time, from the browser. So, I decided to give it a try and build such application for my rowing machine.

The machine in question is a Concept2 Model D, but the most important part is it's PM5 monitor. The monitor is BLE enabled, and, in fact, Concept2 offers native applications for both Android and iOS.A quick search on Google took me to the protocol used by the monitor.

The resulting application is hosted here, and the source code is publicly available on GitHub.

Connecting to the Monitor

The PM5 specification outlines 4 different services on the device: The Information, Discovery, Control and Rowing services. The Discovery Service is the one that announces the device, so we need to pass it as a filter for the connection.

const options = {
filters: [{services: ['ce060000-43e5-11e4-916c-0800200c9a66']}],
optionalServices: [
'ce060010-43e5-11e4-916c-0800200c9a66', // Information Service.
'ce060020-43e5-11e4-916c-0800200c9a66', // Control Service.
'ce060030-43e5-11e4-916c-0800200c9a66' // Rowing Service.

.then(device => {
// ...

The characteristics we want to use are accessed through the other services. In order to have access to those services later, we need to pass them as optionalServices when requesting the device.

Selecting a device

When the application requests the device, the browser will show a native interface, showing the devices that are compatible with the configuration requested. This dialog doubles both as a request for the application to access the bluetooth and a device selection screen!

Choosing a PM5 Monitor

For developers who have gone through the process of developing a Bluetooth application on native platforms, this is very welcome news: On most platforms, developers have to build an interface that deals with device scans and handles the user selecting the devices by themselves.

Accessing characteristics

There are 2 ways to access characteristics. It's possible to request them at any time for the application and, more interesting to our objectives, sign up for notifications for when a characteristic changes.

First, retrieve the Service this characteristic belongs to:

.then(server => {
server.getPrimaryService('ce060030-43e5-11e4-916c-0800200c9a66') // Rowing Service.
.then(service => {
// ...

Then, retrieve the characteristic itself. Call characteristic.startNotifications and then setup an eventListener to get updates on the characteristic.

service.getCharacteristic('ce060031-43e5-11e4-916c-0800200c9a66') // General Info.
.then(characteristic => characteristic.startNotifications())
.then(characteristic => {
characteristic.addEventListener('characteristicvaluechanged', e => {
// Parse characteristic value.

Some characteristics will have values that are strings. The serialNumber characteristic, from the Information Service is one example. Here's how a developer can parse a characteristic like this.

characteristic.addEventListener('characteristicvaluechanged', e => {
const decoder = new TextDecoder('utf-8');
const value = decoder.decode(;
// ...

On the Rowing Monitor, most characteristics are made of data. The specific format for that data is specified on the documentation, and it can can be accessed from the event object, in the form of a DataView. Here's how a developer can parse such data.

characteristic.addEventListener('characteristicvaluechanged', e => {
const dataView =;
const avgStrokeRate = dataView.getUint8(10);
const endingHeartRate = dataView.getUint8(11);
const averageHeartRat = dataView.getUint8(12);
// ...

Always test on multiple devices

The Bluetooth LE stack doesn't support parallel access to the API. On some operating systems, the bluetooth stack implements a queue and serializes the calls to the API. But on other operating systems, such as Android, the OS does not queue the calls and it's up to the application developer to manage the queue. For more context around this, check this Github issue, where it's under active discussion.

This leads to an interesting problem when developing using Web Bluetooth: An inadvertent developer may create an app and not care about serializing the calls to the API. The application would work flawlessly on a Mac OS, but fails on Android, with an error message that is not so obvious.

Developers can implement their own queue to serialize API calls, or carefully design their application so that parallel calls don't happen. It's also important to test the application on multiple Operating Systems to make sure apps behave properly.

Look mom, no internet!

An application without access to the internet can be a dull application. But the Rowing Monitor takes advantages of features such as ServiceWorkers and IndexedDB to create an application that fully works offline: Once the user visits the website for the first time, the service worker is installed and fully caches the application for offline usage. The Rowing Monitor's service worker is generated during build time, using the sw-precache library. Here's what the service worker generator task looks like in Gulp:

gulp.task('generate-service-worker', callback => {
const rootDir = 'dist';
swPrecache.write(path.join(rootDir, 'service-worker.js'), {
staticFileGlobs: [rootDir + '/**/*.{js,html,css,png,jpg,gif,svg,eot,ttf,woff}'],
stripPrefix: rootDir
}, callback);

The workouts are persisted using IndexedDB, so even if the user doesn't have connection the application is fully functional. The data is only persisted locally, but the application could be easily extended to offer the user the change to login and persist the workout data to a remote server, once it is online.

Almost there!

The Web Bluetooth API offers the functionality needed to connect and gather information from a Bluetooth enabled device, but there's still one thing missing to transform the Rowing Monitor into a fully functional fitness tracker.

When using the application to track an exercise, the screen will go to sleep after a few seconds and the bluetooth connection will be lost. This is due to the lack of a Wakelock API. Fortunately, such a Web API is already under discussion on W3C.

In fact, Chrome already implements an early version of the API, and it can be enabled by activating Chrome's experimental features. To activate it, go to chrome://flags/#enable-experimental-web-platform-features and click Enable.

// The PM5 API returns a Promise when the connection is established. The WakeLock
// is acquired once a connection is established.
.then(() => {
screen.keepAwake = true;

// The API also provides a disconnect event. The WakeLock is released once the
// application is disconnected from the rowing machine.
pm5.addEventListener('disconnect', () => {
screen.keepAwake = false;


The potential of integrating fitness equipment with Web Bluetooth is amazing. Imagine someone arriving to the gym and instead of having to download a full app to track the exercise, they can just receive the URL through a Physical Web Beacon, scan a NFC tag or a QR code, and the application is made available instantly — no download involved.

It also opens the possibility of deeper integrations. The Rowing Monitor application, for instance, could be evolved into an online racing application, a game or a VR experience that lets users row at different parts of the world.

Another application worth checking is, which lets a user sync a YouTube riding video with a smart trainer, using Web Bluetooth.

← Home