-- -- configure.lua -- -- Guided, paginated configurator (mimic). Steps through role -> identity -> -- role-specific peripherals -> review, picking every peripheral from a menu of -- what's attached, and writes /config.lua. Next / Back to move between pages. -- -- configure -- -- Needs mimic + facility installed. A plain text version is in configure_text.lua -- if this GUI misbehaves. -- require("/initenv").init_env() local mimic = require("mimic") local m = require("mimic.elements") local psil = require("scada-common.psil") local style = mimic.style local cpair = mimic.cpair local ALIGN = mimic.ALIGN local CONFIG_PATH = "/config.lua" local PROGRAM = { coordinator="coordinator", sim_chamber="chamber_rtu", me_network="me_rtu", data_center="dc_rtu" } local ROLE_VALUE = { "coordinator", "sim_chamber", "me_network", "data_center" } local FIELD_BG = cpair(colors.white, colors.gray) -- ── peripheral discovery ──────────────────────────────────────────────────── local function namesOfType(t) local out = {} for _, n in ipairs(peripheral.getNames()) do if peripheral.getType(n) == t then out[#out + 1] = n end end table.sort(out) return out end local modems = namesOfType("modem") local monitors = namesOfType("monitor") local bridges = namesOfType("meBridge") local dports = namesOfType("hostilenetworks:data_center_io_port") local barrels = namesOfType("minecraft:barrel") local function invHint(n) local p = peripheral.wrap(n) if not (p and p.list) then return "" end local ok, list = pcall(p.list) if not ok or not list then return "" end local total = 0 local first for slot, item in pairs(list) do total = total + item.count if not first then local okd, d = pcall(p.getItemDetail, slot) first = (okd and type(d) == "table" and d.displayName) or item.name end end if total == 0 then return "(empty)" end return "(" .. tostring(first) .. " x" .. total .. ")" end local function modemHint(n) local ok, w = pcall(peripheral.call, n, "isWireless") return (ok and w) and "(wireless)" or "(wired)" end -- ── frame ─────────────────────────────────────────────────────────────────── local ps = psil.create() local root = mimic.init{ps=ps} local W, H = root.get_width(), root.get_height() m.TextBox{parent=root, x=1, y=1, width=W, text="FACILITY CONFIG", alignment=ALIGN.CENTER, fg_bg=style.theme.header} local subtitle = m.TextBox{parent=root, x=2, y=2, width=W-2, text="", fg_bg=style.label} local PANE_Y, PANE_H = 3, H - 4 local P_ROLE, P_ID, P_CHAMBER, P_ME, P_DCPORTS, P_DCBARRELS, P_COORDCH, P_COORDME, P_COORDDC, P_REVIEW = 1,2,3,4,5,6,7,8,9,10 local NPAGES = 10 local SUBTITLES = { "Step 1 - what is this computer?", "Step 2 - identity + rednet modem", "Sim chamber settings", "ME network settings", "Data center - IO ports", "Data center - barrels", "Coordinator - chambers monitor", "Coordinator - ME monitor", "Coordinator - data-center monitor", "Review - write /config.lua", } local panes = {} for i = 1, NPAGES do panes[i] = m.Div{parent=root, x=2, y=PANE_Y, width=W-2, height=PANE_H, hidden=(i > 1)} end local pane = m.MultiPane{parent=root, x=2, y=PANE_Y, width=W-2, height=PANE_H, panes=panes} local cfg = {} local readers = {} -- readers[page]() pulls that page's widgets into cfg -- labeled radio over peripheral names; returns resolver() -> name|false local function radioPicker(p, y, label, names, o) o = o or {} m.TextBox{parent=p, x=1, y=y, width=W-4, text=label, fg_bg=style.label} local labels, values = {}, {} if o.none then labels[1] = o.noneLabel or "(none)"; values[1] = false end for _, n in ipairs(names) do labels[#labels + 1] = o.annotate and (n .. " " .. o.annotate(n)) or n values[#values + 1] = n end if #labels == 0 then labels[1] = "(none detected)"; values[1] = false end local rb = m.RadioButton{parent=p, x=1, y=y+1, options=labels, default=1, min_width=W-6, callback=function () end, fg_bg=style.text_colors, radio_colors=cpair(colors.lightGray, colors.gray), select_color=colors.green} return function () return values[rb.get_value()] end end -- ── pages ─────────────────────────────────────────────────────────────────── -- 1: role local role_rb = m.RadioButton{parent=panes[P_ROLE], x=1, y=1, default=1, min_width=W-6, options={ "Coordinator (control + monitors)", "Sim-chamber RTU", "ME-network RTU", "Data-center RTU" }, callback=function () end, fg_bg=style.text_colors, radio_colors=cpair(colors.lightGray, colors.gray), select_color=colors.green} readers[P_ROLE] = function () cfg.unit_type = ROLE_VALUE[role_rb.get_value()] end -- 2: identity m.TextBox{parent=panes[P_ID], x=1, y=1, width=W-4, text="Unit id (unique across the facility):", fg_bg=style.label} local id_field = m.NumberField{parent=panes[P_ID], x=1, y=2, width=8, default=1, min=1, max=9999, fg_bg=FIELD_BG} local get_modem = radioPicker(panes[P_ID], 4, "Ender (wireless) modem for the rednet link:", modems, {annotate=modemHint}) readers[P_ID] = function () cfg.unit_id = tonumber(id_field.get_value()) or 1; cfg.modem = get_modem() end -- 3: sim chamber m.TextBox{parent=panes[P_CHAMBER], x=1, y=1, width=W-4, text="Chamber peripheral type:", fg_bg=style.label} local ch_type = m.TextField{parent=panes[P_CHAMBER], x=1, y=2, width=W-6, value="hostilenetworks:sim_chamber", fg_bg=FIELD_BG} m.TextBox{parent=panes[P_CHAMBER], x=1, y=4, width=W-4, text="Barrel side/name:", fg_bg=style.label} local ch_barrel = m.TextField{parent=panes[P_CHAMBER], x=1, y=5, width=16, value="back", fg_bg=FIELD_BG} m.TextBox{parent=panes[P_CHAMBER], x=1, y=7, width=W-4, text="Low-catalyst threshold:", fg_bg=style.label} local ch_low = m.NumberField{parent=panes[P_CHAMBER], x=1, y=8, width=8, default=8, min=0, max=999, fg_bg=FIELD_BG} readers[P_CHAMBER] = function () cfg.chamber_type = ch_type.get_value() cfg.barrel_side = ch_barrel.get_value() cfg.catalyst_low = tonumber(ch_low.get_value()) or 8 end -- 4: ME local get_bridge = radioPicker(panes[P_ME], 1, "ME Bridge (or auto-detect):", bridges, {none=true, noneLabel="(auto-detect)"}) readers[P_ME] = function () local b = get_bridge(); cfg.me_bridge = b or nil end -- 5: data center ports local get_inp = radioPicker(panes[P_DCPORTS], 1, "INPUT port (0):", dports, {annotate=invHint}) local get_mdl = radioPicker(panes[P_DCPORTS], 6, "MODELS port (1):", dports, {annotate=invHint}) local get_out = radioPicker(panes[P_DCPORTS], 11, "OUTPUT port (2):", dports, {annotate=invHint, none=true}) readers[P_DCPORTS] = function () cfg.dc_input_port = get_inp() cfg.dc_model_port = get_mdl() cfg.dc_output_port = get_out() or nil end -- 6: data center barrels local get_msrc = radioPicker(panes[P_DCBARRELS], 1, "MODELS barrel:", barrels, {annotate=invHint, none=true}) local get_psrc = radioPicker(panes[P_DCBARRELS], 8, "PREDICTIONS barrel:", barrels, {annotate=invHint, none=true}) readers[P_DCBARRELS] = function () cfg.dc_model_source = get_msrc() or nil cfg.dc_input_source = get_psrc() or nil end -- 7 & 8: coordinator monitors local get_chmon = radioPicker(panes[P_COORDCH], 1, "Chambers monitor (optional):", monitors, {none=true}) readers[P_COORDCH] = function () cfg.chambers_monitor = get_chmon() or nil end local get_memon = radioPicker(panes[P_COORDME], 1, "ME monitor (optional):", monitors, {none=true}) readers[P_COORDME] = function () cfg.me_monitor = get_memon() or nil end -- 9: coordinator data-center monitor (one here; add more by editing config.lua) m.TextBox{parent=panes[P_COORDDC], x=1, y=1, width=W-4, text="Data-center unit_id it drives:", fg_bg=style.label} local dc_uid = m.NumberField{parent=panes[P_COORDDC], x=1, y=2, width=8, default=1, min=1, max=9999, fg_bg=FIELD_BG} local get_dcmon = radioPicker(panes[P_COORDDC], 4, "Its monitor (optional):", monitors, {none=true}) m.TextBox{parent=panes[P_COORDDC], x=1, y=PANE_H, width=W-4, text="(one here; edit config.lua for more)", fg_bg=style.label} readers[P_COORDDC] = function () local mon = get_dcmon() if mon then cfg.dc_monitors = { [tonumber(dc_uid.get_value()) or 1] = mon } end end -- 10: review local review = m.TextBox{parent=panes[P_REVIEW], x=1, y=1, width=W-4, height=PANE_H-3, text="", fg_bg=style.label} -- track the checkbox via its callback (its read-method name isn't reliable) local startup_wanted = true m.Checkbox{parent=panes[P_REVIEW], x=1, y=PANE_H-1, label="Auto-run this device on boot (startup.lua)", default=true, fg_bg=style.label, box_fg_bg=cpair(colors.green, colors.gray), callback=function () startup_wanted = not startup_wanted end} -- ── navigation ────────────────────────────────────────────────────────────── local current = 1 local history = {} local back_btn, next_btn, write_btn local function refreshNav() if current == 1 then back_btn.hide(true) else back_btn.show() end if current == P_REVIEW then next_btn.hide(true); write_btn.show() else write_btn.hide(true); next_btn.show() end end local function goTo(page) current = page pane.set_value(page) subtitle.set_value(SUBTITLES[page]) if page == P_REVIEW then review.set_value(textutils.serialize(cfg)) end refreshNav() end local function goNext() if readers[current] then readers[current]() end local nxt if current == P_ID then nxt = ({ sim_chamber=P_CHAMBER, me_network=P_ME, data_center=P_DCPORTS, coordinator=P_COORDCH })[cfg.unit_type] elseif current == P_DCPORTS then nxt = P_DCBARRELS elseif current == P_COORDCH then nxt = P_COORDME elseif current == P_COORDME then nxt = P_COORDDC else nxt = ({ [P_ROLE]=P_ID, [P_CHAMBER]=P_REVIEW, [P_ME]=P_REVIEW, [P_DCBARRELS]=P_REVIEW, [P_COORDDC]=P_REVIEW })[current] end if nxt then history[#history + 1] = current; goTo(nxt) end end local function goBack() local prev = table.remove(history) if prev then goTo(prev) end end local function doWrite() local f = fs.open(CONFIG_PATH, "w") f.write("-- generated by configure (mimic)\nreturn " .. textutils.serialize(cfg) .. "\n") f.close() local prog = PROGRAM[cfg.unit_type] or "?" local wrote = CONFIG_PATH local added_startup = false if startup_wanted and PROGRAM[cfg.unit_type] then local sf = fs.open("/startup.lua", "w") sf.write("-- generated by configure\nshell.run(\"" .. prog .. "\")\n") sf.close() wrote = wrote .. " + startup.lua" added_startup = true end subtitle.set_value("Wrote " .. wrote) review.set_value("Done.\n\n" .. (added_startup and ("Auto-runs '" .. prog .. "' on boot - reboot or run it now.") or ("Run it with: " .. prog)) .. "\n\nCtrl+T to exit.") back_btn.hide(true); write_btn.hide(true) end back_btn = m.PushButton{parent=root, x=2, y=H, text="< BACK", min_width=8, fg_bg=style.wh_gray, active_fg_bg=style.ind_yel, callback=goBack} next_btn = m.PushButton{parent=root, x=W-9, y=H, text="NEXT >", min_width=8, fg_bg=style.wh_gray, active_fg_bg=style.ind_grn, callback=goNext} write_btn = m.PushButton{parent=root, x=W-9, y=H, text="WRITE", min_width=8, fg_bg=style.wh_gray, active_fg_bg=style.ind_grn, callback=doWrite} goTo(1) mimic.run()