diff --git a/pocketcontrol.lua b/pocketcontrol.lua new file mode 100644 index 0000000..614926d --- /dev/null +++ b/pocketcontrol.lua @@ -0,0 +1,623 @@ +-- Unified Pocket Control Center +-- Combines turtle control, GPS tracking, server management, and webbridge control +-- Communicates wirelessly with webbridge - NO direct HTTP calls + +local CHANNEL_SEND = 100 +local CHANNEL_RECEIVE = 101 +local STATUS_CHANNEL = 102 +local POCKET_CHANNEL = 103 -- Pocket <-> Webbridge communication + +-- Find modem +local modem = peripheral.find("modem") +if not modem then + error("No wireless modem found!") +end + +-- Equip modem if this is a pocket computer +if pocket then + pocket.equipBack() + modem = peripheral.find("modem") +end + +modem.open(CHANNEL_RECEIVE) +modem.open(STATUS_CHANNEL) +modem.open(POCKET_CHANNEL) + +local w, h = term.getSize() + +-- State +local turtles = {} +local selectedTurtle = nil +local mode = "overview" -- overview, control, gps, webbridge, server +local myPosition = nil +local webbridgeStatus = nil +local serverStats = nil +local logMessages = {} +local maxLogs = 50 + +-- Button system +local buttons = {} + +local function addLog(message, color) + table.insert(logMessages, 1, { + text = message, + time = os.epoch("utc"), + color = color or colors.white + }) + if #logMessages > maxLogs then + table.remove(logMessages) + end +end + +local function clearButtons() + buttons = {} +end + +local function addButton(x, y, width, height, label, action, color) + table.insert(buttons, { + x = x, y = y, width = width, height = height, + label = label, action = action, color = color or colors.gray + }) +end + +local function drawButton(btn, highlight) + term.setCursorPos(btn.x, btn.y) + local bg = highlight and colors.white or btn.color + local fg = highlight and colors.black or colors.white + term.setBackgroundColor(bg) + term.setTextColor(fg) + + local text = btn.label + local padding = math.floor((btn.width - #text) / 2) + + for dy = 0, btn.height - 1 do + term.setCursorPos(btn.x, btn.y + dy) + if dy == math.floor(btn.height / 2) then + term.write(string.rep(" ", padding) .. text .. string.rep(" ", btn.width - padding - #text)) + else + term.write(string.rep(" ", btn.width)) + end + end +end + +local function checkButton(x, y) + for _, btn in ipairs(buttons) do + if x >= btn.x and x < btn.x + btn.width and + y >= btn.y and y < btn.y + btn.height then + return btn + end + end +end + +-- Update my GPS position and send to webbridge +local function updateMyPosition() + local x, y, z = gps.locate(2) + if x then + myPosition = {x = math.floor(x), y = math.floor(y), z = math.floor(z)} + -- Send to webbridge (which will forward to server) + modem.transmit(POCKET_CHANNEL, CHANNEL_RECEIVE, { + type = "player_position", + playerID = os.getComputerID(), + position = myPosition, + timestamp = os.epoch("utc") + }) + return true + end + return false +end + +-- Send command to turtle via webbridge +local function sendCommand(turtleID, command, param) + modem.transmit(POCKET_CHANNEL, CHANNEL_RECEIVE, { + type = "turtle_command", + turtleID = turtleID, + command = command, + param = param, + from = os.getComputerID() + }) + addLog("Sent: " .. command .. " -> Turtle #" .. turtleID, colors.yellow) +end + +-- Send webbridge control command +local function sendWebbridgeCommand(command) + modem.transmit(POCKET_CHANNEL, CHANNEL_RECEIVE, { + type = "webbridge_control", + command = command, + from = os.getComputerID() + }) + addLog("Webbridge: " .. command, colors.cyan) +end + +-- Request server stats via webbridge +local function fetchServerStats() + modem.transmit(POCKET_CHANNEL, CHANNEL_RECEIVE, { + type = "server_stats_request", + from = os.getComputerID() + }) +end + +-- Draw header +local function drawHeader() + term.setBackgroundColor(colors.blue) + term.setTextColor(colors.white) + term.setCursorPos(1, 1) + term.clearLine() + + local title = "POCKET CONTROL CENTER" + term.setCursorPos(math.floor((w - #title) / 2) + 1, 1) + term.write(title) + + -- Show mode indicator + term.setCursorPos(w - 10, 1) + local modeText = mode:upper():sub(1, 10) + term.write(modeText) +end + +-- Draw mode tabs +local function drawTabs() + clearButtons() + local tabWidth = math.floor(w / 5) + local y = 2 + + addButton(1, y, tabWidth, 1, "TURTLES", function() mode = "overview" end, + mode == "overview" and colors.green or colors.gray) + addButton(tabWidth + 1, y, tabWidth, 1, "CONTROL", function() mode = "control" end, + mode == "control" and colors.green or colors.gray) + addButton(tabWidth * 2 + 1, y, tabWidth, 1, "GPS", function() mode = "gps" end, + mode == "gps" and colors.green or colors.gray) + addButton(tabWidth * 3 + 1, y, tabWidth, 1, "BRIDGE", function() mode = "webbridge" end, + mode == "webbridge" and colors.green or colors.gray) + addButton(tabWidth * 4 + 1, y, w - tabWidth * 4, 1, "SERVER", function() mode = "server" end, + mode == "server" and colors.green or colors.gray) + + for _, btn in ipairs(buttons) do + drawButton(btn, false) + end +end + +-- Draw overview mode +local function drawOverview() + term.setBackgroundColor(colors.black) + local y = 4 + + term.setTextColor(colors.white) + term.setCursorPos(2, y) + term.write("Turtles Online: " .. #turtles) + y = y + 2 + + if #turtles == 0 then + term.setTextColor(colors.gray) + term.setCursorPos(2, y) + term.write("No turtles detected") + else + for i, turtle in ipairs(turtles) do + if y >= h - 2 then break end + + term.setCursorPos(2, y) + term.setTextColor(colors.yellow) + term.write(string.format("Turtle #%-3d", turtle.turtleID)) + + term.setTextColor(colors.lightGray) + term.setCursorPos(15, y) + term.write(turtle.mode or "idle") + + if turtle.position then + term.setCursorPos(25, y) + term.setTextColor(colors.gray) + term.write(string.format("[%d,%d,%d]", turtle.position.x, turtle.position.y, turtle.position.z)) + end + + y = y + 1 + end + end + + -- Add select buttons + local btnY = h - 1 + addButton(2, btnY, 12, 1, "SELECT", function() + if #turtles > 0 then + selectedTurtle = (selectedTurtle or 0) % #turtles + 1 + addLog("Selected Turtle #" .. turtles[selectedTurtle].turtleID, colors.lime) + end + end, colors.green) + + drawButton(buttons[#buttons], false) +end + +-- Draw control mode +local function drawControl() + term.setBackgroundColor(colors.black) + local y = 4 + + if not selectedTurtle or not turtles[selectedTurtle] then + term.setTextColor(colors.red) + term.setCursorPos(2, y) + term.write("No turtle selected") + term.setCursorPos(2, y + 1) + term.setTextColor(colors.gray) + term.write("Go to TURTLES and SELECT") + return + end + + local turtle = turtles[selectedTurtle] + term.setTextColor(colors.yellow) + term.setCursorPos(2, y) + term.write("Controlling Turtle #" .. turtle.turtleID) + y = y + 2 + + -- Movement buttons + local btnSize = 5 + local centerX = math.floor(w / 2) + local centerY = 10 + + addButton(centerX - btnSize / 2, centerY - btnSize - 1, btnSize, 2, "FWD", function() + sendCommand(turtle.turtleID, "forward") + end, colors.blue) + + addButton(centerX - btnSize / 2, centerY + 2, btnSize, 2, "BACK", function() + sendCommand(turtle.turtleID, "back") + end, colors.blue) + + addButton(centerX - btnSize - 2, centerY, btnSize, 2, "LEFT", function() + sendCommand(turtle.turtleID, "turnLeft") + end, colors.blue) + + addButton(centerX + 2, centerY, btnSize, 2, "RIGHT", function() + sendCommand(turtle.turtleID, "turnRight") + end, colors.blue) + + addButton(centerX - btnSize / 2, centerY - 1, btnSize, 2, "UP", function() + sendCommand(turtle.turtleID, "up") + end, colors.green) + + addButton(2, centerY + 5, 7, 2, "DOWN", function() + sendCommand(turtle.turtleID, "down") + end, colors.green) + + addButton(10, centerY + 5, 7, 2, "DIG", function() + sendCommand(turtle.turtleID, "dig") + end, colors.red) + + addButton(18, centerY + 5, 7, 2, "MINE", function() + sendCommand(turtle.turtleID, "mineStart") + end, colors.orange) + + addButton(26, centerY + 5, 7, 2, "HOME", function() + sendCommand(turtle.turtleID, "returnHome") + end, colors.yellow) + + for i = #buttons - 8, #buttons do + if buttons[i] then + drawButton(buttons[i], false) + end + end +end + +-- Draw GPS mode +local function drawGPS() + term.setBackgroundColor(colors.black) + local y = 4 + + term.setTextColor(colors.yellow) + term.setCursorPos(2, y) + term.write("GPS TRACKING") + y = y + 2 + + -- My position + term.setTextColor(colors.lime) + term.setCursorPos(2, y) + term.write("Your Position:") + y = y + 1 + + if myPosition then + term.setTextColor(colors.white) + term.setCursorPos(4, y) + term.write(string.format("X: %d Y: %d Z: %d", myPosition.x, myPosition.y, myPosition.z)) + else + term.setTextColor(colors.red) + term.setCursorPos(4, y) + term.write("GPS not available") + end + y = y + 2 + + -- Distance to selected turtle + if selectedTurtle and turtles[selectedTurtle] and myPosition then + local turtle = turtles[selectedTurtle] + if turtle.position then + local dx = turtle.position.x - myPosition.x + local dy = turtle.position.y - myPosition.y + local dz = turtle.position.z - myPosition.z + local dist = math.sqrt(dx*dx + dy*dy + dz*dz) + + term.setTextColor(colors.cyan) + term.setCursorPos(2, y) + term.write(string.format("Turtle #%d distance:", turtle.turtleID)) + y = y + 1 + term.setTextColor(colors.white) + term.setCursorPos(4, y) + term.write(string.format("%.1f blocks", dist)) + y = y + 1 + term.setCursorPos(4, y) + term.setTextColor(colors.gray) + term.write(string.format("Delta: %d,%d,%d", dx, dy, dz)) + end + end + + y = y + 2 + addButton(2, y, 15, 2, "UPDATE GPS", function() + term.setTextColor(colors.yellow) + term.setCursorPos(2, h - 2) + term.write("Locating...") + if updateMyPosition() then + addLog("GPS updated", colors.lime) + else + addLog("GPS failed", colors.red) + end + end, colors.green) + + addButton(18, y, 15, 2, "GOTO TURTLE", function() + if selectedTurtle and turtles[selectedTurtle] and turtles[selectedTurtle].position then + local pos = turtles[selectedTurtle].position + addLog(string.format("Turtle at: %d,%d,%d", pos.x, pos.y, pos.z), colors.cyan) + end + end, colors.blue) + + drawButton(buttons[#buttons-1], false) + drawButton(buttons[#buttons], false) +end + +-- Draw webbridge mode +local function drawWebbridge() + term.setBackgroundColor(colors.black) + local y = 4 + + term.setTextColor(colors.yellow) + term.setCursorPos(2, y) + term.write("WEBBRIDGE CONTROL") + y = y + 2 + + if webbridgeStatus then + term.setTextColor(colors.lime) + term.setCursorPos(2, y) + term.write("Status: ONLINE") + y = y + 1 + term.setTextColor(colors.white) + term.setCursorPos(2, y) + term.write("Messages: " .. (webbridgeStatus.messages or 0)) + y = y + 1 + term.setCursorPos(2, y) + term.write("Commands: " .. (webbridgeStatus.commands or 0)) + else + term.setTextColor(colors.gray) + term.setCursorPos(2, y) + term.write("Status: Unknown") + end + + y = y + 2 + + addButton(2, y, 15, 2, "PING", function() + sendWebbridgeCommand("ping") + end, colors.blue) + + addButton(18, y, 15, 2, "RESTART", function() + sendWebbridgeCommand("restart") + end, colors.orange) + + y = y + 3 + addButton(2, y, 15, 2, "GET STATUS", function() + sendWebbridgeCommand("status") + end, colors.green) + + addButton(18, y, 15, 2, "VIEW LOGS", function() + sendWebbridgeCommand("logs") + end, colors.cyan) + + for i = #buttons - 3, #buttons do + if buttons[i] then + drawButton(buttons[i], false) + end + end + + -- Show recent logs + y = y + 4 + term.setTextColor(colors.gray) + term.setCursorPos(2, y) + term.write("Recent Activity:") + y = y + 1 + + for i = 1, math.min(5, #logMessages) do + if y >= h - 1 then break end + local log = logMessages[i] + term.setTextColor(log.color) + term.setCursorPos(2, y) + local text = log.text + if #text > w - 3 then + text = text:sub(1, w - 6) .. "..." + end + term.write(text) + y = y + 1 + end +end + +-- Draw server mode +local function drawServer() + term.setBackgroundColor(colors.black) + local y = 4 + + term.setTextColor(colors.yellow) + term.setCursorPos(2, y) + term.write("SERVER INTERFACE") + y = y + 2 + + if serverStats then + term.setTextColor(colors.lime) + term.setCursorPos(2, y) + term.write("Server: ONLINE") + y = y + 1 + + term.setTextColor(colors.white) + term.setCursorPos(2, y) + term.write("Turtles: " .. (serverStats.turtles or 0)) + y = y + 1 + term.setCursorPos(2, y) + term.write("Blocks: " .. (serverStats.blocks or 0)) + y = y + 1 + term.setCursorPos(2, y) + term.write("Uptime: " .. (serverStats.uptime or "?")) + else + term.setTextColor(colors.red) + term.setCursorPos(2, y) + term.write("Server: OFFLINE") + end + + y = y + 2 + + addButton(2, y, 15, 2, "REFRESH", function() + term.setTextColor(colors.yellow) + term.setCursorPos(2, h - 2) + term.write("Fetching...") + fetchServerStats() + addLog("Server stats refreshed", colors.lime) + end, colors.green) + + addButton(18, y, 15, 2, "VIEW API", function() + modem.transmit(POCKET_CHANNEL, CHANNEL_RECEIVE, { + type = "web_interface_request", + from = os.getComputerID() + }) + addLog("Requesting web interface info", colors.cyan) + end, colors.blue) + + drawButton(buttons[#buttons-1], false) + drawButton(buttons[#buttons], false) +end + +-- Main draw function +local function draw() + term.setBackgroundColor(colors.black) + term.clear() + + drawHeader() + drawTabs() + + if mode == "overview" then + drawOverview() + elseif mode == "control" then + drawControl() + elseif mode == "gps" then + drawGPS() + elseif mode == "webbridge" then + drawWebbridge() + elseif mode == "server" then + drawServer() + end + + -- Status bar + term.setBackgroundColor(colors.gray) + term.setCursorPos(1, h) + term.clearLine() + term.setTextColor(colors.white) + term.setCursorPos(2, h) + term.write(string.format("Online:%d", #turtles)) + if selectedTurtle and turtles[selectedTurtle] then + term.setCursorPos(15, h) + term.write("T#" .. turtles[selectedTurtle].turtleID) + end + if myPosition then + term.setCursorPos(w - 10, h) + term.write("GPS:OK") + end +end + +-- Main loop +print("Pocket Control Center") +print("Initializing...") + +-- Initial GPS update +updateMyPosition() + +-- Start background tasks +parallel.waitForAny( + function() + -- GPS update loop + while true do + sleep(5) + updateMyPosition() + end + end, + + function() + -- Server stats update loop + while true do + sleep(10) + fetchServerStats() + end + end, + + function() + -- Message listener + while true do + local event, side, channel, replyChannel, message = os.pullEvent("modem_message") + + if channel == STATUS_CHANNEL and type(message) == "table" then + if message.type == "status" then + -- Update turtle list + local found = false + for i, t in ipairs(turtles) do + if t.turtleID == message.turtleID then + turtles[i] = message + found = true + break + end + end + if not found then + table.insert(turtles, message) + addLog("Turtle #" .. message.turtleID .. " connected", colors.lime) + end + end + elseif channel == POCKET_CHANNEL and type(message) == "table" then + -- Handle responses from webbridge + if message.type == "webbridge_status" then + webbridgeStatus = message.data + addLog("Webbridge status received", colors.cyan) + elseif message.type == "server_stats" then + serverStats = message.data + addLog("Server stats received", colors.lime) + elseif message.type == "webbridge_log" then + addLog("[WB] " .. (message.text or ""), colors.lightGray) + elseif message.type == "command_ack" then + addLog("Command acknowledged", colors.green) + elseif message.type == "error" then + addLog("Error: " .. (message.error or "unknown"), colors.red) + end + end + end + end, + + function() + -- UI loop + draw() + while true do + local event, p1, p2, p3 = os.pullEvent() + + if event == "mouse_click" or event == "mouse_up" then + local btn = checkButton(p2, p3) + if btn and btn.action then + btn.action() + end + draw() + elseif event == "char" then + if p1 == "q" then + break + elseif p1 == "r" then + draw() + end + end + end + end +) + +term.setBackgroundColor(colors.black) +term.setTextColor(colors.white) +term.clear() +term.setCursorPos(1, 1) +print("Pocket Control Center closed")