diff --git a/src/components/cards/SensorConsumptionTable.vue b/src/components/cards/SensorConsumptionTable.vue index d312a3b..bf7c801 100644 --- a/src/components/cards/SensorConsumptionTable.vue +++ b/src/components/cards/SensorConsumptionTable.vue @@ -1,7 +1,7 @@ \ No newline at end of file + +onMounted(async () => { + await sensorStore.fetchApiSensors() +}) + diff --git a/src/services/api.ts b/src/services/api.ts index 1b6db78..9a36455 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -50,12 +50,11 @@ export interface DataResponse { } export interface SensorReading { - _id?: string sensor_id: string - room?: string + room: string sensor_type: string timestamp: number - created_at?: string + type: string energy?: { value: number unit: string @@ -176,29 +175,18 @@ export interface SystemEvent { } export interface SensorDevice { - id: string + _id: string sensor_id: string name: string type: SensorType sensor_type: SensorType room: string status: SensorStatus - location?: string - lastSeen: number - total_readings?: number capabilities: { monitoring: string[] actions: SensorAction[] } - metadata: { - location?: string - model?: string - firmware?: string - battery?: number - created_at?: string - updated_at?: string - manufacturer?: string - } + metadata: SensorMetadata } export interface SensorAction { @@ -245,7 +233,20 @@ export enum SensorStatus { ERROR = 'error', } -// HTTP Client class +export interface SensorMetadata { + time_factor?: number + occupancy_factor?: number + quality_level?: string + duration_seconds?: number + location?: string + model?: string + firmware?: string + battery?: number + created_at?: string + updated_at?: string + manufacturer?: string +} + class ApiClient { private baseUrl: string diff --git a/src/services/sensorsApi.ts b/src/services/sensorsApi.ts index 5c4371f..547370a 100644 --- a/src/services/sensorsApi.ts +++ b/src/services/sensorsApi.ts @@ -6,6 +6,7 @@ import { type DataResponse, type SensorType, type SensorStatus, + type SensorMetadata, } from './api' export const sensorsApi = { @@ -13,8 +14,24 @@ export const sensorsApi = { room?: string sensor_type?: SensorType status?: SensorStatus - }): Promise { - return apiClient.get('/api/v1/sensors/get', params) + }): Promise<{ + sensors: SensorDevice[] + count: number + filters: { + room: string + sensor_type: SensorType + status: SensorStatus + } + }> { + return apiClient.get<{ + sensors: SensorDevice[] + count: number + filters: { + room: string + sensor_type: SensorType + status: SensorStatus + } + }>('/api/v1/sensors/get', params) }, async getSensor(sensorId: string): Promise { @@ -39,7 +56,7 @@ export const sensorsApi = { async updateSensorMetadata( sensorId: string, - metadata: Record, + metadata: SensorMetadata, ): Promise<{ message: string }> { return apiClient.put<{ message: string }>(`/api/v1/sensors/${sensorId}/metadata`, metadata) }, diff --git a/src/stores/energy.ts b/src/stores/energy.ts index 5f4cb3c..73d09d3 100644 --- a/src/stores/energy.ts +++ b/src/stores/energy.ts @@ -3,18 +3,12 @@ import { computed } from 'vue' import { useSensorStore } from './sensor' import { useRoomStore } from './room' import { useAnalyticsStore } from './analytics' -import { useWebSocketStore } from './websocket' export const useEnergyStore = defineStore('energy', () => { // Get instances of other stores const sensorStore = useSensorStore() const roomStore = useRoomStore() const analyticsStore = useAnalyticsStore() - const webSocketStore = useWebSocketStore() - - // Delegate to WebSocket store - const connect = (url: string) => webSocketStore.connect(url) - const disconnect = () => webSocketStore.disconnect() // Initialize data from APIs async function initializeFromApi() { @@ -27,15 +21,9 @@ export const useEnergyStore = defineStore('energy', () => { } return { - // WebSocket state (delegated) - isConnected: computed(() => webSocketStore.isConnected), - latestMessage: computed(() => webSocketStore.latestMessage), - timeSeriesData: computed(() => webSocketStore.timeSeriesData), - // Sensor state (delegated) sensorsData: computed(() => sensorStore.sensorsData), sensorDevices: computed(() => sensorStore.sensorDevices), - latestReadings: computed(() => sensorStore.latestReadings), apiSensors: computed(() => Array.from(sensorStore.sensorDevices.values())), // Convert Map to Array // Room state (delegated) @@ -51,13 +39,11 @@ export const useEnergyStore = defineStore('energy', () => { healthStatus: computed(() => analyticsStore.healthStatus), // Combined API loading/error state - apiLoading: computed(() => sensorStore.apiLoading || roomStore.apiLoading || analyticsStore.apiLoading), + apiLoading: computed( + () => sensorStore.apiLoading || roomStore.apiLoading || analyticsStore.apiLoading, + ), apiError: computed(() => sensorStore.apiError || roomStore.apiError || analyticsStore.apiError), - // WebSocket functions (delegated) - connect, - disconnect, - // Sensor functions (delegated) updateSensorRoom: sensorStore.updateSensorRoom, executeSensorAction: sensorStore.executeSensorAction, diff --git a/src/stores/sensor.ts b/src/stores/sensor.ts index 136fe1b..9b70978 100644 --- a/src/stores/sensor.ts +++ b/src/stores/sensor.ts @@ -3,37 +3,11 @@ import { ref, reactive } from 'vue' import { sensorsApi, SensorType, - SensorStatus, type SensorDevice, - type SensorAction, + type SensorStatus, + type SensorReading, } from '@/services' -interface SensorReading { - id: string - sensorId: string - room: string - timestamp: number - energy: { - value: number - unit: string - } - co2: { - value: number - unit: string - } - temperature?: { - value: number - unit: string - } -} - -interface LegacyEnergyData { - sensorId: string - timestamp: number - value: number - unit: string -} - export const useSensorStore = defineStore('sensor', () => { // State const sensorDevices = reactive>(new Map()) @@ -52,7 +26,7 @@ export const useSensorStore = defineStore('sensor', () => { } } - async function executeSensorAction(sensorId: string, actionId: string, parameters?: any) { + async function executeSensorAction(sensorId: string, actionId: string) { const sensor = sensorDevices.get(sensorId) if (!sensor) return false @@ -75,49 +49,18 @@ export const useSensorStore = defineStore('sensor', () => { return Array.from(sensorDevices.values()).filter((sensor) => sensor.type === type) } - function updateSensorData(data: LegacyEnergyData) { - const existingSensor = sensorsData.get(data.sensorId) - - if (existingSensor) { - const newTotal = existingSensor.totalConsumption + data.value - const dataPoints = Math.floor((data.timestamp - existingSensor.lastUpdated) / 60) + 1 - - sensorsData.set(data.sensorId, { - ...existingSensor, - latestValue: data.value, - totalConsumption: newTotal, - averageConsumption: newTotal / dataPoints, - lastUpdated: data.timestamp, - }) - } else { - sensorsData.set(data.sensorId, { - sensorId: data.sensorId, - latestValue: data.value, - totalConsumption: data.value, - averageConsumption: data.value, - lastUpdated: data.timestamp, - 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 updateEnergySensors(data: Sensor) { + console.log(data) } function updateLatestReading(reading: SensorReading) { - latestReadings.set(reading.sensorId, reading) + latestReadings.set(reading.sensor_id, reading) // Mark sensor as recently updated - recentlyUpdatedSensors.add(reading.sensorId) - console.log(reading.sensor_type) + recentlyUpdatedSensors.add(reading.sensor_id) // Remove from recently updated after 2 seconds setTimeout(() => { - recentlyUpdatedSensors.delete(reading.sensorId) + recentlyUpdatedSensors.delete(reading.sensor_id) }, 2000) } @@ -225,17 +168,21 @@ export const useSensorStore = defineStore('sensor', () => { } // Sensors API functions - async function fetchApiSensors(params?: { room?: string; sensor_type?: any; status?: any }) { + 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._id || sensor.sensor_id + const sensorKey = sensor._id || sensor.sensor_id const sensorType = sensor.sensor_type || sensor.type const sensorName = sensor.name || '' - // Normalize sensor data structure for frontend compatibility const normalizedSensor = { ...sensor, id: sensorKey, @@ -255,58 +202,18 @@ export const useSensorStore = defineStore('sensor', () => { signalStrength: sensor.metadata?.signalStrength, ...sensor.metadata, }, - tags: sensor.tags || [], - lastSeen: sensor.last_seen || sensor.lastSeen || Date.now() / 1000, + lastSeen: sensor.last_seen || 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 - const sensorType = sensor.sensor_type || sensor.type - const sensorName = sensor.name || '' - - // Normalize sensor data structure for frontend compatibility - 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, - }, - 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.warn('Unexpected result format:', typeof result, result) } } else { - console.log('No result received from API') + console.error('No result received from API') } - console.log('Current sensor devices:', Array.from(sensorDevices.entries())) return result } @@ -344,11 +251,11 @@ export const useSensorStore = defineStore('sensor', () => { apiError, // Actions + updateEnergySensors, updateSensorRoom, executeSensorAction, getSensorsByRoom, getSensorsByType, - updateSensorData, updateLatestReading, // API functions diff --git a/src/stores/websocket.ts b/src/stores/websocket.ts index e692648..84b3904 100644 --- a/src/stores/websocket.ts +++ b/src/stores/websocket.ts @@ -5,13 +5,6 @@ import { useRoomStore } from './room' const MAX_DATA_POINTS = 100 -interface LegacyEnergyData { - sensorId: string - timestamp: number - value: number - unit: string -} - interface SensorReading { sensorId: string room: string @@ -31,9 +24,8 @@ interface SensorReading { } export const useWebSocketStore = defineStore('websocket', () => { - // State const isConnected = ref(false) - const latestMessage = ref(null) + const latestMessage = ref(null) const timeSeriesData = reactive<{ labels: string[] datasets: { data: number[] }[] @@ -43,16 +35,14 @@ export const useWebSocketStore = defineStore('websocket', () => { }) let socket: WebSocket | null = null - const newDataBuffer: (LegacyEnergyData | SensorReading)[] = [] + const newDataBuffer: SensorReading[] = [] - // Actions function connect(url: string) { if (isConnected.value && socket) { console.log('Already connected.') return } - // Close any existing connection first if (socket) { socket.onclose = null socket.onerror = null @@ -61,11 +51,11 @@ export const useWebSocketStore = defineStore('websocket', () => { socket = null } - console.log(`Connecting to WebSocket at ${url}`) + console.info(`Connecting to WebSocket at ${url}`) socket = new WebSocket(url) socket.onopen = () => { - console.log('WebSocket connection established.') + console.info('WebSocket connection established.') isConnected.value = true } @@ -74,7 +64,7 @@ export const useWebSocketStore = defineStore('websocket', () => { const data = JSON.parse(event.data) if (data.type === 'proxy_info' && data.sensor_service_url) { - console.log('Received proxy info, reconnecting to sensor service...') + console.warn('Received proxy info, reconnecting to sensor service...') if (socket) { socket.onclose = null socket.close() @@ -83,7 +73,7 @@ export const useWebSocketStore = defineStore('websocket', () => { isConnected.value = false setTimeout(() => { - console.log('Connecting directly to sensor service at ws://localhost:8007/ws') + console.info('Connecting directly to sensor service at ws://localhost:8007/ws') connect('ws://localhost:8007/ws') }, 100) return @@ -125,155 +115,41 @@ export const useWebSocketStore = defineStore('websocket', () => { } } - function isLegacyData(data: any): data is LegacyEnergyData { - 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() - - // 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) { + function processIncomingData(data: SensorReading) { // Skip non-data messages if ('type' in data && (data.type === 'connection_established' || data.type === 'proxy_info')) { return } - // 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() - // Handle both legacy and new data formats - if (isLegacyData(data)) { - 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', - }, + // Handle new multi-metric data + // Only update room data if we have the proper structure + if (data.energy && data.co2 && data.room) { + if (data.energy) { + sensorStore.updateEnergySensors(data) } - sensorStore.updateLatestReading(sensorReading) + roomStore.updateRoomData(data) + } - // 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 - // Only update room data if we have the proper structure - if (data.energy && data.co2 && data.room) { - roomStore.updateRoomData(data) - } - - // 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 + // Map the sensor ID for individual sensor updates + // const mappedSensorId = mapWebSocketSensorId(data.sensorId) + const mappedData = { ...data, sensorId: data.sensorId, id: data.sensorId } + sensorStore.updateLatestReading(data) // Update individual sensor readings for cards + if (data.energy) { // 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 || 0) + timeSeriesData.datasets[0].data.push(data.energy?.value) } + } - // Keep only the latest data points - if (timeSeriesData.labels.length > MAX_DATA_POINTS) { - timeSeriesData.labels.shift() - timeSeriesData.datasets[0].data.shift() - } + // Keep only the latest data points + if (timeSeriesData.labels.length > MAX_DATA_POINTS) { + timeSeriesData.labels.shift() + timeSeriesData.datasets[0].data.shift() } return {