slopfixing / issue 004

This commit is contained in:
legop3
2026-06-12 12:33:00 -04:00
parent dc193d09db
commit cc06709674
29 changed files with 855 additions and 338 deletions
@@ -1,5 +1,6 @@
import { useSessionSelector } from '../../../context/SessionContext.jsx';
import { useTelemetryFrame } from '../../../context/TelemetryContext.jsx';
import { useVisualTelemetrySelector } from '../../../context/TelemetryContext.jsx';
import { batteryTelemetryEqual, selectBatteryTelemetry } from '../../../context/telemetryViews.js';
import LightBumpBars from '../LightBumpBars/index.jsx';
import { buildBatteryVisual } from '../../../lib/battery.js';
import BatteryBar from '../../BatteryBar/index.jsx';
@@ -7,8 +8,7 @@ import BatteryBar from '../../BatteryBar/index.jsx';
export default function DriverBottomStrip({ roverId = null, mobileHud = false }) {
const assignedRoverId = useSessionSelector((state) => state.session?.assignment?.roverId ?? null);
const effectiveRoverId = roverId ?? assignedRoverId;
const frame = useTelemetryFrame(effectiveRoverId);
const sensors = frame?.sensors ?? null;
const batteryTelemetry = useVisualTelemetrySelector(effectiveRoverId, selectBatteryTelemetry, batteryTelemetryEqual);
const batteryConfig = useSessionSelector((state) => {
if (!effectiveRoverId) return null;
const roster = state.session?.roster || [];
@@ -16,7 +16,7 @@ export default function DriverBottomStrip({ roverId = null, mobileHud = false })
return rover?.battery ?? null;
});
const batteryVisual = buildBatteryVisual({
charge: sensors?.batteryChargeMah ?? null,
charge: batteryTelemetry?.batteryChargeMah ?? null,
config: batteryConfig,
});
@@ -2,6 +2,7 @@ import TopDownMap from '../../TopDownMap/index.jsx';
export default function HudMapOverlay({
sensors,
mapTelemetry = null,
show = true,
mapPosition = 'top-center',
layoutFormat = 'desktop',
@@ -32,7 +33,7 @@ export default function HudMapOverlay({
return (
<div className="pointer-events-none absolute rounded" style={mapStyle}>
<TopDownMap sensors={sensors} size={240} overlay />
<TopDownMap sensors={sensors} mapTelemetry={mapTelemetry} size={240} overlay />
</div>
);
}
@@ -1,17 +1,20 @@
export default function SpectatorTelemetryOverlay({ sensors, mobileHud = false }) {
export default function SpectatorTelemetryOverlay({ sensors, telemetry = null, mobileHud = false }) {
const voltageMv = telemetry?.voltageMv ?? sensors?.voltageMv ?? null;
const currentMa = telemetry?.currentMa ?? sensors?.currentMa ?? null;
const batteryChargeMah = telemetry?.batteryChargeMah ?? sensors?.batteryChargeMah ?? null;
const oiLabel = telemetry?.oiModeLabel ?? sensors?.oiMode?.label ?? 'Unknown';
const docked = telemetry?.homeBase ?? Boolean(sensors?.chargingSources?.homeBase);
const chargingLabel = telemetry?.chargingStateLabel ?? sensors?.chargingState?.label ?? '';
const statusPadClass = mobileHud ? 'px-0.25 py-0.25' : 'px-1 py-0.5';
const telemetryPosClass = mobileHud ? 'left-0.5 top-1/2' : 'left-1 top-1/2';
const telemetryTextClass = mobileHud ? 'text-[0.45rem]' : 'text-[0.65rem]';
const telemetryEntries = [
['Voltage', sensors?.voltageMv != null ? `${(sensors.voltageMv / 1000).toFixed(2)} V` : '--'],
['Current', sensors?.currentMa != null ? `${sensors.currentMa} mA` : '--'],
['Charge', sensors?.batteryChargeMah != null ? `${sensors.batteryChargeMah}` : '--'],
['OI', sensors?.oiMode?.label || '--'],
['Voltage', voltageMv != null ? `${(voltageMv / 1000).toFixed(2)} V` : '--'],
['Current', currentMa != null ? `${currentMa} mA` : '--'],
['Charge', batteryChargeMah != null ? `${batteryChargeMah}` : '--'],
['Oi', oiLabel || '--'],
];
const docked = Boolean(sensors?.chargingSources?.homeBase);
const chargingLabel = sensors?.chargingState?.label || '';
const charging = Boolean(chargingLabel && chargingLabel.toLowerCase() !== 'not charging');
const oiLabel = sensors?.oiMode?.label || 'Unknown';
const oiNormalized = oiLabel.toLowerCase();
const oiTone =
oiNormalized === 'full'
@@ -38,7 +41,7 @@ export default function SpectatorTelemetryOverlay({ sensors, mobileHud = false }
<span className={`rounded px-1.5 py-0.5 ${chargingTone}`}>
{charging ? 'Charging' : docked ? 'Not charging' : 'Not charging'}
</span>
<span className={`rounded px-1.5 py-0.5 ${oiTone}`}>OI: {oiLabel}</span>
<span className={`rounded px-1.5 py-0.5 ${oiTone}`}>Oi: {oiLabel}</span>
</div>
{telemetryEntries.map(([labelText, value]) => (
<span key={labelText} className="flex items-center justify-between gap-0.5">
@@ -4,7 +4,13 @@
import React from 'react';
import { useHudMapSetting } from '../../../hooks/useHudMapSetting.js';
import { useSessionSelector } from '../../../context/SessionContext.jsx';
import { useTelemetryFrame } from '../../../context/TelemetryContext.jsx';
import { useVisualTelemetrySelector } from '../../../context/TelemetryContext.jsx';
import {
mapTelemetryEqual,
selectSpectatorTelemetry,
selectVisualMapTelemetry,
spectatorTelemetryEqual,
} from '../../../context/telemetryViews.js';
import RoverLabelOverlay from './RoverLabelOverlay.jsx';
import SpectatorTelemetryOverlay from './SpectatorTelemetryOverlay.jsx';
import HudMapOverlay from './HudMapOverlay.jsx';
@@ -24,7 +30,8 @@ function HudOverlay({
}) {
const assignedRoverId = useSessionSelector((state) => state.session?.assignment?.roverId ?? null);
const effectiveRoverId = roverId ?? assignedRoverId;
const frame = useTelemetryFrame(effectiveRoverId);
const mapTelemetry = useVisualTelemetrySelector(effectiveRoverId, selectVisualMapTelemetry, mapTelemetryEqual);
const spectatorTelemetry = useVisualTelemetrySelector(effectiveRoverId, selectSpectatorTelemetry, spectatorTelemetryEqual);
const rosterInfo = useSessionSelector((state) => {
if (!effectiveRoverId) return { label: null, roverColor: null };
const roster = state.session?.roster || [];
@@ -41,7 +48,8 @@ function HudOverlay({
const match = users.find((u) => String(u.socketId || '') === String(activeId || ''));
return match?.nickname || match?.name || null;
});
const resolvedSensors = sensors ?? frame?.sensors ?? null;
const resolvedMapTelemetry = sensors ? null : mapTelemetry;
const resolvedSensors = sensors ?? null;
const resolvedLabel = label ?? rosterInfo.label ?? null;
const resolvedRoverColor = roverColor ?? rosterInfo.roverColor ?? null;
const resolvedDriverLabel = driverLabel ?? derivedDriverLabel;
@@ -66,7 +74,7 @@ function HudOverlay({
return (
<>
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<SpectatorTelemetryOverlay sensors={resolvedSensors} mobileHud={isMobile} />
<SpectatorTelemetryOverlay sensors={resolvedSensors} telemetry={sensors ? null : spectatorTelemetry} mobileHud={isMobile} />
<RoverLabelOverlay
variant="spectator"
label={resolvedLabel}
@@ -78,6 +86,7 @@ function HudOverlay({
</div>
<HudMapOverlay
sensors={resolvedSensors}
mapTelemetry={resolvedMapTelemetry}
show={resolvedShowTopDown}
mapPosition={resolvedMapPosition}
layoutFormat={layoutFormat}
@@ -98,6 +107,7 @@ function HudOverlay({
/>
<HudMapOverlay
sensors={resolvedSensors}
mapTelemetry={resolvedMapTelemetry}
show={resolvedShowTopDown && variant !== 'spectator'}
mapPosition={resolvedMapPosition}
layoutFormat={layoutFormat}
@@ -3,21 +3,23 @@
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import React from 'react';
import { useSessionSelector } from '../../../context/SessionContext.jsx';
import { useTelemetryFrame } from '../../../context/TelemetryContext.jsx';
import { useVisualTelemetrySelector } from '../../../context/TelemetryContext.jsx';
import { lightBumpTelemetryEqual, selectLightBumpTelemetry } from '../../../context/telemetryViews.js';
function LightBumpBars({ roverId = null, sensors }) {
const assignedRoverId = useSessionSelector((state) => state.session?.assignment?.roverId ?? null);
const effectiveRoverId = roverId ?? assignedRoverId;
const frame = useTelemetryFrame(effectiveRoverId);
const resolvedSensors = sensors ?? frame?.sensors ?? null;
const values = [
resolvedSensors?.lightBumpLeftSignal,
resolvedSensors?.lightBumpFrontLeftSignal,
resolvedSensors?.lightBumpCenterLeftSignal,
resolvedSensors?.lightBumpCenterRightSignal,
resolvedSensors?.lightBumpFrontRightSignal,
resolvedSensors?.lightBumpRightSignal,
];
const selectedValues = useVisualTelemetrySelector(effectiveRoverId, selectLightBumpTelemetry, lightBumpTelemetryEqual);
const values = sensors
? [
sensors?.lightBumpLeftSignal,
sensors?.lightBumpFrontLeftSignal,
sensors?.lightBumpCenterLeftSignal,
sensors?.lightBumpCenterRightSignal,
sensors?.lightBumpFrontRightSignal,
sensors?.lightBumpRightSignal,
]
: selectedValues;
const max = values.filter((v) => v != null).reduce((acc, v) => Math.max(acc, v), 1200);
const eased = (v) => Math.pow(Math.max(0, Math.min(1, (v ?? 0) / max)), 0.35);
const hueFor = (v) => {
@@ -3,30 +3,36 @@
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
import React from 'react';
import { useSessionSelector } from '../../../context/SessionContext.jsx';
import { useTelemetryFrame } from '../../../context/TelemetryContext.jsx';
import { useVisualTelemetrySelector } from '../../../context/TelemetryContext.jsx';
import { batteryTelemetryEqual, selectBatteryTelemetry } from '../../../context/telemetryViews.js';
import { buildBatteryVisual } from '../../../lib/battery.js';
function LowBatteryOverlay({ roverId = null, sensors, batteryConfig, compact = false }) {
const assignedRoverId = useSessionSelector((state) => state.session?.assignment?.roverId ?? null);
const effectiveRoverId = roverId ?? assignedRoverId;
const frame = useTelemetryFrame(effectiveRoverId);
const batteryTelemetry = useVisualTelemetrySelector(effectiveRoverId, selectBatteryTelemetry, batteryTelemetryEqual);
const rosterBatteryConfig = useSessionSelector((state) => {
if (!effectiveRoverId) return null;
const roster = state.session?.roster || [];
const rover = roster.find((entry) => String(entry.id) === String(effectiveRoverId));
return rover?.battery ?? null;
});
const resolvedSensors = sensors ?? frame?.sensors ?? null;
const resolvedBatteryTelemetry = sensors
? {
batteryChargeMah: sensors?.batteryChargeMah ?? null,
batteryCapacityMah: sensors?.batteryCapacityMah ?? null,
}
: batteryTelemetry;
const resolvedBatteryConfig = batteryConfig ?? rosterBatteryConfig;
const battery = buildBatteryVisual({
charge: resolvedSensors?.batteryChargeMah ?? null,
charge: resolvedBatteryTelemetry?.batteryChargeMah ?? null,
config: resolvedBatteryConfig,
});
if (!battery?.available) return null;
if (!battery.warnActive && !battery.urgentActive) return null;
const message = battery.urgentActive
? 'BATTERY VERY LOW, DOCK THE ROVER AND CHARGE IMMEDIATELY!!'
? 'Battery very low, dock the rover and charge immediately!!'
: 'Battery low! please dock and charge the rover soon.';
const containerClass = compact ? 'p-2 top-6' : 'p-4 top-10';
@@ -4,26 +4,26 @@
import React from 'react';
import { useMemo } from 'react';
import { useSessionSelector } from '../../../context/SessionContext.jsx';
import { useTelemetryFrame } from '../../../context/TelemetryContext.jsx';
import { useVisualTelemetrySelector } from '../../../context/TelemetryContext.jsx';
import { overcurrentFlagsEqual, selectOvercurrentFlags } from '../../../context/telemetryViews.js';
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 selectedOvercurrents = useVisualTelemetrySelector(effectiveRoverId, selectOvercurrentFlags, overcurrentFlagsEqual);
const internalLimiter = useOvercurrentLimiter(effectiveRoverId);
const resolvedSensors = sensors ?? frame?.sensors ?? null;
const resolvedOvercurrents = sensors?.wheelOvercurrents ?? selectedOvercurrents;
const resolvedOvercurrentLimiter = overcurrentLimiter ?? internalLimiter ?? null;
const wheelOvercurrents = resolvedSensors?.wheelOvercurrents || null;
const overcurrentMotors = useMemo(
() =>
wheelOvercurrents == null
resolvedOvercurrents == null
? []
: Object.entries(wheelOvercurrents)
: Object.entries(resolvedOvercurrents)
.filter(([, active]) => Boolean(active))
.map(([key]) => key),
[wheelOvercurrents],
[resolvedOvercurrents],
);
const limiterCaps = resolvedOvercurrentLimiter?.caps || null;
const limiterFill = useMemo(() => {
@@ -56,7 +56,7 @@ function OvercurrentOverlay({ roverId = null, sensors, overcurrentLimiter = null
<div className="h-full bg-red-700/60" style={{ width: fillWidth }} />
</div>
<div className={`relative z-10 flex h-full w-full flex-col items-center justify-center text-center font-semibold text-white animate-pulse ${textClass} ${padClass}`}>
<div>OVERCURRENT</div>
<div>Overcurrent</div>
<div className={`mt-0 font-medium text-white ${subTextClass}`}>{safeLabels.join(', ')}</div>
</div>
</div>
@@ -1,21 +1,27 @@
import React from 'react';
import { useSessionSelector } from '../../../context/SessionContext.jsx';
import { useTelemetryFrame } from '../../../context/TelemetryContext.jsx';
import { useVisualTelemetrySelector } from '../../../context/TelemetryContext.jsx';
import { batteryTelemetryEqual, selectBatteryTelemetry } from '../../../context/telemetryViews.js';
import { buildBatteryVisual } from '../../../lib/battery.js';
import BatteryBar from '../../BatteryBar/index.jsx';
function VerticalBatteryOverlay({ show = false, roverId = null, sensors, batteryConfig, mobileHud = false }) {
const frame = useTelemetryFrame(roverId);
const batteryTelemetry = useVisualTelemetrySelector(roverId, selectBatteryTelemetry, batteryTelemetryEqual);
const rosterBatteryConfig = useSessionSelector((state) => {
if (!roverId) return null;
const roster = state.session?.roster || [];
const rover = roster.find((entry) => String(entry.id) === String(roverId));
return rover?.battery ?? null;
});
const resolvedSensors = sensors ?? frame?.sensors ?? null;
const resolvedBatteryTelemetry = sensors
? {
batteryChargeMah: sensors?.batteryChargeMah ?? null,
batteryCapacityMah: sensors?.batteryCapacityMah ?? null,
}
: batteryTelemetry;
const resolvedBatteryConfig = batteryConfig ?? rosterBatteryConfig;
const batteryVisual = buildBatteryVisual({
charge: resolvedSensors?.batteryChargeMah ?? null,
charge: resolvedBatteryTelemetry?.batteryChargeMah ?? null,
config: resolvedBatteryConfig,
});
if (!show || !batteryVisual?.available) return null;