-- Web Bridge Dashboard for Turtle System -- Beautiful visual interface for 2x3 monitor setup -- Forwards turtle status updates to the web server local SERVER_URL = "http://10.10.10.6:4200" -- Change to your server address local CHANNEL_RECEIVE = 101 local STATUS_CHANNEL = 102 local COMMAND_CHANNEL = 100 local POCKET_CHANNEL = 103 -- Pocket computer communication -- Find peripherals local modem = peripheral.find("modem") local monitor = peripheral.find("monitor") 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 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 = { messagesReceived = 0, commandsSent = 0, serverUpdates = 0, errors = 0, startTime = os.epoch("utc") } local activityLog = {} -- Dashboard drawing functions (only if monitor available) local function centerText(y, text, fg, bg) if not hasMonitor then return end local w = monitor.getSize() monitor.setCursorPos(math.floor((w - #text) / 2) + 1, y) monitor.setTextColor(fg or colors.white) monitor.setBackgroundColor(bg or colors.black) 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 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 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 monitor.setBackgroundColor(colors.blue) monitor.setTextColor(colors.white) monitor.setCursorPos(1, 1) monitor.clearLine() centerText(1, "TURTLE BRIDGE - " .. 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.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...") 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 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) end monitor.setCursorPos(2, y) monitor.setTextColor(statusColor) monitor.write("\7") monitor.setTextColor(colors.white) monitor.write(string.format(" Turtle #%-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 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)) end -- Last seen monitor.setTextColor(colors.gray) monitor.write(string.format(" %ds", timeSince or 0)) y = y + 1 count = count + 1 end end -- Activity log (last 5 lines of screen) local logY = h - 4 monitor.setBackgroundColor(colors.gray) monitor.setCursorPos(1, logY) monitor.clearLine() 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.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 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 end -- 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 end -- Function to poll for commands from server local function pollCommands(turtleID) local success, result = pcall(function() local response = http.get(SERVER_URL .. "/api/turtle/" .. turtleID .. "/commands") if response then local content = response.readAll() response.close() local data = textutils.unserializeJSON(content) if data and data.commands then return data.commands end end return {} end) if success then return result else stats.errors = stats.errors + 1 return {} 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 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) end addLog("Listening on channels " .. STATUS_CHANNEL .. ", " .. CHANNEL_RECEIVE .. ", " .. POCKET_CHANNEL, colors.lightBlue) -- 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 -- Start polling timer local POLL_INTERVAL = 2 -- Poll every 2 seconds (reduced frequency for better reliability) os.startTimer(POLL_INTERVAL) -- Track which turtles we've sent commands to recently local recentCommandSends = {} -- 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) else -- Legacy command protocol commandPacket = { command = cmd.command, param = cmd.param, target = turtleID } addLog(" CMD: " .. cmd.command .. " -> T#" .. turtleID, colors.yellow) end -- Send command multiple times for reliability for i = 1, 3 do modem.transmit(COMMAND_CHANNEL, CHANNEL_RECEIVE, commandPacket) os.sleep(0.05) end end -- Acknowledge that we sent the commands os.sleep(1.5) if acknowledgeCommands(turtleID) then addLog(" ACK: Commands acknowledged", colors.lime) else addLog(" WARN: Failed to acknowledge", colors.orange) 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 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 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 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