How to Use Bluetooth LE
- Overview
- SensorTag Communication via Command Line
- BLE Bundle for TI SensorTag
- Cloud-enabled BLE Bundle
Overview
This section provides an example of how to develop a simple bundle that discovers and connects to a Smart device (BLE), retrieves data from it, and publishes the results to the cloud. This example uses the TI SensorTag based on CC2541 or CC2650. For more information about this device, refer to http://www.ti.com/tool/cc2541dk-sensor and http://www.ti.com/ww/en/wireless_connectivity/sensortag2015/index.html.
You will learn how to perform the following functions:
-
Prepare the embedded device to communicate with a Smart device
-
Develop a bundle retrieves data from the device
-
Optionally publish the data in the cloud
Prerequisites
-
Hardware
- Use an embedded device running ESF with Bluetooth 4.0 (LE) capabilities
- Use at least one TI SensorTag
This tutorial uses a Raspberry Pi Type B with a LMTechnologies LM506 Bluetooth 4.0 http://lm-technologies.com/wireless-adapters/lm506-class-1-bluetooth-4-0-usb-adapter/ dongle.
Prepare the Embedded Device
In order to communicate with Smart devices, the bluez package must be installed on the embedded device. To do so, make sure you have the necessary libraries on the Raspberry Pi and proceed as follows:
NOTE: Depending on the size of your display window, you may need to scroll across to the right in order to view the commands and code samples in their entirety.
sudo apt-get install libusb-dev libdbus-1-dev libglib2.0-dev libudev-dev libical-dev libreadline-dev
Next, download and uncompress the package:
sudo wget https://www.kernel.org/pub/linux/bluetooth/bluez-4.101.tar.xz
sudo tar xvf bluez-4.101.tar
Change to the bluez folder; configure and install the package:
cd bluez-4.101
sudo ./configure --disable-systemd
sudo make
sudo make install
Finally, change the location of the hciconfig command:
sudo mv /usr/local/sbin/hciconfig /usr/sbin
SensorTag Communication via Command Line
Once configured, you can scan and connect with a Smart device. A TI SensorTag is used in the example that follows.
Plug in the Bluetooth dongle if needed and verify that the interface is up:
sudo hciconfig hci0
If the interface is down, enable it with the following command:
sudo hciconfig hci0 up
Perform a BLE scan with hcitool (this process may be interrupted with ctrl-c):
sudo hcitool lescan
LE Scan ...
BC:6A:29:AE:CC:96 (unknown)
BC:6A:29:AE:CC:96 SensorTag
If the SensorTag is not listed, press the button on the left side of the device to make it discoverable. Interactive communication with the device is possible using the gatttool:
sudo gatttool -b BC:6A:29:AE:CC:96 -I
[ ][BC:6A:29:AE:CC:96][LE]> connect
[CON][BC:6A:29:AE:CC:96][LE]>
In order to read the sensor values from the SensorTag, you need to write some registers on the device. For details, please refer to the CC2541 user guide: http://processors.wiki.ti.com/index.php/SensorTag_User_Guide. However, note that the reported BLE handles are not up-to-date on this page. Also refer to this updated attribute table: http://processors.wiki.ti.com/images/archive/a/a8/20130111154127!BLE_SensorTag_GATT_Server.pdf.
The example that follows shows the procedure for retrieving the temperature value from the SensorTag.
Once connected with gatttool, the IR temperature sensor is enabled to write the value 01 to the handle 0x0029:
[CON][BC:6A:29:AE:CC:96][LE]> char-write-cmd 0x0029 01
Next, the temperature value is read from the 0x0025 handle:
[CON][BC:6A:29:AE:CC:96][LE]> char-read-hnd 0x0025
[CON][BC:6A:29:AE:CC:96][LE]>
Characteristic value/descriptor: a7 fe 2c 0d
In accordance with the documentation, the retrieved raw values have to be refined in order to obtain the ambient and object temperature.
Enable notifications writing the value 0001 to the 0x0026 register:
[CON][BC:6A:29:AE:CC:96][LE]> char-write-cmd 0x0026 0100
[CON][BC:6A:29:AE:CC:96][LE]>
Notification handle = 0x0025 value: a5 fe 3c 0d
[CON][BC:6A:29:AE:CC:96][LE]>
Notification handle = 0x0025 value: 9f fe 3c 0d
[CON][BC:6A:29:AE:CC:96][LE]>
Notification handle = 0x0025 value: 9a fe 3c 0d
Stop the notifications by writing 0000 to the same register:
[CON][BC:6A:29:AE:CC:96][LE]>
Notification handle = 0x0025 value: 9e fe 3c 0d
[CON][BC:6A:29:AE:CC:96][LE]> char-write-cmd 0x0026 000
Notification handle = 0x0025 value: a3 fe 3c 0d
[CON][BC:6A:29:AE:CC:96][LE]>
BLE Bundle for TI SensorTag
The BLE bundle performs the following operations:
-
Starts a scan for smart devices (lescan)
-
Selects all the TI SensorTag in range
-
Connects to the discovered SensorTags and discovers their capabilities
-
Reads data from all the sensors onboard and writes the values in the log file
Develop the BLE Bundle
Once the required packages are installed and communication with the SensorTag via command line is established, you may start to develop the BLE bundle as follows:
NOTE: For more detailed information about bundle development (i.e., the plug-in project, classes, and MANIFEST file configuration), please refer to the Hello World Example located here.
-
Create a Plug-in Project named org.eclipse.kura.example.ble.tisensortag.
- Create the following classes: BluetoothLe, TiSensorTag, and TiSensorTagGatt.
- Include the following bundles in the MANIFEST.MF:
- org.eclipse.kura.bluetooth
- org.eclipse.kura.configuration
- org.eclipse.kura.message
- org.osgi.service.component
- org.slf4j
The following files need to be implemented in order to write the source code:
-
META-INF/MANIFEST.MF - OSGI manifest that describes the bundle and its dependencies.
-
OSGI-INF/bleExample.xml - declarative services definition that describes the services exposed and consumed by this bundle.
-
OSGI-INF/metatype/com.eurotech.example.ble.tisensortag.BluetoothLe.xml - configuration description of the bundle and its parameters, types, and defaults.
-
com.eurotech.example.ble.tisensortag.BluetoothLe.java - main implementation class.
-
com.eurotech.example.ble.tisensortag.TiSensorTag.java - class used to connect with a TI SensorTag.
-
com.eurotech.example.ble.tisensortag.TiSensorTagGatt.java - class that describes all the handles and UUIDs to access to the SensorTag sensors.
OSGI-INF/metatype/com.eurotech.example.ble.tisensortag.BluetoothLe.xml File
The OSGI-INF/metatype/com.eurotech.example.ble.tisensortag.BluetoothLe.xml file describes the parameters for this bundle including the following:
-
cc2650 - defines the type of TI Sensor Tag; CC2650 is selected if the parameter is true.
-
scan_time - specifies the length of time to scan for devices in seconds.
-
period - specifies the time interval in seconds between two publishes.
-
publishTopic - supplies the topic to publish data to the cloud.
com.eurotech.example.ble.tisensortag.BluetoothLe.java File
The com.eurotech.example.ble.tisensortag.BluetoothLe.java file contains the activate and deactivate methods for this bundle. The activate method gets the BluetoothAdapter and defines a ScheduledExecutorService, which schedules the execution of the updateSensors method every second. The following code sample shows part of the activate method:
try {
m_cloudClient = m_cloudService.newCloudClient(APP_ID);
m_cloudClient.addCloudClientListener(this);
// Get Bluetooth adapter and ensure it is enabled
m_bluetoothAdapter = m_bluetoothService.getBluetoothAdapter();
if (m_bluetoothAdapter != null) {
s_logger.info("Bluetooth adapter address => " + m_bluetoothAdapter.getAddress());
s_logger.info("Bluetooth adapter le enabled => " + m_bluetoothAdapter.isLeReady());
if (!m_bluetoothAdapter.isEnabled()) {
s_logger.info("Enabling bluetooth adapter...");
m_bluetoothAdapter.enable();
s_logger.info("Bluetooth adapter address => " + m_bluetoothAdapter.getAddress());
}
m_workerCount = 0;
m_found = false;
m_connected = false;
m_startTime = 0;
m_handle = m_worker.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
updateSensors();
}
}, 0, 1, TimeUnit.SECONDS);
}
else s_logger.warn("No Bluetooth adapter found ...");
} catch (Exception e) {
s_logger.error("Error starting component", e);
throw new ComponentException(e);
}
The updateSensors code starts and stops the scanning procedure. If a SensorTag is detected, the bundle tries to connect to it and reads the temperature from the on-board sensor. The updateSensors code is shown below.
void updateSensors() {
// Scan
if(!m_found){
if(m_bluetoothAdapter.isScanning()){
s_logger.debug("m_bluetoothAdapter.isScanning");
if((System.currentTimeMillis() - m_startTime) >= (m_scantime*1000)){
m_bluetoothAdapter.killLeScan();
}
}
else{
s_logger.info("startLeScan");
m_bluetoothAdapter.startLeScan(this);
m_startTime = System.currentTimeMillis();
}
}
else if(m_bluetoothAdapter.isScanning()){
m_bluetoothAdapter.killLeScan();
}
// connect SensorTag
if(m_found){
if(!m_connected){
s_logger.info("Found, connecting...");
m_connected = myTiSensorTag.connect();
if(m_connected){
myTiSensorTag.enableTemperatureSensor(m_cc2650);
myTiSensorTag.enableTemperatureNotifications(m_cc2650);
}
else {
s_logger.info("Cannot connect to TI SensorTag " + myTiSensorTag.getBluetoothDevice().getAdress() + ".");
}
}
// Temperature
if(myTiSensorTag.isTemperatureReceived()){
myTiSensorTag.setTemperatureReceived(false);
}
}
}
com.eurotech.example.ble.sensortag.TiSensorTag.java File
The com.eurotech.example.ble.sensortag.TiSensorTag.java file is used to connect and disconnect to the SensorTag. It also contains the methods to configure and read data from the sensor. The connection method uses the BluetoothGatt Service as shown below:
public boolean connect() {
m_bluetoothGatt = m_device.getBluetoothGatt();
boolean connected = m_bluetoothGatt.connect();
if(connected) {
m_bluetoothGatt.setBluetoothLeNotificationListener(this);
m_connected = true;
return true;
}
else {
// If connect command is not executed, close gatttool
m_bluetoothGatt.disconnect();
m_connected = false;
return false;
}
}
A set of methods for reading from and writing to the internal register of the device are included in the file. The following code sample presents the methods to manage the temperature sensor.
// ---------------------------------------------------------------------------------------------
//
// Temperature Sensor, reference: http://processors.wiki.ti.com/index.php/SensorTag_User_Guide
//
// ---------------------------------------------------------------------------------------------
/*
* Enable temperature sensor
*/
public void enableTemperatureSensor(boolean cc2650) {
// Write "01" to 0x29 to enable temperature sensor
if(cc2650)
m_bluetoothGatt.writeCharacteristicValue(TiSensorTagGatt.HANDLE_TEMP_SENSOR_ENABLE_2650, "01");
else
m_bluetoothGatt.writeCharacteristicValue(TiSensorTagGatt.HANDLE_TEMP_SENSOR_ENABLE_2541, "01");
}
/*
* Disable temperature sensor
*/
public void disableTemperatureSensor(boolean cc2650) {
// Write "00" to 0x29 to enable temperature sensor
if(cc2650)
m_bluetoothGatt.writeCharacteristicValue(TiSensorTagGatt.HANDLE_TEMP_SENSOR_ENABLE_2650, "00");
else
m_bluetoothGatt.writeCharacteristicValue(TiSensorTagGatt.HANDLE_TEMP_SENSOR_ENABLE_2541, "00");
}
/*
* Read temperature sensor
*/
public String readTemperature(String handleValue) {
// Read value from handle 0x25
return m_bluetoothGatt.readCharacteristicValue(handleValue);
}
/*
* Read temperature sensor by UUID
*/
public String readTemperatureByUuid(UUID uuid) {
return m_bluetoothGatt.readCharacteristicValueByUuid(uuid);
}
/*
* Enable temperature notifications
*/
public void enableTemperatureNotifications(boolean cc2650) {
//Write "01:00 to 0x26 to enable notifications
if(cc2650)
m_bluetoothGatt.writeCharacteristicValue(TiSensorTagGatt.HANDLE_TEMP_SENSOR_NOTIFICATION_2650, "01:00");
else
m_bluetoothGatt.writeCharacteristicValue(TiSensorTagGatt.HANDLE_TEMP_SENSOR_NOTIFICATION_2541, "01:00");
}
/*
* Disable temperature notifications
*/
public void disableTemperatureNotifications(boolean cc2650) {
//Write "00:00 to 0x26 to enable notifications
if(cc2650)
m_bluetoothGatt.writeCharacteristicValue(TiSensorTagGatt.HANDLE_TEMP_SENSOR_NOTIFICATION_2650, "00:00");
else
m_bluetoothGatt.writeCharacteristicValue(TiSensorTagGatt.HANDLE_TEMP_SENSOR_NOTIFICATION_2541, "00:00");
}
/*
* Calculate temperature
*/
private double calculateTemperature(double obj, double amb) {
double Vobj2 = obj;
Vobj2 *= 0.00000015625;
double Tdie = (amb / 128.0) + 273.15;
double S0 = 5.593E-14; // Calibration factor
double a1 = 1.75E-3;
double a2 = -1.678E-5;
double b0 = -2.94E-5;
double b1 = -5.7E-7;
double b2 = 4.63E-9;
double c2 = 13.4;
double Tref = 298.15;
double S = S0*(1+a1*(Tdie - Tref)+a2*Math.pow((Tdie - Tref),2));
double Vos = b0 + b1*(Tdie - Tref) + b2*Math.pow((Tdie - Tref),2);
double fObj = (Vobj2 - Vos) + c2*Math.pow((Vobj2 - Vos),2);
double tObj = Math.pow(Math.pow(Tdie,4) + (fObj/S),.25);
return tObj - 273.15;
}
Deploy and Validate the Bundle
In order to proceed, you need to know the IP address of your embedded gateway that is on the remote target unit. Once you do, follow the mToolkit instructions for installing a single bundle to the remote target unit. When this installation completes, the bundle starts automatically. You should see a message similar to the one below from /var/log/esf.log indicating that the bundle was successfully installed and configured, and started to search for TI SensorTags.
2015-06-05 14:54:29,978 [Component Resolve Thread (Bundle 6)] INFO o.e.k.e.b.t.BluetoothLe - Activating BluetoothLe example...
2015-06-05 14:54:30,551 [Component Resolve Thread (Bundle 6)] INFO o.e.k.e.b.t.BluetoothLe - Bluetooth adapter address => null
2015-06-05 14:54:30,554 [Component Resolve Thread (Bundle 6)] INFO o.e.k.e.b.t.BluetoothLe - Bluetooth adapter le enabled => false
2015-06-05 14:54:30,576 [Component Resolve Thread (Bundle 6)] INFO o.e.k.e.b.t.BluetoothLe - Enabling bluetooth adapter...
2015-06-05 14:54:31,587 [Component Resolve Thread (Bundle 6)] INFO o.e.k.e.b.t.BluetoothLe - Bluetooth adapter address => 5C:F3:70:60:63:9E
2015-06-05 14:54:31,685 [pool-21-thread-1] INFO o.e.k.e.b.t.BluetoothLe - startLeScan
2015-06-05 14:54:31,693 [Component Resolve Thread (Bundle 6)] INFO o.e.k.c.c.ConfigurableComponentTracker - Adding ConfigurableComponent org.eclipse.kura.example.ble.tisensortag.BluetoothLe
2015-06-05 14:54:31,700 [Component Resolve Thread (Bundle 6)] INFO o.e.k.c.c.ConfigurationServiceImpl - Registration of ConfigurableComponent org.eclipse.kura.example.ble.tisensortag.BluetoothLe by org.eclipse.kura.core.configuration.ConfigurationServiceImpl@1075fdf...
2015-06-05 14:54:31,738 [pool-21-thread-1] INFO o.e.k.l.b.l.BluetoothLeScanner - Starting bluetooth le scan...
2015-06-05 14:54:31,757 [Component Resolve Thread (Bundle 6)] INFO o.e.k.c.c.ConfigurationServiceImpl - Registering org.eclipse.kura.example.ble.tisensortag.BluetoothLe with ocd: org.eclipse.kura.core.configuration.metatype.Tocd@17e09e5 ...
2015-06-05 14:54:31,761 [Component Resolve Thread (Bundle 6)] INFO o.e.k.c.c.ConfigurationServiceImpl - Registration Completed for Component org.eclipse.kura.example.ble.tisensortag.BluetoothLe.
2015-06-05 14:54:52,680 [pool-21-thread-1] INFO o.e.k.l.b.l.BluetoothLeScanner - Killing hcitool...
2015-06-05 14:55:36,814 [BluetoothProcess Input Stream Gobbler] INFO o.e.k.l.b.u.BluetoothProcess - EEE LE Scan ...
BC:6A:29:AC:4E:23 (unknown)
BC:6A:29:AC:4E:23 SensorTag
2015-06-05 14:55:36,820 [pool-21-thread-1] INFO o.e.k.l.b.u.BluetoothProcess - Closing streams and killing...
2015-06-05 14:55:36,827 [BluetoothProcess Input Stream Gobbler] INFO o.e.k.l.b.l.BluetoothLeScanner - LE Scan ...
2015-06-05 14:55:36,873 [BluetoothProcess Input Stream Gobbler] INFO o.e.k.l.b.l.BluetoothLeScanner - BC:6A:29:AC:4E:23 (unknown)
2015-06-05 14:55:36,888 [BluetoothProcess Input Stream Gobbler] INFO o.e.k.l.b.l.BluetoothLeScanner - BC:6A:29:AC:4E:23 SensorTag
2015-06-05 14:55:36,909 [BluetoothProcess Input Stream Gobbler] INFO o.e.k.l.b.l.BluetoothLeScanner - m_scanResult.add BC:6A:29:AC:4E:23 - SensorTag
2015-06-05 14:55:36,928 [BluetoothProcess Input Stream Gobbler] INFO o.e.k.e.b.t.BluetoothLe - Address BC:6A:29:AC:4E:23 Name SensorTag
2015-06-05 14:55:36,931 [BluetoothProcess Input Stream Gobbler] INFO o.e.k.e.b.t.BluetoothLe - TI SensorTag BC:6A:29:AC:4E:23 found.
2015-06-05 14:55:37,680 [pool-21-thread-1] INFO o.e.k.e.b.t.BluetoothLe - Found, connecting...
2015-06-05 14:55:37,826 [pool-21-thread-1] INFO o.e.k.l.b.l.BluetoothGattImpl - Sending connect message...
2015-06-05 14:55:38,425 [BluetoothProcess Input Stream Gobbler] INFO o.e.k.l.b.l.BluetoothGattImpl - Receiving notification: Notification handle = 0x0025 value: ab fe 70 0c
2015-06-05 14:55:38,458 [BluetoothProcess Input Stream Gobbler] INFO o.e.k.e.b.t.TiSensorTag - Received temp value: Ambient: 24.875 Target: 20.745652488725113
2015-06-05 14:55:39,369 [BluetoothProcess Input Stream Gobbler] INFO o.e.k.l.b.l.BluetoothGattImpl - Receiving notification: Notification handle = 0x0025 value: b5 fe 74 0c
2015-06-05 14:55:39,373 [BluetoothProcess Input Stream Gobbler] INFO o.e.k.e.b.t.TiSensorTag - Received temp value: Ambient: 24.90625 Target: 21.056125826758375
2015-06-05 14:55:40,367 [BluetoothProcess Input Stream Gobbler] INFO o.e.k.l.b.l.BluetoothGattImpl - Receiving notification: Notification handle = 0x0025 value: ab fe 74 0c
Cloud-enabled BLE Bundle
Optionally, the bundle may be modified to publish the retrieved sensor values to the cloud. This part of the tutorial requires an active Everyware Cloud account.
In this section, you can enable the application bundle to communicate with the Everyware Cloud. This capability requires some modifications to the ESF bundle application. With this feature, instead of writing the sensor values on the log file, ESF publishes a message containing the values to the Everyware Cloud using the CloudClient service. Each sensor value is published on a specific topic containing the sensor name (temperature, accelerometer, humidity, etc.).
Before continuing, make sure your embedded device is already connected to the Everyware Cloud.
Modify the Code
To publish the received sensor values to the cloud, you need to modify the file com.eurotech.example.ble.sensortag.BluetoothLe.java. In the case of temperature sensor, the updateSensors will run the doPublishTemp method as shown below.
void updateSensors() {
// Scan
if(!m_found){
if(m_bluetoothAdapter.isScanning()){
s_logger.debug("m_bluetoothAdapter.isScanning");
if((System.currentTimeMillis() - m_startTime) >= (m_scantime*1000)){
m_bluetoothAdapter.killLeScan();
}
}
else{
s_logger.info("startLeScan");
m_bluetoothAdapter.startLeScan(this);
m_startTime = System.currentTimeMillis();
}
}
else if(m_bluetoothAdapter.isScanning()){
m_bluetoothAdapter.killLeScan();
}
// connect SensorTag
if(m_found){
if(!m_connected){
s_logger.info("Found, connecting...");
m_connected = myTiSensorTag.connect();
if(m_connected){
myTiSensorTag.enableTemperatureSensor(m_cc2650);
myTiSensorTag.enableTemperatureNotifications(m_cc2650);
}
else {
s_logger.info("Cannot connect to TI SensorTag " + myTiSensorTag.getBluetoothDevice().getAdress() + ".");
}
}
// Temperature
if(myTiSensorTag.isTemperatureReceived()){
if(m_workerCount==m_pubrate)
doPublishTemp(myTiSensorTag.getBluetoothDevice().getAdress(), myTiSensorTag.getTempAmbient(), myTiSensorTag.getTempTarget());
myTiSensorTag.setTemperatureReceived(false);
}
}
m_workerCount++;
if(m_workerCount>m_pubrate){
m_workerCount=0;
}
}
The following doPublishTemp method creates a KuraPayload object and uses the CloudClient service to publish it on a specific topic:
private void doPublishTemp(String address, Object ambValue, Object targetValue) {
if(m_topic!=null){
KuraPayload payload = new KuraPayload();
payload.setTimestamp(new Date());
payload.addMetric("Ambient", ambValue);
payload.addMetric("Target", targetValue);
try {
m_cloudClient.publish(m_topic+"/"+address + "/temperature", payload, 0, false);
} catch (Exception e) {
s_logger.error("Can't publish message, " + "temperature", e);
}
}
}
Once the code is updated, the bundle has to be redeployed. As with the previous version of the code, the BLE bundle searches for a SensorTag and once connected, it writes the sensor values in the log file. In addition, the bundle publishes a message for every sensor value received. You can see them in the console of your account in the Everyware Cloud.