ESP-C3入门17. 低功耗蓝牙GATT Server
- 一、基本概念
- 1. GATT、属性、服务
- 2. 角色
- (1)GATT Server
- (2)GATT Client
- 二、ESP32 IDF建立 GATT Server步骤
- 1. 初始化 BLE 驱动程序并创建 GATT 应用程序
- 2. 注册应用程序事件处理程序
- 3. 配置 GATT 层 MTU (最大传输单元)
- 4. 创建 GATT 服务并声明其 UUID
- 5. 创建 GATT 特征并声明其 UUID 和属性
- 6. 将特征添加到服务中
- 7. 声明 GATT 服务器的回调函数,以便处理 GATT 层的事件
- 8. 初始化 GATT 服务器
- 9. 实现 GATT 服务器的回调函数以处理事件
- (1)处理读请求
- (2)处理写请求
- (3)处理通知
- (4)处理断开连接
- 10. 定义服务特征值的 UUID 和属性
- 11. 添加服务特征值和属性
- 12. 启用 GATT 服务器
- 13. 监听来自 GATT 客户端的连接请求
- 14. 使用 GATT API 发送数据到 GATT 客户端或接收数据
- 15. 断开连接并停用 GATT 服务器
- 三、源代码
- 1. 代码
- 2. 运行
一、基本概念
1. GATT、属性、服务
GATT (Generic Attribute Profile) 是蓝牙低功耗 (Bluetooth Low Energy, BLE) 协议栈中的一部分,它定义了 BLE 设备之间交换数据的格式和规范。
GATT 是基于属性 (attribute) 和服务 (service) 的概念,通过将数据封装在属性中,从而实现设备之间的通信。
在 GATT 中,一个服务表示一个特定的功能,一个服务可以包含多个属性。
每个属性都有一个唯一的标识符 (UUID),可以用来识别它们。属性可以是只读的 (Read),也可以是可写的 (Write)。
属性还可以包含一个描述符 (Descriptor),用于描述属性的特性和值。描述符是可选的,但是它们可以提供关于属性的额外信息,例如范围、单位或名称。
GATT 使用基于请求-响应模型的通信方式。当一个设备想要读取或写入属性时,它会发送一个请求给另一个设备,请求的格式包含要访问的属性的 UUID 和操作类型 (读或写)。接收方设备会根据请求返回响应消息,其中包含请求的数据,或者在写入操作时返回确认消息。
GATT 还定义了一些通用的属性和服务,例如设备信息服务 (Device Information Service)、电池服务 (Battery Service) 等,这些服务可以让开发人员更容易地实现常见的功能。
下图展示了GATT各层概念之间的包含关系:
2. 角色
在 GATT 中,存在两种角色:GATT Server 和 GATT Client。
(1)GATT Server
是指具有 GATT 数据的设备,它可以被 GATT Client 连接并提供服务。
GATT Server 存储着一个或多个服务,每个服务都包含一个或多个属性。当 GATT Client 连接到 GATT Server 时,它可以通过 GATT 协议来访问服务和属性。GATT Server 需要响应 GATT Client 的请求,例如读取和写入属性值。
(2)GATT Client
是指需要访问 GATT Server 的设备。GATT Client 可以扫描周围的 BLE 设备,找到包含 GATT 数据的设备,并连接到它们。一旦连接建立,GATT Client 可以通过 GATT 协议来读取和写入 GATT Server 上的服务和属性。例如,GATT Client 可以读取一个温度传感器的属性值,或者写入一个 LED 灯的属性值。
一个设备既可以作为 GATT Server,也可以作为 GATT Client。例如,一个智能手表可以作为 GATT Server,提供心率监测服务;同时,它也可以作为 GATT Client 连接到另一个设备,例如智能手机,以获取其他服务的数据,如来自手机的通知。
二、ESP32 IDF建立 GATT Server步骤
在 ESP32 IDF 中建立 GATT Server 的步骤如下:
1. 初始化 BLE 驱动程序并创建 GATT 应用程序
在初始化 BLE 驱动程序之前,需要配置 BLE 栈。
- BLE 栈可以通过
esp_bt_controller_mem_release()
函数来释放。 - 使用
esp_bt_controller_init()
函数来初始化 BLE 控制器。 - 使用
esp_bt_controller_enable()
函数来启用 BLE 控制器。 - 使用
esp_bluedroid_init()
函数来初始化 BlueDroid 栈,这是 ESP32 IDF 中的 Bluetooth 栈实现。 - 使用
esp_bluedroid_enable()
函数来启用 BlueDroid 栈。
初始化 BLE 驱动程序并创建 GATT 应用程序使用 esp_ble_gatts_app_register()
函数。
该函数的原型如下:
esp_err_t esp_ble_gatts_app_register(uint16_t app_id);
其中 app_id
是应用程序的标识符,用于将应用程序与其注册的事件处理程序相关联。
在调用该函数之前,需要先调用 esp_ble_gatts_register_callback()
函数来注册 GATT 服务器的回调函数。
然后,可以使用 esp_ble_gatts_app_register()
函数来创建 GATT 应用程序并启动 BLE 驱动程序。
2. 注册应用程序事件处理程序
esp_ble_gatts_register_callback()
函数。
该函数的原型如下:
esp_err_t esp_ble_gatts_register_callback(esp_gatts_cb_t callback);
其中,callback 是指向回调函数的指针,用于处理 GATT 服务器的事件。
3. 配置 GATT 层 MTU (最大传输单元)
该函数的原型如下:
```c
esp_err_t esp_ble_gatt_set_local_mtu(uint16_t mtu);
其中,mtu 是要设置的 MTU 值。
在使用该函数之前,需要在创建 GATT 应用程序时,通过 esp_ble_gatts_app_register() 函数设置 GATT 层的 MTU 值。例如:
esp_ble_gatts_app_register(APP_ID);
esp_ble_gatt_set_local_mtu(512);
上述代码将 GATT 应用程序的标识符设置为 APP_ID,并将 GATT 层的 MTU 值设置为 512。这样,当 GATT 客户端和服务器进行通信时,可以支持较大的数据包传输。
不同的设备支持的 MTU 值可能会有所不同。因此,在设置 MTU 值时,需要考虑到设备的兼容性,并确保所设置的 MTU 值能够满足设备之间的通信需求。
4. 创建 GATT 服务并声明其 UUID
通过 esp_ble_gatts_create_service()
函数来创建 GATT 服务并声明其 UUID。
该函数的原型如下:
esp_err_t esp_ble_gatts_create_service(esp_gatts_srvc_id_t *service_id, uint8_t num_handle);
其中,service_id 是指向服务 ID 的指针,num_handle 是指服务包含的句柄数量。
5. 创建 GATT 特征并声明其 UUID 和属性
使用 esp_ble_gatts_add_char()
函数来创建 GATT 特征并声明其 UUID 和属性。
该函数的原型如下:
esp_err_t esp_ble_gatts_add_char(esp_gatt_srvc_id_t service_id, const esp_gatts_char_inst_t *char_inst, const esp_gatts_attr_db_t *char_attr,
esp_gatt_perm_t perm, esp_gatt_char_prop_t property, const esp_attr_value_t *char_val, esp_gatts_descr_elem_t *char_descr,
uint16_t *char_handle);
其中:
- service_id :服务 ID
- char_inst :指向特征实例的指针
- char_attr :指向特征属性的指针
- perm :特征的权限
- property :特征的属性
- char_val :指向特征值的指针
- char_descr :指向特征描述符的指针
- char_handle :特征的句柄。
6. 将特征添加到服务中
使用 esp_ble_gatts_add_char()
函数将特征添加到服务中。
在调用 esp_ble_gatts_create_service()
函数创建服务后,可以按照需要多次调用 esp_ble_gatts_add_char()
函数来添加不同的特征。每次调用该函数时,需要传递以下参数:
- service_id:服务 ID,与调用 esp_ble_gatts_create_service() 函数时传递的服务 ID 相同。
- char_inst:指向特征实例的指针,可以使用不同的实例 ID 来添加多个特征到同一个服务中。
- char_attr:指向特征属性的指针,定义特征的属性,如读写权限、属性类型、特征值等。
- perm:特征的权限,即读写权限。
- property:特征的属性,如是否具有读、写、通知等属性。
- char_val:指向特征值的指针,可以为 NULL。
- char_descr:指向特征描述符的指针,可以为 NULL。
- char_handle:特征的句柄,可以为 NULL。
7. 声明 GATT 服务器的回调函数,以便处理 GATT 层的事件
使用 esp_ble_gatts_register_callback()
函数来注册回调函数。这个函数的参数是一个结构体,其中包含了各种事件处理函数的指针:
- event_handler: 用于处理 GATT 层的事件,例如 GATT 服务器被启动、连接成功、断开连接等事件。
- connect_cb: 用于处理连接事件,例如有设备连接到 GATT 服务器时的回调。
- disconnect_cb: 用于处理断开连接事件,例如有设备从 GATT 服务器断开连接时的回调。
- mtu_cb: 用于处理 MTU (Maximum Transmission Unit,最大传输单元) 事件,例如设备请求更改 MTU 时的回调。
- write_cb: 用于处理写操作事件,例如设备向 GATT 服务器写入数据时的回调。
- create_service_cb: 用于处理创建服务事件,例如创建服务时的回调。
- add_char_cb: 用于处理添加特征事件,例如添加特征时的回调。
注册回调函数后,当 GATT 层发生相关事件时,会自动调用相应的回调函数进行处理。
8. 初始化 GATT 服务器
使用 esp_ble_gatts_app_register()
函数。
该函数的参数是一个包含 GATT 应用程序的信息的结构体,包括应用程序的 ID、GATT 服务的数量、GATT 服务的数组等。
9. 实现 GATT 服务器的回调函数以处理事件
在 esp_ble_gatts_register_callback()
函数中注册回调函数并处理不同的事件。以下是一些常见的事件及其处理方式:
(1)处理读请求
在 read_cb 回调函数中处理读请求。当连接的设备请求从 GATT 特征中读取数据时,该回调函数将被触发。在这个回调函数中,你需要使用 esp_ble_gatts_send_response() 函数来发送读取请求的响应。该函数需要提供读取的数据、数据的长度和 GATT 请求的句柄。
(2)处理写请求
在 write_cb 回调函数中处理写请求。当连接的设备向 GATT 特征写入数据时,该回调函数将被触发。在这个回调函数中,你需要检查数据的长度和数据本身是否有效,并在需要时将数据存储到应用程序中。如果写入操作成功,则需要使用 esp_ble_gatts_send_response() 函数来发送成功的响应。
(3)处理通知
在 notify_cb 回调函数中处理通知。当 GATT 特征的值被更改时,你可以使用 esp_ble_gatts_send_indicate() 函数向已连接的设备发送通知或指示。该函数需要提供特征的句柄、数据的长度和数据本身。
(4)处理断开连接
在 disconnect_cb 回调函数中处理断开连接。当设备与 GATT 服务器断开连接时,该回调函数将被触发。在这个回调函数中,你可以执行一些清理操作并关闭连接。
除了上述事件外,还有一些其他事件,例如创建服务和添加特征等。
10. 定义服务特征值的 UUID 和属性
UUID 是一个唯一标识符,用于标识 GATT 服务和特征值。你可以使用 esp_ble_uuid_t 结构体定义 UUID;
然后使用 esp_gatts_attr_db_t 结构体定义特征值的属性。其中,属性包括权限、特征值的值和长度等。
示例:
uint8_t char1_value[1] = {0x00};
esp_gatts_attr_db_t gatts_attr_tab[1] = {
[0] = {
.att_desc = {
.uuid = {
.len = ESP_UUID_LEN_16,
.uuid = {0x00, 0xFF},
},
.perm = ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
.max_length = 1,
.length = sizeof(char1_value),
.value = char1_value,
},
},
};
这个例子中,特征值的 UUID 是 0x00FF,具有读写权限。特征值的值是一个长度为 1 的字节数组,即 char1_value,可以通过读写请求进行读写操作。
11. 添加服务特征值和属性
使用 esp_ble_gatts_add_char()
函数来添加服务特征值和属性。该函数有多个参数,包括特征值的 UUID、权限和属性等。以下是一个添加特征值和属性的例子:
esp_err_t esp_ble_gatts_add_char(
uint16_t service_handle, // 服务的句柄
const esp_bt_uuid_t *uuid, // 特征值的 UUID
esp_gatt_perm_t perm, // 特征值的权限
esp_gatt_char_prop_t property, // 特征值的属性
const esp_attr_value_t *p_value, // 特征值的值
esp_gatt_rsp_type_t rsp_type // 读操作的响应类型
);
其中:
- service_handle:服务的句柄
- uuid :特征值的 UUID
- perm :特征值的权限
- property:特征值的属性
- p_value :特征值的值
- rsp_type :读操作的响应类型。
特征值的值必须是 esp_attr_value_t
结构体类型。
12. 启用 GATT 服务器
使用 esp_ble_gatts_app_register() 函数来启用 GATT 服务器。该函数需要一个 esp_ble_gatts_cb_t 类型的回调函数作为参数,用于处理 GATT 层的事件。
示例:
esp_err_t esp_ble_gatts_app_register(uint16_t app_id);
...
// 注册 GATT 应用程序并启用 GATT 服务器
esp_ble_gatts_app_register(APP_ID);
13. 监听来自 GATT 客户端的连接请求
使用esp_ble_gatts_register_callback(gatts_event_handler);
来设置回调函数。
14. 使用 GATT API 发送数据到 GATT 客户端或接收数据
15. 断开连接并停用 GATT 服务器
使用 esp_ble_gatts_close()
函数来关闭连接,并使用esp_ble_gatts_app_unregister()
函数来注销 GATT 应用程序并停用 GATT 服务器。
三、源代码
本文使用ESP32 IDF 示例代码,进行了部分精简。
1. 代码
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/****************************************************************************
*
* This demo showcases BLE GATT server. It can send adv data, be connected by client.
* Run the gatt_client demo, the client demo will automatically connect to the gatt_server demo.
* Client demo will enable gatt_server's notify after connection. The two devices will then exchange
* data.
*
****************************************************************************/
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"
#include "sdkconfig.h"
#define GATTS_TAG "GATTS_DEMO"
///Declare the static function
static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
#define GATTS_SERVICE_UUID_TEST_A 0x00FF
#define GATTS_CHAR_UUID_TEST_A 0xFF01
#define GATTS_DESCR_UUID_TEST_A 0x3333
#define GATTS_NUM_HANDLE_TEST_A 4
#define TEST_DEVICE_NAME "ESP_GATTS_DEMO"
#define TEST_MANUFACTURER_DATA_LEN 17
#define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40
#define PREPARE_BUF_MAX_SIZE 1024
// 定义一个静态的 uint8_t 数组,用于存储 char1_str 的值
static uint8_t char1_str[] = {0x11,0x22,0x33};
// 定义一个 esp_gatt_char_prop_t 类型的变量,用于存储属性值
static esp_gatt_char_prop_t a_property = 0;
// 定义一个 esp_attr_value_t 类型的变量,用于存储 gatts_demo_char1_val 的值
static esp_attr_value_t gatts_demo_char1_val =
{
.attr_max_len = GATTS_DEMO_CHAR_VAL_LEN_MAX,
.attr_len = sizeof(char1_str),
.attr_value = char1_str,
};
static uint8_t adv_config_done = 0;
#define adv_config_flag (1 << 0)
#define scan_rsp_config_flag (1 << 1)
#ifdef CONFIG_SET_RAW_ADV_DATA
static uint8_t raw_adv_data[] = {
0x02, 0x01, 0x06,
0x02, 0x0a, 0xeb, 0x03, 0x03, 0xab, 0xcd
};
static uint8_t raw_scan_rsp_data[] = {
0x0f, 0x09, 0x45, 0x53, 0x50, 0x5f, 0x47, 0x41, 0x54, 0x54, 0x53, 0x5f, 0x44,
0x45, 0x4d, 0x4f
};
#else
// 定义2个Service
static uint8_t adv_service_uuid128[32] = {
/* LSB <--------------------------------------------------------------------------------> MSB */
//first uuid, 16bit, [12],[13] is the value
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xEE, 0x00, 0x00, 0x00,
//second uuid, 32bit, [12], [13], [14], [15] is the value
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
};
// 配置蓝牙广播的参数
//static uint8_t test_manufacturer[TEST_MANUFACTURER_DATA_LEN] = {0x12, 0x23, 0x45, 0x56};
//adv data
static esp_ble_adv_data_t adv_data = {
.set_scan_rsp = false, // 是否为扫描响应数据,这里设置为广播数据
.include_name = true, // 是否包含设备名称
.include_txpower = false, // 是否包含广播信号强度值
.min_interval = 0x0006, // 从设备在两次广播之间最短的时间间隔,单位为 1.25 毫秒,这里设置为 7.5 毫秒
.max_interval = 0x0010, // 从设备在两次广播之间最长的时间间隔,单位为 1.25 毫秒,这里设置为 20 毫秒
.appearance = 0x00, // 设备外观,这里设置为默认值 0
.manufacturer_len = 0, // 厂商数据长度,这里设置为 0
.p_manufacturer_data = NULL, // 厂商数据指针,这里设置为 NULL
.service_data_len = 0, // 服务数据长度,这里设置为 0
.p_service_data = NULL, // 服务数据指针,这里设置为 NULL
.service_uuid_len = sizeof(adv_service_uuid128), // 服务 UUID 长度,这里设置为预定义的 adv_service_uuid128 的长度
.p_service_uuid = adv_service_uuid128, // 服务 UUID 指针,这里设置为预定义的 adv_service_uuid128
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), // 广播标志,这里设置为一般发现模式和不支持 BR/EDR
};
// 定义BLE广播中的扫描响应数据
static esp_ble_adv_data_t scan_rsp_data = {
.set_scan_rsp = true, // 标记这个结构体是否用于扫描响应数据(true 为扫描响应数据,false 为广播数据)
.include_name = true, // 是否在广播中包含设备名
.include_txpower = true, // 是否在广播中包含发送功率
//.min_interval = 0x0006,
//.max_interval = 0x0010,
.appearance = 0x00, // 设备的外观类别
.manufacturer_len = 0, // 制造商数据的长度(单位为字节)
.p_manufacturer_data = NULL, // 指向包含制造商
.service_data_len = 0, // 服务数据的长度(单位为字节)
.p_service_data = NULL, // 指向包含服务数据
.service_uuid_len = sizeof(adv_service_uuid128), // 服务 UUID 的长度(单位为字节)
.p_service_uuid = adv_service_uuid128, // 指向服务 UUID 的缓冲区
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), // 广播标志,可以指定多个广播标志(如通用发现标志和 BR/EDR 不支持标志)。
};
#endif /* CONFIG_SET_RAW_ADV_DATA */
static esp_ble_adv_params_t adv_params = {
.adv_int_min = 0x20, // 广播的最短时间间隔,单位0.625毫秒
.adv_int_max = 0x40, // 广播的最长时间间隔
.adv_type = ADV_TYPE_IND, // 广播的类型,这里为 ADV_TYPE_IND,表示非定向广播
.own_addr_type = BLE_ADDR_TYPE_PUBLIC, // 是广播自身的地址类型,这里为 BLE_ADDR_TYPE_PUBLIC,表示使用公共地址
//.peer_addr =
//.peer_addr_type =
.channel_map = ADV_CHNL_ALL, // 表示广播的通道,这里为 ADV_CHNL_ALL,表示使用所有通道
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, // 表示广播过滤策略,这里为 ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,表示不进行任何筛选,允许任何扫描设备或连接设备接收到广播包
};
#define PROFILE_NUM 1
#define PROFILE_A_APP_ID 0
// 用于实例化GATT Profile的结构体
struct gatts_profile_inst {
esp_gatts_cb_t gatts_cb; // GATT Server事件回调函数
uint16_t gatts_if; // GATT Server接口ID
uint16_t app_id; // 应用程序ID
uint16_t conn_id; // 连接ID
uint16_t service_handle; // 服务句柄
esp_gatt_srvc_id_t service_id; // 服务ID
uint16_t char_handle; // 特征句柄
esp_bt_uuid_t char_uuid; // 特征UUID
esp_gatt_perm_t perm; // 特征属性权限
esp_gatt_char_prop_t property; // 特征属性
uint16_t descr_handle; // 特征描述符句柄
esp_bt_uuid_t descr_uuid; // 特征描述符UUID
};
/**
* 结构体数组,包含 gatts_profile_inst 结构体元素,每个元素对应一个 GATT 服务
*/
static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = {
[PROFILE_A_APP_ID] = {
.gatts_cb = gatts_profile_a_event_handler, // 回调函数,用于处理与该服务相关的事件
.gatts_if = ESP_GATT_IF_NONE, // 表示与该服务关联的 GATT 接口标识符(IF)的整数值。初始化时,该值被设置为 ESP_GATT_IF_NONE,表示尚未获取 GATT IF
},
};
typedef struct {
uint8_t *prepare_buf;
int prepare_len;
} prepare_type_env_t;
static prepare_type_env_t a_prepare_write_env;
void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param);
void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param);
/**
* 处理 BLE GAP事件
*/
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
#ifdef CONFIG_SET_RAW_ADV_DATA
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
// 广播数据设置完毕,当通过 esp_ble_gap_config_adv_data_raw() 设置原始广播数据时,ESP32 蓝牙栈会向应用程序发送这个事件,表示数据已经成功设置到芯片上
adv_config_done &= (~adv_config_flag);
if (adv_config_done==0){
// 启用广播
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
// 设置原始扫描响应数据完成的事件。当使用原始数据格式来设置扫描响应数据时,会触发该事件
adv_config_done &= (~scan_rsp_config_flag);
if (adv_config_done==0){
// 启用广播
esp_ble_gap_start_advertising(&adv_params);
}
break;
#else
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
// 表示广播数据设置完毕的事件
adv_config_done &= (~adv_config_flag);
if (adv_config_done == 0){
// 启用广播
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
// 扫描响应数据已成功设置完毕
adv_config_done &= (~scan_rsp_config_flag);
if (adv_config_done == 0){
esp_ble_gap_start_advertising(&adv_params);
}
break;
#endif
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
// 通知 BLE 底层主机(host)设备开始广播操作的结果。此事件可以指示广播是否成功启动或失败
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TAG, "Advertising start failed\n");
}
break;
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
// 表示停止广播的完成事件
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TAG, "Advertising stop failed\n");
} else {
ESP_LOGI(GATTS_TAG, "Stop adv successfully\n");
}
break;
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
// 指示 BLE 连接参数已更新的事件
ESP_LOGI(GATTS_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d",
param->update_conn_params.status,
param->update_conn_params.min_int,
param->update_conn_params.max_int,
param->update_conn_params.conn_int,
param->update_conn_params.latency,
param->update_conn_params.timeout);
break;
default:
break;
}
}
/**
* 实现 BLE GATT Server 在接收到 characteristic write请求时的处理函数
*/
void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){
esp_gatt_status_t status = ESP_GATT_OK;
// 收到写请求时
if (param->write.need_rsp){
// 是否需要发送响应
if (param->write.is_prep){
// 准备一个 GATT response,并发送给客户端
if (prepare_write_env->prepare_buf == NULL) {
prepare_write_env->prepare_buf = (uint8_t *)malloc(PREPARE_BUF_MAX_SIZE*sizeof(uint8_t));
prepare_write_env->prepare_len = 0;
if (prepare_write_env->prepare_buf == NULL) {
ESP_LOGE(GATTS_TAG, "Gatt_server prep no mem\n");
status = ESP_GATT_NO_RESOURCES;
}
} else {
if(param->write.offset > PREPARE_BUF_MAX_SIZE) {
status = ESP_GATT_INVALID_OFFSET;
} else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) {
status = ESP_GATT_INVALID_ATTR_LEN;
}
}
esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t));
gatt_rsp->attr_value.len = param->write.len;
gatt_rsp->attr_value.handle = param->write.handle;
gatt_rsp->attr_value.offset = param->write.offset;
gatt_rsp->attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len);
esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, gatt_rsp);
if (response_err != ESP_OK){
ESP_LOGE(GATTS_TAG, "Send response error\n");
}
free(gatt_rsp);
if (status != ESP_GATT_OK){
return;
}
memcpy(prepare_write_env->prepare_buf + param->write.offset,
param->write.value,
param->write.len);
prepare_write_env->prepare_len += param->write.len;
}else{
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL);
}
}
}
/**
* 处理BLE GATT协议中的Prepare Write操作的回调函数
*/
void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){
// 是否是执行写操作
if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC){
// 将之前保存在缓存中的数据打印出来
esp_log_buffer_hex(GATTS_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len);
}else{
// 取消写操作
ESP_LOGI(GATTS_TAG,"ESP_GATT_PREP_WRITE_CANCEL");
}
// 将之前分配的缓存释放掉,并将缓存长度清零
if (prepare_write_env->prepare_buf) {
free(prepare_write_env->prepare_buf);
prepare_write_env->prepare_buf = NULL;
}
prepare_write_env->prepare_len = 0;
}
/**
* GATT Profile A 的事件处理程序,处理来自 BLE GATT stack 的事件和操作
* @param event: 事件类型
* @param gatts_if: GATT 接口标识符
* @param param: BLE GATT 事件回调参数的指针
**/
static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) {
switch (event) {
case ESP_GATTS_REG_EVT:
// 注册事件
ESP_LOGI(GATTS_TAG, "REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id);
gl_profile_tab[PROFILE_A_APP_ID].service_id.is_primary = true;
gl_profile_tab[PROFILE_A_APP_ID].service_id.id.inst_id = 0x00;
gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_A;
esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(TEST_DEVICE_NAME);
if (set_dev_name_ret){
ESP_LOGE(GATTS_TAG, "set device name failed, error code = %x", set_dev_name_ret);
}
#ifdef CONFIG_SET_RAW_ADV_DATA
esp_err_t raw_adv_ret = esp_ble_gap_config_adv_data_raw(raw_adv_data, sizeof(raw_adv_data));
if (raw_adv_ret){
ESP_LOGE(GATTS_TAG, "config raw adv data failed, error code = %x ", raw_adv_ret);
}
adv_config_done |= adv_config_flag;
esp_err_t raw_scan_ret = esp_ble_gap_config_scan_rsp_data_raw(raw_scan_rsp_data, sizeof(raw_scan_rsp_data));
if (raw_scan_ret){
ESP_LOGE(GATTS_TAG, "config raw scan rsp data failed, error code = %x", raw_scan_ret);
}
adv_config_done |= scan_rsp_config_flag;
#else
//config adv data
esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
if (ret){
ESP_LOGE(GATTS_TAG, "config adv data failed, error code = %x", ret);
}
adv_config_done |= adv_config_flag;
//config scan response data
ret = esp_ble_gap_config_adv_data(&scan_rsp_data);
if (ret){
ESP_LOGE(GATTS_TAG, "config scan response data failed, error code = %x", ret);
}
adv_config_done |= scan_rsp_config_flag;
#endif
esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_A_APP_ID].service_id, GATTS_NUM_HANDLE_TEST_A);
break;
case ESP_GATTS_READ_EVT: {
// 读取事件,从外设读取数据
ESP_LOGI(GATTS_TAG, "GATT_READ_EVT, conn_id %d, trans_id %" PRIu32 ", handle %d\n", param->read.conn_id, param->read.trans_id, param->read.handle);
esp_gatt_rsp_t rsp;
memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
rsp.attr_value.handle = param->read.handle;
rsp.attr_value.len = 4;
rsp.attr_value.value[0] = 0xde;
rsp.attr_value.value[1] = 0xed;
rsp.attr_value.value[2] = 0xbe;
rsp.attr_value.value[3] = 0xef;
esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id,
ESP_GATT_OK, &rsp);
break;
}
case ESP_GATTS_WRITE_EVT: {
// 写请求事件
ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %" PRIu32 ", handle %d", param->write.conn_id, param->write.trans_id, param->write.handle);
if (!param->write.is_prep){
ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len);
esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len);
if (gl_profile_tab[PROFILE_A_APP_ID].descr_handle == param->write.handle && param->write.len == 2){
uint16_t descr_value = param->write.value[1]<<8 | param->write.value[0];
if (descr_value == 0x0001){
if (a_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY){
ESP_LOGI(GATTS_TAG, "notify enable");
uint8_t notify_data[15];
for (int i = 0; i < sizeof(notify_data); ++i)
{
notify_data[i] = i%0xff;
}
//the size of notify_data[] need less than MTU size
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_A_APP_ID].char_handle,
sizeof(notify_data), notify_data, false);
}
}else if (descr_value == 0x0002){
if (a_property & ESP_GATT_CHAR_PROP_BIT_INDICATE){
ESP_LOGI(GATTS_TAG, "indicate enable");
uint8_t indicate_data[15];
for (int i = 0; i < sizeof(indicate_data); ++i)
{
indicate_data[i] = i%0xff;
}
//the size of indicate_data[] need less than MTU size
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_A_APP_ID].char_handle,
sizeof(indicate_data), indicate_data, true);
}
}
else if (descr_value == 0x0000){
ESP_LOGI(GATTS_TAG, "notify/indicate disable ");
}else{
ESP_LOGE(GATTS_TAG, "unknown descr value");
esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len);
}
}
}
example_write_event_env(gatts_if, &a_prepare_write_env, param);
break;
}
case ESP_GATTS_EXEC_WRITE_EVT:
// 收到远程设备的Prepare Write Request后,当远程设备完成所有Write请求并发送Execute Write Request时触发的事件
ESP_LOGI(GATTS_TAG,"ESP_GATTS_EXEC_WRITE_EVT");
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);
example_exec_write_event_env(&a_prepare_write_env, param);
break;
case ESP_GATTS_MTU_EVT:
// 当GATT客户端和服务器连接并协商MTU大小时的事件
ESP_LOGI(GATTS_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu);
break;
case ESP_GATTS_UNREG_EVT:
// GATT 服务器已被注销
break;
case ESP_GATTS_CREATE_EVT:
// 一个GATT服务器被创建的事件
ESP_LOGI(GATTS_TAG, "CREATE_SERVICE_EVT, status %d, service_handle %d\n", param->create.status, param->create.service_handle);
gl_profile_tab[PROFILE_A_APP_ID].service_handle = param->create.service_handle;
gl_profile_tab[PROFILE_A_APP_ID].char_uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[PROFILE_A_APP_ID].char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_TEST_A;
esp_ble_gatts_start_service(gl_profile_tab[PROFILE_A_APP_ID].service_handle);
a_property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY;
esp_err_t add_char_ret = esp_ble_gatts_add_char(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].char_uuid,
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
a_property,
&gatts_demo_char1_val, NULL);
if (add_char_ret){
ESP_LOGE(GATTS_TAG, "add char failed, error code =%x",add_char_ret);
}
break;
case ESP_GATTS_ADD_INCL_SRVC_EVT:
// 一个包含在服务定义中的其它服务已经成功添加到GATT数据库
break;
case ESP_GATTS_ADD_CHAR_EVT:
// 向GATT服务器中添加新特征时的事件
uint16_t length = 0;
const uint8_t *prf_char;
ESP_LOGI(GATTS_TAG, "ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n",
param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle);
gl_profile_tab[PROFILE_A_APP_ID].char_handle = param->add_char.attr_handle;
gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
esp_err_t get_attr_ret = esp_ble_gatts_get_attr_value(param->add_char.attr_handle, &length, &prf_char);
if (get_attr_ret == ESP_FAIL){
ESP_LOGE(GATTS_TAG, "ILLEGAL HANDLE");
}
ESP_LOGI(GATTS_TAG, "the gatts demo char length = %x\n", length);
for(int i = 0; i < length; i++){
ESP_LOGI(GATTS_TAG, "prf_char[%x] =%x\n",i,prf_char[i]);
}
esp_err_t add_descr_ret = esp_ble_gatts_add_char_descr(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].descr_uuid,
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, NULL, NULL);
if (add_descr_ret){
ESP_LOGE(GATTS_TAG, "add char descr failed, error code =%x", add_descr_ret);
}
break;
case ESP_GATTS_ADD_CHAR_DESCR_EVT:
// 表示添加一个新的特征描述符
gl_profile_tab[PROFILE_A_APP_ID].descr_handle = param->add_char_descr.attr_handle;
ESP_LOGI(GATTS_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n",
param->add_char_descr.status, param->add_char_descr.attr_handle, param->add_char_descr.service_handle);
break;
case ESP_GATTS_DELETE_EVT:
// 删除GATT服务器的服务或属性
break;
case ESP_GATTS_START_EVT:
// GATT 通用属性 服务器成功启动
ESP_LOGI(GATTS_TAG, "SERVICE_START_EVT, status %d, service_handle %d\n",
param->start.status, param->start.service_handle);
break;
case ESP_GATTS_STOP_EVT:
// GATT 服务器停止
break;
case ESP_GATTS_CONNECT_EVT: {
// GATT服务器已确认对于某个客户端发送的数据的接收。
// 表示有一个BLE*设备连接到该 GATT 服务器
esp_ble_conn_update_params_t conn_params = {0};
memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
/* For the IOS system, please reference the apple official documents about the ble connection parameters restrictions. */
conn_params.latency = 0;
conn_params.max_int = 0x20; // max_int = 0x20*1.25ms = 40ms
conn_params.min_int = 0x10; // min_int = 0x10*1.25ms = 20ms
conn_params.timeout = 400; // timeout = 400*10ms = 4000ms
ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONNECT_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:",
param->connect.conn_id,
param->connect.remote_bda[0], param->connect.remote_bda[1], param->connect.remote_bda[2],
param->connect.remote_bda[3], param->connect.remote_bda[4], param->connect.remote_bda[5]);
gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id;
//start sent the update connection parameters to the peer device.
esp_ble_gap_update_conn_params(&conn_params);
break;
}
case ESP_GATTS_DISCONNECT_EVT:
// 一个客户端设备断开
ESP_LOGI(GATTS_TAG, "ESP_GATTS_DISCONNECT_EVT, disconnect reason 0x%x", param->disconnect.reason);
esp_ble_gap_start_advertising(&adv_params);
break;
case ESP_GATTS_CONF_EVT:
ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONF_EVT, status %d attr_handle %d", param->conf.status, param->conf.handle);
if (param->conf.status != ESP_GATT_OK){
esp_log_buffer_hex(GATTS_TAG, param->conf.value, param->conf.len);
}
break;
case ESP_GATTS_OPEN_EVT: // 表示一个新的客户端连接
case ESP_GATTS_CANCEL_OPEN_EVT: // GATT服务器已经取消连接请求
case ESP_GATTS_CLOSE_EVT: // 表示 GATT 服务器已经关闭了一个连接
case ESP_GATTS_LISTEN_EVT: // GATT 已经开始监听请求
case ESP_GATTS_CONGEST_EVT: // GATT因为传输过多数据而处于拥塞状态
default:
break;
}
}
/**
* 将 GATT 服务器事件分发到相应的处理函数中
*/
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
// 如果事件类型是注册事件(ESP_GATTS_REG_EVT),则将每个 profile 的 gatts_if 存储起来
if (event == ESP_GATTS_REG_EVT) {
if (param->reg.status == ESP_GATT_OK) {
gl_profile_tab[param->reg.app_id].gatts_if = gatts_if;
} else {
ESP_LOGI(GATTS_TAG, "Reg app failed, app_id %04x, status %d\n",
param->reg.app_id,
param->reg.status);
return;
}
}
/* If the gatts_if equal to profile A, call profile A cb handler,
* so here call each profile's callback */
do {
int idx;
for (idx = 0; idx < PROFILE_NUM; idx++) {
if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, 调用所有 profile 的回调函数,否则只调用与当前 gatts_if 对应的 profile 的回调函数 */
gatts_if == gl_profile_tab[idx].gatts_if) {
if (gl_profile_tab[idx].gatts_cb) {
gl_profile_tab[idx].gatts_cb(event, gatts_if, param);
}
}
}
} while (0);
}
void app_main(void)
{
esp_err_t ret;
// 初始化 NVS.
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK( ret );
// 释放经典蓝牙模式下的内存
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
// 初始化蓝牙控制器
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(GATTS_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret));
return;
}
// 启用蓝牙控制器的 BLE 模式
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(GATTS_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
return;
}
// 初始化蓝牙协议栈
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(GATTS_TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
return;
}
// 启用蓝牙协议栈
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(GATTS_TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
return;
}
// 注册 GATT 事件回调函数
ret = esp_ble_gatts_register_callback(gatts_event_handler);
if (ret){
ESP_LOGE(GATTS_TAG, "gatts register error, error code = %x", ret);
return;
}
// 注册 GAP 事件回调函数
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret){
ESP_LOGE(GATTS_TAG, "gap register error, error code = %x", ret);
return;
}
// 注册 GATT 应用程序
ret = esp_ble_gatts_app_register(PROFILE_A_APP_ID);
if (ret){
ESP_LOGE(GATTS_TAG, "gatts app register error, error code = %x", ret);
return;
}
// 配置 GATT 层的 MTU
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500);
if (local_mtu_ret){
ESP_LOGE(GATTS_TAG, "set local MTU failed, error code = %x", local_mtu_ret);
}
return;
}
2. 运行
手机端连接示例:
广播数据:
GATT: