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

T] 📡 ���布统计: 总108次, 成功107, 失败1
[雷达] 未识别控制字=0x01 �       数据: 0F
─── 遥测数据 ───
  环境: T=29.4°C H=38.0% (温湿度:1)
  存在:1 运动:1 跌倒:0 驻留:1 (雷达:1)
  心率:85 呼吸:25 (体征:1)
  水浸:1 (水浸有效:0) | WiFi:1 蜂鸣器:1
  运行:165s 序号:113
─────────────────
─── 遥测数据 ───
  环境: T=29.4°C H=37.0% (温湿度:1)
  存在:1 运动:1 跌倒:0 驻留:1 (雷达:1)
  心率:85 呼吸:25 (体征:1)
  水浸:1 (水浸有效:0) | WiFi:1 蜂鸣器:1
  运行:170s 序号:118
─────────────────
─── 遥测数据 ───
  环境: T=29.6°C H=37.0% (温湿度:1)
  存在:1 运动:1 跌倒:0 驻留:1 (雷达:1)
  心率:85 呼吸:25 (体征:1)
  水浸:1 (水浸有效:0) | WiFi:1 蜂鸣器:1
  运行:175s 序号:123
─────────────────
─── 遥测数据 ───
  环境: T=29.2°C H=37.0% (温湿度:1)
  存在:1 运动:1 跌倒:0 驻留:1 (雷达:1)
  心率:85 呼吸:25 (体征:1)
  水浸:1 (水浸有效:0) | WiFi:1 蜂鸣器:1
  运行:177s 序号:125
─────────────────
[MQTT] 📡 发布统计: 总124次, 成功122, 失败2
─── 遥测数据 ───
  环境: T=29.6°C H=37.0% (温湿度:1)
  存在:1 运动:1 跌倒:0 驻留:1 (雷达:1)
  心率:85 呼吸:25 (体征:1)
  水浸:1 (水浸有效:0) | WiFi:1 蜂鸣器:1
  运行:187s 序号:126
─────────────────
[MQTT] 📊 连接正常 | WiFi RSSI=-50 | 服务器=202.110.234.102:8000
─── 遥测数据 ───
  环境: T=29.0°C H=38.0% (温湿度:1)
  存在:1 运动:1 跌倒:0 驻留:1 (雷达:1)
  心率:85 呼吸:25 (体征:1)
  水浸:1 (水浸有效:0) | WiFi:1 蜂鸣器:1
  运行:197s 序号:132
─────────────────
─── 遥测数据 ───
  环境: T=29.3°C H=38.0% (温湿度:1)
  存在:1 运动:1 跌倒:0 驻留:1 (雷达:1)
  心率:85 呼吸:25 (体征:1)
  水浸:1 (水浸有效:0) | WiFi:1 蜂鸣器:1
  运行:200s 序号:135
─────────────────
─── 遥测数据 ───
  环境: T=29.3°C H=38.0% (温湿度:1)
  存在:1 运动:1 跌倒:0 驻留:1 (雷达:1)
  心率:85 呼吸:25 (体征:1)
  水浸:1 (水浸有效:0) | WiFi:1 蜂鸣器:1
  运行:208s 序号:141
─────────────────
─── 遥测数据 ───
  环境: T=29.1°C H=37.0% (温湿度:1)
  存在:1 运动:1 跌倒:0 驻留:1 (雷达:1)
  心率:85 呼吸:25 (体征:1)
  水浸:1 (水浸有效:0) | WiFi:1 蜂鸣器:1
  运行:213s 序号:146
─────────────────
看看是为啥报警


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

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

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

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

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

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

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


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

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


The user is showing me serial output from their Health v2.0 device (an ESP32-based health monitoring system with radar, temperature/humidity, heart rate, water immersion sensors, etc.). They're asking "看看是为啥报警" which means "look at why it's alarming."

Looking at the data, I can see:
- 水浸:1 (water immersion: 1) - this indicates water detected
- 水浸有效:0 (water immersion valid: 0) - but it says not valid

Wait, let me re-read. The user is asking why the alarm is going off. Let me look at the data more carefully:

The telemetry data shows:
- 水浸:1 (water immersion detected = 1, meaning water IS detected)
- 水浸有效:0 (water immersion valid = 0, meaning the reading is not considered valid)

So the water immersion sensor is detecting water (value=1) but the "valid" flag is 0. This seems contradictory - if it's not valid, why is it alarming?

Let me look at the code to understand the alarm logic and the water immersion sensor handling.


让我查看代码来了解报警逻辑和水浸传感器的处理方式。


Agent

查找报警和水浸逻辑

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

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


Async agent launched successfully. (This tool result is internal metadata — never quote or paste any part of it, including the agentId below, into a user-facing reply.)
agentId: a35e2a3a1b61c577f (internal ID - do not mention to user. Use SendMessage with to: 'a35e2a3a1b61c577f', summary: '<5-10 word recap>' to continue this agent.)
The agent is working in the background. You will be notified automatically when it completes. You know nothing about its results until that notification arrives — do not report, assume, or predict them; continue other work or respond to the user in the meantime.
Do not duplicate this agent's work — avoid working with the same files or topics it is using.
output_file: C:\default\AppData\Local\Temp\claude\d--PlatformIO1-Projects-Healthv2-0\d07c3716-a856-4ff7-b6ef-cd0a3519488e\tasks\a35e2a3a1b61c577f.output
Do NOT Read or tail this file via the shell tool — it is the full subagent JSONL transcript and reading it will overflow your context. If the user asks for progress, say the agent is still running; you'll get a completion notification.

查找报警和水浸逻辑

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

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


The agent is running in the background. Let me also look at the relevant files directly while waiting.


同时让我直接查看关键的源文件来快速定位问题。


src\alarm_manager.cpp:2: * @file alarm_manager.cpp
src\alarm_manager.cpp:3: * @brief 报警管理器 - 双模式实现
src\alarm_manager.cpp:5: * 本地: 跌倒/水浸/心率异常/呼吸异常 → 立即蜂鸣器+LED
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: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: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:31:    ledcSetup(BUZZER_PWM_CHANNEL, BUZZER_PWM_FREQ, BUZZER_PWM_RESOLUTION);
src\alarm_manager.cpp:32:    ledcAttachPin(BUZZER_PIN, BUZZER_PWM_CHANNEL);
src\alarm_manager.cpp:33:    ledcWrite(BUZZER_PWM_CHANNEL, 0);  // 关
src\alarm_manager.cpp:35:    pinMode(LED_ALARM_PIN, OUTPUT);
src\alarm_manager.cpp:36:    digitalWrite(LED_ALARM_PIN, LOW);
src\alarm_manager.cpp:38:    _buzzerToggleTime = 0;
src\alarm_manager.cpp:39:    _buzzerBeepState = false;
src\alarm_manager.cpp:42:    Serial.println(F("[告警] ✅ 双模式报警 (本地+服务端)"));
src\alarm_manager.cpp:50:void AlarmManager::loop() {
src\alarm_manager.cpp:51:    _updateBuzzer();
src\alarm_manager.cpp:56:// 本地报警触发 (离线可用)
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:78:    Serial.printf("[告警] 🚨 本地报警触发: %s (活动报警:%d)\n", name, _activeCount);
src\alarm_manager.cpp:80:    // 立即开启蜂鸣器和 LED
src\alarm_manager.cpp:81:    _setBuzzer(true);
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:97:    Serial.printf("[告警] 本地报警清除, 剩余活动:%d\n", _activeCount);
src\alarm_manager.cpp:99:    // 没有活动报警时关闭
src\alarm_manager.cpp:101:        _setBuzzer(false);
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:109:    _setBuzzer(false);
src\alarm_manager.cpp:111:    Serial.println(F("[告警] 清除所有本地报警"));
src\alarm_manager.cpp:118:void AlarmManager::executeAlarm(const AlarmCommand& cmd) {
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: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: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: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: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: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: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:200:        ledcWrite(BUZZER_PWM_CHANNEL, 0);
src\alarm_manager.cpp:201:        _buzzerOffTime = 0;
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:212:    if (_buzzerOn && _buzzerOffTime > 0 && millis() >= _buzzerOffTime) {
src\alarm_manager.cpp:213:        _buzzerOn = false;
src\alarm_manager.cpp:214:        ledcWrite(BUZZER_PWM_CHANNEL, 0);
src\alarm_manager.cpp:215:        Serial.println(F("[告警] ⏰ 蜂鸣器定时关闭"));
src\alarm_manager.cpp:218:    // 本地报警: 三连哔 停 — 循环 (哔哔哔...哔哔哔...)
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:229:                ledcWrite(BUZZER_PWM_CHANNEL, 128);
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:237:                ledcWrite(BUZZER_PWM_CHANNEL, 0);
src\alarm_manager.cpp:242:    else if (_buzzerOn && _buzzerOffTime > 0) {
src\alarm_manager.cpp:243:        ledcWrite(BUZZER_PWM_CHANNEL, 128);
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\alarm_manager.h:2: * @file alarm_manager.h
include\alarm_manager.h:3: * @brief 报警管理器 - 双模式 (本地判定 + 服务端指令)
include\alarm_manager.h:5: * 本地模式: 离线也能用, 跌倒/水浸/心率异常/呼吸异常 本地判定触发
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:34:    // 本地报警接口 (离线可用)
include\alarm_manager.h:37:    /** 触发本地报警 */
include\alarm_manager.h:38:    void triggerLocal(AlarmType type);
include\alarm_manager.h:39:    /** 清除本地报警 */
include\alarm_manager.h:40:    void clearLocal(AlarmType type);
include\alarm_manager.h:41:    /** 清除所有本地报警 */
include\alarm_manager.h:49:    void executeAlarm(const AlarmCommand& cmd);
include\alarm_manager.h:51:    void executeRestore(const AlarmCommand& cmd);
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:60:    bool hasActiveAlarm() const  { return _activeCount > 0; }
include\alarm_manager.h:61:    const char* getActiveAlarmType() const;
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\alarm_manager.h:77:    // 活动报警列表 (最多 4 个同时)
include\alarm_manager.h:78:    AlarmType _activeAlarms[4];
include\alarm_manager.h:85:#endif // ALARM_MANAGER_H
include\config.h:32:// --- 水浸传感器 ---
include\config.h:33:#define WATER_SENSOR_PIN      45            // 数字输入, 低电平=有水
include\config.h:35:// --- 蜂鸣器 (PWM) ---
include\config.h:36:#define BUZZER_PIN            21
include\config.h:37:#define BUZZER_PWM_CHANNEL    0
include\config.h:38:#define BUZZER_PWM_FREQ       2000
include\config.h:39:#define BUZZER_PWM_RESOLUTION 8
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:123:// 7. 报警阈值 (协议规范 §6, 服务端判定用)
include\config.h:134:#define WATER_DEBOUNCE_MS     50
include\config.h:193:    // --- 水浸 ---
include\config.h:194:    bool     rainDO;              // 1=正常 0=水浸触发
include\config.h:195:    bool     rainValid;           // 水浸传感器有效性
include\config.h:200:    bool     buzzerState;         // 蜂鸣器状态 1=开启
include\config.h:206:// 结构体: 下行报警指令 (协议规范 §5.1, §5.2)
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];       // 告警描述
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
include\button_manager.h:6: * 按钮2 (GPIO15): 短按 → 解除报警
include\button_manager.h:30:    void onAlarmDismiss(ButtonCallback cb)          { _cbDismiss  = cb; }
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:32:    Serial.printf("  配网: GPIO%d | 解除报警: GPIO%d | 翻页: GPIO%d\n",
src\button_manager.cpp:33:                  BTN_PROVISION_PIN, BTN_ALARM_DISMISS_PIN, BTN_PAGE_SWITCH_PIN);
src\button_manager.cpp:44:    // 按钮2: 解除报警 (仅短按)
src\button_manager.cpp:76:            if (btn.pin == BTN_ALARM_DISMISS_PIN && _cbDismiss) _cbDismiss();
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:218:    _u8g2.drawUTF8(4, 30, "水浸:");
src\display_manager.cpp:220:    else _u8g2.drawUTF8(60, 30, data.rainDO ? "正常" : "!!报警");
src\display_manager.cpp:222:    _u8g2.drawUTF8(4, 44, "蜂鸣器:");
src\display_manager.cpp:223:    _u8g2.drawUTF8(60, 44, buzzerOn ? "响" : "关");
src\main.cpp:5: * ESP32-S3 + R60ABD1 60G 毫米波雷达 + DHT11 + 水浸
src\main.cpp:6: * 本地紧急报警 (离线可用) + 服务端告警联动
src\main.cpp:17:#include "alarm_manager.h"
src\main.cpp:29:AlarmManager   g_alarm;
src\main.cpp:38:// 本地报警跟踪 (防重复触发)
src\main.cpp:40:bool g_localWaterActive      = false;
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:109:    if (strcmp(cmd.controlType, "buzzer_test") == 0) {
src\main.cpp:111:        g_alarm.buzzerTest(dur);
src\main.cpp:219:    // 水浸
src\main.cpp:226:    t.buzzerState = g_alarm.isBuzzerOn();
src\main.cpp:233:// 本地报警检查 (离线可用)
src\main.cpp:236:void checkLocalAlarms(const RadarRawData& radar) {
src\main.cpp:242:        if (now - g_fallStartTime >= 1000) {  // 跌倒持续1秒才报警
src\main.cpp:244:            g_alarm.triggerLocal(AlarmManager::ALARM_FALL);
src\main.cpp:249:        g_alarm.clearLocal(AlarmManager::ALARM_FALL);
src\main.cpp:253:    // --- 水浸检测 ---
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:257:        g_alarm.triggerLocal(AlarmManager::ALARM_WATER_LEAK);
src\main.cpp:258:    } else if (!waterLeak && g_localWaterActive) {
src\main.cpp:259:        g_localWaterActive = false;
src\main.cpp:260:        g_alarm.clearLocal(AlarmManager::ALARM_WATER_LEAK);
src\main.cpp:263:    // --- 有心率呼吸报警延迟: 检测到人后等 30 秒数据稳定 ---
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:269:            Serial.println(F("[报警] 心率呼吸监测就绪 (30秒稳定期结束)"));
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:287:    // 无人时清除心率异常报警
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:304:    // 无人时清除呼吸异常报警
src\main.cpp:307:        g_alarm.clearLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
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:359:    Serial.println(F("║  ESP32-S3 + R60ABD1 + DHT11 + 水浸     ║"));
src\main.cpp:365:    // 1. 报警模块 (最先, 引脚安全)
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);

[Showing results with pagination = limit: 250]


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          1             // UART 发送 (从18挪到1)
23	#define RADAR_BAUD_RATE       115200
24
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	// ============================================================
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;
242	    bool     mqtt_useTls;
243	    uint32_t timestamp;
244	};
245
246	#endif // CONFIG_H
247

/**
 * @file config.h
 * @brief 全局配置文件 - 五劳无感康养监测系统
 *
 * 协议版本: V0.08 (2026-07-17)
 * 包含: 硬件引脚定义、WiFi/MQTT 默认参数、
 *        雷达协议常量、告警阈值、任务配置等
 */

#ifndef CONFIG_H
#define CONFIG_H

#include <Arduino.h>

// ============================================================
// 1. 硬件引脚定义 (ESP32-S3)
// ============================================================

// --- R60ABD1 毫米波雷达 ---
#define RADAR_UART_PORT       Serial2
#define RADAR_RX_PIN          17
#define RADAR_TX_PIN          1             // UART 发送 (从18挪到1)
#define RADAR_BAUD_RATE       115200

// --- 雷达备用 GPIO ---
#define RADAR_GPIO1_PIN       2             // 活跃/静止指示 (从10挪到2)
#define RADAR_GPIO2_PIN       11            // 有人/无人指示

// --- DHT11 温湿度传感器 ---
#define DHT11_PIN             4             // 温湿度传感器

// --- 水浸传感器 ---
#define WATER_SENSOR_PIN      45            // 数字输入, 低电平=有水

// --- 蜂鸣器 (PWM) ---
#define BUZZER_PIN            21
#define BUZZER_PWM_CHANNEL    0
#define BUZZER_PWM_FREQ       2000
#define BUZZER_PWM_RESOLUTION 8

// --- 配网按钮 ---
#define CONFIG_BUTTON_PIN     0             // 长按 3 秒复位 WiFi
#define BUTTON_LONG_PRESS_MS  3000

// --- 状态指示灯 ---
#define LED_WIFI_PIN          8             // 蓝色 - WiFi 状态
#define LED_ALARM_PIN         9             // 红色 - 报警指示

// --- OLED 显示屏 (SSD1306 128x64 I2C) ---
#define OLED_SDA_PIN          15            // I2C 数据线
#define OLED_SCL_PIN          16            // I2C 时钟线

// --- 交互按钮 ---
#define BTN_PROVISION_PIN     14            // 擦除WiFi配网按钮 (长按3秒)
#define BTN_ALARM_DISMISS_PIN 13            // 解除报警按钮
#define BTN_PAGE_SWITCH_PIN   46            // 翻页按钮
#define BTN_DEBOUNCE_MS       50            // 去抖时间
#define BTN_LONG_PRESS_MS     1500          // 长按判定 (1.5秒)

// ============================================================
// 2. 设备标识 (协议规范 §2)
// ============================================================

#define SALEID_DEFAULT        "10100001"    // 站点/系统编号
#define GATEID_DEFAULT        "10100001001" // 网关唯一编号
#define METER_ID_DEFAULT      "ENV001"      // 感知节点ID (卧室=ENV001, 卫生间=ENV002, 客厅=ENV003)
#define METER_NAME_DEFAULT    "RadarDHT11"  // 设备型号名称
#define FW_VERSION            "V1.0.0"

// ============================================================
// 3. WiFi 默认配置 (配网使用)
// ============================================================

#define WIFI_AP_SSID          "HealthMonitor-Setup"
#define WIFI_AP_PASSWORD      "12345678"
#define WIFI_CONFIG_TIMEOUT   60
#define WIFI_CONNECT_TIMEOUT  15
#define WIFI_RETRY_INTERVAL   10000

// ============================================================
// 4. MQTT 默认配置 (协议规范 §2)
// ============================================================

#define MQTT_DEFAULT_SERVER   "202.110.234.102"
#define MQTT_DEFAULT_PORT     8000
#define MQTT_DEFAULT_PORT_STR "8000"         // WiFiManager 参数用字符串
#define MQTT_DEFAULT_USER     "test"
#define MQTT_DEFAULT_PASS     "testtest"
#define MQTT_DEFAULT_DEVICE   "10100001001"  // 默认网关ID = gateid
#define MQTT_DEFAULT_ENV      "主卧"           // 默认环境备注
#undef MQTT_KEEPALIVE
#define MQTT_KEEPALIVE        30              // 30秒 (协议规范)
#define MQTT_RECONNECT_MIN    1000
#define MQTT_RECONNECT_MAX    60000

// ============================================================
// 5. MQTT 主题定义 (协议规范 §3)
// ============================================================

// --- 上行主题 (设备 → 服务端) ---
#define MQTT_TOPIC_REPORT_ALLPOINTS   "report/allpoints"                // 全量遥测上报 QoS0
#define MQTT_TOPIC_STATUS_ONLINE      "status/%s/online"                // 上线通知 QoS1
#define MQTT_TOPIC_STATUS_OFFLINE     "status/%s/offline"               // 离线遗嘱 QoS1
#define MQTT_TOPIC_STATUS_PROVISION   "status/%s/provision"             // 配网状态 QoS1
#define MQTT_TOPIC_RESPONSE_CMD       "response/%s/cmd"                 // 指令响应 QoS1
#define MQTT_TOPIC_RESPONSE_PROVISION "response/%s/provision"           // 配网响应 QoS1

// --- 下行主题 (服务端 → 设备) ---
#define MQTT_TOPIC_CMD_ALARM          "cmd/%s/alarm"                    // 告警指令 QoS1
#define MQTT_TOPIC_CMD_ALARM_RESTORE  "cmd/%s/alarm/restore"            // 告警恢复 QoS1
#define MQTT_TOPIC_CMD_CONTROL        "cmd/%s/control"                  // 控制指令 QoS1
#define MQTT_TOPIC_CMD_PROVISION_WIFI "cmd/%s/provision/wifi"           // WiFi配网 QoS1

// ============================================================
// 6. 上报配置 (协议规范 §4.1, §7.2)
// ============================================================

#define REPORT_INTERVAL_MS    1000                // 遥测上报周期 1秒
#define REPORT_QOS            0                   // 遥测 QoS 0
#define STATUS_QOS            1                   // 状态/响应 QoS 1

// ============================================================
// 7. 报警阈值 (协议规范 §6, 服务端判定用)
// ============================================================

#define HEART_RATE_MIN        50
#define HEART_RATE_MAX        120
#define BREATH_RATE_MIN       8
#define BREATH_RATE_MAX       24
#define TEMP_LOW              5.0f
#define TEMP_HIGH             45.0f
#define HUMI_LOW              15.0f
#define HUMI_HIGH             90.0f
#define WATER_DEBOUNCE_MS     50

// ============================================================
// 8. 任务配置
// ============================================================

#define RADAR_TASK_STACK      4096
#define RADAR_TASK_PRIORITY   5
#define RADAR_TASK_CORE       0

#define MQTT_TASK_STACK       8192
#define MQTT_TASK_PRIORITY    4
#define MQTT_TASK_CORE        1

// ============================================================
// 9. NTP 时间同步
// ============================================================

#define NTP_SERVER            "ntp.aliyun.com"
#define NTP_GMT_OFFSET_SEC    28800             // UTC+8 (北京时间)
#define NTP_DAYLIGHT_OFFSET   0

// ============================================================
// 10. LittleFS 配置文件名
// ============================================================

#define CONFIG_FILE_MQTT      "/mqtt_config.json"
#define CONFIG_FILE_WIFI      "/wifi_config.json"
#define CONFIG_FILE_DEVICE    "/device_config.json"

// ============================================================
// 11. 结构体: 全量遥测数据 (协议规范 §4.1)
// ============================================================

struct TelemetryData {
    // --- 设备标识 ---
    char     saleid[12];          // 站点编号 "10100001"
    char     gateid[16];          // 网关编号 "10100001001"
    char     meterId[8];          // 节点ID "ENV001"
    char     meterName[16];       // 设备型号 "RadarDHT11"

    // --- 环境数据 (DHT11) ---
    float    temp;                // 温度 ℃
    float    humi;                // 湿度 %RH
    bool     dhtValid;            // 温湿度有效性

    // --- 雷达: 存在/运动 ---
    bool     presence;            // 有人=1 无人=0
    bool     motion;              // 有运动=1 无运动=0
    bool     fall;                // 跌倒检测 1=触发
    bool     resident;            // 驻留状态 1=驻留

    // --- 雷达: 生命体征 ---
    uint8_t  heartRate;           // 心率 bpm (无效=0)
    uint8_t  breathRate;          // 呼吸 次/分 (无效=0)
    uint8_t  body;                // 人体能量/体征值
    bool     radarValid;          // 雷达数据有效性
    bool     vitalValid;          // 心率呼吸有效性

    // --- 水浸 ---
    bool     rainDO;              // 1=正常 0=水浸触发
    bool     rainValid;           // 水浸传感器有效性

    // --- 设备状态 ---
    bool     wifiReady;           // WiFi/MQTT 连接状态
    uint32_t uptimeSec;           // 设备累计运行秒数
    bool     buzzerState;         // 蜂鸣器状态 1=开启

    // --- 时间戳 ---
    uint32_t lastUpdate;          // 最后更新 ms
};

// 结构体: 下行报警指令 (协议规范 §5.1, §5.2)
struct AlarmCommand {
    char     cmdId[32];           // 指令唯一ID
    char     type[20];            // alarm / alarm_restore
    char     alarmType[24];       // fall/water_leak/heart_abnormal/breath_abnormal
    char     alarmLevel[8];       // low/medium/high
    char     alarmDesc[64];       // 告警描述
    char     meterId[8];          // 触发节点ID
    char     relatedCmdId[32];    // 关联原始告警ID (恢复用)

    // 联动动作
    bool     buzzerOn;            // 蜂鸣器开关
    uint16_t buzzerDuration;      // 蜂鸣持续秒数 (0=持续到恢复)
    bool     nightLight;          // 夜灯

    uint32_t timestamp;
};

// 结构体: 下行控制指令 (协议规范 §5.3)
struct ControlCommand {
    char     cmdId[32];
    char     type[20];            // control
    char     controlType[24];     // buzzer_test/device_restart/report_interval_set/factory_reset
    uint32_t params_duration;     // 参数: 持续秒数
    uint32_t params_interval;     // 参数: 上报间隔
    uint32_t timestamp;
};

// 结构体: 下行配网指令 (协议规范 §5.4)
struct ProvisionCommand {
    char     cmdId[32];
    char     wifi_ssid[33];
    char     wifi_password[65];
    char     wifi_encrypt[8];     // OPEN/WPA2/WPA3
    char     mqtt_broker[64];
    uint16_t mqtt_port;
    bool     mqtt_useTls;
    uint32_t timestamp;
};

#endif // CONFIG_H


T] 📡 ���布统计: 总108次, 成功107, 失败1 [雷达] 未识别控制字=0x01 �       数据: 0F  ─── 遥测数据 ───   环境: T=29.4°C H=38.0% (温湿度:1)   存在:1 运动:1 跌倒:0 驻留:1 (雷达:1)   心率:85 呼吸:25 (体征:1)   水浸:1 (水浸有效:0) | WiFi:1 蜂鸣器:1   运行:165s…


Now I can see the key issue. Let me look at the critical code in main.cpp:

```cpp
// 水浸检测 (line 254)
bool waterLeak = !g_sensor.getRainDO() && g_sensor.isRainValid();
```

And the telemetry output shows:
- 水浸:1 — this means `rainDO = 1`, which in config.h line 194 says "1=正常 0=水浸触发"
- 水浸有效:0 — `rainValid = 0`

Wait, but 水浸:1 and 水浸有效:0 means:
- `rainDO = 1` → 正常 (no water leak)
- `rainValid = 0` → sensor not valid

So the water leak detection condition is:
```cpp
bool waterLeak = !g_sensor.getRainDO() && g_sensor.isRainValid();
```
= `!1 && 0` = `false && false` = `false`

So water leak is NOT being triggered locally. But the buzzer is showing as 1 (蜂鸣器:1) in the serial output.

Wait, let me re-read more carefully. The serial output shows:
```
水浸:1 (水浸有效:0) | WiFi:1 蜂鸣器:1
```

So `rainDO = 1` (normal, no water), `rainValid = 0` (not valid), buzzer is ON.

If water leak alarm is not the cause, then what is? Let me look at what other alarms could be triggered.

Looking at the telemetry data:
- 心率:85 (heart rate 85) — within range 50-120, should NOT trigger alarm
- 呼吸:25 (breath rate 25) — this is ABOVE the max of 24! (BREATH_RATE_MAX is 24)

Let me check the breath alarm logic:
```cpp
if (g_vitalAlarmReady && radar.vitalValid && radar.breathRate > 0) {
    // line 294-301
    ...
    g_alarm.triggerLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
    ...
}
```

Wait, I need to see the actual threshold check. Let me look at main.cpp more carefully around lines 277-307.

Also, looking at this more carefully - the 呼吸:25 is exactly at or above the BREATH_RATE_MAX of 24. This is likely what's triggering the alarm.

Let me read the relevant section of main.cpp to see the exact alarm threshold checks.


找到关键线索了！让我查看主文件中报警判断的具体代码。


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

}

// ============================================================
// 本地报警检查 (离线可用)
// ============================================================

void checkLocalAlarms(const RadarRawData& radar) {
    uint32_t now = millis();

    // --- 跌倒检测 (持续1秒才触发，防坐下误判) ---
    if (radar.fall && radar.radarValid && !g_localFallActive) {
        if (g_fallStartTime == 0) g_fallStartTime = now;
        if (now - g_fallStartTime >= 1000) {  // 跌倒持续1秒才报警
            g_localFallActive = true;
            g_alarm.triggerLocal(AlarmManager::ALARM_FALL);
        }
    } else if (!radar.fall && g_localFallActive) {
        g_localFallActive = false;
        g_fallStartTime = 0;
        g_alarm.clearLocal(AlarmManager::ALARM_FALL);
    }
    if (!radar.fall) g_fallStartTime = 0;  // 没有跌倒时重置计时

    // --- 水浸检测 ---
    bool waterLeak = !g_sensor.getRainDO() && g_sensor.isRainValid();
    if (waterLeak && !g_localWaterActive) {
        g_localWaterActive = true;
        g_alarm.triggerLocal(AlarmManager::ALARM_WATER_LEAK);
    } else if (!waterLeak && g_localWaterActive) {
        g_localWaterActive = false;
        g_alarm.clearLocal(AlarmManager::ALARM_WATER_LEAK);
    }

    // --- 有心率呼吸报警延迟: 检测到人后等 30 秒数据稳定 ---
    #define VITAL_ALARM_DELAY_MS  30000
    if (radar.presence && !g_vitalAlarmReady) {
        if (g_presenceStartTime == 0) g_presenceStartTime = now;
        if (now - g_presenceStartTime >= VITAL_ALARM_DELAY_MS) {
            g_vitalAlarmReady = true;
            Serial.println(F("[报警] 心率呼吸监测就绪 (30秒稳定期结束)"));
        }
    } else if (!radar.presence) {
        g_presenceStartTime = 0;
        g_vitalAlarmReady   = false;
    }

    // --- 心率异常 (仅有人+就绪时判定) ---
    if (g_vitalAlarmReady && radar.vitalValid && radar.heartRate > 0) {
        bool heartAbn = (radar.heartRate < HEART_RATE_MIN || radar.heartRate > HEART_RATE_MAX);
        if (heartAbn && !g_localHeartAbnActive) {
            g_localHeartAbnActive = true;
            g_alarm.triggerLocal(AlarmManager::ALARM_HEART_ABNORMAL);
        } else if (!heartAbn && g_localHeartAbnActive) {
            g_localHeartAbnActive = false;
            g_alarm.clearLocal(AlarmManager::ALARM_HEART_ABNORMAL);
        }
    }
    // 无人时清除心率异常报警
    else if (!radar.presence && g_localHeartAbnActive) {
        g_localHeartAbnActive = false;
        g_alarm.clearLocal(AlarmManager::ALARM_HEART_ABNORMAL);
    }

    // --- 呼吸异常 (仅有人+就绪时判定) ---
    if (g_vitalAlarmReady && radar.vitalValid && radar.breathRate > 0) {
        bool breathAbn = (radar.breathRate < BREATH_RATE_MIN || radar.breathRate > BREATH_RATE_MAX);
        if (breathAbn && !g_localBreathAbnActive) {
            g_localBreathAbnActive = true;
            g_alarm.triggerLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
        } else if (!breathAbn && g_localBreathAbnActive) {
            g_localBreathAbnActive = false;
            g_alarm.clearLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
        }
    }
    // 无人时清除呼吸异常报警
    else if (!radar.presence && g_localBreathAbnActive) {
        g_localBreathAbnActive = false;
        g_alarm.clearLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
    }
}

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

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


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

/**
 * @file sensor_manager.cpp
 * @brief 传感器管理器实现 - DHT11 + 水浸
 */

#include "sensor_manager.h"

SensorManager::SensorManager()
    : _dht(nullptr)
    , _temp(0)
    , _humi(0)
    , _dhtValid(false)
    , _dhtErrorCnt(0)
    , _rainDO(true)       // 1=正常
    , _rainValid(false)
    , _lastRainRaw(true)
    , _rainChangeTime(0)
    , _callback(nullptr)
    , _lastReadTime(0)
{
}

SensorManager::~SensorManager() {
    if (_dht) delete _dht;
}

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

bool SensorManager::begin() {
    // DHT11 — 允许失败，不影响水浸
    _dht = new DHT(DHT11_PIN, DHT_TYPE);
    _dht->begin();
    _dhtValid = false;
    _dhtErrorCnt = 0;

    // 水浸传感器 — 不接不误报
    pinMode(WATER_SENSOR_PIN, INPUT_PULLUP);
    _lastRainRaw = (digitalRead(WATER_SENSOR_PIN) == HIGH);
    _rainDO = true;
    _rainValid = false;       // 默认无效，接传感器后自动变 true
    _rainChangeTime = millis();

    // 首次读取 (DHT11 可能失败)
    read();

    Serial.println(F("[传感器] 初始化完成"));
    Serial.printf("  DHT11: %s\n",
                  _dhtValid ? "✅ 正常" : "⚠ 未检测到 (不影响系统运行)");
    Serial.printf("  水浸:  %s\n",
                  _rainValid ? "✅ 正常" : "⚠ 未检测到");
    return true;  // 始终成功，不阻塞系统启动
}

// ============================================================
// 读取传感器
// ============================================================

void SensorManager::read() {
    // --- DHT11 读取 (独立，失败不影响水浸) ---
    if (millis() - _lastReadTime >= 2000) {
        _lastReadTime = millis();

        float t = _dht->readTemperature();
        float h = _dht->readHumidity();

        if (!isnan(t) && !isnan(h)) {
            if (t > -20 && t < 80 && h >= 0 && h <= 100) {
                _temp = t;
                _humi = h;
                _dhtValid = true;
                _dhtErrorCnt = 0;  // 恢复正常，清零计数
            } else {
                _dhtValid = false;
                _dhtErrorCnt++;
            }
        } else {
            _dhtValid = false;
            _dhtErrorCnt++;
        }

        // 连续失败 10 次 (~20秒) 后只报一次，避免刷屏
        if (_dhtErrorCnt == 10) {
            Serial.println(F("[传感器] ⚠ DHT11 持续读取失败 (不影响其他传感器)"));
        }
    }

    // 水浸传感器读取 (防抖)
    bool raw = (digitalRead(WATER_SENSOR_PIN) == HIGH);  // HIGH=正常
    if (raw != _lastRainRaw) {
        _rainChangeTime = millis();
        _lastRainRaw = raw;
    }

    if (millis() - _rainChangeTime >= WATER_DEBOUNCE_MS) {
        bool newState = _lastRainRaw;
        if (newState != _rainDO) {
            _rainDO = newState;
            // 连续3次读到同值才确认传感器存在
            static uint8_t stableCnt = 0;
            static bool lastStable = false;
            if (newState == lastStable) stableCnt++;
            else { stableCnt = 1; lastStable = newState; }
            if (stableCnt >= 3) _rainValid = true;

            if (!_rainDO) {
                Serial.println(F("[传感器] ⚠ 检测到水浸!"));
            } else {
                Serial.println(F("[传感器] ✅ 水浸解除"));
            }

            if (_callback) _callback();
        }
    }
}

// ============================================================
// 回调
// ============================================================

void SensorManager::onChange(SensorCallback cb) {
    _callback = cb;
}


Now I have a clear picture of the issue. Let me analyze:

**Alarm thresholds in config.h:**
- `HEART_RATE_MIN` = 50, `HEART_RATE_MAX` = 120
- `BREATH_RATE_MIN` = 8, `BREATH_RATE_MAX` = 24

**Telemetry data from the serial output:**
- 心率:85 (heart rate 85) → within 50-120 → **NOT alarming**
- 呼吸:25 (breath rate 25) → `25 > 24 (BREATH_RATE_MAX)` → **THIS IS TRIGGERING THE ALARM!**

The breath rate is 25, which exceeds the `BREATH_RATE_MAX` of 24. This is what's causing the buzzer to go off.

But wait — the user is asking me to look at the serial data to figure out why it's alarming. Let me look again...

Looking at the telemetry data more carefully across all snapshots:
- 心率 is consistently 85 (within range)
- 呼吸 is consistently 25 (ABOVE the max of 24!)

So the alarm is being triggered by **呼吸异常 (breath rate abnormal)** — the breath rate of 25 exceeds the configured maximum of 24.

But there's also the issue with the water immersion sensor:
- 水浸:1 means `rainDO = 1` (normal, no leak)
- 水浸有效:0 means `rainValid = 0` (sensor not validated)

The water immersion sensor is not considered "valid" because the `_rainValid` flag never gets set to true. Looking at the sensor code:

```cpp
_rainValid = false;  // 默认无效

// Only set to true after 3 consecutive stable readings
if (stableCnt >= 3) _rainValid = true;
```

So the water sensor hasn't seen 3 stable readings of the same value yet, OR the sensor is not connected/detected properly.

But the main alarm trigger here is **呼吸:25 > BREATH_RATE_MAX:24**.

Now let me also check the `报警` section more carefully. The user says "看看是为啥报警" — they want to know why the alarm is going off. The buzzer is on (蜂鸣器:1), meaning some alarm is active.

The likely cause is **呼吸异常** since the breath rate is 25 which exceeds the max of 24.

Let me also verify: is there maybe another alarm source? Let me check:
- 跌倒:0 → no fall
- 水浸:1 (正常) and 水浸有效:0 → water leak alarm NOT triggered (because `!rainDO && rainValid` = `!1 && 0` = false)
- 心率:85 → within 50-120 range, not triggered
- 呼吸:25 → ABOVE max of 24 → **THIS IS THE CAUSE**

So the answer is clear: **呼吸频率 25 次/分钟超过了阈值上限 24 次/分钟，触发了呼吸异常报警。**

The BREATH_RATE_MAX is set to 24 in config.h, and the actual reading is 25. This is a very tight threshold — 25 is only slightly above 24 and could be normal for some people (normal adult respiratory rate is 12-20, but can vary).

I should tell the user:
1. The alarm is caused by breath rate (25) exceeding BREATH_RATE_MAX (24)
2. Suggest whether to adjust the threshold


## 分析结果：呼吸异常触发报警

根据串口输出和代码分析，报警原因是 **呼吸频率超标**：

### 关键数据

| 指标 | 实际值 | 阈值范围 | 状态 |
|------|--------|----------|------|
| 心率 | 85 | 50~120 | ✅ 正常 |
| **呼吸** | **25** | **8~24** | ❌ **超标触发报警** |
| 跌倒 | 0 | — | ✅ 无跌倒 |
| 水浸 | 1 (正常) | 有效=0 | ⚠ 传感器未验证，不触发 |

### 代码逻辑

在 [src/main.cpp:294-295](src/main.cpp#L294-L295)：

```cpp
bool breathAbn = (radar.breathRate < BREATH_RATE_MIN || radar.breathRate > BREATH_RATE_MAX);
// BREATH_RATE_MIN = 8, BREATH_RATE_MAX = 24  (定义在 config.h:128-129)
```

呼吸频率 **25 > 24** (`BREATH_RATE_MAX`)，触发了 `ALARM_BREATH_ABNORMAL`，蜂鸣器开始间歇鸣叫（三连哔模式）。

### 建议

呼吸 25 次/分仅比阈值 24 高出 1，成人正常静息呼吸频率为 12~20 次/分，但 25 也不算严重异常。如果你觉得这个阈值太敏感，可以把 [include/config.h:129](include/config.h#L129) 的 `BREATH_RATE_MAX` 调大一点，比如改成 **28** 或 **30**：

```cpp
#define BREATH_RATE_MAX       28   // 原来是 24
```

需要我帮你调整这个阈值吗？


<task-notification>
<task-id>a35e2a3a1b61c577f</task-id>
<tool-use-id>call_00_wPSOk3gDw0mP3Mj0s8Xb0494</tool-use-id>
<output-file>C:\default\AppData\Local\Temp\claude\d--PlatformIO1-Projects-Healthv2-0\d07c3716-a856-4ff7-b6ef-cd0a3519488e\tasks\a35e2a3a1b61c577f.output</output-file>
<status>completed</status>
<summary>Agent "查找报警和水浸逻辑" finished</summary>
<note>A task-notification fires each time this agent stops with no live background children of its own. The user can send it another message and resume it, so the same task-id may notify more than once.</note>
<result>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";
```</result>
<usage><subagent_tokens>34329</subagent_tokens><tool_uses>16</tool_uses><duration_ms>58550</duration_ms></usage>
</task-notification>


The background agent has completed its analysis. It confirms my earlier findings. Now I should summarize the findings for the user clearly.

The key finding is: **呼吸频率 25 > BREATH_RATE_MAX (24)** is triggering the alarm.

Let me also note the water sensor situation: 水浸:1 (正常), 水浸有效:0 (传感器未验证) — this means the water sensor is NOT causing the alarm.

The user's question "看看是为啥报警" has been answered. Let me provide a clear summary.


## 结论：呼吸频率超标触发报警

背景任务也完成了全面分析，结论一致。汇总如下：

### 报警根因

| 检测项 | 实际值 | 阈值 | 结果 |
|--------|--------|------|------|
| 跌倒 | 0 | — | ✅ |
| 水浸 | rainDO=1(正常), rainValid=0(无效) | 需有效+低电平 | ✅ 不触发 |
| 心率 | 85 | 50~120 | ✅ |
| **呼吸** | **25** | **8~24** | ❌ **超标！** |

代码在 [src/main.cpp:295](src/main.cpp#L295)：
```cpp
bool breathAbn = (radar.breathRate < BREATH_RATE_MIN || radar.breathRate > BREATH_RATE_MAX);
```

`25 > 24` → 触发 `ALARM_BREATH_ABNORMAL` → 蜂鸣器三连哔循环 → 所以串口显示 `蜂鸣器:1`。

### 为什么 25 算"异常"

[include/config.h:129](include/config.h#L129) 定义的 `BREATH_RATE_MAX` 是 **24**。成人静息呼吸频率正常范围是 12~20 次/分，24 已经是上限，25 刚好超出 1，但雷达本身有 ±2~3 的测量误差，所以 25 很可能只是正常波动。

### 解决建议

把 `BREATH_RATE_MAX` 从 24 放宽到 **28** 或 **30**，避免雷达测量波动导致的误报。需要我帮你改吗？


<ide_selection>The user selected the lines 6 to 6 from d:\PlatformIO1\Projects\Healthv2.0\include\mqtt_manager.h:
/provision

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

每次配网成功，的提示要和online在一起发送


The user wants to change the behavior so that when WiFi provisioning succeeds, the provision notification and online notification are sent together (or combined into one message). Let me look at the relevant code to understand how provisioning and online status are currently handled.

Let me read the MQTT manager and WiFi manager to understand the current flow.


让我先看看当前配网成功和 online 消息的发送逻辑。


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

/**
 * @file mqtt_manager.h
 * @brief MQTT 通信管理器 (协议规范 V0.08)
 *
 * 上行: report/allpoints, status/{gateid}/online, status/{gateid}/offline,
 *       status/{gateid}/provision, response/{gateid}/cmd, response/{gateid}/provision
 * 下行: cmd/{gateid}/alarm, cmd/{gateid}/alarm/restore,
 *       cmd/{gateid}/control, cmd/{gateid}/provision/wifi
 */

#ifndef MQTT_MANAGER_H
#define MQTT_MANAGER_H

#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include "config.h"

// ============================================================
// 回调函数类型
// ============================================================

// 告警指令回调 (alarm / alarm_restore)
typedef void (*MQTTAlarmCallback)(const AlarmCommand& cmd);

// 控制指令回调
typedef void (*MQTTControlCallback)(const ControlCommand& cmd);

// 配网指令回调 (WiFi 凭证下发)
typedef void (*MQTTProvisionCallback)(const ProvisionCommand& cmd);

// ============================================================
// MQTTManager 类
// ============================================================

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

    /** 初始化 */
    bool begin(const char* gateid);

    /** 主循环 */
    void loop();

    /** 连接/断开 */
    bool connect();
    void disconnect();
    bool isConnected();

    /** 设置服务器 */
    void setServer(const char* host, uint16_t port,
                   const char* user, const char* pass);

    // ============================================================
    // 上行: 发布方法 (协议规范 §4)
    // ============================================================

    /** 全量遥测上报 report/allpoints (§4.1) */
    void publishAllPoints(const TelemetryData& data, uint32_t sequence);

    /** 设备上线通知 status/{gateid}/online (§4.2) */
    void publishOnline();

    /** 设备离线通知 status/{gateid}/offline (§4.3) — 主动调用 */
    void publishOffline();

    /** 配网状态通知 status/{gateid}/provision (§4.4) */
    void publishProvisionStatus(const char* state, const char* bleName);

    /** 指令执行响应 response/{gateid}/cmd (§4.5) */
    void publishCmdResponse(const char* cmdId, const char* cmdType,
                            const char* alarmType, bool success,
                            const char* errorCode, const char* errorMsg);

    /** 配网指令响应 response/{gateid}/provision (§4.6) */
    void publishProvisionResponse(const char* cmdId, bool success,
                                  const char* errorCode, const char* errorMsg,
                                  const char* wifiSignal = "");

    // ============================================================
    // 回调注册 (协议规范 §5)
    // ============================================================

    void onAlarmCommand(MQTTAlarmCallback cb);
    void onControlCommand(MQTTControlCallback cb);
    void onProvisionCommand(MQTTProvisionCallback cb);

    /** 获取 gateid */
    const char* getGateId() const { return _gateid; }

private:
    static void _mqttCallback(char* topic, byte* payload, unsigned int length);
    static MQTTManager* _instance;

    // 主题构建
    String _buildTopic(const char* tmpl) const;

    // 下行报文解析
    void _handleMessage(const char* topic, const char* payload);
    void _parseAlarm(const char* payload, bool isRestore);
    void _parseControl(const char* payload);
    void _parseProvision(const char* payload);

    // 发布辅助
    bool _publish(const char* topicTmpl, const char* payload, uint8_t qos, bool retain = false);

    WiFiClient      _wifiClient;
    PubSubClient    _mqtt;
    char            _gateid[16];

    // 服务器配置
    char     _server[64];
    uint16_t _port;
    char     _user[32];
    char     _pass[32];

    // 重连
    uint32_t _reconnectInterval;
    uint32_t _lastReconnectAttempt;
    bool     _connected;

    // 回调
    MQTTAlarmCallback     _alarmCb;
    MQTTControlCallback   _controlCb;
    MQTTProvisionCallback _provisionCb;
};

#endif // MQTT_MANAGER_H