Giter Club home page Giter Club logo

cordova-plugin-bluetoothle's Introduction

Cordova Bluetooth LE Plugin

This plugin allows you to interact with Bluetooth LE devices on Android, iOS, and Windows.

Table of contents

Requirements

  • Cordova 5.0.0 or higher
  • Android Cordova library 5.0.0 or higher, target Android API 23/Platform 6.0 or higher (support for older Android versions should use versions 2.4.0 or below)
  • iOS 10 or higher
  • Windows Phone 8.1 (Tested on Nokia Lumia 630)
  • Windows 10 UWP
  • Device hardware must be certified for Bluetooth LE. i.e. Nexus 7 (2012) doesn't support Bluetooth LE even after upgrading to 4.3 (or higher) without a modification
  • List of devices: http://www.bluetooth.com/Pages/Bluetooth-Smart-Devices-List.aspx

Limitations / Issues

  • Disconnect and quickly reconnecting can cause issues on Android. Add a small timeout.
  • Indication type subscription hasn't been well .
  • OS X is still experimental. I'm experiencing some problems, but may be related to Cordova itself.

Upgrade 2.x to 3.x

  • Instead of specifying serviceUuids, serviceUuid, characteristicUuid, etc in the params, use services, service, characteristic, etc. Check out the scan-related, discovery-related and read/write/subscribe operation functions for more info. Discovery related functions will also return uuid properties instead of serviceUuid, characteristicUuid or descriptorUuid.
  • The connecting and disconnecting events were removed.

Upgrade 3.x to 4.x

  • Background modes aren't added automatically. See Installation Quirks (iOS) for details.

To Do

  • Improved notifications on peripheral/server role between Android and iOS
  • Code refactoring. It's getting pretty messy.

Using AngularJS

Check out ng-cordova-bluetoothle here!

If timeouts are needed, please check out the Angular wrapper and its example.

Installation

Cordova

cordova plugin add cordova-plugin-bluetoothle

PhoneGap Build

<gap:plugin name="cordova-plugin-bluetoothle" source="npm" />

Debugging

Check out these guides for lower level debugging on Android and iOS:

Apps like LightBlue are great for verifying Bluetooth LE behavior.

Installation Quirks (Android)

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 Modes (iOS)

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:

  1. Foreground - Service UUID doesn't need to be specified and allowDuplicates isn't ignored.
  2. Background (Screen On) - Service UUID must be specified. allowDuplicates isn't ignored. Scanning is very slow!
  3. Background (Screen Off) - Service UUID must be specified. allowDuplicates is ignored, so a device will only be returned once per scan. One possible work around is to start and stop the scan while in background. Unfortunately, there's a slim chance that the app may fall asleep again in between starting and stopping the scan.

Usage Description (iOS)

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 Quirks (iOS vs Android)

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.

Queuing (Android)

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

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

Advertisement Data / MAC Address

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.

Emulator

Neither Android nor iOS support Bluetooth on emulators, so you'll need to test on a real device.

Methods

Errors

Whenever the error callback is executed, the return object will contain the error type and a message.

  • initialize - Bluetooth isn't initialized (Try initializing Bluetooth)
  • enable - Bluetooth isn't enabled (Request user to enable Bluetooth)
  • disable - Bluetooth isn't disabled (Can't enabled if already disabled)
  • startScan - Scan couldn't be started (Is the scan already running?)
  • stopScan - Scan couldn't be stopped (Is the scan already stopped?)
  • bond - Bond couldn't be formed (Is it already bonding? Is the device Android?)
  • unbond - Bond couldn' be broken (Is it already unbonding? Is the device Android?)
  • connect - Connection attempt failed (Is the device address correct?)
  • reconnect - Reconnection attempt failed (Was the device ever connected?)
  • discover - Failed to discover device (Is the device already discovered or discovering?)
  • services - Failed to discover services (Is the device iOS?)
  • characteristics - Failed to discover characteristics (Is the device iOS?)
  • descriptors - Failed to discover descriptors (Is the device iOS?)
  • service - Service doesn't exist (Was it discovered? Correct uuid? Is the device iOS?)
  • characteristic - Characteristic doesn't exist (Was it discovered? Correct uuid? Is the device iOS?)
  • descriptor - Descriptor doesn't exist (Was it discovered? Correct uuid? Is the device iOS?)
  • read - Failed to read (Not sure what would cause this)
  • subscription - Failed to subscribe or unsubscribe (Does the characteristic have the Client Configuration descriptor?)
  • write - Failed to write (Was a write value provided?)
  • readDescriptor - Failed to read descriptor (Not sure what would cause this)
  • writeDescriptor - Failed to write descriptor (Was a write value provided?)
  • rssi - Failed to read RSSI (Not sure what would cause this)
  • mtu - Failed to set MTU (Is device Android?)
  • requestConnectionPriority - Failed to request connection priority (Is the device iOS?)
  • arguments - Invalid arguments (Check arguments)
  • neverConnected - Device never connected (Call connect, not reconnect)
  • isNotDisconnected - Device is not disconnected (Don't call connect or reconnect while connected)
  • isNotConnected - Device isn't connected (Don't call discover or any read/write operations)
  • isDisconnected - Device is disconnected (Don't call disconnect)
  • isBonded - Operation is unsupported. (Is the device Android?)
  • setPin - Operation is unsupported. (Is the device Android?)
  • retrievePeripheralsByAddress - Operation is unsupported (Is the device iOS?)

For example:

{"error":"startScan", "message":"Scanning already started"}

Permissions (Android)

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().

Android Docs

Properties

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

Central Life Cycle

  1. initialize
  2. scan (if device address is unknown)
  3. connect
  4. discover OR services/characteristics/descriptors (iOS)
  5. read/subscribe/write characteristics AND read/write descriptors
  6. disconnect
  7. close

initialize

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);
Params
  • request = true / false (default) - Should user be prompted to enable Bluetooth
  • statusReceiver = true / false (default) - Should change in Bluetooth status notifications be sent.
  • restoreKey = A unique string to identify your app. Bluetooth Central background mode is required to use this, but background mode doesn't seem to require specifying the restoreKey.
{
  "request": true,
  "statusReceiver": false,
  "restoreKey" : "bluetoothleplugin"
}
Success
  • status => enabled = Bluetooth is enabled
  • status => disabled = Bluetooth is disabled
{
  "status": "enabled"
}

enable

Enable Bluetooth on the device. Android support only.

bluetoothle.enable(enableSuccess, enableError);
Error
  • errorDisable = Bluetooth isn't disabled, so unable to enable.
  • errorEnable = Immediate failure of the internal enable() function due to Bluetooth already on or airplane mode, so unable to enable.
Success

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

Disable Bluetooth on the device. Android support only.

bluetoothle.disable(disableSuccess, disableError);
Error
  • errorEnable = Bluetooth isn't enabled, so unable to disable.
  • errorDisable = Immediate failure of the internal disable() function due to Bluetooth already off, so unable to enable. This shouldn't occur since the plugin is already checking this condition anyways.
Success

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.

getAdapterInfo

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);
Success

The successCallback contains the following properties:

  • name = the adapters's display name
  • address = the adapter's address
  • isInitialized = boolean value, true if the adapter was initialized, otherwise false
  • isEnabled = boolean value, true if the adapter was enabled, otherwise false
  • isScanning = boolean value, true if the adapter is currently scanning, otherwise false
  • isDiscoverable = boolean value, true if the adapter is in discoverable mode, otherwise false (currently largely false)

startScan

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);
Params
  • services = An array of service IDs to filter the scan or empty array / null. This parameter is not supported on Windows platform yet.
  • iOS - See iOS Docs
    • allowDuplicates = True/false to allow duplicate advertisement packets, defaults to false.
  • Android - See Android Docs
    • scanMode - Defaults to Low Power. Available from API21 / API 23.
    • matchMode - Defaults to Aggressive. Available from API23.
    • matchNum - Defaults to One Advertisement. Available from API23.
    • callbackType - Defaults to All Matches. Available from API21 / API 23. *Note: Careful using this one. When using CALLBACK_TYPE_FIRST_MATCH on a Nexus 7 on API 21, I received a Feature Unsupported error when starting the scan.
  • Windows 10 UWP
    • isConnectable - Windows 10 will, by default, show all devices ever seen by the system, even if the device is off. If you want to only devices that are on and connectable, set this to true.
{
  "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,
}
Success
  • status => scanStarted = Scan has started
  • status => scanResult = Scan has found a device
    • name = the device's display name
    • address = the device's address / identifier for connecting to the object
    • rssi = signal strength
    • advertisement = advertisement data in encoded string of bytes, use bluetoothle.encodedStringToBytes() (Android)
    • advertisement = advertisement hash with the keys specified here (iOS)
    • advertisement = empty (Windows)
{
  "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"
}

stopScan

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);
Success
  • status => scanStop = Scan has stopped
{
  "status": "scanStopped"
}

retrieveConnected

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);
Params
  • services = An array of service IDs to filter the retrieval by. If no service IDs are specified, no devices will be returned. Ignored on Android
{
  "services": [
    "180D",
    "180F"
  ]
}
Success

An array of device objects:

  • name = the device's display name
  • address = the device's address / identifier for connecting to the object
[
  {
    "name": "Polar H7 3B321015",
    "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
  }
]

bond

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);
Params
  • address = The address/identifier provided by the scan's return object
{
  "address": "5A:94:4B:38:B3:FD"
}
Success
  • status => bonded = Device is bonded
  • status => bonding = Device is bonding
  • status => unbonded = Device is unbonded
{
  "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

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);
Params
  • address = The address/identifier provided by the scan's return object
{
  "address": "5A:94:4B:38:B3:FD"
}
Success
  • status => unbonded = Device is unbonded
{
  "name": "Hello World",
  "address": "5A:94:4B:38:B3:FD",
  "status": "unbonded"
}

connect

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);
Params
  • address = The address/identifier provided by the scan's return object
  • autoConnect = Automatically connect as soon as the remote device becomes available (Android)
  • transport = Mode of transport - Auto = 0, Prefer BR/EDR = 1, Prefer LE = 2, (Android API 23+)
{
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
Success
  • status => connected = Device connected
  • status => disconnected = Device unexpectedly disconnected
{
  "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

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);
Params
  • address = The address/identifier provided by the scan's return object
{
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
Success
  • status => connected = Device connected
  • status => disconnected = Device unexpectedly disconnected
{
  "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

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);
Params
  • address = The address/identifier provided by the scan's return object
{
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
Success
  • status => disconnected = Device disconnected
{
  "name": "Polar H7 3B321015",
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
  "status": "disconnected"
}

close

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);
Params
  • address = The address/identifier provided by the scan's return object
{
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
Success
  • status => closed = Connection with device completely closed down
{
  "name": "Polar H7 3B321015",
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
  "status": "closed"
}

discover

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);
Params
  • address = The address/identifier provided by the scan's return object
  • clearCache = true / false (default) Force the device to re-discover services, instead of relying on cache from previous discovery (Android only)
{
  "address": "00:22:D0:3B:32:10",
  "clearCache": true
}
Return

Device Object:

  • status => discovered = Device was discovered
  • address = Device address
  • name = Device name
  • services = Array of service objects below

Service Object:

  • uuid = Service's uuid
  • characteristics = Array of characteristic objects below

Characteristic Object:

  • uuid = Characteristic's uuid
  • properties = If the property is defined as a key, the characteristic has that property
  • permissions = If the permission is defined as a key, the character has that permission
  • descriptors = Array of descriptor objects below

Descriptor Object:

  • uuid = Descriptor's uuid
{
  "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"
}

services

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);
Params
  • address = The address/identifier provided by the scan's return object
  • services = An array of service IDs to filter the scan or empty array / null
{
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
  "services": [

  ]
}
Success
  • status => services = Services discovered
    • services = Array of service UUIDS
{
  "status": "services",
  "services": [
    "180d",
    "180a",
    "180f",
    "6217ff4b-fb31-1140-ad5a-a45545d7ecf3"
  ],
  "name": "Polar H7 3B321015",
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}

characteristics

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);
Params
  • address = The address/identifier provided by the scan's return object
  • service = Service UUID
  • characteristics = An array of characteristic IDs to discover or empty array / null
{
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
  "service": "180d",
  "characteristics": [

  ]
}
Success
  • status => characteristics = Characteristics discovered
    • uuid = Service UUID
    • characteristics = Array of characteristics
      • properties = Object of defined properties
      • uuid = Characteristic UUID
{
  "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"
}

descriptors

Discover the characteristic's descriptors. iOS support only.

bluetoothle.descriptors(descriptorsSuccess, descriptorsError, params);
Params
  • address = The address/identifier provided by the scan's return object
  • service = The service's ID
  • characteristic = The characteristic's ID
{
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
  "service": "180d",
  "characteristic": "2a37"
}
Success
  • status => descriptors = Descriptors discovered
    • service = Service UUID
    • characteristic = characteristic UUID
    • descriptors = Array of Descriptor UUIDs
{
  "status": "descriptors",
  "descriptors": [
    "2902"
  ],
  "characteristic": "2a37",
  "name": "Polar H7 3B321015",
  "service": "180d",
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}

read

Read a particular service's characteristic once.

bluetoothle.read(readSuccess, readError, params);
Params
  • address = The address/identifier provided by the scan's return object
  • service = The service's UUID
  • serviceIndex = When dealing with multiple services with the same UUID, this index will determine which service will be used (OPTIONAL)
  • characteristic = The characteristic's UUID
  • characteristicIndex = When dealing with multiple characteristics with the same UUID, this index will determine which chracteristic will be used (OPTIONAL)
{
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
  "service": "180d",
  "characteristic": "2a38"
}
Success
  • status => read = Characteristics read
    • service = Service UUID
    • characteristic = Characteristic UUID
    • value = Base64 encoded string of bytes. Use bluetoothle.encodedStringToBytes(obj.value) to convert to a unit8Array. See characteristic's specification and example below on how to correctly parse this.
{
  "status": "read",
  "value": "UmVhZCBIZWxsbyBXb3JsZA==", //Read Hello World
  "characteristic": "2a38",
  "name": "Polar H7 3B321015",
  "service": "180d",
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}

subscribe

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);
Params
  • address = The address/identifier provided by the scan's return object
  • service = The service's UUID
  • serviceIndex = When dealing with multiple services with the same UUID, this index will determine which service will be used (OPTIONAL)
  • characteristic = The characteristic's UUID
  • characteristicIndex = When dealing with multiple characteristics with the same UUID, this index will determine which chracteristic will be used (OPTIONAL)
{
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
  "service": "180d",
  "characteristic": "2a37",
}
Success
  • status => subscribed = Subscription has started
  • status => subscribedResult = Subscription result has been received
    • service = Service UUID
    • characteristic = Characteristic UUID
    • value = Base64 encoded string of bytes. Use bluetoothle.encodedStringToBytes(obj.value) to convert to a unit8Array. See characteristic's specification and example below on how to correctly parse this.
{
  "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

Unsubscribe to a particular service's characteristic.

bluetoothle.unsubscribe(unsubscribeSuccess, unsubscribeError, params);
Params
  • address = The address/identifier provided by the scan's return object
  • service = The service's UUID
  • serviceIndex = When dealing with multiple services with the same UUID, this index will determine which service will be used (OPTIONAL)
  • characteristic = The characteristic's UUID
  • characteristicIndex = When dealing with multiple characteristics with the same UUID, this index will determine which chracteristic will be used (OPTIONAL)
{
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
  "service": "180d",
  "characteristic": "2a37"
}
Success
  • status => unsubscribed = Characteristics unsubscribed
    • service = Service UUID
    • characteristic = Characteristic UUID
{
  "status": "unsubscribed",
  "characteristic": "2a37",
  "name": "Polar H7 3B321015",
  "service": "180d",
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}

write

Write a particular service's characteristic.

bluetoothle.write(writeSuccess, writeError, params);
Params
  • address = The address/identifier provided by the scan's return object
  • service = The service's UUID
  • serviceIndex = When dealing with multiple services with the same UUID, this index will determine which service will be used (OPTIONAL)
  • characteristic = The characteristic's UUID
  • characteristicIndex = When dealing with multiple characteristics with the same UUID, this index will determine which chracteristic will be used (OPTIONAL)
  • value = Base64 encoded string
  • type = Set to "noResponse" to enable write without response, all other values will write normally.

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"}
Success

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);

writeQ

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

  • This is experimental. Test heavily before using in any production code.
  • To see a performance gain you should use this in combination with requestConnectionPriority('high') and mtu(MTU_VALUE) and then calling this method with type="noResponse" and set chunkSize to MTU_VALUE.
  • Only supported on iOS11+.
  • Only supports one call at a time. Don't execute back to back, use on multiple devices, or multiple characteristics.
bluetoothle.writeQ(writeSuccess, writeError, params);
Params
  • address = The address/identifier provided by the scan's return object
  • service = The service's UUID
  • serviceIndex = When dealing with multiple services with the same UUID, this index will determine which service will be used (OPTIONAL)
  • characteristic = The characteristic's UUID
  • characteristicIndex = When dealing with multiple characteristics with the same UUID, this index will determine which chracteristic will be used (OPTIONAL)
  • value = Base64 encoded string
  • type = Set to "noResponse" to enable write without response, all other values will write normally.
  • chunkSize = Define the size of packets. This should be according to MTU value
Success

See write() above.

readDescriptor

Read a particular characterist's descriptor

bluetoothle.read(readDescriptorSuccess, readDescriptorError, params);
Params
  • address = The address/identifier provided by the scan's return object
  • service = The service's ID
  • characteristic = The characteristic's ID
  • descriptor = The descriptor's ID
{
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
  "service": "180d",
  "characteristic": "2a37",
  "descriptor": "2902"
}
Success
  • status => readDescriptor = Descriptor was read
    • service = Service UUID
    • characteristic = Characteristic UUID
    • descriptor = Descriptor UUID
    • value = Base64 encoded string of bytes. Use bluetoothle.encodedStringToBytes(obj.value) to convert to a unit8Array.
{
  "status": "readDescriptor",
  "service": "180d",
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
  "characteristic": "2a37",
  "value": "AQAAAAAAAAA=",
  "name": "Polar H7 3B321015",
  "descriptor": "2902"
}

writeDescriptor

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);
Params
  • address = The address/identifier provided by the scan's return object
  • service = The service's ID
  • characteristic = The characteristic's ID
  • descriptor = The descriptor's ID
  • value - Base64 encoded string, number or string.
  • type - Specifies type (data, number or string). Default is base64. (iOS Only, use base64 encoded string for Android)

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"}
Success

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!

rssi

Read RSSI of a connected device. RSSI is also returned with scanning.

bluetoothle.rssi(rssiSuccess, rssiError, params);

Params

  • address = The address/identifier provided by the scan's return object
{
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
Success
  • status => rssi = RSSI retrieved
    • rssi = signal strength
{
  "status": "rssi",
  "rssi": -50,
  "name": "Polar H7 3B321015",
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}

mtu

Set MTU of a connected device. Android only.

bluetoothle.mtu(mtuSuccess, mtuError, params);

Params

  • address = The address/identifier provided by the scan's return object
  • mtu - Integer value mtu should be set to
{
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
  "mtu" : 50
}
Success
  • status => mtu = MTU set
    • mtu = mtu value
{
  "status": "mtu",
  "mtu": 50,
  "name": "Polar H7 3B321015",
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}

requestConnectionPriority

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);

Params

  • address = The address/identifier provided by the scan's return object
  • connectionPriority = low / balanced / high
{
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
  "connectionPriority" : "balanced"
}
Success
  • status => connectionPriorityRequested = true
{
  "name": "Polar H7 3B321015",
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
  "status" : "connectionPriorityRequested"
}

isInitialized

Determine whether the adapter is initialized. No error callback. Returns true or false

bluetoothle.isInitialized(isInitialized);
Success Return
  • status => isInitialized = true/false
{
  "isInitialized": true
}

isEnabled

Determine whether the adapter is enabled. No error callback

bluetoothle.isEnabled(isEnabled);
Success
  • status => isEnabled = true/false
{
  "isEnabled": true
}

isScanning

Determine whether the adapter is initialized. No error callback. Returns true or false

bluetoothle.isScanning(isScanning);
Return
  • status => isScanning = true/false
{
  "isScanning": false
}

isBonded

Determine whether the device is bonded or not, or error if not initialized. Android support only.

bluetoothle.isBonded(isBondedSuccess, isBondedError, params);

Params

  • address = The address/identifier provided by the scan's return object
{
  "address": "5A:94:4B:38:B3:FD"
}
Success
  • status => isBonded = true/false
{
  "name": "Polar H7 3B321015",
  "address": "5A:94:4B:38:B3:FD",
  "isBonded": false
}

setPin

Set PIN if required by the pairing process. Android support only.

bluetoothle.setPin(success, error, params);

Params

  • address = The address/identifier provided by the scan's return object
  • pin = Pairing PIN code
{
  "address": "5A:94:4B:38:B3:FD",
  "pin": "1234"
}
Success
  • status => string
{
  "status": "pinSet",
}

wasConnected

Determine whether the device was connected, or error if not initialized.

bluetoothle.wasConnected(wasConnectedSuccess, wasConnectedError, params);

Params

  • address = The address/identifier provided by the scan's return object
{
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
Success
  • status => wasConnected = true/false
{
  "name": "Polar H7 3B321015",
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
  "wasConnected": false
}

isConnected

Determine whether the device is connected, or error if not initialized or never connected to device.

bluetoothle.isConnected(isConnectedSuccess, isConnectedError, params);

Params

  • address = The address/identifier provided by the scan's return object
{
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
Success
  • status => isConnected = true/false
{
  "name": "Polar H7 3B321015",
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
  "isConnected": false
}

isDiscovered

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);

Params

  • address = The address/identifier provided by the scan's return object
{
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
}
Success
  • status => isDiscovered = true/false
{
  "name": "Polar H7 3B321015",
  "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63",
  "isDiscovered": false
}

hasPermission

Determine whether coarse location privileges are granted since scanning for unpaired devices requies it in Android API 23

bluetoothle.hasPermission(hasPermissionSuccess);
Success
  • status => hasPermission = true/false
{
  "hasPermission": true
}

requestPermission

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);
Success
  • status => requestPermission = true/false
{
  "requestPermission": true
}

hasPermissionBtScan

Determine whether Bluetooth scanning privileges are granted since scanning for unpaired devices requies it in Android API 31

bluetoothle.hasPermissionBtScan(hasPermissionSuccess);
Success
  • status => hasPermission = true/false
{
  "hasPermission": true
}

requestPermissionBtScan

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);
Success
  • status => requestPermission = true/false
{
  "requestPermission": true
}

hasPermissionBtConnect

Determine whether Bluetooth connect privileges are granted since connecting to unpaired devices requies it in Android API 31

bluetoothle.hasPermissionBtConnect(hasPermissionSuccess);
Success
  • status => hasPermission = true/false
{
  "hasPermission": true
}

requestPermissionBtConnect

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);
Success
  • status => requestPermission = true/false
{
  "requestPermission": true
}

hasPermissionBtAdvertise

Determine whether Bluetooth advertise privileges are granted since making the current device discoverable requies it in Android API 31

bluetoothle.hasPermissionBtAdvertise(hasPermissionSuccess);
Success
  • status => hasPermission = true/false
{
  "hasPermission": true
}

requestPermissionBtAdvertise

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);
Success
  • status => requestPermission = true/false
{
  "requestPermission": true
}

isLocationEnabled

Determine if location services are enabled or not. Location Services are required to find devices in Android API 23.

bluetoothle.isLocationEnabled(isLocationEnabledSuccess, isLocationEnabledError);
Success
  • status => isLocationEnabled = true/false
{
  "isLocationEnabled": true
}

requestLocation

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);
Success
  • status => requestLocation = true/false
{
  "requestLocation": true
}

retrievePeripheralsByAddress

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);
Params
  • addresses = An arrays of addresses/identifiers to lookup devices by. If no addresses are specified, no devices will be returned
{
  "addresses": ["ECC037FD-72AE-AFC5-9213-CA785B3B5C63"]
}
Success

Returns an array of device objects:

  • name = the device's display name
  • address = the device's address / identifier for connecting to the object
[
  {
    "name": "Polar H7 3B321015",
    "address": "ECC037FD-72AE-AFC5-9213-CA785B3B5C63"
  }
]

Peripheral Life Cycle

  1. initializePeripheral
  2. addService
  3. startAdvertising
  4. Listen for events on initializePeripheral callback
  5. Respond to events using respond or notify
  6. stopAdvertising
  7. removeService / removeAllServices

Initilization

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

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.

Descriptors

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.

initializePeripheral

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);
Params
  • request = true / false (default) - Should user be prompted to enable Bluetooth
  • restoreKey = A unique string to identify your app. Bluetooth Peripheral background mode is required to use this, but background mode doesn't seem to require specifying the restoreKey.
{
  "request": true
  "restoreKey": "bluetoothleplugin"
}
Success
  • status => enabled = Bluetooth is enabled
  • status => disabled = Bluetooth is disabled
  • status => readRequested = Respond to a read request with respond(). Characteristic (Android/iOS) or Descriptor (Android)
  • status => writeRequested = Respond to a write request with respond(). Characteristic (Android/iOS) or Descriptor (Android)
  • status => subscribed = Subscription started request, use notify() to send new data
  • status => unsubscribed = Subscription ended request, stop sending data
  • status => notificationReady = Resume sending subscription updates (iOS)
  • status => notificationSent = Notification has been sent (Android)
  • status => connected = A device has connected
  • status => disconnected = A device has disconnected
  • status => mtuChanged = MTU has changed for device
Enabled/Disabled
{
  "status": "enabled"
}
readRequested
{
  "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
}
writeRequested
{
  "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
}
subscribed
{
  "status":"subscribed",
  "address":"5163F1E0-5341-AF9B-9F67-613E15EC83F7",
  "service":"1234",
  "characteristic":"ABCD"
}
unsubscribed
{
  "status":"unsubscribed",
  "address":"5163F1E0-5341-AF9B-9F67-613E15EC83F7",
  "service":"1234",
  "characteristic":"ABCD"
}
notificationReady
{
  "status":"notificationReady"
}
connected
{
  "status":"connected",
  "address":"5163F1E0-5341-AF9B-9F67-613E15EC83F7",
}
disconnected
{
  "status":"disconnected",
  "address":"5163F1E0-5341-AF9B-9F67-613E15EC83F7",
}
mtuChanged
{
  "status":"mtuChanged",
  "address":"5163F1E0-5341-AF9B-9F67-613E15EC83F7",
  "mtu":20,
}

addService

Add a service with characteristics and descriptors. If more than one service is added, add them sequentially.

bluetoothle.addService(success, error, params);
Params
  • service = A service UUID.
  • characteristics = An object of characteristics data as shown below.
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,
      }
    }
  ]
};
Return
{
  "service":"1234",
  "status":"serviceAdded"
}

removeService

Remove a service.

bluetoothle.removeService(success, error, params);
Params
  • service = A service UUID.
var params = {
  service: "1234",
};
Return
{
  "service":"1234",
  "status":"serviceRemoved"
}

removeAllServices

Remove all services

bluetoothle.removeAllServices(success, error);
Return
{
  "status":"allServicesRemoved"
}

startAdvertising

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);
Params
var params = {
  "services":["1234"], //iOS
  "service":"1234", //Android
  "name":"Hello World",
};
Return
{
  "status":"advertisingStarted"
}

stopAdvertising

Stop advertising

bluetoothle.stopAdvertising(success, error);
Return
{
  "status":"advertisingStopped"
}

isAdvertising

Determine if app is advertising or not.

bluetoothle.isAdvertising(success, error);
Return
{
  "isAdvertising":true
}

respond

Respond to a read or write request. On Android, a device address is required

bluetoothle.respond(success, error, params);
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
};
Return
{
  "status":"responded"
}

notify

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);
Params
var params = {
  "service":"1234",
  "characteristic":"ABCD",
  "value":"U3Vic2NyaWJlIEhlbGxvIFdvcmxk" //Subscribe Hello World
  // "address": "5163F1E0-5341-AF9B-9F67-613E15EC83F7" // only on android
};
Return
{
  "status":"notified",
  "sent":true
}

encodedStringToBytes

Helper function to convert a base64 encoded string from a characteristic or descriptor value into a uint8Array object.

bluetoothle.encodedStringToBytes(string);

bytesToEncodedString

Helper function to convert a unit8Array to a base64 encoded string for a characteric or descriptor write.

bluetoothle.bytesToEncodedString(bytes);

stringToBytes

Helper function to convert a string to bytes.

bluetoothle.stringToBytes(string);

bytesToString

Helper function to convert bytes to a string.

bluetoothle.bytesToString(bytes);

Example

See the example provided with the Angular Wrapper

Data Parsing Example

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;
      }
  }
}

encodeUnicode

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);

decodeUnicode

Helper function to convert a base64 encoded string to unicode string. This function also decodes special characters such as emojis.

bluetoothle.decodeUnicode(string);

Sample: Discover and interact with Bluetooth LE devices

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.

  • Initialize the BluetoothLE adapter.
  • Scan for devices.
  • Connect to a device.
  • Show device services.
  • Show service characteristics.

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.

Initialize the Bluetooth adapter

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);
    }
}

Scan for devices

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);
}

Connect to a device

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");
    }
}

Get device services

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");
    }
}

Get services on an Android device

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);

    });

    }
}

Get services on a Windows device

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);
    }
}

Show services and characteristics in an app page

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.

More information

License

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.

cordova-plugin-bluetoothle's People

Contributors

amittel avatar dibari avatar dki1110 avatar helixhuang avatar jholzer-cciq avatar kabaehr avatar lei-xiaoming avatar lhelsloot avatar matrosov-nikita avatar mattkhaw avatar mible avatar ndrake avatar normesta avatar nunosav avatar p-andreas avatar pfried avatar phatpaul avatar pschlang avatar randdusing avatar thebosz avatar tillz avatar ttongsul avatar untilbit avatar vladimir-kotikov avatar wieltje avatar wtetsu avatar wwboy6 avatar wynout avatar wzijden avatar xrmx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cordova-plugin-bluetoothle's Issues

Would it help if...

Hi, Rand.
.
I'm a total noob at smartphones, 30 years embedded ANSI C sort of guy.
I get along with modifying working js, java, xml, etc...though it's difficult for me to write functions from scratch as of yet.
.
I was using ADT under Eclipse with USB to my 'Droid but want to abandon that route and have instead opted for PhoneGap Build.
.
My first extremely simple project works fine
https://build.phonegap.com/apps/995482/download/android
.
Now I want to Read/Write a few bytes to my BlueGiga BLE113 DEVKIT.
.
Q1: Would it benefit your project if a guy like me started using your methods for discovering, reading, writing and gave feedback?
.
Q2: What is the SIMPLEST way to use your excellent code in my PhoneGap Build project to:

  • discover my DEVKIT's BLE module
  • write 7 bytes to it
  • read 8 bytes from it
    This is because I am such a noob with PhoneGab and PhoneGap Build.

Forgive me if 'issues' is the wrong forum. Your website suggested that I leave questions here - but I'm a noob to GitHub too.

Thank You,
Frank R. Nichols
Oceanside, CA

getRSSI

Hi, thank you for your good work.
I'm actually trying to use your library in order to get the distance from a point, so I need to grab the RSSI of the corresponding BLE Device.
As far as I know, right now it's only possible to get the RSSI while scanning,
Could you add a function to read the RSSI of a connected device ?

Thank you,

Vincent.

Cordova doesn't build with this plugin installed

jose@mint ~/ble $ cordova run android
Generating config.xml from defaults for platform "android"
Preparing android project
Running app on platform "android" via command "/home/jose/ble/platforms/android/cordova/run" --device

/usr/local/lib/node_modules/cordova/node_modules/q/q.js:126
                    throw e;
                          ^
Error: An error occurred while running the android project.
/home/jose/ble/platforms/android/cordova/node_modules/q/q.js:126
                    throw e;
                          ^
Error executing "ant debug -f "/home/jose/ble/platforms/android/build.xml"": 
BUILD FAILED
/home/jose/Downloads/adt-bundle-linux-x86_64-20131030/sdk/tools/ant/build.xml:720: The following error occurred while executing this line:
/home/jose/Downloads/adt-bundle-linux-x86_64-20131030/sdk/tools/ant/build.xml:734: Compile failed; see the compiler error output for details.

Total time: 3 seconds


    at ChildProcess.<anonymous> (/usr/local/lib/node_modules/cordova/src/run.js:67:22)
    at ChildProcess.EventEmitter.emit (events.js:98:17)
    at maybeClose (child_process.js:730:16)
    at Process.ChildProcess._handle.onexit (child_process.js:797:5)
jose@mint ~/ble $ 

Returned address on iOS is incorrect

When I do a scan for BLE signals, I get back the right devices (I've been given some iBeacons, and they have the right names) but the returned UUID is incorrect. I've looked into it myself, and I can't explain it in the slightest- the incorrect UUID's are persistent, but when I use any other app to find them, I get a completely different one.

I'm wondering if this is a problem unique to me or if anyone else has had this too.

Arguments error when providing no arguments for scan

Hi randdusing

First of all, this plugin sounds very promising for me- I'm trying to get my app to include BLE for locational data on Android and iOS, so thanks very much for your efforts.

At the moment I'm working on the iOS version.

But as I've tried to scan for BLE signals, I've provided no arguments, and I'm getting "Argument object not found". From what I understand, if you provide an empty array, it's supposed to return all detected signals. In the native code it just returns if the dictionary that holds the arguments is nil.

Either I'm misinterpreting, or it should allow that.

Build failed with error 720 & 734

Hi,

I am new to cordova/phonegap application. I am working on a cordova/phonegap application which should utilize the Bluetooth Low Energy feature to establish communication with another Bluetooth enabled device (basically mobile to mobile device). I came across this fantastic plugin ( may be just what I need for my project to get working ) only to realize that I am not being able to make use of the plugin provided.

I am using phonegap/cordova CLI to build the project. After installing the plugin with the following procedure, I am getting an error while building it.

E:\BleTest>cordova create BleTestApp

E:\BleTest>cd BleTestApp

E:\BleTest\BleTestApp>

E:\BleTest\BleTestApp>cordova add plugin ../BluetootLE-master

E:\BleTest\BleTestApp>cordova plugin ls
[ 'com.randdusing.bluetoothle' ]

E:\BleTest\BleTestApp>cordova build android

Generating config.xml from defaults for platform "android"
Preparing android project
Compiling app on platform "android" via command "cmd" /c E:\BleTest\BleTestApp\platforms\android\cordova\build
[Error: An error occurred while building the android project.Error executing "ant debug -f E:\BleTest\BleTestApp\platforms\android\build.xml":
BUILD FAILED
E:\sdk\tools\ant\build.xml:720: The following error occurred while executing this line:
E:\sdk\tools\ant\build.xml:734: Compile failed; see the compiler error output for details.

Total time: 4 seconds

]

I will be glad if you can provide me some insights on making this work; or, a simple index.html file utilizing the plugins features will be much appreciated. Thanks in advance.

setting up environment

Hello, I've got some trouble using this plug-in. This is the first time that I try to install one, so I may be missing something.

In my repo :
$cordova plugin ls
com.randdusing.bluetoothle 1.0.0 "Bluetooth LE"

So the plug-in should be installed.
And here is my js :
$(document).ready(function(){
//try to enable bluetooth
bluetoothle.initialize(initializeSuccess, initializeError, true);
$("#tiles").append($('Bye'));
});

function initializeSuccess(obj) {
$("#tiles").append($('Hello'));
}

function initializeError(obj) {
$("#tiles").append($('Hello'));
}

So, I should have on my application page an "Hello" followed by a "Bye", but it doesn't work. I haven't touch any file generated by cordova but www folder.
What am I missing?

Unable to pair to a device

Bluetooth initialized successfully, starting scan for heart rate devices.
Start scan error: startScan - Scanning already in progress
Scanning time out, stopping
Scan was stopped successfully

Weirdly I can see the device under "Bluetooth" on Android and I can pair to it there yet Bluetooth is grayed out on on the "Show which hardware is turned on" drop down on this N7? Weird huh?

On Monday I can test an N5 to see if same behavior persists.. Will post an update then.. ;\

Device with no name fails

First I like to thank you for a great job! Just what I being looking for. I think BLE has a massive potential in years to come.

Anyway, I'm working on connecting a scale (see the kitchen scale at http://www.escali.com). The good ting is - I succeeded! But I had to make some addition in the .m file. The problem is that the scale do not provide a name (actually a bare minimum profile is in the scale). And in your code you assume the device has a name - else the return object fails.

All the places you are adding the name of the device, I have just made a test before like this (peripheral.name ? peripheral.name : @"").

E.g.

NSDictionary* returnObj = [NSDictionary dictionaryWithObjectsAndKeys: statusScanResult, keyStatus, (peripheral.name ? peripheral.name : @""), keyName, peripheral.identifier.UUIDString, keyAddress, RSSI, keyRssi, nil];

This has to be done for both peripheral.name and activePeripheral.name in all functions.

There might be a better way - but I have never coded in ObjC :-(

/Claus

Subscription issue after write

I'm working on Android at the moment. I connect to the BLE device, then subscribe to a characteristic using indication. I'm seeing data come in from the BLE device after subscribing, but after writing to the same characteristic I run into issues.

It seems the subscription is canceled, and the writeSuccessCallback is called one time with the return data. I'm looking into the Java class to try to figure out why this is, but I'm out of time for the night. Any ideas what's going on? Is the write callback overwriting the subscription callback?

PhoneGap Build old version

It looks like phonegap build is using a version from 4 months ago. Not sure if this is b/c the version is still 1.0.0 or if you would need to rev this and do a tag or something. Could you consider rev'ing the version to 1.0.1 and/or making a tag and then re-updating PhoneGap Build?

Here's where you can submit:
https://build.phonegap.com/plugins#add

Thanks!

Full Example needed

fragments of code are not incredibly useful. Can someone uploads a full, working, freshly build example with the code working?

obj.address is not consistent while changing device

Hi randdusing,

I'm currently developping an ios app based on your library,
But I just discovered that while scanning beacons from a different ipad than the one i'm using for my dev, the addresses of the beacons aren't the same.
Is there any way to make these addresses consistent from any device ?

Thank you,

1 host multiple client joins

I'm trying to build an app that will be set in a room. One person with their device (iOS or Android) will be the host/server and multiple other devices (iOS or Android) will join as clients. Once join the clients will send data to the host/server. And most likely the host/server will send data to the clients. Is this possible with this plugin?

wp8.1

Hello,

I start to develop BLE plugin for wp8.1. I have some successes but there is something unclear. Primarily wp has one major difference - connection carry out through phone settings. Therefore I have to modify bluetoothle.initialize, bluetoothle.startScan, bluetoothle.stopScan, bluetoothle.connect, bluetoothle.reconnect, bluetoothle.disconnect, bluetoothle.close functions. I can't decide which function to use (bluetoothle.discover or bluetoothle.services-> bluetoothle.characteristics ) because Cordova creates silverlight app which, for some reason, allows find all service but I have to specify which characteristics I need. During the week I wiill make some updates. This is link for curent version. https://github.com/MiBLE/BluetoothLE To start with cordova on wp8.1 I had to add libs from corodova 3.6.0 from here https://issues.apache.org/jira/browse/CB-4873 Can you give me some consultation? If you need my project I can share to you.

Kind regards,
Mihail

Provide some examples

I'm trying to figure out what the UUID value is, is it the same as the mac address?

bluetoothle.read crashing

Hi, I've been trying to retrieve the temperature data from a Sensor tag cc2541.
Uuid are aa00 for service, and aa01 for characteristic.
(var paramsObj = {"serviceUuid":"f000aa00-0451-4000-b000-00000000", "characteristicUuid":"f000aa01-0451-4000-b000-00000000"};)

When I'm trying : bluetoothle.read(readSuccess, readError, paramsObj);
I get those message :
readCharacteristic() - uuid: f000aa01-0451-4000-b000-00000000
readCharacteristic() - address=34:B1:F7:D4:F9:E1
btif_gattc_read_char
btgattc_handle_event: Event 1013
btif_gattc_upstreams_evt: Event 3
onReadCharacteristic() - address=34:B1:F7:D4:F9:E1, status=0, length=4
onCharacteristicRead() - Device=34:B1:F7:D4:F9:E1 UUID=f000aa01-0451-4000-b000-00000000 Status=0
onMessage(spinner,stop)
btif-config_save_file(L153): in file name:/data/misc/bluedroid/bt_config.new
[1] 5.onFinished: Installation state replication succeeded

And it stops, no more messages (can't see my console.log nor in the success callback nor in the failed one. The length is correct tough.

But, if I'm trying to use readDescriptor, it works fine :
var paramsObj = {"serviceUuid":"f000aa00-0451-4000-b000-00000000", "characteristicUuid":"f000aa01-0451-4000-b000-00000000", "descriptorUuid":"2901"};
gives me the expected data.

Returning scanRecord

I'm pretty new to phonegap, java, and android, but I'd like to get the scan callback to return scanRecord. Would I just add the following to the java file?

at line 101

private final String keyScanRecord = "scanRecord";

at line 1303

addProperty(returnObj, keyScanRecord, scanRecord);

Will this not work because scanRecord is a byte?

if you can point me in the right direction and/or want this functionality, I'll try to get it working and make a pull request.

service id in bluetoothle.characteristics

Wouldn't it make a lot of sense to pass the service id to bluetoothle.characteristics so you can do something like this..

  services.forEach(function(service){ // for each service
    console.log("attempting service connect", service);
    bluetoothle.characteristics(function(service, char){ // get the characteristics
      console.log("Got characteristics", char, "service", service);
      console.log("Attempting read of", service , ":", char[0]); // here I read the first but I could have another forEach and iterate over each service..
      bluetoothle.read(read, err, service, char[0]);
    }), err, service);
  });


Missing property isInitialized, isConnected, isScanning on iOS

Hi,

The property described in the readme file of the above functions is missing on iOS.

Eg. isScanning-function on Android returns: { "isScanning": true }
the same on iOS returns only { true } (without the property)

Anyway, good job with the plugin. I have a complete Bluetooth App now powered by your plugin and it works very well.

Example depends on org.apache.cordova.device but doesnt mention that

On cordova 3.4.1-0.1.0

On ios anyway this was terrible to hunt down as app started up ok, scanned and found the device, but then never calling any of the connect success or failure callbacks--seemingly frozen.

I guess device maybe was once included pre 3.0 but isnt anymore?

To make the platform checks work, and thus the example work, you need to
cordova plugin add org.apache.cordova.device

iOS background scanning

Does scanning fully work in the background in iOS?

I've added the phonegap background plugin (https://github.com/hazemhagrass/phonegap-background), and Bluetooh is scanning in the background, but it does not get any results. I get results when I have the app open, just not when it is in the background.

The console shows the scanStart is succeeding when running in the background, just not getting results. Any thoughts?

FYI, I'm testing on an iPad Mini.

Using CoreLocation to get true UUID

I've managed to use CoreLocation to get the actual UUID and other information from BLE devices. However, you have to specify the UUID when searching (sucks, yes, but there's no other way using CoreLocation). It also returns the object as a JSON string rather than a JS function (I'm not sure how to fix this, maybe you can).
This code might be useful to you, despite these caveats:

  • (void)getForUUUIDs:(CDVInvokedUrlCommand *)command
    {
    self->locationManager = [[CLLocationManager alloc] init];
    self->locationManager.delegate = self;
    scanCallback = command.callbackId;

    //Get an array of UUID's to filter by
    NSDictionary *obj = [self getArgsObject:command.arguments];

    if (obj != nil)
    {
    self->locationUUIDs = [self getNSUuids:obj forType:keyServiceAssignedNumbers];
    }

    for(NSInteger i = 0; i < [locationUUIDs count]; i++) {
    NSString *identifier = [NSString stringWithFormat:@"BLERegion %ld",(long)i];
    CLBeaconRegion *thisRegion = [[CLBeaconRegion alloc] initWithProximityUUID:[self->locationUUIDs objectAtIndex:i] identifier:identifier];
    [self->locationManager startMonitoringForRegion:thisRegion];
    [self->locationManager startRangingBeaconsInRegion:thisRegion];
    }
    }

-(void)locationManager:(CLLocationManager_)manager
didRangeBeacons:(NSArray_)beacons
inRegion:(CLBeaconRegion*)region
{
CLBeacon *foundBeacon = [beacons firstObject];

if(foundBeacon != nil) {
// You can retrieve the beacon data from its properties
NSString *uuid = foundBeacon.proximityUUID.UUIDString;
//NSString *major = [NSString stringWithFormat:@"%@", foundBeacon.major];
//NSString *minor = [NSString stringWithFormat:@"%@", foundBeacon.minor];

NSMutableString *retString = [NSMutableString stringWithString:@"{\"name\":null,\"address\":\""];
[retString appendString:uuid];
[retString appendString:@"\",\"rssi\":"];
[retString appendString:[NSString stringWithFormat:@"%ld", (long)foundBeacon.rssi]];
[retString appendString:@",\"status\":\"scanResult\"}"];

CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:retString];
[pluginResult setKeepCallbackAsBool:false];
[self.commandDelegate sendPluginResult:pluginResult callbackId:scanCallback];
}
return;

}

16 or 128bit UUID:s?

Hi,

I have a question about UUID:s. The device i'm using for testing has a custom service with a custom UUID. All the "out of the box" UUID:s i presented with 16 bits on both Android and iOS and that is fine, but the custom service is presented with 128 bits on iOS but only with 16 bits on Android.

I think the custom UUID should use the 128 bit version on Android as well?

In the code it seems to be strictly this bits XXXX1234-XXXXXXXX-... that is used for the short version on Android, you may run into conflicts if this bits are the same for more than one service, right?

Thanks in advance!

bluetoothle.services issue

Hi,

I'm running your library on iOs, so i'm using the bluetoothle.services method to discover the available services.
Based on your example, the functions is successful and leads me to characteristicsHeartSuccess with obj.status = "discoveredCharacteristics".
But here comes the problem, the data obj.serviceUuids is undefined. I tried to call the function bluetoothle.characteristics by stating the serviceUuid by myself but the function tells me that she can't find this service (which is the 180d and actually exists on my ble device).

Do you know what's happening ?

Thank you for your help !

Vincent.

128 bit UUID Issue on android

Great plugin!

I'm trying to connect to a Bluegigga BLE112 module from Android with your plugin. After a ton of testing I found only 4 character uuids will read/subscribe/write.

After reviewing the plugin source I see you're formatting the uuid here:
private final String uuidBase = "0000%s-0000-1000-8000-00805f9b34fb";

So I had to change my service and characteristic uuids in the BLE112 to four character strings, then everything works. But I need to use full 128 bit uuids.

Is this by design? Does the iOS plugin function like this as well?

Not finding write Characteristic ipad mini

I'm using:
-mac osx 10.9.3
-xcode 5.1.1
-phonegap 3.4.0.19.21
-plugins com.randdusing.bluetoothle, org.apache.cordova.device
-ipad mini version 7.1.1 model md531ll/a
-ti cc2541 mini dev kit
-your example code only with a few console.log additions and my 128bit uuids in place of the heart rate uuids

I'm able to use the LightBlue ios app and discover and write to this same characteristic.

The following is the log(from bottom to top) for a 128bit uuid characteristic that is read/notify
code:

    function desiredServiceSuccess(obj)
    {
      if (obj.status == "discoveredServices")
      {
        var serviceUuids = obj.serviceUuids;
        for (var i = 0; i < serviceUuids.length; i++)
        {
          var serviceUuid = serviceUuids[i];

          if (serviceUuid == desiredServiceUuid)
          {
            console.log("Finding desired characteristics");
            var paramsObj = {"serviceUuid":desiredServiceUuid, "characteristicUuids":[desiredCharacteristicUuid]};

log:

remote console.loglink
"Subscription started"
remote console.loglink
"Subscribing to desired device for 5 seconds"
remote console.loglink
"Battery level: 18"
remote console.loglink
"Reading battery level"
remote console.loglink
"Found battery service, now finding characteristic"
remote console.loglink
"Discovered desired descriptors, now discovering battery service"
remote console.loglink
"exiting desiredCharacteristicSuccess"
remote console.loglink
"Desired characteristics found, now discovering descriptor"
remote console.loglink
"Starting Characteristic0"
remote console.loglink
"Discovering desired device service"
remote console.loglink
"Finding desired characteristics"
remote console.loglink
"Clearing reconnect timeout"

The following is the log(from bottom to top) for a 128bit uuid characteristic that is write only
code:

    function desiredServiceSuccess(obj)
    {
      if (obj.status == "discoveredServices")
      {
        var serviceUuids = obj.serviceUuids;
        for (var i = 0; i < serviceUuids.length; i++)
        {
          var serviceUuid = serviceUuids[i];

          if (serviceUuid == desiredServiceUuid)
          {
            console.log("Finding desired characteristics");
            var paramsObj = {"serviceUuid":desiredServiceUuid, "characteristicUuids":[desiredCharacteristicUuid2]};

log:

remote console.loglink
"Discovering desired device service"
remote console.loglink
"Descriptors heart error: characteristic - Characteristic not found"
remote console.loglink
"Disconnect device"
remote console.loglink
"Closed device"
remote console.loglink
"exiting desiredCharacteristicSuccess"
remote console.loglink
"Starting Characteristic0"
remote console.loglink
"Desired characteristics found, now discovering descriptor"

whole file:

function runblueComms(){
    var addressKey = "address";

    var heartRateServiceUuid = "180d";
    var heartRateMeasurementCharacteristicUuid = "2a37";
    var clientCharacteristicConfigDescriptorUuid = "2902";
    var batteryServiceUuid = "180f";
    var batteryLevelCharacteristicUuid = "2a19";
    var desiredServiceUuid="6428e6a6-cd71-3a9e-f34b-7188cca94b36";//"364ba9cc-8871-4bf3-9e3a-71cda6e62864";
    var desiredCharacteristicUuid="8ea959e6-1ddb-33a5-cc45-8fff9d83a501";//"01a5839d-ff8f-45cc-a533-db1de659a98e";
    var desiredCharacteristicUuid2="e2c74434-afb2-6eb3-1740-27d308babd21";//"21bdba08-d327-4017-b36e-b2af3444c7e2";
    var desiredCharacteristicFoundCount=0;

    var scanTimer = null;
    var connectTimer = null;
    var reconnectTimer = null;

    var iOSPlatform = "iOS";
    var androidPlatform = "Android";

    bluetoothle.initialize(initializeSuccess, initializeError);

    function initializeSuccess(obj)
    {
      if (obj.status == "initialized")
      {
        var address = window.localStorage.getItem(addressKey);
        if (address == null)
        {
            console.log("Bluetooth initialized successfully, starting scan for desired device devices.");
            var paramsObj = null;//{"serviceUuids":[desiredServiceUuid]};
            bluetoothle.startScan(startScanSuccess, startScanError, paramsObj);
        }
        else
        {
            connectDevice(address);
        }
      }
      else
      {
        console.log("Unexpected initialize status: " + obj.status);
      }
    }

    function initializeError(obj)
    {
      console.log("Initialize error: " + obj.error + " - " + obj.message);
    }

    function startScanSuccess(obj)
    {
      if (obj.status == "scanResult")
      {
        console.log("Stopping scan..");
        bluetoothle.stopScan(stopScanSuccess, stopScanError);
        clearScanTimeout();

        window.localStorage.setItem(addressKey, obj.address);
            connectDevice(obj.address);
      }
      else if (obj.status == "scanStarted")
      {
        console.log("Scan was started successfully, stopping in 10");
        scanTimer = setTimeout(scanTimeout, 10000);
      }
      else
      {
        console.log("Unexpected start scan status: " + obj.status);
      }
    }

    function startScanError(obj)
    {
      console.log("Start scan error: " + obj.error + " - " + obj.message);
    }

    function scanTimeout()
    {
      console.log("Scanning time out, stopping");
      bluetoothle.stopScan(stopScanSuccess, stopScanError);
    }

    function clearScanTimeout()
    {
        console.log("Clearing scanning timeout");
      if (scanTimer != null)
      {
        clearTimeout(scanTimer);
      }
    }

    function stopScanSuccess(obj)
    {
      if (obj.status == "scanStopped")
      {
        console.log("Scan was stopped successfully");
      }
      else
      {
        console.log("Unexpected stop scan status: " + obj.status);
      }
    }

    function stopScanError(obj)
    {
      console.log("Stop scan error: " + obj.error + " - " + obj.message);
    }

    function connectDevice(address)
    {
      console.log("Begining connection to: " + address + " with 5 second timeout");
        var paramsObj = {"address":address};
      bluetoothle.connect(connectSuccess, connectError, paramsObj);
      connectTimer = setTimeout(connectTimeout, 5000);
    }

    function connectSuccess(obj)
    {
      if (obj.status == "connected")
      {
        console.log("Connected to : " + obj.name + " - " + obj.address);

        clearConnectTimeout();

        desiredDeviceDisconnect();
      }
      else if (obj.status == "connecting")
      {
        console.log("Connecting to : " + obj.name + " - " + obj.address);
      }
        else
      {
        console.log("Unexpected connect status: " + obj.status);
        clearConnectTimeout();
      }
    }

    function connectError(obj)
    {
      console.log("Connect error: " + obj.error + " - " + obj.message);
      clearConnectTimeout();
    }

    function connectTimeout()
    {
      console.log("Connection timed out");
    }

    function clearConnectTimeout()
    {
        console.log("Clearing connect timeout");
      if (connectTimer != null)
      {
        clearTimeout(connectTimer);
      }
    }

    function desiredDeviceDisconnect()
    {
      console.log("Disconnecting from device to test reconnect");
        bluetoothle.disconnect(desiredDeviceDisconnectSuccess, desiredDeviceDisconnectError);
    }

    function desiredDeviceDisconnectSuccess(obj)
    {
        if (obj.status == "disconnected")
        {
            console.log("Desired device disconnect device and reconnecting in 1 second. Instantly reconnecting can cause issues");
            setTimeout(reconnect, 1000);
        }
        else if (obj.status == "disconnecting")
        {
            console.log("Desired device disconnecting device");
        }
        else
      {
        console.log("Unexpected Desired device disconnect status: " + obj.status);
      }
    }

    function desiredDeviceDisconnectError(obj)
    {
      console.log("Desired device disconnect error: " + obj.error + " - " + obj.message);
    }

    function reconnect()
    {
      console.log("Reconnecting with 5 second timeout");
      bluetoothle.reconnect(reconnectSuccess, reconnectError);
      reconnectTimer = setTimeout(reconnectTimeout, 5000);
    }

    function reconnectSuccess(obj)
    {
      if (obj.status == "connected")
      {
        console.log("Reconnected to : " + obj.name + " - " + obj.address);

        clearReconnectTimeout();

        if (window.device.platform == iOSPlatform)
        {
          console.log("Discovering desired device service");
          var paramsObj = {"serviceUuids":[desiredServiceUuid]};
          bluetoothle.services(desiredServiceSuccess, desiredServiceError, paramsObj);
        }
        else if (window.device.platform == androidPlatform)
        {
          console.log("Beginning discovery");
          bluetoothle.discover(discoverSuccess, discoverError);
        }
      }
      else if (obj.status == "connecting")
      {
        console.log("Reconnecting to : " + obj.name + " - " + obj.address);
      }
      else
      {
        console.log("Unexpected reconnect status: " + obj.status);
        disconnectDevice();
      }
    }

    function reconnectError(obj)
    {
      console.log("Reconnect error: " + obj.error + " - " + obj.message);
      disconnectDevice();
    }

    function reconnectTimeout()
    {
      console.log("Reconnection timed out");
    }

    function clearReconnectTimeout()
    {
        console.log("Clearing reconnect timeout");
      if (reconnectTimer != null)
      {
        clearTimeout(reconnectTimer);
      }
    }

    function desiredServiceSuccess(obj)
    {
      if (obj.status == "discoveredServices")
      {
        var serviceUuids = obj.serviceUuids;
        for (var i = 0; i < serviceUuids.length; i++)
        {
          var serviceUuid = serviceUuids[i];

          if (serviceUuid == desiredServiceUuid)
          {
            console.log("Finding desired characteristics");
            var paramsObj = {"serviceUuid":desiredServiceUuid, "characteristicUuids":[desiredCharacteristicUuid2]};
            bluetoothle.characteristics(desiredCharacteristicsSuccess, desiredCharacteristicsError, paramsObj);
            return;
          }
        }
        console.log("Error: Desired service not found");
      }
        else
      {
        console.log("Unexpected services status: " + obj.status);
      }
      disconnectDevice();
    }

    function desiredServiceError(obj)
    {
      console.log("Services error: " + obj.error + " - " + obj.message);
      disconnectDevice();
    }

    function desiredCharacteristicsSuccess(obj)
    {
        var characteristicFound=false;
      if (obj.status == "discoveredCharacteristics")
      {
        var characteristicUuids = obj.characteristicUuids;
        for (var i = 0; i < characteristicUuids.length; i++)
        {
          console.log("Desired characteristics found, now discovering descriptor");
          var characteristicUuid = characteristicUuids[i];

          if (characteristicUuid == desiredCharacteristicUuid || characteristicUuid == desiredCharacteristicUuid2)
          {
            var paramsObj = {"serviceUuid":desiredServiceUuid, "characteristicUuid":desiredCharacteristicUuid};
            bluetoothle.descriptors(desiredDescriptorsSuccess, desiredDescriptorsError, paramsObj);
            console.log("Starting Characteristic"+i);
            setTimeout
            characteristicFound=true;
          }
        }
        if(characteristicFound){
            console.log("exiting desiredCharacteristicSuccess");
            return;
        }
        console.log("Error: Desired characteristic not found.");
      }
        else
      {
        console.log("Unexpected characteristics status: " + obj.status);
      }
      disconnectDevice();
    }
    function foundCharacteristicsSuccess(obj){
        if (obj.status == "discoveredDescriptors")
        {
            console.log("Found Descriptor"+desiredCharacteristicFoundCount);
        }
        else{
            console.log("Failed to find Descriptor"+desiredCharacteristicFoundCount);
        }
        desiredCharacteristicFoundCount++;
        if(desiredCharacteristicFoundCount>0){
            console.log("trying to move onto desiredDescriptorsSuccess");
            setTimeout(desiredDescriptorsSuccess, 1000);
        }
    }
    function desiredCharacteristicsError(obj)
    {
      console.log("Characteristics error: " + obj.error + " - " + obj.message);
      disconnectDevice();
    }

    function desiredDescriptorsSuccess(obj)
    {
      if (obj.status == "discoveredDescriptors")
      {
        console.log("Discovered desired descriptors, now discovering battery service");
        var paramsObj = {"serviceUuids":[batteryServiceUuid]};
        bluetoothle.services(servicesBatterySuccess, servicesBatteryError, paramsObj);
      }
        else
      {
        console.log("Unexpected descriptors heart status: " + obj.status);
        disconnectDevice();
      }
    }

    function desiredDescriptorsError(obj)
    {
      console.log("Descriptors heart error: " + obj.error + " - " + obj.message);
      disconnectDevice();
    }

    function servicesBatterySuccess(obj)
    {
      if (obj.status == "discoveredServices")
      {
        var serviceUuids = obj.serviceUuids;
        for (var i = 0; i < serviceUuids.length; i++)
        {
          var serviceUuid = serviceUuids[i];

          if (serviceUuid == batteryServiceUuid)
          {
            console.log("Found battery service, now finding characteristic");
            var paramsObj = {"serviceUuid":batteryServiceUuid, "characteristicUuids":[batteryLevelCharacteristicUuid]};
            bluetoothle.characteristics(characteristicsBatterySuccess, characteristicsBatteryError, paramsObj);
            return;
          }
        }
        console.log("Error: battery service not found");
      }
        else
      {
        console.log("Unexpected services battery status: " + obj.status);
      }
      disconnectDevice();
    }

    function servicesBatteryError(obj)
    {
      console.log("Services battery error: " + obj.error + " - " + obj.message);
      disconnectDevice();
    }

    function characteristicsBatterySuccess(obj)
    {
      if (obj.status == "discoveredCharacteristics")
      {
        var characteristicUuids = obj.characteristicUuids;
        for (var i = 0; i < characteristicUuids.length; i++)
        {
          var characteristicUuid = characteristicUuids[i];

          if (characteristicUuid == batteryLevelCharacteristicUuid)
          {
            readBatteryLevel();
            return;
          }
        }
        console.log("Error: Battery characteristic not found.");
      }
        else
      {
        console.log("Unexpected characteristics battery status: " + obj.status);
      }
      disconnectDevice();
    }

    function characteristicsBatteryError(obj)
    {
      console.log("Characteristics battery error: " + obj.error + " - " + obj.message);
      disconnectDevice();
    }

    function discoverSuccess(obj)
    {
        if (obj.status == "discovered")
        {
            console.log("Discovery completed");

        readBatteryLevel();
      }
      else
      {
        console.log("Unexpected discover status: " + obj.status);
        disconnectDevice();
      }
    }

    function discoverError(obj)
    {
      console.log("Discover error: " + obj.error + " - " + obj.message);
      disconnectDevice();
    }

    function readBatteryLevel()
    {
      console.log("Reading battery level");
      var paramsObj = {"serviceUuid":batteryServiceUuid, "characteristicUuid":batteryLevelCharacteristicUuid};
      bluetoothle.read(readSuccess, readError, paramsObj);
    }

    function readSuccess(obj)
    {
        if (obj.status == "read")
        {
            var bytes = bluetoothle.encodedStringToBytes(obj.value);
            console.log("Battery level: " + bytes[0]);

           console.log("Subscribing to desired device for 5 seconds");
           var paramsObj = {"serviceUuid":desiredServiceUuid, "characteristicUuid":desiredCharacteristicUuid};
           bluetoothle.subscribe(subscribeSuccess, subscribeError, paramsObj);
           //setTimeout(unsubscribeDevice, 5000);
        }
        else
      {
        console.log("Unexpected read status: " + obj.status);
        disconnectDevice();
      }
    }

    function readError(obj)
    {
      console.log("Read error: " + obj.error + " - " + obj.message);
      disconnectDevice();
    }

    function subscribeSuccess(obj)
    {
        if (obj.status == "subscribedResult")
        {
            console.log("Subscription data received");

            //Parse array of int32 into uint8
            var bytes = bluetoothle.encodedStringToBytes(obj.value);

            //Check for data
            if (bytes.length == 0)
            {
                console.log("Subscription result had zero length data");
                return;
            }

            //Get the first byte that contains flags
            var flag = bytes[0];

            //Check if u8 or u16 and get desired device
            var hr;
            if ((flag & 0x01) == 1)
            {
                var u16bytes = bytes.buffer.slice(1, 3);
                var u16 = new Uint16Array(u16bytes)[0];
                hr = u16;
            }
            else
            {
                var u8bytes = bytes.buffer.slice(1, 2);
                var u8 = new Uint8Array(u8bytes)[0];
                hr = u8;
            }
            console.log("desired device: " + hr);
        }
        else if (obj.status == "subscribed")
        {
            console.log("Subscription started");
        }
        else
      {
        console.log("Unexpected subscribe status: " + obj.status);
        disconnectDevice();
      }
    }

    function subscribeError(msg)
    {
      console.log("Subscribe error: " + obj.error + " - " + obj.message);
      disconnectDevice();
    }

    function unsubscribeDevice()
    {
      console.log("Unsubscribing heart service");
      var paramsObj = {"serviceUuid":desiredServiceUuid, "characteristicUuid":desiredCharacteristicUuid};
      bluetoothle.unsubscribe(unsubscribeSuccess, unsubscribeError, paramsObj);
    }

    function unsubscribeSuccess(obj)
    {
        if (obj.status == "unsubscribed")
        {
            console.log("Unsubscribed device");

            console.log("Reading client configuration descriptor");
            var paramsObj = {"serviceUuid":desiredServiceUuid, "characteristicUuid":desiredCharacteristicUuid, "descriptorUuid":clientCharacteristicConfigDescriptorUuid};
            bluetoothle.readDescriptor(readDescriptorSuccess, readDescriptorError, paramsObj);
        }
        else
      {
        console.log("Unexpected unsubscribe status: " + obj.status);
        disconnectDevice();
      }
    }

    function unsubscribeError(obj)
    {
      console.log("Unsubscribe error: " + obj.error + " - " + obj.message);
      disconnectDevice();
    }

    function readDescriptorSuccess(obj)
    {
        if (obj.status == "readDescriptor")
        {
            var bytes = bluetoothle.encodedStringToBytes(obj.value);
            var u16Bytes = new Uint16Array(bytes.buffer);
            console.log("Read descriptor value: " + u16Bytes[0]);
            disconnectDevice();
        }
        else
      {
        console.log("Unexpected read descriptor status: " + obj.status);
        disconnectDevice();
      }
    }

    function readDescriptorError(obj)
    {
      console.log("Read Descriptor error: " + obj.error + " - " + obj.message);
      disconnectDevice();
    }

    function disconnectDevice()
    {
      bluetoothle.disconnect(disconnectSuccess, disconnectError);
    }

    function disconnectSuccess(obj)
    {
        if (obj.status == "disconnected")
        {
            console.log("Disconnect device");
            closeDevice();
        }
        else if (obj.status == "disconnecting")
        {
            console.log("Disconnecting device");
        }
        else
      {
        console.log("Unexpected disconnect status: " + obj.status);
      }
    }

    function disconnectError(obj)
    {
      console.log("Disconnect error: " + obj.error + " - " + obj.message);
    }

    function closeDevice()
    {
      bluetoothle.close(closeSuccess, closeError);
    }

    function closeSuccess(obj)
    {
        if (obj.status == "closed")
        {
            console.log("Closed device");
        }
        else
      {
        console.log("Unexpected close status: " + obj.status);
      }
    }

    function closeError(obj)
    {
      console.log("Close error: " + obj.error + " - " + obj.message);
    }
}

Add CBPeripheral functionality

Do you have plans to add functionality for the iOS CBPeripheral functionality to this plugin? The CBCentralManager interface is a huge help, but I also need to be able to access the CBPeripheral class. Thanks for your work!

Requesting a disconnect event

Hi,
It seems that there is not possible to know when the connection is closed (eg. because of out of range etc). It would be nice to have a disconnect event to subscribe to. This would eliminate the need to poll the isConnected() func all the time. I know there is such a event in at least Android, don't know about iOS. But what do you think @randdusing ?

Thanks in advance.

bluetoothle.getString

Hello,
I want use this method but it's call error.
You can show example of arguments for this method.

It's my example:
var Tval = "ati";
bluetoothle.getString(Tval);
but have error.

Please help me solve this problem.

Thank you!

write characteristic problem

On android, when i call write characteristic with, writeSuccessCallback return "written". But no data send to device.

Subscribe question

I have this way:
StartScan -> Connect -> StopScan -> Write -> Read
But read last bytes.
I want read all data and start Subscribe but all time after subscribe have the same data.
My device have feature when I make Subscribe device restarted.
You can explain how right? How I can read all data after write command?
I am desperate
Thanks!

Connect to multiple peripheral units at the same time

Hi @randdusing ,

I'm requesting (for the future) the possibility to connect to multiple Bluetooth peripheral units simultaneously. My understanding is that is is possible via the Android (and iOS?) API.

This may require a lot of work but it is something that would be very nice to have!

module.js

I'm having difficulty adding this plugin, I'm just getting started, this could be a problem with something I did.

main.js

var bluetoothle;

function enableBluetooth() {
bluetoothle.initialize(initializeSuccessCallback, initializeErrorCallback);
document.getElementById("scan").disabled=false;
}

the bluetoothle.js file is referenced in the index.html file as shown (without the |'s they're just there so it actually shows up):

<|script src="../plugins/com.randdusing.bluetoothle/www/bluetoothle.js"|><|/script|>

after adding the var bluetoothle i got rid of some errors, but I still have an error with the module.exports = bluetoothle line.

image

startScan filtering

I'm trying to filter my scans by serviceUuids. When I do this, the scans succeed, but I never get any results. Maybe you can give me a hint of where I'm going wrong. Here is the process I went through:

First, I got a list of serviceUuids with the service method (on iOS), which returned this:

Service Success:{"status":"discoveredServices","name":"estimote","address":"72E0B809-A6D4-C82A-3953-AF3C542128B0","serviceUuids":["b9401000-f5f8-466e-aff9-25556b57fe6d","b9403000-f5f8-466e-aff9-25556b57fe6d","b9402000-f5f8-466e-aff9-25556b57fe6d","b9404000-f5f8-466e-aff9-25556b57fe6d"]}

So based on this I did the following:

var scanParam = {"serviceUuids":["b9401000-f5f8-466e-aff9-25556b57fe6d","b9403000-f5f8-466e-aff9-25556b57fe6d","b9402000-f5f8-466e-aff9-25556b57fe6d","b9404000-f5f8-466e-aff9-25556b57fe6d""]}

bluetoothle.startScan(startScanSuccess,startScanError, scanParam);

Thanks!

startScan crashes on iPad Mini

Whenever I try to call the startScan method on an iPad Mini it crashes the application. I've tried using:

bluetoothle.startScan(startScanSuccess, startScanError);
bluetoothle.startScan(startScanSuccess, startScanError, null);

I have it working just fine on Android.

It's crashing on an iPad Mini with OS 7.1.1

I've tried uninstalling and reinstalling the app, but no luck.

Suggestion: Option whether or not to request BL activation on init

I've built this feature into my own version of the plugin (which is by now actually heavily modified), where you pass in a boolean during initialisation to determine whether or not to request that BlueTooth be activated if inactive. Others might find this feature useful, and it's an easy one to include.

Start Scan

Hello, Loving what you did here. I am just having a slight issue. I initialize successfully but when i start scanning in the initialize success callback i don't get any kind of response. Shouldn't it tell me if it began scanning?

var ServiceUUID = "";
bluetoothle.initialize(initializeSuccessCallback, initializeErrorCallback);

initializeSuccessCallback: function (success){
    alert(success.status);
        if(success.status == "initialized"){
            var paramsObj = {
                "serviceUuids":[ServiceUUID]
                };
            bluetoothle.startScan(startScanSuccessCallback, startScanErrorCallback,  paramsObj); //Optional null = UUID; 
        }else{
            alert("Unexpected init status: " + success.status);

        }
    }
startScanSuccessCallback : function (success){
        alert("Scan Success: " + JSON.stringfy(success));
    }
    startScanErrorCallback : function (error){
        alert("Scan Error: " + JSON.stringfy(error));
    }

android discover hangs

I'm trying to write an app on a Samsung S4 that gets data from Estimote iBeacons. I'm
getting a strange issue where .discover() just hangs and eventually my app crashes.

My call chain, simplified, looks a bit like :

  • bluetoothle.initialize
  • bluetoothle.startScan
  • bluetoothle.stopScan
  • bluetoothle.connect
  • bluetoothle.discover
    and then it just hangs for a minute before the app shuts down.

Calling bluetoothle.isDiscovered still works for me. I've tried forcing a device disconnect and device close before reconnecting, it doesn't help. Tried disabling and enabling bluetooth on my phone, wifi is also disabled, rebooted my phone too, didn't help.

It should be pointed out that until a few days ago discover was working fine for me, then it suddenly stopped. I'm not sure if something in my automated buildchain got updated (I noticed that Cordova just updated, and your last RSSI commit doesn't seem to compile with that, so I'm forcing my build script to use the previous BluetoothLE commit (350d645).

Also, thanks for the great plugin, this is the first BLE plugin I've gotten my hands on that's getting me somewhere.

Server Side Support

I don't have immediate plans to add server side support, but I'd like to eventually add this for completeness.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.