- Remove room and sensor logic from energy store - Update components to use useRoomStore and useSensorStore directly - Fix sensor/room ID mismatches and API response handling in room store - Update AIOptimizationView to use useWebSocketStore for connection status - Update SensorManagementView to use useRoomStore and useSensorStore directly
290 lines
8.7 KiB
TypeScript
290 lines
8.7 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { ref, reactive } from 'vue'
|
|
import { roomsApi, type RoomInfo as ApiRoomInfo, type SensorReading } from '@/services'
|
|
import { useSensorStore } from './sensor'
|
|
|
|
interface RoomMetrics {
|
|
room: string
|
|
sensors: string[]
|
|
energy: {
|
|
current: number
|
|
total: number
|
|
average: number
|
|
unit: string
|
|
}
|
|
co2: {
|
|
current: number
|
|
average: number
|
|
max: number
|
|
status: 'good' | 'moderate' | 'poor' | 'critical'
|
|
unit: string
|
|
}
|
|
occupancyEstimate: 'low' | 'medium' | 'high'
|
|
lastUpdated: number
|
|
}
|
|
|
|
export const useRoomStore = defineStore('room', () => {
|
|
// State
|
|
const roomsData = reactive<Map<string, RoomMetrics>>(new Map())
|
|
const availableRooms = ref<string[]>([])
|
|
const apiRooms = ref<ApiRoomInfo[]>([])
|
|
const roomsLoading = ref<boolean>(false)
|
|
const roomsLoaded = ref<boolean>(false)
|
|
const apiLoading = ref(false)
|
|
const apiError = ref<string | null>(null)
|
|
|
|
// Actions
|
|
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)
|
|
|
|
// Get or create room metrics
|
|
let roomMetrics = roomsData.get(data.room)
|
|
|
|
if (!roomMetrics) {
|
|
roomMetrics = {
|
|
room: data.room,
|
|
sensors: [data.sensor_id],
|
|
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,
|
|
}
|
|
roomsData.set(data.room, roomMetrics)
|
|
}
|
|
|
|
// Update room sensors list
|
|
if (!roomMetrics.sensors.includes(data.sensor_id)) {
|
|
roomMetrics.sensors.push(data.sensor_id)
|
|
}
|
|
|
|
// Recalculate room metrics from all sensors in the room
|
|
const roomSensors = Array.from(sensorStore.latestReadings.values()).filter(
|
|
(reading) => reading.room === data.room,
|
|
)
|
|
|
|
// Energy calculations
|
|
roomMetrics.energy.current = roomSensors.reduce((sum, sensor) => sum + sensor.energy.value, 0)
|
|
roomMetrics.energy.total += data.energy.value // Accumulate total
|
|
roomMetrics.energy.average = roomMetrics.energy.total / roomSensors.length
|
|
|
|
// CO2 calculations
|
|
const co2Values = roomSensors.map((sensor) => sensor.co2.value)
|
|
roomMetrics.co2.current = co2Values.reduce((sum, val) => sum + val, 0) / co2Values.length
|
|
roomMetrics.co2.max = Math.max(roomMetrics.co2.max, ...co2Values)
|
|
roomMetrics.co2.average = (roomMetrics.co2.average + roomMetrics.co2.current) / 2
|
|
|
|
// CO2 status classification
|
|
if (roomMetrics.co2.current < 400) roomMetrics.co2.status = 'good'
|
|
else if (roomMetrics.co2.current < 1000) roomMetrics.co2.status = 'moderate'
|
|
else if (roomMetrics.co2.current < 5000) roomMetrics.co2.status = 'poor'
|
|
else roomMetrics.co2.status = 'critical'
|
|
|
|
// Occupancy estimate based on CO2 levels
|
|
if (roomMetrics.co2.current < 600) roomMetrics.occupancyEstimate = 'low'
|
|
else if (roomMetrics.co2.current < 1200) roomMetrics.occupancyEstimate = 'medium'
|
|
else roomMetrics.occupancyEstimate = 'high'
|
|
|
|
roomMetrics.lastUpdated = data.timestamp
|
|
}
|
|
|
|
function getCO2Status(ppm: number): 'good' | 'moderate' | 'poor' | 'critical' {
|
|
if (ppm < 400) return 'good'
|
|
if (ppm < 1000) return 'moderate'
|
|
if (ppm < 5000) return 'poor'
|
|
return 'critical'
|
|
}
|
|
|
|
const loadRoomsFromAPI = async (): Promise<void> => {
|
|
if (roomsLoading.value || roomsLoaded.value) {
|
|
return
|
|
}
|
|
|
|
roomsLoading.value = true
|
|
|
|
try {
|
|
const data = await roomsApi.getRoomNames()
|
|
if (data.rooms && Array.isArray(data.rooms)) {
|
|
availableRooms.value = data.rooms.sort()
|
|
roomsLoaded.value = true
|
|
return
|
|
}
|
|
|
|
console.warn('No rooms found in API response, starting with empty list')
|
|
availableRooms.value = []
|
|
roomsLoaded.value = true
|
|
} catch (error) {
|
|
console.error('Error loading rooms:', error)
|
|
availableRooms.value = []
|
|
roomsLoaded.value = true
|
|
} finally {
|
|
roomsLoading.value = false
|
|
}
|
|
}
|
|
|
|
function addRoom(roomName: string): boolean {
|
|
if (!roomName.trim()) return false
|
|
|
|
if (availableRooms.value.includes(roomName.trim())) {
|
|
return false
|
|
}
|
|
|
|
availableRooms.value.push(roomName.trim())
|
|
availableRooms.value.sort()
|
|
|
|
console.log(`Added new room: ${roomName}`)
|
|
return true
|
|
}
|
|
|
|
function removeRoom(roomName: string): boolean {
|
|
const sensorStore = useSensorStore()
|
|
const index = availableRooms.value.indexOf(roomName)
|
|
if (index === -1) return false
|
|
|
|
// Check if any sensors are assigned to this room
|
|
const sensorsInRoom = sensorStore.getSensorsByRoom(roomName)
|
|
if (sensorsInRoom.length > 0) {
|
|
// Reassign sensors to empty room
|
|
sensorsInRoom.forEach((sensor) => {
|
|
sensor.room = ''
|
|
sensorStore.sensorDevices.set(sensor.sensor_id, { ...sensor })
|
|
})
|
|
}
|
|
|
|
// Remove room data
|
|
roomsData.delete(roomName)
|
|
|
|
// Remove from available rooms
|
|
availableRooms.value.splice(index, 1)
|
|
|
|
console.log(`Removed room: ${roomName}`)
|
|
return true
|
|
}
|
|
|
|
function getRoomStats(roomName: string) {
|
|
const sensorStore = useSensorStore()
|
|
const sensorsInRoom = sensorStore.getSensorsByRoom(roomName)
|
|
const roomMetrics = roomsData.get(roomName)
|
|
|
|
return {
|
|
sensorCount: sensorsInRoom.length,
|
|
sensorTypes: [...new Set(sensorsInRoom.map((s) => s.type))],
|
|
hasMetrics: !!roomMetrics,
|
|
energyConsumption: roomMetrics?.energy.current || 0,
|
|
co2Level: roomMetrics?.co2.current || 0,
|
|
lastUpdated: roomMetrics?.lastUpdated || null,
|
|
}
|
|
}
|
|
|
|
function getAllRoomsWithStats() {
|
|
return availableRooms.value.map((room) => ({
|
|
name: room,
|
|
...getRoomStats(room),
|
|
}))
|
|
}
|
|
|
|
// API Integration Functions
|
|
async function handleApiCall<T>(apiCall: () => Promise<T>): Promise<T | null> {
|
|
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'
|
|
|
|
if (errorMessage.includes('401') || errorMessage.includes('Authorization')) {
|
|
console.warn('Authentication error detected, attempting to re-authenticate...')
|
|
|
|
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...')
|
|
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
|
|
}
|
|
}
|
|
|
|
// Rooms API functions
|
|
async function fetchApiRooms() {
|
|
const result = await handleApiCall(() => roomsApi.getRooms())
|
|
if (result) {
|
|
// Handle both response formats: {rooms: [...]} or direct array [...]
|
|
const roomsArray = Array.isArray(result) ? result : result.rooms || []
|
|
apiRooms.value = roomsArray
|
|
|
|
// Update available rooms from API data
|
|
const roomNames = roomsArray.map((room) => room.name || 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))
|
|
}
|
|
|
|
// Initialize rooms on store creation
|
|
loadRoomsFromAPI()
|
|
|
|
return {
|
|
// State
|
|
roomsData,
|
|
availableRooms,
|
|
apiRooms,
|
|
roomsLoading,
|
|
roomsLoaded,
|
|
apiLoading,
|
|
apiError,
|
|
|
|
// Actions
|
|
updateRoomData,
|
|
getCO2Status,
|
|
loadRoomsFromAPI,
|
|
addRoom,
|
|
removeRoom,
|
|
getRoomStats,
|
|
getAllRoomsWithStats,
|
|
|
|
// API functions
|
|
fetchApiRooms,
|
|
fetchApiRoomData,
|
|
}
|
|
})
|