Web Bluetooth API - Utilizing Bluetooth capabilities with a website

Tablet with keyboard on a light background beside smart home devices on a dark background. Includes smart bulbs and a Bluetooth logo. Modern and tech-focused.
Himanshu
Android Engineer
|
September 7, 2023
BLE
API

Introduction

The Web Bluetooth API is a JavaScript API that allows browsers to interact with Bluetooth Low-Energy (BLE) devices. This API lets web applications discover and communicate with devices over the Bluetooth radio interface. The Web Bluetooth API is designed to be used with existing web APIs and is built on top of the Generic Attribute Profile (GATT) protocol.

The API enables web applications to discover nearby Bluetooth devices, query for services, and interact with the characteristics of those services. The Web Bluetooth API is designed to be easy to use and is accessible to developers with little or no prior knowledge of Bluetooth.

The Web Bluetooth API is supported by Chrome, Opera, and Android Webview. On Chrome, the API is available for Android, Chrome OS, Linux, macOS, and Windows. It is important to note that the Web Bluetooth API only works on secure contexts, such as HTTPS and localhost.

With the Web Bluetooth API, web developers can create web applications that interact with many Bluetooth-enabled devices, including smartwatches, fitness trackers, and home automation devices. This API opens up new possibilities for web-based IoT applications and enables web developers to create new applications that were not previously possible.

Note: Many browser supports the Web Bluetooth API, but not all, because of security reason or infeasibility. Visit here to check the current status of browser compatibility.

Getting Started

A website needs to request access to nearby devices using navigator.bluetooth.requestDevice. Then the browser will prompt the user with a device chooser where they can pick one device or simply cancel the request.

The navigator.bluetooth.requestDevice() function requires a mandatory object that defines filters.

Filters

Filters are used to return only devices that match some advertised Bluetooth GATT services and/or the device name.

The devices can be filtered by many aspects

  1. Services
  2. Name
  3. Manufacturer Data

Devices can also be excluded/ignored or all available devices can be received by applying the required filters.

Services Filter

For example, to request Bluetooth devices advertising the battery service. The following filter can be applied:

navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
    .then(device => { /* … */ })
    .catch(error => { console.error(error); });
    [add copy]


The parameter filters receives an object of BluetoothLEScanFilter, with the parameter services containing BluetoothServiceUUID.

In our case, BluetoothServiceUUID is battery_service, which is one of the standardized Bluetooth GATT services.

You may provide either the full Bluetooth UUID or a short 16- or 32-bit form.

navigator.bluetooth
		.requestDevice({ filters: [{ services: ['0783b03e-8535-b5a0-7140-a304d2495cb7'] }] })
		.then(device => { /* … */ })
		.catch(error => { console.error(error); });
        [add copy]


You can add multiple services at one time, like:

navigator.bluetooth.requestDevice({
		filters: [{
				services: ['0783b03e-8535-b5a0-7140-a304d2495cb7', 0x1234, 'heart_rate']
		}]
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
[add copy]


Name Filter

Bluetooth devices can also be requested on the basis of the device name being advertised with the name filters key, or even a prefix of this name with the namePrefix filters key.

Important: Whenever you add other filter as primary filter, do not forget to add the services you might be interested in after connecting to the device, to communicate or read data.It is mandatory to tell system in advance about the scope of access required.
navigator.bluetooth.requestDevice({
  filters: [{ name: 'RORO BLE Device' }],
  optionalServices: ['battery_service'] // Required to access service later.
})
[add copy]


Manufacturer data filter

Request Bluetooth devices based on the manufacturer data.

The manufacturer-specific data is being advertised with the manufacturerData filters key. This key is an array of objects with a mandatory key named companyIdentifier. A data prefix can also be provided that filters manufacturer data from Bluetooth devices that start with it.

navigator.bluetooth.requestDevice({
  filters: [{
    manufacturerData: [{ companyIdentifier: 0x00a0, dataPrefix: new Uint8Array([0x01]) }],
    optionalServices: [ 0x1234 ] // Required to access service later.
  }]
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
[add copy]


Exclusion filter

There are some cases where you want to exclude BLE devices with certain properties, like name or services. The exclusionFilters option in navigator.bluetooth.requestDevice() allows you to exclude devices from the browser picker.

navigator.bluetooth.requestDevice({
  filters: [{ namePrefix: "RORO BLE" }],
  exclusionFilters: [{ name: "RORO BLE Tracker" }],
  optionalServices: [ 0x1234 ] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
[add copy]


No Filters

Let's say, you want to query all the available devices, in such case filters can be replaced with acceptAllDevices.

navigator.bluetooth.requestDevice({
  acceptAllDevices: true,
  optionalServices: ['battery_service'] // Required to access service later.
})
.then(device => { /* … */ })
.catch(error => { console.error(error); });
[add copy]


Note: The browser might need permission to access the Bluetooth services of the device if the OS of that device has such restrictions. However, the flow to ask permission from the user is handled by the browser itself, therefore is no need to handle or check any such thing here.

The browser will present a pop-up to the user, where devices on the basis of the filters applied, will appear, Your website/code will not have access to any BLE device information unless the user selects and pairs the device, the user can also simply cancel the pop-up.

Now we know that how can we query the BLE devices of our interest, lets start interacting with the BLE device. First thing first, connect to the device.

Connect to a Bluetooth device

After we request a device with specified filters, it will return a promise, with the reference to the Bluetooth device, if the user selects the device from the picker.

Then call .gatt.connect() on the device reference.

navigator.bluetooth.requestDevice({ filters: [{ services: ['battery_service'] }] })
		.then(device => {
		  // Human-readable name of the device.
		  console.log(device.name);
		  // Attempts to connect to remote GATT Server.
		  return device.gatt.connect();
		})
		.then(server => { /* … */ })
.
.
.
[add copy]


Here it further returns the GATT Server, from which we can get the services we added to the filter. Now we want to get a Primary GATT Service and read a characteristic that belongs to this service. Try the following method to read the current charge level of the device's battery.

.
.
.
.then(service => {
  // Getting Battery Level Characteristic…
  return service.getCharacteristic('battery_level');
})
.then(characteristic => {
  // Reading Battery Level…
  return characteristic.readValue();
})
.then(value => {
  console.log(`Battery level - ${value.getUint8(0)} %`);
})
.catch(error => { console.error(error); });
[add cop]


A service has many characteristics, and those can be different types: read, write, read/write.

For example, battery_service is the service where we can find the characteristics related to the battery, and battery_level is a characteristic where we can read the value.

Write to Bluetooth characteristics

We know how to read the value that is being advertised at a service. But communication is two-way, so to send the data to the BLE device, we need to write the value to the characteristics of the service.

Let’s say the display is a service in a BLE device, which has display_message characteristics to which we can send data and update the display message or perform certain actions.

.
.
.
.then(server => server.getPrimaryService('display'))
.then(service => service.getCharacteristic('display_message'))
.then(characteristic => {
	let textEncoder = new TextEncoder();
  let value = textEncoder.encode("Hello World!");
  return characteristic.writeValueWithoutResponse(value);
})
.then(_ => {
  console.log('Message has been sent.');
})
.catch(error => { console.error(error); });
[add copy]


We can also write command/characters to it, and the BLE device will perform certain actions if it is programmed for it.

Here our device is programmed to clear the display of Bluetooth device, when clr is sent to the characteristics. Replacing “Hello World!” with “clr” will do the same.

Receive GATT notifications

The device in question displays the heart rate of the user, so we want to get notified whenever the heart rate changes. So, let's see how to be notified when the Heart Rate Measurement characteristic changes on the device:

.
.
.
.then(server => server.getPrimaryService('heart_rate'))
.then(service => service.getCharacteristic('heart_rate_measurement'))
.then(characteristic => characteristic.startNotifications())
.then(characteristic => {
  characteristic.addEventListener('characteristicvaluechanged',
                                  handleCharacteristicValueChanged);
  console.log('Notifications have been started.');
})
.catch(error => { console.error(error); });

function handleCharacteristicValueChanged(event) {
  const value = event.target.value;
  console.log('Received ' + value);
}
[add copy]


What else? Disconnect

It is best practice to free the resources, you no longer need. It will also save energy.

Simply call device.gatt.disconnect() to disconnect the device. Here device is the reference of a Bluetooth device.

Bonus

There is an inbuilt tool in chrome(chrome://bluetooth-internals/#devices), where you can get full details of the device.

You can expand and navigate to further characteristics, and other details of the selected device.

This is it?

Of course not, this was just an introduction, and hands-on implementation which will be useful in many cases. but there is more to it how to read descriptors, and write descriptors.

Here are some important links

Reference - https://developer.chrome.com/articles/bluetooth/

Web Bluetooth Samples - https://googlechrome.github.io/samples/web-bluetooth/index.html

Recommended Posts

A person holds a smartphone showing a facial recognition interface with digital patterns overlaying their face, conveying a sense of technology and security.
IoT
Beauty Tech
Glow with the flow: How IoT is shaping the Beauty Tech industry
Jul 20, 2023
|
Sohini
Stylized grid background with a floating gray browser window showing "I/O" in bold silver. Futuristic and tech-inspired theme.
AI
Google
Google I/O 2024: The Future of AI is Private (and Local)
May 22, 2024
|
Sanskar

Ready to get started?

Schedule a discovery call and see how we’ve helped hundreds of SaaS companies grow!