目錄
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。
struct gatts_profile_inst {
esp_gatt_srvc_id_t service_id;
esp_gatt_char_prop_t property;
esp_bt_uuid_t descr_uuid;
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還沒有連接任何客戶端。
/* 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] = {
.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 */
.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ù)組??梢杂肁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ù)類型進行分析
typedef void (* esp_gatts_cb_t)(esp_gatts_cb_event_t event,
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
ESP_GATTS_REG_EVT = 0, /*!< When register application id, the event comes */
ESP_GATTS_READ_EVT = 1, /*!< When gatt client request read operation, the event comes */
ESP_GATTS_WRITE_EVT = 2, /*!< When gatt client request write operation, the event comes */
ESP_GATTS_EXEC_WRITE_EVT = 3, /*!< When gatt client request execute write, the event comes */
ESP_GATTS_MTU_EVT = 4, /*!< When set mtu complete, the event comes */
ESP_GATTS_CONF_EVT = 5, /*!< When receive confirm, the event comes */
ESP_GATTS_UNREG_EVT = 6, /*!< When unregister application id, the event comes */
ESP_GATTS_CREATE_EVT = 7, /*!< When create service complete, the event comes */
ESP_GATTS_ADD_INCL_SRVC_EVT = 8, /*!< When add included service complete, the event comes */
ESP_GATTS_ADD_CHAR_EVT = 9, /*!< When add characteristic complete, the event comes */
ESP_GATTS_ADD_CHAR_DESCR_EVT = 10, /*!< When add descriptor complete, the event comes */
ESP_GATTS_DELETE_EVT = 11, /*!< When delete service complete, the event comes */
ESP_GATTS_START_EVT = 12, /*!< When start service complete, the event comes */
ESP_GATTS_STOP_EVT = 13, /*!< When stop service complete, the event comes */
ESP_GATTS_CONNECT_EVT = 14, /*!< When gatt client connect, the event comes */
ESP_GATTS_DISCONNECT_EVT = 15, /*!< When gatt client disconnect, the event comes */
ESP_GATTS_OPEN_EVT = 16, /*!< When connect to peer, the event comes */
ESP_GATTS_CANCEL_OPEN_EVT = 17, /*!< When disconnect from peer, the event comes */
ESP_GATTS_CLOSE_EVT = 18, /*!< When gatt server close, the event comes */
ESP_GATTS_LISTEN_EVT = 19, /*!< When gatt listen to be connected the event comes */
ESP_GATTS_CONGEST_EVT = 20, /*!< When congest happen, the event comes */
/* following is extra event */
ESP_GATTS_RESPONSE_EVT = 21, /*!< When gatt send response complete, the event comes */
ESP_GATTS_CREAT_ATTR_TAB_EVT = 22, /*!< When gatt create table complete, the event comes */
ESP_GATTS_SET_ATTR_VAL_EVT = 23, /*!< When gatt set attr value complete, the event comes */
ESP_GATTS_SEND_SERVICE_CHANGE_EVT = 24, /*!< When gatt send service change indication complete, the event comes */
第二步、再來看一個很有意思的聯(lián)合體類型esp_ble_gatts_cb_param_t,不同的事件類型聯(lián)合體的對象也不同。
* @brief ESP_GATTS_REG_EVT
struct gatts_reg_evt_param {
esp_gatt_status_t status; /*!< Operation status */
uint16_t app_id; /*!< Application id which input in register API */
} reg; /*!< Gatt server callback param of ESP_GATTS_REG_EVT */
* @brief ESP_GATTS_SET_ATTR_VAL_EVT
struct gatts_set_attr_val_evt_param{
uint16_t srvc_handle; /*!< The service handle */
uint16_t attr_handle; /*!< The attribute handle */
esp_gatt_status_t status; /*!< Operation status*/
} set_attr_val; /*!< Gatt server callback param of ESP_GATTS_SET_ATTR_VAL_EVT */
* @brief ESP_GATTS_SEND_SERVICE_CHANGE_EVT
struct gatts_send_service_change_evt_param{
esp_gatt_status_t status; /*!< Operation status*/
} service_change; /*!< Gatt server callback param of ESP_GATTS_SEND_SERVICE_CHANGE_EVT */
} 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ù)處理事情。
if (gatts_if == ESP_GATT_IF_NONE||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);
狀態(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()
esp_err_t esp_ble_gatts_create_attr_tab(const esp_gatts_attr_db_t *gatts_attr_db,
作用:創(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
esp_attr_control_t attr_control; /*!< The attribute control type */
esp_attr_desc_t att_desc; /*!< The attribute type */
對于結(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)體類型
* @brief Attribute description (used to create database)
uint16_t uuid_length; /*!< UUID length */
uint8_t *uuid_p; /*!< UUID value */
uint16_t perm; /*!< Attribute permission */
uint16_t max_length; /*!< Maximum length of the element*/
uint16_t length; /*!< Current length of the element*/
uint8_t *value; /*!< Element value array*/
以心率計Profile為例說明:
esp_ble_gatts_create_attr_tab(heart_rate_gatt_db, gatts_if,
HRS_IDX_NB, HEART_RATE_SVC_INST_ID);
/// Full HRS Database Description - Used to add attributes into the database
static const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB] =
// Heart Rate Service Declaration
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
sizeof(uint16_t), sizeof(heart_rate_svc), (uint8_t *)&heart_rate_svc}},
// Heart Rate Measurement Characteristic Declaration
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_notify}},
// Heart Rate Measurement Characteristic Value
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&heart_rate_meas_uuid, ESP_GATT_PERM_READ,
HRPS_HT_MEAS_MAX_LEN,0, NULL}},
// Heart Rate Measurement Characteristic - Client Characteristic Configuration Descriptor
[HRS_IDX_HR_MEAS_NTF_CFG] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
sizeof(uint16_t),sizeof(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}},
// Body Sensor Location Characteristic Declaration
[HRS_IDX_BOBY_SENSOR_LOC_CHAR] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read}},
// Body Sensor Location Characteristic Value
[HRS_IDX_BOBY_SENSOR_LOC_VAL] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&body_sensor_location_uuid, ESP_GATT_PERM_READ_ENCRYPTED,
sizeof(uint8_t), sizeof(body_sensor_loc_val), (uint8_t *)body_sensor_loc_val}},
// Heart Rate Control Point Characteristic Declaration
[HRS_IDX_HR_CTNL_PT_CHAR] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write}},
// Heart Rate Control Point Characteristic Value
[HRS_IDX_HR_CTNL_PT_VAL] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&heart_rate_ctrl_point, ESP_GATT_PERM_WRITE_ENCRYPTED|ESP_GATT_PERM_READ_ENCRYPTED,
sizeof(uint8_t), sizeof(heart_ctrl_point), (uint8_t *)heart_ctrl_point}},
上面的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。
esp_err_t esp_ble_gatts_create_service(esp_gatt_if_t gatts_if,
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的類型
#define ESP_UUID_LEN_16 2
#define ESP_UUID_LEN_32 4
#define ESP_UUID_LEN_128 16
uint16_t len; /*!< UUID length, 16bit, 32bit or 128bit */
uint8_t uuid128[ESP_UUID_LEN_128];
} __attribute__((packed)) esp_bt_uuid_t;
* @brief Gatt id, include uuid and instance id
esp_bt_uuid_t uuid; /*!< UUID */
uint8_t inst_id; /*!< Instance id */
} __attribute__((packed)) esp_gatt_id_t;
* @brief Gatt service id, include id
* (uuid and instance id) and primary flag
esp_gatt_id_t id; /*!< Gatt id, include uuid and instance */
bool is_primary; /*!< This service is primary or not */
} __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)建。
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);
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+其他信息>) 如下所示。
* @brief ESP_GATTS_CREATE_EVT
struct gatts_create_evt_param {
esp_gatt_status_t status; /*!< Operation status */
uint16_t service_handle; /*!< Service attribute handle */
esp_gatt_srvc_id_t service_id; /*!< Service id, include service uuid and other information */
} create; /*!< Gatt server callback param of ESP_GATTS_CREATE_EVT */
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的接口
esp_err_t esp_ble_gatts_add_char(uint16_t service_handle, esp_bt_uuid_t *char_uuid,
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寫}
* @brief Attribute permissions
#define ESP_GATT_PERM_READ (1 << 0) /* bit 0 - 0x0001 */ /* relate to BTA_GATT_PERM_READ in bta/bta_gatt_api.h */
#define ESP_GATT_PERM_READ_ENCRYPTED (1 << 1) /* bit 1 - 0x0002 */ /* relate to BTA_GATT_PERM_READ_ENCRYPTED in bta/bta_gatt_api.h */
#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 */
#define ESP_GATT_PERM_WRITE (1 << 4) /* bit 4 - 0x0010 */ /* relate to BTA_GATT_PERM_WRITE in bta/bta_gatt_api.h */
#define ESP_GATT_PERM_WRITE_ENCRYPTED (1 << 5) /* bit 5 - 0x0020 */ /* relate to BTA_GATT_PERM_WRITE_ENCRYPTED in bta/bta_gatt_api.h */
#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 */
#define ESP_GATT_PERM_WRITE_SIGNED (1 << 7) /* bit 7 - 0x0080 */ /* relate to BTA_GATT_PERM_WRITE_SIGNED in bta/bta_gatt_api.h */
#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 尚未明確指出。
示例:
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_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,
characteristic value------這個屬性ATT怎么添加入Characteristic中,看gatts_demo_char1_val的具體部分
* @brief set the attribute value type
uint16_t attr_max_len; /*!< attribute max value length */
uint16_t attr_len; /*!< attribute current value length */
uint8_t *attr_value; /*!< the pointer to attribute value */
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),
在這個結(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) 。
* @brief ESP_GATTS_ADD_CHAR_EVT
struct gatts_add_char_evt_param {
esp_gatt_status_t status; /*!< Operation status */
uint16_t attr_handle; /*!< Characteristic attribute handle */
uint16_t service_handle; /*!< Service attribute handle */
esp_bt_uuid_t char_uuid; /*!< Characteristic uuid */
} 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() 源碼,進行分析
esp_err_t esp_ble_gatts_get_attr_value(uint16_t attr_handle, uint16_t *length, const uint8_t **value)
if (attr_handle == ESP_GATT_ILLEGAL_HANDLE) {
btc_gatts_get_attr_value(attr_handle, length, (uint8_t **)value);
void btc_gatts_get_attr_value(uint16_t attr_handle, uint16_t *length, uint8_t **value)
BTA_GetAttributeValue(attr_handle, length, value);
void BTA_GetAttributeValue(UINT16 attr_handle, UINT16 *length, UINT8 **value)
bta_gatts_get_attr_value(attr_handle, length, value);
void bta_gatts_get_attr_value(UINT16 attr_handle, UINT16 *length, UINT8 **value)
GATTS_GetAttributeValue(attr_handle, length, value);
/*******************************************************************************
** Function GATTS_GetAttributeValue
** Description This function sends to set the attribute value .
** Parameter attr_handle: the attribute handle
** length:the attribute value length in the database
** value: the attribute value out put
** Returns GATT_SUCCESS if successfully sent; otherwise error code.
*******************************************************************************/
tGATT_STATUS GATTS_GetAttributeValue(UINT16 attr_handle, UINT16 *length, UINT8 **value)
tGATT_HDL_LIST_ELEM *p_decl;
GATT_TRACE_DEBUG("GATTS_GetAttributeValue: attr_handle: %u\n",
if ((p_decl = gatt_find_hdl_buffer_by_attr_handle(attr_handle)) == NULL) {
GATT_TRACE_ERROR("Service not created\n");
return GATT_INVALID_HANDLE;
status = gatts_get_attribute_value(&p_decl->svc_db, attr_handle, length, value);
關(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的例子
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 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);
case ESP_GATTS_ADD_CHAR_DESCR_EVT:
gl_profile_tab[PROFILE_A_APP_ID].descr_handle = param->add_char_descr.attr_handle;
ESP_LOGI(GATTS_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n",
param->add_char_descr.status, param->add_char_descr.attr_handle, param->add_char_descr.service_handle);
看看esp_ble_gatts_add_char_descr()這個函數(shù)的原型
esp_err_t esp_ble_gatts_add_char_descr (uint16_t service_handle,
esp_bt_uuid_t *descr_uuid,
esp_gatt_perm_t perm, esp_attr_value_t *char_descr_val,
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事件 。
* @brief ESP_GATTS_CONNECT_EVT
struct gatts_connect_evt_param {
uint16_t conn_id; /*!< Connection id */
esp_bd_addr_t remote_bda; /*!< Remote bluetooth device address */
} connect; /*!< Gatt server callback param of ESP_GATTS_CONNECT_EVT */
/// Bluetooth address length
#define ESP_BD_ADDR_LEN 6
/// Bluetooth device address
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)用。
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.max_int = 0x20; // max_int = 0x20*1.25ms = 40ms
conn_params.min_int = 0x10; // min_int = 0x10*1.25ms = 20ms
conn_params.timeout = 400; // timeout = 400*10ms = 4000ms
esp_ble_gap_update_conn_params(&conn_params);
參數(shù)連接更新參數(shù)(藍牙設(shè)備地址、最小連接間隔、最大連接間隔、連接數(shù)量、LE LINK超時)。
/// Connection update parameters
esp_bd_addr_t bda; /*!< Bluetooth device address */
uint16_t min_int; /*!< Min connection interval */
uint16_t max_int; /*!< Max connection interval */
uint16_t latency; /*!< Slave latency for the connection in number of connection events. Range: 0x0000 to 0x01F3 */
uint16_t timeout; /*!< Supervision timeout for the LE Link. Range: 0x000A to 0x0C80.
Mandatory Range: 0x000A to 0x0C80 Time = N * 10 msec
Time Range: 100 msec to 32 seconds */
} 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ù)
* @brief ESP_GATTS_CONNECT_EVT
struct gatts_connect_evt_param {
uint16_t conn_id; /*!< Connection id */
esp_bd_addr_t remote_bda; /*!< Remote bluetooth device address */
上面是連接回調(diào)函數(shù)param的參數(shù)結(jié)構(gòu)體,包括連接id和遠端(對端)藍牙設(shè)備地址(bda)。
在一般回調(diào)函數(shù)處理中,記錄對端的信息,且發(fā)送更新后的連接參數(shù)到對端設(shè)備 。
//start sent the update connection parameters to the peer device.
esp_ble_gap_update_conn_params(&conn_params);
深入到 esp_ble_gap_update_conn_params(&conn_params)。這個函數(shù)只能在連接上時使用,用于更新連接參數(shù)。
* @brief Update connection parameters, can only be used when connection is up.
* @param[in] params - connection update parameters
/// Connection update parameters
esp_bd_addr_t bda; /*!< Bluetooth device address */
uint16_t min_int; /*!< Min connection interval */
uint16_t max_int; /*!< Max connection interval */
uint16_t latency; /*!< Slave latency for the connection in number of connection events. Range: 0x0000 to 0x01F3 */
uint16_t timeout; /*!< Supervision timeout for the LE Link. Range: 0x000A to 0x0C80.
Mandatory Range: 0x000A to 0x0C80 Time = N * 10 msec
Time Range: 100 msec to 32 seconds */
} esp_ble_conn_update_params_t;
esp_err_t esp_ble_gap_update_conn_params(esp_ble_conn_update_params_t *params)
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
msg.sig = BTC_SIG_API_CALL;
msg.pid = BTC_PID_GAP_BLE;
msg.act = BTC_GAP_BLE_ACT_UPDATE_CONN_PARAM;
memcpy(&arg.conn_update_params.conn_params, params, sizeof(esp_ble_conn_update_params_t));
return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gap_args_t), NULL) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL);
繼續(xù)深入bt_status_t btc_transfer_context(btc_msg_t *msg, void *arg, int arg_len, btc_arg_deep_copy_t copy_func)
bt_status_t btc_transfer_context(btc_msg_t *msg, void *arg, int arg_len, btc_arg_deep_copy_t copy_func)
return BT_STATUS_PARM_INVALID;
BTC_TRACE_DEBUG("%s msg %u %u %u %p\n", __func__, msg->sig, msg->pid, msg->act, arg);
memcpy(&lmsg, msg, sizeof(btc_msg_t));
lmsg.arg = (void *)osi_malloc(arg_len);
memset(lmsg.arg, 0x00, arg_len); //important, avoid arg which have no length
memcpy(lmsg.arg, arg, arg_len);
copy_func(&lmsg, lmsg.arg, arg);
return btc_task_post(&lmsg, TASK_POST_BLOCKING);
到此,不繼續(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>
* @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è)置一個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ù)
#if (GATTS_INCLUDED == TRUE)
esp_err_t esp_ble_gatts_send_response(esp_gatt_if_t gatts_if, uint16_t conn_id, uint32_t trans_id,
esp_gatt_status_t status, esp_gatt_rsp_t *rsp);
btc_ble_gatts_args_t arg;
ESP_BLUEDROID_STATUS_CHECK(ESP_BLUEDROID_STATUS_ENABLED);
msg.sig = BTC_SIG_API_CALL;
msg.act = BTC_GATTS_ACT_SEND_RESPONSE;
arg.send_rsp.conn_id = BTC_GATT_CREATE_CONN_ID(gatts_if, conn_id);
arg.send_rsp.trans_id = trans_id;
arg.send_rsp.status = status;
return (btc_transfer_context(&msg, &arg, sizeof(btc_ble_gatts_args_t),
btc_gatts_arg_deep_copy) == BT_STATUS_SUCCESS ? ESP_OK : ESP_FAIL);
這個函數(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)
#define ESP_GATT_MAX_ATTR_LEN 600 //as same as GATT_MAX_ATTR_LEN
uint8_t value[ESP_GATT_MAX_ATTR_LEN]; /*!< Gatt attribute value */
uint16_t handle; /*!< Gatt attribute handle */
uint16_t offset; /*!< Gatt attribute value offset */
uint16_t len; /*!< Gatt attribute value length */
uint8_t auth_req; /*!< Gatt authentication request */
/// GATT remote read request response type
esp_gatt_value_t attr_value; /*!< Gatt attribute structure */
uint16_t handle; /*!< Gatt attribute handle */
4.4 客戶端和服務(wù)器端 收發(fā)數(shù)據(jù)
4.4.1 手機端的操作
接下去看,連接以后收發(fā)數(shù)據(jù)
首先了解一下手機端在藍牙連接、數(shù)據(jù)交互過程中的操作。
作者:WuTa0
鏈接:https://www.jianshu.com/p/f8130a0bfd94
檢測藍牙是否可用,綁定藍牙服務(wù)
使用BluetoothAdapter.startLeScan來掃描 低功耗藍牙設(shè)備
在掃描到設(shè)備的 回調(diào)函數(shù)中會得到BluetoothDevice對象 ,并使用BluetoothAdapter.stopLeScan停止掃描
使用BluetoothDevice.connectGatt來 獲取到BluetoothGatt對象 /*****************************************************************************************************************************************/
執(zhí)行BluetoothGatt.discoverServices,這個方法是異步操作,在回調(diào)函數(shù)onServicesDiscovered中得到status, 通過判斷status是否等于BluetoothGatt.GATT_SUCCESS來判斷 查找服務(wù)Service 是否成功
如果成功了,則通過BluetoothGatt.getServices()來獲取所有的 Services ;根據(jù)Sevice_UUID來查找想要的服務(wù) BluetoothGattService
接著通過BluetoothGattService.getCharacteristic(spiecial_char_UUID)獲取BluetoothGattCharacteristic,最基本的一般獲取 Read_UUID 的Charcteristic、 Write_UUID 的Characteristic、 Notification_UUID 的Characteristic。
設(shè)置通知打開------通知打開或關(guān)閉實際山是一次寫入操作
然后通過BluetoothGattCharacteristic. getDescriptor 獲取 BluetoothGattDescriptor
對于發(fā)送和接收數(shù)據(jù) 都是從BluetoothGatt.readCharacteristic和BluetoothGatt.writeCharcteristic來實現(xiàn)。
注意:在寫時參數(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ā),兩相對照看。
寫特征值
wx.writeBLECharacteristicValue(Object object)
deviceId::string //必填,藍牙設(shè)備id
serviceId::string //必填,藍牙特征值Characteristic對應(yīng)服務(wù)的UUID
characteristicId::string //必填,藍牙特征值的UUID
value::ArrayBuffer //必填,藍牙設(shè)備特征值對應(yīng)的二進制值
success::function //非必填,接口調(diào)用成功后的回調(diào)函數(shù)
fail::function //非必填,接口調(diào)用失敗后的回調(diào)函數(shù)
complete::function //非必填,接口調(diào)用結(jié)束后的回調(diào)函數(shù)(調(diào)用成功、失敗都會執(zhí)行)
向低功耗藍牙設(shè)備特征值中寫入二進制數(shù)據(jù)。 必須設(shè)備的特征值支持write權(quán)限才可以成功調(diào)用。
具體的如下所示,
wx.writeBLECharacteristicValue({
deviceId: that.data.deviceId,//設(shè)備deviceId
serviceId: that.data.service_id,//設(shè)備service_id
characteristicId: that.data.write_id,//設(shè)備write特征值
value: buffer,//寫入數(shù)據(jù)
success: function (res) {
console.log('發(fā)送數(shù)據(jù):', res.errMsg)
原文:https://blog.csdn.net/caohoucheng/article/details/81633822
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請附上博文鏈接!
讀特征值
wx.readBLECharacteristicValue(Object object)
deviceId::string //必填,藍牙設(shè)備id
serviceId::string //必填,藍牙特征值對應(yīng)服務(wù)的UUID
characteristic::string //必填,藍牙特征值UUID
success::function //非必填,接口調(diào)用成功后回調(diào)函數(shù)
fail::function //非必填,接口調(diào)用失敗后回調(diào)函數(shù)
complete::function //非必填,接口調(diào)用結(jié)束后回調(diào)函數(shù)(調(diào)用成功、失敗都會執(zhí)行)
具體的如下所示
wx.readBLECharacteristicValue({
// 這里的 deviceId 需要已經(jīng)通過 createBLEConnection 與對應(yīng)設(shè)備建立鏈接
// 這里的 serviceId 需要在 getBLEDeviceServices 接口中獲取
// 這里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中獲取
console.log('readBLECharacteristicValue:', res.errCode)
小程序上低功耗藍牙一般操作流程
初始化藍牙模塊(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ù)的類型如下
* @brief ESP_GATTS_READ_EVT
struct gatts_read_evt_param {
uint16_t conn_id; /*!< Connection id */
uint32_t trans_id; /*!< Transfer id */
esp_bd_addr_t bda; /*!< The bluetooth device address which been read */
uint16_t handle; /*!< The attribute handle */
uint16_t offset; /*!< Offset of the value, if the value is too long */
bool is_long; /*!< The value is too long or not */
bool need_rsp; /*!< The read operation need to do response */
} read; /*!< Gatt server callback param of ESP_GATTS_READ_EVT */
* @brief ESP_GATTS_WRITE_EVT
struct gatts_write_evt_param {
uint16_t conn_id; /*!< Connection id */
uint32_t trans_id; /*!< Transfer id */
esp_bd_addr_t bda; /*!< The bluetooth device address which been written */
uint16_t handle; /*!< The attribute handle */
uint16_t offset; /*!< Offset of the value, if the value is too long */
bool need_rsp; /*!< The write operation need to do response */
bool is_prep; /*!< This write operation is prepare write */
uint16_t len; /*!< The write attribute value length */
uint8_t *value; /*!< The write attribute value */
} write; /*!< Gatt server callback param of ESP_GATTS_WRITE_EVT */
* @brief ESP_GATTS_EXEC_WRITE_EVT
struct gatts_exec_write_evt_param {
uint16_t conn_id; /*!< Connection id */
uint32_t trans_id; /*!< Transfer id */
esp_bd_addr_t bda; /*!< The bluetooth device address which been written */
#define ESP_GATT_PREP_WRITE_CANCEL 0x00 /*!< Prepare write flag to indicate cancel prepare write */
#define ESP_GATT_PREP_WRITE_EXEC 0x01 /*!< Prepare write flag to indicate execute prepare write */
uint8_t exec_write_flag; /*!< Execute write flag */
4.4.4.1 使能通知
使能notify并讀取藍牙發(fā)過來的數(shù)據(jù),開啟這個后我們就能實時獲取藍牙發(fā)過來的值了。
使能通知(notify enable)的打印如下所示,打開通知實際上的一個WRITE。對應(yīng)于手機端的mBluetoothGatt.setCharacteristicNotification(characteristic,true).
【2018-12-25 16:01:24:307】_[0;32mI (27345) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 3, handle 43_[0m
_[0;32mI (27345) GATTS_DEMO: GATT_WRITE_EVT, value len 2, value :_[0m
_[0;32mI (27345) GATTS_DEMO: 01 00 _[0m
_[0;32mI (27355) GATTS_DEMO: notify enable_[0m
_[0;33mW (27355) BT_BTC: btc_gatts_arg_deep_copy 11, NULL response_[0m
_[0;32mI (27365) GATTS_DEMO: ESP_GATTS_CONF_EVT, status 0 attr_handle 42_[0m
【2018-12-25 16:01:24:610】_[0;32mI (27645) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 4, handle 47
_[0;32mI (27645) GATTS_DEMO: GATT_WRITE_EVT, value len 2, value :_[0m
_[0;32mI (27645) GATTS_DEMO: 01 00 _[0m
_[0;32mI (27655) GATTS_DEMO: notify enable_[0m
_[0;33mW (27655) BT_BTC: btc_gatts_arg_deep_copy 11, NULL response_[0m
_[0;32mI (27665) GATTS_DEMO: ESP_GATTS_CONF_EVT status 0 attr_handle 46_[0m
_[0;32mI (27725) GATTS_DEMO: GATT_READ_EVT, conn_id 0, trans_id 5, handle 42
如果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);
深入看看esp_ble_gatts_send_indicate
* @brief Send indicate or notify to GATT client.
* Set param need_confirm as false will send notification, otherwise indication.
* @param[in] gatts_if: GATT server access interface
* @param[in] conn_id - connection id to indicate.
* @param[in] attr_handle - attribute handle to indicate.
* @param[in] value_len - indicate value length.
* @param[in] value: value to indicate.
* @param[in] need_confirm - Whether a confirmation is required.
* false sends a GATT notification, true sends a GATT indication.
esp_err_t esp_ble_gatts_send_indicate(esp_gatt_if_t gatts_if, uint16_t conn_id, uint16_t attr_handle,
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ù);讀寫打印如下所示:
【2019-01-02 10:37:09:539】[0;32mI (4697198) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 11, handle 42[0m
[0;32mI (4697198) GATTS_DEMO: GATT_WRITE_EVT, value len 3, value :[0m
[0;32mI (4697208) GATTS_DEMO: 31 32 33 [0m
[0;33mW (4697208) BT_BTC: btc_gatts_arg_deep_copy 11, NULL response[0m
[0;32mI (4697278) GATTS_DEMO: GATT_READ_EVT, conn_id 0, trans_id 12, handle 42
/*************************************************************************************************************************************/
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);
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);
example_write_event_env(gatts_if, &a_prepare_write_env, param);
深入example_write_event_env(),其核心代碼如下 esp_ble_gatts_send_response函數(shù)
if (param->write.need_rsp){
esp_err_t esp_ble_gatts_send_response(esp_gatt_if_t gatts_if, uint16_t conn_id, uint32_t trans_id,
esp_gatt_status_t status, esp_gatt_rsp_t *rsp);
根據(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ù)
* @brief ESP_GATTS_WRITE_EVT
struct gatts_write_evt_param {
uint16_t conn_id; /*!< Connection id */
uint32_t trans_id; /*!< Transfer id */
esp_bd_addr_t bda; /*!< The bluetooth device address which been written */
uint16_t handle; /*!< The attribute handle */
uint16_t offset; /*!< Offset of the value, if the value is too long */
bool need_rsp; /*!< The write operation need to do response */
bool is_prep; /*!< This write operation is prepare write */
uint16_t len; /*!< The write attribute value length */
uint8_t *value; /*!< The write attribute value */
這里的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ù)
* @brief ESP_GATTS_READ_EVT
struct gatts_read_evt_param {
uint16_t conn_id; /*!< Connection id */
uint32_t trans_id; /*!< Transfer id */
esp_bd_addr_t bda; /*!< The bluetooth device address which been read */
uint16_t handle; /*!< The attribute handle */
uint16_t offset; /*!< Offset of the value, if the value is too long */
bool is_long; /*!< The value is too long or not */
bool need_rsp; /*!< The read operation need to do response */
} 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)體。
uint8_t value[ESP_GATT_MAX_ATTR_LEN]; /*!< Gatt attribute value */
uint16_t handle; /*!< Gatt attribute handle */
uint16_t offset; /*!< Gatt attribute value offset */
uint16_t len; /*!< Gatt attribute value length */
uint8_t auth_req; /*!< Gatt authentication request */
/// GATT remote read request response type
esp_gatt_value_t attr_value; /*!< Gatt attribute structure */
uint16_t handle; /*!< Gatt attribute handle */
在我的項目中,客戶端先寫再讀。
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)容有其他問題,請指正,及時糾正我。在此謝謝大家。