GATT Server Service Table 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 create a GATT Server on ESP32, register a service table, configure features and their attributes, and allow clients to read, write, and subscribe to notifications. Through this example, you can learn how to define services and features, handle client requests, and implement real-time data updates and notification mechanisms.

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

The example is suitable for data collection, status reporting, or control command reception scenarios of BLE peripherals, such as smart bracelets, Bluetooth speakers, or various sensor devices. Developers can quickly master the basic implementation method of GATT Server through this example, providing a reference for building custom BLE services.

Running Method

The complete code of the example can be found at gatt server service table example. The configuration instructions, build and burn 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 function 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, task management, and event group synchronization mechanisms.

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

#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.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"
#include "esp_bt_device.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 Server functions and the GATT attribute table definition file for this example, used to centrally define services, feature values, and descriptors, and configure their UUID, permissions, and attributes to handle client read and write requests.

#include "esp_gatts_api.h"
#include "gatts_table_creat_demo.h"
#include "esp_gatt_common_api.h"

Macro Definition Description

The macro definitions involved in this example are mainly used to configure BLE GATT Profile, service instances, feature values, and broadcast parameters, which are convenient for unified management in multi-Profile scenarios, reduce hard coding, and improve code maintainability.

The macro definitions are categorized by function as follows:

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

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

#define PROFILE_NUM                 1
  • Set the index of each Profile in the Profile array for accessing the data structure of the specified Profile, avoiding hard-coded index values. When there are multiple Profiles, an index value should be set for each Profile, starting from 0 and increasing without repetition.

#define PROFILE_APP_IDX             0

Note

Hard coding refers to writing values directly into the code, which requires changes in multiple places when modifying, and is easy to miss. Using macro definitions or constants instead allows you to change only one place to affect all references, such as device names, UUIDs, etc.

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

#define ESP_APP_ID                  0x55
  • Set the service instance ID. When there are multiple services with the same UUID in the same Profile, an instance ID should be set for each service, starting from 0 and increasing without repetition, to distinguish instances.

#define SVC_INST_ID                 0
  1. Characteristic Values and Buffer: Used to limit the size of characteristic values and buffer capacity to prevent data from exceeding memory range.

  • Set the maximum length of characteristic values to prevent the data length written by the client from exceeding the buffer and causing overflow. In this example, it is set to 500 bytes.

#define GATTS_DEMO_CHAR_VAL_LEN_MAX 500
  • Set the maximum length of the pre-write buffer for limiting the buffer of the “Prepare Write” operation to avoid excessive memory usage.

#define PREPARE_BUF_MAX_SIZE        1024
  • Indicate the number of bytes occupied by the characteristic declaration, used for memory allocation when building the GATT attribute table.

#define CHAR_DECLARATION_SIZE       (sizeof(uint8_t))
  1. Broadcast Related Configuration: Used to set the device name and the configuration status flags of broadcast/scan response.

  • Set the device name to ESP_GATTS_DEMO, used to identify the device in the broadcast, avoiding hard-coding strings directly in the code.

#define SAMPLE_DEVICE_NAME          "ESP_GATTS_DEMO"
  • Define the broadcast data configuration status flag bit. When this bit is set to 1, it indicates that the broadcast data configuration is complete, preventing the broadcast from starting before the configuration is complete.

#define ADV_CONFIG_FLAG             (1 << 0)
  • Define the scan response data configuration status flag bit. When this bit is set to 1, it indicates that the scan response data configuration is complete, preventing the scan from starting before the configuration is complete.

#define SCAN_RSP_CONFIG_FLAG        (1 << 1)

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. Broadcast Configuration:

  1. GATT Profile and Connection Information:

  • heart_rate_handle_table[HRS_IDX_NB]: Used to store the handles of various characteristics and descriptors in the GATT database for subsequent read and write operations. This example only includes one profile. If there are multiple profiles, a handle table needs to be defined for each profile. Here, HRS_IDX_NB is a macro or enumeration value, representing the total number of GATT attributes (services, characteristics, descriptors) in the service.

  • prepare_type_env_t / prepare_write_env: A prepare_type_env_t structure is defined to buffer the fragment data of prepare write operation, and the fragments and their lengths currently received by the GATT server are stored through the prepare_write_env instance.

  • gatts_profile_inst: Create a GATT Server instance structure.

  • heart_rate_profile_tab: An array of gatts_profile_inst structure types, each array element represents a Server Profile instance. This example only includes one Profile. If there are multiple Profiles, they need to be configured one by one as needed. The example only defines two structure members for event callbacks and interface management. The remaining members are not used in this example, so they are omitted when defining the structure.

  1. UUID and Attribute Configuration

UUID and Flag Bits

Function

Declaration

Custom service UUID, used to identify service content.

GATTS_SERVICE_UUID_TEST

Custom characteristic UUID, used to identify characteristic content.

GATTS_CHAR_UUID_TEST_A

GATTS_CHAR_UUID_TEST_B

GATTS_CHAR_UUID_TEST_C

Primary service declaration UUID, identifies attribute type.

primary_service_uuid

Characteristic declaration UUID, identifies attribute type.

character_declaration_uuid

Client characteristic configuration UUID, identifies attribute type.

character_client_config_uuid

Attribute flag bits of the characteristic (read, write, and notify)

char_prop_read

char_prop_write

char_prop_read_write_notify

Initial value of CCC Descriptor, 2 bytes, 0x0000 represents disabling notification

heart_measurement_ccc

Initial content of the characteristic value

char_value

Note

In the BLE (Bluetooth Low Energy) protocol, UUID (Universal Unique Identifier) is used to distinguish services and characteristics.

Typically, an attribute contains two types of UUIDs, where the declaration UUID identifies the type, and the specific UUID identifies the content. Taking the service UUID as an example:

  • Primary service UUID: Uniquely identifies a specific service type.

  • Service declaration UUID: Identifies “this is a service declaration”, which is a meta-information in the GATT protocol.

Characteristics and descriptors also follow the same logic.

  1. GATT Attribute Table Definition: Defines the attribute table for centralized storage of services, characteristics, and descriptors, facilitating unified management and access.

  • The attribute index is defined in the example header file gatts_table_creat_demo.h for easy unified management and access.

  • The gatt_db[] array is used to define all GATT attributes, including service declarations, characteristic declarations, characteristic values, and optional descriptors.

  • The attributes defined in the example are as follows:

    • Primary service: Heart rate measurement service.

    • Characteristic A: Used for heart rate values or control commands.

    • Descriptor of Characteristic A: Client Characteristic Configuration Descriptor (CCC), used to control the notification switch of Characteristic A.

    • Characteristic B: Used to provide static information or configuration parameters.

    • Characteristic C: Used to receive client commands or data.

Function Description

gap_event_handler()

gap_event_handler() is a BLE GAP event callback function, used to handle broadcast, scan response, and connection parameter update events.

Note

Event callback functions are functions used to “receive notifications” during program execution. They are automatically called when specific events occur, allowing the program to respond and handle these events in a timely manner.

static void gap_event_handler( 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, such as broadcast start, scan completion, connection parameter update, etc.

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

param

Pointer to the structure of 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 based on different events:

Event

event Setting

Trigger Timing

Execution Logic

Broadcast data setting completed

ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT

Triggered after the broadcast data configuration is completed.

Clear the broadcast data configuration completion flag. After successful clearance, start broadcasting.

Scan response data setting completed.

ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT

Triggered after the scan response data configuration is completed.

Clear the scan response configuration completion flag. After successful clearance, start broadcasting.

Broadcast start completed

ESP_GAP_BLE_ADV_START_COMPLETE_EVT

Triggered after the broadcast is successfully started.

Check the broadcast status and output corresponding logs, indicating whether the start was successful or not.

Broadcast stop

ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT

Triggered after the broadcast is successfully stopped.

Check the broadcast status and output corresponding logs, indicating whether the stop was successful or not.

Update connection parameters

ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT

Triggered when the connection parameters are negotiated by both parties after the connection is established.

Output logs, including status, connection interval, delay, and timeout time.

Note

The broadcast data packet configuration method will be judged by conditional compilation before starting the broadcast, and the corresponding correct data packet will be sent.

example_prepare_write_event_env()

example_prepare_write_event_env() is used to handle the BLE GATT’s Prepare Write event, that is, the server’s processing logic for each segment of data when the client writes the characteristic value in segments.

void example_prepare_write_event_env( esp_gatt_if_t gatts_if,
                                      prepare_type_env_t *prepare_write_env,
                                      esp_ble_gatts_cb_param_t *param );
Parameter explanation

Input parameter

Parameter function

Parameter description

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.

prepare_write_env

Points to the structure that saves the segmented write data.

Stores the Prepare Write buffer and the written length.

param

Points to the structure pointer of the event parameter, which contains detailed information about this event.

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

The function execution logic is as follows:

  1. Print logs, display the currently written attribute handle and the length of the written data.

  2. Initialize the write status to success.

  3. Check whether the write offset and total length are legal:

  • If the offset is greater than the maximum value of the buffer, modify the write status to ESP_GATT_INVALID_OFFSET, which represents an invalid offset.

  • If the offset and data length exceed the maximum value of the buffer, modify the write status to ESP_GATT_INVALID_ATTR_LEN, which represents an invalid attribute length.

  1. If the write status is normal and the buffer has not been allocated:

  • Dynamically allocate memory, the size is PREPARE_BUF_MAX_SIZE, used to store the segmented write data.

  • Initialize the written length to 0.

  • If memory allocation fails, print logs and record error status.

  1. Determine whether the client needs to write a response, if needed:

  • Dynamically apply for memory, allocate a GATT response structure, used to send write responses. For specific structure members, please refer to GATT API.

  • After the memory application is successful, fill in the response structure information and send the response:

    • Write data length, handle, offset.

    • Set the authorization request to none (no authentication required), that is, the client can directly write data without pairing or encryption verification.

    • Copy the data sent by the client to the response structure.

    • Send a write response to the client send write response, and release the allocated structure memory.

    • If the sending fails, print error logs.

  • If the memory application fails, print error logs and record error status.

Note

The write response is a confirmation message from the server to the client’s write operation, used to tell the client whether the data has been successfully received and processed.

  1. If the write status is abnormal, return directly without performing cache write.

  2. Copy the data written by the client to the corresponding offset position of the Prepare Write buffer, and update the length of the written data.

example_exec_write_event_env()

example_exec_write_event_env() is used to handle the Execute Write event of BLE GATT, that is, after the client completes the segmented write, the server decides whether to actually write or cancel the write according to the client’s instructions.

void example_exec_write_event_env( prepare_type_env_t *prepare_write_env,
                                   esp_ble_gatts_cb_param_t *param );
Parameter Explanation

Input Parameter

Parameter Function

Parameter Description

prepare_write_env

Points to the structure that saves the segmented write data.

Stores the Prepare Write buffer and the written length.

param

Points to the structure pointer 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.

The function execution logic is as follows:

  1. Determine whether the client sends the execute write command and whether there is data in the buffer.

  • If the write needs to be executed and the buffer is not empty, print all the write data stored in the buffer.

  • If the client cancels the write or the buffer is empty, print a log indicating that Prepare Write has been cancelled.

Note

The example only performs log output, in actual applications, you can handle write logic here, such as writing to non-volatile storage or updating characteristic values.

  1. If the buffer exists, release the dynamically allocated memory to avoid memory leaks, and set the pointer to null to prevent the use of dangling pointers.

  2. Reset the written length to 0 to prepare for the next segmented write.

gatts_profile_event_handler()

gatts_profile_event_handler() is the callback processing function of the GATT Server Profile, used to handle various events generated by the BLE GATT Server during operation, such as registration, read and write requests, connection, disconnection, etc.

static void gatts_profile_event_handler( esp_gatts_cb_event_t event,
                                         esp_gatt_if_t gatts_if,
                                         esp_ble_gatts_cb_param_t *param );
Parameter Explanation

Input Parameter

Parameter Function

Parameter Description

event

Event type, such as registration, read and write, connection, disconnection, etc.

Different treatments can be carried out 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

Points to the structure pointer 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_GATTS_REG_EVT, triggered when the application registration is completed.

  • Set the name of the local Bluetooth device, if the setting fails, print an error log.

  • Choose the packet configuration mode according to conditional compilation, set the broadcast data and scan response data, and mark the configuration as completed.

  • Create GATT Attribute Table. If creation fails, print an error log.

  • After the GATT attribute table is created, the ESP_GATTS_CREAT_ATTR_TAB_EVT event will be triggered.

  1. Read Event: ESP_GATTS_READ_EVT, triggered when the client initiates a read request for a certain characteristic.

  • Print a log to show that this event is triggered.

  1. Write Event: ESP_GATTS_WRITE_EVT, triggered when the client writes to a certain characteristic.

  • If it is not a Prepare Write:

    • Print the write handle, data length, and data content.

    • Determine whether the written attribute is CCC and whether it corresponds to the current feature. If so, determine the operation to be performed based on the Written Value.

    • If the client needs a response, Send Write Response.

  • If it is a Prepare Write, call the task function example_prepare_write_event_env() to handle Segmented Write.

  1. Execute Prepare Write Event: ESP_GATTS_EXEC_WRITE_EVT, triggered when the client initiates an “execute write” operation (after prepare write).

  1. MTU Update Event: ESP_GATTS_MTU_EVT, triggered after the server or client modifies the MTU value (including negotiation completion).

  • Print the new MTU value.

  1. Server Confirmation Event: ESP_GATTS_CONF_EVT, triggered after the server sends a response or notification/indication to the client.

  • Print the confirmation status and attribute handle.

  1. Service Start Completion Event: ESP_GATTS_START_EVT, triggered when the service starts successfully.

  • Print the start status and service handle.

  1. Client Connection Event: ESP_GATTS_CONNECT_EVT, triggered when the client connects to the GATT Server.

  • Print the connection ID and client Bluetooth address.

  • Initialize the connection parameters and copy the client’s Bluetooth address.

  • Set the connection parameters: connection delay, connection interval, and connection timeout.

  • Request the client to Update the connection parameters.

  1. Client Disconnection Event: ESP_GATTS_DISCONNECT_EVT, triggered when the client disconnects.

  1. Attribute Table Creation Completion Event: ESP_GATTS_CREAT_ATTR_TAB_EVT, triggered when the attribute table is created.

  • Determine the attribute table creation status:

    • If creation fails, print an error log.

    • If the number of handles created is abnormal, print a warning.

    • Otherwise, print a successful creation log, copy the returned handle array to heart_rate_handle_table, and finally Start the Service.

  • After the service starts successfully, the ESP_GATTS_START_EVT event will be triggered.

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

gatts_event_handler()

gatts_event_handler() is the callback processing function for the GATT Server’s main entry point, used for service registration, handling read and write requests, sending notifications or indications, connecting and disconnecting events. For detailed explanation, see GATT Event Callback Function.

Main Function Description

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

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 additional details in this example will be emphasized.

  • When releasing Bluetooth memory, since this example only uses BLE, it is necessary to completely release the memory of the classic Bluetooth part.

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

  • 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 event of the program, the following execution results can be seen after burning and running:

I (369) Steps: 1 Initialize NVS
I (399) Steps: 2 Release mem
I (399) Steps: 3 Initialize controller
I (409) Steps: 4 Enable controller
I (459) Steps: 5 Initialize bluedroid
I (469) Steps: 6 Enable bluedroid
I (479) Steps: 7 Register gatts event handler
I (479) Steps: 8 Register gap event handler
I (479) Steps: 9 Register gatts app
I (479) Steps: 10 ESP_GATTS_REG_EVT in gatts_event_handler
I (489) Steps: 11 Event Dispatch
I (489) Steps: 12 ESP_GATTS_REG_EVT
I (499) Steps: 13 Event Dispatch
I (499) Steps: 14 ESP_GATTS_CREAT_ATTR_TAB_EVT
I (509) Steps: 15 ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT
I (509) Steps: 16 ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT
I (519) Steps: 17 Event Dispatch
I (519) Steps: 18 ESP_GATTS_START_EVT
I (529) Steps: 19 ESP_GAP_BLE_ADV_START_COMPLETE_EVT
I (539) Steps: 20 Set MTU
I (1389) Step: 21 Event Dispatch
I (1389) Step: 22 ESP_GATTS_CONNECT_EVT
I (1689) Step: 23 ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT
I (2449) Step: 24 Event Dispatch
I (2449) Step: 25 ESP_GATTS_MTU_EVT
I (2609) Step: 26 Event Dispatch
I (2609) Step: 27 ESP_GATTS_WRITE_EVT
I (2619) Step: 28 Event Dispatch
I (2619) Step: 29 ESP_GATTS_CONF_EVT
I (2689) Step: 30 Event Dispatch
I (2689) Step: 31 ESP_GATTS_WRITE_EVT

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

  1. Initialization phase: The program sequentially 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 Server and GAP, and then register the GATT application.

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

  4. Create attribute table: After the protocol stack creates the service and feature attribute table, it triggers ESP_GATTS_CREAT_ATTR_TAB_EVT, and the service is started in this event callback.

  5. Prepare broadcast data: The completion of writing broadcast data and scan response data respectively triggers ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT and ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT to start broadcasting.

  6. Service startup completed: After the service is started, it triggers ESP_GATTS_START_EVT.

  7. Broadcast startup completed: After the broadcast is started, it triggers ESP_GAP_BLE_ADV_START_COMPLETE_EVT, at which point the device can be scanned and connected by the client.

  8. MTU setting: Perform MTU configuration to optimize transmission efficiency.

  9. Establish connection: After the client initiates a connection, it triggers ESP_GATTS_CONNECT_EVT, indicating that the GATT Server and Client have established a physical connection, and subsequent parameter updates and data exchanges can be performed.

  10. Connection parameter update: After the connection is established, both parties may update the connection parameters (such as connection interval, timeout time) to optimize communication stability and power consumption. After the parameter update is completed, it triggers ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT.

  11. MTU negotiation completed: After the Server receives the MTU request from the Client, the negotiation is completed, determining the maximum length of the data packets transmitted by both parties. After the negotiation is completed, it triggers ESP_GATTS_MTU_EVT.

  12. Client writes data: When the Client writes data to a feature, it triggers ESP_GATTS_WRITE_EVT. The Server receives and processes the written content in this event.

  13. Write confirmation: If the write operation requires a response, the Server will notify the confirmation result by triggering ESP_GATTS_CONF_EVT after processing, indicating that the write operation has been completed.

  14. Further interaction: Trigger ESP_GATTS_WRITE_EVT again, indicating that the client continues to write data, and the Server continues to receive and process.

Precautions:

  • Before executing GATTS events, you need to register the event callback and start the event dispatcher first to ensure that the event can be correctly delivered to the corresponding Profile callback function.

  • Service startup, broadcasting, and scanning are asynchronous operations. The order of event triggering reflects the processing flow of the protocol stack, but there may be slight differences in different devices or configurations.