Go to previous page

Web Bluetooth API - Utilizing Bluetooth capabilities with a website

Web Bluetooth API
#
BLE
#
API
Himanshu Bansal
Himanshu
Android Engineer
September 7, 2023

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 <span class="text-color-code">navigator.bluetooth.requestDevice</span>. Then the browser will prompt the user with a device chooser where they can pick one device or simply cancel the request.

The <span class="text-color-code">navigator.bluetooth.requestDevice()</span> 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:

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

The parameter <span class="text-color-code">filters</span> receives an object of <span class="text-color-code">BluetoothLEScanFilter</span>, with the parameter <span class="text-color-code">services</span> containing <span class="text-color-code">BluetoothServiceUUID</span>.

In our case, <span class="text-color-code">BluetoothServiceUUID</span> is <span class="text-color-code">battery_service</span>, 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.

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

You can add multiple services at one time, like:

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

Name Filter

Bluetooth devices can also be requested on the basis of the device name being advertised with the <span class="text-color-code">name</span> filters key, or even a prefix of this name with the <span class="text-color-code">namePrefix</span> 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.
	
Copy
navigator.bluetooth.requestDevice({ filters: [{ name: 'RORO BLE Device' }], optionalServices: ['battery_service'] // Required to access service later. })

Manufacturer data filter

Request Bluetooth devices based on the manufacturer data.

The manufacturer-specific data is being advertised with the <span class="text-color-code">manufacturerData</span> filters key. This key is an array of objects with a mandatory key named <span class="text-color-code">companyIdentifier</span>. A data prefix can also be provided that filters manufacturer data from Bluetooth devices that start with it.

	
Copy
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); });

Exclusion filter

There are some cases where you want to exclude BLE devices with certain properties, like name or services. The <span class="text-color-code">exclusionFilters</span> option in <span class="text-color-code">navigator.bluetooth.requestDevice()</span> allows you to exclude devices from the browser picker.

	
Copy
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); });

No Filters

Let's say, you want to query all the available devices, in such case <span class="text-color-code">filters</span> can be replaced with <span class="text-color-code">acceptAllDevices</span>.

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

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 <span class="text-color-code">.gatt.connect()</span> on the device reference.

	
Copy
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 => { /* … */ }) . . .

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.

	
Copy
. . . .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); });

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

For example, <span class="text-color-code">battery_service</span> is the service where we can find the characteristics related to the battery, and <span class="text-color-code">battery_level</span> 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 <span class="text-color-code">display</span> is a service in a BLE device, which has <span class="text-color-code">display_message</span> characteristics to which we can send data and update the display message or perform certain actions.

	
Copy
. . . .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); });

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 <span class="text-color-code">clr</span> 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:

	
Copy
. . . .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); }

What else? Disconnect

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

Simply call <span class="text-color-code">device.gatt.disconnect()</span> to disconnect the device. Here <span class="text-color-code">device</span> s 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

Open new blog
CES 2024
#
CES
#
FutureTech

A Sneak Peek into CES 2024 - The Future of Tech Innovations

Sohini
December 7, 2023
Open new blog
MVVM and MVC Architectures in Swift
#
Swift
#
iOS

Exploring MVVM and MVC Architectures in Swift

Shubham
February 8, 2024
Open new blog
Memory Leak
#
Swift
#
iOS

Understanding Retain Cycles in Swift's ARC: A Simple Guide

Shubham
January 29, 2024