从ESP32 BLE应用理解GATT

目录

1、背景

1.1参考资料

1.2 GATT是什么玩意

2、ESP32 例程分析

2.1 GATT 服务器的架构组织

2.2 从GATT回调函数注册程序esp_ble_gatts_register_callback开始深入分析

3 建立连接之前的GATT状态机

3.1 创建服务 creating services

3.2 启动服务并创建Characteristics

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

3.3 添加Characteristic descriptor 描述符

4 建立连接时GATT的状态机研究

4.1连接后触发的连接回调函数

4.2连接后MTU大小确定

4.3 发送应答数据

4.4 客户端和服务器端 收发数据

4.4.1 手机端的操作

4.4.2 设备端的操作

4.4.4.1 使能通知

4.4.4.2 读写数据

4.5 Characteristic结构

5. 数据交互其他必要知道的

6.结束语


 1、背景

虽然看了一些BLE的资料,可是对蓝牙依旧不太了解。现在从ESP32 的示例出发结合esp_gatts_api.h学习GATT。

本文以创建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(属性协议)定义的一个service(服务)框架。这个框架定义了Services以及它们的Characteristics的格式和规程。规程就是定义了包括发现、读、写、通知、指示以及配置广播的characteristics。------BLE V4.2给出了如下定义(翻译有待加强)。

Profile中所使用的Protocols(协议)

为实现这个Profile的设备定义了两种角色:Client(客户端)、Server(服务器)。角色不是不固定的哦.....

在GATT的Profile的定义11个features,映射了程序Procedure。

Feature对应的Procedure
序号FeatureProcedure
1Server Configuration 服务器配置Exchange MTU  连接期间只能配置一次,确定连接通信的ATT_MTU大小;否则就用默认的23-3 = 20
2Primary Service Discover 主服务查找Discover All Primary Services/Discover Primary Services By Service UUID 把主服务都找出来,
3Relationship Discover查找Included Services
4Characteristic 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)发现一个设备上的服务s和特征s

3)读取一个特征值(characteristic value)

4)写入一个特征值

5)通知一个特征值

6)指示一个特征值 

2、ESP32 例程分析

一旦两个设备建立了连接,GATT就开始发挥效用,同时意味着GAP协议管理的广播过程结束了。

GATT连接是独占的,即一个BLE周边设备同时只能与一个中心设备连接。??????

profile 可以理解为一种规范,一个标准的通信协议中,存于从机(Server)中。蓝牙组织规定了一些标准的Profile。每个profile中包含多个Service,每个service代表从机的一种能力。

2.1 GATT 服务器的架构组织

一个GATT 服务器应用程序架构(由Application Profiles组织起来)如下: 

每个Application Profile描述了一个方法来对为一个客户端应用程序设计的功能进行分组,例如在智能手机或平板电脑上运行的移动应用程序。

每个Profile定义为一个结构体,结构体成员依赖于该Application Profile 实现的services服务和characteristic特征。结构体成员还包括GATT interface(GATT 接口)、Application ID(应用程序ID)和处理profile事件的回调函数。

每个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),它就必须是实现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存储在数组中,并分配相应的回调函数gatts_profile_a_event_handler() 和 gatts_profile_b_event_handler()。

在GATT客户机上的不同的应用程序使用不同的接口,用gatts_if参数来表示。在初始化时,gatts-if参数初始化为ESP_GATT_IF_NONE,在注册客户端时(如注册profile A的客户端时gatt_if = 3,在注册profile B的客户端时gatt_if = 4)


   
   
  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. };

这是两个元素的数组。可以用Application ID来注册Application Profiles,Application ID是由应用程序分配的用来标识每个Profile。 通过这种方法,可以在一个Server中run多个Application Profile。

esp_ble_gatts_app_register(PROFILE_A_APP_ID);

2.2 从GATT回调函数注册程序esp_ble_gatts_register_callback开始深入分析

看样子,也是一个状态机,下面是GATT回调函数的注册函数esp_ble_gatts_register_callback。

esp_err_t esp_ble_gatts_register_callback(esp_gatts_cb_t callback);
   
   

作用:向BTA GATTS模块注册应用程序回调函数。

callback回调函数处理从BLE堆栈推送到应用程序的所有事件。

对于GATT server回调函数类型进行分析


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

回调函数的参数:
event: esp_gatts_cb_event_t  这是一个枚举类型,表示调用该回调函数时的事件(或蓝牙的状态)

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 指向回调函数的参数,是个联合体类型,不同的事件类型采用联合体内不同的成员结构体。

第一步、看看蓝牙状态机有哪些状态类型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;

第二步、再来看一个很有意思的联合体类型esp_ble_gatts_cb_param_t,不同的事件类型联合体的对象也不同。


   
   
  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()回调函数---调用esp_ble_gatts_app_register(1),触发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时,调用对应profile的回调函数处理事情。


   
   
  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. }

状态机一般状态转变过程:

以gatts_server这个demo为例,讲解GATT状态机的一般过程:

/*********************************建立连接之前*******************************************************/
ESP_GATTS_REG_EVT---->ESP_GATTS_CREATE_EVT---->ESP_GATTS_START_EVT---->ESP_GATTS_ADD_CHAR_EVT--->ESP_GATTS_ADD_CHAR_DESCR_EVT

注册->创建->启动->添加特征->添加特征描述

/*********************************有Client开始连接之后:*******************************************************/

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

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

                                    *建立连接之前回调函数事件分析

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

在Demo的ESP_GATTS_REG_EVT事件中,调用esp_ble_gap_set_device_name(char *)来设置蓝牙设备名字;调用esp_ble_gap_config_adv_data(esp_ble_adv_data_t *adv_data)来配置广播数据;最后调用

esp_ble_gatts_create_service(esp_gatt_if_t gatts_if, esp_gatt_srvc_id_t *service_id, uint16_t num_handle)指定gatts_if和service_id来创建服务<实际调用btc_transfer_context()来完成服务的创建和调用回调函数>。服务创建完成就会触发回调函数向frofile报告状态和服务ID。Service_id对于后面添加included serivces 和 characteristics/descriptor都要用到。触发ESP_GATTS_CREATE_EVT事件

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

在Demo的ESP_GATTS_CREATE_EVT中调用esp_ble_gatts_start_service(uint16_t service_handle)来启动服务;再调用

esp_ble_gatts_add_char(uint16_t service_handleesp_bt_uuid_t *char_uuidesp_gatt_perm_t permesp_gatt_char_prop_t propertyesp_attr_value_t *char_valesp_attr_control_t *control)来添加特性(特征的UUID, 特征值描述符属性权限, 特征属性、特征值、属性响应控制字节)。

触发ESP_GATTS_START_EVT和ESP_GATTS_ADD_CHAR_EVT事件

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

在ESP_GATTS_ADD_CHAR_EVT事件中,获取特征值调用esp_err_tesp_ble_gatts_add_char_descr(uint16_t service_handleesp_bt_uuid_t *descr_uuidesp_gatt_perm_tpermesp_attr_value_t *char_descr_valesp_attr_control_t *control)来添加特征描述符

对于注册函数,还可以这样操作

1>当-调用esp_ble_gatts_app_register()注册一个应用程序Profile(Application Profile),触发ESP_GATTS_REG_EVT事件,除了可以完成对应profile的gatts_if的注册,还可以调用esp_bel_create_attr_tab()来创建profile Attributes 表或创建一个服务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);

作用:创建一个服务Attribute表。

参数

gatts_attr_db :指向加入profile的服务 attr 表 (从Service 到 Characteristic....)

gatts_if: GATT服务器的访问接口

max_nb_attr: 加入服务数据库的attr的数目

srvc_inst_id: 服务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;

 对于结构体esp_gatts_attr_db_t的成员attr_control的可取值

#define ESP_GATT_RSP_BY_APP             0         //由应用程序回复写入\读取操作应答
#define ESP_GATT_AUTO_RSP                1         //由GATT堆栈自动回复吸入\读取操作应答

成员 att_desc的结构体类型


   
   
  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状态机搞清楚

3 建立连接之前的GATT状态机

3.1 创建服务 creating services

在触发ESP_GATTS_REG_EVT时,除了创建表还可以创建服务S,调用esp_ble_gatts_create_service来创建服务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);

作用:创建一个service。当一个service创建成功(done)后,ESP_CREATE_SERVICE_EVT事件触发回调函数被调用,该回调函数报告了profile的stauts和service ID。当要添加include service和 characteristics/descriptors入服务service,Service ID在回调函数中用到。

参数:gatts_if——GATT 服务器访问接口

service_id: 服务UUID相关信息

num_handle:该服务所需的句柄数 service handle、characteristic declaration handle、 characteristic value handle、characteristic description handle 的句柄数总和。Demo中用的是4.

查看参数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;

服务ID参数包括

is_primary参数当前服务是否是首要的;---------服务声明中的Attribute Type 0x2800---Primary/0x2801---Secondary

id参数UUID的信息(包括uuid 和服务实例instance id)

         其中uuid是UUID的信息包括UUID的长度(16bit/32bit/128bit)及UUID具体值。

在Demo中,服务被定义为16bit的UUID的主服务。服务ID以实例ID为0,UUID为0x00FF来初始化,len为2个字节。

服务实例ID是用来区分在同一个Profile中具有相同UUID的多个服务。Application Profile中拥有相同UUID的两个服务,需要用不同的实例ID来引用。

如下示例展示了一个Service的创建。


   
   
  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. }

 一个profile有一个primary服务,服务有UUID以及服务实例的ID。

 

3.2 启动服务并创建Characteristics

 当一个服务service创建成功(done)后,由该profile GATT handler 管理的 ESP_GATTS_CREATE_EVT事件被触发,在这个事件可以启动服务和添加characteristics到服务中。调用esp_ble_gatts_start_service来启动指定服务。

Characteristic是在GATT规范中最小的逻辑数据单元,由一个Value和多个描述特性的Desciptior组成。实际上,在与蓝牙设备打交道,主要就是读写Characteristic的value来完成。同样的,Characteristic也是通过16bit或128bit的UUID唯一标识

我们根据蓝牙设备的协议用对应的Characteristci进行读写即可达到与其通信的目的。

ESP_GATTS_CREATE_EVT事件中回调函数参数的类型为gatts_create_evt_param(包括操作函数、servic的句柄、服务的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()这个函数启动一个服务。

参数: service_handle-------要被启动的服务句柄。

首先,由BLE堆栈生成生成的服务句柄(service handle)存储在Profile表中,应用层将用服务句柄来引用这个服务。调用esp_ble_gatts_start_service()和先前产生服务句柄来启动服务。

esp_err_t esp_ble_gatts_add_char(uint16_t service_handleesp_bt_uuid_t *char_uuid

esp_gatt_perm_tpermesp_gatt_char_prop_tpropertyesp_attr_value_t *char_val

esp_attr_control_t *control)

这样ESP_ATTS_START_EVT事件触发时,将打印输出信息启动的Service Handle之类的信息。如图所示

添加特征到service中,调用esp_ble_gatts_add_char()来添加characteristics连同characteristic 权限和property(属性)到服务service中。

有必要再次解释一下property

Characteristic Properties这个域(bit控制)决定了Characteristic Value如何使用、Characteristic descriptors 如何访问。只要下标中对应bit被设备,那么对应描述的action就被允许。

在Demo中,特征uuid的len为2字节,uuid为0x00FF,权限是可读可写,属性的读、写、通知bit置1,且设置特征值,


   
   
  1. gl_profile_tab.char_uuid.len = ESP_UUID_LEN_16;
  2. gl_profile_tab.char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_TEST_A;
  3. a_property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY;
  4. esp_err_t add_char_ret = esp_ble_gatts_add_char(gl_profile_tab.service_handle, &gl_profile_tab.char_uuid,
  5. ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
  6. a_property,
  7. &gatts_demo_char1_val, NULL);

 

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)

参数:service_handle-------Characteristic要添加到的服务的Service handler服务句柄,一个Characteristic至少包括2个属性ATT,一个属性用于characteristic declaration/另一个用于存放特征值(characteristic value declaration).

char_uuid-------Characteristic 的UUID;  属于Characteristic declaration 这个ATT

perm------特征值声明(Characteristic value declaration) 属性(Attribute)访问权限;

ATT具有一组与其关联的权限值,权限值指定了读/写权限、认证权限、授权许可

                permission权限的可取值{可读、可加密读、可加密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-------属性响应控制字节

characteristic declaration的Attribute Value  包括 property、characteristic Value declaration handle、char_uuid 三个段;其中property、char_uuid在添加Characteristic调用的函数的参数中已经指明,只有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. };

 在这个结构体里面包括了属性值最大长度、当前长度、当前值。由这个来指定 characteristic value declaration的。有这些值就足以构成一个 characteristic  value declaration ATT了

3.3 添加Characteristic descriptor 描述符

当特征添加到service中成功(done)时,触发ESP_GATTS_ADD_CHAR_EVT事件。触发ESP_GATTS_ADD_CHAR_EVT事件时,回调函数参数param的结构体为gatts_add_char_evt_param,包括操作状态、特征ATT的handle()、service_handle(服务句柄)、characteristc uuid(服务的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 */

还可以通过调用esp_ble_gatts_get_attr_value()来获取跟具体的Characteristic Value declartation 属性的具体信息。

下面是调用的例子,输入参数是特征句柄;输出参数是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. }

关于gatts_get_attribute_value()更底层的东西就要看ble协议栈的更底层的实现,这里很难查下去。但是可以通过打印输出来判断这里实现些什么。

有上述打印可以看出,这里输出Characteristic value declaration的信息,因此esp_ble_gatts_get_attr_value()来输出特征值长度和特征值。

还可在ESP_GATTS_ADD_CHAR_EVT事件的回调函数中,给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()这个函数的原型


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

参数:service_handle:这个characteristic descriptor要添加的service handle。

perm: 描述符访问权限

descr_uuid:描述符UUID

char_descr_val:描述符值

control:ATT 应答控制字节

这个函数被用来添加Characteristic descriptor。当添加完成时,BTA_GATTS_ADD_DESCR_EVT 回调函数被调用去报告它的状态和ID。

 

gatt_server例子中:一共建立两个profile。A profile中包括的service UUID为0x00FF, characteristic UUID为0xFF01;

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

下面看是看开始建立连接后,GATT的状态机转变过程:

手机client连接到server,触发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事件回调函数param参数包括连接ID以及远端蓝牙设备地址。调用esp_ble_update_conn_params(&conn_params)来更新连接参数。这个函数只有在连接上以后才可以调用。


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

 参数连接更新参数(蓝牙设备地址、最小连接间隔、最大连接间隔、连接数量、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的状态机研究

 设备设备连上外围蓝牙时,打印如下所示:第一个触发了CONNECT回调函数;接着触发了MTU尺寸的大小确认事件。

4.1连接后触发的连接回调函数


   
   
  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;

上面是连接回调函数param的参数结构体,包括连接id和远端(对端)蓝牙设备地址(bda)。

在一般回调函数处理中,记录对端的信息,且发送更新后的连接参数到对端设备 。


   
   
  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)。这个函数只能在连接上时使用,用于更新连接参数。


   
   
  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. }

继续深入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. }

 到此,不继续下去。

4.2连接后MTU大小确定

 当有手机(client客户端)连上server时,触发ESP_GATTS_MTU_EVT事件,其打印如下图所示

ESP_GATTS_MTU_EVT事件对应的回调函数中参数param的结构体为gatts_mtu_evt_param(包括连接id和MTU大小)


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

在例子中设置本地的MTU大小为500,代码如下所示:

esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500);
   
   

如上所述,设置了MTU的值(经过MTU交换,从而设置一个PDU中最大能够交换的数据量)。例如:主设备发出一个150字节的MTU请求,但是从设备回应的MTU是23字节,那么今后双方要以较小的值23字节作为以后的MTU。即主从双方每次在做数据传输时不超过这个最大数据单元。 MTU交换通常发生在主从双方建立连接后。MTU比较小,就是为什么BLE不能传输大数据的原因所在。

-----参照一分钟读懂低功耗(BLE)MTU交换数据包https://blog.csdn.net/viewtoolsz/article/details/76177465 这篇文章就可以了解MTU交换过程。

MTU交换请求用于client通知server关于client最大接收MTU大小并请求server响应它的最大接收MTU大小。

Client的接收MTU 应该大于或等于默认ATT_MTU(23).这个请求已建立连接就由client发出。这个Client Rx MTU参数应该设置为client可以接收的attribute protocol PDU最大尺寸。

MTU交换应答发送用于接收到一个Exchange MTU请求

这个应答由server发出,server的接收MTU必须大于或等于默认ATT_MTU大小。这里的Server Rx MTU应该设置为 服务器可以接收的attribute protocol PDU 最大尺寸。 

Server和Client应该设置ATT_MTU为Client Rx MTU和Server Rx MTU两者的较小值。

这个ATT_MTU在server在发出这个应答后,在发其他属性协议PDU之前生效;在client收到这个应答并在发其他属性协议PDU之前生效。

4.3 发送应答数据


   
   
  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

这个函数用于发送应答给对应请求。

参数: gatts_if-------GATT server 访问接口

conn_id-----连接ID

trans_id-----传输ID

status----应答状态

rsp-----应答数据 gatt attribute value

查看GATT 读应答结构


   
   
  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 客户端和服务器端 收发数据

4.4.1 手机端的操作

接下去看,连接以后收发数据

首先了解一下手机端在蓝牙连接、数据交互过程中的操作。


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

  1. 检测蓝牙是否可用,绑定蓝牙服务
  2. 使用BluetoothAdapter.startLeScan来扫描低功耗蓝牙设备
  3. 在扫描到设备的回调函数中会得到BluetoothDevice对象,并使用BluetoothAdapter.stopLeScan停止扫描
  4. 使用BluetoothDevice.connectGatt来获取到BluetoothGatt对象 /*****************************************************************************************************************************************/
  5. 执行BluetoothGatt.discoverServices,这个方法是异步操作,在回调函数onServicesDiscovered中得到status, 通过判断status是否等于BluetoothGatt.GATT_SUCCESS来判断查找服务Service是否成功
  6. 如果成功了,则通过BluetoothGatt.getServices()来获取所有的Services;根据Sevice_UUID来查找想要的服务BluetoothGattService
  7. 接着通过BluetoothGattService.getCharacteristic(spiecial_char_UUID)获取BluetoothGattCharacteristic,最基本的一般获取Read_UUID的Charcteristic、Write_UUID的Characteristic、Notification_UUID的Characteristic。
  8. 设置通知打开------通知打开或关闭实际山是一次写入操作
  9. 然后通过BluetoothGattCharacteristic.getDescriptor获取BluetoothGattDescriptor
  10. 对于发送和接收数据 都是从BluetoothGatt.readCharacteristic和BluetoothGatt.writeCharcteristic来实现。
  11. 注意:在写时参数characteristic,调用setValue设置Characteristic的属性值,调用setWriteType设置写的方式(如WRITR_TYPE_NO_RESPONSE)

第3步扫描到设备会触发回调,在回调中,通常根据设备名称找到想要连接的设备,完成连接;连接后,获取到BluetoothGATT 对象,再从BluetoothGatt.discoverServices中获取各个Services,根据硬件工程师提供的UUID连接你需要的Service。最后,根据硬件工程师提供的UUID找到读、写和通知的UUID,然后再进行读写操作。  读写和设置通知均为单步操作,执行完一个才能执行下一个。

从这里出发,现在仍感觉不是很痛快,因此我从小程序的低功耗蓝牙接口出发,两相对照看。

写特征值


   
   
  1. wx.writeBLECharacteristicValue(Object object)
  2. Object{
  3. deviceId:: string //必填,蓝牙设备id
  4. serviceId:: string //必填,蓝牙特征值Characteristic对应服务的UUID
  5. characteristicId:: string //必填,蓝牙特征值的UUID
  6. value::ArrayBuffer //必填,蓝牙设备特征值对应的二进制值
  7. success::function //非必填,接口调用成功后的回调函数
  8. fail::function //非必填,接口调用失败后的回调函数
  9. complete::function //非必填,接口调用结束后的回调函数(调用成功、失败都会执行)
  10. }
  11. 向低功耗蓝牙设备特征值中写入二进制数据。 必须设备的特征值支持write权限才可以成功调用。

具体的如下所示,


   
   
  1. wx.writeBLECharacteristicValue({
  2. deviceId: that.data.deviceId, //设备deviceId
  3. serviceId: that.data.service_id, //设备service_id
  4. characteristicId: that.data.write_id, //设备write特征值
  5. value: buffer, //写入数据
  6. success: function (res) {
  7. console.log( '发送数据:', res.errMsg)
  8. }
  9. });
  10. ---------------------
  11. 作者:Cc_JoJo
  12. 来源:CSDN
  13. 原文:https: //blog.csdn.net/caohoucheng/article/details/81633822
  14. 版权声明:本文为博主原创文章,转载请附上博文链接!

读特征值


   
   
  1. wx.readBLECharacteristicValue(Object object)
  2. Objcet{
  3. deviceId:: string //必填,蓝牙设备id
  4. serviceId:: string //必填,蓝牙特征值对应服务的UUID
  5. characteristic:: string //必填,蓝牙特征值UUID
  6. success::function //非必填,接口调用成功后回调函数
  7. fail::function //非必填,接口调用失败后回调函数
  8. complete::function //非必填,接口调用结束后回调函数(调用成功、失败都会执行)
  9. }

具体的如下所示


   
   
  1. wx.readBLECharacteristicValue({
  2. // 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
  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)------>开始搜索蓝牙外围设备(耗资源)(wx.startBluetoothDevicesDiscovery)----->

获取在蓝牙模块生效期间所有已发现的蓝牙设备(包括已经和本机处于连接状态的设备)(wx.getBluetoothDevices)----->

监听找到的新设备的事件(wx.onBluetoothDeviceFound(callback))------->连接低功耗蓝牙设备(wx.createBLEConnection())------>

获取蓝牙设备所有services(wx.getBLEDeviceServices())------>获取蓝牙设备某个服务中所有Characteristic(特征)(wx.getBLEDeviceCharacteristics)-------->启动低功耗蓝牙设备特征的值变化时的notify功能(wx.notifyBLECharacteristicValueChange())------->写入wx.writeBLECharactericValue()

目前,微信小程序上也应该是无应答写。

 

4.4.2 设备端的操作

将ESP_GATTS_READ_EVT、ESP_GATTS_WRITE_EVT和ESP_GATTS_EXEC_WRITE_EVT三类事件param参数的类型如下


   
   
  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并读取蓝牙发过来的数据,开启这个后我们就能实时获取蓝牙发过来的值了。

使能通知(notify enable)的打印如下所示,打开通知实际上的一个WRITE。对应于手机端的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描述值,根据描述值开启/关闭 通知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);

该函数将notify或indicate发给GATT的客户端;

need_confirm = false,则发送的是notification通知;

==true,发送的是指示indication。

其他参数: 服务端访问接口;连接id; 属性句柄,value_len; 值 

4.4.4.2 读写数据

看看Write 和Read 触发的回调函数;读写打印如下所示:


   
   
  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函数


   
   
  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. }

根据param->write.need_rsp是否需要应答来决定,是否调用esp_ble_gatts_send_response来应答。 

而esp_ble_gatts_send_response()函数实际调用了btc_transfer_context,将信息发送出去。

need_rsp如何确定这个值,我很有疑问,看到这里的看客,知道的请回答我一下。

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

先把Write搞清楚,先看看写的事件参数


   
   
  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; 由这个参数确定参数的是长包还是短包。

offset 是这包数据相对长数据的偏移,第一包是0x0000;

need_rsp是针对该写操作是否需要应答response。

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

目前,我弄的是Write Without Response。

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

接下去看看Read,看看读的事件参数


   
   
  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 信息大小时,表示传输的是长数据,就用Read Blob Request。

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

offset 就是要读的Characteristic Value偏移位置,第一包Read Blob Request时,offset为0x00;

对应的是Read Blob Response ATT 以一部分的Characteristic Value作为ATT Value 参数。

在看看ESP_IDF中关于read Response 的结构体。


   
   
  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结构

再次对Characteristic的结构分析一遍,记不住啊。

Characteristic 声明、Characteistic Value 声明、Characteristic Descriptor 声明。

5. 数据交互其他必要知道的

 权限对于数据交换,通常设置有读Characteristic写Characteristic-----------Write_SSID/Read_SSID.

Read/Write读/写-----一次会同时出发读和写两个回调事件;如上打印结果所述。

Read Only只读-------只能触发读回调事件;

Write Only只写------只能触发写回调事件;

6.结束语

关于GATT就先告一个段落,其他还有很多不足一处,需要一一修正补充。如果上述内容有其他问题,请指正,及时纠正我。在此谢谢大家。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值