refactor: enhance WebSocket handling for bridge connections and command forwarding
This commit is contained in:
164
server/server.js
164
server/server.js
@@ -7,7 +7,6 @@ import { Turtle } from './Turtle.js';
|
||||
|
||||
const app = express();
|
||||
const PORT = 3001;
|
||||
const WS_PORT = 3002;
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json({ limit: '5mb' }));
|
||||
@@ -99,13 +98,9 @@ function getOrCreateTurtle(turtleID) {
|
||||
|
||||
// ---- Event handlers ----
|
||||
|
||||
// Forward eval commands to webbridge via pending command queue
|
||||
// Forward eval commands to webbridge via WebSocket (or fallback to poll queue)
|
||||
turtle.on('sendCommand', (command) => {
|
||||
// Queue eval command for webbridge to poll
|
||||
turtle.pendingCommands.push({
|
||||
...command,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
pushCommandToBridge(turtleID, command);
|
||||
});
|
||||
|
||||
// Store discovered blocks
|
||||
@@ -201,15 +196,162 @@ function getBlockPosition(turtlePos, facing, direction) {
|
||||
// Create HTTP server
|
||||
const server = createServer(app);
|
||||
|
||||
// WebSocket server for web clients
|
||||
const wss = new WebSocketServer({ port: WS_PORT });
|
||||
// WebSocket server for web clients AND bridge connections
|
||||
const wss = new WebSocketServer({ server });
|
||||
|
||||
// Track bridge connections separately
|
||||
const bridgeClients = new Set();
|
||||
|
||||
console.log(`🚀 Turtle Control Server starting...`);
|
||||
console.log(`📡 HTTP Server: http://localhost:${PORT}`);
|
||||
console.log(`🔌 WebSocket Server: ws://localhost:${WS_PORT}`);
|
||||
console.log(`🔌 WebSocket Server: ws://localhost:${PORT}/ws`);
|
||||
|
||||
/**
|
||||
* Push a command to the webbridge for a specific turtle.
|
||||
* If a bridge is connected via WebSocket, send instantly.
|
||||
* Otherwise, queue for HTTP polling (fallback).
|
||||
*/
|
||||
function pushCommandToBridge(turtleID, command) {
|
||||
let sent = false;
|
||||
for (const bridge of bridgeClients) {
|
||||
if (bridge.readyState === 1) { // OPEN
|
||||
bridge.send(JSON.stringify({
|
||||
type: 'command',
|
||||
turtleID,
|
||||
command,
|
||||
}));
|
||||
sent = true;
|
||||
}
|
||||
}
|
||||
if (!sent) {
|
||||
// Fallback: queue for HTTP polling (backward compatible)
|
||||
const turtle = turtles.get(turtleID);
|
||||
if (turtle) {
|
||||
turtle.pendingCommands.push({ ...command, timestamp: Date.now() });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WebSocket connection handler
|
||||
wss.on('connection', (ws) => {
|
||||
wss.on('connection', (ws, req) => {
|
||||
const url = req.url || '';
|
||||
|
||||
// ---- Bridge WebSocket connection ----
|
||||
if (url.startsWith('/ws/bridge')) {
|
||||
console.log('🌉 Webbridge connected via WebSocket');
|
||||
bridgeClients.add(ws);
|
||||
|
||||
// Send list of known turtle IDs so bridge knows who to listen for
|
||||
ws.send(JSON.stringify({
|
||||
type: 'init',
|
||||
turtleIDs: Array.from(turtles.keys()),
|
||||
}));
|
||||
|
||||
ws.on('message', (raw) => {
|
||||
try {
|
||||
const data = JSON.parse(raw);
|
||||
|
||||
if (data.type === 'status') {
|
||||
// Turtle status update forwarded from bridge
|
||||
const turtleID = data.turtleID;
|
||||
if (!turtleID) return;
|
||||
|
||||
const turtle = getOrCreateTurtle(turtleID);
|
||||
turtle.updateFromStatus(data);
|
||||
|
||||
// Store home position if provided
|
||||
if (data.homePosition) {
|
||||
turtleHomes.set(turtleID, data.homePosition);
|
||||
db.saveTurtleHome(turtleID, data.homePosition);
|
||||
}
|
||||
|
||||
} else if (data.type === 'eval_response') {
|
||||
// Eval response from turtle via bridge
|
||||
const turtle = turtles.get(data.turtleID);
|
||||
if (turtle) {
|
||||
const handled = turtle.handleResponse(data.uuid, data.result, data.error);
|
||||
if (handled) {
|
||||
console.log(`📩 Eval response T#${data.turtleID} uuid:${(data.uuid || '').substring(0, 8)} - resolved`);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (data.type === 'event') {
|
||||
// Real-time events (inventory, peripheral)
|
||||
const turtle = turtles.get(data.turtleID);
|
||||
if (turtle) {
|
||||
turtle.handleEvent(data.eventType, data.message);
|
||||
}
|
||||
|
||||
} else if (data.type === 'request_home') {
|
||||
// Turtle requesting home position
|
||||
const home = turtleHomes.get(data.turtleID);
|
||||
ws.send(JSON.stringify({
|
||||
type: 'home_position',
|
||||
turtleID: data.turtleID,
|
||||
homePosition: home || null,
|
||||
}));
|
||||
|
||||
} else if (data.type === 'set_home') {
|
||||
// Turtle setting home position
|
||||
const pos = data.position;
|
||||
if (pos && data.turtleID) {
|
||||
turtleHomes.set(data.turtleID, pos);
|
||||
db.saveTurtleHome(data.turtleID, pos);
|
||||
const turtle = turtles.get(data.turtleID);
|
||||
if (turtle) turtle.homePosition = pos;
|
||||
ws.send(JSON.stringify({
|
||||
type: 'home_set_confirm',
|
||||
turtleID: data.turtleID,
|
||||
homePosition: pos,
|
||||
}));
|
||||
}
|
||||
|
||||
} else if (data.type === 'blocks_discovered') {
|
||||
// Batch block discovery from turtle
|
||||
if (data.blocks && Array.isArray(data.blocks)) {
|
||||
for (const block of data.blocks) {
|
||||
storeBlock(block.x, block.y, block.z, { name: block.name, metadata: block.metadata || 0 }, data.turtleID || block.discoveredBy);
|
||||
}
|
||||
broadcastToClients({
|
||||
type: 'blocks_discovered',
|
||||
blocks: data.blocks.map(b => ({
|
||||
x: b.x, y: b.y, z: b.z,
|
||||
name: b.name, metadata: b.metadata || 0,
|
||||
discoveredBy: data.turtleID || b.discoveredBy,
|
||||
timestamp: Date.now(),
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
} else if (data.type === 'player_update') {
|
||||
// Player position from pocket computer
|
||||
if (data.playerID && data.position) {
|
||||
db.savePlayerPosition(data.playerID, data.position);
|
||||
broadcastToClients({
|
||||
type: 'player_update',
|
||||
player: { id: data.playerID, ...data.position },
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Bridge WS message error:', error.message);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
console.log('🌉 Webbridge disconnected');
|
||||
bridgeClients.delete(ws);
|
||||
});
|
||||
|
||||
ws.on('error', (error) => {
|
||||
console.error('❌ Bridge WS error:', error);
|
||||
bridgeClients.delete(ws);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- Web client WebSocket connection ----
|
||||
console.log('🌐 New web client connected');
|
||||
webClients.add(ws);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user