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:
connect
anddisconnect
to connect/cisconnect to the remote BLE device.discoverServices
to discover services, characteristics, descriptors offered by the remote GATT.readRemoteRssi
to 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 compatability 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.