我刚才不小心把你关了


claude claude-code-guide Explore general-purpose Plan statusline-setup

- claude: Catch-all for any task that doesn't fit a more specific agent. FleetView's default when no agent name is typed. (Tools: *)

- claude-code-guide: Use this agent when the user asks questions ("Can Claude...", "Does Claude...", "How do I...") about: (1) Claude Code (the CLI tool) - features, hooks, slash commands, MCP servers, settings, IDE integrations, keyboard shortcuts; (2) Claude Agent SDK - building custom agents; (3) Claude API (formerly Anthropic API) - Messages API for directly passing messages to Claude, Tool Runner (`client.beta.messages.tool_runner`) for running an agentic loop over your own tools, manual tool-use loops, Managed Agents for server-hosted agents with a managed sandbox, prompt caching, and general Anthropic SDK usage; (4) Claude Tag (Claude in Slack) - what it is, setting it up for a Slack workspace, `/install-slack-app`. **IMPORTANT:** Before spawning a new agent, check if there is already a running or recently completed claude-code-guide agent that you can continue via SendMessage. (Tools: Glob, Grep, Read, WebFetch, WebSearch)

- Explore: Read-only search agent for broad fan-out searches — when answering means sweeping many files, directories, or naming conventions and you only need the conclusion, not the file dumps. It reads excerpts rather than whole files, so it locates code; it doesn't review or audit it. Specify search breadth: "medium" for moderate exploration, "very thorough" for multiple locations and naming conventions. (Tools: All tools except Agent, Artifact, ExitPlanMode, Edit, Write, NotebookEdit)

- general-purpose: General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you. (Tools: *)

- Plan: Software architect agent for designing implementation plans. Use this when you need to plan the implementation strategy for a task. Returns step-by-step plans, identifies critical files, and considers architectural trade-offs. (Tools: All tools except Agent, Artifact, ExitPlanMode, Edit, Write, NotebookEdit)

- statusline-setup: Use this agent to configure the user's Claude Code status line setting. (Tools: Read, Edit)


- dataviz: Use this skill whenever you are about to create ANY chart, graph, plot, dashboard, or data visualization, in ANY output medium — an HTML or React artifact, inline SVG, plotting code in any library (matplotlib, plotly, d3, Recharts, …), an image/PNG you will render and upload, or a chart shared into Slack. Read it BEFORE writing the first line of chart code, choosing chart colors, building a stat tile / meter / KPI row, or laying out a dashboard. Produces visualizations that read as one system — elegant, accessible, consistent in light and dark — using a brand-neutral placeholder palette you swap for your own. Teaches a design-system-agnostic method: a form heuristic, a color formula with a runnable validator, mark specs, and interaction rules. A validated default palette is documented in `references/palette.md` — swap that file's values for your brand's. Triggers on: "chart", "graph", "plot", "data viz", "visualization", "dashboard", "analytics", "visualize data", "categorical colors", "sequential / diverging palette", "stat tile", "sparkline", "heatmap", "legend", "axis", "tooltip", "chart colors", "color by series".
- update-config: Use this skill to configure the Claude Code harness via settings.json. Automated behaviors ("from now on when X", "each time X", "whenever X", "before/after X") require hooks configured in settings.json - the harness executes these, not Claude, so memory/preferences cannot fulfill them. Also use for: permissions ("allow X", "add permission", "move permission to"), env vars ("set X=Y"), hook troubleshooting, or any changes to settings.json/settings.local.json files. Examples: "allow npm commands", "add bq permission to global settings", "move permission to user settings", "set DEBUG=true", "when claude stops show X". For simple settings like theme/model, suggest the /config command.
- keybindings-help: Use when the user wants to customize keyboard shortcuts, rebind keys, add chord bindings, or modify ~/.claude/keybindings.json. Examples: "rebind ctrl+s", "add a chord shortcut", "change the submit key", "customize keybindings".
- simplify: Review the changed code for reuse, simplification, efficiency, and altitude cleanups, then apply the fixes. Quality only — it does not hunt for bugs; use /code-review for that.
- fewer-permission-prompts: Scan your transcripts for common read-only Bash and MCP tool calls, then add a prioritized allowlist to project .claude/settings.json to reduce permission prompts.
- loop: Run a prompt or slash command on a recurring interval (e.g. /loop 5m /foo). Omit the interval to let the model self-pace. - When the user wants to set up a recurring task, poll for status, or run something repeatedly on an interval (e.g. "check the deploy every 5 minutes", "keep running /babysit-prs"). Do NOT invoke for one-off tasks.
- claude-api: Reference for the Claude API / Anthropic SDK — model ids, pricing, params, streaming, tool use, MCP, agents, caching, token counting, model migration.
TRIGGER — read BEFORE opening the target file; don't skip because it "looks like a one-liner" — whenever: the prompt names Claude/Anthropic in any form (Claude, Anthropic, Fable, Opus, Sonnet, Haiku, `anthropic`, `@anthropic-ai`, `claude-*`, `us.anthropic.*`, `[1m]`); the user asks about an LLM (pricing/model choice/limits/caching) — never answer from memory; OR the task is LLM-shaped with provider unstated (agent/MCP/tool-definition/multi-agent/RAG/LLM-judge/computer-use; generate/summarize/extract/classify/rewrite/converse over NL; debugging refusals/cutoffs/streaming/tool-calls/tokens).
SKIP only when another provider is being worked on (overrides all triggers): OpenAI/GPT/Gemini/Llama/Mistral/Cohere/Ollama named in the query; OR `grep -rE 'openai|langchain_openai|google.generativeai|genai|mistralai|cohere|ollama'` over the project hits (run this grep FIRST if no provider named — don't Read the file).
- run: Launch and drive this project's app to see a change working. Use when asked to run, start, or screenshot the app, or to confirm a change works in the real app (not just tests). First looks for a project skill that already covers launching the app; otherwise falls back to built-in patterns per project type (CLI, server, TUI, Electron, browser-driven, library).
- init: Initialize a new CLAUDE.md file with codebase documentation
- review: Review a GitHub pull request; for your working diff use /code-review
- security-review: Complete a security review of the pending changes on the current branch

dataviz update-config keybindings-help simplify fewer-permission-prompts loop claude-api run init review security-review


The user is saying they accidentally closed me (turned me off). This is just a casual comment — they're back now and want to continue. Let me check what we were working on before by looking at the git status and any recent changes.


没关系，欢迎回来！😊

让我看看我们之前在做什么。根据 git 状态，你当前在 `main` 分支上，有两个未提交的修改：

- [data/index.html](data/index.html) — 已暂存 (staged)
- [src/wifi_manager.cpp](src/wifi_manager.cpp) — 未暂存 (unstaged)

这是一个 **PlatformIO 项目**（Healthv2.0），看起来是一个嵌入式健康监测设备项目。

你想继续之前的工作吗？需要我帮你做什么？


就是我i现在的WiFi指示灯，怎么亮着时，一关串口监视器毁灭掉呢


The user is asking about a WiFi indicator light issue - when the serial monitor is closed, the WiFi indicator LED turns off. This suggests the WiFi LED is somehow tied to the serial connection being active, which shouldn't be the case.

Let me look at the relevant code to understand the issue. Let me start by examining the modified files and the project structure.


让我先看看相关代码，理解 WiFi 指示灯的控制逻辑。


1	/**
2	 * @file wifi_manager.cpp
3	 * @brief WiFi 配网管理器实现
4	 */
5
6	#include "wifi_manager.h"
7
8	// 单例指针
9	WiFiMgmt* WiFiMgmt::_instance = nullptr;
10
11	// ============================================================
12	// 构造函数 & 析构函数
13	// ============================================================
14
15	WiFiMgmt::WiFiMgmt()
16	    : _connected(false)
17	    , _lastCheckTime(0)
18	    , _buttonPressTime(0)
19	    , _buttonPressed(false)
20	    , _ledTaskHandle(nullptr)
21	    , _ledMode(0)
22	    , _param_mqtt_server(nullptr)
23	    , _param_mqtt_port(nullptr)
24	    , _param_mqtt_user(nullptr)
25	    , _param_mqtt_pass(nullptr)
26	    , _param_device_id(nullptr)
27	    , _param_env_note(nullptr)
28	{
29	    memset(&_config, 0, sizeof(_config));
30	    _instance = this;  // 设置单例
31	}
32
33	WiFiMgmt::~WiFiMgmt() {
34	    _instance = nullptr;
35	}
36
37	// ============================================================
38	// 初始化
39	// ============================================================
40
41	bool WiFiMgmt::begin() {
42	    Serial.println(F("\n========================================"));
43	    Serial.println(F("  WiFi 配网管理器 初始化"));
44	    Serial.println(F("========================================"));
45
46	    // 初始化引脚
47	    pinMode(CONFIG_BUTTON_PIN, INPUT_PULLUP);
48	    pinMode(LED_WIFI_PIN, OUTPUT);
49	    digitalWrite(LED_WIFI_PIN, LOW);
50
51	    // 启动 LED 独立任务 (避免 startConfigPortal 阻塞影响闪烁)
52	    xTaskCreate(_ledTaskFunc, "WiFiLed", 1024, nullptr, 1, &_ledTaskHandle);
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	    // 配置 WiFiManager
74	    _wm.setDebugOutput(false);  // 关闭调试输出 (避免干扰)
75	    _wm.setConfigPortalTimeout(WIFI_CONFIG_TIMEOUT);
76	    _wm.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
77	    _wm.setTitle("观照-五劳无感 · 设备配网");
78	    _wm.setCustomHeadElement(
79	        "<meta name='theme-color' content='#12a889'>"
80	        ""
158	    );
159
160	    // 自定义菜单 HTML — 软件组设计的头部 + 引导弹窗
161	    _wm.setCustomMenuHTML(
162	        "
"
163	        /* 头部 */
164	        "
"
165	        "
📡
"
168	        "

观照-五劳无感 · 设备配网
"
169	        "
WiFi 联网与参数配置

"
170	        /* 帮助按钮 */
171	        "
?
"
175	        "
"
176	        /* 网关信息 */
177	        "
"
179	        "📍 当前网关"
180	        "Gateway
"
181	        "
"
182	        /* 引导弹窗 */
183	        "
"
184	        "
"
185	        "
"
186	        "
📋
"
187	        "

配网流程
"
188	        "
按顺序完成 4 步操作

"
189	        "
×

"
190	        "
"
191	        "

1

连接此热点
"
192	        "
手机连接 " + String(WIFI_AP_SSID) + " 热点，密码 " + String(WIFI_AP_PASSWORD) + "

"
193	        "

2

选择 WiFi 并输入密码
"
194	        "
选择您的 2.4GHz WiFi 网络

"
195	        "

3

填写设备信息
"
196	        "
设备编号与安装环境便于管理

"
197	        "

4

保存并等待连接
"
198	        "
指示灯常亮即表示配网完成

"
199	        "
"
200	        "
"
201	        "<button class='ghost' onclick='toggleHelp(false)'>稍后查看</button>"
202	        "<button class='pri' onclick='toggleHelp(false)'>我知道了</button>
"
203	        "

"
204	        ""
207	        ""
215	    );
216	    WiFi.setAutoReconnect(true);  // ESP32 自动重连
217
218	    // 注册保存回调 (必须使用静态函数, 不能使用 lambda)
219	    _wm.setSaveConfigCallback(_saveParamsCallback);
220
221	    // 创建自定义参数
222	    _param_mqtt_server = new WiFiManagerParameter(
223	        "mqtt_server", "MQTT 服务器地址",
224	        _config.mqtt_server, 40);
225	    _param_mqtt_port = new WiFiManagerParameter(
226	        "mqtt_port", "MQTT 端口",
227	        _config.mqtt_port, 6);
228	    _param_mqtt_user = new WiFiManagerParameter(
229	        "mqtt_user", "MQTT 用户名",
230	        _config.mqtt_user, 20);
231	    _param_mqtt_pass = new WiFiManagerParameter(
232	        "mqtt_pass", "MQTT 密码",
233	        _config.mqtt_pass, 20);
234	    _param_device_id = new WiFiManagerParameter(
235	        "device_id", "设备名称",
236	        _config.device_id, 20);
237	    _param_env_note = new WiFiManagerParameter(
238	        "env_note", "🏠 环境备注 (如: 主卧/客厅/老人房)",
239	        _config.env_note, 20);
240
241	    // 注册自定义参数
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	    _wm.addParameter(_param_device_id);
247	    _wm.addParameter(_param_env_note);
248
249	    // 设置 AP 模式回调 (静态函数)
250	    _wm.setAPCallback(_apCallback);
251
252	    // 尝试连接 WiFi
253	    _connected = _connectWiFi();
254
255	    if (!_connected) {
256	        // 如果自动连接失败, 启动配网页面
257	        Serial.println(F("[WiFi] 自动连接失败, 启动配网模式..."));
258	        _setLEDMode(2);  // 快闪 = 配网模式
259	        _connected = _connectWiFi();  // 内部会启动配网页面
260	    }
261
262	    if (_connected) {
263	        _setLEDMode(3);  // 常亮 = 已连接
264	    }
265
266	    _lastCheckTime = millis();
267	    return _connected;
268	}
269
270	// ============================================================
271	// 主循环
272	// ============================================================
273
274	bool WiFiMgmt::loop() {
275	    // 检测按钮长按
276	    if (_checkButtonLongPress()) {
277	        Serial.println(F("[WiFi] ⚠  检测到长按, 清除配置并重启..."));
278	        resetSettings();
279	        return false;
280	    }
281
282	    // 检查 WiFi 状态
283	    if (millis() - _lastCheckTime > WIFI_RETRY_INTERVAL) {
284	        _lastCheckTime = millis();
285
286	        if (WiFi.status() != WL_CONNECTED) {
287	            _connected = false;
288	            _setLEDMode(1);  // 慢闪 = 未连接
289	            Serial.println(F("[WiFi] ⚠  WiFi 断开, 尝试重连..."));
290	            _connectWiFi();
291	        } else {
292	            _connected = true;
293	            _setLEDMode(3);  // 常亮 = 已连接
294	        }
295	    }
296
297	    // LED 由独立 FreeRTOS 任务处理 (不受 startConfigPortal 阻塞影响)
298	    return _connected;
299	}
300
301	// ============================================================
302	// WiFi 连接
303	// ============================================================
304
305	bool WiFiMgmt::_connectWiFi() {
306	    // 检查是否有已保存的配置
307	    // autoConnect 会先尝试连接, 失败后启动 AP 配网页面
308	    _connected = _wm.autoConnect(WIFI_AP_SSID, WIFI_AP_PASSWORD);
309
310	    if (_connected) {
311	        Serial.println(F("[WiFi] ✅ 已连接!"));
312	        Serial.printf("  WiFi名称: %s\n", WiFi.SSID().c_str());
313	        Serial.printf("  IP地址:   %s\n", WiFi.localIP().toString().c_str());
314	        Serial.printf("  MAC地址:  %s\n", WiFi.macAddress().c_str());
315
316	        // 重新加载配置 (确保获取最新值)
317	        // autoConnect 过程中如果有保存, 回调已写入 LittleFS
318	        _loadConfig();
319
320	        // 同步自定义参数 (如果配置是从 LittleFS 加载的)
321	        _param_mqtt_server->setValue(_config.mqtt_server, sizeof(_config.mqtt_server));
322	        _param_mqtt_port->setValue(_config.mqtt_port, sizeof(_config.mqtt_port));
323	        _param_mqtt_user->setValue(_config.mqtt_user, sizeof(_config.mqtt_user));
324	        _param_mqtt_pass->setValue(_config.mqtt_pass, sizeof(_config.mqtt_pass));
325	        _param_device_id->setValue(_config.device_id, sizeof(_config.device_id));
326	        _param_env_note->setValue(_config.env_note, sizeof(_config.env_note));
327
328	        Serial.printf("  设备名: %s\n", _config.device_id);
329	        Serial.printf("  环境:   %s\n", _config.env_note);
330	        Serial.printf("  MQTT服务器: %s:%s\n", _config.mqtt_server, _config.mqtt_port);
331	    }
332
333	    return _connected;
334	}
335
336	// ============================================================
337	// 配网页面
338	// ============================================================
339
340	void WiFiMgmt::startConfigPortal() {
341	    Serial.println(F("[WiFi] 🔧 手动启动配网页面..."));
342	    _setLEDMode(2);  // 快闪 = 配网模式
343	    _wm.startConfigPortal(WIFI_AP_SSID, WIFI_AP_PASSWORD);
344	    _connected = (WiFi.status() == WL_CONNECTED);
345	    if (_connected) {
346	        _setLEDMode(3);  // 常亮 = 已连接
347	    }
348	}
349
350	// ============================================================
351	// 重置设置
352	// ============================================================
353
354	void WiFiMgmt::resetSettings() {
355	    Serial.println(F("[WiFi] 清除所有配置..."));
356
357	    // 删除 LittleFS 中的配置文件
358	    if (LittleFS.exists(CONFIG_FILE_WIFI)) LittleFS.remove(CONFIG_FILE_WIFI);
359	    if (LittleFS.exists(CONFIG_FILE_MQTT)) LittleFS.remove(CONFIG_FILE_MQTT);
360	    if (LittleFS.exists(CONFIG_FILE_DEVICE)) LittleFS.remove(CONFIG_FILE_DEVICE);
361
362	    // 清除 WiFiManager 的配置
363	    _wm.resetSettings();
364
365	    // LED 快闪提示
366	    for (int i = 0; i < 10; i++) {
367	        digitalWrite(LED_WIFI_PIN, !digitalRead(LED_WIFI_PIN));
368	        delay(100);
369	    }
370
371	    Serial.println(F("[WiFi] 🔄 正在重启..."));
372	    delay(500);
373	    ESP.restart();
374	}
375
376	// ============================================================
377	// 按钮长按检测
378	// ============================================================
379
380	bool WiFiMgmt::_checkButtonLongPress() {
381	    bool pressed = (digitalRead(CONFIG_BUTTON_PIN) == LOW);
382
383	    if (pressed && !_buttonPressed) {
384	        // 按下
385	        _buttonPressed = true;
386	        _buttonPressTime = millis();
387	    } else if (!pressed && _buttonPressed) {
388	        // 松开
389	        _buttonPressed = false;
390	        if (millis() - _buttonPressTime >= BUTTON_LONG_PRESS_MS) {
391	            return true;  // 长按确认
392	        }
393	    }
394
395	    return false;
396	}
397
398	// ============================================================
399	// LittleFS 配置持久化
400	// ============================================================
401
402	bool WiFiMgmt::_loadConfig() {
403	    if (!LittleFS.exists(CONFIG_FILE_DEVICE)) {
404	        return false;
405	    }
406
407	    File file = LittleFS.open(CONFIG_FILE_DEVICE, "r");
408	    if (!file) return false;
409
410	    JsonDocument doc;
411	    DeserializationError err = deserializeJson(doc, file);
412	    file.close();
413
414	    if (err) {
415	        Serial.printf("[WiFi] JSON 解析失败: %s\n", err.c_str());
416	        return false;
417	    }
418
419	    strncpy(_config.mqtt_server, doc["mqtt_server"] | MQTT_DEFAULT_SERVER, sizeof(_config.mqtt_server) - 1);
420	    strncpy(_config.mqtt_port,   doc["mqtt_port"]   | MQTT_DEFAULT_PORT_STR, sizeof(_config.mqtt_port) - 1);
421	    strncpy(_config.mqtt_user,   doc["mqtt_user"]   | MQTT_DEFAULT_USER,     sizeof(_config.mqtt_user) - 1);
422	    strncpy(_config.mqtt_pass,   doc["mqtt_pass"]   | MQTT_DEFAULT_PASS,     sizeof(_config.mqtt_pass) - 1);
423	    strncpy(_config.device_id,   doc["device_id"]   | MQTT_DEFAULT_DEVICE,   sizeof(_config.device_id) - 1);
424	    strncpy(_config.env_note,    doc["env_note"]    | MQTT_DEFAULT_ENV,      sizeof(_config.env_note) - 1);
425
426	    Serial.println(F("[WiFi] ✅ 设备配置已加载"));
427	    Serial.printf("  设备名: %s, 环境: %s\n", _config.device_id, _config.env_note);
428	    return true;
429	}
430
431	bool WiFiMgmt::_saveConfig() {
432	    JsonDocument doc;
433	    doc["mqtt_server"] = _config.mqtt_server;
434	    doc["mqtt_port"]   = _config.mqtt_port;
435	    doc["mqtt_user"]   = _config.mqtt_user;
436	    doc["mqtt_pass"]   = _config.mqtt_pass;
437	    doc["device_id"]   = _config.device_id;
438	    doc["env_note"]    = _config.env_note;
439
440	    File file = LittleFS.open(CONFIG_FILE_DEVICE, "w");
441	    if (!file) {
442	        Serial.println(F("[WiFi] ❌ 无法创建配置文件"));
443	        return false;
444	    }
445
446	    if (serializeJson(doc, file) == 0) {
447	        Serial.println(F("[WiFi] ❌ 配置写入失败"));
448	        file.close();
449	        return false;
450	    }
451
452	    file.close();
453	    Serial.println(F("[WiFi] ✅ 设备配置已保存"));
454	    return true;
455	}
456
457	// ============================================================
458	// WiFiManager 保存参数回调
459	// ============================================================
460
461	// ============================================================
462	// 静态回调函数 (WiFiManager 要求 C 函数指针)
463	// ============================================================
464
465	void WiFiMgmt::_apCallback(WiFiManager* wm) {
466	    Serial.println(F("[WiFi] 🔧 进入配网模式"));
467	    Serial.printf("  AP WiFi名称: %s\n", WIFI_AP_SSID);
468	    Serial.printf("  AP 密码: %s\n", WIFI_AP_PASSWORD);
469	    Serial.println(F("  手机连接此 WiFi 即可打开配网页面 (自动弹出)"));
470	}
471
472	void WiFiMgmt::_saveParamsCallback() {
473	    if (!_instance) return;
474
475	    Serial.println(F("[WiFi] 💾 参数已保存"));
476
477	    // 从 WiFiManager 参数中读取值
478	    strncpy(_instance->_config.mqtt_server, _instance->_param_mqtt_server->getValue(),
479	            sizeof(_instance->_config.mqtt_server) - 1);
480	    strncpy(_instance->_config.mqtt_port,   _instance->_param_mqtt_port->getValue(),
481	            sizeof(_instance->_config.mqtt_port) - 1);
482	    strncpy(_instance->_config.mqtt_user,   _instance->_param_mqtt_user->getValue(),
483	            sizeof(_instance->_config.mqtt_user) - 1);
484	    strncpy(_instance->_config.mqtt_pass,   _instance->_param_mqtt_pass->getValue(),
485	            sizeof(_instance->_config.mqtt_pass) - 1);
486	    strncpy(_instance->_config.device_id,   _instance->_param_device_id->getValue(),
487	            sizeof(_instance->_config.device_id) - 1);
488	    strncpy(_instance->_config.env_note,    _instance->_param_env_note->getValue(),
489	            sizeof(_instance->_config.env_note) - 1);
490
491	    _instance->_saveConfig();
492
493	    Serial.println(F("[WiFi] 📋 新配置:"));
494	    Serial.printf("  MQTT: %s:%s\n",
495	                  _instance->_config.mqtt_server,
496	                  _instance->_config.mqtt_port);
497	    Serial.printf("  设备: %s\n", _instance->_config.device_id);
498	    Serial.printf("  环境: %s\n", _instance->_config.env_note);
499	}
500
501	// ============================================================
502	// Getter 方法
503	// ============================================================
504
505	bool WiFiMgmt::isConnected() const {
506	    return _connected && (WiFi.status() == WL_CONNECTED);
507	}
508
509	DeviceConfig WiFiMgmt::getConfig() const {
510	    return _config;
511	}
512
513	const char* WiFiMgmt::getDeviceId() const {
514	    return _config.device_id;
515	}
516
517	const char* WiFiMgmt::getEnvNote() const {
518	    return _config.env_note;
519	}
520
521	// ============================================================
522	// LED 模式控制 (线程安全, 仅写模式变量，由独立任务执行)
523	// ============================================================
524
525	void WiFiMgmt::_setLEDMode(uint8_t mode) {
526	    _ledMode = mode;  // uint8_t 赋值在 ESP32 上是原子的
527	}
528
529	// ============================================================
530	// LED 闪烁独立任务 (FreeRTOS)
531	// 独立于主循环，startConfigPortal 阻塞期间也能正常闪烁
532	// ============================================================
533
534	void WiFiMgmt::_ledTaskFunc(void* param) {
535	    const TickType_t fastMs = pdMS_TO_TICKS(300);   // 快闪 = 配网模式
536	    const TickType_t slowMs = pdMS_TO_TICKS(1000);  // 慢闪 = 未连接
537
538	    while (true) {
539	        if (!_instance) {
540	            vTaskDelay(slowMs);
541	            continue;
542	        }
543
544	        uint8_t mode = _instance->_ledMode;
545
546	        switch (mode) {
547	        case 0:  // 灭
548	            digitalWrite(LED_WIFI_PIN, LOW);
549	            vTaskDelay(slowMs);
550	            break;
551	        case 1:  // 慢闪 — WiFi 断连
552	            digitalWrite(LED_WIFI_PIN, HIGH);
553	            vTaskDelay(slowMs);
554	            digitalWrite(LED_WIFI_PIN, LOW);
555	            vTaskDelay(slowMs);
556	            break;
557	        case 2:  // 快闪 — 配网模式
558	            digitalWrite(LED_WIFI_PIN, HIGH);
559	            vTaskDelay(fastMs);
560	            digitalWrite(LED_WIFI_PIN, LOW);
561	            vTaskDelay(fastMs);
562	            break;
563	        case 3:  // 常亮 — 已连接
564	            digitalWrite(LED_WIFI_PIN, HIGH);
565	            vTaskDelay(slowMs);
566	            break;
567	        }
568	    }
569	}
570

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

#include "wifi_manager.h"

// 单例指针
WiFiMgmt* WiFiMgmt::_instance = nullptr;

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

WiFiMgmt::WiFiMgmt()
    : _connected(false)
    , _lastCheckTime(0)
    , _buttonPressTime(0)
    , _buttonPressed(false)
    , _ledTaskHandle(nullptr)
    , _ledMode(0)
    , _param_mqtt_server(nullptr)
    , _param_mqtt_port(nullptr)
    , _param_mqtt_user(nullptr)
    , _param_mqtt_pass(nullptr)
    , _param_device_id(nullptr)
    , _param_env_note(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);

    // 启动 LED 独立任务 (避免 startConfigPortal 阻塞影响闪烁)
    xTaskCreate(_ledTaskFunc, "WiFiLed", 1024, nullptr, 1, &_ledTaskHandle);

    // 初始化 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("观照-五劳无感 · 设备配网");
    _wm.setCustomHeadElement(
        "<meta name='theme-color' content='#12a889'>"
        ""
    );

    // 自定义菜单 HTML — 软件组设计的头部 + 引导弹窗
    _wm.setCustomMenuHTML(
        "
"
        /* 头部 */
        "
"
        "
📡
"
        "

观照-五劳无感 · 设备配网
"
        "
WiFi 联网与参数配置

"
        /* 帮助按钮 */
        "
?
"
        "
"
        /* 网关信息 */
        "
"
        "📍 当前网关"
        "Gateway
"
        "
"
        /* 引导弹窗 */
        "
"
        "
"
        "
"
        "
📋
"
        "

配网流程
"
        "
按顺序完成 4 步操作

"
        "
×

"
        "
"
        "

1

连接此热点
"
        "
手机连接 " + String(WIFI_AP_SSID) + " 热点，密码 " + String(WIFI_AP_PASSWORD) + "

"
        "

2

选择 WiFi 并输入密码
"
        "
选择您的 2.4GHz WiFi 网络

"
        "

3

填写设备信息
"
        "
设备编号与安装环境便于管理

"
        "

4

保存并等待连接
"
        "
指示灯常亮即表示配网完成

"
        "
"
        "
"
        "<button class='ghost' onclick='toggleHelp(false)'>稍后查看</button>"
        "<button class='pri' onclick='toggleHelp(false)'>我知道了</button>
"
        "

"
        ""
        ""
    );
    WiFi.setAutoReconnect(true);  // ESP32 自动重连

    // 注册保存回调 (必须使用静态函数, 不能使用 lambda)
    _wm.setSaveConfigCallback(_saveParamsCallback);

    // 创建自定义参数
    _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);
    _param_device_id = new WiFiManagerParameter(
        "device_id", "设备名称",
        _config.device_id, 20);
    _param_env_note = new WiFiManagerParameter(
        "env_note", "🏠 环境备注 (如: 主卧/客厅/老人房)",
        _config.env_note, 20);

    // 注册自定义参数
    _wm.addParameter(_param_mqtt_server);
    _wm.addParameter(_param_mqtt_port);
    _wm.addParameter(_param_mqtt_user);
    _wm.addParameter(_param_mqtt_pass);
    _wm.addParameter(_param_device_id);
    _wm.addParameter(_param_env_note);

    // 设置 AP 模式回调 (静态函数)
    _wm.setAPCallback(_apCallback);

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

    if (!_connected) {
        // 如果自动连接失败, 启动配网页面
        Serial.println(F("[WiFi] 自动连接失败, 启动配网模式..."));
        _setLEDMode(2);  // 快闪 = 配网模式
        _connected = _connectWiFi();  // 内部会启动配网页面
    }

    if (_connected) {
        _setLEDMode(3);  // 常亮 = 已连接
    }

    _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(1);  // 慢闪 = 未连接
            Serial.println(F("[WiFi] ⚠  WiFi 断开, 尝试重连..."));
            _connectWiFi();
        } else {
            _connected = true;
            _setLEDMode(3);  // 常亮 = 已连接
        }
    }

    // LED 由独立 FreeRTOS 任务处理 (不受 startConfigPortal 阻塞影响)
    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);
    }

    return _connected;
}

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

void WiFiMgmt::startConfigPortal() {
    Serial.println(F("[WiFi] 🔧 手动启动配网页面..."));
    _setLEDMode(2);  // 快闪 = 配网模式
    _wm.startConfigPortal(WIFI_AP_SSID, WIFI_AP_PASSWORD);
    _connected = (WiFi.status() == WL_CONNECTED);
    if (_connected) {
        _setLEDMode(3);  // 常亮 = 已连接
    }
}

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

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("[WiFi] 🔧 进入配网模式"));
    Serial.printf("  AP WiFi名称: %s\n", WIFI_AP_SSID);
    Serial.printf("  AP 密码: %s\n", WIFI_AP_PASSWORD);
    Serial.println(F("  手机连接此 WiFi 即可打开配网页面 (自动弹出)"));
}

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();

    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) {
    _ledMode = mode;  // uint8_t 赋值在 ESP32 上是原子的
}

// ============================================================
// LED 闪烁独立任务 (FreeRTOS)
// 独立于主循环，startConfigPortal 阻塞期间也能正常闪烁
// ============================================================

void WiFiMgmt::_ledTaskFunc(void* param) {
    const TickType_t fastMs = pdMS_TO_TICKS(300);   // 快闪 = 配网模式
    const TickType_t slowMs = pdMS_TO_TICKS(1000);  // 慢闪 = 未连接

    while (true) {
        if (!_instance) {
            vTaskDelay(slowMs);
            continue;
        }

        uint8_t mode = _instance->_ledMode;

        switch (mode) {
        case 0:  // 灭
            digitalWrite(LED_WIFI_PIN, LOW);
            vTaskDelay(slowMs);
            break;
        case 1:  // 慢闪 — WiFi 断连
            digitalWrite(LED_WIFI_PIN, HIGH);
            vTaskDelay(slowMs);
            digitalWrite(LED_WIFI_PIN, LOW);
            vTaskDelay(slowMs);
            break;
        case 2:  // 快闪 — 配网模式
            digitalWrite(LED_WIFI_PIN, HIGH);
            vTaskDelay(fastMs);
            digitalWrite(LED_WIFI_PIN, LOW);
            vTaskDelay(fastMs);
            break;
        case 3:  // 常亮 — 已连接
            digitalWrite(LED_WIFI_PIN, HIGH);
            vTaskDelay(slowMs);
            break;
        }
    }
}


src\radar_manager.cpp
src\button_manager.cpp
src\sensor_manager.cpp
src\alarm_manager.cpp
src\mqtt_manager.cpp
src\display_manager.cpp
src\main.cpp
src\wifi_manager.cpp


No files found


Now I need to look at the header file for wifi_manager to understand the pin definitions, and also check `main.cpp` to see how the WiFi manager is used and if there's any serial-related code that could affect the LED.

The user is saying: when the WiFi LED is on (常亮=已连接), if they close the serial monitor, the LED goes off. This suggests the LED or WiFi connection is somehow tied to the serial connection.

Let me look at main.cpp and the header file.


include\radar_protocol.h
include\wm_strings_zh.h
include\sensor_manager.h
include\radar_manager.h
include\config.h
include\button_manager.h
include\wifi_manager.h
include\alarm_manager.h
include\mqtt_manager.h
include\display_manager.h


1	/**
2	 * @file main.cpp
3	 * @brief 五劳无感康养监测系统 - 主程序 (协议 V0.08)
4	 *
5	 * ESP32-S3 + R60ABD1 60G 毫米波雷达 + DHT11 + 水浸
6	 * 本地紧急报警 (离线可用) + 服务端告警联动
7	 */
8
9	#include <Arduino.h>
10	#include <WiFi.h>
11	#include "config.h"
12	#include "radar_protocol.h"
13	#include "radar_manager.h"
14	#include "wifi_manager.h"
15	#include "mqtt_manager.h"
16	#include "sensor_manager.h"
17	#include "alarm_manager.h"
18	#include "display_manager.h"
19	#include "button_manager.h"
20
21	// ============================================================
22	// 全局对象
23	// ============================================================
24
25	RadarManager   g_radar;
26	WiFiMgmt       g_wifi;
27	MQTTManager    g_mqtt;
28	SensorManager  g_sensor;
29	AlarmManager   g_alarm;
30	DisplayManager g_display;
31	ButtonManager  g_buttons;
32
33	// 上报控制
34	uint32_t g_lastReportTime  = 0;
35	uint32_t g_sequence        = 1;       // 上报序号
36	uint32_t g_bootTime        = 0;       // 启动时刻
37
38	// 本地报警跟踪 (防重复触发)
39	bool    g_localFallActive       = false;
40	bool    g_localWaterActive      = false;
41	bool    g_localHeartAbnActive   = false;
42	bool    g_localBreathAbnActive  = false;
43	bool    g_localTempAbnActive    = false;
44	bool    g_localHumiAbnActive    = false;
45	bool    g_localRadarFaultActive = false;
46	bool    g_localDhtFaultActive   = false;
47
48	// 报警防抖计数器 (连续N次确认才触发)
49	uint8_t g_tempAbnCnt       = 0;
50	uint8_t g_humiAbnCnt       = 0;
51	uint8_t g_radarTimeoutCnt  = 0;
52	#define ALARM_HYSTERESIS    3   // 连续3次确认才触发
53
54	// ============================================================
55	// 时间同步
56	// ============================================================
57
58	void syncTime() {
59	    Serial.println(F("[时间] 正在同步时间..."));
60	    configTime(NTP_GMT_OFFSET_SEC, NTP_DAYLIGHT_OFFSET, NTP_SERVER, "pool.ntp.org");
61
62	    struct tm timeinfo;
63	    int retry = 0;
64	    while (!getLocalTime(&timeinfo) && retry < 20) {
65	        delay(500);
66	        Serial.print(".");
67	        retry++;
68	    }
69
70	    if (retry < 20) {
71	        Serial.println();
72	        Serial.printf("[时间] ✅ 时间同步成功: %04d-%02d-%02d %02d:%02d:%02d\n",
73	                      timeinfo.tm_year + 1900, timeinfo.tm_mon + 1,
74	                      timeinfo.tm_mday, timeinfo.tm_hour,
75	                      timeinfo.tm_min, timeinfo.tm_sec);
76	    } else {
77	        Serial.println(F("\n[时间] ⚠ 时间同步失败, 使用默认时间"));
78	    }
79	}
80
81	// ============================================================
82	// MQTT 告警指令回调 (协议规范 §5.1, §5.2)
83	// ============================================================
84
85	void handleAlarmCommand(const AlarmCommand& cmd) {
86	    if (strcmp(cmd.type, "alarm_restore") == 0) {
87	        // 告警恢复
88	        g_alarm.executeRestore(cmd);
89
90	        // 清除对应本地告警状态
91	        if (strcmp(cmd.alarmType, "fall") == 0)           g_localFallActive = false;
92	        else if (strcmp(cmd.alarmType, "water_leak") == 0) g_localWaterActive = false;
93	        else if (strcmp(cmd.alarmType, "heart_abnormal") == 0)  g_localHeartAbnActive = false;
94	        else if (strcmp(cmd.alarmType, "breath_abnormal") == 0) g_localBreathAbnActive = false;
95	    } else {
96	        // 告警触发
97	        g_alarm.executeAlarm(cmd);
98	    }
99
100	    // 回复响应 (§4.5)
101	    g_mqtt.publishCmdResponse(cmd.cmdId, cmd.type, cmd.alarmType,
102	                              true, "0", "");
103	}
104
105	// ============================================================
106	// MQTT 控制指令回调 (§5.3)
107	// ============================================================
108
109	void handleControlCommand(const ControlCommand& cmd) {
110	    Serial.printf("[控制] 收到控制指令: %s\n", cmd.controlType);
111
112	    bool success = true;
113	    const char* errCode = "0";
114	    const char* errMsg  = "";
115
116	    if (strcmp(cmd.controlType, "buzzer_test") == 0) {
117	        uint32_t dur = cmd.params_duration > 0 ? cmd.params_duration : 3;
118	        g_alarm.buzzerTest(dur);
119	    }
120	    else if (strcmp(cmd.controlType, "device_restart") == 0) {
121	        Serial.println(F("[控制] 🔄 远程重启..."));
122	        g_mqtt.publishCmdResponse(cmd.cmdId, "control", "", true, "0", "");
123	        g_mqtt.publishOffline();
124	        delay(500);
125	        ESP.restart();
126	    }
127	    else if (strcmp(cmd.controlType, "factory_reset") == 0) {
128	        Serial.println(F("[控制] 🔄 恢复出厂设置..."));
129	        g_wifi.resetSettings();
130	    }
131	    else if (strcmp(cmd.controlType, "enter_provision") == 0) {
132	        Serial.println(F("[控制] 📶 进入配网模式"));
133	        g_wifi.startConfigPortal();
134	    }
135	    else {
136	        success = false;
137	        errCode = "2001";
138	        errMsg  = "不支持的指令类型";
139	    }
140
141	    g_mqtt.publishCmdResponse(cmd.cmdId, "control", "",
142	                              success, errCode, errMsg);
143	}
144
145	// ============================================================
146	// MQTT 配网指令回调 (§5.4)
147	// ============================================================
148
149	void handleProvisionCommand(const ProvisionCommand& cmd) {
150	    Serial.printf("[配网] 📶 收到配网指令: SSID=%s\n", cmd.wifi_ssid);
151
152	    if (strlen(cmd.wifi_ssid) == 0) {
153	        g_mqtt.publishProvisionResponse(cmd.cmdId, false,
154	                                        "1002", "未提供WiFi SSID");
155	        return;
156	    }
157
158	    // 连接新 WiFi
159	    g_mqtt.publishProvisionStatus("wifi_connecting", "");
160
161	    WiFi.begin(cmd.wifi_ssid, cmd.wifi_password);
162	    int retry = 0;
163	    while (WiFi.status() != WL_CONNECTED && retry < 30) {
164	        delay(500);
165	        Serial.print(".");
166	        retry++;
167	    }
168
169	    if (WiFi.status() == WL_CONNECTED) {
170	        Serial.printf("\n[配网] ✅ WiFi 已连接, IP=%s\n",
171	                      WiFi.localIP().toString().c_str());
172
173	        // 更新 MQTT 服务器配置
174	        if (strlen(cmd.mqtt_broker) > 0) {
175	            g_mqtt.setServer(cmd.mqtt_broker, cmd.mqtt_port,
176	                           GATEID_DEFAULT, GATEID_DEFAULT); // 正式账号 = gateid
177	        }
178
179	        char signal[8];
180	        snprintf(signal, sizeof(signal), "%d", WiFi.RSSI());
181	        g_mqtt.publishProvisionResponse(cmd.cmdId, true, "0", "", signal);
182
183	        // 重连 MQTT
184	        g_mqtt.disconnect();
185	        g_mqtt.connect();
186	        g_mqtt.publishOnline();
187	    } else {
188	        Serial.println(F("\n[配网] ❌ WiFi 连接失败"));
189	        g_mqtt.publishProvisionStatus("wifi_failed", "");
190	        g_mqtt.publishProvisionResponse(cmd.cmdId, false,
191	                                        "1003", "WiFi连接超时");
192	    }
193	}
194
195	// ============================================================
196	// 构建遥测数据
197	// ============================================================
198
199	TelemetryData buildTelemetry(const RadarRawData& radar) {
200	    TelemetryData t;
201	    memset(&t, 0, sizeof(t));
202
203	    // 设备标识
204	    strncpy(t.saleid,    SALEID_DEFAULT,   sizeof(t.saleid) - 1);
205	    strncpy(t.gateid,    g_mqtt.getGateId(), sizeof(t.gateid) - 1);
206	    strncpy(t.meterId,   METER_ID_DEFAULT, sizeof(t.meterId) - 1);
207	    strncpy(t.meterName, METER_NAME_DEFAULT, sizeof(t.meterName) - 1);
208
209	    // DHT11
210	    t.temp     = g_sensor.getTemp();
211	    t.humi     = g_sensor.getHumi();
212	    t.dhtValid = g_sensor.isDhtValid();
213
214	    // 雷达
215	    t.presence   = radar.presence;
216	    t.motion     = radar.motion;
217	    t.fall       = radar.fall;
218	    t.resident   = radar.resident;
219	    t.heartRate  = radar.heartRate;
220	    t.breathRate = radar.breathRate;
221	    t.body       = radar.body;
222	    t.radarValid = radar.radarValid;
223	    t.vitalValid = radar.vitalValid;
224
225	    // 水浸
226	    t.rainDO    = g_sensor.getRainDO();
227	    t.rainValid = g_sensor.isRainValid();
228
229	    // 设备状态
230	    t.wifiReady  = g_wifi.isConnected() && g_mqtt.isConnected();
231	    t.uptimeSec  = (millis() - g_bootTime) / 1000;
232	    t.buzzerState = g_alarm.isBuzzerOn();
233	    t.lastUpdate = millis();
234
235	    return t;
236	}
237
238	// ============================================================
239	// 本地报警回调 → MQTT 上报 (连接时)
240	// ============================================================
241
242	void onLocalAlarmChange(uint8_t type, bool triggered, const char* desc) {
243	    if (g_mqtt.isConnected()) {
244	        g_mqtt.publishLocalAlarm(type, triggered, desc);
245	    }
246	}
247
248	// ============================================================
249	// 本地报警检查 (离线可用) — 人身安全 > 环境 > 设备
250	// ============================================================
251
252	void checkLocalAlarms(const RadarRawData& radar) {
253	    // ─── 前置条件: 有人才检测人体生命体征 ───
254	    bool humanPresent = radar.presence && radar.radarValid;
255
256	    // ─── ★★★★★ 人身安全: 跌倒 (立即触发, 无防抖) ───
257	    if (radar.fall && humanPresent && !g_localFallActive) {
258	        g_localFallActive = true;
259	        g_alarm.triggerLocal(AlarmManager::ALARM_FALL, "雷达检测到跌倒");
260	    } else if (!radar.fall && g_localFallActive) {
261	        g_localFallActive = false;
262	        g_alarm.clearLocal(AlarmManager::ALARM_FALL);
263	    }
264
265	    // ─── ★★★★★ 人身安全: 心率异常 (有人才检测) ───
266	    if (humanPresent && radar.vitalValid && radar.heartRate > 0) {
267	        bool heartAbn = (radar.heartRate < HEART_RATE_MIN || radar.heartRate > HEART_RATE_MAX);
268	        if (heartAbn && !g_localHeartAbnActive) {
269	            g_localHeartAbnActive = true;
270	            char buf[32];
271	            snprintf(buf, sizeof(buf), "心率异常: %d bpm", radar.heartRate);
272	            g_alarm.triggerLocal(AlarmManager::ALARM_HEART_ABNORMAL, buf);
273	        } else if (!heartAbn && g_localHeartAbnActive) {
274	            g_localHeartAbnActive = false;
275	            g_alarm.clearLocal(AlarmManager::ALARM_HEART_ABNORMAL);
276	        }
277	    } else if (!humanPresent && g_localHeartAbnActive) {
278	        // 人走了 — 清除之前的报警
279	        g_localHeartAbnActive = false;
280	        g_alarm.clearLocal(AlarmManager::ALARM_HEART_ABNORMAL);
281	    }
282
283	    // ─── ★★★★★ 人身安全: 呼吸异常 (有人才检测) ───
284	    if (humanPresent && radar.vitalValid && radar.breathRate > 0) {
285	        bool breathAbn = (radar.breathRate < BREATH_RATE_MIN || radar.breathRate > BREATH_RATE_MAX);
286	        if (breathAbn && !g_localBreathAbnActive) {
287	            g_localBreathAbnActive = true;
288	            char buf[32];
289	            snprintf(buf, sizeof(buf), "呼吸异常: %d 次/分", radar.breathRate);
290	            g_alarm.triggerLocal(AlarmManager::ALARM_BREATH_ABNORMAL, buf);
291	        } else if (!breathAbn && g_localBreathAbnActive) {
292	            g_localBreathAbnActive = false;
293	            g_alarm.clearLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
294	        }
295	    } else if (!humanPresent && g_localBreathAbnActive) {
296	        g_localBreathAbnActive = false;
297	        g_alarm.clearLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
298	    }
299
300	    // ─── ★★★ 环境安全: 水浸 ───
301	    bool waterLeak = !g_sensor.getRainDO() && g_sensor.isRainValid();
302	    if (waterLeak && !g_localWaterActive) {
303	        g_localWaterActive = true;
304	        g_alarm.triggerLocal(AlarmManager::ALARM_WATER_LEAK, "水浸传感器触发");
305	    } else if (!waterLeak && g_localWaterActive) {
306	        g_localWaterActive = false;
307	        g_alarm.clearLocal(AlarmManager::ALARM_WATER_LEAK);
308	    }
309
310	    // ─── ★★ 环境舒适: 温度异常 (防抖3次) ───
311	    if (g_sensor.isDhtValid()) {
312	        float temp = g_sensor.getTemp();
313	        bool tempAbn = (temp < TEMP_LOW || temp > TEMP_HIGH);
314	        if (tempAbn) {
315	            if (++g_tempAbnCnt >= ALARM_HYSTERESIS && !g_localTempAbnActive) {
316	                g_localTempAbnActive = true;
317	                char buf[32];
318	                snprintf(buf, sizeof(buf), "温度%.1f°C 超出范围", temp);
319	                g_alarm.triggerLocal(AlarmManager::ALARM_TEMP_ABNORMAL, buf);
320	            }
321	        } else {
322	            g_tempAbnCnt = 0;
323	            if (g_localTempAbnActive) {
324	                g_localTempAbnActive = false;
325	                g_alarm.clearLocal(AlarmManager::ALARM_TEMP_ABNORMAL);
326	            }
327	        }
328
329	        // ─── ★★ 环境舒适: 湿度异常 (防抖3次) ───
330	        float humi = g_sensor.getHumi();
331	        bool humiAbn = (humi < HUMI_LOW || humi > HUMI_HIGH);
332	        if (humiAbn) {
333	            if (++g_humiAbnCnt >= ALARM_HYSTERESIS && !g_localHumiAbnActive) {
334	                g_localHumiAbnActive = true;
335	                char buf[32];
336	                snprintf(buf, sizeof(buf), "湿度%.1f%% 超出范围", humi);
337	                g_alarm.triggerLocal(AlarmManager::ALARM_HUMI_ABNORMAL, buf);
338	            }
339	        } else {
340	            g_humiAbnCnt = 0;
341	            if (g_localHumiAbnActive) {
342	                g_localHumiAbnActive = false;
343	                g_alarm.clearLocal(AlarmManager::ALARM_HUMI_ABNORMAL);
344	            }
345	        }
346	    }
347
348	    // ─── ★ 设备：雷达超时 (连续5秒无数据) ───
349	    if (g_radar.isTimeout()) {
350	        if (++g_radarTimeoutCnt >= 5 && !g_localRadarFaultActive) {
351	            g_localRadarFaultActive = true;
352	            g_alarm.triggerLocal(AlarmManager::ALARM_SENSOR_FAULT, "雷达超时无数据");
353	        }
354	    } else {
355	        g_radarTimeoutCnt = 0;
356	        if (g_localRadarFaultActive) {
357	            g_localRadarFaultActive = false;
358	            g_alarm.clearLocal(AlarmManager::ALARM_SENSOR_FAULT);
359	        }
360	    }
361
362	    // ─── ★ 设备：DHT 持续故障 (已由 SensorManager 内部计数) ───
363	    if (g_sensor.isDhtFault() && !g_localDhtFaultActive && millis() > 60000) {
364	        g_localDhtFaultActive = true;
365	        g_alarm.triggerLocal(AlarmManager::ALARM_SENSOR_FAULT, "DHT11温湿度传感器故障");
366	    } else if (!g_sensor.isDhtFault() && g_localDhtFaultActive) {
367	        g_localDhtFaultActive = false;
368	        g_alarm.clearLocal(AlarmManager::ALARM_SENSOR_FAULT);
369	    }
370	}
371
372	// ============================================================
373	// 调试输出
374	// ============================================================
375
376	void printDebug(const TelemetryData& t) {
377	    static uint32_t lastPrint = 0;
378	    if (millis() - lastPrint < 5000) return;
379	    lastPrint = millis();
380
381	    Serial.println(F("─── 遥测数据 ───"));
382	    Serial.printf("  环境: T=%.1f°C H=%.1f%% (温湿度:%d)\n",
383	                  t.temp, t.humi, t.dhtValid);
384	    Serial.printf("  存在:%d 运动:%d 跌倒:%d 驻留:%d (雷达:%d)\n",
385	                  t.presence, t.motion, t.fall, t.resident, t.radarValid);
386	    Serial.printf("  心率:%d 呼吸:%d (体征:%d)\n",
387	                  t.heartRate, t.breathRate, t.vitalValid);
388	    Serial.printf("  水浸:%d (水浸有效:%d) | WiFi:%d 蜂鸣器:%d\n",
389	                  t.rainDO, t.rainValid, t.wifiReady, t.buzzerState);
390	    Serial.printf("  运行:%ds 序号:%d\n", t.uptimeSec, g_sequence);
391	    Serial.println(F("─────────────────"));
392	}
393
394	// ============================================================
395	// 雷达任务 - 核心0 专用
396	// ============================================================
397
398	void radarTask(void* param) {
399	    Serial.println(F("[任务] 📡 雷达任务启动 (核心0)"));
400	    TickType_t lastWake = xTaskGetTickCount();
401	    const TickType_t freq = pdMS_TO_TICKS(10);
402
403	    while (true) {
404	        g_radar.loop();
405	        vTaskDelayUntil(&lastWake, freq);
406	    }
407	}
408
409	// ============================================================
410	// Setup
411	// ============================================================
412
413	void setup() {
414	    Serial.begin(115200);
415	    delay(500);
416
417	    Serial.println(F("\n"));
418	    Serial.println(F("╔══════════════════════════════════════════╗"));
419	    Serial.println(F("║  🏥 五劳无感康养监测系统 v1.0.0       ║"));
420	    Serial.println(F("║  ESP32-S3 + R60ABD1 + DHT11 + 水浸     ║"));
421	    Serial.println(F("║  MQTT 协议 V0.08                        ║"));
422	    Serial.println(F("╚══════════════════════════════════════════╝"));
423
424	    g_bootTime = millis();
425
426	    // 1. 报警模块 (最先, 引脚安全)
427	    g_alarm.begin();
428
429	    // 2. 雷达
430	    g_radar.begin();
431
432	    // 3. OLED 显示
433	    g_display.begin();
434
435	    // 4. 按钮
436	    g_buttons.begin();
437	    g_buttons.onPageSwitch([]() {
438	        g_display.nextPage();
439	    });
440	    g_buttons.onAlarmDismiss([]() {
441	        g_alarm.clearAllLocal();
442	    });
443	    g_buttons.onProvisionLongPress([]() {
444	        g_wifi.startConfigPortal();
445	    });
446
447	    // 5. WiFi (WiFiManager 配网)
448	    g_wifi.begin();
449
450	    // 6. 时间同步
451	    if (g_wifi.isConnected()) {
452	        syncTime();
453	    }
454
455	    // 7. MQTT
456	    DeviceConfig cfg = g_wifi.getConfig();
457	    g_mqtt.setServer(cfg.mqtt_server, atoi(cfg.mqtt_port),
458	                     cfg.mqtt_user, cfg.mqtt_pass);
459	    g_mqtt.begin(cfg.device_id);
460
461	    // 注册 MQTT 回调
462	    g_mqtt.onAlarmCommand(handleAlarmCommand);
463	    g_mqtt.onControlCommand(handleControlCommand);
464	    g_mqtt.onProvisionCommand(handleProvisionCommand);
465
466	    // 注册本地报警回调 → MQTT 上报
467	    g_alarm.onAlarmChange(onLocalAlarmChange);
468
469	    // 连接 MQTT
470	    if (g_wifi.isConnected()) {
471	        g_mqtt.connect();
472	    }
473
474	    // 8. 传感器
475	    g_sensor.begin();
476
477	    // 9. 雷达任务 (核心0)
478	    xTaskCreatePinnedToCore(
479	        radarTask, "RadarTask",
480	        RADAR_TASK_STACK, nullptr,
481	        RADAR_TASK_PRIORITY, nullptr,
482	        RADAR_TASK_CORE
483	    );
484
485	    Serial.println(F("[系统] ✅ 系统就绪"));
486	    Serial.println(F("══════════════════════════════════════════\n"));
487	}
488
489	// ============================================================
490	// 主循环 (核心1)
491	// ============================================================
492
493	void loop() {
494	    uint32_t now = millis();
495
496	    // --- 0. 按钮轮询 ---
497	    g_buttons.loop();
498
499	    // --- 1. WiFi ---
500	    bool wifiOk = g_wifi.loop();
501
502	    // --- 2. MQTT (含自动重连) ---
503	    if (wifiOk) {
504	        g_mqtt.loop();
505
506	        // WiFi 刚连上但 MQTT 还没连
507	        static bool wasDisconnected = true;
508	        if (g_mqtt.isConnected()) {
509	            if (wasDisconnected) {
510	                // 刚恢复连接, 同步时间
511	                syncTime();
512	                wasDisconnected = false;
513	            }
514	        } else {
515	            wasDisconnected = true;
516	        }
517	    }
518
519	    // --- 3. 传感器 (独立，任何传感器故障不影响其他) ---
520	    g_sensor.read();
521
522	    // --- 4. 本地报警检查 (离线也工作) ---
523	    RadarRawData radar = g_radar.getData();
524	    checkLocalAlarms(radar);
525
526	    // --- 传感器故障日志 (报警系统统一管理触发, 此处仅记录) ---
527	    static uint32_t lastFaultLog = 0;
528	    if (now - lastFaultLog > 60000) {
529	        lastFaultLog = now;
530	        if (!g_sensor.isDhtValid() && millis() > 60000 && !g_localDhtFaultActive) {
531	            Serial.println(F("[系统] ⚠ DHT11 读数异常"));
532	        }
533	        if (g_radar.isTimeout() && !g_localRadarFaultActive) {
534	            Serial.println(F("[系统] ⚠ 雷达超时无数据"));
535	        }
536	    }
537
538	    // --- 5. 报警模块更新 (定时器/闪烁) ---
539	    g_alarm.loop();
540
541	    // --- 6. 遥测上报 (1秒周期) ---
542	    if (now - g_lastReportTime >= REPORT_INTERVAL_MS) {
543	        g_lastReportTime = now;
544
545	        TelemetryData tele = buildTelemetry(radar);
546	        g_mqtt.publishAllPoints(tele, g_sequence);
547	        g_sequence++;
548
549	        // 调试输出 + OLED 显示
550	        printDebug(tele);
551	        g_display.update(tele, wifiOk, g_alarm.isBuzzerOn(),
552	                         g_alarm.hasActiveAlarm(), g_alarm.getActiveAlarmStr());
553	    }
554
555	    // --- 7. MQTT 配网状态 (WiFiManager 触发时) ---
556	    // WiFiManager 的 captive portal 启动/关闭时更新状态
557	    // 此逻辑已集成在 wifi_manager 中
558
559	    delay(10);
560	}
561

/**
 * @file main.cpp
 * @brief 五劳无感康养监测系统 - 主程序 (协议 V0.08)
 *
 * ESP32-S3 + R60ABD1 60G 毫米波雷达 + DHT11 + 水浸
 * 本地紧急报警 (离线可用) + 服务端告警联动
 */

#include <Arduino.h>
#include <WiFi.h>
#include "config.h"
#include "radar_protocol.h"
#include "radar_manager.h"
#include "wifi_manager.h"
#include "mqtt_manager.h"
#include "sensor_manager.h"
#include "alarm_manager.h"
#include "display_manager.h"
#include "button_manager.h"

// ============================================================
// 全局对象
// ============================================================

RadarManager   g_radar;
WiFiMgmt       g_wifi;
MQTTManager    g_mqtt;
SensorManager  g_sensor;
AlarmManager   g_alarm;
DisplayManager g_display;
ButtonManager  g_buttons;

// 上报控制
uint32_t g_lastReportTime  = 0;
uint32_t g_sequence        = 1;       // 上报序号
uint32_t g_bootTime        = 0;       // 启动时刻

// 本地报警跟踪 (防重复触发)
bool    g_localFallActive       = false;
bool    g_localWaterActive      = false;
bool    g_localHeartAbnActive   = false;
bool    g_localBreathAbnActive  = false;
bool    g_localTempAbnActive    = false;
bool    g_localHumiAbnActive    = false;
bool    g_localRadarFaultActive = false;
bool    g_localDhtFaultActive   = false;

// 报警防抖计数器 (连续N次确认才触发)
uint8_t g_tempAbnCnt       = 0;
uint8_t g_humiAbnCnt       = 0;
uint8_t g_radarTimeoutCnt  = 0;
#define ALARM_HYSTERESIS    3   // 连续3次确认才触发

// ============================================================
// 时间同步
// ============================================================

void syncTime() {
    Serial.println(F("[时间] 正在同步时间..."));
    configTime(NTP_GMT_OFFSET_SEC, NTP_DAYLIGHT_OFFSET, NTP_SERVER, "pool.ntp.org");

    struct tm timeinfo;
    int retry = 0;
    while (!getLocalTime(&timeinfo) && retry < 20) {
        delay(500);
        Serial.print(".");
        retry++;
    }

    if (retry < 20) {
        Serial.println();
        Serial.printf("[时间] ✅ 时间同步成功: %04d-%02d-%02d %02d:%02d:%02d\n",
                      timeinfo.tm_year + 1900, timeinfo.tm_mon + 1,
                      timeinfo.tm_mday, timeinfo.tm_hour,
                      timeinfo.tm_min, timeinfo.tm_sec);
    } else {
        Serial.println(F("\n[时间] ⚠ 时间同步失败, 使用默认时间"));
    }
}

// ============================================================
// MQTT 告警指令回调 (协议规范 §5.1, §5.2)
// ============================================================

void handleAlarmCommand(const AlarmCommand& cmd) {
    if (strcmp(cmd.type, "alarm_restore") == 0) {
        // 告警恢复
        g_alarm.executeRestore(cmd);

        // 清除对应本地告警状态
        if (strcmp(cmd.alarmType, "fall") == 0)           g_localFallActive = false;
        else if (strcmp(cmd.alarmType, "water_leak") == 0) g_localWaterActive = false;
        else if (strcmp(cmd.alarmType, "heart_abnormal") == 0)  g_localHeartAbnActive = false;
        else if (strcmp(cmd.alarmType, "breath_abnormal") == 0) g_localBreathAbnActive = false;
    } else {
        // 告警触发
        g_alarm.executeAlarm(cmd);
    }

    // 回复响应 (§4.5)
    g_mqtt.publishCmdResponse(cmd.cmdId, cmd.type, cmd.alarmType,
                              true, "0", "");
}

// ============================================================
// MQTT 控制指令回调 (§5.3)
// ============================================================

void handleControlCommand(const ControlCommand& cmd) {
    Serial.printf("[控制] 收到控制指令: %s\n", cmd.controlType);

    bool success = true;
    const char* errCode = "0";
    const char* errMsg  = "";

    if (strcmp(cmd.controlType, "buzzer_test") == 0) {
        uint32_t dur = cmd.params_duration > 0 ? cmd.params_duration : 3;
        g_alarm.buzzerTest(dur);
    }
    else if (strcmp(cmd.controlType, "device_restart") == 0) {
        Serial.println(F("[控制] 🔄 远程重启..."));
        g_mqtt.publishCmdResponse(cmd.cmdId, "control", "", true, "0", "");
        g_mqtt.publishOffline();
        delay(500);
        ESP.restart();
    }
    else if (strcmp(cmd.controlType, "factory_reset") == 0) {
        Serial.println(F("[控制] 🔄 恢复出厂设置..."));
        g_wifi.resetSettings();
    }
    else if (strcmp(cmd.controlType, "enter_provision") == 0) {
        Serial.println(F("[控制] 📶 进入配网模式"));
        g_wifi.startConfigPortal();
    }
    else {
        success = false;
        errCode = "2001";
        errMsg  = "不支持的指令类型";
    }

    g_mqtt.publishCmdResponse(cmd.cmdId, "control", "",
                              success, errCode, errMsg);
}

// ============================================================
// MQTT 配网指令回调 (§5.4)
// ============================================================

void handleProvisionCommand(const ProvisionCommand& cmd) {
    Serial.printf("[配网] 📶 收到配网指令: SSID=%s\n", cmd.wifi_ssid);

    if (strlen(cmd.wifi_ssid) == 0) {
        g_mqtt.publishProvisionResponse(cmd.cmdId, false,
                                        "1002", "未提供WiFi SSID");
        return;
    }

    // 连接新 WiFi
    g_mqtt.publishProvisionStatus("wifi_connecting", "");

    WiFi.begin(cmd.wifi_ssid, cmd.wifi_password);
    int retry = 0;
    while (WiFi.status() != WL_CONNECTED && retry < 30) {
        delay(500);
        Serial.print(".");
        retry++;
    }

    if (WiFi.status() == WL_CONNECTED) {
        Serial.printf("\n[配网] ✅ WiFi 已连接, IP=%s\n",
                      WiFi.localIP().toString().c_str());

        // 更新 MQTT 服务器配置
        if (strlen(cmd.mqtt_broker) > 0) {
            g_mqtt.setServer(cmd.mqtt_broker, cmd.mqtt_port,
                           GATEID_DEFAULT, GATEID_DEFAULT); // 正式账号 = gateid
        }

        char signal[8];
        snprintf(signal, sizeof(signal), "%d", WiFi.RSSI());
        g_mqtt.publishProvisionResponse(cmd.cmdId, true, "0", "", signal);

        // 重连 MQTT
        g_mqtt.disconnect();
        g_mqtt.connect();
        g_mqtt.publishOnline();
    } else {
        Serial.println(F("\n[配网] ❌ WiFi 连接失败"));
        g_mqtt.publishProvisionStatus("wifi_failed", "");
        g_mqtt.publishProvisionResponse(cmd.cmdId, false,
                                        "1003", "WiFi连接超时");
    }
}

// ============================================================
// 构建遥测数据
// ============================================================

TelemetryData buildTelemetry(const RadarRawData& radar) {
    TelemetryData t;
    memset(&t, 0, sizeof(t));

    // 设备标识
    strncpy(t.saleid,    SALEID_DEFAULT,   sizeof(t.saleid) - 1);
    strncpy(t.gateid,    g_mqtt.getGateId(), sizeof(t.gateid) - 1);
    strncpy(t.meterId,   METER_ID_DEFAULT, sizeof(t.meterId) - 1);
    strncpy(t.meterName, METER_NAME_DEFAULT, sizeof(t.meterName) - 1);

    // DHT11
    t.temp     = g_sensor.getTemp();
    t.humi     = g_sensor.getHumi();
    t.dhtValid = g_sensor.isDhtValid();

    // 雷达
    t.presence   = radar.presence;
    t.motion     = radar.motion;
    t.fall       = radar.fall;
    t.resident   = radar.resident;
    t.heartRate  = radar.heartRate;
    t.breathRate = radar.breathRate;
    t.body       = radar.body;
    t.radarValid = radar.radarValid;
    t.vitalValid = radar.vitalValid;

    // 水浸
    t.rainDO    = g_sensor.getRainDO();
    t.rainValid = g_sensor.isRainValid();

    // 设备状态
    t.wifiReady  = g_wifi.isConnected() && g_mqtt.isConnected();
    t.uptimeSec  = (millis() - g_bootTime) / 1000;
    t.buzzerState = g_alarm.isBuzzerOn();
    t.lastUpdate = millis();

    return t;
}

// ============================================================
// 本地报警回调 → MQTT 上报 (连接时)
// ============================================================

void onLocalAlarmChange(uint8_t type, bool triggered, const char* desc) {
    if (g_mqtt.isConnected()) {
        g_mqtt.publishLocalAlarm(type, triggered, desc);
    }
}

// ============================================================
// 本地报警检查 (离线可用) — 人身安全 > 环境 > 设备
// ============================================================

void checkLocalAlarms(const RadarRawData& radar) {
    // ─── 前置条件: 有人才检测人体生命体征 ───
    bool humanPresent = radar.presence && radar.radarValid;

    // ─── ★★★★★ 人身安全: 跌倒 (立即触发, 无防抖) ───
    if (radar.fall && humanPresent && !g_localFallActive) {
        g_localFallActive = true;
        g_alarm.triggerLocal(AlarmManager::ALARM_FALL, "雷达检测到跌倒");
    } else if (!radar.fall && g_localFallActive) {
        g_localFallActive = false;
        g_alarm.clearLocal(AlarmManager::ALARM_FALL);
    }

    // ─── ★★★★★ 人身安全: 心率异常 (有人才检测) ───
    if (humanPresent && radar.vitalValid && radar.heartRate > 0) {
        bool heartAbn = (radar.heartRate < HEART_RATE_MIN || radar.heartRate > HEART_RATE_MAX);
        if (heartAbn && !g_localHeartAbnActive) {
            g_localHeartAbnActive = true;
            char buf[32];
            snprintf(buf, sizeof(buf), "心率异常: %d bpm", radar.heartRate);
            g_alarm.triggerLocal(AlarmManager::ALARM_HEART_ABNORMAL, buf);
        } else if (!heartAbn && g_localHeartAbnActive) {
            g_localHeartAbnActive = false;
            g_alarm.clearLocal(AlarmManager::ALARM_HEART_ABNORMAL);
        }
    } else if (!humanPresent && g_localHeartAbnActive) {
        // 人走了 — 清除之前的报警
        g_localHeartAbnActive = false;
        g_alarm.clearLocal(AlarmManager::ALARM_HEART_ABNORMAL);
    }

    // ─── ★★★★★ 人身安全: 呼吸异常 (有人才检测) ───
    if (humanPresent && radar.vitalValid && radar.breathRate > 0) {
        bool breathAbn = (radar.breathRate < BREATH_RATE_MIN || radar.breathRate > BREATH_RATE_MAX);
        if (breathAbn && !g_localBreathAbnActive) {
            g_localBreathAbnActive = true;
            char buf[32];
            snprintf(buf, sizeof(buf), "呼吸异常: %d 次/分", radar.breathRate);
            g_alarm.triggerLocal(AlarmManager::ALARM_BREATH_ABNORMAL, buf);
        } else if (!breathAbn && g_localBreathAbnActive) {
            g_localBreathAbnActive = false;
            g_alarm.clearLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
        }
    } else if (!humanPresent && g_localBreathAbnActive) {
        g_localBreathAbnActive = false;
        g_alarm.clearLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
    }

    // ─── ★★★ 环境安全: 水浸 ───
    bool waterLeak = !g_sensor.getRainDO() && g_sensor.isRainValid();
    if (waterLeak && !g_localWaterActive) {
        g_localWaterActive = true;
        g_alarm.triggerLocal(AlarmManager::ALARM_WATER_LEAK, "水浸传感器触发");
    } else if (!waterLeak && g_localWaterActive) {
        g_localWaterActive = false;
        g_alarm.clearLocal(AlarmManager::ALARM_WATER_LEAK);
    }

    // ─── ★★ 环境舒适: 温度异常 (防抖3次) ───
    if (g_sensor.isDhtValid()) {
        float temp = g_sensor.getTemp();
        bool tempAbn = (temp < TEMP_LOW || temp > TEMP_HIGH);
        if (tempAbn) {
            if (++g_tempAbnCnt >= ALARM_HYSTERESIS && !g_localTempAbnActive) {
                g_localTempAbnActive = true;
                char buf[32];
                snprintf(buf, sizeof(buf), "温度%.1f°C 超出范围", temp);
                g_alarm.triggerLocal(AlarmManager::ALARM_TEMP_ABNORMAL, buf);
            }
        } else {
            g_tempAbnCnt = 0;
            if (g_localTempAbnActive) {
                g_localTempAbnActive = false;
                g_alarm.clearLocal(AlarmManager::ALARM_TEMP_ABNORMAL);
            }
        }

        // ─── ★★ 环境舒适: 湿度异常 (防抖3次) ───
        float humi = g_sensor.getHumi();
        bool humiAbn = (humi < HUMI_LOW || humi > HUMI_HIGH);
        if (humiAbn) {
            if (++g_humiAbnCnt >= ALARM_HYSTERESIS && !g_localHumiAbnActive) {
                g_localHumiAbnActive = true;
                char buf[32];
                snprintf(buf, sizeof(buf), "湿度%.1f%% 超出范围", humi);
                g_alarm.triggerLocal(AlarmManager::ALARM_HUMI_ABNORMAL, buf);
            }
        } else {
            g_humiAbnCnt = 0;
            if (g_localHumiAbnActive) {
                g_localHumiAbnActive = false;
                g_alarm.clearLocal(AlarmManager::ALARM_HUMI_ABNORMAL);
            }
        }
    }

    // ─── ★ 设备：雷达超时 (连续5秒无数据) ───
    if (g_radar.isTimeout()) {
        if (++g_radarTimeoutCnt >= 5 && !g_localRadarFaultActive) {
            g_localRadarFaultActive = true;
            g_alarm.triggerLocal(AlarmManager::ALARM_SENSOR_FAULT, "雷达超时无数据");
        }
    } else {
        g_radarTimeoutCnt = 0;
        if (g_localRadarFaultActive) {
            g_localRadarFaultActive = false;
            g_alarm.clearLocal(AlarmManager::ALARM_SENSOR_FAULT);
        }
    }

    // ─── ★ 设备：DHT 持续故障 (已由 SensorManager 内部计数) ───
    if (g_sensor.isDhtFault() && !g_localDhtFaultActive && millis() > 60000) {
        g_localDhtFaultActive = true;
        g_alarm.triggerLocal(AlarmManager::ALARM_SENSOR_FAULT, "DHT11温湿度传感器故障");
    } else if (!g_sensor.isDhtFault() && g_localDhtFaultActive) {
        g_localDhtFaultActive = false;
        g_alarm.clearLocal(AlarmManager::ALARM_SENSOR_FAULT);
    }
}

// ============================================================
// 调试输出
// ============================================================

void printDebug(const TelemetryData& t) {
    static uint32_t lastPrint = 0;
    if (millis() - lastPrint < 5000) return;
    lastPrint = millis();

    Serial.println(F("─── 遥测数据 ───"));
    Serial.printf("  环境: T=%.1f°C H=%.1f%% (温湿度:%d)\n",
                  t.temp, t.humi, t.dhtValid);
    Serial.printf("  存在:%d 运动:%d 跌倒:%d 驻留:%d (雷达:%d)\n",
                  t.presence, t.motion, t.fall, t.resident, t.radarValid);
    Serial.printf("  心率:%d 呼吸:%d (体征:%d)\n",
                  t.heartRate, t.breathRate, t.vitalValid);
    Serial.printf("  水浸:%d (水浸有效:%d) | WiFi:%d 蜂鸣器:%d\n",
                  t.rainDO, t.rainValid, t.wifiReady, t.buzzerState);
    Serial.printf("  运行:%ds 序号:%d\n", t.uptimeSec, g_sequence);
    Serial.println(F("─────────────────"));
}

// ============================================================
// 雷达任务 - 核心0 专用
// ============================================================

void radarTask(void* param) {
    Serial.println(F("[任务] 📡 雷达任务启动 (核心0)"));
    TickType_t lastWake = xTaskGetTickCount();
    const TickType_t freq = pdMS_TO_TICKS(10);

    while (true) {
        g_radar.loop();
        vTaskDelayUntil(&lastWake, freq);
    }
}

// ============================================================
// Setup
// ============================================================

void setup() {
    Serial.begin(115200);
    delay(500);

    Serial.println(F("\n"));
    Serial.println(F("╔══════════════════════════════════════════╗"));
    Serial.println(F("║  🏥 五劳无感康养监测系统 v1.0.0       ║"));
    Serial.println(F("║  ESP32-S3 + R60ABD1 + DHT11 + 水浸     ║"));
    Serial.println(F("║  MQTT 协议 V0.08                        ║"));
    Serial.println(F("╚══════════════════════════════════════════╝"));

    g_bootTime = millis();

    // 1. 报警模块 (最先, 引脚安全)
    g_alarm.begin();

    // 2. 雷达
    g_radar.begin();

    // 3. OLED 显示
    g_display.begin();

    // 4. 按钮
    g_buttons.begin();
    g_buttons.onPageSwitch([]() {
        g_display.nextPage();
    });
    g_buttons.onAlarmDismiss([]() {
        g_alarm.clearAllLocal();
    });
    g_buttons.onProvisionLongPress([]() {
        g_wifi.startConfigPortal();
    });

    // 5. WiFi (WiFiManager 配网)
    g_wifi.begin();

    // 6. 时间同步
    if (g_wifi.isConnected()) {
        syncTime();
    }

    // 7. MQTT
    DeviceConfig cfg = g_wifi.getConfig();
    g_mqtt.setServer(cfg.mqtt_server, atoi(cfg.mqtt_port),
                     cfg.mqtt_user, cfg.mqtt_pass);
    g_mqtt.begin(cfg.device_id);

    // 注册 MQTT 回调
    g_mqtt.onAlarmCommand(handleAlarmCommand);
    g_mqtt.onControlCommand(handleControlCommand);
    g_mqtt.onProvisionCommand(handleProvisionCommand);

    // 注册本地报警回调 → MQTT 上报
    g_alarm.onAlarmChange(onLocalAlarmChange);

    // 连接 MQTT
    if (g_wifi.isConnected()) {
        g_mqtt.connect();
    }

    // 8. 传感器
    g_sensor.begin();

    // 9. 雷达任务 (核心0)
    xTaskCreatePinnedToCore(
        radarTask, "RadarTask",
        RADAR_TASK_STACK, nullptr,
        RADAR_TASK_PRIORITY, nullptr,
        RADAR_TASK_CORE
    );

    Serial.println(F("[系统] ✅ 系统就绪"));
    Serial.println(F("══════════════════════════════════════════\n"));
}

// ============================================================
// 主循环 (核心1)
// ============================================================

void loop() {
    uint32_t now = millis();

    // --- 0. 按钮轮询 ---
    g_buttons.loop();

    // --- 1. WiFi ---
    bool wifiOk = g_wifi.loop();

    // --- 2. MQTT (含自动重连) ---
    if (wifiOk) {
        g_mqtt.loop();

        // WiFi 刚连上但 MQTT 还没连
        static bool wasDisconnected = true;
        if (g_mqtt.isConnected()) {
            if (wasDisconnected) {
                // 刚恢复连接, 同步时间
                syncTime();
                wasDisconnected = false;
            }
        } else {
            wasDisconnected = true;
        }
    }

    // --- 3. 传感器 (独立，任何传感器故障不影响其他) ---
    g_sensor.read();

    // --- 4. 本地报警检查 (离线也工作) ---
    RadarRawData radar = g_radar.getData();
    checkLocalAlarms(radar);

    // --- 传感器故障日志 (报警系统统一管理触发, 此处仅记录) ---
    static uint32_t lastFaultLog = 0;
    if (now - lastFaultLog > 60000) {
        lastFaultLog = now;
        if (!g_sensor.isDhtValid() && millis() > 60000 && !g_localDhtFaultActive) {
            Serial.println(F("[系统] ⚠ DHT11 读数异常"));
        }
        if (g_radar.isTimeout() && !g_localRadarFaultActive) {
            Serial.println(F("[系统] ⚠ 雷达超时无数据"));
        }
    }

    // --- 5. 报警模块更新 (定时器/闪烁) ---
    g_alarm.loop();

    // --- 6. 遥测上报 (1秒周期) ---
    if (now - g_lastReportTime >= REPORT_INTERVAL_MS) {
        g_lastReportTime = now;

        TelemetryData tele = buildTelemetry(radar);
        g_mqtt.publishAllPoints(tele, g_sequence);
        g_sequence++;

        // 调试输出 + OLED 显示
        printDebug(tele);
        g_display.update(tele, wifiOk, g_alarm.isBuzzerOn(),
                         g_alarm.hasActiveAlarm(), g_alarm.getActiveAlarmStr());
    }

    // --- 7. MQTT 配网状态 (WiFiManager 触发时) ---
    // WiFiManager 的 captive portal 启动/关闭时更新状态
    // 此逻辑已集成在 wifi_manager 中

    delay(10);
}


Now let me look at the wifi_manager.h to see the pin definitions.


1	/**
2	 * @file wifi_manager.h
3	 * @brief WiFi 配网管理器
4	 *
5	 * 功能:
6	 *  - 自动连接已保存的 WiFi
7	 *  - 配网失败/首次启动 → AP 模式 + 强制门户页面
8	 *  - 自定义参数: MQTT 服务器/端口/用户/密码、设备名称、环境备注
9	 *  - 长按按钮重置配置
10	 *  - LED 状态指示
11	 *  - LittleFS 持久化存储
12	 */
13
14	#ifndef WIFI_MANAGER_H
15	#define WIFI_MANAGER_H
16
17	#include <Arduino.h>
18	#include <WiFiManager.h>
19	#include <LittleFS.h>
20	#include <ArduinoJson.h>
21	#include "config.h"
22
23	// 设备配置结构体
24	struct DeviceConfig {
25	    char mqtt_server[40];
26	    char mqtt_port[6];
27	    char mqtt_user[20];
28	    char mqtt_pass[20];
29	    char device_id[20];
30	    char env_note[20];      // 环境备注 (如: "主卧", "客厅", "老人房")
31	};
32
33	class WiFiMgmt {
34	public:
35	    WiFiMgmt();
36	    ~WiFiMgmt();
37
38	    // 单例指针 (用于静态回调)
39	    static WiFiMgmt* _instance;
40
41	    /**
42	     * @brief 初始化 WiFi 管理器
43	     * @return true=成功
44	     */
45	    bool begin();
46
47	    /**
48	     * @brief 主循环, 处理配网流程和按钮检测
49	     * @return true=WiFi 已连接
50	     */
51	    bool loop();
52
53	    /**
54	     * @brief WiFi 是否已连接
55	     */
56	    bool isConnected() const;
57
58	    /**
59	     * @brief 获取设备配置
60	     */
61	    DeviceConfig getConfig() const;
62
63	    /**
64	     * @brief 获取设备 ID (用于 MQTT client_id)
65	     */
66	    const char* getDeviceId() const;
67
68	    /**
69	     * @brief 获取环境备注
70	     */
71	    const char* getEnvNote() const;
72
73	    /**
74	     * @brief 强制进入配网模式
75	     */
76	    void startConfigPortal();
77
78	    /**
79	     * @brief 清除所有配置并重启
80	     */
81	    void resetSettings();
82
83	private:
84	    /**
85	     * @brief 尝试连接 WiFi
86	     */
87	    bool _connectWiFi();
88
89	    /**
90	     * @brief 设置 LED 模式 (线程安全, 无阻塞)
91	     * @param mode 0=灭 1=慢闪(断连1s) 2=快闪(配网300ms) 3=常亮(已连接)
92	     */
93	    void _setLEDMode(uint8_t mode);
94
95	    /**
96	     * @brief LED 闪烁独立任务 (FreeRTOS, 不受主循环阻塞影响)
97	     */
98	    static void _ledTaskFunc(void* param);
99
100	    /**
101	     * @brief 检测按钮长按
102	     */
103	    bool _checkButtonLongPress();
104
105	    /**
106	     * @brief 加载/保存配置到 LittleFS
107	     */
108	    bool _loadConfig();
109	    bool _saveConfig();
110
111	    /**
112	     * @brief 保存 WiFi 参数回调 (WiFiManager 的回调)
113	     */
114	    static void _saveParamsCallback();
115	    static void _apCallback(WiFiManager* wm);
116
117	    WiFiManager     _wm;
118	    DeviceConfig    _config;
119	    bool            _connected;
120	    uint32_t        _lastCheckTime;
121	    uint32_t        _buttonPressTime;
122	    bool            _buttonPressed;
123
124	    // LED 独立任务 (不被 startConfigPortal 阻塞影响)
125	    TaskHandle_t    _ledTaskHandle;
126	    volatile uint8_t _ledMode;  // 0=灭 1=慢闪 2=快闪 3=常亮
127
128	    // WiFiManager 自定义参数
129	    WiFiManagerParameter* _param_mqtt_server;
130	    WiFiManagerParameter* _param_mqtt_port;
131	    WiFiManagerParameter* _param_mqtt_user;
132	    WiFiManagerParameter* _param_mqtt_pass;
133	    WiFiManagerParameter* _param_device_id;
134	    WiFiManagerParameter* _param_env_note;
135	};
136
137	#endif // WIFI_MANAGER_H
138

/**
 * @file wifi_manager.h
 * @brief WiFi 配网管理器
 *
 * 功能:
 *  - 自动连接已保存的 WiFi
 *  - 配网失败/首次启动 → AP 模式 + 强制门户页面
 *  - 自定义参数: MQTT 服务器/端口/用户/密码、设备名称、环境备注
 *  - 长按按钮重置配置
 *  - LED 状态指示
 *  - LittleFS 持久化存储
 */

#ifndef WIFI_MANAGER_H
#define WIFI_MANAGER_H

#include <Arduino.h>
#include <WiFiManager.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
#include "config.h"

// 设备配置结构体
struct DeviceConfig {
    char mqtt_server[40];
    char mqtt_port[6];
    char mqtt_user[20];
    char mqtt_pass[20];
    char device_id[20];
    char env_note[20];      // 环境备注 (如: "主卧", "客厅", "老人房")
};

class WiFiMgmt {
public:
    WiFiMgmt();
    ~WiFiMgmt();

    // 单例指针 (用于静态回调)
    static WiFiMgmt* _instance;

    /**
     * @brief 初始化 WiFi 管理器
     * @return true=成功
     */
    bool begin();

    /**
     * @brief 主循环, 处理配网流程和按钮检测
     * @return true=WiFi 已连接
     */
    bool loop();

    /**
     * @brief WiFi 是否已连接
     */
    bool isConnected() const;

    /**
     * @brief 获取设备配置
     */
    DeviceConfig getConfig() const;

    /**
     * @brief 获取设备 ID (用于 MQTT client_id)
     */
    const char* getDeviceId() const;

    /**
     * @brief 获取环境备注
     */
    const char* getEnvNote() const;

    /**
     * @brief 强制进入配网模式
     */
    void startConfigPortal();

    /**
     * @brief 清除所有配置并重启
     */
    void resetSettings();

private:
    /**
     * @brief 尝试连接 WiFi
     */
    bool _connectWiFi();

    /**
     * @brief 设置 LED 模式 (线程安全, 无阻塞)
     * @param mode 0=灭 1=慢闪(断连1s) 2=快闪(配网300ms) 3=常亮(已连接)
     */
    void _setLEDMode(uint8_t mode);

    /**
     * @brief LED 闪烁独立任务 (FreeRTOS, 不受主循环阻塞影响)
     */
    static void _ledTaskFunc(void* param);

    /**
     * @brief 检测按钮长按
     */
    bool _checkButtonLongPress();

    /**
     * @brief 加载/保存配置到 LittleFS
     */
    bool _loadConfig();
    bool _saveConfig();

    /**
     * @brief 保存 WiFi 参数回调 (WiFiManager 的回调)
     */
    static void _saveParamsCallback();
    static void _apCallback(WiFiManager* wm);

    WiFiManager     _wm;
    DeviceConfig    _config;
    bool            _connected;
    uint32_t        _lastCheckTime;
    uint32_t        _buttonPressTime;
    bool            _buttonPressed;

    // LED 独立任务 (不被 startConfigPortal 阻塞影响)
    TaskHandle_t    _ledTaskHandle;
    volatile uint8_t _ledMode;  // 0=灭 1=慢闪 2=快闪 3=常亮

    // WiFiManager 自定义参数
    WiFiManagerParameter* _param_mqtt_server;
    WiFiManagerParameter* _param_mqtt_port;
    WiFiManagerParameter* _param_mqtt_user;
    WiFiManagerParameter* _param_mqtt_pass;
    WiFiManagerParameter* _param_device_id;
    WiFiManagerParameter* _param_env_note;
};

#endif // WIFI_MANAGER_H