Refactor to decouple energy, room, and sensor stores

- 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
This commit is contained in:
rafaeldpsilva
2025-10-01 12:26:44 +01:00
parent 71d1d82761
commit b9348140b8
8 changed files with 97 additions and 115 deletions

View File

@@ -70,12 +70,12 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useEnergyStore } from '@/stores/energy'
import { useRoomStore } from '@/stores/room'
const energyStore = useEnergyStore()
const roomStore = useRoomStore()
const roomsList = computed(() => {
return Array.from(energyStore.roomsData.values()).sort((a, b) =>
return Array.from(roomStore.roomsData.values()).sort((a, b) =>
b.co2.current - a.co2.current // Sort by CO2 level descending
)
})
@@ -86,7 +86,7 @@ const overallCO2 = computed(() => {
})
const overallStatus = computed(() => {
return energyStore.getCO2Status(overallCO2.value)
return roomStore.getCO2Status(overallCO2.value)
})
const roomsWithGoodAir = computed(() => {

View File

@@ -165,7 +165,6 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useEnergyStore } from '@/stores/energy'
import { useSensorStore } from '@/stores/sensor'
const props = defineProps<{
@@ -179,28 +178,25 @@ const emit = defineEmits<{
executeAction: [sensor: any, action: any]
}>()
const energyStore = useEnergyStore()
const sensorStore = useSensorStore()
const getSensorValues = (sensor: any) => {
const values = []
// Get real-time sensor reading from store
const latestReading = energyStore.latestReadings.get(sensor.id)
const latestReading = sensorStore.latestReadings.get(sensor.id) || sensorStore.latestReadings.get(sensor.sensor_id)
console.log(`[Detailed] Getting values for sensor ${sensor.id}, found reading:`, latestReading)
console.log('[Detailed] Available readings:', Array.from(energyStore.latestReadings.keys()))
console.log('[Detailed] Available readings:', Array.from(sensorStore.latestReadings.keys()))
console.log(`[Detailed] Sensor capabilities:`, sensor.capabilities?.monitoring)
// Only show energy if the sensor actually monitors energy
if (sensor.capabilities?.monitoring?.includes('energy')) {
const energyValue = latestReading?.energy?.value?.toFixed(2) ||
energyStore.latestMessage?.value?.toFixed(2) ||
'0.00'
const energyValue = latestReading?.energy?.value?.toFixed(2) || '0.00'
values.push({
type: 'energy',
label: 'Energy Consumption',
value: energyValue,
unit: latestReading?.energy?.unit || energyStore.latestMessage?.unit || 'kWh'
unit: latestReading?.energy?.unit || 'kWh'
})
}

View File

@@ -72,12 +72,12 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useEnergyStore } from '@/stores/energy'
import { useRoomStore } from '@/stores/room'
const energyStore = useEnergyStore()
const roomStore = useRoomStore()
const roomsList = computed(() => {
return Array.from(energyStore.roomsData.values()).sort((a, b) =>
return Array.from(roomStore.roomsData.values()).sort((a, b) =>
a.room.localeCompare(b.room)
)
})

View File

@@ -173,9 +173,9 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useEnergyStore } from '@/stores/energy'
import { useRoomStore } from '@/stores/room'
const energyStore = useEnergyStore()
const roomStore = useRoomStore()
// Emit events
const emit = defineEmits<{
@@ -191,7 +191,7 @@ const roomToDelete = ref<string | null>(null)
// Computed properties
const roomsWithStats = computed(() => {
return energyStore.getAllRoomsWithStats()
return roomStore.getAllRoomsWithStats()
})
// Methods
@@ -215,8 +215,8 @@ const addNewRoom = async () => {
errorMessage.value = ''
try {
const success = energyStore.addRoom(newRoomName.value.trim())
const success = roomStore.addRoom(newRoomName.value.trim())
if (success) {
newRoomName.value = ''
// Show success feedback
@@ -242,8 +242,8 @@ const deleteRoom = async () => {
isDeleting.value = roomToDelete.value
try {
const success = energyStore.removeRoom(roomToDelete.value)
const success = roomStore.removeRoom(roomToDelete.value)
if (success) {
console.log(`Room "${roomToDelete.value}" deleted successfully`)
} else {
@@ -258,7 +258,7 @@ const deleteRoom = async () => {
}
const getRoomStats = (roomName: string) => {
return energyStore.getRoomStats(roomName)
return roomStore.getRoomStats(roomName)
}
const getCO2Color = (co2Level: number) => {

View File

@@ -1,16 +1,52 @@
import { defineStore } from 'pinia'
import { computed } from 'vue'
import { ref, computed } from 'vue'
import { useWebSocketStore } from './websocket'
import { useSensorStore } from './sensor'
import { useRoomStore } from './room'
import { useAnalyticsStore } from './analytics'
/**
* Energy Store - Simplified to only track energy consumption metrics
* For sensor data: use useSensorStore()
* For room data: use useRoomStore()
* For WebSocket: use useWebSocketStore()
* For analytics: use useAnalyticsStore()
*/
export const useEnergyStore = defineStore('energy', () => {
// Get instances of other stores
const websocketStore = useWebSocketStore()
const sensorStore = useSensorStore()
const roomStore = useRoomStore()
const analyticsStore = useAnalyticsStore()
// Initialize data from APIs
// Energy-specific state
const currentConsumption = ref(0) // Current energy consumption in kWh
const averageConsumption = ref(0) // Average energy consumption in kWh
// Computed: Current energy value from WebSocket
const currentEnergyValue = computed(() => {
return websocketStore.latestMessage?.energy?.value || 0
})
// Computed: Average energy usage from time series
const averageEnergyUsage = computed(() => {
const data = websocketStore.timeSeriesData.datasets[0].data
if (data.length === 0) return 0
const sum = data.reduce((acc, val) => acc + val, 0)
return sum / data.length
})
// Update current consumption (called from components or watchers)
function updateCurrentConsumption(value: number) {
currentConsumption.value = value
}
// Update average consumption (called from components or watchers)
function updateAverageConsumption(value: number) {
averageConsumption.value = value
}
// Initialize data from APIs (convenience function for AnalyticsView)
async function initializeFromApi() {
await Promise.allSettled([
roomStore.loadRoomsFromAPI(),
@@ -21,59 +57,30 @@ export const useEnergyStore = defineStore('energy', () => {
}
return {
// Sensor state (delegated)
sensorsData: computed(() => sensorStore.sensorsData),
sensorDevices: computed(() => sensorStore.sensorDevices),
apiSensors: computed(() => Array.from(sensorStore.sensorDevices.values())), // Convert Map to Array
// Energy-specific state
currentConsumption,
averageConsumption,
currentEnergyValue,
averageEnergyUsage,
// Room state (delegated)
roomsData: computed(() => roomStore.roomsData),
availableRooms: computed(() => roomStore.availableRooms),
roomsLoading: computed(() => roomStore.roomsLoading),
roomsLoaded: computed(() => roomStore.roomsLoaded),
// Energy-specific actions
updateCurrentConsumption,
updateAverageConsumption,
// Legacy delegation for AnalyticsView (keep for now to avoid breaking)
apiSensors: computed(() => Array.from(sensorStore.sensorDevices.values())),
apiRooms: computed(() => roomStore.apiRooms),
// Analytics state (delegated)
analyticsData: computed(() => analyticsStore.analyticsData),
systemStatus: computed(() => analyticsStore.systemStatus),
healthStatus: computed(() => analyticsStore.healthStatus),
// Combined API loading/error state
apiLoading: computed(
() => sensorStore.apiLoading || roomStore.apiLoading || analyticsStore.apiLoading,
),
apiError: computed(() => sensorStore.apiError || roomStore.apiError || analyticsStore.apiError),
// Sensor functions (delegated)
updateSensorRoom: sensorStore.updateSensorRoom,
executeSensorAction: sensorStore.executeSensorAction,
getSensorsByRoom: sensorStore.getSensorsByRoom,
getSensorsByType: sensorStore.getSensorsByType,
// Legacy API functions for AnalyticsView
fetchApiSensors: sensorStore.fetchApiSensors,
fetchApiSensorData: sensorStore.fetchApiSensorData,
updateApiSensorMetadata: sensorStore.updateApiSensorMetadata,
deleteApiSensor: sensorStore.deleteApiSensor,
exportApiData: sensorStore.exportApiData,
// Room functions (delegated)
getCO2Status: roomStore.getCO2Status,
loadRoomsFromAPI: roomStore.loadRoomsFromAPI,
addRoom: roomStore.addRoom,
removeRoom: roomStore.removeRoom,
getRoomStats: roomStore.getRoomStats,
getAllRoomsWithStats: roomStore.getAllRoomsWithStats,
fetchApiRooms: roomStore.fetchApiRooms,
fetchApiRoomData: roomStore.fetchApiRoomData,
// Analytics functions (delegated)
fetchAnalyticsSummary: analyticsStore.fetchAnalyticsSummary,
fetchEnergyTrends: analyticsStore.fetchEnergyTrends,
fetchRoomComparison: analyticsStore.fetchRoomComparison,
fetchSystemEvents: analyticsStore.fetchSystemEvents,
fetchSystemStatus: analyticsStore.fetchSystemStatus,
fetchHealthStatus: analyticsStore.fetchHealthStatus,
// Initialize function
initializeFromApi,
}
})

View File

@@ -1,6 +1,6 @@
import { defineStore } from 'pinia'
import { ref, reactive } from 'vue'
import { roomsApi, type RoomInfo as ApiRoomInfo } from '@/services'
import { roomsApi, type RoomInfo as ApiRoomInfo, type SensorReading } from '@/services'
import { useSensorStore } from './sensor'
interface RoomMetrics {
@@ -23,24 +23,6 @@ interface RoomMetrics {
lastUpdated: number
}
interface SensorReading {
sensorId: string
room: string
timestamp: number
energy: {
value: number
unit: string
}
co2: {
value: number
unit: string
}
temperature?: {
value: number
unit: string
}
}
export const useRoomStore = defineStore('room', () => {
// State
const roomsData = reactive<Map<string, RoomMetrics>>(new Map())
@@ -70,7 +52,7 @@ export const useRoomStore = defineStore('room', () => {
if (!roomMetrics) {
roomMetrics = {
room: data.room,
sensors: [data.sensorId],
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',
@@ -80,8 +62,8 @@ export const useRoomStore = defineStore('room', () => {
}
// Update room sensors list
if (!roomMetrics.sensors.includes(data.sensorId)) {
roomMetrics.sensors.push(data.sensorId)
if (!roomMetrics.sensors.includes(data.sensor_id)) {
roomMetrics.sensors.push(data.sensor_id)
}
// Recalculate room metrics from all sensors in the room
@@ -173,7 +155,7 @@ export const useRoomStore = defineStore('room', () => {
// Reassign sensors to empty room
sensorsInRoom.forEach((sensor) => {
sensor.room = ''
sensorStore.sensorDevices.set(sensor.id, { ...sensor })
sensorStore.sensorDevices.set(sensor.sensor_id, { ...sensor })
})
}
@@ -258,9 +240,12 @@ export const useRoomStore = defineStore('room', () => {
async function fetchApiRooms() {
const result = await handleApiCall(() => roomsApi.getRooms())
if (result) {
apiRooms.value = 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 = result.map((room) => room.room).filter((name) => name)
const roomNames = roomsArray.map((room) => room.name || room.room).filter((name) => name)
if (roomNames.length > 0) {
availableRooms.value = [...new Set([...availableRooms.value, ...roomNames])].sort()
}
@@ -301,4 +286,4 @@ export const useRoomStore = defineStore('room', () => {
fetchApiRooms,
fetchApiRoomData,
}
})
})

View File

@@ -12,9 +12,9 @@
<div class="flex items-center gap-2 text-sm text-gray-600">
<div
class="w-3 h-3 rounded-full"
:class="energyStore.isConnected ? 'bg-green-500' : 'bg-red-500'"
:class="websocketStore.isConnected ? 'bg-green-500' : 'bg-red-500'"
></div>
<span>{{ energyStore.isConnected ? 'Connected' : 'Disconnected' }}</span>
<span>{{ websocketStore.isConnected ? 'Connected' : 'Disconnected' }}</span>
<span class="mx-2"></span>
<span>{{ activeOptimizations.length }} active optimizations</span>
</div>
@@ -598,10 +598,10 @@
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useEnergyStore } from '@/stores/energy'
import { ref, computed } from 'vue'
import { useWebSocketStore } from '@/stores/websocket'
const energyStore = useEnergyStore()
const websocketStore = useWebSocketStore()
// Modal states
const showNewOptimizationModal = ref(false)
@@ -725,11 +725,4 @@ const pauseOptimization = (id: number) => {
// In a real app, this would make an API call
}
}
// Initialize connection
onMounted(() => {
if (!energyStore.isConnected) {
energyStore.connect('ws://localhost:8000/ws')
}
})
</script>

View File

@@ -44,7 +44,7 @@
class="px-4 py-2 border border-gray-200 rounded-lg bg-white flex-1"
>
<option value="">All Rooms</option>
<option v-for="room in energyStore.availableRooms" :key="room" :value="room">
<option v-for="room in roomStore.availableRooms" :key="room" :value="room">
{{ room }}
</option>
</select>
@@ -145,7 +145,7 @@
v-for="sensor in filteredSensors"
:key="sensor.sensor_id"
:sensor="sensor"
:available-rooms="energyStore.availableRooms"
:available-rooms="roomStore.availableRooms"
:is-executing-action="isExecutingAction"
@update-room="updateRoom"
@execute-action="executeAction"
@@ -154,7 +154,7 @@
</div>
<!-- Loading State -->
<div v-if="energyStore.apiLoading" class="text-center py-12">
<div v-if="sensorStore.apiLoading" class="text-center py-12">
<div
class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"
></div>
@@ -163,10 +163,10 @@
</div>
<!-- Error State -->
<div v-else-if="energyStore.apiError" class="text-center py-12">
<div v-else-if="sensorStore.apiError" class="text-center py-12">
<div class="text-red-400 text-6xl mb-4"></div>
<h3 class="text-lg font-medium text-gray-900 mb-2">Error loading sensors</h3>
<p class="text-gray-600 mb-4">{{ energyStore.apiError }}</p>
<p class="text-gray-600 mb-4">{{ sensorStore.apiError }}</p>
<button
@click="reloadSensors"
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
@@ -199,7 +199,7 @@
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useSensorStore } from '@/stores/sensor'
import { useEnergyStore } from '@/stores/energy'
import { useRoomStore } from '@/stores/room'
import { useWebSocketStore } from '@/stores/websocket'
import ActionModal from '@/components/modals/ActionModal.vue'
import RoomManagementModal from '@/components/modals/RoomManagementModal.vue'
@@ -207,7 +207,7 @@ import SimpleSensorCard from '@/components/cards/SimpleSensorCard.vue'
import DetailedSensorCard from '@/components/cards/DetailedSensorCard.vue'
const sensorStore = useSensorStore()
const energyStore = useEnergyStore()
const roomStore = useRoomStore()
const websocketStore = useWebSocketStore()
const viewMode = ref<'simple' | 'detailed'>('simple')
@@ -238,7 +238,7 @@ const filteredSensors = computed(() => {
})
const updateRoom = (sensorId: string, newRoom: string) => {
energyStore.updateSensorRoom(sensorId, newRoom)
sensorStore.updateSensorRoom(sensorId, newRoom)
}
const executeAction = (sensor: any, action: any) => {
@@ -254,7 +254,7 @@ const executeAction = (sensor: any, action: any) => {
const handleActionExecute = async (sensorId: string, actionId: string, parameters: any) => {
isExecutingAction.value = true
try {
await energyStore.executeSensorAction(sensorId, actionId, parameters)
await sensorStore.executeSensorAction(sensorId, actionId)
} catch (error) {
console.error('Action execution failed:', error)
} finally {
@@ -274,11 +274,12 @@ const showSensorDetails = () => {
}
const reloadSensors = async () => {
await energyStore.fetchApiSensors()
await sensorStore.fetchApiSensors()
}
onMounted(async () => {
await sensorStore.fetchApiSensors()
await roomStore.fetchApiRooms()
if (!websocketStore.isConnected) {
websocketStore.connect('ws://localhost:8007/ws')
@@ -286,6 +287,6 @@ onMounted(async () => {
})
onUnmounted(() => {
// energyStore.disconnect()
// WebSocket managed globally, no cleanup needed here
})
</script>