The Web Bluetooth API is one of the interesting new technologies that the Google Chrome team has cooked up that further narrows the gap between web apps and “real” apps. It allows full access to Bluetooth devices straight from the browser. The ability to connect directly to various devices like sensors, printers, or input devices opens a new door for web apps.
One nasty thing about the Web Bluetooth API is browser support. Web Bluetooth APIs have been available in Chrome for a couple of years now, but other browser vendors are not enthusiastic about jumping onboard. Although Chrome and other Blink-based browsers are very popular these days, Safari and Firefox users and the whole iOS ecosystem are still ruled out.
For Java developers, another issue is that the API is JavaScript. Following the documentation and examples guides you towards a route that might make you decide to drop your plans. While playing with a Bluetooth heart rate monitor to analyze my random arrhythmias, I came up with a little trick that allows you to write most of your device integration in Java and potentially utilize existing libraries. My example uses Vaadin, but the same approach can be adapted to using any technology and even other languages.
A screenshot of a Vaadin web application reading and analyzing ECG data from a Bluetooth device.
Write the connection script
There is almost no way around the fact that some of your code executes in the browser when connecting to the device. But this is the only part you need in JS, and it is relatively simple, especially when working with the Bluetooth Low Energy API.
In the following code snippet, I request the user to choose a Bluetooth device that advertises itself as a heart rate monitor and subscribe to a handler for its value change events.
const device = await navigator.bluetooth.requestDevice(
{
filters: [{ services:[ "heart_rate" ] }]
});
const server = await device.gatt.connect();
const service = await server.getPrimaryService("heart_rate");
const ch = await service.getCharacteristic("heart_rate_measurement");
await ch.startNotifications();
ch.addEventListener("characteristicvaluechanged", hrHandler);
The heart_rate service
and heart_rate_measurement
are some sort of standards, and are supported by most heart rate belts and some sport watches that measure heart rate using optical sensors.
In the example application, I also request Polar H10 specific ECG data and pass that to a separate handler.
const ecgService = await server.getPrimaryService(PMD_SERVICE)
.catch((err) => { errorHandler("No ECG available"); });;
var ecgCtrlCharacteristic = await ecgService.getCharacteristic(PMD_CONTROL);
// the bit flag array to configure what data to fetch,
// values drawn from Polar Android Java/Kotlin library
await ecgCtrlCharacteristic.writeValue(new Uint8Array([0x02, 0, 0x00, 0x01, 0x82, 0x00, 0x01, 0x01, 0x0E, 0x00]));
console.log("ECG requested");
const dataCharacteristic = await ecgService.getCharacteristic(PMD_DATA);
await dataCharacteristic.startNotifications();
dataCharacteristic.addEventListener("characteristicvaluechanged", ecgHandler);
A way to improve this in a framework like Vaadin Flow could be to write a piece of reusable integration code. Using this, one could proxy the API to the Java side, and just provide things like device filters and requested characteristics in Java. But as I’m not expecting to do new device integrations every year, I’ll settle with JavaScript for this part.
In the example project, the above snippets can be found from h10tooling.js, where I expose a method to just hook the data handlers.
Move the raw data from the device to JVM
The trick to making developing device integrations easy is to do as much as possible in your favored development environment – in this case, Java. With modern JavaScript, one could push the byte array over to the server side using XHR to a custom endpoint. In my case, I wanted to use the existing client-server communication channel provided by Vaadin. Although it is flexible with, for example, strings and numbers, there is no support for a raw byte array or stream.
As a workaround, I chose to take the good old approach from email standards and encode the bytes using the base64 algorithm. JavaScript has the built-in btoa function to do the encoding, but we need a little bit of fiddling with String and byte arrays as it doesn't accept the raw byte buffer.
Finally, in my handler code that I register from the Vaadin UI class, I just take in the raw data from the event and pass it to the server-side method, which is a one-liner:
el.$server.handleECGData(btoa(String.fromCharCode(...new Uint8Array(event.target.value.buffer))));
Parse, analyze and utilize
As I’m using Vaadin, I didn’t need to declare a servlet or REST endpoint but marked the method using the @ClientCallable
annotation, so that it got a proxy method published to the client side. The first thing to do is to decode the base64 string back into byte[]. This we can do simply, using the built-in JDK helper:
@ClientCallable
public void handleECGData(String base64encoded) {
// Vaadin and many other web frameworks don't support
// transferring raw byte data, decode the Base64 string
byte[] bytes = Base64.getDecoder().decode(base64encoded);
// Now we are on the safe side, use all your Java skills
Now we are pretty much on the safe side, ready to use our existing skills and code. Initially, I planned to utilize the SDK for Polar sensors in my application but, as it was not available as a pre-built artifact and had some Android/Kotlin dependencies, I just used it as a reference and built a simple helper class to parse the ECG data.
Check out the example application
Based on my experiences with this example and other exercises with an IoT sensor, I’d claim that Web Bluetooth is a well-established technology and ready for prime time. The only downside is that we are ruling out iOS users, but if you are ready to take that hit, it is probably one of the best ways to access your accessories from a web app.
To get even more inspired about Web Bluetooth, check out the sources of my example app or try it out online using your own heart rate monitor belt. (Please note, the Azure free tier deployment is used, so it might be a bit sluggish to load from time to time.)