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"
- />
-
+ {/* 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"
+ />
+
+
+
-
+
+
+
+
+ {/* Action Buttons Grid */}
+
+
handleSend(0x83, undefined, 1)}
+ className="flex items-center justify-center gap-2 bg-blue-900/40 hover:bg-blue-800 border border-blue-800 text-blue-200 py-2 rounded text-xs transition-colors"
>
- GO
+ Pick ON
+
+
handleSend(0x83, undefined, 0)}
+ className="flex items-center justify-center gap-2 bg-gray-700 hover:bg-gray-600 border border-gray-600 text-gray-300 py-2 rounded text-xs transition-colors"
+ >
+ Pick OFF
+
+
+
handleSend(0x84, undefined, 1)}
+ className="flex items-center justify-center gap-2 bg-yellow-900/40 hover:bg-yellow-800 border border-yellow-800 text-yellow-200 py-2 rounded text-xs transition-colors"
+ >
+ Charge ON
+
+
handleSend(0x84, undefined, 0)}
+ className="flex items-center justify-center gap-2 bg-gray-700 hover:bg-gray-600 border border-gray-600 text-gray-300 py-2 rounded text-xs transition-colors"
+ >
+ Charge OFF
- {/* 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"
- />
-
+ {/* Lift Controls (New) */}
+
+
Lift Control
+
+ handleSend(0x85, undefined, 1)} className="flex-1 bg-gray-700 hover:bg-green-700 text-white text-xs py-1.5 rounded border border-gray-600">UP
+ handleSend(0x85, undefined, 0)} className="flex-1 bg-gray-700 hover:bg-red-700 text-white text-xs py-1.5 rounded border border-gray-600">STOP
+ handleSend(0x85, undefined, 2)} className="flex-1 bg-gray-700 hover:bg-green-700 text-white text-xs py-1.5 rounded border border-gray-600">DOWN
+
+
+
+ {/* System Controls (New) */}
+
+
System Control
+
+ handleSend(0x86)} className="bg-red-900/40 hover:bg-red-800 text-red-200 border border-red-800 text-xs py-1.5 rounded font-bold">EMG STOP
+ handleSend(0x87)} className="bg-gray-700 hover:bg-gray-600 text-gray-200 border border-gray-600 text-xs py-1.5 rounded">RESET
+
+
+
Mark Stop
+
+ handleSend(0x88, undefined, 1)} className="px-2 py-0.5 text-[10px] bg-green-900 text-green-300 rounded border border-green-800 hover:bg-green-800">ON
+ handleSend(0x88, undefined, 0)} className="px-2 py-0.5 text-[10px] bg-gray-700 text-gray-300 rounded border border-gray-600 hover:bg-gray-600">OFF
+
-
handleSend(0x82, alias)}
- className="bg-gray-700 hover:bg-green-700 text-white text-xs px-3 py-1.5 rounded flex items-center gap-1 transition-colors border border-gray-600"
- >
- GO
-
-
-
-
- {/* Action Buttons Grid */}
-
-
handleSend(0x83, undefined, 1)}
- className="flex items-center justify-center gap-2 bg-blue-900/40 hover:bg-blue-800 border border-blue-800 text-blue-200 py-2 rounded text-xs transition-colors"
- >
- Pick ON
-
-
handleSend(0x83, undefined, 0)}
- className="flex items-center justify-center gap-2 bg-gray-700 hover:bg-gray-600 border border-gray-600 text-gray-300 py-2 rounded text-xs transition-colors"
- >
- Pick OFF
-
-
-
handleSend(0x84, undefined, 1)}
- className="flex items-center justify-center gap-2 bg-yellow-900/40 hover:bg-yellow-800 border border-yellow-800 text-yellow-200 py-2 rounded text-xs transition-colors"
- >
- Charge ON
-
-
handleSend(0x84, undefined, 0)}
- className="flex items-center justify-center gap-2 bg-gray-700 hover:bg-gray-600 border border-gray-600 text-gray-300 py-2 rounded text-xs transition-colors"
- >
- Charge OFF
-
-
-
- );
+ );
};
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 */}
+
+ setIsCharging(!state.isCharging)}
+ className={`w-full py-1.5 rounded flex items-center justify-center gap-2 text-xs font-bold transition-all ${state.isCharging
+ ? 'bg-yellow-600 text-white shadow-[0_0_10px_rgba(202,138,4,0.5)] border border-yellow-500'
+ : 'bg-gray-700 text-gray-400 hover:bg-gray-600 border border-gray-600'
+ }`}
+ >
+
+ {state.isCharging ? 'CHARGING...' : 'START CHARGING'}
+
+
+
+ {/* 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
-
+
+
+
+
+
-
+
{/* 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[];
}