mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-17 10:00:46 -04:00
keyboard light controls
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="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-BgRdubNv.js"></script>
|
<script type="module" crossorigin src="/assets/index-BvGXjw27.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-aZHU9GG3.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-aZHU9GG3.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useSession } from '../context/SessionContext.jsx';
|
import { useSession } from '../context/SessionContext.jsx';
|
||||||
|
import { useControlSystem } from '../controls/index.js';
|
||||||
|
import { formatKeyLabel } from '../controls/keymapUtils.js';
|
||||||
|
|
||||||
function StatusBadge({ label, tone = 'muted' }) {
|
function StatusBadge({ label, tone = 'muted' }) {
|
||||||
const styles =
|
const styles =
|
||||||
@@ -50,9 +52,14 @@ function EntityRow({ entity, connected, onToggle }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function HomeAssistantControls() {
|
export default function HomeAssistantControls() {
|
||||||
|
const {
|
||||||
|
state: { keymap },
|
||||||
|
} = useControlSystem();
|
||||||
const { session, homeAssistantToggle } = useSession();
|
const { session, homeAssistantToggle } = useSession();
|
||||||
const ha = session?.homeAssistant;
|
const ha = session?.homeAssistant;
|
||||||
const entities = useMemo(() => ha?.entities || [], [ha?.entities]);
|
const entities = useMemo(() => ha?.entities || [], [ha?.entities]);
|
||||||
|
const onKeyLabel = formatKeyLabel(keymap?.homeAssistantOn?.[0]);
|
||||||
|
const offKeyLabel = formatKeyLabel(keymap?.homeAssistantOff?.[0]);
|
||||||
|
|
||||||
if (!ha?.enabled) {
|
if (!ha?.enabled) {
|
||||||
return (
|
return (
|
||||||
@@ -80,6 +87,16 @@ export default function HomeAssistantControls() {
|
|||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<p>Light Controls</p>
|
<p>Light Controls</p>
|
||||||
<span className="text-xs text-slate-500">{entities.length}</span>
|
<span className="text-xs text-slate-500">{entities.length}</span>
|
||||||
|
<div className="flex items-center gap-1 text-xs text-slate-300 background-black">
|
||||||
|
<span className="flex items-center gap-0.5">
|
||||||
|
<span>On</span>
|
||||||
|
{onKeyLabel ? <KeyPill label={onKeyLabel} /> : null}
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center gap-0.5">
|
||||||
|
<span>Off</span>
|
||||||
|
{offKeyLabel ? <KeyPill label={offKeyLabel} /> : null}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<StatusBadge label={connected ? 'Connected' : 'Offline'} tone={connected ? 'success' : 'warn'} />
|
<StatusBadge label={connected ? 'Connected' : 'Offline'} tone={connected ? 'success' : 'warn'} />
|
||||||
</header>
|
</header>
|
||||||
@@ -96,3 +113,8 @@ export default function HomeAssistantControls() {
|
|||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function KeyPill({ label }) {
|
||||||
|
if (!label) return null;
|
||||||
|
return <span className="rounded border border-white/40 px-1 text-[0.7rem] text-white">{label}</span>;
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ const KEY_ACTIONS = [
|
|||||||
{ id: 'chatFocus', label: 'Toggle Chat', group: 'Chat' },
|
{ id: 'chatFocus', label: 'Toggle Chat', group: 'Chat' },
|
||||||
{ id: 'songNoteUp', label: 'Song Note Up', group: 'Audio' },
|
{ id: 'songNoteUp', label: 'Song Note Up', group: 'Audio' },
|
||||||
{ id: 'songNoteDown', label: 'Song Note Down', group: 'Audio' },
|
{ id: 'songNoteDown', label: 'Song Note Down', group: 'Audio' },
|
||||||
|
{ id: 'homeAssistantOn', label: 'Room Controls On (Cycle)', group: 'Room Controls' },
|
||||||
|
{ id: 'homeAssistantOff', label: 'Room Controls Off (Cycle)', group: 'Room Controls' },
|
||||||
];
|
];
|
||||||
|
|
||||||
function groupActions(actions) {
|
function groupActions(actions) {
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ export const DEFAULT_KEYMAP = {
|
|||||||
chatFocus: ['enter'],
|
chatFocus: ['enter'],
|
||||||
songNoteUp: ['ArrowUp'],
|
songNoteUp: ['ArrowUp'],
|
||||||
songNoteDown: ['ArrowDown'],
|
songNoteDown: ['ArrowDown'],
|
||||||
|
homeAssistantOn: ['ArrowRight'],
|
||||||
|
homeAssistantOff: ['ArrowLeft'],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_MACROS = [
|
export const DEFAULT_MACROS = [
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
import { useControlSystem } from '../ControlContext.jsx';
|
import { useControlSystem } from '../ControlContext.jsx';
|
||||||
import { useChat } from '../../context/ChatContext.jsx';
|
import { useChat } from '../../context/ChatContext.jsx';
|
||||||
|
import { useSession } from '../../context/SessionContext.jsx';
|
||||||
import { normalizeKeymapEntries, tokensForEvent } from '../keymapUtils.js';
|
import { normalizeKeymapEntries, tokensForEvent } from '../keymapUtils.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';
|
||||||
@@ -136,6 +137,7 @@ export default function KeyboardInputManager() {
|
|||||||
sendSong,
|
sendSong,
|
||||||
},
|
},
|
||||||
} = useControlSystem();
|
} = useControlSystem();
|
||||||
|
const { session, homeAssistantSetState } = useSession();
|
||||||
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);
|
||||||
const keymap = useMemo(() => normalizeKeymapEntries(state.keymap), [state.keymap]);
|
const keymap = useMemo(() => normalizeKeymapEntries(state.keymap), [state.keymap]);
|
||||||
@@ -308,6 +310,38 @@ export default function KeyboardInputManager() {
|
|||||||
registerInputState(SOURCE, { keys: [], vector: ZERO_VECTOR, aux: ZERO_AUX });
|
registerInputState(SOURCE, { keys: [], vector: ZERO_VECTOR, aux: ZERO_AUX });
|
||||||
}, [registerInputState, stopAllMotion, stopServoLoop, stopSongLoop]);
|
}, [registerInputState, stopAllMotion, stopServoLoop, stopSongLoop]);
|
||||||
|
|
||||||
|
const triggerHomeAssistantCycle = useCallback(
|
||||||
|
(targetState) => {
|
||||||
|
const ha = session?.homeAssistant;
|
||||||
|
if (!ha?.enabled || !ha?.connected) return;
|
||||||
|
const entities = ha.entities || [];
|
||||||
|
const eligible = entities.filter(
|
||||||
|
(ent) =>
|
||||||
|
(ent.type === 'light' || ent.type === 'switch') &&
|
||||||
|
ent.available !== false &&
|
||||||
|
ent.state !== 'unavailable',
|
||||||
|
);
|
||||||
|
if (eligible.length === 0) return;
|
||||||
|
if (targetState === 'on') {
|
||||||
|
const next = eligible.find((ent) => ent.state !== 'on');
|
||||||
|
if (next) {
|
||||||
|
homeAssistantSetState(next.id, 'on').catch(() => {});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (targetState === 'off') {
|
||||||
|
for (let idx = eligible.length - 1; idx >= 0; idx -= 1) {
|
||||||
|
const ent = eligible[idx];
|
||||||
|
if (ent.state === 'on') {
|
||||||
|
homeAssistantSetState(ent.id, 'off').catch(() => {});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[homeAssistantSetState, session?.homeAssistant],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleKeyDown(event) {
|
function handleKeyDown(event) {
|
||||||
if (shouldIgnoreEvent(event)) return;
|
if (shouldIgnoreEvent(event)) return;
|
||||||
@@ -339,6 +373,10 @@ export default function KeyboardInputManager() {
|
|||||||
runMacro('seek-dock');
|
runMacro('seek-dock');
|
||||||
} 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.homeAssistantOn?.has(token))) {
|
||||||
|
triggerHomeAssistantCycle('on');
|
||||||
|
} else if (newlyPressed.some((token) => keymap.homeAssistantOff?.has(token))) {
|
||||||
|
triggerHomeAssistantCycle('off');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,6 +421,7 @@ export default function KeyboardInputManager() {
|
|||||||
setMode,
|
setMode,
|
||||||
stopAllMotion,
|
stopAllMotion,
|
||||||
stopSongLoop,
|
stopSongLoop,
|
||||||
|
triggerHomeAssistantCycle,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const latestResetAllRef = useRef(resetAll);
|
const latestResetAllRef = useRef(resetAll);
|
||||||
|
|||||||
@@ -88,6 +88,14 @@ export const HELP_CONTENT = {
|
|||||||
{ action: 'songNoteDown', label: 'Song note down' },
|
{ action: 'songNoteDown', label: 'Song note down' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'room-controls',
|
||||||
|
title: 'Room Controls',
|
||||||
|
items: [
|
||||||
|
{ action: 'homeAssistantOn', label: 'Next room control on' },
|
||||||
|
{ action: 'homeAssistantOff', label: 'Next room control off (reverse)' },
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user