Bluetooth Low Energy

HTC BLE API Samples

Note: these HTC BLE API samples can be modified for the standard Android 4.3 BLE API.
See the section HTC BLE API and 4.3

BleActionLog Demo Download

The BLE Action Log sample uses BLE (Bluetooth Low Energy) to connect to a variety of devices and profiles and logs the results. It is useful for learning the flow of events when using BLE and as a starting point to write apps that work with BLE. The following profile client and services are demonstrated: Accelerometer, HRM, IAS, Temperature, SimpleKeys.

Discovery

The first step to communicating with a BLE device is to determine its address. You need to specify this address when attempting to connect to it. Device discovery for BLE is performed the same as in standard Bluetooth on Android except that you can check the device type to determine if it is standard Bluetooth, BLE, or dual mode. In the BLE Action Log example you can see a working implementation of discovery by starting the app, then choosing the "Device" option.

After selecting this option, make your device discoverable. For the TI SensorTag development kit, this means pressing the side button, which causes the device to advertise itself. For a device like the Polar H7 heart rate monitor, plug the device into the strap, wet the strap, and wear it.

The SelectDeviceActivity that is started first registers a receiver for the BluetoothDevice.ACTION_FOUND intent. Next it calls the BluetoothAdapter#startDiscovery method. The receiver is then called with that intent for each cached device and when new devices are discovered. You can check the type of the device with the BleAdapter.getDeviceType method. In the BLE Action Log sample, only devices that are BLE or dual mode are displayed in the device chooser activity by filtering for these types.

Here is the code it uses to register for the intent:

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
	public void onReceive(final Context aContext, final Intent aIntent) {
		final String action = aIntent.getAction();
		if (BluetoothDevice.ACTION_FOUND.equals(action)) {
			...

protected void onCreate(final Bundle aBundle) {
	super.onCreate(aBundle);
	IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
	registerReceiver(mReceiver, intentFilter);
	...

Here is the code it uses to start discovery:

private BluetoothAdapter mBtAdapter;
protected void onCreate(final Bundle aBundle) {
	super.onCreate(aBundle);
	mBtAdapter = BluetoothAdapter.getDefaultAdapter();
	mBtAdapter.startDiscovery();
	...

Here is the code it uses to determine device type when a new device is discovered:

if (BluetoothDevice.ACTION_FOUND.equals(action)) {
	final BluetoothDevice device = (BluetoothDevice) aIntent
		.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
	if (BleAdapter.getDeviceType(device) == BleAdapter.DEVICE_TYPE_BLE
			|| BleAdapter.getDeviceType(device) 
			== BleAdapter.DEVICE_TYPE_DUMO) {
		...

Choosing a Service

BLE specifies a large number of GATT profiles that cover common use cases such as communicating with heart rate monitors and sending alerts to other devices, such as when a lost device needs to be found and it should make noise or vibrate. Press the "GATT Service" option in the BLE Action Log program and pick a service the device you are testing with implements. You can modify the code to implement other services not currently covered.

Connecting to a Device

To connect to a BLE server on a device you call the connect method on the BleClientProfile subclass that implements your profile. This method will try a single connection immediately. You may need to try to connect multiple times to get a successful connection. An alternative method is the connectBackground method. This method will attempt to scan for the device and connect to it regularly. Because it only tries to connect every once in a while, do not use this method if you need an immediate connection, as the initial attempt may fail.

Here is the code used by the BleActionLog sample to connect:

private BleClientProfile mClient;

	void configure() {
		...
		mClient = ProfileClientFactory.getClient(this);
		...

	private void performForegroundConnectionAttempt(int connectionAttempts) {
		...
		final int result = mClient.connect(device);
		...

If you are implementing a BLE server profile, it is still possible to connect to a peripheral that runs a client profile. In this case you call the open method on a BleServerProfile subclass you have implemented. This method has a boolean for the connection being active or passive. While you can run either a client or server profile on HTC devices that support the BLE SDK, the current SDK does not support running the HTC device as a peripheral. The HTC device is the central device and connects with peripherals.

Pairing, Bonding, and Encryption

Pairing and bonding are performed as in standard Bluetooth on Android. A user can discover, pair, and bond with a device from the Bluetooth Settings on the phone. The device is remembered after that using a bond so that pin keys don't have to be reentered for future connections. If the device specifies it requires pairing and hasn't been paired, Android will also popup a pairing dialog when you connect to the device in your program.

To use encryption, call the setEncryption method with the appropriate constant for the security level to use in the BleClientProfile#onDeviceConnected method. Here is the code used by the BLEActionLog sample to enable encryption:

public class IasProfileClient extends BleClientProfile implements ExampleActions {
@Override
public void onDeviceConnected(final BluetoothDevice aDevice) {
	setEncryption(aDevice, BleConstants.GATT_ENCRYPT);

Writing to a Device

To write to a device you need to create a characteristic with the correct ID, then call writeCharacteristic. Here is an example:

public void turnOnTemperatureReadings(final BluetoothDevice aDevice) {
	final BleCharacteristic tempConfig = new BleCharacteristic(
			new BleGattID(TiBleConstants.IRTEMPERATURE_CONF_UUID));
	byte[] value = { 1 };
	tempConfig.setValue(value);
	tempConfig.setWriteType(BleConstants.GATTC_TYPE_WRITE);
	mService.writeCharacteristic(aDevice, 0, tempConfig);

With the current BLE implementation, if you call getCharacterisitic instead of new Characteristic, you may experience crashing due a known issue with performing multiple operations at once. For now please write your code to only perform one action at once. You can confirm a characteristic is present by checking after the profile is refreshed. In the sample code this looks like this:

public static boolean hasCharacterisitics(BluetoothDevice aDevice, 
		BleClientService aService, String... ids) {
	for ( String id : ids ) {
		final BleCharacteristic characteristic = aService
				.getCharacteristic(aDevice, new BleGattID(id));
		if ( null == characteristic ) {
			return false;
			...

Some devices do not always report all services and characteristics. You may need to disconnect and reconnect in such cases.

Reading from a Device

To read from a device, create a characteristic with the correct ID, and call readCharacteristic. Here is example code for that:

private int readBodySensorLocationCharacteristic(final BluetoothDevice aDevice) {
	final BleCharacteristic sensorLocation = new BleCharacteristic(new BleGattID(
		BleCharacteristics.HEART_RATE_BODY_SENSOR_LOCATION));		
	return mService.readCharacteristic(aDevice, sensorLocation);
}

Receiving Notifications from a Device

To receive notifications from a device you need to call registerForNotification in onDeviceConnected, as well as write to the configuration descriptor of the device to enable notifications:

public void onDeviceConnected(final BluetoothDevice aDevice) {
	mService.registerForNotification(aDevice, 0, new BleGattID(
		TiBleConstants.IRTEMPERATURE_DATA_UUID));
	...
	
public void performSecondAction(final BluetoothDevice aDevice) {
	final BleCharacteristic characteristic = new BleCharacteristic(new BleGattID(
		TiBleConstants.IRTEMPERATURE_DATA_UUID));
	final BleDescriptor clientConfig = new BleDescriptor(new BleGattID(
		BleConstants.GATT_UUID_CHAR_CLIENT_CONFIG));
	characteristic.addDescriptor(clientConfig);
	final byte[] value = new byte[] {
			BleConstants.GATT_CLIENT_CONFIG_NOTIFICATION_BIT, 
			0 // GATT_CLIENT_CHAR_CONFIG_RESERVED_BYTE
	};
	clientConfig.setValue(value);
	clientConfig.setWriteType(BleConstants.GATTC_TYPE_WRITE);
	mService.writeCharacteristic(aDevice, 0, characteristic);
}

Here are photos of using notifications with the TI SensorTag temperature service: