Last active
January 13, 2024 17:20
-
-
Save Derrick355/7f53c9d14c3cbd4ff6b4554d6742492e to your computer and use it in GitHub Desktop.
Monitoring program for Mekanism Fission Reactors and ComputerCraft / CC:Tweaked
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--------------------------------------------------- | |
-- 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