mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 09:31:20 -04:00
first lol
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
const EventEmitter = require('events');
|
||||
const io = require('../globals/io');
|
||||
const logger = require('../globals/logger').child('assignment');
|
||||
const { MODES, getMode, modeEvents } = require('./modeManager');
|
||||
@@ -7,6 +8,7 @@ const roverManager = require('./roverManager');
|
||||
const socketRefs = new Map(); // socketId -> socket
|
||||
const assignments = new Map(); // socketId -> roverId
|
||||
const waiting = new Set(); // socketIds waiting for placement
|
||||
const assignmentEvents = new EventEmitter();
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
socketRefs.set(socket.id, socket);
|
||||
@@ -64,6 +66,7 @@ function assignSocket(socket) {
|
||||
if (!target) {
|
||||
waiting.add(socket.id);
|
||||
logger.info('No rover available, user waiting', socket.id);
|
||||
assignmentEvents.emit('update', socket.id);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -71,9 +74,11 @@ function assignSocket(socket) {
|
||||
assignments.set(socket.id, target.id);
|
||||
waiting.delete(socket.id);
|
||||
logger.info('Assigned user to rover', socket.id, target.id);
|
||||
assignmentEvents.emit('update', socket.id);
|
||||
} catch (err) {
|
||||
logger.warn('Failed to assign user', err.message);
|
||||
waiting.add(socket.id);
|
||||
assignmentEvents.emit('update', socket.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +89,8 @@ function unassignSocket(socket) {
|
||||
if (roverId) {
|
||||
roverManager.releaseControl(roverId, socket);
|
||||
assignments.delete(socket.id);
|
||||
logger.info('Unassigned socket from rover', socket.id, roverId);
|
||||
assignmentEvents.emit('update', socket.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +105,7 @@ function reassignFromRover(roverId) {
|
||||
roverManager.releaseControl(rid, socket);
|
||||
assignments.delete(socketId);
|
||||
assignSocket(socket);
|
||||
assignmentEvents.emit('update', socketId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +117,7 @@ function reassignWaiting() {
|
||||
} else {
|
||||
waiting.delete(socketId);
|
||||
}
|
||||
assignmentEvents.emit('update', socketId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +125,8 @@ function releaseAssignment(socket, roverId) {
|
||||
roverManager.releaseControl(roverId, socket);
|
||||
assignments.delete(socket.id);
|
||||
waiting.add(socket.id);
|
||||
logger.info('Released assignment back to queue', socket.id, roverId);
|
||||
assignmentEvents.emit('update', socket.id);
|
||||
}
|
||||
|
||||
function pickRover() {
|
||||
@@ -133,3 +144,18 @@ function pickRover() {
|
||||
candidates.sort((a, b) => a.drivers.size - b.drivers.size);
|
||||
return candidates[0];
|
||||
}
|
||||
|
||||
function describeAssignment(socketId) {
|
||||
const roverId = assignments.get(socketId) || null;
|
||||
const waitingIndex = waiting.has(socketId) ? Array.from(waiting).indexOf(socketId) : -1;
|
||||
return {
|
||||
roverId,
|
||||
status: roverId ? 'assigned' : waiting.has(socketId) ? 'waiting' : null,
|
||||
queuePosition: waitingIndex >= 0 ? waitingIndex + 1 : null,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
assignmentEvents,
|
||||
describeAssignment,
|
||||
};
|
||||
|
||||
@@ -36,6 +36,7 @@ io.on('connection', (socket) => {
|
||||
const requestedRole = socket.handshake?.query?.role;
|
||||
const initialRole = requestedRole === 'spectator' ? 'spectator' : 'user';
|
||||
setRole(socket, initialRole);
|
||||
logger.info('Socket connected with role', socket.id, initialRole);
|
||||
socket.emit('auth:role', { role: initialRole });
|
||||
socket.on('auth:login', async ({ username, password }, cb = () => {}) => {
|
||||
try {
|
||||
@@ -45,18 +46,27 @@ io.on('connection', (socket) => {
|
||||
setRole(socket, role);
|
||||
socket.emit('auth:role', { role });
|
||||
clearLockdownTimer(socket);
|
||||
logger.info('Login success', socket.id, role);
|
||||
cb({ success: true, role: socket.data.role });
|
||||
} catch (err) {
|
||||
logger.warn('Login failed', socket.id, err.message);
|
||||
cb({ success: false, error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('role:set', ({ role }) => {
|
||||
function handleRoleChange({ role } = {}, cb = () => {}) {
|
||||
if (role === 'spectator' || role === 'user') {
|
||||
setRole(socket, role);
|
||||
socket.emit('auth:role', { role });
|
||||
logger.info('Role changed via client request', socket.id, role);
|
||||
cb({ success: true, role });
|
||||
} else {
|
||||
cb({ error: 'Invalid role' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
socket.on('role:set', handleRoleChange);
|
||||
socket.on('session:setRole', handleRoleChange);
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const io = require('../globals/io');
|
||||
const roverManager = require('./roverManager');
|
||||
const logger = require('../globals/logger').child('commandService');
|
||||
|
||||
const pendingCommands = new Map(); // id -> { roverId }
|
||||
|
||||
@@ -13,6 +14,7 @@ function issueCommand(roverId, payload) {
|
||||
const message = { ...payload, id };
|
||||
record.ws.send(JSON.stringify(message));
|
||||
pendingCommands.set(id, { roverId, ts: Date.now(), type: payload.type });
|
||||
logger.info('Issued command', roverId, payload.type, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -20,6 +22,7 @@ function handleAck(msg) {
|
||||
const pending = pendingCommands.get(msg.id);
|
||||
if (!pending) return;
|
||||
pendingCommands.delete(msg.id);
|
||||
logger.info('Command acknowledged', pending.roverId, pending.type, msg.status);
|
||||
io.emit('commandAck', {
|
||||
roverId: pending.roverId,
|
||||
id: msg.id,
|
||||
@@ -34,7 +37,7 @@ module.exports = {
|
||||
};
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
socket.on('command', ({ roverId, type, data } = {}, cb = () => {}) => {
|
||||
function handleCommand({ roverId, type, data } = {}, cb = () => {}) {
|
||||
try {
|
||||
if (!roverId) {
|
||||
throw new Error('roverId required');
|
||||
@@ -44,9 +47,14 @@ io.on('connection', (socket) => {
|
||||
}
|
||||
const payload = data ? { ...data } : {};
|
||||
const id = issueCommand(roverId, { type, ...payload });
|
||||
logger.info('Queued command', socket.id, roverId, type);
|
||||
cb({ id });
|
||||
} catch (err) {
|
||||
logger.warn('Command rejected', socket.id, err.message);
|
||||
cb({ error: err.message });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
socket.on('command', handleCommand);
|
||||
socket.on('command:issue', handleCommand);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const io = require('../globals/io');
|
||||
const loggerRoot = require('../globals/logger');
|
||||
const logger = loggerRoot.child('logStream');
|
||||
|
||||
const MAX_HISTORY = 200;
|
||||
const history = [];
|
||||
|
||||
function pushEntry(entry) {
|
||||
history.push(entry);
|
||||
if (history.length > MAX_HISTORY) {
|
||||
history.shift();
|
||||
}
|
||||
}
|
||||
|
||||
function broadcast(entry) {
|
||||
io.emit('log:entry', entry);
|
||||
}
|
||||
|
||||
function hydrateSocket(socket) {
|
||||
if (!socket) return;
|
||||
socket.emit('log:init', history);
|
||||
}
|
||||
|
||||
loggerRoot.registerSink(({ level, label, message, timestamp }) => {
|
||||
const entry = {
|
||||
id: uuidv4(),
|
||||
level,
|
||||
label,
|
||||
message,
|
||||
timestamp,
|
||||
};
|
||||
pushEntry(entry);
|
||||
broadcast(entry);
|
||||
});
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
logger.info('Hydrating log history for', socket.id);
|
||||
hydrateSocket(socket);
|
||||
});
|
||||
@@ -212,43 +212,72 @@ io.on('connection', (socket) => {
|
||||
enableSpectator(socket);
|
||||
}
|
||||
|
||||
socket.on('requestControl', ({ roverId, force } = {}) => {
|
||||
function handleRequestControl({ roverId, force } = {}, cb = () => {}) {
|
||||
try {
|
||||
const targetId = roverId || Array.from(rovers.keys())[0];
|
||||
if (!targetId) {
|
||||
throw new Error('No rovers available');
|
||||
}
|
||||
logger.info('Request control', socket.id, targetId, { force });
|
||||
requestControl(targetId, socket, { force: Boolean(force) });
|
||||
socket.emit('controlGranted', { roverId: targetId });
|
||||
cb({ success: true, roverId: targetId });
|
||||
} catch (err) {
|
||||
logger.warn('Request control failed', socket.id, err.message);
|
||||
sendAlert({ color: COLORS.warn, title: 'Control denied', message: err.message });
|
||||
cb({ error: err.message });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
socket.on('releaseControl', ({ roverId }) => {
|
||||
if (!roverId) return;
|
||||
releaseControl(roverId, socket);
|
||||
});
|
||||
|
||||
socket.on('lockRover', ({ roverId, locked }) => {
|
||||
if (!isAdmin(socket)) return;
|
||||
try {
|
||||
lockRover(roverId, locked);
|
||||
} catch (err) {
|
||||
sendAlert({ color: COLORS.error, title: 'Lock failed', message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('subscribeAll', () => {
|
||||
if (socket.data?.role !== 'spectator') {
|
||||
function handleReleaseControl({ roverId } = {}, cb = () => {}) {
|
||||
if (!roverId) {
|
||||
cb({ error: 'roverId required' });
|
||||
return;
|
||||
}
|
||||
logger.info('Release control', socket.id, roverId);
|
||||
releaseControl(roverId, socket);
|
||||
cb({ success: true, roverId });
|
||||
}
|
||||
|
||||
function handleLockToggle({ roverId, locked } = {}, cb = () => {}) {
|
||||
if (!isAdmin(socket)) {
|
||||
cb({ error: 'Not authorized' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
lockRover(roverId, locked);
|
||||
logger.info('Lock state changed', roverId, locked);
|
||||
cb({ success: true });
|
||||
} catch (err) {
|
||||
logger.warn('Lock change failed', roverId, err.message);
|
||||
sendAlert({ color: COLORS.error, title: 'Lock failed', message: err.message });
|
||||
cb({ error: err.message });
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubscribeAll(_, cb = () => {}) {
|
||||
if (socket.data?.role !== 'spectator') {
|
||||
cb({ error: 'Spectator role required' });
|
||||
return;
|
||||
}
|
||||
logger.info('Spectator subscribing to all rovers', socket.id);
|
||||
for (const record of rovers.values()) {
|
||||
socket.join(record.room);
|
||||
}
|
||||
});
|
||||
cb({ success: true });
|
||||
}
|
||||
|
||||
socket.on('requestControl', handleRequestControl);
|
||||
socket.on('session:requestControl', handleRequestControl);
|
||||
socket.on('releaseControl', handleReleaseControl);
|
||||
socket.on('session:releaseControl', handleReleaseControl);
|
||||
socket.on('lockRover', handleLockToggle);
|
||||
socket.on('session:lockRover', handleLockToggle);
|
||||
socket.on('subscribeAll', handleSubscribeAll);
|
||||
socket.on('session:subscribeAll', handleSubscribeAll);
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
logger.info('Socket disconnected', socket.id);
|
||||
removeSocket(socket);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
const io = require('../globals/io');
|
||||
const logger = require('../globals/logger').child('sessionService');
|
||||
const { getRole, roleEvents } = require('./roleService');
|
||||
const { getMode, modeEvents } = require('./modeManager');
|
||||
const roverManager = require('./roverManager');
|
||||
const { managerEvents } = roverManager;
|
||||
const assignmentService = require('./assignmentService');
|
||||
|
||||
function buildSession(socket) {
|
||||
return {
|
||||
role: getRole(socket),
|
||||
mode: getMode(),
|
||||
roster: roverManager.getRoster(),
|
||||
assignment: assignmentService.describeAssignment(socket?.id || ''),
|
||||
};
|
||||
}
|
||||
|
||||
function syncSocket(socket) {
|
||||
if (!socket) return;
|
||||
const payload = buildSession(socket);
|
||||
logger.info('Syncing session', socket.id, payload.role, payload.assignment);
|
||||
socket.emit('session:sync', payload);
|
||||
}
|
||||
|
||||
function syncAll() {
|
||||
logger.info('Broadcasting session sync to all sockets');
|
||||
io.sockets.sockets.forEach((socket) => syncSocket(socket));
|
||||
}
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
logger.info('New socket connected', socket.id);
|
||||
syncSocket(socket);
|
||||
});
|
||||
|
||||
roleEvents.on('change', ({ socket }) => {
|
||||
if (!socket) return;
|
||||
logger.info('Role changed; syncing session', socket.id);
|
||||
syncSocket(socket);
|
||||
});
|
||||
|
||||
assignmentService.assignmentEvents.on('update', (socketId) => {
|
||||
const socket = io.sockets.sockets.get(socketId);
|
||||
if (socket) {
|
||||
logger.info('Assignment update; syncing session', socketId);
|
||||
syncSocket(socket);
|
||||
}
|
||||
});
|
||||
|
||||
modeEvents.on('change', () => {
|
||||
logger.info('Mode change detected; syncing all clients');
|
||||
syncAll();
|
||||
});
|
||||
|
||||
managerEvents.on('rover', () => {
|
||||
logger.info('Rover roster change; syncing all clients');
|
||||
syncAll();
|
||||
});
|
||||
|
||||
managerEvents.on('lock', ({ roverId, locked }) => {
|
||||
logger.info('Rover lock change', roverId, locked);
|
||||
syncAll();
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
buildSession,
|
||||
syncSocket,
|
||||
syncAll,
|
||||
};
|
||||
Reference in New Issue
Block a user