资讯动态

ESP32蓝牙BLE配网实战:原理、完整代码与常见问题详解

发布时间:2026/10/16 3:21:19 来源:尧图企业网站定制
一、引言在ESP32物联网项目中设备需要连接WiFi才能进行数据上报、OTA升级和远程控制。传统的WiFi配置方式有两种硬编码SSID和密码将WiFi凭据直接写在固件中用户无法修改产品化程度低。SoftAP配网ESP32开启热点用户连接热点后通过浏览器配置。该方式需要用户切换网络且手机可能自动切回移动数据导致配置页面无法打开。蓝牙BLE配网通过蓝牙将WiFi凭据发送给ESP32用户无需切换网络操作流程更简洁。ESP32内置BLE 4.2无需额外硬件即可实现。本文基于Arduino IDE和NimBLE-Arduino库实现完整的BLE配网流程包括接收WiFi信息、连接路由器、回传状态、保存至NVS并在下次上电时自动重连。二、整体通信流程BLE配网的核心流程如下ESP32开启BLE广播等待手机App连接。手机App扫描到设备并建立BLE连接。App向ESP32的写特征值Write Characteristic发送JSON格式的WiFi信息。ESP32解析JSON提取SSID和密码尝试连接路由器。ESP32通过通知特征值Notify Characteristic将配网结果回传给App。连接成功后ESP32将WiFi凭据保存到NVS非易失性存储下次上电自动重连。整个过程中BLE仅用于传输WiFi凭据和状态实际数据通信仍由WiFi完成。三、环境准备硬件· 任意ESP32开发板ESP32-WROOM-32、ESP32-S3、ESP32-C3等均可。软件· Arduino IDE。· ESP32开发板支持包通过开发板管理器安装。· 库· NimBLE-Arduino作者h2zero轻量级BLE库编译速度快RAM占用小。· ArduinoJson作者bblanchon用于解析和生成JSON数据。在Arduino IDE的库管理器中搜索并安装上述两个库即可。四、完整代码以下代码可直接复制到Arduino IDE中编译上传。代码实现了最小可用的BLE配网功能包含NVS保存和自动重连。#includeNimBLEDevice.h#includeWiFi.h#includeArduinoJson.h#includePreferences.h// 自定义Service UUID和Characteristic UUID// 需与手机App端保持一致#defineSERVICE_UUID0000FFF0-0000-1000-8000-00805F9B34FB#defineCHAR_WRITE_UUID0000FFF1-0000-1000-8000-00805F9B34FB// App写入WiFi信息#defineCHAR_NOTIFY_UUID0000FFF2-0000-1000-8000-00805F9B34FB// ESP32回传状态boolprovisionedfalse;Preferences prefs;NimBLECharacteristic*pNotifyCharnullptr;// WiFi连接函数超时时间默认15秒boolconnectWiFi(constchar*ssid,constchar*password,unsignedlongtimeoutMs15000){WiFi.begin(ssid,password);unsignedlongstartmillis();while(WiFi.status()!WL_CONNECTEDmillis()-starttimeoutMs){delay(500);Serial.print(.);}if(WiFi.status()WL_CONNECTED){Serial.println(\nWiFi connected, IP: WiFi.localIP().toString());returntrue;}Serial.println(\nWiFi connection failed);returnfalse;}// 通过通知特征值发送状态给AppvoidsendStatus(constchar*status,constchar*message){if(!pNotifyChar)return;StaticJsonDocument128doc;doc[status]status;doc[message]message;String jsonStr;serializeJson(doc,jsonStr);pNotifyChar-setValue(jsonStr.c_str());pNotifyChar-notify();Serial.println(Notify: jsonStr);}// BLE写回调处理App发来的WiFi信息classProvisioningCallback:publicNimBLECharacteristicCallbacks{voidonWrite(NimBLECharacteristic*pCharacteristic)override{std::string valuepCharacteristic-getValue();if(value.empty())return;Serial.println(Received: String(value.c_str()));StaticJsonDocument256doc;DeserializationError errdeserializeJson(doc,value);if(err){sendStatus(error,JSON parse failed);return;}constchar*ssiddoc[ssid];constchar*passworddoc[password];if(!ssid||!password){sendStatus(error,Missing ssid or password);return;}sendStatus(connecting,Connecting to WiFi...);if(connectWiFi(ssid,password)){// 保存到NVSprefs.begin(wifi_cfg,false);prefs.putString(ssid,ssid);prefs.putString(password,password);prefs.end();sendStatus(success,WiFi.localIP().toString().c_str());provisionedtrue;}else{sendStatus(error,WiFi connection failed, check credentials);}}};voidsetup(){Serial.begin(115200);Serial.println(ESP32 BLE Provisioning starting...);// 读取NVS中保存的WiFi信息prefs.begin(wifi_cfg,true);String savedSSIDprefs.getString(ssid,);String savedPassprefs.getString(password,);prefs.end();if(savedSSID.length()0){Serial.println(Found saved WiFi: savedSSID);if(connectWiFi(savedSSID.c_str(),savedPass.c_str(),10000)){provisionedtrue;return;// 已连接无需BLE配网}Serial.println(Saved WiFi failed, entering provisioning mode);}// 初始化BLENimBLEDevice::init(ESP32_Config);// BLE广播名NimBLEServer*pServerNimBLEDevice::createServer();NimBLEService*pServicepServer-createService(SERVICE_UUID);// 写特征值用于接收App数据NimBLECharacteristic*pWriteCharpService-createCharacteristic(CHAR_WRITE_UUID,NIMBLE_PROPERTY::WRITE|NIMBLE_PROPERTY::WRITE_NR);pWriteChar-setCallbacks(newProvisioningCallback());// 通知特征值用于回传状态pNotifyCharpService-createCharacteristic(CHAR_NOTIFY_UUID,NIMBLE_PROPERTY::NOTIFY);pService-start();// 开始广播NimBLEAdvertising*pAdvertisingNimBLEDevice::getAdvertising();pAdvertising-addServiceUUID(SERVICE_UUID);pAdvertising-setScanResponse(true);pAdvertising-start();Serial.println(BLE advertising started, waiting for app...);}voidloop(){if(provisioned){// 配网成功后执行业务逻辑例如MQTT连接、传感器读取等delay(1000);}}五、代码详细说明5.1 UUID定义SERVICE_UUID、CHAR_WRITE_UUID、CHAR_NOTIFY_UUID 三个UUID需要与手机App端保持一致。本文使用标准的128位UUID格式实际项目中可以自定义。5.2 写特征值与通知特征值· 写特征值属性为 WRITE | WRITE_NR允许App写入数据。WRITE_NR 表示无响应写入提高传输效率。· 通知特征值属性为 NOTIFYESP32通过它主动向App推送状态。App端需要订阅该特征值才能收到通知。5.3 回调函数 onWrite当App向写特征值写入数据时onWrite 被触发。代码从 pCharacteristic-getValue() 获取数据使用ArduinoJson解析。JSON格式为{ssid:YourWiFi,password:YourPassword}解析成功后提取SSID和密码调用 connectWiFi 连接路由器。连接结果通过 sendStatus 回传成功时将凭据保存到NVS。5.4 WiFi连接与超时处理connectWiFi 函数调用 WiFi.begin 后轮询 WiFi.status()超时时间可配置。默认15秒自动重连时使用10秒。超时后返回失败。5.5 NVS保存与自动重连使用 Preferences 库将SSID和密码保存到NVS分区。setup 中首先读取NVS若存在则尝试连接。连接成功则直接进入业务逻辑跳过BLE配网失败则进入配网模式。5.6 BLE初始化与广播NimBLEDevice::init(“ESP32_Config”) 设置BLE设备名。创建服务和特征值后通过 pAdvertising-addServiceUUID 将服务UUID加入广播包setScanResponse(true) 允许扫描响应最后 start() 开始广播。六、常见问题与解决方案6.1 WiFi.SSID()在冷启动时返回空字符串现象ESP32重启后调用 WiFi.SSID() 返回空字符串无法判断设备是否已配网。原因ESP32的WiFi库在未建立连接时不会保存上次连接的SSID。解决方案使用 Preferences 手动保存SSID和密码启动时从NVS读取而不是依赖 WiFi.SSID()。6.2 密码错误时无法区分具体错误现象WiFi连接失败但无法判断是密码错误、信号弱还是路由器未响应。原因WiFi协议在四次握手阶段不提供具体的错误码设备只能等待超时。解决方案设置合理的超时时间建议10-15秒超时后统一提示“连接失败请检查凭据和信号”。若需要更精确的错误信息可尝试分析 WiFi.status() 的返回值但ESP32的WiFi库支持有限。6.3 BLE广播名与WiFi SSID冲突现象手机同时扫描到两个同名设备用户无法区分。原因若ESP32同时开启了WiFi AP模式其SSID可能与BLE广播名相同。解决方案BLE广播名使用独立命名例如 ESP32_Config_XXXXXXXX为MAC地址后四位避免与WiFi SSID重复。6.4 NimBLE与Arduino自带BLE库不兼容现象同时包含 BLEDevice.h 和 NimBLEDevice.h 导致编译错误。原因两个库的类名和宏定义存在冲突。解决方案仅使用NimBLE-Arduino库移除Arduino自带的BLE库。将所有 BLEDevice 替换为 NimBLEDeviceBLECharacteristic 替换为 NimBLECharacteristic依此类推。6.5 通知特征值未订阅导致notify无效现象ESP32调用 notify() 后App端未收到任何数据。原因BLE客户端必须使能通知写入CCCD描述符才能接收通知。解决方案App端连接后需主动订阅通知特征值。ESP32端确保特征值属性包含 NOTIFY并且 pNotifyChar 已正确初始化。6.6 NVS写入频率与寿命现象频繁保存WiFi凭据可能导致NVS分区磨损。原因NVS基于Flash存储写入次数有限通常约10万次。解决方案仅在配网成功时写入一次日常运行中不重复写入。如果需要更新凭据先比较新旧值相同则不写入。6.7 广播未启动或未添加Service UUID现象手机扫描不到ESP32设备。原因未调用 startAdvertising() 或未添加Service UUID到广播包。解决方案确保在 setup 中调用 pAdvertising-addServiceUUID(SERVICE_UUID) 和 pAdvertising-start()。部分手机要求广播包中包含Service UUID才能显示设备。6.8 JSON解析内存不足现象deserializeJson 返回 NoMemory 错误。原因StaticJsonDocument 容量设置过小。解决方案根据实际JSON大小调整容量。本文接收的JSON包含ssid和password256字节足够。发送状态时使用128字节。如果增加Token等字段需相应增大。七、安全建议配网Token验证在JSON中增加Token字段ESP32验证通过后才执行配网防止未授权设备连接。配网成功后关闭BLE调用 NimBLEDevice::deinit() 释放BLE资源降低功耗并提高安全性。使用加密连接NimBLE支持BLE加密可在配对时要求输入密码或使用加密特征值。限制配网时间设置配网超时例如5分钟后自动关闭BLE广播避免长期暴露。八、总结本文实现了基于NimBLE-Arduino的ESP32 BLE配网方案包含完整的代码和常见问题分析。核心流程为广播 → 接收WiFi信息 → 连接路由器 → 回传状态 → 保存NVS。代码可直接复制使用适用于ESP32全系列开发板。后续可在此基础上扩展MQTT通信、OTA升级等功能构建完整的物联网设备框架。参考文档· Espressif官方文档BLE Provisioning· NimBLE-Arduino库GitHub仓库· ArduinoJson官方文档

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑