diff --git a/app/api/sensors/config/route.ts b/app/api/sensors/config/route.ts
index 86350a2..7727d13 100644
--- a/app/api/sensors/config/route.ts
+++ b/app/api/sensors/config/route.ts
@@ -1,36 +1,209 @@
import { NextResponse } from 'next/server';
+import { SensorConfig } from '@/types/sensor';
// Sensor configuration that matches the API route
-const SENSOR_CONFIG = [
- // Head area
- { id: "head-1", x: 45, y: 15, zone: "head", label: "Head Left", pin: 2 },
- { id: "head-2", x: 55, y: 15, zone: "head", label: "Head Right", pin: 3 },
+const SENSOR_CONFIG: SensorConfig[] = [
+ // Head area - Higher thresholds due to critical nature, faster escalation
+ {
+ id: "head-1",
+ x: 45,
+ y: 15,
+ zone: "head",
+ label: "Head Left",
+ pin: 2,
+ warningThreshold: 3000,
+ alarmThreshold: 3500,
+ warningDelayMs: 30000, // 30 seconds - fast escalation for head
+ baseNoise: 200
+ },
+ {
+ id: "head-2",
+ x: 55,
+ y: 15,
+ zone: "head",
+ label: "Head Right",
+ pin: 3,
+ warningThreshold: 3000,
+ alarmThreshold: 3500,
+ warningDelayMs: 30000, // 30 seconds
+ baseNoise: 150
+ },
- // Shoulder area
- { id: "shoulder-1", x: 35, y: 25, zone: "shoulders", label: "Left Shoulder", pin: 4 },
- { id: "shoulder-2", x: 65, y: 25, zone: "shoulders", label: "Right Shoulder", pin: 5 },
+ // Shoulder area - Moderate thresholds, medium escalation time
+ {
+ id: "shoulder-1",
+ x: 35,
+ y: 25,
+ zone: "shoulders",
+ label: "Left Shoulder",
+ pin: 4,
+ warningThreshold: 2800,
+ alarmThreshold: 3200,
+ warningDelayMs: 45000, // 45 seconds
+ baseNoise: 250
+ },
+ {
+ id: "shoulder-2",
+ x: 65,
+ y: 25,
+ zone: "shoulders",
+ label: "Right Shoulder",
+ pin: 5,
+ warningThreshold: 2800,
+ alarmThreshold: 3200,
+ warningDelayMs: 45000, // 45 seconds
+ baseNoise: 220
+ },
- // Upper back
- { id: "back-1", x: 40, y: 35, zone: "back", label: "Upper Back Left", pin: 6 },
- { id: "back-2", x: 50, y: 35, zone: "back", label: "Upper Back Center", pin: 7 },
- { id: "back-3", x: 60, y: 35, zone: "back", label: "Upper Back Right", pin: 8 },
+ // Upper back - Moderate thresholds, 1 minute escalation
+ {
+ id: "back-1",
+ x: 40,
+ y: 35,
+ zone: "back",
+ label: "Upper Back Left",
+ pin: 6,
+ warningThreshold: 2500,
+ alarmThreshold: 3000,
+ warningDelayMs: 60000, // 1 minute
+ baseNoise: 300
+ },
+ {
+ id: "back-2",
+ x: 50,
+ y: 35,
+ zone: "back",
+ label: "Upper Back Center",
+ pin: 7,
+ warningThreshold: 2500,
+ alarmThreshold: 3000,
+ warningDelayMs: 60000, // 1 minute
+ baseNoise: 350
+ },
+ {
+ id: "back-3",
+ x: 60,
+ y: 35,
+ zone: "back",
+ label: "Upper Back Right",
+ pin: 8,
+ warningThreshold: 2500,
+ alarmThreshold: 3000,
+ warningDelayMs: 60000, // 1 minute
+ baseNoise: 280
+ },
- // Lower back/Hip area
- { id: "hip-1", x: 35, y: 50, zone: "hips", label: "Left Hip", pin: 9 },
- { id: "hip-2", x: 50, y: 50, zone: "hips", label: "Lower Back", pin: 10 },
- { id: "hip-3", x: 65, y: 50, zone: "hips", label: "Right Hip", pin: 11 },
+ // Lower back/Hip area - Lower thresholds, 90 second escalation
+ {
+ id: "hip-1",
+ x: 35,
+ y: 50,
+ zone: "hips",
+ label: "Left Hip",
+ pin: 9,
+ warningThreshold: 2200,
+ alarmThreshold: 2800,
+ warningDelayMs: 90000, // 90 seconds
+ baseNoise: 400
+ },
+ {
+ id: "hip-2",
+ x: 50,
+ y: 50,
+ zone: "hips",
+ label: "Lower Back",
+ pin: 10,
+ warningThreshold: 2200,
+ alarmThreshold: 2800,
+ warningDelayMs: 90000, // 90 seconds
+ baseNoise: 450
+ },
+ {
+ id: "hip-3",
+ x: 65,
+ y: 50,
+ zone: "hips",
+ label: "Right Hip",
+ pin: 11,
+ warningThreshold: 2200,
+ alarmThreshold: 2800,
+ warningDelayMs: 90000, // 90 seconds
+ baseNoise: 380
+ },
- // Thigh area
- { id: "thigh-1", x: 40, y: 65, zone: "legs", label: "Left Thigh", pin: 12 },
- { id: "thigh-2", x: 60, y: 65, zone: "legs", label: "Right Thigh", pin: 13 },
+ // Thigh area - Lower thresholds, 2 minute escalation
+ {
+ id: "thigh-1",
+ x: 40,
+ y: 65,
+ zone: "legs",
+ label: "Left Thigh",
+ pin: 12,
+ warningThreshold: 2000,
+ alarmThreshold: 2500,
+ warningDelayMs: 120000, // 2 minutes
+ baseNoise: 320
+ },
+ {
+ id: "thigh-2",
+ x: 60,
+ y: 65,
+ zone: "legs",
+ label: "Right Thigh",
+ pin: 13,
+ warningThreshold: 2000,
+ alarmThreshold: 2500,
+ warningDelayMs: 120000, // 2 minutes
+ baseNoise: 300
+ },
- // Calf area (mock data)
- { id: "calf-1", x: 40, y: 75, zone: "legs", label: "Left Calf" },
- { id: "calf-2", x: 60, y: 75, zone: "legs", label: "Right Calf" },
+ // Calf area (mock data) - Lower thresholds, 2.5 minute escalation
+ {
+ id: "calf-1",
+ x: 40,
+ y: 75,
+ zone: "legs",
+ label: "Left Calf",
+ warningThreshold: 1800,
+ alarmThreshold: 2200,
+ warningDelayMs: 150000, // 2.5 minutes
+ baseNoise: 200
+ },
+ {
+ id: "calf-2",
+ x: 60,
+ y: 75,
+ zone: "legs",
+ label: "Right Calf",
+ warningThreshold: 1800,
+ alarmThreshold: 2200,
+ warningDelayMs: 150000, // 2.5 minutes
+ baseNoise: 220
+ },
- // Feet (mock data)
- { id: "feet-1", x: 45, y: 85, zone: "feet", label: "Left Foot" },
- { id: "feet-2", x: 55, y: 85, zone: "feet", label: "Right Foot" },
+ // Feet (mock data) - Lowest thresholds, 3 minute escalation
+ {
+ id: "feet-1",
+ x: 45,
+ y: 85,
+ zone: "feet",
+ label: "Left Foot",
+ warningThreshold: 1500,
+ alarmThreshold: 1800,
+ warningDelayMs: 180000, // 3 minutes
+ baseNoise: 150
+ },
+ {
+ id: "feet-2",
+ x: 55,
+ y: 85,
+ zone: "feet",
+ label: "Right Foot",
+ warningThreshold: 1500,
+ alarmThreshold: 1800,
+ warningDelayMs: 180000, // 3 minutes
+ baseNoise: 160
+ },
];
export async function GET() {
diff --git a/app/api/sensors/route.ts b/app/api/sensors/route.ts
index 282dfc7..77727c8 100644
--- a/app/api/sensors/route.ts
+++ b/app/api/sensors/route.ts
@@ -1,9 +1,10 @@
import { NextRequest, NextResponse } from 'next/server';
import { BedHardware, PinState, PinChange } from '@/services/BedHardware';
import { SensorDataStorage, SensorDataPoint } from '@/services/SensorDataStorage';
+import { SensorConfig } from '@/types/sensor';
// Complete sensor configuration with positions, pin mappings, and thresholds
-const SENSOR_CONFIG = [
+const SENSOR_CONFIG: SensorConfig[] = [
// Head area
{ id: "head-1", x: 45, y: 15, zone: "head", label: "Head Left", pin: 2, baseNoise: 200, warningThreshold: 3000, alarmThreshold: 3500, warningDelayMs: 30000 },
{ id: "head-2", x: 55, y: 15, zone: "head", label: "Head Right", pin: 3, baseNoise: 150, warningThreshold: 3000, alarmThreshold: 3500, warningDelayMs: 30000 },
@@ -235,6 +236,25 @@ function updateMockSensorData() {
const currentSensor = sensorData[sensor.id];
const variation = (Math.random() - 0.5) * 200; // Larger variation for analog values
const newValue = Math.max(0, Math.min(4095, currentSensor.value + variation));
+ const timestamp = Date.now();
+
+ // Determine status based on thresholds
+ let status = 'normal';
+ let warningStartTime = currentSensor.warningStartTime;
+
+ if (newValue >= sensor.alarmThreshold) {
+ status = 'alarm';
+ warningStartTime = undefined; // Clear warning timer for immediate alarm
+ } else if (newValue >= sensor.warningThreshold) {
+ status = 'warning';
+ if (!warningStartTime) {
+ warningStartTime = timestamp; // Start warning timer
+ } else if (timestamp - warningStartTime >= sensor.warningDelayMs) {
+ status = 'alarm'; // Escalate to alarm after delay
+ }
+ } else {
+ warningStartTime = undefined; // Clear warning timer
+ }
sensorData[sensor.id] = {
...currentSensor,
@@ -248,8 +268,8 @@ function updateMockSensorData() {
value: newValue,
}
],
- status: newValue >= sensor.alarmThreshold ? 'alarm' :
- newValue >= sensor.warningThreshold ? 'warning' : 'normal'
+ status,
+ warningStartTime
};
}
});
diff --git a/app/api/sensors/zones/route.ts b/app/api/sensors/zones/route.ts
new file mode 100644
index 0000000..0bcea3d
--- /dev/null
+++ b/app/api/sensors/zones/route.ts
@@ -0,0 +1,60 @@
+import { NextResponse } from 'next/server';
+import { ZONE_CONFIGS, validateSensorConfig } from '@/utils/sensorConfig';
+
+export async function GET() {
+ try {
+ return NextResponse.json({
+ success: true,
+ zones: ZONE_CONFIGS,
+ description: 'Available sensor zones with default threshold configurations',
+ timestamp: new Date().toISOString()
+ });
+ } catch (error) {
+ console.error('Zone config API error:', error);
+ return NextResponse.json({
+ success: false,
+ error: 'Failed to get zone configurations',
+ zones: {},
+ timestamp: new Date().toISOString()
+ }, { status: 500 });
+ }
+}
+
+export async function POST(request: Request) {
+ try {
+ const sensorConfigs = await request.json();
+
+ if (!Array.isArray(sensorConfigs)) {
+ return NextResponse.json({
+ success: false,
+ error: 'Expected array of sensor configurations'
+ }, { status: 400 });
+ }
+
+ const validationResults = sensorConfigs.map(config => ({
+ sensorId: config.id,
+ ...validateSensorConfig(config)
+ }));
+
+ const hasErrors = validationResults.some(result => !result.isValid);
+
+ return NextResponse.json({
+ success: !hasErrors,
+ validationResults,
+ summary: {
+ total: sensorConfigs.length,
+ valid: validationResults.filter(r => r.isValid).length,
+ withWarnings: validationResults.filter(r => r.warnings.length > 0).length,
+ withErrors: validationResults.filter(r => !r.isValid).length
+ },
+ timestamp: new Date().toISOString()
+ });
+
+ } catch (error) {
+ console.error('Sensor validation API error:', error);
+ return NextResponse.json({
+ success: false,
+ error: 'Failed to validate sensor configurations'
+ }, { status: 500 });
+ }
+}
\ No newline at end of file
diff --git a/app/api/test-scenarios/route.ts b/app/api/test-scenarios/route.ts
new file mode 100644
index 0000000..d4a4e8b
--- /dev/null
+++ b/app/api/test-scenarios/route.ts
@@ -0,0 +1,197 @@
+import { NextRequest, 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
+ ];
+}
+
+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" }'
+ });
+}
\ No newline at end of file
diff --git a/app/page.tsx b/app/page.tsx
index d97247f..ea89759 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,7 +1,37 @@
"use client"
-import Component from "@/components/bed-pressure-monitor"
+import { BedPressureHeader } from "@/components/bed-pressure/BedPressureHeader"
+import { StatsCards } from "@/components/bed-pressure/StatsCards"
+import { BedVisualization } from "@/components/bed-pressure/BedVisualization"
+import { AlertsPanel } from "@/components/bed-pressure/AlertsPanel"
+import { AlarmDashboard } from "@/components/bed-pressure/AlarmDashboard"
+import { SensorDetailModal } from "@/components/bed-pressure/SensorDetailModal"
+import { useBedPressureData } from "@/hooks/useBedPressureData"
export default function Page() {
- return
+ {isConnected ? 'Hardware Connected' : 'Using Mock Data'} • + {activeAlarms.length} Active Alarms • + {unsilencedAlarms} Unsilenced +
++ {activeAlarms.filter(a => a.type === 'alarm' && !a.silenced).length} +
++ {activeAlarms.filter(a => a.type === 'warning' && !a.silenced).length} +
++ {activeAlarms.filter(a => a.silenced).length} +
++ {activeAlarms.filter(a => a.acknowledged).length} +
++ Click to simulate different alarm scenarios for testing +
+No active alarms
+{alarm.sensorLabel}
++ {alarm.type.toUpperCase()} • Value: {alarm.value.toFixed(0)} • {alarm.time} +
+