feat: 添加配网模式选择功能,支持按键触发与常驻配网模式
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
- 长按按键进入配网模式
|
- 长按按键进入配网模式
|
||||||
|
- 支持两种配网策略:按键触发 / 常驻配网
|
||||||
- 设备开启 SoftAP(`ESP32-xxxxxx`)+ Captive Portal
|
- 设备开启 SoftAP(`ESP32-xxxxxx`)+ Captive Portal
|
||||||
- 手机访问 `http://192.168.4.1` 完成 Wi-Fi 配置
|
- 手机访问 `http://192.168.4.1` 完成 Wi-Fi 配置
|
||||||
- 支持清除已保存 Wi-Fi 参数并重新配网
|
- 支持清除已保存 Wi-Fi 参数并重新配网
|
||||||
@@ -43,7 +44,8 @@
|
|||||||
- 在 `app_main` 中调用:`ESP_ERROR_CHECK(start_lvgl_demo());`
|
- 在 `app_main` 中调用:`ESP_ERROR_CHECK(start_lvgl_demo());`
|
||||||
- 可选:`ESP_ERROR_CHECK(lvgl_st7735s_set_center_text("BotanicalBuddy"));`
|
- 可选:`ESP_ERROR_CHECK(lvgl_st7735s_set_center_text("BotanicalBuddy"));`
|
||||||
4. 配网
|
4. 配网
|
||||||
- 长按设备按键进入配网模式
|
- 按键触发模式:长按设备按键进入配网模式
|
||||||
|
- 常驻配网模式:上电自动进入配网模式
|
||||||
- 手机连接 `ESP32-xxxxxx`
|
- 手机连接 `ESP32-xxxxxx`
|
||||||
- 打开 `http://192.168.4.1`
|
- 打开 `http://192.168.4.1`
|
||||||
- 选择路由器并输入密码提交
|
- 选择路由器并输入密码提交
|
||||||
@@ -55,6 +57,7 @@
|
|||||||
- `WIFI_CONNECT_BUTTON_STARTUP_GUARD_MS`(建议 8000~10000)
|
- `WIFI_CONNECT_BUTTON_STARTUP_GUARD_MS`(建议 8000~10000)
|
||||||
- `WIFI_CONNECT_BUTTON_RELEASE_ARM_MS`(建议 300~500)
|
- `WIFI_CONNECT_BUTTON_RELEASE_ARM_MS`(建议 300~500)
|
||||||
- 若硬件允许,优先给配网按键使用独立 GPIO,避免与屏幕复位脚复用。
|
- 若硬件允许,优先给配网按键使用独立 GPIO,避免与屏幕复位脚复用。
|
||||||
|
- 若使用常驻配网模式,可不依赖按键触发(适合按键与 LCD 复位脚复用场景)。
|
||||||
|
|
||||||
## 当前状态
|
## 当前状态
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
menu "WiFi Connect"
|
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
|
config WIFI_CONNECT_BUTTON_GPIO
|
||||||
int "Provision button GPIO"
|
int "Provision button GPIO"
|
||||||
range 0 21
|
range 0 21
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
- 手机连接热点后,通过网页扫描并选择路由器
|
- 手机连接热点后,通过网页扫描并选择路由器
|
||||||
- 保存 Wi-Fi 凭据到 NVS
|
- 保存 Wi-Fi 凭据到 NVS
|
||||||
- 下次开机自动重连
|
- 下次开机自动重连
|
||||||
- 配网成功后自动关闭热点(可配置延迟)
|
- 支持两种配网模式:按键触发 / 常驻配网
|
||||||
|
|
||||||
面向最终用户的一页版操作说明见:`USER_GUIDE.md`
|
面向最终用户的一页版操作说明见:`USER_GUIDE.md`
|
||||||
现场打印张贴版(四步卡)见:`QUICK_POSTER.md`
|
现场打印张贴版(四步卡)见:`QUICK_POSTER.md`
|
||||||
@@ -64,11 +64,15 @@ void app_main(void)
|
|||||||
|
|
||||||
运行后:
|
运行后:
|
||||||
|
|
||||||
1. 长按配置按键进入配网
|
1. 选择配网模式:
|
||||||
|
- 按键触发模式:长按配置按键进入配网
|
||||||
|
- 常驻配网模式:上电自动进入配网
|
||||||
2. 手机连接 `ESP32-xxxxxx` 热点
|
2. 手机连接 `ESP32-xxxxxx` 热点
|
||||||
3. 打开 `http://192.168.4.1`
|
3. 打开 `http://192.168.4.1`
|
||||||
4. 选择 Wi-Fi 并输入密码提交
|
4. 选择 Wi-Fi 并输入密码提交
|
||||||
5. 设备连接成功后自动关闭配网热点
|
5. 配网行为:
|
||||||
|
- 按键触发模式:连接成功后按配置自动关闭热点
|
||||||
|
- 常驻配网模式:配网热点保持开启,不自动关闭
|
||||||
|
|
||||||
如需清空历史凭据,可在配网页面点击“清除已保存”。
|
如需清空历史凭据,可在配网页面点击“清除已保存”。
|
||||||
|
|
||||||
@@ -78,6 +82,10 @@ void app_main(void)
|
|||||||
|
|
||||||
在 `idf.py menuconfig` 中:`WiFi Connect` 菜单
|
在 `idf.py menuconfig` 中:`WiFi Connect` 菜单
|
||||||
|
|
||||||
|
- `Provisioning mode`:配网模式(二选一)
|
||||||
|
- `Button triggered`:按键触发配网(默认)
|
||||||
|
- `Always-on provisioning`:常驻配网(上电自动进入且不自动关闭)
|
||||||
|
|
||||||
- `WIFI_CONNECT_BUTTON_GPIO`:进入配网的按键 GPIO
|
- `WIFI_CONNECT_BUTTON_GPIO`:进入配网的按键 GPIO
|
||||||
- `WIFI_CONNECT_BUTTON_ACTIVE_LEVEL`:按键有效电平
|
- `WIFI_CONNECT_BUTTON_ACTIVE_LEVEL`:按键有效电平
|
||||||
- `WIFI_CONNECT_DEBOUNCE_MS`:按键去抖时间
|
- `WIFI_CONNECT_DEBOUNCE_MS`:按键去抖时间
|
||||||
@@ -129,10 +137,10 @@ void app_main(void)
|
|||||||
- 可增大 `WIFI_CONNECT_BUTTON_RELEASE_ARM_MS`(如 300~500)
|
- 可增大 `WIFI_CONNECT_BUTTON_RELEASE_ARM_MS`(如 300~500)
|
||||||
- 若硬件允许,优先给配网按键使用独立 GPIO
|
- 若硬件允许,优先给配网按键使用独立 GPIO
|
||||||
|
|
||||||
### 3) 成功后热点消失是否正常
|
### 5) 成功后热点消失是否正常
|
||||||
|
|
||||||
- 正常。组件设计为连接成功后自动关闭配网热点
|
- 在按键触发模式下:正常,可通过 `WIFI_CONNECT_AP_GRACEFUL_STOP_SEC` 调整关闭延时
|
||||||
- 可通过 `WIFI_CONNECT_AP_GRACEFUL_STOP_SEC` 调整关闭延时
|
- 在常驻配网模式下:热点不会自动关闭
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,11 @@
|
|||||||
|
|
||||||
## 2. 进入配网模式
|
## 2. 进入配网模式
|
||||||
|
|
||||||
1. 长按设备上的配网按键(约 2 秒)
|
有两种工作模式,请按项目配置使用:
|
||||||
2. 看到串口日志或提示为“配网已启动”
|
|
||||||
3. 手机 Wi-Fi 列表会出现:`ESP32-xxxxxx`
|
1. 按键触发模式:长按设备上的配网按键(约 2 秒)
|
||||||
|
2. 常驻配网模式:设备上电后会自动开启配网
|
||||||
|
3. 当看到“配网已启动”后,手机 Wi-Fi 列表会出现:`ESP32-xxxxxx`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -40,7 +42,10 @@
|
|||||||
3. 输入 Wi-Fi 密码
|
3. 输入 Wi-Fi 密码
|
||||||
4. 点击“连接”
|
4. 点击“连接”
|
||||||
|
|
||||||
成功后页面会提示连接成功,设备热点会在几秒后自动关闭(正常现象)。
|
成功后页面会提示连接成功:
|
||||||
|
|
||||||
|
- 按键触发模式:设备热点会在几秒后自动关闭(正常现象)
|
||||||
|
- 常驻配网模式:设备热点保持开启(正常现象)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,24 @@
|
|||||||
|
|
||||||
static const char *TAG = "wifi_connect";
|
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)
|
static void wifi_connect_log_state_i(const char *state, const char *detail)
|
||||||
{
|
{
|
||||||
if (detail != NULL && detail[0] != '\0') {
|
if (detail != NULL && detail[0] != '\0') {
|
||||||
@@ -160,7 +178,7 @@ static const char *s_html_page =
|
|||||||
"catch(e){setError('清除失败');setStatus('');}finally{setBusy(false);}}"
|
"catch(e){setError('清除失败');setStatus('');}finally{setBusy(false);}}"
|
||||||
"async function pollStatus(){"
|
"async function pollStatus(){"
|
||||||
"try{const r=await fetch('/api/status');const d=await r.json();setStatus('状态: '+statusText(d.status));setError(d.error||'');"
|
"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);"
|
"loadScan();setInterval(pollStatus,2500);"
|
||||||
"</script></body></html>";
|
"</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)
|
static void wifi_connect_refresh_idle_timeout(void)
|
||||||
{
|
{
|
||||||
|
if (wifi_connect_is_always_on_mode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (s_ctx.idle_timer == NULL) {
|
if (s_ctx.idle_timer == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -865,12 +886,18 @@ static void wifi_connect_connect_timeout_cb(void *arg)
|
|||||||
static void wifi_connect_ap_stop_timer_cb(void *arg)
|
static void wifi_connect_ap_stop_timer_cb(void *arg)
|
||||||
{
|
{
|
||||||
(void)arg;
|
(void)arg;
|
||||||
|
if (wifi_connect_is_always_on_mode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
wifi_connect_stop();
|
wifi_connect_stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void wifi_connect_idle_timeout_cb(void *arg)
|
static void wifi_connect_idle_timeout_cb(void *arg)
|
||||||
{
|
{
|
||||||
(void)arg;
|
(void)arg;
|
||||||
|
if (wifi_connect_is_always_on_mode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
xSemaphoreTake(s_ctx.lock, portMAX_DELAY);
|
xSemaphoreTake(s_ctx.lock, portMAX_DELAY);
|
||||||
bool should_stop = s_ctx.provisioning_active;
|
bool should_stop = s_ctx.provisioning_active;
|
||||||
if (should_stop) {
|
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 (provisioning_active) {
|
||||||
|
if (wifi_connect_is_always_on_mode()) {
|
||||||
|
wifi_connect_log_state_i("常驻配网模式", "联网成功后保持配网热点开启");
|
||||||
|
} else {
|
||||||
if (CONFIG_WIFI_CONNECT_AP_GRACEFUL_STOP_SEC == 0) {
|
if (CONFIG_WIFI_CONNECT_AP_GRACEFUL_STOP_SEC == 0) {
|
||||||
wifi_connect_stop();
|
wifi_connect_stop();
|
||||||
} else {
|
} 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);
|
esp_timer_start_once(s_ctx.ap_stop_timer, (uint64_t)CONFIG_WIFI_CONNECT_AP_GRACEFUL_STOP_SEC * 1000000ULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1091,6 +1122,9 @@ esp_err_t wifi_connect_start(void)
|
|||||||
char ap_msg[96] = {0};
|
char ap_msg[96] = {0};
|
||||||
snprintf(ap_msg, sizeof(ap_msg), "配网热点已开启,SSID=%s,访问 http://192.168.4.1", s_ctx.ap_ssid);
|
snprintf(ap_msg, sizeof(ap_msg), "配网热点已开启,SSID=%s,访问 http://192.168.4.1", s_ctx.ap_ssid);
|
||||||
wifi_connect_log_state_i("配网已启动", ap_msg);
|
wifi_connect_log_state_i("配网已启动", ap_msg);
|
||||||
|
if (wifi_connect_is_always_on_mode()) {
|
||||||
|
wifi_connect_log_state_i("当前模式", "常驻配网模式(不会自动关闭)");
|
||||||
|
}
|
||||||
return ESP_OK;
|
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");
|
ESP_RETURN_ON_ERROR(esp_wifi_start(), TAG, "wifi start failed");
|
||||||
s_ctx.wifi_started = true;
|
s_ctx.wifi_started = true;
|
||||||
|
|
||||||
|
if (wifi_connect_is_button_mode()) {
|
||||||
gpio_config_t io = {
|
gpio_config_t io = {
|
||||||
.pin_bit_mask = (1ULL << CONFIG_WIFI_CONNECT_BUTTON_GPIO),
|
.pin_bit_mask = (1ULL << CONFIG_WIFI_CONNECT_BUTTON_GPIO),
|
||||||
.mode = GPIO_MODE_INPUT,
|
.mode = GPIO_MODE_INPUT,
|
||||||
@@ -1171,6 +1206,7 @@ esp_err_t wifi_connect_init(void)
|
|||||||
.intr_type = GPIO_INTR_DISABLE,
|
.intr_type = GPIO_INTR_DISABLE,
|
||||||
};
|
};
|
||||||
ESP_RETURN_ON_ERROR(gpio_config(&io), TAG, "button gpio config failed");
|
ESP_RETURN_ON_ERROR(gpio_config(&io), TAG, "button gpio config failed");
|
||||||
|
}
|
||||||
|
|
||||||
s_ctx.lock = xSemaphoreCreateMutex();
|
s_ctx.lock = xSemaphoreCreateMutex();
|
||||||
ESP_RETURN_ON_FALSE(s_ctx.lock != NULL, ESP_ERR_NO_MEM, TAG, "create lock failed");
|
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(&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");
|
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);
|
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");
|
ESP_RETURN_ON_FALSE(ok == pdPASS, ESP_ERR_NO_MEM, TAG, "button task create failed");
|
||||||
|
}
|
||||||
|
|
||||||
s_ctx.initialized = true;
|
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();
|
err = wifi_connect_try_auto_connect();
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
char skip_msg[96] = {0};
|
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_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 初始化完成", "长按按键可进入配网");
|
wifi_connect_log_state_i("wifi-connect 初始化完成", "长按按键可进入配网");
|
||||||
|
}
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user