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
- Introduced state property on Peripheral objects and related constants.
- Added support for Beacons and BeaconRegions.
- Introduced maximumUpdateValueLength property on Central objects.
- Introduced subscribedCentrals property and equals function on Characteristic objects.
- Introduced backgroundDataAuthorizationStatus property on the BluetoothLEModule object and related constants.
- Added the retrievePeripheralsWithIdentifiers, and retrieveConnectedPeripheralsWithServices functions.
- Added ADVERT_DATA_KEY_IS_CONNECTABLE, and ADVERT_DATA_KEY_SOLICITED_SERVICE_UUIDS constants.
- Sample app improvements.
- Documentation fixes.
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
- Added proximityChange event.
- Added rangedRegions property.
- Added option to turn off ranging for all regions at once.
- Added filterDuplicateBeacons property.
- Added locationManagerAuthorizationStatus property, locationManagerAuthorizationChanged event, and related constants.
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
- Improved the updatedValueForCharacteristic event.
- Documentation improvements.
- Added BPM V125 example.
Version 1.2.3
- Added example for Texas Instruments CC2541 Sensor Tag.
Version 1.2.4
- Documentation improvements
- Sample app improvements
- Added requestRegionState function.
Version 1.2.5
- Added restoredCentralManagerIdentifiers and restoredPeripheralManagerIdentifiers properties.
- Added retrieveMonitoredRegions function and retrievedMonitoredRegions event.
- Added wasLocationLaunch property.
Version 1.2.6
- Fixed bug related to service data in discoveredPeripheral event.
Version 1.2.7
- Introduced the requestWhenInUseAuthorization and requestAlwaysAuthorization functions.
- Updated Readme with Beacon Usage Permissions section.
- Updated sample app.
Author
Zsombor Papp, Logical Labs
titanium@logicallabs.com
License
Logical Labs Commercial License
Copyright
Copyright (c) 2012-2013 by Logical Labs, LLC