Files
sa4cps-frontend/src/stores/sensor.ts
rafaeldpsilva 83eaa7e121 Refactor sensor data handling for new API and WebSocket format
- Update SensorConsumptionTable to use new sensorStore and
websocketStore - Normalize sensor and reading interfaces for consistency
- Remove legacy energy data handling and mapping logic - Update API and
store types for new backend schema - Fetch sensors on mount in
SensorConsumptionTable - Simplify WebSocket data processing and remove
legacy code
2025-09-30 17:58:06 +01:00

269 lines
8.3 KiB
TypeScript

import { defineStore } from 'pinia'
import { ref, reactive } from 'vue'
import {
sensorsApi,
SensorType,
type SensorDevice,
type SensorStatus,
type SensorReading,
} from '@/services'
export const useSensorStore = defineStore('sensor', () => {
// State
const sensorDevices = reactive<Map<string, SensorDevice>>(new Map())
const latestReadings = reactive<Map<string, SensorReading>>(new Map())
const sensorsData = reactive<Map<string, any>>(new Map()) // Legacy support
const recentlyUpdatedSensors = reactive<Set<string>>(new Set()) // Track recently updated sensors
const apiLoading = ref(false)
const apiError = ref<string | null>(null)
// Actions
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) {
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: Sensor) {
console.log(data)
}
function updateLatestReading(reading: SensorReading) {
latestReadings.set(reading.sensor_id, reading)
// 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<T>(apiCall: () => Promise<T>): Promise<T | null> {
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) {
console.log(result)
// Check if result has a sensors property (common API pattern)
if (result.sensors && Array.isArray(result.sensors)) {
result.sensors.forEach((sensor) => {
const sensorKey = sensor._id || sensor.sensor_id
const sensorType = sensor.sensor_type || sensor.type
const sensorName = sensor.name || ''
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)
})
} 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<string, any>) {
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,
apiLoading,
apiError,
// Actions
updateEnergySensors,
updateSensorRoom,
executeSensorAction,
getSensorsByRoom,
getSensorsByType,
updateLatestReading,
// API functions
fetchApiSensors,
fetchApiSensorData,
updateApiSensorMetadata,
deleteApiSensor,
exportApiData,
}
})