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.
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.
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.
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.
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.
<<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().
So, now we have the following code which defines a custom button service containing a read-only characteristic.
<<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:
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.
<<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:
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.
We can move more of the setup of the service into the constructor.
And here's a small extension with a helper API to update button state:
And now with this encapsulated away in the ButtonService, the main application reads slightly better.
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.