4. Code Overview
This section provides the code blocks needed to successfully execute this tutorial.
4.1. Custom Ble Service Header File
Under the project’s path create a new header file, for instance my_custom_service.h
, and add the following code:
#include <stdint.h>
#include <ble_service.h>
/* User-defined callback functions - Prototyping */
typedef void (* mcs_get_char_value_cb_t) (ble_service_t *svc, uint16_t conn_idx);
typedef void (* mcs_set_char_value_cb_t) (ble_service_t *svc, uint16_t conn_idx,
const uint8_t *value);
/* User-defined callback functions */
typedef struct {
/* Handler for read requests - Triggered on application context */
mcs_get_char_value_cb_t get_characteristic_value;
/* Handler for write requests - Triggered on application context */
mcs_set_char_value_cb_t set_characteristic_value;
} my_custom_service_cb_t;
/*
* \brief This function creates the custom BLE service and registers it in BLE framework
*
* \param [in] variable_value Default characteristic value
* \param [in] cb Application callback functions
*
* \return service handle
*
*/
ble_service_t *mcs_init(const uint8_t *variable_value, const my_custom_service_cb_t *cb);
/*
* This function should be called by the application as a response to a read request
*
* \param[in] svc service instance
* \param[in] conn_idx connection index
* \param[in] status ATT error
* \param[in] value attribute value
*/
void mcs_get_char_value_cfm(ble_service_t *svc, uint16_t conn_idx, att_error_t status,
const uint8_t *value);
/*
* This function should be called by the application as a response to a write request
*
* \param[in] svc service instance
* \param[in] conn_idx connection index
* \param[in] status ATT error
*/
void mcs_set_char_value_cfm(ble_service_t *svc, uint16_t conn_idx, att_error_t status);
/*
* Notify all the connected peer devices that characteristic value has been updated.
*
* \param[in] svc service instance
* \param[in] value updated characteristic value
*/
void mcs_notify_char_value_all(ble_service_t *svc, const uint8_t *value);
4.2. Custom BLE Service Source File
Under the project’s path create a new source file, for instance my_custom_service.c
, and add the following code:
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include "osal.h"
#include "ble_att.h"
#include "ble_bufops.h"
#include "ble_common.h"
#include "ble_gatt.h"
#include "ble_gatts.h"
#include "ble_storage.h"
#include "ble_uuid.h"
#include "my_custom_service.h"
#define UUID_GATT_CLIENT_CHAR_CONFIGURATION (0x2902)
static const char char_user_descriptor_val[] = "Switch ON/OFF LED D1 on DevKit";
void mcs_notify_char_value(ble_service_t *svc, uint16_t conn_idx, const uint8_t *value);
/* Service related variables */
typedef struct {
ble_service_t svc;
// User-defined callback functions
const my_custom_service_cb_t *cb;
// Attribute handles of BLE service
uint16_t mc_char_value_h;
uint16_t mc_char_value_ccc_h;
} mc_service_t;
/* This function is called upon write requests to characteristic attribute value */
static att_error_t do_char_value_write(mc_service_t *mcs, uint16_t conn_idx,
uint16_t offset, uint16_t length, const uint8_t *value)
{
uint8_t ct;
if (offset) {
return ATT_ERROR_ATTRIBUTE_NOT_LONG;
}
/* Check if the length of the envoy data exceed the maximum permitted */
if (length != 1) {
return ATT_ERROR_INVALID_VALUE_LENGTH;
}
/*
* Check whether the application has defined a callback function
* for handling the event.
*/
if (!mcs->cb || !mcs->cb->set_characteristic_value) {
return ATT_ERROR_WRITE_NOT_PERMITTED;
}
ct = get_u8(value);
/*
* The application should get the data written by the peer device.
*/
mcs->cb->set_characteristic_value(&mcs->svc, conn_idx, &ct);
return ATT_ERROR_OK;
}
/* This function is called upon write requests to CCC attribute value */
static att_error_t do_char_value_ccc_write(mc_service_t *mcs, uint16_t conn_idx,
uint16_t offset, uint16_t length, const uint8_t *value)
{
uint16_t ccc;
if (offset) {
return ATT_ERROR_ATTRIBUTE_NOT_LONG;
}
if (length != sizeof(ccc)) {
return ATT_ERROR_INVALID_VALUE_LENGTH;
}
ccc = get_u16(value);
/* Store the envoy CCC value to the ble storage */
ble_storage_put_u32(conn_idx, mcs->mc_char_value_ccc_h, ccc, true);
return ATT_ERROR_OK;
}
/* This function is called upon read requests to characteristic attribue value */
static void do_char_value_read(mc_service_t *mcs, const ble_evt_gatts_read_req_t *evt)
{
/*
* Check whether the application has defined a callback function
* for handling the event.
*/
if (!mcs->cb || !mcs->cb->get_characteristic_value) {
ble_gatts_read_cfm(evt->conn_idx, evt->handle,
ATT_ERROR_READ_NOT_PERMITTED, 0, NULL);
return;
}
/*
* The application should provide the requested data to the peer device.
*/
mcs->cb->get_characteristic_value(&mcs->svc, evt->conn_idx);
// callback executed properly
}
/*-------------------------------------------------------------------------------------*/
/*
* Notify all the connected peer devices that characteristic attribute value
* has been updated
*/
void mcs_notify_char_value_all(ble_service_t *svc, const uint8_t *value)
{
uint8_t num_conn;
uint16_t *conn_idx;
ble_gap_get_connected(&num_conn, &conn_idx);
while (num_conn--) {
mcs_notify_char_value(svc, conn_idx[num_conn], value);
}
if (conn_idx) {
OS_FREE(conn_idx);
}
}
/* Notify the peer device that characteristic attribute value has been updated */
void mcs_notify_char_value(ble_service_t *svc, uint16_t conn_idx, const uint8_t *value)
{
mc_service_t *mcs = (mc_service_t *) svc;
uint16_t ccc = 0x0000;
uint8_t pdu[1];
ble_storage_get_u16(conn_idx, mcs->mc_char_value_ccc_h, &ccc);
/*
* Check if the notifications are enabled from the peer device,
* otherwise don't send anything.
*/
if (!(ccc & GATT_CCC_NOTIFICATIONS)) {
return;
}
pdu[0] = *((uint8_t *)value);
ble_gatts_send_event(conn_idx, mcs->mc_char_value_h, GATT_EVENT_NOTIFICATION,
sizeof(pdu), pdu);
}
/*
* This function should be called by the application as a response to read requests
*/
void mcs_get_char_value_cfm(ble_service_t *svc, uint16_t conn_idx, att_error_t status,
const uint8_t *value)
{
mc_service_t *mcs = (mc_service_t *) svc;
uint8_t pdu[1];
pdu[0] = *value;
/* This function should be used as a response for every read request */
ble_gatts_read_cfm(conn_idx, mcs->mc_char_value_h, ATT_ERROR_OK,
sizeof(pdu), pdu);
}
/*
* This function should be called by the application as a response to write requests
*/
void mcs_set_char_value_cfm(ble_service_t *svc, uint16_t conn_idx, att_error_t status)
{
mc_service_t *mcs = (mc_service_t *) svc;
/* This function should be used as a response for every write request */
ble_gatts_write_cfm(conn_idx, mcs->mc_char_value_h, status);
}
/* Handler for read requests, that is BLE_EVT_GATTS_READ_REQ */
static void handle_read_req(ble_service_t *svc, const ble_evt_gatts_read_req_t *evt)
{
mc_service_t *mcs = (mc_service_t *) svc;
/*
* Identify for which attribute handle the read request has been sent to
* and call the appropriate function.
*/
if (evt->handle == mcs->mc_char_value_h) {
do_char_value_read(mcs, evt);
} else if (evt->handle == mcs->mc_char_value_ccc_h) {
uint16_t ccc = 0x0000;
/* Extract the CCC value from the ble storage */
ble_storage_get_u16(evt->conn_idx, mcs->mc_char_value_ccc_h, &ccc);
// We're little-endian - OK to write directly from uint16_t
ble_gatts_read_cfm(evt->conn_idx, evt->handle, ATT_ERROR_OK,
sizeof(ccc), &ccc);
/* Otherwise read operations are not permitted */
} else {
ble_gatts_read_cfm(evt->conn_idx, evt->handle,
ATT_ERROR_READ_NOT_PERMITTED, 0, NULL);
}
}
/* Handler for write requests, that is BLE_EVT_GATTS_WRITE_REQ */
static void handle_write_req(ble_service_t *svc, const ble_evt_gatts_write_req_t *evt)
{
mc_service_t *mcs = (mc_service_t *) svc;
att_error_t status = ATT_ERROR_WRITE_NOT_PERMITTED;
/*
* Identify for which attribute handle the write request has been sent to
* and call the appropriate function.
*/
if (evt->handle == mcs->mc_char_value_h) {
status = do_char_value_write(mcs, evt->conn_idx, evt->offset,
evt->length, evt->value);
goto done;
} else if (evt->handle == mcs->mc_char_value_ccc_h) {
status = do_char_value_ccc_write(mcs, evt->conn_idx, evt->offset,
evt->length, evt->value);
goto done;
}
done:
if (status == ((att_error_t) - 1)) {
// Write handler executed properly, will be replied by cfm call
return;
}
/* Otherwise write operations are not permitted */
ble_gatts_write_cfm(evt->conn_idx, evt->handle, status);
}
/* Function to be called after a cleanup event */
static void cleanup(ble_service_t *svc)
{
mc_service_t *mcs = (mc_service_t *) svc;
ble_storage_remove_all(mcs->mc_char_value_ccc_h);
OS_FREE(mcs);
}
/* Initialization function for My Custom Service (mcs).*/
ble_service_t *mcs_init(const uint8_t *variable_value ,const my_custom_service_cb_t *cb)
{
mc_service_t *mcs;
uint16_t num_attr;
att_uuid_t uuid;
uint16_t char_user_descriptor_h;
/* Allocate memory for the sevice hanle */
mcs = (mc_service_t *)OS_MALLOC(sizeof(*mcs));
memset(mcs, 0, sizeof(*mcs));
/* Declare handlers for specific BLE events */
mcs->svc.read_req = handle_read_req;
mcs->svc.write_req = handle_write_req;
mcs->svc.cleanup = cleanup;
mcs->cb = cb;
/*
* 0 --> Number of Included Services
* 1 --> Number of Characteristic Declarations
* 2 --> Number of Descriptors
*/
num_attr = ble_gatts_get_num_attr(0, 1, 2);
/* Service declaration */
ble_uuid_from_string("00000000-1111-2222-2222-333333333333", &uuid);
ble_gatts_add_service(&uuid, GATT_SERVICE_PRIMARY, num_attr);
/* Characteristic declaration */
ble_uuid_from_string("11111111-0000-0000-0000-111111111111", &uuid);
ble_gatts_add_characteristic(&uuid, GATT_PROP_READ | GATT_PROP_NOTIFY |
GATT_PROP_WRITE, ATT_PERM_RW, 1, GATTS_FLAG_CHAR_READ_REQ,
NULL, &mcs->mc_char_value_h);
/* Define descriptor of type Client Characteristic Configuration (CCC) */
ble_uuid_create16(UUID_GATT_CLIENT_CHAR_CONFIGURATION, &uuid);
ble_gatts_add_descriptor(&uuid, ATT_PERM_RW, 2, 0, &mcs->mc_char_value_ccc_h);
/* Define descriptor of type Characteristic User Description (CUD) */
ble_uuid_create16(UUID_GATT_CHAR_USER_DESCRIPTION, &uuid);
ble_gatts_add_descriptor(&uuid, ATT_PERM_READ, sizeof(char_user_descriptor_val),
0, &char_user_descriptor_h);
/*
* Register all the attribute handles so that they can be updated
* by the BLE manager automatically.
*/
ble_gatts_register_service(&mcs->svc.start_h, &mcs->mc_char_value_h,
&mcs->mc_char_value_ccc_h, &char_user_descriptor_h ,0);
/* Calculate the last attribute handle of the BLE service */
mcs->svc.end_h = mcs->svc.start_h + num_attr;
/* Set default attribute values */
ble_gatts_set_value(mcs->mc_char_value_h, 1, variable_value);
ble_gatts_set_value(char_user_descriptor_h, sizeof(char_user_descriptor_val),
char_user_descriptor_val);
/* Register the BLE service in BLE framework */
ble_service_add(&mcs->svc);
/* Return the service handle */
return &mcs->svc;
}
4.3. Initializing the Custom BLE Service
In ble_peripheral_task.c
file and before the definition of ble_peripheral_task()
, declare all the user-defined callback functions as well as
routines/variables for initializing the custom Bluetooth service:
#if CFG_MY_CUSTOM_SERVICE
#include "my_custom_service.h"
#include "hw_gpio.h"
#include "hw_sys.h"
/* Characteristic value */
__RETAINED_RW uint8_t mcs_char_val = 0;
/* Handle of custom BLE service */
__RETAINED_RW ble_service_t *mcs = NULL;
/* Handler for read requests */
static void mcs_get_char_val_cb(ble_service_t *svc, uint16_t conn_idx)
{
uint8_t var_value = mcs_char_val;
/* Send the requested data to the peer device. */
mcs_get_char_value_cfm(svc, conn_idx, ATT_ERROR_OK, &var_value);
}
/* Handler for write requests */
static void mcs_set_char_val_cb(ble_service_t *svc, uint16_t conn_idx,
const uint8_t *value)
{
mcs_char_val = *value;
/*
* Check the written value and if it is equal to 0x01
* then turn on LED D2 on DevKit.
*/
if (mcs_char_val == 0x01) {
/* Enable the COM power domain before handling any GPIO pin */
hw_sys_pd_com_enable();
hw_gpio_configure_pin(LED1_PORT, LED1_PIN, LED1_MODE, LED1_FUNC,
true);
hw_gpio_pad_latch_enable (LED1_PORT, LED1_PIN);
hw_gpio_pad_latch_disable(LED1_PORT, LED1_PIN); /* Lock pin status */
/* Disable the COM power domain after handling the GPIO pins */
hw_sys_pd_com_disable();
} else {
/* Enable the COM power domain before handling any GPIO pin */
hw_sys_pd_com_enable();
hw_gpio_configure_pin(LED1_PORT, LED1_PIN, LED1_MODE, LED1_FUNC,
false);
hw_gpio_pad_latch_enable (LED1_PORT, LED1_PIN);
hw_gpio_pad_latch_disable(LED1_PORT, LED1_PIN); /* Lock pin status */
/* Disable the COM power domain after handling the GPIO pins */
hw_sys_pd_com_disable();
}
/* Send an ACK to the peer device as a response to the write request */
mcs_set_char_value_cfm(svc, conn_idx, ATT_ERROR_OK);
/*
* Notify all the connected peer devices that characteristic value
* has been changed.
*/
mcs_notify_char_value_all(mcs, &mcs_char_val);
}
/* Declare callback functions for specific BLE events */
static const my_custom_service_cb_t mcs_callbacks = {
.get_characteristic_value = mcs_get_char_val_cb,
.set_characteristic_value = mcs_set_char_val_cb,
};
#endif
In ble_peripheral_task()
application task and before starting advertising, register the custom Bluetooth service in Dialog BLE database:
#if CFG_MY_CUSTOM_SERVICE
/* Initialize the custom BLE service */
mcs = mcs_init(&mcs_char_val, &mcs_callbacks);
#endif
Optionally, in ble_peripheral_task.c
file change the advertising data:
/*
* BLE peripheral advertising data
*/
static const uint8_t adv_data[] = {
0x12, GAP_DATA_TYPE_LOCAL_NAME,
'M', 'y', ' ', 'C', 'u', 's', 't', 'o', 'm', ' ', 'S', 'e', 'r', 'v', 'i', 'c', 'e'
};
4.4. Macro Definitions
In config/ble_peripheral_config.h
file, disable all the predefined services (this step is optional) and add/enable the custom declared service:
// enable debug service (see readme.txt for details)
#define CFG_DEBUG_SERVICE (0)
#define CFG_BAS (0) // register BAS service
#define CFG_BAS_MULTIPLE (0) // add 2 instances of BAS service
#define CFG_CTS (0) // register CTS
#define CFG_DIS (0) // register DIS
#define CFG_DIS_FULL (0) // add all possible characteristics to DIS
#define CFG_SCPS (0) // register ScPS
#define CFG_USER_SERVICE (0) // register custom service (using 128-bit UUIDs)
#define CFG_MY_CUSTOM_SERVICE (1)