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 } interface SensorDevice { id: string name: string type: 'energy' | 'co2' | 'temperature' | 'humidity' | 'hvac' | 'lighting' | 'security' room: string status: 'online' | 'offline' | 'error' lastSeen: number capabilities: { monitoring: string[] // e.g., ['energy', 'temperature'] actions: SensorAction[] // Available actions } metadata: { location: string model?: string firmware?: string battery?: number } } interface SensorAction { id: string name: string type: 'toggle' | 'adjust' | 'trigger' icon: string parameters?: { min?: number max?: number step?: number options?: 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()) const sensorDevices = reactive>(new Map()) const availableRooms = ref([ 'Conference Room A', 'Conference Room B', 'Office Floor 1', 'Office Floor 2', 'Kitchen', 'Lobby', 'Server Room', 'Storage Room', 'Meeting Room 1', 'Meeting Room 2' ]) 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' } // Initialize mock sensor devices function initializeMockSensors() { const mockSensors: SensorDevice[] = [ { id: 'sensor_1', name: 'Energy Monitor 1', type: 'energy', room: 'Conference Room A', status: 'online', lastSeen: Date.now() / 1000, capabilities: { monitoring: ['energy'], actions: [] }, metadata: { location: 'Wall mounted', model: 'EM-100', firmware: '2.1.0' } }, { id: 'sensor_2', name: 'HVAC Controller 1', type: 'hvac', room: 'Conference Room A', status: 'online', lastSeen: Date.now() / 1000, capabilities: { monitoring: ['temperature', 'co2'], actions: [ { id: 'temp_adjust', name: 'Adjust Temperature', type: 'adjust', icon: '🌡️', parameters: { min: 18, max: 28, step: 0.5 } }, { id: 'fan_speed', name: 'Fan Speed', type: 'adjust', icon: '💨', parameters: { min: 0, max: 5, step: 1 } }, { id: 'power_toggle', name: 'Power', type: 'toggle', icon: '⚡' } ] }, metadata: { location: 'Ceiling mounted', model: 'HVAC-200', firmware: '3.2.1' } }, { id: 'sensor_3', name: 'Smart Light Controller', type: 'lighting', room: 'Office Floor 1', status: 'online', lastSeen: Date.now() / 1000, capabilities: { monitoring: ['energy'], actions: [ { id: 'brightness', name: 'Brightness', type: 'adjust', icon: '💡', parameters: { min: 0, max: 100, step: 5 } }, { id: 'power_toggle', name: 'Power', type: 'toggle', icon: '⚡' }, { id: 'scene', name: 'Scene', type: 'adjust', icon: '🎨', parameters: { options: ['Work', 'Meeting', 'Presentation', 'Relax'] } } ] }, metadata: { location: 'Ceiling grid', model: 'SL-300', firmware: '1.5.2' } }, { id: 'sensor_4', name: 'CO2 Sensor', type: 'co2', room: 'Meeting Room 1', status: 'online', lastSeen: Date.now() / 1000, capabilities: { monitoring: ['co2', 'temperature', 'humidity'], actions: [ { id: 'calibrate', name: 'Calibrate', type: 'trigger', icon: '⚙️' } ] }, metadata: { location: 'Wall mounted', model: 'CO2-150', firmware: '2.0.3', battery: 85 } }, { id: 'sensor_5', name: 'Security Camera', type: 'security', room: 'Lobby', status: 'online', lastSeen: Date.now() / 1000, capabilities: { monitoring: ['motion'], actions: [ { id: 'record_toggle', name: 'Recording', type: 'toggle', icon: '📹' }, { id: 'ptz_control', name: 'Pan/Tilt/Zoom', type: 'trigger', icon: '🎥' }, { id: 'night_mode', name: 'Night Mode', type: 'toggle', icon: '🌙' } ] }, metadata: { location: 'Corner ceiling', model: 'SEC-400', firmware: '4.1.0' } } ] mockSensors.forEach(sensor => { sensorDevices.set(sensor.id, sensor) }) } // Sensor management functions function updateSensorRoom(sensorId: string, newRoom: string) { const sensor = sensorDevices.get(sensorId) if (sensor) { sensor.room = newRoom sensorDevices.set(sensorId, { ...sensor }) } } async function executeSensorAction(sensorId: string, actionId: string, parameters?: any) { const sensor = sensorDevices.get(sensorId) if (!sensor) return false const action = sensor.capabilities.actions.find(a => a.id === actionId) if (!action) return false // Simulate API call to device console.log(`Executing action ${actionId} on sensor ${sensorId}`, parameters) // Here you would make the actual API call to control the device // For now, we'll simulate a successful action return new Promise((resolve) => { setTimeout(() => { console.log(`Action ${action.name} executed successfully on ${sensor.name}`) resolve(true) }, 1000) }) } function getSensorsByRoom(room: string): SensorDevice[] { return Array.from(sensorDevices.values()).filter(sensor => sensor.room === room) } function getSensorsByType(type: SensorDevice['type']): SensorDevice[] { return Array.from(sensorDevices.values()).filter(sensor => sensor.type === type) } // Initialize mock sensors on store creation initializeMockSensors() return { isConnected, latestMessage, timeSeriesData, sensorsData, roomsData, latestReadings, sensorDevices, availableRooms, connect, disconnect, getCO2Status, updateSensorRoom, executeSensorAction, getSensorsByRoom, getSensorsByType } })