This commit is contained in:
2026-08-14 12:51:51 +08:00
parent 8facb8b719
commit 577eafd0ee
4 changed files with 68 additions and 31 deletions
+1 -1
View File
@@ -19,7 +19,7 @@
// --- 雷达型号选择 --- // --- 雷达型号选择 ---
#define RADAR_TYPE_ABD1 0 // R60ABD1: 心率/呼吸精准, 跌倒一般 #define RADAR_TYPE_ABD1 0 // R60ABD1: 心率/呼吸精准, 跌倒一般
#define RADAR_TYPE_AFD1 1 // R60AFD1: 跌倒/姿态精准, 无心率呼吸 #define RADAR_TYPE_AFD1 1 // R60AFD1: 跌倒/姿态精准, 无心率呼吸
#define RADAR_TYPE RADAR_TYPE_ABD1 // AFD1 (跌倒/姿态精准, 卫生间用) #define RADAR_TYPE RADAR_TYPE_AFD1 // AFD1 (跌倒/姿态精准, 卫生间用)
// --- 毫米波雷达 --- // --- 毫米波雷达 ---
#define RADAR_UART_PORT Serial2 #define RADAR_UART_PORT Serial2
+1 -1
View File
@@ -42,4 +42,4 @@ lib_deps =
; 上传配置 ; 上传配置
upload_speed = 921600 upload_speed = 921600
upload_port = COM20 upload_port = COM19
+5
View File
@@ -258,6 +258,11 @@ void AlarmManager::executeAlarm(const AlarmCommand& cmd) {
AlarmType localType = ALARM_SERVER; AlarmType localType = ALARM_SERVER;
if (strcmp(cmd.alarmType, "fall") == 0) localType = ALARM_FALL; if (strcmp(cmd.alarmType, "fall") == 0) localType = ALARM_FALL;
else if (strcmp(cmd.alarmType, "water_leak") == 0) localType = ALARM_WATER_LEAK; else if (strcmp(cmd.alarmType, "water_leak") == 0) localType = ALARM_WATER_LEAK;
else if (strcmp(cmd.alarmType, "heart_abnormal") == 0) localType = ALARM_HEART_ABNORMAL;
else if (strcmp(cmd.alarmType, "breath_abnormal") == 0) localType = ALARM_BREATH_ABNORMAL;
else if (strcmp(cmd.alarmType, "temp_abnormal") == 0) localType = ALARM_TEMP_ABNORMAL;
else if (strcmp(cmd.alarmType, "humi_abnormal") == 0) localType = ALARM_HUMI_ABNORMAL;
else if (strcmp(cmd.alarmType, "sensor_fault") == 0) localType = ALARM_SENSOR_FAULT;
if (cmd.buzzerOn) { if (cmd.buzzerOn) {
triggerLocal(localType, cmd.alarmDesc); triggerLocal(localType, cmd.alarmDesc);
+54 -22
View File
@@ -45,6 +45,13 @@ bool g_localHumiAbnActive = false;
bool g_localRadarFaultActive = false; bool g_localRadarFaultActive = false;
bool g_localDhtFaultActive = false; bool g_localDhtFaultActive = false;
// 手动解除抑制 — dismiss 后不重复报警, 条件恢复正常才重新武装
bool g_waterDismissed = false;
bool g_heartDismissed = false;
bool g_breathDismissed = false;
bool g_tempDismissed = false;
bool g_humiDismissed = false;
// 报警防抖计数器 (连续N次确认才触发) // 报警防抖计数器 (连续N次确认才触发)
uint8_t g_tempAbnCnt = 0; uint8_t g_tempAbnCnt = 0;
uint8_t g_humiAbnCnt = 0; uint8_t g_humiAbnCnt = 0;
@@ -113,6 +120,13 @@ void handleAlarmCommand(const AlarmCommand& cmd) {
g_fallDetectMs = 0; g_fallDetectMs = 0;
g_fallClearMs = 0; g_fallClearMs = 0;
g_fallDismissed = false; g_fallDismissed = false;
// 解除抑制标志也复位 (远程恢复后重新武装)
g_waterDismissed = false;
g_heartDismissed = false;
g_breathDismissed = false;
g_tempDismissed = false;
g_humiDismissed = false;
} else { } else {
// 告警触发 // 告警触发
g_alarm.executeAlarm(cmd); g_alarm.executeAlarm(cmd);
@@ -286,48 +300,47 @@ void checkLocalAlarms(const RadarRawData& radar) {
bool humanPresent = radar.presence && radar.radarValid; bool humanPresent = radar.presence && radar.radarValid;
// ─── ★★★★★ 人身安全: 跌倒 (仅浴室/走廊/客厅) ─── // ─── ★★★★★ 人身安全: 跌倒 (仅浴室/走廊/客厅) ───
if (enFall && radar.fall && humanPresent && !g_fallDismissed && millis() > 30000) { bool fallNow = enFall && radar.fall && humanPresent; // 当前是否处于跌倒状态
if (fallNow && !g_fallDismissed && millis() > 30000) {
// 跌倒中且未解除 → 累积时长, 满 FALL_DETECT_MS 触发
if (!g_localFallActive) { if (!g_localFallActive) {
// 持续检测中: 必须满 FALL_DETECT_MS 才报警
if (g_fallDetectMs == 0) { if (g_fallDetectMs == 0) {
g_fallDetectMs = millis(); g_fallDetectMs = millis();
} else if (millis() - g_fallDetectMs >= FALL_DETECT_MS) { } else if (millis() - g_fallDetectMs >= FALL_DETECT_MS) {
g_localFallActive = true; g_localFallActive = true;
g_fallDetectMs = 0; g_fallDetectMs = 0;
g_fallClearMs = 0;
g_alarm.triggerLocal(AlarmManager::ALARM_FALL, "雷达检测到跌倒"); g_alarm.triggerLocal(AlarmManager::ALARM_FALL, "雷达检测到跌倒");
} }
} else {
g_fallClearMs = 0; // 还在跌倒, 重置清除计时
} }
} else { } else {
g_fallDetectMs = 0; g_fallDetectMs = 0;
// 解除抑制需要 fall 持续消失 FALL_CLEAR_MS (防 AFD1 频繁跳变)
if (g_fallDismissed && g_fallClearMs == 0) {
g_fallClearMs = millis();
} }
if (!g_fallDismissed || millis() - g_fallClearMs >= FALL_CLEAR_MS) {
g_fallDismissed = false; // 跌倒信号真正消失后: 延迟清除报警 + 重新武装解除抑制 (防 AFD1 频繁跳变)
g_fallClearMs = 0; if (!fallNow) {
}
if (g_localFallActive) {
// 跌倒已消失 → 延迟清除
if (g_fallClearMs == 0) { if (g_fallClearMs == 0) {
g_fallClearMs = millis(); g_fallClearMs = millis();
} else if (millis() - g_fallClearMs >= FALL_CLEAR_MS) { }
if (millis() - g_fallClearMs >= FALL_CLEAR_MS) {
if (g_localFallActive) {
g_localFallActive = false; g_localFallActive = false;
g_fallClearMs = 0;
g_alarm.clearLocal(AlarmManager::ALARM_FALL); g_alarm.clearLocal(AlarmManager::ALARM_FALL);
} }
if (g_fallDismissed) {
g_fallDismissed = false; // 跌倒真正消失才重新武装
} }
} }
} else {
g_fallClearMs = 0; // 仍在跌倒, 重置消失计时
}
// ─── ★★★★★ 人身安全: 心率异常 (仅卧室+ABD1, 防抖3次, 30s) ─── // ─── ★★★★★ 人身安全: 心率异常 (仅卧室+ABD1, 防抖3次, 30s) ───
#if RADAR_TYPE != RADAR_TYPE_AFD1 #if RADAR_TYPE != RADAR_TYPE_AFD1
if (enVital && millis() > 30000 && humanPresent && radar.vitalValid && radar.heartRate > 0) { if (enVital && millis() > 30000 && humanPresent && radar.vitalValid && radar.heartRate > 0) {
bool heartAbn = (radar.heartRate < HEART_RATE_MIN || radar.heartRate > HEART_RATE_MAX); bool heartAbn = (radar.heartRate < HEART_RATE_MIN || radar.heartRate > HEART_RATE_MAX);
if (heartAbn) { if (heartAbn) {
if (++g_heartAbnCnt >= ALARM_HYSTERESIS && !g_localHeartAbnActive) { if (!g_heartDismissed && ++g_heartAbnCnt >= ALARM_HYSTERESIS && !g_localHeartAbnActive) {
g_localHeartAbnActive = true; g_localHeartAbnActive = true;
char buf[32]; char buf[32];
snprintf(buf, sizeof(buf), "心率异常: %d bpm", radar.heartRate); snprintf(buf, sizeof(buf), "心率异常: %d bpm", radar.heartRate);
@@ -335,6 +348,7 @@ void checkLocalAlarms(const RadarRawData& radar) {
} }
} else { } else {
g_heartAbnCnt = 0; g_heartAbnCnt = 0;
g_heartDismissed = false;
if (g_localHeartAbnActive) { if (g_localHeartAbnActive) {
g_localHeartAbnActive = false; g_localHeartAbnActive = false;
g_alarm.clearLocal(AlarmManager::ALARM_HEART_ABNORMAL); g_alarm.clearLocal(AlarmManager::ALARM_HEART_ABNORMAL);
@@ -343,6 +357,7 @@ void checkLocalAlarms(const RadarRawData& radar) {
} else if (!humanPresent && g_localHeartAbnActive) { } else if (!humanPresent && g_localHeartAbnActive) {
g_localHeartAbnActive = false; g_localHeartAbnActive = false;
g_heartAbnCnt = 0; g_heartAbnCnt = 0;
g_heartDismissed = false;
g_alarm.clearLocal(AlarmManager::ALARM_HEART_ABNORMAL); g_alarm.clearLocal(AlarmManager::ALARM_HEART_ABNORMAL);
} }
#endif // RADAR_TYPE != AFD1 #endif // RADAR_TYPE != AFD1
@@ -352,7 +367,7 @@ void checkLocalAlarms(const RadarRawData& radar) {
if (enVital && millis() > 30000 && humanPresent && radar.vitalValid && radar.breathRate > 0) { if (enVital && millis() > 30000 && humanPresent && radar.vitalValid && radar.breathRate > 0) {
bool breathAbn = (radar.breathRate < BREATH_RATE_MIN || radar.breathRate > BREATH_RATE_MAX); bool breathAbn = (radar.breathRate < BREATH_RATE_MIN || radar.breathRate > BREATH_RATE_MAX);
if (breathAbn) { if (breathAbn) {
if (++g_breathAbnCnt >= ALARM_HYSTERESIS && !g_localBreathAbnActive) { if (!g_breathDismissed && ++g_breathAbnCnt >= ALARM_HYSTERESIS && !g_localBreathAbnActive) {
g_localBreathAbnActive = true; g_localBreathAbnActive = true;
char buf[32]; char buf[32];
snprintf(buf, sizeof(buf), "呼吸异常: %d 次/分", radar.breathRate); snprintf(buf, sizeof(buf), "呼吸异常: %d 次/分", radar.breathRate);
@@ -360,6 +375,7 @@ void checkLocalAlarms(const RadarRawData& radar) {
} }
} else { } else {
g_breathAbnCnt = 0; g_breathAbnCnt = 0;
g_breathDismissed = false;
if (g_localBreathAbnActive) { if (g_localBreathAbnActive) {
g_localBreathAbnActive = false; g_localBreathAbnActive = false;
g_alarm.clearLocal(AlarmManager::ALARM_BREATH_ABNORMAL); g_alarm.clearLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
@@ -368,6 +384,7 @@ void checkLocalAlarms(const RadarRawData& radar) {
} else if (!humanPresent && g_localBreathAbnActive) { } else if (!humanPresent && g_localBreathAbnActive) {
g_localBreathAbnActive = false; g_localBreathAbnActive = false;
g_breathAbnCnt = 0; g_breathAbnCnt = 0;
g_breathDismissed = false;
g_alarm.clearLocal(AlarmManager::ALARM_BREATH_ABNORMAL); g_alarm.clearLocal(AlarmManager::ALARM_BREATH_ABNORMAL);
} }
#endif // RADAR_TYPE != AFD1 #endif // RADAR_TYPE != AFD1
@@ -375,13 +392,18 @@ void checkLocalAlarms(const RadarRawData& radar) {
// ─── ★★★ 环境安全: 水浸 (仅浴室) ─── // ─── ★★★ 环境安全: 水浸 (仅浴室) ───
if (enWater) { if (enWater) {
bool waterLeak = !g_sensor.getRainDO() && g_sensor.isRainValid(); bool waterLeak = !g_sensor.getRainDO() && g_sensor.isRainValid();
if (waterLeak && !g_localWaterActive) { if (waterLeak) {
if (!g_waterDismissed && !g_localWaterActive) {
g_localWaterActive = true; g_localWaterActive = true;
g_alarm.triggerLocal(AlarmManager::ALARM_WATER_LEAK, "水浸传感器触发"); g_alarm.triggerLocal(AlarmManager::ALARM_WATER_LEAK, "水浸传感器触发");
} else if (!waterLeak && g_localWaterActive) { }
} else {
g_waterDismissed = false;
if (g_localWaterActive) {
g_localWaterActive = false; g_localWaterActive = false;
g_alarm.clearLocal(AlarmManager::ALARM_WATER_LEAK); g_alarm.clearLocal(AlarmManager::ALARM_WATER_LEAK);
} }
}
} // enWater } // enWater
// ─── ★★ 环境舒适: 温湿度异常 (仅卧室/阳台/厨房, 防抖3次, 30s) ─── // ─── ★★ 环境舒适: 温湿度异常 (仅卧室/阳台/厨房, 防抖3次, 30s) ───
@@ -389,7 +411,7 @@ void checkLocalAlarms(const RadarRawData& radar) {
float temp = g_sensor.getTemp(); float temp = g_sensor.getTemp();
bool tempAbn = (temp < TEMP_LOW || temp > TEMP_HIGH); bool tempAbn = (temp < TEMP_LOW || temp > TEMP_HIGH);
if (tempAbn) { if (tempAbn) {
if (++g_tempAbnCnt >= ALARM_HYSTERESIS && !g_localTempAbnActive) { if (!g_tempDismissed && ++g_tempAbnCnt >= ALARM_HYSTERESIS && !g_localTempAbnActive) {
g_localTempAbnActive = true; g_localTempAbnActive = true;
char buf[32]; char buf[32];
snprintf(buf, sizeof(buf), "温度%.1f°C 超出范围", temp); snprintf(buf, sizeof(buf), "温度%.1f°C 超出范围", temp);
@@ -397,6 +419,7 @@ void checkLocalAlarms(const RadarRawData& radar) {
} }
} else { } else {
g_tempAbnCnt = 0; g_tempAbnCnt = 0;
g_tempDismissed = false;
if (g_localTempAbnActive) { if (g_localTempAbnActive) {
g_localTempAbnActive = false; g_localTempAbnActive = false;
g_alarm.clearLocal(AlarmManager::ALARM_TEMP_ABNORMAL); g_alarm.clearLocal(AlarmManager::ALARM_TEMP_ABNORMAL);
@@ -407,7 +430,7 @@ void checkLocalAlarms(const RadarRawData& radar) {
float humi = g_sensor.getHumi(); float humi = g_sensor.getHumi();
bool humiAbn = (humi < HUMI_LOW || humi > HUMI_HIGH); bool humiAbn = (humi < HUMI_LOW || humi > HUMI_HIGH);
if (humiAbn) { if (humiAbn) {
if (++g_humiAbnCnt >= ALARM_HYSTERESIS && !g_localHumiAbnActive) { if (!g_humiDismissed && ++g_humiAbnCnt >= ALARM_HYSTERESIS && !g_localHumiAbnActive) {
g_localHumiAbnActive = true; g_localHumiAbnActive = true;
char buf[32]; char buf[32];
snprintf(buf, sizeof(buf), "湿度%.1f%% 超出范围", humi); snprintf(buf, sizeof(buf), "湿度%.1f%% 超出范围", humi);
@@ -415,6 +438,7 @@ void checkLocalAlarms(const RadarRawData& radar) {
} }
} else { } else {
g_humiAbnCnt = 0; g_humiAbnCnt = 0;
g_humiDismissed = false;
if (g_localHumiAbnActive) { if (g_localHumiAbnActive) {
g_localHumiAbnActive = false; g_localHumiAbnActive = false;
g_alarm.clearLocal(AlarmManager::ALARM_HUMI_ABNORMAL); g_alarm.clearLocal(AlarmManager::ALARM_HUMI_ABNORMAL);
@@ -527,9 +551,17 @@ void setup() {
g_localDhtFaultActive = false; g_localDhtFaultActive = false;
g_tempAbnCnt = 0; g_tempAbnCnt = 0;
g_humiAbnCnt = 0; g_humiAbnCnt = 0;
g_heartAbnCnt = 0;
g_breathAbnCnt = 0;
g_radarTimeoutCnt = 0; g_radarTimeoutCnt = 0;
g_fallClearMs = 0; g_fallClearMs = 0;
g_fallDismissed = true; // 抑制重复报警, 等跌倒信号消了再恢复 g_fallDismissed = true; // 抑制重复报警, 等跌倒信号消了再恢复
// 其余类型同样抑制, 条件恢复正常后才重新武装
g_waterDismissed = true;
g_heartDismissed = true;
g_breathDismissed = true;
g_tempDismissed = true;
g_humiDismissed = true;
}); });
g_buttons.onProvisionLongPress([]() { g_buttons.onProvisionLongPress([]() {
g_wifi.startConfigPortal(); g_wifi.startConfigPortal();