Mirrors the live CC autopilot (computer 0) and cockpit (computer 4) from the Modda wii world. install.lua fetches each role's manifest over HTTP so the system installs on any server regardless of computer id. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
172 lines
7.8 KiB
Lua
172 lines
7.8 KiB
Lua
-- cockpit.lua -- pilot-hus konsoll. Status + full live-styring av autopiloten over rednet.
|
|
-- To faner: STATUS (fly/standby-styring) og SETTINGS (live brukerinnstillinger). UI = English.
|
|
-- ADAPTIV layout: kompakt paa 1x1-monitor, full paa storre.
|
|
|
|
local progDir = fs.getDir(shell.getRunningProgram())
|
|
if package and package.path and not string.find(package.path, progDir, 1, true) then
|
|
package.path = progDir .. "/?.lua;" .. package.path
|
|
end
|
|
local link = require("link")
|
|
|
|
local mon = peripheral.find("monitor")
|
|
if not mon then print("No monitor found. Attach a monitor (Advanced recommended)."); return end
|
|
if not link.open() then print("No modem found. Attach a wireless modem."); return end
|
|
|
|
mon.setTextScale(0.5)
|
|
local W, H = mon.getSize()
|
|
|
|
local C = { bg = colors.black, title = colors.cyan, label = colors.lightGray, val = colors.white,
|
|
ok = colors.lime, warn = colors.orange, fault = colors.red, btn = colors.gray, btnTxt = colors.white,
|
|
engage = colors.green, stop = colors.red, tabOn = colors.blue, active = colors.green }
|
|
|
|
-- live-justerbare brukerinnstillinger (syntetiske noekler -> autopiloten mapper til cfg)
|
|
local SETTINGS = {
|
|
{ key = "climbSpeed", short = "Climb", step = 0.1, fmt = "%.1f", unit = "b/s", def = 0.8 },
|
|
{ key = "holdBand", short = "Band", step = 1, fmt = "%.0f", unit = "blk", def = 3 },
|
|
{ key = "maxPower", short = "Power", step = 1, fmt = "%.0f", unit = "", def = 6 },
|
|
}
|
|
|
|
local page = "status"
|
|
local status, lastRx = nil, -999
|
|
local buttons = {}
|
|
local function now() return os.clock() end
|
|
local function online() return (now() - lastRx) < 2.0 end
|
|
|
|
local function button(x1, y1, x2, y2, label, action, col)
|
|
buttons[#buttons + 1] = { x1 = x1, y1 = y1, x2 = x2, y2 = y2, action = action }
|
|
mon.setBackgroundColor(col or C.btn); mon.setTextColor(C.btnTxt)
|
|
for y = y1, y2 do mon.setCursorPos(x1, y); mon.write(string.rep(" ", x2 - x1 + 1)) end
|
|
mon.setCursorPos(x1 + math.max(0, math.floor((x2 - x1 + 1 - #label) / 2)), y1 + math.floor((y2 - y1) / 2))
|
|
mon.write(label); mon.setBackgroundColor(C.bg)
|
|
end
|
|
local function center(y, text, color)
|
|
mon.setTextColor(color or C.val)
|
|
mon.setCursorPos(math.max(1, math.floor((W - #text) / 2) + 1), y); mon.write(text:sub(1, W))
|
|
end
|
|
local function putline(y, text, color)
|
|
mon.setTextColor(color or C.val); mon.setCursorPos(1, y); mon.write(text:sub(1, W))
|
|
end
|
|
|
|
local function drawTabs()
|
|
local hw = math.floor(W / 2)
|
|
button(1, 1, hw, 1, "STATUS", function() page = "status" end, page == "status" and C.tabOn or C.btn)
|
|
button(hw + 1, 1, W, 1, "SETUP", function() page = "settings" end, page == "settings" and C.tabOn or C.btn)
|
|
end
|
|
|
|
local function curVal(p)
|
|
if status and status[p.key] ~= nil then return status[p.key] end
|
|
return p.def
|
|
end
|
|
|
|
local function drawStatus()
|
|
local compact = W < 28
|
|
local bRowH = (H >= 16) and 2 or 1
|
|
local landTop = H - bRowH + 1
|
|
local tgtTop = landTop - bRowH
|
|
local top, statusMax = 2, tgtTop - 1
|
|
|
|
if not online() then
|
|
center(math.floor((top + statusMax) / 2), "OFFLINE", C.fault)
|
|
if not compact then center(math.floor((top + statusMax) / 2) + 1, "no link to machine room", C.warn) end
|
|
return
|
|
end
|
|
local s = status
|
|
local inFlight = s.state ~= "IDLE" and s.state ~= "SETUP NEEDED"
|
|
local stateColor = C.fault
|
|
if s.state == "FLYING" then stateColor = C.ok
|
|
elseif s.state == "IDLE" then stateColor = (s.ready and C.title or C.warn) end
|
|
local pcolor = (math.abs(s.pitch or 0) < 3) and C.ok or C.warn
|
|
|
|
-- bygg status-linjer
|
|
local lines = {}
|
|
lines[#lines + 1] = { tostring(s.state) .. (s.hold and " HOLD" or ""), stateColor }
|
|
if s.reason and s.reason ~= "" then lines[#lines + 1] = { tostring(s.reason), C.fault } end
|
|
if compact then
|
|
lines[#lines + 1] = { string.format("%.0f > %.0f", s.height or 0, s.target or 0), C.val }
|
|
lines[#lines + 1] = { string.format("PITCH %+.1f", s.pitch or 0), pcolor }
|
|
if inFlight then lines[#lines + 1] = { string.format("VSPD %+.2f", s.vVel or 0), C.val } end
|
|
if s.state == "IDLE" then lines[#lines + 1] = { s.ready and "ready -> ENGAGE" or "needs setup", s.ready and C.ok or C.warn } end
|
|
else
|
|
if s.state == "IDLE" then lines[#lines + 1] = { s.ready and "ready -> ENGAGE" or "needs setup (Wiring+Sign)", s.ready and C.ok or C.warn } end
|
|
lines[#lines + 1] = { string.format("ALT %.1f > %.0f", s.height or 0, s.target or 0), C.val }
|
|
lines[#lines + 1] = { string.format("PITCH %+.1f deg", s.pitch or 0), pcolor }
|
|
if inFlight then
|
|
lines[#lines + 1] = { string.format("VSPD %+.2f b/s", s.vVel or 0), C.val }
|
|
lines[#lines + 1] = { string.format("OUT f%d a%d lift %.1f", s.fore or 0, s.aft or 0, s.uSum or 0), C.val }
|
|
end
|
|
end
|
|
|
|
-- render: kompakt = sentrert + luftig (fyll den ledige plassen); full = venstrejustert top-down
|
|
if compact then
|
|
local avail = statusMax - top + 1
|
|
local gap = (avail >= 2 * #lines - 1) and 1 or 0
|
|
local blockH = #lines + (#lines - 1) * gap
|
|
local startY = top + math.max(0, math.floor((avail - blockH) / 2))
|
|
for i, L in ipairs(lines) do
|
|
local y = startY + (i - 1) * (1 + gap)
|
|
if y >= top and y <= statusMax then center(y, L[1], L[2]) end
|
|
end
|
|
else
|
|
for i, L in ipairs(lines) do local y = top + i - 1; if y <= statusMax then putline(y, L[1], L[2]) end end
|
|
end
|
|
|
|
-- knapper (HOLD/LAND lyser solid naar den modusen er aktiv)
|
|
if inFlight then
|
|
local bw = math.floor(W / 4)
|
|
button(1, tgtTop, bw, tgtTop + bRowH - 1, "-5", function() link.sendCommand({ cmd = "adjust", delta = -5 }) end)
|
|
button(bw + 1, tgtTop, 2 * bw, tgtTop + bRowH - 1, "-1", function() link.sendCommand({ cmd = "adjust", delta = -1 }) end)
|
|
button(2 * bw + 1, tgtTop, 3 * bw, tgtTop + bRowH - 1, "+1", function() link.sendCommand({ cmd = "adjust", delta = 1 }) end)
|
|
button(3 * bw + 1, tgtTop, W, tgtTop + bRowH - 1, "+5", function() link.sendCommand({ cmd = "adjust", delta = 5 }) end)
|
|
local tw = math.floor(W / 3)
|
|
button(1, landTop, tw, H, "HOLD", function() link.sendCommand({ cmd = "hold" }) end, (s.mode == "hold") and C.active or C.btn)
|
|
button(tw + 1, landTop, 2 * tw, H, "LAND", function() link.sendCommand({ cmd = "land" }) end, (s.mode == "land") and C.active or C.btn)
|
|
button(2 * tw + 1, landTop, W, H, "STOP", function() link.sendCommand({ cmd = "stop" }) end, C.stop)
|
|
else
|
|
button(1, tgtTop, W, H, "ENGAGE FLY", function() link.sendCommand({ cmd = "engage" }) end, C.engage)
|
|
end
|
|
end
|
|
|
|
local function drawSettings()
|
|
local n = #SETTINGS
|
|
local areaTop = 2
|
|
local blockH = math.max(1, math.floor((H - areaTop + 1) / n))
|
|
local bw = math.max(3, math.floor(W / 5))
|
|
for i, p in ipairs(SETTINGS) do
|
|
local y1 = areaTop + (i - 1) * blockH
|
|
local y2 = (i == n) and H or (y1 + blockH - 1)
|
|
local cur = curVal(p)
|
|
button(1, y1, bw, y2, "-", function() link.sendCommand({ cmd = "set", key = p.key, value = cur - p.step }) end)
|
|
button(W - bw + 1, y1, W, y2, "+", function() link.sendCommand({ cmd = "set", key = p.key, value = cur + p.step }) end)
|
|
local txt = string.format("%s %s%s", p.short, string.format(p.fmt, cur), p.unit)
|
|
mon.setTextColor(C.val)
|
|
mon.setCursorPos(bw + 1 + math.max(0, math.floor((W - 2 * bw - #txt) / 2)), math.floor((y1 + y2) / 2))
|
|
mon.write(txt)
|
|
end
|
|
end
|
|
|
|
local function draw()
|
|
mon.setBackgroundColor(C.bg); mon.clear()
|
|
buttons = {}
|
|
drawTabs()
|
|
if page == "settings" then drawSettings() else drawStatus() end
|
|
end
|
|
|
|
local function handleTouch(x, y)
|
|
for _, b in ipairs(buttons) do
|
|
if x >= b.x1 and x <= b.x2 and y >= b.y1 and y <= b.y2 then b.action(); draw(); return end
|
|
end
|
|
end
|
|
|
|
draw()
|
|
local redraw = os.startTimer(0.5)
|
|
while true do
|
|
local e = { os.pullEvent() }
|
|
if e[1] == "rednet_message" and e[4] == link.STATUS then
|
|
status = e[3]; lastRx = now()
|
|
elseif e[1] == "monitor_touch" then
|
|
handleTouch(e[3], e[4])
|
|
elseif e[1] == "timer" and e[2] == redraw then
|
|
redraw = os.startTimer(0.5); draw()
|
|
end
|
|
end
|