Archives
Trending
Support
Login
clear text
XML
Django
JavaScript
MATLAB
C
C++
C#
Python
SQL
Shell
Bash
Markdown
YAML
JSON
HTML
CSS
PHP
Java
Ruby
Go
Rust
Swift
Kotlin
Arduino
TypeScript
Perl
Autohotkey
Lua
SQF
R
Scala
Haskell
Groovy
Dart
Clojure
VB.NET
Objective-C
PowerShell
Bash
CoffeeScript
Verilog
-- reactor_control.lua -- Mekanism Fission SCADA controller for Mekanism fission reactors using ComputerCraft -- Features: -- - Auto-detect one or more reactor peripherals -- - Monitor dashboard rendering on attached monitor peripheral -- - File logging of events and periodic stats to `scada_log.txt` -- - Network status replies over rednet (if a modem is present) -- - Per-reactor control via peripheral methods or redstone output -- - Alarm handling and safe shutdown local config = { control_mode = "auto", -- "auto", "peripheral" or "redstone" peripheral_sides = nil, -- if provided, a table mapping reactor names to sides, or nil for auto-discovery redstone_side_default = "left", -- default redstone side for reactors without a peripheral control poll_interval = 2, -- seconds between polls start_below_temp = 800, -- start when measured temperature falls below this stop_above_temp = 1200, -- stop when temperature rises above this safe_shutdown_temp = 1400,-- emergency shutdown if temperature > this log_to_term = true, log_file = "scada_log.txt", max_history = 1000, } local function term_write(...) if config.log_to_term then print(...) end end -- Utility: safe pcall wrapper for peripheral methods local function safe_call(fn, ...) if not fn then return false, "nofn" end local ok, res = pcall(fn, ...) if not ok then return false, res end return true, res end -- Discover monitor peripheral (first available) local function find_monitor() for _, name in ipairs(peripheral.getNames()) do local ok, ptype = pcall(peripheral.getType, name) if ok and ptype == "monitor" then local mon = peripheral.wrap(name) return mon, name end end return nil end -- Discover modem for rednet local function find_modem() for _, name in ipairs(peripheral.getNames()) do local ok, ptype = pcall(peripheral.getType, name) if ok and (ptype == "modem" or ptype == "wireless_modem") then local modem = peripheral.wrap(name) return modem, name end end return nil end -- Heuristics for reactor peripheral detection local reactor_method_signatures = {"getTemperature","getHeat","getReactorTemperature","getCasingTemperature","getCoreTemperature","getFissionTemperature"} local function looks_like_reactor(name) local methods = peripheral.getMethods(name) or {} local joined = table.concat(methods, " ") for _, sig in ipairs(reactor_method_signatures) do if joined:match(sig) then return true end end -- fallback: presence of keywords if joined:match("Fuel") or joined:match("Active") or joined:match("Heat") then return true end return false end -- Scan and build list of reactors local function scan_reactors() local reactors = {} for _, name in ipairs(peripheral.getNames()) do if looks_like_reactor(name) then local ok, p = pcall(peripheral.wrap, name) if ok and p then table.insert(reactors, {name = name, periph = p, control = "peripheral"}) end end end -- If none found and config allows redstone, create a pseudo-reactor that uses redstone side if #reactors == 0 and config.control_mode ~= "peripheral" then -- support a single redstone-controlled reactor table.insert(reactors, {name = "local_redstone", periph = nil, control = "redstone", redstone_side = config.redstone_side_default}) end return reactors end -- Probe a reactor peripheral for commonly available stats local read_attempt_names = { temperature = {"getTemperature","getHeat","getReactorTemperature","getCasingTemperature","getCoreTemperature","getFissionTemperature"}, active = {"getActive","isActive","getStatus","isRunning","isOnline"}, power = {"getEnergy","getEnergyStored","getGeneration","getOutput"}, fuel = {"getFuelAmount","getFuelRemaining","getFuelPercent","getFuel"} } local function probe_reactor_stats(periph) local stats = {} if not periph then return stats end -- temperature for _, name in ipairs(read_attempt_names.temperature) do if periph[name] then local ok, val = safe_call(periph[name]) if ok and type(val) == "number" then stats.temperature = val; stats.temp_method = name; break end end end -- active for _, name in ipairs(read_attempt_names.active) do if periph[name] then local ok, val = safe_call(periph[name]) if ok then stats.active = val; stats.active_method = name; break end end end -- power for _, name in ipairs(read_attempt_names.power) do if periph[name] then local ok, val = safe_call(periph[name]) if ok and type(val) == "number" then stats.power = val; stats.power_method = name; break end end end -- fuel for _, name in ipairs(read_attempt_names.fuel) do if periph[name] then local ok, val = safe_call(periph[name]) if ok and (type(val) == "number" or type(val) == "table") then stats.fuel = val; stats.fuel_method = name; break end end end return stats end -- Try to set reactor active via peripheral local function set_reactor_active_peripheral(periph, active) if not periph then return false end local candidates = {"setActive","setRunning","activate","deactivate","setOnline","setEnabled","enable","disable","setPower"} for _, name in ipairs(candidates) do if periph[name] then local ok, res = pcall(periph[name], active) if ok then return true end end end return false end -- Redstone control local function set_reactor_active_redstone(side, active) pcall(redstone.setOutput, side, active) return true end -- Simple logger local function write_log_line(line) local ok, f = pcall(fs.open, config.log_file, "a") if ok and f then f.writeLine(line) f.close() else -- fallback: try native io if available pcall(print, "LOG FAIL: " .. tostring(line)) end end local function log(msg) local timestamp = os.date("%Y-%m-%d %H:%M:%S") local line = string.format("[%s] %s", timestamp, tostring(msg)) term_write(line) write_log_line(line) end -- Render a dashboard on a monitor peripheral local function render_dashboard(mon, reactors, statuses) if not mon then return end local w,h = mon.getSize() mon.setTextScale(1) mon.setBackgroundColor(colors.black) mon.clear() mon.setCursorPos(1,1) mon.setTextColor(colors.white) mon.write("Mekanism Fission SCADA") mon.setCursorPos(1,2) mon.write(string.rep("-", math.max(1,w))) local row = 3 for i, r in ipairs(reactors) do local s = statuses[r.name] or {} local line = string.format("%d) %s | T:%s | A:%s | P:%s | F:%s", i, r.name, tostring(s.temperature or "?") , tostring(s.active or "?") , tostring(s.power or "?") , tostring(s.fuel or "?")) if row <= h then mon.setCursorPos(1,row) if s.temperature and s.temperature >= config.safe_shutdown_temp then mon.setTextColor(colors.red) else mon.setTextColor(colors.white) end mon.write(" " .. string.sub(line,1,w-1)) end row = row + 1 end -- footer if row <= h then mon.setCursorPos(1,row) mon.setTextColor(colors.gray) mon.write("Poll: " .. tostring(config.poll_interval) .. "s Reactors: " .. tostring(#reactors)) end end -- Main control loop local function main() log("SCADA starting...") local monitor, monName = find_monitor() if monitor then log("Monitor found: " .. monName) end local modem, modemName = find_modem() if modem then log("Modem found: " .. modemName) -- open rednet on the modem side if necessary local ok = pcall(rednet.open) if not ok then pcall(rednet.open, modemName) end -- rednet messages will be polled in the main loop end local reactors = scan_reactors() if #reactors == 0 then log("No reactors discovered") end local statuses = {} local history = {} while true do -- handle rednet messages (non-blocking) if modem and rednet then local sender, message = rednet.receive(0) if sender and message then if type(message) == "string" then if message:upper() == "STATUS" then rednet.send(sender, "SCADA: OK") elseif message:upper() == "PING" then rednet.send(sender, "PONG") else rednet.send(sender, "UNKNOWN") end end end end -- refresh reactor list periodically reactors = scan_reactors() for _, r in ipairs(reactors) do local s = {} if r.periph then s = probe_reactor_stats(r.periph) end -- attach control metadata if r.control == "redstone" then s.redstone_side = r.redstone_side or config.redstone_side_default end statuses[r.name] = s -- Take action based on temperature thresholds local temp = tonumber(s.temperature) local active = s.active if temp then if temp >= config.safe_shutdown_temp then log(string.format("EMERGENCY: %s temp=%s >= safe_shutdown=%s -> forcing shutdown", r.name, tostring(temp), tostring(config.safe_shutdown_temp))) if r.periph then set_reactor_active_peripheral(r.periph, false) else set_reactor_active_redstone(s.redstone_side or config.redstone_side_default, false) end elseif temp >= config.stop_above_temp and active then log(string.format("Stopping %s: temp=%s >= stop=%s", r.name, tostring(temp), tostring(config.stop_above_temp))) if r.periph then set_reactor_active_peripheral(r.periph, false) else set_reactor_active_redstone(s.redstone_side or config.redstone_side_default, false) end elseif temp <= config.start_below_temp and not active then log(string.format("Starting %s: temp=%s <= start=%s", r.name, tostring(temp), tostring(config.start_below_temp))) if r.periph then set_reactor_active_peripheral(r.periph, true) else set_reactor_active_redstone(s.redstone_side or config.redstone_side_default, true) end end end -- record history entry table.insert(history, {time = os.time(), reactor = r.name, stats = s}) if #history > config.max_history then table.remove(history,1) end -- alarms: beep if dangerously hot if monitor and s.temperature and s.temperature >= config.safe_shutdown_temp then term_write("ALARM: " .. r.name .. " temperature: " .. tostring(s.temperature)) end end -- render dashboard render_dashboard(monitor, reactors, statuses) -- periodic logging of all statuses for name, s in pairs(statuses) do local line = string.format("STAT %s T=%s A=%s P=%s F=%s", name, tostring(s.temperature or "?"), tostring(s.active or "?"), tostring(s.power or "?"), tostring(s.fuel or "?")) write_log_line(line) end os.sleep(config.poll_interval) end end -- Run main local ok, err = pcall(main) if not ok then log("Unhandled error: " .. tostring(err)) end
Mark as private (unlisted)
for 30 minutes
for 6 hours
for 1 day
for 1 week
for 1 month
for 1 year
everlasting (like CCCP)