ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ESP32免拆机改WiFi密码:浏览器直改NVS键值实战

ESP32免拆机改WiFi密码:浏览器直改NVS键值实战 1. 从一个让人抓狂的场景说起如果你玩过 ESP32大概率经历过这个场景设备已经焊好、装进壳子、挂在墙上跑了大半年突然要换 WiFi 密码。你翻出数据线拆壳找串口打开 Arduino IDE 或者 ESP-IDF改两行代码重新编译重新烧录运气不好还得按住 BOOT 键进下载模式。整个过程十分钟起步如果设备装在难拆的位置半小时都搞不定。问题的根源在于很多人把 WiFi 的 SSID 和密码直接写死在代码里编译进固件。固件是只读的改一个字符就得整体重刷。而 ESP32 其实自带一块叫NVSNon-Volatile Storage非易失性存储的区域专门用来存这种运行时可变的配置数据。把 WiFi 凭据放进 NVS固件只负责读改密码就变成了改一个键值对根本不用碰固件。更进一步如果 ESP32 上跑了一个内嵌的 Web 服务浏览器打开一个页面就能改 NVS 里的键值那连数据线都不需要了。手机连上设备的热点或者设备已经在局域网里打开浏览器输入 IP填个表单提交重启新密码生效。这就是标题里说的浏览器工具直接改 NVS 键值。这篇内容适合三类人一是刚接触 ESP32、还在把配置写死在代码里的新手二是做过几个项目、想给设备加免拆机配置能力的进阶玩家三是做小批量产品、需要给客户留一个配置入口的开发者。我会把 NVS 的原理、Web 服务的搭建、键值读写的完整代码、以及实际踩过的坑都讲清楚代码可以直接抄。2. 为什么是 NVS而不是其他方案2.1 配置存储的几种常见做法对比在决定用 NVS 之前我试过几种存配置的方式各有各的问题。先摆一张表把常见方案拉出来对比。方案掉电保存运行时修改擦写寿命适用场景代码硬编码是否不涉及一次性烧录、永不改配置EEPROM 模拟是是约 10 万次少量字节配置SPIFFS/LittleFS 文件是是约 10 万次大块数据、JSON 配置NVS是是约 10 万次键值对配置、WiFi 凭据SD 卡是是极高大数据、可插拔硬编码的问题不用多说改一次刷一次。EEPROM 在 ESP32 上其实是 Flash 模拟出来的用起来要自己算地址偏移键多了容易冲突。文件系统适合存大块 JSON但读写一个键要把整个文件读出来解析再写回去小配置用它是杀鸡用牛刀而且文件系统本身占用的 Flash 空间比 NVS 大。NVS 的设计目标就是键值对存储天生适合存 WiFi SSID、密码、设备名、阈值参数这类零散配置。它内部做了磨损均衡你不用关心写哪个扇区。API 也简单nvs_set_str、nvs_get_str几个函数就搞定。2.2 NVS 的底层结构理解了这个才不慌NVS 在 Flash 上占用一个独立分区默认大小 0x500020KB在分区表里通常长这样# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x5000,这 20KB 被划分成多个 4KB 的扇区page。每个扇区里存若干条条目entry每条 32 字节。一个键值对可能占用一条或多条 entry取决于值的长度。字符串短的话一条就够长字符串会跨多条。关键点在于NVS 写入不是原地覆盖而是写到新位置然后把旧条目标记为已删除。这就是磨损均衡的实现方式。所以频繁写同一个键会加速扇区消耗。这也是为什么我不建议把 NVS 当日志用一秒写一次几周就能写坏一个扇区。注意NVS 的键名最长 15 个字符不含结束符超过会被截断。命名空间namespace名字最长 15 个字符。这个限制很多人第一次用会踩键名写太长读的时候读不到排查半天。2.3 为什么选 Web 服务作为配置入口配置入口有好几种串口命令行、蓝牙、按键组合、Web 页面。串口要接线蓝牙要装 App按键组合能配的东西太少。Web 页面的优势是零安装——任何有浏览器的设备都能用手机、电脑、平板都行界面还能做得直观。ESP32 跑 Web 服务完全没压力。用WebServer库Arduino 环境或者esp_http_serverIDF 环境几十行代码就能起一个 HTTP 服务。配置页面就是一个简单的 HTML 表单提交后 POST 到某个路径后端解析参数写进 NVS。这里有个设计取舍配置页面是放在 Flash 里PROGMEM还是动态生成。放 Flash 里省内存但改页面要重刷固件又回到老问题。动态生成灵活但代码里拼 HTML 字符串比较丑。我的做法是页面结构固定、用PROGMEM存模板只有少量动态内容比如当前 SSID用代码填充。这样既省内存又不用为了改个样式重刷。3. 核心实现从 NVS 读写到 Web 配置页3.1 环境准备与依赖说明我用的是 Arduino 环境因为上手快库生态成熟。ESP-IDF 也能做原理一样API 换成nvs_open、nvs_set_str那套。Arduino 环境下NVS 的封装在Preferences库里比裸调 IDF 的 NVS API 舒服很多。需要的库Preferences.hArduino ESP32 核心自带不用额外装WiFi.h自带WebServer.h自带如果你用的是 PlatformIOplatformio.ini里指定 ESP32 平台即可这些库都在核心包里。Arduino IDE 的话确保 ESP32 开发板包版本在 2.0 以上Preferences库的 API 在 2.0 之后稳定了。提示Preferences库本质是对 IDF NVS API 的 C 封装底层还是 NVS。所以前面讲的键名长度限制、磨损均衡特性在这里同样适用。3.2 NVS 读写的最小可用代码先看最核心的部分——怎么把 WiFi 凭据存进去、读出来。下面这段是能直接跑的最小示例#include Preferences.h Preferences prefs; void saveWifiConfig(const String ssid, const String password) { prefs.begin(wifi, false); // 打开命名空间 wififalse 表示读写模式 prefs.putString(ssid, ssid); // 写入 SSID prefs.putString(pass, password); // 写入密码 prefs.end(); // 关闭释放句柄 } bool loadWifiConfig(String ssid, String password) { prefs.begin(wifi, true); // true 表示只读模式 ssid prefs.getString(ssid, ); // 第二个参数是默认值 password prefs.getString(pass, ); prefs.end(); return ssid.length() 0; // SSID 为空说明没配置过 }几个细节值得说清楚。prefs.begin的第一个参数是命名空间你可以理解成 NVS 里的一个文件夹不同项目的配置放不同命名空间互不干扰。第二个参数readOnly读的时候传true写的时候传false。这个参数很多人忽略但它影响底层打开 NVS 的方式只读模式不会触发擦写更安全。getString的第二个参数是默认值。当键不存在时返回这个默认值。这个设计很关键——第一次开机时 NVS 是空的getString返回空字符串你的代码就能判断还没配置过然后进入配网模式。注意prefs.begin之后一定要prefs.end。虽然 ESP32 的 NVS 句柄数量有限默认最多同时打开几个但更重要的是写完不 end数据可能还在缓存里没落盘。我遇到过写完立刻断电、重启后配置丢失的情况就是没 end 导致的。3.3 把配置页面跑起来Web 服务完整实现有了 NVS 读写接下来搭 Web 服务。整体流程是设备启动 → 读 NVS → 有配置就连接 WiFi → 起 Web 服务 → 浏览器访问配置页 → 提交表单 → 写 NVS → 重启生效。先看 HTML 页面。我用PROGMEM存一个模板里面用占位符标记要动态填充的地方const char CONFIG_PAGE[] PROGMEM Rrawliteral( !DOCTYPE html html head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title设备配置/title style body{font-family:sans-serif;max-width:400px;margin:40px auto;padding:0 20px;} input{width:100%;padding:10px;margin:8px 0;box-sizing:border-box;} button{width:100%;padding:12px;background:#2c7;color:#fff;border:none;font-size:16px;} /style /head body h2WiFi 配置/h2 form action/save methodPOST labelSSID/label input namessid value%SSID% maxlength32 label密码/label input namepass typepassword value%PASS% maxlength64 button typesubmit保存并重启/button /form /body /html )rawliteral;这个页面很朴素但够用。%SSID%和%PASS%是占位符服务端处理请求时替换成 NVS 里的当前值这样用户打开页面能看到已配置的内容改起来方便。然后是服务端的路由处理#include WebServer.h WebServer server(80); String renderPage() { String ssid, pass; loadWifiConfig(ssid, pass); String page FPSTR(CONFIG_PAGE); page.replace(%SSID%, ssid); page.replace(%PASS%, pass); return page; } void handleRoot() { server.send(200, text/html, renderPage()); } void handleSave() { if (server.hasArg(ssid) server.hasArg(pass)) { String ssid server.arg(ssid); String pass server.arg(pass); saveWifiConfig(ssid, pass); server.send(200, text/html, meta charsetUTF-8h3已保存设备重启中.../h3); delay(1000); ESP.restart(); // 重启让新配置生效 } else { server.send(400, text/plain, missing params); } } void setupWebServer() { server.on(/, handleRoot); server.on(/save, HTTP_POST, handleSave); server.begin(); }FPSTR宏把PROGMEM里的字符串读到 RAM然后replace替换占位符。这里有个性能考量每次请求都读一次 NVS 再拼页面对配置页这种低频访问完全够用不用做缓存。handleSave里保存完先返回一个提示页面delay(1000)给浏览器一点时间渲染然后ESP.restart()。为什么要重启因为 WiFi 连接是在setup里建立的运行中改 SSID 需要重新走一遍连接流程。重启是最简单可靠的做法虽然粗暴但有效。3.4 主流程串起来启动逻辑与配网兜底把上面的片段拼成完整的setupvoid setup() { Serial.begin(115200); String ssid, pass; bool configured loadWifiConfig(ssid, pass); if (configured) { WiFi.mode(WIFI_STA); WiFi.begin(ssid.c_str(), pass.c_str()); unsigned long start millis(); while (WiFi.status() ! WL_CONNECTED millis() - start 15000) { delay(500); Serial.print(.); } } if (WiFi.status() WL_CONNECTED) { Serial.println(\nConnected: WiFi.localIP().toString()); } else { // 连不上就开热点让用户来配 WiFi.mode(WIFI_AP); WiFi.softAP(ESP32-Config, 12345678); Serial.println(AP mode: WiFi.softAPIP().toString()); } setupWebServer(); } void loop() { server.handleClient(); }这段逻辑的关键是兜底。如果 NVS 里没配置或者配置的 WiFi 连不上比如密码改了、路由器换了设备自动切到 AP 模式自己发一个热点。用户连上这个热点浏览器打开192.168.4.1就能重新配置。这样设备永远不会失联——最坏情况你还能连它的热点进去改。提示AP 模式的默认密码12345678建议改掉或者做成开放热点但加个配置页面的简单校验。开放热点方便但同环境下任何人都能连进来改你的配置看你的使用场景取舍。4. 实操中踩过的坑与排查技巧4.1 NVS 相关的典型问题问题一写完配置重启后读不到。最常见的原因是prefs.end()没调用数据没落盘。另一个原因是命名空间写错了——begin(wifi)和begin(WiFi)是两个不同的命名空间大小写敏感。我见过有人读的时候用大写、写的时候用小写排查了一下午。问题二键名超长被截断。NVS 键名上限 15 字符。wifi_password是 13 字符没问题wifi_password_config就超了会被截断成前 15 个字符。写进去和读出来用的是同一个截断后的名字所以能对上但如果你在代码里用完整名字去比对就会困惑。养成习惯键名短一点ssid、pass、dev_name这种。问题三NVS 分区满了。默认 20KB存几十个键值对没问题但如果你存了很长的字符串比如证书、大段 JSON可能写满。写满后putString会返回 0失败但很多人不检查返回值。建议关键写入检查一下返回值size_t written prefs.putString(ssid, ssid); if (written 0) { Serial.println(NVS write failed!); }4.2 Web 服务相关的坑坑一表单提交后页面卡住。因为handleSave里delay(1000)然后重启浏览器在等服务端关闭连接。如果重启太快浏览器可能报连接被重置。我的做法是先server.send把响应发完再delay再重启。顺序反了就会出现页面加载不出来的情况。坑二中文乱码。HTML 里必须声明meta charsetUTF-8服务端send时 Content-Type 也要带 charsetserver.send(200, text/html; charsetutf-8, page)。少一个都可能乱码。SSID 里如果有中文还要注意 URL 编码的问题server.arg会自动解码一般不用管。坑三AP 模式下手机连不上。有些手机在 AP 模式下会提示无互联网连接然后自动切回移动数据导致打不开配置页。解决办法是在手机上手动选择保持连接或者关掉移动数据。这是手机系统的行为不是 ESP32 的问题但用户不知道就会以为设备坏了。可以在配置页面上加一句提示。4.3 常见问题速查表现象可能原因排查方向配置保存后丢失没调 end / 写后立即断电检查 end 调用写后延时再断电读不到刚写的键命名空间或键名大小写不一致统一命名打印出来比对写入返回 0NVS 分区满清理无用键或扩大分区配置页打不开AP 模式手机切回移动数据手动保持连接关移动数据页面中文乱码缺 charset 声明HTML 和响应头都加 UTF-8提交后无响应重启早于响应发送先 send 再 delay 再 restart连不上新 WiFi密码含特殊字符检查转义避免引号等4.4 几个提升体验的小技巧技巧一配置页加一个扫描周边 WiFi按钮。用WiFi.scanNetworks()扫一圈把 SSID 列成下拉框用户点选就行不用手打。这个功能在 AP 模式下也能用体验提升很大。实现上就是加一个/scan接口返回 JSON前端用 JS 填充下拉框。技巧二保存前做一次连接测试。不要直接写 NVS 然后重启而是先用新凭据尝试连接连上了再写、再重启。连不上就返回错误提示让用户重新填。这样避免了填错密码→重启→连不上→再进 AP 模式→再填的循环。代价是保存时多等几秒但值得。技巧三给配置页加个简单的访问密码。如果设备在公共网络里任何人都能访问配置页改你的 WiFi。加一个 HTTP Basic Auth或者页面上加个密码输入框校验通过才允许保存。简单但有效。技巧四OTA 和 NVS 配置配合。既然配置在 NVS 里固件升级OTA时配置不会丢。这意味着你可以放心地远程升级固件WiFi 凭据、设备参数都保留。这是 NVS 方案相比硬编码的一个隐藏优势——升级和配置解耦了。5. 这套方案还能怎么扩展把 WiFi 配置放进 NVS、用 Web 页面管理这个模式可以推广到很多其他配置项。比如设备的 MQTT 服务器地址、上报间隔、传感器校准系数、设备别名全都可以做成 NVS 键值在同一个配置页面上分组展示。页面稍微改改加几个输入框后端多几个putString/putInt就行。再进一步可以做一个通用的配置项注册表代码里定义一个结构体数组描述每个配置项的键名、类型、默认值、显示名称配置页面根据这个表自动生成表单。这样加新配置项不用改页面代码只改注册表。这个思路在做过几个项目之后会特别省事。我在实际使用中发现最容易被忽略的是配置的版本管理。当固件升级、配置项结构变了比如某个键改名了、类型从字符串变成整数旧 NVS 里的数据可能读不出来或者读错。稳妥的做法是在 NVS 里存一个config_ver键启动时检查版本不匹配就迁移或重置。这个坑我在第二个项目才意识到第一个项目升级固件后配置全乱排查了很久。最后分享一个小技巧调试 NVS 的时候可以写一个/dump接口把当前命名空间下所有键值以 JSON 形式打印出来。Preferences库没有直接列所有键的 API但你可以维护一个键名列表遍历读取。这个接口在排查到底存进去没有的时候特别好用比反复重启看串口快得多。
返回列表