horn button light up

This commit is contained in:
legop3
2026-01-31 15:37:46 -05:00
parent 7eeb9d13e4
commit 5916520b1a
9 changed files with 159 additions and 131 deletions
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-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" /> <meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
<title>Multi Roomba Rover</title> <title>Multi Roomba Rover</title>
<script type="module" crossorigin src="/assets/index-DfpLHSrv.js"></script> <script type="module" crossorigin src="/assets/index-CqhqXcs8.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CQCz6Dh4.css"> <link rel="stylesheet" crossorigin href="/assets/index-CQCz6Dh4.css">
</head> </head>
<body> <body>
+2 -1
View File
@@ -13,7 +13,7 @@ function formatDegrees(value) {
export default function CameraServoPanel() { export default function CameraServoPanel() {
const { const {
state: { roverId, camera, keymap }, state: { roverId, camera, keymap, horn },
pipeline, pipeline,
actions: { setServoAngle, nudgeServo, goServoHome, setNightVision, startHorn, stopHorn }, actions: { setServoAngle, nudgeServo, goServoHome, setNightVision, startHorn, stopHorn },
} = useControlSystem(); } = useControlSystem();
@@ -110,6 +110,7 @@ export default function CameraServoPanel() {
onStart={startHorn} onStart={startHorn}
onStop={stopHorn} onStop={stopHorn}
keyLabel={hornKey} keyLabel={hornKey}
active={horn?.active}
/> />
)} )}
{enabled && ( {enabled && (
+8 -2
View File
@@ -75,7 +75,7 @@ export default function ControlSummary() {
export function InlineCameraTilt({ keymap }) { export function InlineCameraTilt({ keymap }) {
const { const {
state: { roverId, camera }, state: { roverId, camera, horn },
pipeline, pipeline,
actions: { setServoAngle, setNightVision, startHorn, stopHorn }, actions: { setServoAngle, setNightVision, startHorn, stopHorn },
} = useControlSystem(); } = useControlSystem();
@@ -132,7 +132,13 @@ export function InlineCameraTilt({ keymap }) {
/> />
)} )}
{hornAvailable && ( {hornAvailable && (
<HornControl disabled={!roverId} onStart={startHorn} onStop={stopHorn} keyLabel={hornLabel} /> <HornControl
disabled={!roverId}
onStart={startHorn}
onStop={stopHorn}
keyLabel={hornLabel}
active={horn?.active}
/>
)} )}
{enabled && ( {enabled && (
<div className="space-y-0.5 px-1 py-1"> <div className="space-y-0.5 px-1 py-1">
+6 -4
View File
@@ -14,6 +14,7 @@ export default function HornControl({
onStart, onStart,
onStop, onStop,
keyLabel, keyLabel,
active,
className = '', className = '',
}) { }) {
const [pressed, setPressed] = useState(false); const [pressed, setPressed] = useState(false);
@@ -34,15 +35,16 @@ export default function HornControl({
} }
}, [hornSettings?.freqs, hornSettings?.waveform]); }, [hornSettings?.freqs, hornSettings?.waveform]);
const isActive = Boolean(active);
const buttonClasses = useMemo(() => { const buttonClasses = useMemo(() => {
const base = const base =
'group flex w-full flex-col gap-0.5 rounded-xl border-2 px-1 py-1.5 text-xs font-semibold'; 'group flex w-full flex-col gap-0.5 rounded-xl border-2 px-1 py-1.5 text-xs font-semibold';
const active = 'border-fuchsia-300/70 bg-fuchsia-700 text-fuchsia-50'; const active = 'border-fuchsia-300/70 bg-fuchsia-700 text-fuchsia-50';
const inactive = 'border-cyan-300/70 bg-cyan-900 text-cyan-50 hover:bg-cyan-800'; const inactive = 'border-cyan-300/70 bg-cyan-900 text-cyan-50 hover:bg-cyan-800';
return [base, pressed ? active : inactive, 'disabled:opacity-50', className] return [base, isActive ? active : inactive, 'disabled:opacity-50', className]
.filter(Boolean) .filter(Boolean)
.join(' '); .join(' ');
}, [className, pressed]); }, [className, isActive]);
const start = () => { const start = () => {
if (disabled) return; if (disabled) return;
@@ -120,10 +122,10 @@ export default function HornControl({
</span> </span>
<span <span
className={`rounded px-1 py-0.5 text-[0.65rem] font-semibold ${ className={`rounded px-1 py-0.5 text-[0.65rem] font-semibold ${
pressed ? 'bg-fuchsia-200 text-fuchsia-900' : 'bg-slate-800 text-slate-200' isActive ? 'bg-fuchsia-200 text-fuchsia-900' : 'bg-slate-800 text-slate-200'
}`} }`}
> >
{pressed ? 'Honk' : 'Hold'} {isActive ? 'Honk' : 'Hold'}
</span> </span>
</div> </div>
<div className="grid grid-cols-[minmax(0,1.2fr)_repeat(4,minmax(0,1fr))] items-center gap-0.5 text-[0.65rem]"> <div className="grid grid-cols-[minmax(0,1.2fr)_repeat(4,minmax(0,1fr))] items-center gap-0.5 text-[0.65rem]">
+2 -2
View File
@@ -134,7 +134,7 @@ function FloatingJoystick({ disabled, layout, radius, onMove, onStop }) {
function MobileJoystickPanel({ layout }) { function MobileJoystickPanel({ layout }) {
const { const {
state: { roverId, camera }, state: { roverId, camera, horn },
pipeline, pipeline,
actions: { setDriveVector, registerInputState, setServoAngle, setNightVision, startHorn, stopHorn }, actions: { setDriveVector, registerInputState, setServoAngle, setNightVision, startHorn, stopHorn },
} = useControlSystem(); } = useControlSystem();
@@ -226,7 +226,7 @@ function MobileJoystickPanel({ layout }) {
/> />
)} )}
{hornAvailable && ( {hornAvailable && (
<HornControl disabled={disabled} onStart={startHorn} onStop={stopHorn} /> <HornControl disabled={disabled} onStart={startHorn} onStop={stopHorn} active={horn?.active} />
)} )}
{cameraEnabled && ( {cameraEnabled && (
<div className="bg-zinc-950 p-0.5 text-xs"> <div className="bg-zinc-950 p-0.5 text-xs">
+4 -2
View File
@@ -365,13 +365,15 @@ export function ControlSystemProvider({ children }) {
const startHorn = useCallback(() => { const startHorn = useCallback(() => {
if (!pipeline.horn) return; if (!pipeline.horn) return;
pipeline.sendHorn({ action: 'start', ...normalizedHornSettings }); pipeline.sendHorn({ action: 'start', ...normalizedHornSettings });
dispatch({ type: 'control/set-horn-active', payload: true });
recordControlIntent(); recordControlIntent();
}, [normalizedHornSettings, pipeline, recordControlIntent]); }, [dispatch, normalizedHornSettings, pipeline, recordControlIntent]);
const stopHorn = useCallback(() => { const stopHorn = useCallback(() => {
if (!pipeline.horn) return; if (!pipeline.horn) return;
pipeline.sendHorn({ action: 'stop' }); pipeline.sendHorn({ action: 'stop' });
}, [pipeline]); dispatch({ type: 'control/set-horn-active', payload: false });
}, [dispatch, pipeline]);
const registerInputState = useCallback((source, data) => { const registerInputState = useCallback((source, data) => {
dispatch({ type: 'control/register-input-state', payload: { source, state: data } }); dispatch({ type: 'control/register-input-state', payload: { source, state: data } });
+17
View File
@@ -27,6 +27,12 @@ function createSongState() {
}; };
} }
function createHornState() {
return {
active: false,
};
}
export const initialControlState = { export const initialControlState = {
roverId: null, roverId: null,
mode: 'drive', mode: 'drive',
@@ -34,6 +40,7 @@ export const initialControlState = {
aux: createAuxState(), aux: createAuxState(),
camera: createCameraState(), camera: createCameraState(),
song: createSongState(), song: createSongState(),
horn: createHornState(),
lastControlIntentAt: 0, lastControlIntentAt: 0,
macros: DEFAULT_MACROS, macros: DEFAULT_MACROS,
keymap: DEFAULT_KEYMAP, keymap: DEFAULT_KEYMAP,
@@ -49,6 +56,7 @@ export function controlReducer(state, action) {
drive: action.payload ? state.drive : createDriveState(), drive: action.payload ? state.drive : createDriveState(),
aux: action.payload ? state.aux : createAuxState(), aux: action.payload ? state.aux : createAuxState(),
song: action.payload ? state.song : createSongState(), song: action.payload ? state.song : createSongState(),
horn: action.payload ? state.horn : createHornState(),
lastControlIntentAt: action.payload ? state.lastControlIntentAt : 0, lastControlIntentAt: action.payload ? state.lastControlIntentAt : 0,
}; };
case 'control/set-mode': case 'control/set-mode':
@@ -125,8 +133,17 @@ export function controlReducer(state, action) {
drive: createDriveState(), drive: createDriveState(),
aux: createAuxState(), aux: createAuxState(),
song: createSongState(), song: createSongState(),
horn: createHornState(),
lastControlIntentAt: 0, lastControlIntentAt: 0,
}; };
case 'control/set-horn-active':
return {
...state,
horn: {
...(state.horn || createHornState()),
active: Boolean(action.payload),
},
};
case 'control/record-intent': case 'control/record-intent':
return { return {
...state, ...state,