This commit is contained in:
legop3
2026-04-29 18:45:41 -04:00
parent 01a8d044e7
commit 9bf449ca19
12 changed files with 219 additions and 127 deletions
@@ -0,0 +1,86 @@
// Idle Service Actions
// Purpose: Implements the expandable action pipeline executed when the system remains idle.
// Scope: Provides isolated action handlers and shared execution helpers for idle automation.
const logger = require('../../globals/logger').child('idleService');
const roverManager = require('../roverManager');
const { issueCommand } = require('../commandService');
const homeAssistantService = require('../homeAssistantService');
const neatoService = require('../neatoService');
const {
NIGHT_VISION_DISABLE_ACTION,
DOCK_COMMAND_BASE64,
} = require('./constants');
async function turnOffRoomControls() {
await homeAssistantService.setAllControllableEntitiesState('off');
return { action: 'roomControlsOff' };
}
async function dockAllRovers() {
const attempted = [];
const failed = [];
roverManager.rovers.forEach((record) => {
if (!record?.ws) return;
const roverId = String(record.id);
try {
issueCommand(roverId, { type: 'raw', raw: DOCK_COMMAND_BASE64 });
attempted.push(roverId);
} catch (err) {
failed.push({ roverId, error: err.message });
}
});
return { action: 'dockAllRovers', attempted, failed };
}
async function disableAllRoverNightVision() {
const attempted = [];
const failed = [];
roverManager.rovers.forEach((record) => {
if (!record?.ws) return;
const roverId = String(record.id);
try {
issueCommand(roverId, {
type: 'nightVision',
nightVision: { action: NIGHT_VISION_DISABLE_ACTION },
});
attempted.push(roverId);
} catch (err) {
failed.push({ roverId, error: err.message });
}
});
return { action: 'disableRoverNightVision', attempted, failed };
}
async function sendNeatoHome() {
try {
await neatoService.sendHome();
return { action: 'neatoSendHome', success: true };
} catch (err) {
return { action: 'neatoSendHome', success: false, error: err.message };
}
}
const idleActions = [
turnOffRoomControls,
dockAllRovers,
disableAllRoverNightVision,
sendNeatoHome,
];
async function runIdleActions() {
const results = [];
for (const action of idleActions) {
try {
const result = await action();
results.push({ ok: true, result });
} catch (err) {
logger.warn('Idle action failed', { action: action.name, error: err.message });
results.push({ ok: false, action: action.name, error: err.message });
}
}
return results;
}
module.exports = {
runIdleActions,
};
@@ -0,0 +1,12 @@
// Idle Service Constants
// Purpose: Defines idle timing and command constants used by idle automation workflows.
// Scope: Centralizes immutable configuration for trigger windows and rover command payloads.
const IDLE_TIMEOUT_MS = 2 * 60 * 1000;
const NIGHT_VISION_DISABLE_ACTION = 'on';
const DOCK_COMMAND_BASE64 = Buffer.from([143]).toString('base64');
module.exports = {
IDLE_TIMEOUT_MS,
NIGHT_VISION_DISABLE_ACTION,
DOCK_COMMAND_BASE64,
};
+65
View File
@@ -0,0 +1,65 @@
// Idle Service
// Purpose: Triggers a modular idle action pipeline after a sustained no-driver period.
// Scope: Observes driver activity events and coordinates timer-based idle automation execution.
const logger = require('../../globals/logger').child('idleService');
const { getActiveDrivers, turnEvents } = require('../turnService');
const roverManager = require('../roverManager');
const { IDLE_TIMEOUT_MS } = require('./constants');
const { runtime } = require('./state');
const { runIdleActions } = require('./actions');
function getActiveDriverCount() {
const active = getActiveDrivers();
const turnCount = active && typeof active === 'object' ? Object.keys(active).length : 0;
if (turnCount > 0) return turnCount;
let liveCount = 0;
roverManager.rovers.forEach((record) => {
if (record?.drivers?.size > 0) liveCount += 1;
});
return liveCount;
}
function clearIdleTimer() {
if (runtime.timer) {
clearTimeout(runtime.timer);
runtime.timer = null;
}
runtime.deadlineAt = null;
}
function scheduleIdleTimer() {
if (runtime.timer) return;
runtime.deadlineAt = Date.now() + IDLE_TIMEOUT_MS;
runtime.timer = setTimeout(async () => {
runtime.timer = null;
runtime.deadlineAt = null;
if (getActiveDriverCount() > 0) {
return;
}
runtime.lastTriggeredAt = Date.now();
const results = await runIdleActions();
logger.info('Idle automation executed', {
idleMs: IDLE_TIMEOUT_MS,
resultCount: results.length,
failures: results.filter((entry) => !entry.ok).length,
});
refreshIdleState();
}, IDLE_TIMEOUT_MS);
}
function refreshIdleState() {
if (getActiveDriverCount() > 0) {
clearIdleTimer();
return;
}
scheduleIdleTimer();
}
turnEvents.on('activeDriver', refreshIdleState);
turnEvents.on('queue', refreshIdleState);
refreshIdleState();
module.exports = {
refreshIdleState,
};
+12
View File
@@ -0,0 +1,12 @@
// Idle Service State
// Purpose: Stores runtime timer and bookkeeping state for idle automation scheduling.
// Scope: Encapsulates mutable idle-tracking data shared by idle service modules.
const runtime = {
timer: null,
deadlineAt: null,
lastTriggeredAt: null,
};
module.exports = {
runtime,
};