From 6782af2d4a11372f5f1d22fbd9224abfb7ea19df Mon Sep 17 00:00:00 2001 From: Wang Beihong Date: Fri, 6 Mar 2026 13:21:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20console=5Fuser=5Fc?= =?UTF-8?q?mds=20=E7=BB=84=E4=BB=B6=EF=BC=8C=E6=8F=90=E4=BE=9B=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=91=BD=E4=BB=A4=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/console_user_cmds/CMakeLists.txt | 3 + .../console_user_cmds/console_user_cmds.c | 214 ++++++++ .../include/console_user_cmds.h | 5 + components/wifi-connect/BLOG.md | 467 ++++++++++++------ main/CMakeLists.txt | 2 +- main/main.c | 9 +- 6 files changed, 531 insertions(+), 169 deletions(-) create mode 100644 components/console_user_cmds/CMakeLists.txt create mode 100644 components/console_user_cmds/console_user_cmds.c create mode 100644 components/console_user_cmds/include/console_user_cmds.h diff --git a/components/console_user_cmds/CMakeLists.txt b/components/console_user_cmds/CMakeLists.txt new file mode 100644 index 0000000..e1f6bd7 --- /dev/null +++ b/components/console_user_cmds/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "console_user_cmds.c" + INCLUDE_DIRS "include" + REQUIRES console_simple_init console i2c_master_messager io_device_control wifi-connect) diff --git a/components/console_user_cmds/console_user_cmds.c b/components/console_user_cmds/console_user_cmds.c new file mode 100644 index 0000000..8c318c1 --- /dev/null +++ b/components/console_user_cmds/console_user_cmds.c @@ -0,0 +1,214 @@ +#include +#include + +#include "console_simple_init.h" +#include "console_user_cmds.h" +#include "i2c_master_messager.h" +#include "io_device_control.h" +#include "wifi-connect.h" + +static const char *wifi_status_to_str(wifi_connect_status_t status) +{ + switch (status) { + case WIFI_CONNECT_STATUS_IDLE: + return "idle"; + case WIFI_CONNECT_STATUS_PROVISIONING: + return "provisioning"; + case WIFI_CONNECT_STATUS_CONNECTING: + return "connecting"; + case WIFI_CONNECT_STATUS_CONNECTED: + return "connected"; + case WIFI_CONNECT_STATUS_FAILED: + return "failed"; + case WIFI_CONNECT_STATUS_TIMEOUT: + return "timeout"; + default: + return "unknown"; + } +} + +// hello: 最小可用命令,用于验证 console 链路是否正常。 +static int cmd_hello(int argc, char **argv) +{ + (void)argc; + (void)argv; + printf("hello from BotanicalBuddy\n"); + return 0; +} + +// sensor: 读取一次传感器缓存数据并打印,便于快速排查现场状态。 +static int cmd_sensor(int argc, char **argv) +{ + (void)argc; + (void)argv; + + i2c_master_messager_data_t data = {0}; + esp_err_t ret = i2c_master_messager_get_data(&data); + if (ret != ESP_OK) { + printf("sensor read failed: %s\n", esp_err_to_name(ret)); + return 1; + } + + if (data.bh1750.valid) { + printf("BH1750: lux=%.1f, ts=%lld ms\n", + data.bh1750.lux, + (long long)data.bh1750.last_update_ms); + } else { + printf("BH1750: invalid, err=%s\n", esp_err_to_name(data.bh1750.last_error)); + } + + if (data.aht30.valid) { + printf("AHT30: temp=%.1f C, hum=%.1f %%, ts=%lld ms\n", + data.aht30.temperature_c, + data.aht30.humidity_rh, + (long long)data.aht30.last_update_ms); + } else { + printf("AHT30: invalid, err=%s\n", esp_err_to_name(data.aht30.last_error)); + } + + return 0; +} + +static bool parse_on_off_arg(const char *arg, bool *on) +{ + if (strcmp(arg, "on") == 0 || strcmp(arg, "1") == 0) { + *on = true; + return true; + } + if (strcmp(arg, "off") == 0 || strcmp(arg, "0") == 0) { + *on = false; + return true; + } + return false; +} + +// pump: 控制水泵开关,参数支持 on/off 或 1/0。 +static int cmd_pump(int argc, char **argv) +{ + if (argc < 2) { + printf("usage: pump \n"); + return 1; + } + + bool on = false; + if (!parse_on_off_arg(argv[1], &on)) { + printf("invalid arg: %s\n", argv[1]); + printf("usage: pump \n"); + return 1; + } + + esp_err_t ret = io_device_control_set_pump(on); + if (ret != ESP_OK) { + printf("set pump failed: %s\n", esp_err_to_name(ret)); + return 1; + } + + printf("pump: %s\n", on ? "on" : "off"); + return 0; +} + +// light: 控制补光灯开关,参数支持 on/off 或 1/0。 +static int cmd_light(int argc, char **argv) +{ + if (argc < 2) { + printf("usage: light \n"); + return 1; + } + + bool on = false; + if (!parse_on_off_arg(argv[1], &on)) { + printf("invalid arg: %s\n", argv[1]); + printf("usage: light \n"); + return 1; + } + + esp_err_t ret = io_device_control_set_light(on); + if (ret != ESP_OK) { + printf("set light failed: %s\n", esp_err_to_name(ret)); + return 1; + } + + printf("light: %s\n", on ? "on" : "off"); + return 0; +} + +// wifi: 查询或控制配网状态,支持 status/start/stop/clear 子命令。 +static int cmd_wifi(int argc, char **argv) +{ + if (argc < 2 || strcmp(argv[1], "status") == 0) { + wifi_connect_config_t cfg = {0}; + esp_err_t cfg_ret = wifi_connect_get_config(&cfg); + printf("wifi status: %s\n", wifi_status_to_str(wifi_connect_get_status())); + if (cfg_ret == ESP_OK && cfg.has_config) { + printf("saved ssid: %s\n", cfg.ssid); + } else { + printf("saved config: none\n"); + } + return 0; + } + + if (strcmp(argv[1], "start") == 0) { + esp_err_t ret = wifi_connect_start(); + if (ret != ESP_OK) { + printf("wifi start failed: %s\n", esp_err_to_name(ret)); + return 1; + } + printf("wifi start requested\n"); + return 0; + } + + if (strcmp(argv[1], "stop") == 0) { + esp_err_t ret = wifi_connect_stop(); + if (ret != ESP_OK) { + printf("wifi stop failed: %s\n", esp_err_to_name(ret)); + return 1; + } + printf("wifi stop requested\n"); + return 0; + } + + if (strcmp(argv[1], "clear") == 0) { + esp_err_t ret = wifi_connect_clear_config(); + if (ret != ESP_OK) { + printf("wifi clear failed: %s\n", esp_err_to_name(ret)); + return 1; + } + printf("wifi config cleared\n"); + return 0; + } + + printf("usage: wifi \n"); + return 1; +} + +esp_err_t console_user_cmds_register(void) +{ + esp_err_t ret = ESP_OK; + + ret = console_cmd_user_register("hello", cmd_hello); + if (ret != ESP_OK) { + return ret; + } + + ret = console_cmd_user_register("sensor", cmd_sensor); + if (ret != ESP_OK) { + return ret; + } + + ret = console_cmd_user_register("pump", cmd_pump); + if (ret != ESP_OK) { + return ret; + } + + ret = console_cmd_user_register("light", cmd_light); + if (ret != ESP_OK) { + return ret; + } + + ret = console_cmd_user_register("wifi", cmd_wifi); + if (ret != ESP_OK) { + return ret; + } + + return ESP_OK; +} diff --git a/components/console_user_cmds/include/console_user_cmds.h b/components/console_user_cmds/include/console_user_cmds.h new file mode 100644 index 0000000..54b8255 --- /dev/null +++ b/components/console_user_cmds/include/console_user_cmds.h @@ -0,0 +1,5 @@ +#pragma once + +#include "esp_err.h" + +esp_err_t console_user_cmds_register(void); diff --git a/components/wifi-connect/BLOG.md b/components/wifi-connect/BLOG.md index 76b479e..54bc802 100644 --- a/components/wifi-connect/BLOG.md +++ b/components/wifi-connect/BLOG.md @@ -1,60 +1,69 @@ -# 从 0 到 1:我在 ESP32-C3 上做了一个可落地的 Wi-Fi 配网组件(含中文日志与一键清除配置) +# ESP32 配网组件设计实践:聚焦功能与实现,而不是项目绑定 -> 项目:BotanicalBuddy -> 平台:ESP-IDF v5.5.2(ESP32-C3) +很多 ESP32 设备在开发阶段都把“配网”当成一个小功能,但真正落地后会发现: + +- 用户第一次接入要顺畅 +- 失败后要能恢复 +- 日志要便于现场排障 + +这篇文章只讲配网组件本身,聚焦能力设计和实现思路,不依赖具体业务项目。 --- -## 一、为什么要自己做一个配网组件? +## 一、目标能力定义 -很多物联网项目都会遇到同一个问题: +一个可落地的配网组件,建议至少包含以下能力: -- 设备第一次上电,怎么让用户把它连进家里 Wi-Fi? -- 配网失败时,怎么给用户清晰反馈? -- 现场调试时,日志如何快速看懂? +- 按键触发配网 +- 常驻配网模式(可选) +- SoftAP + Web Portal 配网 +- DNS 劫持与常见 Captive Portal 兼容 +- 凭据持久化(NVS)与重启自动重连 +- 清除历史配置(API + Web) +- 状态机与可读日志 -我这次的目标很明确: +这几项组合起来,才能覆盖“首次成功 + 失败恢复 + 现场维护”三个关键场景。 -1. **用户侧简单**:长按按键 → 连接热点 → 打开网页 → 输入密码; -2. **开发侧可维护**:接口清晰、状态可追踪、日志可读; -3. **现场可排障**:失败能看到原因,支持“一键清除历史配置”。 +图示(整体功能目标关联): -最终我把这些能力收敛成一个组件:`wifi-connect`。 - ---- - -## 二、组件能力概览 - -`wifi-connect` 目前实现了这些核心能力: - -- 长按按键进入配网模式; -- 设备开启 SoftAP(`ESP32-xxxxxx`); -- 内置 HTTP 配网页面(扫描、提交、状态轮询); -- DNS 劫持 + Captive Portal 路径兼容(提升手机弹窗成功率); -- 配网成功后保存凭据到 NVS; -- 上电自动重连已保存网络; -- 中文状态日志(便于现场阅读); -- **清除已保存配置**(网页按钮 + API + SDK 接口)。 - ---- - -## 三、整体架构(简化) - -```text -[按键任务] --长按--> [进入配网] - | - +--> APSTA 模式 - +--> HTTP Server(网页) - +--> DNS 劫持服务 - -[网页] --POST /api/connect--> [设置 STA 参数并连接] -[网页] --GET /api/status --> [轮询状态] -[网页] --POST /api/clear --> [清除 NVS 凭据] - -[Wi-Fi/IP 事件] --> [更新状态机 + 打印中文日志 + 保存凭据] +```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` @@ -63,96 +72,29 @@ - `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(按策略) +``` -### 1)配网页面的“够用即好”设计 - -我没有引入前端框架,而是把 HTML/JS 直接内嵌在 C 字符串里,避免增加构建复杂度。页面只保留 3 个动作: - -- 扫描网络 -- 提交连接 -- 清除已保存配置 - -这种做法的好处是:**部署轻、调试快、资源占用低**。 +上图展示了组件的主要数据流与恢复路径。 --- -### 2)手机“连上热点但不弹页面”的处理 +## 三、对外 API 设计建议 -这是配网常见痛点。为了提高兼容性,我做了两件事: - -1. 注册常见探测路径(如 `/generate_204`、`/hotspot-detect.html` 等); -2. 对探测/未知 GET 请求统一返回 `302` 到 `http://192.168.4.1/`。 - -这样很多手机系统会更容易触发门户页面。 - ---- - -### 3)连接超时问题的根因与修复 - -我遇到过这样一条典型日志: - -- `sta is connected, disconnect before connecting to new ap` - -说明设备当时还连着旧网络,却直接尝试切到新网络,最终走到连接超时。修复方案很直接: - -- 在 `esp_wifi_set_config + esp_wifi_connect` 前,先 `esp_wifi_disconnect()`; -- 若断开失败(非 `NOT_CONNECT`),记录告警日志。 - -这一步对稳定性提升很明显。 - ---- - -### 4)新增“清除已保存配置”能力 - -为了提升可恢复性,我新增了完整链路: - -- SDK API:`wifi_connect_clear_config()` -- HTTP API:`POST /api/clear` -- 页面按钮:“清除已保存” - -执行逻辑: - -1. 清除 NVS 的 `ssid`/`pass`; -2. 清空运行时 pending 参数; -3. 若正在连接中,取消当前连接流程; -4. 在配网模式下把状态恢复为 `provisioning`,并清空旧错误文案。 - -这让“失败后重试”路径变得非常顺畅。 - ---- - -## 五、中文日志:现场效率提升非常大 - -为了让非固件同学也能看懂串口,我统一了状态日志风格: - -- `【状态】配网已启动:配网热点已开启,SSID=...` -- `【状态】开始连接路由器:收到配网请求,目标网络:...` -- `【状态】联网成功:已连接 ...,获取 IP=...` -- `【状态】连接路由器超时:请确认密码和路由器信号` - -这类日志在现场排障时比纯英文驱动日志直观很多。 - -> 注:`wifi:`、`esp_netif_lwip:` 前缀日志依然是 ESP-IDF 框架默认输出。 - ---- - -## 六、前端交互做了哪些“小而有效”的优化? - -在配网页面里,我加了几项很实用的小优化: - -- 连接/清除时禁用按钮,防止连点并发请求; -- 清除成功后自动清空密码框; -- 清除后自动刷新状态和扫描结果; -- 状态枚举映射成中文显示(`connecting -> 连接中`)。 - -这些改动代码不多,但用户体验差异非常明显。 - ---- - -## 七、对外 API(当前版本) +推荐保持“少而稳”的接口: ```c esp_err_t wifi_connect_init(void); @@ -163,58 +105,261 @@ esp_err_t wifi_connect_get_config(wifi_connect_config_t *config); esp_err_t wifi_connect_clear_config(void); ``` -建议调用顺序: +设计原则: -1. 启动时调用 `wifi_connect_init()`; -2. 用户长按或业务触发时调用 `wifi_connect_start()`; -3. 成功后由组件自动收口,必要时可手动 `wifi_connect_stop()`; -4. 需要重置时调用 `wifi_connect_clear_config()`。 +- `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 配网页面保持轻量 -1. 首次配网成功; -2. 密码错误后重试成功; -3. 已连接旧网时切换到新网; -4. 清除配置后重新配网; -5. 空闲超时自动退出; -6. 断电重启后自动重连。 +不一定要引入前端框架。对于资源受限设备,内嵌简洁 HTML/JS 往往更稳定。 -如果这 6 条都稳定通过,组件可用性通常已经比较高。 +建议页面只保留核心动作: + +- 扫描网络 +- 提交连接 +- 查看状态 +- 清除配置 + +### 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[前端轮询显示 可重新配网] +``` --- -## 九、我这次的经验总结 +## 五、日志策略(非常重要) -如果你也在做 ESP32 配网,我建议优先做好三件事: +建议日志遵循“状态 + 原因”格式,例如: -1. **状态机清晰**:每个阶段可见、可回退; -2. **日志可读**:现场的人不一定是固件开发; -3. **失败可恢复**:必须有“清除历史配置”的入口。 +- `【状态】配网已启动:热点已开启,SSID=...` +- `【状态】开始连接路由器:目标网络=...` +- `【状态】联网成功:获取 IP=...` +- `【状态】连接失败:原因=...` -很多时候,不是“功能没做出来”,而是“异常路径没兜住”。把恢复路径做顺,产品体验会提升一大截。 +这样做的收益是: + +- 开发调试快 +- 测试可直接定位阶段 +- 现场人员无需先理解底层驱动日志 + +可展示一个统一日志函数风格: + +```c +static void log_state_i(const char *title, const char *detail) +{ + ESP_LOGI(TAG, "【状态】%s:%s", title, detail ? detail : "-"); +} +``` + +如需补充非 Mermaid 图,建议仅放一张关键串口日志截图(启动配网、连接中、成功/失败重试)。 --- -## 十、后续可继续优化的方向 +## 六、常见问题与排障思路 -- 增加多语言页面(中/英切换); -- 增加 AP 密码与会话保护; -- 支持 BLE 辅助配网; -- 接入云端激活与设备绑定流程; -- 做更细粒度的连接错误码映射(前端可读提示)。 +### 问题 1:手机连上 AP 但页面不弹 + +排查: + +- 手动访问 `http://192.168.4.1` +- 检查 DNS 劫持和门户探测路径是否启用 +- 检查 HTTP 服务是否启动成功 + +### 问题 2:提交密码后长时间连接失败 + +排查: + +- 是否先断开旧 STA +- 是否正确处理了连接超时和重试 +- 失败原因是否上报到状态机和前端 + +### 问题 3:配网失败后无法恢复 + +排查: + +- NVS 清除逻辑是否真正执行 +- 内存态缓存是否同时清空 +- 配网状态是否回到 `provisioning` --- -## 参考(项目内文档) +## 七、测试清单(可复用) -- `components/wifi-connect/README.md` -- `components/wifi-connect/USER_GUIDE.md` -- `components/wifi-connect/QUICK_POSTER.md` +建议每次迭代最少覆盖: + +1. 首次配网成功 +2. 密码错误后重试成功 +3. 连接旧网状态下切换新网成功 +4. 清除配置后重新配网成功 +5. 重启后自动重连 +6. 空闲超时与手动停止路径可用 + +这 6 条通过后,组件稳定性通常会显著提升。 --- -如果你正在做类似项目,希望这篇实践记录能帮你少踩一些坑。 \ No newline at end of file +## 八、可继续增强的方向 + +- 配网页面安全增强(鉴权/会话) +- 多语言提示 +- 更细粒度错误码映射 +- BLE 辅助配网 +- 命令行/远程维护接口联动 + +--- + +## 结语 + +配网组件的核心价值,不是“让设备连上一次网”,而是: + +- 功能完整 +- 异常可恢复 +- 排障可落地 + +当这三件事做好后,它才是一个能复用、能维护、能上线的基础能力组件。 \ No newline at end of file diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 3198b6e..d60eec1 100755 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register(SRCS "main.c" INCLUDE_DIRS "." - REQUIRES wifi-connect esp_lvgl_port lvgl_st7735s_use i2c_master_messager io_device_control console_simple_init console + REQUIRES wifi-connect esp_lvgl_port lvgl_st7735s_use i2c_master_messager io_device_control console_simple_init console console_user_cmds ) diff --git a/main/main.c b/main/main.c index efa0a46..ad09b13 100755 --- a/main/main.c +++ b/main/main.c @@ -10,6 +10,7 @@ #include "i2c_master_messager.h" #include "io_device_control.h" #include "console_simple_init.h" // 提供 console_cmd_user_register 和 console_cmd_all_register +#include "console_user_cmds.h" #ifndef CONFIG_I2C_MASTER_MESSAGER_BH1750_ENABLE #define CONFIG_I2C_MASTER_MESSAGER_BH1750_ENABLE 0 @@ -42,12 +43,6 @@ static const char *TAG = "main"; -static int cmd_hello(int argc, char **argv) -{ - printf("hello from BotanicalBuddy\n"); - return 0; -} - void app_main(void) { // 初始化 Wi-Fi 配网组件,支持长按按键进入配网 @@ -55,7 +50,7 @@ void app_main(void) printf("设备启动完成:长按按键进入配网模式,手机连接 ESP32-* 后访问 http://192.168.4.1\n"); ESP_ERROR_CHECK(console_cmd_init()); - ESP_ERROR_CHECK(console_cmd_user_register("hello", cmd_hello)); + ESP_ERROR_CHECK(console_user_cmds_register()); ESP_ERROR_CHECK(console_cmd_all_register()); // 可选:自动注册插件命令 ESP_ERROR_CHECK(console_cmd_start());