Skip to content

Instantly share code, notes, and snippets.

@InternetUnexplorer
Created May 15, 2022 00:05
Show Gist options
  • Save InternetUnexplorer/ea13f1713d325b914126bcfb9b35e6fd to your computer and use it in GitHub Desktop.
Save InternetUnexplorer/ea13f1713d325b914126bcfb9b35e6fd to your computer and use it in GitHub Desktop.
ComputerCraft program to control a Mekanism fission reactor
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
}
------------------------------------------------
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_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.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 and data.lever_on then
-- READY -> RUNNING
state = STATES.RUNNING
-- Activate reactor
pcall(reactor.activate)
data.reactor_on = true
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()
@InternetUnexplorer
Copy link
Author

InternetUnexplorer commented Mar 28, 2023

You should be able to just place it on top of the computer :). If you want the redstone input to be on another side, you will have to change what is currently line 64.

The lever is nice because it lets you control the reactor without having to open the computer. I'm realizing now that I should probably update the gist with a description of my setup when I get a chance so people can use it more easily.

@Nima0908
Copy link

Placing it on the top doesn´t work

@InternetUnexplorer
Copy link
Author

Can you be more specific? Does the "LEVER" status in the UI change when you flip the lever?

@Nima0908
Copy link

Nima0908 commented Mar 28, 2023

Sorry, it does funktion, i was just stupid.
Thanks for your help

@InternetUnexplorer
Copy link
Author

No worries, glad you got it working :)

@DahleyvanH
Copy link

So i have been succesfully using this code very well done! i was more wondering if i can be able to activate the fission remotely using perhaps another computer and also display it on monitors there.. this would be cross dimension though. i did some test and i believe it can communicate using ender modems but i cant get the stuff to work.

@InternetUnexplorer
Copy link
Author

@DahleyvanH It's definitely possible (ender modems work across dimensions), but it will require a lot of changes! As I mentioned in another comment, this was designed for a very specific use-case (advanced computer controlled with a lever on top). I would be very cautious when modifying it since there can be a lot of not-so-obvious edge-cases for stuff like this, but it's definitely doable as long as you're careful! You could also consider moving the display and networking code to a second computer/program, and leaving the core control logic in place. You will have to make big changes to the code either way, so I think it mostly comes down to what you're comfortable with.

@DahleyvanH
Copy link

Hey Thanks to for the reply, i will not change the code as of now untill maybe i understand more about lua.. again great work on the code

@jon-chis
Copy link

jon-chis commented Apr 15, 2023

hi, so using the program and works well but (not sure what MC and Mekanism generators version this was made for) for MC 1.19.2 and Mekanism generators 10.3.8.477 the damage value for the fission reactor seems to already be a percentage so if the reactor took 1% of damage then the program would end up reading as 100% so i had to go and remove *100 from the damage math and then it was reading it correctly.
so thought i give a heads up

@Waltbo
Copy link

Waltbo commented Apr 28, 2023

Having issues trying to use this with a ender modem. Anyone have any solutions?

@InternetUnexplorer
Copy link
Author

@jon-chis Thanks for the feedback, I've been really busy the past few weeks but will change it when I get a chance.

@Waltbo Did you figure it out? If not, could you be more specific about what issues you are having?

@Superdie02
Copy link

Superdie02 commented Jan 7, 2024

ok so for some reason whenever i pull the lever connected via a fission reactor logic adapter it turns on for a milisecond and then turns off
does anyone know how to fix this?
edit: i was too lazy to read the other comments

@Derrick355
Copy link

Is it possible to have the reactor turn itself back on if the only reason it stopped was because the turbine had too much power? Maybe so that if the turbine power went above 95% it stopped but if it goes back down to 50% or something it'll turn itself back on automatically?

@InternetUnexplorer
Copy link
Author

@Derrick355 As it's written, the safety rules are never supposed to be violated; what I usually do is either modify this program (or add another computer, to keep concerns separate) that constantly adjusts the reactor's burn rate linearly between 100% and 0% at e.g. 75% and 90% turbine energy. This makes it so that only enough power is generated to meet demand, and the turbine energy doesn't exceed 90% (with the 95% limit still there in case something goes wrong).

@Derrick355
Copy link

@Derrick355 As it's written, the safety rules are never supposed to be violated; what I usually do is either modify this program (or add another computer, to keep concerns separate) that constantly adjusts the reactor's burn rate linearly between 100% and 0% at e.g. 75% and 90% turbine energy. This makes it so that only enough power is generated to meet demand, and the turbine energy doesn't exceed 90% (with the 95% limit still there in case something goes wrong).

Do you have the script that can do that? (I'm not that great at coding in lua but I can do a bit)

@InternetUnexplorer
Copy link
Author

@Derrick355 Sure, try inserting this on line 215 and it should adjust the burn rate linearly between 100% at ≤50% turbine energy and 0% at ≥75% turbine energy:

-- Update max burn rate to keep turbine below 75% energy
if state ~= STATES.UNKNOWN then
    local tgt_burn_pct = math.min(math.max(0.0, 1 - (data.turbine_energy - 0.5) / 0.25), 1.0)
    pcall(reactor.setBurnRate, tgt_burn_pct * data.reactor_max_burn_rate)
end

@Derrick355
Copy link

Derrick355 commented Jan 10, 2024

I can change data.reactor_max_burn_rate if I want to set a manual cap on max burn rate, right?
If I let it go to the max burn rate it instantly vaporizes a lot of water and the reactor will auto shutoff

Also, if I want to change the 75% and 50% values, what do I edit?

@InternetUnexplorer
Copy link
Author

@Derrick355 Yes, you can replace reactor_max_burn_rate with a constant (in mB/t) if you want a manual cap.

The function is math.min(math.max(0.0, 1 - (data.turbine_energy - A) / B), 1.0), where:

  • A is the lower limit (currently 0.5 = 50%), and
  • A + B is the upper limit (currently 0.5 + 0.25 = 0.75 = 75%).

@GeilesLuder
Copy link

GeilesLuder commented Jan 12, 2024

Hey, this might be a stupid question but how do i need to connect the computer to the reactor and what should the mode of the redstone logic adapter be?
i tried it with a modem to the logic adapter and every mode possible on the logic adapter. I also tried just sticking the computer directly to the logic adapter but nothing worked. im a bit lost here.
It always says failed to retrive data.
thanks in advance.

Edit:
Nvmd i figured it out. I forgot to connect the turbine.

@Kekalicious
Copy link

Hey this code is exactly what I was looking for. Do you know if there is a way to have the display scale to any size?

@InternetUnexplorer
Copy link
Author

@Kekalicious Hi! So, unfortunately I wrote this in a hurry and designed the UI around one specific size (built-in screen of an advanced computer) since it's easier to make everything look nice for a fixed size.

That said, it shouldn't be too hard to change; it's not a very complicated program, and so it should be easy to tweak it as needed to fit your situation. :)

@stefancreteanu
Copy link

@GeilesLuder connect the turbine to what?
I am also having some issues with the connection.

@GeilesLuder
Copy link

@GeilesLuder connect the turbine to what? I am also having some issues with the connection.

Hey, you also need to connect the turbine to the computer(via modem, same as the reactor itself). I forgot that initially.

@DesiredToResign
Copy link

how do i use rednet with this instead of wired routers?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment