零知派——ESP32-S3 AI 小智 使用 Preferences NVS 实现Web配网持久化
✔零知派零知开源是一个专为电子初学者/电子兴趣爱好者设计的开源软硬件平台在硬件上提供超高性价比STM32系列开发板、物联网控制板。取消了Bootloader程序烧录让开发重心从 “配置环境” 转移到 “创意实现”极大降低了技术门槛。零知开源编程软件内置上千个覆盖多场景的示例代码支持项目源码一键下载项目文章在线浏览。零知派(零知开源)平台通过软硬件协同创新让你的创意快速转化为实物来动手试试吧目录一、为什么需要网页配网二、整体架构从 AP 启动到配置保存三、AP 热点与 DNS Captive Portal3.1 启动 AP 热点3.2 DNS Captive Portal 实现四、Web 服务器与 API 设计4.1 建立 Web 服务器4.2 配网页面 HTML4.3 WiFi 扫描接口4.4 接收凭据并异步连接4.5 状态查询接口五、配置持久化Preferences 库的使用六、主循环中的配网处理七、按键触发重新配网八、完整流程图项目概述本方案利用零知派ESP32-S3内置的NVS非易失性存储与Preferences库实现WiFi配置的持久化保存。首次通过网页配网成功后SSID和密码被自动写入Flash分区断电不丢失。设备每次重启时优先从Flash读取已保存的凭证调用WiFi.begin()自动重连。若网络环境变化导致连接失败系统自动回退到网页配网模式。项目难点及解决方案问题描述AP/STA模式的无缝切换 与 非阻塞事件处理解决方案关闭AP前确保STA已成功连接采用轮询 WiFi 状态取代while() 等待 WiFi 连接的阻塞式等待。一、为什么需要网页配网常见方案对比方案优点缺点硬编码 SSID/密码简单直接无法适应不同网络毫无通用性SmartConfig微信配网无需连接 AP兼容性差部分路由器不支持成功率低依赖微信生态蓝牙配网稳定需要蓝牙硬件App 开发成本高网页配网跨平台任何手机/电脑浏览器无需安装 App实现简单用户需手动切换 WiFi 连接设备 AP网页配网的核心流程设备上电后如果没有有效 WiFi 配置自动进入 AP 模式并启动一个 Web 服务器。用户用手机连接设备的热点在浏览器中打开配置页面通常会自动弹出选择 WiFi 并输入密码设备收到后尝试连接成功后保存配置并重启。二、整体架构从 AP 启动到配置保存配网模块涉及以下几个关键组件AP 热点设备作为接入点供用户连接。DNS Captive Portal自动劫持 DNS将任意域名解析到设备 IP实现配网页面自动弹出。Web Server提供 HTML 页面和 REST API。异步连接 状态轮询WiFi 连接不阻塞 Web 服务前端实时获取状态。PreferencesNVS持久化存储 WiFi 凭据。按键重置长按按键清除配置重新进入配网模式。三、AP 热点与 DNS Captive Portal3.1 启动 AP 热点ESP32 可以同时工作在 STA连接路由器和 AP自身作为热点模式。配网阶段我们使用APSTA模式但 AP 是核心WiFi.mode(WIFI_AP_STA); WiFi.softAP(kApSsid, kApPassword); // SSID: XiaoZhi-AI, 密码: 12345678 Serial.printf(AP IP address: %s\n, WiFi.softAPIP().toString().c_str()); // 通常是 192.168.4.1密码不能为空至少 8 位否则部分手机无法连接。设置简单易记的密码即可。3.2 DNS Captive Portal 实现Captive Portal强制门户技术能让用户连接热点后自动弹出认证/配置页面而不需要手动输入 IP。原理是启动一个 DNS 服务器将所有域名解析到设备的 AP IP。g_dnsServer new DNSServer(); g_dnsServer-start(53, *, WiFi.softAPIP()); // 监听 53 端口所有域名都解析到 AP IP然后在主循环中不断处理 DNS 请求g_dnsServer-processNextRequest();配合 Web 服务器的根路径/返回配网 HTML用户打开任意浏览器或点击弹窗就会显示配置界面。四、Web 服务器与 API 设计4.1 建立 Web 服务器使用WebServer库监听 80 端口g_webServer new WebServer(80); g_webServer-on(/, HandleRoot); // 配网主页 g_webServer-on(/connect, HTTP_POST, HandleConnect); // 提交 WiFi 凭据 g_webServer-on(/status, HandleStatus); // 查询连接状态 g_webServer-on(/scan, HandleScan); // 扫描周围 WiFi 列表 g_webServer-on(/clear, HandleClear); // 清除已保存配置 g_webServer-onNotFound(HandleRoot); // 其他路径重定向到根 g_webServer-begin();4.2 配网页面 HTML页面内容被转换为 C 字符串数组存储在webconfig_html.h中编译时烧录到 Flash。页面包含WiFi 列表下拉框通过 Ajax 调用/scan填充密码输入框连接按钮状态提示区这样无需外置文件系统所有资源内嵌。4.3 WiFi 扫描接口为了提高用户体验让用户无需手动输入 SSID实现了 WiFi 扫描功能。注意WiFi.scanNetworks()是同步阻塞的可能耗时 1~3 秒。为了避免影响 Web 响应我们做了以下设计使用g_scanning标志防止并发扫描虽然 WebServer 单线程但防止定时器或多次点击。扫描前确保 WiFi 模式为WIFI_AP_STAAP 不能关闭。返回 JSON 数组包含ssid、rssi、encrypted、encryptionType字段。关键代码片段int n WiFi.scanNetworks(); String json [; for (int i 0; i n i 20; i) { json {\ssid\:\ EscapeJsonString(WiFi.SSID(i)) \,; json \rssi\: String(WiFi.RSSI(i)) ,; json \encrypted\: String(WiFi.encryptionType(i) ! WIFI_AUTH_OPEN) }; if (i n-1) json ,; } json ]; g_webServer-send(200, application/json, json); WiFi.scanDelete(); // 释放内存其中EscapeJsonString()处理 SSID 中的双引号、反斜杠等特殊字符防止 JSON 格式错误。4.4 接收凭据并异步连接connect接口收到ssid和password后不能阻塞等待连接成功否则浏览器会超时。因此采用立即返回 后台连接 前端轮询的策略。void HandleConnect() { g_pendingSsid ssid; g_pendingPassword password; WiFi.begin(ssid.c_str(), password.c_str()); g_connecting true; g_connectSuccess false; g_connectStartTime millis(); g_webServer-send(200, application/json, {\success\: true}); }4.5 状态查询接口前端每隔 1 秒请求status根据返回值更新界面if (!g_connecting !g_connectSuccess) status idle; else if (g_connectSuccess) status connected; else if (g_connecting) { if (millis() - g_connectStartTime 20000) { // 超时 20 秒 status failed; g_connecting false; WiFi.disconnect(); } else { wl_status_t wifiStatus WiFi.status(); if (wifiStatus WL_CONNECTED) { status connected; g_connectSuccess true; g_connecting false; SaveWifiConfig(g_pendingSsid, g_pendingPassword); } else if (wifiStatus WL_CONNECT_FAILED || wifiStatus WL_NO_SSID_AVAIL) { status failed; g_connecting false; } else { status connecting; } } }当状态变为 connected 时前端可以跳转到成功页面设备稍后自动重启。五、配置持久化Preferences 库的使用ESP32 提供Preferences库用于在 NVSNon-Volatile Storage中存储键值对非常适合保存 WiFi 凭据#include Preferences.h Preferences g_preferences; const char* kNamespace wifi_config; bool SaveWifiConfig(const String ssid, const String password) { g_preferences.begin(kNamespace, false); g_preferences.putString(ssid, ssid); g_preferences.putString(password, password); g_preferences.end(); return true; } bool LoadWifiConfig(String ssid, String password) { g_preferences.begin(kNamespace, true); ssid g_preferences.getString(ssid, ); password g_preferences.getString(password, ); g_preferences.end(); return ssid.length() 0; }注意begin()的第二个参数为false表示可写为true表示只读。保存配置后即使断电也不会丢失六、主循环中的配网处理在setup()中调用ConfigureWifi()函数其逻辑尝试加载并连接保存的 WiFiConnectToSavedWifi()。如果成功直接返回进入正常应用流程。如果失败或没有保存配置调用StartWebConfig()进入配网模式。在配网模式中主循环不断调用HandleWebConfig()处理 DNS 和 Web 请求同时检查异步连接状态。一旦连接成功延迟重启下次启动就会加载新配置HandleWebConfig()的非阻塞实现void HandleWebConfig() { if (g_isWebConfigMode g_dnsServer g_webServer) { g_dnsServer-processNextRequest(); g_webServer-handleClient(); if (g_connectSuccess) { static unsigned long lastRestartTime 0; if (lastRestartTime 0) { lastRestartTime millis(); } else if (millis() - lastRestartTime 5000) { ESP.restart(); } } } }七、按键触发重新配网ESP_ERROR_CHECK(iot_button_register_cb( g_button_boot_handle, // Boot 按键句柄 BUTTON_PRESS_DOWN, // 按键事件按下 nullptr, [](void*, void* data) { // 回调函数 printf(boot button pressed\n); ClearWifiConfig(); //清除配置后重启 delay(100); ESP.restart(); }, nullptr)); iot_button_unregister_cb(g_button_boot_handle, BUTTON_PRESS_DOWN, nullptr); //连接成功后注销重新配网功能在连接WiFi成功前按下Boot按键可以强制重新配网且在连接成功后注销该功能不会跟后续的语音打断功能冲突八、完整流程图
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2548737.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!