Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs
This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

nRF52840 BLE HID Keyboard can not reconnect after reboot/power-cycle

Hi Nordic team,

We have tried using Bluetooth peripheral HID keyboard example from nRF5 SDK and found an interesting problem.

In our setup, nRF52840DK is running the provided HID keyboard example from nrf samples folder (`peripheral_hids_keyboard`). Android device (running Android 11) is connected and paired with the device successfully.
We can send keystrokes (pressing buttons on the DK) without any issues and see them on the phone as well as on the BLE debug traces.

Once the nRF52840 is power cycled, Android phone successfully reconnects to the device, however we are no longer able to send keystrokes and are getting back error `-13` which is `-EACCES`.

After some debugging, we found out that nRF52840 is no sending any BLE packets and is actually failing internally before. Tracing it down from where `key_report_con_send` is called in the `main.c`, failure (return -EACCESS) happens in `hids.c` at

int bt_hids_inp_rep_send(struct bt_hids *hids_obj,
			 struct bt_conn *conn, uint8_t rep_index,
			 uint8_t const *rep, uint8_t len,
			 bt_gatt_complete_func_t cb)
{
	struct bt_hids_inp_rep *hids_inp_rep =
	    &hids_obj->inp_rep_group.reports[rep_index];
	struct bt_gatt_attr *rep_attr =
	    &hids_obj->gp.svc.attrs[hids_inp_rep->att_ind];
	uint8_t *rep_data;

	if (hids_inp_rep->size != len) {
		return -EINVAL;
	}

	if (!conn) {
		return inp_rep_notify_all(hids_obj, hids_inp_rep, rep, len, cb);
	}
    
    // -- !! Below check fails on power cycle with already paired+connected device !!
	if (!bt_gatt_is_subscribed(conn, rep_attr, BT_GATT_CCC_NOTIFY)) {
		return -EACCES;
	}

	struct bt_hids_conn_data *conn_data =
		bt_conn_ctx_get(hids_obj->conn_ctx, conn);

	if (!conn_data) {
		LOG_WRN("The context was not found");
		return -EINVAL;
	}

	rep_data = conn_data->inp_rep_ctx + hids_inp_rep->offset;

	store_input_report(hids_inp_rep, rep_data, rep, len);

	struct bt_gatt_notify_params params = {0};

	params.attr = &hids_obj->gp.svc.attrs[hids_inp_rep->att_ind];
	params.data = rep;
	params.len = hids_inp_rep->size;
	params.func = cb;

	int err = bt_gatt_notify_cb(conn, &params);

	bt_conn_ctx_release(hids_obj->conn_ctx, (void *)conn_data);

	return err;
}

And tracing through the `bt_gatt_is_subscribed` function, we found that very last `return false` is reached which indicates that `/* Check if the connection is subscribed */` portion does not find a subscribed connection. (included function below for completeness).

bool bt_gatt_is_subscribed(struct bt_conn *conn,
			   const struct bt_gatt_attr *attr, uint16_t ccc_value)
{
	const struct _bt_gatt_ccc *ccc;

	__ASSERT(conn, "invalid parameter\n");
	__ASSERT(attr, "invalid parameter\n");

	if (conn->state != BT_CONN_CONNECTED) {
		return false;
	}

	/* Check if attribute is a characteristic declaration */
	if (!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC)) {
		struct bt_gatt_chrc *chrc = attr->user_data;

		if (!(chrc->properties &
			(BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE))) {
			/* Characteristic doesn't support subscription */
			return false;
		}

		attr = bt_gatt_attr_next(attr);
	}

	/* Check if attribute is a characteristic value */
	if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CCC) != 0) {
		attr = bt_gatt_attr_next(attr);
	}

	/* Check if the attribute is the CCC Descriptor */
	if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CCC) != 0) {
		return false;
	}

	ccc = attr->user_data;

	/* Check if the connection is subscribed */
	for (size_t i = 0; i < BT_GATT_CCC_MAX; i++) {
		const struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i];

		if (bt_conn_is_peer_addr_le(conn, cfg->id, &cfg->peer) &&
		    (ccc_value & ccc->cfg[i].value)) {
			return true;
		}
	}

    // -- This return is reached. Above check did not find subscribed connection!
	return false;
}

Right now, a quick and dirty hack to get this working is commenting out `bt_gatt_is_subscribed` check in `hids.c:bt_hids_inp_rep_send()`. With this line commented out, BLE keyboard works trough reboot/power-cycle without any issues and keys are received on the phone side successfully.

Obviously this is a hack, and we would like to know what would be a proper way of  fixing this issue?
What is the recommended approach for "massaging" a re-established connection to a good known state after power-cycle so that EACCES error is not thrown?

Best regards,
Sasa

Parents Reply Children
No Data
Related