ESP32基于GATT Client蓝牙网关 (一主多从)

前言

对于传统的BLE低功耗设备,通过手机ble作为主机角色进行通信。如果需要主动接入网络,通过esp32作为主机,采用一主多从的应用可以并行多台接入,本章主要针对Multi-connection的应用例程展开讲解

ESP32工作在GATT client的角色,扫描并连接GATT server的广播设备

如有异议,欢迎留言指正

模型架构

基于BLE GATT直连的方案,主从拓扑结构,通信消息统一由网关主机处理,组网方式为星型网络
在这里插入图片描述

一对多连接配置

IDF为每个BLE服务创建一个单独的配置文件,应用配置文件以ID号单独定义
在这里插入图片描述

配置流程

主要步骤

  • 系统初始化
  • 扫描参数配置
  • 发现附近的设备
  • 与匹配的设备进行连接
  • 注册服务通知 register notify
    在这里插入图片描述
修改最大连接数

进入配置工具菜单idf.py menuconfig中选择,进入Component config->Bluetooth->Bluedroid Options->BT/BLE MAX ACL CONNECTIONS
在这里插入图片描述

常用API
GAP

api头文件路径components/bt/host/bluedroid/api/include/api/esp_gap_ble_api.h

  • esp_err_t esp_ble_gap_register_callback(esp_gap_ble_cb_t callback):注册GAP事件回调
    • callback:回调函数
  • esp_err_t esp_ble_gap_set_scan_params(esp_ble_scan_params_t *scan_params):设置扫描参数
    • *scan_params:配置扫描类型、地址类型、滤波类型、间隔、窗口等
  • esp_err_t esp_ble_gap_start_scanning(uint32_t duration):启动扫描
    • duration:扫描时间,单位sec;写0为持续扫描
  • esp_err_t esp_ble_gap_stop_scanning(void):停止扫描
  • esp_err_t esp_ble_gap_read_rssi(esp_bd_addr_t remote_addr):读取RSSI信号强度,结果通过ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT事件触发返回
  • esp_err_t esp_ble_gap_disconnect(esp_bd_addr_t remote_device):断开连接
    • remote_device:断开peer目标地址

更多内容请参考GAP API

GATTC

api头文件路径components/bt/host/bluedroid/api/include/api/esp_gattc_api.h

  • esp_err_t esp_ble_gattc_register_callback(esp_gattc_cb_tcallback):注册GATTC事件回调
    • callback:回调函数指针
  • esp_err_t esp_ble_gattc_app_register(uint16_t app_id): 注册GATT服务应用
  • esp_err_t esp_ble_gattc_open(esp_gatt_if_t gattc_if, esp_bd_addr_t remote_bda, esp_ble_addr_type_t remote_addr_type, bool is_direct):打开连接
    • gattc_if:gattc访问接口
    • remote_bda:远程设备地址
    • remote_addr_type:远程设备地址类型
    • is_direct:true - 直接连接;false - 后台连接
  • esp_err_t esp_ble_gattc_close(esp_gatt_if_t gattc_if, uint16_t conn_id):关闭与gatt服务端连接
    • gattc_if:gattc访问接口
    • conn_id:连接id
  • esp_err_t esp_ble_gattc_send_mtu_req(esp_gatt_if_t gattc_if, uint16_t conn_id):设置通道MTU大小
    • gattc_if:gattc访问接口
    • conn_id:连接id
  • esp_err_t esp_ble_gattc_search_service(esp_gatt_if_t gattc_if, uint16_t conn_id, esp_bt_uuid_t *filter_uuid):查找指定服务,仅本地缓存中查找匹配
    • gattc_if:gattc访问接口
    • conn_id:连接id
    • filter_uuid:指定服务uuid
  • esp_gatt_status_t esp_ble_gattc_get_attr_count(esp_gatt_if_t gattc_if, uint16_t conn_id, esp_gatt_db_attr_type_t type, uint16_t start_handle, uint16_t end_handle, uint16_t char_handle, uint16_t *count):获取指定服务或特征的属性个数
    • gattc_if:gattc访问接口
    • conn_id:连接id
    • type:属性类型
    • start_handle:属性起始句柄
    • end_handle:属性结束句柄
    • char_handle:特征句柄
    • count:输出查找匹配属性类型的个数
  • esp_gatt_status_t esp_ble_gattc_get_char_by_uuid(esp_gatt_if_t gattc_if, uint16_t conn_id, uint16_t start_handle, uint16_t end_handle, esp_bt_uuid_t char_uuid, esp_gattc_char_elem_t *result, uint16_t *count):获取指定特征UUID的特征,从本地缓存中获取
    • gattc_if:gattc访问接口
    • conn_id:连接id句柄
    • start_handle:属性起始句柄
    • end_handle:属性结束句柄
    • char_uuid:查找的特征uuid
    • result:输出匹配特征的指针
    • count:输入需要查找的特征数,并输出已经获取到的特征数
  • esp_gatt_status_t esp_ble_gattc_get_descr_by_uuid(esp_gatt_if_t gattc_if, uint16_t conn_id, uint16_t start_handle, uint16_t end_handle, esp_bt_uuid_t char_uuid, esp_bt_uuid_t descr_uuid, esp_gattc_descr_elem_t *result, uint16_t *count):获取具有给定特征uuid的描述符
    • gattc_if:gattc访问接口
    • conn_id:连接id句柄
    • start_handle:属性起始句柄
    • end_handle:属性结束句柄
    • char_uuid:特征uuid
    • descr_uuid:描述符uuid
    • result:输出匹配特征描述符的指针
    • count:输入需要查找的描述符数,并输出已经查找到的描述符数量
  • esp_err_t esp_ble_gattc_write_char(esp_gatt_if_t gattc_if, uint16_t conn_id, uint16_t handle, uint16_t value_len, uint8_t *value, esp_gatt_write_type_t write_type, esp_gatt_auth_req_t auth_req):gattc写入特征值
    • gattc_if:gattc访问接口
    • conn_id:连接id
    • handle:特征句柄
    • value_len:写入数据长度
    • value:写入数据缓存
    • write_type:写属性类型(需要应答、无需应答)
    • auth_req:认证请求(0 - 无需认证)
  • esp_err_t esp_ble_gattc_write_char_descr(esp_gatt_if_t gattc_if, uint16_t conn_id, uint16_t handle, uint16_t value_len, uint8_t *value, esp_gatt_write_type_t write_type, esp_gatt_auth_req_t auth_req):gattc写入特征描述符值
    • gattc_if:gattc访问接口
    • conn_id:连接id
    • handle:描述符句柄
    • value_len:写入数据长度
    • value:写入数据缓存
    • write_type:写属性类型(需要应答、无需应答)
    • auth_req:认证请求(0 - 无需认证)
  • esp_err_t esp_ble_gattc_register_for_notify(esp_gatt_if_t gattc_if, esp_bd_addr_t server_bda, uint16_t handle):注册服务通知
    • gattc_if:gattc访问接口
    • server_bda:服务端地址
    • handle:服务特征句柄

详情内容请参考GATT CLIENT API

代码讲解

示例demo,目录examples/bluetooth/bluedroid/ble/gattc_multi_connect,例程为BLE GATT基础上,实现一个GATT客户端同时连接到三个不同的GATT服务端设备

入口app_main

app_main()中进行了蓝牙协议栈初始化与回调事件注册

esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg); //初始化蓝牙控制器
if (ret) {
    ESP_LOGE(GATTC_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret));
    return;
}
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);//使能BLE模式
if (ret) {
    ESP_LOGE(GATTC_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret));
    return;
}
ret = esp_bluedroid_init();//初始化与分配资源
if (ret) {
    ESP_LOGE(GATTC_TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
    return;
}
ret = esp_bluedroid_enable();//使能蓝牙
if (ret) {
    ESP_LOGE(GATTC_TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret));
    return;
}
//register the  callback function to the gap module
ret = esp_ble_gap_register_callback(esp_gap_cb); //注册gap事件回调
if (ret){
    ESP_LOGE(GATTC_TAG, "gap register error, error code = %x", ret);
    return;
}
//register the callback function to the gattc module
ret = esp_ble_gattc_register_callback(esp_gattc_cb);//注册gattc事件回调
if(ret){
    ESP_LOGE(GATTC_TAG, "gattc register error, error code = %x", ret);
    return;
}
ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID); //注册GATT A服务app
if (ret){
    ESP_LOGE(GATTC_TAG, "gattc app register error, error code = %x", ret);
    return;
}
ret = esp_ble_gattc_app_register(PROFILE_B_APP_ID);//注册GATT B服务app
if (ret){
    ESP_LOGE(GATTC_TAG, "gattc app register error, error code = %x", ret);
    return;
}
ret = esp_ble_gattc_app_register(PROFILE_C_APP_ID);//注册GATT C服务app
if (ret){
    ESP_LOGE(GATTC_TAG, "gattc app register error, error code = %x", ret);
    return;
}
ret = esp_ble_gatt_set_local_mtu(200);//设置MTU为200
if (ret){
    ESP_LOGE(GATTC_TAG, "set local  MTU failed, error code = %x", ret);
}
esp_gap_cb回调

通过esp_gap_cb回调,通过获取扫描结果事件ESP_GAP_BLE_SCAN_RESULT_EVT,对比广播名称来判断需要连接的设备

  • remote_device_name定义了设备广播名称,可以根据实际的定义进行修改
static const char remote_device_name[3][20] = {"ESP_GATTS_DEMO_1", "ESP_GATTS_DEMO_2", “ESP_GATTS_DEMO_3"};
  • 匹配结果与执行连接
//提取广播名称
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);
//获取到广播名称
if (adv_name != NULL) 
{
    //比对名称与长度
    //设备a
    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_LOGI(GATTC_TAG, "Searched device %s", remote_device_name[0]);
            esp_ble_gap_stop_scanning(); //停止扫描
            esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, scan_result->scan_rst.bda, scan_result->scan_rst.ble_addr_type, true);//执行连接(使用设备a配置文件)
            Isconnecting = true;//置位连接中状态
        }
        break;
    }  
    //设备b
    else if (strlen(remote_device_name[1]) == adv_name_len && strncmp((char *)adv_name, remote_device_name[1], adv_name_len) == 0) {
    ...
    ...
    }
    //设备a
    else if (strlen(remote_device_name[2]) == adv_name_len && strncmp((char *)adv_name, remote_device_name[1], adv_name_len) == 0) {
    ...
    ...
    }  
GATTC事件回调

在BT初始化时,为每一个从设备注册了gattc回调事件,针对每一台设备的profile匹配唯一的gattc处理回调

static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
static void gattc_profile_b_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
static void gattc_profile_c_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param);
A、B、C处理基本相同,重点看下内部的事件处理
  • 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;//a连接失败,尝试连接b设备
            //start_scan();
            break;
        }
        //连接成功
        memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->open.remote_bda, 6);//地址拷贝
        gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->open.conn_id; //保存连接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);//发送mtu请求200
        if (mtu_ret){
            ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret);
        }
        break;
  • ESP_GATTC_CFG_MTU_EVT: MTU配置完成事件,进行搜索服务;服务定义在remote_filter_service_uuid结构中
    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, &remote_filter_service_uuid); //搜索服务
        break;
  • ESP_GATTC_SEARCH_RES_EVT:服务搜索到后触发,进行置位标志
   case ESP_GATTC_SEARCH_RES_EVT: {
        ESP_LOGI(GATTC_TAG, "SEARCH RES: conn_id = %x is primary service %d", p_data->search_res.conn_id, p_data->search_res.is_primary);
        ESP_LOGI(GATTC_TAG, "start handle %d end handle %d current handle value %d", p_data->search_res.start_handle, p_data->search_res.end_handle, p_data->search_res.srvc_id.inst_id);
        if (p_data->search_res.srvc_id.uuid.len == ESP_UUID_LEN_16 && p_data->search_res.srvc_id.uuid.uuid.uuid16 == REMOTE_SERVICE_UUID) {
            ESP_LOGI(GATTC_TAG, "UUID16: %x", p_data->search_res.srvc_id.uuid.uuid.uuid16);
            get_service_a = true; //置位标志
            gl_profile_tab[PROFILE_A_APP_ID].service_start_handle = p_data->search_res.start_handle;//保存句柄
            gl_profile_tab[PROFILE_A_APP_ID].service_end_handle = p_data->search_res.end_handle;
        }
        break;
  • ESP_GATTC_SEARCH_CMPL_EVT:所有服务搜索完成后该事件触发,进行特征的获取与注册通知notify
case ESP_GATTC_SEARCH_CMPL_EVT:
        if (p_data->search_cmpl.status != ESP_GATT_OK){
            ESP_LOGE(GATTC_TAG, "search service failed, error status = %x", p_data->search_cmpl.status);
            break;
        }
        if (get_service_a){
            uint16_t count = 0;
            //获取属性
            esp_gatt_status_t status = esp_ble_gattc_get_attr_count( gattc_if,
                                                                     p_data->search_cmpl.conn_id,
                                                                     ESP_GATT_DB_CHARACTERISTIC,
                                                                     gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
                                                                     gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
                                                                     INVALID_HANDLE,
                                                                     &count);
            if (status != ESP_GATT_OK){
                ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error");
            }
            if (count > 0) {
                char_elem_result_a = (esp_gattc_char_elem_t *)malloc(sizeof(esp_gattc_char_elem_t) * count);
                if (!char_elem_result_a){
                    ESP_LOGE(GATTC_TAG, "gattc no mem");
                }else {
                   //获取指定uuid
                    status = esp_ble_gattc_get_char_by_uuid( gattc_if,
                                                             p_data->search_cmpl.conn_id,
                                                             gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
                                                             gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
                                                             remote_filter_char_uuid,
                                                             char_elem_result_a,
                                                             &count);
                    if (status != ESP_GATT_OK){
                        ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_char_by_uuid error");
                    }
                    /*  Every service have only one char in our 'ESP_GATTS_DEMO' demo, so we used first 'char_elem_result' */
                    if (count > 0 && (char_elem_result_a[0].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY)){
                        gl_profile_tab[PROFILE_A_APP_ID].char_handle = char_elem_result_a[0].char_handle;
                        //注册通知服务
                        esp_ble_gattc_register_for_notify (gattc_if, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, char_elem_result_a[0].char_handle);
                    }
                }
                /* free char_elem_result */
                free(char_elem_result_a);
            }else {
                ESP_LOGE(GATTC_TAG, "no char found");
            }
        }
        break;
  • ESP_GATTC_REG_FOR_NOTIFY_EVT:注册通知服务完成事件,写入CCC配置特征使能通知notify

   case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
        if (p_data->reg_for_notify.status != ESP_GATT_OK){
            ESP_LOGE(GATTC_TAG, "reg notify failed, error status =%x", p_data->reg_for_notify.status);
            break;
        }
        uint16_t count = 0;
        uint16_t notify_en = 1;
        //获取属性数量
        esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count( gattc_if,
                                                                     gl_profile_tab[PROFILE_A_APP_ID].conn_id,
                                                                     ESP_GATT_DB_DESCRIPTOR,
                                                                     gl_profile_tab[PROFILE_A_APP_ID].service_start_handle,
                                                                     gl_profile_tab[PROFILE_A_APP_ID].service_end_handle,
                                                                     gl_profile_tab[PROFILE_A_APP_ID].char_handle,
                                                                     &count);
        if (ret_status != ESP_GATT_OK){
            ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error");
        }
        if (count > 0){
            descr_elem_result_a = (esp_gattc_descr_elem_t *)malloc(sizeof(esp_gattc_descr_elem_t) * count);
            if (!descr_elem_result_a){
                ESP_LOGE(GATTC_TAG, "malloc error, gattc no mem");
            }else{
              // 获取描述符
                ret_status = esp_ble_gattc_get_descr_by_char_handle( gattc_if,
                                                                     gl_profile_tab[PROFILE_A_APP_ID].conn_id,
                                                                     p_data->reg_for_notify.handle,
                                                                     notify_descr_uuid,
                                                                     descr_elem_result_a,
                                                                     &count);
                if (ret_status != ESP_GATT_OK){
                    ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_descr_by_char_handle error");
                }
                /* Every char has only one descriptor in our 'ESP_GATTS_DEMO' demo, so we used first 'descr_elem_result' */
                if (count > 0 && descr_elem_result_a[0].uuid.len == ESP_UUID_LEN_16 && descr_elem_result_a[0].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG){
                    //使能通知 notify_en = 1
                    ret_status = esp_ble_gattc_write_char_descr( gattc_if,
                                                                 gl_profile_tab[PROFILE_A_APP_ID].conn_id,
                                                                 descr_elem_result_a[0].handle,
                                                                 sizeof(notify_en),
                                                                 (uint8_t *)&notify_en,
                                                                 ESP_GATT_WRITE_TYPE_RSP,
                                                                 ESP_GATT_AUTH_REQ_NONE);
                }
                if (ret_status != ESP_GATT_OK){
                    ESP_LOGE(GATTC_TAG, "esp_ble_gattc_write_char_descr error");
                }
                /* free descr_elem_result */
                free(descr_elem_result_a);
            }
        }
        else{
            ESP_LOGE(GATTC_TAG, "decsr not found");
        }
        break;
  • ESP_GATTC_NOTIFY_EVT:数据接收通知事件,例程中将数据打印
    case ESP_GATTC_NOTIFY_EVT:
        ESP_LOGI(GATTC_TAG, "ESP_GATTC_NOTIFY_EVT, Receive notify value:");
        esp_log_buffer_hex(GATTC_TAG, p_data->notify.value, p_data->notify.value_len);
        break;
  • ESP_GATTC_WRITE_DESCR_EVT:描述符写入事件,例程写入发送了35字节累加数字;写入完成后会触发ESP_GATTC_WRITE_CHAR_EVT事件
    case ESP_GATTC_WRITE_DESCR_EVT:
        if (p_data->write.status != ESP_GATT_OK){
            ESP_LOGE(GATTC_TAG, "write descr failed, error status = %x", p_data->write.status);
            break;
        }
        ESP_LOGI(GATTC_TAG, "write descr success");
        uint8_t write_char_data[35];
        for (int i = 0; i < sizeof(write_char_data); ++i)
        {
            write_char_data[i] = i % 256;
        }
        esp_ble_gattc_write_char( gattc_if,
                                  gl_profile_tab[PROFILE_A_APP_ID].conn_id,
                                  gl_profile_tab[PROFILE_A_APP_ID].char_handle,
                                  sizeof(write_char_data),
                                  write_char_data,
                                  ESP_GATT_WRITE_TYPE_RSP,
                                  ESP_GATT_AUTH_REQ_NONE);
        break;
  • ESP_GATTC_DISCONNECT_EVT:断开事件
    case ESP_GATTC_DISCONNECT_EVT:
        //Start scanning again
        start_scan(); //开启扫描
        if (memcmp(p_data->disconnect.remote_bda, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, 6) == 0){
            ESP_LOGI(GATTC_TAG, "device a disconnect");
            conn_device_a = false; //清标志
            get_service_a = false;
        }
        break;
总结

移植例程代码,仅需要修改为对应设备的配置文件和广播名称即可;而增加设备数量相对应增加profile配置文件与gattc回调实现,由于代码处理几乎相同,会出现较大的代码冗余,可以在esp_gattc_cb判断中进行优化和合并

  • 5
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值