ESP32 Bluedroid 篇(1)—— ibeacon 广播

news2024/11/18 17:20:17

前言

  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.coloradmin.cn/o/2173817.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小徐影院:Spring Boot影院管理新体验

第三章 系统分析 整个系统的功能模块主要是对各个项目元素组合、分解和更换做出对应的单元&#xff0c;最后在根据各个系统模块来做出一个简单的原则&#xff0c;系统的整体设计是根据用户的需求来进行设计的。为了更好的服务于用户要从小徐影城管理系统的设计与实现方面上做出…

24年下重庆事业单位考试报名超详细流程

&#x1f388;提交报考申请 考生通过重庆市人力资源和社会保障局官网&#xff08;rlsbj.cq.gov.cn&#xff09;“热点服务”中“人事考试网上报名”栏进行报名。报名时间为2024年8月12日9:00—8月17日9:00。 &#x1f388;网上缴费 资格初审合格后&#xff0c;考生应在2024年8…

【Python】1.初始Python--打开Python的大门

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 |《MySQL探索之旅》 |《Web世界探险家》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更…

数据结构:排序(内部排序+各种排序算法的性质总结)

数据结构的排序是计算机科学中的一个基本且重要的操作&#xff0c;它指的是将一组数据元素&#xff08;或记录&#xff09;按照一定规则&#xff08;通常是关键字的大小&#xff09;进行排列的过程。排序后的数据元素在物理或逻辑上呈现出某种顺序性&#xff0c;从而便于后续的…

GPIO端口的使用

目录 一. 前言 二. APB2外设时钟使能寄存器 三. GPIO端口的描述 四. GPIO端口使用案例 一. 前言 基于库函数的开发方式就是使用ST官方提供的封装好的函数。而如果没有添加库函数&#xff0c;那就是基于寄存器的开发方式&#xff0c;这种方式一般不是很推荐。因为由于ST对寄存…

华为-IPv6与IPv4网络互通的6to4自动隧道配置实验

IPv4向IPv6的过渡不是一次性的,而是逐步地分层次地。在过渡时期,为了保证IPv4和IPv6能够共存、互通,人们发明了一些IPv4/IPv6的互通技术。 本实验以6to4技术为例,阐述如何配置IPv6过渡技术。 配置参考 R1 # sysname R1 # ipv6# interface GigabitEthernet0/0/1ip address 200…

Java程序的控制结构

1、分支语句 1.1、if语句 if语句的三种格式 &#xff08;1&#xff09;单分支语句(if...) 格式&#xff1a;if&#xff08;表达式&#xff09;{ 语句 } 例&#xff1a;两个数比较最大值 int a3,b9; int maxa; if(b>max){System.out.println(max) } &#xff08;2&#x…

你要的录音播放录音功能,直接用!—Air201资产定位模组LuatOS

超低功耗、精准定位、快速量产——迷你小巧的合宙Air201&#xff0c;正给越来越多的行业客户带来高效开发体验。 此前有小伙伴问&#xff1a;是否支持录音、播放录音功能&#xff1f; 高集成化设计的Air201自带了ES8311音频解码芯片&#xff08;Audio Codec&#xff09;及MIC…

2. PH47代码框架二次开发功能特性

2.1. 概述 PH47代码框架为二次开发用户提供了丰富的&#xff0c;面向无人机飞行控制以及其他运动控制领域的功能特性&#xff0c;依托这些预设的功能特性&#xff0c;用户能够在短时间内开发出具体具备强大功能及可靠性的二次开发应用。此章节主要对二次开发特性进行了详细描述…

sentinel原理源码分析系列(一)-总述

背景 微服务是目前java主流开发架构&#xff0c;微服务架构技术栈有&#xff0c;服务注册中心&#xff0c;网关&#xff0c;熔断限流&#xff0c;服务同学&#xff0c;配置中心等组件&#xff0c;其中&#xff0c;熔断限流主要3个功能特性&#xff0c;限流&#xff0c;熔断&…

云原生之运维监控实践-OpenEuler22.03SP3上安装Prometheus与Grafana实现主机状态监测

背景 如果没有监控&#xff0c;那么最好的情况是没有问题发生&#xff0c;最糟糕的情况则是问题发生了但没有被发现。——《Prometheus监控实战》 去年写了一篇在Docker环境下部署若依微服务ruoyi-cloud项目的文章&#xff0c;当时使用的是 docker-compose 在单台机器上部署若依…

NutUI 单元测试:从 jest 到 vitest

NutUI Vue 自 3.0 版本起&#xff0c;开始使用 vite 作为项目的构建工具&#xff0c;单元测试工具则依然使用 jest。而后 vite 官方团队开源了 vitest 作为 vite 的首选测试框架和 jest 的替代品。 本文主要介绍 NutUI 从 jest 到 vitest 的迁移过程&#xff0c;以及后续对于单…

电商系统开发全攻略:基于Spring Boot的在线商城

2 相关技术 2.1 Springboot框架介绍 Spring Boot是由Pivotal团队提供的全新框架&#xff0c;其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置&#xff0c;从而使开发人员不再需要定义样板化的配置。通过这种方式&#xff0c;Spring…

Go 项目开发常用设计模式

设计模式就某些编码场景下的最佳实践&#xff0c;用来解决常见的软件设计问题。Go 语言不是面向对象语言&#xff0c;但是可以使用结构体、接口等类型实现面向对象语言的特性&#xff0c;想要弄懂设计模式&#xff0c;要熟练的使用 Go 语言接口类型 和结构体类型 设计模式总体上…

关于Chrome浏览器F12调试,显示未连接到互联网的问题

情况说明 最近笔者更新下电脑的Chrome浏览器&#xff0c;在调试前端代码的时候&#xff0c;遇到下面一个情况&#xff1a; 发现打开调试面板后&#xff0c;页面上显示未连接到互联网&#xff0c;但实际电脑网络是没有问题的&#xff0c;关闭调试面板后&#xff0c;网页又能正…

Python 爬虫 根据ID获得UP视频信息

思路&#xff1a; 用selenium库对网页进行获取&#xff0c;然后用bs4进行分析&#xff0c;拿到bv号&#xff0c;标题&#xff0c;封面&#xff0c;时长&#xff0c;播放量&#xff0c;发布时间 先启动webdriver.&#xff0c;进入网页之后&#xff0c;先等几秒&#xff0c;等加…

chatglm本地服务器大模型量化cpu INT4 INT8 half float运行、多卡多GPU运行改这一条指令就行啦!

一、ChatGLM3的几种推演方式 ChatGLM3常规方案的GPU推演中half和float是两种最常用的格式&#xff0c;half格式占13GB显存&#xff0c;float格式占40GB显存。此外还提供了几种GPU量化格式的推演&#xff1a;INT4和INT8量化。 CPU版本的ChatGLM3推演&#xff1a; model Auto…

Java 常用的一些Collection的实现类

Java 常用的一些Collection的实现类 Collection 1.集合基础 Java 集合框架是一个强大的工具&#xff0c;它提供了一套标准化的接口和类&#xff0c;用于存储和操作集合数据。Collection 接口是这个框架的核心&#xff0c;它定义了一系列通用的集合操作。 2.Collection接口方法 …

既然有HTTP协议,为什么还要有RPC?

既然有HTTP协议&#xff0c;为什么还要有RPC&#xff1f; ​ 既然有HTTP协议&#xff0c;为什么还要有RPC&#xff1f; 有点既生瑜何生亮的味道。 第一次接触RPC我就很懵&#xff0c;平时我HTTP协议用得好好的&#xff0c;为什么还需要RPC协议&#xff1f; 于是我去百度&am…

最详细!适合AI大模型零基础入门的学习路线+学习方法+学习资料,全篇干货,建议收藏!

前言 随着ChatGPT的横空出世&#xff0c;大模型时代正式来临。千亿甚至万亿参数的大模型陆续出现&#xff0c;各大企业、高校纷纷推出自己的大模型&#xff0c;这标志着通用智能时代的到来。对于零基础的初学者来说&#xff0c;如何快速入门AI大模型&#xff0c;抓住这个时代的…