news 2026/10/16 3:21:17

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESP32蓝牙BLE配网实战:原理、完整代码与常见问题详解

一、引言

在ESP32物联网项目中,设备需要连接WiFi才能进行数据上报、OTA升级和远程控制。传统的WiFi配置方式有两种:

  1. 硬编码SSID和密码:将WiFi凭据直接写在固件中,用户无法修改,产品化程度低。
  2. SoftAP配网:ESP32开启热点,用户连接热点后通过浏览器配置。该方式需要用户切换网络,且手机可能自动切回移动数据,导致配置页面无法打开。

蓝牙BLE配网通过蓝牙将WiFi凭据发送给ESP32,用户无需切换网络,操作流程更简洁。ESP32内置BLE 4.2,无需额外硬件即可实现。本文基于Arduino IDE和NimBLE-Arduino库,实现完整的BLE配网流程,包括接收WiFi信息、连接路由器、回传状态、保存至NVS,并在下次上电时自动重连。

二、整体通信流程

BLE配网的核心流程如下:

  1. ESP32开启BLE广播,等待手机App连接。
  2. 手机App扫描到设备并建立BLE连接。
  3. App向ESP32的写特征值(Write Characteristic)发送JSON格式的WiFi信息。
  4. ESP32解析JSON,提取SSID和密码,尝试连接路由器。
  5. ESP32通过通知特征值(Notify Characteristic)将配网结果回传给App。
  6. 连接成功后,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保存和自动重连。

#include<NimBLEDevice.h>#include<WiFi.h>#include<ArduinoJson.h>#include<Preferences.h>// 自定义Service UUID和Characteristic UUID// 需与手机App端保持一致#defineSERVICE_UUID"0000FFF0-0000-1000-8000-00805F9B34FB"#defineCHAR_WRITE_UUID"0000FFF1-0000-1000-8000-00805F9B34FB"// App写入WiFi信息#defineCHAR_NOTIFY_UUID"0000FFF2-0000-1000-8000-00805F9B34FB"// ESP32回传状态boolprovisioned=false;Preferences prefs;NimBLECharacteristic*pNotifyChar=nullptr;// WiFi连接函数,超时时间默认15秒boolconnectWiFi(constchar*ssid,constchar*password,unsignedlongtimeoutMs=15000){WiFi.begin(ssid,password);unsignedlongstart=millis();while(WiFi.status()!=WL_CONNECTED&&millis()-start<timeoutMs){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;StaticJsonDocument<128>doc;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 value=pCharacteristic->getValue();if(value.empty())return;Serial.println("Received: "+String(value.c_str()));StaticJsonDocument<256>doc;DeserializationError err=deserializeJson(doc,value);if(err){sendStatus("error","JSON parse failed");return;}constchar*ssid=doc["ssid"];constchar*password=doc["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());provisioned=true;}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 savedSSID=prefs.getString("ssid","");String savedPass=prefs.getString("password","");prefs.end();if(savedSSID.length()>0){Serial.println("Found saved WiFi: "+savedSSID);if(connectWiFi(savedSSID.c_str(),savedPass.c_str(),10000)){provisioned=true;return;// 已连接,无需BLE配网}Serial.println("Saved WiFi failed, entering provisioning mode");}// 初始化BLENimBLEDevice::init("ESP32_Config");// BLE广播名NimBLEServer*pServer=NimBLEDevice::createServer();NimBLEService*pService=pServer->createService(SERVICE_UUID);// 写特征值,用于接收App数据NimBLECharacteristic*pWriteChar=pService->createCharacteristic(CHAR_WRITE_UUID,NIMBLE_PROPERTY::WRITE|NIMBLE_PROPERTY::WRITE_NR);pWriteChar->setCallbacks(newProvisioningCallback());// 通知特征值,用于回传状态pNotifyChar=pService->createCharacteristic(CHAR_NOTIFY_UUID,NIMBLE_PROPERTY::NOTIFY);pService->start();// 开始广播NimBLEAdvertising*pAdvertising=NimBLEDevice::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 表示无响应写入,提高传输效率。
· 通知特征值:属性为 NOTIFY,ESP32通过它主动向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_XXXX(XXXX为MAC地址后四位),避免与WiFi SSID重复。

6.4 NimBLE与Arduino自带BLE库不兼容

现象:同时包含 <BLEDevice.h> 和 <NimBLEDevice.h> 导致编译错误。
原因:两个库的类名和宏定义存在冲突。
解决方案:仅使用NimBLE-Arduino库,移除Arduino自带的BLE库。将所有 BLEDevice 替换为 NimBLEDevice,BLECharacteristic 替换为 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和password,256字节足够。发送状态时使用128字节。如果增加Token等字段,需相应增大。

七、安全建议

  1. 配网Token验证:在JSON中增加Token字段,ESP32验证通过后才执行配网,防止未授权设备连接。
  2. 配网成功后关闭BLE:调用 NimBLEDevice::deinit() 释放BLE资源,降低功耗并提高安全性。
  3. 使用加密连接:NimBLE支持BLE加密,可在配对时要求输入密码或使用加密特征值。
  4. 限制配网时间:设置配网超时,例如5分钟后自动关闭BLE广播,避免长期暴露。

八、总结

本文实现了基于NimBLE-Arduino的ESP32 BLE配网方案,包含完整的代码和常见问题分析。核心流程为:广播 → 接收WiFi信息 → 连接路由器 → 回传状态 → 保存NVS。代码可直接复制使用,适用于ESP32全系列开发板。后续可在此基础上扩展MQTT通信、OTA升级等功能,构建完整的物联网设备框架。


参考文档:

· Espressif官方文档:BLE Provisioning
· NimBLE-Arduino库GitHub仓库
· ArduinoJson官方文档

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 3:21:15

Ace Data Cloud 语音转写接入指南:OpenAI 兼容接口、字幕与流式反馈

把语音变成可用数据&#xff1a;用 Ace Data Cloud 接入 OpenAI 兼容转写接口 会议录音、播客、客服语音和短视频素材&#xff0c;真正进入搜索、摘要、知识库和字幕流水线之前&#xff0c;第一步往往是转写。与其为语音识别另起一套调用方式&#xff0c;不如沿用开发者熟悉的…

作者头像 李华
网站建设 2026/10/16 3:20:51

Unity WebGL 基于 AVProVideo 播放海康监控实时画面 Demo 源码解析

简介&#xff1a;这份资源面向需要在Unity WebGL环境下接入海康监控设备实时画面的开发者&#xff0c;重点解决浏览器端无法直接播放RTSP流、AVProVideo在WebGL平台配置繁琐的问题。作者重写了Http请求模块&#xff0c;并附带无水印版AVProVideo的WebGL发布设置指导配置文件&am…

作者头像 李华
网站建设 2026/10/16 3:20:34

世界地图GeoJSON资源:前端可视化与后端解析实战指南

简介&#xff1a;这份资源整理了世界地图与主要国家的 GeoJSON 数据&#xff0c;面向从事数据可视化、Web 地图开发的前端工程师与 GIS 初学者&#xff0c;解决绘制世界或单国地图时缺少现成矢量边界数据的问题。压缩包共 25 个文件&#xff0c;以 23 个 json 为主&#xff0c;…

作者头像 李华
网站建设 2026/10/16 3:19:56

封装产线选型观察:真空共晶炉为何频频被产线工程师点名

最近跟几位封测厂的朋友聊天&#xff0c;话题绕不开真空共晶炉。有人说产线上那台老设备又开始闹脾气&#xff0c;温度曲线跑偏&#xff0c;炉膛里残留物清理不及时&#xff0c;一批功率器件焊出来空洞率就上去了。也有人在为新产线选型发愁&#xff0c;翻来覆去对比几家供应商…

作者头像 李华
网站建设 2026/10/16 3:19:18

复用 webpack 运行时生成 h5st:比手搓 AST 快 3 倍且稳 2 个量级

简介&#xff1a;本资源是一份面向中高级爬虫工程师与前端逆向学习者的实战技术方案&#xff0c;聚焦京东H5ST参数的Webpack构建环境下的完整逆向分析与代码实现。针对电商网站日益强化的前端反爬机制&#xff0c;该方案提供可直接运行的逆向逻辑复现能力&#xff0c;帮助开发者…

作者头像 李华