在上一篇章大致了解了Eddystone数据包的结构,这篇文章使用两块ESP32通过广播进行收发实验,首先要讲述一下接收端和发送端的配置过程
首先打开ESP32官方的demo例程并分析Eddystone启动过程
void esp_eddystone_appRegister(void)
{
esp_err_t status;
ESP_LOGI(DEMO_TAG,"Register callback");
/*<! register the scan callback function to the gap module */
//注册GAP应用层回调函数--ESP_GAP_CB()
if((status = esp_ble_gap_register_callback(esp_gap_cb)) != ESP_OK) {
ESP_LOGE(DEMO_TAG,"gap register error: %s", esp_err_to_name(status));
return;
}
}
void esp_eddystone_init(void)
{
esp_bluedroid_init();
esp_bluedroid_enable();
esp_eddystone_appRegister();
}
void app_main(void)
{
//芯片初始化
ESP_ERROR_CHECK(nvs_flash_init());
//释放经典蓝牙控制器内存
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_eddystone_init();
/*<! set scan parameters */
esp_ble_gap_set_scan_params(&ble_scan_params);
}
可见,在main函数里,首先进行了芯片初始化、释放控制器内存、开启BLE(低功耗蓝牙)之后再调用esp_eddystone_init()进行eddystone的初始化,在eddystone初始化函数里进行了蓝牙初始化和开启,随后调用esp_eddystone_appRegister()进行GAP层回调函数的注册。
问题来了,①什么是GAP层?②为什么要进行GAP层回调函数的注册?
答:
①GAP层在蓝牙4.2里位于主机层面中,GAP 层的主要功能是定义设备的访问模式、设备发现、连接建立与管理、安全性等策略。暂时了解这些即可
②这是因为蓝牙设备的交互是基于事件驱动的。在蓝牙通信过程中,会发生各种各样的事件,比如设备发现新的蓝牙设备、连接状态发生变化、接收到新的数据等。通过注册 GAP 回调函数,应用程序可以对这些事件做出响应。给出的代码示例中,esp_gap_cb
就是一个回调函数,当 GAP 层发生特定事件时,系统会自动调用这个函数。
在该示例中esp_gap_cb就是需要注册的回调函数,其函数具体内容具体如下
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param)
{
esp_err_t err;
switch(event)
{
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: {
uint32_t duration = 0;
esp_ble_gap_start_scanning(duration);
break;
}
case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: {
if((err = param->scan_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(DEMO_TAG,"Scan start failed: %s", esp_err_to_name(err));
}
else {
ESP_LOGI(DEMO_TAG,"Start scanning...");
}
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_eddystone_result_t eddystone_res;
memset(&eddystone_res, 0, sizeof(eddystone_res));
//判断是否是Eddystone广播包格式
esp_err_t ret = esp_eddystone_decode(scan_result->scan_rst.ble_adv, scan_result->scan_rst.adv_data_len, &eddystone_res);
if (ret) {
// error:The received data is not an eddystone frame packet or a correct eddystone frame packet.
// just return
return;
} else {
// The received adv data is a correct eddystone frame packet.
// Here, we get the eddystone infomation in eddystone_res, we can use the data in res to do other things.
// For example, just print them:
ESP_LOGI(DEMO_TAG, "--------Eddystone Found----------");
esp_log_buffer_hex("EDDYSTONE_DEMO: Device address:", scan_result->scan_rst.bda, ESP_BD_ADDR_LEN);
ESP_LOGI(DEMO_TAG, "RSSI of packet:%d dbm", scan_result->scan_rst.rssi);
esp_eddystone_show_inform(&eddystone_res);
}
break;
}
default:
break;
}
break;
}
case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:{
if((err = param->scan_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(DEMO_TAG,"Scan stop failed: %s", esp_err_to_name(err));
}
else {
ESP_LOGI(DEMO_TAG,"Stop scan successfully");
}
break;
}
default:
break;
}
}
通过分析可以得出其运行逻辑,通过switchcase来判断蓝牙事件的类别,并根据不同的类别做出不同的具体动作。重点关注ESP_GAP_BLE_SCAN_RESULT_EVT,这是一个事件常量,表示蓝牙低功耗扫描操作产生了扫描结果。当蓝牙控制器完成一次扫描,发现了周围的蓝牙设备并接收到其广播数据时,就会触发这个事件。程序进入这个 case
分支来处理扫描结果。在case分支ESP_GAP_SEARCH_INQ_RES_EVT,这代表收到了周围设备的一个扫描响应包。若进入该分支,则先判断该包是否是一个Eddystone广播包,如不是则返回,如果是,则调用 esp_eddystone_show_inform(&eddystone_res),这个函数的实现如下
static void esp_eddystone_show_inform(const esp_eddystone_result_t* res)
{
switch(res->common.frame_type)
{
case EDDYSTONE_FRAME_TYPE_UID: {
ESP_LOGI(DEMO_TAG, "Eddystone UID inform:");
ESP_LOGI(DEMO_TAG, "Measured power(RSSI at 0m distance):%d dbm", res->inform.uid.ranging_data);
ESP_LOGI(DEMO_TAG, "EDDYSTONE_DEMO: Namespace ID:0x");
esp_log_buffer_hex(DEMO_TAG, res->inform.uid.namespace_id, 10);
ESP_LOGI(DEMO_TAG, "EDDYSTONE_DEMO: Instance ID:0x");
esp_log_buffer_hex(DEMO_TAG, res->inform.uid.instance_id, 6);
break;
}
case EDDYSTONE_FRAME_TYPE_URL: {
ESP_LOGI(DEMO_TAG, "Eddystone URL inform:");
ESP_LOGI(DEMO_TAG, "Measured power(RSSI at 0m distance):%d dbm", res->inform.url.tx_power);
ESP_LOGI(DEMO_TAG, "URL: %s", res->inform.url.url);
break;
}
case EDDYSTONE_FRAME_TYPE_TLM: {
ESP_LOGI(DEMO_TAG, "Eddystone TLM inform:");
ESP_LOGI(DEMO_TAG, "version: %d", res->inform.tlm.version);
ESP_LOGI(DEMO_TAG, "battery voltage: %d mV", res->inform.tlm.battery_voltage);
ESP_LOGI(DEMO_TAG, "beacon temperature in degrees Celsius: %6.1f", res->inform.tlm.temperature);
ESP_LOGI(DEMO_TAG, "adv pdu count since power-up: %d", res->inform.tlm.adv_count);
ESP_LOGI(DEMO_TAG, "time since power-up: %d s", (res->inform.tlm.time)/10);
break;
}
default:
break;
}
}
这个函数内部做了一些打印信息,打印了UID、URL、TLM信息,这些信息便是上一篇文章提到的Eddystone广播包格式里的关键信息
分析完这一套逻辑,再留意一下main函数里的最后一行,esp_ble_gap_set_scan_params(&ble_scan_params),如果说刚才提到的代码都是官方的demo不需要去改,那这个地方就需要我们自己去根据实际情况去修改了,跳转到ble_scan_params的定义
static esp_ble_scan_params_t ble_scan_params = {
.scan_type = BLE_SCAN_TYPE_ACTIVE,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
.scan_interval = 0x50,
.scan_window = 0x30,
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE
};
这些东西对初学者来说是比较乱的,也不知道每个成员的作用是什么,这个时候我们可以看看乐鑫官网的文档https://docs.espressif.com/projects/esp-idf/zh_CN/release-v5.0/esp32/api-reference/bluetooth/esp_gap_ble.html#_CPPv421esp_ble_scan_params_t
这是官方文档的描述,大致了解即可
对于接收端,以上代码不需要进行任何修改,只需读懂
以上便是接收端demo的大致框架,现在看看发送端
打开同一目录下的ble_throughput,打开server端工程即可
广播数据包adv的代码部分:
static esp_ble_adv_data_t adv_data = {
.set_scan_rsp = false,
.include_name = true,
.include_txpower = true,
.min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec
.max_interval = 0x000C, //slave connection max interval, Time = max_interval * 1.25 msec
.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 = 32,
.p_service_uuid = adv_service_uuid128,
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};
官方文档的解释如图
这个flag标志位指被发现的模式,具体指的AD结构中的类型,通过八个比特位去判断被发现的模式
对这个结构体进行设置,并调用 esp_ble_gap_config_adv_data进行广播包配置,部分代码如下
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\n", param->reg.status, param->reg.app_id);
gl_profile_tab[PROFILE_A_APP_ID].service_id.is_primary = true;
gl_profile_tab[PROFILE_A_APP_ID].service_id.id.inst_id = 0x00;
gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16;
gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_A;
gl_profile_tab[PROFILE_A_APP_ID].gatts_if = gatts_if;
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_EXAMPLE_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
在进行广播数据包配置操作之后,便通过特定标志位adv_config_done和中断事件 去开启广播
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
#ifdef CONFIG_EXAMPLE_SET_RAW_ADV_DATA
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
adv_config_done &= (~adv_config_flag);
if (adv_config_done==0){
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
adv_config_done &= (~scan_rsp_config_flag);
if (adv_config_done==0){
esp_ble_gap_start_advertising(&adv_params);
}
break;
#else
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
adv_config_done &= (~adv_config_flag);
if (adv_config_done == 0){
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
adv_config_done &= (~scan_rsp_config_flag);
if (adv_config_done == 0){
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\n");
}
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\n");
}
else {
ESP_LOGI(GATTS_TAG, "Stop adv successfully\n");
}
break;
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
ESP_LOGI(GATTS_TAG, "update connetion 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;
default:
break;
}
}
以上便是收发的大致流程,下次更具体的代码