// rover Manager // Purpose: Composes rover-manager policy, lifecycle, sensor, and socket modules into the public service API. // Scope: Keeps runtime behavior unchanged while making this entrypoint a thin wiring/orchestration layer. const io = require('../../globals/io'); const logger = require('../../globals/logger').child('roverManager'); const { sendAlert } = require('../alertService'); const { parseSensorFrame } = require('../../helpers/sensorDecoder'); const odometerService = require('../odometerService'); const overcurrentProtectionService = require('../overcurrentProtectionService'); const { MODES, getMode } = require('../modeManager'); const { isAdmin, isLockdownAdmin, roleEvents } = require('../roleService'); const { publishEvent } = require('../eventBus'); const videoSessions = require('../videoSessions'); const turnService = require('../turnService'); const { ALERT_COLOR, DOCK_GUARD_WINDOW_MS, IDLE_UNDOCKED_MS, PASSIVE_UNDOCKED_MS, DOCK_GUARD_RETRY_MS, DOCK_COMMAND_BASE64, BACKOFF_MS, BACKOFF_SPEED, PRIVATE_BUTTON_HOLD_MS, PRIVATE_AUTO_CLOSE_IDLE_MS, PRIVATE_AUTO_CLOSE_TICK_MS, SAFETY_BACKOFF_MIN, SAFETY_BACKOFF_MAX, DEFAULT_PRIVATE_SAFETY, } = require('./constants'); const { rovers, socketToRovers, spectatorSockets, managerEvents, backoffTimers, dockGuardStates, dockProtectionStrikeStates, privateButtonStates, privateNoUsersSince, privateSafetyTimers, privateSafetyStates, } = require('./state'); const { normalizePrivateSafety, clampInt, computeBatteryState } = require('./mathUtils'); const { createPrivateAccessPolicy } = require('./privateAccess'); const { createSensorPipeline } = require('./sensorPipeline'); const { createRoverLifecycle } = require('./roverLifecycle'); const { createRosterLifecycle } = require('./rosterLifecycle'); const { createSpectatorAccess } = require('./spectatorAccess'); const { registerSocketHandlers } = require('./socketHandlers'); const privateAccess = createPrivateAccessPolicy({ io, turnService, MODES, getMode, isAdmin, isLockdownAdmin, normalizePrivateSafety, DEFAULT_PRIVATE_SAFETY, }); const { parsePrivateMeta, isPrivateRecord, isPrivateOpen, getPrivateSafety, shouldApplyPrivateSafety, shouldApplyPrivateSensorSafety, isRoverVisibleToSocket, getControlDenialReason, } = privateAccess; const roverLifecycle = createRoverLifecycle({ io, rovers, socketToRovers, managerEvents, turnService, isAdmin, sendAlert, ALERT_COLOR, getMode, getControlDenialReason, }); const { requestControl, releaseControl, removeRoverDrivers, isDriver, canDrive, getRoversForSocket, getPrimaryRoverForSocket, canLeaveCurrentRover, canSwitchRover, } = roverLifecycle; let stopDockGuard = () => {}; const rosterLifecycle = createRosterLifecycle({ io, logger, sendAlert, publishEvent, turnService, ALERT_COLOR, DEFAULT_PRIVATE_SAFETY, rovers, spectatorSockets, managerEvents, privateButtonStates, privateNoUsersSince, privateSafetyTimers, privateSafetyStates, parsePrivateMeta, isPrivateRecord, isPrivateOpen, getPrivateSafety, isRoverVisibleToSocket, normalizePrivateSafety, stopDockGuard: (...args) => stopDockGuard(...args), getControlDenialReason, removeRoverDrivers, }); const { upsertRover, removeRover, lockRover, setPrivateOpen, setPrivateSafety, getRoster, getRosterForSocket, broadcastRoster, setToggleState, handleHostStats, canSeeRover, canRequestControl, } = rosterLifecycle; const spectatorAccess = createSpectatorAccess({ io, logger, rovers, spectatorSockets, privateNoUsersSince, PRIVATE_AUTO_CLOSE_IDLE_MS, isPrivateRecord, isPrivateOpen, isRoverVisibleToSocket, setPrivateOpen, }); const { enableSpectator, disableSpectator, tickPrivateAutoClose } = spectatorAccess; const sensorPipeline = createSensorPipeline({ io, logger, rovers, managerEvents, dockGuardStates, dockProtectionStrikeStates, backoffTimers, privateButtonStates, privateSafetyTimers, privateSafetyStates, parseSensorFrame, computeBatteryState, clampInt, DEFAULT_PRIVATE_SAFETY, SAFETY_BACKOFF_MIN, SAFETY_BACKOFF_MAX, PRIVATE_BUTTON_HOLD_MS, IDLE_UNDOCKED_MS, PASSIVE_UNDOCKED_MS, DOCK_GUARD_RETRY_MS, DOCK_GUARD_WINDOW_MS, DOCK_COMMAND_BASE64, BACKOFF_MS, BACKOFF_SPEED, ALERT_COLOR, sendAlert, publishEvent, processOdometerFrame: odometerService.processSensorFrame, processOvercurrentTelemetry: overcurrentProtectionService.processTelemetry, isPrivateRecord, isPrivateOpen, getPrivateSafety, setPrivateOpen, shouldApplyPrivateSafety, shouldApplyPrivateSensorSafety, }); const { handleSensorFrame, applyPrivateDriveSafety } = sensorPipeline; stopDockGuard = sensorPipeline.stopDockGuard; managerEvents.on('rover', ({ roverId, action }) => { /* Protection state contains the last motor intent for a specific physical rover connection. Removing it with the roster record prevents a reconnect from inheriting stale stress, an old administrator bypass, or a neutral requirement from the previous connection. */ if (action === 'removed') { overcurrentProtectionService.cleanupRover(roverId); } }); function removeSocket(socket) { roverLifecycle.removeSocket(socket, disableSpectator); } function canReplayRoverId(roverId, socket = null) { const record = rovers.get(String(roverId)); if (!record) return false; if (!isPrivateRecord(record)) return true; if (isPrivateOpen(record)) return true; return socket ? isRoverVisibleToSocket(record, socket) : false; } function getOdometersForSocket(socket) { const visibleIds = getRosterForSocket(socket).map((entry) => entry.id); return odometerService.getSnapshots(visibleIds); } roleEvents.on('change', ({ socket, role }) => { if (role === 'spectator') { enableSpectator(socket); } else { disableSpectator(socket); } }); registerSocketHandlers({ io, logger, MODES, ALERT_COLOR, getMode, isAdmin, isLockdownAdmin, sendAlert, videoSessions, rovers, isPrivateRecord, isRoverVisibleToSocket, tickPrivateAutoClose, removeSocket, enableSpectator, getRosterForSocket, canRequestControl, canSwitchRover, getRoversForSocket, requestControl, releaseControl, managerEvents, setPrivateOpen, lockRover, setPrivateSafety, getOdometersForSocket, }); odometerService.odometerEvents.on('update', ({ roverId, odometer }) => { io.sockets.sockets.forEach((socket) => { const record = rovers.get(String(roverId)); if (isRoverVisibleToSocket(record, socket)) { socket.volatile.emit('odometer:update', { roverId, odometer }); } }); }); setInterval(tickPrivateAutoClose, PRIVATE_AUTO_CLOSE_TICK_MS); module.exports = { upsertRover, removeRover, lockRover, setPrivateOpen, setPrivateSafety, getRoster, getRosterForSocket, broadcastRoster, setToggleState, handleHostStats, handleSensorFrame, requestControl, releaseControl, removeSocket, isDriver, canDrive, enableSpectator, disableSpectator, rovers, managerEvents, getRoversForSocket, getPrimaryRoverForSocket, canLeaveCurrentRover, canSeeRover, canRequestControl, applyPrivateDriveSafety, canReplayRoverId, getOdometersForSocket, };