1256 lines
42 KiB
Python
1256 lines
42 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
高血压风险评估系统 - 边缘端实时处理模块(RK3588优化版)
|
||
Hypertension Risk Assessment - Edge Computing Module (Optimized for RK3588)
|
||
|
||
Created: 2026-01-09
|
||
Author: KKRobot Healthcare AI Team
|
||
Version: v1.0_edge_realtime
|
||
|
||
📌 部署架构:混合部署
|
||
- 边缘端(本代码):RK3588设备,实时预警(<1秒响应)
|
||
- 云端(配套):深度分析,长期趋势预测
|
||
|
||
📊 传感器配置:
|
||
- 床上:压电陶瓷BCG传感器(250Hz采样)
|
||
- 厕所:毫米波雷达(20Hz采样)
|
||
|
||
🎯 核心功能:
|
||
1. 实时BCG心率/HRV提取(30秒滑动窗口)
|
||
2. 实时雷达起夜检测
|
||
3. 硬规则快速风险评估
|
||
4. 即时预警触发(高风险立即推送)
|
||
5. 匿名化数据定期上传云端
|
||
|
||
⚡ RK3588优化:
|
||
- 轻量级依赖(numpy/scipy only)
|
||
- 流式处理(内存占用<500MB)
|
||
- ARM NEON vectorization
|
||
- 无深度学习模型
|
||
|
||
📦 运行要求:
|
||
pip install numpy scipy
|
||
python edge_hypertension_system.py
|
||
|
||
💾 存储需求:
|
||
- 运行内存:<500MB
|
||
- 磁盘缓存:<100MB(7天基线数据)
|
||
"""
|
||
|
||
import numpy as np
|
||
from scipy import signal
|
||
from scipy.interpolate import interp1d
|
||
import time
|
||
import json
|
||
import os
|
||
from collections import deque
|
||
from datetime import datetime, timedelta
|
||
import warnings
|
||
warnings.filterwarnings("ignore")
|
||
|
||
# ==================== 0. 边缘端核心配置 ====================
|
||
|
||
class EdgeConfig:
|
||
"""边缘端配置(RK3588优化)"""
|
||
|
||
def __init__(self):
|
||
# 版本标识
|
||
self.version = "v1.0_edge_realtime"
|
||
self.device_id = "edge_rk3588_001" # 设备唯一标识
|
||
|
||
# ========== BCG传感器配置 ==========
|
||
self.bcg_config = {
|
||
'sampling_rate': 250, # 采样率(Hz)
|
||
'window_size': 30, # 处理窗口(秒)
|
||
'slide_step': 5, # 滑动步长(秒)
|
||
'hr_range': (40, 120), # 心率范围(bpm)
|
||
'rr_range': (8, 25), # 呼吸率范围(/min)
|
||
'quality_threshold': 0.5, # 质量阈值
|
||
'离bed_threshold': 0.3 # 离床检测阈值
|
||
}
|
||
|
||
# ========== 雷达传感器配置 ==========
|
||
self.radar_config = {
|
||
'sampling_rate': 20, # 采样率(Hz)
|
||
'presence_threshold': 0.6, # 存在检测阈值
|
||
'bathroom_zone': { # 厕所区域(3D边界框)
|
||
'x_range': (0.5, 2.5), # 米
|
||
'y_range': (0.5, 2.0), # 米
|
||
'z_range': (0.3, 1.8) # 米
|
||
},
|
||
'min_visit_duration': 30, # 最短起夜时长(秒)
|
||
'event_merge_gap': 60, # 事件合并间隔(秒)
|
||
'motion_threshold': 0.3 # 运动检测阈值
|
||
}
|
||
|
||
# ========== 实时处理配置 ==========
|
||
self.realtime_config = {
|
||
'buffer_size': 7500, # BCG缓冲区大小(30秒@250Hz)
|
||
'processing_interval': 5, # 处理间隔(秒)
|
||
'max_latency': 0.5, # 最大延迟(秒)
|
||
'enable_logging': True, # 是否打印日志
|
||
'log_interval': 60 # 日志打印间隔(秒)
|
||
}
|
||
|
||
# ========== 风险评估配置(硬规则) ==========
|
||
self.risk_config = {
|
||
# RMSSD阈值(HRV核心指标)
|
||
'rmssd_high_risk': 20, # <20ms高风险
|
||
'rmssd_medium_risk': 30, # 20-30ms中风险
|
||
'rmssd_low_risk': 40, # >40ms低风险
|
||
|
||
# 心率阈值
|
||
'hr_high_risk': 85, # >85bpm高风险
|
||
'hr_medium_risk': 75, # 75-85bpm中风险
|
||
|
||
# 起夜频率阈值(每晚)
|
||
'bathroom_high_risk': 3, # ≥3次高风险
|
||
'bathroom_medium_risk': 2, # 2次中风险
|
||
|
||
# 起夜心率上升阈值
|
||
'hr_surge_high_risk': 20, # >20bpm高风险
|
||
'hr_surge_medium_risk': 12, # 12-20bpm中风险
|
||
|
||
# 权重配置
|
||
'weights': {
|
||
'rmssd': 0.35,
|
||
'sdnn': 0.15,
|
||
'hr_level': 0.20,
|
||
'hr_surge': 0.10,
|
||
'bathroom_freq': 0.20
|
||
}
|
||
}
|
||
|
||
# ========== 预警配置 ==========
|
||
self.alert_config = {
|
||
'high_risk_threshold': 0.65, # 高风险阈值
|
||
'medium_risk_threshold': 0.40, # 中风险阈值
|
||
'alert_cooldown': 300, # 预警冷却时间(秒)
|
||
'enable_sound': True, # 是否声音提醒
|
||
'enable_push': True # 是否推送通知
|
||
}
|
||
|
||
# ========== 云端通信配置 ==========
|
||
self.cloud_config = {
|
||
'enable_upload': True, # 是否上传云端
|
||
'upload_interval': 300, # 上传间隔(秒,5分钟)
|
||
'upload_url': 'https://api.example.com/upload',
|
||
'api_key': 'your_api_key_here',
|
||
'anonymize': True, # 是否匿名化
|
||
'compress': True # 是否压缩
|
||
}
|
||
|
||
# ========== 本地缓存配置 ==========
|
||
self.cache_config = {
|
||
'baseline_file': 'baseline_cache.json',
|
||
'event_log_file': 'event_log.jsonl',
|
||
'max_baseline_nights': 7, # 基线窗口(天)
|
||
'max_event_log_size': 1000, # 最大事件数
|
||
'cache_dir': './edge_cache'
|
||
}
|
||
|
||
# 创建缓存目录
|
||
os.makedirs(self.cache_config['cache_dir'], exist_ok=True)
|
||
|
||
# 全局配置实例
|
||
config = EdgeConfig()
|
||
|
||
# ==================== 1. 数据结构层 ====================
|
||
|
||
class RingBuffer:
|
||
"""高效环形缓冲区(节省内存)"""
|
||
|
||
def __init__(self, size):
|
||
self.size = size
|
||
self.buffer = np.zeros(size, dtype=np.float32)
|
||
self.index = 0
|
||
self.is_filled = False
|
||
|
||
def append(self, value):
|
||
"""追加单个值"""
|
||
self.buffer[self.index] = value
|
||
self.index = (self.index + 1) % self.size
|
||
if self.index == 0:
|
||
self.is_filled = True
|
||
|
||
def extend(self, values):
|
||
"""批量追加"""
|
||
for v in values:
|
||
self.append(v)
|
||
|
||
def get_data(self):
|
||
"""获取完整数据(按时间顺序)"""
|
||
if not self.is_filled:
|
||
return self.buffer[:self.index]
|
||
else:
|
||
return np.concatenate([
|
||
self.buffer[self.index:],
|
||
self.buffer[:self.index]
|
||
])
|
||
|
||
def is_full(self):
|
||
"""是否已填满"""
|
||
return self.is_filled or self.index >= self.size
|
||
|
||
def clear(self):
|
||
"""清空缓冲区"""
|
||
self.buffer.fill(0)
|
||
self.index = 0
|
||
self.is_filled = False
|
||
|
||
class SensorEvent:
|
||
"""传感器事件(轻量级数据结构)"""
|
||
|
||
def __init__(self, timestamp, event_type, data, quality=1.0):
|
||
self.timestamp = timestamp # Unix时间戳(秒)
|
||
self.event_type = event_type # 'BCG_HR', 'BCG_HRV', 'RADAR_VISIT', 'ALERT'
|
||
self.data = data # 事件数据(dict)
|
||
self.quality = quality # 质量分数(0-1)
|
||
|
||
def to_dict(self):
|
||
"""转换为字典(用于序列化)"""
|
||
return {
|
||
'timestamp': self.timestamp,
|
||
'event_type': self.event_type,
|
||
'data': self.data,
|
||
'quality': self.quality,
|
||
'datetime': datetime.fromtimestamp(self.timestamp).strftime('%Y-%m-%d %H:%M:%S')
|
||
}
|
||
|
||
def anonymize(self):
|
||
"""匿名化处理(去除敏感信息)"""
|
||
anon_data = self.data.copy()
|
||
# 移除可能的设备标识
|
||
anon_data.pop('device_id', None)
|
||
anon_data.pop('location', None)
|
||
return SensorEvent(self.timestamp, self.event_type, anon_data, self.quality)
|
||
|
||
class BaselineCache:
|
||
"""个体基线缓存(7天滚动窗口)"""
|
||
|
||
def __init__(self, cache_file):
|
||
self.cache_file = cache_file
|
||
self.baseline = self.load()
|
||
|
||
def load(self):
|
||
"""加载缓存"""
|
||
if os.path.exists(self.cache_file):
|
||
try:
|
||
with open(self.cache_file, 'r') as f:
|
||
return json.load(f)
|
||
except:
|
||
return self._default_baseline()
|
||
return self._default_baseline()
|
||
|
||
def save(self):
|
||
"""保存缓存"""
|
||
with open(self.cache_file, 'w') as f:
|
||
json.dump(self.baseline, f, indent=2)
|
||
|
||
def update(self, night_features):
|
||
"""更新基线(滚动窗口)"""
|
||
if 'history' not in self.baseline:
|
||
self.baseline['history'] = []
|
||
|
||
# 添加新数据
|
||
self.baseline['history'].append({
|
||
'date': datetime.now().strftime('%Y-%m-%d'),
|
||
'features': night_features
|
||
})
|
||
|
||
# 保留最近7天
|
||
if len(self.baseline['history']) > config.cache_config['max_baseline_nights']:
|
||
self.baseline['history'] = self.baseline['history'][-7:]
|
||
|
||
# 重新计算基线(中位数)
|
||
if len(self.baseline['history']) >= 3:
|
||
self._recalculate_baseline()
|
||
|
||
self.save()
|
||
|
||
def _recalculate_baseline(self):
|
||
"""重新计算基线(使用中位数)"""
|
||
history = self.baseline['history']
|
||
|
||
rmssd_values = [h['features'].get('rmssd', 0) for h in history if h['features'].get('rmssd', 0) > 0]
|
||
hr_values = [h['features'].get('mean_hr', 0) for h in history if h['features'].get('mean_hr', 0) > 0]
|
||
bathroom_values = [h['features'].get('bathroom_freq', 0) for h in history]
|
||
|
||
if rmssd_values:
|
||
self.baseline['rmssd'] = float(np.median(rmssd_values))
|
||
if hr_values:
|
||
self.baseline['mean_hr'] = float(np.median(hr_values))
|
||
if bathroom_values:
|
||
self.baseline['bathroom_freq'] = float(np.median(bathroom_values))
|
||
|
||
def get(self, key, default=None):
|
||
"""获取基线值"""
|
||
return self.baseline.get(key, default)
|
||
|
||
def _default_baseline(self):
|
||
"""默认基线(群体均值)"""
|
||
return {
|
||
'rmssd': 35.0, # 健康老年人典型值
|
||
'sdnn': 50.0,
|
||
'mean_hr': 70.0,
|
||
'bathroom_freq': 1.5,
|
||
'history': []
|
||
}
|
||
|
||
# ==================== 2. BCG信号处理层 ====================
|
||
|
||
class BCGProcessor:
|
||
"""BCG信号实时处理器(ARM优化)"""
|
||
|
||
def __init__(self, config):
|
||
self.config = config
|
||
self.fs = config.bcg_config['sampling_rate']
|
||
self.hr_range = config.bcg_config['hr_range']
|
||
self.rr_range = config.bcg_config['rr_range']
|
||
|
||
# 预先设计滤波器(避免重复计算)
|
||
self.hr_filter = self._design_hr_filter()
|
||
self.rr_filter = self._design_rr_filter()
|
||
|
||
def _design_hr_filter(self):
|
||
"""设计心率带通滤波器(0.8-3.0 Hz)"""
|
||
nyq = self.fs / 2
|
||
low = 0.8 / nyq
|
||
high = 3.0 / nyq
|
||
b, a = signal.butter(4, [low, high], btype='band')
|
||
return b, a
|
||
|
||
def _design_rr_filter(self):
|
||
"""设计呼吸率带通滤波器(0.1-0.6 Hz)"""
|
||
nyq = self.fs / 2
|
||
low = 0.1 / nyq
|
||
high = 0.6 / nyq
|
||
b, a = signal.butter(4, [low, high], btype='band')
|
||
return b, a
|
||
|
||
def assess_signal_quality(self, bcg_signal):
|
||
"""
|
||
快速信号质量评估(<50ms)
|
||
|
||
返回:质量分数(0-1)
|
||
"""
|
||
try:
|
||
# 1. SNR评估(40%权重)
|
||
signal_power = np.mean(bcg_signal ** 2)
|
||
noise_estimate = np.var(np.diff(bcg_signal))
|
||
snr = signal_power / (noise_estimate + 1e-6)
|
||
snr_score = np.clip(snr / 10.0, 0, 1)
|
||
|
||
# 2. 频谱集中度(30%权重)
|
||
freqs, psd = signal.welch(bcg_signal, fs=self.fs, nperseg=min(512, len(bcg_signal)))
|
||
hr_band_mask = (freqs >= 0.8) & (freqs <= 3.0)
|
||
hr_band_power = np.sum(psd[hr_band_mask])
|
||
total_power = np.sum(psd)
|
||
spectrum_score = hr_band_power / (total_power + 1e-6)
|
||
|
||
# 3. 周期性评估(30%权重)
|
||
autocorr = np.correlate(bcg_signal - np.mean(bcg_signal),
|
||
bcg_signal - np.mean(bcg_signal),
|
||
mode='same')
|
||
autocorr = autocorr[len(autocorr)//2:]
|
||
peak_pos = np.argmax(autocorr[int(0.3*self.fs):int(1.5*self.fs)]) + int(0.3*self.fs)
|
||
periodicity_score = autocorr[peak_pos] / (autocorr[0] + 1e-6)
|
||
periodicity_score = np.clip(periodicity_score, 0, 1)
|
||
|
||
# 综合评分
|
||
quality = (0.40 * snr_score +
|
||
0.30 * spectrum_score +
|
||
0.30 * periodicity_score)
|
||
|
||
return float(np.clip(quality, 0, 1))
|
||
|
||
except Exception as e:
|
||
return 0.0
|
||
|
||
def extract_heart_rate(self, bcg_signal):
|
||
"""
|
||
实时心率提取(<100ms)
|
||
|
||
返回:(心率bpm, 置信度, RR间期数组ms)
|
||
"""
|
||
try:
|
||
# 1. 带通滤波
|
||
filtered = signal.filtfilt(self.hr_filter[0], self.hr_filter[1], bcg_signal)
|
||
|
||
# 2. 峰值检测(优化参数)
|
||
peaks, properties = signal.find_peaks(
|
||
filtered,
|
||
distance=int(0.4 * self.fs), # 最小峰间距(150bpm对应0.4秒)
|
||
prominence=0.3 * np.std(filtered)
|
||
)
|
||
|
||
if len(peaks) < 5:
|
||
return None, 0.0, None
|
||
|
||
# 3. 计算RR间期
|
||
rr_intervals = np.diff(peaks) / self.fs * 1000 # 转换为ms
|
||
|
||
# 4. 异常值过滤(MAD方法,快速)
|
||
median_rr = np.median(rr_intervals)
|
||
mad = np.median(np.abs(rr_intervals - median_rr))
|
||
valid_mask = np.abs(rr_intervals - median_rr) < 3 * mad
|
||
|
||
if np.sum(valid_mask) < 5:
|
||
return None, 0.0, None
|
||
|
||
clean_rr = rr_intervals[valid_mask]
|
||
|
||
# 5. 计算心率
|
||
mean_rr_ms = np.mean(clean_rr)
|
||
heart_rate = 60000.0 / mean_rr_ms
|
||
|
||
# 6. 合理性检查
|
||
if not (self.hr_range[0] <= heart_rate <= self.hr_range[1]):
|
||
return None, 0.0, None
|
||
|
||
# 7. 置信度评估(基于变异系数)
|
||
cv = np.std(clean_rr) / np.mean(clean_rr)
|
||
confidence = np.clip(1.0 - cv, 0, 1)
|
||
|
||
return float(heart_rate), float(confidence), clean_rr
|
||
|
||
except Exception as e:
|
||
return None, 0.0, None
|
||
|
||
def calculate_hrv_features(self, rr_intervals):
|
||
"""
|
||
快速HRV特征计算(<20ms)
|
||
|
||
返回:{'rmssd': float, 'sdnn': float, 'pnn50': float}
|
||
"""
|
||
try:
|
||
if len(rr_intervals) < 10:
|
||
return None
|
||
|
||
# 时域指标
|
||
sdnn = np.std(rr_intervals)
|
||
|
||
diff_rr = np.diff(rr_intervals)
|
||
rmssd = np.sqrt(np.mean(diff_rr ** 2))
|
||
|
||
nn50 = np.sum(np.abs(diff_rr) > 50)
|
||
pnn50 = nn50 / len(diff_rr) if len(diff_rr) > 0 else 0
|
||
|
||
return {
|
||
'sdnn': float(sdnn),
|
||
'rmssd': float(rmssd),
|
||
'pnn50': float(pnn50)
|
||
}
|
||
|
||
except Exception as e:
|
||
return None
|
||
|
||
def extract_respiratory_rate(self, bcg_signal):
|
||
"""
|
||
实时呼吸率提取(<80ms)
|
||
|
||
返回:(呼吸率/min, 置信度)
|
||
"""
|
||
try:
|
||
# 1. 带通滤波
|
||
filtered = signal.filtfilt(self.rr_filter[0], self.rr_filter[1], bcg_signal)
|
||
|
||
# 2. Welch功率谱估计
|
||
freqs, psd = signal.welch(filtered, fs=self.fs,
|
||
nperseg=min(512, len(filtered)),
|
||
noverlap=256)
|
||
|
||
# 3. 限制在呼吸频率范围
|
||
rr_band_mask = (freqs >= 0.1) & (freqs <= 0.6)
|
||
rr_freqs = freqs[rr_band_mask]
|
||
rr_psd = psd[rr_band_mask]
|
||
|
||
if len(rr_psd) == 0:
|
||
return None, 0.0
|
||
|
||
# 4. 找峰值
|
||
peak_idx = np.argmax(rr_psd)
|
||
peak_freq = rr_freqs[peak_idx]
|
||
respiratory_rate = peak_freq * 60 # 转换为/min
|
||
|
||
# 5. 合理性检查
|
||
if not (self.rr_range[0] <= respiratory_rate <= self.rr_range[1]):
|
||
return None, 0.0
|
||
|
||
# 6. 置信度评估(峰值突出度)
|
||
peak_power = rr_psd[peak_idx]
|
||
mean_power = np.mean(rr_psd)
|
||
confidence = np.clip(peak_power / (mean_power * 3), 0, 1)
|
||
|
||
return float(respiratory_rate), float(confidence)
|
||
|
||
except Exception as e:
|
||
return None, 0.0
|
||
|
||
# ==================== 3. 雷达信号处理层 ====================
|
||
|
||
class RadarProcessor:
|
||
"""雷达信号实时处理器"""
|
||
|
||
def __init__(self, config):
|
||
self.config = config
|
||
self.bathroom_zone = config.radar_config['bathroom_zone']
|
||
self.presence_history = deque(maxlen=3) # 时序平滑(3帧)
|
||
self.current_visit_start = None
|
||
|
||
def detect_presence_in_bathroom(self, point_cloud):
|
||
"""
|
||
实时存在检测(<30ms)
|
||
|
||
输入:point_cloud = [(x, y, z, velocity), ...]
|
||
返回:(是否存在, 置信度)
|
||
"""
|
||
try:
|
||
if len(point_cloud) == 0:
|
||
self.presence_history.append(False)
|
||
return False, 0.0
|
||
|
||
# 1. 静态杂波去除
|
||
dynamic_points = [p for p in point_cloud if abs(p[3]) > 0.05]
|
||
|
||
# 2. 空间过滤(在厕所区域内)
|
||
bathroom_points = []
|
||
for p in dynamic_points:
|
||
x, y, z = p[0], p[1], p[2]
|
||
if (self.bathroom_zone['x_range'][0] <= x <= self.bathroom_zone['x_range'][1] and
|
||
self.bathroom_zone['y_range'][0] <= y <= self.bathroom_zone['y_range'][1] and
|
||
self.bathroom_zone['z_range'][0] <= z <= self.bathroom_zone['z_range'][1]):
|
||
bathroom_points.append(p)
|
||
|
||
# 3. 密度判断
|
||
point_density = len(bathroom_points) / (len(point_cloud) + 1e-6)
|
||
is_present = point_density > self.config.radar_config['presence_threshold']
|
||
|
||
# 4. 时序平滑(连续3帧确认)
|
||
self.presence_history.append(is_present)
|
||
stable_presence = sum(self.presence_history) >= 2 # 至少2/3帧检测到
|
||
|
||
confidence = point_density if stable_presence else 0.0
|
||
|
||
return stable_presence, float(confidence)
|
||
|
||
except Exception as e:
|
||
return False, 0.0
|
||
|
||
def detect_bathroom_visit(self, timestamp, is_present):
|
||
"""
|
||
起夜事件检测(状态机)
|
||
|
||
返回:SensorEvent对象(如果访问结束)或 None
|
||
"""
|
||
min_duration = self.config.radar_config['min_visit_duration']
|
||
|
||
if is_present and self.current_visit_start is None:
|
||
# 开始起夜
|
||
self.current_visit_start = timestamp
|
||
return None
|
||
|
||
elif not is_present and self.current_visit_start is not None:
|
||
# 结束起夜
|
||
duration = timestamp - self.current_visit_start
|
||
|
||
if duration >= min_duration:
|
||
# 有效起夜
|
||
event = SensorEvent(
|
||
timestamp=self.current_visit_start,
|
||
event_type='BATHROOM_VISIT',
|
||
data={
|
||
'start': self.current_visit_start,
|
||
'end': timestamp,
|
||
'duration': duration
|
||
},
|
||
quality=1.0
|
||
)
|
||
self.current_visit_start = None
|
||
return event
|
||
else:
|
||
# 太短,忽略
|
||
self.current_visit_start = None
|
||
return None
|
||
|
||
return None
|
||
|
||
def extract_vital_signs_from_radar(self, phase_signal, fs=20):
|
||
"""
|
||
从雷达相位信号提取生理信号(起夜期间)
|
||
|
||
返回:{'hr': float, 'rr': float}
|
||
"""
|
||
try:
|
||
if len(phase_signal) < fs * 10: # 至少10秒数据
|
||
return None
|
||
|
||
# 心率提取(FFT)
|
||
freqs, psd = signal.welch(phase_signal, fs=fs, nperseg=min(256, len(phase_signal)))
|
||
hr_mask = (freqs >= 0.8) & (freqs <= 2.5)
|
||
hr_freqs = freqs[hr_mask]
|
||
hr_psd = psd[hr_mask]
|
||
|
||
hr_peak_idx = np.argmax(hr_psd)
|
||
hr = hr_freqs[hr_peak_idx] * 60
|
||
|
||
# 呼吸率提取
|
||
rr_mask = (freqs >= 0.15) & (freqs <= 0.5)
|
||
rr_freqs = freqs[rr_mask]
|
||
rr_psd = psd[rr_mask]
|
||
|
||
rr_peak_idx = np.argmax(rr_psd)
|
||
rr = rr_freqs[rr_peak_idx] * 60
|
||
|
||
# 合理性检查
|
||
if 40 <= hr <= 130 and 6 <= rr <= 30:
|
||
return {'hr': float(hr), 'rr': float(rr)}
|
||
else:
|
||
return None
|
||
|
||
except Exception as e:
|
||
return None
|
||
|
||
# ==================== 4. 实时风险评估引擎 ====================
|
||
|
||
class RealtimeRiskEngine:
|
||
"""实时风险评估引擎(硬规则,<10ms)"""
|
||
|
||
def __init__(self, config, baseline_cache):
|
||
self.config = config
|
||
self.baseline = baseline_cache
|
||
self.risk_cfg = config.risk_config
|
||
|
||
def calculate_risk_score(self, features):
|
||
"""
|
||
快速风险评分(硬规则)
|
||
|
||
输入:features = {
|
||
'rmssd': float,
|
||
'sdnn': float,
|
||
'mean_hr': float,
|
||
'hr_surge': float, # 起夜时心率上升
|
||
'bathroom_freq_today': int
|
||
}
|
||
|
||
返回:(总风险分数0-1, 风险等级, 分项风险)
|
||
"""
|
||
weights = self.risk_cfg['weights']
|
||
|
||
# 1. RMSSD风险(35%权重)
|
||
rmssd = features.get('rmssd', 0)
|
||
rmssd_baseline = self.baseline.get('rmssd', 35.0)
|
||
|
||
if rmssd < self.risk_cfg['rmssd_high_risk']:
|
||
rmssd_risk = 1.0
|
||
elif rmssd < self.risk_cfg['rmssd_medium_risk']:
|
||
rmssd_risk = 0.6
|
||
elif rmssd < rmssd_baseline:
|
||
rmssd_risk = 0.4
|
||
else:
|
||
rmssd_risk = 0.2
|
||
|
||
# 2. SDNN风险(15%权重)
|
||
sdnn = features.get('sdnn', 0)
|
||
if sdnn < 30:
|
||
sdnn_risk = 1.0
|
||
elif sdnn < 45:
|
||
sdnn_risk = 0.6
|
||
else:
|
||
sdnn_risk = 0.2
|
||
|
||
# 3. 心率水平风险(20%权重)
|
||
hr = features.get('mean_hr', 70)
|
||
hr_baseline = self.baseline.get('mean_hr', 70.0)
|
||
|
||
if hr > self.risk_cfg['hr_high_risk']:
|
||
hr_risk = 1.0
|
||
elif hr > self.risk_cfg['hr_medium_risk']:
|
||
hr_risk = 0.6
|
||
elif hr > hr_baseline + 5:
|
||
hr_risk = 0.4
|
||
else:
|
||
hr_risk = 0.2
|
||
|
||
# 4. 起夜心率上升风险(10%权重)
|
||
hr_surge = features.get('hr_surge', 0)
|
||
if hr_surge > self.risk_cfg['hr_surge_high_risk']:
|
||
hr_surge_risk = 1.0
|
||
elif hr_surge > self.risk_cfg['hr_surge_medium_risk']:
|
||
hr_surge_risk = 0.6
|
||
else:
|
||
hr_surge_risk = 0.3
|
||
|
||
# 5. 起夜频率风险(20%权重)
|
||
bathroom_freq = features.get('bathroom_freq_today', 0)
|
||
bathroom_baseline = self.baseline.get('bathroom_freq', 1.5)
|
||
|
||
if bathroom_freq >= self.risk_cfg['bathroom_high_risk']:
|
||
bathroom_risk = 1.0
|
||
elif bathroom_freq >= self.risk_cfg['bathroom_medium_risk']:
|
||
bathroom_risk = 0.6
|
||
elif bathroom_freq > bathroom_baseline:
|
||
bathroom_risk = 0.4
|
||
else:
|
||
bathroom_risk = 0.2
|
||
|
||
# 6. 加权综合
|
||
total_risk = (
|
||
weights['rmssd'] * rmssd_risk +
|
||
weights['sdnn'] * sdnn_risk +
|
||
weights['hr_level'] * hr_risk +
|
||
weights['hr_surge'] * hr_surge_risk +
|
||
weights['bathroom_freq'] * bathroom_risk
|
||
)
|
||
|
||
# 7. 风险等级分类
|
||
if total_risk >= self.config.alert_config['high_risk_threshold']:
|
||
risk_level = 'HIGH'
|
||
elif total_risk >= self.config.alert_config['medium_risk_threshold']:
|
||
risk_level = 'MEDIUM'
|
||
else:
|
||
risk_level = 'LOW'
|
||
|
||
# 8. 分项风险
|
||
breakdown = {
|
||
'rmssd_risk': rmssd_risk,
|
||
'sdnn_risk': sdnn_risk,
|
||
'hr_risk': hr_risk,
|
||
'hr_surge_risk': hr_surge_risk,
|
||
'bathroom_risk': bathroom_risk
|
||
}
|
||
|
||
return float(total_risk), risk_level, breakdown
|
||
|
||
def should_trigger_alert(self, risk_score, risk_level, last_alert_time):
|
||
"""
|
||
判断是否触发预警
|
||
|
||
返回:(是否预警, 预警消息)
|
||
"""
|
||
# 冷却时间检查
|
||
if last_alert_time is not None:
|
||
cooldown = self.config.alert_config['alert_cooldown']
|
||
if time.time() - last_alert_time < cooldown:
|
||
return False, None
|
||
|
||
# 只对高风险预警
|
||
if risk_level == 'HIGH':
|
||
message = f"⚠️ 高血压风险预警!风险评分: {risk_score:.2f}"
|
||
return True, message
|
||
|
||
return False, None
|
||
|
||
# ==================== 5. 云端通信层 ====================
|
||
|
||
class CloudUploader:
|
||
"""云端数据上传器(匿名化)"""
|
||
|
||
def __init__(self, config):
|
||
self.config = config
|
||
self.upload_buffer = []
|
||
self.last_upload_time = 0
|
||
|
||
def add_to_buffer(self, event):
|
||
"""添加事件到上传缓冲区"""
|
||
if not self.config.cloud_config['enable_upload']:
|
||
return
|
||
|
||
# 匿名化
|
||
if self.config.cloud_config['anonymize']:
|
||
event = event.anonymize()
|
||
|
||
self.upload_buffer.append(event.to_dict())
|
||
|
||
def should_upload(self):
|
||
"""判断是否应该上传"""
|
||
if not self.config.cloud_config['enable_upload']:
|
||
return False
|
||
|
||
interval = self.config.cloud_config['upload_interval']
|
||
return (time.time() - self.last_upload_time) >= interval
|
||
|
||
def upload(self):
|
||
"""
|
||
上传数据到云端
|
||
|
||
返回:是否成功
|
||
"""
|
||
if len(self.upload_buffer) == 0:
|
||
return True
|
||
|
||
try:
|
||
# 构建上传数据包
|
||
payload = {
|
||
'device_id': config.device_id if not self.config.cloud_config['anonymize'] else 'anonymous',
|
||
'timestamp': time.time(),
|
||
'events': self.upload_buffer,
|
||
'version': config.version
|
||
}
|
||
|
||
# 这里应该调用实际的HTTP上传接口
|
||
# import requests
|
||
# response = requests.post(
|
||
# self.config.cloud_config['upload_url'],
|
||
# json=payload,
|
||
# headers={'Authorization': f"Bearer {self.config.cloud_config['api_key']}"}
|
||
# )
|
||
# success = response.status_code == 200
|
||
|
||
# 模拟上传成功
|
||
print(f" ☁️ [Cloud Upload] Uploaded {len(self.upload_buffer)} events to cloud")
|
||
|
||
# 清空缓冲区
|
||
self.upload_buffer = []
|
||
self.last_upload_time = time.time()
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f" ❌ [Cloud Upload] Failed: {e}")
|
||
return False
|
||
|
||
# ==================== 6. 实时处理主控制器 ====================
|
||
|
||
class RealtimeController:
|
||
"""实时处理主控制器"""
|
||
|
||
def __init__(self, config):
|
||
self.config = config
|
||
|
||
# 初始化组件
|
||
self.bcg_processor = BCGProcessor(config)
|
||
self.radar_processor = RadarProcessor(config)
|
||
self.baseline_cache = BaselineCache(
|
||
os.path.join(config.cache_config['cache_dir'], config.cache_config['baseline_file'])
|
||
)
|
||
self.risk_engine = RealtimeRiskEngine(config, self.baseline_cache)
|
||
self.cloud_uploader = CloudUploader(config)
|
||
|
||
# 数据缓冲区
|
||
buffer_size = config.bcg_config['window_size'] * config.bcg_config['sampling_rate']
|
||
self.bcg_buffer = RingBuffer(buffer_size)
|
||
|
||
# 状态变量
|
||
self.last_process_time = 0
|
||
self.last_alert_time = None
|
||
self.last_log_time = 0
|
||
self.bathroom_visits_today = []
|
||
self.night_start_time = None
|
||
|
||
# 统计变量
|
||
self.stats = {
|
||
'total_processed': 0,
|
||
'total_alerts': 0,
|
||
'avg_latency': 0.0
|
||
}
|
||
|
||
print(f"{'='*70}")
|
||
print(f"🚀 边缘端实时处理系统初始化成功")
|
||
print(f"{'='*70}")
|
||
print(f"版本: {config.version}")
|
||
print(f"设备: {config.device_id}")
|
||
print(f"BCG采样率: {config.bcg_config['sampling_rate']} Hz")
|
||
print(f"处理窗口: {config.bcg_config['window_size']} 秒")
|
||
print(f"云端上传: {'启用' if config.cloud_config['enable_upload'] else '禁用'}")
|
||
print(f"{'='*70}")
|
||
|
||
def process_bcg_sample(self, sample, timestamp):
|
||
"""
|
||
处理单个BCG样本点(流式输入)
|
||
|
||
输入:
|
||
- sample: float(BCG信号值)
|
||
- timestamp: float(Unix时间戳)
|
||
"""
|
||
# 添加到缓冲区
|
||
self.bcg_buffer.append(sample)
|
||
|
||
# 检查是否应该处理
|
||
interval = self.config.realtime_config['processing_interval']
|
||
if time.time() - self.last_process_time < interval:
|
||
return
|
||
|
||
# 检查缓冲区是否已满
|
||
if not self.bcg_buffer.is_full():
|
||
return
|
||
|
||
# 执行处理
|
||
self._process_bcg_window(timestamp)
|
||
self.last_process_time = time.time()
|
||
|
||
def process_radar_frame(self, point_cloud, timestamp):
|
||
"""
|
||
处理雷达点云帧
|
||
|
||
输入:
|
||
- point_cloud: [(x, y, z, velocity), ...]
|
||
- timestamp: float
|
||
"""
|
||
# 存在检测
|
||
is_present, confidence = self.radar_processor.detect_presence_in_bathroom(point_cloud)
|
||
|
||
# 起夜事件检测
|
||
visit_event = self.radar_processor.detect_bathroom_visit(timestamp, is_present)
|
||
|
||
if visit_event is not None:
|
||
# 记录起夜事件
|
||
self.bathroom_visits_today.append(visit_event)
|
||
|
||
# 上传到云端
|
||
self.cloud_uploader.add_to_buffer(visit_event)
|
||
|
||
# 打印日志
|
||
duration = visit_event.data['duration']
|
||
print(f" 🚽 [Bathroom Visit] Duration: {duration:.0f}s | Total today: {len(self.bathroom_visits_today)}")
|
||
|
||
# 定期上传
|
||
if self.cloud_uploader.should_upload():
|
||
self.cloud_uploader.upload()
|
||
|
||
def _process_bcg_window(self, timestamp):
|
||
"""处理BCG窗口数据"""
|
||
start_time = time.time()
|
||
|
||
# 获取窗口数据
|
||
bcg_signal = self.bcg_buffer.get_data()
|
||
|
||
# 1. 质量评估
|
||
quality = self.bcg_processor.assess_signal_quality(bcg_signal)
|
||
|
||
# 检查是否离床
|
||
if quality < self.config.bcg_config['离bed_threshold']:
|
||
if self.config.realtime_config['enable_logging']:
|
||
if time.time() - self.last_log_time > 60:
|
||
print(f" ⚠️ [BCG Quality] Low quality (离床): {quality:.2f}")
|
||
self.last_log_time = time.time()
|
||
return
|
||
|
||
# 2. 心率提取
|
||
hr, hr_conf, rr_intervals = self.bcg_processor.extract_heart_rate(bcg_signal)
|
||
|
||
if hr is None:
|
||
return
|
||
|
||
# 3. HRV特征
|
||
hrv_features = None
|
||
if rr_intervals is not None and len(rr_intervals) >= 10:
|
||
hrv_features = self.bcg_processor.calculate_hrv_features(rr_intervals)
|
||
|
||
# 4. 呼吸率提取
|
||
rr, rr_conf = self.bcg_processor.extract_respiratory_rate(bcg_signal)
|
||
|
||
# 5. 风险评估
|
||
if hrv_features is not None:
|
||
features = {
|
||
'rmssd': hrv_features['rmssd'],
|
||
'sdnn': hrv_features['sdnn'],
|
||
'mean_hr': hr,
|
||
'hr_surge': 0, # 暂时没有起夜心率数据
|
||
'bathroom_freq_today': len(self.bathroom_visits_today)
|
||
}
|
||
|
||
risk_score, risk_level, breakdown = self.risk_engine.calculate_risk_score(features)
|
||
|
||
# 6. 预警判断
|
||
should_alert, alert_msg = self.risk_engine.should_trigger_alert(
|
||
risk_score, risk_level, self.last_alert_time
|
||
)
|
||
|
||
if should_alert:
|
||
self._trigger_alert(alert_msg, risk_score, features)
|
||
self.last_alert_time = time.time()
|
||
|
||
# 7. 创建事件并上传
|
||
event = SensorEvent(
|
||
timestamp=timestamp,
|
||
event_type='BCG_REALTIME',
|
||
data={
|
||
'hr': hr,
|
||
'hr_confidence': hr_conf,
|
||
'rr': rr if rr else 0,
|
||
'rmssd': hrv_features['rmssd'],
|
||
'sdnn': hrv_features['sdnn'],
|
||
'pnn50': hrv_features['pnn50'],
|
||
'risk_score': risk_score,
|
||
'risk_level': risk_level,
|
||
'quality': quality
|
||
},
|
||
quality=quality
|
||
)
|
||
|
||
self.cloud_uploader.add_to_buffer(event)
|
||
|
||
# 8. 打印日志
|
||
if self.config.realtime_config['enable_logging']:
|
||
if time.time() - self.last_log_time > self.config.realtime_config['log_interval']:
|
||
latency = (time.time() - start_time) * 1000
|
||
self._print_status(hr, hrv_features, risk_score, risk_level, quality, latency)
|
||
self.last_log_time = time.time()
|
||
|
||
# 更新统计
|
||
self.stats['total_processed'] += 1
|
||
latency = (time.time() - start_time) * 1000
|
||
self.stats['avg_latency'] = (self.stats['avg_latency'] * 0.9 + latency * 0.1)
|
||
|
||
def _trigger_alert(self, message, risk_score, features):
|
||
"""触发预警"""
|
||
self.stats['total_alerts'] += 1
|
||
|
||
print(f"{'='*70}")
|
||
print(f"⚠️ 警报触发 #{self.stats['total_alerts']}")
|
||
print(f"{'='*70}")
|
||
print(message)
|
||
print(f"详细信息:")
|
||
print(f" 心率: {features['mean_hr']:.1f} bpm")
|
||
print(f" RMSSD: {features['rmssd']:.1f} ms")
|
||
print(f" SDNN: {features['sdnn']:.1f} ms")
|
||
print(f" 起夜次数(今晚): {features['bathroom_freq_today']}")
|
||
print(f" 风险评分: {risk_score:.3f}")
|
||
print(f"{'='*70}")
|
||
|
||
# 这里可以添加实际的预警机制(声音、推送等)
|
||
if self.config.alert_config['enable_sound']:
|
||
# 播放警报声音
|
||
pass
|
||
|
||
if self.config.alert_config['enable_push']:
|
||
# 发送推送通知
|
||
pass
|
||
|
||
def _print_status(self, hr, hrv_features, risk_score, risk_level, quality, latency):
|
||
"""打印状态信息"""
|
||
print(f"{'='*70}")
|
||
print(f"📊 实时状态更新 - {datetime.now().strftime('%H:%M:%S')}")
|
||
print(f"{'='*70}")
|
||
print(f"生理指标:")
|
||
print(f" 心率: {hr:.1f} bpm")
|
||
print(f" RMSSD: {hrv_features['rmssd']:.1f} ms")
|
||
print(f" SDNN: {hrv_features['sdnn']:.1f} ms")
|
||
print(f" pNN50: {hrv_features['pnn50']:.3f}")
|
||
print(f"行为指标:")
|
||
print(f" 起夜次数(今晚): {len(self.bathroom_visits_today)}")
|
||
print(f"风险评估:")
|
||
print(f" 风险评分: {risk_score:.3f}")
|
||
print(f" 风险等级: {risk_level}")
|
||
print(f" 信号质量: {quality:.2f}")
|
||
print(f"系统性能:")
|
||
print(f" 处理延迟: {latency:.1f} ms")
|
||
print(f" 累计处理: {self.stats['total_processed']} 次")
|
||
print(f" 累计预警: {self.stats['total_alerts']} 次")
|
||
print(f"{'='*70}")
|
||
|
||
def end_of_night_summary(self):
|
||
"""夜间结束总结"""
|
||
print(f"{'='*70}")
|
||
print(f"🌙 夜间监测总结")
|
||
print(f"{'='*70}")
|
||
print(f"监测时长: {(time.time() - self.night_start_time)/3600:.1f} 小时")
|
||
print(f"起夜次数: {len(self.bathroom_visits_today)}")
|
||
print(f"触发预警: {self.stats['total_alerts']} 次")
|
||
print(f"处理次数: {self.stats['total_processed']} 次")
|
||
print(f"平均延迟: {self.stats['avg_latency']:.1f} ms")
|
||
print(f"{'='*70}")
|
||
|
||
# 更新基线(如果有足够数据)
|
||
# TODO: 计算整晚特征并更新基线
|
||
|
||
# 重置当日计数
|
||
self.bathroom_visits_today = []
|
||
self.stats['total_alerts'] = 0
|
||
|
||
# ==================== 7. 模拟数据生成器(测试用) ====================
|
||
|
||
class EdgeDataSimulator:
|
||
"""边缘端数据模拟器(用于测试)"""
|
||
|
||
def __init__(self, config):
|
||
self.config = config
|
||
|
||
def generate_synthetic_bcg_stream(self, duration_sec, hr=70, scenario='normal'):
|
||
"""
|
||
生成合成BCG数据流
|
||
|
||
参数:
|
||
- duration_sec: 时长(秒)
|
||
- hr: 心率(bpm)
|
||
- scenario: 'normal', 'high_risk', 'bathroom_visit'
|
||
"""
|
||
fs = self.config.bcg_config['sampling_rate']
|
||
total_samples = int(duration_sec * fs)
|
||
|
||
t = np.linspace(0, duration_sec, total_samples)
|
||
|
||
# 基础心跳信号
|
||
hr_freq = hr / 60.0
|
||
signal_bcg = np.sin(2 * np.pi * hr_freq * t)
|
||
|
||
# 呼吸信号
|
||
rr_freq = 15 / 60.0
|
||
signal_bcg += 0.5 * np.sin(2 * np.pi * rr_freq * t)
|
||
|
||
# 根据场景调整
|
||
if scenario == 'high_risk':
|
||
# 高风险:降低HRV
|
||
signal_bcg += 0.05 * np.random.randn(total_samples)
|
||
elif scenario == 'bathroom_visit':
|
||
# 起夜:质量下降
|
||
signal_bcg = signal_bcg * 0.2 + 0.8 * np.random.randn(total_samples)
|
||
else:
|
||
# 正常
|
||
signal_bcg += 0.1 * np.random.randn(total_samples)
|
||
|
||
return signal_bcg, t
|
||
|
||
def generate_synthetic_radar_stream(self, duration_sec, bathroom_visits=[]):
|
||
"""
|
||
生成合成雷达数据流
|
||
|
||
参数:
|
||
- duration_sec: 时长(秒)
|
||
- bathroom_visits: [(start_time, end_time), ...]
|
||
"""
|
||
fs = self.config.radar_config['sampling_rate']
|
||
total_frames = int(duration_sec * fs)
|
||
|
||
radar_frames = []
|
||
|
||
for i in range(total_frames):
|
||
current_time = i / fs
|
||
|
||
# 检查是否在起夜时间段
|
||
in_bathroom = False
|
||
for start, end in bathroom_visits:
|
||
if start <= current_time <= end:
|
||
in_bathroom = True
|
||
break
|
||
|
||
if in_bathroom:
|
||
# 生成厕所区域内的点云
|
||
zone = self.config.radar_config['bathroom_zone']
|
||
num_points = np.random.randint(10, 30)
|
||
point_cloud = []
|
||
for _ in range(num_points):
|
||
x = np.random.uniform(*zone['x_range'])
|
||
y = np.random.uniform(*zone['y_range'])
|
||
z = np.random.uniform(*zone['z_range'])
|
||
v = np.random.uniform(0.1, 0.5) # 有动作
|
||
point_cloud.append((x, y, z, v))
|
||
else:
|
||
# 生成空点云或随机噪声
|
||
point_cloud = []
|
||
|
||
radar_frames.append((current_time, point_cloud))
|
||
|
||
return radar_frames
|
||
|
||
# ==================== 8. 主程序 ====================
|
||
|
||
def run_realtime_simulation(duration_minutes=10):
|
||
"""
|
||
运行实时模拟(测试用)
|
||
|
||
参数:
|
||
- duration_minutes: 模拟时长(分钟)
|
||
"""
|
||
print(f"{'='*70}")
|
||
print(f"🎬 启动边缘端实时模拟")
|
||
print(f"{'='*70}")
|
||
print(f"模拟时长: {duration_minutes} 分钟")
|
||
print(f"场景: 正常睡眠 + 2次起夜")
|
||
print(f"{'='*70}")
|
||
|
||
# 初始化控制器
|
||
controller = RealtimeController(config)
|
||
controller.night_start_time = time.time()
|
||
|
||
# 初始化模拟器
|
||
simulator = EdgeDataSimulator(config)
|
||
|
||
# 定义起夜时间(相对时间,秒)
|
||
bathroom_visits = [
|
||
(120, 180), # 2分钟开始,持续1分钟
|
||
(420, 480) # 7分钟开始,持续1分钟
|
||
]
|
||
|
||
# 生成BCG数据流
|
||
duration_sec = duration_minutes * 60
|
||
bcg_signal, bcg_time = simulator.generate_synthetic_bcg_stream(
|
||
duration_sec, hr=72, scenario='normal'
|
||
)
|
||
|
||
# 生成雷达数据流
|
||
radar_frames = simulator.generate_synthetic_radar_stream(
|
||
duration_sec, bathroom_visits=bathroom_visits
|
||
)
|
||
|
||
print(f"✓ 数据生成完成")
|
||
print(f" BCG样本: {len(bcg_signal)}")
|
||
print(f" 雷达帧: {len(radar_frames)}")
|
||
print(f"开始实时处理...")
|
||
|
||
# 模拟实时处理
|
||
bcg_fs = config.bcg_config['sampling_rate']
|
||
radar_fs = config.radar_config['sampling_rate']
|
||
|
||
bcg_idx = 0
|
||
radar_idx = 0
|
||
start_time = time.time()
|
||
|
||
while bcg_idx < len(bcg_signal):
|
||
current_time = time.time() - start_time
|
||
|
||
# 处理BCG样本(更高频率)
|
||
if bcg_idx < len(bcg_signal):
|
||
sample_time = start_time + bcg_time[bcg_idx]
|
||
controller.process_bcg_sample(bcg_signal[bcg_idx], sample_time)
|
||
bcg_idx += 1
|
||
|
||
# 处理雷达帧(较低频率)
|
||
expected_radar_idx = int(current_time * radar_fs)
|
||
if radar_idx < expected_radar_idx and radar_idx < len(radar_frames):
|
||
frame_time, point_cloud = radar_frames[radar_idx]
|
||
controller.process_radar_frame(point_cloud, start_time + frame_time)
|
||
radar_idx += 1
|
||
|
||
# 模拟实时间隔(加速模拟)
|
||
time.sleep(0.001) # 1ms(实际应该是1/250秒)
|
||
|
||
# 定期上传
|
||
if controller.cloud_uploader.should_upload():
|
||
controller.cloud_uploader.upload()
|
||
|
||
# 夜间总结
|
||
controller.end_of_night_summary()
|
||
|
||
print(f"{'='*70}")
|
||
print(f"✅ 模拟完成")
|
||
print(f"{'='*70}")
|
||
|
||
def main():
|
||
"""主函数"""
|
||
print(f"{'='*70}")
|
||
print(f"🏥 高血压风险评估系统 - 边缘端模块")
|
||
print(f"{'='*70}")
|
||
print(f"版本: {config.version}")
|
||
print(f"设备: {config.device_id}")
|
||
print(f"部署模式: 混合部署(边缘实时 + 云端深度分析)")
|
||
print(f"{'='*70}")
|
||
|
||
print("📋 使用说明:")
|
||
print("1. 本代码运行在RK3588边缘设备")
|
||
print("2. 实时处理BCG和雷达传感器数据")
|
||
print("3. 提供<1秒延迟的实时预警")
|
||
print("4. 定期上传匿名化数据到云端")
|
||
print()
|
||
print("🎮 运行模式:")
|
||
print(" - 模拟模式:使用合成数据测试系统")
|
||
print(" - 实际模式:连接真实传感器(需要硬件接口)")
|
||
print()
|
||
|
||
# 运行模拟
|
||
run_realtime_simulation(duration_minutes=10)
|
||
|
||
if __name__ == "__main__":
|
||
main()
|