From 6830619316cd3406282cd096f47a01bd85ebf48f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 28 Dec 2021 16:37:10 -0500 Subject: [PATCH] version 0.7 from external repo - hardcoded UI/config, redstone control only --- main/controller.lua | 135 ++++++++++++++++ main/defs.lua | 23 +++ main/log.lua | 52 +++++++ main/reactor.lua | 28 ++++ main/regulator.lua | 128 +++++++++++++++ main/render.lua | 370 ++++++++++++++++++++++++++++++++++++++++++++ main/server.lua | 109 +++++++++++++ signal-router.lua | 159 +++++++++++++++++++ 8 files changed, 1004 insertions(+) create mode 100644 main/controller.lua create mode 100644 main/defs.lua create mode 100644 main/log.lua create mode 100644 main/reactor.lua create mode 100644 main/regulator.lua create mode 100644 main/render.lua create mode 100644 main/server.lua create mode 100644 signal-router.lua diff --git a/main/controller.lua b/main/controller.lua new file mode 100644 index 0000000..b0e18b7 --- /dev/null +++ b/main/controller.lua @@ -0,0 +1,135 @@ +-- mekanism reactor controller +-- monitors and regulates mekanism reactors + +os.loadAPI("reactor.lua") +os.loadAPI("defs.lua") +os.loadAPI("log.lua") +os.loadAPI("render.lua") +os.loadAPI("server.lua") +os.loadAPI("regulator.lua") + +-- constants, aliases, properties +local header = "MEKANISM REACTOR CONTROLLER - v" .. defs.CTRL_VERSION +local monitor_0 = peripheral.wrap(defs.MONITOR_0) +local monitor_1 = peripheral.wrap(defs.MONITOR_1) +local monitor_2 = peripheral.wrap(defs.MONITOR_2) +local monitor_3 = peripheral.wrap(defs.MONITOR_3) + +monitor_0.setBackgroundColor(colors.black) +monitor_0.setTextColor(colors.white) +monitor_0.clear() + +monitor_1.setBackgroundColor(colors.black) +monitor_1.setTextColor(colors.white) +monitor_1.clear() + +monitor_2.setBackgroundColor(colors.black) +monitor_2.setTextColor(colors.white) +monitor_2.clear() + +log.init(monitor_3) + +local main_w, main_h = monitor_0.getSize() +local view = window.create(monitor_0, 1, 1, main_w, main_h) +view.setBackgroundColor(colors.black) +view.clear() + +local stat_w, stat_h = monitor_1.getSize() +local stat_view = window.create(monitor_1, 1, 1, stat_w, stat_h) +stat_view.setBackgroundColor(colors.black) +stat_view.clear() + +local reactors = { + reactor.create(1, view, stat_view, 62, 3, 63, 2), + reactor.create(2, view, stat_view, 42, 3, 43, 2), + reactor.create(3, view, stat_view, 22, 3, 23, 2), + reactor.create(4, view, stat_view, 2, 3, 3, 2) +} +print("[debug] reactor tables created") + +server.init(reactors) +print("[debug] modem server started") + +regulator.init(reactors) +print("[debug] regulator started") + +-- header +view.setBackgroundColor(colors.white) +view.setTextColor(colors.black) +view.setCursorPos(1, 1) +local header_pad_x = (main_w - string.len(header)) / 2 +view.write(string.rep(" ", header_pad_x) .. header .. string.rep(" ", header_pad_x)) + +-- inital draw of each reactor +for key, rctr in pairs(reactors) do + render.draw_reactor_system(rctr) + render.draw_reactor_status(rctr) +end + +-- inital draw of clock +monitor_2.setTextScale(2) +monitor_2.setCursorPos(1, 1) +monitor_2.write(os.date("%Y/%m/%d %H:%M:%S")) + +local clock_update_timer = os.startTimer(1) + +while true do + event, param1, param2, param3, param4, param5 = os.pullEvent() + + if event == "redstone" then + -- redstone state change + regulator.handle_redstone() + elseif event == "modem_message" then + -- received signal router packet + packet = { + side = param1, + sender = param2, + reply = param3, + message = param4, + distance = param5 + } + + server.handle_message(packet, reactors) + elseif event == "monitor_touch" then + if param1 == "monitor_5" then + local tap_x = param2 + local tap_y = param3 + + for key, rctr in pairs(reactors) do + if tap_x >= rctr.render.stat_x and tap_x <= (rctr.render.stat_x + 15) then + local old_val = rctr.waste_production + -- width in range + if tap_y == (rctr.render.stat_y + 12) then + rctr.waste_production = "plutonium" + elseif tap_y == (rctr.render.stat_y + 14) then + rctr.waste_production = "polonium" + elseif tap_y == (rctr.render.stat_y + 16) then + rctr.waste_production = "antimatter" + end + + -- notify reactor of changes + if old_val ~= rctr.waste_production then + server.send(rctr.id, rctr.waste_production) + end + end + end + end + elseif event == "timer" then + -- update the clock about every second + monitor_2.setCursorPos(1, 1) + monitor_2.write(os.date("%Y/%m/%d %H:%M:%S")) + clock_update_timer = os.startTimer(1) + + -- send keep-alive + server.broadcast(1) + end + + -- update reactor display + for key, rctr in pairs(reactors) do + render.draw_reactor_system(rctr) + render.draw_reactor_status(rctr) + end + + -- update system status monitor + render.update_system_monitor(monitor_2, regulator.is_scrammed(), reactors) +end diff --git a/main/defs.lua b/main/defs.lua new file mode 100644 index 0000000..13f6803 --- /dev/null +++ b/main/defs.lua @@ -0,0 +1,23 @@ +-- configuration definitions + +CTRL_VERSION = "0.7" + +-- monitors +MONITOR_0 = "monitor_6" +MONITOR_1 = "monitor_5" +MONITOR_2 = "monitor_7" +MONITOR_3 = "monitor_8" + +-- modem server +LISTEN_PORT = 1000 + +-- regulator (should match the number of reactors present) +BUNDLE_DEF = { colors.red, colors.orange, colors.yellow, colors.lime } + +-- stats calculation +REACTOR_MB_T = 39 +TURBINE_MRF_T = 3.114 +PLUTONIUM_PER_WASTE = 0.1 +POLONIUM_PER_WASTE = 0.1 +SPENT_PER_BYPRODUCT = 1 +ANTIMATTER_PER_POLONIUM = 0.001 diff --git a/main/log.lua b/main/log.lua new file mode 100644 index 0000000..c4e1cbb --- /dev/null +++ b/main/log.lua @@ -0,0 +1,52 @@ +os.loadAPI("defs.lua") + +local out, out_w, out_h +local output_full = false + +-- initialize the logger to the given monitor +-- monitor: monitor to write to (in addition to calling print()) +function init(monitor) + out = monitor + out_w, out_h = out.getSize() + + out.clear() + out.setTextColor(colors.white) + out.setBackgroundColor(colors.black) + + out.setCursorPos(1, 1) + out.write("version " .. defs.CTRL_VERSION) + out.setCursorPos(1, 2) + out.write("system startup at " .. os.date("%Y/%m/%d %H:%M:%S")) + + print("server v" .. defs.CTRL_VERSION .. " started at " .. os.date("%Y/%m/%d %H:%M:%S")) +end + +-- write a log message to the log screen and console +-- msg: message to write +-- color: (optional) color to print in, defaults to white +function write(msg, color) + color = color or colors.white + local _x, _y = out.getCursorPos() + + if output_full then + out.scroll(1) + out.setCursorPos(1, _y) + else + if _y == out_h then + output_full = true + out.scroll(1) + out.setCursorPos(1, _y) + else + out.setCursorPos(1, _y + 1) + end + end + + -- output to screen + out.setTextColor(colors.lightGray) + out.write(os.date("[%H:%M:%S] ")) + out.setTextColor(color) + out.write(msg) + + -- output to console + print(os.date("[%H:%M:%S] ") .. msg) +end diff --git a/main/reactor.lua b/main/reactor.lua new file mode 100644 index 0000000..137b46c --- /dev/null +++ b/main/reactor.lua @@ -0,0 +1,28 @@ +-- create a new reactor 'object' +-- reactor_id: the ID for this reactor +-- main_view: the parent window/monitor for the main display (components) +-- status_view: the parent window/monitor for the status display +-- main_x: where to create the main window, x coordinate +-- main_y: where to create the main window, y coordinate +-- status_x: where to create the status window, x coordinate +-- status_y: where to create the status window, y coordinate +function create(reactor_id, main_view, status_view, main_x, main_y, status_x, status_y) + return { + id = reactor_id, + render = { + win_main = window.create(main_view, main_x, main_y, 20, 60, true), + win_stat = window.create(status_view, status_x, status_y, 20, 20, true), + stat_x = status_x, + stat_y = status_y + }, + control_state = false, + waste_production = "antimatter", -- "plutonium", "polonium", "antimatter" + state = { + run = false, + no_fuel = false, + full_waste = false, + high_temp = false, + damage_crit = false + } + } +end diff --git a/main/regulator.lua b/main/regulator.lua new file mode 100644 index 0000000..e8acf55 --- /dev/null +++ b/main/regulator.lua @@ -0,0 +1,128 @@ +os.loadAPI("defs.lua") +os.loadAPI("log.lua") +os.loadAPI("server.lua") + +local reactors +local scrammed +local auto_scram + +-- initialize the system regulator which provides safety measures, SCRAM functionality, and handles redstone +-- _reactors: reactor table +function init(_reactors) + reactors = _reactors + scrammed = false + auto_scram = false + + -- scram all reactors + server.broadcast(false, reactors) + + -- check initial states + regulator.handle_redstone() +end + +-- check if the system is scrammed +function is_scrammed() + return scrammed +end + +-- handle redstone state changes +function handle_redstone() + -- check scram button + if not rs.getInput("right") then + if not scrammed then + log.write("user SCRAM", colors.red) + scram() + end + + -- toggling scram will release auto scram state + auto_scram = false + else + scrammed = false + end + + -- check individual control buttons + local input = rs.getBundledInput("left") + for key, rctr in pairs(reactors) do + if colors.test(input, defs.BUNDLE_DEF[key]) ~= rctr.control_state then + -- state changed + rctr.control_state = colors.test(input, defs.BUNDLE_DEF[key]) + if not scrammed then + local safe = true + + if rctr.control_state then + safe = check_enable_safety(reactors[key]) + if safe then + log.write("reactor " .. reactors[key].id .. " enabled", colors.lime) + end + else + log.write("reactor " .. reactors[key].id .. " disabled", colors.cyan) + end + + -- start/stop reactor + if safe then + server.send(rctr.id, rctr.control_state) + end + elseif colors.test(input, defs.BUNDLE_DEF[key]) then + log.write("scrammed: state locked off", colors.yellow) + end + end + end +end + +-- make sure enabling the provided reactor is safe +-- reactor: reactor to check +function check_enable_safety(reactor) + if reactor.state.no_fuel or reactor.state.full_waste or reactor.state.high_temp or reactor.state.damage_crit then + log.write("RCT-" .. reactor.id .. ": unsafe enable denied", colors.yellow) + return false + else + return true + end +end + +-- make sure no running reactors are in a bad state +function enforce_safeties() + for key, reactor in pairs(reactors) do + local overridden = false + local state = reactor.state + + -- check for problems + if state.damage_crit and state.run then + reactor.control_state = false + log.write("RCT-" .. reactor.id .. ": shut down (damage)", colors.yellow) + + -- scram all, so ignore setting overridden + log.write("auto SCRAM all reactors", colors.red) + auto_scram = true + scram() + elseif state.high_temp and state.run then + reactor.control_state = false + overridden = true + log.write("RCT-" .. reactor.id .. ": shut down (temp)", colors.yellow) + elseif state.full_waste and state.run then + reactor.control_state = false + overridden = true + log.write("RCT-" .. reactor.id .. ": shut down (waste)", colors.yellow) + elseif state.no_fuel and state.run then + reactor.control_state = false + overridden = true + log.write("RCT-" .. reactor.id .. ": shut down (fuel)", colors.yellow) + end + + if overridden then + server.send(reactor.id, false) + end + end +end + +-- shut down all reactors and prevent enabling them until the scram button is toggled/released +function scram() + scrammed = true + server.broadcast(false, reactors) + + for key, rctr in pairs(reactors) do + if rctr.control_state then + log.write("reactor " .. reactors[key].id .. " disabled", colors.cyan) + end + end +end diff --git a/main/render.lua b/main/render.lua new file mode 100644 index 0000000..e10614d --- /dev/null +++ b/main/render.lua @@ -0,0 +1,370 @@ +os.loadAPI("defs.lua") + +-- draw pipes between machines +-- win: window to render in +-- x: starting x coord +-- y: starting y coord +-- spacing: spacing between the pipes +-- color_out: output pipe contents color +-- color_ret: return pipe contents color +-- tick: tick the pipes for an animation +function draw_pipe(win, x, y, spacing, color_out, color_ret, tick) + local _color + local _off + tick = tick or 0 + + for i = 0, 4, 1 + do + _off = (i + tick) % 2 == 0 or (tick == 1 and i == 0) or (tick == 3 and i == 4) + + if _off then + _color = colors.lightGray + else + _color = color_out + end + + win.setBackgroundColor(_color) + win.setCursorPos(x, y + i) + win.write(" ") + + if not _off then + _color = color_ret + end + + win.setBackgroundColor(_color) + win.setCursorPos(x + spacing, y + i) + win.write(" ") + end +end + +-- draw a reactor view consisting of the reactor, boiler, turbine, and pipes +-- data: reactor table +function draw_reactor_system(data) + local win = data.render.win_main + local win_w, win_h = win.getSize() + + win.setBackgroundColor(colors.black) + win.setTextColor(colors.black) + win.clear() + win.setCursorPos(1, 1) + + -- draw header -- + + local header = "REACTOR " .. data.id + local header_pad_x = (win_w - string.len(header) - 2) / 2 + local header_color + if data.state.no_fuel then + if data.state.run then + header_color = colors.purple + else + header_color = colors.brown + end + elseif data.state.full_waste then + header_color = colors.yellow + elseif data.state.high_temp then + header_color = colors.orange + elseif data.state.damage_crit then + header_color = colors.red + elseif data.state.run then + header_color = colors.green + else + header_color = colors.lightGray + end + + local running = data.state.run and not data.state.no_fuel + + win.write(" ") + win.setBackgroundColor(header_color) + win.write(string.rep(" ", win_w - 2)) + win.setBackgroundColor(colors.black) + win.write(" ") + win.setCursorPos(1, 2) + win.write(" ") + win.setBackgroundColor(header_color) + win.write(string.rep(" ", header_pad_x) .. header .. string.rep(" ", header_pad_x)) + win.setBackgroundColor(colors.black) + win.write(" ") + + -- create strings for use in blit + local line_text = string.rep(" ", 14) + local line_text_color = string.rep("0", 14) + + -- draw components -- + + -- draw reactor + local rod = "88" + if data.state.high_temp then + rod = "11" + elseif running then + rod = "99" + end + + win.setCursorPos(4, 4) + win.setBackgroundColor(colors.gray) + win.write(line_text) + win.setCursorPos(4, 5) + win.blit(line_text, line_text_color, "77" .. rod .. "77" .. rod .. "77" .. rod .. "77") + win.setCursorPos(4, 6) + win.blit(line_text, line_text_color, "7777" .. rod .. "77" .. rod .. "7777") + win.setCursorPos(4, 7) + win.blit(line_text, line_text_color, "77" .. rod .. "77" .. rod .. "77" .. rod .. "77") + win.setCursorPos(4, 8) + win.blit(line_text, line_text_color, "7777" .. rod .. "77" .. rod .. "7777") + win.setCursorPos(4, 9) + win.blit(line_text, line_text_color, "77" .. rod .. "77" .. rod .. "77" .. rod .. "77") + win.setCursorPos(4, 10) + win.write(line_text) + + -- boiler + local steam = "ffffffffff" + if running then + steam = "0000000000" + end + + win.setCursorPos(4, 16) + win.setBackgroundColor(colors.gray) + win.write(line_text) + win.setCursorPos(4, 17) + win.blit(line_text, line_text_color, "77" .. steam .. "77") + win.setCursorPos(4, 18) + win.blit(line_text, line_text_color, "77" .. steam .. "77") + win.setCursorPos(4, 19) + win.blit(line_text, line_text_color, "77888888888877") + win.setCursorPos(4, 20) + win.blit(line_text, line_text_color, "77bbbbbbbbbb77") + win.setCursorPos(4, 21) + win.blit(line_text, line_text_color, "77bbbbbbbbbb77") + win.setCursorPos(4, 22) + win.blit(line_text, line_text_color, "77bbbbbbbbbb77") + win.setCursorPos(4, 23) + win.setBackgroundColor(colors.gray) + win.write(line_text) + + -- turbine + win.setCursorPos(4, 29) + win.setBackgroundColor(colors.gray) + win.write(line_text) + win.setCursorPos(4, 30) + if running then + win.blit(line_text, line_text_color, "77000000000077") + else + win.blit(line_text, line_text_color, "77ffffffffff77") + end + win.setCursorPos(4, 31) + if running then + win.blit(line_text, line_text_color, "77008000080077") + else + win.blit(line_text, line_text_color, "77ff8ffff8ff77") + end + win.setCursorPos(4, 32) + if running then + win.blit(line_text, line_text_color, "77000800800077") + else + win.blit(line_text, line_text_color, "77fff8ff8fff77") + end + win.setCursorPos(4, 33) + if running then + win.blit(line_text, line_text_color, "77000088000077") + else + win.blit(line_text, line_text_color, "77ffff88ffff77") + end + win.setCursorPos(4, 34) + if running then + win.blit(line_text, line_text_color, "77000800800077") + else + win.blit(line_text, line_text_color, "77fff8ff8fff77") + end + win.setCursorPos(4, 35) + if running then + win.blit(line_text, line_text_color, "77008000080077") + else + win.blit(line_text, line_text_color, "77ff8ffff8ff77") + end + win.setCursorPos(4, 36) + if running then + win.blit(line_text, line_text_color, "77000000000077") + else + win.blit(line_text, line_text_color, "77ffffffffff77") + end + win.setCursorPos(4, 37) + win.setBackgroundColor(colors.gray) + win.write(line_text) + + -- draw reactor coolant pipes + draw_pipe(win, 7, 11, 6, colors.orange, colors.lightBlue) + + -- draw turbine pipes + draw_pipe(win, 7, 24, 6, colors.white, colors.blue) +end + +-- draw the reactor statuses on the status screen +-- data: reactor table +function draw_reactor_status(data) + local win = data.render.win_stat + + win.setBackgroundColor(colors.black) + win.setTextColor(colors.white) + win.clear() + + -- show control state + win.setCursorPos(1, 1) + if data.control_state then + win.blit(" + ENABLED", "00000000000", "dddffffffff") + else + win.blit(" - DISABLED", "000000000000", "eeefffffffff") + end + + -- show run state + win.setCursorPos(1, 2) + if data.state.run then + win.blit(" + RUNNING", "00000000000", "dddffffffff") + else + win.blit(" - STOPPED", "00000000000", "888ffffffff") + end + + -- show fuel state + win.setCursorPos(1, 4) + if data.state.no_fuel then + win.blit(" - NO FUEL", "00000000000", "eeeffffffff") + else + win.blit(" + FUEL OK", "00000000000", "999ffffffff") + end + + -- show waste state + win.setCursorPos(1, 5) + if data.state.full_waste then + win.blit(" - WASTE FULL", "00000000000000", "eeefffffffffff") + else + win.blit(" + WASTE OK", "000000000000", "999fffffffff") + end + + -- show high temp state + win.setCursorPos(1, 6) + if data.state.high_temp then + win.blit(" - HIGH TEMP", "0000000000000", "eeeffffffffff") + else + win.blit(" + TEMP OK", "00000000000", "999ffffffff") + end + + -- show damage state + win.setCursorPos(1, 7) + if data.state.damage_crit then + win.blit(" - CRITICAL DAMAGE", "0000000000000000000", "eeeffffffffffffffff") + else + win.blit(" + CASING INTACT", "00000000000000000", "999ffffffffffffff") + end + + -- waste processing options -- + win.setTextColor(colors.black) + win.setBackgroundColor(colors.white) + + win.setCursorPos(1, 10) + win.write(" ") + win.setCursorPos(1, 11) + win.write(" WASTE OUTPUT ") + + win.setCursorPos(1, 13) + win.setBackgroundColor(colors.cyan) + if data.waste_production == "plutonium" then + win.write(" > plutonium ") + else + win.write(" plutonium ") + end + + win.setCursorPos(1, 15) + win.setBackgroundColor(colors.green) + if data.waste_production == "polonium" then + win.write(" > polonium ") + else + win.write(" polonium ") + end + + win.setCursorPos(1, 17) + win.setBackgroundColor(colors.purple) + if data.waste_production == "antimatter" then + win.write(" > antimatter ") + else + win.write(" antimatter ") + end +end + +-- update the system monitor screen +-- mon: monitor to update +-- is_scrammed: +function update_system_monitor(mon, is_scrammed, reactors) + if is_scrammed then + -- display scram banner + mon.setTextColor(colors.white) + mon.setBackgroundColor(colors.black) + mon.setCursorPos(1, 2) + mon.clearLine() + mon.setBackgroundColor(colors.red) + mon.setCursorPos(1, 3) + mon.write(" ") + mon.setCursorPos(1, 4) + mon.write(" SCRAM ") + mon.setCursorPos(1, 5) + mon.write(" ") + mon.setBackgroundColor(colors.black) + mon.setCursorPos(1, 6) + mon.clearLine() + mon.setTextColor(colors.white) + else + -- clear where scram banner would be + mon.setCursorPos(1, 3) + mon.clearLine() + mon.setCursorPos(1, 4) + mon.clearLine() + mon.setCursorPos(1, 5) + mon.clearLine() + + -- show production statistics-- + + local mrf_t = 0 + local mb_t = 0 + local plutonium = 0 + local polonium = 0 + local spent_waste = 0 + local antimatter = 0 + + -- determine production values + for key, rctr in pairs(reactors) do + if rctr.state.run then + mrf_t = mrf_t + defs.TURBINE_MRF_T + mb_t = mb_t + defs.REACTOR_MB_T + + if rctr.waste_production == "plutonium" then + plutonium = plutonium + (defs.REACTOR_MB_T * defs.PLUTONIUM_PER_WASTE) + spent_waste = spent_waste + (defs.REACTOR_MB_T * defs.PLUTONIUM_PER_WASTE * defs.SPENT_PER_BYPRODUCT) + elseif rctr.waste_production == "polonium" then + polonium = polonium + (defs.REACTOR_MB_T * defs.POLONIUM_PER_WASTE) + spent_waste = spent_waste + (defs.REACTOR_MB_T * defs.POLONIUM_PER_WASTE * defs.SPENT_PER_BYPRODUCT) + elseif rctr.waste_production == "antimatter" then + antimatter = antimatter + (defs.REACTOR_MB_T * defs.POLONIUM_PER_WASTE * defs.ANTIMATTER_PER_POLONIUM) + end + end + end + + -- draw stats + mon.setTextColor(colors.lightGray) + mon.setCursorPos(1, 2) + mon.clearLine() + mon.write("ENERGY: " .. string.format("%0.2f", mrf_t) .. " MRF/t") + -- mon.setCursorPos(1, 3) + -- mon.clearLine() + -- mon.write("FUEL: " .. mb_t .. " mB/t") + mon.setCursorPos(1, 3) + mon.clearLine() + mon.write("Pu: " .. string.format("%0.2f", plutonium) .. " mB/t") + mon.setCursorPos(1, 4) + mon.clearLine() + mon.write("Po: " .. string.format("%0.2f", polonium) .. " mB/t") + mon.setCursorPos(1, 5) + mon.clearLine() + mon.write("SPENT: " .. string.format("%0.2f", spent_waste) .. " mB/t") + mon.setCursorPos(1, 6) + mon.clearLine() + mon.write("ANTI-M: " .. string.format("%0.2f", antimatter * 1000) .. " uB/t") + mon.setTextColor(colors.white) + end +end diff --git a/main/server.lua b/main/server.lua new file mode 100644 index 0000000..61ad386 --- /dev/null +++ b/main/server.lua @@ -0,0 +1,109 @@ +os.loadAPI("defs.lua") +os.loadAPI("log.lua") +os.loadAPI("regulator.lua") + +local modem +local reactors + +-- initalize the listener running on the wireless modem +-- _reactors: reactor table +function init(_reactors) + modem = peripheral.wrap("top") + reactors = _reactors + + -- open listening port + if not modem.isOpen(defs.LISTEN_PORT) then + modem.open(defs.LISTEN_PORT) + end + + -- send out a greeting to solicit responses for clients that are already running + broadcast(0, reactors) +end + +-- handle an incoming message from the modem +-- packet: table containing message fields +function handle_message(packet) + if type(packet.message) == "number" then + -- this is a greeting + log.write("reactor " .. packet.message .. " connected", colors.green) + + -- send current control command + for key, rctr in pairs(reactors) do + if rctr.id == packet.message then + send(rctr.id, rctr.control_state) + break + end + end + else + -- got reactor status + local eval_safety = false + + for key, value in pairs(reactors) do + if value.id == packet.message.id then + local tag = "RCT-" .. value.id .. ": " + + if value.state.run ~= packet.message.run then + value.state.run = packet.message.run + if value.state.run then + eval_safety = true + log.write(tag .. "running", colors.green) + end + end + + if value.state.no_fuel ~= packet.message.no_fuel then + value.state.no_fuel = packet.message.no_fuel + if value.state.no_fuel then + eval_safety = true + log.write(tag .. "insufficient fuel", colors.gray) + end + end + + if value.state.full_waste ~= packet.message.full_waste then + value.state.full_waste = packet.message.full_waste + if value.state.full_waste then + eval_safety = true + log.write(tag .. "waste tank full", colors.brown) + end + end + + if value.state.high_temp ~= packet.message.high_temp then + value.state.high_temp = packet.message.high_temp + if value.state.high_temp then + eval_safety = true + log.write(tag .. "high temperature", colors.orange) + end + end + + if value.state.damage_crit ~= packet.message.damage_crit then + value.state.damage_crit = packet.message.damage_crit + if value.state.damage_crit then + eval_safety = true + log.write(tag .. "critical damage", colors.red) + end + end + + break + end + end + + -- check to ensure safe operation + if eval_safety then + regulator.enforce_safeties() + end + end +end + +-- send a message to a given reactor +-- dest: reactor ID +-- message: true or false for enable control or another value for other functionality, like 0 for greeting +function send(dest, message) + modem.transmit(dest + defs.LISTEN_PORT, defs.LISTEN_PORT, message) +end + +-- broadcast a message to all reactors +-- message: true or false for enable control or another value for other functionality, like 0 for greeting +function broadcast(message) + for key, value in pairs(reactors) do + modem.transmit(value.id + defs.LISTEN_PORT, defs.LISTEN_PORT, message) + end +end diff --git a/signal-router.lua b/signal-router.lua new file mode 100644 index 0000000..37a7610 --- /dev/null +++ b/signal-router.lua @@ -0,0 +1,159 @@ +-- reactor signal router +-- transmits status information and controls enable state + +-- bundeled redstone key +-- top: +-- black (in): insufficent fuel +-- brown (in): excess waste +-- orange (in): overheat +-- red (in): damage critical +-- right: +-- cyan (out): plutonium/plutonium pellet pipe +-- green (out): polonium pipe +-- magenta (out): polonium pellet pipe +-- purple (out): antimatter pipe +-- white (out): reactor enable + +-- constants +REACTOR_ID = 1 +DEST_PORT = 1000 + +local state = { + id = REACTOR_ID, + run = false, + no_fuel = false, + full_waste = false, + high_temp = false, + damage_crit = false +} + +local waste_production = "antimatter" + +local listen_port = 1000 + REACTOR_ID +local modem = peripheral.wrap("left") + +print("Reactor Signal Router v1.0") +print("Configured for Reactor #" .. REACTOR_ID) + +if not modem.isOpen(listen_port) then + modem.open(listen_port) +end + +-- greeting +modem.transmit(DEST_PORT, listen_port, REACTOR_ID) + +-- queue event to read initial state and make sure reactor starts off +os.queueEvent("redstone") +rs.setBundledOutput("right", colors.white) +rs.setBundledOutput("right", 0) +re_eval_output = true + +local connection_timeout = os.startTimer(3) + +-- event loop +while true do + local event, param1, param2, param3, param4, param5 = os.pullEvent() + + if event == "redstone" then + -- redstone state change + input = rs.getBundledInput("top") + + if state.no_fuel ~= colors.test(input, colors.black) then + state.no_fuel = colors.test(input, colors.black) + if state.no_fuel then + print("insufficient fuel") + end + end + + if state.full_waste ~= colors.test(input, colors.brown) then + state.full_waste = colors.test(input, colors.brown) + if state.full_waste then + print("waste tank full") + end + end + + if state.high_temp ~= colors.test(input, colors.orange) then + state.high_temp = colors.test(input, colors.orange) + if state.high_temp then + print("high temperature") + end + end + + if state.damage_crit ~= colors.test(input, colors.red) then + state.damage_crit = colors.test(input, colors.red) + if state.damage_crit then + print("damage critical") + end + end + elseif event == "modem_message" then + -- got data, reset timer + if connection_timeout ~= nil then + os.cancelTimer(connection_timeout) + end + connection_timeout = os.startTimer(3) + + if type(param4) == "number" and param4 == 0 then + print("[info] controller server startup detected") + modem.transmit(DEST_PORT, listen_port, REACTOR_ID) + elseif type(param4) == "number" and param4 == 1 then + -- keep-alive, do nothing, just had to reset timer + elseif type(param4) == "boolean" then + state.run = param4 + + if state.run then + print("[alert] reactor enabled") + else + print("[alert] reactor disabled") + end + + re_eval_output = true + elseif type(param4) == "string" then + if param4 == "plutonium" then + print("[alert] switching to plutonium production") + waste_production = param4 + re_eval_output = true + elseif param4 == "polonium" then + print("[alert] switching to polonium production") + waste_production = param4 + re_eval_output = true + elseif param4 == "antimatter" then + print("[alert] switching to antimatter production") + waste_production = param4 + re_eval_output = true + end + else + print("[error] got unknown packet (" .. param4 .. ")") + end + elseif event == "timer" and param1 == connection_timeout then + -- haven't heard from server in 3 seconds? shutdown + -- timer won't be restarted until next packet, so no need to do anything with it + print("[alert] server timeout, reactor disabled") + state.run = false + re_eval_output = true + end + + -- check for control state changes + if re_eval_output then + re_eval_output = false + + local run_color = 0 + if state.run then + run_color = colors.white + end + + -- values are swapped, as on disables and off enables + local waste_color + if waste_production == "plutonium" then + waste_color = colors.green + elseif waste_production == "polonium" then + waste_color = colors.cyan + colors.purple + else + -- antimatter (default) + waste_color = colors.cyan + colors.magenta + end + + rs.setBundledOutput("right", run_color + waste_color) + end + + modem.transmit(DEST_PORT, listen_port, state) +end