This commit is contained in:
legop3
2025-11-18 03:21:13 -05:00
parent 98b61a7845
commit e7793c8ef2
8 changed files with 84 additions and 36 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
@@ -5,8 +5,8 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>webui</title> <title>webui</title>
<script type="module" crossorigin src="/assets/index-NmzNrOYX.js"></script> <script type="module" crossorigin src="/assets/index-NGdTJyVK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C4V7xiyW.css"> <link rel="stylesheet" crossorigin href="/assets/index-ZYHUqx5G.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+2 -1
View File
@@ -3,6 +3,7 @@ import { useControlSystem } from '../controls/index.js';
import AuthPanel from './AuthPanel.jsx'; import AuthPanel from './AuthPanel.jsx';
import AdminPanel from './AdminPanel.jsx'; import AdminPanel from './AdminPanel.jsx';
import KeymapSettings from './KeymapSettings.jsx'; import KeymapSettings from './KeymapSettings.jsx';
import InputSettings from './InputSettings.jsx';
const manualTabs = [ const manualTabs = [
{ key: 'start', label: 'Start OI' }, { key: 'start', label: 'Start OI' },
@@ -31,10 +32,10 @@ export default function AdvancedSettings() {
if (!roverId) return; if (!roverId) return;
setSensorStream(enable); setSensorStream(enable);
}; };
// why is this broken
return ( return (
<div className="space-y-1"> <div className="space-y-1">
<KeymapSettings /> <KeymapSettings />
<InputSettings />
<section className="rounded-sm bg-[#242a32] p-1 text-sm text-slate-100"> <section className="rounded-sm bg-[#242a32] p-1 text-sm text-slate-100">
<p className="text-xs text-slate-400">Manual OI commands</p> <p className="text-xs text-slate-400">Manual OI commands</p>
<div className="mt-1 flex flex-wrap gap-1"> <div className="mt-1 flex flex-wrap gap-1">
+40 -12
View File
@@ -1,10 +1,11 @@
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
import { useControlSystem } from '../controls/index.js'; import { useControlSystem } from '../controls/index.js';
import { clampUnit } from '../controls/controlMath.js'; import { clampUnit } from '../controls/controlMath.js';
import { useSettingsNamespace } from '../settings/index.js';
import { INPUT_SETTINGS_DEFAULTS } from '../settings/namespaces.js';
import DriveModeToggle from './controls/DriveModeToggle.jsx'; import DriveModeToggle from './controls/DriveModeToggle.jsx';
const SOURCE = 'mobile-joystick'; const SOURCE = 'mobile-joystick';
const JOYSTICK_RADIUS = 80;
const AUX_BUTTONS = [ const AUX_BUTTONS = [
{ id: 'main-forward', label: 'Main +', values: { main: 127 }, hold: true, color: 'bg-emerald-600' }, { id: 'main-forward', label: 'Main +', values: { main: 127 }, hold: true, color: 'bg-emerald-600' },
{ id: 'main-reverse', label: 'Main -', values: { main: -127 }, hold: true, color: 'bg-emerald-800' }, { id: 'main-reverse', label: 'Main -', values: { main: -127 }, hold: true, color: 'bg-emerald-800' },
@@ -16,7 +17,7 @@ const AUX_BUTTONS = [
{ id: 'stop-all', label: 'Stop', values: { main: 0, side: 0, vacuum: 0 }, hold: false, color: 'bg-slate-900' }, { id: 'stop-all', label: 'Stop', values: { main: 0, side: 0, vacuum: 0 }, hold: false, color: 'bg-slate-900' },
]; ];
function FloatingJoystick({ disabled, layout, onMove, onStop }) { function FloatingJoystick({ disabled, layout, radius, onMove, onStop }) {
const containerRef = useRef(null); const containerRef = useRef(null);
const pointerIdRef = useRef(null); const pointerIdRef = useRef(null);
const baseRef = useRef({ x: 0, y: 0 }); const baseRef = useRef({ x: 0, y: 0 });
@@ -57,13 +58,13 @@ function FloatingJoystick({ disabled, layout, onMove, onStop }) {
const currentY = event.clientY - rect.top; const currentY = event.clientY - rect.top;
const dx = currentX - baseRef.current.x; const dx = currentX - baseRef.current.x;
const dy = currentY - baseRef.current.y; const dy = currentY - baseRef.current.y;
const distance = Math.min(Math.hypot(dx, dy), JOYSTICK_RADIUS); const distance = Math.min(Math.hypot(dx, dy), radius);
const angle = Math.atan2(dy, dx); const angle = Math.atan2(dy, dx);
const knobX = Math.cos(angle) * distance; const knobX = Math.cos(angle) * distance;
const knobY = Math.sin(angle) * distance; const knobY = Math.sin(angle) * distance;
const vector = { const vector = {
x: clampUnit(knobX / JOYSTICK_RADIUS), x: clampUnit(knobX / radius),
y: clampUnit(-knobY / JOYSTICK_RADIUS), y: clampUnit(-knobY / radius),
boost: false, boost: false,
}; };
setVisual((prev) => ({ ...prev, knob: { x: knobX, y: knobY } })); setVisual((prev) => ({ ...prev, knob: { x: knobX, y: knobY } }));
@@ -134,6 +135,8 @@ function MobileJoystickPanel({ layout }) {
state: { roverId, camera }, state: { roverId, camera },
actions: { setDriveVector, registerInputState, stopAllMotion, setServoAngle }, actions: { setDriveVector, registerInputState, stopAllMotion, setServoAngle },
} = useControlSystem(); } = useControlSystem();
const { value: inputSettings } = useSettingsNamespace('inputs', INPUT_SETTINGS_DEFAULTS);
const mobileSettings = inputSettings.mobile ?? INPUT_SETTINGS_DEFAULTS.mobile;
const disabled = !roverId; const disabled = !roverId;
const cameraConfig = camera?.config; const cameraConfig = camera?.config;
const cameraEnabled = Boolean(roverId && camera?.enabled && cameraConfig); const cameraEnabled = Boolean(roverId && camera?.enabled && cameraConfig);
@@ -142,9 +145,18 @@ function MobileJoystickPanel({ layout }) {
const cameraValue = const cameraValue =
typeof camera?.angle === 'number' typeof camera?.angle === 'number'
? camera.angle ? camera.angle
: typeof cameraConfig?.homeAngle === 'number' : typeof cameraConfig?.homeAngle === 'number'
? cameraConfig.homeAngle ? cameraConfig.homeAngle
: (cameraMin + cameraMax) / 2; : (cameraMin + cameraMax) / 2;
const joystickRadius = Math.max(40, mobileSettings.joystickRadius ?? INPUT_SETTINGS_DEFAULTS.mobile.joystickRadius);
const smoothing = Math.min(Math.max(mobileSettings.joystickSmoothing ?? 0, 0), 0.85);
const smoothedVectorRef = useRef({ x: 0, y: 0, boost: false });
useEffect(() => {
if (disabled) {
smoothedVectorRef.current = { x: 0, y: 0, boost: false };
}
}, [disabled]);
const handleMove = useCallback( const handleMove = useCallback(
(vector = {}) => { (vector = {}) => {
@@ -154,15 +166,25 @@ function MobileJoystickPanel({ layout }) {
y: clampUnit(vector.y ?? 0), y: clampUnit(vector.y ?? 0),
boost: Boolean(vector.boost), boost: Boolean(vector.boost),
}; };
setDriveVector(next, { source: SOURCE }); const applied =
registerInputState(SOURCE, { vector: next, lastEvent: 'move' }); smoothing > 0
? {
x: smoothedVectorRef.current.x + (next.x - smoothedVectorRef.current.x) * (1 - smoothing),
y: smoothedVectorRef.current.y + (next.y - smoothedVectorRef.current.y) * (1 - smoothing),
boost: next.boost,
}
: next;
smoothedVectorRef.current = applied;
setDriveVector(applied, { source: SOURCE });
registerInputState(SOURCE, { vector: applied, lastEvent: 'move' });
}, },
[disabled, registerInputState, setDriveVector], [disabled, registerInputState, setDriveVector, smoothing],
); );
const handleStop = useCallback(() => { const handleStop = useCallback(() => {
if (disabled) return; if (disabled) return;
const zero = { x: 0, y: 0, boost: false }; const zero = { x: 0, y: 0, boost: false };
smoothedVectorRef.current = zero;
setDriveVector(zero, { source: SOURCE }); setDriveVector(zero, { source: SOURCE });
registerInputState(SOURCE, { vector: zero, lastEvent: 'stop' }); registerInputState(SOURCE, { vector: zero, lastEvent: 'stop' });
}, [disabled, registerInputState, setDriveVector]); }, [disabled, registerInputState, setDriveVector]);
@@ -177,7 +199,13 @@ function MobileJoystickPanel({ layout }) {
return ( return (
<div className="flex flex-col gap-1 text-slate-100"> <div className="flex flex-col gap-1 text-slate-100">
<DriveModeToggle size="compact" /> <DriveModeToggle size="compact" />
<FloatingJoystick disabled={disabled} layout={layout} onMove={handleMove} onStop={handleStop} /> <FloatingJoystick
disabled={disabled}
layout={layout}
radius={joystickRadius}
onMove={handleMove}
onStop={handleStop}
/>
<button <button
type="button" type="button"
onClick={stopAllMotion} onClick={stopAllMotion}
@@ -1,12 +1,13 @@
import { useCallback, useEffect, useRef } from 'react'; import { useCallback, useEffect, useRef } from 'react';
import { useControlSystem } from '../ControlContext.jsx'; import { useControlSystem } from '../ControlContext.jsx';
import { clampUnit } from '../controlMath.js'; import { clampUnit } from '../controlMath.js';
import { useSettingsNamespace } from '../../settings/index.js';
import { INPUT_SETTINGS_DEFAULTS } from '../../settings/namespaces.js';
const SOURCE = 'gamepad'; const SOURCE = 'gamepad';
const DEADZONE = 0.2;
function applyDeadzone(value) { function applyDeadzone(value, amount) {
return Math.abs(value) < DEADZONE ? 0 : value; return Math.abs(value) < amount ? 0 : value;
} }
function computeTriggerSpeed(button, reverseButton, max = 127, reverseScale = 1) { function computeTriggerSpeed(button, reverseButton, max = 127, reverseScale = 1) {
@@ -42,6 +43,12 @@ export default function GamepadInputManager() {
registerInputState, registerInputState,
}, },
} = useControlSystem(); } = useControlSystem();
const { value: inputSettings } = useSettingsNamespace('inputs', INPUT_SETTINGS_DEFAULTS);
const gamepadSettings = inputSettings.gamepad ?? INPUT_SETTINGS_DEFAULTS.gamepad;
const driveDeadzone = Math.min(Math.max(gamepadSettings.driveDeadzone ?? 0.2, 0), 0.8);
const cameraDeadzone = Math.min(Math.max(gamepadSettings.cameraDeadzone ?? 0.25, 0), 0.9);
const servoStep = gamepadSettings.servoStep ?? INPUT_SETTINGS_DEFAULTS.gamepad.servoStep;
const auxReverseScale = gamepadSettings.auxReverseScale ?? INPUT_SETTINGS_DEFAULTS.gamepad.auxReverseScale;
const rafRef = useRef(null); const rafRef = useRef(null);
const lastVectorRef = useRef({ x: 0, y: 0, boost: false }); const lastVectorRef = useRef({ x: 0, y: 0, boost: false });
const lastAuxRef = useRef({ main: 0, side: 0, vacuum: 0 }); const lastAuxRef = useRef({ main: 0, side: 0, vacuum: 0 });
@@ -76,8 +83,8 @@ export default function GamepadInputManager() {
return; return;
} }
const axisLX = clampUnit(applyDeadzone(pad.axes?.[0] ?? 0)); const axisLX = clampUnit(applyDeadzone(pad.axes?.[0] ?? 0, driveDeadzone));
const axisLY = clampUnit(applyDeadzone(-(pad.axes?.[1] ?? 0))); const axisLY = clampUnit(applyDeadzone(-(pad.axes?.[1] ?? 0), driveDeadzone));
const vector = { x: axisLX, y: axisLY, boost: false }; const vector = { x: axisLX, y: axisLY, boost: false };
if (!vectorsEqual(vector, lastVectorRef.current)) { if (!vectorsEqual(vector, lastVectorRef.current)) {
lastVectorRef.current = vector; lastVectorRef.current = vector;
@@ -85,7 +92,7 @@ export default function GamepadInputManager() {
} }
const main = computeTriggerSpeed(pad.buttons?.[7], pad.buttons?.[5]); const main = computeTriggerSpeed(pad.buttons?.[7], pad.buttons?.[5]);
const side = computeTriggerSpeed(pad.buttons?.[6], pad.buttons?.[4], 127, 0.55); const side = computeTriggerSpeed(pad.buttons?.[6], pad.buttons?.[4], 127, auxReverseScale);
const vacuum = pad.buttons?.[1]?.pressed ? 127 : 0; const vacuum = pad.buttons?.[1]?.pressed ? 127 : 0;
let aux = { main, side, vacuum }; let aux = { main, side, vacuum };
if (pad.buttons?.[0]?.pressed) { if (pad.buttons?.[0]?.pressed) {
@@ -96,11 +103,11 @@ export default function GamepadInputManager() {
setAuxMotors(aux); setAuxMotors(aux);
} }
const cameraAxis = clampUnit(applyDeadzone(-(pad.axes?.[3] ?? 0))); const cameraAxis = clampUnit(applyDeadzone(-(pad.axes?.[3] ?? 0), cameraDeadzone));
const now = performance.now(); const now = performance.now();
if (Math.abs(cameraAxis) > 0.25 && now - servoThrottleRef.current > 120) { if (Math.abs(cameraAxis) > 0.25 && now - servoThrottleRef.current > 120) {
servoThrottleRef.current = now; servoThrottleRef.current = now;
nudgeServo(cameraAxis > 0 ? 2 : -2); nudgeServo(cameraAxis > 0 ? servoStep : -servoStep);
} }
if (handleButtonEdge(14, pad.buttons?.[14]?.pressed)) { if (handleButtonEdge(14, pad.buttons?.[14]?.pressed)) {
@@ -124,7 +131,19 @@ export default function GamepadInputManager() {
rb: pad.buttons?.[5]?.pressed || false, rb: pad.buttons?.[5]?.pressed || false,
}, },
}); });
}, [handleButtonEdge, nudgeServo, registerInputState, runMacro, setAuxMotors, setDriveVector, setMode]); }, [
auxReverseScale,
cameraDeadzone,
driveDeadzone,
handleButtonEdge,
nudgeServo,
registerInputState,
runMacro,
servoStep,
setAuxMotors,
setDriveVector,
setMode,
]);
useEffect(() => { useEffect(() => {
function loop() { function loop() {