mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 09:31:20 -04:00
green mode slop 1
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
// Reward Definition: Green Mode
|
||||
// Purpose: Enables the server-wide green theme and room effect for twenty minutes.
|
||||
// Scope: Owns button-box timing/recovery while delegating the actual mode to greenModeService.
|
||||
const DURATION_MS = 20 * 60 * 1000;
|
||||
|
||||
let activeTimer = null;
|
||||
let unsubscribeGreenMode = null;
|
||||
|
||||
function clearRuntimeWatchers() {
|
||||
if (activeTimer) {
|
||||
clearTimeout(activeTimer);
|
||||
activeTimer = null;
|
||||
}
|
||||
if (unsubscribeGreenMode) {
|
||||
unsubscribeGreenMode();
|
||||
unsubscribeGreenMode = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function stopGreenMode(ctx) {
|
||||
clearRuntimeWatchers();
|
||||
await ctx.setGreenMode(false, { source: 'buttonbox:greenModeExpired' });
|
||||
ctx.clearEffect('greenMode');
|
||||
}
|
||||
|
||||
async function startGreenMode(ctx, effect = {}) {
|
||||
clearRuntimeWatchers();
|
||||
const endsAt = Number(effect.endsAt || Date.now() + DURATION_MS);
|
||||
const remaining = Math.max(0, endsAt - Date.now());
|
||||
|
||||
if (remaining <= 0) {
|
||||
await stopGreenMode(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.setGreenMode(true, { source: 'buttonbox:greenMode' });
|
||||
ctx.saveEffect('greenMode', { endsAt });
|
||||
|
||||
/*
|
||||
Access-mode changes disable green mode through greenModeService. Watching
|
||||
that shared state transition lets the reward discard its persisted effect
|
||||
immediately, so a restart cannot accidentally revive a reward that was
|
||||
intentionally ended early.
|
||||
*/
|
||||
unsubscribeGreenMode = ctx.onGreenModeChange((enabled) => {
|
||||
if (enabled) return;
|
||||
clearRuntimeWatchers();
|
||||
ctx.clearEffect('greenMode');
|
||||
});
|
||||
|
||||
activeTimer = setTimeout(() => {
|
||||
stopGreenMode(ctx).catch((err) => {
|
||||
ctx.logger.warn('green mode reward stop failed', { error: err.message });
|
||||
});
|
||||
}, remaining);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
id: 'greenMode',
|
||||
name: 'Green mode',
|
||||
description: 'Makes the room and server green for 20 minutes.',
|
||||
goal: 5,
|
||||
async run(ctx) {
|
||||
await startGreenMode(ctx, { endsAt: Date.now() + DURATION_MS });
|
||||
},
|
||||
async recover(ctx, effect) {
|
||||
// Recovery must never manufacture a fresh twenty-minute window from a
|
||||
// missing or corrupt persisted deadline. Treat it as expired and clean up.
|
||||
if (!Number.isFinite(Number(effect?.endsAt))) {
|
||||
await stopGreenMode(ctx);
|
||||
return;
|
||||
}
|
||||
await startGreenMode(ctx, effect);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
// Green Mode Reward Tests
|
||||
// Purpose: Pins the five-press metadata and persisted timed-effect lifecycle.
|
||||
// Scope: Uses a small context double; greenModeService behavior is tested through its public contract.
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const reward = require('./greenMode');
|
||||
|
||||
function createContext() {
|
||||
const calls = [];
|
||||
let changeListener = null;
|
||||
return {
|
||||
calls,
|
||||
logger: { warn: () => {} },
|
||||
setGreenMode: async (enabled, options) => {
|
||||
calls.push({ type: 'set', enabled, source: options?.source });
|
||||
return enabled;
|
||||
},
|
||||
saveEffect: (id, payload) => calls.push({ type: 'save', id, payload }),
|
||||
clearEffect: (id) => calls.push({ type: 'clear', id }),
|
||||
onGreenModeChange: (listener) => {
|
||||
changeListener = listener;
|
||||
return () => {
|
||||
changeListener = null;
|
||||
};
|
||||
},
|
||||
emitGreenModeChange: (enabled) => changeListener?.(enabled),
|
||||
};
|
||||
}
|
||||
|
||||
test('green mode reward requires five presses and starts a persisted effect', async () => {
|
||||
const ctx = createContext();
|
||||
assert.equal(reward.goal, 5);
|
||||
|
||||
await reward.run(ctx);
|
||||
|
||||
assert.deepEqual(ctx.calls[0], { type: 'set', enabled: true, source: 'buttonbox:greenMode' });
|
||||
const saved = ctx.calls.find((call) => call.type === 'save');
|
||||
assert.equal(saved?.id, 'greenMode');
|
||||
assert.ok(saved?.payload?.endsAt > Date.now());
|
||||
|
||||
// Simulate an access-mode shutdown so the test also clears the reward's
|
||||
// twenty-minute timer instead of leaving background work in the test process.
|
||||
ctx.emitGreenModeChange(false);
|
||||
assert.ok(ctx.calls.some((call) => call.type === 'clear' && call.id === 'greenMode'));
|
||||
});
|
||||
|
||||
test('invalid recovery state is cleared instead of starting a new duration', async () => {
|
||||
const ctx = createContext();
|
||||
await reward.recover(ctx, {});
|
||||
|
||||
assert.deepEqual(ctx.calls[0], {
|
||||
type: 'set',
|
||||
enabled: false,
|
||||
source: 'buttonbox:greenModeExpired',
|
||||
});
|
||||
assert.ok(ctx.calls.some((call) => call.type === 'clear' && call.id === 'greenMode'));
|
||||
});
|
||||
@@ -10,6 +10,7 @@ const discordPingEveryone = require('./definitions/discordPingEveryone');
|
||||
const modeJam = require('./definitions/modeJam');
|
||||
const assignmentRoulette = require('./definitions/assignmentRoulette');
|
||||
const chatSpam = require('./definitions/chatSpam');
|
||||
const greenMode = require('./definitions/greenMode');
|
||||
|
||||
const orderedRewards = [
|
||||
dockPanic,
|
||||
@@ -22,6 +23,7 @@ const orderedRewards = [
|
||||
modeJam,
|
||||
assignmentRoulette,
|
||||
chatSpam,
|
||||
greenMode,
|
||||
];
|
||||
|
||||
const rewardById = new Map(orderedRewards.map((reward, idx) => [reward.id, { ...reward, number: idx + 1 }]));
|
||||
|
||||
@@ -22,6 +22,9 @@ function createButtonBoxCore(deps) {
|
||||
getHomeAssistantState,
|
||||
setHomeAssistantEntityState,
|
||||
setHomeAssistantLightsLockedOn,
|
||||
setGreenMode,
|
||||
isGreenModeEnabled,
|
||||
onGreenModeChange,
|
||||
store,
|
||||
} = deps;
|
||||
|
||||
@@ -215,6 +218,11 @@ function createButtonBoxCore(deps) {
|
||||
setHomeAssistantEntityState(entityId, state, { source: 'buttonBoxReward' }),
|
||||
setHomeAssistantLightsLockedOn: (next, options = {}) =>
|
||||
setHomeAssistantLightsLockedOn(next, options),
|
||||
// Rewards receive the standalone feature boundary rather than reaching
|
||||
// into Home Assistant or duplicating green-mode state and alerts.
|
||||
setGreenMode: (next, options = {}) => setGreenMode(next, options),
|
||||
isGreenModeEnabled: () => isGreenModeEnabled(),
|
||||
onGreenModeChange: (listener) => onGreenModeChange(listener),
|
||||
saveEffect: (effectId, payload = {}) => saveEffect(effectId, payload, { broadcast: false }),
|
||||
clearEffect: (effectId) => clearEffect(effectId, { broadcast: false }),
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ const { isLocalNetwork, normalizeIp } = require('../../helpers/ipResolver');
|
||||
const { createButtonBoxStore } = require('./store');
|
||||
const { createButtonBoxCore } = require('./core');
|
||||
const { registerButtonBoxRoute } = require('./httpRoute');
|
||||
const greenModeService = require('../greenModeService');
|
||||
|
||||
const DATA_DIR = resolveDataDir();
|
||||
const STORE_PATH = resolveDataPath('buttonbox-state.json');
|
||||
@@ -61,6 +62,14 @@ const core = createButtonBoxCore({
|
||||
getHomeAssistantState,
|
||||
setHomeAssistantEntityState,
|
||||
setHomeAssistantLightsLockedOn,
|
||||
setGreenMode: greenModeService.setEnabled,
|
||||
isGreenModeEnabled: greenModeService.isEnabled,
|
||||
// Return an explicit cleanup function so timed rewards can stop observing
|
||||
// the global service when they expire, rerun, or are recovered.
|
||||
onGreenModeChange: (listener) => {
|
||||
greenModeService.greenModeEvents.on('change', listener);
|
||||
return () => greenModeService.greenModeEvents.off('change', listener);
|
||||
},
|
||||
store,
|
||||
});
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ const { getNickname } = require('../nicknameService');
|
||||
const { getGlobalObjective, setGlobalObjective, clearGlobalObjective } = require('../globalObjectiveService');
|
||||
const { getAdminReason, setAdminReason, clearAdminReason } = require('../adminReasonService');
|
||||
const homeAssistantService = require('../homeAssistantService');
|
||||
const greenModeService = require('../greenModeService');
|
||||
const liftService = require('../liftService');
|
||||
const neatoService = require('../neatoService');
|
||||
const { isFeatureEnabled } = require('../../helpers/features');
|
||||
@@ -174,6 +175,7 @@ async function runChatTextCommand({ text, socket, sendSystemMessage }) {
|
||||
// lights lock/unlock` from becoming transport-specific, and it preserves
|
||||
// the existing session update path for all connected browsers.
|
||||
homeAssistantService,
|
||||
greenModeService,
|
||||
liftService,
|
||||
neatoService,
|
||||
isFeatureEnabled,
|
||||
|
||||
@@ -63,6 +63,7 @@ const { subscribe } = require('../eventBus');
|
||||
const { createPresenceManager } = require('./presence');
|
||||
const { createChannelIO } = require('./channelIO');
|
||||
const { createCommandHandlers } = require('../operatorCommandService');
|
||||
const greenModeService = require('../greenModeService');
|
||||
const { createDiscordTransportHandlers, createDiscordCommandRequest } = require('./commandAdapter');
|
||||
const { createIntegrations } = require('./integrations');
|
||||
const { createFleetDailyReports } = require('./fleetDailyReports');
|
||||
@@ -240,6 +241,7 @@ const commandDependencies = {
|
||||
// service into the shared command router keeps Discord and mirrored web-chat
|
||||
// command behavior aligned without duplicating Home Assistant calls here.
|
||||
homeAssistantService,
|
||||
greenModeService,
|
||||
liftService,
|
||||
neatoService,
|
||||
isFeatureEnabled,
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
// Green Mode Service
|
||||
// Purpose: Owns the temporary server-wide green visual mode and its tiny light workflow.
|
||||
// Scope: Composes existing Home Assistant operations; it does not add policy to that service.
|
||||
const EventEmitter = require('events');
|
||||
const logger = require('../../globals/logger').child('greenModeService');
|
||||
const { sendAlert } = require('../alertService');
|
||||
const homeAssistantService = require('../homeAssistantService');
|
||||
const { modeEvents } = require('../modeManager');
|
||||
|
||||
const GREEN_MODE_COLOR = '#00ff00';
|
||||
const greenModeEvents = new EventEmitter();
|
||||
let enabled = false;
|
||||
|
||||
function isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
async function setEnabled(nextValue, options = {}) {
|
||||
const next = Boolean(nextValue);
|
||||
if (enabled === next) return enabled;
|
||||
|
||||
if (next) {
|
||||
/*
|
||||
Lock first because the existing locked-on transition sets lights white.
|
||||
Recoloring RGB lights afterward leaves them green while retaining the
|
||||
established room-control lock, idle protection, and laser safety rules.
|
||||
*/
|
||||
await homeAssistantService.setLightsLockedOn(true, {
|
||||
source: String(options?.source || 'greenMode:enable'),
|
||||
});
|
||||
|
||||
const entities = homeAssistantService.getState()?.entities || [];
|
||||
/*
|
||||
RGB-capable lights become the requested solid green. Every other
|
||||
configured room control, including white-only bulbs and switches, is
|
||||
explicitly turned off so the physical room has one unambiguous effect.
|
||||
These remain generic Home Assistant calls; that service does not know
|
||||
that the operations belong to green mode.
|
||||
*/
|
||||
const results = await Promise.allSettled(
|
||||
entities.map((entity) => (
|
||||
entity?.supportsColor
|
||||
? homeAssistantService.setLightColor(entity.id, GREEN_MODE_COLOR)
|
||||
: homeAssistantService.setEntityState(entity.id, 'off', {
|
||||
source: 'greenMode:non-rgb-off',
|
||||
})
|
||||
)),
|
||||
);
|
||||
const failures = results
|
||||
.map((result, index) => ({ result, entityId: entities[index].id }))
|
||||
.filter(({ result }) => result.status === 'rejected')
|
||||
.map(({ result, entityId }) => ({ entityId, error: result.reason?.message || 'unknown error' }));
|
||||
|
||||
if (failures.length) {
|
||||
logger.warn('Some room controls failed to enter green mode', { failures });
|
||||
}
|
||||
} else {
|
||||
// Disabling the visual mode simply releases the lock it created. Bulb
|
||||
// colors remain untouched, matching the existing one-shot light behavior.
|
||||
await homeAssistantService.setLightsLockedOn(false, {
|
||||
source: String(options?.source || 'greenMode:disable'),
|
||||
});
|
||||
}
|
||||
|
||||
enabled = next;
|
||||
logger.info('Green mode changed', {
|
||||
enabled,
|
||||
source: options?.source || 'unknown',
|
||||
});
|
||||
// Emit one shared server alert for every completed transition. Automatic
|
||||
// access-mode shutdown uses this same function, so clients also receive the
|
||||
// inactive notice when green mode ends without an explicit chat command.
|
||||
sendAlert({
|
||||
color: GREEN_MODE_COLOR,
|
||||
title: 'Green mode',
|
||||
message: enabled ? 'Green mode is active.' : 'Green mode is inactive.',
|
||||
});
|
||||
greenModeEvents.emit('change', enabled);
|
||||
return enabled;
|
||||
}
|
||||
|
||||
modeEvents.on('change', () => {
|
||||
if (!enabled) return;
|
||||
setEnabled(false, { source: 'modeGateReset' }).catch((err) => {
|
||||
logger.warn('Failed to disable green mode on access-mode change', err.message);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
isEnabled,
|
||||
setEnabled,
|
||||
greenModeEvents,
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
// Operator Green Command
|
||||
// Purpose: Toggles the intentionally silly server-wide green visual and room-light mode.
|
||||
// Scope: Keeps command presentation here while Home Assistant owns the runtime policy.
|
||||
const { getCommandConfig } = require('../../operatorCommandService/config');
|
||||
|
||||
function createGreenCommand({ greenModeService, sanitizeMentions, config }) {
|
||||
const { prefix: commandPrefix } = getCommandConfig(config);
|
||||
|
||||
return async function handleGreenCommand(message, tokens = []) {
|
||||
const action = String(tokens.shift() || '').trim().toLowerCase();
|
||||
if (action !== 'on' && action !== 'off') {
|
||||
await message.reply({
|
||||
content: `Invalid green command. Use \`${commandPrefix} green on\` or \`${commandPrefix} green off\`.`,
|
||||
allowedMentions: { parse: [], repliedUser: false },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const enabled = action === 'on';
|
||||
const result = await greenModeService.setEnabled(enabled, {
|
||||
source: `bot-command:green:${action}`,
|
||||
});
|
||||
await message.reply({
|
||||
content: sanitizeMentions(result ? 'Green mode enabled.' : 'Green mode disabled.'),
|
||||
allowedMentions: { parse: [], repliedUser: false },
|
||||
});
|
||||
} catch (err) {
|
||||
await message.reply({
|
||||
content: sanitizeMentions(`Failed to update green mode: ${err.message}`),
|
||||
allowedMentions: { parse: [], repliedUser: false },
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { createGreenCommand };
|
||||
@@ -10,6 +10,7 @@ const { createVerifyCommand } = require('./commands/verify');
|
||||
const { createDeterCommand } = require('./commands/deter');
|
||||
const { createPermissionsCommand } = require('./commands/permissions');
|
||||
const { createLightsCommand } = require('./commands/lights');
|
||||
const { createGreenCommand } = require('./commands/green');
|
||||
const { createKickCommand } = require('./commands/kick');
|
||||
const { createLiftCommand } = require('./commands/lift');
|
||||
const { createNeatoCommand } = require('./commands/neato');
|
||||
@@ -61,6 +62,7 @@ function createCommandHandlers(deps) {
|
||||
const handleBridgeCommand = transportHandlers.bridge;
|
||||
const handleTimeStatusCommand = transportHandlers.timeStatus;
|
||||
const handleLightsCommand = createLightsCommand(deps);
|
||||
const handleGreenCommand = createGreenCommand(deps);
|
||||
const handleKickCommand = createKickCommand(deps);
|
||||
const handleLiftCommand = createLiftCommand(deps);
|
||||
const handleNeatoCommand = createNeatoCommand(deps);
|
||||
@@ -108,7 +110,7 @@ function createCommandHandlers(deps) {
|
||||
// is included because its lock/unlock subcommands change room policy. Its
|
||||
// ordinary on/off/color actions are also intentionally restricted to a
|
||||
// lockdown admin while the entire server is in lockdown.
|
||||
const moderationActions = new Set(['lock', 'unlock', 'mode', 'goal', 'reason', 'verify', 'deter', 'permissions', 'lights', 'kick', 'lift', 'neato']);
|
||||
const moderationActions = new Set(['lock', 'unlock', 'mode', 'goal', 'reason', 'verify', 'deter', 'permissions', 'lights', 'green', 'kick', 'lift', 'neato']);
|
||||
const isAccessModeCommand = commandDefinition?.permission === 'access-mode';
|
||||
|
||||
// Feature commands are public activities while access is open or managed
|
||||
@@ -144,6 +146,8 @@ function createCommandHandlers(deps) {
|
||||
return handleBridgeCommand(request, tokens);
|
||||
case 'lights':
|
||||
return handleLightsCommand(request, tokens);
|
||||
case 'green':
|
||||
return handleGreenCommand(request, tokens);
|
||||
case 'kick':
|
||||
return handleKickCommand(request, rest);
|
||||
case 'lift':
|
||||
|
||||
@@ -30,6 +30,9 @@ function createRouter({ mode = MODES.OPEN, featureEnabled = true } = {}) {
|
||||
isFeatureEnabled: () => featureEnabled,
|
||||
sanitizeMentions: (text) => String(text || ''),
|
||||
homeAssistantService: { getLightPolicyState: () => ({}), setAllControllableEntitiesState: () => Promise.resolve() },
|
||||
// Green mode is admin-only at the dispatcher, so this double only needs to
|
||||
// prove that an authorized command reaches the small standalone service.
|
||||
greenModeService: { setEnabled: (enabled) => Promise.resolve(Boolean(enabled)) },
|
||||
liftService: null,
|
||||
neatoService: null,
|
||||
listVerifiedUsers: () => [],
|
||||
@@ -72,7 +75,7 @@ const admin = { id: 's1', userId: 'u-alice', label: 'alice', isAdmin: true, isLo
|
||||
|
||||
test('admin-only commands stay admin-only for a non-admin', async () => {
|
||||
const run = createRouter();
|
||||
for (const command of ['rs lock rover-1', 'rs unlock rover-1', 'rs mode open', 'rs kick alice', 'rs permissions list']) {
|
||||
for (const command of ['rs lock rover-1', 'rs unlock rover-1', 'rs mode open', 'rs green on', 'rs kick alice', 'rs permissions list']) {
|
||||
assert.match(await run(command, nonAdmin), ADMIN_DENIAL, `${command} must stay admin-only`);
|
||||
}
|
||||
});
|
||||
@@ -100,11 +103,22 @@ test('admin mode restricts access-mode feature commands', async () => {
|
||||
|
||||
test('lockdown still suspends the pre-existing moderation-sensitive commands', async () => {
|
||||
const run = createRouter({ mode: MODES.LOCKDOWN });
|
||||
for (const command of ['rs lock rover-1', 'rs mode open', 'rs lights on', 'rs goal', 'rs kick alice']) {
|
||||
for (const command of ['rs lock rover-1', 'rs mode open', 'rs lights on', 'rs green on', 'rs goal', 'rs kick alice']) {
|
||||
assert.match(await run(command, admin), LOCKDOWN_DENIAL, `${command} should stay lockdown-gated`);
|
||||
}
|
||||
});
|
||||
|
||||
test('admins can toggle green mode through the shared command path', async () => {
|
||||
const run = createRouter();
|
||||
assert.match(await run('rs green on', admin), /Green mode enabled/);
|
||||
assert.match(await run('rs green off', admin), /Green mode disabled/);
|
||||
});
|
||||
|
||||
test('green mode rejects unknown actions with focused usage guidance', async () => {
|
||||
const run = createRouter();
|
||||
assert.match(await run('rs green maybe', admin), /rs green on.*rs green off/);
|
||||
});
|
||||
|
||||
test('status and help survive lockdown', async () => {
|
||||
const run = createRouter({ mode: MODES.LOCKDOWN });
|
||||
assert.match(await run('rs status', nonAdmin), /\[status handler\]/);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// Scope: Supplies transport-neutral metadata; execution handlers remain focused on server operations.
|
||||
const CATEGORIES = {
|
||||
system: { title: 'System', names: ['help', 'status', 'replay', 'time-status'] },
|
||||
admin: { title: 'Admin', names: ['lock', 'unlock', 'mode', 'reason', 'goal', 'kick', 'verify', 'deter', 'permissions'] },
|
||||
admin: { title: 'Admin', names: ['lock', 'unlock', 'mode', 'reason', 'goal', 'green', 'kick', 'verify', 'deter', 'permissions'] },
|
||||
features: { title: 'Features', names: ['lights', 'lift', 'neato'] },
|
||||
discord: { title: 'Discord', names: ['bridge'] },
|
||||
};
|
||||
@@ -19,6 +19,7 @@ function buildCommandRegistry(prefix, timeCommand) {
|
||||
mode: { category: 'admin', summary: 'Change the server mode.', usage: [`${prefix} mode <open|turns|admin|lockdown>`], access: 'Admin', permission: 'admin' },
|
||||
reason: { category: 'admin', summary: 'Show, set, or clear the admin-mode reason.', usage: [`${prefix} reason [text|clear]`], access: 'Admin to change' },
|
||||
goal: { category: 'admin', summary: 'Show, set, or clear the global objective.', usage: [`${prefix} goal [text|clear]`], access: 'Admin to change' },
|
||||
green: { category: 'admin', summary: 'Toggle green room and page mode.', usage: [`${prefix} green <on|off>`], access: 'Admin', permission: 'admin', requiredFeature: 'homeAssistant', unavailableLabel: 'Home Assistant' },
|
||||
lights: {
|
||||
category: 'features',
|
||||
summary: 'Control room lights or manage the admin light lock.',
|
||||
|
||||
@@ -17,6 +17,7 @@ const {
|
||||
ptzCameraEvents,
|
||||
} = require('../ptzCameraService');
|
||||
const { getState: getHomeAssistantState, homeAssistantEvents } = require('../homeAssistantService');
|
||||
const { isEnabled: isGreenModeEnabled, greenModeEvents } = require('../greenModeService');
|
||||
const { getState: getNeatoState, neatoEvents } = require('../neatoService');
|
||||
const { getState: getLiftState, liftEvents } = require('../liftService');
|
||||
const { getState: getKinectState, kinectEvents } = require('../kinectService');
|
||||
@@ -199,6 +200,9 @@ function buildSession(socket) {
|
||||
roomCameras: getRoomCameras(),
|
||||
ptzCamera: getPtzCameraState(socket),
|
||||
homeAssistant: getHomeAssistantState(),
|
||||
// Green mode is a server-wide visual feature. It stays separate from Home
|
||||
// Assistant state because HA only supplies the generic light operations.
|
||||
greenMode: isGreenModeEnabled(),
|
||||
neato: getNeatoState(),
|
||||
lift: getLiftState(),
|
||||
kinect: getKinectState(),
|
||||
@@ -391,6 +395,11 @@ homeAssistantEvents.on('update', () => {
|
||||
syncAll();
|
||||
});
|
||||
|
||||
greenModeEvents.on('change', () => {
|
||||
logger.info('Green mode change detected; syncing all clients');
|
||||
syncAll();
|
||||
});
|
||||
|
||||
homeAssistantEvents.on('status', () => {
|
||||
logger.info('Home Assistant status change; syncing all clients');
|
||||
syncAll();
|
||||
|
||||
Reference in New Issue
Block a user