GATT Service Discovery - Hard Fault

Support,

Due to the limitations described in the referenced previous ticket, I am unable to successfully discover a service and its characteristics because of a hard fault occurrence. This fault does not occur when compiling and executing under debug mode but only fails in the release build. I have been developing this solution for a few weeks and recently began pre-production testing for a release scheduled for next week. It was during this production testing that I discovered this error. It has been challenging to determine the exact cause of the hard fault. My theory is that there is a GATT service conflict, but I cannot prove. The process can discover the primary service and begin the characteristic discovery process. However, a hard fault is thrown in the middle of the discovery. My logic is based off the "ble_app_interactive" example in the SDK.

I believe using something similar to a GATT queue would be ideal, but due to the discovery limitations described in my previous ticket, I can't utilize it as far as I'm aware. Do you have any recommendations or guidance to help understand this issue?

Thank you...

Related Ticket: https://devzone.nordicsemi.com/f/nordic-q-a/108832/gatt-service-discovery-failure-on-sig-base-uuid

#ifndef __SM_M_BLE_PEEKSMITH_H__
#define __SM_M_BLE_PEEKSMITH_H__

#include <stdint.h>
#include "ble.h"
#include "ble_srv_common.h"
#include "nrf_sdh_ble.h"
#include "nrf_ble_gq.h"

/**@brief   Macro for defining a ble_ps_c instance.
 *
 * @param   _name   Name of the instance.
 * @hideinitializer
 */
#define BLE_PEEKSMITH_C_DEF(_name)  \
static ble_ps_c_t _name; \
NRF_SDH_BLE_OBSERVER(_name ## _obs, \
                     BLE_PEEKSMITH_C_BLE_OBSERVER_PRIO, \
                     m_ble_peeksmith_c_on_ble_evt, &_name)


#define PS_UUID_DISPLAY_SERVICE  0xFFE0   /**< Test Service: Submit text to PS3 */ 
#define PS_UUID_DISPLAY_CHAR_1   0xFFE1   /**< Legacy: Diplay text and vibration */ 
#define PS_UUID_DISPLAY_CHAR_2   0xFFE2   /**< Custom Binary Images */ 
#define PS_UUID_DISPLAY_CHAR_3   0xFFE3   /**< Proxy for communicating with device connected to PS3 */ 


// --------------------------------------------------------------------------------------------------------------------
// Section: Service Discovery Structs
// --------------------------------------------------------------------------------------------------------------------

#define NRF_BLE_LINK_COUNT (NRF_SDH_BLE_PERIPHERAL_LINK_COUNT + NRF_SDH_BLE_CENTRAL_LINK_COUNT)
#define MAX_SERVICE_COUNT        6
#define MAX_CHARACTERISTIC_COUNT 6


// Structure storing data of all discovered services.
typedef struct {
    ble_gattc_service_t services[MAX_SERVICE_COUNT]; /**< Data of the services found. */
    uint8_t             count;                       /**< Count of the services found. */
} m_ble_device_srv_t;

typedef struct {
    ble_uuid_t            uuid;              /**< UUID of the characteristic. */
    uint16_t              decl_handle;       /**< Handle of the characteristic declaration. */
    uint16_t              value_handle;      /**< Handle of the characteristic value. */
    uint16_t              cccd_desc_handle;  /**< Handle of the CCCD descriptors. */
    ble_gatt_char_props_t char_props;        /**< GATT Characteristic Properties. */
} m_ble_char_data_t;

// Structure storing the data of all discovered characteristics.
typedef struct {
    m_ble_char_data_t char_data[MAX_CHARACTERISTIC_COUNT]; /**< Characteristics data. */
    uint8_t           count;                               /**< Characteristics count. */
} m_ble_srv_char_t;


// --------------------------------------------------------------------------------------------------------------------
// Section: Client Structs
// --------------------------------------------------------------------------------------------------------------------

/**@brief time Client event type. */
typedef enum {
    BLE_PS_C_EVT_NOTIFICATION = 1 /**< Event indicating that a notification of the time characteristic was received from the peer. */
} ble_ps_c_evt_type_t;


/**@brief  Forward declaration of the ble_ps_c_t type.
*/
typedef struct ble_ps_c_s ble_ps_c_t;


/**@brief Structure containing the handles related to the 
          SppekSmith Service found on the peer. */
typedef struct  {
    uint16_t legacy_handle; /**< Handle of the CCCD of the ??? characteristic. */
    uint16_t custom_handle; /**< Handle of the CCCD of the ??? characteristic. */
    uint16_t proxy_handle;  /**< Handle of the CCCD of the ??? characteristic. */
} ble_ps_handles_t;


/**@brief PeekSmith Client Structure. */
struct ble_ps_c_s {
    uint16_t          conn_handle;   /**< Connection handle as provided by the SoftDevice. */
    ble_ps_handles_t  ps_handles;    /**< Handles related characteristics on the peer. */
    nrf_ble_gq_t     *p_gatt_queue;  /**< Pointer to the BLE GATT Queue instance. */
};

/**@brief PeekSmith Client initialization structure. */
typedef struct {
    nrf_ble_gq_t * p_gatt_queue;  /**< Pointer to the BLE GATT Queue instance. */
} ble_ps_c_init_t;


/**@brief Display Configuration */
__ALIGN(4) typedef struct PACKED {
    uint8_t           version;                         /**< Configuration Version */
    char              name[NRF_BLE_SCAN_NAME_MAX_LEN]; /**< Name of the device */ 
    uint8_t           name_len;                        /**< Length of device name */
    uint8_t           mac_address[BLE_GAP_ADDR_LEN];   /**< MAC Address */
    bool              mac_registered;                  /**< MAC Address Saved */
    bool              enabled;                         /**< Display client is enabled */
    bool              auto_connect;                    /**< Auto connection is enabled */
} ble_ps_config_t;


#define PS_DEFAULT_NAME     "PeekSmith-"
#define PS_DEFAULT_NAME_LEN 10

/**@brief */
#define PEEKSMITH_CONFIG_DEFAULT { \
    .name           = PS_DEFAULT_NAME, \
    .name_len       = PS_DEFAULT_NAME_LEN, \
    .mac_address    = {0}, \
    .mac_registered = false, \
    .enabled        = true, \
    .auto_connect   = false \
}


/**@brief Function for initializing the PeekSmith client module.
 *
 * @details
 *
 * @retval    NRF_SUCCESS On successful initialization. 
 * @retval    err_code    Otherwise, this function propagates the error code returned by the Database Discovery module API
 *                        @ref ble_db_discovery_evt_register.
 */
ret_code_t m_ble_peeksmith_c_init(ble_ps_c_init_t  *p_ble_ps_c_init,
                                  ble_ps_config_t **p_ps_config,
                                  bool              factory_reset);


/**@brief Function for handling BLE events from the SoftDevice.
 *
 * @details This function handles the BLE events received from the SoftDevice. If a BLE event
 *          is relevant to the LED Button Client module, the function uses the event's data to update interval
 *          variables and, if necessary, send events to the application.
 *
 * @param[in] p_ble_evt     Pointer to the BLE event.
 * @param[in] p_context     Pointer to the SBWatch client structure.
 */
void m_ble_peeksmith_c_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context);



/**@brief Function for starting the service discovery process.
 *
 * @details 
 *
 * @param[in] conn_handle
 */
ret_code_t m_ble_peeksmith_discovery_start();


/**@brief Function for handling primary service discovery response.
 *
 * @details This function will handle the primary service discovery response.
 *
 * @param[in] p_ble_gattc_evt   Pointer to the GATT Client event.
 */
void m_ble_peeksmith_primary_discovery_rsp(ble_gattc_evt_t const *p_ble_gattc_evt);


/**@brief Function for handling a characteristic discovery response.
 *
 * @param[in] p_ble_gattc_evt   Pointer to the GATT Client event.
 */
bool m_ble_peeksmith_characteristics_discovery_rsp(ble_gattc_evt_t const * p_ble_gattc_evt);


/**@brief Function for assigning handles to this instance of PeekSmith Client.
 *
 * @details 
 *
 * @param[in] conn_handle    Connection handle to associate with the given PeekSmith Client Instance.
 *
 * @retval NRF_SUCCESS If the status was sent successfully.
 * @retval err_code    Otherwise, this API propagates the error code returned by function
 *                     @ref nrf_ble_gq_item_add.
 *
 */
ret_code_t m_ble_peeksmith_c_assign_handle(uint16_t conn_handle);


/**@brief Function for checking if the client has a GAP connection
 */
bool m_ble_peeksmith_is_connected();


/**@brief Function for handling the client GAP disconnect
*/
ret_code_t m_ble_peeksmith_disconnect();


/**@brief Function to get the auto connection state
 */
bool m_ble_peeksmith_auto_conn_status();


/**@brief Function to toggle the auto connection state
 */
bool m_ble_peeksmith_toggle_auto_conn();


/**@brief Clear PeekSmith Screen Buffer
 */
void m_ble_peeksmith_buffer_clear(void);


/**@brief Append PeekSmith Screen Buffer
 */
ret_code_t m_ble_peeksmith_buffer_append(char data, bool flush_buffer);


/**@brief Load PeekSmith Screen Buffer
 */
ret_code_t m_ble_peeksmith_buffer_load(char* p_data, uint8_t length,  bool flush_buffer);


/**@brief 
 */
ret_code_t m_ble_peeksmith_send_logo();


/**@brief 
 */
void m_ble_peeksmith_rpt_bad_input(void);

#endif /* __SM_M_BLE_PEEKSMITH_H__ */

#include <stdlib.h>
#include "app_config.h"
#include "ble_conn_state.h"
#include "ble_gatt_db.h"
#include "m_ble_peeksmith.h"
#include "m_ble_peeksmith_flash.h"
#include "m_haptic.h"

#define NRF_LOG_MODULE_NAME ble_ps_c
#define NRF_LOG_LEVEL       BLE_MODULE_LOG_LEVEL
#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();

/**< Main structure used by the SBWatch client module. */
BLE_PEEKSMITH_C_DEF(m_ble_ps_c);


// --------------------------------------------------------------------------------------------------------------------
// Section: Module Variables
// --------------------------------------------------------------------------------------------------------------------

#define SRV_DISC_START_HANDLE  0x0001                    /**< The start handle value used during service discovery. */

static ble_ps_config_t          *m_ble_peeksmith_config;
static const ble_ps_config_t     m_ble_peeksmith_config_default = PEEKSMITH_CONFIG_DEFAULT;
static bool                      m_forced_disconnect = false;
static bool                      m_vendor_char_uuid_read = false;
static bool                      m_vendor_uuid_read = false;
static ble_gattc_handle_range_t  m_handle_range;
static uint16_t                  m_central_start_handle = 0x0001;
static m_ble_device_srv_t       *m_ble_device_srv[NRF_BLE_LINK_COUNT];
static m_ble_srv_char_t          m_srv_char;

static ret_code_t m_ble_peeksmith_flush_buffer();

// --------------------------------------------------------------------------------------------------------------------
// Section: Error Handling Methods
// --------------------------------------------------------------------------------------------------------------------

/**@brief Function for handling the SBWatch Service client errors.
 *
 * @param[in]   nrf_error   Error code containing information about what went wrong.
 */
static void m_ble_peeksmith_client_error_handler(ret_code_t nrf_error) {
    APP_ERROR_HANDLER(nrf_error);
}


/**@brief Function for intercepting the errors of GATTC and the BLE GATT Queue.
 *
 * @param[in] nrf_error   Error code.
 * @param[in] p_ctx       Parameter from the event handler.
 * @param[in] conn_handle Connection handle.
 */
static void m_ble_peeksmith_gatt_error_handler(uint32_t nrf_error,
                                               void*    p_ctx,
                                               uint16_t conn_handle) {
    ble_ps_c_t * p_ble_ps_c = (ble_ps_c_t *)p_ctx;
    NRF_LOG_DEBUG("A GATT Client error has occurred on conn_handle: 0X%X", conn_handle);
    m_ble_peeksmith_client_error_handler(nrf_error);
}


// --------------------------------------------------------------------------------------------------------------------
// Section: Client Methods
// --------------------------------------------------------------------------------------------------------------------

/**@brief Function for assigning handles to this instance of PeekSmith Client.
 *
 * @details 
 *
 * @param[in] conn_handle    Connection handle to associate with the given PeekSmith Client Instance.
 *
 * @retval NRF_SUCCESS If the status was sent successfully.
 * @retval err_code    Otherwise, this API propagates the error code returned by function
 *                     @ref nrf_ble_gq_item_add.
 *
 */
ret_code_t m_ble_peeksmith_c_assign_handle(uint16_t conn_handle) {
    NRF_LOG_INFO("Assigning Connection Handle, handle %d", conn_handle);

    m_ble_ps_c.conn_handle = conn_handle;    
    return NRF_SUCCESS;
}


// --------------------------------------------------------------------------------------------------------------------
// Section: Buffer Timer Methods
// --------------------------------------------------------------------------------------------------------------------

APP_TIMER_DEF(m_ble_peeksmith_buff_flush_timer_id);

static bool m_ble_peeksmith_buff_flush_timer_started = false;

static void m_ble_peeksmith_buff_flush_handler(void *unused) {
    NRF_LOG_DEBUG("PeekSmith Buffer Flush Handler Triggered!");
    ret_code_t err_code;
    m_ble_peeksmith_buff_flush_timer_started = false;
    
    err_code = m_ble_peeksmith_flush_buffer();
    if (err_code != NRF_SUCCESS) {
        NRF_LOG_ERROR("Unable to flush the PeekSmith buffer!");
    }
}


static ret_code_t m_ble_peeksmith_buff_flush_timer_start() {
    ret_code_t err_code;

    if (!m_ble_peeksmith_buff_flush_timer_started) {
        NRF_LOG_DEBUG("PeekSmith Buffer Flush Timer Scheduled!");
        err_code = app_timer_start(m_ble_peeksmith_buff_flush_timer_id, 0, NULL);
        if (err_code == NRF_SUCCESS) {
            m_ble_peeksmith_buff_flush_timer_started = true;  
        }
        VERIFY_SUCCESS(err_code);
    } else {
        NRF_LOG_DEBUG("PeekSmith Buffer Flush Timer Already Scheduled!");
    }
    return NRF_SUCCESS;
}


// --------------------------------------------------------------------------------------------------------------------
// Section: Buffer Methods
// --------------------------------------------------------------------------------------------------------------------

#define PS_MAX_MEM_BUFFER_SIZE 35

static char    m_peeksmith_buffer[PS_MAX_MEM_BUFFER_SIZE];
static uint8_t m_peeksmith_buffer_len = 0;


/**@brief Flush PeekSmith Screen Buffer
 */
static ret_code_t m_ble_peeksmith_flush_buffer() {
    if (!m_ble_peeksmith_config->enabled) {
        NRF_LOG_INFO("PeekSmith is not enalbled!");
        return NRF_ERROR_INVALID_STATE;
    }

    ble_conn_state_status_t conn_status = ble_conn_state_status(m_ble_ps_c.conn_handle);
    if (conn_status == BLE_CONN_STATUS_INVALID
     || conn_status == BLE_CONN_STATUS_DISCONNECTED) {
        NRF_LOG_ERROR("Failed PeekSmith connection is in a invalid state!");
        return NRF_ERROR_INVALID_STATE;
    }

    if (m_peeksmith_buffer_len == 0) {
        return NRF_ERROR_INVALID_STATE;
    }

    if (m_peeksmith_buffer_len+1 <= PS_MAX_MEM_BUFFER_SIZE) {
        m_peeksmith_buffer[m_peeksmith_buffer_len] = 0x0A;
        m_peeksmith_buffer_len++;
    } else {
        NRF_LOG_ERROR("PeekSmith character buffer is full!");
        return NRF_ERROR_INVALID_STATE;
    }

    ble_gattc_write_params_t const write_params = {
        .write_op = BLE_GATT_OP_WRITE_CMD,
        .flags    = BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE,
        .handle   = m_ble_ps_c.ps_handles.legacy_handle,
        .offset   = 0,
        .len      = m_peeksmith_buffer_len,
        .p_value  = (uint8_t*)&m_peeksmith_buffer
    };

    return sd_ble_gattc_write(m_ble_ps_c.conn_handle, &write_params);
}


/**@brief Clear PeekSmith Screen Buffer
 */
void m_ble_peeksmith_buffer_clear(void) {
    memset(m_peeksmith_buffer, 0x00, PS_MAX_MEM_BUFFER_SIZE);
    m_peeksmith_buffer_len = 0;
}


/**@brief Append PeekSmith Screen Buffer
 */
ret_code_t m_ble_peeksmith_buffer_append(char data, bool flush_buffer) {
    if (m_peeksmith_buffer_len > PS_MAX_MEM_BUFFER_SIZE) {
        NRF_LOG_ERROR("PeekSmith character buffer is full!");
        return NRF_ERROR_NO_MEM;
    }

    if (m_peeksmith_buffer_len == 0) {
        m_peeksmith_buffer[0] = 0x23;
        m_peeksmith_buffer_len++;
    }
    
    if (m_peeksmith_buffer_len >= 1) {
        if (m_peeksmith_buffer[m_peeksmith_buffer_len-1] == 0x0A) {
            m_peeksmith_buffer[m_peeksmith_buffer_len-1] = data;
        } else {
            m_peeksmith_buffer[m_peeksmith_buffer_len] = data;
            m_peeksmith_buffer_len++;
        }
    }

    if (flush_buffer & m_ble_peeksmith_is_connected()) {
        return m_ble_peeksmith_buff_flush_timer_start();
    }
    return NRF_SUCCESS;
}


/**@brief Load PeekSmith Screen Buffer
 */
ret_code_t m_ble_peeksmith_buffer_load(char* p_data, uint8_t length, bool flush_buffer) {
    if (length > PS_MAX_MEM_BUFFER_SIZE) {
        NRF_LOG_ERROR("Data array is larger than buffer size!");
        return NRF_ERROR_NO_MEM;
    }
    
    m_ble_peeksmith_buffer_clear();
    memcpy((char**)&m_peeksmith_buffer[1], p_data, length);
    m_peeksmith_buffer[0] = 0x23;
    m_peeksmith_buffer_len = length + 1;

    if (flush_buffer & m_ble_peeksmith_is_connected()) {
        return m_ble_peeksmith_buff_flush_timer_start();
    }
    return NRF_SUCCESS;
}


/**@brief 
 */
ret_code_t m_ble_peeksmith_send_logo() {
    static char data[] = { ' ', 'N', 'E', 'X', 'U', 'S' };
    return m_ble_peeksmith_buffer_load(data, sizeof(data), true);
}


/**@brief 
 */
void m_ble_peeksmith_rpt_bad_input(void) {
    ret_code_t err_code;
    char bad_input_map[] = { 'B', 'A', 'D', ' ', 'I', 'N', 'P', 'U', 'T' };
    err_code = m_ble_peeksmith_buffer_load(bad_input_map, 9, true);
}

// --------------------------------------------------------------------------------------------------------------------
// Section: GATT Timer Methods
// --------------------------------------------------------------------------------------------------------------------

APP_TIMER_DEF(m_ble_peeksmith_gatt_svsc_timer_id);

static bool m_ble_peeksmith_gatt_timer_started = false;

static void m_ble_peeksmith_gatt_timer_handler(void *unused) {
    NRF_LOG_DEBUG("PeekSmith GATT Service Handler Triggered!");
    ret_code_t err_code;
    m_ble_peeksmith_gatt_timer_started = false;
    
    err_code = sd_ble_gattc_primary_services_discover(m_ble_ps_c.conn_handle, m_central_start_handle, NULL);
    if (err_code != NRF_SUCCESS) {
        APP_ERROR_CHECK(err_code);
    }
    // TODO: Handle Error
}


static ret_code_t  m_ble_peeksmith_gatt_timer_start(uint32_t mills) {
    ret_code_t err_code;

    NRF_LOG_DEBUG("PeekSmith GATT Service Timer Started!");
    err_code = app_timer_start(m_ble_peeksmith_gatt_svsc_timer_id,
                               APP_TIMER_TICKS(mills),
                               NULL);
    if (err_code == NRF_SUCCESS) {
        m_ble_peeksmith_gatt_timer_started = true;  
    }
    VERIFY_SUCCESS(err_code);
}


// --------------------------------------------------------------------------------------------------------------------
// Section: GATT Discovery Methods
// --------------------------------------------------------------------------------------------------------------------

/**@brief Function for printing the UUID for each service.
 *
 * @param[in] conn_handle    The connection handle identifying the connection to perform this procedure on.
 * @param[in] service_p      Pointer to ble_gattc_service_t.
 */
static void m_ble_peeksmith_uuid_print(uint16_t conn_handle, ble_gattc_service_t const * p_service) {
    NRF_LOG_RAW_INFO("Found service UUIDs: \r\n");

    for (uint8_t i = 0; i < m_ble_device_srv[conn_handle]->count; i++) {
        NRF_LOG_RAW_INFO("%s: %X %s: 0x%X\r\n", 
                         "UUID", 
                         p_service[i].uuid.uuid,
                         "type", 
                         p_service[i].uuid.type);
    }
}

static void m_ble_peeksmith_char_print(void) {
    for (uint8_t i = 0; i < m_srv_char.count; i++) {
           ble_gatt_char_props_t const * p_char_props = 
                                 &m_srv_char.char_data[i].char_props;
           NRF_LOG_RAW_INFO("Characteristic UUID: %X\r\n",
                            m_srv_char.char_data[i].uuid.uuid);
           NRF_LOG_RAW_INFO("Parameters:\r\n");
           NRF_LOG_RAW_INFO("broadcast: %d ", p_char_props->broadcast);
           NRF_LOG_RAW_INFO("read: %d ", p_char_props->read);
           NRF_LOG_RAW_INFO("write_wo_resp: %d ", p_char_props->write_wo_resp);
           NRF_LOG_RAW_INFO("write: %d ", p_char_props->write);
           NRF_LOG_RAW_INFO("notify: %d\r\n", p_char_props->notify);
           NRF_LOG_RAW_INFO("indicate: %d ", p_char_props->indicate);
           NRF_LOG_RAW_INFO("auth_signed_wr: %d\r\n", p_char_props->auth_signed_wr);
    }

    NRF_LOG_RAW_INFO("Number of characteristics: %d\r\n", m_srv_char.count);

}

//static void m_ble_cccd_descriptors_discovery(ble_gattc_evt_t const * p_ble_gattc_evt) {
//    for (uint8_t i = 0; i < m_srv_char.count; i++) {
//        // If it is possible to enable notification.
//        if ((m_srv_char.char_data[i].char_props.notify ||
//             m_srv_char.char_data[i].char_props.indicate) &&
//            (m_srv_char.char_data[i].cccd_desc_handle == 0))
//        {
//            // Search for CCCD descriptor handle
//            cccd_descriptors_search(m_srv_char.char_data[i].uuid.uuid, p_ble_gattc_evt->conn_handle);
//            break;
//        }
//    }
//}


/**@brief Function for starting the service discovery process.
 *
 * @details 
 *
 * @param[in] conn_handle
 */
ret_code_t m_ble_peeksmith_discovery_start() {
    return m_ble_peeksmith_gatt_timer_start(2000);
}


/**@brief Function for handling primary service discovery response.
 *
 * @details This function will handle the primary service discovery response.
 *
 * @param[in] p_ble_gattc_evt   Pointer to the GATT Client event.
 */
void m_ble_peeksmith_primary_discovery_rsp(ble_gattc_evt_t const *p_ble_gattc_evt) {
    ret_code_t      err_code;
    uint16_t        count;
    uint16_t        bytes_to_copy;
    static uint16_t offset = 0;


    //For readability.
    ble_gattc_evt_prim_srvc_disc_rsp_t const *p_prim_serv = &(p_ble_gattc_evt->params.prim_srvc_disc_rsp);
    ble_gattc_service_t *p_service = m_ble_device_srv[m_ble_ps_c.conn_handle]->services;

    // Number of services, currently discovered.
    count = p_prim_serv->count;

    // If no more services are found.
    if ((count != 0) && (p_ble_gattc_evt->gatt_status == BLE_GATT_STATUS_SUCCESS)) {
        if ((count + offset) > MAX_SERVICE_COUNT) {
            bytes_to_copy = MAX_SERVICE_COUNT - offset;
        } else {
            bytes_to_copy = count;
        }

        // Save services data.
        memcpy((p_service + offset), p_prim_serv->services, bytes_to_copy * sizeof(ble_gattc_service_t));
        offset += count;
        
        NRF_LOG_INFO("Services Found: UUID-[%X], Type-[%d]", p_prim_serv->services[count - 1].uuid.uuid, p_prim_serv->services[count - 1].uuid.type);
        
        if (p_prim_serv->services[count - 1].uuid.uuid == PS_UUID_DISPLAY_SERVICE) {
            NRF_LOG_INFO("Found PeekSmith primary service!")
            m_handle_range.start_handle = p_prim_serv->services[count - 1].handle_range.start_handle;
            m_handle_range.end_handle = p_prim_serv->services[count - 1].handle_range.end_handle;
            err_code = sd_ble_gattc_characteristics_discover(m_ble_ps_c.conn_handle, &m_handle_range);
            APP_ERROR_CHECK(err_code);
        } else {
            // If the main service has not been found, this function must be called again with a new start handle.
            m_central_start_handle = p_prim_serv->services[count - 1].handle_range.end_handle + 1;
            err_code = sd_ble_gattc_primary_services_discover(m_ble_ps_c.conn_handle, m_central_start_handle, NULL);
            APP_ERROR_CHECK(err_code);
        }
    } else {
        m_ble_device_srv[m_ble_ps_c.conn_handle]->count = offset;

        // If service UUID type is unknown, then look for a 128-bit UUID.
        // Only the first one is searched for here, the rest is searched for in the @ref on_read_rsp.
        for (uint8_t i = 0; i < offset; i++) {
            if (p_service[i].uuid.type == BLE_UUID_TYPE_UNKNOWN) {
                m_vendor_uuid_read = true;
                // Read service 128-bit UUID.
                err_code = sd_ble_gattc_read(m_ble_ps_c.conn_handle, p_service[i].handle_range.start_handle, 0);
                APP_ERROR_CHECK(err_code);
                offset = 0;
                return;
            }
        }

        //NRF_LOG_INFO("Services count: %d", offset);
        //m_ble_peeksmith_uuid_print(p_ble_gattc_evt->conn_handle, p_service);

        offset = 0;
    }
}


/**@brief Function for handling a characteristic discovery response.
 *
 * @param[in] p_ble_gattc_evt   Pointer to the GATT Client event.
 */
bool m_ble_peeksmith_characteristics_discovery_rsp(ble_gattc_evt_t const * p_ble_gattc_evt) {
    uint16_t        count;
    static uint16_t offset = 0;
    uint16_t        bytes_to_copy;
    ret_code_t      err_code;

    // For readability.
    count = p_ble_gattc_evt->params.char_disc_rsp.count;
    ble_gattc_evt_char_disc_rsp_t const * p_char_disc_rsp_evt;

    p_char_disc_rsp_evt = &(p_ble_gattc_evt->params.char_disc_rsp);

    if (p_ble_gattc_evt->gatt_status == BLE_GATT_STATUS_SUCCESS) {
        if ((count + offset) > MAX_CHARACTERISTIC_COUNT) {
            bytes_to_copy = MAX_CHARACTERISTIC_COUNT - offset;
            NRF_LOG_RAW_INFO("Too many characteristics discovered\r\n");
        } else {
            bytes_to_copy = count;
        }

        // Save characteristics data.
        for (uint8_t i = 0; i < bytes_to_copy; i++) {
            m_srv_char.char_data[i + offset].decl_handle      = p_char_disc_rsp_evt->chars[i].handle_decl;
            m_srv_char.char_data[i + offset].value_handle     = p_char_disc_rsp_evt->chars[i].handle_value;
            m_srv_char.char_data[i + offset].uuid             = p_char_disc_rsp_evt->chars[i].uuid;
            m_srv_char.char_data[i + offset].char_props       = p_char_disc_rsp_evt->chars[i].char_props;
            m_srv_char.char_data[i + offset].cccd_desc_handle = 0;

            switch (p_char_disc_rsp_evt->chars[i].uuid.uuid) {
                case PS_UUID_DISPLAY_CHAR_1: {
                    NRF_LOG_INFO("Found PeekSmith legacy service characteristic");
                    m_ble_ps_c.ps_handles.legacy_handle = p_char_disc_rsp_evt->chars[i].handle_value;
                } break;
                case PS_UUID_DISPLAY_CHAR_2: {
                    NRF_LOG_INFO("Found PeekSmith custom service characteristic");
                    m_ble_ps_c.ps_handles.custom_handle = p_char_disc_rsp_evt->chars[i].handle_value;
                } break;
                case PS_UUID_DISPLAY_CHAR_3: {
                    NRF_LOG_INFO("Found PeekSmith proxy service characteristic");
                    m_ble_ps_c.ps_handles.proxy_handle  = p_char_disc_rsp_evt->chars[i].handle_value;
                } break;
            }
        }

        offset += bytes_to_copy;
    }

    // If the last characteristic has not been reached, look for a new handle range.
    //ble_gattc_handle_range_t handle_range;
    m_handle_range.start_handle = m_srv_char.char_data[offset - 1].value_handle + 1;

    // Search for end handle.
    for (uint8_t j = 0; j < m_ble_device_srv[m_ble_ps_c.conn_handle]->count; j++) {
        if ((m_handle_range.start_handle >
             m_ble_device_srv[m_ble_ps_c.conn_handle]->services[j].handle_range.start_handle) &&
            (m_handle_range.start_handle < m_ble_device_srv[m_ble_ps_c.conn_handle]->services[j].handle_range.end_handle))
        {
            m_handle_range.end_handle = m_ble_device_srv[m_ble_ps_c.conn_handle]->services[j].handle_range.end_handle;
            break;
        }
    }

    // Handle value of the characteristic being discovered is less than the end handle of
    // the service being discovered. There is no possibility of more characteristics being
    // present.
    if ((m_srv_char.char_data[offset - 1].value_handle >= m_handle_range.end_handle) ||
        (offset == MAX_CHARACTERISTIC_COUNT) ||
        (p_ble_gattc_evt->gatt_status != BLE_GATT_STATUS_SUCCESS))
    {
        m_srv_char.count = offset;
        offset           = 0;

        for (uint8_t i = 0; i < m_srv_char.count; i++) {
            if (m_srv_char.char_data[i].uuid.type == BLE_UUID_TYPE_UNKNOWN) {
                m_vendor_char_uuid_read = true;
                // Read char 128-bit UUID.
                err_code = sd_ble_gattc_read(m_ble_ps_c.conn_handle, m_srv_char.char_data[i].decl_handle, 0);
                APP_ERROR_CHECK(err_code);

                return true; // TODO: Is this the correct response?
            }
        }

        // Print characteristic data.
        //m_ble_peeksmith_char_print();
        
        // Discovery Complete
        return true;
    }

    // If the last Characteristic has not been reached, this function must be called again with new handle range.
    err_code = sd_ble_gattc_characteristics_discover(p_ble_gattc_evt->conn_handle, &m_handle_range);
    APP_ERROR_CHECK(err_code);
    return false;
}

// --------------------------------------------------------------------------------------------------------------------


/**@brief Function for checking if the client has a GAP connection
 */
/**@brief Check the status of the connection
*/
bool m_ble_peeksmith_is_connected() {
    return ble_conn_state_status(m_ble_ps_c.conn_handle) == BLE_CONN_STATUS_CONNECTED ? true : false;
}

/**@brief Function for handling the client GAP disconnect
*/
ret_code_t m_ble_peeksmith_disconnect() {
    ret_code_t err_code;

    NRF_LOG_DEBUG("Attempting to disconnect time connection.");
    
    if (m_ble_peeksmith_is_connected()) {
        err_code = sd_ble_gap_disconnect(m_ble_ps_c.conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
        if (err_code != NRF_SUCCESS) {
            NRF_LOG_WARNING("Failed to disconnect time connection. Connection handle: %d Error: %d", m_ble_ps_c.conn_handle, err_code);
        } else {
            NRF_LOG_DEBUG("Disconnected time connection handle %d", m_ble_ps_c.conn_handle);
            m_forced_disconnect = true;
        }
        return err_code;
    } else {
        NRF_LOG_DEBUG("time is not connected to remote.");
        return NRF_SUCCESS;
    }
}


/**@brief Function to get the auto connection state
 */
bool m_ble_peeksmith_auto_conn_status() {
    return m_ble_peeksmith_config->auto_connect;
}


/**@brief Function to toggle the auto connection state
 */
bool m_ble_peeksmith_toggle_auto_conn() {
    m_ble_peeksmith_config->auto_connect ^= true;
    if (m_ble_peeksmith_fds_update() == NRF_SUCCESS) {
        return true;
    } else {
        return false;
    }
}

/**@brief Function for handling the Disconnected event received from the SoftDevice.
 *
 * @details This function checks whether the disconnect event is happening on the link
 *          associated with the current instance of the module. If the event is happening, the function sets the instance's
 *          conn_handle to invalid.
 *
 * @param[in] p_ble_lbs_c Pointer to the Led Button Client structure.
 * @param[in] p_ble_evt   Pointer to the BLE event received.
 */
static void m_ble_peeksmith_on_disconnected(ble_ps_c_t *p_ble_ps_c, ble_evt_t const *p_ble_evt) {
    if (p_ble_ps_c->conn_handle == p_ble_evt->evt.gap_evt.conn_handle) {
        p_ble_ps_c->conn_handle              = BLE_CONN_HANDLE_INVALID;
        p_ble_ps_c->ps_handles.custom_handle = BLE_GATT_HANDLE_INVALID;
        p_ble_ps_c->ps_handles.legacy_handle = BLE_GATT_HANDLE_INVALID;
        p_ble_ps_c->ps_handles.proxy_handle  = BLE_GATT_HANDLE_INVALID;
        NRF_LOG_DEBUG("PeekSmith Disconnected!");
        m_haptic_play_feedback(HFB_ERROR);
    }
}


/**@brief Function for handling Handle Value Notification received from the SoftDevice.
 *
 * @details This function uses the Handle Value Notification received from the SoftDevice
 *          and checks whether it is a notification of Button state from the peer. If
 *          it is, this function decodes the state of the button and sends it to the
 *          application.
 *
 * @param[in] p_ble_ps_c Pointer to the PeekSmith Client structure.
 * @param[in] p_ble_evt   Pointer to the BLE event received.
 */
static void m_ble_peeksmith_on_hvx(ble_ps_c_t *p_ble_ps_c, ble_evt_t const *p_ble_evt) {
    if (p_ble_ps_c->conn_handle != p_ble_evt->evt.gattc_evt.conn_handle) {
        return;
    }

    if (p_ble_evt->evt.gattc_evt.params.hvx.handle == p_ble_ps_c->ps_handles.legacy_handle) {
        if (p_ble_evt->evt.gattc_evt.params.hvx.len >= 1) {
            NRF_LOG_HEXDUMP_DEBUG(p_ble_evt->evt.gattc_evt.params.hvx.data, p_ble_evt->evt.gattc_evt.params.hvx.len);
        }
    }
}


/**@brief Function for handling BLE events from the SoftDevice.
*/
void m_ble_peeksmith_c_on_ble_evt(ble_evt_t const *p_ble_evt, void *p_context) {
    if ((p_context == NULL) || (p_ble_evt == NULL)) {
        return;
    }

    ble_ps_c_t * p_ble_ps_c = (ble_ps_c_t *)p_context;

    switch (p_ble_evt->header.evt_id)
    {
        case BLE_GATTC_EVT_HVX: {
            NRF_LOG_DEBUG("NOTIFICATION Event Recieved!");
            m_ble_peeksmith_on_hvx(p_ble_ps_c, p_ble_evt);
        } break;

        case BLE_GAP_EVT_CONNECTED: {
            NRF_LOG_DEBUG("CONNECTED Event Recieved!");
            //forced_disconnect = false;
        } break;

        case BLE_GAP_EVT_DISCONNECTED: {
            NRF_LOG_DEBUG("DISCONNECTED Event Recieved!");
            m_ble_peeksmith_on_disconnected(p_ble_ps_c, p_ble_evt);
        } break;

        default:
            break;
    }
}


/**@brief Initialize the PeekSmith Bluetooth Client
*/
ret_code_t m_ble_peeksmith_c_init(ble_ps_c_init_t  *p_ble_ps_c_init,
                                  ble_ps_config_t **p_ps_config,
                                  bool              factory_reset) {
    ret_code_t err_code;

    VERIFY_PARAM_NOT_NULL(p_ble_ps_c_init);
    VERIFY_PARAM_NOT_NULL(p_ps_config);
    
    m_forced_disconnect = false;

    /**< Initialize Flash */
    err_code = m_ble_peeksmith_fds_init(&m_ble_peeksmith_config_default,
                                        &m_ble_peeksmith_config,
                                        factory_reset);
    if (err_code != NRF_SUCCESS) {
        NRF_LOG_ERROR("Failed to initialize time configuration flash - %d", err_code);
        return err_code;
    }
    *p_ps_config = m_ble_peeksmith_config;


    /**< Initialize Timer */
    err_code = app_timer_create(&m_ble_peeksmith_gatt_svsc_timer_id,
                                APP_TIMER_MODE_SINGLE_SHOT,
                                m_ble_peeksmith_gatt_timer_handler);
    VERIFY_SUCCESS(err_code);

    err_code = app_timer_create(&m_ble_peeksmith_buff_flush_timer_id,
                                APP_TIMER_MODE_SINGLE_SHOT,
                                m_ble_peeksmith_buff_flush_handler);
    VERIFY_SUCCESS(err_code);

    

    m_ble_ps_c.conn_handle              = BLE_CONN_HANDLE_INVALID;
    m_ble_ps_c.ps_handles.custom_handle = BLE_GATT_HANDLE_INVALID;
    m_ble_ps_c.ps_handles.legacy_handle = BLE_GATT_HANDLE_INVALID;
    m_ble_ps_c.ps_handles.proxy_handle  = BLE_GATT_HANDLE_INVALID;
    m_ble_ps_c.p_gatt_queue             = p_ble_ps_c_init->p_gatt_queue;

    m_ble_peeksmith_buffer_clear();

    return NRF_SUCCESS;
}

/**@brief Function for handling BLE Stack events that concern the central application.
 *
 * @details This function keeps the connection handles of the central application up-to-date. It
 * parses scanning reports, initiating a connection attempt to peripherals,
 * and manages connection parameter update requests. Additionally, it updates the status
 * of LEDs used to report the central application's activity.
 *
 * @note        Since this function updates the connection handles, @ref BLE_GAP_EVT_DISCONNECTED events
 *              should be dispatched to the target application before invoking this function.
 *
 * @param[in]   p_ble_evt   Bluetooth stack event.
 */
static void m_ble_central_event_handler(ble_evt_t const * p_ble_evt) {
    ret_code_t            err_code;
    ble_gap_evt_t const * p_gap_evt = &p_ble_evt->evt.gap_evt;

    switch (p_ble_evt->header.evt_id) {
        case BLE_GAP_EVT_CONNECTED: {
            NRF_LOG_INFO("CENTRAL: GAP - Device Connected, handle %d", p_gap_evt->conn_handle);
            
            switch (m_current_cntrl_device) {
                case BLE_DEVICE_PEEKSMITH: {
                    m_haptic_play_effect(DRV2605_EFF_LONG_DBL_CLK_STRONG_1);
                    
                    err_code =  m_ble_peeksmith_c_assign_handle(p_gap_evt->conn_handle);
                    APP_ERROR_CHECK(err_code);

                    err_code = m_ble_peeksmith_discovery_start();
                    APP_ERROR_CHECK(err_code);
                } break;

                case BLE_DEVICE_TIME_DEVICE: {
                    m_haptic_play_effect(DRV2605_EFF_LONG_DBL_CLK_STRONG_1);
                    
                    err_code = m_ble_tdevice_client_handles_assign(p_gap_evt->conn_handle, NULL);
                    APP_ERROR_CHECK(err_code);

                    err_code = ble_db_discovery_start(&m_db_disc[p_gap_evt->conn_handle], p_gap_evt->conn_handle);
                    APP_ERROR_CHECK(err_code);
                } break;

                default:
                    return;
            }

            // Assign connection handle to the QWR module.
            m_ble_multi_qwr_conn_handle_assign(p_ble_evt->evt.gap_evt.conn_handle);
        } break;

        case BLE_GAP_EVT_DISCONNECTED: {
            NRF_LOG_INFO("CENTRAL: Disconnected, handle %d, reason 0x%x.",
                         p_ble_evt->evt.gap_evt.conn_handle,
                         p_ble_evt->evt.gap_evt.params.disconnected.reason);

            switch (m_current_cntrl_device) {
                case BLE_DEVICE_PEEKSMITH: {
                    // TODO: Implement PeekSmith Disconnect Event Handler
                } break;

                case BLE_DEVICE_TIME_DEVICE: {
                    if (m_ble_tdevice_is_conn_handle(p_ble_evt->evt.gap_evt.conn_handle)) {
                        NRF_LOG_INFO("CENTRAL: Time Device Disconnected");
                        if (m_tdevice_config->enabled &&
                            !m_ble_tdevice_is_connected() &&
                            !m_ble_tdevice_disconenct_forced()) {
                            err_code = m_ble_client_auto_conn_enqueue(BLE_DEVICE_TIME_DEVICE);
                            APP_ERROR_CHECK(err_code);
                        }
                    }
                } break;

                default:
                    return;
            }

            m_leds_off(BLE_STATUS_LED);
            m_leds_flash(BLE_STATUS_LED, 1, false);
            m_ble_client_auto_conn_handler();
        } break;

        case BLE_GAP_EVT_TIMEOUT: {
            // No timeout for scanning is specified, so only connection attemps can timeout.
            if (p_ble_evt->evt.gap_evt.params.timeout.src == BLE_GAP_TIMEOUT_SRC_CONN) {
                NRF_LOG_INFO("CENTRAL: GAP - Connection Request timed out.");
            }
        } break;

        case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST: {
            // Accept parameters requested by peer.
            err_code = sd_ble_gap_conn_param_update(p_ble_evt->evt.gap_evt.conn_handle,
                                                   &p_ble_evt->evt.gap_evt.params.conn_param_update_request.conn_params);
            APP_ERROR_CHECK(err_code);
        } break;

        case BLE_GAP_EVT_PHY_UPDATE_REQUEST: {
            NRF_LOG_DEBUG("CENTRAL: GAP - PHY update request.");
            ble_gap_phys_t const phys = {
                .rx_phys = BLE_GAP_PHY_AUTO,
                .tx_phys = BLE_GAP_PHY_AUTO,
            };
            err_code = sd_ble_gap_phy_update(p_ble_evt->evt.gap_evt.conn_handle, &phys);
            APP_ERROR_CHECK(err_code);
        } break;

        case BLE_GATTS_EVT_WRITE: {
            NRF_LOG_DEBUG("CENTRAL: GATT - Client Timeout.");
        } break;

        case BLE_GATTC_EVT_TIMEOUT: {
            NRF_LOG_DEBUG("CENTRAL: GATT - Client Timeout.");
            err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gattc_evt.conn_handle,
                                             BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
            APP_ERROR_CHECK(err_code);
        } break;

        case BLE_GATTS_EVT_TIMEOUT: {
            NRF_LOG_DEBUG("CENTRAL: GATT - Server Timeout.");
            err_code = sd_ble_gap_disconnect(p_ble_evt->evt.gatts_evt.conn_handle,
                                             BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
            APP_ERROR_CHECK(err_code);
        } break;

        case BLE_GATTC_EVT_PRIM_SRVC_DISC_RSP: {
            NRF_LOG_DEBUG("CENTRAL: GATT - Primary Service Discovery Response.");
            if (m_current_cntrl_device == BLE_DEVICE_PEEKSMITH) {
                m_ble_peeksmith_primary_discovery_rsp(&(p_ble_evt->evt.gattc_evt));
            }
        } break;

        case BLE_GATTC_EVT_CHAR_DISC_RSP: {
            NRF_LOG_DEBUG("CENTRAL: GATT - Characteristics Discovery Response.");
            if (m_current_cntrl_device == BLE_DEVICE_PEEKSMITH) {
                if (m_ble_peeksmith_characteristics_discovery_rsp(&(p_ble_evt->evt.gattc_evt))) {
                    m_current_cntrl_device = BLE_DEVICE_NONE;
                    m_leds_flash(BLE_STATUS_LED, 2, false);
                    m_ble_peeksmith_send_logo();
                    if (m_advertising) {
                        m_leds_flash(BLE_STATUS_LED, 0, false);
                    }
                    m_ble_client_auto_conn_handler();
                }
            }
        } break;

        case BLE_GATTC_EVT_DESC_DISC_RSP: {
            NRF_LOG_DEBUG("CENTRAL: GATT - Discription Discovery Response.");
            //on_descriptor_discovery_rsp(&(p_ble_evt->evt.gattc_evt));
        } break;

        default:
            break;
    }
}

Related