mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-15 17:12:59 -04:00
manual docking mode real life testing
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
- roombas are worn out, cant dock themselves anymore using seek dock
|
||||
- completely redesign the user facing docking process
|
||||
- get rid of the auto dock instructions
|
||||
- replace it with a flow for manual docking mode:
|
||||
- does a little explanation
|
||||
- line up your round front sensor with the sensor on the dock
|
||||
- maybe show a video or an image in the HUD to assist
|
||||
- plays a song on the roomba to indicate that "docking mode" has been entered even though its all client side.
|
||||
- forces the camera to look all the way down so people can see their round front sensor
|
||||
- limits movement speed to be very slow to help people be more precise
|
||||
- MAYBE try some computer vision stuff that runs server-side and guides people? sounds hard and slow and not worth it.
|
||||
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-title" content="Roomba Rover" />
|
||||
<title>Roomba Rover</title>
|
||||
<script type="module" crossorigin src="/assets/index-BGI3M6ER.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-C8fFfWJK.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-jcgqUKI2.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -68,7 +68,7 @@ function DockModal({ instructions, onConfirm, onCancel, pending }) {
|
||||
<div className="fixed inset-0 z-40 flex items-center justify-center bg-black/70 p-1">
|
||||
<div className="surface w-full max-w-md space-y-0.5 border border-indigo-700 bg-indigo-950/90 p-1 text-slate-100 shadow-2xl">
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-lg font-semibold text-indigo-50">Dock Rover</p>
|
||||
<p className="text-lg font-semibold text-indigo-50">Manual Docking Assist</p>
|
||||
<p className="text-sm text-slate-200">{instructions.summary}</p>
|
||||
<StepList steps={instructions.steps} tone="indigo" />
|
||||
</div>
|
||||
@@ -86,7 +86,7 @@ function DockModal({ instructions, onConfirm, onCancel, pending }) {
|
||||
onClick={onConfirm}
|
||||
className="bg-indigo-600 px-0.5 py-1 font-semibold text-indigo-50 transition hover:bg-indigo-500 disabled:opacity-50"
|
||||
>
|
||||
{pending ? 'Docking…' : 'Confirm Dock'}
|
||||
{pending ? 'Starting…' : 'Enter Assist'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -103,7 +103,7 @@ export default function DriveDockAction({
|
||||
}) {
|
||||
const isMobile = layout === 'mobile';
|
||||
const {
|
||||
state: { roverId, keymap },
|
||||
state: { roverId, keymap, manualDockAssist },
|
||||
actions,
|
||||
} = useControlSystem();
|
||||
const frame = useTelemetryFrame(roverId);
|
||||
@@ -112,6 +112,7 @@ export default function DriveDockAction({
|
||||
const [pending, setPending] = useState(null);
|
||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const manualAssistActive = Boolean(manualDockAssist?.active);
|
||||
|
||||
const driveDisabled = !roverId || pending !== null;
|
||||
const dockDisabled = !roverId || pending !== null;
|
||||
@@ -120,10 +121,16 @@ export default function DriveDockAction({
|
||||
const dockKeyLabel = formatKeyLabel(keymap?.dockMacro?.[0]);
|
||||
|
||||
const dockInstructions = {
|
||||
summary: 'You are about to trigger an automatic docking attempt! To have a successful dock:',
|
||||
steps: ['Line up the rover straight in front of the dock', 'Make sure the rover is about one foot or half a meter away from the dock', 'Press Confirm Dock to begin the docking process'],
|
||||
summary: 'This enables precision manual docking assist in the web UI.',
|
||||
steps: [
|
||||
'Line up the round front sensor on the rover with the sensor on the dock',
|
||||
'Camera will tilt all the way down so you can see the front sensor',
|
||||
'Drive speed will be limited for precise alignment',
|
||||
],
|
||||
};
|
||||
const dockButtonCaption = 'Click this button to initate auto-dock.';
|
||||
const dockButtonCaption = manualAssistActive
|
||||
? 'Manual docking assist is active: slow speed limit + camera down.'
|
||||
: 'Click to enter manual docking assist.';
|
||||
|
||||
const startDriveInstructions = {
|
||||
summary: 'You must enable driving mode before you can move the rover.',
|
||||
@@ -138,6 +145,7 @@ export default function DriveDockAction({
|
||||
if (!roverId || pending) return;
|
||||
setPending('drive');
|
||||
try {
|
||||
actions.setManualDockAssistActive(false);
|
||||
actions.setMode('drive');
|
||||
await actions.runMacro('drive-sequence');
|
||||
} catch (err) {
|
||||
@@ -153,6 +161,7 @@ export default function DriveDockAction({
|
||||
setShowModal(false);
|
||||
setPending('drive');
|
||||
try {
|
||||
actions.setManualDockAssistActive(false);
|
||||
actions.setMode('drive');
|
||||
await actions.runMacro('drive-sequence');
|
||||
} catch (err) {
|
||||
@@ -166,8 +175,7 @@ export default function DriveDockAction({
|
||||
if (!roverId || pending) return;
|
||||
setPending('dock');
|
||||
try {
|
||||
actions.setMode('dock');
|
||||
await actions.runMacro('seek-dock');
|
||||
actions.setManualDockAssistActive(true);
|
||||
} catch (err) {
|
||||
alert(err.message);
|
||||
} finally {
|
||||
@@ -179,6 +187,10 @@ export default function DriveDockAction({
|
||||
|
||||
const handleOpenDock = () => {
|
||||
if (dockDisabled) return;
|
||||
if (manualAssistActive) {
|
||||
actions.setManualDockAssistActive(false);
|
||||
return;
|
||||
}
|
||||
setShowModal(true);
|
||||
setConfirmOpen(true);
|
||||
};
|
||||
@@ -299,12 +311,12 @@ export default function DriveDockAction({
|
||||
>
|
||||
<div className="flex w-full flex-col items-center gap-0.25">
|
||||
<span className="text-base font-semibold text-indigo-50 md:text-lg">
|
||||
{compactDockedDriving ? 'Docked!' : 'Dock and Charge'}
|
||||
{compactDockedDriving ? 'Docked!' : manualAssistActive ? 'Exit Docking Assist' : 'Enter Docking Assist'}
|
||||
</span>
|
||||
{!isMobile && expanded ? (
|
||||
<div className="flex flex-wrap items-center justify-center gap-0.5">
|
||||
{dockKeyLabel ? <KeyPill label={dockKeyLabel} /> : null}
|
||||
<ActionPill label="Click to begin docking" tone="indigo" />
|
||||
<ActionPill label={manualAssistActive ? 'Click to exit assist' : 'Click to enable assist'} tone="indigo" />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -316,7 +328,7 @@ export default function DriveDockAction({
|
||||
<StatusRow label="Charge" value={chargeValue} tone={chargeTone} />
|
||||
</div>
|
||||
<div className="text-xs text-indigo-100/80">
|
||||
Line up straight, about 1 foot away, before docking.
|
||||
Align front sensor to dock sensor; use tiny movements.
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -30,7 +30,7 @@ function DesktopQuickstart({ keymap }) {
|
||||
<ControlRow label="Move slower" keyLabel={formatKeyLabel(keymap?.slowModifier?.[0])} />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-slate-200">3. When done, please dock your rover! Line up with the dock and press "Dock and Charge".</p>
|
||||
<p className="text-sm text-slate-200">3. When done, enter "Docking Assist" and line up the front sensor with the dock sensor.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -41,7 +41,7 @@ function MobileQuickstart() {
|
||||
<p>1. Press "Start Driving" put your rover into driving mode.</p>
|
||||
<p>2. Touch and hold in Joystick area to move.</p>
|
||||
<p>3. Use the other column for motor, horn, and camera controls.</p>
|
||||
<p>4. When done, please dock your rover! Line up with the dock and press "Dock and Charge".</p>
|
||||
<p>4. When done, enter "Docking Assist" and line up the front sensor with the dock sensor.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
HORN_HEAT_RESUME_THRESHOLD,
|
||||
HORN_HEAT_UP_PER_SEC,
|
||||
HORN_MAX_MS,
|
||||
MANUAL_DOCK_ASSIST_MAX_SPEED,
|
||||
SONG_DEFAULT_NOTE,
|
||||
} from './constants.js';
|
||||
import { canonicalizeKeyInput } from './keymapUtils.js';
|
||||
@@ -212,7 +213,23 @@ export function ControlSystemProvider({ children }) {
|
||||
|
||||
const setDriveVector = useCallback(
|
||||
(vector, meta = {}) => {
|
||||
const computed = computeDifferentialSpeeds(vector, meta.speedOptions);
|
||||
const speedOptions = { ...(meta.speedOptions || {}) };
|
||||
if (state.manualDockAssist?.active) {
|
||||
const capped = Math.max(1, Math.min(MANUAL_DOCK_ASSIST_MAX_SPEED, 500));
|
||||
speedOptions.maxSpeed = Math.min(
|
||||
typeof speedOptions.maxSpeed === 'number' ? speedOptions.maxSpeed : capped,
|
||||
capped,
|
||||
);
|
||||
speedOptions.baseSpeed = Math.min(
|
||||
typeof speedOptions.baseSpeed === 'number' ? speedOptions.baseSpeed : capped,
|
||||
capped,
|
||||
);
|
||||
speedOptions.boostSpeed = Math.min(
|
||||
typeof speedOptions.boostSpeed === 'number' ? speedOptions.boostSpeed : capped,
|
||||
capped,
|
||||
);
|
||||
}
|
||||
const computed = computeDifferentialSpeeds(vector, speedOptions);
|
||||
dispatch({
|
||||
type: 'control/update-drive',
|
||||
payload: { ...computed, source: meta.source ?? null },
|
||||
@@ -220,7 +237,7 @@ export function ControlSystemProvider({ children }) {
|
||||
recordControlIntent();
|
||||
pipeline.sendDriveDirect(computed.speeds);
|
||||
},
|
||||
[pipeline, recordControlIntent],
|
||||
[pipeline, recordControlIntent, state.manualDockAssist?.active],
|
||||
);
|
||||
|
||||
const setAuxMotors = useCallback(
|
||||
@@ -318,8 +335,6 @@ export function ControlSystemProvider({ children }) {
|
||||
steps: removeDriveSequenceBackoff(macro.steps),
|
||||
};
|
||||
}
|
||||
} else if (macroId === 'seek-dock') {
|
||||
recordControlIntent();
|
||||
}
|
||||
await pipeline.runMacroSteps(macroToRun);
|
||||
},
|
||||
@@ -509,6 +524,28 @@ export function ControlSystemProvider({ children }) {
|
||||
return () => clearInterval(interval);
|
||||
}, [dispatch, hornNeedsTick, HORN_HEAT_COOL_PER_SEC, HORN_HEAT_RESUME_THRESHOLD, HORN_HEAT_UP_PER_SEC]);
|
||||
|
||||
const setManualDockAssistActive = useCallback(
|
||||
(active) => {
|
||||
const nextActive = Boolean(active);
|
||||
const prevActive = Boolean(state.manualDockAssist?.active);
|
||||
if (prevActive === nextActive) return;
|
||||
dispatch({ type: 'control/set-manual-dock-assist', payload: nextActive });
|
||||
if (!nextActive) return;
|
||||
const minAngle =
|
||||
typeof pipeline.servoConfig?.minAngle === 'number'
|
||||
? pipeline.servoConfig.minAngle
|
||||
: -45;
|
||||
setServoAngle(minAngle);
|
||||
pipeline.sendSong([{ note: 76, duration: 10 }, { note: 83, duration: 10 }], { slot: 0 });
|
||||
recordControlIntent();
|
||||
},
|
||||
[pipeline, recordControlIntent, setServoAngle, state.manualDockAssist?.active],
|
||||
);
|
||||
|
||||
const toggleManualDockAssist = useCallback(() => {
|
||||
setManualDockAssistActive(!state.manualDockAssist?.active);
|
||||
}, [setManualDockAssistActive, state.manualDockAssist?.active]);
|
||||
|
||||
const registerInputState = useCallback((source, data) => {
|
||||
dispatch({ type: 'control/register-input-state', payload: { source, state: data } });
|
||||
}, []);
|
||||
@@ -539,6 +576,8 @@ export function ControlSystemProvider({ children }) {
|
||||
updateKeyBinding,
|
||||
resetKeyBindings,
|
||||
registerInputState,
|
||||
setManualDockAssistActive,
|
||||
toggleManualDockAssist,
|
||||
setSongNote,
|
||||
sendSong,
|
||||
startHorn,
|
||||
@@ -565,6 +604,8 @@ export function ControlSystemProvider({ children }) {
|
||||
updateKeyBinding,
|
||||
resetKeyBindings,
|
||||
registerInputState,
|
||||
setManualDockAssistActive,
|
||||
toggleManualDockAssist,
|
||||
setSongNote,
|
||||
sendSong,
|
||||
startHorn,
|
||||
|
||||
@@ -12,6 +12,8 @@ export const DRIVE_LIMITS = {
|
||||
boostSpeed: 400,
|
||||
};
|
||||
|
||||
export const MANUAL_DOCK_ASSIST_MAX_SPEED = 110;
|
||||
|
||||
export const COMMAND_DELAY_MS = 100;
|
||||
|
||||
export const SONG_NOTE_RANGE = [31, 127];
|
||||
@@ -78,12 +80,4 @@ export const DEFAULT_MACROS = [
|
||||
{ type: 'drive', speeds: { left: 0, right: 0 } },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'seek-dock',
|
||||
label: 'Dock',
|
||||
description: 'Send the seek dock command.',
|
||||
steps: [
|
||||
{ type: 'oi', command: 'dock' }
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -43,6 +43,12 @@ function createMicState() {
|
||||
};
|
||||
}
|
||||
|
||||
function createManualDockAssistState() {
|
||||
return {
|
||||
active: false,
|
||||
};
|
||||
}
|
||||
|
||||
export const initialControlState = {
|
||||
roverId: null,
|
||||
mode: 'drive',
|
||||
@@ -52,6 +58,7 @@ export const initialControlState = {
|
||||
song: createSongState(),
|
||||
horn: createHornState(),
|
||||
mic: createMicState(),
|
||||
manualDockAssist: createManualDockAssistState(),
|
||||
lastControlIntentAt: 0,
|
||||
macros: DEFAULT_MACROS,
|
||||
keymap: DEFAULT_KEYMAP,
|
||||
@@ -69,6 +76,7 @@ export function controlReducer(state, action) {
|
||||
song: action.payload ? state.song : createSongState(),
|
||||
horn: action.payload ? state.horn : createHornState(),
|
||||
mic: action.payload ? state.mic : createMicState(),
|
||||
manualDockAssist: action.payload ? state.manualDockAssist : createManualDockAssistState(),
|
||||
lastControlIntentAt: action.payload ? state.lastControlIntentAt : 0,
|
||||
};
|
||||
case 'control/set-mode':
|
||||
@@ -187,6 +195,14 @@ export function controlReducer(state, action) {
|
||||
pttActive: Boolean(action.payload),
|
||||
},
|
||||
};
|
||||
case 'control/set-manual-dock-assist':
|
||||
return {
|
||||
...state,
|
||||
manualDockAssist: {
|
||||
...(state.manualDockAssist || createManualDockAssistState()),
|
||||
active: Boolean(action.payload),
|
||||
},
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ export default function GamepadInputManager() {
|
||||
state,
|
||||
actions: {
|
||||
setMode,
|
||||
setManualDockAssistActive,
|
||||
toggleManualDockAssist,
|
||||
setDriveVector,
|
||||
setAuxMotors,
|
||||
setServoAngle,
|
||||
@@ -243,6 +245,7 @@ export default function GamepadInputManager() {
|
||||
}
|
||||
|
||||
if (outputs.buttons.driveMacro && handleButtonEdge('driveMacro', true)) {
|
||||
setManualDockAssistActive(false);
|
||||
setMode('drive');
|
||||
runMacro('drive-sequence');
|
||||
} else if (!outputs.buttons.driveMacro) {
|
||||
@@ -250,8 +253,7 @@ export default function GamepadInputManager() {
|
||||
}
|
||||
|
||||
if (outputs.buttons.dockMacro && handleButtonEdge('dockMacro', true)) {
|
||||
setMode('dock');
|
||||
runMacro('seek-dock');
|
||||
toggleManualDockAssist();
|
||||
} else if (!outputs.buttons.dockMacro) {
|
||||
handleButtonEdge('dockMacro', false);
|
||||
}
|
||||
@@ -288,9 +290,11 @@ export default function GamepadInputManager() {
|
||||
handleCameraAxis,
|
||||
registerInputState,
|
||||
runMacro,
|
||||
setManualDockAssistActive,
|
||||
setAuxMotors,
|
||||
setDriveVector,
|
||||
setMode,
|
||||
toggleManualDockAssist,
|
||||
toggleNightVision,
|
||||
]);
|
||||
|
||||
|
||||
@@ -122,6 +122,8 @@ export default function KeyboardInputManager() {
|
||||
state,
|
||||
actions: {
|
||||
setMode,
|
||||
setManualDockAssistActive,
|
||||
toggleManualDockAssist,
|
||||
setDriveVector,
|
||||
setAuxMotors,
|
||||
nudgeServo,
|
||||
@@ -372,11 +374,11 @@ export default function KeyboardInputManager() {
|
||||
|
||||
if (newlyPressed.length > 0) {
|
||||
if (newlyPressed.some((token) => keymap.driveMacro?.has(token))) {
|
||||
setManualDockAssistActive(false);
|
||||
setMode('drive');
|
||||
runMacro('drive-sequence');
|
||||
} else if (newlyPressed.some((token) => keymap.dockMacro?.has(token))) {
|
||||
setMode('dock');
|
||||
runMacro('seek-dock');
|
||||
toggleManualDockAssist();
|
||||
} else if (newlyPressed.some((token) => keymap.nightVisionToggle?.has(token))) {
|
||||
toggleNightVision();
|
||||
} else if (newlyPressed.some((token) => keymap.hornHonk?.has(token))) {
|
||||
@@ -441,12 +443,14 @@ export default function KeyboardInputManager() {
|
||||
keymap.micPtt,
|
||||
resetAll,
|
||||
runMacro,
|
||||
setManualDockAssistActive,
|
||||
setMicPttActive,
|
||||
setMode,
|
||||
stopAllMotion,
|
||||
stopSongLoop,
|
||||
startHorn,
|
||||
stopHorn,
|
||||
toggleManualDockAssist,
|
||||
triggerHomeAssistantCycle,
|
||||
]);
|
||||
|
||||
|
||||
+11
-10
@@ -27,9 +27,10 @@ export const HELP_CONTENT = {
|
||||
type: 'list',
|
||||
title: 'Docking the rover',
|
||||
items: [
|
||||
'Line up the rover to the dock, about a foot away, then:',
|
||||
{ segments: ['Press the "Dock and Charge" button onscreen, or press ', { action: 'dockMacro' }, ' on your keyboard to start docking.'] },
|
||||
'Wait for the rover to confirm it is docked and charging before leaving it unattended.',
|
||||
'If the rover shows "Docking in Progress", it is already auto-seeking the dock.',
|
||||
'To dock manually, enter Docking Assist from the drive panel.',
|
||||
{ segments: ['Press "Enter Docking Assist", or press ', { action: 'dockMacro' }, '.'] },
|
||||
'In assist mode, camera tilts down and driving speed is limited for precise alignment.',
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -56,7 +57,7 @@ export const HELP_CONTENT = {
|
||||
title: 'Rover modes & chat',
|
||||
items: [
|
||||
{ action: 'driveMacro', label: 'Drive macro' },
|
||||
{ action: 'dockMacro', label: 'Dock macro' },
|
||||
{ action: 'dockMacro', label: 'Docking assist toggle' },
|
||||
{ action: 'chatFocus', label: 'Chat focus' },
|
||||
],
|
||||
},
|
||||
@@ -133,9 +134,9 @@ export const HELP_CONTENT = {
|
||||
type: 'list',
|
||||
title: 'Docking the rover',
|
||||
items: [
|
||||
'Line up the rover to the dock, about a foot away, then:',
|
||||
{ segments: ['Press the "Dock and Charge" button onscreen to start docking.'] },
|
||||
'Wait for the rover to confirm it is docked and charging before leaving it unattended.',
|
||||
'If you see "Docking in Progress", the rover is currently auto-seeking the dock.',
|
||||
{ segments: ['For manual docking, press "Enter Docking Assist".'] },
|
||||
'Assist mode tilts camera down and limits speed for precise alignment.',
|
||||
],
|
||||
}
|
||||
],
|
||||
@@ -163,9 +164,9 @@ export const HELP_CONTENT = {
|
||||
type: 'list',
|
||||
title: 'Docking the rover',
|
||||
items: [
|
||||
'Line up the rover to the dock, about a foot away, then:',
|
||||
{ segments: ['Press the "Dock and Charge" or the "Dock" button onscreen to start docking.'] },
|
||||
'Wait for the rover to confirm it is docked and charging before leaving it unattended.',
|
||||
'If you see "Docking in Progress", the rover is currently auto-seeking the dock.',
|
||||
{ segments: ['For manual docking, press "Enter Docking Assist" (or "Dock" button).'] },
|
||||
'Assist mode tilts camera down and limits speed for precise alignment.',
|
||||
],
|
||||
}
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user