赞
踩
对于传统的BLE低功耗设备,通过手机ble作为主机角色进行通信。如果需要主动接入网络,通过esp32作为主机,采用一主多从的应用可以并行多台接入,本章主要针对Multi-connection的应用例程展开讲解
ESP32工作在GATT client的角色,扫描并连接GATT server的广播设备
如有异议,欢迎留言指正
基于BLE GATT直连的方案,主从拓扑结构,通信消息统一由网关主机处理,组网方式为星型网络
IDF为每个BLE服务创建一个单独的配置文件,应用配置文件以ID号单独定义
主要步骤
进入配置工具菜单idf.py menuconfig中选择,进入Component config->Bluetooth->Bluedroid Options->BT/BLE MAX ACL CONNECTIONS
api头文件路径
components/bt/host/bluedroid/api/include/api/esp_gap_ble_api.h
callback
:回调函数*scan_params
:配置扫描类型、地址类型、滤波类型、间隔、窗口等duration
:扫描时间,单位sec;写0为持续扫描ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT
事件触发返回remote_device
:断开peer目标地址api头文件路径
components/bt/host/bluedroid/api/include/api/esp_gattc_api.h
callback
:回调函数指针gattc_if
:gattc访问接口remote_bda
:远程设备地址remote_addr_type
:远程设备地址类型is_direct
:true - 直接连接;false - 后台连接gattc_if
:gattc访问接口conn_id
:连接idgattc_if
:gattc访问接口conn_id
:连接idgattc_if
:gattc访问接口conn_id
:连接idfilter_uuid
:指定服务uuidgattc_if
:gattc访问接口conn_id
:连接idtype
:属性类型start_handle
:属性起始句柄end_handle
:属性结束句柄char_handle
:特征句柄count
:输出查找匹配属性类型的个数gattc_if
:gattc访问接口conn_id
:连接id句柄start_handle
:属性起始句柄end_handle
:属性结束句柄char_uuid
:查找的特征uuidresult
:输出匹配特征的指针count
:输入需要查找的特征数,并输出已经获取到的特征数gattc_if
:gattc访问接口conn_id
:连接id句柄start_handle
:属性起始句柄end_handle
:属性结束句柄char_uuid
:特征uuiddescr_uuid
:描述符uuidresult
:输出匹配特征描述符的指针count
:输入需要查找的描述符数,并输出已经查找到的描述符数量gattc_if
:gattc访问接口conn_id
:连接idhandle
:特征句柄value_len
:写入数据长度value
:写入数据缓存write_type
:写属性类型(需要应答、无需应答)auth_req
:认证请求(0 - 无需认证)gattc_if
:gattc访问接口conn_id
:连接idhandle
:描述符句柄value_len
:写入数据长度value
:写入数据缓存write_type
:写属性类型(需要应答、无需应答)auth_req
:认证请求(0 - 无需认证)gattc_if
:gattc访问接口server_bda
:服务端地址handle
:服务特征句柄详情内容请参考GATT CLIENT API
示例demo,目录examples/bluetooth/bluedroid/ble/gattc_multi_connect
,例程为BLE GATT基础上,实现一个GATT客户端同时连接到三个不同的GATT服务端设备
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_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) { ... ... }
在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);
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;
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;
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;
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;
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 *)¬ify_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;
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_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;
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
判断中进行优化和合并
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。