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.
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 a recent post, we went over the process of setting up a custom BLE service
encapsulating a read-only characteristic. In this document, we'd like to
capture the creation of a service with a write-only characteristic. Taken
together, these would then form the basis for most BLE services.
Let's work our way towards creating a service for a trivial actuator: an LED.
We'll assume a use-case where a phone-app would like to connect to this mbed
application and set the LED state. In the non-connected state, the application
simply advertises its capability of providing an LED service.
Here's some template code to get started with the very basics. We've thrown in
a blinking LED to indicate program aliveness. We'll soon introduce a second
LED to represent the actuator.
#include "mbed.h"
#include "BLEDevice.h"
BLEDevice ble;
DigitalOut alivenessLED(LED1);
const static char DEVICE_NAME[] = "LED";
void disconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason)
{
ble.startAdvertising();
}
void periodicCallback(void)
{
alivenessLED = !alivenessLED; /* Do blinky on LED1 to indicate system aliveness. */
}
int main(void)
{
alivenessLED = 0;
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 "LED" 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 an LED. This
service will have a single write-only Characteristic holding a boolean for
LED state.
Bluetooth Smart requires the use of UUIDs to identify 'types' for all involved
entities. We'll need UUIDs for the LED service 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 LED service: 0xA000 for the
service, and 0xA001 for the contained characteristic. This avoids collision
with the standard UUIDs.
#define LED_SERVICE_UUID 0xA000
#define LED_STATE_CHARACTERISTIC_UUID 0xA001
...
static const uint16_t uuid16_list[] = {LED_SERVICE_UUID};
...
ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, (uint8_t *)uuid16_list, sizeof(uuid16_list));
The part above where we advertise the LEDService 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 LED-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 write-only characteristic encapsulating boolean
state. The constructor for LEDState takes in the UUID and a pointer to the
initial value of the characteristic.
bool initialValueForLEDCharacteristic = false;
WriteOnlyGattCharacteristic<bool> ledState(LED_STATE_CHARACTERISTIC_UUID, &initialValueForLEDCharacteristic);
There are several variants of GattCharacterisitc available to ease
instantiation. Other common types are: ReadOnlyGattCharacterisitc<T> and
ReadWriteGattCharacteristic<T>.
Refer to template declarations at the bottom of https://github.com/mbedmicro/BLE_API/blob/master/public/GattCharacteristic.h.
We could just as easily have used a ReadWriteGattCharacterisitc<T> for the
ledState, to make it readable as well. This will allow a phone app to connect
and probe the ledState.
bool initialValueForLEDCharacteristic = false;
ReadWriteGattCharacteristic<bool> ledState(LED_STATE_CHARACTERISTIC_UUID, &initialValueForLEDCharacteristic);
The ledState characteristic can be used to construct a GattService, which
we'll call 'ledService'. 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[] = {&ledState};
GattService ledService(LED_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
ble.addService(ledService);
So, now we have the following code which defines a custom led service
containing a readable and writable characteristic.
#include "mbed.h"
#include "BLEDevice.h"
BLEDevice ble;
DigitalOut alivenessLED(LED1);
#define LED_SERVICE_UUID 0xA000
#define LED_STATE_CHARACTERISTIC_UUID 0xA001
const static char DEVICE_NAME[] = "LED";
static const uint16_t uuid16_list[] = {LED_SERVICE_UUID};
void disconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason)
{
ble.startAdvertising();
}
void periodicCallback(void)
{
alivenessLED = !alivenessLED; /* Do blinky on LED1 to indicate system aliveness. */
}
int main(void)
{
alivenessLED = 0;
Ticker ticker;
ticker.attach(periodicCallback, 1);
ble.init();
ble.onDisconnection(disconnectionCallback);
bool initialValueForLEDCharacteristic = false;
ReadWriteGattCharacteristic<bool> ledState(LED_STATE_CHARACTERISTIC_UUID, &initialValueForLEDCharacteristic);
GattCharacteristic *charTable[] = {&ledState};
GattService ledService(LED_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
ble.addService(ledService);
/* 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 and simple transactions on the characteristic>>
Thus far, the ledState characteristic within the service has no binding to a
physical LED. The user may be able to write to this characteristic from a
phone app, but it would not actuate anything in the application.
We can introduce a real LED with the following:
DigitalOut actuatedLed(LED2);
We can now add some code to respond to client-writes to the ledState
characteristic and update the physical led. BLE_API provides a onDataWritten
callback, which can be set to an application specific handler.
The following bits of code setup callbacks for when the phone app attempts to
write to the ledState characteristic of the LEDService:
DigitalOut actuatedLed(LED2);
...
/**
* This callback allows the LEDService to receive updates to the ledState Characteristic.
*
* @param[in] params
* Information about the characterisitc being updated.
*/
void onDataWrittenCallback(const GattCharacteristicWriteCBParams *params) {
if (params->charHandle == ledState.getValueHandle()) {
/* Do something here to the actuated LED based on the received params. */
}
}
int main(void)
{
...
ble.onDataWritten(onDataWrittenCallback);
Note that within the onDataWritten callback, we can identify the
characteristic being updated using a 'value handle'. The ledState
characteristic needs to be moved into a global context in order for the
onDataWritten callback to access it. Here's the full code.
#include "mbed.h"
#include "BLEDevice.h"
BLEDevice ble;
DigitalOut alivenessLED(LED1);
DigitalOut actuatedLED(LED2);
#define LED_SERVICE_UUID 0xA000
#define LED_STATE_CHARACTERISTIC_UUID 0xA001
const static char DEVICE_NAME[] = "LED";
static const uint16_t uuid16_list[] = {LED_SERVICE_UUID};
bool initialValueForLEDCharacteristic = false;
ReadWriteGattCharacteristic<bool> ledState(LED_STATE_CHARACTERISTIC_UUID, &initialValueForLEDCharacteristic);
void disconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason)
{
ble.startAdvertising();
}
void periodicCallback(void)
{
alivenessLED = !alivenessLED; /* Do blinky on LED1 to indicate system aliveness. */
}
/**
* This callback allows the LEDService to receive updates to the ledState Characteristic.
*
* @param[in] params
* Information about the characterisitc being updated.
*/
void onDataWrittenCallback(const GattCharacteristicWriteCBParams *params) {
if ((params->charHandle == ledState.getValueHandle()) && (params->len == 1)) {
actuatedLED = *(params->data);
}
}
int main(void)
{
alivenessLED = 0;
actuatedLED = 0;
Ticker ticker;
ticker.attach(periodicCallback, 1);
ble.init();
ble.onDisconnection(disconnectionCallback);
ble.onDataWritten(onDataWrittenCallback);
GattCharacteristic *charTable[] = {&ledState};
GattService ledService(LED_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
ble.addService(ledService);
/* 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 ledState can be polled and updated; and the corresponding effect on the actuatedLED>>
The above application is complete in functionality, but has grown to be a bit
messy. In particular, most of the plumbing around creating the led service
could be substituted with a simple initialization of a 'LEDService' class while retaining the functionality.
Here's something to get started with the LEDService:
#ifndef __BLE_LED_SERVICE_H__
#define __BLE_LED_SERVICE_H__
class LEDService {
public:
const static uint16_t LED_SERVICE_UUID = 0xA000;
const static uint16_t LED_STATE_CHARACTERISTIC_UUID = 0xA001;
private:
/* private members to come */
};
#endif /* #ifndef __BLE_LED_SERVICE_H__ */
Nearly all BLE APIs require a reference to the BLEDevice; so we must require
this in the constructor. The ledState characteristic should be encapsulated
as well.
#ifndef __BLE_LED_SERVICE_H__
#define __BLE_LED_SERVICE_H__
class LEDService {
public:
const static uint16_t LED_SERVICE_UUID = 0xA000;
const static uint16_t LED_STATE_CHARACTERISTIC_UUID = 0xA001;
LEDService(BLEDevice &_ble, bool initialValueForLEDCharacteristic) :
ble(_ble), ledState(LED_STATE_CHARACTERISTIC_UUID, &initialValueForLEDCharacteristic)
{
/* empty */
}
private:
BLEDevice &ble;
ReadWriteGattCharacteristic<bool> ledState;
};
#endif /* #ifndef __BLE_LED_SERVICE_H__ */
We can move more of the setup of the service into the constructor.
#ifndef __BLE_LED_SERVICE_H__
#define __BLE_LED_SERVICE_H__
class LEDService {
public:
const static uint16_t LED_SERVICE_UUID = 0xA000;
const static uint16_t LED_STATE_CHARACTERISTIC_UUID = 0xA001;
LEDService(BLEDevice &_ble, bool initialValueForLEDCharacteristic) :
ble(_ble), ledState(LED_STATE_CHARACTERISTIC_UUID, &initialValueForLEDCharacteristic)
{
GattCharacteristic *charTable[] = {&ledState};
GattService ledService(LED_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
ble.addService(ledService);
}
private:
BLEDevice &ble;
ReadWriteGattCharacteristic<bool> ledState;
};
#endif /* #ifndef __BLE_LED_SERVICE_H__ */
And here's a small extension with a helper API to fetch the value handle for the sake of the onDataWritten callback:
#ifndef __BLE_LED_SERVICE_H__
#define __BLE_LED_SERVICE_H__
class LEDService {
public:
const static uint16_t LED_SERVICE_UUID = 0xA000;
const static uint16_t LED_STATE_CHARACTERISTIC_UUID = 0xA001;
LEDService(BLEDevice &_ble, bool initialValueForLEDCharacteristic) :
ble(_ble), ledState(LED_STATE_CHARACTERISTIC_UUID, &initialValueForLEDCharacteristic)
{
GattCharacteristic *charTable[] = {&ledState};
GattService ledService(LED_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
ble.addService(ledService);
}
GattAttribute::Handle_t getValueHandle() const {
return ledState.getValueHandle();
}
private:
BLEDevice &ble;
ReadWriteGattCharacteristic<bool> ledState;
};
#endif /* #ifndef __BLE_LED_SERVICE_H__ */
And now with this encapsulated away in the LEDService, the main application reads slightly better.
#include "mbed.h"
#include "BLEDevice.h"
#include "LEDService.h"
BLEDevice ble;
DigitalOut alivenessLED(LED1);
DigitalOut actuatedLED(LED2);
const static char DEVICE_NAME[] = "LED";
static const uint16_t uuid16_list[] = {LEDService::LED_SERVICE_UUID};
LEDService *ledServicePtr;
void disconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason)
{
ble.startAdvertising();
}
void periodicCallback(void)
{
alivenessLED = !alivenessLED; /* Do blinky on LED1 to indicate system aliveness. */
}
/**
* This callback allows the LEDService to receive updates to the ledState Characteristic.
*
* @param[in] params
* Information about the characterisitc being updated.
*/
void onDataWrittenCallback(const GattCharacteristicWriteCBParams *params) {
if ((params->charHandle == ledServicePtr->getValueHandle()) && (params->len == 1)) {
actuatedLED = *(params->data);
}
}
int main(void)
{
alivenessLED = 0;
actuatedLED = 0;
Ticker ticker;
ticker.attach(periodicCallback, 1);
ble.init();
ble.onDisconnection(disconnectionCallback);
ble.onDataWritten(onDataWrittenCallback);
bool initialValueForLEDCharacteristic = false;
LEDService ledService(ble, initialValueForLEDCharacteristic);
ledServicePtr = &ledService;
/* 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 'ledServicePtr'.
This was necessary because onDataWritten callback needs to refer to the
ledService object, but the ledService object is instantiated within the
context of main() because it needs to come after ble.init().
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.
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 a recent post, we went over the process of setting up a custom BLE service encapsulating a read-only characteristic. In this document, we'd like to capture the creation of a service with a write-only characteristic. Taken together, these would then form the basis for most BLE services.
Let's work our way towards creating a service for a trivial actuator: an LED. We'll assume a use-case where a phone-app would like to connect to this mbed application and set the LED state. In the non-connected state, the application simply advertises its capability of providing an LED service.
Here's some template code to get started with the very basics. We've thrown in a blinking LED to indicate program aliveness. We'll soon introduce a second LED to represent the actuator.
The above code doesn't create any custom service. It advertises "LED" 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 an LED. This service will have a single write-only Characteristic holding a boolean for LED state.
Bluetooth Smart requires the use of UUIDs to identify 'types' for all involved entities. We'll need UUIDs for the LED service 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 LED 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 LEDService 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 LED-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 write-only characteristic encapsulating boolean state. The constructor for LEDState 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: ReadOnlyGattCharacterisitc<T> and ReadWriteGattCharacteristic<T>. Refer to template declarations at the bottom of https://github.com/mbedmicro/BLE_API/blob/master/public/GattCharacteristic.h.
We could just as easily have used a ReadWriteGattCharacterisitc<T> for the ledState, to make it readable as well. This will allow a phone app to connect and probe the ledState.
The ledState characteristic can be used to construct a GattService, which we'll call 'ledService'. 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 led service containing a readable and writable 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 and simple transactions on the characteristic>>
Thus far, the ledState characteristic within the service has no binding to a physical LED. The user may be able to write to this characteristic from a phone app, but it would not actuate anything in the application.
We can introduce a real LED with the following:
We can now add some code to respond to client-writes to the ledState characteristic and update the physical led. BLE_API provides a onDataWritten callback, which can be set to an application specific handler.
The following bits of code setup callbacks for when the phone app attempts to write to the ledState characteristic of the LEDService:
Note that within the onDataWritten callback, we can identify the characteristic being updated using a 'value handle'. The ledState characteristic needs to be moved into a global context in order for the onDataWritten callback to access it. Here's the full code.
<<add snapshots showing how ledState can be polled and updated; and the corresponding effect on the actuatedLED>>
The above application is complete in functionality, but has grown to be a bit messy. In particular, most of the plumbing around creating the led service could be substituted with a simple initialization of a 'LEDService' class while retaining the functionality.
Here's something to get started with the LEDService:
Nearly all BLE APIs require a reference to the BLEDevice; so we must require this in the constructor. The ledState 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 fetch the value handle for the sake of the onDataWritten callback:
And now with this encapsulated away in the LEDService, the main application reads slightly better.
And now a little thing at the end. Notice that we've setup a 'ledServicePtr'. This was necessary because onDataWritten callback needs to refer to the ledService object, but the ledService object is instantiated within the context of main() because it needs to come after ble.init().