-
Notifications
You must be signed in to change notification settings - Fork 103
Design of the BLE plugin API
This is a design document for the updated BLE plugin API.
The purpose of this document is to discuss an extended BLE plugin API.
The BLE plugin API is rather low-level as it is. The EasyBLE library was created to make the plugin API easier to use.
Having a library on top of the plugin API has however been a source of confusion. The Evothings library structure for EasyBLE has also been overly complex and hard to use (much because of async script loading and lack of a single distribution JS file, which has been fixed now).
Then there is also the Web Bluetooth library implemented in Bleat (and the Bleat Classic API), which is available for the BLE plugin.
This results in 3-4 different APIs to choose from, with varying degree of abstraction level.
We have decided to revise the BLE plugin library structure. The purpose of this document is to discuss the future direction of the BLE plugin API and high-level libraries.
We are currently proposing the following:
- Deprecate the EasyBLE library since it is overlapping with the BLE plugin API and has caused confusion.
- Introduce a few new functions from EasyBLE to the BLE plugin API, to make the API more straightforward to use.
- Focus on Web Bluetooth as high level BLE API, since it is a promising standard likely to become widespread among JavaScript developers.
Feedback on this proposal is most welcome.
With some additions to the BLE plugin it can become much more straightforward to use, and thus eliminate the need for EasyBLE.
The plugin API will still be fully backwards compatible.
This section outlines the BLE plugin API extensions implemented here: https://github.com/mikaelkindborg/cordova-ble
One thing that requires to write your own code in the BLE plugin API is to get handles for services, characteristics and descriptors. Function evothings.ble.readAllServiceData(deviceHandle, success, error)
helps to read services, characteristics and descriptors, but to find the object with the handle for use in the other functions, you need to search the service tree manually.
Three new functions can help with this:
evothings.ble.getService(device, uuid)
evothings.ble.getCharacteristic(service, uuid)
evothings.ble.getDescriptor(characteristic, uuid)
Code from TI SensorTag example app in file app.js:
// Get Luxometer service and characteristics.
var service = evothings.ble.getService(device, LUXOMETER_SERVICE)
var configCharacteristic = evothings.ble.getCharacteristic(service, LUXOMETER_CONFIG)
var dataCharacteristic = evothings.ble.getCharacteristic(service, LUXOMETER_DATA)
We have seen devices where characteristics within a service don't have unique UUIDs. In such special cases, the developer can scan the service tree manually, to find the desired characteristic. It would also be possible to provide optional parameters to evothings.ble.getCharacteristic(service, uuid)
, such as writeType, permissions, and property flags, as a convenience for developers. For example: evothings.ble.getCharacteristic(service, uuid, [writeType])
The plugin API uses handles for most functions, to reference devices, services, characteristics and descriptors.
There also exist JavaScript object structures for these objects. Each object has a field named "handle".
The API would be more high-level (less details to keep track of for the developer) if we would allow an object to be passed as a parameter where handle is expected. Handles would still be allowed to pass, for backwards compatibility.
Example of using handles as parameters (old API):
// Turn Luxometer ON.
evothings.ble.writeCharacteristic(
deviceHandle,
configCharacteristic.handle,
new Uint8Array([1]),
turnOnLuxometerSuccess,
turnOnLuxometerError)
// Enable notifications from the Luxometer.
evothings.ble.enableNotification(
deviceHandle,
dataCharacteristic.handle,
readLuxometerSuccess,
readLuxometerError)
One can assume it could be a common programming error to forget to specify the handle, that is writing configCharacteristic
instead of configCharacteristic.handle
Allowing objects as parameters would prevent this error and reduce the number of concepts used.
Example of using objects as parameters (extended API):
// Turn Luxometer ON.
evothings.ble.writeCharacteristic(
device,
configCharacteristic,
new Uint8Array([1]),
turnOnLuxometerSuccess,
turnOnLuxometerError)
// Enable notifications from the Luxometer.
evothings.ble.enableNotification(
device,
dataCharacteristic,
readLuxometerSuccess,
readLuxometerError)
There are some options that would be useful, like being able to tell evothings.ble.readAllServiceData(deviceHandle, success, error)
to accept an option read only selected services (for improved performance).
The API could consistently use an options object as last parameter for functions that have options (this has been suggested by developers using the API). (Implemented)
EasyBLE provides parsing of the advertisement data record on Android (iOS provides this natively). This should be included in the BLE plugin API.
Once a device is discovered, here is how to connect, read services and read/write to the device:
evothings.ble.connect(device, connectCallback, connectError)
function connectCallback(connectInfo)
{
if (connectInfo.state == evothings.ble.connectionState.STATE_CONNECTED)
{
// Read all services, characteristics and descriptors.
evothings.ble.readAllServiceData(
device,
readServicesSuccess,
readServicesError)
}
if (connectInfo.state == evothings.ble.connectionState.STATE_DISCONNECTED)
{
showMessage('Device disconnected')
}
}
function readServicesSuccess(services)
{
// Get service and characteristic.
var service = evothings.ble.getService(services, SERVICE_UUID)
var characteristic = evothings.ble.getCharacteristic(service, CHARACTERISTIC_UUID)
// Start writing/reading/enabling notifications for the characteristic.
communicateWithDevice(device, characteristic)
}
function communicateWithDevice(device, characteristic)
{
// How to write a characteristic.
evothings.ble.writeCharacteristic(
device,
characteristic,
dataToWrite,
writeSuccess,
writeError)
// How to read a characteristic.
evothings.ble.readCharacteristic(
device,
characteristic,
readSuccess,
readError)
// How to enable notifications.
evothings.ble.enableNotification(
device,
characteristic,
notificationCallback,
notificationError)
}
function readServicesError(error)
{
showMessage('Read services error: ' + error)
}
function connectError(error)
{
showMessage('Connect error: ' + error)
}
Same code as above, but made higher level by automatic discovery of services by connect (this can be turned off by supplying an options object), and an optional disconnected callback added to connect to eliminate the if-statement inside the connected callback used in the above code:
evothings.ble.connectToDevice(
device,
connectedCallback,
disconnectedCallback,
connectError)
function connectedCallback(device)
{
// Get service and characteristic.
var service = evothings.ble.getService(device, SERVICE_UUID)
var characteristic = evothings.ble.getCharacteristic(service, CHARACTERISTIC_UUID)
// Start writing/reading/enabling notifications for the characteristic.
communicateWithDevice(device, characteristic)
}
function communicateWithDevice(device, characteristic)
{
// How to write a characteristic.
evothings.ble.writeCharacteristic(
device,
characteristic,
dataToWrite,
writeSuccess,
writeError)
// How to read a characteristic.
evothings.ble.readCharacteristic(
device,
characteristic,
readSuccess,
readError)
// How to enable notifications.
evothings.ble.enableNotification(
device,
characteristic,
notificationCallback,
notificationError)
}
function disconnectedCallback(device)
{
showMessage('Device disconnected')
}
function connectError(error)
{
showMessage('Connect error: ' + error)
}
Functions for parsing Eddystone scan records could also be made avalable in the BLE plugin, either in the code API, or as an small library bundled with the plugin.
The plan is to deprecate EasyBLE and recommend using the updated BLE plugin API, or Web Bluetooth.
The drawbacks with EasyBLE is that it is a separate library, currently not part of the plugin. It is three years old, and has grown incrementally and is now rather bulky, without providing truly high-level abstractions like Web Bluetooth does.
Moreover, with the extended BLE plugin API, the level of detail becomes very similar to EasyBLE, and this library would no longer be needed.
Web Bluetooth is regarded by several people in the IoT community to be the future standard for writing JavaScript applications for BLE.
The Bleat library has an implementation of Web Bluetooth for the BLE plugin.
Major advantages with Web Bluetooth are:
- High level abstractions (like promises)
- Has a standards group
- Working implementation exists
- Available both in Cordova apps and in (future) web browsers
Drawback is that the standard is not yet fully finalized.
Our proposal is to extend the BLE plugin API to make it comparable to EasyBLE in ease-of-use, while still keep full backwards compatibility and access to low level functionality.
Two APIs would be supported:
- The BLE plugin API (extended version)
- Web Bluetooth (add-on library)
EasyBLE would be deprecated.
Implementation of extended API is in file: https://github.com/mikaelkindborg/cordova-ble/blob/master/ble.js
Comparative examples:
- Extended BLE plugin API: 172 lines of code - https://github.com/mikaelkindborg/cordova-ble/blob/master/examples/core-api/tisensortag/app.js
- BLE plugin API using handles: 196 lines of code - https://github.com/mikaelkindborg/cordova-ble/blob/master/examples/core-api/tisensortag-low-level-api/app.js
- EasyBLE: 179 lines of code - https://github.com/mikaelkindborg/cordova-ble/blob/master/examples/deprecated/easyble/tisensortag/app.js
- Web Bluetooth: 116 lines of code - https://github.com/mikaelkindborg/cordova-ble/blob/new-api/examples/webbluetooth/tisensortag/app.js
You are most welcome to give feedback on this proposal:
- By writing on the Evothings Gitter room: https://gitter.im/evothings/evothings
- By editing this wiki page
- By commenting on this issue: https://github.com/evothings/cordova-ble/issues/115
We are using NRF52 as a reference device and also the TI SensorTag CC2650 for the BLE API development.
For future examples, one idea is to use the Heart Rate service, as specified by the Bluetooth standard:
https://www.bluetooth.com/specifications/gatt/services
https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=239865
A heart rate service can be implemented on NRF52 and be used for testing.