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:
171
files/cockpit/cockpit.lua
Normal file
171
files/cockpit/cockpit.lua
Normal 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
33
files/cockpit/link.lua
Normal 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
3
files/cockpit/manifest
Normal file
@@ -0,0 +1,3 @@
|
||||
cockpit.lua
|
||||
link.lua
|
||||
startup.lua
|
||||
2
files/cockpit/startup.lua
Normal file
2
files/cockpit/startup.lua
Normal file
@@ -0,0 +1,2 @@
|
||||
-- pilot-hus: auto-run cockpit
|
||||
shell.run("cockpit")
|
||||
Reference in New Issue
Block a user