Introduction

At a recent hackathon I got the chance to experiment with Web Bluetooth. The specification abstract states Web Bluetooth is an:

“…API to discover and communicate with devices over the Bluetooth 4 wireless standard using the Generic Attribute Profile (GATT).”

Being even less familiar with Bluetooth than most technologies, this ultimately ended up meaning: “will work with some (newer) Bluetooth peripherals, but likely not with many older ones”.

As part of my initial experimentation at the hackathon, I had tried connecting to my Polar M400 watch - some limited success. However, it was my hunch that interacting fullly with the watch was not possible using GATT, and that it likely relied on an older standard, incompatible with Web Bluetooth. (to be explored…)

Polar H7

On returning home I reasoned that my Polar H7 sensor was likely to be a much better candidate for meaningful use with Web Bluetooth:

  • It is a Bluetooth LE (BLE) device.
  • BLE provides most of its functionality through key/value pairs provided by GATT.
  • GATT already defines a heart_rate service, so for maximum compatibility this would seem the logical choice.

Setup

At present, support for the API is not widespread and not generally complete. If the API is available within your browser, then the navigator.bluetooth object will exist. When using the API in application, it would be worth testing whether this exists.

On both Chrome OS and Android 6 it was necessary to enable Web Bluetooth from the chrome://flags page. I was not able to get Ubuntu 16.04 with Chrome 50 Beta working at the time.

Initial Connection

To connect to a device, the navigator.bluetooth.requestDevice(options) function is used. Through this, filters can be set which determine which devices are returned. These are presented to the user via the user-interface, and user interaction and selection of the device to connect to is a fundamental part of the security of the specification.

The options passed to requestDevice allow devices to be filtered by:

So, it would be possible to specify that I am interested in any devices offering the heart_rate service.

However, as I am only interested in my Polar device, I chose using a name prefix, and added the heart_rate service also:

let options = {
  filters: [{
    services: [0x180D], // heart_rate service
    namePrefix: "Polar H7"
  }]
};
navigator.bluetooth.requestDevice(options).then(/* ... */)

When successful, the returned Promise resolves to a BluetoothDevice.

BluetoothDevice has a single method connectGATT to facilitate connecting to the selected device.

Services and Characteristics

A successful call of connectGATT returns a Promise that resolves to a BluetoothRemoteGATTServer. This represents the GATT server on the Polar H7.

The server provides access to services and characteristics: Services group logical collections of capabilities known as characteristics.

For example, the heart_rate service has several characteristics, such as heart_rate_measurement and body_sensor_location.

Once connected, the fun begins, as the services and characteristics of the device can be used to access individual values. The following is an example of obtaining a specific characteristic, once successfully connected:

/* from connectGATT() ... */
.then(server => {
  return server.getPrimaryService(0x180D); // heart_rate
})
.then(service => {
  return service.getCharacteristic(0x2A37); // heart_rate_measurement
})
.then(characteristic => /* ... handle characteristic ... */)

Monitoring Changes to Characteristics

Having obtained a BluetoothRemoteGATTCharacteristic representing the characteristic, readValue can be used to obtain a Promise that will ultimately resolve to a value.

However, in the case of the heart rate monitor, I want to keep monitoring for changes to the value. For this, the startNotificatons method is available. A function can be passed to this method, which will be called with the new value whenever a change occurs.

For example:

characteristic.addEventListener('characteristicvaluechanged',
    event => this.onHeartRateChanged_(event));
return characteristic.startNotifications();

Putting it together

Screenshot

Example application - source