ocl in hud

This commit is contained in:
legop3
2026-01-21 12:53:07 -05:00
parent a99e6e5286
commit 26d7335c9a
7 changed files with 166 additions and 128 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -11,8 +11,8 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
<title>Multi Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-CCvZD-uD.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BBEWYgmH.css">
<script type="module" crossorigin src="/assets/index-DAIbNlvO.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-HhAzSaxc.css">
</head>
<body>
<div id="root"></div>
@@ -10,6 +10,7 @@ export default function DriverVideoPanel({layoutFormat = 'desktop'}) {
const { session } = useSession();
const {
state: { song, lastControlIntentAt },
overcurrentLimiter,
} = useControlSystem();
const [now, setNow] = useState(() => Date.now());
const [turnCueVisible, setTurnCueVisible] = useState(false);
@@ -122,6 +123,7 @@ export default function DriverVideoPanel({layoutFormat = 'desktop'}) {
telemetryFrame={frame}
batteryConfig={batteryConfig}
layoutFormat={layoutFormat}
overcurrentLimiter={overcurrentLimiter}
songNote={song?.note}
qualityNotice={!shouldShowVideo ? 'Preview feed (low FPS) until your turn.' : null}
showTurnCue={turnCueVisible}
+42 -6
View File
@@ -37,6 +37,11 @@ function buildBatteryVisual(charge, config) {
};
}
function clampUnit(value) {
if (!Number.isFinite(value)) return 0;
return Math.max(0, Math.min(1, value));
}
export default function VideoTile({
sessionInfo,
audioSessionInfo,
@@ -54,6 +59,7 @@ export default function VideoTile({
hudMapPosition = 'top-right',
hudLabelScale = 1,
fitParent = false,
overcurrentLimiter = null,
showTurnCue = false,
turnTimerText = null,
turnSeconds = null,
@@ -94,6 +100,27 @@ export default function VideoTile({
: Object.entries(wheelOvercurrents)
.filter(([, active]) => Boolean(active))
.map(([key]) => key);
const limiterCaps = overcurrentLimiter?.caps || null;
const limiterGroups = overcurrentLimiter?.overcurrent?.groups || 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 overcurrentLabels = useMemo(() => {
if (!overcurrentLimiter) {
return overcurrentMotors.map((name) => OVERCURRENT_LABELS[name] || name);
}
const labels = [];
const driveCap = Number.isFinite(limiterCaps?.drive?.cap) ? limiterCaps.drive.cap : 1;
const auxCap = Number.isFinite(limiterCaps?.aux?.cap) ? limiterCaps.aux.cap : 1;
const driveActive = Boolean(limiterGroups?.drive) || driveCap < 0.999;
const auxActive = Boolean(limiterGroups?.aux) || auxCap < 0.999;
if (driveActive) labels.push('Drive wheels');
if (auxActive) labels.push('Aux motors');
return labels;
}, [limiterCaps, limiterGroups, overcurrentLimiter, overcurrentMotors]);
const scheduleRestart = useCallback(() => {
clearTimeout(restartTimer.current);
@@ -420,7 +447,11 @@ export default function VideoTile({
labelScale={hudLabelScale}
/>
<HudChatInput compact={mobileHud} />
<OvercurrentOverlay motors={overcurrentMotors} compact={mobileHud} />
<OvercurrentOverlay
labels={overcurrentLabels}
fill={overcurrentLimiter ? limiterFill ?? 0 : overcurrentMotors.length ? 1 : 0}
compact={mobileHud}
/>
<LowBatteryOverlay charge={batteryCharge} config={batteryConfig} compact={mobileHud} />
{showVerticalBattery && batteryVisual.available ? (
<BatteryBarVertical visual={batteryVisual} />
@@ -720,17 +751,22 @@ const OVERCURRENT_LABELS = {
sideBrush: 'Side brush',
};
function OvercurrentOverlay({ motors, compact = false }) {
if (!motors?.length) return null;
const labels = motors.map((name) => OVERCURRENT_LABELS[name] || name);
function OvercurrentOverlay({ labels, fill = 0, compact = false }) {
if (!labels?.length) return null;
const containerClass = compact ? 'p-2' : 'p-4';
const textClass = compact ? 'text-lg' : 'text-4xl';
const subTextClass = compact ? 'text-xs' : 'text-xl';
const fillWidth = `${Math.round(clampUnit(fill) * 100)}%`;
const opacity = clampUnit(fill * 1.2);
return (
<div
className={`pointer-events-none absolute flex items-center justify-center bg-red-900/60 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 ${containerClass}`}
className={`pointer-events-none absolute flex items-center justify-center bg-red-900/60 top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 ${containerClass} relative`}
style={{ opacity }}
>
<div className={`text-center font-semibold text-white animate-pulse ${textClass}`}>
<div className="absolute inset-0 overflow-hidden">
<div className="h-full bg-red-700/60" style={{ width: fillWidth }} />
</div>
<div className={`relative z-10 text-center font-semibold text-white animate-pulse ${textClass}`}>
<div>OVERCURRENT</div>
<div className={`mt-0 font-medium text-white ${subTextClass}`}>{labels.join(', ')}</div>
</div>