diff --git a/webbridge.lua b/webbridge.lua index 3406e8d..ccca413 100644 --- a/webbridge.lua +++ b/webbridge.lua @@ -1,108 +1,342 @@ --- Web Bridge for Turtle System --- This script forwards turtle status updates to the web server --- Place this on a computer connected to the wireless network +-- 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 +-- 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) -print("Web Bridge Started") -print("Listening for turtle updates...") -print("Server: " .. SERVER_URL) - --- Track turtles to forward updates +-- 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() + + -- Header + drawBox(1, 1, w, 3, colors.blue) + centerText(2, "=== TURTLE NETWORK BRIDGE ===", colors.white, colors.blue) + monitor.setCursorPos(2, 2) + monitor.setTextColor(colors.lime) + monitor.setBackgroundColor(colors.blue) + monitor.write("\7 ONLINE") + monitor.setCursorPos(w - 15, 2) + monitor.setTextColor(colors.lightBlue) + monitor.write("Server: ACTIVE") + + -- Stats box + local startY = 4 + drawBox(2, startY, 30, 8, colors.gray) + drawBox(3, startY + 1, 28, 6, colors.black) + + monitor.setTextColor(colors.yellow) + monitor.setBackgroundColor(colors.black) + monitor.setCursorPos(4, startY + 1) + monitor.write("SYSTEM STATISTICS") + + monitor.setTextColor(colors.lightGray) + monitor.setCursorPos(4, startY + 3) + monitor.write("Messages Received:") + monitor.setTextColor(colors.white) + monitor.setCursorPos(23, startY + 3) + monitor.write(tostring(stats.messagesReceived)) + + monitor.setTextColor(colors.lightGray) + monitor.setCursorPos(4, startY + 4) + monitor.write("Commands Sent:") + monitor.setTextColor(colors.lime) + monitor.setCursorPos(23, startY + 4) + monitor.write(tostring(stats.commandsSent)) + + monitor.setTextColor(colors.lightGray) + monitor.setCursorPos(4, startY + 5) + monitor.write("Server Updates:") + monitor.setTextColor(colors.lightBlue) + monitor.setCursorPos(23, startY + 5) + monitor.write(tostring(stats.serverUpdates)) + + monitor.setTextColor(colors.lightGray) + monitor.setCursorPos(4, startY + 6) + monitor.write("Uptime:") + monitor.setTextColor(colors.white) + monitor.setCursorPos(23, startY + 6) + monitor.write(formatTime(os.epoch("utc") - stats.startTime)) + + -- Turtle list + local startX = 34 + local boxWidth = w - startX - 1 + drawBox(startX, startY, boxWidth, 8, colors.gray) + drawBox(startX + 1, startY + 1, boxWidth - 2, 6, colors.black) + + monitor.setTextColor(colors.yellow) + monitor.setCursorPos(startX + 2, startY + 1) + monitor.write("ACTIVE TURTLES") + + local y = startY + 3 + local count = 0 + for id, turtle in pairs(turtles) do + if count >= 4 then break end + + local statusColor = getStatusColor(turtle) + monitor.setCursorPos(startX + 2, y) + monitor.setTextColor(statusColor) + monitor.write("\7") + + monitor.setTextColor(colors.white) + monitor.write(" Turtle #" .. id) + + monitor.setCursorPos(startX + 16, y) + monitor.setTextColor(colors.lightGray) + if turtle.state then + monitor.write(turtle.state:upper()) + else + monitor.write("UNKNOWN") + end + + y = y + 1 + count = count + 1 + end + + if count == 0 then + monitor.setCursorPos(startX + 2, startY + 4) + monitor.setTextColor(colors.gray) + monitor.write("No turtles detected") + end + + -- Activity log + local logY = 13 + drawBox(2, logY, w - 2, h - logY - 1, colors.gray) + drawBox(3, logY + 1, w - 4, h - logY - 3, colors.black) + + monitor.setTextColor(colors.yellow) + monitor.setCursorPos(4, logY + 1) + monitor.write("ACTIVITY LOG") + + local maxLines = h - logY - 3 + for i = 1, math.min(#activityLog, maxLines) do + local entry = activityLog[i] + monitor.setCursorPos(4, logY + 2 + i - 1) + monitor.setBackgroundColor(colors.black) + + monitor.setTextColor(colors.gray) + local time = os.date("%H:%M:%S", entry.time / 1000) + monitor.write("[" .. time .. "] ") + + monitor.setTextColor(entry.color) + monitor.write(entry.text) + end + + -- Footer + monitor.setBackgroundColor(colors.gray) + monitor.setCursorPos(1, h) + monitor.clearLine() + centerText(h, "Server: " .. SERVER_URL:sub(8), colors.white, colors.gray) +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 jsonData = textutils.serializeJSON(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) - 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 + return success and result end -- Function to poll for commands from server local function pollCommands(turtleID) - local response = http.get(SERVER_URL .. "/api/turtle/" .. turtleID .. "/commands") - - if response then - local content = response.readAll() - response.close() + local success, result = pcall(function() + local response = http.get(SERVER_URL .. "/api/turtle/" .. turtleID .. "/commands") - local data = textutils.unserializeJSON(content) - if data and data.commands then - return data.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 - end + + return {} + end) - return {} + if success then + return result + else + stats.errors = stats.errors + 1 + return {} + end 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 .. " and " .. CHANNEL_RECEIVE, colors.lightBlue) + -- Main loop +local lastRefresh = os.epoch("utc") while true do - local event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message") + local event, side, channel, replyChannel, message, distance = os.pullEvent() - -- Only process messages on our channels - if channel == STATUS_CHANNEL or channel == CHANNEL_RECEIVE then - print("Received modem message on channel " .. channel) - - if type(message) == "table" then - print(" Message type: " .. (message.type or "no type")) - print(" Turtle ID: " .. tostring(message.turtleID)) + if 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 message.type == "status" then - -- Status update from turtle - print("✓ Valid status from Turtle " .. (message.turtleID or "?")) - - -- Update local cache - turtles[message.turtleID] = message - - -- Forward to web server - local success = sendToServer(message) - if success then - print(" -> Sent to server") + if type(message) == "table" then + if message.type == "status" then + local turtleID = message.turtleID - -- Check for commands from server - local commands = pollCommands(message.turtleID) + -- Update turtle data + turtles[turtleID] = message + turtles[turtleID].lastSeen = os.epoch("utc") - -- Forward commands back to turtle - for _, cmd in ipairs(commands) do - print(" -> Command for Turtle " .. message.turtleID .. ": " .. cmd.command) - modem.transmit(100, 101, { - command = cmd.command, - param = cmd.param, - target = message.turtleID - }) + addLog("Turtle #" .. turtleID .. " - " .. (message.state 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) + + -- Check for commands from server + local commands = pollCommands(turtleID) + + -- Forward commands back to turtle + for _, cmd in ipairs(commands) do + stats.commandsSent = stats.commandsSent + 1 + addLog(" -> CMD: " .. cmd.command .. " to Turtle #" .. turtleID, colors.yellow) + + modem.transmit(COMMAND_CHANNEL, CHANNEL_RECEIVE, { + command = cmd.command, + param = cmd.param, + target = turtleID + }) + end + else + stats.errors = stats.errors + 1 + addLog(" -> Server error", colors.red) end - else - print(" -> Failed to send to server") end - else - print(" Ignoring non-status message") end - else - -- Ignore non-table messages (from other mods/systems) - -- Silently skip to avoid spam end end - sleep(0.1) -- Small delay to prevent overwhelming the system + -- Refresh display every 2 seconds if we have a monitor + if hasMonitor then + local now = os.epoch("utc") + if now - lastRefresh > 2000 then + drawDashboard() + lastRefresh = now + end + end end