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