This plugin allows you to interact with Bluetooth LE devices on Android, iOS, and Windows.
Check out ng-cordova-bluetoothle here!
If timeouts are needed, please check out the Angular wrapper and its example.
Cordova
cordova plugin add cordova-plugin-bluetoothle
PhoneGap Build
<gap:plugin name="cordova-plugin-bluetoothle" source="npm" />
Check out these guides for lower level debugging on Android and iOS:
Apps like LightBlue are great for verifying Bluetooth LE behavior.
The latest version of the plugin requires you to set the Android target API to a minimum of 23 to support permission requirements for scanning. If you can't target 23, please use plugin version 2.4.0 or below.
Background mode(s) are no longer added to your project's plist file by default. They can be added manually by editing the plist file, or you can use the following plugins: cordova-plugin-background-mode-bluetooth-central and/or cordova-plugin-background-mode-bluetooth-peripheral.
Scanning works differently in the background. There seem to be three different states:
iOS now requires a usage description in the plist file. Use the following plugin to easily customize it: cordova-plugin-bluetooth-peripheral-usage-description
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 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. iOS now supports Android style discovery, but use with caution. It's a bit buggy on iOS8, but seems to work fine on iOS9.
Read, write, subscribe, unsubscribe, readDescriptor and writeDescriptor queueing has been added to the master branch and will be part of the 4.1.0 release. If you'd like to try it out, install the plugin directly from GitHub using: cordova plugin https://github.com/randdusing/cordova-plugin-bluetoothle
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 Bluetooth Developer Portal
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 StackOverflow posts for more info: here and here
Advertisement data is not supported on Windows 10 UWP.
Neither Android nor iOS support Bluetooth on emulators, so you'll need to test on a real device.
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 permissions: read, readEncrypted, readEncryptedMITM, write, writeEncrypted, writeEncryptedMITM, writeSigned, writeSignedMITM. Unfortuately, the getProperties() call always seems to return 0, which means no properties are set. Not sure if this is an issue with my mobile device or that all the Bluetooth devices just don't have the properties set. If the characteristic has a permission, it will exist as a key in the characteristic's permissions object. See discovery().
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()
Android Docs and iOS Docs
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. The restoreKey
property requires using the Bluetooth Central background mode. This function should only be called once.
bluetoothle.initialize(initializeResult, params);
{
"request": true,
"statusReceiver": false,
"restoreKey" : "bluetoothleplugin"
}
{
"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.
Retrieve useful information such as the address, name, and various states (initialized, enabled, scanning, discoverable). This can be very useful when the general state of the adapter has been lost, and we would otherwise need to go through a series of callbacks to get the correct state (first initialized, then enabled, then isScanning, and so forth). The result of this method allows us to take business logic decisions while avoiding a large part of the callback hell.
Currently the discoverable state does not have any relevance because there is no "setDiscoverable" functionality in place. That may change in the future.
bluetoothle.getAdapterInfo(successCallback);
The successCallback contains the following properties:
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. Android API >= 23 requires ACCESS_COARSE_LOCATION permissions to find unpaired devices. Permissions can be requested by using the hasPermission and requestPermission functions. Android API >= 23 also requires location services to be enabled. Use isLocationEnabled
to determine whether location services are enabled. If not enabled, use requestLocation
to prompt the location services settings page. Android API >= 31 also requires BLUETOOTH_SCAN permissions to perform scanning. You can use hasPermissionBtScan
to determine whether scanning permission is granted or use requestPermissionBtScan
to prompt for it.
bluetoothle.startScan(startScanSuccess, startScanError, params);
{
"services": [
"180D",
"180F"
],
"allowDuplicates": true,
"scanMode": bluetoothle.SCAN_MODE_LOW_LATENCY,
"matchMode": bluetoothle.MATCH_MODE_AGGRESSIVE,
"matchNum": bluetoothle.MATCH_NUM_MAX_ADVERTISEMENT,
"callbackType": bluetoothle.CALLBACK_TYPE_ALL_MATCHES,
}
{
"status": "scanStarted"
}
{
"status": "scanResult",
"advertisement": "awArG05L", //Android
"advertisement": { //iOS
"serviceUuids": [
"180D"
],
"manufacturerData": "awAvFFZY",
"txPowerLevel": 0,
"overflowServiceUuids": [
],
"isConnectable": true,
"solicitedServiceUuids": [
],
"serviceData": {
},
"localName": "Polar H7 3B321015"
},
"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 app should use a timer to limit the scanning time.
bluetoothle.stopScan(stopScanSuccess, stopScanError);
{
"status": "scanStopped"
}
Retrieved paired Bluetooth LE devices. Yes, this function should be renamed, but I went with iOS's naming. 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 BLE devices.
bluetoothle.retrieveConnected(retrieveConnectedSuccess, retrieveConnectedError, params);
{
"services": [
"180D",
"180F"
]
}
An array of device objects:
[
{
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
]
Bond with a device. The first success callback should always return with status == bonding
. If the bond is created, the callback will return again with status == bonded
. If the bonding popup is canceled or the wrong code is entered, the callback will return again with status == unbonded
. The device doesn't need to be connected to initiate bonding. Android support only.
bluetoothle.bond(bondSuccess, bondError, params);
{
"address": "5A:94:4B:38:B3:FD"
}
{
"name": "Hello World",
"address": "5A:94:4B:38:B3:FD",
"status": "bonded"
}
{
"name": "Hello World",
"address": "5A:94:4B:38:B3:FD",
"status": "bonding"
}
{
"name": "Hello World",
"address": "5A:94:4B:38:B3:FD",
"status": "unbonded"
}
Unbond with a device. The success callback should always return with status == unbonded
. The device doesn't need to be connected to initiate bonding. Android support only.
bluetoothle.unbond(unbondSuccess, unbondError, params);
{
"address": "5A:94:4B:38:B3:FD"
}
{
"name": "Hello World",
"address": "5A:94:4B:38:B3:FD",
"status": "unbonded"
}
Connect to a Bluetooth LE device. The 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. If a timeout occurs, the connection attempt should be canceled using disconnect(). For simplicity, I recommend just using connect() and close(), don't use reconnect() or disconnect(). Android API >= 31 requires BLUETOOTH_CONNECT permissions to connect to devices. You can use hasPermissionBtConnect
to determine whether connect permission is granted or use requestPermissionBtConnect
to prompt for it.
bluetoothle.connect(connectSuccess, connectError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
{
"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 app should use a timer to limit the connecting time. If a timeout occurs, the reconnection attempt should be canceled using disconnect() or close().
bluetoothle.reconnect(reconnectSuccess, reconnectError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
{
"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. It's simpler to just call close(). Starting with iOS 10, disconnecting before closing seems required!
bluetoothle.disconnect(disconnectSuccess, disconnectError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
{
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"status": "disconnected"
}
Close/dispose a Bluetooth LE device. Prior to 2.7.0, you needed to disconnect to the device before closing, but this is no longer the case. Starting with iOS 10, disconnecting before closing seems required!
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. If using iOS, you shouldn't use discover and services/characteristics/descriptors on the same device. There seems to be an issue with calling discover on iOS8 devices, so use with caution. On some Android versions, the discovered services may be cached for a device. Subsequent discover events will make use of this cache. If your device's services change, set the clearCache parameter to force Android to re-discover services.
bluetoothle.discover(discoverSuccess, discoverError, params);
{
"address": "00:22:D0:3B:32:10",
"clearCache": true
}
Device Object:
Service Object:
Characteristic Object:
Descriptor Object:
{
"address": "00:22:D0:3B:32:10",
"status": "discovered",
"services": [
{
"characteristics": [
{
"descriptors": [
],
"uuid": "2a00", // [Device Name](https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.device_name.xml)
"properties": {
"write": true,
"writeWithoutResponse": true,
"read": true
}
},
{
"descriptors": [
],
"uuid": "2a01", // [Appearance](https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml)
"properties": {
"read": true
}
},
{
"descriptors": [
],
"uuid": "2a02", // [Peripheral Privacy Flag](https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.peripheral_privacy_flag.xml)
"properties": {
"read": true
}
},
{
"descriptors": [
],
"uuid": "2a03", // [Reconnection Address](https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.reconnection_address.xml)
"properties": {
"write": true
}
},
{
"descriptors": [
],
"uuid": "2a04", // [Pheripheral Preferred Connection Parameters](https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.peripheral_preferred_connection_parameters.xml)
"properties": {
"read": true
}
}
],
"uuid": "1800" // [Generic Access](https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.generic_access.xml)
},
{
"characteristics": [
{
"descriptors": [
{
"uuid": "2902"
}
],
"uuid": "2a05", // [Service Changed](https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gatt.service_changed.xml)
"properties": {
"indicate": true
}
}
],
"uuid": "1801" // [Generic Attribute](https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.generic_attribute.xml)
},
{
"characteristics": [
{
"descriptors": [
{
"uuid": "2902"
}
],
"uuid": "2a37", // [Heart Rate Measurement](https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml)
"properties": {
"notify": true
}
},
{
"descriptors": [
],
"uuid": "2a38", // [Body Sensor Location](https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.body_sensor_location.xml)
"properties": {
"read": true
}
}
],
"uuid": "180d" // [Heart Rate](https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.heart_rate.xml)
},
{
"characteristics": [
{
"descriptors": [
],
"uuid": "2a23", // [System ID](https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.system_id.xml)
"properties": {
"read": true
}
},
{
"descriptors": [
],
"uuid": "2a24", // [Model Number String](https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.model_number_string.xml)
"properties": {
"read": true
}
},
{
"descriptors": [
],
"uuid": "2a25", // [Serial Number String](https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.serial_number_string.xml)
"properties": {
"read": true
}
},
{
"descriptors": [
],
"uuid": "2a26", // [Firmware Revision String](https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.firmware_revision_string.xml)
"properties": {
"read": true
}
},
{
"descriptors": [
],
"uuid": "2a27", // [hardware Revision String](https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.hardware_revision_string.xml)
"properties": {
"read": true
}
},
{
"descriptors": [
],
"uuid": "2a28", // [Software Revision String](https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.software_revision_string.xml)
"properties": {
"read": true
}
},
{
"descriptors": [
],
"uuid": "2a29", // [Manufacturer Name String](https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.manufacturer_name_string.xml)
"properties": {
"read": true
}
}
],
"uuid": "180a" // [Device Information](https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.device_information.xml)
},
{
"characteristics": [
{
"descriptors": [
],
"uuid": "2a19", // [Battery Level](https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.battery_level.xml)
"properties": {
"read": true
}
}
],
"uuid": "180f" // [Battery Service](https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.battery_service.xml)
},
{
"characteristics": [
{
"descriptors": [
],
"uuid": "6217ff4c-c8ec-b1fb-1380-3ad986708e2d",
"properties": {
"read": true
}
},
{
"descriptors": [
{
"uuid": "2902"
}
],
"uuid": "6217ff4d-91bb-91d0-7e2a-7cd3bda8a1f3",
"properties": {
"write": true,
"indicate": true
}
}
],
"uuid": "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",
"services": [
]
}
{
"status": "services",
"services": [
"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",
"service": "180d",
"characteristics": [
]
}
{
"status": "characteristics",
"characteristics": [
{
"properties": {
"notify": true
},
"uuid": "2a37"
},
{
"properties": {
"read": true
},
"uuid": "2a38"
}
],
"name": "Polar H7 3B321015",
"service": "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",
"service": "180d",
"characteristic": "2a37"
}
{
"status": "descriptors",
"descriptors": [
"2902"
],
"characteristic": "2a37",
"name": "Polar H7 3B321015",
"service": "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",
"service": "180d",
"characteristic": "2a38"
}
{
"status": "read",
"value": "UmVhZCBIZWxsbyBXb3JsZA==", //Read Hello World
"characteristic": "2a38",
"name": "Polar H7 3B321015",
"service": "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 based on the characteristic's properties.
bluetoothle.subscribe(subscribeSuccess, subscribeError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"service": "180d",
"characteristic": "2a37",
}
{
"status": "subscribed",
"characteristic": "2a37",
"characteristicIndex": 0,
"name": "Polar H7 3B321015",
"service": "180d",
"serviceIndex": 0,
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
{
"status": "subscribedResult",
"value": "U3Vic2NyaWJlIEhlbGxvIFdvcmxk", //Subscribe Hello World
"characteristic": "2a37",
"characteristicIndex": 0,
"name": "Polar H7 3B321015",
"service": "180d",
"serviceIndex": 0,
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
Unsubscribe to a particular service's characteristic.
bluetoothle.unsubscribe(unsubscribeSuccess, unsubscribeError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"service": "180d",
"characteristic": "2a37"
}
{
"status": "unsubscribed",
"characteristic": "2a37",
"name": "Polar H7 3B321015",
"service": "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 on iOS.
var string = "Write Hello World";
var bytes = bluetoothle.stringToBytes(string);
var encodedString = bluetoothle.bytesToEncodedString(bytes);
// if your code includes special characters you should use the encodeUnicode helper function
var encodedUnicodeString = bluetoothle.encodeUnicode(string);
//Note, this example doesn't actually work since it's read only characteristic
{"value":"V3JpdGUgSGVsbG8gV29ybGQ=","service":"180F","characteristic":"2A19","type":"noResponse","address":"ABC123"}
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.
var returnObj = {"status":"written","service":"180F","characteristic":"2A19","value":"V3JpdGUgSGVsbG8gV29ybGQ=","address":"ABC123"}
var bytes = bluetoothle.encodedStringToBytes(returnObj.value);
var string = bluetoothle.bytesToString(bytes); //This should equal Write Hello World
// if your code includes special characters you should use the decodeUnicode helper function
var string = bluetoothle.decodeUnicode(returnObj.value);
Write Quick / Queue, use this method to quickly execute write without response commands when writing more than 20 bytes at a time. The data will automatically be split up into 20 bytes packets by default or you can increase that by setting chunkSize
. On iOS, these packets are written immediately since iOS uses queues. You probably won't see much of a performance increase using writeQ unless you use type="noResponse"
and set chunkSize
higher than 20. On Android, a queue isn't used internally. Instead another call shouldn't be made until onCharacteristicWrite is called. This could be done at the Javascript layer, but the Javascript to plugin "bridge" must be crossed twice, which leads to some significant slow downs when milliseconds make a difference. For even better write throughput, use requestConnectionPriority('high') and mtu(SAME_VALUE_AS_CHUNK_SIZE_PARAM) as well.
Warnings
MTU_VALUE
) and then calling this method with type="noResponse"
and set chunkSize
to MTU_VALUE
.bluetoothle.writeQ(writeSuccess, writeError, params);
See write() above.
Read a particular characterist's descriptor
bluetoothle.read(readDescriptorSuccess, readDescriptorError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"service": "180d",
"characteristic": "2a37",
"descriptor": "2902"
}
{
"status": "readDescriptor",
"service": "180d",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"characteristic": "2a37",
"value": "AQAAAAAAAAA=",
"name": "Polar H7 3B321015",
"descriptor": "2902"
}
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.
bluetoothle.writeDescriptor(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.
var string = "Hello World";
var bytes = bluetoothle.stringToBytes(string);
var encodedString = bluetoothle.bytesToEncodedString(bytes);
{"service":"180D","characteristic":"2A37","descriptor":"2902","value":"AQAAAAAAAAA=","address":"ABC123"}
Value is a base64 encoded string of written bytes. Use bluetoothle.encodedStringToBytes(obj.value) to convert to a unit8Array.
{"status":"writeDescriptor","service":"180D","characteristic":"2A37", "descriptor":"2902","value":"AQAAAAAAAAA=","address":"ABC123"}
var bytes = bluetoothle.encodedStringToBytes(returnObj.value);
var string = bluetoothle.bytesToString(bytes); //This should equal Hello World!
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"
}
Set MTU of a connected device. Android only.
bluetoothle.mtu(mtuSuccess, mtuError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"mtu" : 50
}
{
"status": "mtu",
"mtu": 50,
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
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"
}
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 bonded or not, or error if not initialized. Android support only.
bluetoothle.isBonded(isBondedSuccess, isBondedError, params);
{
"address": "5A:94:4B:38:B3:FD"
}
{
"name": "Polar H7 3B321015",
"address": "5A:94:4B:38:B3:FD",
"isBonded": false
}
Set PIN if required by the pairing process. Android support only.
bluetoothle.setPin(success, error, params);
{
"address": "5A:94:4B:38:B3:FD",
"pin": "1234"
}
{
"status": "pinSet",
}
Determine whether the device was connected, or error if not initialized.
bluetoothle.wasConnected(wasConnectedSuccess, wasConnectedError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
{
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"wasConnected": false
}
Determine whether the device is connected, or error if not initialized or never connected to device.
bluetoothle.isConnected(isConnectedSuccess, isConnectedError, params);
{
"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, or error if not initialized or not connected to device. Note, on Android, you can connect, discover and then disconnect. isDiscovered will return an error due to the device not being connected. But if you call reconnect and call isDiscovered again, it will return isDiscovered => true since the device stays discovered until calling close().
bluetoothle.isDiscovered(isDiscoveredSuccess, isDiscoveredError, params);
{
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
{
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
"isDiscovered": false
}
Determine whether coarse location privileges are granted since scanning for unpaired devices requies it in Android API 23
bluetoothle.hasPermission(hasPermissionSuccess);
{
"hasPermission": true
}
Request coarse location privileges since scanning for unpaired devices requires it in Android API 23. Will return an error if called on iOS or Android versions prior to 6.0.
bluetoothle.requestPermission(requestPermissionSuccess, requestPermissionError);
{
"requestPermission": true
}
Determine whether Bluetooth scanning privileges are granted since scanning for unpaired devices requies it in Android API 31
bluetoothle.hasPermissionBtScan(hasPermissionSuccess);
{
"hasPermission": true
}
Request Bluetooth scanning privileges since scanning for unpaired devices requires it in Android API 31. Will return an error if called on iOS or Android versions prior to 6.0.
bluetoothle.requestPermissionBtScan(requestPermissionSuccess, requestPermissionError);
{
"requestPermission": true
}
Determine whether Bluetooth connect privileges are granted since connecting to unpaired devices requies it in Android API 31
bluetoothle.hasPermissionBtConnect(hasPermissionSuccess);
{
"hasPermission": true
}
Request Bluetooth connect privileges since connecting to unpaired devices requires it in Android API 31. Will return an error if called on iOS or Android versions prior to 6.0.
bluetoothle.requestPermissionBtConnect(requestPermissionSuccess, requestPermissionError);
{
"requestPermission": true
}
Determine whether Bluetooth advertise privileges are granted since making the current device discoverable requies it in Android API 31
bluetoothle.hasPermissionBtAdvertise(hasPermissionSuccess);
{
"hasPermission": true
}
Request Bluetooth advertise privileges since making the current device discoverable requires it in Android API 31. Will return an error if called on iOS or Android versions prior to 6.0.
bluetoothle.requestPermissionBtAdvertise(requestPermissionSuccess, requestPermissionError);
{
"requestPermission": true
}
Determine if location services are enabled or not. Location Services are required to find devices in Android API 23.
bluetoothle.isLocationEnabled(isLocationEnabledSuccess, isLocationEnabledError);
{
"isLocationEnabled": true
}
Prompt location services settings pages. requestLocation
property returns whether location services are enabled or disabled. Location Services are required to find devices in Android API 23.
bluetoothle.requestLocation(requestLocationSuccess, requestLocationError);
{
"requestLocation": true
}
Retrieve paired Bluetooth LE devices based on their address. Wraps the iOS method CBCentralManager.retrievePeripheralsWithIdentifiers. iOS support only. Will return an error if used on Android.
bluetoothle.retrievePeripheralsByAddress(success, error, params);
{
"addresses": ["ECC037FD-72AE-AFC5-9213-CA785B3B5C63"]
}
Returns an array of device objects:
[
{
"name": "Polar H7 3B321015",
"address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
]
Initialization works slightly different between Android and iOS. On iOS, you don't need to call intialize() if only acting as a peripheral, just initializePeripheral. On Android, you must always call initialize() before calling initializePeripheral().
Notifications work slightly differently between Android and iOS. On Android, you should wait for the notificationSent
event before calling notify() again. On iOS, you need to check the notify() callback for the sent property. If the sent property is set to false, you should wait until receiving the peripheralManagerIsReadyToUpdateSubscribers
event to resend the notification. In future versions, I hope to standardize the functionality between platforms.
iOS doesn't allow you to respond to read and write descriptor requests. Instead it only provides methods for when a client subscribes or unsubscribes. On Android, read and write descriptor requests are provided. If the write descriptor request is made on the Client Configuration Descriptor (used for subscriptions), a subscribe or unsubscribe event will be received instead of writeDescriptorRequested.
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 readd services, start advertising, 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. The restoreKey
property requires using the Bluetooth Peripheral background mode. This function should only be called once.
Additionally this where new events are delivered for read, write, and subscription requests. See the success section for more details.
bluetoothle.initializePeripheral(success, error, params);
{
"request": true
"restoreKey": "bluetoothleplugin"
}
{
"status": "enabled"
}
{
"status":"readRequested",
"address":"5163F1E0-5341-AF9B-9F67-613E15EC83F7",
"service":"1234",
"characteristic":"ABCD",
"requestId":0, //This integer value will be incremented every read/writeRequested
"offset":0
}
{
"status":"writeRequested",
"address":"5163F1E0-5341-AF9B-9F67-613E15EC83F7",
"service":"1234",
"characteristic":"ABCD",
"requestId":1, //This integer value will be incremented every read/writeRequested
"value":"V3JpdGUgSGVsbG8gV29ybGQ=", //Write Hello World
"offset":0
}
{
"status":"subscribed",
"address":"5163F1E0-5341-AF9B-9F67-613E15EC83F7",
"service":"1234",
"characteristic":"ABCD"
}
{
"status":"unsubscribed",
"address":"5163F1E0-5341-AF9B-9F67-613E15EC83F7",
"service":"1234",
"characteristic":"ABCD"
}
{
"status":"notificationReady"
}
{
"status":"connected",
"address":"5163F1E0-5341-AF9B-9F67-613E15EC83F7",
}
{
"status":"disconnected",
"address":"5163F1E0-5341-AF9B-9F67-613E15EC83F7",
}
{
"status":"mtuChanged",
"address":"5163F1E0-5341-AF9B-9F67-613E15EC83F7",
"mtu":20,
}
Add a service with characteristics and descriptors. If more than one service is added, add them sequentially.
bluetoothle.addService(success, error, params);
var params = {
service: "1234",
characteristics: [
{
uuid: "ABCD",
permissions: {
read: true,
write: true,
//readEncryptionRequired: true,
//writeEncryptionRequired: true,
},
properties : {
read: true,
writeWithoutResponse: true,
write: true,
notify: true,
indicate: true,
//authenticatedSignedWrites: true,
//notifyEncryptionRequired: true,
//indicateEncryptionRequired: true,
}
}
]
};
{
"service":"1234",
"status":"serviceAdded"
}
Remove a service.
bluetoothle.removeService(success, error, params);
var params = {
service: "1234",
};
{
"service":"1234",
"status":"serviceRemoved"
}
Remove all services
bluetoothle.removeAllServices(success, error);
{
"status":"allServicesRemoved"
}
Start advertising as a BLE device. Note: This needs to be improved so services can be used for both Android and iOS.
On iOS, the advertising devices likes to rename itself back to the name of the device, i.e. Rand' iPhone.
Android API >= 31 also requires BLUETOOTH_ADVERTISE permissions to perform advertising. You can use hasPermissionBtAdvertise
to determine whether advertise permission is granted or use requestPermissionBtAdvertise
to prompt for it.
bluetoothle.startAdvertising(success, error, params);
var params = {
"services":["1234"], //iOS
"service":"1234", //Android
"name":"Hello World",
};
{
"status":"advertisingStarted"
}
Stop advertising
bluetoothle.stopAdvertising(success, error);
{
"status":"advertisingStopped"
}
Determine if app is advertising or not.
bluetoothle.isAdvertising(success, error);
{
"isAdvertising":true
}
Respond to a read or write request. On Android, a device address is required
bluetoothle.respond(success, error, params);
//This was a read
var params = {
"requestId":0,
"value":"UmVhZCBIZWxsbyBXb3JsZA==" //Read Hello World
};
//This was a write
var params = {
"requestId":1,
"value":"V3JpdGUgSGVsbG8gV29ybGQ=" //Write Hello World
};
{
"status":"responded"
}
Update a value for a subscription. Currently all subscribed devices will receive updates on iOS. Device specific updates will be added in the future. On Android, a device address is required. If sent
equals false in the return value, you must wait for the peripheralManagerIsReadyToUpdateSubscribers
event before sending more updates.
bluetoothle.notify(success, error, params);
var params = {
"service":"1234",
"characteristic":"ABCD",
"value":"U3Vic2NyaWJlIEhlbGxvIFdvcmxk" //Subscribe Hello World
// "address": "5163F1E0-5341-AF9B-9F67-613E15EC83F7" // only on android
};
{
"status":"notified",
"sent":true
}
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);
See the example provided with the Angular Wrapper
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;
}
}
}
Helper function to convert unicode string to base64 encoded string. This function can be used to encode special characters such as emojis.
bluetoothle.encodeUnicode(string);
Helper function to convert a base64 encoded string to unicode string. This function also decodes special characters such as emojis.
bluetoothle.decodeUnicode(string);
We'll build an app that lets you discover Bluetooth Low Energy (LE) devices that are around you, connect to a one, and then look at all of the information that you can obtain from that device such as signal strength, supported services, battery level and more.
You could use an app like this to find a lost device or to debug a device that isn't behaving as expected.
Our code performs these tasks.
We'll build this app for Android and Windows devices.
Also, we'll use a Promise object for each of the Bluetooth LE functions. If you're unfamiliar with promises, it's just a cleaner way to organize asynchronous functions. You can read more about them here.
If you're ready to go, let's start.
The BluetoothLE plugin uses an adapter to interact with each device's Bluetooth LE capability so you'll have to initialize it. To do that, call the initialize function.
document.addEventListener('deviceready', function () {
new Promise(function (resolve) {
bluetoothle.initialize(resolve, { request: true, statusReceiver: false });
}).then(initializeSuccess, handleError);
});
If your call succeeds, use result.status
property to find out if Bluetooth is enabled on their device.
function initializeSuccess(result) {
if (result.status === "enabled") {
log("Bluetooth is enabled.");
log(result);
}
else {
document.getElementById("start-scan").disabled = true;
log("Bluetooth is not enabled:", "status");
log(result, "status");
}
}
If your call fails, you can find out why by using the error
object. This code shows one way to do that. We'll re-use this function throughout this example.
function handleError(error) {
var msg;
if (error.error && error.message) {
var errorItems = [];
if (error.service) {
errorItems.push("service: " + (uuids[error.service] || error.service));
}
if (error.characteristic) {
errorItems.push("characteristic: " + (uuids[error.characteristic] || error.characteristic));
}
msg = "Error on " + error.error + ": " + error.message + (errorItems.length && (" (" + errorItems.join(", ") + ")"));
}
else {
msg = error;
}
log(msg, "error");
if (error.error === "read" && error.service && error.characteristic) {
reportValue(error.service, error.characteristic, "Error: " + error.message);
}
}
The block of code above calls a function named log
. It's just a helper function that shows one of many ways to show output to the users.
function log(msg, level) {
level = level || "log";
if (typeof msg === "object") {
msg = JSON.stringify(msg, null, " ");
}
console.log(msg);
if (level === "status" || level === "error") {
var msgDiv = document.createElement("div");
msgDiv.textContent = msg;
if (level === "error") {
msgDiv.style.color = "red";
}
msgDiv.style.padding = "5px 0";
msgDiv.style.borderBottom = "rgb(192,192,192) solid 1px";
document.getElementById("output").appendChild(msgDiv);
}
}
Call the startScan function to find Bluetooth LE devices that are around you. This function succeeds for iOS and Android devices but not for Windows. So why is that?
It turns out that Windows devices detect only those Bluetooth LE devices that are paired to it. For Windows devices, call the retrieveConnected function.
var foundDevices = [];
function startScan() {
log("Starting scan for devices...", "status");
foundDevices = [];
document.getElementById("devices").innerHTML = "";
document.getElementById("services").innerHTML = "";
document.getElementById("output").innerHTML = "";
if (window.cordova.platformId === "windows") {
bluetoothle.retrieveConnected(retrieveConnectedSuccess, handleError, {});
}
else {
bluetoothle.startScan(startScanSuccess, handleError, { services: [] });
}
}
Every time that a Bluetooth LE device is detected, the startScanSuccess
callback function is called. In that function, use the result
object to get information about the device.
In this example, we'll add each result
object to an array. We use this array to detect duplicates. We'll compare the MAC address of the current result
to all result
objects in the array before we add it.
After we've determined that the detected device is unique, we'll call a helper function named addDevice
to show that device as a button on the screen. We'll take a look at that function shortly.
function startScanSuccess(result) {
log("startScanSuccess(" + result.status + ")");
if (result.status === "scanStarted") {
log("Scanning for devices (will continue to scan until you select a device)...", "status");
}
else if (result.status === "scanResult") {
if (!foundDevices.some(function (device) {
return device.address === result.address;
})) {
log('FOUND DEVICE:');
log(result);
foundDevices.push(result);
addDevice(result.name, result.address);
}
}
}
Remember that Windows devices detect only those Bluetooth LE devices that are paired to it, so we called the retrieveConnected function to get paired devices.
If the function succeeds, we get an array of result
objects.
In this example, we iterate through that array and then call a helper function named addDevice
to show that device as a button on the screen.
function retrieveConnectedSuccess(result) {
log("retrieveConnectedSuccess()");
log(result);
result.forEach(function (device) {
addDevice(device.name, device.address);
});
}
This helper function adds a button for each available device. The click
event of each button calls a helper function named connect
. We'll define that function in the next section.
function addDevice(name, address) {
var button = document.createElement("button");
button.style.width = "100%";
button.style.padding = "10px";
button.style.fontSize = "16px";
button.textContent = name + ": " + address;
button.addEventListener("click", function () {
document.getElementById("services").innerHTML = "";
connect(address);
});
document.getElementById("devices").appendChild(button);
}
If the user clicks a button for any of the devices, the connect
helper function is called. In that function, we'll call the connect function of the Bluetooth LE plugin.
If the user has a Windows device, we'll call a helper function named getDeviceServices
to get information about that device's services. We don't have to connect to it first because we know that if it appears in the list, that device is already paired.
function connect(address) {
log('Connecting to device: ' + address + "...", "status");
if (cordova.platformId === "windows") {
getDeviceServices(address);
}
else {
stopScan();
new Promise(function (resolve, reject) {
bluetoothle.connect(resolve, reject, { address: address });
}).then(connectSuccess, handleError);
}
}
function stopScan() {
new Promise(function (resolve, reject) {
bluetoothle.stopScan(resolve, reject);
}).then(stopScanSuccess, handleError);
}
function stopScanSuccess() {
if (!foundDevices.length) {
log("NO DEVICES FOUND");
}
else {
log("Found " + foundDevices.length + " devices.", "status");
}
}
If the call to the connect function succeeds, use the result.status
property to find out if you've managed to connect.
In this example, if we're connected to the Bluethooth LE device, we'll call a helper function named getDeviceServices
to get information about that device's services
function connectSuccess(result) {
log("- " + result.status);
if (result.status === "connected") {
getDeviceServices(result.address);
}
else if (result.status === "disconnected") {
log("Disconnected from device: " + result.address, "status");
}
}
Now we'll take a look at that helper function named getDeviceServices
that we referred to above. In this method we'll call either the discover function or the services depending on the platform of users device.
For Android devices, call the discover function to find the services that are available on the Bluetooth LE device.
For Windows devices, you can use the services function to get straight to the services available on the paired device.
function getDeviceServices(address) {
log("Getting device services...", "status");
var platform = window.cordova.platformId;
if (platform === "android") {
new Promise(function (resolve, reject) {
bluetoothle.discover(resolve, reject,
{ address: address });
}).then(discoverSuccess, handleError);
}
else if (platform === "windows") {
new Promise(function (resolve, reject) {
bluetoothle.services(resolve, reject,
{ address: address });
}).then(servicesSuccess, handleError);
}
else {
log("Unsupported platform: '" + window.cordova.platformId + "'", "error");
}
}
If the call to the discover function succeeds, we'll get an array of services.
In this example, we'll call a helper function named addService
for each service in that array.
That function will show all of the characteristics of the service. We'll look at that function a bit later.
function discoverSuccess(result) {
log("Discover returned with status: " + result.status);
if (result.status === "discovered") {
// Create a chain of read promises so we don't try to read a property until we've finished
// reading the previous property.
var readSequence = result.services.reduce(function (sequence, service) {
return sequence.then(function () {
return addService(result.address, service.uuid, service.characteristics);
});
}, Promise.resolve());
// Once we're done reading all the values, disconnect
readSequence.then(function () {
new Promise(function (resolve, reject) {
bluetoothle.disconnect(resolve, reject,
{ address: result.address });
}).then(connectSuccess, handleError);
});
}
}
If the call to the services function succeeds, we'll get an array of services.
we'll call the Characteristics function to get all of the characteristics of the service.
function servicesSuccess(result) {
log("servicesSuccess()");
log(result);
if (result.status === "services") {
var readSequence = result.services.reduce(function (sequence, service) {
return sequence.then(function () {
console.log('Executing promise for service: ' + service);
new Promise(function (resolve, reject) {
bluetoothle.characteristics(resolve, reject,
{ address: result.address, service: service });
}).then(characteristicsSuccess, handleError);
}, handleError);
}, Promise.resolve());
// Once we're done reading all the values, disconnect
readSequence.then(function () {
new Promise(function (resolve, reject) {
bluetoothle.disconnect(resolve, reject,
{ address: result.address });
}).then(connectSuccess, handleError);
});
}
if (result.status === "services") {
result.services.forEach(function (service) {
new Promise(function (resolve, reject) {
bluetoothle.characteristics(resolve, reject,
{ address: result.address, service: service });
}).then(characteristicsSuccess, handleError);
});
}
}
If the call to the characteristics function succeeds, we'll call a helper function named addService
.
That function will show all of the characteristics of the service. We'll look at that function in the next section.
function characteristicsSuccess(result) {
log("characteristicsSuccess()");
log(result);
if (result.status === "characteristics") {
return addService(result.address, result.service, result.characteristics);
}
}
The addService
helper method shows the details of each service and their characteristics. To show each characteristic, this function calls the read function.
The array of uuid values that is used in this example comes from a helper js file that contains the unique identifiers of all known characteristics. That file does not appear in this example, but the values in it come from this page on the Bluetooth web site: Characteristics.
function addService(address, serviceUuid, characteristics) {
log('Adding service ' + serviceUuid + '; characteristics:');
log(characteristics);
var readSequence = Promise.resolve();
var wrapperDiv = document.createElement("div");
wrapperDiv.className = "service-wrapper";
var serviceDiv = document.createElement("div");
serviceDiv.className = "service";
serviceDiv.textContent = uuids[serviceUuid] || serviceUuid;
wrapperDiv.appendChild(serviceDiv);
characteristics.forEach(function (characteristic) {
var characteristicDiv = document.createElement("div");
characteristicDiv.className = "characteristic";
var characteristicNameSpan = document.createElement("span");
characteristicNameSpan.textContent = (uuids[characteristic.uuid] || characteristic.uuid) + ":";
characteristicDiv.appendChild(characteristicNameSpan);
characteristicDiv.appendChild(document.createElement("br"));
var characteristicValueSpan = document.createElement("span");
characteristicValueSpan.id = serviceUuid + "." + characteristic.uuid;
characteristicValueSpan.style.color = "blue";
characteristicDiv.appendChild(characteristicValueSpan);
wrapperDiv.appendChild(characteristicDiv);
readSequence = readSequence.then(function () {
return new Promise(function (resolve, reject) {
bluetoothle.read(resolve, reject,
{ address: address, service: serviceUuid, characteristic: characteristic.uuid });
}).then(readSuccess, handleError);
});
});
document.getElementById("services").appendChild(wrapperDiv);
return readSequence;
}
If the call to the read function succeeds, we'll write the value of that characteristic to the app page.
function readSuccess(result) {
log("readSuccess():");
log(result);
if (result.status === "read") {
reportValue(result.service, result.characteristic, window.atob(result.value));
}
}
function reportValue(serviceUuid, characteristicUuid, value) {
document.getElementById(serviceUuid + "." + characteristicUuid).textContent = value;
}
That's it! Find the complete sample here: https://github.com/Microsoft/cordova-samples/tree/c3135acbf5e59c0b7fd0f7f097c07c57aa275974/cordova-plugin-bluetoothle.
The MIT License (MIT)
Copyright (c) 2016 Rand Dusing and contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.