m2-inno-bedpressure/services/AlarmManager.ts
2025-06-21 12:55:27 +07:00

207 lines
No EOL
5.9 KiB
TypeScript

export interface AlarmEvent {
id: string;
sensorId: string;
sensorLabel: string;
type: 'warning' | 'alarm';
value: number;
threshold: number;
timestamp: number;
time: string;
acknowledged: boolean;
silenced: boolean;
silencedUntil?: number;
}
export class AlarmManager {
private static instance: AlarmManager;
private activeAlarms: Map<string, AlarmEvent> = new Map();
private alarmHistory: AlarmEvent[] = [];
private alarmCallbacks: ((alarm: AlarmEvent) => void)[] = [];
private audioContext: AudioContext | null = null;
private alarmSound: AudioBuffer | null = null;
private isPlayingAlarm = false;
private constructor() {
this.initializeAudio();
}
static getInstance(): AlarmManager {
if (!AlarmManager.instance) {
AlarmManager.instance = new AlarmManager();
}
return AlarmManager.instance;
}
private async initializeAudio() {
try {
// Check if we're in browser environment
if (typeof window !== 'undefined') {
this.audioContext = new (window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext)();
// Generate alarm sound programmatically
await this.generateAlarmSound();
}
} catch (error) {
console.warn('Audio context not available:', error);
}
}
private async generateAlarmSound() {
if (!this.audioContext) return;
const sampleRate = this.audioContext.sampleRate;
const duration = 1; // 1 second
const buffer = this.audioContext.createBuffer(1, sampleRate * duration, sampleRate);
const data = buffer.getChannelData(0);
// Generate a beeping sound (sine wave with modulation)
for (let i = 0; i < data.length; i++) {
const t = i / sampleRate;
const frequency = 800; // Hz
const envelope = Math.sin(t * Math.PI * 4) > 0 ? 1 : 0; // Beeping pattern
data[i] = Math.sin(2 * Math.PI * frequency * t) * envelope * 0.3;
}
this.alarmSound = buffer;
}
private async playAlarmSound() {
if (!this.audioContext || !this.alarmSound || this.isPlayingAlarm) return;
try {
// Resume audio context if suspended
if (this.audioContext.state === 'suspended') {
await this.audioContext.resume();
}
const source = this.audioContext.createBufferSource();
source.buffer = this.alarmSound;
source.connect(this.audioContext.destination);
source.start();
this.isPlayingAlarm = true;
source.onended = () => {
this.isPlayingAlarm = false;
};
} catch (error) {
console.warn('Failed to play alarm sound:', error);
}
}
addAlarm(sensorId: string, sensorLabel: string, type: 'warning' | 'alarm', value: number, threshold: number) {
const alarmId = `${sensorId}-${type}`;
const timestamp = Date.now();
const alarm: AlarmEvent = {
id: alarmId,
sensorId,
sensorLabel,
type,
value,
threshold,
timestamp,
time: new Date(timestamp).toLocaleTimeString(),
acknowledged: false,
silenced: false
};
// Check if alarm is already active and silenced
const existingAlarm = this.activeAlarms.get(alarmId);
if (existingAlarm?.silenced && existingAlarm.silencedUntil && timestamp < existingAlarm.silencedUntil) {
// Update values but keep silenced state
alarm.silenced = true;
alarm.silencedUntil = existingAlarm.silencedUntil;
}
this.activeAlarms.set(alarmId, alarm);
this.alarmHistory.unshift(alarm);
// Keep only last 100 history items
if (this.alarmHistory.length > 100) {
this.alarmHistory = this.alarmHistory.slice(0, 100);
}
// Play sound for new alarms
if (type === 'alarm' && !alarm.silenced) {
this.playAlarmSound();
}
// Notify callbacks
this.alarmCallbacks.forEach(callback => callback(alarm));
return alarm;
}
clearAlarm(sensorId: string, type?: 'warning' | 'alarm') {
if (type) {
const alarmId = `${sensorId}-${type}`;
this.activeAlarms.delete(alarmId);
} else {
// Clear all alarms for this sensor
this.activeAlarms.delete(`${sensorId}-warning`);
this.activeAlarms.delete(`${sensorId}-alarm`);
}
}
acknowledgeAlarm(alarmId: string) {
const alarm = this.activeAlarms.get(alarmId);
if (alarm) {
alarm.acknowledged = true;
this.activeAlarms.set(alarmId, alarm);
}
}
silenceAlarm(alarmId: string, durationMs: number = 300000) { // Default 5 minutes
const alarm = this.activeAlarms.get(alarmId);
if (alarm) {
alarm.silenced = true;
alarm.silencedUntil = Date.now() + durationMs;
this.activeAlarms.set(alarmId, alarm);
}
}
silenceAllAlarms(durationMs: number = 300000) {
const timestamp = Date.now();
this.activeAlarms.forEach((alarm, alarmId) => {
alarm.silenced = true;
alarm.silencedUntil = timestamp + durationMs;
this.activeAlarms.set(alarmId, alarm);
});
}
getActiveAlarms(): AlarmEvent[] {
const now = Date.now();
const activeAlarms: AlarmEvent[] = [];
this.activeAlarms.forEach((alarm, alarmId) => {
// Check if silence period has expired
if (alarm.silenced && alarm.silencedUntil && now >= alarm.silencedUntil) {
alarm.silenced = false;
alarm.silencedUntil = undefined;
this.activeAlarms.set(alarmId, alarm);
}
activeAlarms.push(alarm);
});
return activeAlarms.sort((a, b) => b.timestamp - a.timestamp);
}
getAlarmHistory(): AlarmEvent[] {
return this.alarmHistory;
}
hasUnacknowledgedAlarms(): boolean {
return Array.from(this.activeAlarms.values()).some(alarm => !alarm.acknowledged);
}
onAlarm(callback: (alarm: AlarmEvent) => void) {
this.alarmCallbacks.push(callback);
}
offAlarm(callback: (alarm: AlarmEvent) => void) {
const index = this.alarmCallbacks.indexOf(callback);
if (index > -1) {
this.alarmCallbacks.splice(index, 1);
}
}
}