import { defineStore } from 'pinia' import { ref, reactive, computed } from 'vue' import { sensorsApi, SensorType, type SensorDevice, type SensorStatus, type SensorReading, } from '@/services' export const useSensorStore = defineStore('sensor', () => { // State const sensorDevices = reactive>(new Map()) const latestReadings = reactive>(new Map()) const sensorsData = reactive>(new Map()) // Legacy support const recentlyUpdatedSensors = reactive>(new Set()) // Track recently updated sensors const totalReadings = ref(0) // Total number of readings across all sensors const apiLoading = ref(false) const apiError = ref(null) // Computed properties const totalSensors = computed(() => sensorDevices.size) const activeSensors = computed(() => { return Array.from(sensorDevices.values()).filter( (sensor) => sensor.status === 'active' || sensor.status === 'online', ).length }) // Actions function updateSensorRoom(sensorId: string, newRoom: string): void { const sensor = sensorDevices.get(sensorId) if (sensor) { sensor.room = newRoom sensorDevices.set(sensorId, { ...sensor }) } } async function executeSensorAction(sensorId: string, actionId: string): Promise { const sensor = sensorDevices.get(sensorId) if (!sensor) return false const action = sensor.capabilities.actions.find((a) => a.id === actionId) if (!action) return false 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) } function updateEnergySensors(data: SensorReading): void { console.log(data) } function updateLatestReading(reading: SensorReading): void { latestReadings.set(reading.sensor_id, reading) // Increment total readings count totalReadings.value++ // Mark sensor as recently updated recentlyUpdatedSensors.add(reading.sensor_id) // Remove from recently updated after 2 seconds setTimeout(() => { recentlyUpdatedSensors.delete(reading.sensor_id) }, 2000) } // API Integration Functions async function handleApiCall(apiCall: () => Promise): Promise { apiLoading.value = true apiError.value = null try { const result = await apiCall() return result } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' console.error('API call error:', error) console.error('Error message:', errorMessage) if (errorMessage.includes('401') || errorMessage.includes('Authorization')) { console.warn('Authentication error detected, attempting to re-authenticate...') try { const authStore = (window as any).__AUTH_STORE__ if (authStore && typeof authStore.ensureAuthenticated === 'function') { const authSuccess = await authStore.ensureAuthenticated() if (authSuccess) { console.log('Re-authentication successful, retrying API call...') try { const retryResult = await apiCall() return retryResult } catch (retryError) { const retryErrorMessage = retryError instanceof Error ? retryError.message : 'Retry failed' apiError.value = retryErrorMessage console.error('API retry failed:', retryErrorMessage) return null } } } } catch (authError) { console.error('Re-authentication failed:', authError) } } // Check if it's a connection error (backend not running) if ( errorMessage.includes('fetch') || errorMessage.includes('ERR_CONNECTION') || errorMessage.includes('ECONNREFUSED') ) { const backendError = 'Backend server not running on http://localhost:8000. Please start the backend service.' apiError.value = backendError console.error('Connection error - backend not running') return null } apiError.value = errorMessage console.error('API call failed:', errorMessage) return null } finally { apiLoading.value = false } } // Helper function to get default monitoring capabilities based on sensor type function getDefaultMonitoringCapabilities(sensorType: string, sensorName: string): string[] { // First check the sensor name for clues const nameLower = sensorName?.toLowerCase() || '' if (nameLower.includes('energy') || nameLower.includes('power')) { return ['energy'] } if (nameLower.includes('temperature') || nameLower.includes('temp')) { return ['temperature'] } if (nameLower.includes('humidity')) { return ['humidity'] } if (nameLower.includes('co2') || nameLower.includes('air quality')) { return ['co2'] } if (nameLower.includes('motion')) { return ['motion'] } // Then fall back to sensor type switch (sensorType?.toLowerCase()) { case 'energy': return ['energy'] case 'temperature': return ['temperature'] case 'humidity': return ['humidity'] case 'co2': return ['co2'] case 'motion': return ['motion'] case 'hvac': return ['temperature'] case 'lighting': return [] // Lighting sensors don't typically monitor environmental data case 'security': return ['motion'] default: return [] // No default monitoring capabilities } } // Sensors API functions async function fetchApiSensors(params?: { room?: string sensor_type?: SensorType status?: SensorStatus }) { const result = await handleApiCall(() => sensorsApi.getSensors(params)) if (result) { // Check if result has a sensors property (common API pattern) if (result.sensors && Array.isArray(result.sensors)) { let totalReadingsCount: number = 0 result.sensors.forEach((sensor) => { const sensorKey: string = sensor._id || sensor.sensor_id const sensorType: string = sensor.sensor_type || sensor.type const sensorName: string = sensor.name || '' // Accumulate total readings if (sensor.total_readings) { totalReadingsCount += sensor.total_readings } const normalizedSensor = { ...sensor, id: sensorKey, type: sensorType, capabilities: { actions: [], // Default empty actions array monitoring: sensor.capabilities?.monitoring || getDefaultMonitoringCapabilities(sensorType, sensorName), ...sensor.capabilities, }, metadata: { model: sensor.metadata?.model || 'Unknown', firmware: sensor.metadata?.firmware || 'Unknown', location: sensor.metadata?.location || sensor.room || 'Unknown', battery: sensor.metadata?.battery, signalStrength: sensor.metadata?.signalStrength, ...sensor.metadata, }, lastSeen: sensor.last_seen || Date.now() / 1000, } sensorDevices.set(sensorKey, normalizedSensor) }) // Update total readings totalReadings.value = totalReadingsCount } else { console.warn('Unexpected result format:', typeof result, result) } } else { console.error('No result received from API') } return result } async function fetchApiSensorData( sensorId: string, params?: { start_time?: number; end_time?: number; limit?: number; offset?: number }, ) { return handleApiCall(() => sensorsApi.getSensorData(sensorId, params)) } async function updateApiSensorMetadata(sensorId: string, metadata: Record) { return handleApiCall(() => sensorsApi.updateSensorMetadata(sensorId, metadata)) } async function deleteApiSensor(sensorId: string) { return handleApiCall(() => sensorsApi.deleteSensor(sensorId)) } async function exportApiData(params: { start_time: number end_time: number sensor_ids?: string format?: 'json' | 'csv' }) { return handleApiCall(() => sensorsApi.exportData(params)) } return { // State sensorDevices, latestReadings, sensorsData, recentlyUpdatedSensors, totalReadings, apiLoading, apiError, // Computed totalSensors, activeSensors, // Actions updateEnergySensors, updateSensorRoom, executeSensorAction, getSensorsByRoom, getSensorsByType, updateLatestReading, // API functions fetchApiSensors, fetchApiSensorData, updateApiSensorMetadata, deleteApiSensor, exportApiData, } })