manual docking mode real life testing

This commit is contained in:
legop3
2026-05-17 13:32:21 -04:00
parent d326ab755a
commit 74e5187bad
12 changed files with 254 additions and 171 deletions
@@ -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
+1 -1
View File
@@ -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>
+23 -11
View File
@@ -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>
);
}
+45 -4
View File
@@ -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,
+2 -8
View File
@@ -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' }
],
},
];
+16
View File
@@ -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
View File
@@ -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.',
],
}
],