feat: Initialize Turtle Control Center with React and WebSocket server
- Added index.html for the main entry point of the client application. - Created package.json for client dependencies and scripts. - Implemented App component with view controls and layout. - Added CSS styles for the application and components. - Developed ControlPanel component for turtle management and commands. - Created Map3D component for 3D visualization of turtles. - Established Zustand store for state management of turtles and WebSocket connection. - Set up server with Express and WebSocket for handling turtle updates and commands. - Added REST API endpoints for turtle status updates and command handling. - Implemented start.sh script for setting up and running the application.
This commit is contained in:
191
server/server.js
Normal file
191
server/server.js
Normal file
@@ -0,0 +1,191 @@
|
||||
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`);
|
||||
});
|
||||
Reference in New Issue
Block a user