feat: Implement command acknowledgment and improve command polling reliability for turtles

This commit is contained in:
MayaTheShy
2026-02-19 23:27:23 -05:00
parent 6e50a109dc
commit 4bda9c536b
2 changed files with 104 additions and 20 deletions

View File

@@ -254,10 +254,20 @@ app.get('/api/turtle/:id/commands', (req, res) => {
commands.forEach(cmd => console.log(` - ${cmd.command}`, cmd.param ? `(${cmd.param})` : '')); commands.forEach(cmd => console.log(` - ${cmd.command}`, cmd.param ? `(${cmd.param})` : ''));
} }
// Clear pending commands // Mark commands as sent but don't clear them yet - they'll be cleared on next poll if turtle is processing
turtle.pendingCommands = []; // This allows the webbridge to retry if the turtle didn't receive them
if (!turtle.lastCommandPollTime || (Date.now() - turtle.lastCommandPollTime) > 5000) {
res.json({ commands }); // First poll or more than 5 seconds since last poll - send commands
turtle.lastCommandPollTime = Date.now();
res.json({ commands });
} else {
// Recent poll - assume previous commands were received, clear them
if (commands.length > 0) {
console.log(`✅ Clearing ${commands.length} command(s) for turtle ${turtleID} (acknowledged)`);
turtle.pendingCommands = [];
}
res.json({ commands: [] });
}
} else { } else {
res.json({ commands: [] }); res.json({ commands: [] });
} }
@@ -267,6 +277,30 @@ app.get('/api/turtle/:id/commands', (req, res) => {
} }
}); });
// Acknowledge commands were delivered (clears them)
app.post('/api/turtle/:id/commands/ack', (req, res) => {
try {
const turtleID = parseInt(req.params.id);
if (turtleData.has(turtleID)) {
const turtle = turtleData.get(turtleID);
const clearedCount = (turtle.pendingCommands || []).length;
if (clearedCount > 0) {
console.log(`✅ Turtle ${turtleID} acknowledged ${clearedCount} command(s)`);
turtle.pendingCommands = [];
}
res.json({ success: true, cleared: clearedCount });
} else {
res.status(404).json({ error: 'Turtle not found' });
}
} catch (error) {
console.error('❌ Error acknowledging commands:', error);
res.status(500).json({ error: error.message });
}
});
// Get all turtles // Get all turtles
app.get('/api/turtles', (req, res) => { app.get('/api/turtles', (req, res) => {
res.json({ res.json({

View File

@@ -320,6 +320,25 @@ local function pollCommands(turtleID)
end end
end end
-- Function to acknowledge commands were sent
local function acknowledgeCommands(turtleID)
local success = pcall(function()
local response = http.post(
SERVER_URL .. "/api/turtle/" .. turtleID .. "/commands/ack",
"{}",
{["Content-Type"] = "application/json"}
)
if response then
response.close()
return true
end
return false
end)
return success
end
-- Initial setup -- Initial setup
if hasMonitor then if hasMonitor then
drawDashboard() drawDashboard()
@@ -333,9 +352,12 @@ end
addLog("Listening on channels " .. STATUS_CHANNEL .. " and " .. CHANNEL_RECEIVE, colors.lightBlue) addLog("Listening on channels " .. STATUS_CHANNEL .. " and " .. CHANNEL_RECEIVE, colors.lightBlue)
-- Start polling timer -- Start polling timer
local POLL_INTERVAL = 1 -- Poll every 1 second local POLL_INTERVAL = 2 -- Poll every 2 seconds (reduced frequency for better reliability)
os.startTimer(POLL_INTERVAL) os.startTimer(POLL_INTERVAL)
-- Track which turtles we've sent commands to recently
local recentCommandSends = {}
-- Main loop -- Main loop
local lastRefresh = os.epoch("utc") local lastRefresh = os.epoch("utc")
while true do while true do
@@ -346,23 +368,51 @@ while true do
for turtleID, turtleData in pairs(turtles) do for turtleID, turtleData in pairs(turtles) do
local commands = pollCommands(turtleID) local commands = pollCommands(turtleID)
-- Forward commands back to turtle -- Only send commands if we got some
for _, cmd in ipairs(commands) do if #commands > 0 then
stats.commandsSent = stats.commandsSent + 1 addLog("Received " .. #commands .. " command(s) for Turtle #" .. turtleID, colors.cyan)
addLog("CMD: " .. cmd.command .. " -> Turtle #" .. turtleID, colors.yellow)
local commandPacket = { -- Forward commands back to turtle
command = cmd.command, for _, cmd in ipairs(commands) do
param = cmd.param, stats.commandsSent = stats.commandsSent + 1
target = turtleID addLog(" CMD: " .. cmd.command .. " -> Turtle #" .. turtleID, colors.yellow)
}
local commandPacket = {
modem.transmit(COMMAND_CHANNEL, CHANNEL_RECEIVE, commandPacket) command = cmd.command,
param = cmd.param,
-- Debug: show what we're sending target = turtleID
if not hasMonitor then }
print(" Sent to channel " .. COMMAND_CHANNEL .. ": target=" .. turtleID)
-- Send command multiple times for reliability
for i = 1, 3 do
modem.transmit(COMMAND_CHANNEL, CHANNEL_RECEIVE, commandPacket)
os.sleep(0.05) -- Small delay between retransmissions
end
-- Debug: show what we're sending
if not hasMonitor then
print(" Sent to channel " .. COMMAND_CHANNEL .. ": target=" .. turtleID)
end
end end
-- Acknowledge that we sent the commands
os.sleep(0.5) -- Give turtle time to receive
if acknowledgeCommands(turtleID) then
addLog(" ACK: Commands acknowledged", colors.lime)
else
addLog(" WARN: Failed to acknowledge", colors.orange)
end
-- Mark that we sent commands to this turtle
recentCommandSends[turtleID] = os.epoch("utc")
end
end
-- Clean up old command send timestamps (older than 10 seconds)
local now = os.epoch("utc")
for turtleID, timestamp in pairs(recentCommandSends) do
if now - timestamp > 10000 then
recentCommandSends[turtleID] = nil
end end
end end