import { analyzeHRVData, fetchHRVWithStatus } from '@/utils/health'; import AsyncStorage from '@/utils/kvStore'; import { logger } from '@/utils/logger'; import { convertHrvToStressIndex, getStressLevelInfo, StressLevel } from '@/utils/stress'; import { NativeEventEmitter, NativeModules } from 'react-native'; import { sendHRVStressNotification } from './hrvNotificationService'; const { HealthKitManager } = NativeModules; const HRV_EVENT_NAME = 'hrvUpdate'; const HRV_NOTIFICATION_STATE_KEY = '@hrv_stress_notification_state'; const MIN_NOTIFICATION_INTERVAL_HOURS = 2; interface HrvEventData { timestamp: number; type: 'hrv_data_updated' | string; } interface NotificationState { lastSentAt: number; lastStressLevel: StressLevel; } class HRVMonitorService { private eventEmitter: NativeEventEmitter | null = null; private eventSubscription: any = null; private isInitialized = false; private lastProcessedTime = 0; private debounceDelay = 3000; async initialize(): Promise { if (this.isInitialized) { logger.info('[HRVMonitor] Already initialized'); return true; } try { this.eventEmitter = new NativeEventEmitter(HealthKitManager); this.eventSubscription = this.eventEmitter.addListener( HRV_EVENT_NAME, this.handleHRVUpdate.bind(this) ); if (HealthKitManager.startHRVObserver) { await HealthKitManager.startHRVObserver(); } else { logger.warn('[HRVMonitor] Native startHRVObserver not available'); return false; } this.isInitialized = true; logger.info('[HRVMonitor] Initialized successfully'); return true; } catch (error) { logger.error('[HRVMonitor] Initialization failed:', error); return false; } } async stop(): Promise { if (!this.isInitialized) return; try { if (this.eventSubscription) { this.eventSubscription.remove(); this.eventSubscription = null; } if (HealthKitManager.stopHRVObserver) { await HealthKitManager.stopHRVObserver(); } this.isInitialized = false; logger.info('[HRVMonitor] Stopped successfully'); } catch (error) { logger.error('[HRVMonitor] Failed to stop:', error); } } private async handleHRVUpdate(event: HrvEventData): Promise { logger.info('[HRVMonitor] HRV data updated:', event); const now = Date.now(); if (now - this.lastProcessedTime < this.debounceDelay) { logger.info('[HRVMonitor] Debouncing HRV update'); return; } this.lastProcessedTime = now; try { const canNotify = await this.canSendNotification(now); if (!canNotify) { logger.info('[HRVMonitor] Notification recently sent, skipping'); return; } const { hrvData, message } = await fetchHRVWithStatus(new Date()); if (!hrvData) { logger.warn('[HRVMonitor] No HRV data available for analysis'); return; } const analysis = analyzeHRVData(hrvData); const stressIndex = convertHrvToStressIndex(hrvData.value); if (stressIndex == null) { logger.warn('[HRVMonitor] Unable to derive stress index from HRV value:', hrvData.value); return; } const stressInfo = getStressLevelInfo(stressIndex); await sendHRVStressNotification({ hrvValue: Math.round(hrvData.value), stressIndex, stressLevel: stressInfo.level, recordedAt: hrvData.recordedAt, interpretation: analysis.interpretation, recommendations: analysis.recommendations, dataSource: analysis.dataSource, message, }); await this.markNotificationSent(now, stressInfo.level); } catch (error) { logger.error('[HRVMonitor] Failed to process HRV update:', error); } } private async canSendNotification(now: number): Promise { try { const stored = await AsyncStorage.getItem(HRV_NOTIFICATION_STATE_KEY); if (!stored) { return true; } const state: NotificationState = JSON.parse(stored); const elapsed = now - state.lastSentAt; const minIntervalMs = MIN_NOTIFICATION_INTERVAL_HOURS * 60 * 60 * 1000; // 只检查最小间隔时间(2小时),不再限制每天只能发送一次 if (elapsed < minIntervalMs) { const hoursLeft = ((minIntervalMs - elapsed) / (1000 * 60 * 60)).toFixed(1); logger.info(`[HRVMonitor] Cooldown active, ${hoursLeft}h remaining`); return false; } return true; } catch (error) { logger.warn('[HRVMonitor] Failed to read notification state:', error); return true; } } private async markNotificationSent(timestamp: number, level: StressLevel): Promise { try { const state: NotificationState = { lastSentAt: timestamp, lastStressLevel: level, }; await AsyncStorage.setItem(HRV_NOTIFICATION_STATE_KEY, JSON.stringify(state)); } catch (error) { logger.warn('[HRVMonitor] Failed to persist notification state:', error); } } } export const hrvMonitorService = new HRVMonitorService();