在 d:\PlatformIO1\Projects\Healthv2.0 项目中，搜索所有与"报警"(alarm)、"水浸"(water/flood/leak)、"蜂鸣器"(buzzer/beeper)相关的代码逻辑。我需要理解：
1. 什么条件会触发报警/蜂鸣器
2. 水浸传感器的数据处理逻辑（特别是"水浸有效"标志的含义）
3. 报警的判断条件是什么

请搜索以下关键词：报警、alarm、水浸、water、flood、leak、蜂鸣器、buzzer、beeper、alert、warning
输出匹配的代码片段和文件路径。


- 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 wants me to search for code related to alarm, water/flood/leak, and buzzer/beeper functionality in their project. Let me search for multiple keywords in parallel to be efficient.


I will search for all the relevant keywords in parallel across the project.


include\alarm_manager.h:3: * @brief 报警管理器 - 双模式 (本地判定 + 服务端指令)
include\alarm_manager.h:34:    // 本地报警接口 (离线可用)
include\alarm_manager.h:37:    /** 触发本地报警 */
include\alarm_manager.h:39:    /** 清除本地报警 */
include\alarm_manager.h:41:    /** 清除所有本地报警 */
include\alarm_manager.h:77:    // 活动报警列表 (最多 4 个同时)
include\button_manager.h:6: * 按钮2 (GPIO15): 短按 → 解除报警
include\config.h:47:#define LED_ALARM_PIN         9             // 红色 - 报警指示
include\config.h:55:#define BTN_ALARM_DISMISS_PIN 13            // 解除报警按钮
include\config.h:123:// 7. 报警阈值 (协议规范 §6, 服务端判定用)
include\config.h:206:// 结构体: 下行报警指令 (协议规范 §5.1, §5.2)
src\button_manager.cpp:32:    Serial.printf("  配网: GPIO%d | 解除报警: GPIO%d | 翻页: GPIO%d\n",
src\button_manager.cpp:44:    // 按钮2: 解除报警 (仅短按)
include\radar_manager.h:44:    /** 清除跌倒标记 (报警解除后调用) */
src\alarm_manager.cpp:3: * @brief 报警管理器 - 双模式实现
src\alarm_manager.cpp:42:    Serial.println(F("[告警] ✅ 双模式报警 (本地+服务端)"));
src\alarm_manager.cpp:56:// 本地报警触发 (离线可用)
src\alarm_manager.cpp:78:    Serial.printf("[告警] 🚨 本地报警触发: %s (活动报警:%d)\n", name, _activeCount);
src\alarm_manager.cpp:97:    Serial.printf("[告警] 本地报警清除, 剩余活动:%d\n", _activeCount);
src\alarm_manager.cpp:99:    // 没有活动报警时关闭
src\alarm_manager.cpp:111:    Serial.println(F("[告警] 清除所有本地报警"));
src\alarm_manager.cpp:198:        _buzzerOffTime = 0;  // 本地报警=无限哔哔
src\alarm_manager.cpp:218:    // 本地报警: 三连哔 停 — 循环 (哔哔哔...哔哔哔...)
src\display_manager.cpp:220:    else _u8g2.drawUTF8(60, 30, data.rainDO ? "正常" : "!!报警");
src\main.cpp:6: * 本地紧急报警 (离线可用) + 服务端告警联动
src\main.cpp:38:// 本地报警跟踪 (防重复触发)
src\main.cpp:44:bool     g_vitalAlarmReady   = false;   // 心率呼吸报警就绪 (有人30秒后)
src\main.cpp:233:// 本地报警检查 (离线可用)
src\main.cpp:242:        if (now - g_fallStartTime >= 1000) {  // 跌倒持续1秒才报警
src\main.cpp:263:    // --- 有心率呼吸报警延迟: 检测到人后等 30 秒数据稳定 ---
src\main.cpp:269:            Serial.println(F("[报警] 心率呼吸监测就绪 (30秒稳定期结束)"));
src\main.cpp:287:    // 无人时清除心率异常报警
src\main.cpp:304:    // 无人时清除呼吸异常报警
src\main.cpp:365:    // 1. 报警模块 (最先, 引脚安全)
src\main.cpp:463:    // --- 4. 本地报警检查 (离线也工作) ---
src\main.cpp:479:    // --- 5. 报警模块更新 (定时器/闪烁) ---


include\alarm_manager.h:2: * @file alarm_manager.h
include\alarm_manager.h:6: * 服务端模式: 收到 cmd/{gateid}/alarm, alarm/restore 执行联动
include\alarm_manager.h:9:#ifndef ALARM_MANAGER_H
include\alarm_manager.h:10:#define ALARM_MANAGER_H
include\alarm_manager.h:15:class AlarmManager {
include\alarm_manager.h:18:    enum AlarmType {
include\alarm_manager.h:19:        ALARM_NONE = 0,
include\alarm_manager.h:20:        ALARM_FALL,             // 跌倒 (本地 + 服务端)
include\alarm_manager.h:21:        ALARM_WATER_LEAK,       // 水浸 (本地 + 服务端)
include\alarm_manager.h:22:        ALARM_HEART_ABNORMAL,   // 心率异常 (本地)
include\alarm_manager.h:23:        ALARM_BREATH_ABNORMAL,  // 呼吸异常 (本地)
include\alarm_manager.h:24:        ALARM_SERVER,           // 服务端下发 (通用)
include\alarm_manager.h:27:    AlarmManager();
include\alarm_manager.h:28:    ~AlarmManager();
include\alarm_manager.h:38:    void triggerLocal(AlarmType type);
include\alarm_manager.h:40:    void clearLocal(AlarmType type);
include\alarm_manager.h:49:    void executeAlarm(const AlarmCommand& cmd);
include\alarm_manager.h:51:    void executeRestore(const AlarmCommand& cmd);
include\alarm_manager.h:60:    bool hasActiveAlarm() const  { return _activeCount > 0; }
include\alarm_manager.h:61:    const char* getActiveAlarmType() const;
include\alarm_manager.h:78:    AlarmType _activeAlarms[4];
include\alarm_manager.h:85:#endif // ALARM_MANAGER_H
include\button_manager.h:30:    void onAlarmDismiss(ButtonCallback cb)          { _cbDismiss  = cb; }
include\config.h:47:#define LED_ALARM_PIN         9             // 红色 - 报警指示
include\config.h:55:#define BTN_ALARM_DISMISS_PIN 13            // 解除报警按钮
include\config.h:109:#define MQTT_TOPIC_CMD_ALARM          "cmd/%s/alarm"                    // 告警指令 QoS1
include\config.h:110:#define MQTT_TOPIC_CMD_ALARM_RESTORE  "cmd/%s/alarm/restore"            // 告警恢复 QoS1
include\config.h:207:struct AlarmCommand {
include\config.h:209:    char     type[20];            // alarm / alarm_restore
include\config.h:210:    char     alarmType[24];       // fall/water_leak/heart_abnormal/breath_abnormal
include\config.h:211:    char     alarmLevel[8];       // low/medium/high
include\config.h:212:    char     alarmDesc[64];       // 告警描述
src\alarm_manager.cpp:2: * @file alarm_manager.cpp
src\alarm_manager.cpp:6: * 服务端: cmd/alarm, cmd/alarm/restore → 执行联动 + 回复响应
src\alarm_manager.cpp:9:#include "alarm_manager.h"
src\alarm_manager.cpp:11:AlarmManager::AlarmManager()
src\alarm_manager.cpp:21:    for (int i = 0; i < 4; i++) _activeAlarms[i] = ALARM_NONE;
src\alarm_manager.cpp:24:AlarmManager::~AlarmManager() {}
src\alarm_manager.cpp:30:bool AlarmManager::begin() {
src\alarm_manager.cpp:35:    pinMode(LED_ALARM_PIN, OUTPUT);
src\alarm_manager.cpp:36:    digitalWrite(LED_ALARM_PIN, LOW);
src\alarm_manager.cpp:50:void AlarmManager::loop() {
src\alarm_manager.cpp:59:void AlarmManager::triggerLocal(AlarmType type) {
src\alarm_manager.cpp:62:        if (_activeAlarms[i] == type) return;
src\alarm_manager.cpp:66:        _activeAlarms[_activeCount++] = type;
src\alarm_manager.cpp:71:    case ALARM_FALL:            name = "跌倒";        break;
src\alarm_manager.cpp:72:    case ALARM_WATER_LEAK:      name = "水浸";        break;
src\alarm_manager.cpp:73:    case ALARM_HEART_ABNORMAL:  name = "心率异常";    break;
src\alarm_manager.cpp:74:    case ALARM_BREATH_ABNORMAL: name = "呼吸异常";    break;
src\alarm_manager.cpp:85:void AlarmManager::clearLocal(AlarmType type) {
src\alarm_manager.cpp:87:        if (_activeAlarms[i] == type) {
src\alarm_manager.cpp:89:                _activeAlarms[j] = _activeAlarms[j + 1];
src\alarm_manager.cpp:91:            _activeAlarms[_activeCount - 1] = ALARM_NONE;
src\alarm_manager.cpp:106:void AlarmManager::clearAllLocal() {
src\alarm_manager.cpp:108:    for (int i = 0; i < 4; i++) _activeAlarms[i] = ALARM_NONE;
src\alarm_manager.cpp:118:void AlarmManager::executeAlarm(const AlarmCommand& cmd) {
src\alarm_manager.cpp:120:                  cmd.alarmType, cmd.alarmLevel, cmd.buzzerOn);
src\alarm_manager.cpp:122:    // 映射 alarmType 到本地类型
src\alarm_manager.cpp:123:    AlarmType localType = ALARM_SERVER;
src\alarm_manager.cpp:124:    if (strcmp(cmd.alarmType, "fall") == 0)           localType = ALARM_FALL;
src\alarm_manager.cpp:125:    else if (strcmp(cmd.alarmType, "water_leak") == 0) localType = ALARM_WATER_LEAK;
src\alarm_manager.cpp:145:void AlarmManager::executeRestore(const AlarmCommand& cmd) {
src\alarm_manager.cpp:146:    Serial.printf("[告警] ☁ 服务端恢复: %s\n", cmd.alarmType);
src\alarm_manager.cpp:148:    AlarmType localType = ALARM_SERVER;
src\alarm_manager.cpp:149:    if (strcmp(cmd.alarmType, "fall") == 0)           localType = ALARM_FALL;
src\alarm_manager.cpp:150:    else if (strcmp(cmd.alarmType, "water_leak") == 0) localType = ALARM_WATER_LEAK;
src\alarm_manager.cpp:166:void AlarmManager::buzzerTest(uint32_t durationSec) {
src\alarm_manager.cpp:176:const char* AlarmManager::getActiveAlarmType() const {
src\alarm_manager.cpp:178:    switch (_activeAlarms[0]) {
src\alarm_manager.cpp:179:    case ALARM_FALL:            return "fall";
src\alarm_manager.cpp:180:    case ALARM_WATER_LEAK:      return "water_leak";
src\alarm_manager.cpp:181:    case ALARM_HEART_ABNORMAL:  return "heart_abnormal";
src\alarm_manager.cpp:182:    case ALARM_BREATH_ABNORMAL: return "breath_abnormal";
src\alarm_manager.cpp:183:    case ALARM_SERVER:          return "server";
src\alarm_manager.cpp:192:void AlarmManager::_setBuzzer(bool on) {
src\alarm_manager.cpp:205:void AlarmManager::_setLED(bool on) {
src\alarm_manager.cpp:207:    if (!on) digitalWrite(LED_ALARM_PIN, LOW);
src\alarm_manager.cpp:210:void AlarmManager::_updateBuzzer() {
src\alarm_manager.cpp:247:void AlarmManager::_updateLED() {
src\alarm_manager.cpp:249:        digitalWrite(LED_ALARM_PIN, LOW);
src\alarm_manager.cpp:256:        if (_activeAlarms[i] == ALARM_FALL) {
src\alarm_manager.cpp:264:        digitalWrite(LED_ALARM_PIN, !digitalRead(LED_ALARM_PIN));
include\mqtt_manager.h:7: * 下行: cmd/{gateid}/alarm, cmd/{gateid}/alarm/restore,
include\mqtt_manager.h:24:// 告警指令回调 (alarm / alarm_restore)
include\mqtt_manager.h:25:typedef void (*MQTTAlarmCallback)(const AlarmCommand& cmd);
include\mqtt_manager.h:75:                            const char* alarmType, bool success,
include\mqtt_manager.h:87:    void onAlarmCommand(MQTTAlarmCallback cb);
include\mqtt_manager.h:103:    void _parseAlarm(const char* payload, bool isRestore);
include\mqtt_manager.h:126:    MQTTAlarmCallback     _alarmCb;
src\button_manager.cpp:18:    _btnDismiss   = { BTN_ALARM_DISMISS_PIN, false, 0, false };
src\button_manager.cpp:28:    pinMode(BTN_ALARM_DISMISS_PIN, INPUT_PULLUP);
src\button_manager.cpp:33:                  BTN_PROVISION_PIN, BTN_ALARM_DISMISS_PIN, BTN_PAGE_SWITCH_PIN);
src\button_manager.cpp:76:            if (btn.pin == BTN_ALARM_DISMISS_PIN && _cbDismiss) _cbDismiss();
src\main.cpp:17:#include "alarm_manager.h"
src\main.cpp:29:AlarmManager   g_alarm;
src\main.cpp:44:bool     g_vitalAlarmReady   = false;   // 心率呼吸报警就绪 (有人30秒后)
src\main.cpp:78:void handleAlarmCommand(const AlarmCommand& cmd) {
src\main.cpp:79:    if (strcmp(cmd.type, "alarm_restore") == 0) {
src\main.cpp:81:        g_alarm.executeRestore(cmd);
src\main.cpp:84:        if (strcmp(cmd.alarmType, "fall") == 0)           g_localFallActive = false;
src\main.cpp:85:        else if (strcmp(cmd.alarmType, "water_leak") == 0) g_localWaterActive = false;
src\main.cpp:86:        else if (strcmp(cmd.alarmType, "heart_abnormal") == 0)  g_localHeartAbnActive = false;
src\main.cpp:87:        else if (strcmp(cmd.alarmType, "breath_abnormal") == 0) g_localBreathAbnActive = false;
src\main.cpp:90:        g_alarm.executeAlarm(cmd);
src\main.cpp:94:    g_mqtt.publishCmdResponse(cmd.cmdId, cmd.type, cmd.alarmType,
src\main.cpp:111:        g_alarm.buzzerTest(dur);
src\main.cpp:226:    t.buzzerState = g_alarm.isBuzzerOn();
src\main.cpp:236:void checkLocalAlarms(const RadarRawData& radar) {
src\main.cpp:244:            g_alarm.triggerLocal(AlarmManager::ALARM_FALL);
src\main.cpp:249:        g_alarm.clearLocal(AlarmManager::ALARM_FALL);
src\main.cpp:257:        g_alarm.triggerLocal(AlarmManager::ALARM_WATER_LEAK);
src\main.cpp:260:        g_alarm.clearLocal(AlarmManager::ALARM_WATER_LEAK);
src\main.cpp:264:    #define VITAL_ALARM_DELAY_MS  30000
src\main.cpp:265:    if (radar.presence && !g_vitalAlarmReady) {
src\main.cpp:267:        if (now - g_presenceStartTime >= VITAL_ALARM_DELAY_MS) {
src\main.cpp:268:            g_vitalAlarmReady = true;
src\main.cpp:273:        g_vitalAlarmReady   = false;
src\main.cpp:277:    if (g_vitalAlarmReady && radar.vitalValid && radar.heartRate > 0) {
src\main.cpp:281:            g_alarm.triggerLocal(AlarmManager::ALARM_HEART_ABNORMAL);
src\main.cpp:284:            g_alarm.clearLocal(AlarmManager::ALARM_HEART_ABNORMAL);
src\main.cpp:290:        g_alarm.clearLocal(AlarmManager::ALARM_HEART_ABNORMAL);
src\main.cpp:294:    if (g_vitalAlarmReady && radar.vitalValid && radar.breathRate > 0) {
src\main.cpp:298:            g_alarm.triggerLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
src\main.cpp:301:            g_alarm.clearLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
src\main.cpp:307:        g_alarm.clearLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
src\main.cpp:366:    g_alarm.begin();
src\main.cpp:379:    g_buttons.onAlarmDismiss([]() {
src\main.cpp:380:        g_alarm.clearAllLocal();
src\main.cpp:406:    g_mqtt.onAlarmCommand(handleAlarmCommand);
src\main.cpp:465:    checkLocalAlarms(radar);
src\main.cpp:480:    g_alarm.loop();
src\main.cpp:492:        g_display.update(tele, wifiOk, g_alarm.isBuzzerOn());
src\mqtt_manager.cpp:20:    , _alarmCb(nullptr)
src\mqtt_manager.cpp:117:        String subAlarm    = _buildTopic(MQTT_TOPIC_CMD_ALARM);
src\mqtt_manager.cpp:118:        String subRestore  = _buildTopic(MQTT_TOPIC_CMD_ALARM_RESTORE);
src\mqtt_manager.cpp:122:        _mqtt.subscribe(subAlarm.c_str(), 1);
src\mqtt_manager.cpp:127:        Serial.printf("[MQTT]   已订阅: %s\n", subAlarm.c_str());
src\mqtt_manager.cpp:364:                                      const char* alarmType, bool success,
src\mqtt_manager.cpp:379:    doc["cmdType"]   = cmdType;     // alarm / alarm_restore / control
src\mqtt_manager.cpp:380:    doc["alarmType"] = alarmType ? alarmType : "";
src\mqtt_manager.cpp:428:void MQTTManager::onAlarmCommand(MQTTAlarmCallback cb)     { _alarmCb = cb; }
src\mqtt_manager.cpp:458:    if (t.indexOf("/alarm/restore") > 0) {
src\mqtt_manager.cpp:459:        _parseAlarm(payload, true);
src\mqtt_manager.cpp:460:    } else if (t.indexOf("/alarm") > 0) {
src\mqtt_manager.cpp:461:        _parseAlarm(payload, false);
src\mqtt_manager.cpp:475:void MQTTManager::_parseAlarm(const char* payload, bool isRestore) {
src\mqtt_manager.cpp:483:    AlarmCommand cmd;
src\mqtt_manager.cpp:487:    strncpy(cmd.type,    doc["type"]    | (isRestore ? "alarm_restore" : "alarm"), sizeof(cmd.type) - 1);
src\mqtt_manager.cpp:488:    strncpy(cmd.alarmType, doc["alarmType"] | "", sizeof(cmd.alarmType) - 1);
src\mqtt_manager.cpp:489:    strncpy(cmd.alarmLevel, doc["alarmLevel"] | "medium", sizeof(cmd.alarmLevel) - 1);
src\mqtt_manager.cpp:490:    strncpy(cmd.alarmDesc, doc["alarmDesc"] | "", sizeof(cmd.alarmDesc) - 1);
src\mqtt_manager.cpp:502:    Serial.printf("[MQTT] 🚨 告警指令: type=%s alarmType=%s buzzer=%d\n",
src\mqtt_manager.cpp:503:                  cmd.type, cmd.alarmType, cmd.buzzerOn);
src\mqtt_manager.cpp:505:    if (_alarmCb) {
src\mqtt_manager.cpp:506:        _alarmCb(cmd);


include\alarm_manager.h:5: * 本地模式: 离线也能用, 跌倒/水浸/心率异常/呼吸异常 本地判定触发
include\alarm_manager.h:21:        ALARM_WATER_LEAK,       // 水浸 (本地 + 服务端)
src\alarm_manager.cpp:5: * 本地: 跌倒/水浸/心率异常/呼吸异常 → 立即蜂鸣器+LED
src\alarm_manager.cpp:72:    case ALARM_WATER_LEAK:      name = "水浸";        break;
include\config.h:32:// --- 水浸传感器 ---
include\config.h:193:    // --- 水浸 ---
include\config.h:194:    bool     rainDO;              // 1=正常 0=水浸触发
include\config.h:195:    bool     rainValid;           // 水浸传感器有效性
src\display_manager.cpp:218:    _u8g2.drawUTF8(4, 30, "水浸:");
include\display_manager.h:9: *   Page 3 - 安全信息 (水浸 + 蜂鸣器 + 运行时间)
src\mqtt_manager.cpp:250:    // 水浸
src\mqtt_manager.cpp:251:    // 水浸: 0=有水 1=正常 2=无效(未接/故障)
src\main.cpp:5: * ESP32-S3 + R60ABD1 60G 毫米波雷达 + DHT11 + 水浸
src\main.cpp:219:    // 水浸
src\main.cpp:253:    // --- 水浸检测 ---
src\main.cpp:327:    Serial.printf("  水浸:%d (水浸有效:%d) | WiFi:%d 蜂鸣器:%d\n",
src\main.cpp:359:    Serial.println(F("║  ESP32-S3 + R60ABD1 + DHT11 + 水浸     ║"));
include\sensor_manager.h:3: * @brief 传感器管理器 - DHT11温湿度 + 水浸检测
include\sensor_manager.h:37:    /** 获取水浸状态 */
include\sensor_manager.h:39:    /** 水浸传感器有效性 */
include\sensor_manager.h:52:    bool    _rainDO;        // 1=正常 0=水浸
src\sensor_manager.cpp:3: * @brief 传感器管理器实现 - DHT11 + 水浸
src\sensor_manager.cpp:32:    // DHT11 — 允许失败，不影响水浸
src\sensor_manager.cpp:38:    // 水浸传感器 — 不接不误报
src\sensor_manager.cpp:51:    Serial.printf("  水浸:  %s\n",
src\sensor_manager.cpp:61:    // --- DHT11 读取 (独立，失败不影响水浸) ---
src\sensor_manager.cpp:89:    // 水浸传感器读取 (防抖)
src\sensor_manager.cpp:108:                Serial.println(F("[传感器] ⚠ 检测到水浸!"));
src\sensor_manager.cpp:110:                Serial.println(F("[传感器] ✅ 水浸解除"));


src\alarm_manager.cpp:125:    else if (strcmp(cmd.alarmType, "water_leak") == 0) localType = ALARM_WATER_LEAK;
src\alarm_manager.cpp:150:    else if (strcmp(cmd.alarmType, "water_leak") == 0) localType = ALARM_WATER_LEAK;
src\alarm_manager.cpp:180:    case ALARM_WATER_LEAK:      return "water_leak";
include\config.h:210:    char     alarmType[24];       // fall/water_leak/heart_abnormal/breath_abnormal
src\main.cpp:40:bool g_localWaterActive      = false;
src\main.cpp:85:        else if (strcmp(cmd.alarmType, "water_leak") == 0) g_localWaterActive = false;
src\main.cpp:254:    bool waterLeak = !g_sensor.getRainDO() && g_sensor.isRainValid();
src\main.cpp:255:    if (waterLeak && !g_localWaterActive) {
src\main.cpp:256:        g_localWaterActive = true;
src\main.cpp:258:    } else if (!waterLeak && g_localWaterActive) {
src\main.cpp:259:        g_localWaterActive = false;


src\alarm_manager.cpp:5: * 本地: 跌倒/水浸/心率异常/呼吸异常 → 立即蜂鸣器+LED
src\alarm_manager.cpp:12:    : _buzzerOn(false)
src\alarm_manager.cpp:14:    , _buzzerOffTime(0)
src\alarm_manager.cpp:15:    , _buzzerToggleTime(0)
src\alarm_manager.cpp:16:    , _buzzerBeepState(false)
src\alarm_manager.cpp:38:    _buzzerToggleTime = 0;
src\alarm_manager.cpp:39:    _buzzerBeepState = false;
src\alarm_manager.cpp:51:    _updateBuzzer();
src\alarm_manager.cpp:80:    // 立即开启蜂鸣器和 LED
src\alarm_manager.cpp:81:    _setBuzzer(true);
src\alarm_manager.cpp:101:        _setBuzzer(false);
src\alarm_manager.cpp:109:    _setBuzzer(false);
src\alarm_manager.cpp:119:    Serial.printf("[告警] ☁ 服务端告警: %s (等级=%s, 蜂鸣器=%d)\n",
src\alarm_manager.cpp:120:                  cmd.alarmType, cmd.alarmLevel, cmd.buzzerOn);
src\alarm_manager.cpp:127:    if (cmd.buzzerOn) {
src\alarm_manager.cpp:129:        if (cmd.buzzerDuration > 0) {
src\alarm_manager.cpp:130:            _buzzerOffTime = millis() + (uint32_t)cmd.buzzerDuration * 1000;
src\alarm_manager.cpp:132:            _buzzerOffTime = 0;
src\alarm_manager.cpp:136:    if (cmd.nightLight || cmd.buzzerOn) {
src\alarm_manager.cpp:154:    if (!cmd.buzzerOn) {
src\alarm_manager.cpp:155:        _setBuzzer(false);
src\alarm_manager.cpp:163:// 蜂鸣器测试
src\alarm_manager.cpp:166:void AlarmManager::buzzerTest(uint32_t durationSec) {
src\alarm_manager.cpp:167:    Serial.printf("[告警] 🔔 蜂鸣器测试 %d秒\n", durationSec);
src\alarm_manager.cpp:168:    _setBuzzer(true);
src\alarm_manager.cpp:169:    _buzzerOffTime = millis() + durationSec * 1000;
src\alarm_manager.cpp:192:void AlarmManager::_setBuzzer(bool on) {
src\alarm_manager.cpp:193:    _buzzerOn = on;
src\alarm_manager.cpp:194:    _buzzerBeepState = false;
src\alarm_manager.cpp:195:    _buzzerToggleTime = 0;
src\alarm_manager.cpp:198:        _buzzerOffTime = 0;  // 本地报警=无限哔哔
src\alarm_manager.cpp:201:        _buzzerOffTime = 0;
src\alarm_manager.cpp:210:void AlarmManager::_updateBuzzer() {
src\alarm_manager.cpp:212:    if (_buzzerOn && _buzzerOffTime > 0 && millis() >= _buzzerOffTime) {
src\alarm_manager.cpp:213:        _buzzerOn = false;
src\alarm_manager.cpp:215:        Serial.println(F("[告警] ⏰ 蜂鸣器定时关闭"));
src\alarm_manager.cpp:219:    if (_buzzerOn && _buzzerOffTime == 0) {
src\alarm_manager.cpp:222:        if (!_buzzerBeepState) {
src\alarm_manager.cpp:225:            if (now - _buzzerToggleTime >= pause) {
src\alarm_manager.cpp:226:                _buzzerToggleTime = now;
src\alarm_manager.cpp:228:                _buzzerBeepState = true;
src\alarm_manager.cpp:234:            if (now - _buzzerToggleTime >= 150) {
src\alarm_manager.cpp:235:                _buzzerToggleTime = now;
src\alarm_manager.cpp:236:                _buzzerBeepState = false;
src\alarm_manager.cpp:242:    else if (_buzzerOn && _buzzerOffTime > 0) {
include\alarm_manager.h:52:    /** 蜂鸣器测试 */
include\alarm_manager.h:53:    void buzzerTest(uint32_t durationSec);
include\alarm_manager.h:59:    bool isBuzzerOn() const      { return _buzzerOn; }
include\alarm_manager.h:64:    void _setBuzzer(bool on);
include\alarm_manager.h:66:    void _updateBuzzer();
include\alarm_manager.h:69:    // 蜂鸣器/LED 状态
include\alarm_manager.h:70:    bool     _buzzerOn;
include\alarm_manager.h:72:    uint32_t _buzzerOffTime;       // 定时关闭时刻 (0=手动关)
include\alarm_manager.h:73:    uint32_t _buzzerToggleTime;    // 间歇切换时刻
include\alarm_manager.h:74:    bool     _buzzerBeepState;     // 间歇状态 (响/停)
include\config.h:35:// --- 蜂鸣器 (PWM) ---
include\config.h:200:    bool     buzzerState;         // 蜂鸣器状态 1=开启
include\config.h:217:    bool     buzzerOn;            // 蜂鸣器开关
include\config.h:218:    uint16_t buzzerDuration;      // 蜂鸣持续秒数 (0=持续到恢复)
include\config.h:228:    char     controlType[24];     // buzzer_test/device_restart/report_interval_set/factory_reset
src\display_manager.cpp:74:void DisplayManager::update(const TelemetryData& data, bool wifiOk, bool buzzerOn) {
src\display_manager.cpp:84:    case 0: _drawPage0(data, wifiOk, buzzerOn); break;
src\display_manager.cpp:87:    case 3: _drawPage3(data, buzzerOn, wifiOk); break;
src\display_manager.cpp:88:    default: _drawPage0(data, wifiOk, buzzerOn); break;
src\display_manager.cpp:98:void DisplayManager::_drawPage0(const TelemetryData& data, bool wifiOk, bool buzzerOn) {
src\display_manager.cpp:129:    // 运行时间 + 蜂鸣器状态
src\display_manager.cpp:134:    if (buzzerOn) {
src\display_manager.cpp:213:void DisplayManager::_drawPage3(const TelemetryData& data, bool buzzerOn, bool wifiOk) {
src\display_manager.cpp:222:    _u8g2.drawUTF8(4, 44, "蜂鸣器:");
src\display_manager.cpp:223:    _u8g2.drawUTF8(60, 44, buzzerOn ? "响" : "关");
include\display_manager.h:9: *   Page 3 - 安全信息 (水浸 + 蜂鸣器 + 运行时间)
include\display_manager.h:31:     * @param buzzerOn 蜂鸣器是否在响
include\display_manager.h:33:    void update(const TelemetryData& data, bool wifiOk, bool buzzerOn);
include\display_manager.h:42:    void _drawPage0(const TelemetryData& data, bool wifiOk, bool buzzerOn);
include\display_manager.h:45:    void _drawPage3(const TelemetryData& data, bool buzzerOn, bool wifiOk);
src\main.cpp:109:    if (strcmp(cmd.controlType, "buzzer_test") == 0) {
src\main.cpp:111:        g_alarm.buzzerTest(dur);
src\main.cpp:226:    t.buzzerState = g_alarm.isBuzzerOn();
src\main.cpp:327:    Serial.printf("  水浸:%d (水浸有效:%d) | WiFi:%d 蜂鸣器:%d\n",
src\main.cpp:328:                  t.rainDO, t.rainValid, t.wifiReady, t.buzzerState);
src\main.cpp:492:        g_display.update(tele, wifiOk, g_alarm.isBuzzerOn());
src\mqtt_manager.cpp:258:    v["BuzzerState"] = d.buzzerState ? "1" : "0";
src\mqtt_manager.cpp:496:        cmd.buzzerOn       = action["buzzer"] | false;
src\mqtt_manager.cpp:497:        cmd.buzzerDuration = action["buzzerDuration"] | 0;
src\mqtt_manager.cpp:502:    Serial.printf("[MQTT] 🚨 告警指令: type=%s alarmType=%s buzzer=%d\n",
src\mqtt_manager.cpp:503:                  cmd.type, cmd.alarmType, cmd.buzzerOn);


No matches found


Good, I have a lot of results. Now let me read the key source files to understand the full logic. The most critical files are:

1. `src/alarm_manager.cpp` - The alarm manager implementation
2. `src/sensor_manager.cpp` - Water leak sensor handling
3. `src/main.cpp` - Main logic for triggering alarms
4. `include/alarm_manager.h` - Alarm manager header
5. `include/config.h` - Configuration (the relevant parts)

Let me read the most critical files now.


Now let me read the core implementation files to understand the full logic.


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


1	/**
2	 * @file sensor_manager.cpp
3	 * @brief 传感器管理器实现 - DHT11 + 水浸
4	 */
5
6	#include "sensor_manager.h"
7
8	SensorManager::SensorManager()
9	    : _dht(nullptr)
10	    , _temp(0)
11	    , _humi(0)
12	    , _dhtValid(false)
13	    , _dhtErrorCnt(0)
14	    , _rainDO(true)       // 1=正常
15	    , _rainValid(false)
16	    , _lastRainRaw(true)
17	    , _rainChangeTime(0)
18	    , _callback(nullptr)
19	    , _lastReadTime(0)
20	{
21	}
22
23	SensorManager::~SensorManager() {
24	    if (_dht) delete _dht;
25	}
26
27	// ============================================================
28	// 初始化
29	// ============================================================
30
31	bool SensorManager::begin() {
32	    // DHT11 — 允许失败，不影响水浸
33	    _dht = new DHT(DHT11_PIN, DHT_TYPE);
34	    _dht->begin();
35	    _dhtValid = false;
36	    _dhtErrorCnt = 0;
37
38	    // 水浸传感器 — 不接不误报
39	    pinMode(WATER_SENSOR_PIN, INPUT_PULLUP);
40	    _lastRainRaw = (digitalRead(WATER_SENSOR_PIN) == HIGH);
41	    _rainDO = true;
42	    _rainValid = false;       // 默认无效，接传感器后自动变 true
43	    _rainChangeTime = millis();
44
45	    // 首次读取 (DHT11 可能失败)
46	    read();
47
48	    Serial.println(F("[传感器] 初始化完成"));
49	    Serial.printf("  DHT11: %s\n",
50	                  _dhtValid ? "✅ 正常" : "⚠ 未检测到 (不影响系统运行)");
51	    Serial.printf("  水浸:  %s\n",
52	                  _rainValid ? "✅ 正常" : "⚠ 未检测到");
53	    return true;  // 始终成功，不阻塞系统启动
54	}
55
56	// ============================================================
57	// 读取传感器
58	// ============================================================
59
60	void SensorManager::read() {
61	    // --- DHT11 读取 (独立，失败不影响水浸) ---
62	    if (millis() - _lastReadTime >= 2000) {
63	        _lastReadTime = millis();
64
65	        float t = _dht->readTemperature();
66	        float h = _dht->readHumidity();
67
68	        if (!isnan(t) && !isnan(h)) {
69	            if (t > -20 && t < 80 && h >= 0 && h <= 100) {
70	                _temp = t;
71	                _humi = h;
72	                _dhtValid = true;
73	                _dhtErrorCnt = 0;  // 恢复正常，清零计数
74	            } else {
75	                _dhtValid = false;
76	                _dhtErrorCnt++;
77	            }
78	        } else {
79	            _dhtValid = false;
80	            _dhtErrorCnt++;
81	        }
82
83	        // 连续失败 10 次 (~20秒) 后只报一次，避免刷屏
84	        if (_dhtErrorCnt == 10) {
85	            Serial.println(F("[传感器] ⚠ DHT11 持续读取失败 (不影响其他传感器)"));
86	        }
87	    }
88
89	    // 水浸传感器读取 (防抖)
90	    bool raw = (digitalRead(WATER_SENSOR_PIN) == HIGH);  // HIGH=正常
91	    if (raw != _lastRainRaw) {
92	        _rainChangeTime = millis();
93	        _lastRainRaw = raw;
94	    }
95
96	    if (millis() - _rainChangeTime >= WATER_DEBOUNCE_MS) {
97	        bool newState = _lastRainRaw;
98	        if (newState != _rainDO) {
99	            _rainDO = newState;
100	            // 连续3次读到同值才确认传感器存在
101	            static uint8_t stableCnt = 0;
102	            static bool lastStable = false;
103	            if (newState == lastStable) stableCnt++;
104	            else { stableCnt = 1; lastStable = newState; }
105	            if (stableCnt >= 3) _rainValid = true;
106
107	            if (!_rainDO) {
108	                Serial.println(F("[传感器] ⚠ 检测到水浸!"));
109	            } else {
110	                Serial.println(F("[传感器] ✅ 水浸解除"));
111	            }
112
113	            if (_callback) _callback();
114	        }
115	    }
116	}
117
118	// ============================================================
119	// 回调
120	// ============================================================
121
122	void SensorManager::onChange(SensorCallback cb) {
123	    _callback = cb;
124	}
125


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	    uint32_t _buzzerToggleTime;    // 间歇切换时刻
74	    bool     _buzzerBeepState;     // 间歇状态 (响/停)
75	    uint8_t  _beepCount;           // 当前周期哔了几声
76
77	    // 活动报警列表 (最多 4 个同时)
78	    AlarmType _activeAlarms[4];
79	    uint8_t   _activeCount;
80
81	    // LED 闪烁
82	    uint32_t _lastLEDToggle;
83	};
84
85	#endif // ALARM_MANAGER_H
86


220	    t.rainDO    = g_sensor.getRainDO();
221	    t.rainValid = g_sensor.isRainValid();
222
223	    // 设备状态
224	    t.wifiReady  = g_wifi.isConnected() && g_mqtt.isConnected();
225	    t.uptimeSec  = (millis() - g_bootTime) / 1000;
226	    t.buzzerState = g_alarm.isBuzzerOn();
227	    t.lastUpdate = millis();
228
229	    return t;
230	}
231
232	// ============================================================
233	// 本地报警检查 (离线可用)
234	// ============================================================
235
236	void checkLocalAlarms(const RadarRawData& radar) {
237	    uint32_t now = millis();
238
239	    // --- 跌倒检测 (持续1秒才触发，防坐下误判) ---
240	    if (radar.fall && radar.radarValid && !g_localFallActive) {
241	        if (g_fallStartTime == 0) g_fallStartTime = now;
242	        if (now - g_fallStartTime >= 1000) {  // 跌倒持续1秒才报警
243	            g_localFallActive = true;
244	            g_alarm.triggerLocal(AlarmManager::ALARM_FALL);
245	        }
246	    } else if (!radar.fall && g_localFallActive) {
247	        g_localFallActive = false;
248	        g_fallStartTime = 0;
249	        g_alarm.clearLocal(AlarmManager::ALARM_FALL);
250	    }
251	    if (!radar.fall) g_fallStartTime = 0;  // 没有跌倒时重置计时
252
253	    // --- 水浸检测 ---
254	    bool waterLeak = !g_sensor.getRainDO() && g_sensor.isRainValid();
255	    if (waterLeak && !g_localWaterActive) {
256	        g_localWaterActive = true;
257	        g_alarm.triggerLocal(AlarmManager::ALARM_WATER_LEAK);
258	    } else if (!waterLeak && g_localWaterActive) {
259	        g_localWaterActive = false;
260	        g_alarm.clearLocal(AlarmManager::ALARM_WATER_LEAK);
261	    }
262
263	    // --- 有心率呼吸报警延迟: 检测到人后等 30 秒数据稳定 ---
264	    #define VITAL_ALARM_DELAY_MS  30000
265	    if (radar.presence && !g_vitalAlarmReady) {
266	        if (g_presenceStartTime == 0) g_presenceStartTime = now;
267	        if (now - g_presenceStartTime >= VITAL_ALARM_DELAY_MS) {
268	            g_vitalAlarmReady = true;
269	            Serial.println(F("[报警] 心率呼吸监测就绪 (30秒稳定期结束)"));
270	        }
271	    } else if (!radar.presence) {
272	        g_presenceStartTime = 0;
273	        g_vitalAlarmReady   = false;
274	    }
275
276	    // --- 心率异常 (仅有人+就绪时判定) ---
277	    if (g_vitalAlarmReady && radar.vitalValid && radar.heartRate > 0) {
278	        bool heartAbn = (radar.heartRate < HEART_RATE_MIN || radar.heartRate > HEART_RATE_MAX);
279	        if (heartAbn && !g_localHeartAbnActive) {
280	            g_localHeartAbnActive = true;
281	            g_alarm.triggerLocal(AlarmManager::ALARM_HEART_ABNORMAL);
282	        } else if (!heartAbn && g_localHeartAbnActive) {
283	            g_localHeartAbnActive = false;
284	            g_alarm.clearLocal(AlarmManager::ALARM_HEART_ABNORMAL);
285	        }
286	    }
287	    // 无人时清除心率异常报警
288	    else if (!radar.presence && g_localHeartAbnActive) {
289	        g_localHeartAbnActive = false;
290	        g_alarm.clearLocal(AlarmManager::ALARM_HEART_ABNORMAL);
291	    }
292
293	    // --- 呼吸异常 (仅有人+就绪时判定) ---
294	    if (g_vitalAlarmReady && radar.vitalValid && radar.breathRate > 0) {
295	        bool breathAbn = (radar.breathRate < BREATH_RATE_MIN || radar.breathRate > BREATH_RATE_MAX);
296	        if (breathAbn && !g_localBreathAbnActive) {
297	            g_localBreathAbnActive = true;
298	            g_alarm.triggerLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
299	        } else if (!breathAbn && g_localBreathAbnActive) {
300	            g_localBreathAbnActive = false;
301	            g_alarm.clearLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
302	        }
303	    }
304	    // 无人时清除呼吸异常报警
305	    else if (!radar.presence && g_localBreathAbnActive) {
306	        g_localBreathAbnActive = false;
307	        g_alarm.clearLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
308	    }
309	}
310
311	// ============================================================
312	// 调试输出
313	// ============================================================
314
315	void printDebug(const TelemetryData& t) {
316	    static uint32_t lastPrint = 0;
317	    if (millis() - lastPrint < 5000) return;
318	    lastPrint = millis();
319
320	    Serial.println(F("─── 遥测数据 ───"));
321	    Serial.printf("  环境: T=%.1f°C H=%.1f%% (温湿度:%d)\n",
322	                  t.temp, t.humi, t.dhtValid);
323	    Serial.printf("  存在:%d 运动:%d 跌倒:%d 驻留:%d (雷达:%d)\n",
324	                  t.presence, t.motion, t.fall, t.resident, t.radarValid);
325	    Serial.printf("  心率:%d 呼吸:%d (体征:%d)\n",
326	                  t.heartRate, t.breathRate, t.vitalValid);
327	    Serial.printf("  水浸:%d (水浸有效:%d) | WiFi:%d 蜂鸣器:%d\n",
328	                  t.rainDO, t.rainValid, t.wifiReady, t.buzzerState);
329	    Serial.printf("  运行:%ds 序号:%d\n", t.uptimeSec, g_sequence);
330	    Serial.println(F("─────────────────"));
331	}
332
333	// ============================================================
334	// 雷达任务 - 核心0 专用
335	// ============================================================
336
337	void radarTask(void* param) {
338	    Serial.println(F("[任务] 📡 雷达任务启动 (核心0)"));
339	    TickType_t lastWake = xTaskGetTickCount();


25	// --- 雷达备用 GPIO ---
26	#define RADAR_GPIO1_PIN       2             // 活跃/静止指示 (从10挪到2)
27	#define RADAR_GPIO2_PIN       11            // 有人/无人指示
28
29	// --- DHT11 温湿度传感器 ---
30	#define DHT11_PIN             4             // 温湿度传感器
31
32	// --- 水浸传感器 ---
33	#define WATER_SENSOR_PIN      45            // 数字输入, 低电平=有水
34
35	// --- 蜂鸣器 (PWM) ---
36	#define BUZZER_PIN            21
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	// --- OLED 显示屏 (SSD1306 128x64 I2C) ---
50	#define OLED_SDA_PIN          15            // I2C 数据线
51	#define OLED_SCL_PIN          16            // I2C 时钟线
52
53	// --- 交互按钮 ---
54	#define BTN_PROVISION_PIN     14            // 擦除WiFi配网按钮 (长按3秒)
55	#define BTN_ALARM_DISMISS_PIN 13            // 解除报警按钮
56	#define BTN_PAGE_SWITCH_PIN   46            // 翻页按钮
57	#define BTN_DEBOUNCE_MS       50            // 去抖时间
58	#define BTN_LONG_PRESS_MS     1500          // 长按判定 (1.5秒)
59
60	// ============================================================
61	// 2. 设备标识 (协议规范 §2)
62	// ============================================================
63
64	#define SALEID_DEFAULT        "10100001"    // 站点/系统编号
65	#define GATEID_DEFAULT        "10100001001" // 网关唯一编号
66	#define METER_ID_DEFAULT      "ENV001"      // 感知节点ID (卧室=ENV001, 卫生间=ENV002, 客厅=ENV003)
67	#define METER_NAME_DEFAULT    "RadarDHT11"  // 设备型号名称
68	#define FW_VERSION            "V1.0.0"
69
70	// ============================================================
71	// 3. WiFi 默认配置 (配网使用)
72	// ============================================================
73
74	#define WIFI_AP_SSID          "HealthMonitor-Setup"
75	#define WIFI_AP_PASSWORD      "12345678"
76	#define WIFI_CONFIG_TIMEOUT   60
77	#define WIFI_CONNECT_TIMEOUT  15
78	#define WIFI_RETRY_INTERVAL   10000
79
80	// ============================================================
81	// 4. MQTT 默认配置 (协议规范 §2)
82	// ============================================================
83
84	#define MQTT_DEFAULT_SERVER   "202.110.234.102"
85	#define MQTT_DEFAULT_PORT     8000
86	#define MQTT_DEFAULT_PORT_STR "8000"         // WiFiManager 参数用字符串
87	#define MQTT_DEFAULT_USER     "test"
88	#define MQTT_DEFAULT_PASS     "testtest"
89	#define MQTT_DEFAULT_DEVICE   "10100001001"  // 默认网关ID = gateid
90	#define MQTT_DEFAULT_ENV      "主卧"           // 默认环境备注
91	#undef MQTT_KEEPALIVE
92	#define MQTT_KEEPALIVE        30              // 30秒 (协议规范)
93	#define MQTT_RECONNECT_MIN    1000
94	#define MQTT_RECONNECT_MAX    60000
95
96	// ============================================================
97	// 5. MQTT 主题定义 (协议规范 §3)
98	// ============================================================
99
100	// --- 上行主题 (设备 → 服务端) ---
101	#define MQTT_TOPIC_REPORT_ALLPOINTS   "report/allpoints"                // 全量遥测上报 QoS0
102	#define MQTT_TOPIC_STATUS_ONLINE      "status/%s/online"                // 上线通知 QoS1
103	#define MQTT_TOPIC_STATUS_OFFLINE     "status/%s/offline"               // 离线遗嘱 QoS1
104	#define MQTT_TOPIC_STATUS_PROVISION   "status/%s/provision"             // 配网状态 QoS1
105	#define MQTT_TOPIC_RESPONSE_CMD       "response/%s/cmd"                 // 指令响应 QoS1
106	#define MQTT_TOPIC_RESPONSE_PROVISION "response/%s/provision"           // 配网响应 QoS1
107
108	// --- 下行主题 (服务端 → 设备) ---
109	#define MQTT_TOPIC_CMD_ALARM          "cmd/%s/alarm"                    // 告警指令 QoS1
110	#define MQTT_TOPIC_CMD_ALARM_RESTORE  "cmd/%s/alarm/restore"            // 告警恢复 QoS1
111	#define MQTT_TOPIC_CMD_CONTROL        "cmd/%s/control"                  // 控制指令 QoS1
112	#define MQTT_TOPIC_CMD_PROVISION_WIFI "cmd/%s/provision/wifi"           // WiFi配网 QoS1
113
114	// ============================================================
115	// 6. 上报配置 (协议规范 §4.1, §7.2)
116	// ============================================================
117
118	#define REPORT_INTERVAL_MS    1000                // 遥测上报周期 1秒
119	#define REPORT_QOS            0                   // 遥测 QoS 0
120	#define STATUS_QOS            1                   // 状态/响应 QoS 1
121
122	// ============================================================
123	// 7. 报警阈值 (协议规范 §6, 服务端判定用)
124	// ============================================================


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	    /** DHT 是否持续故障 */
35	    bool  isDhtFault() const     { return _dhtErrorCnt >= 10; }
36
37	    /** 获取水浸状态 */
38	    bool  getRainDO() const      { return _rainDO; }
39	    /** 水浸传感器有效性 */
40	    bool  isRainValid() const    { return _rainValid; }
41
42	    /** 设置数据变化回调 */
43	    void onChange(SensorCallback cb);
44
45	private:
46	    DHT*    _dht;
47	    float   _temp;
48	    float   _humi;
49	    bool    _dhtValid;
50	    uint8_t _dhtErrorCnt;   // 连续失败计数
51
52	    bool    _rainDO;        // 1=正常 0=水浸
53	    bool    _rainValid;
54	    bool    _lastRainRaw;
55	    uint32_t _rainChangeTime;
56
57	    SensorCallback _callback;
58	    uint32_t _lastReadTime;
59	};
60
61	#endif // SENSOR_MANAGER_H
62


Now let me also read the config.h for the alarm threshold definitions and the display manager for the "报警" display logic.


Let me also read the threshold definitions and the display logic.


122	// ============================================================
123	// 7. 报警阈值 (协议规范 §6, 服务端判定用)
124	// ============================================================
125
126	#define HEART_RATE_MIN        50
127	#define HEART_RATE_MAX        120
128	#define BREATH_RATE_MIN       8
129	#define BREATH_RATE_MAX       24
130	#define TEMP_LOW              5.0f
131	#define TEMP_HIGH             45.0f
132	#define HUMI_LOW              15.0f
133	#define HUMI_HIGH             90.0f
134	#define WATER_DEBOUNCE_MS     50
135
136	// ============================================================
137	// 8. 任务配置
138	// ============================================================
139
140	#define RADAR_TASK_STACK      4096
141	#define RADAR_TASK_PRIORITY   5
142	#define RADAR_TASK_CORE       0
143
144	#define MQTT_TASK_STACK       8192
145	#define MQTT_TASK_PRIORITY    4
146	#define MQTT_TASK_CORE        1
147
148	// ============================================================
149	// 9. NTP 时间同步
150	// ============================================================
151
152	#define NTP_SERVER            "ntp.aliyun.com"
153	#define NTP_GMT_OFFSET_SEC    28800             // UTC+8 (北京时间)
154	#define NTP_DAYLIGHT_OFFSET   0
155
156	// ============================================================
157	// 10. LittleFS 配置文件名
158	// ============================================================
159
160	#define CONFIG_FILE_MQTT      "/mqtt_config.json"
161	#define CONFIG_FILE_WIFI      "/wifi_config.json"
162	#define CONFIG_FILE_DEVICE    "/device_config.json"
163
164	// ============================================================
165	// 11. 结构体: 全量遥测数据 (协议规范 §4.1)
166	// ============================================================
167
168	struct TelemetryData {
169	    // --- 设备标识 ---
170	    char     saleid[12];          // 站点编号 "10100001"
171	    char     gateid[16];          // 网关编号 "10100001001"
172	    char     meterId[8];          // 节点ID "ENV001"
173	    char     meterName[16];       // 设备型号 "RadarDHT11"
174
175	    // --- 环境数据 (DHT11) ---
176	    float    temp;                // 温度 ℃
177	    float    humi;                // 湿度 %RH
178	    bool     dhtValid;            // 温湿度有效性
179
180	    // --- 雷达: 存在/运动 ---
181	    bool     presence;            // 有人=1 无人=0
182	    bool     motion;              // 有运动=1 无运动=0
183	    bool     fall;                // 跌倒检测 1=触发
184	    bool     resident;            // 驻留状态 1=驻留
185
186	    // --- 雷达: 生命体征 ---
187	    uint8_t  heartRate;           // 心率 bpm (无效=0)
188	    uint8_t  breathRate;          // 呼吸 次/分 (无效=0)
189	    uint8_t  body;                // 人体能量/体征值
190	    bool     radarValid;          // 雷达数据有效性
191	    bool     vitalValid;          // 心率呼吸有效性
192
193	    // --- 水浸 ---
194	    bool     rainDO;              // 1=正常 0=水浸触发
195	    bool     rainValid;           // 水浸传感器有效性
196
197	    // --- 设备状态 ---
198	    bool     wifiReady;           // WiFi/MQTT 连接状态
199	    uint32_t uptimeSec;           // 设备累计运行秒数
200	    bool     buzzerState;         // 蜂鸣器状态 1=开启
201
202	    // --- 时间戳 ---
203	    uint32_t lastUpdate;          // 最后更新 ms
204	};
205
206	// 结构体: 下行报警指令 (协议规范 §5.1, §5.2)
207	struct AlarmCommand {
208	    char     cmdId[32];           // 指令唯一ID
209	    char     type[20];            // alarm / alarm_restore
210	    char     alarmType[24];       // fall/water_leak/heart_abnormal/breath_abnormal
211	    char     alarmLevel[8];       // low/medium/high
212	    char     alarmDesc[64];       // 告警描述
213	    char     meterId[8];          // 触发节点ID
214	    char     relatedCmdId[32];    // 关联原始告警ID (恢复用)
215
216	    // 联动动作
217	    bool     buzzerOn;            // 蜂鸣器开关
218	    uint16_t buzzerDuration;      // 蜂鸣持续秒数 (0=持续到恢复)
219	    bool     nightLight;          // 夜灯
220
221	    uint32_t timestamp;
222	};
223
224	// 结构体: 下行控制指令 (协议规范 §5.3)
225	struct ControlCommand {
226	    char     cmdId[32];
227	    char     type[20];            // control
228	    char     controlType[24];     // buzzer_test/device_restart/report_interval_set/factory_reset
229	    uint32_t params_duration;     // 参数: 持续秒数
230	    uint32_t params_interval;     // 参数: 上报间隔
231	    uint32_t timestamp;
232	};
233
234	// 结构体: 下行配网指令 (协议规范 §5.4)
235	struct ProvisionCommand {
236	    char     cmdId[32];
237	    char     wifi_ssid[33];
238	    char     wifi_password[65];
239	    char     wifi_encrypt[8];     // OPEN/WPA2/WPA3
240	    char     mqtt_broker[64];
241	    uint16_t mqtt_port;


205
206	    _u8g2.drawUTF8(110, 12, "3/4");
207	}
208
209	// ============================================================
210	// Page 3: 安全信息
211	// ============================================================
212
213	void DisplayManager::_drawPage3(const TelemetryData& data, bool buzzerOn, bool wifiOk) {
214	    _u8g2.setFont(u8g2_font_wqy12_t_gb2312);
215	    _u8g2.drawUTF8(2, 12, "安全信息");
216	    _u8g2.drawHLine(0, 15, 128);
217
218	    _u8g2.drawUTF8(4, 30, "水浸:");
219	    if (!data.rainValid) _u8g2.drawUTF8(60, 30, "--");
220	    else _u8g2.drawUTF8(60, 30, data.rainDO ? "正常" : "!!报警");
221
222	    _u8g2.drawUTF8(4, 44, "蜂鸣器:");
223	    _u8g2.drawUTF8(60, 44, buzzerOn ? "响" : "关");
224
225	    _u8g2.drawUTF8(4, 58, "WiFi:");
226	    _u8g2.drawUTF8(60, 58, wifiOk ? "已连接" : "未连接");
227
228	    _u8g2.drawUTF8(2, 64, _fmtUptime(data.uptimeSec).c_str());
229	    _u8g2.drawUTF8(110, 12, "4/4");
230	}
231
232	// ============================================================
233	// 辅助方法
234	// ============================================================


360	    Serial.println(F("║  MQTT 协议 V0.08                        ║"));
361	    Serial.println(F("╚══════════════════════════════════════════╝"));
362
363	    g_bootTime = millis();
364
365	    // 1. 报警模块 (最先, 引脚安全)
366	    g_alarm.begin();
367
368	    // 2. 雷达
369	    g_radar.begin();
370
371	    // 3. OLED 显示
372	    g_display.begin();
373
374	    // 4. 按钮
375	    g_buttons.begin();
376	    g_buttons.onPageSwitch([]() {
377	        g_display.nextPage();
378	    });
379	    g_buttons.onAlarmDismiss([]() {
380	        g_alarm.clearAllLocal();
381	        g_radar.clearFall();  // 数据也归零
382	    });
383	    g_buttons.onProvisionLongPress([]() {
384	        g_wifi.startConfigPortal();
385	    });
386
387	    // 5. WiFi (WiFiManager 配网)
388	    // 先注册配网状态回调, 再 begin(), 这样配网过程中的 ready/success 都不会丢失
389	    g_wifi.onProvisionState([](const char* state) {
390	        g_mqtt.publishProvisionStatus(state, "");
391	    });
392	    g_wifi.begin();
393
394	    // 6. 时间同步
395	    if (g_wifi.isConnected()) {
396	        syncTime();
397	    }
398
399	    // 7. MQTT
400	    DeviceConfig cfg = g_wifi.getConfig();
401	    g_mqtt.setServer(cfg.mqtt_server, atoi(cfg.mqtt_port),
402	                     cfg.mqtt_user, cfg.mqtt_pass);
403	    g_mqtt.begin(cfg.device_id);
404
405	    // 注册 MQTT 回调
406	    g_mqtt.onAlarmCommand(handleAlarmCommand);
407	    g_mqtt.onControlCommand(handleControlCommand);
408	    g_mqtt.onProvisionCommand(handleProvisionCommand);
409
410	    // 连接 MQTT
411	    if (g_wifi.isConnected()) {
412	        g_mqtt.connect();
413	    }
414
415	    // 8. 传感器
416	    g_sensor.begin();
417
418	    // 9. 雷达任务 (核心0)
419	    xTaskCreatePinnedToCore(
420	        radarTask, "RadarTask",
421	        RADAR_TASK_STACK, nullptr,
422	        RADAR_TASK_PRIORITY, nullptr,
423	        RADAR_TASK_CORE
424	    );
425
426	    Serial.println(F("[系统] ✅ 系统就绪"));
427	    Serial.println(F("══════════════════════════════════════════\n"));
428	}
429
430	// ============================================================
431	// 主循环 (核心1)
432	// ============================================================
433
434	void loop() {
435	    uint32_t now = millis();
436
437	    // --- 0. 按钮轮询 ---
438	    g_buttons.loop();
439
440	    // --- 1. WiFi ---
441	    bool wifiOk = g_wifi.loop();
442
443	    // --- 2. MQTT (含自动重连) ---
444	    if (wifiOk) {
445	        g_mqtt.loop();
446
447	        // WiFi 刚连上但 MQTT 还没连
448	        static bool wasDisconnected = true;
449	        if (g_mqtt.isConnected()) {
450	            if (wasDisconnected) {
451	                // 刚恢复连接, 同步时间
452	                syncTime();
453	                wasDisconnected = false;
454	            }
455	        } else {
456	            wasDisconnected = true;
457	        }
458	    }
459
460	    // --- 3. 传感器 (独立，任何传感器故障不影响其他) ---
461	    g_sensor.read();
462
463	    // --- 4. 本地报警检查 (离线也工作) ---
464	    RadarRawData radar = g_radar.getData();
465	    checkLocalAlarms(radar);
466
467	    // --- 传感器故障检测 (每30秒检查一次，避免刷屏) ---
468	    static uint32_t lastFaultCheck = 0;
469	    if (now - lastFaultCheck > 30000) {
470	        lastFaultCheck = now;
471	        if (!g_sensor.isDhtValid() && millis() > 60000) {
472	            Serial.println(F("[系统] ⚠ DHT11 未检测到 (不影响运行)"));
473	        }
474	        if (g_radar.isTimeout()) {
475	            Serial.println(F("[系统] ⚠ 雷达超时无数据 (不影响运行)"));
476	        }
477	    }
478
479	    // --- 5. 报警模块更新 (定时器/闪烁) ---
480	    g_alarm.loop();
481
482	    // --- 6. 遥测上报 (1秒周期) ---
483	    if (now - g_lastReportTime >= REPORT_INTERVAL_MS) {
484	        g_lastReportTime = now;
485
486	        TelemetryData tele = buildTelemetry(radar);
487	        g_mqtt.publishAllPoints(tele, g_sequence);
488	        g_sequence++;
489
490	        // 调试输出 + OLED 显示
491	        printDebug(tele);
492	        g_display.update(tele, wifiOk, g_alarm.isBuzzerOn());
493	    }
494
495	    // --- 7. MQTT 配网状态 (WiFiManager 触发时) ---
496	    // WiFiManager 的 captive portal 启动/关闭时更新状态
497	    // 此逻辑已集成在 wifi_manager 中
498
499	    delay(10);


Now I have all the information needed to provide a comprehensive analysis. Let me also check the main.cpp from the beginning for the global variables and the handleAlarmCommand / handleControlCommand functions.


Let me read the beginning of main.cpp for variable declarations and the handler functions.


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	uint32_t g_presenceStartTime = 0;       // 首次检测到人的时刻
44	bool     g_vitalAlarmReady   = false;   // 心率呼吸报警就绪 (有人30秒后)
45	uint32_t g_fallStartTime     = 0;       // 跌倒开始时刻 (防误判)
46
47	// ============================================================
48	// 时间同步
49	// ============================================================
50
51	void syncTime() {
52	    Serial.println(F("[时间] 正在同步时间..."));
53	    configTime(NTP_GMT_OFFSET_SEC, NTP_DAYLIGHT_OFFSET, NTP_SERVER, "pool.ntp.org");
54
55	    struct tm timeinfo;
56	    int retry = 0;
57	    while (!getLocalTime(&timeinfo) && retry < 20) {
58	        delay(500);
59	        Serial.print(".");
60	        retry++;
61	    }
62
63	    if (retry < 20) {
64	        Serial.println();
65	        Serial.printf("[时间] ✅ 时间同步成功: %04d-%02d-%02d %02d:%02d:%02d\n",
66	                      timeinfo.tm_year + 1900, timeinfo.tm_mon + 1,
67	                      timeinfo.tm_mday, timeinfo.tm_hour,
68	                      timeinfo.tm_min, timeinfo.tm_sec);
69	    } else {
70	        Serial.println(F("\n[时间] ⚠ 时间同步失败, 使用默认时间"));
71	    }
72	}
73
74	// ============================================================
75	// MQTT 告警指令回调 (协议规范 §5.1, §5.2)
76	// ============================================================
77
78	void handleAlarmCommand(const AlarmCommand& cmd) {
79	    if (strcmp(cmd.type, "alarm_restore") == 0) {
80	        // 告警恢复
81	        g_alarm.executeRestore(cmd);
82
83	        // 清除对应本地告警状态
84	        if (strcmp(cmd.alarmType, "fall") == 0)           g_localFallActive = false;
85	        else if (strcmp(cmd.alarmType, "water_leak") == 0) g_localWaterActive = false;
86	        else if (strcmp(cmd.alarmType, "heart_abnormal") == 0)  g_localHeartAbnActive = false;
87	        else if (strcmp(cmd.alarmType, "breath_abnormal") == 0) g_localBreathAbnActive = false;
88	    } else {
89	        // 告警触发
90	        g_alarm.executeAlarm(cmd);
91	    }
92
93	    // 回复响应 (§4.5)
94	    g_mqtt.publishCmdResponse(cmd.cmdId, cmd.type, cmd.alarmType,
95	                              true, "0", "");
96	}
97
98	// ============================================================
99	// MQTT 控制指令回调 (§5.3)
100	// ============================================================


I now have all the information I need to provide a comprehensive analysis. Let me organize my findings.


Now I have all the data needed. Here is the complete analysis.

---

## 综合分析报告：报警(Alarm)、水浸(Water/Leak)、蜂鸣器(Buzzer) 相关逻辑

---

### 一、文件清单

以下是所有包含相关逻辑的文件（绝对路径）：

| 文件 | 角色 |
|------|------|
| `d:\PlatformIO1\Projects\Healthv2.0\include\alarm_manager.h` | 报警管理器头文件，定义告警类型枚举和接口 |
| `d:\PlatformIO1\Projects\Healthv2.0\src\alarm_manager.cpp` | 报警管理器实现：蜂鸣器控制、LED控制、本地/服务端双模式 |
| `d:\PlatformIO1\Projects\Healthv2.0\src\main.cpp` | 主程序逻辑：`checkLocalAlarms()` 是报警判定的核心入口 |
| `d:\PlatformIO1\Projects\Healthv2.0\src\sensor_manager.cpp` | 水浸传感器驱动：GPIO读取、防抖、有效性判断 |
| `d:\PlatformIO1\Projects\Healthv2.0\include\sensor_manager.h` | 传感器管理器头文件 |
| `d:\PlatformIO1\Projects\Healthv2.0\include\config.h` | 硬件引脚定义、MQTT主题、阈值常量、AlarmCommand 结构体 |
| `d:\PlatformIO1\Projects\Healthv2.0\src\display_manager.cpp` | OLED显示 Page3 绘制水浸状态与蜂鸣器状态 |
| `d:\PlatformIO1\Projects\Healthv2.0\src\button_manager.cpp` | 按钮管理，GPIO13 短按解除报警 |
| `d:\PlatformIO1\Projects\Healthv2.0\src\mqtt_manager.cpp` | MQTT 解析服务端下发的 alarm / alarm_restore 指令 |
| `d:\PlatformIO1\Projects\Healthv2.0\include\mqtt_manager.h` | MQTT 告警回调函数类型定义 |

---

### 二、报警类型定义 (AlarmManager::AlarmType)

定义在 `alarm_manager.h` 第18-24行：

```cpp
enum AlarmType {
    ALARM_NONE = 0,
    ALARM_FALL,             // 跌倒 (本地 + 服务端)
    ALARM_WATER_LEAK,       // 水浸 (本地 + 服务端)
    ALARM_HEART_ABNORMAL,   // 心率异常 (本地)
    ALARM_BREATH_ABNORMAL,  // 呼吸异常 (本地)
    ALARM_SERVER,           // 服务端下发 (通用)
};
```

最多同时存在 4 个活动报警（`_activeAlarms[4]`）。

---

### 三、蜂鸣器(Buzzer)与报警触发的完整条件链

系统采用双模式报警架构：

#### 3.1 本地报警 (离线可用)

核心判定函数是 `main.cpp` 中的 `checkLocalAlarms()` (第236行)，在主循环每次迭代时调用 (第465行)。四个触发条件如下：

**条件1：跌倒检测 (第240-251行)**
```
触发条件: radar.fall == true  AND  radar.radarValid == true  AND  跌倒状态持续 >= 1000ms
```
- 有跌倒信号且雷达数据有效，且持续1秒以上才触发，防止坐下时误判。
- 清除条件：雷达跌倒信号消失，自动清除。

**条件2：水浸检测 (第254-261行)**
```
触发条件: waterLeak == true
waterLeak = (getRainDO() == false) AND (isRainValid() == true)
```
- `getRainDO()` 返回 `false` 表示有水（引脚低电平=有水）。
- `isRainValid()` 返回 `true` 表示水浸传感器已被确认存在且正常工作（详见下文第四节）。
- 清除条件：水浸状态解除（传感器读回正常），自动清除。

**条件3：心率异常 (第277-291行)**
```
触发条件: g_vitalAlarmReady == true
          AND radar.vitalValid == true
          AND radar.heartRate > 0
          AND (heartRate < 50  OR  heartRate > 120)
```
- `g_vitalAlarmReady` 需要"检测到人后等待 30 秒数据稳定"才变为 true (第265-269行)。
- 阈值：`HEART_RATE_MIN = 50`，`HEART_RATE_MAX = 120`（定义在 config.h 第126-127行）。
- 清除条件：心率恢复到正常区间，或人离开（无人时自动清除）。

**条件4：呼吸异常 (第294-308行)**
```
触发条件: g_vitalAlarmReady == true
          AND radar.vitalValid == true
          AND radar.breathRate > 0
          AND (breathRate < 8  OR  breathRate > 24)
```
- 阈值：`BREATH_RATE_MIN = 8`，`BREATH_RATE_MAX = 24`（定义在 config.h 第128-129行）。

#### 3.2 服务端报警 (在线联动)

服务端通过 MQTT 主题下发告警指令：
- Topic: `cmd/{gateid}/alarm` -- 触发告警
- Topic: `cmd/{gateid}/alarm/restore` -- 恢复告警

在 `main.cpp` 第78-96行 `handleAlarmCommand()` 处理：
- 收到 `alarm` 类型 -> 调用 `g_alarm.executeAlarm(cmd)`，根据 `cmd.buzzerOn` 决定是否开启蜂鸣器，`cmd.buzzerDuration` 决定持续时长。
- 收到 `alarm_restore` 类型 -> 调用 `g_alarm.executeRestore(cmd)`，清除对应本地告警状态。
- 都返回指令响应到服务端。

#### 3.3 蜂鸣器测试 (控制指令)

在 `main.cpp` 第109-111行，收到 `controlType == "buzzer_test"` 的控制指令后，调用 `g_alarm.buzzerTest(durationSec)` 进行定时蜂鸣器测试。

---

### 四、水浸传感器数据处理逻辑 (重点："水浸有效"标志)

实现位于 `sensor_manager.cpp`，关键逻辑如下：

#### 4.1 硬件接线

- 引脚：`WATER_SENSOR_PIN = 45`（config.h 第33行）
- 引脚模式：`INPUT_PULLUP`（内部上拉）
- 电平含义：`HIGH = 正常(无水)`，`LOW = 有水(水浸触发)`（config.h 第33行注释）

#### 4.2 "水浸有效" (`rainValid`) 标志的含义和产生逻辑

这是整个水浸检测中最重要的设计决策，目的是**防止传感器未连接时产生误报**。

初始化时 `_rainValid = false` (第42行)，即**默认无效**。

变为 `true` 的过程 (第89-115行)：

1. **电平变化检测**：每次 `read()` 采样 GPIO45，与上一次 `_lastRainRaw` 比较。
2. **防抖**：如果电平发生变化，记录变化时间。只有当变化后的电平稳定 >= `WATER_DEBOUNCE_MS` (50ms) 才确认状态改变。
3. **连续3次同值确认**：当状态确实变化后，还需要连续3次读到相同的新值（通过 `stableCnt` 计数器），才会将 `_rainValid` 置为 `true`。

简单来说：`_rainValid = true` 表示"传感器确实存在且已正常工作，检测到了至少3次一致的稳定电平变化"。

**这个设计的意图**（代码注释第38行）："水浸传感器 -- 不接不误报"。如果传感器根本没接，引脚悬空，电平可能随机跳变但不会连续3次稳定读到同一值，因此 `_rainValid` 始终为 `false`，对应的 `waterLeak` 判定条件 `!getRainDO() && isRainValid()` 永远为 `false`，不会触发误报警。

#### 4.3 状态变量总结

| 变量 | 含义 |
|------|------|
| `_rainDO` | `true` = 正常(无水)，`false` = 检测到水浸 |
| `_rainValid` | `true` = 传感器已确认存在且有效，`false` = 传感器未检测到/未接 |
| `_lastRainRaw` | 最新去抖后的原始 GPIO 电平 |
| `_rainChangeTime` | 最后检测到电平变化的时间（防抖用） |

---

### 五、报警判断的完整条件总结

当本地报警触发时，`alarm_manager.cpp` 的 `triggerLocal()` (第59行) 执行以下动作：

1. **去重**：已存在的相同类型报警不重复触发。
2. **记录**：将 AlarmType 加入 `_activeAlarms[]` 数组。
3. **开启蜂鸣器**：调用 `_setBuzzer(true)`，启动**三连哔循环模式**（哔哔哔...停...哔哔哔...停...），每个周期3声（每声150ms响+100ms停），周期间隔600ms。本地报警的 `_buzzerOffTime = 0` 表示**无限循环，直到手动解除或报警条件消失**。
4. **开启红色LED**：调用 `_setLED(true)`，根据报警类型调整闪烁频率。跌倒报警快闪200ms间隔，其他500ms间隔。

当所有活动报警被清除后（`_activeCount == 0`），蜂鸣器和LED同时关闭。

解除报警的方式：
- **自动解除**：报警条件消失（如传感器读数恢复正常、人离开房间等），对应的 `checkLocalAlarms()` 自动调用 `clearLocal()`。
- **手动解除**：按下 GPIO13 按钮（BTN_ALARM_DISMISS_PIN），触发 `clearAllLocal()` 清除全部报警（main.cpp 第379-381行）。
- **服务端恢复指令**：收到 MQTT `alarm/restore` 消息。

---

### 六、OLED 显示报警

在 `display_manager.cpp` 的 Page 3（安全信息页，第213-230行）：

```
水浸:  正常          (rainValid=false 时显示 "--")
水浸:  !!报警        (rainValid=true 且 rainDO=false 时显示)
蜂鸣器: 响 / 关
```

---

### 七、MQTT 上报

在 `mqtt_manager.cpp` 第250-258行，水浸和蜂鸣器状态作为遥测数据的一部分上报到服务端：

```cpp
// 水浸: 0=有水 1=正常 2=无效(未接/故障)
v["RainDO"]      = d.rainDO ? "1" : "0";       // 1=正常 0=有水
v["RainValid"]   = d.rainValid ? "1" : "0";
v["BuzzerState"] = d.buzzerState ? "1" : "0";
```