// Overcurrent Overlay // Purpose: Defines the Overcurrent Overlay module and the local helpers/components used in this file. // Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit. import React from 'react'; import { useMemo } from 'react'; import { useSessionSelector } from '../../../context/SessionContext.jsx'; import { useTelemetryFrame } from '../../../context/TelemetryContext.jsx'; import { useOvercurrentLimiter } from '../../../controls/index.js'; import { OVERCURRENT_LABELS } from './constants.js'; function OvercurrentOverlay({ roverId = null, sensors, overcurrentLimiter = null, compact = false }) { const assignedRoverId = useSessionSelector((state) => state.session?.assignment?.roverId ?? null); const effectiveRoverId = roverId ?? assignedRoverId; const frame = useTelemetryFrame(effectiveRoverId); const internalLimiter = useOvercurrentLimiter(effectiveRoverId); const resolvedSensors = sensors ?? frame?.sensors ?? null; const resolvedOvercurrentLimiter = overcurrentLimiter ?? internalLimiter ?? null; const wheelOvercurrents = resolvedSensors?.wheelOvercurrents || null; const overcurrentMotors = useMemo( () => wheelOvercurrents == null ? [] : Object.entries(wheelOvercurrents) .filter(([, active]) => Boolean(active)) .map(([key]) => key), [wheelOvercurrents], ); const limiterCaps = resolvedOvercurrentLimiter?.caps || null; const limiterFill = useMemo(() => { if (!limiterCaps) return null; const driveCap = Number.isFinite(limiterCaps?.drive?.cap) ? limiterCaps.drive.cap : 1; const auxCap = Number.isFinite(limiterCaps?.aux?.cap) ? limiterCaps.aux.cap : 1; return Math.max(0, Math.min(1, 1 - Math.min(driveCap, auxCap))); }, [limiterCaps]); const limiterActive = Boolean(resolvedOvercurrentLimiter?.isActive); const motors = useMemo( () => (overcurrentMotors.length ? overcurrentMotors : limiterActive ? ['limiter'] : []), [overcurrentMotors, limiterActive], ); const fill = limiterFill ?? (overcurrentMotors.length ? 1 : 0); if (!motors?.length) return null; const safeLabels = motors.map((name) => OVERCURRENT_LABELS[name] || name); const containerClass = compact ? 'w-[12rem] h-[3.5rem]' : 'w-[20rem] h-[7rem]'; const padClass = compact ? 'px-2 py-1' : 'px-4 py-2'; const textClass = compact ? 'text-lg' : 'text-4xl'; const subTextClass = compact ? 'text-xs' : 'text-xl'; const safeFill = Math.max(0, Math.min(1, fill)); const fillWidth = `${Math.round(safeFill * 100)}%`; return (
OVERCURRENT
{safeLabels.join(', ')}
); } export default React.memo(OvercurrentOverlay);