mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 09:31:20 -04:00
slopfixing / issue 004
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user