- Add hardware error banner with priority system (motion > i/o > emergency) - Add DIO status to HW status display with backend integration - Remove status text from HW status, keep only LED indicators - Add VisionDataPanel showing real-time recognized data for L/C/R ports - Add GetVisionData API in MachineBridge with batch field support - Add BroadcastVisionData function (250ms interval) - Replace 3D model with detailed reel handler equipment - Use OrthographicCamera with front view for distortion-free display - Fix ProcessedDataPanel layout to avoid right sidebar overlap - Show log viewer filename in error message when file not found 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
137 lines
5.8 KiB
TypeScript
137 lines
5.8 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { AlertTriangle, Siren } from 'lucide-react';
|
|
import { Machine3D } from '../components/Machine3D';
|
|
import { SettingsModal } from '../components/SettingsModal';
|
|
import { InitializeModal } from '../components/InitializeModal';
|
|
import { RecipePanel } from '../components/RecipePanel';
|
|
import { MotionPanel } from '../components/MotionPanel';
|
|
import { CyberPanel } from '../components/common/CyberPanel';
|
|
import { ModelInfoPanel } from '../components/ModelInfoPanel';
|
|
import { ProcessedDataPanel } from '../components/ProcessedDataPanel';
|
|
import { VisionDataPanel } from '../components/VisionDataPanel';
|
|
import { SystemStatusPanel } from '../components/SystemStatusPanel';
|
|
import { EventLogPanel } from '../components/EventLogPanel';
|
|
import { SystemState, Recipe, IOPoint, LogEntry, RobotTarget, ConfigItem } from '../types';
|
|
|
|
interface HomePageProps {
|
|
systemState: SystemState;
|
|
currentRecipe: Recipe;
|
|
robotTarget: RobotTarget;
|
|
logs: LogEntry[];
|
|
ioPoints: IOPoint[];
|
|
doorStates: { front: boolean; right: boolean; left: boolean; back: boolean };
|
|
isLowPressure: boolean;
|
|
isEmergencyStop: boolean;
|
|
isHostConnected: boolean;
|
|
activeTab: 'recipe' | 'motion' | 'camera' | 'setting' | 'initialize' | null;
|
|
onSelectRecipe: (r: Recipe) => void;
|
|
onMove: (axis: 'X' | 'Y' | 'Z', val: number) => void;
|
|
onControl: (action: 'start' | 'stop' | 'reset') => void;
|
|
onSaveConfig: (config: ConfigItem[]) => void;
|
|
onCloseTab: () => void;
|
|
videoRef: React.RefObject<HTMLVideoElement>;
|
|
}
|
|
|
|
export const HomePage: React.FC<HomePageProps> = ({
|
|
systemState,
|
|
currentRecipe,
|
|
robotTarget,
|
|
logs,
|
|
ioPoints,
|
|
doorStates,
|
|
isLowPressure,
|
|
isEmergencyStop,
|
|
isHostConnected,
|
|
activeTab,
|
|
onSelectRecipe,
|
|
onMove,
|
|
onControl,
|
|
onSaveConfig,
|
|
onCloseTab,
|
|
videoRef
|
|
}) => {
|
|
return (
|
|
<main className="relative w-full h-full">
|
|
{/* 3D Canvas (Background Layer) */}
|
|
<div className="absolute inset-0 z-0">
|
|
<Machine3D target={robotTarget} ioState={ioPoints} doorStates={doorStates} />
|
|
</div>
|
|
|
|
{/* Center Alarms */}
|
|
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-50 pointer-events-none flex flex-col items-center gap-4">
|
|
{!isHostConnected && !isEmergencyStop && (
|
|
<div className="bg-red-600/90 text-white p-8 border-4 border-red-500 shadow-glow-red flex items-center gap-6 animate-pulse">
|
|
<AlertTriangle className="w-16 h-16" />
|
|
<div>
|
|
<h1 className="text-4xl font-tech font-bold tracking-widest">HOST DISCONNECTED</h1>
|
|
<p className="text-center font-mono text-base">WAITING FOR CONNECTION...</p>
|
|
<p className="text-center font-mono text-sm mt-2 text-red-200">PLEASE CHECK THE HANDLER PROGRAM</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{isEmergencyStop && (
|
|
<div className="bg-red-600/90 text-white p-8 border-4 border-red-500 shadow-glow-red flex items-center gap-6 animate-pulse">
|
|
<Siren className="w-16 h-16 animate-spin" />
|
|
<div>
|
|
<h1 className="text-5xl font-tech font-bold tracking-widest">EMERGENCY STOP</h1>
|
|
<p className="text-center font-mono text-lg">SYSTEM HALTED - RELEASE TO RESET</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{isLowPressure && !isEmergencyStop && isHostConnected && (
|
|
<div className="bg-amber-500/80 text-black px-8 py-4 rounded font-bold text-2xl tracking-widest flex items-center gap-4 shadow-glow-red animate-bounce">
|
|
<AlertTriangle className="w-8 h-8" /> LOW AIR PRESSURE WARNING
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Floating Panel (Left) */}
|
|
{activeTab === 'motion' && (
|
|
<div className="absolute left-6 top-0 bottom-52 w-[450px] z-20 animate-in slide-in-from-left-20 duration-500 fade-in">
|
|
<CyberPanel className="h-full flex flex-col">
|
|
<MotionPanel robotTarget={robotTarget} onMove={onMove} />
|
|
</CyberPanel>
|
|
</div>
|
|
)}
|
|
|
|
{/* Recipe Selection Modal */}
|
|
<RecipePanel
|
|
isOpen={activeTab === 'recipe'}
|
|
currentRecipe={currentRecipe}
|
|
onSelectRecipe={onSelectRecipe}
|
|
onClose={onCloseTab}
|
|
/>
|
|
|
|
{/* Settings Modal */}
|
|
<SettingsModal
|
|
isOpen={activeTab === 'setting'}
|
|
onClose={onCloseTab}
|
|
onSave={onSaveConfig}
|
|
/>
|
|
|
|
{/* Initialize Modal */}
|
|
<InitializeModal
|
|
isOpen={activeTab === 'initialize'}
|
|
onClose={onCloseTab}
|
|
/>
|
|
|
|
{/* Right Sidebar (Dashboard) */}
|
|
<div className="absolute right-6 top-0 bottom-52 w-80 z-20 flex flex-col gap-4">
|
|
<ModelInfoPanel currentRecipe={currentRecipe} />
|
|
<SystemStatusPanel systemState={systemState} onControl={onControl} />
|
|
<EventLogPanel logs={logs} />
|
|
</div>
|
|
|
|
{/* Left - Vision Data Panel (Recognized Data) - 세로 공간 최대 사용 */}
|
|
<div className="absolute left-6 top-0 bottom-52 w-[680px] z-20">
|
|
<VisionDataPanel />
|
|
</div>
|
|
|
|
{/* Bottom Docked - Processed Data Panel (우측 사이드바 피함) */}
|
|
<div className="absolute left-6 right-[356px] bottom-10 h-40 z-20">
|
|
<ProcessedDataPanel />
|
|
</div>
|
|
</main>
|
|
);
|
|
};
|