ESP32学习笔记(30)——BLE GATT服务端自定义服务和特征
Posted Leung_ManWah
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ESP32学习笔记(30)——BLE GATT服务端自定义服务和特征相关的知识,希望对你有一定的参考价值。
一、简介
1.1 低功耗蓝牙(BLE)协议栈
链路层(LL) 控制设备的射频状态,有五个设备状态:待机、广播、扫描、初始化和连接。
广播 为广播数据包,而 扫描 则是监听广播。
GAP通信中角色,中心设备(Central - 主机) 用来扫描和连接 外围设备(Peripheral - 从机)。
大部分情况下外围设备通过广播自己来让中心设备发现自己,并建立 GATT 连接,从而进行更多的数据交换。
也有些情况是不需要连接的,只要外设广播自己的数据即可,用这种方式主要目的是让外围设备,把自己的信息发送给多个中心设备。
1.2 通用属性协议(GATT)
GATT是用Attribute Protocal(属性协议)定义的一个service(服务)框架。这个框架定义了Services以及它们的Characteristics的格式和规程。规程就是定义了包括发现、读、写、通知、指示以及配置广播的characteristics。
为实现配置文件(Profile)的设备定义了两种角色:Client(客户端)、Server(服务器)。esp32的ble一般就处于Server模式。
一旦两个设备建立了连接,GATT就开始发挥效用,同时意味着GAP协议管理的广播过程结束了。
1.2.1 Profile(规范)
profile 可以理解为一种规范,建立的蓝牙应用任务,蓝牙任务实际上分为两类:标准蓝牙任务规范 profile(公有任务),非标准蓝牙任务规范 profile(私有任务)。
-
标准蓝牙任务规范 profile:指的是从蓝牙特别兴趣小组 SIG 的官网上已经发布的 GATT 规范列表,包括警告通知(alert notification),血压测量(blood pressure),心率(heart rate),电池(battery)等等。它们都是针对具体的低功耗蓝牙的应用实例来设计的。目前蓝牙技术联盟还在不断的制定新的规范,并且发布。
-
非标准蓝牙任务规范 profile:指的是供应商自定义的任务,在蓝牙 SIG 小组内未定义的任务规范。
1.2.2 Service(服务)
service 可以理解为一个服务,在 BLE 从机中有多个服务,例如:电量信息服务、系统信息服务等;
每个 service 中又包含多个 characteristic 特征值;
每个具体的 characteristic 特征值才是 BLE 通信的主题,比如当前的电量是 80%,电量的 characteristic 特征值存在从机的 profile 里,这样主机就可以通过这个 characteristic 来读取 80% 这个数据。
GATT 服务一般包含几个具有相关的功能,比如特定传感器的读取和设置,人机接口的输入输出。组织具有相关的特性到服务中既实用又有效,因为它使得逻辑上和用户数据上的边界变得更加清晰,同时它也有助于不同应用程序间代码的重用。
1.2.3 Characteristic(特征)
characteristic 特征,BLE 主从机的通信均是通过 characteristic 来实现,可以理解为一个标签,通过这个标签可以获取或者写入想要的内容。
1.2.4 UUID(通用唯一识别码)
uuid 通用唯一识别码,我们刚才提到的 service 和 characteristic 都需要一个唯一的 uuid 来标识;
每个从机都会有一个 profile,不管是自定义的 simpleprofile,还是标准的防丢器 profile,他们都是由一些 service 组成,每个 service 又包含了多个 characteristic,主机和从机之间的通信,均是通过characteristic来实现。
1.3 ESP32蓝牙应用结构
蓝牙是⼀种短距通信系统,其关键特性包括鲁棒性、低功耗、低成本等。蓝牙系统分为两种不同的技术:经典蓝牙 (Classic Bluetooth) 和蓝牙低功耗 (Bluetooth Low Energy)。
ESP32 支持双模蓝牙,即同时支持经典蓝牙和蓝牙低功耗。
从整体结构上,蓝牙可分为控制器 (Controller) 和主机 (Host) 两⼤部分:控制器包括了 PHY、Baseband、Link Controller、Link Manager、Device Manager、HCI 等模块,用于硬件接⼝管理、链路管理等等;主机则包括了 L2CAP、SMP、SDP、ATT、GATT、GAP 以及各种规范,构建了向应用层提供接口的基础,方便应用层对蓝牙系统的访问。主机可以与控制器运行在同⼀个宿主上,也可以分布在不同的宿主上。ESP32 可以支持上述两种方式。
1.4 Bluedroid主机架构
在 ESP-IDF 中,使用经过大量修改后的 BLUEDROID 作为蓝牙主机 (Classic BT + BLE)。BLUEDROID 拥有较为完善的功能,⽀持常用的规范和架构设计,同时也较为复杂。经过大量修改后,BLUEDROID 保留了大多数 BTA 层以下的代码,几乎完全删去了 BTIF 层的代码,使用了较为精简的 BTC 层作为内置规范及 Misc 控制层。修改后的 BLUEDROID 及其与控制器之间的关系如下图:
二、API说明
以下控制器和虚拟 HCI 接口位于 bt/include/esp32/include/esp_bt.h。
2.1 esp_bt_controller_mem_release
2.2 esp_bt_controller_init
2.3 esp_bt_controller_enable
以下 GATT 接口位于 bt/host/bluedroid/api/include/api/esp_bt_main.h 和 bt/host/bluedroid/api/include/api/esp_gatts_api.h。
2.4 esp_bluedroid_init
2.5 esp_bluedroid_enable
2.6 esp_ble_gatts_register_callback
2.7 esp_ble_gatts_app_register
2.8 esp_ble_gatts_create_service
2.9 esp_ble_gatts_add_char
2.10 esp_ble_gatts_add_char_descr
2.11 esp_ble_gatts_start_service
2.12 esp_ble_gatts_send_indicate
2.13 esp_ble_gatts_send_response
2.14 esp_ble_gatts_get_attr_value
三、蓝牙4.0通信实现过程
- 扫描蓝牙BLE终端设备,对应esp32就是广播给大家供扫描
- 连接蓝牙BLE终端设备,pad扫描到后去连接
- 启动服务发现,连接到esp32后获取相应的服务。
连接成功后,我们就要去寻找我们所需要的服务,这里需要先启动服务发现。 - 获取Characteristic
之前我们说过,我们的最终目的就是获取Characteristic来进行通信,正常情况下,我们可以从硬件工程师那边得到serviceUUID和characteristicUUID,也就是我们所比喻的班级号和学号,以此来获得我们的characteristic。 - 开始通信
我们在得到Characteristic后,就可以开始读写操作进行通信了。
a. 对于读操作来说,读取BLE终端设备返回的数据会通过回调方法mGattCallback中的onCharacteristicChanged函数返回。
b. 对于写操作来说,可以通过向Characteristic写入指令以此来达到控制BLE终端设备的目的
四、Demo程序GATT启动流程
使用 esp-idf\\examples\\bluetooth\\bluedroid\\ble\\gatt_server 中的例程
.........
//esp_bt_controller_config_t是蓝牙控制器配置结构体,这里使用了一个默认的参数
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;
}
//使能蓝牙控制器,mode是蓝牙模式,如果想要动态改变蓝牙模式不能直接调用该函数,
//应该先用disable关闭蓝牙再使用该API来改变蓝牙模式
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;
}
//初始化蓝牙并分配系统资源,它应该被第一个调用
/*
蓝牙栈bluedroid stack包括了BT和BLE使用的基本的define和API
初始化蓝牙栈以后并不能直接使用蓝牙功能,
还需要用FSM管理蓝牙连接情况
*/
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;
}
//建立蓝牙的FSM(有限状态机)
//这里使用回调函数来控制每个状态下的响应,需要将其在GATT和GAP层的回调函数注册
/*gatts_event_handler和gap_event_handler处理蓝牙栈可能发生的所有情况,达到FSM的效果*/
ret = esp_ble_gatts_register_callback(gatts_event_handler);
if (ret){
ESP_LOGE(GATTS_TAG, "gatts register error, error code = %x", ret);
return;
}
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret){
ESP_LOGE(GATTS_TAG, "gap register error, error code = %x", ret);
return;
}
//下面创建了BLE GATT服务A,相当于1个独立的应用程序
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;
}
//下面创建了BLE GATT服务B,相当于1个独立的应用程序
ret = esp_ble_gatts_app_register(PROFILE_B_APP_ID);
if (ret){
ESP_LOGE(GATTS_TAG, "gatts app register error, error code = %x", ret);
return;
}
/*
设置了MTU的值(经过MTU交换,从而设置一个PDU中最大能够交换的数据量)。
例如:主设备发出一个1000字节的MTU请求,但是从设备回应的MTU是500字节,那么今后双方要以较小的值500字节作为以后的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);
}
.......
五、服务数据结构体设置
一个GATT 服务器应用程序架构(由Application Profiles组织起来)如下:
每个Profile定义为一个结构体,结构体成员依赖于该Application Profile 实现的services服务和characteristic特征。结构体成员还包括GATT interface(GATT 接口)、Application ID(应用程序ID)和处理profile事件的回调函数。
每个profile包括GATT interface(GATT 接口)、Application ID(应用程序ID)、 Connection ID(连接ID)、Service Handle(服务句柄)、Service ID(服务ID)、Characteristic handle(特征句柄)、Characteristic UUID(特征UUID)、ATT权限、Characteristic Properties、描述符句柄、描述符UUID。
如果Characteristic支持通知(notifications)或指示(indicatons),它就必须是实现CCCD(Client Characteristic Configuration Descriptor)----这是额外的ATT。描述符有一个句柄和UUID。如:
struct gatts_profile_inst {
esp_gatts_cb_t gatts_cb; //GATT的回调函数
uint16_t gatts_if; //GATT的接口
uint16_t app_id; //应用的ID
uint16_t conn_id; //连接的ID
uint16_t service_handle; //服务Service句柄
esp_gatt_srvc_id_t service_id; //服务Service ID
uint16_t char_handle; //特征Characteristic句柄
esp_bt_uuid_t char_uuid; //特征Characteristic的UUID
esp_gatt_perm_t perm; //特征属性Attribute 授权
esp_gatt_char_prop_t property; //特征Characteristic的特性
uint16_t descr_handle; //描述descriptor句柄
esp_bt_uuid_t descr_uuid; //描述descriptorUUID
};
Application Profile存储在数组中,并分配相应的回调函数gatts_profile_a_event_handler()
和 gatts_profile_b_event_handler()
。GATT 客户端上的不同应用程序使用不同的接口,由 gatts_if 参数表示。对于初始化,此参数设置为ESP_GATT_IF_NONE
,这意味着应用程序配置文件尚未链接到任何客户端。
/* One gatt-based profile one app_id and one gatts_if, this array will store the gatts_if returned by ESP_GATTS_REG_EVT */
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, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
},
[PROFILE_B_APP_ID] = {
.gatts_cb = gatts_profile_b_event_handler, /* This demo does not implement, similar as profile A */
.gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
},
};
这是两个元素的数组。可以用Application ID来注册Application Profiles,Application ID是由应用程序分配的用来标识每个Profile。 通过这种方法,可以在一个Server中拥有多个Application Profile。
esp_ble_gatts_app_register (PROFILE_A_APP_ID);
esp_ble_gatts_app_register (PROFILE_B_APP_ID);
六、GATT事件处理程序
其作用就是建立了蓝牙GATT的FSM(有限状态机),callback回调函数处理从BLE堆栈推送到应用程序的所有事件。
回调函数的参数:
- event: esp_gatts_cb_event_t 这是一个枚举类型,表示调用该回调函数时的事件(或蓝牙的状态)
- gatts_if: esp_gatt_if_t (uint8_t) 这是GATT访问接口类型,通常在GATT客户端上不同的应用程序用不同的gatt_if(不同的Application profile对应不同的gatts_if) ,调用
esp_ble_gatts_app_register()
时,注册Application profile 就会有一个gatts_if。 - param: esp_ble_gatts_cb_param_t 指向回调函数的参数,是个联合体类型,不同的事件类型采用联合体内不同的成员结构体。
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
/*如果事件是注册事件,则为每个配置文件存储 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;
}
}
/*如果 gatts_if 等于 profile A,则调用 profile A cb handler,
* 所以这里调用每个 profile 的回调*/
do {
int idx;
for (idx = 0; idx < PROFILE_NUM; idx++) {
if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
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);
}
七、注册创建服务
当调用esp_ble_gatts_app_register()
注册一个应用程序Profile(Application Profile),将触发ESP_GATTS_REG_EVT
事件,除了可以完成对应profile的gatts_if的注册,还可以调用esp_bel_create_attr_tab()
来创建profile Attributes 表或创建一个服务esp_ble_gatts_create_service()
。
static void gatts_profile_b_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_B_APP_ID].service_id.is_primary = true;
gl_profile_tab[PROFILE_B_APP_ID].service_id.id.inst_id = 0x00;
gl_profile_tab[PROFILE_B_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[PROFILE_B_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_B;
esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_B_APP_ID].service_id, GATTS_NUM_HANDLE_TEST_B);
break;
…
}
句柄数定义为4:
#define GATTS_NUM_HANDLE_TEST_B 4
句柄是:
- 服务句柄
GATTS_SERVICE_UUID_TEST_B 0x00EE
- 特征手柄
GATTS_CHAR_UUID_TEST_B 0xEE01
- 特征值句柄
- 特征描述符句柄
GATTS_DESCR_UUID_TEST_B 0x2222
该服务被定义为具有 16 位 UUID 长度的主要服务。服务 ID 使用实例 ID = 0 和由 定义的 UUID 进行初始化GATTS_SERVICE_UUID_TEST_A。
服务实例 ID 可用于区分具有相同 UUID 的多个服务。在此示例中,由于每个应用程序配置文件只有一个服务并且服务具有不同的 UUID,因此在配置文件 A 和 B 中可以将服务实例 ID 定义为 0。但是,如果只有一个应用程序配置文件具有两个服务使用相同的 UUID,则有必要使用不同的实例 ID 来引用一个或另一个服务。
demo中的gatts_event_handler()
回调函数—调用esp_ble_gatts_app_register()
,触发ESP_GATTS_REG_EVT
时,完成对每个profile 的gatts_if 的注册。
gl_profile_tab[param->reg.app_id].gatts_if = gatts_if;
如果gatts_if == 某个Profile的gatts_if时,调用对应profile的回调函数处理事情。
if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
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);
}
}
八、启动服务和创建特征
8.1 启动服务
当一个服务service创建成功后,由该profile GATT handler 管理的 ESP_GATTS_CREATE_EVT
事件被触发,在这个事件可以启动服务和添加特征characteristics到服务中。调用esp_ble_gatts_start_service()
来启动指定服务。
case ESP_GATTS_CREATE_EVT:
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;
首先,由BLE堆栈生成生成的服务句柄(service handle)存储在配置文件Profile表中,应用层将用服务句柄来引用这个服务。调用esp_ble_gatts_start_service()
和先前产生服务句柄来启动服务。
8.2 创建特征
Characteristic是在GATT规范中最小的逻辑数据单元,由一个Value和多个描述特性的Desciptior组成。实际上,在与蓝牙设备打交道,主要就是读写Characteristic的value来完成。 同样的,Characteristic也是通过16bit或128bit的UUID唯一标识。
我们根据蓝牙设备的协议用对应的Characteristci进行读写即可达到与其通信的目的。
添加特征到service中,调用esp_ble_gatts_add_char()
来添加characteristics连同characteristic权限和property(属性)到服务service中。
权限:
ESP_GATT_PERM_READ
: 允许读取特征值ESP_GATT_PERM_WRITE
: 允许写入特征值
特性:
ESP_GATT_CHAR_PROP_BIT_READ
: 可以读取特性ESP_GATT_CHAR_PROP_BIT_WRITE
: 特征可写ESP_GATT_CHAR_PROP_BIT_NOTIFY
: 特性可以通知值的变化
同时拥有读写权限和属性似乎是多余的。但是,属性的读写属性是向客户端显示的信息,目的是让客户端知道服务器是否接受读写请求。从这个意义上说,这些属性充当客户端正确访问服务器资源的提示。另一方面,权限是授予客户端读取或写入该属性的授权。例如,如果客户端尝试写入它没有写入权限的属性,即使设置了写入属性,服务器也会拒绝该请求。
此外,demo还为表示特征提供了一个初始值gatts_demo_char1_val。初始值定义如下:
#define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40
uint8_t char1_str[] = {0x11,0x22,0x33};
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,
};
特征初始值必须是非空对象并且特征长度必须始终大于零,否则堆栈将返回错误。
最后,特性被配置为每次读取或写入特性时都需要手动发送响应,而不是让堆栈自动响应。这是通过将esp_ble_gatts_add_char()
函数的最后一个参数(表示属性响应控制参数)设置为ESP_GATT_RSP_BY_APP
或 NULL 来配置的。
七、创建特征描述符
当特征添加到service中成功时,触发ESP_GATTS_ADD_CHAR_EVT
事件。该事件返回由堆栈为刚刚添加的特征生成的句柄。该事件包括以下参数:
esp_gatt_status_t状态; /* !< 操作状态*/
uint16_t attr_handle; /* !< 特征属性句柄*/
uint16_t service_handle; /* !< 服务属性句柄*/
esp_bt_uuid_t char_uuid; /* !< 特征 uuid */
事件返回的属性句柄存储在配置文件表中,并且还设置了特征描述符长度和 UUID。使用该esp_ble_gatts_get_attr_value()
函数读取特征长度和值,然后打印以供参考。最后,使用该esp_ble_gatts_add_char_descr()
函数添加特征描述。使用的参数是服务句柄、描述符 UUID、写入和读取权限、初始值和自动响应设置。特征描述符的初始值可以是空指针,自动响应参数也设置为空,这意味着需要响应的请求必须手动回复。
case ESP_GATTS_ADD_CHAR_EVT: {
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;
}
添加描述符后,将ESP_GATTS_ADD_CHAR_DESCR_EVT
触发事件,在此示例中用于打印信息消息。
case ESP_GATTS_ADD_CHAR_DESCR_EVT:
ESP_LOGI(GATTS_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\\n",
param->add_char.status, param->add_char.attr_handle,
param->add_char.service_handle);
break;
九、连接事件
9.1 更新连接参数
一个ESP_GATTS_CONNECT_EVT
当客户端已连接到服务器GATT被触发。此事件用于更新连接参数,例如延迟、最小连接间隔、最大连接间隔和超时。连接参数存储在一个esp_ble_conn_update_params_t
结构中,然后传递给esp_ble_gap_update_conn_params()
函数。更新连接参数过程只需执行一次,因此配置文件 B 连接事件处理程序不包含该esp_ble_gap_update_conn_params()
函数。最后,事件返回的连接 ID 存储在配置文件表中。
配置文件 A 连接事件:
case ESP_GATTS_CONNECT_EVT: {
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 = 0x30; // max_int = 0x30*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:, is_conn %d",
param->connect.conn_id,
param->connect.remote_bda[0],
param->connect.remote_bda[1],
param->connect.remote_bda[2],
param->connect.remote_bda[以上是关于ESP32学习笔记(30)——BLE GATT服务端自定义服务和特征的主要内容,如果未能解决你的问题,请参考以下文章
ESP32学习笔记(33)——BLE GATT客户端发现服务和读写特征值