一文迅速上手 ESP32 bluedroid 蓝牙从机开发

news2024/9/21 4:40:16

前言

  1. 个人邮箱:zhangyixu02@gmail.com
  2. 该博客主要针对希望迅速上手 ESP32 蓝牙从机开发人员,因此,很多蓝牙技术细节知识并不会进行介绍,仅仅介绍我认为需要了解的 API 函数和回调内容。
  3. 本文主要是基于gatt_server demo来微调进行进行讲解。

app_main

NVS 初始化

  1. 首先我们来看 app_main() 函数。在该函数中,我们先初始化了 NVS 分区。其实这个部分可以不要。他主要用于存储一些 RF(射频)校准数据,以确保无线通信的性能和稳定性
  2. 当 ESP32 第一次启动并运行无线功能(如 Wi-Fi 或蓝牙)时,它会进行 RF 校准,以确定在当前硬件和环境条件下的最佳射频参数。
  3. 校准过程的结果会被存储在 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 );
  1. 如果是 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)
  1. 如果不初始化 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

协议栈初始化

  1. 我们需要知道,ESP32 是存在两个存在两个蓝牙协议栈的。
    • Bluedroid(默认) : 支持传统蓝牙(BR/EDR)低功耗蓝牙(BLE)。同时涉及传统蓝牙(BR/EDR)低功耗蓝牙(BLE) 的用例应当使用该协议栈。
    • Apache NimBLE : 仅支持低功耗蓝牙(BLE)。仅涉及低功耗蓝牙(BLE),建议使用该协议栈,因为在代码占用和运行时,NimBLE 对内存的要求较低
  2. 这里我将介绍的是 Bluedroid 协议栈开发。但是需要注意,虽然我们用的是 Bluedroid 协议栈,但实际情况却仅仅用到了 低功耗蓝牙(BLE) 的功能。因为,我们需要释放 传统蓝牙(BR/EDR) 的协议栈。
  3. 对蓝牙开发结构不清楚的朋友,可以看看 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;
    }
  1. 通过上述操作,我们成功的初始化了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;
    }
  1. 上述回调函数注册完成后,我们就可以根据需求来创建服务信息。这里我后续回进一步讲解。
    /* 注册一个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;
    }
  1. 当我们希望每一次传输的数据更多,能够拥有更大的吞吐量,那么就可以调用这个函数设置本地的 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);
    }
  1. 当进行完成上述操作后,我们可以初始化广播包了。需要注意的一点是,这里有一个宏定义 CONFIG_SET_RAW_ADV_DATA 。如果我们希望广播包的内容更加灵活,就可以定义 CONFIG_SET_RAW_ADV_DATA 宏。如果是新手,还是不建议打开这个宏。
  2. 如果不打开这个宏,那么我们广播数据就会被局限为发送名称发射功率连接间隔外观厂商自定义数据服务 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 服务启动流程

  1. 现在我们开始看程序执行流程。当我们调用 esp_ble_gatts_app_register() 注册一个 APP 任务,就会触发 ESP_GATTS_REG_EVT 事件。这里需要注意的一点是,APP ID 可以为 0 ~ 65535 的任意值,但是不能重复
  2. 之后我们需要在 ESP_GATTS_REG_EVT 事件中将协议栈分配的 gatts_if 进行存储。同时利用 esp_ble_gatts_create_service() 函数注册一个首要服务。
  3. esp_ble_gatts_create_service() 函数执行完成后,将会触发 ESP_GATTS_CREATE_EVT 事件。在该事件中,我们可以调用 esp_ble_gatts_add_char() 函数创建对应的特征信息,并且调用 esp_ble_gatts_start_service() 函数启动服务。
  4. 在调用 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)
  5. 在我们调用 esp_ble_gatts_start_service() 函数启动服务之后,将会触发 ESP_GATTS_START_EVT 事件,用于通知服务端(server)应用程序服务启动成功。
  6. 在我们调用 esp_ble_gatts_add_char_descr() 函数创建 特征配置描述符(characteristic descriptor) 后,将会触发 ESP_GATTS_ADD_CHAR_DESCR_EVT 事件。
  7. 例如下方,我们的日志打印信息可以看到任务执行流程。
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() 函数之后,触发该事件。

连接/断连事件

  1. 当成功与客户端(client) 建立连接,触发 ESP_GATTS_CONNECT_EVT 事件。我们在该事件中利用 esp_ble_gap_update_conn_params() 函数发起连接参数更新请求。
  2. 这里有两个注意的点,因为我们注册了两个 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:
  1. 正因为 ESP_GATTS_CONNECT_EVT 事件会触发两次,因此我们将连接参数更新的内容放在了 A 服务的回调函数中。如果放在公共的 GATT 回调函数中,那么连接参数更新将会被调用两次,而这两次的间隔事件太短,因此会出现如下警告。
W (22451) BT_L2CAP: l2cble_start_conn_update, the last connection update command still pending.
  1. 当与客户端(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) 断开连接时候,触发该事件。

读事件触发流程

  1. 当我们客户端(client) 发送读请求时,将会触发 ESP_GATTS_READ_EVT 事件。
    在这里插入图片描述
  2. 在该事件中,我们将要返回给客户端(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
  1. 当调用 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 写

  1. 当我们连接上设备后,点击发送按键。

在这里插入图片描述
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 : 写入的数据值。
  1. 通过上面的分析,我们现在就可以知道了,手机作为客户端(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 写

  1. 同理,我们执行写操作,只不过这次我们需要服务端(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

  1. 我们需要按照如下方法将双方的 MTU 设置为 23。

在这里插入图片描述
在这里插入图片描述

  1. 需要注意一点,prepare write 操作一定需要选择 Request。
    在这里插入图片描述
  2. 之后我们可以看到如下日志信息,我们一步一步来进行分析。
  3. 首先我们触发 ESP_GATTS_WRITE_EVT 事件发现这是一个准备写请求。那么就直接进入 example_write_event_env() 函数操作。因为在 prepare wirte 请求中,那么需要进行一些堆空间,以及判断传入数据是否超出堆空间大小。最终将收到的数据利用 esp_ble_gatts_send_response() 函数发送回去,并且存储在 a_prepare_write_env 或者 b_prepare_write_env 准备缓冲区。
  4. esp_ble_gatts_send_response() 函数会触发 ESP_GATTS_RESPONSE_EVT 事件表示发送了回包数据。
  5. 客户端(client) 收到回包数据后,继续将剩余的数据发送出来。此时再次触发 ESP_GATTS_WRITE_EVT 事件,因为这是 prepare wirte,因此进入 example_write_event_env() 函数操作,继续将发送回包,并且将剩余数据存储进 a_prepare_write_env 或者 b_prepare_write_env 准备缓冲区。
  6. esp_ble_gatts_send_response() 函数会触发 ESP_GATTS_RESPONSE_EVT 事件表示发送了回包数据。
  7. 客户端(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)

  1. 我们点击如下按键可以使能 notify 功能。
    在这里插入图片描述
  2. 亦或者可以通过如下方法数据数据使能 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
  1. 当我们启动 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
  1. 当关闭 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 服务初始化

  1. 在注册完 APP 之后,我们调用 advertise_init() 函数初始化广播数据。当我们调用 esp_ble_gap_config_adv_data() 函数时候,将会触发 ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT 事件。
  2. 之后调用 esp_ble_gap_config_adv_data() 函数,触发 ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT 事件。在该事件里面,我们调用 esp_ble_gap_start_advertising() 函数启动广播。
  3. 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() 函数触发。

连接事件

  1. 在连接过程中需要协商双方的收发数据,因此会触发 ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT 事件。
  2. 在连接成功后,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

参考

  1. ESP32 蓝牙 API
  2. BLE学习笔记(0.0) —— 基础概念(0)
  3. 乐鑫论坛 : Write a string to characteristic

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2073079.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

# ‘telnet‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。

‘telnet’ 不是内部或外部命令,也不是可运行的程序 或批处理文件。 一、报错描述: 1、当使用 telnet 命令,连接本地 tomcat 的 8005 端口时报错。 2、报错解释 这个错误表明系统无法识别telnet命令,因为它不是内置命令,也没有…

跳马(华为od机考题)

一、题目 1.原题 马是象棋(包括中国象棋和国际象棋)中的棋子, 走法是每步直一格再斜一格, 即先横着或直着走一格,然后再斜着走一个对角线, 可进可退,可越过河界,俗称“马走‘日’字。 给顶m行n列的棋盘&…

人工智能在专业领域的斗争

介绍 ChatGPT 等大型语言模型 (LLM) 在用自然语言讨论一般话题的能力方面令人印象深刻。然而,他们在医学、金融和法律等专业领域却举步维艰。这是由于缺乏真正的理解,并且注重模仿而不是智力。 大语言模型正处于炒作的顶峰。由于能够用自然语言回答和讨…

“Docker中部署Kibana:步骤与指南“

博主这篇文章是跟Elasticsearch那篇文章是有关系的,建议大家先去看: 轻松上手:Docker部署Elasticsearch,高效构建搜索引擎环境_docker 启动 es-CSDN博客 这篇博文,还有镜像下载不下来的情况,大家可以去看…

攻破:重定向 缓冲区

文章目录 前言:认识读文件read认识重定向&&缓冲区重定向现象及分析:dup2的介绍: 缓冲区的引入:缓冲区的理解: 前言: ​ 从上一章开始,我们进入了文件IO的学习,认识了文件描…

浅谈C# RabbitMQ

一、基本介绍 RabbitMQ——Rabbit Message Queue的简写,但不能仅仅理解其为消息队列,消息代理更合适。 RabbitMQ 是一个由 Erlang 语言开发的AMQP(高级消息队列协议)的开源实现,其内部结构如下: RabbitMQ作…

今年秋招太吓人了。(20届,在得物做Java开发)

有个学弟来问我诉苦最近好忙好累,说竞争压力特别大,让我给点建议,要不要放弃实习闷头搞秋招,我才意识到时间太快了,想想我都毕业几年了,感慨颇深,整理一下我的求职经验和目前的心得吧&#xff0…

SpingBoot集成kafka-发送读取消息示例

SpingBoot集成kafka开发 kafka的几个常见概念 1、springboot和kafka对应版本(重要)2、创建springboot项目,引入kafka依赖2.1、生产者EventProducer2.2、消费者EventConsumer2.3、启动生产者的方法SpringBoot01KafkaBaseApplication2.4、appli…

监控电脑屏幕的软件叫什么?6款电脑屏幕监控软件分享!

监控电脑屏幕的软件可以帮助企业和家长监控电脑的使用情况,确保工作和学习的效率与安全。 以下是六款常用的电脑屏幕监控软件及其特点: 1. Keylogger 特点:专注于企业数据安全和员工上网行为管理。 功能:全面的屏幕监控、上网…

Redis持久化(RDB、AOF、混合持久化)

目录 1、持久化机制 (1)RDB (2)AOF 2、混合持久化 3、总结 ❓为什么需要持久化? Redis 是一个基于内存的键值存储系统,它提供了非常快的数据访问速度,因为它不需要像传统的磁盘存储那样进…

竞猜足球核心算法源码

需要实现的功能如下: 仅用于学习 竞猜足球核心算法源码 package com.lotterysource.portsadmin.service; import com.aliyun.oss.common.utils.DateUtil; import com.fasterxml.jackson.core.type.TypeReference; import com.lotterysource.portsadmin.dbprovid…

进存销系统

摘 要 伴随着我国全面推动信息化的趋势,我国的很多行业都在朝着互联网的方向进发。商品销售行业也有很多挑战。这次论文介绍的进存销系统就是为了能够解决当前传统商品进存销存在的问题,使得商品进存销能够更加有效率。电商智能化管理必不可少的帮手有进…

功能安全实战系列02-RamTst(RamTest)开发介绍

本文框架 前言1. What(RamTst相关概念)1.1 后台检测1.2 前台检测1.3 RamTst对应状态机2.How?2.1 接口调用2.2 配置开发2.3 测试模式选择前言 在本系列笔者将结合工作中对功能安全实战部分的开发经验进一步介绍常用,包括Memory(Flash,Ram)失效检测,程序运行时序时间检测,及…

数字模拟IC设计前端、后端、前仿、后仿新版虚拟机

虚拟化平台:VMware Workstation 15 Pro以上版本 操作系统:CentOS Linux release 7.9.2009 (Core) 一、射频模拟IC设计必备软件 Cadence IC06.18.350/IC23.10.080(virtuoso) Cadence SPECTRE23.10.538-isr10 Cadence ASSURA04.…

Python优化算法15——麻雀搜索算法(SSA)

科研里面优化算法都用的多,尤其是各种动物园里面的智能仿生优化算法,但是目前都是MATLAB的代码多,python几乎没有什么包,这次把优化算法系列的代码都从底层手写开始。 需要看以前的优化算法文章可以参考:Python优化算…

Mozilla为本地音频到文本翻译开发Whisperfile引擎

Mozilla Ocho 小组正进行 Mozilla 的"创新和实验"。Llamafile 用于将大型语言模型以单个文件的形式发布,以便在不同的硬件/软件间轻松执行。Whisperfile 是一项将音频轻松转化为文本的新引擎。 正如其名称所暗示的,Whisperfile 是围绕 OpenAI…

嵌入式UI开发-lvgl+wsl2+vscode系列:10、控件(Widgets)(三)

1、scale(标尺) 示例1 #include "../../lv_examples.h" #if LV_USE_SCALE && LV_BUILD_EXAMPLES/*** 简单的水平标尺*/ void lv_example_scale_1(void) {lv_obj_t * scale lv_scale_create(lv_screen_active());lv_obj_set_size(sca…

MyBatis源码(6)拦截器

1、目标 本文的主要目标是学习MyBatis拦截器的源码,本文将以插入操作为例debug拦截器相关的源码 2、拦截器源码分析 调用mapper接口的insert插入记录方法,会调用SqlSession对象的insert方法 SqlSession执行insert方法 Spring容器会创建SqlSessionTemp…

Python画笔案例-011 绘制草帽

1、绘制草帽 通过 python 的turtle 库绘制一个草帽的图案,如下图: 2、实现代码 绘制以上草帽的图案,代码如下: """草帽.py """ import turtle # 导入海龟模块turtle.delay(20) …

多动症的孩子有哪些症状表现?

在星启帆自闭症儿童康复机构,我们不仅关注自闭症儿童的成长与康复,也深刻认识到多动症对儿童日常生活、学习和社交的深远影响。多动症,全称注意缺陷多动障碍,是一种常见于儿童时期的神经发育性疾病,其症状表现多种多样…