This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

pc-ble-driver-py - sd_ble_gap_sec_params_reply fails

I have been using the pc-ble-driver Python bindings to implement a peripheral. I have extended BLEDriver to support sd_ble_gatts_* the functions. I now have a working peripheral, however I have a problem when trying to support bonding.

When the central tries to bond, my code receives a BLE_GAP_EVT_SEC_PARAMS_REQUEST event. I then call BleDriver.ble_gap_sec_params_reply. However the driver appears to hang at this point, as sd_ble_gap_sec_params_reply never returns (I don't even get an error code). I'm using the latest pc-ble-driver-py module on Linux with connectivity_1.0.1_115k2_with_s130_2.0.1.hex on a NRF51 development board.

UPDATE: I've changed the code to call sd_ble_gap_sec_params_reply on the main thread, as suggested by below. The call now returns instead of hanging, but fails with error code 3 (Internal Error). I've updated the below code to show this:

import sys
import time 
import logging
import threading

from pc_ble_driver_py.observers import BLEDriverObserver

logging.basicConfig(level=logging.DEBUG)

def init(conn_ic_id):
    global BLEDriver, BLEAdvData, BLEGapSecStatus, BLEGapSecParams, BLEGapIOCaps, BLEGapSecKDist
    from pc_ble_driver_py import config
    config.__conn_ic_id__ = conn_ic_id
    from pc_ble_driver_py.ble_driver import BLEDriver, BLEAdvData, BLEGapSecStatus, BLEGapSecParams, BLEGapIOCaps, BLEGapSecKDist

class PeripheralTest(BLEDriverObserver):

    def on_gap_evt_connected(self, ble_driver, conn_handle, peer_addr, role, conn_params):
        print('on_gap_evt_connected - conn_handle: {} role: {}'.format(conn_handle, role))

    def on_gap_evt_disconnected(self, ble_driver, conn_handle, reason):
        print('on_gap_evt_disconnected - conn_handle: {} reason: {}'.format(conn_handle, reason))

    def on_gap_evt_sec_params_request(self, ble_driver, conn_handle, peer_params):
        print('on_gap_evt_sec_params_request - conn_handle'.format('conn_handle'))
        for k in ('bond', 'mitm', 'lesc', 'keypress', 'io_caps', 'oob', 'min_key_size', 'max_key_size'):
            val = getattr(peer_params, k)
            print('\t{}: {}'.format(k, val))
        self.sec_params_requested = (ble_driver, conn_handle)
        
    def do_sec_params_reply(self):
        ble_driver, conn_handle = self.sec_params_requested
        self.sec_params_requested = None
        sec_params = BLEGapSecParams(bond = 1, mitm = 0, lesc = 0,keypress = 0, io_caps = BLEGapIOCaps.none, oob = 0, 
            min_key_size = 7, max_key_size = 16,
            kdist_own = BLEGapSecKDist(enc=1, id=1, sign=0, link=0),
            kdist_peer = BLEGapSecKDist(enc=1, id=1, sign=0, link=0))
        
        # this is now called on the main thread, but returns error code 3 - NRF_ERROR_INTERNAL
        ble_driver.ble_gap_sec_params_reply(conn_handle, BLEGapSecStatus.success, sec_params, None, None)

    def loop(self):
        self.sec_params_requested = None
        while True:
            if self.sec_params_requested:
                self.do_sec_params_reply()
            time.sleep(0.1)

def main(serial_port):
    print('Serial port used: {}'.format(serial_port))
    driver = BLEDriver(serial_port=serial_port, auto_flash=False)
    periph = PeripheralTest()
    driver.observer_register(periph)
    driver.open()
    driver.ble_enable()
    driver.ble_gap_adv_data_set(BLEAdvData(complete_local_name='Bond Test'))
    driver.ble_gap_adv_start()
    print('Started advertising')
    try:
        periph.loop()
    except KeyboardInterrupt:
        pass
    print('Closing')
    driver.close()

if __name__ == '__main__':
    serial_port = None
    if len(sys.argv) < 3:
        print("Please specify connectivity IC identifier (NRF51, NRF52) and serial port")
        exit(1)
    init(sys.argv[1])
    serial_port = sys.argv[2]
    main(serial_port)
    quit()
  • Hi Paul,

    We found it's a bug with the ble_driver that if you call ble_gap_sec_params_reply() inside an interrupt handler it will crash.

    So if you can instead set a flag and then execute the command in main loop it should be ok.

  • Thanks for looking into this! Calling ble_gap_sec_params_reply from the main thread no longer crashes but instead returns error code 3. I've updated my example above to show this.

  • Hi Paul, Not sure why you got the issue. I did a quick mod of your example and it worked for me:

    import sys
    import time 
    import logging
    from pc_ble_driver_py.observers import BLEDriverObserver
    
    logging.basicConfig(level=logging.DEBUG)
    flag = 0
    def init(conn_ic_id):
        global BLEDriver, BLEAdvData, BLEGapSecStatus, BLEGapSecParams, BLEGapIOCaps, BLEGapSecKDist
        from pc_ble_driver_py import config
        config.__conn_ic_id__ = conn_ic_id
        from pc_ble_driver_py.ble_driver import BLEDriver, BLEAdvData, BLEGapSecStatus, BLEGapSecParams, BLEGapIOCaps, BLEGapSecKDist
    
    class PeripheralTest(BLEDriverObserver):
        def on_gap_evt_connected(self, ble_driver, conn_handle, peer_addr, role, conn_params):
            print('on_gap_evt_connected - conn_handle: {} role: {}'.format(conn_handle, role))
    
        def on_gap_evt_disconnected(self, ble_driver, conn_handle, reason):
            print('on_gap_evt_disconnected - conn_handle: {} reason: {}'.format(conn_handle, reason))
    
        def on_gap_evt_sec_params_request(self, ble_driver, conn_handle, peer_params):
            global flag
            flag = 1
            print('on_gap_evt_sec_params_request - conn_handle'.format('conn_handle'))
     
    def main(serial_port):
        global flag
        print('Serial port used: {}'.format(serial_port))
        driver = BLEDriver(serial_port=serial_port, auto_flash=False)
        periph = PeripheralTest()
        driver.observer_register(periph)
        driver.open()
        driver.ble_enable()
        driver.ble_gap_adv_data_set(BLEAdvData(complete_local_name='Bond Test'))
        driver.ble_gap_adv_start()
        print('Started advertising')
        try:
            while 1==1:
                time.sleep(1)
                if flag == 1:    
                    sec_params = BLEGapSecParams(bond = 1, mitm = 0, lesc = 0,keypress = 0, io_caps = BLEGapIOCaps.none, oob = 0, 
                    min_key_size = 7, max_key_size = 16,
                    kdist_own = BLEGapSecKDist(enc=1, id=1, sign=0, link=0),
                    kdist_peer = BLEGapSecKDist(enc=1, id=1, sign=0, link=0))
                    driver.ble_gap_sec_params_reply(0, BLEGapSecStatus.success, sec_params, None, None)		
                    flag=0
        except KeyboardInterrupt:
            pass
        print('Closing')
        driver.close()
    
    if __name__ == '__main__':
        serial_port = None
        if len(sys.argv) < 3:
            print("Please specify connectivity IC identifier (NRF51, NRF52) and serial port")
            exit(1)
        init(sys.argv[1])
        serial_port = sys.argv[2]
        main(serial_port)
        quit()
    
  • Unfortunately, I still get the same error 3 with your code (which seems to be equivalent to mine). I'm using nRF Connect on Android 6 to connect and initiate bonding.

  • Could you catch a sniffer trace ? I tried again with a nRF51 and it also worked (tried with nRF52 and v3 earlier) it also work with my Android S6.

    Note that error 3 is not in the list of error return code of sd_ble_gap_sec_params_reply()

Related