Bluetooth


Table of Contents

Introduction

The framework supports a layer of abstraction above the native Android handling of Bluetooth and Bluetooth Low Energy (BLE) connections, in order to simplify their inclusion in any application.  In Android, this handling occurs at a very low level, and has separate philosophies for Bluetooth versus BLE, namely their treatment in polling versus event-based terms. In the Berkeley Telemonitoring framework we have endeavored to make the experience with dealing with each of these more similar and straightforward, and have included a more generic overarching Bluetooth Health Device class that encapsulates both device types with a unified interface.

This tutorial will briefly show the functionality of Bluetooth, BLE, and generic Bluetooth Health Device operation within the framework. Finally we will show a full example of usage for a Health Device.

Prerequisites

In order to communicate with any Bluetooth or BLE device, permission to enable and use Bluetooth must be specified in the AndroidManifest.xml file of the client application by adding the following lines:

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

Bluetooth Device Flow

Ensuring Bluetooth Discoverability

The first step in incorporating Bluetooth communications into an application is to turn on Bluetooth capabilities and ensure the device is discoverable with the following line of code:

private BluetoothService mBluetoothService = BluetoothService.getInstance(this);
this.mBluetoothService.ensureDiscoverable();

Scanning for Devices

Unless the MAC address of the peripheral Bluetooth device you wish to connect to is already known, it is necessary to scan for nearby devices to find those accessible for connection.  Before starting the scan, you will likely want to add a listener to handle events like devices found and scan completed:

mBluetoothService.setListener(new BluetoothServiceEventListener(this));

public class BluetoothServiceEventListener implements
            BluetoothServiceEventListenerInterface {
        // setup ...
        public void onBeginScanning() {
            // code to run when a scan starts successfully
        };
        public void onFinishedScanning() {
            // code to run when a scan finishes
        };
        public void onObtainedOneUnpairedDevice(UnpairedBluetoothDevice device){
           // code to run for any unpaired device found
        };
        // additional callbacks for other Bluetooth status and connection events ...
    }

Once any desired listeners to handle events relating to the scan have been added, you can start a scan with the following line:

mBluetoothService.startScan();

Establishing a Connection

Next, to establish a pairing connection with a Bluetooth device found or specified (represented as a UnpairedBluetoothDevice with a name and address), we can set a PairedBluetoothDevice as the result of calling pairToDevice() on the unpaired device, if successful, which we can finally initiate an attempt to connect to:

UnpairedBluetoothDevice unpairedDevice;
PairedBluetoothDevice paired = unpairedDevice.pairToDevice();
paired.connect();

We will again likely wish to add a listener to handle events such a successful connection or data received from the paired device after connection is established:

paired.setListener(new BluetoothPairedDeviceEventListener());

public class BluetoothPairedDeviceEventListener implements BluetoothPairedDeviceEventListenerInterface {
        public void onConnect(PairedBluetoothDevice device) {
            // code to run upon successful connection
        };
        public void onReceive(PairedBluetoothDevice device, BluetoothSendable<?> payload) {
            // code to run when data is received from the paired device
        };
        // additional callbacks for success or failure on connection and data transfer events        
    }

It is now also possible to write data in a BluetoothSendable format to the paired and connected device using:

BluetoothSendable b = new BluetoothSendable("Hello world");
device.write(b);

BLE Device Flow

The process for establishing connection with a BLE device is fairly similar in our framework to that for Bluetooth devices.

Ensuring BLE Is Enabled

To ensure that BLE communications can be established, the service can be called to switch on Bluetooth:

private BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
private BluetoothLEService = BluetoothLEService.getInstance(this, mBluetoothAdapter);
mBluetoothLEService.switchOnBluetooth();

Scanning for Devices

Again, if the device MAC address is not already known it may be necessary to scan for nearby devices, and again it is likely desirable to add a listener to handle BLE events before beginning the scan:

public class BluetoothLEServiceEventListener implements
        BluetoothLEServiceListenerInterface {
    // setup code ...
    public void onFinishedScanning(){
        // code to run on scan completion
    }
    public void onBeginScanning(){
        // code to run on scan start
    }
    @Override
    public void onFoundLEDevice(BluetoothLEDevice device) {
        // code to run on each found BLE device
    }
}
mBluetoothLEService.setListener(new BluetoothLEServiceEventListener(this));
mBluetoothLEService.startScan();

Establishing a Connection

Next, to begin exchanging data with a BLE device, given a BLEDevice instance with the device’s name and address, as with Bluetooth it is then necessary to call connect for that device:

BluetoothLEDevice mBLEDevice;
mBLEDevice.connect();

And as for Bluetooth we will likely wish to add an event listener to handle connection and data events from the BLE device:

mBLEDevice.setListener(new BLEDeviceEventListener());

public class BLEDeviceEventListener implements BluetoothLEDeviceListenerInterface {
        public void onDeviceConnected(BluetoothLEDevice device) {
            // code to run upon successful connection
        };
        public void onFinishedDiscoveringServices(BluetoothLEDevice device, List<BluetoothGattService> services) {
            // code to run upon service discovery
        };
        public void onReadCharacteristicValue(BluetoothLEDevice device, BluetoothGattCharacteristic characteristic, boolean changed) {
            // code to run when data is read from a characteristic advertised by the device
        };
        // additional callbacks for success or failure on connection and data transfer events
    }

The framework abstracts away concepts like the Gatt server that you may be familiar with from native Android BLE support, and automatically handles tasks like starting service discovery upon connection, reading values when a characteristic changes, and following specified retry logic upon lost connection (described below in the section on Bluetooth Connection Configuration).

Bluetooth Health Device

This unified class combines Bluetooth and BLE device handling into one common interaction, abstracting the lower-level details as much as possible.  To establish a connection with a Bluetooth Health Device, it is still necessary to either scan for or provide a device address, and then to create a BluetoothHealthDevice  instance from the paired Bluetooth device or BLE device object device :

public BluetoothHealthDevice mHealthDevice = new BluetoothHealthDevice(new BluetoothLEDevice(mBluetoothAdapter, device, this));
mHealthDevice.connect();

Bluetooth Health Devices have their own set of listeners that can be added to handle connection and data events, specific to the profile they follow. For instance, given a Bluetooth Health Device representing a Heart Rate monitor, we might add a listener as follows:

mHealthDevice.addHealthListener(new BluetoothHealthHRListener(this));

public class BluetoothHealthHRListener implements
            BluetoothHeartRateListenerInterface {
        // setup code ...
        @Override
        public void heartRateDataPointReceived(BluetoothHealthDevice device, HeartRateDataPoint datapoint) {
            // code to run upon datapoint received
        }
        @Override
        public void updateConnectionState(BluetoothHealthDevice device, boolean connected) {
            // code to run on connection state change
    }

At present listener interfaces have been created to allow the developer to handle events for Heart Rate, Temperature, Blood Pressure, Weighing Scale, and Glucose monitoring devices, but the framework can easily be extended to support additional health device profiles in the future.

Bluetooth Connection Configuration

In addition to the framework’s setup to handle scanning for and establishing connections to devices, it also allows for more specific configuration of retry logic upon initial and lost connections to devices (for each of Bluetooth, BLE, or Bluetooth Health Device). This configuration is optional, but can be added as follows, to set these preferences before connection to the device:

BluetoothConnectionConfiguration.ConnectionPreferenceFlag connectFlag = BluetoothConnectionConfiguration.ConnectionPreferenceFlag.RETRY_INDEFINITE;
BluetoothConnectionConfiguration.ConnectionPreferenceFlag reconnectFlag = BluetoothConnectionConfiguration.ConnectionPreferenceFlag.RETRY_INDEFINITE;
mConnectionConfiguration = new BluetoothConnectionConfiguration(connectFlag, reconnectFlag);
mHealthDevice.applyConnectionConfigurationSettings(mConnectionConfiguration);

Bluetooth Connection Configuration options include flags for both initial connection and reconnection to follow patterns of NO_RETRY, RETRY_UNTIL_TIMEOUT, or RETRY_INDEFINITELY . For instance, to modify the above configuration to retry connection only until a timeout of 15 seconds you could apply the following:

mConnectionConfiguration = mConnectionConfiguration.newConfigurationWithReconnectionRetryFlagAndTimeout(BluetoothConnectionConfiguration.ConnectionPreferenceFlag.RETRY_UNTIL_TIMEOUT, 15);
mHealthDevice.applyConnectionConfigurationSettings(mConnectionConfiguration);
mHealthDevice.connect();

The default setting is to have no retry attempts on either initial or lost connection, simply to try once to connect to a specified device and to close the connection if it is lost.

A visualization of the connection lifecycle of a device following each configuration option is depicted by the following:

bt_lifecycle

Heart Rate Monitor Example

The following code is an example that shows how a listener would be implemented to access data being received from an external Bluetooth heart rate monitor. The class should extend the BluetoothHeartRateListenerInterface , and therefore contain the method heartRateDataReceived. Within this method you can access the incoming Bluetooth heart rate monitor data.

public class BluetoothHealthHRListener implements
            BluetoothHeartRateListenerInterface {

        private Context mContext;

        public BluetoothHealthHRListener(Context context) {
            this.mContext = context;
        }

        @Override
        public void heartRateDataReceived(BluetoothHealthDevice device,
                                               final HeartRateContainer data) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    display = (TextView) findViewById(R.id.displayHR);
                    String heartRate = "Heart Rate: " + Float.toString(data.getHeartRate());
                    display.setText(heartRate);
                }
            });
        }
    }