ESP32 Bluedroid 篇(1)—— ibeacon 广播

前言

  1. 前面我们已经了解了 ESP32 的 BLE 整体架构,现在我们开始实际学习一下Bluedroid 从机篇的广播和扫描。
  2. 本文将会以 ble_ibeacon demo 为例子进行讲解,需要注意的一点是。ibeacon 分为两个部分,一个是作为广播者,一个是作为观察者IBEACON_RECEIVER 这个宏表示作为观察者IBEACON_SENDER 这个宏被置 1 表示为广播者
  3. 需要注意的一点是,本文先仅介绍广播相关内容

ibeacon 介绍

ibeacon 是什么?

  1. 作为一名初学者,当听到 ibeacon 时候,大概率是一脸懵逼的。即使网上搜索大量资料,没有亲身体验,也是一头雾水。为了方便各位理解,就以我来上海实习,周末逛的第一个景点 – 豫园 为例子进行分析。
  2. 当我们进入景点,肯定会有一个二维码建议我们扫描,然后之后就会有电子讲解功能。例如我现在扫描了豫园的二维码,打开了一个微信小程序,此时微信小程序上就能够显示出我的位置在哪里。

在这里插入图片描述
3. 如果你移动到一个地方,该小程序就会进行相关讲解当前景点的一些历史文化信息。此时,各位有没有想过一个问题,该小程序,是如何精确的知道我们当前是在哪个景点呢?
4. 此时,就是利用的 ibeacon 技术进行的。如果你有兴趣的话,可以在走到某个景点,发现微信小程序播报讲解时刻停下来,然后在这个附近十米内的范围转转,会惊奇的发现,一些地方藏有这种小方块。

在这里插入图片描述

在这里插入图片描述
5. 这个小方块,就是本文要进行讲解的,ESP32 作为广播者的功能。而你手机,就是充当的观察者

ibeacon 有什么用?

  1. 现在我们明白了 ibeacon 技术大概是什么东西了。那么这个有什么作用呢?从上面的例子我们就可以很好的知道,室内定位广播信息
  2. 当前,室内定位技术一直是一项值得探索的技术,ibeacon 可以说提供了一个不错的选择(不过个人感觉目前 BLE 室内定位更多的是聚焦于 AOA)。
  3. 同样,在商场,我们只需要打开微信小程序走到哪家店铺,就可以直接查看那家店铺的商品信息,这样一定程度上可以方便用户挑选商品。

ibeacon 工程介绍

工程源码

  1. 我们先拷贝 ble_ibeacon demo 例程出来,打开ibeacon_demo.c 文件,将其替换为如下内容。
/*
 * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Unlicense OR CC0-1.0
 */



/****************************************************************************
*
* This file is for iBeacon demo. It supports both iBeacon sender and receiver
* which is distinguished by macros IBEACON_SENDER and IBEACON_RECEIVER,
*
* iBeacon is a trademark of Apple Inc. Before building devices which use iBeacon technology,
* visit https://developer.apple.com/ibeacon/ to obtain a license.
*
****************************************************************************/

#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include "nvs_flash.h"

#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gattc_api.h"
#include "esp_gatt_defs.h"
#include "esp_bt_main.h"
#include "esp_bt_defs.h"
#include "esp_ibeacon_api.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"

static const char* DEMO_TAG = "IBEACON_DEMO";
extern esp_ble_ibeacon_vendor_t vendor_config;

#if (IBEACON_MODE == IBEACON_RECEIVER)
// 在停止扫描请求发送后,蓝牙堆栈可能还会处理一些尚未完成的扫描结果。因此需要通过这个标志位来设置是否需要继续处理扫描完成事件
static bool is_scanning = false;
#endif

///Declare static functions
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);

#if (IBEACON_MODE == IBEACON_RECEIVER)
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
};

#elif (IBEACON_MODE == IBEACON_SENDER)
static esp_ble_adv_params_t ble_adv_params = {
    .adv_int_min        = 0x20,                 // 0x20*0.625ms=20ms,Range: 0x0020 to 0x4000 (20ms to 10240ms)
    .adv_int_max        = 0x40,                 // 0x40*0.625ms=40ms
    .adv_type           = ADV_TYPE_NONCONN_IND, // 不可连接广播
    // .adv_type           = ADV_TYPE_DIRECT_IND_HIGH,  // 设置为高占空比定向广播
    // .peer_addr          = {0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0xF6},  // 目标设备MAC地址
    // .peer_addr_type     = BLE_ADDR_TYPE_PUBLIC,      // 目标设备的地址类型

    .own_addr_type      = BLE_ADDR_TYPE_PUBLIC, // 公共地址
    .channel_map        = ADV_CHNL_ALL,         // 在 37,38,39 频道广播
    .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, // 允许任何设备扫描和连接
};
#endif


static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
    esp_err_t err;
    ESP_LOGI(DEMO_TAG, "====> ESP_GAP_BLE_EVT %d <====", event);
    switch (event) {
#if (IBEACON_MODE == IBEACON_SENDER)
    case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: { // 原始广播数据设置完成事件
        if ((err = param->adv_data_raw_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
            ESP_LOGE(DEMO_TAG, "Set raw adv data failed: %s", esp_err_to_name(err));
            return;
        }
        esp_ble_gap_start_advertising(&ble_adv_params);
        break;
    }
    case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: { // 广播启动完成事件
        //adv start complete event to indicate adv start successfully or failed
        if ((err = param->adv_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
            ESP_LOGE(DEMO_TAG, "Adv start failed: %s", esp_err_to_name(err));
        }
        break;
    }
    case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: { // 广播停止完成事件
        if ((err = param->adv_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS){
            ESP_LOGE(DEMO_TAG, "Adv stop failed: %s", esp_err_to_name(err));
        }
        else {
            ESP_LOGI(DEMO_TAG, "Stop adv successfully");
        }
        break;
    }
#endif
#if (IBEACON_MODE == IBEACON_RECEIVER)
    case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { // 扫描参数设置完成事件
        //the unit of the duration is second, 0 means scan permanently
        uint32_t duration = 0;
        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(DEMO_TAG, "Scan start failed: %s", esp_err_to_name(err));
        } else {
            is_scanning = true;
        }
        break;
    case ESP_GAP_BLE_SCAN_RESULT_EVT: { // 扫描结果准备完毕事件
        if (is_scanning == false) { // 如果没有在扫描,则不处理扫描结果
            ESP_LOGW(DEMO_TAG, "Scan is not started yet");
            break;
        }
        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: {
            /* 搜索 BLE iBeacon 数据包 */
            if (esp_ble_is_ibeacon_packet(scan_result->scan_rst.ble_adv, scan_result->scan_rst.adv_data_len)) {
                esp_ble_ibeacon_t *ibeacon_data = (esp_ble_ibeacon_t*)(scan_result->scan_rst.ble_adv);
                ESP_LOGI(DEMO_TAG, "----------iBeacon Found----------");
                esp_log_buffer_hex("IBEACON_DEMO: Device address:", scan_result->scan_rst.bda, ESP_BD_ADDR_LEN );
                esp_log_buffer_hex("IBEACON_DEMO: Proximity UUID:", ibeacon_data->ibeacon_vendor.proximity_uuid, ESP_UUID_LEN_128);

                uint16_t major = ENDIAN_CHANGE_U16(ibeacon_data->ibeacon_vendor.major);
                uint16_t minor = ENDIAN_CHANGE_U16(ibeacon_data->ibeacon_vendor.minor);
                ESP_LOGI(DEMO_TAG, "Major: 0x%04x (%d)", major, major);
                ESP_LOGI(DEMO_TAG, "Minor: 0x%04x (%d)", minor, minor);
                ESP_LOGI(DEMO_TAG, "Measured power (RSSI at a 1m distance):%d dbm", ibeacon_data->ibeacon_vendor.measured_power);
                ESP_LOGI(DEMO_TAG, "RSSI of packet:%d dbm", scan_result->scan_rst.rssi);
                esp_err_t err = esp_ble_gap_stop_scanning();
                if (err != ESP_OK) {
                    ESP_LOGE(DEMO_TAG, "Stop scaning failed: %s", esp_err_to_name(err));
                } else {
                    is_scanning = false;
                    ESP_LOGI(DEMO_TAG, "Stop scaning"); 
                }
            }
            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;
    }
#endif
    default:
        break;
    }
}


void ble_ibeacon_appRegister(void)
{
    esp_err_t status;

    ESP_LOGI(DEMO_TAG, "register callback");

    /* 注册 GAP 回调函数 */
    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 ble_ibeacon_init(void)
{
    /* 初始化蓝牙 HOST 层 */
    esp_bluedroid_init();
    /* 使能蓝牙 HOST 层 */
    esp_bluedroid_enable();
    /* 注册 ibeacon */
    ble_ibeacon_appRegister();
}

void app_main(void)
{
    /* 初始化 NVS */
    ESP_ERROR_CHECK(nvs_flash_init());
    /* 释放经典蓝牙 Control 层内存 */
    ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
    /* 初始化 BLE Control 层 */
    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    esp_bt_controller_init(&bt_cfg);
    /* 启动 BLE Control 层 */
    esp_bt_controller_enable(ESP_BT_MODE_BLE);
    /* BLE Ibeacon 功能初始化 */
    ble_ibeacon_init();

#if (IBEACON_MODE == IBEACON_RECEIVER)
    /* 设置扫描参数 */
    esp_ble_gap_set_scan_params(&ble_scan_params);

#elif (IBEACON_MODE == IBEACON_SENDER)
    esp_ble_ibeacon_t ibeacon_adv_data;
    /* 填充 ibeacon 数据 */
    esp_err_t status = esp_ble_config_ibeacon_data (&vendor_config, &ibeacon_adv_data);
    if (status == ESP_OK) {
        for (int i = 0; i < sizeof(ibeacon_adv_data); i++) {
            ESP_LOGI(DEMO_TAG, "ibeacon_adv_data[%d] = 0x%x", i, *((uint8_t*)(&ibeacon_adv_data) + i));
        }
        /* 设置广播原始数据,此函数将会触发 ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT 事件 */
        esp_ble_gap_config_adv_data_raw((uint8_t*)&ibeacon_adv_data, sizeof(ibeacon_adv_data));
    }
    else {
        ESP_LOGE(DEMO_TAG, "Config iBeacon data failed: %s", esp_err_to_name(status));
    }
#endif
}

解析代码流程

  1. 现在,我们开始捋一遍 ibeacon 工程代码顺序以方便我们后续理解。

NVS 分区

  1. 首先是 NVS 分区的初始化,他主要用于存储一些 RF(射频)校准数据,以确保无线通信的性能和稳定性。
  2. 当 ESP32 第一次启动并运行无线功能(如 Wi-Fi 或蓝牙)时,它会进行 RF 校准,以确定在当前硬件和环境条件下的最佳射频参数。
  3. 校准过程的结果会被存储在 NVS 中,这样在后续启动时,设备可以直接使用这些校准数据,避免每次启动都需要重新校准。从而提高设备运行效率。
    /* 初始化 NVS */
    ESP_ERROR_CHECK(nvs_flash_init());
  1. 为了更为方便的理解 NVS 分区在当前的作用,我们可以进行如下实验。我们会发现,只有第一次才会产生 RF 校验数据失败,而后就将不再出现该信息。
  2. 这是因为,在第一次芯片启动时,芯片会去检测 NVS 分区是否存在 RF 校验数据。如果有,那么就马上利用该数据进行启动射频模块。如果没有,那么就先进行 RF 校准,然后将校准数据存储在 NVS 分区,方便后续芯片快速启动。
# 将 Flash 全部擦除,该命令必须执行,否则现象可能无法出现
idf.py erase-flash
# 重新烧录程序
idf.py flash monitor
# 烧录完成后,我们将能够看到这样的日志打印信息
# -------------
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)
# -------------
# 看到这条信息后,复位芯片,我们将看不到这条信息。
idf.py monitor

bluedroid 协议栈启动

  1. 如下为 bluedroid 协议栈启动代码,为什么这样编写,我已经在 ESP32 的 BLE 整体架构 这篇博客讲解,不再进行赘述。
    /* 释放经典蓝牙 Control 层内存 */
    ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
    /* 初始化 BLE Control 层 */
    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    esp_bt_controller_init(&bt_cfg);
    /* 启动 BLE Control 层 */
    esp_bt_controller_enable(ESP_BT_MODE_BLE);
    /* BLE Ibeacon 功能初始化 */
    ble_ibeacon_init();

esp_ble_gap_config_adv_data_raw() 设置广播数据

  1. 在设置广播数据之前,我们需要先知道 BLE 的广播数据包格式是什么样的。下面两张图即可展示出广播包的数据格式,具体含义请阅读 BTHome数据格式解析 的 BLE 广播包格式 章节,此处不做赘述。
    在这里插入图片描述
    在这里插入图片描述
  2. 了解了 BLE 广播格式之后,现在我们要做的就是分析一下 ibeacon 的广播包 AD Structure 应该如何编写。
  3. 首先,一个 BLE 广播包必须存在 0x01 类型的广播数据,该广播主要是用于指示当前设备能进行的一些行为。因为我们当前是单模,因此需要置位 bit1 和 bit2,因此 AD Data 为 0x06。
bit描述
0有限可发现模式
1一般可发现模式
2不支持 BR/EDR ,当前为单模设备
3设备同时支持 LE 和 BR/EDR 的 Control
4设备同时支持 LE 和 BR/EDR 的 HOST,需要注意,在Core Specification Supplement 10 该位被取消
5~7保留
  1. 通过上面分析,因此我们可以得到,第一个 flags 应该填入的数据内容如下:
esp_ble_ibeacon_head_t ibeacon_common_head = {
    .flags = {0x02, 0x01, 0x06},
    // ...
};
  1. 分析完 Flags 的数据包,我们再来看看最关键的 ibeacon 数据格式。
    在这里插入图片描述
byte描述
0数据长度,固定为 0x1A
1AD Type,固定为 0xFF 表示厂商自定义数据
2~3固定为 0x004C ,此为 Apple 公司的标识符。可在Assigned Numbers Document (PDF) 7 Company Identifiers 章节查阅
4固定为 0x02,这个是由 Apple 公司定义的数据类型
5ibeacon 数据长度,固定为 0x15
6~21用户定义的 iBeacon UUID,用于唯一标识应用场景
22~23用户自定义的主要值,用于分组或区域标识
24~25用户自定义的次要值,用于更精细的分组或区域标识
26发射功率,可通过该值配合 RSSI 获得当前位置与广播设备距离
  1. 通过上述分析,我们就可以将固定的报头数据先设置好。
// 注意:BLE 广播为小端存储
esp_ble_ibeacon_head_t ibeacon_common_head = {
    .flags = {0x02, 0x01, 0x06},
    .length = 0x1A,         // iBeacon 数据长度为 26 bytes
    .type = 0xFF,           // 自定义数据类型
    .company_id = 0x004C,   // Apple 公司的标识符
    .beacon_type = 0x1502   // 0x02 表示 iBeacon 类型,0x15 表示接下来的 ibeacon 数据长度为 21 bytes。
};
  1. 之后我们就可以设置自己想要广播的数据了。
/* Vendor part of iBeacon data*/
esp_ble_ibeacon_vendor_t vendor_config = {
    .proximity_uuid = ESP_UUID,  // 16 字节,用户定义的 iBeacon UUID,用于唯一标识应用场景。
    .major = ENDIAN_CHANGE_U16(ESP_MAJOR), // 2 字节,用户自定义的主要值,用于分组或区域标识。
    .minor = ENDIAN_CHANGE_U16(ESP_MINOR), // 2 字节,用户自定义的次要值,用于更精细的分组或区域标识。
    .measured_power = 0xC5    // 发射功率为 -59 dBm
};
  1. 一切设置完成后,我们只需要调用 esp_ble_config_ibeacon_data() 函数将数据进行填充即可。该函数其实就是对 Payload 段数据进行了一下配置,看如下日志打印信息就可知道。如果感兴趣,各位可以看一下该函数实现,其实是非常简单的。

注意: esp_ble_config_ibeacon_data() 并不是 ESP32 官方的库函数!!!这个是编写例程的人写的自定义函数!!!

I (538) IBEACON_DEMO: ibeacon_adv_data[0] = 0x2
I (538) IBEACON_DEMO: ibeacon_adv_data[1] = 0x1
I (548) IBEACON_DEMO: ibeacon_adv_data[2] = 0x6
I (548) IBEACON_DEMO: ibeacon_adv_data[3] = 0x1a
I (558) IBEACON_DEMO: ibeacon_adv_data[4] = 0xff
I (558) IBEACON_DEMO: ibeacon_adv_data[5] = 0x4c
I (568) IBEACON_DEMO: ibeacon_adv_data[6] = 0x0
I (568) IBEACON_DEMO: ibeacon_adv_data[7] = 0x2
I (578) IBEACON_DEMO: ibeacon_adv_data[8] = 0x15
I (588) IBEACON_DEMO: ibeacon_adv_data[9] = 0xfd
I (588) IBEACON_DEMO: ibeacon_adv_data[10] = 0xa5
I (598) IBEACON_DEMO: ibeacon_adv_data[11] = 0x6
I (598) IBEACON_DEMO: ibeacon_adv_data[12] = 0x93
I (608) IBEACON_DEMO: ibeacon_adv_data[13] = 0xa4
I (608) IBEACON_DEMO: ibeacon_adv_data[14] = 0xe2
I (618) IBEACON_DEMO: ibeacon_adv_data[15] = 0x4f
I (618) IBEACON_DEMO: ibeacon_adv_data[16] = 0xb1
I (628) IBEACON_DEMO: ibeacon_adv_data[17] = 0xaf
I (628) IBEACON_DEMO: ibeacon_adv_data[18] = 0xcf
I (638) IBEACON_DEMO: ibeacon_adv_data[19] = 0xc6
I (638) IBEACON_DEMO: ibeacon_adv_data[20] = 0xeb
I (648) IBEACON_DEMO: ibeacon_adv_data[21] = 0x7
I (648) IBEACON_DEMO: ibeacon_adv_data[22] = 0x64
I (658) IBEACON_DEMO: ibeacon_adv_data[23] = 0x78
I (668) IBEACON_DEMO: ibeacon_adv_data[24] = 0x25
I (668) IBEACON_DEMO: ibeacon_adv_data[25] = 0x27
I (678) IBEACON_DEMO: ibeacon_adv_data[26] = 0xb7
I (678) IBEACON_DEMO: ibeacon_adv_data[27] = 0xf2
I (688) IBEACON_DEMO: ibeacon_adv_data[28] = 0x6
I (688) IBEACON_DEMO: ibeacon_adv_data[29] = 0xc5
  1. 配置好要广播的数据后,直接调用库函数 esp_ble_gap_config_adv_data_raw() 即可。我们来看看 nrf Connect 结合上述打印信息,就会发现这个函数就是将你需要广播的数据存入 Payload 段

在这里插入图片描述

esp_ble_gap_start_advertising() 启动广播

  1. 在上面,我们调用 esp_ble_gap_config_adv_data_raw() 函数设置原始广播数据完成之后,将会触发 ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT 事件。
  2. 我们可以在该事件中判断原始广播数据是否设置成功,如果设置成功,那么我们即可调用 esp_ble_gap_start_advertising() 函数启动广播。
  3. 在调用 esp_ble_gap_start_advertising() 函数时,需要传入如下结构体。
typedef struct {
    uint16_t                adv_int_min;        /*!< 最小的广告间隔无向和低占空比定向广告。
                                                  取值范围:0x0020 ~ 0x4000  
                                                  默认值:N = 0x0800(1.28秒)
                                                  时间 = N * 0.625 ms 
                                                  时间范围: 20 ms to 10.24 s */
    uint16_t                adv_int_max;        /*!< 无向和低占空比定向广告的最大广告间隔。
                                                  取值范围:0x0020 ~ 0x4000  
                                                  默认值:N = 0x0800(1.28秒)
                                                  时间 = N * 0.625 ms 
                                                  时间范围: 20 ms to 10.24 s */
    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;
  1. 如果是对 BLE HCI 比较熟悉的朋友就会发现,这个函数其实就是让 HOST 层向 Control 层发送 LE Set Advertising Parameters command 命令。
  2. 我们可以打开 Core 5.3 的 2353 页,会发现这个命令传入的参数和 esp_ble_gap_start_advertising() 函数传入的参数一模一样。

在这里插入图片描述
12. 关于这个命令,规范书中的描述如下:

  1. HCI_LE_Set_Advertising_Parameters 命令用于设置广播参数。
  2. advertissing_interval_min 小于或等于 Advertising_Interval_Max 。advertissing_interval_min 和 Advertising_Interval_Max 不应该是相同的值,以使控制器能够确定给定其他活动的最佳广告间隔。
  3. 对于高占空比定向广告,即当 Advertising_Type 为 0x01 (ADV_DIRECT_IND,高占空比)时,不使用 Advertising_Interval_Min 和 Advertising_Interval_Max 参数,应忽略。
  4. Advertising_Type 用于确定在启用发布时用于发布的数据包类型。
  5. “Own_Address_Type” 参数表示发布报文使用的地址类型。
  6. 如果 Own_Address_Type 等于 0x02 或 0x03, Peer_Address 参数包含对端设备的身份地址,Peer_Address_Type 参数包含对端设备的身份类型(即0x00或0x01)。这些参数用于在解析表中定位相应的本地 IRK;此 IRK 用于生成广告中使用的自己的地址。
  7. 如果是定向广播,即当 Advertising_Type 设置为 0x01 (ADV_DIRECT_IND,高占空比)或0x04 (ADV_DIRECT_IND,低占空比模式)时,则 Peer_Address_Type 和 Peer_Address 有效。
  8. 如果 Own_Address_Type 等于 0x02 或 0x03,Control 使用 Peer_Address 参数中包含的对端设备的身份地址和 Peer_Address_Type 参数中包含的对端设备的身份地址类型(即0x00或0x01)对应的对端设备的 IRK 生成对端设备的可解析私有地址。
  9. Advertising_Channel_Map 是一个位字段,表示发送广播报文时应使用的广告通道索引。在 Advertising_Channel_Map 参数中至少要设置一个通道位。
  10. 当定向广播被启用时,Advertising_Filter_Policy 参数应该被忽略。
  11. 如果 Control 启用了广播功能,则 HOST 不得发出该命令;如果启用了广播功能,则应使用 "命令禁用 "错误代码。
  12. 如果 HOST 提供的发布间隔范围(Advertising_Interval_Min, Advertising_Interval_Max)超出了 Control 支持的发布间隔范围,则 Control 将返回不支持的特征或参数值(0x11)错误码。
adv_int_min 和 adv_int_max
  1. adv_int_minadv_int_max 用于指示广播的时间间隔。bluedroid 协议栈会选取该范围内的任意值作为广播间隔,但是实测后发现,他一般选取 adv_int_max 作为广播间隔

  2. adv_int_minadv_int_max 范围都要求在 0x0020 ~ 0x4000 之间,如果没有设置该值,默认为 0x0800 (1.28 s) 。时间计算公式为 Time = N * 0.625 ms,既最终的广播时间范围为 20 ms to 10.24 s。
    在这里插入图片描述

  3. 此时有人可能会有疑问了,我代码里面命令设置的最大广播间隔时间为 40ms 啊,怎么抓包数据有些广播间隔为 48ms 多呢?

  4. 这个就需要涉及到 SIG 规定的广播间隔内容了。SIG 规定,为了解决多个设备同时广播时的冲突问题,从而避免广播包的碰撞,确保更加可靠的广播和接收,在 BLE 广播过程中,存在一个 随机延迟(random delay),通常称为 广播延迟(advertising delay)

static esp_ble_adv_params_t ble_adv_params = {
    .adv_int_min        = 0x20,                 // 0x20*0.625ms=20ms,Range: 0x0020 to 0x4000 (20ms to 10240ms)
    .adv_int_max        = 0x40,                 // 0x40*0.625ms=40ms
	// ...
};

在这里插入图片描述

  1. 如果我们希望一个确切的广播间隔,那么就可以让adv_int_minadv_int_max 相等即可。

注: adv_int_minadv_int_max 相等只是让 advInterval 为我们设定的固定值,advDelay 依旧会存在!而且 SIG 不建议让 adv_int_minadv_int_max 相等!

  1. adv_int_max 必须大于 adv_int_min ,否则就会出现如下报错。(部分日志信息是本例程用于调试写的)
I (658) IBEACON_DEMO: ====> ESP_GAP_BLE_EVT 4 <====
E (668) BT_APPL: bta_dm_ble_set_adv_params_all(), fail to set ble adv params.
E (678) BT_HCI: hci write adv params error 0x12
I (678) IBEACON_DEMO: ====> ESP_GAP_BLE_EVT 6 <====
E (688) IBEACON_DEMO: Adv start failed: ERROR
adv_type
  1. 该参数用于设置 BLE 的广播类型。
typedef enum {
    ADV_TYPE_IND                = 0x00, // 可连接和可扫描的无定向广告(ADV_IND)(默认)
    ADV_TYPE_DIRECT_IND_HIGH    = 0x01, // 可连接的高占空比定向广告(ADV_DIRECT_IND,高占空比)
    ADV_TYPE_SCAN_IND           = 0x02, // 可扫描的非定向广告(ADV_SCAN_IND)
    ADV_TYPE_NONCONN_IND        = 0x03, // 不可连接非定向广告(ADV_NONCONN_IND)
    ADV_TYPE_DIRECT_IND_LOW     = 0x04, // 可连接低占空比定向广告(ADV_DIRECT_IND,低占空比)
} esp_ble_adv_type_t;
  1. 如果是学习 《低功耗权威指南》或者其他类型的熟记中会发现,广播类型只有四种:通用广播定向广播不可连接广播可发现广播。这个时候有人肯定会疑问,为什么这里的定向广播有两种。
  2. 这个就设计到版本更替的问题了,《低功耗权威指南》是用于讲解 BLE 4.0 的权威书籍,但是 ESP32 的 Bluedroid 是支持 BLE 5.0 的。从 BLE 5.0 开始,广播拥有第五种类型,即可连接低占空比定向广告
  3. 现在我就开始分别介绍一下这几种广播的区别:
广播类型描述关闭方式
ADV_TYPE_IND这种广播是最常用的广播方式。进行通用广播的设备是能够被扫描,被连接的调用 esp_ble_gap_stop_advertising() 函数主动关闭广播,或者连接建立。
ADV_TYPE_DIRECT_IND_HIGH该广播类型主要针对希望快速建立连接的需求开启定向广播后,完整的广播事件必须每 3.75ms 重复一次,正因如此之快的广播速率,导致该广播包将在占满整个广播信道,进而导致该区域内其他设备无法进行广播,因此定向广播不可以持续超过 1.28s 之上该广播只有两种结束方式,第一种是收到指定的对端设备连接请求,第二种是超过 1.28s。一旦超过 1.28s 还没有建立连接,Control 层应该向 HOST 层发送 Advertising Timeout 事件,告知广播超时。(这里需要注意,ESP32 的该广播事件似乎有 bug,并不会上报广播超时事件)
ADV_TYPE_SCAN_IND该类型广播不可以用于发起连接,但允许其他设备进行扫描,可以理解为将连接功能去除的 ADV_TYPE_IND调用 esp_ble_gap_stop_advertising() 函数主动关闭广播。
ADV_TYPE_NONCONN_IND该类型广播针对的是不想被连接,仅进行广播的设备,例如本文的 Ibeacon 设备。这也是唯一可用于只有发射机而没有接收机设备的广播类型调用 esp_ble_gap_stop_advertising() 函数主动关闭广播。
ADV_TYPE_DIRECT_IND_LOW该广播属于定向广播,但是并不会像 ADV_TYPE_DIRECT_IND_HIGH 那样快速的将整个广播信道占满,他是会在 adv_int_minadv_int_max 范围内保持一定的频率进行广播。调用 esp_ble_gap_stop_advertising() 函数主动关闭广播,或者和指定的对端设备连接建立。
own_addr_type
  1. 这里设置设备的地址类型,没有特殊需求,直接设置为公共地址即可。
typedef enum {
    BLE_ADDR_TYPE_PUBLIC        = 0x00,     /*!< 公共地址 */
    BLE_ADDR_TYPE_RANDOM        = 0x01,     /*!< 随机设备地址。要设置此地址,请使用esp_ble_gap_set_rand_addr(esp_bd_addr_t rand_addr)函数 */
    BLE_ADDR_TYPE_RPA_PUBLIC    = 0x02,     /*!< 具有公共身份地址的可解析私有地址(RPA) */
    BLE_ADDR_TYPE_RPA_RANDOM    = 0x03,     /*!< 带有随机身份地址的可解析私有地址(RPA)。要设置此地址,请使用esp_ble_gap_set_rand_addr(esp_bd_addr_t rand_addr)函数 */
} esp_ble_addr_type_t;

在这里插入图片描述

设备地址类型描述
公共地址(BLE_ADDR_TYPE_PUBLIC)全球唯一且固定的地址,需要向 IEEE 组织购买。因为全球唯一,因此容易被跟踪
静态地址(BLE_ADDR_TYPE_RANDOM)自己定义,上电初始化完成后不能再进行修改。每次芯片启动都可能会更换地址
可解析地址(BLE_ADDR_TYPE_RPA_PUBLIC)通讯双方共享 IRK ,生成随机可解析私有地址。只有拥有广播者的 IRK 时,才能跟踪其广播活动。目的是为了防止恶意第三方跟踪蓝牙设备。
不可解析地址(BLE_ADDR_TYPE_RPA_RANDOM)定期更新地址,SIG 推荐15 min 更新一次,更新时间间隔不要超过 1 小时。通常用于设备只需要一次性广播数据,且不需要被接收方识别或跟踪。例如,发送传感器数据的设备、匿名设备发现、或仅广播某些临时数据的场景。
peer_addr
  1. 对端设备 MAC 地址。只有当 adv_typeADV_TYPE_DIRECT_IND_HIGH 或者 ADV_TYPE_DIRECT_IND_LOW 才有效。
peer_addr_type
  1. 对端设备地址类型。只有当 adv_typeADV_TYPE_DIRECT_IND_HIGH 或者 ADV_TYPE_DIRECT_IND_LOW 才有效。
channel_map
  1. 广播的信道。你可以设置在指定的广播信道进行广播,或者是所有广播信道都广播。
typedef enum {
    ADV_CHNL_37     = 0x01,  // 仅在 37 信道广播
    ADV_CHNL_38     = 0x02,  // 仅在 38 信道广播
    ADV_CHNL_39     = 0x04,  // 仅在 39 信道广播
    ADV_CHNL_ALL    = 0x07,  // 在 37,38,39 信道广播
} esp_ble_adv_channel_t;
adv_filter_policy
  1. 因为我们是 Ibeacon 所有需要让所有人能够扫描到,因此设置为 ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY
  2. 白名单是指的特定的对端设备地址,我们可以调用 esp_ble_gap_update_whitelist() 函数更新白名单。

如果开发过 Nordic 的相关芯片,会发现一个问题,怎么 Nordic 的芯片还可以过滤 UUID,名称,外观等信息呢?这个就是 Nordic 的协议栈中软件实现的,和 SIG 相关规定无关,如果你希望有这样的功能,可以自行实现。

typedef enum {
    // 允许任何人的扫描和连接请求
    ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY  = 0x00,
    // 只允许来自白名单设备的扫描请求和来自任何人的连接请求
    ADV_FILTER_ALLOW_SCAN_WLST_CON_ANY,
    // 只允许任何人的扫描请求和来自白名单设备的连接请求
    ADV_FILTER_ALLOW_SCAN_ANY_CON_WLST,
    // 只允许来自白名单设备的扫描和连接请求
    ADV_FILTER_ALLOW_SCAN_WLST_CON_WLST,
} esp_ble_adv_filter_t;

广播启动完成

  1. 当调用 esp_ble_gap_start_advertising() 函数之后,将会触发 ESP_GAP_BLE_ADV_START_COMPLETE_EVT 事件。在该事件中,我们可以知道广播是否完成。
  2. 当广播结束之后,将会触发 ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT 事件,不过当前的 Ibeacon 例程中,不会停止广播,因此该事件不会触发。

参考

  1. Apple Ibeacon 官方介绍
  2. 低功耗蓝牙开发者手册
  3. BTHome数据格式解析
  4. nRF52832蓝牙iBeacon广播
  5. 谷雨文档中心:BLE技术揭秘
  6. Core 5.3

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/884690.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

“数字武当”项目荣获2024年“数据要素×”大赛湖北分赛文化旅游赛道一等奖

9月26日&#xff0c;由国家数据局、湖北省人民政府指导的首届湖北省数据要素创新大会暨2024年“数据要素”大赛湖北分赛颁奖仪式在湖北武汉举行。由大势智慧联合武当山文化旅游发展集团有限公司参报的武当山“数字武当”项目&#xff0c;荣获文化旅游赛道一等奖。 据悉&#x…

VIVADO IP核之FIR抽取器多相滤波仿真

VIVADO IP核之FIR抽取器多相滤波仿真&#xff08;含有与MATLAB仿真数据的对比&#xff09; 目录 前言 一、滤波器系数生成 二、用MATLAB生成仿真数据 三、VIVADO FIR抽取多相滤波器使用 四、VIVADO FIR抽取多相滤波器仿真 五、VIVADO工程下载 总结 前言 关于FIR低通滤波…

[论文精读]TorWard: Discovery, Blocking, and Traceback of Malicious Traffic Over Tor

期刊名称&#xff1a;IEEE Transactions on Information Forensics and Security 发布链接&#xff1a;TorWard: Discovery, Blocking, and Traceback of Malicious Traffic Over Tor | IEEE Journals & Magazine | IEEE Xplore 中文译名&#xff1a;TorWard&#xff1a;…

基于python+django+vue的电影数据分析及可视化系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

【工具分享】BigBobRoss勒索病毒解密工具

前言 BigBobRoss勒索软件首次被发现于2019年初。它由C编写&#xff0c;并使用了QT框架。该勒索软件的加密方法相对基础&#xff0c;使用AES-128 ECB算法对受害者的文件进行加密。尽管加密技术并不复杂&#xff0c;但它的传播和影响力迅速扩展&#xff0c;导致大量用户的数据被…

vs2019从一个含main函数的cpp文件到生成动态生成库

小白&#xff0c;只会写简单的cpp文件&#xff0c;算法写完之后需要项目工程化&#xff0c;和上位机开发人员完成交接&#xff0c;记录一下。 文章目录 一、VS创建空项目二、编写代码 一、VS创建空项目 点击下一步&#xff0c; 我这里创建的项目名称为LidarCoreDetection 位置D…

AdaptIoT——制造业中使用因果关系的自我标签系统

0.概述 论文地址&#xff1a;https://arxiv.org/abs/2404.05976 在许多制造应用中&#xff0c;机器学习&#xff08;ML&#xff09;已被证明可以提高生产率。针对制造业应用提出了一些软件和工业物联网&#xff08;IIoT&#xff09;系统&#xff0c;以接收这些 ML 应用。最近&…

FastAPI 第六课 -- 请求和响应

目录 一. 前言 二. 请求数据 2.1. 查询参数 2.2. 路径参数 2.3. 请求体 三. 响应数据 3.1. 返回 JSON 数据 3.2. 返回 Pydantic 模型 3.3. 请求头和 Cookie 四. 重定向和状态码 五. 自定义响应头 一. 前言 在 FastAPI 中&#xff0c;请求&#xff08;Request&#…

每日论文7-17MWCL基于IMOS的小vco增益变化的VCO

《Small VCO-Gain Variation Adding a Bias-Shifted Inversion-Mode MOS Varactor》17MWCL 对于PLL来说&#xff0c;其中VCO的调谐增益KVCO越线性&#xff0c;其变化程度ΔKvco越小&#xff0c;对PLL的稳定有较大的好处。这篇文章给了一个很简单朴素而有效的补偿var非线性的方…

nuclei配合burpsuite快速生成POC

nuclei配合burpsuite快速生成POC 简介 Nuclei是一款基于YAML语法模板的开发的定制化快速漏洞扫描器。它使用Go语言开发&#xff0c;具有很强的可配置性、可扩展性和易用性 官网&#xff1a;https://nuclei.projectdiscovery.io Nuclei项目地址&#xff1a;https://github.com/…

2024热门AIPPT工具大盘点

随着人工智能技术的飞速发展&#xff0c;一种全新的 PPT 制作方式应运而生——Ai 制作 PPT。它如同一位智能助手&#xff0c;为我们带来了高效、创新且个性化的 PPT 制作体验。今天我们一起探讨有哪些工具可以助力我们轻松打造出令人惊艳的演示文稿的。 1.笔灵AIPPT 链接一下…

从零开始手写STL库:Stack

从零开始手写STL库–Stack的实现 Gihub链接&#xff1a;miniSTL 文章目录 从零开始手写STL库–Stack的实现一、stack是什么&#xff1f;二、stack要包含什么函数总结 一、stack是什么&#xff1f; 栈是一种后进先出&#xff08;LIFO&#xff0c;Last In First Out&#xff09…

【STM32】江科大STM32笔记汇总(已完结)

STM32江科大笔记汇总 STM32学习笔记课程简介(01)STM32简介(02)软件安装(03)新建工程(04)GPIO输出(05)LED闪烁& LED流水灯& 蜂鸣器(06)GPIO输入(07)按键控制LED 光敏传感器控制蜂鸣器(08)OLED调试工具(09)OLED显示屏(10)EXTI外部中断(11)对射式红外传感器计次 旋转编码器…

大功率蓝外光激光模组能使用多长时间?

在高科技迅猛发展的今天&#xff0c;大功率蓝外光激光模组作为精密光学技术的重要成果&#xff0c;广泛应用于科研探索、工业加工及安防监控等多个领域。其强大的光束能量与独特的波长特性&#xff0c;为各行各业带来了前所未有的效率提升与创新可能。然而&#xff0c;对于这一…

物理学基础精解【40】

文章目录 矢量积矢量积&#xff08;又称叉积、外积&#xff09;的几何意义一、面积表示二、垂直性三、方向性四、应用实例五、数学表达 矢量积&#xff08;叉积&#xff09;的坐标表示法矢量积的坐标表示法的几何意义矢量积的性质矢量积的应用 矢量积&#xff08;又称叉积、外积…

【frp】frp重启、frp启动、frp后台启动、frps dashboard等等

我写的关于frp配置的文章&#xff1a;frp配置 服务端frps 1. 创建服务文件 sudo nano /etc/systemd/system/frps.service2. 添加服务配置 在打开的文件中添加以下内容&#xff1a; [Unit] DescriptionFRPS Server Afternetwork.target[Service] Typesimple ExecStart/root…

力扣高频 SQL 50 题(基础版)|分析、题解

注意一些语法 1、group by出现在having前面&#xff0c;但是having中所使用的聚合必须是select中的 2、date类型之间的比较&#xff1a;datediff&#xff08;&#xff09; 差的绝对值 or 用字符框起来比较边界 3、算日期长度需要相减之后加一 4、round(, n)n默认是0&#x…

0基础跟德姆(dom)一起学AI 机器学习01-机器学习概述

【知道】人工智能 - Artificial Intelligence 人工智能 - AI is the field that studies the synthesis and analysis of computational agents that act intelligently - AI is to use computers to analog and instead of human brain - 释义 - 仿智&#xff1b; 像人…

在线翻译器工具横评:性能、准确率大比拼

无论是旅行者在异国他乡探寻风土人情&#xff0c;学者研究国外的前沿学术成果&#xff0c;还是商务人士与国际伙伴洽谈合作&#xff0c;都离不开一种高效、准确的语言沟通工具。而翻译器在线翻译能很好的帮我们解决这个问题。今天我们一起来探讨有那些好用的翻译工具。 1.福昕…

玄机--蚁剑流量

木马的连接密码是多少 黑客执行的第一个命令是什么 id 黑客读取了哪个文件的内容&#xff0c;提交文件绝对路径 /etc/passwd 黑客上传了什么文件到服务器&#xff0c;提交文件名 黑客上传的文件内容是什么 黑客下载了哪个文件&#xff0c;提交文件绝对路径 蚁剑流量特征总结 …