Improve sensor ID mapping and error handling for real-time data

- Add robust mapping from WebSocket sensor IDs to API sensor IDs -
Enhance error handling for backend connection issues - Remove legacy
room metrics summary from SensorManagementView - Add loading and error
states to sensor grid - Track recently updated sensors for UI feedback -
Normalize incoming sensor data for compatibility
This commit is contained in:
rafaeldpsilva
2025-09-29 13:29:15 +01:00
parent 3299472c85
commit 3681890ec5
6 changed files with 327 additions and 318 deletions

View File

@@ -1,8 +1,15 @@
import { defineStore } from 'pinia'
import { ref, reactive } from 'vue'
import { sensorsApi, SensorType, SensorStatus, type SensorDevice, type SensorAction } from '@/services'
import {
sensorsApi,
SensorType,
SensorStatus,
type SensorDevice,
type SensorAction,
} from '@/services'
interface SensorReading {
id: string
sensorId: string
room: string
timestamp: number
@@ -32,6 +39,7 @@ export const useSensorStore = defineStore('sensor', () => {
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)
@@ -93,10 +101,30 @@ export const useSensorStore = defineStore('sensor', () => {
unit: data.unit,
})
}
// Mark sensor as recently updated for legacy data as well
recentlyUpdatedSensors.add(data.sensorId)
// Remove from recently updated after 2 seconds
setTimeout(() => {
recentlyUpdatedSensors.delete(data.sensorId)
}, 2000)
}
function updateLatestReading(reading: SensorReading) {
console.log('Updating latest reading for sensor:', reading.sensorId, reading)
latestReadings.set(reading.sensorId, reading)
// Mark sensor as recently updated
recentlyUpdatedSensors.add(reading.sensorId)
// Remove from recently updated after 2 seconds
setTimeout(() => {
recentlyUpdatedSensors.delete(reading.sensorId)
}, 2000)
console.log('Latest readings now contains:', Array.from(latestReadings.keys()))
}
// API Integration Functions
@@ -104,11 +132,16 @@ export const useSensorStore = defineStore('sensor', () => {
apiLoading.value = true
apiError.value = null
console.log('Making API call...')
try {
const result = await apiCall()
console.log('API call successful:', result)
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...')
@@ -136,6 +169,19 @@ export const useSensorStore = defineStore('sensor', () => {
}
}
// 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
@@ -147,11 +193,79 @@ export const useSensorStore = defineStore('sensor', () => {
// Sensors API functions
async function fetchApiSensors(params?: { room?: string; sensor_type?: any; status?: any }) {
const result = await handleApiCall(() => sensorsApi.getSensors(params))
if (result && Array.isArray(result)) {
result.forEach((sensor) => {
sensorDevices.set(sensor.id, sensor)
})
if (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._id || sensor.sensor_id
// Normalize sensor data structure for frontend compatibility
const normalizedSensor = {
...sensor,
id: sensorKey,
type: sensor.sensor_type || sensor.type,
capabilities: {
actions: [], // Default empty actions array
monitoring: sensor.capabilities?.monitoring || ['energy'], // Default monitoring capability
...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,
},
tags: sensor.tags || [],
lastSeen: sensor.last_seen || sensor.lastSeen || Date.now() / 1000,
}
sensorDevices.set(sensorKey, normalizedSensor)
})
}
// Check if result is directly an array
else if (Array.isArray(result)) {
console.log('Result is direct array:', result)
result.forEach((sensor) => {
console.log('Adding sensor:', sensor)
const sensorKey = sensor.id || sensor._id || sensor.sensor_id
// Normalize sensor data structure for frontend compatibility
const normalizedSensor = {
...sensor,
id: sensorKey,
type: sensor.sensor_type || sensor.type,
capabilities: {
actions: [], // Default empty actions array
monitoring: sensor.capabilities?.monitoring || ['energy'], // Default monitoring capability
...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,
},
tags: sensor.tags || [],
lastSeen: sensor.last_seen || sensor.lastSeen || Date.now() / 1000,
}
sensorDevices.set(sensorKey, normalizedSensor)
})
}
// Log what we actually got
else {
console.log('Unexpected result format:', typeof result, result)
}
} else {
console.log('No result received from API')
}
console.log('Current sensor devices:', Array.from(sensorDevices.entries()))
return result
}
@@ -179,161 +293,12 @@ export const useSensorStore = defineStore('sensor', () => {
return handleApiCall(() => sensorsApi.exportData(params))
}
// Initialize mock sensor devices
function initializeMockSensors() {
const mockSensors: SensorDevice[] = [
{
id: 'sensor_1',
sensor_id: 'sensor_1',
name: 'Energy Monitor 1',
type: 'energy',
sensor_type: 'energy',
room: 'Conference Room A',
status: 'online',
lastSeen: Date.now() / 1000,
total_readings: 1250,
capabilities: {
monitoring: ['energy'],
actions: [],
},
metadata: {
location: 'Wall mounted',
model: 'EM-100',
firmware: '2.1.0',
},
} as SensorDevice,
{
id: 'sensor_2',
sensor_id: 'sensor_2',
name: 'HVAC Controller 1',
type: 'hvac',
sensor_type: 'hvac',
room: 'Conference Room A',
status: 'online',
lastSeen: Date.now() / 1000,
total_readings: 890,
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',
},
} as SensorDevice,
{
id: 'sensor_3',
sensor_id: 'sensor_3',
name: 'Smart Light Controller',
type: 'lighting',
sensor_type: 'lighting',
room: 'Office Floor 1',
status: 'online',
lastSeen: Date.now() / 1000,
total_readings: 2100,
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',
},
} as SensorDevice,
{
id: 'sensor_4',
sensor_id: 'sensor_4',
name: 'CO2 Sensor',
type: 'co2',
sensor_type: 'co2',
room: 'Meeting Room 1',
status: 'online',
lastSeen: Date.now() / 1000,
total_readings: 1580,
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,
},
} as SensorDevice,
{
id: 'sensor_5',
sensor_id: 'sensor_5',
name: 'Security Camera',
type: 'security',
sensor_type: 'security',
room: 'Lobby',
status: 'online',
lastSeen: Date.now() / 1000,
total_readings: 945,
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',
},
} as SensorDevice,
]
mockSensors.forEach((sensor) => {
sensorDevices.set(sensor.id, sensor)
})
}
// Initialize on store creation
initializeMockSensors()
return {
// State
sensorDevices,
latestReadings,
sensorsData,
recentlyUpdatedSensors,
apiLoading,
apiError,
@@ -352,4 +317,4 @@ export const useSensorStore = defineStore('sensor', () => {
deleteApiSensor,
exportApiData,
}
})
})