Skip to content

Instantly share code, notes, and snippets.

@Derrick355
Last active January 13, 2024 17:20
Show Gist options
  • Save Derrick355/7f53c9d14c3cbd4ff6b4554d6742492e to your computer and use it in GitHub Desktop.
Save Derrick355/7f53c9d14c3cbd4ff6b4554d6742492e to your computer and use it in GitHub Desktop.
Monitoring program for Mekanism Fission Reactors and ComputerCraft / CC:Tweaked
---------------------------------------------------
-- Mekanism Fission Reactor Monitoring Program --
-- Created for CC:Tweaked and Mekanism --
-- Originally created by InternetUnexplorer on --
-- GitHub. Modified for personal use, by --
-- iPlay_G on Minecraft, Derrick355 on GitHub. --
-- As the original program did not contain any --
-- license, this will not contain any license. --
-- However, this software is provided "AS-IS", --
-- with NO WARRANTY of ANY kind. There is no --
-- guarentee of fitness for a particular --
-- purpose. --
-- Feel free to use it, but I, nor the --
-- original author are responsible for any --
-- issues arising from the use of this program. --
-- -=@=- --
-- Thank you for downloading this program. --
---------------------------------------------------
--- NOTES ---
-- If you see 'CRITICAL' on your monitor as the
-- state, that is OK. Critical is what real
-- reactors are considered when operating normally.
-- It is generally recommended to edit settings
-- in an IDE or text editor for ease of use.
-- It is assumed you have a 6 wide, 4 tall monitor
-- attached via a modem.
-- References to an analog lever are common, since
-- I used the Create mod, however if you don't
-- have it, any way to input a variable-power
-- redstone signal should work.
-- Binary levers are just vanillia levers.
-- I recommend putting this program on a disk,
-- and having a startup script run this program.
-- I have a second computer that I use to edit
-- the program in-game so that the main one
-- only runs this program, and nothing else.
--- RECOMMENDED & ASSUMED COMPUTER I/O SETUP ---
-- TOP: Binary lever, used to enable/disable manual burn rate control
-- LEFT: Analog lever, used as coarse adjust on burn control
-- RIGHT: Analog lever, used as fine adjust on burn control
-- BOTTOM: Binary lever, used as reactor on/off control
-- BACK: Modem to connect to reactor, turbine, and monitor
-- FRONT: Alarm or siren. Outputs a redstone signal if turbine power falls below a set threshold.
--- VARIABLE SETTINGS ---
-- Set the multiplier for the burn rate coarse setting.
-- Default = 75
burn_rate_coarse_multi = 75
-- Set the multiplier for the burn rate fine setting.
-- Default = 5
burn_rate_fine_multi = 5
-- Maximum auto burn rate to establish
-- Default = 120
burn_rate_auto_max = 120
-- What turbine power percent value should burn rate be max at?
-- For this value and all lower, burn rate will be at the max defined above.
-- Default = 0.5 (for 50%)
min_turbine_power = 0.50
-- What turbine power percent value should burn rate be zero at?
-- For this value and all higher, burn rate will be at zero.
-- Default = 0.75 (for 75%)
max_turbine_power = 0.75
-- What value should the alarm go off if turbine power falls below?
-- Default = 0.46
turbine_min_power = 0.46
-- What is the monitor called on the network?
-- Default = monitor_0
monitor_name = "monitor_2"
-- Are you playing on a server with Advanced peripherals?
-- This is used so that if a server is restarting the computer can
-- shot down the reactor. Note, the server must issue some kind of
-- restart warning beforehand.
-- Default = true
on_server = true
-- If the above is true, you must configure this line.
-- What message in chat should the program look for to see if the server
-- is restarting?
-- Default = "SCRAM"
restart_message = "SCRAM"
-- Do not modify anything past this line unless you know what you are doing.
------------------------------------------------
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 function check_for_restart_scram()
if fs.exists("disk/scrammed") then
incoming_restart = true
fs.delete("disk/scrammed")
fs.makeDir("disk/notify_restart")
else
incoming_restart = false
end
return incoming_restart
end
incoming_restart = check_for_restart_scram()
local function check_for_restart_notify()
if fs.exists("disk/notify_restart") then
notify_restart = true
fs.delete("disk/notify_restart")
else
notify_restart = false
end
return notify_restart
end
notify_restart = check_for_restart_notify()
local function pollChatbox()
while true do
local box = peripheral.find('chatBox', function(_, chatbox) return chatbox.getOperationCooldown('chatMessage') == 0 end)
if box then
return box
end
sleep(0.1)
end
end
------------------------------------------------
local rules = {}
local function add_rule(name, fn)
table.insert(rules, function()
local ok, rule_met, value = pcall(fn)
if ok and value ~= false then
return rule_met, string.format("%s ( %s)", name, value)
elseif ok then
return rule_met, string.format("%s (%s)", name, value)
else
return false, name
end
end)
end
add_rule("REACTOR TEMPERATURE <= 1100K ", function()
local value = string.format("%3dK", math.ceil(data.reactor_temp))
return data.reactor_temp <= 1100, 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)
if on_server then
add_rule("INCOMING SERVER RESTART == FALSE ", function()
return not incoming_restart, notify_restart
end)
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()
coarse_adjust = (tonumber(redstone.getAnalogInput("left")) * burn_rate_coarse_multi)
fine_adjust = (tonumber(redstone.getAnalogInput("right")) * burn_rate_fine_multi)
set_burn_rate = (coarse_adjust + fine_adjust)
data = {
lever_on = redstone.getInput("bottom"),
burn_rate_limit = set_burn_rate,
burn_rate_limited = 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(),
turbine_power = turbine.getEnergy(),
turbine_power = turbine.getEnergy(),
max_turbine_power = turbine.getMaxEnergy()
}
end
------------------------------------------------
local monitor = peripheral.wrap(monitor_name)
term.redirect(monitor)
local function colored(text, fg, bg)
term.setTextColor(fg or colors.white)
term.setBackgroundColor(bg or colors.black)
term.write(string.upper(text))
end
local function make_section(name, x, y, w, h, color)
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", color or 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 "CRITICAL" or "SHUTDOWN", data.reactor_on and colors.green or colors.red)
colored(" LEVER: ")
colored(data.lever_on and "POWERED" or "SECURED", data.lever_on and colors.green or colors.red)
colored(" BURN RATE: ")
if data.burn_rate_limited == false then
colored(string.format("%4.1f", data.reactor_burn_rate), colors.blue)
else
colored(string.format("%4.1f", data.reactor_burn_rate), colors.yellow)
end
colored("/", colors.lightGray)
colored(string.format("%4.0f", data.reactor_max_burn_rate), colors.blue)
term.setCursorPos(34, 2)
colored("SET LIMIT: ")
if data.burn_rate_limited then
colored(string.format("%4.1f", data.burn_rate_limit), colors.green)
else
colored(string.format("%4.1f", data.burn_rate_limit), colors.gray)
end
term.setCursorPos(1, 4)
colored("STATUS: ")
if state == STATES.READY then
colored("READY - PULL LEVER TO STARTUP", colors.blue)
elseif state == STATES.RUNNING then
colored("RUNNING - PULL LEVER TO SHUTDOWN", colors.green)
elseif state == STATES.ESTOP and not all_rules_met() then
colored("SCRAM - SAFETY RULE VIOLATED", colors.red)
elseif state == STATES.ESTOP then
colored("SCRAM - TOGGLE LEVER TO RESET", colors.red)
end -- STATES.UNKNOWN cases handled above
term.setCursorPos(1, 6)
colored("STORED POWER: ")
colored(string.format("%4.0f", (data.turbine_power*4/1000000)), colors.green)
colored(" MFE", colors.green)
colored("/", colors.lightGray)
colored(string.format("%4.0f", (data.max_turbine_power*4/1000000)), colors.blue)
colored(" MFE", colors.blue)
colored(" (")
colored(string.format("%5.2f", (data.turbine_energy*100)), colors.green)
colored("%", colors.green)
colored(")")
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("[ SAFE ] ", 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
-- Make Windows
local width = term.getSize()
if state == STATES.ESTOP then
window_color = colors.red
elseif state == STATES.READY then
window_color = colors.green
else
window_color = colors.gray
end
info_window = make_section("INFORMATION", 2, 2, width - 2, 10, window_color)
rules_window = make_section("SAFETY RULES", 2, 13, width - 2, 13, window_color)
-- Update info and rules windows
pcall(update_info)
pcall(update_rules)
-- Update max burn rate to keep turbine below 75% energy.
if state ~= STATES.UNKNOWN and data.burn_rate_limited == false then
local tgt_burn_pct = math.min(math.max(0.0, 1 - (data.turbine_energy - min_turbine_power) / (max_turbine_power - min_turbine_power)), 1.0)
pcall(reactor.setBurnRate, tgt_burn_pct * burn_rate_auto_max)
elseif state ~= STATES.UNKNOWN and data.burn_rate_limited == true then
pcall(reactor.setBurnRate, data.burn_rate_limit) -- Let burn rate be limited by an analog lever
end
if data.turbine_energy < turbine_min_power then --Sound an alarm that's attached to the front of the computer if turbine power falls below 45%.
redstone.setOutput("right", true)
else
redstone.setOutput("right", false)
end
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()
function scram_on_restart()
chatBox = peripheral.find("chatBox")
local event, username, message, uuid, isHidden = os.pullEvent("chat")
pollChatbox().sendMessage("username: " .. username .. " message: `".. message .. "`")
if message == restart_message and username == "sayCommand" then
--fs.copy("/disk/scram.lua", "/disk/scrammed.lua")
fs.makeDir("/disk/scrammed")
pollChatbox().sendMessage("SCRAM aye")
end
end
if on_server then
parallel.waitForAny(main_loop, scram_on_restart)
else
parallel.waitForAny(main_loop, function()
os.pullEventRaw("terminate")
end)
end
os.reboot()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment