Airship autopilot: role-based wget installer for server

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>
This commit is contained in:
2026-07-23 12:53:41 +02:00
commit 4cd2573f56
36 changed files with 2897 additions and 0 deletions

171
files/cockpit/cockpit.lua Normal file
View File

@@ -0,0 +1,171 @@
-- 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

33
files/cockpit/link.lua Normal file
View File

@@ -0,0 +1,33 @@
-- link.lua -- lettvekts rednet-lenke mellom autopiloten (maskinrom) og piloten (pilot-hus).
-- Modem-agnostisk: virker over wired (nettverkskabel) ELLER wireless/ender modem. Graceful:
-- hvis ingen modem finnes, blir publish/send no-ops (autopiloten kjorer fint solo).
local M = {}
M.STATUS = "airship_status" -- autopilot -> pilot (status-kringkasting)
M.CMD = "airship_cmd" -- pilot -> autopilot (kommandoer)
local opened = false
-- aapne rednet paa ALLE tilkoblede modem (wired og/eller wireless)
function M.open()
for _, name in ipairs(peripheral.getNames()) do
if peripheral.getType(name) == "modem" and not rednet.isOpen(name) then
rednet.open(name); opened = true
end
end
-- ogsaa hvis et modem allerede var aapent
for _, name in ipairs(peripheral.getNames()) do
if peripheral.getType(name) == "modem" and rednet.isOpen(name) then opened = true end
end
return opened
end
function M.isOpen() return opened end
-- kringkast status (kalles hver tick av autopiloten)
function M.publishStatus(t) if opened then rednet.broadcast(t, M.STATUS) end end
-- send en kommando-tabell (kalles av piloten ved knappetrykk)
function M.sendCommand(t) if opened then rednet.broadcast(t, M.CMD) end end
return M

3
files/cockpit/manifest Normal file
View File

@@ -0,0 +1,3 @@
cockpit.lua
link.lua
startup.lua

View File

@@ -0,0 +1,2 @@
-- pilot-hus: auto-run cockpit
shell.run("cockpit")