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;
    }
}

  • Hello,

    Is it the the app error handler or the HardFault handler that gets invoked in your application? Enabling the HardFault handling library by setting HARDFAULT_HANDLER_ENABLED to 1 in sdk_config.h and including the fault handler from /components/libraries/hardfault/nrf52/handler in your project may help us better understand the nature of the fault if it is the latter.

    From the code snippets you posted, it appears that you have forgotten to allocate memory for your m_ble_device_srv pointer(s). Since you know the number of services you will discover beforehand, I recommend allocating the service struct statically.

    Best regards,

    Vidar

Related