
深入解读 esp-nimble-cppESP32 上基于 Apache NimBLE 的高性能 BLE C 库【免费下载链接】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本文以 Tasmota 仓库内置的 esp-nimble-cpp 为主体系统讲解这一替代 Bluedroid 的 ESP32 BLE 开发库从核心 API、安装配置、Server/Client 快速上手到 Bluedroid 迁移、1.x 到 2.x 升级要点、线程安全与资源优化技巧以及蓝牙 5 扩展广播能力。读完本文你将掌握用NimBLEDevice.h一个头文件完成 ESP32 蓝牙外设与中心设备的开发并能理解它在 Tasmota 的 BLE 驱动与 iBeacon 扫描中如何落地。esp-nimble-cpp 是什么esp-nimble-cpp 是运行于 ESP32含 ESP32-S2/S3/C3/C5/C6/P4 等系列的 NimBLE C 封装库目标是在尽力保持与 nkolban cpp_utils BLE API 兼容的前提下用 Apache 的 NimBLE 协议栈替代 ESP-IDF 默认的 Bluedroid 栈。库本身是线程安全的特性Characteristic可以在任意线程中安全读写。它背后的 NimBLE 是 Apache 推出的完全开源的 BLE 协议栈比 Bluedroid 更适合资源受限设备并已由 Espressif 官方移植到 ESP32 平台。相比基于 Bluedroid 的原库上游 README 给出的测试数据显示Flash 占用减少近 50%RAM 占用减少约 100kB原文同时注明Your results may vary即实际结果随配置而异。这也是该库更活跃地迭代维护、提供更丰富能力与稳定性的原因。当前仓库内置的版本为2.3.32025-09-05 发布详见 CHANGELOG.md2.x 版本已正式发布若从 1.x 升级可参考 1.x 到 2.x 迁移指南。仓库结构一览lib/libesp32_div/esp-nimble-cpp/ ├── README.md # 项目总览、安装与快速指引 ├── CHANGELOG.md # 版本变更记录 ├── Kconfig # ESP-IDF menuconfig 配置项 ├── CMakeLists.txt / library.json # 构建与依赖描述 ├── docs/ # New_user_guide / Migration_guide / Usage_tips / Bluetooth 5 features 等 ├── examples/ # Server、Client、Async Client、Continuous Scan、L2CAP、BLE 5 等完整示例 └── src/ # NimBLEDevice / NimBLEServer / NimBLEClient / NimBLEScan 等全部实现所有类的声明与实现集中在 src 目录核心入口为 NimBLEDevice.h。安装与启用ESP-IDF v4.0 环境按上游 README 的安装方式将本库下载为 .zip 解压或直接 clone 到你的 ESP-IDF 工程的components目录下。运行idf.py menuconfig进入Component config - Bluetooth启用 Bluetooth并在Bluetooth host中选择NimBLE。在NimBLE Options中按需配置各项参数对应本仓库的 Kconfig。在main.cpp顶部#include NimBLEDevice.h。在app_main中调用NimBLEDevice::init();完成协议栈初始化。与 Arduino 配合 CMake 编译时的注意事项当与 Arduino 一同使用并采用CMake构建时必须在工程CMakeLists.txt中include($ENV{IDF_PATH}/tools/cmake/project.cmake)这一行之后追加add_compile_definitions(ARDUINO_ARCH_ESP321)否则 Arduino 会自行释放 BLE 相关内存导致库无法正常工作。在 Tasmota 项目中的使用在 Tasmota 仓库中该库作为 ESP32 分支的第三方组件位于 lib/libesp32_div/esp-nimble-cpp由 xdrv_79_esp32_ble.inoESP32 BLE 传感器驱动通过NimBLEClient连接各类 BLE 外设并执行配对/读值与 xsns_52_ibeacon.inoiBeacon 扫描器通过NimBLEDevice::init()与NimBLEDevice::getScan()扫描附近信标直接调用。这说明该库在真实固件中同时承担了 BLE 中心设备Central与扫描器Observer两种角色。快速上手创建 BLE Server详细教程见 New_user_guide.md。所有功能只需包含一个头文件#include NimBLEDevice.h1. 初始化任何 BLE 操作之前都必须初始化协议栈NimBLEDevice::init(your device name here);参数为要广播的设备名字符串如果不做 Server 或不想广播名称传空字符串即可。该调用不强制要求出现在app_mainIDF或setupArduino中但惯例如此。2. 创建 Server 与 Serviceextern C void app_main(void) { NimBLEDevice::init(NimBLE); NimBLEServer *pServer NimBLEDevice::createServer(); NimBLEService *pService pServer-createService(ABCD); }createService的 UUID 参数为十六进制字符串支持16 位、32 位、128 位三种长度。示例使用 16 位的ABCD。3. 添加 CharacteristicNimBLECharacteristic *pCharacteristic pService-createCharacteristic(1234);createCharacteristic接受两个参数UUID 与属性位掩码。属性默认值为NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE即允许无加密读写。完整属性列表如下属性含义NIMBLE_PROPERTY::READ允许读取NIMBLE_PROPERTY::READ_ENC需加密配对/绑定后读取NIMBLE_PROPERTY::READ_AUTHEN需认证后读取NIMBLE_PROPERTY::READ_AUTHOR需授权后读取NIMBLE_PROPERTY::WRITE允许写入NIMBLE_PROPERTY::WRITE_NR允许无响应写入NIMBLE_PROPERTY::WRITE_ENC需加密后写入NIMBLE_PROPERTY::WRITE_AUTHEN需认证后写入NIMBLE_PROPERTY::WRITE_AUTHOR需授权后写入NIMBLE_PROPERTY::BROADCAST允许广播NIMBLE_PROPERTY::NOTIFY支持通知NotificationNIMBLE_PROPERTY::INDICATE支持指示Indication4. 启动服务并广播extern C void app_main(void) { NimBLEDevice::init(NimBLE); NimBLEServer *pServer NimBLEDevice::createServer(); NimBLEService *pService pServer-createService(ABCD); NimBLECharacteristic *pCharacteristic pService-createCharacteristic(1234); pService-start(); pCharacteristic-setValue(Hello BLE); NimBLEAdvertising *pAdvertising NimBLEDevice::getAdvertising(); pAdvertising-addServiceUUID(ABCD); // 广播服务 UUID pAdvertising-setName(NimBLE); // 广播设备名 pAdvertising-start(); // 开始广播 }完成后用手机上的 nRF Connect 等 BLE 工具扫描即可看到名为 NimBLE、包含服务 ABCD 的设备。更完整的 Server 示例含连接参数更新、MTU 回调、Passkey 配对、2904 描述符、通知循环等见 examples/NimBLE_Server/main/main.cpp。快速上手创建 BLE Client1. 初始化与扫描#include NimBLEDevice.h extern C void app_main(void) { NimBLEDevice::init(); NimBLEScan *pScan NimBLEDevice::getScan(); NimBLEScanResults results pScan-getResults(10 * 1000); // 扫描 10 秒阻塞式 }getResults(duration)的duration为毫秒传0表示无限扫描getResults是阻塞式库同时提供非阻塞重载。2. 在结果中查找目标服务NimBLEUUID serviceUuid(ABCD); for (int i 0; i results.getCount(); i) { const NimBLEAdvertisedDevice *device results.getDevice(i); if (device-isAdvertisingService(serviceUuid)) { // 找到目标设备 } }3. 连接、读取特征值并清理NimBLEUUID serviceUuid(ABCD); for (int i 0; i results.getCount(); i) { const NimBLEAdvertisedDevice *device results.getDevice(i); if (device-isAdvertisingService(serviceUuid)) { NimBLEClient *pClient NimBLEDevice::createClient(); if (!pClient) { // 确认客户端创建成功 break; } if (pClient-connect(device)) { // 返回 true 表示连接成功 NimBLERemoteService *pService pClient-getService(serviceUuid); if (pService ! nullptr) { NimBLERemoteCharacteristic *pCharacteristic pService-getCharacteristic(1234); if (pCharacteristic ! nullptr) { std::string value pCharacteristic-readValue(); // 使用读取到的 value } } } else { // 连接失败 } NimBLEDevice::deleteClient(pClient); // 用完删除释放资源 } }几点注意NimBLEClient::connect的返回值必须检查getService/getCharacteristic返回nullptr时必须判空否则对空指针调用方法必然崩溃。库支持创建多个 Client 实例上限为配置的最大连接数默认 3用完通过NimBLEDevice::deleteClient删除。删除 Client 时会自动断开连接无需单独调用 disconnect。原文档示例中connect(device)实为对const NimBLEAdvertisedDevice*的直接传递应写作connect(device)。更完整的 Client 示例见 examples/NimBLE_Client/main/main.cpp普通客户端与 examples/NimBLE_Async_Client/main/main.cpp异步非阻塞客户端。从 Bluedroid 迁移到 NimBLE完整指南见 Migration_guide.md以下为必须修改的要点。头文件与类名统一包含NimBLEDevice.h无需其它头文件。所有类名在原名称前加 Nim 前缀BLEDevice→NimBLEDevice、BLEServer→NimBLEServer等。为方便迁移库提供了别名定义两种名称均可使用多数现有代码无需改名。调试日志可使用NimBLELog.h中的NIMBLE_LOGx宏用法与ESP_LOGx一致。地址 APIBLEAddressNimBLEAddress构造时新增可选参数uint8_t type指定地址类型默认 0Public 静态地址。例如BLEAddress addr(11:22:33:44:55:66, 1)表示类型 1Random。getNative更名为getBase返回const ble_addr_t*。Server 端回调签名变化onConnect(NimBLEServer* pServer, NimBLEConnInfo connInfo)新增必需的NimBLEConnInfo参数用于获取对端信息。onDisconnect(NimBLEServer* pServer, NimBLEConnInfo connInfo, int reason)新增对端信息与断开原因码。onMtuChanged更名为onMTUChange(uint16_t MTU, NimBLEConnInfo connInfo)。所有回调都有默认实现应用只需覆写关心的回调。特性属性与取值属性枚举从BLECharacteristic::PROPERTY_XXX改为NIMBLE_PROPERTY::XXX完整列表见上文表格。BLECharacteristic::getData已移除返回易失内部指针容易引发异常改用getValue()取得数据副本后再取指针或使用模板std::string value pCharacteristic-getValue(); uint8_t *pData (uint8_t*)value.data(); // 或 my_struct_t myStruct pChr-getValuemy_struct_t();描述符描述符统一通过NimBLECharacteristic::createDescriptor创建签名如下NimBLEDescriptor* createDescriptor(const char* uuid, uint32_t properties NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE, uint16_t max_len 100); NimBLEDescriptor* createDescriptor(NimBLEUUID uuid, uint32_t properties NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE, uint16_t max_len 100);0x2902CCCD类已被移除只要特性带有 NOTIFY 或 INDICATE 属性NimBLE 会自动创建 0x2902 描述符订阅回调由新增的NimBLECharacteristicCallbacks::onSubscribe处理。手动创建 0x2902 会触发警告并被内部标记为失效。0x2904Characteristic Presentation Format有专门类通过create2904()创建返回NimBLE2904*。描述符回调onRead/onWrite同样新增必需的NimBLEConnInfo connInfo参数。Server 安全在特性/描述符属性上应用NIMBLE_PROPERTY::READ_ENC / READ_AUTHEN / READ_AUTHOR / WRITE_ENC / WRITE_AUTHEN / WRITE_AUTHOR即可触发配对流程默认自动执行 just-works 配对可改为 Passkey 认证或数字比较见下文安全 API。广播 APIsetAdvertisementData会整体替换addServiceUUID、setAppearance等设置的数据需要把全部数据放进NimBLEAdvertisementData统一设置。start新增两个可选参数广播时长毫秒与定向广播目标NimBLEAddress。Client API支持多个 Client 实例上限为配置的最大连接数默认 3删除必须用NimBLEDevice::deleteClient。connect新签名原 type 参数移除NimBLEClient::connect(bool deleteServices true, bool asyncConnect false, bool exchangeMTU true); NimBLEClient::connect(const NimBLEAddress address, bool deleteAttributes true, bool asyncConnect false, bool exchangeMTU true); NimBLEClient::connect(const NimBLEAdvertisedDevice* device, bool deleteServices true, bool asyncConnect false, bool exchangeMTU true);deleteServices是否丢弃之前获取的属性数据库。置false可复用上次连接缓存加速重连、省电。asyncConnect置true时立即返回连接结果通过onConnect/onConnectFail回调通知。exchangeMTU连接时是否执行 MTU 协商小数据量场景可关闭以提升连接速度。移除了连接时自动发现全部属性的行为耗时耗资源改为按需getService/getCharacteristic获取需要全部属性时调用新增的NimBLEClient::discoverAttributes。getServices/getCharacteristics/getDescriptors增加可选 bool 参数true 表示从服务器重新获取默认 false 返回已知数据库返回值从std::map改为std::vector指针/引用。远端特性writeValue现在返回true/false表示写入成败。registerForNotify已移除改用subscribe/unsubscribe。readUInt8/16/32、readFloat移除统一用模板readValuetype()readRawData移除改用readValue()返回的NimBLEAttValue的data()成员或readValuemy_struct_t()模板。扫描 APINimBLEScan::start的时长参数从秒改为毫秒回调参数被移除新增bool restart置 true 可重启进行中的扫描并清空重复缓存阻塞式扫描改用getResults重载。扫描回调由NimBLEAdvertisedDeviceCallbacks替换为NimBLEScanCallbacks含onResult、onScanEnd、onDiscovered注册接口为NimBLEScan::setScanCallbacks。安全 API安全操作统一收口到NimBLEDevice回调并入NimBLEServerCallbacks/NimBLEClientCallbacks回调说明bool onConfirmPasskey(NimBLEConnInfo connInfo, uint32_t pin)数字比较认证时收到 pin调用NimBLEDevice::injectConfirmPasskey(connInfo, true/false)接受或拒绝void onPassKeyEntry(NimBLEConnInfo connInfo)Client 回调调用NimBLEDevice::injectPassKey(connInfo, 123456)响应uint32_t onPassKeyDisplay()Server 回调返回期望客户端输入的 Passkeyvoid onAuthenticationComplete(NimBLEConnInfo connInfo)认证完成成败信息从NimBLEConnInfo读取安全设置方法NimBLEDevice::setSecurityAuth(bool bonding, bool mitm, bool sc); NimBLEDevice::setSecurityAuth(uint8_t auth_req); // 如 BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM | BLE_SM_PAIR_AUTHREQ_SC NimBLEDevice::setSecurityIOCap(uint8_t iocap); // 如 BLE_HS_IO_DISPLAY_ONLY / BLE_HS_IO_DISPLAY_YESNO / BLE_HS_IO_NO_INPUT_OUTPUT NimBLEDevice::setSecurityInitKey(uint8_t init_key); // 发起安全流程时我方分发的密钥 NimBLEDevice::setSecurityRespKey(uint8_t resp_key); // 配对时我方愿意接受的对端密钥IO 能力决定配对方式BLE_HS_IO_DISPLAY_ONLY触发 Passkey 配对BLE_HS_IO_DISPLAY_YESNO触发数字比较BLE_HS_IO_NO_INPUT_OUTPUT默认为 just-works。Arduino 配置与 ESP32-Arduino 预装的原始库不同本库将本应在 menuconfig 中设置的配置项全部暴露在src/nimconfig.h中该文件带完整注释Arduino 用户可直接修改例如增加最大连接数或将 BLE 栈加载到外部 PSRAM。从 1.x 升级到 2.x1.x_to2.x_migration_guide.md 列出了 2.x 的破坏性变更核心要点时间参数统一为毫秒如NimBLEScan::start(10000)表示 10 秒。NimBLESecurity类移除功能并入NimBLEServer/NimBLEClient。所有面向连接的回调改用NimBLEConnInfo替代ble_gap_conn_desc*如onAuthenticationComplete(NimBLEConnInfo connInfo)。NimBLEDevice变化getInitialized→isInitializedsetPower直接接受 dbm 整数如9代替ESP_PWR_LVL_P9setOwnAddrType移除nrpa参数getClientListSize→getCreatedClientCountgetClientByID→getClientByHandle。地址比较现在包含地址类型按 BLE 规范同值不同型的地址视为不同构造地址时需显式指定类型。getNative系列替换为getBaseNimBLEUUID构造函数移除msbFirst参数。notify不再接受is_notification参数指示改调indicate()onNotify回调移除onStatus移除 status 参数。onPassKeyRequestServer→onPassKeyDisplayonConfirmPIN→onConfirmPasskey、onPassKeyRequestClient→onPassKeyEntry。扫描NimBLEScan::start(10, false)应改为getResults(10000, false)clearDuplicateCache移除改用start(..., restarttrue)结果迭代器为const_iterator。广播setMinPreferred/setMaxPreferred合并为setPreferredParams名称与 TX 功率不再默认广播需手动setName/addTxPowersetAdvertisementType→setConnectableModesetScanResponse→enableScanResponse扫描响应默认关闭start的回调参数移除改用setAdvertisingCompleteCallback。信标移除 Eddystone URLGoogle 已于 2021 年关闭服务NimBLEEddystoneTLM温度改用int16_tsetData/getData改用BeaconData结构。工具dumpGapEvent移除buildHexData→dataToHexString。使用技巧与性能建议Usage_tips.md 总结了大量实战经验线程安全库是线程安全的属性可被自由地跨线程操作。不要随意删除 Client 实例Client 连接后会在实例生命周期内缓存已获取的服务/特性信息。若周期性重连同一设备却反复删除 Client会导致每次重新抓取属性——显著消耗对端设备电池、加剧堆碎片、降低连接性能。本库的 Client 实例内存仅约为 Bluedroid 版的 20%删除收益有限。建议距下次连接同一设备间隔小于 5 分钟时保留 Client 实例。只按需获取服务与特性getServices/getCharacteristics的true参数强制从服务器抓取应仅用于未知设备。对已知设备优先使用getService(NimBLEUUID)/getCharacteristic(NimBLEUUID)定向获取可降低能耗、减少堆分配、缩短连接时间。务必检查返回值多数函数都应检查结果如connect检查 true/falsegetService检查 nullptr对 nullptr 调用方法必然崩溃。持久化绑定丢失与 MAX_CCCDS现象CONFIG_BT_NIMBLE_MAX_BONDS设为 N但实际保留的绑定数量更少甚至只有一个。原因每个绑定都会持久化客户端订阅过的每个 CCCD 值若CONFIG_BT_NIMBLE_MAX_CCCDS太小旧 CCCD 会被新值覆盖导致旧绑定失效。修复调大CONFIG_BT_NIMBLE_MAX_CCCDS每个 CCCD 在 NVS 中约占 40 字节2 字节值 NVS 元数据开销建议取值不小于CONFIG_BT_NIMBLE_MAX_BONDS × 可订阅特性最大数量。设备 Local Name 的两种来源Advertising Local name广播数据中的名称字段通过NimBLEAdvertising::setName()设置。GATT Device NameGeneric Access 服务中 UUID 0x2A00 特性通过NimBLEDevice::init()或NimBLEDevice::setDeviceName()设置连接后才会被读取。注意操作系统会缓存 GATT Device Name并在连接后据此更新设备显示名——若广播名是 ABCD 而 GATT 名是 12345设备在连接前显示 ABCD、连接后变为 12345若未设置广播名iOS 等系统在连接前可能显示 Unnamed。因此建议两者都妥善设置。蓝牙 5 特性扩展广播Bluetooth 5 features.md 介绍了扩展广播能力广播数据从 31 字节提升到251 字节链式广播configuration dependent最多可达1650 字节。新增物理层PHY2M PHY更高速率、CODED PHY更远距离/更慢速率以及原有 1M PHY。周期性广播Periodic Advertising扫描设备可与信标广播同步在下次广播前休眠或执行其它任务以节省 CPU 与功耗该库中待实现。启用方式将配置项CONFIG_BT_NIMBLE_EXT_ADV设为 1menuconfig 的Component config Bluetooth NimBLE options Enable extended advertisingArduino 在nimconfig.h设置PlatformIO 通过build_flags设置。启用后的行为变化NimBLEScan::start自动在 1M PHY 与 CODED PHY 上同时扫描。NimBLEClient::connect使用设备监听的 primary PHY可用NimBLEClient::setConnectPhy指定默认全部。NimBLEAdvertising被NimBLEExtAdvertising取代getAdvertising返回后者。NimBLEAdvertisementData被NimBLEExtAdvertisement取代广播间隔与广播结束回调等都在新类中配置。对应完整示例见 examples/Bluetooth_5 目录Extended Server / Extended Client / Extended Scan / Multi Advertiser。其它构建与运行配置Kconfig 展示了该库自身的可配置项包括NIMBLE_CPP_LOG_LEVEL日志详细程度None/Error/Warning/Info/Debug及日志颜色覆盖。NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT/NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT/NIMBLE_CPP_ENABLE_ADVERTISEMENT_TYPE_TEXT将返回码/事件码/广播类型以文本形式打印分别占用约 8kB / 1kB / 250B Flash。NIMBLE_CPP_ADDR_FMT_EXCLUDE_DELIMITER/NIMBLE_CPP_ADDR_FMT_UPPERCASEMAC 地址打印格式。NIMBLE_CPP_ATT_VALUE_TIMESTAMP_ENABLED为属性值附带时间戳getValue(time_t*)/getTimeStamp()可用关闭可省内存。NIMBLE_CPP_ATT_VALUE_INIT_LENGTH空属性值的初始分配字节数默认 20范围 1–512。NIMBLE_CPP_DEBUG_ASSERT_ENABLED启用调试断言。NIMBLE_CPP_FREERTOS_TASK_BLOCK_BITFreeRTOS 任务阻塞位默认 31。BT_ENABLED/BT_NIMBLE_ENABLED启用蓝牙与 NimBLE 主机栈。ESP32-P4 目标下另有 UART 传输与 ESP-Hosted BT 相关选项。在 Tasmota 中的真实应用作为佐证该库在 Tasmota 固件中的实际调用点包括xdrv_79_esp32_ble.ino实现 ESP32 BLE 传感器驱动。内部通过NimBLEDevice::init(BLE_ESP32)初始化使用NimBLEClientCallbacksBLESensorCallback覆写onConnect/onDisconnect/onConnParamsUpdateRequest、NimBLEScanCallbacksBLEAdvCallbacks覆写onScanEnd、NimBLEDevice::getClientByPeerAddress等完成多传感器轮询、扫描与配对流程。xsns_52_ibeacon.inoiBeacon 扫描器通过NimBLEDevice::init()与NimBLEDevice::getScan()建立扫描实例在ESP32scanEndedCB(NimBLEScanResults results)回调中解析并登记附近的 iBeacon 数据。也就是说库的 Server/Client/Scan 三大核心 API 在 Tasmota 固件中均有生产级使用可直接作为嵌入式集成范本。进一步阅读新手入门New_user_guide.mdBluedroid 迁移Migration_guide.md1.x → 2.x 升级1.x_to2.x_migration_guide.md使用技巧Usage_tips.md蓝牙 5 特性Bluetooth 5 features.md完整示例examples 目录覆盖 Server、Client、Async Client、Continuous Scan、L2CAP 及 Bluetooth 5 扩展广播变更记录CHANGELOG.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),仅供参考