Mutlilink central with multiple different peripheral with different service advertisements

Hello, 

We are developing an application to work as a multilink central with 3 different kind of BLE devices that include 1 custom device which advertise customised NUS service, 1 Polar belts which advertise HRS, BAS and DIS services and 1 EMG sensors which has a single notification service.

We would like to connect 6 custom device, 6 polar belts (of any kind such as Polar Belt H10, H7 etc) and 4 EMG sensors at the same time (or any configuration of such). 

We are using Nordic SDK v15 with NRF52840 but eventually it will be used on NRF52849 dongle. We have developed multilink central application for the dongle where each kind of devices separately can connect and send notifications to central. For these applications, we have taken reference from ble_app_multilink_central and ble_app_hrs_c and from ble/ble_services/ble_lbs_c, ble_hrs_c, ble_bas_c and ble_nus_c.

 

However, we are now trying to see if we can combine these 3 applications to make only 1 central. 

For testing purposes, we are only currently using one H10 Polar belt with one custom device for now. The problems we are facing are as follows: 

1) After registering 6 arrays for hrs_c, bas_c and nus_c, we need to assign handles in BLE_GAP_EVT_CONNECTED for each kind of services. The problem is that we don't know how to keep track of handles. Considering that Polar belt and custom device can have any addr value (in ble_gap_addr_t), the assignment of handle is  a problem. Is there any trick to keep track of handle assignment? The same thing goes when a specific device disconnects. I understand that a conn_handle is assign for each device in SoftDevice however, this does not tell us what kind of device it is?

2) After discovering the first custom device, the central application will find the polar belt (i.e. BLE_GAP_EVT_CONNECTED is triggered) but its service such as BLE_HRS_C_EVT_DISCOVERY_COMPLETE or BLE_BAS_C_EVT_DISCOVERY_COMPLETE events are never triggered. We are sure that we are doing correct assignment of handles i.e. ble_hrs_c_handles_assign or ble_bas_c_handles_assign in BLE_GAP_EVT_CONNECTED as we used device address with if-else conditions to check if the problem (1) is causing this. The NRF_DEBUG_LOG does not print any logs in case of polar belt after its discovery. 

Strangely enough, if the central is restarted with custom device 1 turned off, the Polar Belt HRS and BAS services are discovered and HRS notifications are received fully. 

3) We will eventually be using CDC ACM to send the data receive on each notification to each service for each device to the computer. Considering that the custom device sends a notification of 64 bytes / 100 m-secs, Polar Belt sends about 8 bytes / 1 secs and the EMG sensors sends 2 bytes / 100 m-secs , and that there will be 6+6+4 device connected (in worst case), what are the best possible way to structure the data and send it through CDC ACM in timely manner with possibly some device-id identifier for data-device recognition on our main PC-application? 

Looking forward for any suggestions/solutions. 

 

Thank you 

  • Hi

    1. If I recall correctly, the lowest connection handle that is free is always given to a new connection by the SoftDevice. The Multiperipheral application uses the Connection state Library to keep track of different connections and what state they're in, so I would suggest that you check out that library as well.

    2. The discovery issues you're having I think are due to the device trying to connect to the next device before discovery is completed on the central end. Please make sure that you wait for the COMPLETE events before you let the central application start scanning again. You can also use a sniffer trace if you're not getting it to work to see what is going on over the air. That might help us pinpoint what exactly is going wrong.

    3. I guess this would be mostly up to you as we don't have a specific "how to" on this. I would think forwarding the data to CDC ACM and the computer whenever it's incoming with as you say with a way to identify the data (device id or name for example).

    Best regards,

    Simon

  • Hi, 

    1) Your suggestion for connection state library seems really good. Thank you, I will check it out. 

    2) You are right, I did start scanning again in the BLE_GAP_EVT_CONNECTED & in BLE_GAP_EVT_DISCONNECTED  instead of SERVICE_DISCOVERY_COMPLETED events (either for HRS, BAS, NUS or LBS) - bad copy/paste on my end. :) 

    I re-checked the application code and managed to connect to 1 Polar belt, 2 EMG and 2 custom devices at the same time. 

    However, I am having a hard time reconnecting to the device if I disconnect the peripheral by powering it down and powering it up again.

    I am using the following services for each type of device: 

    Custom Device -> NUS Service (with modified UUIDs)

    Polar Belt -> HRS & BAS Service 

    EMG -> LBS Service (with modified UUIDs).

    I am restarting scanning in each service discovery completed event. However, the problem is that Polar Belt has 2 services so the nrf_ble_scan_start function is called probably twice and when I try to reconnect another peripheral, it won't reconnect. 

    I tested the above situation by not using the Polar Belt and just using the other two types of devices. 

    So my question is how can I check if the scanning is already in process and if it is, I do not call the nrf_ble_scan_start again. Is there an internal state that keeps track of the scanning mode?

    3) Actually we tried to send the data as soon as possible i.e. whenever the central receive notification from one of the device, we check if the cdc_acm_port is open, and if it is, we send the data. However, considering that the custom device sends data at 64-bytes / 100 m-secs and there can be up to 6 devices, this became a problem even when we only developed a multilink central application for the custom device. It may be that the 2 device sends notification with short period of time apart and that can cause the port to close (as far as I understand) and on our software device, the time sync for the data becomes a mess. 

    I was hoping that there might be a queue-dequeue class/library already implemented in the Nordic SDK which I can use along with a timer to repeatedly send 64 * 6 * 10 (6 custom device notifications) + 8 * 6 (6 Polar Belts notifications) + 2 * 10 * 6 (4 EMG sensors notifications) = 4008 bytes + device identifiers bytes / second? 

    Thank you

    Best regards,

    arsh

  • Hi

    2. I think you should not start the scanning until all service discoveries are completed. If you call the scan_start function while the device is already scanning you should get an NRF_ERROR_INVALID_STATE error indicating that the device is already scanning or that the scan parameters are set incorrectly, so you should be able to see that if the scan_start was called multiple times.

    3. Unfortunately the inner workings of the CDC-ACM class/library is not my personal area of expertise, and it's somewhat unrelated to the discovery process. I think you'll get better advice/support if you open a separate case on this topic. Sorry I couldn't be on more help here.

    Best regards,

    Simon

  • Hi, 

    2. It seems like that the Polar Belt is causing the reconnection issue i.e. starting the scanning again. So this is what we tested: 

    We are using modified name filter where we check if a substring of the peripheral name was found in the advertisement response. We are also turning the scanning back when the services are fully discovered for a specific device i.e. NUS (for custom device), LBS (for EMG sensors), HRS and BAS (for Polar Belt). 

    We noticed that when we only use our custom peripheral device and EMG sensors, they work perfectly and the scanning is started fully and we can turn off any custom or EMG sensor peripheral and reconnect again. 

    However, if we turn on the Polar Belt peripheral, it basically advertises its name twice. For the first time, it gets discovered and then the name of the device is advertised again (for some reason). This second time basically never triggers the service discovery completed event and consequently start scan function and after that if we turn off any device and restart it, it does not reconnect.

    I am guessing that this second advertisement results in a  ble_db_discovery_start getting stuck and never returning because the services for this peripheral i.e. Polar Belt are already discovered and connected. 

    P.S: We also tried to use the scan timeout with an app timer where we timeout was set to 5 seconds and the timer starts the scanning again with 7 seconds. 

    P.S.S: We did not use the peer management for bonding for Polar belt or HRS/BAS services as shown in the ble_app_hrs_c because when we tried to integrate it, it was overlapping some memory addresses and we removed it instead :D 

    The code is attached for your reference. Please note that I am taking references from ble_multilink_app and ble_hrs_c. 

    I also removed the comments (not proud of it, just for readability). 

     
    #include <stdint.h>
    #include <stdio.h>
    #include <string.h>
    #include "nordic_common.h"
    #include "nrf_sdh.h"
    #include "nrf_sdh_ble.h"
    #include "app_timer.h"
    #include "bsp_btn_ble.h"
    #include "ble.h"
    #include "ble_hci.h"
    
    #include "ble_advertising.h"
    #include "ble_conn_params.h"
    #include "ble_db_discovery.h"
    
    // HRS ADditionals 
    #include "ble_srv_common.h"
    #include "nrf_sdm.h"
    #include "app_util.h"
    #include "fds.h"
    #include "nrf_fstorage.h"
    #include "ble_gap.h"
    #include "nrf_sdh_soc.h"
    #include "nrf_ble_lesc.h"
    #include "peer_manager.h"
    #include "peer_manager_handler.h"
    // HRS ADditionals
    
    #include "ble_lbs_c.h"
    #include "ble_hrs_c.h"
    #include "ble_bas_c.h"
    #include "ble_nus_c.h"
    
    
    #include "ble_conn_state.h" 
    #include "nrf_ble_gatt.h"
    #include "nrf_pwr_mgmt.h"
    #include "nrf_ble_scan.h"
    
    #include "nrf_log.h"
    #include "nrf_log_ctrl.h"
    #include "nrf_log_default_backends.h"
    
    
    
    // HRS AND BAS CONFIG
    #define APP_BLE_CONN_CFG_TAG        1                                   /**< A tag identifying the SoftDevice BLE configuration. */
    
    #define APP_BLE_OBSERVER_PRIO       3                                   /**< Application's BLE observer priority. You shouldn't need to modify this value. */
    #define APP_SOC_OBSERVER_PRIO       1                                   /**< Applications' SoC observer priority. You shouldn't need to modify this value. */
    
    #define LESC_DEBUG_MODE             0                                   /**< Set to 1 to use LESC debug keys, allows you to use a sniffer to inspect traffic. */
    
    
    
    #define SEC_PARAM_BOND              1                                   /**< Perform bonding. */
    #define SEC_PARAM_MITM              0                                   /**< Man In The Middle protection not required. */
    #define SEC_PARAM_LESC              1                                   /**< LE Secure Connections enabled. */
    #define SEC_PARAM_KEYPRESS          0                                   /**< Keypress notifications not enabled. */
    #define SEC_PARAM_IO_CAPABILITIES   BLE_GAP_IO_CAPS_NONE                /**< No I/O capabilities. */
    #define SEC_PARAM_OOB               0                                   /**< Out Of Band data not available. */
    #define SEC_PARAM_MIN_KEY_SIZE      7                                   /**< Minimum encryption key size in octets. */
    #define SEC_PARAM_MAX_KEY_SIZE      16                                  /**< Maximum encryption key size in octets. */
    // HRS AND BAS CONFIG
    
    #define CENTRAL_SCANNING_LED      BSP_BOARD_LED_0
    #define CENTRAL_CONNECTED_LED     BSP_BOARD_LED_1
    #define LEDBUTTON_LED             BSP_BOARD_LED_2                       /**< LED to indicate a change of state of the Button characteristic on the peer. */
    
    #define LEDBUTTON_BUTTON          BSP_BUTTON_0                          /**< Button that writes to the LED characteristic of the peer. */
    #define BUTTON_DETECTION_DELAY    APP_TIMER_TICKS(50)                   /**< Delay from a GPIOTE event until a button is reported as pushed (in number of timer ticks). */
    #define TARGET_UUID                 BLE_UUID_HEART_RATE_SERVICE         /**< Target device uuid that application is looking for. */
    
    
    NRF_BLE_GATT_DEF(m_gatt);                                               /**< GATT module instance. */
    BLE_LBS_C_ARRAY_DEF(m_lbs_c, NRF_SDH_BLE_CENTRAL_LINK_COUNT);           /**< LED button client instances. */
    BLE_HRS_C_ARRAY_DEF(m_hrs_c, NRF_SDH_BLE_CENTRAL_LINK_COUNT);           /**< LED button client instances. */
    BLE_BAS_C_ARRAY_DEF(m_bas_c, NRF_SDH_BLE_CENTRAL_LINK_COUNT);           /**< LED button client instances. */
    BLE_NUS_C_ARRAY_DEF(m_nus_c, NRF_SDH_BLE_CENTRAL_LINK_COUNT);           /**< LED button client instances. */
    
    BLE_DB_DISCOVERY_ARRAY_DEF(m_db_disc, NRF_SDH_BLE_CENTRAL_LINK_COUNT);  /**< Database discovery module instances. */
    NRF_BLE_SCAN_DEF(m_scan);                                               /**< Scanning Module instance. */
    NRF_BLE_GQ_DEF(m_ble_gatt_queue,                                        /**< BLE GATT Queue instance. */
                   NRF_SDH_BLE_CENTRAL_LINK_COUNT,
                   NRF_BLE_GQ_QUEUE_SIZE);
     
    
    static char const m_target_capno_name[] = "CAPT-";             /**< Name of the device to try to connect to. This name is searched for in the scanning report data. */
    static char const m_target_hrs_bas_name[] = "Polar";             /**< Name of the device to try to connect to. This name is searched for in the scanning report data. */
    static char const m_target_emg_name[] = "ANR Corp";             /**< Name of the device to try to connect to. This name is searched for in the scanning report data. */
    
    static bool m_target_has_been_before = false;
    
    
    static uint16_t lbs_connection_handles[NRF_SDH_BLE_CENTRAL_LINK_COUNT];
    static uint16_t hrs_connection_handles[NRF_SDH_BLE_CENTRAL_LINK_COUNT];
    static uint16_t bas_connection_handles[NRF_SDH_BLE_CENTRAL_LINK_COUNT];
    static uint16_t nus_connection_handles[NRF_SDH_BLE_CENTRAL_LINK_COUNT];
    
    static uint32_t nus_notification_count[NRF_SDH_BLE_CENTRAL_LINK_COUNT] ;
    static uint32_t lbs_notification_count[NRF_SDH_BLE_CENTRAL_LINK_COUNT] ;
    
    // HRS peer manager 
    static bool     m_whitelist_disabled;                               /**< True if whitelist has been temporarily disabled. */
    static bool     m_memory_access_in_progress;                        /**< Flag to keep track of ongoing operations on persistent memory. */
    
    
    // scanning variables 
    static bool is_ble_scanning = false;
    APP_TIMER_DEF(m_repeated_timer_id);  
    
    // App specific 
    typedef enum {DEVTYPE_NONE, DEVTYPE_CAPNO, DEVTYPE_EMG, DEVTYPE_HRS} device_type_t;
    
    typedef struct
    {
        uint32_t dev_type;
        const char *dev_name;
        uint32_t phy;
    }connected_device_info_t;
    char             m_device_name_being_connected_to[30];
    connected_device_info_t m_device_being_connected_info = {DEVTYPE_NONE, m_device_name_being_connected_to, 0};
    
    
    static ble_gap_scan_params_t m_scan_param =
    {
        .active        = 0x01,
        .interval      = NRF_BLE_SCAN_SCAN_INTERVAL,
        .window        = NRF_BLE_SCAN_SCAN_WINDOW,
        .filter_policy = BLE_GAP_SCAN_FP_ACCEPT_ALL,
        .timeout       = NRF_BLE_SCAN_SCAN_DURATION,
        .scan_phys     = BLE_GAP_PHY_1MBPS,
    };
    
    
    
    void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name)
    {
        app_error_handler(0xDEADBEEF, line_num, p_file_name);
    }
    
    
    static void service_error_handler(uint32_t nrf_error)
    {
        APP_ERROR_HANDLER(nrf_error);
    }
    
    
    static void leds_init(void)
    {
        bsp_board_init(BSP_INIT_LEDS);
    }
    
    
    
    static void scan_stop(void)
    {
        NRF_LOG_DEBUG("scan_stop()");
        uint32_t err_code = sd_ble_gap_scan_stop();
        if(err_code != NRF_SUCCESS)
        {
            NRF_LOG_ERROR("scan_stop() failed with error code: %x", err_code);
        }  
    }
    
    static void scan_start(void)
    { 
        ret_code_t ret;
        ret = nrf_ble_scan_params_set(&m_scan, &m_scan_param);
        APP_ERROR_CHECK(ret);
    
        if ( ret != NRF_SUCCESS )
        {
            NRF_LOG_INFO("Scanning start set param error");
        }
    
        ret = nrf_ble_scan_start(&m_scan);
        APP_ERROR_CHECK(ret);
    
        if ( ret != NRF_SUCCESS)
        {
            NRF_LOG_INFO("Problems in starting scan");
        }
        else 
        {
            NRF_LOG_INFO("Scanning Started...");
        }
    
    }
    
    
    static void scan_evt_handler(scan_evt_t const * p_scan_evt)
    {
        ret_code_t err_code;
    
        switch(p_scan_evt->scan_evt_id)
        {
            case NRF_BLE_SCAN_EVT_SCAN_TIMEOUT:
            {
                NRF_LOG_INFO("Scan timed out.");
            } break;
    
            case NRF_BLE_SCAN_EVT_CONNECTED: 
            {
                NRF_LOG_INFO("Scan event connected");
            } break;
    
            case NRF_BLE_SCAN_EVT_CONNECTING_ERROR:
            {
                NRF_LOG_INFO("Connection error");
                err_code = p_scan_evt->params.connecting_err.err_code;
                APP_ERROR_CHECK(err_code);
            } break;
    
            case NRF_BLE_SCAN_EVT_FILTER_MATCH:
            {
                // NRF_LOG_INFO("A filter was matched")
             
                // ret_code_t err;
                // uint8_array_t adv_data;
                // uint8_array_t dev_name;
                // // For readability.
                // const ble_gap_evt_adv_report_t *p_adv_report = &p_scan_evt->params.p_not_found;
                // const ble_data_t *data = &p_adv_report->data;
    
                // uint8_t *device_name = ble_advdata_parse(data->p_data, data->len, BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME);
                
                // if(data->len > 0)
                // {
                //     NRF_LOG_INFO("Device name: %s \r\n",device_name);  
                // }
    
            } break;
            default:
                break;
        }
    }
    
    
    static void scan_init(void)
    {
        ret_code_t          err_code;
        nrf_ble_scan_init_t init_scan;
    
        memset(&init_scan, 0, sizeof(init_scan));
    
        init_scan.connect_if_match = true;
        init_scan.conn_cfg_tag     = APP_BLE_CONN_CFG_TAG;
    
    
        ble_uuid_t m_target_hrs_uuid =
        {
            .uuid = BLE_UUID_HEART_RATE_SERVICE,
            .type = BLE_UUID_TYPE_BLE,
        };
    
        ble_uuid_t m_target_capno_uuid =
        {
            .uuid = BLE_UUID_NUS_SERVICE,
            .type = BLE_UUID_TYPE_VENDOR_BEGIN,
        };
    
        ble_uuid_t m_target_emg_uuid =
        {
            .uuid = LBS_UUID_SERVICE,
            .type = BLE_UUID_TYPE_VENDOR_BEGIN,
        };
    
        err_code = nrf_ble_scan_init(&m_scan, &init_scan, scan_evt_handler);
        APP_ERROR_CHECK(err_code);
    
        bool m_use_name_filters = true;
    
        if (m_use_name_filters)
        {
            err_code = nrf_ble_scan_filter_set(&m_scan, SCAN_NAME_FILTER, m_target_capno_name);
            APP_ERROR_CHECK(err_code);
    
            err_code = nrf_ble_scan_filter_set(&m_scan, SCAN_NAME_FILTER, m_target_hrs_bas_name);
            APP_ERROR_CHECK(err_code);
    
            err_code = nrf_ble_scan_filter_set(&m_scan, SCAN_NAME_FILTER, m_target_emg_name);
            APP_ERROR_CHECK(err_code);
    
            err_code = nrf_ble_scan_filters_enable(&m_scan, NRF_BLE_SCAN_NAME_FILTER, false);
            APP_ERROR_CHECK(err_code);
        }
        else 
        {
            err_code = nrf_ble_scan_filter_set(&m_scan, SCAN_UUID_FILTER,  &m_target_hrs_uuid);
            APP_ERROR_CHECK(err_code);
    
            err_code = nrf_ble_scan_filter_set(&m_scan, SCAN_UUID_FILTER,  &m_target_capno_uuid);
            APP_ERROR_CHECK(err_code);
    
            err_code = nrf_ble_scan_filter_set(&m_scan, SCAN_UUID_FILTER,  &m_target_emg_uuid);
            APP_ERROR_CHECK(err_code);
    
            err_code = nrf_ble_scan_filters_enable(&m_scan, NRF_BLE_SCAN_UUID_FILTER, false);
            APP_ERROR_CHECK(err_code);
        }
    
    }
    
    
    static uint32_t adv_report_parse(uint8_t type, uint8_array_t * p_advdata, uint8_array_t * p_typedata)
    {
        uint32_t  index = 0;
        uint8_t * p_data;
    
        p_data = p_advdata->p_data;
    
        while (index < p_advdata->size)
        {
            uint8_t field_length = p_data[index];
            uint8_t field_type   = p_data[index + 1];
    
            if (field_type == type)
            {
                p_typedata->p_data = &p_data[index + 2];
                p_typedata->size   = field_length - 1;
                return NRF_SUCCESS;
            }
            index += field_length + 1;
        }
        return NRF_ERROR_NOT_FOUND;
    }
    
    static void on_adv_report(ble_evt_t const * p_ble_evt)
    {
        uint32_t      err_code;
        uint8_array_t adv_data;
        uint8_array_t dev_name;
        uint8_array_t service_uuid;
        //NRF_LOG_INFO("ADV");
    
        if (m_device_being_connected_info.dev_type == DEVTYPE_NONE)
        {
            // For readibility.
            ble_gap_evt_t  const * p_gap_evt  = &p_ble_evt->evt.gap_evt;
            ble_gap_addr_t const * peer_addr  = &p_gap_evt->params.adv_report.peer_addr;
    
            // Prepare advertisement report for parsing.
            adv_data.p_data = (uint8_t *)p_gap_evt->params.adv_report.data.p_data;
            adv_data.size   = p_gap_evt->params.adv_report.data.len;
    
            // Search for advertising names.
            bool found_name = false;
            bool found_capno_name = false, found_emg_name = false, found_hrs_name = false;
            err_code = adv_report_parse(BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME,
                                        &adv_data,
                                        &dev_name);
            if (err_code != NRF_SUCCESS)
            {
                // Look for the short local name if it was not found as complete.
                err_code = adv_report_parse(BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME, &adv_data, &dev_name);
                if (err_code != NRF_SUCCESS)
                {
                    // If we can't parse the data, then exit.
                    //return;
                }
                else
                {
                    found_name = true;
                }
            }
            else
            {
                found_name = true;
            } 
            
    
            if ( found_name )
            {   
                memset(m_device_name_being_connected_to, 0, sizeof(m_device_name_being_connected_to) );
                if ( 
                        strstr( dev_name.p_data, m_target_capno_name ) 
                        && strlen( dev_name.p_data) >= strlen(m_target_capno_name) 
                        && strlen( dev_name.p_data ) >= 0
                )
                {
                    NRF_LOG_INFO("Device Name: %s" , dev_name.p_data);       
                    memcpy( m_device_name_being_connected_to, dev_name.p_data , 0 );
                    m_device_being_connected_info.dev_type = DEVTYPE_CAPNO;
                    found_capno_name = true;
                }
                else if (
                    strstr( dev_name.p_data, m_target_hrs_bas_name ) 
                    && strlen( dev_name.p_data) >= strlen(m_target_hrs_bas_name) 
                    && strlen( dev_name.p_data ) >= 0
                ) 
                {
                    NRF_LOG_INFO("Device Name: %s" , dev_name.p_data);       
                    memcpy( m_device_name_being_connected_to, dev_name.p_data , 0 );
                    found_hrs_name = true;
                    m_device_being_connected_info.dev_type = DEVTYPE_HRS;
                }
                else if ( 
                    strstr( dev_name.p_data, m_target_emg_name ) 
                    && strlen( dev_name.p_data) >= strlen(m_target_emg_name) 
                    && strlen( dev_name.p_data ) >= 0
                )
                {
                    NRF_LOG_INFO("Device Name: %s" , dev_name.p_data);       
                    memcpy( m_device_name_being_connected_to, dev_name.p_data , 0 );
                    m_device_being_connected_info.dev_type = DEVTYPE_EMG;
                    found_emg_name = true;
                }
               
            }
    
        }
        
    }
    
    
    static void disconnected_ble_handles(uint16_t discoonected_handle, uint8_t reason)
    {
        ret_code_t err_code;
    
        NRF_LOG_INFO("Disconnecting 0x%x handle because of 0x%x ", discoonected_handle, reason);
        for (uint32_t i = 0; i < NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++)
        {
            if ( lbs_connection_handles[i] == discoonected_handle )
            {
                NRF_LOG_INFO("Disconnecting 0x%u handle which was LBS handle ", discoonected_handle);
                lbs_connection_handles[i] = BLE_CONN_HANDLE_INVALID;
                break;
            }
        }
        for (uint32_t i = 0; i < NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++)
        {
            if ( hrs_connection_handles[i] == discoonected_handle )
            {
                NRF_LOG_INFO("Disconnecting 0x%u handle which was HRS handle ", discoonected_handle);
                hrs_connection_handles[i] = BLE_CONN_HANDLE_INVALID;
                break;
            }
        }
        for (uint32_t i = 0; i < NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++)
        {
            if ( bas_connection_handles[i] == discoonected_handle )
            {
                NRF_LOG_INFO("Disconnecting 0x%u handle which was BAS handle ", discoonected_handle);
                bas_connection_handles[i] = BLE_CONN_HANDLE_INVALID;
                break;
            }
        }
    
        return;
    }
     
    
    static void lbs_c_evt_handler(ble_lbs_c_t * p_lbs_c, ble_lbs_c_evt_t * p_lbs_c_evt)
    {
        switch (p_lbs_c_evt->evt_type)
        {
            case BLE_LBS_C_EVT_DISCOVERY_COMPLETE:
            {
                ret_code_t err_code;
    
                NRF_LOG_INFO("LED Button Service discovered on conn_handle 0x%x",
                             p_lbs_c_evt->conn_handle);
    
                err_code = ble_lbs_c_handles_assign(&m_lbs_c[p_lbs_c->conn_handle],
                                                    p_lbs_c_evt->conn_handle,
                                                    &p_lbs_c_evt->params.peer_db);
                
                APP_ERROR_CHECK(err_code);
                
    
                err_code = app_button_enable();
                APP_ERROR_CHECK(err_code);
    
                // LED Button Service discovered. Enable notification of Button.
                err_code = ble_lbs_c_button_notif_enable(p_lbs_c);
                APP_ERROR_CHECK(err_code);
    
                lbs_connection_handles[p_lbs_c->conn_handle] = p_lbs_c->conn_handle;
    
            } break; // BLE_LBS_C_EVT_DISCOVERY_COMPLETE
    
            case BLE_LBS_C_EVT_BUTTON_NOTIFICATION:
            {
                if (lbs_notification_count[p_lbs_c_evt->conn_handle] % 10 == 0){
                    NRF_LOG_INFO("MR40 EMG      Link:  0x%x     Data: %u", p_lbs_c_evt->conn_handle, p_lbs_c_evt->raw_data_len);
                }
                lbs_notification_count[p_lbs_c_evt->conn_handle] += 1;
    
            } break; // BLE_LBS_C_EVT_BUTTON_NOTIFICATION
    
            default:
                // No implementation needed.
                break;
        }
    }
    
    
    static void hrs_c_evt_handler(ble_hrs_c_t * p_hrs_c, ble_hrs_c_evt_t * p_hrs_c_evt)
    {
        ret_code_t err_code;
    
        switch (p_hrs_c_evt->evt_type)
        {
            case BLE_HRS_C_EVT_DISCOVERY_COMPLETE:
            {
                NRF_LOG_DEBUG("Heart rate service discovered.");
    
                err_code = ble_hrs_c_handles_assign(&m_hrs_c[p_hrs_c->conn_handle],
                                                    p_hrs_c_evt->conn_handle,
                                                    &p_hrs_c_evt->params.peer_db);
                APP_ERROR_CHECK(err_code);
     
                // Heart rate service discovered. Enable notification of Heart Rate Measurement.
                err_code = ble_hrs_c_hrm_notif_enable(p_hrs_c);
                APP_ERROR_CHECK(err_code);
    
        
            } break;
    
            case BLE_HRS_C_EVT_HRM_NOTIFICATION:
            {
                NRF_LOG_INFO("Polar HR      Link:  0x%x     Data:%d.", p_hrs_c->conn_handle, p_hrs_c_evt->params.hrm.hr_value);
     
            } break;
    
            default:
                break;
        }
    }
    
     
    static void bas_c_evt_handler(ble_bas_c_t * p_bas_c, ble_bas_c_evt_t * p_bas_c_evt)
    {
        ret_code_t err_code;
    
        switch (p_bas_c_evt->evt_type)
        {
            case BLE_BAS_C_EVT_DISCOVERY_COMPLETE:
            {
                err_code = ble_bas_c_handles_assign(&m_bas_c[p_bas_c->conn_handle],
                                                    p_bas_c_evt->conn_handle,
                                                    &p_bas_c_evt->params.bas_db);
                APP_ERROR_CHECK(err_code);
    
                // Battery service discovered. Enable notification of Battery Level.
                NRF_LOG_DEBUG("Battery Service discovered. Reading battery level.");
    
                err_code = ble_bas_c_bl_read(p_bas_c);
                APP_ERROR_CHECK(err_code);
    
                NRF_LOG_DEBUG("Enabling Battery Level Notification.");
                err_code = ble_bas_c_bl_notif_enable(p_bas_c);
                APP_ERROR_CHECK(err_code);
    
    
            } break;
    
            case BLE_BAS_C_EVT_BATT_NOTIFICATION:
                NRF_LOG_INFO("Polar Battery     Link:  0x%x     Data: %d %%.", p_bas_c->conn_handle, p_bas_c_evt->params.battery_level);
                break;
    
            case BLE_BAS_C_EVT_BATT_READ_RESP:
                NRF_LOG_INFO("Polar Battery     Link:  0x%x     Data: %d %%.", p_bas_c->conn_handle, p_bas_c_evt->params.battery_level);
                break;
    
            default:
                break;
        }
    }
    
    
    static void nus_c_evt_handler(ble_nus_c_t * p_nus_c, ble_nus_c_evt_t const * p_nus_c_evt)
    {
        ret_code_t err_code;
    
        switch (p_nus_c_evt->evt_type)
        {
            case BLE_NUS_C_EVT_DISCOVERY_COMPLETE:
            {
                NRF_LOG_INFO("NUS Discovery complete.");
                err_code = ble_nus_c_handles_assign(&m_nus_c[p_nus_c->conn_handle], p_nus_c_evt->conn_handle, &p_nus_c_evt->handles);
                APP_ERROR_CHECK(err_code);
    
                err_code = ble_nus_c_tx_notif_enable(p_nus_c);
                APP_ERROR_CHECK(err_code);
                NRF_LOG_INFO("Connected to device with Nordic UART Service.");
    
                nus_connection_handles[p_nus_c->conn_handle] = p_nus_c->conn_handle;
    
            } break;
    
            case BLE_NUS_C_EVT_NUS_TX_EVT:
                if (nus_notification_count[p_nus_c->conn_handle] % 250 == 0){
                    NRF_LOG_INFO("Capno Go      Link:  0x%x     Data: %u", p_nus_c->conn_handle,  p_nus_c_evt->data_len);
                }
                nus_notification_count[p_nus_c->conn_handle] += 1;
                break;
        }
    }
    
    
    static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
    {
        ret_code_t err_code;
    
        // For readability.
        ble_gap_evt_t const * p_gap_evt = &p_ble_evt->evt.gap_evt;
            
        switch (p_ble_evt->header.evt_id)
        {
            // Upon connection, check which peripheral is connected, initiate DB
            // discovery, update LEDs status, and resume scanning, if necessary.
            case BLE_GAP_EVT_CONNECTED:
            { 
    
                APP_ERROR_CHECK_BOOL(p_gap_evt->conn_handle < NRF_SDH_BLE_CENTRAL_LINK_COUNT);
                
                if ( m_device_being_connected_info.dev_type == DEVTYPE_CAPNO )
                {
                    NRF_LOG_INFO("Discovering serivces for Capno Go");
                    err_code = ble_nus_c_handles_assign(&m_nus_c[p_gap_evt->conn_handle],
                                                        p_gap_evt->conn_handle,
                                                        NULL);
                    APP_ERROR_CHECK(err_code);
                }
    
                if ( m_device_being_connected_info.dev_type == DEVTYPE_HRS)
                {
                    err_code = ble_hrs_c_handles_assign(&m_hrs_c[p_gap_evt->conn_handle],
                                        p_gap_evt->conn_handle,
                                        NULL);
                    APP_ERROR_CHECK(err_code);
    
                    err_code = ble_bas_c_handles_assign(&m_bas_c[p_gap_evt->conn_handle],
                                                        p_gap_evt->conn_handle,
                                                        NULL);
                    APP_ERROR_CHECK(err_code);
    
                }
    
                if ( m_device_being_connected_info.dev_type == DEVTYPE_EMG)
                {
                    NRF_LOG_INFO("Discovering serivces for ANR Corp EMG");            
                    err_code = ble_lbs_c_handles_assign(&m_lbs_c[p_gap_evt->conn_handle],
                                                        p_gap_evt->conn_handle,
                                                        NULL);
                    APP_ERROR_CHECK(err_code);
    
                }
            
                if ( m_device_being_connected_info.dev_type != DEVTYPE_NONE )
                {
                    NRF_LOG_INFO("DB DISCOVERY FOR  found for %u", p_gap_evt->conn_handle);
                    err_code = ble_db_discovery_start(&m_db_disc[p_gap_evt->conn_handle],
                                                    p_gap_evt->conn_handle);
    
                    APP_ERROR_CHECK(err_code);
                    
                    m_device_being_connected_info.dev_type = DEVTYPE_NONE;
                    NRF_LOG_INFO("DB Discovery error code: 0x%u", err_code);
    
                }
    
            } break; // BLE_GAP_EVT_CONNECTED
    
            // Upon disconnection, reset the connection handle of the peer that disconnected, update
            // the LEDs status and start scanning again.
            case BLE_GAP_EVT_DISCONNECTED:
            {
                NRF_LOG_INFO("LBS central link 0x%x disconnected (reason: 0x%x)",
                             p_gap_evt->conn_handle,
                             p_gap_evt->params.disconnected.reason);
    
                // disconnected_ble_handles(p_gap_evt->conn_handle,  p_gap_evt->params.disconnected.reason);
                if (ble_conn_state_central_conn_count() == 0)
                {
                    err_code = app_button_disable();
                    APP_ERROR_CHECK(err_code);
    
                    // Turn off the LED that indicates the connection.
                    bsp_board_led_off(CENTRAL_CONNECTED_LED);
                }
          
                
            } break;
    
            case BLE_GAP_EVT_ADV_REPORT:
                on_adv_report(p_ble_evt);
                break;
    
            case BLE_GAP_EVT_TIMEOUT:
            {
                // Timeout for scanning is not specified, so only the connection requests can time out.
                if (p_gap_evt->params.timeout.src == BLE_GAP_TIMEOUT_SRC_CONN)
                {
                    NRF_LOG_DEBUG("Connection request timed out.");
                }
            } break;
    
            case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:
            {
                NRF_LOG_DEBUG("BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST.");
                // Accept parameters requested by peer.
                err_code = sd_ble_gap_conn_param_update(p_gap_evt->conn_handle,
                                            &p_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("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_GATTC_EVT_TIMEOUT:
            {
                // Disconnect on GATT client timeout event.
                NRF_LOG_DEBUG("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:
            {
                // Disconnect on GATT server timeout event.
                NRF_LOG_DEBUG("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;
    
            default:
                // No implementation needed.
                break;
        }
    }
    
    
    
    static void lbs_c_init(void)
    {
        ret_code_t       err_code;
        ble_lbs_c_init_t lbs_c_init_obj;
    
        lbs_c_init_obj.evt_handler   = lbs_c_evt_handler;
        lbs_c_init_obj.p_gatt_queue  = &m_ble_gatt_queue;
        lbs_c_init_obj.error_handler = service_error_handler;
    
        for (uint32_t i = 0; i < NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++)
        {
            lbs_connection_handles[i] = BLE_CONN_HANDLE_INVALID;
            lbs_notification_count[i] = 0;
            err_code = ble_lbs_c_init(&m_lbs_c[i], &lbs_c_init_obj);
            APP_ERROR_CHECK(err_code);
        }
    }
    
    static void hrs_c_init(void)
    {
        ret_code_t       err_code;
        ble_hrs_c_init_t hrs_c_init_obj;
    
        hrs_c_init_obj.evt_handler   = hrs_c_evt_handler;
        hrs_c_init_obj.error_handler = service_error_handler;
        hrs_c_init_obj.p_gatt_queue  = &m_ble_gatt_queue;
    
        for (uint32_t i = 0; i < NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++)
        {
            hrs_connection_handles[i] = BLE_CONN_HANDLE_INVALID;
            ret_code_t err_code = ble_hrs_c_init(&m_hrs_c[i], &hrs_c_init_obj);
            APP_ERROR_CHECK(err_code);
        }
    
    }
    
    static void bas_c_init(void)
    {
        ret_code_t       err_code;
        ble_bas_c_init_t bas_c_init_obj;
    
        bas_c_init_obj.evt_handler   = bas_c_evt_handler;
        bas_c_init_obj.error_handler = service_error_handler;
        bas_c_init_obj.p_gatt_queue  = &m_ble_gatt_queue;
    
        for (uint32_t i = 0; i < NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++)
        {
            bas_connection_handles[i] = BLE_CONN_HANDLE_INVALID;
            ret_code_t err_code = ble_bas_c_init(&m_bas_c[i], &bas_c_init_obj);
            APP_ERROR_CHECK(err_code);
        }
    }
    
    static void nus_c_init(void)
    {
        ret_code_t       err_code;
        ble_nus_c_init_t nus_c_init_obj;
    
        nus_c_init_obj.evt_handler   = nus_c_evt_handler;
        nus_c_init_obj.error_handler = service_error_handler;
        nus_c_init_obj.p_gatt_queue  = &m_ble_gatt_queue;
    
    
        for (uint32_t i = 0; i < NRF_SDH_BLE_CENTRAL_LINK_COUNT; i++)
        {
            nus_connection_handles[i] = BLE_CONN_HANDLE_INVALID;
            nus_notification_count[i] = 0;
            ret_code_t err_code = ble_nus_c_init(&m_nus_c[i], &nus_c_init_obj);
            APP_ERROR_CHECK(err_code);
        }
    }
    
    
    static void ble_stack_init(void)
    {
        ret_code_t err_code;
    
        err_code = nrf_sdh_enable_request();
        APP_ERROR_CHECK(err_code);
    
        // Configure the BLE stack using the default settings.
        // Fetch the start address of the application RAM.
        uint32_t ram_start = 0;
        err_code = nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start);
        APP_ERROR_CHECK(err_code);
    
        // Enable BLE stack.
        err_code = nrf_sdh_ble_enable(&ram_start);
        APP_ERROR_CHECK(err_code);
    
        // Register a handler for BLE events.
        NRF_SDH_BLE_OBSERVER(m_ble_observer, APP_BLE_OBSERVER_PRIO, ble_evt_handler, NULL);
    
    }
    
    
    static void button_event_handler(uint8_t pin_no, uint8_t button_action)
    {
        ret_code_t err_code;
    
        switch (pin_no)
        {
            case LEDBUTTON_BUTTON:
                break;
    
            default:
                APP_ERROR_HANDLER(pin_no);
                break;
        }
    }
    
    
    static void buttons_init(void)
    {
        ret_code_t err_code;
    
       // The array must be static because a pointer to it is saved in the button handler module.
        static app_button_cfg_t buttons[] =
        {
            {LEDBUTTON_BUTTON, false, BUTTON_PULL, button_event_handler}
        };
    
        err_code = app_button_init(buttons, ARRAY_SIZE(buttons), BUTTON_DETECTION_DELAY);
        APP_ERROR_CHECK(err_code);
    }
    
     
    static void db_disc_handler(ble_db_discovery_evt_t * p_evt)
    {
        switch (p_evt->evt_type)
        {
        case BLE_DB_DISCOVERY_COMPLETE:
            {
                // NRF_LOG_INFO("BLE_DB_DISCOVERY_COMPLETE found for %u", p_evt->conn_handle);
                break;
            } 
            case BLE_DB_DISCOVERY_ERROR:
            {
                // NRF_LOG_INFO("BLE_DB_DISCOVERY_ERROR found for %u", p_evt->conn_handle);
                break;
            } 
            case BLE_DB_DISCOVERY_SRV_NOT_FOUND:
            {
                // NRF_LOG_INFO("BLE_DB_DISCOVERY_SRV_NOT_FOUND found for %u", p_evt->conn_handle);
                break;
            }
            case BLE_DB_DISCOVERY_AVAILABLE:
            {
                // NRF_LOG_INFO("BLE_DB_DISCOVERY_AVAILABLE found for %u", p_evt->conn_handle);
                break;
            }
        default:
            break;
        }
    
        // NRF_LOG_INFO("Call to ble_lbs_on_db_disc_evt for %d and link 0x%x", p_evt->conn_handle, p_evt->conn_handle);
        ble_lbs_on_db_disc_evt(&m_lbs_c[p_evt->conn_handle], p_evt);
        // NRF_LOG_INFO("Call to ble_hrs_on_db_disc_evt for %d and link 0x%x", p_evt->conn_handle, p_evt->conn_handle);
        ble_hrs_on_db_disc_evt(&m_hrs_c[p_evt->conn_handle], p_evt);
        // NRF_LOG_INFO("Call to ble_bas_on_db_disc_evt for %d and link 0x%x", p_evt->conn_handle, p_evt->conn_handle);
        ble_bas_on_db_disc_evt(&m_bas_c[p_evt->conn_handle], p_evt);
        // NRF_LOG_INFO("Call to ble_nus_c_on_db_disc_evt for %d and link 0x%x", p_evt->conn_handle, p_evt->conn_handle);
        ble_nus_c_on_db_disc_evt(&m_nus_c[p_evt->conn_handle], p_evt);
    }
    
     
    static void db_discovery_init(void)
    {
        ble_db_discovery_init_t db_init;
    
        memset(&db_init, 0, sizeof(ble_db_discovery_init_t));
    
        db_init.evt_handler  = db_disc_handler;
        db_init.p_gatt_queue = &m_ble_gatt_queue;
    
        ret_code_t err_code = ble_db_discovery_init(&db_init);
        APP_ERROR_CHECK(err_code);
    }
    
    
    static void power_management_init(void)
    {
        ret_code_t err_code;
        err_code = nrf_pwr_mgmt_init();
        APP_ERROR_CHECK(err_code);
    }
    
     
    static void idle_state_handle(void)
    {
        if (NRF_LOG_PROCESS() == false)
        {
            nrf_pwr_mgmt_run();
        }
    }
    
     
    static void log_init(void)
    {
        ret_code_t err_code = NRF_LOG_INIT(NULL);
        APP_ERROR_CHECK(err_code);
    
        NRF_LOG_DEFAULT_BACKENDS_INIT();
    }
    
     
    static void timer_init(void)
    {
        ret_code_t err_code = app_timer_init();
        APP_ERROR_CHECK(err_code);
    }
    
     
    static void gatt_init(void)
    {
        ret_code_t err_code = nrf_ble_gatt_init(&m_gatt, NULL);
        APP_ERROR_CHECK(err_code);
    }
    
    
    static void repeated_timer_handler(void * p_context)
    {
        ret_code_t err_code; 
        scan_start();
    }
    
     
    static void create_timers()
    {
        ret_code_t err_code;
    
        // Create timers
        err_code = app_timer_create(&m_repeated_timer_id,
                                    APP_TIMER_MODE_REPEATED,
                                    repeated_timer_handler);
        APP_ERROR_CHECK(err_code);
    
        err_code = app_timer_start(m_repeated_timer_id, APP_TIMER_TICKS(5 * 1000), NULL);
        APP_ERROR_CHECK(err_code);
    
    }
    
    
    int main(void)
    {
        NRF_LOG_INFO("--------  Start here -------" );
        // Start execution.
        NRF_LOG_INFO("Multilink example started.");
    
        // Initialize.
        log_init();
        timer_init();
        leds_init();
        buttons_init();
        power_management_init();
        ble_stack_init();
        gatt_init();
        db_discovery_init();
        
        lbs_c_init();
        hrs_c_init();
        bas_c_init();
        nus_c_init();
    
        ble_conn_state_init();
        scan_init();
        
        create_timers();
        scan_start();
    
    
        for (;;)
        {
            NRF_LOG_DEBUG("GATT server timeout.");
            idle_state_handle();
        }
    }
    

    3. Thank you. I will figure it out later. 

  • Hi

    2. You say that the polar belt starts advertising again after it has connected, does your central try to connect to it a second time? That should not happen, as the central shouldn't try connecting to already connected devices. Do you have any kind of check of what devices you connect to? Since you're not using the peer manager I guess maybe not. Using the peer manager is recommended to keep track of connections and security. What were the peer manager overlapping exactly?

    Best regards,

    Simon

Related