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:
+2
-2
@@ -196,9 +196,9 @@ function MobileFeatureTabs({
|
||||
{/* activities tab */}
|
||||
<TabPanel id="activities">
|
||||
<div className={`flex flex-col ${themeGapClass}`}>
|
||||
<BarcodeGamesPanel />
|
||||
<LiftCard />
|
||||
<NeatoCard />
|
||||
<LiftCard />
|
||||
<BarcodeGamesPanel />
|
||||
<OdometerPanel />
|
||||
<ButtonBoxPanel />
|
||||
<KinectPanel />
|
||||
|
||||
@@ -12,6 +12,9 @@ import VerticalCameraTilt from './VerticalCameraTilt.jsx';
|
||||
import { trackAnalyticsEvent } from '../../analytics/index.js';
|
||||
import { useSessionSelector } from '../../context/SessionContext.jsx';
|
||||
|
||||
const CAMERA_TILT_STEP_DEGREES = 0.5;
|
||||
const CAMERA_TILT_PRECISION_STEP_DEGREES = 0.1;
|
||||
|
||||
function AuxColumnContent() {
|
||||
const roverId = useControlSelector((control) => control.state.roverId);
|
||||
const roomLightsLockedOn = useSessionSelector((state) => Boolean(state.session?.homeAssistant?.lightPolicy?.lockedOn));
|
||||
@@ -41,6 +44,14 @@ function AuxColumnContent() {
|
||||
? cameraConfig.homeAngle
|
||||
: (cameraMin + cameraMax) / 2;
|
||||
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(
|
||||
(nextOn) => {
|
||||
@@ -101,7 +112,7 @@ function AuxColumnContent() {
|
||||
value={cameraValue}
|
||||
min={cameraMin}
|
||||
max={cameraMax}
|
||||
step={0.5}
|
||||
step={cameraTiltStep}
|
||||
disabled={cameraDisabled}
|
||||
onChange={setServoAngle}
|
||||
/>
|
||||
|
||||
@@ -30,7 +30,7 @@ function getSpeedModeConfig(speedMode) {
|
||||
|
||||
export default function ControlPadPanel({ disabled = false }) {
|
||||
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 [speedMode, setSpeedMode] = useState('normal');
|
||||
const [activeInputLabel, setActiveInputLabel] = useState('stop');
|
||||
@@ -71,10 +71,18 @@ export default function ControlPadPanel({ disabled = false }) {
|
||||
const tokens = buildVirtualKeyTokens(cell, modeId);
|
||||
const vector = computeKeyboardDriveVector(tokens, keymap);
|
||||
const speedOptions = getKeyboardDriveSpeedOptions(tokens, keymap, keyboardSpeeds);
|
||||
const precisionActive = modeId === 'precision';
|
||||
|
||||
// Mobile deliberately routes through the keyboard vector/speed helpers. The
|
||||
// thumb pad only chooses which virtual keys are down, so changes to keyboard
|
||||
// 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 });
|
||||
registerInputState(SOURCE, {
|
||||
keys: Array.from(tokens),
|
||||
@@ -90,6 +98,7 @@ export default function ControlPadPanel({ disabled = false }) {
|
||||
keyboardSpeeds,
|
||||
keymap,
|
||||
registerInputState,
|
||||
setCameraPrecisionMode,
|
||||
setDriveVector,
|
||||
],
|
||||
);
|
||||
@@ -99,7 +108,7 @@ export default function ControlPadPanel({ disabled = false }) {
|
||||
clearRepeatTimer();
|
||||
activeCellRef.current = null;
|
||||
setActiveInputLabel('stop');
|
||||
sendDriveCell({ id: 'stop', actions: [] }, lastEvent, 'normal');
|
||||
sendDriveCell({ id: 'stop', actions: [] }, lastEvent, speedModeRef.current);
|
||||
},
|
||||
[clearRepeatTimer, sendDriveCell],
|
||||
);
|
||||
@@ -127,21 +136,31 @@ export default function ControlPadPanel({ disabled = false }) {
|
||||
(nextMode) => {
|
||||
setSpeedMode(nextMode);
|
||||
speedModeRef.current = nextMode;
|
||||
setCameraPrecisionMode(nextMode === 'precision');
|
||||
if (activeCellRef.current) {
|
||||
sendDriveCell(activeCellRef.current, 'speed', nextMode);
|
||||
}
|
||||
},
|
||||
[sendDriveCell],
|
||||
[sendDriveCell, setCameraPrecisionMode],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => clearRepeatTimer();
|
||||
}, [clearRepeatTimer]);
|
||||
return () => {
|
||||
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(() => {
|
||||
if (!disabled) return;
|
||||
stopDrivePad('disabled');
|
||||
}, [disabled, stopDrivePad]);
|
||||
setCameraPrecisionMode(false);
|
||||
}, [disabled, setCameraPrecisionMode, stopDrivePad]);
|
||||
|
||||
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">
|
||||
|
||||
@@ -39,6 +39,8 @@ const CHAT_DOCK_INITIAL_HEIGHT = 224;
|
||||
const CHAT_DOCK_MIN_HEIGHT = 144;
|
||||
const CHAT_DOCK_MAX_HEIGHT = 300;
|
||||
const CHAT_DOCK_BOTTOM_INSET = 8;
|
||||
const CAMERA_TILT_STEP_DEGREES = 0.5;
|
||||
const CAMERA_TILT_PRECISION_STEP_DEGREES = 0.1;
|
||||
|
||||
function TopDownMapPanel() {
|
||||
const roverId = useControlSelector((control) => control.state.roverId);
|
||||
@@ -90,6 +92,14 @@ function DriveDockPanel() {
|
||||
const upLabel = formatKeyLabel(keymap?.cameraUp?.[0]);
|
||||
const downLabel = formatKeyLabel(keymap?.cameraDown?.[0]);
|
||||
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(
|
||||
() => ({
|
||||
setHeadlight: (nextOn) => {
|
||||
@@ -156,6 +166,7 @@ function DriveDockPanel() {
|
||||
value={value}
|
||||
min={min}
|
||||
max={max}
|
||||
step={cameraTiltStep}
|
||||
label="Camera tilt"
|
||||
disabled={cameraDisabled}
|
||||
onChange={setServoAngle}
|
||||
@@ -388,9 +399,9 @@ export default function RightPaneTabs({ layout, onOpenHelpOverlay }) {
|
||||
{/* activities tab */}
|
||||
<TabPanel id="activities">
|
||||
<div className={`flex flex-col ${themeGapClass}`}>
|
||||
<BarcodeGamesPanel />
|
||||
<LiftCard />
|
||||
<NeatoCard />
|
||||
<LiftCard />
|
||||
<BarcodeGamesPanel />
|
||||
<OdometerPanel />
|
||||
<ButtonBoxPanel />
|
||||
<KinectPanel />
|
||||
|
||||
@@ -49,6 +49,7 @@ const CONTROL_ACTION_NAMES = [
|
||||
'setServoAngle',
|
||||
'nudgeServo',
|
||||
'goServoHome',
|
||||
'setCameraPrecisionMode',
|
||||
'runMacro',
|
||||
'stopAllMotion',
|
||||
'sendOiCommand',
|
||||
@@ -411,6 +412,15 @@ export function ControlSystemProvider({ children }) {
|
||||
setServoAngle(target);
|
||||
}, [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(
|
||||
async (macroId) => {
|
||||
const macro = state.macros.find((item) => item.id === macroId) || null;
|
||||
@@ -675,6 +685,7 @@ export function ControlSystemProvider({ children }) {
|
||||
setServoAngle,
|
||||
nudgeServo,
|
||||
goServoHome,
|
||||
setCameraPrecisionMode,
|
||||
runMacro,
|
||||
stopAllMotion,
|
||||
sendOiCommand,
|
||||
@@ -701,6 +712,7 @@ export function ControlSystemProvider({ children }) {
|
||||
setServoAngle,
|
||||
nudgeServo,
|
||||
goServoHome,
|
||||
setCameraPrecisionMode,
|
||||
runMacro,
|
||||
stopAllMotion,
|
||||
sendOiCommand,
|
||||
|
||||
@@ -20,6 +20,12 @@ function createCameraState() {
|
||||
enabled: false,
|
||||
angle: 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,
|
||||
enabled: Boolean(action.payload?.config),
|
||||
config: action.payload?.config ?? null,
|
||||
precisionMode: action.payload?.config ? state.camera.precisionMode : false,
|
||||
angle:
|
||||
typeof action.payload?.angle === 'number'
|
||||
? action.payload.angle
|
||||
@@ -123,6 +130,22 @@ export function controlReducer(state, action) {
|
||||
...state,
|
||||
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': {
|
||||
const sourceKey = action.payload?.source || 'unknown';
|
||||
return {
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
computeKeyboardAuxMotors,
|
||||
computeKeyboardDriveVector,
|
||||
getKeyboardDriveSpeedOptions,
|
||||
isPrecisionDriveActive,
|
||||
resolveKeyboardSpeeds,
|
||||
} from './driveIntent.js';
|
||||
import { trackAnalyticsEvent } from '../../analytics/index.js';
|
||||
@@ -34,6 +35,7 @@ const TILT_INTERVAL_MIN = 5;
|
||||
const TILT_INTERVAL_MAX = 500;
|
||||
const TILT_SPEED_MIN = 1;
|
||||
const TILT_SPEED_MAX = 100;
|
||||
const PRECISION_SERVO_NUDGE_DEGREES = 0.25;
|
||||
const VIDEO_FILTER_SEQUENCE = ['none', 'grayscale', 'greenscale'];
|
||||
|
||||
function normalizeVideoFilter(value) {
|
||||
@@ -91,6 +93,7 @@ export default function KeyboardInputManager() {
|
||||
runMacro,
|
||||
stopAllMotion,
|
||||
registerInputState,
|
||||
setCameraPrecisionMode,
|
||||
toggleHeadlight,
|
||||
toggleLaser,
|
||||
startHorn,
|
||||
@@ -157,9 +160,16 @@ export default function KeyboardInputManager() {
|
||||
const latest = latestRef.current;
|
||||
if (!latest) return;
|
||||
const tokensSnapshot = new Set(activeTokensRef.current);
|
||||
const precisionActive = isPrecisionDriveActive(tokensSnapshot, latest.keymap);
|
||||
const speedOptions = getKeyboardDriveSpeedOptions(tokensSnapshot, latest.keymap, latest.keyboardSpeeds);
|
||||
const vector = computeKeyboardDriveVector(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 (
|
||||
vector.x !== lastVectorRef.current.x ||
|
||||
vector.y !== lastVectorRef.current.y ||
|
||||
@@ -219,7 +229,10 @@ export default function KeyboardInputManager() {
|
||||
stopServoLoop();
|
||||
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, 0);
|
||||
@@ -288,6 +301,7 @@ export default function KeyboardInputManager() {
|
||||
latest?.stopHorn();
|
||||
}
|
||||
latest?.setMicPttActive(false);
|
||||
latest?.setCameraPrecisionMode(false);
|
||||
latest?.stopAllMotion();
|
||||
latest?.registerInputState(SOURCE, { keys: [], vector: ZERO_VECTOR, aux: ZERO_AUX });
|
||||
}, [stopServoLoop, stopSongLoop]);
|
||||
@@ -369,6 +383,7 @@ export default function KeyboardInputManager() {
|
||||
servoRepeatMs,
|
||||
servoStep,
|
||||
setAuxMotors,
|
||||
setCameraPrecisionMode,
|
||||
setDriveVector,
|
||||
setMicPttActive,
|
||||
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) {
|
||||
const allForward = bindingActive(keymap.auxAllForward, keys);
|
||||
if (allForward) {
|
||||
@@ -78,7 +88,7 @@ export function computeKeyboardAuxMotors(keys, keymap) {
|
||||
}
|
||||
|
||||
export function getKeyboardDriveSpeedOptions(keys, keymap, keyboardSpeeds) {
|
||||
const slowActive = bindingActive(keymap.slowModifier, keys);
|
||||
const slowActive = isPrecisionDriveActive(keys, keymap);
|
||||
return slowActive
|
||||
? { baseSpeed: keyboardSpeeds.precisionSpeed, boostSpeed: keyboardSpeeds.precisionSpeed }
|
||||
: { baseSpeed: keyboardSpeeds.baseSpeed, boostSpeed: keyboardSpeeds.turboSpeed };
|
||||
|
||||
Reference in New Issue
Block a user