GATT Client 示例
示例说明
本示例展示如何在 ESP32 上实现 GATT Client,完成设备扫描、连接、服务搜索以及特征读写与通知订阅等操作。通过该示例,可以学习如何发现远端设备的服务与特征、发起读写请求,以及接收来自服务端的通知或指示,从而实现与外设的数据交互。
该示例需要与 GATT Server 示例配合执行,以实现完整的 BLE 通信流程。
示例适用于需要作为 BLE 中心设备的场景,如手机配件、数据采集网关或远程控制终端。开发者可通过该示例快速掌握 GATT Client 的基本实现方法,为构建自定义 BLE 客户端应用提供参考。
运行方法
示例完整代码见 gatt client 示例。运行前的配置说明、构建与烧录流程详见示例目录下的 README.md 文件。
如需自定义配置项及查看默认值,可参考文档中的 宏定义说明 部分。
头文件说明
本示例所使用的头文件涵盖了 FreeRTOS 任务管理、系统工具模块、蓝牙控制器与协议栈接口以及 BLE GATT 服务相关 API 等功能模块,构建了 BLE 初始化、Profile 管理、事件处理与数据交互的核心功能。
各头文件按功能分类如下:
FreeRTOS:提供基本任务调度和任务管理机制。
#include "freertos/FreeRTOS.h"
系统工具:用于系统初始化、日志打印以及非易失性存储的初始化。
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include "nvs.h"
#include "nvs_flash.h"
#include "esp_log.h"
蓝牙协议栈:用于管理蓝牙控制器的初始化与释放、启停协议栈,以及获取本地蓝牙设备信息。
#include "esp_bt.h"
#include "esp_bt_main.h"
BLE GAP 接口:负责广播、扫描等设备发现与连接管理功能。
#include "esp_gap_ble_api.h"
BLE GATT 接口:提供 GATT client 功能,处理对远端设备服务、特征和描述符的发现与读写操作。
#include "esp_gattc_api.h"
#include "esp_gatt_defs.h"
#include "esp_gatt_common_api.h"
宏定义说明
本示例中涉及的宏定义主要用于配置 BLE GATT Client 的 Profile、远端服务和特征 UUID,以及调试标签和测试循环次数,方便在多 Profile 或多设备场景下统一管理、减少硬编码,并提高代码可维护性。
宏定义按功能分类如下:
GATT Profile 基本参数:用于统一管理 Profile 数量、索引、应用 ID 及服务实例 ID。
定义 GATT Profile 的数量,方便在多 Profile 应用中统一管理数量和索引。此示例中配置为 1。
#define PROFILE_NUM 1
设置应用 ID,用于区分不同 Profile,方便协议栈管理。此示例中设置为 0。当存在多个 Profile 时,应为每个 Profile 设定一个应用 ID,取值可为任意不重复的整数或十六进制数。
#define PROFILE_A_APP_ID 0
UUID:用于指定要访问的远端服务和通知特征,客户端通过 UUID 发现服务并订阅通知。
定义远端服务 UUID,用于在扫描和连接后发现指定服务。
#define REMOTE_SERVICE_UUID 0x00FF
定义远端通知特征 UUID,用于订阅和接收服务器端发送的通知或指示。
#define REMOTE_NOTIFY_CHAR_UUID 0xFF01
客户端调试与句柄管理:用于日志打印标识和无效句柄表示。
设置日志打印标签,方便调试输出定位到 GATT Client 示例。
#define GATTC_TAG "GATTC_DEMO"
定义无效句柄值,用于初始化句柄或判断句柄是否有效。
#define INVALID_HANDLE 0
测试循环配置:用于配置初始化与反复启停测试的次数。
当配置项
CONFIG_EXAMPLE_INIT_DEINIT_LOOP
被启用时,定义循环次数,用于测试初始化和反初始化稳定性。#if CONFIG_EXAMPLE_INIT_DEINIT_LOOP #define EXAMPLE_TEST_COUNT 50 #endif备注
反复启停测试是指在程序中多次循环执行蓝牙协议栈的初始化与反初始化过程,用于验证在频繁启用和关闭蓝牙功能时,系统是否会出现内存泄漏、资源未释放或崩溃等问题,从而确保协议栈的稳定性和可靠性。
全局变量说明
本示例中定义的全局变量主要用于管理 BLE 广播配置、GATT Profile 实例、连接状态以及属性数据等核心信息。通过这些全局变量,示例能够在不同回调函数之间共享状态,处理客户端请求,并维护 GATT 服务的完整性。
其中部分内容已在 通用步骤 文档中详细介绍,对于重复内容此处不再赘述,仅重点说明本示例中的关键实现与差异部分。
设备信息与状态标志:
remote_device_name[]
:保存目标远端设备的名称,用于扫描时匹配设备。
connect
:标记当前是否已建立连接,初始化为未连接。
get_server
:标记是否已发现目标服务,初始化为服务未发现。
GATT 属性元素缓存指针:
char_elem_result
:指向远端设备 GATT 特征元素的缓存,用于保存特征发现结果。
descr_elem_result
:指向远端设备 GATT 描述符元素的缓存,用于保存描述符发现结果。
目标 UUID 定义:通过
esp_bt_uuid_t
结构体定义。结构体成员可参考 Bluetooth Defines。
remote_filter_service_uuid
:目标远端服务的 UUID,用于服务发现过滤。
remote_filter_char_uuid
:目标远端通知特征的 UUID,用于特征发现过滤。
notify_descr_uuid
:客户端配置描述符(CCCD)的 UUID,用于启用/禁用特征通知。
扫描配置:
ble_scan_params
:配置 扫描参数。
gattc_profile_inst
:创建 GATT Client 实例结构体。
gl_profile_tab
:gattc_profile_inst
结构体类型的数组,每个数组元素表示一个 Client Profile 实例。本实例仅包含一个 Profile,若存在多个 Profile,需要按需一一进行配置。示例中只定义了两个结构体成员,用于事件回调和接口管理,其余成员在本示例中未使用,因此结构体定义时省略。
功能函数说明
gattc_profile_event_handler()
gattc_profile_event_handler()
是 GATT Client Profile 的回调处理函数,用于处理 BLE GATT Client 在运行过程中产生的各种事件,例如连接、服务发现、特征获取、通知订阅、读写操作等。
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 );
传入参数 |
参数功能 |
参数说明 |
---|---|---|
|
事件类型,例如广播开始、扫描完成、连接参数更新等。 |
可根据事件类型在回调中进行不同处理。 |
|
GATT 服务器访问接口。 |
由协议栈分配,用于区分不同的 GATT Profile 服务实例,在事件回调中识别对应的 Profile。 |
|
指向事件参数的结构体指针,包含该事件的详细信息。 |
不同事件对应不同的联合体成员,例如广播数据、扫描结果、连接信息等。 |
该函数根据不同事件类型执行对应的处理逻辑:
注册事件:
ESP_GATTC_REG_EVT
,客户端应用注册完成时触发。
打印注册状态等信息。
设置 扫描参数以开始发现远端设备。
如果设置失败,打印错误日志。
连接事件:
ESP_GATTC_CONNECT_EVT
,在客户端成功与远程服务器建立连接后触发。
打印并保存连接 ID 和远端设备地址。
发起 MTU 协商 请求。
如果发起失败,打印错误日志。
备注
MTU 协商请求指 GATT 客户端和服务端在建立连接后交换 MTU 大小,以确定最大单次传输的数据长度。保证客户端发送的数据不会超过服务端缓冲能力,提高传输效率。
打开连接事件:
ESP_GATTC_OPEN_EVT
,客户端与服务器完成底层链路建立并准备好 GATT 访问的状态时触发。
如果连接状态异常,打印错误日志,并直接退出。
如果成功连接,打印当前 MTU 值。
服务发现完成事件:
ESP_GATTC_DIS_SRVC_CMPL_EVT
,客户端完成对服务器指定范围内服务的扫描与读取时触发。
如果发现状态异常,打印错误日志,并直接退出。
如果发现成功,打印连接 ID,并继续 搜索 特定 UUID 的服务。
MTU 配置事件:
ESP_GATTC_CFG_MTU_EVT
,客户端与服务器完成 MTU 大小协商后触发。
打印 MTU 状态和当前的值。
服务搜索结果事件:
ESP_GATTC_SEARCH_RES_EVT
,在服务搜索过程中,每发现一个服务时触发。
打印连接 ID、是否为主服务以及相关句柄。
判断服务 UUID 是否为目标服务。
保存服务起止句柄并标记目标服务已找到。
打印服务 UUID。
服务搜索完成事件:
ESP_GATTC_SEARCH_CMPL_EVT
,客户端完成对所有目标服务的扫描与发现后触发。
注册通知事件:
ESP_GATTC_REG_FOR_NOTIFY_EVT
,当客户端向服务器注册某个特征的通知或指示时,在服务器响应注册请求后触发。
通知事件:
ESP_GATTC_NOTIFY_EVT
,当服务器主动发送通知或指示给客户端时触发。
判断收到的是通知还是指示。
打印接收到的数据。
描述符写入事件:
ESP_GATTC_WRITE_DESCR_EVT
,当客户端向服务器写入特征描述符(如 CCCD)后,服务器完成写入并返回响应时触发。
检查写入状态。
如果描述符成功写入,构造数据数组并 发起写特征 操作。
特征成功写入后会触发
ESP_GATTC_WRITE_CHAR_EVT
事件。
服务变更事件:
ESP_GATTC_SRVC_CHG_EVT
,当服务器的服务表发生变化(如添加、删除或修改服务)时,客户端收到服务变化通知时触发。
复制远端设备地址并打印日志。
特征写入事件:
ESP_GATTC_WRITE_CHAR_EVT
,当客户端向服务器写入特征值后,服务器完成写入并返回响应时触发。
检查写入状态并打印对应日志。
断开事件:
ESP_GATTC_DISCONNECT_EVT
,当客户端与服务器断开连接时触发。
清除连接状态标志。
打印断开原因和远端地址。
其他事件:默认不处理。
esp_gap_cb()
esp_gap_cb()
是 GATT Client 的 GAP 回调函数,用于处理蓝牙底层广播与扫描相关的事件,是 Client 端与远端设备建立连接和获取广播信息的核心逻辑。
static void esp_gap_cb( esp_gap_ble_cb_event_t event,
esp_ble_gap_cb_param_t *param );
传入参数 |
参数功能 |
参数说明 |
---|---|---|
|
事件类型。 |
可根据事件类型在回调中进行不同处理。 |
|
指向事件参数的结构体指针,包含该事件的详细信息。 |
不同事件对应不同的联合体成员,例如广播数据、扫描结果、连接信息等。 |
扫描参数设置完成事件:
ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT
,当 BLE 扫描参数设置完成后触发。
开始扫描,扫描持续时间为 30 秒。
备注
当扫描持续时间为 0 时表示无限扫描。
扫描启动完成事件:
ESP_GAP_BLE_SCAN_START_COMPLETE_EVT
,当扫描操作启动完成后触发。
检查扫描启动状态,并打印对应日志。
扫描结果事件:
ESP_GAP_BLE_SCAN_RESULT_EVT
,当扫描到一个广播包或扫描响应数据时触发。
根据
scan_rst.search_evt
区分事件类型:
ESP_GAP_SEARCH_INQ_RES_EVT
:发现一个设备时触发。
ESP_GAP_SEARCH_INQ_CMPL_EVT
:扫描完成,无处理。
扫描停止完成事件:
ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT
,当停止扫描操作完成后触发。
检查是否成功停止扫描并打印相应日志。
广播停止完成事件:
ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT
,当停止广播操作完成后触发。
检查是否成功停止广播并打印相应日志。
连接参数更新事件:
ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT
,当连接参数(如间隔、超时)更新完成后触发。
打印连接参数状态、连接间隔、从属延迟和超时值。
包长度设置完成事件:
ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT
,当设置 BLE 数据包长度完成后触发。
打印状态及最终接收/发送包长度。
其他事件:默认不处理。
esp_gattc_cb()
esp_gattc_cb()
是 GATT Client 的统一事件回调,根据事件类型执行不同逻辑,例如注册、发起连接、发现服务和特征、读取/写入特征、订阅通知、接收通知或指示、断开连接等。详细说明见 GATT 事件回调函数。
主函数说明
该函数作为程序入口函数,用于完成蓝牙协议栈初始化、回调注册、应用注册等工作。
该示例需要与 GATT Server 示例配合执行,以实现完整的 BLE 通信流程。
以下为函数的主要实现步骤,鉴于流程的大部分步骤已在通用步骤文档的 主函数说明 部分中详细介绍,对于重复内容此处不再赘述,仅重点说明本示例中的差异及细节补充部分。
设置目标设备名称,方便自动化连接。仅在编译配置
CONFIG_EXAMPLE_CI_PIPELINE_ID
被启用时才会执行。从示例函数
esp_bluedroid_get_example_name()
中获取默认设备名称,并将该名称复制到缓冲区,用于后续扫描和匹配。
在 释放蓝牙内存 时,因为本示例仅使用 BLE,所以需要将经典蓝牙部分的内存全部释放。
在 启动蓝牙控制器 时,需要将其指定为 BLE 模式。
在使能蓝牙协议栈后 测试蓝牙稳定性,用来验证系统在反复初始化和关闭蓝牙的情况下的稳定性和内存占用情况,仅在编译配置
CONFIG_EXAMPLE_INIT_DEINIT_LOOP
被启用时才会执行。在
EXAMPLE_TEST_COUNT
次循环中执行以下操作:反初始化:禁用蓝牙协议栈并释放资源。
延时 10ms,并打印当前空闲堆内存。
初始化并启用蓝牙协议栈。
再延时 10ms,确保初始化完成。
循环结束后直接返回。
在 注册回调函数 时:
传入
gatts_event_handler
注册 GATT Server 回调函数。传入
gap_event_handler
注册 GAP 回调函数。
将 本地 GATT MTU 设置为 500 字节。
程序执行逻辑
在程序每个事件中添加日志打印后,烧录运行可以看到如下执行结果:
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
程序执行逻辑可以总结为以下几个阶段:
初始化阶段:程序依次初始化非易失性存储(NVS)、释放内存、初始化并启用蓝牙控制器,随后初始化并启用 Bluedroid 协议栈。
注册回调与应用:注册 GATT Client 和 GAP 的事件回调函数,随后注册 GATT 应用。
应用注册完成:应用注册成功后触发
ESP_GATTS_REG_EVT
,表示 GATT Server 应用已成功登记到协议栈中。此时事件进入统一事件回调gattc_profile_event_handler()
,由事件分发器分发到具体的 Profile 回调中,在该回调中启动广播和扫描响应数据的写入操作。扫描参数设置与启动:设置扫描参数完成后触发
ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT
,扫描启动完成后触发ESP_GAP_BLE_SCAN_START_COMPLETE_EVT
,客户端开始扫描周围广播设备。MTU 设置:进行 MTU 配置,以便优化传输效率。
扫描与发现设备:在扫描过程中,多次触发
ESP_GAP_BLE_SCAN_RESULT_EVT
和ESP_GAP_SEARCH_INQ_RES_EVT
,表示发现设备并获取广播信息。扫描停止:扫描结束后触发
ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT
,停止扫描操作。发起连接:客户端根据目标设备信息发起连接,触发
ESP_GATTC_CONNECT_EVT
表示建立了物理连接,随后触发ESP_GATTC_OPEN_EVT
确认连接建立。数据包长度设置:设置 BLE 数据包长度完成后触发
ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT
。连接参数更新:在连接建立后,可能更新连接间隔、超时等参数以优化通信,参数更新完成触发
ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT
。服务发现完成:客户端收到
ESP_GATTC_DIS_SRVC_CMPL_EVT
表示服务发现完成,随后触发ESP_GATTC_SEARCH_RES_EVT
获取具体服务信息,最终触发ESP_GATTC_SEARCH_CMPL_EVT
表示服务搜索完成。注册通知:客户端注册特征通知后触发
ESP_GATTC_REG_FOR_NOTIFY_EVT
,表示可以接收服务器发送的通知。MTU 协商完成:客户端与服务器协商 MTU 后触发
ESP_GATTC_CFG_MTU_EVT
,确定双方最大传输单元大小。接收通知数据:收到服务器发送的数据通知时触发
ESP_GATTC_NOTIFY_EVT
,客户端可以读取并处理数据。写入描述符:客户端向特征描述符写入数据(如开启通知)后触发
ESP_GATTC_WRITE_DESCR_EVT
,服务器确认写入成功。写入特征值:客户端向特征写入数据后触发
ESP_GATTC_WRITE_CHAR_EVT
,服务器接收并处理写入操作。
注意事项:
MTU 协商决定了一次传输的最大长度,不同设备支持的值不同,如果协商失败会回退到默认值。
扫描停止是异步操作,控制器处理缓冲中的数据还会产生事件,所以会出现扫描停止后仍有扫描结果打印的情况。