Write a string to characteristic

doduong
Posts: 7
Joined: Wed Aug 30, 2017 7:58 am

Write a string to characteristic

Postby doduong » Tue Sep 19, 2017 9:00 am

Hi everyone,
I'm working with ESP32 using sample project "gatt_server_service_table". I have a service with 4 characteristic. Now, I want to write a string to characteristic. This is my code in event :ESP_GATTS_READ_EVT

Code: Select all

ESP_LOGI(GATTS_TAG, "GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", param->read.conn_id, param->read.trans_id, param->read.handle);
        esp_gatt_rsp_t rsp;
        memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
        rsp.attr_value.handle = param->read.handle;
        rsp.attr_value.len = 4;
        rsp.attr_value.value[0] = 0xde;
        rsp.attr_value.value[1] = 0xed;
        rsp.attr_value.value[2] = 0xbe;
        rsp.attr_value.value[3] = 0xef;
        esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id,
                                    ESP_GATT_OK, &rsp);
        break;
But it doesn't work because i don't know Handle of characteristic (rsp.attr_value.handle = param->read.handle )
I try to use this function :

Code: Select all

esp_ble_gatts_get_attr_value(uint16_t attr_handle, uint16_t *length, const uint8_t **value)
but it need to know Handle of characteristic.
Someone can help me ?

I will be greatful for any help!

halfro
Posts: 18
Joined: Sat Jul 15, 2017 11:13 am

Re: Write a string to characteristic

Postby halfro » Tue Sep 19, 2017 9:01 pm

The handle of the characteristic is stored in the attribute handle table returned after ESP_GATTS_CREAT_ATTR_TAB_EVT:

Code: Select all

heart_rate_handle_table[CHARACTERISTIC_ENUM_VALUE]
will return the handle for CHARACTERISTIC_ENUM_VALUE.

doduong
Posts: 7
Joined: Wed Aug 30, 2017 7:58 am

Re: Write a string to characteristic

Postby doduong » Wed Sep 20, 2017 8:41 am

halfro wrote:The handle of the characteristic is stored in the attribute handle table returned after ESP_GATTS_CREAT_ATTR_TAB_EVT:

Code: Select all

heart_rate_handle_table[CHARACTERISTIC_ENUM_VALUE]
will return the handle for CHARACTERISTIC_ENUM_VALUE.
Hi halfro,
Thanks for your response.But I can't get handle. I want to write a string to characteristic so I need use handle of characteristic for function :

Code: Select all

esp_ble_gatts_get_attr_value(uint16_t attr_handle, uint16_t *length, const uint8_t **value)
I have 4 characteristic and string need to be written to characteristic 2 but I don't know how to do that.

halfro
Posts: 18
Joined: Sat Jul 15, 2017 11:13 am

Re: Write a string to characteristic

Postby halfro » Sun Sep 24, 2017 10:17 am

doduong wrote:
halfro wrote:The handle of the characteristic is stored in the attribute handle table returned after ESP_GATTS_CREAT_ATTR_TAB_EVT:

Code: Select all

heart_rate_handle_table[CHARACTERISTIC_ENUM_VALUE]
will return the handle for CHARACTERISTIC_ENUM_VALUE.
Hi halfro,
Thanks for your response.But I can't get handle. I want to write a string to characteristic so I need use handle of characteristic for function :

Code: Select all

esp_ble_gatts_get_attr_value(uint16_t attr_handle, uint16_t *length, const uint8_t **value)
I have 4 characteristic and string need to be written to characteristic 2 but I don't know how to do that.
I will try to be as detailed as I can for you. This works in v2.1 stable release of the SDK as I only use stable releases in production environments.

Firstly, reads are easier that writes. Writes are divided into two types, short writes for payload size of (MTU-3) or less and long writes for payload sizes greater than (MTU-3). The default MTU is 23 bytes so strings longer than 20 bytes have to be written in chunks. When a write happens, the message sent consists of the operation code, handle value and string chunk. prepare writes also contain an offset and such under the esp_gatts_api.h write union entry. The client knows the handle value when writing as it has populated a database representing the server attributes it is connected to. So when a write is occurring, the server(ESP32) needs to check the handle in the GATT server event, which in this case is a write. This can be found in esp_gatts_api.h in location esp-idf/components/bt/bluedroid/api/include . So when an event occurs, say a write event, the ESP_GATTS_WRITE_EVT is triggered for the specific profile event handle which then calls a prepare write fuction. The prepare write function is a means to temporarily hold data for short writes and long writes. If the write is a long write, then (param->write.is_prep) will be set, if it is a short write then (param->write.is_prep) will not be set. After filling a prepare write operation, the gatt client will issue an execute write operation ESP_GATTS_EXEC_WRITE_EVT to the server. Here is my sample:

Code: Select all

static void usage_profile_prepare_write_event_handler(prepare_write_t *prepare_write_env, esp_gatt_if_t gatts_if,
                                                      esp_ble_gatts_cb_param_t *param)
{
    ESP_LOGD(BLE_TAG, "ENTERED FUNCTION: %s", __func__);
    if (param->write.need_rsp)
    {
        if (param->write.is_prep)
        {
            /*Long writes of payload size greater than (MTU-3)*/
            if (prepare_write_buffer(prepare_write_env, gatts_if, param) != ESP_GATT_OK)
            {
                return;
            }
            memcpy(prepare_write_env->prepare_buf + param->write.offset, param->write.value, param->write.len);
            prepare_write_env->prepare_len += param->write.len;
            prepare_write_env->handle =  param->write.handle;
        }
        else
        {
            /*Short writes, of maximum payload size (MTU-3)*/
            if (prepare_write_buffer(prepare_write_env, gatts_if, param) != ESP_GATT_OK)
            {
                return;
            }

            memcpy(prepare_write_env->prepare_buf, param->write.value, param->write.len);
            prepare_write_env->prepare_len += param->write.len;
            prepare_write_env->handle =  param->write.handle;
            if (prepare_write_env->handle == usage_handle_table[USAGE_IDX_DEVICE_STATE_VAL])
            {
                uint8_check_then_write(&usage_state_attribute, prepare_write_env, param);
                clear_write_buffer(prepare_write_env);
            }
            else if (prepare_write_env->handle == usage_handle_table[USAGE_IDX_DISPLAY_STRING_VAL])
            {
                bytestring_check_then_write(&usage_runtime_string_attribute, prepare_write_env, param);
                clear_write_buffer(prepare_write_env);
            }
        }
    }
}
USAGE_IDX_DEVICE_STATE_VAL is a uint8_t val and USAGE_IDX_STARTUP_STRING_VAL is a string characteristic value. This is a modification of the attribute table sample. Unfortunately I cant share the code fully but can give hints. The attributes are initialiased at the global level as below:

Code: Select all

esp_attr_value_t usage_startup_string_attribute =
{
    .attr_max_len= CHAR_VAL_LEN_MAX,
    .attr_value  = NULL,
    .attr_len    = UNINITIALISED,
};

esp_attr_value_t usage_state_attribute = 
{
    .attr_max_len= CHAR_VAL_LEN_MAX,
    .attr_value  = NULL,
    .attr_len    = UNINITIALISED,
};
The value is a pointer to void that you can cast to the data-type you want if you need to perform operations.

As a modification, i added an entry in my prepare write structure called handle that would be good for long writes.

Code: Select all

typedef struct
{
    uint8_t* prepare_buf;
    int prepare_len;
    uint16_t handle;
} prepare_write_t;
This is because the execute write operation union entry doesn't have a handle attribute in esp_gatts_api.h ,

Code: Select all

    struct gatts_exec_write_evt_param {
        uint16_t conn_id;               /*!< Connection id */
        uint32_t trans_id;              /*!< Transfer id */
        esp_bd_addr_t bda;              /*!< The bluetooth device address which been written */
#define ESP_GATT_PREP_WRITE_CANCEL 0x00 /*!< Prepare write flag to indicate cancel prepare write */
#define ESP_GATT_PREP_WRITE_EXEC   0x01 /*!< Prepare write flag to indicate execute prepare write */
        uint8_t exec_write_flag;        /*!< Execute write flag */
    } exec_write; 
I would then use the handle entry written to the prepare_write struct to determine which attribute the write would be commited to, based on the last prepare write operation. I do hope from this you can forge your own path on how to handle your specific problem.

Who is online

Users browsing this forum: Google [Bot] and 97 guests