mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
update darkness reward
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
You are The Overseer of the rovers.
|
You are The Overseer of the rovers. You are observing and studying them. You cannot control them.
|
||||||
|
|
||||||
Output contract:
|
Output contract:
|
||||||
- Output must be either SKIP if you want to stay silent, or a message if you want to speak..
|
- Output must be either SKIP if you want to stay silent, or a message if you want to speak.
|
||||||
- No markdown.
|
- No markdown.
|
||||||
|
|
||||||
Key legend:
|
Key legend:
|
||||||
|
|||||||
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-CZe46TcG.js"></script>
|
<script type="module" crossorigin src="/assets/index-CCaG2_WI.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-C3BucANX.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-C3BucANX.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,12 +1,41 @@
|
|||||||
const DURATION_MS = 60 * 1000;
|
const DURATION_MS = 2 * 60 * 1000;
|
||||||
|
const LIGHT_ENFORCE_TICK_MS = 3000;
|
||||||
|
|
||||||
let activeTimer = null;
|
let activeTimer = null;
|
||||||
|
let enforceLightsTimer = null;
|
||||||
|
let nightVisionLockUntil = 0;
|
||||||
|
|
||||||
async function stopDarkness(ctx, effect = {}) {
|
function isNightVisionBlocked() {
|
||||||
|
return Date.now() < nightVisionLockUntil;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearTimers() {
|
||||||
if (activeTimer) {
|
if (activeTimer) {
|
||||||
clearTimeout(activeTimer);
|
clearTimeout(activeTimer);
|
||||||
activeTimer = null;
|
activeTimer = null;
|
||||||
}
|
}
|
||||||
|
if (enforceLightsTimer) {
|
||||||
|
clearInterval(enforceLightsTimer);
|
||||||
|
enforceLightsTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function forceAllLightsOff(ctx) {
|
||||||
|
const entities = ctx.getHomeAssistantEntities();
|
||||||
|
await Promise.all(
|
||||||
|
entities.map(async (entity) => {
|
||||||
|
try {
|
||||||
|
await ctx.setHomeAssistantEntityState(entity.id, 'off');
|
||||||
|
} catch (err) {
|
||||||
|
ctx.logger.warn('darkness force light off failed', { entityId: entity.id, error: err.message });
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stopDarkness(ctx, effect = {}) {
|
||||||
|
clearTimers();
|
||||||
|
nightVisionLockUntil = 0;
|
||||||
|
|
||||||
const prevLights = Array.isArray(effect.prevLights) ? effect.prevLights : [];
|
const prevLights = Array.isArray(effect.prevLights) ? effect.prevLights : [];
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
@@ -19,6 +48,23 @@ async function stopDarkness(ctx, effect = {}) {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const prevLockState = effect?.prevLightLockState;
|
||||||
|
if (prevLockState === 'on' || prevLockState === 'off') {
|
||||||
|
await ctx.setHomeAssistantLightsLockedOn(true, {
|
||||||
|
source: 'buttonbox:darknessRestore',
|
||||||
|
forceApply: true,
|
||||||
|
targetState: prevLockState,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await ctx.setHomeAssistantLightsLockedOn(false, {
|
||||||
|
source: 'buttonbox:darknessRestore',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
ctx.logger.warn('darkness restore light lock failed', { error: err.message });
|
||||||
|
}
|
||||||
|
|
||||||
const prevNightVision = effect.prevNightVision && typeof effect.prevNightVision === 'object'
|
const prevNightVision = effect.prevNightVision && typeof effect.prevNightVision === 'object'
|
||||||
? effect.prevNightVision
|
? effect.prevNightVision
|
||||||
: {};
|
: {};
|
||||||
@@ -36,14 +82,28 @@ async function stopDarkness(ctx, effect = {}) {
|
|||||||
ctx.clearEffect('darkness');
|
ctx.clearEffect('darkness');
|
||||||
}
|
}
|
||||||
|
|
||||||
function startDarkness(ctx, effect) {
|
async function startDarkness(ctx, effect) {
|
||||||
if (activeTimer) {
|
clearTimers();
|
||||||
clearTimeout(activeTimer);
|
|
||||||
activeTimer = null;
|
|
||||||
}
|
|
||||||
const endsAt = Number(effect.endsAt || Date.now() + DURATION_MS);
|
const endsAt = Number(effect.endsAt || Date.now() + DURATION_MS);
|
||||||
const remaining = Math.max(0, endsAt - Date.now());
|
const remaining = Math.max(0, endsAt - Date.now());
|
||||||
|
nightVisionLockUntil = endsAt;
|
||||||
|
try {
|
||||||
|
await ctx.setHomeAssistantLightsLockedOn(true, {
|
||||||
|
source: 'buttonbox:darkness',
|
||||||
|
forceApply: true,
|
||||||
|
targetState: 'off',
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
ctx.logger.warn('darkness set light lock failed', { error: err.message });
|
||||||
|
}
|
||||||
ctx.saveEffect('darkness', effect);
|
ctx.saveEffect('darkness', effect);
|
||||||
|
|
||||||
|
enforceLightsTimer = setInterval(() => {
|
||||||
|
forceAllLightsOff(ctx).catch((err) => {
|
||||||
|
ctx.logger.warn('darkness periodic light enforcement failed', { error: err.message });
|
||||||
|
});
|
||||||
|
}, LIGHT_ENFORCE_TICK_MS);
|
||||||
|
|
||||||
activeTimer = setTimeout(() => {
|
activeTimer = setTimeout(() => {
|
||||||
stopDarkness(ctx, effect).catch((err) => {
|
stopDarkness(ctx, effect).catch((err) => {
|
||||||
ctx.logger.warn('darkness stop failed', { error: err.message });
|
ctx.logger.warn('darkness stop failed', { error: err.message });
|
||||||
@@ -54,19 +114,12 @@ function startDarkness(ctx, effect) {
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
id: 'darkness',
|
id: 'darkness',
|
||||||
name: 'Darkness',
|
name: 'Darkness',
|
||||||
|
isNightVisionBlocked,
|
||||||
goal: 650,
|
goal: 650,
|
||||||
async run(ctx) {
|
async run(ctx) {
|
||||||
const entities = ctx.getHomeAssistantEntities();
|
const entities = ctx.getHomeAssistantEntities();
|
||||||
const prevLights = entities.map((entity) => ({ id: entity.id, state: entity.state === 'on' ? 'on' : 'off' }));
|
const prevLights = entities.map((entity) => ({ id: entity.id, state: entity.state === 'on' ? 'on' : 'off' }));
|
||||||
await Promise.all(
|
await forceAllLightsOff(ctx);
|
||||||
entities.map(async (entity) => {
|
|
||||||
try {
|
|
||||||
await ctx.setHomeAssistantEntityState(entity.id, 'off');
|
|
||||||
} catch (err) {
|
|
||||||
ctx.logger.warn('darkness light off failed', { entityId: entity.id, error: err.message });
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const prevNightVision = {};
|
const prevNightVision = {};
|
||||||
ctx.listOnlineRovers().forEach((rover) => {
|
ctx.listOnlineRovers().forEach((rover) => {
|
||||||
@@ -83,15 +136,17 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const effect = { endsAt: Date.now() + DURATION_MS, prevLights, prevNightVision };
|
const prevPolicy = ctx.getHomeAssistantLightPolicy?.() || null;
|
||||||
startDarkness(ctx, effect);
|
const prevLightLockState = prevPolicy?.lockState || (prevPolicy?.lockedOn ? 'on' : null);
|
||||||
ctx.sendAlert({ color: '#212121', title: 'Darkness', message: 'Darkness effect active for 60 seconds.' });
|
const effect = { endsAt: Date.now() + DURATION_MS, prevLights, prevNightVision, prevLightLockState };
|
||||||
|
await startDarkness(ctx, effect);
|
||||||
|
ctx.sendAlert({ color: '#212121', title: 'Darkness', message: 'Darkness effect active for 120 seconds.' });
|
||||||
},
|
},
|
||||||
async recover(ctx, effect) {
|
async recover(ctx, effect) {
|
||||||
if (!effect || Number(effect.endsAt || 0) <= Date.now()) {
|
if (!effect || Number(effect.endsAt || 0) <= Date.now()) {
|
||||||
await stopDarkness(ctx, effect || {});
|
await stopDarkness(ctx, effect || {});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
startDarkness(ctx, effect);
|
await startDarkness(ctx, effect);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const assignmentService = require('./assignmentService');
|
|||||||
const {
|
const {
|
||||||
getState: getHomeAssistantState,
|
getState: getHomeAssistantState,
|
||||||
setEntityState: setHomeAssistantEntityState,
|
setEntityState: setHomeAssistantEntityState,
|
||||||
|
setLightsLockedOn: setHomeAssistantLightsLockedOn,
|
||||||
} = require('./homeAssistantService');
|
} = require('./homeAssistantService');
|
||||||
const { getRequestIp, isLocalNetwork, normalizeIp } = require('../helpers/ipResolver');
|
const { getRequestIp, isLocalNetwork, normalizeIp } = require('../helpers/ipResolver');
|
||||||
|
|
||||||
@@ -175,7 +176,10 @@ function buildRewardContext() {
|
|||||||
const entities = getHomeAssistantState()?.entities;
|
const entities = getHomeAssistantState()?.entities;
|
||||||
return Array.isArray(entities) ? entities : [];
|
return Array.isArray(entities) ? entities : [];
|
||||||
},
|
},
|
||||||
|
getHomeAssistantLightPolicy: () => getHomeAssistantState()?.lightPolicy || null,
|
||||||
setHomeAssistantEntityState,
|
setHomeAssistantEntityState,
|
||||||
|
setHomeAssistantLightsLockedOn: (next, options = {}) =>
|
||||||
|
setHomeAssistantLightsLockedOn(next, options),
|
||||||
saveEffect: (effectId, payload = {}) => saveEffect(effectId, payload, { broadcast: false }),
|
saveEffect: (effectId, payload = {}) => saveEffect(effectId, payload, { broadcast: false }),
|
||||||
clearEffect: (effectId) => clearEffect(effectId, { broadcast: false }),
|
clearEffect: (effectId) => clearEffect(effectId, { broadcast: false }),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const io = require('../globals/io');
|
|||||||
const roverManager = require('./roverManager');
|
const roverManager = require('./roverManager');
|
||||||
const { isAdmin, isLockdownAdmin } = require('./roleService');
|
const { isAdmin, isLockdownAdmin } = require('./roleService');
|
||||||
const logger = require('../globals/logger').child('commandService');
|
const logger = require('../globals/logger').child('commandService');
|
||||||
|
const { isNightVisionBlocked } = require('../rewards/definitions/darkness');
|
||||||
|
|
||||||
const pendingCommands = new Map(); // id -> { roverId }
|
const pendingCommands = new Map(); // id -> { roverId }
|
||||||
const lastDriveActivity = new Map(); // roverId -> { ts, socketId, direction, speed, isAdmin }
|
const lastDriveActivity = new Map(); // roverId -> { ts, socketId, direction, speed, isAdmin }
|
||||||
@@ -70,6 +71,11 @@ io.on('connection', (socket) => {
|
|||||||
if (type === 'audioLevels') {
|
if (type === 'audioLevels') {
|
||||||
throw new Error('audioLevels command is service-managed');
|
throw new Error('audioLevels command is service-managed');
|
||||||
}
|
}
|
||||||
|
if (type === 'nightVision' && isNightVisionBlocked()) {
|
||||||
|
logger.info('Ignoring night vision command while darkness lock is active', { socketId: socket.id, roverId });
|
||||||
|
reply({ ignored: true, reason: 'darknessActive' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
const payload = data ? { ...data } : {};
|
const payload = data ? { ...data } : {};
|
||||||
const isRebootCommand = type === 'reboot';
|
const isRebootCommand = type === 'reboot';
|
||||||
const isSongCommand = type === 'song' || (type === 'raw' && isSongRawPayload(payload));
|
const isSongCommand = type === 'song' || (type === 'raw' && isSongRawPayload(payload));
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ let connection = null;
|
|||||||
let unsubscribeEntities = null;
|
let unsubscribeEntities = null;
|
||||||
let reconnectTimer = null;
|
let reconnectTimer = null;
|
||||||
let connected = false;
|
let connected = false;
|
||||||
let lightsLockedOn = false;
|
let lightsLockState = null; // null | 'on' | 'off'
|
||||||
let lightsIdleOffTimer = null;
|
let lightsIdleOffTimer = null;
|
||||||
let lightsIdleOffDeadline = null;
|
let lightsIdleOffDeadline = null;
|
||||||
|
|
||||||
@@ -215,7 +215,7 @@ function clearLightsIdleOffTimer() {
|
|||||||
function scheduleLightsIdleOffTimer() {
|
function scheduleLightsIdleOffTimer() {
|
||||||
if (!enabled) return;
|
if (!enabled) return;
|
||||||
if (getControllableEntityIds().length === 0) return;
|
if (getControllableEntityIds().length === 0) return;
|
||||||
if (lightsIdleOffTimer || lightsLockedOn || hasActiveDrivers()) {
|
if (lightsIdleOffTimer || lightsLockState != null || hasActiveDrivers()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lightsIdleOffDeadline = Date.now() + LIGHT_IDLE_OFF_MS;
|
lightsIdleOffDeadline = Date.now() + LIGHT_IDLE_OFF_MS;
|
||||||
@@ -238,7 +238,7 @@ function scheduleLightsIdleOffTimer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function evaluateLightAutomation() {
|
function evaluateLightAutomation() {
|
||||||
if (lightsLockedOn) {
|
if (lightsLockState != null) {
|
||||||
clearLightsIdleOffTimer();
|
clearLightsIdleOffTimer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -464,12 +464,14 @@ async function setLightColor(entityId, rgbColor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isLightControlLocked() {
|
function isLightControlLocked() {
|
||||||
return lightsLockedOn;
|
return lightsLockState != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLightPolicyState() {
|
function getLightPolicyState() {
|
||||||
return {
|
return {
|
||||||
lockedOn: lightsLockedOn,
|
locked: lightsLockState != null,
|
||||||
|
lockState: lightsLockState,
|
||||||
|
lockedOn: lightsLockState === 'on',
|
||||||
idleOffMs: LIGHT_IDLE_OFF_MS,
|
idleOffMs: LIGHT_IDLE_OFF_MS,
|
||||||
idleOffAt: lightsIdleOffDeadline,
|
idleOffAt: lightsIdleOffDeadline,
|
||||||
activeDrivers: getActiveDriverCount(),
|
activeDrivers: getActiveDriverCount(),
|
||||||
@@ -478,14 +480,16 @@ function getLightPolicyState() {
|
|||||||
|
|
||||||
async function setLightsLockedOn(nextValue, options = {}) {
|
async function setLightsLockedOn(nextValue, options = {}) {
|
||||||
const next = Boolean(nextValue);
|
const next = Boolean(nextValue);
|
||||||
|
const targetState = options?.targetState === 'off' ? 'off' : 'on';
|
||||||
const forceApply = Boolean(options.forceApply);
|
const forceApply = Boolean(options.forceApply);
|
||||||
const changed = lightsLockedOn !== next;
|
const nextLockState = next ? targetState : null;
|
||||||
lightsLockedOn = next;
|
const changed = lightsLockState !== nextLockState;
|
||||||
if (lightsLockedOn) {
|
lightsLockState = nextLockState;
|
||||||
|
if (lightsLockState != null) {
|
||||||
clearLightsIdleOffTimer();
|
clearLightsIdleOffTimer();
|
||||||
if (changed || forceApply) {
|
if (changed || forceApply) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
await setAllControllableEntitiesState('on');
|
await setAllControllableEntitiesState(lightsLockState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -493,16 +497,17 @@ async function setLightsLockedOn(nextValue, options = {}) {
|
|||||||
}
|
}
|
||||||
if (changed) {
|
if (changed) {
|
||||||
logger.info('Room lights lock state changed', {
|
logger.info('Room lights lock state changed', {
|
||||||
lockedOn: lightsLockedOn,
|
locked: lightsLockState != null,
|
||||||
|
lockState: lightsLockState,
|
||||||
source: options.source || 'unknown',
|
source: options.source || 'unknown',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
emitUpdate();
|
emitUpdate();
|
||||||
return lightsLockedOn;
|
return lightsLockState === 'on';
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleLightsLockedOn(options = {}) {
|
async function toggleLightsLockedOn(options = {}) {
|
||||||
return setLightsLockedOn(!lightsLockedOn, options);
|
return setLightsLockedOn(lightsLockState == null, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getState() {
|
function getState() {
|
||||||
@@ -532,7 +537,7 @@ turnEvents.on('queue', () => {
|
|||||||
|
|
||||||
modeEvents.on('change', (mode) => {
|
modeEvents.on('change', (mode) => {
|
||||||
if (mode === MODES.ADMIN || mode === MODES.LOCKDOWN) {
|
if (mode === MODES.ADMIN || mode === MODES.LOCKDOWN) {
|
||||||
if (lightsLockedOn) {
|
if (isLightControlLocked()) {
|
||||||
setLightsLockedOn(false, { source: 'modeGateReset' }).catch((err) => {
|
setLightsLockedOn(false, { source: 'modeGateReset' }).catch((err) => {
|
||||||
logger.warn('Failed to disable lights lock on mode change', err.message);
|
logger.warn('Failed to disable lights lock on mode change', err.message);
|
||||||
});
|
});
|
||||||
@@ -554,7 +559,7 @@ io.on('connection', (socket) => {
|
|||||||
return cb({ error: 'Insufficient permissions to control Home Assistant' });
|
return cb({ error: 'Insufficient permissions to control Home Assistant' });
|
||||||
}
|
}
|
||||||
if (isLightControlLocked()) {
|
if (isLightControlLocked()) {
|
||||||
return cb({ error: 'Room controls are locked on' });
|
return cb({ error: 'Room controls are locked' });
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (!entityId) throw new Error('entityId required');
|
if (!entityId) throw new Error('entityId required');
|
||||||
@@ -574,7 +579,7 @@ io.on('connection', (socket) => {
|
|||||||
return cb({ error: 'Insufficient permissions to control Home Assistant' });
|
return cb({ error: 'Insufficient permissions to control Home Assistant' });
|
||||||
}
|
}
|
||||||
if (isLightControlLocked()) {
|
if (isLightControlLocked()) {
|
||||||
return cb({ error: 'Room controls are locked on' });
|
return cb({ error: 'Room controls are locked' });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -595,7 +600,7 @@ io.on('connection', (socket) => {
|
|||||||
return cb({ error: 'Insufficient permissions to control Home Assistant' });
|
return cb({ error: 'Insufficient permissions to control Home Assistant' });
|
||||||
}
|
}
|
||||||
if (isLightControlLocked()) {
|
if (isLightControlLocked()) {
|
||||||
return cb({ error: 'Room controls are locked on' });
|
return cb({ error: 'Room controls are locked' });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -213,7 +213,8 @@ export default function HomeAssistantControls() {
|
|||||||
const ha = session?.homeAssistant;
|
const ha = session?.homeAssistant;
|
||||||
const entities = useMemo(() => ha?.entities || [], [ha?.entities]);
|
const entities = useMemo(() => ha?.entities || [], [ha?.entities]);
|
||||||
const lightPolicy = ha?.lightPolicy || null;
|
const lightPolicy = ha?.lightPolicy || null;
|
||||||
const controlsLocked = Boolean(lightPolicy?.lockedOn);
|
const controlsLocked = Boolean(lightPolicy?.locked || lightPolicy?.lockedOn);
|
||||||
|
const lockState = lightPolicy?.lockState || (lightPolicy?.lockedOn ? 'on' : null);
|
||||||
const onKeyLabel = formatKeyLabel(keymap?.homeAssistantOn?.[0]);
|
const onKeyLabel = formatKeyLabel(keymap?.homeAssistantOn?.[0]);
|
||||||
const offKeyLabel = formatKeyLabel(keymap?.homeAssistantOff?.[0]);
|
const offKeyLabel = formatKeyLabel(keymap?.homeAssistantOff?.[0]);
|
||||||
|
|
||||||
@@ -243,7 +244,7 @@ export default function HomeAssistantControls() {
|
|||||||
<div className="flex items-center gap-0.5">
|
<div className="flex items-center gap-0.5">
|
||||||
<p>Room Controls</p>
|
<p>Room Controls</p>
|
||||||
<span className="text-xs text-slate-500">{entities.length}</span>
|
<span className="text-xs text-slate-500">{entities.length}</span>
|
||||||
{controlsLocked ? <StatusBadge label="Locked On" tone="warn" /> : null}
|
{controlsLocked ? <StatusBadge label={lockState === 'off' ? 'Locked Off' : 'Locked On'} tone="warn" /> : null}
|
||||||
<div className="flex items-center gap-0.5 text-xs text-slate-300 background-black">
|
<div className="flex items-center gap-0.5 text-xs text-slate-300 background-black">
|
||||||
<span className="flex items-center gap-0.5">
|
<span className="flex items-center gap-0.5">
|
||||||
<span>On</span>
|
<span>On</span>
|
||||||
@@ -259,7 +260,9 @@ export default function HomeAssistantControls() {
|
|||||||
</header>
|
</header>
|
||||||
{controlsLocked ? (
|
{controlsLocked ? (
|
||||||
<p className="rounded border border-amber-600/60 bg-amber-900/40 px-1 py-0.5 text-xs text-amber-100">
|
<p className="rounded border border-amber-600/60 bg-amber-900/40 px-1 py-0.5 text-xs text-amber-100">
|
||||||
Lights are locked on. Room controls are disabled.
|
{lockState === 'off'
|
||||||
|
? 'Lights are locked off. Room controls are disabled.'
|
||||||
|
: 'Lights are locked on. Room controls are disabled.'}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="flex flex-wrap gap-0.5">
|
<div className="flex flex-wrap gap-0.5">
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ export default function KeyboardInputManager() {
|
|||||||
(targetState) => {
|
(targetState) => {
|
||||||
const ha = session?.homeAssistant;
|
const ha = session?.homeAssistant;
|
||||||
if (!ha?.enabled || !ha?.connected) return;
|
if (!ha?.enabled || !ha?.connected) return;
|
||||||
if (ha?.lightPolicy?.lockedOn) return;
|
if (ha?.lightPolicy?.locked || ha?.lightPolicy?.lockedOn) return;
|
||||||
const entities = ha.entities || [];
|
const entities = ha.entities || [];
|
||||||
const eligible = entities.filter(
|
const eligible = entities.filter(
|
||||||
(ent) =>
|
(ent) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user