275 lines
No EOL
8.6 KiB
TypeScript
275 lines
No EOL
8.6 KiB
TypeScript
import { create } from 'zustand'
|
|
import { AlarmManager, AlarmEvent } from '@/services/AlarmManager'
|
|
|
|
export interface SensorConfig {
|
|
id: string;
|
|
x: number;
|
|
y: number;
|
|
zone: string;
|
|
label: string;
|
|
pin?: number;
|
|
}
|
|
|
|
export interface SensorData {
|
|
id: string;
|
|
x: number;
|
|
y: number;
|
|
zone: string;
|
|
label: string;
|
|
currentValue: number; // Changed from currentPressure to currentValue (0-4095)
|
|
data: Array<{ time: string; timestamp: number; value: number }>; // Changed from pressure to value
|
|
status: string;
|
|
source?: 'hardware' | 'mock';
|
|
pin?: number;
|
|
digitalState?: number;
|
|
warningThreshold?: number;
|
|
alarmThreshold?: number;
|
|
warningDelayMs?: number;
|
|
}
|
|
|
|
export interface Alert {
|
|
id: string;
|
|
message: string;
|
|
time: string;
|
|
}
|
|
|
|
interface BedPressureStore {
|
|
// State
|
|
sensorData: Record<string, SensorData>;
|
|
sensorConfig: SensorConfig[];
|
|
selectedSensor: string | null;
|
|
isModalOpen: boolean;
|
|
isMonitoring: boolean;
|
|
alerts: Alert[];
|
|
isConnected: boolean;
|
|
selectedTimespan: number; // in milliseconds
|
|
activeAlarms: AlarmEvent[];
|
|
alarmManager: AlarmManager;
|
|
|
|
// Actions
|
|
setSensorData: (data: Record<string, SensorData>) => void;
|
|
updateSensorData: (updater: (prev: Record<string, SensorData>) => Record<string, SensorData>) => void;
|
|
setSensorConfig: (config: SensorConfig[]) => void;
|
|
setSelectedSensor: (sensorId: string | null) => void;
|
|
setIsModalOpen: (isOpen: boolean) => void;
|
|
setIsMonitoring: (isMonitoring: boolean) => void;
|
|
addAlerts: (newAlerts: Alert[]) => void;
|
|
setIsConnected: (isConnected: boolean) => void;
|
|
setSelectedTimespan: (timespan: number) => void;
|
|
setActiveAlarms: (alarms: AlarmEvent[]) => void;
|
|
|
|
// Computed values
|
|
averageValue: () => number; // Changed from averagePressure
|
|
criticalSensors: () => number;
|
|
|
|
// API actions
|
|
fetchSensorConfig: () => Promise<void>;
|
|
fetchSensorData: () => Promise<void>;
|
|
fetchSensorHistory: (sensorId: string, timespan?: number) => Promise<void>;
|
|
|
|
// Alarm actions
|
|
acknowledgeAlarm: (alarmId: string) => void;
|
|
silenceAlarm: (alarmId: string, durationMs?: number) => void;
|
|
silenceAllAlarms: (durationMs?: number) => void;
|
|
}
|
|
|
|
export const useBedPressureStore = create<BedPressureStore>((set, get) => ({
|
|
// Initial state
|
|
sensorData: {},
|
|
sensorConfig: [],
|
|
selectedSensor: null,
|
|
isModalOpen: false,
|
|
isMonitoring: true,
|
|
alerts: [],
|
|
isConnected: false,
|
|
selectedTimespan: 24 * 60 * 60 * 1000, // Default 24 hours
|
|
activeAlarms: [],
|
|
alarmManager: AlarmManager.getInstance(),
|
|
|
|
// Actions
|
|
setSensorData: (data) => set({ sensorData: data }),
|
|
|
|
updateSensorData: (updater) => set((state) => ({
|
|
sensorData: updater(state.sensorData)
|
|
})),
|
|
|
|
setSensorConfig: (config) => set({ sensorConfig: config }),
|
|
|
|
setSelectedSensor: (sensorId) => set({ selectedSensor: sensorId }),
|
|
|
|
setIsModalOpen: (isOpen) => set({ isModalOpen: isOpen }),
|
|
|
|
setIsMonitoring: (isMonitoring) => set({ isMonitoring }),
|
|
|
|
addAlerts: (newAlerts) => set((state) => ({
|
|
alerts: [...newAlerts, ...state.alerts].slice(0, 10)
|
|
})),
|
|
|
|
setIsConnected: (isConnected) => set({ isConnected }),
|
|
|
|
setSelectedTimespan: (timespan) => set({ selectedTimespan: timespan }),
|
|
|
|
setActiveAlarms: (alarms) => set({ activeAlarms: alarms }),
|
|
|
|
// Computed values
|
|
averageValue: () => {
|
|
const { sensorData } = get();
|
|
const sensors = Object.values(sensorData) as SensorData[];
|
|
if (sensors.length === 0) return 0;
|
|
return sensors.reduce((sum: number, sensor: SensorData) => sum + sensor.currentValue, 0) / sensors.length;
|
|
},
|
|
|
|
criticalSensors: () => {
|
|
const { sensorData } = get();
|
|
return (Object.values(sensorData) as SensorData[]).filter((sensor: SensorData) =>
|
|
sensor.status === 'alarm' || sensor.status === 'critical'
|
|
).length;
|
|
},
|
|
|
|
// API actions
|
|
fetchSensorConfig: async () => {
|
|
try {
|
|
const response = await fetch('/api/sensors/config');
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.sensors) {
|
|
set({ sensorConfig: data.sensors });
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to fetch sensor config:', error);
|
|
}
|
|
},
|
|
|
|
fetchSensorData: async () => {
|
|
try {
|
|
const response = await fetch('/api/sensors');
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.sensors) {
|
|
const { sensorData, sensorConfig, alarmManager } = get();
|
|
const updated = { ...sensorData };
|
|
const newAlerts: Alert[] = [];
|
|
|
|
data.sensors.forEach((sensor: {
|
|
id: string;
|
|
label: string;
|
|
zone: string;
|
|
value: number; // Changed from pressure to value
|
|
source: 'hardware' | 'mock';
|
|
pin?: number;
|
|
digitalState?: number;
|
|
warningThreshold?: number;
|
|
alarmThreshold?: number;
|
|
status?: string;
|
|
}) => {
|
|
const currentSensor = updated[sensor.id];
|
|
const newValue = sensor.value;
|
|
|
|
// Check for alarms based on thresholds
|
|
if (sensor.alarmThreshold && newValue >= sensor.alarmThreshold) {
|
|
alarmManager.addAlarm(sensor.id, sensor.label, 'alarm', newValue, sensor.alarmThreshold);
|
|
} else if (sensor.warningThreshold && newValue >= sensor.warningThreshold) {
|
|
alarmManager.addAlarm(sensor.id, sensor.label, 'warning', newValue, sensor.warningThreshold);
|
|
} else {
|
|
alarmManager.clearAlarm(sensor.id);
|
|
}
|
|
|
|
// Check for alerts (legacy alert system)
|
|
if (newValue > 3000 && currentSensor && currentSensor.currentValue <= 3000) {
|
|
newAlerts.push({
|
|
id: `${sensor.id}-${Date.now()}`,
|
|
message: `High value detected at ${sensor.label}`,
|
|
time: new Date().toLocaleTimeString(),
|
|
});
|
|
}
|
|
|
|
// Update sensor data
|
|
updated[sensor.id] = {
|
|
...sensorConfig.find(s => s.id === sensor.id),
|
|
id: sensor.id,
|
|
x: sensorConfig.find(s => s.id === sensor.id)?.x || 50,
|
|
y: sensorConfig.find(s => s.id === sensor.id)?.y || 50,
|
|
zone: sensor.zone,
|
|
label: sensor.label,
|
|
currentValue: newValue,
|
|
data: currentSensor ? [
|
|
...currentSensor.data.slice(1),
|
|
{
|
|
time: new Date().toLocaleTimeString("en-US", { hour12: false }),
|
|
timestamp: Date.now(),
|
|
value: newValue,
|
|
},
|
|
] : [{
|
|
time: new Date().toLocaleTimeString("en-US", { hour12: false }),
|
|
timestamp: Date.now(),
|
|
value: newValue,
|
|
}],
|
|
status: sensor.status || (newValue > 3000 ? "critical" : newValue > 2500 ? "warning" : "normal"),
|
|
source: sensor.source,
|
|
pin: sensor.pin,
|
|
digitalState: sensor.digitalState,
|
|
warningThreshold: sensor.warningThreshold,
|
|
alarmThreshold: sensor.alarmThreshold
|
|
};
|
|
});
|
|
|
|
set({
|
|
sensorData: updated,
|
|
isConnected: data.connected,
|
|
activeAlarms: alarmManager.getActiveAlarms()
|
|
});
|
|
|
|
if (newAlerts.length > 0) {
|
|
get().addAlerts(newAlerts);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to fetch sensor data:', error);
|
|
}
|
|
},
|
|
|
|
fetchSensorHistory: async (sensorId: string, timespan?: number) => {
|
|
try {
|
|
const { selectedTimespan } = get();
|
|
const timespanToUse = timespan || selectedTimespan;
|
|
|
|
const response = await fetch(`/api/sensors/history?sensorId=${sensorId}×pan=${timespanToUse}`);
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.data) {
|
|
const { sensorData } = get();
|
|
const updated = { ...sensorData };
|
|
|
|
if (updated[sensorId]) {
|
|
updated[sensorId] = {
|
|
...updated[sensorId],
|
|
data: data.data
|
|
};
|
|
|
|
set({ sensorData: updated });
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to fetch sensor history:', error);
|
|
}
|
|
},
|
|
|
|
// Alarm actions
|
|
acknowledgeAlarm: (alarmId: string) => {
|
|
const { alarmManager } = get();
|
|
alarmManager.acknowledgeAlarm(alarmId);
|
|
set({ activeAlarms: alarmManager.getActiveAlarms() });
|
|
},
|
|
|
|
silenceAlarm: (alarmId: string, durationMs?: number) => {
|
|
const { alarmManager } = get();
|
|
alarmManager.silenceAlarm(alarmId, durationMs);
|
|
set({ activeAlarms: alarmManager.getActiveAlarms() });
|
|
},
|
|
|
|
silenceAllAlarms: (durationMs?: number) => {
|
|
const { alarmManager } = get();
|
|
alarmManager.silenceAllAlarms(durationMs);
|
|
set({ activeAlarms: alarmManager.getActiveAlarms() });
|
|
}
|
|
})); |