diff --git a/gate.lua b/gate.lua index 3e5c35b..3de6b07 100644 --- a/gate.lua +++ b/gate.lua @@ -1,10 +1,13 @@ local DEFAULT_SERVER_BASE = "https://ticket.fse-media.group" local DEFAULT_SERVER_PATH = "/api/tickets/check" local GATE_OPEN_SECONDS = 2 -local VERSION = "v1.5.29" +local VERSION = "v1.5.30" local VERSION_CHECK_INTERVAL = 5 local PERIPHERAL_WAIT_INTERVAL = 0.5 local PERIPHERAL_WAIT_MAX_RETRIES = 40 +local MONITOR_HANDSHAKE_RETRIES = 20 +local STARTUP_REDRAW_DURATION = 20 +local STARTUP_REDRAW_INTERVAL = 0.5 local serverURL = nil @@ -118,11 +121,22 @@ end local function waitForPeripherals(requireInspection) local attempts = 0 local lastDrawMsg = nil + local handshakeDone = false while attempts < PERIPHERAL_WAIT_MAX_RETRIES do local hadChange = false if not monitor then local m, ms = safeFindPeripheral("monitor", monitorSide) - if m then monitor = m; monitorSide = ms; hadChange = true end + if m then + monitor = m + monitorSide = ms + hadChange = true + end + end + if monitor and (hadChange or not handshakeDone) then + termDev = monitor + local ok = false + pcall(function() ok = handshakeMonitor(monitor) end) + if ok then handshakeDone = true end end if not speaker then local s, _ = safeFindPeripheral("speaker") @@ -135,12 +149,17 @@ local function waitForPeripherals(requireInspection) local ok = true if not monitor then ok = false end if requireInspection and not inspection then ok = false end - if ok then return true end + if ok and handshakeDone then return true end + if ok and not handshakeDone then + local ok2 = false + pcall(function() ok2 = handshakeMonitor(monitor) end) + if ok2 then handshakeDone = true; return true end + end local msg = nil - if not monitor and requireInspection and not inspection then - msg = "Waiting for monitor + gate..." - elseif not monitor then + if not monitor then msg = "Waiting for monitor..." + elseif not handshakeDone then + msg = "Waking monitor..." elseif requireInspection and not inspection then msg = "Waiting for gate peripheral..." end @@ -171,7 +190,7 @@ local function waitForPeripherals(requireInspection) sleep(PERIPHERAL_WAIT_INTERVAL) attempts = attempts + 1 end - return false + return not not monitor end pcall(function() waitForPeripherals(false) end) @@ -248,33 +267,81 @@ local function jsonDecode(str) if ok then return res else return nil end end -local function setServerConnected(ok) - if serverConnected == ok then return end - serverConnected = ok - serverLastChangeTs = os.epoch("utc") -end - local termDev = nativeTerm if peripheral and type(peripheral.getName) == "function" and monitor then pcall(function() monitorSide = peripheral.getName(monitor) end) end +local function safeCallTerm(method, ...) + if not termDev or type(termDev[method]) ~= "function" then return nil end + local ok, r1, r2, r3 = pcall(termDev[method], ...) + if not ok then return nil end + return r1, r2, r3 +end + +local function safeCallMonitor(target, method, ...) + if not target or type(target[method]) ~= "function" then return false end + local ok, r1 = pcall(target[method], ...) + if not ok then return false end + return r1 ~= false +end + +local function handshakeMonitor(target) + if not target then return false end + local scaleOk = false + local getSizeOk = false + local w, h = nil, nil + for _ = 1, MONITOR_HANDSHAKE_RETRIES do + if safeCallMonitor(target, "setTextScale", 0.5) then scaleOk = true end + w, h = safeCallTerm("getSize") + if type(w) == "number" and type(h) == "number" and w >= 2 and h >= 2 then + getSizeOk = true + end + if scaleOk and getSizeOk then + local cOk = safeCallMonitor(target, "setBackgroundColor", colors.black) + local c2Ok = safeCallMonitor(target, "setTextColor", colors.white) + local clearOk = safeCallMonitor(target, "clear") + if clearOk then return true end + end + sleep(PERIPHERAL_WAIT_INTERVAL) + local rewrapOk, rewrapped = pcall(function() + if monitorSide and peripheral and type(peripheral.isPresent) == "function" and peripheral.isPresent(monitorSide) and type(peripheral.wrap) == "function" then + local okW, wd = pcall(peripheral.wrap, monitorSide) + if okW and wd then return wd end + end + if peripheral and type(peripheral.find) == "function" then + return peripheral.find("monitor") + end + return nil + end) + if rewrapOk and rewrapped then + target = rewrapped + monitor = rewrapped + termDev = rewrapped + end + end + return scaleOk or getSizeOk +end + local function refreshDisplayTarget() local nextMonitor = nil local nextMonitorSide = monitorSide if peripheral then - if nextMonitorSide and type(peripheral.isPresent) == "function" and peripheral.isPresent(nextMonitorSide) and type(peripheral.wrap) == "function" then - local okWrap, wrapped = pcall(peripheral.wrap, nextMonitorSide) - if okWrap and wrapped then - nextMonitor = wrapped + if nextMonitorSide and type(peripheral.isPresent) == "function" then + local okPres, pres = pcall(peripheral.isPresent, nextMonitorSide) + if okPres and pres and type(peripheral.wrap) == "function" then + local okWrap, wrapped = pcall(peripheral.wrap, nextMonitorSide) + if okWrap and wrapped then nextMonitor = wrapped end end end if not nextMonitor and type(peripheral.find) == "function" then local foundName = nil - local found = peripheral.find("monitor", function(name, wrapped) - foundName = name - nextMonitor = wrapped - return true + local found = nil + pcall(function() + found = peripheral.find("monitor", function(name, wrapped) + foundName = name + return true + end) end) if found then nextMonitor = found @@ -284,10 +351,18 @@ local function refreshDisplayTarget() end end end - monitor = nextMonitor - monitorSide = nextMonitorSide + if nextMonitor and nextMonitor ~= monitor then + monitor = nextMonitor + monitorSide = nextMonitorSide + pcall(function() handshakeMonitor(nextMonitor) end) + elseif nextMonitor then + monitor = nextMonitor + monitorSide = nextMonitorSide + end if monitor then - pcall(monitor.setTextScale, 0.5) + if not safeCallMonitor(monitor, "setTextScale", 0.5) then + pcall(function() handshakeMonitor(monitor) end) + end termDev = monitor else termDev = nativeTerm or term @@ -296,52 +371,48 @@ local function refreshDisplayTarget() end local function clear() - refreshDisplayTarget() - termDev.setBackgroundColor(colors.black) - termDev.setTextColor(colors.white) - termDev.clear() - termDev.setCursorPos(1, 1) + pcall(refreshDisplayTarget) + if not termDev then return end + safeCallTerm("setBackgroundColor", colors.black) + safeCallTerm("setTextColor", colors.white) + safeCallTerm("clear") + safeCallTerm("setCursorPos", 1, 1) end local function centerText(y, text, color) - local w = termDev.getSize() - termDev.setTextColor(color or colors.white) - local x = math.max(1, math.floor((w - #text) / 2) + 1) - termDev.setCursorPos(x, y) - termDev.write(text) + if not termDev then return end + local w = safeCallTerm("getSize") + w = tonumber(w) or 0 + if w < 2 then return end + safeCallTerm("setTextColor", color or colors.white) + local x = math.max(1, math.floor((w - #tostring(text or "")) / 2) + 1) + safeCallTerm("setCursorPos", x, y) + safeCallTerm("write", tostring(text or "")) end -local function splitDisplayLines(text) - local out = {} - text = tostring(text or "") - if #text == 0 then return out end - for line in text:gmatch("([^\n]*)\n?") do - if line == "" and #out > 0 and text:sub(-1) ~= "\n" then break end - if #line > 0 then table.insert(out, line) end - end - return out -end +local lastStartupRedrawAt = 0 local function drawServerStatusIndicator(w) + w = tonumber(w) or 0 if w < 2 then return end local col = colors.yellow if serverConnected == true then col = colors.lime elseif serverConnected == false then col = colors.red end - termDev.setBackgroundColor(colors.black) - termDev.setTextColor(col) - termDev.setCursorPos(w - 1, 1) - termDev.write("S") - termDev.setCursorPos(w, 1) - termDev.write("*") - termDev.setTextColor(colors.white) + safeCallTerm("setBackgroundColor", colors.black) + safeCallTerm("setTextColor", col) + safeCallTerm("setCursorPos", w - 1, 1) + safeCallTerm("write", "S") + safeCallTerm("setCursorPos", w, 1) + safeCallTerm("write", "*") + safeCallTerm("setTextColor", colors.white) end local lastUpdateError = nil local function drawVersionIndicator(w) + w = tonumber(w) or 0 local s = tostring(VERSION or "") - if #s == 0 then return end - if w < #s then return end + if #s == 0 or w < #s then return end local markerColor = colors.lime local markerText = "* " if isUpdating then @@ -351,25 +422,31 @@ local function drawVersionIndicator(w) markerColor = colors.red markerText = "* " end - termDev.setBackgroundColor(colors.black) - termDev.setTextColor(colors.gray) - termDev.setCursorPos(1, 1) - termDev.write(s) - termDev.setTextColor(markerColor) - termDev.write(markerText) - + safeCallTerm("setBackgroundColor", colors.black) + safeCallTerm("setTextColor", colors.gray) + safeCallTerm("setCursorPos", 1, 1) + safeCallTerm("write", s) + safeCallTerm("setTextColor", markerColor) + safeCallTerm("write", markerText) + if lastUpdateError then - termDev.setCursorPos(1, 2) - termDev.setTextColor(colors.red) - termDev.write(string.sub(tostring(lastUpdateError), 1, w)) + safeCallTerm("setCursorPos", 1, 2) + safeCallTerm("setTextColor", colors.red) + safeCallTerm("write", string.sub(tostring(lastUpdateError), 1, w)) end - - termDev.setTextColor(colors.white) + + safeCallTerm("setTextColor", colors.white) end +local _cachedDrawArgHash = nil + local function draw(statusLine1, statusLine2, statusColor) clear() - local w, h = termDev.getSize() + if not termDev then return end + local w, h = safeCallTerm("getSize") + w = tonumber(w) or 0 + h = tonumber(h) or 0 + if w < 2 or h < 2 then return end centerText(2, "GATE", colors.cyan) drawVersionIndicator(w) drawServerStatusIndicator(w) @@ -385,9 +462,9 @@ local function draw(statusLine1, statusLine2, statusColor) centerText(y, line, statusColor or colors.white) end end - termDev.setCursorPos(1, h) - termDev.setTextColor(colors.gray) - termDev.write(string.rep(" ", w)) + safeCallTerm("setCursorPos", 1, h) + safeCallTerm("setTextColor", colors.gray) + safeCallTerm("write", string.rep(" ", w)) end local function pulseLeftRedstone(seconds) @@ -1726,6 +1803,23 @@ local function mainLoop() drawReadyScreen() elseif ev[1] == "peripheral" or ev[1] == "peripheral_detach" or ev[1] == "monitor_resize" or ev[1] == "config_updated" then pcall(drawReadyScreen) + elseif ev[1] == "timer" then + pcall(drawReadyScreen) + end + end +end + +local function startupRedrawTask() + local startTs = (os.epoch and os.epoch("utc")) or (os.time() * 3600000) + local endTs = startTs + STARTUP_REDRAW_DURATION * 1000 + local nextRedraw = startTs + while true do + local now = (os.epoch and os.epoch("utc")) or (os.time() * 3600000) + if now <= endTs then + pcall(drawReadyScreen) + sleep(STARTUP_REDRAW_INTERVAL) + else + sleep(60) end end end @@ -1797,4 +1891,4 @@ local function backgroundSyncTask() end end -parallel.waitForAny(mainLoop, backgroundSyncTask) +parallel.waitForAny(mainLoop, startupRedrawTask, backgroundSyncTask)