local state, data, reactor, turbine, info_window, rules_window local STATES = { READY = 1, -- Reactor is off and can be started with the lever RUNNING = 2, -- Reactor is running and all rules are met ESTOP = 3, -- Reactor is stopped due to rule(s) being violated UNKNOWN = 4, -- Reactor or turbine peripherals are missing ENERGY = 5, FUEL = 6, } ------------------------------------------------ local rules = {} local function add_rule(name, fn) table.insert(rules, function() local ok, rule_met, value = pcall(fn) if ok then return rule_met, string.format("%s (%s)", name, value) else return false, name end end) end add_rule("REACTOR TEMPERATURE <= 745K", function() local value = string.format("%3dK", math.ceil(data.reactor_temp)) return data.reactor_temp <= 745, value end) add_rule("REACTOR DAMAGE <= 10%", function() local value = string.format("%3d%%", math.ceil(data.reactor_damage * 100)) return data.reactor_damage <= 0.10, value end) add_rule("REACTOR COOLANT LEVEL >= 95%", function() local value = string.format("%3d%%", math.floor(data.reactor_coolant * 100)) return data.reactor_coolant >= 0.95, value end) add_rule("REACTOR WASTE LEVEL <= 90%", function() local value = string.format("%3d%%", math.ceil(data.reactor_waste * 100)) return data.reactor_waste <= 0.90, value end) add_rule("TURBINE ENERGY LEVEL <= 95%", function() local value = string.format("%3d%%", math.ceil(data.turbine_energy * 100)) return data.turbine_energy <= 0.95, value end) local function all_rules_met() for i, rule in ipairs(rules) do if not rule() then return false end end -- Allow manual emergency stop with SCRAM button return state ~= STATES.RUNNING or data.reactor_on end ------------------------------------------------ local function update_data() data = { lever_on = redstone.getInput("top"), reactor_on = reactor.getStatus(), reactor_fuel, reactor_burn_rate = reactor.getBurnRate(), reactor_max_burn_rate = reactor.getMaxBurnRate(), reactor_temp = reactor.getTemperature(), reactor_damage = reactor.getDamagePercent(), reactor_coolant = reactor.getCoolantFilledPercentage(), reactor_waste = reactor.getWasteFilledPercentage(), turbine_energy = turbine.getEnergyFilledPercentage(), } end ------------------------------------------------ local function colored(text, fg, bg) term.setTextColor(fg or colors.white) term.setBackgroundColor(bg or colors.black) term.write(text) end local function make_section(name, x, y, w, h) for row = 1, h do term.setCursorPos(x, y + row - 1) local char = (row == 1 or row == h) and "\127" or " " colored("\127" .. string.rep(char, w - 2) .. "\127", colors.gray) end term.setCursorPos(x + 2, y) colored(" " .. name .. " ") return window.create(term.current(), x + 2, y + 2, w - 4, h - 4) end local function update_info() local prev_term = term.redirect(info_window) term.clear() term.setCursorPos(1, 1) if state == STATES.UNKNOWN then colored("ERROR RETRIEVING DATA", colors.red) return end colored("REACTOR: ") colored(data.reactor_on and "ON " or "OFF", data.reactor_on and colors.green or colors.red) colored(" LEVER: ") colored(data.lever_on and "ON " or "OFF", data.lever_on and colors.green or colors.red) colored(" R. LIMIT: ") colored(string.format("%4.1f", data.reactor_burn_rate), colors.blue) colored("/", colors.lightGray) colored(string.format("%4.1f", data.reactor_max_burn_rate), colors.blue) term.setCursorPos(1, 3) colored("STATUS: ") if state == STATES.READY then colored("READY, flip lever to start", colors.blue) elseif state == STATES.RUNNING then colored("RUNNING, flip lever to stop", colors.green) elseif state == STATES.ENERGY then colored("STOPPED, Reached energy target", colors.yellow) elseif state == STATES.FUEL then colored("STOPPED, Not enough fuel", colors.yellow) elseif state == STATES.ESTOP and not all_rules_met() then colored("EMERGENCY STOP, safety rules violated", colors.red) elseif state == STATES.ESTOP then colored("EMERGENCY STOP, toggle lever to reset", colors.red) end -- STATES.UNKNOWN cases handled above term.redirect(prev_term) end local estop_reasons = {} local function update_rules() local prev_term = term.redirect(rules_window) term.clear() if state ~= STATES.ESTOP then estop_reasons = {} end for i, rule in ipairs(rules) do local ok, text = rule() term.setCursorPos(1, i) if ok and not estop_reasons[i] then colored("[ OK ] ", colors.green) colored(text, colors.lightGray) else colored("[ FAIL ] ", colors.red) colored(text, colors.red) estop_reasons[i] = true end end term.redirect(prev_term) end ------------------------------------------------ local function main_loop() -- Search for peripherals again if one or both are missing if not state or state == STATES.UNKNOWN then reactor = peripheral.find("fissionReactorLogicAdapter") turbine = peripheral.find("turbineValve") end if not pcall(update_data) then -- Error getting data (either reactor or turbine is nil?) data = {} state = STATES.UNKNOWN elseif data.reactor_on == nil then -- Reactor is not connected state = STATES.UNKNOWN elseif data.turbine_energy == nil then -- Turbine is not connected state = STATES.UNKNOWN elseif not state then -- Program just started, get current state from lever state = data.lever_on and STATES.RUNNING or STATES.READY elseif state == STATES.READY or state == STATES.RUNNING or STATES.ENERGY and data.turbine_energy > 50 then -- RUNNING -> READY pcall(reactor.scram) state = STATES.ENERGY elseif state == STATES.READY or state == STATES.RUNNING or STATES.FUEL and data.reactor_fuel < 20 then -- RUNNING -> READY pcall(reactor.scram) state = STATES.FUEL elseif (state = STATES.ENERGY and data.turbine_energy < 1) and (STATES.FUEL and data.reactor_fuel > 20) then state = STATES.READY elseif state == STATES.READY and data.lever_on then -- READY -> RUNNING state = STATES.RUNNING -- Activate reactor pcall(reactor.setBurnRate(1)) pcall(reactor.activate) data.reactor_on = true elseif state == STATES.RUNNING and data.reactor_coolant > 98 and data.lever_on and not (data.reactor_max_burn_rate == data.reactor_burn_rate) then -- RUNNING -> READY pcall(reactor.setBurnRate(data.reactor_burn_rate+1)) elseif state == STATES.RUNNING and not data.lever_on then -- RUNNING -> READY state = STATES.READY elseif state == STATES.ESTOP and not data.lever_on then -- ESTOP -> READY state = STATES.READY elseif state == STATES.UNKNOWN then -- UNKNOWN -> ESTOP state = data.lever_on and STATES.ESTOP or STATES.READY estop_reasons = {} end -- Always enter ESTOP if safety rules are not met if state ~= STATES.UNKNOWN and not all_rules_met() then state = STATES.ESTOP end -- SCRAM reactor if not running if state ~= STATES.RUNNING and reactor then pcall(reactor.scram) end -- Update info and rules windows pcall(update_info) pcall(update_rules) sleep() -- Other calls should already yield, this is just in case return main_loop() end term.setPaletteColor(colors.black, 0x000000) term.setPaletteColor(colors.gray, 0x343434) term.setPaletteColor(colors.lightGray, 0xababab) term.setPaletteColor(colors.red, 0xdb2d20) term.setPaletteColor(colors.green, 0x01a252) term.setPaletteColor(colors.blue, 0x01a0e4) term.clear() local width = term.getSize() info_window = make_section("INFORMATION", 2, 2, width - 2, 7) rules_window = make_section("SAFETY RULES", 2, 10, width - 2, 9) parallel.waitForAny(main_loop, function() os.pullEventRaw("terminate") end) os.reboot()