Skip to main content

02 - Sensor Simulation Component

Build a real-time sensor simulator UI (30 min)


Sensor Simulator Component

Create src/app/(core)/cad-cam-demo/components/SensorSimulator.tsx:

"use client";

import { useState, useEffect, useRef } from "react";
import { SensorData } from "../utils/types";
import { generateSensorData } from "@/lib/cad/simulator";

interface Props {
onDataUpdate: (data: SensorData) => void;
isRunning: boolean;
}

export function SensorSimulator({ onDataUpdate, isRunning }: Props) {
const [scenario, setScenario] = useState<"normal" | "polluted" | "clean">("normal");
const [data, setData] = useState<SensorData | null>(null);
const [tick, setTick] = useState(0);
const intervalRef = useRef<NodeJS.Timeout | null>(null);

useEffect(() => {
if (isRunning) {
intervalRef.current = setInterval(() => {
setTick((t) => {
const newTick = (t + 1) % 100;
const progress = newTick / 100;
const newData = generateSensorData(progress, scenario);
setData(newData);
onDataUpdate(newData);
return newTick;
});
}, 1000); // Update every second
} else {
if (intervalRef.current) clearInterval(intervalRef.current);
}

return () => {
if (intervalRef.current) clearInterval(intervalRef.current);
};
}, [isRunning, scenario, onDataUpdate]);

const getStatusColor = (param: string, value: number) => {
const thresholds: Record<string, { warn: number; danger: number }> = {
pH: { warn: 6.5, danger: 6.0 },
BOD: { warn: 200, danger: 350 },
COD: { warn: 400, danger: 700 },
TSS: { warn: 150, danger: 300 },
};
const t = thresholds[param];
if (!t) return "bg-blue-100 text-blue-700";
if (value >= t.danger) return "bg-red-100 text-red-700";
if (value >= t.warn) return "bg-yellow-100 text-yellow-700";
return "bg-green-100 text-green-700";
};

return (
<div className="bg-white rounded-xl shadow-md p-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold">Sensor Readings (Simulated)</h3>
<select value={scenario} onChange={(e) => setScenario(e.target.value as any)} className="px-3 py-1 border rounded-lg text-sm">
<option value="normal">Normal Water</option>
<option value="polluted">Polluted Water</option>
<option value="clean">Clean Water</option>
</select>
</div>

{data ? (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{Object.entries(data).map(([key, value]) => (
<div key={key} className="text-center p-3 rounded-lg bg-gray-50">
<div className="text-xs text-gray-500 uppercase">{key}</div>
<div className={`text-xl font-bold mt-1 px-2 py-1 rounded ${getStatusColor(key, value)}`}>
{typeof value === "number" ? value.toFixed(key === "pH" ? 1 : 0) : value}
</div>
<div className="text-xs text-gray-400 mt-1">
{key === "pH" ? "" : key === "flowRate" ? "m³/hr" : key === "temperature" ? "°C" : "mg/L"}
</div>
</div>
))}
</div>
) : (
<div className="text-center text-gray-400 py-8">Press Start to begin simulation</div>
)}

{isRunning && (
<div className="mt-4">
<div className="h-2 bg-gray-200 rounded-full overflow-hidden">
<div className="h-full bg-blue-500 transition-all duration-300" style={{ width: `${tick}%` }} />
</div>
<div className="text-xs text-gray-400 text-center mt-1">Simulation cycle: {tick}%</div>
</div>
)}
</div>
);
}

Parameter Panel Component

Create src/app/(core)/cad-cam-demo/components/ParameterPanel.tsx:

"use client";

import { DesignParams } from "../utils/types";

interface Props {
params: DesignParams | null;
mlPrediction: { reusabilityClass: string; treatmentStage: string; confidence: number } | null;
}

export function ParameterPanel({ params, mlPrediction }: Props) {
if (!params) {
return (
<div className="bg-white rounded-xl shadow-md p-6">
<h3 className="text-lg font-semibold mb-4">Design Parameters</h3>
<div className="text-center text-gray-400 py-8">Waiting for sensor data...</div>
</div>
);
}

const getClassColor = (cls: string) => {
const colors: Record<string, string> = {
A: "bg-green-500",
B: "bg-lime-500",
C: "bg-yellow-500",
D: "bg-orange-500",
E: "bg-red-500",
};
return colors[cls] || "bg-gray-500";
};

return (
<div className="bg-white rounded-xl shadow-md p-6">
<h3 className="text-lg font-semibold mb-4">Generated Design</h3>

{/* ML Prediction Summary */}
{mlPrediction && (
<div className="mb-4 p-3 bg-blue-50 rounded-lg">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-600">Water Class</span>
<span className={`px-3 py-1 rounded-full text-white text-sm font-bold ${getClassColor(mlPrediction.reusabilityClass)}`}>
Class {mlPrediction.reusabilityClass}
</span>
</div>
<div className="flex items-center justify-between mt-2">
<span className="text-sm text-gray-600">Treatment</span>
<span className="text-sm font-medium capitalize">{mlPrediction.treatmentStage}</span>
</div>
<div className="flex items-center justify-between mt-2">
<span className="text-sm text-gray-600">Confidence</span>
<span className="text-sm font-medium">{(mlPrediction.confidence * 100).toFixed(0)}%</span>
</div>
</div>
)}

{/* Design Parameters */}
<div className="space-y-3">
<div className="flex justify-between items-center py-2 border-b">
<span className="text-gray-600">Tank Diameter</span>
<span className="font-mono font-bold">{params.tankDiameter} m</span>
</div>
<div className="flex justify-between items-center py-2 border-b">
<span className="text-gray-600">Tank Height</span>
<span className="font-mono font-bold">{params.tankHeight} m</span>
</div>
<div className="flex justify-between items-center py-2 border-b">
<span className="text-gray-600">Volume</span>
<span className="font-mono font-bold">{params.tankVolume}</span>
</div>
<div className="flex justify-between items-center py-2 border-b">
<span className="text-gray-600">Inlet/Outlet</span>
<span className="font-mono font-bold">Ø{params.inletDiameter} mm</span>
</div>
<div className="flex justify-between items-center py-2 border-b">
<span className="text-gray-600">Baffles</span>
<span className="font-mono font-bold">{params.baffleCount}</span>
</div>
<div className="flex justify-between items-center py-2 border-b">
<span className="text-gray-600">Aeration Slots</span>
<span className="font-mono font-bold">{params.aerationSlots}</span>
</div>
<div className="flex justify-between items-center py-2">
<span className="text-gray-600">Wall Thickness</span>
<span className="font-mono font-bold">{params.wallThickness} mm</span>
</div>
</div>
</div>
);
}

Usage Example

// In your page.tsx
const [sensorData, setSensorData] = useState<SensorData | null>(null);
const [designParams, setDesignParams] = useState<DesignParams | null>(null);
const [mlPrediction, setMlPrediction] = useState(null);
const [isRunning, setIsRunning] = useState(false);

const handleSensorUpdate = useCallback((data: SensorData) => {
setSensorData(data);

// Get ML prediction (mock or real API)
const prediction = mockMLPrediction(data);
setMlPrediction(prediction);

// Calculate design parameters
const params = calculateDesignParams(data, prediction);
setDesignParams(params);
}, []);

return (
<div className="grid grid-cols-2 gap-6">
<SensorSimulator onDataUpdate={handleSensorUpdate} isRunning={isRunning} />
<ParameterPanel params={designParams} mlPrediction={mlPrediction} />
</div>
);

Next: 03-DESIGN-GENERATOR.md

Create 2D SVG design preview →