feat: Remove test scenario functionality and update related components for real MQTT data usage

This commit is contained in:
Siwat Sirichai 2025-06-21 15:35:24 +07:00
parent fd8cacd62b
commit 738ae59c69
5 changed files with 36 additions and 290 deletions

View file

@ -1,197 +1,16 @@
import { NextRequest, NextResponse } from 'next/server';
import { NextResponse } from 'next/server';
interface TestSensorData {
sensorId: string;
value: number;
status: string;
warningStartTime?: number;
}
// Test scenarios for the bed pressure monitoring system
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { scenario } = body;
let testData: TestSensorData[] = [];
switch (scenario) {
case 'normal':
testData = generateNormalScenario();
break;
case 'warning':
testData = generateWarningScenario();
break;
case 'alarm':
testData = generateAlarmScenario();
break;
case 'escalation':
testData = generateEscalationScenario();
break;
case 'mixed':
testData = generateMixedScenario();
break;
default:
return NextResponse.json({
success: false,
error: 'Invalid scenario. Use: normal, warning, alarm, escalation, or mixed'
}, { status: 400 });
}
return NextResponse.json({
success: true,
scenario,
message: `Generated ${scenario} test scenario`,
testData,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('Test scenario API error:', error);
return NextResponse.json({
success: false,
error: 'Failed to generate test scenario'
}, { status: 500 });
}
}
function generateNormalScenario() {
return [
{ sensorId: 'head-1', value: 1200, status: 'normal' },
{ sensorId: 'head-2', value: 1150, status: 'normal' },
{ sensorId: 'shoulder-1', value: 1800, status: 'normal' },
{ sensorId: 'shoulder-2', value: 1750, status: 'normal' },
{ sensorId: 'back-1', value: 2000, status: 'normal' },
{ sensorId: 'back-2', value: 2100, status: 'normal' },
{ sensorId: 'back-3', value: 1950, status: 'normal' },
{ sensorId: 'hip-1', value: 1900, status: 'normal' },
{ sensorId: 'hip-2', value: 2000, status: 'normal' },
{ sensorId: 'hip-3', value: 1850, status: 'normal' },
{ sensorId: 'thigh-1', value: 1600, status: 'normal' },
{ sensorId: 'thigh-2', value: 1550, status: 'normal' },
{ sensorId: 'calf-1', value: 1400, status: 'normal' },
{ sensorId: 'calf-2', value: 1350, status: 'normal' },
{ sensorId: 'feet-1', value: 1200, status: 'normal' },
{ sensorId: 'feet-2', value: 1150, status: 'normal' }
];
}
function generateWarningScenario() {
return [
{ sensorId: 'head-1', value: 3100, status: 'warning' }, // Above warning threshold
{ sensorId: 'head-2', value: 1150, status: 'normal' },
{ sensorId: 'shoulder-1', value: 2900, status: 'warning' }, // Above warning threshold
{ sensorId: 'shoulder-2', value: 1750, status: 'normal' },
{ sensorId: 'back-1', value: 2600, status: 'warning' }, // Above warning threshold
{ sensorId: 'back-2', value: 2100, status: 'normal' },
{ sensorId: 'back-3', value: 1950, status: 'normal' },
{ sensorId: 'hip-1', value: 1900, status: 'normal' },
{ sensorId: 'hip-2', value: 2300, status: 'warning' }, // Above warning threshold
{ sensorId: 'hip-3', value: 1850, status: 'normal' },
{ sensorId: 'thigh-1', value: 1600, status: 'normal' },
{ sensorId: 'thigh-2', value: 1550, status: 'normal' },
{ sensorId: 'calf-1', value: 1400, status: 'normal' },
{ sensorId: 'calf-2', value: 1350, status: 'normal' },
{ sensorId: 'feet-1', value: 1200, status: 'normal' },
{ sensorId: 'feet-2', value: 1150, status: 'normal' }
];
}
function generateAlarmScenario() {
return [
{ sensorId: 'head-1', value: 3600, status: 'alarm' }, // Above alarm threshold
{ sensorId: 'head-2', value: 3550, status: 'alarm' }, // Above alarm threshold
{ sensorId: 'shoulder-1', value: 3300, status: 'alarm' }, // Above alarm threshold
{ sensorId: 'shoulder-2', value: 1750, status: 'normal' },
{ sensorId: 'back-1', value: 3100, status: 'alarm' }, // Above alarm threshold
{ sensorId: 'back-2', value: 2100, status: 'normal' },
{ sensorId: 'back-3', value: 1950, status: 'normal' },
{ sensorId: 'hip-1', value: 2900, status: 'alarm' }, // Above alarm threshold
{ sensorId: 'hip-2', value: 2000, status: 'normal' },
{ sensorId: 'hip-3', value: 1850, status: 'normal' },
{ sensorId: 'thigh-1', value: 1600, status: 'normal' },
{ sensorId: 'thigh-2', value: 1550, status: 'normal' },
{ sensorId: 'calf-1', value: 1400, status: 'normal' },
{ sensorId: 'calf-2', value: 1350, status: 'normal' },
{ sensorId: 'feet-1', value: 1200, status: 'normal' },
{ sensorId: 'feet-2', value: 1150, status: 'normal' }
];
}
function generateEscalationScenario() {
// This scenario would simulate sensors that have been in warning state for a while
// and are about to escalate to alarm
const now = Date.now();
const warningStartTime = now - 25000; // Started warning 25 seconds ago (close to 30s threshold)
return [
{ sensorId: 'head-1', value: 3100, status: 'warning', warningStartTime },
{ sensorId: 'head-2', value: 1150, status: 'normal' },
{ sensorId: 'shoulder-1', value: 2900, status: 'warning', warningStartTime: now - 40000 }, // Close to 45s threshold
{ sensorId: 'shoulder-2', value: 1750, status: 'normal' },
{ sensorId: 'back-1', value: 2600, status: 'warning', warningStartTime: now - 55000 }, // Close to 60s threshold
{ sensorId: 'back-2', value: 2100, status: 'normal' },
{ sensorId: 'back-3', value: 1950, status: 'normal' },
{ sensorId: 'hip-1', value: 1900, status: 'normal' },
{ sensorId: 'hip-2', value: 2300, status: 'warning', warningStartTime: now - 85000 }, // Close to 90s threshold
{ sensorId: 'hip-3', value: 1850, status: 'normal' },
{ sensorId: 'thigh-1', value: 2100, status: 'warning', warningStartTime: now - 115000 }, // Close to 120s threshold
{ sensorId: 'thigh-2', value: 1550, status: 'normal' },
{ sensorId: 'calf-1', value: 1400, status: 'normal' },
{ sensorId: 'calf-2', value: 1350, status: 'normal' },
{ sensorId: 'feet-1', value: 1200, status: 'normal' },
{ sensorId: 'feet-2', value: 1150, status: 'normal' }
];
}
function generateMixedScenario() {
const now = Date.now();
return [
{ sensorId: 'head-1', value: 3600, status: 'alarm' }, // Immediate alarm
{ sensorId: 'head-2', value: 3100, status: 'warning', warningStartTime: now - 10000 }, // Recent warning
{ sensorId: 'shoulder-1', value: 2900, status: 'warning', warningStartTime: now - 40000 }, // Long warning
{ sensorId: 'shoulder-2', value: 1750, status: 'normal' },
{ sensorId: 'back-1', value: 3100, status: 'alarm' }, // Immediate alarm
{ sensorId: 'back-2', value: 2600, status: 'warning', warningStartTime: now - 30000 }, // Warning close to escalation
{ sensorId: 'back-3', value: 1950, status: 'normal' },
{ sensorId: 'hip-1', value: 1900, status: 'normal' },
{ sensorId: 'hip-2', value: 2900, status: 'alarm' }, // Immediate alarm
{ sensorId: 'hip-3', value: 2300, status: 'warning', warningStartTime: now - 60000 }, // Warning
{ sensorId: 'thigh-1', value: 1600, status: 'normal' },
{ sensorId: 'thigh-2', value: 2100, status: 'warning', warningStartTime: now - 90000 }, // Warning
{ sensorId: 'calf-1', value: 2300, status: 'alarm' }, // Immediate alarm
{ sensorId: 'calf-2', value: 1900, status: 'warning', warningStartTime: now - 120000 }, // Warning
{ sensorId: 'feet-1', value: 1200, status: 'normal' },
{ sensorId: 'feet-2', value: 1900, status: 'alarm' } // Immediate alarm
];
// API endpoint disabled - only real MQTT data is used
export async function POST() {
return NextResponse.json({
success: false,
error: 'Test scenarios have been removed. System uses real MQTT data only.'
}, { status: 410 });
}
export async function GET() {
return NextResponse.json({
success: true,
availableScenarios: [
{
name: 'normal',
description: 'All sensors operating within normal ranges'
},
{
name: 'warning',
description: 'Several sensors in warning state (above warning threshold)'
},
{
name: 'alarm',
description: 'Multiple sensors in immediate alarm state (above alarm threshold)'
},
{
name: 'escalation',
description: 'Sensors in warning state close to escalating to alarm after delay'
},
{
name: 'mixed',
description: 'Mixed scenario with normal, warning, and alarm states'
}
],
usage: 'POST to /api/test-scenarios with body: { "scenario": "scenario_name" }'
});
success: false,
error: 'Test scenarios have been removed. System uses real MQTT data only.'
}, { status: 410 });
}

View file

@ -1,7 +1,7 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { AlertTriangle, VolumeX, CheckCircle, Bell, TestTube } from "lucide-react"
import { AlertTriangle, VolumeX, CheckCircle, Bell } from "lucide-react"
import { useBedPressureStore } from "@/stores/bedPressureStore"
import { useEffect, useState } from "react"
@ -12,35 +12,13 @@ export function AlarmDashboard() {
acknowledgeAlarm,
silenceAllAlarms
} = useBedPressureStore()
const [unsilencedAlarms, setUnsilencedAlarms] = useState(0)
const [testScenario, setTestScenario] = useState<string>('')
// Update alarm counts
useEffect(() => {
const unsilenced = activeAlarms.filter(alarm => !alarm.silenced).length
setUnsilencedAlarms(unsilenced)
}, [activeAlarms])
const handleTestScenario = async (scenario: string) => {
try {
setTestScenario(scenario)
const response = await fetch('/api/test-scenarios', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ scenario })
})
if (response.ok) {
console.log(`Applied ${scenario} test scenario`)
}
} catch (error) {
console.error('Failed to apply test scenario:', error)
} finally {
setTimeout(() => setTestScenario(''), 2000)
}
}
const getSystemStatus = () => {
const alarmCount = activeAlarms.filter(a => a.type === 'alarm' && !a.silenced).length
const warningCount = activeAlarms.filter(a => a.type === 'warning' && !a.silenced).length
@ -71,9 +49,8 @@ export function AlarmDashboard() {
<div>
<CardTitle className={`text-lg ${systemStatus.status === 'ALARM' ? 'text-red-700' : systemStatus.status === 'WARNING' ? 'text-yellow-700' : 'text-green-700'}`}>
SYSTEM STATUS: {systemStatus.status}
</CardTitle>
<p className="text-sm text-gray-600">
{isConnected ? 'Hardware Connected' : 'Using Mock Data'}
</CardTitle> <p className="text-sm text-gray-600">
{isConnected ? 'Hardware Connected' : 'Hardware Offline'}
{activeAlarms.length} Active Alarms
{unsilencedAlarms} Unsilenced
</p>
@ -149,45 +126,9 @@ export function AlarmDashboard() {
<p className="text-2xl font-bold text-green-600">
{activeAlarms.filter(a => a.acknowledged).length}
</p>
</CardContent>
</Card>
</CardContent> </Card>
</div>
{/* Test Scenarios */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<TestTube className="w-5 h-5" />
Test Scenarios
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
{[
{ name: 'normal', label: 'Normal', color: 'bg-green-100 text-green-700 hover:bg-green-200' },
{ name: 'warning', label: 'Warning', color: 'bg-yellow-100 text-yellow-700 hover:bg-yellow-200' },
{ name: 'alarm', label: 'Alarm', color: 'bg-red-100 text-red-700 hover:bg-red-200' },
{ name: 'escalation', label: 'Escalation', color: 'bg-orange-100 text-orange-700 hover:bg-orange-200' },
{ name: 'mixed', label: 'Mixed', color: 'bg-purple-100 text-purple-700 hover:bg-purple-200' }
].map(scenario => (
<Button
key={scenario.name}
variant="outline"
size="sm"
onClick={() => handleTestScenario(scenario.name)}
disabled={testScenario === scenario.name}
className={`${scenario.color} border-0`}
>
{testScenario === scenario.name ? 'Applied!' : scenario.label}
</Button>
))}
</div>
<p className="text-xs text-gray-500 mt-2">
Click to simulate different alarm scenarios for testing
</p>
</CardContent>
</Card>
{/* Recent Alarm Activity */}
<Card>
<CardHeader>

View file

@ -143,10 +143,9 @@ export function SensorDetailModal() {
</SelectContent>
</Select>
</div>
</div>
<p className="text-sm text-gray-600">
</div> <p className="text-sm text-gray-600">
Showing data for the last {getTimespanLabel(selectedTimespan)}
{sensor.source === 'hardware' ? ' (Real sensor data)' : ' (Mock data)'}
{sensor.source === 'hardware' ? ' (Real sensor data)' : ' (Hardware offline - no data)'}
</p>
</CardHeader>
<CardContent>

View file

@ -9,7 +9,7 @@ export interface SensorDataPoint {
value: number; // Changed from pressure to value (0-4095)
timestamp: number;
time: string;
source: 'hardware' | 'mock';
source: 'hardware';
pin?: number;
digitalState?: number;
}
@ -93,39 +93,24 @@ export class SensorDataStorage {
async forceSave() {
await this.saveData();
}
// Generate time series data for a specific timespan
} // Generate time series data for a specific timespan
generateTimeSeriesData(
sensorData: SensorDataPoint[],
timespan: number = 24 * 60 * 60 * 1000
): Array<{ time: string; timestamp: number; value: number }> {
if (sensorData.length === 0) {
// Generate mock data if no real data exists
return this.generateMockTimeSeriesData(timespan);
// Return empty array if no real data exists
return [];
}
return sensorData.map(point => ({
// Filter data by timespan
const cutoffTime = Date.now() - timespan;
const filteredData = sensorData.filter(point => point.timestamp > cutoffTime);
return filteredData.map(point => ({
time: point.time,
timestamp: point.timestamp,
value: point.value
}));
}
private generateMockTimeSeriesData(timespan: number): Array<{ time: string; timestamp: number; value: number }> {
const data = [];
const now = Date.now();
const interval = Math.max(1000, timespan / 288); // At least 1 second intervals, up to 288 points
for (let i = timespan; i >= 0; i -= interval) {
const timestamp = now - i;
const time = new Date(timestamp);
data.push({
time: time.toLocaleTimeString("en-US", { hour12: false }),
timestamp: timestamp,
value: Math.floor(Math.random() * 4096 + Math.sin(i / 60000) * 500 + 2000), // 0-4095 range
});
}
return data;
}
}

View file

@ -11,7 +11,7 @@ export interface SensorData {
currentValue: number; // Changed from currentPressure to currentValue (0-4095)
data: Array<{ time: string; timestamp: number; value: number }>; // Changed from pressure to value
status: string;
source?: 'hardware' | 'mock';
source?: 'hardware';
pin?: number;
digitalState?: number;
warningThreshold?: number;
@ -132,8 +132,7 @@ export const useBedPressureStore = create<BedPressureStore>((set, get) => ({
console.error('Failed to fetch sensor config:', error);
}
},
fetchSensorData: async () => {
fetchSensorData: async () => {
try {
const response = await fetch('/api/sensors');
const data = await response.json();
@ -143,12 +142,13 @@ export const useBedPressureStore = create<BedPressureStore>((set, get) => ({
const updated = { ...sensorData };
const newAlerts: Alert[] = [];
// Only process hardware sensors, no mock data
data.sensors.forEach((sensor: {
id: string;
label: string;
zone: string;
value: number; // Changed from pressure to value
source: 'hardware' | 'mock';
value: number;
source: 'hardware';
pin?: number;
digitalState?: number;
warningThreshold?: number;
@ -156,6 +156,10 @@ export const useBedPressureStore = create<BedPressureStore>((set, get) => ({
warningDelayMs?: number;
status?: string;
}) => {
// Only process hardware data
if (sensor.source !== 'hardware') {
return;
}
const currentSensor = updated[sensor.id];
const newValue = sensor.value;
const config = sensorConfig.find(s => s.id === sensor.id);
@ -224,9 +228,7 @@ export const useBedPressureStore = create<BedPressureStore>((set, get) => ({
} else {
warningStartTime = undefined; // Clear warning timer
alarmManager.clearAlarm(sensor.id); // Clear any existing alarms
}
// Update sensor data
} // Update sensor data
updated[sensor.id] = {
...sensorConfig.find(s => s.id === sensor.id),
id: sensor.id,
@ -236,7 +238,7 @@ export const useBedPressureStore = create<BedPressureStore>((set, get) => ({
label: sensor.label,
currentValue: newValue,
data: currentSensor ? [
...currentSensor.data.slice(1),
...currentSensor.data.slice(-100), // Keep only last 100 points
{
time: new Date().toLocaleTimeString("en-US", { hour12: false }),
timestamp: Date.now(),