BT_ATT_ERR_INVALID_ATTRIBUTE_LEN when writing to a BLE characteristic

Hello,

I'm attempting to write to a ble characteristic but when i call bt_gatt_write, I get BT_ATT_ERR_INVALID_ATTRIBUTE_LEN, I have tried to send the same command using nRF Connect application with ByteArray format and it works.

Here's my source code :

#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/sys/printk.h>

static struct bt_conn *default_conn;
static  bt_addr_le_t target_addr = {
	.type = BT_ADDR_LE_PUBLIC,
	.a = {
        .val = {0x0e, 0xa0, 0x80, 0x7d, 0xcf, 0x85}
	}
};
static struct bt_gatt_read_params read_params;
static struct bt_gatt_subscribe_params subscribe_params;

// K_THREAD_DEFINE(ble_thread, 1024, read_characteristic, NULL, NULL, NULL, 5, 0, 0);
K_SEM_DEFINE(ble_sem, 0, 1);

static struct bt_uuid_128 uuid = BT_UUID_INIT_128(
    0xE6, 0xA9, 0xF4, 0xB4, 0x3E, 0x00, 0x0F, 0x9A, 
    0xDC, 0x4E, 0x42, 0x24, 0xD4, 0x74, 0x3D, 0x36
);

// static struct bt_uuid_128 uuid = BT_UUID_INIT_128(
//     0x36, 0x3D, 0x74, 0xD0, 0x24, 0x42, 0x4E, 0xDC, 
//     0x9A, 0x0F, 0x00, 0x3E, 0xB4, 0xF4, 0xA9, 0xE6
// );
static void read_func(struct bt_conn *conn,
                      struct bt_gatt_subscribe_params *params,
                      const void *data, uint16_t length)
{

    if (data) {
        printk("Characteristic value: ");
        for (int i = 0; i < length; i++) {
            printk("%02x ", ((uint8_t *)data)[i]);
        }
        printk("\n");
    } else {
        printk("Read complete\n");
    }
}

static void ccc_write_cb(struct bt_conn *conn, uint8_t err,
    struct bt_gatt_write_params *params)
{
if (err) {
printk("CCCD write failed (err %u)\n", err);
} else {
printk("CCCD write successful\n");
}
}

static struct bt_gatt_write_params write_params;
static uint8_t ccc_value[] = {0x04, 0x62, 0x15, 0x00, 0x00} ; /* Write cmd to send current value*/
// static uint64_t ccc_value = 0x04062150000; /* Write cmd to send current value*/
// static uint8_t ccc_value[] = {'04', '62', '15', '00', '00'} ;
// static uint8_t ccc_value[] = {'0x04', '0x62', '0x15', '0x00', '0x00'} ;
// static uint8_t ccc_value[] = "0x04062150000" ;
// Write cmd to send current value in byte array 
// 0x04, 0x62, 0x15, 0x00, 0x00


static uint8_t discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr,
                             struct bt_gatt_discover_params *params)
{
    if (!attr) {
        printk("Discover complete\n");
        memset(params, 0, sizeof(*params));
        return BT_GATT_ITER_STOP;
    }

    struct bt_gatt_chrc *chrc = (struct bt_gatt_chrc *)attr->user_data;

    if (!bt_uuid_cmp(chrc->uuid, &uuid.uuid)) {
        printk("Found characteristic\n");
        printk("Value handle: %u\n", chrc->value_handle);
        // // Read the characteristic value
        // read_params.func = read_func;
        // read_params.handle_count = 1;
        // read_params.single.handle = chrc->value_handle + 1;
        // read_params.single.offset = 0;
        // Subscribe to notifications
        subscribe_params.value = BT_GATT_CCC_NOTIFY;
        subscribe_params.notify = read_func;
        subscribe_params.ccc_handle = chrc->value_handle; // CCCD handle is usually characteristic handle + 1
        int err = bt_gatt_subscribe(conn, &subscribe_params);
        if (err) {
            printk("Subscribe failed (err %d)\n", err);
        }

        printk("Subscribed\n");
    

        // Write to CCCD to send value of current 
        write_params.data = ccc_value;
        write_params.length = sizeof(ccc_value);
        write_params.handle = chrc->value_handle + 1;// CCCD handle is usually characteristic handle + 
        write_params.func = ccc_write_cb;


        err = bt_gatt_write(conn, &write_params);

        printk("Writing CCCD\n");

        if (err) {
            printk("Failed to write CCCD (err %d)\n", err);
        }

        printk("CCCD write started\n");

        k_sem_give(&ble_sem);
        return BT_GATT_ITER_STOP;
    }else{
        // PRINT UUID
        char uuid_str[37];
        bt_uuid_to_str(chrc->uuid, uuid_str, sizeof(uuid_str));
        printk("UUID Found: %s\n", uuid_str);
    }

    return BT_GATT_ITER_CONTINUE;
}

static void connected(struct bt_conn *conn, uint8_t err)
{
    if (err) {
        printk("Connection failed (err %u)\n", err);
        return;
    }

    default_conn = bt_conn_ref(conn);
    printk("Connected\n");

    // Discover services and characteristics
    static struct bt_gatt_discover_params discover_params;
    memset(&discover_params, 0, sizeof(discover_params));
    discover_params.func = discover_func;
    discover_params.start_handle = 0x0001;
    discover_params.end_handle = 0xffff;
    discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
    printk("Discovering primary services\n");
    int ret = bt_gatt_discover(default_conn, &discover_params);
    if (ret) {
        printk("Discover failed (err %d)\n", ret);
    } else {
        printk("Discover started\n");
    }
}

static void disconnected(struct bt_conn *conn, uint8_t reason)
{
    printk("Disconnected (reason %u)\n", reason);

    if (default_conn) {
        bt_conn_unref(default_conn);
        default_conn = NULL;
    }
}

static struct bt_conn_cb conn_callbacks = {
    .connected = connected,
    .disconnected = disconnected,
};

static void scan_cb(const bt_addr_le_t *addr, int8_t rssi, uint8_t adv_type,
                    struct net_buf_simple *buf)
{
    char addr_str[18];
    char add_str_target[18];
    bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
    bt_addr_le_to_str(&target_addr, add_str_target, sizeof(add_str_target));
	printk("Scanned %s || target : %s \n", addr_str, add_str_target);
    if (memcmp(add_str_target, addr_str, 18*sizeof(char)) == 0) {
		printk("Found device: %s (RSSI %d)\n", addr_str, rssi);
        printk("Connecting to %s\n", addr_str);
        bt_le_scan_stop();
        bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, BT_LE_CONN_PARAM_DEFAULT, &default_conn);
    }
}

void read_characteristic_thread(void)
{
    k_sem_take(&ble_sem, K_FOREVER);
    printk("characteristic read thread\n");
    while (1) {
        // if (default_conn) {
        //     int err = bt_gatt_read(default_conn, &read_params);
        //     if (err) {
        //         printk("Read failed (err %d)\n", err);
        //     }
        // }
        k_sleep(K_SECONDS(1)); // Adjust the sleep duration as needed
    }
}

K_THREAD_DEFINE(read_char_thread, 1024, read_characteristic_thread, NULL, NULL, NULL, 5, 0, 0);

void main(void)
{
    int err;

    err = bt_enable(NULL);
    if (err) {
        printk("Bluetooth init failed (err %d)\n", err);
        return;
    }

    printk("Bluetooth initialized\n");

    bt_conn_cb_register(&conn_callbacks);

    struct bt_le_scan_param scan_param = {
        .type       = BT_HCI_LE_SCAN_PASSIVE,
        .options    = BT_LE_SCAN_OPT_NONE,
        .interval   = BT_GAP_SCAN_FAST_INTERVAL,
        .window     = BT_GAP_SCAN_FAST_WINDOW,
    };

    err = bt_le_scan_start(&scan_param, scan_cb);
    if (err) {
        printk("Scanning failed to start (err %d)\n", err);
        return;
    }

    printk("Scanning started\n");
}

prj.conf :

CONFIG_BT=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_HCI=y
CONFIG_BT_CTLR=y
CONFIG_BT_SCAN=y
CONFIG_BT_H4=n
CONFIG_BT_SCAN=y
CONFIG_BT_GATT_CLIENT=y
CONFIG_PRINTK=y
CONFIG_USE_SEGGER_RTT=y
CONFIG_RTT_CONSOLE=y
CONFIG_LOG_BACKEND_RTT=y
CONFIG_LOG_MODE_IMMEDIATE=n
CONFIG_LOG_MODE_DEFERRED=y

Btw I'm using thingy91/nrf52840

Best regards

Youssef

Parents
  • You have several issues in your code.

    1. You do not follow the Bluetooth standard regarding characteristic discovery. To reach compliance, you must follow the standard. Please see https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-60/out/en/host/generic-attribute-profile--gatt-.html#UUID-82cb5893-4bd4-1888-f69b-85bd43a9048c as well as the documentation for the bt_gatt_discover function https://docs.zephyrproject.org/apidoc/latest/group__bt__gatt__client.html#gac06a945e5f7939b6716bc4f2cea781bd. You have two options when discovering characteristics: either by uuid or by discovering characteristics within a known attribute handle range of a service. You use 1 and 0xffff which is not the range within a specific service. You should either first discover the service or discover by uuid. This is not really related to your particular issue, but is good to have in mind when you are about to certify your device.

    2. You do not follow the Bluetooth standard regarding descriptor discovery but assume the attribute handle of the cccd (which you got incorrect). The standard says "The characteristic descriptor declaration may appear in any order within the characteristic definition. The client shall not assume the order in which a characteristic descriptor declaration appears in a characteristic definition following the Characteristic Value declaration.". After you have discovered a characteristic, you may discover its descriptors given the handle range of the characteristic. You use the BT_GATT_DISCOVER_DESCRIPTOR type to do this. You need to know the end_handle of the characteristic when you perform the descriptor discovery which can be determined by checking at what handle the next characteristic starts minus 1 or where the current service ends, in case this is the last characteristic within the service.

    3. When calling bt_gatt_subscribe, you need to fill in the mandatory fields in the bt_gatt_subscribe_params object. You forget to fill in the value_handle. This is needed so that the local bluetooth stack can dispatch the notifications to the correct callback. As also mentioned, the ccc_handle is incorrect (you are pointing to the characteristic value attribute instead of the cccd). The ccc_handle should be discovered as in 2.

    4. When using bt_gatt_subscribe, you don't have to and should not manually be calling bt_gatt_write. And even if you do, the value is not correct for a CCCD. If you want to enable notifications, you should write the bytes {0x01, 0x00} and not {0x04, 0x62, 0x15, 0x00, 0x00}. This is probably why you get the error message of incorrect length. But as I mentioned you should not call this function when you want to subscribe since bt_gatt_subscribe already does that for you.

    Edit:

    Checking your question and code again, it is unclear what you really want. Do you want to write a value to the characteristic or activate notifications, or both? If you want to write a value to the characteristic, you need to write to the value handle of the characteristic and not to the cccd.

Reply
  • You have several issues in your code.

    1. You do not follow the Bluetooth standard regarding characteristic discovery. To reach compliance, you must follow the standard. Please see https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-60/out/en/host/generic-attribute-profile--gatt-.html#UUID-82cb5893-4bd4-1888-f69b-85bd43a9048c as well as the documentation for the bt_gatt_discover function https://docs.zephyrproject.org/apidoc/latest/group__bt__gatt__client.html#gac06a945e5f7939b6716bc4f2cea781bd. You have two options when discovering characteristics: either by uuid or by discovering characteristics within a known attribute handle range of a service. You use 1 and 0xffff which is not the range within a specific service. You should either first discover the service or discover by uuid. This is not really related to your particular issue, but is good to have in mind when you are about to certify your device.

    2. You do not follow the Bluetooth standard regarding descriptor discovery but assume the attribute handle of the cccd (which you got incorrect). The standard says "The characteristic descriptor declaration may appear in any order within the characteristic definition. The client shall not assume the order in which a characteristic descriptor declaration appears in a characteristic definition following the Characteristic Value declaration.". After you have discovered a characteristic, you may discover its descriptors given the handle range of the characteristic. You use the BT_GATT_DISCOVER_DESCRIPTOR type to do this. You need to know the end_handle of the characteristic when you perform the descriptor discovery which can be determined by checking at what handle the next characteristic starts minus 1 or where the current service ends, in case this is the last characteristic within the service.

    3. When calling bt_gatt_subscribe, you need to fill in the mandatory fields in the bt_gatt_subscribe_params object. You forget to fill in the value_handle. This is needed so that the local bluetooth stack can dispatch the notifications to the correct callback. As also mentioned, the ccc_handle is incorrect (you are pointing to the characteristic value attribute instead of the cccd). The ccc_handle should be discovered as in 2.

    4. When using bt_gatt_subscribe, you don't have to and should not manually be calling bt_gatt_write. And even if you do, the value is not correct for a CCCD. If you want to enable notifications, you should write the bytes {0x01, 0x00} and not {0x04, 0x62, 0x15, 0x00, 0x00}. This is probably why you get the error message of incorrect length. But as I mentioned you should not call this function when you want to subscribe since bt_gatt_subscribe already does that for you.

    Edit:

    Checking your question and code again, it is unclear what you really want. Do you want to write a value to the characteristic or activate notifications, or both? If you want to write a value to the characteristic, you need to write to the value handle of the characteristic and not to the cccd.

Children
  • Hi,

    Thank you for your reply, What I want is to first enable notifications then write a command that is {0x04, 0x62, 0x15, 0x00, 0x00}. So the characteristic shows me its value.

    And when i used bt_gatt_write_without_response() instead of bt_gatt_write() the write command is accepted but no value is returned from the characterstic

  • Per the screenshot from nRF Connect, we can see that the characteristic supports notify and write without response. This means that you have to use bt_gatt_write_without_response or bt_gatt_write_without_response_cb rather than bt_gatt_write. In this case, the remote GATT server will not send any response to the write on the (G)ATT protocol level, so possible errors will not be detected at your GATT client (such as invalid length). However, it is possible that the application running on the GATT server will send a notification when it receives a valid "write without response" to the correct handle, which I think you are referring to. So please update your code to use one of the two mentioned functions instead of bt_gatt_write and make sure you use the value_handle of the characteristic. To be able to receive notifications, make sure you supply the correct handles to the subscribe_params.value_handle and subscribe_params.ccc_handle. In practice these will most likely be chrc->value_handle and chrc->value_handle+1, respectively, but you should discover the cccd per my recommendations if you are to follow the specification.

    Please also follow Jakob Ruhe's recommendations to keep the comments and variable names matching the code. If you want to write to the characteristic value rather than the characteristic client configuration (ccc), you should not call that variable "ccc_value" etc.

Related