# ESP32 配网组件设计实践:聚焦功能与实现,而不是项目绑定 很多 ESP32 设备在开发阶段都把“配网”当成一个小功能,但真正落地后会发现: - 用户第一次接入要顺畅 - 失败后要能恢复 - 日志要便于现场排障 这篇文章只讲配网组件本身,聚焦能力设计和实现思路,不依赖具体业务项目。 --- ## 一、目标能力定义 一个可落地的配网组件,建议至少包含以下能力: - 按键触发配网 - 常驻配网模式(可选) - SoftAP + Web Portal 配网 - DNS 劫持与常见 Captive Portal 兼容 - 凭据持久化(NVS)与重启自动重连 - 清除历史配置(API + Web) - 状态机与可读日志 这几项组合起来,才能覆盖“首次成功 + 失败恢复 + 现场维护”三个关键场景。 图示(整体功能目标关联): ```mermaid flowchart TD A[按键任务/业务触发] --> B[进入 provisioning] B --> C[启动 SoftAP] B --> D[启动 HTTP Server] B --> E[启动 DNS Hijack] D --> F[/api/scan] D --> G[/api/connect] D --> H[/api/status] D --> I[/api/clear] G --> J[设置 STA 参数] J --> K[发起 esp_wifi_connect] K --> L{Wi-Fi/IP 事件} L -->|成功| M[状态=connected] L -->|失败| N[状态=failed] I --> O[清除 NVS 凭据] O --> P[清空运行态缓存] P --> B ``` --- ## 二、组件架构(通用) ```text [按键任务] --> [进入配网] | +--> SoftAP + HTTP Server + DNS Hijack [Web] -- /api/scan --> Wi-Fi 扫描 [Web] -- /api/connect --> 设置 STA 并发起连接 [Web] -- /api/status --> 轮询状态 [Web] -- /api/clear --> 清除已保存配置 [Wi-Fi/IP 事件] --> 更新状态机 + 打印日志 + 保存凭据 ``` 推荐状态机: - `idle` - `provisioning` - `connecting` - `connected` - `failed` - `timeout` 图示(状态机): ```mermaid stateDiagram-v2 [*] --> idle idle --> provisioning: wifi_connect_start() provisioning --> connecting: POST /api/connect connecting --> connected: GOT_IP connecting --> failed: AUTH_FAIL / NO_AP / ... connecting --> timeout: connect timeout failed --> provisioning: POST /api/clear timeout --> provisioning: POST /api/clear connected --> provisioning: 常驻配网继续开放入口(可选) connected --> idle: stop provisioning(按策略) ``` 上图展示了组件的主要数据流与恢复路径。 --- ## 三、对外 API 设计建议 推荐保持“少而稳”的接口: ```c esp_err_t wifi_connect_init(void); esp_err_t wifi_connect_start(void); esp_err_t wifi_connect_stop(void); wifi_connect_status_t wifi_connect_get_status(void); esp_err_t wifi_connect_get_config(wifi_connect_config_t *config); esp_err_t wifi_connect_clear_config(void); ``` 设计原则: - `init` 只做初始化和基础恢复 - `start/stop` 控制配网生命周期 - `get_status` 作为 UI/接口层统一读取入口 - `clear_config` 提供失败恢复通道 可以展示一段“典型调用顺序”代码: ```c ESP_ERROR_CHECK(wifi_connect_init()); // 按键触发或业务触发时 ESP_ERROR_CHECK(wifi_connect_start()); // UI 侧轮询状态 wifi_connect_status_t st = wifi_connect_get_status(); // 需要恢复出厂配网时 ESP_ERROR_CHECK(wifi_connect_clear_config()); ``` 图示(API 生命周期时序): ```mermaid sequenceDiagram participant App as 应用层 participant WC as wifi-connect participant WiFi as esp_wifi participant Web as 配网页面 App->>WC: wifi_connect_init() WC-->>App: ESP_OK App->>WC: wifi_connect_start() WC->>WiFi: 开启 APSTA / 事件注册 WC-->>App: ESP_OK Web->>WC: POST /api/connect(ssid,pass) WC->>WiFi: esp_wifi_disconnect() WC->>WiFi: esp_wifi_set_config() WC->>WiFi: esp_wifi_connect() WiFi-->>WC: WIFI_EVENT / IP_EVENT alt 获取到 IP WC-->>Web: status=connected else 连接失败或超时 WC-->>Web: status=failed|timeout end Web->>WC: POST /api/clear WC-->>Web: status=provisioning ``` 上图对应完整 API 生命周期(init -> start -> connecting -> connected/failed -> clear)。 --- ## 四、关键实现点 ### 1) Web 配网页面保持轻量 不一定要引入前端框架。对于资源受限设备,内嵌简洁 HTML/JS 往往更稳定。 建议页面只保留核心动作: - 扫描网络 - 提交连接 - 查看状态 - 清除配置 ### 2) Captive Portal 兼容是体验关键 仅提供首页 URL 通常不够。建议额外处理: - 常见探测路径(如 `generate_204` 等) - 未知路径统一 302 到配网页 这样手机系统弹门户页面成功率会明显提高。 示例代码(伪代码): ```c static esp_err_t captive_redirect_handler(httpd_req_t *req) { httpd_resp_set_status(req, "302 Found"); httpd_resp_set_hdr(req, "Location", "http://192.168.4.1/"); return httpd_resp_send(req, NULL, 0); } // 注册常见探测路径 httpd_register_uri_handler(server, &uri_generate_204); httpd_register_uri_handler(server, &uri_hotspot_detect); httpd_register_uri_handler(server, &uri_ncsi); ``` 可直接使用以下流程图(对应 Captive Portal 重定向路径): ```mermaid flowchart LR A[手机连接设备 AP] --> B[访问任意域名] B --> C[DNS Hijack 返回 192.168.4.1] C --> D[HTTP 探测路径请求
/generate_204 等] D --> E[302 Location: http://192.168.4.1/] E --> F[打开配网页面] F --> G[扫描 / 连接 / 清除配置] ``` ### 3) 连接前主动断开旧连接 这是一个高频坑:设备已有 STA 连接时,直接连接新 AP 可能导致超时或异常状态。 建议在 `esp_wifi_set_config + esp_wifi_connect` 前先执行 `esp_wifi_disconnect()`,并对异常返回做日志记录。 示例代码: ```c esp_err_t err = esp_wifi_disconnect(); if (err != ESP_OK && err != ESP_ERR_WIFI_NOT_CONNECT) { ESP_LOGW(TAG, "disconnect before reconnect failed: %s", esp_err_to_name(err)); } ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &cfg)); ESP_ERROR_CHECK(esp_wifi_connect()); ``` ### 4) “清除配置”必须打通全链路 建议同时提供: - SDK API:`wifi_connect_clear_config()` - HTTP API:`POST /api/clear` - 页面按钮:`清除已保存` 这样现场人员无需改固件即可恢复设备。 建议把清除动作写成“存储层 + 运行态”两段: ```c esp_err_t wifi_connect_clear_config(void) { // 1) 清 NVS 凭据 ESP_RETURN_ON_ERROR(nvs_erase_key(nvs, "ssid"), TAG, "erase ssid failed"); ESP_RETURN_ON_ERROR(nvs_erase_key(nvs, "pass"), TAG, "erase pass failed"); ESP_RETURN_ON_ERROR(nvs_commit(nvs), TAG, "nvs commit failed"); // 2) 清内存态并回到 provisioning memset(&s_ctx.pending_cfg, 0, sizeof(s_ctx.pending_cfg)); s_ctx.status = WIFI_CONNECT_STATUS_PROVISIONING; return ESP_OK; } ``` 可直接使用以下流程图(对应 clear 后状态回到 provisioning): ```mermaid flowchart TD A[开始清除配置] --> B[擦除 NVS:ssid] B --> C[擦除 NVS:pass] C --> D[nvs_commit] D --> E[清空 pending 配置缓存] E --> F[清空错误原因/中间状态] F --> G[状态切回 provisioning] G --> H[前端轮询显示 可重新配网] ``` --- ## 五、日志策略(非常重要) 建议日志遵循“状态 + 原因”格式,例如: - `【状态】配网已启动:热点已开启,SSID=...` - `【状态】开始连接路由器:目标网络=...` - `【状态】联网成功:获取 IP=...` - `【状态】连接失败:原因=...` 这样做的收益是: - 开发调试快 - 测试可直接定位阶段 - 现场人员无需先理解底层驱动日志 可展示一个统一日志函数风格: ```c static void log_state_i(const char *title, const char *detail) { ESP_LOGI(TAG, "【状态】%s:%s", title, detail ? detail : "-"); } ``` 如需补充非 Mermaid 图,建议仅放一张关键串口日志截图(启动配网、连接中、成功/失败重试)。 --- ## 六、常见问题与排障思路 ### 问题 1:手机连上 AP 但页面不弹 排查: - 手动访问 `http://192.168.4.1` - 检查 DNS 劫持和门户探测路径是否启用 - 检查 HTTP 服务是否启动成功 ### 问题 2:提交密码后长时间连接失败 排查: - 是否先断开旧 STA - 是否正确处理了连接超时和重试 - 失败原因是否上报到状态机和前端 ### 问题 3:配网失败后无法恢复 排查: - NVS 清除逻辑是否真正执行 - 内存态缓存是否同时清空 - 配网状态是否回到 `provisioning` --- ## 七、测试清单(可复用) 建议每次迭代最少覆盖: 1. 首次配网成功 2. 密码错误后重试成功 3. 连接旧网状态下切换新网成功 4. 清除配置后重新配网成功 5. 重启后自动重连 6. 空闲超时与手动停止路径可用 这 6 条通过后,组件稳定性通常会显著提升。 --- ## 八、可继续增强的方向 - 配网页面安全增强(鉴权/会话) - 多语言提示 - 更细粒度错误码映射 - BLE 辅助配网 - 命令行/远程维护接口联动 --- ## 结语 配网组件的核心价值,不是“让设备连上一次网”,而是: - 功能完整 - 异常可恢复 - 排障可落地 当这三件事做好后,它才是一个能复用、能维护、能上线的基础能力组件。