From 26cce3a46adabc5f858e83fb268852bfcd2e27cd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 1 Jan 2022 19:45:33 -0500 Subject: [PATCH] reactor control and safety system attempting server connection --- common/comms.lua | 157 ++++++++++++++++++++++++++++++ common/util.lua | 29 ++++++ rcass/config.lua | 6 ++ rcass/rcass.lua | 122 +++++++++++++++++++++++ rcass/safety.lua | 80 +++++++++++++++ {rcss => rcass}/signal-router.lua | 0 rcass/startup.lua | 13 +++ 7 files changed, 407 insertions(+) create mode 100644 common/comms.lua create mode 100644 common/util.lua create mode 100644 rcass/config.lua create mode 100644 rcass/rcass.lua create mode 100644 rcass/safety.lua rename {rcss => rcass}/signal-router.lua (100%) create mode 100644 rcass/startup.lua diff --git a/common/comms.lua b/common/comms.lua new file mode 100644 index 0000000..4e47eb3 --- /dev/null +++ b/common/comms.lua @@ -0,0 +1,157 @@ + +function server_comms() + local self = { + reactor_struct_cache = nil + } + + local record_struct = function (id, mek_data) + end + + -- send the structure data by request to pocket computers + local send_struct = function () + end + + local command_waste = function () + end +end + +function rcass_comms(id, modem, local_port, server_port, reactor) + local self = { + _id = id, + _modem = modem, + _server = server_port, + _local = local_port, + _reactor = reactor, + _status_cache = nil, + + _send = function (msg) + self._modem.transmit(self._server, self._local, msg) + end + } + + local _send = function (msg) + self._modem.transmit(self._server, self._local, msg) + end + + -- variable reactor status information, excluding heating rate + local _reactor_status = function () + return { + status = self._reactor.getStatus(), + burn_rate = self._reactor.getBurnRate(), + act_burn_r = self._reactor.getActualBurnRate(), + temp = self._reactor.getTemperature(), + damage = self._reactor.getDamagePercent(), + boil_eff = self._reactor.getBoilEfficiency(), + env_loss = self._reactor.getEnvironmentalLoss(), + + fuel = self._reactor.getFuel(), + fuel_need = self._reactor.getFuelNeeded(), + fuel_fill = self._reactor.getFuelFilledPercentage(), + waste = self._reactor.getWaste(), + waste_need = self._reactor.getWasteNeeded(), + waste_fill = self._reactor.getWasteFilledPercentage(), + cool_type = self._reactor.getCoolant()['name'], + cool_amnt = self._reactor.getCoolant()['amount'], + cool_need = self._reactor.getCoolantNeeded(), + cool_fill = self._reactor.getCoolantFilledPercentage(), + hcool_type = self._reactor.getHeatedCoolant()['name'], + hcool_amnt = self._reactor.getHeatedCoolant()['amount'], + hcool_need = self._reactor.getHeatedCoolantNeeded(), + hcool_fill = self._reactor.getHeatedCoolantFilledPercentage() + } + end + + local _status_changed = function () + local status = self._reactor_status() + local changed = false + + for key, value in pairs() do + if value ~= _status_cache[key] then + changed = true + break + end + end + + return changed + end + + -- attempt to establish link with + local send_link_req = function () + local linking_data = { + id = self._id, + type = "link_req" + } + + _send(linking_data) + end + + -- send structure properties (these should not change) + -- server will cache these + local send_struct = function () + local mek_data = { + heat_cap = self._reactor.getHeatCapacity(), + fuel_asm = self._reactor.getFuelAssemblies(), + fuel_sa = self._reactor.getFuelSurfaceArea(), + fuel_cap = self._reactor.getFuelCapacity(), + waste_cap = self._reactor.getWasteCapacity(), + cool_cap = self._reactor.getCoolantCapacity(), + hcool_cap = self._reactor.getHeatedCoolantCapacity(), + max_burn = self._reactor.getMaxBurnRate() + } + + local struct_packet = { + id = self._id, + type = "struct_data", + mek_data = mek_data + } + + _send(struct_packet) + end + + -- send live status information + local send_status = function () + local mek_data = self._reactor_status() + + local sys_data = { + timestamp = os.time(), + control_state = false, + overridden = false, + faults = {}, + waste_production = "antimatter" -- "plutonium", "polonium", "antimatter" + } + end + + local send_keep_alive = function () + -- heating rate is volatile, so it is skipped in status + -- send it with keep alive packets + local mek_data = { + heating_rate = self._reactor.getHeatingRate() + } + + -- basic keep alive packet to server + local keep_alive_packet = { + id = self._id, + type = "keep_alive", + timestamp = os.time(), + mek_data = mek_data + } + + _send(keep_alive_packet) + end + + local handle_link = function (packet) + if packet.type == "link_response" then + return packet.accepted + else + return "wrong_type" + end + end + + return { + send_link_req = send_link_req, + send_struct = send_struct, + send_status = send_status, + send_keep_alive = send_keep_alive, + handle_link = handle_link + } +end \ No newline at end of file diff --git a/common/util.lua b/common/util.lua new file mode 100644 index 0000000..f6fd611 --- /dev/null +++ b/common/util.lua @@ -0,0 +1,29 @@ +-- timestamped print +function print_ts(message) + term.write(os.date("[%H:%M:%S] ") .. message) +end + +-- ComputerCraft OS Timer based Watchdog +-- triggers a timer event if not fed within 'timeout' seconds +function new_watchdog(timeout) + local self = { + _timeout = timeout, + _wd_timer = os.startTimer(_timeout) + } + + local get_timer = function () + return self._wd_timer + end + + local feed = function () + if self._wd_timer ~= nil then + os.cancelTimer(self._wd_timer) + end + self._wd_timer = os.startTimer(self._timeout) + end + + return { + get_timer = get_timer, + feed = feed + } +end diff --git a/rcass/config.lua b/rcass/config.lua new file mode 100644 index 0000000..f79895c --- /dev/null +++ b/rcass/config.lua @@ -0,0 +1,6 @@ +-- unique reactor ID +REACTOR_ID = 1 +-- port to send packets TO server +SERVER_PORT = 1000 +-- port to listen to incoming packets FROM server +LISTEN_PORT = 1001 diff --git a/rcass/rcass.lua b/rcass/rcass.lua new file mode 100644 index 0000000..8738896 --- /dev/null +++ b/rcass/rcass.lua @@ -0,0 +1,122 @@ +-- +-- RCaSS: Reactor Controller and Safety Subsystem +-- + +os.loadAPI("common/util.lua") +os.loadAPI("common/comms.lua") +os.loadAPI("rcass/config.lua") +os.loadAPI("rcass/safety.lua") + +local RCASS_VERSION = "alpha-v0.1" + +local print_ts = util.print_ts + +local reactor = peripheral.find("fissionReactor") +local modem = peripheral.find("modem") + +print(">> RCaSS " .. RCASS_VERSION .. " <<") + +-- we need a reactor and a modem +if reactor == nil then + print("Fission reactor not found, exiting..."); + return +elseif modem == nil then + print("No modem found, disabling reactor and exiting...") + reactor.scram() + return +end + +-- just booting up, no fission allowed (neutrons stay put thanks) +reactor.scram() + +-- init internal safety system +local iss = safety.iss_init(reactor) +local iss_status = "ok" +local iss_tripped = false + +-- read config + +-- start comms +if not modem.isOpen(config.LISTEN_PORT) then + modem.open(config.LISTEN_PORT) +end + +local comms = comms.rcass_comms(config.REACTOR_ID, modem, config.LISTEN_PORT, config.SERVER_PORT, reactor) + +-- attempt server connection +local linked = false +local link_timeout = os.startTimer(5) +comms.send_link_req() +print_ts("sent link request") +repeat + local event, param1, param2, param3, param4, param5 = os.pullEvent() + + -- handle event + if event == "timer" and param1 == link_timeout then + -- no response yet + print("...no response"); + comms.send_link_req() + print_ts("sent link request") + link_timeout = os.startTimer(5) + elseif event == "modem_message" then + -- server response? cancel timeout + if link_timeout ~= nil then + os.cancelTimer(link_timeout) + end + + local packet = { + side = param1, + sender = param2, + reply_to = param3, + message = param4, + distance = param5 + } + + -- handle response + response = comms.handle_link(packet) + if response == "wrong_type" then + print_ts("invalid link response, bad channel?\n") + return + elseif response == true then + print_ts("...linked!\n") + linked = true + else + print_ts("...denied, exiting\n") + return + end + end +until linked + +-- comms watchdog, 3 second timeout +local conn_watchdog = watchdog.new_watchdog(3) + +-- loop clock (10Hz, 2 ticks) +-- send status updates at 4Hz (every 5 ticks) +local loop_tick = os.startTimer(0.05) +local ticks_to_update = 5 + +-- event loop +while true do + local event, param1, param2, param3, param4, param5 = os.pullEvent() + + -- check safety (SCRAM occurs if tripped) + iss_status, iss_tripped = iss.check() + + -- handle event + if event == "timer" and param1 == loop_tick then + -- basic event tick, send updated data if it is time + ticks_to_update = ticks_to_update - 1 + if ticks_to_update == 0 then + ticks_to_update = 5 + end + elseif event == "modem_message" then + -- got a packet + -- feed the watchdog first so it doesn't eat our packets + conn_watchdog.feed() + + elseif event == "timer" and param1 == conn_watchdog.get_timer() then + -- haven't heard from server recently? shutdown + reactor.scram() + print_ts("[alert] server timeout, reactor disabled\n") + end +end diff --git a/rcass/safety.lua b/rcass/safety.lua new file mode 100644 index 0000000..29733ab --- /dev/null +++ b/rcass/safety.lua @@ -0,0 +1,80 @@ +-- Internal Safety System +-- identifies dangerous states and SCRAMs reactor if warranted +-- autonomous from main control +function iss_init(reactor) + local self = { + _reactor = reactor, + _tripped = false, + _trip_cause = "" + } + + local check = function () + local status = "ok" + + -- check system states in order of severity + if self.damage_critical() then + status = "dmg_crit" + elseif self.high_temp() then + status = "high_temp" + elseif self.excess_heated_coolant() then + status = "heated_coolant_backup" + elseif self.excess_waste() then + status = "full_waste" + elseif self.insufficient_fuel() then + status = "no_fuel" + elseif self._tripped then + status = self._trip_cause + else + self._tripped = false + end + + if status ~= "ok" then + self._tripped = true + self._trip_cause = status + self._reactor.scram() + end + + return self._tripped, status + end + + local reset = function () + self._tripped = false + self._trip_cause = "" + end + + local damage_critical = function () + return self._reactor.getDamagePercent() >= 100 + end + + local excess_heated_coolant = function () + return self._reactor.getHeatedCoolantNeeded() == 0 + end + + local excess_waste = function () + return self._reactor.getWasteNeeded() == 0 + end + + local high_temp = function () + -- mekanism: MAX_DAMAGE_TEMPERATURE = 1_200 + return self._reactor.getTemperature() >= 1200 + end + + local insufficient_fuel = function () + return self._reactor.getFuel() == 0 + end + + local no_coolant = function() + return self._reactor.getCoolantFilledPercentage() < 2 + end + + return { + check = check, + reset = reset, + damage_critical = damage_critical, + excess_heated_coolant = excess_heated_coolant, + excess_waste = excess_waste, + high_temp = high_temp, + insufficient_fuel = insufficient_fuel, + no_coolant = no_coolant + } +end diff --git a/rcss/signal-router.lua b/rcass/signal-router.lua similarity index 100% rename from rcss/signal-router.lua rename to rcass/signal-router.lua diff --git a/rcass/startup.lua b/rcass/startup.lua new file mode 100644 index 0000000..a0b746a --- /dev/null +++ b/rcass/startup.lua @@ -0,0 +1,13 @@ +print(">>RCASS LOADER START<<") +print(">>CHECKING SETTINGS...") +loaded = settings.load("rcass.settings") +if loaded then + print(">>SETTINGS FOUND, VERIFIYING INTEGRITY...") + settings.getNames() +else + print(">>SETTINGS NOT FOUND") + print(">>LAUNCHING CONFIGURATOR...") + shell.run("config") +end +print(">>LAUNCHING RCASS...") +shell.run("rcass")