Support multiple subscription for single service with multiple characteristics

Hi,

I am working on project where the peripheral has 2 services, and each has multiple characteristics.

The central needs to subscribe to all the characteristics and when doing so I am seeing error: -120 (Socket already connected)

Below is the device log from the central, as you can see I am reading 2 characteristics COMMAND and INDICATION.

When trying to subscribe to the characteristics, I can subscribe to the first characteristics whereas when subscribing to the second characteristics I am getting the error.

Found CMD CHR: 14
Found IND CHR: 17
bt_gatt_subscribe: 0, ccc_handle: 15
[SUBSCRIBED]
bt_gatt_subscribe: -120, ccc_handle: 18

Let me also add our peripheral service server list here for reference

BT_GATT_SERVICE_DEFINE(dev_svc,
    BT_GATT_PRIMARY_SERVICE(BT_UUID_DEV),
    BT_GATT_CHARACTERISTIC(BT_UUID_CMD,
                           (BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE),
                           BT_GATT_PERM_WRITE,
                           NULL, callback_write_cmd,
                           NULL),
	BT_GATT_CCC(notif_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
    BT_GATT_CHARACTERISTIC(BT_UUID_IND,
                           (BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE),
                           BT_GATT_PERM_WRITE,
                           NULL, callback_write_ind,
                           NULL),
	BT_GATT_CCC(notif_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
);

Please let me know if I am doing anything wrong or any config we should add.

I am working on NRF Connect SDK v2.7.0

Board: NRF52840

Parents
  • Hi,

    There is no fundamental issue with having multiple characteristics with nofify or indicate in the same service. I have some qustions in order to understand more:

    1. How are you testing?
    2. Are you abel to subscribe to any of the two characteristics, just not both at the same time?
    3. Have you tested with a different central implementation (for instance nRF Connect for Mobile) to narrow down the issue to understand if the issue is on the central or peripheral side?

    That said, I assume we need to look more closely at your central implementation. bt_gatt_subscribe() will return -EALREADY (-120) if allready subscribed, so I suspect you are referencing the same CCCD for some reason.

  • Hi Einar,

    Thank you for the response, let me put my comments here

    How are you testing?

    --> I have here a NRF52840DK and a custom PCB with NRF52840 in it.

    So, I have configured DK as central device for logging and custom PCB as peripheral.

    When I try to subscribe using my DK, I am seeing the above error.

    Are you abel to subscribe to any of the two characteristics, just not both at the same time?

    --> Yes, in the above log you can see, the CMD characteristics is successfully subscribed and from the characteristics the peripheral can send the indication and central receive correctly.

    If I subscribe to IND characteristics without subscribing to the CMD characteristics, it works.

    What I see is I am not able to subscribe to both at a time.

    Have you tested with a different central implementation (for instance nRF Connect for Mobile) to narrow down the issue to understand if the issue is on the central or peripheral side?

    --> Not yet, I just got to know about the mobile app (NRF Connect), will give a try with that and update to you here.

    I suspect you are referencing the same CCCD for some reason.

    --> Yes, I am also thinking same. 

    Let me share you my service discovery code snippet, so you can get to know any mistake I am doing.

    static uint8_t discover_cb (struct bt_conn *conn, const struct bt_gatt_attr *attr,
    			                struct bt_gatt_discover_params *params)
    {
    	int err;
    	static app_task_msg_t msg;
    	static struct bt_uuid_128 uuid = BT_UUID_INIT_128(0);
    
    	if (!attr) {
    		// The control should not come here. Just in case, the attribute handles are not handled
            // properly it comes, disconnect the peer and wait for reconnection
            //
    		(void)memset(params, 0, sizeof(*params));
    		bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
    
    		return BT_GATT_ITER_STOP;
    	}
    
    	if (!bt_uuid_cmp(g_discover_params.uuid, BT_UUID_NOTIFY_DEV))
        {
    		// Found the NOTIF_DEV_SERVICE. Look for the CMD characteristic handle
            //
    		memcpy(&uuid, BT_UUID_CMD, sizeof(uuid));
    		g_discover_params.uuid = &uuid.uuid;
    		g_discover_params.start_handle = attr->handle + 1;
    		g_discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
    		bt_gatt_discover(conn, &g_discover_params);
    	}
        else if (!bt_uuid_cmp(g_discover_params.uuid, BT_UUID_CMD)) {
    		// Copy command characteristics handle for future reference
            //
    		current_dev_info.cmd_chr_handle = (attr->handle + 1);
    
            // As we have found command characteristics, look for indication characteristics
            //
            printk("Found CMD CHR: %d\n", current_dev_info.cmd_chr_handle);
    		memcpy(&uuid, BT_UUID_IND, sizeof(uuid));
    		g_discover_params.uuid = &uuid.uuid;
    		g_discover_params.start_handle = attr->handle;
    		g_discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
    		bt_gatt_discover(conn, &g_discover_params);
    	}
        else if (!bt_uuid_cmp(g_discover_params.uuid, BT_UUID_IND)) {
    		// Copy indication characteristics handle for future reference
            //
    		current_dev_info.ind_chr_handle = (attr->handle + 1);
    
    		// Found the NOTIF_DEV_CMD_CHR handle. Find the DESCRIPTOR handle
    		//
            printk("Found IND CHR: %d\n", current_dev_info.ind_chr_handle);
    		memcpy(&uuid, BT_UUID_GATT_CCC, sizeof(struct bt_uuid_16));
    		g_discover_params.uuid = &uuid.uuid;
    		g_discover_params.start_handle = attr->handle + 2;
    		g_discover_params.type = BT_GATT_DISCOVER_DESCRIPTOR;
    		g_subscribe_params.value_handle = bt_gatt_attr_value_handle(attr);
    		bt_gatt_discover(conn, &g_discover_params);
    	}
        else {
    		// Subscribe to the INDICATIONS, this will enable indications on the peripheral
    		//
    		g_subscribe_params.notify = indication_cb;
    		g_subscribe_params.value = BT_GATT_CCC_INDICATE;
    		g_subscribe_params.ccc_handle = current_dev_info.cmd_chr_handle + 1;
    
            // Subscribe for CMD 
            //
    		err = bt_gatt_subscribe(conn, &g_subscribe_params);
            printk("bt_gatt_subscribe: %d, ccc_handle: %d\n", err, g_subscribe_params.ccc_handle);
    		if (err == 0) {
                printk("[SUBSCRIBED]\n");
    		}
    
            // Subscribe for IND
            //
            g_subscribe_params.ccc_handle = current_dev_info.ind_chr_handle + 1;
    		err = bt_gatt_subscribe(conn, &g_subscribe_params);
            printk("bt_gatt_subscribe: %d, ccc_handle: %d\n", err, g_subscribe_params.ccc_handle);
    		if (err == 0) {
                printk("[SUBSCRIBED]\n");
    		}
    	}
    
    	return BT_GATT_ITER_STOP;
    }
    

    Please review it once and let me know.

    Thank you in advance

  • Hi,

    I see one problem with your implementation. You are re-using the same variable for both calls to bt_gatt_subscribe(), and that will cause problems. Moreover these vairables need to stay valid for as long as subscribed. One way to improve this is to make two separate variables that are static.

    For reference, this is the relevant section from the bt_gatt_subscribe() API documentation:

    Notifications are asynchronous therefore the params must remain valid while subscribed and cannot be reused for additional subscriptions whilst active.

Reply Children
Related