前言
- 个人邮箱:zhangyixu02@gmail.com
- 该博客主要针对希望迅速上手 ESP32 蓝牙从机开发人员,因此,很多蓝牙技术细节知识并不会进行介绍,仅仅介绍我认为需要了解的 API 函数和回调内容。
- 本文主要是基于gatt_server demo来微调进行进行讲解。
app_main
NVS 初始化
- 首先我们来看
app_main()
函数。在该函数中,我们先初始化了 NVS 分区。其实这个部分可以不要。他主要用于存储一些 RF(射频)校准数据,以确保无线通信的性能和稳定性。 - 当 ESP32 第一次启动并运行无线功能(如 Wi-Fi 或蓝牙)时,它会进行 RF 校准,以确定在当前硬件和环境条件下的最佳射频参数。
- 校准过程的结果会被存储在 NVS 中,这样在后续启动时,设备可以直接使用这些校准数据,避免每次启动都需要重新校准。从而提高设备运行效率。
// 初始化 NVS.
ret = nvs_flash_init();
/* 如果 NVS 分区没有剩余空间或者 NVS 分区包含新格式数据,当前版本无法识别。*/
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
/* 清空 NVS 分区数据 */
ESP_ERROR_CHECK(nvs_flash_erase());
/* NVS 分区重新进行初始化 */
ret = nvs_flash_init();
}
ESP_ERROR_CHECK( ret );
- 如果是 NVS 分区没有 RF(射频)校准数据,那么就会出现如下日志信息,之后就开始 RF 校准,并将相关校准信息存放进 NVS 分区。
W (602) phy_init: failed to load RF calibration data (0x1102), falling back to full calibration
W (642) phy_init: saving new calibration data because of checksum failure, mode(2)
- 如果不初始化 NVS 分区,将会出现如下错误。虽然不影响程序的正常运行,但是这样每次芯片启动都需要进行 RF 校准,从而拖慢启动速度。
// 将 NVS 初始化注释掉
// // 初始化 NVS.
// ret = nvs_flash_init();
// /* 如果 NVS 分区没有剩余空间或者 NVS 分区包含新格式数据,当前版本无法识别。*/
// if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
// /* 清空 NVS 分区数据 */
// ESP_ERROR_CHECK(nvs_flash_erase());
// /* NVS 分区重新进行初始化 */
// ret = nvs_flash_init();
// }
// ESP_ERROR_CHECK( ret );
E (599) phy_init: esp_phy_load_cal_data_from_nvs: NVS has not been initialized. Call nvs_flash_init before starting WiFi/BT.
W (619) phy_init: failed to load RF calibration data (0x1101), falling back to full calibration
W (659) phy_init: saving new calibration data because of checksum failure, mode(2)
I (659) phy: libbtbb version: b97859f, Jun 4 2024, 16:44:27
E (669) BT_OSI: config_new: NVS not initialized. Call nvs_flash_init before initializing bluetooth.
W (679) BT_BTC: btc_config_init unable to load config file; starting unconfigured.
E (689) BT_OSI: config_save: NVS not initialized. Call nvs_flash_init before initializing bluetooth.
E (709) BT_OSI: config_save, err_code: 0x2
E (729) BT_OSI: config_save: NVS not initialized. Call nvs_flash_init before initializing bluetooth.
E (729) BT_OSI: config_save, err_code: 0x2
E (739) BT_OSI: config_save: NVS not initialized. Call nvs_flash_init before initializing bluetooth.
E (749) BT_OSI: config_save, err_code: 0x2
E (759) BT_OSI: config_save: NVS not initialized. Call nvs_flash_init before initializing bluetooth.
E (779) BT_OSI: config_save, err_code: 0x2
E (779) BT_OSI: config_save: NVS not initialized. Call nvs_flash_init before initializing bluetooth.
E (799) BT_OSI: config_save, err_code: 0x2
协议栈初始化
- 我们需要知道,ESP32 是存在两个存在两个蓝牙协议栈的。
- Bluedroid(默认) : 支持传统蓝牙(BR/EDR)和低功耗蓝牙(BLE)。同时涉及传统蓝牙(BR/EDR) 和低功耗蓝牙(BLE) 的用例应当使用该协议栈。
- Apache NimBLE : 仅支持低功耗蓝牙(BLE)。仅涉及低功耗蓝牙(BLE),建议使用该协议栈,因为在代码占用和运行时,NimBLE 对内存的要求较低。
- 这里我将介绍的是 Bluedroid 协议栈开发。但是需要注意,虽然我们用的是 Bluedroid 协议栈,但实际情况却仅仅用到了 低功耗蓝牙(BLE) 的功能。因为,我们需要释放 传统蓝牙(BR/EDR) 的协议栈。
- 对蓝牙开发结构不清楚的朋友,可以看看 BLE学习笔记(0.0) —— 基础概念(0)
/* 释放经典蓝牙协议栈空间 */
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
/* 初始化 Control 层 */
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", __func__, esp_err_to_name(ret));
return;
}
/* 使能 Control 层 */
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(GATTS_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
/* 初始化 HOST层,Bluedroid 协议栈 */
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(GATTS_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
/* 使能 HOST层,Bluedroid 协议栈 */
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(GATTS_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
- 通过上述操作,我们成功的初始化了ESP32 的 bluedroid 协议栈。那么,此时就需要创建 GAP 和 GATT 任务。当我们触发 GAP 或者 GATT 事件时候,就会进入到如下的回调函数中。需要注意,如下的两个函数只能注册一次。
/* 注册 GATTS 事件回调函数 */
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;
}
- 上述回调函数注册完成后,我们就可以根据需求来创建服务信息。这里我后续回进一步讲解。
/* 注册一个app_id, 协议栈将会分配一个对应的 gatts_if,用于标识一个 GATT 服务。
* 调用这个函数就会触发 esp_ble_gatts_register_callback() 注册的回调函数中的 ESP_GATTS_REG_EVT 事件
*/
ret = esp_ble_gatts_app_register(gl_profile_tab[PROFILE_A].app_id);
if (ret){
ESP_LOGE(GATTS_TAG, "gatts app register error, error code = %x", ret);
return;
}
/* 注册一个app_id, 协议栈将会分配一个对应的 gatts_if,用于标识一个 GATT 服务。
* 调用这个函数就会触发 esp_ble_gatts_register_callback() 注册的回调函数中的 ESP_GATTS_REG_EVT 事件
*/
ret = esp_ble_gatts_app_register(gl_profile_tab[PROFILE_B].app_id);
if (ret){
ESP_LOGE(GATTS_TAG, "gatts app register error, error code = %x", ret);
return;
}
- 当我们希望每一次传输的数据更多,能够拥有更大的吞吐量,那么就可以调用这个函数设置本地的 MTU。需要注意的一点是,这个实际的 MTU 大小是要根据与客户端(client) 协商后得到的。
/* 设置本地 MTU 为 500 字节,实际交换时刻的 MTU 要通过与**客户端(client)** 协商后得到。
* MTU 大小范围为 23 ~ 517 字节之间,如果没有调用该函数,则默认为 23 字节。
*/
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);
}
- 当进行完成上述操作后,我们可以初始化广播包了。需要注意的一点是,这里有一个宏定义
CONFIG_SET_RAW_ADV_DATA
。如果我们希望广播包的内容更加灵活,就可以定义CONFIG_SET_RAW_ADV_DATA
宏。如果是新手,还是不建议打开这个宏。 - 如果不打开这个宏,那么我们广播数据就会被局限为发送名称、发射功率、连接间隔、外观、厂商自定义数据、服务 UUID 和数据、广播发现模式标志位。如果你希望发送一些其他格式的数据例如 BTHome,使用
esp_ble_gap_config_adv_data()
函数就不是合适的选择。那么我们就需要使用原始数据包函数。
static void advertise_init(void)
{
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
}
GATT 回调事件
GATT 服务启动流程
- 现在我们开始看程序执行流程。当我们调用
esp_ble_gatts_app_register()
注册一个 APP 任务,就会触发 ESP_GATTS_REG_EVT 事件。这里需要注意的一点是,APP ID 可以为 0 ~ 65535 的任意值,但是不能重复。 - 之后我们需要在 ESP_GATTS_REG_EVT 事件中将协议栈分配的 gatts_if 进行存储。同时利用
esp_ble_gatts_create_service()
函数注册一个首要服务。 esp_ble_gatts_create_service()
函数执行完成后,将会触发 ESP_GATTS_CREATE_EVT 事件。在该事件中,我们可以调用esp_ble_gatts_add_char()
函数创建对应的特征信息,并且调用esp_ble_gatts_start_service()
函数启动服务。- 在调用
esp_ble_gatts_add_char()
函数之后,将会触发 ESP_GATTS_ADD_CHAR_EVT 事件。如果特征信息中,含有 ESP_GATT_CHAR_PROP_BIT_NOTIFY 或者 ESP_GATT_CHAR_PROP_BIT_INDICATE ,那么就必须调用esp_ble_gatts_add_char_descr()
函数创建一个 特征配置描述符(characteristic descriptor) 。 - 在我们调用
esp_ble_gatts_start_service()
函数启动服务之后,将会触发 ESP_GATTS_START_EVT 事件,用于通知服务端(server)应用程序服务启动成功。 - 在我们调用
esp_ble_gatts_add_char_descr()
函数创建 特征配置描述符(characteristic descriptor) 后,将会触发 ESP_GATTS_ADD_CHAR_DESCR_EVT 事件。 - 例如下方,我们的日志打印信息可以看到任务执行流程。
I (741) GATTS_DEMO: GATTS event = 0
I (741) GATTS_DEMO: gatts_if = 4,param->reg.app_id = 0x66
I (751) GATTS_DEMO: REGISTER_APP_EVT, status 0, app_id 102
I (761) GATTS_DEMO: GATTS event = 7
I (771) GATTS_DEMO: CREATE_SERVICE_EVT, status 0, service_handle 44
I (781) GATTS_DEMO: GATTS event = 9
I (791) GATTS_DEMO: ADD_CHAR_EVT, status 0, attr_handle 46, service_handle 44
I (801) GATTS_DEMO: GATTS event = 12
I (801) GATTS_DEMO: SERVICE_START_EVT, status 0, service_handle 44
I (811) GATTS_DEMO: GATTS event = 10
- ESP_GATTS_REG_EVT : APP 注册事件。当调用
esp_ble_gatts_app_register()
函数之后,会触发该。 - ESP_GATTS_CREATE_EVT : 服务创建完成事件。当调用
esp_ble_gatts_create_service()
函数之后,会触发该事件。 - ESP_GATTS_ADD_CHAR_EVT : 特征申明(characteristic declaration) 和 特征值(characteristic value) 添加成功事件。 当调用
esp_ble_gatts_add_char()
函数之后触发该事件。 - ESP_GATTS_START_EVT : GATT 服务启动成功事件。当调用
esp_ble_gatts_start_service()
函数之后,会触发该事件。 - ESP_GATTS_ADD_CHAR_DESCR_EVT : 特征配置描述符(characteristic descriptor) 添加成功事件。当调用
esp_ble_gatts_add_char_descr()
函数之后,触发该事件。
连接/断连事件
- 当成功与客户端(client) 建立连接,触发 ESP_GATTS_CONNECT_EVT 事件。我们在该事件中利用
esp_ble_gap_update_conn_params()
函数发起连接参数更新请求。 - 这里有两个注意的点,因为我们注册了两个 APP,因此我们会发现日志信息中有两次 ESP_GATTS_CONNECT_EVT 事件触发。
I (1619781) GATTS_DEMO: GATTS event = 14
I (1619791) GATTS_DEMO: ESP_GATTS_CONNECT_EVT, conn_id 0, remote 56:ae:b5:84:6f:16:
I (1619801) GATTS_DEMO: GATTS event = 14
I (1619811) GATTS_DEMO: CONNECT_EVT, conn_id 0, remote 56:ae:b5:84:6f:16:
- 正因为 ESP_GATTS_CONNECT_EVT 事件会触发两次,因此我们将连接参数更新的内容放在了 A 服务的回调函数中。如果放在公共的 GATT 回调函数中,那么连接参数更新将会被调用两次,而这两次的间隔事件太短,因此会出现如下警告。
W (22451) BT_L2CAP: l2cble_start_conn_update, the last connection update command still pending.
- 当与客户端(client) 连接断开,将会触发 ESP_GATTS_DISCONNECT_EVT 事件。
I (2275581) GATTS_DEMO: GATTS event = 15
I (2275581) GATTS_DEMO: ESP_GATTS_DISCONNECT_EVT, disconnect reason 0x13
- ESP_GATTS_CONNECT_EVT : 当与客户端(client) 连接,触发该事件。
- ESP_GATTS_DISCONNECT_EVT : 与客户端(client) 断开连接时候,触发该事件。
读事件触发流程
- 当我们客户端(client) 发送读请求时,将会触发 ESP_GATTS_READ_EVT 事件。
- 在该事件中,我们将要返回给客户端(client) 的内容,通过
esp_ble_gatts_send_response()
函数返回。需要注意,一定要调用esp_ble_gatts_send_response()
函数进行返回,否则客户端(client) 将会一直死等数据,直到断连。
I (2029071) GATTS_DEMO: GATTS event = 1
I (2029071) GATTS_DEMO: GATT_READ_EVT, conn_id 0, trans_id 1, handle 46
I (2029071) GATTS_DEMO: GATTS event = 21
- 当调用
esp_ble_gatts_send_response()
函数之后,将会触发 ESP_GATTS_RESPONSE_EVT 事件,表示回包数据发送成功。
- ESP_GATTS_READ_EVT : 当客户端(client) 发起读请求,触发该事件。
- ESP_GATTS_RESPONSE_EVT : GATT 服务回报完成。该事件由
esp_ble_gatts_send_response()
函数执行完成后触发。
写事件触发流程
Command 写
- 当我们连接上设备后,点击发送按键。
2. 按照如下步骤发送数据。
3. 此时 ESP32 将会触发 ESP_GATTS_WRITE_EVT 事件。
- param->write.conn_id : 如果是多连接,我们可以利用这个参数判断是那个主机发送的操作数据。因为该实验是一台手机作为客户端(client) ,一个 ESP32 作为服务端(Server)的点对点通讯,因此这个参数用不上。
- param->write.trans_id : 用于标识一次写操作。因为客户端(client) 可能在同一时间对服务端(server)存在大量的写操作,那么就可以利用这个参数来确定服务端(server)到底是在对哪一个写操作回复。
- param->write.handle : 用于表示是哪一个目标特征(Characteristic)或描述符(descriptor)的写操作。
- param->write.need_rsp : 是否需要服务端(server)进行回复。因为这里我们是 Command,因此该参数是 false。
- param->write.is_prep : 是否为准备写操作。当写入的数据大于 MTU-3 时候,将进行分段写数据,那么该参数为 true。
- param->write.len : 写入数据值长度。
- param->write.value : 写入的数据值。
- 通过上面的分析,我们现在就可以知道了,手机作为客户端(client) 进行 Command 写操作能够触发 ESP32 的 ESP_GATTS_WRITE_EVT 事件,而且无需回应,因此打印信息如下。
I (12711) GATTS_DEMO: GATTS event = 2
I (12711) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 1, handle 46
I (12711) GATTS_DEMO: It's not prepare write, value len 2, value :
I (12721) GATTS_DEMO: 55 66
I (12731) GATTS_DEMO: write event, but not need to response
- ESP_GATTS_WRITE_EVT : 当客户端(client) 发起写请求,触发该事件。
Request 写
- 同理,我们执行写操作,只不过这次我们需要服务端(server)进行数据回复。
2. 当客户端(client) 执行如上操作,将会触发 ESP_GATTS_WRITE_EVT 事件,在该事件中,我们调用 example_write_event_env()
处理写操作。
3. 因为客户端(client) 是需要数据回应。并且写入的数据小于 MTU - 3(初始化的时候。调用 esp_ble_gatt_set_local_mtu()
,函数设置的 MTU 为23),因此 param->write.need_rsp 为 false ,不是准备写操作,直接调用 esp_ble_gatts_send_response()
进行数据包的回复。
4. 在调用 esp_ble_gatts_send_response()
函数之后,将会触发 ESP_GATTS_RESPONSE_EVT 事件告诉应用层表示回包数据发送完成。
I (56091) GATTS_DEMO: GATTS event = 2
I (56091) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 3, handle 46
I (56091) GATTS_DEMO: It's not prepare write, value len 2, value :
I (56101) GATTS_DEMO: 11 22
I (56111) GATTS_DEMO: write event, but not prepare
I (56121) GATTS_DEMO: GATTS event = 21
- ESP_GATTS_WRITE_EVT : 当客户端(client) 发起写请求,触发该事件。
- ESP_GATTS_RESPONSE_EVT : GATT 服务回报完成。该事件由
esp_ble_gatts_send_response()
函数执行完成后触发。
prepare write
- 我们需要按照如下方法将双方的 MTU 设置为 23。
- 需要注意一点,prepare write 操作一定需要选择 Request。
- 之后我们可以看到如下日志信息,我们一步一步来进行分析。
- 首先我们触发 ESP_GATTS_WRITE_EVT 事件发现这是一个准备写请求。那么就直接进入
example_write_event_env()
函数操作。因为在 prepare wirte 请求中,那么需要进行一些堆空间,以及判断传入数据是否超出堆空间大小。最终将收到的数据利用esp_ble_gatts_send_response()
函数发送回去,并且存储在 a_prepare_write_env 或者 b_prepare_write_env 准备缓冲区。 esp_ble_gatts_send_response()
函数会触发 ESP_GATTS_RESPONSE_EVT 事件表示发送了回包数据。- 客户端(client) 收到回包数据后,继续将剩余的数据发送出来。此时再次触发 ESP_GATTS_WRITE_EVT 事件,因为这是 prepare wirte,因此进入
example_write_event_env()
函数操作,继续将发送回包,并且将剩余数据存储进 a_prepare_write_env 或者 b_prepare_write_env 准备缓冲区。 esp_ble_gatts_send_response()
函数会触发 ESP_GATTS_RESPONSE_EVT 事件表示发送了回包数据。- 客户端(client) 收到回包之后,发现数据发完了,此时执行 Execute Write Request,然后 ESP32 触发 ESP_GATTS_EXEC_WRITE_EVT 事件。在该事件中,ESP32 需要调用
esp_ble_gatts_send_response()
函数回发响应数据。然后调用example_exec_write_event_env()
函数将收到的数据进行打印处理,并且释放申请到的缓冲区。
I (45331) GATTS_DEMO: GATTS event = 2
I (45331) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 1, handle 46
I (45331) GATTS_DEMO: GATTS event = 21
I (45391) GATTS_DEMO: GATTS event = 2
I (45391) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 2, handle 46
I (45391) GATTS_DEMO: GATTS event = 21
I (45451) GATTS_DEMO: GATTS event = 3
I (45451) GATTS_DEMO: ESP_GATTS_EXEC_WRITE_EVT
I (45451) GATTS_DEMO: 00 11 22 33 44 55 66 77 88 99 00 11 22 33 44 55
I (45451) GATTS_DEMO: 66 77 88 99 00
I (45461) GATTS_DEMO: GATTS event = 21
- ESP_GATTS_WRITE_EVT : 当客户端(client) 发起写请求,触发该事件。
- ESP_GATTS_RESPONSE_EVT : GATT 服务回报完成。该事件由
esp_ble_gatts_send_response()
函数执行完成后触发。 - ESP_GATTS_EXEC_WRITE_EVT : 客户端(client) 发送 Execute Write Request 触发该事件。
特征配置描述符(characteristic descriptor) 写
- 我们点击如下按键可以使能 notify 功能。
- 亦或者可以通过如下方法数据数据使能 notify。
3. 这里感觉有个 bug,理论上来说,notify 应该是不需要回包的,因此不会触发 ESP_GATTS_CONF_EVT 事件的,但是这里依旧有触发,这里建议抓包测试一下。
4. 除了 ESP_GATTS_CONF_EVT 事件,其他两个事件就很好理解了。客户端(client) 向 ESP32 特征配置描述 中写入数据是需要回包的,因此就会触发 ESP_GATTS_WRITE_EVT 和 ESP_GATTS_RESPONSE_EVT 事件。
I (14481) GATTS_DEMO: GATTS event = 2
I (14481) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 1, handle 47
I (14481) GATTS_DEMO: It's not prepare write, value len 2, value :
I (14491) GATTS_DEMO: 01 00
I (14501) GATTS_DEMO: profile b notify enable
I (14511) GATTS_DEMO: write event, but not prepare
I (14521) GATTS_DEMO: GATTS event = 21
I (14521) GATTS_DEMO: GATTS event = 5
I (14531) GATTS_DEMO: ESP_GATTS_CONF_EVT status 0 attr_handle 46
- 当我们启动 indicate 或者输入 02 00 时候,将会触发如下事件。需要注意的一点是,notify 和 indicate 两者只能存在一个。
I (94731) GATTS_DEMO: GATTS event = 2
I (94731) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 3, handle 47
I (94731) GATTS_DEMO: It's not prepare write, value len 2, value :
I (94741) GATTS_DEMO: 02 00
I (94751) GATTS_DEMO: indicate enable
I (94751) GATTS_DEMO: write event, but not prepare
I (94761) GATTS_DEMO: GATTS event = 21
I (94821) GATTS_DEMO: GATTS event = 5
I (94821) GATTS_DEMO: ESP_GATTS_CONF_EVT status 0 attr_handle 46
- 当关闭 indicate/notify 或者输入 00 00 时候,将触发如下事件。需要注意的是,如果要关,那么两个都会同时关闭。
I (42801) GATTS_DEMO: GATTS event = 2
I (42801) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 2, handle 47
I (42801) GATTS_DEMO: It's not prepare write, value len 2, value :
I (42811) GATTS_DEMO: 00 00
I (42821) GATTS_DEMO: notify/indicate disable
I (42831) GATTS_DEMO: write event, but not prepare
I (42831) GATTS_DEMO: GATTS event = 21
- ESP_GATTS_WRITE_EVT : 当客户端(client) 发起写请求,触发该事件。
- ESP_GATTS_RESPONSE_EVT : GATT 服务回报完成。该事件由
esp_ble_gatts_send_response()
函数执行完成后触发。 - ESP_GATTS_CONF_EVT : 调用
esp_ble_gatts_send_indicate()
函数触发。
GAP 回调事件
GAP 服务初始化
- 在注册完 APP 之后,我们调用 advertise_init() 函数初始化广播数据。当我们调用
esp_ble_gap_config_adv_data()
函数时候,将会触发 ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT 事件。 - 之后调用
esp_ble_gap_config_adv_data()
函数,触发 ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT 事件。在该事件里面,我们调用esp_ble_gap_start_advertising()
函数启动广播。 esp_ble_gap_start_advertising()
函数将会触发 ESP_GAP_BLE_ADV_START_COMPLETE_EVT 事件。我们可以在该事件中知道广播是否成功启动。
I (841) GATTS_DEMO: GAP Event:0
I (841) GATTS_DEMO: GAP Event:1
I (851) GATTS_DEMO: GAP Event:6
- ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT : 设置广播数据完成事件。当调用
esp_ble_gap_config_adv_data()
函数,其中 set_scan_rsp 设置为 false 时触发该事件。 - ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT : 设置广播回包数据完成事件。当调用
esp_ble_gap_config_adv_data()
函数,其中 set_scan_rsp 设置为 true 时触发该事件。 - ESP_GAP_BLE_ADV_START_COMPLETE_EVT : 当调用
esp_ble_gap_start_advertising()
函数触发。
连接事件
- 在连接过程中需要协商双方的收发数据,因此会触发 ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT 事件。
- 在连接成功后,ESP_GATTS_CONNECT_EVT 事件中调用的
esp_ble_gap_update_conn_params()
函数,将会触发连接参数更新,因此将会触发 ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT 事件。我们可以在该事件中看到最终的从机延迟,连接间隔,监管超时等信息。
I (1619781) GATTS_DEMO: GAP Event:21
I (1619781) GATTS_DEMO: packet length updated: rx = 27, tx = 251, status = 0
# ... 这里省略 GATT 回调
I (1620171) GATTS_DEMO: GAP Event:20
I (1620171) GATTS_DEMO: update connection params status = 0, min_int = 16, max_int = 32,conn_int = 6,latency = 0, timeout = 500
I (1620501) GATTS_DEMO: GAP Event:20
I (1620501) GATTS_DEMO: update connection params status = 0, min_int = 0, max_int = 0,conn_int = 24,latency = 0, timeout = 400
参考
- ESP32 蓝牙 API
- BLE学习笔记(0.0) —— 基础概念(0)
- 乐鑫论坛 : Write a string to characteristic