mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
precision camera stuff
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
@@ -78,7 +78,7 @@
|
|||||||
<script defer src="https://analytics.otter.land/script.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land"></script>
|
<script defer src="https://analytics.otter.land/script.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land"></script>
|
||||||
<script defer src="https://analytics.otter.land/recorder.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land" data-sample-rate="0.15" data-mask-level="moderate" data-max-duration="300000"></script>
|
<script defer src="https://analytics.otter.land/recorder.js" data-website-id="82dd56a5-db44-4279-bd1e-a4d9fee39af7" data-domains="rover.otter.land" data-sample-rate="0.15" data-mask-level="moderate" data-max-duration="300000"></script>
|
||||||
<title>Roomba Rover</title>
|
<title>Roomba Rover</title>
|
||||||
<script type="module" crossorigin src="/assets/index-B5yBezZ5.js"></script>
|
<script type="module" crossorigin src="/assets/index-yGP4eLx4.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-JqEX_oga.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-JqEX_oga.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
+2
-2
@@ -196,9 +196,9 @@ function MobileFeatureTabs({
|
|||||||
{/* activities tab */}
|
{/* activities tab */}
|
||||||
<TabPanel id="activities">
|
<TabPanel id="activities">
|
||||||
<div className={`flex flex-col ${themeGapClass}`}>
|
<div className={`flex flex-col ${themeGapClass}`}>
|
||||||
<BarcodeGamesPanel />
|
|
||||||
<LiftCard />
|
|
||||||
<NeatoCard />
|
<NeatoCard />
|
||||||
|
<LiftCard />
|
||||||
|
<BarcodeGamesPanel />
|
||||||
<OdometerPanel />
|
<OdometerPanel />
|
||||||
<ButtonBoxPanel />
|
<ButtonBoxPanel />
|
||||||
<KinectPanel />
|
<KinectPanel />
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import VerticalCameraTilt from './VerticalCameraTilt.jsx';
|
|||||||
import { trackAnalyticsEvent } from '../../analytics/index.js';
|
import { trackAnalyticsEvent } from '../../analytics/index.js';
|
||||||
import { useSessionSelector } from '../../context/SessionContext.jsx';
|
import { useSessionSelector } from '../../context/SessionContext.jsx';
|
||||||
|
|
||||||
|
const CAMERA_TILT_STEP_DEGREES = 0.5;
|
||||||
|
const CAMERA_TILT_PRECISION_STEP_DEGREES = 0.1;
|
||||||
|
|
||||||
function AuxColumnContent() {
|
function AuxColumnContent() {
|
||||||
const roverId = useControlSelector((control) => control.state.roverId);
|
const roverId = useControlSelector((control) => control.state.roverId);
|
||||||
const roomLightsLockedOn = useSessionSelector((state) => Boolean(state.session?.homeAssistant?.lightPolicy?.lockedOn));
|
const roomLightsLockedOn = useSessionSelector((state) => Boolean(state.session?.homeAssistant?.lightPolicy?.lockedOn));
|
||||||
@@ -41,6 +44,14 @@ function AuxColumnContent() {
|
|||||||
? cameraConfig.homeAngle
|
? cameraConfig.homeAngle
|
||||||
: (cameraMin + cameraMax) / 2;
|
: (cameraMin + cameraMax) / 2;
|
||||||
const cameraDisabled = Boolean(disabled || dockAssist.cameraLocked);
|
const cameraDisabled = Boolean(disabled || dockAssist.cameraLocked);
|
||||||
|
/*
|
||||||
|
The mobile tilt track shares the same precision flag as desktop tilt. This
|
||||||
|
keeps the servo fine-step behavior tied to the selected movement mode rather
|
||||||
|
than inventing a separate mobile-only camera setting.
|
||||||
|
*/
|
||||||
|
const cameraTiltStep = camera?.precisionMode
|
||||||
|
? CAMERA_TILT_PRECISION_STEP_DEGREES
|
||||||
|
: CAMERA_TILT_STEP_DEGREES;
|
||||||
|
|
||||||
const handleHeadlightToggle = useCallback(
|
const handleHeadlightToggle = useCallback(
|
||||||
(nextOn) => {
|
(nextOn) => {
|
||||||
@@ -101,7 +112,7 @@ function AuxColumnContent() {
|
|||||||
value={cameraValue}
|
value={cameraValue}
|
||||||
min={cameraMin}
|
min={cameraMin}
|
||||||
max={cameraMax}
|
max={cameraMax}
|
||||||
step={0.5}
|
step={cameraTiltStep}
|
||||||
disabled={cameraDisabled}
|
disabled={cameraDisabled}
|
||||||
onChange={setServoAngle}
|
onChange={setServoAngle}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ function getSpeedModeConfig(speedMode) {
|
|||||||
|
|
||||||
export default function ControlPadPanel({ disabled = false }) {
|
export default function ControlPadPanel({ disabled = false }) {
|
||||||
const rawKeymap = useControlSelector((control) => control.state.keymap);
|
const rawKeymap = useControlSelector((control) => control.state.keymap);
|
||||||
const { setDriveVector, registerInputState } = useControlActions();
|
const { setCameraPrecisionMode, setDriveVector, registerInputState } = useControlActions();
|
||||||
const { value: inputSettings } = useSettingsNamespace('inputs', INPUT_SETTINGS_DEFAULTS);
|
const { value: inputSettings } = useSettingsNamespace('inputs', INPUT_SETTINGS_DEFAULTS);
|
||||||
const [speedMode, setSpeedMode] = useState('normal');
|
const [speedMode, setSpeedMode] = useState('normal');
|
||||||
const [activeInputLabel, setActiveInputLabel] = useState('stop');
|
const [activeInputLabel, setActiveInputLabel] = useState('stop');
|
||||||
@@ -71,10 +71,18 @@ export default function ControlPadPanel({ disabled = false }) {
|
|||||||
const tokens = buildVirtualKeyTokens(cell, modeId);
|
const tokens = buildVirtualKeyTokens(cell, modeId);
|
||||||
const vector = computeKeyboardDriveVector(tokens, keymap);
|
const vector = computeKeyboardDriveVector(tokens, keymap);
|
||||||
const speedOptions = getKeyboardDriveSpeedOptions(tokens, keymap, keyboardSpeeds);
|
const speedOptions = getKeyboardDriveSpeedOptions(tokens, keymap, keyboardSpeeds);
|
||||||
|
const precisionActive = modeId === 'precision';
|
||||||
|
|
||||||
// Mobile deliberately routes through the keyboard vector/speed helpers. The
|
// Mobile deliberately routes through the keyboard vector/speed helpers. The
|
||||||
// thumb pad only chooses which virtual keys are down, so changes to keyboard
|
// thumb pad only chooses which virtual keys are down, so changes to keyboard
|
||||||
// drive behavior automatically stay matched here.
|
// drive behavior automatically stay matched here.
|
||||||
|
/*
|
||||||
|
The selected mobile speed mode is persistent, unlike the keyboard's held
|
||||||
|
Shift modifier. Publishing camera precision here makes the servo tilt
|
||||||
|
controls follow the mobile movement mode even before the driver starts
|
||||||
|
dragging on the pad.
|
||||||
|
*/
|
||||||
|
setCameraPrecisionMode(precisionActive);
|
||||||
setDriveVector(vector, { source: SOURCE, speedOptions });
|
setDriveVector(vector, { source: SOURCE, speedOptions });
|
||||||
registerInputState(SOURCE, {
|
registerInputState(SOURCE, {
|
||||||
keys: Array.from(tokens),
|
keys: Array.from(tokens),
|
||||||
@@ -90,6 +98,7 @@ export default function ControlPadPanel({ disabled = false }) {
|
|||||||
keyboardSpeeds,
|
keyboardSpeeds,
|
||||||
keymap,
|
keymap,
|
||||||
registerInputState,
|
registerInputState,
|
||||||
|
setCameraPrecisionMode,
|
||||||
setDriveVector,
|
setDriveVector,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -99,7 +108,7 @@ export default function ControlPadPanel({ disabled = false }) {
|
|||||||
clearRepeatTimer();
|
clearRepeatTimer();
|
||||||
activeCellRef.current = null;
|
activeCellRef.current = null;
|
||||||
setActiveInputLabel('stop');
|
setActiveInputLabel('stop');
|
||||||
sendDriveCell({ id: 'stop', actions: [] }, lastEvent, 'normal');
|
sendDriveCell({ id: 'stop', actions: [] }, lastEvent, speedModeRef.current);
|
||||||
},
|
},
|
||||||
[clearRepeatTimer, sendDriveCell],
|
[clearRepeatTimer, sendDriveCell],
|
||||||
);
|
);
|
||||||
@@ -127,21 +136,31 @@ export default function ControlPadPanel({ disabled = false }) {
|
|||||||
(nextMode) => {
|
(nextMode) => {
|
||||||
setSpeedMode(nextMode);
|
setSpeedMode(nextMode);
|
||||||
speedModeRef.current = nextMode;
|
speedModeRef.current = nextMode;
|
||||||
|
setCameraPrecisionMode(nextMode === 'precision');
|
||||||
if (activeCellRef.current) {
|
if (activeCellRef.current) {
|
||||||
sendDriveCell(activeCellRef.current, 'speed', nextMode);
|
sendDriveCell(activeCellRef.current, 'speed', nextMode);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[sendDriveCell],
|
[sendDriveCell, setCameraPrecisionMode],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => clearRepeatTimer();
|
return () => {
|
||||||
}, [clearRepeatTimer]);
|
clearRepeatTimer();
|
||||||
|
/*
|
||||||
|
Mobile controls can unmount when layouts change or the driver leaves the
|
||||||
|
control surface. Clear the shared flag so a stale mobile precision choice
|
||||||
|
cannot leave desktop/keyboard camera tilt in fine-step mode.
|
||||||
|
*/
|
||||||
|
setCameraPrecisionMode(false);
|
||||||
|
};
|
||||||
|
}, [clearRepeatTimer, setCameraPrecisionMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!disabled) return;
|
if (!disabled) return;
|
||||||
stopDrivePad('disabled');
|
stopDrivePad('disabled');
|
||||||
}, [disabled, stopDrivePad]);
|
setCameraPrecisionMode(false);
|
||||||
|
}, [disabled, setCameraPrecisionMode, stopDrivePad]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mobile-touch-control flex flex-1 min-h-0 flex-col overflow-hidden rounded-xl border-2 border-slate-700 bg-slate-900 text-slate-100 shadow-md">
|
<div className="mobile-touch-control flex flex-1 min-h-0 flex-col overflow-hidden rounded-xl border-2 border-slate-700 bg-slate-900 text-slate-100 shadow-md">
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ const CHAT_DOCK_INITIAL_HEIGHT = 224;
|
|||||||
const CHAT_DOCK_MIN_HEIGHT = 144;
|
const CHAT_DOCK_MIN_HEIGHT = 144;
|
||||||
const CHAT_DOCK_MAX_HEIGHT = 300;
|
const CHAT_DOCK_MAX_HEIGHT = 300;
|
||||||
const CHAT_DOCK_BOTTOM_INSET = 8;
|
const CHAT_DOCK_BOTTOM_INSET = 8;
|
||||||
|
const CAMERA_TILT_STEP_DEGREES = 0.5;
|
||||||
|
const CAMERA_TILT_PRECISION_STEP_DEGREES = 0.1;
|
||||||
|
|
||||||
function TopDownMapPanel() {
|
function TopDownMapPanel() {
|
||||||
const roverId = useControlSelector((control) => control.state.roverId);
|
const roverId = useControlSelector((control) => control.state.roverId);
|
||||||
@@ -90,6 +92,14 @@ function DriveDockPanel() {
|
|||||||
const upLabel = formatKeyLabel(keymap?.cameraUp?.[0]);
|
const upLabel = formatKeyLabel(keymap?.cameraUp?.[0]);
|
||||||
const downLabel = formatKeyLabel(keymap?.cameraDown?.[0]);
|
const downLabel = formatKeyLabel(keymap?.cameraDown?.[0]);
|
||||||
const cameraDisabled = Boolean(!roverId || dockAssist.cameraLocked);
|
const cameraDisabled = Boolean(!roverId || dockAssist.cameraLocked);
|
||||||
|
/*
|
||||||
|
Precision movement mode also tightens the servo slider step. The command
|
||||||
|
path still sends ordinary angle targets; only the UI increment changes while
|
||||||
|
precision mode is active.
|
||||||
|
*/
|
||||||
|
const cameraTiltStep = camera?.precisionMode
|
||||||
|
? CAMERA_TILT_PRECISION_STEP_DEGREES
|
||||||
|
: CAMERA_TILT_STEP_DEGREES;
|
||||||
const trackedControls = useMemo(
|
const trackedControls = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
setHeadlight: (nextOn) => {
|
setHeadlight: (nextOn) => {
|
||||||
@@ -156,6 +166,7 @@ function DriveDockPanel() {
|
|||||||
value={value}
|
value={value}
|
||||||
min={min}
|
min={min}
|
||||||
max={max}
|
max={max}
|
||||||
|
step={cameraTiltStep}
|
||||||
label="Camera tilt"
|
label="Camera tilt"
|
||||||
disabled={cameraDisabled}
|
disabled={cameraDisabled}
|
||||||
onChange={setServoAngle}
|
onChange={setServoAngle}
|
||||||
@@ -388,9 +399,9 @@ export default function RightPaneTabs({ layout, onOpenHelpOverlay }) {
|
|||||||
{/* activities tab */}
|
{/* activities tab */}
|
||||||
<TabPanel id="activities">
|
<TabPanel id="activities">
|
||||||
<div className={`flex flex-col ${themeGapClass}`}>
|
<div className={`flex flex-col ${themeGapClass}`}>
|
||||||
<BarcodeGamesPanel />
|
|
||||||
<LiftCard />
|
|
||||||
<NeatoCard />
|
<NeatoCard />
|
||||||
|
<LiftCard />
|
||||||
|
<BarcodeGamesPanel />
|
||||||
<OdometerPanel />
|
<OdometerPanel />
|
||||||
<ButtonBoxPanel />
|
<ButtonBoxPanel />
|
||||||
<KinectPanel />
|
<KinectPanel />
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ const CONTROL_ACTION_NAMES = [
|
|||||||
'setServoAngle',
|
'setServoAngle',
|
||||||
'nudgeServo',
|
'nudgeServo',
|
||||||
'goServoHome',
|
'goServoHome',
|
||||||
|
'setCameraPrecisionMode',
|
||||||
'runMacro',
|
'runMacro',
|
||||||
'stopAllMotion',
|
'stopAllMotion',
|
||||||
'sendOiCommand',
|
'sendOiCommand',
|
||||||
@@ -411,6 +412,15 @@ export function ControlSystemProvider({ children }) {
|
|||||||
setServoAngle(target);
|
setServoAngle(target);
|
||||||
}, [pipeline.servoConfig, setServoAngle]);
|
}, [pipeline.servoConfig, setServoAngle]);
|
||||||
|
|
||||||
|
const setCameraPrecisionMode = useCallback((active) => {
|
||||||
|
/*
|
||||||
|
This action only changes how the browser chooses servo increments. The Pi
|
||||||
|
already accepts decimal angle targets, so no server or rover command shape
|
||||||
|
changes are needed for precision camera mode.
|
||||||
|
*/
|
||||||
|
dispatch({ type: 'control/set-camera-precision-mode', payload: Boolean(active) });
|
||||||
|
}, []);
|
||||||
|
|
||||||
const runMacro = useCallback(
|
const runMacro = useCallback(
|
||||||
async (macroId) => {
|
async (macroId) => {
|
||||||
const macro = state.macros.find((item) => item.id === macroId) || null;
|
const macro = state.macros.find((item) => item.id === macroId) || null;
|
||||||
@@ -675,6 +685,7 @@ export function ControlSystemProvider({ children }) {
|
|||||||
setServoAngle,
|
setServoAngle,
|
||||||
nudgeServo,
|
nudgeServo,
|
||||||
goServoHome,
|
goServoHome,
|
||||||
|
setCameraPrecisionMode,
|
||||||
runMacro,
|
runMacro,
|
||||||
stopAllMotion,
|
stopAllMotion,
|
||||||
sendOiCommand,
|
sendOiCommand,
|
||||||
@@ -701,6 +712,7 @@ export function ControlSystemProvider({ children }) {
|
|||||||
setServoAngle,
|
setServoAngle,
|
||||||
nudgeServo,
|
nudgeServo,
|
||||||
goServoHome,
|
goServoHome,
|
||||||
|
setCameraPrecisionMode,
|
||||||
runMacro,
|
runMacro,
|
||||||
stopAllMotion,
|
stopAllMotion,
|
||||||
sendOiCommand,
|
sendOiCommand,
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ function createCameraState() {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
angle: null,
|
angle: null,
|
||||||
config: null,
|
config: null,
|
||||||
|
/*
|
||||||
|
Servo precision is UI/control state, not rover hardware state. Keeping it
|
||||||
|
beside the camera angle lets every camera-tilt surface use the same
|
||||||
|
precision setting while still sending the normal decimal angle commands.
|
||||||
|
*/
|
||||||
|
precisionMode: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +116,7 @@ export function controlReducer(state, action) {
|
|||||||
...state.camera,
|
...state.camera,
|
||||||
enabled: Boolean(action.payload?.config),
|
enabled: Boolean(action.payload?.config),
|
||||||
config: action.payload?.config ?? null,
|
config: action.payload?.config ?? null,
|
||||||
|
precisionMode: action.payload?.config ? state.camera.precisionMode : false,
|
||||||
angle:
|
angle:
|
||||||
typeof action.payload?.angle === 'number'
|
typeof action.payload?.angle === 'number'
|
||||||
? action.payload.angle
|
? action.payload.angle
|
||||||
@@ -123,6 +130,22 @@ export function controlReducer(state, action) {
|
|||||||
...state,
|
...state,
|
||||||
camera: { ...state.camera, angle: action.payload },
|
camera: { ...state.camera, angle: action.payload },
|
||||||
};
|
};
|
||||||
|
case 'control/set-camera-precision-mode':
|
||||||
|
if (state.camera.precisionMode === Boolean(action.payload)) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
camera: {
|
||||||
|
...state.camera,
|
||||||
|
/*
|
||||||
|
Movement inputs own this flag because precision camera mode is meant
|
||||||
|
to follow movement precision mode automatically instead of becoming a
|
||||||
|
separate toggle the driver has to remember to reset.
|
||||||
|
*/
|
||||||
|
precisionMode: Boolean(action.payload),
|
||||||
|
},
|
||||||
|
};
|
||||||
case 'control/register-input-state': {
|
case 'control/register-input-state': {
|
||||||
const sourceKey = action.payload?.source || 'unknown';
|
const sourceKey = action.payload?.source || 'unknown';
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
computeKeyboardAuxMotors,
|
computeKeyboardAuxMotors,
|
||||||
computeKeyboardDriveVector,
|
computeKeyboardDriveVector,
|
||||||
getKeyboardDriveSpeedOptions,
|
getKeyboardDriveSpeedOptions,
|
||||||
|
isPrecisionDriveActive,
|
||||||
resolveKeyboardSpeeds,
|
resolveKeyboardSpeeds,
|
||||||
} from './driveIntent.js';
|
} from './driveIntent.js';
|
||||||
import { trackAnalyticsEvent } from '../../analytics/index.js';
|
import { trackAnalyticsEvent } from '../../analytics/index.js';
|
||||||
@@ -34,6 +35,7 @@ const TILT_INTERVAL_MIN = 5;
|
|||||||
const TILT_INTERVAL_MAX = 500;
|
const TILT_INTERVAL_MAX = 500;
|
||||||
const TILT_SPEED_MIN = 1;
|
const TILT_SPEED_MIN = 1;
|
||||||
const TILT_SPEED_MAX = 100;
|
const TILT_SPEED_MAX = 100;
|
||||||
|
const PRECISION_SERVO_NUDGE_DEGREES = 0.25;
|
||||||
const VIDEO_FILTER_SEQUENCE = ['none', 'grayscale', 'greenscale'];
|
const VIDEO_FILTER_SEQUENCE = ['none', 'grayscale', 'greenscale'];
|
||||||
|
|
||||||
function normalizeVideoFilter(value) {
|
function normalizeVideoFilter(value) {
|
||||||
@@ -91,6 +93,7 @@ export default function KeyboardInputManager() {
|
|||||||
runMacro,
|
runMacro,
|
||||||
stopAllMotion,
|
stopAllMotion,
|
||||||
registerInputState,
|
registerInputState,
|
||||||
|
setCameraPrecisionMode,
|
||||||
toggleHeadlight,
|
toggleHeadlight,
|
||||||
toggleLaser,
|
toggleLaser,
|
||||||
startHorn,
|
startHorn,
|
||||||
@@ -157,9 +160,16 @@ export default function KeyboardInputManager() {
|
|||||||
const latest = latestRef.current;
|
const latest = latestRef.current;
|
||||||
if (!latest) return;
|
if (!latest) return;
|
||||||
const tokensSnapshot = new Set(activeTokensRef.current);
|
const tokensSnapshot = new Set(activeTokensRef.current);
|
||||||
|
const precisionActive = isPrecisionDriveActive(tokensSnapshot, latest.keymap);
|
||||||
const speedOptions = getKeyboardDriveSpeedOptions(tokensSnapshot, latest.keymap, latest.keyboardSpeeds);
|
const speedOptions = getKeyboardDriveSpeedOptions(tokensSnapshot, latest.keymap, latest.keyboardSpeeds);
|
||||||
const vector = computeKeyboardDriveVector(tokensSnapshot, latest.keymap);
|
const vector = computeKeyboardDriveVector(tokensSnapshot, latest.keymap);
|
||||||
const aux = computeKeyboardAuxMotors(tokensSnapshot, latest.keymap);
|
const aux = computeKeyboardAuxMotors(tokensSnapshot, latest.keymap);
|
||||||
|
/*
|
||||||
|
Keyboard precision is a held modifier, so publish the camera precision
|
||||||
|
state from the same token snapshot that drives movement. This keeps the
|
||||||
|
servo UI in lockstep with the actual precision-driving intent.
|
||||||
|
*/
|
||||||
|
latest.setCameraPrecisionMode(precisionActive);
|
||||||
if (
|
if (
|
||||||
vector.x !== lastVectorRef.current.x ||
|
vector.x !== lastVectorRef.current.x ||
|
||||||
vector.y !== lastVectorRef.current.y ||
|
vector.y !== lastVectorRef.current.y ||
|
||||||
@@ -219,7 +229,10 @@ export default function KeyboardInputManager() {
|
|||||||
stopServoLoop();
|
stopServoLoop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
latest.nudgeServo(nextDirection * latest.servoStep);
|
const tokensSnapshot = new Set(activeTokensRef.current);
|
||||||
|
const precisionActive = isPrecisionDriveActive(tokensSnapshot, latest.keymap);
|
||||||
|
const servoStep = precisionActive ? PRECISION_SERVO_NUDGE_DEGREES : latest.servoStep;
|
||||||
|
latest.nudgeServo(nextDirection * servoStep);
|
||||||
servoIntervalRef.current = setTimeout(tick, latest.servoRepeatMs);
|
servoIntervalRef.current = setTimeout(tick, latest.servoRepeatMs);
|
||||||
};
|
};
|
||||||
servoIntervalRef.current = setTimeout(tick, 0);
|
servoIntervalRef.current = setTimeout(tick, 0);
|
||||||
@@ -288,6 +301,7 @@ export default function KeyboardInputManager() {
|
|||||||
latest?.stopHorn();
|
latest?.stopHorn();
|
||||||
}
|
}
|
||||||
latest?.setMicPttActive(false);
|
latest?.setMicPttActive(false);
|
||||||
|
latest?.setCameraPrecisionMode(false);
|
||||||
latest?.stopAllMotion();
|
latest?.stopAllMotion();
|
||||||
latest?.registerInputState(SOURCE, { keys: [], vector: ZERO_VECTOR, aux: ZERO_AUX });
|
latest?.registerInputState(SOURCE, { keys: [], vector: ZERO_VECTOR, aux: ZERO_AUX });
|
||||||
}, [stopServoLoop, stopSongLoop]);
|
}, [stopServoLoop, stopSongLoop]);
|
||||||
@@ -369,6 +383,7 @@ export default function KeyboardInputManager() {
|
|||||||
servoRepeatMs,
|
servoRepeatMs,
|
||||||
servoStep,
|
servoStep,
|
||||||
setAuxMotors,
|
setAuxMotors,
|
||||||
|
setCameraPrecisionMode,
|
||||||
setDriveVector,
|
setDriveVector,
|
||||||
setMicPttActive,
|
setMicPttActive,
|
||||||
setMode,
|
setMode,
|
||||||
|
|||||||
@@ -54,6 +54,16 @@ export function computeKeyboardDriveVector(keys, keymap) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isPrecisionDriveActive(keys, keymap) {
|
||||||
|
/*
|
||||||
|
The slow modifier is the canonical precision-drive signal shared by physical
|
||||||
|
keyboard input and the mobile pad's virtual-key path. Centralizing the check
|
||||||
|
keeps camera precision coupled to the same driver intent instead of copying
|
||||||
|
modifier-specific knowledge into each caller.
|
||||||
|
*/
|
||||||
|
return bindingActive(keymap?.slowModifier, keys);
|
||||||
|
}
|
||||||
|
|
||||||
export function computeKeyboardAuxMotors(keys, keymap) {
|
export function computeKeyboardAuxMotors(keys, keymap) {
|
||||||
const allForward = bindingActive(keymap.auxAllForward, keys);
|
const allForward = bindingActive(keymap.auxAllForward, keys);
|
||||||
if (allForward) {
|
if (allForward) {
|
||||||
@@ -78,7 +88,7 @@ export function computeKeyboardAuxMotors(keys, keymap) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getKeyboardDriveSpeedOptions(keys, keymap, keyboardSpeeds) {
|
export function getKeyboardDriveSpeedOptions(keys, keymap, keyboardSpeeds) {
|
||||||
const slowActive = bindingActive(keymap.slowModifier, keys);
|
const slowActive = isPrecisionDriveActive(keys, keymap);
|
||||||
return slowActive
|
return slowActive
|
||||||
? { baseSpeed: keyboardSpeeds.precisionSpeed, boostSpeed: keyboardSpeeds.precisionSpeed }
|
? { baseSpeed: keyboardSpeeds.precisionSpeed, boostSpeed: keyboardSpeeds.precisionSpeed }
|
||||||
: { baseSpeed: keyboardSpeeds.baseSpeed, boostSpeed: keyboardSpeeds.turboSpeed };
|
: { baseSpeed: keyboardSpeeds.baseSpeed, boostSpeed: keyboardSpeeds.turboSpeed };
|
||||||
|
|||||||
Reference in New Issue
Block a user