1 # GATT Server Service Table Example Walkthrough
5 This document presents a walkthrough of the GATT Server Service Table example code for the ESP32. This example implements a Bluetooth Low Energy (BLE) Generic Attribute (GATT) Server using a table-like data structure to define the server services and characteristics such as the one shown in the figure below Therefore, it demonstrates a practical way to define the server functionality in one place instead of adding services and characteristics one by one.
7 This example implements the *Heart Rate Profile* as defined by the [Traditional Profile Specifications](https://www.bluetooth.com/specifications/profiles-overview).
9 <div align="center"><img src="image/Heart_Rate_Service.png" width = "450" alt="Table-like data structure representing the Heart Rate Service" align=center /> </div>
13 Let’s start by taking a look at the included headers in the [gatts_table_creat_demo.c](../main/gatts_table_creat_demo.c) file:
16 #include "freertos/FreeRTOS.h"
17 #include "freertos/task.h"
18 #include "freertos/event_groups.h"
19 #include "esp_system.h"
21 #include "nvs_flash.h"
25 #include "esp_gap_ble_api.h"
26 #include "esp_gatts_api.h"
27 #include "esp_bt_defs.h"
28 #include "esp_bt_main.h"
29 #include "esp_bt_main.h"
30 #include “gatts_table_creat_demo.h"
32 These includes are required for the *FreeRTOS* and underlaying system components to run, including logging functionality and a library to store data in non-volatile flash memory. We are interested in ``bt.h``, ``esp_bt_main.h``, ``esp_gap_ble_api.h`` and ``esp_gatts_api.h`` which expose the BLE APIs required to implement this example.
34 * ``bt.h``: implements BT controller and VHCI configuration procedures from the host side.
35 * ``esp_bt_main.h``: implements initialization and enabling of the Bluedroid stack.
36 * ``esp_gap_ble_api.h``: implements GAP configuration such as advertising and connection parameters.
37 * ``esp_gatts_api.h``: implements GATT Server configuration such as creating services and characteristics.
41 The header file [gatts_table_creat_demo.h](../main/gatts_table_creat_demo.h) is where an enumeration of the services and characteristics is created:
50 HRS_IDX_HR_MEAS_NTF_CFG,
52 HRS_IDX_BOBY_SENSOR_LOC_CHAR,
53 HRS_IDX_BOBY_SENSOR_LOC_VAL,
55 HRS_IDX_HR_CTNL_PT_CHAR,
56 HRS_IDX_HR_CTNL_PT_VAL,
61 The enumeration elements are set up in the same order as the Heart Rate Profile attributes, starting with the service followed by the characteristics of that service. In addition, the Heart Rate Measurement characteristic has a Client Characteristic Configuration (CCC) descriptor which is an additional attribute that describes if the characteristic has notifications enabled. The enumeration index can be used to identify each element later when creating the actual attributes table. In summary, the elements are described as follows:
63 * ``HRS_IDX_SVC``: Heart Rate Service index
64 * ``HRS_IDX_HR_MEAS_CHAR``: Heart Rate Measurement characteristic index
65 * ``HRS_IDX_HR_MEAS_VAL``: Heart Rate Measurement characteristic value index
66 * ``HRS_IDX_HR_MEAS_NTF_CFG``: Heart Rate Measurement notifications configuration (CCC) index
67 * ``HRS_IDX_BOBY_SENSOR_LOC_CHAR``: Heart Rate Body Sensor Location characteristic index
68 * ``HRS_IDX_BOBY_SENSOR_LOC_VAL``: Heart Rate Body Sensor Location characteristic value index
69 * ``HRS_IDX_HR_CTNL_PT_CHAR``: Heart Rate Control Point characteristic index
70 * ``HRS_IDX_HR_CTNL_PT_VAL``: Heart Rate Control Point characteristic value index
71 * ``HRS_IDX_NB``: Number of table elements.
75 The entry point to this example is the ``app_main()`` function:
83 ret = nvs_flash_init();
84 if (ret == ESP_ERR_NVS_NO_FREE_PAGES) {
85 ESP_ERROR_CHECK(nvs_flash_erase());
86 ret = nvs_flash_init();
88 ESP_ERROR_CHECK( ret );
90 esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
91 ret = esp_bt_controller_init(&bt_cfg);
93 ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed\n", __func__);
97 ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
99 ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed\n", __func__);
103 ESP_LOGI(GATTS_TABLE_TAG, "%s init bluetooth\n", __func__);
104 ret = esp_bluedroid_init();
106 ESP_LOGE(GATTS_TABLE_TAG, "%s init bluetooth failed\n", __func__);
109 ret = esp_bluedroid_enable();
111 ESP_LOGE(GATTS_TABLE_TAG, "%s enable bluetooth failed\n", __func__);
115 esp_ble_gatts_register_callback(gatts_event_handler);
116 esp_ble_gap_register_callback(gap_event_handler);
117 esp_ble_gatts_app_register(ESP_HEART_RATE_APP_ID);
122 The main function starts by initializing the non-volatile storage library in order to be able to save parameters in flash memory.
125 ret = nvs_flash_init();
128 ## BT Controller and Stack Initialization
130 See this section in [GATT Server Example Walkthrough](../../gatt_server/tutorial/Gatt_Server_Example_Walkthrough.md).
133 ## Application Profiles
135 This example implements one Application Profile for the Heart Rate Service. An Application Profile is a way to group functionality which is designed to be used by one client application, for example one smartphone mobile app. In this way, different types of profiles can be accommodated in one server. The Application Profile ID, which is an user-assigned number to identify each profile, is used to register the profile in the stack, in this example the ID is 0x55.
138 #define HEART_PROFILE_NUM 1
139 #define HEART_PROFILE_APP_IDX 0
140 #define ESP_HEART_RATE_APP_ID 0x55
143 The profiles are stored in the ``heart_rate_profile_tab`` array. Since there is only one profile in this example, one element is stored in the array with index zero as defined by the ``HEART_PROFILE_APP_IDX``. Additionally, the profile event handler callback function is initialized. Each application on the GATT server uses a different interface, represented by the gatts_if parameter. For initialization, this parameter is set to ``ESP_GATT_IF_NONE``, later when the application is registered, the gatts_if parameter is updated with the corresponding interface generated by the stack.
146 /* One gatt-based profile one app_id and one gatts_if, this array will store the gatts_if returned by ESP_GATTS_REG_EVT */
147 static struct gatts_profile_inst heart_rate_profile_tab[HEART_PROFILE_NUM] = {
148 [HEART_PROFILE_APP_IDX] = {
149 .gatts_cb = gatts_profile_event_handler,
150 .gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
156 The application registration takes place inside ``app_main()`` using the ``esp_ble_gatts_app_register()`` function:
159 esp_ble_gatts_app_register(ESP_HEART_RATE_APP_ID);
162 ## Setting GAP Parameters
164 The register application event is the first one that is triggered during the lifetime of the program. This example uses this event to configure advertising parameters upon registration in the profile event handler. The functions used to achieve this are:
166 * ``esp_ble_gap_set_device_name()``: used to set the advertised device name.
167 * ``esp_ble_gap_config_adv_data()``: used to configure standard advertising data.
169 The function used to configure standard Bluetooth Specification advertisement parameters is ``esp_ble_gap_config_adv_data()`` which takes a pointer to an ``esp_ble_adv_data_t`` structure. The ``esp_ble_adv_data_t`` data structure for advertising data has the following definition:
173 bool set_scan_rsp; /*!< Set this advertising data as scan response or not*/
174 bool include_name; /*!< Advertising data include device name or not */
175 bool include_txpower; /*!< Advertising data include TX power */
176 int min_interval; /*!< Advertising data show advertising min interval */
177 int max_interval; /*!< Advertising data show advertising max interval */
178 int appearance; /*!< External appearance of device */
179 uint16_t manufacturer_len; /*!< Manufacturer data length */
180 uint8_t *p_manufacturer_data; /*!< Manufacturer data point */
181 uint16_t service_data_len; /*!< Service data length */
182 uint8_t *p_service_data; /*!< Service data point */
183 uint16_t service_uuid_len; /*!< Service uuid length */
184 uint8_t *p_service_uuid; /*!< Service uuid array point */
185 uint8_t flag; /*!< Advertising flag of discovery mode, see BLE_ADV_DATA_FLAG detail */
186 } esp_ble_adv_data_t;
189 In this example, the structure is initialized as follows:
192 static esp_ble_adv_data_t heart_rate_adv_config = {
193 .set_scan_rsp = false,
194 .include_name = true,
195 .include_txpower = true,
196 .min_interval = 0x20,
197 .max_interval = 0x40,
199 .manufacturer_len = 0, //TEST_MANUFACTURER_DATA_LEN,
200 .p_manufacturer_data = NULL, //&test_manufacturer[0],
201 .service_data_len = 0,
202 .p_service_data = NULL,
203 .service_uuid_len = sizeof(heart_rate_service_uuid),
204 .p_service_uuid = heart_rate_service_uuid,
205 .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
209 The minimum and maximum advertisement intervals are set in units of 0.625 ms. In this example, the minimum advertisement interval is defined as 0x20 * 0.625 ms = 20 ms and the maximum advertisement interval is initialized as 0x40 * 0.625 ms = 40 ms.
211 An advertising payload can be up to 31 bytes of data. It is possible that some of the parameters surpass the 31-byte advertisement packet limit which causes the stack to cut the message and leave some of the parameters out. To solve this, usually the longer parameters are stored in the scan response, which can be configured using the same ``esp_ble_gap_config_adv_data()`` function and an additional esp_ble_adv_data_t type structure with the .set_scan_rsp parameter is set to true. Finally, to set the device name the ``esp_ble_gap_set_device_name()`` function is used. The registering event handler is shown as follows:
214 static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
215 esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
217 ESP_LOGE(GATTS_TABLE_TAG, "event = %x\n",event);
219 case ESP_GATTS_REG_EVT:
220 ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
221 esp_ble_gap_set_device_name(SAMPLE_DEVICE_NAME);
222 ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
223 esp_ble_gap_config_adv_data(&heart_rate_adv_config);
224 ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
230 Once the advertising data have been set, the ``ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT`` is triggered and managed by the GAP event handler. Moreover, an ``ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT`` is triggered as well if the scan response is also set. Once the configuration of the advertising and scan response data has been set, the handler can use any of these events to start advertising, which is done using the ``esp_ble_gap_start_advertising()`` function:
233 static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
235 ESP_LOGE(GATTS_TABLE_TAG, "GAP_EVT, event %d\n", event);
238 case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
239 esp_ble_gap_start_advertising(&heart_rate_adv_params);
241 case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
242 //advertising start complete event to indicate advertising start successfully or failed
243 if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
244 ESP_LOGE(GATTS_TABLE_TAG, "Advertising start failed\n");
253 The function to start advertising takes a structure of type ``esp_ble_adv_params_t`` with the advertising parameters required.
256 /// Advertising parameters
258 uint16_t adv_int_min; /*!< Minimum advertising interval for undirected and low duty cycle directed advertising.
259 Range: 0x0020 to 0x4000
260 Default: N = 0x0800 (1.28 second)
261 Time = N * 0.625 msec
262 Time Range: 20 ms to 10.24 sec */
263 uint16_t adv_int_max; /*!< Maximum advertising interval for undirected and low duty cycle directed advertising.
264 Range: 0x0020 to 0x4000
265 Default: N = 0x0800 (1.28 second)
266 Time = N * 0.625 msec
267 Time Range: 20 ms to 10.24 sec */
268 esp_ble_adv_type_t adv_type; /*!< Advertising type */
269 esp_ble_addr_type_t own_addr_type; /*!< Owner bluetooth device address type */
270 esp_bd_addr_t peer_addr; /*!< Peer device bluetooth device address */
271 esp_ble_addr_type_t peer_addr_type; /*!< Peer device bluetooth device address type */
272 esp_ble_adv_channel_t channel_map; /*!< Advertising channel map */
273 esp_ble_adv_filter_t adv_filter_policy; /*!< Advertising filter policy */
274 } esp_ble_adv_params_t;
277 Note that ``esp_ble_gap_config_adv_data()`` configures the data that is advertised to the client and takes an ``esp_ble_adv_data_t structure``, while ``esp_ble_gap_start_advertising()`` makes the server to actually start advertising and takes an ``esp_ble_adv_params_t`` structure. The advertising data is the information that is shown to the client, while the advertising parameters are the configuration required by the BLE stack to execute.
279 For this example, the advertisement parameters are initialized as follows:
282 static esp_ble_adv_params_t heart_rate_adv_params = {
285 .adv_type = ADV_TYPE_IND,
286 .own_addr_type = BLE_ADDR_TYPE_PUBLIC,
289 .channel_map = ADV_CHNL_ALL,
290 .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
294 These parameters configure the advertising interval between 20 ms to 40 ms. The advertisement is of type ADV_IND, which is generic, not directed to a particular central device and advertises the server as connectable. The address type is public, uses all channels and allows both scan and connection requests from any central.
296 If the advertising started successfully, an ``ESP_GAP_BLE_ADV_START_COMPLETE_EVT`` event is generated which in this example is used to check if the advertising status is indeed advertising or otherwise print an error message.
300 case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
301 //advertising start complete event to indicate advertising start successfully or failed
302 if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
303 ESP_LOGE(GATTS_TABLE_TAG, "Advertising start failed\n");
309 ## GATT Event Handlers
311 When an Application Profile is registered, an ``ESP_GATTS_REG_EVT`` event is triggered. The parameters of the ``ESP_GATTS_REG_EVT`` are:
314 esp_gatt_status_t status; /*!< Operation status */
315 uint16_t app_id; /*!< Application id which input in register API */
318 In addition to the previous parameters, the event also contains the GATT interface assigned by the BLE stack. The event is captured by the ``gatts_event_handler()`` which stores the generated interface in the profile table and then forwards it to the corresponding profile event handler.
321 static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
323 ESP_LOGI(GATTS_TABLE_TAG, "EVT %d, gatts if %d\n", event, gatts_if);
325 /* If event is register event, store the gatts_if for each profile */
326 if (event == ESP_GATTS_REG_EVT) {
327 if (param->reg.status == ESP_GATT_OK) {
328 heart_rate_profile_tab[HEART_PROFILE_APP_IDX].gatts_if = gatts_if;
330 ESP_LOGI(GATTS_TABLE_TAG, "Reg app failed, app_id %04x, status %d\n",
339 for (idx = 0; idx < HEART_PROFILE_NUM; idx++) {
340 if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
341 gatts_if == heart_rate_profile_tab[idx].gatts_if) {
342 if (heart_rate_profile_tab[idx].gatts_cb) {
343 heart_rate_profile_tab[idx].gatts_cb(event, gatts_if, param);
351 ## Creating Services and Characteristics with the Attribute Table
353 The register event is used to create a table of profile attributes by employing the ``esp_ble_gatts_create_attr_tab()`` function. This function takes an argument of type ``esp_gatts_attr_db_t`` which corresponds to a look up table keyed by the enumeration values defined in the header file.
355 The ``esp_gatts_attr_db_t`` structure has two members:
358 esp_attr_control_t attr_control; /*!< The attribute control type*/
359 esp_attr_desc_t att_desc; /*!< The attribute type*/
362 The attr_control is the auto-respond parameter which can be set as ``ESP_GATT_AUTO_RSP`` to allow the BLE stack to take care of responding messages when read or write events arrive. The other option is ``ESP_GATT_RSP_BY_APP`` which allows to manually respond to messages using the ``esp_ble_gatts_send_response()`` function.
364 The ``att_desc`` is the attribute description which is made of:
367 uint16_t uuid_length; /*!< UUID length */
368 uint8_t *uuid_p; /*!< UUID value */
369 uint16_t perm; /*!< Attribute permission */
370 uint16_t max_length; /*!< Maximum length of the element*/
371 uint16_t length; /*!< Current length of the element*/
372 uint8_t *value; /*!< Element value array*/
375 For example, the first element of the table in this example is the service attribute:
379 {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
380 sizeof(uint16_t), sizeof(heart_rate_svc), (uint8_t *)&heart_rate_svc}},
383 The initialization values are:
385 * ``[HRS_IDX_SVC]``: Named or designated initializer in the enum table.
386 * ``ESP_GATT_AUTO_RSP``: Auto respond configuration, set to respond automatically by the stack.
387 * ``ESP_UUID_LEN_16``: UUID length set to 16 bits.
388 * ``(uint8_t *)&primary_service_uuid``: UUID to identify the service as a primary one (0x2800).
389 * ``ESP_GATT_PERM_READ``: Read Permission for the service.
390 * ``sizeof(uint16_t)``: Maximum length of the service UUID (16 bits).
391 * ``sizeof(heart_rate_svc)``: Current service length set to the size of the variable *heart_rate_svc*, which is 16 bits.
392 * ``(uint8_t *)&heart_rate_svc``: Service attribute value set to the variable *heart_rate_svc* which contains the Heart Rate Service UUID (0x180D).
394 The rest of the attributes is initialized in the same way. Some attributes also have the *NOTIFY* property which is set by ``&char_prop_notify``. The complete table structure is initialized as follows:
397 /// Full HRS Database Description - Used to add attributes into the database
398 static const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB] =
400 // Heart Rate Service Declaration
402 {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
403 sizeof(uint16_t), sizeof(heart_rate_svc), (uint8_t *)&heart_rate_svc}},
405 // Heart Rate Measurement Characteristic Declaration
406 [HRS_IDX_HR_MEAS_CHAR] =
407 {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
408 CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_notify}},
410 // Heart Rate Measurement Characteristic Value
411 [HRS_IDX_HR_MEAS_VAL] =
412 {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&heart_rate_meas_uuid, ESP_GATT_PERM_READ,
413 HRPS_HT_MEAS_MAX_LEN,0, NULL}},
415 // Heart Rate Measurement Characteristic - Client Characteristic Configuration Descriptor
416 [HRS_IDX_HR_MEAS_NTF_CFG] =
417 {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
418 sizeof(uint16_t),sizeof(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}},
420 // Body Sensor Location Characteristic Declaration
421 [HRS_IDX_BOBY_SENSOR_LOC_CHAR] =
422 {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
423 CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read}},
425 // Body Sensor Location Characteristic Value
426 [HRS_IDX_BOBY_SENSOR_LOC_VAL] =
427 {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&body_sensor_location_uuid, ESP_GATT_PERM_READ,
428 sizeof(uint8_t), sizeof(body_sensor_loc_val), (uint8_t *)body_sensor_loc_val}},
430 // Heart Rate Control Point Characteristic Declaration
431 [HRS_IDX_HR_CTNL_PT_CHAR] =
432 {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
433 CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write}},
435 // Heart Rate Control Point Characteristic Value
436 [HRS_IDX_HR_CTNL_PT_VAL] =
437 {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&heart_rate_ctrl_point, ESP_GATT_PERM_WRITE|ESP_GATT_PERM_READ,
438 sizeof(uint8_t), sizeof(heart_ctrl_point), (uint8_t *)heart_ctrl_point}},
442 ## Starting the Service
443 When the attribute table is created, an ``ESP_GATTS_CREAT_ATTR_TAB_EVT`` event is triggered. This event has the following parameters:
446 esp_gatt_status_t status; /*!< Operation status */
447 esp_bt_uuid_t svc_uuid; /*!< Service uuid type */
448 uint16_t num_handle; /*!< The number of the attribute handle to be added to the gatts database */
449 uint16_t *handles; /*!< The number to the handles */
452 This example uses this event to print information and to check that the size of the created table equals the number of elements in the enumeration HRS_IDX_NB. If the table is correctly created, the attribute handles are copied into the handle table heart_rate_handle_table and the service is started using the ``esp_ble_gatts_start_service()`` function:
455 case ESP_GATTS_CREAT_ATTR_TAB_EVT:{
456 ESP_LOGI(GATTS_TABLE_TAG, "The number handle =%x\n",param->add_attr_tab.num_handle);
457 if (param->add_attr_tab.status != ESP_GATT_OK){
458 ESP_LOGE(GATTS_TABLE_TAG, "Create attribute table failed, error code=0x%x", param->add_attr_tab.status);
460 else if (param->add_attr_tab.num_handle != HRS_IDX_NB){
461 ESP_LOGE(GATTS_TABLE_TAG, "Create attribute table abnormally, num_handle (%d) \
462 doesn't equal to HRS_IDX_NB(%d)", param->add_attr_tab.num_handle, HRS_IDX_NB);
465 memcpy(heart_rate_handle_table, param->add_attr_tab.handles, sizeof(heart_rate_handle_table));
466 esp_ble_gatts_start_service(heart_rate_handle_table[HRS_IDX_SVC]);
471 The handles stored in the handles pointer of the event parameters are numbers that identify each attribute. The handles can be used to know which characteristic is being read or written to, therefore they can be passed around and to upper layers of the application to handle different actions.
473 Finally, the heart_rate_handle_table contains the Application Profile in the form of a structure with information about the attribute parameters as well as GATT interface, connection ID, permissions and application ID. The profile structure is shown as follows, note that not all members are used in this example:
476 struct gatts_profile_inst {
477 esp_gatts_cb_t gatts_cb;
481 uint16_t service_handle;
482 esp_gatt_srvc_id_t service_id;
483 uint16_t char_handle;
484 esp_bt_uuid_t char_uuid;
485 esp_gatt_perm_t perm;
486 esp_gatt_char_prop_t property;
487 uint16_t descr_handle;
488 esp_bt_uuid_t descr_uuid;
493 This document explains the work flow of the GATT Server Service Table example code that implements a Heart Rate Profile. This example begins by defining a table of attributes which include all the services and characteristics of the server, then it registers the Application Profile which triggers events that are used to configure GAP parameters and to create the service table. A service table is initialized with all the parameters required for each attribute and the service is started. This example shows a practical way of defining the server attributes by using a table instead of adding characteristic one by one.