feat: Enhance turtle command handling with legacy support and eval response endpoints

This commit is contained in:
MayaTheShy
2026-02-20 01:57:09 -05:00
parent e38551dfc2
commit 4c774ac306

View File

@@ -364,14 +364,14 @@ app.get('/api/turtle/:id/commands', (req, res) => {
app.post('/api/turtle/:id/commands/ack', (req, res) => {
try {
const turtleID = parseInt(req.params.id);
const turtle = turtles.get(turtleID);
if (turtleData.has(turtleID)) {
const turtle = turtleData.get(turtleID);
const clearedCount = (turtle.pendingCommands || []).length;
if (turtle) {
const clearedCount = (turtle.pendingLegacyCommands || []).length;
if (clearedCount > 0) {
console.log(`✅ Turtle ${turtleID} acknowledged ${clearedCount} command(s)`);
turtle.pendingCommands = [];
turtle.pendingLegacyCommands = [];
}
res.json({ success: true, cleared: clearedCount });
@@ -387,7 +387,7 @@ app.post('/api/turtle/:id/commands/ack', (req, res) => {
// Get all turtles
app.get('/api/turtles', (req, res) => {
res.json({
turtles: Array.from(turtleData.values())
turtles: Array.from(turtles.values()).map(t => t.toJSON())
});
});
@@ -407,16 +407,15 @@ app.post('/api/turtle/:id/home', (req, res) => {
// Persist to database
db.saveTurtleHome(turtleID, position);
// Update turtle data
if (turtleData.has(turtleID)) {
const turtle = turtleData.get(turtleID);
// Update turtle instance
const turtle = turtles.get(turtleID);
if (turtle) {
turtle.homePosition = position;
turtleData.set(turtleID, turtle);
// Broadcast update
broadcastToClients({
type: 'turtle_update',
turtle: turtle
turtle: turtle.toJSON()
});
}
@@ -486,17 +485,24 @@ app.post('/api/turtle/:id/command', (req, res) => {
try {
const turtleID = parseInt(req.params.id);
const { command, param } = req.body;
const turtle = turtles.get(turtleID);
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);
if (turtle) {
// Check for state change commands
if (command === 'set_state' || command === 'setState') {
const stateName = param?.state || param;
const stateData = param?.data || {};
turtle.setState(stateName, stateData);
console.log(`📤 State set for turtle ${turtleID}: ${stateName}`);
} else {
// Legacy command - queue for webbridge
turtle.pendingLegacyCommands.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' });
@@ -507,6 +513,141 @@ app.post('/api/turtle/:id/command', (req, res) => {
}
});
// ========== EVAL PROTOCOL ENDPOINTS ==========
// Receive eval response from webbridge (turtle -> webbridge -> server)
app.post('/api/turtle/eval-response', (req, res) => {
try {
const { turtleID, uuid, result, error } = req.body;
if (!turtleID || !uuid) {
return res.status(400).json({ error: 'Missing turtleID or uuid' });
}
const turtle = turtles.get(turtleID);
if (turtle) {
const handled = turtle.handleResponse(uuid, result, error);
if (handled) {
console.log(`📩 Eval response from T#${turtleID} uuid:${uuid.substring(0, 8)} - resolved`);
} else {
console.log(`📩 Eval response from T#${turtleID} uuid:${uuid.substring(0, 8)} - no matching pending command`);
}
} else {
console.log(`📩 Eval response from unknown turtle ${turtleID}`);
}
res.json({ success: true });
} catch (error) {
console.error('❌ Error processing eval response:', error);
res.status(500).json({ error: error.message });
}
});
// Set turtle state (state machine control)
app.post('/api/turtle/:id/state', (req, res) => {
try {
const turtleID = parseInt(req.params.id);
const { state, data } = req.body;
if (!state) {
return res.status(400).json({ error: 'Missing state name' });
}
const turtle = turtles.get(turtleID);
if (turtle) {
turtle.setState(state, data || {});
console.log(`🔄 State set for turtle ${turtleID}: ${state}`);
res.json({ success: true, state: turtle.stateName, description: turtle.stateDescription });
} else {
res.status(404).json({ error: 'Turtle not found' });
}
} catch (error) {
console.error('❌ Error setting state:', error);
res.status(500).json({ error: error.message });
}
});
// Get turtle state
app.get('/api/turtle/:id/state', (req, res) => {
try {
const turtleID = parseInt(req.params.id);
const turtle = turtles.get(turtleID);
if (turtle) {
res.json({
state: turtle.stateName,
description: turtle.stateDescription,
turtleID,
});
} else {
res.status(404).json({ error: 'Turtle not found' });
}
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Execute Lua code on a turtle directly (for debugging/admin)
app.post('/api/turtle/:id/exec', async (req, res) => {
try {
const turtleID = parseInt(req.params.id);
const { code, timeout } = req.body;
if (!code) {
return res.status(400).json({ error: 'Missing code' });
}
const turtle = turtles.get(turtleID);
if (!turtle) {
return res.status(404).json({ error: 'Turtle not found' });
}
const result = await turtle.exec(code, timeout || 30000);
res.json({ success: true, result });
} catch (error) {
console.error(`❌ Exec error for turtle ${req.params.id}:`, error.message);
res.json({ success: false, error: error.message });
}
});
// Bulk blocks discovery (from webbridge batch uploads)
app.post('/api/world/blocks/batch', (req, res) => {
try {
const { blocks, turtleID } = req.body;
if (!Array.isArray(blocks)) {
return res.status(400).json({ error: 'blocks must be an array' });
}
let stored = 0;
for (const block of blocks) {
if (block.x !== undefined && block.y !== undefined && block.z !== undefined && block.name) {
storeBlock(block.x, block.y, block.z, { name: block.name, metadata: block.metadata || 0 }, turtleID || block.discoveredBy);
stored++;
}
}
// Broadcast to web clients
if (stored > 0) {
broadcastToClients({
type: 'blocks_discovered',
blocks: blocks.filter(b => b.x !== undefined && b.name).map(b => ({
x: b.x, y: b.y, z: b.z,
name: b.name,
metadata: b.metadata || 0,
discoveredBy: turtleID || b.discoveredBy,
timestamp: Date.now(),
})),
});
}
res.json({ success: true, stored });
} catch (error) {
console.error('❌ Error batch storing blocks:', error);
res.status(500).json({ error: error.message });
}
});
// ========== PATH RECORDING ENDPOINTS ==========
// Save a recorded path
@@ -835,8 +976,8 @@ app.post('/api/mining-areas/:areaId/close', (req, res) => {
app.get('/api/stats', (req, res) => {
try {
const stats = {
activeTurtles: turtleData.size,
turtles: turtleData.size,
activeTurtles: turtles.size,
turtles: turtles.size,
totalBlocks: worldBlocks.size,
blocks: worldBlocks.size,
savedHomes: turtleHomes.size,
@@ -1028,14 +1169,19 @@ app.post('/api/groups/:groupId/command', (req, res) => {
let successCount = 0;
for (const member of members) {
if (turtleData.has(member.turtle_id)) {
const turtle = turtleData.get(member.turtle_id);
turtle.pendingCommands = turtle.pendingCommands || [];
turtle.pendingCommands.push({
command,
param,
timestamp: Date.now()
});
const turtle = turtles.get(member.turtle_id);
if (turtle) {
if (command === 'set_state' || command === 'setState') {
const stateName = param?.state || param;
const stateData = param?.data || {};
turtle.setState(stateName, stateData);
} else {
turtle.pendingLegacyCommands.push({
command,
param,
timestamp: Date.now()
});
}
successCount++;
}
}