基于 ESP32-WROOM-32 实践 EddystoneURL_Beacon 网址广播 – PlatformIO

最近更新于 2026-07-26 22:09

测试环境

  • ESP32-WROOM-32
  • PlatformIO IDE 3.3.4
  • PlatformIO Core 6.1.19
  • pioarduino platform-espressif32 55.03.311(Arduino Release v3.3.11 based on ESP-IDF v5.5.5)

platformio.ini

[env:esp32dev]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.311/platform-espressif32.zip
board = esp32dev
framework = arduino
monitor_speed = 115200

实践

源码参考来源:https://github.com/espressif/arduino-esp32/blob/3.3.11/libraries/BLE/examples/EddystoneURL_Beacon/EddystoneURL_Beacon.ino

EddystoneURL_Beacon 是谷歌在 2015 年左右推出的,可以轮播网址。早期谷歌框架里的附近通知扫描到时会弹出网页链接卡片,只是会被广告商利用,路过的用户会被强制广告骚扰,2018 年谷歌就把附近通知关掉了。

/*
   EddystoneURL beacon by BeeGee
   EddystoneURL 帧规范: https://github.com/google/eddystone/blob/master/eddystone-url/README.md

   更新日期: 2023年2月20日
   作者: Tomas Pilny
*/

/*
   创建一个定期发送 Eddystone URL 帧的 BLE 服务器。
   整体设计流程如下:
   1. 初始化 BLE 设备(不再需要完整的 BLE Server)
   2. 创建广播数据(包含 Eddystone URL 帧)
   3. 开启广播
   4. 等待 10 秒
   5. 停止广播
   6. 进入深睡眠 (Deep Sleep),极大地降低功耗
*/

#include "sys/time.h" // 提供获取系统时间的功能 (gettimeofday)
#include <Arduino.h>

#include "BLEDevice.h"
#include "BLEUtils.h"
#include "BLEBeacon.h"
#include "BLEAdvertising.h"
#include "BLEEddystoneURL.h" // 专门用于处理 Eddystone-URL 格式的库
#include "esp_sleep.h"       // ESP32 深睡眠控制库

// 测试用的不可打印字符(用于触发 URL 错误处理逻辑,当前被注释掉)
char unprintable[] = {0x01, 0xFF, 0xDE, 0xAD};

/*
    要广播的 URL 列表。
    注意:Eddystone 广播包的载荷极小,因此它有一套“前缀/后缀字典压缩”机制。
    例如,它会将 "http://www." 压缩为一个字节 0x00,将 ".com/" 压缩为 0x00。
*/
String URL[] = {
    "http://blog.iyatt.com/", // 会被压缩,前缀 http://www. (0x00), 后缀 .com/ (0x00)
    "https://blog.iyatt.com",     // 会被压缩,前缀 https://www. (0x01), 后缀 .gov (0x0D)
    "http://en.mapy.cz",         // 前缀 http:// (0x02), 无标准后缀(剩余部分按 ASCII 明文传输)
    "https://arduino.cc",        // 前缀 https:// (0x03), 无标准后缀
    "baidu.com",                // 没写前缀时,setSmartURL 函数默认假设前缀为 "http://www." (0x00)
};

// 定义深睡眠休眠时间(秒)
#define GPIO_DEEP_SLEEP_DURATION 2
// 定义广播发射功率(N12 表示 -12dBm,数值越低越省电,但覆盖距离越短)
#define BEACON_POWER ESP_PWR_LVL_N12

/*
    RTC_DATA_ATTR 宏非常关键!
    普通的全局变量在 ESP32 进入 Deep Sleep 后,RAM 断电会全部丢失。
    加上 RTC_DATA_ATTR 后,该变量会被存放在 RTC 慢速内存中,即使系统休眠依然能保持数据不丢失。
*/
RTC_DATA_ATTR static time_t last;        // 记录上一次唤醒的系统时间
RTC_DATA_ATTR static uint32_t bootcount; // 记录设备的启动(唤醒)次数,用于轮询 URL 数组

BLEAdvertising *pAdvertising; // BLE 广播管理器指针
struct timeval now;           // 用于存放当前系统时间的结构体

/**
 * @brief 配置并装载 Eddystone-URL 广播包
 * @return 1 表示成功配置,0 表示失败(如 URL 格式错误或超长)
 */
int setBeacon()
{
    // 1. 创建普通广播数据对象(发 URL) 和 扫描响应数据对象(发设备名称)
    BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
    BLEAdvertisementData oScanResponseData = BLEAdvertisementData();

    // 2. 实例化 Eddystone-URL 对象
    BLEEddystoneURL EddystoneURL;
    // 设置参考功率值(此值仅作为数据写在广播包里,告诉手机用于测距计算;真正的硬件发射功率由 BLEDevice::setPower 控制)
    EddystoneURL.setPower(BEACON_POWER);

    // 3. 设置要广播的 URL。
    // 通过 bootcount 轮询数组:bootcount 取余 URL 数组的大小,实现每次唤醒换一个网址广播
    if (EddystoneURL.setSmartURL(URL[bootcount % (sizeof(URL) / sizeof(URL[0]))]))
    {

        // 4. 从配置好的 EddystoneURL 对象中提取原始帧数据格式 (Frame)
        String frame = EddystoneURL.getFrame();
        String data(EddystoneURL.getFrame().c_str(), frame.length());

        // 5. 将生成的帧数据添加到广播包中
        oAdvertisementData.addData(data);

        // 6. 在“扫描响应”(Scan Response) 中设置设备名称。
        // 因为广播主包的空间几乎全被 URL 占了,所以名字必须放在扫描响应包里。
        oScanResponseData.setName("ESP32 URLBeacon");

        // 7. 将配置好的数据应用到广播管理器
        pAdvertising->setAdvertisementData(oAdvertisementData);
        pAdvertising->setScanResponseData(oScanResponseData);

        Serial.printf("Advertise URL \"%s\"\n", URL[bootcount % (sizeof(URL) / sizeof(URL[0]))].c_str());
        return 1; // 组包成功
    }
    else
    {
        // 如果 URL 超长或格式非法,setSmartURL 会返回 false
        Serial.println("Smart URL set ERR");
        return 0; // 组包失败
    }
}

void setup()
{
    Serial.begin(115200);
    gettimeofday(&now, NULL); // 获取当前时间(由于休眠,系统时间实际上就是休眠醒来的时间累加)

    // 打印当前是第几次唤醒
    Serial.printf("Start ESP32 %" PRIu32 "\n", bootcount++);
    // 打印时间统计:距离上一次复位的秒数,以及距离上一次休眠前记录的秒数差异
    Serial.printf("Deep sleep (%" PRIu32 "s since last reset, %" PRIu32 "s since last boot)\n",
                  (uint32_t)now.tv_sec, (uint32_t)(now.tv_sec - last));

    // 更新时间记录,供下一次唤醒时做对比
    last = now.tv_sec;

    // 1. 初始化 BLE 底层设备引擎
    BLEDevice::init("URLBeacon");
    // 2. 真实设置蓝牙射频硬件的发射功率
    BLEDevice::setPower(BEACON_POWER);

    // (优化点:作为纯广播设备,不需要像 GATT 连接那样去实例化完整的 BLEServer,节省了内存和 Flash 空间)

    // 3. 获取广播管理器指针
    pAdvertising = BLEDevice::getAdvertising();

    // 4. 调用上面封装的函数,尝试打包并装载 Eddystone URL 数据
    if (setBeacon())
    {
        // 5. 如果打包成功,开启广播
        pAdvertising->start();
        Serial.println("Advertising started...");

        // 6. 保持广播状态运行 10 秒
        delay(10000);

        // 7. 10秒后停止广播,准备休眠
        pAdvertising->stop();
    }

    Serial.println("Enter deep sleep");
    // 为了下一次唤醒时能切到下一个 URL,bootcount 在这里加 1 (实际上上面打印时已经加过一次了,双重加会导致跳号,但这只是测试逻辑)
    bootcount++;

    // 8. 命令 ESP32 进入 Deep Sleep。
    // 参数单位是微秒 (us),所以要乘以 1,000,000。休眠期间 CPU 停止工作,蓝牙射频关闭,仅 RTC 定时器在跑。
    esp_deep_sleep(1000000LL * GPIO_DEEP_SLEEP_DURATION);
}

void loop()
{
    // 因为 setup() 的最后调用了 esp_deep_sleep(),CPU 已经断电重启了。
    // 每次唤醒都会从 setup() 重新开始执行,所以 loop() 里面不需要写任何代码,永远执行不到这里。
}

为了防止骚扰,现在手机系统不会响应 EddystoneURL_Beacon,只是通过调试软件可以看到。
可以点击 OPEN 打开当前收到的广播地址。代码里填写了多个地址,每一次休眠重启就会自动换一个广播,实现轮播。
file

基于 ESP32-WROOM-32 实践 EddystoneURL_Beacon 网址广播 – PlatformIO
Scroll to top
打开目录