From cddd9f74372f93c1a1966c23a456a077f4415ac3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Oct 2025 22:19:37 -0400 Subject: [PATCH] #634 work on reactor PLC backplane --- reactor-plc/backplane.lua | 143 +++++++++++++++++++++++++------------- reactor-plc/plc.lua | 16 +++++ reactor-plc/renderer.lua | 11 ++- reactor-plc/startup.lua | 19 ++++- reactor-plc/threads.lua | 98 ++++++-------------------- 5 files changed, 153 insertions(+), 134 deletions(-) diff --git a/reactor-plc/backplane.lua b/reactor-plc/backplane.lua index 7c63934..f5e2230 100644 --- a/reactor-plc/backplane.lua +++ b/reactor-plc/backplane.lua @@ -49,6 +49,8 @@ function backplane.init(config, __shared_memory) log.info("BKPLN: WIRED PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. _bp.lan_iface) + plc_state.wd_modem = _bp.wd_nic.is_connected() + -- set this as active for now _bp.wl_act = false _bp.act_nic = _bp.wd_nic @@ -61,6 +63,8 @@ function backplane.init(config, __shared_memory) log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. iface) + plc_state.wl_modem = _bp.wl_nic.is_connected() + -- 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 @@ -68,12 +72,8 @@ function backplane.init(config, __shared_memory) 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 + if not (plc_state.wd_modem or plc_state.wl_modem) then println("startup> comms modem not found") log.warning("BKPLN: no comms modem on startup") @@ -121,61 +121,106 @@ function backplane.active_nic() return _bp.act_nic end ---@param device table ---@param print_no_fp function function backplane.attach(iface, type, device, print_no_fp) + local MQ__RPS_CMD = _bp.smem.q_cmds.MQ__RPS_CMD + 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 + if type ~= nil and device ~= nil then + 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") + 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") + -- 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 state.no_reactor then + if ((not networked) or (state.wd_modem or state.wl_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_reattach() + elseif networked and type == "modem" then + ---@cast device Modem + + local m_is_wl = device.isWireless() + + log.info(util.c("BKPLN: ", util.trinary(m_is_wl, "WIRELESS", "WIRED"), " PHY_ATTACH ", iface)) + + local is_wd = _bp.wd_nic and (_bp.lan_iface == iface) + local is_wl = _bp.wl_nic and (not _bp.wl_nic.is_connected()) and m_is_wl + + if is_wd then + -- connect this as the wired NIC + _bp.wd_nic.connect(device) + + log.info("BKPLN: WIRED PHY_UP " .. iface) + print_no_fp("wired comms modem reconnected") + + state.wd_modem = true + + if _bp.act_nic == _bp.wd_nic then + -- set as active + _bp.wl_act = false + _bp.act_nic = _bp.wd_nic + elseif _bp.wl_act and not _bp.wlan_pref then + -- switch back to preferred wired + _bp.wl_act = false + _bp.act_nic = _bp.wd_nic + + sys.plc_comms.switch_nic(_bp.act_nic) + log.info("BKPLN: switched comms to wired modem (preferred)") + end + elseif is_wl then + -- connect this as the wireless NIC + _bp.wl_nic.connect(device) + + log.info("BKPLN: WIRELESS PHY_UP " .. iface) + print_no_fp("wireless comms modem reconnected") + + state.wl_modem = true + + if _bp.act_nic == _bp.wl_nic then + -- set as active + _bp.wl_act = true + _bp.act_nic = _bp.wl_nic + elseif (not _bp.wl_act) and _bp.wlan_pref then + -- switch back to preferred wireless + _bp.wl_act = true + _bp.act_nic = _bp.wl_nic + + sys.plc_comms.switch_nic(_bp.act_nic) + log.info("BKPLN: switched comms to wireless modem (preferred)") + end + elseif _bp.wl_nic and m_is_wl then + -- the wireless NIC already has a modem + print_no_fp("standby wireless modem connected") + log.info("BKPLN: standby wireless modem connected") + else + print_no_fp("unassigned modem connected") + log.warning("BKPLN: unassigned modem connected") + end + + -- determine if we are still in a degraded state + if (state.wd_modem or state.wl_modem) and state.reactor_formed and 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 diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 84af579..a88a119 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -830,6 +830,22 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) ---@class plc_comms local public = {} + -- switch the current active NIC + ---@param _nic nic + function public.switch_nic(_nic) + nic.closeAll() + + if _nic.isWireless() then + comms.set_trusted_range(config.TrustedRange) + end + + -- configure receive channels + _nic.closeAll() + _nic.open(config.PLC_Channel) + + nic = _nic + end + -- reconnect a newly connected reactor ---@param new_reactor table function public.reconnect_reactor(new_reactor) diff --git a/reactor-plc/renderer.lua b/reactor-plc/renderer.lua index f8a8044..5ba8f89 100644 --- a/reactor-plc/renderer.lua +++ b/reactor-plc/renderer.lua @@ -18,15 +18,14 @@ local ui = { } -- try to start the UI ----@param theme FP_THEME front panel theme ----@param color_mode COLOR_MODE color mode +---@param config plc_config configuration ---@return boolean success, any error_msg -function renderer.try_start_ui(theme, color_mode) +function renderer.try_start_ui(config) local status, msg = true, nil if ui.display == nil then -- set theme - style.set_theme(theme, color_mode) + style.set_theme(config.FrontPanelTheme, config.ColorMode) -- reset terminal term.setTextColor(colors.white) @@ -40,7 +39,7 @@ function renderer.try_start_ui(theme, color_mode) end -- apply color mode - local c_mode_overrides = style.theme.color_modes[color_mode] + local c_mode_overrides = style.theme.color_modes[config.ColorMode] for i = 1, #c_mode_overrides do term.setPaletteColor(c_mode_overrides[i].c, c_mode_overrides[i].hex) end @@ -48,7 +47,7 @@ function renderer.try_start_ui(theme, color_mode) -- init front panel view status, msg = pcall(function () ui.display = DisplayBox{window=term.current(),fg_bg=style.fp.root} - panel_view(ui.display) + panel_view(ui.display, config) end) if status then diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index a051adb..1fc5137 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -91,9 +91,10 @@ local function main() fp_ok = false, shutdown = false, degraded = true, - reactor_formed = true, no_reactor = true, - no_modem = true + reactor_formed = true, + wd_modem = false, + wl_modem = false }, -- control setpoints @@ -123,6 +124,18 @@ local function main() mq_rps = mqueue.new(), mq_comms_tx = mqueue.new(), mq_comms_rx = mqueue.new() + }, + + -- message queue commands + q_cmds = { + MQ__RPS_CMD = { + SCRAM = 1, + DEGRADED_SCRAM = 2, + TRIP_TIMEOUT = 3 + }, + MQ__COMM_CMD = { + SEND_STATUS = 1 + } } } @@ -142,7 +155,7 @@ local function main() -- setup front panel local message - plc_state.fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode) + plc_state.fp_ok, message = renderer.try_start_ui(config) -- ...or not if not plc_state.fp_ok then diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index da46bd0..281ad16 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -1,13 +1,14 @@ -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local ppm = require("scada-common.ppm") -local tcd = require("scada-common.tcd") -local util = require("scada-common.util") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local ppm = require("scada-common.ppm") +local tcd = require("scada-common.tcd") +local util = require("scada-common.util") -local databus = require("reactor-plc.databus") -local renderer = require("reactor-plc.renderer") +local backplane = require("reactor-plc.backplane") +local databus = require("reactor-plc.databus") +local renderer = require("reactor-plc.renderer") -local core = require("graphics.core") +local core = require("graphics.core") local threads = {} @@ -18,16 +19,6 @@ local SP_CTRL_SLEEP = 250 -- 250ms, 5 ticks local BURN_RATE_RAMP_mB_s = 5.0 -local MQ__RPS_CMD = { - SCRAM = 1, - DEGRADED_SCRAM = 2, - TRIP_TIMEOUT = 3 -} - -local MQ__COMM_CMD = { - SEND_STATUS = 1 -} - -- main thread ---@nodiscard ---@param smem plc_shared_memory @@ -50,9 +41,12 @@ function threads.thread__main(smem) local loop_clock = util.new_clock(MAIN_CLOCK) -- load in from shared memory - local networked = smem.networked - local plc_state = smem.plc_state - local plc_dev = smem.plc_dev + local networked = smem.networked + local plc_state = smem.plc_state + local plc_dev = smem.plc_dev + + local MQ__RPS_CMD = smem.q_cmds.MQ__RPS_CMD + local MQ__COMM_CMD = smem.q_cmds.MQ__COMM_CMD -- start clock loop_clock.start() @@ -175,60 +169,8 @@ function threads.thread__main(smem) elseif event == "peripheral" then -- peripheral connect local type, device = ppm.mount(param1) - if type ~= nil and device ~= nil then - if plc_state.no_reactor and (type == "fissionReactorLogicAdapter") then - -- reconnected reactor - plc_dev.reactor = device - plc_state.no_reactor = false - - println_ts("reactor reconnected") - log.info("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 - plc_state.reactor_formed = true - - -- determine if we are still in a degraded state - if (not networked or not plc_state.no_modem) and plc_state.reactor_formed then - plc_state.degraded = false - end - - smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM) - - rps.reconnect_reactor(plc_dev.reactor) - if networked then - plc_comms.reconnect_reactor(plc_dev.reactor) - end - - -- partial reset of RPS, specific to becoming formed/reconnected - -- without this, auto control can't resume on chunk load - rps.reset_reattach() - elseif networked and type == "modem" then - ---@cast device Modem - local is_comms_modem = util.trinary(plc_dev.modem_wired, plc_dev.modem_iface == param1, device.isWireless()) - - -- note, check init_ok first since nic will be nil if it is false - if is_comms_modem and not nic.is_connected() then - -- reconnected modem - plc_dev.modem = device - plc_state.no_modem = false - - nic.connect(device) - - println_ts("comms modem reconnected") - log.info("comms modem reconnected") - - -- determine if we are still in a degraded state - if plc_state.reactor_formed and not plc_state.no_reactor then - plc_state.degraded = false - end - elseif device.isWireless() then - log.info("unused wireless modem connected") - else - log.info("non-comms wired modem connected") - end - end + backplane.attach(param1, type, device, println_ts) end -- update indicators @@ -295,6 +237,8 @@ function threads.thread__rps(smem) local rps_queue = smem.q.mq_rps + local MQ__RPS_CMD = smem.q_cmds.MQ__RPS_CMD + local was_linked = false local last_update = util.time() @@ -425,8 +369,10 @@ function threads.thread__comms_tx(smem) log.debug("OS: comms tx thread start") -- load in from shared memory - local plc_state = smem.plc_state - local comms_queue = smem.q.mq_comms_tx + local plc_state = smem.plc_state + local comms_queue = smem.q.mq_comms_tx + + local MQ__COMM_CMD = smem.q_cmds.MQ__COMM_CMD local last_update = util.time()