尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

ESP32-S3 BLE配网实战:从GATT服务到Wi-Fi切换的完整工程指南

发布时间:2026/9/28 17:10:38

资讯中心
01
ARTICLE

ESP32-S3 BLE配网实战:从GATT服务到Wi-Fi切换的完整工程指南

ESP32-S3 BLE配网实战:从GATT服务到Wi-Fi切换的完整工程指南
1. 为什么我最终选了BLE配网而不是SmartConfig或AP配网做嵌入式开发的朋友应该都经历过给设备配网的纠结。手里这块板子没有屏幕、没有键盘、连个实体按键都金贵得不行但客户要求它必须连上家里的Wi-Fi这时候配网方案就成了绕不开的坎。最初我给ESP32-S3的方案是手机热点配网就是设备先开一个SoftAP热点手机连上这个热点之后通过HTTP页面把路由器的SSID和密码提交过去。这个方案思路直白但是踩过坑的都知道麻烦在手机上——用户得先断开自己正在用的Wi-Fi去连一个叫什么ESP32-XXXX的热点页面加载还时快时慢家里路由器如果开了AP隔离之类的东西又得折腾一通。每次调试我都要跟用户解释一遍你先连那个没密码的热点体验属实糟糕。后来我也试过ESP32原生支持的SmartConfig手机和ESP32连同一个路由器手机通过UDP广播密文设备抓包解析。这个方案的好处是用户操作几乎无感缺点是成功率受环境影响太大——路由器开了AP隔离、Wi-Fi频段不匹配、周围信道太挤都可能导致设备迟迟收不到报文。我在办公室试还好拿到用户家里一测经常卡在等待配网出不来。蓝牙配网把这两个方案的问题都绕开了。它的交互路径是手机App通过BLE GATT和ESP32-S3建立连接直接把SSID和密码写进设备的某个特征值里设备拿到这些信息之后自己去连路由器连接结果再通过BLE回传给手机。整个过程手机不需要切换网络用户只需要站在设备旁边点几下屏幕成功率比SmartConfig稳得多交互反馈也比AP配网直观得多。这篇我直接把整个工程从原理到代码拆开讲涵盖GATT服务设计、ESP32-S3侧的蓝牙协议栈配置、Wi-Fi连接的状态机管理以及实际调试中遇到的几个比较隐蔽的问题。如果你正在做IoT设备配网或者打算给手上的板子加一个配网功能这篇应该能帮你省下不少查文档的时间。2. 配网前必须先想清楚的事协议栈选型与配网流程设计2.1 蓝牙配网不是开蓝牙就连这么简单很多第一次做蓝牙配网的朋友会下意识地以为蓝牙配网就是把蓝牙当作一根数据线手机和设备连上之后把Wi-Fi密码传过去就行。这个理解方向没错但真正的工程难点在于整个配网过程涉及两条完全不同的通信链路、两种独立的协议栈以及一个需要精心设计的状态交接过程。配网的本质是一个信息搬运任务把用户手机里的Wi-Fi凭据安全、可靠地搬到设备上然后设备拿着这份凭据自己去完成网络接入。搬运工具是BLE接入动作走的是Wi-Fi协议栈。BLE负责的是控制面Wi-Fi负责的是数据面两者在配网过程中需要协调工作而不是各自为政。在设计配网流程时我建议先把整个交互时序画出来。我自己的设计参考如下设备上电初始化BLE协议栈并开启广播手机App扫描到设备发起BLE连接手机App通过GATT写操作把SSID写入指定特征值手机App通过GATT写操作把密码写入指定特征值设备收到完整的配网信息后主动断开BLE连接或者保持连接等待结果设备启动Wi-Fi Station模式尝试连接目标路由器连接成功或失败后设备把结果通过BLE回调若断开则重新广播通知手机这套流程里第5步有一个值得思考的设计决策收到配网信息后是立刻断开BLE连接还是保持连接等待Wi-Fi连接结果。两种做法各有取舍。立刻断开的好处是BLE协议栈和Wi-Fi协议栈不会在同时工作减少了可能的资源竞争坏处是如果Wi-Fi连接失败用户需要重新发起配网流程。保持连接的好处是可以实时上报连接进度但代价是BLE广播和Wi-Fi Station模式同时开启时如果引脚冲突或内存不足容易出现一些莫名其妙的问题。我个人倾向于保持连接等待结果只在确认连接成功或失败之后再决定是正常退出还是让用户重试。这样用户体验最顺而且ESP32-S3的硬件资源足够撑住两边同时工作。2.2 选BLE 4.2以上协议栈ATT MTU与传输效率的取舍关于蓝牙协议栈版本这里多说一句。ESP32-S3支持BLE 5.0但配网这种低频小数据量的场景其实用不到5.0新增的高吞吐特性。真正影响配网体验的是ATT MTUMaximum Transmission Unit的大小也就是单次ATT协议数据单元能够承载的有效数据长度。默认的ATT MTU是23字节扣除3字节的ATT操作头opcode handle实际每次最多只能传20字节的用户数据。一个SSID动辄十几个字符密码十几个字符如果按默认MTU走写SSID可能一两次就够了但密码如果达到20字节以上就得分片传。分片本身不是问题问题是分片需要双方协调如果接收方的缓冲区不够大或者处理逻辑不够健壮容易丢数据。我在工程里用的是MTU协商机制。ESP32-S3的蓝牙协议栈基于Bluedroid或NimBLE默认支持MTU协商手机端发起连接后双方可以协商一个更大的MTU值。ESP32-S3侧配置为接受至少247字节的MTU这样整个配网信息最多两次写操作就传完了基本不会出现分片相关的问题。2.3 配网数据格式一包一字段还是JSON一把梭配网数据如果只有SSID和密码字段设计怎么搞都行。但考虑到后续可能扩展配网字段比如Wi-Fi的BSSID过滤、是否使用静态IP、设备名称等我建议在一开始就设计一个稍微有点扩展性的数据格式。一种做法是每个字段一个特征值比如SSID一个、密码一个、扩展字段再各占一个。这种设计简单直观但特征值数量多了之后手机端的操作逻辑会变复杂而且你没法保证每个特征值都能进行一次完整的MTU写入。另一种做法是定义一个JSON字符串序列化之后通过一个特征值传过来。JSON的好处是格式灵活、可读性强、扩展方便。比如{ ssid: MyHomeWiFi, password: password123, bssid: AA:BB:CC:DD:EE:FF }手机端把这个字符串以一次或几次写操作发到特征值设备端解析JSON然后取出字段。坏处也很明显JSON解析需要额外的代码和内存开销而且如果数据长度超过MTU分片与重组这部分逻辑必须自己处理好。我这个工程用的是折中方案核心字段用固定偏移的二进制帧格式每个字段定长像这样typedef struct { char ssid[33]; char password[65]; uint8_t flags; } wifi_config_frame_t;定长结构的好处是解析零成本而且天然规避了拼包和分包问题。缺点是灵活性差如果后续要加字段得改结构体并升级手机端的封包逻辑。但对于一个只做配网的场景我认为定长结构的确定性价值大于JSON的灵活性价值。你要做产品化的话按需选择即可。3. 硬件环境与工程初始化ESP-IDF版本、开发板选型和第一个BLE工程3.1 开发板选型别只看主控芯片板载天线和晶振更重要ESP32-S3这颗芯片本身没什么好说的双核240MHz、512KB SRAM、2.4GHz Wi-Fi BLE 5.0做配网功能绰绰有余。但开发板的差异可能会直接影响蓝牙信号质量进而影响配网成功率这里有个容易被忽视的细节。我用过好几款ESP32-S3开发板有的板子蓝牙广播距离能到15米以上有的板子在5米外就断断续续。区别主要在两个地方板载PCB天线的布局以及晶振的精度。天线好理解PCB天线周围如果有大面积的铺铜或者其他金属器件遮挡信号会明显变差。晶振的影响很多人没注意到——BLE对时钟精度有要求晶振频率偏差大会导致蓝牙的跳频同步出问题配网时表现为手机能偶尔扫到设备但连不上或者连上了但通信不稳定。所以选开发板的时候尽量选天线区域干净、晶振有明确规格说明的板子。市面上主流的ESP32-S3-DevKitC和合宙ESP32-S3开发板我都试过整体信号表现稳定适合做配网功能验证。3.2 ESP-IDF环境快速搭建从零到能编译BLE工程ESP32-S3的开发我建议直接用乐鑫官方的ESP-IDF不要用Arduino。不是说Arduino不行Arduino的BLE库封装得确实好用但配网这种涉及协议栈细节的场景ESP-IDF能让你看到更多底层行为排查问题的时候不至于两眼一抹黑。我用的是ESP-IDF v5.1版本。安装过程不赘述官方文档有很详细的步骤这里只提几个关键点安装路径不要有中文和空格否则后面编译会有一堆莫名其妙的路径问题用乐鑫的vscode扩展ESP-IDF Extension创建工程最省事它会帮你管理工具链和依赖创建工程时模板选择BLE GATT Server作为起点可以省去不少协议栈初始化的样板代码工程创建好之后第一步先把默认的BLE GATT Server例程烧进板子用手机nRF Connect搜一下能看到ESP32-S3的广播名并成功连上说明环境和协议栈基本没问题再往下写配网逻辑。3.3 蓝牙协议栈配置Bluedroid还是NimBLEESP-IDF里BLE协议栈有两种选择Bluedroid和NimBLE。Bluedroid是Android移植过来的全套协议栈功能最全但内存占用也比较大配置复杂。NimBLE是专为嵌入式设计的轻量级协议栈资源占用少API风格更现代而且对GATT的操作方式更加灵活。配网这个场景不需要什么高级蓝牙特性NimBLE完全够用而且NimBLE在ESP32-S3上对Wi-Fi共存的处理比Bluedroid更好一些启动速度也快。所以我这个工程用的是NimBLE。在ESP-IDF的menuconfig里这样配置Component config → Bluetooth → Bluetooth controller → 开启 Component config → Bluetooth → Host → NimBLE → 开启注意Bluedroid和NimBLE只能二选一开了NimBLE就把Bluedroid关掉。另外我把NimBLE的日志级别调到Warning这样能减少串口输出噪音调试的时候看正经日志不费眼。4. 核心代码实现从GATT服务表到Wi-Fi接管一整套可运行的配网逻辑4.1 GATT服务与特征值的设计用最小的表干最多的事BLE配网的核心是一个GATT Server这里面最重要的是服务表和特征值回调函数的编写。我先直接给出服务表定义再逐个解释每一行的设计意图。// BLE服务UUID自定义的配网服务 #define SERVICE_UUID 0x00FF // SSID特征值支持写入 #define CHAR_UUID_SSID 0xFF01 // 密码特征值支持写入 #define CHAR_UUID_PASSWORD 0xFF02 // 配网状态特征值只读/通知设备主动上报状态 #define CHAR_UUID_STATUS 0xFF03这里有个设计选择需要注意我刻意没有做一个写命令打包完所有配网信息的特征值而是把SSID和密码分开。原因有两个一是SSID和密码的长度差异大分开写可以独立控制长度校验二是很多手机App的交互逻辑本来就是先让用户选Wi-Fi再输密码两步操作各自对应一次写逻辑清晰。特征值回调函数的核心逻辑如下static int gatt_svr_chr_write(struct os_mbuf *om, uint16_t min_len, uint16_t max_len, void *dst, uint16_t *out_len) { uint16_t om_len OS_MBUF_PKTLEN(om); if (om_len min_len || om_len max_len) { return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; } *out_len om_len; return ble_hs_mbuf_to_flat(om, dst, max_len, out_len); } static int gatt_svr_chr_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { switch (ctxt-op) { case BLE_GATT_ACCESS_OP_WRITE_CHR: if (ctxt-chr-uuid BLE_UUID16_DECLARE(CHAR_UUID_SSID)) { gatt_svr_chr_write(ctxt-om, 1, sizeof(ssid_buf), ssid_buf, out_len); ssid_len out_len; ESP_LOGI(TAG, 收到SSID: %.*s, ssid_len, ssid_buf); } else if (ctxt-chr-uuid BLE_UUID16_DECLARE(CHAR_UUID_PASSWORD)) { gatt_svr_chr_write(ctxt-om, 1, sizeof(pass_buf), pass_buf, out_len); pass_len out_len; ESP_LOGI(TAG, 收到密码: %.*s, pass_len, pass_buf); // 密码写完后自动触发Wi-Fi连接 start_wifi_connect_task(); } break; case BLE_GATT_ACCESS_OP_READ_CHR: if (ctxt-chr-uuid BLE_UUID16_DECLARE(CHAR_UUID_STATUS)) { // 返回当前配网状态 if (wifi_connected) { ctxt-val connected; } else { ctxt-val idle; } } break; default: break; } return 0; }这个回调函数有几个细节值得说明。第一写SSID和写密码是两个独立操作设备端不需要关心接收顺序——手机App是先写SSID再写密码就算顺序反过来程序也不会有问题除非你主动判断了顺序。第二我在收到密码之后立刻启动Wi-Fi连接任务这个密码写完即触发连接的方式对手机端最简单不用单独发送一个开始配网的命令。第三状态特征值支持手机主动读取这为一些特殊情况提供了便利比如手机意外退出配网页面重新打开时可以先读一下状态避免重复配网。4.2 广播报文设计让手机在一堆设备里准确找到你的设备BLE广播是配网流程的门面手机App扫描周边设备时靠的就是广播报文里的信息来决定要不要显示可配网设备。NimBLE里启动广播的代码结构大致如下static void start_ble_advertising(void) { struct ble_gap_adv_params adv_params; struct ble_hs_adv_fields adv_fields; memset(adv_fields, 0, sizeof(adv_fields)); // 设置广播标志仅支持BR/EDR不支持只用LE adv_fields.flags BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP; // 设置完整设备名 adv_fields.name (uint8_t *)DEVICE_NAME; adv_fields.name_len strlen(DEVICE_NAME); adv_fields.name_is_complete 1; // 设置厂商自定义字段标记这是配网设备 adv_fields.mfg_data mfg_data; adv_fields.mfg_data_len sizeof(mfg_data); ble_gap_adv_set_fields(adv_fields); memset(adv_params, 0, sizeof(adv_params)); adv_params.conn_mode BLE_GAP_CONN_MODE_UND; // 可连接 adv_params.disc_mode BLE_GAP_DISC_MODE_GEN; // 可发现 ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER, adv_params, ble_gap_event_handler, NULL); }这里有个实操经验想分享广播报文里除了设备名我建议加一个厂商自定义字段比如用一个固定的tag表示这是一个配网设备。这样做的好处是手机App扫描时可以凭借这个tag精确过滤而不是靠匹配设备名前缀这种笨办法。你想啊如果用户家里有5个ESP32设备都在广播其中3个是传感器、2个是配网模式的开关App如果不看厂商字段只能把5个全显示出来让用户去猜哪个是哪个。有了厂商markerApp可以在扫描层直接过滤。广播间隔我也说一句默认的100ms~200ms就够用不必调得太密。广播间隔短会增加功耗而配网场景里用户本来就要花几秒钟去打开App、点开始配网所以100ms的间隔完全来得及被发现。4.3 从BLE到Wi-Fi状态机设计与后台任务切换的边界配网逻辑作为一个状态机来写是我在这个工程里比较坚持的一点。状态机的好处是让代码的可读性和可维护性大幅提升尤其是当配网过程出现异常时通过状态值能快速定位到当前设备到底卡在哪个环节。我用的是简单的枚举状态typedef enum { WIFI_IDLE 0, WIFI_CONNECTING, WIFI_CONNECTED, WIFI_CONNECT_FAILED, } wifi_config_state_t;配网主流程对应的状态迁移是这样的设备上电处于WIFI_IDLEBLE广播开启手机写入SSID和密码后start_wifi_connect_task()被调用状态从WIFI_IDLE切到WIFI_CONNECTINGWi-Fi连接结果通过事件回调上报成功则切到WIFI_CONNECTED失败则切到WIFI_CONNECT_FAILED失败时设备重新回到BLE广播状态等待手机再次配网Wi-Fi连接任务的核心代码static void start_wifi_connect_task(void) { wifi_connected false; current_state WIFI_CONNECTING; wifi_config_t wifi_cfg {0}; memcpy(wifi_cfg.sta.ssid, ssid_buf, ssid_len); memcpy(wifi_cfg.sta.password, pass_buf, pass_len); vTaskDelay(pdMS_TO_TICKS(200)); // 给BLE写入操作一点收尾时间 esp_wifi_set_config(WIFI_IF_STA, wifi_cfg); esp_wifi_connect(); }注意我在这里加了一个vTaskDelay(200)。这个delay的来源是我踩过的坑在BLE的GATT回调函数里写操作的事件返回后协议栈可能还有一些内部状态没用完。如果立刻调用esp_wifi_set_config偶尔会出现Wi-Fi配置丢失或者连接不稳定的情况。加200ms的delay说白了是给NimBLE一个喘口气的时间实测下来这个值足够稳定又不至于让用户感觉到明显卡顿。Wi-Fi连接结果如何通知到BLE呢ESP-IDF的Wi-Fi事件机制提供了WIFI_EVENT_STA_DISCONNECTED和IP_EVENT_STA_GOT_IP两个关键事件。我在事件处理函数里更新状态机的状态并通过BLE通知把结果推给手机static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { if (event_base WIFI_EVENT event_id WIFI_EVENT_STA_START) { esp_wifi_connect(); } else if (event_base WIFI_EVENT event_id WIFI_EVENT_STA_DISCONNECTED) { current_state WIFI_CONNECT_FAILED; wifi_connected false; // 通知手机端配网失败 notify_phone_with_state(WIFI_CONNECT_FAILED); // 重要重新开启BLE广播允许用户再次配网 start_ble_advertising(); } else if (event_base IP_EVENT event_id IP_EVENT_STA_GOT_IP) { current_state WIFI_CONNECTED; wifi_connected true; // 通知手机端配网成功 notify_phone_with_state(WIFI_CONNECTED); // 配网成功后关闭BLE省电并避免冲突 ble_gap_adv_stop(); ble_gap_terminate(BLE_CONN_HANDLE_INVALID, BLE_ERR_REM_USER_TERM); } }这里有一个细节要特别强调Wi-Fi连接失败后设备一定要重新打开BLE广播。因为此时手机和设备的BLE连接很可能还保持着用户需要在App上看到配网失败的提示然后决定是重试还是检查密码。如果不重新广播手机App很难再重新连接上设备这就把用户逼到了只能重启设备的死胡同里。反过来配网成功之后设备可以主动关闭BLE广播并断开连接。因为此时设备的使命已经完成了继续开着蓝牙除了增加功耗没有任何意义。不过要注意断开BLE的时候不应该影响Wi-Fi连接本身这两个协议栈在ESP32-S3上是共存关系断开蓝牙不会波及Wi-Fi。4.4 手机端配合用nRF Connect手测到写一个最小可用的配网App在写正式的手机App之前我强烈建议先用nRF Connect这款工具把手动配网流程跑通。这个工具可以手动扫描、连接、读写GATT特征值是调试BLE外设的利器。用手动工具验证的步骤打开nRF Connect扫描到ESP32-S3设备点击连接查看服务列表找到UUID为00FF的服务点击FF01特征值写入SSID字符串点击FF02特征值写入密码字符串观察ESP32-S3的串口日志看到收到SSID和收到密码之后等待Wi-Fi连接结果读取FF03特征值确认当前配网状态这五步跑通说明设备的BLE侧逻辑基本没问题剩下的就是手机App层的封装了。如果你用的是Flutter或者Android原生开发手机AppBLE操作本身并不复杂扫描使用系统BLE扫描接口过滤设备名或厂商字段连接建立GATT连接发现服务写特征值通过BluetoothGatt.writeCharacteristic()发送SSID和密码有一点建议手机App的配网页面上SSID建议做成下拉列表直接列出手机当前扫描到的Wi-Fi网络用户不用手输。密码输入框用明文显示加显示切换按钮因为配网输错密码是最常见的失败原因让用户看到自己输入的内容能够减少很多不必要的重试。5. 实测中遇到的两个隐蔽问题直连失败和Wi-Fi回连后的BLE假死5.1 问题一手机连不上设备的不可感知原因在调试过程中手机端nRF Connect偶尔会提示连接超时但设备端广播看起来一切正常。这个问题让我排查了很久最后发现是手机和ESP32-S3的BLE地址类型不匹配导致的。具体来说ESP32-S3默认的BLE地址类型是Public Address公共地址但很多手机的BLE扫描会优先尝试连接使用随机地址Random Address的设备。如果设备的地址类型配置和手机期望的不一致手机虽然能看到设备但连接过程中会卡在正在连接状态最终超时。解决方式有两种。一种是在初始化时把BLE地址类型改为随机地址ble_hs_id_infer_auto(0, own_addr_type); if (own_addr_type ! BLE_OWN_ADDR_PUBLIC) { own_addr_type BLE_OWN_ADDR_RANDOM; }另一种是确保在广播参数里正确指定地址类型并和手机端的连接参数保持一致。我最后使用了第一种方案把设备地址类型统一设置为随机地址之后手机连接的成功率提高了不少。5.2 问题二配网成功后第一次回连Wi-FiBLE回调却没响应这个问题比较诡异配网流程一切正常Wi-Fi也连上了手机收到了配网成功的通知但此时如果用户重新打开App去连接设备发现设备根本不在广播列表里或者虽然能扫描到设备但连接之后写特征值设备完全没反应。排查到最后发现问题出在Wi-Fi连接成功后的BLE资源释放逻辑上。我在代码里调用了ble_gap_adv_stop()和ble_gap_terminate()这两个API的目的是停止广播并断开连接。但是在NimBLE的实现中如果直接在主任务上下文里调用这两个API可能会和协议栈本身的事件循环产生竞争条件。直观表现就是Wi-Fi连接成功的瞬间协议栈内部状态被破坏了蓝牙进入了半死不活的假死状态。我最终的解决方案是不在Wi-Fi事件回调里直接操作BLE而是把关闭BLE这个动作丢到延时任务里让NimBLE线程先把当前事件处理完再执行关闭操作。static void ble_cleanup_task(void *arg) { vTaskDelay(pdMS_TO_TICKS(500)); ble_gap_adv_stop(); ble_gap_terminate(BLE_CONN_HANDLE_INVALID, BLE_ERR_REM_USER_TERM); vTaskDelete(NULL); } // 在Wi-Fi连接成功事件中 xTaskCreate(ble_cleanup_task, ble_cleanup, 2048, NULL, 5, NULL);加了500ms的延时之后这个问题再没有出现过。如果你用Bluedroid协议栈这个问题相对少见但NimBLE因为是高度异步的架构这种在别人的事件回调里干别人的活的时序冲突更容易触发。5.3 一个被忽略的软硬件协同问题天线位置与配网距离最后说一个偏工程实践的观察。蓝牙配网对距离和遮挡非常敏感这不是代码能完全弥补的。我测试过把ESP32-S3放在金属外壳的机箱里BLE广播距离直接折半放在塑料外壳里则基本不受影响。如果你做的是产品而不是开发板验证建议在主板上预留一个IPEX天线接口把天线引到外壳外面或者外壳的塑料区域。配网场景里用户通常站在设备旁边操作所以3到5米的稳定通信距离就够用了但如果天线被金属完全包裹可能连这个距离都达不到。另外ESP32-S3的Wi-Fi和BLE共用同一个2.4GHz射频前端两者同时工作会互相影响吞吐量。配网场景不涉及大数据量传输问题不大但如果你在做一些需要蓝牙持续传输数据的场景需要考虑分时复用或使用Coexistence共存机制的参数调整。6. 工程健壮性如果用户输入了错误密码、路由器换了密码、设备换了网络代码跑通只是第一步配网功能真正考验的是异常情况下的表现。做产品的话有几个场景一定要覆盖。6.1 密码错误时的重试流程用户输错密码是最高频的异常场景。设备收到错误密码后会收到WIFI_EVENT_STA_DISCONNECTED事件状态机切到WIFI_CONNECT_FAILED此时BLE广播必须重新打开让手机App能够再次连接设备并尝试新的密码。这里有个产品逻辑需要定义清楚设备进入配网失败状态后是保持BLE广播一段时间还是无限期广播如果是电池供电的设备无限期广播会持续耗电最后把电池耗干。我的建议是配网失败后广播5到10分钟超时后进入休眠。用户重新操作时必须重启设备才能再次进入配网模式。6.2 设备已经配过网二次配网的覆盖逻辑很多IoT设备出厂时已经预置了Wi-Fi配置用户搬家或者换路由器后需要重新配网。这就要求设备必须支持覆盖配网——第二次配网信息写入时新配置要能覆盖旧配置而不是报错。在代码层面这只需要确保esp_wifi_set_config函数在调用时不会因为之前已经设置过配置而失败。实际上这个函数是幂等的直接覆盖即可。但要注意覆盖后设备需要主动断开旧连接并重新连接新网络这个逻辑可以用esp_wifi_disconnect()再esp_wifi_connect()实现。6.3 配网超时和自动重试还有一种情况用户写入SSID和密码后Wi-Fi迟迟连不上可能是路由器太远、信号太弱也可能是路由器设置了MAC地址过滤。设备如果一味等待Wi-Fi事件用户体验会很差。我建议在代码里加一个配网超时机制从发起Wi-Fi连接开始计时30秒内没有收到IP_EVENT_STA_GOT_IP事件就自动切换为WIFI_CONNECT_FAILED状态并重新进入BLE广播等待用户重新配网。// 在start_wifi_connect_task中注册一个一次性定时器 esp_timer_create(wifi_timeout_timer_args, wifi_timeout_timer); esp_timer_start_once(wifi_timeout_timer, 30 * 1000 * 1000); // 30秒 // 定时器回调函数 static void wifi_timeout_timer_cb(void *arg) { if (current_state WIFI_CONNECTING) { current_state WIFI_CONNECT_FAILED; notify_phone_with_state(WIFI_CONNECT_FAILED); start_ble_advertising(); } }这个30秒的取值基于经验正常家庭路由器的连接和DHCP获取通常在10秒内完成30秒的宽限足够覆盖大部分慢速路由器的场景同时也不会让用户等得太久。7. 实测参数与后续优化方向最后把工程里比较重要的参数实测结果列一个表方便你对照自己的环境做参考。参数项测试条件实测结果说明BLE广播距离空旷室内手机贴近约15米稳定扫描20米以上开始丢包天线位置和方向影响明显BLE连接成功率30次重复配网29次成功1次超时重试超时原因与手机系统BLE调度有关从设备上电到可配网时间冷启动约1.2秒NimBLE启动较快密码写入到Wi-Fi连接成功家用路由器信号良好约2.5秒含DHCP获取IP时间配网失败重新可配网时间密码错误场景约300ms重新开启广播并等待用户连接配网期间功耗BLE广播Wi-Fi未连接平均约80mA3.3V完成配网后Wi-Fi数据面功耗另算从参数里能看出这个方案的配网体验已经可以做到从打开App到设备上线大约5到10秒比AP配网动辄30秒起步的体验好了一个量级。后续有几个方向可以做优化配网加密目前的方案是明文传SSID和密码在家庭内网场景问题不大但在公共环境或严格安全要求的场景建议增加AES加密。可以用ESP32-S3的硬件AES引擎密钥通过设备外壳上的二维码或PIN码预共享这样即使空中抓包也不容易拿到明文密码。批量配网如果要做多设备场景比如一套智能家居系统有10个灯泡可以考虑把配网信息存储在手机端一台一台地连过去并自动写入省去每台都要手工操作的麻烦。配网后诊断设备接入Wi-Fi后可以周期性地把信号强度RSSI、连接到的AP的BSSID、IP地址等信息通过BLE或MQTT上报给手机App。用户说设备离线了时这些数据能帮你快速判断是设备掉线、路由器重启还是Wi-Fi信号太弱。配网是整个IoT设备使用流程的入口这个入口顺滑不顺滑直接决定了用户对产品的第一印象。BLE配网在这条路上算是目前体验和成功率平衡得最好的方案之一ESP32-S3的原生协议栈让实现成本也不算高。照着上面的流程做一遍再根据自己的产品场景调整细节配网这块基本就能收工了。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。