ESP32學習筆記(30)——BLE GATT服務端自定義服務和特征

一、簡介

1.1 低功耗藍牙(BLE)協(xié)議棧


鏈路層(LL) 控制設備的射頻狀態(tài)具则,有五個設備狀態(tài):待機、廣播具帮、掃描博肋、初始化和連接。

廣播 為廣播數(shù)據(jù)包蜂厅,而 掃描 則是監(jiān)聽廣播匪凡。

GAP通信中角色,中心設備(Central - 主機) 用來掃描和連接 外圍設備(Peripheral - 從機)掘猿。

大部分情況下外圍設備通過廣播自己來讓中心設備發(fā)現(xiàn)自己病游,并建立 GATT 連接,從而進行更多的數(shù)據(jù)交換稠通。

也有些情況是不需要連接的衬衬,只要外設廣播自己的數(shù)據(jù)即可,用這種方式主要目的是讓外圍設備改橘,把自己的信息發(fā)送給多個中心設備滋尉。

1.2 通用屬性協(xié)議(GATT)

GATT是用Attribute Protocal(屬性協(xié)議)定義的一個service(服務)框架。這個框架定義了Services以及它們的Characteristics的格式和規(guī)程飞主。規(guī)程就是定義了包括發(fā)現(xiàn)狮惜、讀、寫碌识、通知碾篡、指示以及配置廣播的characteristics。

為實現(xiàn)配置文件(Profile)的設備定義了兩種角色:Client(客戶端)筏餐、Server(服務器)开泽。esp32的ble一般就處于Server模式。

一旦兩個設備建立了連接胖烛,GATT就開始發(fā)揮效用眼姐,同時意味著GAP協(xié)議管理的廣播過程結束了。

1.2.1 Profile(規(guī)范)

profile 可以理解為一種規(guī)范佩番,建立的藍牙應用任務众旗,藍牙任務實際上分為兩類:標準藍牙任務規(guī)范 profile(公有任務),非標準藍牙任務規(guī)范 profile(私有任務)趟畏。

  • 標準藍牙任務規(guī)范 profile:指的是從藍牙特別興趣小組 SIG 的官網(wǎng)上已經發(fā)布的 GATT 規(guī)范列表贡歧,包括警告通知(alert notification),血壓測量(blood pressure),心率(heart rate)利朵,電池(battery)等等律想。它們都是針對具體的低功耗藍牙的應用實例來設計的。目前藍牙技術聯(lián)盟還在不斷的制定新的規(guī)范绍弟,并且發(fā)布技即。

  • 非標準藍牙任務規(guī)范 profile:指的是供應商自定義的任務,在藍牙 SIG 小組內未定義的任務規(guī)范樟遣。

1.2.2 Service(服務)

service 可以理解為一個服務而叼,在 BLE 從機中有多個服務,例如:電量信息服務豹悬、系統(tǒng)信息服務等葵陵;
每個 service 中又包含多個 characteristic 特征值;
每個具體的 characteristic 特征值才是 BLE 通信的主題瞻佛,比如當前的電量是 80%脱篙,電量的 characteristic 特征值存在從機的 profile 里,這樣主機就可以通過這個 characteristic 來讀取 80% 這個數(shù)據(jù)伤柄。
GATT 服務一般包含幾個具有相關的功能绊困,比如特定傳感器的讀取和設置,人機接口的輸入輸出响迂。組織具有相關的特性到服務中既實用又有效考抄,因為它使得邏輯上和用戶數(shù)據(jù)上的邊界變得更加清晰,同時它也有助于不同應用程序間代碼的重用蔗彤。

1.2.3 Characteristic(特征)

characteristic 特征川梅,BLE 主從機的通信均是通過 characteristic 來實現(xiàn),可以理解為一個標簽然遏,通過這個標簽可以獲取或者寫入想要的內容贫途。

1.2.4 UUID(通用唯一識別碼)

uuid 通用唯一識別碼,我們剛才提到的 service 和 characteristic 都需要一個唯一的 uuid 來標識待侵;
每個從機都會有一個 profile丢早,不管是自定義的 simpleprofile,還是標準的防丟器 profile秧倾,他們都是由一些 service 組成怨酝,每個 service 又包含了多個 characteristic,主機和從機之間的通信那先,均是通過characteristic來實現(xiàn)农猬。

1.3 ESP32藍牙應用結構

藍牙是?種短距通信系統(tǒng),其關鍵特性包括魯棒性售淡、低功耗斤葱、低成本等慷垮。藍牙系統(tǒng)分為兩種不同的技術:經典藍牙 (Classic Bluetooth) 和藍牙低功耗 (Bluetooth Low Energy)。
ESP32 支持雙模藍牙揍堕,即同時支持經典藍牙和藍牙低功耗闸迷。

從整體結構上犯戏,藍牙可分為控制器 (Controller) 和主機 (Host) 兩?部分:控制器包括了 PHY、Baseband变姨、Link Controller墓臭、Link Manager采郎、Device Manager粟誓、HCI 等模塊枷邪,用于硬件接?管理、鏈路管理等等抖部;主機則包括了 L2CAP、SMP议惰、SDP慎颗、ATT、GATT言询、GAP 以及各種規(guī)范俯萎,構建了向應用層提供接口的基礎,方便應用層對藍牙系統(tǒng)的訪問运杭。主機可以與控制器運行在同?個宿主上夫啊,也可以分布在不同的宿主上。ESP32 可以支持上述兩種方式辆憔。

1.4 Bluedroid主機架構

在 ESP-IDF 中撇眯,使用經過大量修改后的 BLUEDROID 作為藍牙主機 (Classic BT + BLE)。BLUEDROID 擁有較為完善的功能虱咧,?持常用的規(guī)范和架構設計熊榛,同時也較為復雜。經過大量修改后腕巡,BLUEDROID 保留了大多數(shù) BTA 層以下的代碼玄坦,幾乎完全刪去了 BTIF 層的代碼,使用了較為精簡的 BTC 層作為內置規(guī)范及 Misc 控制層绘沉。修改后的 BLUEDROID 及其與控制器之間的關系如下圖:

1.5 ESP32的GATT服務器服務表示例

使用類似表格的數(shù)據(jù)結構來定義服務器服務和特性煎楣,因此,它展示了一種定義服務器的實用方法功能集中在一處车伞,而不是一一添加服務和特性择懂。

二、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

三沸枯、藍牙4.0通信實現(xiàn)過程

  1. 掃描藍牙BLE終端設備,對應esp32就是廣播給大家供掃描
  2. 連接藍牙BLE終端設備赂弓,pad掃描到后去連接
  3. 啟動服務發(fā)現(xiàn)绑榴,連接到esp32后獲取相應的服務。
    連接成功后盈魁,我們就要去尋找我們所需要的服務翔怎,這里需要先啟動服務發(fā)現(xiàn)。
  4. 獲取Characteristic
    之前我們說過杨耙,我們的最終目的就是獲取Characteristic來進行通信赤套,正常情況下,我們可以從硬件工程師那邊得到serviceUUID和characteristicUUID珊膜,也就是我們所比喻的班級號和學號容握,以此來獲得我們的characteristic。
  5. 開始通信
    我們在得到Characteristic后车柠,就可以開始讀寫操作進行通信了剔氏。
    a. 對于讀操作來說,讀取BLE終端設備返回的數(shù)據(jù)會通過回調方法mGattCallback中的onCharacteristicChanged函數(shù)返回竹祷。
    b. 對于寫操作來說谈跛,可以通過向Characteristic寫入指令以此來達到控制BLE終端設備的目的

四、Demo程序GATT啟動流程

使用 esp-idf\examples\bluetooth\bluedroid\ble\gatt_server_service_table 中的例程

.........
//esp_bt_controller_config_t是藍牙控制器配置結構體塑陵,這里使用了一個默認的參數(shù)
    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    //初始化藍牙控制器感憾,此函數(shù)只能被調用一次,且必須在其他藍牙功能被調用之前調用
    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;
    }

    //使能藍牙控制器令花,mode是藍牙模式阻桅,如果想要動態(tài)改變藍牙模式不能直接調用該函數(shù),
    //應該先用disable關閉藍牙再使用該API來改變藍牙模式
    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;
    }
    //初始化藍牙并分配系統(tǒng)資源彭则,它應該被第一個調用
    /*
    藍牙棧bluedroid stack包括了BT和BLE使用的基本的define和API
    初始化藍牙棧以后并不能直接使用藍牙功能鳍刷,
    還需要用FSM管理藍牙連接情況
    */
    ret = esp_bluedroid_init();
    if (ret) {
        ESP_LOGE(GATTS_TABLE_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
        return;
    }
    //使能藍牙棧
    ret = esp_bluedroid_enable();
    if (ret) {
        ESP_LOGE(GATTS_TABLE_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
        return;
    }

    //建立藍牙的FSM(有限狀態(tài)機)
    //這里使用回調函數(shù)來控制每個狀態(tài)下的響應,需要將其在GATT和GAP層的回調函數(shù)注冊
    /*gatts_event_handler和gap_event_handler處理藍牙椄┒叮可能發(fā)生的所有情況输瓜,達到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服務A,相當于1個獨立的應用程序
    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服務B芬萍,相當于1個獨立的應用程序
    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;
    }
    /*
    設置了MTU的值(經過MTU交換尤揣,從而設置一個PDU中最大能夠交換的數(shù)據(jù)量)。
    例如:主設備發(fā)出一個1000字節(jié)的MTU請求柬祠,但是從設備回應的MTU是500字節(jié)北戏,那么今后雙方要以較小的值500字節(jié)作為以后的MTU。
    即主從雙方每次在做數(shù)據(jù)傳輸時不超過這個最大數(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);
    }
.......

五嗜愈、服務數(shù)據(jù)結構體設置

一個GATT 服務器應用程序架構(由Application Profiles組織起來)如下:


每個Profile定義為一個結構體旧蛾,結構體成員依賴于該Application Profile 實現(xiàn)的services服務和characteristic特征。結構體成員還包括GATT interface(GATT 接口)蠕嫁、Application ID(應用程序ID)和處理profile事件的回調函數(shù)锨天。

每個profile包括GATT interface(GATT 接口)、Application ID(應用程序ID)剃毒、 Connection ID(連接ID)病袄、Service Handle(服務句柄)、Service ID(服務ID)赘阀、Characteristic handle(特征句柄)益缠、Characteristic UUID(特征UUID)、ATT權限基公、Characteristic Properties幅慌、描述符句柄、描述符UUID轰豆。

如果Characteristic支持通知(notifications)或指示(indicatons)欠痴,它就必須是實現(xiàn)CCCD(Client Characteristic Configuration Descriptor)----這是額外的ATT。描述符有一個句柄和UUID秒咨。如:

struct gatts_profile_inst {
    esp_gatts_cb_t gatts_cb;       //GATT的回調函數(shù)
    uint16_t gatts_if;             //GATT的接口
    uint16_t app_id;               //應用的ID
    uint16_t conn_id;              //連接的ID
    uint16_t service_handle;       //服務Service句柄
    esp_gatt_srvc_id_t service_id; //服務Service ID
    uint16_t char_handle;          //特征Characteristic句柄
    esp_bt_uuid_t char_uuid;       //特征Characteristic的UUID
    esp_gatt_perm_t perm;          //特征屬性Attribute 授權
    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ù)組中,由于本示例中只有一個配置文件掌挚,因此一個元素存儲在數(shù)組中雨席,索引為零,如HEART_PROFILE_APP_IDX吠式。此外陡厘,還初始化了配置文件事件處理程序回調函數(shù)gatts_profile_event_handler。GATT 服務端上的不同應用程序使用不同的接口特占,由 gatts_if 參數(shù)表示糙置。對于初始化,此參數(shù)設置為ESP_GATT_IF_NONE是目,這意味著應用程序配置文件尚未鏈接到任何客戶端谤饭。

/* 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 */
    },
};

這是兩個元素的數(shù)組“媚桑可以用Application ID來注冊Application Profiles揉抵,Application ID是由應用程序分配的用來標識每個Profile。 通過這種方法嗤疯,可以在一個Server中擁有多個Application Profile冤今。

esp_ble_gatts_app_register (PROFILE_A_APP_ID);
esp_ble_gatts_app_register (PROFILE_B_APP_ID);

六、GATT事件處理程序

其作用就是建立了藍牙GATT的FSM(有限狀態(tài)機)茂缚,callback回調函數(shù)處理從BLE堆棧推送到應用程序的所有事件戏罢。

回調函數(shù)的參數(shù):

  • event: esp_gatts_cb_event_t 這是一個枚舉類型屋谭,表示調用該回調函數(shù)時的事件(或藍牙的狀態(tài))
  • gatts_if: esp_gatt_if_t (uint8_t) 這是GATT訪問接口類型,通常在GATT客戶端上不同的應用程序用不同的gatt_if(不同的Application profile對應不同的gatts_if) 龟糕,調用esp_ble_gatts_app_register()時桐磁,注冊Application profile 就會有一個gatts_if。
  • param: esp_ble_gatts_cb_param_t 指向回調函數(shù)的參數(shù)翩蘸,是個聯(lián)合體類型所意,不同的事件類型采用聯(lián)合體內不同的成員結構體。
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
    /*如果事件是注冊事件催首,則為每個配置文件存儲 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扶踊,則調用 profile A cb handler,
     * 所以這里調用每個 profile 的回調*/
    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)建服務

當調用esp_ble_gatts_app_register()注冊一個應用程序Profile(Application Profile)秧耗,將觸發(fā)ESP_GATTS_REG_EVT事件,除了可以完成對應profile的gatts_if的注冊,還可以調用esp_bel_create_attr_tab()來創(chuàng)建profile Attributes 表或創(chuàng)建一個服務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

句柄是:

  • 服務句柄 GATTS_SERVICE_UUID_TEST_B 0x00EE
  • 特征手柄 GATTS_CHAR_UUID_TEST_B 0xEE01
  • 特征值句柄
  • 特征描述符句柄 GATTS_DESCR_UUID_TEST_B 0x2222

該服務被定義為具有 16 位 UUID 長度的主要服務分井。服務 ID 使用實例 ID = 0 和由 定義的 UUID 進行初始化GATTS_SERVICE_UUID_TEST_A。

服務實例 ID 可用于區(qū)分具有相同 UUID 的多個服務霉猛。在此示例中尺锚,由于每個應用程序配置文件只有一個服務并且服務具有不同的 UUID,因此在配置文件 A 和 B 中可以將服務實例 ID 定義為 0惜浅。但是瘫辩,如果只有一個應用程序配置文件具有兩個服務使用相同的 UUID,則有必要使用不同的實例 ID 來引用一個或另一個服務坛悉。

demo中的gatts_event_handler()回調函數(shù)—調用esp_ble_gatts_app_register()伐厌,觸發(fā)ESP_GATTS_REG_EVT時,完成對每個profile 的gatts_if 的注冊裸影。

gl_profile_tab[param->reg.app_id].gatts_if = gatts_if;

如果gatts_if == 某個Profile的gatts_if時挣轨,調用對應profile的回調函數(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);
    }
}

八轩猩、啟動服務和創(chuàng)建特征

8.1 啟動服務

當一個服務service創(chuàng)建成功后卷扮,由該profile GATT handler 管理的 ESP_GATTS_CREATE_EVT事件被觸發(fā),在這個事件可以啟動服務和添加特征characteristics到服務中界轩。調用esp_ble_gatts_start_service()來啟動指定服務画饥。

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堆棧生成生成的服務句柄(service handle)存儲在配置文件Profile表中浊猾,應用層將用服務句柄來引用這個服務抖甘。調用esp_ble_gatts_start_service()和先前產生服務句柄來啟動服務。

8.2 創(chuàng)建特征

Characteristic是在GATT規(guī)范中最小的邏輯數(shù)據(jù)單元葫慎,由一個Value和多個描述特性的Desciptior組成衔彻。實際上薇宠,在與藍牙設備打交道,主要就是讀寫Characteristic的value來完成艰额。 同樣的澄港,Characteristic也是通過16bit或128bit的UUID唯一標識。

我們根據(jù)藍牙設備的協(xié)議用對應的Characteristci進行讀寫即可達到與其通信的目的柄沮。

添加特征到service中回梧,調用esp_ble_gatts_add_char()來添加characteristics連同characteristic權限和property(屬性)到服務service中。

權限:

  • 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: 特性可以通知值的變化

同時擁有讀寫權限和屬性似乎是多余的祖搓。但是狱意,屬性的讀寫屬性是向客戶端顯示的信息,目的是讓客戶端知道服務器是否接受讀寫請求拯欧。從這個意義上說详囤,這些屬性充當客戶端正確訪問服務器資源的提示。另一方面镐作,權限是授予客戶端讀取或寫入該屬性的授權藏姐。例如,如果客戶端嘗試寫入它沒有寫入權限的屬性该贾,即使設置了寫入屬性羔杨,服務器也會拒絕該請求。

此外杨蛋,demo還為表示特征提供了一個初始值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, 
};

特征初始值必須是非空對象并且特征長度必須始終大于零,否則堆棧將返回錯誤六荒。

最后,特性被配置為每次讀取或寫入特性時都需要手動發(fā)送響應矾端,而不是讓堆棧自動響應掏击。這是通過將esp_ble_gatts_add_char()函數(shù)的最后一個參數(shù)(表示屬性響應控制參數(shù))設置為ESP_GATT_RSP_BY_APP或 NULL 來配置的。

七秩铆、創(chuàng)建特征描述符

當特征添加到service中成功時砚亭,觸發(fā)ESP_GATTS_ADD_CHAR_EVT事件。該事件返回由堆棧為剛剛添加的特征生成的句柄殴玛。該事件包括以下參數(shù):

esp_gatt_status_t狀態(tài)捅膘;          /* !< 操作狀態(tài)*/
uint16_t attr_handle;              /* !< 特征屬性句柄*/
uint16_t service_handle;           /* !< 服務屬性句柄*/
esp_bt_uuid_t char_uuid;           /* !< 特征 uuid */

事件返回的屬性句柄存儲在配置文件表中,并且還設置了特征描述符長度和 UUID滚粟。使用該esp_ble_gatts_get_attr_value()函數(shù)讀取特征長度和值寻仗,然后打印以供參考。最后凡壤,使用該esp_ble_gatts_add_char_descr()函數(shù)添加特征描述署尤。使用的參數(shù)是服務句柄耙替、描述符 UUID、寫入和讀取權限曹体、初始值和自動響應設置俗扇。特征描述符的初始值可以是空指針,自動響應參數(shù)也設置為空箕别,這意味著需要響應的請求必須手動回復铜幽。

    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ù)

一個ESP_GATTS_CONNECT_EVT當客戶端已連接到服務器GATT被觸發(fā)厨诸。此事件用于更新連接參數(shù)镶殷,例如延遲、最小連接間隔微酬、最大連接間隔和超時绘趋。連接參數(shù)存儲在一個esp_ble_conn_update_params_t結構中,然后傳遞給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ā)一個 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大小

當有手機(client客戶端)連上server時比吭,觸發(fā)ESP_GATTS_MTU_EVT事件绽族,其打印如下圖所示

ESP_GATTS_MTU_EVT事件對應的回調函數(shù)中參數(shù)param的結構體為gatts_mtu_evt_param(包括連接id和MTU大小)

/**
 * @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 */

在例子中設置本地的MTU大小為500衩藤,代碼如下所示:

esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500);
如上所述吧慢,設置了MTU的值(經過MTU交換,從而設置一個PDU中最大能夠交換的數(shù)據(jù)量)赏表。例如:主設備發(fā)出一個150字節(jié)的MTU請求检诗,但是從設備回應的MTU是23字節(jié),那么今后雙方要以較小的值23字節(jié)作為以后的MTU瓢剿。即主從雙方每次在做數(shù)據(jù)傳輸時不超過這個最大數(shù)據(jù)單元逢慌。 MTU交換通常發(fā)生在主從雙方建立連接后。MTU比較小间狂,就是為什么BLE不能傳輸大數(shù)據(jù)的原因所在攻泼。

參照一分鐘讀懂低功耗(BLE)MTU交換數(shù)據(jù)包 這篇文章就可以了解MTU交換過程。

MTU交換請求用于client通知server關于client最大接收MTU大小并請求server響應它的最大接收MTU大小。

Client的接收MTU 應該大于或等于默認ATT_MTU(23).這個請求已建立連接就由client發(fā)出坠韩。這個Client Rx MTU參數(shù)應該設置為client可以接收的attribute protocol PDU最大尺寸距潘。

MTU交換應答發(fā)送用于接收到一個Exchange MTU請求

這個應答由server發(fā)出,server的接收MTU必須大于或等于默認ATT_MTU大小只搁。這里的Server Rx MTU應該設置為 服務器可以接收的attribute protocol PDU 最大尺寸音比。

Server和Client應該設置ATT_MTU為Client Rx MTU和Server Rx MTU兩者的較小值。

這個ATT_MTU在server在發(fā)出這個應答后氢惋,在發(fā)其他屬性協(xié)議PDU之前生效洞翩;在client收到這個應答并在發(fā)其他屬性協(xié)議PDU之前生效。

十焰望、管理讀取事件

現(xiàn)在已經創(chuàng)建并啟動了服務和特征骚亿,程序可以接收讀寫事件。讀取操作由ESP_GATTS_READ_EVT事件表示熊赖,它具有以下參數(shù):

uint16_t conn_id;          /* !< 連接 ID */
uint32_t trans_id;         /* !< 傳輸 ID */
esp_bd_addr_t bda;         /* !< 讀取的藍牙設備地址*/
uint16_t handle;           /* !< 屬性句柄*/
uint16_t offset;           /* !< 值的偏移量来屠,如果值太長*/
bool is_long;              /* !< 值是否過長*/
bool need_rsp;             /*!<讀操作需要做響應*/

demo中,響應是用虛擬數(shù)據(jù)構造的震鹉,并使用事件給定的相同句柄發(fā)送回主機俱笛。除了響應之外,GATT 接口传趾、連接 ID 和傳輸 ID 也作為參數(shù)包含在esp_ble_gatts_send_response()函數(shù)中迎膜。如果在創(chuàng)建特征或描述符時將自動響應字節(jié)設置為 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;        /* !< 寫入的藍牙設備地址*/
uint16_t handle;          /* !< 屬性句柄*/
uint16_t offset;          /* !< 值的偏移量簸呈,如果值太長*/
bool need_rsp;            /* !< 寫操作需要做響應*/
bool is_prep;             /*!< 這個寫操作是prepare write */
uint16_t len;             /* !< 寫入屬性值長度*/
uint8_t *value;           /* !< 寫入屬性值*/

demo中實現(xiàn)了兩種類型的寫事件榕订,寫特征值和寫長特征值。當特征值可以容納在一個屬性協(xié)議最大傳輸單元 (ATT MTU) 中時蜕便,使用第一種類型的寫入卸亮,該單元通常為 23 字節(jié)長。當要寫入的屬性長于單個 ATT 消息中可以發(fā)送的屬性時使用第二種類型玩裙,通過使用準備寫入響應將數(shù)據(jù)分成多個塊,然后使用執(zhí)行寫入請求來確認或取消完整的寫入請求. 此行為在藍牙規(guī)范版本 4.2段直,第 3 卷吃溅,G 部分,第 4.9 節(jié)中定義鸯檬。寫長特征消息流如下圖所示决侈。

當觸發(fā)寫入事件時,此示例打印日志消息,然后執(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);
        }
    }
}

當客戶端發(fā)送寫請求或準備寫請求時枉圃,服務器應響應。但是庐冯,如果客戶端發(fā)送 Write without Response 命令孽亲,則服務器不需要回復響應。這是在寫入過程中通過檢查 的值來檢查的write.need_rsp parameter展父。如果需要響應返劲,程序繼續(xù)做響應準備,如果不存在栖茉,客戶端不需要響應篮绿,因此程序結束。響應的話會影響數(shù)據(jù)傳輸速度吕漂,在需要大數(shù)據(jù)量的場合是否合適需要試驗亲配?

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設置了由 表示的 Prepare Write Request 參數(shù),這意味著客戶端正在請求 Write Long Characteristic惶凝。如果存在吼虎,該過程繼續(xù)準備多個寫響應,如果不存在梨睁,則服務器簡單地發(fā)回單個寫響應鲸睛。

…
if (param->write.is_prep){
…
}else{
    esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL);
}
…

為了處理長特征寫入,定義并實例化了一個準備緩沖區(qū)結構:

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;

為了使用準備緩沖區(qū)坡贺,為其分配了一些內存空間官辈。如果由于內存不足導致分配失敗,則會打印錯誤:

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)在準備esp_gatt_rsp_t要發(fā)送回客戶端的類型響應遍坟。它使用寫入請求的相同參數(shù)構造的響應拳亿,例如長度、句柄和偏移量愿伴。另外肺魁,寫入該特性所需的GATT認證類型設置為ESP_GATT_AUTH_REQ_NONE,這意味著客戶端可以寫入該特性而無需先進行身份驗證隔节。一旦發(fā)送響應鹅经,分配給它使用的內存就會被釋放。

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ù)被復制到創(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í)行寫入用于確認或取消之前完成的寫入過程,由長特征寫入過程幻妓。為此蹦误,該函數(shù)會檢查exec_write_flag隨事件接收到的參數(shù)中的 。如果標志等于 表示的執(zhí)行標志exec_write_flag,則確認寫入并在日志中打印緩沖區(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ū)結構被釋放熟吏,并將其指針設置為 NULL 以使其為下一個長寫過程做好準備。

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并讀取藍牙發(fā)過來的數(shù)據(jù)涡真,開啟這個后我們就能實時獲取藍牙發(fā)過來的值了分俯。

使能通知(notify enable)的打印如下所示,打開通知實際上的一個WRITE哆料。

如果write.handle和descr_handle相同缸剪,且長度==2,確定descr_value描述值东亦,根據(jù)描述值開啟/關閉 通知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ù): 服務端訪問接口壮啊;連接id嫉鲸; 屬性句柄,value_len; 值


? 由 Leung 寫于 2021 年 7 月 7 日

? 參考:ESP32學習筆記(7)藍牙GATT服務應用
    Gatt 服務器示例演練

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末歹啼,一起剝皮案震驚了整個濱河市玄渗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌狸眼,老刑警劉巖藤树,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異拓萌,居然都是意外死亡岁钓,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門微王,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屡限,“玉大人,你說我怎么就攤上這事炕倘【螅” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵激才,是天一觀的道長。 經常有香客問我,道長瘸恼,這世上最難降的妖魔是什么劣挫? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮东帅,結果婚禮上压固,老公的妹妹穿的比我還像新娘。我一直安慰自己靠闭,他們只是感情好帐我,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布认臊。 她就那樣靜靜地躺著扒袖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪毅往。 梳的紋絲不亂的頭發(fā)上檩淋,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天芬为,我揣著相機與錄音,去河邊找鬼蟀悦。 笑死媚朦,一個胖子當著我的面吹牛,可吹牛的內容都是我干的日戈。 我是一名探鬼主播询张,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼浙炼!你這毒婦竟也來了份氧?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鼓拧,失蹤者是張志新(化名)和其女友劉穎半火,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體季俩,經...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡钮糖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了酌住。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片店归。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖酪我,靈堂內的尸體忽然破棺而出消痛,到底是詐尸還是另有隱情,我是刑警寧澤都哭,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布秩伞,位于F島的核電站逞带,受9級特大地震影響,放射性物質發(fā)生泄漏纱新。R本人自食惡果不足惜展氓,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望脸爱。 院中可真熱鬧遇汞,春花似錦、人聲如沸簿废。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽族檬。三九已至歪赢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間导梆,已是汗流浹背轨淌。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留看尼,地道東北人递鹉。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像藏斩,于是被迫代替她去往敵國和親躏结。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內容