Logical Labs Bluetooth Low Energy Module for iOS

API Docs for: 1.2.7
Show:

Readme

This module makes the iOS Core Bluetooth framework available to Titanium developers.

To access this module from JavaScript, you would do the following:

var BluetoothLE = require("com.logicallabs.bluetoothle");

The BluetoothLE variable is a reference to the Module object.

The iOS Core Bluetooth framework defines numerous object classes. The module exposes most of these in the form of Titanium proxy objects. Specifically:

  • Characteristic: wrapper for CBCharacteristic and CBMutableCharacteristic
  • Descriptor: wrapper for CBDescriptor and CBMutableDescriptor
  • Peripheral: wrapper for CBPeripheral
  • Request: wrapper for CBATTRequest
  • Central: wrapper for CBCentral
  • Service: wrapper for CBService and CBMutableService

You do not need to (and in fact: can't) create any of these objects explicitly. You will, however, receive instances of these (proxy) classes as event parameters. With a few exceptions, the properties available on these JavaScript objects correspond to the properties of their counterpart(s) in the Core Bluetooth framework.

The module also maintaines, internally, one instance of CBPeripheralManager and CBCentralManager. You do not have direct access to these objects. You can access them through methods attached to the module object. Delegate methods are represented by events fired by the module object. The properties of the event object correspond to the parameters of the given delegate method.

Usage

The following code segments are not complete; they only demonstrate the essentials of using the module. The module includes a complete example app that demonstrates many different use cases, including iOS-to-iOS communication, communicating with a heart rate monitor, and monitoring iBeacons. You will find this in the standard example directory.

Basic steps of discovering and connecting to a peripheral from the central:

var BluetoothLE = require('com.logicallabs.bluetoothle'),

BluetoothLE.addEventListener('centralManagerStateChange', function(e) {
    if (e.state === BluetoothLE.CENTRAL_MANAGER_STATE_POWERED_ON) {
        BluetoothLE.startScan();
    }
});

BluetoothLE.addEventListener('discoveredPeripheral', function(e) {
    BluetoothLE.stopScan();
    BluetoothLE.connectPeripheral({
        peripheral: e.peripheral
    });
});

BluetoothLE.initCentralManager();

Basic steps of defining a service and starting advertisement as a peripheral:

BluetoothLE.addEventListener('peripheralManagerStateChange', function(e) {
    if (e.state === BluetoothLE.PERIPHERAL_MANAGER_STATE_POWERED_ON) {
        BluetoothLE.addService({
            uuid: CUSTOM_SERVICE_UUID,
            primary: true,
            characteristics: [
                {
                    uuid: NOTIF_CHAR_UUID,
                    properties: BluetoothLE.CHAR_PROP_NOTIFY,
                    permissions: BluetoothLE.CHAR_PERM_NONE
                },
                {
                    uuid: READ_CHAR_UUID,
                    properties: BluetoothLE.CHAR_PROP_READ,
                    permissions: BluetoothLE.CHAR_PERM_READABLE
                 },
                 {
                     uuid: WRITE_CHAR_UUID,
                     properties: BluetoothLE.CHAR_PROP_WRITE_WITHOUT_RESPONSE,
                     permissions: BluetoothLE.CHAR_PERM_READABLE + BluetoothLE.CHAR_PERM_WRITEABLE
                 }
            ]
        });

        advertParams = {};
        advertParams[BluetoothLE.ADVERT_DATA_KEY_SERVICE_UUIDS] = [ CUSTOM_SERVICE_UUID ];
        advertParams[BluetoothLE.ADVERT_DATA_KEY_LOCAL_NAME] = Ti.Platform.username;
        BluetoothLE.startAdvertising(advertParams);
    }
});

BluetoothLE.initPeripheralManager();

Basic steps for querying the value of a characteristic:

peripheral.addEventListener('updatedValueForCharacteristics', function(e) {
    Ti.API.info('Received new value for characteristic ' + e.characteristic.UUID);
    Ti.API.info('Value as string: ' + e.value)
    Ti.API.info('First byte of value: ' + e.value[0]);
});

peripheral.readValueForCharacteristic(readChar);

Basic steps to respond to a read request (on the central):

BluetoothLE.addEventListener('receivedReadRequest', function(e) {
    var buffer;

    Ti.API.info('Received read request for characteristic: ' + e.request.characteristic.UUID);
    buffer = Ti.createBuffer({ length: 3 });
    buffer[0] = 1;
    buffer[1] = 2;
    buffer[2] = 3;

    e.request.value = buffer;

    BluetoothLE.respondToRequest({
        request: e.request,
        result: BluetoothLE.ATT_SUCCESS
    });
});

Note that the last two examples demonstrate how to access and construct the value of a characteristic as an array of bytes. This is a built-in (although undocumented) capability of the TiBuffer objects that are used to represent these values.

Beacons

Use the createBeaconRegion function to create beacon region objects:

var beaconRegion = BluetoothLE.createBeaconRegion({
    UUID: uuid,
    identifier: '#' + idCounter
});

This object then can be passed to the startRegionMonitoring function to start monitoring:

BluetoothLE.startRegionMonitoring({
    beaconRegion: region
});

This will result in enteredRegion, exitedRegion, and regionStateUpdated events.

You will typically want to call the requestRegionState function right after you call startRegionMonitoring to get an update of the current region state immediately. Otherwise you might only receive the first update when the state of a region changes. This can be a problem if the user is in the region you are interested in at the time the app starts.

Once the user enters a region, the startRangingBeacons function can be used to get periodic updates about the beacons in range:

BluetoothLE.addEventListener('regionStateUpdated', function(e) {
    switch(e.state) {
        case BluetoothLE.REGION_STATE_INSIDE:
            BluetoothLE.startRangingBeacons({
                beaconRegion: e.region
            });
            break;
        case BluetoothLE.REGION_STATE_OUTSIDE:
            BluetoothLE.stopRangingBeacons({
                beaconRegion: e.region
            });
            break;
    }
});

This will result in rangedBeacons events.

Use the stopRangingBeacons and stopRegionMonitoring functions to stop ranging and monitoring, respectively.

Background operations

If the app started beacon region monitoring, it will receive enteredRegion, exitedRegion, and regionStateUpdated events while in the background. The iBeacons example demonstrates how to display a local notification to the user when this happens.

The app will also receive generic Bluetooth LE related events while in the background if you declare the appropriate background modes in the Info.plist file. For Titanium apps, this can be done in the tiapp.xml file as follows:

<ios>
   <plist>
       <dict>
           <key>UIBackgroundModes</key>
           <array>
                <string>bluetooth-central</string>
                <string>bluetooth-peripheral</string>
           </array>
       </dict>
   </plist>
</ios>

Restarting into the background

Starting with iOS 7, BLE and beacon related events can trigger the app to be restarted into (!) the background after it was stopped (removed from memory) by iOS to free up resources. Starting with iOS7.1, beacon related events can restart the app into the background even if the user explicitly removed the app from the active app list.

In order to take advantage of this feature, specify a restoreIdentifier parameter of the initCentralManager and initPeripheralManager functions, or simply start monitoring a beacon region.

When the app starts, the restoredCentralManagerIdentifiers and restoredPeripheralManagerIdentifiers properties will hold the restoraiton identifiers of the central and peripheral managers, respectively, that can be restored. Restoration is initiated by calling the initCentralManager and initPeripheralManager functions with the previously used restoreIdentifier. Thereafter the peripheralWillRestoreState and centralWillRestoreState will be fired to complete the restoration.

If the restart was triggered by a beacon related event, the wasLocationLaunch property will be true. Use the retrieveMonitoredRegions function to retrieve the beacon regions that are already being monitored.

Beacon Usage Permissions

Starting with iOS 8, the authorization model related to location services, and thus beacons, has changed. If the app wants to use beacon related functionality, it now needs to explicitly call either the requestWhenInUseAuthorization or the requestAlwaysAuthorization function, and specify either the NSLocationAlwaysUsageDescription or the NSLocationWhenInUseUsageDescription Info.plist entry in tiapp.xml:

<ios>
   <plist>
       <dict>
           <key>NSLocationAlwaysUsageDescription</key>
           <string>
                Please allow access to enable beacon monitoring!
            </string>
           <key>NSLocationWhenInUseUsageDescription</key>
           <string>
                Please allow access to enable beacon ranging!
            </string>
       </dict>
   </plist>
</ios>

When the app calls the requestWhenInUseAuthorization or the requestAlwaysAuthorization function, the user is presented with a dialog that requests permission. The string you associated with the NSLocationAlwaysUsageDescription or the NSLocationWhenInUseUsageDescription key will be displayed to the user in this dialog.

The difference between "when-in-use" and "always" authorization is significant: "when-in-use" means that the app can only access these services while it's running in the foreground, and it cannot request region monitoring even then. Therefore many apps that use beacons will need "always" authorization.

However, Apple discourages the use of "always" authorization. To quote: "Requesting “Always” authorization is discouraged because of the potential negative impacts to user privacy. You should request this level of authorization only when doing so offers a genuine benefit to the user."

Issues and Limitations

When using an iOS device as peripheral, the advertisement packets may only contain the ADVERT_DAT_KEY_SERVICE_UUIDS and/or ADVERT_DATA_KEY_LOCAL_NAME fields. This is a limitation of the Core Bluetooth framework.

While testing Core Bluetooth framework (without Titanium), we experienced some unexplained problems that were eventually resolved by resetting the devices.

Change Log

Version 1.0.1

  • Added RSSI example to sample app.
  • Added TiBuffer-as-array example to sample app.
  • Bug fixes in sample app.
  • Fixed typos in documentation.
  • Added more examples to documentation.

Version 1.0.2

  • Improved examples and documentation.
  • Fixed defect in discoverCharacteristics function of Peripheral.
  • Added writeValueForDescriptor method to Peripheral object.
  • Fixed defect in retrievePeripherals function of the module object.

Version 1.0.3

  • Changed sample app styling.
  • Tested with iOS7.
  • Added ability to include module in apps built for older iOS versions without Bluetooth Low Energy capability.

Version 1.0.4

  • Improved background functionality.
  • Documentation updates.

Version 1.1.0

Version 1.1.1

  • Extended iBeacon example with multiple regions.
  • Added identifier property to BeaconRegion class.
  • Added Estimote Beacons to example app.

Version 1.1.2

Version 1.1.3

  • Improved iBeacon and Estimote Beacon examples.

Version 1.2.0

  • Improved documentation and sample app
  • Introduced the moduleReady event.

Version 1.2.2

Version 1.2.3

  • Added example for Texas Instruments CC2541 Sensor Tag.

Version 1.2.4

Version 1.2.5

Version 1.2.6

Version 1.2.7

Author

Zsombor Papp, Logical Labs

titanium@logicallabs.com

License

Logical Labs Commercial License

Copyright (c) 2012-2013 by Logical Labs, LLC