240619-蓝牙开发¶
ESP32-S3 的 ESP-Bluedroid 仅支持低功耗蓝牙, 不支持经典蓝牙
1 基础概念¶
参考博客: https://blog.csdn.net/qq_36347513/article/details/118567281
1.1 GATT¶
基于 Attribute Protocal(属性协议)定义的一个 service(服务)框架。这个框架定义了 Services 以及它们的 Characteristics 的格式和规程。规程就是定义了包括发现、读、写、通知、指示以及配置广播的 characteristics。
框架还定义了在蓝牙通信中的两个角色, 客户端以及服务器,一般终端设备的 ble 工作在 server 模式。
当两个设备建立了连接后即表示 GAP 协议管理的广播过程已经结束。
1.2 Profile¶
一般理解做规范, 分成标准以及非标准, 或者公有以及私有规范。
1.3 Service¶
理解成服务,存在于 Profile 下。每个 Service 包含有若干个 Characteristic,即特征。
每个特征可以理解为一个标签,是实际想要传输的信息载体。
1.4 Characteristic¶
若特征值支持通知以及指示功能,则该特征值需要实现一个额外的 ATT——CCCD(Client Characteristic Configuration Descriptor)
1.5 UUID¶
所有的服务以及特征都需要有唯一一个 UUID 来标识。
2 基于../ble_gatt_server_service_table 例程的二次开发¶
官方 GATT_SERVER_API 参考: https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32s3/api-reference/bluetooth/esp_gatts.html
2.1 APP Profiles¶
demo 中只定义了一个用于测量心率的 Profile,开头的宏定义如下:
#define PROFILE_NUM 1
#define PROFILE_APP_IDX 0
#define ESP_APP_ID 0x55
其中 ESP_APP_ID 是由用户定义的一个 ID,用于区别不同的 profile。
在 IDF 中 profile 被保存在一个类型为 gatts_profile_inst
的数组中,并需要指定 gatts_cb
以及 gatts_if
,如下所示:
static struct gatts_profile_inst heart_rate_profile_tab[HEART_PROFILE_NUM] = {
[HEART_PROFILE_APP_IDX] = {
.gatts_cb = gatts_profile_event_handler,
.gatts_if = ESP_GATT_IF_NONE,
},
};
2.2 设置广播数据¶
注册应用程序事件是在程序生命周期中触发的第一个事件。
此示例使用此事件在注册概要文件事件处理程序时配置广播参数。用于实现此目的的函数有:
esp_ble_gap_set_device_name()
: 用于设置被广播的设备名称。esp_ble_gap_config_adv_data()
: 用于配置标准广播数据。
后者接收一个指向 esp_ble_adv_data_t
类型结构体的指针,该结构体定义了广播数据包的具体负载。此处需要注意,一般广播数据包的具体负载不能超过 31 个字节,否则将会被堆栈截断后面的消息。
一般为了解决这个问题,较长的参数可以存储在扫描响应中。如果要采取扫描响应的话,可以新建一个 esp_ble_adv_data_t
类型的结构体,并将 set_scan_rsp
设置为 true
后使用相同的函数进行配置。
以上的操作被放在 gatts_profile_event_handler
这个函数中的 ESP_GATTS_REG_EVT
事件中,一旦设置完广播数据之后,将会触发 ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT
事件,并交由 gap_event_handler
函数进行管理,若也设置了扫描响应,则 ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT
事件也会被触发。
2.3 GAP 事件(gap_event_handler
)¶
接着上面,如果广播数据两个事件中的任意一个事件(设置广播数据或设置扫描响应完成)被触发,则会调用 esp_ble_gap_start_advertising
函数来开始广播,该函数需要一个 esp_ble_adv_params_t
类型的广播参数。
该参数指定了开启广播所需的各种参数,例如广播间隔、广播类型、广播信道以及广播过滤策略等:
/// 广播参数
typedef struct {
uint16_t adv_int_min; /*!< 未定向和低占空比定向广播的最小广播间隔。
范围:0x0020到0x4000
默认值:N = 0x0800(1.28秒)
时间= N * 0.625毫秒
时间范围:20毫秒到10.24秒 */
uint16_t adv_int_max; /*!< 未定向和低占空比定向广播的最大广播间隔。
范围:0x0020到0x4000
默认值:N = 0x0800(1.28秒)
时间= N * 0.625毫秒
时间范围:20毫秒到10.24秒 */
esp_ble_adv_type_t adv_type; /*!< 广播类型 */
esp_ble_addr_type_t own_addr_type; /*!< 所有者蓝牙设备地址类型 */
esp_bd_addr_t peer_addr; /*!< 对等方设备的蓝牙设备地址 */
esp_ble_addr_type_t peer_addr_type; /*!< 对等方设备的蓝牙设备地址类型 */
esp_ble_adv_channel_t channel_map; /*!< 广播通道映射 */
esp_ble_adv_filter_t adv_filter_policy; /*!< 广播过滤策略 */
} esp_ble_adv_params_t;
例程中配置的几个重要参数:
adv_type
-ADV_ IND
类型,通用类型,不针对特定的中心设备,广播服务器为可连接的。own_addr_type
-BLE_ADDR_TYPE_PUBLIC
公共地址类型channel_map
-ADV_CHNL_ALL
使用所有通道adv_filter_policy
-ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY
允许来自任何中心设备的扫描和连接请求
一旦广播成功开始后,esp_ble_gap_start_advertising
函数将会触发 ESP_GAP_BLE_ADV_START_COMPLETE_EVT
事件,例程中在这个事件中只是单纯检查广播状态,并打印消息。
2.4 GATT 事件¶
应用的 Profile
注册操作在例程中的 ble_server_task_init
初始化流程中通过调用 esp_ble_gatts_app_register
函数实现,该函数使用一个 APP ID
用于区分各种应用层的回调。
调用 esp_ble_gatts_app_register
函数后, ESP_GATTS_REG_EVT
事件将会被触发,该事件会被 gatts_event_handler
函数捕获,然后将生成的 gatt_if
(interface)接口存放在 profile_tab
中,也就是一开始初始化的时候设置为 ESP_GATT_IF_NONE
的 Profile
。
本例中定义的应用程序只有一个 Profile
,因此在将生成的接口存放过去之后,也就相当于将其转发到相应的 gatts_profile_event_handler
函数中,开始对这个 Profile
触发的事件进行处理了。
2.5 服务表¶
enum
{
HRS_IDX_SVC,
HRS_IDX_HR_MEAS_CHAR,
HRS_IDX_HR_MEAS_VAL,
HRS_IDX_HR_MEAS_NTF_CFG,
HRS_IDX_BOBY_SENSOR_LOC_CHAR,
HRS_IDX_BOBY_SENSOR_LOC_VAL,
HRS_IDX_HR_CTNL_PT_CHAR,
HRS_IDX_HR_CTNL_PT_VAL,
HRS_IDX_NB,
};
这个枚举元素定义了心率服务的一系列索引,每个服务和特征都有一个唯一的索引,这些索引用于在代码中应用特定的服务和特征。
通过这种方式,可以方便地管理和操作 GATT_server 的特性。
HRS for Heart Rate Service.
BTW,这个服务表中的枚举类型为自己定义的东西,例程只是给出了一个心率测量应用场景下的样例。walkthrough 文档中的代码似乎是比较早的版本了,现在的实际代码文件中给出的例子是这样的,通用性更强一点:
enum
{
IDX_SVC,
IDX_CHAR_A,
IDX_CHAR_VAL_A,
IDX_CHAR_CFG_A,
IDX_CHAR_B,
IDX_CHAR_VAL_B,
IDX_CHAR_C,
IDX_CHAR_VAL_C,
HRS_IDX_NB,
};
2.6 通过属性表(Attribute Table)创建 Services 和 Characteristics¶
注册 Services 和 Characteristics 的事件通过使用 esp_ble_gatts_create_attr_tab
函数来创建属性表,该函数需要一个类型为 esp_gatts_attr_db_t
的参数。该参数由枚举值进行索引,即上节定义的服务表,包括两个成员:
typedef struct {
esp_attr_control_t attr_control;
esp_attr_desc_t att_desc;
} esp_gatts_attr_db_t;
第一个是自动响应参数,有两个可选:
ESP_GATT_AUTO_RSP
- 允许 BLE 堆栈对于读取或写入事件到达时自动发送响应消息ESP_GATT_RSP_BY_APP
- 使用esp_ble_gatts_send_response
函数进行手动响应消息
第二个是属性类型:
uint16_t uuid_length; /*!< UUID长度 */
uint8_t *uuid_p; /*!< UUID值 */
uint16_t perm; /*!< 属性权限 */
uint16_t max_length; /*!< 元素的最大长度 */
uint16_t length; /*!< 元素的当前长度 */
uint8_t *value; /*!< 元素值数组*/
通过函数 esp_ble_gatts_create_attr_tab
创建完后将会触发 ESP_GATTS_CREAT_ATTR_TAB_EVT
事件,用于打印各种信息,检查创建的属性表大小是否等于 HRS_IDX_NB
,在检查完成之后将会将属性表的句柄复制到 heart_rate_handle_table
中,以便用于传递,以及用于 app 的上层来进行不同的操作。通过这些属性表的句柄,可以用于知道当前正在读取/写入哪些特征。
最后通过 esp_ble_gatts_start_service
函数开始服务。
2.7 添加新的 Service¶
- 宏定义处添加新 Service 实例的 ID,如:
#define SVC_INST_ID1 1
- 添加新的 Service_handle_table,如:
uint16_t <srvc>_handle_table[<svc>_IDX_NB];
- 定义新的 Service 与 Characteristic 的 UUID,如:
static const uint16_t <srvc>_SERVICE_UUID_DM = 0x00EE; static const uint16_t GATTS_CHAR_UUID_<srvc>_<value> = 0xEE01;
- 定义新的 Service database,如:
/* 特征值以及UUID名可能有出入 */ static const esp_gatts_attr_db_t gatt_db2[DM_IDX_NB] = { [IDX_SVC2] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ, sizeof(uint16_t), sizeof(GATTS_SERVICE_UUID_DM), (uint8_t *)&GATTS_SERVICE_UUID_DM}}, [IDX_CHAR_A2] = {{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_notify}}, [IDX_CHAR_VAL_A2] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_DM_A, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value2), (uint8_t *)char_value2}}, };
esp_gatts_attr_db_t
的成员定义参考如下:
/* esp_gatts_attr_db_t */
esp_attr_control_t attr_control;
esp_attr_desc_t att_desc;
/* esp_attr_desc_t */
uint16_t uuid_length; /*!< UUID长度 */
uint8_t *uuid_p; /*!< UUID值 */
uint16_t perm; /*!< 属性权限 */
uint16_t max_length; /*!< 元素的最大长度 */
uint16_t length; /*!< 元素的当前长度 */
uint8_t *value; /*!< 元素值数组*/
其中一个 service 需要包含:一个主服务声明、特征值声明、特征值。
除此之外,如果服务属性有 Notify 属性,还需要包含一个 CCCD(客户端特征值配置描述符)。
以上只有特征值的 UUID 是用户自己定义的,其他三个可以参考蓝牙规范,例程中使用了如下定义:
static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
static const uint16_t character_client_config_uuid = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
关于服务以及特征值的读写属性,注意如下逻辑:
不管是服务声明、特征值声明还是特征值本身,都需要设置读写权限,这里的读写权限是指服务端的权限,即服务端是否允许客户端读写服务声明、特征值声明还是特征值。
而对于特征值本身的属性来说,需要在特征值声明中设置值以声明是否包含读写属性以及 notify/indicate 属性。
也就是注意区分以下两组概念:
/* file: esp_gatt_defs.h */
/* relate to BTA_GATT_PERM_xxx in bta/bta_gatt_api.h */
/**
* @brief Attribute permissions
*/
#define ESP_GATT_PERM_READ (1 << 0) /* bit 0 - 0x0001 */ /* relate to BTA_GATT_PERM_READ in bta/bta_gatt_api.h */
#define ESP_GATT_PERM_READ_ENCRYPTED (1 << 1) /* bit 1 - 0x0002 */ /* relate to BTA_GATT_PERM_READ_ENCRYPTED in bta/bta_gatt_api.h */
#define ESP_GATT_PERM_READ_ENC_MITM (1 << 2) /* bit 2 - 0x0004 */ /* relate to BTA_GATT_PERM_READ_ENC_MITM in bta/bta_gatt_api.h */
#define ESP_GATT_PERM_WRITE (1 << 4) /* bit 4 - 0x0010 */ /* relate to BTA_GATT_PERM_WRITE in bta/bta_gatt_api.h */
#define ESP_GATT_PERM_WRITE_ENCRYPTED (1 << 5) /* bit 5 - 0x0020 */ /* relate to BTA_GATT_PERM_WRITE_ENCRYPTED in bta/bta_gatt_api.h */
#define ESP_GATT_PERM_WRITE_ENC_MITM (1 << 6) /* bit 6 - 0x0040 */ /* relate to BTA_GATT_PERM_WRITE_ENC_MITM in bta/bta_gatt_api.h */
#define ESP_GATT_PERM_WRITE_SIGNED (1 << 7) /* bit 7 - 0x0080 */ /* relate to BTA_GATT_PERM_WRITE_SIGNED in bta/bta_gatt_api.h */
#define ESP_GATT_PERM_WRITE_SIGNED_MITM (1 << 8) /* bit 8 - 0x0100 */ /* relate to BTA_GATT_PERM_WRITE_SIGNED_MITM in bta/bta_gatt_api.h */
#define ESP_GATT_PERM_READ_AUTHORIZATION (1 << 9) /* bit 9 - 0x0200 */
#define ESP_GATT_PERM_WRITE_AUTHORIZATION (1 << 10) /* bit 10 - 0x0400 */
#define ESP_GATT_PERM_ENCRYPT_KEY_SIZE(keysize) (((keysize - 6) & 0xF) << 12) /* bit 12:15 - 0xF000 */
typedef uint16_t esp_gatt_perm_t;
/* relate to BTA_GATT_CHAR_PROP_BIT_xxx in bta/bta_gatt_api.h */
/* definition of characteristic properties */
#define ESP_GATT_CHAR_PROP_BIT_BROADCAST (1 << 0) /* 0x01 */ /* relate to BTA_GATT_CHAR_PROP_BIT_BROADCAST in bta/bta_gatt_api.h */
#define ESP_GATT_CHAR_PROP_BIT_READ (1 << 1) /* 0x02 */ /* relate to BTA_GATT_CHAR_PROP_BIT_READ in bta/bta_gatt_api.h */
#define ESP_GATT_CHAR_PROP_BIT_WRITE_NR (1 << 2) /* 0x04 */ /* relate to BTA_GATT_CHAR_PROP_BIT_WRITE_NR in bta/bta_gatt_api.h */
#define ESP_GATT_CHAR_PROP_BIT_WRITE (1 << 3) /* 0x08 */ /* relate to BTA_GATT_CHAR_PROP_BIT_WRITE in bta/bta_gatt_api.h */
#define ESP_GATT_CHAR_PROP_BIT_NOTIFY (1 << 4) /* 0x10 */ /* relate to BTA_GATT_CHAR_PROP_BIT_NOTIFY in bta/bta_gatt_api.h */
#define ESP_GATT_CHAR_PROP_BIT_INDICATE (1 << 5) /* 0x20 */ /* relate to BTA_GATT_CHAR_PROP_BIT_INDICATE in bta/bta_gatt_api.h */
#define ESP_GATT_CHAR_PROP_BIT_AUTH (1 << 6) /* 0x40 */ /* relate to BTA_GATT_CHAR_PROP_BIT_AUTH in bta/bta_gatt_api.h */
#define ESP_GATT_CHAR_PROP_BIT_EXT_PROP (1 << 7) /* 0x80 */ /* relate to BTA_GATT_CHAR_PROP_BIT_EXT_PROP in bta/bta_gatt_api.h */
typedef uint8_t esp_gatt_char_prop_t;
- 于头文件中定义新的属性表,用于给上述第二点的 handle_table 索引,以便后续开发中读写某 Service 的某特征值,如:
/* 设备管理相关service表 */
enum
{
IDX_SVC2,
IDX_CHAR_A2,
IDX_CHAR_VAL_A2,
DM_IDX_NB,
};
- 修改
gatts_profile_event_handler
中对于新建 database 的逻辑,主要思路为**创建一个启动一个**,关键修改部分如下:
/* ESP_GATTS_REG_EVT 事件分支 */
esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, LOCK_IDX_NB, SVC_INST_ID);
if (create_attr_ret)
{
ESP_LOGE(GATTS_TABLE_TAG, "create attr table failed, error code = %x", create_attr_ret);
}
首先在 ESP_GATTS_REG_EVT
事件时创建第一个 database,该事件创建成功后,将会触发 ESP_GATTS_CREAT_ATTR_TAB_EVT
事件,此时再创建第二个 database,如下:
ESP_GATTS_REG_EVT
被最开始的esp_ble_gatts_app_register
函数触发
/* ESP_GATTS_CREAT_ATTR_TAB_EVT 事件分支*/
if (create_tab1 == false)
{
if (param->add_attr_tab.status != ESP_GATT_OK)
{
ESP_LOGE(GATTS_TABLE_TAG, "create attribute table failed, error code=0x%x", param->add_attr_tab.status);
}
else if (param->add_attr_tab.num_handle != LOCK_IDX_NB)
{
ESP_LOGE(GATTS_TABLE_TAG, "create attribute table abnormally, num_handle (%d) \
doesn't equal to LOCK_IDX_NB(%d)",
param->add_attr_tab.num_handle, LOCK_IDX_NB);
}
else
{
ESP_LOGI(GATTS_TABLE_TAG, "create attribute table successfully, the number handle = %d", param->add_attr_tab.num_handle);
memcpy(lock_handle_table, param->add_attr_tab.handles, sizeof(lock_handle_table));
esp_ble_gatts_start_service(lock_handle_table[IDX_SVC]);
}
}
else
{
if (param->add_attr_tab.status != ESP_GATT_OK)
{
ESP_LOGE(GATTS_TABLE_TAG, "create attribute table failed, error code=0x%x", param->add_attr_tab.status);
}
else if (param->add_attr_tab.num_handle != DM_IDX_NB)
{
ESP_LOGE(GATTS_TABLE_TAG, "create attribute table abnormally, num_handle (%d) \
doesn't equal to HRS_IDX_NB(%d)",
param->add_attr_tab.num_handle, DM_IDX_NB);
}
else
{
ESP_LOGI(GATTS_TABLE_TAG, "create attribute table successfully, the number handle = %d\n", param->add_attr_tab.num_handle);
memcpy(device_manage_handle_table, param->add_attr_tab.handles, sizeof(device_manage_handle_table));
esp_ble_gatts_start_service(device_manage_handle_table[IDX_SVC2]);
}
}
break;
相较于原始例程,此处添加了一个 create_tab1
的标志位,用于判断是否已经创建了第一个 database,如果没有则创建第一个,如果已经创建了,则创建第二个,以后有多个服务则以此类推。
在检查完创建的属性表大小是否等于 <srvc>_IDX_NB
之后,将会将属性表的句柄复制到 <srvc>_handle_table
中,并调用 esp_ble_gatts_start_service
函数开始服务,触发 ESP_GATTS_START_EVT
事件:
/* ESP_GATTS_START_EVT 事件分支*/
if (create_tab1 == false)
{
ESP_LOGI(GATTS_TABLE_TAG, "SERVICE_START_EVT1, status %d, service_handle %d", param->start.status, param->start.service_handle);
esp_err_t creat_attr_ret1 = esp_ble_gatts_create_attr_tab(gatt_db2, gatts_if, DM_IDX_NB, SVC_INST_ID1);
if (creat_attr_ret1)
{
ESP_LOGE(GATTS_TABLE_TAG, "create attr table2 failed, error code = %x", creat_attr_ret1);
}
create_tab1 = true;
}
else
{
ESP_LOGI(GATTS_TABLE_TAG, "SERVICE_START_EVT2, status %d, service_handle %d", param->start.status, param->start.service_handle);
}
break;
在该事件中因为开始服务成功后事件才会被触发,因此创建好第一个 database 后则可以开始创建第二个 database,则调用 esp_ble_gatts_create_attr_tab
函数继续创建第二个 database,以此类推。
3 门禁控制器中的蓝牙任务 Walkthrough¶
首先调用 ble_server_task_init
函数用于初始化蓝牙控制器,蓝牙协议栈,注册 GATT 回调函数以及 GAP 回调函数,设置 MTU,并注册 profile esp_ble_gatts_app_register
,注册 profile 将会触发事件 ESP_GATTS_REG_EVT
回调函数:其中 GATT 回调函数以及 GAP 回调函数用于处理蓝牙栈所有可能发生的情况,来达到 FSM 的效果
MTU(最大传输单元):通过设置 MTU 的值限制 PDU(协议数据单元)最大能够交换的数据量
gap_event_handler
用于处理 GAP 中的事件,本次不太需要关心
gatts_event_handler
接收来自协议栈中的 GATTS 事件,分发给不同的 Profile,由于本次系统设计只使用了一个 Profile ,因此相当于将事件直接传递给这唯一的 Profile
gatts_profile_event_handler
是实际处理各个 GATT 操作的状态机,所有有关于对 Service 和 Characteristic 的操作都在此处进行,下面针对该状态机做一个简要概述:
3.1 ESP_GATTS_REG_EVT
¶
在该事件中设置设备名称并配置 gap 广播数据包之后,开始创建第一个服务的属性表,通过调用该函数进行创建:esp_ble_gatts_create_attr_tab
调用该函数之后将会触发 ESP_GATTS_CREAT_ATTR_TAB_EVT
事件
3.2 ESP_GATTS_CREAT_ATTR_TAB_EVT
¶
如果属性表创建成功的话,事件所携带的参数将会返回一个属性表的句柄,以下是该事件下的 param 结构体:
struct gatts_add_attr_tab_evt_param{
esp_gatt_status_t status; /*!< Operation status */
esp_bt_uuid_t svc_uuid; /*!< Service uuid type */
uint8_t svc_inst_id; /*!< Service id */
uint16_t num_handle; /*!< The number of the attribute handle to be added to the gatts database */
uint16_t *handles; /*!< The number to the handles */
} add_attr_tab; /*!< Gatt server callback param of ESP_GATTS_CREAT_ATTR_TAB_EVT */
在该事件内通过先前维护的一个全局布尔类型变量判断此时创建的是第几个属性表
之后将所有该属性表中的所有特征值句柄赋值给对应的句柄列表,方便后续统一管理某一个特征值的操作函数
最后调用 esp_ble_gatts_start_service
启动服务,启动完成后将会触发 ESP_GATTS_START_EVT
事件
3.3 ESP_GATTS_START_EVT
¶
该事件下的 param 结构体:
struct gatts_start_evt_param {
esp_gatt_status_t status; /*!< Operation status */
uint16_t service_handle; /*!< Service attribute handle */
} start; /*!< Gatt server callback param of ESP_GATTS_START_EVT */
同样,根据全局维护的全局布尔类型变量判断是否进行下一个属性表的创建,如果有多个属性表就以此类推,完成一个属性表的创建就将对应的变量置 TRUE
3.4 ESP_GATTS_WRITE_EVT
¶
该事件在发生 GATT 写操作的时候被触发,param 如下:
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 */
本次 gatts 中实现了一个函数 write_event_handler
,接收三个参数,分别是:
- 操作的特征值句柄
- 数据长度
- 写入数据的指针
该函数将会遍历预先设定好的查找表,匹配操作特征值的对应业务函数
3.5 业务函数一般接口¶
由于提前规范了双方的加密协议,一般客户端写入的特征值都是统一的加密帧数据包,先要对数据包进行解密,通过先前提及的 data_frame_unpacking
和 data_frame_packing
函数可以进行处理
函数一般接受来自前级传递来的特征值句柄、数据长度、数据的指针