How to Resubscribe to Old BLE Handles Upon Reconnection

Hello Nordic Team,

For my application, I am using two nRF54L15 chips with one configured as a master and the other a slave. For the remainder of this post, I will be discussing my Bluetooth Master code. I setup the discovery process to search through all characteristics of the slave device and subscribe to the characteristics that I want notifying.

static struct bt_gatt_subscribe_params notifyStruct[NUM_OF_NOTIFY_CHARS] = {0};

// For initially subscribing to a notification
static void collectNotifyHandle(struct bt_conn *conn, const struct bt_gatt_attr *attr) {
    int err;
    //volatile struct bt_gatt_attr* attrTemp = attr;
    printk("      - - Descriptor Discovered. Val = 0x%x, Handle = %d\n", BT_UUID_16(attr->uuid)->val, attr->handle);

    // Check if Next Notify Characteristic is Set
    switch(nextNotifyChar) {
        //// Femur Sensor Notifies
        case NOTIFY_FS_STATUS:
            notifyStruct[NOTIFY_FS_STATUS].notify = notifyFSStatusCb;
            break;
        case NOTIFY_FS_QUAT:
            notifyStruct[NOTIFY_FS_QUAT].notify = notifyFSQuatCb;
            break;
        case NOTIFY_FS_LOG:
            notifyStruct[NOTIFY_FS_LOG].notify = notifyFSLogCb;
            break;
        case NOTIFY_FS_RAW:
            notifyStruct[NOTIFY_FS_RAW].notify = notifyFSRawCb;
            break;

        //// Glasses Notifies
        case NOTIFY_GL_VALUE:
            notifyStruct[NOTIFY_GL_VALUE].notify = notifyGLValueCb;
            break;

        //// Tablet Notifies
        case NOTIFY_TB_CMD:
            notifyStruct[NOTIFY_TB_CMD].notify = notifyTBCmdCb;
            break;
        case NOTIFY_FWU_INFO:
            notifyStruct[NOTIFY_FWU_INFO].notify = notifyFWUInfoCb;
            break;
        case NOTIFY_FWU_DATA:
            notifyStruct[NOTIFY_FWU_DATA].notify = notifyFWUDataCb;
            break;

        case NUM_OF_NOTIFY_CHARS:
        default:
            // Do Nothing
            break;
    }

    // If Next Notify Characteristic is Set, Grab Generic Subscribe Data
    if (nextNotifyChar < NUM_OF_NOTIFY_CHARS) {
        notifyStruct[nextNotifyChar].value = BT_GATT_CCC_NOTIFY;
        if (attr->uuid->type == BT_UUID_TYPE_128){
            notifyStruct[nextNotifyChar].ccc_handle = attr->handle + 1;
        }else{
            notifyStruct[nextNotifyChar].ccc_handle = attr->handle;
        }

        // Attempt Subscription
        err = bt_gatt_subscribe(conn, &notifyStruct[nextNotifyChar]);
        if (err && err != -EALREADY) {
            printk("[BLE] FAILURE: Subscribe failed (err %d)\n", err);
        } else {
            printk("      - - Subscribed to Handle = %d, Notify = %d\n", attr->handle,  nextNotifyChar);
        }

        // Reset Notify Structure
        nextNotifyChar = NUM_OF_NOTIFY_CHARS;
    }
}

// For receiving a subscribed notification
uint8_t genericNotifyCollect(volatile uint8_t* storageArr, volatile bool* storageFlag, void (*codeInject)(uint8_t*),
    struct bt_gatt_subscribe_params *params, const void *data) {
    int result = BT_GATT_ITER_STOP;

    if ((uint8_t*)data) {
        uint8_t* myData = (uint8_t*)data;

        // Optionally Collect the Data
        if (storageArr != NULL && storageFlag != NULL) {
            *storageFlag = true;
		    oa_memcpy((uint8_t*)storageArr, myData, VND_MAX_LEN);
        }

        // Optionally, Perform Code Injection
        // This drastically reduces the code for some files
        if (codeInject != NULL) {
            codeInject(myData);
        }

		// Continue Receiving Notification
		result = BT_GATT_ITER_CONTINUE;

    } else {
        printk("[BLE] UNSUBSCRIBING: Notify Returned Null: %d\n", params->value_handle);
		params->value_handle = 0U;
    }

	return result;
}


// For re-subscribing to stored handles on re-pair
void subscribeToStoredHandles(struct bt_conn *conn) {
    bool subscribe;

    for (NOTIFY_CHAR_HANDLES_T handle = NOTIFY_FS_STATUS; handle < NUM_OF_NOTIFY_CHARS; handle++) {
        // Reset Subscribe Status
        subscribe = false;

        // Check if Handle is Valid
        if (notifyStruct[handle].ccc_handle != 0) {
            // Determine if Current Connection Matches Handle
            switch(handle) {
                case NOTIFY_FS_STATUS:
                case NOTIFY_FS_QUAT:
                case NOTIFY_FS_LOG:
                case NOTIFY_FS_RAW:
                    if (conn == *ptrFSConn) {
                        subscribe = true;
                    }
                    break;
                case NOTIFY_GL_VALUE:
                    if (conn == *ptrGLConn) {
                        subscribe = true;
                    }
                    break;
                case NOTIFY_TB_CMD:
                case NOTIFY_FWU_INFO:
                case NOTIFY_FWU_DATA:
                    if (conn == *ptrTBConn) {
                        subscribe = true;
                    }
                    break;
                default:
                    break;
            }

            // Perform Subscribe
            if (subscribe) {
                int err = bt_gatt_subscribe(conn, &notifyStruct[handle]);
                if (err) {
                    printk("[BLE] FAILURE: Subscribe failed (err %d)\n", err);
                } else {
                    printk("      - - Subscribed to Handle = %d, Notify = %d\n", notifyStruct[handle].ccc_handle, handle);
                }
            }
        }
    }
}

It is common for my application that the two devices lose connection and have to re-pair. To skip the discover process, I maintain the structures used to write/read/subscribe with the my device and re-use them. Both of my devices will not be software updated during this disconnection, so I am assuming this is safe to do. 

Unfortunately, this tactic is not completely working. I have not thoroughly tested the write/read, but the notifies do not seem to re-subscribe correctly. Currently, two of them are failing to re-subscribe. It seems when the notifications unsubscribe during the disconnect, some of the notify structure variables are changed. I printed out the entire notify structure and found the node member is being set to 0 for the values that are failing to subscribe. My custom logs are shown below that hopefully provide some context. Is there a more proper way to accomplish what I am doing? Or am I missing a step to reset the connection/notifications properly?

[BLE] Primary Service Discovered. Val = 0x1855, Handle = 41, End Handle = 55
      - - Subscribed to value=1, value_handle=45, ccc_handle=46, notify=174433, subscribe=0, node=0,         flags=536904816, handle=0
      - - Subscribed to value=1, value_handle=48, ccc_handle=49, notify=174469, subscribe=0, node=536904820, flags=536904840, handle=1
      - - Subscribed to value=1, value_handle=51, ccc_handle=52, notify=174505, subscribe=0, node=536904844, flags=536904864, handle=2
      - - Subscribed to value=1, value_handle=54, ccc_handle=55, notify=174537, subscribe=0, node=536904868, flags=536904888, handle=3
[BLE] Bluetooth Discovery: Complete


[BLE] UNSUBSCRIBING: Notify Returned Null: 54
[BLE] UNSUBSCRIBING: Notify Returned Null: 51
[BLE] UNSUBSCRIBING: Notify Returned Null: 48
[BLE] UNSUBSCRIBING: Notify Returned Null: 45
[BLE] FS Status: Disconnected 0x8


[BLE] Device Status: Connected
[BLE] Using Cached Handles
      - - Subscribed to      value=1, value_handle=45, ccc_handle=46, notify=174433, subscribe=0, node=0,         flags=536904816, handle=0
      - - Subscribed to      value=1, value_handle=48, ccc_handle=49, notify=174469, subscribe=0, node=536904820, flags=536904840, handle=1
      - - Failed Subscribing value=1, value_handle=51, ccc_handle=52, notify=174505, subscribe=0, node=0,         flags=536904864, handle=2
      - - Failed Subscribing value=1, value_handle=54, ccc_handle=55, notify=174537, subscribe=0, node=0,         flags=536904888, handle=3

Thanks,

Levi

  • Hello again,

    I spent some time trying a few different tactics as well as the example code you have provided. No attempt has worked.

    Despite me re-initializing the structure like your example code above, the last notification will always fail to subscribe with an error code (-12 ---> ENOMEM). Why am I receiving this error code when trying to subscribe outside of the discovery process? This error code seems to suggest that I am running out of memory for this operation.

    // Create fresh structure for fast subscription
    struct bt_gatt_subscribe_params fresh = {
        .notify       = notifyStructBackup[handle].notify,
        .value_handle = notifyStructBackup[handle].value_handle,
        .ccc_handle   = notifyStructBackup[handle].ccc_handle,
        .value        = BT_GATT_CCC_NOTIFY,
    };
    notifyStruct[handle] = fresh;
    
    int err = bt_gatt_subscribe(conn, &notifyStruct[handle]);

    I am also using the memset in the notify callback as you suggested, but that did not change anything unfortunately.

    uint8_t genericNotifyCollect(volatile uint8_t* storageArr, volatile bool* storageFlag, void (*codeInject)(uint8_t*),
        struct bt_gatt_subscribe_params *params, const void *data) {
        if ((uint8_t*)data) {
            uint8_t* myData = (uint8_t*)data;
    
            // Optionally Collect the Data
            if (storageArr != NULL && storageFlag != NULL) {
                *storageFlag = true;
    		    oa_memcpy((uint8_t*)storageArr, myData, VND_MAX_LEN);
            }
    
            // Optionally, Perform Code Injection
            // This drastically reduces the code for some files
            if (codeInject != NULL) {
                codeInject(myData);
            }
        } else {
              printk("[BLE] UNSUBSCRIBING: Notify Returned Null: %d\n", params->value_handle);
    
            // Delete Parameters and Unsubscribe
    		memset(params, 0, sizeof(*params));
            return BT_GATT_ITER_STOP;
        }
    
    	// Continue Receiving Notification
        return BT_GATT_ITER_CONTINUE;
    }

    Researching this a bit more, it seems bonding is the proper way to do this. I haven't any good examples of how to do this online. I saw 2 examples on the Nordic Academy for enabling bonding on the peripheral but not the central.

  • To complete my thought, are there any good examples how to perform bonding?

  • Most of the central samples in the NCS SDK have this feature already and show you how to trigger bonding.. For example if you look into central_bas it shows how to discover the battery service and use bt_conn_set_security in your connected() callback to trigger bonding. It also shows how to use bt_gatt_bas_c_subscribe in your security_changed callback handler once the link is encrypted. You can also see central_hids sample for the same reference.

  • Thank you for the direction and insights.

    After going over your description and the example you suggested, I believe I know where I am going wrong with bonding.  I was confused by the bt_conn_set_bondable( ) function which doesn't actually initiate the bond and instead should be using bt_conn_set_security to triggers the bond (like you suggested). I added this call at BT_SECURITY_L2 (since BT_SECURITY_L1 is the default and won't trigger) and this seems to be working. 

    I need to do more architectural changes to accomplish my goal of skipping discovery, but this is the solution I needed.

    Thanks for all your help, Susheel!

Related