How to determine Bluetooth connection and bond status?

Hello Nordic Engineers!

Thank you in advance for your time and advice.

===== Overview =====

I'm developing (sort of) a Bluetooth mouse using the nRF Desktop Application as a jumping-off point. I've run into an undesirable program state and I'm not sure how to go about handling / preventing it.

It's possible to enter a state where the central (e.g. your computer) has forgotten the device (dev kit), yet the device is using directed advertising towards that computer. That directed advertising makes the device invisible to anyone (else) searching for it, including the central that just forgot it (biggest issue).

The only way out of this state that we've found is to manually run a peer erase, which is acceptable, but not overtly obvious (especially to an end user).

Ultimately, I'd like to have a "Bluetooth No Connection Indicator" that is distinct from "Bluetooth Advertising" -- with this, I can inform the user via LED that the device is looking for a specific connection that it hasn't found yet. That indicator will prompt the user to either run a Peer Erase Operation, connect to the dedicated device (in the case where the bonded central is just "off"), or switch to an unoccupied peer identity with our peer swap button (in the case that the user wants to add a second supported central).

===== Environment =====

Here are my environment details:

nRF52840DK (development environment)

which will transition to Xiao BLE Sense for (production environment)

Using Nordic's SDK v2.5.0 (installed via nRF Connect Desktop App)

(Though I'm willing to switch to 2.6.Latest if there is a fix there)

Running in VS Code, Win 11 Host Machine, MINGW Bash environment

===== Questions =====

Preamble to question:

Connecting to our device from a central causes the device store some bond data. When the central disconnects from that device, the information persists, which is desirable. However, once the device resets (e.g. Device Power Management / User switches "Off"), that information still persists, but is no longer available to the user (my) application (that I can find). The device doesn't automatically reconnect (which makes sense since the central dropped it), but also doesn't show as available on either the (former) central or other scanning devices.

In summary: the problem state occurs when the central disconnects, then the peripheral resets, which causes the device to become invisible until a peer erase. I'd like to indicate that the device is in this state, despite device resets.

Question:

How can I access that information from my application modules?

I would be happy for a way to query:

1. The current bt_conn object, so I can check it's connection status and use its security (level) information to judge if that connection is "occupied" by a bond

2. The ble_adv data, so I can determine if the advertising is directed

3. The generic "ble settings / bond settings" persistent information -- to determine which identities are "occupied"

Where I'm stuck:

When my device boots, I get quite a lot of events about the startup of the device. Notably, though, I do not see an event whose structure contains info on (1), (2), or (3) above that might let me infer the current saved-information state.

Those only occur once I actually run a connect operation, which isn't possible if the device is already directed advertising to a computer that ignores it.

Thanks again for any advice you can give!

============

Wishing you all a happy weekend,

    - Finn

  • Hi Einar,

    Thanks for sticking with me. I think I have a bit better insight as to what's going on now than I did before. I'm incorrect about directed advertising, so please disregard that component.

    What I actually MEANT by directed advertising was a different behavior that I wrongly assumed was directed. (This is because the bond id and the peer id matched -- more on this later.)

    I think I disagree that there is no connection object before a pairing event. Even immediately on reset, the method bt_conn_foreach returns a connection object (but in a disconnected state).

    First, I'll share minimal changes to nRF Desktop that will let you reproduce.

    First:

    set CONFIG_BT_MAX_PAIRED = 3 and CONFIG_BT_ID_MAX = 4.

    Second (required after First change):

    Make the following changes to (your board)/led_state_def.h

    Change this:
    
    static const struct led_effect led_peer_state_effect[LED_PEER_COUNT][LED_PEER_STATE_COUNT] = {
    	{
    		[LED_PEER_STATE_DISCONNECTED]   = LED_EFFECT_LED_OFF(),
    		[LED_PEER_STATE_CONNECTED]      = LED_EFFECT_LED_ON(LED_COLOR(100, 100, 100)),
    		[LED_PEER_STATE_PEER_SEARCH]    = LED_EFFECT_LED_BREATH(1000, LED_COLOR(100, 100, 100)),
    		[LED_PEER_STATE_CONFIRM_SELECT] = LED_EFFECT_LED_BLINK(50, LED_COLOR(100, 100, 100)),
    		[LED_PEER_STATE_CONFIRM_ERASE]  = LED_EFFECT_LED_BLINK(25, LED_COLOR(100, 100, 100)),
    		[LED_PEER_STATE_ERASE_ADV]	= LED_EFFECT_LED_BREATH(100, LED_COLOR(100, 100, 100)),
    	},
    };
    
    To this:
    
    static const struct led_effect led_peer_state_effect[LED_PEER_COUNT][LED_PEER_STATE_COUNT] = {
    	[0 ... LED_PEER_COUNT-1] = {
    		[LED_PEER_STATE_DISCONNECTED]   = LED_EFFECT_LED_OFF(),
    		[LED_PEER_STATE_CONNECTED]      = LED_EFFECT_LED_ON(LED_COLOR(100, 100, 100)),
    		[LED_PEER_STATE_PEER_SEARCH]    = LED_EFFECT_LED_BREATH(1000, LED_COLOR(100, 100, 100)),
    		[LED_PEER_STATE_CONFIRM_SELECT] = LED_EFFECT_LED_BLINK(50, LED_COLOR(100, 100, 100)),
    		[LED_PEER_STATE_CONFIRM_ERASE]  = LED_EFFECT_LED_BLINK(25, LED_COLOR(100, 100, 100)),
    		[LED_PEER_STATE_ERASE_ADV]	= LED_EFFECT_LED_BREATH(100, LED_COLOR(100, 100, 100)),
    	},
    };
    

    ----------

    Next, (but not required)

    I also add some introspection code that triggers on a button event to log data about the current bonds and connections. That function and its helpers are below (and I subscribe to caf/button_event in ble_bond.c). As a summary, this code calls bt_foreach_bond with identities ranging from 0-4 and bt_conn_foreach.

    
    /*** Excerpt from ble_bond.c ***/
    
    static void conn_check_func(const struct bt_conn *conn, void *user_data) {
    	static struct bt_conn_info conn_info;
    	int err = bt_conn_get_info(conn, &conn_info);
    	LOG_INF(
    		"CONN INFO: Type (%d), Role (0x%02x), ID (0x%02x), State (%d), Security { Level (%d), Size (%d), Flags (%d) }",
    		conn_info.type, conn_info.role, conn_info.id, conn_info.state, 
    		conn_info.security.level, conn_info.security.enc_key_size, conn_info.security.flags
    	);
    }
    
    static void bond_check_func(const struct bt_bond_info *bond, void *user_data) {
    	int my_addr = 0;
    	for (int i = 0; i < BT_ADDR_SIZE; i++) {
    		my_addr += (bond->addr.a.val[i] << 8*i);
    	}
    
    
    	LOG_INF("Bond: Address (0x%012x), Type (0x%02x)", my_addr, bond->addr.type);
    }
    
    static bool handle_button_event(const struct button_event *event) {
    	if(event->key_id == 0x01 && event->pressed == true)
    	{
    		bt_foreach_bond(BT_ID_DEFAULT, bond_check_func, NULL);
    		LOG_INF("1");
    		bt_foreach_bond(1, bond_check_func, NULL);
    		LOG_INF("2");
    		bt_foreach_bond(2, bond_check_func, NULL);
    		LOG_INF("3");
    		bt_foreach_bond(3, bond_check_func, NULL);
    		LOG_INF("4");
    		bt_foreach_bond(4, bond_check_func, NULL);
    
    		bt_conn_foreach(BT_CONN_TYPE_LE, conn_check_func, NULL);
    	}
    	return false;
    }

    (Which, thank you for the pointer to the foreach methods -- I was stuck until I started using both)

    I've now observed is that my "error state" occurs when the bluetooth connection id also contains a bond on that same identity.

    Here is a trace of ble_bond and ble_connection information in both the discoverable and non-discoverable state.

    /*** Working -- no bond ***/
    [00099557] <inf> ble_bond: 0 (default)
    [00099558] <inf> ble_bond: 1
    [00099558] <inf> ble_bond: 2
    [00099559] <inf> ble_bond: 3
    [00099559] <inf> ble_bond: 4 (out of bounds)
    [00099560] <inf> ble_bond: CONN INFO: Type (1), Role (0x00), ID (0x00), State (1), Security { Level (1), Size (0), Flags (0) }
    
    
    /*** Working -- active connection ***/
    [04858377] <inf> ble_bond: 0 (default)
    [04858378] <inf> ble_bond: Bond: Address (0x00009ea5fbb2), Type (0x00)
    [04858378] <inf> ble_bond: 1
    [04858379] <inf> ble_bond: 2
    [04858379] <inf> ble_bond: 3
    [04858379] <inf> ble_bond: 4 (out of bounds)
    [04858381] <inf> ble_bond: CONN INFO: Type (1), Role (0x01), ID (0x00), State (2), Security { Level (2), Size (16), Flags (1) }
    
    
    /*** Working -- bond present ***/
    // Connection ID is not equal bond's id
    [00366213] <inf> ble_bond: 0 (default)
    [00366214] <inf> ble_bond: Bond: Address (0x00009ea5fbb2), Type (0x00)
    [00366214] <inf> ble_bond: 1
    [00366215] <inf> ble_bond: 2
    [00366215] <inf> ble_bond: 3
    [00366216] <inf> ble_bond: 4 (out of bounds)
    [00366216] <inf> ble_bond: CONN INFO: Type (1), Role (0x00), ID (0x03), State (1), Security { Level (1), Size (0), Flags (0) }
    
    
    /*** NOT Working -- bond present (my error state) ***/
    // Connection ID matches bond's id
    [08168933] <inf> ble_bond: 0 (default)
    [08168934] <inf> ble_bond: Bond: Address (0x00009ea5fbb2), Type (0x00)
    [08168934] <inf> ble_bond: 1
    [08168935] <inf> ble_bond: 2
    [08168935] <inf> ble_bond: 3
    [08168936] <inf> ble_bond: 4 (out of bounds)
    [08168936] <inf> ble_bond: CONN INFO: Type (1), Role (0x00), ID (0x00), State (1), Security { Level (1), Size (0), Flags (0) }

    Briefly, I'll jump back to (my confusion regarding) directed advertising. I thought that since there was a bond saved and associated with the current connection identity, the device must be using directed advertising.

    Instead, what happens when the saved bond matches the current ID, is that the advertising packets provided by CAF have the following flags in their advertising.

    WORKING: [Packet 11]

    Flags
        Length: 2
        Type: Flags (0x01)
        000. .... = Reserved: 0x0
        ...0 .... = Simultaneous LE and BR/EDR to Same Device Capable (Host): false (0x0)
        .... 0... = Simultaneous LE and BR/EDR to Same Device Capable (Controller): false (0x0)
        .... .1.. = BR/EDR Not Supported: true (0x1)
        .... ..1. = LE General Discoverable Mode: true (0x1)
        .... ...0 = LE Limited Discoverable Mode: false (0x0)

    NOT WORKING: [Packet 4448]

    Flags
        Length: 2
        Type: Flags (0x01)
        000. .... = Reserved: 0x0
        ...0 .... = Simultaneous LE and BR/EDR to Same Device Capable (Host): false (0x0)
        .... 0... = Simultaneous LE and BR/EDR to Same Device Capable (Controller): false (0x0)
        .... .1.. = BR/EDR Not Supported: true (0x1)
        .... ..0. = LE General Discoverable Mode: false (0x0)
        .... ...0 = LE Limited Discoverable Mode: false (0x0)

    For some reason, when the computer disconnects, the advertising flags are set to non-discoverable.

    I now have the tools to indicate to my user that their device is need of a reset, but I still need a way to correct these flags.

    Do you have advice for how I might make my device discoverable in this state? 

    (also, would there be unforeseen consequences to this?)

    Thank you again for your reply.

    Best,

        - Finn

  • Hi Finn,

    The CAF use adv_prov (see adv_prov.c/.h) to among other thing configure the LE General discoverable flag based on the pairing_mode, could that be it here? If you look at the implementation in ble_adv.c you can see that pairing mode is set based on the bond count (existing bond(s) or not). If pairing mode, then the general discoverable flag is set. If not, the flag is not set.

  • Hi Einar,

    Thank you -- the current behavior does make sense. I'm pretty sure there's no adv_prov.c, but there is an  adv_prov.h which holds the state.pairing_mode, and I found the set_state code in ble_adv.c (update_undirected_advertising).

    Unfortunately, that behavior means that disconnecting via the computer prevents rediscovery by any device, forever. (until a peer erase operation).

    I've figured out how to warn the user that they've entered this state, but not a graceful way to handle it.

    What I would like the device to do is this:

    • Device disconnected from bonded peer (reason could be)
      • Disconnect signal from computer
      • Bonded peer is too far away (or off).
    • Now, a bond is present, but the connection is not established
      • Device indicates non-connected, but bonded state
        • Done (documenting solution for future developers)
          • Use bt_conn_foreach to determine current id
          • Use id in bt_foreach_bond(id, helper_func, bool_by_ref)
          • Inform user of device state (with LED)
      • Device allows discoverability
        • (Need help here)
    • The now-discoverable device should
      • Connect to its existing bond (if the bonded device appears)
      • Accept an incoming pair request, replacing its old bond

    In your opinion, what's the best way to get this behavior. I'm afraid your answer will be to implement my own BLE mangement system without the CAF modules.

    Thank you for all your help, it makes a huge difference.

    All the best,

        - Finn

  • Hi Finn,

    FinnBiggs said:
    In your opinion, what's the best way to get this behavior. I'm afraid your answer will be to implement my own BLE mangement system without the CAF modules.

    Interesting you should say that. CAF is a set of large modules that are not as generic as they may seem and primarily intended for nRF Desktop. For most applications handling this without CAF is indeed simpler and will often give you a better understanding of what is going on.

    FinnBiggs said:
    Device allows discoverability
    • (Need help here)

    Then you need to ensure that the BT_LE_AD_GENERAL flag is inlcuded. And with CAF that means not having an existing bond. So you can either modify CAF or erase the bond. The latter would then mean that the user of the computer that was bonded would have to erase the old bond and bond again, so that would not be very elegant.

Related