modularize code, add dummy version of roverd

This commit is contained in:
legop3
2025-11-12 01:13:29 -05:00
parent b5ce6706d6
commit 73b1b44c35
41 changed files with 1018 additions and 401 deletions
+4
View File
@@ -0,0 +1,4 @@
registerModule('globals/socket', (require, exports) => {
const socket = io();
exports.socket = socket;
});
+11
View File
@@ -0,0 +1,11 @@
registerModule('helpers/formatters', (require, exports) => {
function formatHex(base64) {
if (!base64) return '';
const buffer = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
return Array.from(buffer)
.map((b) => b.toString(16).padStart(2, '0'))
.join(' ');
}
exports.formatHex = formatHex;
});
+6
View File
@@ -0,0 +1,6 @@
requireModule('globals/socket');
requireModule('helpers/formatters');
requireModule('services/state');
requireModule('services/alertFeed');
requireModule('services/roverUI');
requireModule('services/driveControls');
+25
View File
@@ -0,0 +1,25 @@
registerModule('services/alertFeed', (require, exports) => {
const { socket } = require('globals/socket');
const container = document.getElementById('alerts');
if (!container) return;
function pushAlert({ color = '#2196f3', title, message, ts }) {
const div = document.createElement('div');
div.className = 'alert';
div.style.borderColor = color;
div.style.background = color + '22';
div.innerHTML = `<strong>${title || 'Notice'}</strong> - ${message || ''}`;
container.prepend(div);
while (container.children.length > 5) {
container.removeChild(container.lastChild);
}
setTimeout(() => {
if (div.parentElement === container) {
container.removeChild(div);
}
}, 15000);
}
socket.on('alert', pushAlert);
});
+122
View File
@@ -0,0 +1,122 @@
registerModule('services/driveControls', (require, exports) => {
const { socket } = require('globals/socket');
const state = require('services/state');
const motorsStopBtn = document.getElementById('motorsStop');
const sensorToggleBtn = document.getElementById('sensorToggle');
const modeButtons = document.querySelectorAll('[data-oi]');
let sensorEnabled = false;
let lastDrive = { left: 0, right: 0 };
const keys = new Set();
const driveInterval = 120;
window.addEventListener('keydown', (event) => {
keys.add(event.key.toLowerCase());
});
window.addEventListener('keyup', (event) => {
keys.delete(event.key.toLowerCase());
});
setInterval(() => {
const roverId = state.getSelected();
if (!roverId) return;
const speeds = computeDrive();
if (speeds.left === lastDrive.left && speeds.right === lastDrive.right) return;
lastDrive = speeds;
socket.emit('command', {
roverId,
type: 'drive',
data: { driveDirect: speeds },
});
}, driveInterval);
function computeDrive() {
const forward = keys.has('w');
const backward = keys.has('s');
const left = keys.has('a');
const right = keys.has('d');
const fast = keys.has('shift');
const base = fast ? 300 : 150;
let leftSpeed = 0;
let rightSpeed = 0;
if (forward && !backward) {
leftSpeed += base;
rightSpeed += base;
} else if (backward && !forward) {
leftSpeed -= base;
rightSpeed -= base;
}
if (left && !right) {
leftSpeed -= base;
rightSpeed += base;
} else if (right && !left) {
leftSpeed += base;
rightSpeed -= base;
}
if (!forward && !backward && (left || right)) {
leftSpeed = left ? -base : base;
rightSpeed = left ? base : -base;
}
return {
left: clamp(leftSpeed, -500, 500),
right: clamp(rightSpeed, -500, 500),
};
}
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
motorsStopBtn?.addEventListener('click', () => {
const roverId = state.getSelected();
if (!roverId) return;
socket.emit('command', {
roverId,
type: 'motors',
data: { motorPwm: { main: 0, side: 0, vacuum: 0 } },
});
});
sensorToggleBtn?.addEventListener('click', () => {
sensorEnabled = !sensorEnabled;
sensorToggleBtn.textContent = sensorEnabled ? 'Disable Sensor Stream' : 'Enable Sensor Stream';
const roverId = state.getSelected();
socket.emit('command', {
roverId,
type: 'sensorStream',
data: { sensorStream: { enable: sensorEnabled } },
});
});
const OI_COMMANDS = {
start: [128],
safe: [131],
full: [132],
passive: [128],
dock: [143],
};
modeButtons.forEach((btn) => {
btn.addEventListener('click', () => {
const cmd = btn.dataset.oi;
const bytes = OI_COMMANDS[cmd];
if (!bytes) return;
const roverId = state.getSelected();
socket.emit('command', {
roverId,
type: 'raw',
data: { raw: bytesToBase64(bytes) },
});
});
});
function bytesToBase64(bytes) {
return btoa(String.fromCharCode(...bytes));
}
});
+68
View File
@@ -0,0 +1,68 @@
registerModule('services/roverUI', (require, exports) => {
const { socket } = require('globals/socket');
const { formatHex } = require('helpers/formatters');
const state = require('services/state');
const statusEl = document.getElementById('status');
const roverSelect = document.getElementById('roverSelect');
const sensorOutput = document.getElementById('sensorOutput');
const requestBtn = document.getElementById('requestControl');
const lockBtn = document.getElementById('lockToggle');
let roster = [];
socket.on('rovers', (list) => {
roster = list;
roverSelect.innerHTML = '';
list.forEach((rover) => {
const option = document.createElement('option');
option.value = rover.id;
option.textContent = `${rover.name}${rover.locked ? ' (locked)' : ''}`;
roverSelect.appendChild(option);
});
if (!state.getSelected() && list.length) {
state.setSelected(list[0].id);
roverSelect.value = list[0].id;
}
});
roverSelect.addEventListener('change', () => {
state.setSelected(roverSelect.value);
});
socket.on('controlGranted', ({ roverId }) => {
state.setSelected(roverId);
roverSelect.value = roverId;
statusEl.textContent = `Driving ${roverId}`;
});
socket.on('sensorFrame', ({ roverId, frame, sensors }) => {
if (roverId !== state.getSelected()) return;
const lines = [];
if (sensors) {
Object.entries(sensors).forEach(([key, value]) => {
lines.push(`${key}: ${typeof value === 'object' ? JSON.stringify(value) : value}`);
});
}
lines.push('', 'raw:', formatHex(frame?.data));
sensorOutput.textContent = lines.join('\n');
});
requestBtn?.addEventListener('click', () => {
socket.emit('requestControl', { roverId: state.getSelected() });
});
lockBtn?.addEventListener('click', () => {
const roverId = state.getSelected();
const rover = roster.find((r) => r.id === roverId);
if (!rover) return;
socket.emit('lockRover', { roverId, locked: !rover.locked });
});
socket.on('connect', () => {
statusEl.textContent = 'Connected';
});
socket.on('disconnect', () => {
statusEl.textContent = 'Disconnected';
});
});
+14
View File
@@ -0,0 +1,14 @@
registerModule('services/state', (require, exports) => {
let selectedRover = null;
function setSelected(roverId) {
selectedRover = roverId;
}
function getSelected() {
return selectedRover;
}
exports.setSelected = setSelected;
exports.getSelected = getSelected;
});