Security News
Node.js EOL Versions CVE Dubbed the "Worst CVE of the Year" by Security Experts
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.
com.randdusing.bluetoothle
Advanced tools
Use the Bluetooth Low Energy plugin to connect your Cordova app to new Bluetooth devices like heart rate monitors, thermometers, etc...
Bluetooth LE Cordova Plugin
====================
If you are using PhoneGap add the plugin to your app by running the command below:
phonegap local plugin add https://github.com/randdusing/BluetoothLE
If you are using Apache Cordova use this instead:
cordova plugin add https://github.com/randdusing/BluetoothLE
If you are using PhoneGap Build and want to use the PhoneGap Build Plugin (outdated plugin version 2.0.0), add below to config.xml:
<gap:plugin name="com.randdusing.bluetoothle" />
If you are using PhoneGap Build and want to use the Cordova Plugin Registry (up to date plugin version but PhoneGap Build doesn't support Android API21 yet), add below to config.xml:
<gap:plugin name="com.randdusing.bluetoothle" source="plugins.cordova.io" />
By default, background mode is enabled. If you wish to remove this, follow the steps below:
Updating the plugin for iOS causes BluetoothLePlugin.m to be removed from the Compile Sources and CoreBluetooth.framework to be removed from Link Binary with Libraries. To fix:
The latest version of the plugin requires you to set the Android target API to 21.
I'm no longer updating the PhoneGap Build version of this plugin since it costs money and better alternatives like the Cordova Plugin Registry exist. Once PhoneGap Build supports Android API 21 (required for latest version of my plugin), I'll request deactivation of the plugin on PhoneGap Build to remove the out of date version.
Discovery works differently between Android and iOS. In Android, a single function is called to initiate discovery of all services, characteristics and descriptors on the device. In iOS, a single function is called to discover the device's services. Then another function to discover the characteristics of a particular service. And then another function to discover the descriptors of a particular characteristic. The Device plugin (http://docs.phonegap.com/en/edge/cordova_device_device.md.html#Device) should be used to properly determine the device and make the proper calls if necessary. Additionally, if a device is disconnected, it must be rediscovered when running on iOS.
UUIDs can be 16 bits or 128 bits. The "out of the box" UUIDs from the link below are 16 bits. Since iOS returns the 16 bit version of the "out of the box" UUIDs even if a 128 bit UUID was used in the parameters, the 16 bit version should always be used for the "out of the box" UUIDs for consistency. Android on the other hand only uses the 128 bit version, but the plugin will automatically convert 16 bit UUIDs to the 128 bit version on input and output. For a list of out of the box UUIDS, see https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx
On iOS, the MAC address is hidden from the advertisement packet, and the address returned from the scanResult is a generated, device-specific address. This is a problem when using devices like iBeacons where you need the MAC Address. Fortunately the CLBeacon class can be used for this, but unfortunately it's not supported in this plugin. One option is to set Manufacturer Specific Data in the advertisement packet if that's possible in your project. Another option is to connect to the device and use the "Device Information" (0x180A) service, but connecting to each device is much more energy intensive than scanning for advertisement data. See the following for more info: https://stackoverflow.com/questions/18973098/get-mac-address-of-bluetooth-low-energy-peripheral, https://stackoverflow.com/questions/22833198/get-advertisement-data-for-ble-in-ios
Whenever the error callback is executed, the return object will contain the error type and a message.
For example:
{"error":"startScan", "message":"Scanning already started"}
Characteristics can have the following different properties: broadcast, read, writeWithoutResponse, write, notify, indicate, authenticatedSignedWrites, extendedProperties, notifyEncryptionRequired, indicateEncryptionRequired If the characteristic has a property, it will exist as a key in the characteristic's properties object. See discovery() or characteristics()
https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html https://developer.apple.com/library/mac/documentation/CoreBluetooth/Reference/CBCharacteristic_Class/translated_content/CBCharacteristic.html#//apple_ref/c/tdef/CBCharacteristicProperties
Initialize Bluetooth on the device. Must be called before anything else. Callback will continuously be used whenever Bluetooth is enabled or disabled. Note: Although Bluetooth initialization could initially be successful, there's no guarantee whether it will stay enabled. Each call checks whether Bluetooth is disabled. If it becomes disabled, the user must connect to the device, start a read/write operation, etc again. If Bluetooth is disabled, you can request the user to enable it by setting the request property to true. The request
property in the params
argument is optional and defaults to false. Also, this function should only be called once. But if it's called subsequent times, it will return either status => enabled or error => enable.
bluetoothle.initialize(initializeSuccess, initializeError, params);
{
"request": true,
"statusReceiver": false
}
{
"status": "enabled"
}
Enable Bluetooth on the device. Android support only.
bluetoothle.enable(enableSuccess, enableError);
The successCallback isn't actually used. Listen to initialize callbacks for change in Bluetooth state. A successful enable will return a status => enabled via initialize success callback.
Disable Bluetooth on the device. Android support only.
bluetoothle.disable(disableSuccess, disableError);
The successCallback isn't actually used. Listen to initialize callbacks for change in Bluetooth state. A successful disable will return an error => enable via initialize error callback.
Scan for Bluetooth LE devices. Since scanning is expensive, stop as soon as possible. The Cordova app should use a timer to limit the scan interval. Also, Android uses an AND operator for filtering, while iOS uses an OR operator.
bluetoothle.startScan(startScanSuccess, startScanError, params);
{
"serviceUuids": [
"180D",
"180F"
]
}
{
"status": "scanStarted"
}
{
"status": "scanResult",
"advertisement": "awArG05L",
"rssi": -58,
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
Stop scan for Bluetooth LE devices. Since scanning is expensive, stop as soon as possible. The Cordova app should use a timer to limit the scanning time.
bluetoothle.stopScan(stopScanSuccess, stopScanError);
{
"status": "scanStopped"
}
Retrieved Bluetooth LE devices currently connected. In iOS, devices that are "paired" to will not return during a normal scan. Callback is "instant" compared to a scan. I haven't been able to get UUID filtering working on Android, so it returns all paired devices including non Bluetooth LE ones.
bluetoothle.retrieveConnected(retrieveConnectedSuccess, retrieveConnectedError, params);
{
"serviceUuids": [
"180D",
"180F"
]
}
An array of device objects:
[
{
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
]
Connect to a Bluetooth LE device. The Cordova app should use a timer to limit the connecting time in case connecting is never successful. Once a device is connected, it may disconnect without user intervention. The original connection callback will be called again and receive an object with status => disconnected. To reconnect to the device, use the reconnect method. Before connecting to a new device, the current device must be disconnected and closed. If a timeout occurs, the connection attempt should be canceled using disconnect().
bluetoothle.connect(connectSuccess, connectError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
{
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"status": "connecting"
}
{
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"status": "connected"
}
{
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"status": "disconnected"
}
Reconnect to a previously connected Bluetooth device. The Cordova app should use a timer to limit the connecting time. If a timeout occurs, the reconnection attempt should be canceled using disconnect().
bluetoothle.reconnect(reconnectSuccess, reconnectError);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
{
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"status": "connecting"
}
{
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"status": "connected"
}
{
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"status": "disconnected"
}
Disconnect from a Bluetooth LE device.
bluetoothle.disconnect(disconnectSuccess, disconnectError);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
{
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"status": "disconnecting"
}
{
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"status": "disconnected"
}
Close/dispose a Bluetooth LE device. Must disconnect before closing.
bluetoothle.close(closeSuccess, closeError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
{
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"status": "closed"
}
Discover all the devices services, characteristics and descriptors. Doesn't need to be called again after disconnecting and then reconnecting. Android support only.
bluetoothle.discover(discoverSuccess, discoverError, params);
{
"address": "00:22:D0:3B:32:10"
}
Device Object:
Service Object:
Characteristic Object:
Descriptor Object:
{
"address": "00:22:D0:3B:32:10",
"status": "discovered",
"services": [
{
"characteristics": [
{
"descriptors": [
],
"characteristicUuid": "2a00",
"properties": {
"write": true,
"writeWithoutResponse": true,
"read": true
}
},
{
"descriptors": [
],
"characteristicUuid": "2a01",
"properties": {
"read": true
}
},
{
"descriptors": [
],
"characteristicUuid": "2a02",
"properties": {
"read": true
}
},
{
"descriptors": [
],
"characteristicUuid": "2a03",
"properties": {
"write": true
}
},
{
"descriptors": [
],
"characteristicUuid": "2a04",
"properties": {
"read": true
}
}
],
"serviceUuid": "1800"
},
{
"characteristics": [
{
"descriptors": [
{
"descriptorUuid": "2902"
}
],
"characteristicUuid": "2a05",
"properties": {
"indicate": true
}
}
],
"serviceUuid": "1801"
},
{
"characteristics": [
{
"descriptors": [
{
"descriptorUuid": "2902"
}
],
"characteristicUuid": "2a37",
"properties": {
"notify": true
}
},
{
"descriptors": [
],
"characteristicUuid": "2a38",
"properties": {
"read": true
}
}
],
"serviceUuid": "180d"
},
{
"characteristics": [
{
"descriptors": [
],
"characteristicUuid": "2a23",
"properties": {
"read": true
}
},
{
"descriptors": [
],
"characteristicUuid": "2a24",
"properties": {
"read": true
}
},
{
"descriptors": [
],
"characteristicUuid": "2a25",
"properties": {
"read": true
}
},
{
"descriptors": [
],
"characteristicUuid": "2a26",
"properties": {
"read": true
}
},
{
"descriptors": [
],
"characteristicUuid": "2a27",
"properties": {
"read": true
}
},
{
"descriptors": [
],
"characteristicUuid": "2a28",
"properties": {
"read": true
}
},
{
"descriptors": [
],
"characteristicUuid": "2a29",
"properties": {
"read": true
}
}
],
"serviceUuid": "180a"
},
{
"characteristics": [
{
"descriptors": [
],
"characteristicUuid": "2a19",
"properties": {
"read": true
}
}
],
"serviceUuid": "180f"
},
{
"characteristics": [
{
"descriptors": [
],
"characteristicUuid": "6217ff4c-c8ec-b1fb-1380-3ad986708e2d",
"properties": {
"read": true
}
},
{
"descriptors": [
{
"descriptorUuid": "2902"
}
],
"characteristicUuid": "6217ff4d-91bb-91d0-7e2a-7cd3bda8a1f3",
"properties": {
"write": true,
"indicate": true
}
}
],
"serviceUuid": "6217ff4b-fb31-1140-ad5a-a45545d7ecf3"
}
],
"name": "Polar H7 3B321015"
}
Discover the device's services. Not providing an array of services will return all services and take longer to discover. iOS support only.
bluetoothle.services(servicesSuccess, servicesError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"serviceUuids": [
]
}
{
"status": "services",
"serviceUuids": [
"180d",
"180a",
"180f",
"6217ff4b-fb31-1140-ad5a-a45545d7ecf3"
],
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
Discover the service's characteristics. Not providing an array of characteristics will return all characteristics and take longer to discover. iOS support only.
bluetoothle.characteristics(characteristicsSuccess, characteristicsError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"serviceUuid": "180d",
"characteristicUuids": [
]
}
{
"status": "characteristics",
"characteristics": [
{
"properties": {
"notify": true
},
"characteristicUuid": "2a37"
},
{
"properties": {
"read": true
},
"characteristicUuid": "2a38"
}
],
"name": "Polar H7 3B321015",
"serviceUuid": "180d",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
Discover the characteristic's descriptors. iOS support only.
bluetoothle.descriptors(descriptorsSuccess, descriptorsError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"serviceUuid": "180d",
"characteristicUuid": "2a37"
}
{
"status": "descriptors",
"descriptorUuids": [
"2902"
],
"characteristicUuid": "2a37",
"name": "Polar H7 3B321015",
"serviceUuid": "180d",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
Read a particular service's characteristic once.
bluetoothle.read(readSuccess, readError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"serviceUuid": "180d",
"characteristicUuid": "2a38"
}
{
"status": "read",
"value": "AQ==",
"characteristicUuid": "2a38",
"name": "Polar H7 3B321015",
"serviceUuid": "180d",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
Subscribe to a particular service's characteristic. Once a subscription is no longer needed, execute unsubscribe in a similar fashion. The Client Configuration descriptor will automatically be written to enable notification/indication.
bluetoothle.subscribe(subscribeSuccess, subscribeError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"serviceUuid": "180d",
"characteristicUuid": "2a37",
"isNotification" : true
}
{
"status": "subscribed",
"characteristicUuid": "2a37",
"name": "Polar H7 3B321015",
"serviceUuid": "180d",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
{
"status": "subscribedResult",
"value": "BkY=",
"characteristicUuid": "2a37",
"name": "Polar H7 3B321015",
"serviceUuid": "180d",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
Unsubscribe to a particular service's characteristic.
bluetoothle.unsubscribe(unsubscribeSuccess, unsubscribeError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"serviceUuid": "180d",
"characteristicUuid": "2a37"
}
{
"status": "unsubscribed",
"characteristicUuid": "2a37",
"name": "Polar H7 3B321015",
"serviceUuid": "180d",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
Write a particular service's characteristic.
bluetoothle.write(writeSuccess, writeError, params);
Value is a base64 encoded string of bytes to write. Use bluetoothle.bytesToEncodedString(bytes) to convert to base64 encoded string from a unit8Array. To write without response, set type to "noResponse". Any other value will default to write with response. Note, no callback will occur on write without response.
//Note, this example doesn't actually work since it's read only characteristic
{"value":"","serviceUuid":"180F","characteristicUuid":"2A19","type":"noResponse"}
Value is a base64 encoded string of written bytes. Use bluetoothle.encodedStringToBytes(obj.value) to convert to a unit8Array. See characteristic's specification and example below on how to correctly parse this.
//Write
{"status":"written","serviceUuid":"180F","characteristicUuid":"2A19","value":""}
Read a particular characterist's descriptor
bluetoothle.read(readDescriptorSuccess, readDescriptorError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"serviceUuid": "180d",
"characteristicUuid": "2a37",
"descriptorUuid": "2902"
}
{
"status": "readDescriptor",
"serviceUuid": "180d",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"characteristicUuid": "2a37",
"value": "AQAAAAAAAAA=",
"name": "Polar H7 3B321015",
"descriptorUuid": "2902"
}```
### writeDescriptor ### Needs Update
Write a particular characteristic's descriptor. Unable to write characteristic configuration directly to keep in line with iOS implementation. Instead use subscribe/unsubscribe, which will automatically enable/disable notification. ***Note, limited testing and likely needs to be made more generic***
```javascript
bluetoothle.write(writeDescriptorSuccess, writeDescriptorError, params);
Value is a base64 encoded string of bytes to write. Use bluetoothle.bytesToEncodedString(bytes) to convert to base64 encoded string from a unit8Array.
{"serviceUuid":"180D","characteristicUuid":"2A37","descriptorUuid":"2902","value":"EnableNotification"}
Value is a base64 encoded string of written bytes. Use bluetoothle.encodedStringToBytes(obj.value) to convert to a unit8Array.
{"status":"writeDescriptor","serviceUuid":"180D","characteristicUuid":"2A37", "descriptorUuid":"2902","value":"EnableNotification"}
Read RSSI of a connected device. RSSI is also returned with scanning.
bluetoothle.rssi(rssiSuccess, rssiError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
{
"status": "rssi",
"rssi": -50,
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
Determine whether the adapter is initialized. No error callback. Returns true or false
bluetoothle.isInitialized(isInitialized);
{
"isInitialized": true
}
Determine whether the adapter is enabled. No error callback
bluetoothle.isEnabled(isEnabled);
{
"isEnabled": true
}
Determine whether the adapter is initialized. No error callback. Returns true or false
bluetoothle.isScanning(isScanning);
{
"isScanning": false
}
Determine whether the device is connected. No error callback. Returns true or false
bluetoothle.isConnected(isConnected);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
{
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"isConnected": false
}
Determine whether the device's characteristics and descriptors have been discovered. No error callback. Android support only. iOS will return false.
bluetoothle.isDiscovered(isDiscovered);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
{
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"isDiscovered": false
}
Request a change in the connection priority to improve throughput when transfer large amounts of data via BLE. Android support only. iOS will return error.
bluetoothle.requestConnectionPriority(success, error, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"connectionPriority" : "balanced"
}
{
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"status" : "connectionPriorityRequested"
}
Helper function to convert a base64 encoded string from a characteristic or descriptor value into a uint8Array object.
bluetoothle.encodedStringToBytes(string);
Helper function to convert a unit8Array to a base64 encoded string for a characteric or descriptor write.
bluetoothle.bytesToEncodedString(bytes);
Helper function to convert a string to bytes.
bluetoothle.stringToBytes(string);
Helper function to convert bytes to a string.
bluetoothle.bytesToString(bytes);
if (obj.status == "subscribedResult")
{
//Turn the base64 string into an array of unsigned 8bit integers
var bytes = bluetoothle.encodedStringToBytes(obj.value);
if (bytes.length === 0)
{
return;
}
//NOTE: Follow along to understand how the parsing works
//https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml
//First byte provides instructions on what to do with the remaining bytes
var flag = bytes[0];
//Offset from beginning of the array
var offset = 1;
//If the first bit of the flag is set, the HR is in 16 bit form
if ((flag & 0x01) == 1)
{
//Extract second and third bytes and convert to 16bit unsigned integer
var u16bytesHr = bytes.buffer.slice(offset, offset + 2);
var u16Hr = new Uint16Array(u16bytesHr)[0];
//16 bits takes up 2 bytes, so increase offset by two
offset += 2;
}
//Else the HR is in 8 bit form
else
{
//Extract second byte and convert to 8bit unsigned integer
var u8bytesHr = bytes.buffer.slice(offset, offset + 1);
var u8Hr = new Uint8Array(u8bytesHr)[0];
//Or I believe I could just do this: var u8Hr = u8bytesHr[offset]
//8 bits takes up 1 byte, so increase offset by one
offset += 1;
}
//NOTE: I'm ignoring the second and third bit because I'm not interested in the sensor contact, and it doesn't affect the offset
//If the fourth bit is set, increase the offset to skip over the energy expended information
if ((flag & 0x08) == 8)
{
offset += 2;
}
//If the fifth bit is set, get the RR interval(s)
if ((flag & 0x10) == 16)
{
//Number of rr intervals
var rrCount = (bytes.length - offset) / 2;
for (var i = rrCount - 1; i >= 0; i--)
{
//Cast to 16 bit unsigned int
var u16bytesRr = bytes.buffer.slice(offset, offset + 2);
var u16Rr = new Uint16Array(u16bytesRr)[0];
//Increase offset
offset += 2;
}
}
}
The source files included in the repository are released under the Apache License, Version 2.0.
FAQs
Use the Bluetooth Low Energy plugin to connect your Cordova app to new Bluetooth devices like heart rate monitors, thermometers, etc...
We found that com.randdusing.bluetoothle demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Critics call the Node.js EOL CVE a misuse of the system, sparking debate over CVE standards and the growing noise in vulnerability databases.
Security News
cURL and Go security teams are publicly rejecting CVSS as flawed for assessing vulnerabilities and are calling for more accurate, context-aware approaches.
Security News
Bun 1.2 enhances its JavaScript runtime with 90% Node.js compatibility, built-in S3 and Postgres support, HTML Imports, and faster, cloud-first performance.