Web Bluetooth API introduction

Recently, I’ve been looking through the internet for a way to interact with a microcontroller without a need to develop applications for all sorts of operating systems. What I’ve found is an interesting protocol called Web Bluetooth API. This protocol lets the web browsers access the physical Bluetooth interface. It works on PCs and phones – you can find the compatibility here. It is worth mentioning that the protocol is still under heavy development.

There are two distinct safety features of this protocol:

  • it works only on websites with a secure connection (HTTPS)
  • the only way to trigger a scan for devices is a user gesture

The above make the development a little harder but definitely are useful from users safety perspective.

ESP32 example

In my experiment I’ve used ESP32 dev board from Espressif Systems. I’ve flashed the microcontroller with a slightly modified version of the ESP_notify sketch that comes with a default ESP32 library for Arduino framework.

/*
    Video: https://www.youtube.com/watch?v=oCMOYS71NIU
    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
    Ported to Arduino ESP32 by Evandro Copercini
    updated by chegewara

   Create a BLE server that, once we receive a connection, will send periodic notifications.
   The service advertises itself as: 4fafc201-1fb5-459e-8fcc-c5c9c331914b
   And has a characteristic of: beb5483e-36e1-4688-b7f5-ea07361b26a8

   The design of creating the BLE server is:
   1. Create a BLE Server
   2. Create a BLE Service
   3. Create a BLE Characteristic on the Service
   4. Create a BLE Descriptor on the characteristic
   5. Start the service.
   6. Start advertising.

   A connect hander associated with the server starts a background task that performs notification
   every couple of seconds.
*/
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint32_t value = 115;

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
      Serial.println("Device connected!");
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
      Serial.println("Device disconnected!");
    }
};

void setup() {
  Serial.begin(115200);

  // Create the BLE Device
  BLEDevice::init("ESP32");

  // Create the BLE Server
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic
  pCharacteristic = pService->createCharacteristic(
                      CHARACTERISTIC_UUID,
                      BLECharacteristic::PROPERTY_READ   |
                      BLECharacteristic::PROPERTY_WRITE  |
                      BLECharacteristic::PROPERTY_NOTIFY |
                      BLECharacteristic::PROPERTY_INDICATE
                    );

  // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
  // Create a BLE Descriptor
  pCharacteristic->addDescriptor(new BLE2902());

  // Start the service
  pService->start();

  // Start advertising
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(false);
  pAdvertising->setMinPreferred(0x0);  // set value to 0x00 to not advertise this parameter
  BLEDevice::startAdvertising();
  Serial.println("Waiting a client connection to notify...");
}

void loop() {
    // notify changed value
    if (deviceConnected) {
        pCharacteristic->setValue((uint8_t*)&value, 4);
        pCharacteristic->notify();
//        value++;
        delay(3); // bluetooth stack will go into congestion, if too many packets are sent, in 6 hours test i was able to go as low as 3ms
    }
    // disconnecting
    if (!deviceConnected && oldDeviceConnected) {
        delay(500); // give the bluetooth stack the chance to get things ready
        pServer->startAdvertising(); // restart advertising
        Serial.println("start advertising");
        oldDeviceConnected = deviceConnected;
    }
    // connecting
    if (deviceConnected && !oldDeviceConnected) {
        // do stuff here on connecting
        oldDeviceConnected = deviceConnected;
    }
}

What this code does is creating the server’s GATT Bluetooth profile with a service and custom characteristic. This characteristic lets the client read and write.
For this experiment, the microcontroller serves the value of 115 on a client’s read.

Website example

Web Bluetooth API is based on javascript promises which make working with Bluetooth asynchronously easier. The simple exemplary website created for demo purposes consists of a header, connect button, and a log div.

Website example:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Bluetooth test</title>
</head>

<body>
    <h1> ESP32 BLE test web bluetooth API example </h1>
     <button>CONNECT</button>
    <h1></h1>
    <div id="logs">Log output:</div>
</body>

</html>
<script>
    // Function to display logs on the website instead of console
    function Log(msg){
    document.querySelector('#logs').innerHTML =
                    document.querySelector('#logs').innerHTML + "<br>" + msg
    }

    document.querySelector('button').addEventListener('click', async event => {
        navigator.bluetooth.requestDevice({
            acceptAllDevices: true,
            optionalServices: ['4fafc201-1fb5-459e-8fcc-c5c9c331914b']
        })
        .then(device => { 
            Log("connecting to " + device.name);
            // Attempts to connect to remote GATT Server.
            return device.gatt.connect();
        })
        .then(server => {
            // Getting Service…
            return server.getPrimaryService("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
        })
        .then(service => {
            // Getting Characteristic…
            return service.getCharacteristic("beb5483e-36e1-4688-b7f5-ea07361b26a8");
        })
        .then(characteristic => {
            // Reading value
            return characteristic.readValue();
        })
        .then(value => {
            Log(`Value is ${value.getUint8(0)}`);
        })
        .catch(error => { console.error(error); });
    });
</script>

As you can see the Chrome on Android was able to interact with physical Bluetooth interface. The code has successfully read the value from a characteristics and displayed it onto the screen.

Summary

There is great potential in this protocol, because there is no need to develop custom applications for different platforms. All it needs is a browser supporting this protocol and access to Bluetooth interface.

Similarly to Web Bluetooth there is Web USB protocol being developed. It works in the same manner – the browser requests access to physical USB interface. Stay tuned for a post about Web USB.

In LPN Plant we connect consulting, technical expertise and financial effectiveness to design and implement low power wireless solutions for industry. If you are looking for product developers or just need support in a piece of your system feel free to contact us. If you enjoying this type of content feel free to share content on social media.

Leave a Reply

Your email address will not be published. Required fields are marked *