dynamic graph
This commit is contained in:
parent
a606796d9e
commit
5e029ff99c
17 changed files with 1707 additions and 569 deletions
207
services/AlarmManager.ts
Normal file
207
services/AlarmManager.ts
Normal file
|
@ -0,0 +1,207 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue