Quick peek into the Web Bluetooth API

Joel Oliveira
Joel Oliveira
Sep 24 2021
Posted in Engineering & Technology

Interacting with BT devices with Javascript

Quick peek into the Web Bluetooth API

Connecting with heart rate monitors, weight scales, step counters or basically any device BLE device in a secure and privacy-preserving manner is made possible via Bluetooth. Until very recently, the ability to interact with nearby Bluetooth devices was only possible using a native mobile or desktop application.

The Web Bluetooth API is here to bring this functionality to web browsers. Even though the Web Bluetooth API specification is not finalized yet, a subset of the Web Bluetooth API is currently available in Chrome OS, Chrome for Android 6.0 and up, Mac (Chrome 56 and up) and Windows 10 (Chrome 70 and up). This functionality might also be able in other Chromium based browsers, like Brave, Vivaldi and Edge, although we did not try them all. For Linux and earlier versions of Windows, enable the #experimental-web-platform-features flag in about://flags.

In this post, our goal is to demonstrate how you can request and connect to nearby devices and read and write Bluetooth characteristics, on a very basic level. For a more in-depth and hands-on approach to other scenarios, we strongly suggest you take a look at Google's Chrome Web Bluetooth Samples.

Before we start

There are couple of things to take into consideration before we dive into code. It's also important that you have some knowledge of how Bluetooth Low Energy and Generic Attribute Profiles (GATT) work.

HTTPS Only

This experimental API is only available in secure contexts. Therefore you'll need to build with TLS in mind.

User Gesture Required

Any attempt to use this API will only be possible after it is triggered by a user gesture. This means that you will need to listen to pointerup, click or touchend events before you can use this API:

document.getElementById('myButton').addEventListener('click', e => {
  // Use navigator.bluetooth.requestDevice
});

Implementation Status

Because this API is not widely available, it's important you understand that not all features are available. Please check MDN's Browser compatibility table for more information.

Finally, it's also important to understand the security tradeoffs, we strongly recommend you to read this great post from Jeffrey Yasskin, a software engineer on the Chrome team.

Request Bluetooth Devices

The Web Bluetooth API allows websites to connect to remote GATT Servers over a BLE connection in devices that implement Bluetooth 4.0 or later. When a website requests access using navigator.bluetooth.requestDevice, the browser prompts users with the following device chooser, where they can pick one device or simply cancel the request:

This function requires a mandatory object that defines filters. These filters are used to return only devices that match GATT services, a manufacturer or a device name. For example, if you would like to connect to a standard Bluetooth GATT Heart Rate service, you would do the following:

navigator.bluetooth.requestDevice({
 filters: [{ services: ['heart_rate'] }]
})
.then(device => {
//Paired
})
.catch(error => {
//Handle Errors
});

You can also request Bluetooth devices based on their name:

navigator.bluetooth.requestDevice({
  filters: [{
    name: 'My Robot'
  }],
  optionalServices: ['battery_service']
})
.then(device => {
//Paired
})
.catch(error => {
//Handle Errors
});

It is also possible to request Bluetooth devices based on a manufacturer's specific data:

navigator.bluetooth.requestDevice({
  filters: [{
    manufacturerData: [{
      companyIdentifier: 76 // Apple
    }]
  }],
  optionalServices: ['battery_service']
})
.then(device => {
//Paired
})
.catch(error => {
//Handle Errors
});

Ultimately, you can also connect to any available Bluetooth device by providing the following:

navigator.bluetooth.requestDevice({
  acceptAllDevices: true,
  optionalServices: ['battery_service']
})
.then(device => {
//Paired
})
.catch(error => {
//Handle Errors
});

Connect to a Bluetooth device

Now that you've paired with a Bluetooth device, you are now ready to connect with its remote GATT server. This is where you will find information about the service and its characteristics:

navigator.bluetooth.requestDevice({
 filters: [{ services: ['heart_rate'] }]
})
.then(device => {
  return device.gatt.connect();
})
.then(server => {
//Connected
})
.catch(error => {
//Handle Errors
});

Read a Bluetooth Characteristic

Now that we are connected, we will want to get a Primary GATT Service and read a characteristic that belongs to this service:

navigator.bluetooth.requestDevice({
 filters: [{ services: ['heart_rate'] }]
})
.then(device => device.gatt.connect())
.then(server => {
  return server.getPrimaryService('heart_rate');
})
.then(service => {
  return service.getCharacteristic('heart_rate_measurement');
})
.then(characteristic => {
  return characteristic.readValue();
})
.then(value => {
  // Parse value:
  // See https://github.com/WebBluetoothCG/demos/blob/gh-pages/heart-rate-sensor/heartRateSensor.js#L33
})
.catch(error => {
//Handle Errors
});

Write to a Bluetooth Characteristic

Pretty much the same way, you can write to a remove server's GATT characteristic. In the example below, we will reset the value of the Energy Expended field to 0 in the Heart Rate Control Point service:

navigator.bluetooth.requestDevice({
 filters: [{ services: ['heart_rate'] }]
})
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('heart_rate'))
.then(service => service.getCharacteristic('heart_rate_control_point'))
.then(characteristic => {
  const resetEnergyExpended = Uint8Array.of(1);
  return characteristic.writeValue(resetEnergyExpended);
})
.then(_ => {
  // Mission accomplished
})
.catch(error => {
//Handle Errors
});

As you can see, using Javascript to interact with Bluetooth is pretty straightforward, thanks to the Web Bluetooth API. There are many other examples available here and we strongly recommend you to install this app which simulates BLE peripherals and should allow you to test this API without a real BLE device.

You can also checkout our own demo page, created specially for this post.

What the future holds?

Because this API is under development and not fully complete, there are some more features coming out soon. The most relevant one being the ability to scan for nearby Bluetooth devices. This is perfect for things like detecting BLE beacons. To access this new functionality, it's imperative that you enable the following flag in chrome://flags/:

You can then use navigator.bluetooth.requestLEScan as follows:

navigator.bluetooth.requestLEScan({
  filters: [{ manufacturerData: [{companyIdentifier: 76}] }],
  options: {
   keepRepeatedDevices: true,
  }
}).then(_ => {
  navigator.bluetooth.addEventListener('advertisementreceived', event => {

      let serviceData = event.manufacturerData.get(0x004C);

      if (!serviceData || (serviceData && serviceData.byteLength < 23)) {
        // This is not a beacon
        return;
      }

      let uuidArray = new Uint8Array(serviceData.buffer, 2, 16);
      let major = serviceData.getUint16(18, false);
      let minor = serviceData.getUint16(20, false);
      let rssi = event.rssi;
      let txPower = serviceData.getInt8(22);

      console.log(uuidArray, major, minor, rssi, txPower);

  });
})
.catch(error => {
//Handle Errors
});

The code above will allow you to discover beacons nearby and print their basic information. For a full demo of what this functionality is capable of, you can check this page, where we demonstrate how you discover and display information about nearby beacons. If you do not have any physical BLE beacons, you can emulate them using our own mobile apps (requires an account and previously created data). We have an iOS, Android and Huawei app for you to choose from.

Awesome, right?

We think this API has the potential to revolutionize how companies will create the BLE devices of the future, allowing users to manage and configure these devices by simply using a browser. We also hope this becomes officially supported by more browsers which will eventually enable out team to bring this functionality to our own Web SDK.

As always, we hope you liked this article and if you have anything to add, questions or suggestions to this post, we are available via our Support Channel.

Keep up-to-date with the latest news