mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 17:40:46 -04:00
npm run build
This commit is contained in:
@@ -13,7 +13,7 @@ import {
|
||||
} from './constants.js';
|
||||
import { canonicalizeKeyInput } from './keymapUtils.js';
|
||||
import { useSettingsNamespace } from '../settings/index.js';
|
||||
import { useSession } from '../context/SessionContext.jsx';
|
||||
import { useSessionActions, useSessionSelector } from '../context/SessionContext.jsx';
|
||||
import { HORN_SETTINGS_DEFAULTS } from '../settings/namespaces.js';
|
||||
import {
|
||||
applyAuxOvercurrentScale,
|
||||
@@ -71,7 +71,8 @@ export function ControlSystemProvider({ children }) {
|
||||
typeof pageSettings?.driveMacroBackoffEnabled === 'boolean'
|
||||
? pageSettings.driveMacroBackoffEnabled
|
||||
: true;
|
||||
const { session, homeAssistantSetState } = useSession();
|
||||
const session = useSessionSelector((state) => state.session);
|
||||
const { homeAssistantSetState } = useSessionActions();
|
||||
const roverId = session?.assignment?.roverId ?? null;
|
||||
const overcurrentLimiter = useOvercurrentLimiter(roverId);
|
||||
const driveTransform = useCallback(
|
||||
@@ -455,16 +456,38 @@ export function ControlSystemProvider({ children }) {
|
||||
}
|
||||
}, [dispatch, pipeline, state.horn?.heat, state.horn?.overheated, HORN_HEAT_UP_PER_SEC]);
|
||||
|
||||
const hornStateRef = useRef({
|
||||
active: Boolean(state.horn?.active),
|
||||
heat: state.horn?.heat ?? 0,
|
||||
overheated: Boolean(state.horn?.overheated),
|
||||
});
|
||||
const stopHornRef = useRef(stopHorn);
|
||||
|
||||
useEffect(() => {
|
||||
hornStateRef.current = {
|
||||
active: Boolean(state.horn?.active),
|
||||
heat: state.horn?.heat ?? 0,
|
||||
overheated: Boolean(state.horn?.overheated),
|
||||
};
|
||||
}, [state.horn?.active, state.horn?.heat, state.horn?.overheated]);
|
||||
|
||||
useEffect(() => {
|
||||
stopHornRef.current = stopHorn;
|
||||
}, [stopHorn]);
|
||||
|
||||
const hornNeedsTick = Boolean(state.horn?.active) || (state.horn?.heat ?? 0) > 0 || Boolean(state.horn?.overheated);
|
||||
useEffect(() => {
|
||||
if (!hornNeedsTick) return undefined;
|
||||
const tickMs = 100;
|
||||
const interval = setInterval(() => {
|
||||
const now = Date.now();
|
||||
const last = hornHeatTickRef.current || now;
|
||||
hornHeatTickRef.current = now;
|
||||
const dt = Math.min(1000, Math.max(0, now - last)) / 1000;
|
||||
const currentHeat = state.horn?.heat ?? 0;
|
||||
const active = Boolean(state.horn?.active);
|
||||
const overheated = Boolean(state.horn?.overheated);
|
||||
const current = hornStateRef.current;
|
||||
const currentHeat = current.heat ?? 0;
|
||||
const active = Boolean(current.active);
|
||||
const overheated = Boolean(current.overheated);
|
||||
const delta = dt * (active ? HORN_HEAT_UP_PER_SEC : -HORN_HEAT_COOL_PER_SEC);
|
||||
let nextHeat = Math.max(0, Math.min(1, currentHeat + delta));
|
||||
let nextOverheated = overheated;
|
||||
@@ -472,7 +495,7 @@ export function ControlSystemProvider({ children }) {
|
||||
nextHeat = 1;
|
||||
nextOverheated = true;
|
||||
if (active) {
|
||||
stopHorn();
|
||||
stopHornRef.current();
|
||||
}
|
||||
} else if (nextOverheated && nextHeat <= HORN_HEAT_RESUME_THRESHOLD) {
|
||||
nextOverheated = false;
|
||||
@@ -482,7 +505,7 @@ export function ControlSystemProvider({ children }) {
|
||||
}
|
||||
}, tickMs);
|
||||
return () => clearInterval(interval);
|
||||
}, [dispatch, state.horn?.active, state.horn?.heat, state.horn?.overheated, stopHorn]);
|
||||
}, [dispatch, hornNeedsTick, HORN_HEAT_COOL_PER_SEC, HORN_HEAT_RESUME_THRESHOLD, HORN_HEAT_UP_PER_SEC]);
|
||||
|
||||
const registerInputState = useCallback((source, data) => {
|
||||
dispatch({ type: 'control/register-input-state', payload: { source, state: data } });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useSocket } from '../context/SocketContext.jsx';
|
||||
import { useSession } from '../context/SessionContext.jsx';
|
||||
import { useSessionSelector } from '../context/SessionContext.jsx';
|
||||
import {
|
||||
AUX_LIMITS,
|
||||
COMMAND_DELAY_MS,
|
||||
@@ -14,7 +14,7 @@ import { bytesToBase64, clampRange, sleep } from './controlMath.js';
|
||||
export function useCommandPipeline(options = {}) {
|
||||
const { driveTransform, auxTransform } = options;
|
||||
const socket = useSocket();
|
||||
const { session } = useSession();
|
||||
const session = useSessionSelector((state) => state.session);
|
||||
const roverId = session?.assignment?.roverId;
|
||||
|
||||
const rosterEntry = useMemo(() => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { useControlSystem } from '../ControlContext.jsx';
|
||||
import { useChat } from '../../context/ChatContext.jsx';
|
||||
import { useSession } from '../../context/SessionContext.jsx';
|
||||
import { useSessionActions, useSessionSelector } from '../../context/SessionContext.jsx';
|
||||
import { normalizeKeymapEntries, tokensForEvent } from '../keymapUtils.js';
|
||||
import { isKeyboardCaptureLocked } from './keyboardCaptureLock.js';
|
||||
import { isTextInputElement } from './inputFocusUtils.js';
|
||||
@@ -134,7 +134,8 @@ export default function KeyboardInputManager() {
|
||||
sendSong,
|
||||
},
|
||||
} = useControlSystem();
|
||||
const { session, homeAssistantSetState } = useSession();
|
||||
const session = useSessionSelector((state) => state.session);
|
||||
const { homeAssistantSetState } = useSessionActions();
|
||||
const { focusChat, blurChat, isChatFocused } = useChat();
|
||||
const { value: inputSettings } = useSettingsNamespace('inputs', INPUT_SETTINGS_DEFAULTS);
|
||||
const keymap = useMemo(() => normalizeKeymapEntries(state.keymap), [state.keymap]);
|
||||
|
||||
@@ -4,6 +4,12 @@ import { getPadSignature } from './gamepadBindings.js';
|
||||
const listeners = new Set();
|
||||
let rafId = null;
|
||||
let lastState = { pads: [], timestamp: 0 };
|
||||
let hasDeviceListeners = false;
|
||||
let deviceChangeHandler = null;
|
||||
|
||||
function hasConnectedPads() {
|
||||
return readGamepads().some((pad) => pad?.connected !== false);
|
||||
}
|
||||
|
||||
function readGamepads() {
|
||||
if (typeof navigator === 'undefined' || !navigator.getGamepads) {
|
||||
@@ -41,11 +47,17 @@ function updateState() {
|
||||
|
||||
function loop() {
|
||||
updateState();
|
||||
if (lastState.pads.length === 0) {
|
||||
stopLoop();
|
||||
return;
|
||||
}
|
||||
rafId = requestAnimationFrame(loop);
|
||||
}
|
||||
|
||||
function startLoop() {
|
||||
if (rafId) return;
|
||||
updateState();
|
||||
if (lastState.pads.length === 0) return;
|
||||
rafId = requestAnimationFrame(loop);
|
||||
}
|
||||
|
||||
@@ -57,12 +69,37 @@ function stopLoop() {
|
||||
|
||||
export function subscribeGamepadHub(listener) {
|
||||
listeners.add(listener);
|
||||
startLoop();
|
||||
if (!hasDeviceListeners && typeof window !== 'undefined') {
|
||||
const handleDeviceChange = () => {
|
||||
updateState();
|
||||
if (listeners.size === 0) return;
|
||||
if (hasConnectedPads()) {
|
||||
startLoop();
|
||||
} else {
|
||||
stopLoop();
|
||||
}
|
||||
};
|
||||
window.addEventListener('gamepadconnected', handleDeviceChange);
|
||||
window.addEventListener('gamepaddisconnected', handleDeviceChange);
|
||||
deviceChangeHandler = handleDeviceChange;
|
||||
hasDeviceListeners = true;
|
||||
}
|
||||
if (hasConnectedPads()) {
|
||||
startLoop();
|
||||
} else {
|
||||
updateState();
|
||||
}
|
||||
listener(lastState);
|
||||
return () => {
|
||||
listeners.delete(listener);
|
||||
if (listeners.size === 0) {
|
||||
stopLoop();
|
||||
if (hasDeviceListeners && typeof window !== 'undefined') {
|
||||
window.removeEventListener('gamepadconnected', deviceChangeHandler);
|
||||
window.removeEventListener('gamepaddisconnected', deviceChangeHandler);
|
||||
deviceChangeHandler = null;
|
||||
hasDeviceListeners = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTelemetryFrame } from '../context/TelemetryContext.jsx';
|
||||
import { useSession } from '../context/SessionContext.jsx';
|
||||
import { useSessionSelector } from '../context/SessionContext.jsx';
|
||||
|
||||
export const OVERCURRENT_GROUPS = [
|
||||
{ key: 'drive', motors: ['leftWheel', 'rightWheel'] },
|
||||
@@ -27,7 +27,7 @@ function clampUnit(value) {
|
||||
}
|
||||
|
||||
export function useOvercurrentLimiter(roverId, options = {}) {
|
||||
const { session } = useSession();
|
||||
const session = useSessionSelector((state) => state.session);
|
||||
const frame = useTelemetryFrame(roverId);
|
||||
const sensors = frame?.sensors || {};
|
||||
const overcurrentFlags = sensors?.wheelOvercurrents || {};
|
||||
@@ -47,7 +47,18 @@ export function useOvercurrentLimiter(roverId, options = {}) {
|
||||
setCaps(createInitialCaps());
|
||||
}, [roverId]);
|
||||
|
||||
const hasAnyOvercurrent = useMemo(
|
||||
() => OVERCURRENT_GROUPS.some((group) => group.motors.some((motor) => Boolean(overcurrentFlags?.[motor]))),
|
||||
[overcurrentFlags],
|
||||
);
|
||||
const needsRecoveryTick = useMemo(
|
||||
() => Object.values(caps || {}).some((entry) => (Number.isFinite(entry?.cap) ? entry.cap : 1) < 0.999),
|
||||
[caps],
|
||||
);
|
||||
const shouldTick = Boolean(roverId) && (hasAnyOvercurrent || needsRecoveryTick);
|
||||
|
||||
useEffect(() => {
|
||||
if (!shouldTick) return undefined;
|
||||
const interval = setInterval(() => {
|
||||
const now = typeof performance !== 'undefined' ? performance.now() : Date.now();
|
||||
const deltaMs = Math.max(0, now - lastTickRef.current);
|
||||
@@ -79,7 +90,7 @@ export function useOvercurrentLimiter(roverId, options = {}) {
|
||||
});
|
||||
}, 100);
|
||||
return () => clearInterval(interval);
|
||||
}, [config.downRatePerSec, config.releaseDelaySec, config.upRatePerSec]);
|
||||
}, [config.downRatePerSec, config.releaseDelaySec, config.upRatePerSec, roverId, shouldTick]);
|
||||
|
||||
const scales = useMemo(() => {
|
||||
const perGroup = OVERCURRENT_GROUPS.reduce((acc, group) => {
|
||||
|
||||
Reference in New Issue
Block a user