本篇文章以ESP32C3平臺(tái)作為主機(jī)連接血糖儀藍(lán)牙設(shè)備的過(guò)程為例,對(duì)代碼的實(shí)現(xiàn)進(jìn)行分析與理解。
一、基礎(chǔ)概念
在上手撕代碼之前,讓我們準(zhǔn)備好砍柴刀,先使用nRF Connect APP連接血糖儀對(duì)Gatt協(xié)議概念以及各層次進(jìn)行理解,APP下載鏈接自行百度,這里就不貼出來(lái)了,廢話不多說(shuō),打開(kāi)手機(jī)藍(lán)牙連接血糖儀藍(lán)牙設(shè)備,左圖為血糖儀的所有服務(wù)項(xiàng),分別是Generic Access、Device Information、Unknown Service、Unknown Service四項(xiàng)服務(wù)(Service),右圖是UUID為0x1000的Unknown Service服務(wù)項(xiàng)的內(nèi)容,該服務(wù)有四個(gè)特征(Characteristic)Unknown Characteristic ,每個(gè)特征下又有許多屬性,下面對(duì)涉及到的各個(gè)概念進(jìn)行說(shuō)明。
-
Profile
一個(gè)profile文件可以包含一個(gè)或者多個(gè)服務(wù),一個(gè)profile文件包含需要的服務(wù)的信息或者為對(duì)等設(shè)備如何交互的配置文件的選項(xiàng)信息。一般一個(gè)設(shè)備就一個(gè)profile -
UUID
UUID是“Universally Unique Identifier”的縮寫(xiě),通用唯一識(shí)別碼的意思。對(duì)于藍(lán)牙設(shè)備,每個(gè)服務(wù)都有一個(gè)與它對(duì)應(yīng)的UUID,藍(lán)牙技術(shù)聯(lián)盟SIG定義UUID共用了一個(gè)基本的UUID:0x0000xxxx-0000-1000-8000-00805F9B34FB??偣?28位,為了進(jìn)一步簡(jiǎn)化基本UUID,每一個(gè)藍(lán)牙技術(shù)聯(lián)盟定義的屬性有一個(gè)唯一的16位UUID,以代替上面的基本UUID的‘x’部分。使用16位的UUID便于記憶和操作,技術(shù)聯(lián)盟已定義好較多的標(biāo)準(zhǔn)服務(wù)UUID,同時(shí),也允許廠商定義自己的UUID,以滿足已定義服務(wù)外的功能實(shí)現(xiàn) -
Service
一個(gè)服務(wù)包含一個(gè)或多個(gè)特性,這些特性是邏輯上相關(guān)的集合體。 -
Characteristic
一個(gè)特性至少包含2個(gè)屬性:一個(gè)屬性用于聲明(又叫描述符),一個(gè)屬性用于存放特性的值。
存放特性值的屬性就是真正傳輸?shù)臄?shù)據(jù),聲明屬性用來(lái)確定該特性是否可讀寫(xiě)、是否可以發(fā)起通知(類似于向主機(jī)發(fā)起中斷)等信息。
比如,LED狀態(tài)就是一個(gè)特性,該特性可讀寫(xiě)。 -
Properties
特性的操作類型有:寫(xiě)、沒(méi)有回應(yīng)的寫(xiě)、讀、通知、指示(有回應(yīng)的通知)
通過(guò)字面意思就可以理解,比如LED特性,需要寫(xiě)和讀性質(zhì)。如果設(shè)備有事件需要上報(bào),比如按鍵等,就需要通知或者指示性質(zhì)。通知就是上報(bào)完就沒(méi)事了,指示的話還需要主機(jī)給個(gè)響應(yīng)。 -
Descriptors
描述符就是用于聲明的屬性。 有一個(gè)特別的描述符值得特別地提起:客戶端特性配置描述符(Client Characteristic Configuration Descriptor,CCCD),其uuid為0x2901,畫(huà)知識(shí)點(diǎn),后面會(huì)考…這個(gè)描述符是給任何支持通知或指示功能的特性額外增加的。在CCCD中寫(xiě)入“1”使能通知功能,寫(xiě)入“2”使能指示功能,寫(xiě)入“0”同時(shí)禁止通知和指示功能。
其他概念
-
角色
GATT中也分為兩個(gè)角色,GATT服務(wù)器和GATT客戶端。
提供屬性的設(shè)備稱為GATT服務(wù)器,訪問(wèn)GATT服務(wù)器而獲得屬性的設(shè)備稱為GATT客戶端。
比如手機(jī)通過(guò)訪問(wèn)藍(lán)牙設(shè)備的屬性來(lái)控制設(shè)備LED,所以藍(lán)牙設(shè)備就是GATT服務(wù)器,手機(jī)就是GATT客戶端。 -
屬性
屬性是GATT服務(wù)器中最基本的單元。GATT服務(wù)器將其所有的屬性組成一個(gè)屬性表。
1)一個(gè)屬性包含句柄、UUID、值:
2)句柄是屬性在GATT表中的索引。我們一般用不到
3)UUID是屬性的ID,包含了屬性值的類型等信息,可能有多個(gè)屬性擁有同一個(gè)UUID
關(guān)于GATT協(xié)議的一些基礎(chǔ)知識(shí)
二、相關(guān)API參數(shù)與使用說(shuō)明
下面是本例中用到的也是比較常用的API接口的說(shuō)明
- 注冊(cè)設(shè)備APP Profile接口
- 搜索服務(wù)(通過(guò)給定的服務(wù)UUID來(lái)搜索相應(yīng)的服務(wù),若搜索到服務(wù),會(huì)進(jìn)入回調(diào)函數(shù)中的搜索完成事件,搜索結(jié)果也會(huì)被保存下來(lái)到對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu),將Handle值等都記錄下來(lái))
- 獲取屬性數(shù)量(通過(guò)給定的起始Handle值,在起始Handle范圍內(nèi)搜索屬性的數(shù)量)
- 查找描述符(通過(guò)給定的描述符uuid來(lái)搜索結(jié)果)
-
查找特征(通過(guò)給定的特征uuid來(lái)搜索結(jié)果)
-
注冊(cè)通知(若特征有通知屬性,則需要額外注冊(cè)通知并且寫(xiě)通知的描述符CCCD(描述符UUID:0x2902))
-
寫(xiě)特征描述符(通過(guò)這個(gè)API寫(xiě)描述符的值)
-
讀特征(顧名思義,讀取對(duì)應(yīng)handle特征的值)
-
寫(xiě)特征(同上)
具體官方文檔的API說(shuō)明
三、整體連接流程
本例使用了gattc_multi_connect官方示例進(jìn)行修改,官方示例完成了一個(gè)可以連接多個(gè)藍(lán)牙設(shè)備的主機(jī),主機(jī)連接設(shè)備的整體流程圖如下:
這里流程圖已經(jīng)把GATTC掃描連接的過(guò)程描述得很清楚了,現(xiàn)在讓我們通過(guò)官方提供的接口來(lái)完成這些流程:
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回調(diào)函數(shù)中更新連接參數(shù)的事件,在這個(gè)事件中設(shè)置了掃描參數(shù)
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
//當(dāng)設(shè)置完連接參數(shù)進(jìn)入設(shè)置完成事件開(kāi)啟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;
}
以上四個(gè)步驟是在esp_gap_cb這個(gè)回調(diào)函數(shù)中完成的,之后的流程就在你注冊(cè)的APP Profile接口函數(shù)中完成
那么接下來(lái)了解APP Profile接口函數(shù)的注冊(cè),在注冊(cè)APP Profile接口函數(shù)之前先注冊(cè)了esp_gattc_cb回調(diào)函數(shù),所以APP Profile接口函數(shù)注冊(cè)在回調(diào)函數(shù)中完成:
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;
}
}
/* 定義了多少個(gè)PROFILE_NUM,這里就會(huì)注冊(cè)多少個(gè)APP Profile接口函數(shù) */
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);
}
其中g(shù)l_profile_tab[idx]定義了APP Profile接口函數(shù)的入口,即為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函數(shù)跳轉(zhuǎn)到APP Profile接口函數(shù),那就用到了esp_gap_cb中得到掃描結(jié)果判斷出連接設(shè)備后使用esp_ble_gattc_open()這個(gè)接口;這個(gè)接口完成后會(huì)觸發(fā)跳轉(zhuǎn)到對(duì)應(yīng)設(shè)備的APP Profile接口函數(shù)中的ESP_GATTC_OPEN_EVT事件進(jìn)行處理:
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;
此時(shí)已經(jīng)連接上藍(lán)牙設(shè)備,之后可以對(duì)藍(lán)牙設(shè)備進(jìn)行操作與協(xié)議數(shù)據(jù)的交互…
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);//第三個(gè)參數(shù)是你想要搜索的ServiceUUID,若設(shè)置為NULL則搜索所有Service
break;
2)Get characteristic
//通過(guò)0x1002uuid查找特征并存儲(chǔ)搜索信息
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文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-594729.html
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);
}
總結(jié)
以上就是ESP32C3作為主機(jī)連接藍(lán)牙設(shè)備的基本流程,其實(shí)通過(guò)esp_gap_cb進(jìn)行掃描廣播與設(shè)備連接后,GATT協(xié)議的處理流程就是:
1、搜索sevice uuid 得到結(jié)果信息存儲(chǔ)到相應(yīng)的數(shù)據(jù)結(jié)構(gòu)
2、搜索char uuid得到結(jié)果信息(handle、propertits、uuid)存儲(chǔ)到相應(yīng)的數(shù)據(jù)結(jié)構(gòu)
3、通過(guò)存儲(chǔ)的結(jié)果數(shù)據(jù)結(jié)構(gòu)來(lái)進(jìn)行讀寫(xiě) 通知注冊(cè) 寫(xiě)入描述符值選擇使能通知或指示文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-594729.html
到了這里,關(guān)于ESP32連接BLE設(shè)備具體實(shí)現(xiàn)的說(shuō)明的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!