diff --git a/reactor-plc/backplane.lua b/reactor-plc/backplane.lua new file mode 100644 index 0000000..7c63934 --- /dev/null +++ b/reactor-plc/backplane.lua @@ -0,0 +1,191 @@ +-- +-- Reactor PLC System Core Peripheral Backplane +-- + +local log = require("scada-common.log") +local network = require("scada-common.network") +local ppm = require("scada-common.ppm") +local util = require("scada-common.util") + +local databus = require("reactor-plc.databus") +local plc = require("reactor-plc.plc") + +local println = util.println + +---@class plc_backplane +local backplane = {} + +local _bp = { + smem = nil, ---@type plc_shared_memory + + wlan_pref = true, + lan_iface = "", + + act_nic = nil, ---@type nic + wl_act = true, + wd_nic = nil, ---@type nic|nil + wl_nic = nil ---@type nic|nil +} + +-- initialize the system peripheral backplane
+---@param config plc_config +---@param __shared_memory plc_shared_memory +--- EVENT_CONSUMER: this function consumes events +function backplane.init(config, __shared_memory) + _bp.smem = __shared_memory + _bp.wlan_pref = config.PreferWireless + _bp.lan_iface = config.WiredModem + + local plc_dev = __shared_memory.plc_dev + local plc_state = __shared_memory.plc_state + + -- Modem Init + + if _bp.smem.networked then + -- init wired NIC + if type(config.WiredModem) == "string" then + local modem = ppm.get_modem(_bp.lan_iface) + _bp.wd_nic = network.nic(modem) + + log.info("BKPLN: WIRED PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. _bp.lan_iface) + + -- set this as active for now + _bp.wl_act = false + _bp.act_nic = _bp.wd_nic + end + + -- init wireless NIC(s) + if config.WirelessModem then + local modem, iface = ppm.get_wireless_modem() + _bp.wl_nic = network.nic(modem) + + log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. iface) + + -- set this as active if connected or if both modems are disconnected and this is preferred + if (modem and _bp.wlan_pref) or not (_bp.act_nic and _bp.act_nic.is_connected()) then + _bp.wl_act = true + _bp.act_nic = _bp.wl_nic + end + end + + plc_state.no_modem = not _bp.act_nic.is_connected() + + databus.tx_hw_modem(not plc_state.no_modem) + + -- comms modem is required if networked + if plc_state.no_modem then + println("startup> comms modem not found") + log.warning("BKPLN: no comms modem on startup") + + plc_state.degraded = true + end + end + + -- Reactor Init + +---@diagnostic disable-next-line: assign-type-mismatch + plc_dev.reactor = ppm.get_fission_reactor() + plc_state.no_reactor = plc_dev.reactor == nil + + -- we need a reactor, can at least do some things even if it isn't formed though + if plc_state.no_reactor then + println("startup> fission reactor not found") + log.warning("BKPLN: no reactor on startup") + + plc_state.degraded = true + plc_state.reactor_formed = false + + -- mount a virtual peripheral to init the RPS with + local _, dev = ppm.mount_virtual() + plc_dev.reactor = dev + + log.info("BKPLN: mounted virtual device as reactor") + elseif not plc_dev.reactor.isFormed() then + println("startup> fission reactor is not formed") + log.warning("BKPLN: reactor logic adapter present, but reactor is not formed") + + plc_state.degraded = true + plc_state.reactor_formed = false + else + log.info("BKPLN: reactor detected") + end +end + +-- get the active NIC +---@return nic +function backplane.active_nic() return _bp.act_nic end + +-- handle a backplane peripheral attach +---@param iface string +---@param type string +---@param device table +---@param print_no_fp function +function backplane.attach(iface, type, device, print_no_fp) + local networked = _bp.smem.networked + local state = _bp.smem.plc_state + local dev = _bp.smem.plc_dev + local sys = _bp.smem.plc_sys + + if state.no_reactor and (type == "fissionReactorLogicAdapter") then + -- reconnected reactor + dev.reactor = device + state.no_reactor = false + + print_no_fp("reactor reconnected") + log.info("BKPLN: reactor reconnected") + + -- we need to assume formed here as we cannot check in this main loop + -- RPS will identify if it isn't and this will get set false later + state.reactor_formed = true + + -- determine if we are still in a degraded state + if (not networked or not state.no_modem) and state.reactor_formed then + state.degraded = false + end + + _bp.smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM) + + sys.rps.reconnect_reactor(dev.reactor) + if networked then + sys.plc_comms.reconnect_reactor(dev.reactor) + end + + -- partial reset of RPS, specific to becoming formed/reconnected + -- without this, auto control can't resume on chunk load + sys.rps.reset_formed() + elseif networked and type == "modem" then + ---@cast device Modem + local is_comms_modem = util.trinary(dev.modem_wired, dev.modem_iface == iface, device.isWireless()) + + -- note, check init_ok first since nic will be nil if it is false + if is_comms_modem and not (state.init_ok and nic.is_connected()) then + -- reconnected modem + dev.modem = device + state.no_modem = false + + if state.init_ok then nic.connect(device) end + + print_no_fp("comms modem reconnected") + log.info("comms modem reconnected") + + -- determine if we are still in a degraded state + if not state.no_reactor then + state.degraded = false + end + elseif device.isWireless() then + log.info("unused wireless modem connected") + else + log.info("non-comms wired modem connected") + end + end +end + +-- handle a backplane peripheral detach +---@param iface string +---@param type string +---@param device table +---@param print_no_fp function +function backplane.detach(iface, type, device, print_no_fp) +end + +return backplane diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index e19ebfb..92431fa 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -81,7 +81,9 @@ local tmp_cfg = { SVR_Channel = nil, ---@type integer PLC_Channel = nil, ---@type integer ConnTimeout = nil, ---@type number + WirelessModem = true, WiredModem = false, ---@type string|false + PreferWireless = true, TrustedRange = nil, ---@type number AuthKey = nil, ---@type string|nil LogMode = 0, ---@type LOG_MODE @@ -107,7 +109,9 @@ local fields = { { "SVR_Channel", "SVR Channel", 16240 }, { "PLC_Channel", "PLC Channel", 16241 }, { "ConnTimeout", "Connection Timeout", 5 }, - { "WiredModem", "Wired Modem", false }, + { "WirelessModem", "Wireless/Ender Comms Modem", true }, + { "WiredModem", "Wired Comms Modem", false }, + { "PreferWireless", "Prefer Wireless Modem", true }, { "TrustedRange", "Trusted Range", 0 }, { "AuthKey", "Facility Auth Key" , ""}, { "LogMode", "Log Mode", log.MODE.APPEND }, diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 4b879d8..84af579 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -48,7 +48,9 @@ function plc.load_config() config.SVR_Channel = settings.get("SVR_Channel") config.PLC_Channel = settings.get("PLC_Channel") config.ConnTimeout = settings.get("ConnTimeout") + config.WirelessModem = settings.get("WirelessModem") config.WiredModem = settings.get("WiredModem") + config.PreferWireless = settings.get("PreferWireless") config.TrustedRange = settings.get("TrustedRange") config.AuthKey = settings.get("AuthKey") @@ -71,12 +73,15 @@ function plc.validate_config(cfg) cfv.assert_type_int(cfg.UnitID) cfv.assert_type_bool(cfg.EmerCoolEnable) - if cfg.Networked == true then + if cfg.Networked then cfv.assert_channel(cfg.SVR_Channel) cfv.assert_channel(cfg.PLC_Channel) cfv.assert_type_num(cfg.ConnTimeout) cfv.assert_min(cfg.ConnTimeout, 2) + cfv.assert_type_bool(cfg.WirelessModem) cfv.assert((cfg.WiredModem == false) or (type(cfg.WiredModem) == "string")) + cfv.assert(cfg.WirelessModem or (type(cfg.WiredModem) == "string")) + cfv.assert_type_bool(cfg.PreferWireless) cfv.assert_type_num(cfg.TrustedRange) cfv.assert_min(cfg.TrustedRange, 0) cfv.assert_type_str(cfg.AuthKey) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index bb08252..d76199d 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -3,6 +3,7 @@ -- require("/initenv").init_env() +local backplane = require("reactor-plc.backplane") local comms = require("scada-common.comms") local crash = require("scada-common.crash") @@ -102,14 +103,10 @@ local function main() burn_rate = 0.0 }, - -- core PLC devices + -- global PLC devices, still initialized by the backplane ---@class plc_dev plc_dev = { ----@diagnostic disable-next-line: assign-type-mismatch - reactor = ppm.get_fission_reactor(), ---@type table - modem = nil, ---@type Modem|nil - modem_wired = type(config.WiredModem) == "string", - modem_iface = config.WiredModem + reactor = nil ---@type table }, -- system objects @@ -134,48 +131,8 @@ local function main() local plc_state = __shared_memory.plc_state - -- get the configured modem - if smem_dev.modem_wired then - smem_dev.modem = ppm.get_modem(smem_dev.modem_iface) - else smem_dev.modem = ppm.get_wireless_modem() end - - -- initial state evaluation - plc_state.no_reactor = smem_dev.reactor == nil - plc_state.no_modem = smem_dev.modem == nil - - -- we need a reactor, can at least do some things even if it isn't formed though - if plc_state.no_reactor then - println("startup> fission reactor not found") - log.warning("startup> no reactor on startup") - - plc_state.degraded = true - plc_state.reactor_formed = false - - -- mount a virtual peripheral to init the RPS with - local _, dev = ppm.mount_virtual() - smem_dev.reactor = dev - - log.info("startup> mounted virtual device as reactor") - elseif not smem_dev.reactor.isFormed() then - println("startup> fission reactor is not formed") - log.warning("startup> reactor logic adapter present, but reactor is not formed") - - plc_state.degraded = true - plc_state.reactor_formed = false - end - - -- comms modem is required if networked - if __shared_memory.networked and plc_state.no_modem then - println("startup> comms modem not found") - log.warning("startup> no comms modem on startup") - - -- scram reactor if present and enabled - if (smem_dev.reactor ~= nil) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then - smem_dev.reactor.scram() - end - - plc_state.degraded = true - end + -- reactor and modem initialization + backplane.init(config, __shared_memory) -- scram on boot if networked, otherwise leave the reactor be if __shared_memory.networked and (not plc_state.no_reactor) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then @@ -219,7 +176,7 @@ local function main() log.debug("startup> conn watchdog started") -- create network interface then setup comms - smem_sys.nic = network.nic(smem_dev.modem) + smem_sys.nic = backplane.active_nic() smem_sys.plc_comms = plc.comms(R_PLC_VERSION, smem_sys.nic, smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog) log.debug("startup> comms init") else