import { defineStore } from 'pinia' import { ref, reactive } from 'vue' const MAX_DATA_POINTS = 100 // Keep the last 100 data points for the chart interface SensorReading { sensorId: string room: string timestamp: number energy: { value: number unit: string } co2: { value: number unit: string } temperature?: { value: number unit: string } } interface RoomMetrics { room: string sensors: string[] energy: { current: number total: number average: number unit: string } co2: { current: number average: number max: number status: 'good' | 'moderate' | 'poor' | 'critical' unit: string } occupancyEstimate: 'low' | 'medium' | 'high' lastUpdated: number } interface LegacyEnergyData { sensorId: string timestamp: number value: number unit: string } export const useEnergyStore = defineStore('energy', () => { // State const isConnected = ref(false) const latestMessage = ref(null) const timeSeriesData = reactive<{ labels: string[] datasets: { data: number[] }[] }>({ labels: [], datasets: [{ data: [] }], }) const sensorsData = reactive>(new Map()) // Legacy support const roomsData = reactive>(new Map()) const latestReadings = reactive>(new Map()) let socket: WebSocket | null = null const newDataBuffer: (LegacyEnergyData | SensorReading)[] = [] // Actions function connect(url: string) { if (isConnected.value) { console.log('Already connected.') return } console.log(`Connecting to WebSocket at ${url}`) socket = new WebSocket(url) socket.onopen = () => { console.log('WebSocket connection established.') isConnected.value = true } socket.onmessage = (event) => { try { const data = JSON.parse(event.data) newDataBuffer.push(data) } catch (error) { console.error('Error parsing incoming data:', error) } } socket.onclose = () => { console.log('WebSocket connection closed.') isConnected.value = false socket = null } socket.onerror = (error) => { console.error('WebSocket error:', error) isConnected.value = false socket = null } // Process the buffer at intervals setInterval(() => { if (newDataBuffer.length > 0) { const data = newDataBuffer.shift() // Get the oldest data point if (data) { // Handle both legacy and new data formats if (isLegacyData(data)) { latestMessage.value = data updateSensorData(data) // Update time series for chart const newLabel = new Date(data.timestamp * 1000).toLocaleTimeString() timeSeriesData.labels.push(newLabel) timeSeriesData.datasets[0].data.push(data.value) } else { // Handle new multi-metric data updateRoomData(data) // Update time series for chart (use energy values) const newLabel = new Date(data.timestamp * 1000).toLocaleTimeString() timeSeriesData.labels.push(newLabel) timeSeriesData.datasets[0].data.push(data.energy.value) } if (timeSeriesData.labels.length > MAX_DATA_POINTS) { timeSeriesData.labels.shift() timeSeriesData.datasets[0].data.shift() } } } }, 500) // Process every 500ms } function disconnect() { if (socket) { socket.close() } } function isLegacyData(data: any): data is LegacyEnergyData { return 'value' in data && !('energy' in data) } function updateSensorData(data: LegacyEnergyData) { const existingSensor = sensorsData.get(data.sensorId) if (existingSensor) { // Update existing sensor const newTotal = existingSensor.totalConsumption + data.value const dataPoints = Math.floor((data.timestamp - existingSensor.lastUpdated) / 60) + 1 // Rough estimate sensorsData.set(data.sensorId, { ...existingSensor, latestValue: data.value, totalConsumption: newTotal, averageConsumption: newTotal / dataPoints, lastUpdated: data.timestamp }) } else { // Create new sensor entry sensorsData.set(data.sensorId, { sensorId: data.sensorId, latestValue: data.value, totalConsumption: data.value, averageConsumption: data.value, lastUpdated: data.timestamp, unit: data.unit }) } } function updateRoomData(data: SensorReading) { // Store latest reading latestReadings.set(data.sensorId, data) // Get or create room metrics let roomMetrics = roomsData.get(data.room) if (!roomMetrics) { roomMetrics = { room: data.room, sensors: [data.sensorId], energy: { current: 0, total: 0, average: 0, unit: data.energy.unit }, co2: { current: 0, average: 0, max: 0, status: 'good', unit: data.co2.unit }, occupancyEstimate: 'low', lastUpdated: data.timestamp } roomsData.set(data.room, roomMetrics) } // Update room sensors list if (!roomMetrics.sensors.includes(data.sensorId)) { roomMetrics.sensors.push(data.sensorId) } // Recalculate room metrics from all sensors in the room const roomSensors = Array.from(latestReadings.values()).filter(reading => reading.room === data.room) // Energy calculations roomMetrics.energy.current = roomSensors.reduce((sum, sensor) => sum + sensor.energy.value, 0) roomMetrics.energy.total += data.energy.value // Accumulate total roomMetrics.energy.average = roomMetrics.energy.total / roomSensors.length // CO2 calculations const co2Values = roomSensors.map(sensor => sensor.co2.value) roomMetrics.co2.current = co2Values.reduce((sum, val) => sum + val, 0) / co2Values.length roomMetrics.co2.max = Math.max(roomMetrics.co2.max, ...co2Values) roomMetrics.co2.average = (roomMetrics.co2.average + roomMetrics.co2.current) / 2 // CO2 status classification if (roomMetrics.co2.current < 400) roomMetrics.co2.status = 'good' else if (roomMetrics.co2.current < 1000) roomMetrics.co2.status = 'moderate' else if (roomMetrics.co2.current < 5000) roomMetrics.co2.status = 'poor' else roomMetrics.co2.status = 'critical' // Occupancy estimate based on CO2 levels if (roomMetrics.co2.current < 600) roomMetrics.occupancyEstimate = 'low' else if (roomMetrics.co2.current < 1200) roomMetrics.occupancyEstimate = 'medium' else roomMetrics.occupancyEstimate = 'high' roomMetrics.lastUpdated = data.timestamp } function getCO2Status(ppm: number): 'good' | 'moderate' | 'poor' | 'critical' { if (ppm < 400) return 'good' if (ppm < 1000) return 'moderate' if (ppm < 5000) return 'poor' return 'critical' } return { isConnected, latestMessage, timeSeriesData, sensorsData, roomsData, latestReadings, connect, disconnect, getCO2Status } })