feat: Add CPU status retrieval and display in dashboard
- Implemented `get_cpu_status` method in PLCClient to fetch current CPU state, cycle time, and additional CPU info. - Created a new API endpoint `/api/plc/status` to serve CPU status data. - Updated frontend to load and display CPU status in the Dashboard, including state and cycle time. - Added error handling for CPU status retrieval in the frontend.
This commit is contained in:
parent
dba0ca2528
commit
4eed5d2687
File diff suppressed because it is too large
Load Diff
|
@ -1128,3 +1128,64 @@ class PLCClient:
|
|||
self.logger.warning(f"Error getting batch reader stats: {e}")
|
||||
|
||||
return base_stats
|
||||
|
||||
def get_cpu_status(self) -> Dict[str, Any]:
|
||||
"""Get current CPU status including state and cycle time"""
|
||||
if not self.is_connected():
|
||||
return {"connected": False, "error": "PLC not connected"}
|
||||
|
||||
try:
|
||||
# Get CPU state (RUN, STOP, etc.)
|
||||
cpu_state = self.plc.get_cpu_state()
|
||||
|
||||
# Get execution time (cycle time)
|
||||
exec_time = self.plc.get_exec_time()
|
||||
|
||||
# Get additional CPU info
|
||||
cpu_info = self.plc.get_cpu_info()
|
||||
|
||||
# Map CPU state codes to readable names
|
||||
state_map = {0x08: "RUN", 0x04: "STOP", 0x00: "UNKNOWN"}
|
||||
|
||||
# Safely handle cpu_state formatting
|
||||
try:
|
||||
# Try to get a readable state name
|
||||
if isinstance(cpu_state, (int, bytes)):
|
||||
if isinstance(cpu_state, bytes) and len(cpu_state) > 0:
|
||||
cpu_state = cpu_state[0]
|
||||
state_name = state_map.get(cpu_state, f"STATE_{cpu_state}")
|
||||
else:
|
||||
state_name = f"STATE_{str(cpu_state)}"
|
||||
except Exception as e:
|
||||
state_name = "UNKNOWN"
|
||||
if self.logger:
|
||||
self.logger.warning(f"Error parsing CPU state {cpu_state}: {e}")
|
||||
|
||||
return {
|
||||
"connected": True,
|
||||
"state": state_name,
|
||||
"state_code": cpu_state,
|
||||
"cycle_time_ms": exec_time,
|
||||
"cpu_info": {
|
||||
"module_type_name": cpu_info.ModuleTypeName.decode(
|
||||
"ascii", errors="ignore"
|
||||
).strip(),
|
||||
"serial_number": cpu_info.SerialNumber.decode(
|
||||
"ascii", errors="ignore"
|
||||
).strip(),
|
||||
"as_name": cpu_info.ASName.decode("ascii", errors="ignore").strip(),
|
||||
"module_name": cpu_info.ModuleName.decode(
|
||||
"ascii", errors="ignore"
|
||||
).strip(),
|
||||
"copyright": cpu_info.Copyright.decode(
|
||||
"ascii", errors="ignore"
|
||||
).strip(),
|
||||
},
|
||||
"timestamp": time.time(),
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error reading CPU status: {str(e)}"
|
||||
if self.logger:
|
||||
self.logger.error(error_msg)
|
||||
return {"connected": True, "error": error_msg, "timestamp": time.time()}
|
||||
|
|
|
@ -701,12 +701,44 @@ function StatusBar({ status, isConnected, isLeader, connectionError }) {
|
|||
const [plotJugglerFound, setPlotJugglerFound] = useState(false)
|
||||
const [performanceData, setPerformanceData] = useState(null)
|
||||
const [performanceLoading, setPerformanceLoading] = useState(false)
|
||||
const [cpuStatus, setCpuStatus] = useState(null)
|
||||
const toast = useToast()
|
||||
|
||||
const setLoading = (action, loading) => {
|
||||
setActionLoading(prev => ({ ...prev, [action]: loading }))
|
||||
}
|
||||
|
||||
// Load CPU status data
|
||||
const loadCpuStatus = useCallback(async () => {
|
||||
if (!plcConnected) {
|
||||
setCpuStatus(null)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await api.getPlcStatus()
|
||||
if (response.success && response.status) {
|
||||
setCpuStatus(response.status)
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently fail - CPU status is optional
|
||||
console.warn('Failed to load CPU status:', error)
|
||||
setCpuStatus(null)
|
||||
}
|
||||
}, [plcConnected])
|
||||
|
||||
// Load CPU status when PLC is connected
|
||||
useEffect(() => {
|
||||
if (plcConnected && !connectionError) {
|
||||
loadCpuStatus()
|
||||
// Set up interval to refresh CPU status every 5 seconds
|
||||
const interval = setInterval(loadCpuStatus, 5000)
|
||||
return () => clearInterval(interval)
|
||||
} else {
|
||||
setCpuStatus(null)
|
||||
}
|
||||
}, [plcConnected, connectionError, loadCpuStatus])
|
||||
|
||||
// Load performance data
|
||||
const loadPerformanceData = useCallback(async () => {
|
||||
if (!plcConnected || !csvRecording) {
|
||||
|
@ -925,6 +957,28 @@ function StatusBar({ status, isConnected, isLeader, connectionError }) {
|
|||
</CardBody>
|
||||
</Card>
|
||||
|
||||
{/* CPU Status Card - Shows when PLC is connected */}
|
||||
{plcConnected && cpuStatus && (
|
||||
<Card>
|
||||
<CardBody>
|
||||
<Stat>
|
||||
<StatLabel>🖥️ CPU Status</StatLabel>
|
||||
<StatNumber fontSize="lg" color={cpuStatus.state === 'RUN' ? 'green.500' : 'orange.500'}>
|
||||
{cpuStatus.state || 'UNKNOWN'}
|
||||
</StatNumber>
|
||||
{cpuStatus.cycle_time_ms !== undefined && (
|
||||
<StatHelpText>
|
||||
⏱️ Cycle: {cpuStatus.cycle_time_ms}ms
|
||||
{cpuStatus.cpu_info?.module_type_name && (
|
||||
<><br/>📟 {cpuStatus.cpu_info.module_type_name}</>
|
||||
)}
|
||||
</StatHelpText>
|
||||
)}
|
||||
</Stat>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<Card>
|
||||
<CardBody>
|
||||
<Stat>
|
||||
|
|
|
@ -363,4 +363,12 @@ export async function getHistoricalPerformance(windows = 6) {
|
|||
return toJsonOrThrow(res)
|
||||
}
|
||||
|
||||
// PLC Status API
|
||||
export async function getPlcStatus() {
|
||||
const res = await fetch(`${BASE_URL}/api/plc/status`, {
|
||||
headers: { 'Accept': 'application/json' }
|
||||
})
|
||||
return toJsonOrThrow(res)
|
||||
}
|
||||
|
||||
|
||||
|
|
14
main.py
14
main.py
|
@ -2902,6 +2902,20 @@ def disable_plc_reconnection():
|
|||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
|
||||
@app.route("/api/plc/status")
|
||||
def get_plc_status():
|
||||
"""Get current PLC CPU status including state and cycle time"""
|
||||
error_response = check_streamer_initialized()
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
try:
|
||||
status = streamer.plc_client.get_cpu_status()
|
||||
return jsonify({"success": True, "status": status})
|
||||
except Exception as e:
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
|
||||
@app.route("/api/events")
|
||||
def get_events():
|
||||
"""Get recent events from the application log"""
|
||||
|
|
|
@ -7,6 +7,6 @@
|
|||
]
|
||||
},
|
||||
"auto_recovery_enabled": true,
|
||||
"last_update": "2025-08-28T11:46:10.725708",
|
||||
"last_update": "2025-08-28T13:30:03.511773",
|
||||
"plotjuggler_path": "C:\\Program Files\\PlotJuggler\\plotjuggler.exe"
|
||||
}
|
Loading…
Reference in New Issue