feat: 添加配网模式选择功能,支持按键触发与常驻配网模式

This commit is contained in:
Wang Beihong
2026-03-05 18:46:18 +08:00
parent ebde114be6
commit 32f3f9980d
5 changed files with 117 additions and 27 deletions

View File

@@ -8,6 +8,7 @@
## 功能特性
- 长按按键进入配网模式
- 支持两种配网策略:按键触发 / 常驻配网
- 设备开启 SoftAP`ESP32-xxxxxx`+ Captive Portal
- 手机访问 `http://192.168.4.1` 完成 Wi-Fi 配置
- 支持清除已保存 Wi-Fi 参数并重新配网
@@ -43,7 +44,8 @@
-`app_main` 中调用:`ESP_ERROR_CHECK(start_lvgl_demo());`
- 可选:`ESP_ERROR_CHECK(lvgl_st7735s_set_center_text("BotanicalBuddy"));`
4. 配网
- 长按设备按键进入配网模式
- 按键触发模式:长按设备按键进入配网模式
- 常驻配网模式:上电自动进入配网模式
- 手机连接 `ESP32-xxxxxx`
- 打开 `http://192.168.4.1`
- 选择路由器并输入密码提交
@@ -55,6 +57,7 @@
- `WIFI_CONNECT_BUTTON_STARTUP_GUARD_MS`(建议 8000~10000
- `WIFI_CONNECT_BUTTON_RELEASE_ARM_MS`(建议 300~500
- 若硬件允许,优先给配网按键使用独立 GPIO避免与屏幕复位脚复用。
- 若使用常驻配网模式,可不依赖按键触发(适合按键与 LCD 复位脚复用场景)。
## 当前状态

View File

@@ -1,5 +1,24 @@
menu "WiFi Connect"
choice WIFI_CONNECT_PROVISION_MODE
prompt "Provisioning mode"
default WIFI_CONNECT_PROVISION_MODE_BUTTON
help
Select how provisioning mode is entered.
config WIFI_CONNECT_PROVISION_MODE_BUTTON
bool "Button triggered (default)"
help
Enter provisioning only when button long-press is detected.
config WIFI_CONNECT_PROVISION_MODE_ALWAYS_ON
bool "Always-on provisioning"
help
Start provisioning automatically on boot and keep it running.
Provisioning will not auto-stop after idle timeout or STA connect.
endchoice
config WIFI_CONNECT_BUTTON_GPIO
int "Provision button GPIO"
range 0 21

View File

@@ -7,7 +7,7 @@
- 手机连接热点后,通过网页扫描并选择路由器
- 保存 Wi-Fi 凭据到 NVS
- 下次开机自动重连
- 配网成功后自动关闭热点(可配置延迟)
- 支持两种配网模式:按键触发 / 常驻配网
面向最终用户的一页版操作说明见:`USER_GUIDE.md`
现场打印张贴版(四步卡)见:`QUICK_POSTER.md`
@@ -64,11 +64,15 @@ void app_main(void)
运行后:
1. 长按配置按键进入配网
1. 选择配网模式:
- 按键触发模式:长按配置按键进入配网
- 常驻配网模式:上电自动进入配网
2. 手机连接 `ESP32-xxxxxx` 热点
3. 打开 `http://192.168.4.1`
4. 选择 Wi-Fi 并输入密码提交
5. 设备连接成功后自动关闭配网热点
5. 配网行为:
- 按键触发模式:连接成功后按配置自动关闭热点
- 常驻配网模式:配网热点保持开启,不自动关闭
如需清空历史凭据,可在配网页面点击“清除已保存”。
@@ -78,6 +82,10 @@ void app_main(void)
`idf.py menuconfig` 中:`WiFi Connect` 菜单
- `Provisioning mode`:配网模式(二选一)
- `Button triggered`:按键触发配网(默认)
- `Always-on provisioning`:常驻配网(上电自动进入且不自动关闭)
- `WIFI_CONNECT_BUTTON_GPIO`:进入配网的按键 GPIO
- `WIFI_CONNECT_BUTTON_ACTIVE_LEVEL`:按键有效电平
- `WIFI_CONNECT_DEBOUNCE_MS`:按键去抖时间
@@ -129,10 +137,10 @@ void app_main(void)
- 可增大 `WIFI_CONNECT_BUTTON_RELEASE_ARM_MS`(如 300~500
- 若硬件允许,优先给配网按键使用独立 GPIO
### 3) 成功后热点消失是否正常
### 5) 成功后热点消失是否正常
- 正常。组件设计为连接成功后自动关闭配网热点
- 可通过 `WIFI_CONNECT_AP_GRACEFUL_STOP_SEC` 调整关闭延时
- 在按键触发模式下:正常,可通过 `WIFI_CONNECT_AP_GRACEFUL_STOP_SEC` 调整关闭延时
- 在常驻配网模式下:热点不会自动关闭
---

View File

@@ -15,9 +15,11 @@
## 2. 进入配网模式
1. 长按设备上的配网按键(约 2 秒)
2. 看到串口日志或提示为“配网已启动”
3. 手机 Wi-Fi 列表会出现:`ESP32-xxxxxx`
有两种工作模式,请按项目配置使用:
1. 按键触发模式:长按设备上的配网按键(约 2 秒)
2. 常驻配网模式:设备上电后会自动开启配网
3. 当看到“配网已启动”后,手机 Wi-Fi 列表会出现:`ESP32-xxxxxx`
---
@@ -40,7 +42,10 @@
3. 输入 Wi-Fi 密码
4. 点击“连接”
成功后页面会提示连接成功,设备热点会在几秒后自动关闭(正常现象)。
成功后页面会提示连接成功
- 按键触发模式:设备热点会在几秒后自动关闭(正常现象)
- 常驻配网模式:设备热点保持开启(正常现象)
---

View File

@@ -43,6 +43,24 @@
static const char *TAG = "wifi_connect";
static inline bool wifi_connect_is_always_on_mode(void)
{
#if CONFIG_WIFI_CONNECT_PROVISION_MODE_ALWAYS_ON
return true;
#else
return false;
#endif
}
static inline bool wifi_connect_is_button_mode(void)
{
#if CONFIG_WIFI_CONNECT_PROVISION_MODE_BUTTON
return true;
#else
return false;
#endif
}
static void wifi_connect_log_state_i(const char *state, const char *detail)
{
if (detail != NULL && detail[0] != '\0') {
@@ -160,7 +178,7 @@ static const char *s_html_page =
"catch(e){setError('清除失败');setStatus('');}finally{setBusy(false);}}"
"async function pollStatus(){"
"try{const r=await fetch('/api/status');const d=await r.json();setStatus('状态: '+statusText(d.status));setError(d.error||'');"
"if(d.status==='connected'){setStatus('连接成功,配网服务即将关闭');setError('');}}catch(e){setError('状态获取失败');}}"
"if(d.status==='connected'){setStatus('连接成功');setError('');}}catch(e){setError('状态获取失败');}}"
"loadScan();setInterval(pollStatus,2500);"
"</script></body></html>";
@@ -180,6 +198,9 @@ static void wifi_connect_set_error_locked(const char *message)
static void wifi_connect_refresh_idle_timeout(void)
{
if (wifi_connect_is_always_on_mode()) {
return;
}
if (s_ctx.idle_timer == NULL) {
return;
}
@@ -865,12 +886,18 @@ static void wifi_connect_connect_timeout_cb(void *arg)
static void wifi_connect_ap_stop_timer_cb(void *arg)
{
(void)arg;
if (wifi_connect_is_always_on_mode()) {
return;
}
wifi_connect_stop();
}
static void wifi_connect_idle_timeout_cb(void *arg)
{
(void)arg;
if (wifi_connect_is_always_on_mode()) {
return;
}
xSemaphoreTake(s_ctx.lock, portMAX_DELAY);
bool should_stop = s_ctx.provisioning_active;
if (should_stop) {
@@ -920,6 +947,9 @@ static void wifi_connect_event_handler(void *arg, esp_event_base_t event_base, i
}
if (provisioning_active) {
if (wifi_connect_is_always_on_mode()) {
wifi_connect_log_state_i("常驻配网模式", "联网成功后保持配网热点开启");
} else {
if (CONFIG_WIFI_CONNECT_AP_GRACEFUL_STOP_SEC == 0) {
wifi_connect_stop();
} else {
@@ -927,6 +957,7 @@ static void wifi_connect_event_handler(void *arg, esp_event_base_t event_base, i
esp_timer_start_once(s_ctx.ap_stop_timer, (uint64_t)CONFIG_WIFI_CONNECT_AP_GRACEFUL_STOP_SEC * 1000000ULL);
}
}
}
return;
}
@@ -1091,6 +1122,9 @@ esp_err_t wifi_connect_start(void)
char ap_msg[96] = {0};
snprintf(ap_msg, sizeof(ap_msg), "配网热点已开启SSID=%s访问 http://192.168.4.1", s_ctx.ap_ssid);
wifi_connect_log_state_i("配网已启动", ap_msg);
if (wifi_connect_is_always_on_mode()) {
wifi_connect_log_state_i("当前模式", "常驻配网模式(不会自动关闭)");
}
return ESP_OK;
}
@@ -1163,6 +1197,7 @@ esp_err_t wifi_connect_init(void)
ESP_RETURN_ON_ERROR(esp_wifi_start(), TAG, "wifi start failed");
s_ctx.wifi_started = true;
if (wifi_connect_is_button_mode()) {
gpio_config_t io = {
.pin_bit_mask = (1ULL << CONFIG_WIFI_CONNECT_BUTTON_GPIO),
.mode = GPIO_MODE_INPUT,
@@ -1171,6 +1206,7 @@ esp_err_t wifi_connect_init(void)
.intr_type = GPIO_INTR_DISABLE,
};
ESP_RETURN_ON_ERROR(gpio_config(&io), TAG, "button gpio config failed");
}
s_ctx.lock = xSemaphoreCreateMutex();
ESP_RETURN_ON_FALSE(s_ctx.lock != NULL, ESP_ERR_NO_MEM, TAG, "create lock failed");
@@ -1191,11 +1227,19 @@ esp_err_t wifi_connect_init(void)
ESP_RETURN_ON_ERROR(esp_timer_create(&idle_timer_args, &s_ctx.idle_timer), TAG, "idle timer create failed");
ESP_RETURN_ON_ERROR(esp_timer_create(&ap_stop_timer_args, &s_ctx.ap_stop_timer), TAG, "ap stop timer create failed");
if (wifi_connect_is_button_mode()) {
BaseType_t ok = xTaskCreate(wifi_connect_button_task, "wifi_btn", 3072, NULL, 4, &s_ctx.button_task);
ESP_RETURN_ON_FALSE(ok == pdPASS, ESP_ERR_NO_MEM, TAG, "button task create failed");
}
s_ctx.initialized = true;
if (wifi_connect_is_always_on_mode()) {
wifi_connect_log_state_i("配网模式", "常驻配网(上电自动开启且不会自动关闭)");
} else {
wifi_connect_log_state_i("配网模式", "按键触发配网(长按进入)");
}
err = wifi_connect_try_auto_connect();
if (err != ESP_OK) {
char skip_msg[96] = {0};
@@ -1203,6 +1247,17 @@ esp_err_t wifi_connect_init(void)
wifi_connect_log_state_w("初始化后自动重连未执行", skip_msg);
}
if (wifi_connect_is_always_on_mode()) {
wifi_connect_log_state_i("wifi-connect 初始化完成", "常驻配网模式已启用");
err = wifi_connect_start();
if (err != ESP_OK) {
char mode_msg[96] = {0};
snprintf(mode_msg, sizeof(mode_msg), "自动开启配网失败,错误=%s", esp_err_to_name(err));
wifi_connect_log_state_w("常驻配网启动失败", mode_msg);
return err;
}
} else {
wifi_connect_log_state_i("wifi-connect 初始化完成", "长按按键可进入配网");
}
return ESP_OK;
}