diff --git a/src/services/roomsApi.ts b/src/services/roomsApi.ts new file mode 100644 index 0000000..56b7bcd --- /dev/null +++ b/src/services/roomsApi.ts @@ -0,0 +1,30 @@ +/** + * Rooms API Service + * Handles room-related API calls + */ +import { apiClient, type RoomInfo, type RoomData } from './api' + +export const roomsApi = { + /** + * Get list of all rooms with sensor counts and latest metrics + */ + async getRooms(): Promise { + return apiClient.get('/api/v1/rooms') + }, + + /** + * Get historical data for a specific room + */ + async getRoomData( + roomName: string, + params?: { + start_time?: number + end_time?: number + limit?: number + }, + ): Promise { + return apiClient.get(`/api/v1/rooms/${encodeURIComponent(roomName)}/data`, params) + }, +} + +export default roomsApi diff --git a/src/services/sensorsApi.ts b/src/services/sensorsApi.ts new file mode 100644 index 0000000..bfc68d3 --- /dev/null +++ b/src/services/sensorsApi.ts @@ -0,0 +1,98 @@ +/** + * Sensors API Service + * Handles sensor-related API calls + */ +import { + apiClient, + type SensorInfo, + type SensorReading, + type DataQuery, + type DataResponse, + type SensorType, + type SensorStatus, +} from './api' + +export const sensorsApi = { + /** + * Get all sensors with optional filtering + */ + async getSensors(params?: { + room?: string + sensor_type?: SensorType + status?: SensorStatus + }): Promise { + return apiClient.get('/api/v1/sensors', params) + }, + + /** + * Get detailed information about a specific sensor + */ + async getSensor(sensorId: string): Promise { + return apiClient.get(`/api/v1/sensors/${sensorId}`) + }, + + /** + * Get historical data for a specific sensor + */ + async getSensorData( + sensorId: string, + params?: { + start_time?: number + end_time?: number + limit?: number + offset?: number + }, + ): Promise { + return apiClient.get(`/api/v1/sensors/${sensorId}/data`, params) + }, + + /** + * Advanced data query with multiple filters + */ + async queryData(query: DataQuery): Promise { + return apiClient.post('/api/v1/data/query', query) + }, + + /** + * Update sensor metadata + */ + async updateSensorMetadata( + sensorId: string, + metadata: Record, + ): Promise<{ message: string }> { + return apiClient.put<{ message: string }>(`/api/v1/sensors/${sensorId}/metadata`, metadata) + }, + + /** + * Delete sensor and all its data + */ + async deleteSensor(sensorId: string): Promise<{ + message: string + readings_deleted: number + metadata_deleted?: boolean + }> { + return apiClient.delete(`/api/v1/sensors/${sensorId}`) + }, + + /** + * Export sensor data for specified time range + */ + async exportData(params: { + start_time: number + end_time: number + sensor_ids?: string + format?: 'json' | 'csv' + }): Promise<{ + data: SensorReading[] + count: number + export_params: any + }> { + return apiClient.get<{ + data: SensorReading[] + count: number + export_params: any + }>('/api/v1/export', params) + }, +} + +export default sensorsApi diff --git a/src/stores/energy.ts b/src/stores/energy.ts index d71f51a..2abfeff 100644 --- a/src/stores/energy.ts +++ b/src/stores/energy.ts @@ -1,5 +1,18 @@ import { defineStore } from 'pinia' import { ref, reactive } from 'vue' +import { + sensorsApi, + roomsApi, + analyticsApi, + healthApi, + type SensorInfo as ApiSensorInfo, + type RoomInfo as ApiRoomInfo, + type AnalyticsSummary, + type EnergyTrends, + type RoomComparison, + type SystemStatus, + type HealthCheck +} from '@/services' const MAX_DATA_POINTS = 100 // Keep the last 100 data points for the chart @@ -96,29 +109,46 @@ export const useEnergyStore = defineStore('energy', () => { const roomsData = reactive>(new Map()) const latestReadings = reactive>(new Map()) const sensorDevices = reactive>(new Map()) - const availableRooms = ref([ - 'Conference Room A', - 'Conference Room B', - 'Office Floor 1', - 'Office Floor 2', - 'Kitchen', - 'Lobby', - 'Server Room', - 'Storage Room', - 'Meeting Room 1', - 'Meeting Room 2' - ]) + const availableRooms = ref([]) + const roomsLoading = ref(false) + const roomsLoaded = ref(false) + + // API integration state + const apiSensors = ref([]) + const apiRooms = ref([]) + const analyticsData = ref<{ + summary: AnalyticsSummary | null + trends: EnergyTrends | null + roomComparison: RoomComparison | null + }>({ + summary: null, + trends: null, + roomComparison: null + }) + const systemStatus = ref(null) + const healthStatus = ref(null) + const apiLoading = ref(false) + const apiError = ref(null) let socket: WebSocket | null = null const newDataBuffer: (LegacyEnergyData | SensorReading)[] = [] // Actions function connect(url: string) { - if (isConnected.value) { + if (isConnected.value && socket) { console.log('Already connected.') return } + // Close any existing connection first + if (socket) { + socket.onclose = null + socket.onerror = null + socket.onmessage = null + socket.close() + socket = null + } + console.log(`Connecting to WebSocket at ${url}`) socket = new WebSocket(url) @@ -130,14 +160,35 @@ export const useEnergyStore = defineStore('energy', () => { socket.onmessage = (event) => { try { const data = JSON.parse(event.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.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') + }, 100) + return + } + newDataBuffer.push(data) } catch (error) { console.error('Error parsing incoming data:', error) } } - socket.onclose = () => { - console.log('WebSocket connection closed.') + socket.onclose = (event) => { + console.log(`WebSocket connection closed. Code: ${event.code}, Reason: ${event.reason}`) isConnected.value = false socket = null } @@ -145,7 +196,9 @@ export const useEnergyStore = defineStore('energy', () => { socket.onerror = (error) => { console.error('WebSocket error:', error) isConnected.value = false - socket = null + if (socket) { + socket = null + } } // Process the buffer at intervals @@ -153,11 +206,17 @@ export const useEnergyStore = defineStore('energy', () => { if (newDataBuffer.length > 0) { const data = newDataBuffer.shift() // Get the oldest data point if (data) { + // Skip non-data messages (connection establishment, proxy info, etc.) + if (data.type && (data.type === 'connection_established' || data.type === 'proxy_info')) { + console.log('Received system message:', data.type) + return + } + // Handle both legacy and new data formats if (isLegacyData(data)) { latestMessage.value = data updateSensorData(data) - + // Update time series for chart const newLabel = new Date(data.timestamp * 1000).toLocaleTimeString() timeSeriesData.labels.push(newLabel) @@ -165,7 +224,7 @@ export const useEnergyStore = defineStore('energy', () => { } else { // Handle new multi-metric data updateRoomData(data) - + // Update time series for chart (use energy values) const newLabel = new Date(data.timestamp * 1000).toLocaleTimeString() timeSeriesData.labels.push(newLabel) @@ -426,18 +485,67 @@ export const useEnergyStore = defineStore('energy', () => { } // Room management functions + const loadRoomsFromAPI = async (): Promise => { + if (roomsLoading.value || roomsLoaded.value) { + return // Already loading or loaded + } + + roomsLoading.value = true + + try { + // Try to load from microservices API first + const response = await fetch('/api/v1/rooms/names') + + if (response.ok) { + const data = await response.json() + if (data.rooms && Array.isArray(data.rooms)) { + availableRooms.value = data.rooms.sort() + roomsLoaded.value = true + console.log('Loaded rooms from microservices API:', data.rooms.length) + return + } + } + + // Fallback: try direct sensor service connection + const directResponse = await fetch('http://localhost:8007/rooms/names') + + if (directResponse.ok) { + const data = await directResponse.json() + if (data.rooms && Array.isArray(data.rooms)) { + availableRooms.value = data.rooms.sort() + roomsLoaded.value = true + console.log('Loaded rooms from sensor service:', data.rooms.length) + return + } + } + + // If both fail, use empty list and log warning + console.warn('Failed to load rooms from API, starting with empty list') + availableRooms.value = [] + roomsLoaded.value = true + + } catch (error) { + console.error('Error loading rooms:', error) + // Start with empty list on error + availableRooms.value = [] + roomsLoaded.value = true + } finally { + roomsLoading.value = false + } + } + function addRoom(roomName: string): boolean { if (!roomName.trim()) return false - + // Check if room already exists if (availableRooms.value.includes(roomName.trim())) { return false } - + // Add room to available rooms list availableRooms.value.push(roomName.trim()) availableRooms.value.sort() // Keep rooms sorted alphabetically - + console.log(`Added new room: ${roomName}`) return true } @@ -487,10 +595,178 @@ export const useEnergyStore = defineStore('energy', () => { })) } + // API Integration Functions + async function handleApiCall(apiCall: () => Promise): Promise { + 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' + + // Handle authentication errors + if (errorMessage.includes('401') || errorMessage.includes('Authorization')) { + console.warn('Authentication error detected, attempting to re-authenticate...') + + // Try to get fresh auth token + 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...') + // Retry the original 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) + } + } + + apiError.value = errorMessage + console.error('API call failed:', errorMessage) + return null + } finally { + apiLoading.value = false + } + } + + // Sensors API functions + async function fetchApiSensors(params?: { room?: string; sensor_type?: any; status?: any }) { + const result = await handleApiCall(() => sensorsApi.getSensors(params)) + if (result) { + apiSensors.value = result + } + 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) { + 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)) + } + + // Rooms API functions + async function fetchApiRooms() { + const result = await handleApiCall(() => roomsApi.getRooms()) + if (result) { + apiRooms.value = result + // Update available rooms from API data + const roomNames = result.map(room => room.room).filter(name => name) + if (roomNames.length > 0) { + availableRooms.value = [...new Set([...availableRooms.value, ...roomNames])].sort() + } + } + return result + } + + async function fetchApiRoomData( + roomName: string, + params?: { start_time?: number; end_time?: number; limit?: number } + ) { + return handleApiCall(() => roomsApi.getRoomData(roomName, params)) + } + + // Analytics API functions + async function fetchAnalyticsSummary(hours: number = 24) { + const result = await handleApiCall(() => analyticsApi.getAnalyticsSummary(hours)) + if (result) { + analyticsData.value.summary = result + } + return result + } + + async function fetchEnergyTrends(hours: number = 168) { + const result = await handleApiCall(() => analyticsApi.getEnergyTrends(hours)) + if (result) { + analyticsData.value.trends = result + } + return result + } + + async function fetchRoomComparison(hours: number = 24) { + const result = await handleApiCall(() => analyticsApi.getRoomComparison(hours)) + if (result) { + analyticsData.value.roomComparison = result + } + return result + } + + async function fetchSystemEvents(params?: { + severity?: string + event_type?: string + hours?: number + limit?: number + }) { + return handleApiCall(() => analyticsApi.getEvents(params)) + } + + // Health API functions + async function fetchSystemStatus() { + const result = await handleApiCall(() => healthApi.getStatus()) + if (result) { + systemStatus.value = result + } + return result + } + + async function fetchHealthStatus() { + const result = await handleApiCall(() => healthApi.getHealth()) + if (result) { + healthStatus.value = result + } + return result + } + + // Initialize data from APIs + async function initializeFromApi() { + await Promise.allSettled([ + loadRoomsFromAPI(), // Load room names first + fetchApiSensors(), + fetchApiRooms(), + fetchAnalyticsSummary(), + fetchSystemStatus(), + fetchHealthStatus() + ]) + } + // Initialize mock sensors on store creation initializeMockSensors() + // Load rooms from API on store initialization + loadRoomsFromAPI() + return { + // WebSocket state isConnected, latestMessage, timeSeriesData, @@ -499,6 +775,19 @@ export const useEnergyStore = defineStore('energy', () => { latestReadings, sensorDevices, availableRooms, + roomsLoading, + roomsLoaded, + + // API state + apiSensors, + apiRooms, + analyticsData, + systemStatus, + healthStatus, + apiLoading, + apiError, + + // WebSocket functions connect, disconnect, getCO2Status, @@ -506,9 +795,26 @@ export const useEnergyStore = defineStore('energy', () => { executeSensorAction, getSensorsByRoom, getSensorsByType, + loadRoomsFromAPI, addRoom, removeRoom, getRoomStats, - getAllRoomsWithStats + getAllRoomsWithStats, + + // API functions + fetchApiSensors, + fetchApiSensorData, + updateApiSensorMetadata, + deleteApiSensor, + exportApiData, + fetchApiRooms, + fetchApiRoomData, + fetchAnalyticsSummary, + fetchEnergyTrends, + fetchRoomComparison, + fetchSystemEvents, + fetchSystemStatus, + fetchHealthStatus, + initializeFromApi } }) diff --git a/src/views/SensorManagementView.vue b/src/views/SensorManagementView.vue index fec162c..35618cf 100644 --- a/src/views/SensorManagementView.vue +++ b/src/views/SensorManagementView.vue @@ -24,7 +24,10 @@
-
@@ -360,7 +365,7 @@ const getOccupancyColor = (occupancy: string) => { // WebSocket connection for real-time updates onMounted(() => { if (!energyStore.isConnected) { - energyStore.connect('ws://192.168.1.73:8000/ws') + energyStore.connect('ws://localhost:8000/ws') } })