Appearance
GATT
GATT (Generic Attribute Profile) 和 ATT (Attribute Protocol) 是蓝牙协议栈中的两个重要协议,主要用于在蓝牙设备之间传输数据和交换信息。
ATT 是蓝牙协议栈中的底层协议,它定义了蓝牙设备之间交换数据的规则和格式。ATT 协议使用一种基于属性的数据交换模型,其中每个属性都有一个唯一的标识符和对应的值。
GATT 是建立在 ATT 协议之上的协议,它定义了蓝牙设备之间如何组织和交换数据。GATT 协议使用一种基于属性的数据协议,其中每个属性都有一个唯一的UUID(Universally Unique Identifier)来标识,并且可以包含不同类型的数据,例如传感器数据、配置信息、命令等。
GATT 和 ATT 协议结合在一起,可以让蓝牙设备之间进行灵活地数据交换和通信。例如,一个蓝牙智能手表可以使用 GATT 协议向手机发送心率数据,手机可以使用 GATT 协议向手表发送配置信息,从而实现设备之间的交互和控制。 GATT/ATT 协议在蓝牙传感器、智能家居、健康监测等领域得到了广泛的应用。
- ATT基本属性
属性句柄(属性表下标), 属性类型(UUID), 属性值, 访问权限, 相当于一个设备的数据库
- GATT属性
定义了两个角色
服务器GATTS, 客户端GATTC
独立于GAP, 服务器提供数据, 客户端访问数据, 应用的时候外围设备为服务器
一个设备可以同时为服务器和客户端
可以进行交换配置, 发现对方设备服务和特性, 读写特征值, 特征值的通知和指示
串口穿透
初始化
- 扫描结果查询
SDP指的是Service Discovery Protocol,它是蓝牙技术中用于设备发现和服务查找的协议。SDP允许蓝牙设备发现其附近的其他蓝牙设备,并且查询这些设备上提供的各种服务和功能。通过SDP,一个蓝牙设备可以向其他设备发送查询请求,以获取它们所提供的服务的详细信息。
属性表
一个characteristic包含三种条目:characteristic声明,characteristic的值以及characteristic的描述符(可以有多个描述符):
Characteristic declaration
就是每个characteristic的分界符。解析时一旦遇到characteristicdeclaration,就可以认为接下来又是一个新的characteristic了,同时characteristic declaration还将包含value的读写属性等。
Characteristic value
就是数据的值了,这个比较好理解就不再说了。
Characteristic descriptor
就是数据的额外信息。比如温度的单位是什么,数据是用小数表示还是百分比表示等之类的数据描述信息。CCCD是一种特殊的characteristicdescriptor,当characteristic具有notify或者indicate操作功能时,那么必须为其添加相应CCCD,以方便client来使能或者禁止notify或者indicate功能。
Attribute句柄。Client要访问Server的Attribute,都是通过这个句柄来访问的,也就是说ATT PDU一般都包含handle的值。用户在软件代码添加characteristic的时候,系统会自动按顺序地为相关attribute生成句柄。
Attribute类型。在BLE中我们使用UUID来定义数据的类型,UUID是128 bit的,所以我们有足够的UUID来表达万事万物。
就是数据真正的值,0到512字节长。
Attribute的权限属性,权限属性不会直接在空中包中体现,而是隐含在ATT命令的操作结果中。目前主要有如下四种权限属性:
- Open,直接可以读或者写
- No Access,禁止读或者写
- Authentication,需要配对才能读或者写,由于配对有多种类型,因此authentication又衍生多种子类型,比如带不带MITM,有没有LESC
- Authorization,跟open一样,不过server返回attribute的值之前需要应用先授权,也就是说应用可以在回调函数里面去修改读或者写的原始值。
- Signed,签名后才能读或者写,这个用得比较少。
示例代码服务器
初始化
c
void app_main(void)
{
esp_err_t ret;
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); //低功耗蓝牙参数
// Initialize NVS 初始化falsh
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK( ret );
//释放经典蓝牙的内存
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
//初始化低功耗蓝牙
ret = esp_bt_controller_init(&bt_cfg);
//开启蓝牙控制器
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
//初始化bluedroid的协议栈
ret = esp_bluedroid_init();
ret = esp_bluedroid_enable();
//注册事件回调
esp_ble_gatts_register_callback(gatts_event_handler);
esp_ble_gap_register_callback(gap_event_handler);
//注册APP
esp_ble_gatts_app_register(ESP_SPP_APP_ID);
spp_task_init();
return;
}
示例代码客户端
初始化
c
esp_err_t ret;
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); //标准蓝牙释放
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
esp_bt_controller_init(&bt_cfg); //初始化控制器
esp_bt_controller_enable(ESP_BT_MODE_BLE); //使能
esp_bluedroid_init(); //初始化框架
esp_bluedroid_enable(); //使能框架
ble_client_appRegister(); //申请APP
c
void ble_client_appRegister(void)
{
esp_err_t status;
char err_msg[20];
ESP_LOGI(GATTC_TAG, "register callback");
//register the scan callback function to the gap module
//注册GAP的回调
if ((status = esp_ble_gap_register_callback(esp_gap_cb)) != ESP_OK) {
ESP_LOGE(GATTC_TAG, "gap register error: %s", esp_err_to_name_r(status, err_msg, sizeof(err_msg)));
return;
}
//register the callback function to the gattc module
//客户端的回调
if ((status = esp_ble_gattc_register_callback(esp_gattc_cb)) != ESP_OK) {
ESP_LOGE(GATTC_TAG, "gattc register error: %s", esp_err_to_name_r(status, err_msg, sizeof(err_msg)));
return;
}
//注册GATTC的APP
esp_ble_gattc_app_register(PROFILE_APP_ID);
//设置最大回调单元
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(200);
if (local_mtu_ret){
ESP_LOGE(GATTC_TAG, "set local MTU failed: %s", esp_err_to_name_r(local_mtu_ret, err_msg, sizeof(err_msg)));
}
//一个队列用于spp, 这一个会阻塞等待发送事件
cmd_reg_queue = xQueueCreate(10, sizeof(uint32_t));
xTaskCreate(spp_client_reg_task, "spp_client_reg_task", 2048, NULL, 10, NULL);
}
gattc回调函数
c
static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
{
ESP_LOGI(GATTC_TAG, "EVT %d, gattc if %d", event, gattc_if);
/* If event is register event, store the gattc_if for each profile */
if (event == ESP_GATTC_REG_EVT) {
//这一个是一个注册的事件
if (param->reg.status == ESP_GATT_OK) {
gl_profile_tab[param->reg.app_id].gattc_if = gattc_if;
} else {
ESP_LOGI(GATTC_TAG, "Reg app failed, app_id %04x, status %d", param->reg.app_id, param->reg.status);
return;
}
}
/* If the gattc_if equal to profile A, call profile A cb handler,
* so here call each profile's callback */
do {
//遍历一下所有的回调函数, 实际是调用事件处理函数
int idx;
for (idx = 0; idx < PROFILE_NUM; idx++) {
if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
gattc_if == gl_profile_tab[idx].gattc_if) {
if (gl_profile_tab[idx].gattc_cb) {
gl_profile_tab[idx].gattc_cb(event, gattc_if, param);
}
}
}
} while (0);
}
GAP回调(获取连接)
c
//在这一个里面实现扫描对方, 检测名字之后连接
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
uint8_t *adv_name = NULL;
uint8_t adv_name_len = 0;
esp_err_t err;
switch(event){
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: {
//判断是不是设置参数成功
if((err = param->scan_param_cmpl.status) != ESP_BT_STATUS_SUCCESS){
ESP_LOGE(GATTC_TAG, "Scan param set failed: %s", esp_err_to_name(err));
break;
}
//the unit of the duration is second
uint32_t duration = 0xFFFF;
ESP_LOGI(GATTC_TAG, "Enable Ble Scan:during time %04" PRIx32 " minutes.",duration);
//开启扫描, 之后进入ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT
esp_ble_gap_start_scanning(duration);
break;
}
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT:
//scan start complete event to indicate scan start successfully or failed
//成功打开了
if ((err = param->scan_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTC_TAG, "Scan start failed: %s", esp_err_to_name(err));
break;
}
ESP_LOGI(GATTC_TAG, "Scan start successed");
//之后进入 ESP_GAP_BLE_SCAN_RESULT_EVT
break;
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:
//停止事件
if ((err = param->scan_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTC_TAG, "Scan stop failed: %s", esp_err_to_name(err));
break;
}
ESP_LOGI(GATTC_TAG, "Scan stop successed");
if (is_connect == false) {
ESP_LOGI(GATTC_TAG, "Connect to the remote device.");
//开启gattc, 使用获取的数据
//gattc_if获取是在esp_gattc_cb里面的ESP_GATTC_REG_EVT事件的时候设置
//其余的数据是scan的时候获取的地址和类型
//这一个函数之后进入gattc回调函数的ESP_GATTC_CONNECT_EVT
esp_ble_gattc_open(gl_profile_tab[PROFILE_APP_ID].gattc_if, scan_rst.scan_rst.bda,
scan_rst.scan_rst.ble_addr_type, true);
}
break;
case ESP_GAP_BLE_SCAN_RESULT_EVT: {
//扫描有结果了
esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param;
switch (scan_result->scan_rst.search_evt) {
case ESP_GAP_SEARCH_INQ_RES_EVT:
//查询成功事件
esp_log_buffer_hex(GATTC_TAG, scan_result->scan_rst.bda, 6);
//解析一下数据
ESP_LOGI(GATTC_TAG, "Searched Adv Data Len %d, Scan Response Len %d",
scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len);
adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv,
ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);
ESP_LOGI(GATTC_TAG, "Searched Device Name Len %d", adv_name_len);
esp_log_buffer_char(GATTC_TAG, adv_name, adv_name_len);
ESP_LOGI(GATTC_TAG, "\n");
if (adv_name != NULL) {
//对比一下是不是这一个名字
if ( strncmp((char *)adv_name, device_name, adv_name_len) == 0) {
//记录一下参数在scan_rst这一个全局变量里面
memcpy(&(scan_rst), scan_result, sizeof(esp_ble_gap_cb_param_t));
//把这一个GAP停止了, 之后进入ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT
esp_ble_gap_stop_scanning();
}
}
break;
case ESP_GAP_SEARCH_INQ_CMPL_EVT:
break;
default:
break;
}
break;
}
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
if ((err = param->adv_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS){
ESP_LOGE(GATTC_TAG, "Adv stop failed: %s", esp_err_to_name(err));
}else {
ESP_LOGI(GATTC_TAG, "Stop adv successfully");
}
break;
default:
break;
}
}
GATTC回调函数
c
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
{
esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param;
switch (event) {
case ESP_GATTC_REG_EVT:
ESP_LOGI(GATTC_TAG, "REG EVT, set scan params");
esp_ble_gap_set_scan_params(&ble_scan_params);
break;
case ESP_GATTC_CONNECT_EVT:
//连接事件
ESP_LOGI(GATTC_TAG, "ESP_GATTC_CONNECT_EVT: conn_id=%d, gatt_if = %d",
spp_conn_id, gattc_if);
ESP_LOGI(GATTC_TAG, "REMOTE BDA:");
esp_log_buffer_hex(GATTC_TAG, gl_profile_tab[PROFILE_APP_ID].remote_bda,
sizeof(esp_bd_addr_t));
//记录一下全局变量
spp_gattc_if = gattc_if;
is_connect = true;
spp_conn_id = p_data->connect.conn_id;
//记录设备地址
memcpy(gl_profile_tab[PROFILE_APP_ID].remote_bda, p_data->connect.remote_bda,
sizeof(esp_bd_addr_t));
//对服务进行搜索, 进入ESP_GATTC_SEARCH_RES_EVT
esp_ble_gattc_search_service(spp_gattc_if, spp_conn_id, &spp_service_uuid);
break;
case ESP_GATTC_DISCONNECT_EVT:
ESP_LOGI(GATTC_TAG, "disconnect");
free_gattc_srv_db();
esp_ble_gap_start_scanning(SCAN_ALL_THE_TIME);
break;
case ESP_GATTC_SEARCH_RES_EVT:
//搜索事件
ESP_LOGI(GATTC_TAG, "ESP_GATTC_SEARCH_RES_EVT: start_handle = %d, end_handle = %d, UUID:0x%04x",
p_data->search_res.start_handle,
p_data->search_res.end_handle,
p_data->search_res.srvc_id.uuid.uuid.uuid16);
//记录一下对方的Handler数据
spp_srv_start_handle = p_data->search_res.start_handle;
spp_srv_end_handle = p_data->search_res.end_handle;
break;
case ESP_GATTC_SEARCH_CMPL_EVT:
//搜索完成
ESP_LOGI(GATTC_TAG, "SEARCH_CMPL: conn_id = %x, status %d", spp_conn_id, p_data->search_cmpl.status);
//发送以及确定最大传输单元
//之后进入ESP_GATTC_CFG_MTU_EVT事件
esp_ble_gattc_send_mtu_req(gattc_if, spp_conn_id);
break;
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
//注册通知esp_ble_gattc_register_for_notify这一个函数之后
//这一个函数是在cmd设置以后, 在spp的任务里面启用的
ESP_LOGI(GATTC_TAG,"Index = %d,status = %d,handle = %d\n",
cmd, p_data->reg_for_notify.status, p_data->reg_for_notify.handle);
if(p_data->reg_for_notify.status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG, "ESP_GATTC_REG_FOR_NOTIFY_EVT, status = %d",
p_data->reg_for_notify.status);
break;
}
uint16_t notify_en = 1;
//写入特征描述符
esp_ble_gattc_write_char_descr(
spp_gattc_if,
spp_conn_id,
(db+cmd+1)->attribute_handle,
sizeof(notify_en),
(uint8_t *)¬ify_en,
ESP_GATT_WRITE_TYPE_RSP,
ESP_GATT_AUTH_REQ_NONE);
break;
}
case ESP_GATTC_NOTIFY_EVT:
//接收通知事件
ESP_LOGI(GATTC_TAG,"ESP_GATTC_NOTIFY_EVT\n");
notify_event_handler(p_data);
break;
case ESP_GATTC_READ_CHAR_EVT:
ESP_LOGI(GATTC_TAG,"ESP_GATTC_READ_CHAR_EVT\n");
break;
case ESP_GATTC_WRITE_CHAR_EVT:
ESP_LOGI(GATTC_TAG,"ESP_GATTC_WRITE_CHAR_EVT:status = %d,handle = %d", param->write.status, param->write.handle);
if(param->write.status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG, "ESP_GATTC_WRITE_CHAR_EVT, error status = %d", p_data->write.status);
break;
}
break;
case ESP_GATTC_PREP_WRITE_EVT:
break;
case ESP_GATTC_EXEC_EVT:
break;
case ESP_GATTC_WRITE_DESCR_EVT:
ESP_LOGI(GATTC_TAG,"ESP_GATTC_WRITE_DESCR_EVT: status =%d,handle = %d \n", p_data->write.status, p_data->write.handle);
if(p_data->write.status != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG, "ESP_GATTC_WRITE_DESCR_EVT, error status = %d", p_data->write.status);
break;
}
switch(cmd){
case SPP_IDX_SPP_DATA_NTY_VAL:
cmd = SPP_IDX_SPP_STATUS_VAL;
xQueueSend(cmd_reg_queue, &cmd,10/portTICK_PERIOD_MS);
break;
case SPP_IDX_SPP_STATUS_VAL:
#ifdef SUPPORT_HEARTBEAT
cmd = SPP_IDX_SPP_HEARTBEAT_VAL;
xQueueSend(cmd_reg_queue, &cmd, 10/portTICK_PERIOD_MS);
#endif
break;
#ifdef SUPPORT_HEARTBEAT
case SPP_IDX_SPP_HEARTBEAT_VAL:
xQueueSend(cmd_heartbeat_queue, &cmd, 10/portTICK_PERIOD_MS);
break;
#endif
default:
break;
};
break;
case ESP_GATTC_CFG_MTU_EVT:
//配置最大传输单元
if(p_data->cfg_mtu.status != ESP_OK){
break;
}
ESP_LOGI(GATTC_TAG,"+MTU:%d\n", p_data->cfg_mtu.mtu);
spp_mtu_size = p_data->cfg_mtu.mtu;//记录一下对方的值
//申请服务特征数据库
//这一个数据库和对方相比, 差了一个声明
db = (esp_gattc_db_elem_t *)malloc(count*sizeof(esp_gattc_db_elem_t));
if(db == NULL){
ESP_LOGE(GATTC_TAG,"%s:malloc db falied\n",__func__);
break;
}
//获取数据库的数据
if(esp_ble_gattc_get_db(spp_gattc_if, spp_conn_id, spp_srv_start_handle, spp_srv_end_handle, db, &count) != ESP_GATT_OK){
ESP_LOGE(GATTC_TAG,"%s:get db falied\n",__func__);
break;
}
if(count != SPP_IDX_NB){
//判断是不是获取成功了
ESP_LOGE(GATTC_TAG,"%s:get db count != SPP_IDX_NB, count = %d, SPP_IDX_NB = %d\n",__func__,count,SPP_IDX_NB);
break;
}
//根据数据的类型进行打印
for(int i = 0;i < SPP_IDX_NB;i++){
switch((db+i)->type){
case ESP_GATT_DB_PRIMARY_SERVICE:
ESP_LOGI(GATTC_TAG,"attr_type = PRIMARY_SERVICE,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x\n",\
(db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16);
break;
case ESP_GATT_DB_SECONDARY_SERVICE:
ESP_LOGI(GATTC_TAG,"attr_type = SECONDARY_SERVICE,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x\n",\
(db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16);
break;
case ESP_GATT_DB_CHARACTERISTIC:
ESP_LOGI(GATTC_TAG,"attr_type = CHARACTERISTIC,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x\n",\
(db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16);
break;
case ESP_GATT_DB_DESCRIPTOR:
ESP_LOGI(GATTC_TAG,"attr_type = DESCRIPTOR,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x\n",\
(db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16);
break;
case ESP_GATT_DB_INCLUDED_SERVICE:
ESP_LOGI(GATTC_TAG,"attr_type = INCLUDED_SERVICE,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x\n",\
(db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16);
break;
case ESP_GATT_DB_ALL:
ESP_LOGI(GATTC_TAG,"attr_type = ESP_GATT_DB_ALL,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x\n",\
(db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16);
break;
default:
break;
}
}
cmd = SPP_IDX_SPP_DATA_NTY_VAL;
xQueueSend(cmd_reg_queue, &cmd, 10/portTICK_PERIOD_MS);
break;
case ESP_GATTC_SRVC_CHG_EVT:
break;
default:
break;
}
}
属性表
c
/**
* @brief attribute auto response flag
*/
typedef struct
{
#define ESP_GATT_RSP_BY_APP 0 //手动回复
#define ESP_GATT_AUTO_RSP 1 //自动回复
/**
* @brief if auto_rsp set to ESP_GATT_RSP_BY_APP, means the response of
Write/Read operation will by replied by application.
if auto_rsp set to ESP_GATT_AUTO_RSP, means the response of Write/Read
operation will be replied by GATT stack automatically.
*/
uint8_t auto_rsp;
} esp_attr_control_t;
/**
* @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 value最大长度*/
uint16_t length; /*!< Current length of the element 当前的长度*/
uint8_t *value; /*!< Element value array 数据*/
} esp_attr_desc_t;
/**
* @brief attribute type added to the gatt server database
*/
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;
///Full HRS Database Description - Used to add attributes into the database
//实际是一个数据库的特征声明
static const esp_gatts_attr_db_t spp_gatt_db[SPP_IDX_NB] =
{
//SPP - Service Declaration
[SPP_IDX_SVC] =
{
{ESP_GATT_AUTO_RSP}, //使用自动回复
{ESP_UUID_LEN_16, //UUID长度
(uint8_t *)&primary_service_uuid, //使用的UUID(实际是标记一个类型)
ESP_GATT_PERM_READ, //有读权限
sizeof(spp_service_uuid), //两个长度
sizeof(spp_service_uuid),
(uint8_t *)&spp_service_uuid //数据
}
},
//SPP - data receive characteristic Declaration 声明
[SPP_IDX_SPP_DATA_RECV_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}},
//SPP - data receive characteristic Value 值
[SPP_IDX_SPP_DATA_RECV_VAL] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_data_receive_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
SPP_DATA_MAX_LEN,sizeof(spp_data_receive_val), (uint8_t *)spp_data_receive_val}},
//SPP - data notify characteristic Declaration
[SPP_IDX_SPP_DATA_NOTIFY_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_notify}},
//SPP - data notify characteristic Value
[SPP_IDX_SPP_DATA_NTY_VAL] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_data_notify_uuid, ESP_GATT_PERM_READ,
SPP_DATA_MAX_LEN, sizeof(spp_data_notify_val), (uint8_t *)spp_data_notify_val}},
//SPP - data notify characteristic - Client Characteristic Configuration Descriptor
[SPP_IDX_SPP_DATA_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(spp_data_notify_ccc), (uint8_t *)spp_data_notify_ccc}},
//SPP - command characteristic Declaration
[SPP_IDX_SPP_COMMAND_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}},
//SPP - command characteristic Value
[SPP_IDX_SPP_COMMAND_VAL] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_command_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
SPP_CMD_MAX_LEN,sizeof(spp_command_val), (uint8_t *)spp_command_val}},
//SPP - status characteristic Declaration
[SPP_IDX_SPP_STATUS_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_notify}},
//SPP - status characteristic Value
[SPP_IDX_SPP_STATUS_VAL] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_status_uuid, ESP_GATT_PERM_READ,
SPP_STATUS_MAX_LEN,sizeof(spp_status_val), (uint8_t *)spp_status_val}},
//SPP - status characteristic - Client Characteristic Configuration Descriptor
[SPP_IDX_SPP_STATUS_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(spp_status_ccc), (uint8_t *)spp_status_ccc}},
#ifdef SUPPORT_HEARTBEAT
//心跳, 实际使用的时候需要实现一个类似的 0x2803是一个没有使用的UUID
//SPP - Heart beat characteristic Declaration
[SPP_IDX_SPP_HEARTBEAT_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_notify}},
//SPP - Heart beat characteristic Value
//使用0xABF5
[SPP_IDX_SPP_HEARTBEAT_VAL] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_heart_beat_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
sizeof(spp_heart_beat_val), sizeof(spp_heart_beat_val), (uint8_t *)spp_heart_beat_val}},
//SPP - Heart beat characteristic - Client Characteristic Configuration Descriptor
//客户端配置 0x2902
[SPP_IDX_SPP_HEARTBEAT_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(spp_data_notify_ccc), (uint8_t *)spp_heart_beat_ccc}},
#endif
};
//创建属性列表(重点!!!)
ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__);
esp_ble_gatts_create_attr_tab(spp_gatt_db, gatts_if, SPP_IDX_NB, SPP_SVC_INST_ID);
- 建立成功的处理函数
c
case ESP_GATTS_CREAT_ATTR_TAB_EVT:{
//创建属性表的事件
ESP_LOGI(GATTS_TABLE_TAG, "The number handle =%x\n",param->add_attr_tab.num_handle);
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 != SPP_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, SPP_IDX_NB);
}
else {
//记录属性表
memcpy(spp_handle_table, param->add_attr_tab.handles, sizeof(spp_handle_table));
//开启服务
esp_ble_gatts_start_service(spp_handle_table[SPP_IDX_SVC]);
}
break;
}
实际使用
服务器
- 服务器
- 在注册表里面添加使用的属性, 包括属性的声明, 值和描述符
- 可以使用esp_ble_gatts_get_attr_value获取本地的值
- 在ESP_GATTS_WRITE_EVT事件里面获取要写入的值, 使用esp_ble_gatts_set_attr_value进行更新
c
esp_ble_gatts_set_attr_value(p_data->write.handle,p_data->write.len,p_data->write.value)
- 完成写入以后会进入这一个ESP_GATTS_SET_ATTR_VAL_EVT事件里面
- 可以使用esp_ble_gatts_send_indicate发送通知给客户端
客户端
- 客户端读取
- 在注册表里面添加自己的属性(下标, 描述符)
- 连接以后可以使用esp_ble_gattc_read_char函数获取数据库里面的值
- 在ESP_GATTC_READ_CHAR_EVT事件里面处理实际的读取
c
//判断是否读取成功
if(p_data->read.status == ESP_GATT_OK)
{
//判断属性句柄句柄
if(p_data->read.handle == (db+TEST_LED_VAL)->attribute_handle)//判断一下读取的属性
{
printf("read value = %d,len = %d\r\n",*p_data->read.value,p_data->read.value_len);
//记录通知状态
memcpy(&read_status,p_data->read.value,p_data->read.value_len);
}
}
- 客户端写入
- 添加属性表属性
- 使用esp_ble_gattc_write_char函数进行写入
- 注册通知
- 使用esp_ble_gattc_register_for_notify注册通知, 绑定一个属性表的值
- ESP_GATTC_REG_FOR_NOTIFY_EVT事件里面使用esp_ble_gattc_write_char_descr告诉服务器
- ESP_GATTC_NOTIFY_EVT可以捕获到这一个属性表值的通知
API
注册通知
c
esp_err_t esp_ble_gattc_register_for_notify(esp_gatt_if_t gattc_if, esp_bd_addr_t server_bda, uint16_t handle);
注册一个通知, 之后会调用回调函数里面的ESP_GATTC_REG_FOR_NOTIFY_EVT这一个回调函数
发送一个值
c
esp_err_t esp_ble_gattc_write_char_descr(esp_gatt_if_t gattc_if, uint16_t conn_id, uint16_t handle, uint16_t value_len, uint8_t *value, esp_gatt_write_type_t write_type, esp_gatt_auth_req_t auth_req);
gattc_if: 使用的客户端的类型, 是回调函数的参数
conn_id: 连接的对方的id, 这一个数据是在连接的时候获取
spp_conn_id = p_data->connect.conn_id;
handle: 操作的对象的句柄
value_len, value写入的数据
write_type: 需不需要远程响应
ESP_GATT_WRITE_TYPE_RSP\_NO_RSP
, 示例使用后者auth_req: 加密认证
ESP_GATT_AUTH_REQ_NONE
最大传输单元设置
c
esp_ble_gatt_set_local_mtu(200); //设置本地的最大传输单元
esp_err_t esp_ble_gattc_send_mtu_req(esp_gatt_if_t gattc_if, uint16_t conn_id);//交换数据, 需要在本地设置以后
开启GATT
c
esp_err_t esp_ble_gattc_open(esp_gatt_if_t gattc_if, esp_bd_addr_t remote_bda, esp_ble_addr_type_t remote_addr_type, bool is_direct)
gattc_if: Gatt client access interface.注册的时候获取
remote_bda, remote_addr_type: 扫描的时候获取
is_direct: 直接连接还是后台连接, 使用true
ESP_GATTC_CONNECT_EVT
注册回调函数
c
esp_err_t esp_ble_gattc_register_callback(esp_gattc_cb_t callback); //在这里记录回调函数
esp_err_t esp_ble_gattc_app_register(uint16_t app_id); //实际用于这一个app_id
获取服务
c
esp_err_t esp_ble_gattc_search_service(esp_gatt_if_t gattc_if, uint16_t conn_id, esp_bt_uuid_t *filter_uuid);
- gattc_if -- [in] Gatt client access interface. gattc申请的id
- conn_id -- [in] connection ID. 连接id
- filter_uuid -- [in] a UUID of the service application is interested in. If Null, discover for all services.
获取数据库信息
c
esp_gatt_status_t esp_ble_gattc_get_db(esp_gatt_if_t gattc_if, uint16_t conn_id, uint16_t start_handle, uint16_t end_handle, esp_gattc_db_elem_t *db, uint16_t *count)
This function is called to get the GATT database. Note: It just get attribute data base from local cache, won't get from remote devices.
db: 传出参数, 获取到的数据库
注册一个通知
c
esp_err_t esp_ble_gattc_register_for_notify(esp_gatt_if_t gattc_if, esp_bd_addr_t server_bda, uint16_t handle);
服务器设置属性表的一个值
c
esp_err_t esp_ble_gatts_set_attr_value(uint16_t attr_handle, uint16_t length, const uint8_t *value);
服务器使用, 之后会调用ESP_GATTS_SET_ATTR_VAL_EVT这一个事件的处理
设置的这一个属性值可以使用
c
esp_gatt_status_t esp_ble_gatts_get_attr_value(uint16_t attr_handle, uint16_t *length, const uint8_t **value);
进行获取
attr_handle属性句柄
客户端读取
c
esp_err_t esp_ble_gattc_read_char(esp_gatt_if_t gattc_if, uint16_t conn_id, uint16_t handle, esp_gatt_auth_req_t auth_req);
c//读取特征值完成事件 case ESP_GATTC_READ_CHAR_EVT: ESP_LOGI(GATTC_TAG,"ESP_GATTC_READ_CHAR_EVT\n"); #ifdef SUPPORT_LED //判断是否读取成功 if(p_data->read.status == ESP_GATT_OK) { //判断属性句柄句柄 if(p_data->read.handle == (db+TEST_LED_VAL)->attribute_handle) { printf("read value = %d,len = %d\r\n",*p_data->read.value,p_data->read.value_len); //记录通知状态 memcpy(&read_status,p_data->read.value,p_data->read.value_len); } } else printf("read faild, status = %d\r\n",p_data->read.status); #endif break;
服务器发通知
c
esp_err_t esp_ble_gatts_send_indicate(esp_gatt_if_t gatts_if, uint16_t conn_id, uint16_t attr_handle, uint16_t value_len, uint8_t *value, bool need_confirm);
发送一指示或者通知给客户端
need_confirm: 1指示 0通知
之后客户端会进入ESP_GATTC_NOTIFY_EVT这一个事件里面(需要注册这一个句柄的通知)
实现通知的时候, 需要自己手动调用
客户端注册通知
c
esp_err_t esp_ble_gattc_register_for_notify(esp_gatt_if_t gattc_if, esp_bd_addr_t server_bda, uint16_t handle);
给某一个句柄注册一个通知, 之后就可以获这一个句柄的通知, 这一个注册的时候不会对服务端进行通知, 需要在ESP_GATTC_REG_FOR_NOTIFY_EVT里面自己通知