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.