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:
@@ -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,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user