|
@@ -877,14 +877,12 @@ class AlarmPlan:
|
|
MOTION_SMOOTH_WINDOW = 10 # 平滑窗口大小(取最近10帧计算平均)
|
|
MOTION_SMOOTH_WINDOW = 10 # 平滑窗口大小(取最近10帧计算平均)
|
|
STAY_THRESHOLD_PEACEFUL = 0.05 # 静止阈值
|
|
STAY_THRESHOLD_PEACEFUL = 0.05 # 静止阈值
|
|
STAY_THRESHOLD_MICRO = 0.15 # 微动阈值
|
|
STAY_THRESHOLD_MICRO = 0.15 # 微动阈值
|
|
- LEAVE_BED_TS = 3 # 离床判定时间阈值
|
|
|
|
|
|
+ LEAVE_BED_TS = 3 # 离床判定时间阈值
|
|
|
|
|
|
try:
|
|
try:
|
|
dev_id = self.dev_id_
|
|
dev_id = self.dev_id_
|
|
- device:Device = g_Dev.g_dev_mgr.find_dev_map(dev_id)
|
|
|
|
- if not device:
|
|
|
|
- return
|
|
|
|
- if (not self.rect_):
|
|
|
|
|
|
+ device: Device = g_Dev.g_dev_mgr.find_dev_map(dev_id)
|
|
|
|
+ if not device or not self.rect_:
|
|
return
|
|
return
|
|
|
|
|
|
rtd_list = device.get_rtd_que_copy()
|
|
rtd_list = device.get_rtd_que_copy()
|
|
@@ -893,136 +891,130 @@ class AlarmPlan:
|
|
|
|
|
|
# 初始化状态机
|
|
# 初始化状态机
|
|
if not hasattr(self, "sleep_stat_"):
|
|
if not hasattr(self, "sleep_stat_"):
|
|
- self.sleep_stat_: str = "leave" # 当前睡眠状态
|
|
|
|
- self.update_sleep_stat_: str = "leave" # 要更新的睡眠状态
|
|
|
|
- self.avg_motion_: float = 0.0 # 当前运动幅值
|
|
|
|
- self.motion_stat_: str = "leave" # 当前运动状态
|
|
|
|
- self.avg_breath_: float = 0.0 # 当前呼吸率
|
|
|
|
- self.breathe_stat_: str = "r0" # 当前呼吸状态
|
|
|
|
-
|
|
|
|
- self.start_sleep_ts_: int = -1 # 睡眠开始时间
|
|
|
|
- self.end_sleep_ts_: int = -1 # 睡眠结束时间
|
|
|
|
-
|
|
|
|
- self.motion_window = deque(maxlen=MOTION_SMOOTH_WINDOW) # 近 N 次运动距离均值
|
|
|
|
- self.sleep_segments_ = [] # 状态阶段记录 [{"ts":xxx, "stat":xxx}, ...]
|
|
|
|
- self.last_pts_ = [] # 最后的点目标
|
|
|
|
- self.last_update_ts_ = -1
|
|
|
|
|
|
+ self.sleep_stat_: str = "leave" # 当前睡眠状态
|
|
|
|
+ self.update_sleep_stat_: str = "leave"
|
|
|
|
+ self.avg_motion_: float = 0.0
|
|
|
|
+ self.motion_stat_: str = "leave"
|
|
|
|
+ self.avg_breath_: float = 0.0
|
|
|
|
+ self.breathe_stat_: str = "r0"
|
|
|
|
+
|
|
|
|
+ self.start_sleep_ts_: int = -1
|
|
|
|
+ self.end_sleep_ts_: int = -1
|
|
|
|
+
|
|
|
|
+ self.motion_window = deque(maxlen=MOTION_SMOOTH_WINDOW)
|
|
|
|
+ self.sleep_segments_ = [] # [{"ts":xxx, "stat":xxx}, ...]
|
|
|
|
+ self.last_pts_ = []
|
|
|
|
+ self.last_update_ts_ = -1
|
|
self.miss_target_count_ = 0
|
|
self.miss_target_count_ = 0
|
|
- self.last_leave_ts = -1 # 上次离床判定时间,离床判定时间超过5秒视为离床事件
|
|
|
|
|
|
+ self.last_leave_ts = -1 # 离床检测起点
|
|
|
|
|
|
- now_ts = get_utc_time_s()
|
|
|
|
|
|
+ now_ts = get_utc_time_s()
|
|
rtd_unit = rtd_list[-1]
|
|
rtd_unit = rtd_list[-1]
|
|
ts = rtd_unit["timestamp"]
|
|
ts = rtd_unit["timestamp"]
|
|
target_point = rtd_unit["target_point"]
|
|
target_point = rtd_unit["target_point"]
|
|
|
|
|
|
- ## 1. 空间状态分析
|
|
|
|
|
|
+ ## 1. 空间状态分析(motion)
|
|
if now_ts - ts > NO_TARGET_TIMEOUT_S:
|
|
if now_ts - ts > NO_TARGET_TIMEOUT_S:
|
|
- ## 目标不存在
|
|
|
|
- # 起夜
|
|
|
|
- x, y, z, snr = target_point
|
|
|
|
- if not helper.is_point_in_rect(x, y, self.rect_):
|
|
|
|
|
|
+ # 长时间无数据 → 目标消失
|
|
|
|
+ self.miss_target_count_ += 1
|
|
|
|
+ if self.miss_target_count_ >= NO_TARGET_MAX_COUNT:
|
|
motion = "leave"
|
|
motion = "leave"
|
|
-
|
|
|
|
- # 检测不到体动
|
|
|
|
- if self.start_sleep_ts_ == -1:
|
|
|
|
- return
|
|
|
|
else:
|
|
else:
|
|
- motion = "peaceful"
|
|
|
|
|
|
+ motion = self.motion_stat_
|
|
else:
|
|
else:
|
|
- ## 目标存在
|
|
|
|
- x = target_point[0]
|
|
|
|
- y = target_point[1]
|
|
|
|
|
|
+ self.miss_target_count_ = 0
|
|
|
|
+ x, y, z, snr = target_point
|
|
if not helper.is_point_in_rect(x, y, self.rect_):
|
|
if not helper.is_point_in_rect(x, y, self.rect_):
|
|
# 不在床上
|
|
# 不在床上
|
|
- motion = "leave"
|
|
|
|
|
|
+ if self.last_leave_ts == -1:
|
|
|
|
+ self.last_leave_ts = now_ts
|
|
|
|
+ elif now_ts - self.last_leave_ts > LEAVE_BED_TS:
|
|
|
|
+ motion = "leave"
|
|
|
|
+ else:
|
|
|
|
+ motion = "active"
|
|
else:
|
|
else:
|
|
- # 在床上
|
|
|
|
- # 计算体动
|
|
|
|
- WINDOWS_S = 10 # 滑动事件窗口
|
|
|
|
|
|
+ self.last_leave_ts = -1
|
|
|
|
+ # 在床上,计算位移
|
|
|
|
+ WINDOWS_S = 10
|
|
recent_rtds = [r for r in rtd_list if now_ts - r["timestamp"] <= WINDOWS_S]
|
|
recent_rtds = [r for r in rtd_list if now_ts - r["timestamp"] <= WINDOWS_S]
|
|
- if len(recent_rtds) < 5:
|
|
|
|
- return
|
|
|
|
-
|
|
|
|
- # 取最新点
|
|
|
|
- x,y,z,snr = recent_rtds[-1]["target_point"]
|
|
|
|
- # 检查是否在床上
|
|
|
|
- if not helper.is_point_in_rect(x, y, self.rect_):
|
|
|
|
- if self.last_leave_ts == -1:
|
|
|
|
- self.last_leave_ts = now_ts
|
|
|
|
- elif now_ts - self.last_leave_ts > LEAVE_BED_TS:
|
|
|
|
- motion = "leave"
|
|
|
|
|
|
+ if len(recent_rtds) < 2:
|
|
|
|
+ motion = self.motion_stat_
|
|
else:
|
|
else:
|
|
- self.last_leave_ts = -1
|
|
|
|
- # 计算位移序列
|
|
|
|
motions = []
|
|
motions = []
|
|
for i in range(1, len(recent_rtds)):
|
|
for i in range(1, len(recent_rtds)):
|
|
x1, y1, z1, _ = recent_rtds[i - 1]["target_point"]
|
|
x1, y1, z1, _ = recent_rtds[i - 1]["target_point"]
|
|
x2, y2, z2, _ = recent_rtds[i]["target_point"]
|
|
x2, y2, z2, _ = recent_rtds[i]["target_point"]
|
|
dist = ((x2 - x1)**2 + (y2 - y1)**2 + (z2 - z1)**2)**0.5
|
|
dist = ((x2 - x1)**2 + (y2 - y1)**2 + (z2 - z1)**2)**0.5
|
|
motions.append(dist)
|
|
motions.append(dist)
|
|
|
|
+
|
|
if motions:
|
|
if motions:
|
|
avg_motion = sum(motions) / len(motions)
|
|
avg_motion = sum(motions) / len(motions)
|
|
self.motion_window.append(avg_motion)
|
|
self.motion_window.append(avg_motion)
|
|
motion_smooth = sum(self.motion_window) / len(self.motion_window)
|
|
motion_smooth = sum(self.motion_window) / len(self.motion_window)
|
|
else:
|
|
else:
|
|
- motion_smooth = 0
|
|
|
|
|
|
+ motion_smooth = 0.0
|
|
|
|
|
|
- # 状态判定
|
|
|
|
- if motion_smooth < STAY_THRESHOLD_PEACEFUL:
|
|
|
|
- motion = "peaceful"
|
|
|
|
- elif motion_smooth < STAY_THRESHOLD_MICRO:
|
|
|
|
- motion = "mocro"
|
|
|
|
- else:
|
|
|
|
- motion = "active"
|
|
|
|
|
|
+ # 判定运动状态
|
|
|
|
+ if motion_smooth < STAY_THRESHOLD_PEACEFUL:
|
|
|
|
+ motion = "peaceful"
|
|
|
|
+ elif motion_smooth < STAY_THRESHOLD_MICRO:
|
|
|
|
+ motion = "micro"
|
|
|
|
+ else:
|
|
|
|
+ motion = "active"
|
|
|
|
+
|
|
|
|
+ self.motion_stat_ = motion
|
|
|
|
|
|
- self.motion_stat_ = motion
|
|
|
|
|
|
+ ## 2. 呼吸状态分析(breathe)
|
|
|
|
+ BREATHE_WINDOWS_S = 5
|
|
|
|
+ recent_breaths = [
|
|
|
|
+ r.get("breath_rpm", 0.0)
|
|
|
|
+ for r in rtd_list
|
|
|
|
+ if (now_ts - r["timestamp"] <= BREATHE_WINDOWS_S)
|
|
|
|
+ and isinstance(r.get("breath_rpm"), (int, float))
|
|
|
|
+ ]
|
|
|
|
|
|
- ## 2. 呼吸率分析
|
|
|
|
- BREATHE_WINDOWS_S = 5 # 呼吸滑动时间窗口
|
|
|
|
- recent_breaths =[r["breath_rpm"] for r in rtd_list
|
|
|
|
- if (now_ts - r["timestamp"] <= BREATHE_WINDOWS_S) and
|
|
|
|
- ("breath_rpm" in r and isinstance(r["breath_rpm"], (int, float)))]
|
|
|
|
-
|
|
|
|
if not recent_breaths:
|
|
if not recent_breaths:
|
|
- breathe_stat = "r0" # 无数据时视为异常/无呼吸
|
|
|
|
avg_breath = 0.0
|
|
avg_breath = 0.0
|
|
|
|
+ breathe_stat = "r0"
|
|
else:
|
|
else:
|
|
avg_breath = sum(recent_breaths) / len(recent_breaths)
|
|
avg_breath = sum(recent_breaths) / len(recent_breaths)
|
|
-
|
|
|
|
if avg_breath < 8:
|
|
if avg_breath < 8:
|
|
breathe_stat = "r0"
|
|
breathe_stat = "r0"
|
|
elif avg_breath < 12:
|
|
elif avg_breath < 12:
|
|
breathe_stat = "r1"
|
|
breathe_stat = "r1"
|
|
elif avg_breath < 18:
|
|
elif avg_breath < 18:
|
|
- breathe_stat = "r1"
|
|
|
|
|
|
+ breathe_stat = "r2"
|
|
|
|
+ elif avg_breath < 25:
|
|
|
|
+ breathe_stat = "r3"
|
|
else:
|
|
else:
|
|
- breathe_stat = "r1"
|
|
|
|
|
|
+ breathe_stat = "r4"
|
|
|
|
|
|
self.avg_breath_ = avg_breath
|
|
self.avg_breath_ = avg_breath
|
|
self.breathe_stat_ = breathe_stat
|
|
self.breathe_stat_ = breathe_stat
|
|
|
|
|
|
- ## 3. 睡眠状态分析
|
|
|
|
|
|
+ ## 3. 睡眠状态分析(sleep)
|
|
try:
|
|
try:
|
|
i_motion = EventAttr_SleepMonitoring.motion_stat.index(self.motion_stat_)
|
|
i_motion = EventAttr_SleepMonitoring.motion_stat.index(self.motion_stat_)
|
|
i_breath = EventAttr_SleepMonitoring.breathe_stat.index(breathe_stat)
|
|
i_breath = EventAttr_SleepMonitoring.breathe_stat.index(breathe_stat)
|
|
self.update_sleep_stat_ = EventAttr_SleepMonitoring.sleep_stat[i_motion][i_breath]
|
|
self.update_sleep_stat_ = EventAttr_SleepMonitoring.sleep_stat[i_motion][i_breath]
|
|
except ValueError:
|
|
except ValueError:
|
|
- LOGERR(f"infalid i_montion or i_breath")
|
|
|
|
-
|
|
|
|
|
|
+ LOGERR(f"[SleepMonitoring] invalid motion/breath index")
|
|
|
|
+ return
|
|
|
|
|
|
- # 3.1 更新睡眠报告
|
|
|
|
- sleep_node = {
|
|
|
|
- "ts": now_ts,
|
|
|
|
- "sleep_stat": self.update_sleep_stat_
|
|
|
|
- }
|
|
|
|
- self.sleep_segments_.append(sleep_node)
|
|
|
|
|
|
+ ## 4. 记录阶段变化
|
|
|
|
+ if (not self.sleep_segments_) or (self.update_sleep_stat_ != self.sleep_segments_[-1]["sleep_stat"]):
|
|
|
|
+ sleep_node = {
|
|
|
|
+ "ts": now_ts,
|
|
|
|
+ "sleep_stat": self.update_sleep_stat_
|
|
|
|
+ }
|
|
|
|
+ self.sleep_segments_.append(sleep_node)
|
|
|
|
+ LOGINFO(f"update sleep_stat, current: {self.update_sleep_stat_}")
|
|
|
|
|
|
self.sleep_stat_ = self.update_sleep_stat_
|
|
self.sleep_stat_ = self.update_sleep_stat_
|
|
|
|
|
|
|
|
+ ## 5. 调试输出(可选)
|
|
|
|
+ LOGINFO(f"[SleepMonitoring] {dev_id} motion={self.motion_stat_}, breath={self.avg_breath_:.1f}, sleep={self.sleep_stat_}")
|
|
|
|
|
|
- # 1. 分析空间状态
|
|
|
|
- # 2. 分析呼吸率
|
|
|
|
- # 3. 分析睡眠状态
|
|
|
|
return
|
|
return
|
|
|
|
|
|
except json.JSONDecodeError as e:
|
|
except json.JSONDecodeError as e:
|