Peripheral is connected to two Centrals, each with two Gatt Services, occurred mtu exchange fail.

Hello,

I am using three nrf52840 DKs to test sending gatt notify data. They are one peripheral and two central.

When they each have only one Gatt Customer Service, peripheral can connect to both centrals at the same time and successfully send gatt notify data to them.

But when they both have two Gatt Customer Services, when peripheral connects to the second central, the first central will crash, and the second central will have mtu exchange fail (-14).

Is it possible for one peripheral to connect to two centrals at the same time, one central to register two Gatt Services, and then send gatt_notify at the same time?

  • Hi,

    -12 is -ENOMEM and that can be returned from GATT APIs in several situations. But generally you can solve it by waiting a bit and trying again ("ATT request queue is full and blocking would cause deadlock.").

    That said, I wonder if you can share more of your code so that I understand what you are doing? In these code snippets I do not see that you have any handling of the two connections (or how you handle it) . That will requiere quite a bit as the examples are based on a single connection. (Multi peripheral role is not common so there is unfortunately no example of it that I can point to).

  • As shown in the code snippet above, after the peripheral side connected to the central, I performed bt_gatt_exchange_mtu and bt_gatt_discover and then started transmitting data directly without doing any other processing on the connection. Both connections are handled this way.

    Here is a more detailed snippet of my code. Other than that, I don't do anything else in the peripheral.

    BT_GATT_SERVICE_DEFINE(test_svc,
    	BT_GATT_PRIMARY_SERVICE(&test_uuid),
    	BT_GATT_CHARACTERISTIC(&test_chrc_uuid.uuid, 
    		BT_GATT_CHRC_WRITE_WITHOUT_RESP |BT_GATT_CHRC_NOTIFY,
    		BT_GATT_PERM_WRITE,
    		NULL, write_test, NULL),
    	BT_GATT_CCC(vnd_ccc_cfg_changed, BT_GATT_PERM_WRITE), //2
    );
    BT_GATT_SERVICE_DEFINE(test_svc_2,
    	BT_GATT_PRIMARY_SERVICE(&test_uuid_2),
    	BT_GATT_CHARACTERISTIC(&test_chrc_uuid_2.uuid, 
    		BT_GATT_CHRC_WRITE_WITHOUT_RESP |BT_GATT_CHRC_NOTIFY,
    		BT_GATT_PERM_WRITE,
    		NULL, write_test, NULL),
    	BT_GATT_CCC(vnd_ccc_cfg_changed, BT_GATT_PERM_WRITE), //2
    );
    
    static void vnd_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
    {
    	ARG_UNUSED(attr);
    
    	bool notif_enabled = (value == BT_GATT_CCC_NOTIFY);
    
    	LOG_INF("notifications %s", notif_enabled ? "enabled" : "disabled");
    }
    
    static void exchange_func(struct bt_conn *conn, uint8_t err, struct bt_gatt_exchange_params *params)
    {
    	if (!err) {
    		LOG_INF("MTU exchange done\n");
    	} else {
    		LOG_INF("MTU exchange failed (err %" PRIu8 ")\n", err);
    	}
    }
    
    static uint8_t notify_func(struct bt_conn *conn,
    		struct bt_gatt_subscribe_params *params,
    		const void *data, uint16_t length)
    {
    	uint8_t value[200];
    	memcpy(value,data,length);
    	if (!data) {
    		LOG_INF("[UNSUBSCRIBED]\n");
    		connected_flag = 0;
    		return BT_GATT_ITER_STOP;
    	}
    	LOG_INF("[NOTIFICATION] data %x %x length %u\n", value[0],value[1], length);
    	connected_flag = 1;
    	return BT_GATT_ITER_CONTINUE;
    }
    
    static uint8_t discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr,
    			     struct bt_gatt_discover_params *params)
    {
        int err;
        if (!attr) {
    		LOG_INF("Discover complete");
    		(void)memset(params, 0, sizeof(*params));
    		return BT_GATT_ITER_STOP;
    	}
        LOG_INF("[ATTRIBUTE] handle %u", attr->handle);
        if (!bt_uuid_cmp(discover_params.uuid, &test_uuid.uuid)) {
    		memcpy(&uuid, &test_chrc_uuid.uuid, sizeof(uuid));
    		discover_params.uuid = &uuid.uuid;
    		discover_params.start_handle = attr->handle + 1;
    		discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
    
    		err = bt_gatt_discover(conn, &discover_params);
    		LOG_INF("Discover Service");
    		if (err) {
    			LOG_INF("Discover failed (err %d)\n", err);
    		}
    	} else if (!bt_uuid_cmp(discover_params.uuid,&test_chrc_uuid.uuid)) {
    		memcpy(&uuid, BT_UUID_GATT_CCC, sizeof(uuid));
    		discover_params.uuid = &uuid.uuid;
    		discover_params.start_handle = attr->handle + 2;
    		discover_params.type = BT_GATT_DISCOVER_DESCRIPTOR;
    		subscribe_params.value_handle = bt_gatt_attr_value_handle(attr);
    		err = bt_gatt_discover(conn, &discover_params);
    		LOG_INF("Discover Character");
    		if (err) {
    			LOG_INF("Discover failed (err %d)\n", err);
    		}
    	} else if (bt_uuid_cmp(discover_params.uuid, &test_chrc_uuid.uuid)){
    		subscribe_params.notify = notify_func;
    		subscribe_params.value = BT_GATT_CCC_NOTIFY;
    		subscribe_params.ccc_handle = attr->handle;
    		err = bt_gatt_subscribe(conn, &subscribe_params);
    		if (err && err != -EALREADY) {
    			LOG_INF("Subscribe failed (err %d)\n", err);
    		} else {
    			LOG_INF("[SUBSCRIBED]\n");
    		}
    	}
    	return BT_GATT_ITER_STOP;
    }
    
    static uint8_t discover_func_2(struct bt_conn *conn, const struct bt_gatt_attr *attr,
    			     struct bt_gatt_discover_params *params)
    {
        int err;
        if (!attr) {
    		LOG_INF("Discover complete");
    		(void)memset(params, 0, sizeof(*params));
    		return BT_GATT_ITER_STOP;
    	}
        LOG_INF("[ATTRIBUTE] handle %u", attr->handle);
        if (!bt_uuid_cmp(discover_params_2.uuid, &test_uuid_2.uuid)) {
    		memcpy(&uuid_2, &test_chrc_uuid_2.uuid, sizeof(uuid_2));
    		discover_params_2.uuid = &uuid_2.uuid;
    		discover_params_2.start_handle = attr->handle + 1;
    		discover_params_2.type = BT_GATT_DISCOVER_CHARACTERISTIC;
    
    		err = bt_gatt_discover(conn, &discover_params_2);
    		LOG_INF("Discover Service");
    		if (err) {
    			LOG_INF("Discover failed (err %d)\n", err);
    		}
    	} else if (!bt_uuid_cmp(discover_params_2.uuid,&test_chrc_uuid_2.uuid)) {
    		memcpy(&uuid_2, BT_UUID_GATT_CCC, sizeof(uuid_2));
    		discover_params_2.uuid = &uuid_2.uuid;
    		discover_params_2.start_handle = attr->handle + 2;
    		discover_params_2.type = BT_GATT_DISCOVER_DESCRIPTOR;
    		subscribe_params_2.value_handle = bt_gatt_attr_value_handle(attr);
    
    		err = bt_gatt_discover(conn, &discover_params_2);
    		LOG_INF("Discover Character");
    		if (err) {
    			LOG_INF("Discover failed (err %d)\n", err);
    		}
    	} else if (bt_uuid_cmp(discover_params_2.uuid, &test_chrc_uuid_2.uuid)){
    		subscribe_params_2.notify = notify_func;
    		subscribe_params_2.value = BT_GATT_CCC_NOTIFY;
    		subscribe_params_2.ccc_handle = attr->handle;
    		err = bt_gatt_subscribe(conn, &subscribe_params_2);
    		if (err && err != -EALREADY) {
    			LOG_INF("Subscribe failed (err %d)\n", err);
    		} else {
    			LOG_INF("[SUBSCRIBED]\n");
    			connected_flag = 1;
    		}
    	}
    	return BT_GATT_ITER_STOP;
    }
    
    void connected(struct bt_conn *conn, uint8_t err)
    {
    	connected_count++;
    	if(connected_count == 1){
    		app_conn1 = conn;
    	}else if(connected_count == 2){
    		app_conn2 = conn;
    	}
    	char addr[BT_ADDR_LE_STR_LEN];
    	static struct bt_gatt_exchange_params exchange_params;
    
    	bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
    
    	if (err != 0) {
    		LOG_INF("Failed to connect to %s (%u)", addr, err);
    		return;
    	}
    	connected_flag = 1;
    	exchange_params.func = exchange_func;
    	err = bt_gatt_exchange_mtu(conn, &exchange_params);
    	if (err) {
    		LOG_INF("MTU exchange failed (err %d)", err);
    	}
    	memcpy(&uuid, &test_uuid.uuid, sizeof(uuid));
    	discover_params.uuid = &uuid.uuid;
        discover_params.func = discover_func;
    	discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
    	discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
    	discover_params.type = BT_GATT_DISCOVER_PRIMARY;
        err = bt_gatt_discover(conn,&discover_params);
    	if (err) {
    		LOG_ERR("Discover failed(err %d)\n", err);
    		return;
    	}
    #if CONFIG_TWO_GATT_SERVICE
    	memcpy(&uuid_2, &test_uuid_2.uuid, sizeof(uuid_2));
    	discover_params_2.uuid = &uuid_2.uuid;
        discover_params_2.func = discover_func_2;
    	discover_params_2.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
    	discover_params_2.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
    	discover_params_2.type = BT_GATT_DISCOVER_PRIMARY;
        err = bt_gatt_discover(conn,&discover_params_2);
    	if (err) {
    		LOG_ERR("Discover failed(err %d)\n", err);
    		return;
    	}
    #endif
    }
    void disconnected(struct bt_conn *conn, uint8_t reason)
    {
    	LOG_INF("disconnected reason:%d",reason);
    	le_app_advertising();
    	connected_flag = 0;
    	connected_count--;
    }
    static struct bt_conn_cb conn_callbacks = {
    	.connected = connected,
    	.disconnected = disconnected,
    };
    
    /* Start advertising for app */
    void le_app_advertising(void)
    {
        int ret;
        ret = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad),
    			      sd, ARRAY_SIZE(sd));
    	if (ret) {
    		LOG_ERR("Advertising failed to start (err %d)", ret);
    	}
    }
    
    void app_initial(void){
        bt_conn_cb_register(&conn_callbacks);
        le_app_advertising();
    }
    
    static void send_data(void){
    	int ret;
    	if(connected_count == 1){
    		ret = bt_gatt_notify(app_conn1, &test_svc.attrs[1], &info, 240);
    		ret = bt_gatt_notify(app_conn1, &test_svc_2.attrs[1], &info, 240);
    	}else if(connected_count == 2){
    		ret = bt_gatt_notify(app_conn1, &test_svc.attrs[1], &info, 240);
    		ret = bt_gatt_notify(app_conn1, &test_svc_2.attrs[1], &info, 240);
    		ret = bt_gatt_notify(app_conn2, &test_svc.attrs[1], &info, 240);
    		ret = bt_gatt_notify(app_conn2, &test_svc_2.attrs[1], &info, 240);
    	}
    }
    
    static void my_check_handler(struct k_work *work);
    K_WORK_DEFINE(my_check, my_check_handler);
    struct k_timer my_timer;
    static void my_timer_handler(struct k_timer *dummy)
    {
    	k_work_submit(&my_check);
    }
    K_TIMER_DEFINE(my_timer,my_timer_handler,NULL);
    
    static void my_check_handler(struct k_work *work){
    	if(connected_flag){
    		send_data();
    	}
    }
    
    int main(void)
    {
    	const struct device *hs_dev;
    	int ret;
    
    	ret = bt_enable(NULL);
    	if (ret) {
    	    LOG_ERR("bt enable error %d",ret);
    	}
        if (IS_ENABLED(CONFIG_SETTINGS)) {
    		settings_load();
    	}
        app_initial();
    	connected_flag = 0;
    
    	k_timer_start(&my_timer, K_MSEC(2000), K_MSEC(7.5)); //wang20240903 temp pass(den't send ble data)
    
    	return 0;
    }
    

    In addition to bt_gatt_exchange_mtu and bt_gatt_discover, does peripheral need to do other processing on the connections when connecting?

  • Hi,

    Which sample in which SDK version have you based your peiheral application on? Can you share a full diff?

    The errors you get essentially tell us that there are no buffers available, and at the same time you get a timeout on the central side. Can you try to incrase some buffer and stack sizses and see if htat helps? For instance, try something like this:

    CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
    CONFIG_BT_CONN_TX_MAX=10

    Does that improve things?

  • Thank you for your assistance.

    I tried increasing buffer and stack size, it helped.

    Now I can successfully connect to two centrals with two Gatt Services.

Related