import express from 'express'; import { WebSocketServer } from 'ws'; import cors from 'cors'; import { createServer } from 'http'; const app = express(); const PORT = 3001; const WS_PORT = 3002; app.use(cors()); app.use(express.json()); // Store connected web clients and turtle data const webClients = new Set(); const turtleData = new Map(); // turtleID -> turtle state // Create HTTP server const server = createServer(app); // WebSocket server for web clients const wss = new WebSocketServer({ port: WS_PORT }); console.log(`🚀 Turtle Control Server starting...`); console.log(`📡 HTTP Server: http://localhost:${PORT}`); console.log(`🔌 WebSocket Server: ws://localhost:${WS_PORT}`); // WebSocket connection handler wss.on('connection', (ws) => { console.log('🌐 New web client connected'); webClients.add(ws); // Send current turtle data to new client ws.send(JSON.stringify({ type: 'initial_state', turtles: Array.from(turtleData.values()) })); ws.on('message', (message) => { try { const data = JSON.parse(message); console.log('📨 Received from web client:', data); // Handle commands from web client if (data.type === 'command') { // Store command for turtle to poll const turtleID = data.turtleID; if (turtleData.has(turtleID)) { const turtle = turtleData.get(turtleID); turtle.pendingCommands = turtle.pendingCommands || []; turtle.pendingCommands.push({ command: data.command, param: data.param, timestamp: Date.now() }); console.log(`📤 Command queued for turtle ${turtleID}:`, data.command); } } } catch (error) { console.error('❌ Error processing web client message:', error); } }); ws.on('close', () => { console.log('👋 Web client disconnected'); webClients.delete(ws); }); ws.on('error', (error) => { console.error('❌ WebSocket error:', error); }); }); // Broadcast to all web clients function broadcastToClients(data) { const message = JSON.stringify(data); webClients.forEach((client) => { if (client.readyState === 1) { // OPEN client.send(message); } }); } // REST API endpoint for turtles to update their status app.post('/api/turtle/update', (req, res) => { try { const turtleUpdate = req.body; const turtleID = turtleUpdate.turtleID; console.log(`🐢 Status update from Turtle ${turtleID}`); // Update turtle data if (!turtleData.has(turtleID)) { console.log(`✨ New turtle registered: ${turtleID}`); } turtleData.set(turtleID, { ...turtleUpdate, lastUpdate: Date.now() }); // Broadcast to web clients broadcastToClients({ type: 'turtle_update', turtle: turtleData.get(turtleID) }); res.json({ success: true }); } catch (error) { console.error('❌ Error processing turtle update:', error); res.status(500).json({ error: error.message }); } }); // Endpoint for turtles to poll for commands app.get('/api/turtle/:id/commands', (req, res) => { try { const turtleID = parseInt(req.params.id); if (turtleData.has(turtleID)) { const turtle = turtleData.get(turtleID); const commands = turtle.pendingCommands || []; // Clear pending commands turtle.pendingCommands = []; res.json({ commands }); } else { res.json({ commands: [] }); } } catch (error) { console.error('❌ Error getting commands:', error); res.status(500).json({ error: error.message }); } }); // Get all turtles app.get('/api/turtles', (req, res) => { res.json({ turtles: Array.from(turtleData.values()) }); }); // Send command to turtle app.post('/api/turtle/:id/command', (req, res) => { try { const turtleID = parseInt(req.params.id); const { command, param } = req.body; if (turtleData.has(turtleID)) { const turtle = turtleData.get(turtleID); turtle.pendingCommands = turtle.pendingCommands || []; turtle.pendingCommands.push({ command, param, timestamp: Date.now() }); console.log(`📤 Command queued for turtle ${turtleID}:`, command); res.json({ success: true }); } else { res.status(404).json({ error: 'Turtle not found' }); } } catch (error) { console.error('❌ Error sending command:', error); res.status(500).json({ error: error.message }); } }); // Cleanup stale turtles (haven't updated in 30 seconds) setInterval(() => { const now = Date.now(); const timeout = 30000; // 30 seconds for (const [turtleID, turtle] of turtleData.entries()) { if (now - turtle.lastUpdate > timeout) { console.log(`🗑️ Removing stale turtle: ${turtleID}`); turtleData.delete(turtleID); broadcastToClients({ type: 'turtle_disconnected', turtleID }); } } }, 10000); // Check every 10 seconds server.listen(PORT, () => { console.log(`✅ Server ready!`); console.log(`\nConfigured turtles to send updates to:`); console.log(` http://localhost:${PORT}/api/turtle/update`); });