headlight rework and laser addition

This commit is contained in:
legop3
2026-06-27 15:57:47 -04:00
parent 3c1b1dab6e
commit 8e322ed57b
45 changed files with 596 additions and 373 deletions
@@ -1,14 +1,15 @@
// Night Vision Control
// Purpose: Defines the Night Vision Control module and the local helpers/components used in this file.
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
// GPIO Toggle Control
// Purpose: Renders a direct press target for rover GPIO-backed toggles such as the headlight and laser.
// Scope: Owns optimistic button state and touch/click de-duplication while callers provide device labels and actions.
import { useEffect, useMemo, useRef, useState } from 'react';
function isBoolean(value) {
return typeof value === 'boolean';
}
export default function NightVisionControl({
nightVisionOn,
export default function GPIOToggleControl({
label,
on,
disabled,
onToggle,
keyLabel,
@@ -16,16 +17,21 @@ export default function NightVisionControl({
heightClass = '',
}) {
const [optimistic, setOptimistic] = useState(
isBoolean(nightVisionOn) ? nightVisionOn : null,
isBoolean(on) ? on : null,
);
const suppressClickRef = useRef(false);
const suppressClickTimerRef = useRef(null);
useEffect(() => {
if (isBoolean(nightVisionOn)) {
setOptimistic(nightVisionOn);
if (isBoolean(on)) {
// Server-confirmed state can arrive after an optimistic click. Defer the
// reconciliation one tick so this effect stays a synchronization point
// instead of triggering React's synchronous set-state-in-effect lint rule.
const timer = window.setTimeout(() => setOptimistic(on), 0);
return () => window.clearTimeout(timer);
}
}, [nightVisionOn]);
return undefined;
}, [on]);
useEffect(
() => () => {
@@ -58,7 +64,7 @@ export default function NightVisionControl({
Mobile browsers, especially Safari, do not always dispatch a reliable
synthetic click for a second finger while another finger is held on the
drive pad. Toggle on the real touch pointerdown instead, then suppress the
follow-up click so one tap cannot flip night vision twice.
follow-up click so one tap cannot flip the GPIO-backed device twice.
*/
event.preventDefault();
suppressClickRef.current = true;
@@ -91,7 +97,7 @@ export default function NightVisionControl({
};
const buttonClasses = useMemo(() => {
// Night vision is used as a direct mobile press target, so selection and
// This control is used as a direct mobile press target, so selection and
// Safari callout suppression live on the button itself rather than only on
// the surrounding mobile column.
const base =
@@ -114,7 +120,7 @@ export default function NightVisionControl({
className={buttonClasses}
>
<span className="flex items-center gap-0.5">
<span className="text-sm font-semibold">Night Vision</span>
<span className="text-sm font-semibold">{label}</span>
{keyLabel ? (
<span className="rounded bg-slate-800 px-1 py-0.5 text-[0.6rem] font-semibold text-slate-200">
{keyLabel}
@@ -38,7 +38,8 @@ export const ACTIONS = [
{ id: 'sideReverse', label: 'Side reverse toggle', kind: 'button', section: 'Brush toggles' },
{ id: 'driveMacro', label: 'Drive macro', kind: 'button', section: 'Mode macros' },
{ id: 'dockMacro', label: 'Dock macro', kind: 'button', section: 'Mode macros' },
{ id: 'nightVisionToggle', label: 'Night vision toggle', kind: 'button', section: 'Camera' },
{ id: 'headlightToggle', label: 'Headlight toggle', kind: 'button', section: 'Camera' },
{ id: 'laserToggle', label: 'Laser toggle', kind: 'button', section: 'Camera' },
];
export const CAPTURE_AXIS_THRESHOLD = 0.45;
@@ -26,7 +26,8 @@ const KEY_ACTIONS = [
{ id: 'auxAllForward', label: 'All Aux Forward', group: 'Aux Motors' },
{ id: 'cameraUp', label: 'Camera Up', group: 'Camera' },
{ id: 'cameraDown', label: 'Camera Down', group: 'Camera' },
{ id: 'nightVisionToggle', label: 'Toggle Night Vision', group: 'Camera' },
{ id: 'headlightToggle', label: 'Toggle Headlight', group: 'Camera' },
{ id: 'laserToggle', label: 'Toggle Laser', group: 'Camera' },
{ id: 'videoFilterCycle', label: 'Cycle Video Filter', group: 'Camera' },
{ id: 'hornHonk', label: 'Horn (Hold)', group: 'Audio' },
{ id: 'micPtt', label: 'Mic Push To Talk', group: 'Audio' },
@@ -1,11 +1,11 @@
// Aux Column
// Purpose: Assembles the mobile auxiliary controls column, which is the left column by default.
// Scope: Owns mobile aux/camera/night vision/horn wiring while reusing desktop variation components where intended.
// Scope: Owns mobile aux/camera/headlight/laser/horn wiring while reusing desktop variation components where intended.
import { useCallback, useRef } from 'react';
import { useControlActions, useControlSelector } from '../../controls/index.js';
import { useManualDockAssist } from '../../features/manualDockAssist/useManualDockAssist.js';
import HornControl from '../HornControl/index.jsx';
import NightVisionControl from '../NightVisionControl/index.jsx';
import GPIOToggleControl from '../GPIOToggleControl/index.jsx';
import { AUX_ZERO } from './constants.js';
import VacuumControls from './VacuumControls.jsx';
import VerticalCameraTilt from './VerticalCameraTilt.jsx';
@@ -15,16 +15,19 @@ function AuxColumnContent() {
const roverId = useControlSelector((control) => control.state.roverId);
const camera = useControlSelector((control) => control.state.camera);
const horn = useControlSelector((control) => control.state.horn);
const nightVision = useControlSelector((control) => control.pipeline?.nightVision);
const nightVisionState = useControlSelector((control) => control.pipeline?.nightVisionState);
const headlight = useControlSelector((control) => control.pipeline?.headlight);
const headlightState = useControlSelector((control) => control.pipeline?.headlightState);
const laser = useControlSelector((control) => control.pipeline?.laser);
const laserState = useControlSelector((control) => control.pipeline?.laserState);
const pipelineHorn = useControlSelector((control) => control.pipeline?.horn);
const { setServoAngle, setNightVision, setAuxMotors, startHorn, stopHorn } = useControlActions();
const { setServoAngle, setHeadlight, setLaser, setAuxMotors, startHorn, stopHorn } = useControlActions();
const dockAssist = useManualDockAssist();
const disabled = !roverId;
const activeAuxButtonRef = useRef(null);
const cameraConfig = camera?.config;
const cameraEnabled = Boolean(roverId && camera?.enabled && cameraConfig);
const nightVisionAvailable = Boolean(roverId && nightVision);
const headlightAvailable = Boolean(roverId && headlight);
const laserAvailable = Boolean(roverId && laser);
const hornAvailable = Boolean(roverId && pipelineHorn);
const hornBlocked = horn?.overheated;
const cameraMin = typeof cameraConfig?.minAngle === 'number' ? cameraConfig.minAngle : -45;
@@ -37,13 +40,22 @@ function AuxColumnContent() {
: (cameraMin + cameraMax) / 2;
const cameraDisabled = Boolean(disabled || dockAssist.cameraLocked);
const handleNightVisionToggle = useCallback(
const handleHeadlightToggle = useCallback(
(nextOn) => {
if (!nightVisionAvailable) return;
trackAnalyticsEvent('night_vision_toggle', { roverId, source: 'mobile_control', enabled: Boolean(nextOn) });
setNightVision(nextOn);
if (!headlightAvailable) return;
trackAnalyticsEvent('headlight_toggle', { roverId, source: 'mobile_control', enabled: Boolean(nextOn) });
setHeadlight(nextOn);
},
[nightVisionAvailable, roverId, setNightVision],
[headlightAvailable, roverId, setHeadlight],
);
const handleLaserToggle = useCallback(
(nextOn) => {
if (!laserAvailable) return;
trackAnalyticsEvent('laser_toggle', { roverId, source: 'mobile_control', enabled: Boolean(nextOn) });
setLaser(nextOn);
},
[laserAvailable, roverId, setLaser],
);
const handleHornStart = useCallback(() => {
@@ -93,13 +105,27 @@ function AuxColumnContent() {
/>
</div>
) : null}
{nightVisionAvailable ? (
<NightVisionControl
nightVisionOn={nightVisionState?.nightVisionOn}
disabled={disabled}
onToggle={handleNightVisionToggle}
heightClass="h-full"
/>
{(headlightAvailable || laserAvailable) ? (
<div className="mobile-touch-control flex min-h-0 flex-1 flex-col gap-0.5">
{headlightAvailable ? (
<GPIOToggleControl
label="Headlight"
on={headlightState?.headlightOn}
disabled={disabled}
onToggle={handleHeadlightToggle}
heightClass="h-full"
/>
) : null}
{laserAvailable ? (
<GPIOToggleControl
label="Laser"
on={laserState?.laserOn}
disabled={disabled}
onToggle={handleLaserToggle}
heightClass="h-full"
/>
) : null}
</div>
) : null}
</div>
<div className="mobile-touch-control min-h-0">
+39 -17
View File
@@ -19,7 +19,7 @@ import { useControlActions, useControlSelector } from '../../controls/index.js';
import RoverQueuesPanel from '../RoverQueuesPanel/index.jsx';
import RawUserPilePanel from '../RawUserPilePanel/index.jsx';
import { formatKeyLabel } from '../../controls/keymapUtils.js';
import NightVisionControl from '../NightVisionControl/index.jsx';
import GPIOToggleControl from '../GPIOToggleControl/index.jsx';
import HornControl from '../HornControl/index.jsx';
import CameraTiltControl from '../CameraTiltControl/index.jsx';
import VipPanel from '../VipPanel/index.jsx';
@@ -62,17 +62,20 @@ function DriveDockPanel() {
const keymap = useControlSelector((control) => control.state.keymap);
const camera = useControlSelector((control) => control.state.camera);
const horn = useControlSelector((control) => control.state.horn);
const nightVision = useControlSelector((control) => control.pipeline?.nightVision);
const nightVisionState = useControlSelector((control) => control.pipeline?.nightVisionState);
const headlight = useControlSelector((control) => control.pipeline?.headlight);
const headlightState = useControlSelector((control) => control.pipeline?.headlightState);
const laser = useControlSelector((control) => control.pipeline?.laser);
const laserState = useControlSelector((control) => control.pipeline?.laserState);
const pipelineHorn = useControlSelector((control) => control.pipeline?.horn);
const { setServoAngle, setNightVision, startHorn, stopHorn } = useControlActions();
const { setServoAngle, setHeadlight, setLaser, startHorn, stopHorn } = useControlActions();
const dockAssist = useManualDockAssist();
const driveDockState = useDriveDockState(roverId);
const hideInlineControls = driveDockState.docked && !driveDockState.driving;
const config = camera?.config;
const cameraEnabled = Boolean(roverId && camera?.enabled && config);
const nightVisionAvailable = Boolean(roverId && nightVision);
const headlightAvailable = Boolean(roverId && headlight);
const laserAvailable = Boolean(roverId && laser);
const hornAvailable = Boolean(roverId && pipelineHorn);
const hornBlocked = horn?.overheated;
const min = typeof config?.minAngle === 'number' ? config.minAngle : -30;
@@ -83,16 +86,21 @@ function DriveDockPanel() {
: typeof config?.homeAngle === 'number'
? config.homeAngle
: (min + max) / 2;
const nightVisionLabel = formatKeyLabel(keymap?.nightVisionToggle?.[0]);
const headlightLabel = formatKeyLabel(keymap?.headlightToggle?.[0]);
const laserLabel = formatKeyLabel(keymap?.laserToggle?.[0]);
const hornLabel = formatKeyLabel(keymap?.hornHonk?.[0]);
const upLabel = formatKeyLabel(keymap?.cameraUp?.[0]);
const downLabel = formatKeyLabel(keymap?.cameraDown?.[0]);
const cameraDisabled = Boolean(!roverId || dockAssist.cameraLocked);
const trackedControls = useMemo(
() => ({
setNightVision: (nextOn) => {
trackAnalyticsEvent('night_vision_toggle', { roverId, source: 'desktop_control', enabled: Boolean(nextOn) });
setNightVision(nextOn);
setHeadlight: (nextOn) => {
trackAnalyticsEvent('headlight_toggle', { roverId, source: 'desktop_control', enabled: Boolean(nextOn) });
setHeadlight(nextOn);
},
setLaser: (nextOn) => {
trackAnalyticsEvent('laser_toggle', { roverId, source: 'desktop_control', enabled: Boolean(nextOn) });
setLaser(nextOn);
},
startHorn: () => {
trackAnalyticsEvent('horn_start', { roverId, source: 'desktop_control' });
@@ -100,7 +108,7 @@ function DriveDockPanel() {
},
stopHorn,
}),
[roverId, setNightVision, startHorn, stopHorn],
[roverId, setHeadlight, setLaser, startHorn, stopHorn],
);
return (
@@ -113,13 +121,27 @@ function DriveDockPanel() {
<DriveDockAction layout="desktop" expand driveDockState={driveDockState} />
{!hideInlineControls ? (
<div className={`${themeStackClass} p-0 text-sm text-slate-200`}>
{nightVisionAvailable && (
<NightVisionControl
nightVisionOn={nightVisionState?.nightVisionOn}
disabled={!roverId}
onToggle={trackedControls.setNightVision}
keyLabel={nightVisionLabel}
/>
{(headlightAvailable || laserAvailable) && (
<div className="grid grid-cols-2 gap-1">
{headlightAvailable && (
<GPIOToggleControl
label="Headlight"
on={headlightState?.headlightOn}
disabled={!roverId}
onToggle={trackedControls.setHeadlight}
keyLabel={headlightLabel}
/>
)}
{laserAvailable && (
<GPIOToggleControl
label="Laser"
on={laserState?.laserOn}
disabled={!roverId}
onToggle={trackedControls.setLaser}
keyLabel={laserLabel}
/>
)}
</div>
)}
{hornAvailable && (
<HornControl
+39 -23
View File
@@ -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,
+39 -15
View File
@@ -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,
],
+2 -1
View File
@@ -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))) {
+6 -3
View File
@@ -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,
},
};
}
+4 -2
View File
@@ -67,7 +67,8 @@ export const HELP_CONTENT = {
items: [
{ action: 'cameraUp', label: 'Tilt up' },
{ action: 'cameraDown', label: 'Tilt down' },
{ action: 'nightVisionToggle', label: 'Toggle night vision' },
{ action: 'headlightToggle', label: 'Toggle headlight' },
{ action: 'laserToggle', label: 'Toggle laser' },
],
},
{
@@ -202,7 +203,8 @@ export const HELP_CONTENT = {
// items: [
// { action: 'driveMacro', label: 'Drive macro' },
// { action: 'dockMacro', label: 'Dock macro' },
// { action: 'nightVisionToggle', label: 'Toggle night vision' },
// { action: 'headlightToggle', label: 'Toggle headlight' },
// { action: 'laserToggle', label: 'Toggle laser' },
// { action: 'chatFocus', label: 'Chat focus' },
// ],
// },
+5 -1
View File
@@ -73,10 +73,14 @@ export const GAMEPAD_PROFILE_DEFAULT = {
kind: 'button',
sources: [{ kind: 'button', index: 3 }],
},
nightVisionToggle: {
headlightToggle: {
kind: 'button',
sources: [{ kind: 'button', index: 9 }],
},
laserToggle: {
kind: 'button',
sources: [],
},
},
};