当前位置:   article > 正文

ESP32学习笔记十九之BLE协议GAP&GATT_remove the bond device

remove the bond device

GAP

GAP全名是Generic Access Profile,通用访问配置文件,它定义了蓝牙设备的角色,中心和外设,并且控制他们的连接和广播数据。广播数据有两种方式:广播数据和扫描回复数据,数据包大小最长为31字节,其中广播数据方式是必需的。广播数据就是蓝牙设备自己定时广播数据出来,让周围环境的其它设备可以扫描到它,知道它的存在,是外设主动完成的。而扫描回复数据是可选的,它会根据需要响应请求,比如收到中心设备连接请求,会响应这个请求。这个协议决定了蓝牙设备的交互方式,比如iBeacon只能向外广播消息,而小米手环可以与中心设备双向通信。

GAP的工作流程如下图:

大部分情况外设广播自己来让中心设备发现自己并建立连接,然后GATT(后面讲到)就可以用来进行更多的数据交换,这种方式是独占的,就是建立连接了以后外设就不再广播了,只跟连接上的中心通信。但也有一种是不需要连接的,比如上面说的Beancon设备,它只需要向外广播数据,只要在一定的范围内,所有的中心设备都可以收到数据,不需要指定跟具体某个中心建立连接,他们的网络拓扑图如下:

GATT

GATT的介绍

GATT是用Attribute Protocol(属性协议)定义的一个Service服务框架。这个框架定义了Service以及他们的Characteristics的格式和规程。规程就是定义了包括发现、读、写、通知、指示以及配置广播的characteristics。

 一个attribute由三个元素组成:

  1. 16 bit的句柄(唯一性,用于区分和查找不同的attribute)
  2. UUID(ATT本身不定义,留给GATT来定义)
  3. 一个定长的值value(配合UUID使用,由GATT来决定这个UUID的意义和数据)

GATT是建立在GAP基础之上发挥作用的,就是两个BLE设备只有通过GAP建立连接之后才能用GATT进行通信。上面说了扫描回复数据交互方式是独占的,所以GATT通信只允许是一个外设和一个中心连接。如果两个外设想要通信,唯一的方式就是建立GATT连接,通过中心来中转。

一个外设只能跟一个中心建立连接(独占的),而一个中心可以同时连接多个外设(一个手机可以同时连接多个BLE设备)

GATT连接网络拓扑图

 GATT通信的双方是C/S关系,外设作为GATT的服务器(Server),中心设备是GATT的客户端(Client),它向Server发起请求。注意,所有的通信都是由主设备(客户端Client)发起,服务器Server响应数据反馈给Client。一旦建立了通信连接,外设会建议中心设备做定时连接(connection interval),这样中心设备就会在每个连接间隔尝试重新连接,检查有没有新的数据。这个只是一个建议,中心设备可能不会严格按照这个时间间隔来执行,例如中心设备正忙于连接其它外设或者资源太忙。

外设与中心数据交换图:

GATT结构由嵌套的Profile、Service、Characteristics组成,如下图:

Service就是一个独立的逻辑项,它包含一个或多个Characteristic,每个Service都由唯一的UUID标识,UUID有16位的有128位的,16位的UUID是官方通过认证的,需要花钱购买,128位的可以随便自己定义。 

Characteristic是GATT中最小的逻辑数据单元,当然它可能包含一组关联的数据;中心可以通过读取service、再进一步读取characteristic来获得具体的数值。

与Service类似,每个 Characteristic 用 16 bit 或者 128 bit 的 UUID 唯一标识。所有数据交互必须通过明确的UUID确定到service和characteristic。

实际上,和 BLE 外设打交道,主要是通过 Characteristic。你可以从 Characteristic 读取数据,也可以往 Characteristic 写数据。这样就实现了双向的通信。所以你可以自己实现一个类似串口(UART)的 Sevice,这个 Service 中包含两个 Characteristic,一个被配置只读的通道(RX),另一个配置为只写的通道(TX)。

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

  1. 配置交换(exchanging configuration) 
  2. 发现一个设备上的服务s和特征s
  3. 读取一个特征值(characteristic value)
  4. 写入一个特征值
  5. 通知一个特征值
  6. 指示一个特征值 

ESP32例程分析

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

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

GATT服务器的架构组织

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

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

如果Characteristic 支持通知(notifications)或指示(indicatons),它就必须是实现CCCD(Client Characteristic  Configuration Descriptor)----这是额外的ATT。描述符有一个句柄和UUID。

  1. struct gatts_profile_inst {
  2. esp_gatts_cb_t gatts_cb;
  3. dd_uint16_t gatts_if;
  4. dd_uint16_t app_id;
  5. dd_uint16_t conn_id;
  6. dd_uint16_t service_handle;
  7. esp_gatt_srvc_id_t service_id;
  8. dd_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. dd_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,这意味着Application Profile还没有连接任何客户端。

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

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

esp_ble_gatts_app_register(PROFILE_A_APP_ID);

GATT回调函数的注册以及分析

 下面是GATT回调函数的注册函数esp_ble_gatts_register_callback:

esp_err_t esp_ble_gatts_register_callback(esp_gatts_cb_t callback);

对于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  这是一个枚举类型,表示调用该回调函数时的事件(或蓝牙的状态)

  1. /// GAP BLE callback event type
  2. typedef enum {
  3. ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT = 0, /*!< When advertising data set complete, the event comes */
  4. ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT, /*!< When scan response data set complete, the event comes */
  5. ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT, /*!< When scan parameters set complete, the event comes */
  6. ESP_GAP_BLE_SCAN_RESULT_EVT, /*!< When one scan result ready, the event comes each time */
  7. ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT, /*!< When raw advertising data set complete, the event comes */
  8. ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT, /*!< When raw advertising data set complete, the event comes */
  9. ESP_GAP_BLE_ADV_START_COMPLETE_EVT, /*!< When start advertising complete, the event comes */
  10. ESP_GAP_BLE_SCAN_START_COMPLETE_EVT, /*!< When start scan complete, the event comes */
  11. ESP_GAP_BLE_AUTH_CMPL_EVT, /* Authentication complete indication. */
  12. ESP_GAP_BLE_KEY_EVT, /* BLE key event for peer device keys */
  13. ESP_GAP_BLE_SEC_REQ_EVT, /* BLE security request */
  14. ESP_GAP_BLE_PASSKEY_NOTIF_EVT, /* passkey notification event */
  15. ESP_GAP_BLE_PASSKEY_REQ_EVT, /* passkey request event */
  16. ESP_GAP_BLE_OOB_REQ_EVT, /* OOB request event */
  17. ESP_GAP_BLE_LOCAL_IR_EVT, /* BLE local IR event */
  18. ESP_GAP_BLE_LOCAL_ER_EVT, /* BLE local ER event */
  19. ESP_GAP_BLE_NC_REQ_EVT, /* Numeric Comparison request event */
  20. ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT, /*!< When stop adv complete, the event comes */
  21. ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT, /*!< When stop scan complete, the event comes */
  22. ESP_GAP_BLE_SET_STATIC_RAND_ADDR_EVT, /*!< When set the static rand address complete, the event comes */
  23. ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT, /*!< When update connection parameters complete, the event comes */
  24. ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT, /*!< When set pkt length complete, the event comes */
  25. ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT, /*!< When Enable/disable privacy on the local device complete, the event comes */
  26. ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT, /*!< When remove the bond device complete, the event comes */
  27. ESP_GAP_BLE_CLEAR_BOND_DEV_COMPLETE_EVT, /*!< When clear the bond device clear complete, the event comes */
  28. ESP_GAP_BLE_GET_BOND_DEV_COMPLETE_EVT, /*!< When get the bond device list complete, the event comes */
  29. ESP_GAP_BLE_READ_RSSI_COMPLETE_EVT, /*!< When read the rssi complete, the event comes */
  30. ESP_GAP_BLE_UPDATE_WHITELIST_COMPLETE_EVT, /*!< When add or remove whitelist complete, the event comes */
  31. ESP_GAP_BLE_UPDATE_DUPLICATE_EXCEPTIONAL_LIST_COMPLETE_EVT, /*!< When update duplicate exceptional list complete, the event comes */
  32. ESP_GAP_BLE_EVT_MAX,
  33. } esp_gap_ble_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_ble_gatts_app_register(1),触发回调函数的ESP_GATTS_REG_EVT时,在其中完成以下操作:

  1. 完成对每个profile 的gatts_if 的注册
  1. if (event == ESP_GATTS_REG_EVT) {
  2. if (param->reg.status == ESP_GATT_OK) {
  3. gl_profile_tab[param->reg.app_id].gatts_if = gatts_if;
  4. } else {
  5. ddlog_warn("Reg app failed, app_id %04x, status %d\n",
  6. param->reg.app_id,
  7. param->reg.status);
  8. return;
  9. }
  10. }

GATT Server状态机的一般过程为:

没有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()
  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堆栈自动回复吸入\读取操作应答

  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;

建立连接之前的GATT状态机

  • 创建服务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 的句柄数总和

  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) ---------uuid是UUID的信息包括UUID的长度(16bit/32bit/128bit)及UUID具体值。

例如:服务被定义为16bit的UUID的主服务。服务ID以实例ID为0,UUID为0x00FF来初始化。
服务实例ID是用来区分在同一个Profile中具有相同UUID的多个服务。Application Profile中拥有相同UUID的两个服务,需要用不同的实例ID来引用。

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

启动服务并创建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_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就被允许

 添加Characteristic Value declaration ATT

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

添加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

 

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

还可在ESP_GATTS_ADD_CHAR_EVT事件的回调函数中,给characteristic添加characteristic description ATT。 

下面是添加char_desc的例子

  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的状态机转变过程:

手机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;

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

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

  • 连接后触发的连接回调函数
  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. }
  • 连接后MTU大小确认

 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不能传输大数据的原因所在。

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

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

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

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

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

发送应答数据

  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;

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

  • 手机端操作 

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

  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权限才可以成功调用。
  12. wx.writeBLECharacteristicValue({
  13. deviceId: that.data.deviceId,//设备deviceId
  14. serviceId: that.data.service_id,//设备service_id
  15. characteristicId: that.data.write_id,//设备write特征值
  16. value: buffer,//写入数据
  17. success: function (res) {
  18. console.log('发送数据:', res.errMsg)
  19. }
  20. });
  • 读特征值
  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. }
  10. wx.readBLECharacteristicValue({
  11. // 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
  12. deviceId,
  13. // 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
  14. serviceId,
  15. // 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
  16. characteristicId,
  17. success(res) {
  18. console.log('readBLECharacteristicValue:', res.errCode)
  19. }
  20. })
  •  小程序上低功耗蓝牙一般操作流程
  1. 初始化蓝牙模块(wx.openBluetoothAdapter)
  2. 开始搜索蓝牙外围设备(耗资源)(wx.startBluetoothDevicesDiscovery)
  3. 获取在蓝牙模块生效期间所有已发现的蓝牙设备(包括已经和本机处于连接状态的设备)(wx.getBluetoothDevices)
  4. 监听找到的新设备的事件(wx.onBluetoothDeviceFound(callback))
  5. 连接低功耗蓝牙设备(wx.createBLEConnection())
  6. 获取蓝牙设备所有services(wx.getBLEDeviceServices())
  7. 获取蓝牙设备某个服务中所有Characteristic(特征)(wx.getBLEDeviceCharacteristics)
  8. 启动低功耗蓝牙设备特征的值变化时的notify功能(wx.notifyBLECharacteristicValueChange())
  9. 写入wx.writeBLECharactericValue()

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

设备端的操作

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

使能通知

使能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; 值

读写数据 

看看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
  7. 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);
  8. if (!param->write.is_prep){
  9. ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len);
  10. esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len);
  11. example_write_event_env(gatts_if, &a_prepare_write_env, param);
  12. break;
  13. }

 深入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,将信息发送出去。

先把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的值。

 

接下来看看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;

 Characteristic结构

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

其他注意事项

权限对于数据交换,通常设置有读Characteristic写Characteristic

Read/Write读/写-----一次会同时出发读和写两个回调事件;

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

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

参考文献

https://blog.csdn.net/zhejfl/article/details/85136102

https://www.cnblogs.com/smart-mutouren/p/5937990.html

一分钟读懂低功耗蓝牙(BLE)MTU交换数据包:https://blog.csdn.net/viewtoolsz/article/details/76177465

BLE低功耗蓝牙详解,解决读写、通知失败问题:https://www.jianshu.com/p/f8130a0bfd94

https://blog.csdn.net/NBDR_YL/article/details/99546718

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/218101
推荐阅读
相关标签
  

闽ICP备14008679号