commit 10f284a076fa32d5558335579df92039dde4079f Author: Dzejkobik007 Date: Tue Mar 7 10:47:52 2023 +0100 Add reactor.lua diff --git a/reactor.lua b/reactor.lua new file mode 100644 index 0000000..ef2acb5 --- /dev/null +++ b/reactor.lua @@ -0,0 +1,257 @@ +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()