/** * StatusHeader.jsx — Persistent status bar with robot health indicators * * Features: * - Battery percentage and status indicator * - WiFi signal strength (RSSI) * - Motor status (running/stopped/error) * - Emergency state indicator (active/clear) * - System uptime * - Current operational mode (idle/navigation/social/docking) * - Real-time updates from ROS topics * - Always visible at top of dashboard */ import { useEffect, useState } from 'react'; function StatusHeader({ subscribe }) { const [batteryPercent, setBatteryPercent] = useState(null); const [batteryVoltage, setBatteryVoltage] = useState(null); const [wifiRssi, setWifiRssi] = useState(null); const [wifiQuality, setWifiQuality] = useState('unknown'); const [motorStatus, setMotorStatus] = useState('idle'); const [motorCurrent, setMotorCurrent] = useState(null); const [emergencyActive, setEmergencyActive] = useState(false); const [uptime, setUptime] = useState(0); const [currentMode, setCurrentMode] = useState('idle'); const [connected, setConnected] = useState(true); // Battery subscriber useEffect(() => { const unsubBattery = subscribe( '/saltybot/battery', 'sensor_msgs/BatteryState', (msg) => { try { setBatteryPercent(Math.round(msg.percentage * 100)); setBatteryVoltage(msg.voltage?.toFixed(1)); } catch (e) { console.error('Error parsing battery data:', e); } } ); return unsubBattery; }, [subscribe]); // WiFi RSSI subscriber useEffect(() => { const unsubWifi = subscribe( '/saltybot/wifi_rssi', 'std_msgs/Float32', (msg) => { try { const rssi = Math.round(msg.data); setWifiRssi(rssi); if (rssi > -50) setWifiQuality('excellent'); else if (rssi > -60) setWifiQuality('good'); else if (rssi > -70) setWifiQuality('fair'); else if (rssi > -80) setWifiQuality('weak'); else setWifiQuality('poor'); } catch (e) { console.error('Error parsing WiFi data:', e); } } ); return unsubWifi; }, [subscribe]); // Motor status subscriber useEffect(() => { const unsubMotor = subscribe( '/saltybot/motor_status', 'std_msgs/String', (msg) => { try { const status = msg.data?.toLowerCase() || 'unknown'; setMotorStatus(status); } catch (e) { console.error('Error parsing motor status:', e); } } ); return unsubMotor; }, [subscribe]); // Motor current subscriber useEffect(() => { const unsubCurrent = subscribe( '/saltybot/motor_current', 'std_msgs/Float32', (msg) => { try { setMotorCurrent(Math.round(msg.data * 100) / 100); } catch (e) { console.error('Error parsing motor current:', e); } } ); return unsubCurrent; }, [subscribe]); // Emergency subscriber useEffect(() => { const unsubEmergency = subscribe( '/saltybot/emergency', 'std_msgs/Bool', (msg) => { try { setEmergencyActive(msg.data === true); } catch (e) { console.error('Error parsing emergency status:', e); } } ); return unsubEmergency; }, [subscribe]); // Uptime tracking useEffect(() => { const startTime = Date.now(); const interval = setInterval(() => { const elapsed = Math.floor((Date.now() - startTime) / 1000); const hours = Math.floor(elapsed / 3600); const minutes = Math.floor((elapsed % 3600) / 60); setUptime(`${hours}h ${minutes}m`); }, 1000); return () => clearInterval(interval); }, []); // Current mode subscriber useEffect(() => { const unsubMode = subscribe( '/saltybot/current_mode', 'std_msgs/String', (msg) => { try { const mode = msg.data?.toLowerCase() || 'idle'; setCurrentMode(mode); } catch (e) { console.error('Error parsing mode:', e); } } ); return unsubMode; }, [subscribe]); // Connection status useEffect(() => { const timer = setTimeout(() => { setConnected(batteryPercent !== null); }, 2000); return () => clearTimeout(timer); }, [batteryPercent]); const getBatteryColor = () => { if (batteryPercent === null) return 'text-gray-600'; if (batteryPercent > 60) return 'text-green-400'; if (batteryPercent > 30) return 'text-amber-400'; return 'text-red-400'; }; const getWifiColor = () => { if (wifiRssi === null) return 'text-gray-600'; if (wifiQuality === 'excellent' || wifiQuality === 'good') return 'text-green-400'; if (wifiQuality === 'fair') return 'text-amber-400'; return 'text-red-400'; }; const getMotorColor = () => { if (motorStatus === 'running') return 'text-green-400'; if (motorStatus === 'idle') return 'text-gray-500'; return 'text-red-400'; }; const getModeColor = () => { switch (currentMode) { case 'navigation': return 'text-cyan-400'; case 'social': return 'text-purple-400'; case 'docking': return 'text-blue-400'; default: return 'text-gray-500'; } }; return (
{/* Connection status */}
{connected ? 'CONNECTED' : 'DISCONNECTED'}
{/* Battery */}
🔋 {batteryPercent !== null ? `${batteryPercent}%` : '—'} {batteryVoltage && ( {batteryVoltage}V )}
{/* WiFi */}
📡 {wifiRssi !== null ? `${wifiRssi}dBm` : '—'} {wifiQuality}
{/* Motors */}
⚙️ {motorStatus} {motorCurrent !== null && ( {motorCurrent}A )}
{/* Emergency */}
{emergencyActive ? '🚨 EMERGENCY' : '✓ Safe'}
{/* Uptime */}
⏱️ {uptime}
{/* Current Mode */}
Mode: {currentMode}
); } export { StatusHeader };