NCS Connection handle or alternative for keeping track of multiple connected devices

Hello,

I am using NCS v2.5.0 and I am working on an central application that keeps track of multiple connected peripheral devices. I wanted to manage the connected devices through a list of structs that contain some information about each connected device. The plan was to easily identify connection events based on their connection handle, and thus use the connection handle as the array index to identify the relevant device when an event happens similar to the below example:

static uint8_t on_sensor_data_received(
    struct bt_conn *conn,
    struct bt_gatt_subscribe_params *params,
    const void *data, uint16_t length)
{
    uint16_t conn_handle = conn->handle;
    struct peripheral_t peripheral = connected_devices[conn_handle];
    // Do something with peripheral
    
    return BT_GATT_ITER_CONTINUE;
}

The issue is that it seems that I cannot use the bt_conn struct pointer like that, because I get a 'pointer to incomplete class type "struct bt_conn" is not allowed' build error, even when I include <zephyr/bluetooth/conn.h>.

I use Visual Studio Code (with nRF Connect Extension), and when I go to the definition of the bt_conn struct, it takes me to conn_internal.h, which I cannot include. The definition there indicates that the handle is present in the bt_conn struct.

I am also tracking the RSSI of connected devices, and following the HCI Power Control example, I saw that you can get the connection handle using the bt_hci_get_conn_handle function from the HCI driver, however when I try to get the handle in the on_disconnected callback, the handle no longer exists using that approach(since there is a connection anymore).

So now I'm wondering, what is the best way to get a connection handle (from a bt_conn struct?) such that it also applies during the on_disconnected callback (i.e. connection handle of the lost connection)? Or is there a better way to create a list of structs that can be used to keep information from each connected device and that each individual struct can easily be extracted upon a connection event?

And why can I not do things with the bt_conn struct pointer, even if I include conn.h?

Parents
  • Ho Wout

    The bt_conn struct is a opaque type, meaning the internal fields are not known outside the Bluetooth libraries. This is by design to avoid applications altering the internal fields, and to allow changing the internals without risk of breaking application code. 

    Instead you should simply use the pointer itself as the handle, and use that to reference the connection. 

    I made a multi link demo some time back and used this principle to keep track of the connected devices, and saved various link specific state data in a struct that also included the *conn pointer. You can see the struct here

    I must admit to not being very familiar with opaque C types myself, and I found this article quite illuminating. 

    Best regards
    Torbjørn

  • Hi Torbjørn,

    It's been a while since I asked this question, but after developing quite a bit more and getting more familiar with the SDK and the patterns used there, I was wondering if we can't just use CONTAINTER_OF(conn, struct per_context_t, conn) (in the case of the project you shared) to get the correct per_context pointer?

    It seems that that does exactly what is needed without having to loop over the array, and hence would achieve O(1) complexity. Or am I missing something as to why this won't work (reliably) in this case?

  • Hi,

    I do not immediately see how you will ue container_of() here? However, as Torbjørn wrote, the number of connections is so low that the theoretical issue of looping over it should be negligable. So it seems there is verry little to gain by attempting to optimize this.

  • Hi Einar,

    Thank you for getting back to me. I commented this not from an approach to optimize further, as it would indeed not matter that much, but really from the perspective of using patterns used elsewhere in the (Zephyr) SDK. 

    Since the struct type per_context_t contains the struct bt_conn *conn, I was under the impression that using CONTAINER_OF(conn, struct per_context_t, conn) for example in the disconnected callback would allow to immediately get the pointer reference to the correct per_context 'object' related to the connection that was disconnected.

    My understanding is based on the recommended way of passing additional information (context) to a work item, i.e. creating a struct containing the work item along with whatever context you want to provide, and then retrieving the context by calling CONTAINER_OF() on the work item pointer which is passed to the work handler function.

    Am I missing something that would invalidate this approach for the use case I'm talking about?

Reply
  • Hi Einar,

    Thank you for getting back to me. I commented this not from an approach to optimize further, as it would indeed not matter that much, but really from the perspective of using patterns used elsewhere in the (Zephyr) SDK. 

    Since the struct type per_context_t contains the struct bt_conn *conn, I was under the impression that using CONTAINER_OF(conn, struct per_context_t, conn) for example in the disconnected callback would allow to immediately get the pointer reference to the correct per_context 'object' related to the connection that was disconnected.

    My understanding is based on the recommended way of passing additional information (context) to a work item, i.e. creating a struct containing the work item along with whatever context you want to provide, and then retrieving the context by calling CONTAINER_OF() on the work item pointer which is passed to the work handler function.

    Am I missing something that would invalidate this approach for the use case I'm talking about?

Children
  • Ah no, nevermind, I see that it is in fact not the same if it is possible that the element that I am looking for is actually not part of any per_context object in the array.

    So if for example something is disconnecting but it wasn't part of the array, using CONTAINER_OF would probably return a faulty pointer (right?), where as doing it in the way it's done in the example would return NULL which could be used to ignore further processing if it doesn't make sense.

Related