Experimenting with Bluetooth in JavaScript apps on the web, in hybrid apps and React Native
With the popularity of IoT and Bluetooth support in browsers increasing, we asked ourselves: Can we control a device over Bluetooth, from a JavaScript app, on iOS and Android? Time for an experiment.
We will use 3 different environments: a web page in a desktop browser, a hybrid Cordova app and a React Native app. From each of these environments a connection is made to a Bluetooth device. Information is read from the Bluetooth device and displayed realtime on screen:
Our testing device: a Nordic Thingy:52
We found the perfect device; the Nordic Thingy:52. The Bluetooth Low Energy (BLE) device opens up a lot of possibilities and challenges for developers to do some fun stuff with.
The Nordic Thingy:52 is a small multi-sensor semiconductor which has environment (temperature, humidity, pressure, air quality) and motion (accelerometer, gyroscope, compass) sensors. Thingy connects to Bluetooth-enabled devices and sends data to/from its sensors and actuators to an app and to the cloud. For Android/iOS there is a Nordic Thing:52 app and a web app.
How to read values of a Bluetooth Low Energy device?
When you’re connected to the Thingy, you can check which Generic Attributes (GATT) services are available. There are a list of standardised services available in the Bluetooth spec, such as Battery Level, Heart Rate, Location and Navigation.
Besides the standardised services from the spec, BLE devices can feature their own services and characteristic definitions. The Nordic Thingy:52, for example, has the Weather station service. This service has several characteristics such as temperature, pressure and humidity.
A mobile phone/tablet/browser is a GATT Client and the BLE device (Thingy) is the GATT Server. The Client will always initiate the request to a GATT Server. GATT (Generic Attributes) defines a hierarchical data structure that is exposed to connected Bluetooth Low Energy (LE) devices.
Each service and characteristic has a universally unique identifier (UUID). The UUIDs are necessary to connect and read data from a BLE device. Services from the spec have their UUIDs also standardised. The UUID of the Battery Service for, example, is 0x180F.
If a device has its own services (like the Weather station example in the Thingy:52), it needs to provide UUIDs for those services and characteristics. These UUIDs can usually be found in the documentation of the device.
Now we have everything we need to start experimenting.
Can we control a device over Bluetooth via Web Bluetooth?
We're going to read the battery level of the Thingy with the Web Bluetooth API from Chrome on desktop. Let's get into the code.
Requesting Bluetooth devices advertising the Bluetooth GATT Thingy Configuration Service:
const TCS_UUID = "ef680100-9b35-4933-9b10-52ffa9740042" //Thingy Configuration Service
navigator.bluetooth.requestDevice({
filters: [{ services: [TCS_UUID] }],
optionalServices: ["battery_service"]
})
Using filters only shows the relevant device and saves energy. Executing requestDevice()
opens a browser dialog window that only shows the device you need.
requestDevice()
returns a promise. Connect to the GATT server, get the Battery Service and the Battery Level:
navigator.bluetooth.requestDevice({...})
.then(device => device.gatt.connect())
.then(server => server.getPrimaryService('battery_service'))
.then(service => service.getCharacteristic('battery_level'))
To enable notifications when a characteristic changes we use startNotifications()
. After enabling this we add the event listener characteristicvaluechanged
to get notified of changes in the characteristic.
.then(characteristic => {
characteristic.startNotifications()
.then(() => {
characteristic.addEventListener(
'characteristicvaluechanged',
onCharacteristicChanged
)
})
.catch(error => console.error(error.code, error.name, error.message))
})
Each characteristic requires the data to be read in a specific kind of format. The Thingy docs for battery service mentions to use an unsigned 8-bit integer getUint8()
:
onCharacteristicChanged (event) {
const batteryLevel = event.target.value.getUint8(0)
console.log('battery level', batteryLevel)
}
Support for the Web Bluetooth API is currently very limited. WebKit (iOS/Safari) is unfortunately not considering this API. That means the availability of this API is not to be expected any time soon or in the immediate future for WebKit.
If you need more support, then developing a hybrid or native mobile app is recommended.
Can we control a device over Bluetooth with a hybrid app?
Since bluetooth on mobile devices is almost always available, we want to find out if we can reach more users with a hybrid app. So, we’re going to create a hybrid mobile app with Cordova. Cordova wraps your web app into a native container which can access the device functions of several platforms. These functions are exposed via a unified JavaScript API. With the Cordova hybrid app that is natively installed on a mobile device, we can load a web app for the user interface.
The Cordova plugin to work with BLE devices is called cordova-plugin-ble-central. First we scan for Bluetooth devices:
ble.scan([], 5, onDiscoverDevice, onError)
ble
is globally available via the JavaScript API and we can use it to call methods of the Cordova plugin. In the onDiscoverDevice
callback, we get an object with information about a device, such as MAC address and name (e.g. ‘Thingy’).
From here on out, the process is roughly the same as the Web Bluetooth version. First we connect to the device; then we listen to notifications on the specified service and characteristic.
function onDiscoverDevice(device) {
if (device.name === 'Thingy') {
connect(device)
}
}
function connect(device) {
var deviceId = device.deviceId
var onConnect = function () {
ble.startNotification(
deviceId,
batteryService,
batteryLevel,
onBatteryLevelChange,
onError)
}
ble.connect(deviceId, onConnect, onError)
}
After a connection has been made with Thingy, we enable to receive notifications for the battery level. Next we display the value (once again in Uint8Array format) to screen:
Can we control a device over Bluetooth via React Native?
To see if we can get it to work in a React Native environment, we tried to access the temperature value of the Thingy:52.
Like Cordova, React Native does not support Bluetooth support out of the box. We used the plugin react-native-ble-plx.
The process is again fairly similar; Listen for devices, connect to a device, provide service and characteristic and subscribe to notifications:
const bleManager = new BleManager()
bleManager.startDeviceScan(null, null, (err, device) => {
if (device.name === 'Thingy') {
device.connect()
.then(getAllServicesAndCharacteristics)
.then(subscribeToTemperatureCharaceristic)
}
})
function getAllServicesAndCharacteristics() {
/* get all services and characteristics */
}
function subscribeToTemperatureCharaceristic() {
/* Subscribe to temperature characteristic and display to screen */
}
Which results in the following:
Conclusion
We can control and read from a device over Bluetooth with JavaScript in a hybrid (Cordova) and native (React Native) app. Since both of those do not support bluetooth out of the box, we are dependent on plugins to make them work. These plugins provide their own APIs. That is something to take into consideration when building an app for production.
We can also control a device over Web Bluetooth, from a JavaScript app. The support is currently limited to Chrome only. In its defense, the Web Bluetooth API is very stable and more reliable than Cordova/React Native plugins.
Want to take it for a spin? Here’s our repo!