diff --git a/App.tsx b/App.tsx index eb157a5..39a2ddc 100644 --- a/App.tsx +++ b/App.tsx @@ -71,7 +71,8 @@ const App: React.FC = () => { // Lift & Magnet magnetOn: false, liftStatus: 'IDLE', - lidarEnabled: true, // Default ON + lidarEnabled: false, + isCharging: false, // Initial state // Protocol Flags Initial State system0: 0, @@ -80,8 +81,8 @@ const App: React.FC = () => { signalFlags: 0, // Will be updated by useEffect batteryLevel: 95, - maxCapacity: 100, // 100Ah - batteryTemp: 25, + maxCapacity: 100, + batteryTemps: [25.0, 26.5], cellVoltages: Array(8).fill(3.2), }); @@ -607,6 +608,26 @@ const App: React.FC = () => { const totalV = Math.floor(s.cellVoltages.reduce((a, b) => a + b, 0) * 100); resp[4] = (totalV >> 8) & 0xFF; resp[5] = totalV & 0xFF; + // 6, 7: Current (mA) + // Power (W) = (mA / 1000) * V => mA = (W / V) * 1000. + // Discharging: ~50W, Charging: ~-450W. + const isCharging = s.isCharging; // Use manual state + const powerW = isCharging ? -450 : 50; + + let currentMA = 0; + if (totalV > 0) { + const realV = totalV / 100; // Convert 0.01V to Volts + currentMA = Math.floor((powerW / totalV) * 10); + } + + // Write Int16 (Little Endian or Big Endian? BMS usually Big Endian for these usually? + // Code above uses Big Endian for Voltage (resp[4] = high, resp[5] = low). + // Standard BMS protocols often BE. I will follow the pattern of Voltage. + // Handle negative numbers via Two's Complement for manual byte splitting if needed, + // but bitwise ops on standard JS numbers (treated as 32bit int) work fine for & 0xFF. + resp[6] = (currentMA >> 8) & 0xFF; + resp[7] = currentMA & 0xFF; + const remainAh = Math.floor(s.maxCapacity * (s.batteryLevel / 100)); resp[8] = (remainAh >> 8) & 0xFF; resp[9] = remainAh & 0xFF; const maxAh = s.maxCapacity; @@ -614,8 +635,8 @@ const App: React.FC = () => { resp[23] = Math.floor(s.batteryLevel); - const t1 = Math.floor((s.batteryTemp * 10) + 2731); - const t2 = Math.floor((s.batteryTemp * 10) + 2731); + const t1 = Math.floor((s.batteryTemps[0] * 10) + 2731); + const t2 = Math.floor((s.batteryTemps[1] * 10) + 2731); resp[27] = (t1 >> 8) & 0xFF; resp[28] = t1 & 0xFF; resp[29] = (t2 >> 8) & 0xFF; resp[30] = t2 & 0xFF; @@ -1145,6 +1166,40 @@ const App: React.FC = () => { }; }, [agvState, agvConnected, sendTag]); + // BMS Simulation + useEffect(() => { + const timer = setInterval(() => { + setAgvState(s => { + const isCharging = s.isCharging; + let newLevel = s.batteryLevel; + + if (isCharging) { + // Charge: 1% per min (faster than discharge) + newLevel = Math.min(100, s.batteryLevel + (1.0 / 60)); + } else { + // Discharge: 5% per 10 min = 0.5% per min = 0.5 / 60 per sec + newLevel = Math.max(0, s.batteryLevel - (0.5 / 60)); + } + + // Fluctuate Voltages (+- 0.01V) + const newVoltages = s.cellVoltages.map(v => { + const delta = (Math.random() - 0.5) * 0.02; + let val = v + delta; + if (val < 2.8) val = 2.8; + if (val > 3.6) val = 3.6; + return val; + }); + + return { + ...s, + batteryLevel: newLevel, + cellVoltages: newVoltages + }; + }); + }, 1000); + return () => clearInterval(timer); + }, []); + return (
{
{/* 2. Inner Left: Protocol Flags (Separated Sidebar) - Removed BMS from here */} -
+
{/* Auto Run Controls (Left Sidebar) */} -
+
{
{/* 4. Right: Controls & BMS & ACS */} -
+
{/* Top: Controls (Scrollable if needed) */}
@@ -1286,7 +1341,13 @@ const App: React.FC = () => { {/* Middle 1: BMS Panel (Fixed) */}
- setAgvState(s => ({ ...s, batteryLevel: l }))} /> + setAgvState(s => ({ ...s, batteryLevel: l }))} + setIsCharging={(isConnected) => { + setAgvState(s => ({ ...s, isCharging: isConnected })); + }} + />
{/* Bottom: System Logs (Fixed Height) */} diff --git a/bms_sim_code.txt b/bms_sim_code.txt new file mode 100644 index 0000000..02a9fc6 --- /dev/null +++ b/bms_sim_code.txt @@ -0,0 +1,33 @@ + // BMS Simulation + useEffect(() => { + const timer = setInterval(() => { + setAgvState(s => { + const isCharging = s.isCharging; + let newLevel = s.batteryLevel; + + if (isCharging) { + // Charge: 1% per min (faster than discharge) + newLevel = Math.min(100, s.batteryLevel + (1.0 / 60)); + } else { + // Discharge: 5% per 10 min = 0.5% per min = 0.5 / 60 per sec + newLevel = Math.max(0, s.batteryLevel - (0.5 / 60)); + } + + // Fluctuate Voltages (+- 0.01V) + const newVoltages = s.cellVoltages.map(v => { + const delta = (Math.random() - 0.5) * 0.02; + let val = v + delta; + if (val < 2.8) val = 2.8; + if (val > 3.6) val = 3.6; + return val; + }); + + return { + ...s, + batteryLevel: newLevel, + cellVoltages: newVoltages + }; + }); + }, 1000); + return () => clearInterval(timer); + }, []); diff --git a/commit_msg_2.txt b/commit_msg_2.txt deleted file mode 100644 index 6f44bca..0000000 --- a/commit_msg_2.txt +++ /dev/null @@ -1 +0,0 @@ -Update: Relocate AutoRun controls and cleanup diff --git a/components/AcsControls.tsx b/components/AcsControls.tsx index f222716..2058e25 100644 --- a/components/AcsControls.tsx +++ b/components/AcsControls.tsx @@ -3,140 +3,167 @@ import { Navigation, Package, Zap, ChevronRight, Hash, Type } from 'lucide-react import { SimulationMap } from '../types'; interface AcsControlsProps { - onSend: (cmd: number, data?: number[]) => void; - isConnected: boolean; - mapData: SimulationMap; + onSend: (cmd: number, data?: number[]) => void; + isConnected: boolean; + mapData: SimulationMap; } const AcsControls: React.FC = ({ onSend, isConnected, mapData }) => { - const [tagId, setTagId] = useState('0001'); - const [alias, setAlias] = useState('charger1'); + const [tagId, setTagId] = useState('0001'); + const [alias, setAlias] = useState('charger1'); - const PRESET_ALIASES = [ - 'charger1', 'charger2', - 'loader', 'unloader', 'cleaner', - 'buffer1', 'buffer2', 'buffer3', 'buffer4', 'buffer5', 'buffer6' - ]; + const PRESET_ALIASES = [ + 'charger1', 'charger2', + 'loader', 'unloader', 'cleaner', + 'buffer1', 'buffer2', 'buffer3', 'buffer4', 'buffer5', 'buffer6' + ]; - const availableTags = useMemo(() => { - // Extract unique, non-empty RFID tags from map nodes - const tags = mapData.nodes - .filter(n => n.rfidId && n.rfidId.trim() !== '') - .map(n => n.rfidId); - return Array.from(new Set(tags)).sort(); - }, [mapData]); + const availableTags = useMemo(() => { + // Extract unique, non-empty RFID tags from map nodes + const tags = mapData.nodes + .filter(n => n.rfidId && n.rfidId.trim() !== '') + .map(n => n.rfidId); + return Array.from(new Set(tags)).sort(); + }, [mapData]); - const handleSend = (cmd: number, dataStr?: string, dataByte?: number) => { - if (!isConnected) return; - - let payload: number[] = []; - - if (dataStr !== undefined) { - // Convert string to ASCII bytes - for (let i = 0; i < dataStr.length; i++) { - payload.push(dataStr.charCodeAt(i)); + const handleSend = (cmd: number, dataStr?: string, dataByte?: number) => { + if (!isConnected) return; + + let payload: number[] = []; + + if (dataStr !== undefined) { + // Convert string to ASCII bytes + for (let i = 0; i < dataStr.length; i++) { + payload.push(dataStr.charCodeAt(i)); + } + } else if (dataByte !== undefined) { + payload.push(dataByte); } - } else if (dataByte !== undefined) { - payload.push(dataByte); - } - onSend(cmd, payload); - }; + onSend(cmd, payload); + }; - return ( -
-

- ACS Control - EXT CMD -

+ return ( +
+

+ ACS Control + EXT CMD +

- {/* Navigation Commands */} -
- {/* Goto Tag */} -
-
- - setTagId(e.target.value)} - className="w-full bg-gray-900 border border-gray-600 rounded py-1 pl-7 pr-2 text-xs text-white font-mono focus:border-green-500 outline-none" - placeholder="Tag ID" - /> - - {availableTags.map(tag => ( - + {/* Navigation Commands */} +
+ {/* Goto Tag */} +
+
+ + setTagId(e.target.value)} + className="w-full bg-gray-900 border border-gray-600 rounded py-1 pl-7 pr-2 text-xs text-white font-mono focus:border-green-500 outline-none" + placeholder="Tag ID" + /> + + {availableTags.map(tag => ( + +
+
- +
+
+ + +
+ + {/* Action Buttons Grid */} +
+ + + + +
- {/* Goto Alias */} -
-
- - setAlias(e.target.value)} - className="w-full bg-gray-900 border border-gray-600 rounded py-1 pl-7 pr-2 text-xs text-white font-mono focus:border-green-500 outline-none" - placeholder="Alias Name" - /> - - {PRESET_ALIASES.map(item => ( - + {/* Lift Controls (New) */} +
+

Lift Control

+
+ + + +
+
+ + {/* System Controls (New) */} +
+

System Control

+
+ + +
+
+ Mark Stop +
+ + +
-
- -
- - {/* Action Buttons Grid */} -
- - - - - -
-
- ); + ); }; export default AcsControls; \ No newline at end of file diff --git a/components/BmsPanel.tsx b/components/BmsPanel.tsx index 1b4d9fd..b06aeb1 100644 --- a/components/BmsPanel.tsx +++ b/components/BmsPanel.tsx @@ -3,86 +3,119 @@ import { BatteryCharging, Zap, Thermometer, Sliders } from 'lucide-react'; import { AgvState } from '../types'; interface BmsPanelProps { - state: AgvState; - setBatteryLevel: (level: number) => void; + state: AgvState; + setBatteryLevel: (level: number) => void; + setIsCharging: (isCharging: boolean) => void; } -const BmsPanel: React.FC = ({ state, setBatteryLevel }) => { - const currentCapacity = (state.maxCapacity * (state.batteryLevel / 100)).toFixed(1); +const BmsPanel: React.FC = ({ state, setBatteryLevel, setIsCharging }) => { + const currentCapacity = (state.maxCapacity * (state.batteryLevel / 100)).toFixed(1); - return ( -
-

- BMS Monitor - ID: 01 -

- - {/* Main Info Grid */} -
-
-
Total Voltage
-
- {(state.cellVoltages.reduce((a,b)=>a+b, 0)).toFixed(1)} V -
-
-
-
- Temp -
-
- {state.batteryTemp.toFixed(1)} °C -
-
- -
-
Capacity (Ah)
-
- {currentCapacity} - / - {state.maxCapacity} -
-
-
-
Level (%)
-
- {state.batteryLevel.toFixed(1)}% + return ( +
+

+ BMS Monitor + ID: 01 +

+ + {/* Main Info Grid */} +
+
+
Total Voltage
+
+ {(state.cellVoltages.reduce((a, b) => a + b, 0)).toFixed(1)} V +
+
+
+
+ Temp 1 / 2 +
+
+
+ 1: {state.batteryTemps?.[0]?.toFixed(1)}°C +
+
+ 2: {state.batteryTemps?.[1]?.toFixed(1)}°C +
+
-
-
- {/* Manual Slider */} -
-
- Manual Adjust -
- setBatteryLevel(Number(e.target.value))} - className="w-full h-1.5 bg-gray-600 rounded-lg appearance-none cursor-pointer accent-green-500 hover:accent-green-400" - /> -
+
+
Capacity (Ah)
+
+ {currentCapacity} + / + {state.maxCapacity} +
+
+
+
Level (%)
+
+ {state.batteryLevel.toFixed(1)}% +
+
- {/* Cells */} -
-
Cell Voltages (V)
-
- {state.cellVoltages.map((v, i) => ( -
3.5 ? 'bg-blue-900/30 border-blue-800 text-blue-300' : - 'bg-gray-700/30 border-gray-600 text-gray-300' - }`}> - {v.toFixed(3)} -
- ))} -
-
-
- ); +
+
Current (A)
+
+ {(state.isCharging ? -18.7 : 2.1).toFixed(1)} A +
+
+ +
+
Power (W)
+
+ {state.isCharging ? -450 : 50} W +
+
+
+ + {/* Charge Button */} +
+ +
+ + {/* Manual Slider */} +
+
+ Manual Adjust +
+ setBatteryLevel(Number(e.target.value))} + className="w-full h-1.5 bg-gray-600 rounded-lg appearance-none cursor-pointer accent-green-500 hover:accent-green-400" + /> +
+ + {/* Cells */} +
+
Cell Voltages (V)
+
+ {state.cellVoltages.map((v, i) => ( +
3.5 ? 'bg-blue-900/30 border-blue-800 text-blue-300' : + 'bg-gray-700/30 border-gray-600 text-gray-300' + }`}> + {v.toFixed(3)} +
+ ))} +
+
+
+ ); }; export default BmsPanel; diff --git a/components/SystemLogPanel.tsx b/components/SystemLogPanel.tsx index ae5b6a6..2581795 100644 --- a/components/SystemLogPanel.tsx +++ b/components/SystemLogPanel.tsx @@ -15,7 +15,7 @@ const SystemLogPanel: React.FC = ({ logs, onClear }) => { // Force scroll to top useEffect(() => { if (logContainerRef.current) { - logContainerRef.current.scrollTop = 0; + logContainerRef.current.scrollTop = 0; } }, [reversedLogs]); @@ -23,44 +23,44 @@ const SystemLogPanel: React.FC = ({ logs, onClear }) => {
{/* Header */}
-
-
- SYSTEM LOG -
+
+
+ SYSTEM LOG
- - +
+ +
- + {/* Log Body */} -
- {reversedLogs.length === 0 && ( -
- No System Activity -
- )} - - {reversedLogs.map((log, index) => ( -
- {index + 1} - {log.timestamp} - + No System Activity +
+ )} + + {reversedLogs.map((log, index) => ( +
+ {index + 1} + {log.timestamp} + - {log.message} - -
- ))} + {log.message} + +
+ ))}
); diff --git a/types.ts b/types.ts index 66a6dc8..add5abc 100644 --- a/types.ts +++ b/types.ts @@ -22,7 +22,7 @@ export enum NodeType { UnLoader = 2, Charging = 3, // Used for Cleaner in new map Buffer = 4, - ChargerStation = 5, + ChargerStation = 5, Label = 6, Image = 7 } @@ -34,14 +34,14 @@ export interface MapNode extends Point { name: string; rfidId: string; // RFID is property of Node connectedNodes: string[]; // Keep track for export - + // Visual props labelText?: string; foreColor?: string; backColor?: string; imageBase64?: string; displayColor?: string; - + // New props preservation fontSize?: number; } @@ -177,10 +177,10 @@ export enum AgvError { Charger_pos_error = 3, line_out_error = 4, runerror_by_no_magent_line = 5, - agv_system_error=6, - battery_low_voltage=7, - lift_time_over=9, - lift_driver_ocr=10, + agv_system_error = 6, + battery_low_voltage = 7, + lift_time_over = 9, + lift_driver_ocr = 10, lift_driver_emg = 11, arrive_ctl_comm_error = 12, door_ctl_comm_error = 13, @@ -241,7 +241,7 @@ export interface AgvState { runConfig: AgvRunConfig; error: string | null; sensorStatus: string; // Corresponds to sts_sensor in C# - + // Physical Sensors detectedRfid: string | null; sensorLineFront: boolean; @@ -251,7 +251,9 @@ export interface AgvState { // New features magnetOn: boolean; liftStatus: 'IDLE' | 'UP' | 'DOWN'; + lidarEnabled: boolean; // 1=ON, 0=OFF + isCharging: boolean; // Manual charging state // Protocol Flags (Integers representing bitmaps) system0: number; @@ -262,7 +264,7 @@ export interface AgvState { // Battery batteryLevel: number; // Percentage 0-100 maxCapacity: number; // Ah (Total Capacity) - batteryTemp: number; + batteryTemps: number[]; // [Temp1, Temp2] cellVoltages: number[]; }