|
|
@@ -1,10 +1,12 @@
|
|
|
-#GATT CLIENT EXAMPLE WALKTHROUGH
|
|
|
+# Gatt Client Example Walkthrough
|
|
|
+
|
|
|
+## Introduction
|
|
|
|
|
|
-##INTRODUCTION
|
|
|
In this example, the GATT Client example code for the ESP32 is reviewed. This example implements a Bluetooth Low Energy (BLE) Generic Attribute (GATT) client which scans for nearby peripheral servers and connects to a predefined service. The client then searches for available characteristics and subscribes to a known characteristic in order to receive notifications or indications. The example can register an Application Profile and initializes a sequence of events, which can be used to configure Generic Access Profile (GAP) parameters and to handle events such as scanning, connecting to peripherals and reading and writing characteristics.
|
|
|
|
|
|
-#INCLUDES
|
|
|
-This example is located in the examples folder of the ESP-IDF under the bluetooth/gatt_client/main directory. The gattc_demo.c file located in the main folder contains all the functionality that we are going to review. The header files contained in gattc_demo.c are:
|
|
|
+# Includes
|
|
|
+
|
|
|
+This example is located in the examples folder of the ESP-IDF under the `bluetooth/gatt_client/main` directory. The `gattc_demo.c` file located in the main folder contains all the functionality that we are going to review. The header files contained in `gattc_demo.c` are:
|
|
|
|
|
|
```c
|
|
|
#include <stdint.h>
|
|
|
@@ -23,14 +25,15 @@ This example is located in the examples folder of the ESP-IDF under the bluetoot
|
|
|
#include "esp_gatt_common_api.h"
|
|
|
```
|
|
|
|
|
|
-These "includes" are required for the FreeRTOS and underlaying system components to run, including the 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_gattc_api.h”`, which expose the BLE APIs required to implement this example.
|
|
|
+These `includes` are required for the FreeRTOS and underlaying system components to run, including the 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_gattc_api.h”`, which expose the BLE APIs required to implement this example.
|
|
|
|
|
|
* `bt.h`: configures the BT controller and VHCI from the host side.
|
|
|
* `esp_bt_main.h`: initializes and enables the Bluedroid stack.
|
|
|
* `esp_gap_ble_api.h`: implements the GAP configuration, such as advertising and connection parameters.
|
|
|
* `esp_gattc_api.h`: implements the GATT Client configuration, such as connecting to peripherals and searching for services.
|
|
|
|
|
|
-##MAIN ENTRY POINT
|
|
|
+## Main Entry Point
|
|
|
+
|
|
|
The program’s entry point is the app_main() function:
|
|
|
|
|
|
```c
|
|
|
@@ -107,7 +110,7 @@ The main function starts by initializing the non-volatile storage library. This
|
|
|
ESP_ERROR_CHECK( ret );
|
|
|
```
|
|
|
|
|
|
-##BT CONTROLLER AND STACK INITIALIZATION
|
|
|
+## BT Controller and Stack Initialization
|
|
|
|
|
|
The main function also initializes the BT controller by first creating a BT controller configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. The BT controller implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL) and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` function:
|
|
|
|
|
|
@@ -123,6 +126,7 @@ ret = esp_bt_controller_enable(ESP_BT_MODE_BTDM);
|
|
|
```
|
|
|
|
|
|
There are four Bluetooth modes supported:
|
|
|
+
|
|
|
1. `ESP_BT_MODE_IDLE`: Bluetooth not running
|
|
|
2. `ESP_BT_MODE_BLE`: BLE mode
|
|
|
3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode
|
|
|
@@ -159,7 +163,7 @@ esp_ble_gattc_register_callback();
|
|
|
```
|
|
|
The functions `esp_gap_cb()` and `esp_gattc_cb()` handle all the events generated by the BLE stack.
|
|
|
|
|
|
-##APPLICATION PROFILES
|
|
|
+## Application Profiles
|
|
|
|
|
|
The Application Profiles are a way to group functionalities that are designed for one or more server applications. For example, you can have an Application Profile connected to the Heart Rate Sensors, and another one connected to the Temperature Sensors. Each Application Profile creates a GATT interface to connect to other devices. The Application Profiles in the code are instances of the `gattc_profile_inst` structure, which is defined as:
|
|
|
|
|
|
@@ -177,14 +181,15 @@ struct gattc_profile_inst {
|
|
|
```
|
|
|
|
|
|
The Application Profile structure contains:
|
|
|
- * `gattc_cb`: GATT client callback function
|
|
|
- * `gattc_if`: GATT client interface number for this profile
|
|
|
- * `app_id`: Application Profile ID number
|
|
|
- * `conn_id`: Connection ID
|
|
|
- * `service_start_handle`: Service start handle
|
|
|
- * `service_end_handle`: Service end handle
|
|
|
- * `char_handle`: Char handle
|
|
|
- * `remote_bda`: Remote device address connected to this client.
|
|
|
+
|
|
|
+* `gattc_cb`: GATT client callback function
|
|
|
+* `gattc_if`: GATT client interface number for this profile
|
|
|
+* `app_id`: Application Profile ID number
|
|
|
+* `conn_id`: Connection ID
|
|
|
+* `service_start_handle`: Service start handle
|
|
|
+* `service_end_handle`: Service end handle
|
|
|
+* `char_handle`: Char handle
|
|
|
+* `remote_bda`: Remote device address connected to this client.
|
|
|
|
|
|
In this example there is one Application Profile and its ID is defined as:
|
|
|
|
|
|
@@ -203,6 +208,7 @@ static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = {
|
|
|
},
|
|
|
};
|
|
|
```
|
|
|
+
|
|
|
The initialization of the Application Profile table array includes defining the callback functions for each Profile. These are `gattc_profile_a_event_handler()` and `gattc_profile_a_event_handler()` respectively. In addition, the GATT interface is initialized to the default value of `ESP_GATT_IF_NONE`. Later on, when the Application Profile is registered, the BLE stack returns a GATT interface instance to use with that Application Profile.
|
|
|
|
|
|
The profile registration triggers an `ESP_GATTC_REG_EVT` event, which is handled by the `esp_gattc_cb()` event handler. The handler takes the GATT interface returned by the event and stores it in the profile table:
|
|
|
@@ -245,7 +251,7 @@ Finally, the callback function invokes the corresponding event handler for each
|
|
|
} while (0);
|
|
|
}
|
|
|
```
|
|
|
-##SETTING SCAN PARAMETERS
|
|
|
+## Setting Scan Parameters
|
|
|
|
|
|
The GATT client normally scans for nearby servers and tries connect to them, if interested. However, in order to perform the scanning, first the configuration parameters need to be set. This is done after the registration of the Application Profiles, because the registration, once completed, triggers an `ESP_GATTC_REG_EVT` event. The first time this event is triggered, the GATT event handler captures it and assigns a GATT interface to Profile A, then the event is forwarded to the GATT event handler of Profile A. One in this event handler, the event is used to call the `esp_ble_gap_set_scan_params()` function, which takes a `ble_scan_params` structure instance as parameter. This structure is defined as:
|
|
|
|
|
|
@@ -302,8 +308,10 @@ case ESP_GATTC_REG_EVT:
|
|
|
break;
|
|
|
```
|
|
|
|
|
|
-##START SCANNING
|
|
|
+## Start Scanning
|
|
|
+
|
|
|
Once the scanning parameters are set, an `ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT` event is triggered, which is handled by the GAP event handler `esp_gap_cb()`. This event is used to start the scanning of nearby GATT servers:
|
|
|
+
|
|
|
```c
|
|
|
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: {
|
|
|
//the unit of the duration is second
|
|
|
@@ -312,9 +320,10 @@ Once the scanning parameters are set, an `ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EV
|
|
|
break;
|
|
|
}
|
|
|
```
|
|
|
+
|
|
|
The scanning is started using the `esp_ble_gap_start_scanning()` function which takes a parameter representing the duration of the continuous scanning (in seconds). Once the scanning period is ended, an `ESP_GAP_SEARCH_INQ_CMPL_EVT` event is triggered.
|
|
|
|
|
|
-##GETTING SCAN RESULTS
|
|
|
+## Getting Scan Results
|
|
|
|
|
|
The results of the scanning are displayed as soon as they arrive with the `ESP_GAP_BLE_SCAN_RESULT_EVT` event, which includes the following parameters:
|
|
|
|
|
|
@@ -387,7 +396,8 @@ We are interested in the `ESP_GAP_SEARCH_INQ_RES_EVT` event, which is called eve
|
|
|
First the device name is resolved and compared to the one defined in `remote_device_name`. If it equals to the device name of the GATT Server we are interested in, then the scanning is stopped.
|
|
|
|
|
|
|
|
|
-##CONNECTING TO A GATT SERVER
|
|
|
+## Connecting to A GATT Server
|
|
|
+
|
|
|
Every time we receive a result from the `ESP_GAP_SEARCH_INQ_RES_EVT` event, the code first prints the address of the remote device:
|
|
|
|
|
|
```c
|
|
|
@@ -413,7 +423,7 @@ esp_log_buffer_char(GATTC_TAG, adv_name, adv_name_len);
|
|
|
|
|
|
Finally if the remote device name is the same as we have defined above, the local device stops scanning and tries to open a connection to the remote device using the `esp_ble_gattc_open()` function. This function takes as parameters the Application Profile GATT interface, the remote server address and a boolean value. The boolean value is used to indicate if the connection is done directly or if it’s done in the background (auto-connection), at the moment this boolean value must be set to true in order to establish the connection. Notice that the client opens a virtual connection to the server. The virtual connection returns a connection ID. The virtual connection is the connection between the Application Profile and the remote server. Since many Application Profiles can run on one ESP32, there could be many virtual connection opened to the same remote server. There is also the physical connection which is the actual BLE link between the client and the server. Therefore, if the physical connection is disconnected with the `esp_ble_gap_disconnect()` function, all other virtual connections are closed as well. In this example, each Application Profile creates a virtual connection to the same server with the `esp_ble_gattc_open()` function, so when the close function is called, only that connection from the Application Profile is closed, while if the gap disconnect function is called, both connections will be closed. In addition, connect events are propagated to all profiles because it relates to the physical connection, while open events are propagated only to the profile that creates the virtual connection.
|
|
|
|
|
|
-##CONFIGURING MTU SIZE
|
|
|
+## Configuring the MTU Size
|
|
|
|
|
|
ATT_MTU is defined as the maximum size of any packet sent between a client and a server. When the client connects to the server, it informs the server which MTU size to use by exchanging MTU Request and Response protocol data units (PDUs). This is done after the opening of a connection. After opening the connection, an `ESP_GATTC_CONNECT_EVT` event is triggered:
|
|
|
|
|
|
@@ -455,7 +465,7 @@ if (mtu_ret){
|
|
|
break;
|
|
|
```
|
|
|
|
|
|
-The connection opening also triggers an ESP_GATTC_OPEN_EVT, which is used to check that the opening of the connection was done successfully, otherwise print an error and exit.
|
|
|
+The connection opening also triggers an `ESP_GATTC_OPEN_EVT`, which is used to check that the opening of the connection was done successfully, otherwise print an error and exit.
|
|
|
|
|
|
```c
|
|
|
case ESP_GATTC_OPEN_EVT:
|
|
|
@@ -477,7 +487,7 @@ case ESP_GATTC_CFG_MTU_EVT:
|
|
|
…
|
|
|
```
|
|
|
|
|
|
-##DISCOVERING SERVICES
|
|
|
+## Discovering Services
|
|
|
|
|
|
The MTU configuration event is also used to start discovering the services available in the server that the client just connected to. To discover the services, the function `esp_ble_gattc_search_service()` is used. The parameters of the function are the GATT interface, the Application Profile connection ID and the UUID of the service application that the client is interested in. The service we are looking for is defined as:
|
|
|
|
|
|
@@ -517,7 +527,7 @@ REMOTE_SERVICE_UUID) {
|
|
|
|
|
|
In case that the client finds the service that it is looking for, the flag get_server is set to true, and the start handle value and end handle value, which will be used later to get all the characteristics of that service, are saved. After all service results are returned, the search is completed and an `ESP_GATTC_SEARCH_CMPL_EVT` event is triggered.
|
|
|
|
|
|
-##GETTING CHARACTERISTICS
|
|
|
+## Getting Characteristics
|
|
|
|
|
|
This examples implements getting characteristic data from a predefined service. The service that we want the characteristics from has an UUID of 0x00FF, and the characteristic we are interested in has an UUID of 0xFF01:
|
|
|
|
|
|
@@ -608,7 +618,7 @@ Once defined, we can get the characteristics from that service using the `esp_bl
|
|
|
|
|
|
`esp_ble_gattc_get_attr_count()` gets the attribute count with the given service or characteristic in the gattc cache. The parameters of `esp_ble_gattc_get_attr_count()` function are the GATT interface, the connection ID, the attribute type defined in `esp_gatt_db_attr_type_t`, the attribute start handle, the attribute end handle, the characteristic handle (this parameter is only valid when the type is set to `ESP_GATT_DB_DESCRIPTOR`.) and output the number of attribute has been found in the gattc cache with the given attribute type. Then we allocate a buffer to save the char information for `esp_ble_gattc_get_char_by_uuid()` function. The function finds the characteristic with the given characteristic UUID in the gattc cache. It just gets characteristic from local cache, instead of the remote devices. In a server, there might be more than one chars sharing the same UUID. However, in our gatt_server demo, every char has an unique UUID and that’s why we only use the first char in `char_elem_result`, which is the pointer to the characteristic of the service. Count initially stores the number of the characteristics that the client wants to find, and will be updated with the number of the characteristics that have been actually found in the gattc cache with `esp_ble_gattc_get_char_by_uuid`.
|
|
|
|
|
|
-##REGISTERING FOR NOTIFICATIONS
|
|
|
+## Registering for Notifications
|
|
|
|
|
|
The client can register to receive notifications from the server every time the characteristic value changes. In this example, we want to register for notifications of the characteristic identified with an UUID of 0xff01. After getting all the characteristics, we check the properties of the received characteristic, then use the `esp_ble_gattc_register_for_notify()` function to register notifications. The function arguments are the GATT interface, the address of the remote server, and the handle we want to register for notifications.
|
|
|
|
|
|
@@ -712,7 +722,7 @@ The value to write is “1” to enable notifications. We also pass `ESP_GATT_WR
|
|
|
|
|
|
|
|
|
|
|
|
-##CONCLUSION
|
|
|
+## Conclusion
|
|
|
|
|
|
We have reviewed the GATT Client example code for the ESP32. This example scans for nearby devices and searches for services and characteristics of servers of interest. When the server of interest is found, a connection is made with that server and a search for services is performed. Finally, the client looks for a specific characteristic in the services found, if found, gets the characteristic value and registers for notifications to that characteristic. This is done by registering one Application Profile and following a sequence of events to configure the GAP and GATT parameters required.
|
|
|
|