Files
remoteturtle/webbridge.lua

410 lines
13 KiB
Lua

-- 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)
-- 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 (larger for vertical display)
drawBox(1, 1, w, 4, colors.blue)
centerText(2, "╔════════════════════════════════════════════════╗", colors.white, colors.blue)
centerText(3, "║ TURTLE NETWORK BRIDGE CONTROL CENTER ║", colors.white, colors.blue)
centerText(4, "╚════════════════════════════════════════════════╝", colors.white, colors.blue)
monitor.setCursorPos(2, 2)
monitor.setTextColor(colors.lime)
monitor.setBackgroundColor(colors.blue)
monitor.write("\7 ONLINE")
-- Stats box (left side, taller)
local statsY = 6
drawBox(2, statsY, 50, 20, colors.gray)
drawBox(3, statsY + 1, 48, 18, colors.black)
monitor.setTextColor(colors.yellow)
monitor.setBackgroundColor(colors.black)
monitor.setCursorPos(4, statsY + 1)
monitor.write("╔═══ SYSTEM STATISTICS ═══╗")
local statY = statsY + 3
-- Messages
monitor.setTextColor(colors.lightGray)
monitor.setCursorPos(5, statY)
monitor.write("Messages Received:")
monitor.setTextColor(colors.white)
monitor.setCursorPos(38, statY)
monitor.write(tostring(stats.messagesReceived))
statY = statY + 2
-- Commands
monitor.setTextColor(colors.lightGray)
monitor.setCursorPos(5, statY)
monitor.write("Commands Sent:")
monitor.setTextColor(colors.lime)
monitor.setCursorPos(38, statY)
monitor.write(tostring(stats.commandsSent))
statY = statY + 2
-- Server Updates
monitor.setTextColor(colors.lightGray)
monitor.setCursorPos(5, statY)
monitor.write("Server Updates:")
monitor.setTextColor(colors.lightBlue)
monitor.setCursorPos(38, statY)
monitor.write(tostring(stats.serverUpdates))
statY = statY + 2
-- Errors
monitor.setTextColor(colors.lightGray)
monitor.setCursorPos(5, statY)
monitor.write("Errors:")
monitor.setTextColor(stats.errors > 0 and colors.red or colors.lime)
monitor.setCursorPos(38, statY)
monitor.write(tostring(stats.errors))
statY = statY + 2
-- Uptime
monitor.setTextColor(colors.lightGray)
monitor.setCursorPos(5, statY)
monitor.write("Uptime:")
monitor.setTextColor(colors.white)
monitor.setCursorPos(38, statY)
monitor.write(formatTime(os.epoch("utc") - stats.startTime))
statY = statY + 3
-- Connection info
monitor.setTextColor(colors.gray)
monitor.setCursorPos(5, statY)
monitor.write("Server: " .. SERVER_URL:sub(8))
statY = statY + 1
monitor.setCursorPos(5, statY)
monitor.write("Channels: " .. STATUS_CHANNEL .. "/" .. COMMAND_CHANNEL)
-- Turtle list (right side, much taller)
local turtleX = 54
local turtleWidth = w - turtleX - 1
drawBox(turtleX, statsY, turtleWidth, 20, colors.gray)
drawBox(turtleX + 1, statsY + 1, turtleWidth - 2, 18, colors.black)
monitor.setTextColor(colors.yellow)
monitor.setCursorPos(turtleX + 2, statsY + 1)
monitor.write("╔═══ ACTIVE TURTLES ═══╗")
local turtleY = statsY + 3
local count = 0
for id, turtle in pairs(turtles) do
if count >= 15 then break end -- Show up to 15 turtles
local statusColor = getStatusColor(turtle)
monitor.setCursorPos(turtleX + 2, turtleY)
monitor.setTextColor(statusColor)
monitor.write("\7")
monitor.setTextColor(colors.white)
monitor.write(string.format(" T#%-2d", id))
monitor.setCursorPos(turtleX + 12, turtleY)
monitor.setTextColor(colors.lightGray)
if turtle.state then
local state = turtle.state:upper()
if #state > 10 then state = state:sub(1, 10) end
monitor.write(state)
else
monitor.write("UNKNOWN")
end
-- Show position if available
if turtle.position then
turtleY = turtleY + 1
monitor.setCursorPos(turtleX + 4, turtleY)
monitor.setTextColor(colors.gray)
monitor.write(string.format("X:%d Y:%d Z:%d",
turtle.position.x or 0,
turtle.position.y or 0,
turtle.position.z or 0))
end
turtleY = turtleY + 2
count = count + 1
end
if count == 0 then
monitor.setCursorPos(turtleX + 2, statsY + 5)
monitor.setTextColor(colors.gray)
monitor.write("No turtles detected")
monitor.setCursorPos(turtleX + 2, statsY + 6)
monitor.write("Waiting for signals...")
end
-- Activity log (bottom section, MUCH taller for vertical display)
local logY = 28
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 ═══╗")
-- Calculate how many log lines we can show (huge vertical space!)
local maxLines = h - logY - 4
for i = 1, math.min(#activityLog, maxLines) do
local entry = activityLog[i]
monitor.setCursorPos(4, logY + 2 + i)
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)
-- Truncate long messages to fit width
local maxTextWidth = w - 18
local text = entry.text
if #text > maxTextWidth then
text = text:sub(1, maxTextWidth - 3) .. "..."
end
monitor.write(text)
end
-- Footer
monitor.setBackgroundColor(colors.gray)
monitor.setCursorPos(1, h)
monitor.clearLine()
centerText(h, "Ctrl+T to terminate | Monitoring " .. STATUS_CHANNEL .. " & " .. COMMAND_CHANNEL, 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 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
-- 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)
-- Start polling timer
local POLL_INTERVAL = 1 -- Poll every 1 second
os.startTimer(POLL_INTERVAL)
-- Main loop
local lastRefresh = os.epoch("utc")
while true do
local event, side, channel, replyChannel, message, distance = os.pullEvent()
if event == "timer" then
-- Poll for commands for all known turtles
for turtleID, turtleData in pairs(turtles) do
local commands = pollCommands(turtleID)
-- Forward commands back to turtle
for _, cmd in ipairs(commands) do
stats.commandsSent = stats.commandsSent + 1
addLog("CMD: " .. cmd.command .. " -> Turtle #" .. turtleID, colors.yellow)
local commandPacket = {
command = cmd.command,
param = cmd.param,
target = turtleID
}
modem.transmit(COMMAND_CHANNEL, CHANNEL_RECEIVE, commandPacket)
-- Debug: show what we're sending
if not hasMonitor then
print(" Sent to channel " .. COMMAND_CHANNEL .. ": target=" .. turtleID)
end
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
if message.type == "status" then
local turtleID = message.turtleID
-- Update turtle data
turtles[turtleID] = message
turtles[turtleID].lastSeen = os.epoch("utc")
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)
else
stats.errors = stats.errors + 1
addLog(" -> Server error", colors.red)
end
end
end
end
end
end