feat: Implement web bridge dashboard for turtle network monitoring
This commit is contained in:
370
webbridge.lua
370
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
|
||||
|
||||
Reference in New Issue
Block a user