diff --git a/src/components/cards/DetailedSensorCard.vue b/src/components/cards/DetailedSensorCard.vue index 64d8b46..46b512b 100644 --- a/src/components/cards/DetailedSensorCard.vue +++ b/src/components/cards/DetailedSensorCard.vue @@ -179,9 +179,11 @@ const energyStore = useEnergyStore() const getSensorValues = (sensor: any) => { const values = [] - + // Get real-time sensor reading from store const latestReading = energyStore.latestReadings.get(sensor.id) + console.log(`[Detailed] Getting values for sensor ${sensor.id}, found reading:`, latestReading) + console.log('[Detailed] Available readings:', Array.from(energyStore.latestReadings.keys())) if (sensor.capabilities.monitoring.includes('energy')) { const energyValue = latestReading?.energy?.value?.toFixed(2) || diff --git a/src/components/cards/SimpleSensorCard.vue b/src/components/cards/SimpleSensorCard.vue index 5820e85..e7acb34 100644 --- a/src/components/cards/SimpleSensorCard.vue +++ b/src/components/cards/SimpleSensorCard.vue @@ -11,13 +11,9 @@

{{ sensor.room || 'Unassigned' }}

-
-
+
{{ sensor.status }}
@@ -25,8 +21,7 @@
-
+
{{ metric.label }}
{{ metric.value }} {{ metric.unit }} @@ -50,7 +45,7 @@ {{ action.icon }} {{ action.name }} - +
\ No newline at end of file + diff --git a/src/stores/room.ts b/src/stores/room.ts index ef082cf..9d1bb36 100644 --- a/src/stores/room.ts +++ b/src/stores/room.ts @@ -55,6 +55,12 @@ export const useRoomStore = defineStore('room', () => { function updateRoomData(data: SensorReading) { const sensorStore = useSensorStore() + // Validate data structure and provide fallbacks + if (!data.energy || !data.co2) { + console.warn('Invalid sensor reading data, missing energy or co2 properties:', data) + return + } + // Store latest reading in sensor store sensorStore.updateLatestReading(data) @@ -65,8 +71,8 @@ export const useRoomStore = defineStore('room', () => { 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 }, + energy: { current: 0, total: 0, average: 0, unit: data.energy?.unit || 'kWh' }, + co2: { current: 0, average: 0, max: 0, status: 'good', unit: data.co2?.unit || 'ppm' }, occupancyEstimate: 'low', lastUpdated: data.timestamp, } diff --git a/src/stores/sensor.ts b/src/stores/sensor.ts index 82f1b19..aa435eb 100644 --- a/src/stores/sensor.ts +++ b/src/stores/sensor.ts @@ -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>(new Map()) const latestReadings = reactive>(new Map()) const sensorsData = reactive>(new Map()) // Legacy support + const recentlyUpdatedSensors = reactive>(new Set()) // Track recently updated sensors const apiLoading = ref(false) const apiError = ref(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, } -}) \ No newline at end of file +}) diff --git a/src/stores/websocket.ts b/src/stores/websocket.ts index 37c2bc1..421756d 100644 --- a/src/stores/websocket.ts +++ b/src/stores/websocket.ts @@ -72,20 +72,17 @@ export const useWebSocketStore = defineStore('websocket', () => { socket.onmessage = (event) => { try { const data = JSON.parse(event.data) + console.log('WebSocket received data:', data) - // Handle proxy info message from API Gateway if (data.type === 'proxy_info' && data.sensor_service_url) { console.log('Received proxy info, reconnecting to sensor service...') - // Close current connection gracefully if (socket) { - socket.onclose = null // Prevent triggering disconnect handlers + socket.onclose = null socket.close() socket = null } - // Set disconnected state temporarily isConnected.value = false - // Connect directly to sensor service after a short delay setTimeout(() => { console.log('Connecting directly to sensor service at ws://localhost:8007/ws') connect('ws://localhost:8007/ws') @@ -113,7 +110,6 @@ export const useWebSocketStore = defineStore('websocket', () => { } } - // Process the buffer at intervals setInterval(() => { if (newDataBuffer.length > 0) { const data = newDataBuffer.shift() @@ -131,19 +127,107 @@ export const useWebSocketStore = defineStore('websocket', () => { } function isLegacyData(data: any): data is LegacyEnergyData { - return 'value' in data && !('energy' in data) + return 'value' in data && !('energy' in data) && !('co2' in data) + } + + function mapWebSocketSensorIdd(webSocketSensorId: string): string { + const sensorStore = useSensorStore() + + // First try exact match + if (sensorStore.sensorDevices.has(webSocketSensorId)) { + return webSocketSensorId + } + + console.log(`Attempting to map WebSocket sensor ID: ${webSocketSensorId}`) + console.log( + 'Available API sensors:', + Array.from(sensorStore.sensorDevices.entries()).map(([id, sensor]) => ({ + id, + name: sensor.name, + room: sensor.room, + })), + ) + + // Try to find a sensor by matching room and type patterns + const sensors = Array.from(sensorStore.sensorDevices.entries()) + + // Pattern matching for common WebSocket ID formats + for (const [apiSensorId, sensor] of sensors) { + const sensorName = sensor.name.toLowerCase() + const sensorRoom = sensor.room?.toLowerCase() || '' + const wsId = webSocketSensorId.toLowerCase() + + console.log(`Checking sensor: ${sensor.name} (${sensorRoom}) against ${webSocketSensorId}`) + + // Room-based matching (more comprehensive) + if (wsId.includes('living') && sensorName.includes('living')) return apiSensorId + if (wsId.includes('lr') && sensorName.includes('living')) return apiSensorId + if (wsId.includes('bt') && sensorName.includes('bathroom')) return apiSensorId // bt = bathroom + if (wsId.includes('bathroom') && sensorName.includes('bathroom')) return apiSensorId + if (wsId.includes('br') && sensorName.includes('bedroom')) return apiSensorId // br = bedroom + if (wsId.includes('bedroom') && sensorName.includes('bedroom')) return apiSensorId + if (wsId.includes('kt') && sensorName.includes('kitchen')) return apiSensorId // kt = kitchen + if (wsId.includes('kitchen') && sensorName.includes('kitchen')) return apiSensorId + if (wsId.includes('gr') && sensorName.includes('garage')) return apiSensorId // gr = garage + if (wsId.includes('garage') && sensorName.includes('garage')) return apiSensorId + + // Type-based matching + if (wsId.includes('energy') && sensorName.includes('energy')) return apiSensorId + if (wsId.includes('co2') && sensorName.includes('co2')) return apiSensorId + if (wsId.includes('temp') && sensorName.includes('temp')) return apiSensorId + if (wsId.includes('humidity') && sensorName.includes('humidity')) return apiSensorId + + // Combined room + type matching for better accuracy + if ( + wsId.includes('bathroom') && + wsId.includes('humidity') && + sensorName.includes('bathroom') && + sensorName.includes('humidity') + ) + return apiSensorId + if ( + wsId.includes('bedroom') && + wsId.includes('temp') && + sensorName.includes('bedroom') && + sensorName.includes('temp') + ) + return apiSensorId + if ( + wsId.includes('kitchen') && + wsId.includes('humidity') && + sensorName.includes('kitchen') && + sensorName.includes('humidity') + ) + return apiSensorId + } + + // If no mapping found, let's try to use the first sensor as a fallback for testing + if (sensors.length > 0) { + const fallbackSensor = sensors[0] + console.warn( + `No mapping found for ${webSocketSensorId}, using fallback sensor: ${fallbackSensor[1].name}`, + ) + return fallbackSensor[0] + } + + console.warn(`Could not map WebSocket sensor ID ${webSocketSensorId} to any API sensor`) + return webSocketSensorId // Return original if no mapping found } function processIncomingData(data: LegacyEnergyData | SensorReading) { // Skip non-data messages - if ( - 'type' in data && - (data.type === 'connection_established' || data.type === 'proxy_info') - ) { + if ('type' in data && (data.type === 'connection_established' || data.type === 'proxy_info')) { console.log('Received system message:', data.type) return } + console.log('Processing incoming data:', data) + + // Normalize property names: sensor_id -> sensorId + if ('sensor_id' in data && !('sensorId' in data)) { + data.sensorId = data.sensor_id + } + const sensorStore = useSensorStore() const roomStore = useRoomStore() @@ -152,18 +236,44 @@ export const useWebSocketStore = defineStore('websocket', () => { latestMessage.value = data sensorStore.updateSensorData(data) + // Convert legacy data to SensorReading format for individual sensor updates + const mappedSensorId = mapWebSocketSensorId(data.sensorId) + const sensorReading = { + sensorId: mappedSensorId, + room: 'Unknown', // Legacy data doesn't include room info + timestamp: data.timestamp, + energy: { + value: data.value, + unit: data.unit, + }, + co2: { + value: 400, // Default CO2 value for legacy data + unit: 'ppm', + }, + } + console.log(`Mapped WebSocket sensor ID ${data.sensorId} to ${mappedSensorId}`) + sensorStore.updateLatestReading(sensorReading) + // 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 - roomStore.updateRoomData(data) + // Only update room data if we have the proper structure + if (data.energy && data.co2 && data.room) { + roomStore.updateRoomData(data) + } - // Update time series for chart (use energy values) + // Map the sensor ID for individual sensor updates + // const mappedSensorId = mapWebSocketSensorId(data.sensorId) + const mappedData = { ...data, sensorId: data.sensorId, id: data.sensorId } + sensorStore.updateLatestReading(mappedData) // Update individual sensor readings for cards + + // Update time series for chart (use energy values if available) const newLabel = new Date(data.timestamp * 1000).toLocaleTimeString() timeSeriesData.labels.push(newLabel) - timeSeriesData.datasets[0].data.push(data.energy.value) + timeSeriesData.datasets[0].data.push(data.energy?.value || 0) } // Keep only the latest data points @@ -183,4 +293,4 @@ export const useWebSocketStore = defineStore('websocket', () => { connect, disconnect, } -}) \ No newline at end of file +}) diff --git a/src/views/SensorManagementView.vue b/src/views/SensorManagementView.vue index 4cc88bd..a962b55 100644 --- a/src/views/SensorManagementView.vue +++ b/src/views/SensorManagementView.vue @@ -118,44 +118,6 @@
- -
-

Room-based Real-time Metrics

-
-
-
{{ room }}
-
-
- Energy: - {{ metrics.energy.toFixed(2) }} kWh -
-
- CO2: - - {{ Math.round(metrics.co2) }} ppm - -
-
- Sensors: - {{ metrics.sensorCount }} -
-
- Occupancy: - - {{ metrics.occupancy }} - -
-
-
-
-
+ +
+
+

Loading sensors...

+

Fetching sensor data from the backend

+
+ + +
+
⚠️
+

Error loading sensors

+

{{ energyStore.apiError }}

+ +
+ -
+
🔍

No sensors found

Try adjusting your filters or check if sensors are connected.

@@ -241,7 +223,7 @@ const isExecutingAction = ref(false) const showRoomManagementModal = ref(false) const sensorList = computed(() => { - console.log(energyStore.sensorDevices) + console.log('Sensors from store:', energyStore.sensorDevices) return Array.from(energyStore.sensorDevices.values()).sort((a, b) => a.name.localeCompare(b.name)) }) @@ -255,53 +237,6 @@ const filteredSensors = computed(() => { }) }) -// Real-time room metrics aggregation -const roomMetricsSummary = computed(() => { - const summary: Record = {} - - // Process room data from energy store - Array.from(energyStore.roomsData.values()).forEach((roomData) => { - if (roomData.room && roomData.sensors.length > 0) { - summary[roomData.room] = { - energy: roomData.energy.current || 0, - co2: roomData.co2.current || 0, - sensorCount: roomData.sensors.length, - occupancy: roomData.occupancyEstimate || 'low', - lastUpdated: roomData.lastUpdated, - } - } - }) - - // Fallback: Aggregate from individual sensor readings - if (Object.keys(summary).length === 0) { - const readings = Array.from(energyStore.latestReadings.values()) - readings.forEach((reading) => { - if (!reading.room) return - - if (!summary[reading.room]) { - summary[reading.room] = { - energy: 0, - co2: 0, - temperature: 0, - sensorCount: 0, - occupancy: 'low', - } - } - - summary[reading.room].energy += reading.energy?.value || 0 - summary[reading.room].co2 += reading.co2?.value || 0 - summary[reading.room].sensorCount += 1 - - // Simple occupancy estimate based on CO2 - const avgCo2 = summary[reading.room].co2 / summary[reading.room].sensorCount - if (avgCo2 > 1200) summary[reading.room].occupancy = 'high' - else if (avgCo2 > 600) summary[reading.room].occupancy = 'medium' - else summary[reading.room].occupancy = 'low' - }) - } - - return summary -}) const updateRoom = (sensorId: string, newRoom: string) => { energyStore.updateSensorRoom(sensorId, newRoom) @@ -342,25 +277,10 @@ const showSensorDetails = () => { viewMode.value = 'detailed' } -// Helper functions for styling -const getCO2StatusColor = (co2Level: number) => { - if (co2Level < 400) return 'text-green-600' - if (co2Level < 1000) return 'text-yellow-600' - if (co2Level < 5000) return 'text-orange-600' - return 'text-red-600' -} -const getOccupancyColor = (occupancy: string) => { - switch (occupancy) { - case 'low': - return 'text-green-600' - case 'medium': - return 'text-yellow-600' - case 'high': - return 'text-red-600' - default: - return 'text-gray-600' - } +// Reload sensors function +const reloadSensors = async () => { + await energyStore.fetchApiSensors() } // Load sensors from API and connect WebSocket for real-time updates