// Overcurrent Protection Panel // Purpose: Presents detailed server-calculated motor stress and command-tracking diagnostics. // Scope: Read-only status surface for the assigned rover; protection and recovery remain server-owned. import { useControlSelector } from '../../controls/index.js'; import CardFrame from '../CardFrame/index.jsx'; const MOTOR_LABELS = { leftWheel: 'Left wheel', rightWheel: 'Right wheel', mainBrush: 'Main brush', sideBrush: 'Side brush', }; function formatPct(value) { if (!Number.isFinite(value)) return '--'; return `${Math.round(value * 100)}%`; } function formatSpeed(value) { if (!Number.isFinite(value)) return '--'; return `${Math.round(value)} mm/s`; } function formatClassification(value) { if (value === 'stalled') return 'Stalled'; if (value === 'partial') return 'Partial'; if (value === 'moving') return 'Moving'; return 'Unknown'; } function ProgressBar({ value, color = 'bg-emerald-500' }) { const width = `${Math.round(Math.max(0, Math.min(1, Number(value) || 0)) * 100)}%`; return (
); } function statusLabel(protection) { if (protection?.adminImmune) return 'Admin bypass'; if (protection?.status === 'stopped') return 'Drive stopped'; if (protection?.status === 'limiting') return 'Limiting'; if (protection?.status === 'overcurrent') return 'Overcurrent detected'; if (protection?.status === 'recovering') return 'Recovering'; return 'Ready'; } export default function OvercurrentLimiterPanel() { const roverId = useControlSelector((control) => control.state.roverId); const protection = useControlSelector((control) => control.overcurrentLimiter); const motors = protection?.motors || {}; return ( {!roverId ? (

Assign a rover to view protection status.

) : (
{Object.entries(MOTOR_LABELS).map(([key, label]) => { const motor = motors[key] || {}; const wheel = key === 'leftWheel' || key === 'rightWheel'; return (
{label} {motor.overcurrent ? 'Overcurrent' : 'Clear'}
Stress {formatPct(motor.stress)} Output {formatPct(motor.cap)}
{wheel ? (
{`Command ${formatSpeed(Math.abs(Number(motor.commandedSpeed)))}`} {`Measured ${formatSpeed(motor.measuredSpeed)}`} {`Progress ${formatPct(motor.progressRatio)}`} {`${formatClassification(motor.classification)} ยท stall ${formatPct(motor.stallFactor)}`}
) : null}
); })} {protection?.drive?.blocked ? (

{protection.drive.requiresNeutral ? 'Drive is stopped. Release controls to neutral before resuming.' : 'Drive is stopped while the wheel condition clears.'}

) : null}
{`Drive output ${formatPct(protection?.drive?.cap)}`}
{protection?.adminImmune ? 'This session bypasses all overcurrent enforcement.' : 'Status and output limits are calculated by the server.'}
)}
); }