一、簡介
1.1 低功耗藍(lán)牙(BLE)協(xié)議棧

鏈路層(LL) 控制設(shè)備的射頻狀態(tài),有五個(gè)設(shè)備狀態(tài):待機(jī)、廣播、掃描、初始化和連接。
廣播 為廣播數(shù)據(jù)包,而 掃描 則是監(jiān)聽廣播。
GAP通信中角色,中心設(shè)備(Central - 主機(jī)) 用來掃描和連接 外圍設(shè)備(Peripheral - 從機(jī))。
大部分情況下外圍設(shè)備通過廣播自己來讓中心設(shè)備發(fā)現(xiàn)自己,并建立 GATT 連接,從而進(jìn)行更多的數(shù)據(jù)交換。
也有些情況是不需要連接的,只要外設(shè)廣播自己的數(shù)據(jù)即可,用這種方式主要目的是讓外圍設(shè)備,把自己的信息發(fā)送給多個(gè)中心設(shè)備。
1.2 通用屬性協(xié)議(GATT)
GATT是用Attribute Protocal(屬性協(xié)議)定義的一個(gè)service(服務(wù))框架。這個(gè)框架定義了Services以及它們的Characteristics的格式和規(guī)程。規(guī)程就是定義了包括發(fā)現(xiàn)、讀、寫、通知、指示以及配置廣播的characteristics。
為實(shí)現(xiàn)配置文件(Profile)的設(shè)備定義了兩種角色:Client(客戶端)、Server(服務(wù)器)。esp32的ble一般就處于Server模式。
一旦兩個(gè)設(shè)備建立了連接,GATT就開始發(fā)揮效用,同時(shí)意味著GAP協(xié)議管理的廣播過程結(jié)束了。
1.2.1 Profile(規(guī)范)
profile 可以理解為一種規(guī)范,建立的藍(lán)牙應(yīng)用任務(wù),藍(lán)牙任務(wù)實(shí)際上分為兩類:標(biāo)準(zhǔn)藍(lán)牙任務(wù)規(guī)范 profile(公有任務(wù)),非標(biāo)準(zhǔn)藍(lán)牙任務(wù)規(guī)范 profile(私有任務(wù))。
標(biāo)準(zhǔn)藍(lán)牙任務(wù)規(guī)范 profile:指的是從藍(lán)牙特別興趣小組 SIG 的官網(wǎng)上已經(jīng)發(fā)布的 GATT 規(guī)范列表,包括警告通知(alert notification),血壓測量(blood pressure),心率(heart rate),電池(battery)等等。它們都是針對具體的低功耗藍(lán)牙的應(yīng)用實(shí)例來設(shè)計(jì)的。目前藍(lán)牙技術(shù)聯(lián)盟還在不斷的制定新的規(guī)范,并且發(fā)布。
非標(biāo)準(zhǔn)藍(lán)牙任務(wù)規(guī)范 profile:指的是供應(yīng)商自定義的任務(wù),在藍(lán)牙 SIG 小組內(nèi)未定義的任務(wù)規(guī)范。
1.2.2 Service(服務(wù))
service 可以理解為一個(gè)服務(wù),在 BLE 從機(jī)中有多個(gè)服務(wù),例如:電量信息服務(wù)、系統(tǒng)信息服務(wù)等;
每個(gè) service 中又包含多個(gè) characteristic 特征值;
每個(gè)具體的 characteristic 特征值才是 BLE 通信的主題,比如當(dāng)前的電量是 80%,電量的 characteristic 特征值存在從機(jī)的 profile 里,這樣主機(jī)就可以通過這個(gè) characteristic 來讀取 80% 這個(gè)數(shù)據(jù)。
GATT 服務(wù)一般包含幾個(gè)具有相關(guān)的功能,比如特定傳感器的讀取和設(shè)置,人機(jī)接口的輸入輸出。組織具有相關(guān)的特性到服務(wù)中既實(shí)用又有效,因?yàn)樗沟眠壿嬌虾陀脩魯?shù)據(jù)上的邊界變得更加清晰,同時(shí)它也有助于不同應(yīng)用程序間代碼的重用。
1.2.3 Characteristic(特征)
characteristic 特征,BLE 主從機(jī)的通信均是通過 characteristic 來實(shí)現(xiàn),可以理解為一個(gè)標(biāo)簽,通過這個(gè)標(biāo)簽可以獲取或者寫入想要的內(nèi)容。
1.2.4 UUID(通用唯一識別碼)
uuid 通用唯一識別碼,我們剛才提到的 service 和 characteristic 都需要一個(gè)唯一的 uuid 來標(biāo)識;
每個(gè)從機(jī)都會有一個(gè) profile,不管是自定義的 simpleprofile,還是標(biāo)準(zhǔn)的防丟器 profile,他們都是由一些 service 組成,每個(gè) service 又包含了多個(gè) characteristic,主機(jī)和從機(jī)之間的通信,均是通過characteristic來實(shí)現(xiàn)。
1.3 ESP32藍(lán)牙應(yīng)用結(jié)構(gòu)
藍(lán)牙是?種短距通信系統(tǒng),其關(guān)鍵特性包括魯棒性、低功耗、低成本等。藍(lán)牙系統(tǒng)分為兩種不同的技術(shù):經(jīng)典藍(lán)牙 (Classic Bluetooth) 和藍(lán)牙低功耗 (Bluetooth Low Energy)。
ESP32 支持雙模藍(lán)牙,即同時(shí)支持經(jīng)典藍(lán)牙和藍(lán)牙低功耗。
從整體結(jié)構(gòu)上,藍(lán)牙可分為控制器 (Controller) 和主機(jī) (Host) 兩?部分:控制器包括了 PHY、Baseband、Link Controller、Link Manager、Device Manager、HCI 等模塊,用于硬件接?管理、鏈路管理等等;主機(jī)則包括了 L2CAP、SMP、SDP、ATT、GATT、GAP 以及各種規(guī)范,構(gòu)建了向應(yīng)用層提供接口的基礎(chǔ),方便應(yīng)用層對藍(lán)牙系統(tǒng)的訪問。主機(jī)可以與控制器運(yùn)行在同?個(gè)宿主上,也可以分布在不同的宿主上。ESP32 可以支持上述兩種方式。

1.4 Bluedroid主機(jī)架構(gòu)
在 ESP-IDF 中,使用經(jīng)過大量修改后的 BLUEDROID 作為藍(lán)牙主機(jī) (Classic BT + BLE)。BLUEDROID 擁有較為完善的功能,?持常用的規(guī)范和架構(gòu)設(shè)計(jì),同時(shí)也較為復(fù)雜。經(jīng)過大量修改后,BLUEDROID 保留了大多數(shù) BTA 層以下的代碼,幾乎完全刪去了 BTIF 層的代碼,使用了較為精簡的 BTC 層作為內(nèi)置規(guī)范及 Misc 控制層。修改后的 BLUEDROID 及其與控制器之間的關(guān)系如下圖:

1.5 ESP32的GATT服務(wù)器服務(wù)表示例
使用類似表格的數(shù)據(jù)結(jié)構(gòu)來定義服務(wù)器服務(wù)和特性,因此,它展示了一種定義服務(wù)器的實(shí)用方法功能集中在一處,而不是一一添加服務(wù)和特性。
二、API說明
以下控制器和虛擬 HCI 接口位于 bt/include/esp32/include/esp_bt.h。
2.1 esp_bt_controller_mem_release

2.2 esp_bt_controller_init

2.3 esp_bt_controller_enable

以下 GATT 接口位于 bt/host/bluedroid/api/include/api/esp_bt_main.h 和 bt/host/bluedroid/api/include/api/esp_gatts_api.h。
2.4 esp_bluedroid_init

2.5 esp_bluedroid_enable

2.6 esp_ble_gatts_register_callback

2.7 esp_ble_gatts_app_register

2.8 esp_ble_gatts_create_service

2.9 esp_ble_gatts_add_char

2.10 esp_ble_gatts_add_char_descr

2.11 esp_ble_gatts_start_service

2.12 esp_ble_gatts_send_indicate

2.13 esp_ble_gatts_send_response

2.14 esp_ble_gatts_get_attr_value

三、藍(lán)牙4.0通信實(shí)現(xiàn)過程
- 掃描藍(lán)牙BLE終端設(shè)備,對應(yīng)esp32就是廣播給大家供掃描
- 連接藍(lán)牙BLE終端設(shè)備,pad掃描到后去連接
- 啟動服務(wù)發(fā)現(xiàn),連接到esp32后獲取相應(yīng)的服務(wù)。
連接成功后,我們就要去尋找我們所需要的服務(wù),這里需要先啟動服務(wù)發(fā)現(xiàn)。 - 獲取Characteristic
之前我們說過,我們的最終目的就是獲取Characteristic來進(jìn)行通信,正常情況下,我們可以從硬件工程師那邊得到serviceUUID和characteristicUUID,也就是我們所比喻的班級號和學(xué)號,以此來獲得我們的characteristic。 - 開始通信
我們在得到Characteristic后,就可以開始讀寫操作進(jìn)行通信了。
a. 對于讀操作來說,讀取BLE終端設(shè)備返回的數(shù)據(jù)會通過回調(diào)方法mGattCallback中的onCharacteristicChanged函數(shù)返回。
b. 對于寫操作來說,可以通過向Characteristic寫入指令以此來達(dá)到控制BLE終端設(shè)備的目的
四、Demo程序GATT啟動流程
使用 esp-idf\examples\bluetooth\bluedroid\ble\gatt_server_service_table 中的例程
.........
//esp_bt_controller_config_t是藍(lán)牙控制器配置結(jié)構(gòu)體,這里使用了一個(gè)默認(rèn)的參數(shù)
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
//初始化藍(lán)牙控制器,此函數(shù)只能被調(diào)用一次,且必須在其他藍(lán)牙功能被調(diào)用之前調(diào)用
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
//使能藍(lán)牙控制器,mode是藍(lán)牙模式,如果想要動態(tài)改變藍(lán)牙模式不能直接調(diào)用該函數(shù),
//應(yīng)該先用disable關(guān)閉藍(lán)牙再使用該API來改變藍(lán)牙模式
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
//初始化藍(lán)牙并分配系統(tǒng)資源,它應(yīng)該被第一個(gè)調(diào)用
/*
藍(lán)牙棧bluedroid stack包括了BT和BLE使用的基本的define和API
初始化藍(lán)牙棧以后并不能直接使用藍(lán)牙功能,
還需要用FSM管理藍(lán)牙連接情況
*/
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(GATTS_TABLE_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
//使能藍(lán)牙棧
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(GATTS_TABLE_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
//建立藍(lán)牙的FSM(有限狀態(tài)機(jī))
//這里使用回調(diào)函數(shù)來控制每個(gè)狀態(tài)下的響應(yīng),需要將其在GATT和GAP層的回調(diào)函數(shù)注冊
/*gatts_event_handler和gap_event_handler處理藍(lán)牙??赡馨l(fā)生的所有情況,達(dá)到FSM的效果*/
ret = esp_ble_gatts_register_callback(gatts_event_handler);
if (ret){
ESP_LOGE(GATTS_TAG, "gatts register error, error code = %x", ret);
return;
}
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret){
ESP_LOGE(GATTS_TAG, "gap register error, error code = %x", ret);
return;
}
//下面創(chuàng)建了BLE GATT服務(wù)A,相當(dāng)于1個(gè)獨(dú)立的應(yīng)用程序
ret = esp_ble_gatts_app_register(PROFILE_A_APP_ID);
if (ret){
ESP_LOGE(GATTS_TAG, "gatts app register error, error code = %x", ret);
return;
}
//下面創(chuàng)建了BLE GATT服務(wù)B,相當(dāng)于1個(gè)獨(dú)立的應(yīng)用程序
ret = esp_ble_gatts_app_register(PROFILE_B_APP_ID);
if (ret){
ESP_LOGE(GATTS_TAG, "gatts app register error, error code = %x", ret);
return;
}
/*
設(shè)置了MTU的值(經(jīng)過MTU交換,從而設(shè)置一個(gè)PDU中最大能夠交換的數(shù)據(jù)量)。
例如:主設(shè)備發(fā)出一個(gè)1000字節(jié)的MTU請求,但是從設(shè)備回應(yīng)的MTU是500字節(jié),那么今后雙方要以較小的值500字節(jié)作為以后的MTU。
即主從雙方每次在做數(shù)據(jù)傳輸時(shí)不超過這個(gè)最大數(shù)據(jù)單元。
*/
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500);
if (local_mtu_ret){
ESP_LOGE(GATTS_TAG, "set local MTU failed, error code = %x", local_mtu_ret);
}
.......
五、服務(wù)數(shù)據(jù)結(jié)構(gòu)體設(shè)置
一個(gè)GATT 服務(wù)器應(yīng)用程序架構(gòu)(由Application Profiles組織起來)如下:

每個(gè)Profile定義為一個(gè)結(jié)構(gòu)體,結(jié)構(gòu)體成員依賴于該Application Profile 實(shí)現(xiàn)的services服務(wù)和characteristic特征。結(jié)構(gòu)體成員還包括GATT interface(GATT 接口)、Application ID(應(yīng)用程序ID)和處理profile事件的回調(diào)函數(shù)。
每個(gè)profile包括GATT interface(GATT 接口)、Application ID(應(yīng)用程序ID)、 Connection ID(連接ID)、Service Handle(服務(wù)句柄)、Service ID(服務(wù)ID)、Characteristic handle(特征句柄)、Characteristic UUID(特征UUID)、ATT權(quán)限、Characteristic Properties、描述符句柄、描述符UUID。
如果Characteristic支持通知(notifications)或指示(indicatons),它就必須是實(shí)現(xiàn)CCCD(Client Characteristic Configuration Descriptor)----這是額外的ATT。描述符有一個(gè)句柄和UUID。如:
struct gatts_profile_inst {
esp_gatts_cb_t gatts_cb; //GATT的回調(diào)函數(shù)
uint16_t gatts_if; //GATT的接口
uint16_t app_id; //應(yīng)用的ID
uint16_t conn_id; //連接的ID
uint16_t service_handle; //服務(wù)Service句柄
esp_gatt_srvc_id_t service_id; //服務(wù)Service ID
uint16_t char_handle; //特征Characteristic句柄
esp_bt_uuid_t char_uuid; //特征Characteristic的UUID
esp_gatt_perm_t perm; //特征屬性Attribute 授權(quán)
esp_gatt_char_prop_t property; //特征Characteristic的特性
uint16_t descr_handle; //描述descriptor句柄
esp_bt_uuid_t descr_uuid; //描述descriptorUUID
};
配置文件Application Profile存儲在heart_rate_profile_tab數(shù)組中,由于本示例中只有一個(gè)配置文件,因此一個(gè)元素存儲在數(shù)組中,索引為零,如HEART_PROFILE_APP_IDX。此外,還初始化了配置文件事件處理程序回調(diào)函數(shù)gatts_profile_event_handler。GATT 服務(wù)端上的不同應(yīng)用程序使用不同的接口,由 gatts_if 參數(shù)表示。對于初始化,此參數(shù)設(shè)置為ESP_GATT_IF_NONE,這意味著應(yīng)用程序配置文件尚未鏈接到任何客戶端。
/* One gatt-based profile one app_id and one gatts_if, this array will store the gatts_if returned by ESP_GATTS_REG_EVT */
static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = {
[PROFILE_A_APP_ID] = {
.gatts_cb = gatts_profile_a_event_handler,
.gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
},
[PROFILE_B_APP_ID] = {
.gatts_cb = gatts_profile_b_event_handler, /* This demo does not implement, similar as profile A */
.gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
},
};
這是兩個(gè)元素的數(shù)組??梢杂肁pplication ID來注冊Application Profiles,Application ID是由應(yīng)用程序分配的用來標(biāo)識每個(gè)Profile。 通過這種方法,可以在一個(gè)Server中擁有多個(gè)Application Profile。
esp_ble_gatts_app_register (PROFILE_A_APP_ID);
esp_ble_gatts_app_register (PROFILE_B_APP_ID);
六、GATT事件處理程序
其作用就是建立了藍(lán)牙GATT的FSM(有限狀態(tài)機(jī)),callback回調(diào)函數(shù)處理從BLE堆棧推送到應(yīng)用程序的所有事件。
回調(diào)函數(shù)的參數(shù):
- event: esp_gatts_cb_event_t 這是一個(gè)枚舉類型,表示調(diào)用該回調(diào)函數(shù)時(shí)的事件(或藍(lán)牙的狀態(tài))
-
gatts_if: esp_gatt_if_t (uint8_t) 這是GATT訪問接口類型,通常在GATT客戶端上不同的應(yīng)用程序用不同的gatt_if(不同的Application profile對應(yīng)不同的gatts_if) ,調(diào)用
esp_ble_gatts_app_register()時(shí),注冊Application profile 就會有一個(gè)gatts_if。 - param: esp_ble_gatts_cb_param_t 指向回調(diào)函數(shù)的參數(shù),是個(gè)聯(lián)合體類型,不同的事件類型采用聯(lián)合體內(nèi)不同的成員結(jié)構(gòu)體。
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
/*如果事件是注冊事件,則為每個(gè)配置文件存儲 gatts_if */
if (event == ESP_GATTS_REG_EVT) {
if (param->reg.status == ESP_GATT_OK) {
gl_profile_tab[param->reg.app_id].gatts_if = gatts_if;
} else {
ESP_LOGI(GATTS_TAG, "Reg app failed, app_id %04x, status %d\n",
param->reg.app_id,
param->reg.status);
return;
}
}
/*如果 gatts_if 等于 profile A,則調(diào)用 profile A cb handler,
* 所以這里調(diào)用每個(gè) profile 的回調(diào)*/
do {
int idx;
for (idx = 0; idx < PROFILE_NUM; idx++) {
if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
gatts_if == gl_profile_tab[idx].gatts_if) {
if (gl_profile_tab[idx].gatts_cb) {
gl_profile_tab[idx].gatts_cb(event, gatts_if, param);
}
}
}
} while (0);
}
七、注冊創(chuàng)建服務(wù)
當(dāng)調(diào)用esp_ble_gatts_app_register()注冊一個(gè)應(yīng)用程序Profile(Application Profile),將觸發(fā)ESP_GATTS_REG_EVT事件,除了可以完成對應(yīng)profile的gatts_if的注冊,還可以調(diào)用esp_bel_create_attr_tab()來創(chuàng)建profile Attributes 表或創(chuàng)建一個(gè)服務(wù)esp_ble_gatts_create_service()。
static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) {
switch (event) {
case ESP_GATTS_REG_EVT:
ESP_LOGI(GATTS_TAG, "REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id);
gl_profile_tab[PROFILE_B_APP_ID].service_id.is_primary = true;
gl_profile_tab[PROFILE_B_APP_ID].service_id.id.inst_id = 0x00;
gl_profile_tab[PROFILE_B_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[PROFILE_B_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_B;
esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_B_APP_ID].service_id, GATTS_NUM_HANDLE_TEST_B);
break;
…
}
句柄數(shù)定義為4:
#define GATTS_NUM_HANDLE_TEST_B 4
句柄是:
- 服務(wù)句柄
GATTS_SERVICE_UUID_TEST_B 0x00EE - 特征手柄
GATTS_CHAR_UUID_TEST_B 0xEE01 - 特征值句柄
- 特征描述符句柄
GATTS_DESCR_UUID_TEST_B 0x2222
該服務(wù)被定義為具有 16 位 UUID 長度的主要服務(wù)。服務(wù) ID 使用實(shí)例 ID = 0 和由 定義的 UUID 進(jìn)行初始化GATTS_SERVICE_UUID_TEST_A。
服務(wù)實(shí)例 ID 可用于區(qū)分具有相同 UUID 的多個(gè)服務(wù)。在此示例中,由于每個(gè)應(yīng)用程序配置文件只有一個(gè)服務(wù)并且服務(wù)具有不同的 UUID,因此在配置文件 A 和 B 中可以將服務(wù)實(shí)例 ID 定義為 0。但是,如果只有一個(gè)應(yīng)用程序配置文件具有兩個(gè)服務(wù)使用相同的 UUID,則有必要使用不同的實(shí)例 ID 來引用一個(gè)或另一個(gè)服務(wù)。
demo中的gatts_event_handler()回調(diào)函數(shù)—調(diào)用esp_ble_gatts_app_register(),觸發(fā)ESP_GATTS_REG_EVT時(shí),完成對每個(gè)profile 的gatts_if 的注冊。
gl_profile_tab[param->reg.app_id].gatts_if = gatts_if;
如果gatts_if == 某個(gè)Profile的gatts_if時(shí),調(diào)用對應(yīng)profile的回調(diào)函數(shù)處理事情。
if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
gatts_if == gl_profile_tab[idx].gatts_if) {
if (gl_profile_tab[idx].gatts_cb) {
gl_profile_tab[idx].gatts_cb(event, gatts_if, param);
}
}
八、啟動服務(wù)和創(chuàng)建特征
8.1 啟動服務(wù)
當(dāng)一個(gè)服務(wù)service創(chuàng)建成功后,由該profile GATT handler 管理的 ESP_GATTS_CREATE_EVT事件被觸發(fā),在這個(gè)事件可以啟動服務(wù)和添加特征characteristics到服務(wù)中。調(diào)用esp_ble_gatts_start_service()來啟動指定服務(wù)。
case ESP_GATTS_CREATE_EVT:
ESP_LOGI(GATTS_TAG, "CREATE_SERVICE_EVT, status %d, service_handle %d\n", param->create.status, param->create.service_handle);
gl_profile_tab[PROFILE_A_APP_ID].service_handle = param->create.service_handle;
gl_profile_tab[PROFILE_A_APP_ID].char_uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[PROFILE_A_APP_ID].char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_TEST_A;
esp_ble_gatts_start_service(gl_profile_tab[PROFILE_A_APP_ID].service_handle);
a_property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY;
esp_err_t add_char_ret =
esp_ble_gatts_add_char(gl_profile_tab[PROFILE_A_APP_ID].service_handle,
&gl_profile_tab[PROFILE_A_APP_ID].char_uuid,
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
a_property,
&gatts_demo_char1_val,
NULL);
if (add_char_ret){
ESP_LOGE(GATTS_TAG, "add char failed, error code =%x",add_char_ret);
}
break;
首先,由BLE堆棧生成生成的服務(wù)句柄(service handle)存儲在配置文件Profile表中,應(yīng)用層將用服務(wù)句柄來引用這個(gè)服務(wù)。調(diào)用esp_ble_gatts_start_service()和先前產(chǎn)生服務(wù)句柄來啟動服務(wù)。
8.2 創(chuàng)建特征
Characteristic是在GATT規(guī)范中最小的邏輯數(shù)據(jù)單元,由一個(gè)Value和多個(gè)描述特性的Desciptior組成。實(shí)際上,在與藍(lán)牙設(shè)備打交道,主要就是讀寫Characteristic的value來完成。 同樣的,Characteristic也是通過16bit或128bit的UUID唯一標(biāo)識。
我們根據(jù)藍(lán)牙設(shè)備的協(xié)議用對應(yīng)的Characteristci進(jìn)行讀寫即可達(dá)到與其通信的目的。
添加特征到service中,調(diào)用esp_ble_gatts_add_char()來添加characteristics連同characteristic權(quán)限和property(屬性)到服務(wù)service中。
權(quán)限:
-
ESP_GATT_PERM_READ: 允許讀取特征值 -
ESP_GATT_PERM_WRITE: 允許寫入特征值
特性:
-
ESP_GATT_CHAR_PROP_BIT_READ: 可以讀取特性 -
ESP_GATT_CHAR_PROP_BIT_WRITE: 特征可寫 -
ESP_GATT_CHAR_PROP_BIT_NOTIFY: 特性可以通知值的變化
同時(shí)擁有讀寫權(quán)限和屬性似乎是多余的。但是,屬性的讀寫屬性是向客戶端顯示的信息,目的是讓客戶端知道服務(wù)器是否接受讀寫請求。從這個(gè)意義上說,這些屬性充當(dāng)客戶端正確訪問服務(wù)器資源的提示。另一方面,權(quán)限是授予客戶端讀取或?qū)懭朐搶傩缘氖跈?quán)。例如,如果客戶端嘗試寫入它沒有寫入權(quán)限的屬性,即使設(shè)置了寫入屬性,服務(wù)器也會拒絕該請求。
此外,demo還為表示特征提供了一個(gè)初始值gatts_demo_char1_val。初始值定義如下:
#define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40
uint8_t char1_str[] = {0x11,0x22,0x33};
esp_attr_value_t gatts_demo_char1_val =
{
. attr_max_len = GATTS_DEMO_CHAR_VAL_LEN_MAX,
. attr_len = sizeof (char1_str),
. attr_value = char1_str,
};
特征初始值必須是非空對象并且特征長度必須始終大于零,否則堆棧將返回錯(cuò)誤。
最后,特性被配置為每次讀取或?qū)懭胩匦詴r(shí)都需要手動發(fā)送響應(yīng),而不是讓堆棧自動響應(yīng)。這是通過將esp_ble_gatts_add_char()函數(shù)的最后一個(gè)參數(shù)(表示屬性響應(yīng)控制參數(shù))設(shè)置為ESP_GATT_RSP_BY_APP或 NULL 來配置的。
七、創(chuàng)建特征描述符
當(dāng)特征添加到service中成功時(shí),觸發(fā)ESP_GATTS_ADD_CHAR_EVT事件。該事件返回由堆棧為剛剛添加的特征生成的句柄。該事件包括以下參數(shù):
esp_gatt_status_t狀態(tài); /* !< 操作狀態(tài)*/
uint16_t attr_handle; /* !< 特征屬性句柄*/
uint16_t service_handle; /* !< 服務(wù)屬性句柄*/
esp_bt_uuid_t char_uuid; /* !< 特征 uuid */
事件返回的屬性句柄存儲在配置文件表中,并且還設(shè)置了特征描述符長度和 UUID。使用該esp_ble_gatts_get_attr_value()函數(shù)讀取特征長度和值,然后打印以供參考。最后,使用該esp_ble_gatts_add_char_descr()函數(shù)添加特征描述。使用的參數(shù)是服務(wù)句柄、描述符 UUID、寫入和讀取權(quán)限、初始值和自動響應(yīng)設(shè)置。特征描述符的初始值可以是空指針,自動響應(yīng)參數(shù)也設(shè)置為空,這意味著需要響應(yīng)的請求必須手動回復(fù)。
case ESP_GATTS_ADD_CHAR_EVT: {
uint16_t length = 0;
const uint8_t *prf_char;
ESP_LOGI(GATTS_TAG, "ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n",
param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle);
gl_profile_tab[PROFILE_A_APP_ID].char_handle = param->add_char.attr_handle;
gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
esp_err_t get_attr_ret = esp_ble_gatts_get_attr_value(param->add_char.attr_handle, &length, &prf_char);
if (get_attr_ret == ESP_FAIL){
ESP_LOGE(GATTS_TAG, "ILLEGAL HANDLE");
}
ESP_LOGI(GATTS_TAG, "the gatts demo char length = %x\n", length);
for(int i = 0; i < length; i++){
ESP_LOGI(GATTS_TAG, "prf_char[%x] = %x\n",i,prf_char[i]);
}
esp_err_t add_descr_ret = esp_ble_gatts_add_char_descr(
gl_profile_tab[PROFILE_A_APP_ID].service_handle,
&gl_profile_tab[PROFILE_A_APP_ID].descr_uuid,
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
NULL,NULL);
if (add_descr_ret){
ESP_LOGE(GATTS_TAG, "add char descr failed, error code = %x", add_descr_ret);
}
break;
}
添加描述符后,將ESP_GATTS_ADD_CHAR_DESCR_EVT觸發(fā)事件,在此示例中用于打印信息消息。
case ESP_GATTS_ADD_CHAR_DESCR_EVT:
ESP_LOGI(GATTS_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n",
param->add_char.status, param->add_char.attr_handle,
param->add_char.service_handle);
break;
九、連接事件
9.1 更新連接參數(shù)
一個(gè)ESP_GATTS_CONNECT_EVT當(dāng)客戶端已連接到服務(wù)器GATT被觸發(fā)。此事件用于更新連接參數(shù),例如延遲、最小連接間隔、最大連接間隔和超時(shí)。連接參數(shù)存儲在一個(gè)esp_ble_conn_update_params_t結(jié)構(gòu)中,然后傳遞給esp_ble_gap_update_conn_params()函數(shù)。更新連接參數(shù)過程只需執(zhí)行一次,因此配置文件 B 連接事件處理程序不包含該esp_ble_gap_update_conn_params()函數(shù)。最后,事件返回的連接 ID 存儲在配置文件表中。
配置文件 A 連接事件:
case ESP_GATTS_CONNECT_EVT: {
esp_ble_conn_update_params_t conn_params = {0};
memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
/* For the IOS system, please reference the apple official documents about the ble connection parameters restrictions. */
conn_params.latency = 0;
conn_params.max_int = 0x30; // max_int = 0x30*1.25ms = 40ms
conn_params.min_int = 0x10; // min_int = 0x10*1.25ms = 20ms
conn_params.timeout = 400; // timeout = 400*10ms = 4000ms
ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONNECT_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:, is_conn %d",
param->connect.conn_id,
param->connect.remote_bda[0],
param->connect.remote_bda[1],
param->connect.remote_bda[2],
param->connect.remote_bda[3],
param->connect.remote_bda[4],
param->connect.remote_bda[5],
param->connect.is_connected);
gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id;
//start sent the update connection parameters to the peer device.
esp_ble_gap_update_conn_params(&conn_params);
break;
}
配置文件 B 連接事件:
case ESP_GATTS_CONNECT_EVT:
ESP_LOGI(GATTS_TAG, "CONNECT_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:, is_conn %d\n",
param->connect.conn_id,
param->connect.remote_bda[0],
param->connect.remote_bda[1],
param->connect.remote_bda[2],
param->connect.remote_bda[3],
param->connect.remote_bda[4],
param->connect.remote_bda[5],
param->connect.is_connected);
gl_profile_tab[PROFILE_B_APP_ID].conn_id = param->connect.conn_id;
break;
該esp_ble_gap_update_conn_params()函數(shù)觸發(fā)一個(gè) GAP 事件ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT,用于打印連接信息:
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
ESP_LOGI(GATTS_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;
9.2 確定MTU大小
當(dāng)有手機(jī)(client客戶端)連上server時(shí),觸發(fā)ESP_GATTS_MTU_EVT事件,其打印如下圖所示
ESP_GATTS_MTU_EVT事件對應(yīng)的回調(diào)函數(shù)中參數(shù)param的結(jié)構(gòu)體為gatts_mtu_evt_param(包括連接id和MTU大?。?/p>
/**
* @brief ESP_GATTS_MTU_EVT
*/
struct gatts_mtu_evt_param {
uint16_t conn_id; /*!< Connection id */
uint16_t mtu; /*!< MTU size */
} mtu; /*!< Gatt server callback param of ESP_GATTS_MTU_EVT */
在例子中設(shè)置本地的MTU大小為500,代碼如下所示:
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500);
如上所述,設(shè)置了MTU的值(經(jīng)過MTU交換,從而設(shè)置一個(gè)PDU中最大能夠交換的數(shù)據(jù)量)。例如:主設(shè)備發(fā)出一個(gè)150字節(jié)的MTU請求,但是從設(shè)備回應(yīng)的MTU是23字節(jié),那么今后雙方要以較小的值23字節(jié)作為以后的MTU。即主從雙方每次在做數(shù)據(jù)傳輸時(shí)不超過這個(gè)最大數(shù)據(jù)單元。 MTU交換通常發(fā)生在主從雙方建立連接后。MTU比較小,就是為什么BLE不能傳輸大數(shù)據(jù)的原因所在。
參照一分鐘讀懂低功耗(BLE)MTU交換數(shù)據(jù)包 這篇文章就可以了解MTU交換過程。
MTU交換請求用于client通知server關(guān)于client最大接收MTU大小并請求server響應(yīng)它的最大接收MTU大小。
Client的接收MTU 應(yīng)該大于或等于默認(rèn)ATT_MTU(23).這個(gè)請求已建立連接就由client發(fā)出。這個(gè)Client Rx MTU參數(shù)應(yīng)該設(shè)置為client可以接收的attribute protocol PDU最大尺寸。
MTU交換應(yīng)答發(fā)送用于接收到一個(gè)Exchange MTU請求
這個(gè)應(yīng)答由server發(fā)出,server的接收MTU必須大于或等于默認(rèn)ATT_MTU大小。這里的Server Rx MTU應(yīng)該設(shè)置為 服務(wù)器可以接收的attribute protocol PDU 最大尺寸。
Server和Client應(yīng)該設(shè)置ATT_MTU為Client Rx MTU和Server Rx MTU兩者的較小值。
這個(gè)ATT_MTU在server在發(fā)出這個(gè)應(yīng)答后,在發(fā)其他屬性協(xié)議PDU之前生效;在client收到這個(gè)應(yīng)答并在發(fā)其他屬性協(xié)議PDU之前生效。
十、管理讀取事件
現(xiàn)在已經(jīng)創(chuàng)建并啟動了服務(wù)和特征,程序可以接收讀寫事件。讀取操作由ESP_GATTS_READ_EVT事件表示,它具有以下參數(shù):
uint16_t conn_id; /* !< 連接 ID */
uint32_t trans_id; /* !< 傳輸 ID */
esp_bd_addr_t bda; /* !< 讀取的藍(lán)牙設(shè)備地址*/
uint16_t handle; /* !< 屬性句柄*/
uint16_t offset; /* !< 值的偏移量,如果值太長*/
bool is_long; /* !< 值是否過長*/
bool need_rsp; /*!<讀操作需要做響應(yīng)*/
demo中,響應(yīng)是用虛擬數(shù)據(jù)構(gòu)造的,并使用事件給定的相同句柄發(fā)送回主機(jī)。除了響應(yīng)之外,GATT 接口、連接 ID 和傳輸 ID 也作為參數(shù)包含在esp_ble_gatts_send_response()函數(shù)中。如果在創(chuàng)建特征或描述符時(shí)將自動響應(yīng)字節(jié)設(shè)置為 NULL,則此功能是必需的。
case ESP_GATTS_READ_EVT: {
ESP_LOGI(GATTS_TAG, "GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n",
param->read.conn_id, param->read.trans_id, param->read.handle);
esp_gatt_rsp_t rsp;
memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
rsp.attr_value.handle = param->read.handle;
rsp.attr_value.len = 4;
rsp.attr_value.value[0] = 0xde;
rsp.attr_value.value[1] = 0xed;
rsp.attr_value.value[2] = 0xbe;
rsp.attr_value.value[3] = 0xef;
esp_ble_gatts_send_response(gatts_if,
param->read.conn_id,
param->read.trans_id,
ESP_GATT_OK, &rsp);
break;
}
十一、管理寫入事件
寫入事件由事件表示ESP_GATTS_WRITE_EVT,它具有以下參數(shù):
uint16_t conn_id; /* !< 連接 ID */
uint32_t trans_id; /* !< 傳輸 ID */
esp_bd_addr_t bda; /* !< 寫入的藍(lán)牙設(shè)備地址*/
uint16_t handle; /* !< 屬性句柄*/
uint16_t offset; /* !< 值的偏移量,如果值太長*/
bool need_rsp; /* !< 寫操作需要做響應(yīng)*/
bool is_prep; /*!< 這個(gè)寫操作是prepare write */
uint16_t len; /* !< 寫入屬性值長度*/
uint8_t *value; /* !< 寫入屬性值*/
demo中實(shí)現(xiàn)了兩種類型的寫事件,寫特征值和寫長特征值。當(dāng)特征值可以容納在一個(gè)屬性協(xié)議最大傳輸單元 (ATT MTU) 中時(shí),使用第一種類型的寫入,該單元通常為 23 字節(jié)長。當(dāng)要寫入的屬性長于單個(gè) ATT 消息中可以發(fā)送的屬性時(shí)使用第二種類型,通過使用準(zhǔn)備寫入響應(yīng)將數(shù)據(jù)分成多個(gè)塊,然后使用執(zhí)行寫入請求來確認(rèn)或取消完整的寫入請求. 此行為在藍(lán)牙規(guī)范版本 4.2,第 3 卷,G 部分,第 4.9 節(jié)中定義。寫長特征消息流如下圖所示。
當(dāng)觸發(fā)寫入事件時(shí),此示例打印日志消息,然后執(zhí)行example_write_event_env()函數(shù)。
case ESP_GATTS_WRITE_EVT: {
ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d\n", param->write.conn_id, param->write.trans_id, param->write.handle);
if (!param->write.is_prep){
ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len);
esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len);
if (gl_profile_tab[PROFILE_B_APP_ID].descr_handle == param->write.handle && param->write.len == 2){
uint16_t descr_value= param->write.value[1]<<8 | param->write.value[0];
if (descr_value == 0x0001){
if (b_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY){
ESP_LOGI(GATTS_TAG, "notify enable");
uint8_t notify_data[15];
for (int i = 0; i < sizeof(notify_data); ++i)
{
notify_data[i] = i%0xff;
}
//the size of notify_data[] need less than MTU size
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id,
gl_profile_tab[PROFILE_B_APP_ID].char_handle,
sizeof(notify_data),
notify_data, false);
}
}else if (descr_value == 0x0002){
if (b_property & ESP_GATT_CHAR_PROP_BIT_INDICATE){
ESP_LOGI(GATTS_TAG, "indicate enable");
uint8_t indicate_data[15];
for (int i = 0; i < sizeof(indicate_data); ++i)
{
indicate_data[i] = i % 0xff;
}
//the size of indicate_data[] need less than MTU size
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id,
gl_profile_tab[PROFILE_B_APP_ID].char_handle,
sizeof(indicate_data),
indicate_data, true);
}
}
else if (descr_value == 0x0000){
ESP_LOGI(GATTS_TAG, "notify/indicate disable ");
}else{
ESP_LOGE(GATTS_TAG, "unknown value");
}
}
}
example_write_event_env(gatts_if, &a_prepare_write_env, param);
break;
}
該example_write_event_env()函數(shù)包含寫長特征過程的邏輯:
void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){
esp_gatt_status_t status = ESP_GATT_OK;
if (param->write.need_rsp){
if (param->write.is_prep){
if (prepare_write_env->prepare_buf == NULL){
prepare_write_env->prepare_buf = (uint8_t *)malloc(PREPARE_BUF_MAX_SIZE*sizeof(uint8_t));
prepare_write_env->prepare_len = 0;
if (prepare_write_env->prepare_buf == NULL) {
ESP_LOGE(GATTS_TAG, "Gatt_server prep no mem\n");
status = ESP_GATT_NO_RESOURCES;
}
} else {
if(param->write.offset > PREPARE_BUF_MAX_SIZE) {
status = ESP_GATT_INVALID_OFFSET;
}
else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) {
status = ESP_GATT_INVALID_ATTR_LEN;
}
}
esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t));
gatt_rsp->attr_value.len = param->write.len;
gatt_rsp->attr_value.handle = param->write.handle;
gatt_rsp->attr_value.offset = param->write.offset;
gatt_rsp->attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len);
esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id,
param->write.trans_id, status, gatt_rsp);
if (response_err != ESP_OK){
ESP_LOGE(GATTS_TAG, "Send response error\n");
}
free(gatt_rsp);
if (status != ESP_GATT_OK){
return;
}
memcpy(prepare_write_env->prepare_buf + param->write.offset,
param->write.value,
param->write.len);
prepare_write_env->prepare_len += param->write.len;
}else{
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL);
}
}
}
當(dāng)客戶端發(fā)送寫請求或準(zhǔn)備寫請求時(shí),服務(wù)器應(yīng)響應(yīng)。但是,如果客戶端發(fā)送 Write without Response 命令,則服務(wù)器不需要回復(fù)響應(yīng)。這是在寫入過程中通過檢查 的值來檢查的write.need_rsp parameter。如果需要響應(yīng),程序繼續(xù)做響應(yīng)準(zhǔn)備,如果不存在,客戶端不需要響應(yīng),因此程序結(jié)束。響應(yīng)的話會影響數(shù)據(jù)傳輸速度,在需要大數(shù)據(jù)量的場合是否合適需要試驗(yàn)?
void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env,
esp_ble_gatts_cb_param_t *param){
esp_gatt_status_t status = ESP_GATT_OK;
if (param->write.need_rsp){
…
然后該函數(shù)檢查是否write.is_prep設(shè)置了由 表示的 Prepare Write Request 參數(shù),這意味著客戶端正在請求 Write Long Characteristic。如果存在,該過程繼續(xù)準(zhǔn)備多個(gè)寫響應(yīng),如果不存在,則服務(wù)器簡單地發(fā)回單個(gè)寫響應(yīng)。
…
if (param->write.is_prep){
…
}else{
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL);
}
…
為了處理長特征寫入,定義并實(shí)例化了一個(gè)準(zhǔn)備緩沖區(qū)結(jié)構(gòu):
typedef struct {
uint8_t *prepare_buf;
int prepare_len;
} prepare_type_env_t;
static prepare_type_env_t a_prepare_write_env;
static prepare_type_env_t b_prepare_write_env;
為了使用準(zhǔn)備緩沖區(qū),為其分配了一些內(nèi)存空間。如果由于內(nèi)存不足導(dǎo)致分配失敗,則會打印錯(cuò)誤:
else {
if(param->write.offset > PREPARE_BUF_MAX_SIZE) {
status = ESP_GATT_INVALID_OFFSET;
}
else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) {
status = ESP_GATT_INVALID_ATTR_LEN;
}
}
該過程現(xiàn)在準(zhǔn)備esp_gatt_rsp_t要發(fā)送回客戶端的類型響應(yīng)。它使用寫入請求的相同參數(shù)構(gòu)造的響應(yīng),例如長度、句柄和偏移量。另外,寫入該特性所需的GATT認(rèn)證類型設(shè)置為ESP_GATT_AUTH_REQ_NONE,這意味著客戶端可以寫入該特性而無需先進(jìn)行身份驗(yàn)證。一旦發(fā)送響應(yīng),分配給它使用的內(nèi)存就會被釋放。
esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t));
gatt_rsp->attr_value.len = param->write.len;
gatt_rsp->attr_value.handle = param->write.handle;
gatt_rsp->attr_value.offset = param->write.offset;
gatt_rsp->attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len);
esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id,
param->write.trans_id, status, gatt_rsp);
if (response_err != ESP_OK){
ESP_LOGE(GATTS_TAG, "Send response error\n");
}
free(gatt_rsp);
if (status != ESP_GATT_OK){
return;
}
最后,傳入的數(shù)據(jù)被復(fù)制到創(chuàng)建的緩沖區(qū)中,其長度按偏移量遞增:
case ESP_GATTS_EXEC_WRITE_EVT:
ESP_LOGI(GATTS_TAG,"ESP_GATTS_EXEC_WRITE_EVT");
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);
example_exec_write_event_env(&a_prepare_write_env, param);
break;
我們來看看Executive Write函數(shù):
void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){
if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC){
esp_log_buffer_hex(GATTS_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len);
}
else{
ESP_LOGI(GATTS_TAG,"ESP_GATT_PREP_WRITE_CANCEL");
}
if (prepare_write_env->prepare_buf) {
free(prepare_write_env->prepare_buf);
prepare_write_env->prepare_buf = NULL;
}
#### prepare_write_env->prepare_len = 0;
}
執(zhí)行寫入用于確認(rèn)或取消之前完成的寫入過程,由長特征寫入過程。為此,該函數(shù)會檢查exec_write_flag隨事件接收到的參數(shù)中的 。如果標(biāo)志等于 表示的執(zhí)行標(biāo)志exec_write_flag,則確認(rèn)寫入并在日志中打印緩沖區(qū);如果不是,則表示取消寫入并刪除所有已寫入的數(shù)據(jù)。
if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC){
esp_log_buffer_hex(GATTS_TAG,
prepare_write_env->prepare_buf,
prepare_write_env->prepare_len);
}
else{
ESP_LOGI(GATTS_TAG,"ESP_GATT_PREP_WRITE_CANCEL");
}
最后,為存儲來自長寫操作的數(shù)據(jù)塊而創(chuàng)建的緩沖區(qū)結(jié)構(gòu)被釋放,并將其指針設(shè)置為 NULL 以使其為下一個(gè)長寫過程做好準(zhǔn)備。
if (prepare_write_env->prepare_buf) {
free(prepare_write_env->prepare_buf);
prepare_write_env->prepare_buf = NULL;
}
prepare_write_env->prepare_len = 0;
11.1 使能通知
使能notify并讀取藍(lán)牙發(fā)過來的數(shù)據(jù),開啟這個(gè)后我們就能實(shí)時(shí)獲取藍(lán)牙發(fā)過來的值了。
使能通知(notify enable)的打印如下所示,打開通知實(shí)際上的一個(gè)WRITE。
如果write.handle和descr_handle相同,且長度==2,確定descr_value描述值,根據(jù)描述值開啟/關(guān)閉 通知notify/indicate。
//the size of notify_data[] need less than MTU size
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_A_APP_ID].char_handle,
sizeof(notify_data), notify_data, false);
該函數(shù)將notify或indicate發(fā)給GATT的客戶端;
need_confirm = false,則發(fā)送的是notification通知;
==true,發(fā)送的是指示indication。
其他參數(shù): 服務(wù)端訪問接口;連接id; 屬性句柄,value_len; 值
? 由 Leung 寫于 2021 年 7 月 7 日
? 參考:ESP32學(xué)習(xí)筆記(7)藍(lán)牙GATT服務(wù)應(yīng)用
Gatt 服務(wù)器示例演練