Web Bluetooth Experiments
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:
- Name
- Name prefix
- Service(s) required
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();