目录

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
序号 Feature Procedure
1 Server Configuration 服务器配置 Exchange MTU  连接期间只能配置一次,确定连接通信的ATT_MTU大小;否则就用默认的23-3 = 20
2 Primary Service Discover 主服务查找 Discover All Primary Services/Discover Primary Services By Service UUID 把主服务都找出来,
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)发现一个设备上的服务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_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)来添加特性(特征的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_handle, esp_bt_uuid_t *descr_uuid, esp_gatt_perm_tperm, esp_attr_value_t *char_descr_val, esp_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_handle, esp_bt_uuid_t *char_uuid,

esp_gatt_perm_tperm, esp_gatt_char_prop_tproperty, esp_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就先告一个段落,其他还有很多不足一处,需要一一修正补充。如果上述内容有其他问题,请指正,及时纠正我。在此谢谢大家。

从ESP32 BLE应用理解GATT相关推荐

  1. ESP32 BLE 学习:关于 RSSI 与发射功率

    ESP32 BLE 学习:关于 RSSI 与发射功率 文章目录 ESP32 BLE 学习:关于 RSSI 与发射功率 蓝牙发射功率 RSSI(接收的信号强度指示) API 函数设置和获取 发射功率 蓝 ...

  2. (七)深入理解蓝牙BLE之“蓝牙GATT服务”

    目录 前言: 学习总结: uuid的定义: GATT Declarations: GATT Service: GATT Characteristic and Object Type:

  3. AliOS-Things+ESP32 BLE篇 (1)BLE peripheral

    由于STM32的板子没有自带蓝牙和wifi模组,所以外设方面的demo,我选择放到乐鑫的ESP32模组上.一方面是由于ESP32这块板子有丰富的BT/WIFI的实现例程,还因为乐鑫的这款SOC扩展性很 ...

  4. 菜鸟哥玩蓝牙Ble4.0系列 ESP32初玩篇⑤ GATT —— 连接数据通信之Server模式

    文章目录 1.前言 2.ATT -- 属性协议 2.1 概述 2.2 属性(Attribute) 2.2.1 属性类型 -- UUID 2.2.2 属性句柄 -- Handle 2.2.3 属性值 - ...

  5. Android BLE 开发,GATT报错 status 133全面解析

    困扰了一天的问题. 这里,全部参考google 官方文档 BLE开发. 但是事与愿违,最终出现了133的问题. 参考了很多网上的攻略,无一幸免. 这里声明一下,这篇博客仅仅针对有一定蓝牙开发基础的同学 ...

  6. 【Bluetooth蓝牙开发】九、BLE协议之GATT

    个人主页:董哥聊技术 我是董哥,嵌入式领域新星创作者 创作理念:专注分享高质量嵌入式文章,让大家读有所得! [所有文章汇总] 文章目录 1.简介 角色的定义 2.GATT协议框架 2.1 Profil ...

  7. BLE ATT和GATT小结

    1.ATT(Attribute Protocol)协议 ATT协议允许服务器设备将一组属性及其相关值暴露给对等的客户端设备.服务器公开的属性可以被客户端发现,读取和写入,并且可以由服务器指示和通知.  ...

  8. ESP32-C3 学习测试 蓝牙 篇(四、GATT Server 示例解析)

    了解了蓝牙 GATT 相关概念,趁热打铁,分析一下官方示例 GATT Server 的应用程序架构. 目录 前言 一.GATT Server 示例分析 1.1 初始化 1.2 回调函数 gatts_e ...

  9. ESP32 入门笔记05: BLE 蓝牙客户端和服务器 (ESP32 for Arduino IDE)

    目录 1. BLE 服务器和客户端 2. GATT 协议 3. UUID 4. 项目概述 4.1 材料清单 4.2 ESP32 BLE 服务器 4.3 ESP32 BLE 客户端 参考资料 1. BL ...

最新文章

  1. Java基础(七)--Exception异常处理
  2. Google Guice结合模式
  3. Java中的do-while循环——通过示例学习Java编程(11)
  4. Java摇骰子比大小_还在摇骰子比大小?这几款火热的KTV喝酒游戏你值得收藏
  5. 5G 2.0 的最新动向
  6. 介绍几个 window 下面的terminal
  7. elasticsearch 海选功能
  8. Chrome 开发者工具网络性能使用
  9. 高效地获取XMLhttp对象
  10. c语言起点到目的地方法数,最短路径动态规划问题及C语言实现探讨
  11. easyui java下拉列表项目_EasyUI/TopJUI之如何动态改变下拉列表框ComboBox输入框的背景颜色...
  12. 纪念DOS下的经典软件
  13. 二年级上册计算题_二年级上册数学计算题
  14. 基于Proteus无实物STM32入门自学教程(三)--静态数码管
  15. 【论文阅读-表情捕捉】High-quality Real Time Facial Capture Based on Single Camera
  16. LA 3713 Astronauts
  17. 算法与数据结构学习资源大搜罗——良心推荐
  18. Unity - Timeline 之Creating a Timeline Asset and Timeline instance(创建Timeline Asset和Timeline 实例)
  19. 阿里云大学推出云学院中小企业学习优惠方案,加速提升企业云时代人才竞争力
  20. 打造微信小程序伪状态管理器

热门文章

  1. 信息系统项目管理师----质量管理论文(飘过)
  2. 对象不支持此属性或方法
  3. 牛 TikTok的这一数据 Tik Tok出海称王
  4. python 服务器日志分析
  5. readdir()函数:读取目录函数
  6. 云计算基础——云存储
  7. C8051F396 Flash操作
  8. 【有效】2016/2013/2010 软件的安装解决方法
  9. filter,map,reduce
  10. 圆圆的球像什么_关于圆圆的月亮像什么的句子有哪些?