从ESP32 BLE应用理解GATT

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhejfl/article/details/85136102

目录

1、背景

1.1参考资料

1.2 GATT是什么玩意

2、ESP32 例程分析

2.1 GATT 服务器的架构组织

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

2.3 创建服务 creating services

2.4 启动服务并创建Characteristics

2.4.1 添加Characteristic Value declaration ATT

2.5 添加Characteristic descriptor 描述符


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(服务器)。角色不是不固定的哦.....

Profile的使用包括如下一些情景

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。

struct gatts_profile_inst {
    esp_gatts_cb_t gatts_cb;
    uint16_t gatts_if;
    uint16_t app_id;
    uint16_t conn_id;
    uint16_t service_handle;
    esp_gatt_srvc_id_t service_id;
    uint16_t char_handle;
    esp_bt_uuid_t char_uuid;
    esp_gatt_perm_t perm;
    esp_gatt_char_prop_t property;
    uint16_t descr_handle;
    esp_bt_uuid_t descr_uuid;
};

Application Profile存储在数组中,并分配相应的回调函数gatts_profile_a_event_handler() 和 gatts_profile_b_event_handler()。

在GATT客户机上的不同的应用程序使用不同的接口,用gatts_if参数来表示。在初始化时,gatts-if参数初始化为ESP_GATT_IF_NONE,这意味着Application Profile还没有连接任何客户端。

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

这是两个元素的数组。可以用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回调函数类型进行分析

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

回调函数的参数:
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 

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

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

   
typedef union {
    /**
     * @brief ESP_GATTS_REG_EVT
     */
    struct gatts_reg_evt_param {
        esp_gatt_status_t status;       /*!< Operation status */
        uint16_t app_id;                /*!< Application id which input in register API */
    } reg;                              /*!< Gatt server callback param of ESP_GATTS_REG_EVT */


........................
  /**
    * @brief ESP_GATTS_SET_ATTR_VAL_EVT
    */
 struct gatts_set_attr_val_evt_param{
        uint16_t srvc_handle;           /*!< The service handle */
        uint16_t attr_handle;           /*!< The attribute  handle */
        esp_gatt_status_t status;       /*!< Operation status*/
    } set_attr_val;                     /*!< Gatt server callback param of ESP_GATTS_SET_ATTR_VAL_EVT */

    /**
    * @brief ESP_GATTS_SEND_SERVICE_CHANGE_EVT
    */
    struct gatts_send_service_change_evt_param{
        esp_gatt_status_t status;       /*!< Operation status*/
    } service_change;                    /*!< Gatt server callback param of ESP_GATTS_SEND_SERVICE_CHANGE_EVT */

} esp_ble_gatts_cb_param_t;

示例中的gatts_event_handler()回调函数---调用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的回调函数处理事情。

if (gatts_if == ESP_GATT_IF_NONE||gatts_if == gl_profile_tab[idx].gatts_if) 			{
                if (gl_profile_tab[idx].gatts_cb) {
                    gl_profile_tab[idx].gatts_cb(event, gatts_if, param);
                }
            }

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

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

没有Client连接之前:
REGISTER_APP_EVT---->CREATE_SERVICE_EVT---->SERVICE_START_EVT---->ADD_CHAR_EVT--->ADD_DESCR_EVT

有Client开始连接之后:

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

1>当-调用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()

esp_err_t esp_ble_gatts_create_attr_tab(const esp_gatts_attr_db_t *gatts_attr_db, 
                                            esp_gatt_if_t gatts_if,
                                            uint8_t max_nb_attr,
                                            uint8_t srvc_inst_id);

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

参数

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

gatts_if: GATT服务器的访问接口

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

srvc_inst_id: 服务instance

typedef struct
{
    esp_attr_control_t      attr_control;                   /*!< The attribute control type */
    esp_attr_desc_t         att_desc;                       /*!< The attribute type */
} 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的结构体类型

/**
 * @brief Attribute description (used to create database)
 */
 typedef struct
 {   
     uint16_t uuid_length;              /*!< UUID length */
     uint8_t  *uuid_p;                  /*!< UUID value */
     uint16_t perm;                     /*!< Attribute permission */
     uint16_t max_length;               /*!< Maximum length of the element*/
     uint16_t length;                   /*!< Current length of the element*/
     uint8_t  *value;                   /*!< Element value array*/
 } esp_attr_desc_t;

以心率计Profile为例说明:

 esp_ble_gatts_create_attr_tab(heart_rate_gatt_db, gatts_if,
                                      HRS_IDX_NB, HEART_RATE_SVC_INST_ID);

详细的heart_rate_gatt_db
/// Full HRS Database Description - Used to add attributes into the database
static const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB] =
{
    // Heart Rate Service Declaration
    [HRS_IDX_SVC]                    =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
      sizeof(uint16_t), sizeof(heart_rate_svc), (uint8_t *)&heart_rate_svc}},

    // Heart Rate Measurement Characteristic Declaration
    [HRS_IDX_HR_MEAS_CHAR]            =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
      CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_notify}},

    // Heart Rate Measurement Characteristic Value
    [HRS_IDX_HR_MEAS_VAL]             =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&heart_rate_meas_uuid, ESP_GATT_PERM_READ,
      HRPS_HT_MEAS_MAX_LEN,0, NULL}},

    // Heart Rate Measurement Characteristic - Client Characteristic Configuration Descriptor
    [HRS_IDX_HR_MEAS_NTF_CFG]        =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
      sizeof(uint16_t),sizeof(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}},

    // Body Sensor Location Characteristic Declaration
    [HRS_IDX_BOBY_SENSOR_LOC_CHAR]  =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
      CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read}},

    // Body Sensor Location Characteristic Value
    [HRS_IDX_BOBY_SENSOR_LOC_VAL]   =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&body_sensor_location_uuid, ESP_GATT_PERM_READ_ENCRYPTED,
      sizeof(uint8_t), sizeof(body_sensor_loc_val), (uint8_t *)body_sensor_loc_val}},

    // Heart Rate Control Point Characteristic Declaration
    [HRS_IDX_HR_CTNL_PT_CHAR]          =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
      CHAR_DECLARATION_SIZE,CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write}},

    // Heart Rate Control Point Characteristic Value
    [HRS_IDX_HR_CTNL_PT_VAL]             =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&heart_rate_ctrl_point, ESP_GATT_PERM_WRITE_ENCRYPTED|ESP_GATT_PERM_READ_ENCRYPTED,
      sizeof(uint8_t), sizeof(heart_ctrl_point), (uint8_t *)heart_ctrl_point}},
};

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

........

先把建立连接之前的GATT状态机搞清楚

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

3.1 创建服务 creating services

在触发ESP_GATTS_REG_EVT时,除了创建表还可以创建服务S,调用esp_ble_gatts_create_service来创建服务S。

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

作用:创建一个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 的句柄数总和

查看参数service_id的类型

/// UUID type
typedef struct {
#define ESP_UUID_LEN_16     2
#define ESP_UUID_LEN_32     4
#define ESP_UUID_LEN_128    16
    uint16_t len;							/*!< UUID length, 16bit, 32bit or 128bit */
    union {
        uint16_t    uuid16;
        uint32_t    uuid32;
        uint8_t     uuid128[ESP_UUID_LEN_128];
    } uuid;									/*!< UUID */
} __attribute__((packed)) esp_bt_uuid_t;
/**
 * @brief Gatt id, include uuid and instance id
 */
typedef struct {
    esp_bt_uuid_t   uuid;                   /*!< UUID */
    uint8_t         inst_id;                /*!< Instance id */
} __attribute__((packed)) esp_gatt_id_t;
/**
 * @brief Gatt service id, include id
 *        (uuid and instance id) and primary flag
 */
typedef struct {
    esp_gatt_id_t   id;                     /*!< Gatt id, include uuid and instance */
    bool            is_primary;             /*!< This service is primary or not */
} __attribute__((packed)) esp_gatt_srvc_id_t;

服务ID包括

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

id参数UUID的信息(包括uuid 和实例instance) ---------uuid是UUID的信息包括UUID的长度(16bit/32bit/128bit)及UUID具体值。

例如:服务被定义为16bit的UUID的主服务。服务ID以实例ID为0,UUID为0x00FF来初始化。

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

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

 switch (event) {
    case ESP_GATTS_REG_EVT:
         ESP_LOGI(GATTS_TAG, "REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id);
         gl_profile_tab[PROFILE_B_APP_ID].service_id.is_primary = true;
         gl_profile_tab[PROFILE_B_APP_ID].service_id.id.inst_id = 0x00;
         gl_profile_tab[PROFILE_B_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16;
         gl_profile_tab[PROFILE_B_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_B;

         esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_B_APP_ID].service_id, GATTS_NUM_HANDLE_TEST_B);
         break;
…
}

3.2 启动服务并创建Characteristics

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

ESP_GATTS_CREATE_EVT事件中回调函数参数的类型为gatts_create_evt_param(包括操作函数、servic的句柄、服务的id<UUID+其他信息>) 如下所示。

  /**
     * @brief ESP_GATTS_CREATE_EVT
     */
    struct gatts_create_evt_param {
        esp_gatt_status_t status;       /*!< Operation status */
        uint16_t service_handle;        /*!< Service attribute handle */
        esp_gatt_srvc_id_t service_id;  /*!< Service id, include service uuid and other information */
    } create;                           /*!< Gatt server callback param of ESP_GATTS_CREATE_EVT */

esp_err_t esp_ble_gatts_start_service(uint16_t service_handle)

 esp_ble_gatts_start_service()这个函数启动一个服务。

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

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

这样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就被允许。

3.2.1 添加Characteristic Value declaration ATT

当添加成功时,触发ESP_GATTS_ADD_CHAR_EVT事件。

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

参数: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)权限;

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尚未明确指出。

示例:

gl_profile_tab[PROFILE_A_APP_ID].service_handle = param->create.service_handle;
gl_profile_tab[PROFILE_A_APP_ID].char_uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[PROFILE_A_APP_ID].char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_TEST_A;   
esp_ble_gatts_add_char(gl_profile_tab[PROFILE_A_APP_ID].service_handle,  
                            &gl_profile_tab[PROFILE_A_APP_ID].char_uuid,  
                            ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,  
                            a_property,  
                            &gatts_demo_char1_val,  
                            NULL);

 characteristic value------这个属性ATT怎么添加入Characteristic中,看gatts_demo_char1_val的具体部分

/**
  * @brief set the attribute value type
  */
typedef struct
{
    uint16_t attr_max_len;                      /*!<  attribute max value length */     
    uint16_t attr_len;                          /*!<  attribute current value length */ 
    uint8_t  *attr_value;                       /*!<  the pointer to attribute value */
} esp_attr_value_t;

uint8_t char1_str[] = {0x11,0x22,0x33};
esp_attr_value_t gatts_demo_char1_val = 
{
    .attr_max_len = GATTS_DEMO_CHAR_VAL_LEN_MAX,
    .attr_len     = sizeof(char1_str),
    .attr_value   = char1_str,
};

 在这个结构体里面包括了属性值最大长度、当前长度、当前值。由这个来指定 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)

    /**
     * @brief ESP_GATTS_ADD_CHAR_EVT
     */
    struct gatts_add_char_evt_param {
        esp_gatt_status_t status;       /*!< Operation status */
        uint16_t attr_handle;           /*!< Characteristic attribute handle */
        uint16_t service_handle;        /*!< Service attribute handle */
        esp_bt_uuid_t char_uuid;        /*!< Characteristic uuid */
    } add_char;                         /*!< Gatt server callback param of ESP_GATTS_ADD_CHAR_EVT */

还可以通过调用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() 源码,进行分析 

esp_err_t esp_ble_gatts_get_attr_value(uint16_t attr_handle, uint16_t *length, const uint8_t **value)
{
    if (attr_handle == ESP_GATT_ILLEGAL_HANDLE) {
        return ESP_FAIL;
    }
    btc_gatts_get_attr_value(attr_handle, length, (uint8_t **)value);
    return ESP_OK;
}
void btc_gatts_get_attr_value(uint16_t attr_handle, uint16_t *length, uint8_t **value)
{
    
    BTA_GetAttributeValue(attr_handle, length, value);
}
void BTA_GetAttributeValue(UINT16 attr_handle, UINT16 *length, UINT8 **value)
{
    bta_gatts_get_attr_value(attr_handle, length, value);
}
void bta_gatts_get_attr_value(UINT16 attr_handle, UINT16 *length, UINT8 **value)
{
    GATTS_GetAttributeValue(attr_handle, length, value);
}
/*******************************************************************************
**
** Function         GATTS_GetAttributeValue
**
** Description      This function sends to set the attribute value .
**
** Parameter        attr_handle: the attribute handle
**                  length:the attribute value length in the database
**                  value: the attribute value out put
**
** Returns          GATT_SUCCESS if successfully sent; otherwise error code.
**
*******************************************************************************/
tGATT_STATUS GATTS_GetAttributeValue(UINT16 attr_handle, UINT16 *length, UINT8 **value)
{
     tGATT_STATUS status;
     tGATT_HDL_LIST_ELEM  *p_decl;

     GATT_TRACE_DEBUG("GATTS_GetAttributeValue: attr_handle: %u\n",
                    attr_handle);

     if ((p_decl = gatt_find_hdl_buffer_by_attr_handle(attr_handle)) == NULL) {
         GATT_TRACE_ERROR("Service not created\n"); 
         return GATT_INVALID_HANDLE;
     }

     status =  gatts_get_attribute_value(&p_decl->svc_db, attr_handle, length, value);
     return status;
}

关于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的例子

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

 看看esp_ble_gatts_add_char_descr()这个函数的原型

esp_err_t esp_ble_gatts_add_char_descr (uint16_t service_handle,
                                        esp_bt_uuid_t   *descr_uuid,
                                        esp_gatt_perm_t perm, esp_attr_value_t *char_descr_val,
                                        esp_attr_control_t *control);

参数: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事件

/**
     * @brief ESP_GATTS_CONNECT_EVT
     */
    struct gatts_connect_evt_param {
        uint16_t conn_id;               /*!< Connection id */
        esp_bd_addr_t remote_bda;       /*!< Remote bluetooth device address */
    } connect;                          /*!< Gatt server callback param of ESP_GATTS_CONNECT_EVT */
/// Bluetooth address length
#define ESP_BD_ADDR_LEN     6

/// Bluetooth device address
typedef uint8_t esp_bd_addr_t[ESP_BD_ADDR_LEN];

ESP_GATTS_CONNECT_EVT事件回调函数param参数包括连接ID以及远端蓝牙设备地址。调用esp_ble_update_conn_params(&conn_params)来更新连接参数。这个函数只有在连接上以后才可以调用。

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

 参数连接更新参数(蓝牙设备地址、最小连接间隔、最大连接间隔、连接数量、LE LINK超时)。

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

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

4.1 连接后MTU大小确定

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

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

    /**
     * @brief ESP_GATTS_MTU_EVT
     */
    struct gatts_mtu_evt_param {
        uint16_t conn_id;               /*!< Connection id */
        uint16_t mtu;                   /*!< MTU size */
    } mtu;                              /*!< Gatt server callback param of ESP_GATTS_MTU_EVT */

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

esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500);

如上所述,设置了MTU的值(经过MTU交换,从而设置一个PDU中最大能够交换的数据量)。例如:主设备发出一个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.2 发送应答数据

esp_err_t esp_ble_gatts_send_response(esp_gatt_if_t gatts_if, uint16_t conn_id, uint32_t trans_id,
                                      esp_gatt_status_t status, esp_gatt_rsp_t *rsp);


        esp_gatt_rsp_t rsp;
        memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
        rsp.attr_value.handle = param->read.handle;
        rsp.attr_value.len = 4;
        rsp.attr_value.value[0] = 0xde;
        rsp.attr_value.value[1] = 0xed;
        rsp.attr_value.value[2] = 0xbe;
        rsp.attr_value.value[3] = 0xef;
        esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id,
                                    ESP_GATT_OK, &rsp);

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

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

conn_id-----连接ID

trans_id-----传输ID

status----应答状态

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

查看GATT 读应答结构

#define ESP_GATT_MAX_ATTR_LEN   600 //as same as GATT_MAX_ATTR_LEN
/// Gatt attribute value 
typedef struct {
    uint8_t           value[ESP_GATT_MAX_ATTR_LEN];         /*!< Gatt attribute value */
    uint16_t          handle;                               /*!< Gatt attribute handle */
    uint16_t          offset;                               /*!< Gatt attribute value offset */
    uint16_t          len;                                  /*!< Gatt attribute value length */
    uint8_t           auth_req;                             /*!< Gatt authentication request */
} esp_gatt_value_t;
/// GATT remote read request response type
typedef union {
    esp_gatt_value_t attr_value;                            /*!< Gatt attribute structure */
    uint16_t            handle;                             /*!< Gatt attribute handle */
} esp_gatt_rsp_t;

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

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


作者: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.getService来获取BluetoothGattService
  7. 接着通过BluetoothGattService.getCharacteristic获取BluetoothGattCharacteristic
  8. 然后通过BluetoothGattCharacteristic.getDescriptor获取BluetoothGattDescriptor

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

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

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


    /**
     * @brief ESP_GATTS_WRITE_EVT
     */
    struct gatts_write_evt_param {
        uint16_t conn_id;               /*!< Connection id */
        uint32_t trans_id;              /*!< Transfer id */
        esp_bd_addr_t bda;              /*!< The bluetooth device address which been written */
        uint16_t handle;                /*!< The attribute handle */
        uint16_t offset;                /*!< Offset of the value, if the value is too long */
        bool need_rsp;                  /*!< The write operation need to do response */
        bool is_prep;                   /*!< This write operation is prepare write */
        uint16_t len;                   /*!< The write attribute value length */
        uint8_t *value;                 /*!< The write attribute value */
    } write;                            /*!< Gatt server callback param of ESP_GATTS_WRITE_EVT */

    /**
     * @brief ESP_GATTS_EXEC_WRITE_EVT
     */
    struct gatts_exec_write_evt_param {
        uint16_t conn_id;               /*!< Connection id */
        uint32_t trans_id;              /*!< Transfer id */
        esp_bd_addr_t bda;              /*!< The bluetooth device address which been written */
#define ESP_GATT_PREP_WRITE_CANCEL 0x00 /*!< Prepare write flag to indicate cancel prepare write */
#define ESP_GATT_PREP_WRITE_EXEC   0x01 /*!< Prepare write flag to indicate execute prepare write */
        uint8_t exec_write_flag;        /*!< Execute write flag */
    } exec_write; 

参考例子打印

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

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

6.结束语

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

猜你喜欢

转载自blog.csdn.net/zhejfl/article/details/85136102