feat:增加跌倒防抖帧

fix:修复开机错误跌倒报警。
This commit is contained in:
2026-08-18 14:51:57 +08:00
parent 03129c9ed8
commit 78458361fa
5 changed files with 54 additions and 26 deletions
+1 -1
View File
@@ -19,7 +19,7 @@
// --- 雷达型号选择 ---
#define RADAR_TYPE_ABD1 0 // R60ABD1: 心率/呼吸精准, 跌倒一般
#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
+5
View File
@@ -75,6 +75,7 @@ private:
void _extractWorkStatus(const RadarFrame& frame);
void _extractDetectRange(const RadarFrame& frame);
void _filterMotion(bool rawMotion); // 运动状态 3 帧去抖
void _filterFall(bool rawFall); // 跌倒状态 2 帧去抖 (防 AFD1 自检期噪声)
uint8_t _calcChecksum(const uint8_t* data, uint16_t len);
void _notifyUpdate();
@@ -100,6 +101,10 @@ private:
// 运动状态防抖 (体动信号常因小动作抖动, 加 3 帧持续才确认)
uint8_t _motionCnt; // 连续一致计数
bool _lastRawMotion; // 上一次原始值
// 跌倒状态防抖 (AFD1 自检期 fall 位可能为随机值, 2 帧持续才确认)
uint8_t _fallCnt; // 连续一致计数
bool _lastRawFall; // 上一次原始值
};
#endif // RADAR_MANAGER_H
+1 -1
View File
@@ -42,4 +42,4 @@ lib_deps =
; 上传配置
upload_speed = 921600
upload_port = COM20
upload_port = COM19
+27 -22
View File
@@ -108,8 +108,8 @@ void syncTime() {
void handleAlarmCommand(const AlarmCommand& cmd) {
if (strcmp(cmd.type, "alarm_restore") == 0) {
// 告警恢复 — 远程指令与手动按钮同等优先级
g_alarm.executeRestore(cmd);
// 告警恢复 — 远程指令与手动按钮同等优先级 (镜像 onAlarmDismiss 逻辑)
g_alarm.clearAllLocal();
// 清除全部本地告警状态 (远程恢复=手动解除)
g_localFallActive = false;
@@ -131,17 +131,18 @@ void handleAlarmCommand(const AlarmCommand& cmd) {
g_breathNormalMs = 0;
g_fallDetectMs = 0;
g_fallClearMs = 0;
g_fallDismissed = false;
g_vitalLostMs = 0;
g_vitalLostActive = false;
// 解除抑制标志也复位 (远程恢复后重新武装)
g_waterDismissed = false;
g_heartDismissed = false;
g_breathDismissed = false;
g_tempDismissed = false;
g_humiDismissed = false;
g_vitalLostDismissed = false;
// 抑制标志置 true — 与按钮一致 (原 BUG: 置 false 导致报警立即重新触发)
// 等报警源信号真正恢复正常后才由 checkLocalAlarms() 重新武装
g_fallDismissed = true;
g_waterDismissed = true;
g_heartDismissed = true;
g_breathDismissed = true;
g_tempDismissed = true;
g_humiDismissed = true;
g_vitalLostDismissed = true;
} else {
// 告警触发
g_alarm.executeAlarm(cmd);
@@ -271,21 +272,25 @@ TelemetryData buildTelemetry(const RadarRawData& radar) {
t.heartRate = radar.heartRate;
t.breathRate = radar.breathRate;
t.body = radar.body;
t.sleepState = radar.sleepState;
// 清醒时不报睡眠评分:
// - radar.resident=false 人离床 → 必然清醒
// - radar.sleepState==0 雷达说清醒 → 不报
// - 持续静默 < 5 分钟 → 可能是清醒坐着被误判, 也不报
// 这道闸门防止雷达误判深睡时上报误导评分
// ─── 睡眠状态判定: 设备端二次校验 ───
// 雷达常把"清醒静坐"误判为深睡 (只看体动/呼吸/心率不看眼睛)
// 设备端用"持续静默"过滤: 5 分钟内必须无任何体动才算真睡
// - 持续微动 (手掌点击/键盘敲击/呼吸起伏) → 必然判清醒
// - 完全无体动持续 5 分钟 → 才是真睡
if (radar.body > 0) {
g_stillMs = 0; // 有体动, 重置静默计时 (人在清醒活动)
g_stillMs = 0; // 任何体动立即重置, 表明此刻清醒
} else if (g_stillMs == 0) {
g_stillMs = millis();
}
bool sleepConfirmed = (radar.resident && radar.sleepState != 0 &&
radar.vitalValid &&
millis() - g_stillMs >= SLEEP_QUIET_MS);
t.sleepScore = sleepConfirmed ? radar.sleepScore : 0;
bool realSleep = (radar.resident && radar.vitalValid &&
millis() - g_stillMs >= SLEEP_QUIET_MS);
// sleepState 二次校验: 雷达说睡 + 静默达标 → 上报; 否则强制清醒
t.sleepState = realSleep ? radar.sleepState : 0;
// 评分与 sleepState 保持一致 (同步显示)
t.sleepScore = realSleep ? radar.sleepScore : 0;
t.radarValid = radar.radarValid;
t.vitalValid = radar.vitalValid;
@@ -330,7 +335,7 @@ void checkLocalAlarms(const RadarRawData& radar) {
// ─── ★★★★★ 人身安全: 跌倒 (仅浴室/走廊/客厅) ───
bool fallNow = enFall && radar.fall && humanPresent; // 当前是否处于跌倒状态
if (fallNow && !g_fallDismissed && millis() > 30000) {
if (fallNow && !g_fallDismissed && millis() > 30000) { // AFD1 自检约15-30s, 30s 跳过 + 2 帧去抖兜底
// 跌倒中且未解除 → 累积时长, 满 FALL_DETECT_MS 触发
if (!g_localFallActive) {
if (g_fallDetectMs == 0) {
+20 -2
View File
@@ -26,6 +26,8 @@ RadarManager::RadarManager()
, _lastRawPresence(false)
, _motionCnt(0)
, _lastRawMotion(false)
, _fallCnt(0)
, _lastRawFall(false)
{
memset(&_currentFrame, 0, sizeof(_currentFrame));
memset(&_data, 0, sizeof(_data));
@@ -371,13 +373,28 @@ void RadarManager::_extractHeartRate(const RadarFrame& frame) {
// AFD1 专用: 跌倒检测 (控制字 0x83)
void RadarManager::_extractFall(const RadarFrame& frame) {
if (frame.data_length >= 1) {
_data.fall = (frame.data[0] != 0);
bool rawFall = (frame.data[0] != 0);
_filterFall(rawFall); // 2 帧去抖, 防 AFD1 自检期误报
_data.radarValid = true;
_data.lastUpdate = millis();
_notifyUpdate();
}
}
// 跌倒状态防抖: 连续 2 帧一致才更新 _data.fall
// AFD1 自检期 fall 位可能是随机值, 单帧误报会被过滤
void RadarManager::_filterFall(bool rawFall) {
if (rawFall == _lastRawFall) {
if (_fallCnt < 2) _fallCnt++;
} else {
_fallCnt = 0;
_lastRawFall = rawFall;
}
if (_fallCnt >= 2 && rawFall != _data.fall) {
_data.fall = rawFall;
}
}
void RadarManager::_extractSleep(const RadarFrame& frame) {
// 从睡眠数据提取: cmd=0x00/0x01 为入床离床状态
if (frame.data_length >= 1) {
@@ -399,7 +416,8 @@ void RadarManager::_extractSleep(const RadarFrame& frame) {
if (frame.data_length >= 4) {
#if RADAR_TYPE == RADAR_TYPE_AFD1
// AFD1 协议: data[3] 低 2 位 = 跌倒/驻留
_data.fall = (frame.data[3] & 0x01) != 0;
bool rawFallFromSleep = (frame.data[3] & 0x01) != 0;
_filterFall(rawFallFromSleep); // 走防抖, 0x83 和 0x84 帧任一报 fall=true 都计数
_data.resident = (frame.data[3] & 0x02) != 0;
_data.lastUpdate = millis();
_notifyUpdate();