OMI Glass 固件生态中的 NimBLE-Arduino Eddystone URL 信标:广播帧构造、URL 压缩编码与 Deep Sleep 低功耗循环
发布时间:2026/9/16 22:53:48 作者:尧图编辑部 阅读量:1,286

OMI Glass 固件生态中的 NimBLE-Arduino Eddystone URL 信标广播帧构造、URL 压缩编码与 Deep Sleep 低功耗循环【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend本文基于 Friend 仓库中 OMI Glass 固件依赖的 NimBLE-Arduino 库所附带的BLE_EddystoneURL_Beacon示例文档展开完整解析一个 ESP32-S3 周期性 Eddystone URL 信标的完整实现如何逐字节构造 BLE 广播数据、如何按 Eddystone URL 规范对 URL 做前缀/后缀压缩编码以及如何通过“广播 10 秒 → 停止 → 深度睡眠 10 秒”的循环把功耗压到最低。读完后你可以复刻出这个信标工作流并理解NimBLEEddystoneURL类底层的数据结构与编解码逻辑。1. 示例定位与设计目标该文档位于仓库的 PlatformIO 依赖缓存目录中是 OMI Glass 固件目标板seeed_xiao_esp32s3基于 Seeed XIAO ESP32S3所链接的 NimBLE-Arduino 库自带示例之一说明文档BLE_EddystoneURL_Beacon.md示例源码BLE_EddystoneURL_Beacon.ino同系列 TLM 帧示例BLE_EddystoneTLM_Beacon.md文档原文给出了示例的作者脉络BeeGee 基于 pcbreflux 的 ESP32 Eddystone URL deepsleep 示例改写与 Eddystone URL 帧规范的出处并明确了整个 BLE 服务的设计骨架Create a BLE server that will send periodic Eddystone URL frames. The design of creating the BLE server is: 1. Create a BLE Server 2. Create advertising data 3. Start advertising. 4. wait 5. Stop advertising. 6. deep sleep这套六步设计是典型的“低功耗信标duty-cycled beacon”模式设备并不持续广播而是以固定占空比示例中广播 10 秒、睡眠 10 秒周期性地向周围发布 URL 帧。对 OMI Glass 这类电池供电仓库固件使用双 250mAh 电池的穿戴设备而言这种模式与 OMI Glass 固件指南 中描述的“Sleep Mode ~2mA”低功耗目标是同一类工程思路用深度睡眠换取续航。与 iBeacon 不同Eddystone 是 Google 提出的开放蓝牙广播协议族其 URL 帧类型码为0x10TLMTeleseal/Telemetry帧类型码为0x20对应库中还有 NimBLEEddystoneTLM.h 等配套类。本文聚焦 URL 帧。2. 关键配置参数示例源码头部的宏与全局变量定义了信标的核心参数#define GPIO_DEEP_SLEEP_DURATION 10 // sleep x seconds and then wake up // UUID 1 128-Bit (may use linux tool uuidgen or random numbers via https://www.uuidgenerator.net/) #define BEACON_UUID 8ec76ea3-6668-48da-9866-75be8bc86f4d RTC_DATA_ATTR static time_t last; // remember last boot in RTC Memory RTC_DATA_ATTR static uint32_t bootcount; // remember number of boots in RTC Memory各参数含义与取值说明参数示例值含义GPIO_DEEP_SLEEP_DURATION10每次唤醒广播结束后进入深度睡眠的秒数作为esp_deep_sleep(1000000LL * GPIO_DEEP_SLEEP_DURATION)的倍率即睡眠 10,000,000 微秒BEACON_UUID8ec76ea3-...128 位 UUID用于标识信标身份。注意在 URL 帧方案中该宏仅作为身份标识定义在代码里示例并未把它写入广播负载Eddystone URL 帧本身不携带 128 位 UUID而是靠 URL 内容寻址last/bootcountRTC 变量RTC_DATA_ATTR修饰的变量存放在 ESP32 的 RTC 内存中深度睡眠掉电后依然保留用于跨唤醒周期记录“上次启动时间”和“累计启动次数”BLE 设备名MeBeaconBLEDevice::init(MeBeacon)使用并写入扫描响应数据广播功率ESP_PWR_LVL_N12通过BLEDevice::setPower(ESP_PWR_LVL_N12)设为 -12 dBm缩小广播半径以进一步省电TX Power 字段0xF4广播帧中的“0 米处参考发射功率”字节0xF4即补码表示的 -12 dBm从源码结构看last与bootcount在setup()中被用于串口打印本次唤醒距上次唤醒的时间差now.tv_sec - last这是排查“占空比周期是否被系统异常重置”的实用手段。3. 广播负载的逐字节构造示例没有使用库的高层封装而是在setBeacon()中手工拼出整个广播结构体。这是理解 Eddystone URL 帧布局最直接的素材。先给出代码BLEAdvertisementData oAdvertisementData BLEAdvertisementData(); BLEAdvertisementData oScanResponseData BLEAdvertisementData(); const char url[] https://d.giesecke.tk; int scheme_len, ext_len 1, i, idx, url_idx; char *ret_data; int url_len strlen(url); ret_data (char *)calloc(1, url_len 13); ret_data[0] 2; // Len ret_data[1] 0x01; // Type Flags ret_data[2] 0x06; // GENERAL_DISC_MODE 0x02 | BR_EDR_NOT_SUPPORTED 0x04 ret_data[3] 3; // Len ret_data[4] 0x03; // Type 16-Bit UUID ret_data[5] 0xAA; // Eddystone UUID 2 - 0xFEAA LSB ret_data[6] 0xFE; // Eddystone UUID 1 MSB ret_data[7] 19; // Length of Beacon Data ret_data[8] 0x16; // Type Service Data ret_data[9] 0xAA; // Eddystone UUID 2 - 0xFEAA LSB ret_data[10] 0xFE; // Eddystone UUID 1 MSB ret_data[11] 0x10; // Eddystone Frame Type ret_data[12] 0xF4; // Beacons TX power at 0m按 BLE 广播数据“长度 类型 值”的 AD 结构体AD Structure格式拆解偏移字节值AD 结构含义0–202 01 06Flags 结构LE General Discoverable0x02 BR/EDR 不支持0x043–603 03 AA FE16 位 UUID 列表声明支持 Eddystone 服务 UUID0xFEAA小端序先低字节0xAA后高字节0xFE719后被重算Service Data 结构体的长度字段值字节共 19 个UUID 2 帧类型 1 TX Power 1 URL 16 以内8–1016 AA FEService Data 结构类型0x16 Eddystone UUID0xFEAA110x10Eddystone 帧类型URL 帧120xF40 米处参考发射功率-12 dBm有符号补码13 起压缩后的 URL最多 16 字节的 URL 编码字段几点关键结论URL 字段上限 16 字节。这是 BLE 广播数据 31 字节总上限约束下的结果Flags3 UUID 列表4 Service Data21 28再加 16 字节 URL 恰好接近上限因此 Eddystone URL 规范把 URL 字段定为 16 字节。设备名不放在广播数据里而放在扫描响应里。代码随后执行oScanResponseData.setName(MeBeacon)把设备名放入 scan response避免与 URL 内容争夺 31 字节的宝贵空间。ret_data[7] idx - 8;一行在 URL 编码完成后回填真实长度保证长度字段与实际编码结果一致。4. Eddystone URL 压缩编码表BLE 广播空间极其有限Eddystone URL 规范因此定义了前缀表与后缀表把最常见的 URL 片段编码成单个字节。示例源码内嵌了这两张表static const char *eddystone_url_prefix_subs[] { http://www., // 0 https://www., // 1 http://, // 2 https://, // 3 urn:uuid:, // 4 NULL }; static const char *eddystone_url_suffix_subs[] { .com/, // 0 .org/, // 1 .edu/, // 2 .net/, // 3 .info/, // 4 .biz/, // 5 .gov/, // 6 .com, // 7 .org, // 8 .edu, // 9 .net, // 10 .info, // 11 .biz, // 12 .gov, // 13 NULL };前缀表按数组下标编码0x00http://www.0x01https://www.0x02http://0x03https://0x04urn:uuid:后缀表前 7 项带尾斜杠如.com/后 7 项不带如.com下标0x00–0x0D依次对应上表未命中任何表项的普通字符按原值逐字节放入。匹配由string_begin_with()辅助函数完成用strncmp比较前缀命中则返回前缀长度否则返回 0。4.1 用示例 URL 走一遍编码过程以示例 URLhttps://d.giesecke.tk18 个字符为例编码过程如下前缀匹配string_begin_with依次比对命中下标 3 的https://长度 8ret_data[13] 0x03url_idx前进 8剩余 10 个字符d.giesecke.tk逐个尝试后缀匹配均未命中.tk不在后缀表中因此按原字符逐字节写入各占 1 字节最终 URL 字段 1前缀字节 10原文字符 11 字节ret_data[7]被回填为13 11 - 8 16。由此得到完整服务数据10 F4 03 64 2E 67 69 65 73 65 63 6B 65 2E 74 6B即帧类型、TX 功率、前缀编码 3随后是d.giesecke.tk的 ASCII。接收端按同一张表逆向展开即可还原出原始 URL。4.2 库实现中的逆向解码库源码 NimBLEEddystoneURL.cpp 的getDecodedURL()实现了上述编码的逆过程其 switch 分支与上文编码表一一对应0x01展开为https://www.后缀0x00展开为.com/、0x07展开为.com等等介于0x21–0x7E之间的可打印字符直接追加。对照这段库实现可以快速验证自己手拼广播帧的编码是否正确——用任意 BLE 抓包工具如 nRF Connect 的 Beacon 解析扫到信标后把解码 URL 与此函数输出比对即可。5. NimBLEEddystoneURL 类高层封装路径除手工拼帧外NimBLE-Arduino 还提供了面向对象的封装。类定义见 NimBLEEddystoneURL.h#define EDDYSTONE_URL_FRAME_TYPE 0x10 class NimBLEEddystoneURL { public: NimBLEEddystoneURL(); std::string getData(); NimBLEUUID getUUID(); int8_t getPower(); std::string getURL(); std::string getDecodedURL(); void setData(const std::string data); void setUUID(const NimBLEUUID l_uuid); void setPower(int8_t advertisedTxPower); void setURL(const std::string url); private: uint16_t beaconUUID; uint8_t lengthURL; struct { uint8_t frameType; int8_t advertisedTxPower; uint8_t url[16]; } __attribute__((packed)) m_eddystoneData; };实现要点见 NimBLEEddystoneURL.cpp构造函数固定beaconUUID 0xFEAA、frameType 0x10与手工拼帧示例中ret_data[9..11]的取值一致m_eddystoneData是一个__attribute__((packed))紧凑结构体帧类型 1 发射功率 1 URL 16getData()直接把它序列化为字符串即“帧类型 TX Power URL 字段”这 18 字节的服务数据负载setData()会校验输入长度不超过结构体总大小并回填lengthURL需要注意setURL()是把传入字符串原样拷入 16 字节字段并不做前缀/后缀压缩。从源码结构看若走高层 API 发布压缩后的 URL需要在调用侧先完成编码正如示例.ino手工所做的那样或者接受未压缩形式而getDecodedURL()负责把压缩形式还原为可读 URL二者并非严格互逆。6. 广告-睡眠工作流与 RTC 状态保持setup()的完整调用链体现了文档中“六步设计”void setup() { Serial.begin(115200); gettimeofday(now, NULL); Serial.printf(start ESP32 %d\n, bootcount); Serial.printf(deep sleep (%lds since last reset, %lds since last boot)\n, now.tv_sec, now.tv_sec - last); last now.tv_sec; // Create the BLE Device BLEDevice::init(MeBeacon); BLEDevice::setPower(ESP_PWR_LVL_N12); pAdvertising BLEDevice::getAdvertising(); setBeacon(); // Start advertising pAdvertising-start(); Serial.println(Advertizing started...); delay(10000); pAdvertising-stop(); Serial.printf(enter deep sleep\n); esp_deep_sleep(1000000LL * GPIO_DEEP_SLEEP_DURATION); Serial.printf(in deep sleep\n); } void loop() { }流程说明初始化 BLE 栈BLEDevice::init(MeBeacon)完成控制器/主机栈初始化并设定设备名压低发射功率BLEDevice::setPower(ESP_PWR_LVL_N12)-12 dBm与广播帧中的0xF4TX Power 字段保持自洽接收端据此修正 RSSI 测距填充广告数据setBeacon()内通过pAdvertising-setAdvertisementData(...)与setScanResponseData(...)分别注入广播数据第 3 节的 AD 结构体和扫描响应数据设备名广播窗口pAdvertising-start()启动广播delay(10000)维持 10 秒停止并休眠pAdvertising-stop()后调用esp_deep_sleep(1000000LL * 10)进入 10 秒深度睡眠唤醒后 CPU 重新执行setup()形成周期性循环空loop()由于esp_deep_sleep在setup()中同步阻塞主循环实际不会被调度这是 ESP32 Arduino 框架下“一次性 setup 即完成全部工作”的惯用写法。RTC_DATA_ATTR变量last、bootcount在此循环中的价值在于深度睡眠会重置程序状态但 RTC 内存内容保留因此每次唤醒打印的 bootcount 递增计数与“距上次唤醒的秒数”都能跨周期连续便于用串口日志platformio device monitor --baud 115200115200 波特率与示例Serial.begin(115200)一致验证占空比是否按预期运转。7. 工程要点与验证方法31 字节预算构造 Eddystone URL 广播帧时务必把设备名挪到 scan response且压缩后 URL 不超过 16 字节否则广告数据会被截断长度字段回填手工拼帧时Service Data 的长度字节示例中ret_data[7]必须在 URL 编码完成后用idx - 8重算这是示例中容易被遗漏的一步UUID 字节序0xFEAA在广播中按小端序写作AA FE16 位 UUID 列表类型0x03与 Service Data类型0x16两处都要保持该顺序验证路径烧录后可用手机端 BLE 扫描类应用查看广播扫到Service Data 0xFEAA的帧后比对解码出的 URL 是否与setBeacon()中url常量一致即可确认编码表、长度字段与帧类型均正确低功耗调参广播窗口delay(10000)与睡眠时长GPIO_DEEP_SLEEP_DURATION可按检测概率需求调整。广播窗口越长被手机扫到的概率越高但平均功耗也随之上升setPower(ESP_PWR_LVL_N12)则用于控制覆盖半径。8. 小结与延伸阅读本文以 BLE_EddystoneURL_Beacon.md 描述的六步设计为主线结合 BLE_EddystoneURL_Beacon.ino 的完整源码完整走通了 Eddystone URL 信标的实现广播负载的 AD 结构布局、16 字节 URL 字段的压缩编码与长度回填、NimBLEEddystoneURL类的紧凑数据结构和解码逻辑以及基于RTC_DATA_ATTResp_deep_sleep的低功耗占空比循环。对于 OMI Glass 固件这种电池供电的 BLE 场景这套“周期广播 深度睡眠”的模式是可直接借鉴的参考实现。延伸阅读同库示例与类实现BLE_EddystoneTLM_Beacon.md、BLE_EddystoneTLM_Beacon.ino同模式的 TLM遥测帧信标NimBLEBeacon.hiBeacon 帧的对应封装含 Major/Minor/Proximity UUID/信号功率字段ble_eddystone.c 与 ble_eddystone.hNimBLE 协议栈底层的 Eddystone 支持。最后说明.pio/libdeps是 PlatformIO 的依赖缓存目录上述文件随NimBLE-Arduino库版本变化本文结论以仓库当前缓存的库代码为准该示例本身面向独立 ESP32-S3 开发板演示与 OMI Glass 主固件firmware.ino、platformio.ini中的音频/相机功能相互独立可单独编译烧录用于验证。【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考