feat: 添加配网模式选择功能,支持按键触发与常驻配网模式
This commit is contained in:
@@ -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 复位脚复用场景)。
|
||||
|
||||
## 当前状态
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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` 调整关闭延时
|
||||
- 在常驻配网模式下:热点不会自动关闭
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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. 点击“连接”
|
||||
|
||||
成功后页面会提示连接成功,设备热点会在几秒后自动关闭(正常现象)。
|
||||
成功后页面会提示连接成功:
|
||||
|
||||
- 按键触发模式:设备热点会在几秒后自动关闭(正常现象)
|
||||
- 常驻配网模式:设备热点保持开启(正常现象)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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,11 +947,15 @@ static void wifi_connect_event_handler(void *arg, esp_event_base_t event_base, i
|
||||
}
|
||||
|
||||
if (provisioning_active) {
|
||||
if (CONFIG_WIFI_CONNECT_AP_GRACEFUL_STOP_SEC == 0) {
|
||||
wifi_connect_stop();
|
||||
if (wifi_connect_is_always_on_mode()) {
|
||||
wifi_connect_log_state_i("常驻配网模式", "联网成功后保持配网热点开启");
|
||||
} else {
|
||||
esp_timer_stop(s_ctx.ap_stop_timer);
|
||||
esp_timer_start_once(s_ctx.ap_stop_timer, (uint64_t)CONFIG_WIFI_CONNECT_AP_GRACEFUL_STOP_SEC * 1000000ULL);
|
||||
if (CONFIG_WIFI_CONNECT_AP_GRACEFUL_STOP_SEC == 0) {
|
||||
wifi_connect_stop();
|
||||
} else {
|
||||
esp_timer_stop(s_ctx.ap_stop_timer);
|
||||
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,14 +1197,16 @@ esp_err_t wifi_connect_init(void)
|
||||
ESP_RETURN_ON_ERROR(esp_wifi_start(), TAG, "wifi start failed");
|
||||
s_ctx.wifi_started = true;
|
||||
|
||||
gpio_config_t io = {
|
||||
.pin_bit_mask = (1ULL << CONFIG_WIFI_CONNECT_BUTTON_GPIO),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
ESP_RETURN_ON_ERROR(gpio_config(&io), TAG, "button gpio config failed");
|
||||
if (wifi_connect_is_button_mode()) {
|
||||
gpio_config_t io = {
|
||||
.pin_bit_mask = (1ULL << CONFIG_WIFI_CONNECT_BUTTON_GPIO),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.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");
|
||||
|
||||
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");
|
||||
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);
|
||||
}
|
||||
|
||||
wifi_connect_log_state_i("wifi-connect 初始化完成", "长按按键可进入配网");
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user