mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
refactorio
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
@@ -11,7 +11,7 @@
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
<meta name="apple-mobile-web-app-title" content="Roomba Rover" />
|
<meta name="apple-mobile-web-app-title" content="Roomba Rover" />
|
||||||
<title>Roomba Rover</title>
|
<title>Roomba Rover</title>
|
||||||
<script type="module" crossorigin src="/assets/index-X92JgDHr.js"></script>
|
<script type="module" crossorigin src="/assets/index-D-6ytlM1.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-CkqCBh_q.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-CkqCBh_q.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
// Drive Dock Action
|
// Drive Dock Action
|
||||||
// Purpose: Defines the Drive Dock Action module and the local helpers/components used in this file.
|
// Purpose: Defines the Drive Dock Action module and the local helpers/components used in this file.
|
||||||
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
|
// Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit.
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useControlSystem } from '../../controls/index.js';
|
import { useControlSystem } from '../../controls/index.js';
|
||||||
import { useTelemetryFrame } from '../../context/TelemetryContext.jsx';
|
import { useTelemetryFrame } from '../../context/TelemetryContext.jsx';
|
||||||
import { formatKeyLabel } from '../../controls/keymapUtils.js';
|
import { formatKeyLabel } from '../../controls/keymapUtils.js';
|
||||||
|
import { useManualDockAssist } from '../../features/manualDockAssist/useManualDockAssist.js';
|
||||||
|
|
||||||
export function deriveDriveDockState(frame) {
|
export function deriveDriveDockState(frame) {
|
||||||
const sensors = frame?.sensors || {};
|
const sensors = frame?.sensors || {};
|
||||||
@@ -103,17 +104,17 @@ export default function DriveDockAction({
|
|||||||
}) {
|
}) {
|
||||||
const isMobile = layout === 'mobile';
|
const isMobile = layout === 'mobile';
|
||||||
const {
|
const {
|
||||||
state: { roverId, keymap, manualDockAssist },
|
state: { roverId, keymap },
|
||||||
actions,
|
actions,
|
||||||
} = useControlSystem();
|
} = useControlSystem();
|
||||||
|
const dockAssist = useManualDockAssist();
|
||||||
const frame = useTelemetryFrame(roverId);
|
const frame = useTelemetryFrame(roverId);
|
||||||
const state = driveDockState ?? deriveDriveDockState(frame);
|
const state = driveDockState ?? deriveDriveDockState(frame);
|
||||||
const { driving, docked, charging, dockingInProgress } = state;
|
const { driving, docked, charging, dockingInProgress } = state;
|
||||||
const [pending, setPending] = useState(null);
|
const [pending, setPending] = useState(null);
|
||||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
const manualAssistActive = Boolean(manualDockAssist?.active);
|
const manualAssistActive = Boolean(dockAssist.active);
|
||||||
const wasDockedRef = useRef(false);
|
|
||||||
|
|
||||||
const driveDisabled = !roverId || pending !== null;
|
const driveDisabled = !roverId || pending !== null;
|
||||||
const dockDisabled = !roverId || pending !== null;
|
const dockDisabled = !roverId || pending !== null;
|
||||||
@@ -139,25 +140,11 @@ export default function DriveDockAction({
|
|||||||
const chargeValue = charging ? 'Charging' : docked ? 'Charging soon...' : '—';
|
const chargeValue = charging ? 'Charging' : docked ? 'Charging soon...' : '—';
|
||||||
const chargeTone = charging ? 'good' : docked ? 'warn' : 'bad';
|
const chargeTone = charging ? 'good' : docked ? 'warn' : 'bad';
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const justDocked = docked && !wasDockedRef.current;
|
|
||||||
if (manualAssistActive && justDocked) {
|
|
||||||
actions.sendSong([{ note: 84, duration: 6 }], { slot: 1 });
|
|
||||||
}
|
|
||||||
wasDockedRef.current = docked;
|
|
||||||
}, [actions, docked, manualAssistActive]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!manualAssistActive) return;
|
|
||||||
if (!charging) return;
|
|
||||||
actions.setManualDockAssistActive(false);
|
|
||||||
}, [actions, charging, manualAssistActive]);
|
|
||||||
|
|
||||||
const handleReturnToDrive = async () => {
|
const handleReturnToDrive = async () => {
|
||||||
if (!roverId || pending) return;
|
if (!roverId || pending) return;
|
||||||
setPending('drive');
|
setPending('drive');
|
||||||
try {
|
try {
|
||||||
actions.setManualDockAssistActive(false);
|
dockAssist.exitAssist();
|
||||||
actions.setMode('drive');
|
actions.setMode('drive');
|
||||||
await actions.runMacro('drive-sequence');
|
await actions.runMacro('drive-sequence');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -173,7 +160,7 @@ export default function DriveDockAction({
|
|||||||
setShowModal(false);
|
setShowModal(false);
|
||||||
setPending('drive');
|
setPending('drive');
|
||||||
try {
|
try {
|
||||||
actions.setManualDockAssistActive(false);
|
dockAssist.exitAssist();
|
||||||
actions.setMode('drive');
|
actions.setMode('drive');
|
||||||
await actions.runMacro('drive-sequence');
|
await actions.runMacro('drive-sequence');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -187,7 +174,7 @@ export default function DriveDockAction({
|
|||||||
if (!roverId || pending) return;
|
if (!roverId || pending) return;
|
||||||
setPending('dock');
|
setPending('dock');
|
||||||
try {
|
try {
|
||||||
actions.setManualDockAssistActive(true);
|
dockAssist.enterAssist();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert(err.message);
|
alert(err.message);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -200,7 +187,7 @@ export default function DriveDockAction({
|
|||||||
const handleOpenDock = () => {
|
const handleOpenDock = () => {
|
||||||
if (dockDisabled) return;
|
if (dockDisabled) return;
|
||||||
if (manualAssistActive) {
|
if (manualAssistActive) {
|
||||||
actions.setManualDockAssistActive(false);
|
dockAssist.exitAssist();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setShowModal(true);
|
setShowModal(true);
|
||||||
|
|||||||
@@ -1,29 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useControlSystem } from '../../../controls/index.js';
|
import { useManualDockAssist } from '../../../features/manualDockAssist/useManualDockAssist.js';
|
||||||
import { useSessionSelector } from '../../../context/SessionContext.jsx';
|
|
||||||
import { useTelemetryFrame } from '../../../context/TelemetryContext.jsx';
|
|
||||||
|
|
||||||
function ManualDockAssistOverlay({ mobileHud = false }) {
|
function ManualDockAssistOverlay({ mobileHud = false }) {
|
||||||
const {
|
const { visible, statusLabel, statusTone } = useManualDockAssist({ manageLifecycle: true });
|
||||||
state: { manualDockAssist },
|
if (!visible) return null;
|
||||||
} = useControlSystem();
|
const toneClass =
|
||||||
const roverId = useSessionSelector((state) => state.session?.assignment?.roverId ?? null);
|
statusTone === 'good'
|
||||||
const frame = useTelemetryFrame(roverId);
|
|
||||||
const sensors = frame?.sensors || {};
|
|
||||||
const chargingLabel = sensors?.chargingState?.label || '';
|
|
||||||
const docked = Boolean(sensors?.chargingSources?.homeBase);
|
|
||||||
const charging = docked && chargingLabel.toLowerCase() !== 'not charging' && chargingLabel !== '';
|
|
||||||
const active = Boolean(manualDockAssist?.active);
|
|
||||||
if (!active && !docked) return null;
|
|
||||||
|
|
||||||
const status = charging
|
|
||||||
? 'Docked and charging'
|
|
||||||
: docked
|
|
||||||
? 'Docked'
|
|
||||||
: 'Docking assist active';
|
|
||||||
const toneClass = charging
|
|
||||||
? 'border-emerald-200/70 bg-emerald-900/85 text-emerald-50'
|
? 'border-emerald-200/70 bg-emerald-900/85 text-emerald-50'
|
||||||
: docked
|
: statusTone === 'warn'
|
||||||
? 'border-amber-200/70 bg-amber-900/85 text-amber-50'
|
? 'border-amber-200/70 bg-amber-900/85 text-amber-50'
|
||||||
: 'border-indigo-200/70 bg-indigo-900/85 text-indigo-50';
|
: 'border-indigo-200/70 bg-indigo-900/85 text-indigo-50';
|
||||||
|
|
||||||
@@ -34,7 +18,7 @@ function ManualDockAssistOverlay({ mobileHud = false }) {
|
|||||||
mobileHud ? 'text-[0.65rem]' : 'text-xs'
|
mobileHud ? 'text-[0.65rem]' : 'text-xs'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{status}
|
{statusLabel}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
AUX_ALL_FORWARD,
|
AUX_ALL_FORWARD,
|
||||||
AUX_ALL_BACKWARD,
|
AUX_ALL_BACKWARD,
|
||||||
} from './constants.js';
|
} from './constants.js';
|
||||||
|
import { useManualDockAssist } from '../../features/manualDockAssist/useManualDockAssist.js';
|
||||||
|
|
||||||
function MobileJoystickPanel({ layout }) {
|
function MobileJoystickPanel({ layout }) {
|
||||||
const {
|
const {
|
||||||
@@ -95,10 +96,11 @@ function MobileJoystickPanel({ layout }) {
|
|||||||
|
|
||||||
function MobileActionsColumnContent({ layout }) {
|
function MobileActionsColumnContent({ layout }) {
|
||||||
const {
|
const {
|
||||||
state: { roverId, camera, horn, manualDockAssist },
|
state: { roverId, camera, horn },
|
||||||
pipeline,
|
pipeline,
|
||||||
actions: { setServoAngle, setNightVision, setAuxMotors, startHorn, stopHorn },
|
actions: { setServoAngle, setNightVision, setAuxMotors, startHorn, stopHorn },
|
||||||
} = useControlSystem();
|
} = useControlSystem();
|
||||||
|
const dockAssist = useManualDockAssist();
|
||||||
const disabled = !roverId;
|
const disabled = !roverId;
|
||||||
const activeRef = useRef(null);
|
const activeRef = useRef(null);
|
||||||
const cameraConfig = camera?.config;
|
const cameraConfig = camera?.config;
|
||||||
@@ -115,7 +117,7 @@ function MobileActionsColumnContent({ layout }) {
|
|||||||
: typeof cameraConfig?.homeAngle === 'number'
|
: typeof cameraConfig?.homeAngle === 'number'
|
||||||
? cameraConfig.homeAngle
|
? cameraConfig.homeAngle
|
||||||
: (cameraMin + cameraMax) / 2;
|
: (cameraMin + cameraMax) / 2;
|
||||||
const cameraDisabled = Boolean(disabled || manualDockAssist?.active);
|
const cameraDisabled = Boolean(disabled || dockAssist.cameraLocked);
|
||||||
|
|
||||||
const handleNightVisionToggle = useCallback(
|
const handleNightVisionToggle = useCallback(
|
||||||
(nextOn) => {
|
(nextOn) => {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { useSessionSelector } from '../../context/SessionContext.jsx';
|
|||||||
import { useSettingsNamespace } from '../../settings/index.js';
|
import { useSettingsNamespace } from '../../settings/index.js';
|
||||||
import ButtonBoxPanel from '../ButtonBoxPanel/index.jsx';
|
import ButtonBoxPanel from '../ButtonBoxPanel/index.jsx';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { useManualDockAssist } from '../../features/manualDockAssist/useManualDockAssist.js';
|
||||||
|
|
||||||
function TopDownMapPanel() {
|
function TopDownMapPanel() {
|
||||||
const {
|
const {
|
||||||
@@ -43,10 +44,11 @@ function TopDownMapPanel() {
|
|||||||
|
|
||||||
function DriveDockPanel() {
|
function DriveDockPanel() {
|
||||||
const {
|
const {
|
||||||
state: { roverId, keymap, camera, horn, manualDockAssist },
|
state: { roverId, keymap, camera, horn },
|
||||||
pipeline,
|
pipeline,
|
||||||
actions: { setServoAngle, setNightVision, startHorn, stopHorn },
|
actions: { setServoAngle, setNightVision, startHorn, stopHorn },
|
||||||
} = useControlSystem();
|
} = useControlSystem();
|
||||||
|
const dockAssist = useManualDockAssist();
|
||||||
const driveDockState = useDriveDockState(roverId);
|
const driveDockState = useDriveDockState(roverId);
|
||||||
const hideInlineControls = driveDockState.docked && !driveDockState.driving;
|
const hideInlineControls = driveDockState.docked && !driveDockState.driving;
|
||||||
|
|
||||||
@@ -68,7 +70,7 @@ function DriveDockPanel() {
|
|||||||
const hornLabel = formatKeyLabel(keymap?.hornHonk?.[0]);
|
const hornLabel = formatKeyLabel(keymap?.hornHonk?.[0]);
|
||||||
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 || manualDockAssist?.active);
|
const cameraDisabled = Boolean(!roverId || dockAssist.cameraLocked);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="panel-section grid h-full min-h-0 grid-rows-[minmax(0,1fr)_auto] gap-0.5">
|
<section className="panel-section grid h-full min-h-0 grid-rows-[minmax(0,1fr)_auto] gap-0.5">
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from './gamepadBindings.js';
|
} from './gamepadBindings.js';
|
||||||
import { subscribeGamepadHub } from './gamepadHub.js';
|
import { subscribeGamepadHub } from './gamepadHub.js';
|
||||||
import { isTextEntryActive } from './inputFocusUtils.js';
|
import { isTextEntryActive } from './inputFocusUtils.js';
|
||||||
|
import { useManualDockAssist } from '../../features/manualDockAssist/useManualDockAssist.js';
|
||||||
|
|
||||||
const SOURCE = 'gamepad';
|
const SOURCE = 'gamepad';
|
||||||
const ZERO_VECTOR = { x: 0, y: 0, boost: false };
|
const ZERO_VECTOR = { x: 0, y: 0, boost: false };
|
||||||
@@ -50,8 +51,6 @@ export default function GamepadInputManager() {
|
|||||||
state,
|
state,
|
||||||
actions: {
|
actions: {
|
||||||
setMode,
|
setMode,
|
||||||
setManualDockAssistActive,
|
|
||||||
toggleManualDockAssist,
|
|
||||||
setDriveVector,
|
setDriveVector,
|
||||||
setAuxMotors,
|
setAuxMotors,
|
||||||
setServoAngle,
|
setServoAngle,
|
||||||
@@ -60,6 +59,7 @@ export default function GamepadInputManager() {
|
|||||||
registerInputState,
|
registerInputState,
|
||||||
},
|
},
|
||||||
} = useControlSystem();
|
} = useControlSystem();
|
||||||
|
const dockAssist = useManualDockAssist();
|
||||||
const { value: gamepadSettings, save: saveGamepadSettings } = useSettingsNamespace(
|
const { value: gamepadSettings, save: saveGamepadSettings } = useSettingsNamespace(
|
||||||
'gamepad',
|
'gamepad',
|
||||||
GAMEPAD_SETTINGS_DEFAULTS,
|
GAMEPAD_SETTINGS_DEFAULTS,
|
||||||
@@ -245,7 +245,7 @@ export default function GamepadInputManager() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (outputs.buttons.driveMacro && handleButtonEdge('driveMacro', true)) {
|
if (outputs.buttons.driveMacro && handleButtonEdge('driveMacro', true)) {
|
||||||
setManualDockAssistActive(false);
|
dockAssist.exitAssist();
|
||||||
setMode('drive');
|
setMode('drive');
|
||||||
runMacro('drive-sequence');
|
runMacro('drive-sequence');
|
||||||
} else if (!outputs.buttons.driveMacro) {
|
} else if (!outputs.buttons.driveMacro) {
|
||||||
@@ -253,7 +253,7 @@ export default function GamepadInputManager() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (outputs.buttons.dockMacro && handleButtonEdge('dockMacro', true)) {
|
if (outputs.buttons.dockMacro && handleButtonEdge('dockMacro', true)) {
|
||||||
toggleManualDockAssist();
|
dockAssist.toggleAssist();
|
||||||
} else if (!outputs.buttons.dockMacro) {
|
} else if (!outputs.buttons.dockMacro) {
|
||||||
handleButtonEdge('dockMacro', false);
|
handleButtonEdge('dockMacro', false);
|
||||||
}
|
}
|
||||||
@@ -290,12 +290,11 @@ export default function GamepadInputManager() {
|
|||||||
handleCameraAxis,
|
handleCameraAxis,
|
||||||
registerInputState,
|
registerInputState,
|
||||||
runMacro,
|
runMacro,
|
||||||
setManualDockAssistActive,
|
|
||||||
setAuxMotors,
|
setAuxMotors,
|
||||||
setDriveVector,
|
setDriveVector,
|
||||||
setMode,
|
setMode,
|
||||||
toggleManualDockAssist,
|
|
||||||
toggleNightVision,
|
toggleNightVision,
|
||||||
|
dockAssist,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { isKeyboardCaptureLocked } from './keyboardCaptureLock.js';
|
|||||||
import { isTextInputElement } from './inputFocusUtils.js';
|
import { isTextInputElement } from './inputFocusUtils.js';
|
||||||
import { useSettingsNamespace } from '../../settings/index.js';
|
import { useSettingsNamespace } from '../../settings/index.js';
|
||||||
import { INPUT_SETTINGS_DEFAULTS } from '../../settings/namespaces.js';
|
import { INPUT_SETTINGS_DEFAULTS } from '../../settings/namespaces.js';
|
||||||
|
import { useManualDockAssist } from '../../features/manualDockAssist/useManualDockAssist.js';
|
||||||
import {
|
import {
|
||||||
SONG_DEFAULT_DURATION,
|
SONG_DEFAULT_DURATION,
|
||||||
SONG_DEFAULT_NOTE,
|
SONG_DEFAULT_NOTE,
|
||||||
@@ -122,8 +123,6 @@ export default function KeyboardInputManager() {
|
|||||||
state,
|
state,
|
||||||
actions: {
|
actions: {
|
||||||
setMode,
|
setMode,
|
||||||
setManualDockAssistActive,
|
|
||||||
toggleManualDockAssist,
|
|
||||||
setDriveVector,
|
setDriveVector,
|
||||||
setAuxMotors,
|
setAuxMotors,
|
||||||
nudgeServo,
|
nudgeServo,
|
||||||
@@ -139,6 +138,7 @@ export default function KeyboardInputManager() {
|
|||||||
},
|
},
|
||||||
} = useControlSystem();
|
} = useControlSystem();
|
||||||
const homeAssistant = useSessionSelector((state) => state.session?.homeAssistant || null);
|
const homeAssistant = useSessionSelector((state) => state.session?.homeAssistant || null);
|
||||||
|
const dockAssist = useManualDockAssist();
|
||||||
const { homeAssistantSetState } = useSessionActions();
|
const { homeAssistantSetState } = useSessionActions();
|
||||||
const { focusChat, blurChat, isChatFocused } = useChat();
|
const { focusChat, blurChat, isChatFocused } = useChat();
|
||||||
const { value: inputSettings } = useSettingsNamespace('inputs', INPUT_SETTINGS_DEFAULTS);
|
const { value: inputSettings } = useSettingsNamespace('inputs', INPUT_SETTINGS_DEFAULTS);
|
||||||
@@ -374,11 +374,11 @@ export default function KeyboardInputManager() {
|
|||||||
|
|
||||||
if (newlyPressed.length > 0) {
|
if (newlyPressed.length > 0) {
|
||||||
if (newlyPressed.some((token) => keymap.driveMacro?.has(token))) {
|
if (newlyPressed.some((token) => keymap.driveMacro?.has(token))) {
|
||||||
setManualDockAssistActive(false);
|
dockAssist.exitAssist();
|
||||||
setMode('drive');
|
setMode('drive');
|
||||||
runMacro('drive-sequence');
|
runMacro('drive-sequence');
|
||||||
} else if (newlyPressed.some((token) => keymap.dockMacro?.has(token))) {
|
} else if (newlyPressed.some((token) => keymap.dockMacro?.has(token))) {
|
||||||
toggleManualDockAssist();
|
dockAssist.toggleAssist();
|
||||||
} else if (newlyPressed.some((token) => keymap.nightVisionToggle?.has(token))) {
|
} else if (newlyPressed.some((token) => keymap.nightVisionToggle?.has(token))) {
|
||||||
toggleNightVision();
|
toggleNightVision();
|
||||||
} else if (newlyPressed.some((token) => keymap.hornHonk?.has(token))) {
|
} else if (newlyPressed.some((token) => keymap.hornHonk?.has(token))) {
|
||||||
@@ -443,15 +443,14 @@ export default function KeyboardInputManager() {
|
|||||||
keymap.micPtt,
|
keymap.micPtt,
|
||||||
resetAll,
|
resetAll,
|
||||||
runMacro,
|
runMacro,
|
||||||
setManualDockAssistActive,
|
|
||||||
setMicPttActive,
|
setMicPttActive,
|
||||||
setMode,
|
setMode,
|
||||||
stopAllMotion,
|
stopAllMotion,
|
||||||
stopSongLoop,
|
stopSongLoop,
|
||||||
startHorn,
|
startHorn,
|
||||||
stopHorn,
|
stopHorn,
|
||||||
toggleManualDockAssist,
|
|
||||||
triggerHomeAssistantCycle,
|
triggerHomeAssistantCycle,
|
||||||
|
dockAssist,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const latestResetAllRef = useRef(resetAll);
|
const latestResetAllRef = useRef(resetAll);
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
|
import { useControlSystem } from '../../controls/index.js';
|
||||||
|
import { useTelemetryFrame } from '../../context/TelemetryContext.jsx';
|
||||||
|
|
||||||
|
export function useManualDockAssist(options = {}) {
|
||||||
|
const { manageLifecycle = false } = options;
|
||||||
|
const {
|
||||||
|
state: { roverId, manualDockAssist },
|
||||||
|
actions,
|
||||||
|
} = useControlSystem();
|
||||||
|
const frame = useTelemetryFrame(roverId);
|
||||||
|
const sensors = frame?.sensors || {};
|
||||||
|
const chargingLabel = sensors?.chargingState?.label || '';
|
||||||
|
const docked = Boolean(sensors?.chargingSources?.homeBase);
|
||||||
|
const charging = docked && chargingLabel.toLowerCase() !== 'not charging' && chargingLabel !== '';
|
||||||
|
const active = Boolean(manualDockAssist?.active);
|
||||||
|
const wasDockedRef = useRef(false);
|
||||||
|
|
||||||
|
const enterAssist = useCallback(() => {
|
||||||
|
actions.setManualDockAssistActive(true);
|
||||||
|
}, [actions]);
|
||||||
|
|
||||||
|
const exitAssist = useCallback(() => {
|
||||||
|
actions.setManualDockAssistActive(false);
|
||||||
|
}, [actions]);
|
||||||
|
|
||||||
|
const toggleAssist = useCallback(() => {
|
||||||
|
actions.toggleManualDockAssist();
|
||||||
|
}, [actions]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!manageLifecycle) return;
|
||||||
|
const justDocked = docked && !wasDockedRef.current;
|
||||||
|
if (active && justDocked) {
|
||||||
|
actions.sendSong([{ note: 84, duration: 6 }], { slot: 1 });
|
||||||
|
}
|
||||||
|
wasDockedRef.current = docked;
|
||||||
|
}, [actions, active, docked, manageLifecycle]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!manageLifecycle || !active || !charging) return;
|
||||||
|
exitAssist();
|
||||||
|
}, [active, charging, exitAssist, manageLifecycle]);
|
||||||
|
|
||||||
|
const statusLabel = charging ? 'Docked and charging' : docked ? 'Docked' : 'Docking assist active';
|
||||||
|
const statusTone = charging ? 'good' : docked ? 'warn' : 'active';
|
||||||
|
const visible = active || docked;
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
active,
|
||||||
|
docked,
|
||||||
|
charging,
|
||||||
|
cameraLocked: active,
|
||||||
|
visible,
|
||||||
|
statusLabel,
|
||||||
|
statusTone,
|
||||||
|
enterAssist,
|
||||||
|
exitAssist,
|
||||||
|
toggleAssist,
|
||||||
|
}),
|
||||||
|
[active, charging, docked, enterAssist, exitAssist, statusLabel, statusTone, toggleAssist, visible],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user