This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Only two correct bytes when for read and notify as BLE central

I am trying to read (the report map) and receive notifications (the report) from an HID device as a BLE central. The bonding process has been completedsuccessfully to my knowledge.

When using sd_ble_gattc_read(), only the first two bytes of the response in the BLE_GATTC_EVT_READ_RSP event is correct. I can call sd_ble_gattc_read() multiple times with the offset increasing and only the first two bytes for each call is correct. Since 22 bytes are read with each call, only bytes 1 and 2, 23 and 24, 45 and 46, and so on are correct.

Similarly, only the first two bytes received in BLE_GATTC_EVT_HVX is correct.

To begin with, I registered the HID UUID with the discovery module.

ble_uuid_t hids_uuid = {
    .uuid = BLE_UUID_HUMAN_INTERFACE_DEVICE_SERVICE,
    .type = BLE_UUID_TYPE_BLE,
};

APP_ERROR_CHECK(ble_db_discovery_init(db_disc_handler));
APP_ERROR_CHECK(ble_db_discovery_evt_register(&hids_uuid));

I start service discovery inside ble_evt_handler.

case BLE_GAP_EVT_CONNECTED: {
        // Initiate bonding.
        err_code = pm_conn_secure(p_ble_evt->evt.gap_evt.conn_handle, false);
        if (err_code != NRF_ERROR_BUSY) {
                APP_ERROR_CHECK(err_code);
        }

        err_code = ble_db_discovery_start(&m_db_disc, p_ble_evt->evt.gap_evt.conn_handle);
        APP_ERROR_CHECK(err_code);
} break;

Inside the service discover handler, I begin reading the report map value.

uint16_t report_map_handle, report_handle;

static void db_disc_handler(ble_db_discovery_evt_t * p_evt) {
    if (p_evt->evt_type != BLE_DB_DISCOVERY_COMPLETE) {
                return;
        }
        if (p_evt->params.discovered_db.srv_uuid.uuid != hids_uuid.uuid) {
                return;
        }
        if (p_evt->params.discovered_db.srv_uuid.type != hids_uuid.type) {
                return;
        }

    for (int i = 0; i < p_evt->params.discovered_db.char_count; i++) {
                ble_gatt_db_char_t chr = p_evt->params.discovered_db.charateristics[i];
                uint16_t uuid = chr.characteristic.uuid.uuid;
        switch (uuid) {
                case BLE_UUID_REPORT_MAP_CHAR:
                        report_map_handle = chr.characteristic.handle_value;
                        break;
                case BLE_UUID_REPORT_CHAR:
                        report_handle = chr.cccd_handle;
                        break;
                default:
                        break;
                }
    }

        APP_ERROR_CHECK(sd_ble_gattc_read(p_evt->conn_handle, report_map_handle, 0));
}

Also inside ble_evt_handler, I handle the read values. Once all of the report map has been read, I enable notifications on the report characteristic using the CCCD.

case BLE_GATTC_EVT_READ_RSP: {
        ble_gattc_evt_read_rsp_t resp = p_ble_evt->evt.gattc_evt.params.read_rsp;
        memcpy(&hid_data[hid_len], resp.data, resp.len);
        hid_len += resp.len;
        NRF_LOG_HEXDUMP_INFO(resp.data, resp.len);
        NRF_LOG_INFO("total read %d", hid_len);
        uint16_t ch = p_ble_evt->evt.gattc_evt.conn_handle;
        if (resp.len > 0) {
                APP_ERROR_CHECK(sd_ble_gattc_read(ch, report_map_handle, hid_len));
        } else {
                start_notify(ch);
        }
} break;

For start_notify(), I used code from cccd_configure() in components/ble/ble_services/ble_nus_c/ble_nus_c.c

static void start_notify(uint16_t ch) {
    uint8_t buf[BLE_CCCD_VALUE_LEN];
    buf[0] = BLE_GATT_HVX_NOTIFICATION;
    buf[1] = 0;

    ble_gattc_write_params_t const write_params = {
        .write_op = BLE_GATT_OP_WRITE_REQ,
        .flags    = BLE_GATT_EXEC_WRITE_FLAG_PREPARED_WRITE,
        .handle   = report_handle,
        .offset   = 0,
        .len      = sizeof(buf),
        .p_value  = buf
    };

    APP_ERROR_CHECK(sd_ble_gattc_write(ch, &write_params));
}

Inside ble_evt_handler I simply dump all of the notification data.

case BLE_GATTC_EVT_HVX: {
        ble_gattc_evt_hvx_t hvx = p_ble_evt->evt.gattc_evt.params.hvx;
        NRF_LOG_HEXDUMP_INFO(hvx.data, hvx.len);
} break;

For the report map, I get the following log output. Note that bytes 3 through 22 of each read is identical.

<info> app:  05 01 00 00 00 00 08 D9|........
<info> app:  03 00 1C FD 03 20 08 D9|..... ..
<info> app:  03 00 F4 01 00 00      |......
<info> app: total read 22
<info> app:  81 02 00 00 00 00 08 D9|........
<info> app:  03 00 1C FD 03 20 08 D9|..... ..
<info> app:  03 00 F4 01 00 00      |......
<info> app: total read 44
<info> app:  01 A1 00 00 00 00 08 D9|........
<info> app:  03 00 1C FD 03 20 08 D9|..... ..
<info> app:  03 00 F4 01 00 00      |......
<info> app: total read 66
<info> app:  05 01 00 00 00 00 08 D9|........
<info> app:  03 00 1C FD 03 20 08 D9|..... ..
<info> app:  03 00 F4 01 00 00      |......
<info> app: total read 88
<info> app:  15 00 00 00 00 00 08 D9|........
<info> app:  03 00 1C FD 03 20 08 D9|..... ..
<info> app:  03 00 F4 01 00 00      |......
<info> app: total read 110
<info> app:  09 38 00 00 00 00 08 D9|.8......
<info> app:  03 00                  |..
<info> app: total read 120

I'm inclined to think there is something wrong on the central side since the HID device works correctly. Suggestions on how to debug this would be appreciated.

  • Hello,

    I am a bit confused from your explanation and your snippets. You say that you want to use notifications, and you refer to the ble_nus_c service (which uses notifications). Notifications is something other than read. Did you test the ble_app_uart and ble_app_uart_c examples together? Does that work? Notifications will trigger an event at the central, which you can use to receive the data, and it is in general a bit easier to work with than the sd_ble_gattc_read().

    I also get the impression that you try to use queued write, or long write. Is that correct? And is that intentional?

    If you need to use write and sd_ble_gattc_read(), is there a reason for this, and how do you write this in your peripheral?

    Sorry that I don't have an exact answer, but I need to nest up in a few things first, so that we are on the same page. 

  • It turns out that the problem is saving/copying p_ble_evt->evt.gattc_evt.params.read_rsp as a variable. The solution was to store a pointer (or just accessing the data directly via p_ble_evt->evt.gattc_evt.params.read_rsp.data)

            ble_gattc_evt_read_rsp_t *resp = &p_ble_evt->evt.gattc_evt.params.read_rsp;

Related