ESP32连接BLE设备具体实现的说明

文章目录

  • 一、基础概念
  • 二、相关API参数与使用说明
  • 三、整体连接流程
  • 总结


本篇文章以ESP32C3平台作为主机连接血糖仪蓝牙设备的过程为例,对代码的实现进行分析与理解。


一、基础概念

在上手撕代码之前,让我们准备好砍柴刀,先使用nRF Connect APP连接血糖仪对Gatt协议概念以及各层次进行理解,下载链接自行百度,这里就不贴出来了,废话不多说,打开手机蓝牙连接血糖仪蓝牙设备,左图为血糖仪的所有服务项,分别是Generic Access、Device Information、Unknown Service、Unknown Service四项服务(Service),右图是UUID为0x1000的Unknown Service服务项的内容,该服务有四个特征(Characteristic)Unknown Characteristic ,每个特征下又有许多属性,下面对涉及到的各个概念进行说明。
ESP32连接BLE设备具体实现的说明_第1张图片

  • Profile
    一个profile文件可以包含一个或者多个服务,一个profile文件包含需要的服务的信息或者为对等设备如何交互的配置文件的选项信息。一般一个设备就一个profile
  • UUID
    UUID是“Universally Unique Identifier”的缩写,通用唯一识别码的意思。对于蓝牙设备,每个服务都有一个与它对应的UUID,蓝牙技术联盟SIG定义UUID共用了一个基本的UUID:0x0000xxxx-0000-1000-8000-00805F9B34FB。总共128位,为了进一步简化基本UUID,每一个蓝牙技术联盟定义的属性有一个唯一的16位UUID,以代替上面的基本UUID的‘x’部分。使用16位的UUID便于记忆和操作,技术联盟已定义好较多的标准服务UUID,同时,也允许厂商定义自己的UUID,以满足已定义服务外的功能实现
  • Service
    一个服务包含一个或多个特性,这些特性是逻辑上相关的集合体。
  • Characteristic
    一个特性至少包含2个属性:一个属性用于声明(又叫描述符),一个属性用于存放特性的值。
    存放特性值的属性就是真正传输的数据,声明属性用来确定该特性是否可读写、是否可以发起通知(类似于向主机发起中断)等信息。
    比如,LED状态就是一个特性,该特性可读写。
  • Properties
    特性的操作类型有:写、没有回应的写、读、通知、指示(有回应的通知)
    通过字面意思就可以理解,比如LED特性,需要写和读性质。如果设备有事件需要上报,比如按键等,就需要通知或者指示性质。通知就是上报完就没事了,指示的话还需要主机给个响应。
  • Descriptors
    描述符就是用于声明的属性。 有一个特别的描述符值得特别地提起:客户端特性配置描述符(Client Characteristic Configuration Descriptor,CCCD),其uuid为0x2901,画知识点,后面会考…这个描述符是给任何支持通知或指示功能的特性额外增加的。在CCCD中写入“1”使能通知功能,写入“2”使能指示功能,写入“0”同时禁止通知和指示功能。

其他概念

  • 角色
    GATT中也分为两个角色,GATT服务器和GATT客户端。
    提供属性的设备称为GATT服务器,访问GATT服务器而获得属性的设备称为GATT客户端。
    比如手机通过访问蓝牙设备的属性来控制设备LED,所以蓝牙设备就是GATT服务器,手机就是GATT客户端。
  • 属性
    属性是GATT服务器中最基本的单元。GATT服务器将其所有的属性组成一个属性表。
    1)一个属性包含句柄、UUID、值:
    2)句柄是属性在GATT表中的索引。我们一般用不到
    3)UUID是属性的ID,包含了属性值的类型等信息,可能有多个属性拥有同一个UUID
    关于GATT协议的一些基础知识

二、相关API参数与使用说明

下面是本例中用到的也是比较常用的API接口的说明

  • 注册设备APP Profile接口
    ESP32连接BLE设备具体实现的说明_第2张图片
  • 搜索服务(通过给定的服务UUID来搜索相应的服务,若搜索到服务,会进入回调函数中的搜索完成事件,搜索结果也会被保存下来到对应的数据结构,将Handle值等都记录下来)

ESP32连接BLE设备具体实现的说明_第3张图片

  • 获取属性数量(通过给定的起始Handle值,在起始Handle范围内搜索属性的数量)
    ESP32连接BLE设备具体实现的说明_第4张图片
  • 查找描述符(通过给定的描述符uuid来搜索结果)

ESP32连接BLE设备具体实现的说明_第5张图片

  • 查找特征(通过给定的特征uuid来搜索结果)
    ESP32连接BLE设备具体实现的说明_第6张图片

  • 注册通知(若特征有通知属性,则需要额外注册通知并且写通知的描述符CCCD(描述符UUID:0x2902))
    ESP32连接BLE设备具体实现的说明_第7张图片

  • 写特征描述符(通过这个API写描述符的值)
    ESP32连接BLE设备具体实现的说明_第8张图片

  • 读特征(顾名思义,读取对应handle特征的值)
    ESP32连接BLE设备具体实现的说明_第9张图片

  • 写特征(同上)
    ESP32连接BLE设备具体实现的说明_第10张图片
    具体官方文档的API说明

三、整体连接流程

本例使用了gattc_multi_connect官方示例进行修改,官方示例完成了一个可以连接多个蓝牙设备的主机,主机连接设备的整体流程图如下:
ESP32连接BLE设备具体实现的说明_第11张图片
这里流程图已经把GATTC扫描连接的过程描述得很清楚了,现在让我们通过官方提供的接口来完成这些流程:

1) Register Callbacks & Register APP Profile

 	//Register Callbacks: esp_gap_cb && esp_gattc_cb
	esp_ble_gap_register_callback(esp_gap_cb);
    esp_ble_gattc_register_callback(esp_gattc_cb);
    
    //Register APP Profile: PROFILE_Bioland_BGM_APP_ID
    esp_ble_gattc_app_register(PROFILE_Bioland_BGM_APP_ID);

2)Set scan parameters

	//以下是esp_gap_cb回调函数中更新连接参数的事件,在这个事件中设置了扫描参数
 	case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
         ESP_LOGI(GATTC_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;

3)Start scanning

    //当设置完连接参数进入设置完成事件开启GAP扫描    
    case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: {
         //the unit of the duration is second
         uint32_t duration = 30;
         esp_ble_gap_start_scanning(duration);
         break;
    }

4)Get scan results

case ESP_GAP_BLE_SCAN_RESULT_EVT: {
        esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param;
        switch (scan_result->scan_rst.search_evt) {
        case ESP_GAP_SEARCH_INQ_RES_EVT:
            esp_log_buffer_hex(GATTC_TAG, scan_result->scan_rst.bda, 6);
            ESP_LOGI(GATTC_TAG, "Searched Adv Data Len %d, Scan Response Len %d", scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len);
            adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv,
                                                ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);
            ESP_LOGI(GATTC_TAG, "Searched Device Name Len %d", adv_name_len);
            esp_log_buffer_char(GATTC_TAG, adv_name, adv_name_len);
            ESP_LOGI(GATTC_TAG, "\n");
            if (Isconnecting){
                break;
            }
            if (conn_device_a && conn_device_b && !stop_scan_done){
                stop_scan_done = true;
                esp_ble_gap_stop_scanning();
                ESP_LOGI(GATTC_TAG, "all devices are connected");
                break;
            }
            if (adv_name != NULL) {

                if (strlen(remote_device_name[0]) == adv_name_len && strncmp((char *)adv_name, remote_device_name[0], adv_name_len) == 0) {
                    if (conn_device_a == false) {
                        conn_device_a = true;
                        esp_bd_addr_t bda;
                        memcpy(bda, scan_result->scan_rst.bda, sizeof(esp_bd_addr_t));
                        ESP_LOGI(GATTC_TAG, "bd_addr:%08x%04x",(bda[0] << 24) + (bda[1] << 16) + (bda[2] << 8) + bda[3],(bda[4] << 8) + bda[5]);
                        esp_ble_gap_stop_scanning();
                        ESP_LOGI(GATTC_TAG, "%s", remote_device_name[0]);
                        esp_ble_gattc_open(gl_profile_tab[PROFILE_Bioland_IT_APP_ID].gattc_if, scan_result->scan_rst.bda, scan_result->scan_rst.ble_addr_type, true);
                        Isconnecting = true;
                    }
                    break;
                }
            }
            break;
}

以上四个步骤是在esp_gap_cb这个回调函数中完成的,之后的流程就在你注册的APP Profile接口函数中完成


那么接下来了解APP Profile接口函数的注册,在注册APP Profile接口函数之前先注册了esp_gattc_cb回调函数,所以APP Profile接口函数注册在回调函数中完成:

static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
{
    if (event == ESP_GATTC_REG_EVT) {
        if (param->reg.status == ESP_GATT_OK) {
            gl_profile_tab[param->reg.app_id].gattc_if = gattc_if;
        } else {
            ESP_LOGI(GATTC_TAG, "Reg app failed, app_id %04x, status %d",
                    param->reg.app_id,
                    param->reg.status);
            return;
        }
    }

    /* 定义了多少个PROFILE_NUM,这里就会注册多少个APP Profile接口函数 */
    do {
        int idx;
        for (idx = 0; idx < PROFILE_NUM; idx++) {
            if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
                    gattc_if == gl_profile_tab[idx].gattc_if) {
                if (gl_profile_tab[idx].gattc_cb) {
                    gl_profile_tab[idx].gattc_cb(event, gattc_if, param);
                }
            }
        }
    } while (0);
}

其中gl_profile_tab[idx]定义了APP Profile接口函数的入口,即为gattc_profile_event_handler

static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = {
    [PROFILE_Bioland_BGM_APP_ID] = {
        .gattc_cb = gattc_profile_event_handler,
        .gattc_if = ESP_GATT_IF_NONE,      
    },
};

那如何能够从esp_gap_cb函数跳转到APP Profile接口函数,那就用到了esp_gap_cb中得到扫描结果判断出连接设备后使用esp_ble_gattc_open()这个接口;这个接口完成后会触发跳转到对应设备的APP Profile接口函数中的ESP_GATTC_OPEN_EVT事件进行处理:

case ESP_GATTC_OPEN_EVT:
        if (p_data->open.status != ESP_GATT_OK){
            //open failed, ignore the first device, connect the second device
            ESP_LOGE(GATTC_TAG, "connect device failed, status %d", p_data->open.status);
            conn_device_a = false;
            //start_scan();
            break;
        }
        memcpy(gl_profile_tab[PROFILE_Bioland_BGM_APP_ID].remote_bda, p_data->open.remote_bda, 6);
        gl_profile_tab[PROFILE_Bioland_BGM_APP_ID].conn_id = p_data->open.conn_id;
        ESP_LOGI(GATTC_TAG, "ESP_GATTC_OPEN_EVT conn_id %d, if %d, status %d, mtu %d", p_data->open.conn_id, gattc_if, p_data->open.status, p_data->open.mtu);
        ESP_LOGI(GATTC_TAG, "REMOTE BDA:");
        esp_log_buffer_hex(GATTC_TAG, p_data->open.remote_bda, sizeof(esp_bd_addr_t));
        esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req (gattc_if, p_data->open.conn_id);
        if (mtu_ret){
            ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret);
        }
        break;

此时已经连接上蓝牙设备,之后可以对蓝牙设备进行操作与协议数据的交互…

1)Configure MTU size && Search service

case ESP_GATTC_CFG_MTU_EVT:
        if (param->cfg_mtu.status != ESP_GATT_OK){
            ESP_LOGE(GATTC_TAG,"Config mtu failed");
        }
        ESP_LOGI(GATTC_TAG, "Status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id);
        esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, NULL);//第三个参数是你想要搜索的ServiceUUID,若设置为NULL则搜索所有Service
        break;

2)Get characteristic

//通过0x1002uuid查找特征并存储搜索信息 
    ESP_LOGE(GATTC_TAG, "remote_handle.service_start_handle %d",remote_handle.service_start_handle);
    status = esp_ble_gattc_get_char_by_uuid( gattc_if,
                                             p_data->search_cmpl.conn_id,
                                             remote_handle.service_start_handle,
                                             remote_handle.service_end_handle,
                                             remote_filter_char_uuid_TX,
                                             char_elem_result_REMOTE_TX,
                                             &count);
    ESP_LOGE(GATTC_TAG, "Txchar_by_uuid %d",count);
    ESP_LOGE(GATTC_TAG, "status %d",status);
    if (status != ESP_GATT_OK){
       ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_Txchar_by_uuid error");
    } 

3)Register for notifications

if (count > 0 && (char_elem_result_REMOTE_TX[0].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY)){
     ESP_LOGE(GATTC_TAG, "handle:%d",char_elem_result_REMOTE_TX[0].char_handle);
     remote_handle.char_handle = char_elem_result_REMOTE_TX[0].char_handle;
     esp_ble_gattc_register_for_notify (gattc_if, gl_profile_tab[PROFILE_Bioland_IT_APP_ID].remote_bda, char_elem_result_REMOTE_TX[0].char_handle);
} 

总结

以上就是ESP32C3作为主机连接蓝牙设备的基本流程,其实通过esp_gap_cb进行扫描广播与设备连接后,GATT协议的处理流程就是:
1、搜索sevice uuid 得到结果信息存储到相应的数据结构
2、搜索char uuid得到结果信息(handle、propertits、uuid)存储到相应的数据结构
3、通过存储的结果数据结构来进行读写 通知注册 写入描述符值选择使能通知或指示

你可能感兴趣的:(ESP32,物联网,单片机,iot,stm32)