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

Parents
  • It looks like you are re-using the structures from the previous connection. Do not reuse bt_gatt_subscribe_params structures from a previous connection. Reinitialize these structures before resubscribing. Use bt_gatt_resubscribe() if you are bonded and the server supports persistent subscriptions, but still ensure the parameters are valid and reinitialized. Always set all required fields before calling bt_gatt_subscribe() or bt_gatt_resubscribe().

  • Hi Susheel,

    I have tested your initial response for resetting the subscribe params and found this yields the same result as before. Additionally, I tried unsubscribing during the disconnected() callback and found that the unsubscribing fails, providing a (-128) socket not connected error. See updated code below with log output. Thoughts?

    Code:

    void subscribeToStoredHandles(struct bt_conn *conn, char* addr) {
        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) {
                    // Create fresh structure for fast subscription
                    struct bt_gatt_subscribe_params fresh = {
                        .notify       = notifyStruct[handle].notify,
                        .value_handle = notifyStruct[handle].value_handle,
                        .ccc_handle   = notifyStruct[handle].ccc_handle,
                        .value        = BT_GATT_CCC_NOTIFY,
                    };
                    notifyStruct[handle] = fresh;
    
                    int err = bt_gatt_subscribe(conn, &notifyStruct[handle]);
                    // int err = bt_gatt_resubscribe(BT_ID_DEFAULT, addr, &notifyStruct[handle]);
                    if (err) {
                        printk("[BLE] FAILURE: Subscribe failed (err %d). value=%d, value_handle=%d, ccc_handle=%d, notify=%d, subscribe=%d, node=%d, flags=%d, handle=%d\n",
                            err,
                            notifyStruct[handle].value,
                            notifyStruct[handle].value_handle,
                            notifyStruct[handle].ccc_handle,
                            notifyStruct[handle].notify,
                            notifyStruct[handle].subscribe,
                            notifyStruct[handle].node,
                            notifyStruct[handle].flags,
                            handle);
                    } else {
                        printk("      - - Subscribed to value=%d, value_handle=%d, ccc_handle=%d, notify=%d, subscribe=%d, node=%d, flags=%d, handle=%d\n",
                            notifyStruct[handle].value,
                            notifyStruct[handle].value_handle,
                            notifyStruct[handle].ccc_handle,
                            notifyStruct[handle].notify,
                            notifyStruct[handle].subscribe,
                            notifyStruct[handle].node,
                            notifyStruct[handle].flags,
                            handle);
    
                    }
                }
            }
        }

    Log:

    00> [BLE] Primary Service Discovered. Val = 0x1855, Handle = 41, End Handle = 55
    00>       - 16-Bit Characteristic Discovered. Val = 0x2b26, Handle = 42
    00>       - 16-Bit Characteristic Discovered. Val = 0x2b29, Handle = 44
    00>       - - Looking for Notify Descriptor...
    00>       - - Descriptor Discovered. Val = 0x2902, Handle = 46
    00>       - - Subscribed to value=1, value_handle=45, ccc_handle=46, notify=176809, subscribe=0, node=0, flags=536904920, handle=0
    00>       - 16-Bit Characteristic Discovered. Val = 0x2b2a, Handle = 47
    00>       - - Looking for Notify Descriptor...
    00>       - - Descriptor Discovered. Val = 0x2902, Handle = 49
    00>       - - Subscribed to value=1, value_handle=48, ccc_handle=49, notify=176845, subscribe=0, node=536904924, flags=536904944, handle=1
    00>       - 16-Bit Characteristic Discovered. Val = 0x2b2c, Handle = 50
    00>       - - Looking for Notify Descriptor...
    00>       - - Descriptor Discovered. Val = 0x2902, Handle = 52
    00>       - - Subscribed to value=1, value_handle=51, ccc_handle=52, notify=176881, subscribe=0, node=536904948, flags=536904968, handle=2
    00>       - 16-Bit Characteristic Discovered. Val = 0x2b2d, Handle = 53
    00>       - - Looking for Notify Descriptor...
    00>       - - Descriptor Discovered. Val = 0x2902, Handle = 55
    00>       - - Subscribed to value=1, value_handle=54, ccc_handle=55, notify=176913, subscribe=0, node=536904972, flags=536904992, handle=3
    00> [BLE] Looking for Next Service...
    00> [BLE] Bluetooth Discovery: Complete
    
    
    00> [BLE] UNSUBSCRIBING: Notify Returned Null: 54
    00> [BLE] UNSUBSCRIBING: Notify Returned Null: 51
    00> [BLE] UNSUBSCRIBING: Notify Returned Null: 48
    00> [BLE] UNSUBSCRIBING: Notify Returned Null: 45
    00> [BLE] FAILURE: Unsubscribe failed (err -128). value=0, value_handle=45, ccc_handle=46, notify=176809, subscribe=0, node=0, flags=536904920, handle=0
    00> [BLE] FAILURE: Unsubscribe failed (err -128). value=0, value_handle=48, ccc_handle=49, notify=176845, subscribe=0, node=0, flags=536904944, handle=1
    00> [BLE] FAILURE: Unsubscribe failed (err -128). value=0, value_handle=51, ccc_handle=52, notify=176881, subscribe=0, node=0, flags=536904968, handle=2
    00> [BLE] FAILURE: Unsubscribe failed (err -128). value=0, value_handle=54, ccc_handle=55, notify=176913, subscribe=0, node=0, flags=536904992, handle=3
    00> [BLE] Device Status: Disconnected 0x8
    
    
    00> [BLE] Device Status: Connected
    00> [BLE] Using Cached Handles
    00>       - - Subscribed to value=1, value_handle=45, ccc_handle=46, notify=176809, subscribe=0, node=0, flags=536904920, handle=0
    00>       - - Subscribed to value=1, value_handle=48, ccc_handle=49, notify=176845, subscribe=0, node=536904924, flags=536904944, handle=1
    00>       - - Subscribed to value=1, value_handle=51, ccc_handle=52, notify=176881, subscribe=0, node=536904948, flags=536904968, handle=2
    00> [BLE] FAILURE: Subscribe failed (err -12). value=1, value_handle=54, ccc_handle=55, notify=176913, subscribe=0, node=0, flags=536904992, handle=3

    I am not bonded with my device, but I also tried using bt_gatt_resubscribe(). Predictably, this does not work either. I did some quick searching for the bonding API but did not find anything. What function must be called to initiate bonding with my peripheral? 

  • You’re seeing the “-128” errors because once the ATT channel is torn down there’s no socket left to drive the write-of-“0” to the CCCD. In other words, calling bt_gatt_unsubscribe() inside your disconnected() handler is too late. the GATT bearer is already gone, so the unsubscribe write fails with ENOTCONN. Instead, let Zephyr tell you when it’s done (it will invoke your notify callback with data==NULL to signal that unsubscribe has completed) and then just clear out your params struct. Something like this:

    static uint8_t genericNotifyCollect(struct bt_gatt_subscribe_params *params,
                                        const void *data, uint16_t length)
    {
        if (!data) {
            // Zephyr has completed the CCCD-write of “0” for us.
            memset(params, 0, sizeof(*params));
            return BT_GATT_ITER_STOP;
        }
        
        return BT_GATT_ITER_CONTINUE;
    }
    

    When subscriptions are removed notify callback is called with the data set to NULL according to the documentation here

    You need to then build a fresh bt_gatt_subscribe_params (i.e. zero-initialized) on every new connect , fill in only:

    p.value_handle = stored_handle;
    p.ccc_handle   = stored_ccc;
    p.value        = BT_GATT_CCC_NOTIFY;
    p.notify       = your_notify_cb;
    
    and then call 
    
    bt_gatt_subscribe(conn, &p);

    To kick of bonding (SMP pairing) from your master side you need to register your authentication callbacks using bt_conn_auth_cb_register and also set the security level once you are connected using bt_conn_set_security to say BT_SECUTIRY_L2 for example.

Reply
  • You’re seeing the “-128” errors because once the ATT channel is torn down there’s no socket left to drive the write-of-“0” to the CCCD. In other words, calling bt_gatt_unsubscribe() inside your disconnected() handler is too late. the GATT bearer is already gone, so the unsubscribe write fails with ENOTCONN. Instead, let Zephyr tell you when it’s done (it will invoke your notify callback with data==NULL to signal that unsubscribe has completed) and then just clear out your params struct. Something like this:

    static uint8_t genericNotifyCollect(struct bt_gatt_subscribe_params *params,
                                        const void *data, uint16_t length)
    {
        if (!data) {
            // Zephyr has completed the CCCD-write of “0” for us.
            memset(params, 0, sizeof(*params));
            return BT_GATT_ITER_STOP;
        }
        
        return BT_GATT_ITER_CONTINUE;
    }
    

    When subscriptions are removed notify callback is called with the data set to NULL according to the documentation here

    You need to then build a fresh bt_gatt_subscribe_params (i.e. zero-initialized) on every new connect , fill in only:

    p.value_handle = stored_handle;
    p.ccc_handle   = stored_ccc;
    p.value        = BT_GATT_CCC_NOTIFY;
    p.notify       = your_notify_cb;
    
    and then call 
    
    bt_gatt_subscribe(conn, &p);

    To kick of bonding (SMP pairing) from your master side you need to register your authentication callbacks using bt_conn_auth_cb_register and also set the security level once you are connected using bt_conn_set_security to say BT_SECUTIRY_L2 for example.

Children
No Data
Related