Bluetooth Low Energy (a.k.a Bluetooth LE, BTLE, Bluetooth Smart)

template for an input service

18 Feb 2015

With mbed/BLE, we offer a growing set of SIG defined BLE services implemented as C++ headers to ease application development. These can be found under https://github.com/mbedmicro/BLE_API/tree/master/services.

If, for instance, you needed to develop a heart-rate application for an mbed platform, you could get started with the BLE_HeartRate demo. This demo instantiates the HeartRateService, which takes care of majority of the BLE plumbing and offers high-level APIs to work with service configuration and sensor values. You'd need to add custom code to poll sensor data periodically, and the rest gets taken care of by the HeartRateService.

We expect our users to be developing applications for custom sensors and actuators, often outside the scope of the standard Bluetooth services, or the service templates offered by mbed-BLE. For custom applications, users will need to use the BLE_API to setup services (and characteristics) for the Bluetooth stack, or even model their services as C++ classes for ease of use (and reuse). In this post we'd like to capture the process of creating a C++ class to encapsulate a BLE service for a trivial sensor: a button.

We'll assume a use-case where a phone-app would like to connect to this mbed application and poll for button state; notifications could also be setup for asynchronous updates. In the non-connected state, the application simply advertises its capability of providing the button service.

Here's some template code to get started with the very basics. We've thrown in a blinking LED to indicate program stability.

#include "mbed.h"
#include "BLEDevice.h"

BLEDevice  ble;
DigitalOut led1(LED1);

const static char DEVICE_NAME[] = "Button";

void disconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason)
{
    ble.startAdvertising();
}

void periodicCallback(void)
{
    led1 = !led1; /* Do blinky on LED1 to indicate system aliveness. */
}

int main(void)
{
    led1 = 1;
    Ticker ticker;
    ticker.attach(periodicCallback, 1);

    ble.init();
    ble.onDisconnection(disconnectionCallback);

    /* setup advertising */
    ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
    ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble.setAdvertisingInterval(Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(1000)); /* 1000ms. */
    ble.startAdvertising();

    while (true) {
        ble.waitForEvent();
    }
}

The above code doesn't create any custom service. It advertises "Button" as the device-name through the advertisement payload. The application is discoverable (LE_GENERAL_DISCOVERABLE) and connectible (ADV_CONNECTABLE_UNDIRECTED), and offers only the standard GAP and GATT services. 'disconnectionCallback' re-starts advertisements following a disconnection. This trivial template just gets you off the ground.

<<add snapshots of how the services/application appears when scanned from a phone>>

Now, let's get down to the business of creating a BLE Service for button. This service will have a single read-only Characteristic holding a boolean for button state.

Bluetooth Smart requires the use of UUIDs to identify 'types' for all involved entities. We'll need UUIDs for the button serivce and the encapsulated characteristic. If we had been creating one of the standard SIG defined services, we'd have followed the standard UUID definitions https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx.

We've chosen a custom UUID space for our button service: 0xA000 for the service, and 0xA001 for the contained characteristic. This avoids collision with the standard UUIDs.

#define BUTTON_SERVICE_UUID              0xA000
#define BUTTON_STATE_CHARACTERISTIC_UUID 0xA001

...

static const uint16_t uuid16_list[] = {BUTTON_SERVICE_UUID};

...

    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));

The part above where we advertise the ButtonService UUID in the advertising payload is purely optional. Having it is good practice, since it gives an early and cheap indication to interested client apps regarding the capabilities of the mbed application. Caveat: interpreting non-standard service UUIDs has limited use, and may only work with custom phone apps. While developing your demo, you could still use generic BLE apps capable of service- discovery and simple read/write transactions on characteristics; one example of this would be Nordic's Master Control Panel.

The button-state characteristic can be defined with the following snippet from some context where memory allocations remain persistent for the lifetime of the application. This could be from within the main() function, for example. The code only looks complicated, but it is essentially a simple use of C++ templates to instantiate a read-only characteristic encapsulating boolean state. The constructor for buttonState takes in the UUID and a pointer to the initial value of the characteristic.

    bool buttonPressed = false;
    ReadOnlyGattCharacteristic<bool> buttonState(BUTTON_STATE_CHARACTERISTIC_UUID, &buttonPressed);

There are several variants of GattCharacterisitc available to ease instantiation. Other common types are: WriteOnlyGattCharacterisitc<T> and ReadWriteGattCharacteristic<T>. Refer to template declarations at the bottom of https://github.com/mbedmicro/BLE_API/blob/master/public/GattCharacteristic.h.

The above definition for the buttonState characteristic may be enhanced to allow 'notifications.' This is possible through the use of the optional parameters to specify additional properties.

    ReadOnlyGattCharacteristic<bool> buttonState(BUTTON_STATE_CHARACTERISTIC_UUID, &buttonPressed, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY);

<<add some small blurb about why notifications are useful>>

The buttonState characteristic can be used to construct a GattService, which we'll call 'buttonService'. This is done through a little bit of C/C++ syntax to create a one-element array using an initializer list of pointers to GattCharacteristics. This service can be added to the BLE stack using BLEDevice::addService().

    GattCharacteristic *charTable[] = {&buttonState};
    GattService         buttonService(BUTTON_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
    ble.addService(buttonService);

So, now we have the following code which defines a custom button service containing a read-only characteristic.

#include "mbed.h"
#include "BLEDevice.h"

BLEDevice  ble;
DigitalOut led1(LED1);

#define BUTTON_SERVICE_UUID              0xA000
#define BUTTON_STATE_CHARACTERISTIC_UUID 0xA001

const static char     DEVICE_NAME[] = "Button";
static const uint16_t uuid16_list[] = {BUTTON_SERVICE_UUID};

void disconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason)
{
    ble.startAdvertising();
}

void periodicCallback(void)
{
    led1 = !led1; /* Do blinky on LED1 to indicate system aliveness. */
}

int main(void)
{
    led1 = 1;
    Ticker ticker;
    ticker.attach(periodicCallback, 1);

    ble.init();
    ble.onDisconnection(disconnectionCallback);

    bool buttonPressed = false;
    ReadOnlyGattCharacteristic<bool> buttonState(BUTTON_STATE_CHARACTERISTIC_UUID, &buttonPressed, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY);

    GattCharacteristic *charTable[] = {&buttonState};
    GattService         buttonService(BUTTON_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
    ble.addService(buttonService);

    /* setup advertising */
    ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
    ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble.setAdvertisingInterval(Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(1000)); /* 1000ms. */
    ble.startAdvertising();

    while (true) {
        ble.waitForEvent();
    }
}

<<based on this state of the code, add some snapshots on how the service/characteristic appears from a generic phone app capable of service discovery>>

Thus far, the buttonState characteristic within the service has been static. We can now add some code to respond to changes in the button state and update the characteristic. Updating characteristic values can be achieved using the BLEDevice::updateCharacteristicValue() API.

The following bits of code setup callbacks for when button1 is pressed or released:

InterruptIn button(BUTTON1);

...

void buttonPressedCallback(void)
{
    buttonPressed = true;
    ble.updateCharacteristicValue(buttonState.getValueHandle(), (uint8_t *)&buttonPressed, sizeof(bool));
}

void buttonReleasedCallback(void)
{
    buttonPressed = false;
    ble.updateCharacteristicValue(buttonState.getValueHandle(), (uint8_t *)&buttonPressed, sizeof(bool));
}

...

int main(void)
{
...
    button.fall(buttonPressedCallback);
    button.rise(buttonReleasedCallback);

Note that updateCharacteristicValue() identifies the buttonState characteristic using a 'value handle'. The buttonState characteristic needs to be moved into a global context in order for the button callbacks to access it. Here's the full code.

#include "mbed.h"
#include "BLEDevice.h"

BLEDevice   ble;
DigitalOut  led1(LED1);
InterruptIn button(BUTTON1);

#define BUTTON_SERVICE_UUID              0xA000
#define BUTTON_STATE_CHARACTERISTIC_UUID 0xA001

const static char     DEVICE_NAME[] = "Button";
static const uint16_t uuid16_list[] = {BUTTON_SERVICE_UUID};

bool buttonPressed = false;
ReadOnlyGattCharacteristic<bool> buttonState(BUTTON_STATE_CHARACTERISTIC_UUID, &buttonPressed, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY);

void buttonPressedCallback(void)
{
    buttonPressed = true;
    ble.updateCharacteristicValue(buttonState.getValueHandle(), (uint8_t *)&buttonPressed, sizeof(bool));
}

void buttonReleasedCallback(void)
{
    buttonPressed = false;
    ble.updateCharacteristicValue(buttonState.getValueHandle(), (uint8_t *)&buttonPressed, sizeof(bool));
}

void disconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason)
{
    ble.startAdvertising();
}

void periodicCallback(void)
{
    led1 = !led1; /* Do blinky on LED1 to indicate system aliveness. */
}

int main(void)
{
    led1 = 1;
    Ticker ticker;
    ticker.attach(periodicCallback, 1);
    button.fall(buttonPressedCallback);
    button.rise(buttonReleasedCallback);

    ble.init();
    ble.onDisconnection(disconnectionCallback);

    GattCharacteristic *charTable[] = {&buttonState};
    GattService         buttonService(BUTTON_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
    ble.addService(buttonService);

    /* setup advertising */
    ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
    ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble.setAdvertisingInterval(Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(1000)); /* 1000ms. */
    ble.startAdvertising();

    while (true) {
        ble.waitForEvent();
    }
}

<<add snapshots showing how notifications can be enabled; and how the phone app updates the buttonState>>

The above application is complete in functionality, but has grown to be a bit messy. In particular, most of the plumbing around creating the button service could be substituted with a simple initialization of a 'ButtonService' class while retaining the functionality.

Here's something to get started with the ButtonService:

#ifndef __BLE_BUTTON_SERVICE_H__
#define __BLE_BUTTON_SERVICE_H__

class ButtonService {
public:
    const static uint16_t BUTTON_SERVICE_UUID              = 0xA000;
    const static uint16_t BUTTON_STATE_CHARACTERISTIC_UUID = 0xA001;

private:
    /* private members to come */
};

#endif /* #ifndef __BLE_BUTTON_SERVICE_H__ */

Nearly all BLE APIs require a reference to the BLEDevice; so we must require this in the constructor. The buttonState characteristic should be encapsulated as well.

#ifndef __BLE_BUTTON_SERVICE_H__
#define __BLE_BUTTON_SERVICE_H__

class ButtonService {
public:
    const static uint16_t BUTTON_SERVICE_UUID              = 0xA000;
    const static uint16_t BUTTON_STATE_CHARACTERISTIC_UUID = 0xA001;

    ButtonService(BLEDevice &_ble, bool buttonPressedInitial) :
        ble(_ble), buttonState(BUTTON_STATE_CHARACTERISTIC_UUID, &buttonPressedInitial, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY)
    {
        /* empty */
    }

private:
    BLEDevice                        &ble;
    ReadOnlyGattCharacteristic<bool>  buttonState;
};

#endif /* #ifndef __BLE_BUTTON_SERVICE_H__ */

We can move more of the setup of the service into the constructor.

#ifndef __BLE_BUTTON_SERVICE_H__
#define __BLE_BUTTON_SERVICE_H__

class ButtonService {
public:
    const static uint16_t BUTTON_SERVICE_UUID              = 0xA000;
    const static uint16_t BUTTON_STATE_CHARACTERISTIC_UUID = 0xA001;

    ButtonService(BLEDevice &_ble, bool buttonPressedInitial) :
        ble(_ble), buttonState(BUTTON_STATE_CHARACTERISTIC_UUID, &buttonPressedInitial, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY)
    {
        GattCharacteristic *charTable[] = {&buttonState};
        GattService         buttonService(ButtonService::BUTTON_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
        ble.addService(buttonService);
    }

private:
    BLEDevice                        &ble;
    ReadOnlyGattCharacteristic<bool>  buttonState;
};

#endif /* #ifndef __BLE_BUTTON_SERVICE_H__ */

And here's a small extension with a helper API to update button state:

#ifndef __BLE_BUTTON_SERVICE_H__
#define __BLE_BUTTON_SERVICE_H__

class ButtonService {
public:
    const static uint16_t BUTTON_SERVICE_UUID              = 0xA000;
    const static uint16_t BUTTON_STATE_CHARACTERISTIC_UUID = 0xA001;

    ButtonService(BLEDevice &_ble, bool buttonPressedInitial) :
        ble(_ble), buttonState(BUTTON_STATE_CHARACTERISTIC_UUID, &buttonPressedInitial, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY)
    {
        GattCharacteristic *charTable[] = {&buttonState};
        GattService         buttonService(ButtonService::BUTTON_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
        ble.addService(buttonService);
    }

    void updateButtonState(bool newState) {
        ble.updateCharacteristicValue(buttonState.getValueHandle(), (uint8_t *)&newState, sizeof(bool));
    }

private:
    BLEDevice                        &ble;
    ReadOnlyGattCharacteristic<bool>  buttonState;
};

#endif /* #ifndef __BLE_BUTTON_SERVICE_H__ */

And now with this encapsulated away in the ButtonService, the main application reads slightly better.

#include "mbed.h"
#include "BLEDevice.h"
#include "ButtonService.h"

BLEDevice   ble;
DigitalOut  led1(LED1);
InterruptIn button(BUTTON1);

const static char     DEVICE_NAME[] = "Button";
static const uint16_t uuid16_list[] = {ButtonService::BUTTON_SERVICE_UUID};

ButtonService *buttonServicePtr;

void buttonPressedCallback(void)
{
    buttonServicePtr->updateButtonState(true);
}

void buttonReleasedCallback(void)
{
    buttonServicePtr->updateButtonState(false);
}

void disconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason)
{
    ble.startAdvertising();
}

void periodicCallback(void)
{
    led1 = !led1; /* Do blinky on LED1 to indicate system aliveness. */
}

int main(void)
{
    led1 = 1;
    Ticker ticker;
    ticker.attach(periodicCallback, 1);
    button.fall(buttonPressedCallback);
    button.rise(buttonReleasedCallback);

    ble.init();
    ble.onDisconnection(disconnectionCallback);

    ButtonService buttonService(ble, false /* initial value for button pressed */);
    buttonServicePtr = &buttonService;

    /* setup advertising */
    ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
    ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble.setAdvertisingInterval(Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(1000)); /* 1000ms. */
    ble.startAdvertising();

    while (true) {
        ble.waitForEvent();
    }
}

And now a little thing at the end. Notice that we've setup a 'buttonServicePtr'. This was necessary because callback functions like buttonPressedCallback() need to refer to the buttonService object, but the buttonService object is instantiated within the context of main() because it needs to come after ble.init().

Et Voila.

17 Feb 2015

Hi Rohit Grover,

Thank you very much, it's very clear now ! I work with mbed since one month and it is much easier than Nordic code (in C) and Keil.

I have a code wich works now, and i understand better the Bluetooth Protocol (Gatt, Gap, characteristic, services ...).

Thanks for your work and for facilitate the BLE for beginners. Yacire.

21 Jul 2015

hello Mr. Grover, thank you for providing the code. I am a novice in coding so i am not able to understand how I can include these classes, also i am not getting device.h error in other ble codes i am trying to compile. Please help me as I am stuck with the ble nano from three months. i have a lasr doubt that when i first bootloaded the ble nano firmware i was getting the signal in my android device. but now when i power my ble it does not get detected in any of my bluetooth devices even by using ble scanner or ble controller apps.

thank you

21 Jul 2015

i got this error after i updated my libraries in order to compile the ble simple controls code. /media/uploads/jayvikram/pic_error.png

21 Jul 2015

i just want to add one induction sensor to the analog input, calculate and convert the data into readable unit through algorithm and transmit the current data to an android device.

22 Jul 2015

Hello Jay,

Please take a moment to review the docs under http://docs.mbed.org/docs/ble-intros/en/latest/GettingStarted/DevIntro/ You'll find some tutorials which run through BLE demos. Perhaps you can begin with one of these demos and modify it in small steps towards your intended application.