Page 1 of 1

Where are characteristic values stored in ESP-IDF?

Posted: Thu Apr 08, 2021 2:16 am
by jhulbert
I was looking through the gatts_table_creat_demo.c example and I noticed in the attribute table all of the characteristic value entries in the table, the "value" field (i.e. esp_attr_desc_t.value) is a pointer to char_value[4] (a literal array of 8-bit hex values).

This struck me as odd because I thought esp_attr_desc_t.value was a pointer to some memory containing the actually value of the characteristic attribute. If that were true, why would characteristics A, B, and C be a pointer to the same memory region? I've removed the other attributes in the table below for simplicity.

Code: Untitled.c Select all


/* Full Database Description - Used to add attributes into the database */
static const uint8_t char_value[4] = {0x11, 0x22, 0x33, 0x44};

static const esp_gatts_attr_db_t gatt_db[HRS_IDX_NB] =
{
/* Characteristic Value */
[IDX_CHAR_VAL_A] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_A, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},

/* Characteristic Value */
[IDX_CHAR_VAL_B] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_B, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},

/* Characteristic Value */
[IDX_CHAR_VAL_C] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_C, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},

};

To dig into this deeper, I then added some code to print the value field of each of these attributes, as shown in the listing below. I used the nRF Connect app to connect to the example and write to the characteristics.

Code: Untitled.c Select all

void log_char_value_task(void* param)
{
while (1) {
ESP_LOGI(GATTS_TABLE_TAG, "Characteristic A Value:");
esp_log_buffer_hex(GATTS_TABLE_TAG, gatt_db[IDX_CHAR_VAL_A].att_desc.value, gatt_db[IDX_CHAR_VAL_A].att_desc.length);
ESP_LOGI(GATTS_TABLE_TAG, "Characteristic B Value:");
esp_log_buffer_hex(GATTS_TABLE_TAG, gatt_db[IDX_CHAR_VAL_B].att_desc.value, gatt_db[IDX_CHAR_VAL_B].att_desc.length);
ESP_LOGI(GATTS_TABLE_TAG, "Characteristic C Value:");
esp_log_buffer_hex(GATTS_TABLE_TAG, gatt_db[IDX_CHAR_VAL_C].att_desc.value, gatt_db[IDX_CHAR_VAL_C].att_desc.length);
vTaskDelay(5000/portTICK_PERIOD_MS);
}
}

What I found is that the att_desc.value printed for each characteristic did not change; it was always the default value in char_value[4] of {0x11, 0x22, 0x33, 0x44}. I guess this makes sense since char_value[4] is defined a const. The characteristic values did change as expected after writing with nRF Connect; what was shown in this app differed to what was printed below. Therefore, the ESP-IDF BLE code doesn't appear to directly expose the characteristic values in esp_attr_desc_t.value, which makes sense since reads and writes should be through the appropriate BLE protocol functions anyway.

I (51225) GATTS_TABLE_DEMO: Characteristic A Value:
I (51225) GATTS_TABLE_DEMO: 11 22 33 44
I (51225) GATTS_TABLE_DEMO: Characteristic B Value:
I (51225) GATTS_TABLE_DEMO: 11 22 33 44
I (51225) GATTS_TABLE_DEMO: Characteristic C Value:
I (51235) GATTS_TABLE_DEMO: 11 22 33 44
I (56235) GATTS_TABLE_DEMO: Characteristic A Value:
I (56235) GATTS_TABLE_DEMO: 11 22 33 44
I (56235) GATTS_TABLE_DEMO: Characteristic B Value:
I (56235) GATTS_TABLE_DEMO: 11 22 33 44
I (56235) GATTS_TABLE_DEMO: Characteristic C Value:
I (56245) GATTS_TABLE_DEMO: 11 22 33 44


This leaves me with some questions/knowledge gaps:

1. What is esp_attr_desc_t.value? Is it just the initial characteristic value?

2. When building an attribute table, how should esp_attr_desc_t.value be assigned? As mentioned above in gatts_table_creat_demo.c they are pointers to the same array for each characteristic. What's odd is this differs from the walkthrough tutorial for this example. There, esp_attr_desc_t.value are given as NULL, (uint8_t *)body_sensor_loc_val, and (uint8_t *)heart_ctrl_point.

Code: Untitled.c Select all

/// Full HRS Database Description - Used to add attributes into the database
static const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB] =
{
// Heart Rate Measurement Characteristic Value
[HRS_IDX_HR_MEAS_VAL] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&heart_rate_meas_uuid, ESP_GATT_PERM_READ,
HRPS_HT_MEAS_MAX_LEN,0, NULL}},

// Body Sensor Location Characteristic Value
[HRS_IDX_BOBY_SENSOR_LOC_VAL] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&body_sensor_location_uuid, ESP_GATT_PERM_READ,
sizeof(uint8_t), sizeof(body_sensor_loc_val), (uint8_t *)body_sensor_loc_val}},

// Heart Rate Control Point Characteristic Value
[HRS_IDX_HR_CTNL_PT_VAL] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&heart_rate_ctrl_point, ESP_GATT_PERM_WRITE|ESP_GATT_PERM_READ,
sizeof(uint8_t), sizeof(heart_ctrl_point), (uint8_t *)heart_ctrl_point}},
};

3. Where are characteristic values, i.e. the memory which is changed on a write event & returned on a read event, actually stored? Is this something under the hood that I need not worry about?