前言
- 该博客主要针对希望迅速上手 ESP32 蓝牙从机开发人员,因此,很多蓝牙技术细节知识并不会进行介绍,仅仅介绍我认为需要了解的 API 函数和回调内容。
- 本文主要是基于gatt_server demo来微调进行进行讲解。
代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"
#include "sdkconfig.h"
#define GATTS_TAG "GATTS_DEMO"
///Declare the static function
static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
#define GATTS_SERVICE_UUID_TEST_A 0x00FF
#define GATTS_CHAR_UUID_TEST_A 0xFF01
#define GATTS_DESCR_UUID_TEST_A 0x3333
#define GATTS_NUM_HANDLE_TEST_A 4
#define GATTS_SERVICE_UUID_TEST_B 0x00EE
#define GATTS_CHAR_UUID_TEST_B 0xEE01
#define GATTS_DESCR_UUID_TEST_B 0x2222
#define GATTS_NUM_HANDLE_TEST_B 4
/* 广播设备名称,如果需要使用中文,则需要中文转 URL 编码。可以使用 https://tool.chinaz.com/tools/urlencode.aspx 进行转换 */
// #define TEST_DEVICE_NAME "ESP_GATTS_DEMO"
const char TEST_DEVICE_NAME[] = {0xE9,0xA3,0x8E,0xE6,0xAD,0xA3,0xE8,0xB1,0xAA};
#define TEST_MANUFACTURER_DATA_LEN 17
#define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40
#define PREPARE_BUF_MAX_SIZE 1024
static uint8_t char1_str[] = {0x11,0x22,0x33};
static esp_gatt_char_prop_t a_property = 0;
static esp_gatt_char_prop_t b_property = 0;
static 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,
};
#define adv_config_flag (1 << 0)
#define scan_rsp_config_flag (1 << 1)
#ifdef CONFIG_SET_RAW_ADV_DATA
static uint8_t raw_adv_data[] = {
0x02, 0x01, 0x06, // Length 2, Data Type 1 (Flags), Data 1 (LE General Discoverable Mode, BR/EDR Not Supported)
0x02, 0x0a, 0xeb, // Length 2, Data Type 10 (TX power leve), Data 2 (-21)
0x03, 0x03, 0xab, 0xcd, // Length 3, Data Type 3 (Complete 16-bit Service UUIDs), Data 3 (UUID)
};
static uint8_t raw_scan_rsp_data[] = { // Length 15, Data Type 9 (Complete Local Name), Data 1 (ESP_GATTS_DEMO)
0x0f, 0x09, 0x45, 0x53, 0x50, 0x5f, 0x47, 0x41, 0x54, 0x54, 0x53, 0x5f, 0x44,
0x45, 0x4d, 0x4f
};
#else
static uint8_t adv_service_uuid128[32] = {
/* LSB <--------------------------------------------------------------------------------> MSB */
//first uuid, 16bit, [12],[13] is the value
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xEE, 0x00, 0x00, 0x00,
//second uuid, 32bit, [12], [13], [14], [15] is the value
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
};
// 广播报数据长度应当小于 31 bytes
//static uint8_t test_manufacturer[TEST_MANUFACTURER_DATA_LEN] = {0x12, 0x23, 0x45, 0x56};
//adv data
static esp_ble_adv_data_t adv_data = {
.set_scan_rsp = false,
.include_name = true,
.include_txpower = false,
.min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec
.max_interval = 0x0010, //slave connection max interval, Time = max_interval * 1.25 msec
.appearance = ESP_BLE_APPEARANCE_SPORTS_WATCH,
.manufacturer_len = 0, //TEST_MANUFACTURER_DATA_LEN,
.p_manufacturer_data = NULL, //&test_manufacturer[0],
.service_data_len = 0,
.p_service_data = NULL,
.service_uuid_len = sizeof(adv_service_uuid128),
.p_service_uuid = adv_service_uuid128,
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};
// scan response data
static esp_ble_adv_data_t scan_rsp_data = {
.set_scan_rsp = true,
.include_name = true,
.include_txpower = true,
//.min_interval = 0x0006,
//.max_interval = 0x0010,
// .appearance = 0x00,
.manufacturer_len = 0, //TEST_MANUFACTURER_DATA_LEN,
.p_manufacturer_data = NULL, //&test_manufacturer[0],
.service_data_len = 0,
.p_service_data = NULL,
.service_uuid_len = sizeof(adv_service_uuid128),
.p_service_uuid = adv_service_uuid128,
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};
#endif /* CONFIG_SET_RAW_ADV_DATA */
static esp_ble_adv_params_t adv_params = {
.adv_int_min = 0x20,
.adv_int_max = 0x40,
.adv_type = ADV_TYPE_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
//.peer_addr =
//.peer_addr_type =
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
enum {
PROFILE_A,
PROFILE_B,
PROFILE_NUM
};
/* 这个注册 APP ID 可为 0~65535 任意值,只要 APP ID 没有重复即可 */
#define PROFILE_A_APP_ID 0x55
#define PROFILE_B_APP_ID 0x66
struct gatts_profile_inst {
esp_gatts_cb_t gatts_cb; /* GATT 回调函数 */
uint16_t gatts_if; /* 协议栈分配的,用于指定服务的标识符 */
uint16_t app_id; /* 注册的 APP ID,可为 0~65535 任意值,但不可重复 */
uint16_t conn_id; /* 用于表示一个设备连接,因为 ESP32 作为从机可以被多个主机同时连接,那么就需要利用这个参数指定是那个主机连接 */
uint16_t service_handle; /* 服务(Service)句柄 */
esp_gatt_srvc_id_t service_id; /* 首要服务的一些参数 */
uint16_t char_handle; /* 特征(Characteristic)句柄 */
esp_bt_uuid_t char_uuid; /* 特征(Characteristic) UUID 值 */
esp_gatt_perm_t perm;
esp_gatt_char_prop_t property;
uint16_t descr_handle; /* 特征配置描述符(descriptor)句柄 */
esp_bt_uuid_t descr_uuid; /* UUID 长度 */
};
/* 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 = PROFILE_A_APP_ID,
.gatts_cb = gatts_profile_a_event_handler,
.gatts_if = ESP_GATT_IF_NONE,
},
[PROFILE_B] = {
.app_id = PROFILE_B_APP_ID,
.gatts_cb = gatts_profile_b_event_handler, /* 配置 B 服务的回调函数 */
.gatts_if = ESP_GATT_IF_NONE, /* 如果协议栈没有分配 gatts_if ,那么就是 ESP_GATT_IF_NONE */
},
};
typedef struct {
uint8_t *prepare_buf;
int prepare_len;
} prepare_type_env_t;
static prepare_type_env_t a_prepare_write_env; /* 存储 A 服务准备写操作内容 */
static prepare_type_env_t b_prepare_write_env; /* 存储 B 服务准备写操作内容 */
void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param);
void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param);
static void advertise_init(void)
{
esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(TEST_DEVICE_NAME);
if (set_dev_name_ret){
/* 设置设备名称失败 */
ESP_LOGE(GATTS_TAG, "set device name failed, error code = %x", set_dev_name_ret);
}
#ifdef CONFIG_SET_RAW_ADV_DATA
// 设置广播数据
esp_err_t raw_adv_ret = esp_ble_gap_config_adv_data_raw(raw_adv_data, sizeof(raw_adv_data));
if (raw_adv_ret){
ESP_LOGE(GATTS_TAG, "config raw adv data failed, error code = %x ", raw_adv_ret);
}
// 设置扫描响应数据
esp_err_t raw_scan_ret = esp_ble_gap_config_scan_rsp_data_raw(raw_scan_rsp_data, sizeof(raw_scan_rsp_data));
if (raw_scan_ret){
ESP_LOGE(GATTS_TAG, "config raw scan rsp data failed, error code = %x", raw_scan_ret);
}
#else
// 设置广播数据
esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
if (ret){
ESP_LOGE(GATTS_TAG, "config adv data failed, error code = %x", ret);
}
// 设置扫描响应数据
ret = esp_ble_gap_config_adv_data(&scan_rsp_data);
if (ret){
ESP_LOGE(GATTS_TAG, "config scan response data failed, error code = %x", ret);
}
#endif
}
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
ESP_LOGI(GATTS_TAG, "GAP Event:%d", event);
switch (event) {
#ifdef CONFIG_SET_RAW_ADV_DATA
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: {
if (param->adv_data_raw_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TAG, "advertising data set failed");
}
break;
}
case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: {
if (param->scan_rsp_data_raw_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TAG, "advertising data set failed");
}
esp_ble_gap_start_advertising(&adv_params);
break;
}
#else
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: {
if (param->adv_data_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TAG, "advertising data set failed");
}
break;
}
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: {
if (param->scan_rsp_data_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TAG, "advertising data set failed");
}
esp_ble_gap_start_advertising(&adv_params);
break;
}
#endif
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: {
//advertising start complete event to indicate advertising start successfully or failed
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TAG, "Advertising start failed");
}
break;
}
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: {
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(GATTS_TAG, "Advertising stop failed");
} else {
ESP_LOGI(GATTS_TAG, "Stop adv successfully");
}
break;
}
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: {
ESP_LOGI(GATTS_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d",
param->update_conn_params.status,
param->update_conn_params.min_int,
param->update_conn_params.max_int,
param->update_conn_params.conn_int,
param->update_conn_params.latency,
param->update_conn_params.timeout);
break;
}
case ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT: {
ESP_LOGI(GATTS_TAG, "packet length updated: rx = %d, tx = %d, status = %d",
param->pkt_data_length_cmpl.params.rx_len,
param->pkt_data_length_cmpl.params.tx_len,
param->pkt_data_length_cmpl.status);
break;
}
default:
break;
}
}
/**
* @brief 处理写事件的函数,用于处理普通写入和准备写入
*
* @param gatts_if GATT 接口,用于标识 GATT 服务端
* @param prepare_write_env 指向准备写环境结构体的指针,保存准备写的数据
* @param param GATT 回调参数,包括写事件的信息
*/
void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){
esp_gatt_status_t status = ESP_GATT_OK;
/* 检查写操作是否需要响应 */
if (param->write.need_rsp) {
/* 判断是否为准备写操作,注意 : 准备写入(Prepare Write)操作是必须有响应 */
if (param->write.is_prep) {
/* 检查写入偏移量是否超过缓冲区最大大小 */
if (param->write.offset > PREPARE_BUF_MAX_SIZE) {
/* 无效偏移量 */
status = ESP_GATT_INVALID_OFFSET;
} else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) {
/* 写操作的偏移量加上写入数据的长度超过了预设的缓冲区大小,表示这次写入操作会导致数据超出缓冲区的容量,这是一个无效的操作。 */
status = ESP_GATT_INVALID_ATTR_LEN;
}
/* 如果状态正常且准备缓冲区为空,则分配缓冲区内存 */
if (status == ESP_GATT_OK && prepare_write_env->prepare_buf == NULL) {
prepare_write_env->prepare_buf = (uint8_t *)malloc(PREPARE_BUF_MAX_SIZE*sizeof(uint8_t));
prepare_write_env->prepare_len = 0;
if (prepare_write_env->prepare_buf == NULL) {
/* malloc 申请分配内存失败 */
ESP_LOGE(GATTS_TAG, "Gatt_server prep no mem");
status = ESP_GATT_NO_RESOURCES;
}
}
esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t));
if (gatt_rsp) {
gatt_rsp->attr_value.len = param->write.len;
gatt_rsp->attr_value.handle = param->write.handle;
gatt_rsp->attr_value.offset = param->write.offset;
gatt_rsp->attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len);
/* 发送响应 */
esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, gatt_rsp);
if (response_err != ESP_OK){
/* 发送响应失败 */
ESP_LOGE(GATTS_TAG, "Send response error\n");
}
free(gatt_rsp);
} else {
/* 申请 esp_gatt_rsp_t 结构体内存数据失败 */
ESP_LOGE(GATTS_TAG, "malloc failed, no resource to send response error\n");
status = ESP_GATT_NO_RESOURCES;
}
if (status != ESP_GATT_OK){
return;
}
/* 将写入的数据复制到 a_prepare_write_env 或者 b_prepare_write_env 准备缓冲区 */
memcpy(prepare_write_env->prepare_buf + param->write.offset,
param->write.value,
param->write.len);
prepare_write_env->prepare_len += param->write.len;
}else{
ESP_LOGI(GATTS_TAG, "write event, but not prepare");
/* 对于普通写入,直接发送响应。注意,该函数必须有,否则会引起客户端(client)断连 */
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL);
}
} else{
/* 如果不需要响应,则直接返回 */
ESP_LOGI(GATTS_TAG, "write event, but not need to response");
return;
}
}
/**
* @brief 处理 GATT 服务端的执行写操作事件。
*
* @param prepare_write_env 准备写操作的环境参数,包括缓冲区和长度。
* @param param 事件回调参数,其中包含执行写操作的标志和其他信息。
*/
void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param) {
// 检查执行写操作的标志
if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC) {
// 如果标志为执行写操作,打印缓冲区内容
esp_log_buffer_hex(GATTS_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len);
}else {
// 如果标志为取消写操作,记录日志
ESP_LOGI(GATTS_TAG,"ESP_GATT_PREP_WRITE_CANCEL");
}
// 释放准备缓冲区的内存
if (prepare_write_env->prepare_buf) {
free(prepare_write_env->prepare_buf);
prepare_write_env->prepare_buf = NULL;
}
// 重置准备缓冲区的长度
prepare_write_env->prepare_len = 0;
}
static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) {
switch (event) {
case ESP_GATTS_REG_EVT: {
ESP_LOGI(GATTS_TAG, "REGISTER_APP_EVT, status %d, app_id %d", param->reg.status, param->reg.app_id);
/* 创造 GATTS 首要服务 */
gl_profile_tab[PROFILE_A].service_id.is_primary = true;
gl_profile_tab[PROFILE_A].service_id.id.inst_id = 0x00;
gl_profile_tab[PROFILE_A].service_id.id.uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[PROFILE_A].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_A;
esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_A].service_id, GATTS_NUM_HANDLE_TEST_A);
break;
}
case ESP_GATTS_READ_EVT: {
ESP_LOGI(GATTS_TAG, "GATT_READ_EVT, conn_id %d, trans_id %" PRIu32 ", handle %d", param->read.conn_id, param->read.trans_id, param->read.handle);
/* 读取 esp_ble_gatts_add_char 初始化时候的 char_val */
uint16_t length = 0;
const uint8_t *prf_char;
esp_gatt_rsp_t rsp;
memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
/* 获取特征值 */
esp_err_t get_attr_ret = esp_ble_gatts_get_attr_value(param->read.handle, &length, &prf_char);
if (get_attr_ret == ESP_FAIL){
ESP_LOGE(GATTS_TAG, "ILLEGAL HANDLE");
}
rsp.attr_value.handle = param->read.handle;
rsp.attr_value.len = length;
memcpy(rsp.attr_value.value, prf_char, length);
for(int i = 0; i < length; i++){
ESP_LOGD(GATTS_TAG, "ESP_GATTS_READ_EVT : prf_char[%x] =%x",i,prf_char[i]);
ESP_LOGD(GATTS_TAG, "ESP_GATTS_READ_EVT : i,rsp.attr_value.value[%x] =%x",i,rsp.attr_value.value[i]);
}
esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id,
ESP_GATT_OK, &rsp);
// 更新特征的值
rsp.attr_value.value[0] = 0x55;
rsp.attr_value.value[1] = 0x66;
rsp.attr_value.value[2] = 0x77;
esp_ble_gatts_set_attr_value(rsp.attr_value.handle, 3, rsp.attr_value.value);
break;
}
case ESP_GATTS_WRITE_EVT: {
ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %" PRIu32 ", handle %d", param->write.conn_id, param->write.trans_id, param->write.handle);
if (!param->write.is_prep) {
/* 打印客户端(client)写入的数据长度和值 */
ESP_LOGI(GATTS_TAG, "It's not prepare write, value len %d, value", param->write.len);
esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len);
/* 如果是 特征配置描述符(descriptor)操作句柄,并且写入数据长度为2 */
if (gl_profile_tab[PROFILE_A].descr_handle == param->write.handle && param->write.len == 2) {
uint16_t descr_value = param->write.value[1]<<8 | param->write.value[0];
if (descr_value == 0x0001){
if (a_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY){
ESP_LOGI(GATTS_TAG, "notify enable");
uint8_t notify_data[15];
for (int i = 0; i < sizeof(notify_data); ++i)
{
notify_data[i] = i%0xff;
}
//the size of notify_data[] need less than MTU size
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_A].char_handle,
sizeof(notify_data), notify_data, false);
}
} else if (descr_value == 0x0002){
/* */
if (a_property & ESP_GATT_CHAR_PROP_BIT_INDICATE){
ESP_LOGI(GATTS_TAG, "indicate enable");
uint8_t indicate_data[15];
for (int i = 0; i < sizeof(indicate_data); ++i)
{
indicate_data[i] = i%0xff;
}
//the size of indicate_data[] need less than MTU size
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_A].char_handle,
sizeof(indicate_data), indicate_data, true);
}
} else if (descr_value == 0x0000){
ESP_LOGI(GATTS_TAG, "notify/indicate disable ");
} else {
ESP_LOGE(GATTS_TAG, "unknown descr value");
esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len);
}
}
}
example_write_event_env(gatts_if, &a_prepare_write_env, param);
break;
}
case ESP_GATTS_EXEC_WRITE_EVT: {
ESP_LOGI(GATTS_TAG,"ESP_GATTS_EXEC_WRITE_EVT");
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);
example_exec_write_event_env(&a_prepare_write_env, param);
break;
}
case ESP_GATTS_MTU_EVT: {
ESP_LOGI(GATTS_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu);
break;
}
case ESP_GATTS_CREATE_EVT: {
ESP_LOGI(GATTS_TAG, "CREATE_SERVICE_EVT, status %d, service_handle %d", param->create.status, param->create.service_handle);
/* 添加 特征申明 和 特征值 */
gl_profile_tab[PROFILE_A].service_handle = param->create.service_handle;
gl_profile_tab[PROFILE_A].char_uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[PROFILE_A].char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_TEST_A;
a_property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY;
esp_err_t add_char_ret = esp_ble_gatts_add_char(gl_profile_tab[PROFILE_A].service_handle, &gl_profile_tab[PROFILE_A].char_uuid,
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
a_property,
&gatts_demo_char1_val, NULL);
/* 启动服务 */
esp_ble_gatts_start_service(gl_profile_tab[PROFILE_A].service_handle);
if (add_char_ret){
ESP_LOGE(GATTS_TAG, "add char failed, error code =%x",add_char_ret);
}
break;
}
case ESP_GATTS_ADD_CHAR_EVT: {
uint16_t length = 0;
const uint8_t *prf_char;
ESP_LOGI(GATTS_TAG, "ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d",
param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle);
/* 添加 特征配置描述符 */
gl_profile_tab[PROFILE_A].char_handle = param->add_char.attr_handle;
gl_profile_tab[PROFILE_A].descr_uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[PROFILE_A].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].service_handle, &gl_profile_tab[PROFILE_A].descr_uuid,
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, NULL, NULL);
esp_err_t get_attr_ret = esp_ble_gatts_get_attr_value(param->add_char.attr_handle, &length, &prf_char);
if (get_attr_ret == ESP_FAIL){
ESP_LOGE(GATTS_TAG, "ILLEGAL HANDLE");
}
ESP_LOGI(GATTS_TAG, "the gatts demo char length = %x", length);
for(int i = 0; i < length; i++){
ESP_LOGI(GATTS_TAG, "prf_char[%x] =%x",i,prf_char[i]);
}
if (add_descr_ret){
ESP_LOGE(GATTS_TAG, "add char descr failed, error code =%x", add_descr_ret);
}
break;
}
case ESP_GATTS_ADD_CHAR_DESCR_EVT: {
gl_profile_tab[PROFILE_A].descr_handle = param->add_char_descr.attr_handle;
ESP_LOGI(GATTS_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d",
param->add_char_descr.status, param->add_char_descr.attr_handle, param->add_char_descr.service_handle);
break;
}
case ESP_GATTS_START_EVT: {
ESP_LOGI(GATTS_TAG, "SERVICE_START_EVT, status %d, service_handle %d",
param->start.status, param->start.service_handle);
break;
}
case ESP_GATTS_CONNECT_EVT: {
ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONNECT_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:",
param->connect.conn_id,
param->connect.remote_bda[0], param->connect.remote_bda[1], param->connect.remote_bda[2],
param->connect.remote_bda[3], param->connect.remote_bda[4], param->connect.remote_bda[5]);
gl_profile_tab[PROFILE_A].conn_id = param->connect.conn_id;
//向对端设备发起连接参数更新
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);
break;
}
case ESP_GATTS_DISCONNECT_EVT: {
ESP_LOGI(GATTS_TAG, "ESP_GATTS_DISCONNECT_EVT, disconnect reason 0x%x", param->disconnect.reason);
break;
}
case ESP_GATTS_CONF_EVT: {
ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONF_EVT, status %d attr_handle %d", param->conf.status, param->conf.handle);
if (param->conf.status != ESP_GATT_OK){
esp_log_buffer_hex(GATTS_TAG, param->conf.value, param->conf.len);
}
break;
}
default:
break;
}
}
static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) {
switch (event) {
case ESP_GATTS_REG_EVT: {
ESP_LOGI(GATTS_TAG, "REGISTER_APP_EVT, status %d, app_id %d", param->reg.status, param->reg.app_id);
/* 创造 GATTS 首要服务 */
gl_profile_tab[PROFILE_B].service_id.is_primary = true;
gl_profile_tab[PROFILE_B].service_id.id.inst_id = 0x00;
gl_profile_tab[PROFILE_B].service_id.id.uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[PROFILE_B].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_B;
esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_B].service_id, GATTS_NUM_HANDLE_TEST_B);
break;
}
case ESP_GATTS_READ_EVT: {
ESP_LOGI(GATTS_TAG, "GATT_READ_EVT, conn_id %d, trans_id %" PRIu32 ", handle %d", param->read.conn_id, param->read.trans_id, param->read.handle);
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);
break;
}
case ESP_GATTS_WRITE_EVT: {
ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %" PRIu32 ", handle %d", param->write.conn_id, param->write.trans_id, param->write.handle);
if (!param->write.is_prep) {
ESP_LOGI(GATTS_TAG, "It's not prepare write, value len %d, value :", param->write.len);
esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len);
/* 如果是 特征配置描述符(descriptor)操作句柄,并且写入数据长度为2 */
if (gl_profile_tab[PROFILE_B].descr_handle == param->write.handle && param->write.len == 2) {
uint16_t descr_value= param->write.value[1]<<8 | param->write.value[0];
if (descr_value == 0x0001){
if (b_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY){
ESP_LOGI(GATTS_TAG, "profile b notify enable");
uint8_t notify_data[15];
for (int i = 0; i < sizeof(notify_data); ++i)
{
notify_data[i] = i%0xff;
}
//the size of notify_data[] need less than MTU size
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_B].char_handle,
sizeof(notify_data), notify_data, false);
}
}else if (descr_value == 0x0002){
if (b_property & ESP_GATT_CHAR_PROP_BIT_INDICATE){
ESP_LOGI(GATTS_TAG, "indicate enable");
uint8_t indicate_data[15];
for (int i = 0; i < sizeof(indicate_data); ++i)
{
indicate_data[i] = i%0xff;
}
//the size of indicate_data[] need less than MTU size
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_B].char_handle,
sizeof(indicate_data), indicate_data, true);
}
}
else if (descr_value == 0x0000){
ESP_LOGI(GATTS_TAG, "notify/indicate disable ");
}else{
ESP_LOGE(GATTS_TAG, "unknown value");
}
}
}
example_write_event_env(gatts_if, &b_prepare_write_env, param);
break;
}
case ESP_GATTS_EXEC_WRITE_EVT: {
ESP_LOGI(GATTS_TAG,"ESP_GATTS_EXEC_WRITE_EVT");
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);
example_exec_write_event_env(&b_prepare_write_env, param);
break;
}
case ESP_GATTS_MTU_EVT: {
ESP_LOGI(GATTS_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu);
break;
}
case ESP_GATTS_CREATE_EVT: {
ESP_LOGI(GATTS_TAG, "CREATE_SERVICE_EVT, status %d, service_handle %d", param->create.status, param->create.service_handle);
/* 创建特征 */
gl_profile_tab[PROFILE_B].service_handle = param->create.service_handle;
gl_profile_tab[PROFILE_B].char_uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[PROFILE_B].char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_TEST_B;
b_property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY | ESP_GATT_CHAR_PROP_BIT_INDICATE;
esp_err_t add_char_ret =esp_ble_gatts_add_char( gl_profile_tab[PROFILE_B].service_handle, &gl_profile_tab[PROFILE_B].char_uuid,
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
b_property,
NULL, NULL);
esp_ble_gatts_start_service(gl_profile_tab[PROFILE_B].service_handle);
if (add_char_ret){
ESP_LOGE(GATTS_TAG, "add char failed, error code =%x",add_char_ret);
}
break;
}
case ESP_GATTS_ADD_CHAR_EVT: {
ESP_LOGI(GATTS_TAG, "ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d",
param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle);
/* 添加 特征配置描述符 */
gl_profile_tab[PROFILE_B].char_handle = param->add_char.attr_handle;
gl_profile_tab[PROFILE_B].descr_uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[PROFILE_B].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
esp_ble_gatts_add_char_descr(gl_profile_tab[PROFILE_B].service_handle, &gl_profile_tab[PROFILE_B].descr_uuid,
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
NULL, NULL);
break;
}
case ESP_GATTS_ADD_CHAR_DESCR_EVT: {
gl_profile_tab[PROFILE_B].descr_handle = param->add_char_descr.attr_handle;
ESP_LOGI(GATTS_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d",
param->add_char_descr.status, param->add_char_descr.attr_handle, param->add_char_descr.service_handle);
break;
}
case ESP_GATTS_START_EVT: {
ESP_LOGI(GATTS_TAG, "SERVICE_START_EVT, status %d, service_handle %d",
param->start.status, param->start.service_handle);
break;
}
case ESP_GATTS_CONNECT_EVT: {
ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONNECT_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:",
param->connect.conn_id,
param->connect.remote_bda[0], param->connect.remote_bda[1], param->connect.remote_bda[2],
param->connect.remote_bda[3], param->connect.remote_bda[4], param->connect.remote_bda[5]);
gl_profile_tab[PROFILE_B].conn_id = param->connect.conn_id;
break;
}
case ESP_GATTS_CONF_EVT: {
ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONF_EVT status %d attr_handle %d", param->conf.status, param->conf.handle);
if (param->conf.status != ESP_GATT_OK){
esp_log_buffer_hex(GATTS_TAG, param->conf.value, param->conf.len);
}
break;
}
case ESP_GATTS_DISCONNECT_EVT: {
ESP_LOGI(GATTS_TAG, "ESP_GATTS_DISCONNECT_EVT, disconnect reason 0x%x", param->disconnect.reason);
}
default:
break;
}
}
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
ESP_LOGI(GATTS_TAG, "GATTS event = %d", event);
int idx;
/* 如果事件是注册事件,则存储每个概要文件的 gatts_if,该事件由 esp_ble_gatts_app_register() 函数触发 */
if (event == ESP_GATTS_REG_EVT) {
// 依次遍历 GATT profile
for (idx = 0; idx < PROFILE_NUM; idx++) {
// 找到对应的注册的 app_id ,如果 GATT 处于正常运行,那么将 GATT 接口 gatts_if 进行存储
if (param->reg.app_id == gl_profile_tab[idx].app_id && param->reg.status == ESP_GATT_OK) {
/* 将协议栈分配的 gatts_if 存储进对应的结构体中 */
gl_profile_tab[idx].gatts_if = gatts_if;
ESP_LOGI(GATTS_TAG,"gatts_if = %d,param->reg.app_id = 0x%x",gatts_if,param->reg.app_id);
}
}
}
// 触发 GATT 回调事件,如下处理为判断是那个 GATT profile 事件,并作出响应的处理
for (idx = 0; idx < PROFILE_NUM; idx++) {
/* 如果 gatts_if 为 ESP_GATT_IF_NONE 表示当前事件或操作不对应于任何特定的 GATT 应用程序接口,
* 因此所有的 GATT profile都会被调用一次。如果指定了特点的 GATT 应用,则调用对应的应用程序。
*/
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);
}
}
}
}
void app_main(void)
{
esp_err_t ret;
// 初始化 NVS.
ret = nvs_flash_init();
/* 如果 NVS 分区没有剩余空间或者 NVS 分区包含新格式数据,当前版本无法识别。*/
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
/* 清空 NVS 分区数据 */
ESP_ERROR_CHECK(nvs_flash_erase());
/* NVS 分区重新进行初始化 */
ret = nvs_flash_init();
}
ESP_ERROR_CHECK( ret );
/* 释放经典蓝牙协议栈空间 */
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
/* 初始化 Control 层 */
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(GATTS_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
/* 使能 Control 层 */
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(GATTS_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
/* 初始化 HOST层,Bluedroid 协议栈 */
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(GATTS_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
/* 使能 HOST层,Bluedroid 协议栈 */
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(GATTS_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
/* 注册 GATTS 事件回调函数 */
ret = esp_ble_gatts_register_callback(gatts_event_handler);
if (ret){
ESP_LOGE(GATTS_TAG, "gatts register error, error code = %x", ret);
return;
}
/* 注册 GAP 事件回调函数 */
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret){
ESP_LOGE(GATTS_TAG, "gap register error, error code = %x", ret);
return;
}
/* 注册一个app_id, 协议栈将会分配一个对应的 gatts_if,用于标识一个 GATT 服务。
* 调用这个函数就会触发 esp_ble_gatts_register_callback() 注册的回调函数中的 ESP_GATTS_REG_EVT 事件
*/
ret = esp_ble_gatts_app_register(gl_profile_tab[PROFILE_A].app_id);
if (ret){
ESP_LOGE(GATTS_TAG, "gatts app register error, error code = %x", ret);
return;
}
/* 注册一个app_id, 协议栈将会分配一个对应的 gatts_if,用于标识一个 GATT 服务。
* 调用这个函数就会触发 esp_ble_gatts_register_callback() 注册的回调函数中的 ESP_GATTS_REG_EVT 事件
*/
ret = esp_ble_gatts_app_register(gl_profile_tab[PROFILE_B].app_id);
if (ret){
ESP_LOGE(GATTS_TAG, "gatts app register error, error code = %x", ret);
return;
}
/* 设置本地 MTU 为 23 字节,实际交换时刻的 MTU 要通过与客户端(client) 协商后得到。
* MTU 大小范围为 23 ~ 517 字节之间,如果没有调用该函数,则默认为 23 字节。
*/
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(23);
if (local_mtu_ret){
ESP_LOGE(GATTS_TAG, "set local MTU failed, error code = %x", local_mtu_ret);
}
/* 初始化广播参数 */
advertise_init();
return;
}
app_main
NVS 初始化
- 首先我们来看
app_main()
函数。在该函数中,我们先初始化了 NVS 分区。其实这个部分可以不要。他主要用于存储一些 RF(射频)校准数据,以确保无线通信的性能和稳定性。 - 当 ESP32 第一次启动并运行无线功能(如 Wi-Fi 或蓝牙)时,它会进行 RF 校准,以确定在当前硬件和环境条件下的最佳射频参数。
- 校准过程的结果会被存储在 NVS 中,这样在后续启动时,设备可以直接使用这些校准数据,避免每次启动都需要重新校准。从而提高设备运行效率。
// 初始化 NVS.
ret = nvs_flash_init();
/* 如果 NVS 分区没有剩余空间或者 NVS 分区包含新格式数据,当前版本无法识别。*/
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
/* 清空 NVS 分区数据 */
ESP_ERROR_CHECK(nvs_flash_erase());
/* NVS 分区重新进行初始化 */
ret = nvs_flash_init();
}
ESP_ERROR_CHECK( ret );
- 如果是 NVS 分区没有 RF(射频)校准数据,那么就会出现如下日志信息,之后就开始 RF 校准,并将相关校准信息存放进 NVS 分区。
W (602) phy_init: failed to load RF calibration data (0x1102), falling back to full calibration
W (642) phy_init: saving new calibration data because of checksum failure, mode(2)
- 如果不初始化 NVS 分区,将会出现如下错误。虽然不影响程序的正常运行,但是这样每次芯片启动都需要进行 RF 校准,从而拖慢启动速度。
// 将 NVS 初始化注释掉
// // 初始化 NVS.
// ret = nvs_flash_init();
// /* 如果 NVS 分区没有剩余空间或者 NVS 分区包含新格式数据,当前版本无法识别。*/
// if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
// /* 清空 NVS 分区数据 */
// ESP_ERROR_CHECK(nvs_flash_erase());
// /* NVS 分区重新进行初始化 */
// ret = nvs_flash_init();
// }
// ESP_ERROR_CHECK( ret );
E (599) phy_init: esp_phy_load_cal_data_from_nvs: NVS has not been initialized. Call nvs_flash_init before starting WiFi/BT.
W (619) phy_init: failed to load RF calibration data (0x1101), falling back to full calibration
W (659) phy_init: saving new calibration data because of checksum failure, mode(2)
I (659) phy: libbtbb version: b97859f, Jun 4 2024, 16:44:27
E (669) BT_OSI: config_new: NVS not initialized. Call nvs_flash_init before initializing bluetooth.
W (679) BT_BTC: btc_config_init unable to load config file; starting unconfigured.
E (689) BT_OSI: config_save: NVS not initialized. Call nvs_flash_init before initializing bluetooth.
E (709) BT_OSI: config_save, err_code: 0x2
E (729) BT_OSI: config_save: NVS not initialized. Call nvs_flash_init before initializing bluetooth.
E (729) BT_OSI: config_save, err_code: 0x2
E (739) BT_OSI: config_save: NVS not initialized. Call nvs_flash_init before initializing bluetooth.
E (749) BT_OSI: config_save, err_code: 0x2
E (759) BT_OSI: config_save: NVS not initialized. Call nvs_flash_init before initializing bluetooth.
E (779) BT_OSI: config_save, err_code: 0x2
E (779) BT_OSI: config_save: NVS not initialized. Call nvs_flash_init before initializing bluetooth.
E (799) BT_OSI: config_save, err_code: 0x2
协议栈初始化
- 我们需要知道,ESP32 是存在两个存在两个蓝牙协议栈的。
- Bluedroid(默认) : 支持传统蓝牙(BR/EDR)和低功耗蓝牙(BLE)。同时涉及传统蓝牙(BR/EDR) 和低功耗蓝牙(BLE) 的用例应当使用该协议栈。
- Apache NimBLE : 仅支持低功耗蓝牙(BLE)。仅涉及低功耗蓝牙(BLE),建议使用该协议栈,因为在代码占用和运行时,NimBLE 对内存的要求较低。
- 这里我将介绍的是 Bluedroid 协议栈开发。但是需要注意,虽然我们用的是 Bluedroid 协议栈,但实际情况却仅仅用到了 低功耗蓝牙(BLE) 的功能。因为,我们需要释放 传统蓝牙(BR/EDR) 的协议栈。
- 对蓝牙开发结构不清楚的朋友,可以看看 BLE学习笔记(0.0) —— 基础概念(0)
/* 释放经典蓝牙协议栈空间 */
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
/* 初始化 Control 层 */
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
if (ret) {
ESP_LOGE(GATTS_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
/* 使能 Control 层 */
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
if (ret) {
ESP_LOGE(GATTS_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
return;
}
/* 初始化 HOST层,Bluedroid 协议栈 */
ret = esp_bluedroid_init();
if (ret) {
ESP_LOGE(GATTS_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
/* 使能 HOST层,Bluedroid 协议栈 */
ret = esp_bluedroid_enable();
if (ret) {
ESP_LOGE(GATTS_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
return;
}
- 通过上述操作,我们成功的初始化了ESP32 的 bluedroid 协议栈。那么,此时就需要创建 GAP 和 GATT 任务。当我们触发 GAP 或者 GATT 事件时候,就会进入到如下的回调函数中。需要注意,如下的两个函数只能注册一次。
/* 注册 GATTS 事件回调函数 */
ret = esp_ble_gatts_register_callback(gatts_event_handler);
if (ret){
ESP_LOGE(GATTS_TAG, "gatts register error, error code = %x", ret);
return;
}
/* 注册 GAP 事件回调函数 */
ret = esp_ble_gap_register_callback(gap_event_handler);
if (ret){
ESP_LOGE(GATTS_TAG, "gap register error, error code = %x", ret);
return;
}
- 上述回调函数注册完成后,我们就可以根据需求来创建服务信息。这里我后续回进一步讲解。
/* 注册一个app_id, 协议栈将会分配一个对应的 gatts_if,用于标识一个 GATT 服务。
* 调用这个函数就会触发 esp_ble_gatts_register_callback() 注册的回调函数中的 ESP_GATTS_REG_EVT 事件
*/
ret = esp_ble_gatts_app_register(gl_profile_tab[PROFILE_A].app_id);
if (ret){
ESP_LOGE(GATTS_TAG, "gatts app register error, error code = %x", ret);
return;
}
/* 注册一个app_id, 协议栈将会分配一个对应的 gatts_if,用于标识一个 GATT 服务。
* 调用这个函数就会触发 esp_ble_gatts_register_callback() 注册的回调函数中的 ESP_GATTS_REG_EVT 事件
*/
ret = esp_ble_gatts_app_register(gl_profile_tab[PROFILE_B].app_id);
if (ret){
ESP_LOGE(GATTS_TAG, "gatts app register error, error code = %x", ret);
return;
}
- 当我们希望每一次传输的数据更多,能够拥有更大的吞吐量,那么就可以调用这个函数设置本地的 MTU。需要注意的一点是,这个实际的 MTU 大小是要根据与客户端(client) 协商后得到的。
/* 设置本地 MTU 为 500 字节,实际交换时刻的 MTU 要通过与**客户端(client)** 协商后得到。
* MTU 大小范围为 23 ~ 517 字节之间,如果没有调用该函数,则默认为 23 字节。
*/
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500);
if (local_mtu_ret){
ESP_LOGE(GATTS_TAG, "set local MTU failed, error code = %x", local_mtu_ret);
}
- 当进行完成上述操作后,我们可以初始化广播包了。需要注意的一点是,这里有一个宏定义
CONFIG_SET_RAW_ADV_DATA
。如果我们希望广播包的内容更加灵活,就可以定义CONFIG_SET_RAW_ADV_DATA
宏。如果是新手,还是不建议打开这个宏。 - 如果不打开这个宏,那么我们广播数据就会被局限为发送名称、发射功率、连接间隔、外观、厂商自定义数据、服务 UUID 和数据、广播发现模式标志位。如果你希望发送一些其他格式的数据例如 BTHome,使用
esp_ble_gap_config_adv_data()
函数就不是合适的选择。那么我们就需要使用原始数据包函数。
static void advertise_init(void)
{
esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(TEST_DEVICE_NAME);
if (set_dev_name_ret){
ESP_LOGE(GATTS_TAG, "set device name failed, error code = %x", set_dev_name_ret);
}
#ifdef CONFIG_SET_RAW_ADV_DATA
esp_err_t raw_adv_ret = esp_ble_gap_config_adv_data_raw(raw_adv_data, sizeof(raw_adv_data));
if (raw_adv_ret){
ESP_LOGE(GATTS_TAG, "config raw adv data failed, error code = %x ", raw_adv_ret);
}
adv_config_done |= adv_config_flag;
esp_err_t raw_scan_ret = esp_ble_gap_config_scan_rsp_data_raw(raw_scan_rsp_data, sizeof(raw_scan_rsp_data));
if (raw_scan_ret){
ESP_LOGE(GATTS_TAG, "config raw scan rsp data failed, error code = %x", raw_scan_ret);
}
adv_config_done |= scan_rsp_config_flag;
#else
//config adv data
esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
if (ret){
ESP_LOGE(GATTS_TAG, "config adv data failed, error code = %x", ret);
}
adv_config_done |= adv_config_flag;
//config scan response data
ret = esp_ble_gap_config_adv_data(&scan_rsp_data);
if (ret){
ESP_LOGE(GATTS_TAG, "config scan response data failed, error code = %x", ret);
}
adv_config_done |= scan_rsp_config_flag;
#endif
}
GATT 回调事件
GATT 服务启动流程
- 现在我们开始看程序执行流程。当我们调用
esp_ble_gatts_app_register()
注册一个 APP 任务,就会触发 ESP_GATTS_REG_EVT 事件。这里需要注意的一点是,APP ID 可以为 0 ~ 65535 的任意值,但是不能重复。 - 之后我们需要在 ESP_GATTS_REG_EVT 事件中将协议栈分配的 gatts_if 进行存储。同时利用
esp_ble_gatts_create_service()
函数注册一个首要服务。 -
esp_ble_gatts_create_service()
函数执行完成后,将会触发 ESP_GATTS_CREATE_EVT 事件。在该事件中,我们可以调用esp_ble_gatts_add_char()
函数创建对应的特征信息,并且调用esp_ble_gatts_start_service()
函数启动服务。 - 在调用
esp_ble_gatts_add_char()
函数之后,将会触发 ESP_GATTS_ADD_CHAR_EVT 事件。如果特征信息中,含有 ESP_GATT_CHAR_PROP_BIT_NOTIFY 或者 ESP_GATT_CHAR_PROP_BIT_INDICATE ,那么就必须调用esp_ble_gatts_add_char_descr()
函数创建一个 特征配置描述符(characteristic descriptor) 。 - 在我们调用
esp_ble_gatts_start_service()
函数启动服务之后,将会触发 ESP_GATTS_START_EVT 事件,用于通知服务端(server)应用程序服务启动成功。 - 在我们调用
esp_ble_gatts_add_char_descr()
函数创建 特征配置描述符(characteristic descriptor) 后,将会触发 ESP_GATTS_ADD_CHAR_DESCR_EVT 事件。 - 例如下方,我们的日志打印信息可以看到任务执行流程。
I (741) GATTS_DEMO: GATTS event = 0
I (741) GATTS_DEMO: gatts_if = 4,param->reg.app_id = 0x66
I (751) GATTS_DEMO: REGISTER_APP_EVT, status 0, app_id 102
I (761) GATTS_DEMO: GATTS event = 7
I (771) GATTS_DEMO: CREATE_SERVICE_EVT, status 0, service_handle 44
I (781) GATTS_DEMO: GATTS event = 9
I (791) GATTS_DEMO: ADD_CHAR_EVT, status 0, attr_handle 46, service_handle 44
I (801) GATTS_DEMO: GATTS event = 12
I (801) GATTS_DEMO: SERVICE_START_EVT, status 0, service_handle 44
I (811) GATTS_DEMO: GATTS event = 10
- ESP_GATTS_REG_EVT : APP 注册事件。当调用
esp_ble_gatts_app_register()
函数之后,会触发该。 - ESP_GATTS_CREATE_EVT : 服务创建完成事件。当调用
esp_ble_gatts_create_service()
函数之后,会触发该事件。 - ESP_GATTS_ADD_CHAR_EVT : 特征申明(characteristic declaration) 和 特征值(characteristic value) 添加成功事件。 当调用
esp_ble_gatts_add_char()
函数之后触发该事件。 - ESP_GATTS_START_EVT : GATT 服务启动成功事件。当调用
esp_ble_gatts_start_service()
函数之后,会触发该事件。 - ESP_GATTS_ADD_CHAR_DESCR_EVT : 特征配置描述符(characteristic descriptor) 添加成功事件。当调用
esp_ble_gatts_add_char_descr()
函数之后,触发该事件。
连接/断连事件
- 当成功与客户端(client) 建立连接,触发 ESP_GATTS_CONNECT_EVT 事件。我们在该事件中利用
esp_ble_gap_update_conn_params()
函数发起连接参数更新请求。 - 这里有两个注意的点,因为我们注册了两个 APP,因此我们会发现日志信息中有两次 ESP_GATTS_CONNECT_EVT 事件触发。
I (1619781) GATTS_DEMO: GATTS event = 14
I (1619791) GATTS_DEMO: ESP_GATTS_CONNECT_EVT, conn_id 0, remote 56:ae:b5:84:6f:16:
I (1619801) GATTS_DEMO: GATTS event = 14
I (1619811) GATTS_DEMO: CONNECT_EVT, conn_id 0, remote 56:ae:b5:84:6f:16:
- 正因为 ESP_GATTS_CONNECT_EVT 事件会触发两次,因此我们将连接参数更新的内容放在了 A 服务的回调函数中。如果放在公共的 GATT 回调函数中,那么连接参数更新将会被调用两次,而这两次的间隔事件太短,因此会出现如下警告。
W (22451) BT_L2CAP: l2cble_start_conn_update, the last connection update command still pending.
- 当与客户端(client) 连接断开,将会触发 ESP_GATTS_DISCONNECT_EVT 事件。
I (2275581) GATTS_DEMO: GATTS event = 15
I (2275581) GATTS_DEMO: ESP_GATTS_DISCONNECT_EVT, disconnect reason 0x13
- ESP_GATTS_CONNECT_EVT : 当与客户端(client) 连接,触发该事件。
- ESP_GATTS_DISCONNECT_EVT : 与客户端(client) 断开连接时候,触发该事件。
读事件触发流程
- 当我们客户端(client) 发送读请求时,将会触发 ESP_GATTS_READ_EVT 事件。
- 在该事件中,我们将要返回给客户端(client) 的内容,通过
esp_ble_gatts_send_response()
函数返回。需要注意,一定要调用esp_ble_gatts_send_response()
函数进行返回,否则客户端(client) 将会一直死等数据,直到断连。
I (2029071) GATTS_DEMO: GATTS event = 1
I (2029071) GATTS_DEMO: GATT_READ_EVT, conn_id 0, trans_id 1, handle 46
I (2029071) GATTS_DEMO: GATTS event = 21
- 当调用
esp_ble_gatts_send_response()
函数之后,将会触发 ESP_GATTS_RESPONSE_EVT 事件,表示回包数据发送成功。
- ESP_GATTS_READ_EVT : 当客户端(client) 发起读请求,触发该事件。
- ESP_GATTS_RESPONSE_EVT : GATT 服务回报完成。该事件由
esp_ble_gatts_send_response()
函数执行完成后触发。
写事件触发流程
Command 写
- 当我们连接上设备后,点击发送按键。
2. 按照如下步骤发送数据。
3. 此时 ESP32 将会触发 ESP_GATTS_WRITE_EVT 事件。
- param->write.conn_id : 如果是多连接,我们可以利用这个参数判断是那个主机发送的操作数据。因为该实验是一台手机作为客户端(client) ,一个 ESP32 作为服务端(Server)的点对点通讯,因此这个参数用不上。
- param->write.trans_id : 用于标识一次写操作。因为客户端(client) 可能在同一时间对服务端(server)存在大量的写操作,那么就可以利用这个参数来确定服务端(server)到底是在对哪一个写操作回复。
- param->write.handle : 用于表示是哪一个目标特征(Characteristic)或描述符(descriptor)的写操作。
- param->write.need_rsp : 是否需要服务端(server)进行回复。因为这里我们是 Command,因此该参数是 false。
- param->write.is_prep : 是否为准备写操作。当写入的数据大于 MTU-3 时候,将进行分段写数据,那么该参数为 true。
- param->write.len : 写入数据值长度。
- param->write.value : 写入的数据值。
- 通过上面的分析,我们现在就可以知道了,手机作为客户端(client) 进行 Command 写操作能够触发 ESP32 的 ESP_GATTS_WRITE_EVT 事件,而且无需回应,因此打印信息如下。
I (12711) GATTS_DEMO: GATTS event = 2
I (12711) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 1, handle 46
I (12711) GATTS_DEMO: It's not prepare write, value len 2, value :
I (12721) GATTS_DEMO: 55 66
I (12731) GATTS_DEMO: write event, but not need to response
- ESP_GATTS_WRITE_EVT : 当客户端(client) 发起写请求,触发该事件。
Request 写
- 同理,我们执行写操作,只不过这次我们需要服务端(server)进行数据回复。
2. 当客户端(client) 执行如上操作,将会触发 ESP_GATTS_WRITE_EVT 事件,在该事件中,我们调用 example_write_event_env()
处理写操作。
3. 因为客户端(client) 是需要数据回应。并且写入的数据小于 MTU - 3(初始化的时候。调用 esp_ble_gatt_set_local_mtu()
,函数设置的 MTU 为23),因此 param->write.need_rsp 为 false ,不是准备写操作,直接调用 esp_ble_gatts_send_response()
进行数据包的回复。
4. 在调用 esp_ble_gatts_send_response()
函数之后,将会触发 ESP_GATTS_RESPONSE_EVT 事件告诉应用层表示回包数据发送完成。
I (56091) GATTS_DEMO: GATTS event = 2
I (56091) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 3, handle 46
I (56091) GATTS_DEMO: It's not prepare write, value len 2, value :
I (56101) GATTS_DEMO: 11 22
I (56111) GATTS_DEMO: write event, but not prepare
I (56121) GATTS_DEMO: GATTS event = 21
- ESP_GATTS_WRITE_EVT : 当客户端(client) 发起写请求,触发该事件。
- ESP_GATTS_RESPONSE_EVT : GATT 服务回报完成。该事件由
esp_ble_gatts_send_response()
函数执行完成后触发。
prepare write
- 我们需要按照如下方法将双方的 MTU 设置为 23。
- 需要注意一点,prepare write 操作一定需要选择 Request。
- 之后我们可以看到如下日志信息,我们一步一步来进行分析。
- 首先我们触发 ESP_GATTS_WRITE_EVT 事件发现这是一个准备写请求。那么就直接进入
example_write_event_env()
函数操作。因为在 prepare wirte 请求中,那么需要进行一些堆空间,以及判断传入数据是否超出堆空间大小。最终将收到的数据利用esp_ble_gatts_send_response()
函数发送回去,并且存储在 a_prepare_write_env 或者 b_prepare_write_env 准备缓冲区。 -
esp_ble_gatts_send_response()
函数会触发 ESP_GATTS_RESPONSE_EVT 事件表示发送了回包数据。 - 客户端(client) 收到回包数据后,继续将剩余的数据发送出来。此时再次触发 ESP_GATTS_WRITE_EVT 事件,因为这是 prepare wirte,因此进入
example_write_event_env()
函数操作,继续将发送回包,并且将剩余数据存储进 a_prepare_write_env 或者 b_prepare_write_env 准备缓冲区。 -
esp_ble_gatts_send_response()
函数会触发 ESP_GATTS_RESPONSE_EVT 事件表示发送了回包数据。 - 客户端(client) 收到回包之后,发现数据发完了,此时执行 Execute Write Request,然后 ESP32 触发 ESP_GATTS_EXEC_WRITE_EVT 事件。在该事件中,ESP32 需要调用
esp_ble_gatts_send_response()
函数回发响应数据。然后调用example_exec_write_event_env()
函数将收到的数据进行打印处理,并且释放申请到的缓冲区。
I (45331) GATTS_DEMO: GATTS event = 2
I (45331) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 1, handle 46
I (45331) GATTS_DEMO: GATTS event = 21
I (45391) GATTS_DEMO: GATTS event = 2
I (45391) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 2, handle 46
I (45391) GATTS_DEMO: GATTS event = 21
I (45451) GATTS_DEMO: GATTS event = 3
I (45451) GATTS_DEMO: ESP_GATTS_EXEC_WRITE_EVT
I (45451) GATTS_DEMO: 00 11 22 33 44 55 66 77 88 99 00 11 22 33 44 55
I (45451) GATTS_DEMO: 66 77 88 99 00
I (45461) GATTS_DEMO: GATTS event = 21
- ESP_GATTS_WRITE_EVT : 当客户端(client) 发起写请求,触发该事件。
- ESP_GATTS_RESPONSE_EVT : GATT 服务回报完成。该事件由
esp_ble_gatts_send_response()
函数执行完成后触发。 - ESP_GATTS_EXEC_WRITE_EVT : 客户端(client) 发送 Execute Write Request 触发该事件。
特征配置描述符(characteristic descriptor) 写
- 我们点击如下按键可以使能 notify 功能。
- 亦或者可以通过如下方法数据数据使能 notify。
3. 这里感觉有个 bug,理论上来说,notify 应该是不需要回包的,因此不会触发 ESP_GATTS_CONF_EVT 事件的,但是这里依旧有触发,这里建议抓包测试一下。
4. 除了 ESP_GATTS_CONF_EVT 事件,其他两个事件就很好理解了。客户端(client) 向 ESP32 特征配置描述 中写入数据是需要回包的,因此就会触发 ESP_GATTS_WRITE_EVT 和 ESP_GATTS_RESPONSE_EVT 事件。
I (14481) GATTS_DEMO: GATTS event = 2
I (14481) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 1, handle 47
I (14481) GATTS_DEMO: It's not prepare write, value len 2, value :
I (14491) GATTS_DEMO: 01 00
I (14501) GATTS_DEMO: profile b notify enable
I (14511) GATTS_DEMO: write event, but not prepare
I (14521) GATTS_DEMO: GATTS event = 21
I (14521) GATTS_DEMO: GATTS event = 5
I (14531) GATTS_DEMO: ESP_GATTS_CONF_EVT status 0 attr_handle 46
- 当我们启动 indicate 或者输入 02 00 时候,将会触发如下事件。需要注意的一点是,notify 和 indicate 两者只能存在一个。
I (94731) GATTS_DEMO: GATTS event = 2
I (94731) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 3, handle 47
I (94731) GATTS_DEMO: It's not prepare write, value len 2, value :
I (94741) GATTS_DEMO: 02 00
I (94751) GATTS_DEMO: indicate enable
I (94751) GATTS_DEMO: write event, but not prepare
I (94761) GATTS_DEMO: GATTS event = 21
I (94821) GATTS_DEMO: GATTS event = 5
I (94821) GATTS_DEMO: ESP_GATTS_CONF_EVT status 0 attr_handle 46
- 当关闭 indicate/notify 或者输入 00 00 时候,将触发如下事件。需要注意的是,如果要关,那么两个都会同时关闭。
I (42801) GATTS_DEMO: GATTS event = 2
I (42801) GATTS_DEMO: GATT_WRITE_EVT, conn_id 0, trans_id 2, handle 47
I (42801) GATTS_DEMO: It's not prepare write, value len 2, value :
I (42811) GATTS_DEMO: 00 00
I (42821) GATTS_DEMO: notify/indicate disable
I (42831) GATTS_DEMO: write event, but not prepare
I (42831) GATTS_DEMO: GATTS event = 21
- ESP_GATTS_WRITE_EVT : 当客户端(client) 发起写请求,触发该事件。
- ESP_GATTS_RESPONSE_EVT : GATT 服务回报完成。该事件由
esp_ble_gatts_send_response()
函数执行完成后触发。 - ESP_GATTS_CONF_EVT : 调用
esp_ble_gatts_send_indicate()
函数触发。
GAP 回调事件
GAP 服务初始化
- 在注册完 APP 之后,我们调用 advertise_init() 函数初始化广播数据。当我们调用
esp_ble_gap_config_adv_data()
函数时候,将会触发 ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT 事件。 - 之后调用
esp_ble_gap_config_adv_data()
函数,触发 ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT 事件。在该事件里面,我们调用esp_ble_gap_start_advertising()
函数启动广播。 -
esp_ble_gap_start_advertising()
函数将会触发 ESP_GAP_BLE_ADV_START_COMPLETE_EVT 事件。我们可以在该事件中知道广播是否成功启动。
I (841) GATTS_DEMO: GAP Event:0
I (841) GATTS_DEMO: GAP Event:1
I (851) GATTS_DEMO: GAP Event:6
- ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT : 设置广播数据完成事件。当调用
esp_ble_gap_config_adv_data()
函数,其中 set_scan_rsp 设置为 false 时触发该事件。 - ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT : 设置广播回包数据完成事件。当调用
esp_ble_gap_config_adv_data()
函数,其中 set_scan_rsp 设置为 true 时触发该事件。 - ESP_GAP_BLE_ADV_START_COMPLETE_EVT : 当调用
esp_ble_gap_start_advertising()
函数触发。
连接事件
- 在连接过程中需要协商双方的收发数据,因此会触发 ESP_GAP_BLE_SET_PKT_LENGTH_COMPLETE_EVT 事件。
- 在连接成功后,ESP_GATTS_CONNECT_EVT 事件中调用的
esp_ble_gap_update_conn_params()
函数,将会触发连接参数更新,因此将会触发 ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT 事件。我们可以在该事件中看到最终的从机延迟,连接间隔,监管超时等信息。
I (1619781) GATTS_DEMO: GAP Event:21
I (1619781) GATTS_DEMO: packet length updated: rx = 27, tx = 251, status = 0
# ... 这里省略 GATT 回调
I (1620171) GATTS_DEMO: GAP Event:20
I (1620171) GATTS_DEMO: update connection params status = 0, min_int = 16, max_int = 32,conn_int = 6,latency = 0, timeout = 500
I (1620501) GATTS_DEMO: GAP Event:20
I (1620501) GATTS_DEMO: update connection params status = 0, min_int = 0, max_int = 0,conn_int = 24,latency = 0, timeout = 400
参考
- ESP32 蓝牙 API
- BLE学习笔记(0.0) —— 基础概念(0)
- 乐鑫论坛 : Write a string to characteristic