183	        "input:focus,select:focus{border-color:#3a9ec4}"
184	        ".msg{background:#252540}"
185	        ".msg.S{background:#0d3328}"
186	        ".msg.P{background:#0d2833}"
187	        ".msg.D{background:#330d0d}"
188	        "}"
189	        "</style>"
190	    );
191
192	    // 开启密码可见切换
193	    _wm.setShowPassword(true);
194
195	    // --- 首页引导文字 (显示在 WiFi 列表上方) ---
196	    _wm.setCustomMenuHTML(
197	        "
"
198	        "
"
199	        "📋 配网步骤
"
200	        "
"
201	        "1 选择 WiFi 并输入密码
"
202	        "2 填写 MQTT 服务器参数
"
203	        "3 设置设备名称和安装环境
"
204	        "4 点击保存，等待连接成功"
205	        "

"
206	    );
207
208	    WiFi.setAutoReconnect(true);
209
210	    // 注册保存回调
211	    _wm.setSaveConfigCallback(_saveParamsCallback);
212
213	    // ============================================================
214	    // 自定义参数 (分三组: WiFi / MQTT / 设备)
215	    // ============================================================
216
217	    // --- 设备信息 ---
218	    _param_device_id = new WiFiManagerParameter(
219	        "device_id", "🔖 设备编号 (网关唯一标识)",
220	        _config.device_id, 20);
221	    _param_env_note = new WiFiManagerParameter(
222	        "env_note", "📍 安装环境 (如: 主卧 / 客厅 / 老人房)",
223	        _config.env_note, 20);
224
225	    // --- MQTT 服务器 ---
226	    _param_mqtt_server = new WiFiManagerParameter(
227	        "mqtt_server", "🌐 MQTT 服务器地址",
228	        _config.mqtt_server, 40);
229	    _param_mqtt_port = new WiFiManagerParameter(
230	        "mqtt_port", "🔌 MQTT 端口号",
231	        _config.mqtt_port, 6);
232	    _param_mqtt_user = new WiFiManagerParameter(
233	        "mqtt_user", "👤 MQTT 用户名",
234	        _config.mqtt_user, 20);
235	    _param_mqtt_pass = new WiFiManagerParameter(
236	        "mqtt_pass", "🔑 MQTT 密码",
237	        _config.mqtt_pass, 20);
238
239	    // 参数按顺序注册: 设备在前, MQTT 在后
240	    _wm.addParameter(_param_device_id);
241	    _wm.addParameter(_param_env_note);
242	    _wm.addParameter(_param_mqtt_server);
243	    _wm.addParameter(_param_mqtt_port);
244	    _wm.addParameter(_param_mqtt_user);
245	    _wm.addParameter(_param_mqtt_pass);
246
247	    // 设置 AP 模式回调
248	    _wm.setAPCallback(_apCallback);
249
250	    // 尝试连接 WiFi
251	    _connected = _connectWiFi();
252
253	    if (!_connected) {
254	        // 如果自动连接失败, 启动配网页面
255	        Serial.println(F("[WiFi] 自动连接失败, 启动配网模式..."));
256	        _emitProvisionState("ready");  // 协议 §4.4: 进入配网模式
257	        _setLEDMode(1);  // 快闪 = 配网模式
258	        // 配网期间 loop() 阻塞，LED 靠另一个小任务单独闪 (见 _startLEDTask)
259	        _startLEDTask(300);  // 300ms 快闪
260	        _connected = _connectWiFi();  // 内部会启动配网页面 (阻塞!)
261	        _stopLEDTask();
262	    }

"input:focus,select:focus{border-color:#3a9ec4}"
        ".msg{background:#252540}"
        ".msg.S{background:#0d3328}"
        ".msg.P{background:#0d2833}"
        ".msg.D{background:#330d0d}"
        "}"
        "</style>"
    );

    // 开启密码可见切换
    _wm.setShowPassword(true);

    // --- 首页引导文字 (显示在 WiFi 列表上方) ---
    _wm.setCustomMenuHTML(
        "
"
        "
"
        "📋 配网步骤
"
        "
"
        "1 选择 WiFi 并输入密码
"
        "2 填写 MQTT 服务器参数
"
        "3 设置设备名称和安装环境
"
        "4 点击保存，等待连接成功"
        "

"
    );

    WiFi.setAutoReconnect(true);

    // 注册保存回调
    _wm.setSaveConfigCallback(_saveParamsCallback);

    // ============================================================
    // 自定义参数 (分三组: WiFi / MQTT / 设备)
    // ============================================================

    // --- 设备信息 ---
    _param_device_id = new WiFiManagerParameter(
        "device_id", "🔖 设备编号 (网关唯一标识)",
        _config.device_id, 20);
    _param_env_note = new WiFiManagerParameter(
        "env_note", "📍 安装环境 (如: 主卧 / 客厅 / 老人房)",
        _config.env_note, 20);

    // --- MQTT 服务器 ---
    _param_mqtt_server = new WiFiManagerParameter(
        "mqtt_server", "🌐 MQTT 服务器地址",
        _config.mqtt_server, 40);
    _param_mqtt_port = new WiFiManagerParameter(
        "mqtt_port", "🔌 MQTT 端口号",
        _config.mqtt_port, 6);
    _param_mqtt_user = new WiFiManagerParameter(
        "mqtt_user", "👤 MQTT 用户名",
        _config.mqtt_user, 20);
    _param_mqtt_pass = new WiFiManagerParameter(
        "mqtt_pass", "🔑 MQTT 密码",
        _config.mqtt_pass, 20);

    // 参数按顺序注册: 设备在前, MQTT 在后
    _wm.addParameter(_param_device_id);
    _wm.addParameter(_param_env_note);
    _wm.addParameter(_param_mqtt_server);
    _wm.addParameter(_param_mqtt_port);
    _wm.addParameter(_param_mqtt_user);
    _wm.addParameter(_param_mqtt_pass);

    // 设置 AP 模式回调
    _wm.setAPCallback(_apCallback);

    // 尝试连接 WiFi
    _connected = _connectWiFi();

    if (!_connected) {
        // 如果自动连接失败, 启动配网页面
        Serial.println(F("[WiFi] 自动连接失败, 启动配网模式..."));
        _emitProvisionState("ready");  // 协议 §4.4: 进入配网模式
        _setLEDMode(1);  // 快闪 = 配网模式
        // 配网期间 loop() 阻塞，LED 靠另一个小任务单独闪 (见 _startLEDTask)
        _startLEDTask(300);  // 300ms 快闪
        _connected = _connectWiFi();  // 内部会启动配网页面 (阻塞!)
        _stopLEDTask();
    }


我现在需要把配网界面优化。把代码给软件组


Everything looks good. Let me also read the AP callback to confirm it's updated:


495
496	// ============================================================
497	// 静态回调函数 (WiFiManager 要求 C 函数指针)
498	// ============================================================
499
500	void WiFiMgmt::_apCallback(WiFiManager* wm) {
501	    Serial.println(F("\n╔══════════════════════════════════════════╗"));
502	    Serial.println(F("║  🔧 配网模式已启动                       ║"));
503	    Serial.println(F("╠══════════════════════════════════════════╣"));
504	    Serial.printf( "║  WiFi名称: %-29s ║\n", WIFI_AP_SSID);
505	    Serial.printf( "║  密码:     %-29s ║\n", WIFI_AP_PASSWORD);
506	    Serial.println(F("╠══════════════════════════════════════════╣"));
507	    Serial.println(F("║  📱 请按以下步骤操作:                   ║"));
508	    Serial.println(F("║  1. 手机打开 WiFi 设置                  ║"));
509	    Serial.println(F("║  2. 连接上方显示的 WiFi                 ║"));
510	    Serial.println(F("║  3. 浏览器将自动弹出配网页面            ║"));
511	    Serial.println(F("║     (如未弹出, 手动访问 192.168.4.1)    ║"));
512	    Serial.println(F("╚══════════════════════════════════════════╝\n"));
513	}
514
515	void WiFiMgmt::_saveParamsCallback() {
516	    if (!_instance) return;
517
518	    Serial.println(F("[WiFi] 💾 参数已保存"));
519
520	    // 从 WiFiManager 参数中读取值
521	    strncpy(_instance->_config.mqtt_server, _instance->_param_mqtt_server->getValue(),
522	            sizeof(_instance->_config.mqtt_server) - 1);
523	    strncpy(_instance->_config.mqtt_port,   _instance->_param_mqtt_port->getValue(),
524	            sizeof(_instance->_config.mqtt_port) - 1);

// ============================================================
// 静态回调函数 (WiFiManager 要求 C 函数指针)
// ============================================================

void WiFiMgmt::_apCallback(WiFiManager* wm) {
    Serial.println(F("\n╔══════════════════════════════════════════╗"));
    Serial.println(F("║  🔧 配网模式已启动                       ║"));
    Serial.println(F("╠══════════════════════════════════════════╣"));
    Serial.printf( "║  WiFi名称: %-29s ║\n", WIFI_AP_SSID);
    Serial.printf( "║  密码:     %-29s ║\n", WIFI_AP_PASSWORD);
    Serial.println(F("╠══════════════════════════════════════════╣"));
    Serial.println(F("║  📱 请按以下步骤操作:                   ║"));
    Serial.println(F("║  1. 手机打开 WiFi 设置                  ║"));
    Serial.println(F("║  2. 连接上方显示的 WiFi                 ║"));
    Serial.println(F("║  3. 浏览器将自动弹出配网页面            ║"));
    Serial.println(F("║     (如未弹出, 手动访问 192.168.4.1)    ║"));
    Serial.println(F("╚══════════════════════════════════════════╝\n"));
}

void WiFiMgmt::_saveParamsCallback() {
    if (!_instance) return;

    Serial.println(F("[WiFi] 💾 参数已保存"));

    // 从 WiFiManager 参数中读取值
    strncpy(_instance->_config.mqtt_server, _instance->_param_mqtt_server->getValue(),
            sizeof(_instance->_config.mqtt_server) - 1);
    strncpy(_instance->_config.mqtt_port,   _instance->_param_mqtt_port->getValue(),
            sizeof(_instance->_config.mqtt_port) - 1);


TodoWrite

重写 setCustomHeadElement CSS 样式

添加 setCustomMenuHTML 引导说明

优化参数标签（分组、更专业的描述）

更新 AP 模式回调解说

验证最终代码完整性


Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable

重写 setCustomHeadElement CSS 样式

添加 setCustomMenuHTML 引导说明

优化参数标签（分组、更专业的描述）

更新 AP 模式回调解说

验证最终代码完整性

重写 setCustomHeadElement CSS 样式

添加 setCustomMenuHTML 引导说明

优化参数标签（分组、更专业的描述）

更新 AP 模式回调解说

验证最终代码完整性


All changes are complete. Let me summarize the optimizations for the user.


优化完成。所有改动都在 [src/wifi_manager.cpp](src/wifi_manager.cpp) 一个文件中，下面是完整对比：

---

## 配网界面优化总结

### 改动清单（均在 `wifi_manager.cpp`）

| 位置 | 改动 | 效果 |
|------|------|------|
| 第80行 | 标题改为 `健康监测 · 设备配网` | 更简洁专业 |
| 第83-190行 | **CSS 全面重写** (~110行) | 见下方详细说明 |
| 第193行 | `setShowPassword(true)` | 新增密码可见切换按钮 |
| 第196-206行 | `setCustomMenuHTML()` | 新增首页 4 步引导卡片 |
| 第217-245行 | 参数标签加 emoji + 重新排序 | 设备→MQTT 分组，语义更清晰 |
| 第500-513行 | `_apCallback` 重写 | 串口打印框线指引 |

### CSS 优化详情

```
旧: 4行（仅改字体）
新: ~110行，涵盖：
├── 渐变背景（浅蓝→浅绿→暖色）
├── 卡片式容器 (白色圆角 + 阴影)
├── 输入框圆角 8px + focus 发光环
├── 按钮渐变蓝色 + hover 上浮效果
├── 消息提示 左色条 (绿/蓝/红)
├── 步骤编号圆形标记
└── 暗色模式自动适配 (prefers-color-scheme)
```

### 页面结构变化

**旧流程**：WiFi列表 → 一堆平铺参数 → 保存

**新流程**：
```
┌─────────────────────────────────┐
│  📋 配网步骤                    │
│  ① 选择WiFi  ② 填MQTT参数      │
│  ③ 设设备名  ④ 点保存          │  ← 首页引导卡片
├─────────────────────────────────┤
│  WiFi 名称: [__________]        │
│  WiFi 密码: [__________] 👁     │  ← 密码可见切换
├─────────────────────────────────┤
│  🔖 设备编号: [__________]      │
│  📍 安装环境: [__________]      │  ← 设备信息组
│  🌐 MQTT地址: [__________]      │
│  🔌 MQTT端口: [__________]      │
│  👤 MQTT用户: [__________]      │  ← MQTT 服务器组
│  🔑 MQTT密码: [__________]      │
├─────────────────────────────────┤
│  [ 保存配置 ]                   │  ← 渐变蓝色按钮
└─────────────────────────────────┘
```

可以直接交给软件组了。


代码给就行，软件组优化


The user is saying they just want the raw code output, and the software team will do the optimization themselves. They don't need my summary or explanation - just the code files that were changed. Let me provide the final code files.


明白，直接给代码。

改动涉及 4 个文件，核心在 [src/wifi_manager.cpp](src/wifi_manager.cpp)：


1	/**
2	 * @file wifi_manager.cpp
3	 * @brief WiFi 配网管理器实现
4	 */
5
6	#include "wifi_manager.h"
7
8	// 单例指针 + LED 任务
9	WiFiMgmt* WiFiMgmt::_instance = nullptr;
10	bool WiFiMgmt::_ledTaskRunning = false;
11	uint32_t WiFiMgmt::_ledTaskInterval = 300;
12
13	// ============================================================
14	// 构造函数 & 析构函数
15	// ============================================================
16
17	WiFiMgmt::WiFiMgmt()
18	    : _connected(false)
19	    , _lastCheckTime(0)
20	    , _buttonPressTime(0)
21	    , _buttonPressed(false)
22	    , _ledLastToggle(0)
23	    , _ledState(false)
24	    , _param_mqtt_server(nullptr)
25	    , _param_mqtt_port(nullptr)
26	    , _param_mqtt_user(nullptr)
27	    , _param_mqtt_pass(nullptr)
28	    , _param_device_id(nullptr)
29	    , _param_env_note(nullptr)
30	    , _provisionStateCb(nullptr)
31	{
32	    memset(&_config, 0, sizeof(_config));
33	    _instance = this;  // 设置单例
34	}
35
36	WiFiMgmt::~WiFiMgmt() {
37	    _instance = nullptr;
38	}
39
40	// ============================================================
41	// 初始化
42	// ============================================================
43
44	bool WiFiMgmt::begin() {
45	    Serial.println(F("\n========================================"));
46	    Serial.println(F("  WiFi 配网管理器 初始化"));
47	    Serial.println(F("========================================"));
48
49	    // 初始化引脚
50	    pinMode(CONFIG_BUTTON_PIN, INPUT_PULLUP);
51	    pinMode(LED_WIFI_PIN, OUTPUT);
52	    digitalWrite(LED_WIFI_PIN, LOW);
53
54	    // 初始化 LittleFS
55	    if (!LittleFS.begin(true)) {
56	        Serial.println(F("[WiFi] ❌ LittleFS 挂载失败!"));
57	        return false;
58	    }
59	    Serial.println(F("[WiFi] ✅ LittleFS 已挂载"));
60
61	    // 加载配置
62	    if (!_loadConfig()) {
63	        Serial.println(F("[WiFi] 使用默认配置"));
64	        // 设置默认值
65	        strncpy(_config.mqtt_server, MQTT_DEFAULT_SERVER,   sizeof(_config.mqtt_server) - 1);
66	        strncpy(_config.mqtt_port,   MQTT_DEFAULT_PORT_STR, sizeof(_config.mqtt_port) - 1);
67	        strncpy(_config.mqtt_user,   MQTT_DEFAULT_USER,     sizeof(_config.mqtt_user) - 1);
68	        strncpy(_config.mqtt_pass,   MQTT_DEFAULT_PASS,     sizeof(_config.mqtt_pass) - 1);
69	        strncpy(_config.device_id,   MQTT_DEFAULT_DEVICE,   sizeof(_config.device_id) - 1);
70	        strncpy(_config.env_note,    MQTT_DEFAULT_ENV,      sizeof(_config.env_note) - 1);
71	    }
72
73	    // ============================================================
74	    // 配置 WiFiManager (配网页面的外观和行为)
75	    // ============================================================
76
77	    _wm.setDebugOutput(false);  // 关闭调试输出 (避免干扰)
78	    _wm.setConfigPortalTimeout(WIFI_CONFIG_TIMEOUT);
79	    _wm.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
80	    _wm.setTitle("健康监测 · 设备配网");
81
82	    // --- 自定义 CSS (现代医疗主题, 移动端优先) ---
83	    _wm.setCustomHeadElement(
84	        "<meta name='theme-color' content='#2c7da0'>"
85	        ""
190	    );
191
192	    // 开启密码可见切换
193	    _wm.setShowPassword(true);
194
195	    // --- 首页引导文字 (显示在 WiFi 列表上方) ---
196	    _wm.setCustomMenuHTML(
197	        "
"
198	        "
"
199	        "📋 配网步骤
"
200	        "
"
201	        "1 选择 WiFi 并输入密码
"
202	        "2 填写 MQTT 服务器参数
"
203	        "3 设置设备名称和安装环境
"
204	        "4 点击保存，等待连接成功"
205	        "

"
206	    );
207
208	    WiFi.setAutoReconnect(true);
209
210	    // 注册保存回调
211	    _wm.setSaveConfigCallback(_saveParamsCallback);
212
213	    // ============================================================
214	    // 自定义参数 (分三组: WiFi / MQTT / 设备)
215	    // ============================================================
216
217	    // --- 设备信息 ---
218	    _param_device_id = new WiFiManagerParameter(
219	        "device_id", "🔖 设备编号 (网关唯一标识)",
220	        _config.device_id, 20);
221	    _param_env_note = new WiFiManagerParameter(
222	        "env_note", "📍 安装环境 (如: 主卧 / 客厅 / 老人房)",
223	        _config.env_note, 20);
224
225	    // --- MQTT 服务器 ---
226	    _param_mqtt_server = new WiFiManagerParameter(
227	        "mqtt_server", "🌐 MQTT 服务器地址",
228	        _config.mqtt_server, 40);
229	    _param_mqtt_port = new WiFiManagerParameter(
230	        "mqtt_port", "🔌 MQTT 端口号",
231	        _config.mqtt_port, 6);
232	    _param_mqtt_user = new WiFiManagerParameter(
233	        "mqtt_user", "👤 MQTT 用户名",
234	        _config.mqtt_user, 20);
235	    _param_mqtt_pass = new WiFiManagerParameter(
236	        "mqtt_pass", "🔑 MQTT 密码",
237	        _config.mqtt_pass, 20);
238
239	    // 参数按顺序注册: 设备在前, MQTT 在后
240	    _wm.addParameter(_param_device_id);
241	    _wm.addParameter(_param_env_note);
242	    _wm.addParameter(_param_mqtt_server);
243	    _wm.addParameter(_param_mqtt_port);
244	    _wm.addParameter(_param_mqtt_user);
245	    _wm.addParameter(_param_mqtt_pass);
246
247	    // 设置 AP 模式回调
248	    _wm.setAPCallback(_apCallback);
249
250	    // 尝试连接 WiFi
251	    _connected = _connectWiFi();
252
253	    if (!_connected) {
254	        // 如果自动连接失败, 启动配网页面
255	        Serial.println(F("[WiFi] 自动连接失败, 启动配网模式..."));
256	        _emitProvisionState("ready");  // 协议 §4.4: 进入配网模式
257	        _setLEDMode(1);  // 快闪 = 配网模式
258	        // 配网期间 loop() 阻塞，LED 靠另一个小任务单独闪 (见 _startLEDTask)
259	        _startLEDTask(300);  // 300ms 快闪
260	        _connected = _connectWiFi();  // 内部会启动配网页面 (阻塞!)
261	        _stopLEDTask();
262	    }
263
264	    if (_connected) {
265	        _setLEDMode(2);  // 常亮 = 已连接
266	        _emitProvisionState("success");  // 协议 §4.4: 配网成功
267	    }
268
269	    _lastCheckTime = millis();
270	    return _connected;
271	}
272
273	// ============================================================
274	// 主循环
275	// ============================================================
276
277	bool WiFiMgmt::loop() {
278	    // 检测按钮长按
279	    if (_checkButtonLongPress()) {
280	        Serial.println(F("[WiFi] ⚠  检测到长按, 清除配置并重启..."));
281	        resetSettings();
282	        return false;
283	    }
284
285	    // 检查 WiFi 状态
286	    if (millis() - _lastCheckTime > WIFI_RETRY_INTERVAL) {
287	        _lastCheckTime = millis();
288
289	        if (WiFi.status() != WL_CONNECTED) {
290	            _connected = false;
291	            _setLEDMode(0);  // 慢闪 = 未连接
292	            Serial.println(F("[WiFi] ⚠  WiFi 断开, 尝试重连..."));
293	            _connectWiFi();
294	        } else {
295	            _connected = true;
296	            _setLEDMode(2);  // 常亮
297	        }
298	    }
299
300	    // LED 闪烁处理
301	    if (!_connected) {
302	        uint32_t interval = (_wm.getConfigPortalActive()) ? 300 : 1000;
303	        if (millis() - _ledLastToggle > interval) {
304	            _ledLastToggle = millis();
305	            _ledState = !_ledState;
306	            digitalWrite(LED_WIFI_PIN, _ledState ? HIGH : LOW);
307	        }
308	    }
309
310	    return _connected;
311	}
312
313	// ============================================================
314	// WiFi 连接
315	// ============================================================
316
317	bool WiFiMgmt::_connectWiFi() {
318	    // 检查是否有已保存的配置
319	    // autoConnect 会先尝试连接, 失败后启动 AP 配网页面
320	    _connected = _wm.autoConnect(WIFI_AP_SSID, WIFI_AP_PASSWORD);
321
322	    if (_connected) {
323	        Serial.println(F("[WiFi] ✅ 已连接!"));
324	        Serial.printf("  WiFi名称: %s\n", WiFi.SSID().c_str());
325	        Serial.printf("  IP地址:   %s\n", WiFi.localIP().toString().c_str());
326	        Serial.printf("  MAC地址:  %s\n", WiFi.macAddress().c_str());
327
328	        // 重新加载配置 (确保获取最新值)
329	        // autoConnect 过程中如果有保存, 回调已写入 LittleFS
330	        _loadConfig();
331
332	        // 同步自定义参数 (如果配置是从 LittleFS 加载的)
333	        _param_mqtt_server->setValue(_config.mqtt_server, sizeof(_config.mqtt_server));
334	        _param_mqtt_port->setValue(_config.mqtt_port, sizeof(_config.mqtt_port));
335	        _param_mqtt_user->setValue(_config.mqtt_user, sizeof(_config.mqtt_user));
336	        _param_mqtt_pass->setValue(_config.mqtt_pass, sizeof(_config.mqtt_pass));
337	        _param_device_id->setValue(_config.device_id, sizeof(_config.device_id));
338	        _param_env_note->setValue(_config.env_note, sizeof(_config.env_note));
339
340	        Serial.printf("  设备名: %s\n", _config.device_id);
341	        Serial.printf("  环境:   %s\n", _config.env_note);
342	        Serial.printf("  MQTT服务器: %s:%s\n", _config.mqtt_server, _config.mqtt_port);
343
344	        _emitProvisionState("success");  // 协议 §4.4: 配网成功
345	    } else {
346	        _emitProvisionState("wifi_failed");  // 协议 §4.4: WiFi 连接失败
347	    }
348
349	    return _connected;
350	}
351
352	// ============================================================
353	// 配网状态回调注册
354	// ============================================================
355
356	void WiFiMgmt::onProvisionState(ProvisionStateCallback cb) {
357	    _provisionStateCb = cb;
358	}
359
360	void WiFiMgmt::_emitProvisionState(const char* state) {
361	    Serial.printf("[WiFi] 📡 配网状态: %s\n", state);
362	    if (_provisionStateCb) {
363	        _provisionStateCb(state);
364	    }
365	}
366
367	// ============================================================
368	// 配网页面
369	// ============================================================
370
371	void WiFiMgmt::startConfigPortal() {
372	    Serial.println(F("[WiFi] 🔧 手动启动配网页面..."));
373	    _emitProvisionState("ready");  // 协议 §4.4: 进入配网模式
374	    _setLEDMode(1);
375	    _startLEDTask(300);  // 快闪
376	    _wm.startConfigPortal(WIFI_AP_SSID, WIFI_AP_PASSWORD);
377	    _stopLEDTask();
378	    _connected = (WiFi.status() == WL_CONNECTED);
379	    if (_connected) {
380	        _setLEDMode(2);
381	        _emitProvisionState("success");  // 协议 §4.4: 配网成功
382	    }
383	}
384
385	// ============================================================
386	// 重置设置
387	// ============================================================
388
389	void WiFiMgmt::resetSettings() {
390	    Serial.println(F("[WiFi] 清除所有配置..."));
391
392	    // 删除 LittleFS 中的配置文件
393	    if (LittleFS.exists(CONFIG_FILE_WIFI)) LittleFS.remove(CONFIG_FILE_WIFI);
394	    if (LittleFS.exists(CONFIG_FILE_MQTT)) LittleFS.remove(CONFIG_FILE_MQTT);
395	    if (LittleFS.exists(CONFIG_FILE_DEVICE)) LittleFS.remove(CONFIG_FILE_DEVICE);
396
397	    // 清除 WiFiManager 的配置
398	    _wm.resetSettings();
399
400	    // LED 快闪提示
401	    for (int i = 0; i < 10; i++) {
402	        digitalWrite(LED_WIFI_PIN, !digitalRead(LED_WIFI_PIN));
403	        delay(100);
404	    }
405
406	    Serial.println(F("[WiFi] 🔄 正在重启..."));
407	    delay(500);
408	    ESP.restart();
409	}
410
411	// ============================================================
412	// 按钮长按检测
413	// ============================================================
414
415	bool WiFiMgmt::_checkButtonLongPress() {
416	    bool pressed = (digitalRead(CONFIG_BUTTON_PIN) == LOW);
417
418	    if (pressed && !_buttonPressed) {
419	        // 按下
420	        _buttonPressed = true;
421	        _buttonPressTime = millis();
422	    } else if (!pressed && _buttonPressed) {
423	        // 松开
424	        _buttonPressed = false;
425	        if (millis() - _buttonPressTime >= BUTTON_LONG_PRESS_MS) {
426	            return true;  // 长按确认
427	        }
428	    }
429
430	    return false;
431	}
432
433	// ============================================================
434	// LittleFS 配置持久化
435	// ============================================================
436
437	bool WiFiMgmt::_loadConfig() {
438	    if (!LittleFS.exists(CONFIG_FILE_DEVICE)) {
439	        return false;
440	    }
441
442	    File file = LittleFS.open(CONFIG_FILE_DEVICE, "r");
443	    if (!file) return false;
444
445	    JsonDocument doc;
446	    DeserializationError err = deserializeJson(doc, file);
447	    file.close();
448
449	    if (err) {
450	        Serial.printf("[WiFi] JSON 解析失败: %s\n", err.c_str());
451	        return false;
452	    }
453
454	    strncpy(_config.mqtt_server, doc["mqtt_server"] | MQTT_DEFAULT_SERVER, sizeof(_config.mqtt_server) - 1);
455	    strncpy(_config.mqtt_port,   doc["mqtt_port"]   | MQTT_DEFAULT_PORT_STR, sizeof(_config.mqtt_port) - 1);
456	    strncpy(_config.mqtt_user,   doc["mqtt_user"]   | MQTT_DEFAULT_USER,     sizeof(_config.mqtt_user) - 1);
457	    strncpy(_config.mqtt_pass,   doc["mqtt_pass"]   | MQTT_DEFAULT_PASS,     sizeof(_config.mqtt_pass) - 1);
458	    strncpy(_config.device_id,   doc["device_id"]   | MQTT_DEFAULT_DEVICE,   sizeof(_config.device_id) - 1);
459	    strncpy(_config.env_note,    doc["env_note"]    | MQTT_DEFAULT_ENV,      sizeof(_config.env_note) - 1);
460
461	    Serial.println(F("[WiFi] ✅ 设备配置已加载"));
462	    Serial.printf("  设备名: %s, 环境: %s\n", _config.device_id, _config.env_note);
463	    return true;
464	}
465
466	bool WiFiMgmt::_saveConfig() {
467	    JsonDocument doc;
468	    doc["mqtt_server"] = _config.mqtt_server;
469	    doc["mqtt_port"]   = _config.mqtt_port;
470	    doc["mqtt_user"]   = _config.mqtt_user;
471	    doc["mqtt_pass"]   = _config.mqtt_pass;
472	    doc["device_id"]   = _config.device_id;
473	    doc["env_note"]    = _config.env_note;
474
475	    File file = LittleFS.open(CONFIG_FILE_DEVICE, "w");
476	    if (!file) {
477	        Serial.println(F("[WiFi] ❌ 无法创建配置文件"));
478	        return false;
479	    }
480
481	    if (serializeJson(doc, file) == 0) {
482	        Serial.println(F("[WiFi] ❌ 配置写入失败"));
483	        file.close();
484	        return false;
485	    }
486
487	    file.close();
488	    Serial.println(F("[WiFi] ✅ 设备配置已保存"));
489	    return true;
490	}
491
492	// ============================================================
493	// WiFiManager 保存参数回调
494	// ============================================================
495
496	// ============================================================
497	// 静态回调函数 (WiFiManager 要求 C 函数指针)
498	// ============================================================
499
500	void WiFiMgmt::_apCallback(WiFiManager* wm) {
501	    Serial.println(F("\n╔══════════════════════════════════════════╗"));
502	    Serial.println(F("║  🔧 配网模式已启动                       ║"));
503	    Serial.println(F("╠══════════════════════════════════════════╣"));
504	    Serial.printf( "║  WiFi名称: %-29s ║\n", WIFI_AP_SSID);
505	    Serial.printf( "║  密码:     %-29s ║\n", WIFI_AP_PASSWORD);
506	    Serial.println(F("╠══════════════════════════════════════════╣"));
507	    Serial.println(F("║  📱 请按以下步骤操作:                   ║"));
508	    Serial.println(F("║  1. 手机打开 WiFi 设置                  ║"));
509	    Serial.println(F("║  2. 连接上方显示的 WiFi                 ║"));
510	    Serial.println(F("║  3. 浏览器将自动弹出配网页面            ║"));
511	    Serial.println(F("║     (如未弹出, 手动访问 192.168.4.1)    ║"));
512	    Serial.println(F("╚══════════════════════════════════════════╝\n"));
513	}
514
515	void WiFiMgmt::_saveParamsCallback() {
516	    if (!_instance) return;
517
518	    Serial.println(F("[WiFi] 💾 参数已保存"));
519
520	    // 从 WiFiManager 参数中读取值
521	    strncpy(_instance->_config.mqtt_server, _instance->_param_mqtt_server->getValue(),
522	            sizeof(_instance->_config.mqtt_server) - 1);
523	    strncpy(_instance->_config.mqtt_port,   _instance->_param_mqtt_port->getValue(),
524	            sizeof(_instance->_config.mqtt_port) - 1);
525	    strncpy(_instance->_config.mqtt_user,   _instance->_param_mqtt_user->getValue(),
526	            sizeof(_instance->_config.mqtt_user) - 1);
527	    strncpy(_instance->_config.mqtt_pass,   _instance->_param_mqtt_pass->getValue(),
528	            sizeof(_instance->_config.mqtt_pass) - 1);
529	    strncpy(_instance->_config.device_id,   _instance->_param_device_id->getValue(),
530	            sizeof(_instance->_config.device_id) - 1);
531	    strncpy(_instance->_config.env_note,    _instance->_param_env_note->getValue(),
532	            sizeof(_instance->_config.env_note) - 1);
533
534	    _instance->_saveConfig();
535	    _instance->_freshProvision = true;  // 标记刚完成配网
536
537	    Serial.println(F("[WiFi] 📋 新配置:"));
538	    Serial.printf("  MQTT: %s:%s\n",
539	                  _instance->_config.mqtt_server,
540	                  _instance->_config.mqtt_port);
541	    Serial.printf("  设备: %s\n", _instance->_config.device_id);
542	    Serial.printf("  环境: %s\n", _instance->_config.env_note);
543	}
544
545	// ============================================================
546	// Getter 方法
547	// ============================================================
548
549	bool WiFiMgmt::isConnected() const {
550	    return _connected && (WiFi.status() == WL_CONNECTED);
551	}
552
553	DeviceConfig WiFiMgmt::getConfig() const {
554	    return _config;
555	}
556
557	const char* WiFiMgmt::getDeviceId() const {
558	    return _config.device_id;
559	}
560
561	const char* WiFiMgmt::getEnvNote() const {
562	    return _config.env_note;
563	}
564
565	// ============================================================
566	// LED 模式控制
567	// ============================================================
568
569	void WiFiMgmt::_setLEDMode(uint8_t mode) {
570	    switch (mode) {
571	    case 0:
572	    case 1:
573	        break;
574	    case 2:
575	        digitalWrite(LED_WIFI_PIN, HIGH);
576	        _ledState = true;
577	        break;
578	    }
579	}
580
581	// ============================================================
582	// LED 闪烁任务 (配网阻塞期间的独立线程)
583	// ============================================================
584
585	void WiFiMgmt::_startLEDTask(uint32_t interval) {
586	    _stopLEDTask();
587	    _ledTaskInterval = interval;
588	    _ledTaskRunning = true;
589	    xTaskCreatePinnedToCore(
590	        _ledBlinkTask, "LedBlink",
591	        2048, nullptr, 1, nullptr, 0
592	    );
593	}
594
595	void WiFiMgmt::_stopLEDTask() {
596	    _ledTaskRunning = false;
597	}
598
599	void WiFiMgmt::_ledBlinkTask(void* param) {
600	    while (_ledTaskRunning) {
601	        digitalWrite(LED_WIFI_PIN, HIGH);
602	        vTaskDelay(pdMS_TO_TICKS(_ledTaskInterval / 2));
603	        digitalWrite(LED_WIFI_PIN, LOW);
604	        vTaskDelay(pdMS_TO_TICKS(_ledTaskInterval / 2));
605	    }
606	    vTaskDelete(nullptr);
607	}
608

/**
 * @file wifi_manager.cpp
 * @brief WiFi 配网管理器实现
 */

#include "wifi_manager.h"

// 单例指针 + LED 任务
WiFiMgmt* WiFiMgmt::_instance = nullptr;
bool WiFiMgmt::_ledTaskRunning = false;
uint32_t WiFiMgmt::_ledTaskInterval = 300;

// ============================================================
// 构造函数 & 析构函数
// ============================================================

WiFiMgmt::WiFiMgmt()
    : _connected(false)
    , _lastCheckTime(0)
    , _buttonPressTime(0)
    , _buttonPressed(false)
    , _ledLastToggle(0)
    , _ledState(false)
    , _param_mqtt_server(nullptr)
    , _param_mqtt_port(nullptr)
    , _param_mqtt_user(nullptr)
    , _param_mqtt_pass(nullptr)
    , _param_device_id(nullptr)
    , _param_env_note(nullptr)
    , _provisionStateCb(nullptr)
{
    memset(&_config, 0, sizeof(_config));
    _instance = this;  // 设置单例
}

WiFiMgmt::~WiFiMgmt() {
    _instance = nullptr;
}

// ============================================================
// 初始化
// ============================================================

bool WiFiMgmt::begin() {
    Serial.println(F("\n========================================"));
    Serial.println(F("  WiFi 配网管理器 初始化"));
    Serial.println(F("========================================"));

    // 初始化引脚
    pinMode(CONFIG_BUTTON_PIN, INPUT_PULLUP);
    pinMode(LED_WIFI_PIN, OUTPUT);
    digitalWrite(LED_WIFI_PIN, LOW);

    // 初始化 LittleFS
    if (!LittleFS.begin(true)) {
        Serial.println(F("[WiFi] ❌ LittleFS 挂载失败!"));
        return false;
    }
    Serial.println(F("[WiFi] ✅ LittleFS 已挂载"));

    // 加载配置
    if (!_loadConfig()) {
        Serial.println(F("[WiFi] 使用默认配置"));
        // 设置默认值
        strncpy(_config.mqtt_server, MQTT_DEFAULT_SERVER,   sizeof(_config.mqtt_server) - 1);
        strncpy(_config.mqtt_port,   MQTT_DEFAULT_PORT_STR, sizeof(_config.mqtt_port) - 1);
        strncpy(_config.mqtt_user,   MQTT_DEFAULT_USER,     sizeof(_config.mqtt_user) - 1);
        strncpy(_config.mqtt_pass,   MQTT_DEFAULT_PASS,     sizeof(_config.mqtt_pass) - 1);
        strncpy(_config.device_id,   MQTT_DEFAULT_DEVICE,   sizeof(_config.device_id) - 1);
        strncpy(_config.env_note,    MQTT_DEFAULT_ENV,      sizeof(_config.env_note) - 1);
    }

    // ============================================================
    // 配置 WiFiManager (配网页面的外观和行为)
    // ============================================================

    _wm.setDebugOutput(false);  // 关闭调试输出 (避免干扰)
    _wm.setConfigPortalTimeout(WIFI_CONFIG_TIMEOUT);
    _wm.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
    _wm.setTitle("健康监测 · 设备配网");

    // --- 自定义 CSS (现代医疗主题, 移动端优先) ---
    _wm.setCustomHeadElement(
        "<meta name='theme-color' content='#2c7da0'>"
        ""
    );

    // 开启密码可见切换
    _wm.setShowPassword(true);

    // --- 首页引导文字 (显示在 WiFi 列表上方) ---
    _wm.setCustomMenuHTML(
        "
"
        "
"
        "📋 配网步骤
"
        "
"
        "1 选择 WiFi 并输入密码
"
        "2 填写 MQTT 服务器参数
"
        "3 设置设备名称和安装环境
"
        "4 点击保存，等待连接成功"
        "

"
    );

    WiFi.setAutoReconnect(true);

    // 注册保存回调
    _wm.setSaveConfigCallback(_saveParamsCallback);

    // ============================================================
    // 自定义参数 (分三组: WiFi / MQTT / 设备)
    // ============================================================

    // --- 设备信息 ---
    _param_device_id = new WiFiManagerParameter(
        "device_id", "🔖 设备编号 (网关唯一标识)",
        _config.device_id, 20);
    _param_env_note = new WiFiManagerParameter(
        "env_note", "📍 安装环境 (如: 主卧 / 客厅 / 老人房)",
        _config.env_note, 20);

    // --- MQTT 服务器 ---
    _param_mqtt_server = new WiFiManagerParameter(
        "mqtt_server", "🌐 MQTT 服务器地址",
        _config.mqtt_server, 40);
    _param_mqtt_port = new WiFiManagerParameter(
        "mqtt_port", "🔌 MQTT 端口号",
        _config.mqtt_port, 6);
    _param_mqtt_user = new WiFiManagerParameter(
        "mqtt_user", "👤 MQTT 用户名",
        _config.mqtt_user, 20);
    _param_mqtt_pass = new WiFiManagerParameter(
        "mqtt_pass", "🔑 MQTT 密码",
        _config.mqtt_pass, 20);

    // 参数按顺序注册: 设备在前, MQTT 在后
    _wm.addParameter(_param_device_id);
    _wm.addParameter(_param_env_note);
    _wm.addParameter(_param_mqtt_server);
    _wm.addParameter(_param_mqtt_port);
    _wm.addParameter(_param_mqtt_user);
    _wm.addParameter(_param_mqtt_pass);

    // 设置 AP 模式回调
    _wm.setAPCallback(_apCallback);

    // 尝试连接 WiFi
    _connected = _connectWiFi();

    if (!_connected) {
        // 如果自动连接失败, 启动配网页面
        Serial.println(F("[WiFi] 自动连接失败, 启动配网模式..."));
        _emitProvisionState("ready");  // 协议 §4.4: 进入配网模式
        _setLEDMode(1);  // 快闪 = 配网模式
        // 配网期间 loop() 阻塞，LED 靠另一个小任务单独闪 (见 _startLEDTask)
        _startLEDTask(300);  // 300ms 快闪
        _connected = _connectWiFi();  // 内部会启动配网页面 (阻塞!)
        _stopLEDTask();
    }

    if (_connected) {
        _setLEDMode(2);  // 常亮 = 已连接
        _emitProvisionState("success");  // 协议 §4.4: 配网成功
    }

    _lastCheckTime = millis();
    return _connected;
}

// ============================================================
// 主循环
// ============================================================

bool WiFiMgmt::loop() {
    // 检测按钮长按
    if (_checkButtonLongPress()) {
        Serial.println(F("[WiFi] ⚠  检测到长按, 清除配置并重启..."));
        resetSettings();
        return false;
    }

    // 检查 WiFi 状态
    if (millis() - _lastCheckTime > WIFI_RETRY_INTERVAL) {
        _lastCheckTime = millis();

        if (WiFi.status() != WL_CONNECTED) {
            _connected = false;
            _setLEDMode(0);  // 慢闪 = 未连接
            Serial.println(F("[WiFi] ⚠  WiFi 断开, 尝试重连..."));
            _connectWiFi();
        } else {
            _connected = true;
            _setLEDMode(2);  // 常亮
        }
    }

    // LED 闪烁处理
    if (!_connected) {
        uint32_t interval = (_wm.getConfigPortalActive()) ? 300 : 1000;
        if (millis() - _ledLastToggle > interval) {
            _ledLastToggle = millis();
            _ledState = !_ledState;
            digitalWrite(LED_WIFI_PIN, _ledState ? HIGH : LOW);
        }
    }

    return _connected;
}

// ============================================================
// WiFi 连接
// ============================================================

bool WiFiMgmt::_connectWiFi() {
    // 检查是否有已保存的配置
    // autoConnect 会先尝试连接, 失败后启动 AP 配网页面
    _connected = _wm.autoConnect(WIFI_AP_SSID, WIFI_AP_PASSWORD);

    if (_connected) {
        Serial.println(F("[WiFi] ✅ 已连接!"));
        Serial.printf("  WiFi名称: %s\n", WiFi.SSID().c_str());
        Serial.printf("  IP地址:   %s\n", WiFi.localIP().toString().c_str());
        Serial.printf("  MAC地址:  %s\n", WiFi.macAddress().c_str());

        // 重新加载配置 (确保获取最新值)
        // autoConnect 过程中如果有保存, 回调已写入 LittleFS
        _loadConfig();

        // 同步自定义参数 (如果配置是从 LittleFS 加载的)
        _param_mqtt_server->setValue(_config.mqtt_server, sizeof(_config.mqtt_server));
        _param_mqtt_port->setValue(_config.mqtt_port, sizeof(_config.mqtt_port));
        _param_mqtt_user->setValue(_config.mqtt_user, sizeof(_config.mqtt_user));
        _param_mqtt_pass->setValue(_config.mqtt_pass, sizeof(_config.mqtt_pass));
        _param_device_id->setValue(_config.device_id, sizeof(_config.device_id));
        _param_env_note->setValue(_config.env_note, sizeof(_config.env_note));

        Serial.printf("  设备名: %s\n", _config.device_id);
        Serial.printf("  环境:   %s\n", _config.env_note);
        Serial.printf("  MQTT服务器: %s:%s\n", _config.mqtt_server, _config.mqtt_port);

        _emitProvisionState("success");  // 协议 §4.4: 配网成功
    } else {
        _emitProvisionState("wifi_failed");  // 协议 §4.4: WiFi 连接失败
    }

    return _connected;
}

// ============================================================
// 配网状态回调注册
// ============================================================

void WiFiMgmt::onProvisionState(ProvisionStateCallback cb) {
    _provisionStateCb = cb;
}

void WiFiMgmt::_emitProvisionState(const char* state) {
    Serial.printf("[WiFi] 📡 配网状态: %s\n", state);
    if (_provisionStateCb) {
        _provisionStateCb(state);
    }
}

// ============================================================
// 配网页面
// ============================================================

void WiFiMgmt::startConfigPortal() {
    Serial.println(F("[WiFi] 🔧 手动启动配网页面..."));
    _emitProvisionState("ready");  // 协议 §4.4: 进入配网模式
    _setLEDMode(1);
    _startLEDTask(300);  // 快闪
    _wm.startConfigPortal(WIFI_AP_SSID, WIFI_AP_PASSWORD);
    _stopLEDTask();
    _connected = (WiFi.status() == WL_CONNECTED);
    if (_connected) {
        _setLEDMode(2);
        _emitProvisionState("success");  // 协议 §4.4: 配网成功
    }
}

// ============================================================
// 重置设置
// ============================================================

void WiFiMgmt::resetSettings() {
    Serial.println(F("[WiFi] 清除所有配置..."));

    // 删除 LittleFS 中的配置文件
    if (LittleFS.exists(CONFIG_FILE_WIFI)) LittleFS.remove(CONFIG_FILE_WIFI);
    if (LittleFS.exists(CONFIG_FILE_MQTT)) LittleFS.remove(CONFIG_FILE_MQTT);
    if (LittleFS.exists(CONFIG_FILE_DEVICE)) LittleFS.remove(CONFIG_FILE_DEVICE);

    // 清除 WiFiManager 的配置
    _wm.resetSettings();

    // LED 快闪提示
    for (int i = 0; i < 10; i++) {
        digitalWrite(LED_WIFI_PIN, !digitalRead(LED_WIFI_PIN));
        delay(100);
    }

    Serial.println(F("[WiFi] 🔄 正在重启..."));
    delay(500);
    ESP.restart();
}

// ============================================================
// 按钮长按检测
// ============================================================

bool WiFiMgmt::_checkButtonLongPress() {
    bool pressed = (digitalRead(CONFIG_BUTTON_PIN) == LOW);

    if (pressed && !_buttonPressed) {
        // 按下
        _buttonPressed = true;
        _buttonPressTime = millis();
    } else if (!pressed && _buttonPressed) {
        // 松开
        _buttonPressed = false;
        if (millis() - _buttonPressTime >= BUTTON_LONG_PRESS_MS) {
            return true;  // 长按确认
        }
    }

    return false;
}

// ============================================================
// LittleFS 配置持久化
// ============================================================

bool WiFiMgmt::_loadConfig() {
    if (!LittleFS.exists(CONFIG_FILE_DEVICE)) {
        return false;
    }

    File file = LittleFS.open(CONFIG_FILE_DEVICE, "r");
    if (!file) return false;

    JsonDocument doc;
    DeserializationError err = deserializeJson(doc, file);
    file.close();

    if (err) {
        Serial.printf("[WiFi] JSON 解析失败: %s\n", err.c_str());
        return false;
    }

    strncpy(_config.mqtt_server, doc["mqtt_server"] | MQTT_DEFAULT_SERVER, sizeof(_config.mqtt_server) - 1);
    strncpy(_config.mqtt_port,   doc["mqtt_port"]   | MQTT_DEFAULT_PORT_STR, sizeof(_config.mqtt_port) - 1);
    strncpy(_config.mqtt_user,   doc["mqtt_user"]   | MQTT_DEFAULT_USER,     sizeof(_config.mqtt_user) - 1);
    strncpy(_config.mqtt_pass,   doc["mqtt_pass"]   | MQTT_DEFAULT_PASS,     sizeof(_config.mqtt_pass) - 1);
    strncpy(_config.device_id,   doc["device_id"]   | MQTT_DEFAULT_DEVICE,   sizeof(_config.device_id) - 1);
    strncpy(_config.env_note,    doc["env_note"]    | MQTT_DEFAULT_ENV,      sizeof(_config.env_note) - 1);

    Serial.println(F("[WiFi] ✅ 设备配置已加载"));
    Serial.printf("  设备名: %s, 环境: %s\n", _config.device_id, _config.env_note);
    return true;
}

bool WiFiMgmt::_saveConfig() {
    JsonDocument doc;
    doc["mqtt_server"] = _config.mqtt_server;
    doc["mqtt_port"]   = _config.mqtt_port;
    doc["mqtt_user"]   = _config.mqtt_user;
    doc["mqtt_pass"]   = _config.mqtt_pass;
    doc["device_id"]   = _config.device_id;
    doc["env_note"]    = _config.env_note;

    File file = LittleFS.open(CONFIG_FILE_DEVICE, "w");
    if (!file) {
        Serial.println(F("[WiFi] ❌ 无法创建配置文件"));
        return false;
    }

    if (serializeJson(doc, file) == 0) {
        Serial.println(F("[WiFi] ❌ 配置写入失败"));
        file.close();
        return false;
    }

    file.close();
    Serial.println(F("[WiFi] ✅ 设备配置已保存"));
    return true;
}

// ============================================================
// WiFiManager 保存参数回调
// ============================================================

// ============================================================
// 静态回调函数 (WiFiManager 要求 C 函数指针)
// ============================================================

void WiFiMgmt::_apCallback(WiFiManager* wm) {
    Serial.println(F("\n╔══════════════════════════════════════════╗"));
    Serial.println(F("║  🔧 配网模式已启动                       ║"));
    Serial.println(F("╠══════════════════════════════════════════╣"));
    Serial.printf( "║  WiFi名称: %-29s ║\n", WIFI_AP_SSID);
    Serial.printf( "║  密码:     %-29s ║\n", WIFI_AP_PASSWORD);
    Serial.println(F("╠══════════════════════════════════════════╣"));
    Serial.println(F("║  📱 请按以下步骤操作:                   ║"));
    Serial.println(F("║  1. 手机打开 WiFi 设置                  ║"));
    Serial.println(F("║  2. 连接上方显示的 WiFi                 ║"));
    Serial.println(F("║  3. 浏览器将自动弹出配网页面            ║"));
    Serial.println(F("║     (如未弹出, 手动访问 192.168.4.1)    ║"));
    Serial.println(F("╚══════════════════════════════════════════╝\n"));
}

void WiFiMgmt::_saveParamsCallback() {
    if (!_instance) return;

    Serial.println(F("[WiFi] 💾 参数已保存"));

    // 从 WiFiManager 参数中读取值
    strncpy(_instance->_config.mqtt_server, _instance->_param_mqtt_server->getValue(),
            sizeof(_instance->_config.mqtt_server) - 1);
    strncpy(_instance->_config.mqtt_port,   _instance->_param_mqtt_port->getValue(),
            sizeof(_instance->_config.mqtt_port) - 1);
    strncpy(_instance->_config.mqtt_user,   _instance->_param_mqtt_user->getValue(),
            sizeof(_instance->_config.mqtt_user) - 1);
    strncpy(_instance->_config.mqtt_pass,   _instance->_param_mqtt_pass->getValue(),
            sizeof(_instance->_config.mqtt_pass) - 1);
    strncpy(_instance->_config.device_id,   _instance->_param_device_id->getValue(),
            sizeof(_instance->_config.device_id) - 1);
    strncpy(_instance->_config.env_note,    _instance->_param_env_note->getValue(),
            sizeof(_instance->_config.env_note) - 1);

    _instance->_saveConfig();
    _instance->_freshProvision = true;  // 标记刚完成配网

    Serial.println(F("[WiFi] 📋 新配置:"));
    Serial.printf("  MQTT: %s:%s\n",
                  _instance->_config.mqtt_server,
                  _instance->_config.mqtt_port);
    Serial.printf("  设备: %s\n", _instance->_config.device_id);
    Serial.printf("  环境: %s\n", _instance->_config.env_note);
}

// ============================================================
// Getter 方法
// ============================================================

bool WiFiMgmt::isConnected() const {
    return _connected && (WiFi.status() == WL_CONNECTED);
}

DeviceConfig WiFiMgmt::getConfig() const {
    return _config;
}

const char* WiFiMgmt::getDeviceId() const {
    return _config.device_id;
}

const char* WiFiMgmt::getEnvNote() const {
    return _config.env_note;
}

// ============================================================
// LED 模式控制
// ============================================================

void WiFiMgmt::_setLEDMode(uint8_t mode) {
    switch (mode) {
    case 0:
    case 1:
        break;
    case 2:
        digitalWrite(LED_WIFI_PIN, HIGH);
        _ledState = true;
        break;
    }
}

// ============================================================
// LED 闪烁任务 (配网阻塞期间的独立线程)
// ============================================================

void WiFiMgmt::_startLEDTask(uint32_t interval) {
    _stopLEDTask();
    _ledTaskInterval = interval;
    _ledTaskRunning = true;
    xTaskCreatePinnedToCore(
        _ledBlinkTask, "LedBlink",
        2048, nullptr, 1, nullptr, 0
    );
}

void WiFiMgmt::_stopLEDTask() {
    _ledTaskRunning = false;
}

void WiFiMgmt::_ledBlinkTask(void* param) {
    while (_ledTaskRunning) {
        digitalWrite(LED_WIFI_PIN, HIGH);
        vTaskDelay(pdMS_TO_TICKS(_ledTaskInterval / 2));
        digitalWrite(LED_WIFI_PIN, LOW);
        vTaskDelay(pdMS_TO_TICKS(_ledTaskInterval / 2));
    }
    vTaskDelete(nullptr);
}


Wasted call — file unchanged since your last Read. Refer to that earlier tool_result instead.


The user wants the final code for all modified files. Let me also provide the other 3 files that were changed (wifi_manager.h, main.cpp, mqtt_manager.cpp). I already have their full contents from earlier reads. Let me present all the code.


其他 3 个文件也已修改，下面是全部改动：


<ide_opened_file>The user opened the file d:\PlatformIO1\Projects\Healthv2.0\src\wifi_manager.cpp in the IDE. This may or may not be related to the current task.</ide_opened_file>

把什么给他们


The user is asking "把什么给他们" - "what should I give to them (the software team)?" They want to know what exactly to hand over. Let me clarify - they want the optimized provisioning interface code for the software team. Let me just list the changed files clearly.


就这 4 个文件，都在项目里，直接拉过去就行：

| 文件 | 改动内容 |
|------|----------|
| `src/wifi_manager.cpp` | 配网界面优化（CSS重设计、引导卡片、emoji标签、AP回调框线） |
| `include/wifi_manager.h` | 新增 `isFreshProvision()` / `clearFreshProvision()` |
| `src/main.cpp` | 配网成功 + online 一起发送（setup/主循环/手动配网三条路径） |
| `src/mqtt_manager.cpp` | 移除 `loop()` 重连中重复的 `publishOnline()` |

`wifi_manager.cpp` 刚才已展示完整代码，你要我把另外 3 个文件的最终代码也贴出来吗？


主要的html配网界面在哪里。给我、


The user is asking for the main HTML provisioning interface code. They want the actual HTML that gets served to the browser. As I explained earlier, the HTML is embedded as C++ PROGMEM strings in the WiFiManager library's `wm_strings_zh.h` file. Let me read that file and present it to them.


在 `.pio/libdeps/` 里的 WiFiManager 库源码中：


1	/**
2	 * wm_strings_zh.h
3	 * 中文语言包 for WiFiManager
4	 * 基于 wm_strings_en.h 翻译
5	 *
6	 * WiFiManager, a library for the ESP8266/Arduino platform
7	 * for configuration of WiFi credentials using a Captive Portal
8	 *
9	 * @author Creator tzapu
10	 * @author tablatronix
11	 * @version 0.0.0
12	 * @license MIT
13	 */
14
15	#ifndef _WM_STRINGS_ZH_H_
16	#define _WM_STRINGS_ZH_H_
17
18	// strings files must include a consts file!
19	#include "wm_consts_en.h" // include constants, tokens, routes
20
21	const char WM_LANGUAGE[] PROGMEM = "zh-CN"; // i18n lang code
22
23	const char HTTP_HEAD_START[]       PROGMEM = "<!DOCTYPE html>"
24	""
25	"<meta name='format-detection' content='telephone=no'>"
26	"<meta charset='UTF-8'>"
27	"<meta  name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>"
28	"<title>{v}</title>";
29
30	const char HTTP_SCRIPT[]           PROGMEM = "";
37
38	const char HTTP_HEAD_END[]         PROGMEM = "
"; // {c} = _bodyclass
39
40	const char HTTP_ROOT_MAIN[]        PROGMEM = "
{t}

{v}
";
41
42	const char * const HTTP_PORTAL_MENU[] PROGMEM = {
43	"<form action='/wifi'    method='get'><button>配置 WiFi</button></form>
\n",     // MENU_WIFI
44	"<form action='/0wifi'   method='get'><button>配置 WiFi (不扫描)</button></form>
\n", // MENU_WIFINOSCAN
45	"<form action='/info'    method='get'><button>系统信息</button></form>
\n",       // MENU_INFO
46	"<form action='/param'   method='get'><button>参数设置</button></form>
\n",       // MENU_PARAM
47	"<form action='/close'   method='get'><button>关闭页面</button></form>
\n",       // MENU_CLOSE
48	"<form action='/restart' method='get'><button>重启设备</button></form>
\n",       // MENU_RESTART
49	"<form action='/exit'    method='get'><button>退出配网</button></form>
\n",       // MENU_EXIT
50	"<form action='/erase'   method='get'><button class='D'>清除配置</button></form>
\n", // MENU_ERASE
51	"<form action='/update'  method='get'><button>固件升级</button></form>
\n",       // MENU_UPDATE
52	"

" // MENU_SEP
53	};
54
55	const char HTTP_PORTAL_OPTIONS[]   PROGMEM = "";
56	const char HTTP_ITEM_QI[]          PROGMEM = "

";
57	const char HTTP_ITEM_QP[]          PROGMEM = "
{r}%
";
58	const char HTTP_ITEM[]             PROGMEM = "
{v}{qi}{qp}
";
59
60	const char HTTP_FORM_START[]       PROGMEM = "<form method='POST' action='{v}'>";
61	const char HTTP_FORM_WIFI[]        PROGMEM = "WiFi 名称 (SSID)<input id='s' name='s' maxlength='32' autocorrect='off' autocapitalize='none' placeholder='{v}'>
WiFi 密码<input id='p' name='p' maxlength='64' type='password' placeholder='{p}'><input type='checkbox' id='showpass' onclick='f()'> 显示密码
";
62	const char HTTP_FORM_WIFI_END[]    PROGMEM = "";
63	const char HTTP_FORM_STATIC_HEAD[] PROGMEM = "

";
64	const char HTTP_FORM_END[]         PROGMEM = "

<button type='submit'>保存配置</button></form>";
65	const char HTTP_FORM_LABEL[]       PROGMEM = "{t}";
66	const char HTTP_FORM_PARAM_HEAD[]  PROGMEM = "

";
67	const char HTTP_FORM_PARAM[]       PROGMEM = "
<input id='{i}' name='{n}' maxlength='{l}' value='{v}' {c}>\n";
68
69	const char HTTP_SCAN_LINK[]        PROGMEM = "
<form action='/wifi?refresh=1' method='POST'><button name='refresh' value='1'>重新扫描</button></form>";
70	const char HTTP_SAVED[]            PROGMEM = "
正在保存配置
尝试连接 WiFi...
如果连接失败，请重新连接 AP 再试一次
";
71	const char HTTP_PARAMSAVED[]       PROGMEM = "
配置已保存

";
72	const char HTTP_END[]              PROGMEM = "
";
73	const char HTTP_ERASEBTN[]         PROGMEM = "
<form action='/erase' method='get'><button class='D'>清除 WiFi 配置</button></form>";
74	const char HTTP_UPDATEBTN[]        PROGMEM = "
<form action='/update' method='get'><button>固件升级</button></form>";
75	const char HTTP_BACKBTN[]          PROGMEM = "

<form action='/' method='get'><button>返回首页</button></form>";
76
77	const char HTTP_STATUS_ON[]        PROGMEM = "
已连接 {v}
IP 地址: {i}
";
78	const char HTTP_STATUS_OFF[]       PROGMEM = "
未连接 {v}{r}
";
79	const char HTTP_STATUS_OFFPW[]     PROGMEM = "
WiFi 密码错误";
80	const char HTTP_STATUS_OFFNOAP[]   PROGMEM = "
未找到该 WiFi";
81	const char HTTP_STATUS_OFFFAIL[]   PROGMEM = "
无法连接";
82	const char HTTP_STATUS_NONE[]      PROGMEM = "
未设置 WiFi
";
83	const char HTTP_BR[]               PROGMEM = "
";
84
85	const char HTTP_STYLE[]            PROGMEM = "";
109
110	#ifndef WM_NOHELP
111	const char HTTP_HELP[]             PROGMEM =
112	 "

可用页面

"
113	 "
"
114	 "

页面	功能

"
115	 "
/	"
116	 "主菜单页面
"
117	 "
/wifi	"
118	 "显示 WiFi 扫描结果并配置连接 (/0wifi 不扫描)
"
119	 "
/wifisave	"
120	 "保存 WiFi 配置并连接设备
"
121	 "
/param	"
122	 "参数设置页面
"
123	 "
/info	"
124	 "系统信息页面
"
125	 "
/u	"
126	 "固件 OTA 升级
"
127	 "
/close	"
128	 "关闭弹出窗口，配网服务保持运行
"
129	 "
/exit	"
130	 "退出配网模式，配网服务将关闭
"
131	 "
/restart	"
132	 "重启设备
"
133	 "
/erase	"
134	 "清除 WiFi 配置并重启。清除后设备不会自动连接，需重新配网
"
135	 "
"
136	 "
GitHub https://github.com/tzapu/WiFiManager.";
137	#else
138	const char HTTP_HELP[]             PROGMEM = "";
139	#endif
140
141	const char HTTP_UPDATE[] PROGMEM = "上传新固件
<form method='POST' action='u' enctype='multipart/form-data' onchange=\"(function(el){document.getElementById('uploadbin').style.display = el.value=='' ? 'none' : 'initial';})(this)\"><input type='file' name='update' accept='.bin,application/octet-stream'><button id='uploadbin' type='submit' class='h D'>开始升级</button></form>* 如果在配网页面内无法使用，请在浏览器中打开 http://192.168.4.1";
142	const char HTTP_UPDATE_FAIL[] PROGMEM = "
升级失败!
请重启设备后重试
";
143	const char HTTP_UPDATE_SUCCESS[] PROGMEM = "
升级成功!
设备正在重启...
";
144
145	#ifdef WM_JSTEST
146	const char HTTP_JS[] PROGMEM =
147	"";
163	#endif
164
165	// Info html
166	#ifdef ESP32
167		const char HTTP_INFO_esphead[]    PROGMEM = "
ESP32 信息

";
168		const char HTTP_INFO_chiprev[]    PROGMEM = "
芯片版本

{1}
";
169	  	const char HTTP_INFO_lastreset[]  PROGMEM = "
上次复位原因

CPU0: {1}
CPU1: {2}
";
170	  	const char HTTP_INFO_aphost[]     PROGMEM = "
AP 主机名

{1}
";
171	    const char HTTP_INFO_psrsize[]    PROGMEM = "
PSRAM 大小

{1} 字节
";
172		const char HTTP_INFO_temp[]       PROGMEM = "
芯片温度

{1}°C / {2}°F
";
173	    const char HTTP_INFO_hall[]       PROGMEM = "
霍尔传感器

{1}
";
174	#else
175		const char HTTP_INFO_esphead[]    PROGMEM = "
ESP8266 信息

";
176		const char HTTP_INFO_fchipid[]    PROGMEM = "
Flash 芯片 ID

{1}
";
177		const char HTTP_INFO_corever[]    PROGMEM = "
Core 版本

{1}
";
178		const char HTTP_INFO_bootver[]    PROGMEM = "
Boot 版本

{1}
";
179		const char HTTP_INFO_lastreset[]  PROGMEM = "
上次复位原因

{1}
";
180		const char HTTP_INFO_flashsize[]  PROGMEM = "
实际 Flash 大小

{1} 字节
";
181	#endif
182
183	const char HTTP_INFO_memsmeter[]  PROGMEM = "
<progress value='{1}' max='{2}'></progress>
";
184	const char HTTP_INFO_memsketch[]  PROGMEM = "
内存 - 程序大小

已用 / 总计
{1} / {2} 字节";
185	const char HTTP_INFO_freeheap[]   PROGMEM = "
内存 - 可用堆

{1} 字节
";
186	const char HTTP_INFO_wifihead[]   PROGMEM = "

WiFi 信息

";
187	const char HTTP_INFO_uptime[]     PROGMEM = "
运行时间

{1} 分 {2} 秒
";
188	const char HTTP_INFO_chipid[]     PROGMEM = "
芯片 ID

{1}
";
189	const char HTTP_INFO_idesize[]    PROGMEM = "
Flash 大小

{1} 字节
";
190	const char HTTP_INFO_sdkver[]     PROGMEM = "
SDK 版本

{1}
";
191	const char HTTP_INFO_cpufreq[]    PROGMEM = "
CPU 频率

{1}MHz
";
192	const char HTTP_INFO_apip[]       PROGMEM = "
AP IP 地址

{1}
";
193	const char HTTP_INFO_apmac[]      PROGMEM = "
AP MAC 地址

{1}
";
194	const char HTTP_INFO_apssid[]     PROGMEM = "
AP SSID

{1}
";
195	const char HTTP_INFO_apbssid[]    PROGMEM = "
BSSID

{1}
";
196	const char HTTP_INFO_stassid[]    PROGMEM = "
Station SSID

{1}
";
197	const char HTTP_INFO_staip[]      PROGMEM = "
Station IP

{1}
";
198	const char HTTP_INFO_stagw[]      PROGMEM = "
网关地址

{1}
";
199	const char HTTP_INFO_stasub[]     PROGMEM = "
子网掩码

{1}
";
200	const char HTTP_INFO_dnss[]       PROGMEM = "
DNS 服务器

{1}
";
201	const char HTTP_INFO_host[]       PROGMEM = "
主机名

{1}
";
202	const char HTTP_INFO_stamac[]     PROGMEM = "
Station MAC

{1}
";
203	const char HTTP_INFO_conx[]       PROGMEM = "
连接状态

{1}
";
204	const char HTTP_INFO_autoconx[]   PROGMEM = "
自动连接

{1}
";
205
206	const char HTTP_INFO_aboutver[]     PROGMEM = "
WiFiManager 版本

{1}
";
207	const char HTTP_INFO_aboutarduino[] PROGMEM = "
Arduino 版本

{1}
";
208	const char HTTP_INFO_aboutsdk[]     PROGMEM = "
ESP-SDK/IDF 版本

{1}
";
209	const char HTTP_INFO_aboutdate[]    PROGMEM = "
构建日期

{1}
";
210
211	// ============================================================
212	// 文本常量
213	// ============================================================
214	const char S_brand[]              PROGMEM = "健康监测配网";
215	const char S_debugPrefix[]        PROGMEM = "*wm:";
216	const char S_y[]                  PROGMEM = "是";
217	const char S_n[]                  PROGMEM = "否";
218	const char S_enable[]             PROGMEM = "已启用";
219	const char S_disable[]            PROGMEM = "已禁用";
220	const char S_GET[]                PROGMEM = "GET";
221	const char S_POST[]               PROGMEM = "POST";
222	const char S_NA[]                 PROGMEM = "未知";
223	const char S_passph[]             PROGMEM = "********";
224	const char S_titlewifisaved[]     PROGMEM = "WiFi 已保存";
225	const char S_titlewifisettings[]  PROGMEM = "设置已保存";
226	const char S_titlewifi[]          PROGMEM = "配置 WiFi";
227	const char S_titleinfo[]          PROGMEM = "系统信息";
228	const char S_titleparam[]         PROGMEM = "参数设置";
229	const char S_titleparamsaved[]    PROGMEM = "设置已保存";
230	const char S_titleexit[]          PROGMEM = "退出配网";
231	const char S_titlereset[]         PROGMEM = "重置";
232	const char S_titleerase[]         PROGMEM = "清除配置";
233	const char S_titleclose[]         PROGMEM = "关闭页面";
234	const char S_options[]            PROGMEM = "选项";
235	const char S_nonetworks[]         PROGMEM = "未发现 WiFi 网络，请点击刷新重新扫描。";
236	const char S_staticip[]           PROGMEM = "静态 IP";
237	const char S_staticgw[]           PROGMEM = "静态网关";
238	const char S_staticdns[]          PROGMEM = "静态 DNS";
239	const char S_subnet[]             PROGMEM = "子网掩码";
240	const char S_exiting[]            PROGMEM = "正在退出配网模式";
241	const char S_resetting[]          PROGMEM = "设备将在几秒后重启";
242	const char S_closing[]            PROGMEM = "可以关闭此页面，配网服务将继续运行";
243	const char S_error[]              PROGMEM = "发生错误";
244	const char S_notfound[]           PROGMEM = "页面未找到\n\n";
245	const char S_uri[]                PROGMEM = "URI: ";
246	const char S_method[]             PROGMEM = "\n请求方法: ";
247	const char S_args[]               PROGMEM = "\n参数: ";
248	const char S_parampre[]           PROGMEM = "param_";
249
250	// debug strings
251	const char D_HR[]                 PROGMEM = "--------------------";
252
253	// softap ssid default prefix
254	#ifdef ESP8266
255	    const char S_ssidpre[]        PROGMEM = "ESP";
256	#elif defined(ESP32)
257	    const char S_ssidpre[]        PROGMEM = "ESP32";
258	#else
259	    const char S_ssidpre[]        PROGMEM = "WM";
260	#endif
261
262	#endif
263

/**
 * wm_strings_zh.h
 * 中文语言包 for WiFiManager
 * 基于 wm_strings_en.h 翻译
 *
 * WiFiManager, a library for the ESP8266/Arduino platform
 * for configuration of WiFi credentials using a Captive Portal
 *
 * @author Creator tzapu
 * @author tablatronix
 * @version 0.0.0
 * @license MIT
 */

#ifndef _WM_STRINGS_ZH_H_
#define _WM_STRINGS_ZH_H_

// strings files must include a consts file!
#include "wm_consts_en.h" // include constants, tokens, routes

const char WM_LANGUAGE[] PROGMEM = "zh-CN"; // i18n lang code

const char HTTP_HEAD_START[]       PROGMEM = "<!DOCTYPE html>"
""
"<meta name='format-detection' content='telephone=no'>"
"<meta charset='UTF-8'>"
"<meta  name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>"
"<title>{v}</title>";

const char HTTP_SCRIPT[]           PROGMEM = "";

const char HTTP_HEAD_END[]         PROGMEM = "
"; // {c} = _bodyclass

const char HTTP_ROOT_MAIN[]        PROGMEM = "
{t}

{v}
";

const char * const HTTP_PORTAL_MENU[] PROGMEM = {
"<form action='/wifi'    method='get'><button>配置 WiFi</button></form>
\n",     // MENU_WIFI
"<form action='/0wifi'   method='get'><button>配置 WiFi (不扫描)</button></form>
\n", // MENU_WIFINOSCAN
"<form action='/info'    method='get'><button>系统信息</button></form>
\n",       // MENU_INFO
"<form action='/param'   method='get'><button>参数设置</button></form>
\n",       // MENU_PARAM
"<form action='/close'   method='get'><button>关闭页面</button></form>
\n",       // MENU_CLOSE
"<form action='/restart' method='get'><button>重启设备</button></form>
\n",       // MENU_RESTART
"<form action='/exit'    method='get'><button>退出配网</button></form>
\n",       // MENU_EXIT
"<form action='/erase'   method='get'><button class='D'>清除配置</button></form>
\n", // MENU_ERASE
"<form action='/update'  method='get'><button>固件升级</button></form>
\n",       // MENU_UPDATE
"

" // MENU_SEP
};

const char HTTP_PORTAL_OPTIONS[]   PROGMEM = "";
const char HTTP_ITEM_QI[]          PROGMEM = "

";
const char HTTP_ITEM_QP[]          PROGMEM = "
{r}%
";
const char HTTP_ITEM[]             PROGMEM = "
{v}{qi}{qp}
";

const char HTTP_FORM_START[]       PROGMEM = "<form method='POST' action='{v}'>";
const char HTTP_FORM_WIFI[]        PROGMEM = "WiFi 名称 (SSID)<input id='s' name='s' maxlength='32' autocorrect='off' autocapitalize='none' placeholder='{v}'>
WiFi 密码<input id='p' name='p' maxlength='64' type='password' placeholder='{p}'><input type='checkbox' id='showpass' onclick='f()'> 显示密码
";
const char HTTP_FORM_WIFI_END[]    PROGMEM = "";
const char HTTP_FORM_STATIC_HEAD[] PROGMEM = "

";
const char HTTP_FORM_END[]         PROGMEM = "

<button type='submit'>保存配置</button></form>";
const char HTTP_FORM_LABEL[]       PROGMEM = "{t}";
const char HTTP_FORM_PARAM_HEAD[]  PROGMEM = "

";
const char HTTP_FORM_PARAM[]       PROGMEM = "
<input id='{i}' name='{n}' maxlength='{l}' value='{v}' {c}>\n";

const char HTTP_SCAN_LINK[]        PROGMEM = "
<form action='/wifi?refresh=1' method='POST'><button name='refresh' value='1'>重新扫描</button></form>";
const char HTTP_SAVED[]            PROGMEM = "
正在保存配置
尝试连接 WiFi...
如果连接失败，请重新连接 AP 再试一次
";
const char HTTP_PARAMSAVED[]       PROGMEM = "
配置已保存

";
const char HTTP_END[]              PROGMEM = "
";
const char HTTP_ERASEBTN[]         PROGMEM = "
<form action='/erase' method='get'><button class='D'>清除 WiFi 配置</button></form>";
const char HTTP_UPDATEBTN[]        PROGMEM = "
<form action='/update' method='get'><button>固件升级</button></form>";
const char HTTP_BACKBTN[]          PROGMEM = "

<form action='/' method='get'><button>返回首页</button></form>";

const char HTTP_STATUS_ON[]        PROGMEM = "
已连接 {v}
IP 地址: {i}
";
const char HTTP_STATUS_OFF[]       PROGMEM = "
未连接 {v}{r}
";
const char HTTP_STATUS_OFFPW[]     PROGMEM = "
WiFi 密码错误";
const char HTTP_STATUS_OFFNOAP[]   PROGMEM = "
未找到该 WiFi";
const char HTTP_STATUS_OFFFAIL[]   PROGMEM = "
无法连接";
const char HTTP_STATUS_NONE[]      PROGMEM = "
未设置 WiFi
";
const char HTTP_BR[]               PROGMEM = "
";

const char HTTP_STYLE[]            PROGMEM = "";

#ifndef WM_NOHELP
const char HTTP_HELP[]             PROGMEM =
 "

可用页面

"
 "
"
 "

页面	功能

"
 "
/	"
 "主菜单页面
"
 "
/wifi	"
 "显示 WiFi 扫描结果并配置连接 (/0wifi 不扫描)
"
 "
/wifisave	"
 "保存 WiFi 配置并连接设备
"
 "
/param	"
 "参数设置页面
"
 "
/info	"
 "系统信息页面
"
 "
/u	"
 "固件 OTA 升级
"
 "
/close	"
 "关闭弹出窗口，配网服务保持运行
"
 "
/exit	"
 "退出配网模式，配网服务将关闭
"
 "
/restart	"
 "重启设备
"
 "
/erase	"
 "清除 WiFi 配置并重启。清除后设备不会自动连接，需重新配网
"
 "
"
 "
GitHub https://github.com/tzapu/WiFiManager.";
#else
const char HTTP_HELP[]             PROGMEM = "";
#endif

const char HTTP_UPDATE[] PROGMEM = "上传新固件
<form method='POST' action='u' enctype='multipart/form-data' onchange=\"(function(el){document.getElementById('uploadbin').style.display = el.value=='' ? 'none' : 'initial';})(this)\"><input type='file' name='update' accept='.bin,application/octet-stream'><button id='uploadbin' type='submit' class='h D'>开始升级</button></form>* 如果在配网页面内无法使用，请在浏览器中打开 http://192.168.4.1";
const char HTTP_UPDATE_FAIL[] PROGMEM = "
升级失败!
请重启设备后重试
";
const char HTTP_UPDATE_SUCCESS[] PROGMEM = "
升级成功!
设备正在重启...
";

#ifdef WM_JSTEST
const char HTTP_JS[] PROGMEM =
"";
#endif

// Info html
#ifdef ESP32
	const char HTTP_INFO_esphead[]    PROGMEM = "
ESP32 信息

";
	const char HTTP_INFO_chiprev[]    PROGMEM = "
芯片版本

{1}
";
  	const char HTTP_INFO_lastreset[]  PROGMEM = "
上次复位原因

CPU0: {1}
CPU1: {2}
";
  	const char HTTP_INFO_aphost[]     PROGMEM = "
AP 主机名

{1}
";
    const char HTTP_INFO_psrsize[]    PROGMEM = "
PSRAM 大小

{1} 字节
";
	const char HTTP_INFO_temp[]       PROGMEM = "
芯片温度

{1}°C / {2}°F
";
    const char HTTP_INFO_hall[]       PROGMEM = "
霍尔传感器

{1}
";
#else
	const char HTTP_INFO_esphead[]    PROGMEM = "
ESP8266 信息

";
	const char HTTP_INFO_fchipid[]    PROGMEM = "
Flash 芯片 ID

{1}
";
	const char HTTP_INFO_corever[]    PROGMEM = "
Core 版本

{1}
";
	const char HTTP_INFO_bootver[]    PROGMEM = "
Boot 版本

{1}
";
	const char HTTP_INFO_lastreset[]  PROGMEM = "
上次复位原因

{1}
";
	const char HTTP_INFO_flashsize[]  PROGMEM = "
实际 Flash 大小

{1} 字节
";
#endif

const char HTTP_INFO_memsmeter[]  PROGMEM = "
<progress value='{1}' max='{2}'></progress>
";
const char HTTP_INFO_memsketch[]  PROGMEM = "
内存 - 程序大小

已用 / 总计
{1} / {2} 字节";
const char HTTP_INFO_freeheap[]   PROGMEM = "
内存 - 可用堆

{1} 字节
";
const char HTTP_INFO_wifihead[]   PROGMEM = "

WiFi 信息

";
const char HTTP_INFO_uptime[]     PROGMEM = "
运行时间

{1} 分 {2} 秒
";
const char HTTP_INFO_chipid[]     PROGMEM = "
芯片 ID

{1}
";
const char HTTP_INFO_idesize[]    PROGMEM = "
Flash 大小

{1} 字节
";
const char HTTP_INFO_sdkver[]     PROGMEM = "
SDK 版本

{1}
";
const char HTTP_INFO_cpufreq[]    PROGMEM = "
CPU 频率

{1}MHz
";
const char HTTP_INFO_apip[]       PROGMEM = "
AP IP 地址

{1}
";
const char HTTP_INFO_apmac[]      PROGMEM = "
AP MAC 地址

{1}
";
const char HTTP_INFO_apssid[]     PROGMEM = "
AP SSID

{1}
";
const char HTTP_INFO_apbssid[]    PROGMEM = "
BSSID

{1}
";
const char HTTP_INFO_stassid[]    PROGMEM = "
Station SSID

{1}
";
const char HTTP_INFO_staip[]      PROGMEM = "
Station IP

{1}
";
const char HTTP_INFO_stagw[]      PROGMEM = "
网关地址

{1}
";
const char HTTP_INFO_stasub[]     PROGMEM = "
子网掩码

{1}
";
const char HTTP_INFO_dnss[]       PROGMEM = "
DNS 服务器

{1}
";
const char HTTP_INFO_host[]       PROGMEM = "
主机名

{1}
";
const char HTTP_INFO_stamac[]     PROGMEM = "
Station MAC

{1}
";
const char HTTP_INFO_conx[]       PROGMEM = "
连接状态

{1}
";
const char HTTP_INFO_autoconx[]   PROGMEM = "
自动连接

{1}
";

const char HTTP_INFO_aboutver[]     PROGMEM = "
WiFiManager 版本

{1}
";
const char HTTP_INFO_aboutarduino[] PROGMEM = "
Arduino 版本

{1}
";
const char HTTP_INFO_aboutsdk[]     PROGMEM = "
ESP-SDK/IDF 版本

{1}
";
const char HTTP_INFO_aboutdate[]    PROGMEM = "
构建日期

{1}
";

// ============================================================
// 文本常量
// ============================================================
const char S_brand[]              PROGMEM = "健康监测配网";
const char S_debugPrefix[]        PROGMEM = "*wm:";
const char S_y[]                  PROGMEM = "是";
const char S_n[]                  PROGMEM = "否";
const char S_enable[]             PROGMEM = "已启用";
const char S_disable[]            PROGMEM = "已禁用";
const char S_GET[]                PROGMEM = "GET";
const char S_POST[]               PROGMEM = "POST";
const char S_NA[]                 PROGMEM = "未知";
const char S_passph[]             PROGMEM = "********";
const char S_titlewifisaved[]     PROGMEM = "WiFi 已保存";
const char S_titlewifisettings[]  PROGMEM = "设置已保存";
const char S_titlewifi[]          PROGMEM = "配置 WiFi";
const char S_titleinfo[]          PROGMEM = "系统信息";
const char S_titleparam[]         PROGMEM = "参数设置";
const char S_titleparamsaved[]    PROGMEM = "设置已保存";
const char S_titleexit[]          PROGMEM = "退出配网";
const char S_titlereset[]         PROGMEM = "重置";
const char S_titleerase[]         PROGMEM = "清除配置";
const char S_titleclose[]         PROGMEM = "关闭页面";
const char S_options[]            PROGMEM = "选项";
const char S_nonetworks[]         PROGMEM = "未发现 WiFi 网络，请点击刷新重新扫描。";
const char S_staticip[]           PROGMEM = "静态 IP";
const char S_staticgw[]           PROGMEM = "静态网关";
const char S_staticdns[]          PROGMEM = "静态 DNS";
const char S_subnet[]             PROGMEM = "子网掩码";
const char S_exiting[]            PROGMEM = "正在退出配网模式";
const char S_resetting[]          PROGMEM = "设备将在几秒后重启";
const char S_closing[]            PROGMEM = "可以关闭此页面，配网服务将继续运行";
const char S_error[]              PROGMEM = "发生错误";
const char S_notfound[]           PROGMEM = "页面未找到\n\n";
const char S_uri[]                PROGMEM = "URI: ";
const char S_method[]             PROGMEM = "\n请求方法: ";
const char S_args[]               PROGMEM = "\n参数: ";
const char S_parampre[]           PROGMEM = "param_";

// debug strings
const char D_HR[]                 PROGMEM = "--------------------";

// softap ssid default prefix
#ifdef ESP8266
    const char S_ssidpre[]        PROGMEM = "ESP";
#elif defined(ESP32)
    const char S_ssidpre[]        PROGMEM = "ESP32";
#else
    const char S_ssidpre[]        PROGMEM = "WM";
#endif

#endif


The user wants just the HTML file. The main HTML template strings are all in this one file: `wm_strings_zh.h`. This is the Chinese language pack that defines all the HTML fragments that get assembled into the final page. Let me also check `wm_consts_en.h` for the route definitions, and then look at `WiFiManager.cpp` to see how these fragments are assembled into complete pages.

Actually, the user just asked for the main HTML provisioning interface. The key file is `wm_strings_zh.h` which I just showed them. That's the answer.

But I should also point them to how the fragments are assembled in `WiFiManager.cpp` - specifically the `handleRoot()`, `handleWifi()`, and `handleWifiSave()` functions which piece together the HTML strings.

Let me just give them the file path - they already have it now.


就是这个文件：

```
.pio/libdeps/esp32-s3-devkitc-1/WiFiManager/wm_strings_zh.h
```

所有的 HTML 页面都由这些 `PROGMEM` 字符串片段拼装而成，按路由分发：

| 路由 | 拼接逻辑（在 WiFiManager.cpp） | 用到的片段 |
|------|-------------------------------|-----------|
| `/` | `handleRoot()` | `HTTP_HEAD_START` + `HTTP_STYLE` + `HTTP_ROOT_MAIN` + 自定义参数菜单 + `HTTP_END` |
| `/wifi` | `handleWifi(true)` | 同上 + `HTTP_FORM_WIFI` + WiFi 扫描列表 + `HTTP_FORM_PARAM` |
| `/wifisave` | `handleWifiSave()` | `HTTP_SAVED` |
| `/param` | 仅参数表单 | `HTTP_FORM_PARAM` + 自定义参数 |

拼接代码在 [WiFiManager.cpp](.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp) 约第 1277-1807 行。要我把拼装逻辑部分也截出来吗？


<ide_selection>The user selected the lines 1 to 608 from d:\PlatformIO1\Projects\Healthv2.0\src\wifi_manager.cpp:
/**
 * @file wifi_manager.cpp
 * @brief WiFi 配网管理器实现
 */

#include "wifi_manager.h"

// 单例指针 + LED 任务
WiFiMgmt* WiFiMgmt::_instance = nullptr;
bool WiFiMgmt::_ledTaskRunning = false;
uint32_t WiFiMgmt::_ledTaskInterval = 300;

// ============================================================
// 构造函数 & 析构函数
// ============================================================

WiFiMgmt::WiFiMgmt()
    : _connected(false)
    , _lastCheckTime(0)
    , _buttonPressTime(0)
    , _buttonPressed(false)
    , _ledLastToggle(0)
    , _ledState(false)
    , _param_mqtt_server(nullptr)
    , _param_mqtt_port(nullptr)
    , _param_mqtt_user(nullptr)
    , _param_mqtt_pass(nullptr)
    , _param_device_id(nullptr)
    , _param_env_note(nullptr)
    , _provisionStateCb(nullptr)
{
    memset(&_config, 0, sizeof(_config));
    _instance = this;  // 设置单例
}

WiFiMgmt::~WiFiMgmt() {
    _instance = nullptr;
}

// ============================================================
// 初始化
// ============================================================

bool WiFiMgmt::begin() {
    Serial.println(F("\n========================================"));
    Serial.println(F("  WiFi 配网管理器 初始化"));
    Serial.println(F("========================================"));

    // 初始化引脚
    pinMode(CONFIG_BUTTON_PIN, INPUT_PULLUP);
    pinMode(LED_WIFI_PIN, OUTPUT);
    digitalWrite(LED_WIFI_PIN, LOW);

    // 初始化 LittleFS
    if (!LittleFS.begin(true)) {
        Serial.println(F("[WiFi] ❌ LittleFS 挂载失败!"));
        return false;
    }
    Serial.println(F("[WiFi] ✅ LittleFS 已挂载"));

    // 加载配置
    if (!_loadConfig()) {
        Serial.println(F("[WiFi] 使用默认配置"));
        // 设置默认值
        strncpy(_config.mqtt_server, MQTT_DEFAULT_SERVER,   sizeof(_config.mqtt_server) - 1);
        strncpy(_config.mqtt_port,   MQTT_DEFAULT_PORT_STR, sizeof(_config.mqtt_port) - 1);
        strncpy(_config.mqtt_user,   MQTT_DEFAULT_USER,     sizeof(_config.mqtt_user) - 1);
        strncpy(_config.mqtt_pass,   MQTT_DEFAULT_PASS,     sizeof(_config.mqtt_pass) - 1);
        strncpy(_config.device_id,   MQTT_DEFAULT_DEVICE,   sizeof(_config.device_id) - 1);
        strncpy(_config.env_note,    MQTT_DEFAULT_ENV,      sizeof(_config.env_note) - 1);
    }

    // ============================================================
    // 配置 WiFiManager (配网页面的外观和行为)
    // ============================================================

    _wm.setDebugOutput(false);  // 关闭调试输出 (避免干扰)
    _wm.setConfigPortalTimeout(WIFI_CONFIG_TIMEOUT);
    _wm.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
    _wm.setTitle("健康监测 · 设备配网");

    // --- 自定义 CSS (现代医疗主题, 移动端优先) ---
    _wm.setCustomHeadElement(
        "<meta name='theme-color' content='#2c7da0'>"
        ""
    );

    // 开启密码可见切换
    _wm.setShowPassword(true);

    // --- 首页引导文字 (显示在 WiFi 列表上方) ---
    _wm.setCustomMenuHTML(
        "
"
        "
"
        "📋 配网步骤
"
        "
"
        "1 选择 WiFi 并输入密码
"
        "2 填写 MQTT 服务器参数
"
        "3 设置设备名称和安装环境
"
        "4 点击保存，等待连接成功"
        "

"
    );

    WiFi.setAutoReconnect(true);

    // 注册保存回调
    _wm.setSaveConfigCallback(_saveParamsCallback);

    // ============================================================
    // 自定义参数 (分三组: WiFi / MQTT / 设备)
    // ============================================================

    // --- 设备信息 ---
    _param_device_id = new WiFiManagerParameter(
        "device_id", "🔖 设备编号 (网关唯一标识)",
        _config.device_id, 20);
    _param_env_note = new WiFiManagerParameter(
        "env_note", "📍 安装环境 (如: 主卧 / 客厅 / 老人房)",
        _config.env_note, 20);

    // --- MQTT 服务器 ---
    _param_mqtt_server = new WiFiManagerParameter(
        "mqtt_server", "🌐 MQTT 服务器地址",
        _config.mqtt_server, 40);
    _param_mqtt_port = new WiFiManagerParameter(
        "mqtt_port", "🔌 MQTT 端口号",
        _config.mqtt_port, 6);
    _param_mqtt_user = new WiFiManagerParameter(
        "mqtt_user", "👤 MQTT 用户名",
        _config.mqtt_user, 20);
    _param_mqtt_pass = new WiFiManagerParameter(
        "mqtt_pass", "🔑 MQTT 密码",
        _config.mqtt_pass, 20);

    // 参数按顺序注册: 设备在前, MQTT 在后
    _wm.addParameter(_param_device_id);
    _wm.addParameter(_param_env_note);
    _wm.addParameter(_param_mqtt_server);
    _wm.addParameter(_param_mqtt_port);
    _wm.addParameter(_param_mqtt_user);
    _wm.addParameter(_param_mqtt_pass);

    // 设置 AP 模式回调
    _wm.setAPCallback(_apCallback);

    // 尝试连接 WiFi
    _connected = _connectWiFi();

    if (!_connected) {
        // 如果自动连接失败, 启动配网页面
        Serial.println(F("[WiFi] 自动连接失败, 启动配网模式..."));
        _emitProvisionState("ready");  // 协议 §4.4: 进入配网模式
        _setLEDMode(1);  // 快闪 = 配网模式
        // 配网期间 loop() 阻塞，LED 靠另一个小任务单独闪 (见 _startLEDTask)
        _startLEDTask(300);  // 300ms 快闪
        _connected = _connectWiFi();  // 内部会启动配网页面 (阻塞!)
        _stopLEDTask();
    }

    if (_connected) {
        _setLEDMode(2);  // 常亮 = 已连接
        _emitProvisionState("success");  // 协议 §4.4: 配网成功
    }

    _lastCheckTime = millis();
    return _connected;
}

// ============================================================
// 主循环
// ============================================================

bool WiFiMgmt::loop() {
    // 检测按钮长按
    if (_checkButtonLongPress()) {
        Serial.println(F("[WiFi] ⚠  检测到长按, 清除配置并重启..."));
        resetSettings();
        return false;
    }

    // 检查 WiFi 状态
    if (millis() - _lastCheckTime > WIFI_RETRY_INTERVAL) {
        _lastCheckTime = millis();

        if (WiFi.status() != WL_CONNECTED) {
            _connected = false;
            _setLEDMode(0);  // 慢闪 = 未连接
            Serial.println(F("[WiFi] ⚠  WiFi 断开, 尝试重连..."));
            _connectWiFi();
        } else {
            _connected = true;
            _setLEDMode(2);  // 常亮
        }
    }

    // LED 闪烁处理
    if (!_connected) {
        uint32_t interval = (_wm.getConfigPortalActive()) ? 300 : 1000;
        if (millis() - _ledLastToggle > interval) {
            _ledLastToggle = millis();
            _ledState = !_ledState;
            digitalWrite(LED_WIFI_PIN, _ledState ? HIGH : LOW);
        }
    }

    return _connected;
}

// ============================================================
// WiFi 连接
// ============================================================

bool WiFiMgmt::_connectWiFi() {
    // 检查是否有已保存的配置
    // autoConnect 会先尝试连接, 失败后启动 AP 配网页面
    _connected = _wm.autoConnect(WIFI_AP_SSID, WIFI_AP_PASSWORD);

    if (_connected) {
        Serial.println(F("[WiFi] ✅ 已连接!"));
        Serial.printf("  WiFi名称: %s\n", WiFi.SSID().c_str());
        Serial.printf("  IP地址:   %s\n", WiFi.localIP().toString().c_str());
        Serial.printf("  MAC地址:  %s\n", WiFi.macAddress().c_str());

        // 重新加载配置 (确保获取最新值)
        // autoConnect 过程中如果有保存, 回调已写入 LittleFS
        _loadConfig();

        // 同步自定义参数 (如果配置是从 LittleFS 加载的)
        _param_mqtt_server->setValue(_config.mqtt_server, sizeof(_config.mqtt_server));
        _param_mqtt_port->setValue(_config.mqtt_port, sizeof(_config.mqtt_port));
        _param_mqtt_user->setValue(_config.mqtt_user, sizeof(_config.mqtt_user));
        _param_mqtt_pass->setValue(_config.mqtt_pass, sizeof(_config.mqtt_pass));
        _param_device_id->setValue(_config.device_id, sizeof(_config.device_id));
        _param_env_note->setValue(_config.env_note, sizeof(_config.env_note));

        Serial.printf("  设备名: %s\n", _config.device_id);
        Serial.printf("  环境:   %s\n", _config.env_note);
        Serial.printf("  MQTT服务器: %s:%s\n", _config.mqtt_server, _config.mqtt_port);

        _emitProvisionState("success");  // 协议 §4.4: 配网成功
    } else {
        _emitProvisionState("wifi_failed");  // 协议 §4.4: WiFi 连接失败
    }

    return _connected;
}

// ============================================================
// 配网状态回调注册
// ============================================================

void WiFiMgmt::onProvisionState(ProvisionStateCallback cb) {
    _provisionStateCb = cb;
}

void WiFiMgmt::_emitProvisionState(const char* state) {
    Serial.printf("[WiFi] 📡 配网状态: %s\n", state);
    if (_provisionStateCb) {
        _provisionStateCb(state);
    }
}

// ============================================================
// 配网页面
// ============================================================

void WiFiMgmt::startConfigPortal() {
    Serial.println(F("[WiFi] 🔧 手动启动配网页面..."));
    _emitProvisionState("ready");  // 协议 §4.4: 进入配网模式
    _setLEDMode(1);
    _startLEDTask(300);  // 快闪
    _wm.startConfigPortal(WIFI_AP_SSID, WIFI_AP_PASSWORD);
    _stopLEDTask();
    _connected = (WiFi.status() == WL_CONNECTED);
    if (_connected) {
        _setLEDMode(2);
        _emitProvisionState("success");  // 协议 §4.4: 配网成功
    }
}

// ============================================================
// 重置设置
// ============================================================

void WiFiMgmt::resetSettings() {
    Serial.println(F("[WiFi] 清除所有配置..."));

    // 删除 LittleFS 中的配置文件
    if (LittleFS.exists(CONFIG_FILE_WIFI)) LittleFS.remove(CONFIG_FILE_WIFI);
    if (LittleFS.exists(CONFIG_FILE_MQTT)) LittleFS.remove(CONFIG_FILE_MQTT);
    if (LittleFS.exists(CONFIG_FILE_DEVICE)) LittleFS.remove(CONFIG_FILE_DEVICE);

    // 清除 WiFiManager 的配置
    _wm.resetSettings();

    // LED 快闪提示
    for (int i = 0; i < 10; i++) {
        digitalWrite(LED_WIFI_PIN, !digitalRead(LED_WIFI_PIN));
        delay(100);
    }

    Serial.println(F("[WiFi] 🔄 正在重启..."));
    delay(500);
    ESP.restart();
}

// ============================================================
// 按钮长按检测
// ============================================================

bool WiFiMgmt::_checkButtonLongPress() {
    bool pressed = (digitalRead(CONFIG_BUTTON_PIN) == LOW);

    if (pressed && !_buttonPressed) {
        // 按下
        _buttonPressed = true;
        _buttonPressTime = millis();
    } else if (!pressed && _buttonPressed) {
        // 松开
        _buttonPressed = false;
        if (millis() - _buttonPressTime >= BUTTON_LONG_PRESS_MS) {
            return true;  // 长按确认
        }
    }

    return false;
}

// ============================================================
// LittleFS 配置持久化
// ============================================================

bool WiFiMgmt::_loadConfig() {
    if (!LittleFS.exists(CONFIG_FILE_DEVICE)) {
        return false;
    }

    File file = LittleFS.open(CONFIG_FILE_DEVICE, "r");
    if (!file) return false;

    JsonDocument doc;
    DeserializationError err = deserializeJson(doc, file);
    file.close();

    if (err) {
        Serial.printf("[WiFi] JSON 解析失败: %s\n", err.c_str());
        return false;
    }

    strncpy(_config.mqtt_server, doc["mqtt_server"] | MQTT_DEFAULT_SERVER, sizeof(_config.mqtt_server) - 1);
    strncpy(_config.mqtt_port,   doc["mqtt_port"]   | MQTT_DEFAULT_PORT_STR, sizeof(_config.mqtt_port) - 1);
    strncpy(_config.mqtt_user,   doc["mqtt_user"]   | MQTT_DEFAULT_USER,     sizeof(_config.mqtt_user) - 1);
    strncpy(_config.mqtt_pass,   doc["mqtt_pass"]   | MQTT_DEFAULT_PASS,     sizeof(_config.mqtt_pass) - 1);
    strncpy(_config.device_id,   doc["device_id"]   | MQTT_DEFAULT_DEVICE,   sizeof(_config.device_id) - 1);
    strncpy(_config.env_note,    doc["env_note"]    | MQTT_DEFAULT_ENV,      sizeof(_config.env_note) - 1);

    Serial.println(F("[WiFi] ✅ 设备配置已加载"));
    Serial.printf("  设备名: %s, 环境: %s\n", _config.device_id, _config.env_note);
    return true;
}

bool WiFiMgmt::_saveConfig() {
    JsonDocument doc;
    doc["mqtt_server"] = _config.mqtt_server;
    doc["mqtt_port"]   = _config.mqtt_port;
    doc["mqtt_user"]   = _config.mqtt_user;
    doc["mqtt_pass"]   = _config.mqtt_pass;
    doc["device_id"]   = _config.device_id;
    doc["env_note"]    = _config.env_note;

    File file = LittleFS.open(CONFIG_FILE_DEVICE, "w");
    if (!file) {
        Serial.println(F("[WiFi] ❌ 无法创建配置文件"));
        return false;
    }

    if (serializeJson(doc, file) == 0) {
        Serial.println(F("[WiFi] ❌ 配置写入失败"));
        file.close();
        return false;
    }

    file.close();
    Serial.println(F("[WiFi] ✅ 设备配置已保存"));
    return true;
}

// ============================================================
// WiFiManager 保存参数回调
// ============================================================

// ============================================================
// 静态回调函数 (WiFiManager 要求 C 函数指针)
// ============================================================

void WiFiMgmt::_apCallback(WiFiManager* wm) {
    Serial.println(F("\n╔══════════════════════════════════════════╗"));
    Serial.println(F("║  🔧 配网模式已启动                       ║"));
    Serial.println(F("╠══════════════════════════════════════════╣"));
    Serial.printf( "║  WiFi名称: %-29s ║\n", WIFI_AP_SSID);
    Serial.printf( "║  密码:     %-29s ║\n", WIFI_AP_PASSWORD);
    Serial.println(F("╠══════════════════════════════════════════╣"));
    Serial.println(F("║  📱 请按以下步骤操作:                   ║"));
    Serial.println(F("║  1. 手机打开 WiFi 设置                  ║"));
    Serial.println(F("║  2. 连接上方显示的 WiFi                 ║"));
    Serial.println(F("║  3. 浏览器将自动弹出配网页面            ║"));
    Serial.println(F("║     (如未弹出, 手动访问 192.168.4.1)    ║"));
    Serial.println(F("╚══════════════════════════════════════════╝\n"));
}

void WiFiMgmt::_saveParamsCallback() {
    if (!_instance) return;

    Serial.println(F("[WiFi] 💾 参数已保存"));

    // 从 WiFiManager 参数中读取值
    strncpy(_instance->_config.mqtt_server, _instance->_param_mqtt_server->getValue(),
            sizeof(_instance->_config.mqtt_server) - 1);
    strncpy(_instance->_config.mqtt_port,   _instance->_param_mqtt_port->getValue(),
            sizeof(_instance->_config.mqtt_port) - 1);
    strncpy(_instance->_config.mqtt_user,   _instance->_param_mqtt_user->getValue(),
            sizeof(_instance->_config.mqtt_user) - 1);
    strncpy(_instance->_config.mqtt_pass,   _instance->_param_mqtt_pass->getValue(),
            sizeof(_instance->_config.mqtt_pass) - 1);
    strncpy(_instance->_config.device_id,   _instance->_param_device_id->getValue(),
            sizeof(_instance->_config.device_id) - 1);
    strncpy(_instance->_config.env_note,    _instance->_param_env_note->getValue(),
            sizeof(_instance->_config.env_note) - 1);

    _instance->_saveConfig();
    _instance->_freshProvision = true;  // 标记刚完成配网

    Serial.println(F("[WiFi] 📋 新配置:"));
    Serial.printf("  MQTT: %s:%s\n",
                  _instance->_config.mqtt_server,
                  _instance->_config.mqtt_port);
    Serial.printf("  设备: %s\n", _instance->_config.device_id);
    Serial.printf("  环境: %s\n", _instance->_config.env_note);
}

// ============================================================
// Getter 方法
// ============================================================

bool WiFiMgmt::isConnected() const {
    return _connected && (WiFi.status() == WL_CONNECTED);
}

DeviceConfig WiFiMgmt::getConfig() const {
    return _config;
}

const char* WiFiMgmt::getDeviceId() const {
    return _config.device_id;
}

const char* WiFiMgmt::getEnvNote() const {
    return _config.env_note;
}

// ============================================================
// LED 模式控制
// ============================================================

void WiFiMgmt::_setLEDMode(uint8_t mode) {
    switch (mode) {
    case 0:
    case 1:
        break;
    case 2:
        digitalWrite(LED_WIFI_PIN, HIGH);
        _ledState = true;
        break;
    }
}

// ============================================================
// LED 闪烁任务 (配网阻塞期间的独立线程)
// ============================================================

void WiFiMgmt::_startLEDTask(uint32_t interval) {
    _stopLEDTask();
    _ledTaskInterval = interval;
    _ledTaskRunning = true;
    xTaskCreatePinnedToCore(
        _ledBlinkTask, "LedBlink",
        2048, nullptr, 1, nullptr, 0
    );
}

void WiFiMgmt::_stopLEDTask() {
    _ledTaskRunning = false;
}

void WiFiMgmt::_ledBlinkTask(void* param) {
    while (_ledTaskRunning) {
        digitalWrite(LED_WIFI_PIN, HIGH);
        vTaskDelay(pdMS_TO_TICKS(_ledTaskInterval / 2));
        digitalWrite(LED_WIFI_PIN, LOW);
        vTaskDelay(pdMS_TO_TICKS(_ledTaskInterval / 2));
    }
    vTaskDelete(nullptr);
}

This may or may not be related to the current task.</ide_selection>

现在无论是重启，啥的oled屏幕显示的有点慢


The user is saying the OLED screen display is a bit slow, especially after reboot. They want me to look at the OLED/display code to see if there are performance issues.

Let me look at the display_manager code and understand what might be causing slowness. Common causes:
1. Display updates happening too frequently or too slowly
2. Blocking operations in the display update
3. U8g2 library configuration (buffer mode, etc.)
4. I2C speed settings

Let me check the display manager code and see how it's set up.


1	/**
2	 * @file display_manager.cpp
3	 * @brief OLED 显示管理器实现 - SSD1306 128x64 I2C
4	 */
5
6	#include "display_manager.h"
7
8	// ============================================================
9	// 构造 & 析构
10	// ============================================================
11
12	DisplayManager::DisplayManager()
13	    : _u8g2(U8G2_R0, U8X8_PIN_NONE, OLED_SCL_PIN, OLED_SDA_PIN)
14	    , _pageIndex(0)
15	    , _lastDrawTime(0)
16	    , _initialized(false)
17	{
18	}
19
20	DisplayManager::~DisplayManager() {
21	}
22
23	// ============================================================
24	// 初始化
25	// ============================================================
26
27	bool DisplayManager::begin() {
28	    // SSD1306 常见地址: 0x3C 或 0x3D
29	    // 先用 0x3C 尝试，失败再用 0x3D
30	    _u8g2.setI2CAddress(0x3C << 1);  // U8g2 需要左移一位
31	    if (!_u8g2.begin()) {
32	        // 尝试 0x3D
33	        _u8g2.setI2CAddress(0x3D << 1);
34	        if (!_u8g2.begin()) {
35	            Serial.println(F("[显示] ❌ OLED 未检测到! (I2C 0x3C/0x3D)"));
36	            Serial.println(F("[显示]    请检查接线: VCC→3.3V GND→GND SCL→22 SDA→21"));
37	            Serial.println(F("[显示]    系统将继续运行, 不影响其他功能"));
38	            return false;
39	        }
40	        Serial.println(F("[显示]    使用 I2C 地址 0x3D"));
41	    } else {
42	        Serial.println(F("[显示]    使用 I2C 地址 0x3C"));
43	    }
44
45	    _u8g2.enableUTF8Print();
46	    _u8g2.setFont(u8g2_font_wqy12_t_gb2312);
47	    _initialized = true;
48
49	    // 显示启动画面
50	    _u8g2.clearBuffer();
51	    _u8g2.setFont(u8g2_font_wqy16_t_gb2312);
52	    _u8g2.drawUTF8(10, 30, "健康监测系统");
53	    _u8g2.setFont(u8g2_font_wqy12_t_gb2312);
54	    _u8g2.drawUTF8(30, 52, "启动中...");
55	    _u8g2.sendBuffer();
56
57	    Serial.println(F("[显示] ✅ OLED 初始化完成 (SSD1306 128x64 I2C)"));
58	    return true;
59	}
60
61	// ============================================================
62	// 翻页
63	// ============================================================
64
65	void DisplayManager::nextPage() {
66	    _pageIndex = (_pageIndex + 1) % 4;
67	    _lastDrawTime = 0;  // 强制立即刷新
68	}
69
70	// ============================================================
71	// 主更新入口
72	// ============================================================
73
74	void DisplayManager::update(const TelemetryData& data, bool wifiOk, bool buzzerOn) {
75	    if (!_initialized) return;
76
77	    // 每 5 秒刷新一次 (防止 OLED 烧屏，也避免 SPI 占用过多)
78	    if (millis() - _lastDrawTime < 5000) return;
79	    _lastDrawTime = millis();
80
81	    _u8g2.clearBuffer();
82
83	    switch (_pageIndex) {
84	    case 0: _drawPage0(data, wifiOk, buzzerOn); break;
85	    case 1: _drawPage1(data); break;
86	    case 2: _drawPage2(data); break;
87	    case 3: _drawPage3(data, buzzerOn, wifiOk); break;
88	    default: _drawPage0(data, wifiOk, buzzerOn); break;
89	    }
90
91	    _u8g2.sendBuffer();
92	}
93
94	// ============================================================
95	// Page 0: 环境数据
96	// ============================================================
97
98	void DisplayManager::_drawPage0(const TelemetryData& data, bool wifiOk, bool buzzerOn) {
99	    // 标题栏
100	    _u8g2.setFont(u8g2_font_wqy12_t_gb2312);
101	    _u8g2.drawUTF8(2, 12, "🏥 健康监测 V1.0");
102
103	    // 分隔线
104	    _u8g2.drawHLine(0, 15, 128);
105
106	    // 温湿度
107	    _u8g2.setFont(u8g2_font_wqy12_t_gb2312);
108	    _u8g2.drawUTF8(4, 30, "温度:");
109	    _u8g2.setCursor(52, 30);
110	    if (data.dhtValid) {
111	        _u8g2.printf("%s°C", _fmtFloat(data.temp).c_str());
112	    } else {
113	        _u8g2.print("-- °C");
114	    }
115
116	    _u8g2.drawUTF8(4, 44, "湿度:");
117	    _u8g2.setCursor(52, 44);
118	    if (data.dhtValid) {
119	        _u8g2.printf("%s%%", _fmtFloat(data.humi).c_str());
120	    } else {
121	        _u8g2.print("-- %");
122	    }
123
124	    // WiFi 状态
125	    _u8g2.drawUTF8(4, 58, "WiFi:");
126	    _u8g2.setCursor(52, 58);
127	    _u8g2.print(wifiOk ? "已连接" : "未连接");
128
129	    // 运行时间 + 蜂鸣器状态
130	    _u8g2.setFont(u8g2_font_wqy12_t_gb2312);
131	    _u8g2.setCursor(2, 64);
132	    _u8g2.print(_fmtUptime(data.uptimeSec));
133
134	    if (buzzerOn) {
135	        _u8g2.drawUTF8(104, 64, "🔔");
136	    }
137
138	    // 页码
139	    _u8g2.setCursor(110, 12);
140	    _u8g2.print("1/4");
141	}
142
143	// ============================================================
144	// Page 1: 生命体征
145	// ============================================================
146
147	void DisplayManager::_drawPage1(const TelemetryData& data) {
148	    // 标题栏
149	    _u8g2.setFont(u8g2_font_wqy12_t_gb2312);
150	    _u8g2.drawUTF8(2, 12, "🫀 生命体征");
151	    _u8g2.drawHLine(0, 15, 128);
152
153	    // 心率
154	    _u8g2.drawUTF8(4, 32, "心率:");
155	    _u8g2.setCursor(52, 32);
156	    if (data.vitalValid && data.heartRate > 0) {
157	        _u8g2.printf("%d bpm", data.heartRate);
158	    } else {
159	        _u8g2.print("-- bpm");
160	    }
161
162	    // 呼吸
163	    _u8g2.drawUTF8(4, 48, "呼吸:");
164	    _u8g2.setCursor(52, 48);
165	    if (data.vitalValid && data.breathRate > 0) {
166	        _u8g2.printf("%d 次/分", data.breathRate);
167	    } else {
168	        _u8g2.print("-- 次/分");
169	    }
170
171	    // 体动/能量值
172	    _u8g2.drawUTF8(4, 64, "体动:");
173	    _u8g2.setCursor(52, 64);
174	    _u8g2.printf("%d", data.body);
175
176	    // 页码
177	    _u8g2.setCursor(110, 12);
178	    _u8g2.print("2/4");
179	}
180
181	// ============================================================
182	// Page 2: 人体状态
183	// ============================================================
184
185	void DisplayManager::_drawPage2(const TelemetryData& data) {
186	    _u8g2.setFont(u8g2_font_wqy12_t_gb2312);
187	    _u8g2.drawUTF8(2, 12, "人体状态");
188	    _u8g2.drawHLine(0, 15, 128);
189
190	    _u8g2.drawUTF8(4, 30, "有人:");
191	    _u8g2.drawUTF8(60, 30, data.presence ? "V" : "X");
192
193	    _u8g2.drawUTF8(90, 30, "运动:");
194	    _u8g2.drawUTF8(120, 30, data.motion ? "V" : "X");
195
196	    _u8g2.drawUTF8(4, 44, "跌倒:");
197	    _u8g2.drawUTF8(60, 44, data.fall ? "!!" : "X");
198
199	    _u8g2.drawUTF8(90, 44, "驻留:");
200	    _u8g2.drawUTF8(120, 44, data.resident ? "V" : "X");
201
202	    _u8g2.drawUTF8(4, 58, "体动:");
203	    _u8g2.setCursor(60, 58);
204	    _u8g2.print(data.body);
205
206	    _u8g2.drawUTF8(110, 12, "3/4");
207	}
208
209	// ============================================================
210	// Page 3: 安全信息
211	// ============================================================
212
213	void DisplayManager::_drawPage3(const TelemetryData& data, bool buzzerOn, bool wifiOk) {
214	    _u8g2.setFont(u8g2_font_wqy12_t_gb2312);
215	    _u8g2.drawUTF8(2, 12, "安全信息");
216	    _u8g2.drawHLine(0, 15, 128);
217
218	    _u8g2.drawUTF8(4, 30, "水浸:");
219	    if (!data.rainValid) _u8g2.drawUTF8(60, 30, "--");
220	    else _u8g2.drawUTF8(60, 30, data.rainDO ? "正常" : "!!报警");
221
222	    _u8g2.drawUTF8(4, 44, "蜂鸣器:");
223	    _u8g2.drawUTF8(60, 44, buzzerOn ? "响" : "关");
224
225	    _u8g2.drawUTF8(4, 58, "WiFi:");
226	    _u8g2.drawUTF8(60, 58, wifiOk ? "已连接" : "未连接");
227
228	    _u8g2.drawUTF8(2, 64, _fmtUptime(data.uptimeSec).c_str());
229	    _u8g2.drawUTF8(110, 12, "4/4");
230	}
231
232	// ============================================================
233	// 辅助方法
234	// ============================================================
235
236	String DisplayManager::_fmtUptime(uint32_t secs) const {
237	    uint32_t h = secs / 3600;
238	    uint32_t m = (secs % 3600) / 60;
239	    uint32_t s = secs % 60;
240	    char buf[12];
241	    snprintf(buf, sizeof(buf), "%02d:%02d:%02d", (int)h, (int)m, (int)s);
242	    return String(buf);
243	}
244
245	String DisplayManager::_fmtFloat(float val) const {
246	    char buf[8];
247	    snprintf(buf, sizeof(buf), "%.1f", val);
248	    return String(buf);
249	}
250

/**
 * @file display_manager.cpp
 * @brief OLED 显示管理器实现 - SSD1306 128x64 I2C
 */

#include "display_manager.h"

// ============================================================
// 构造 & 析构
// ============================================================

DisplayManager::DisplayManager()
    : _u8g2(U8G2_R0, U8X8_PIN_NONE, OLED_SCL_PIN, OLED_SDA_PIN)
    , _pageIndex(0)
    , _lastDrawTime(0)
    , _initialized(false)
{
}

DisplayManager::~DisplayManager() {
}

// ============================================================
// 初始化
// ============================================================

bool DisplayManager::begin() {
    // SSD1306 常见地址: 0x3C 或 0x3D
    // 先用 0x3C 尝试，失败再用 0x3D
    _u8g2.setI2CAddress(0x3C << 1);  // U8g2 需要左移一位
    if (!_u8g2.begin()) {
        // 尝试 0x3D
        _u8g2.setI2CAddress(0x3D << 1);
        if (!_u8g2.begin()) {
            Serial.println(F("[显示] ❌ OLED 未检测到! (I2C 0x3C/0x3D)"));
            Serial.println(F("[显示]    请检查接线: VCC→3.3V GND→GND SCL→22 SDA→21"));
            Serial.println(F("[显示]    系统将继续运行, 不影响其他功能"));
            return false;
        }
        Serial.println(F("[显示]    使用 I2C 地址 0x3D"));
    } else {
        Serial.println(F("[显示]    使用 I2C 地址 0x3C"));
    }

    _u8g2.enableUTF8Print();
    _u8g2.setFont(u8g2_font_wqy12_t_gb2312);
    _initialized = true;

    // 显示启动画面
    _u8g2.clearBuffer();
    _u8g2.setFont(u8g2_font_wqy16_t_gb2312);
    _u8g2.drawUTF8(10, 30, "健康监测系统");
    _u8g2.setFont(u8g2_font_wqy12_t_gb2312);
    _u8g2.drawUTF8(30, 52, "启动中...");
    _u8g2.sendBuffer();

    Serial.println(F("[显示] ✅ OLED 初始化完成 (SSD1306 128x64 I2C)"));
    return true;
}

// ============================================================
// 翻页
// ============================================================

void DisplayManager::nextPage() {
    _pageIndex = (_pageIndex + 1) % 4;
    _lastDrawTime = 0;  // 强制立即刷新
}

// ============================================================
// 主更新入口
// ============================================================

void DisplayManager::update(const TelemetryData& data, bool wifiOk, bool buzzerOn) {
    if (!_initialized) return;

    // 每 5 秒刷新一次 (防止 OLED 烧屏，也避免 SPI 占用过多)
    if (millis() - _lastDrawTime < 5000) return;
    _lastDrawTime = millis();

    _u8g2.clearBuffer();

    switch (_pageIndex) {
    case 0: _drawPage0(data, wifiOk, buzzerOn); break;
    case 1: _drawPage1(data); break;
    case 2: _drawPage2(data); break;
    case 3: _drawPage3(data, buzzerOn, wifiOk); break;
    default: _drawPage0(data, wifiOk, buzzerOn); break;
    }

    _u8g2.sendBuffer();
}

// ============================================================
// Page 0: 环境数据
// ============================================================

void DisplayManager::_drawPage0(const TelemetryData& data, bool wifiOk, bool buzzerOn) {
    // 标题栏
    _u8g2.setFont(u8g2_font_wqy12_t_gb2312);
    _u8g2.drawUTF8(2, 12, "🏥 健康监测 V1.0");

    // 分隔线
    _u8g2.drawHLine(0, 15, 128);

    // 温湿度
    _u8g2.setFont(u8g2_font_wqy12_t_gb2312);
    _u8g2.drawUTF8(4, 30, "温度:");
    _u8g2.setCursor(52, 30);
    if (data.dhtValid) {
        _u8g2.printf("%s°C", _fmtFloat(data.temp).c_str());
    } else {
        _u8g2.print("-- °C");
    }

    _u8g2.drawUTF8(4, 44, "湿度:");
    _u8g2.setCursor(52, 44);
    if (data.dhtValid) {
        _u8g2.printf("%s%%", _fmtFloat(data.humi).c_str());
    } else {
        _u8g2.print("-- %");
    }

    // WiFi 状态
    _u8g2.drawUTF8(4, 58, "WiFi:");
    _u8g2.setCursor(52, 58);
    _u8g2.print(wifiOk ? "已连接" : "未连接");

    // 运行时间 + 蜂鸣器状态
    _u8g2.setFont(u8g2_font_wqy12_t_gb2312);
    _u8g2.setCursor(2, 64);
    _u8g2.print(_fmtUptime(data.uptimeSec));

    if (buzzerOn) {
        _u8g2.drawUTF8(104, 64, "🔔");
    }

    // 页码
    _u8g2.setCursor(110, 12);
    _u8g2.print("1/4");
}

// ============================================================
// Page 1: 生命体征
// ============================================================

void DisplayManager::_drawPage1(const TelemetryData& data) {
    // 标题栏
    _u8g2.setFont(u8g2_font_wqy12_t_gb2312);
    _u8g2.drawUTF8(2, 12, "🫀 生命体征");
    _u8g2.drawHLine(0, 15, 128);

    // 心率
    _u8g2.drawUTF8(4, 32, "心率:");
    _u8g2.setCursor(52, 32);
    if (data.vitalValid && data.heartRate > 0) {
        _u8g2.printf("%d bpm", data.heartRate);
    } else {
        _u8g2.print("-- bpm");
    }

    // 呼吸
    _u8g2.drawUTF8(4, 48, "呼吸:");
    _u8g2.setCursor(52, 48);
    if (data.vitalValid && data.breathRate > 0) {
        _u8g2.printf("%d 次/分", data.breathRate);
    } else {
        _u8g2.print("-- 次/分");
    }

    // 体动/能量值
    _u8g2.drawUTF8(4, 64, "体动:");
    _u8g2.setCursor(52, 64);
    _u8g2.printf("%d", data.body);

    // 页码
    _u8g2.setCursor(110, 12);
    _u8g2.print("2/4");
}

// ============================================================
// Page 2: 人体状态
// ============================================================

void DisplayManager::_drawPage2(const TelemetryData& data) {
    _u8g2.setFont(u8g2_font_wqy12_t_gb2312);
    _u8g2.drawUTF8(2, 12, "人体状态");
    _u8g2.drawHLine(0, 15, 128);

    _u8g2.drawUTF8(4, 30, "有人:");
    _u8g2.drawUTF8(60, 30, data.presence ? "V" : "X");

    _u8g2.drawUTF8(90, 30, "运动:");
    _u8g2.drawUTF8(120, 30, data.motion ? "V" : "X");

    _u8g2.drawUTF8(4, 44, "跌倒:");
    _u8g2.drawUTF8(60, 44, data.fall ? "!!" : "X");

    _u8g2.drawUTF8(90, 44, "驻留:");
    _u8g2.drawUTF8(120, 44, data.resident ? "V" : "X");

    _u8g2.drawUTF8(4, 58, "体动:");
    _u8g2.setCursor(60, 58);
    _u8g2.print(data.body);

    _u8g2.drawUTF8(110, 12, "3/4");
}

// ============================================================
// Page 3: 安全信息
// ============================================================

void DisplayManager::_drawPage3(const TelemetryData& data, bool buzzerOn, bool wifiOk) {
    _u8g2.setFont(u8g2_font_wqy12_t_gb2312);
    _u8g2.drawUTF8(2, 12, "安全信息");
    _u8g2.drawHLine(0, 15, 128);

    _u8g2.drawUTF8(4, 30, "水浸:");
    if (!data.rainValid) _u8g2.drawUTF8(60, 30, "--");
    else _u8g2.drawUTF8(60, 30, data.rainDO ? "正常" : "!!报警");

    _u8g2.drawUTF8(4, 44, "蜂鸣器:");
    _u8g2.drawUTF8(60, 44, buzzerOn ? "响" : "关");

    _u8g2.drawUTF8(4, 58, "WiFi:");
    _u8g2.drawUTF8(60, 58, wifiOk ? "已连接" : "未连接");

    _u8g2.drawUTF8(2, 64, _fmtUptime(data.uptimeSec).c_str());
    _u8g2.drawUTF8(110, 12, "4/4");
}

// ============================================================
// 辅助方法
// ============================================================

String DisplayManager::_fmtUptime(uint32_t secs) const {
    uint32_t h = secs / 3600;
    uint32_t m = (secs % 3600) / 60;
    uint32_t s = secs % 60;
    char buf[12];
    snprintf(buf, sizeof(buf), "%02d:%02d:%02d", (int)h, (int)m, (int)s);
    return String(buf);
}

String DisplayManager::_fmtFloat(float val) const {
    char buf[8];
    snprintf(buf, sizeof(buf), "%.1f", val);
    return String(buf);
}