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)