前言
对于传统的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目标地址
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 *)¬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;
- 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
判断中进行优化和合并