From e3abdb612cee7bdd52cf70c13322cbd8b8111b1e Mon Sep 17 00:00:00 2001 From: MayaTheShy Date: Fri, 20 Feb 2026 04:14:57 -0500 Subject: [PATCH] refactor: update Web Bridge to enhance WebSocket integration and streamline dashboard functionality --- webbridge.lua | 991 ++++++++++++++++++-------------------------------- 1 file changed, 358 insertions(+), 633 deletions(-) diff --git a/webbridge.lua b/webbridge.lua index 24a2431..29770cd 100644 --- a/webbridge.lua +++ b/webbridge.lua @@ -1,12 +1,14 @@ --- Web Bridge Dashboard for Turtle System --- Beautiful visual interface for 2x3 monitor setup --- Forwards turtle status updates to the web server +-- Web Bridge v2 (WebSocket Protocol) +-- Connects to server via WebSocket for instant bidirectional communication. +-- Forwards turtle modem messages to server and server commands to turtles. +-- Falls back to HTTP polling if WebSocket is unavailable. -local SERVER_URL = "http://beta:4200" -- Direct to Docker server on LAN +local SERVER_URL = "http://beta:4200" +local WS_URL = "ws://beta:4200/ws/bridge" local CHANNEL_RECEIVE = 101 local STATUS_CHANNEL = 102 local COMMAND_CHANNEL = 100 -local POCKET_CHANNEL = 103 -- Pocket computer communication +local POCKET_CHANNEL = 103 -- Find peripherals local modem = peripheral.find("modem") @@ -16,9 +18,7 @@ if not modem then error("No wireless modem found!") end --- Check if we have a monitor for dashboard local hasMonitor = monitor ~= nil - if hasMonitor then monitor.setTextScale(0.5) end @@ -27,12 +27,6 @@ modem.open(CHANNEL_RECEIVE) modem.open(STATUS_CHANNEL) modem.open(POCKET_CHANNEL) -print("Webbridge Channel Configuration:") -print(" CHANNEL_RECEIVE: " .. CHANNEL_RECEIVE) -print(" STATUS_CHANNEL: " .. STATUS_CHANNEL) -print(" COMMAND_CHANNEL: " .. COMMAND_CHANNEL .. " (transmit only)") -print(" POCKET_CHANNEL: " .. POCKET_CHANNEL) - -- Track turtles and stats local turtles = {} local stats = { @@ -43,8 +37,11 @@ local stats = { startTime = os.epoch("utc") } local activityLog = {} +local wsHandle = nil +local wsConnected = false + +-- ========== Dashboard Drawing ========== --- Dashboard drawing functions (only if monitor available) local function centerText(y, text, fg, bg) if not hasMonitor then return end local w = monitor.getSize() @@ -54,123 +51,89 @@ local function centerText(y, text, fg, bg) monitor.write(text) end -local function drawBox(x, y, width, height, color) - if not hasMonitor then return end - monitor.setBackgroundColor(color) - for dy = 0, height - 1 do - monitor.setCursorPos(x, y + dy) - monitor.write(string.rep(" ", width)) - end +local function getStatusColor(turtle) + if not turtle.lastSeen then return colors.red end + local timeSince = os.epoch("utc") - turtle.lastSeen + if timeSince < 10000 then return colors.lime + elseif timeSince < 30000 then return colors.yellow + else return colors.red end end local function formatTime(ms) local seconds = math.floor(ms / 1000) local minutes = math.floor(seconds / 60) local hours = math.floor(minutes / 60) - - if hours > 0 then - return string.format("%dh %dm", hours, minutes % 60) - elseif minutes > 0 then - return string.format("%dm %ds", minutes, seconds % 60) - else - return string.format("%ds", seconds) - end -end - -local function getStatusColor(turtle) - if not turtle.lastSeen then return colors.red end - - local timeSince = os.epoch("utc") - turtle.lastSeen - if timeSince < 10000 then - return colors.lime - elseif timeSince < 30000 then - return colors.yellow - else - return colors.red - end + if hours > 0 then return string.format("%dh %dm", hours, minutes % 60) + elseif minutes > 0 then return string.format("%dm %ds", minutes, seconds % 60) + else return string.format("%ds", seconds) end end local function drawDashboard() if not hasMonitor then return end - + local w, h = monitor.getSize() monitor.setBackgroundColor(colors.black) monitor.clear() - + -- Count turtles local turtleCount = 0 - for _ in pairs(turtles) do - turtleCount = turtleCount + 1 - end - - -- Simple header + for _ in pairs(turtles) do turtleCount = turtleCount + 1 end + + -- Header + local connIcon = wsConnected and "WS" or "HTTP" monitor.setBackgroundColor(colors.blue) - monitor.setTextColor(colors.white) monitor.setCursorPos(1, 1) monitor.clearLine() - centerText(1, "TURTLE BRIDGE - " .. turtleCount .. " ONLINE", colors.white, colors.blue) - + centerText(1, "TURTLE BRIDGE [" .. connIcon .. "] - " .. turtleCount .. " ONLINE", colors.white, colors.blue) + -- Status line monitor.setBackgroundColor(colors.gray) monitor.setCursorPos(1, 2) monitor.clearLine() monitor.setTextColor(colors.white) monitor.setCursorPos(2, 2) - monitor.write("MSG: " .. stats.messagesReceived .. " | CMD: " .. stats.commandsSent .. " | ERR: " .. stats.errors) - - -- Turtle list - much larger, starts at line 4 + monitor.write("MSG:" .. stats.messagesReceived .. " CMD:" .. stats.commandsSent .. " ERR:" .. stats.errors) + + -- Turtle list monitor.setBackgroundColor(colors.black) local startY = 4 - + if turtleCount == 0 then monitor.setTextColor(colors.gray) monitor.setCursorPos(2, startY) - monitor.write("No turtles connected. Waiting for signals...") + monitor.write("No turtles connected. Waiting...") else - -- Show all turtles in a compact list local y = startY - local count = 0 for id, turtle in pairs(turtles) do - if y >= h - 5 then break end -- Leave room for activity log - + if y >= h - 5 then break end local statusColor = getStatusColor(turtle) local timeSince = 0 if turtle.lastSeen then - local currentTime = os.epoch("utc") or 0 - timeSince = math.floor((currentTime - turtle.lastSeen) / 1000) + timeSince = math.floor((os.epoch("utc") - turtle.lastSeen) / 1000) end - + monitor.setCursorPos(2, y) monitor.setTextColor(statusColor) monitor.write("\7") - monitor.setTextColor(colors.white) - monitor.write(string.format(" Turtle #%-3d", id)) - + monitor.write(string.format(" T#%-3d", id)) monitor.setTextColor(colors.lightGray) - local state = (turtle.mode or "IDLE"):upper() - if #state > 12 then state = state:sub(1, 12) end - monitor.write(" " .. state) - - -- Position on same line + local tState = (turtle.mode or "IDLE"):upper() + if #tState > 10 then tState = tState:sub(1, 10) end + monitor.write(" " .. tState) + if turtle.position then monitor.setTextColor(colors.gray) - monitor.write(string.format(" [%d,%d,%d]", - turtle.position.x or 0, - turtle.position.y or 0, - turtle.position.z or 0)) + monitor.write(string.format(" [%d,%d,%d]", turtle.position.x or 0, turtle.position.y or 0, turtle.position.z or 0)) end - - -- Last seen + monitor.setTextColor(colors.gray) - monitor.write(string.format(" %ds", timeSince or 0)) - + monitor.write(string.format(" %ds", timeSince)) y = y + 1 - count = count + 1 end end - - -- Activity log (last 5 lines of screen) + + -- Activity log local logY = h - 4 monitor.setBackgroundColor(colors.gray) monitor.setCursorPos(1, logY) @@ -178,606 +141,368 @@ local function drawDashboard() monitor.setTextColor(colors.yellow) monitor.setCursorPos(2, logY) monitor.write("ACTIVITY LOG") - + logY = logY + 1 monitor.setBackgroundColor(colors.black) - - -- Show last 3 log entries for i = 1, math.min(3, #activityLog) do local entry = activityLog[i] monitor.setCursorPos(1, logY) monitor.clearLine() - monitor.setTextColor(colors.gray) - local time = os.date("%H:%M:%S", (entry.time or 0) / 1000) - monitor.write(time .. " ") - + monitor.write(os.date("%H:%M:%S", (entry.time or 0) / 1000) .. " ") monitor.setTextColor(entry.color or colors.white) local maxWidth = w - 10 local text = entry.text - if #text > maxWidth then - text = text:sub(1, maxWidth - 3) .. "..." - end + if #text > maxWidth then text = text:sub(1, maxWidth - 3) .. "..." end monitor.write(text) - logY = logY + 1 end end local function addLog(text, color) - table.insert(activityLog, 1, {text = text, color = color or colors.white, time = os.epoch("utc")}) - if #activityLog > 35 then - table.remove(activityLog) - end - - -- Also print to console if no monitor - if not hasMonitor then - print(text) - end + table.insert(activityLog, 1, { text = text, color = color or colors.white, time = os.epoch("utc") }) + if #activityLog > 35 then table.remove(activityLog) end + if not hasMonitor then print(text) end end +-- ========== WebSocket Send Helper ========== --- Function to send data to web server -local function sendToServer(data) - local success, result = pcall(function() - local jsonData = textutils.serializeJSON(data) - - local response = http.post( - SERVER_URL .. "/api/turtle/update", - jsonData, - {["Content-Type"] = "application/json"} - ) - - if response then - response.close() - return true - else - return false - end - end) - - return success and result +local function wsSend(data) + if wsHandle and wsConnected then + local ok = pcall(function() + wsHandle.send(textutils.serializeJSON(data)) + end) + return ok + end + return false end --- Function to poll for commands from server -local function pollCommands(turtleID) +-- ========== HTTP Fallback Functions ========== + +local function httpPost(path, data) local success, result = pcall(function() - local response = http.get(SERVER_URL .. "/api/turtle/" .. turtleID .. "/commands") - + local response = http.post(SERVER_URL .. path, textutils.serializeJSON(data), { ["Content-Type"] = "application/json" }) if response then local content = response.readAll() response.close() - - local data = textutils.unserializeJSON(content) - if data and data.commands then - return data.commands + return textutils.unserializeJSON(content) + end + return nil + end) + return success and result or nil +end + +local function httpGet(path) + local success, result = pcall(function() + local response = http.get(SERVER_URL .. path) + if response then + local content = response.readAll() + response.close() + return textutils.unserializeJSON(content) + end + return nil + end) + return success and result or nil +end + +-- ========== Forward Turtle Modem Message to Server ========== + +local function forwardToServer(message) + if not message or type(message) ~= "table" then return end + + local msgType = message.type + local turtleID = message.turtleID + + if msgType == "status" then + turtles[turtleID] = message + turtles[turtleID].lastSeen = os.epoch("utc") + addLog("T#" .. turtleID .. " status", colors.lightBlue) + + if wsSend(message) then + stats.serverUpdates = stats.serverUpdates + 1 + else + if httpPost("/api/turtle/update", message) then + stats.serverUpdates = stats.serverUpdates + 1 + else + stats.errors = stats.errors + 1 + addLog(" -> Server error", colors.red) end end - - return {} - end) - - if success then - return result - else - stats.errors = stats.errors + 1 - return {} + + elseif msgType == "eval_response" then + addLog("Eval resp T#" .. turtleID .. " " .. (message.uuid or "?"):sub(1, 8), colors.cyan) + if not wsSend({ type = "eval_response", turtleID = turtleID, uuid = message.uuid, result = message.result, error = message.error }) then + httpPost("/api/turtle/eval-response", { turtleID = turtleID, uuid = message.uuid, result = message.result, error = message.error }) + end + + elseif msgType == "request_home" then + addLog("T#" .. turtleID .. " requesting home", colors.cyan) + if wsConnected then + wsSend({ type = "request_home", turtleID = turtleID }) + else + local result = httpGet("/api/turtle/" .. turtleID .. "/home") + modem.transmit(COMMAND_CHANNEL, CHANNEL_RECEIVE, { + type = "home_position", + turtleID = turtleID, + homePosition = result and result.homePosition or nil + }) + end + + elseif msgType == "set_home" then + addLog("T#" .. turtleID .. " setting home", colors.cyan) + if wsConnected then + wsSend({ type = "set_home", turtleID = turtleID, position = message.position }) + else + local result = httpPost("/api/turtle/" .. turtleID .. "/home", { position = message.position }) + modem.transmit(COMMAND_CHANNEL, CHANNEL_RECEIVE, { + type = "home_set_confirm", + turtleID = turtleID, + homePosition = (result and result.success) and result.homePosition or message.position + }) + end + + elseif msgType == "blocks_discovered" then + local blocks = message.blocks or {} + if #blocks > 0 then + addLog("T#" .. turtleID .. " discovered " .. #blocks .. " blocks", colors.cyan) + if not wsSend({ type = "blocks_discovered", turtleID = turtleID, blocks = blocks }) then + for _, block in ipairs(blocks) do + httpPost("/api/world/blocks", { x = block.x, y = block.y, z = block.z, blockName = block.name, metadata = block.metadata or 0, discoveredBy = turtleID }) + end + end + end + + elseif msgType == "inventory_update" then + addLog("T#" .. turtleID .. " inventory", colors.cyan) + if not wsSend({ type = "event", turtleID = turtleID, eventType = "INVENTORY_UPDATE", message = message.inventory }) then + httpPost("/api/turtle/event", { turtleID = turtleID, type = "INVENTORY_UPDATE", message = message.inventory }) + end + + elseif msgType == "peripheral_attached" then + addLog("T#" .. turtleID .. " peripheral attached", colors.cyan) + if not wsSend({ type = "event", turtleID = turtleID, eventType = "PERIPHERAL_ATTACHED", message = message.peripherals }) then + httpPost("/api/turtle/event", { turtleID = turtleID, type = "PERIPHERAL_ATTACHED", message = message.peripherals }) + end + + elseif msgType == "peripheral_detached" then + addLog("T#" .. turtleID .. " peripheral detached: " .. (message.side or "?"), colors.cyan) + if not wsSend({ type = "event", turtleID = turtleID, eventType = "PERIPHERAL_DETACHED", message = message.side }) then + httpPost("/api/turtle/event", { turtleID = turtleID, type = "PERIPHERAL_DETACHED", message = message.side }) + 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 +-- ========== Handle Server Commands (from WS or HTTP poll) ========== + +local function handleServerCommand(data) + if data.type == "command" then + local turtleID = data.turtleID + local cmd = data.command + + if cmd and cmd.type == "eval" then + addLog("EVAL -> T#" .. turtleID .. " " .. (cmd.uuid or "?"):sub(1, 8), colors.yellow) + stats.commandsSent = stats.commandsSent + 1 + + local commandPacket = { + type = "eval", + uuid = cmd.uuid, + code = cmd.code, + target = turtleID, + } + + -- Send twice for reliability over modem + for i = 1, 2 do + modem.transmit(COMMAND_CHANNEL, CHANNEL_RECEIVE, commandPacket) + os.sleep(0.05) + end end - return false - end) - - return success + + elseif data.type == "home_position" then + modem.transmit(COMMAND_CHANNEL, CHANNEL_RECEIVE, { + type = "home_position", + turtleID = data.turtleID, + homePosition = data.homePosition + }) + addLog("Home -> T#" .. data.turtleID, colors.lime) + + elseif data.type == "home_set_confirm" then + modem.transmit(COMMAND_CHANNEL, CHANNEL_RECEIVE, { + type = "home_set_confirm", + turtleID = data.turtleID, + homePosition = data.homePosition + }) + addLog("Home confirmed T#" .. data.turtleID, colors.lime) + + elseif data.type == "init" then + local ids = data.turtleIDs or {} + addLog("Server knows " .. #ids .. " turtles", colors.lime) + end end --- Initial setup +-- ========== Handle Pocket Computer Messages ========== + +local function handlePocketMessage(message) + if type(message) ~= "table" or not message.from then return end + local pocketID = message.from + + if message.type == "turtle_command" then + addLog("Pocket #" .. pocketID .. " -> T#" .. message.turtleID .. ": " .. message.command, colors.magenta) + modem.transmit(COMMAND_CHANNEL, CHANNEL_RECEIVE, { + command = message.command, + param = message.param, + target = message.turtleID + }) + modem.transmit(POCKET_CHANNEL, POCKET_CHANNEL, { type = "command_ack", to = pocketID }) + + elseif message.type == "player_position" then + addLog("Pocket #" .. pocketID .. " GPS", colors.cyan) + if not wsSend({ type = "player_update", playerID = message.playerID, position = message.position }) then + httpPost("/api/player/update", { playerID = message.playerID, position = message.position, timestamp = message.timestamp }) + end + + elseif message.type == "server_stats_request" then + addLog("Pocket #" .. pocketID .. " stats", colors.yellow) + local result = httpGet("/api/stats") + modem.transmit(POCKET_CHANNEL, POCKET_CHANNEL, { + type = result and "server_stats" or "error", + to = pocketID, + data = result, + error = not result and "Failed to fetch" or nil + }) + + elseif message.type == "webbridge_control" then + addLog("Pocket #" .. pocketID .. " " .. message.command, colors.orange) + if message.command == "ping" then + modem.transmit(POCKET_CHANNEL, POCKET_CHANNEL, { type = "webbridge_log", to = pocketID, text = "Pong! (" .. (wsConnected and "WS" or "HTTP") .. ")" }) + elseif message.command == "status" then + local tc = 0 + for _ in pairs(turtles) do tc = tc + 1 end + modem.transmit(POCKET_CHANNEL, POCKET_CHANNEL, { + type = "webbridge_status", to = pocketID, + data = { messages = stats.messagesReceived, commands = stats.commandsSent, turtles = tc, uptime = os.epoch("utc") - stats.startTime, wsConnected = wsConnected } + }) + elseif message.command == "restart" then + modem.transmit(POCKET_CHANNEL, POCKET_CHANNEL, { type = "webbridge_log", to = pocketID, text = "Restarting..." }) + sleep(1) + os.reboot() + elseif message.command == "logs" then + for i = math.max(1, #activityLog - 5), #activityLog do + if activityLog[i] then + modem.transmit(POCKET_CHANNEL, POCKET_CHANNEL, { type = "webbridge_log", to = pocketID, text = activityLog[i].text }) + end + end + end + end +end + +-- ========== Initialization ========== + +print("Web Bridge v2 (WebSocket Protocol)") +print("Server: " .. SERVER_URL) +print("WS: " .. WS_URL) +print("Channels: RX=" .. CHANNEL_RECEIVE .. " STATUS=" .. STATUS_CHANNEL .. " CMD=" .. COMMAND_CHANNEL) + if hasMonitor then drawDashboard() - addLog("System initialized with monitor", colors.lime) -else - print("Web Bridge Started (No monitor)") - print("Listening for turtle updates...") - print("Server: " .. SERVER_URL) + addLog("System initialized", colors.lime) end -addLog("Listening on channels " .. STATUS_CHANNEL .. ", " .. CHANNEL_RECEIVE .. ", " .. POCKET_CHANNEL, colors.lightBlue) +-- ========== Main Loop ========== --- Debug: Print what channels are actually open -print("Opened channels:") -for i = 0, 65535 do - if modem.isOpen(i) then - print(" Channel " .. i .. " is OPEN") - end -end +parallel.waitForAny( + -- WebSocket connection manager (reconnects automatically) + function() + while true do + addLog("Connecting WebSocket...", colors.yellow) + local ok, handle = pcall(http.websocket, WS_URL) --- Start polling timer -local POLL_INTERVAL = 1 -- Poll every 1 second for responsive command execution -os.startTimer(POLL_INTERVAL) + if ok and handle then + wsHandle = handle + wsConnected = true + addLog("WebSocket connected!", colors.lime) --- Track which turtles we've sent commands to recently -local recentCommandSends = {} + -- Read messages from WebSocket + while true do + local readOk, msg = pcall(function() return wsHandle.receive(30) end) --- Main loop -local lastRefresh = os.epoch("utc") -print("🎧 Starting main event loop...") -while true do - local event, side, channel, replyChannel, message, distance = os.pullEvent() - - -- Log ALL modem messages for debugging - if event == "modem_message" then - print("🔔 MODEM MESSAGE RECEIVED!") - print(" Event: " .. event) - print(" Channel: " .. channel) - print(" Expected channels: " .. STATUS_CHANNEL .. " (status), " .. CHANNEL_RECEIVE .. " (receive), " .. POCKET_CHANNEL .. " (pocket)") - end - - if event == "timer" then - -- Poll for commands for all known turtles - for turtleID, turtleData in pairs(turtles) do - local commands = pollCommands(turtleID) - - -- Only send commands if we got some - if #commands > 0 then - addLog("Received " .. #commands .. " command(s) for Turtle #" .. turtleID, colors.cyan) - - -- Forward commands back to turtle - for _, cmd in ipairs(commands) do - stats.commandsSent = stats.commandsSent + 1 - - local commandPacket - - if cmd.type == "eval" then - -- New eval protocol command - commandPacket = { - type = "eval", - uuid = cmd.uuid, - code = cmd.code, - target = turtleID, - stateUpdate = cmd.stateUpdate - } - addLog(" EVAL: " .. (cmd.uuid or "?"):sub(1, 8) .. " -> T#" .. turtleID, colors.yellow) + if not readOk then + addLog("WS read error, reconnecting...", colors.red) + break + end + + if msg == nil then + -- Timeout, send keepalive + local pingOk = pcall(function() wsHandle.send('{"type":"ping"}') end) + if not pingOk then + addLog("WS ping failed, reconnecting...", colors.red) + break + end else - -- Legacy command protocol - commandPacket = { - command = cmd.command, - param = cmd.param, - target = turtleID - } - addLog(" CMD: " .. cmd.command .. " -> T#" .. turtleID, colors.yellow) - end - - -- Send command twice for reliability - for i = 1, 2 do - modem.transmit(COMMAND_CHANNEL, CHANNEL_RECEIVE, commandPacket) - os.sleep(0.05) + local parseOk, data = pcall(textutils.unserializeJSON, msg) + if parseOk and data then + handleServerCommand(data) + end end end - - -- Acknowledge that we sent the commands - os.sleep(0.3) - if acknowledgeCommands(turtleID) then - addLog(" ACK: Commands acknowledged", colors.lime) - else - addLog(" WARN: Failed to acknowledge", colors.orange) + + -- Clean up + pcall(function() wsHandle.close() end) + wsHandle = nil + wsConnected = false + addLog("WebSocket disconnected", colors.orange) + else + addLog("WS connect failed, retry in 5s", colors.red) + stats.errors = stats.errors + 1 + end + + sleep(5) + end + end, + + -- Modem message handler (turtles -> bridge -> server) + function() + while true do + local event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message") + stats.messagesReceived = stats.messagesReceived + 1 + + if channel == STATUS_CHANNEL or channel == CHANNEL_RECEIVE then + if type(message) == "table" then + forwardToServer(message) + end + elseif channel == POCKET_CHANNEL then + if type(message) == "table" then + handlePocketMessage(message) end - - 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, + + -- HTTP fallback polling (only active when WS is down) + function() + while true do + if not wsConnected then + for turtleID, _ in pairs(turtles) do + local result = httpGet("/api/turtle/" .. turtleID .. "/commands") + if result and result.commands and #result.commands > 0 then + addLog("HTTP poll: " .. #result.commands .. " cmd(s) for T#" .. turtleID, colors.cyan) + for _, cmd in ipairs(result.commands) do + handleServerCommand({ type = "command", turtleID = turtleID, command = cmd }) + end + httpPost("/api/turtle/" .. turtleID .. "/commands/ack", {}) + end + end end + sleep(wsConnected and 10 or 1) end - - -- Restart timer - os.startTimer(POLL_INTERVAL) - - -- Refresh display if we have a monitor - if hasMonitor then - local now = os.epoch("utc") - if now - lastRefresh > 2000 then + end, + + -- Dashboard refresh + function() + while true do + sleep(2) + if hasMonitor then drawDashboard() - lastRefresh = now - end - end - - elseif event == "modem_message" then - -- Only process messages on our channels - if channel == STATUS_CHANNEL or channel == CHANNEL_RECEIVE then - stats.messagesReceived = stats.messagesReceived + 1 - - if type(message) == "table" then - -- Debug: log what type of message we got - print("📨 Received message on channel " .. channel) - if message.type then - print(" Type: " .. message.type) - end - if message.turtleID then - print(" From turtle: " .. message.turtleID) - end - - if message.type == "status" then - local turtleID = message.turtleID - - print("✅ Processing status update from Turtle #" .. turtleID) - - -- Update turtle data - turtles[turtleID] = message - turtles[turtleID].lastSeen = os.epoch("utc") - - -- Count turtles - local count = 0 - for _ in pairs(turtles) do - count = count + 1 - end - print(" Total turtles tracked: " .. count) - - addLog("Turtle #" .. turtleID .. " - " .. (message.mode or "status"), colors.lightBlue) - - -- Forward to web server - local success = sendToServer(message) - if success then - stats.serverUpdates = stats.serverUpdates + 1 - addLog(" -> Forwarded to server", colors.lime) - else - stats.errors = stats.errors + 1 - addLog(" -> Server error", colors.red) - end - - elseif message.type == "request_home" then - -- Turtle requesting its home position from server - local turtleID = message.turtleID - addLog("Turtle #" .. turtleID .. " requesting home", colors.cyan) - - local success, result = pcall(function() - local response = http.get(SERVER_URL .. "/api/turtle/" .. turtleID .. "/home") - if response then - local content = response.readAll() - response.close() - local data = textutils.unserializeJSON(content) - return data - end - return nil - end) - - if success and result then - modem.transmit(COMMAND_CHANNEL, CHANNEL_RECEIVE, { - type = "home_position", - turtleID = turtleID, - homePosition = result.homePosition - }) - addLog(" -> Sent home position", colors.lime) - else - modem.transmit(COMMAND_CHANNEL, CHANNEL_RECEIVE, { - type = "home_position", - turtleID = turtleID, - homePosition = nil - }) - addLog(" -> No home on server", colors.yellow) - end - - elseif message.type == "set_home" then - -- Turtle setting its home position - local turtleID = message.turtleID - local position = message.position - addLog("Turtle #" .. turtleID .. " setting home", colors.cyan) - - local success, result = pcall(function() - local response = http.post( - SERVER_URL .. "/api/turtle/" .. turtleID .. "/home", - textutils.serializeJSON({position = position}), - {["Content-Type"] = "application/json"} - ) - if response then - local content = response.readAll() - response.close() - local data = textutils.unserializeJSON(content) - return data - end - return nil - end) - - if success and result and result.success then - modem.transmit(COMMAND_CHANNEL, CHANNEL_RECEIVE, { - type = "home_set_confirm", - turtleID = turtleID, - homePosition = result.homePosition - }) - addLog(" -> Home saved to server", colors.lime) - else - modem.transmit(COMMAND_CHANNEL, CHANNEL_RECEIVE, { - type = "home_set_confirm", - turtleID = turtleID, - homePosition = position - }) - addLog(" -> Server error, local only", colors.red) - end - - elseif message.type == "eval_response" then - -- Turtle eval response - forward to server - local turtleID = message.turtleID - local uuid = message.uuid - addLog("Eval response from T#" .. turtleID .. " uuid:" .. (uuid or "?"):sub(1, 8), colors.cyan) - - local success = pcall(function() - local response = http.post( - SERVER_URL .. "/api/turtle/eval-response", - textutils.serializeJSON({ - turtleID = turtleID, - uuid = uuid, - result = message.result, - error = message.error - }), - {["Content-Type"] = "application/json"} - ) - if response then - response.close() - end - end) - - if not success then - stats.errors = stats.errors + 1 - addLog(" -> Failed to forward eval response", colors.red) - end - - elseif message.type == "blocks_discovered" then - -- Turtle discovered new blocks - forward to server - local turtleID = message.turtleID - local blocks = message.blocks or {} - - if #blocks > 0 then - print("📦 Turtle #" .. turtleID .. " discovered " .. #blocks .. " blocks") - - -- Send each block to the server - for _, block in ipairs(blocks) do - local success = pcall(function() - local response = http.post( - SERVER_URL .. "/api/world/blocks", - textutils.serializeJSON({ - x = block.x, - y = block.y, - z = block.z, - blockName = block.name, - metadata = block.metadata or 0, - discoveredBy = turtleID - }), - {["Content-Type"] = "application/json"} - ) - if response then - response.close() - end - end) - - if not success then - stats.errors = stats.errors + 1 - end - end - - addLog(" -> Sent " .. #blocks .. " blocks to server", colors.lime) - end - - elseif message.type == "inventory_update" then - -- Turtle inventory changed in real-time - local turtleID = message.turtleID - addLog("Turtle #" .. turtleID .. " inventory update", colors.cyan) - - local success = pcall(function() - local response = http.post( - SERVER_URL .. "/api/turtle/event", - textutils.serializeJSON({ - turtleID = turtleID, - type = "INVENTORY_UPDATE", - message = message.inventory - }), - {["Content-Type"] = "application/json"} - ) - if response then - response.close() - end - end) - - if not success then - stats.errors = stats.errors + 1 - addLog(" -> Failed to forward inventory update", colors.red) - end - - elseif message.type == "peripheral_attached" then - -- Turtle peripheral attached in real-time - local turtleID = message.turtleID - addLog("Turtle #" .. turtleID .. " peripheral attached", colors.cyan) - - local success = pcall(function() - local response = http.post( - SERVER_URL .. "/api/turtle/event", - textutils.serializeJSON({ - turtleID = turtleID, - type = "PERIPHERAL_ATTACHED", - message = message.peripherals - }), - {["Content-Type"] = "application/json"} - ) - if response then - response.close() - end - end) - - if not success then - stats.errors = stats.errors + 1 - addLog(" -> Failed to forward peripheral attach", colors.red) - end - - elseif message.type == "peripheral_detached" then - -- Turtle peripheral detached in real-time - local turtleID = message.turtleID - addLog("Turtle #" .. turtleID .. " peripheral detached: " .. (message.side or "?"), colors.cyan) - - local success = pcall(function() - local response = http.post( - SERVER_URL .. "/api/turtle/event", - textutils.serializeJSON({ - turtleID = turtleID, - type = "PERIPHERAL_DETACHED", - message = message.side - }), - {["Content-Type"] = "application/json"} - ) - if response then - response.close() - end - end) - - if not success then - stats.errors = stats.errors + 1 - addLog(" -> Failed to forward peripheral detach", colors.red) - end - end - end - elseif channel == POCKET_CHANNEL then - -- Handle pocket computer requests - stats.messagesReceived = stats.messagesReceived + 1 - - if type(message) == "table" and message.from then - local pocketID = message.from - - if message.type == "turtle_command" then - -- Forward command to turtle - local turtleID = message.turtleID - addLog("Pocket #" .. pocketID .. " -> Turtle #" .. turtleID .. ": " .. message.command, colors.magenta) - - modem.transmit(COMMAND_CHANNEL, CHANNEL_RECEIVE, { - command = message.command, - param = message.param, - target = turtleID - }) - - -- Send acknowledgment - modem.transmit(POCKET_CHANNEL, POCKET_CHANNEL, { - type = "command_ack", - to = pocketID - }) - - elseif message.type == "player_position" then - -- Forward player position to server - addLog("Pocket #" .. pocketID .. " GPS update", colors.cyan) - - local success = pcall(function() - http.post( - SERVER_URL .. "/api/player/update", - textutils.serializeJSON({ - playerID = message.playerID, - position = message.position, - timestamp = message.timestamp - }), - {["Content-Type"] = "application/json"} - ) - end) - - if not success then - addLog(" -> Failed to send player position", colors.red) - end - - elseif message.type == "server_stats_request" then - -- Fetch server stats and send to pocket - addLog("Pocket #" .. pocketID .. " requesting stats", colors.yellow) - - local success, result = pcall(function() - local response = http.get(SERVER_URL .. "/api/stats") - if response then - local content = response.readAll() - response.close() - return textutils.unserializeJSON(content) - end - return nil - end) - - if success and result then - modem.transmit(POCKET_CHANNEL, POCKET_CHANNEL, { - type = "server_stats", - to = pocketID, - data = result - }) - else - modem.transmit(POCKET_CHANNEL, POCKET_CHANNEL, { - type = "error", - to = pocketID, - error = "Failed to fetch server stats" - }) - end - - elseif message.type == "webbridge_control" then - -- Handle webbridge control commands - addLog("Pocket #" .. pocketID .. " control: " .. message.command, colors.orange) - - if message.command == "ping" then - modem.transmit(POCKET_CHANNEL, POCKET_CHANNEL, { - type = "webbridge_log", - to = pocketID, - text = "Pong! Webbridge online" - }) - elseif message.command == "status" then - local turtleCount = 0 - for _ in pairs(turtles) do - turtleCount = turtleCount + 1 - end - - modem.transmit(POCKET_CHANNEL, POCKET_CHANNEL, { - type = "webbridge_status", - to = pocketID, - data = { - messages = stats.messagesReceived, - commands = stats.commandsSent, - turtles = turtleCount, - uptime = os.epoch("utc") - stats.startTime - } - }) - elseif message.command == "restart" then - modem.transmit(POCKET_CHANNEL, POCKET_CHANNEL, { - type = "webbridge_log", - to = pocketID, - text = "Restarting webbridge..." - }) - sleep(1) - os.reboot() - elseif message.command == "logs" then - -- Send recent log entries - for i = math.max(1, #activityLog - 5), #activityLog do - if activityLog[i] then - modem.transmit(POCKET_CHANNEL, POCKET_CHANNEL, { - type = "webbridge_log", - to = pocketID, - text = activityLog[i].text - }) - end - end - end - - elseif message.type == "web_interface_request" then - -- Send web interface info - modem.transmit(POCKET_CHANNEL, POCKET_CHANNEL, { - type = "webbridge_log", - to = pocketID, - text = "Web: " .. SERVER_URL - }) - end end end end -end +) \ No newline at end of file