import React, { useState, useEffect, useRef } from 'react'; import { HashRouter, Routes, Route } from 'react-router-dom'; import { Layout } from './components/layout/Layout'; import { HomePage } from './pages/HomePage'; import { IOMonitorPage } from './pages/IOMonitorPage'; import { SystemState, Recipe, IOPoint, LogEntry, RobotTarget, ConfigItem } from './types'; import { comms } from './communication'; // --- MOCK DATA --- const INITIAL_IO: IOPoint[] = [ ...Array.from({ length: 32 }, (_, i) => { let name = `DOUT_${i.toString().padStart(2, '0')}`; if (i === 0) name = "Tower Lamp Red"; if (i === 1) name = "Tower Lamp Yel"; if (i === 2) name = "Tower Lamp Grn"; return { id: i, name, type: 'output' as const, state: false }; }), ...Array.from({ length: 32 }, (_, i) => { let name = `DIN_${i.toString().padStart(2, '0')}`; let initialState = false; if (i === 0) name = "Front Door Sensor"; if (i === 1) name = "Right Door Sensor"; if (i === 2) name = "Left Door Sensor"; if (i === 3) name = "Back Door Sensor"; if (i === 4) { name = "Main Air Pressure"; initialState = true; } if (i === 5) { name = "Vacuum Generator"; initialState = true; } if (i === 6) { name = "Emergency Stop Loop"; initialState = true; } return { id: i, name, type: 'input' as const, state: initialState }; }) ]; // --- MAIN APP --- export default function App() { const [activeTab, setActiveTab] = useState<'recipe' | 'motion' | 'camera' | 'setting' | null>(null); const [activeIOTab, setActiveIOTab] = useState<'in' | 'out'>('in'); const [systemState, setSystemState] = useState(SystemState.IDLE); const [recipes, setRecipes] = useState([]); const [currentRecipe, setCurrentRecipe] = useState(null); const [robotTarget, setRobotTarget] = useState({ x: 0, y: 0, z: 0 }); const [logs, setLogs] = useState([]); const [ioPoints, setIoPoints] = useState([]); const [currentTime, setCurrentTime] = useState(new Date()); const [config, setConfig] = useState(null); const [isConfigRefreshing, setIsConfigRefreshing] = useState(false); const [isLoading, setIsLoading] = useState(true); const [isHostConnected, setIsHostConnected] = useState(false); const videoRef = useRef(null); // -- COMMUNICATION LAYER -- useEffect(() => { const unsubscribe = comms.subscribe((msg: any) => { if (!msg) return; if (msg.type === 'CONNECTION_STATE') { setIsHostConnected(msg.connected); addLog(msg.connected ? "HOST CONNECTED" : "HOST DISCONNECTED", msg.connected ? "info" : "warning"); } if (msg.type === 'STATUS_UPDATE') { if (msg.position) { setRobotTarget({ x: msg.position.x, y: msg.position.y, z: msg.position.z }); } if (msg.ioState) { setIoPoints(prev => { const newIO = [...prev]; msg.ioState.forEach((update: { id: number, type: string, state: boolean }) => { const idx = newIO.findIndex(p => p.id === update.id && p.type === update.type); if (idx >= 0) newIO[idx] = { ...newIO[idx], state: update.state }; }); return newIO; }); } if (msg.sysState) { setSystemState(msg.sysState as SystemState); } } }); addLog("COMMUNICATION CHANNEL OPEN", "info"); setIsHostConnected(comms.getConnectionState()); const timer = setInterval(() => setCurrentTime(new Date()), 1000); return () => { clearInterval(timer); unsubscribe(); }; }, []); // -- INITIALIZATION -- useEffect(() => { const initSystem = async () => { addLog("SYSTEM STARTED", "info"); try { const ioStr = await comms.getIOList(); const ioData = JSON.parse(ioStr); setIoPoints(ioData); addLog("IO LIST LOADED", "info"); const recipeStr = await comms.getRecipeList(); const recipeData = JSON.parse(recipeStr); setRecipes(recipeData); if (recipeData.length > 0) setCurrentRecipe(recipeData[0]); addLog("RECIPE LIST LOADED", "info"); } catch (e) { addLog("FAILED TO LOAD SYSTEM DATA", "error"); // Fallback to empty or keep initial if needed } setIsLoading(false); }; initSystem(); }, []); // -- CONFIG FETCHING (for settings modal) -- const fetchConfig = React.useCallback(async () => { setIsConfigRefreshing(true); try { const configStr = await comms.getConfig(); setConfig(JSON.parse(configStr)); addLog("CONFIG REFRESHED", "info"); } catch (e) { addLog("CONFIG REFRESH FAILED", "error"); } setIsConfigRefreshing(false); }, []); const addLog = (msg: string, type: 'info' | 'warning' | 'error' = 'info') => { setLogs(prev => [{ id: Date.now() + Math.random(), timestamp: new Date().toLocaleTimeString(), message: msg, type }, ...prev].slice(0, 50)); }; // Logic Helpers const doorStates = { front: ioPoints.find(p => p.id === 0 && p.type === 'input')?.state || false, right: ioPoints.find(p => p.id === 1 && p.type === 'input')?.state || false, left: ioPoints.find(p => p.id === 2 && p.type === 'input')?.state || false, back: ioPoints.find(p => p.id === 3 && p.type === 'input')?.state || false, }; const isLowPressure = !(ioPoints.find(p => p.id === 4 && p.type === 'input')?.state ?? true); const isEmergencyStop = !(ioPoints.find(p => p.id === 6 && p.type === 'input')?.state ?? true); // -- COMMAND HANDLERS -- const handleControl = async (action: 'start' | 'stop' | 'reset') => { if (isEmergencyStop && action === 'start') return addLog('EMERGENCY STOP ACTIVE', 'error'); try { await comms.sendControl(action.toUpperCase()); addLog(`CMD SENT: ${action.toUpperCase()}`, 'info'); } catch (e) { addLog('COMM ERROR', 'error'); } }; const toggleIO = async (id: number, type: 'input' | 'output', forceState?: boolean) => { if (type === 'output') { const current = ioPoints.find(p => p.id === id && p.type === type)?.state; const nextState = forceState !== undefined ? forceState : !current; await comms.setIO(id, nextState); } }; const moveAxis = async (axis: 'X' | 'Y' | 'Z', value: number) => { if (isEmergencyStop) return; await comms.moveAxis(axis, value); addLog(`CMD MOVE ${axis}: ${value}`, 'info'); }; const handleSaveConfig = async (newConfig: ConfigItem[]) => { try { await comms.saveConfig(JSON.stringify(newConfig)); setConfig(newConfig); addLog("CONFIGURATION SAVED", "info"); } catch (e) { console.error(e); addLog("FAILED TO SAVE CONFIG", "error"); } }; const handleSelectRecipe = async (r: Recipe) => { try { addLog(`LOADING: ${r.name}`, 'info'); const result = await comms.selectRecipe(r.id); if (result.success) { setCurrentRecipe(r); addLog(`RECIPE LOADED: ${r.name}`, 'info'); } else { addLog(`RECIPE LOAD FAILED: ${result.message}`, 'error'); } } catch (error: any) { addLog(`RECIPE LOAD ERROR: ${error.message || 'Unknown error'}`, 'error'); console.error('Recipe selection error:', error); } }; return ( { setActiveTab(tab); if (tab === null) setActiveIOTab('in'); // Reset IO tab when closing }} activeTab={activeTab} isLoading={isLoading} > setActiveTab(null)} videoRef={videoRef} /> } /> } /> ); }