Refactor sensor data handling for new API and WebSocket format
- Update SensorConsumptionTable to use new sensorStore and websocketStore - Normalize sensor and reading interfaces for consistency - Remove legacy energy data handling and mapping logic - Update API and store types for new backend schema - Fetch sensors on mount in SensorConsumptionTable - Simplify WebSocket data processing and remove legacy code
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="bg-white rounded-2xl shadow-sm p-4">
|
<div class="bg-white rounded-2xl shadow-sm p-4">
|
||||||
<h6 class="text-sm font-bold text-gray-500 mb-4">Sensor Consumption</h6>
|
<h6 class="text-sm font-bold text-gray-500 mb-4">Sensor Consumption</h6>
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="min-w-full">
|
<table class="min-w-full">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -29,21 +29,37 @@
|
|||||||
No sensor data available. Waiting for WebSocket connection...
|
No sensor data available. Waiting for WebSocket connection...
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-for="sensor in sensorList" :key="sensor.sensorId" class="hover:bg-gray-50">
|
<tr
|
||||||
|
v-for="sensor in sensorStore.latestReadings.values()"
|
||||||
|
:key="sensor.sensor_id"
|
||||||
|
class="hover:bg-gray-50"
|
||||||
|
>
|
||||||
<td class="py-3 text-sm font-medium text-gray-900">
|
<td class="py-3 text-sm font-medium text-gray-900">
|
||||||
{{ sensor.sensorId }}
|
{{ sensor.sensor_id }}
|
||||||
</td>
|
</td>
|
||||||
<td class="py-3 text-sm text-gray-600 text-right">
|
<td class="py-3 text-sm text-gray-600 text-right">
|
||||||
{{ sensor.latestValue.toFixed(2) }} {{ sensor.unit }}
|
{{ sensor.room }}
|
||||||
</td>
|
</td>
|
||||||
<td class="py-3 text-sm text-gray-600 text-right">
|
<td class="py-3 text-sm text-gray-600 text-right">
|
||||||
{{ sensor.totalConsumption.toFixed(2) }} {{ sensor.unit }}
|
{{
|
||||||
|
sensor.energy?.value ||
|
||||||
|
sensor.co2?.value ||
|
||||||
|
sensor.temperature?.value ||
|
||||||
|
sensor.humidity?.value ||
|
||||||
|
'N/A'
|
||||||
|
}}
|
||||||
|
{{
|
||||||
|
sensor.energy?.unit ||
|
||||||
|
sensor.co2?.unit ||
|
||||||
|
sensor.temperature?.unit ||
|
||||||
|
sensor.humidity?.unit
|
||||||
|
}}
|
||||||
</td>
|
</td>
|
||||||
<td class="py-3 text-sm text-gray-600 text-right">
|
<td class="py-3 text-sm text-gray-600 text-right">
|
||||||
{{ sensor.averageConsumption.toFixed(2) }} {{ sensor.unit }}
|
{{ sensor.room }}
|
||||||
</td>
|
</td>
|
||||||
<td class="py-3 text-sm text-gray-500 text-right">
|
<td class="py-3 text-sm text-gray-500 text-right">
|
||||||
{{ formatTime(sensor.lastUpdated) }}
|
{{ formatTime(sensor.timestamp) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -53,29 +69,27 @@
|
|||||||
<!-- Connection Status Indicator -->
|
<!-- Connection Status Indicator -->
|
||||||
<div class="mt-4 flex items-center justify-between text-xs text-gray-500">
|
<div class="mt-4 flex items-center justify-between text-xs text-gray-500">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<div
|
<div
|
||||||
class="w-2 h-2 rounded-full"
|
class="w-2 h-2 rounded-full"
|
||||||
:class="energyStore.isConnected ? 'bg-green-500' : 'bg-red-500'"
|
:class="websocketStore.isConnected ? 'bg-green-500' : 'bg-red-500'"
|
||||||
></div>
|
></div>
|
||||||
<span>{{ energyStore.isConnected ? 'Connected' : 'Disconnected' }}</span>
|
<span>{{ websocketStore.isConnected ? 'Connected' : 'Disconnected' }}</span>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{{ sensorList.length }} sensor{{ sensorList.length !== 1 ? 's' : '' }} active
|
|
||||||
</div>
|
</div>
|
||||||
|
<div>{{ sensorList.length }} sensor{{ sensorList.length !== 1 ? 's' : '' }} active</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed, onMounted } from 'vue'
|
||||||
import { useEnergyStore } from '@/stores/energy'
|
import { useSensorStore } from '@/stores/sensor'
|
||||||
|
import { useWebSocketStore } from '@/stores/websocket'
|
||||||
|
|
||||||
const energyStore = useEnergyStore()
|
const sensorStore = useSensorStore()
|
||||||
|
const websocketStore = useWebSocketStore()
|
||||||
|
|
||||||
const sensorList = computed(() => {
|
const sensorList = computed(() => {
|
||||||
return Array.from(energyStore.sensorsData.values()).sort((a, b) =>
|
return Array.from(sensorStore.sensorDevices.values()).sort((a, b) => a.name.localeCompare(b.name))
|
||||||
a.sensorId.localeCompare(b.sensorId)
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const formatTime = (timestamp: number) => {
|
const formatTime = (timestamp: number) => {
|
||||||
@@ -83,7 +97,7 @@ const formatTime = (timestamp: number) => {
|
|||||||
const now = new Date()
|
const now = new Date()
|
||||||
const diffMs = now.getTime() - date.getTime()
|
const diffMs = now.getTime() - date.getTime()
|
||||||
const diffSecs = Math.floor(diffMs / 1000)
|
const diffSecs = Math.floor(diffMs / 1000)
|
||||||
|
|
||||||
if (diffSecs < 60) {
|
if (diffSecs < 60) {
|
||||||
return `${diffSecs}s ago`
|
return `${diffSecs}s ago`
|
||||||
} else if (diffSecs < 3600) {
|
} else if (diffSecs < 3600) {
|
||||||
@@ -92,4 +106,8 @@ const formatTime = (timestamp: number) => {
|
|||||||
return date.toLocaleTimeString()
|
return date.toLocaleTimeString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await sensorStore.fetchApiSensors()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|||||||
@@ -50,12 +50,11 @@ export interface DataResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SensorReading {
|
export interface SensorReading {
|
||||||
_id?: string
|
|
||||||
sensor_id: string
|
sensor_id: string
|
||||||
room?: string
|
room: string
|
||||||
sensor_type: string
|
sensor_type: string
|
||||||
timestamp: number
|
timestamp: number
|
||||||
created_at?: string
|
type: string
|
||||||
energy?: {
|
energy?: {
|
||||||
value: number
|
value: number
|
||||||
unit: string
|
unit: string
|
||||||
@@ -176,29 +175,18 @@ export interface SystemEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SensorDevice {
|
export interface SensorDevice {
|
||||||
id: string
|
_id: string
|
||||||
sensor_id: string
|
sensor_id: string
|
||||||
name: string
|
name: string
|
||||||
type: SensorType
|
type: SensorType
|
||||||
sensor_type: SensorType
|
sensor_type: SensorType
|
||||||
room: string
|
room: string
|
||||||
status: SensorStatus
|
status: SensorStatus
|
||||||
location?: string
|
|
||||||
lastSeen: number
|
|
||||||
total_readings?: number
|
|
||||||
capabilities: {
|
capabilities: {
|
||||||
monitoring: string[]
|
monitoring: string[]
|
||||||
actions: SensorAction[]
|
actions: SensorAction[]
|
||||||
}
|
}
|
||||||
metadata: {
|
metadata: SensorMetadata
|
||||||
location?: string
|
|
||||||
model?: string
|
|
||||||
firmware?: string
|
|
||||||
battery?: number
|
|
||||||
created_at?: string
|
|
||||||
updated_at?: string
|
|
||||||
manufacturer?: string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SensorAction {
|
export interface SensorAction {
|
||||||
@@ -245,7 +233,20 @@ export enum SensorStatus {
|
|||||||
ERROR = 'error',
|
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 {
|
class ApiClient {
|
||||||
private baseUrl: string
|
private baseUrl: string
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
type DataResponse,
|
type DataResponse,
|
||||||
type SensorType,
|
type SensorType,
|
||||||
type SensorStatus,
|
type SensorStatus,
|
||||||
|
type SensorMetadata,
|
||||||
} from './api'
|
} from './api'
|
||||||
|
|
||||||
export const sensorsApi = {
|
export const sensorsApi = {
|
||||||
@@ -13,8 +14,24 @@ export const sensorsApi = {
|
|||||||
room?: string
|
room?: string
|
||||||
sensor_type?: SensorType
|
sensor_type?: SensorType
|
||||||
status?: SensorStatus
|
status?: SensorStatus
|
||||||
}): Promise<SensorDevice[]> {
|
}): Promise<{
|
||||||
return apiClient.get<SensorDevice[]>('/api/v1/sensors/get', params)
|
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<SensorDevice> {
|
async getSensor(sensorId: string): Promise<SensorDevice> {
|
||||||
@@ -39,7 +56,7 @@ export const sensorsApi = {
|
|||||||
|
|
||||||
async updateSensorMetadata(
|
async updateSensorMetadata(
|
||||||
sensorId: string,
|
sensorId: string,
|
||||||
metadata: Record<string, any>,
|
metadata: SensorMetadata,
|
||||||
): Promise<{ message: string }> {
|
): Promise<{ message: string }> {
|
||||||
return apiClient.put<{ message: string }>(`/api/v1/sensors/${sensorId}/metadata`, metadata)
|
return apiClient.put<{ message: string }>(`/api/v1/sensors/${sensorId}/metadata`, metadata)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,18 +3,12 @@ import { computed } from 'vue'
|
|||||||
import { useSensorStore } from './sensor'
|
import { useSensorStore } from './sensor'
|
||||||
import { useRoomStore } from './room'
|
import { useRoomStore } from './room'
|
||||||
import { useAnalyticsStore } from './analytics'
|
import { useAnalyticsStore } from './analytics'
|
||||||
import { useWebSocketStore } from './websocket'
|
|
||||||
|
|
||||||
export const useEnergyStore = defineStore('energy', () => {
|
export const useEnergyStore = defineStore('energy', () => {
|
||||||
// Get instances of other stores
|
// Get instances of other stores
|
||||||
const sensorStore = useSensorStore()
|
const sensorStore = useSensorStore()
|
||||||
const roomStore = useRoomStore()
|
const roomStore = useRoomStore()
|
||||||
const analyticsStore = useAnalyticsStore()
|
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
|
// Initialize data from APIs
|
||||||
async function initializeFromApi() {
|
async function initializeFromApi() {
|
||||||
@@ -27,15 +21,9 @@ export const useEnergyStore = defineStore('energy', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// WebSocket state (delegated)
|
|
||||||
isConnected: computed(() => webSocketStore.isConnected),
|
|
||||||
latestMessage: computed(() => webSocketStore.latestMessage),
|
|
||||||
timeSeriesData: computed(() => webSocketStore.timeSeriesData),
|
|
||||||
|
|
||||||
// Sensor state (delegated)
|
// Sensor state (delegated)
|
||||||
sensorsData: computed(() => sensorStore.sensorsData),
|
sensorsData: computed(() => sensorStore.sensorsData),
|
||||||
sensorDevices: computed(() => sensorStore.sensorDevices),
|
sensorDevices: computed(() => sensorStore.sensorDevices),
|
||||||
latestReadings: computed(() => sensorStore.latestReadings),
|
|
||||||
apiSensors: computed(() => Array.from(sensorStore.sensorDevices.values())), // Convert Map to Array
|
apiSensors: computed(() => Array.from(sensorStore.sensorDevices.values())), // Convert Map to Array
|
||||||
|
|
||||||
// Room state (delegated)
|
// Room state (delegated)
|
||||||
@@ -51,13 +39,11 @@ export const useEnergyStore = defineStore('energy', () => {
|
|||||||
healthStatus: computed(() => analyticsStore.healthStatus),
|
healthStatus: computed(() => analyticsStore.healthStatus),
|
||||||
|
|
||||||
// Combined API loading/error state
|
// 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),
|
apiError: computed(() => sensorStore.apiError || roomStore.apiError || analyticsStore.apiError),
|
||||||
|
|
||||||
// WebSocket functions (delegated)
|
|
||||||
connect,
|
|
||||||
disconnect,
|
|
||||||
|
|
||||||
// Sensor functions (delegated)
|
// Sensor functions (delegated)
|
||||||
updateSensorRoom: sensorStore.updateSensorRoom,
|
updateSensorRoom: sensorStore.updateSensorRoom,
|
||||||
executeSensorAction: sensorStore.executeSensorAction,
|
executeSensorAction: sensorStore.executeSensorAction,
|
||||||
|
|||||||
@@ -3,37 +3,11 @@ import { ref, reactive } from 'vue'
|
|||||||
import {
|
import {
|
||||||
sensorsApi,
|
sensorsApi,
|
||||||
SensorType,
|
SensorType,
|
||||||
SensorStatus,
|
|
||||||
type SensorDevice,
|
type SensorDevice,
|
||||||
type SensorAction,
|
type SensorStatus,
|
||||||
|
type SensorReading,
|
||||||
} from '@/services'
|
} 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', () => {
|
export const useSensorStore = defineStore('sensor', () => {
|
||||||
// State
|
// State
|
||||||
const sensorDevices = reactive<Map<string, SensorDevice>>(new Map())
|
const sensorDevices = reactive<Map<string, SensorDevice>>(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)
|
const sensor = sensorDevices.get(sensorId)
|
||||||
if (!sensor) return false
|
if (!sensor) return false
|
||||||
|
|
||||||
@@ -75,49 +49,18 @@ export const useSensorStore = defineStore('sensor', () => {
|
|||||||
return Array.from(sensorDevices.values()).filter((sensor) => sensor.type === type)
|
return Array.from(sensorDevices.values()).filter((sensor) => sensor.type === type)
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSensorData(data: LegacyEnergyData) {
|
function updateEnergySensors(data: Sensor) {
|
||||||
const existingSensor = sensorsData.get(data.sensorId)
|
console.log(data)
|
||||||
|
|
||||||
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 updateLatestReading(reading: SensorReading) {
|
function updateLatestReading(reading: SensorReading) {
|
||||||
latestReadings.set(reading.sensorId, reading)
|
latestReadings.set(reading.sensor_id, reading)
|
||||||
|
|
||||||
// Mark sensor as recently updated
|
// Mark sensor as recently updated
|
||||||
recentlyUpdatedSensors.add(reading.sensorId)
|
recentlyUpdatedSensors.add(reading.sensor_id)
|
||||||
console.log(reading.sensor_type)
|
|
||||||
// Remove from recently updated after 2 seconds
|
// Remove from recently updated after 2 seconds
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
recentlyUpdatedSensors.delete(reading.sensorId)
|
recentlyUpdatedSensors.delete(reading.sensor_id)
|
||||||
}, 2000)
|
}, 2000)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,17 +168,21 @@ export const useSensorStore = defineStore('sensor', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sensors API functions
|
// 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))
|
const result = await handleApiCall(() => sensorsApi.getSensors(params))
|
||||||
if (result) {
|
if (result) {
|
||||||
|
console.log(result)
|
||||||
// Check if result has a sensors property (common API pattern)
|
// Check if result has a sensors property (common API pattern)
|
||||||
if (result.sensors && Array.isArray(result.sensors)) {
|
if (result.sensors && Array.isArray(result.sensors)) {
|
||||||
result.sensors.forEach((sensor) => {
|
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 sensorType = sensor.sensor_type || sensor.type
|
||||||
const sensorName = sensor.name || ''
|
const sensorName = sensor.name || ''
|
||||||
|
|
||||||
// Normalize sensor data structure for frontend compatibility
|
|
||||||
const normalizedSensor = {
|
const normalizedSensor = {
|
||||||
...sensor,
|
...sensor,
|
||||||
id: sensorKey,
|
id: sensorKey,
|
||||||
@@ -255,58 +202,18 @@ export const useSensorStore = defineStore('sensor', () => {
|
|||||||
signalStrength: sensor.metadata?.signalStrength,
|
signalStrength: sensor.metadata?.signalStrength,
|
||||||
...sensor.metadata,
|
...sensor.metadata,
|
||||||
},
|
},
|
||||||
tags: sensor.tags || [],
|
lastSeen: sensor.last_seen || Date.now() / 1000,
|
||||||
lastSeen: sensor.last_seen || sensor.lastSeen || Date.now() / 1000,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sensorDevices.set(sensorKey, normalizedSensor)
|
sensorDevices.set(sensorKey, normalizedSensor)
|
||||||
})
|
})
|
||||||
}
|
} else {
|
||||||
// Check if result is directly an array
|
console.warn('Unexpected result format:', typeof result, result)
|
||||||
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 {
|
} 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
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,11 +251,11 @@ export const useSensorStore = defineStore('sensor', () => {
|
|||||||
apiError,
|
apiError,
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
updateEnergySensors,
|
||||||
updateSensorRoom,
|
updateSensorRoom,
|
||||||
executeSensorAction,
|
executeSensorAction,
|
||||||
getSensorsByRoom,
|
getSensorsByRoom,
|
||||||
getSensorsByType,
|
getSensorsByType,
|
||||||
updateSensorData,
|
|
||||||
updateLatestReading,
|
updateLatestReading,
|
||||||
|
|
||||||
// API functions
|
// API functions
|
||||||
|
|||||||
@@ -5,13 +5,6 @@ import { useRoomStore } from './room'
|
|||||||
|
|
||||||
const MAX_DATA_POINTS = 100
|
const MAX_DATA_POINTS = 100
|
||||||
|
|
||||||
interface LegacyEnergyData {
|
|
||||||
sensorId: string
|
|
||||||
timestamp: number
|
|
||||||
value: number
|
|
||||||
unit: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SensorReading {
|
interface SensorReading {
|
||||||
sensorId: string
|
sensorId: string
|
||||||
room: string
|
room: string
|
||||||
@@ -31,9 +24,8 @@ interface SensorReading {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useWebSocketStore = defineStore('websocket', () => {
|
export const useWebSocketStore = defineStore('websocket', () => {
|
||||||
// State
|
|
||||||
const isConnected = ref(false)
|
const isConnected = ref(false)
|
||||||
const latestMessage = ref<LegacyEnergyData | null>(null)
|
const latestMessage = ref<SensorReading | null>(null)
|
||||||
const timeSeriesData = reactive<{
|
const timeSeriesData = reactive<{
|
||||||
labels: string[]
|
labels: string[]
|
||||||
datasets: { data: number[] }[]
|
datasets: { data: number[] }[]
|
||||||
@@ -43,16 +35,14 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
let socket: WebSocket | null = null
|
let socket: WebSocket | null = null
|
||||||
const newDataBuffer: (LegacyEnergyData | SensorReading)[] = []
|
const newDataBuffer: SensorReading[] = []
|
||||||
|
|
||||||
// Actions
|
|
||||||
function connect(url: string) {
|
function connect(url: string) {
|
||||||
if (isConnected.value && socket) {
|
if (isConnected.value && socket) {
|
||||||
console.log('Already connected.')
|
console.log('Already connected.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close any existing connection first
|
|
||||||
if (socket) {
|
if (socket) {
|
||||||
socket.onclose = null
|
socket.onclose = null
|
||||||
socket.onerror = null
|
socket.onerror = null
|
||||||
@@ -61,11 +51,11 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
|||||||
socket = null
|
socket = null
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Connecting to WebSocket at ${url}`)
|
console.info(`Connecting to WebSocket at ${url}`)
|
||||||
socket = new WebSocket(url)
|
socket = new WebSocket(url)
|
||||||
|
|
||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
console.log('WebSocket connection established.')
|
console.info('WebSocket connection established.')
|
||||||
isConnected.value = true
|
isConnected.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +64,7 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
|||||||
const data = JSON.parse(event.data)
|
const data = JSON.parse(event.data)
|
||||||
|
|
||||||
if (data.type === 'proxy_info' && data.sensor_service_url) {
|
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) {
|
if (socket) {
|
||||||
socket.onclose = null
|
socket.onclose = null
|
||||||
socket.close()
|
socket.close()
|
||||||
@@ -83,7 +73,7 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
|||||||
isConnected.value = false
|
isConnected.value = false
|
||||||
|
|
||||||
setTimeout(() => {
|
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')
|
connect('ws://localhost:8007/ws')
|
||||||
}, 100)
|
}, 100)
|
||||||
return
|
return
|
||||||
@@ -125,155 +115,41 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLegacyData(data: any): data is LegacyEnergyData {
|
function processIncomingData(data: SensorReading) {
|
||||||
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) {
|
|
||||||
// Skip non-data messages
|
// 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')) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize property names: sensor_id -> sensorId
|
|
||||||
if ('sensor_id' in data && !('sensorId' in data)) {
|
|
||||||
data.sensorId = data.sensor_id
|
|
||||||
}
|
|
||||||
|
|
||||||
const sensorStore = useSensorStore()
|
const sensorStore = useSensorStore()
|
||||||
const roomStore = useRoomStore()
|
const roomStore = useRoomStore()
|
||||||
|
|
||||||
// Handle both legacy and new data formats
|
// Handle new multi-metric data
|
||||||
if (isLegacyData(data)) {
|
// Only update room data if we have the proper structure
|
||||||
latestMessage.value = data
|
if (data.energy && data.co2 && data.room) {
|
||||||
sensorStore.updateSensorData(data)
|
if (data.energy) {
|
||||||
|
sensorStore.updateEnergySensors(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',
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
sensorStore.updateLatestReading(sensorReading)
|
roomStore.updateRoomData(data)
|
||||||
|
}
|
||||||
|
|
||||||
// Update time series for chart
|
// Map the sensor ID for individual sensor updates
|
||||||
const newLabel = new Date(data.timestamp * 1000).toLocaleTimeString()
|
// const mappedSensorId = mapWebSocketSensorId(data.sensorId)
|
||||||
timeSeriesData.labels.push(newLabel)
|
const mappedData = { ...data, sensorId: data.sensorId, id: data.sensorId }
|
||||||
timeSeriesData.datasets[0].data.push(data.value)
|
sensorStore.updateLatestReading(data) // Update individual sensor readings for cards
|
||||||
} 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
|
|
||||||
|
|
||||||
|
if (data.energy) {
|
||||||
// Update time series for chart (use energy values if available)
|
// Update time series for chart (use energy values if available)
|
||||||
const newLabel = new Date(data.timestamp * 1000).toLocaleTimeString()
|
const newLabel = new Date(data.timestamp * 1000).toLocaleTimeString()
|
||||||
timeSeriesData.labels.push(newLabel)
|
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
|
// Keep only the latest data points
|
||||||
if (timeSeriesData.labels.length > MAX_DATA_POINTS) {
|
if (timeSeriesData.labels.length > MAX_DATA_POINTS) {
|
||||||
timeSeriesData.labels.shift()
|
timeSeriesData.labels.shift()
|
||||||
timeSeriesData.datasets[0].data.shift()
|
timeSeriesData.datasets[0].data.shift()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user