Update: Relocate AutoRun controls and cleanup

This commit is contained in:
2025-12-19 00:05:27 +09:00
parent c4089aeb20
commit 051138489b
3 changed files with 1227 additions and 1217 deletions

73
App.tsx
View File

@@ -8,6 +8,7 @@ import AgvControls from './components/AgvControls';
import BmsPanel from './components/BmsPanel'; import BmsPanel from './components/BmsPanel';
import AcsControls from './components/AcsControls'; import AcsControls from './components/AcsControls';
import AgvStatusPanel from './components/AgvStatusPanel'; import AgvStatusPanel from './components/AgvStatusPanel';
import AgvAutoRunControls from './components/AgvAutoRunControls';
import SystemLogPanel from './components/SystemLogPanel'; import SystemLogPanel from './components/SystemLogPanel';
import { SerialPortHandler } from './services/serialService'; import { SerialPortHandler } from './services/serialService';
@@ -184,12 +185,12 @@ const App: React.FC = () => {
}, [addLog]); }, [addLog]);
const handleAgvData = useCallback((data: Uint8Array) => { const handleAgvData = useCallback((data: Uint8Array) => {
for(let i=0; i<data.length; i++) { for (let i = 0; i < data.length; i++) {
agvBufferRef.current.push(data[i]); agvBufferRef.current.push(data[i]);
} }
const buf = agvBufferRef.current; const buf = agvBufferRef.current;
while(true) { while (true) {
const stxIdx = buf.indexOf(0x02); const stxIdx = buf.indexOf(0x02);
if (stxIdx === -1) { if (stxIdx === -1) {
agvBufferRef.current = []; agvBufferRef.current = [];
@@ -232,7 +233,7 @@ const App: React.FC = () => {
setAgvState(s => { setAgvState(s => {
const newBranch = (bunki === 'L' ? 'LEFT' : bunki === 'R' ? 'RIGHT' : bunki === 'S' ? 'STRAIGHT' : s.runConfig.branch); const newBranch = (bunki === 'L' ? 'LEFT' : bunki === 'R' ? 'RIGHT' : bunki === 'S' ? 'STRAIGHT' : s.runConfig.branch);
const newSpeed = (['L','M','H'].includes(speed) ? speed : s.runConfig.speedLevel) as any; const newSpeed = (['L', 'M', 'H'].includes(speed) ? speed : s.runConfig.speedLevel) as any;
// Treat '0' as placeholder for sensor if we want to preserve CBR logic, // Treat '0' as placeholder for sensor if we want to preserve CBR logic,
// BUT allow manual toggle via UI to override later. // BUT allow manual toggle via UI to override later.
@@ -298,7 +299,7 @@ const App: React.FC = () => {
setAgvState(s => { setAgvState(s => {
// Logic: Update if specific char provided, else keep existing // Logic: Update if specific char provided, else keep existing
const newBranch = (bunki === 'L' ? 'LEFT' : bunki === 'R' ? 'RIGHT' : bunki === 'S' ? 'STRAIGHT' : s.runConfig.branch); const newBranch = (bunki === 'L' ? 'LEFT' : bunki === 'R' ? 'RIGHT' : bunki === 'S' ? 'STRAIGHT' : s.runConfig.branch);
const newSpeed = (['L','M','H'].includes(speed) ? speed : s.runConfig.speedLevel) as any; const newSpeed = (['L', 'M', 'H'].includes(speed) ? speed : s.runConfig.speedLevel) as any;
const newLidar = sensor !== '0'; const newLidar = sensor !== '0';
@@ -603,7 +604,7 @@ const App: React.FC = () => {
const resp = new Uint8Array(34); const resp = new Uint8Array(34);
resp[0] = 0xDD; resp[1] = 0x03; resp[2] = 0x00; resp[3] = 0x1b; //arrary 3value 0->1b resp[0] = 0xDD; resp[1] = 0x03; resp[2] = 0x00; resp[3] = 0x1b; //arrary 3value 0->1b
const totalV = Math.floor(s.cellVoltages.reduce((a,b)=>a+b, 0) * 100); const totalV = Math.floor(s.cellVoltages.reduce((a, b) => a + b, 0) * 100);
resp[4] = (totalV >> 8) & 0xFF; resp[5] = totalV & 0xFF; resp[4] = (totalV >> 8) & 0xFF; resp[5] = totalV & 0xFF;
const remainAh = Math.floor(s.maxCapacity * (s.batteryLevel / 100)); const remainAh = Math.floor(s.maxCapacity * (s.batteryLevel / 100));
@@ -675,7 +676,7 @@ const App: React.FC = () => {
acsSerialRef.current.send(new Uint8Array(buffer)); acsSerialRef.current.send(new Uint8Array(buffer));
const hex = buffer.map(b => b.toString(16).padStart(2,'0').toUpperCase()).join(' '); const hex = buffer.map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' ');
addLog('ACS', 'TX', `CMD:${cmd.toString(16).toUpperCase()} [${hex}]`); addLog('ACS', 'TX', `CMD:${cmd.toString(16).toUpperCase()} [${hex}]`);
}, [addLog]); }, [addLog]);
@@ -735,7 +736,7 @@ const App: React.FC = () => {
// Default Little Endian. So index len-3 is Low, len-2 is High. // Default Little Endian. So index len-3 is Low, len-2 is High.
if (receivedCrc !== 0xFFFF && calcCrc !== receivedCrc) { if (receivedCrc !== 0xFFFF && calcCrc !== receivedCrc) {
const hex = packetData.map(b => b.toString(16).padStart(2,'0').toUpperCase()).join(' '); const hex = packetData.map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' ');
addLog('ACS', 'ERROR', `CRC Fail: ${hex}`); addLog('ACS', 'ERROR', `CRC Fail: ${hex}`);
acsParseErrorsRef.current++; acsParseErrorsRef.current++;
if (acsParseErrorsRef.current > 3) buf.length = 0; // Reset on too many errors if (acsParseErrorsRef.current > 3) buf.length = 0; // Reset on too many errors
@@ -750,7 +751,7 @@ const App: React.FC = () => {
const cmd = packetData[3]; const cmd = packetData[3];
const payload = packetData.slice(4, totalPacketSize - 3); const payload = packetData.slice(4, totalPacketSize - 3);
const hexStr = packetData.map(b => b.toString(16).padStart(2,'0').toUpperCase()).join(' '); const hexStr = packetData.map(b => b.toString(16).padStart(2, '0').toUpperCase()).join(' ');
addLog('ACS', 'RX', `[ID:${id} CMD:${cmd}] ${hexStr}`); addLog('ACS', 'RX', `[ID:${id} CMD:${cmd}] ${hexStr}`);
// Consume buffer // Consume buffer
@@ -890,7 +891,7 @@ const App: React.FC = () => {
const [xStr, yStr] = l.Position.split(',').map(s => s.trim()); const [xStr, yStr] = l.Position.split(',').map(s => s.trim());
newNodes.push({ newNodes.push({
id: l.Id, id: l.Id,
x: parseFloat(xStr)||0, y: parseFloat(yStr)||0, x: parseFloat(xStr) || 0, y: parseFloat(yStr) || 0,
type: NodeType.Label, type: NodeType.Label,
name: "", name: "",
rfidId: "", rfidId: "",
@@ -909,7 +910,7 @@ const App: React.FC = () => {
const [xStr, yStr] = img.Position.split(',').map(s => s.trim()); const [xStr, yStr] = img.Position.split(',').map(s => s.trim());
newNodes.push({ newNodes.push({
id: img.Id, id: img.Id,
x: parseFloat(xStr)||0, y: parseFloat(yStr)||0, x: parseFloat(xStr) || 0, y: parseFloat(yStr) || 0,
type: NodeType.Image, type: NodeType.Image,
name: img.Name, name: img.Name,
rfidId: "", rfidId: "",
@@ -1047,7 +1048,7 @@ const App: React.FC = () => {
NodeTextFontSize: n.fontSize || 7.0, NodeTextFontSize: n.fontSize || 7.0,
AliasName: "", AliasName: "",
SpeedLimit: 0, SpeedLimit: 0,
CanDocking: [1,2,3,4,5].includes(n.type), CanDocking: [1, 2, 3, 4, 5].includes(n.type),
DockDirection: n.type === NodeType.Charging || n.type === NodeType.ChargerStation ? 1 : 0, DockDirection: n.type === NodeType.Charging || n.type === NodeType.ChargerStation ? 1 : 0,
CanTurnLeft: true, CanTurnLeft: true,
CanTurnRight: true, CanTurnRight: true,
@@ -1126,7 +1127,7 @@ const App: React.FC = () => {
}; };
// --- Real-time updates to Serial (AGV Events) --- // --- Real-time updates to Serial (AGV Events) ---
const prevSensors = useRef({ rfid: null as string|null, mark: false, line: false }); const prevSensors = useRef({ rfid: null as string | null, mark: false, line: false });
useEffect(() => { useEffect(() => {
if (!agvConnected || !agvSerialRef.current) return; if (!agvConnected || !agvSerialRef.current) return;
@@ -1164,8 +1165,38 @@ const App: React.FC = () => {
<div className="w-80 border-r border-gray-800 bg-gray-900 flex flex-col shrink-0"> <div className="w-80 border-r border-gray-800 bg-gray-900 flex flex-col shrink-0">
<div className="flex-1 overflow-hidden"> <div className="flex-1 overflow-hidden">
<AgvStatusPanel agvState={agvState} /> <AgvStatusPanel agvState={agvState} />
</div> </div>
{/* Auto Run Controls (Left Sidebar) */}
<div className="w-80 border-r border-gray-800 bg-gray-900 flex flex-col shrink-0">
<div className="flex-1 overflow-hidden">
<AgvAutoRunControls
agvState={agvState}
updateRunConfig={(key, value) => {
if (agvState.error) return;
setAgvState(s => ({ ...s, runConfig: { ...s.runConfig, [key]: value } }));
}}
toggleRun={() => {
if (agvState.error) return;
if (agvState.motionState === AgvMotionState.RUNNING || agvState.motionState === AgvMotionState.MARK_STOPPING) {
setAgvState(s => ({ ...s, motionState: AgvMotionState.IDLE }));
} else {
const isFwd = agvState.runConfig.direction === 'FWD';
const hasLine = isFwd ? agvState.sensorLineFront : agvState.sensorLineRear;
if (!hasLine) {
setAgvState(s => ({ ...s, error: 'LINE_OUT' }));
return;
}
setAgvState(s => ({ ...s, motionState: AgvMotionState.RUNNING }));
}
}}
isRunning={agvState.motionState === AgvMotionState.RUNNING || agvState.motionState === AgvMotionState.MARK_STOPPING}
isError={agvState.error !== null}
setLidar={(isOn) => setAgvState(s => ({ ...s, lidarEnabled: isOn, sensorStatus: isOn ? '1' : '0' }))}
/>
</div>
</div>
{/* Middle 2: ACS Panel (Fixed) */} {/* Middle 2: ACS Panel (Fixed) */}
<div className="flex-shrink-0"> <div className="flex-shrink-0">
@@ -1242,20 +1273,20 @@ const App: React.FC = () => {
<div className="flex-1 overflow-y-auto"> <div className="flex-1 overflow-y-auto">
<AgvControls <AgvControls
agvState={agvState} agvState={agvState}
setMotion={(m) => setAgvState(s => ({...s, motionState: m}))} setMotion={(m) => setAgvState(s => ({ ...s, motionState: m }))}
setLift={(h) => setAgvState(s => ({...s, liftHeight: h}))} setLift={(h) => setAgvState(s => ({ ...s, liftHeight: h }))}
setRunConfig={(c) => setAgvState(s => ({...s, runConfig: c}))} setRunConfig={(c) => setAgvState(s => ({ ...s, runConfig: c }))}
setError={(e) => setAgvState(s => ({...s, error: e}))} setError={(e) => setAgvState(s => ({ ...s, error: e }))}
onTurn180={handleTurn180} onTurn180={handleTurn180}
setMagnet={(isOn) => setAgvState(s => ({...s, magnetOn: isOn}))} setMagnet={(isOn) => setAgvState(s => ({ ...s, magnetOn: isOn }))}
setLiftStatus={(status) => setAgvState(s => ({...s, liftStatus: status}))} setLiftStatus={(status) => setAgvState(s => ({ ...s, liftStatus: status }))}
setLidar={(isOn) => setAgvState(s => ({...s, lidarEnabled: isOn, sensorStatus: isOn ? '1' : '0'}))} setLidar={(isOn) => setAgvState(s => ({ ...s, lidarEnabled: isOn, sensorStatus: isOn ? '1' : '0' }))}
/> />
</div> </div>
{/* Middle 1: BMS Panel (Fixed) */} {/* Middle 1: BMS Panel (Fixed) */}
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<BmsPanel state={agvState} setBatteryLevel={(l) => setAgvState(s => ({...s, batteryLevel: l}))} /> <BmsPanel state={agvState} setBatteryLevel={(l) => setAgvState(s => ({ ...s, batteryLevel: l }))} />
</div> </div>
{/* Bottom: System Logs (Fixed Height) */} {/* Bottom: System Logs (Fixed Height) */}
@@ -1263,7 +1294,7 @@ const App: React.FC = () => {
<SystemLogPanel logs={logs.filter(l => l.source === 'SYSTEM')} onClear={() => clearLogs('SYSTEM')} /> <SystemLogPanel logs={logs.filter(l => l.source === 'SYSTEM')} onClear={() => clearLogs('SYSTEM')} />
</div> </div>
</div> </div>
</div> </div >
); );
}; };

1
commit_msg_2.txt Normal file
View File

@@ -0,0 +1 @@
Update: Relocate AutoRun controls and cleanup

View File

@@ -3,7 +3,7 @@ import React from 'react';
import { StopCircle, Play, Square, AlertTriangle, ChevronsUp, ChevronsDown, Magnet, Radar, ArrowLeft, ArrowRight } from 'lucide-react'; import { StopCircle, Play, Square, AlertTriangle, ChevronsUp, ChevronsDown, Magnet, Radar, ArrowLeft, ArrowRight } from 'lucide-react';
import { AgvState, AgvMotionState, AgvRunConfig } from '../types'; import { AgvState, AgvMotionState, AgvRunConfig } from '../types';
import AgvManualControls from './AgvManualControls'; import AgvManualControls from './AgvManualControls';
import AgvAutoRunControls from './AgvAutoRunControls';
interface AgvControlsProps { interface AgvControlsProps {
agvState: AgvState; agvState: AgvState;
@@ -29,22 +29,7 @@ const AgvControls: React.FC<AgvControlsProps> = ({ agvState, setMotion, setLift,
}); });
}; };
const toggleRun = () => {
if (isError) return;
if (isRunning) {
setMotion(AgvMotionState.IDLE);
} else {
const isFwd = agvState.runConfig.direction === 'FWD';
const hasLine = isFwd ? agvState.sensorLineFront : agvState.sensorLineRear;
if (!hasLine) {
setError('LINE_OUT');
return;
}
setMotion(AgvMotionState.RUNNING);
}
};
const handleMarkStop = () => { const handleMarkStop = () => {
if (agvState.motionState === AgvMotionState.RUNNING) { if (agvState.motionState === AgvMotionState.RUNNING) {
@@ -152,14 +137,7 @@ const AgvControls: React.FC<AgvControlsProps> = ({ agvState, setMotion, setLift,
{/* Auto Run Controls */} {/* Auto Run Controls */}
{/* Auto Run Controls (분리된 콤포넌트) */} {/* Auto Run Controls (분리된 콤포넌트) */}
<AgvAutoRunControls
agvState={agvState}
updateRunConfig={updateRunConfig}
toggleRun={toggleRun}
isRunning={isRunning}
isError={isError}
setLidar={setLidar}
/>
</div> </div>
); );
}; };