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:
rafaeldpsilva
2025-09-30 15:07:50 +01:00
parent 3681890ec5
commit 5cb87ef5c5
4 changed files with 228 additions and 59 deletions

View File

@@ -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]

View File

@@ -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'

View File

@@ -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: {

View File

@@ -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