From bfa24b366558fa7e374c91b2f6040388e3ab12fa Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 1 Oct 2023 17:10:16 -0400 Subject: [PATCH] #307 PLC integration with new config storage --- reactor-plc/panel/front_panel.lua | 4 +- reactor-plc/plc.lua | 82 +++++++++++++++++++------- reactor-plc/startup.lua | 97 +++++++++++++------------------ 3 files changed, 105 insertions(+), 78 deletions(-) diff --git a/reactor-plc/panel/front_panel.lua b/reactor-plc/panel/front_panel.lua index a59a016..cf04194 100644 --- a/reactor-plc/panel/front_panel.lua +++ b/reactor-plc/panel/front_panel.lua @@ -5,8 +5,8 @@ local types = require("scada-common.types") local util = require("scada-common.util") -local config = require("reactor-plc.config") local databus = require("reactor-plc.databus") +local plc = require("reactor-plc.plc") local style = require("reactor-plc.panel.style") @@ -86,7 +86,7 @@ local function init(panel) local active = LED{parent=status,x=2,width=12,label="RCT ACTIVE",colors=ind_grn} -- only show emergency coolant LED if emergency coolant is configured for this device - if type(config.EMERGENCY_COOL) == "table" then + if plc.config.EmerCoolEnable then local emer_cool = LED{parent=status,x=2,width=14,label="EMER COOLANT",colors=cpair(colors.yellow,colors.yellow_off)} emer_cool.register(databus.ps, "emer_cool", emer_cool.update) end diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index bbb59ff..9140841 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -26,14 +26,58 @@ local RPS_LIMITS = const.RPS_LIMITS local PCALL_SCRAM_MSG = "pcall: Scram requires the reactor to be active." local PCALL_START_MSG = "pcall: Reactor is already active." +---@type plc_config +local config = {} + +plc.config = config + +-- load the PLC configuration +function plc.load_config() + config.Networked = settings.get("Networked") + config.UnitID = settings.get("UnitID") + config.EmerCoolEnable = settings.get("EmerCoolEnable") + config.EmerCoolSide = settings.get("EmerCoolSide") + config.EmerCoolColor = settings.get("EmerCoolColor") + config.SVR_Channel = settings.get("SVR_Channel") + config.PLC_Channel = settings.get("PLC_Channel") + config.ConnTimeout = settings.get("ConnTimeout") + config.TrustedRange = settings.get("TrustedRange") + config.AuthKey = settings.get("AuthKey") + config.LogMode = settings.get("LogMode") + config.LogPath = settings.get("LogPath") + config.LogDebug = settings.get("LogDebug") + + local cfv = util.new_validator() + + cfv.assert_type_bool(config.Networked) + cfv.assert_type_int(config.UnitID) + cfv.assert_type_bool(config.EmerCoolEnable) + cfv.assert_channel(config.SVR_Channel) + cfv.assert_channel(config.PLC_Channel) + cfv.assert_type_int(config.ConnTimeout) + cfv.assert_min(config.ConnTimeout, 2) + cfv.assert_type_num(config.TrustedRange) + cfv.assert_type_str(config.AuthKey) + cfv.assert_type_int(config.LogMode) + cfv.assert_type_str(config.LogPath) + cfv.assert_type_bool(config.LogDebug) + + -- check emergency coolant configuration if enabled + if config.EmerCoolEnable then + cfv.assert_eq(rsio.is_valid_side(config.EmerCoolSide), true) + cfv.assert_eq(config.EmerCoolColor == nil or rsio.is_color(config.EmerCoolColor), true) + end + + return cfv.valid() +end + -- RPS: Reactor Protection System
-- identifies dangerous states and SCRAMs reactor if warranted
-- autonomous from main SCADA supervisor/coordinator control ---@nodiscard ---@param reactor table ---@param is_formed boolean ----@param emer_cool nil|table emergency coolant configuration -function plc.rps_init(reactor, is_formed, emer_cool) +function plc.rps_init(reactor, is_formed) local state_keys = { high_dmg = 1, high_temp = 2, @@ -73,22 +117,22 @@ function plc.rps_init(reactor, is_formed, emer_cool) ---@param state boolean true to enable emergency coolant, false to disable local function _set_emer_cool(state) -- check if this was configured: if it's a table, fields have already been validated. - if type(emer_cool) == "table" then + if config.EmerCoolEnable then local level = rsio.digital_write_active(rsio.IO.U_EMER_COOL, state) if level ~= false then - if rsio.is_color(emer_cool.color) then - local output = rs.getBundledOutput(emer_cool.side) + if rsio.is_color(config.EmerCoolColor) then + local output = rs.getBundledOutput(config.EmerCoolSide) if rsio.digital_write(level) then - output = colors.combine(output, emer_cool.color) + output = colors.combine(output, config.EmerCoolColor) else - output = colors.subtract(output, emer_cool.color) + output = colors.subtract(output, config.EmerCoolColor) end - rs.setBundledOutput(emer_cool.side, output) + rs.setBundledOutput(config.EmerCoolSide, output) else - rs.setOutput(emer_cool.side, rsio.digital_write(level)) + rs.setOutput(config.EmerCoolSide, rsio.digital_write(level)) end if state ~= self.emer_cool_active then @@ -443,16 +487,12 @@ end -- Reactor PLC Communications ---@nodiscard ----@param id integer reactor ID ---@param version string PLC version ---@param nic nic network interface device ----@param plc_channel integer PLC comms channel ----@param svr_channel integer supervisor server channel ----@param range integer trusted device connection range ---@param reactor table reactor device ---@param rps rps RPS reference ---@param conn_watchdog watchdog watchdog reference -function plc.comms(id, version, nic, plc_channel, svr_channel, range, reactor, rps, conn_watchdog) +function plc.comms(version, nic, reactor, rps, conn_watchdog) local self = { sv_addr = comms.BROADCAST, seq_num = 0, @@ -466,13 +506,13 @@ function plc.comms(id, version, nic, plc_channel, svr_channel, range, reactor, r max_burn_rate = nil } - comms.set_trusted_range(range) + comms.set_trusted_range(config.TrustedRange) -- PRIVATE FUNCTIONS -- -- configure network channels nic.closeAll() - nic.open(plc_channel) + nic.open(config.PLC_Channel) -- send an RPLC packet ---@param msg_type RPLC_TYPE @@ -481,10 +521,10 @@ function plc.comms(id, version, nic, plc_channel, svr_channel, range, reactor, r local s_pkt = comms.scada_packet() local r_pkt = comms.rplc_packet() - r_pkt.make(id, msg_type, msg) + r_pkt.make(config.UnitID, msg_type, msg) s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.RPLC, r_pkt.raw_sendable()) - nic.transmit(svr_channel, plc_channel, s_pkt) + nic.transmit(config.SVR_Channel, config.PLC_Channel, s_pkt) self.seq_num = self.seq_num + 1 end @@ -498,7 +538,7 @@ function plc.comms(id, version, nic, plc_channel, svr_channel, range, reactor, r m_pkt.make(msg_type, msg) s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) - nic.transmit(svr_channel, plc_channel, s_pkt) + nic.transmit(config.SVR_Channel, config.PLC_Channel, s_pkt) self.seq_num = self.seq_num + 1 end @@ -673,7 +713,7 @@ function plc.comms(id, version, nic, plc_channel, svr_channel, range, reactor, r -- attempt to establish link with supervisor function public.send_link_req() - _send_mgmt(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PLC, id }) + _send_mgmt(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PLC, config.UnitID }) end -- send live status information @@ -769,7 +809,7 @@ function plc.comms(id, version, nic, plc_channel, svr_channel, range, reactor, r local src_addr = packet.scada_frame.src_addr() -- handle packets now that we have prints setup - if l_chan == plc_channel then + if l_chan == config.PLC_Channel then -- check sequence number if self.r_seq_num == nil then self.r_seq_num = packet.scada_frame.seq_num() diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 97ac227..300fd16 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -4,58 +4,46 @@ require("/initenv").init_env() -local comms = require("scada-common.comms") -local crash = require("scada-common.crash") -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local network = require("scada-common.network") -local ppm = require("scada-common.ppm") -local rsio = require("scada-common.rsio") -local util = require("scada-common.util") +local comms = require("scada-common.comms") +local crash = require("scada-common.crash") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local network = require("scada-common.network") +local ppm = require("scada-common.ppm") +local util = require("scada-common.util") -local config = require("reactor-plc.config") -local databus = require("reactor-plc.databus") -local plc = require("reactor-plc.plc") -local renderer = require("reactor-plc.renderer") -local threads = require("reactor-plc.threads") +local configure = require("reactor-plc.configure") +local databus = require("reactor-plc.databus") +local plc = require("reactor-plc.plc") +local renderer = require("reactor-plc.renderer") +local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v1.5.10" +local R_PLC_VERSION = "v1.6.0" local println = util.println local println_ts = util.println_ts ---------------------------------------- --- config validation +-- get configuration ---------------------------------------- -local cfv = util.new_validator() - -cfv.assert_type_bool(config.NETWORKED) -cfv.assert_type_int(config.REACTOR_ID) -cfv.assert_channel(config.SVR_CHANNEL) -cfv.assert_channel(config.PLC_CHANNEL) -cfv.assert_type_int(config.TRUSTED_RANGE) -cfv.assert_type_num(config.COMMS_TIMEOUT) -cfv.assert_min(config.COMMS_TIMEOUT, 2) -cfv.assert_type_str(config.LOG_PATH) -cfv.assert_type_int(config.LOG_MODE) - -assert(cfv.valid(), "bad config file: missing/invalid fields") - --- check emergency coolant configuration -if type(config.EMERGENCY_COOL) == "table" then - if not rsio.is_valid_side(config.EMERGENCY_COOL.side) then - assert(false, "bad config file: emergency coolant side unrecognized") - elseif config.EMERGENCY_COOL.color ~= nil and not rsio.is_color(config.EMERGENCY_COOL.color) then - assert(false, "bad config file: emergency coolant invalid redstone channel color provided") +if not plc.load_config() then + -- try to reconfigure (user action) + local success, error = configure.configure(true) + if success then + assert(plc.load_config(), "failed to load valid reactor PLC configuration") + else + assert(success, "reactor PLC configuration error: " .. error) end end +local config = plc.config + ---------------------------------------- -- log init ---------------------------------------- -log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG == true) +log.init(config.LogPath, config.LogMode, config.LogDebug) log.info("========================================") log.info("BOOTING reactor-plc.startup " .. R_PLC_VERSION) @@ -75,32 +63,32 @@ local function main() -- record firmware versions and ID databus.tx_versions(R_PLC_VERSION, comms.version) - databus.tx_id(config.REACTOR_ID) + databus.tx_id(config.UnitID) -- mount connected devices ppm.mount_all() -- message authentication init - if type(config.AUTH_KEY) == "string" then - network.init_mac(config.AUTH_KEY) + if string.len(config.AuthKey) > 0 then + network.init_mac(config.AuthKey) end -- shared memory across threads ---@class plc_shared_memory local __shared_memory = { -- networked setting - networked = config.NETWORKED, ---@type boolean + networked = config.Networked, -- PLC system state flags ---@class plc_state plc_state = { - init_ok = true, - fp_ok = false, - shutdown = false, - degraded = true, + init_ok = true, + fp_ok = false, + shutdown = false, + degraded = true, reactor_formed = true, - no_reactor = true, - no_modem = true + no_reactor = true, + no_modem = true }, -- control setpoints @@ -118,10 +106,10 @@ local function main() -- system objects plc_sys = { - rps = nil, ---@type rps - nic = nil, ---@type nic - plc_comms = nil, ---@type plc_comms - conn_watchdog = nil ---@type watchdog + rps = nil, ---@type rps + nic = nil, ---@type nic + plc_comms = nil, ---@type plc_comms + conn_watchdog = nil---@type watchdog }, -- message queues @@ -196,18 +184,17 @@ local function main() if plc_state.init_ok then -- init reactor protection system - smem_sys.rps = plc.rps_init(smem_dev.reactor, plc_state.reactor_formed, config.EMERGENCY_COOL) + smem_sys.rps = plc.rps_init(smem_dev.reactor, plc_state.reactor_formed) log.debug("init> rps init") if __shared_memory.networked then -- comms watchdog - smem_sys.conn_watchdog = util.new_watchdog(config.COMMS_TIMEOUT) + smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout) log.debug("init> conn watchdog started") -- create network interface then setup comms smem_sys.nic = network.nic(smem_dev.modem) - smem_sys.plc_comms = plc.comms(config.REACTOR_ID, R_PLC_VERSION, smem_sys.nic, config.PLC_CHANNEL, config.SVR_CHANNEL, - config.TRUSTED_RANGE, smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog) + smem_sys.plc_comms = plc.comms(R_PLC_VERSION, smem_sys.nic, smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog) log.debug("init> comms init") else _println_no_fp("init> starting in offline mode") @@ -215,7 +202,7 @@ local function main() end -- notify user of emergency coolant configuration status - if config.EMERGENCY_COOL ~= nil then + if config.EmerCoolEnable then println("init> emergency coolant control ready") log.info("init> running with emergency coolant control available") end