GATT Client Example

[中文]

Note

This document is automatically translated using AI. Please excuse any detailed errors. The official English version is still in progress.

Example Description

This example demonstrates how to implement a GATT Client on ESP32, completing device scanning, connection, service discovery, and characteristic reading/writing and notification subscription operations. Through this example, you can learn how to discover services and characteristics of remote devices, initiate read/write requests, and receive notifications or indications from the server, thus realizing data interaction with peripherals.

This example needs to be executed in conjunction with the GATT Server example to achieve a complete BLE communication process.

The example is suitable for scenarios that need to act as a BLE central device, such as mobile phone accessories, data collection gateways, or remote control terminals. Developers can quickly master the basic implementation method of GATT Client through this example, providing a reference for building custom BLE client applications.

Running Method

The complete code of the example can be found at gatt client example. The configuration instructions, building and burning process before running can be found in the README.md file in the example directory.

For custom configuration items and viewing default values, refer to the Macro Definition Description section in the document.

Header File Description

The header files used in this example cover FreeRTOS task management, system tool modules, Bluetooth controller and protocol stack interfaces, and BLE GATT service-related API modules, building the core functions of BLE initialization, Profile management, event handling, and data interaction.

The header files are categorized by function as follows:

  1. FreeRTOS: Provides basic task scheduling and task management mechanisms.

#include "freertos/FreeRTOS.h"
  1. System Tools: Used for system initialization, log printing, and non-volatile storage initialization.

#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_log.h"
  1. Bluetooth Protocol Stack: Used to manage the initialization and release of the Bluetooth controller, start and stop the protocol stack, and obtain local Bluetooth device information.

#include "esp_bt.h"
#include "esp_bt_main.h"
  1. BLE GAP Interface: Responsible for broadcasting, scanning, and other device discovery and connection management functions.

#include "esp_gap_ble_api.h"
  1. BLE GATT Interface: Provides GATT client functionality, handling the discovery and read/write operations of services, characteristics, and descriptors of remote devices.

#include "esp_gattc_api.h"
#include "esp_gatt_defs.h"
#include "esp_gatt_common_api.h"

Macro Definition Description

The macro definitions involved in this example are mainly used to configure the Profile, remote service, and characteristic UUID of the BLE GATT Client, as well as the debug tag and test loop count, which is convenient for unified management in multi-Profile or multi-device scenarios, reducing hard coding, and improving code maintainability.

The macro definitions are categorized by function as follows:

  1. GATT Profile Basic Parameters: Used to uniformly manage the number, index, application ID, and service instance ID of Profiles.

  • Define the number of GATT Profiles for unified management of quantity and index in multi-Profile applications. In this example, it is configured as 1.

#define PROFILE_NUM                 1
  • Set the application ID to distinguish different Profiles for easy protocol stack management. In this example, it is set to 0. When there are multiple Profiles, each Profile should be assigned an application ID, which can be any non-repeating integer or hexadecimal number.

#define PROFILE_A_APP_ID            0
  1. UUID: Used to specify the remote service and notification characteristics to be accessed. The client discovers the service and subscribes to notifications via UUID.

  • Define the remote service UUID for discovering the specified service after scanning and connecting.

#define REMOTE_SERVICE_UUID        0x00FF
  • Define the remote notification characteristic UUID for subscribing and receiving notifications or indications sent by the server.

#define REMOTE_NOTIFY_CHAR_UUID    0xFF01
  1. Client Debugging and Handle Management: Used for log printing identification and invalid handle representation.

  • Set the log printing tag to facilitate debugging output positioning to the GATT Client example.

#define GATTC_TAG "GATTC_DEMO"
  • Define the invalid handle value for initializing the handle or judging whether the handle is valid.

#define INVALID_HANDLE   0
  1. Test Loop Configuration: Used to configure the number of initialization and repeated start-stop tests.

  • When the configuration item CONFIG_EXAMPLE_INIT_DEINIT_LOOP is enabled, define the number of loops for testing the stability of initialization and de-initialization.

#if CONFIG_EXAMPLE_INIT_DEINIT_LOOP
#define EXAMPLE_TEST_COUNT 50
#endif

Note

Repeated start-stop testing refers to the process of repeatedly executing the initialization and de-initialization of the Bluetooth protocol stack in the program, to verify whether the system will have problems such as memory leaks, unreleased resources, or crashes when the Bluetooth function is frequently enabled and disabled, thereby ensuring the stability and reliability of the protocol stack.

Global Variable Description

The global variables defined in this example are mainly used to manage core information such as BLE broadcast configuration, GATT Profile instances, connection status, and attribute data. Through these global variables, the example can share states between different callback functions, handle client requests, and maintain the integrity of GATT services.

Some of the content has been detailed in the General Steps document, and repeated content will not be repeated here, only the key implementations and differences in this example will be highlighted.

  1. Device Information and Status Flags:

  • remote_device_name[]: Save the name of the target remote device for device matching during scanning.

  • connect: Mark whether a connection has been established, initialized as not connected.

  • get_server: Mark whether the target service has been discovered, initialized as the service not discovered.

  1. GATT Attribute Element Cache Pointer:

  • char_elem_result: Point to the cache of the GATT characteristic element of the remote device to save the characteristic discovery result.

  • descr_elem_result: Point to the cache of the GATT descriptor element of the remote device to save the descriptor discovery result.

  1. Target UUID Definition: Defined through the esp_bt_uuid_t structure. For structure members, refer to Bluetooth Defines.

  • remote_filter_service_uuid: The UUID of the target remote service for service discovery filtering.

  • remote_filter_char_uuid: The UUID of the target remote notification characteristic for characteristic discovery filtering.

  • notify_descr_uuid: The UUID of the Client Configuration Descriptor (CCCD) for enabling/disabling characteristic notifications.

  1. Scan Configuration:

  • ble_scan_params: Configure scan parameters.

  • gattc_profile_inst: Create GATT Client instance structure.

  • gl_profile_tab: An array of gattc_profile_inst structure types, each array element represents a Client Profile instance. This instance only contains one Profile. If there are multiple Profiles, they need to be configured one by one as needed. Only two structure members are defined in the example for event callback and interface management. The remaining members are not used in this example, so they are omitted when defining the structure.

Function Description

gattc_profile_event_handler()

gattc_profile_event_handler() is the callback processing function of the GATT Client Profile, which is used to handle various events generated by the BLE GATT Client during operation, such as connection, service discovery, characteristic acquisition, notification subscription, read and write operations, etc.

static void gattc_profile_event_handler( esp_gattc_cb_event_t event,
                                         esp_gatt_if_t gattc_if,
                                         esp_ble_gattc_cb_param_t *param );
Parameter Explanation

Input Parameter

Parameter Function

Parameter Description

event

Event type, such as broadcast start, scan completion, connection parameter update, etc.

Different processing can be performed in the callback according to the event type.

gatts_if

GATT server access interface.

Allocated by the protocol stack, used to distinguish different GATT Profile service instances, and identify the corresponding Profile in the event callback.

param

A pointer to the structure of the event parameters, containing detailed information about the event.

Different events correspond to different union members, such as broadcast data, scan results, connection information, etc.

This function executes corresponding processing logic according to different event types:

  1. Registration Event: ESP_GATTC_REG_EVT, triggered when the client application registration is completed.

  • Print registration status and other information.

  • Set scan parameters to start discovering remote devices.

  • If the setting fails, print the error log.

  1. Connection Event: ESP_GATTC_CONNECT_EVT, triggered when the client successfully establishes a connection with the remote server.

  • Print and save the connection ID and remote device address.

  • Initiate MTU negotiation request.

  • If the initiation fails, print the error log.

Note

MTU negotiation request refers to the exchange of MTU size between the GATT client and server after establishing a connection, to determine the maximum data length of a single transmission. It ensures that the data sent by the client does not exceed the server’s buffering capability, improving transmission efficiency.

  1. Open Connection Event: ESP_GATTC_OPEN_EVT, triggered when the client and server complete the establishment of the underlying link and are ready for GATT access.

  • If the connection status is abnormal, print the error log and exit directly.

  • If the connection is successful, print the current MTU value.

  1. Service Discovery Completion Event: ESP_GATTC_DIS_SRVC_CMPL_EVT, triggered when the client completes the scanning and reading of the server’s specified range of services.

  • If the discovery status is abnormal, print the error log and exit directly.

  • If the discovery is successful, print the connection ID and continue to search for services with a specific UUID.

  1. MTU Configuration Event: ESP_GATTC_CFG_MTU_EVT, triggered after the client and server have negotiated the MTU size.

  • Print the MTU status and current value.

  1. Service Search Result Event: ESP_GATTC_SEARCH_RES_EVT, triggered each time a service is discovered during the service search process.

  • Print the connection ID, whether it is a primary service, and related handles.

  • Determine whether the service UUID is the target service.

  • Save the service start and end handles and mark the target service as found.

  • Print the service UUID.

  1. Service Search Completion Event: ESP_GATTC_SEARCH_CMPL_EVT, triggered after the client has completed scanning and discovering all target services.

  • Check the search status and print the service source.

  • If the target service has been found:

    • Obtain the number of characteristics. If the characteristic count retrieval fails, print an error log.

    • If the characteristic count is greater than 0, dynamically allocate memory to save the characteristic information. If dynamic memory allocation fails, print an error log.

    • After successful memory allocation, Obtain the target characteristic based on the UUID. If the target characteristic retrieval fails, release the memory and return immediately.

    • Check if the characteristic supports notifications. If it does, Register the notification with the server.

    • Successful notification registration will trigger the ESP_GATTC_REG_FOR_NOTIFY_EVT event.

    • Release the temporarily allocated characteristic array.

  1. Notification Registration Event: ESP_GATTC_REG_FOR_NOTIFY_EVT, triggered after the server responds to the client’s registration of a characteristic’s notification or indication.

  • Check the registration status and print the result.

  • Obtain the number of descriptors. If the descriptor count retrieval fails, print an error log and return immediately.

  • If the descriptor count is greater than 0, dynamically allocate memory to save the descriptor information. If dynamic memory allocation fails, print an error log.

  • After successful memory allocation, Obtain the target descriptor based on the Handle. If the target descriptor retrieval fails, release the memory and return immediately.

  • Find the CCCD and Write 1 to enable notifications.

  • Successful descriptor writing will trigger the ESP_GATTC_WRITE_DESCR_EVT event.

  • Release the temporarily allocated descriptor array.

  1. Notification Event: ESP_GATTC_NOTIFY_EVT, triggered when the server actively sends notifications or indications to the client.

  • Determine whether the received data is a notification or an indication.

  • Print the received data.

  1. Descriptor Write Event: ESP_GATTC_WRITE_DESCR_EVT, triggered after the server completes writing and returns a response when the client writes a characteristic descriptor (such as CCCD) to the server.

  • Check the write status.

  • If the descriptor is successfully written, construct a data array and Initiate a write characteristic operation.

  • Successful characteristic writing will trigger the ESP_GATTC_WRITE_CHAR_EVT event.

  1. Service Change Event: ESP_GATTC_SRVC_CHG_EVT, triggered when the client receives a service change notification when the server’s service table changes (such as adding, deleting, or modifying services).

  • Copy the remote device address and print the log.

  1. Characteristic Write Event: ESP_GATTC_WRITE_CHAR_EVT, triggered after the server completes writing and returns a response when the client writes a characteristic value to the server.

  • Check the write status and print the corresponding log.

  1. Disconnection Event: ESP_GATTC_DISCONNECT_EVT, triggered when the client and server disconnect.

  • Clear the connection status flag.

  • Print the disconnection reason and remote address.

  1. Other Events: By default, these are not processed.

esp_gap_cb()

esp_gap_cb() is the GAP callback function of the GATT Client, used to handle events related to Bluetooth low-level broadcasting and scanning. It is the core logic for the Client to establish a connection with the remote device and obtain broadcast information.

static void esp_gap_cb( esp_gap_ble_cb_event_t event,
                        esp_ble_gap_cb_param_t *param );
Parameter Explanation

Input Parameter

Parameter Function

Parameter Description

event

Event type.

Different handling can be performed in the callback based on the event type.

param

A pointer to the structure of the event parameter, containing detailed information about the event.

Different events correspond to different union members, such as broadcast data, scan results, connection information, etc.

  1. Scan Parameter Setting Complete Event: ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT, triggered when the BLE scan parameter setting is completed.

Note

When the scanning duration is 0, it means infinite scanning.

  1. Scan Start Complete Event: ESP_GAP_BLE_SCAN_START_COMPLETE_EVT, triggered when the scanning operation is started.

  • Check the scanning start status and print the corresponding log.

  1. Scan Result Event: ESP_GAP_BLE_SCAN_RESULT_EVT, triggered when a broadcast packet or scan response data is detected.

  • Distinguish the event type according to scan_rst.search_evt:

    • ESP_GAP_SEARCH_INQ_RES_EVT: Triggered when a device is discovered.

      • Get the device broadcast name and print the address and name information.

      • If the CONFIG_EXAMPLE_DUMP_ADV_DATA_AND_SCAN_RESP macro is enabled, print the complete broadcast and scan response data.

      • If the broadcast name matches the target name and is not connected:

        • Change the connection status to true.

        • Stop scanning.

        • Copy the remote device address and construct the connection parameters.

        • Initiate a direct connection, that is, the client actively sends a connection request to a specified single device without waiting for the response of all surrounding devices.

        • The ESP_GATTC_CONNECT_EVT and ESP_GATTC_OPEN_EVT events will be triggered after successfully initiating a direct connection.

    • ESP_GAP_SEARCH_INQ_CMPL_EVT: Scan completed, no processing.

  1. Scan Stop Complete Event: ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT, triggered when the stop scanning operation is completed.

  • Check whether the scanning has been successfully stopped and print the corresponding log.

  1. Broadcast Stop Complete Event: ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT, triggered when the stop broadcasting operation is completed.

  • Check whether the broadcasting has been successfully stopped and print the corresponding log.

  1. Connection Parameter Update Event: ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT, triggered when the connection parameters (such as interval, timeout) are updated.

  • Print the connection parameter status, connection interval, slave delay, and timeout value.

  1. Packet Length Setting Complete Event: ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT, triggered when the setting of the BLE packet length is completed.

  • Print the status and the final received/sent packet length.

  1. Other events: Not processed by default.

esp_gattc_cb()

esp_gattc_cb() is the unified event callback for the GATT Client, which performs different logic according to the event type, such as registration, initiating connection, discovering services and characteristics, reading/writing characteristics, subscribing to notifications, receiving notifications or indications, disconnecting, etc. For detailed explanation, see GATT event callback function.

Main Function Description

This function serves as the program entry function, responsible for completing tasks such as Bluetooth protocol stack initialization, callback registration, and application registration.

This example needs to be executed in conjunction with the GATT Server example to achieve a complete BLE communication process.

The following are the main implementation steps of the function. Given that most of the steps in the process have been detailed in the Main Function Description section of the general steps document, repeated content will not be reiterated here. Only the differences and detailed supplements in this example will be highlighted.

  • Set the target device name for automated connection. This is only executed when the compile configuration CONFIG_EXAMPLE_CI_PIPELINE_ID is enabled.

    • Get the default device name from the example function esp_bluedroid_get_example_name() and copy this name to the buffer for subsequent scanning and matching.

  • When releasing Bluetooth memory, since this example only uses BLE, all memory of the classic Bluetooth part needs to be released.

  • When starting the Bluetooth controller, it needs to be set to BLE mode.

  • After enabling the Bluetooth protocol stack, test Bluetooth stability to verify the system’s stability and memory usage under repeated initialization and shutdown of Bluetooth. This is only executed when the compile configuration CONFIG_EXAMPLE_INIT_DEINIT_LOOP is enabled.

    • Perform the following operations in the EXAMPLE_TEST_COUNT loop:

      • De-initialization: Disable the Bluetooth protocol stack and release resources.

      • Delay 10ms and print the current free heap memory.

      • Initialize and enable the Bluetooth protocol stack.

      • Delay another 10ms to ensure initialization is complete.

    • Return directly after the loop ends.

  • When registering callback functions:

    • Pass in gatts_event_handler to register the GATT Server callback function.

    • Pass in gap_event_handler to register the GAP callback function.

  • Set the local GATT MTU to 500 bytes.

Program Execution Logic

After adding log printing in each program event, the following execution results can be seen when burning and running:

I (371) Step: 1 Initialize NVS
I (401) Step: 2 Release mem
I (401) Step: 3 Initialize controller
I (411) Step: 4 Enable controller
I (491) Step: 5 Initialize bluedroid
I (501) Step: 6 Enable bluedroid
I (521) Step: 7 Register gap event handler
I (521) Step: 8 Register gattc event handler
I (521) Step: 9 Register gattc app
I (521) Step: 10 ESP_GATTC_REG_EVT in esp_gattc_cb
I (521) Step: 11 Event Dispatch
I (521) Step: 12 ESP_GATTC_REG_EVT
I (531) Step: 13 ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT
I (541) Step: 14 ESP_GAP_BLE_SCAN_START_COMPLETE_EVT
I (551) Step: 15 Set MTU
I (551) Step: 16 ESP_GAP_BLE_SCAN_RESULT_EVT
I (561) Step: 17 ESP_GAP_SEARCH_INQ_RES_EVT
I (571) Step: 18 ESP_GAP_BLE_SCAN_RESULT_EVT
I (571) Step: 19 ESP_GAP_SEARCH_INQ_RES_EVT
I (581) Step: 20 ESP_GAP_BLE_SCAN_RESULT_EVT
I (581) Step: 21 ESP_GAP_SEARCH_INQ_RES_EVT
I (591) Step: 22 ESP_GAP_BLE_SCAN_RESULT_EVT
I (601) Step: 23 ESP_GAP_SEARCH_INQ_RES_EVT
I (611) Step: 24 ESP_GAP_BLE_SCAN_RESULT_EVT
I (611) Step: 25 ESP_GAP_SEARCH_INQ_RES_EVT
I (621) Step: 26 ESP_GAP_BLE_SCAN_RESULT_EVT
I (631) Step: 27 ESP_GAP_SEARCH_INQ_RES_EVT
I (641) Step: 28 ESP_GAP_BLE_SCAN_RESULT_EVT
I (641) Step: 29 ESP_GAP_SEARCH_INQ_RES_EVT
I (651) Step: 30 ESP_GAP_BLE_SCAN_RESULT_EVT
I (651) Step: 31 ESP_GAP_SEARCH_INQ_RES_EVT
I (681) Step: 32 ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT
I (691) Step: 33 ESP_GAP_BLE_SCAN_RESULT_EVT
I (691) Step: 34 ESP_GAP_SEARCH_INQ_RES_EVT
I (701) Step: 35 ESP_GAP_BLE_SCAN_RESULT_EVT
I (701) Step: 36 ESP_GAP_SEARCH_INQ_RES_EVT
I (721) Step: 37 ESP_GAP_BLE_SCAN_RESULT_EVT
I (721) Step: 38 ESP_GAP_SEARCH_INQ_RES_EVT
I (801) Step: 39 Event Dispatch
I (801) Step: 40 ESP_GATTC_CONNECT_EVT
I (801) Step: 41 Event Dispatch
I (811) Step: 42 ESP_GATTC_OPEN_EVT
I (831) Step: 43 ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT
I (1121) Step: 44 ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT
I (1841) Step: 45 Event Dispatch
I (1841) Step: 46 ESP_GATTC_DIS_SRVC_CMPL_EVT
I (1841) Step: 47 Event Dispatch
I (1851) Step: 48 ESP_GATTC_SEARCH_RES_EVT
I (1871) Step: 49 Event Dispatch
I (1871) Step: 50 ESP_GATTC_SEARCH_CMPL_EVT
I (1891) Step: 51 Event Dispatch
I (1891) Step: 52 ESP_GATTC_REG_FOR_NOTIFY_EVT
I (1921) Step: 53 Event Dispatch
I (1921) Step: 54 ESP_GATTC_CFG_MTU_EVT
I (2081) Step: 55 Event Dispatch
I (2081) Step: 56 ESP_GATTC_NOTIFY_EVT
I (2091) Step: 57 Event Dispatch
I (2091) Step: 58 ESP_GATTC_WRITE_DESCR_EVT
I (2161) Step: 59 Event Dispatch
I (2161) Step: 60 ESP_GATTC_WRITE_CHAR_EVT

The execution logic of the program can be summarized into the following stages:

  1. Initialization stage: The program initializes non-volatile storage (NVS), releases memory, initializes and enables the Bluetooth controller, and then initializes and enables the Bluedroid protocol stack.

  2. Register callbacks and applications: Register the event callback functions of GATT Client and GAP, and then register the GATT application.

  3. Application registration completed: After the application is successfully registered, the ESP_GATTS_REG_EVT is triggered, indicating that the GATT Server application has been successfully registered in the protocol stack. At this time, the event enters the unified event callback gattc_profile_event_handler(), which is dispatched by the event dispatcher to the specific Profile callback, where the broadcast and scan response data write operations are started.

  4. Setting and starting of scan parameters: After the scan parameters are set, the ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT is triggered. After the scan is started, the ESP_GAP_BLE_SCAN_START_COMPLETE_EVT is triggered, and the client begins to scan the surrounding broadcast devices.

  5. MTU setting: Configure the MTU to optimize transmission efficiency.

  6. Scanning and discovering devices: During the scanning process, the ESP_GAP_BLE_SCAN_RESULT_EVT and ESP_GAP_SEARCH_INQ_RES_EVT are triggered multiple times, indicating that devices are discovered and broadcast information is obtained.

  7. Scan stop: After the scan is completed, the ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT is triggered to stop the scanning operation.

  8. Initiate connection: The client initiates a connection based on the target device information, triggers the ESP_GATTC_CONNECT_EVT to establish a physical connection, and then triggers the ESP_GATTC_OPEN_EVT to confirm the connection establishment.

  9. Packet length setting: After the BLE packet length is set, the ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT is triggered.

  10. Connection parameter update: After the connection is established, the connection interval, timeout, and other parameters may be updated to optimize communication. The parameter update is completed by triggering the ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT.

  11. Service discovery completed: The client receives the ESP_GATTC_DIS_SRVC_CMPL_EVT indicating that the service discovery is completed, then triggers the ESP_GATTC_SEARCH_RES_EVT to obtain specific service information, and finally triggers the ESP_GATTC_SEARCH_CMPL_EVT to indicate that the service search is completed.

  12. Register notification: After the client registers the feature notification, the ESP_GATTC_REG_FOR_NOTIFY_EVT is triggered, indicating that it can receive notifications sent by the server.

  13. MTU negotiation completed: After the client and server negotiate the MTU, the ESP_GATTC_CFG_MTU_EVT is triggered to determine the maximum transmission unit size of both parties.

  14. Receive notification data: When the server sends data notifications, the ESP_GATTC_NOTIFY_EVT is triggered, and the client can read and process the data.

  15. Write descriptor: After the client writes data to the feature descriptor (such as enabling notifications), the ESP_GATTC_WRITE_DESCR_EVT is triggered, and the server confirms the successful write.

  16. Write feature value: After the client writes data to the feature, the ESP_GATTC_WRITE_CHAR_EVT is triggered, and the server receives and processes the write operation.

Precautions:

  • MTU negotiation determines the maximum length of a transmission. Different devices support different values. If the negotiation fails, it will fall back to the default value.

  • Scan stop is an asynchronous operation. The controller processing data in the buffer will still generate events, so there may be scan results printed after the scan stops.