I need to add an OLED display (SSD1306 128x64 I2C) and 3 buttons to an ESP32-S3 project at d:\PlatformIO1\Projects\Healthv2.0. Please explore:

1. The existing button handling in src/wifi_manager.cpp - specifically the long-press detection logic for GPIO 0 (CONFIG_BUTTON_PIN). I want to reuse/extend this pattern.

2. Check include/config.h for ALL currently used GPIO pins so I know which pins are free for the OLED (I2C: SDA+SCL) and 3 new buttons.

3. Check platformio.ini for existing library dependencies, so I know how to add U8g2 library.

4. Look at the main loop structure in src/main.cpp - how often does it run, where would display update logic best fit?

5. Check if there's already any display-related code in the project.

Please provide detailed findings with file paths and line numbers.


- deep-research: Deep research harness — fan-out web searches, fetch sources, adversarially verify claims, synthesize a cited report. - When the user wants a deep, multi-source, fact-checked research report on any topic. BEFORE invoking, check if the question is specific enough to research directly — if underspecified (e.g., "what car to buy" without budget/use-case/region), ask 2-3 clarifying questions to narrow scope. Then pass the refined question as args, weaving the answers in.
- 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

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


The user wants me to explore their ESP32-S3 project to understand the existing codebase before they add an OLED display and buttons. Let me systematically go through each of their 5 requests by reading the relevant files.

Let me start by reading all the files mentioned in parallel.


I will explore the project systematically across all 5 areas. Let me start by reading the key files in parallel.


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	    , _ledLastToggle(0)
21	    , _ledState(false)
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	    // 初始化 LittleFS
52	    if (!LittleFS.begin(true)) {
53	        Serial.println(F("[WiFi] ❌ LittleFS 挂载失败!"));
54	        return false;
55	    }
56	    Serial.println(F("[WiFi] ✅ LittleFS 已挂载"));
57
58	    // 加载配置
59	    if (!_loadConfig()) {
60	        Serial.println(F("[WiFi] 使用默认配置"));
61	        // 设置默认值
62	        strncpy(_config.mqtt_server, MQTT_DEFAULT_SERVER,   sizeof(_config.mqtt_server) - 1);
63	        strncpy(_config.mqtt_port,   MQTT_DEFAULT_PORT_STR, sizeof(_config.mqtt_port) - 1);
64	        strncpy(_config.mqtt_user,   MQTT_DEFAULT_USER,     sizeof(_config.mqtt_user) - 1);
65	        strncpy(_config.mqtt_pass,   MQTT_DEFAULT_PASS,     sizeof(_config.mqtt_pass) - 1);
66	        strncpy(_config.device_id,   MQTT_DEFAULT_DEVICE,   sizeof(_config.device_id) - 1);
67	        strncpy(_config.env_note,    MQTT_DEFAULT_ENV,      sizeof(_config.env_note) - 1);
68	    }
69
70	    // 配置 WiFiManager
71	    _wm.setDebugOutput(false);  // 关闭调试输出 (避免干扰)
72	    _wm.setConfigPortalTimeout(WIFI_CONFIG_TIMEOUT);
73	    _wm.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
74	    _wm.setTitle("🏥 五劳无感康养监测 - 设备配网");  // 配网页面的中文标题
75	    _wm.setCustomHeadElement(
76	        "<style>"
77	        "body { font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif; }"
78	        ".msg .info { font-size: 14px; }"
79	        "</style>"
80	    );
81	    WiFi.setAutoReconnect(true);  // ESP32 自动重连
82
83	    // 注册保存回调 (必须使用静态函数, 不能使用 lambda)
84	    _wm.setSaveConfigCallback(_saveParamsCallback);
85
86	    // 创建自定义参数
87	    _param_mqtt_server = new WiFiManagerParameter(
88	        "mqtt_server", "MQTT 服务器地址",
89	        _config.mqtt_server, 40);
90	    _param_mqtt_port = new WiFiManagerParameter(
91	        "mqtt_port", "MQTT 端口",
92	        _config.mqtt_port, 6);
93	    _param_mqtt_user = new WiFiManagerParameter(
94	        "mqtt_user", "MQTT 用户名",
95	        _config.mqtt_user, 20);
96	    _param_mqtt_pass = new WiFiManagerParameter(
97	        "mqtt_pass", "MQTT 密码",
98	        _config.mqtt_pass, 20);
99	    _param_device_id = new WiFiManagerParameter(
100	        "device_id", "设备名称",
101	        _config.device_id, 20);
102	    _param_env_note = new WiFiManagerParameter(
103	        "env_note", "🏠 环境备注 (如: 主卧/客厅/老人房)",
104	        _config.env_note, 20);
105
106	    // 注册自定义参数
107	    _wm.addParameter(_param_mqtt_server);
108	    _wm.addParameter(_param_mqtt_port);
109	    _wm.addParameter(_param_mqtt_user);
110	    _wm.addParameter(_param_mqtt_pass);
111	    _wm.addParameter(_param_device_id);
112	    _wm.addParameter(_param_env_note);
113
114	    // 设置 AP 模式回调 (静态函数)
115	    _wm.setAPCallback(_apCallback);
116
117	    // 尝试连接 WiFi
118	    _connected = _connectWiFi();
119
120	    if (!_connected) {
121	        // 如果自动连接失败, 启动配网页面
122	        Serial.println(F("[WiFi] 自动连接失败, 启动配网模式..."));
123	        _setLEDMode(1);  // 快闪 = 配网模式
124	        _connected = _connectWiFi();  // 内部会启动配网页面
125	    }
126
127	    if (_connected) {
128	        _setLEDMode(2);  // 常亮 = 已连接
129	    }
130
131	    _lastCheckTime = millis();
132	    return _connected;
133	}
134
135	// ============================================================
136	// 主循环
137	// ============================================================
138
139	bool WiFiMgmt::loop() {
140	    // 检测按钮长按
141	    if (_checkButtonLongPress()) {
142	        Serial.println(F("[WiFi] ⚠  检测到长按, 清除配置并重启..."));
143	        resetSettings();
144	        return false;
145	    }
146
147	    // 检查 WiFi 状态
148	    if (millis() - _lastCheckTime > WIFI_RETRY_INTERVAL) {
149	        _lastCheckTime = millis();
150
151	        if (WiFi.status() != WL_CONNECTED) {
152	            _connected = false;
153	            _setLEDMode(0);  // 慢闪 = 未连接
154	            Serial.println(F("[WiFi] ⚠  WiFi 断开, 尝试重连..."));
155	            _connectWiFi();
156	        } else {
157	            _connected = true;
158	            _setLEDMode(2);  // 常亮
159	        }
160	    }
161
162	    // LED 闪烁处理
163	    if (!_connected) {
164	        uint32_t interval = (_wm.getConfigPortalActive()) ? 300 : 1000;
165	        if (millis() - _ledLastToggle > interval) {
166	            _ledLastToggle = millis();
167	            _ledState = !_ledState;
168	            digitalWrite(LED_WIFI_PIN, _ledState ? HIGH : LOW);
169	        }
170	    }
171
172	    return _connected;
173	}
174
175	// ============================================================
176	// WiFi 连接
177	// ============================================================
178
179	bool WiFiMgmt::_connectWiFi() {
180	    // 检查是否有已保存的配置
181	    // autoConnect 会先尝试连接, 失败后启动 AP 配网页面
182	    _connected = _wm.autoConnect(WIFI_AP_SSID, WIFI_AP_PASSWORD);
183
184	    if (_connected) {
185	        Serial.println(F("[WiFi] ✅ 已连接!"));
186	        Serial.printf("  WiFi名称: %s\n", WiFi.SSID().c_str());
187	        Serial.printf("  IP地址:   %s\n", WiFi.localIP().toString().c_str());
188	        Serial.printf("  MAC地址:  %s\n", WiFi.macAddress().c_str());
189
190	        // 重新加载配置 (确保获取最新值)
191	        // autoConnect 过程中如果有保存, 回调已写入 LittleFS
192	        _loadConfig();
193
194	        // 同步自定义参数 (如果配置是从 LittleFS 加载的)
195	        _param_mqtt_server->setValue(_config.mqtt_server, sizeof(_config.mqtt_server));
196	        _param_mqtt_port->setValue(_config.mqtt_port, sizeof(_config.mqtt_port));
197	        _param_mqtt_user->setValue(_config.mqtt_user, sizeof(_config.mqtt_user));
198	        _param_mqtt_pass->setValue(_config.mqtt_pass, sizeof(_config.mqtt_pass));
199	        _param_device_id->setValue(_config.device_id, sizeof(_config.device_id));
200	        _param_env_note->setValue(_config.env_note, sizeof(_config.env_note));
201
202	        Serial.printf("  设备名: %s\n", _config.device_id);
203	        Serial.printf("  环境:   %s\n", _config.env_note);
204	        Serial.printf("  MQTT服务器: %s:%s\n", _config.mqtt_server, _config.mqtt_port);
205	    }
206
207	    return _connected;
208	}
209
210	// ============================================================
211	// 配网页面
212	// ============================================================
213
214	void WiFiMgmt::startConfigPortal() {
215	    Serial.println(F("[WiFi] 🔧 手动启动配网页面..."));
216	    _setLEDMode(1);
217	    _wm.startConfigPortal(WIFI_AP_SSID, WIFI_AP_PASSWORD);
218	    _connected = (WiFi.status() == WL_CONNECTED);
219	    if (_connected) {
220	        _setLEDMode(2);
221	    }
222	}
223
224	// ============================================================
225	// 重置设置
226	// ============================================================
227
228	void WiFiMgmt::resetSettings() {
229	    Serial.println(F("[WiFi] 清除所有配置..."));
230
231	    // 删除 LittleFS 中的配置文件
232	    if (LittleFS.exists(CONFIG_FILE_WIFI)) LittleFS.remove(CONFIG_FILE_WIFI);
233	    if (LittleFS.exists(CONFIG_FILE_MQTT)) LittleFS.remove(CONFIG_FILE_MQTT);
234	    if (LittleFS.exists(CONFIG_FILE_DEVICE)) LittleFS.remove(CONFIG_FILE_DEVICE);
235
236	    // 清除 WiFiManager 的配置
237	    _wm.resetSettings();
238
239	    // LED 快闪提示
240	    for (int i = 0; i < 10; i++) {
241	        digitalWrite(LED_WIFI_PIN, !digitalRead(LED_WIFI_PIN));
242	        delay(100);
243	    }
244
245	    Serial.println(F("[WiFi] 🔄 正在重启..."));
246	    delay(500);
247	    ESP.restart();
248	}
249
250	// ============================================================
251	// 按钮长按检测
252	// ============================================================
253
254	bool WiFiMgmt::_checkButtonLongPress() {
255	    bool pressed = (digitalRead(CONFIG_BUTTON_PIN) == LOW);
256
257	    if (pressed && !_buttonPressed) {
258	        // 按下
259	        _buttonPressed = true;
260	        _buttonPressTime = millis();
261	    } else if (!pressed && _buttonPressed) {
262	        // 松开
263	        _buttonPressed = false;
264	        if (millis() - _buttonPressTime >= BUTTON_LONG_PRESS_MS) {
265	            return true;  // 长按确认
266	        }
267	    }
268
269	    return false;
270	}
271
272	// ============================================================
273	// LittleFS 配置持久化
274	// ============================================================
275
276	bool WiFiMgmt::_loadConfig() {
277	    if (!LittleFS.exists(CONFIG_FILE_DEVICE)) {
278	        return false;
279	    }
280
281	    File file = LittleFS.open(CONFIG_FILE_DEVICE, "r");
282	    if (!file) return false;
283
284	    JsonDocument doc;
285	    DeserializationError err = deserializeJson(doc, file);
286	    file.close();
287
288	    if (err) {
289	        Serial.printf("[WiFi] JSON 解析失败: %s\n", err.c_str());
290	        return false;
291	    }
292
293	    strncpy(_config.mqtt_server, doc["mqtt_server"] | MQTT_DEFAULT_SERVER, sizeof(_config.mqtt_server) - 1);
294	    strncpy(_config.mqtt_port,   doc["mqtt_port"]   | MQTT_DEFAULT_PORT_STR, sizeof(_config.mqtt_port) - 1);
295	    strncpy(_config.mqtt_user,   doc["mqtt_user"]   | MQTT_DEFAULT_USER,     sizeof(_config.mqtt_user) - 1);
296	    strncpy(_config.mqtt_pass,   doc["mqtt_pass"]   | MQTT_DEFAULT_PASS,     sizeof(_config.mqtt_pass) - 1);
297	    strncpy(_config.device_id,   doc["device_id"]   | MQTT_DEFAULT_DEVICE,   sizeof(_config.device_id) - 1);
298	    strncpy(_config.env_note,    doc["env_note"]    | MQTT_DEFAULT_ENV,      sizeof(_config.env_note) - 1);
299
300	    Serial.println(F("[WiFi] ✅ 设备配置已加载"));
301	    Serial.printf("  设备名: %s, 环境: %s\n", _config.device_id, _config.env_note);
302	    return true;
303	}
304
305	bool WiFiMgmt::_saveConfig() {
306	    JsonDocument doc;
307	    doc["mqtt_server"] = _config.mqtt_server;
308	    doc["mqtt_port"]   = _config.mqtt_port;
309	    doc["mqtt_user"]   = _config.mqtt_user;
310	    doc["mqtt_pass"]   = _config.mqtt_pass;
311	    doc["device_id"]   = _config.device_id;
312	    doc["env_note"]    = _config.env_note;
313
314	    File file = LittleFS.open(CONFIG_FILE_DEVICE, "w");
315	    if (!file) {
316	        Serial.println(F("[WiFi] ❌ 无法创建配置文件"));
317	        return false;
318	    }
319
320	    if (serializeJson(doc, file) == 0) {
321	        Serial.println(F("[WiFi] ❌ 配置写入失败"));
322	        file.close();
323	        return false;
324	    }
325
326	    file.close();
327	    Serial.println(F("[WiFi] ✅ 设备配置已保存"));
328	    return true;
329	}
330
331	// ============================================================
332	// WiFiManager 保存参数回调
333	// ============================================================
334
335	// ============================================================
336	// 静态回调函数 (WiFiManager 要求 C 函数指针)
337	// ============================================================
338
339	void WiFiMgmt::_apCallback(WiFiManager* wm) {
340	    Serial.println(F("[WiFi] 🔧 进入配网模式"));
341	    Serial.printf("  AP WiFi名称: %s\n", WIFI_AP_SSID);
342	    Serial.printf("  AP 密码: %s\n", WIFI_AP_PASSWORD);
343	    Serial.println(F("  手机连接此 WiFi 即可打开配网页面 (自动弹出)"));
344	}
345
346	void WiFiMgmt::_saveParamsCallback() {
347	    if (!_instance) return;
348
349	    Serial.println(F("[WiFi] 💾 参数已保存"));
350
351	    // 从 WiFiManager 参数中读取值
352	    strncpy(_instance->_config.mqtt_server, _instance->_param_mqtt_server->getValue(),
353	            sizeof(_instance->_config.mqtt_server) - 1);
354	    strncpy(_instance->_config.mqtt_port,   _instance->_param_mqtt_port->getValue(),
355	            sizeof(_instance->_config.mqtt_port) - 1);
356	    strncpy(_instance->_config.mqtt_user,   _instance->_param_mqtt_user->getValue(),
357	            sizeof(_instance->_config.mqtt_user) - 1);
358	    strncpy(_instance->_config.mqtt_pass,   _instance->_param_mqtt_pass->getValue(),
359	            sizeof(_instance->_config.mqtt_pass) - 1);
360	    strncpy(_instance->_config.device_id,   _instance->_param_device_id->getValue(),
361	            sizeof(_instance->_config.device_id) - 1);
362	    strncpy(_instance->_config.env_note,    _instance->_param_env_note->getValue(),
363	            sizeof(_instance->_config.env_note) - 1);
364
365	    _instance->_saveConfig();
366
367	    Serial.println(F("[WiFi] 📋 新配置:"));
368	    Serial.printf("  MQTT: %s:%s\n",
369	                  _instance->_config.mqtt_server,
370	                  _instance->_config.mqtt_port);
371	    Serial.printf("  设备: %s\n", _instance->_config.device_id);
372	    Serial.printf("  环境: %s\n", _instance->_config.env_note);
373	}
374
375	// ============================================================
376	// Getter 方法
377	// ============================================================
378
379	bool WiFiMgmt::isConnected() const {
380	    return _connected && (WiFi.status() == WL_CONNECTED);
381	}
382
383	DeviceConfig WiFiMgmt::getConfig() const {
384	    return _config;
385	}
386
387	const char* WiFiMgmt::getDeviceId() const {
388	    return _config.device_id;
389	}
390
391	const char* WiFiMgmt::getEnvNote() const {
392	    return _config.env_note;
393	}
394
395	// ============================================================
396	// LED 模式控制
397	// ============================================================
398
399	void WiFiMgmt::_setLEDMode(uint8_t mode) {
400	    // mode 由 loop() 中的闪烁逻辑处理
401	    // 0=慢闪 1=快闪 2=常亮
402	    switch (mode) {
403	    case 0:
404	    case 1:
405	        // 闪烁由 loop 处理
406	        break;
407	    case 2:
408	        digitalWrite(LED_WIFI_PIN, HIGH);
409	        _ledState = true;
410	        break;
411	    }
412	}
413


1	/**
2	 * @file config.h
3	 * @brief 全局配置文件 - 五劳无感康养监测系统
4	 *
5	 * 协议版本: V0.08 (2026-07-17)
6	 * 包含: 硬件引脚定义、WiFi/MQTT 默认参数、
7	 *        雷达协议常量、告警阈值、任务配置等
8	 */
9
10	#ifndef CONFIG_H
11	#define CONFIG_H
12
13	#include <Arduino.h>
14
15	// ============================================================
16	// 1. 硬件引脚定义 (ESP32-S3)
17	// ============================================================
18
19	// --- R60ABD1 毫米波雷达 ---
20	#define RADAR_UART_PORT       Serial2
21	#define RADAR_RX_PIN          17
22	#define RADAR_TX_PIN          18
23	#define RADAR_BAUD_RATE       115200
24
25	// --- 雷达备用 GPIO ---
26	#define RADAR_GPIO1_PIN       10            // 活跃/静止指示
27	#define RADAR_GPIO2_PIN       11            // 有人/无人指示
28
29	// --- DHT11 温湿度传感器 ---
30	#define DHT11_PIN             4             // 温湿度传感器
31
32	// --- 水浸传感器 ---
33	#define WATER_SENSOR_PIN      6             // 数字输入, 低电平=有水
34
35	// --- 蜂鸣器 (PWM) ---
36	#define BUZZER_PIN            7
37	#define BUZZER_PWM_CHANNEL    0
38	#define BUZZER_PWM_FREQ       2000
39	#define BUZZER_PWM_RESOLUTION 8
40
41	// --- 配网按钮 ---
42	#define CONFIG_BUTTON_PIN     0             // 长按 3 秒复位 WiFi
43	#define BUTTON_LONG_PRESS_MS  3000
44
45	// --- 状态指示灯 ---
46	#define LED_WIFI_PIN          8             // 蓝色 - WiFi 状态
47	#define LED_ALARM_PIN         9             // 红色 - 报警指示
48
49	// ============================================================
50	// 2. 设备标识 (协议规范 §2)
51	// ============================================================
52
53	#define SALEID_DEFAULT        "10100001"    // 站点/系统编号
54	#define GATEID_DEFAULT        "10100001001" // 网关唯一编号
55	#define METER_ID_DEFAULT      "ENV001"      // 感知节点ID (卧室=ENV001, 卫生间=ENV002, 客厅=ENV003)
56	#define METER_NAME_DEFAULT    "RadarDHT11"  // 设备型号名称
57	#define FW_VERSION            "V1.0.0"
58
59	// ============================================================
60	// 3. WiFi 默认配置 (配网使用)
61	// ============================================================
62
63	#define WIFI_AP_SSID          "HealthMonitor-Setup"
64	#define WIFI_AP_PASSWORD      "12345678"
65	#define WIFI_CONFIG_TIMEOUT   300
66	#define WIFI_CONNECT_TIMEOUT  15
67	#define WIFI_RETRY_INTERVAL   10000
68
69	// ============================================================
70	// 4. MQTT 默认配置 (协议规范 §2)
71	// ============================================================
72
73	#define MQTT_DEFAULT_SERVER   "202.110.234.102"
74	#define MQTT_DEFAULT_PORT     8000
75	#define MQTT_DEFAULT_PORT_STR "8000"         // WiFiManager 参数用字符串
76	#define MQTT_DEFAULT_USER     "test"
77	#define MQTT_DEFAULT_PASS     "testtest"
78	#define MQTT_DEFAULT_DEVICE   "10100001001"  // 默认网关ID = gateid
79	#define MQTT_DEFAULT_ENV      "主卧"           // 默认环境备注
80	#undef MQTT_KEEPALIVE
81	#define MQTT_KEEPALIVE        30              // 30秒 (协议规范)
82	#define MQTT_RECONNECT_MIN    1000
83	#define MQTT_RECONNECT_MAX    60000
84
85	// ============================================================
86	// 5. MQTT 主题定义 (协议规范 §3)
87	// ============================================================
88
89	// --- 上行主题 (设备 → 服务端) ---
90	#define MQTT_TOPIC_REPORT_ALLPOINTS   "report/allpoints"                // 全量遥测上报 QoS0
91	#define MQTT_TOPIC_STATUS_ONLINE      "status/%s/online"                // 上线通知 QoS1
92	#define MQTT_TOPIC_STATUS_OFFLINE     "status/%s/offline"               // 离线遗嘱 QoS1
93	#define MQTT_TOPIC_STATUS_PROVISION   "status/%s/provision"             // 配网状态 QoS1
94	#define MQTT_TOPIC_RESPONSE_CMD       "response/%s/cmd"                 // 指令响应 QoS1
95	#define MQTT_TOPIC_RESPONSE_PROVISION "response/%s/provision"           // 配网响应 QoS1
96
97	// --- 下行主题 (服务端 → 设备) ---
98	#define MQTT_TOPIC_CMD_ALARM          "cmd/%s/alarm"                    // 告警指令 QoS1
99	#define MQTT_TOPIC_CMD_ALARM_RESTORE  "cmd/%s/alarm/restore"            // 告警恢复 QoS1
100	#define MQTT_TOPIC_CMD_CONTROL        "cmd/%s/control"                  // 控制指令 QoS1
101	#define MQTT_TOPIC_CMD_PROVISION_WIFI "cmd/%s/provision/wifi"           // WiFi配网 QoS1
102
103	// ============================================================
104	// 6. 上报配置 (协议规范 §4.1, §7.2)
105	// ============================================================
106
107	#define REPORT_INTERVAL_MS    1000                // 遥测上报周期 1秒
108	#define REPORT_QOS            0                   // 遥测 QoS 0
109	#define STATUS_QOS            1                   // 状态/响应 QoS 1
110
111	// ============================================================
112	// 7. 报警阈值 (协议规范 §6, 服务端判定用)
113	// ============================================================
114
115	#define HEART_RATE_MIN        50
116	#define HEART_RATE_MAX        120
117	#define BREATH_RATE_MIN       8
118	#define BREATH_RATE_MAX       24
119	#define TEMP_LOW              5.0f
120	#define TEMP_HIGH             45.0f
121	#define HUMI_LOW              15.0f
122	#define HUMI_HIGH             90.0f
123	#define WATER_DEBOUNCE_MS     50
124
125	// ============================================================
126	// 8. 任务配置
127	// ============================================================
128
129	#define RADAR_TASK_STACK      4096
130	#define RADAR_TASK_PRIORITY   5
131	#define RADAR_TASK_CORE       0
132
133	#define MQTT_TASK_STACK       8192
134	#define MQTT_TASK_PRIORITY    4
135	#define MQTT_TASK_CORE        1
136
137	// ============================================================
138	// 9. NTP 时间同步
139	// ============================================================
140
141	#define NTP_SERVER            "ntp.aliyun.com"
142	#define NTP_GMT_OFFSET_SEC    28800             // UTC+8 (北京时间)
143	#define NTP_DAYLIGHT_OFFSET   0
144
145	// ============================================================
146	// 10. LittleFS 配置文件名
147	// ============================================================
148
149	#define CONFIG_FILE_MQTT      "/mqtt_config.json"
150	#define CONFIG_FILE_WIFI      "/wifi_config.json"
151	#define CONFIG_FILE_DEVICE    "/device_config.json"
152
153	// ============================================================
154	// 11. 结构体: 全量遥测数据 (协议规范 §4.1)
155	// ============================================================
156
157	struct TelemetryData {
158	    // --- 设备标识 ---
159	    char     saleid[12];          // 站点编号 "10100001"
160	    char     gateid[16];          // 网关编号 "10100001001"
161	    char     meterId[8];          // 节点ID "ENV001"
162	    char     meterName[16];       // 设备型号 "RadarDHT11"
163
164	    // --- 环境数据 (DHT11) ---
165	    float    temp;                // 温度 ℃
166	    float    humi;                // 湿度 %RH
167	    bool     dhtValid;            // 温湿度有效性
168
169	    // --- 雷达: 存在/运动 ---
170	    bool     presence;            // 有人=1 无人=0
171	    bool     motion;              // 有运动=1 无运动=0
172	    bool     fall;                // 跌倒检测 1=触发
173	    bool     resident;            // 驻留状态 1=驻留
174
175	    // --- 雷达: 生命体征 ---
176	    uint8_t  heartRate;           // 心率 bpm (无效=0)
177	    uint8_t  breathRate;          // 呼吸 次/分 (无效=0)
178	    uint8_t  body;                // 人体能量/体征值
179	    bool     radarValid;          // 雷达数据有效性
180	    bool     vitalValid;          // 心率呼吸有效性
181
182	    // --- 水浸 ---
183	    bool     rainDO;              // 1=正常 0=水浸触发
184	    bool     rainValid;           // 水浸传感器有效性
185
186	    // --- 设备状态 ---
187	    bool     wifiReady;           // WiFi/MQTT 连接状态
188	    uint32_t uptimeSec;           // 设备累计运行秒数
189	    bool     buzzerState;         // 蜂鸣器状态 1=开启
190
191	    // --- 时间戳 ---
192	    uint32_t lastUpdate;          // 最后更新 ms
193	};
194
195	// 结构体: 下行报警指令 (协议规范 §5.1, §5.2)
196	struct AlarmCommand {
197	    char     cmdId[32];           // 指令唯一ID
198	    char     type[20];            // alarm / alarm_restore
199	    char     alarmType[24];       // fall/water_leak/heart_abnormal/breath_abnormal
200	    char     alarmLevel[8];       // low/medium/high
201	    char     alarmDesc[64];       // 告警描述
202	    char     meterId[8];          // 触发节点ID
203	    char     relatedCmdId[32];    // 关联原始告警ID (恢复用)
204
205	    // 联动动作
206	    bool     buzzerOn;            // 蜂鸣器开关
207	    uint16_t buzzerDuration;      // 蜂鸣持续秒数 (0=持续到恢复)
208	    bool     nightLight;          // 夜灯
209
210	    uint32_t timestamp;
211	};
212
213	// 结构体: 下行控制指令 (协议规范 §5.3)
214	struct ControlCommand {
215	    char     cmdId[32];
216	    char     type[20];            // control
217	    char     controlType[24];     // buzzer_test/device_restart/report_interval_set/factory_reset
218	    uint32_t params_duration;     // 参数: 持续秒数
219	    uint32_t params_interval;     // 参数: 上报间隔
220	    uint32_t timestamp;
221	};
222
223	// 结构体: 下行配网指令 (协议规范 §5.4)
224	struct ProvisionCommand {
225	    char     cmdId[32];
226	    char     wifi_ssid[33];
227	    char     wifi_password[65];
228	    char     wifi_encrypt[8];     // OPEN/WPA2/WPA3
229	    char     mqtt_broker[64];
230	    uint16_t mqtt_port;
231	    bool     mqtt_useTls;
232	    uint32_t timestamp;
233	};
234
235	#endif // CONFIG_H
236


1	; PlatformIO Project Configuration File
2	; ESP32-S3 + R60ABD1 60G 毫米波雷达 健康监测系统
3
4	[env:esp32-s3-devkitc-1]
5	platform = espressif32
6	board = esp32-s3-devkitc-1
7	framework = arduino
8
9	; 串口监视器
10	monitor_speed = 115200
11	monitor_filters = direct
12
13	; Flash 配置
14	board_build.flash_mode = qio
15	board_build.filesystem = littlefs
16	board_build.partitions = default_8MB.csv
17	board_build.arduino.memory_type = qio_opi
18
19	; 编译选项 - 优化速度
20	build_flags =
21	    -Os
22	    -DCORE_DEBUG_LEVEL=0
23	    -DARDUINO_USB_CDC_ON_BOOT=0
24	    -DBOARD_HAS_PSRAM
25
26	; 库依赖
27	lib_deps =
28	    ; WiFi 配网 (自带 captive portal)
29	    tzapu/WiFiManager@^2.0.17
30
31	    ; MQTT 客户端
32	    knolleary/PubSubClient@^2.8
33
34	    ; JSON 解析 (更省内存)
35	    bblanchon/ArduinoJson@^7.1.0
36
37	    ; DHT11 温湿度传感器
38	    adafruit/DHT sensor library@^1.4.6
39	    adafruit/Adafruit Unified Sensor@^1.1.14
40
41	; 上传配置
42	upload_speed = 921600
43	upload_port = COM15
44


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
19	// ============================================================
20	// 全局对象
21	// ============================================================
22
23	RadarManager   g_radar;
24	WiFiMgmt       g_wifi;
25	MQTTManager    g_mqtt;
26	SensorManager  g_sensor;
27	AlarmManager   g_alarm;
28
29	// 上报控制
30	uint32_t g_lastReportTime  = 0;
31	uint32_t g_sequence        = 1;       // 上报序号
32	uint32_t g_bootTime        = 0;       // 启动时刻
33
34	// 本地报警跟踪 (防重复触发)
35	bool g_localFallActive       = false;
36	bool g_localWaterActive      = false;
37	bool g_localHeartAbnActive   = false;
38	bool g_localBreathAbnActive  = false;
39
40	// ============================================================
41	// 时间同步
42	// ============================================================
43
44	void syncTime() {
45	    Serial.println(F("[时间] 正在同步时间..."));
46	    configTime(NTP_GMT_OFFSET_SEC, NTP_DAYLIGHT_OFFSET, NTP_SERVER, "pool.ntp.org");
47
48	    struct tm timeinfo;
49	    int retry = 0;
50	    while (!getLocalTime(&timeinfo) && retry < 20) {
51	        delay(500);
52	        Serial.print(".");
53	        retry++;
54	    }
55
56	    if (retry < 20) {
57	        Serial.println();
58	        Serial.printf("[时间] ✅ 时间同步成功: %04d-%02d-%02d %02d:%02d:%02d\n",
59	                      timeinfo.tm_year + 1900, timeinfo.tm_mon + 1,
60	                      timeinfo.tm_mday, timeinfo.tm_hour,
61	                      timeinfo.tm_min, timeinfo.tm_sec);
62	    } else {
63	        Serial.println(F("\n[时间] ⚠ 时间同步失败, 使用默认时间"));
64	    }
65	}
66
67	// ============================================================
68	// MQTT 告警指令回调 (协议规范 §5.1, §5.2)
69	// ============================================================
70
71	void handleAlarmCommand(const AlarmCommand& cmd) {
72	    if (strcmp(cmd.type, "alarm_restore") == 0) {
73	        // 告警恢复
74	        g_alarm.executeRestore(cmd);
75
76	        // 清除对应本地告警状态
77	        if (strcmp(cmd.alarmType, "fall") == 0)           g_localFallActive = false;
78	        else if (strcmp(cmd.alarmType, "water_leak") == 0) g_localWaterActive = false;
79	        else if (strcmp(cmd.alarmType, "heart_abnormal") == 0)  g_localHeartAbnActive = false;
80	        else if (strcmp(cmd.alarmType, "breath_abnormal") == 0) g_localBreathAbnActive = false;
81	    } else {
82	        // 告警触发
83	        g_alarm.executeAlarm(cmd);
84	    }
85
86	    // 回复响应 (§4.5)
87	    g_mqtt.publishCmdResponse(cmd.cmdId, cmd.type, cmd.alarmType,
88	                              true, "0", "");
89	}
90
91	// ============================================================
92	// MQTT 控制指令回调 (§5.3)
93	// ============================================================
94
95	void handleControlCommand(const ControlCommand& cmd) {
96	    Serial.printf("[控制] 收到控制指令: %s\n", cmd.controlType);
97
98	    bool success = true;
99	    const char* errCode = "0";
100	    const char* errMsg  = "";
101
102	    if (strcmp(cmd.controlType, "buzzer_test") == 0) {
103	        uint32_t dur = cmd.params_duration > 0 ? cmd.params_duration : 3;
104	        g_alarm.buzzerTest(dur);
105	    }
106	    else if (strcmp(cmd.controlType, "device_restart") == 0) {
107	        Serial.println(F("[控制] 🔄 远程重启..."));
108	        g_mqtt.publishCmdResponse(cmd.cmdId, "control", "", true, "0", "");
109	        g_mqtt.publishOffline();
110	        delay(500);
111	        ESP.restart();
112	    }
113	    else if (strcmp(cmd.controlType, "factory_reset") == 0) {
114	        Serial.println(F("[控制] 🔄 恢复出厂设置..."));
115	        g_wifi.resetSettings();
116	    }
117	    else if (strcmp(cmd.controlType, "enter_provision") == 0) {
118	        Serial.println(F("[控制] 📶 进入配网模式"));
119	        g_wifi.startConfigPortal();
120	    }
121	    else {
122	        success = false;
123	        errCode = "2001";
124	        errMsg  = "不支持的指令类型";
125	    }
126
127	    g_mqtt.publishCmdResponse(cmd.cmdId, "control", "",
128	                              success, errCode, errMsg);
129	}
130
131	// ============================================================
132	// MQTT 配网指令回调 (§5.4)
133	// ============================================================
134
135	void handleProvisionCommand(const ProvisionCommand& cmd) {
136	    Serial.printf("[配网] 📶 收到配网指令: SSID=%s\n", cmd.wifi_ssid);
137
138	    if (strlen(cmd.wifi_ssid) == 0) {
139	        g_mqtt.publishProvisionResponse(cmd.cmdId, false,
140	                                        "1002", "未提供WiFi SSID");
141	        return;
142	    }
143
144	    // 连接新 WiFi
145	    g_mqtt.publishProvisionStatus("wifi_connecting", "");
146
147	    WiFi.begin(cmd.wifi_ssid, cmd.wifi_password);
148	    int retry = 0;
149	    while (WiFi.status() != WL_CONNECTED && retry < 30) {
150	        delay(500);
151	        Serial.print(".");
152	        retry++;
153	    }
154
155	    if (WiFi.status() == WL_CONNECTED) {
156	        Serial.printf("\n[配网] ✅ WiFi 已连接, IP=%s\n",
157	                      WiFi.localIP().toString().c_str());
158
159	        // 更新 MQTT 服务器配置
160	        if (strlen(cmd.mqtt_broker) > 0) {
161	            g_mqtt.setServer(cmd.mqtt_broker, cmd.mqtt_port,
162	                           GATEID_DEFAULT, GATEID_DEFAULT); // 正式账号 = gateid
163	        }
164
165	        char signal[8];
166	        snprintf(signal, sizeof(signal), "%d", WiFi.RSSI());
167	        g_mqtt.publishProvisionResponse(cmd.cmdId, true, "0", "", signal);
168
169	        // 重连 MQTT
170	        g_mqtt.disconnect();
171	        g_mqtt.connect();
172	        g_mqtt.publishOnline();
173	    } else {
174	        Serial.println(F("\n[配网] ❌ WiFi 连接失败"));
175	        g_mqtt.publishProvisionStatus("wifi_failed", "");
176	        g_mqtt.publishProvisionResponse(cmd.cmdId, false,
177	                                        "1003", "WiFi连接超时");
178	    }
179	}
180
181	// ============================================================
182	// 构建遥测数据
183	// ============================================================
184
185	TelemetryData buildTelemetry(const RadarRawData& radar) {
186	    TelemetryData t;
187	    memset(&t, 0, sizeof(t));
188
189	    // 设备标识
190	    strncpy(t.saleid,    SALEID_DEFAULT,   sizeof(t.saleid) - 1);
191	    strncpy(t.gateid,    g_mqtt.getGateId(), sizeof(t.gateid) - 1);
192	    strncpy(t.meterId,   METER_ID_DEFAULT, sizeof(t.meterId) - 1);
193	    strncpy(t.meterName, METER_NAME_DEFAULT, sizeof(t.meterName) - 1);
194
195	    // DHT11
196	    t.temp     = g_sensor.getTemp();
197	    t.humi     = g_sensor.getHumi();
198	    t.dhtValid = g_sensor.isDhtValid();
199
200	    // 雷达
201	    t.presence   = radar.presence;
202	    t.motion     = radar.motion;
203	    t.fall       = radar.fall;
204	    t.resident   = radar.resident;
205	    t.heartRate  = radar.heartRate;
206	    t.breathRate = radar.breathRate;
207	    t.body       = radar.body;
208	    t.radarValid = radar.radarValid;
209	    t.vitalValid = radar.vitalValid;
210
211	    // 水浸
212	    t.rainDO    = g_sensor.getRainDO();
213	    t.rainValid = g_sensor.isRainValid();
214
215	    // 设备状态
216	    t.wifiReady  = g_wifi.isConnected() && g_mqtt.isConnected();
217	    t.uptimeSec  = (millis() - g_bootTime) / 1000;
218	    t.buzzerState = g_alarm.isBuzzerOn();
219	    t.lastUpdate = millis();
220
221	    return t;
222	}
223
224	// ============================================================
225	// 本地报警检查 (离线可用)
226	// ============================================================
227
228	void checkLocalAlarms(const RadarRawData& radar) {
229	    uint32_t now = millis();
230
231	    // --- 跌倒检测 (本地紧急报警, 立即触发) ---
232	    if (radar.fall && radar.radarValid && !g_localFallActive) {
233	        g_localFallActive = true;
234	        g_alarm.triggerLocal(AlarmManager::ALARM_FALL);
235	    } else if (!radar.fall && g_localFallActive) {
236	        g_localFallActive = false;
237	        g_alarm.clearLocal(AlarmManager::ALARM_FALL);
238	    }
239
240	    // --- 水浸检测 ---
241	    bool waterLeak = !g_sensor.getRainDO() && g_sensor.isRainValid();
242	    if (waterLeak && !g_localWaterActive) {
243	        g_localWaterActive = true;
244	        g_alarm.triggerLocal(AlarmManager::ALARM_WATER_LEAK);
245	    } else if (!waterLeak && g_localWaterActive) {
246	        g_localWaterActive = false;
247	        g_alarm.clearLocal(AlarmManager::ALARM_WATER_LEAK);
248	    }
249
250	    // --- 心率异常 (离线本地判定, 阈值预留后续调整) ---
251	    if (radar.vitalValid && radar.heartRate > 0) {
252	        bool heartAbn = (radar.heartRate < HEART_RATE_MIN || radar.heartRate > HEART_RATE_MAX);
253	        if (heartAbn && !g_localHeartAbnActive) {
254	            g_localHeartAbnActive = true;
255	            g_alarm.triggerLocal(AlarmManager::ALARM_HEART_ABNORMAL);
256	        } else if (!heartAbn && g_localHeartAbnActive) {
257	            g_localHeartAbnActive = false;
258	            g_alarm.clearLocal(AlarmManager::ALARM_HEART_ABNORMAL);
259	        }
260	    }
261
262	    // --- 呼吸异常 ---
263	    if (radar.vitalValid && radar.breathRate > 0) {
264	        bool breathAbn = (radar.breathRate < BREATH_RATE_MIN || radar.breathRate > BREATH_RATE_MAX);
265	        if (breathAbn && !g_localBreathAbnActive) {
266	            g_localBreathAbnActive = true;
267	            g_alarm.triggerLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
268	        } else if (!breathAbn && g_localBreathAbnActive) {
269	            g_localBreathAbnActive = false;
270	            g_alarm.clearLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
271	        }
272	    }
273	}
274
275	// ============================================================
276	// 调试输出
277	// ============================================================
278
279	void printDebug(const TelemetryData& t) {
280	    static uint32_t lastPrint = 0;
281	    if (millis() - lastPrint < 5000) return;
282	    lastPrint = millis();
283
284	    Serial.println(F("─── 遥测数据 ───"));
285	    Serial.printf("  环境: T=%.1f°C H=%.1f%% (温湿度:%d)\n",
286	                  t.temp, t.humi, t.dhtValid);
287	    Serial.printf("  存在:%d 运动:%d 跌倒:%d 驻留:%d (雷达:%d)\n",
288	                  t.presence, t.motion, t.fall, t.resident, t.radarValid);
289	    Serial.printf("  心率:%d 呼吸:%d (体征:%d)\n",
290	                  t.heartRate, t.breathRate, t.vitalValid);
291	    Serial.printf("  水浸:%d (水浸有效:%d) | WiFi:%d 蜂鸣器:%d\n",
292	                  t.rainDO, t.rainValid, t.wifiReady, t.buzzerState);
293	    Serial.printf("  运行:%ds 序号:%d\n", t.uptimeSec, g_sequence);
294	    Serial.println(F("─────────────────"));
295	}
296
297	// ============================================================
298	// 雷达任务 - 核心0 专用
299	// ============================================================
300
301	void radarTask(void* param) {
302	    Serial.println(F("[任务] 📡 雷达任务启动 (核心0)"));
303	    TickType_t lastWake = xTaskGetTickCount();
304	    const TickType_t freq = pdMS_TO_TICKS(10);
305
306	    while (true) {
307	        g_radar.loop();
308	        vTaskDelayUntil(&lastWake, freq);
309	    }
310	}
311
312	// ============================================================
313	// Setup
314	// ============================================================
315
316	void setup() {
317	    Serial.begin(115200);
318	    delay(500);
319
320	    Serial.println(F("\n"));
321	    Serial.println(F("╔══════════════════════════════════════════╗"));
322	    Serial.println(F("║  🏥 五劳无感康养监测系统 v1.0.0       ║"));
323	    Serial.println(F("║  ESP32-S3 + R60ABD1 + DHT11 + 水浸     ║"));
324	    Serial.println(F("║  MQTT 协议 V0.08                        ║"));
325	    Serial.println(F("╚══════════════════════════════════════════╝"));
326
327	    g_bootTime = millis();
328
329	    // 1. 报警模块 (最先, 引脚安全)
330	    g_alarm.begin();
331
332	    // 2. 雷达
333	    g_radar.begin();
334
335	    // 3. WiFi (WiFiManager 配网)
336	    g_wifi.begin();
337
338	    // 4. 时间同步
339	    if (g_wifi.isConnected()) {
340	        syncTime();
341	    }
342
343	    // 5. MQTT
344	    DeviceConfig cfg = g_wifi.getConfig();
345	    g_mqtt.setServer(cfg.mqtt_server, atoi(cfg.mqtt_port),
346	                     cfg.mqtt_user, cfg.mqtt_pass);
347	    g_mqtt.begin(cfg.device_id);
348
349	    // 注册 MQTT 回调
350	    g_mqtt.onAlarmCommand(handleAlarmCommand);
351	    g_mqtt.onControlCommand(handleControlCommand);
352	    g_mqtt.onProvisionCommand(handleProvisionCommand);
353
354	    // 连接 MQTT
355	    if (g_wifi.isConnected()) {
356	        g_mqtt.connect();
357	    }
358
359	    // 6. 传感器
360	    g_sensor.begin();
361
362	    // 7. 雷达任务 (核心0)
363	    xTaskCreatePinnedToCore(
364	        radarTask, "RadarTask",
365	        RADAR_TASK_STACK, nullptr,
366	        RADAR_TASK_PRIORITY, nullptr,
367	        RADAR_TASK_CORE
368	    );
369
370	    Serial.println(F("[系统] ✅ 系统就绪"));
371	    Serial.println(F("══════════════════════════════════════════\n"));
372	}
373
374	// ============================================================
375	// 主循环 (核心1)
376	// ============================================================
377
378	void loop() {
379	    uint32_t now = millis();
380
381	    // --- 1. WiFi ---
382	    bool wifiOk = g_wifi.loop();
383
384	    // --- 2. MQTT (含自动重连) ---
385	    if (wifiOk) {
386	        g_mqtt.loop();
387
388	        // WiFi 刚连上但 MQTT 还没连
389	        static bool wasDisconnected = true;
390	        if (g_mqtt.isConnected()) {
391	            if (wasDisconnected) {
392	                // 刚恢复连接, 同步时间
393	                syncTime();
394	                wasDisconnected = false;
395	            }
396	        } else {
397	            wasDisconnected = true;
398	        }
399	    }
400
401	    // --- 3. 传感器 ---
402	    g_sensor.read();
403
404	    // --- 4. 本地报警检查 (离线也工作) ---
405	    RadarRawData radar = g_radar.getData();
406	    checkLocalAlarms(radar);
407
408	    // --- 5. 报警模块更新 (定时器/闪烁) ---
409	    g_alarm.loop();
410
411	    // --- 6. 遥测上报 (1秒周期) ---
412	    if (now - g_lastReportTime >= REPORT_INTERVAL_MS) {
413	        g_lastReportTime = now;
414
415	        TelemetryData tele = buildTelemetry(radar);
416	        g_mqtt.publishAllPoints(tele, g_sequence);
417	        g_sequence++;
418
419	        // 调试输出
420	        printDebug(tele);
421	    }
422
423	    // --- 7. MQTT 配网状态 (WiFiManager 触发时) ---
424	    // WiFiManager 的 captive portal 启动/关闭时更新状态
425	    // 此逻辑已集成在 wifi_manager 中
426
427	    delay(10);
428	}
429


The user wants a thorough exploration of their project. Let me now search for any existing display-related code, and also look at the WiFi manager header file which declares the long-press detection logic.


Now let me search for any existing display-related code and also check the WiFi manager header for the button-related declarations.


Error: File does not exist. Note: your current working directory is d:\PlatformIO1\Projects\Healthv2.0. Did you mean wifi_manager.cpp?


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


Let me now look for the header files. The WiFiManager header wasn't found at `src/wifi_manager.h`, so let me look for it in the include directory or elsewhere. Also, let me check for all header files related to the project.


Let me find the header files and also check for any display-related code in the include directory.


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


Found 5 files
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\wm_strings_en.h
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\wm_strings_zh.h
include\wm_strings_zh.h
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\WiFiManager.cpp
.pio\libdeps\esp32-s3-devkitc-1\WiFiManager\wm_strings_es.h


Good. There are no display-related files in the project. All the "display" hits are just CSS `display:inline-block` strings in WiFiManager's HTML templates. Let me now read the WiFi manager header file and also check `alarm_manager.h` and `alarm_manager.cpp` for any button-related pins or patterns that might be relevant.


No display-related code exists in the project. Let me now read the WiFi manager header and the alarm manager files.


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=快闪(配网) 2=常亮(已连接)
92	     */
93	    void _setLEDMode(uint8_t mode);
94
95	    /**
96	     * @brief 检测按钮长按
97	     */
98	    bool _checkButtonLongPress();
99
100	    /**
101	     * @brief 加载/保存配置到 LittleFS
102	     */
103	    bool _loadConfig();
104	    bool _saveConfig();
105
106	    /**
107	     * @brief 保存 WiFi 参数回调 (WiFiManager 的回调)
108	     */
109	    static void _saveParamsCallback();
110	    static void _apCallback(WiFiManager* wm);
111
112	    WiFiManager     _wm;
113	    DeviceConfig    _config;
114	    bool            _connected;
115	    uint32_t        _lastCheckTime;
116	    uint32_t        _buttonPressTime;
117	    bool            _buttonPressed;
118
119	    // LED 闪烁状态
120	    uint32_t        _ledLastToggle;
121	    bool            _ledState;
122
123	    // WiFiManager 自定义参数
124	    WiFiManagerParameter* _param_mqtt_server;
125	    WiFiManagerParameter* _param_mqtt_port;
126	    WiFiManagerParameter* _param_mqtt_user;
127	    WiFiManagerParameter* _param_mqtt_pass;
128	    WiFiManagerParameter* _param_device_id;
129	    WiFiManagerParameter* _param_env_note;
130	};
131
132	#endif // WIFI_MANAGER_H
133


1	/**
2	 * @file alarm_manager.h
3	 * @brief 报警管理器 - 双模式 (本地判定 + 服务端指令)
4	 *
5	 * 本地模式: 离线也能用, 跌倒/水浸/心率异常/呼吸异常 本地判定触发
6	 * 服务端模式: 收到 cmd/{gateid}/alarm, alarm/restore 执行联动
7	 */
8
9	#ifndef ALARM_MANAGER_H
10	#define ALARM_MANAGER_H
11
12	#include <Arduino.h>
13	#include "config.h"
14
15	class AlarmManager {
16	public:
17	    // 告警类型 (本地 + 服务端共用)
18	    enum AlarmType {
19	        ALARM_NONE = 0,
20	        ALARM_FALL,             // 跌倒 (本地 + 服务端)
21	        ALARM_WATER_LEAK,       // 水浸 (本地 + 服务端)
22	        ALARM_HEART_ABNORMAL,   // 心率异常 (本地)
23	        ALARM_BREATH_ABNORMAL,  // 呼吸异常 (本地)
24	        ALARM_SERVER,           // 服务端下发 (通用)
25	    };
26
27	    AlarmManager();
28	    ~AlarmManager();
29
30	    bool begin();
31	    void loop();
32
33	    // ============================================================
34	    // 本地报警接口 (离线可用)
35	    // ============================================================
36
37	    /** 触发本地报警 */
38	    void triggerLocal(AlarmType type);
39	    /** 清除本地报警 */
40	    void clearLocal(AlarmType type);
41	    /** 清除所有本地报警 */
42	    void clearAllLocal();
43
44	    // ============================================================
45	    // 服务端指令接口 (协议规范 §5.1, §5.2)
46	    // ============================================================
47
48	    /** 执行服务端告警指令 */
49	    void executeAlarm(const AlarmCommand& cmd);
50	    /** 执行服务端恢复指令 */
51	    void executeRestore(const AlarmCommand& cmd);
52	    /** 蜂鸣器测试 */
53	    void buzzerTest(uint32_t durationSec);
54
55	    // ============================================================
56	    // 状态查询 (上报用)
57	    // ============================================================
58
59	    bool isBuzzerOn() const      { return _buzzerOn; }
60	    bool hasActiveAlarm() const  { return _activeCount > 0; }
61	    const char* getActiveAlarmType() const;
62
63	private:
64	    void _setBuzzer(bool on);
65	    void _setLED(bool on);
66	    void _updateBuzzer();
67	    void _updateLED();
68
69	    // 蜂鸣器/LED 状态
70	    bool     _buzzerOn;
71	    bool     _ledOn;
72	    uint32_t _buzzerOffTime;       // 定时关闭时刻 (0=手动关)
73
74	    // 活动报警列表 (最多 4 个同时)
75	    AlarmType _activeAlarms[4];
76	    uint8_t   _activeCount;
77
78	    // LED 闪烁
79	    uint32_t _lastLEDToggle;
80	};
81
82	#endif // ALARM_MANAGER_H
83


1	/**
2	 * @file alarm_manager.cpp
3	 * @brief 报警管理器 - 双模式实现
4	 *
5	 * 本地: 跌倒/水浸/心率异常/呼吸异常 → 立即蜂鸣器+LED
6	 * 服务端: cmd/alarm, cmd/alarm/restore → 执行联动 + 回复响应
7	 */
8
9	#include "alarm_manager.h"
10
11	AlarmManager::AlarmManager()
12	    : _buzzerOn(false)
13	    , _ledOn(false)
14	    , _buzzerOffTime(0)
15	    , _activeCount(0)
16	    , _lastLEDToggle(0)
17	{
18	    for (int i = 0; i < 4; i++) _activeAlarms[i] = ALARM_NONE;
19	}
20
21	AlarmManager::~AlarmManager() {}
22
23	// ============================================================
24	// 初始化
25	// ============================================================
26
27	bool AlarmManager::begin() {
28	    ledcSetup(BUZZER_PWM_CHANNEL, BUZZER_PWM_FREQ, BUZZER_PWM_RESOLUTION);
29	    ledcAttachPin(BUZZER_PIN, BUZZER_PWM_CHANNEL);
30	    ledcWrite(BUZZER_PWM_CHANNEL, 0);
31
32	    pinMode(LED_ALARM_PIN, OUTPUT);
33	    digitalWrite(LED_ALARM_PIN, LOW);
34
35	    Serial.println(F("[告警] ✅ 双模式报警 (本地+服务端)"));
36	    return true;
37	}
38
39	// ============================================================
40	// 主循环
41	// ============================================================
42
43	void AlarmManager::loop() {
44	    _updateBuzzer();
45	    _updateLED();
46	}
47
48	// ============================================================
49	// 本地报警触发 (离线可用)
50	// ============================================================
51
52	void AlarmManager::triggerLocal(AlarmType type) {
53	    // 去重
54	    for (uint8_t i = 0; i < _activeCount; i++) {
55	        if (_activeAlarms[i] == type) return;
56	    }
57
58	    if (_activeCount < 4) {
59	        _activeAlarms[_activeCount++] = type;
60	    }
61
62	    const char* name = "";
63	    switch (type) {
64	    case ALARM_FALL:            name = "跌倒";        break;
65	    case ALARM_WATER_LEAK:      name = "水浸";        break;
66	    case ALARM_HEART_ABNORMAL:  name = "心率异常";    break;
67	    case ALARM_BREATH_ABNORMAL: name = "呼吸异常";    break;
68	    default: name = "未知"; break;
69	    }
70
71	    Serial.printf("[告警] 🚨 本地报警触发: %s (活动报警:%d)\n", name, _activeCount);
72
73	    // 立即开启蜂鸣器和 LED
74	    _setBuzzer(true);
75	    _setLED(true);
76	}
77
78	void AlarmManager::clearLocal(AlarmType type) {
79	    for (uint8_t i = 0; i < _activeCount; i++) {
80	        if (_activeAlarms[i] == type) {
81	            for (uint8_t j = i; j < _activeCount - 1; j++) {
82	                _activeAlarms[j] = _activeAlarms[j + 1];
83	            }
84	            _activeAlarms[_activeCount - 1] = ALARM_NONE;
85	            _activeCount--;
86	            break;
87	        }
88	    }
89
90	    Serial.printf("[告警] 本地报警清除, 剩余活动:%d\n", _activeCount);
91
92	    // 没有活动报警时关闭
93	    if (_activeCount == 0) {
94	        _setBuzzer(false);
95	        _setLED(false);
96	    }
97	}
98
99	void AlarmManager::clearAllLocal() {
100	    _activeCount = 0;
101	    for (int i = 0; i < 4; i++) _activeAlarms[i] = ALARM_NONE;
102	    _setBuzzer(false);
103	    _setLED(false);
104	    Serial.println(F("[告警] 清除所有本地报警"));
105	}
106
107	// ============================================================
108	// 服务端告警指令 (§5.1)
109	// ============================================================
110
111	void AlarmManager::executeAlarm(const AlarmCommand& cmd) {
112	    Serial.printf("[告警] ☁ 服务端告警: %s (等级=%s, 蜂鸣器=%d)\n",
113	                  cmd.alarmType, cmd.alarmLevel, cmd.buzzerOn);
114
115	    // 映射 alarmType 到本地类型
116	    AlarmType localType = ALARM_SERVER;
117	    if (strcmp(cmd.alarmType, "fall") == 0)           localType = ALARM_FALL;
118	    else if (strcmp(cmd.alarmType, "water_leak") == 0) localType = ALARM_WATER_LEAK;
119
120	    if (cmd.buzzerOn) {
121	        triggerLocal(localType);
122	        if (cmd.buzzerDuration > 0) {
123	            _buzzerOffTime = millis() + (uint32_t)cmd.buzzerDuration * 1000;
124	        } else {
125	            _buzzerOffTime = 0;
126	        }
127	    }
128
129	    if (cmd.nightLight || cmd.buzzerOn) {
130	        _setLED(true);
131	    }
132	}
133
134	// ============================================================
135	// 服务端恢复指令 (§5.2)
136	// ============================================================
137
138	void AlarmManager::executeRestore(const AlarmCommand& cmd) {
139	    Serial.printf("[告警] ☁ 服务端恢复: %s\n", cmd.alarmType);
140
141	    AlarmType localType = ALARM_SERVER;
142	    if (strcmp(cmd.alarmType, "fall") == 0)           localType = ALARM_FALL;
143	    else if (strcmp(cmd.alarmType, "water_leak") == 0) localType = ALARM_WATER_LEAK;
144
145	    clearLocal(localType);
146
147	    if (!cmd.buzzerOn) {
148	        _setBuzzer(false);
149	    }
150	    if (!cmd.nightLight) {
151	        _setLED(false);
152	    }
153	}
154
155	// ============================================================
156	// 蜂鸣器测试
157	// ============================================================
158
159	void AlarmManager::buzzerTest(uint32_t durationSec) {
160	    Serial.printf("[告警] 🔔 蜂鸣器测试 %d秒\n", durationSec);
161	    _setBuzzer(true);
162	    _buzzerOffTime = millis() + durationSec * 1000;
163	}
164
165	// ============================================================
166	// 状态查询
167	// ============================================================
168
169	const char* AlarmManager::getActiveAlarmType() const {
170	    if (_activeCount == 0) return "";
171	    switch (_activeAlarms[0]) {
172	    case ALARM_FALL:            return "fall";
173	    case ALARM_WATER_LEAK:      return "water_leak";
174	    case ALARM_HEART_ABNORMAL:  return "heart_abnormal";
175	    case ALARM_BREATH_ABNORMAL: return "breath_abnormal";
176	    case ALARM_SERVER:          return "server";
177	    default: return "";
178	    }
179	}
180
181	// ============================================================
182	// 内部控制
183	// ============================================================
184
185	void AlarmManager::_setBuzzer(bool on) {
186	    _buzzerOn = on;
187	    if (on) {
188	        ledcWrite(BUZZER_PWM_CHANNEL, 128);
189	    } else {
190	        ledcWrite(BUZZER_PWM_CHANNEL, 0);
191	        _buzzerOffTime = 0;
192	    }
193	}
194
195	void AlarmManager::_setLED(bool on) {
196	    _ledOn = on;
197	    if (!on) digitalWrite(LED_ALARM_PIN, LOW);
198	}
199
200	void AlarmManager::_updateBuzzer() {
201	    // 定时关闭检查
202	    if (_buzzerOn && _buzzerOffTime > 0 && millis() >= _buzzerOffTime) {
203	        _setBuzzer(false);
204	        Serial.println(F("[告警] ⏰ 蜂鸣器定时关闭"));
205	    }
206	}
207
208	void AlarmManager::_updateLED() {
209	    if (!_ledOn) {
210	        digitalWrite(LED_ALARM_PIN, LOW);
211	        return;
212	    }
213
214	    // 根据告警类型调整闪烁频率
215	    uint32_t interval = 500;
216	    for (uint8_t i = 0; i < _activeCount; i++) {
217	        if (_activeAlarms[i] == ALARM_FALL) {
218	            interval = 200;  // 跌倒快闪
219	            break;
220	        }
221	    }
222
223	    if (millis() - _lastLEDToggle >= interval) {
224	        _lastLEDToggle = millis();
225	        digitalWrite(LED_ALARM_PIN, !digitalRead(LED_ALARM_PIN));
226	    }
227	}
228


1	/**
2	 * @file sensor_manager.h
3	 * @brief 传感器管理器 - DHT11温湿度 + 水浸检测
4	 */
5
6	#ifndef SENSOR_MANAGER_H
7	#define SENSOR_MANAGER_H
8
9	#include <Arduino.h>
10	#include <DHT.h>
11	#include "config.h"
12
13	#define DHT_TYPE DHT11
14
15	typedef void (*SensorCallback)(void);  // 数据变化通知
16
17	class SensorManager {
18	public:
19	    SensorManager();
20	    ~SensorManager();
21
22	    /** 初始化 */
23	    bool begin();
24
25	    /** 读取所有传感器 (定时调用) */
26	    void read();
27
28	    /** 获取温度 */
29	    float getTemp() const        { return _temp; }
30	    /** 获取湿度 */
31	    float getHumi() const        { return _humi; }
32	    /** DHT11 有效性 */
33	    bool  isDhtValid() const     { return _dhtValid; }
34
35	    /** 获取水浸状态 */
36	    bool  getRainDO() const      { return _rainDO; }
37	    /** 水浸传感器有效性 */
38	    bool  isRainValid() const    { return _rainValid; }
39
40	    /** 设置数据变化回调 */
41	    void onChange(SensorCallback cb);
42
43	private:
44	    DHT*    _dht;
45	    float   _temp;
46	    float   _humi;
47	    bool    _dhtValid;
48
49	    bool    _rainDO;        // 1=正常 0=水浸
50	    bool    _rainValid;
51	    bool    _lastRainRaw;
52	    uint32_t _rainChangeTime;
53
54	    SensorCallback _callback;
55	    uint32_t _lastReadTime;
56	};
57
58	#endif // SENSOR_MANAGER_H
59


Now I have a comprehensive picture of the project. Let me also quickly check the remaining header files for completeness - radar_manager.h and mqtt_manager.h to see if they use any additional GPIOs.


Let me also check the remaining headers for any additional GPIO usage.


1	/**
2	 * @file radar_manager.h
3	 * @brief R60ABD1 毫米波雷达管理模块
4	 */
5
6	#ifndef RADAR_MANAGER_H
7	#define RADAR_MANAGER_H
8
9	#include <Arduino.h>
10	#include "config.h"
11	#include "radar_protocol.h"
12
13	// 雷达原始数据结构
14	struct RadarRawData {
15	    bool     presence;       // 有人/无人
16	    bool     motion;         // 运动 (false=静止)
17	    bool     fall;           // 跌倒
18	    bool     resident;       // 驻留
19
20	    uint8_t  heartRate;      // 心率
21	    uint8_t  breathRate;     // 呼吸
22	    uint8_t  body;           // 体动/能量值
23	    uint16_t distance;       // 距离 cm
24
25	    bool     radarValid;     // 雷达有效性
26	    bool     vitalValid;     // 生命体征有效性
27
28	    uint32_t lastUpdate;
29	};
30
31	typedef void (*RadarCallback)(const RadarRawData& data);
32
33	class RadarManager {
34	public:
35	    RadarManager();
36	    ~RadarManager();
37
38	    bool begin();
39	    void loop();
40
41	    /** 获取最新数据 */
42	    RadarRawData getData() const;
43
44	    /** 数据更新回调 */
45	    void onDataUpdate(RadarCallback cb);
46
47	    /** 发送命令 */
48	    void sendCommand(uint8_t control, uint8_t command,
49	                     const uint8_t* data = nullptr, uint16_t len = 0);
50	    void queryInitStatus();
51	    void queryDetectRange();
52
53	    bool isInitialized() const { return _initialized; }
54
55	private:
56	    void _parseByte(uint8_t byte);
57	    void _resetParser();
58	    bool _validateChecksum();
59	    void _processCompleteFrame();
60
61	    void _extractPresence(const RadarFrame& frame);
62	    void _extractBreathing(const RadarFrame& frame);
63	    void _extractHeartRate(const RadarFrame& frame);
64	    void _extractSleep(const RadarFrame& frame);
65	    void _extractWorkStatus(const RadarFrame& frame);
66	    void _extractDetectRange(const RadarFrame& frame);
67
68	    uint8_t _calcChecksum(const uint8_t* data, uint16_t len);
69	    void _notifyUpdate();
70
71	    HardwareSerial* _serial;
72	    FrameState      _state;
73	    RadarFrame      _currentFrame;
74	    uint16_t        _dataIndex;
75	    RadarRawData    _data;
76	    bool            _initialized;
77
78	    RadarCallback   _callback;
79	    uint32_t        _frameCount;
80	    uint32_t        _errorCount;
81	};
82
83	#endif // RADAR_MANAGER_H
84


1	/**
2	 * @file mqtt_manager.h
3	 * @brief MQTT 通信管理器 (协议规范 V0.08)
4	 *
5	 * 上行: report/allpoints, status/{gateid}/online, status/{gateid}/offline,
6	 *       status/{gateid}/provision, response/{gateid}/cmd, response/{gateid}/provision
7	 * 下行: cmd/{gateid}/alarm, cmd/{gateid}/alarm/restore,
8	 *       cmd/{gateid}/control, cmd/{gateid}/provision/wifi
9	 */
10
11	#ifndef MQTT_MANAGER_H
12	#define MQTT_MANAGER_H
13
14	#include <Arduino.h>
15	#include <WiFi.h>
16	#include <PubSubClient.h>
17	#include <ArduinoJson.h>
18	#include "config.h"
19
20	// ============================================================
21	// 回调函数类型
22	// ============================================================
23
24	// 告警指令回调 (alarm / alarm_restore)
25	typedef void (*MQTTAlarmCallback)(const AlarmCommand& cmd);
26
27	// 控制指令回调
28	typedef void (*MQTTControlCallback)(const ControlCommand& cmd);
29
30	// 配网指令回调 (WiFi 凭证下发)
31	typedef void (*MQTTProvisionCallback)(const ProvisionCommand& cmd);
32
33	// ============================================================
34	// MQTTManager 类
35	// ============================================================
36
37	class MQTTManager {
38	public:
39	    MQTTManager();
40	    ~MQTTManager();
41
42	    /** 初始化 */
43	    bool begin(const char* gateid);
44
45	    /** 主循环 */
46	    void loop();
47
48	    /** 连接/断开 */
49	    bool connect();
50	    void disconnect();
51	    bool isConnected();
52
53	    /** 设置服务器 */
54	    void setServer(const char* host, uint16_t port,
55	                   const char* user, const char* pass);
56
57	    // ============================================================
58	    // 上行: 发布方法 (协议规范 §4)
59	    // ============================================================
60
61	    /** 全量遥测上报 report/allpoints (§4.1) */
62	    void publishAllPoints(const TelemetryData& data, uint32_t sequence);
63
64	    /** 设备上线通知 status/{gateid}/online (§4.2) */
65	    void publishOnline();
66
67	    /** 设备离线通知 status/{gateid}/offline (§4.3) — 主动调用 */
68	    void publishOffline();
69
70	    /** 配网状态通知 status/{gateid}/provision (§4.4) */
71	    void publishProvisionStatus(const char* state, const char* bleName);
72
73	    /** 指令执行响应 response/{gateid}/cmd (§4.5) */
74	    void publishCmdResponse(const char* cmdId, const char* cmdType,
75	                            const char* alarmType, bool success,
76	                            const char* errorCode, const char* errorMsg);
77
78	    /** 配网指令响应 response/{gateid}/provision (§4.6) */
79	    void publishProvisionResponse(const char* cmdId, bool success,
80	                                  const char* errorCode, const char* errorMsg,
81	                                  const char* wifiSignal = "");
82
83	    // ============================================================
84	    // 回调注册 (协议规范 §5)
85	    // ============================================================
86
87	    void onAlarmCommand(MQTTAlarmCallback cb);
88	    void onControlCommand(MQTTControlCallback cb);
89	    void onProvisionCommand(MQTTProvisionCallback cb);
90
91	    /** 获取 gateid */
92	    const char* getGateId() const { return _gateid; }
93
94	private:
95	    static void _mqttCallback(char* topic, byte* payload, unsigned int length);
96	    static MQTTManager* _instance;
97
98	    // 主题构建
99	    String _buildTopic(const char* tmpl) const;
100
101	    // 下行报文解析
102	    void _handleMessage(const char* topic, const char* payload);
103	    void _parseAlarm(const char* payload, bool isRestore);
104	    void _parseControl(const char* payload);
105	    void _parseProvision(const char* payload);
106
107	    // 发布辅助
108	    bool _publish(const char* topicTmpl, const char* payload, uint8_t qos, bool retain = false);
109
110	    WiFiClient      _wifiClient;
111	    PubSubClient    _mqtt;
112	    char            _gateid[16];
113
114	    // 服务器配置
115	    char     _server[64];
116	    uint16_t _port;
117	    char     _user[32];
118	    char     _pass[32];
119
120	    // 重连
121	    uint32_t _reconnectInterval;
122	    uint32_t _lastReconnectAttempt;
123	    bool     _connected;
124
125	    // 回调
126	    MQTTAlarmCallback     _alarmCb;
127	    MQTTControlCallback   _controlCb;
128	    MQTTProvisionCallback _provisionCb;
129	};
130
131	#endif // MQTT_MANAGER_H
132


Now I have all the information needed to provide a comprehensive report. Let me compile my findings.

**1. Button handling in wifi_manager.cpp**

The long-press detection logic is at lines 254-270 of `src/wifi_manager.cpp`, implemented in the `_checkButtonLongPress()` method. The pattern uses GPIO 0 (CONFIG_BUTTON_PIN) with INPUT_PULLUP. It tracks press state with `_buttonPressed` and `_buttonPressTime`. On release, it checks if the duration >= 3000ms (BUTTON_LONG_PRESS_MS). This is a state-machine pattern that can be reused.

Key variables in the header:
- `uint32_t _buttonPressTime` (line 116)
- `bool _buttonPressed` (line 117)

**2. GPIO pin usage in config.h**

Let me enumerate all GPIOs:
- GPIO 0: CONFIG_BUTTON_PIN (配网按钮, long press reset)
- GPIO 4: DHT11_PIN (温湿度传感器)
- GPIO 6: WATER_SENSOR_PIN (水浸传感器)
- GPIO 7: BUZZER_PIN (蜂鸣器 PWM)
- GPIO 8: LED_WIFI_PIN (蓝色, WiFi状态)
- GPIO 9: LED_ALARM_PIN (红色, 报警指示)
- GPIO 10: RADAR_GPIO1_PIN (雷达 活跃/静止 指示)
- GPIO 11: RADAR_GPIO2_PIN (雷达 有人/无人 指示)
- GPIO 17: RADAR_RX_PIN (雷达 UART RX)
- GPIO 18: RADAR_TX_PIN (雷达 UART TX)

So used pins: 0, 4, 6, 7, 8, 9, 10, 11, 17, 18

On ESP32-S3, common available I2C pins would be:
- GPIO 1, 2, 3, 5, 12, 13, 14, 15, 16, 19, 20, 21, 26, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48

Default I2C pins for ESP32-S3 are GPIO 41 (SDA) and GPIO 42 (SCL), but many boards use different defaults. Common choices are GPIO 21 (SDA) and GPIO 22 (SCL), or GPIO 1 and 2.

Available pins for:
- I2C SDA: GPIO 1, 2, 3, 5, 12, 13, 14, 15, 16, 21, 26, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48
- I2C SCL: GPIO 1, 2, 3, 5, 12, 13, 14, 15, 16, 21, 26, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48
- 3 buttons: GPIO 1, 2, 3, 5, 12, 13, 14, 15, 16, 21, 26, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48

**3. platformio.ini dependencies**

Current dependencies:
- tzapu/WiFiManager@^2.0.17
- knolleary/PubSubClient@^2.8
- bblanchon/ArduinoJson@^7.1.0
- adafruit/DHT sensor library@^1.4.6
- adafruit/Adafruit Unified Sensor@^1.1.14

To add U8g2, you'd add something like: `olikraus/U8g2@^2.35.30`

**4. Main loop structure**

The `loop()` function in `src/main.cpp` runs as fast as possible (only a `delay(10)` at line 427). Key features:
- It checks WiFi, MQTT, sensors, alarms, and telemetry reporting every loop iteration
- Telemetry reporting is throttled to 1-second intervals (line 412)
- Debug printing is throttled to 5-second intervals (line 281)
- The main loop on Core 1 runs a non-blocking pattern - all blocking operations would be problematic

For display update, the best place would be either:
- Inside the 1-second report block (lines 411-421) if you want display to update at 1Hz
- A separate throttled block in loop() if you want different refresh rates

**5. No existing display code**

The grep confirmed there is NO existing display/OLED/U8g2/SSD1306 code in the project. All "display" hits are CSS `display:inline-block` in WiFiManager's HTML templates.


Here are my detailed findings across all 5 areas.

---

## 1. Button Handling: Long-Press Detection Logic

**File:** `d:\PlatformIO1\Projects\Healthv2.0\src\wifi_manager.cpp`

The long-press detection is implemented in the private method `_checkButtonLongPress()` at **lines 254-270**. The pattern is a simple state machine:

```
254: bool WiFiMgmt::_checkButtonLongPress() {
255:     bool pressed = (digitalRead(CONFIG_BUTTON_PIN) == LOW);
257:     if (pressed && !_buttonPressed) {
258:         // Press-down edge: record press start time
259:         _buttonPressed = true;
260:         _buttonPressTime = millis();
261:     } else if (!pressed && _buttonPressed) {
262:         // Release edge: check elapsed time
263:         _buttonPressed = false;
264:         if (millis() - _buttonPressTime >= BUTTON_LONG_PRESS_MS) {
265:             return true;  // Long press confirmed
266:         }
267:     }
269:     return false;
270: }
```

Key design points of this pattern:
- Pin is configured as `INPUT_PULLUP` (line 47), so pressed = `LOW`.
- It detects on the **release edge** only -- the action fires when the user releases the button after holding at least `BUTTON_LONG_PRESS_MS` (3000ms, defined at line 43 of `config.h`).
- It does NOT support short-press detection. There is no "press" callback; the method only returns `true` for long-press events.
- It only handles a single button. There is no debouncing, no callback registration, and no mechanism for distinguishing multiple button types (click vs. long-press vs. double-click).

The state variables are declared in the header at `d:\PlatformIO1\Projects\Healthv2.0\include\wifi_manager.h`:
- Line 116: `uint32_t _buttonPressTime;`
- Line 117: `bool _buttonPressed;`
- Line 43 (config.h): `#define CONFIG_BUTTON_PIN 0`
- Line 44 (config.h): `#define BUTTON_LONG_PRESS_MS 3000`

The method is called from `loop()` at line 141 of `wifi_manager.cpp`, which in turn is called from `main.cpp` loop at line 382.

**To reuse/extend this pattern:** You could generalize it into a reusable `Button` class that tracks `_buttonPressed`, `_buttonPressTime`, and `_lastReleaseTime` per pin, exposing callbacks for `onShortPress()`, `onLongPress()`, and optionally `onDoubleClick()`. The existing `_checkButtonLongPress()` returns a bool for the caller to handle the action inline; a cleaner extension would register callback function pointers (similar to how `MQTTManager` registers callbacks: `MQTTAlarmCallback`, `MQTTControlCallback`, `MQTTProvisionCallback`).

---

## 2. GPIO Pin Inventory (All Currently Used Pins)

**File:** `d:\PlatformIO1\Projects\Healthv2.0\include\config.h`, lines 19-48

| GPIO Pin | Macro | Purpose |
|---|---|---|
| **0** | `CONFIG_BUTTON_PIN` | WiFi reset button (INPUT_PULLUP, long-press 3s) |
| **4** | `DHT11_PIN` | DHT11 temperature/humidity sensor |
| **6** | `WATER_SENSOR_PIN` | Water leak sensor (digital input, LOW = water) |
| **7** | `BUZZER_PIN` | Buzzer PWM output (channel 0, 2kHz, 8-bit) |
| **8** | `LED_WIFI_PIN` | Blue LED - WiFi status indicator |
| **9** | `LED_ALARM_PIN` | Red LED - Alarm indicator |
| **10** | `RADAR_GPIO1_PIN` | Radar active/stationary indicator |
| **11** | `RADAR_GPIO2_PIN` | Radar presence/absence indicator |
| **17** | `RADAR_RX_PIN` | Radar UART RX (Serial2) |
| **18** | `RADAR_TX_PIN` | Radar UART TX (Serial2) |

**Total used pins: 10** (0, 4, 6, 7, 8, 9, 10, 11, 17, 18).

**Recommended free pins for new hardware:**

For the **OLED I2C (SDA + SCL)**, the safest and most conventional choice on ESP32-S3 is:
- **GPIO 21 (SDA)** and **GPIO 22 (SCL)** -- these are rarely conflicted and are the Arduino-ecosystem default for I2C on ESP32 boards. Alternatively, GPIO 1/2 or GPIO 41/42 could work, but 21/22 is the most portable choice.

For the **3 new buttons**, plenty of pins are free. Good candidates that are easy to access and avoid flash/PSRAM conflicts:
- **GPIO 1, 2, 3** (adjacent, easy to wire)
- Or **GPIO 12, 13, 14** (also adjacent)
- Or **GPIO 5, 15, 16**

Note: GPIOs 19 and 20 are used by USB on many ESP32-S3 dev boards, so avoid those for general I/O unless you know your board does not use them. GPIOs 33-37 are typically safe. GPIOs 26-32 can have ADC conflicts but are fine for digital button input.

---

## 3. PlatformIO.ini Library Dependencies

**File:** `d:\PlatformIO1\Projects\Healthv2.0\platformio.ini`, lines 27-39

Current `lib_deps`:
```ini
lib_deps =
    ; WiFi 配网 (自带 captive portal)
    tzapu/WiFiManager@^2.0.17

    ; MQTT 客户端
    knolleary/PubSubClient@^2.8

    ; JSON 解析 (更省内存)
    bblanchon/ArduinoJson@^7.1.0

    ; DHT11 温湿度传感器
    adafruit/DHT sensor library@^1.4.6
    adafruit/Adafruit Unified Sensor@^1.1.14
```

Other notable build settings:
- Board: `esp32-s3-devkitc-1` (line 5)
- Framework: `arduino` (line 7)
- Optimization: `-Os` (line 21)
- Filesystem: `littlefs` (line 15)
- Flash: `qio_opi`, 8MB partition (lines 14,16-17)

**To add U8g2**, you would add to the `lib_deps` section:
```ini
    ; OLED 显示屏 (SSD1306 128x64 I2C)
    olikraus/U8g2@^2.35.30
```

U8g2 is the recommended library for SSD1306 on ESP32 -- it supports I2C, hardware SPI, and software SPI, has built-in font handling, and is well-maintained. The PlatformIO library ID is `olikraus/U8g2`. Version `2.35.30` is a recent stable release; you can also omit the version constraint and use `olikraus/U8g2` for the latest.

---

## 4. Main Loop Structure

**File:** `d:\PlatformIO1\Projects\Healthv2.0\src\main.cpp`, lines 378-428

The `loop()` function runs on Core 1 and operates as a **fast, non-blocking super-loop** with a `delay(10)` at line 427. This means each iteration completes in roughly 10ms plus execution time. Here is the execution order:

```
378: void loop() {
379:     uint32_t now = millis();
382:     bool wifiOk = g_wifi.loop();            // 1. WiFi + button check
385:     if (wifiOk) {
386:         g_mqtt.loop();                       // 2. MQTT (if WiFi up)
389:         static bool wasDisconnected...       // 2b. Re-sync time on reconnect
401:     }
402:     g_sensor.read();                         // 3. Poll DHT11 + water sensor
405:     RadarRawData radar = g_radar.getData();  // 4. Get radar data
406:     checkLocalAlarms(radar);                 // 4b. Local alarm check
409:     g_alarm.loop();                          // 5. Buzzer/LED updates
412:     if (now - g_lastReportTime >= REPORT_INTERVAL_MS) {   // 6. 1Hz reporting
415:         TelemetryData tele = buildTelemetry(radar);
416:         g_mqtt.publishAllPoints(tele, g_sequence++);
420:         printDebug(tele);                    // (every 5s, line 281)
421:     }
427:     delay(10);
428: }
```

**Where to add display update logic:**

The best location is inside the **1-second report block** (lines 412-421). This block runs at exactly 1Hz (controlled by `REPORT_INTERVAL_MS` = 1000 at config.h line 107). A 1Hz refresh rate is perfectly adequate for an OLED status display showing sensor readings, WiFi/MQTT status, and alarm state.

Specifically, I recommend adding display update after line 420 (after `buildTelemetry` and `printDebug`) because at that point you have the fully populated `TelemetryData` struct `tele` containing all sensor values, radar data, WiFi status, uptime, and alarm state -- everything you would want to display.

If you want a faster refresh for button-driven menu navigation or screen transitions, you could break the display update out of the 1-second block and call it every loop iteration (with its own millis-based throttling, e.g., every 100ms), passing `radar` and a reference to `g_sensor` for live data.

The `buildTelemetry()` function at lines 185-222 constructs the complete data struct from all subsystems, making it a natural single source of truth for display content.

---

## 5. Display-Related Code: None Found

There is **zero existing display, OLED, SSD1306, U8g2, LCD, or screen-related code** anywhere in the project source files. The grep search across all `*.cpp`, `*.h`, and `*.ini` files in the project only returned hits inside the WiFiManager library's HTML template CSS strings (e.g., `display:inline-block` in `wm_strings_zh.h`), which are unrelated.

All project source files are:
```
src/main.cpp
src/wifi_manager.cpp
src/radar_manager.cpp
src/mqtt_manager.cpp
src/sensor_manager.cpp
src/alarm_manager.cpp
include/config.h
include/wifi_manager.h
include/radar_manager.h
include/radar_protocol.h
include/mqtt_manager.h
include/sensor_manager.h
include/alarm_manager.h
include/wm_strings_zh.h
```

You will be adding display support completely from scratch. There are no existing abstractions to conflict with or modify -- you just need a new `DisplayManager` class (e.g., `src/display_manager.cpp` + `include/display_manager.h`) initialized after `g_alarm.begin()` in `setup()` and updated in the main `loop()`.