日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

從ESP32 BLE應(yīng)用理解GATT

 iamlijin 2019-05-20

目錄

1、背景

1.1參考資料

1.2 GATT是什么玩意

2、ESP32 例程分析

2.1 GATT 服務(wù)器的架構(gòu)組織

2.2 從GATT回調(diào)函數(shù)注冊程序esp_ble_gatts_register_callback開始深入分析

3 建立連接之前的GATT狀態(tài)機

3.1 創(chuàng)建服務(wù) creating services

3.2 啟動服務(wù)并創(chuàng)建Characteristics

3.2.1 添加Characteristic Value declaration ATT-------看這里

3.3 添加Characteristic descriptor 描述符

4 建立連接時GATT的狀態(tài)機研究

4.1連接后觸發(fā)的連接回調(diào)函數(shù)

4.2連接后MTU大小確定

4.3 發(fā)送應(yīng)答數(shù)據(jù)

4.4 客戶端和服務(wù)器端 收發(fā)數(shù)據(jù)

4.4.1 手機端的操作

4.4.2 設(shè)備端的操作

4.4.4.1 使能通知

4.4.4.2 讀寫數(shù)據(jù)

4.5 Characteristic結(jié)構(gòu)

5. 數(shù)據(jù)交互其他必要知道的

6.結(jié)束語


 1、背景

雖然看了一些BLE的資料,可是對藍牙依舊不太了解?,F(xiàn)在從ESP32 的示例出發(fā)結(jié)合esp_gatts_api.h學(xué)習(xí)GATT。

本文以創(chuàng)建Service和Characteristic為中心展開分析。

1.1參考資料

BLE GATT 介紹 http://www.cnblogs.com/smart-mutouren/p/5937990.html

ble v4.2

1.2 GATT是什么玩意

GATT是用A他tribute Protocal(屬性協(xié)議)定義的一個service(服務(wù))框架。這個框架定義了Services以及它們的Characteristics的格式和規(guī)程。規(guī)程就是定義了包括發(fā)現(xiàn)、讀、寫、通知、指示以及配置廣播的characteristics。------BLE V4.2給出了如下定義(翻譯有待加強)。

Profile中所使用的Protocols(協(xié)議)

為實現(xiàn)這個Profile的設(shè)備定義了兩種角色:Client(客戶端)、Server(服務(wù)器)。角色不是不固定的哦.....

在GATT的Profile的定義11個features,映射了程序Procedure。

Feature對應(yīng)的Procedure
序號 Feature Procedure
1 Server Configuration 服務(wù)器配置 Exchange MTU  連接期間只能配置一次,確定連接通信的ATT_MTU大小;否則就用默認的23-3 = 20
2 Primary Service Discover 主服務(wù)查找 Discover All Primary Services/Discover Primary Services By Service UUID 把主服務(wù)都找出來,
3 Relationship Discover 查找Included Services
4 Characteristic Discover 特征查找

Discover All Characteristic of a Service./Discover Characteristic By UUID

這里找到的是Characteristic declaration的ATT Handle 和ATT Value。這個ATT value包括Characteristic Properties, 特征值的句柄和Characteristic UUID。特征值的句柄在讀寫特征值時要用到。

5    
6    
7    
8    
9    
10    
11    

1)配置交換(exchanging configuration)

2)發(fā)現(xiàn)一個設(shè)備上的服務(wù)s和特征s

3)讀取一個特征值(characteristic value)

4)寫入一個特征值

5)通知一個特征值

6)指示一個特征值 

2、ESP32 例程分析

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

GATT連接是獨占的,即一個BLE周邊設(shè)備同時只能與一個中心設(shè)備連接。??????

profile 可以理解為一種規(guī)范,一個標準的通信協(xié)議中,存于從機(Server)中。藍牙組織規(guī)定了一些標準的Profile。每個profile中包含多個Service,每個service代表從機的一種能力。

2.1 GATT 服務(wù)器的架構(gòu)組織

一個GATT 服務(wù)器應(yīng)用程序架構(gòu)(由Application Profiles組織起來)如下: 

每個Application Profile描述了一個方法來對為一個客戶端應(yīng)用程序設(shè)計的功能進行分組,例如在智能手機或平板電腦上運行的移動應(yīng)用程序。

每個Profile定義為一個結(jié)構(gòu)體,結(jié)構(gòu)體成員依賴于該Application Profile 實現(xiàn)的services服務(wù)和characteristic特征。結(jié)構(gòu)體成員還包括GATT interface(GATT 接口)、Application ID(應(yīng)用程序ID)和處理profile事件的回調(diào)函數(shù)。

每個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),它就必須是實現(xiàn)CCCD(Client Characteristic  Configuration Descriptor)----這是額外的ATT。描述符有一個句柄和UUID。

  1. struct gatts_profile_inst {
  2. esp_gatts_cb_t gatts_cb;
  3. uint16_t gatts_if;
  4. uint16_t app_id;
  5. uint16_t conn_id;
  6. uint16_t service_handle;
  7. esp_gatt_srvc_id_t service_id;
  8. uint16_t char_handle;
  9. esp_bt_uuid_t char_uuid;
  10. esp_gatt_perm_t perm;
  11. esp_gatt_char_prop_t property;
  12. uint16_t descr_handle;
  13. esp_bt_uuid_t descr_uuid;
  14. };

Application Profile存儲在數(shù)組中,并分配相應(yīng)的回調(diào)函數(shù)gatts_profile_a_event_handler() 和 gatts_profile_b_event_handler()。

在GATT客戶機上的不同的應(yīng)用程序使用不同的接口,用gatts_if參數(shù)來表示。在初始化時,gatts-if參數(shù)初始化為ESP_GATT_IF_NONE,這意味著Application Profile還沒有連接任何客戶端。

  1. /* One gatt-based profile one app_id and one gatts_if, this array will store the gatts_if returned by ESP_GATTS_REG_EVT */
  2. static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = {
  3. [PROFILE_A_APP_ID] = {
  4. .gatts_cb = gatts_profile_a_event_handler,
  5. .gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
  6. },
  7. [PROFILE_B_APP_ID] = {
  8. .gatts_cb = gatts_profile_b_event_handler,/* This demo does not implement, similar as profile A */
  9. .gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
  10. },
  11. };

這是兩個元素的數(shù)組??梢杂肁pplication ID來注冊Application Profiles,Application ID是由應(yīng)用程序分配的用來標識每個Profile。 通過這種方法,可以在一個Server中run多個Application Profile。

esp_ble_gatts_app_register(PROFILE_A_APP_ID);

2.2 從GATT回調(diào)函數(shù)注冊程序esp_ble_gatts_register_callback開始深入分析

看樣子,也是一個狀態(tài)機,下面是GATT回調(diào)函數(shù)的注冊函數(shù)esp_ble_gatts_register_callback。

esp_err_t esp_ble_gatts_register_callback(esp_gatts_cb_t callback);

作用:向BTA GATTS模塊注冊應(yīng)用程序回調(diào)函數(shù)。

callback回調(diào)函數(shù)處理從BLE堆棧推送到應(yīng)用程序的所有事件。

對于GATT server回調(diào)函數(shù)類型進行分析

  1. typedef void (* esp_gatts_cb_t)(esp_gatts_cb_event_t event,
  2. esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);

回調(diào)函數(shù)的參數(shù):
event: esp_gatts_cb_event_t  這是一個枚舉類型,表示調(diào)用該回調(diào)函數(shù)時的事件(或藍牙的狀態(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()時,注冊Application profile 就會有一個gatts_if。

param: esp_ble_gatts_cb_param_t 指向回調(diào)參數(shù),是個聯(lián)合體類型,不同的事件類型采用聯(lián)合體內(nèi)不同的成員結(jié)構(gòu)體。

第一步、看看藍牙狀態(tài)機有哪些狀態(tài)類型esp_gatts_cb_event_t 

  1. typedef enum {
  2. ESP_GATTS_REG_EVT = 0, /*!< When register application id, the event comes */
  3. ESP_GATTS_READ_EVT = 1, /*!< When gatt client request read operation, the event comes */
  4. ESP_GATTS_WRITE_EVT = 2, /*!< When gatt client request write operation, the event comes */
  5. ESP_GATTS_EXEC_WRITE_EVT = 3, /*!< When gatt client request execute write, the event comes */
  6. ESP_GATTS_MTU_EVT = 4, /*!< When set mtu complete, the event comes */
  7. ESP_GATTS_CONF_EVT = 5, /*!< When receive confirm, the event comes */
  8. ESP_GATTS_UNREG_EVT = 6, /*!< When unregister application id, the event comes */
  9. ESP_GATTS_CREATE_EVT = 7, /*!< When create service complete, the event comes */
  10. ESP_GATTS_ADD_INCL_SRVC_EVT = 8, /*!< When add included service complete, the event comes */
  11. ESP_GATTS_ADD_CHAR_EVT = 9, /*!< When add characteristic complete, the event comes */
  12. ESP_GATTS_ADD_CHAR_DESCR_EVT = 10, /*!< When add descriptor complete, the event comes */
  13. ESP_GATTS_DELETE_EVT = 11, /*!< When delete service complete, the event comes */
  14. ESP_GATTS_START_EVT = 12, /*!< When start service complete, the event comes */
  15. ESP_GATTS_STOP_EVT = 13, /*!< When stop service complete, the event comes */
  16. ESP_GATTS_CONNECT_EVT = 14, /*!< When gatt client connect, the event comes */
  17. ESP_GATTS_DISCONNECT_EVT = 15, /*!< When gatt client disconnect, the event comes */
  18. ESP_GATTS_OPEN_EVT = 16, /*!< When connect to peer, the event comes */
  19. ESP_GATTS_CANCEL_OPEN_EVT = 17, /*!< When disconnect from peer, the event comes */
  20. ESP_GATTS_CLOSE_EVT = 18, /*!< When gatt server close, the event comes */
  21. ESP_GATTS_LISTEN_EVT = 19, /*!< When gatt listen to be connected the event comes */
  22. ESP_GATTS_CONGEST_EVT = 20, /*!< When congest happen, the event comes */
  23. /* following is extra event */
  24. ESP_GATTS_RESPONSE_EVT = 21, /*!< When gatt send response complete, the event comes */
  25. ESP_GATTS_CREAT_ATTR_TAB_EVT = 22, /*!< When gatt create table complete, the event comes */
  26. ESP_GATTS_SET_ATTR_VAL_EVT = 23, /*!< When gatt set attr value complete, the event comes */
  27. ESP_GATTS_SEND_SERVICE_CHANGE_EVT = 24, /*!< When gatt send service change indication complete, the event comes */
  28. } esp_gatts_cb_event_t;

第二步、再來看一個很有意思的聯(lián)合體類型esp_ble_gatts_cb_param_t,不同的事件類型聯(lián)合體的對象也不同。

  1. typedef union {
  2. /**
  3. * @brief ESP_GATTS_REG_EVT
  4. */
  5. struct gatts_reg_evt_param {
  6. esp_gatt_status_t status; /*!< Operation status */
  7. uint16_t app_id; /*!< Application id which input in register API */
  8. } reg; /*!< Gatt server callback param of ESP_GATTS_REG_EVT */
  9. ........................
  10. /**
  11. * @brief ESP_GATTS_SET_ATTR_VAL_EVT
  12. */
  13. struct gatts_set_attr_val_evt_param{
  14. uint16_t srvc_handle; /*!< The service handle */
  15. uint16_t attr_handle; /*!< The attribute handle */
  16. esp_gatt_status_t status; /*!< Operation status*/
  17. } set_attr_val; /*!< Gatt server callback param of ESP_GATTS_SET_ATTR_VAL_EVT */
  18. /**
  19. * @brief ESP_GATTS_SEND_SERVICE_CHANGE_EVT
  20. */
  21. struct gatts_send_service_change_evt_param{
  22. esp_gatt_status_t status; /*!< Operation status*/
  23. } service_change; /*!< Gatt server callback param of ESP_GATTS_SEND_SERVICE_CHANGE_EVT */
  24. } esp_ble_gatts_cb_param_t;

示例中的gatts_event_handler()回調(diào)函數(shù)---調(diào)用esp_ble_gatts_app_register(1),觸發(fā)ESP_GATTS_REG_EVT時

1.完成對每個profile 的gatts_if 的注冊

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

2.如果gatts_if == 某個Profile的gatts_if時,調(diào)用對應(yīng)profile的回調(diào)函數(shù)處理事情。

  1. if (gatts_if == ESP_GATT_IF_NONE||gatts_if == gl_profile_tab[idx].gatts_if) {
  2. if (gl_profile_tab[idx].gatts_cb) {
  3. gl_profile_tab[idx].gatts_cb(event, gatts_if, param);
  4. }
  5. }

狀態(tài)機一般狀態(tài)轉(zhuǎn)變過程:

以gatts_server這個demo為例,講解GATT狀態(tài)機的一般過程:

沒有Client連接之前:
REGISTER_APP_EVT---->CREATE_SERVICE_EVT---->SERVICE_START_EVT---->ADD_CHAR_EVT--->ADD_DESCR_EVT

有Client開始連接之后:

CONNECT_EVT---->ESP_GATTS_MTU_EVT--->GATT_WRITE_EVT--->ESP_GATTS_CONF_EVT-->GATT_READ_EVT

1>當-調(diào)用esp_ble_gatts_app_register()注冊一個應(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)建一個服務(wù)esp_ble_gatts_create_service()

  1. esp_err_t esp_ble_gatts_create_attr_tab(const esp_gatts_attr_db_t *gatts_attr_db,
  2. esp_gatt_if_t gatts_if,
  3. uint8_t max_nb_attr,
  4. uint8_t srvc_inst_id);

作用:創(chuàng)建一個服務(wù)Attribute表。

參數(shù)

gatts_attr_db :指向加入profile的服務(wù) attr 表 (從Service 到 Characteristic....)

gatts_if: GATT服務(wù)器的訪問接口

max_nb_attr: 加入服務(wù)數(shù)據(jù)庫的attr的數(shù)目

srvc_inst_id: 服務(wù)instance

  1. typedef struct
  2. {
  3. esp_attr_control_t attr_control; /*!< The attribute control type */
  4. esp_attr_desc_t att_desc; /*!< The attribute type */
  5. } esp_gatts_attr_db_t;

 對于結(jié)構(gòu)體esp_gatts_attr_db_t的成員attr_control的可取值

#define ESP_GATT_RSP_BY_APP             0         //由應(yīng)用程序回復(fù)寫入\讀取操作應(yīng)答
#define ESP_GATT_AUTO_RSP                1         //由GATT堆棧自動回復(fù)吸入\讀取操作應(yīng)答

成員 att_desc的結(jié)構(gòu)體類型

  1. /**
  2. * @brief Attribute description (used to create database)
  3. */
  4. typedef struct
  5. {
  6. uint16_t uuid_length; /*!< UUID length */
  7. uint8_t *uuid_p; /*!< UUID value */
  8. uint16_t perm; /*!< Attribute permission */
  9. uint16_t max_length; /*!< Maximum length of the element*/
  10. uint16_t length; /*!< Current length of the element*/
  11. uint8_t *value; /*!< Element value array*/
  12. } esp_attr_desc_t;

以心率計Profile為例說明:

  1. esp_ble_gatts_create_attr_tab(heart_rate_gatt_db, gatts_if,
  2. HRS_IDX_NB, HEART_RATE_SVC_INST_ID);
  3. 詳細的heart_rate_gatt_db
  4. /// Full HRS Database Description - Used to add attributes into the database
  5. static const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB] =
  6. {
  7. // Heart Rate Service Declaration
  8. [HRS_IDX_SVC] =
  9. {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
  10. sizeof(uint16_t), sizeof(heart_rate_svc), (uint8_t *)&heart_rate_svc}},
  11. // Heart Rate Measurement Characteristic Declaration
  12. [HRS_IDX_HR_MEAS_CHAR] =
  13. {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
  14. CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_notify}},
  15. // Heart Rate Measurement Characteristic Value
  16. [HRS_IDX_HR_MEAS_VAL] =
  17. {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&heart_rate_meas_uuid, ESP_GATT_PERM_READ,
  18. HRPS_HT_MEAS_MAX_LEN,0, NULL}},
  19. // Heart Rate Measurement Characteristic - Client Characteristic Configuration Descriptor
  20. [HRS_IDX_HR_MEAS_NTF_CFG] =
  21. {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
  22. sizeof(uint16_t),sizeof(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}},
  23. // Body Sensor Location Characteristic Declaration
  24. [HRS_IDX_BOBY_SENSOR_LOC_CHAR] =
  25. {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
  26. CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read}},
  27. // Body Sensor Location Characteristic Value
  28. [HRS_IDX_BOBY_SENSOR_LOC_VAL] =
  29. {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&body_sensor_location_uuid, ESP_GATT_PERM_READ_ENCRYPTED,
  30. sizeof(uint8_t), sizeof(body_sensor_loc_val), (uint8_t *)body_sensor_loc_val}},
  31. // Heart Rate Control Point Characteristic Declaration
  32. [HRS_IDX_HR_CTNL_PT_CHAR] =
  33. {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
  34. CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write}},
  35. // Heart Rate Control Point Characteristic Value
  36. [HRS_IDX_HR_CTNL_PT_VAL] =
  37. {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&heart_rate_ctrl_point, ESP_GATT_PERM_WRITE_ENCRYPTED|ESP_GATT_PERM_READ_ENCRYPTED,
  38. sizeof(uint8_t), sizeof(heart_ctrl_point), (uint8_t *)heart_ctrl_point}},
  39. };

上面的att 表有一個service、三個characteristic。

........

先把建立連接之前的GATT狀態(tài)機搞清楚。

3 建立連接之前的GATT狀態(tài)機

3.1 創(chuàng)建服務(wù) creating services

在觸發(fā)ESP_GATTS_REG_EVT時,除了創(chuàng)建表還可以創(chuàng)建服務(wù)S,調(diào)用esp_ble_gatts_create_service來創(chuàng)建服務(wù)S。

  1. esp_err_t esp_ble_gatts_create_service(esp_gatt_if_t gatts_if,
  2. esp_gatt_srvc_id_t *service_id, uint16_t num_handle);

作用:創(chuàng)建一個service。當一個service創(chuàng)建成功(done)后,ESP_CREATE_SERVICE_EVT事件觸發(fā)回調(diào)函數(shù)被調(diào)用,該回調(diào)函數(shù)報告了profile的stauts和service ID。當要添加include service和 characteristics/descriptors入服務(wù)service,Service ID在回調(diào)函數(shù)中用到。

參數(shù):gatts_if——GATT 服務(wù)器訪問接口

service_id: 服務(wù)UUID相關(guān)信息

num_handle:該服務(wù)所需的句柄數(shù) service handle、characteristic declaration handle、 characteristic value handle、characteristic description handle 的句柄數(shù)總和

查看參數(shù)service_id的類型

  1. /// UUID type
  2. typedef struct {
  3. #define ESP_UUID_LEN_16 2
  4. #define ESP_UUID_LEN_32 4
  5. #define ESP_UUID_LEN_128 16
  6. uint16_t len; /*!< UUID length, 16bit, 32bit or 128bit */
  7. union {
  8. uint16_t uuid16;
  9. uint32_t uuid32;
  10. uint8_t uuid128[ESP_UUID_LEN_128];
  11. } uuid; /*!< UUID */
  12. } __attribute__((packed)) esp_bt_uuid_t;
  13. /**
  14. * @brief Gatt id, include uuid and instance id
  15. */
  16. typedef struct {
  17. esp_bt_uuid_t uuid; /*!< UUID */
  18. uint8_t inst_id; /*!< Instance id */
  19. } __attribute__((packed)) esp_gatt_id_t;
  20. /**
  21. * @brief Gatt service id, include id
  22. * (uuid and instance id) and primary flag
  23. */
  24. typedef struct {
  25. esp_gatt_id_t id; /*!< Gatt id, include uuid and instance */
  26. bool is_primary; /*!< This service is primary or not */
  27. } __attribute__((packed)) esp_gatt_srvc_id_t;

服務(wù)ID包括

is_primary參數(shù)當前服務(wù)是否是首要的;---------服務(wù)聲明中的Attribute Type 0x2800---Primary/0x2801---Secondary

id參數(shù)UUID的信息(包括uuid 和實例instance) ---------uuid是UUID的信息包括UUID的長度(16bit/32bit/128bit)及UUID具體值。

例如:服務(wù)被定義為16bit的UUID的主服務(wù)。服務(wù)ID以實例ID為0,UUID為0x00FF來初始化。

服務(wù)實例ID是用來區(qū)分在同一個Profile中具有相同UUID的多個服務(wù)。Application Profile中擁有相同UUID的兩個服務(wù),需要用不同的實例ID來引用。

如下示例展示了一個Service的創(chuàng)建。

  1. switch (event) {
  2. case ESP_GATTS_REG_EVT:
  3. ESP_LOGI(GATTS_TAG, "REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id);
  4. gl_profile_tab[PROFILE_B_APP_ID].service_id.is_primary = true;
  5. gl_profile_tab[PROFILE_B_APP_ID].service_id.id.inst_id = 0x00;
  6. gl_profile_tab[PROFILE_B_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16;
  7. gl_profile_tab[PROFILE_B_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_B;
  8. esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_B_APP_ID].service_id, GATTS_NUM_HANDLE_TEST_B);
  9. break;
  10. }

3.2 啟動服務(wù)并創(chuàng)建Characteristics

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

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

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

ESP_GATTS_CREATE_EVT事件中回調(diào)函數(shù)參數(shù)的類型為gatts_create_evt_param(包括操作函數(shù)、servic的句柄、服務(wù)的id<UUID+其他信息>) 如下所示。

  1. /**
  2. * @brief ESP_GATTS_CREATE_EVT
  3. */
  4. struct gatts_create_evt_param {
  5. esp_gatt_status_t status; /*!< Operation status */
  6. uint16_t service_handle; /*!< Service attribute handle */
  7. esp_gatt_srvc_id_t service_id; /*!< Service id, include service uuid and other information */
  8. } create; /*!< Gatt server callback param of ESP_GATTS_CREATE_EVT */
  9. esp_err_t esp_ble_gatts_start_service(uint16_t service_handle)

 esp_ble_gatts_start_service()這個函數(shù)啟動一個服務(wù)。

參數(shù): service_handle-------要被啟動的服務(wù)句柄。

首先,由BLE堆棧生成生成的服務(wù)句柄(service handle)存儲在Profile表中,應(yīng)用層將用服務(wù)句柄來引用這個服務(wù)。調(diào)用esp_ble_gatts_start_service()和先前產(chǎn)生服務(wù)句柄來啟動服務(wù)。

這樣ESP_ATTS_START_EVT事件觸發(fā)時,將打印輸出信息啟動的Service Handle之類的信息

添加特征到service中,調(diào)用esp_ble_gatts_add_char()來添加characteristics連同characteristic 權(quán)限和property到服務(wù)service中。

有必要再次解釋一下property

Characteristic Properties這個域(bit控制)決定了Characteristic Value如何使用、Characteristic descriptors 如何訪問。只要下標中對應(yīng)bit被設(shè)備,那么對應(yīng)描述的action就被允許。

3.2.1 添加Characteristic Value declaration ATT-------看這里

添加Characteristic的接口

  1. esp_err_t esp_ble_gatts_add_char(uint16_t service_handle, esp_bt_uuid_t *char_uuid,
  2. esp_gatt_perm_t perm, esp_gatt_char_prop_t property, esp_attr_value_t *char_val, esp_attr_control_t *control)

參數(shù):service_handle-------Characteristic要添加到的服務(wù)的Service handler服務(wù)句柄,一個Characteristic至少包括2個屬性ATT,一個屬性用于characteristic declaration/另一個用于存放特征值(characteristic value declaration).

char_uuid-------Characteristic 的UUID;  屬于Characteristic declaration 這個ATT

perm------特征值聲明(Characteristic value declaration) 屬性(Attribute)訪問權(quán)限;

ATT具有一組與其關(guān)聯(lián)的權(quán)限值,權(quán)限值指定了讀/寫權(quán)限、認證權(quán)限、授權(quán)許可

                permission權(quán)限的可取值{可讀、可加密讀、可加密MITM讀、可寫、可加密寫、可加密MITM寫}

  1. /**
  2. * @brief Attribute permissions
  3. */
  4. #define ESP_GATT_PERM_READ (1 << 0) /* bit 0 - 0x0001 */ /* relate to BTA_GATT_PERM_READ in bta/bta_gatt_api.h */
  5. #define ESP_GATT_PERM_READ_ENCRYPTED (1 << 1) /* bit 1 - 0x0002 */ /* relate to BTA_GATT_PERM_READ_ENCRYPTED in bta/bta_gatt_api.h */
  6. #define ESP_GATT_PERM_READ_ENC_MITM (1 << 2) /* bit 2 - 0x0004 */ /* relate to BTA_GATT_PERM_READ_ENC_MITM in bta/bta_gatt_api.h */
  7. #define ESP_GATT_PERM_WRITE (1 << 4) /* bit 4 - 0x0010 */ /* relate to BTA_GATT_PERM_WRITE in bta/bta_gatt_api.h */
  8. #define ESP_GATT_PERM_WRITE_ENCRYPTED (1 << 5) /* bit 5 - 0x0020 */ /* relate to BTA_GATT_PERM_WRITE_ENCRYPTED in bta/bta_gatt_api.h */
  9. #define ESP_GATT_PERM_WRITE_ENC_MITM (1 << 6) /* bit 6 - 0x0040 */ /* relate to BTA_GATT_PERM_WRITE_ENC_MITM in bta/bta_gatt_api.h */
  10. #define ESP_GATT_PERM_WRITE_SIGNED (1 << 7) /* bit 7 - 0x0080 */ /* relate to BTA_GATT_PERM_WRITE_SIGNED in bta/bta_gatt_api.h */
  11. #define ESP_GATT_PERM_WRITE_SIGNED_MITM (1 << 8) /* bit 8 - 0x0100 */ /* relate to BTA_GATT_PERM_WRITE_SIGNED_MITM in bta/bta_gatt_api.h */

property-----Characteristic Properties (特征值聲明屬性的Properties)

char_val------屬性值 Characteristic Value

control-------屬性響應(yīng)控制字節(jié)

characteristic declaration的Attribute Value  包括 property、characteristic Value declaration handle、char_uuid 三個段;其中property、char_uuid在添加Characteristic調(diào)用的函數(shù)的參數(shù)中已經(jīng)指明,只有characteristic Value declaration handle尚未明確指出。

示例:

  1. gl_profile_tab[PROFILE_A_APP_ID].service_handle = param->create.service_handle;
  2. gl_profile_tab[PROFILE_A_APP_ID].char_uuid.len = ESP_UUID_LEN_16;
  3. gl_profile_tab[PROFILE_A_APP_ID].char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_TEST_A;
  4. esp_ble_gatts_add_char(gl_profile_tab[PROFILE_A_APP_ID].service_handle,
  5. &gl_profile_tab[PROFILE_A_APP_ID].char_uuid,
  6. ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
  7. a_property,
  8. &gatts_demo_char1_val,
  9. NULL);

 characteristic value------這個屬性ATT怎么添加入Characteristic中,看gatts_demo_char1_val的具體部分

  1. /**
  2. * @brief set the attribute value type
  3. */
  4. typedef struct
  5. {
  6. uint16_t attr_max_len; /*!< attribute max value length */
  7. uint16_t attr_len; /*!< attribute current value length */
  8. uint8_t *attr_value; /*!< the pointer to attribute value */
  9. } esp_attr_value_t;
  10. uint8_t char1_str[] = {0x11,0x22,0x33};
  11. esp_attr_value_t gatts_demo_char1_val =
  12. {
  13. .attr_max_len = GATTS_DEMO_CHAR_VAL_LEN_MAX,
  14. .attr_len = sizeof(char1_str),
  15. .attr_value = char1_str,
  16. };

 在這個結(jié)構(gòu)體里面包括了屬性值最大長度、當前長度、當前值。由這個來指定 characteristic value declaration的。有這些值就足以構(gòu)成一個 characteristic  value declaration ATT了。

3.3 添加Characteristic descriptor 描述符

當特征添加到service中成功(done)時,觸發(fā)ESP_GATTS_ADD_CHAR_EVT事件。觸發(fā)ESP_GATTS_ADD_CHAR_EVT事件時,回調(diào)函數(shù)參數(shù)param的結(jié)構(gòu)體為gatts_add_char_evt_param,包括操作狀態(tài)、特征ATT的handle()、service_handle(服務(wù)句柄)、characteristc uuid(服務(wù)的UUID)

  1. /**
  2. * @brief ESP_GATTS_ADD_CHAR_EVT
  3. */
  4. struct gatts_add_char_evt_param {
  5. esp_gatt_status_t status; /*!< Operation status */
  6. uint16_t attr_handle; /*!< Characteristic attribute handle */
  7. uint16_t service_handle; /*!< Service attribute handle */
  8. esp_bt_uuid_t char_uuid; /*!< Characteristic uuid */
  9. } add_char; /*!< Gatt server callback param of ESP_GATTS_ADD_CHAR_EVT */

還可以通過調(diào)用esp_ble_gatts_get_attr_value()來獲取跟具體的Characteristic Value declartation 屬性的具體信息。

下面是調(diào)用的例子,輸入?yún)?shù)是特征句柄;輸出參數(shù)是length和prf_char

esp_ble_gatts_get_attr_value(param->add_char.attr_handle,  &length, &prf_char);

查看esp_ble_gatts_get_attr_value() 源碼,進行分析 

  1. esp_err_t esp_ble_gatts_get_attr_value(uint16_t attr_handle, uint16_t *length, const uint8_t **value)
  2. {
  3. if (attr_handle == ESP_GATT_ILLEGAL_HANDLE) {
  4. return ESP_FAIL;
  5. }
  6. btc_gatts_get_attr_value(attr_handle, length, (uint8_t **)value);
  7. return ESP_OK;
  8. }
  9. void btc_gatts_get_attr_value(uint16_t attr_handle, uint16_t *length, uint8_t **value)
  10. {
  11. BTA_GetAttributeValue(attr_handle, length, value);
  12. }
  13. void BTA_GetAttributeValue(UINT16 attr_handle, UINT16 *length, UINT8 **value)
  14. {
  15. bta_gatts_get_attr_value(attr_handle, length, value);
  16. }
  17. void bta_gatts_get_attr_value(UINT16 attr_handle, UINT16 *length, UINT8 **value)
  18. {
  19. GATTS_GetAttributeValue(attr_handle, length, value);
  20. }
  21. /*******************************************************************************
  22. **
  23. ** Function GATTS_GetAttributeValue
  24. **
  25. ** Description This function sends to set the attribute value .
  26. **
  27. ** Parameter attr_handle: the attribute handle
  28. ** length:the attribute value length in the database
  29. ** value: the attribute value out put
  30. **
  31. ** Returns GATT_SUCCESS if successfully sent; otherwise error code.
  32. **
  33. *******************************************************************************/
  34. tGATT_STATUS GATTS_GetAttributeValue(UINT16 attr_handle, UINT16 *length, UINT8 **value)
  35. {
  36. tGATT_STATUS status;
  37. tGATT_HDL_LIST_ELEM *p_decl;
  38. GATT_TRACE_DEBUG("GATTS_GetAttributeValue: attr_handle: %u\n",
  39. attr_handle);
  40. if ((p_decl = gatt_find_hdl_buffer_by_attr_handle(attr_handle)) == NULL) {
  41. GATT_TRACE_ERROR("Service not created\n");
  42. return GATT_INVALID_HANDLE;
  43. }
  44. status = gatts_get_attribute_value(&p_decl->svc_db, attr_handle, length, value);
  45. return status;
  46. }

關(guān)于gatts_get_attribute_value()更底層的東西就要看ble協(xié)議棧的更底層的實現(xiàn),這里很難查下去。但是可以通過打印輸出來判斷這里實現(xiàn)些什么。

有上述打印可以看出,這里輸出Characteristic value declaration的信息,因此esp_ble_gatts_get_attr_value()來輸出特征值長度和特征值。

還可在ESP_GATTS_ADD_CHAR_EVT事件的回調(diào)函數(shù)中,給characteristic添加characteristic description ATT。

下面是添加char_descr的例子

  1. gl_profile_tab[PROFILE_A_APP_ID].char_handle = param->add_char.attr_handle;
  2. gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.len = ESP_UUID_LEN_16;
  3. gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
  4. esp_err_t add_descr_ret = esp_ble_gatts_add_char_descr(
  5. gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].descr_uuid,
  6. ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, NULL, NULL);
  7. case ESP_GATTS_ADD_CHAR_DESCR_EVT:
  8. gl_profile_tab[PROFILE_A_APP_ID].descr_handle = param->add_char_descr.attr_handle;
  9. ESP_LOGI(GATTS_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n",
  10. param->add_char_descr.status, param->add_char_descr.attr_handle, param->add_char_descr.service_handle);
  11. break;

 看看esp_ble_gatts_add_char_descr()這個函數(shù)的原型

  1. esp_err_t esp_ble_gatts_add_char_descr (uint16_t service_handle,
  2. esp_bt_uuid_t *descr_uuid,
  3. esp_gatt_perm_t perm, esp_attr_value_t *char_descr_val,
  4. esp_attr_control_t *control);

參數(shù):service_handle:這個characteristic descriptor要添加的service handle。

perm: 描述符訪問權(quán)限

descr_uuid:描述符UUID

char_descr_val:描述符值

control:ATT 應(yīng)答控制字節(jié)

這個函數(shù)被用來添加Characteristic descriptor。當添加完成時,BTA_GATTS_ADD_DESCR_EVT 回調(diào)函數(shù)被調(diào)用去報告它的狀態(tài)和ID。

 

gatt_server例子中:一共建立兩個profile。A profile中包括的service UUID為0x00FF, characteristic UUID為0xFF01;

B profile中包括的service UUID為0x00EE, characteristic UUID為0xEE01

下面看是看開始建立連接后,GATT的狀態(tài)機轉(zhuǎn)變過程:

手機client連接到server,觸發(fā)ESP_GATTS_CONNECT_EVT事件。

  1. /**
  2. * @brief ESP_GATTS_CONNECT_EVT
  3. */
  4. struct gatts_connect_evt_param {
  5. uint16_t conn_id; /*!< Connection id */
  6. esp_bd_addr_t remote_bda; /*!< Remote bluetooth device address */
  7. } connect; /*!< Gatt server callback param of ESP_GATTS_CONNECT_EVT */
  8. /// Bluetooth address length
  9. #define ESP_BD_ADDR_LEN 6
  10. /// Bluetooth device address
  11. typedef uint8_t esp_bd_addr_t[ESP_BD_ADDR_LEN];

ESP_GATTS_CONNECT_EVT事件回調(diào)函數(shù)param參數(shù)包括連接ID以及遠端藍牙設(shè)備地址。調(diào)用esp_ble_update_conn_params(&conn_params)來更新連接參數(shù)。這個函數(shù)只有在連接上以后才可以調(diào)用。

  1. esp_ble_conn_update_params_t conn_params = {0};
  2. memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
  3. /* For the IOS system, please reference the apple official documents about the ble connection parameters restrictions. */
  4. conn_params.latency = 0;
  5. conn_params.max_int = 0x20; // max_int = 0x20*1.25ms = 40ms
  6. conn_params.min_int = 0x10; // min_int = 0x10*1.25ms = 20ms
  7. conn_params.timeout = 400; // timeout = 400*10ms = 4000ms
  8. esp_ble_gap_update_conn_params(&conn_params);

 參數(shù)連接更新參數(shù)(藍牙設(shè)備地址、最小連接間隔、最大連接間隔、連接數(shù)量、LE LINK超時)。

  1. /// Connection update parameters
  2. typedef struct {
  3. esp_bd_addr_t bda; /*!< Bluetooth device address */
  4. uint16_t min_int; /*!< Min connection interval */
  5. uint16_t max_int; /*!< Max connection interval */
  6. uint16_t latency; /*!< Slave latency for the connection in number of connection events. Range: 0x0000 to 0x01F3 */
  7. uint16_t timeout; /*!< Supervision timeout for the LE Link. Range: 0x000A to 0x0C80.
  8. Mandatory Range: 0x000A to 0x0C80 Time = N * 10 msec
  9. Time Range: 100 msec to 32 seconds */
  10. } esp_ble_conn_update_params_t;

4 建立連接時GATT的狀態(tài)機研究

 設(shè)備設(shè)備連上外圍藍牙時,打印如下所示:第一個觸發(fā)了CONNECT回調(diào)函數(shù);接著觸發(fā)了MTU尺寸的大小確認事件。

4.1連接后觸發(fā)的連接回調(diào)函數(shù)

  1. /**
  2. * @brief ESP_GATTS_CONNECT_EVT
  3. */
  4. struct gatts_connect_evt_param {
  5. uint16_t conn_id; /*!< Connection id */
  6. esp_bd_addr_t remote_bda; /*!< Remote bluetooth device address */
  7. } connect;

上面是連接回調(diào)函數(shù)param的參數(shù)結(jié)構(gòu)體,包括連接id和遠端(對端)藍牙設(shè)備地址(bda)。

在一般回調(diào)函數(shù)處理中,記錄對端的信息,且發(fā)送更新后的連接參數(shù)到對端設(shè)備 。

  1. //start sent the update connection parameters to the peer device.
  2. esp_ble_gap_update_conn_params(&conn_params);

深入到 esp_ble_gap_update_conn_params(&conn_params)。這個函數(shù)只能在連接上時使用,用于更新連接參數(shù)。

  1. /**
  2. * @brief Update connection parameters, can only be used when connection is up.
  3. *
  4. * @param[in] params - connection update parameters
  5. *
  6. * @return
  7. * - ESP_OK : success
  8. * - other : failed
  9. *
  10. */
  11. /// Connection update parameters
  12. typedef struct {
  13. esp_bd_addr_t bda; /*!< Bluetooth device address */
  14. uint16_t min_int; /*!< Min connection interval */
  15. uint16_t max_int; /*!< Max connection interval */
  16. uint16_t latency; /*!< Slave latency for the connection in number of connection events. Range: 0x0000 to 0x01F3 */
  17. uint16_t timeout; /*!< Supervision timeout for the LE Link. Range: 0x000A to 0x0C80.
  18. Mandatory Range: 0x000A to 0x0C80 Time = N * 10 msec
  19. Time Range: 100 msec to 32 seconds */
  20. } esp_ble_conn_update_params_t;
  21. esp_err_t esp_ble_gap_update_conn_params(esp_ble_conn_update_params_t *params)
  22. {
  23. btc_msg_t msg;
  24. btc_ble_gap_args_t arg;
  25. ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
  26. msg.sig = BTC_SIG_API_CALL;
  27. msg.pid = BTC_PID_GAP_BLE;
  28. msg.act = BTC_GAP_BLE_ACT_UPDATE_CONN_PARAM;
  29. memcpy(&arg.conn_update_params.conn_params, params, sizeof(esp_ble_conn_update_params_t));
  30. return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gap_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL);
  31. }

繼續(xù)深入bt_status_t btc_transfer_context(btc_msg_t *msg, void *arg, int arg_len, btc_arg_deep_copy_t copy_func) 

  1. bt_status_t btc_transfer_context(btc_msg_t *msg, void *arg, int arg_len, btc_arg_deep_copy_t copy_func)
  2. {
  3. btc_msg_t lmsg;
  4. if (msg == NULL) {
  5. return BT_STATUS_PARM_INVALID;
  6. }
  7. BTC_TRACE_DEBUG("%s msg %u %u %u %p\n", __func__, msg->sig, msg->pid, msg->act, arg);
  8. memcpy(&lmsg, msg, sizeof(btc_msg_t));
  9. if (arg) {
  10. lmsg.arg = (void *)osi_malloc(arg_len);
  11. if (lmsg.arg == NULL) {
  12. return BT_STATUS_NOMEM;
  13. }
  14. memset(lmsg.arg, 0x00, arg_len); //important, avoid arg which have no length
  15. memcpy(lmsg.arg, arg, arg_len);
  16. if (copy_func) {
  17. copy_func(&lmsg, lmsg.arg, arg);
  18. }
  19. } else {
  20. lmsg.arg = NULL;
  21. }
  22. return btc_task_post(&lmsg, TASK_POST_BLOCKING);
  23. }

 到此,不繼續(xù)下去。

4.2連接后MTU大小確定

 當有手機(client客戶端)連上server時,觸發(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>

  1. /**
  2. * @brief ESP_GATTS_MTU_EVT
  3. */
  4. struct gatts_mtu_evt_param {
  5. uint16_t conn_id; /*!< Connection id */
  6. uint16_t mtu; /*!< MTU size */
  7. } 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è)置一個PDU中最大能夠交換的數(shù)據(jù)量)。例如:主設(shè)備發(fā)出一個150字節(jié)的MTU請求,但是從設(shè)備回應(yīng)的MTU是23字節(jié),那么今后雙方要以較小的值23字節(jié)作為以后的MTU。即主從雙方每次在做數(shù)據(jù)傳輸時不超過這個最大數(shù)據(jù)單元。 MTU交換通常發(fā)生在主從雙方建立連接后。MTU比較小,就是為什么BLE不能傳輸大數(shù)據(jù)的原因所在。

-----參照一分鐘讀懂低功耗(BLE)MTU交換數(shù)據(jù)包https://blog.csdn.net/viewtoolsz/article/details/76177465 這篇文章就可以了解MTU交換過程。

MTU交換請求用于client通知server關(guān)于client最大接收MTU大小并請求server響應(yīng)它的最大接收MTU大小。

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

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

這個應(yīng)答由server發(fā)出,server的接收MTU必須大于或等于默認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兩者的較小值。

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

4.3 發(fā)送應(yīng)答數(shù)據(jù)

  1. #if (GATTS_INCLUDED == TRUE)
  2. esp_err_t esp_ble_gatts_send_response(esp_gatt_if_t gatts_if, uint16_t conn_id, uint32_t trans_id,
  3. esp_gatt_status_t status, esp_gatt_rsp_t *rsp);
  4. btc_msg_t msg;
  5. btc_ble_gatts_args_t arg;
  6. ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
  7. msg.sig = BTC_SIG_API_CALL;
  8. msg.pid = BTC_PID_GATTS;
  9. msg.act = BTC_GATTS_ACT_SEND_RESPONSE;
  10. arg.send_rsp.conn_id = BTC_GATT_CREATE_CONN_ID(gatts_if, conn_id);
  11. arg.send_rsp.trans_id = trans_id;
  12. arg.send_rsp.status = status;
  13. arg.send_rsp.rsp = rsp;
  14. return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t),
  15. btc_gatts_arg_deep_copy) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL);
  16. }
  17. #endif

這個函數(shù)用于發(fā)送應(yīng)答給對應(yīng)請求。

參數(shù): gatts_if-------GATT server 訪問接口

conn_id-----連接ID

trans_id-----傳輸ID

status----應(yīng)答狀態(tài)

rsp-----應(yīng)答數(shù)據(jù) gatt attribute value

查看GATT 讀應(yīng)答結(jié)構(gòu)

  1. #define ESP_GATT_MAX_ATTR_LEN 600 //as same as GATT_MAX_ATTR_LEN
  2. /// Gatt attribute value
  3. typedef struct {
  4. uint8_t value[ESP_GATT_MAX_ATTR_LEN]; /*!< Gatt attribute value */
  5. uint16_t handle; /*!< Gatt attribute handle */
  6. uint16_t offset; /*!< Gatt attribute value offset */
  7. uint16_t len; /*!< Gatt attribute value length */
  8. uint8_t auth_req; /*!< Gatt authentication request */
  9. } esp_gatt_value_t;
  10. /// GATT remote read request response type
  11. typedef union {
  12. esp_gatt_value_t attr_value; /*!< Gatt attribute structure */
  13. uint16_t handle; /*!< Gatt attribute handle */
  14. } esp_gatt_rsp_t;

 

4.4 客戶端和服務(wù)器端 收發(fā)數(shù)據(jù)

4.4.1 手機端的操作

接下去看,連接以后收發(fā)數(shù)據(jù)

首先了解一下手機端在藍牙連接、數(shù)據(jù)交互過程中的操作。


作者:WuTa0
鏈接:https://www.jianshu.com/p/f8130a0bfd94

  1. 檢測藍牙是否可用,綁定藍牙服務(wù)
  2. 使用BluetoothAdapter.startLeScan來掃描低功耗藍牙設(shè)備
  3. 在掃描到設(shè)備的回調(diào)函數(shù)中會得到BluetoothDevice對象,并使用BluetoothAdapter.stopLeScan停止掃描
  4. 使用BluetoothDevice.connectGatt來獲取到BluetoothGatt對象 /*****************************************************************************************************************************************/
  5. 執(zhí)行BluetoothGatt.discoverServices,這個方法是異步操作,在回調(diào)函數(shù)onServicesDiscovered中得到status, 通過判斷status是否等于BluetoothGatt.GATT_SUCCESS來判斷查找服務(wù)Service是否成功
  6. 如果成功了,則通過BluetoothGatt.getServices()來獲取所有的Services;根據(jù)Sevice_UUID來查找想要的服務(wù)BluetoothGattService
  7. 接著通過BluetoothGattService.getCharacteristic(spiecial_char_UUID)獲取BluetoothGattCharacteristic,最基本的一般獲取Read_UUID的Charcteristic、Write_UUID的Characteristic、Notification_UUID的Characteristic。
  8. 設(shè)置通知打開------通知打開或關(guān)閉實際山是一次寫入操作
  9. 然后通過BluetoothGattCharacteristic.getDescriptor獲取BluetoothGattDescriptor
  10. 對于發(fā)送和接收數(shù)據(jù) 都是從BluetoothGatt.readCharacteristic和BluetoothGatt.writeCharcteristic來實現(xiàn)。
  11. 注意:在寫時參數(shù)characteristic,調(diào)用setValue設(shè)置Characteristic的屬性值,調(diào)用setWriteType設(shè)置寫的方式(如WRITR_TYPE_NO_RESPONSE)

第3步掃描到設(shè)備會觸發(fā)回調(diào),在回調(diào)中,通常根據(jù)設(shè)備名稱找到想要連接的設(shè)備,完成連接;連接后,獲取到BluetoothGATT 對象,再從BluetoothGatt.discoverServices中獲取各個Services,根據(jù)硬件工程師提供的UUID連接你需要的Service。最后,根據(jù)硬件工程師提供的UUID找到讀、寫和通知的UUID,然后再進行讀寫操作。  讀寫和設(shè)置通知均為單步操作,執(zhí)行完一個才能執(zhí)行下一個。

從這里出發(fā),現(xiàn)在仍感覺不是很痛快,因此我從小程序的低功耗藍牙接口出發(fā),兩相對照看。

寫特征值

  1. wx.writeBLECharacteristicValue(Object object)
  2. Object{
  3. deviceId::string //必填,藍牙設(shè)備id
  4. serviceId::string //必填,藍牙特征值Characteristic對應(yīng)服務(wù)的UUID
  5. characteristicId::string //必填,藍牙特征值的UUID
  6. value::ArrayBuffer //必填,藍牙設(shè)備特征值對應(yīng)的二進制值
  7. success::function //非必填,接口調(diào)用成功后的回調(diào)函數(shù)
  8. fail::function //非必填,接口調(diào)用失敗后的回調(diào)函數(shù)
  9. complete::function //非必填,接口調(diào)用結(jié)束后的回調(diào)函數(shù)(調(diào)用成功、失敗都會執(zhí)行)
  10. }
  11. 向低功耗藍牙設(shè)備特征值中寫入二進制數(shù)據(jù)。 必須設(shè)備的特征值支持write權(quán)限才可以成功調(diào)用。

具體的如下所示,

  1. wx.writeBLECharacteristicValue({
  2. deviceId: that.data.deviceId,//設(shè)備deviceId
  3. serviceId: that.data.service_id,//設(shè)備service_id
  4. characteristicId: that.data.write_id,//設(shè)備write特征值
  5. value: buffer,//寫入數(shù)據(jù)
  6. success: function (res) {
  7. console.log('發(fā)送數(shù)據(jù):', res.errMsg)
  8. }
  9. });
  10. ---------------------
  11. 作者:Cc_JoJo
  12. 來源:CSDN
  13. 原文:https://blog.csdn.net/caohoucheng/article/details/81633822
  14. 版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請附上博文鏈接!

讀特征值

  1. wx.readBLECharacteristicValue(Object object)
  2. Objcet{
  3. deviceId::string //必填,藍牙設(shè)備id
  4. serviceId::string //必填,藍牙特征值對應(yīng)服務(wù)的UUID
  5. characteristic::string //必填,藍牙特征值UUID
  6. success::function //非必填,接口調(diào)用成功后回調(diào)函數(shù)
  7. fail::function //非必填,接口調(diào)用失敗后回調(diào)函數(shù)
  8. complete::function //非必填,接口調(diào)用結(jié)束后回調(diào)函數(shù)(調(diào)用成功、失敗都會執(zhí)行)
  9. }

具體的如下所示

  1. wx.readBLECharacteristicValue({
  2. // 這里的 deviceId 需要已經(jīng)通過 createBLEConnection 與對應(yīng)設(shè)備建立鏈接
  3. deviceId,
  4. // 這里的 serviceId 需要在 getBLEDeviceServices 接口中獲取
  5. serviceId,
  6. // 這里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中獲取
  7. characteristicId,
  8. success(res) {
  9. console.log('readBLECharacteristicValue:', res.errCode)
  10. }
  11. })

小程序上低功耗藍牙一般操作流程

初始化藍牙模塊(wx.openBluetoothAdapter)------>開始搜索藍牙外圍設(shè)備(耗資源)(wx.startBluetoothDevicesDiscovery)----->

獲取在藍牙模塊生效期間所有已發(fā)現(xiàn)的藍牙設(shè)備(包括已經(jīng)和本機處于連接狀態(tài)的設(shè)備)(wx.getBluetoothDevices)----->

監(jiān)聽找到的新設(shè)備的事件(wx.onBluetoothDeviceFound(callback))------->連接低功耗藍牙設(shè)備(wx.createBLEConnection())------>

獲取藍牙設(shè)備所有services(wx.getBLEDeviceServices())------>獲取藍牙設(shè)備某個服務(wù)中所有Characteristic(特征)(wx.getBLEDeviceCharacteristics)-------->啟動低功耗藍牙設(shè)備特征的值變化時的notify功能(wx.notifyBLECharacteristicValueChange())------->寫入wx.writeBLECharactericValue()

目前,微信小程序上也應(yīng)該是無應(yīng)答寫。

 

4.4.2 設(shè)備端的操作

將ESP_GATTS_READ_EVT、ESP_GATTS_WRITE_EVT和ESP_GATTS_EXEC_WRITE_EVT三類事件param參數(shù)的類型如下

  1. /**
  2. * @brief ESP_GATTS_READ_EVT
  3. */
  4. struct gatts_read_evt_param {
  5. uint16_t conn_id; /*!< Connection id */
  6. uint32_t trans_id; /*!< Transfer id */
  7. esp_bd_addr_t bda; /*!< The bluetooth device address which been read */
  8. uint16_t handle; /*!< The attribute handle */
  9. uint16_t offset; /*!< Offset of the value, if the value is too long */
  10. bool is_long; /*!< The value is too long or not */
  11. bool need_rsp; /*!< The read operation need to do response */
  12. } read; /*!< Gatt server callback param of ESP_GATTS_READ_EVT */
  13. /**
  14. * @brief ESP_GATTS_WRITE_EVT
  15. */
  16. struct gatts_write_evt_param {
  17. uint16_t conn_id; /*!< Connection id */
  18. uint32_t trans_id; /*!< Transfer id */
  19. esp_bd_addr_t bda; /*!< The bluetooth device address which been written */
  20. uint16_t handle; /*!< The attribute handle */
  21. uint16_t offset; /*!< Offset of the value, if the value is too long */
  22. bool need_rsp; /*!< The write operation need to do response */
  23. bool is_prep; /*!< This write operation is prepare write */
  24. uint16_t len; /*!< The write attribute value length */
  25. uint8_t *value; /*!< The write attribute value */
  26. } write; /*!< Gatt server callback param of ESP_GATTS_WRITE_EVT */
  27. /**
  28. * @brief ESP_GATTS_EXEC_WRITE_EVT
  29. */
  30. struct gatts_exec_write_evt_param {
  31. uint16_t conn_id; /*!< Connection id */
  32. uint32_t trans_id; /*!< Transfer id */
  33. esp_bd_addr_t bda; /*!< The bluetooth device address which been written */
  34. #define ESP_GATT_PREP_WRITE_CANCEL 0x00 /*!< Prepare write flag to indicate cancel prepare write */
  35. #define ESP_GATT_PREP_WRITE_EXEC 0x01 /*!< Prepare write flag to indicate execute prepare write */
  36. uint8_t exec_write_flag; /*!< Execute write flag */
  37. } exec_write;

4.4.4.1 使能通知

使能notify并讀取藍牙發(fā)過來的數(shù)據(jù),開啟這個后我們就能實時獲取藍牙發(fā)過來的值了。

使能通知(notify enable)的打印如下所示,打開通知實際上的一個WRITE。對應(yīng)于手機端的mBluetoothGatt.setCharacteristicNotification(characteristic,true).

  1. 【2018-12-25 16:01:24:307】_[0;32mI (27345) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 3, handle 43_[0m
  2. _[0;32mI (27345) GATTS_DEMO: GATT_WRITE_EVT, value len 2, value :_[0m
  3. _[0;32mI (27345) GATTS_DEMO: 01 00 _[0m
  4. _[0;32mI (27355) GATTS_DEMO: notify enable_[0m
  5. _[0;33mW (27355) BT_BTC: btc_gatts_arg_deep_copy 11, NULL response_[0m
  6. _[0;32mI (27365) GATTS_DEMO: ESP_GATTS_CONF_EVT, status 0 attr_handle 42_[0m
  7. 【2018-12-25 16:01:24:610】_[0;32mI (27645) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 4, handle 47
  8. _[0m
  9. _[0;32mI (27645) GATTS_DEMO: GATT_WRITE_EVT, value len 2, value :_[0m
  10. _[0;32mI (27645) GATTS_DEMO: 01 00 _[0m
  11. _[0;32mI (27655) GATTS_DEMO: notify enable_[0m
  12. _[0;33mW (27655) BT_BTC: btc_gatts_arg_deep_copy 11, NULL response_[0m
  13. _[0;32mI (27665) GATTS_DEMO: ESP_GATTS_CONF_EVT status 0 attr_handle 46_[0m
  14. _[0;32mI (27725) GATTS_DEMO: GATT_READ_EVT, conn_id 0, trans_id 5, handle 42
  15. _[0m

 如果write.handle和descr_handle相同,且長度==2,確定descr_value描述值,根據(jù)描述值開啟/關(guān)閉 通知notify/indicate。

  1. //the size of notify_data[] need less than MTU size
  2. esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_A_APP_ID].char_handle,
  3. sizeof(notify_data), notify_data, false);

深入看看esp_ble_gatts_send_indicate

  1. /**
  2. * @brief Send indicate or notify to GATT client.
  3. * Set param need_confirm as false will send notification, otherwise indication.
  4. *
  5. * @param[in] gatts_if: GATT server access interface
  6. * @param[in] conn_id - connection id to indicate.
  7. * @param[in] attr_handle - attribute handle to indicate.
  8. * @param[in] value_len - indicate value length.
  9. * @param[in] value: value to indicate.
  10. * @param[in] need_confirm - Whether a confirmation is required.
  11. * false sends a GATT notification, true sends a GATT indication.
  12. *
  13. * @return
  14. * - ESP_OK : success
  15. * - other : failed
  16. *
  17. */
  18. esp_err_t esp_ble_gatts_send_indicate(esp_gatt_if_t gatts_if, uint16_t conn_id, uint16_t attr_handle,
  19. uint16_t value_len, uint8_t *value, bool need_confirm);

該函數(shù)將notify或indicate發(fā)給GATT的客戶端;

need_confirm = false,則發(fā)送的是notification通知;

==true,發(fā)送的是指示indication。

其他參數(shù): 服務(wù)端訪問接口;連接id; 屬性句柄,value_len; 值 

4.4.4.2 讀寫數(shù)據(jù)

看看Write 和Read 觸發(fā)的回調(diào)函數(shù);讀寫打印如下所示:

  1. 【2019-01-02 10:37:09:539】[0;32mI (4697198) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 11, handle 42[0m
  2. [0;32mI (4697198) GATTS_DEMO: GATT_WRITE_EVT, value len 3, value :[0m
  3. [0;32mI (4697208) GATTS_DEMO: 31 32 33 [0m
  4. [0;33mW (4697208) BT_BTC: btc_gatts_arg_deep_copy 11, NULL response[0m
  5. [0;32mI (4697278) GATTS_DEMO: GATT_READ_EVT, conn_id 0, trans_id 12, handle 42
  6. [0m

/*************************************************************************************************************************************/

  1. ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d", param->write.conn_id, param->write.trans_id, param->write.handle);
  2. if (!param->write.is_prep){
  3. ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len);
  4. esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len);
  5. example_write_event_env(gatts_if, &a_prepare_write_env, param);
  6. break;
  7. }

深入example_write_event_env(),其核心代碼如下 esp_ble_gatts_send_response函數(shù)

  1. if (param->write.need_rsp){
  2. .....
  3. esp_err_t esp_ble_gatts_send_response(esp_gatt_if_t gatts_if, uint16_t conn_id, uint32_t trans_id,
  4. esp_gatt_status_t status, esp_gatt_rsp_t *rsp);
  5. }

根據(jù)param->write.need_rsp是否需要應(yīng)答來決定,是否調(diào)用esp_ble_gatts_send_response來應(yīng)答。 

而esp_ble_gatts_send_response()函數(shù)實際調(diào)用了btc_transfer_context,將信息發(fā)送出去。

need_rsp如何確定這個值,我很有疑問,看到這里的看客,知道的請回答我一下。

/***********************************************************************************************************************/

先把Write搞清楚,先看看寫的事件參數(shù)

  1. /**
  2. * @brief ESP_GATTS_WRITE_EVT
  3. */
  4. struct gatts_write_evt_param {
  5. uint16_t conn_id; /*!< Connection id */
  6. uint32_t trans_id; /*!< Transfer id */
  7. esp_bd_addr_t bda; /*!< The bluetooth device address which been written */
  8. uint16_t handle; /*!< The attribute handle */
  9. uint16_t offset; /*!< Offset of the value, if the value is too long */
  10. bool need_rsp; /*!< The write operation need to do response */
  11. bool is_prep; /*!< This write operation is prepare write */
  12. uint16_t len; /*!< The write attribute value length */
  13. uint8_t *value; /*!< The write attribute value */
  14. } write;

 這里的is_prep 是針對單獨一個Write Request Attribute Protocol 信息放不下的情況,即Write Long Characteristic;一般先prepare在excute; 由這個參數(shù)確定參數(shù)的是長包還是短包。

offset 是這包數(shù)據(jù)相對長數(shù)據(jù)的偏移,第一包是0x0000;

need_rsp是針對該寫操作是否需要應(yīng)答response。

value就是待寫入的Characteristic Value的值。

目前,我弄的是Write Without Response。

/*****************************************************************************************************************************************/

接下去看看Read,看看讀的事件參數(shù)

  1. /**
  2. * @brief ESP_GATTS_READ_EVT
  3. */
  4. struct gatts_read_evt_param {
  5. uint16_t conn_id; /*!< Connection id */
  6. uint32_t trans_id; /*!< Transfer id */
  7. esp_bd_addr_t bda; /*!< The bluetooth device address which been read */
  8. uint16_t handle; /*!< The attribute handle */
  9. uint16_t offset; /*!< Offset of the value, if the value is too long */
  10. bool is_long; /*!< The value is too long or not */
  11. bool need_rsp; /*!< The read operation need to do response */
  12. } read; /*!< Gatt server callback param of ESP_GATTS_READ_EVT */

 is_long針對客戶端知道特征值句柄和特征值長度大于單獨一個Read Response ATT 信息大小時,表示傳輸?shù)氖情L數(shù)據(jù),就用Read Blob Request。

handle 就是要讀的Characteristic Value的句柄;

offset 就是要讀的Characteristic Value偏移位置,第一包Read Blob Request時,offset為0x00;

對應(yīng)的是Read Blob Response ATT 以一部分的Characteristic Value作為ATT Value 參數(shù)。

在看看ESP_IDF中關(guān)于read Response 的結(jié)構(gòu)體。

  1. /// Gatt attribute value
  2. typedef struct {
  3. uint8_t value[ESP_GATT_MAX_ATTR_LEN]; /*!< Gatt attribute value */
  4. uint16_t handle; /*!< Gatt attribute handle */
  5. uint16_t offset; /*!< Gatt attribute value offset */
  6. uint16_t len; /*!< Gatt attribute value length */
  7. uint8_t auth_req; /*!< Gatt authentication request */
  8. } esp_gatt_value_t;
  9. /// GATT remote read request response type
  10. typedef union {
  11. esp_gatt_value_t attr_value; /*!< Gatt attribute structure */
  12. uint16_t handle; /*!< Gatt attribute handle */
  13. } esp_gatt_rsp_t;

在我的項目中,客戶端先寫再讀。 

4.5 Characteristic結(jié)構(gòu)

再次對Characteristic的結(jié)構(gòu)分析一遍,記不住啊。

Characteristic 聲明、Characteistic Value 聲明、Characteristic Descriptor 聲明。

5. 數(shù)據(jù)交互其他必要知道的

 權(quán)限對于數(shù)據(jù)交換,通常設(shè)置有讀Characteristic寫Characteristic-----------Write_SSID/Read_SSID.

Read/Write讀/寫-----一次會同時出發(fā)讀和寫兩個回調(diào)事件;如上打印結(jié)果所述。

Read Only只讀-------只能觸發(fā)讀回調(diào)事件;

Write Only只寫------只能觸發(fā)寫回調(diào)事件;

6.結(jié)束語

關(guān)于GATT就先告一個段落,其他還有很多不足一處,需要一一修正補充。如果上述內(nèi)容有其他問題,請指正,及時糾正我。在此謝謝大家。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多