Working with Bluetooth Low Energy (BLE) on KaiOS
Use Cases
Bluetooth Low Energy (BLE) is a recent version of Bluetooth designed for very low power consumption. Compared to Bluetooth Enhanced Data Rate (EDR), BLE is designed to transfer much smaller amounts of data between devices via an ephemeral connection, not a durable pairing. Common BLE applications include:
- Healthcare devices - Thermometer, heart rate and glucose monitor, etc
- Proximity sensing - Tile, AirTag, “Find my” device
- Contact tracing - People near me
- Remote sensing - Battery level, volume, etc
Accessing Bluetooth APIs
Permissions
The
"bluetooth" permission is needed to access Bluetooth Low Energy APIs on KaiOS. Although the permission is available to both privileged and
certified apps, many Bluetooth features are only available in certified apps.
Availability
While most KaiOS devices support Bluetooth, it’s possible your app might get installed on a device without Bluetooth. KaiOS has no direct analog to the
uses-feature element in AndroidManifest.xml, so it’s not possible to prevent installation on unsupported devices. However, you can detect Bluetooth support at runtime.
The
feature-detection permission exposes an API to check the availability of specific device features and capabilities. For Bluetooth, check the device.bt key using the code below:
1navigator.hasFeature('device.bt')
2 .then((hasBluetooth) => {
3 if (hasBluetooth) {
4 /* Use Bluetooth */
5 }
6 });
Note: feature detection returns static values that describe what the device model is reportedly capably of, not what capabilities are presently available.
BT Interfaces
Bluetooth APIs are accessible via the BluetoothManager interface for both KaiOS 2.5 & 3.0.
KaiOS 2.5: navigator.mozBluetooth
KaiSO 3.0: navigator.b2g.mozBluetooth
Runtime Check: If your app was not granted the bluetooth permission, then these APIs will not be available. Confirm with a simple check, i.e. navigator.mozBluetooth !== null.
Note: KaiOS does not support the Web Bluetooth API. Instead, use BluetoothAdapter from FirefoxOS/ Boot2Gecko (B2G). The APIs are largely unchanged on KaiOS.
Bluetooth Adapter
Although KaiOS APIs support multiple BluetoothAdapters, all devices available today have a single antenna. The default adapter is accessible via mozBluetooth.defaultAdapter. However, the default BluetoothAdapter may not be immediately available after DOMContentLoaded. To listen for availability without polling, listen for the attributechanged event on BluetoothManager. The event passed to the callback is a
BluetoothAttributeEvent with an extra attrs property, listing all the attributes that have changed.
The example below can be used at any point in the DOM rendering lifecycle and returns a Promise that will resolve to the default BluetoothAdapter, when it becomes available.
1function getBluetoothAdapter() {
2 if (navigator.mozBluetooth.defaultAdapter) {
3 return Promise.resolve(navigator.mozBluetooth.defaultAdapter);
4 }
5
6 return new Promise((resolve) => {
7 navigator.mozBluetooth.onattributechanged = (e) => {
8 if (e.attrs.some((attr) => attr === 'defaultAdapter')) {
9 navigator.mozBluetooth.onattributechanged = null;
10 resolve(navigator.mozBluetooth.defaultAdapter);
11 }
12 };
13 });
14}
Note: a more robust version of this function would include a timeout, in the event that the BluetoothAdapter does not become available.
Bluetooth Low Energy
BLE Interfaces
1mozBluetooth.defaultAdapter.gattServer;
2mozBluetooth.defaultAdapter.startLeScan(serviceUuids: string[]): Promise<BluetoothDiscoveryHandle>;
3mozBluetooth.defaultAdapter.stopLeScan(discoveryHandle: BluetoothDiscoveryHandle);
Discovering (Peripheral)
Scan for nearby BLE devices with startLeScan. It takes an optional list of GATT Service UUIDs to filter results and returns a Promise that resolves to a handle that can be passed to stopLeScan to stop discovering devices.
1mozBluetooth.defaultAdapter.startLeScan(serviceUuids: string[]): Promise<BluetoothDiscoveryHandle>;
2mozBluetooth.defaultAdapter.stopLeScan(discoveryHandle: BluetoothDiscoveryHandle);
The
BluetoothDiscoveryHandle published a devicefound that can be listened to when new devices are discovered. For BLE devices, the event passed to the callback is a
BluetoothLeDeviceEvent with a few important properties, namely device which is a
BluetoothDevice and rssi (received signal strength indication) which is a relative index of how close or far the device is.
Here is a simple example putting it all together that scans for 5 seconds and logs devices to the console.
1navigator.mozBluetooth.defaultAdapter.startLeScan()
2 .then((handle) => {
3 handle.ondevicefound = (leDeviceEvent) => {
4 let nameOrAddress = leDeviceEvent.device.name || leDeviceEvent.device.address;
5 console.log(`Device found ${nameOrAddress} RSSI = ${leDeviceEvent.rssi}`);
6 };
7
8 setTimeout(() => {
9 navigator.mozBluetooth.defaultAdapter.stopLeScan(handle);
10 }, 5000)
11 });
Connecting
Once a device has been discovered, the next step is to connect to it. BluetoothDevice has the gatt property (type
BluetoothGatt) that help a few helpful methods:
connectanddisconnectto connect/cisconnect to the remote BLE device.discoverServicesto discover services, characteristics, descriptors offered by the remote GATT.readRemoteRssito read RSSI for the remote BLE device.
Note: All of the above methods return a Promise that might reject depending on the connection state of the remote device.
Services and Characteristics
Once connected, the next step is to find the service(s) of interest and read/ write values. The services array of
BluetoothGattService lists services that can be identified via UUID. Each service itself has an array of
BluetoothGattCharacteristic from which we can finally read or write values, depending on permissions.
Reading and Writing
The value of a characteristic is stored as an ArrayBuffer via the value property. Calling readValue will return a Promise that resolves to the remote characteristic value. Writing works via the writeValue method.
1handle.ondevicefound = (leDeviceEvent) => {
2 let device = leDeviceEvent.device;
3 device.gatt.connect()
4 .then(() => device.gatt.discoverServices())
5 .then(() => device.gatt.services[0].characteristics[0].readValue())
6 .then((valueBuffer) => /* TODO: read ArrayBuffer */)
7};
Notifications and Indications
KaiOS does not support notifications or indications! During runtime testing across a number of devices, I have been unable to receive notifications or indications via the startNotifications method. No characteristicchanged events (or any BLE events) are published for notifications.
GATT Server (Central)
KaiOS devices can also act as central devices and provide GATT services. The first step is to create a BluetoothGattService using the constructor, then add characteristics to the service with the appropriate properties and permissions.
1const SERVICE_UUID = '57bb7cb8-7700-4971-a20b-74a1f0c070c0';
2let service = new BluetoothGattService({
3 isPrimary: true,
4 uuid: SERVICE_UUID,
5});
6let defaultAdapter = navigator.mozBluetooth.defaultAdapter;
Note: addCharacteristic returns a Promise, so it’s best to wait for this to resolve before adding the service to the
BluetoothGattServer.
1service.addCharacteristic(
2 'e8ef9b49-3b70-411b-9ded-f8bf6d651f38',
3 { read: true, readEncrypted: true },
4 { read: true, broadcast: true },
5 new ArrayBuffer()
6)
7.then(() => defaultAdapter.gattServer.addService(service))
8.then(() => defaultAdapter.setDiscoverable(true))
9.then(() => defaultAdapter.gattServer.startAdvertising({
10 includeDevName: true,
11 includeTxPower: true
12 serviceUuid: SERVICE_UUID,
13 serviceUuids: [SERVICE_UUID],
14}));
Once the service is added, make sure the device is discoverable and finally, call startAdvertising to begin advertising the GATT service to nearby devices. startAdvertising accepts a
BluetoothAdvertisingData object with some optional properties like includeDevName, which will broadcast the device’s name (i.e. “Nokia 8110 4G” by default) and includeTxPower, which will broadcast the Transmit (Tx) power. You can even set the name yourself via the defaultAdapter.setName method.
Note: during runtime testing, I have found that only one service can be active and broadcast at a time. The service can have multiple characteristics, but KaiOS does not appear to reliably support broadcasting multiple services.
Conclusion
With different Bluetooth standard versions and configurations, it can be a challenge to leverage complex networking technologies like Bluetooth. Always test your code on actual devices, since KaiOS phones may not perform as expected or as documented. Finally, test connectivity with a range of devices and prototype quickly with free utility apps like Bluetility (Mac OS) and LightBlue (iOS).
Tip: It can be helpful to convert the Firefox WebIDL interface definitions into TypeScript type definitions to more easily and safely code with unfamliar KaiOS Bluetooth APIs.
Credits
Thank you to Jan Frydendal of MochaSoft who provided insights from his own testing of Bluetooth Low Energy compatibility and support on KaiOS.
Contact Author
Bluetooth Low Energy (BLE) is a very useful technology for discovering and connecting to nearby devices to exchange small pieces of information. If you are interested in extending your company’s Bluetooth offering to KaiOS, learn more about the author and find contact information on the About page.