ARTICLE DETAIL

资讯详情

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

ESP32 NVS Web编辑器:不重刷固件,网页直接改WiFi密码

ESP32 NVS Web编辑器:不重刷固件,网页直接改WiFi密码 在 ESP32 这类小设备上改一个 WiFi 密码到底有多麻烦很多人第一反应是找到源码里那个字符串改掉然后重新编译、重新烧录。如果你的固件自己维护这也就是几分钟的事可一旦设备已经部署到现场或者固件是第三方的“黑盒”那就变成了拆机、接线、擦除 flash、整包烧录一套流程下来半天没了。我这次想聊的是另一个思路让 ESP32 把 NVS 里的键值直接暴露成一个浏览器可编辑的接口。你要改 WiFi 密码不需要碰固件只要打开网页找到wifi_password这个键改掉值重启完事。固件一行代码都不用重编。这套东西就叫“NVS Web 编辑器”本质是一个跑在 ESP32 上的 HTTP 服务加一个极简前端页面。听起来不难但真正落地时有不少细节值得记录。下面我按实际开发顺序把这套工具的设计思路、核心代码、实测过程和踩坑点完整写一遍。1. 改个 WiFi 密码为什么大多数人第一反应是“重刷固件”1.1 固件和配置数据两个完全不同的分区很多人把“烧录”理解成把整个 flash 写一遍其实 ESP32 的 flash 是分区管理的。一份典型的分区表长这样# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 0x300000,其中factory分区放的是固件本身也就是编译出来的 bin 文件nvs分区放的是运行时的配置数据。固件和配置数据根本就是两个东西。NVSNon-Volatile Storage非易失存储是 ESP-IDF 提供的一个轻量级键值存储系统作用类似电脑上的注册表。它按照键值对的方式保存数据掉电不丢失。WiFi 账号、设备编号、用户设置、校准参数这些都应该放在 NVS而不是硬编码到固件里。如果你一开始就把 WiFi 密码const char *password 12345678写在代码里那确实改密码只能重刷固件因为配置已经被“焊死”在固件里了。1.2 整包重刷的成本到底高在哪有人会说重新编译烧录一下又不难至于这么大动干戈吗那要看场景。个人开发板当然无所谓但如果你维护的设备分布在好几台现场每台设备都要拆开外壳、找烧录口、接线、跑烧录工具一遍下来成本非常高。哪怕支持 OTA整包固件更新也要承担“升级失败变砖”的风险而且每次改密码都要发一版固件这对版本管理也是巨大负担。更现实的问题是很多第三方的封闭固件根本不提供“只改配置不改程序”的入口。你拿到的是一整包 bin没有源码没有配置项想改 WiFi 只能逆向或者找厂商重新出固件。这时如果固件内部把配置放在 NVS而你又有一个能直接编辑 NVS 键值的工具问题就会简单很多。把 NVS 比作一个小型数据库固件只是读取这个数据库的“程序”。修改数据库里的记录当然不需要重装程序。2. 了解 NVS一张不会断电的“配置注册表”2.1 NVS 的数据类型和容量边界NVS 支持的数据类型不算多但覆盖日常需求足够了类型说明典型用途i8/u88 位整数小状态标志i16/u1616 位整数计数器i32/u3232 位整数开关配置、时间戳string字符串WiFi 密码、MQTT 地址blob二进制数据证书、固件升级包、复杂结构体每个键名最长 15 个字符每个命名空间名也是最长 15 个字符。这个限制非常容易踩坑如果你用一个很长的键名保存时会直接返回ESP_ERR_NVS_KEY_TOO_LONG。NVS 的存储结构是一套类似日志的机制写入时不是原地覆盖而是追加新记录所以频繁修改会导致分区碎片化。分区满了之后即使你删除了旧键也可能因为空间回收失败而写入报错。NVS 不是给“每秒写一次”这种场景设计的它适合低频、小数据量的配置持久化。2.2 WiFi 凭据到底存在哪个键里面这里要区分两种存储方式。第一种使用 ESP-IDF 自带的esp_wifi库并且调用了esp_wifi_set_storage(WIFI_STORAGE_FLASH)。这时 WiFi 配置由 WiFi 库自己管理内部会使用 NVS 中nvs分区下的特定命名空间和键。问题是这些键的存储格式是二进制的内部结构不是普通字符串。你要直接改sta_config这种内部键需要严格按照结构体字节格式去写风险很高稍不留神就把配置写坏。第二种也是我推荐的方式应用层自己管理配置。在固件启动时从 NVS 读取自定义键例如nvs_get_str(handle, wifi_ssid, ssid, len); nvs_get_str(handle, wifi_password, password, len);然后用读到的字符串去初始化 STA。这样键值内容完全是你能控制的网页工具只需要像编辑文本一样修改字符串就够了不需要关心 WiFi 库的内部结构。所以一个实用的浏览器 NVS 编辑器最常用的操作就是列出全部键读取值修改字符串重启设备。类型能覆盖i32和blob是加分项但核心价值在于“不要碰固件就能改数据”。3. 为什么我选择了“浏览器工具”这条路线3.1 串口、App、网页三条路对比要改设备上的配置常见的入口有三种串口控制台、手机 App、浏览器网页。我实际比较过最终选了浏览器原因很直接。串口方案适合开发阶段但不适合交付。现场维护人员不一定有串口线笔记本也可能没有 USB 转串口驱动更别提还要记住一堆命令。App 方案体验好但要同时维护 iOS、Android还要处理设备发现、配对、权限等一堆问题对一个小工具来说太重了。浏览器方案的好处是零安装、跨平台手机、平板、电脑只要能打开网页就行。ESP32 本身自带 Wi-Fi 和 HTTP Server 能力只要启动一个热点用户连上后访问192.168.4.1就能看到配置页面。这几乎是物联网设备做配网配置的事实标准。3.2 工具功能拆解像操作数据库一样操作 NVS我给这个工具定的功能清单很简单浏览 NVS 中所有命名空间、键名、类型。读取并展示当前值字符串直接展示整数按数字展示blob 按十六进制展示。修改已有键的值。新增键。删除键。一键重启设备让修改后的配置生效。本质上就是一套“Web 化的 NVS 管理器”。页面不用花哨重点是把键值读写这个操作做得稳定、直观。这个方案的架构也很清爽ESP32 上跑一个轻量 HTTP Server提供 REST 风格接口前端是内嵌在固件里的单页 HTML通过fetch和 JSON 交互。不需要外网不需要云端所有数据都留在本地设备上。4. 实操从零搭建一个能直改 NVS 键值的网页工具4.1 准备分区表和工程结构我基于 ESP-IDF 开发这样能直接用原生的nvs和esp_http_serverAPI。先看分区表NVS 分区默认在 menuconfig 里已经配置了但如果你的 flash 是 4MB一般默认分区表是这样的# Name, Type, SubType, Offset, Size, Flags nvs, data, nvs, 0x9000, 0x6000, phy_init, data, phy, 0xf000, 0x1000, factory, app, factory, 0x10000, 0x300000,为了保险起见我会把 NVS 分区稍微加大到0x8000甚至0x10000。因为网页工具一旦开始支持 blob 或保存备份默认 24KB 很快就不够用。分区表改法很简单在partitions.csv里把 nvs 那一行的 Size 调大即可。工程结构建议这样组织components/ nvs_web/ nvs_web.c nvs_web.h main/ main.c web_page.h partition_table/ partitions.csv sdkconfig把nvs_web做成独立组件后续其他项目也能复用。4.2 后端列出所有键值对的接口核心功能是“列出来”。ESP-IDF 提供了遍历 NVS 键的迭代器借助它可以扫描整个分区里所有命名空间的所有键。关键代码如下nvs_iterator_t it NULL; esp_err_t err nvs_entry_find(NVS_DEFAULT_PART_NAME, NULL, NVS_TYPE_ANY, it); if (err ! ESP_OK) { return; } while (it ! NULL) { nvs_entry_info_t info; nvs_entry_info(it, info); // info.namespace / info.key / info.type // 根据类型去读取当前值 nvs_handle_t handle; if (nvs_open(info.namespace, NVS_READONLY, handle) ESP_OK) { char value[512] {0}; if (info.type NVS_TYPE_STR) { size_t len sizeof(value); if (nvs_get_str(handle, info.key, value, len) ESP_OK) { // 把 info.key / info.type / value 塞进 JSON 返回 } } else if (info.type NVS_TYPE_I32) { int32_t val 0; if (nvs_get_i32(handle, info.key, val) ESP_OK) { // 塞进 JSON } } nvs_close(handle); } nvs_iterator_t next nvs_entry_next(it); it next; } nvs_release_iterator(it);注意两点第一NULL作为命名空间参数表示遍历所有命名空间第二迭代器用完后必须nvs_release_iterator(it)释放否则发生句柄泄漏。实际返回的 JSON 长这样[ { namespace: storage, key: wifi_ssid, type: string, value: MyHomeWiFi }, { namespace: storage, key: wifi_password, type: string, value: 12345678 } ]4.3 后端修改键值的关键实现修改键值比列举更简单但坑也更多。POST 接口接收 JSON包含命名空间、键名、类型和新值然后打开 NVS 句柄写入。// 假设已经从 HTTP 请求里解析出了 namespace、key、type、value nvs_handle_t handle; esp_err_t err nvs_open(namespace, NVS_READWRITE, handle); if (err ! ESP_OK) { // 打开失败通常是命名空间名过长或分区异常 } if (strcmp(type, string) 0) { err nvs_set_str(handle, key, value); } else if (strcmp(type, i32) 0) { long num strtol(value, NULL, 10); err nvs_set_i32(handle, key, (int32_t)num); } if (err ! ESP_OK) { nvs_close(handle); // 返回保存失败 } err nvs_commit(handle); nvs_close(handle);这里最容易被忽视的是nvs_commit。nvs_set_xxx只是把数据写入了内存缓存不主动调nvs_commit掉电后数据可能根本没落盘。我见过太多人改完配置后一断电就丢其实就是漏了 commit。另外删除键用nvs_erase_key(handle, key)清空命名空间用nvs_erase_all(handle)。这些接口都要求句柄以NVS_READWRITE模式打开如果你用NVS_READONLY操作会直接返回ESP_ERR_NVS_NOT_FOUND或者ESP_ERR_NVS_INVALID_HANDLE。4.4 前端一个极简的可编辑页面前端不需要框架一个 HTML 加上一点点 JavaScript 就够。页面结构大概是这样上方一个“刷新列表”按钮中间一个表格列出所有键和值每个值后面跟一个“保存”按钮底部一个“重启设备”按钮。JavaScript 核心就两个函数async function loadKeys() { const res await fetch(/api/nvs); const data await res.json(); // 渲染表格 } async function saveKey(namespace, key, type, value) { const res await fetch(/api/nvs, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ namespace: namespace, key: key, type: type, value: value }) }); if (res.ok) { alert(保存成功); } }网页文件最好直接转成 C 语言字符串数组用xxd -i web.html或者脚本生成web_page.h避免引入 SPIFFS 文件系统。对于这个工具来说文件系统反而增加了复杂度内嵌页面最省事。一个实用细节页面里所有字符串输入框要确认是 UTF-8 编码。WiFi 密码里可能出现中文、特殊字符NVS 字符串本质是二进制安全的只要前后端都用 UTF-8就没问题。如果页面编码和固件内部编码不一致保存后会出现乱码。4.5 把 WiFi 配置和工具串起来的完整链路网页工具本身只是个“编辑器”真正让它发挥作用的是固件启动时如何读取 NVS。我习惯在app_main里加一段逻辑static void wifi_init_from_nvs(void) { nvs_handle_t handle; if (nvs_open(storage, NVS_READONLY, handle) ! ESP_OK) return; char ssid[64] {0}; char password[64] {0}; size_t ssid_len sizeof(ssid); size_t password_len sizeof(password); nvs_get_str(handle, wifi_ssid, ssid, ssid_len); nvs_get_str(handle, wifi_password, password, password_len); nvs_close(handle); if (strlen(ssid) 0) { // 没有配置启动 AP 模式让用户用网页配置 } else { // 有配置按 STA 模式连接 } }这样修改 NVS 里的wifi_ssid或wifi_password重启后固件读到新值自然连到新 WiFi。这就是“只改数据、不刷固件”的完整链路。5. 实测记录用网页改 WiFi 密码完整流程5.1 场景设计从 AP 模式到 STA 模式我最常用的一种测试场景是这样设备上电后检测 NVS 里有没有有效的 WiFi 配置没有的话就打开 SoftAP热点名叫esp-config-xxxx同时启动 HTTP Server。手机连上这个热点浏览器打开192.168.4.1就能看到前面说的 NVS 编辑页面。页面里把wifi_ssid改成家里的 WiFi 名字把wifi_password改成对应的密码点保存再点重启。设备重启后wifi_init_from_nvs()读到新配置开始以 STA 模式连接路由器。整个过程中固件镜像完全没有变化变的只有 NVS 分区里的两个字符串键。5.2 改密码后的三种生效方式不同固件对“配置生效”的处理不一样选哪种要在设计时定好重启设备最简单可靠用esp_restart()让系统重新跑一遍初始化流程。我的网页工具里专门放了一个重启按钮。重新初始化 WiFi 子系统调用esp_wifi_stop()然后再次esp_wifi_start()再重新设置配置文件。不需要重启整个芯片但是状态管理要小心容易出一些奇怪的时序问题。动态修改运行中的配置调esp_wifi_set_config(WIFI_IF_STA, conf)然后esp_wifi_connect()。这种方式对已经连接的网络影响最小但如果目标 WiFi 变了需要先断开旧连接。我实测下来最稳的还是第一种重启。设备重启也就两三秒对维护场景完全可接受。5.3 如果改错了密码怎么办网页工具比串口强的一点是出错后恢复能力强。如果密码填错了STA 连不上路由器我们得留一个“后门”从错误状态里退出来。我的做法是在固件里加一个看门狗逻辑进入 STA 模式后如果 30 秒内没有成功连接并拿到 IP就回退成 AP 模式。这时候网页工具又会启动用户可以再次打开页面重新修改密码。这个机制类似于“配置失败自动进恢复模式”在实际现场维护中非常救命。static void wifi_fail_safe_timer_cb(void *arg) { if (!wifi_connected) { // 启动 AP 模式 HTTP Server ESP_LOGI(TAG, WiFi connect failed, fallback to AP mode); wifi_start_softap(); } }6. 常见问题与排查技巧实录6.1 问题速查表现象常见原因排查方向页面能打开但键列表是空的NVS 分区名不对或从未初始化检查nvs_flash_init()是否调用成功检查迭代器用的是不是nvs分区保存时提示找不到键键不在当前命名空间确认 JSON 里 namespace 和列表页显示一致NVS 键名超过 15 字符会保存失败修改 WiFi 密码后仍然连不上没有触发重连或重启检查是否执行了nvs_commit确认固件启动时重新读取了配置字符串长度比以前短时能保存变长时失败NVS 分区空间不足查看剩余空间增大 NVS 分区删除无用键保存后重启读取到乱码缓冲区长度分配不对nvs_get_str第一次先获取长度再分配 len1网页在手机上显示乱码编码问题确认 HTML 声明了meta charsetUTF-8页面和 JSON 都按 UTF-8 处理6.2 几个容易踩但不易发现的细节第一个坑是分配字符串缓冲区时漏掉 null 终止符。nvs_get_str第一次以NULL为缓冲区调用时返回的长度不包含末尾的\0。所以分配时要malloc(len 1)再读一次。否则字符串结尾可能读到上一位的残留数据表现为随机多出几个字符。第二个坑是 NVS 迭代器遍历时如果中途nvs_open打开同一个命名空间理论上没问题但如果你在遍历过程中修改了键值某些版本会触发迭代器失效警告。稳妥做法是先完成遍历收集信息再在另一个函数里做修改操作。第三个坑是 HTTP Server 默认并发数。esp_http_server默认最多同时处理几个连接如果前端页面同时发起多个 fetch 请求并且每个请求都要打开 NVS可能因为句柄占用冲突而超时。我实际把max_open_sockets调到 8基本就不再出问题了。第四个坑和 flash 加密有关。如果你的量产设备开启了 flash 加密NVS 分区也处于加密状态但这不会影响 NVS API 的读写因为加密对应用层透明。不过如果开启了 NVS 加密HMAC 方案网页工具修改键值会面临更复杂的鉴权问题需要在设计阶段就考虑进去。7. 部署到真实项目前必须注意的三件事7.1 给工具加一层简单的访问认证浏览器工具好用的同时也意味着任何能连上设备热点的人都能直接改掉所有键值。如果不做任何保护被谁乱改一下配置设备可能直接离线。我的做法是在服务器端做 HTTP Basic Auth。前端页面输入一次用户名密码后后续请求都带上Authorization: Basic xxxESP32 端在 URI handler 里解析并校验。static const char *AUTH_USER admin; static const char *AUTH_PASS esp32nvs;如果是生产环境密码建议放在 NVS 里由用户首次配置时修改而不是硬编码在固件里。这样即使固件被破解密码也不是全局一致。7.2 控制配置工具的暴露范围网页工具不能长期开着。正常工作时设备应该作为一个 STA 连接路由器这时候如果 HTTP Server 还在监听相当于给局域网内所有人开了一个配置后门。我的方案是分模式管理AP 配置模式下HTTP Server 监听所有接口正常 STA 模式下只允许在收到特定指令后短暂开启配置服务比如长按按键 5 秒进入配置模式。这样设备日常使用中不会有未授权访问面。7.3 数据备份与恢复设计NVS 是键值数据库误操作一个键的后果可能不大但如果是工业设备里面有设备地址、校准数据、证书 blob误删一个就麻烦了。我后来加了一个简单的备份功能GET /api/nvs/export把全部键值导出成 JSON 字符串POST /api/nvs/import接收 JSON逐个写到 NVS。另外还有一个“恢复出厂”按钮它不修改固件只调用nvs_flash_erase()然后重启设备相当于把配置数据库清空。这样现场维护时可以先备份再改出问题随时还原。这套浏览器 NVS 编辑器我目前已经用在了几个项目里最直观的收益就是设备送出去之后再也没因为改 WiFi 密码这种事被叫回过现场。多数情况下用户自己连热点、开网页、改密码、重启几步就解决了。最后分享一个小经验不要等产品快交付了才想起加 Web 配置页面。我从项目最开始就把 NVS 键值规范化例如所有配置统一放storage命名空间键名固定类型固定这样 Web 编辑器可以作为一个通用组件长期复用。等到设备批量部署时你才会真正体会到“只改数据、不刷固件”有多省心。
返回列表