ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

esp-nimble-cpp 的 Bluetooth 5.x 扩展广播(Extended Advertising)完全指南:从 PHY 到 API 迁移

esp-nimble-cpp 的 Bluetooth 5.x 扩展广播(Extended Advertising)完全指南:从 PHY 到 API 迁移 esp-nimble-cpp 的 Bluetooth 5.x 扩展广播Extended Advertising完全指南从 PHY 到 API 迁移【免费下载链接】TasmotaAlternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at项目地址: https://gitcode.com/GitHub_Trending/ta/Tasmota本文以 Bluetooth 5 features.md 为骨架结合 esp-nimble-cppNimBLE-Arduino 的 ESP32 移植版作为 Tasmota 固件的 BLE 依赖库存放在lib/libesp32_div/esp-nimble-cpp的源码与示例系统讲解 Bluetooth 5.x 扩展广播的核心概念、三种 PHY 的区别、启用方式以及启用后扫描、连接、广播三大 API 的变化与迁移路径。读完本文你将能在自己的 ESP32 工程中正确开启并配置扩展广播写出支持 251 字节甚至 1650 字节链式广播、多实例广播与 CODED 远距离通信的完整代码。一、扩展广播Extended Advertising带来了什么经典 BLEBluetooth 4.x的广播被严格限制为31 字节的广播数据ADV 报文这迫使开发者把设备名、服务 UUID、厂商数据等塞进一个很小的空间不得不频繁依赖扫描响应Scan Response或牺牲信息量。Bluetooth 5.x 的扩展广播Extended Advertising从根本上打破了这一限制esp-nimble-cpp 在CONFIG_BT_NIMBLE_EXT_ADV开启后完整暴露了这些能力。按原文档的描述其核心收益有三点能力经典广播4.x扩展广播5.x单次广播数据31 字节251 字节链式chained广播数据不支持最高 1650 字节取决于配置PHY物理层仅 1M1M / 2M / CODED 可选广播实例1 个多个实例 ID 递增最多实例数取决于配置周期广播Periodic Advertising不支持支持同步文档标注为待实现需要特别说明的是1650 字节并非默认值。原文档明确写了 “up to 1650 bytes when chained (configuration dependant)”——链式广播允许的最终长度取决于 NimBLE 栈的具体配置如BLE_EXT_ADV_MAX_SIZE等相关选项。251 字节是单次扩展广播报文的理论上限去掉头部等开销后实际可用载荷约为 230 字节左右这一点在仓库示例的注释中也有印证见下文示例代码注释 “251 bytes (minus header bytes ~20)”。1.1 三种 PHY物理层的取舍原文档指出扩展广播引入了新的物理层1M PHY经典 BLE 默认的 1 Mbps 物理层兼容性最好所有 BLE 5.x 设备都支持2M PHY将数据率提升到 2 Mbps适合吞吐优先、链路距离较短的应用CODED PHY通过前向纠错编码Coded PHY即 Long Range 模式换取更远的通信距离但数据率会显著下降约 125 kbps 或 500 kbps适合远距离广播/扫描场景。此外还有周期广播Periodic Advertising扫描设备可以先与某个信标的广播序列建立同步从而在下一个预期广播到来之前休眠或执行其他任务以节省 CPU 周期与功耗。原文档明确标注该项为 “To be implemented”待实现因此本文不做展开读者在使用时应注意该能力在 esp-nimble-cpp 中尚不可用。二、启用扩展广播三种配置入口扩展广播是编译期特性由配置项CONFIG_BT_NIMBLE_EXT_ADV控制取值为 1 时启用。根据你的构建环境有三种设置方式2.1 ESP-IDFmenuconfig 图形化配置在 ESP-IDF 工程中执行idf.py menuconfig依次进入Component config Bluetooth NimBLE options Enable extended advertising勾选该项后保存退出重新构建即可。仓库示例的 sdkconfig.defaults 展示了在 IDF 工程中以文本方式启用该特性的完整配置块CONFIG_BT_ENABLEDy CONFIG_BTDM_CTRL_MODE_BLE_ONLYy CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLYn CONFIG_BTDM_CTRL_MODE_BTDMn CONFIG_BT_BLUEDROID_ENABLEDn CONFIG_BT_NIMBLE_ENABLEDy CONFIG_BT_NIMBLE_EXT_ADVy其中CONFIG_BT_NIMBLE_ENABLEDy表示使用 NimBLE 作为唯一 BLE 协议栈Bluedroid 被关闭CONFIG_BTDM_CTRL_MODE_BLE_ONLYy表示控制器仅启用 BLE 模式——扩展广播要求 NimBLE 栈且控制器支持 BLE 5 特性。2.2 Arduinonimconfig.h在 ArduinoArduino-ESP32 核心 NimBLE-Arduino环境中直接在nimconfig.h中设置该宏为 1 即可。2.3 PlatformIObuild_flags在 PlatformIO 工程包括本仓库 Tasmota 这类基于 PlatformIO 的固件中通过build_flags传入编译宏例如在platformio.ini中加入build_flags -DCONFIG_BT_NIMBLE_EXT_ADV1注意无论哪种方式该选项都需要与 NimBLE 主机栈的BLE_EXT_ADVMYNEWT_VAL联动生效。从 NimBLEExtAdvertising.h 的编译守卫可以看出扩展广播相关类仅在CONFIG_BT_NIMBLE_ENABLED MYNEWT_VAL(BLE_ROLE_BROADCASTER) MYNEWT_VAL(BLE_EXT_ADV)同时成立时才会被编译进固件。三、启用后API 的整体变化原文档给出了启用扩展广播后五条关键行为变化这是理解整套新 API 的纲领扫描端NimBLEScan::start会自动同时在 1M PHY 和 CODED PHY 上扫描连接端NimBLEClient::connect默认使用对端设备正在监听的 primary PHY除非显式指定连接端NimBLEClient::setConnectPhy变为可用用于指定连接使用的 PHY默认全部广播端NimBLEAdvertising不再可用被NimBLEExtAdvertising取代NimBLEDevice::getAdvertising()返回的不再是NimBLEAdvertising*而是NimBLEExtAdvertising*广播数据端NimBLEAdvertisementData不再可用被NimBLEExtAdvertisement取代所有广播配置含广播间隔、广播结束回调都集中在这个新类上。下面的三个小节分别对应扫描、连接、广播三条链路逐一展开源码级细节。四、扫描端双 PHY 自动扫描与 setPhy启用扩展广播后NimBLEScan::start会自动在 1M 与 CODED 两个 PHY 上发起扫描。这一点在源码中得到印证NimBLEScan.cpp 内部调用ble_gap_ext_disc_params构造扩展扫描参数并分别向 1M 与 CODED 两个 PHY 传入扫描参数# if MYNEWT_VAL(BLE_EXT_ADV) ble_gap_ext_disc_params scan_params; scan_params.passive m_scanParams.passive; // ... ble_gap_ext_disc(m_ownAddrType, m_scanParams.limited, m_phy SCAN_1M ? scan_params : NULL, m_phy SCAN_CODED ? scan_params : NULL, NimBLEScan::handleGapEvent, ...); #endif而扫描的 PHY 集合可以通过NimBLEScan::setPhy(Phy phyMask)显式指定见 NimBLEScan.cpp可选的掩码值在 NimBLEScan.cpp 的注释中列出NIMBLE_CPP_SCAN_1M仅扫 1M PHYNIMBLE_CPP_SCAN_CODED仅扫 CODED PHYNIMBLE_CPP_SCAN_ALL默认1M CODED 全扫。同时扫描回调中还能拿到广播报文的 PHY 信息。仓库的 NimBLE_extended_scan 示例演示了如何在onResult中读取getPrimaryPhy()/getSecondaryPhy()并在onScanEnd里轮换SCAN_ALL → SCAN_1M → SCAN_CODED观察不同 PHY 下的扫描差异class ScanCallbacks : public NimBLEScanCallbacks { void onResult(const NimBLEAdvertisedDevice* advertisedDevice) override { printf(Advertised Device found: %s\n PHY1: %d\n PHY2: %d\n, advertisedDevice-toString().c_str(), advertisedDevice-getPrimaryPhy(), advertisedDevice-getSecondaryPhy()); } // ... }; NimBLEDevice::init(NimBLE Extended Scanner); NimBLEScan* pScan NimBLEDevice::getScan(); pScan-setScanCallbacks(scanCallbacks); pScan-setActiveScan(true); pScan-setPhy(NimBLEScan::Phy::SCAN_ALL); // 默认即 SCAN_ALL pScan-start(10 * 1000); // 0 表示无限扫描从源码还可以看到扩展扫描对链式分片广播有专门的合并处理当getDataStatus() BLE_GAP_EXT_ADV_DATA_STATUS_INCOMPLETE时NimBLEScan.cpp扫描器会等待后续分片到达后再上报完整结果对于使用同一地址但不同 set ID 的广播会按sidset identifier区分成独立的广播设备NimBLEScan.cpp。五、连接端setConnectPhy 与连接 PHY 的选择启用扩展广播后NimBLEClient::connect默认会使用对端广播时监听的 primary PHY 发起连接。如果希望显式控制连接使用的 PHY可以使用NimBLEClient::setConnectPhy(uint8_t phyMask)。源码实现非常直接——它只是把掩码存入成员变量m_phyMaskNimBLEClient.cpp真正生效是在连接时传给ble_gap_ext_connectNimBLEClient.cpprc ble_gap_ext_connect(NimBLEDevice::m_ownAddrType, peerAddr, m_connectTimeout, m_phyMask, m_connParams, ...);而m_phyMask的默认值是三个 PHY 全开见构造函数初始化列表NimBLEClient.cpp# if MYNEWT_VAL(BLE_EXT_ADV) m_phyMask{BLE_GAP_LE_PHY_1M_MASK | BLE_GAP_LE_PHY_2M_MASK | BLE_GAP_LE_PHY_CODED_MASK}, # endifsetConnectPhy的掩码定义与 NimBLE 栈一致见 NimBLEClient.cpp 的注释0x01BLE_GAP_LE_PHY_1M_MASK0x02BLE_GAP_LE_PHY_2M_MASK0x04BLE_GAP_LE_PHY_CODED_MASK例如只允许 CODED 远距离连接NimBLEClient* pClient NimBLEDevice::createClient(); pClient-setConnectPhy(BLE_GAP_LE_PHY_CODED_MASK); pClient-connect(targetAddress);此外类中还保留有updatePhy(uint8_t txPhysMask, uint8_t rxPhysMask, uint16_t phyOptions 0)NimBLEClient.h用于在已建立的连接上动态切换收发 PHY——例如先以 1M 建连再协商升级到 2M 提速。六、广播端NimBLEExtAdvertising 与 NimBLEExtAdvertisement这是启用扩展广播后改动最大、也最需要理解的一部分。旧的NimBLEAdvertising类被NimBLEExtAdvertising取代旧的NimBLEAdvertisementData被NimBLEExtAdvertisement取代NimBLEDevice::getAdvertising()返回类型自动切换为NimBLEExtAdvertising*。从 NimBLEExtAdvertising.h 可以看到NimBLEExtAdvertisement是“一切广播配置的集合体”其公开接口覆盖数据内容类setName(name, isComplete)/setShortName(name)设备名完整名或短名addServiceUUID/removeServiceUUID/removeServices单个服务 UUIDsetCompleteServices/setCompleteServices16/32/setPartialServices/setPartialServices16/32完整/部分服务列表16 位、32 位setServiceData(uuid, data)服务数据setManufacturerData(data)厂商自定义数据setURI(uri)、setAppearance、setFlags、setTxPoweraddTxPower、setPreferredParamsURI、外观、标志、发射功率、连接参数等setData(data, len)/addData(data, len)直接写入/追加原始 AD 数据这是构造超过常规长度的自定义载荷的关键入口。PHY 与广播模式类NimBLEExtAdvertisement(priPhy BLE_HCI_LE_PHY_1M, secPhy BLE_HCI_LE_PHY_1M)构造函数指定主/次 PHYsetPrimaryPhy(phy)/setSecondaryPhy(phy)运行时调整主/次 PHYsetLegacyAdvertising(bool)切换为传统Legacy广播模式兼容旧设备setConnectable(bool)/setScannable(bool)可连接 / 可扫描setDirected(bool, high_duty)/setDirectedPeer(addr)定向广播指向指定对端setAnonymous(bool)匿名广播不携带地址setMinInterval/setMaxInterval广播间隔范围setScanFilter(wlOnly, connectWlOnly)白名单过滤setPrimaryChannels(ch37, ch38, ch39)主广播信道选择setTxPower(dbm)、setAddress(addr)发射功率与广播地址enableScanRequestCallback(bool)开启扫描请求回调clearData()/removeData(type)/getDataSize()等数据维护接口。NimBLEExtAdvertisingNimBLEExtAdvertising.h则是广播控制类核心方法是基于**实例 IDinstId**的多实例管理bool start(uint8_t instId, int duration 0, int maxEvents 0); // 启动某个实例duration 毫秒 / maxEvents 最大广播次数 bool setInstanceData(uint8_t instId, NimBLEExtAdvertisement adv); // 设置某实例的广播数据内部拷贝可传局部变量 bool setScanResponseData(uint8_t instId, NimBLEExtAdvertisement data); bool removeInstance(uint8_t instId); bool removeAll(); bool stop(uint8_t instId); bool stop(); bool isActive(uint8_t instId); bool isAdvertising(); void setCallbacks(NimBLEExtAdvertisingCallbacks* callbacks, bool deleteCallbacks true);配套的NimBLEExtAdvertisingCallbacksNimBLEExtAdvertising.h提供两个回调onStopped(pAdv, reason, instId)广播停止可依据 reason 区分“超时停止”还是“客户端正在连接”onScanRequest(pAdv, instId, addr)收到扫描请求需先通过enableScanRequestCallback(true)开启。七、实战完整可运行的扩展广播示例仓库 examples/Bluetooth_5 下提供了四个官方示例覆盖服务器、扫描、客户端与多实例广播四种场景。下面拆解其中最核心的两个。7.1 扩展广播服务器CODED 远距离 长数据NimBLE_extended_server/main/main.cpp 演示了“在 CODED 与 1M PHY 上广播 226 字节长消息并可连接”的完整流程其核心逻辑为// 1. 初始化并建服务器 NimBLEDevice::init(Extended advertiser); NimBLEServer* pServer NimBLEDevice::createServer(); // ... 创建 Service/Characteristicstart() // 2. 构造扩展广播主 PHY 用 CODED远距离次 PHY 用 1M static uint8_t primaryPhy BLE_HCI_LE_PHY_CODED; static uint8_t secondaryPhy BLE_HCI_LE_PHY_1M; NimBLEExtAdvertisement extAdv(primaryPhy, secondaryPhy); // 3. 可连接广播按 BLE 规范扩展广播不能同时 scannable 与 connectable extAdv.setConnectable(true); extAdv.setScannable(false); // 4. 单次扩展广播可放 251 字节扣除头部约 20 字节链式最高 1650 字节 extAdv.setServiceData(NimBLEUUID(SERVICE_UUID), std::string(Extended advertising allows for 251 bytes of data in a single advertisement,\r\n or up to 1650 bytes with chaining.\r\n This example message is 226 bytes long and is using CODED_PHY for long range.)); extAdv.setName(Extended advertiser); // 5. 扩展广播启用后getAdvertising() 返回 NimBLEExtAdvertising* NimBLEExtAdvertising* pAdvertising NimBLEDevice::getAdvertising(); pAdvertising-setCallbacks(advertisingCallbacks); if (pAdvertising-setInstanceData(0, extAdv)) { // 6. start(实例ID, 持续时间毫秒[, 最大广播次数]) pAdvertising-start(0, advTime); // advTime 5000 }配套的AdvertisingCallbacks展示了如何利用onStopped的 reason 参数区分“广播超时”BLE_HS_ETIMEOUT此时进入深睡眠省电与“客户端正在连接”reason 为 0不睡眠class AdvertisingCallbacks : public NimBLEExtAdvertisingCallbacks { void onStopped(NimBLEExtAdvertising* pAdv, int reason, uint8_t instId) override { switch (reason) { case 0: // 客户端正在连接 printf(Client connecting\n); return; case BLE_HS_ETIMEOUT: // 广播超时 printf(Time expired - sleeping\n); break; default: break; } esp_deep_sleep_start(); } };该示例还演示了“广播 5 秒 → 深睡眠 20 秒 → 唤醒再广播”的周期性省电模式esp_sleep_enable_timer_wakeup(sleepSeconds * 1000000)这正是扩展广播典型应用场景——信标节点以极低功耗持续向远距离广播状态信息。7.2 多实例广播Multi Advertiser扩展广播的另一大优势是多个广播实例可以并行工作实例 ID 递增最多实例数取决于 menuconfig 配置实例 0 始终可用。NimBLE_multi_advertiser/main/main.cpp 演示了把两种广播放在不同实例上同时发送// 实例 0扩展的可扫描scannable广播走 CODED 1M NimBLEExtAdvertisement extScannable(primaryPhy, secondaryPhy); extScannable.setScannable(true); extScannable.setConnectable(false); // 规范要求scannable 与 connectable 互斥 extScannable.setServiceData(NimBLEUUID(SERVICE_UUID), std::string(Scan me!)); extScannable.enableScanRequestCallback(true); // 开启扫描请求回调 // 实例 1传统Legacy可连接广播兼容旧扫描器 NimBLEExtAdvertisement legacyConnectable; legacyConnectable.setAddress(NimBLEAddress(DE:AD:BE:EF:BA:AD)); // 自定义广播地址 legacyConnectable.setName(Legacy); legacyConnectable.setCompleteServices16({NimBLEUUID(SERVICE_UUID)}); legacyConnectable.setLegacyAdvertising(true); // 传统模式 legacyConnectable.setConnectable(true); // 实例 1 的可选扫描响应 NimBLEExtAdvertisement legacyScanResponse; legacyScanResponse.setServiceData(NimBLEUUID(SERVICE_UUID), Legacy SR); NimBLEExtAdvertising* pAdvertising NimBLEDevice::getAdvertising(); if (pAdvertising-setInstanceData(0, extScannable) pAdvertising-setInstanceData(1, legacyConnectable) pAdvertising-setScanResponseData(1, legacyScanResponse)) { pAdvertising-start(0, advTime); pAdvertising-start(1, advTime); }这个示例同时演示了三个细节传统广播可通过setLegacyAdvertising(true)复刻用于兼容仅支持 BLE 4.x 的旧扫描器扫描响应数据要设置到与对应广播相同的实例上setScanResponseData(1, ...)以及扫描请求回调的用法——onScanRequest收到对端请求后可通过pAdv-setScanResponseData(instId, sr)动态更新实例的扫描响应内容。八、使用扩展广播的注意事项结合原文档与源码汇总以下容易踩坑的点CONFIG_BT_NIMBLE_EXT_ADV必须与 NimBLE 栈的BLE_EXT_ADV联动即使编译宏打开若 MYNEWT 侧的BLE_ROLE_BROADCASTER/BLE_EXT_ADV未生效相关类不会参与编译见 NimBLEExtAdvertising.h 的编译守卫。scannable 与 connectable 互斥按蓝牙规范扩展广播不允许同时是可扫描的和可连接的代码注释 “As per Bluetooth specification, extended advertising cannot be both scannable and connectable”。需要被连接时设setConnectable(true)setScannable(false)需要可扫描时反之。载荷上限是配置相关的单次 251 字节是协议上限实际可用约 230 字节扣头部1650 字节链式广播需要栈配置支持。API 不兼容是彻底替换而非兼容并存启用扩展广播后旧类NimBLEAdvertising/NimBLEAdvertisementData不再可用getAdvertising()返回类型变化旧代码需要迁移。多实例上限依赖配置默认实例 0 始终可用更多实例需在 menuconfig 中配置示例注释提到 “Up to 5 instances can be used if configured in menuconfig”。周期广播尚未实现这是原文档明确标注 “To be implemented” 的能力目前不能依赖它做同步休眠优化。主机端支持扫描/连接扩展广播需要主机与控制器都支持 BLE 5例如 ESP32 系列中需确认所选 SoC 与 NimBLE 版本支持 2M/CODED PHY。九、进一步阅读本文核心文档docs/Bluetooth 5 features.md类定义与 API 参考src/NimBLEExtAdvertising.h扫描实现src/NimBLEScan.cpp连接 PHY 实现src/NimBLEClient.cpp官方示例目录examples/Bluetooth_5含扩展服务器、扩展扫描、扩展客户端、多实例广播四个工程启用扩展广播的 IDF 配置示例examples/Bluetooth_5/NimBLE_extended_server/sdkconfig.defaults迁移指南旧 API 到新 APIdocs/Migration_guide.md【免费下载链接】TasmotaAlternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at项目地址: https://gitcode.com/GitHub_Trending/ta/Tasmota创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表