pc-ble-driver-py How to use numerical comparison ?

Hi,

I know that pc-ble-driver-py is archived but as there is no real alternative to it, I'm still using this package...

I'm using BLE nRF52840 dongle to connect to a BLE target.

When using exemple "test_lesc_security.py", I'm able to connect to the target. But when I tried to perform bonding (with keyboard_display and lesc=True), the event 'on_gap_evt_passkey_display' is trigger so I can read the passkey but I didn't understand how to match it. (The BLE target automatically match the key on it's side)

While using nRF Connect Desktop, I have no issue with numerical comparison (and same settings used with adapter.driver.ble_gat_authenticate).

Does someone successfully perform numerical comparison with pc-ble-driver-py ?

Note: 

In pc_ble_driver_py/lib.nrf_ble_driver_sd_api_v5.py there is 'ble_gap_evt_passkey_display_t" with "match_request". Does this mean it's possible to match the numerical comparison ?

Best regards,

Antoine

Parents
  • Hi

    Can you share some more details on what this BLE target device is exactly? If you're using the passkey display event and want to confirm it using an nRF52840 Dongle, the most common way to do so is to have the Dongle connected to a serial terminal that writes out the passkey as well and you can confirm it's the same like that. Then a button press or similar usually will allow the devices to connect, or if the target expects some sort of confirmation, you need to confirm the passkey on the target side. Most likely via a button.

    Best regards,

    Simon

  • Hi Simon,

    Thanks for your replay.
    The BLE target is a custom board (ESP32). This board display the passkey (screen connected) and automatically approved the numerical comparison (no action required from human on the target board).

    And with nRF Connect Desktop (so on the PC side), the numerical comparison work properly (passkey display and button "match"/"no match").

    But to use numerical comparison with pc-ble-driver-py (and not nRF Connect anymore in order to perform production test and automatized the BLE connexion) there is not example.
    I tried the following: 

     def on_gap_evt_passkey_display(self, ble_driver, conn_handle, passkey): # Reached with IOCaps = keyboard_display/none/yesno
            self.logger.info("Central on_gap_evt_passkey_display: {}".format(passkey))
            ascii_passkey = ''.join(map(chr, passkey))
            self.logger.info(f"Central on_gap_evt_passekey_display (ASCII): {ascii_passkey}")
            passkeyQueue.put(passkey)
    
            # Send passkey as it has been printed on display
            pk = util.list_to_uint8_array(b'\0')
            driver.sd_ble_gap_auth_key_reply(self.adapter.driver.rpc_adapter,conn_handle,driver.BLE_GAP_AUTH_KEY_TYPE_PASSKEY, pk.cast())

    and the event trigger with the correct passkey displayed but the auth_status is not successful :

    def check_bond(self):
        result = self.adapter.evt_sync[self.conn_handle].wait(BLEEvtID.gap_evt_auth_status)
        self.logger.info("Check bond result: {}".format(result))
        if result != BLEGapSecStatus.success:
            raise NordicSemiException("Auth Status returned error code: {}".format(result))

    Here "result" is a dict with "bond":0.

    With below the sec_params for adapter.driver.ble_gap_authenticate:

    self.sec_params = BLEGapSecParams(bond=True,
                                              mitm=True,
                                              lesc=True,
                                              keypress=True,
                                              io_caps=BLEGapIOCaps.yesno,
                                              oob=False,
                                              min_key_size=7,
                                              max_key_size=16,
                                              kdist_own=self.kdist_own,
                                              kdist_peer=self.kdist_peer)

    Best regards,

    Antoine

  • Hi Antoine,

    Before we proceed further, I would like to point out that there is an alternative in the nRF Connect SDK. It is the Bluetooth Shell.

    Could you please try that first?

    Best regards,

    Hieu

  • Hi Hieu,

    Sorry for the late reply. But do you want me to flash the nRF52840 dongle with Bluetooth Shell ? Or the target board ?

    The target board running with ESP32, the Bluetooth Shell cannot be used right ?

    Best regards,

    Antoine

  • Bluetooth Shell sample should work on ESP32 if you chose the right board files as this is a Zephyr sample.n the current pc_ble_driver_py sources the GAP passkey event is only half-exposed. When the SoftDevice raises BLE_GAP_EVT_PASSKEY_DISPLAY, the C struct includes a match_request flag that signals “this is numeric comparison; please tell me if the numbers match.” The Python glue strips that bit: pc_ble_driver_py/ble_driver.py (line 919) only copies the six-byte passkey, and when the event is dispatched pc_ble_driver_py/ble_driver.py L2718 observers receive just passkey=….   

    Because match_request never reaches Python, the app can’t know it should call sd_ble_gap_auth_key_reply with BLE_GAP_AUTH_KEY_TYPE_PASSKEY (accept) or _NONE (reject), so bonding fails even though the digits match.

    That is why nRF Connect Desktop works (its stack seems to be handling the confirm), while pc-ble-driver-py doesn’t. To support numeric comparison you will have to extend the driver to forward match_request through the observer chain and update your script to send the confirmation key reply. In short, the pc-ble-driver do not seem to support what you want unless you add 

  • Hi Susheel,

    Thank you for the details response.

    As I don't program that much on microcontrollers, it can take some times to perform the test with Bluetoth shell.

    From what I understand, at the event "on_gap_evt_passkey_display" I need to send back the passkey with "sd_ble_gap_auth_key_reply" as below ?

        def on_gap_evt_passkey_display(self, ble_driver, conn_handle, passkey): # Reached with IOCaps = keyboard_display/none/yesno
            self.logger.info("Central on_gap_evt_passkey_display: {}".format(passkey))
            ascii_passkey = ''.join(map(chr, passkey))
            self.logger.info(f"Central on_gap_evt_passekey_display (ASCII): {ascii_passkey}")
            passkeyQueue.put(passkey)
    
            # Send passkey as it has been printed on display
            pk = util.list_to_uint8_array(passkey)
            driver.sd_ble_gap_auth_key_reply(self.adapter.driver.rpc_adapter,conn_handle,driver.BLE_GAP_AUTH_KEY_TYPE_PASSKEY, pk.cast())

    Or should I add a parameter "match_request" in ble_driver.py (and observers.py) for on_gap_evt_passkey_display ?

    By any chance, do you have any exemple on that ?

    As nRF Connect is based on "pc-ble-driver-js", the "numerical comparison" method is a difference between pc-ble-driver-py and pc-ble-driver-js ? Both shouldn't have the same functionalities ?

    Best regards,

    Antoine

Reply
  • Hi Susheel,

    Thank you for the details response.

    As I don't program that much on microcontrollers, it can take some times to perform the test with Bluetoth shell.

    From what I understand, at the event "on_gap_evt_passkey_display" I need to send back the passkey with "sd_ble_gap_auth_key_reply" as below ?

        def on_gap_evt_passkey_display(self, ble_driver, conn_handle, passkey): # Reached with IOCaps = keyboard_display/none/yesno
            self.logger.info("Central on_gap_evt_passkey_display: {}".format(passkey))
            ascii_passkey = ''.join(map(chr, passkey))
            self.logger.info(f"Central on_gap_evt_passekey_display (ASCII): {ascii_passkey}")
            passkeyQueue.put(passkey)
    
            # Send passkey as it has been printed on display
            pk = util.list_to_uint8_array(passkey)
            driver.sd_ble_gap_auth_key_reply(self.adapter.driver.rpc_adapter,conn_handle,driver.BLE_GAP_AUTH_KEY_TYPE_PASSKEY, pk.cast())

    Or should I add a parameter "match_request" in ble_driver.py (and observers.py) for on_gap_evt_passkey_display ?

    By any chance, do you have any exemple on that ?

    As nRF Connect is based on "pc-ble-driver-js", the "numerical comparison" method is a difference between pc-ble-driver-py and pc-ble-driver-js ? Both shouldn't have the same functionalities ?

    Best regards,

    Antoine

Children
  • Hi Antoine, 

    I am no expert in the pc-ble-driver, just reading and reviewing the code along with you for the first time, but I have good experience with BLE.

    SoftDevice sends ble_gap_evt_passkey_display_t with both the six-digit passkey and a match_request bit; when match_request=1 you must reply with sd_ble_gap_auth_key_reply to accept/reject the comparison. pc-ble-driver-py seems to be dropping that flag, do not know why. So your script never knows to confirm and the pairing stalls. To fix this you must extend the feature by carrying the match_request through BLEGapPasskeyDisplay, forward it in the ble_driver and observers and update your handler to call sd_ble_gap_auth_key_reply with the proper key when match_request is true.  I do not have any sample or code snippets to show you but I think this most likely seems to be the reason.

  • Hi Shusheel,

    Thank you for the reply !

    I just update pc-ble-driver-py following your response to have the code below:

    - ble_driver:

    elif evt_id == BLEEvtID.gap_evt_passkey_display:
        for obs in self.observers:
            passkey = BLEGapPasskeyDisplay.from_c(ble_event.evt.gap_evt.params.passkey_display)
    
            obs.on_gap_evt_passkey_display(
                ble_driver=self,
                conn_handle=ble_event.evt.gap_evt.conn_handle,
                passkey=passkey.passkey,
                match_request=ble_event.evt.gap_evt.params.passkey_display.match_request
            )

    - observers:

    def on_gap_evt_passkey_display(
        self, ble_driver, conn_handle, passkey, match_request):
        logger.debug(
            "evt> passkey_display conn({}) passkey({}), match_request({})\n".format(
                conn_handle, passkey, match_request
            )
        )

    - On my test script:

    def on_gap_evt_passkey_display(self, ble_driver, conn_handle, passkey, match_request): # Reached with IOCaps = keyboard_display/none/yesno
        self.logger.info("Central on_gap_evt_passkey_display: {}".format(passkey))
        ascii_passkey = ''.join(map(chr, passkey))
        self.logger.info(f"Central on_gap_evt_passekey_display (ASCII): {ascii_passkey}")
        passkeyQueue.put(passkey)
        
        if match_request:
            # Send passkey as it has been printed on display
            pk = util.list_to_uint8_array(passkey) #b"\0")#
            driver.sd_ble_gap_auth_key_reply(self.adapter.driver.rpc_adapter,conn_handle,driver.BLE_GAP_AUTH_KEY_TYPE_PASSKEY, pk.cast())
    

    The "match_request" parameter is correctly set to 1 so the "if match_request" condition is True and sd_ble_gap_auth_key_reply is reached as expected.

    But I still got issue with bonding as status returned is:

    result = self.adapter.evt_sync[self.conn_handle].wait(BLEEvtID.gap_evt_auth_status)
    
    #=> result = {'error_src': 0, 'bonded': 0, 'sm1_levels': <pc_ble_driver_py.lib.nrf_ble_driver_sd_api_v5.ble_gap_sec_levels_t; proxy of <Swig Object of type 'ble_gap_sec_levels_t *' at 0x000002CBAD782370> >, 'sm2_levels': <pc_ble_driver_py.lib.nrf_ble_driver_sd_api_v5.ble_gap_sec_levels_t; proxy of <Swig Object of type 'ble_gap_sec_levels_t *' at 0x000002CBAE474FF0> >, 'kdist_own': <pc_ble_driver_py.ble_driver.BLEGapSecKDist object at 0x000002CBAE474C40>, 'kdist_peer': <pc_ble_driver_py.ble_driver.BLEGapSecKDist object at 0x000002CBAE474C70>, 'auth_status': <BLEGapSecStatus.timeout: 1>}

    Is there something else to perform numerical comparison ? Like sending something else than just the passkey ? Or waiting for an event that I could miss and not set the hanlder ?

    Best regards,

    Antoine

  • Quick update on the topic:

    The function "sd_ble_gap_auth_key_reply" return the value: 7

    resp = driver.sd_ble_gap_auth_key_reply(self.adapter.driver.rpc_adapter, conn_handle, driver.BLE_GAP_AUTH_KEY_TYPE_NONE, pk.cast())

    From https://docs.nordicsemi.com/bundle/s145-latest/page/group_nrf_error_ga0a5831cf5092e0dd43a01869676ee076.html#ga0a5831cf5092e0dd43a01869676ee076, it's look like 7 means NRF_ERROR_INVALID_PARAM.

    Still with the documentation (https://docs.nordicsemi.com/bundle/s145-latest/page/group_BLE_GAP_FUNCTIONS_ga01e04f368c8e7b3aaa31740dbd53bd12.html#ga01e04f368c8e7b3aaa31740dbd53bd12), function "sd_ble_gap_auth_key_reply" only have 3 parameters (conn_handle, key_type, p_key). But in the pc-ble-driver-py, this function have 4 parameters ("adapter" added). Why there is this difference ?

    Does somone know what could cause NRF_ERROR_INVALID_PARAM ?

    Best regards,

    Antoine

  • Last update:

    Following this spec : https://docs.nordicsemi.com/bundle/s145-latest/page/group_BLE_GAP_FUNCTIONS_ga01e04f368c8e7b3aaa31740dbd53bd12.html#ga01e04f368c8e7b3aaa31740dbd53bd12

    NULL must be used instead of passkey when confirming LE Secure Connections Numeric Comparison.

    Code must be adapted as below:

        def on_gap_evt_passkey_display(self, ble_driver, conn_handle, match_request, passkey): # Reached with IOCaps = keyboard_display/none/yesno
            self.logger.info("Central on_gap_evt_passkey_display: {}".format(passkey))
            ascii_passkey = ''.join(map(chr, passkey))
            self.logger.info(f"Central on_gap_evt_passekey_display (ASCII): {ascii_passkey}")
    
            if match_request:
                resp = driver.sd_ble_gap_auth_key_reply(self.adapter.driver.rpc_adapter, conn_handle, driver.BLE_GAP_AUTH_KEY_TYPE_PASSKEY, None)
                self.logger.info(f"auth_key_reply return value: {resp}")

    I tried with b'\0' for NULL character but only work with "None".

    Anyway, numerical comparison is available following the details posted by   (thanks again !) and using 'None' instead of 'pk.cast()'

Related