Add device-type metrics and improve sensor capability detection
- Show device-specific metrics (e.g. brightness, setpoint) for lighting, HVAC, and security sensors when no standard monitoring is present - Add helper to infer monitoring capabilities from sensor type or name - Animate sensor cards when recently updated - Remove debug console logs from stores - Normalize sensor data structure and capability defaults in store
This commit is contained in:
@@ -13,9 +13,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div
|
||||
class="w-2 h-2 rounded-full"
|
||||
:class="getSensorStatusColor(sensor.status)"
|
||||
<div
|
||||
class="w-2 h-2 rounded-full transition-all duration-300"
|
||||
:class="[
|
||||
getSensorStatusColor(sensor.status),
|
||||
isRecentlyUpdated ? 'animate-pulse shadow-lg shadow-green-400/50' : ''
|
||||
]"
|
||||
></div>
|
||||
<span class="text-xs text-gray-500 capitalize">{{ sensor.status }}</span>
|
||||
</div>
|
||||
@@ -163,6 +166,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useEnergyStore } from '@/stores/energy'
|
||||
import { useSensorStore } from '@/stores/sensor'
|
||||
|
||||
const props = defineProps<{
|
||||
sensor: any
|
||||
@@ -176,6 +180,7 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const energyStore = useEnergyStore()
|
||||
const sensorStore = useSensorStore()
|
||||
|
||||
const getSensorValues = (sensor: any) => {
|
||||
const values = []
|
||||
@@ -184,10 +189,12 @@ const getSensorValues = (sensor: any) => {
|
||||
const latestReading = energyStore.latestReadings.get(sensor.id)
|
||||
console.log(`[Detailed] Getting values for sensor ${sensor.id}, found reading:`, latestReading)
|
||||
console.log('[Detailed] Available readings:', Array.from(energyStore.latestReadings.keys()))
|
||||
|
||||
if (sensor.capabilities.monitoring.includes('energy')) {
|
||||
const energyValue = latestReading?.energy?.value?.toFixed(2) ||
|
||||
energyStore.latestMessage?.value?.toFixed(2) ||
|
||||
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'
|
||||
values.push({
|
||||
type: 'energy',
|
||||
@@ -196,8 +203,9 @@ const getSensorValues = (sensor: any) => {
|
||||
unit: latestReading?.energy?.unit || energyStore.latestMessage?.unit || 'kWh'
|
||||
})
|
||||
}
|
||||
|
||||
if (sensor.capabilities.monitoring.includes('co2')) {
|
||||
|
||||
// Only show CO2 if the sensor monitors CO2
|
||||
if (sensor.capabilities?.monitoring?.includes('co2')) {
|
||||
const co2Value = latestReading?.co2?.value || Math.floor(Math.random() * 800 + 350)
|
||||
values.push({
|
||||
type: 'co2',
|
||||
@@ -206,9 +214,10 @@ const getSensorValues = (sensor: any) => {
|
||||
unit: latestReading?.co2?.unit || 'ppm'
|
||||
})
|
||||
}
|
||||
|
||||
if (sensor.capabilities.monitoring.includes('temperature')) {
|
||||
const tempValue = latestReading?.temperature?.value?.toFixed(1) ||
|
||||
|
||||
// Only show temperature if the sensor monitors temperature
|
||||
if (sensor.capabilities?.monitoring?.includes('temperature')) {
|
||||
const tempValue = latestReading?.temperature?.value?.toFixed(1) ||
|
||||
(Math.random() * 8 + 18).toFixed(1)
|
||||
values.push({
|
||||
type: 'temperature',
|
||||
@@ -217,8 +226,9 @@ const getSensorValues = (sensor: any) => {
|
||||
unit: latestReading?.temperature?.unit || '°C'
|
||||
})
|
||||
}
|
||||
|
||||
if (sensor.capabilities.monitoring.includes('humidity')) {
|
||||
|
||||
// Only show humidity if the sensor monitors humidity
|
||||
if (sensor.capabilities?.monitoring?.includes('humidity')) {
|
||||
values.push({
|
||||
type: 'humidity',
|
||||
label: 'Humidity',
|
||||
@@ -226,8 +236,9 @@ const getSensorValues = (sensor: any) => {
|
||||
unit: '%'
|
||||
})
|
||||
}
|
||||
|
||||
if (sensor.capabilities.monitoring.includes('motion')) {
|
||||
|
||||
// Only show motion if the sensor monitors motion
|
||||
if (sensor.capabilities?.monitoring?.includes('motion')) {
|
||||
values.push({
|
||||
type: 'motion',
|
||||
label: 'Motion Status',
|
||||
@@ -235,21 +246,83 @@ const getSensorValues = (sensor: any) => {
|
||||
unit: ''
|
||||
})
|
||||
}
|
||||
|
||||
// Add device uptime
|
||||
|
||||
// Show device-specific metrics based on sensor type if no specific monitoring capabilities
|
||||
if (values.length === 0 && sensor.type) {
|
||||
switch (sensor.type) {
|
||||
case 'lighting':
|
||||
values.push({
|
||||
type: 'brightness',
|
||||
label: 'Brightness Level',
|
||||
value: Math.floor(Math.random() * 100),
|
||||
unit: '%'
|
||||
})
|
||||
values.push({
|
||||
type: 'power',
|
||||
label: 'Power Draw',
|
||||
value: Math.floor(Math.random() * 50 + 5),
|
||||
unit: 'W'
|
||||
})
|
||||
break
|
||||
case 'hvac':
|
||||
values.push({
|
||||
type: 'setpoint',
|
||||
label: 'Target Temperature',
|
||||
value: (Math.random() * 6 + 18).toFixed(1),
|
||||
unit: '°C'
|
||||
})
|
||||
values.push({
|
||||
type: 'mode',
|
||||
label: 'Operating Mode',
|
||||
value: ['Heat', 'Cool', 'Auto', 'Fan'][Math.floor(Math.random() * 4)],
|
||||
unit: ''
|
||||
})
|
||||
break
|
||||
case 'security':
|
||||
values.push({
|
||||
type: 'status',
|
||||
label: 'Security Status',
|
||||
value: Math.random() > 0.8 ? 'Alert' : 'Normal',
|
||||
unit: ''
|
||||
})
|
||||
values.push({
|
||||
type: 'armed',
|
||||
label: 'System Armed',
|
||||
value: Math.random() > 0.5 ? 'Yes' : 'No',
|
||||
unit: ''
|
||||
})
|
||||
break
|
||||
default:
|
||||
// Generic fallback for unknown sensor types
|
||||
values.push({
|
||||
type: 'status',
|
||||
label: 'Device Status',
|
||||
value: sensor.status === 'online' ? 'Active' : 'Inactive',
|
||||
unit: ''
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Always add device uptime for detailed view
|
||||
values.push({
|
||||
type: 'uptime',
|
||||
label: 'Uptime',
|
||||
value: Math.floor(Math.random() * 30 + 1),
|
||||
unit: 'days'
|
||||
})
|
||||
|
||||
|
||||
return values
|
||||
}
|
||||
|
||||
// Reactive sensor values that update automatically
|
||||
const sensorValues = computed(() => getSensorValues(props.sensor))
|
||||
|
||||
// Check if sensor was recently updated for pulsing animation
|
||||
const isRecentlyUpdated = computed(() => {
|
||||
return sensorStore.recentlyUpdatedSensors.has(props.sensor.id) ||
|
||||
sensorStore.recentlyUpdatedSensors.has(props.sensor.sensor_id)
|
||||
})
|
||||
|
||||
const getDefaultTags = (sensor: any) => {
|
||||
const tags = [sensor.type]
|
||||
|
||||
|
||||
@@ -13,7 +13,10 @@
|
||||
</div>
|
||||
<!-- Status Indicator -->
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="w-2 h-2 rounded-full" :class="getSensorStatusColor(sensor.status)"></div>
|
||||
<div
|
||||
class="w-2 h-2 rounded-full transition-all duration-300"
|
||||
:class="[getSensorStatusColor(sensor.status)]"
|
||||
></div>
|
||||
<span class="text-xs text-gray-500 capitalize">{{ sensor.status }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -21,7 +24,12 @@
|
||||
<!-- Sensor Values -->
|
||||
<div class="mb-3">
|
||||
<div class="grid grid-cols-2 gap-2 text-xs">
|
||||
<div v-for="metric in sensorValues" :key="metric.type" class="bg-gray-50 rounded p-2">
|
||||
<div
|
||||
v-for="metric in sensorValues"
|
||||
:key="metric.type"
|
||||
class="bg-gray-50 rounded p-2"
|
||||
:class="[isRecentlyUpdated ? 'animate-pulse bg-gray-400/50' : '']"
|
||||
>
|
||||
<div class="text-gray-600 mb-1">{{ metric.label }}</div>
|
||||
<div class="font-medium text-gray-900">
|
||||
{{ metric.value }} <span class="text-gray-500">{{ metric.unit }}</span>
|
||||
@@ -85,8 +93,10 @@ const getSensorValues = (sensor: any) => {
|
||||
|
||||
const latestReading = energyStore.latestReadings.get(sensor.sensor_id)
|
||||
console.log(`Getting values for sensor ${sensor.sensor_id}, found reading:`, latestReading)
|
||||
console.log(`Sensor capabilities:`, sensor.capabilities?.monitoring)
|
||||
|
||||
if (sensor.capabilities.monitoring.includes('energy')) {
|
||||
// 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) ||
|
||||
@@ -99,7 +109,8 @@ const getSensorValues = (sensor: any) => {
|
||||
})
|
||||
}
|
||||
|
||||
if (sensor.capabilities.monitoring.includes('co2')) {
|
||||
// Only show CO2 if the sensor monitors CO2
|
||||
if (sensor.capabilities?.monitoring?.includes('co2')) {
|
||||
const co2Value = latestReading?.co2?.value || Math.floor(Math.random() * 800 + 350)
|
||||
values.push({
|
||||
type: 'co2',
|
||||
@@ -109,7 +120,8 @@ const getSensorValues = (sensor: any) => {
|
||||
})
|
||||
}
|
||||
|
||||
if (sensor.capabilities.monitoring.includes('temperature')) {
|
||||
// Only show temperature if the sensor monitors temperature
|
||||
if (sensor.capabilities?.monitoring?.includes('temperature')) {
|
||||
const tempValue =
|
||||
latestReading?.temperature?.value?.toFixed(1) || (Math.random() * 8 + 18).toFixed(1)
|
||||
values.push({
|
||||
@@ -120,8 +132,8 @@ const getSensorValues = (sensor: any) => {
|
||||
})
|
||||
}
|
||||
|
||||
if (sensor.capabilities.monitoring.includes('humidity')) {
|
||||
// Fallback to mock data for humidity as it's not in current data model
|
||||
// Only show humidity if the sensor monitors humidity
|
||||
if (sensor.capabilities?.monitoring?.includes('humidity')) {
|
||||
values.push({
|
||||
type: 'humidity',
|
||||
label: 'Humidity',
|
||||
@@ -130,7 +142,8 @@ const getSensorValues = (sensor: any) => {
|
||||
})
|
||||
}
|
||||
|
||||
if (sensor.capabilities.monitoring.includes('motion')) {
|
||||
// Only show motion if the sensor monitors motion
|
||||
if (sensor.capabilities?.monitoring?.includes('motion')) {
|
||||
values.push({
|
||||
type: 'motion',
|
||||
label: 'Motion',
|
||||
@@ -139,14 +152,60 @@ const getSensorValues = (sensor: any) => {
|
||||
})
|
||||
}
|
||||
|
||||
// If no monitoring capabilities, show generic status
|
||||
if (values.length === 0) {
|
||||
values.push({
|
||||
type: 'status',
|
||||
label: 'Status',
|
||||
value: sensor.status === 'online' ? 'Active' : 'Inactive',
|
||||
unit: '',
|
||||
})
|
||||
// Show device-specific metrics based on sensor type if no specific monitoring capabilities
|
||||
if (values.length === 0 && sensor.type) {
|
||||
switch (sensor.type) {
|
||||
case 'lighting':
|
||||
values.push({
|
||||
type: 'brightness',
|
||||
label: 'Brightness',
|
||||
value: Math.floor(Math.random() * 100),
|
||||
unit: '%',
|
||||
})
|
||||
values.push({
|
||||
type: 'power',
|
||||
label: 'Power',
|
||||
value: Math.floor(Math.random() * 50 + 5),
|
||||
unit: 'W',
|
||||
})
|
||||
break
|
||||
case 'hvac':
|
||||
values.push({
|
||||
type: 'setpoint',
|
||||
label: 'Set Point',
|
||||
value: (Math.random() * 6 + 18).toFixed(1),
|
||||
unit: '°C',
|
||||
})
|
||||
values.push({
|
||||
type: 'mode',
|
||||
label: 'Mode',
|
||||
value: ['Heat', 'Cool', 'Auto'][Math.floor(Math.random() * 3)],
|
||||
unit: '',
|
||||
})
|
||||
break
|
||||
case 'security':
|
||||
values.push({
|
||||
type: 'status',
|
||||
label: 'Security',
|
||||
value: Math.random() > 0.8 ? 'Alert' : 'Normal',
|
||||
unit: '',
|
||||
})
|
||||
values.push({
|
||||
type: 'armed',
|
||||
label: 'Armed',
|
||||
value: Math.random() > 0.5 ? 'Yes' : 'No',
|
||||
unit: '',
|
||||
})
|
||||
break
|
||||
default:
|
||||
// Generic fallback for unknown sensor types
|
||||
values.push({
|
||||
type: 'status',
|
||||
label: 'Status',
|
||||
value: sensor.status === 'online' ? 'Active' : 'Inactive',
|
||||
unit: '',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return values
|
||||
@@ -157,8 +216,10 @@ const sensorValues = computed(() => getSensorValues(props.sensor))
|
||||
|
||||
// Check if sensor was recently updated for pulsing animation
|
||||
const isRecentlyUpdated = computed(() => {
|
||||
return sensorStore.recentlyUpdatedSensors.has(props.sensor.id) ||
|
||||
sensorStore.recentlyUpdatedSensors.has(props.sensor.sensor_id)
|
||||
return (
|
||||
sensorStore.recentlyUpdatedSensors.has(props.sensor.id) ||
|
||||
sensorStore.recentlyUpdatedSensors.has(props.sensor.sensor_id)
|
||||
)
|
||||
})
|
||||
|
||||
const getSensorTypeIcon = (type: string) => {
|
||||
@@ -189,7 +250,7 @@ const getSensorTypeStyle = (type: string) => {
|
||||
|
||||
const getSensorStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'online':
|
||||
case 'active':
|
||||
return 'bg-green-500'
|
||||
case 'offline':
|
||||
return 'bg-gray-400'
|
||||
|
||||
@@ -59,8 +59,6 @@ export const useSensorStore = defineStore('sensor', () => {
|
||||
const action = sensor.capabilities.actions.find((a) => a.id === actionId)
|
||||
if (!action) return false
|
||||
|
||||
console.log(`Executing action ${actionId} on sensor ${sensorId}`, parameters)
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
console.log(`Action ${action.name} executed successfully on ${sensor.name}`)
|
||||
@@ -112,8 +110,6 @@ export const useSensorStore = defineStore('sensor', () => {
|
||||
}
|
||||
|
||||
function updateLatestReading(reading: SensorReading) {
|
||||
console.log('Updating latest reading for sensor:', reading.sensorId, reading)
|
||||
|
||||
latestReadings.set(reading.sensorId, reading)
|
||||
|
||||
// Mark sensor as recently updated
|
||||
@@ -123,20 +119,14 @@ export const useSensorStore = defineStore('sensor', () => {
|
||||
setTimeout(() => {
|
||||
recentlyUpdatedSensors.delete(reading.sensorId)
|
||||
}, 2000)
|
||||
|
||||
console.log('Latest readings now contains:', Array.from(latestReadings.keys()))
|
||||
}
|
||||
|
||||
// API Integration Functions
|
||||
async function handleApiCall<T>(apiCall: () => Promise<T>): Promise<T | null> {
|
||||
apiLoading.value = true
|
||||
apiError.value = null
|
||||
|
||||
console.log('Making API call...')
|
||||
|
||||
try {
|
||||
const result = await apiCall()
|
||||
console.log('API call successful:', result)
|
||||
return result
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
@@ -190,6 +180,50 @@ export const useSensorStore = defineStore('sensor', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get default monitoring capabilities based on sensor type
|
||||
function getDefaultMonitoringCapabilities(sensorType: string, sensorName: string): string[] {
|
||||
// First check the sensor name for clues
|
||||
const nameLower = sensorName?.toLowerCase() || ''
|
||||
|
||||
if (nameLower.includes('energy') || nameLower.includes('power')) {
|
||||
return ['energy']
|
||||
}
|
||||
if (nameLower.includes('temperature') || nameLower.includes('temp')) {
|
||||
return ['temperature']
|
||||
}
|
||||
if (nameLower.includes('humidity')) {
|
||||
return ['humidity']
|
||||
}
|
||||
if (nameLower.includes('co2') || nameLower.includes('air quality')) {
|
||||
return ['co2']
|
||||
}
|
||||
if (nameLower.includes('motion')) {
|
||||
return ['motion']
|
||||
}
|
||||
|
||||
// Then fall back to sensor type
|
||||
switch (sensorType?.toLowerCase()) {
|
||||
case 'energy':
|
||||
return ['energy']
|
||||
case 'temperature':
|
||||
return ['temperature']
|
||||
case 'humidity':
|
||||
return ['humidity']
|
||||
case 'co2':
|
||||
return ['co2']
|
||||
case 'motion':
|
||||
return ['motion']
|
||||
case 'hvac':
|
||||
return ['temperature']
|
||||
case 'lighting':
|
||||
return [] // Lighting sensors don't typically monitor environmental data
|
||||
case 'security':
|
||||
return ['motion']
|
||||
default:
|
||||
return [] // No default monitoring capabilities
|
||||
}
|
||||
}
|
||||
|
||||
// Sensors API functions
|
||||
async function fetchApiSensors(params?: { room?: string; sensor_type?: any; status?: any }) {
|
||||
const result = await handleApiCall(() => sensorsApi.getSensors(params))
|
||||
@@ -199,15 +233,19 @@ export const useSensorStore = defineStore('sensor', () => {
|
||||
if (result.sensors && Array.isArray(result.sensors)) {
|
||||
result.sensors.forEach((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: sensor.sensor_type || sensor.type,
|
||||
type: sensorType,
|
||||
capabilities: {
|
||||
actions: [], // Default empty actions array
|
||||
monitoring: sensor.capabilities?.monitoring || ['energy'], // Default monitoring capability
|
||||
monitoring:
|
||||
sensor.capabilities?.monitoring ||
|
||||
getDefaultMonitoringCapabilities(sensorType, sensorName),
|
||||
...sensor.capabilities,
|
||||
},
|
||||
metadata: {
|
||||
@@ -231,15 +269,19 @@ export const useSensorStore = defineStore('sensor', () => {
|
||||
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: sensor.sensor_type || sensor.type,
|
||||
type: sensorType,
|
||||
capabilities: {
|
||||
actions: [], // Default empty actions array
|
||||
monitoring: sensor.capabilities?.monitoring || ['energy'], // Default monitoring capability
|
||||
monitoring:
|
||||
sensor.capabilities?.monitoring ||
|
||||
getDefaultMonitoringCapabilities(sensorType, sensorName),
|
||||
...sensor.capabilities,
|
||||
},
|
||||
metadata: {
|
||||
|
||||
@@ -72,7 +72,6 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
||||
socket.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
console.log('WebSocket received data:', data)
|
||||
|
||||
if (data.type === 'proxy_info' && data.sensor_service_url) {
|
||||
console.log('Received proxy info, reconnecting to sensor service...')
|
||||
@@ -157,8 +156,6 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
||||
const sensorRoom = sensor.room?.toLowerCase() || ''
|
||||
const wsId = webSocketSensorId.toLowerCase()
|
||||
|
||||
console.log(`Checking sensor: ${sensor.name} (${sensorRoom}) against ${webSocketSensorId}`)
|
||||
|
||||
// Room-based matching (more comprehensive)
|
||||
if (wsId.includes('living') && sensorName.includes('living')) return apiSensorId
|
||||
if (wsId.includes('lr') && sensorName.includes('living')) return apiSensorId
|
||||
@@ -217,12 +214,9 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
||||
function processIncomingData(data: LegacyEnergyData | SensorReading) {
|
||||
// Skip non-data messages
|
||||
if ('type' in data && (data.type === 'connection_established' || data.type === 'proxy_info')) {
|
||||
console.log('Received system message:', data.type)
|
||||
return
|
||||
}
|
||||
|
||||
console.log('Processing incoming data:', data)
|
||||
|
||||
// Normalize property names: sensor_id -> sensorId
|
||||
if ('sensor_id' in data && !('sensorId' in data)) {
|
||||
data.sensorId = data.sensor_id
|
||||
@@ -251,7 +245,6 @@ export const useWebSocketStore = defineStore('websocket', () => {
|
||||
unit: 'ppm',
|
||||
},
|
||||
}
|
||||
console.log(`Mapped WebSocket sensor ID ${data.sensorId} to ${mappedSensorId}`)
|
||||
sensorStore.updateLatestReading(sensorReading)
|
||||
|
||||
// Update time series for chart
|
||||
|
||||
Reference in New Issue
Block a user