mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-19 02:50:46 -04:00
headlight rework and laser addition
This commit is contained in:
@@ -53,8 +53,10 @@ const CONTROL_ACTION_NAMES = [
|
||||
'stopAllMotion',
|
||||
'sendOiCommand',
|
||||
'setSensorStream',
|
||||
'setNightVision',
|
||||
'toggleNightVision',
|
||||
'setHeadlight',
|
||||
'toggleHeadlight',
|
||||
'setLaser',
|
||||
'toggleLaser',
|
||||
'updateKeyBinding',
|
||||
'resetKeyBindings',
|
||||
'registerInputState',
|
||||
@@ -469,28 +471,38 @@ export function ControlSystemProvider({ children }) {
|
||||
[pipeline],
|
||||
);
|
||||
|
||||
const setNightVision = useCallback(
|
||||
(nightVisionOn) => {
|
||||
if (!pipeline.nightVision) return;
|
||||
if (typeof nightVisionOn === 'boolean') {
|
||||
/*
|
||||
Rover daemon command names describe the IR LED, while the UI state
|
||||
describes camera visibility. LED "off" means nightVisionOn=true, and
|
||||
LED "on" means nightVisionOn=false.
|
||||
*/
|
||||
const action = nightVisionOn ? 'off' : 'on';
|
||||
pipeline.sendNightVision(action);
|
||||
} else {
|
||||
pipeline.sendNightVision('toggle');
|
||||
}
|
||||
const setHeadlight = useCallback(
|
||||
(headlightOn) => {
|
||||
if (!pipeline.headlight) return;
|
||||
// Web controls now speak in logical device state. Any electrical
|
||||
// inversion needed by the actual GPIO driver is handled by roverd's
|
||||
// activeLow config, so this command stays readable and direct.
|
||||
const action = typeof headlightOn === 'boolean' ? (headlightOn ? 'on' : 'off') : 'toggle';
|
||||
pipeline.sendHeadlight(action);
|
||||
recordControlIntent();
|
||||
},
|
||||
[pipeline, recordControlIntent],
|
||||
);
|
||||
|
||||
const toggleNightVision = useCallback(() => {
|
||||
setNightVision();
|
||||
}, [setNightVision]);
|
||||
const toggleHeadlight = useCallback(() => {
|
||||
setHeadlight();
|
||||
}, [setHeadlight]);
|
||||
|
||||
const setLaser = useCallback(
|
||||
(laserOn) => {
|
||||
if (!pipeline.laser) return;
|
||||
// The laser shares the same logical toggle contract as the headlight; it
|
||||
// is separate only because it has its own GPIO pin, UI control, and keybind.
|
||||
const action = typeof laserOn === 'boolean' ? (laserOn ? 'on' : 'off') : 'toggle';
|
||||
pipeline.sendLaser(action);
|
||||
recordControlIntent();
|
||||
},
|
||||
[pipeline, recordControlIntent],
|
||||
);
|
||||
|
||||
const toggleLaser = useCallback(() => {
|
||||
setLaser();
|
||||
}, [setLaser]);
|
||||
|
||||
const setSongNote = useCallback(
|
||||
(note) => {
|
||||
@@ -665,8 +677,10 @@ export function ControlSystemProvider({ children }) {
|
||||
stopAllMotion,
|
||||
sendOiCommand,
|
||||
setSensorStream,
|
||||
setNightVision,
|
||||
toggleNightVision,
|
||||
setHeadlight,
|
||||
toggleHeadlight,
|
||||
setLaser,
|
||||
toggleLaser,
|
||||
updateKeyBinding,
|
||||
resetKeyBindings,
|
||||
registerInputState,
|
||||
@@ -689,8 +703,10 @@ export function ControlSystemProvider({ children }) {
|
||||
stopAllMotion,
|
||||
sendOiCommand,
|
||||
setSensorStream,
|
||||
setNightVision,
|
||||
toggleNightVision,
|
||||
setHeadlight,
|
||||
toggleHeadlight,
|
||||
setLaser,
|
||||
toggleLaser,
|
||||
updateKeyBinding,
|
||||
resetKeyBindings,
|
||||
registerInputState,
|
||||
|
||||
@@ -29,9 +29,14 @@ export function useCommandPipeline(options = {}) {
|
||||
return rosterEntry.cameraServo;
|
||||
}, [rosterEntry]);
|
||||
|
||||
const nightVision = useMemo(() => {
|
||||
if (!rosterEntry?.nightVision || !rosterEntry.nightVision.enabled) return null;
|
||||
return rosterEntry.nightVision;
|
||||
const headlight = useMemo(() => {
|
||||
if (!rosterEntry?.headlight || !rosterEntry.headlight.enabled) return null;
|
||||
return rosterEntry.headlight;
|
||||
}, [rosterEntry]);
|
||||
|
||||
const laser = useMemo(() => {
|
||||
if (!rosterEntry?.laser || !rosterEntry.laser.enabled) return null;
|
||||
return rosterEntry.laser;
|
||||
}, [rosterEntry]);
|
||||
|
||||
const horn = useMemo(() => {
|
||||
@@ -39,7 +44,8 @@ export function useCommandPipeline(options = {}) {
|
||||
return rosterEntry.horn;
|
||||
}, [rosterEntry]);
|
||||
|
||||
const nightVisionState = useMemo(() => rosterEntry?.nightVision?.state ?? null, [rosterEntry]);
|
||||
const headlightState = useMemo(() => rosterEntry?.headlight?.state ?? null, [rosterEntry]);
|
||||
const laserState = useMemo(() => rosterEntry?.laser?.state ?? null, [rosterEntry]);
|
||||
|
||||
const emitCommand = useCallback(
|
||||
(payload, cb) => {
|
||||
@@ -167,16 +173,28 @@ export function useCommandPipeline(options = {}) {
|
||||
[roverId, sendOiCommand, sendDriveDirect, sendAuxMotors, sendServoAngle],
|
||||
);
|
||||
|
||||
const sendNightVision = useCallback(
|
||||
const sendHeadlight = useCallback(
|
||||
(action = 'toggle') => {
|
||||
if (!roverId || !nightVision) return null;
|
||||
if (!roverId || !headlight) return null;
|
||||
emitCommand({
|
||||
type: 'nightVision',
|
||||
data: { nightVision: { action } },
|
||||
type: 'headlight',
|
||||
data: { headlight: { action } },
|
||||
});
|
||||
return action;
|
||||
},
|
||||
[emitCommand, nightVision, roverId],
|
||||
[emitCommand, headlight, roverId],
|
||||
);
|
||||
|
||||
const sendLaser = useCallback(
|
||||
(action = 'toggle') => {
|
||||
if (!roverId || !laser) return null;
|
||||
emitCommand({
|
||||
type: 'laser',
|
||||
data: { laser: { action } },
|
||||
});
|
||||
return action;
|
||||
},
|
||||
[emitCommand, laser, roverId],
|
||||
);
|
||||
|
||||
const sendHorn = useCallback(
|
||||
@@ -222,8 +240,10 @@ export function useCommandPipeline(options = {}) {
|
||||
roverId,
|
||||
rosterEntry,
|
||||
servoConfig,
|
||||
nightVision,
|
||||
nightVisionState,
|
||||
headlight,
|
||||
headlightState,
|
||||
laser,
|
||||
laserState,
|
||||
horn,
|
||||
emitCommand,
|
||||
enableSensorStream,
|
||||
@@ -231,7 +251,8 @@ export function useCommandPipeline(options = {}) {
|
||||
sendAuxMotors,
|
||||
sendServoAngle,
|
||||
sendOiCommand,
|
||||
sendNightVision,
|
||||
sendHeadlight,
|
||||
sendLaser,
|
||||
sendHorn,
|
||||
sendSong,
|
||||
runMacroSteps,
|
||||
@@ -240,8 +261,10 @@ export function useCommandPipeline(options = {}) {
|
||||
roverId,
|
||||
rosterEntry,
|
||||
servoConfig,
|
||||
nightVision,
|
||||
nightVisionState,
|
||||
headlight,
|
||||
headlightState,
|
||||
laser,
|
||||
laserState,
|
||||
horn,
|
||||
emitCommand,
|
||||
enableSensorStream,
|
||||
@@ -249,7 +272,8 @@ export function useCommandPipeline(options = {}) {
|
||||
sendAuxMotors,
|
||||
sendServoAngle,
|
||||
sendOiCommand,
|
||||
sendNightVision,
|
||||
sendHeadlight,
|
||||
sendLaser,
|
||||
sendHorn,
|
||||
runMacroSteps,
|
||||
],
|
||||
|
||||
@@ -51,7 +51,8 @@ export const DEFAULT_KEYMAP = {
|
||||
auxAllForward: [","],
|
||||
cameraUp: ['u'],
|
||||
cameraDown: ['j'],
|
||||
nightVisionToggle: ['e'],
|
||||
headlightToggle: ['e'],
|
||||
laserToggle: ['r'],
|
||||
videoFilterCycle: ['2'],
|
||||
hornHonk: ['h'],
|
||||
micPtt: ['m'],
|
||||
|
||||
@@ -54,7 +54,8 @@ export default function GamepadInputManager() {
|
||||
setAuxMotors,
|
||||
setServoAngle,
|
||||
runMacro,
|
||||
toggleNightVision,
|
||||
toggleHeadlight,
|
||||
toggleLaser,
|
||||
registerInputState,
|
||||
} = useControlActions();
|
||||
const cameraAngle = useControlSelector((control) => control.state.camera?.angle);
|
||||
@@ -175,7 +176,8 @@ export default function GamepadInputManager() {
|
||||
setDriveVector,
|
||||
setMode,
|
||||
setServoAngle,
|
||||
toggleNightVision,
|
||||
toggleHeadlight,
|
||||
toggleLaser,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -286,11 +288,18 @@ export default function GamepadInputManager() {
|
||||
handleButtonEdge('dockMacro', false);
|
||||
}
|
||||
|
||||
if (outputs.buttons.nightVisionToggle && handleButtonEdge('nightVisionToggle', true)) {
|
||||
trackAnalyticsEvent('night_vision_toggle', { roverId: latest.roverId || '', source: 'gamepad' });
|
||||
latest.toggleNightVision();
|
||||
} else if (!outputs.buttons.nightVisionToggle) {
|
||||
handleButtonEdge('nightVisionToggle', false);
|
||||
if (outputs.buttons.headlightToggle && handleButtonEdge('headlightToggle', true)) {
|
||||
trackAnalyticsEvent('headlight_toggle', { roverId: latest.roverId || '', source: 'gamepad' });
|
||||
latest.toggleHeadlight();
|
||||
} else if (!outputs.buttons.headlightToggle) {
|
||||
handleButtonEdge('headlightToggle', false);
|
||||
}
|
||||
|
||||
if (outputs.buttons.laserToggle && handleButtonEdge('laserToggle', true)) {
|
||||
trackAnalyticsEvent('laser_toggle', { roverId: latest.roverId || '', source: 'gamepad' });
|
||||
latest.toggleLaser();
|
||||
} else if (!outputs.buttons.laserToggle) {
|
||||
handleButtonEdge('laserToggle', false);
|
||||
}
|
||||
|
||||
if (Math.abs(outputs.cameraAxis) > 0.001) {
|
||||
|
||||
@@ -91,7 +91,8 @@ export default function KeyboardInputManager() {
|
||||
runMacro,
|
||||
stopAllMotion,
|
||||
registerInputState,
|
||||
toggleNightVision,
|
||||
toggleHeadlight,
|
||||
toggleLaser,
|
||||
startHorn,
|
||||
stopHorn,
|
||||
setMicPttActive,
|
||||
@@ -376,7 +377,8 @@ export default function KeyboardInputManager() {
|
||||
startHorn,
|
||||
stopAllMotion,
|
||||
stopHorn,
|
||||
toggleNightVision,
|
||||
toggleHeadlight,
|
||||
toggleLaser,
|
||||
videoColorFilter,
|
||||
};
|
||||
});
|
||||
@@ -413,9 +415,12 @@ export default function KeyboardInputManager() {
|
||||
} else if (newlyPressed.some((token) => latest.keymap.dockMacro?.has(token))) {
|
||||
trackAnalyticsEvent('dock_assist_toggle', { roverId: latest.roverId || '', source: 'keyboard' });
|
||||
latest.dockAssist.toggleAssist();
|
||||
} else if (newlyPressed.some((token) => latest.keymap.nightVisionToggle?.has(token))) {
|
||||
trackAnalyticsEvent('night_vision_toggle', { roverId: latest.roverId || '', source: 'keyboard' });
|
||||
latest.toggleNightVision();
|
||||
} else if (newlyPressed.some((token) => latest.keymap.headlightToggle?.has(token))) {
|
||||
trackAnalyticsEvent('headlight_toggle', { roverId: latest.roverId || '', source: 'keyboard' });
|
||||
latest.toggleHeadlight();
|
||||
} else if (newlyPressed.some((token) => latest.keymap.laserToggle?.has(token))) {
|
||||
trackAnalyticsEvent('laser_toggle', { roverId: latest.roverId || '', source: 'keyboard' });
|
||||
latest.toggleLaser();
|
||||
} else if (newlyPressed.some((token) => latest.keymap.videoFilterCycle?.has(token))) {
|
||||
cycleVideoFilter();
|
||||
} else if (newlyPressed.some((token) => latest.keymap.hornHonk?.has(token))) {
|
||||
|
||||
@@ -163,7 +163,8 @@ export function computeGamepadOutputs(padState, profile) {
|
||||
const sideReverseSource = resolveButtonSource(padState, bindings.sideReverse?.sources);
|
||||
const driveMacroSource = resolveButtonSource(padState, bindings.driveMacro?.sources);
|
||||
const dockMacroSource = resolveButtonSource(padState, bindings.dockMacro?.sources);
|
||||
const nightVisionSource = resolveButtonSource(padState, bindings.nightVisionToggle?.sources);
|
||||
const headlightSource = resolveButtonSource(padState, bindings.headlightToggle?.sources);
|
||||
const laserSource = resolveButtonSource(padState, bindings.laserToggle?.sources);
|
||||
|
||||
return {
|
||||
driveVector: { x: driveX, y: driveY, boost: false },
|
||||
@@ -176,7 +177,8 @@ export function computeGamepadOutputs(padState, profile) {
|
||||
sideReverse: sideReverseSource.pressed,
|
||||
driveMacro: driveMacroSource.pressed,
|
||||
dockMacro: dockMacroSource.pressed,
|
||||
nightVisionToggle: nightVisionSource.pressed,
|
||||
headlightToggle: headlightSource.pressed,
|
||||
laserToggle: laserSource.pressed,
|
||||
},
|
||||
sources: {
|
||||
drive: driveSource.source,
|
||||
@@ -189,7 +191,8 @@ export function computeGamepadOutputs(padState, profile) {
|
||||
sideReverse: sideReverseSource.source,
|
||||
driveMacro: driveMacroSource.source,
|
||||
dockMacro: dockMacroSource.source,
|
||||
nightVisionToggle: nightVisionSource.source,
|
||||
headlightToggle: headlightSource.source,
|
||||
laserToggle: laserSource.source,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user