]> granicus.if.org Git - esp-idf/blob
a317dc8da27eab9ff2222285984f6534f97b3fa3
[esp-idf] /
1 # GATT Server Service Table Example Walkthrough
2
3 ## Introduction
4
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. 
6
7 This example implements the *Heart Rate Profile* as defined by the [Traditional Profile Specifications](https://www.bluetooth.com/specifications/profiles-overview).
8
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>
10
11 ## Includes
12
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:
14
15 ```c
16 #include "freertos/FreeRTOS.h"
17 #include "freertos/task.h"
18 #include "freertos/event_groups.h"
19 #include "esp_system.h"
20 #include "esp_log.h"
21 #include "nvs_flash.h"
22 #include "bt.h"
23 #include "bta_api.h"
24
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"
31 ```
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.
33
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.
38
39 ## Service Table
40
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:
42
43 ```c
44 enum
45 {
46     HRS_IDX_SVC,
47
48     HRS_IDX_HR_MEAS_CHAR,
49     HRS_IDX_HR_MEAS_VAL,
50     HRS_IDX_HR_MEAS_NTF_CFG,
51
52     HRS_IDX_BOBY_SENSOR_LOC_CHAR,
53     HRS_IDX_BOBY_SENSOR_LOC_VAL,
54
55     HRS_IDX_HR_CTNL_PT_CHAR,
56     HRS_IDX_HR_CTNL_PT_VAL,
57
58     HRS_IDX_NB,
59 };
60 ```
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:
62
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.
72
73 ## Main Entry Point
74
75 The entry point to this example is the ``app_main()`` function:
76
77 ```c
78 void app_main()
79 {
80     esp_err_t ret;
81
82     // Initialize NVS.
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();
87     }
88     ESP_ERROR_CHECK( ret );
89
90     esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
91     ret = esp_bt_controller_init(&bt_cfg);
92     if (ret) {
93         ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed\n", __func__);
94         return;
95     }
96
97     ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
98     if (ret) {
99         ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed\n", __func__);
100         return;
101     }
102
103     ESP_LOGI(GATTS_TABLE_TAG, "%s init bluetooth\n", __func__);
104     ret = esp_bluedroid_init();
105     if (ret) {
106         ESP_LOGE(GATTS_TABLE_TAG, "%s init bluetooth failed\n", __func__);
107         return;
108     }
109     ret = esp_bluedroid_enable();
110     if (ret) {
111         ESP_LOGE(GATTS_TABLE_TAG, "%s enable bluetooth failed\n", __func__);
112         return;
113     }
114
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);
118     return;
119 }
120 ```
121
122 The main function starts by initializing the non-volatile storage library in order to be able to save parameters in flash memory.
123
124 ```c
125 ret = nvs_flash_init();
126 ```
127
128 ## BT Controller and Stack Initialization
129
130 See this section in [GATT Server Example Walkthrough](../../gatt_server/tutorial/Gatt_Server_Example_Walkthrough.md).
131
132
133 ## Application Profiles
134
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. 
136
137 ```c
138 #define HEART_PROFILE_NUM                       1
139 #define HEART_PROFILE_APP_IDX                   0
140 #define ESP_HEART_RATE_APP_ID                   0x55
141 ```
142
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.
144
145 ```c
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 */
151     },
152
153 };
154 ```
155
156 The application registration takes place inside ``app_main()`` using the ``esp_ble_gatts_app_register()`` function:
157
158 ```c
159 esp_ble_gatts_app_register(ESP_HEART_RATE_APP_ID);
160 ```
161
162 ## Setting GAP Parameters
163
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:
165
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.
168
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:
170
171 ```c
172 typedef struct {
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;
187 ```
188
189 In this example, the structure is initialized as follows:
190
191 ```c
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,
198     .appearance = 0x00,
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),
206 };
207 ```
208
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.
210
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:
212
213 ```c
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)
216 {
217     ESP_LOGE(GATTS_TABLE_TAG, "event = %x\n",event);
218     switch (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__);
225
226 ```
227
228 ## GAP Event Handler
229
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:
231
232 ```c
233 static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
234 {   
235     ESP_LOGE(GATTS_TABLE_TAG, "GAP_EVT, event %d\n", event);
236     
237     switch (event) {
238     case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
239         esp_ble_gap_start_advertising(&heart_rate_adv_params);
240         break;
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");
245         }
246         break;
247     default:
248         break;
249     }
250 }
251 ```
252
253 The function to start advertising takes a structure of type ``esp_ble_adv_params_t`` with the advertising parameters required.
254
255 ```c
256 /// Advertising parameters
257 typedef struct {
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;
275 ```
276
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.
278
279 For this example, the advertisement parameters are initialized as follows:
280
281 ```c
282 static esp_ble_adv_params_t heart_rate_adv_params = {
283     .adv_int_min        = 0x20,
284     .adv_int_max        = 0x40,
285     .adv_type           = ADV_TYPE_IND,
286     .own_addr_type      = BLE_ADDR_TYPE_PUBLIC,
287     //.peer_addr            =
288     //.peer_addr_type       =
289     .channel_map        = ADV_CHNL_ALL,
290     .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
291 };
292 ```
293
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.
295
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.
297
298 ```c
299
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");
304         }
305         break;
306
307 ```
308
309 ## GATT Event Handlers
310
311 When an Application Profile is registered, an ``ESP_GATTS_REG_EVT`` event is triggered. The parameters of the ``ESP_GATTS_REG_EVT`` are:
312
313 ```c
314 esp_gatt_status_t status;    /*!< Operation status */
315 uint16_t app_id;             /*!< Application id which input in register API */
316 ```
317
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.
319
320 ```c
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)
322 {
323     ESP_LOGI(GATTS_TABLE_TAG, "EVT %d, gatts if %d\n", event, gatts_if);
324
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;
329         } else {
330             ESP_LOGI(GATTS_TABLE_TAG, "Reg app failed, app_id %04x, status %d\n",
331                     param->reg.app_id,
332                     param->reg.status);
333             return;
334         }
335     }
336
337     do {
338         int idx;
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);
344                 }
345             }
346         }
347     } while (0);
348 }
349 ```
350
351 ## Creating Services and Characteristics with the Attribute Table
352
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.
354
355 The ``esp_gatts_attr_db_t`` structure has two members:
356
357 ```c
358 esp_attr_control_t    attr_control;       /*!< The attribute control type*/
359 esp_attr_desc_t       att_desc;           /*!< The attribute type*/
360 ```
361
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.
363
364 The ``att_desc`` is the attribute description which is made of:
365
366 ```c
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*/ 
373 ```
374
375 For example, the first element of the table in this example is the service attribute:
376
377 ```c
378 [HRS_IDX_SVC]                       =
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}},
381 ```
382
383 The initialization values are:
384
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).
393
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:
395
396 ```c
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] =
399 {
400     // Heart Rate Service Declaration
401     [HRS_IDX_SVC]                       =
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}},
404
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}},
409
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}},
414
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}},
419
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}},
424
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}},
429
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}},
434
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}},
439 };
440 ```
441
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:
444
445 ```c
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 */
450 ```
451
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:
453
454 ```c
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);
459         }
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);
463         }
464         else {
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]);
467         }
468         break;
469 ```
470
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. 
472
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:
474
475 ```c
476 struct gatts_profile_inst {
477     esp_gatts_cb_t gatts_cb;
478     uint16_t gatts_if;
479     uint16_t app_id;
480     uint16_t conn_id;
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;
489 };
490 ```
491
492 ## Conclusion
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.
494
495
496