ESP32學(xué)習(xí)筆記(30)——BLE GATT服務(wù)端自定義服務(wù)和特征

一、簡介

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.hbt/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)過程

  1. 掃描藍(lán)牙BLE終端設(shè)備,對應(yīng)esp32就是廣播給大家供掃描
  2. 連接藍(lán)牙BLE終端設(shè)備,pad掃描到后去連接
  3. 啟動服務(wù)發(fā)現(xiàn),連接到esp32后獲取相應(yīng)的服務(wù)。
    連接成功后,我們就要去尋找我們所需要的服務(wù),這里需要先啟動服務(wù)發(fā)現(xiàn)。
  4. 獲取Characteristic
    之前我們說過,我們的最終目的就是獲取Characteristic來進(jìn)行通信,正常情況下,我們可以從硬件工程師那邊得到serviceUUID和characteristicUUID,也就是我們所比喻的班級號和學(xué)號,以此來獲得我們的characteristic。
  5. 開始通信
    我們在得到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ù)器示例演練

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容