From 454d166ac93ac030d3480e37077621875e1db104 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 5 May 2025 17:54:47 +0000 Subject: [PATCH 01/90] #580 reactor PLC changes for wired comms modems --- reactor-plc/configure.lua | 4 +++- reactor-plc/plc.lua | 6 +++++- reactor-plc/startup.lua | 17 ++++++++++++----- reactor-plc/threads.lua | 18 ++++++++++-------- scada-common/ppm.lua | 13 +++++++++++++ 5 files changed, 43 insertions(+), 15 deletions(-) diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index b2baa6d..651b57e 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -79,8 +79,9 @@ local tmp_cfg = { SVR_Channel = nil, ---@type integer PLC_Channel = nil, ---@type integer ConnTimeout = nil, ---@type number + WiredModem = false, ---@type string|false TrustedRange = nil, ---@type number - AuthKey = nil, ---@type string|nil + AuthKey = "", ---@type string LogMode = 0, ---@type LOG_MODE LogPath = "", LogDebug = false, @@ -103,6 +104,7 @@ local fields = { { "SVR_Channel", "SVR Channel", 16240 }, { "PLC_Channel", "PLC Channel", 16241 }, { "ConnTimeout", "Connection Timeout", 5 }, + { "WiredModem", "Wired Modem", false }, { "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 73b95ce..c9af63a 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -47,6 +47,7 @@ function plc.load_config() config.SVR_Channel = settings.get("SVR_Channel") config.PLC_Channel = settings.get("PLC_Channel") config.ConnTimeout = settings.get("ConnTimeout") + config.WiredModem = settings.get("WiredModem") config.TrustedRange = settings.get("TrustedRange") config.AuthKey = settings.get("AuthKey") @@ -74,6 +75,7 @@ function plc.validate_config(cfg) cfv.assert_channel(cfg.PLC_Channel) cfv.assert_type_num(cfg.ConnTimeout) cfv.assert_min(cfg.ConnTimeout, 2) + cfv.assert((cfg.WiredModem == false) or (type(cfg.WiredModem) == "string")) cfv.assert_type_num(cfg.TrustedRange) cfv.assert_min(cfg.TrustedRange, 0) cfv.assert_type_str(cfg.AuthKey) @@ -542,7 +544,9 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) max_burn_rate = nil } - comms.set_trusted_range(config.TrustedRange) + if nic.isWireless() then + comms.set_trusted_range(config.TrustedRange) + end -- PRIVATE FUNCTIONS -- diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index c085e95..1ce7682 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc") local renderer = require("reactor-plc.renderer") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v1.8.20" +local R_PLC_VERSION = "v1.9.0" local println = util.println local println_ts = util.println_ts @@ -106,7 +106,9 @@ local function main() -- core PLC devices plc_dev = { reactor = ppm.get_fission_reactor(), - modem = ppm.get_wireless_modem() + modem_wired = type(config.WiredModem) == "string", + modem_iface = config.WiredModem, + modem = nil }, -- system objects @@ -130,6 +132,11 @@ 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_wired_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 @@ -149,10 +156,10 @@ local function main() plc_state.reactor_formed = false end - -- modem is required if networked + -- comms modem is required if networked if __shared_memory.networked and plc_state.no_modem then - println("init> wireless modem not found") - log.warning("init> no wireless modem on startup") + println("init> comms modem not found") + log.warning("init> 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 diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index b56ccc7..dadb80d 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -145,7 +145,7 @@ function threads.thread__main(smem, init) plc_state.degraded = true elseif networked and type == "modem" then ---@cast device Modem - -- we only care if this is our wireless modem + -- we only care if this is our comms modem -- note, check init_ok first since nic will be nil if it is false if plc_state.init_ok and nic.is_modem(device) then nic.disconnect() @@ -154,7 +154,7 @@ function threads.thread__main(smem, init) log.warning("comms modem disconnected") local other_modem = ppm.get_wireless_modem() - if other_modem then + if other_modem and not plc_dev.modem_wired then log.info("found another wireless modem, using it for comms") nic.connect(other_modem) else @@ -167,7 +167,7 @@ function threads.thread__main(smem, init) end end else - log.warning("a modem was disconnected") + log.warning("a non-comms modem was disconnected") end end end @@ -210,15 +210,17 @@ function threads.thread__main(smem, init) end elseif networked and type == "modem" then ---@cast device Modem - -- note, check init_ok first since nic will be nil if it is false - if device.isWireless() and not (plc_state.init_ok and nic.is_connected()) then + local is_comms_modem = util.trinary(plc_dev.modem_wired, plc_dev.modem_iface == param1, device.isWireless()) + + -- note, check init_ok since nic will be nil if it is false + if is_comms_modem and not (plc_state.init_ok and nic.is_connected()) then -- reconnected modem plc_dev.modem = device plc_state.no_modem = false if plc_state.init_ok then nic.connect(device) end - println_ts("wireless modem reconnected.") + println_ts("comms modem reconnected.") log.info("comms modem reconnected") -- determine if we are still in a degraded state @@ -226,9 +228,9 @@ function threads.thread__main(smem, init) plc_state.degraded = false end elseif device.isWireless() then - log.info("unused wireless modem reconnected") + log.info("unused wireless modem connected") else - log.info("wired modem reconnected") + log.info("non-comms wired modem connected") end end end diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index ea310a3..894e643 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -447,6 +447,19 @@ end ---@return table|nil reactor function table function ppm.get_fission_reactor() return ppm.get_device("fissionReactorLogicAdapter") end +-- get the named wired modem +---@nodiscard +---@param iface string CC peripheral interface +---@return Modem|nil modem function table +function ppm.get_wired_modem(iface) + local modem = nil + local device = ppm_sys.mounts[iface] + + if device.type == "modem" then modem = device.dev end + + return modem +end + -- get the wireless modem (if multiple, returns the first)
-- if this is in a CraftOS emulated environment, wired modems will be used instead ---@nodiscard From 028a161af0d7506317b2315d230d8c6aee3c1000 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 5 May 2025 17:57:54 +0000 Subject: [PATCH 02/90] #580 RTU gateway changes for wired comms modems --- reactor-plc/threads.lua | 2 +- rtu/configure.lua | 4 +++- rtu/databus.lua | 2 +- rtu/rtu.lua | 6 +++++- rtu/startup.lua | 19 +++++++++++++------ rtu/threads.lua | 15 +++++++++------ 6 files changed, 32 insertions(+), 16 deletions(-) diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index dadb80d..0de3b7e 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -167,7 +167,7 @@ function threads.thread__main(smem, init) end end else - log.warning("a non-comms modem was disconnected") + log.warning("non-comms modem disconnected") end end end diff --git a/rtu/configure.lua b/rtu/configure.lua index 03fb1b9..fe06fea 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -88,8 +88,9 @@ local tmp_cfg = { SVR_Channel = nil, ---@type integer RTU_Channel = nil, ---@type integer ConnTimeout = nil, ---@type number + WiredModem = false, ---@type string|false TrustedRange = nil, ---@type number - AuthKey = nil, ---@type string|nil + AuthKey = nil, ---@type string LogMode = 0, ---@type LOG_MODE LogPath = "", LogDebug = false, @@ -107,6 +108,7 @@ local fields = { { "SVR_Channel", "SVR Channel", 16240 }, { "RTU_Channel", "RTU Channel", 16242 }, { "ConnTimeout", "Connection Timeout", 5 }, + { "WiredModem", "Wired Modem", false }, { "TrustedRange", "Trusted Range", 0 }, { "AuthKey", "Facility Auth Key", "" }, { "LogMode", "Log Mode", log.MODE.APPEND }, diff --git a/rtu/databus.lua b/rtu/databus.lua index 0d086f4..c4eb0ed 100644 --- a/rtu/databus.lua +++ b/rtu/databus.lua @@ -31,7 +31,7 @@ function databus.tx_versions(rtu_v, comms_v) databus.ps.publish("comms_version", comms_v) end --- transmit hardware status for modem connection state +-- transmit hardware status for comms modem connection state ---@param has_modem boolean function databus.tx_hw_modem(has_modem) databus.ps.publish("has_modem", has_modem) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index d7a576e..ac1c2f7 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -36,6 +36,7 @@ function rtu.load_config() config.SVR_Channel = settings.get("SVR_Channel") config.RTU_Channel = settings.get("RTU_Channel") config.ConnTimeout = settings.get("ConnTimeout") + config.WiredModem = settings.get("WiredModem") config.TrustedRange = settings.get("TrustedRange") config.AuthKey = settings.get("AuthKey") @@ -61,6 +62,7 @@ function rtu.validate_config(cfg) cfv.assert_channel(cfg.RTU_Channel) cfv.assert_type_num(cfg.ConnTimeout) cfv.assert_min(cfg.ConnTimeout, 2) + cfv.assert((cfg.WiredModem == false) or (type(cfg.WiredModem) == "string")) cfv.assert_type_num(cfg.TrustedRange) cfv.assert_min(cfg.TrustedRange, 0) cfv.assert_type_str(cfg.AuthKey) @@ -299,7 +301,9 @@ function rtu.comms(version, nic, conn_watchdog) local insert = table.insert - comms.set_trusted_range(config.TrustedRange) + if nic.isWireless() then + comms.set_trusted_range(config.TrustedRange) + end -- PRIVATE FUNCTIONS -- diff --git a/rtu/startup.lua b/rtu/startup.lua index c1413a6..1385fae 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -31,7 +31,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "v1.11.8" +local RTU_VERSION = "v1.12.0" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_HW_STATE = databus.RTU_HW_STATE @@ -108,7 +108,9 @@ local function main() -- RTU gateway devices (not RTU units) rtu_dev = { - modem = ppm.get_wireless_modem(), + modem_wired = type(config.WiredModem) == "string", + modem_iface = config.WiredModem, + modem = nil, sounders = {} ---@type rtu_speaker_sounder[] }, @@ -131,8 +133,13 @@ local function main() local rtu_state = __shared_memory.rtu_state + -- get the configured modem + if smem_dev.modem_wired then + smem_dev.modem = ppm.get_wired_modem(smem_dev.modem_iface) + else smem_dev.modem = ppm.get_wireless_modem() end + ---------------------------------------- - -- interpret config and init units + -- interpret RTU configs and init units ---------------------------------------- local units = __shared_memory.rtu_sys.units @@ -506,10 +513,10 @@ local function main() log.info("startup> running in headless mode without front panel") end - -- check modem + -- check comms modem if smem_dev.modem == nil then - println("startup> wireless modem not found") - log.fatal("no wireless modem on startup") + println("startup> comms modem not found") + log.fatal("no comms modem on startup") return end diff --git a/rtu/threads.lua b/rtu/threads.lua index e1d8c6c..99ae9f8 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -189,6 +189,7 @@ function threads.thread__main(smem) -- load in from shared memory local rtu_state = smem.rtu_state + local rtu_dev = smem.rtu_dev local sounders = smem.rtu_dev.sounders local nic = smem.rtu_sys.nic local rtu_comms = smem.rtu_sys.rtu_comms @@ -246,11 +247,11 @@ function threads.thread__main(smem) if type ~= nil and device ~= nil then if type == "modem" then ---@cast device Modem - -- we only care if this is our wireless modem + -- we only care if this is our comms modem if nic.is_modem(device) then nic.disconnect() - println_ts("wireless modem disconnected!") + println_ts("comms modem disconnected!") log.warning("comms modem disconnected") local other_modem = ppm.get_wireless_modem() @@ -301,18 +302,20 @@ function threads.thread__main(smem) if type ~= nil and device ~= nil then if type == "modem" then ---@cast device Modem - if device.isWireless() and not nic.is_connected() then + local is_comms_modem = util.trinary(rtu_dev.modem_wired, rtu_dev.modem_iface == param1, device.isWireless()) + + if is_comms_modem and not nic.is_connected() then -- reconnected modem nic.connect(device) - println_ts("wireless modem reconnected.") + println_ts("comms modem reconnected.") log.info("comms modem reconnected") databus.tx_hw_modem(true) elseif device.isWireless() then - log.info("unused wireless modem reconnected") + log.info("unused wireless modem connected") else - log.info("wired modem reconnected") + log.info("non-comms wired modem connected") end elseif type == "speaker" then ---@cast device Speaker From c319039a4e8d73e2fcc02127daa35f166fa00032 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sat, 24 May 2025 20:01:05 +0000 Subject: [PATCH 03/90] simplified RTU gateway startup check failure code --- rtu/startup.lua | 42 ++++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/rtu/startup.lua b/rtu/startup.lua index 5140239..49a817a 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -147,6 +147,13 @@ local function main() local rtu_redstone = config.Redstone local rtu_devices = config.Peripherals + -- print and log a fatal error during startup + ---@param msg string + local function log_fail(msg) + println(msg) + log.fatal(msg) + end + -- get a string representation of a port interface ---@param entry rtu_rs_definition ---@return string @@ -178,18 +185,14 @@ local function main() assignment = "facility" for_reactor = 0 else - local message = util.c("sys_config> invalid unit assignment at block index #", entry_idx) - println(message) - log.fatal(message) + log_fail(util.c("sys_config> invalid unit assignment at block index #", entry_idx)) return false end -- create the appropriate RTU if it doesn't exist and check relay name validity if entry.relay then if type(entry.relay) ~= "string" then - local message = util.c("sys_config> invalid redstone relay '", entry.relay, '"') - println(message) - log.fatal(message) + log_fail(util.c("sys_config> invalid redstone relay '", entry.relay, '"')) return false elseif not rs_rtus[entry.relay] then log.debug(util.c("sys_config> allocated relay redstone RTU on interface ", entry.relay)) @@ -224,9 +227,7 @@ local function main() local conns = all_conns[for_reactor] if not valid then - local message = util.c("sys_config> invalid redstone definition at block index #", entry_idx) - println(message) - log.fatal(message) + log_fail(util.c("sys_config> invalid redstone definition at block index #", entry_idx)) return false else -- link redstone in RTU @@ -340,17 +341,13 @@ local function main() -- CHECK: name is a string if type(name) ~= "string" then - local message = util.c("sys_config> device entry #", i, ": device ", name, " isn't a string") - println(message) - log.fatal(message) + log_fail(util.c("sys_config> device entry #", i, ": device ", name, " isn't a string")) return false end -- CHECK: index type if (index ~= nil) and (not util.is_int(index)) then - local message = util.c("sys_config> device entry #", i, ": index ", index, " isn't valid") - println(message) - log.fatal(message) + log_fail(util.c("sys_config> device entry #", i, ": index ", index, " isn't valid")) return false end @@ -359,8 +356,7 @@ local function main() if (not util.is_int(index)) or ((index < min) and (max ~= nil and index > max)) then local message = util.c("sys_config> device entry #", i, ": index ", index, " isn't >= ", min) if max ~= nil then message = util.c(message, " and <= ", max) end - println(message) - log.fatal(message) + log_fail(message) return false else return true end end @@ -368,14 +364,10 @@ local function main() -- CHECK: reactor is an integer >= 0 local function validate_assign(for_facility) if for_facility and for_reactor ~= 0 then - local message = util.c("sys_config> device entry #", i, ": must only be for the facility") - println(message) - log.fatal(message) + log_fail(util.c("sys_config> device entry #", i, ": must only be for the facility")) return false elseif (not for_facility) and ((not util.is_int(for_reactor)) or (for_reactor < 1) or (for_reactor > 4)) then - local message = util.c("sys_config> device entry #", i, ": unit assignment ", for_reactor, " isn't vaild") - println(message) - log.fatal(message) + log_fail(util.c("sys_config> device entry #", i, ": unit assignment ", for_reactor, " isn't vaild")) return false else return true end end @@ -491,9 +483,7 @@ local function main() rtu_type = RTU_UNIT_TYPE.VIRTUAL rtu_iface = rtu.init_unit().interface() else - local message = util.c("sys_config> device '", name, "' is not a known type (", type, ")") - println_ts(message) - log.fatal(message) + log_fail(util.c("sys_config> device '", name, "' is not a known type (", type, ")")) return false end From c6143934d86414941f207e1d68408db9899eec2c Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sat, 24 May 2025 20:02:19 +0000 Subject: [PATCH 04/90] check interface side in network logic before handling packets --- scada-common/network.lua | 19 ++++++++++++------- scada-common/util.lua | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/scada-common/network.lua b/scada-common/network.lua index 7eccff7..cd2cfdd 100644 --- a/scada-common/network.lua +++ b/scada-common/network.lua @@ -76,12 +76,17 @@ local function compute_hmac(message) end -- NIC: Network Interface Controller
--- utilizes HMAC-MD5 for message authentication, if enabled +-- utilizes HMAC-MD5 for message authentication, if enabled and this is wireless +---@param iface string peripheral interface name ---@param modem Modem modem to use -function network.nic(modem) +function network.nic(iface, modem) local self = { - connected = true, -- used to avoid costly MAC calculations if modem isn't even present - channels = {} + -- used to quickly return out of tx/rx functions if there is nothing to do + connected = true, + -- used to avoid costly MAC calculations if not required + use_hash = c_eng.hmac and modem.isWireless(), + -- open channels + channels = {} } ---@class nic:Modem @@ -165,7 +170,7 @@ function network.nic(modem) if self.connected then local tx_packet = packet ---@type authd_packet|scada_packet - if c_eng.hmac ~= nil then + if self.use_hash then -- local start = util.time_ms() tx_packet = comms.authd_packet() @@ -190,10 +195,10 @@ function network.nic(modem) function public.receive(side, sender, reply_to, message, distance) local packet = nil - if self.connected then + if self.connected and side == iface then local s_packet = comms.scada_packet() - if c_eng.hmac ~= nil then + if self.use_hash then -- parse packet as an authenticated SCADA packet local a_packet = comms.authd_packet() a_packet.receive(side, sender, reply_to, message, distance) diff --git a/scada-common/util.lua b/scada-common/util.lua index 2a2f81a..8230fca 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -24,7 +24,7 @@ local t_pack = table.pack local util = {} -- scada-common version -util.version = "1.5.2" +util.version = "1.5.3" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 From bee1cdf01ce1a92aa396862e7e4a9d686c1d9986 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sat, 24 May 2025 20:03:17 +0000 Subject: [PATCH 05/90] return wireless modem interface name --- scada-common/ppm.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 894e643..a0a1247 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -463,19 +463,20 @@ end -- get the wireless modem (if multiple, returns the first)
-- if this is in a CraftOS emulated environment, wired modems will be used instead ---@nodiscard ----@return Modem|nil modem function table +---@return Modem|nil modem, string|nil iface function ppm.get_wireless_modem() - local w_modem = nil + local w_modem, w_iface = nil, nil local emulated_env = periphemu ~= nil - for _, device in pairs(ppm_sys.mounts) do + for iface, device in pairs(ppm_sys.mounts) do if device.type == "modem" and (emulated_env or device.dev.isWireless()) then + w_iface = iface w_modem = device.dev break end end - return w_modem + return w_modem, w_iface end -- list all connected monitors From 4a7fc6200e9540b5973886d0c22a2d18513baf8c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 15 Jun 2025 15:43:04 -0400 Subject: [PATCH 06/90] #580 work on supervisor wired modem configuration --- supervisor/config/system.lua | 50 ++++++---- supervisor/configure.lua | 17 +++- supervisor/databus.lua | 12 ++- supervisor/panel/front_panel.lua | 18 +++- supervisor/pcie.lua | 159 ++++++++++++++++++++++++++++++ supervisor/renderer.lua | 5 +- supervisor/session/svsessions.lua | 31 +++--- supervisor/startup.lua | 57 ++--------- supervisor/supervisor.lua | 87 ++++++++-------- 9 files changed, 298 insertions(+), 138 deletions(-) create mode 100644 supervisor/pcie.lua diff --git a/supervisor/config/system.lua b/supervisor/config/system.lua index 48867f5..26bd33c 100644 --- a/supervisor/config/system.lua +++ b/supervisor/config/system.lua @@ -62,8 +62,10 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=49} local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=49} local net_c_4 = Div{parent=net_cfg,x=2,y=4,width=49} + local net_c_5 = Div{parent=net_cfg,x=2,y=4,width=49} + local net_c_6 = Div{parent=net_cfg,x=2,y=4,width=49} - local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4}} + local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4,net_c_5,net_c_6}} TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)} @@ -137,40 +139,54 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=net_c_3,x=1,y=1,text="Please set the trusted range below."} - TextBox{parent=net_c_3,x=1,y=3,height=3,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_3,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_3,x=1,y=1,text="Please set the modem configuration below."} + TextBox{parent=net_c_3,x=1,y=3,height=3,text="Communications with the coordinator,",fg_bg=g_lg_fg_bg} + -- TextBox{parent=net_c_3,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg} - local range = NumberField{parent=net_c_3,x=1,y=10,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg} + local use_wired = Checkbox{parent=net_c_3,x=1,y=12,label="Use Wired Modem",default=ini_cfg.ExtChargeIdling,box_fg_bg=cpair(colors.yellow,colors.black)} - local tr_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Please set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local function submit_modems() + -- tmp_cfg. = use_wired.get_value() + net_pane.set_value(4) + end + + PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_modems,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + TextBox{parent=net_c_5,x=1,y=1,text="Please set the trusted range below."} + TextBox{parent=net_c_5,x=1,y=3,height=3,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_5,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg} + + local range = NumberField{parent=net_c_5,x=1,y=10,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg} + + local tr_err = TextBox{parent=net_c_5,x=8,y=14,width=35,text="Please set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_tr() local range_val = tonumber(range.get_value()) if range_val ~= nil then tmp_cfg.TrustedRange = range_val - net_pane.set_value(4) + net_pane.set_value(6) tr_err.hide(true) else tr_err.show() end end - PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_5,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_5,x=44,y=14,text="Next \x1a",callback=submit_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=net_c_4,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."} - TextBox{parent=net_c_4,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_6,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."} + TextBox{parent=net_c_6,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_4,x=1,y=11,text="Facility Auth Key"} - local key, _ = TextField{parent=net_c_4,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg} + TextBox{parent=net_c_6,x=1,y=11,text="Facility Auth Key"} + local key, _ = TextField{parent=net_c_6,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg} local function censor_key(enable) key.censor(tri(enable, "*", nil)) end - local hide_key = Checkbox{parent=net_c_4,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key} + local hide_key = Checkbox{parent=net_c_6,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key} hide_key.set_value(true) censor_key(true) - local key_err = TextBox{parent=net_c_4,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local key_err = TextBox{parent=net_c_6,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_auth() local v = key.get_value() @@ -181,8 +197,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit else key_err.show() end end - PushButton{parent=net_c_4,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_4,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_6,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_6,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} --#endregion diff --git a/supervisor/configure.lua b/supervisor/configure.lua index b25f891..fcb1611 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -3,6 +3,7 @@ -- local log = require("scada-common.log") +local ppm = require("scada-common.ppm") local tcd = require("scada-common.tcd") local util = require("scada-common.util") @@ -96,8 +97,11 @@ local tmp_cfg = { RTU_Timeout = nil, ---@type number CRD_Timeout = nil, ---@type number PKT_Timeout = nil, ---@type number + WiredModem = false, ---@type string|false + WirelessModem = false, ---@type boolean TrustedRange = nil, ---@type number AuthKey = nil, ---@type string|nil + PocketTest = true, ---@type boolean LogMode = 0, ---@type LOG_MODE LogPath = "", LogDebug = false, @@ -130,8 +134,11 @@ local fields = { { "RTU_Timeout", "RTU Connection Timeout", 5 }, { "CRD_Timeout", "CRD Connection Timeout", 5 }, { "PKT_Timeout", "PKT Connection Timeout", 5 }, + { "WiredModem", "Wired Modem", false }, + { "WirelessModem", "Pocket Wireless/Ender Modem", true }, { "TrustedRange", "Trusted Range", 0 }, - { "AuthKey", "Facility Auth Key" , ""}, + { "AuthKey", "Facility Auth Key" , "" }, + { "PocketTest", "Pocket Testing Features", true }, { "LogMode", "Log Mode", log.MODE.APPEND }, { "LogPath", "Log Path", "/log.txt" }, { "LogDebug", "Log Debug Messages", false }, @@ -314,6 +321,14 @@ function configurator.configure(ask_config) if k_e then display.handle_key(k_e) end elseif event == "paste" then display.handle_paste(param1) + elseif event == "peripheral_detach" then +---@diagnostic disable-next-line: discard-returns + ppm.handle_unmount(param1) + tool_ctl.gen_modem_list() + elseif event == "peripheral" then +---@diagnostic disable-next-line: discard-returns + ppm.mount(param1) + tool_ctl.gen_modem_list() end if event == "terminate" then return end diff --git a/supervisor/databus.lua b/supervisor/databus.lua index f5daefb..2600afb 100644 --- a/supervisor/databus.lua +++ b/supervisor/databus.lua @@ -27,10 +27,16 @@ function databus.tx_versions(sv_v, comms_v) databus.ps.publish("comms_version", comms_v) end --- transmit hardware status for modem connection state +-- transmit hardware status for the core comms modem connection state ---@param has_modem boolean -function databus.tx_hw_modem(has_modem) - databus.ps.publish("has_modem", has_modem) +function databus.tx_hw_c_modem(has_modem) + databus.ps.publish("has_c_modem", has_modem) +end + +-- transmit hardware status for the pocket modem connection state +---@param has_modem boolean +function databus.tx_hw_p_modem(has_modem) + databus.ps.publish("has_p_modem", has_modem) end -- transmit PLC firmware version and session connection state diff --git a/supervisor/panel/front_panel.lua b/supervisor/panel/front_panel.lua index 786fadf..fb865a3 100644 --- a/supervisor/panel/front_panel.lua +++ b/supervisor/panel/front_panel.lua @@ -34,7 +34,8 @@ local ind_grn = style.ind_grn -- create new front panel view ---@param panel DisplayBox main displaybox -local function init(panel) +---@param wl_modem boolean if there is a separate wireless modem +local function init(panel, wl_modem) local s_hi_box = style.theme.highlight_box local s_hi_bright = style.theme.highlight_box_bright @@ -53,7 +54,7 @@ local function init(panel) local main_page = Div{parent=page_div,x=1,y=1} - local system = Div{parent=main_page,width=14,height=17,x=2,y=2} + local system = Div{parent=main_page,width=18,height=17,x=2,y=2} local on = LED{parent=system,label="STATUS",colors=cpair(colors.green,colors.red)} local heartbeat = LED{parent=system,label="HEARTBEAT",colors=ind_grn} @@ -62,14 +63,21 @@ local function init(panel) heartbeat.register(databus.ps, "heartbeat", heartbeat.update) - local modem = LED{parent=system,label="MODEM",colors=ind_grn} + local c_modem = LED{parent=system,label="MODEM"..util.trinary(wl_modem," A",""),colors=ind_grn} system.line_break() - modem.register(databus.ps, "has_modem", modem.update) + c_modem.register(databus.ps, "has_modem_a", c_modem.update) + + if wl_modem then + local p_modem = LED{parent=system,label="MODEM B",colors=ind_grn} + system.line_break() + + p_modem.register(databus.ps, "has_modem_b", p_modem.update) + end ---@diagnostic disable-next-line: undefined-field local comp_id = util.sprintf("(%d)", os.getComputerID()) - TextBox{parent=system,x=9,y=4,width=6,text=comp_id,fg_bg=style.fp.disabled_fg} + TextBox{parent=system,x=11,y=4,width=6,text=comp_id,fg_bg=style.fp.disabled_fg} -- -- about footer diff --git a/supervisor/pcie.lua b/supervisor/pcie.lua new file mode 100644 index 0000000..6629e30 --- /dev/null +++ b/supervisor/pcie.lua @@ -0,0 +1,159 @@ +-- +-- PCIe - Borrowed the name of that protocol for fun (this manages physical peripherals) +-- + +local log = require("scada-common.log") +local network = require("scada-common.network") +local ppm = require("scada-common.ppm") + +local databus = require("supervisor.databus") + +local pcie_bus = {} + +local bus = { + c_wired = false, ---@type string|false wired comms modem + c_nic = nil, ---@type nic core nic + p_nic = nil ---@type nic|nil pocket nic +} + +-- network cards +---@class _svr_pcie_nic +---@field core nic the core comms NIC +---@field pocket nic the pocket NIC +pcie_bus.nic = { + -- close all channels then open a specified one on all nics + ---@param channel integer + reset_open = function (channel) + bus.c_nic.closeAll() + bus.c_nic.open(channel) + + if bus.p_nic then + bus.p_nic.closeAll() + bus.p_nic.open(channel) + end + end +} + +-- initialize peripherals +---@param config svr_config +---@param println function +function pcie_bus.init(config, println) + -- setup networking peripheral(s) + local core_modem, core_iface = ppm.get_wireless_modem() + if type(config.WiredModem) == "string" then + bus.c_wired = config.WiredModem + core_modem = ppm.get_wired_modem(config.WiredModem) + end + + if not (core_modem and core_iface) then + println("startup> core comms modem not found") + log.fatal("no core comms modem on startup") + return + end + + bus.c_nic = network.nic(core_iface, core_modem) + + if config.WirelessModem and config.WiredModem then + local pocket_modem, pocket_iface = ppm.get_wireless_modem() + + if not (pocket_modem and pocket_iface) then + println("startup> pocket wireless modem not found") + log.fatal("no pocket wireless modem on startup") + return + end + + bus.p_nic = network.nic(pocket_iface, pocket_modem) + end + + pcie_bus.nic.core = bus.c_nic + pcie_bus.nic.pocket = bus.p_nic or bus.c_nic + + databus.tx_hw_c_modem(true) + databus.tx_hw_p_modem(config.WirelessModem) +end + +-- handle the connecting of a device +---@param iface string +---@param type string +---@param device table +---@param println function +function pcie_bus.connect(iface, type, device, println) + if type == "modem" then + ---@cast device Modem + if device.isWireless() then + if not (bus.c_wired or bus.c_nic.is_connected()) then + -- reconnected comms modem + bus.c_nic.connect(device) + + println("core comms modem reconnected") + log.info("core comms modem reconnected") + + databus.tx_hw_c_modem(true) + elseif bus.p_nic and not bus.p_nic.is_connected() then + -- reconnected pocket modem + bus.p_nic.connect(device) + + println("pocket modem reconnected") + log.info("pocket modem reconnected") + + databus.tx_hw_p_modem(true) + else + log.info("unused wireless modem reconnected") + end + elseif iface == bus.c_wired then + -- reconnected wired comms modem + bus.c_nic.connect(device) + + println("core comms modem reconnected") + log.info("core comms modem reconnected") + + databus.tx_hw_c_modem(true) + else + log.info("wired modem reconnected") + end + end +end + +-- handle the removal of a device +---@param type string +---@param device table +---@param println function +function pcie_bus.remove(type, device, println) + if type == "modem" then + ---@cast device Modem + if bus.c_nic.is_modem(device) then + bus.c_nic.disconnect() + + println("core comms modem disconnected") + log.warning("core comms modem disconnected") + + local other_modem = ppm.get_wireless_modem() + if other_modem and not bus.c_wired then + log.info("found another wireless modem, using it for comms") + bus.c_nic.connect(other_modem) + else + databus.tx_hw_c_modem(false) + end + elseif bus.p_nic and bus.p_nic.is_modem(device) then + bus.p_nic.disconnect() + + println("pocket modem disconnected") + log.warning("pocket modem disconnected") + + local other_modem = ppm.get_wireless_modem() + if other_modem then + log.info("found another wireless modem, using it for pocket comms") + bus.p_nic.connect(other_modem) + else + databus.tx_hw_p_modem(false) + end + else + log.warning("non-comms modem disconnected") + end + end +end + +-- check if a dedicated pocket nic is in use +function pcie_bus.has_pocket_nic() return bus.p_nic ~= nil end + +return pcie_bus diff --git a/supervisor/renderer.lua b/supervisor/renderer.lua index 57c6b4b..1acde3a 100644 --- a/supervisor/renderer.lua +++ b/supervisor/renderer.lua @@ -19,10 +19,11 @@ local ui = { } -- try to start the UI +---@param wl_modem boolean if there is a separate wireless modem to display the status of ---@param theme FP_THEME front panel theme ---@param color_mode COLOR_MODE color mode ---@return boolean success, any error_msg -function renderer.try_start_ui(theme, color_mode) +function renderer.try_start_ui(wl_modem, theme, color_mode) local status, msg = true, nil if ui.display == nil then @@ -49,7 +50,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, wl_modem) end) if status then diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 087fe19..efdaec9 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -6,6 +6,7 @@ local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") local types = require("scada-common.types") local util = require("scada-common.util") +local pcie = require("supervisor.pcie") local databus = require("supervisor.databus") @@ -41,20 +42,17 @@ svsessions.SESSION_TYPE = SESSION_TYPE local self = { -- references to supervisor state and other data - nic = nil, ---@type nic|nil fp_ok = false, - config = nil, ---@type svr_config + config = nil, ---@type svr_config|nil facility = nil, ---@type facility|nil plc_ini_reset = {}, -- lists of connected sessions ----@diagnostic disable: missing-fields sessions = { - rtu = {}, ---@type rtu_session_struct - plc = {}, ---@type plc_session_struct - crd = {}, ---@type crd_session_struct - pdg = {} ---@type pdg_session_struct + rtu = {}, ---@type rtu_session_struct[] + plc = {}, ---@type plc_session_struct[] + crd = {}, ---@type crd_session_struct[] + pdg = {} ---@type pdg_session_struct[] }, ----@diagnostic enable: missing-fields -- next session IDs next_ids = { rtu = 0, plc = 0, crd = 0, pdg = 0 }, -- rtu device tracking and invalid assignment detection @@ -83,7 +81,9 @@ local function _sv_handle_outq(session) if msg ~= nil then if msg.qtype == mqueue.TYPE.PACKET then -- handle a packet to be sent - self.nic.transmit(session.r_chan, self.config.SVR_Channel, msg.message) + if session.r_chan == self.config.PKT_Channel then + pcie.nic.pocket.transmit(session.r_chan, self.config.SVR_Channel, msg.message) + else pcie.nic.core.transmit(session.r_chan, self.config.SVR_Channel, msg.message) end elseif msg.qtype == mqueue.TYPE.COMMAND then -- handle instruction/notification elseif msg.qtype == mqueue.TYPE.DATA then @@ -139,12 +139,9 @@ end local function _iterate(sessions) for i = 1, #sessions do local session = sessions[i] - if session.open and session.instance.iterate() then _sv_handle_outq(session) - else - session.open = false - end + else session.open = false end end end @@ -158,7 +155,9 @@ local function _shutdown(session) while session.out_queue.ready() do local msg = session.out_queue.pop() if msg ~= nil and msg.qtype == mqueue.TYPE.PACKET then - self.nic.transmit(session.r_chan, self.config.SVR_Channel, msg.message) + if session.r_chan == self.config.PKT_Channel then + pcie.nic.pocket.transmit(session.r_chan, self.config.SVR_Channel, msg.message) + else pcie.nic.core.transmit(session.r_chan, self.config.SVR_Channel, msg.message) end end end @@ -358,12 +357,10 @@ function svsessions.check_rtu_id(unit, list, max) end -- initialize svsessions ----@param nic nic network interface device ---@param fp_ok boolean front panel active ---@param config svr_config supervisor configuration ---@param facility facility -function svsessions.init(nic, fp_ok, config, facility) - self.nic = nic +function svsessions.init(fp_ok, config, facility) self.fp_ok = fp_ok self.config = config self.facility = facility diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 4a88018..29c32b3 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -3,6 +3,7 @@ -- require("/initenv").init_env() +local pcie = require("supervisor.pcie") local crash = require("scada-common.crash") local comms = require("scada-common.comms") @@ -125,18 +126,11 @@ local function main() network.init_mac(config.AuthKey) end - -- get modem - local modem = ppm.get_wireless_modem() - if modem == nil then - println("startup> wireless modem not found") - log.fatal("no wireless modem on startup") - return - end - - databus.tx_hw_modem(true) + -- hardware bus initialization + pcie.init(config, println) -- start UI - local fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode) + local fp_ok, message = renderer.try_start_ui(pcie.has_pocket_nic(), config.FrontPanelTheme, config.ColorMode) if not fp_ok then println_ts(util.c("UI error: ", message)) @@ -150,8 +144,7 @@ local function main() local sv_facility = facility.new(config) -- create network interface then setup comms - local nic = network.nic(modem) - local superv_comms = supervisor.comms(SUPERVISOR_VERSION, nic, fp_ok, sv_facility) + local superv_comms = supervisor.comms(SUPERVISOR_VERSION, fp_ok, sv_facility) -- base loop clock (6.67Hz, 3 ticks) local MAIN_CLOCK = 0.15 @@ -173,49 +166,13 @@ local function main() -- handle event if event == "peripheral_detach" then local type, device = ppm.handle_unmount(param1) - if type ~= nil and device ~= nil then - if type == "modem" then - ---@cast device Modem - -- we only care if this is our wireless modem - if nic.is_modem(device) then - nic.disconnect() - - println_ts("wireless modem disconnected!") - log.warning("comms modem disconnected") - - local other_modem = ppm.get_wireless_modem() - if other_modem then - log.info("found another wireless modem, using it for comms") - nic.connect(other_modem) - else - databus.tx_hw_modem(false) - end - else - log.warning("non-comms modem disconnected") - end - end + pcie.remove(type, device, println_ts) end elseif event == "peripheral" then local type, device = ppm.mount(param1) - if type ~= nil and device ~= nil then - if type == "modem" then - ---@cast device Modem - if device.isWireless() and not nic.is_connected() then - -- reconnected modem - nic.connect(device) - - println_ts("wireless modem reconnected.") - log.info("comms modem reconnected") - - databus.tx_hw_modem(true) - elseif device.isWireless() then - log.info("unused wireless modem reconnected") - else - log.info("wired modem reconnected") - end - end + pcie.connect(param1, type, device, println_ts) end elseif event == "timer" and loop_clock.is_clock(param1) then -- main loop tick diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index b30e218..3da4700 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -1,6 +1,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local util = require("scada-common.util") +local pcie = require("supervisor.pcie") local themes = require("graphics.themes") @@ -123,11 +124,10 @@ end -- supervisory controller communications ---@nodiscard ---@param _version string supervisor version ----@param nic nic network interface device ---@param fp_ok boolean if the front panel UI is running ---@param facility facility facility instance ---@diagnostic disable-next-line: unused-local -function supervisor.comms(_version, nic, fp_ok, facility) +function supervisor.comms(_version, fp_ok, facility) -- print a log message to the terminal as long as the UI isn't running local function println(message) if not fp_ok then util.println_ts(message) end end @@ -137,20 +137,24 @@ function supervisor.comms(_version, nic, fp_ok, facility) comms.set_trusted_range(config.TrustedRange) - -- PRIVATE FUNCTIONS -- - -- configure modem channels - nic.closeAll() - nic.open(config.SVR_Channel) + pcie.nic.reset_open(config.SVR_Channel) -- pass system data and objects to svsessions - svsessions.init(nic, fp_ok, config, facility) + svsessions.init(fp_ok, config, facility) + + -- get nic references + local c_nic = pcie.nic.core + local p_nic = pcie.nic.pocket + + -- PRIVATE FUNCTIONS -- -- send an establish request response + ---@param nic nic ---@param packet scada_packet ---@param ack ESTABLISH_ACK ---@param data? any optional data - local function _send_establish(packet, ack, data) + local function _send_establish(nic, packet, ack, data) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() @@ -175,36 +179,33 @@ function supervisor.comms(_version, nic, fp_ok, facility) ---@param distance integer ---@return modbus_frame|rplc_frame|mgmt_frame|crdn_frame|nil packet function public.parse_packet(side, sender, reply_to, message, distance) - local s_pkt = nic.receive(side, sender, reply_to, message, distance) local pkt = nil + local s_pkt = c_nic.receive(side, sender, reply_to, message, distance) + + if p_nic and not s_pkt then + -- try for it being from the pocket modem + s_pkt = p_nic.receive(side, sender, reply_to, message, distance) + end if s_pkt then -- get as MODBUS TCP packet if s_pkt.protocol() == PROTOCOL.MODBUS_TCP then local m_pkt = comms.modbus_packet() - if m_pkt.decode(s_pkt) then - pkt = m_pkt.get() - end + if m_pkt.decode(s_pkt) then pkt = m_pkt.get() end -- get as RPLC packet elseif s_pkt.protocol() == PROTOCOL.RPLC then local rplc_pkt = comms.rplc_packet() - if rplc_pkt.decode(s_pkt) then - pkt = rplc_pkt.get() - end + if rplc_pkt.decode(s_pkt) then pkt = rplc_pkt.get() end -- get as SCADA management packet elseif s_pkt.protocol() == PROTOCOL.SCADA_MGMT then local mgmt_pkt = comms.mgmt_packet() - if mgmt_pkt.decode(s_pkt) then - pkt = mgmt_pkt.get() - end + if mgmt_pkt.decode(s_pkt) then pkt = mgmt_pkt.get() end -- get as coordinator packet elseif s_pkt.protocol() == PROTOCOL.SCADA_CRDN then local crdn_pkt = comms.crdn_packet() - if crdn_pkt.decode(s_pkt) then - pkt = crdn_pkt.get() - end + if crdn_pkt.decode(s_pkt) then pkt = crdn_pkt.get() end else - log.debug("attempted parse of illegal packet type " .. s_pkt.protocol(), true) + log.debug("receive[" .. side .. "] attempted parse of illegal packet type " .. s_pkt.protocol(), true) end end @@ -257,7 +258,7 @@ function supervisor.comms(_version, nic, fp_ok, facility) log.info(util.c("dropping PLC establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) end - _send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) elseif dev_type == DEVICE_TYPE.PLC then -- PLC linking request if packet.length == 4 and type(packet.data[4]) == "number" then @@ -270,7 +271,7 @@ function supervisor.comms(_version, nic, fp_ok, facility) log.warning(util.c("PLC_ESTABLISH: denied assignment ", reactor_id, " outside of configured unit count ", config.UnitCount)) end - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) else -- try to establish the session local plc_id = svsessions.establish_plc_session(src_addr, i_seq_num, reactor_id, firmware_v) @@ -281,25 +282,25 @@ function supervisor.comms(_version, nic, fp_ok, facility) log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) end - _send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION) + _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.COLLISION) else -- got an ID; assigned to a reactor successfully println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected")) log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id)) - _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) + _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) end end else log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type") - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC channel")) - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else log.debug("invalid establish packet (on PLC channel)") - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else -- any other packet should be session related, discard it @@ -343,7 +344,7 @@ function supervisor.comms(_version, nic, fp_ok, facility) log.info(util.c("dropping RTU establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) end - _send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) elseif dev_type == DEVICE_TYPE.RTU then if packet.length == 4 then -- this is an RTU advertisement for a new session @@ -352,18 +353,18 @@ function supervisor.comms(_version, nic, fp_ok, facility) println(util.c("RTU (", firmware_v, ") [@", src_addr, "] \xbb connected")) log.info(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) + _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) else log.debug("RTU_ESTABLISH: packet length mismatch") - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else log.debug(util.c("illegal establish packet for device ", dev_type, " on RTU channel")) - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else log.debug("invalid establish packet (on RTU channel)") - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else -- any other packet should be session related, discard it @@ -397,7 +398,7 @@ function supervisor.comms(_version, nic, fp_ok, facility) log.info(util.c("dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) end - _send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) elseif dev_type == DEVICE_TYPE.CRD then -- this is an attempt to establish a new coordinator session local s_id = svsessions.establish_crd_session(src_addr, i_seq_num, firmware_v) @@ -406,21 +407,21 @@ function supervisor.comms(_version, nic, fp_ok, facility) println(util.c("CRD (", firmware_v, ") [@", src_addr, "] \xbb connected")) log.info(util.c("CRD_ESTABLISH: coordinator (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, facility.get_cooling_conf() }) + _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, facility.get_cooling_conf() }) else if last_ack ~= ESTABLISH_ACK.COLLISION then log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator") end - _send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION) + _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.COLLISION) end else log.debug(util.c("illegal establish packet for device ", dev_type, " on coordinator channel")) - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else log.debug("CRD_ESTABLISH: establish packet length mismatch") - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else -- any other packet should be session related, discard it @@ -464,7 +465,7 @@ function supervisor.comms(_version, nic, fp_ok, facility) log.info(util.c("dropping PDG establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) end - _send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + _send_establish(p_nic or c_nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) elseif dev_type == DEVICE_TYPE.PKT then -- this is an attempt to establish a new pocket diagnostic session local s_id = svsessions.establish_pdg_session(src_addr, i_seq_num, firmware_v) @@ -472,14 +473,14 @@ function supervisor.comms(_version, nic, fp_ok, facility) println(util.c("PKT (", firmware_v, ") [@", src_addr, "] \xbb connected")) log.info(util.c("PDG_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) + _send_establish(p_nic or c_nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) else log.debug(util.c("illegal establish packet for device ", dev_type, " on pocket channel")) - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(p_nic or c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else log.debug("PDG_ESTABLISH: establish packet length mismatch") - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(p_nic or c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else -- any other packet should be session related, discard it From 391b68d35715482ced173083b8151d3349273ea5 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sat, 28 Jun 2025 17:17:31 +0000 Subject: [PATCH 07/90] #580 supervisor wired comms networking logic --- scada-common/comms.lua | 2 + scada-common/types.lua | 7 ++ scada-common/util.lua | 2 +- supervisor/configure.lua | 19 +++- supervisor/databus.lua | 12 +- supervisor/pcie.lua | 178 ++++++++++++++++-------------- supervisor/session/svsessions.lua | 24 ++-- supervisor/startup.lua | 4 +- supervisor/supervisor.lua | 72 ++++++------ 9 files changed, 177 insertions(+), 143 deletions(-) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index f827ab8..02413f8 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -249,6 +249,8 @@ function comms.scada_packet() ---@nodiscard function public.raw_sendable() return self.raw end + ---@nodiscard + function public.interface() return self.modem_msg_in.iface end ---@nodiscard function public.local_channel() return self.modem_msg_in.s_channel end ---@nodiscard diff --git a/scada-common/types.lua b/scada-common/types.lua index 3f12d12..c4d6c53 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -212,6 +212,13 @@ end --#region ENUMERATION TYPES +---@enum LISTEN_MODE +types.LISTEN_MODE = { + WIRELESS = 0, + WIRED = 1, + ALL = 2 +} + ---@enum TEMP_SCALE types.TEMP_SCALE = { KELVIN = 1, diff --git a/scada-common/util.lua b/scada-common/util.lua index 8230fca..031eaa0 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -24,7 +24,7 @@ local t_pack = table.pack local util = {} -- scada-common version -util.version = "1.5.3" +util.version = "1.5.4" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 diff --git a/supervisor/configure.lua b/supervisor/configure.lua index fcb1611..aed0aa2 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -5,6 +5,7 @@ local log = require("scada-common.log") local ppm = require("scada-common.ppm") local tcd = require("scada-common.tcd") +local types = require("scada-common.types") local util = require("scada-common.util") local facility = require("supervisor.config.facility") @@ -97,11 +98,15 @@ local tmp_cfg = { RTU_Timeout = nil, ---@type number CRD_Timeout = nil, ---@type number PKT_Timeout = nil, ---@type number + WirelessModem = true, ---@type boolean WiredModem = false, ---@type string|false - WirelessModem = false, ---@type boolean + PLC_Listen = 0, ---@type LISTEN_MODE + RTU_Listen = 0, ---@type LISTEN_MODE + CRD_Listen = 0, ---@type LISTEN_MODE + PocketEnabled = true, ---@type boolean + PocketTest = true, ---@type boolean TrustedRange = nil, ---@type number AuthKey = nil, ---@type string|nil - PocketTest = true, ---@type boolean LogMode = 0, ---@type LOG_MODE LogPath = "", LogDebug = false, @@ -134,11 +139,15 @@ local fields = { { "RTU_Timeout", "RTU Connection Timeout", 5 }, { "CRD_Timeout", "CRD Connection Timeout", 5 }, { "PKT_Timeout", "PKT Connection Timeout", 5 }, - { "WiredModem", "Wired Modem", false }, - { "WirelessModem", "Pocket Wireless/Ender Modem", true }, + { "WirelessModem", "Wireless/Ender Comms Modem", true }, + { "WiredModem", "Wired Comms Modem", false }, + { "PLC_Listen", "PLC Listen Mode", types.LISTEN_MODE.WIRELESS }, + { "RTU_Listen", "RTU Gateway Listen Mode", types.LISTEN_MODE.WIRELESS }, + { "CRD_Listen", "Coordinator Listen Mode", types.LISTEN_MODE.WIRELESS }, + { "PocketEnabled", "Pocket Connectivity", true }, + { "PocketTest", "Pocket Testing Features", true }, { "TrustedRange", "Trusted Range", 0 }, { "AuthKey", "Facility Auth Key" , "" }, - { "PocketTest", "Pocket Testing Features", true }, { "LogMode", "Log Mode", log.MODE.APPEND }, { "LogPath", "Log Path", "/log.txt" }, { "LogDebug", "Log Debug Messages", false }, diff --git a/supervisor/databus.lua b/supervisor/databus.lua index 2600afb..2a7f3ec 100644 --- a/supervisor/databus.lua +++ b/supervisor/databus.lua @@ -27,16 +27,16 @@ function databus.tx_versions(sv_v, comms_v) databus.ps.publish("comms_version", comms_v) end --- transmit hardware status for the core comms modem connection state +-- transmit hardware status for the wireless comms modem connection state ---@param has_modem boolean -function databus.tx_hw_c_modem(has_modem) - databus.ps.publish("has_c_modem", has_modem) +function databus.tx_hw_wl_modem(has_modem) + databus.ps.publish("has_wl_modem", has_modem) end --- transmit hardware status for the pocket modem connection state +-- transmit hardware status for the wired comms modem connection state ---@param has_modem boolean -function databus.tx_hw_p_modem(has_modem) - databus.ps.publish("has_p_modem", has_modem) +function databus.tx_hw_wd_modem(has_modem) + databus.ps.publish("has_wd_modem", has_modem) end -- transmit PLC firmware version and session connection state diff --git a/supervisor/pcie.lua b/supervisor/pcie.lua index 6629e30..fab96f0 100644 --- a/supervisor/pcie.lua +++ b/supervisor/pcie.lua @@ -11,65 +11,95 @@ local databus = require("supervisor.databus") local pcie_bus = {} local bus = { - c_wired = false, ---@type string|false wired comms modem - c_nic = nil, ---@type nic core nic - p_nic = nil ---@type nic|nil pocket nic + wired_modem = false, ---@type string|false wired comms modem name + wl_nic = nil, ---@type nic|nil wireless nic + wd_nic = nil ---@type nic|nil wired nic } -- network cards ---@class _svr_pcie_nic ----@field core nic the core comms NIC ----@field pocket nic the pocket NIC +---@field wl nic|nil the wireless comms NIC +---@field wd nic|nil the wired comms NIC pcie_bus.nic = { - -- close all channels then open a specified one on all nics - ---@param channel integer - reset_open = function (channel) - bus.c_nic.closeAll() - bus.c_nic.open(channel) + -- close all channels and then open the configured channels on the appropriate nic(s) + ---@param config svr_config + reset_open = function (config) + if bus.wl_nic then + bus.wl_nic.closeAll() - if bus.p_nic then - bus.p_nic.closeAll() - bus.p_nic.open(channel) + if config.PLC_Listen % 2 == 0 then bus.wl_nic.open(config.PLC_Channel) end + if config.RTU_Listen % 2 == 0 then bus.wl_nic.open(config.RTU_Channel) end + if config.CRD_Listen % 2 == 0 then bus.wl_nic.open(config.CRD_Channel) end + if config.PocketEnabled then bus.wl_nic.open(config.PKT_Channel) end end - end + + if bus.wd_nic then + bus.wd_nic.closeAll() + + if config.PLC_Listen > 0 then bus.wd_nic.open(config.PLC_Channel) end + if config.RTU_Listen > 0 then bus.wd_nic.open(config.RTU_Channel) end + if config.CRD_Listen > 0 then bus.wd_nic.open(config.CRD_Channel) end + end + end, + -- get the requested nic by interface + ---@param iface string + ---@return nic|nil + get = function(iface) + local dev = ppm.get_device(iface) + + if dev then + if bus.wl_nic and bus.wl_nic.is_modem(dev) then return bus.wl_nic end + if bus.wd_nic and bus.wd_nic.is_modem(dev) then return bus.wd_nic end + end + + return nil + end, + -- cards by interface + ---@type { string: nic } + cards = {} } -- initialize peripherals ---@param config svr_config ---@param println function +---@return boolean success function pcie_bus.init(config, println) -- setup networking peripheral(s) - local core_modem, core_iface = ppm.get_wireless_modem() if type(config.WiredModem) == "string" then - bus.c_wired = config.WiredModem - core_modem = ppm.get_wired_modem(config.WiredModem) - end + bus.wired_modem = config.WiredModem - if not (core_modem and core_iface) then - println("startup> core comms modem not found") - log.fatal("no core comms modem on startup") - return - end + local wired_modem = ppm.get_wired_modem(bus.wired_modem) - bus.c_nic = network.nic(core_iface, core_modem) - - if config.WirelessModem and config.WiredModem then - local pocket_modem, pocket_iface = ppm.get_wireless_modem() - - if not (pocket_modem and pocket_iface) then - println("startup> pocket wireless modem not found") - log.fatal("no pocket wireless modem on startup") - return + if not (wired_modem and bus.wired_modem) then + println("startup> wired comms modem not found") + log.fatal("no wired comms modem on startup") + return false end - bus.p_nic = network.nic(pocket_iface, pocket_modem) + bus.wd_nic = network.nic(bus.wired_modem, wired_modem) + pcie_bus.nic.cards[bus.wired_modem] = bus.wd_nic end - pcie_bus.nic.core = bus.c_nic - pcie_bus.nic.pocket = bus.p_nic or bus.c_nic + if config.WirelessModem then + local wireless_modem, wireless_iface = ppm.get_wireless_modem() - databus.tx_hw_c_modem(true) - databus.tx_hw_p_modem(config.WirelessModem) + if not (wireless_modem and wireless_iface) then + println("startup> wireless comms modem not found") + log.fatal("no wireless comms modem on startup") + return false + end + + bus.wl_nic = network.nic(wireless_iface, wireless_modem) + pcie_bus.nic.cards[wireless_iface] = bus.wl_nic + end + + pcie_bus.nic.wl = bus.wl_nic + pcie_bus.nic.wd = bus.wd_nic + + databus.tx_hw_wl_modem(true) + databus.tx_hw_wd_modem(config.WirelessModem) + + return true end -- handle the connecting of a device @@ -81,33 +111,27 @@ function pcie_bus.connect(iface, type, device, println) if type == "modem" then ---@cast device Modem if device.isWireless() then - if not (bus.c_wired or bus.c_nic.is_connected()) then - -- reconnected comms modem - bus.c_nic.connect(device) + if bus.wl_nic and not bus.wl_nic.is_connected() then + -- reconnected wireless comms modem + bus.wl_nic.connect(device) + pcie_bus.nic.cards[iface] = bus.wl_nic - println("core comms modem reconnected") - log.info("core comms modem reconnected") + println("wireless comms modem reconnected") + log.info("wireless comms modem reconnected") - databus.tx_hw_c_modem(true) - elseif bus.p_nic and not bus.p_nic.is_connected() then - -- reconnected pocket modem - bus.p_nic.connect(device) - - println("pocket modem reconnected") - log.info("pocket modem reconnected") - - databus.tx_hw_p_modem(true) + databus.tx_hw_wl_modem(true) else log.info("unused wireless modem reconnected") end - elseif iface == bus.c_wired then + elseif bus.wd_nic and (iface == bus.wired_modem) then -- reconnected wired comms modem - bus.c_nic.connect(device) + bus.wd_nic.connect(device) + pcie_bus.nic.cards[iface] = bus.wd_nic - println("core comms modem reconnected") - log.info("core comms modem reconnected") + println("wired comms modem reconnected") + log.info("wired comms modem reconnected") - databus.tx_hw_c_modem(true) + databus.tx_hw_wl_modem(true) else log.info("wired modem reconnected") end @@ -115,45 +139,39 @@ function pcie_bus.connect(iface, type, device, println) end -- handle the removal of a device +---@param iface string ---@param type string ---@param device table ---@param println function -function pcie_bus.remove(type, device, println) +function pcie_bus.remove(iface, type, device, println) if type == "modem" then + pcie_bus.nic.cards[iface] = nil + ---@cast device Modem - if bus.c_nic.is_modem(device) then - bus.c_nic.disconnect() + if bus.wl_nic and bus.wl_nic.is_modem(device) then + bus.wl_nic.disconnect() - println("core comms modem disconnected") - log.warning("core comms modem disconnected") - - local other_modem = ppm.get_wireless_modem() - if other_modem and not bus.c_wired then - log.info("found another wireless modem, using it for comms") - bus.c_nic.connect(other_modem) - else - databus.tx_hw_c_modem(false) - end - elseif bus.p_nic and bus.p_nic.is_modem(device) then - bus.p_nic.disconnect() - - println("pocket modem disconnected") - log.warning("pocket modem disconnected") + println("wireless comms modem disconnected") + log.warning("wireless comms modem disconnected") local other_modem = ppm.get_wireless_modem() if other_modem then - log.info("found another wireless modem, using it for pocket comms") - bus.p_nic.connect(other_modem) + log.info("found another wireless modem, using it for comms") + bus.wl_nic.connect(other_modem) else - databus.tx_hw_p_modem(false) + databus.tx_hw_wl_modem(false) end + elseif bus.wd_nic and bus.wd_nic.is_modem(device) then + bus.wd_nic.disconnect() + + println("wired modem disconnected") + log.warning("wired modem disconnected") + + databus.tx_hw_wd_modem(false) else log.warning("non-comms modem disconnected") end end end --- check if a dedicated pocket nic is in use -function pcie_bus.has_pocket_nic() return bus.p_nic ~= nil end - return pcie_bus diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index efdaec9..a8c7165 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -81,9 +81,7 @@ local function _sv_handle_outq(session) if msg ~= nil then if msg.qtype == mqueue.TYPE.PACKET then -- handle a packet to be sent - if session.r_chan == self.config.PKT_Channel then - pcie.nic.pocket.transmit(session.r_chan, self.config.SVR_Channel, msg.message) - else pcie.nic.core.transmit(session.r_chan, self.config.SVR_Channel, msg.message) end + session.nic.transmit(session.r_chan, self.config.SVR_Channel, msg.message) elseif msg.qtype == mqueue.TYPE.COMMAND then -- handle instruction/notification elseif msg.qtype == mqueue.TYPE.DATA then @@ -155,9 +153,7 @@ local function _shutdown(session) while session.out_queue.ready() do local msg = session.out_queue.pop() if msg ~= nil and msg.qtype == mqueue.TYPE.PACKET then - if session.r_chan == self.config.PKT_Channel then - pcie.nic.pocket.transmit(session.r_chan, self.config.SVR_Channel, msg.message) - else pcie.nic.core.transmit(session.r_chan, self.config.SVR_Channel, msg.message) end + session.nic.transmit(session.r_chan, self.config.SVR_Channel, msg.message) end end @@ -463,12 +459,13 @@ end -- establish a new PLC session ---@nodiscard +---@param nic nic interface to use for this session ---@param source_addr integer PLC computer ID ---@param i_seq_num integer initial (most recent) sequence number ---@param for_reactor integer unit ID ---@param version string PLC version ---@return integer|false session_id -function svsessions.establish_plc_session(source_addr, i_seq_num, for_reactor, version) +function svsessions.establish_plc_session(nic, source_addr, i_seq_num, for_reactor, version) if svsessions.get_reactor_session(for_reactor) == nil and for_reactor >= 1 and for_reactor <= self.config.UnitCount then ---@class plc_session_struct local plc_s = { @@ -476,6 +473,7 @@ function svsessions.establish_plc_session(source_addr, i_seq_num, for_reactor, v open = true, reactor = for_reactor, version = version, + nic = nic, r_chan = self.config.PLC_Channel, s_addr = source_addr, in_queue = mqueue.new(), @@ -513,17 +511,19 @@ end -- establish a new RTU gateway session ---@nodiscard +---@param nic nic interface to use for this session ---@param source_addr integer RTU gateway computer ID ---@param i_seq_num integer initial (most recent) sequence number ---@param advertisement table RTU capability advertisement ---@param version string RTU gateway version ---@return integer session_id -function svsessions.establish_rtu_session(source_addr, i_seq_num, advertisement, version) +function svsessions.establish_rtu_session(nic, source_addr, i_seq_num, advertisement, version) ---@class rtu_session_struct local rtu_s = { s_type = "rtu", open = true, version = version, + nic = nic, r_chan = self.config.RTU_Channel, s_addr = source_addr, in_queue = mqueue.new(), @@ -554,17 +554,19 @@ end -- establish a new coordinator session ---@nodiscard +---@param nic nic interface to use for this session ---@param source_addr integer coordinator computer ID ---@param i_seq_num integer initial (most recent) sequence number ---@param version string coordinator version ---@return integer|false session_id -function svsessions.establish_crd_session(source_addr, i_seq_num, version) +function svsessions.establish_crd_session(nic, source_addr, i_seq_num, version) if svsessions.get_crd_session() == nil then ---@class crd_session_struct local crd_s = { s_type = "crd", open = true, version = version, + nic = nic, r_chan = self.config.CRD_Channel, s_addr = source_addr, in_queue = mqueue.new(), @@ -599,16 +601,18 @@ end -- establish a new pocket diagnostics session ---@nodiscard +---@param nic nic interface to use for this session ---@param source_addr integer pocket computer ID ---@param i_seq_num integer initial (most recent) sequence number ---@param version string pocket version ---@return integer|false session_id -function svsessions.establish_pdg_session(source_addr, i_seq_num, version) +function svsessions.establish_pdg_session(nic, source_addr, i_seq_num, version) ---@class pdg_session_struct local pdg_s = { s_type = "pkt", open = true, version = version, + nic = nic, r_chan = self.config.PKT_Channel, s_addr = source_addr, in_queue = mqueue.new(), diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 29c32b3..3aa46f1 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -127,7 +127,7 @@ local function main() end -- hardware bus initialization - pcie.init(config, println) + if not pcie.init(config, println) then return end -- start UI local fp_ok, message = renderer.try_start_ui(pcie.has_pocket_nic(), config.FrontPanelTheme, config.ColorMode) @@ -167,7 +167,7 @@ local function main() if event == "peripheral_detach" then local type, device = ppm.handle_unmount(param1) if type ~= nil and device ~= nil then - pcie.remove(type, device, println_ts) + pcie.remove(param1, type, device, println_ts) end elseif event == "peripheral" then local type, device = ppm.mount(param1) diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 3da4700..ec4c92b 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -137,16 +137,12 @@ function supervisor.comms(_version, fp_ok, facility) comms.set_trusted_range(config.TrustedRange) - -- configure modem channels - pcie.nic.reset_open(config.SVR_Channel) + -- configure network channels + pcie.nic.reset_open(config) -- pass system data and objects to svsessions svsessions.init(fp_ok, config, facility) - -- get nic references - local c_nic = pcie.nic.core - local p_nic = pcie.nic.pocket - -- PRIVATE FUNCTIONS -- -- send an establish request response @@ -179,13 +175,8 @@ function supervisor.comms(_version, fp_ok, facility) ---@param distance integer ---@return modbus_frame|rplc_frame|mgmt_frame|crdn_frame|nil packet function public.parse_packet(side, sender, reply_to, message, distance) - local pkt = nil - local s_pkt = c_nic.receive(side, sender, reply_to, message, distance) - - if p_nic and not s_pkt then - -- try for it being from the pocket modem - s_pkt = p_nic.receive(side, sender, reply_to, message, distance) - end + local pkt, nic = nil, pcie.nic.cards[side] + local s_pkt = nic.receive(side, sender, reply_to, message, distance) if s_pkt then -- get as MODBUS TCP packet @@ -215,13 +206,16 @@ function supervisor.comms(_version, fp_ok, facility) -- handle a packet ---@param packet modbus_frame|rplc_frame|mgmt_frame|crdn_frame function public.handle_packet(packet) + local nic = pcie.nic.get(packet.scada_frame.interface()) local l_chan = packet.scada_frame.local_channel() local r_chan = packet.scada_frame.remote_channel() local src_addr = packet.scada_frame.src_addr() local protocol = packet.scada_frame.protocol() local i_seq_num = packet.scada_frame.seq_num() - if l_chan ~= config.SVR_Channel then + if not nic then + log.error("received packet from unconfigured interface " .. packet.scada_frame.interface(), true) + elseif l_chan ~= config.SVR_Channel then log.debug("received packet on unconfigured channel " .. l_chan, true) elseif r_chan == config.PLC_Channel then -- look for an associated session @@ -258,7 +252,7 @@ function supervisor.comms(_version, fp_ok, facility) log.info(util.c("dropping PLC establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) end - _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) elseif dev_type == DEVICE_TYPE.PLC then -- PLC linking request if packet.length == 4 and type(packet.data[4]) == "number" then @@ -271,10 +265,10 @@ function supervisor.comms(_version, fp_ok, facility) log.warning(util.c("PLC_ESTABLISH: denied assignment ", reactor_id, " outside of configured unit count ", config.UnitCount)) end - _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) else -- try to establish the session - local plc_id = svsessions.establish_plc_session(src_addr, i_seq_num, reactor_id, firmware_v) + local plc_id = svsessions.establish_plc_session(nic, src_addr, i_seq_num, reactor_id, firmware_v) if plc_id == false then -- reactor already has a PLC assigned @@ -282,25 +276,25 @@ function supervisor.comms(_version, fp_ok, facility) log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) end - _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.COLLISION) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.COLLISION) else -- got an ID; assigned to a reactor successfully println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected")) log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id)) - _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) end end else log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type") - _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC channel")) - _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else log.debug("invalid establish packet (on PLC channel)") - _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else -- any other packet should be session related, discard it @@ -344,27 +338,27 @@ function supervisor.comms(_version, fp_ok, facility) log.info(util.c("dropping RTU establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) end - _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) elseif dev_type == DEVICE_TYPE.RTU then if packet.length == 4 then -- this is an RTU advertisement for a new session local rtu_advert = packet.data[4] - local s_id = svsessions.establish_rtu_session(src_addr, i_seq_num, rtu_advert, firmware_v) + local s_id = svsessions.establish_rtu_session(nic, src_addr, i_seq_num, rtu_advert, firmware_v) println(util.c("RTU (", firmware_v, ") [@", src_addr, "] \xbb connected")) log.info(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) else log.debug("RTU_ESTABLISH: packet length mismatch") - _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else log.debug(util.c("illegal establish packet for device ", dev_type, " on RTU channel")) - _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else log.debug("invalid establish packet (on RTU channel)") - _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else -- any other packet should be session related, discard it @@ -398,30 +392,30 @@ function supervisor.comms(_version, fp_ok, facility) log.info(util.c("dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) end - _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) elseif dev_type == DEVICE_TYPE.CRD then -- this is an attempt to establish a new coordinator session - local s_id = svsessions.establish_crd_session(src_addr, i_seq_num, firmware_v) + local s_id = svsessions.establish_crd_session(nic, src_addr, i_seq_num, firmware_v) if s_id ~= false then println(util.c("CRD (", firmware_v, ") [@", src_addr, "] \xbb connected")) log.info(util.c("CRD_ESTABLISH: coordinator (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, facility.get_cooling_conf() }) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, facility.get_cooling_conf() }) else if last_ack ~= ESTABLISH_ACK.COLLISION then log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator") end - _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.COLLISION) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.COLLISION) end else log.debug(util.c("illegal establish packet for device ", dev_type, " on coordinator channel")) - _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else log.debug("CRD_ESTABLISH: establish packet length mismatch") - _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else -- any other packet should be session related, discard it @@ -465,22 +459,22 @@ function supervisor.comms(_version, fp_ok, facility) log.info(util.c("dropping PDG establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) end - _send_establish(p_nic or c_nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) elseif dev_type == DEVICE_TYPE.PKT then -- this is an attempt to establish a new pocket diagnostic session - local s_id = svsessions.establish_pdg_session(src_addr, i_seq_num, firmware_v) + local s_id = svsessions.establish_pdg_session(nic, src_addr, i_seq_num, firmware_v) println(util.c("PKT (", firmware_v, ") [@", src_addr, "] \xbb connected")) log.info(util.c("PDG_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - _send_establish(p_nic or c_nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) else log.debug(util.c("illegal establish packet for device ", dev_type, " on pocket channel")) - _send_establish(p_nic or c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else log.debug("PDG_ESTABLISH: establish packet length mismatch") - _send_establish(p_nic or c_nic, packet.scada_frame, ESTABLISH_ACK.DENY) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else -- any other packet should be session related, discard it From 250db00794a28a225c74817fd9c954954764895a Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sat, 28 Jun 2025 17:26:49 +0000 Subject: [PATCH 08/90] #580 supervisor front panel updates for wired modem support --- supervisor/panel/front_panel.lua | 24 +++++++++++++----------- supervisor/renderer.lua | 12 +++++------- supervisor/startup.lua | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/supervisor/panel/front_panel.lua b/supervisor/panel/front_panel.lua index fb865a3..14e224e 100644 --- a/supervisor/panel/front_panel.lua +++ b/supervisor/panel/front_panel.lua @@ -34,8 +34,8 @@ local ind_grn = style.ind_grn -- create new front panel view ---@param panel DisplayBox main displaybox ----@param wl_modem boolean if there is a separate wireless modem -local function init(panel, wl_modem) +---@param config svr_config configuraiton +local function init(panel, config) local s_hi_box = style.theme.highlight_box local s_hi_bright = style.theme.highlight_box_bright @@ -63,21 +63,23 @@ local function init(panel, wl_modem) heartbeat.register(databus.ps, "heartbeat", heartbeat.update) - local c_modem = LED{parent=system,label="MODEM"..util.trinary(wl_modem," A",""),colors=ind_grn} - system.line_break() - - c_modem.register(databus.ps, "has_modem_a", c_modem.update) - - if wl_modem then - local p_modem = LED{parent=system,label="MODEM B",colors=ind_grn} + if config.WirelessModem then + local wl_modem = LED{parent=system,label="WL MODEM",colors=ind_grn} system.line_break() - p_modem.register(databus.ps, "has_modem_b", p_modem.update) + wl_modem.register(databus.ps, "has_wl_modem", wl_modem.update) + end + + if config.WiredModem then + local wd_modem = LED{parent=system,label="WD MODEM",colors=ind_grn} + system.line_break() + + wd_modem.register(databus.ps, "has_wd_modem", wd_modem.update) end ---@diagnostic disable-next-line: undefined-field local comp_id = util.sprintf("(%d)", os.getComputerID()) - TextBox{parent=system,x=11,y=4,width=6,text=comp_id,fg_bg=style.fp.disabled_fg} + TextBox{parent=system,x=12,y=4,width=6,text=comp_id,fg_bg=style.fp.disabled_fg} -- -- about footer diff --git a/supervisor/renderer.lua b/supervisor/renderer.lua index 1acde3a..8db7a4a 100644 --- a/supervisor/renderer.lua +++ b/supervisor/renderer.lua @@ -19,16 +19,14 @@ local ui = { } -- try to start the UI ----@param wl_modem boolean if there is a separate wireless modem to display the status of ----@param theme FP_THEME front panel theme ----@param color_mode COLOR_MODE color mode +---@param config svr_config configuration ---@return boolean success, any error_msg -function renderer.try_start_ui(wl_modem, 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) @@ -42,7 +40,7 @@ function renderer.try_start_ui(wl_modem, 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 @@ -50,7 +48,7 @@ function renderer.try_start_ui(wl_modem, 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, wl_modem) + panel_view(ui.display, config) end) if status then diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 3aa46f1..eab0000 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -130,7 +130,7 @@ local function main() if not pcie.init(config, println) then return end -- start UI - local fp_ok, message = renderer.try_start_ui(pcie.has_pocket_nic(), config.FrontPanelTheme, config.ColorMode) + local fp_ok, message = renderer.try_start_ui(config) if not fp_ok then println_ts(util.c("UI error: ", message)) From 4a38ca7dd119ac94c4350ff5aff073a07dfc60ac Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sat, 28 Jun 2025 17:41:25 +0000 Subject: [PATCH 09/90] #580 nic constructor simplification --- scada-common/network.lua | 8 +++++--- supervisor/pcie.lua | 4 ++-- supervisor/session/svsessions.lua | 1 - 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/scada-common/network.lua b/scada-common/network.lua index cd2cfdd..7483c8e 100644 --- a/scada-common/network.lua +++ b/scada-common/network.lua @@ -4,6 +4,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") +local ppm = require("scada-common.ppm") local util = require("scada-common.util") local md5 = require("lockbox.digest.md5") @@ -77,10 +78,11 @@ end -- NIC: Network Interface Controller
-- utilizes HMAC-MD5 for message authentication, if enabled and this is wireless ----@param iface string peripheral interface name ---@param modem Modem modem to use -function network.nic(iface, modem) +function network.nic(modem) local self = { + -- modem interface name + iface = ppm.get_iface(modem), -- used to quickly return out of tx/rx functions if there is nothing to do connected = true, -- used to avoid costly MAC calculations if not required @@ -195,7 +197,7 @@ function network.nic(iface, modem) function public.receive(side, sender, reply_to, message, distance) local packet = nil - if self.connected and side == iface then + if self.connected and side == self.iface then local s_packet = comms.scada_packet() if self.use_hash then diff --git a/supervisor/pcie.lua b/supervisor/pcie.lua index fab96f0..dea089e 100644 --- a/supervisor/pcie.lua +++ b/supervisor/pcie.lua @@ -76,7 +76,7 @@ function pcie_bus.init(config, println) return false end - bus.wd_nic = network.nic(bus.wired_modem, wired_modem) + bus.wd_nic = network.nic(wired_modem) pcie_bus.nic.cards[bus.wired_modem] = bus.wd_nic end @@ -89,7 +89,7 @@ function pcie_bus.init(config, println) return false end - bus.wl_nic = network.nic(wireless_iface, wireless_modem) + bus.wl_nic = network.nic(wireless_modem) pcie_bus.nic.cards[wireless_iface] = bus.wl_nic end diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index a8c7165..3fae56f 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -6,7 +6,6 @@ local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") local types = require("scada-common.types") local util = require("scada-common.util") -local pcie = require("supervisor.pcie") local databus = require("supervisor.databus") From 9591668f870393490f620c5fd5b99f52b71f6cee Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sat, 28 Jun 2025 17:57:47 +0000 Subject: [PATCH 10/90] #580 supervisor comms config verification --- supervisor/supervisor.lua | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index ec4c92b..6662f62 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -59,6 +59,16 @@ function supervisor.load_config() config.CRD_Timeout = settings.get("CRD_Timeout") config.PKT_Timeout = settings.get("PKT_Timeout") + config.WirelessModem = settings.get("WirelessModem") + config.WiredModem = settings.get("WiredModem") + + config.PLC_Listen = settings.get("PLC_Listen") + config.RTU_Listen = settings.get("RTU_Listen") + config.CRD_Listen = settings.get("CRD_Listen") + + config.PocketEnabled = settings.get("PocketEnabled") + config.PocketTest = settings.get("PocketTest") + config.TrustedRange = settings.get("TrustedRange") config.AuthKey = settings.get("AuthKey") @@ -100,6 +110,19 @@ function supervisor.load_config() cfv.assert_type_num(config.PKT_Timeout) cfv.assert_min(config.PKT_Timeout, 2) + cfv.assert_type_bool(config.WirelessModem) + cfv.assert((config.WiredModem == false) or (type(config.WiredModem) == "string")) + + cfv.assert_type_num(config.PLC_Listen) + cfv.assert_range(config.PLC_Listen, 0, 2) + cfv.assert_type_num(config.RTU_Listen) + cfv.assert_range(config.RTU_Listen, 0, 2) + cfv.assert_type_num(config.CRD_Listen) + cfv.assert_range(config.CRD_Listen, 0, 2) + + cfv.assert_type_bool(config.PocketEnabled) + cfv.assert_type_bool(config.PocketTest) + cfv.assert_type_num(config.TrustedRange) cfv.assert_min(config.TrustedRange, 0) From 2aa5c93404cfcbcb9df1c014292c756352c66e49 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 17 Oct 2025 20:59:36 +0000 Subject: [PATCH 11/90] comment and formatting fixes --- reactor-plc/threads.lua | 2 +- scada-common/network.lua | 4 ++-- scada-common/ppm.lua | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 0de3b7e..f262b4e 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -212,7 +212,7 @@ function threads.thread__main(smem, init) ---@cast device Modem local is_comms_modem = util.trinary(plc_dev.modem_wired, plc_dev.modem_iface == param1, device.isWireless()) - -- note, check init_ok since nic will be nil if it is false + -- note, check init_ok first since nic will be nil if it is false if is_comms_modem and not (plc_state.init_ok and nic.is_connected()) then -- reconnected modem plc_dev.modem = device diff --git a/scada-common/network.lua b/scada-common/network.lua index 7483c8e..279a806 100644 --- a/scada-common/network.lua +++ b/scada-common/network.lua @@ -86,9 +86,9 @@ function network.nic(modem) -- used to quickly return out of tx/rx functions if there is nothing to do connected = true, -- used to avoid costly MAC calculations if not required - use_hash = c_eng.hmac and modem.isWireless(), + use_hash = c_eng.hmac and modem.isWireless(), -- open channels - channels = {} + channels = {} } ---@class nic:Modem diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index a0a1247..d540a34 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -447,7 +447,7 @@ end ---@return table|nil reactor function table function ppm.get_fission_reactor() return ppm.get_device("fissionReactorLogicAdapter") end --- get the named wired modem +-- get a wired modem by name ---@nodiscard ---@param iface string CC peripheral interface ---@return Modem|nil modem function table From 88862726e32fc349a97cff626f3dc49e0f777a4e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 18 Oct 2025 12:37:27 -0400 Subject: [PATCH 12/90] migrate RTU initialization to new file --- rtu/startup.lua | 444 +----------------------------------------------- rtu/uinit.lua | 441 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 448 insertions(+), 437 deletions(-) create mode 100644 rtu/uinit.lua diff --git a/rtu/startup.lua b/rtu/startup.lua index 920b0d9..089053d 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -1,5 +1,5 @@ -- --- RTU: Remote Terminal Unit +-- RTU Gateway: Remote Terminal Unit Gateway -- require("/initenv").init_env() @@ -11,31 +11,17 @@ 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 types = require("scada-common.types") local util = require("scada-common.util") local configure = require("rtu.configure") local databus = require("rtu.databus") -local modbus = require("rtu.modbus") local renderer = require("rtu.renderer") local rtu = require("rtu.rtu") local threads = require("rtu.threads") - -local boilerv_rtu = require("rtu.dev.boilerv_rtu") -local dynamicv_rtu = require("rtu.dev.dynamicv_rtu") -local envd_rtu = require("rtu.dev.envd_rtu") -local imatrix_rtu = require("rtu.dev.imatrix_rtu") -local redstone_rtu = require("rtu.dev.redstone_rtu") -local sna_rtu = require("rtu.dev.sna_rtu") -local sps_rtu = require("rtu.dev.sps_rtu") -local turbinev_rtu = require("rtu.dev.turbinev_rtu") +local uinit = require("rtu.uinit") local RTU_VERSION = "v1.12.3" -local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE -local RTU_HW_STATE = databus.RTU_HW_STATE - local println = util.println local println_ts = util.println_ts @@ -128,439 +114,23 @@ local function main() } } - local smem_sys = __shared_memory.rtu_sys - local smem_dev = __shared_memory.rtu_dev - + local smem_sys = __shared_memory.rtu_sys + local smem_dev = __shared_memory.rtu_dev local rtu_state = __shared_memory.rtu_state + local units = __shared_memory.rtu_sys.units -- get the configured modem if smem_dev.modem_wired then smem_dev.modem = ppm.get_wired_modem(smem_dev.modem_iface) else smem_dev.modem = ppm.get_wireless_modem() end - ---------------------------------------- - -- interpret RTU configs and init units - ---------------------------------------- - - local units = __shared_memory.rtu_sys.units - - local rtu_redstone = config.Redstone - local rtu_devices = config.Peripherals - - -- print and log a fatal error during startup - ---@param msg string - local function log_fail(msg) - println(msg) - log.fatal(msg) - end - - -- get a string representation of a port interface - ---@param entry rtu_rs_definition - ---@return string - local function entry_iface_name(entry) - return util.trinary(entry.color ~= nil, util.c(entry.side, "/", rsio.color_name(entry.color)), entry.side) - end - - -- configure RTU gateway based on settings file definitions - local function sys_config() - --#region Redstone Interfaces - - local rs_rtus = {} ---@type { name: string, hw_state: RTU_HW_STATE, rtu: rtu_rs_device, phy: table, banks: rtu_rs_definition[][] }[] - local all_conns = { [0] = {}, {}, {}, {}, {} } - - -- go through redstone definitions list - for entry_idx = 1, #rtu_redstone do - local entry = rtu_redstone[entry_idx] - - local assignment - local for_reactor = entry.unit - local phy = entry.relay or 0 - local phy_name = entry.relay or "local" - local iface_name = entry_iface_name(entry) - - if util.is_int(entry.unit) and entry.unit > 0 and entry.unit < 5 then - ---@cast for_reactor integer - assignment = "reactor unit " .. entry.unit - elseif entry.unit == nil then - assignment = "facility" - for_reactor = 0 - else - log_fail(util.c("sys_config> invalid unit assignment at block index #", entry_idx)) - return false - end - - -- create the appropriate RTU if it doesn't exist and check relay name validity - if entry.relay then - if type(entry.relay) ~= "string" then - log_fail(util.c("sys_config> invalid redstone relay '", entry.relay, '"')) - return false - elseif not rs_rtus[entry.relay] then - log.debug(util.c("sys_config> allocated relay redstone RTU on interface ", entry.relay)) - - local hw_state = RTU_HW_STATE.OK - local relay = ppm.get_periph(entry.relay) - - if not relay then - hw_state = RTU_HW_STATE.OFFLINE - log.warning(util.c("sys_config> redstone relay ", entry.relay, " is not connected")) - local _, v_device = ppm.mount_virtual() - relay = v_device - elseif ppm.get_type(entry.relay) ~= "redstone_relay" then - hw_state = RTU_HW_STATE.FAULTED - log.warning(util.c("sys_config> redstone relay ", entry.relay, " is not a redstone relay")) - end - - rs_rtus[entry.relay] = { name = entry.relay, hw_state = hw_state, rtu = redstone_rtu.new(relay), phy = relay, banks = { [0] = {}, {}, {}, {}, {} } } - end - elseif rs_rtus[0] == nil then - log.debug(util.c("sys_config> allocated local redstone RTU")) - rs_rtus[0] = { name = "redstone_local", hw_state = RTU_HW_STATE.OK, rtu = redstone_rtu.new(), phy = rs, banks = { [0] = {}, {}, {}, {}, {} } } - end - - -- verify configuration - local valid = false - if rsio.is_valid_port(entry.port) and rsio.is_valid_side(entry.side) then - valid = util.trinary(entry.color == nil, true, rsio.is_color(entry.color)) - end - - local bank = rs_rtus[phy].banks[for_reactor] - local conns = all_conns[for_reactor] - - if not valid then - log_fail(util.c("sys_config> invalid redstone definition at block index #", entry_idx)) - return false - else - -- link redstone in RTU - local mode = rsio.get_io_mode(entry.port) - if mode == rsio.IO_MODE.DIGITAL_IN then - -- can't have duplicate inputs - if util.table_contains(conns, entry.port) then - local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name, " @ ", phy_name) - println(message) - log.warning(message) - else - table.insert(bank, entry) - end - elseif mode == rsio.IO_MODE.ANALOG_IN then - -- can't have duplicate inputs - if util.table_contains(conns, entry.port) then - local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name, " @ ", phy_name) - println(message) - log.warning(message) - else - table.insert(bank, entry) - end - elseif (mode == rsio.IO_MODE.DIGITAL_OUT) or (mode == rsio.IO_MODE.ANALOG_OUT) then - table.insert(bank, entry) - else - -- should be unreachable code, we already validated ports - log.fatal("sys_config> failed to identify IO mode at block index #" .. entry_idx) - println("sys_config> encountered a software error, check logs") - return false - end - - table.insert(conns, entry.port) - - log.debug(util.c("sys_config> banked redstone ", #conns, ": ", rsio.to_string(entry.port), " (", iface_name, " @ ", phy_name, ") for ", assignment)) - end - end - - -- create unit entries for redstone RTUs - for _, def in pairs(rs_rtus) do - local rtu_conns = { [0] = {}, {}, {}, {}, {} } - - -- connect the IO banks - for for_reactor = 0, #def.banks do - local bank = def.banks[for_reactor] - local conns = rtu_conns[for_reactor] - local assign = util.trinary(for_reactor > 0, "reactor unit " .. for_reactor, "the facility") - - -- link redstone to the RTU - for i = 1, #bank do - local conn = bank[i] - local phy_name = conn.relay or "local" - - local mode = rsio.get_io_mode(conn.port) - if mode == rsio.IO_MODE.DIGITAL_IN then - def.rtu.link_di(conn.side, conn.color, conn.invert) - elseif mode == rsio.IO_MODE.DIGITAL_OUT then - def.rtu.link_do(conn.side, conn.color, conn.invert) - elseif mode == rsio.IO_MODE.ANALOG_IN then - def.rtu.link_ai(conn.side) - elseif mode == rsio.IO_MODE.ANALOG_OUT then - def.rtu.link_ao(conn.side) - else - log.fatal(util.c("sys_config> failed to identify IO mode of ", rsio.to_string(conn.port), " (", entry_iface_name(conn), " @ ", phy_name, ") for ", assign)) - println("sys_config> encountered a software error, check logs") - return false - end - - table.insert(conns, conn.port) - - log.debug(util.c("sys_config> linked redstone ", for_reactor, ".", #conns, ": ", rsio.to_string(conn.port), " (", entry_iface_name(conn), ")", " @ ", phy_name, ") for ", assign)) - end - end - - ---@type rtu_registry_entry - local unit = { - uid = 0, - name = def.name, - type = RTU_UNIT_TYPE.REDSTONE, - index = false, - reactor = nil, - device = def.phy, - rs_conns = rtu_conns, - is_multiblock = false, - formed = nil, - hw_state = def.hw_state, - rtu = def.rtu, - modbus_io = modbus.new(def.rtu, false), - pkt_queue = nil, - thread = nil - } - - table.insert(units, unit) - - local type = util.trinary(def.phy == rs, "redstone", "redstone_relay") - - log.info(util.c("sys_config> initialized RTU unit #", #units, ": ", unit.name, " (", type, ")")) - - unit.uid = #units - - databus.tx_unit_hw_status(unit.uid, unit.hw_state) - end - - --#endregion - --#region Mounted Peripherals - - for i = 1, #rtu_devices do - local entry = rtu_devices[i] ---@type rtu_peri_definition - local name = entry.name - local index = entry.index - local for_reactor = util.trinary(entry.unit == nil, 0, entry.unit) - - -- CHECK: name is a string - if type(name) ~= "string" then - log_fail(util.c("sys_config> device entry #", i, ": device ", name, " isn't a string")) - return false - end - - -- CHECK: index type - if (index ~= nil) and (not util.is_int(index)) then - log_fail(util.c("sys_config> device entry #", i, ": index ", index, " isn't valid")) - return false - end - - -- CHECK: index range - local function validate_index(min, max) - if (not util.is_int(index)) or ((index < min) and (max ~= nil and index > max)) then - local message = util.c("sys_config> device entry #", i, ": index ", index, " isn't >= ", min) - if max ~= nil then message = util.c(message, " and <= ", max) end - log_fail(message) - return false - else return true end - end - - -- CHECK: reactor is an integer >= 0 - local function validate_assign(for_facility) - if for_facility and for_reactor ~= 0 then - log_fail(util.c("sys_config> device entry #", i, ": must only be for the facility")) - return false - elseif (not for_facility) and ((not util.is_int(for_reactor)) or (for_reactor < 1) or (for_reactor > 4)) then - log_fail(util.c("sys_config> device entry #", i, ": unit assignment ", for_reactor, " isn't vaild")) - return false - else return true end - end - - local device = ppm.get_periph(name) - - local type ---@type string|nil - local rtu_iface ---@type rtu_device - local rtu_type ---@type RTU_UNIT_TYPE - local is_multiblock = false ---@type boolean - local formed = nil ---@type boolean|nil - local faulted = nil ---@type boolean|nil - - if device == nil then - local message = util.c("sys_config> '", name, "' not found, using placeholder") - println(message) - log.warning(message) - - -- mount a virtual (placeholder) device - type, device = ppm.mount_virtual() - else - type = ppm.get_type(name) - end - - if type == "boilerValve" then - -- boiler multiblock - if not validate_index(1, 2) then return false end - if not validate_assign() then return false end - - rtu_type = RTU_UNIT_TYPE.BOILER_VALVE - rtu_iface, faulted = boilerv_rtu.new(device) - is_multiblock = true - formed = device.isFormed() - - if formed == ppm.ACCESS_FAULT then - println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) - log.warning(util.c("sys_config> failed to check if '", name, "' is a formed boiler multiblock")) - end - elseif type == "turbineValve" then - -- turbine multiblock - if not validate_index(1, 3) then return false end - if not validate_assign() then return false end - - rtu_type = RTU_UNIT_TYPE.TURBINE_VALVE - rtu_iface, faulted = turbinev_rtu.new(device) - is_multiblock = true - formed = device.isFormed() - - if formed == ppm.ACCESS_FAULT then - println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) - log.warning(util.c("sys_config> failed to check if '", name, "' is a formed turbine multiblock")) - end - elseif type == "dynamicValve" then - -- dynamic tank multiblock - if entry.unit == nil then - if not validate_index(1, 4) then return false end - if not validate_assign(true) then return false end - else - if not validate_index(1, 1) then return false end - if not validate_assign() then return false end - end - - rtu_type = RTU_UNIT_TYPE.DYNAMIC_VALVE - rtu_iface, faulted = dynamicv_rtu.new(device) - is_multiblock = true - formed = device.isFormed() - - if formed == ppm.ACCESS_FAULT then - println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) - log.warning(util.c("sys_config> failed to check if '", name, "' is a formed dynamic tank multiblock")) - end - elseif type == "inductionPort" or type == "reinforcedInductionPort" then - -- induction matrix multiblock (normal or reinforced) - if not validate_assign(true) then return false end - - rtu_type = RTU_UNIT_TYPE.IMATRIX - rtu_iface, faulted = imatrix_rtu.new(device) - is_multiblock = true - formed = device.isFormed() - - if formed == ppm.ACCESS_FAULT then - println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) - log.warning(util.c("sys_config> failed to check if '", name, "' is a formed induction matrix multiblock")) - end - elseif type == "spsPort" then - -- SPS multiblock - if not validate_assign(true) then return false end - - rtu_type = RTU_UNIT_TYPE.SPS - rtu_iface, faulted = sps_rtu.new(device) - is_multiblock = true - formed = device.isFormed() - - if formed == ppm.ACCESS_FAULT then - println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) - log.warning(util.c("sys_config> failed to check if '", name, "' is a formed SPS multiblock")) - end - elseif type == "solarNeutronActivator" then - -- SNA - if not validate_assign() then return false end - - rtu_type = RTU_UNIT_TYPE.SNA - rtu_iface, faulted = sna_rtu.new(device) - elseif type == "environmentDetector" or type == "environment_detector" then - -- advanced peripherals environment detector - if not validate_index(1) then return false end - if not validate_assign(entry.unit == nil) then return false end - - rtu_type = RTU_UNIT_TYPE.ENV_DETECTOR - rtu_iface, faulted = envd_rtu.new(device) - elseif type == ppm.VIRTUAL_DEVICE_TYPE then - -- placeholder device - rtu_type = RTU_UNIT_TYPE.VIRTUAL - rtu_iface = rtu.init_unit().interface() - else - log_fail(util.c("sys_config> device '", name, "' is not a known type (", type, ")")) - return false - end - - if is_multiblock then - if not formed then - if formed == false then - log.info(util.c("sys_config> device '", name, "' is not formed")) - else formed = false end - elseif faulted then - -- sometimes there is a race condition on server boot where it reports formed, but - -- the other functions are not yet defined (that's the theory at least). mark as unformed to attempt connection later - formed = false - log.warning(util.c("sys_config> device '", name, "' is formed, but initialization had one or more faults: marked as unformed")) - end - end - - ---@class rtu_registry_entry - local rtu_unit = { - uid = 0, ---@type integer RTU unit ID - name = name, ---@type string unit name - type = rtu_type, ---@type RTU_UNIT_TYPE unit type - index = index or false, ---@type integer|false device index - reactor = for_reactor, ---@type integer|nil unit/facility assignment - device = device, ---@type table peripheral reference - rs_conns = nil, ---@type IO_PORT[][]|nil available redstone connections - is_multiblock = is_multiblock, ---@type boolean if this is for a multiblock peripheral - formed = formed, ---@type boolean|nil if this peripheral is currently formed - hw_state = RTU_HW_STATE.OFFLINE, ---@type RTU_HW_STATE hardware device status - rtu = rtu_iface, ---@type rtu_device|rtu_rs_device RTU hardware interface - modbus_io = modbus.new(rtu_iface, true), ---@type modbus MODBUS interface - pkt_queue = mqueue.new(), ---@type mqueue|nil packet queue - thread = nil ---@type parallel_thread|nil associated RTU thread - } - - rtu_unit.thread = threads.thread__unit_comms(__shared_memory, rtu_unit) - - table.insert(units, rtu_unit) - - local for_message = "the facility" - if for_reactor > 0 then - for_message = util.c("reactor ", for_reactor) - end - - local index_str = util.trinary(index ~= nil, util.c(" [", index, "]"), "") - log.info(util.c("sys_config> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ")", index_str, " for ", for_message)) - - rtu_unit.uid = #units - - -- determine hardware status - if rtu_unit.type == RTU_UNIT_TYPE.VIRTUAL then - rtu_unit.hw_state = RTU_HW_STATE.OFFLINE - else - if rtu_unit.is_multiblock then - rtu_unit.hw_state = util.trinary(rtu_unit.formed == true, RTU_HW_STATE.OK, RTU_HW_STATE.UNFORMED) - elseif faulted then - rtu_unit.hw_state = RTU_HW_STATE.FAULTED - else - rtu_unit.hw_state = RTU_HW_STATE.OK - end - end - - -- report hardware status - databus.tx_unit_hw_status(rtu_unit.uid, rtu_unit.hw_state) - end - - --#endregion - - return true - end - ---------------------------------------- -- start system ---------------------------------------- - log.debug("boot> running sys_config()") + log.debug("boot> running uinit()") - if sys_config() then + if uinit(config, __shared_memory) then -- check comms modem if smem_dev.modem == nil then println("startup> comms modem not found") diff --git a/rtu/uinit.lua b/rtu/uinit.lua new file mode 100644 index 0000000..8892c2e --- /dev/null +++ b/rtu/uinit.lua @@ -0,0 +1,441 @@ +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local ppm = require("scada-common.ppm") +local rsio = require("scada-common.rsio") +local types = require("scada-common.types") +local util = require("scada-common.util") + +local databus = require("rtu.databus") +local modbus = require("rtu.modbus") +local rtu = require("rtu.rtu") +local threads = require("rtu.threads") + +local boilerv_rtu = require("rtu.dev.boilerv_rtu") +local dynamicv_rtu = require("rtu.dev.dynamicv_rtu") +local envd_rtu = require("rtu.dev.envd_rtu") +local imatrix_rtu = require("rtu.dev.imatrix_rtu") +local redstone_rtu = require("rtu.dev.redstone_rtu") +local sna_rtu = require("rtu.dev.sna_rtu") +local sps_rtu = require("rtu.dev.sps_rtu") +local turbinev_rtu = require("rtu.dev.turbinev_rtu") + +local println = util.println +local println_ts = util.println_ts + +local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE +local RTU_HW_STATE = databus.RTU_HW_STATE + +-- print and log a fatal error during startup +---@param msg string +local function log_fail(msg) + println(msg) + log.fatal(msg) +end + +-- get a string representation of a port interface +---@param entry rtu_rs_definition +---@return string +local function entry_iface_name(entry) + return util.trinary(entry.color ~= nil, util.c(entry.side, "/", rsio.color_name(entry.color)), entry.side) +end + +-- configure RTU gateway based on settings file definitions +---@param config rtu_config +---@param __shared_memory rtu_shared_memory +---@return boolean success +return function(config, __shared_memory) + local units = __shared_memory.rtu_sys.units + + local rtu_redstone = config.Redstone + local rtu_devices = config.Peripherals + + --#region Redstone Interfaces + + local rs_rtus = {} ---@type { name: string, hw_state: RTU_HW_STATE, rtu: rtu_rs_device, phy: table, banks: rtu_rs_definition[][] }[] + local all_conns = { [0] = {}, {}, {}, {}, {} } + + -- go through redstone definitions list + for entry_idx = 1, #rtu_redstone do + local entry = rtu_redstone[entry_idx] + + local assignment + local for_reactor = entry.unit + local phy = entry.relay or 0 + local phy_name = entry.relay or "local" + local iface_name = entry_iface_name(entry) + + if util.is_int(entry.unit) and entry.unit > 0 and entry.unit < 5 then + ---@cast for_reactor integer + assignment = "reactor unit " .. entry.unit + elseif entry.unit == nil then + assignment = "facility" + for_reactor = 0 + else + log_fail(util.c("uinit> invalid unit assignment at block index #", entry_idx)) + return false + end + + -- create the appropriate RTU if it doesn't exist and check relay name validity + if entry.relay then + if type(entry.relay) ~= "string" then + log_fail(util.c("uinit> invalid redstone relay '", entry.relay, '"')) + return false + elseif not rs_rtus[entry.relay] then + log.debug(util.c("uinit> allocated relay redstone RTU on interface ", entry.relay)) + + local hw_state = RTU_HW_STATE.OK + local relay = ppm.get_periph(entry.relay) + + if not relay then + hw_state = RTU_HW_STATE.OFFLINE + log.warning(util.c("uinit> redstone relay ", entry.relay, " is not connected")) + local _, v_device = ppm.mount_virtual() + relay = v_device + elseif ppm.get_type(entry.relay) ~= "redstone_relay" then + hw_state = RTU_HW_STATE.FAULTED + log.warning(util.c("uinit> redstone relay ", entry.relay, " is not a redstone relay")) + end + + rs_rtus[entry.relay] = { name = entry.relay, hw_state = hw_state, rtu = redstone_rtu.new(relay), phy = relay, banks = { [0] = {}, {}, {}, {}, {} } } + end + elseif rs_rtus[0] == nil then + log.debug(util.c("uinit> allocated local redstone RTU")) + rs_rtus[0] = { name = "redstone_local", hw_state = RTU_HW_STATE.OK, rtu = redstone_rtu.new(), phy = rs, banks = { [0] = {}, {}, {}, {}, {} } } + end + + -- verify configuration + local valid = false + if rsio.is_valid_port(entry.port) and rsio.is_valid_side(entry.side) then + valid = util.trinary(entry.color == nil, true, rsio.is_color(entry.color)) + end + + local bank = rs_rtus[phy].banks[for_reactor] + local conns = all_conns[for_reactor] + + if not valid then + log_fail(util.c("uinit> invalid redstone definition at block index #", entry_idx)) + return false + else + -- link redstone in RTU + local mode = rsio.get_io_mode(entry.port) + if mode == rsio.IO_MODE.DIGITAL_IN then + -- can't have duplicate inputs + if util.table_contains(conns, entry.port) then + local message = util.c("uinit> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name, " @ ", phy_name) + println(message) + log.warning(message) + else + table.insert(bank, entry) + end + elseif mode == rsio.IO_MODE.ANALOG_IN then + -- can't have duplicate inputs + if util.table_contains(conns, entry.port) then + local message = util.c("uinit> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name, " @ ", phy_name) + println(message) + log.warning(message) + else + table.insert(bank, entry) + end + elseif (mode == rsio.IO_MODE.DIGITAL_OUT) or (mode == rsio.IO_MODE.ANALOG_OUT) then + table.insert(bank, entry) + else + -- should be unreachable code, we already validated ports + log.fatal("uinit> failed to identify IO mode at block index #" .. entry_idx) + println("uinit> encountered a software error, check logs") + return false + end + + table.insert(conns, entry.port) + + log.debug(util.c("uinit> banked redstone ", #conns, ": ", rsio.to_string(entry.port), " (", iface_name, " @ ", phy_name, ") for ", assignment)) + end + end + + -- create unit entries for redstone RTUs + for _, def in pairs(rs_rtus) do + local rtu_conns = { [0] = {}, {}, {}, {}, {} } + + -- connect the IO banks + for for_reactor = 0, #def.banks do + local bank = def.banks[for_reactor] + local conns = rtu_conns[for_reactor] + local assign = util.trinary(for_reactor > 0, "reactor unit " .. for_reactor, "the facility") + + -- link redstone to the RTU + for i = 1, #bank do + local conn = bank[i] + local phy_name = conn.relay or "local" + + local mode = rsio.get_io_mode(conn.port) + if mode == rsio.IO_MODE.DIGITAL_IN then + def.rtu.link_di(conn.side, conn.color, conn.invert) + elseif mode == rsio.IO_MODE.DIGITAL_OUT then + def.rtu.link_do(conn.side, conn.color, conn.invert) + elseif mode == rsio.IO_MODE.ANALOG_IN then + def.rtu.link_ai(conn.side) + elseif mode == rsio.IO_MODE.ANALOG_OUT then + def.rtu.link_ao(conn.side) + else + log.fatal(util.c("uinit> failed to identify IO mode of ", rsio.to_string(conn.port), " (", entry_iface_name(conn), " @ ", phy_name, ") for ", assign)) + println("uinit> encountered a software error, check logs") + return false + end + + table.insert(conns, conn.port) + + log.debug(util.c("uinit> linked redstone ", for_reactor, ".", #conns, ": ", rsio.to_string(conn.port), " (", entry_iface_name(conn), ")", " @ ", phy_name, ") for ", assign)) + end + end + + ---@type rtu_registry_entry + local unit = { + uid = 0, + name = def.name, + type = RTU_UNIT_TYPE.REDSTONE, + index = false, + reactor = nil, + device = def.phy, + rs_conns = rtu_conns, + is_multiblock = false, + formed = nil, + hw_state = def.hw_state, + rtu = def.rtu, + modbus_io = modbus.new(def.rtu, false), + pkt_queue = nil, + thread = nil + } + + table.insert(units, unit) + + local type = util.trinary(def.phy == rs, "redstone", "redstone_relay") + + log.info(util.c("uinit> initialized RTU unit #", #units, ": ", unit.name, " (", type, ")")) + + unit.uid = #units + + databus.tx_unit_hw_status(unit.uid, unit.hw_state) + end + + --#endregion + --#region Mounted Peripherals + + for i = 1, #rtu_devices do + local entry = rtu_devices[i] ---@type rtu_peri_definition + local name = entry.name + local index = entry.index + local for_reactor = util.trinary(entry.unit == nil, 0, entry.unit) + + -- CHECK: name is a string + if type(name) ~= "string" then + log_fail(util.c("uinit> device entry #", i, ": device ", name, " isn't a string")) + return false + end + + -- CHECK: index type + if (index ~= nil) and (not util.is_int(index)) then + log_fail(util.c("uinit> device entry #", i, ": index ", index, " isn't valid")) + return false + end + + -- CHECK: index range + local function validate_index(min, max) + if (not util.is_int(index)) or ((index < min) and (max ~= nil and index > max)) then + local message = util.c("uinit> device entry #", i, ": index ", index, " isn't >= ", min) + if max ~= nil then message = util.c(message, " and <= ", max) end + log_fail(message) + return false + else return true end + end + + -- CHECK: reactor is an integer >= 0 + local function validate_assign(for_facility) + if for_facility and for_reactor ~= 0 then + log_fail(util.c("uinit> device entry #", i, ": must only be for the facility")) + return false + elseif (not for_facility) and ((not util.is_int(for_reactor)) or (for_reactor < 1) or (for_reactor > 4)) then + log_fail(util.c("uinit> device entry #", i, ": unit assignment ", for_reactor, " isn't vaild")) + return false + else return true end + end + + local device = ppm.get_periph(name) + + local type ---@type string|nil + local rtu_iface ---@type rtu_device + local rtu_type ---@type RTU_UNIT_TYPE + local is_multiblock = false ---@type boolean + local formed = nil ---@type boolean|nil + local faulted = nil ---@type boolean|nil + + if device == nil then + local message = util.c("uinit> '", name, "' not found, using placeholder") + println(message) + log.warning(message) + + -- mount a virtual (placeholder) device + type, device = ppm.mount_virtual() + else + type = ppm.get_type(name) + end + + if type == "boilerValve" then + -- boiler multiblock + if not validate_index(1, 2) then return false end + if not validate_assign() then return false end + + rtu_type = RTU_UNIT_TYPE.BOILER_VALVE + rtu_iface, faulted = boilerv_rtu.new(device) + is_multiblock = true + formed = device.isFormed() + + if formed == ppm.ACCESS_FAULT then + println_ts(util.c("uinit> failed to check if '", name, "' is formed")) + log.warning(util.c("uinit> failed to check if '", name, "' is a formed boiler multiblock")) + end + elseif type == "turbineValve" then + -- turbine multiblock + if not validate_index(1, 3) then return false end + if not validate_assign() then return false end + + rtu_type = RTU_UNIT_TYPE.TURBINE_VALVE + rtu_iface, faulted = turbinev_rtu.new(device) + is_multiblock = true + formed = device.isFormed() + + if formed == ppm.ACCESS_FAULT then + println_ts(util.c("uinit> failed to check if '", name, "' is formed")) + log.warning(util.c("uinit> failed to check if '", name, "' is a formed turbine multiblock")) + end + elseif type == "dynamicValve" then + -- dynamic tank multiblock + if entry.unit == nil then + if not validate_index(1, 4) then return false end + if not validate_assign(true) then return false end + else + if not validate_index(1, 1) then return false end + if not validate_assign() then return false end + end + + rtu_type = RTU_UNIT_TYPE.DYNAMIC_VALVE + rtu_iface, faulted = dynamicv_rtu.new(device) + is_multiblock = true + formed = device.isFormed() + + if formed == ppm.ACCESS_FAULT then + println_ts(util.c("uinit> failed to check if '", name, "' is formed")) + log.warning(util.c("uinit> failed to check if '", name, "' is a formed dynamic tank multiblock")) + end + elseif type == "inductionPort" or type == "reinforcedInductionPort" then + -- induction matrix multiblock (normal or reinforced) + if not validate_assign(true) then return false end + + rtu_type = RTU_UNIT_TYPE.IMATRIX + rtu_iface, faulted = imatrix_rtu.new(device) + is_multiblock = true + formed = device.isFormed() + + if formed == ppm.ACCESS_FAULT then + println_ts(util.c("uinit> failed to check if '", name, "' is formed")) + log.warning(util.c("uinit> failed to check if '", name, "' is a formed induction matrix multiblock")) + end + elseif type == "spsPort" then + -- SPS multiblock + if not validate_assign(true) then return false end + + rtu_type = RTU_UNIT_TYPE.SPS + rtu_iface, faulted = sps_rtu.new(device) + is_multiblock = true + formed = device.isFormed() + + if formed == ppm.ACCESS_FAULT then + println_ts(util.c("uinit> failed to check if '", name, "' is formed")) + log.warning(util.c("uinit> failed to check if '", name, "' is a formed SPS multiblock")) + end + elseif type == "solarNeutronActivator" then + -- SNA + if not validate_assign() then return false end + + rtu_type = RTU_UNIT_TYPE.SNA + rtu_iface, faulted = sna_rtu.new(device) + elseif type == "environmentDetector" or type == "environment_detector" then + -- advanced peripherals environment detector + if not validate_index(1) then return false end + if not validate_assign(entry.unit == nil) then return false end + + rtu_type = RTU_UNIT_TYPE.ENV_DETECTOR + rtu_iface, faulted = envd_rtu.new(device) + elseif type == ppm.VIRTUAL_DEVICE_TYPE then + -- placeholder device + rtu_type = RTU_UNIT_TYPE.VIRTUAL + rtu_iface = rtu.init_unit().interface() + else + log_fail(util.c("uinit> device '", name, "' is not a known type (", type, ")")) + return false + end + + if is_multiblock then + if not formed then + if formed == false then + log.info(util.c("uinit> device '", name, "' is not formed")) + else formed = false end + elseif faulted then + -- sometimes there is a race condition on server boot where it reports formed, but + -- the other functions are not yet defined (that's the theory at least). mark as unformed to attempt connection later + formed = false + log.warning(util.c("uinit> device '", name, "' is formed, but initialization had one or more faults: marked as unformed")) + end + end + + ---@class rtu_registry_entry + local rtu_unit = { + uid = 0, ---@type integer RTU unit ID + name = name, ---@type string unit name + type = rtu_type, ---@type RTU_UNIT_TYPE unit type + index = index or false, ---@type integer|false device index + reactor = for_reactor, ---@type integer|nil unit/facility assignment + device = device, ---@type table peripheral reference + rs_conns = nil, ---@type IO_PORT[][]|nil available redstone connections + is_multiblock = is_multiblock, ---@type boolean if this is for a multiblock peripheral + formed = formed, ---@type boolean|nil if this peripheral is currently formed + hw_state = RTU_HW_STATE.OFFLINE, ---@type RTU_HW_STATE hardware device status + rtu = rtu_iface, ---@type rtu_device|rtu_rs_device RTU hardware interface + modbus_io = modbus.new(rtu_iface, true), ---@type modbus MODBUS interface + pkt_queue = mqueue.new(), ---@type mqueue|nil packet queue + thread = nil ---@type parallel_thread|nil associated RTU thread + } + + rtu_unit.thread = threads.thread__unit_comms(__shared_memory, rtu_unit) + + table.insert(units, rtu_unit) + + local for_message = "the facility" + if for_reactor > 0 then + for_message = util.c("reactor ", for_reactor) + end + + local index_str = util.trinary(index ~= nil, util.c(" [", index, "]"), "") + log.info(util.c("uinit> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ")", index_str, " for ", for_message)) + + rtu_unit.uid = #units + + -- determine hardware status + if rtu_unit.type == RTU_UNIT_TYPE.VIRTUAL then + rtu_unit.hw_state = RTU_HW_STATE.OFFLINE + else + if rtu_unit.is_multiblock then + rtu_unit.hw_state = util.trinary(rtu_unit.formed == true, RTU_HW_STATE.OK, RTU_HW_STATE.UNFORMED) + elseif faulted then + rtu_unit.hw_state = RTU_HW_STATE.FAULTED + else + rtu_unit.hw_state = RTU_HW_STATE.OK + end + end + + -- report hardware status + databus.tx_unit_hw_status(rtu_unit.uid, rtu_unit.hw_state) + end + + --#endregion + + return true +end From 194a266730c93489502ce2159ba40e7fca29e986 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 18 Oct 2025 14:44:33 -0400 Subject: [PATCH 13/90] #580 RTU gateway backplane --- rtu/backplane.lua | 268 ++++++++++++++++++++++++++++++++++++++++++++++ rtu/configure.lua | 54 +++++----- rtu/rtu.lua | 75 ++++++++++--- rtu/startup.lua | 79 +++++--------- rtu/threads.lua | 72 ++----------- 5 files changed, 389 insertions(+), 159 deletions(-) create mode 100644 rtu/backplane.lua diff --git a/rtu/backplane.lua b/rtu/backplane.lua new file mode 100644 index 0000000..78b6ac2 --- /dev/null +++ b/rtu/backplane.lua @@ -0,0 +1,268 @@ +-- +-- RTU Gateway 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("rtu.databus") +local rtu = require("rtu.rtu") + +---@class rtu_backplane +local backplane = {} + +local _bp = { + smem = nil, ---@type rtu_shared_memory + + wlan_en = true, + wlan_pref = true, + lan_en = false, + lan_iface = "", + + act_nic = nil, ---@type nic|nil + wl_act = true, + wd_nic = nil, ---@type nic|nil + wl_nic = nil, ---@type nic|nil + + sounders = {} ---@type rtu_speaker_sounder[] +} + +-- initialize the system peripheral backplane +---@param config rtu_config +---@param __shared_memory rtu_shared_memory +function backplane.init(config, __shared_memory) + _bp.smem = __shared_memory + _bp.wlan_en = config.WirelessModem + _bp.wlan_pref = config.PreferWireless + _bp.lan_en = type(config.WiredModem) == "string" + _bp.lan_iface = config.WiredModem + + -- init wired NIC + if _bp.lan_en then + local modem = ppm.get_wired_modem(_bp.lan_iface) + + if modem then + _bp.wd_nic = network.nic(modem) + log.info("BKPLN: WIRED PHY_UP " .. _bp.lan_iface) + end + end + + -- init wireless NIC(s) + if _bp.wlan_en then + local modem, iface = ppm.get_wireless_modem() + + if modem then + _bp.wl_nic = network.nic(modem) + log.info("BKPLN: WIRELESS PHY_UP " .. iface) + end + end + + -- grab the preferred active NIC + if _bp.wlan_pref then + _bp.wl_act = true + _bp.act_nic = _bp.wl_nics[1] + else + _bp.wl_act = false + _bp.act_nic = _bp.wd_nic + end + + databus.tx_hw_modem(_bp.act_nic ~= nil) + + -- find and setup all speakers + local speakers = ppm.get_all_devices("speaker") + for _, s in pairs(speakers) do + local sounder = rtu.init_sounder(s) + + table.insert(_bp.sounders, sounder) + + log.debug(util.c("BKPLN: added speaker, attached as ", sounder.name)) + end + + databus.tx_hw_spkr_count(#_bp.sounders) +end + +-- get the active NIC +---@return nic|nil +function backplane.active_nic() return _bp.act_nic end + +-- get the sounder interfaces +---@return rtu_speaker_sounder[] +function backplane.sounders() return _bp.sounders end + +-- handle a backplane peripheral detach +---@param type string +---@param device table +---@param iface string +function backplane.detach(type, device, iface) + local function println_ts(message) if not _bp.smem.rtu_state.fp_ok then util.println_ts(message) end end + + local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic + + local comms = _bp.smem.rtu_sys.rtu_comms + + if type == "modem" then + ---@cast device Modem + + local was_active = _bp.act_nic and _bp.act_nic.is_modem(device) + local was_wd = wd_nic and wd_nic.is_modem(device) + local was_wl = wl_nic and wl_nic.is_modem(device) + + if wd_nic and was_wd then + log.info("BKPLN: WIRED PHY_DOWN " .. iface) + wd_nic.disconnect() + elseif wl_nic and was_wl then + log.info("BKPLN: WIRELESS PHY_DOWN " .. iface) + wl_nic.disconnect() + end + + -- we only care if this is our active comms modem + if was_active then + println_ts("active comms modem disconnected!") + log.warning("active comms modem disconnected") + + -- failover and try to find a new comms modem + if _bp.wl_act then + -- try to find another wireless modem, otherwise switch to wired + local other_modem = ppm.get_wireless_modem() + if other_modem then + log.info("found another wireless modem, using it for comms") + + -- note: must assign to self.wl_nic if creating a nic, otherwise it only changes locally + if wl_nic then + wl_nic.connect(other_modem) + else _bp.wl_nic = network.nic(other_modem) end + + log.info("BKPLN: WIRELESS PHY_UP " .. iface) + + _bp.act_nic = wl_nic + comms.assign_nic(_bp.act_nic) + log.info("BKPLN: switched comms to new wireless modem") + elseif wd_nic and wd_nic.is_connected() then + _bp.wl_act = false + _bp.act_nic = _bp.wd_nic + + comms.assign_nic(_bp.act_nic) + log.info("BKPLN: switched comms to wired modem") + else + _bp.act_nic = nil + databus.tx_hw_modem(false) + comms.unassign_nic() + end + else + -- switch to wireless if able + if wl_nic then + _bp.wl_act = true + _bp.act_nic = wl_nic + + comms.assign_nic(_bp.act_nic) + log.info("BKPLN: switched comms to wireless modem") + else + _bp.act_nic = nil + databus.tx_hw_modem(false) + comms.unassign_nic() + end + end + else + log.warning("modem disconnected") + end + elseif type == "speaker" then + ---@cast device Speaker + for i = 1, #_bp.sounders do + if _bp.sounders[i].speaker == device then + table.remove(_bp.sounders, i) + + log.warning(util.c("speaker ", iface, " disconnected")) + println_ts("speaker disconnected") + + databus.tx_hw_spkr_count(#_bp.sounders) + break + end + end + end +end + +-- handle a backplane peripheral attach +---@param type string +---@param device table +---@param iface string +function backplane.attach(type, device, iface) + local function println_ts(message) if not _bp.smem.rtu_state.fp_ok then util.println_ts(message) end end + + local comms = _bp.smem.rtu_sys.rtu_comms + + if type == "modem" then + ---@cast device Modem + + local is_wd = _bp.lan_iface == iface + local is_wl = ((not _bp.wl_nic) or (not _bp.wl_nic.is_connected())) and device.isWireless() + + if is_wd then + -- connect this as the wired NIC + if _bp.wd_nic then + _bp.wd_nic.connect(device) + else _bp.wd_nic = network.nic(device) end + + log.info("BKPLN: WIRED PHY_UP " .. iface) + + if _bp.act_nic == nil then + -- set as active + _bp.wl_act = false + _bp.act_nic = _bp.wd_nic + + comms.assign_nic(_bp.act_nic) + databus.tx_hw_modem(true) + println_ts("comms modem reconnected.") + log.info("BKPLN: switched comms to wired modem") + 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 + + comms.assign_nic(_bp.act_nic) + log.info("BKPLN: switched comms to wired modem (preferred)") + end + elseif is_wl then + -- connect this as the wireless NIC + if _bp.wl_nic then + _bp.wl_nic.connect(device) + else _bp.wl_nic = network.nic(device) end + + log.info("BKPLN: WIRELESS PHY_UP " .. iface) + + if _bp.act_nic == nil then + -- set as active + _bp.wl_act = true + _bp.act_nic = _bp.wl_nic + + comms.assign_nic(_bp.act_nic) + databus.tx_hw_modem(true) + println_ts("comms modem reconnected.") + log.info("BKPLN: switched comms to wireless modem") + 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 + + comms.assign_nic(_bp.act_nic) + log.info("BKPLN: switched comms to wireless modem (preferred)") + end + elseif device.isWireless() then + -- the wireless NIC already has a modem + log.info("standby wireless modem connected") + else + log.info("wired modem connected") + end + elseif type == "speaker" then + ---@cast device Speaker + table.insert(_bp.sounders, rtu.init_sounder(device)) + + println_ts("speaker connected") + log.info(util.c("connected speaker ", iface)) + + databus.tx_hw_spkr_count(#_bp.sounders) + end +end + +return backplane diff --git a/rtu/configure.lua b/rtu/configure.lua index 2925841..1333b2b 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -64,40 +64,42 @@ local tool_ctl = { viewing_config = false, jumped_to_color = false, - view_gw_cfg = nil, ---@type PushButton - dev_cfg = nil, ---@type PushButton - rs_cfg = nil, ---@type PushButton - color_cfg = nil, ---@type PushButton - color_next = nil, ---@type PushButton - color_apply = nil, ---@type PushButton - settings_apply = nil, ---@type PushButton - settings_confirm = nil, ---@type PushButton + view_gw_cfg = nil, ---@type PushButton + dev_cfg = nil, ---@type PushButton + rs_cfg = nil, ---@type PushButton + color_cfg = nil, ---@type PushButton + color_next = nil, ---@type PushButton + color_apply = nil, ---@type PushButton + settings_apply = nil, ---@type PushButton + settings_confirm = nil, ---@type PushButton - go_home = nil, ---@type function - gen_summary = nil, ---@type function - load_legacy = nil, ---@type function - update_peri_list = nil, ---@type function - update_relay_list = nil, ---@type function - gen_peri_summary = nil, ---@type function - gen_rs_summary = nil, ---@type function + go_home = nil, ---@type function + gen_summary = nil, ---@type function + load_legacy = nil, ---@type function + update_peri_list = nil, ---@type function + update_relay_list = nil, ---@type function + gen_peri_summary = nil, ---@type function + gen_rs_summary = nil, ---@type function } ---@class rtu_config local tmp_cfg = { SpeakerVolume = 1.0, - Peripherals = {}, ---@type rtu_peri_definition[] - Redstone = {}, ---@type rtu_rs_definition[] - SVR_Channel = nil, ---@type integer - RTU_Channel = nil, ---@type integer - ConnTimeout = nil, ---@type number - WiredModem = false, ---@type string|false - TrustedRange = nil, ---@type number - AuthKey = nil, ---@type string - LogMode = 0, ---@type LOG_MODE + Peripherals = {}, ---@type rtu_peri_definition[] + Redstone = {}, ---@type rtu_rs_definition[] + SVR_Channel = nil, ---@type integer + RTU_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 + LogMode = 0, ---@type LOG_MODE LogPath = "", LogDebug = false, - FrontPanelTheme = 1, ---@type FP_THEME - ColorMode = 1 ---@type COLOR_MODE + FrontPanelTheme = 1, ---@type FP_THEME + ColorMode = 1 ---@type COLOR_MODE } ---@class rtu_config diff --git a/rtu/rtu.lua b/rtu/rtu.lua index a203518..49984aa 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -36,7 +36,9 @@ function rtu.load_config() config.SVR_Channel = settings.get("SVR_Channel") config.RTU_Channel = settings.get("RTU_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") @@ -62,7 +64,9 @@ function rtu.validate_config(cfg) cfv.assert_channel(cfg.RTU_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_type_bool(cfg.PreferWireless) cfv.assert_type_num(cfg.TrustedRange) cfv.assert_min(cfg.TrustedRange, 0) cfv.assert_type_str(cfg.AuthKey) @@ -288,7 +292,7 @@ end -- RTU Communications ---@nodiscard ---@param version string RTU version ----@param nic nic network interface device +---@param nic nic|nil network interface device ---@param conn_watchdog watchdog watchdog reference function rtu.comms(version, nic, conn_watchdog) local self = { @@ -301,30 +305,43 @@ function rtu.comms(version, nic, conn_watchdog) local insert = table.insert - if nic.isWireless() then - comms.set_trusted_range(config.TrustedRange) - end + -- CONDITIONAL PRIVATE FUNCTIONS -- - -- PRIVATE FUNCTIONS -- - - -- configure modem channels - nic.closeAll() - nic.open(config.RTU_Channel) + -- these don't check for nic to be nil to save execution time on functions called extremely often + -- when the nic isn't present, the aliases _send and _send_modbus are cleared -- send a scada management packet ---@param msg_type MGMT_TYPE ---@param msg table - local function _send(msg_type, msg) + local function _nic_send(msg_type, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() m_pkt.make(msg_type, msg) s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) +---@diagnostic disable-next-line: need-check-nil nic.transmit(config.SVR_Channel, config.RTU_Channel, s_pkt) self.seq_num = self.seq_num + 1 end + -- send a MODBUS TCP packet + ---@param m_pkt modbus_packet + local function _nic_send_modbus(m_pkt) + local s_pkt = comms.scada_packet() + + s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable()) + +---@diagnostic disable-next-line: need-check-nil + nic.transmit(config.SVR_Channel, config.RTU_Channel, s_pkt) + self.seq_num = self.seq_num + 1 + end + + -- PRIVATE FUNCTIONS -- + + -- send a scada management packet + local _send = _nic_send + -- keep alive ack ---@param srv_time integer local function _send_keep_alive_ack(srv_time) @@ -355,13 +372,7 @@ function rtu.comms(version, nic, conn_watchdog) local public = {} -- send a MODBUS TCP packet - ---@param m_pkt modbus_packet - function public.send_modbus(m_pkt) - local s_pkt = comms.scada_packet() - s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable()) - nic.transmit(config.SVR_Channel, config.RTU_Channel, s_pkt) - self.seq_num = self.seq_num + 1 - end + public.send_modbus = _nic_send_modbus -- unlink from the server ---@param rtu_state rtu_state @@ -408,6 +419,8 @@ function rtu.comms(version, nic, conn_watchdog) ---@param distance integer ---@return modbus_frame|mgmt_frame|nil packet function public.parse_packet(side, sender, reply_to, message, distance) + -- unreachable if there isn't a nic +---@diagnostic disable-next-line: need-check-nil local s_pkt = nic.receive(side, sender, reply_to, message, distance) local pkt = nil @@ -598,6 +611,34 @@ function rtu.comms(version, nic, conn_watchdog) end end + -- set the current NIC + ---@param _nic nic + function public.assign_nic(_nic) + if nic then nic.closeAll() end + + if _nic.isWireless() then + comms.set_trusted_range(config.TrustedRange) + end + + -- configure receive channels + _nic.closeAll() + _nic.open(config.RTU_Channel) + + nic = _nic + _send = _nic_send + public.send_modbus = _nic_send_modbus + end + + -- clear the current NIC + function public.unassign_nic() + _send = function () end + public.send_modbus = function () end + nic = nil + end + + -- set the NIC if one was given + if nic then public.assign_nic(nic) else public.unassign_nic() end + return public end diff --git a/rtu/startup.lua b/rtu/startup.lua index 089053d..649750d 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -4,23 +4,24 @@ require("/initenv").init_env() -local audio = require("scada-common.audio") -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 audio = require("scada-common.audio") +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 configure = require("rtu.configure") -local databus = require("rtu.databus") -local renderer = require("rtu.renderer") -local rtu = require("rtu.rtu") -local threads = require("rtu.threads") -local uinit = require("rtu.uinit") +local backplane = require("rtu.backplane") +local configure = require("rtu.configure") +local databus = require("rtu.databus") +local renderer = require("rtu.renderer") +local rtu = require("rtu.rtu") +local threads = require("rtu.threads") +local uinit = require("rtu.uinit") -local RTU_VERSION = "v1.12.3" +local RTU_VERSION = "v1.13.0" local println = util.println local println_ts = util.println_ts @@ -92,17 +93,9 @@ local function main() shutdown = false }, - -- RTU gateway devices (not RTU units) - rtu_dev = { - modem_wired = type(config.WiredModem) == "string", - modem_iface = config.WiredModem, - modem = nil, - sounders = {} ---@type rtu_speaker_sounder[] - }, - -- system objects + ---@class rtu_sys rtu_sys = { - nic = nil, ---@type nic rtu_comms = nil, ---@type rtu_comms conn_watchdog = nil, ---@type watchdog units = {} ---@type rtu_registry_entry[] @@ -115,15 +108,9 @@ local function main() } local smem_sys = __shared_memory.rtu_sys - local smem_dev = __shared_memory.rtu_dev local rtu_state = __shared_memory.rtu_state local units = __shared_memory.rtu_sys.units - -- get the configured modem - if smem_dev.modem_wired then - smem_dev.modem = ppm.get_wired_modem(smem_dev.modem_iface) - else smem_dev.modem = ppm.get_wireless_modem() end - ---------------------------------------- -- start system ---------------------------------------- @@ -131,26 +118,8 @@ local function main() log.debug("boot> running uinit()") if uinit(config, __shared_memory) then - -- check comms modem - if smem_dev.modem == nil then - println("startup> comms modem not found") - log.fatal("no comms modem on startup") - return - end - - databus.tx_hw_modem(true) - - -- find and setup all speakers - local speakers = ppm.get_all_devices("speaker") - for _, s in pairs(speakers) do - local sounder = rtu.init_sounder(s) - - table.insert(smem_dev.sounders, sounder) - - log.debug(util.c("startup> added speaker, attached as ", sounder.name)) - end - - databus.tx_hw_spkr_count(#smem_dev.sounders) + -- init backplane peripherals + backplane.init(config, __shared_memory) -- start UI local message @@ -168,9 +137,13 @@ local function main() log.debug("startup> conn watchdog started") -- setup comms - smem_sys.nic = network.nic(smem_dev.modem) - smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_sys.nic, smem_sys.conn_watchdog) - log.debug("startup> comms init") + local nic = backplane.active_nic() + smem_sys.rtu_comms = rtu.comms(RTU_VERSION, nic, smem_sys.conn_watchdog) + if nic then + log.debug("startup> comms init") + else + log.warning("startup> no comms modem on startup") + end -- init threads local main_thread = threads.thread__main(__shared_memory) diff --git a/rtu/threads.lua b/rtu/threads.lua index 1c47efc..1008085 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -5,10 +5,10 @@ local tcd = require("scada-common.tcd") local types = require("scada-common.types") local util = require("scada-common.util") +local backplane = require("rtu.backplane") local databus = require("rtu.databus") local modbus = require("rtu.modbus") local renderer = require("rtu.renderer") -local rtu = require("rtu.rtu") local boilerv_rtu = require("rtu.dev.boilerv_rtu") local dynamicv_rtu = require("rtu.dev.dynamicv_rtu") @@ -191,13 +191,12 @@ function threads.thread__main(smem) -- load in from shared memory local rtu_state = smem.rtu_state - local rtu_dev = smem.rtu_dev - local sounders = smem.rtu_dev.sounders - local nic = smem.rtu_sys.nic local rtu_comms = smem.rtu_sys.rtu_comms local conn_watchdog = smem.rtu_sys.conn_watchdog local units = smem.rtu_sys.units + local sounders = backplane.sounders() + -- start unlinked (in case of restart) rtu_comms.unlink(rtu_state) @@ -247,38 +246,8 @@ function threads.thread__main(smem) local type, device = ppm.handle_unmount(param1) if type ~= nil and device ~= nil then - if type == "modem" then - ---@cast device Modem - -- we only care if this is our comms modem - if nic.is_modem(device) then - nic.disconnect() - - println_ts("comms modem disconnected!") - log.warning("comms modem disconnected") - - local other_modem = ppm.get_wireless_modem() - if other_modem then - log.info("found another wireless modem, using it for comms") - nic.connect(other_modem) - else - databus.tx_hw_modem(false) - end - else - log.warning("non-comms modem disconnected") - end - elseif type == "speaker" then - ---@cast device Speaker - for i = 1, #sounders do - if sounders[i].speaker == device then - table.remove(sounders, i) - - log.warning(util.c("speaker ", param1, " disconnected")) - println_ts("speaker disconnected") - - databus.tx_hw_spkr_count(#sounders) - break - end - end + if type == "modem" or type == "speaker" then + backplane.detach(type, device, param1) else for i = 1, #units do -- find disconnected device @@ -302,31 +271,8 @@ function threads.thread__main(smem) local type, device = ppm.mount(param1) if type ~= nil and device ~= nil then - if type == "modem" then - ---@cast device Modem - local is_comms_modem = util.trinary(rtu_dev.modem_wired, rtu_dev.modem_iface == param1, device.isWireless()) - - if is_comms_modem and not nic.is_connected() then - -- reconnected modem - nic.connect(device) - - println_ts("comms modem reconnected.") - log.info("comms modem reconnected") - - databus.tx_hw_modem(true) - elseif device.isWireless() then - log.info("unused wireless modem connected") - else - log.info("non-comms wired modem connected") - end - elseif type == "speaker" then - ---@cast device Speaker - table.insert(sounders, rtu.init_sounder(device)) - - println_ts("speaker connected") - log.info(util.c("connected speaker ", param1)) - - databus.tx_hw_spkr_count(#sounders) + if type == "modem" or type == "speaker" then + backplane.attach(type, device, param1) else -- relink lost peripheral to correct unit entry for i = 1, #units do @@ -394,12 +340,12 @@ function threads.thread__comms(smem) -- load in from shared memory local rtu_state = smem.rtu_state - local sounders = smem.rtu_dev.sounders local rtu_comms = smem.rtu_sys.rtu_comms local units = smem.rtu_sys.units - local comms_queue = smem.q.mq_comms + local sounders = backplane.sounders() + local last_update = util.time() -- thread loop From fe9ee313f9b1b8a79199048aed0db2d9f6cbfdc2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 18 Oct 2025 17:17:33 -0400 Subject: [PATCH 14/90] fixed Checkbox set_value type hint --- graphics/elements/controls/Checkbox.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphics/elements/controls/Checkbox.lua b/graphics/elements/controls/Checkbox.lua index 1a36a78..252a361 100644 --- a/graphics/elements/controls/Checkbox.lua +++ b/graphics/elements/controls/Checkbox.lua @@ -102,7 +102,7 @@ return function (args) end -- set the value - ---@param val integer new value + ---@param val boolean new value function e.set_value(val) e.value = val draw() From 1fcc91e98b4dda2a1d676861f7322e97ddca3335 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 18 Oct 2025 17:23:06 -0400 Subject: [PATCH 15/90] #580 RTU gateway multi-modem wired/wireless failover networking --- reactor-plc/startup.lua | 2 +- rtu/backplane.lua | 8 +- rtu/config/system.lua | 172 ++++++++++++++++++++++++++++++++-------- rtu/configure.lua | 14 +++- scada-common/ppm.lua | 6 +- supervisor/pcie.lua | 2 +- 6 files changed, 162 insertions(+), 42 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 1ce7682..5cb8f72 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -134,7 +134,7 @@ local function main() -- get the configured modem if smem_dev.modem_wired then - smem_dev.modem = ppm.get_wired_modem(smem_dev.modem_iface) + smem_dev.modem = ppm.get_modem(smem_dev.modem_iface) else smem_dev.modem = ppm.get_wireless_modem() end -- initial state evaluation diff --git a/rtu/backplane.lua b/rtu/backplane.lua index 78b6ac2..c79f02d 100644 --- a/rtu/backplane.lua +++ b/rtu/backplane.lua @@ -41,7 +41,7 @@ function backplane.init(config, __shared_memory) -- init wired NIC if _bp.lan_en then - local modem = ppm.get_wired_modem(_bp.lan_iface) + local modem = ppm.get_modem(_bp.lan_iface) if modem then _bp.wd_nic = network.nic(modem) @@ -62,7 +62,7 @@ function backplane.init(config, __shared_memory) -- grab the preferred active NIC if _bp.wlan_pref then _bp.wl_act = true - _bp.act_nic = _bp.wl_nics[1] + _bp.act_nic = _bp.wl_nic else _bp.wl_act = false _bp.act_nic = _bp.wd_nic @@ -110,11 +110,11 @@ function backplane.detach(type, device, iface) local was_wl = wl_nic and wl_nic.is_modem(device) if wd_nic and was_wd then - log.info("BKPLN: WIRED PHY_DOWN " .. iface) wd_nic.disconnect() + log.info("BKPLN: WIRED PHY_DOWN " .. iface) elseif wl_nic and was_wl then - log.info("BKPLN: WIRELESS PHY_DOWN " .. iface) wl_nic.disconnect() + log.info("BKPLN: WIRELESS PHY_DOWN " .. iface) end -- we only care if this is our active comms modem diff --git a/rtu/config/system.lua b/rtu/config/system.lua index 27beef7..0f7a798 100644 --- a/rtu/config/system.lua +++ b/rtu/config/system.lua @@ -90,22 +90,73 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) local net_c_1 = Div{parent=net_cfg,x=2,y=4,width=49} local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=49} local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=49} + local net_c_4 = Div{parent=net_cfg,x=2,y=4,width=49} - local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3}} + local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4}} TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)} - TextBox{parent=net_c_1,x=1,y=1,text="Please set the network channels below."} - TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 2 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_1,x=1,y=1,text="Please select the network interface(s)."} + TextBox{parent=net_c_1,x=41,y=1,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision - TextBox{parent=net_c_1,x=1,y=8,text="Supervisor Channel"} - local svr_chan = NumberField{parent=net_c_1,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg} - TextBox{parent=net_c_1,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_1,x=1,y=11,text="RTU Channel"} - local rtu_chan = NumberField{parent=net_c_1,x=1,y=12,width=7,default=ini_cfg.RTU_Channel,min=1,max=65535,fg_bg=bw_fg_bg} - TextBox{parent=net_c_1,x=9,y=12,height=4,text="[RTU_CHANNEL]",fg_bg=g_lg_fg_bg} + local wl_pref = Checkbox{parent=net_c_1,x=30,y=3,label="Prefer Wireless",default=ini_cfg.PreferWireless,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg,callback=function()end} - local chan_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local function dis_pref(value) + if not value then + wl_pref.set_value(false) + wl_pref.disable() + else wl_pref.enable() end + end + + dis_pref(ini_cfg.WirelessModem) + + local function on_wired_change(value) + tool_ctl.gen_modem_list(value) + end + + local wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=dis_pref} + local wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change} + TextBox{parent=net_c_1,x=3,y=6,text="MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)} + TextBox{parent=net_c_1,x=3,y=7,text="connecting to peripherals will cause problems",fg_bg=g_lg_fg_bg} + local modem_list = ListBox{parent=net_c_1,x=1,y=8,height=5,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} + + local modem_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + + local function submit_interfaces() + tmp_cfg.WirelessModem = wireless.get_value() + tmp_cfg.PreferWireless = wl_pref.get_value() + + if not wired.get_value() then + tmp_cfg.WiredModem = false + tool_ctl.gen_modem_list() + end + + if not (wired.get_value() or wireless.get_value()) then + modem_err.set_value("Please select a modem type.") + modem_err.show() + elseif wired.get_value() and type(tmp_cfg.WiredModem) ~= "string" then + modem_err.set_value("Please select a wired modem.") + modem_err.show() + else + net_pane.set_value(2) + modem_err.hide(true) + end + end + + PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_interfaces,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + TextBox{parent=net_c_2,x=1,y=1,text="Please set the network channels below."} + TextBox{parent=net_c_2,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 2 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg} + + TextBox{parent=net_c_2,x=1,y=8,text="Supervisor Channel"} + local svr_chan = NumberField{parent=net_c_2,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_2,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_2,x=1,y=11,text="RTU Channel"} + local rtu_chan = NumberField{parent=net_c_2,x=1,y=12,width=7,default=ini_cfg.RTU_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_2,x=9,y=12,height=4,text="[RTU_CHANNEL]",fg_bg=g_lg_fg_bg} + + local chan_err = TextBox{parent=net_c_2,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_channels() local svr_c = tonumber(svr_chan.get_value()) @@ -113,7 +164,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) if svr_c ~= nil and rtu_c ~= nil then tmp_cfg.SVR_Channel = svr_c tmp_cfg.RTU_Channel = rtu_c - net_pane.set_value(2) + net_pane.set_value(3) chan_err.hide(true) elseif svr_c == nil then chan_err.set_value("Please set the supervisor channel.") @@ -124,19 +175,19 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) end end - PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=net_c_2,x=1,y=1,text="Connection Timeout"} - local timeout = NumberField{parent=net_c_2,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} - TextBox{parent=net_c_2,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_3,x=1,y=1,text="Connection Timeout"} + local timeout = NumberField{parent=net_c_3,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} + TextBox{parent=net_c_3,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_3,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_2,x=1,y=8,text="Trusted Range"} - local range = NumberField{parent=net_c_2,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg} - TextBox{parent=net_c_2,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_3,x=1,y=8,text="Trusted Range (Wireless Only)"} + local range = NumberField{parent=net_c_3,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg} + TextBox{parent=net_c_3,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} - local p2_err = TextBox{parent=net_c_2,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local p2_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_ct_tr() local timeout_val = tonumber(timeout.get_value()) @@ -144,7 +195,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) if timeout_val ~= nil and range_val ~= nil then tmp_cfg.ConnTimeout = timeout_val tmp_cfg.TrustedRange = range_val - net_pane.set_value(3) + net_pane.set_value(4) p2_err.hide(true) elseif timeout_val == nil then p2_err.set_value("Please set the connection timeout.") @@ -155,23 +206,23 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) end end - PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=net_c_3,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."} - TextBox{parent=net_c_3,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_4,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."} + TextBox{parent=net_c_4,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for wireless security on multiplayer servers. All devices on the same wireless network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_3,x=1,y=11,text="Facility Auth Key"} - local key, _ = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg} + TextBox{parent=net_c_4,x=1,y=11,text="Auth Key (Wireless Only, Not Used when Wired)"} + local key, _ = TextField{parent=net_c_4,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg} local function censor_key(enable) key.censor(tri(enable, "*", nil)) end - local hide_key = Checkbox{parent=net_c_3,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key} + local hide_key = Checkbox{parent=net_c_4,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key} hide_key.set_value(true) censor_key(true) - local key_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local key_err = TextBox{parent=net_c_4,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_auth() local v = key.get_value() @@ -182,8 +233,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) else key_err.show() end end - PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_4,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_4,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} --#endregion @@ -382,6 +433,9 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) load_settings(ini_cfg) try_set(s_vol, ini_cfg.SpeakerVolume) + try_set(wireless, ini_cfg.WirelessModem) + try_set(wired, ini_cfg.WiredModem ~= false) + try_set(wl_pref, ini_cfg.PreferWireless) try_set(svr_chan, ini_cfg.SVR_Channel) try_set(rtu_chan, ini_cfg.RTU_Channel) try_set(timeout, ini_cfg.ConnTimeout) @@ -665,6 +719,60 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) end end + -- generate the list of available/assigned wired modems + function tool_ctl.gen_modem_list(enable) + modem_list.remove_all() + + local function select(iface) + tmp_cfg.WiredModem = iface + tool_ctl.gen_modem_list(true) + end + + local modems = ppm.get_wired_modem_list() + local missing = { tmp = true, ini = true } + + for iface, _ in pairs(modems) do + if ini_cfg.WiredModem == iface then + missing.ini = false + elseif tmp_cfg.WiredModem == iface then + missing.tmp = false + end + end + + if missing.tmp and tmp_cfg.WiredModem then + local line = Div{parent=modem_list,x=1,y=1,height=1} + + TextBox{parent=line,x=1,y=1,width=4,text="Used",fg_bg=cpair(tri(enable,colors.blue,colors.gray),colors.white)} + PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg}.disable() + TextBox{parent=line,x=15,y=1,text="[missing]",fg_bg=cpair(colors.red,colors.white)} + TextBox{parent=line,x=25,y=1,text=tmp_cfg.WiredModem} + end + + if missing.ini and ini_cfg.WiredModem and (tmp_cfg.WiredModem ~= ini_cfg.WiredModem) then + local line = Div{parent=modem_list,x=1,y=1,height=1} + local used = tmp_cfg.WiredModem == ini_cfg.WiredModem + + TextBox{parent=line,x=1,y=1,width=4,text=tri(used,"Used","----"),fg_bg=cpair(tri(used and enable,colors.blue,colors.gray),colors.white)} + local select_btn = PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()select(ini_cfg.WiredModem)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg} + TextBox{parent=line,x=15,y=1,text="[missing]",fg_bg=cpair(colors.red,colors.white)} + TextBox{parent=line,x=25,y=1,text=ini_cfg.WiredModem} + + if used or not enable then select_btn.disable() end + end + + -- list wired modems + for iface, _ in pairs(modems) do + local line = Div{parent=modem_list,x=1,y=1,height=1} + local used = tmp_cfg.WiredModem == iface + + TextBox{parent=line,x=1,y=1,width=4,text=tri(used,"Used","----"),fg_bg=cpair(tri(used and enable,colors.blue,colors.gray),colors.white)} + local select_btn = PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()select(iface)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg} + TextBox{parent=line,x=15,y=1,text=iface} + + if used or not enable then select_btn.disable() end + end + end + --#endregion end diff --git a/rtu/configure.lua b/rtu/configure.lua index 1333b2b..a752342 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -37,7 +37,8 @@ local changes = { { "v1.9.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } }, { "v1.10.2", { "Re-organized peripheral configuration UI, resulting in some input fields being re-ordered" } }, { "v1.11.8", { "Added advanced option to invert digital redstone signals" } }, - { "v1.12.0", { "Added support for redstone relays" } } + { "v1.12.0", { "Added support for redstone relays" } }, + { "v1.13.0", { "Added support for wired communications modems" } } } ---@class rtu_configurator @@ -80,6 +81,8 @@ local tool_ctl = { update_relay_list = nil, ---@type function gen_peri_summary = nil, ---@type function gen_rs_summary = nil, ---@type function + + gen_modem_list = function (_) end } ---@class rtu_config @@ -112,7 +115,9 @@ local fields = { { "SVR_Channel", "SVR Channel", 16240 }, { "RTU_Channel", "RTU Channel", 16242 }, { "ConnTimeout", "Connection Timeout", 5 }, + { "WirelessModem", "Wireless Modem", true }, { "WiredModem", "Wired Modem", false }, + { "PreferWireless", "Prefer Wireless Modem", true }, { "TrustedRange", "Trusted Range", 0 }, { "AuthKey", "Facility Auth Key", "" }, { "LogMode", "Log Mode", log.MODE.APPEND }, @@ -317,8 +322,11 @@ function configurator.configure(ask_config) load_settings(settings_cfg, true) tool_ctl.has_config = load_settings(ini_cfg) + + -- set tmp_cfg so interface lists are correct tmp_cfg.Peripherals = tool_ctl.deep_copy_peri(ini_cfg.Peripherals) tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone) + tmp_cfg.WiredModem = ini_cfg.WiredModem reset_term() @@ -333,6 +341,8 @@ function configurator.configure(ask_config) local display = DisplayBox{window=term.current(),fg_bg=style.root} config_view(display) + tool_ctl.gen_modem_list(ini_cfg.WiredModem ~= false) + while true do local event, param1, param2, param3, param4, param5 = util.pull_event() @@ -354,11 +364,13 @@ function configurator.configure(ask_config) ppm.handle_unmount(param1) tool_ctl.update_peri_list() tool_ctl.update_relay_list() + tool_ctl.gen_modem_list() elseif event == "peripheral" then ---@diagnostic disable-next-line: discard-returns ppm.mount(param1) tool_ctl.update_peri_list() tool_ctl.update_relay_list() + tool_ctl.gen_modem_list() end if event == "terminate" then return end diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index d540a34..5aeebbe 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -447,15 +447,15 @@ end ---@return table|nil reactor function table function ppm.get_fission_reactor() return ppm.get_device("fissionReactorLogicAdapter") end --- get a wired modem by name +-- get a modem by name ---@nodiscard ---@param iface string CC peripheral interface ---@return Modem|nil modem function table -function ppm.get_wired_modem(iface) +function ppm.get_modem(iface) local modem = nil local device = ppm_sys.mounts[iface] - if device.type == "modem" then modem = device.dev end + if device and device.type == "modem" then modem = device.dev end return modem end diff --git a/supervisor/pcie.lua b/supervisor/pcie.lua index dea089e..e98fa57 100644 --- a/supervisor/pcie.lua +++ b/supervisor/pcie.lua @@ -68,7 +68,7 @@ function pcie_bus.init(config, println) if type(config.WiredModem) == "string" then bus.wired_modem = config.WiredModem - local wired_modem = ppm.get_wired_modem(bus.wired_modem) + local wired_modem = ppm.get_modem(bus.wired_modem) if not (wired_modem and bus.wired_modem) then println("startup> wired comms modem not found") From 9e3922a9727ddfb40a844853b8334d44ab41e59a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 18 Oct 2025 18:38:42 -0400 Subject: [PATCH 16/90] ppm wired modem list --- scada-common/ppm.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 5aeebbe..d17fd41 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -479,6 +479,19 @@ function ppm.get_wireless_modem() return w_modem, w_iface end +-- list all connected wired modems +---@nodiscard +---@return { [string]: ppm_entry } modems +function ppm.get_wired_modem_list() + local list = {} + + for iface, device in pairs(ppm_sys.mounts) do + if device.type == "modem" and not device.dev.isWireless() then list[iface] = device end + end + + return list +end + -- list all connected monitors ---@nodiscard ---@return { [string]: ppm_entry } monitors From 4d6c388f37aec7bc0a551ff856a3d89effac91d9 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Oct 2025 15:19:30 -0400 Subject: [PATCH 17/90] #580 supervisor backplane --- scada-common/network.lua | 2 + scada-common/util.lua | 2 +- supervisor/backplane.lua | 183 ++++++++++++++++++++++++++++++++++++++ supervisor/pcie.lua | 177 ------------------------------------ supervisor/startup.lua | 12 +-- supervisor/supervisor.lua | 12 ++- 6 files changed, 200 insertions(+), 188 deletions(-) create mode 100644 supervisor/backplane.lua delete mode 100644 supervisor/pcie.lua diff --git a/scada-common/network.lua b/scada-common/network.lua index 279a806..7080553 100644 --- a/scada-common/network.lua +++ b/scada-common/network.lua @@ -104,6 +104,8 @@ function network.nic(modem) modem = reconnected_modem self.connected = true + modem.closeAll() + -- open previously opened channels for _, channel in ipairs(self.channels) do modem.open(channel) diff --git a/scada-common/util.lua b/scada-common/util.lua index 031eaa0..c889037 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -24,7 +24,7 @@ local t_pack = table.pack local util = {} -- scada-common version -util.version = "1.5.4" +util.version = "1.5.5" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 diff --git a/supervisor/backplane.lua b/supervisor/backplane.lua new file mode 100644 index 0000000..7736290 --- /dev/null +++ b/supervisor/backplane.lua @@ -0,0 +1,183 @@ +-- +-- Supervisor 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("supervisor.databus") + +---@class supervisor_backplane +local backplane = {} + +local _bp = { + config = nil, ---@type svr_config + lan_iface = false, ---@type string|false wired comms modem name + + wd_nic = nil, ---@type nic|nil wired nic + wl_nic = nil, ---@type nic|nil wireless nic + nic_map = {} +} + +backplane.nics = _bp.nic_map + +-- initialize the system peripheral backplane +---@param config svr_config +---@param println function +---@return boolean success +function backplane.init(config, println) + -- setup the wired modem, if configured + if type(config.WiredModem) == "string" then + _bp.lan_iface = config.WiredModem + + local modem = ppm.get_modem(_bp.lan_iface) + if not (modem and _bp.lan_iface) then + println("startup> wired comms modem not found") + log.fatal("no wired comms modem on startup") + return false + end + + local nic = network.nic(modem) + _bp.wd_nic = nic + _bp.nic_map[_bp.lan_iface] = nic + + nic.closeAll() + + if config.PLC_Listen > 0 then nic.open(config.PLC_Channel) end + if config.RTU_Listen > 0 then nic.open(config.RTU_Channel) end + if config.CRD_Listen > 0 then nic.open(config.CRD_Channel) end + + databus.tx_hw_wd_modem(true) + end + + -- setup the wireless modem, if configured + if config.WirelessModem then + local modem, iface = ppm.get_wireless_modem() + if not (modem and iface) then + println("startup> wireless comms modem not found") + log.fatal("no wireless comms modem on startup") + return false + end + + local nic = network.nic(modem) + _bp.wl_nic = nic + _bp.nic_map[iface] = nic + + nic.closeAll() + + if config.PLC_Listen % 2 == 0 then nic.open(config.PLC_Channel) end + if config.RTU_Listen % 2 == 0 then nic.open(config.RTU_Channel) end + if config.CRD_Listen % 2 == 0 then nic.open(config.CRD_Channel) end + if config.PocketEnabled then nic.open(config.PKT_Channel) end + + databus.tx_hw_wl_modem(true) + end + + if not ((type(config.WiredModem) == "string" or config.WirelessModem)) then + println("startup> no modems configured") + log.fatal("no modems configured") + return false + end + + return true +end + +-- handle a backplane peripheral attach +---@param iface string +---@param type string +---@param device table +---@param println function +function backplane.attach(iface, type, device, println) + if 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) + println("wired comms modem reconnected") + + databus.tx_hw_wd_modem(true) + elseif is_wl then + -- connect this as the wireless NIC + _bp.wl_nic.connect(device) + _bp.nic_map[iface] = _bp.wl_nic + + log.info("BKPLN: WIRELESS PHY_UP " .. iface) + println("wireless comms modem reconnected") + + databus.tx_hw_wl_modem(true) + elseif _bp.wl_nic and m_is_wl then + -- the wireless NIC already has a modem + println("standby wireless modem connected") + log.info("BKPLN: standby wireless modem connected") + else + println("unassigned modem connected") + log.warning("BKPLN: unassigned modem connected") + end + end +end + +-- handle a backplane peripheral detach +---@param iface string +---@param type string +---@param device table +---@param println function +function backplane.detach(iface, type, device, println) + if type == "modem" then + ---@cast device Modem + + local m_is_wl = device.isWireless() + local was_wd = _bp.wd_nic and _bp.wd_nic.is_modem(device) + local was_wl = _bp.wl_nic and _bp.wl_nic.is_modem(device) + + log.info(util.c("BKPLN: ", util.trinary(m_is_wl, "WIRELESS", "WIRED"), " PHY_DETACH ", iface)) + + _bp.nic_map[iface] = nil + + if _bp.wd_nic and was_wd then + _bp.wd_nic.disconnect() + log.info("BKPLN: WIRED PHY_DOWN " .. iface) + + println("wired modem disconnected") + log.warning("BKPLN: wired comms modem disconnected") + + databus.tx_hw_wd_modem(false) + elseif _bp.wl_nic and was_wl then + _bp.wl_nic.disconnect() + log.info("BKPLN: WIRELESS PHY_DOWN " .. iface) + + println("wireless comms modem disconnected") + log.warning("BKPLN: wireless comms modem disconnected") + + local modem, m_iface = ppm.get_wireless_modem() + if modem then + log.info("BKPLN: found another wireless modem, using it for comms") + + _bp.wl_nic.connect(modem) + log.info("BKPLN: WIRELESS PHY_UP " .. m_iface) + else + databus.tx_hw_wl_modem(false) + end + elseif _bp.wl_nic and m_is_wl then + -- wireless, but not active + println("standby wireless modem disconnected") + log.info("BKPLN: standby wireless modem disconnected") + else + println("unassigned modem disconnected") + log.warning("BKPLN: unassigned modem disconnected") + end + end +end + +return backplane diff --git a/supervisor/pcie.lua b/supervisor/pcie.lua deleted file mode 100644 index e98fa57..0000000 --- a/supervisor/pcie.lua +++ /dev/null @@ -1,177 +0,0 @@ --- --- PCIe - Borrowed the name of that protocol for fun (this manages physical peripherals) --- - -local log = require("scada-common.log") -local network = require("scada-common.network") -local ppm = require("scada-common.ppm") - -local databus = require("supervisor.databus") - -local pcie_bus = {} - -local bus = { - wired_modem = false, ---@type string|false wired comms modem name - wl_nic = nil, ---@type nic|nil wireless nic - wd_nic = nil ---@type nic|nil wired nic -} - --- network cards ----@class _svr_pcie_nic ----@field wl nic|nil the wireless comms NIC ----@field wd nic|nil the wired comms NIC -pcie_bus.nic = { - -- close all channels and then open the configured channels on the appropriate nic(s) - ---@param config svr_config - reset_open = function (config) - if bus.wl_nic then - bus.wl_nic.closeAll() - - if config.PLC_Listen % 2 == 0 then bus.wl_nic.open(config.PLC_Channel) end - if config.RTU_Listen % 2 == 0 then bus.wl_nic.open(config.RTU_Channel) end - if config.CRD_Listen % 2 == 0 then bus.wl_nic.open(config.CRD_Channel) end - if config.PocketEnabled then bus.wl_nic.open(config.PKT_Channel) end - end - - if bus.wd_nic then - bus.wd_nic.closeAll() - - if config.PLC_Listen > 0 then bus.wd_nic.open(config.PLC_Channel) end - if config.RTU_Listen > 0 then bus.wd_nic.open(config.RTU_Channel) end - if config.CRD_Listen > 0 then bus.wd_nic.open(config.CRD_Channel) end - end - end, - -- get the requested nic by interface - ---@param iface string - ---@return nic|nil - get = function(iface) - local dev = ppm.get_device(iface) - - if dev then - if bus.wl_nic and bus.wl_nic.is_modem(dev) then return bus.wl_nic end - if bus.wd_nic and bus.wd_nic.is_modem(dev) then return bus.wd_nic end - end - - return nil - end, - -- cards by interface - ---@type { string: nic } - cards = {} -} - --- initialize peripherals ----@param config svr_config ----@param println function ----@return boolean success -function pcie_bus.init(config, println) - -- setup networking peripheral(s) - if type(config.WiredModem) == "string" then - bus.wired_modem = config.WiredModem - - local wired_modem = ppm.get_modem(bus.wired_modem) - - if not (wired_modem and bus.wired_modem) then - println("startup> wired comms modem not found") - log.fatal("no wired comms modem on startup") - return false - end - - bus.wd_nic = network.nic(wired_modem) - pcie_bus.nic.cards[bus.wired_modem] = bus.wd_nic - end - - if config.WirelessModem then - local wireless_modem, wireless_iface = ppm.get_wireless_modem() - - if not (wireless_modem and wireless_iface) then - println("startup> wireless comms modem not found") - log.fatal("no wireless comms modem on startup") - return false - end - - bus.wl_nic = network.nic(wireless_modem) - pcie_bus.nic.cards[wireless_iface] = bus.wl_nic - end - - pcie_bus.nic.wl = bus.wl_nic - pcie_bus.nic.wd = bus.wd_nic - - databus.tx_hw_wl_modem(true) - databus.tx_hw_wd_modem(config.WirelessModem) - - return true -end - --- handle the connecting of a device ----@param iface string ----@param type string ----@param device table ----@param println function -function pcie_bus.connect(iface, type, device, println) - if type == "modem" then - ---@cast device Modem - if device.isWireless() then - if bus.wl_nic and not bus.wl_nic.is_connected() then - -- reconnected wireless comms modem - bus.wl_nic.connect(device) - pcie_bus.nic.cards[iface] = bus.wl_nic - - println("wireless comms modem reconnected") - log.info("wireless comms modem reconnected") - - databus.tx_hw_wl_modem(true) - else - log.info("unused wireless modem reconnected") - end - elseif bus.wd_nic and (iface == bus.wired_modem) then - -- reconnected wired comms modem - bus.wd_nic.connect(device) - pcie_bus.nic.cards[iface] = bus.wd_nic - - println("wired comms modem reconnected") - log.info("wired comms modem reconnected") - - databus.tx_hw_wl_modem(true) - else - log.info("wired modem reconnected") - end - end -end - --- handle the removal of a device ----@param iface string ----@param type string ----@param device table ----@param println function -function pcie_bus.remove(iface, type, device, println) - if type == "modem" then - pcie_bus.nic.cards[iface] = nil - - ---@cast device Modem - if bus.wl_nic and bus.wl_nic.is_modem(device) then - bus.wl_nic.disconnect() - - println("wireless comms modem disconnected") - log.warning("wireless comms modem disconnected") - - local other_modem = ppm.get_wireless_modem() - if other_modem then - log.info("found another wireless modem, using it for comms") - bus.wl_nic.connect(other_modem) - else - databus.tx_hw_wl_modem(false) - end - elseif bus.wd_nic and bus.wd_nic.is_modem(device) then - bus.wd_nic.disconnect() - - println("wired modem disconnected") - log.warning("wired modem disconnected") - - databus.tx_hw_wd_modem(false) - else - log.warning("non-comms modem disconnected") - end - end -end - -return pcie_bus diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 3246051..280695d 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -3,7 +3,6 @@ -- require("/initenv").init_env() -local pcie = require("supervisor.pcie") local crash = require("scada-common.crash") local comms = require("scada-common.comms") @@ -16,6 +15,7 @@ local util = require("scada-common.util") local core = require("graphics.core") +local backplane = require("supervisor.backplane") local configure = require("supervisor.configure") local databus = require("supervisor.databus") local facility = require("supervisor.facility") @@ -24,7 +24,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.7.1" +local SUPERVISOR_VERSION = "v1.8.0" local println = util.println local println_ts = util.println_ts @@ -126,8 +126,8 @@ local function main() network.init_mac(config.AuthKey) end - -- hardware bus initialization - if not pcie.init(config, println) then return end + -- hardware backplane initialization + if not backplane.init(config, println) then return end -- start UI local fp_ok, message = renderer.try_start_ui(config) @@ -167,12 +167,12 @@ local function main() if event == "peripheral_detach" then local type, device = ppm.handle_unmount(param1) if type ~= nil and device ~= nil then - pcie.remove(param1, type, device, println_ts) + backplane.detach(param1, type, device, println_ts) end elseif event == "peripheral" then local type, device = ppm.mount(param1) if type ~= nil and device ~= nil then - pcie.connect(param1, type, device, println_ts) + backplane.attach(param1, type, device, println_ts) end elseif event == "timer" and loop_clock.is_clock(param1) then -- main loop tick diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 6662f62..5388d0a 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -1,10 +1,11 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local util = require("scada-common.util") -local pcie = require("supervisor.pcie") local themes = require("graphics.themes") +local backplane = require("supervisor.backplane") + local svsessions = require("supervisor.session.svsessions") local supervisor = {} @@ -198,8 +199,11 @@ function supervisor.comms(_version, fp_ok, facility) ---@param distance integer ---@return modbus_frame|rplc_frame|mgmt_frame|crdn_frame|nil packet function public.parse_packet(side, sender, reply_to, message, distance) - local pkt, nic = nil, pcie.nic.cards[side] - local s_pkt = nic.receive(side, sender, reply_to, message, distance) + local pkt, s_pkt, nic = nil, nil, backplane.nics[side] + + if nic then + s_pkt = nic.receive(side, sender, reply_to, message, distance) + end if s_pkt then -- get as MODBUS TCP packet @@ -229,7 +233,7 @@ function supervisor.comms(_version, fp_ok, facility) -- handle a packet ---@param packet modbus_frame|rplc_frame|mgmt_frame|crdn_frame function public.handle_packet(packet) - local nic = pcie.nic.get(packet.scada_frame.interface()) + local nic = backplane.nics[packet.scada_frame.interface()] local l_chan = packet.scada_frame.local_channel() local r_chan = packet.scada_frame.remote_channel() local src_addr = packet.scada_frame.src_addr() From fc24f39991966fca7ac2e100a5eae8c2f9aca137 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Oct 2025 17:30:05 -0400 Subject: [PATCH 18/90] #580 supervisor wired/wireless dual networking --- scada-common/comms.lua | 25 +- scada-common/network.lua | 15 +- supervisor/backplane.lua | 2 +- supervisor/supervisor.lua | 476 +++++++++++++++++++++----------------- 4 files changed, 300 insertions(+), 218 deletions(-) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 5de3103..04563b8 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -17,7 +17,7 @@ local max_distance = nil local comms = {} -- protocol/data versions (protocol/data independent changes tracked by util.lua version) -comms.version = "3.0.8" +comms.version = "3.0.9" comms.api_version = "0.0.10" ---@enum PROTOCOL @@ -49,13 +49,14 @@ local MGMT_TYPE = { ESTABLISH = 0, -- establish new connection KEEP_ALIVE = 1, -- keep alive packet w/ RTT CLOSE = 2, -- close a connection - RTU_ADVERT = 3, -- RTU capability advertisement - RTU_DEV_REMOUNT = 4, -- RTU multiblock possbily changed (formed, unformed) due to PPM remount - RTU_TONE_ALARM = 5, -- instruct RTUs to play specified alarm tones - DIAG_TONE_GET = 6, -- (API) diagnostic: get alarm tones - DIAG_TONE_SET = 7, -- (API) diagnostic: set alarm tones - DIAG_ALARM_SET = 8, -- (API) diagnostic: set alarm to simulate audio for - INFO_LIST_CMP = 9 -- (API) info: list all computers on the network + PROBE = 3, + RTU_ADVERT = 4, -- RTU capability advertisement + RTU_DEV_REMOUNT = 5, -- RTU multiblock possbily changed (formed, unformed) due to PPM remount + RTU_TONE_ALARM = 6, -- instruct RTUs to play specified alarm tones + DIAG_TONE_GET = 7, -- (API) diagnostic: get alarm tones + DIAG_TONE_SET = 8, -- (API) diagnostic: set alarm tones + DIAG_ALARM_SET = 9, -- (API) diagnostic: set alarm to simulate audio for + INFO_LIST_CMP = 10 -- (API) info: list all computers on the network } ---@enum CRDN_TYPE @@ -89,6 +90,12 @@ local ESTABLISH_ACK = { ---@enum DEVICE_TYPE device types for establish messages local DEVICE_TYPE = { PLC = 0, RTU = 1, SVR = 2, CRD = 3, PKT = 4 } +---@enum PROBE_ACK +local PROBE_ACK = { + OPEN = 0, + CONFLICT = 1 +} + ---@enum PLC_AUTO_ACK local PLC_AUTO_ACK = { FAIL = 0, -- failed to set burn rate/burn rate invalid @@ -130,6 +137,8 @@ comms.CRDN_TYPE = CRDN_TYPE comms.ESTABLISH_ACK = ESTABLISH_ACK comms.DEVICE_TYPE = DEVICE_TYPE +comms.PROBE_ACK = PROBE_ACK + comms.PLC_AUTO_ACK = PLC_AUTO_ACK comms.UNIT_COMMAND = UNIT_COMMAND diff --git a/scada-common/network.lua b/scada-common/network.lua index 7080553..8066adb 100644 --- a/scada-common/network.lua +++ b/scada-common/network.lua @@ -82,7 +82,9 @@ end function network.nic(modem) local self = { -- modem interface name - iface = ppm.get_iface(modem), + iface = "?", + -- phy name + name = "?", -- used to quickly return out of tx/rx functions if there is nothing to do connected = true, -- used to avoid costly MAC calculations if not required @@ -94,6 +96,10 @@ function network.nic(modem) ---@class nic:Modem local public = {} + -- get the phy name + ---@nodiscard + function public.phy_name() return self.name end + -- check if this NIC has a connected modem ---@nodiscard function public.is_connected() return self.connected end @@ -102,11 +108,14 @@ function network.nic(modem) ---@param reconnected_modem Modem function public.connect(reconnected_modem) modem = reconnected_modem + + self.iface = ppm.get_iface(modem) + self.name = util.c(util.trinary(modem.isWireless(), "WLAN_PHY", "ETH_PHY"), "{", self.iface, "}") self.connected = true + self.use_hash = c_eng.hmac and modem.isWireless() + -- open only previously opened channels modem.closeAll() - - -- open previously opened channels for _, channel in ipairs(self.channels) do modem.open(channel) end diff --git a/supervisor/backplane.lua b/supervisor/backplane.lua index 7736290..9974085 100644 --- a/supervisor/backplane.lua +++ b/supervisor/backplane.lua @@ -18,7 +18,7 @@ local _bp = { wd_nic = nil, ---@type nic|nil wired nic wl_nic = nil, ---@type nic|nil wireless nic - nic_map = {} + nic_map = {} ---@type nic[] connected nics } backplane.nics = _bp.nic_map diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 5388d0a..bbdcad7 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -10,10 +10,11 @@ local svsessions = require("supervisor.session.svsessions") local supervisor = {} -local PROTOCOL = comms.PROTOCOL -local DEVICE_TYPE = comms.DEVICE_TYPE +local PROTOCOL = comms.PROTOCOL +local DEVICE_TYPE = comms.DEVICE_TYPE local ESTABLISH_ACK = comms.ESTABLISH_ACK -local MGMT_TYPE = comms.MGMT_TYPE +local PROBE_ACK = comms.PROBE_ACK +local MGMT_TYPE = comms.MGMT_TYPE ---@type svr_config ---@diagnostic disable-next-line: missing-fields @@ -156,14 +157,11 @@ function supervisor.comms(_version, fp_ok, facility) local function println(message) if not fp_ok then util.println_ts(message) end end local self = { - last_est_acks = {} + last_est_acks = {} ---@type ESTABLISH_ACK[] } comms.set_trusted_range(config.TrustedRange) - -- configure network channels - pcie.nic.reset_open(config) - -- pass system data and objects to svsessions svsessions.init(fp_ok, config, facility) @@ -175,8 +173,7 @@ function supervisor.comms(_version, fp_ok, facility) ---@param ack ESTABLISH_ACK ---@param data? any optional data local function _send_establish(nic, packet, ack, data) - local s_pkt = comms.scada_packet() - local m_pkt = comms.mgmt_packet() + local s_pkt, m_pkt = comms.scada_packet(), comms.mgmt_packet() m_pkt.make(MGMT_TYPE.ESTABLISH, { ack, data }) s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) @@ -185,6 +182,188 @@ function supervisor.comms(_version, fp_ok, facility) self.last_est_acks[packet.src_addr()] = ack end + -- send a probe response + ---@param nic nic + ---@param packet scada_packet + ---@param ack PROBE_ACK + local function _send_probe(nic, packet, ack) + local s_pkt, m_pkt = comms.scada_packet(), comms.mgmt_packet() + + m_pkt.make(MGMT_TYPE.PROBE, { ack }) + s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) + + nic.transmit(packet.remote_channel(), config.SVR_Channel, s_pkt) + end + + --#region Establish Handlers + + -- handle a PLC establish + ---@param nic nic + ---@param packet mgmt_frame + ---@param src_addr integer + ---@param i_seq_num integer + ---@param last_ack ESTABLISH_ACK + local function _establish_plc(nic, packet, src_addr, i_seq_num, last_ack) + local comms_v = packet.data[1] + local firmware_v = packet.data[2] + local dev_type = packet.data[3] + + if comms_v ~= comms.version then + if last_ack ~= ESTABLISH_ACK.BAD_VERSION then + log.info(util.c("dropping PLC establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) + end + + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + elseif dev_type == DEVICE_TYPE.PLC then + -- PLC linking request + if packet.length == 4 and type(packet.data[4]) == "number" then + local reactor_id = packet.data[4] + + -- check ID validity + if reactor_id < 1 or reactor_id > config.UnitCount then + -- reactor index out of range + if last_ack ~= ESTABLISH_ACK.DENY then + log.warning(util.c("PLC_ESTABLISH: denied assignment ", reactor_id, " outside of configured unit count ", config.UnitCount)) + end + + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) + else + -- try to establish the session + local plc_id = svsessions.establish_plc_session(nic, src_addr, i_seq_num, reactor_id, firmware_v) + + if plc_id == false then + -- reactor already has a PLC assigned + if last_ack ~= ESTABLISH_ACK.COLLISION then + log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) + end + + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.COLLISION) + else + -- got an ID; assigned to a reactor successfully + println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected")) + log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id, " on ", nic.phy_name())) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) + end + end + else + log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type") + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC channel")) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) + end + end + + -- handle an RTU gateway establish + ---@param nic nic + ---@param packet mgmt_frame + ---@param src_addr integer + ---@param i_seq_num integer + ---@param last_ack ESTABLISH_ACK + local function _establish_rtu_gw(nic, packet, src_addr, i_seq_num, last_ack) + local comms_v = packet.data[1] + local firmware_v = packet.data[2] + local dev_type = packet.data[3] + + if comms_v ~= comms.version then + if last_ack ~= ESTABLISH_ACK.BAD_VERSION then + log.info(util.c("dropping RTU_GW establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) + end + + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + elseif dev_type == DEVICE_TYPE.RTU then + if packet.length == 4 then + -- this is an RTU advertisement for a new session + local rtu_advert = packet.data[4] + local s_id = svsessions.establish_rtu_session(nic, src_addr, i_seq_num, rtu_advert, firmware_v) + + println(util.c("RTU (", firmware_v, ") [@", src_addr, "] \xbb connected")) + log.info(util.c("RTU_GW_ESTABLISH: RTU_GW (",firmware_v, ") [@", src_addr, "] connected with session ID ", s_id, " on ", nic.phy_name())) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) + else + log.debug("RTU_GW_ESTABLISH: packet length mismatch") + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + log.debug(util.c("illegal establish packet for device ", dev_type, " on RTU channel")) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) + end + end + + -- handle a coordinator establish + ---@param nic nic + ---@param packet mgmt_frame + ---@param src_addr integer + ---@param i_seq_num integer + ---@param last_ack ESTABLISH_ACK + local function _establish_crd(nic, packet, src_addr, i_seq_num, last_ack) + local comms_v = packet.data[1] + local firmware_v = packet.data[2] + local dev_type = packet.data[3] + + if comms_v ~= comms.version then + if last_ack ~= ESTABLISH_ACK.BAD_VERSION then + log.info(util.c("dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) + end + + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + elseif dev_type == DEVICE_TYPE.CRD then + -- this is an attempt to establish a new coordinator session + local s_id = svsessions.establish_crd_session(nic, src_addr, i_seq_num, firmware_v) + + if s_id ~= false then + println(util.c("CRD (", firmware_v, ") [@", src_addr, "] \xbb connected")) + log.info(util.c("CRD_ESTABLISH: CRD (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id, " on ", nic.phy_name())) + + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, facility.get_cooling_conf() }) + else + if last_ack ~= ESTABLISH_ACK.COLLISION then + log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator") + end + + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.COLLISION) + end + else + log.debug(util.c("illegal establish packet for device ", dev_type, " on CRD channel")) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) + end + end + + -- handle a pocket debug establish + ---@param nic nic + ---@param packet mgmt_frame + ---@param src_addr integer + ---@param i_seq_num integer + ---@param last_ack ESTABLISH_ACK + local function _establish_pdg(nic, packet, src_addr, i_seq_num, last_ack) + local comms_v = packet.data[1] + local firmware_v = packet.data[2] + local dev_type = packet.data[3] + + if comms_v ~= comms.version then + if last_ack ~= ESTABLISH_ACK.BAD_VERSION then + log.info(util.c("dropping PKT establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) + end + + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + elseif dev_type == DEVICE_TYPE.PKT then + -- this is an attempt to establish a new pocket diagnostic session + local s_id = svsessions.establish_pdg_session(nic, src_addr, i_seq_num, firmware_v) + + println(util.c("PKT (", firmware_v, ") [@", src_addr, "] \xbb connected")) + log.info(util.c("PDG_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id, " on ", nic.phy_name())) + + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) + else + log.debug(util.c("illegal establish packet for device ", dev_type, " on PKT channel")) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) + end + + end + + --#endregion + -- PUBLIC FUNCTIONS -- ---@class superv_comms @@ -240,89 +419,45 @@ function supervisor.comms(_version, fp_ok, facility) local protocol = packet.scada_frame.protocol() local i_seq_num = packet.scada_frame.seq_num() - if not nic then - log.error("received packet from unconfigured interface " .. packet.scada_frame.interface(), true) - elseif l_chan ~= config.SVR_Channel then + if l_chan ~= config.SVR_Channel then log.debug("received packet on unconfigured channel " .. l_chan, true) elseif r_chan == config.PLC_Channel then -- look for an associated session local session = svsessions.find_plc_session(src_addr) - if protocol == PROTOCOL.RPLC then - ---@cast packet rplc_frame - -- reactor PLC packet - if session ~= nil then + if session then + if nic ~= session.nic then + -- this is from the same device but on a different interface + -- drop unless it is a connection probe + if (protocol == PROTOCOL.SCADA_MGMT) and (packet.type == MGMT_TYPE.PROBE) then + ---@cast packet mgmt_frame + log.debug(util.c("PROBE_ACK: conflict with PLC @", src_addr, " on ", session.nic.phy_name(), " probed on ", nic.phy_name())) + _send_probe(nic, packet.scada_frame, PROBE_ACK.CONFLICT) + else + log.debug(util.c("unexpected packet for PLC @ ", src_addr, " received on ", nic.phy_name())) + end + else -- pass the packet onto the session handler session.in_queue.push_packet(packet) - else - -- any other packet should be session related, discard it - log.debug("discarding RPLC packet without a known session") end + elseif protocol == PROTOCOL.RPLC then + -- reactor PLC packet should be session related, discard it + log.debug("discarding RPLC packet without a known session") elseif protocol == PROTOCOL.SCADA_MGMT then ---@cast packet mgmt_frame -- SCADA management packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - elseif packet.type == MGMT_TYPE.ESTABLISH then - -- establish a new session - local last_ack = self.last_est_acks[src_addr] - - -- validate packet and continue + if packet.type == MGMT_TYPE.ESTABLISH then + -- establish a new session: validate packet and continue if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then - local comms_v = packet.data[1] - local firmware_v = packet.data[2] - local dev_type = packet.data[3] - - if comms_v ~= comms.version then - if last_ack ~= ESTABLISH_ACK.BAD_VERSION then - log.info(util.c("dropping PLC establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) - end - - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) - elseif dev_type == DEVICE_TYPE.PLC then - -- PLC linking request - if packet.length == 4 and type(packet.data[4]) == "number" then - local reactor_id = packet.data[4] - - -- check ID validity - if reactor_id < 1 or reactor_id > config.UnitCount then - -- reactor index out of range - if last_ack ~= ESTABLISH_ACK.DENY then - log.warning(util.c("PLC_ESTABLISH: denied assignment ", reactor_id, " outside of configured unit count ", config.UnitCount)) - end - - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) - else - -- try to establish the session - local plc_id = svsessions.establish_plc_session(nic, src_addr, i_seq_num, reactor_id, firmware_v) - - if plc_id == false then - -- reactor already has a PLC assigned - if last_ack ~= ESTABLISH_ACK.COLLISION then - log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) - end - - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.COLLISION) - else - -- got an ID; assigned to a reactor successfully - println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected")) - log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id)) - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) - end - end - else - log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type") - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) - end - else - log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC channel")) - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) - end + _establish_plc(nic, packet, src_addr, i_seq_num, self.last_est_acks[src_addr]) else log.debug("invalid establish packet (on PLC channel)") _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end + elseif packet.type == MGMT_TYPE.PROBE then + -- connection probing + log.debug(util.c("PROBE_ACK: reporting open to PLC @", src_addr, " probed on ", nic.phy_name())) + _send_probe(nic, packet.scada_frame, PROBE_ACK.OPEN) else -- any other packet should be session related, discard it log.debug(util.c("discarding PLC SCADA_MGMT packet without a known session from computer ", src_addr)) @@ -334,62 +469,43 @@ function supervisor.comms(_version, fp_ok, facility) -- look for an associated session local session = svsessions.find_rtu_session(src_addr) - if protocol == PROTOCOL.MODBUS_TCP then - ---@cast packet modbus_frame - -- MODBUS response - if session ~= nil then + if session then + if nic ~= session.nic then + -- this is from the same device but on a different interface + -- drop unless it is a connection probe + if (protocol == PROTOCOL.SCADA_MGMT) and (packet.type == MGMT_TYPE.PROBE) then + ---@cast packet mgmt_frame + log.debug(util.c("PROBE_ACK: conflict with RTU_GW @", src_addr, " on ", session.nic.phy_name(), " probed on ", nic.phy_name())) + _send_probe(nic, packet.scada_frame, PROBE_ACK.CONFLICT) + else + log.debug(util.c("unexpected packet for RTU_GW @ ", src_addr, " received on ", nic.phy_name())) + end + else -- pass the packet onto the session handler session.in_queue.push_packet(packet) - else - -- any other packet should be session related, discard it - log.debug("discarding MODBUS_TCP packet without a known session") end + elseif protocol == PROTOCOL.MODBUS_TCP then + ---@cast packet modbus_frame + -- MODBUS response, should be session related, discard it + log.debug("discarding MODBUS_TCP packet without a known session") elseif protocol == PROTOCOL.SCADA_MGMT then ---@cast packet mgmt_frame -- SCADA management packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - elseif packet.type == MGMT_TYPE.ESTABLISH then - -- establish a new session - local last_ack = self.last_est_acks[src_addr] - - -- validate packet and continue + if packet.type == MGMT_TYPE.ESTABLISH then + -- establish a new session: validate packet and continue if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then - local comms_v = packet.data[1] - local firmware_v = packet.data[2] - local dev_type = packet.data[3] - - if comms_v ~= comms.version then - if last_ack ~= ESTABLISH_ACK.BAD_VERSION then - log.info(util.c("dropping RTU establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) - end - - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) - elseif dev_type == DEVICE_TYPE.RTU then - if packet.length == 4 then - -- this is an RTU advertisement for a new session - local rtu_advert = packet.data[4] - local s_id = svsessions.establish_rtu_session(nic, src_addr, i_seq_num, rtu_advert, firmware_v) - - println(util.c("RTU (", firmware_v, ") [@", src_addr, "] \xbb connected")) - log.info(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) - else - log.debug("RTU_ESTABLISH: packet length mismatch") - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) - end - else - log.debug(util.c("illegal establish packet for device ", dev_type, " on RTU channel")) - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) - end + _establish_rtu_gw(nic, packet, src_addr, i_seq_num, self.last_est_acks[src_addr]) else log.debug("invalid establish packet (on RTU channel)") _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end + elseif packet.type == MGMT_TYPE.PROBE then + -- connection probing + log.debug(util.c("PROBE_ACK: reporting open to RTU_GW @", src_addr, " probed on ", nic.phy_name())) + _send_probe(nic, packet.scada_frame, PROBE_ACK.OPEN) else -- any other packet should be session related, discard it - log.debug(util.c("discarding RTU SCADA_MGMT packet without a known session from computer ", src_addr)) + log.debug(util.c("discarding RTU gateway SCADA_MGMT packet without a known session from computer ", src_addr)) end else log.debug(util.c("illegal packet type ", protocol, " on RTU channel")) @@ -398,107 +514,61 @@ function supervisor.comms(_version, fp_ok, facility) -- look for an associated session local session = svsessions.find_crd_session(src_addr) - if protocol == PROTOCOL.SCADA_MGMT then - ---@cast packet mgmt_frame - -- SCADA management packet - if session ~= nil then + if session then + if nic ~= session.nic then + -- this is from the same device but on a different interface + -- drop unless it is a connection probe + if (protocol == PROTOCOL.SCADA_MGMT) and (packet.type == MGMT_TYPE.PROBE) then + ---@cast packet mgmt_frame + log.debug(util.c("PROBE_ACK: conflict with CRD @", src_addr, " on ", session.nic.phy_name(), " probed on ", nic.phy_name())) + _send_probe(nic, packet.scada_frame, PROBE_ACK.CONFLICT) + else + log.debug(util.c("unexpected packet for CRD @ ", src_addr, " received on ", nic.phy_name())) + end + else -- pass the packet onto the session handler session.in_queue.push_packet(packet) - elseif packet.type == MGMT_TYPE.ESTABLISH then - -- establish a new session - local last_ack = self.last_est_acks[src_addr] - - -- validate packet and continue + end + elseif protocol == PROTOCOL.SCADA_MGMT then + ---@cast packet mgmt_frame + -- SCADA management packet + if packet.type == MGMT_TYPE.ESTABLISH then + -- establish a new session: validate packet and continue if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then - local comms_v = packet.data[1] - local firmware_v = packet.data[2] - local dev_type = packet.data[3] - - if comms_v ~= comms.version then - if last_ack ~= ESTABLISH_ACK.BAD_VERSION then - log.info(util.c("dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) - end - - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) - elseif dev_type == DEVICE_TYPE.CRD then - -- this is an attempt to establish a new coordinator session - local s_id = svsessions.establish_crd_session(nic, src_addr, i_seq_num, firmware_v) - - if s_id ~= false then - println(util.c("CRD (", firmware_v, ") [@", src_addr, "] \xbb connected")) - log.info(util.c("CRD_ESTABLISH: coordinator (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, facility.get_cooling_conf() }) - else - if last_ack ~= ESTABLISH_ACK.COLLISION then - log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator") - end - - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.COLLISION) - end - else - log.debug(util.c("illegal establish packet for device ", dev_type, " on coordinator channel")) - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) - end + _establish_crd(nic, packet, src_addr, i_seq_num, self.last_est_acks[src_addr]) else log.debug("CRD_ESTABLISH: establish packet length mismatch") _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end + elseif packet.type == MGMT_TYPE.PROBE then + -- connection probing + log.debug(util.c("PROBE_ACK: reporting open to CRD @", src_addr, " probed on ", nic.phy_name())) + _send_probe(nic, packet.scada_frame, PROBE_ACK.OPEN) else -- any other packet should be session related, discard it log.debug(util.c("discarding coordinator SCADA_MGMT packet without a known session from computer ", src_addr)) end elseif protocol == PROTOCOL.SCADA_CRDN then ---@cast packet crdn_frame - -- coordinator packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - else - -- any other packet should be session related, discard it - log.debug(util.c("discarding coordinator SCADA_CRDN packet without a known session from computer ", src_addr)) - end + -- coordinator packet, should be session related, discard it + log.debug(util.c("discarding coordinator SCADA_CRDN packet without a known session from computer ", src_addr)) else - log.debug(util.c("illegal packet type ", protocol, " on coordinator channel")) + log.debug(util.c("illegal packet type ", protocol, " on CRD channel")) end elseif r_chan == config.PKT_Channel then -- look for an associated session local session = svsessions.find_pdg_session(src_addr) - if protocol == PROTOCOL.SCADA_MGMT then + if session then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + elseif protocol == PROTOCOL.SCADA_MGMT then ---@cast packet mgmt_frame -- SCADA management packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - elseif packet.type == MGMT_TYPE.ESTABLISH then - -- establish a new session - local last_ack = self.last_est_acks[src_addr] - - -- validate packet and continue + if packet.type == MGMT_TYPE.ESTABLISH then + -- establish a new session: validate packet and continue if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then - local comms_v = packet.data[1] - local firmware_v = packet.data[2] - local dev_type = packet.data[3] - - if comms_v ~= comms.version then - if last_ack ~= ESTABLISH_ACK.BAD_VERSION then - log.info(util.c("dropping PDG establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) - end - - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) - elseif dev_type == DEVICE_TYPE.PKT then - -- this is an attempt to establish a new pocket diagnostic session - local s_id = svsessions.establish_pdg_session(nic, src_addr, i_seq_num, firmware_v) - - println(util.c("PKT (", firmware_v, ") [@", src_addr, "] \xbb connected")) - log.info(util.c("PDG_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) - else - log.debug(util.c("illegal establish packet for device ", dev_type, " on pocket channel")) - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) - end + _establish_pdg(nic, packet, src_addr, i_seq_num, self.last_est_acks[src_addr]) else log.debug("PDG_ESTABLISH: establish packet length mismatch") _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) @@ -509,14 +579,8 @@ function supervisor.comms(_version, fp_ok, facility) end elseif protocol == PROTOCOL.SCADA_CRDN then ---@cast packet crdn_frame - -- coordinator packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - else - -- any other packet should be session related, discard it - log.debug(util.c("discarding pocket SCADA_CRDN packet without a known session from computer ", src_addr)) - end + -- coordinator packet, should be session related, discard it + log.debug(util.c("discarding pocket SCADA_CRDN packet without a known session from computer ", src_addr)) else log.debug(util.c("illegal packet type ", protocol, " on pocket channel")) end From cb11ece73d1a74d5944dd155df5bc22b1cf79b2b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 19 Oct 2025 20:28:34 -0400 Subject: [PATCH 19/90] #580 WIP updated supervisor network config --- rtu/config/system.lua | 12 +- rtu/configure.lua | 8 +- supervisor/config/system.lua | 205 ++++++++++++++++++++++++++--------- supervisor/configure.lua | 9 +- 4 files changed, 169 insertions(+), 65 deletions(-) diff --git a/rtu/config/system.lua b/rtu/config/system.lua index 0f7a798..a6476db 100644 --- a/rtu/config/system.lua +++ b/rtu/config/system.lua @@ -110,9 +110,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) dis_pref(ini_cfg.WirelessModem) - local function on_wired_change(value) - tool_ctl.gen_modem_list(value) - end + local function on_wired_change(_) tool_ctl.gen_modem_list() end local wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=dis_pref} local wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change} @@ -212,7 +210,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) TextBox{parent=net_c_4,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."} TextBox{parent=net_c_4,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for wireless security on multiplayer servers. All devices on the same wireless network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_4,x=1,y=11,text="Auth Key (Wireless Only, Not Used when Wired)"} + TextBox{parent=net_c_4,x=1,y=11,text="Auth Key (Wireless Only, Not Used for Wired)"} local key, _ = TextField{parent=net_c_4,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg} local function censor_key(enable) key.censor(tri(enable, "*", nil)) end @@ -720,12 +718,14 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) end -- generate the list of available/assigned wired modems - function tool_ctl.gen_modem_list(enable) + function tool_ctl.gen_modem_list() modem_list.remove_all() + local enable = wired.get_value() + local function select(iface) tmp_cfg.WiredModem = iface - tool_ctl.gen_modem_list(true) + tool_ctl.gen_modem_list() end local modems = ppm.get_wired_modem_list() diff --git a/rtu/configure.lua b/rtu/configure.lua index a752342..48d2c99 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -82,7 +82,7 @@ local tool_ctl = { gen_peri_summary = nil, ---@type function gen_rs_summary = nil, ---@type function - gen_modem_list = function (_) end + gen_modem_list = function () end } ---@class rtu_config @@ -115,8 +115,8 @@ local fields = { { "SVR_Channel", "SVR Channel", 16240 }, { "RTU_Channel", "RTU Channel", 16242 }, { "ConnTimeout", "Connection Timeout", 5 }, - { "WirelessModem", "Wireless Modem", true }, - { "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", "" }, @@ -341,7 +341,7 @@ function configurator.configure(ask_config) local display = DisplayBox{window=term.current(),fg_bg=style.root} config_view(display) - tool_ctl.gen_modem_list(ini_cfg.WiredModem ~= false) + tool_ctl.gen_modem_list() while true do local event, param1, param2, param3, param4, param5 = util.pull_event() diff --git a/supervisor/config/system.lua b/supervisor/config/system.lua index 26bd33c..86ee2c2 100644 --- a/supervisor/config/system.lua +++ b/supervisor/config/system.lua @@ -1,4 +1,5 @@ local log = require("scada-common.log") +local ppm = require("scada-common.ppm") local types = require("scada-common.types") local util = require("scada-common.util") @@ -69,30 +70,84 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)} - TextBox{parent=net_c_1,x=1,y=1,text="Please set the network channels below."} - TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the 5 uniquely named channels must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg} + -- TextBox{parent=net_c_1,x=1,y=1,text="Please set the modem configuration below."} + -- TextBox{parent=net_c_1,x=1,y=3,height=3,text="You may use wireless (ender) and/or wired modems. Specific services will be assigned in the next step if you select more than one option.",fg_bg=g_lg_fg_bg} + -- -- TextBox{parent=net_c_1,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_1,x=1,y=8,width=18,text="Supervisor Channel"} - local svr_chan = NumberField{parent=net_c_1,x=21,y=8,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg} - TextBox{parent=net_c_1,x=29,y=8,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg} + -- local use_wireless = Checkbox{parent=net_c_1,x=1,y=7,label="Use Wireless Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black)} + -- local use_wired = Checkbox{parent=net_c_1,x=1,y=8,label="Use Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black)} - TextBox{parent=net_c_1,x=1,y=9,width=11,text="PLC Channel"} - local plc_chan = NumberField{parent=net_c_1,x=21,y=9,width=7,default=ini_cfg.PLC_Channel,min=1,max=65535,fg_bg=bw_fg_bg} - TextBox{parent=net_c_1,x=29,y=9,height=4,text="[PLC_CHANNEL]",fg_bg=g_lg_fg_bg} + -- local function submit_modems() + -- -- tmp_cfg. = use_wired.get_value() + -- net_pane.set_value(4) + -- end - TextBox{parent=net_c_1,x=1,y=10,width=19,text="RTU Gateway Channel"} - local rtu_chan = NumberField{parent=net_c_1,x=21,y=10,width=7,default=ini_cfg.RTU_Channel,min=1,max=65535,fg_bg=bw_fg_bg} - TextBox{parent=net_c_1,x=29,y=10,height=4,text="[RTU_CHANNEL]",fg_bg=g_lg_fg_bg} + -- PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + -- PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_modems,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=net_c_1,x=1,y=11,width=19,text="Coordinator Channel"} - local crd_chan = NumberField{parent=net_c_1,x=21,y=11,width=7,default=ini_cfg.CRD_Channel,min=1,max=65535,fg_bg=bw_fg_bg} - TextBox{parent=net_c_1,x=29,y=11,height=4,text="[CRD_CHANNEL]",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_1,x=1,y=1,text="Please select the network interface(s)."} + TextBox{parent=net_c_1,x=41,y=1,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision - TextBox{parent=net_c_1,x=1,y=12,width=14,text="Pocket Channel"} - local pkt_chan = NumberField{parent=net_c_1,x=21,y=12,width=7,default=ini_cfg.PKT_Channel,min=1,max=65535,fg_bg=bw_fg_bg} - TextBox{parent=net_c_1,x=29,y=12,height=4,text="[PKT_CHANNEL]",fg_bg=g_lg_fg_bg} + local function on_wired_change(_) tool_ctl.gen_modem_list() end - local chan_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="Please set all channels.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=function()end} + local wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change} + TextBox{parent=net_c_1,x=3,y=6,text="MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)} + TextBox{parent=net_c_1,x=3,y=7,text="connecting to peripherals will cause problems",fg_bg=g_lg_fg_bg} + local modem_list = ListBox{parent=net_c_1,x=1,y=8,height=5,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} + + local modem_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + + local function submit_interfaces() + tmp_cfg.WirelessModem = wireless.get_value() + + if not wired.get_value() then + tmp_cfg.WiredModem = false + tool_ctl.gen_modem_list() + end + + if not (wired.get_value() or wireless.get_value()) then + modem_err.set_value("Please select a modem type.") + modem_err.show() + elseif wired.get_value() and type(tmp_cfg.WiredModem) ~= "string" then + modem_err.set_value("Please select a wired modem.") + modem_err.show() + else + net_pane.set_value(2) + modem_err.hide(true) + end + end + + PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_interfaces,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + TextBox{parent=net_c_3,x=1,y=1,text="Please set the network channels below."} + TextBox{parent=net_c_3,x=1,y=3,height=4,text="Each of the 5 uniquely named channels must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg} + + TextBox{parent=net_c_3,x=1,y=8,width=18,text="Supervisor Channel"} + local svr_chan = NumberField{parent=net_c_3,x=21,y=8,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_3,x=29,y=8,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg} + + TextBox{parent=net_c_3,x=1,y=9,width=11,text="PLC Channel"} + local plc_chan = NumberField{parent=net_c_3,x=21,y=9,width=7,default=ini_cfg.PLC_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_3,x=29,y=9,height=4,text="[PLC_CHANNEL]",fg_bg=g_lg_fg_bg} + + TextBox{parent=net_c_3,x=1,y=10,width=19,text="RTU Gateway Channel"} + local rtu_chan = NumberField{parent=net_c_3,x=21,y=10,width=7,default=ini_cfg.RTU_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_3,x=29,y=10,height=4,text="[RTU_CHANNEL]",fg_bg=g_lg_fg_bg} + + TextBox{parent=net_c_3,x=1,y=11,width=19,text="Coordinator Channel"} + local crd_chan = NumberField{parent=net_c_3,x=21,y=11,width=7,default=ini_cfg.CRD_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_3,x=29,y=11,height=4,text="[CRD_CHANNEL]",fg_bg=g_lg_fg_bg} + + TextBox{parent=net_c_3,x=1,y=12,width=14,text="Pocket Channel"} + local pkt_chan = NumberField{parent=net_c_3,x=21,y=12,width=7,default=ini_cfg.PKT_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_3,x=29,y=12,height=4,text="[PKT_CHANNEL]",fg_bg=g_lg_fg_bg} + + local chan_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Please set all channels.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_channels() local svr_c, plc_c, rtu_c = tonumber(svr_chan.get_value()), tonumber(plc_chan.get_value()), tonumber(rtu_chan.get_value()) @@ -100,61 +155,47 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit if svr_c ~= nil and plc_c ~= nil and rtu_c ~= nil and crd_c ~= nil and pkt_c ~= nil then tmp_cfg.SVR_Channel, tmp_cfg.PLC_Channel, tmp_cfg.RTU_Channel = svr_c, plc_c, rtu_c tmp_cfg.CRD_Channel, tmp_cfg.PKT_Channel = crd_c, pkt_c - net_pane.set_value(2) + net_pane.set_value(4) chan_err.hide(true) else chan_err.show() end end - PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=net_c_2,x=1,y=1,text="Please set the connection timeouts below."} - TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally should not need to modify these. On slow servers, you can try to increase this to make the system wait longer before assuming a disconnection. The default for all is 5 seconds.",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_4,x=1,y=1,text="Please set the connection timeouts below."} + TextBox{parent=net_c_4,x=1,y=3,height=4,text="You generally should not need to modify these. On slow servers, you can try to increase this to make the system wait longer before assuming a disconnection. The default for all is 5 seconds.",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_2,x=1,y=8,width=11,text="PLC Timeout"} - local plc_timeout = NumberField{parent=net_c_2,x=21,y=8,width=7,default=ini_cfg.PLC_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} + TextBox{parent=net_c_4,x=1,y=8,width=11,text="PLC Timeout"} + local plc_timeout = NumberField{parent=net_c_4,x=21,y=8,width=7,default=ini_cfg.PLC_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} - TextBox{parent=net_c_2,x=1,y=9,width=19,text="RTU Gateway Timeout"} - local rtu_timeout = NumberField{parent=net_c_2,x=21,y=9,width=7,default=ini_cfg.RTU_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} + TextBox{parent=net_c_4,x=1,y=9,width=19,text="RTU Gateway Timeout"} + local rtu_timeout = NumberField{parent=net_c_4,x=21,y=9,width=7,default=ini_cfg.RTU_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} - TextBox{parent=net_c_2,x=1,y=10,width=19,text="Coordinator Timeout"} - local crd_timeout = NumberField{parent=net_c_2,x=21,y=10,width=7,default=ini_cfg.CRD_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} + TextBox{parent=net_c_4,x=1,y=10,width=19,text="Coordinator Timeout"} + local crd_timeout = NumberField{parent=net_c_4,x=21,y=10,width=7,default=ini_cfg.CRD_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} - TextBox{parent=net_c_2,x=1,y=11,width=14,text="Pocket Timeout"} - local pkt_timeout = NumberField{parent=net_c_2,x=21,y=11,width=7,default=ini_cfg.PKT_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} + TextBox{parent=net_c_4,x=1,y=11,width=14,text="Pocket Timeout"} + local pkt_timeout = NumberField{parent=net_c_4,x=21,y=11,width=7,default=ini_cfg.PKT_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} - TextBox{parent=net_c_2,x=29,y=8,height=4,width=7,text="seconds\nseconds\nseconds\nseconds",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_4,x=29,y=8,height=4,width=7,text="seconds\nseconds\nseconds\nseconds",fg_bg=g_lg_fg_bg} - local ct_err = TextBox{parent=net_c_2,x=8,y=14,width=35,text="Please set all connection timeouts.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local ct_err = TextBox{parent=net_c_4,x=8,y=14,width=35,text="Please set all connection timeouts.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_timeouts() local plc_cto, rtu_cto, crd_cto, pkt_cto = tonumber(plc_timeout.get_value()), tonumber(rtu_timeout.get_value()), tonumber(crd_timeout.get_value()), tonumber(pkt_timeout.get_value()) if plc_cto ~= nil and rtu_cto ~= nil and crd_cto ~= nil and pkt_cto ~= nil then tmp_cfg.PLC_Timeout, tmp_cfg.RTU_Timeout, tmp_cfg.CRD_Timeout, tmp_cfg.PKT_Timeout = plc_cto, rtu_cto, crd_cto, pkt_cto - net_pane.set_value(3) + net_pane.set_value(5) ct_err.hide(true) else ct_err.show() end end - PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_4,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_4,x=44,y=14,text="Next \x1a",callback=submit_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=net_c_3,x=1,y=1,text="Please set the modem configuration below."} - TextBox{parent=net_c_3,x=1,y=3,height=3,text="Communications with the coordinator,",fg_bg=g_lg_fg_bg} - -- TextBox{parent=net_c_3,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg} - - local use_wired = Checkbox{parent=net_c_3,x=1,y=12,label="Use Wired Modem",default=ini_cfg.ExtChargeIdling,box_fg_bg=cpair(colors.yellow,colors.black)} - - local function submit_modems() - -- tmp_cfg. = use_wired.get_value() - net_pane.set_value(4) - end - - PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_modems,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - - TextBox{parent=net_c_5,x=1,y=1,text="Please set the trusted range below."} - TextBox{parent=net_c_5,x=1,y=3,height=3,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_5,x=1,y=1,text="Please set the wireless trusted range below."} + TextBox{parent=net_c_5,x=1,y=3,height=3,text="Setting this to a value larger than 0 prevents wireless connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} TextBox{parent=net_c_5,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg} local range = NumberField{parent=net_c_5,x=1,y=10,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg} @@ -174,9 +215,9 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit PushButton{parent=net_c_5,x=44,y=14,text="Next \x1a",callback=submit_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} TextBox{parent=net_c_6,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."} - TextBox{parent=net_c_6,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_6,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for wireless security on multiplayer servers. All devices on the same wireless network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_6,x=1,y=11,text="Facility Auth Key"} + TextBox{parent=net_c_6,x=1,y=11,text="Auth Key (Wireless Only, Not Used for Wired)"} local key, _ = TextField{parent=net_c_6,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg} local function censor_key(enable) key.censor(tri(enable, "*", nil)) end @@ -719,6 +760,62 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit end end + -- generate the list of available/assigned wired modems + function tool_ctl.gen_modem_list() + modem_list.remove_all() + + local enable = wired.get_value() + + local function select(iface) + tmp_cfg.WiredModem = iface + tool_ctl.gen_modem_list() + end + + local modems = ppm.get_wired_modem_list() + local missing = { tmp = true, ini = true } + + for iface, _ in pairs(modems) do + if ini_cfg.WiredModem == iface then + missing.ini = false + elseif tmp_cfg.WiredModem == iface then + missing.tmp = false + end + end + + if missing.tmp and tmp_cfg.WiredModem then + local line = Div{parent=modem_list,x=1,y=1,height=1} + + TextBox{parent=line,x=1,y=1,width=4,text="Used",fg_bg=cpair(tri(enable,colors.blue,colors.gray),colors.white)} + PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg}.disable() + TextBox{parent=line,x=15,y=1,text="[missing]",fg_bg=cpair(colors.red,colors.white)} + TextBox{parent=line,x=25,y=1,text=tmp_cfg.WiredModem} + end + + if missing.ini and ini_cfg.WiredModem and (tmp_cfg.WiredModem ~= ini_cfg.WiredModem) then + local line = Div{parent=modem_list,x=1,y=1,height=1} + local used = tmp_cfg.WiredModem == ini_cfg.WiredModem + + TextBox{parent=line,x=1,y=1,width=4,text=tri(used,"Used","----"),fg_bg=cpair(tri(used and enable,colors.blue,colors.gray),colors.white)} + local select_btn = PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()select(ini_cfg.WiredModem)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg} + TextBox{parent=line,x=15,y=1,text="[missing]",fg_bg=cpair(colors.red,colors.white)} + TextBox{parent=line,x=25,y=1,text=ini_cfg.WiredModem} + + if used or not enable then select_btn.disable() end + end + + -- list wired modems + for iface, _ in pairs(modems) do + local line = Div{parent=modem_list,x=1,y=1,height=1} + local used = tmp_cfg.WiredModem == iface + + TextBox{parent=line,x=1,y=1,width=4,text=tri(used,"Used","----"),fg_bg=cpair(tri(used and enable,colors.blue,colors.gray),colors.white)} + local select_btn = PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()select(iface)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg} + TextBox{parent=line,x=15,y=1,text=iface} + + if used or not enable then select_btn.disable() end + end + end + --#endregion end diff --git a/supervisor/configure.lua b/supervisor/configure.lua index aed0aa2..6f8dc90 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -75,7 +75,9 @@ local tool_ctl = { cooling_elems = {}, ---@type { line: Div, turbines: NumberField, boilers: NumberField, tank: Checkbox }[] tank_elems = {}, ---@type { div: Div, tank_opt: Radio2D, no_tank: TextBox }[] - aux_cool_elems = {} ---@type { line: Div, enable: Checkbox }[] + aux_cool_elems = {}, ---@type { line: Div, enable: Checkbox }[] + + gen_modem_list = function () end } ---@class svr_config @@ -302,11 +304,14 @@ function configurator.configure(ask_config) tool_ctl.has_config = load_settings(ini_cfg) -- these need to be initialized as they are used before being set + tmp_cfg.WiredModem = ini_cfg.WiredModem tmp_cfg.FacilityTankMode = ini_cfg.FacilityTankMode tmp_cfg.TankFluidTypes = { table.unpack(ini_cfg.TankFluidTypes) } reset_term() + ppm.mount_all() + -- set overridden colors for i = 1, #style.colors do term.setPaletteColor(style.colors[i].c, style.colors[i].hex) @@ -316,6 +321,8 @@ function configurator.configure(ask_config) local display = DisplayBox{window=term.current(),fg_bg=style.root} config_view(display) + tool_ctl.gen_modem_list() + while true do local event, param1, param2, param3 = util.pull_event() From 1890f0a9839899d0e8100249d6c5a02d4618c76c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Oct 2025 14:21:40 -0400 Subject: [PATCH 20/90] remove needless empty graphics callbacks --- coordinator/config/hmi.lua | 8 ++++---- coordinator/config/system.lua | 6 +++--- coordinator/ui/components/process_ctl.lua | 2 +- coordinator/ui/components/unit_detail.lua | 2 +- pocket/config/system.lua | 8 ++++---- pocket/ui/apps/process.lua | 2 +- reactor-plc/config/system.lua | 6 +++--- rtu/config/redstone.lua | 2 +- rtu/config/system.lua | 6 +++--- supervisor/config/system.lua | 4 ++-- 10 files changed, 23 insertions(+), 23 deletions(-) diff --git a/coordinator/config/hmi.lua b/coordinator/config/hmi.lua index 6f52cf1..06c86c6 100644 --- a/coordinator/config/hmi.lua +++ b/coordinator/config/hmi.lua @@ -237,17 +237,17 @@ function hmi.create(tool_ctl, main_pane, cfg_sys, divs, style) TextBox{parent=crd_c_1,x=1,y=1,height=2,text="You can customize the UI with the interface options below."} TextBox{parent=crd_c_1,x=1,y=4,text="Clock Time Format"} - tool_ctl.clock_fmt = RadioButton{parent=crd_c_1,x=1,y=5,default=util.trinary(ini_cfg.Time24Hour,1,2),options={"24-Hour","12-Hour"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} + tool_ctl.clock_fmt = RadioButton{parent=crd_c_1,x=1,y=5,default=util.trinary(ini_cfg.Time24Hour,1,2),options={"24-Hour","12-Hour"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} TextBox{parent=crd_c_1,x=20,y=4,text="Po/Pu Pellet Color"} TextBox{parent=crd_c_1,x=39,y=4,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision - tool_ctl.pellet_color = RadioButton{parent=crd_c_1,x=20,y=5,default=util.trinary(ini_cfg.GreenPuPellet,1,2),options={"Green Pu/Cyan Po","Cyan Pu/Green Po (Mek 10.4+)"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} + tool_ctl.pellet_color = RadioButton{parent=crd_c_1,x=20,y=5,default=util.trinary(ini_cfg.GreenPuPellet,1,2),options={"Green Pu/Cyan Po","Cyan Pu/Green Po (Mek 10.4+)"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} TextBox{parent=crd_c_1,x=1,y=8,text="Temperature Scale"} - tool_ctl.temp_scale = RadioButton{parent=crd_c_1,x=1,y=9,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} + tool_ctl.temp_scale = RadioButton{parent=crd_c_1,x=1,y=9,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} TextBox{parent=crd_c_1,x=20,y=8,text="Energy Scale"} - tool_ctl.energy_scale = RadioButton{parent=crd_c_1,x=20,y=9,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} + tool_ctl.energy_scale = RadioButton{parent=crd_c_1,x=20,y=9,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} local function submit_ui_opts() tmp_cfg.Time24Hour = tool_ctl.clock_fmt.get_value() == 1 diff --git a/coordinator/config/system.lua b/coordinator/config/system.lua index 5becf55..0f24b62 100644 --- a/coordinator/config/system.lua +++ b/coordinator/config/system.lua @@ -188,7 +188,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) TextBox{parent=log_c_1,x=1,y=1,text="Please configure logging below."} TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"} - local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink} + local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink} TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"} local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg} @@ -230,10 +230,10 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg} TextBox{parent=clr_c_1,x=1,y=7,text="Main UI Theme"} - local main_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.MainTheme,options=themes.UI_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta} + local main_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.MainTheme,options=themes.UI_THEME_NAMES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta} TextBox{parent=clr_c_1,x=18,y=7,text="Front Panel Theme"} - local fp_theme = RadioButton{parent=clr_c_1,x=18,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta} + local fp_theme = RadioButton{parent=clr_c_1,x=18,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta} TextBox{parent=clr_c_2,x=1,y=1,height=6,text="This system uses color heavily to distinguish ok and not, with some indicators using many colors. By selecting a mode below, indicators will change as shown. For non-standard modes, indicators with more than two colors will usually be split up."} diff --git a/coordinator/ui/components/process_ctl.lua b/coordinator/ui/components/process_ctl.lua index 780905e..25b6530 100644 --- a/coordinator/ui/components/process_ctl.lua +++ b/coordinator/ui/components/process_ctl.lua @@ -246,7 +246,7 @@ local function new_view(root, x, y) ------------------------- local ctl_opts = { "Monitored Max Burn", "Combined Burn Rate", "Charge Level", "Generation Rate" } - local mode = RadioButton{parent=proc,x=34,y=1,options=ctl_opts,callback=function()end,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.purple} + local mode = RadioButton{parent=proc,x=34,y=1,options=ctl_opts,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.purple} mode.register(facility.ps, "process_mode", mode.set_value) diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 44d082c..5e16f07 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -486,7 +486,7 @@ local function init(parent, id) local auto_ctl = Rectangle{parent=main,border=border(1,colors.purple,true),thin=true,width=13,height=15,x=32,y=37} local auto_div = Div{parent=auto_ctl,width=13,height=15,x=1,y=1} - local group = RadioButton{parent=auto_div,options=types.AUTO_GROUP_NAMES,callback=function()end,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.purple} + local group = RadioButton{parent=auto_div,options=types.AUTO_GROUP_NAMES,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.purple} group.register(u_ps, "auto_group_id", function (gid) group.set_value(gid + 1) end) diff --git a/pocket/config/system.lua b/pocket/config/system.lua index c3251b1..32fc70a 100644 --- a/pocket/config/system.lua +++ b/pocket/config/system.lua @@ -63,7 +63,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) TextBox{parent=ui_c_1,y=4,text="Po/Pu Pellet Color"} TextBox{parent=ui_c_1,x=20,y=4,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision - local pellet_color = RadioButton{parent=ui_c_1,y=5,default=util.trinary(ini_cfg.GreenPuPellet,1,2),options={"Green Pu/Cyan Po","Cyan Pu/Green Po"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} + local pellet_color = RadioButton{parent=ui_c_1,y=5,default=util.trinary(ini_cfg.GreenPuPellet,1,2),options={"Green Pu/Cyan Po","Cyan Pu/Green Po"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} TextBox{parent=ui_c_1,y=8,height=4,text="In Mekanism 10.4 and later, pellet colors now match gas colors (Cyan Pu/Green Po).",fg_bg=g_lg_fg_bg} @@ -78,10 +78,10 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) TextBox{parent=ui_c_2,x=1,y=1,height=3,text="You may customize units below."} TextBox{parent=ui_c_2,x=1,y=4,text="Temperature Scale"} - local temp_scale = RadioButton{parent=ui_c_2,x=1,y=5,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} + local temp_scale = RadioButton{parent=ui_c_2,x=1,y=5,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} TextBox{parent=ui_c_2,x=1,y=10,text="Energy Scale"} - local energy_scale = RadioButton{parent=ui_c_2,x=1,y=11,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} + local energy_scale = RadioButton{parent=ui_c_2,x=1,y=11,default=ini_cfg.EnergyScale,options=types.ENERGY_SCALE_NAMES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} local function submit_ui_units() tmp_cfg.TempScale = temp_scale.get_value() @@ -216,7 +216,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) TextBox{parent=log_c_1,x=1,y=1,text="Configure logging below."} TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"} - local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink} + local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink} TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"} local path = TextField{parent=log_c_1,x=1,y=8,width=24,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg} diff --git a/pocket/ui/apps/process.lua b/pocket/ui/apps/process.lua index ed03d71..196ed4e 100644 --- a/pocket/ui/apps/process.lua +++ b/pocket/ui/apps/process.lua @@ -162,7 +162,7 @@ local function new_view(root) TextBox{parent=o_div,y=1,text="Process Options",alignment=ALIGN.CENTER} local ctl_opts = { "Monitored Max Burn", "Combined Burn Rate", "Charge Level", "Generation Rate" } - local mode = RadioButton{parent=o_div,x=1,y=3,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.purple,dis_fg_bg=style.btn_disable} + local mode = RadioButton{parent=o_div,x=1,y=3,options=ctl_opts,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.purple,dis_fg_bg=style.btn_disable} mode.register(f_ps, "process_mode", mode.set_value) diff --git a/reactor-plc/config/system.lua b/reactor-plc/config/system.lua index 8d37809..7b51a9a 100644 --- a/reactor-plc/config/system.lua +++ b/reactor-plc/config/system.lua @@ -154,7 +154,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) function self.bundled_emcool(en) if en then color.enable() else color.disable() end end TextBox{parent=plc_c_5,x=1,y=1,height=5,text="Advanced Options"} - local invert = Checkbox{parent=plc_c_5,x=1,y=3,label="Invert",default=ini_cfg.EmerCoolInvert,box_fg_bg=cpair(colors.orange,colors.black),callback=function()end} + local invert = Checkbox{parent=plc_c_5,x=1,y=3,label="Invert",default=ini_cfg.EmerCoolInvert,box_fg_bg=cpair(colors.orange,colors.black)} TextBox{parent=plc_c_5,x=10,y=3,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision TextBox{parent=plc_c_5,x=3,y=4,height=4,text="Digital I/O is already inverted (or not) based on intended use. If you have a non-standard setup, you can use this option to avoid needing a redstone inverter.",fg_bg=cpair(colors.gray,colors.lightGray)} PushButton{parent=plc_c_5,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} @@ -283,7 +283,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) TextBox{parent=log_c_1,x=1,y=1,text="Please configure logging below."} TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"} - local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink} + local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink} TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"} local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg} @@ -329,7 +329,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg} TextBox{parent=clr_c_1,x=1,y=7,text="Front Panel Theme"} - local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta} + local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta} TextBox{parent=clr_c_2,x=1,y=1,height=6,text="This system uses color heavily to distinguish ok and not, with some indicators using many colors. By selecting a mode below, indicators will change as shown. For non-standard modes, indicators with more than two colors will be split up."} diff --git a/rtu/config/redstone.lua b/rtu/config/redstone.lua index 74506f3..fb632e0 100644 --- a/rtu/config/redstone.lua +++ b/rtu/config/redstone.lua @@ -484,7 +484,7 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style) PushButton{parent=rs_c_8,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} TextBox{parent=rs_c_9,x=1,y=1,height=5,text="Advanced Options"} - self.rs_cfg_inverted = Checkbox{parent=rs_c_9,x=1,y=3,label="Invert",default=false,box_fg_bg=cpair(colors.red,colors.black),callback=function()end,disable_fg_bg=g_lg_fg_bg} + self.rs_cfg_inverted = Checkbox{parent=rs_c_9,x=1,y=3,label="Invert",default=false,box_fg_bg=cpair(colors.red,colors.black),disable_fg_bg=g_lg_fg_bg} TextBox{parent=rs_c_9,x=3,y=4,height=4,text="Digital I/O is already inverted (or not) based on intended use. If you have a non-standard setup, you can use this option to avoid needing a redstone inverter.",fg_bg=cpair(colors.gray,colors.lightGray)} PushButton{parent=rs_c_9,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} diff --git a/rtu/config/system.lua b/rtu/config/system.lua index a6476db..b121db9 100644 --- a/rtu/config/system.lua +++ b/rtu/config/system.lua @@ -99,7 +99,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) TextBox{parent=net_c_1,x=1,y=1,text="Please select the network interface(s)."} TextBox{parent=net_c_1,x=41,y=1,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision - local wl_pref = Checkbox{parent=net_c_1,x=30,y=3,label="Prefer Wireless",default=ini_cfg.PreferWireless,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg,callback=function()end} + local wl_pref = Checkbox{parent=net_c_1,x=30,y=3,label="Prefer Wireless",default=ini_cfg.PreferWireless,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} local function dis_pref(value) if not value then @@ -245,7 +245,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) TextBox{parent=log_c_1,x=1,y=1,text="Please configure logging below."} TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"} - local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink} + local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink} TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"} local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg} @@ -287,7 +287,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg} TextBox{parent=clr_c_1,x=1,y=7,text="Front Panel Theme"} - local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta} + local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta} TextBox{parent=clr_c_2,x=1,y=1,height=6,text="This system uses color heavily to distinguish ok and not, with some indicators using many colors. By selecting a mode below, indicators will change as shown. For non-standard modes, indicators with more than two colors will be split up."} diff --git a/supervisor/config/system.lua b/supervisor/config/system.lua index 86ee2c2..0001fe2 100644 --- a/supervisor/config/system.lua +++ b/supervisor/config/system.lua @@ -252,7 +252,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit TextBox{parent=log_c_1,x=1,y=1,text="Please configure logging below."} TextBox{parent=log_c_1,x=1,y=3,text="Log File Mode"} - local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink} + local mode = RadioButton{parent=log_c_1,x=1,y=4,default=ini_cfg.LogMode+1,options={"Append on Startup","Replace on Startup"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.pink} TextBox{parent=log_c_1,x=1,y=7,text="Log File Path"} local path = TextField{parent=log_c_1,x=1,y=8,width=49,height=1,value=ini_cfg.LogPath,max_len=128,fg_bg=bw_fg_bg} @@ -294,7 +294,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit TextBox{parent=clr_c_1,x=1,y=4,height=2,text="Click 'Accessibility' below to access colorblind assistive options.",fg_bg=g_lg_fg_bg} TextBox{parent=clr_c_1,x=1,y=7,text="Front Panel Theme"} - local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta} + local fp_theme = RadioButton{parent=clr_c_1,x=1,y=8,default=ini_cfg.FrontPanelTheme,options=themes.FP_THEME_NAMES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.magenta} TextBox{parent=clr_c_2,x=1,y=1,height=6,text="This system uses color heavily to distinguish ok and not, with some indicators using many colors. By selecting a mode below, indicators will change as shown. For non-standard modes, indicators with more than two colors will be split up."} From c62ec1e786a0fb7a30e02e032437b44459a65018 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Oct 2025 15:02:03 -0400 Subject: [PATCH 21/90] include tank fluid types and aux coolant correctly in load_legacy and save_and_continue --- supervisor/config/facility.lua | 16 ++++++---------- supervisor/config/system.lua | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/supervisor/config/facility.lua b/supervisor/config/facility.lua index b67e8f4..21ea1c7 100644 --- a/supervisor/config/facility.lua +++ b/supervisor/config/facility.lua @@ -18,8 +18,6 @@ local tri = util.trinary local cpair = core.cpair local self = { - tank_fluid_opts = {}, ---@type Radio2D[] - vis_draw = nil, ---@type function draw_fluid_ops = nil, ---@type function @@ -621,7 +619,7 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style) if type == 0 then type = 1 end - self.tank_fluid_opts[i] = nil + tool_ctl.tank_fluid_opts[i] = nil if tank_list[i] == 1 then local row = Div{parent=tank_fluid_list,height=2} @@ -636,7 +634,7 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style) tank_fluid.disable() end - self.tank_fluid_opts[i] = tank_fluid + tool_ctl.tank_fluid_opts[i] = tank_fluid elseif tank_list[i] == 2 then local row = Div{parent=tank_fluid_list,height=2} @@ -661,7 +659,7 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style) tank_fluid.disable() end - self.tank_fluid_opts[i] = tank_fluid + tool_ctl.tank_fluid_opts[i] = tank_fluid next_f = next_f + 1 end @@ -676,11 +674,9 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style) tmp_cfg.TankFluidTypes = {} for i = 1, #tmp_cfg.FacilityTankList do - if self.tank_fluid_opts[i] ~= nil then - tmp_cfg.TankFluidTypes[i] = self.tank_fluid_opts[i].get_value() - else - tmp_cfg.TankFluidTypes[i] = 0 - end + if tool_ctl.tank_fluid_opts[i] ~= nil then + tmp_cfg.TankFluidTypes[i] = tool_ctl.tank_fluid_opts[i].get_value() + else tmp_cfg.TankFluidTypes[i] = 0 end end fac_pane.set_value(8) diff --git a/supervisor/config/system.lua b/supervisor/config/system.lua index 0001fe2..5d6419d 100644 --- a/supervisor/config/system.lua +++ b/supervisor/config/system.lua @@ -463,6 +463,17 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit try_set(tool_ctl.aux_cool_elems[i].enable, ini_cfg.AuxiliaryCoolant[i]) end + for i = 1, #ini_cfg.TankFluidTypes do + if tool_ctl.tank_fluid_opts[i] then + if (ini_cfg.TankFluidTypes[i] > 0) then + tool_ctl.tank_fluid_opts[i].enable() + tool_ctl.tank_fluid_opts[i].set_value(ini_cfg.TankFluidTypes[i]) + else + tool_ctl.tank_fluid_opts[i].disable() + end + end + end + tool_ctl.en_fac_tanks.set_value(ini_cfg.FacilityTankMode > 0) tool_ctl.view_cfg.enable() @@ -581,6 +592,9 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit tmp_cfg.FacilityTankList, tmp_cfg.FacilityTankConns = facility.generate_tank_list_and_conns(tmp_cfg.FacilityTankMode, tmp_cfg.FacilityTankDefs) + for i = 1, tmp_cfg.UnitCount do tmp_cfg.AuxiliaryCoolant[i] = false end + for i = 1, tmp_cfg.FacilityTankList do tmp_cfg.TankFluidTypes[i] = types.COOLANT_TYPE.WATER end + tmp_cfg.SVR_Channel = config.SVR_CHANNEL tmp_cfg.PLC_Channel = config.PLC_CHANNEL tmp_cfg.RTU_Channel = config.RTU_CHANNEL From d412f61a5f3af4ceb98a2baaafef62b2cd7006c0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Oct 2025 15:16:02 -0400 Subject: [PATCH 22/90] tool_ctl tank_fluid_opts for prior commit --- supervisor/configure.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/supervisor/configure.lua b/supervisor/configure.lua index 6f8dc90..bbde9f8 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -69,6 +69,7 @@ local tool_ctl = { num_units = nil, ---@type NumberField en_fac_tanks = nil, ---@type Checkbox tank_mode = nil, ---@type RadioButton + tank_fluid_opts = {}, ---@type Radio2D[] gen_summary = nil, ---@type function load_legacy = nil, ---@type function From 22208e91aa55a43c3c6a5d93f72a6b814412ec4f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Oct 2025 15:17:15 -0400 Subject: [PATCH 23/90] #580 supervisor network configurator updates --- scada-common/types.lua | 6 +- supervisor/backplane.lua | 15 ++-- supervisor/config/system.lua | 161 ++++++++++++++++++++++++++++------- supervisor/configure.lua | 31 +++---- 4 files changed, 157 insertions(+), 56 deletions(-) diff --git a/scada-common/types.lua b/scada-common/types.lua index c4d6c53..32bf265 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -214,9 +214,9 @@ end ---@enum LISTEN_MODE types.LISTEN_MODE = { - WIRELESS = 0, - WIRED = 1, - ALL = 2 + WIRELESS = 1, + WIRED = 2, + ALL = 3 } ---@enum TEMP_SCALE diff --git a/supervisor/backplane.lua b/supervisor/backplane.lua index 9974085..1818a0b 100644 --- a/supervisor/backplane.lua +++ b/supervisor/backplane.lua @@ -5,10 +5,13 @@ local log = require("scada-common.log") local network = require("scada-common.network") local ppm = require("scada-common.ppm") +local types = require("scada-common.types") local util = require("scada-common.util") local databus = require("supervisor.databus") +local LISTEN_MODE = types.LISTEN_MODE + ---@class supervisor_backplane local backplane = {} @@ -45,9 +48,9 @@ function backplane.init(config, println) nic.closeAll() - if config.PLC_Listen > 0 then nic.open(config.PLC_Channel) end - if config.RTU_Listen > 0 then nic.open(config.RTU_Channel) end - if config.CRD_Listen > 0 then nic.open(config.CRD_Channel) end + if config.PLC_Listen ~= LISTEN_MODE.WIRELESS then nic.open(config.PLC_Channel) end + if config.RTU_Listen ~= LISTEN_MODE.WIRELESS then nic.open(config.RTU_Channel) end + if config.CRD_Listen ~= LISTEN_MODE.WIRELESS then nic.open(config.CRD_Channel) end databus.tx_hw_wd_modem(true) end @@ -67,9 +70,9 @@ function backplane.init(config, println) nic.closeAll() - if config.PLC_Listen % 2 == 0 then nic.open(config.PLC_Channel) end - if config.RTU_Listen % 2 == 0 then nic.open(config.RTU_Channel) end - if config.CRD_Listen % 2 == 0 then nic.open(config.CRD_Channel) end + if config.PLC_Listen ~= LISTEN_MODE.WIRED then nic.open(config.PLC_Channel) end + if config.RTU_Listen ~= LISTEN_MODE.WIRED then nic.open(config.RTU_Channel) end + if config.CRD_Listen ~= LISTEN_MODE.WIRED then nic.open(config.CRD_Channel) end if config.PocketEnabled then nic.open(config.PKT_Channel) end databus.tx_hw_wl_modem(true) diff --git a/supervisor/config/system.lua b/supervisor/config/system.lua index 5d6419d..b453832 100644 --- a/supervisor/config/system.lua +++ b/supervisor/config/system.lua @@ -15,6 +15,7 @@ local TextBox = require("graphics.elements.TextBox") local Checkbox = require("graphics.elements.controls.Checkbox") local PushButton = require("graphics.elements.controls.PushButton") +local Radio2D = require("graphics.elements.controls.Radio2D") local RadioButton = require("graphics.elements.controls.RadioButton") local NumberField = require("graphics.elements.form.NumberField") @@ -26,14 +27,22 @@ local tri = util.trinary local cpair = core.cpair +local LISTEN_MODE = types.LISTEN_MODE + local RIGHT = core.ALIGN.RIGHT local self = { importing_legacy = false, + update_net_cfg = nil, ---@type function show_auth_key = nil, ---@type function + + pkt_test = nil, ---@type Checkbox + pkt_chan = nil, ---@type NumberField + pkt_timeout = nil, ---@type NumberField show_key_btn = nil, ---@type PushButton auth_key_textbox = nil, ---@type TextBox + auth_key_value = "" } @@ -70,27 +79,13 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)} - -- TextBox{parent=net_c_1,x=1,y=1,text="Please set the modem configuration below."} - -- TextBox{parent=net_c_1,x=1,y=3,height=3,text="You may use wireless (ender) and/or wired modems. Specific services will be assigned in the next step if you select more than one option.",fg_bg=g_lg_fg_bg} - -- -- TextBox{parent=net_c_1,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg} - - -- local use_wireless = Checkbox{parent=net_c_1,x=1,y=7,label="Use Wireless Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black)} - -- local use_wired = Checkbox{parent=net_c_1,x=1,y=8,label="Use Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black)} - - -- local function submit_modems() - -- -- tmp_cfg. = use_wired.get_value() - -- net_pane.set_value(4) - -- end - - -- PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - -- PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_modems,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=net_c_1,x=1,y=1,text="Please select the network interface(s)."} TextBox{parent=net_c_1,x=41,y=1,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision local function on_wired_change(_) tool_ctl.gen_modem_list() end - local wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=function()end} + local wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black)} + TextBox{parent=net_c_1,x=24,y=3,text="(required for Pocket)",fg_bg=g_lg_fg_bg} local wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change} TextBox{parent=net_c_1,x=3,y=6,text="MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)} TextBox{parent=net_c_1,x=3,y=7,text="connecting to peripherals will cause problems",fg_bg=g_lg_fg_bg} @@ -113,6 +108,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit modem_err.set_value("Please select a wired modem.") modem_err.show() else + self.update_net_cfg() net_pane.set_value(2) modem_err.hide(true) end @@ -121,8 +117,63 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_interfaces,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + TextBox{parent=net_c_2,x=1,y=1,text="Please assign device connection interfaces if you selected multiple network interfaces."} + TextBox{parent=net_c_2,x=1,y=4,text="Reactor PLC\nRTU Gateway\nCoordinator",fg_bg=g_lg_fg_bg} + local opts = { "Wireless", "Wired", "Both" } + local plc_listen = Radio2D{parent=net_c_2,x=14,y=4,rows=1,columns=3,default=ini_cfg.PLC_Listen+1,options=opts,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lightBlue,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg} + local rtu_listen = Radio2D{parent=net_c_2,x=14,rows=1,columns=3,default=ini_cfg.RTU_Listen+1,options=opts,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lightBlue,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg} + local crd_listen = Radio2D{parent=net_c_2,x=14,rows=1,columns=3,default=ini_cfg.CRD_Listen+1,options=opts,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lightBlue,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg} + + local function on_pocket_en(en) + if not en then + self.pkt_test.set_value(false) + self.pkt_test.disable() + else self.pkt_test.enable() end + end + + TextBox{parent=net_c_2,y=8,text="With a wireless modem, configure Pocket access."} + local pkt_en = Checkbox{parent=net_c_2,y=10,label="Enable Pocket Access",default=ini_cfg.PocketEnabled,callback=on_pocket_en,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} + self.pkt_test = Checkbox{parent=net_c_2,label="Enable Pocket Remote System Testing",default=ini_cfg.PocketEnabled,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_2,x=3,text="This allows remotely playing alarm sounds.",fg_bg=g_lg_fg_bg} + + local function submit_net_cfg_opts() + if tmp_cfg.WirelessModem and tmp_cfg.WiredModem then + tmp_cfg.PLC_Listen = plc_listen.get_value() - 1 + tmp_cfg.RTU_Listen = rtu_listen.get_value() - 1 + tmp_cfg.CRD_Listen = crd_listen.get_value() - 1 + else + if tmp_cfg.WiredModem then + tmp_cfg.PLC_Listen = LISTEN_MODE.WIRED + tmp_cfg.RTU_Listen = LISTEN_MODE.WIRED + tmp_cfg.CRD_Listen = LISTEN_MODE.WIRED + else + tmp_cfg.PLC_Listen = LISTEN_MODE.WIRELESS + tmp_cfg.RTU_Listen = LISTEN_MODE.WIRELESS + tmp_cfg.CRD_Listen = LISTEN_MODE.WIRELESS + end + end + + if tmp_cfg.WirelessModem then + tmp_cfg.PocketEnabled = pkt_en.get_value() + tmp_cfg.PocketTest = self.pkt_test.get_value() + else + tmp_cfg.PocketEnabled = false + tmp_cfg.PocketTest = false + end + + if tmp_cfg.PocketEnabled then + self.pkt_chan.enable() + self.pkt_timeout.enable() + else + self.pkt_chan.disable() + self.pkt_timeout.disable() + end + + net_pane.set_value(3) + end + PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_net_cfg_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} TextBox{parent=net_c_3,x=1,y=1,text="Please set the network channels below."} TextBox{parent=net_c_3,x=1,y=3,height=4,text="Each of the 5 uniquely named channels must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg} @@ -144,14 +195,14 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit TextBox{parent=net_c_3,x=29,y=11,height=4,text="[CRD_CHANNEL]",fg_bg=g_lg_fg_bg} TextBox{parent=net_c_3,x=1,y=12,width=14,text="Pocket Channel"} - local pkt_chan = NumberField{parent=net_c_3,x=21,y=12,width=7,default=ini_cfg.PKT_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + self.pkt_chan = NumberField{parent=net_c_3,x=21,y=12,width=7,default=ini_cfg.PKT_Channel,min=1,max=65535,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} TextBox{parent=net_c_3,x=29,y=12,height=4,text="[PKT_CHANNEL]",fg_bg=g_lg_fg_bg} local chan_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Please set all channels.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_channels() local svr_c, plc_c, rtu_c = tonumber(svr_chan.get_value()), tonumber(plc_chan.get_value()), tonumber(rtu_chan.get_value()) - local crd_c, pkt_c = tonumber(crd_chan.get_value()), tonumber(pkt_chan.get_value()) + local crd_c, pkt_c = tonumber(crd_chan.get_value()), tonumber(self.pkt_chan.get_value()) if svr_c ~= nil and plc_c ~= nil and rtu_c ~= nil and crd_c ~= nil and pkt_c ~= nil then tmp_cfg.SVR_Channel, tmp_cfg.PLC_Channel, tmp_cfg.RTU_Channel = svr_c, plc_c, rtu_c tmp_cfg.CRD_Channel, tmp_cfg.PKT_Channel = crd_c, pkt_c @@ -176,18 +227,23 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit local crd_timeout = NumberField{parent=net_c_4,x=21,y=10,width=7,default=ini_cfg.CRD_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} TextBox{parent=net_c_4,x=1,y=11,width=14,text="Pocket Timeout"} - local pkt_timeout = NumberField{parent=net_c_4,x=21,y=11,width=7,default=ini_cfg.PKT_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} + self.pkt_timeout = NumberField{parent=net_c_4,x=21,y=11,width=7,default=ini_cfg.PKT_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} TextBox{parent=net_c_4,x=29,y=8,height=4,width=7,text="seconds\nseconds\nseconds\nseconds",fg_bg=g_lg_fg_bg} local ct_err = TextBox{parent=net_c_4,x=8,y=14,width=35,text="Please set all connection timeouts.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_timeouts() - local plc_cto, rtu_cto, crd_cto, pkt_cto = tonumber(plc_timeout.get_value()), tonumber(rtu_timeout.get_value()), tonumber(crd_timeout.get_value()), tonumber(pkt_timeout.get_value()) + local plc_cto, rtu_cto, crd_cto, pkt_cto = tonumber(plc_timeout.get_value()), tonumber(rtu_timeout.get_value()), tonumber(crd_timeout.get_value()), tonumber(self.pkt_timeout.get_value()) if plc_cto ~= nil and rtu_cto ~= nil and crd_cto ~= nil and pkt_cto ~= nil then tmp_cfg.PLC_Timeout, tmp_cfg.RTU_Timeout, tmp_cfg.CRD_Timeout, tmp_cfg.PKT_Timeout = plc_cto, rtu_cto, crd_cto, pkt_cto - net_pane.set_value(5) - ct_err.hide(true) + + if tmp_cfg.WirelessModem then + net_pane.set_value(5) + ct_err.hide(true) + else + main_pane.set_value(4) + end else ct_err.show() end end @@ -211,7 +267,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit else tr_err.show() end end - PushButton{parent=net_c_5,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_5,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=net_c_5,x=44,y=14,text="Next \x1a",callback=submit_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} TextBox{parent=net_c_6,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."} @@ -431,15 +487,22 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit try_set(tool_ctl.num_units, ini_cfg.UnitCount) try_set(tool_ctl.tank_mode, ini_cfg.FacilityTankMode) + try_set(wireless, ini_cfg.WirelessModem) + try_set(wired, ini_cfg.WiredModem ~= false) + try_set(plc_listen, ini_cfg.PLC_Listen + 1) + try_set(rtu_listen, ini_cfg.RTU_Listen + 1) + try_set(crd_listen, ini_cfg.CRD_Listen + 1) + try_set(pkt_en, ini_cfg.PocketEnabled) + try_set(self.pkt_test, ini_cfg.PocketTest) try_set(svr_chan, ini_cfg.SVR_Channel) try_set(plc_chan, ini_cfg.PLC_Channel) try_set(rtu_chan, ini_cfg.RTU_Channel) try_set(crd_chan, ini_cfg.CRD_Channel) - try_set(pkt_chan, ini_cfg.PKT_Channel) + try_set(self.pkt_chan, ini_cfg.PKT_Channel) try_set(plc_timeout, ini_cfg.PLC_Timeout) try_set(rtu_timeout, ini_cfg.RTU_Timeout) try_set(crd_timeout, ini_cfg.CRD_Timeout) - try_set(pkt_timeout, ini_cfg.PKT_Timeout) + try_set(self.pkt_timeout, ini_cfg.PKT_Timeout) try_set(range, ini_cfg.TrustedRange) try_set(key, ini_cfg.AuthKey) try_set(mode, ini_cfg.LogMode) @@ -538,6 +601,42 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit --#region Tool Functions + -- expose the auth key on the summary page + function self.show_auth_key() + self.show_key_btn.disable() + self.auth_key_textbox.set_value(self.auth_key_value) + end + + -- update the network interface configuration options + function self.update_net_cfg() + if tmp_cfg.WirelessModem and tmp_cfg.WiredModem then + plc_listen.enable() + rtu_listen.enable() + crd_listen.enable() + else + plc_listen.set_value(tmp_cfg.PLC_Listen + 1) + rtu_listen.set_value(tmp_cfg.RTU_Listen + 1) + crd_listen.set_value(tmp_cfg.CRD_Listen + 1) + plc_listen.disable() + rtu_listen.disable() + crd_listen.disable() + end + + if tmp_cfg.WirelessModem then + pkt_en.enable() + self.pkt_test.enable() + self.pkt_chan.enable() + self.pkt_timeout.enable() + else + pkt_en.set_value(false) + self.pkt_test.set_value(false) + pkt_en.disable() + self.pkt_test.disable() + self.pkt_chan.disable() + self.pkt_timeout.disable() + end + end + -- load a legacy config file function tool_ctl.load_legacy() local config = require("supervisor.config") @@ -618,12 +717,6 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit self.importing_legacy = true end - -- expose the auth key on the summary page - function self.show_auth_key() - self.show_key_btn.disable() - self.auth_key_textbox.set_value(self.auth_key_value) - end - -- generate the summary list ---@param cfg svr_config function tool_ctl.gen_summary(cfg) @@ -746,6 +839,10 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit end if val == "" then val = "no auxiliary coolant" end + elseif f[1] == "PLC_Listen" or f[1] == "RTU_Listen" or f[1] == "CRD_Listen" then + if raw == LISTEN_MODE.WIRELESS then val = "Wireless Only" + elseif raw == LISTEN_MODE.WIRED then val = "Wired Only" + elseif raw == LISTEN_MODE.ALL then val = "Wireless and Wired" end end if not skip then diff --git a/supervisor/configure.lua b/supervisor/configure.lua index bbde9f8..43ef5da 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -33,7 +33,8 @@ local CENTER = core.ALIGN.CENTER local changes = { { "v1.2.12", { "Added front panel UI theme", "Added color accessibility modes" } }, { "v1.3.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } }, - { "v1.6.0", { "Added sodium emergency coolant option" } } + { "v1.6.0", { "Added sodium emergency coolant option" } }, + { "v1.8.0", { "Added support for both wired and wireless networking" } } } ---@class svr_configurator @@ -92,6 +93,13 @@ local tmp_cfg = { TankFluidTypes = {}, ---@type integer[] which type of fluid each tank in the tank list should be containing AuxiliaryCoolant = {}, ---@type boolean[] if a unit has auxiliary coolant ExtChargeIdling = false, + WirelessModem = true, ---@type boolean + WiredModem = false, ---@type string|false + PLC_Listen = 1, ---@type LISTEN_MODE + RTU_Listen = 1, ---@type LISTEN_MODE + CRD_Listen = 1, ---@type LISTEN_MODE + PocketEnabled = true, ---@type boolean + PocketTest = true, ---@type boolean SVR_Channel = nil, ---@type integer PLC_Channel = nil, ---@type integer RTU_Channel = nil, ---@type integer @@ -101,13 +109,6 @@ local tmp_cfg = { RTU_Timeout = nil, ---@type number CRD_Timeout = nil, ---@type number PKT_Timeout = nil, ---@type number - WirelessModem = true, ---@type boolean - WiredModem = false, ---@type string|false - PLC_Listen = 0, ---@type LISTEN_MODE - RTU_Listen = 0, ---@type LISTEN_MODE - CRD_Listen = 0, ---@type LISTEN_MODE - PocketEnabled = true, ---@type boolean - PocketTest = true, ---@type boolean TrustedRange = nil, ---@type number AuthKey = nil, ---@type string|nil LogMode = 0, ---@type LOG_MODE @@ -133,6 +134,13 @@ local fields = { { "TankFluidTypes", "Tank Fluid Types", {} }, { "AuxiliaryCoolant", "Auxiliary Water Coolant", {} }, { "ExtChargeIdling", "Extended Charge Idling", false }, + { "WirelessModem", "Wireless/Ender Comms Modem", true }, + { "WiredModem", "Wired Comms Modem", false }, + { "PLC_Listen", "PLC Listen Mode", types.LISTEN_MODE.WIRELESS }, + { "RTU_Listen", "RTU Gateway Listen Mode", types.LISTEN_MODE.WIRELESS }, + { "CRD_Listen", "Coordinator Listen Mode", types.LISTEN_MODE.WIRELESS }, + { "PocketEnabled", "Pocket Connectivity", true }, + { "PocketTest", "Pocket Testing Features", true }, { "SVR_Channel", "SVR Channel", 16240 }, { "PLC_Channel", "PLC Channel", 16241 }, { "RTU_Channel", "RTU Channel", 16242 }, @@ -142,13 +150,6 @@ local fields = { { "RTU_Timeout", "RTU Connection Timeout", 5 }, { "CRD_Timeout", "CRD Connection Timeout", 5 }, { "PKT_Timeout", "PKT Connection Timeout", 5 }, - { "WirelessModem", "Wireless/Ender Comms Modem", true }, - { "WiredModem", "Wired Comms Modem", false }, - { "PLC_Listen", "PLC Listen Mode", types.LISTEN_MODE.WIRELESS }, - { "RTU_Listen", "RTU Gateway Listen Mode", types.LISTEN_MODE.WIRELESS }, - { "CRD_Listen", "Coordinator Listen Mode", types.LISTEN_MODE.WIRELESS }, - { "PocketEnabled", "Pocket Connectivity", true }, - { "PocketTest", "Pocket Testing Features", true }, { "TrustedRange", "Trusted Range", 0 }, { "AuthKey", "Facility Auth Key" , "" }, { "LogMode", "Log Mode", log.MODE.APPEND }, From 452fe71ab83a43cfc1cfd2b0abd2a658aa1d2595 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Oct 2025 15:28:30 -0400 Subject: [PATCH 24/90] #580 modem list fix --- rtu/config/system.lua | 7 ++----- rtu/configure.lua | 2 +- supervisor/config/system.lua | 7 ++----- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/rtu/config/system.lua b/rtu/config/system.lua index b121db9..c44e0ba 100644 --- a/rtu/config/system.lua +++ b/rtu/config/system.lua @@ -732,11 +732,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) local missing = { tmp = true, ini = true } for iface, _ in pairs(modems) do - if ini_cfg.WiredModem == iface then - missing.ini = false - elseif tmp_cfg.WiredModem == iface then - missing.tmp = false - end + if ini_cfg.WiredModem == iface then missing.ini = false end + if tmp_cfg.WiredModem == iface then missing.tmp = false end end if missing.tmp and tmp_cfg.WiredModem then diff --git a/rtu/configure.lua b/rtu/configure.lua index 48d2c99..029d91a 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -324,9 +324,9 @@ function configurator.configure(ask_config) tool_ctl.has_config = load_settings(ini_cfg) -- set tmp_cfg so interface lists are correct + tmp_cfg.WiredModem = ini_cfg.WiredModem tmp_cfg.Peripherals = tool_ctl.deep_copy_peri(ini_cfg.Peripherals) tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone) - tmp_cfg.WiredModem = ini_cfg.WiredModem reset_term() diff --git a/supervisor/config/system.lua b/supervisor/config/system.lua index b453832..9b140b6 100644 --- a/supervisor/config/system.lua +++ b/supervisor/config/system.lua @@ -886,11 +886,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit local missing = { tmp = true, ini = true } for iface, _ in pairs(modems) do - if ini_cfg.WiredModem == iface then - missing.ini = false - elseif tmp_cfg.WiredModem == iface then - missing.tmp = false - end + if ini_cfg.WiredModem == iface then missing.ini = false end + if tmp_cfg.WiredModem == iface then missing.tmp = false end end if missing.tmp and tmp_cfg.WiredModem then From 2d44014e2e2a57944130f4bb11e294fde116c77d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Oct 2025 15:30:14 -0400 Subject: [PATCH 25/90] #580 fixes for listen mode enum change --- supervisor/config/system.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/supervisor/config/system.lua b/supervisor/config/system.lua index 9b140b6..bd6a04f 100644 --- a/supervisor/config/system.lua +++ b/supervisor/config/system.lua @@ -138,9 +138,9 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit local function submit_net_cfg_opts() if tmp_cfg.WirelessModem and tmp_cfg.WiredModem then - tmp_cfg.PLC_Listen = plc_listen.get_value() - 1 - tmp_cfg.RTU_Listen = rtu_listen.get_value() - 1 - tmp_cfg.CRD_Listen = crd_listen.get_value() - 1 + tmp_cfg.PLC_Listen = plc_listen.get_value() + tmp_cfg.RTU_Listen = rtu_listen.get_value() + tmp_cfg.CRD_Listen = crd_listen.get_value() else if tmp_cfg.WiredModem then tmp_cfg.PLC_Listen = LISTEN_MODE.WIRED @@ -489,9 +489,9 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit try_set(tool_ctl.tank_mode, ini_cfg.FacilityTankMode) try_set(wireless, ini_cfg.WirelessModem) try_set(wired, ini_cfg.WiredModem ~= false) - try_set(plc_listen, ini_cfg.PLC_Listen + 1) - try_set(rtu_listen, ini_cfg.RTU_Listen + 1) - try_set(crd_listen, ini_cfg.CRD_Listen + 1) + try_set(plc_listen, ini_cfg.PLC_Listen) + try_set(rtu_listen, ini_cfg.RTU_Listen) + try_set(crd_listen, ini_cfg.CRD_Listen) try_set(pkt_en, ini_cfg.PocketEnabled) try_set(self.pkt_test, ini_cfg.PocketTest) try_set(svr_chan, ini_cfg.SVR_Channel) @@ -614,9 +614,9 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit rtu_listen.enable() crd_listen.enable() else - plc_listen.set_value(tmp_cfg.PLC_Listen + 1) - rtu_listen.set_value(tmp_cfg.RTU_Listen + 1) - crd_listen.set_value(tmp_cfg.CRD_Listen + 1) + plc_listen.set_value(tmp_cfg.PLC_Listen) + rtu_listen.set_value(tmp_cfg.RTU_Listen) + crd_listen.set_value(tmp_cfg.CRD_Listen) plc_listen.disable() rtu_listen.disable() crd_listen.disable() From db8bed583f512432df4dc299cb37c9d4c57bd0f6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Oct 2025 15:34:47 -0400 Subject: [PATCH 26/90] #580 additional supervisor configurator fixes --- supervisor/config/system.lua | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/supervisor/config/system.lua b/supervisor/config/system.lua index bd6a04f..a1ab535 100644 --- a/supervisor/config/system.lua +++ b/supervisor/config/system.lua @@ -120,9 +120,9 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit TextBox{parent=net_c_2,x=1,y=1,text="Please assign device connection interfaces if you selected multiple network interfaces."} TextBox{parent=net_c_2,x=1,y=4,text="Reactor PLC\nRTU Gateway\nCoordinator",fg_bg=g_lg_fg_bg} local opts = { "Wireless", "Wired", "Both" } - local plc_listen = Radio2D{parent=net_c_2,x=14,y=4,rows=1,columns=3,default=ini_cfg.PLC_Listen+1,options=opts,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lightBlue,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg} - local rtu_listen = Radio2D{parent=net_c_2,x=14,rows=1,columns=3,default=ini_cfg.RTU_Listen+1,options=opts,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lightBlue,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg} - local crd_listen = Radio2D{parent=net_c_2,x=14,rows=1,columns=3,default=ini_cfg.CRD_Listen+1,options=opts,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lightBlue,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg} + local plc_listen = Radio2D{parent=net_c_2,x=14,y=4,rows=1,columns=3,default=ini_cfg.PLC_Listen,options=opts,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lightBlue,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg} + local rtu_listen = Radio2D{parent=net_c_2,x=14,rows=1,columns=3,default=ini_cfg.RTU_Listen,options=opts,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lightBlue,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg} + local crd_listen = Radio2D{parent=net_c_2,x=14,rows=1,columns=3,default=ini_cfg.CRD_Listen,options=opts,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lightBlue,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg} local function on_pocket_en(en) if not en then @@ -614,9 +614,6 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit rtu_listen.enable() crd_listen.enable() else - plc_listen.set_value(tmp_cfg.PLC_Listen) - rtu_listen.set_value(tmp_cfg.RTU_Listen) - crd_listen.set_value(tmp_cfg.CRD_Listen) plc_listen.disable() rtu_listen.disable() crd_listen.disable() From c7e02efbc7869f89cd15d348e58943ecbc68ffb7 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Oct 2025 15:39:30 -0400 Subject: [PATCH 27/90] #580 RTU configurator updates --- rtu/config/system.lua | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/rtu/config/system.lua b/rtu/config/system.lua index c44e0ba..77900e0 100644 --- a/rtu/config/system.lua +++ b/rtu/config/system.lua @@ -30,9 +30,10 @@ local self = { importing_legacy = false, importing_any_dc = false, - show_auth_key = nil, ---@type function - show_key_btn = nil, ---@type PushButton - auth_key_textbox = nil, ---@type TextBox + wl_pref = nil, ---@type Checkbox + show_auth_key = nil, ---@type function + show_key_btn = nil, ---@type PushButton + auth_key_textbox = nil, ---@type TextBox auth_key_value = "" } @@ -99,13 +100,11 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) TextBox{parent=net_c_1,x=1,y=1,text="Please select the network interface(s)."} TextBox{parent=net_c_1,x=41,y=1,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision - local wl_pref = Checkbox{parent=net_c_1,x=30,y=3,label="Prefer Wireless",default=ini_cfg.PreferWireless,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} - local function dis_pref(value) if not value then - wl_pref.set_value(false) - wl_pref.disable() - else wl_pref.enable() end + self.wl_pref.set_value(false) + self.wl_pref.disable() + else self.wl_pref.enable() end end dis_pref(ini_cfg.WirelessModem) @@ -113,6 +112,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) local function on_wired_change(_) tool_ctl.gen_modem_list() end local wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=dis_pref} + self.wl_pref = Checkbox{parent=net_c_1,x=30,y=3,label="Prefer Wireless",default=ini_cfg.PreferWireless,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} local wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change} TextBox{parent=net_c_1,x=3,y=6,text="MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)} TextBox{parent=net_c_1,x=3,y=7,text="connecting to peripherals will cause problems",fg_bg=g_lg_fg_bg} @@ -122,7 +122,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) local function submit_interfaces() tmp_cfg.WirelessModem = wireless.get_value() - tmp_cfg.PreferWireless = wl_pref.get_value() + tmp_cfg.PreferWireless = self.wl_pref.get_value() if not wired.get_value() then tmp_cfg.WiredModem = false @@ -433,7 +433,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) try_set(s_vol, ini_cfg.SpeakerVolume) try_set(wireless, ini_cfg.WirelessModem) try_set(wired, ini_cfg.WiredModem ~= false) - try_set(wl_pref, ini_cfg.PreferWireless) + try_set(self.wl_pref, ini_cfg.PreferWireless) try_set(svr_chan, ini_cfg.SVR_Channel) try_set(rtu_chan, ini_cfg.RTU_Channel) try_set(timeout, ini_cfg.ConnTimeout) From f0251efec664a1e6a6b567adb54ae9d052c6c77d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Oct 2025 15:41:43 -0400 Subject: [PATCH 28/90] #580 RTU backplane and logging updates --- rtu/backplane.lua | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/rtu/backplane.lua b/rtu/backplane.lua index c79f02d..ebf0961 100644 --- a/rtu/backplane.lua +++ b/rtu/backplane.lua @@ -16,9 +16,7 @@ local backplane = {} local _bp = { smem = nil, ---@type rtu_shared_memory - wlan_en = true, wlan_pref = true, - lan_en = false, lan_iface = "", act_nic = nil, ---@type nic|nil @@ -34,13 +32,11 @@ local _bp = { ---@param __shared_memory rtu_shared_memory function backplane.init(config, __shared_memory) _bp.smem = __shared_memory - _bp.wlan_en = config.WirelessModem _bp.wlan_pref = config.PreferWireless - _bp.lan_en = type(config.WiredModem) == "string" _bp.lan_iface = config.WiredModem -- init wired NIC - if _bp.lan_en then + if type(config.WiredModem) == "string" then local modem = ppm.get_modem(_bp.lan_iface) if modem then @@ -50,7 +46,7 @@ function backplane.init(config, __shared_memory) end -- init wireless NIC(s) - if _bp.wlan_en then + if config.WirelessModem then local modem, iface = ppm.get_wireless_modem() if modem then @@ -105,10 +101,13 @@ function backplane.detach(type, device, iface) if type == "modem" then ---@cast device Modem + local m_is_wl = device.isWireless() local was_active = _bp.act_nic and _bp.act_nic.is_modem(device) local was_wd = wd_nic and wd_nic.is_modem(device) local was_wl = wl_nic and wl_nic.is_modem(device) + log.info(util.c("BKPLN: ", util.trinary(m_is_wl, "WIRELESS", "WIRED"), " PHY_DETACH ", iface)) + if wd_nic and was_wd then wd_nic.disconnect() log.info("BKPLN: WIRED PHY_DOWN " .. iface) @@ -119,22 +118,22 @@ function backplane.detach(type, device, iface) -- we only care if this is our active comms modem if was_active then - println_ts("active comms modem disconnected!") - log.warning("active comms modem disconnected") + println_ts("active comms modem disconnected") + log.warning("BKPLN: active comms modem disconnected") -- failover and try to find a new comms modem if _bp.wl_act then -- try to find another wireless modem, otherwise switch to wired - local other_modem = ppm.get_wireless_modem() - if other_modem then - log.info("found another wireless modem, using it for comms") + local modem, m_iface = ppm.get_wireless_modem() + if modem then + log.info("BKPLN: found another wireless modem, using it for comms") -- note: must assign to self.wl_nic if creating a nic, otherwise it only changes locally if wl_nic then - wl_nic.connect(other_modem) - else _bp.wl_nic = network.nic(other_modem) end + wl_nic.connect(modem) + else _bp.wl_nic = network.nic(modem) end - log.info("BKPLN: WIRELESS PHY_UP " .. iface) + log.info("BKPLN: WIRELESS PHY_UP " .. m_iface) _bp.act_nic = wl_nic comms.assign_nic(_bp.act_nic) @@ -164,8 +163,11 @@ function backplane.detach(type, device, iface) comms.unassign_nic() end end + elseif _bp.wl_nic and m_is_wl then + -- wireless, but not active + log.info("BKPLN: standby wireless modem disconnected") else - log.warning("modem disconnected") + log.warning("BKPLN: unassigned modem disconnected") end elseif type == "speaker" then ---@cast device Speaker @@ -173,7 +175,7 @@ function backplane.detach(type, device, iface) if _bp.sounders[i].speaker == device then table.remove(_bp.sounders, i) - log.warning(util.c("speaker ", iface, " disconnected")) + log.warning(util.c("BKPLN: speaker ", iface, " disconnected")) println_ts("speaker disconnected") databus.tx_hw_spkr_count(#_bp.sounders) @@ -195,8 +197,12 @@ function backplane.attach(type, device, iface) if 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.lan_iface == iface - local is_wl = ((not _bp.wl_nic) or (not _bp.wl_nic.is_connected())) and device.isWireless() + local is_wl = ((not _bp.wl_nic) or (not _bp.wl_nic.is_connected())) and m_is_wl if is_wd then -- connect this as the wired NIC @@ -213,7 +219,7 @@ function backplane.attach(type, device, iface) comms.assign_nic(_bp.act_nic) databus.tx_hw_modem(true) - println_ts("comms modem reconnected.") + println_ts("comms modem reconnected") log.info("BKPLN: switched comms to wired modem") elseif _bp.wl_act and not _bp.wlan_pref then -- switch back to preferred wired @@ -238,7 +244,7 @@ function backplane.attach(type, device, iface) comms.assign_nic(_bp.act_nic) databus.tx_hw_modem(true) - println_ts("comms modem reconnected.") + println_ts("comms modem reconnected") log.info("BKPLN: switched comms to wireless modem") elseif (not _bp.wl_act) and _bp.wlan_pref then -- switch back to preferred wireless @@ -248,7 +254,7 @@ function backplane.attach(type, device, iface) comms.assign_nic(_bp.act_nic) log.info("BKPLN: switched comms to wireless modem (preferred)") end - elseif device.isWireless() then + elseif m_is_wl then -- the wireless NIC already has a modem log.info("standby wireless modem connected") else From 869b342db202b9434df4e7585f1dc64e6342f5aa Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sun, 26 Oct 2025 20:03:42 +0000 Subject: [PATCH 29/90] #634 supervisor backplane logging changes --- supervisor/backplane.lua | 35 ++++++++++++++++++----------------- supervisor/startup.lua | 2 +- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/supervisor/backplane.lua b/supervisor/backplane.lua index 1818a0b..b026958 100644 --- a/supervisor/backplane.lua +++ b/supervisor/backplane.lua @@ -12,6 +12,8 @@ local databus = require("supervisor.databus") local LISTEN_MODE = types.LISTEN_MODE +local println = util.println + ---@class supervisor_backplane local backplane = {} @@ -28,9 +30,8 @@ backplane.nics = _bp.nic_map -- initialize the system peripheral backplane ---@param config svr_config ----@param println function ---@return boolean success -function backplane.init(config, println) +function backplane.init(config) -- setup the wired modem, if configured if type(config.WiredModem) == "string" then _bp.lan_iface = config.WiredModem @@ -38,7 +39,7 @@ function backplane.init(config, println) local modem = ppm.get_modem(_bp.lan_iface) if not (modem and _bp.lan_iface) then println("startup> wired comms modem not found") - log.fatal("no wired comms modem on startup") + log.fatal("BKPLN: no wired comms modem on startup") return false end @@ -60,7 +61,7 @@ function backplane.init(config, println) local modem, iface = ppm.get_wireless_modem() if not (modem and iface) then println("startup> wireless comms modem not found") - log.fatal("no wireless comms modem on startup") + log.fatal("BKPLN: no wireless comms modem on startup") return false end @@ -80,7 +81,7 @@ function backplane.init(config, println) if not ((type(config.WiredModem) == "string" or config.WirelessModem)) then println("startup> no modems configured") - log.fatal("no modems configured") + log.fatal("BKPLN: no modems configured") return false end @@ -91,8 +92,8 @@ end ---@param iface string ---@param type string ---@param device table ----@param println function -function backplane.attach(iface, type, device, println) +---@param print_no_fp function +function backplane.attach(iface, type, device, print_no_fp) if type == "modem" then ---@cast device Modem @@ -108,7 +109,7 @@ function backplane.attach(iface, type, device, println) _bp.wd_nic.connect(device) log.info("BKPLN: WIRED PHY_UP " .. iface) - println("wired comms modem reconnected") + print_no_fp("wired comms modem reconnected") databus.tx_hw_wd_modem(true) elseif is_wl then @@ -117,15 +118,15 @@ function backplane.attach(iface, type, device, println) _bp.nic_map[iface] = _bp.wl_nic log.info("BKPLN: WIRELESS PHY_UP " .. iface) - println("wireless comms modem reconnected") + print_no_fp("wireless comms modem reconnected") databus.tx_hw_wl_modem(true) elseif _bp.wl_nic and m_is_wl then -- the wireless NIC already has a modem - println("standby wireless modem connected") + print_no_fp("standby wireless modem connected") log.info("BKPLN: standby wireless modem connected") else - println("unassigned modem connected") + print_no_fp("unassigned modem connected") log.warning("BKPLN: unassigned modem connected") end end @@ -135,8 +136,8 @@ end ---@param iface string ---@param type string ---@param device table ----@param println function -function backplane.detach(iface, type, device, println) +---@param print_no_fp function +function backplane.detach(iface, type, device, print_no_fp) if type == "modem" then ---@cast device Modem @@ -152,7 +153,7 @@ function backplane.detach(iface, type, device, println) _bp.wd_nic.disconnect() log.info("BKPLN: WIRED PHY_DOWN " .. iface) - println("wired modem disconnected") + print_no_fp("wired modem disconnected") log.warning("BKPLN: wired comms modem disconnected") databus.tx_hw_wd_modem(false) @@ -160,7 +161,7 @@ function backplane.detach(iface, type, device, println) _bp.wl_nic.disconnect() log.info("BKPLN: WIRELESS PHY_DOWN " .. iface) - println("wireless comms modem disconnected") + print_no_fp("wireless comms modem disconnected") log.warning("BKPLN: wireless comms modem disconnected") local modem, m_iface = ppm.get_wireless_modem() @@ -174,10 +175,10 @@ function backplane.detach(iface, type, device, println) end elseif _bp.wl_nic and m_is_wl then -- wireless, but not active - println("standby wireless modem disconnected") + print_no_fp("standby wireless modem disconnected") log.info("BKPLN: standby wireless modem disconnected") else - println("unassigned modem disconnected") + print_no_fp("unassigned modem disconnected") log.warning("BKPLN: unassigned modem disconnected") end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 280695d..ad2f096 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -127,7 +127,7 @@ local function main() end -- hardware backplane initialization - if not backplane.init(config, println) then return end + if not backplane.init(config) then return end -- start UI local fp_ok, message = renderer.try_start_ui(config) From 8fd04e44f311ffcfae0ee7fb275508720a1a61e9 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sun, 26 Oct 2025 20:04:27 +0000 Subject: [PATCH 30/90] #580 require a modem config in RTU gateway config check --- rtu/rtu.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 49984aa..f86d00a 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -66,6 +66,7 @@ function rtu.validate_config(cfg) 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) From b57aceff1554e5b706ee0bd6bc45c790338baad0 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sun, 26 Oct 2025 20:05:19 +0000 Subject: [PATCH 31/90] comment updates --- scada-common/ppm.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index d17fd41..bf5df5e 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -293,7 +293,7 @@ function ppm.remount(iface) return pm_type, pm_dev end --- mount a virtual, placeholder device (specifically designed for RTU startup with missing devices) +-- mount a virtual placeholder device ---@nodiscard ---@return string type, table device function ppm.mount_virtual() From c62eaeb5a250519b5d98d702480b3b39a64bee29 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sun, 26 Oct 2025 20:54:09 +0000 Subject: [PATCH 32/90] #580 work on reactor PLC wired comms --- reactor-plc/backplane.lua | 189 ++++++++++++++++++++++++++++++++++++++ reactor-plc/configure.lua | 6 +- reactor-plc/plc.lua | 7 +- reactor-plc/startup.lua | 162 +++++++++++--------------------- reactor-plc/threads.lua | 49 ++++------ scada-common/network.lua | 17 ++-- 6 files changed, 284 insertions(+), 146 deletions(-) create mode 100644 reactor-plc/backplane.lua diff --git a/reactor-plc/backplane.lua b/reactor-plc/backplane.lua new file mode 100644 index 0000000..07d7694 --- /dev/null +++ b/reactor-plc/backplane.lua @@ -0,0 +1,189 @@ +-- +-- 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 + + -- 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 __shared_memory.networked and plc_state.no_modem then + println("startup> comms modem not found") + log.warning("BKPLN: no comms modem on startup") + + plc_state.degraded = true + 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|nil +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 1f7fa68..47fdc8c 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 = "", ---@type string 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 4381511..f69f289 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 5cb8f72..1e10f93 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") @@ -87,7 +88,6 @@ local function main() -- PLC system state flags ---@class plc_state plc_state = { - init_ok = true, fp_ok = false, shutdown = false, degraded = true, @@ -103,15 +103,14 @@ local function main() burn_rate = 0.0 }, - -- core PLC devices + -- global PLC devices, still initialized by the backplane + ---@class plc_dev plc_dev = { - reactor = ppm.get_fission_reactor(), - modem_wired = type(config.WiredModem) == "string", - modem_iface = config.WiredModem, - modem = nil + reactor = nil ---@type table }, -- system objects + ---@class plc_sys plc_sys = { rps = nil, ---@type rps nic = nil, ---@type nic @@ -132,115 +131,66 @@ 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 + -- reactor and modem initialization + backplane.init(config, __shared_memory) - -- 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("init> fission reactor not found") - log.warning("init> no reactor on startup") - - plc_state.init_ok = false - plc_state.degraded = true - elseif not smem_dev.reactor.isFormed() then - println("init> fission reactor is not formed") - log.warning("init> reactor logic adapter present, but reactor is not formed") - - plc_state.degraded = true - plc_state.reactor_formed = false + -- 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 + log.debug("startup> power-on SCRAM") + smem_dev.reactor.scram() end - -- comms modem is required if networked - if __shared_memory.networked and plc_state.no_modem then - println("init> comms modem not found") - log.warning("init> no comms modem on startup") + -- setup front panel + local message + plc_state.fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode) - -- 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.init_ok = false - plc_state.degraded = true + -- ...or not + if not plc_state.fp_ok then + println_ts(util.c("UI error: ", message)) + println("startup> running without front panel") + log.error(util.c("front panel GUI render failed with error ", message)) + log.info("startup> running in headless mode without front panel") end -- print a log message to the terminal as long as the UI isn't running - local function _println_no_fp(message) if not plc_state.fp_ok then println(message) end end + local function _println_no_fp(msg) if not plc_state.fp_ok then println(msg) end end - -- PLC init
- --- EVENT_CONSUMER: this function consumes events - local function init() - -- 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 - smem_dev.reactor.scram() - end + ---------------------------------------- + -- initialize PLC + ---------------------------------------- - -- setup front panel - if not renderer.ui_ready() then - local message - plc_state.fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode) + -- init reactor protection system + smem_sys.rps = plc.rps_init(smem_dev.reactor, plc_state.reactor_formed) + log.debug("startup> rps init") - -- ...or not - if not plc_state.fp_ok then - println_ts(util.c("UI error: ", message)) - println("init> running without front panel") - log.error(util.c("front panel GUI render failed with error ", message)) - log.info("init> running in headless mode without front panel") - end - end - - if plc_state.init_ok then - -- init reactor protection system - 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.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(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") - log.info("init> running without networking") - end - - -- notify user of emergency coolant configuration status - if config.EmerCoolEnable then - println("init> emergency coolant control ready") - log.info("init> running with emergency coolant control available") - end - - util.push_event("clock_start") - - _println_no_fp("init> completed") - log.info("init> startup completed") - else - _println_no_fp("init> system in degraded state, awaiting devices...") - log.warning("init> started in a degraded state, awaiting peripheral connections...") - end - - databus.tx_hw_status(plc_state) + -- notify user of emergency coolant configuration status + if config.EmerCoolEnable then + _println_no_fp("startup> emergency coolant control ready") + log.info("startup> emergency coolant control available") end - ---------------------------------------- - -- start system - ---------------------------------------- + -- conditionally init comms + if __shared_memory.networked then + -- comms watchdog + smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout) + log.debug("startup> conn watchdog started") - -- initialize PLC - init() + -- create network interface then setup comms + smem_sys.nic = network.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 + _println_no_fp("startup> starting in non-networked mode") + log.info("startup> starting without networking") + end + + databus.tx_hw_status(plc_state) + + _println_no_fp("startup> completed") + log.info("startup> completed") -- init threads - local main_thread = threads.thread__main(__shared_memory, init) + local main_thread = threads.thread__main(__shared_memory) local rps_thread = threads.thread__rps(__shared_memory) if __shared_memory.networked then @@ -254,14 +204,12 @@ local function main() -- run threads parallel.waitForAll(main_thread.p_exec, rps_thread.p_exec, comms_thread_tx.p_exec, comms_thread_rx.p_exec, sp_ctrl_thread.p_exec) - if plc_state.init_ok then - -- send status one last time after RPS shutdown - smem_sys.plc_comms.send_status(plc_state.no_reactor, plc_state.reactor_formed) - smem_sys.plc_comms.send_rps_status() + -- send status one last time after RPS shutdown + smem_sys.plc_comms.send_status(plc_state.no_reactor, plc_state.reactor_formed) + smem_sys.plc_comms.send_rps_status() - -- close connection - smem_sys.plc_comms.close() - end + -- close connection + smem_sys.plc_comms.close() else -- run threads, excluding comms parallel.waitForAll(main_thread.p_exec, rps_thread.p_exec) diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index f262b4e..492a11e 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -31,8 +31,7 @@ local MQ__COMM_CMD = { -- main thread ---@nodiscard ---@param smem plc_shared_memory ----@param init function -function threads.thread__main(smem, init) +function threads.thread__main(smem) -- print a log message to the terminal as long as the UI isn't running local function println_ts(message) if not smem.plc_state.fp_ok then util.println_ts(message) end end @@ -42,7 +41,7 @@ function threads.thread__main(smem, init) -- execute thread function public.exec() databus.tx_rt_status("main", true) - log.debug("main thread init, clock inactive") + log.debug("main thread start") -- send status updates at 2Hz (every 10 server ticks) (every loop tick) -- send link requests at 0.5Hz (every 40 server ticks) (every 8 loop ticks) @@ -55,6 +54,9 @@ function threads.thread__main(smem, init) local plc_state = smem.plc_state local plc_dev = smem.plc_dev + -- start clock + loop_clock.start() + -- event loop while true do -- get plc_sys fields (may have been set late due to degraded boot) @@ -67,7 +69,6 @@ function threads.thread__main(smem, init) -- handle event if event == "timer" and loop_clock.is_clock(param1) then - -- note: loop clock is only running if init_ok = true -- blink heartbeat indicator databus.heartbeat() @@ -118,14 +119,14 @@ function threads.thread__main(smem, init) -- update indicators databus.tx_hw_status(plc_state) - elseif event == "modem_message" and networked and plc_state.init_ok and nic.is_connected() then + elseif event == "modem_message" and networked and nic.is_connected() then -- got a packet local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5) if packet ~= nil then -- pass the packet onto the comms message queue smem.q.mq_comms_rx.push_packet(packet) end - elseif event == "timer" and networked and plc_state.init_ok and conn_watchdog.is_timer(param1) then + elseif event == "timer" and networked and conn_watchdog.is_timer(param1) then -- haven't heard from server recently? close connection and shutdown reactor plc_comms.close() smem.q.mq_rps.push_command(MQ__RPS_CMD.TRIP_TIMEOUT) @@ -146,8 +147,7 @@ function threads.thread__main(smem, init) elseif networked and type == "modem" then ---@cast device Modem -- we only care if this is our comms modem - -- note, check init_ok first since nic will be nil if it is false - if plc_state.init_ok and nic.is_modem(device) then + if nic.is_modem(device) then nic.disconnect() println_ts("comms modem disconnected!") @@ -184,7 +184,7 @@ function threads.thread__main(smem, init) plc_dev.reactor = device plc_state.no_reactor = false - println_ts("reactor reconnected.") + println_ts("reactor reconnected") log.info("reactor reconnected") -- we need to assume formed here as we cannot check in this main loop @@ -220,7 +220,7 @@ function threads.thread__main(smem, init) if plc_state.init_ok then nic.connect(device) end - println_ts("comms modem reconnected.") + println_ts("comms modem reconnected") log.info("comms modem reconnected") -- determine if we are still in a degraded state @@ -235,22 +235,12 @@ function threads.thread__main(smem, init) end end - -- if not init'd and no longer degraded, proceed to init - if not plc_state.init_ok and not plc_state.degraded then - plc_state.init_ok = true - init() - end - -- update indicators databus.tx_hw_status(plc_state) elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then -- handle a mouse event renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3)) - elseif event == "clock_start" then - -- start loop clock - loop_clock.start() - log.debug("main thread clock started") end -- check for termination request @@ -280,7 +270,6 @@ function threads.thread__main(smem, init) -- this thread cannot be slept because it will miss events (namely "terminate" otherwise) if not plc_state.shutdown then log.info("main thread restarting now...") - util.push_event("clock_start") end end end @@ -399,15 +388,15 @@ function threads.thread__rps(smem) if plc_state.shutdown then -- safe exit log.info("rps thread shutdown initiated") - if plc_state.init_ok then - if rps.scram() then - println_ts("reactor disabled") - log.info("rps thread reactor SCRAM OK") - else - println_ts("exiting, reactor failed to disable") - log.error("rps thread failed to SCRAM reactor on exit") - end + + if rps.scram() then + println_ts("exiting, reactor disabled") + log.info("rps thread reactor SCRAM OK") + else + println_ts("exiting, reactor failed to disable") + log.error("rps thread failed to SCRAM reactor on exit") end + log.info("rps thread exiting") break end @@ -430,7 +419,7 @@ function threads.thread__rps(smem) databus.tx_rt_status("rps", false) if not plc_state.shutdown then - if plc_state.init_ok then smem.plc_sys.rps.scram() end + smem.plc_sys.rps.scram() log.info("rps thread restarting in 5 seconds...") util.psleep(5) end diff --git a/scada-common/network.lua b/scada-common/network.lua index 8066adb..f7213bd 100644 --- a/scada-common/network.lua +++ b/scada-common/network.lua @@ -78,7 +78,7 @@ end -- NIC: Network Interface Controller
-- utilizes HMAC-MD5 for message authentication, if enabled and this is wireless ----@param modem Modem modem to use +---@param modem Modem|nil modem to use function network.nic(modem) local self = { -- modem interface name @@ -86,9 +86,9 @@ function network.nic(modem) -- phy name name = "?", -- used to quickly return out of tx/rx functions if there is nothing to do - connected = true, + connected = false, -- used to avoid costly MAC calculations if not required - use_hash = c_eng.hmac and modem.isWireless(), + use_hash = false, -- open channels channels = {} } @@ -135,13 +135,13 @@ function network.nic(modem) function public.is_modem(device) return device == modem end -- wrap modem functions, then create custom functions - public.connect(modem) + if modem then public.connect(modem) end -- open a channel on the modem
-- if disconnected *after* opening, previousy opened channels will be re-opened on reconnection ---@param channel integer function public.open(channel) - modem.open(channel) + if modem then modem.open(channel) end local already_open = false for i = 1, #self.channels do @@ -159,7 +159,7 @@ function network.nic(modem) -- close a channel on the modem ---@param channel integer function public.close(channel) - modem.close(channel) + if modem then modem.close(channel) end for i = 1, #self.channels do if self.channels[i] == channel then @@ -171,7 +171,7 @@ function network.nic(modem) -- close all channels on the modem function public.closeAll() - modem.closeAll() + if modem then modem.closeAll() end self.channels = {} end @@ -193,7 +193,10 @@ function network.nic(modem) -- log.debug("network.modem.transmit: data processing took " .. (util.time_ms() - start) .. "ms") end +---@diagnostic disable-next-line: need-check-nil modem.transmit(dest_channel, local_channel, tx_packet.raw_sendable()) + else + log.debug("network.transmit tx dropped, link is down") end end From a48c8c1efe72fc06ed0311a198928d1914ee44f8 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Oct 2025 17:22:07 -0400 Subject: [PATCH 33/90] merge fixes --- reactor-plc/configure.lua | 2 +- scada-common/ppm.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index 1f7fa68..e19ebfb 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -83,7 +83,7 @@ local tmp_cfg = { ConnTimeout = nil, ---@type number WiredModem = false, ---@type string|false TrustedRange = nil, ---@type number - AuthKey = "", ---@type string + AuthKey = nil, ---@type string|nil LogMode = 0, ---@type LOG_MODE LogPath = "", LogDebug = false, diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 6feed0d..aa46808 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -453,7 +453,7 @@ function ppm.get_fission_reactor() return ppm.get_device("fissionReactorLogicAda ---@return Modem|nil modem function table function ppm.get_modem(iface) local modem = nil - local device = ppm_sys.mounts[iface] + local device = _ppm.mounts[iface] if device and device.type == "modem" then modem = device.dev end @@ -485,7 +485,7 @@ end function ppm.get_wired_modem_list() local list = {} - for iface, device in pairs(ppm_sys.mounts) do + for iface, device in pairs(_ppm.mounts) do if device.type == "modem" and not device.dev.isWireless() then list[iface] = device end end From 2fefe4fbd6618050ddec33c48b3969f2d7031b7a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Oct 2025 18:15:18 -0400 Subject: [PATCH 34/90] version increment and log message formatting --- reactor-plc/startup.lua | 2 +- scada-common/comms.lua | 4 ++-- scada-common/network.lua | 10 +++++----- scada-common/util.lua | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index d76199d..a051adb 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -19,7 +19,7 @@ local plc = require("reactor-plc.plc") local renderer = require("reactor-plc.renderer") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v1.9.0" +local R_PLC_VERSION = "v1.9.1" local println = util.println local println_ts = util.println_ts diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 93a0e69..e3e7403 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -214,7 +214,7 @@ function comms.scada_packet() if (type(max_distance) == "number") and (type(distance) == "number") and (distance > max_distance) then -- outside of maximum allowable transmission distance - -- log.debug("COMMS: comms.scada_packet.receive(): discarding packet with distance " .. distance .. " (outside trusted range)") + -- log.debug("COMMS: scada_packet.receive(): discarding packet with distance " .. distance .. " (outside trusted range)") else if type(self.raw) == "table" then if #self.raw == 5 then @@ -337,7 +337,7 @@ function comms.authd_packet() if (type(max_distance) == "number") and ((type(distance) ~= "number") or (distance > max_distance)) then -- outside of maximum allowable transmission distance - -- log.debug("COMMS: comms.authd_packet.receive(): discarding packet with distance " .. distance .. " (outside trusted range)") + -- log.debug("COMMS: authd_packet.receive(): discarding packet with distance " .. distance .. " (outside trusted range)") else if type(self.raw) == "table" then if #self.raw == 4 then diff --git a/scada-common/network.lua b/scada-common/network.lua index 317d517..8a7e031 100644 --- a/scada-common/network.lua +++ b/scada-common/network.lua @@ -49,7 +49,7 @@ function network.init_mac(passkey) _crypt.hmac.setKey(_crypt.key) local init_time = util.time_ms() - start - log.info("NET: network.init_mac completed in " .. init_time .. "ms") + log.info("NET: network.init_mac() completed in " .. init_time .. "ms") return init_time end @@ -190,13 +190,13 @@ function network.nic(modem) ---@cast tx_packet authd_packet tx_packet.make(packet, compute_hmac) - -- log.debug("NET: network.modem.transmit: data processing took " .. (util.time_ms() - start) .. "ms") + -- log.debug("NET: network.modem.transmit(): data processing took " .. (util.time_ms() - start) .. "ms") end ---@diagnostic disable-next-line: need-check-nil modem.transmit(dest_channel, local_channel, tx_packet.raw_sendable()) else - log.debug("NET: network.transmit tx dropped, link is down") + log.debug("NET: network.transmit() tx dropped, link is down") end end @@ -227,10 +227,10 @@ function network.nic(modem) local computed_hmac = compute_hmac(textutils.serialize(s_packet.raw_header(), { allow_repetitions = true, compact = true })) if a_packet.mac() == computed_hmac then - -- log.debug("NET: network.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms") + -- log.debug("NET: network.modem.receive(): HMAC verified in " .. (util.time_ms() - start) .. "ms") s_packet.stamp_authenticated() else - -- log.debug("NET: network.modem.receive: HMAC failed verification in " .. (util.time_ms() - start) .. "ms") + -- log.debug("NET: network.modem.receive(): HMAC failed verification in " .. (util.time_ms() - start) .. "ms") end end end diff --git a/scada-common/util.lua b/scada-common/util.lua index c889037..fe661be 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -24,7 +24,7 @@ local t_pack = table.pack local util = {} -- scada-common version -util.version = "1.5.5" +util.version = "1.5.6" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 From 96acb03f732d1aac52d5387b694c721fb9a484c4 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Oct 2025 20:31:35 -0400 Subject: [PATCH 35/90] #580 reactor PLC wired/wireless configurator updates, RTU gateway and supervisor updates to theirs as well --- reactor-plc/config/system.lua | 220 ++++++++++++++++++++++++++-------- reactor-plc/configure.lua | 33 +++-- rtu/config/system.lua | 52 +++++--- rtu/configure.lua | 12 +- supervisor/config/system.lua | 2 + 5 files changed, 240 insertions(+), 79 deletions(-) diff --git a/reactor-plc/config/system.lua b/reactor-plc/config/system.lua index 7b51a9a..d1bd5ba 100644 --- a/reactor-plc/config/system.lua +++ b/reactor-plc/config/system.lua @@ -1,4 +1,5 @@ local log = require("scada-common.log") +local ppm = require("scada-common.ppm") local rsio = require("scada-common.rsio") local util = require("scada-common.util") @@ -20,6 +21,8 @@ local TextField = require("graphics.elements.form.TextField") local IndLight = require("graphics.elements.indicators.IndicatorLight") +local tri = util.trinary + local cpair = core.cpair local RIGHT = core.ALIGN.RIGHT @@ -30,6 +33,8 @@ local self = { set_networked = nil, ---@type function bundled_emcool = nil, ---@type function + wl_pref = nil, ---@type Checkbox + range = nil, ---@type NumberField show_auth_key = nil, ---@type function show_key_btn = nil, ---@type PushButton auth_key_textbox = nil, ---@type TextBox @@ -161,7 +166,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) local function submit_emcool() tmp_cfg.EmerCoolSide = side_options_map[side.get_value()] - tmp_cfg.EmerCoolColor = util.trinary(bundled.get_value(), color_options_map[color.get_value()], nil) + tmp_cfg.EmerCoolColor = tri(bundled.get_value(), color_options_map[color.get_value()], nil) tmp_cfg.EmerCoolInvert = invert.get_value() next_from_plc() end @@ -177,22 +182,77 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) local net_c_1 = Div{parent=net_cfg,x=2,y=4,width=49} local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=49} local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=49} + local net_c_4 = Div{parent=net_cfg,x=2,y=4,width=49} - local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3}} + local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4}} TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)} - TextBox{parent=net_c_1,x=1,y=1,text="Please set the network channels below."} - TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 2 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_1,x=1,y=1,text="Please select the network interface(s)."} + TextBox{parent=net_c_1,x=41,y=1,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision - TextBox{parent=net_c_1,x=1,y=8,text="Supervisor Channel"} - local svr_chan = NumberField{parent=net_c_1,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg} - TextBox{parent=net_c_1,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_1,x=1,y=11,text="PLC Channel"} - local plc_chan = NumberField{parent=net_c_1,x=1,y=12,width=7,default=ini_cfg.PLC_Channel,min=1,max=65535,fg_bg=bw_fg_bg} - TextBox{parent=net_c_1,x=9,y=12,height=4,text="[PLC_CHANNEL]",fg_bg=g_lg_fg_bg} + local function dis_pref(value) + if not value then + self.wl_pref.set_value(false) + self.wl_pref.disable() + else self.wl_pref.enable() end + end - local chan_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local function on_wired_change(_) tool_ctl.gen_modem_list() end + + local wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=dis_pref} + self.wl_pref = Checkbox{parent=net_c_1,x=30,y=3,label="Prefer Wireless",default=ini_cfg.PreferWireless,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} + local wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change} + TextBox{parent=net_c_1,x=3,y=6,text="MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)} + TextBox{parent=net_c_1,x=3,y=7,text="connecting to peripherals will cause problems",fg_bg=g_lg_fg_bg} + local modem_list = ListBox{parent=net_c_1,x=1,y=8,height=5,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} + + local modem_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + + dis_pref(ini_cfg.WirelessModem) + + local function submit_interfaces() + tmp_cfg.WirelessModem = wireless.get_value() + tmp_cfg.PreferWireless = tmp_cfg.WirelessModem and self.wl_pref.get_value() + + if not wired.get_value() then + tmp_cfg.WiredModem = false + tool_ctl.gen_modem_list() + end + + if not (wired.get_value() or wireless.get_value()) then + modem_err.set_value("Please select a modem type.") + modem_err.show() + elseif wired.get_value() and type(tmp_cfg.WiredModem) ~= "string" then + modem_err.set_value("Please select a wired modem.") + modem_err.show() + else + if tmp_cfg.WirelessModem then + self.range.enable() + else + self.range.set_value(0) + self.range.disable() + end + + net_pane.set_value(2) + modem_err.hide(true) + end + end + + PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_interfaces,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + TextBox{parent=net_c_2,x=1,y=1,text="Please set the network channels below."} + TextBox{parent=net_c_2,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 2 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg} + + TextBox{parent=net_c_2,x=1,y=8,text="Supervisor Channel"} + local svr_chan = NumberField{parent=net_c_2,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_2,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_2,x=1,y=11,text="PLC Channel"} + local plc_chan = NumberField{parent=net_c_2,x=1,y=12,width=7,default=ini_cfg.PLC_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_2,x=9,y=12,height=4,text="[PLC_CHANNEL]",fg_bg=g_lg_fg_bg} + + local chan_err = TextBox{parent=net_c_2,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_channels() local svr_c = tonumber(svr_chan.get_value()) @@ -200,7 +260,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) if svr_c ~= nil and plc_c ~= nil then tmp_cfg.SVR_Channel = svr_c tmp_cfg.PLC_Channel = plc_c - net_pane.set_value(2) + net_pane.set_value(3) chan_err.hide(true) elseif svr_c == nil then chan_err.set_value("Please set the supervisor channel.") @@ -211,54 +271,62 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) end end - PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=net_c_2,x=1,y=1,text="Connection Timeout"} - local timeout = NumberField{parent=net_c_2,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} - TextBox{parent=net_c_2,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_3,x=1,y=1,text="Connection Timeout"} + local timeout = NumberField{parent=net_c_3,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} + TextBox{parent=net_c_3,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_3,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_2,x=1,y=8,text="Trusted Range"} - local range = NumberField{parent=net_c_2,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg} - TextBox{parent=net_c_2,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_3,x=1,y=8,text="Trusted Range (Wireless Only)"} + self.range = NumberField{parent=net_c_3,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} + TextBox{parent=net_c_3,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents wireless connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} - local p2_err = TextBox{parent=net_c_2,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local n3_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_ct_tr() local timeout_val = tonumber(timeout.get_value()) - local range_val = tonumber(range.get_value()) - if timeout_val ~= nil and range_val ~= nil then - tmp_cfg.ConnTimeout = timeout_val - tmp_cfg.TrustedRange = range_val - net_pane.set_value(3) - p2_err.hide(true) - elseif timeout_val == nil then - p2_err.set_value("Please set the connection timeout.") - p2_err.show() + local range_val = tonumber(self.range.get_value()) + + if timeout_val == nil then + n3_err.set_value("Please set the connection timeout.") + n3_err.show() + elseif tmp_cfg.WirelessModem and (range_val == nil) then + n3_err.set_value("Please set the trusted range.") + n3_err.show() else - p2_err.set_value("Please set the trusted range.") - p2_err.show() + tmp_cfg.ConnTimeout = timeout_val + tmp_cfg.TrustedRange = tri(tmp_cfg.WirelessModem, range_val, 0) + + if tmp_cfg.WirelessModem then + net_pane.set_value(4) + else + main_pane.set_value(4) + tmp_cfg.AuthKey = "" + end + + n3_err.hide(true) end end - PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=net_c_3,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."} - TextBox{parent=net_c_3,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_4,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."} + TextBox{parent=net_c_4,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for wireless security on multiplayer servers. All devices on the same wireless network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_3,x=1,y=11,text="Facility Auth Key"} - local key, _ = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg} + TextBox{parent=net_c_4,x=1,y=11,text="Auth Key (Wireless Only, Not Used for Wired)"} + local key, _ = TextField{parent=net_c_4,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg} - local function censor_key(enable) key.censor(util.trinary(enable, "*", nil)) end + local function censor_key(enable) key.censor(tri(enable, "*", nil)) end - local hide_key = Checkbox{parent=net_c_3,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key} + local hide_key = Checkbox{parent=net_c_4,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key} hide_key.set_value(true) censor_key(true) - local key_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local key_err = TextBox{parent=net_c_4,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_auth() local v = key.get_value() @@ -269,8 +337,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) else key_err.show() end end - PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_4,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_4,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} --#endregion @@ -368,7 +436,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) PushButton{parent=clr_c_2,x=44,y=14,min_width=6,text="Done",callback=function()clr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} local function back_from_colors() - main_pane.set_value(util.trinary(tool_ctl.jumped_to_color, 1, 4)) + main_pane.set_value(tri(tool_ctl.jumped_to_color, 1, 4)) tool_ctl.jumped_to_color = false recolor(1) end @@ -471,10 +539,13 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) try_set(bundled, ini_cfg.EmerCoolColor ~= nil) if ini_cfg.EmerCoolColor ~= nil then try_set(color, color_to_idx(ini_cfg.EmerCoolColor)) end try_set(invert, ini_cfg.EmerCoolInvert) + try_set(wireless, ini_cfg.WirelessModem) + try_set(wired, ini_cfg.WiredModem ~= false) + try_set(self.wl_pref, ini_cfg.PreferWireless) try_set(svr_chan, ini_cfg.SVR_Channel) try_set(plc_chan, ini_cfg.PLC_Channel) try_set(timeout, ini_cfg.ConnTimeout) - try_set(range, ini_cfg.TrustedRange) + try_set(self.range, ini_cfg.TrustedRange) try_set(key, ini_cfg.AuthKey) try_set(mode, ini_cfg.LogMode) try_set(path, ini_cfg.LogPath) @@ -591,7 +662,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) local val = util.strval(raw) if f[1] == "AuthKey" and raw then val = string.rep("*", string.len(val)) - elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") + elseif f[1] == "LogMode" then val = tri(raw == log.MODE.APPEND, "append", "replace") elseif f[1] == "EmerCoolColor" and raw ~= nil then val = rsio.color_name(raw) elseif f[1] == "FrontPanelTheme" then val = util.strval(themes.fp_theme_name(raw)) @@ -601,7 +672,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) if val == "nil" then val = "" end - local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white)) + local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white)) alternate = not alternate if (string.len(val) > val_max_w) or string.find(val, "\n") then @@ -623,6 +694,59 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) end end + -- generate the list of available/assigned wired modems + function tool_ctl.gen_modem_list() + modem_list.remove_all() + + local enable = wired.get_value() + + local function select(iface) + tmp_cfg.WiredModem = iface + tool_ctl.gen_modem_list() + end + + local modems = ppm.get_wired_modem_list() + local missing = { tmp = true, ini = true } + + for iface, _ in pairs(modems) do + if ini_cfg.WiredModem == iface then missing.ini = false end + if tmp_cfg.WiredModem == iface then missing.tmp = false end + end + + if missing.tmp and tmp_cfg.WiredModem then + local line = Div{parent=modem_list,x=1,y=1,height=1} + + TextBox{parent=line,x=1,y=1,width=4,text="Used",fg_bg=cpair(tri(enable,colors.blue,colors.gray),colors.white)} + PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg}.disable() + TextBox{parent=line,x=15,y=1,text="[missing]",fg_bg=cpair(colors.red,colors.white)} + TextBox{parent=line,x=25,y=1,text=tmp_cfg.WiredModem} + end + + if missing.ini and ini_cfg.WiredModem and (tmp_cfg.WiredModem ~= ini_cfg.WiredModem) then + local line = Div{parent=modem_list,x=1,y=1,height=1} + local used = tmp_cfg.WiredModem == ini_cfg.WiredModem + + TextBox{parent=line,x=1,y=1,width=4,text=tri(used,"Used","----"),fg_bg=cpair(tri(used and enable,colors.blue,colors.gray),colors.white)} + local select_btn = PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()select(ini_cfg.WiredModem)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg} + TextBox{parent=line,x=15,y=1,text="[missing]",fg_bg=cpair(colors.red,colors.white)} + TextBox{parent=line,x=25,y=1,text=ini_cfg.WiredModem} + + if used or not enable then select_btn.disable() end + end + + -- list wired modems + for iface, _ in pairs(modems) do + local line = Div{parent=modem_list,x=1,y=1,height=1} + local used = tmp_cfg.WiredModem == iface + + TextBox{parent=line,x=1,y=1,width=4,text=tri(used,"Used","----"),fg_bg=cpair(tri(used and enable,colors.blue,colors.gray),colors.white)} + local select_btn = PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()select(iface)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg} + TextBox{parent=line,x=15,y=1,text=iface} + + if used or not enable then select_btn.disable() end + end + end + --#endregion end diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index 92431fa..4eb728d 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -3,6 +3,7 @@ -- local log = require("scada-common.log") +local ppm = require("scada-common.ppm") local tcd = require("scada-common.tcd") local util = require("scada-common.util") @@ -33,7 +34,8 @@ local changes = { { "v1.6.8", { "ConnTimeout can now have a fractional part" } }, { "v1.6.15", { "Added front panel UI theme", "Added color accessibility modes" } }, { "v1.7.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } }, - { "v1.8.21", { "Added option to invert emergency coolant redstone control" } } + { "v1.8.21", { "Added option to invert emergency coolant redstone control" } }, + { "v1.9.1", { "Added support for wired communications modems" } } } ---@class plc_configurator @@ -68,6 +70,8 @@ local tool_ctl = { gen_summary = nil, ---@type function load_legacy = nil, ---@type function + + gen_modem_list = function () end } ---@class plc_config @@ -78,12 +82,12 @@ local tmp_cfg = { EmerCoolSide = nil, ---@type string|nil EmerCoolColor = nil, ---@type color|nil EmerCoolInvert = false, ---@type boolean - SVR_Channel = nil, ---@type integer - PLC_Channel = nil, ---@type integer - ConnTimeout = nil, ---@type number WirelessModem = true, WiredModem = false, ---@type string|false PreferWireless = true, + SVR_Channel = nil, ---@type integer + PLC_Channel = nil, ---@type integer + ConnTimeout = nil, ---@type number TrustedRange = nil, ---@type number AuthKey = nil, ---@type string|nil LogMode = 0, ---@type LOG_MODE @@ -106,12 +110,12 @@ local fields = { { "EmerCoolSide", "Emergency Coolant Side", nil }, { "EmerCoolColor", "Emergency Coolant Color", nil }, { "EmerCoolInvert", "Emergency Coolant Invert", false }, - { "SVR_Channel", "SVR Channel", 16240 }, - { "PLC_Channel", "PLC Channel", 16241 }, - { "ConnTimeout", "Connection Timeout", 5 }, { "WirelessModem", "Wireless/Ender Comms Modem", true }, { "WiredModem", "Wired Comms Modem", false }, { "PreferWireless", "Prefer Wireless Modem", true }, + { "SVR_Channel", "SVR Channel", 16240 }, + { "PLC_Channel", "PLC Channel", 16241 }, + { "ConnTimeout", "Connection Timeout", 5 }, { "TrustedRange", "Trusted Range", 0 }, { "AuthKey", "Facility Auth Key" , ""}, { "LogMode", "Log Mode", log.MODE.APPEND }, @@ -267,8 +271,13 @@ function configurator.configure(ask_config) load_settings(settings_cfg, true) tool_ctl.has_config = load_settings(ini_cfg) + -- set tmp_cfg so interface lists are correct + tmp_cfg.WiredModem = ini_cfg.WiredModem + reset_term() + ppm.mount_all() + -- set overridden colors for i = 1, #style.colors do term.setPaletteColor(style.colors[i].c, style.colors[i].hex) @@ -278,6 +287,8 @@ function configurator.configure(ask_config) local display = DisplayBox{window=term.current(),fg_bg=style.root} config_view(display) + tool_ctl.gen_modem_list() + while true do local event, param1, param2, param3, param4, param5 = util.pull_event() @@ -294,6 +305,14 @@ function configurator.configure(ask_config) display.handle_paste(param1) elseif event == "modem_message" then check.receive_sv(param1, param2, param3, param4, param5) + elseif event == "peripheral_detach" then +---@diagnostic disable-next-line: discard-returns + ppm.handle_unmount(param1) + tool_ctl.gen_modem_list() + elseif event == "peripheral" then +---@diagnostic disable-next-line: discard-returns + ppm.mount(param1) + tool_ctl.gen_modem_list() end if event == "terminate" then return end diff --git a/rtu/config/system.lua b/rtu/config/system.lua index 77900e0..24ad1fb 100644 --- a/rtu/config/system.lua +++ b/rtu/config/system.lua @@ -31,6 +31,7 @@ local self = { importing_any_dc = false, wl_pref = nil, ---@type Checkbox + range = nil, ---@type NumberField show_auth_key = nil, ---@type function show_key_btn = nil, ---@type PushButton auth_key_textbox = nil, ---@type TextBox @@ -107,8 +108,6 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) else self.wl_pref.enable() end end - dis_pref(ini_cfg.WirelessModem) - local function on_wired_change(_) tool_ctl.gen_modem_list() end local wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=dis_pref} @@ -120,9 +119,11 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) local modem_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + dis_pref(ini_cfg.WirelessModem) + local function submit_interfaces() tmp_cfg.WirelessModem = wireless.get_value() - tmp_cfg.PreferWireless = self.wl_pref.get_value() + tmp_cfg.PreferWireless = tmp_cfg.WirelessModem and self.wl_pref.get_value() if not wired.get_value() then tmp_cfg.WiredModem = false @@ -136,6 +137,13 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) modem_err.set_value("Please select a wired modem.") modem_err.show() else + if tmp_cfg.WirelessModem then + self.range.enable() + else + self.range.set_value(0) + self.range.disable() + end + net_pane.set_value(2) modem_err.hide(true) end @@ -182,25 +190,33 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) TextBox{parent=net_c_3,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg} TextBox{parent=net_c_3,x=1,y=8,text="Trusted Range (Wireless Only)"} - local range = NumberField{parent=net_c_3,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg} - TextBox{parent=net_c_3,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} + self.range = NumberField{parent=net_c_3,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} + TextBox{parent=net_c_3,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents wireless connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} - local p2_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local n3_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_ct_tr() local timeout_val = tonumber(timeout.get_value()) - local range_val = tonumber(range.get_value()) - if timeout_val ~= nil and range_val ~= nil then - tmp_cfg.ConnTimeout = timeout_val - tmp_cfg.TrustedRange = range_val - net_pane.set_value(4) - p2_err.hide(true) - elseif timeout_val == nil then - p2_err.set_value("Please set the connection timeout.") - p2_err.show() + local range_val = tonumber(self.range.get_value()) + + if timeout_val == nil then + n3_err.set_value("Please set the connection timeout.") + n3_err.show() + elseif tmp_cfg.WirelessModem and (range_val == nil) then + n3_err.set_value("Please set the trusted range.") + n3_err.show() else - p2_err.set_value("Please set the trusted range.") - p2_err.show() + tmp_cfg.ConnTimeout = timeout_val + tmp_cfg.TrustedRange = tri(tmp_cfg.WirelessModem, range_val, 0) + + if tmp_cfg.WirelessModem then + net_pane.set_value(4) + else + main_pane.set_value(4) + tmp_cfg.AuthKey = "" + end + + n3_err.hide(true) end end @@ -437,7 +453,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) try_set(svr_chan, ini_cfg.SVR_Channel) try_set(rtu_chan, ini_cfg.RTU_Channel) try_set(timeout, ini_cfg.ConnTimeout) - try_set(range, ini_cfg.TrustedRange) + try_set(self.range, ini_cfg.TrustedRange) try_set(key, ini_cfg.AuthKey) try_set(mode, ini_cfg.LogMode) try_set(path, ini_cfg.LogPath) diff --git a/rtu/configure.lua b/rtu/configure.lua index 029d91a..6b119af 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -90,12 +90,12 @@ local tmp_cfg = { SpeakerVolume = 1.0, Peripherals = {}, ---@type rtu_peri_definition[] Redstone = {}, ---@type rtu_rs_definition[] - SVR_Channel = nil, ---@type integer - RTU_Channel = nil, ---@type integer - ConnTimeout = nil, ---@type number WirelessModem = true, WiredModem = false, ---@type string|false PreferWireless = true, + SVR_Channel = nil, ---@type integer + RTU_Channel = nil, ---@type integer + ConnTimeout = nil, ---@type number TrustedRange = nil, ---@type number AuthKey = nil, ---@type string LogMode = 0, ---@type LOG_MODE @@ -112,12 +112,12 @@ local settings_cfg = {} local fields = { { "SpeakerVolume", "Speaker Volume", 1.0 }, - { "SVR_Channel", "SVR Channel", 16240 }, - { "RTU_Channel", "RTU Channel", 16242 }, - { "ConnTimeout", "Connection Timeout", 5 }, { "WirelessModem", "Wireless/Ender Comms Modem", true }, { "WiredModem", "Wired Comms Modem", false }, { "PreferWireless", "Prefer Wireless Modem", true }, + { "SVR_Channel", "SVR Channel", 16240 }, + { "RTU_Channel", "RTU Channel", 16242 }, + { "ConnTimeout", "Connection Timeout", 5 }, { "TrustedRange", "Trusted Range", 0 }, { "AuthKey", "Facility Auth Key", "" }, { "LogMode", "Log Mode", log.MODE.APPEND }, diff --git a/supervisor/config/system.lua b/supervisor/config/system.lua index a1ab535..281ffcf 100644 --- a/supervisor/config/system.lua +++ b/supervisor/config/system.lua @@ -242,6 +242,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit net_pane.set_value(5) ct_err.hide(true) else + tmp_cfg.TrustedRange = 0 + tmp_cfg.AuthKey = "" main_pane.set_value(4) end else ct_err.show() end From e57c6205e2bb86491463ae0cf8a9ef23d7e1b3bd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Oct 2025 20:41:02 -0400 Subject: [PATCH 36/90] comment cleanup --- coordinator/threads.lua | 4 ++-- pocket/threads.lua | 4 ++-- reactor-plc/threads.lua | 8 ++++---- rtu/threads.lua | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/coordinator/threads.lua b/coordinator/threads.lua index 1e6dbbb..8c6036a 100644 --- a/coordinator/threads.lua +++ b/coordinator/threads.lua @@ -20,8 +20,8 @@ local log_comms = coordinator.log_comms local threads = {} -local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks) -local RENDER_SLEEP = 100 -- (100ms, 2 ticks) +local MAIN_CLOCK = 0.5 -- 2Hz, 10 ticks +local RENDER_SLEEP = 100 -- 100ms, 2 ticks local MQ__RENDER_CMD = { START_MAIN_UI = 1, diff --git a/pocket/threads.lua b/pocket/threads.lua index 50c0e05..0047df6 100644 --- a/pocket/threads.lua +++ b/pocket/threads.lua @@ -11,8 +11,8 @@ local core = require("graphics.core") local threads = {} -local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks) -local RENDER_SLEEP = 100 -- (100ms, 2 ticks) +local MAIN_CLOCK = 0.5 -- 2Hz, 10 ticks +local RENDER_SLEEP = 100 -- 100ms, 2 ticks local MQ__RENDER_DATA = pocket.MQ__RENDER_DATA diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 4216d13..da46bd0 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -11,10 +11,10 @@ local core = require("graphics.core") local threads = {} -local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks) -local RPS_SLEEP = 250 -- (250ms, 5 ticks) -local COMMS_SLEEP = 150 -- (150ms, 3 ticks) -local SP_CTRL_SLEEP = 250 -- (250ms, 5 ticks) +local MAIN_CLOCK = 0.5 -- 2Hz, 10 ticks +local RPS_SLEEP = 250 -- 250ms, 5 ticks +local COMMS_SLEEP = 150 -- 150ms, 3 ticks +local SP_CTRL_SLEEP = 250 -- 250ms, 5 ticks local BURN_RATE_RAMP_mB_s = 5.0 diff --git a/rtu/threads.lua b/rtu/threads.lua index 1008085..734361d 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -25,8 +25,8 @@ local threads = {} local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_HW_STATE = databus.RTU_HW_STATE -local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks) -local COMMS_SLEEP = 100 -- (100ms, 2 ticks) +local MAIN_CLOCK = 0.5 -- 2Hz, 10 ticks +local COMMS_SLEEP = 100 -- 100ms, 2 ticks ---@param smem rtu_shared_memory ---@param println_ts function From 5acc6470e31fb089d4209efad3f781bee7062c0b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Oct 2025 20:41:25 -0400 Subject: [PATCH 37/90] PPM parameter clarity --- scada-common/ppm.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index aa46808..7e4aa30 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -409,13 +409,13 @@ end -- get all mounted peripherals by type ---@nodiscard ----@param name string type name +---@param type string type name ---@return table devices device function tables -function ppm.get_all_devices(name) +function ppm.get_all_devices(type) local devices = {} for _, data in pairs(_ppm.mounts) do - if data.type == name then + if data.type == type then table.insert(devices, data.dev) end end From cddd9f74372f93c1a1966c23a456a077f4415ac3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 26 Oct 2025 22:19:37 -0400 Subject: [PATCH 38/90] #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() From 0cc62b3447c693c7c46df7e61bae4bd798b14729 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sun, 2 Nov 2025 17:01:20 +0000 Subject: [PATCH 39/90] removed unused databus functions --- reactor-plc/databus.lua | 7 ------- rtu/databus.lua | 7 ------- supervisor/databus.lua | 7 ------- 3 files changed, 21 deletions(-) diff --git a/reactor-plc/databus.lua b/reactor-plc/databus.lua index 65ecae9..98c1671 100644 --- a/reactor-plc/databus.lua +++ b/reactor-plc/databus.lua @@ -94,11 +94,4 @@ function databus.tx_rps(tripped, status, emer_cool_active) databus.ps.publish("emer_cool", emer_cool_active) end --- link a function to receive data from the bus ----@param field string field name ----@param func function function to link -function databus.rx_field(field, func) - databus.ps.subscribe(field, func) -end - return databus diff --git a/rtu/databus.lua b/rtu/databus.lua index c4eb0ed..50deeb7 100644 --- a/rtu/databus.lua +++ b/rtu/databus.lua @@ -70,11 +70,4 @@ function databus.tx_link_state(state) databus.ps.publish("link_state", state) end --- link a function to receive data from the bus ----@param field string field name ----@param func function function to link -function databus.rx_field(field, func) - databus.ps.subscribe(field, func) -end - return databus diff --git a/supervisor/databus.lua b/supervisor/databus.lua index 5accd5d..6841157 100644 --- a/supervisor/databus.lua +++ b/supervisor/databus.lua @@ -172,11 +172,4 @@ function databus.tx_pdg_rtt(session_id, rtt) end end --- link a function to receive data from the bus ----@param field string field name ----@param func function function to link -function databus.rx_field(field, func) - databus.ps.subscribe(field, func) -end - return databus From 34eb16df00300bfa15dd9f58afbee91cd3150597 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sun, 2 Nov 2025 17:01:55 +0000 Subject: [PATCH 40/90] #634 supervisor backplane updates --- supervisor/backplane.lua | 14 +++++--------- supervisor/startup.lua | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/supervisor/backplane.lua b/supervisor/backplane.lua index b026958..d1630e4 100644 --- a/supervisor/backplane.lua +++ b/supervisor/backplane.lua @@ -79,6 +79,7 @@ function backplane.init(config) databus.tx_hw_wl_modem(true) end + ---@todo this should be a config check check if not ((type(config.WiredModem) == "string" or config.WirelessModem)) then println("startup> no modems configured") log.fatal("BKPLN: no modems configured") @@ -101,10 +102,7 @@ function backplane.attach(iface, type, device, print_no_fp) 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 + if _bp.wd_nic and (_bp.lan_iface == iface) then -- connect this as the wired NIC _bp.wd_nic.connect(device) @@ -112,7 +110,7 @@ function backplane.attach(iface, type, device, print_no_fp) print_no_fp("wired comms modem reconnected") databus.tx_hw_wd_modem(true) - elseif is_wl then + elseif _bp.wl_nic and (not _bp.wl_nic.is_connected()) and m_is_wl then -- connect this as the wireless NIC _bp.wl_nic.connect(device) _bp.nic_map[iface] = _bp.wl_nic @@ -142,14 +140,12 @@ function backplane.detach(iface, type, device, print_no_fp) ---@cast device Modem local m_is_wl = device.isWireless() - local was_wd = _bp.wd_nic and _bp.wd_nic.is_modem(device) - local was_wl = _bp.wl_nic and _bp.wl_nic.is_modem(device) log.info(util.c("BKPLN: ", util.trinary(m_is_wl, "WIRELESS", "WIRED"), " PHY_DETACH ", iface)) _bp.nic_map[iface] = nil - if _bp.wd_nic and was_wd then + if _bp.wd_nic and _bp.wd_nic.is_modem(device) then _bp.wd_nic.disconnect() log.info("BKPLN: WIRED PHY_DOWN " .. iface) @@ -157,7 +153,7 @@ function backplane.detach(iface, type, device, print_no_fp) log.warning("BKPLN: wired comms modem disconnected") databus.tx_hw_wd_modem(false) - elseif _bp.wl_nic and was_wl then + elseif _bp.wl_nic and _bp.wl_nic.is_modem(device) then _bp.wl_nic.disconnect() log.info("BKPLN: WIRELESS PHY_DOWN " .. iface) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index ad2f096..43d6003 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -126,7 +126,7 @@ local function main() network.init_mac(config.AuthKey) end - -- hardware backplane initialization + -- modem initialization if not backplane.init(config) then return end -- start UI From f88dc0b5b90c58cb0198272a40dbef7aa7793af9 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sun, 2 Nov 2025 17:03:41 +0000 Subject: [PATCH 41/90] #634 RTU gateway backplane rework --- rtu/backplane.lua | 181 ++++++++++++++++++++-------------------------- rtu/rtu.lua | 83 ++++++++------------- rtu/startup.lua | 13 ++-- rtu/threads.lua | 4 +- 4 files changed, 117 insertions(+), 164 deletions(-) diff --git a/rtu/backplane.lua b/rtu/backplane.lua index ebf0961..63dc14c 100644 --- a/rtu/backplane.lua +++ b/rtu/backplane.lua @@ -10,6 +10,8 @@ local util = require("scada-common.util") local databus = require("rtu.databus") local rtu = require("rtu.rtu") +local println = util.println + ---@class rtu_backplane local backplane = {} @@ -19,8 +21,7 @@ local _bp = { wlan_pref = true, lan_iface = "", - act_nic = nil, ---@type nic|nil - wl_act = true, + act_nic = nil, ---@type nic wd_nic = nil, ---@type nic|nil wl_nic = nil, ---@type nic|nil @@ -30,41 +31,51 @@ local _bp = { -- initialize the system peripheral backplane ---@param config rtu_config ---@param __shared_memory rtu_shared_memory +---@return boolean success function backplane.init(config, __shared_memory) _bp.smem = __shared_memory _bp.wlan_pref = config.PreferWireless _bp.lan_iface = config.WiredModem + -- Modem Init + -- init wired NIC if type(config.WiredModem) == "string" then - local modem = ppm.get_modem(_bp.lan_iface) + local modem = ppm.get_modem(_bp.lan_iface) + local wd_nic = network.nic(modem) - if modem then - _bp.wd_nic = network.nic(modem) - log.info("BKPLN: WIRED PHY_UP " .. _bp.lan_iface) - end + log.info("BKPLN: WIRED PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. _bp.lan_iface) + + -- set this as active for now + _bp.act_nic = wd_nic + _bp.wd_nic = wd_nic end -- init wireless NIC(s) if config.WirelessModem then local modem, iface = ppm.get_wireless_modem() + local wl_nic = network.nic(modem) - if modem then - _bp.wl_nic = network.nic(modem) - log.info("BKPLN: WIRELESS PHY_UP " .. iface) + 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.act_nic = wl_nic end + + _bp.wl_nic = wl_nic end - -- grab the preferred active NIC - if _bp.wlan_pref then - _bp.wl_act = true - _bp.act_nic = _bp.wl_nic - else - _bp.wl_act = false - _bp.act_nic = _bp.wd_nic + databus.tx_hw_modem(_bp.act_nic.is_connected()) + + -- at least one comms modem is required + if not ((_bp.wd_nic and _bp.wd_nic.is_connected()) or (_bp.wl_nic and _bp.wl_nic.is_connected())) then + println("startup> comms modem not found") + log.warning("BKPLN: no comms modem on startup") + return false end - databus.tx_hw_modem(_bp.act_nic ~= nil) + -- Speaker Init -- find and setup all speakers local speakers = ppm.get_all_devices("speaker") @@ -77,10 +88,12 @@ function backplane.init(config, __shared_memory) end databus.tx_hw_spkr_count(#_bp.sounders) + + return true end -- get the active NIC ----@return nic|nil +---@return nic function backplane.active_nic() return _bp.act_nic end -- get the sounder interfaces @@ -91,9 +104,8 @@ function backplane.sounders() return _bp.sounders end ---@param type string ---@param device table ---@param iface string -function backplane.detach(type, device, iface) - local function println_ts(message) if not _bp.smem.rtu_state.fp_ok then util.println_ts(message) end end - +---@param print_no_fp function +function backplane.detach(type, device, iface, print_no_fp) local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic local comms = _bp.smem.rtu_sys.rtu_comms @@ -101,72 +113,58 @@ function backplane.detach(type, device, iface) if type == "modem" then ---@cast device Modem - local m_is_wl = device.isWireless() - local was_active = _bp.act_nic and _bp.act_nic.is_modem(device) - local was_wd = wd_nic and wd_nic.is_modem(device) - local was_wl = wl_nic and wl_nic.is_modem(device) + local m_is_wl = device.isWireless() log.info(util.c("BKPLN: ", util.trinary(m_is_wl, "WIRELESS", "WIRED"), " PHY_DETACH ", iface)) - if wd_nic and was_wd then + if wd_nic and wd_nic.is_modem(device) then wd_nic.disconnect() log.info("BKPLN: WIRED PHY_DOWN " .. iface) - elseif wl_nic and was_wl then + elseif wl_nic and wl_nic.is_modem(device) then wl_nic.disconnect() log.info("BKPLN: WIRELESS PHY_DOWN " .. iface) end -- we only care if this is our active comms modem - if was_active then - println_ts("active comms modem disconnected") + if _bp.act_nic.is_modem(device) then + print_no_fp("active comms modem disconnected") log.warning("BKPLN: active comms modem disconnected") -- failover and try to find a new comms modem - if _bp.wl_act then + if _bp.act_nic == wl_nic then + -- wireless active disconnected -- try to find another wireless modem, otherwise switch to wired local modem, m_iface = ppm.get_wireless_modem() - if modem then + if wl_nic and modem then log.info("BKPLN: found another wireless modem, using it for comms") - -- note: must assign to self.wl_nic if creating a nic, otherwise it only changes locally - if wl_nic then - wl_nic.connect(modem) - else _bp.wl_nic = network.nic(modem) end + wl_nic.connect(modem) log.info("BKPLN: WIRELESS PHY_UP " .. m_iface) - - _bp.act_nic = wl_nic - comms.assign_nic(_bp.act_nic) - log.info("BKPLN: switched comms to new wireless modem") elseif wd_nic and wd_nic.is_connected() then - _bp.wl_act = false _bp.act_nic = _bp.wd_nic - comms.assign_nic(_bp.act_nic) + comms.switch_nic(_bp.act_nic) log.info("BKPLN: switched comms to wired modem") else - _bp.act_nic = nil databus.tx_hw_modem(false) - comms.unassign_nic() end - else - -- switch to wireless if able - if wl_nic then - _bp.wl_act = true - _bp.act_nic = wl_nic + elseif wl_nic and wl_nic.is_connected() then + -- wired active disconnected, wireless available + _bp.act_nic = wl_nic - comms.assign_nic(_bp.act_nic) - log.info("BKPLN: switched comms to wireless modem") - else - _bp.act_nic = nil - databus.tx_hw_modem(false) - comms.unassign_nic() - end + comms.switch_nic(_bp.act_nic) + log.info("BKPLN: switched comms to wireless modem") + else + -- wired active disconnected, wireless unavailable + databus.tx_hw_modem(false) end elseif _bp.wl_nic and m_is_wl then -- wireless, but not active + print_no_fp("standby wireless modem disconnected") log.info("BKPLN: standby wireless modem disconnected") else + print_no_fp("unassigned modem disconnected") log.warning("BKPLN: unassigned modem disconnected") end elseif type == "speaker" then @@ -175,8 +173,8 @@ function backplane.detach(type, device, iface) if _bp.sounders[i].speaker == device then table.remove(_bp.sounders, i) + print_no_fp("a speaker was disconnected") log.warning(util.c("BKPLN: speaker ", iface, " disconnected")) - println_ts("speaker disconnected") databus.tx_hw_spkr_count(#_bp.sounders) break @@ -189,8 +187,9 @@ end ---@param type string ---@param device table ---@param iface string -function backplane.attach(type, device, iface) - local function println_ts(message) if not _bp.smem.rtu_state.fp_ok then util.println_ts(message) end end +---@param print_no_fp function +function backplane.attach(type, device, iface, print_no_fp) + local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic local comms = _bp.smem.rtu_sys.rtu_comms @@ -201,71 +200,51 @@ function backplane.attach(type, device, iface) log.info(util.c("BKPLN: ", util.trinary(m_is_wl, "WIRELESS", "WIRED"), " PHY_ATTACH ", iface)) - local is_wd = _bp.lan_iface == iface - local is_wl = ((not _bp.wl_nic) or (not _bp.wl_nic.is_connected())) and m_is_wl - - if is_wd then + if wd_nic and (_bp.lan_iface == iface) then -- connect this as the wired NIC - if _bp.wd_nic then - _bp.wd_nic.connect(device) - else _bp.wd_nic = network.nic(device) end + wd_nic.connect(device) log.info("BKPLN: WIRED PHY_UP " .. iface) + print_no_fp("wired comms modem reconnected") - if _bp.act_nic == nil then - -- set as active - _bp.wl_act = false - _bp.act_nic = _bp.wd_nic - - comms.assign_nic(_bp.act_nic) - databus.tx_hw_modem(true) - println_ts("comms modem reconnected") - log.info("BKPLN: switched comms to wired modem") - elseif _bp.wl_act and not _bp.wlan_pref then + if (_bp.act_nic ~= wd_nic) and not _bp.wlan_pref then -- switch back to preferred wired - _bp.wl_act = false - _bp.act_nic = _bp.wd_nic + _bp.act_nic = wd_nic - comms.assign_nic(_bp.act_nic) + comms.switch_nic(_bp.act_nic) log.info("BKPLN: switched comms to wired modem (preferred)") + + databus.tx_hw_modem(true) end - elseif is_wl then + elseif wl_nic and (not wl_nic.is_connected()) and m_is_wl then -- connect this as the wireless NIC - if _bp.wl_nic then - _bp.wl_nic.connect(device) - else _bp.wl_nic = network.nic(device) end + wl_nic.connect(device) log.info("BKPLN: WIRELESS PHY_UP " .. iface) - if _bp.act_nic == nil then - -- set as active - _bp.wl_act = true - _bp.act_nic = _bp.wl_nic - - comms.assign_nic(_bp.act_nic) - databus.tx_hw_modem(true) - println_ts("comms modem reconnected") - log.info("BKPLN: switched comms to wireless modem") - elseif (not _bp.wl_act) and _bp.wlan_pref then + if (_bp.act_nic ~= wl_nic) and _bp.wlan_pref then -- switch back to preferred wireless - _bp.wl_act = true - _bp.act_nic = _bp.wl_nic + _bp.act_nic = wl_nic - comms.assign_nic(_bp.act_nic) + comms.switch_nic(_bp.act_nic) log.info("BKPLN: switched comms to wireless modem (preferred)") + + databus.tx_hw_modem(true) end - elseif m_is_wl then + elseif wl_nic and m_is_wl then -- the wireless NIC already has a modem - log.info("standby wireless modem connected") + print_no_fp("standby wireless modem connected") + log.info("BKPLN: standby wireless modem connected") else - log.info("wired modem connected") + print_no_fp("unassigned modem connected") + log.warning("BKPLN: unassigned modem connected") end elseif type == "speaker" then ---@cast device Speaker table.insert(_bp.sounders, rtu.init_sounder(device)) - println_ts("speaker connected") - log.info(util.c("connected speaker ", iface)) + print_no_fp("a speaker was connected") + log.info(util.c("BKPLN: connected speaker ", iface)) databus.tx_hw_spkr_count(#_bp.sounders) end diff --git a/rtu/rtu.lua b/rtu/rtu.lua index f86d00a..9901a86 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -293,7 +293,7 @@ end -- RTU Communications ---@nodiscard ---@param version string RTU version ----@param nic nic|nil network interface device +---@param nic nic network interface device ---@param conn_watchdog watchdog watchdog reference function rtu.comms(version, nic, conn_watchdog) local self = { @@ -306,15 +306,12 @@ function rtu.comms(version, nic, conn_watchdog) local insert = table.insert - -- CONDITIONAL PRIVATE FUNCTIONS -- - - -- these don't check for nic to be nil to save execution time on functions called extremely often - -- when the nic isn't present, the aliases _send and _send_modbus are cleared + -- PRIVATE FUNCTIONS -- -- send a scada management packet ---@param msg_type MGMT_TYPE ---@param msg table - local function _nic_send(msg_type, msg) + local function _send(msg_type, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() @@ -326,23 +323,6 @@ function rtu.comms(version, nic, conn_watchdog) self.seq_num = self.seq_num + 1 end - -- send a MODBUS TCP packet - ---@param m_pkt modbus_packet - local function _nic_send_modbus(m_pkt) - local s_pkt = comms.scada_packet() - - s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable()) - ----@diagnostic disable-next-line: need-check-nil - nic.transmit(config.SVR_Channel, config.RTU_Channel, s_pkt) - self.seq_num = self.seq_num + 1 - end - - -- PRIVATE FUNCTIONS -- - - -- send a scada management packet - local _send = _nic_send - -- keep alive ack ---@param srv_time integer local function _send_keep_alive_ack(srv_time) @@ -372,8 +352,21 @@ function rtu.comms(version, nic, conn_watchdog) ---@class rtu_comms local public = {} - -- send a MODBUS TCP packet - public.send_modbus = _nic_send_modbus + -- 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.RTU_Channel) + + nic = _nic + end -- unlink from the server ---@param rtu_state rtu_state @@ -392,6 +385,18 @@ function rtu.comms(version, nic, conn_watchdog) _send(MGMT_TYPE.CLOSE, {}) end + -- send a MODBUS TCP packet + ---@param m_pkt modbus_packet + function public.send_modbus(m_pkt) + local s_pkt = comms.scada_packet() + + s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable()) + +---@diagnostic disable-next-line: need-check-nil + nic.transmit(config.SVR_Channel, config.RTU_Channel, s_pkt) + self.seq_num = self.seq_num + 1 + end + -- send establish request (includes advertisement) ---@param units table function public.send_establish(units) @@ -612,34 +617,6 @@ function rtu.comms(version, nic, conn_watchdog) end end - -- set the current NIC - ---@param _nic nic - function public.assign_nic(_nic) - if nic then nic.closeAll() end - - if _nic.isWireless() then - comms.set_trusted_range(config.TrustedRange) - end - - -- configure receive channels - _nic.closeAll() - _nic.open(config.RTU_Channel) - - nic = _nic - _send = _nic_send - public.send_modbus = _nic_send_modbus - end - - -- clear the current NIC - function public.unassign_nic() - _send = function () end - public.send_modbus = function () end - nic = nil - end - - -- set the NIC if one was given - if nic then public.assign_nic(nic) else public.unassign_nic() end - return public end diff --git a/rtu/startup.lua b/rtu/startup.lua index 649750d..26230e1 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -112,14 +112,15 @@ local function main() local units = __shared_memory.rtu_sys.units ---------------------------------------- - -- start system + -- init and start system ---------------------------------------- + -- modem and speaker initialization + if not backplane.init(config, __shared_memory) then return end + log.debug("boot> running uinit()") if uinit(config, __shared_memory) then - -- init backplane peripherals - backplane.init(config, __shared_memory) -- start UI local message @@ -139,11 +140,7 @@ local function main() -- setup comms local nic = backplane.active_nic() smem_sys.rtu_comms = rtu.comms(RTU_VERSION, nic, smem_sys.conn_watchdog) - if nic then - log.debug("startup> comms init") - else - log.warning("startup> no comms modem on startup") - end + log.debug("startup> comms init") -- init threads local main_thread = threads.thread__main(__shared_memory) diff --git a/rtu/threads.lua b/rtu/threads.lua index 734361d..57fb288 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -247,7 +247,7 @@ function threads.thread__main(smem) if type ~= nil and device ~= nil then if type == "modem" or type == "speaker" then - backplane.detach(type, device, param1) + backplane.detach(type, device, param1, println_ts) else for i = 1, #units do -- find disconnected device @@ -272,7 +272,7 @@ function threads.thread__main(smem) if type ~= nil and device ~= nil then if type == "modem" or type == "speaker" then - backplane.attach(type, device, param1) + backplane.attach(type, device, param1, println_ts) else -- relink lost peripheral to correct unit entry for i = 1, #units do From d7a280bb04fab2eddd39a774c3c82ff261340a17 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sun, 2 Nov 2025 17:10:01 +0000 Subject: [PATCH 42/90] #634 reactor PLC backplane and front panel updates --- reactor-plc/backplane.lua | 135 ++++++++++++++++++++++-------- reactor-plc/databus.lua | 21 ++--- reactor-plc/panel/front_panel.lua | 30 ++++--- reactor-plc/startup.lua | 4 +- reactor-plc/threads.lua | 59 +++---------- 5 files changed, 141 insertions(+), 108 deletions(-) diff --git a/reactor-plc/backplane.lua b/reactor-plc/backplane.lua index f5e2230..e384e50 100644 --- a/reactor-plc/backplane.lua +++ b/reactor-plc/backplane.lua @@ -7,9 +7,6 @@ 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 @@ -22,7 +19,6 @@ local _bp = { lan_iface = "", act_nic = nil, ---@type nic - wl_act = true, wd_nic = nil, ---@type nic|nil wl_nic = nil ---@type nic|nil } @@ -44,32 +40,33 @@ function backplane.init(config, __shared_memory) 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) + local modem = ppm.get_modem(_bp.lan_iface) + local wd_nic = network.nic(modem) log.info("BKPLN: WIRED PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. _bp.lan_iface) - plc_state.wd_modem = _bp.wd_nic.is_connected() + plc_state.wd_modem = wd_nic.is_connected() -- set this as active for now - _bp.wl_act = false - _bp.act_nic = _bp.wd_nic + _bp.act_nic = wd_nic + _bp.wd_nic = wd_nic end -- init wireless NIC(s) if config.WirelessModem then local modem, iface = ppm.get_wireless_modem() - _bp.wl_nic = network.nic(modem) + local wl_nic = network.nic(modem) log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. iface) - plc_state.wl_modem = _bp.wl_nic.is_connected() + plc_state.wl_modem = 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 - _bp.act_nic = _bp.wl_nic + _bp.act_nic = wl_nic end + + _bp.wl_nic = wl_nic end -- comms modem is required if networked @@ -123,6 +120,8 @@ function backplane.active_nic() return _bp.act_nic end function backplane.attach(iface, type, device, print_no_fp) local MQ__RPS_CMD = _bp.smem.q_cmds.MQ__RPS_CMD + local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic + local networked = _bp.smem.networked local state = _bp.smem.plc_state local dev = _bp.smem.plc_dev @@ -163,52 +162,39 @@ function backplane.attach(iface, type, device, print_no_fp) 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 + if wd_nic and (_bp.lan_iface == iface) then -- connect this as the wired NIC - _bp.wd_nic.connect(device) + 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 + if (_bp.act_nic ~= wd_nic) and not _bp.wlan_pref then -- switch back to preferred wired - _bp.wl_act = false - _bp.act_nic = _bp.wd_nic + _bp.act_nic = wd_nic sys.plc_comms.switch_nic(_bp.act_nic) log.info("BKPLN: switched comms to wired modem (preferred)") end - elseif is_wl then + elseif wl_nic and (not wl_nic.is_connected()) and m_is_wl then -- connect this as the wireless NIC - _bp.wl_nic.connect(device) + 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 + if (_bp.act_nic ~= wl_nic) and _bp.wlan_pref then -- switch back to preferred wireless - _bp.wl_act = true - _bp.act_nic = _bp.wl_nic + _bp.act_nic = 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 + elseif 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") @@ -231,6 +217,85 @@ end ---@param device table ---@param print_no_fp function function backplane.detach(iface, type, device, print_no_fp) + local MQ__RPS_CMD = _bp.smem.q_cmds.MQ__RPS_CMD + + local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic + + local state = _bp.smem.plc_state + local dev = _bp.smem.plc_dev + local sys = _bp.smem.plc_sys + + if device == dev.reactor then + print_no_fp("reactor disconnected") + log.warning("BKPLN: reactor disconnected") + + state.no_reactor = true + state.degraded = true + elseif _bp.smem.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_DETACH ", iface)) + + if wd_nic and wd_nic.is_modem(device) then + wd_nic.disconnect() + log.info("BKPLN: WIRED PHY_DOWN " .. iface) + + state.wd_modem = false + elseif wl_nic and wl_nic.is_modem(device) then + wl_nic.disconnect() + log.info("BKPLN: WIRELESS PHY_DOWN " .. iface) + + state.wl_modem = false + end + + -- we only care if this is our active comms modem + if _bp.act_nic.is_modem(device) then + print_no_fp("active comms modem disconnected") + log.warning("BKPLN: active comms modem disconnected") + + -- failover and try to find a new comms modem + if _bp.act_nic == wl_nic then + -- wireless active disconnected + -- try to find another wireless modem, otherwise switch to wired + local modem, m_iface = ppm.get_wireless_modem() + if wl_nic and modem then + log.info("BKPLN: found another wireless modem, using it for comms") + + wl_nic.connect(modem) + + log.info("BKPLN: WIRELESS PHY_UP " .. m_iface) + elseif wd_nic and wd_nic.is_connected() then + _bp.act_nic = wd_nic + + sys.plc_comms.switch_nic(_bp.act_nic) + log.info("BKPLN: switched comms to wired modem") + else + -- no other wireless modems, wired unavailable + state.degraded = true + _bp.smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM) + end + elseif wl_nic and wl_nic.is_connected() then + -- wired active disconnected, wireless available + _bp.act_nic = wl_nic + + sys.plc_comms.switch_nic(_bp.act_nic) + log.info("BKPLN: switched comms to wireless modem") + else + -- wired active disconnected, wireless unavailable + state.degraded = true + _bp.smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM) + end + elseif wl_nic and m_is_wl then + -- wireless, but not active + print_no_fp("standby wireless modem disconnected") + log.info("BKPLN: standby wireless modem disconnected") + else + print_no_fp("unassigned modem disconnected") + log.warning("BKPLN: unassigned modem disconnected") + end + end end return backplane diff --git a/reactor-plc/databus.lua b/reactor-plc/databus.lua index 98c1671..32d3c9f 100644 --- a/reactor-plc/databus.lua +++ b/reactor-plc/databus.lua @@ -43,36 +43,29 @@ end -- transmit unit ID across the bus ---@param id integer unit ID -function databus.tx_id(id) - databus.ps.publish("unit_id", id) -end +function databus.tx_id(id) databus.ps.publish("unit_id", id) end -- transmit hardware status across the bus ---@param plc_state plc_state function databus.tx_hw_status(plc_state) - databus.ps.publish("reactor_dev_state", util.trinary(plc_state.no_reactor, 1, util.trinary(plc_state.reactor_formed, 3, 2))) - databus.ps.publish("has_modem", not plc_state.no_modem) databus.ps.publish("degraded", plc_state.degraded) + databus.ps.publish("reactor_dev_state", util.trinary(plc_state.no_reactor, 1, util.trinary(plc_state.reactor_formed, 3, 2))) + databus.ps.publish("has_wd_modem", plc_state.wd_modem) + databus.ps.publish("has_wl_modem", plc_state.wl_modem) end -- transmit thread (routine) statuses ---@param thread string thread name ---@param ok boolean thread state -function databus.tx_rt_status(thread, ok) - databus.ps.publish(util.c("routine__", thread), ok) -end +function databus.tx_rt_status(thread, ok) databus.ps.publish(util.c("routine__", thread), ok) end -- transmit supervisor link state across the bus ---@param state integer -function databus.tx_link_state(state) - databus.ps.publish("link_state", state) -end +function databus.tx_link_state(state) databus.ps.publish("link_state", state) end -- transmit reactor enable state across the bus ---@param active any reactor active -function databus.tx_reactor_state(active) - databus.ps.publish("reactor_active", active == true) -end +function databus.tx_reactor_state(active) databus.ps.publish("reactor_active", active == true) end -- transmit RPS data across the bus ---@param tripped boolean RPS tripped diff --git a/reactor-plc/panel/front_panel.lua b/reactor-plc/panel/front_panel.lua index dfd9d19..c3e81a7 100644 --- a/reactor-plc/panel/front_panel.lua +++ b/reactor-plc/panel/front_panel.lua @@ -35,7 +35,8 @@ local ind_red = style.ind_red -- create new front panel view ---@param panel DisplayBox main displaybox -local function init(panel) +---@param config plc_config configuraiton +local function init(panel, config) local s_hi_box = style.theme.highlight_box local disabled_fg = style.fp.disabled_fg @@ -59,7 +60,21 @@ local function init(panel) heartbeat.register(databus.ps, "heartbeat", heartbeat.update) local reactor = LEDPair{parent=system,label="REACTOR",off=colors.red,c1=colors.yellow,c2=colors.green} - local modem = LED{parent=system,label="MODEM",colors=ind_grn} + reactor.register(databus.ps, "reactor_dev_state", reactor.update) + + if config.Networked then + if config.WirelessModem and config.WiredModem then + local wl_modem = LED{parent=system,label="WD MODEM",colors=ind_grn} + local wd_modem = LED{parent=system,label="WL MODEM",colors=ind_grn} + wd_modem.register(databus.ps, "wd_modem", wd_modem.update) + wl_modem.register(databus.ps, "wl_modem", wl_modem.update) + else + local modem = LED{parent=system,label="MODEM",colors=ind_grn} + modem.register(databus.ps, util.trinary(config.WirelessModem, "wl_modem", "wd_modem"), modem.update) + end + else + local _ = LED{parent=system,label="MODEM",colors=ind_grn} + end if not style.colorblind then local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.yellow,colors.orange,style.ind_bkg}} @@ -99,9 +114,6 @@ local function init(panel) system.line_break() - reactor.register(databus.ps, "reactor_dev_state", reactor.update) - modem.register(databus.ps, "has_modem", modem.update) - local rt_main = LED{parent=system,label="RT MAIN",colors=ind_grn} local rt_rps = LED{parent=system,label="RT RPS",colors=ind_grn} local rt_cmtx = LED{parent=system,label="RT COMMS TX",colors=ind_grn} @@ -150,12 +162,10 @@ local function init(panel) -- about footer -- - local about = Div{parent=panel,width=15,height=2,y=term_h-1,fg_bg=disabled_fg} - local fw_v = TextBox{parent=about,text="FW: v00.00.00"} - local comms_v = TextBox{parent=about,text="NT: v00.00.00"} + local info_text = util.sprintf("FW: %s | NT: v%s", databus.ps.get("version"), databus.ps.get("comms_version")) - fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end) - comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end) + local about = Div{parent=panel,height=1,y=term_h,fg_bg=disabled_fg} + TextBox{parent=about,y=1,text=info_text} -- -- rps list diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 1fc5137..393b89e 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -114,7 +114,6 @@ local function main() ---@class plc_sys plc_sys = { rps = nil, ---@type rps - nic = nil, ---@type nic plc_comms = nil, ---@type plc_comms conn_watchdog = nil ---@type watchdog }, @@ -189,8 +188,7 @@ local function main() log.debug("startup> conn watchdog started") -- create network interface then setup comms - 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) + smem_sys.plc_comms = plc.comms(R_PLC_VERSION, backplane.active_nic(), smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog) log.debug("startup> comms init") else _println_no_fp("startup> starting in non-networked mode") diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 281ad16..cc0e631 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -41,24 +41,21 @@ 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 MQ__RPS_CMD = smem.q_cmds.MQ__RPS_CMD - local MQ__COMM_CMD = smem.q_cmds.MQ__COMM_CMD + local rps = smem.plc_sys.rps + local plc_comms = smem.plc_sys.plc_comms + local conn_watchdog = smem.plc_sys.conn_watchdog + + 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() -- event loop while true do - -- get plc_sys fields (may have been set late due to degraded boot) - local rps = smem.plc_sys.rps - local nic = smem.plc_sys.nic - local plc_comms = smem.plc_sys.plc_comms - local conn_watchdog = smem.plc_sys.conn_watchdog - local event, param1, param2, param3, param4, param5 = util.pull_event() -- handle event @@ -70,10 +67,10 @@ function threads.thread__main(smem) loop_clock.start() -- send updated data - if networked and nic.is_connected() then + if networked then if plc_comms.is_linked() then smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS) - else + elseif backplane.active_nic().is_connected() then if ticks_to_update == 0 then plc_comms.send_link_req() ticks_to_update = LINK_TICKS @@ -95,7 +92,7 @@ function threads.thread__main(smem) smem.q.mq_rps.push_command(MQ__RPS_CMD.SCRAM) -- determine if we are still in a degraded state - if (not networked) or nic.is_connected() then + if (not networked) or backplane.active_nic().is_connected() then plc_state.degraded = false end @@ -113,7 +110,7 @@ function threads.thread__main(smem) -- update indicators databus.tx_hw_status(plc_state) - elseif event == "modem_message" and networked and nic.is_connected() then + elseif event == "modem_message" and networked then -- got a packet local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5) if packet ~= nil then @@ -130,38 +127,8 @@ function threads.thread__main(smem) elseif event == "peripheral_detach" then -- peripheral disconnect local type, device = ppm.handle_unmount(param1) - if type ~= nil and device ~= nil then - if device == plc_dev.reactor then - println_ts("reactor disconnected!") - log.error("reactor logic adapter disconnected") - - plc_state.no_reactor = true - plc_state.degraded = true - elseif networked and type == "modem" then - ---@cast device Modem - -- we only care if this is our comms modem - if nic.is_modem(device) then - nic.disconnect() - - println_ts("comms modem disconnected!") - log.warning("comms modem disconnected") - - local other_modem = ppm.get_wireless_modem() - if other_modem and not plc_dev.modem_wired then - log.info("found another wireless modem, using it for comms") - nic.connect(other_modem) - else - plc_state.no_modem = true - plc_state.degraded = true - - -- try to scram reactor if it is still connected - smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM) - end - else - log.warning("non-comms modem disconnected") - end - end + backplane.detach(param1, type, device, println_ts) end -- update indicators From 8bbf385c41fe73f4b266abaf7d7fb56588d3f73b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 2 Nov 2025 13:05:07 -0500 Subject: [PATCH 43/90] #580 fixed supervisor listen mode logic --- supervisor/backplane.lua | 25 ++++++------------------- supervisor/supervisor.lua | 20 ++++++++++++++++---- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/supervisor/backplane.lua b/supervisor/backplane.lua index d1630e4..69fcd94 100644 --- a/supervisor/backplane.lua +++ b/supervisor/backplane.lua @@ -5,13 +5,10 @@ local log = require("scada-common.log") local network = require("scada-common.network") local ppm = require("scada-common.ppm") -local types = require("scada-common.types") local util = require("scada-common.util") local databus = require("supervisor.databus") -local LISTEN_MODE = types.LISTEN_MODE - local println = util.println ---@class supervisor_backplane @@ -47,11 +44,10 @@ function backplane.init(config) _bp.wd_nic = nic _bp.nic_map[_bp.lan_iface] = nic - nic.closeAll() + log.info("BKPLN: WIRED PHY_UP " .. _bp.lan_iface) - if config.PLC_Listen ~= LISTEN_MODE.WIRELESS then nic.open(config.PLC_Channel) end - if config.RTU_Listen ~= LISTEN_MODE.WIRELESS then nic.open(config.RTU_Channel) end - if config.CRD_Listen ~= LISTEN_MODE.WIRELESS then nic.open(config.CRD_Channel) end + nic.closeAll() + nic.open(config.SVR_Channel) databus.tx_hw_wd_modem(true) end @@ -69,23 +65,14 @@ function backplane.init(config) _bp.wl_nic = nic _bp.nic_map[iface] = nic - nic.closeAll() + log.info("BKPLN: WIRELESS PHY_UP " .. iface) - if config.PLC_Listen ~= LISTEN_MODE.WIRED then nic.open(config.PLC_Channel) end - if config.RTU_Listen ~= LISTEN_MODE.WIRED then nic.open(config.RTU_Channel) end - if config.CRD_Listen ~= LISTEN_MODE.WIRED then nic.open(config.CRD_Channel) end - if config.PocketEnabled then nic.open(config.PKT_Channel) end + nic.closeAll() + nic.open(config.SVR_Channel) databus.tx_hw_wl_modem(true) end - ---@todo this should be a config check check - if not ((type(config.WiredModem) == "string" or config.WirelessModem)) then - println("startup> no modems configured") - log.fatal("BKPLN: no modems configured") - return false - end - return true end diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index bbdcad7..3c5fd51 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -1,5 +1,6 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") +local types = require("scada-common.types") local util = require("scada-common.util") local themes = require("graphics.themes") @@ -16,6 +17,8 @@ local ESTABLISH_ACK = comms.ESTABLISH_ACK local PROBE_ACK = comms.PROBE_ACK local MGMT_TYPE = comms.MGMT_TYPE +local LISTEN_MODE = types.LISTEN_MODE + ---@type svr_config ---@diagnostic disable-next-line: missing-fields local config = {} @@ -114,6 +117,7 @@ function supervisor.load_config() cfv.assert_type_bool(config.WirelessModem) cfv.assert((config.WiredModem == false) or (type(config.WiredModem) == "string")) + cfv.assert((config.WirelessModem == true) or (type(config.WiredModem) == "string")) cfv.assert_type_num(config.PLC_Listen) cfv.assert_range(config.PLC_Listen, 0, 2) @@ -208,7 +212,9 @@ function supervisor.comms(_version, fp_ok, facility) local firmware_v = packet.data[2] local dev_type = packet.data[3] - if comms_v ~= comms.version then + if (config.PLC_Listen ~= LISTEN_MODE.ALL) and (nic.isWireless() ~= (config.PLC_Listen == LISTEN_MODE.WIRELESS)) and periphemu == nil then + -- drop if not listening + elseif comms_v ~= comms.version then if last_ack ~= ESTABLISH_ACK.BAD_VERSION then log.info(util.c("dropping PLC establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) end @@ -266,7 +272,9 @@ function supervisor.comms(_version, fp_ok, facility) local firmware_v = packet.data[2] local dev_type = packet.data[3] - if comms_v ~= comms.version then + if (config.RTU_Listen ~= LISTEN_MODE.ALL) and (nic.isWireless() ~= (config.RTU_Listen == LISTEN_MODE.WIRELESS)) and periphemu == nil then + -- drop if not listening + elseif comms_v ~= comms.version then if last_ack ~= ESTABLISH_ACK.BAD_VERSION then log.info(util.c("dropping RTU_GW establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) end @@ -302,7 +310,9 @@ function supervisor.comms(_version, fp_ok, facility) local firmware_v = packet.data[2] local dev_type = packet.data[3] - if comms_v ~= comms.version then + if (config.CRD_Listen ~= LISTEN_MODE.ALL) and (nic.isWireless() ~= (config.CRD_Listen == LISTEN_MODE.WIRELESS)) and periphemu == nil then + -- drop if not listening + elseif comms_v ~= comms.version then if last_ack ~= ESTABLISH_ACK.BAD_VERSION then log.info(util.c("dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) end @@ -341,7 +351,9 @@ function supervisor.comms(_version, fp_ok, facility) local firmware_v = packet.data[2] local dev_type = packet.data[3] - if comms_v ~= comms.version then + if not config.PocketEnabled then + -- drop if not listening + elseif comms_v ~= comms.version then if last_ack ~= ESTABLISH_ACK.BAD_VERSION then log.info(util.c("dropping PKT establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) end From 4c8d5bc4a08246435d3ee153279467914fad524d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 2 Nov 2025 13:19:04 -0500 Subject: [PATCH 44/90] #580 plc front panel updates --- reactor-plc/panel/front_panel.lua | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/reactor-plc/panel/front_panel.lua b/reactor-plc/panel/front_panel.lua index c3e81a7..3df6b3b 100644 --- a/reactor-plc/panel/front_panel.lua +++ b/reactor-plc/panel/front_panel.lua @@ -66,11 +66,11 @@ local function init(panel, config) if config.WirelessModem and config.WiredModem then local wl_modem = LED{parent=system,label="WD MODEM",colors=ind_grn} local wd_modem = LED{parent=system,label="WL MODEM",colors=ind_grn} - wd_modem.register(databus.ps, "wd_modem", wd_modem.update) - wl_modem.register(databus.ps, "wl_modem", wl_modem.update) + wd_modem.register(databus.ps, "has_wd_modem", wd_modem.update) + wl_modem.register(databus.ps, "has_wl_modem", wl_modem.update) else local modem = LED{parent=system,label="MODEM",colors=ind_grn} - modem.register(databus.ps, util.trinary(config.WirelessModem, "wl_modem", "wd_modem"), modem.update) + modem.register(databus.ps, util.trinary(config.WirelessModem, "has_wl_modem", "has_wd_modem"), modem.update) end else local _ = LED{parent=system,label="MODEM",colors=ind_grn} @@ -127,12 +127,8 @@ local function init(panel, config) rt_cmrx.register(databus.ps, "routine__comms_rx", rt_cmrx.update) rt_sctl.register(databus.ps, "routine__spctl", rt_sctl.update) ----@diagnostic disable-next-line: undefined-field - local comp_id = util.sprintf("(%d)", os.getComputerID()) - TextBox{parent=system,x=9,y=5,width=6,text=comp_id,fg_bg=disabled_fg} - -- - -- status & controls + -- status & controls & hardware labels -- local status = Div{parent=panel,width=term_w-32,height=18,x=17,y=3} @@ -158,14 +154,14 @@ local function init(panel, config) active.register(databus.ps, "reactor_active", active.update) scram.register(databus.ps, "rps_scram", scram.update) - -- - -- about footer - -- + local hw_labels = Rectangle{parent=status,width=status.get_width()-2,height=5,x=1,border=border(1,s_hi_box.bkg,true),even_inner=true} - local info_text = util.sprintf("FW: %s | NT: v%s", databus.ps.get("version"), databus.ps.get("comms_version")) +---@diagnostic disable-next-line: undefined-field + local comp_id = util.sprintf("%03d", os.getComputerID()) - local about = Div{parent=panel,height=1,y=term_h,fg_bg=disabled_fg} - TextBox{parent=about,y=1,text=info_text} + TextBox{parent=hw_labels,text="FW "..databus.ps.get("version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="COMM v"..databus.ps.get("comms_version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="S/N PLC-"..comp_id,fg_bg=s_hi_box} -- -- rps list From 0417986c15eeece6b0b094d8434b79c78fea4fe5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 2 Nov 2025 13:30:25 -0500 Subject: [PATCH 45/90] #580 updated supervisor front panel --- reactor-plc/panel/front_panel.lua | 2 +- supervisor/panel/front_panel.lua | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/reactor-plc/panel/front_panel.lua b/reactor-plc/panel/front_panel.lua index 3df6b3b..b76b3cb 100644 --- a/reactor-plc/panel/front_panel.lua +++ b/reactor-plc/panel/front_panel.lua @@ -128,7 +128,7 @@ local function init(panel, config) rt_sctl.register(databus.ps, "routine__spctl", rt_sctl.update) -- - -- status & controls & hardware labels + -- status & controls & hardware labeling -- local status = Div{parent=panel,width=term_w-32,height=18,x=17,y=3} diff --git a/supervisor/panel/front_panel.lua b/supervisor/panel/front_panel.lua index 14e224e..9a28db6 100644 --- a/supervisor/panel/front_panel.lua +++ b/supervisor/panel/front_panel.lua @@ -19,6 +19,7 @@ local core = require("graphics.core") local Div = require("graphics.elements.Div") local ListBox = require("graphics.elements.ListBox") local MultiPane = require("graphics.elements.MultiPane") +local Rectangle = require("graphics.elements.Rectangle") local TextBox = require("graphics.elements.TextBox") local TabBar = require("graphics.elements.controls.TabBar") @@ -29,6 +30,7 @@ local DataIndicator = require("graphics.elements.indicators.DataIndicator") local ALIGN = core.ALIGN local cpair = core.cpair +local border = core.border local ind_grn = style.ind_grn @@ -77,20 +79,18 @@ local function init(panel, config) wd_modem.register(databus.ps, "has_wd_modem", wd_modem.update) end + -- + -- hardware labeling + -- + + local hw_labels = Rectangle{parent=main_page,x=2,y=term_h-7,width=17,height=5,border=border(1,s_hi_box.bkg,true),even_inner=true} + ---@diagnostic disable-next-line: undefined-field - local comp_id = util.sprintf("(%d)", os.getComputerID()) - TextBox{parent=system,x=12,y=4,width=6,text=comp_id,fg_bg=style.fp.disabled_fg} + local comp_id = util.sprintf("%03d", os.getComputerID()) - -- - -- about footer - -- - - local about = Div{parent=main_page,width=15,height=2,y=term_h-3,fg_bg=style.fp.disabled_fg} - local fw_v = TextBox{parent=about,text="FW: v00.00.00"} - local comms_v = TextBox{parent=about,text="NT: v00.00.00"} - - fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end) - comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end) + TextBox{parent=hw_labels,text="FW "..databus.ps.get("version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="COMM v"..databus.ps.get("comms_version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="S/N SVR-"..comp_id,fg_bg=s_hi_box} -- -- page handling From d9001090c23d35413fcb902b8fb9dd708caa6061 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 2 Nov 2025 14:30:02 -0500 Subject: [PATCH 46/90] #580 comment updates, rtu listen fix, and trusted range init change --- reactor-plc/plc.lua | 16 ++++++++-------- rtu/rtu.lua | 20 ++++++++++++++------ supervisor/supervisor.lua | 12 +++++++++--- 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index a88a119..3aa5b01 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -559,16 +559,16 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) max_burn_rate = nil } - if nic.isWireless() then + if config.WirelessModem then comms.set_trusted_range(config.TrustedRange) end - -- PRIVATE FUNCTIONS -- - -- configure network channels nic.closeAll() nic.open(config.PLC_Channel) + --#region PRIVATE FUNCTIONS -- + -- send an RPLC packet ---@param msg_type RPLC_TYPE ---@param msg table @@ -825,7 +825,9 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) end end - -- PUBLIC FUNCTIONS -- + --#endregion + + --#region PUBLIC FUNCTIONS -- ---@class plc_comms local public = {} @@ -835,10 +837,6 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) 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) @@ -1123,6 +1121,8 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) ---@nodiscard function public.is_linked() return self.linked end + --#endregion + return public end diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 9901a86..a4c9657 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -306,7 +306,15 @@ function rtu.comms(version, nic, conn_watchdog) local insert = table.insert - -- PRIVATE FUNCTIONS -- + if config.WirelessModem then + comms.set_trusted_range(config.TrustedRange) + end + + -- configure network channels + nic.closeAll() + nic.open(config.RTU_Channel) + + --#region PRIVATE FUNCTIONS -- -- send a scada management packet ---@param msg_type MGMT_TYPE @@ -347,7 +355,9 @@ function rtu.comms(version, nic, conn_watchdog) return advertisement end - -- PUBLIC FUNCTIONS -- + --#endregion + + --#region PUBLIC FUNCTIONS -- ---@class rtu_comms local public = {} @@ -357,10 +367,6 @@ function rtu.comms(version, nic, conn_watchdog) 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.RTU_Channel) @@ -617,6 +623,8 @@ function rtu.comms(version, nic, conn_watchdog) end end + --#endregion + return public end diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 3c5fd51..3778f3a 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -164,12 +164,14 @@ function supervisor.comms(_version, fp_ok, facility) last_est_acks = {} ---@type ESTABLISH_ACK[] } - comms.set_trusted_range(config.TrustedRange) + if config.WirelessModem then + comms.set_trusted_range(config.TrustedRange) + end -- pass system data and objects to svsessions svsessions.init(fp_ok, config, facility) - -- PRIVATE FUNCTIONS -- + --#region PRIVATE FUNCTIONS -- -- send an establish request response ---@param nic nic @@ -376,7 +378,9 @@ function supervisor.comms(_version, fp_ok, facility) --#endregion - -- PUBLIC FUNCTIONS -- + --#endregion + + --#region PUBLIC FUNCTIONS -- ---@class superv_comms local public = {} @@ -601,6 +605,8 @@ function supervisor.comms(_version, fp_ok, facility) end end + --#endregion + return public end From cc9d5fe2d603721844bffe7f8d2a6d1d2ef145d5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 2 Nov 2025 15:02:56 -0500 Subject: [PATCH 47/90] #580 rtu gateway front panel changes and updates to plc and supervisor front panels --- reactor-plc/databus.lua | 16 +++++++--- reactor-plc/panel/front_panel.lua | 14 ++++----- rtu/backplane.lua | 19 +++++++----- rtu/databus.lua | 12 ++++++-- rtu/panel/front_panel.lua | 51 +++++++++++++++++++------------ rtu/renderer.lua | 11 +++---- rtu/startup.lua | 2 +- supervisor/panel/front_panel.lua | 24 ++++++--------- 8 files changed, 87 insertions(+), 62 deletions(-) diff --git a/reactor-plc/databus.lua b/reactor-plc/databus.lua index 32d3c9f..52f74ad 100644 --- a/reactor-plc/databus.lua +++ b/reactor-plc/databus.lua @@ -43,7 +43,9 @@ end -- transmit unit ID across the bus ---@param id integer unit ID -function databus.tx_id(id) databus.ps.publish("unit_id", id) end +function databus.tx_id(id) + databus.ps.publish("unit_id", id) +end -- transmit hardware status across the bus ---@param plc_state plc_state @@ -57,15 +59,21 @@ end -- transmit thread (routine) statuses ---@param thread string thread name ---@param ok boolean thread state -function databus.tx_rt_status(thread, ok) databus.ps.publish(util.c("routine__", thread), ok) end +function databus.tx_rt_status(thread, ok) + databus.ps.publish(util.c("routine__", thread), ok) +end -- transmit supervisor link state across the bus ---@param state integer -function databus.tx_link_state(state) databus.ps.publish("link_state", state) end +function databus.tx_link_state(state) + databus.ps.publish("link_state", state) +end -- transmit reactor enable state across the bus ---@param active any reactor active -function databus.tx_reactor_state(active) databus.ps.publish("reactor_active", active == true) end +function databus.tx_reactor_state(active) + databus.ps.publish("reactor_active", active == true) +end -- transmit RPS data across the bus ---@param tripped boolean RPS tripped diff --git a/reactor-plc/panel/front_panel.lua b/reactor-plc/panel/front_panel.lua index b76b3cb..985d00c 100644 --- a/reactor-plc/panel/front_panel.lua +++ b/reactor-plc/panel/front_panel.lua @@ -39,9 +39,7 @@ local ind_red = style.ind_red local function init(panel, config) local s_hi_box = style.theme.highlight_box - local disabled_fg = style.fp.disabled_fg - - local term_w, term_h = term.getSize() + local term_w, _ = term.getSize() local header = TextBox{parent=panel,y=1,text="FISSION REACTOR PLC - UNIT ?",alignment=ALIGN.CENTER,fg_bg=style.theme.header} header.register(databus.ps, "unit_id", function (id) header.set_value(util.c("FISSION REACTOR PLC - UNIT ", id)) end) @@ -64,8 +62,8 @@ local function init(panel, config) if config.Networked then if config.WirelessModem and config.WiredModem then - local wl_modem = LED{parent=system,label="WD MODEM",colors=ind_grn} - local wd_modem = LED{parent=system,label="WL MODEM",colors=ind_grn} + local wd_modem = LED{parent=system,label="WD MODEM",colors=ind_grn} + local wl_modem = LED{parent=system,label="WL MODEM",colors=ind_grn} wd_modem.register(databus.ps, "has_wd_modem", wd_modem.update) wl_modem.register(databus.ps, "has_wl_modem", wl_modem.update) else @@ -159,9 +157,9 @@ local function init(panel, config) ---@diagnostic disable-next-line: undefined-field local comp_id = util.sprintf("%03d", os.getComputerID()) - TextBox{parent=hw_labels,text="FW "..databus.ps.get("version"),fg_bg=s_hi_box} - TextBox{parent=hw_labels,text="COMM v"..databus.ps.get("comms_version"),fg_bg=s_hi_box} - TextBox{parent=hw_labels,text="S/N PLC-"..comp_id,fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="FW "..databus.ps.get("version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="NT v"..databus.ps.get("comms_version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="S/N PLC-"..comp_id,fg_bg=s_hi_box} -- -- rps list diff --git a/rtu/backplane.lua b/rtu/backplane.lua index 63dc14c..4415fe0 100644 --- a/rtu/backplane.lua +++ b/rtu/backplane.lua @@ -49,6 +49,8 @@ function backplane.init(config, __shared_memory) -- set this as active for now _bp.act_nic = wd_nic _bp.wd_nic = wd_nic + + databus.tx_hw_wd_modem(modem ~= nil) end -- init wireless NIC(s) @@ -64,9 +66,9 @@ function backplane.init(config, __shared_memory) end _bp.wl_nic = wl_nic - end - databus.tx_hw_modem(_bp.act_nic.is_connected()) + databus.tx_hw_wl_modem(modem ~= nil) + end -- at least one comms modem is required if not ((_bp.wd_nic and _bp.wd_nic.is_connected()) or (_bp.wl_nic and _bp.wl_nic.is_connected())) then @@ -120,9 +122,13 @@ function backplane.detach(type, device, iface, print_no_fp) if wd_nic and wd_nic.is_modem(device) then wd_nic.disconnect() log.info("BKPLN: WIRED PHY_DOWN " .. iface) + + databus.tx_hw_wd_modem(false) elseif wl_nic and wl_nic.is_modem(device) then wl_nic.disconnect() log.info("BKPLN: WIRELESS PHY_DOWN " .. iface) + + databus.tx_hw_wl_modem(false) end -- we only care if this is our active comms modem @@ -141,13 +147,13 @@ function backplane.detach(type, device, iface, print_no_fp) wl_nic.connect(modem) log.info("BKPLN: WIRELESS PHY_UP " .. m_iface) + + databus.tx_hw_wl_modem(true) elseif wd_nic and wd_nic.is_connected() then _bp.act_nic = _bp.wd_nic comms.switch_nic(_bp.act_nic) log.info("BKPLN: switched comms to wired modem") - else - databus.tx_hw_modem(false) end elseif wl_nic and wl_nic.is_connected() then -- wired active disconnected, wireless available @@ -157,7 +163,6 @@ function backplane.detach(type, device, iface, print_no_fp) log.info("BKPLN: switched comms to wireless modem") else -- wired active disconnected, wireless unavailable - databus.tx_hw_modem(false) end elseif _bp.wl_nic and m_is_wl then -- wireless, but not active @@ -214,7 +219,7 @@ function backplane.attach(type, device, iface, print_no_fp) comms.switch_nic(_bp.act_nic) log.info("BKPLN: switched comms to wired modem (preferred)") - databus.tx_hw_modem(true) + databus.tx_hw_wd_modem(true) end elseif wl_nic and (not wl_nic.is_connected()) and m_is_wl then -- connect this as the wireless NIC @@ -229,7 +234,7 @@ function backplane.attach(type, device, iface, print_no_fp) comms.switch_nic(_bp.act_nic) log.info("BKPLN: switched comms to wireless modem (preferred)") - databus.tx_hw_modem(true) + databus.tx_hw_wl_modem(true) end elseif wl_nic and m_is_wl then -- the wireless NIC already has a modem diff --git a/rtu/databus.lua b/rtu/databus.lua index 50deeb7..aec06ae 100644 --- a/rtu/databus.lua +++ b/rtu/databus.lua @@ -31,10 +31,16 @@ function databus.tx_versions(rtu_v, comms_v) databus.ps.publish("comms_version", comms_v) end --- transmit hardware status for comms modem connection state +-- transmit hardware status for the wireless comms modem connection state ---@param has_modem boolean -function databus.tx_hw_modem(has_modem) - databus.ps.publish("has_modem", has_modem) +function databus.tx_hw_wl_modem(has_modem) + databus.ps.publish("has_wl_modem", has_modem) +end + +-- transmit hardware status for the wired comms modem connection state +---@param has_modem boolean +function databus.tx_hw_wd_modem(has_modem) + databus.ps.publish("has_wd_modem", has_modem) end -- transmit the number of speakers connected diff --git a/rtu/panel/front_panel.lua b/rtu/panel/front_panel.lua index 0b9c2ed..4166783 100644 --- a/rtu/panel/front_panel.lua +++ b/rtu/panel/front_panel.lua @@ -12,6 +12,7 @@ local style = require("rtu.panel.style") local core = require("graphics.core") local Div = require("graphics.elements.Div") +local Rectangle = require("graphics.elements.Rectangle") local TextBox = require("graphics.elements.TextBox") local DataIndicator = require("graphics.elements.indicators.DataIndicator") @@ -25,6 +26,7 @@ local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local ALIGN = core.ALIGN local cpair = core.cpair +local border = core.border local ind_grn = style.ind_grn @@ -32,8 +34,11 @@ local UNIT_TYPE_LABELS = { "UNKNOWN", "REDSTONE", "BOILER", "TURBINE", "DYNAMIC -- create new front panel view ---@param panel DisplayBox main displaybox +---@param config rtu_config configuraiton ---@param units rtu_registry_entry[] unit list -local function init(panel, units) +local function init(panel, config, units) + local s_hi_box = style.theme.highlight_box + local disabled_fg = style.fp.disabled_fg local term_w, term_h = term.getSize() @@ -53,7 +58,15 @@ local function init(panel, units) heartbeat.register(databus.ps, "heartbeat", heartbeat.update) - local modem = LED{parent=system,label="MODEM",colors=ind_grn} + if config.WirelessModem and config.WiredModem then + local wd_modem = LED{parent=system,label="WD MODEM",colors=ind_grn} + local wl_modem = LED{parent=system,label="WL MODEM",colors=ind_grn} + wd_modem.register(databus.ps, "has_wd_modem", wd_modem.update) + wl_modem.register(databus.ps, "has_wl_modem", wl_modem.update) + else + local modem = LED{parent=system,label="MODEM",colors=ind_grn} + modem.register(databus.ps, util.trinary(config.WirelessModem, "has_wl_modem", "has_wd_modem"), modem.update) + end if not style.colorblind then local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.yellow,colors.orange,style.ind_bkg}} @@ -90,8 +103,6 @@ local function init(panel, units) system.line_break() - modem.register(databus.ps, "has_modem", modem.update) - local rt_main = LED{parent=system,label="RT MAIN",colors=ind_grn} local rt_comm = LED{parent=system,label="RT COMMS",colors=ind_grn} system.line_break() @@ -99,25 +110,27 @@ local function init(panel, units) rt_main.register(databus.ps, "routine__main", rt_main.update) rt_comm.register(databus.ps, "routine__comms", rt_comm.update) + -- + -- hardware labeling + -- + + local hw_labels = Rectangle{parent=panel,y=term_h-6,width=15,height=5,border=border(1,s_hi_box.bkg,true),even_inner=true} + ---@diagnostic disable-next-line: undefined-field - local comp_id = util.sprintf("(%d)", os.getComputerID()) - TextBox{parent=system,x=9,y=4,width=6,text=comp_id,fg_bg=disabled_fg} + local comp_id = util.sprintf("%03d", os.getComputerID()) - TextBox{parent=system,y=term_h-5,text="SPEAKERS",width=8,fg_bg=style.fp.text_fg} - local speaker_count = DataIndicator{parent=system,x=10,y=term_h-5,label="",format="%3d",value=0,width=3,fg_bg=style.theme.field_box} + TextBox{parent=hw_labels,text="FW "..databus.ps.get("version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="NT v"..databus.ps.get("comms_version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="S/N RTU-"..comp_id,fg_bg=s_hi_box} + + -- + -- speaker count + -- + + TextBox{parent=panel,x=2,y=term_h-1,text="SPEAKERS",width=8,fg_bg=style.fp.text_fg} + local speaker_count = DataIndicator{parent=panel,x=11,y=term_h-1,label="",format="%3d",value=0,width=3,fg_bg=style.theme.field_box} speaker_count.register(databus.ps, "speaker_count", speaker_count.update) - -- - -- about label - -- - - local about = Div{parent=panel,width=15,height=2,y=term_h-1,fg_bg=disabled_fg} - local fw_v = TextBox{parent=about,text="FW: v00.00.00"} - local comms_v = TextBox{parent=about,text="NT: v00.00.00"} - - fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end) - comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end) - -- -- unit status list -- diff --git a/rtu/renderer.lua b/rtu/renderer.lua index dd86a06..0c8366d 100644 --- a/rtu/renderer.lua +++ b/rtu/renderer.lua @@ -18,16 +18,15 @@ local ui = { } -- try to start the UI +---@param config rtu_config configuration ---@param units rtu_registry_entry[] RTU entries ----@param theme FP_THEME front panel theme ----@param color_mode COLOR_MODE color mode ---@return boolean success, any error_msg -function renderer.try_start_ui(units, theme, color_mode) +function renderer.try_start_ui(config, units) 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) @@ -41,7 +40,7 @@ function renderer.try_start_ui(units, 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 @@ -49,7 +48,7 @@ function renderer.try_start_ui(units, 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, units) + panel_view(ui.display, config, units) end) if status then diff --git a/rtu/startup.lua b/rtu/startup.lua index 26230e1..ad6172a 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -124,7 +124,7 @@ local function main() -- start UI local message - rtu_state.fp_ok, message = renderer.try_start_ui(units, config.FrontPanelTheme, config.ColorMode) + rtu_state.fp_ok, message = renderer.try_start_ui(config, units) if not rtu_state.fp_ok then println_ts(util.c("UI error: ", message)) diff --git a/supervisor/panel/front_panel.lua b/supervisor/panel/front_panel.lua index 9a28db6..b86520b 100644 --- a/supervisor/panel/front_panel.lua +++ b/supervisor/panel/front_panel.lua @@ -65,32 +65,28 @@ local function init(panel, config) heartbeat.register(databus.ps, "heartbeat", heartbeat.update) - if config.WirelessModem then - local wl_modem = LED{parent=system,label="WL MODEM",colors=ind_grn} - system.line_break() - - wl_modem.register(databus.ps, "has_wl_modem", wl_modem.update) - end - - if config.WiredModem then + if config.WirelessModem and config.WiredModem then local wd_modem = LED{parent=system,label="WD MODEM",colors=ind_grn} - system.line_break() - + local wl_modem = LED{parent=system,label="WL MODEM",colors=ind_grn} wd_modem.register(databus.ps, "has_wd_modem", wd_modem.update) + wl_modem.register(databus.ps, "has_wl_modem", wl_modem.update) + else + local modem = LED{parent=system,label="MODEM",colors=ind_grn} + modem.register(databus.ps, util.trinary(config.WirelessModem, "has_wl_modem", "has_wd_modem"), modem.update) end -- -- hardware labeling -- - local hw_labels = Rectangle{parent=main_page,x=2,y=term_h-7,width=17,height=5,border=border(1,s_hi_box.bkg,true),even_inner=true} + local hw_labels = Rectangle{parent=main_page,y=term_h-7,width=15,height=5,border=border(1,s_hi_box.bkg,true),even_inner=true} ---@diagnostic disable-next-line: undefined-field local comp_id = util.sprintf("%03d", os.getComputerID()) - TextBox{parent=hw_labels,text="FW "..databus.ps.get("version"),fg_bg=s_hi_box} - TextBox{parent=hw_labels,text="COMM v"..databus.ps.get("comms_version"),fg_bg=s_hi_box} - TextBox{parent=hw_labels,text="S/N SVR-"..comp_id,fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="FW "..databus.ps.get("version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="NT v"..databus.ps.get("comms_version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="S/N SVR-"..comp_id,fg_bg=s_hi_box} -- -- page handling From 802ef149c587aebcca290584fcfa300f27554121 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 2 Nov 2025 15:09:45 -0500 Subject: [PATCH 48/90] #638 require pocket testing enabled rather than HMAC for alarm testing --- supervisor/session/pocket.lua | 31 ++++++++++--------------------- supervisor/session/svsessions.lua | 2 +- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/supervisor/session/pocket.lua b/supervisor/session/pocket.lua index 7500110..c42d59d 100644 --- a/supervisor/session/pocket.lua +++ b/supervisor/session/pocket.lua @@ -39,7 +39,8 @@ local PERIODICS = { ---@param sessions svsessions_list list of computer sessions, read-only ---@param facility facility facility data table ---@param fp_ok boolean if the front panel UI is running -function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, sessions, facility, fp_ok) +---@param allow_test boolean if this should allow pocket testing commands +function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, sessions, facility, fp_ok, allow_test) -- print a log message to the terminal as long as the UI isn't running local function println(message) if not fp_ok then util.println_ts(message) end end @@ -143,7 +144,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, local valid = false -- attempt to set a tone state - if pkt.scada_frame.is_authenticated() then + if allow_test then if pkt.length == 2 then if type(pkt.data[1]) == "number" and type(pkt.data[2]) == "boolean" then valid = true @@ -151,22 +152,16 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, -- try to set tone states, then send back if testing is allowed local allow_testing, test_tone_states = facility.diag_set_test_tone(pkt.data[1], pkt.data[2]) _send_mgmt(MGMT_TYPE.DIAG_TONE_SET, { allow_testing, test_tone_states }) - else - log.debug(log_tag .. "SCADA diag tone set packet data type mismatch") - end - else - log.debug(log_tag .. "SCADA diag tone set packet length mismatch") - end - else - log.debug(log_tag .. "DIAG_TONE_SET is blocked without HMAC for security") - end + else log.debug(log_tag .. "SCADA diag tone set packet data type mismatch") end + else log.debug(log_tag .. "SCADA diag tone set packet length mismatch") end + else log.warning(log_tag .. "DIAG_TONE_SET is blocked without pocket test commands enabled") end if not valid then _send_mgmt(MGMT_TYPE.DIAG_TONE_SET, { false }) end elseif pkt.type == MGMT_TYPE.DIAG_ALARM_SET then local valid = false -- attempt to set an alarm state - if pkt.scada_frame.is_authenticated() then + if allow_test then if pkt.length == 2 then if type(pkt.data[1]) == "number" and type(pkt.data[2]) == "boolean" then valid = true @@ -174,15 +169,9 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, -- try to set alarm states, then send back if testing is allowed local allow_testing, test_alarm_states = facility.diag_set_test_alarm(pkt.data[1], pkt.data[2]) _send_mgmt(MGMT_TYPE.DIAG_ALARM_SET, { allow_testing, test_alarm_states }) - else - log.debug(log_tag .. "SCADA diag alarm set packet data type mismatch") - end - else - log.debug(log_tag .. "SCADA diag alarm set packet length mismatch") - end - else - log.debug(log_tag .. "DIAG_ALARM_SET is blocked without HMAC for security") - end + else log.debug(log_tag .. "SCADA diag alarm set packet data type mismatch") end + else log.debug(log_tag .. "SCADA diag alarm set packet length mismatch") end + else log.warning(log_tag .. "DIAG_ALARM_SET is blocked without pocket test commands enabled") end if not valid then _send_mgmt(MGMT_TYPE.DIAG_ALARM_SET, { false }) end elseif pkt.type == MGMT_TYPE.INFO_LIST_CMP then diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 73d5c01..402ea58 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -623,7 +623,7 @@ function svsessions.establish_pdg_session(nic, source_addr, i_seq_num, version) local id = self.next_ids.pdg - pdg_s.instance = pocket.new_session(id, source_addr, i_seq_num, pdg_s.in_queue, pdg_s.out_queue, self.config.PKT_Timeout, self.sessions, self.facility, self.fp_ok) + pdg_s.instance = pocket.new_session(id, source_addr, i_seq_num, pdg_s.in_queue, pdg_s.out_queue, self.config.PKT_Timeout, self.sessions, self.facility, self.fp_ok, self.config.PocketTest) table.insert(self.sessions.pdg, pdg_s) local mt = { From 645c2bacbd318ec026be0edacd87fcb56aa03b91 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 2 Nov 2025 16:23:07 -0500 Subject: [PATCH 49/90] removed old "new!" labels --- coordinator/config/hmi.lua | 1 - reactor-plc/config/system.lua | 1 - 2 files changed, 2 deletions(-) diff --git a/coordinator/config/hmi.lua b/coordinator/config/hmi.lua index 06c86c6..102fdf1 100644 --- a/coordinator/config/hmi.lua +++ b/coordinator/config/hmi.lua @@ -240,7 +240,6 @@ function hmi.create(tool_ctl, main_pane, cfg_sys, divs, style) tool_ctl.clock_fmt = RadioButton{parent=crd_c_1,x=1,y=5,default=util.trinary(ini_cfg.Time24Hour,1,2),options={"24-Hour","12-Hour"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} TextBox{parent=crd_c_1,x=20,y=4,text="Po/Pu Pellet Color"} - TextBox{parent=crd_c_1,x=39,y=4,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision tool_ctl.pellet_color = RadioButton{parent=crd_c_1,x=20,y=5,default=util.trinary(ini_cfg.GreenPuPellet,1,2),options={"Green Pu/Cyan Po","Cyan Pu/Green Po (Mek 10.4+)"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} TextBox{parent=crd_c_1,x=1,y=8,text="Temperature Scale"} diff --git a/reactor-plc/config/system.lua b/reactor-plc/config/system.lua index d1bd5ba..28959c7 100644 --- a/reactor-plc/config/system.lua +++ b/reactor-plc/config/system.lua @@ -160,7 +160,6 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) TextBox{parent=plc_c_5,x=1,y=1,height=5,text="Advanced Options"} local invert = Checkbox{parent=plc_c_5,x=1,y=3,label="Invert",default=ini_cfg.EmerCoolInvert,box_fg_bg=cpair(colors.orange,colors.black)} - TextBox{parent=plc_c_5,x=10,y=3,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision TextBox{parent=plc_c_5,x=3,y=4,height=4,text="Digital I/O is already inverted (or not) based on intended use. If you have a non-standard setup, you can use this option to avoid needing a redstone inverter.",fg_bg=cpair(colors.gray,colors.lightGray)} PushButton{parent=plc_c_5,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} From 6bfa26407a4a999ed8a64360e00760bdec8a87e2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 2 Nov 2025 16:36:20 -0500 Subject: [PATCH 50/90] #580 coordinator wired networking configurator changes --- coordinator/config/facility.lua | 33 +++- coordinator/config/system.lua | 257 ++++++++++++++++++++++++++------ coordinator/configure.lua | 18 ++- coordinator/startup.lua | 2 +- 4 files changed, 255 insertions(+), 55 deletions(-) diff --git a/coordinator/config/facility.lua b/coordinator/config/facility.lua index 423eb19..0e30e1a 100644 --- a/coordinator/config/facility.lua +++ b/coordinator/config/facility.lua @@ -149,18 +149,39 @@ local function handle_timeout() end -- attempt a connection to the supervisor to get cooling info -local function sv_connect() +---@param cfg crd_config current configuration for modem settings +local function sv_connect(cfg) self.sv_conn_button.disable() self.sv_conn_detail.set_value("") - local modem = ppm.get_wireless_modem() + local modem = nil + + if cfg.WirelessModem then + modem = ppm.get_wireless_modem() + + if cfg.WiredModem then + local wd_modem = ppm.get_modem(cfg.WiredModem) + + if cfg.PreferWireless then + if not modem then + modem = wd_modem + end + else + if wd_modem then + modem = wd_modem + end + end + end + elseif cfg.WiredModem then + modem = ppm.get_modem(cfg.WiredModem) + end + if modem == nil then - self.sv_conn_status.set_value("Please connect an ender/wireless modem.") + self.sv_conn_status.set_value("Could not find configured modem(s).") else self.sv_conn_status.set_value("Modem found, connecting...") - if self.nic == nil then self.nic = network.nic(modem) end - self.nic.closeAll() + self.nic = network.nic(modem) self.nic.open(self.tmp_cfg.CRD_Channel) self.sv_addr = comms.BROADCAST @@ -209,7 +230,7 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style) self.sv_conn_status = TextBox{parent=fac_c_1,x=11,y=9,text=""} self.sv_conn_detail = TextBox{parent=fac_c_1,x=1,y=11,height=2,text=""} - self.sv_conn_button = PushButton{parent=fac_c_1,x=1,y=9,text="Connect",min_width=9,callback=function()sv_connect()end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} + self.sv_conn_button = PushButton{parent=fac_c_1,x=1,y=9,text="Connect",min_width=9,callback=function()sv_connect(tmp_cfg)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} local function sv_skip() tcd.abort(handle_timeout) diff --git a/coordinator/config/system.lua b/coordinator/config/system.lua index 0f24b62..7d3111d 100644 --- a/coordinator/config/system.lua +++ b/coordinator/config/system.lua @@ -2,6 +2,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local network = require("scada-common.network") +local ppm = require("scada-common.ppm") local types = require("scada-common.types") local util = require("scada-common.util") @@ -31,6 +32,9 @@ local RIGHT = core.ALIGN.RIGHT local self = { importing_legacy = false, + api_en = nil, ---@type Checkbox + pkt_chan = nil, ---@type NumberField + api_timeout = nil, ---@type NumberField show_auth_key = nil, ---@type function show_key_btn = nil, ---@type PushButton auth_key_textbox = nil, ---@type TextBox @@ -63,100 +67,202 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=49} local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=49} local net_c_4 = Div{parent=net_cfg,x=2,y=4,width=49} + local net_c_5 = Div{parent=net_cfg,x=2,y=4,width=49} + local net_c_6 = Div{parent=net_cfg,x=2,y=4,width=49} - local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4}} + local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4,net_c_5,net_c_6}} TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)} - TextBox{parent=net_c_1,x=1,y=1,text="Please set the network channels below."} - TextBox{parent=net_c_1,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 3 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_1,x=1,y=1,text="Please select the network interface(s)."} + TextBox{parent=net_c_1,x=41,y=1,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision - TextBox{parent=net_c_1,x=1,y=8,width=18,text="Supervisor Channel"} - local svr_chan = NumberField{parent=net_c_1,x=21,y=8,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg} - TextBox{parent=net_c_1,x=29,y=8,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg} + local function on_wired_change(_) tool_ctl.gen_modem_list() end - TextBox{parent=net_c_1,x=1,y=10,width=19,text="Coordinator Channel"} - local crd_chan = NumberField{parent=net_c_1,x=21,y=10,width=7,default=ini_cfg.CRD_Channel,min=1,max=65535,fg_bg=bw_fg_bg} - TextBox{parent=net_c_1,x=29,y=10,height=4,text="[CRD_CHANNEL]",fg_bg=g_lg_fg_bg} + local wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black)} + TextBox{parent=net_c_1,x=24,y=3,text="(required for Pocket)",fg_bg=g_lg_fg_bg} + local wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change} + TextBox{parent=net_c_1,x=3,y=6,text="MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)} + TextBox{parent=net_c_1,x=3,y=7,text="connecting to peripherals will cause problems",fg_bg=g_lg_fg_bg} + local modem_list = ListBox{parent=net_c_1,x=1,y=8,height=5,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} - TextBox{parent=net_c_1,x=1,y=12,width=14,text="Pocket Channel"} - local pkt_chan = NumberField{parent=net_c_1,x=21,y=12,width=7,default=ini_cfg.PKT_Channel,min=1,max=65535,fg_bg=bw_fg_bg} - TextBox{parent=net_c_1,x=29,y=12,height=4,text="[PKT_CHANNEL]",fg_bg=g_lg_fg_bg} + local modem_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} - local chan_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="Please set all channels.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local function submit_interfaces() + tmp_cfg.WirelessModem = wireless.get_value() + + if not wired.get_value() then + tmp_cfg.WiredModem = false + tool_ctl.gen_modem_list() + end + + if not (wired.get_value() or wireless.get_value()) then + modem_err.set_value("Please select a modem type.") + modem_err.show() + elseif wired.get_value() and type(tmp_cfg.WiredModem) ~= "string" then + modem_err.set_value("Please select a wired modem.") + modem_err.show() + else + if tmp_cfg.WirelessModem and tmp_cfg.WiredModem then + self.wl_pref.enable() + else + self.wl_pref.set_value(tmp_cfg.WirelessModem) + self.wl_pref.disable() + end + + if not tmp_cfg.WirelessModem then + self.api_en.set_value(false) + self.api_en.disable() + else + self.api_en.enable() + end + + net_pane.set_value(2) + modem_err.hide(true) + end + end + + PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_interfaces,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + TextBox{parent=net_c_2,text="If you selected multiple interfaces, please specify if this device should prefer wireless or otherwise wired. The preferred interface is switched too when reconnected even if failover has succeeded onto the fallback interface."} + self.wl_pref = Checkbox{parent=net_c_2,y=7,label="Prefer Wireless",default=ini_cfg.PreferWireless,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} + + TextBox{parent=net_c_2,y=9,text="With a wireless modem, configure Pocket access."} + self.api_en = Checkbox{parent=net_c_2,y=11,label="Enable Pocket Access",default=ini_cfg.API_Enabled,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} + + local function submit_net_cfg_opts() + if tmp_cfg.WirelessModem and tmp_cfg.WiredModem then + tmp_cfg.PreferWireless = self.wl_pref.get_value() + else + tmp_cfg.PreferWireless = tmp_cfg.WirelessModem + end + + tmp_cfg.API_Enabled = tri(tmp_cfg.WirelessModem, self.api_en.get_value(), false) + + if tmp_cfg.API_Enabled then + self.pkt_chan.enable() + self.api_timeout.enable() + else + self.pkt_chan.disable() + self.api_timeout.disable() + end + + net_pane.set_value(3) + end + + PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_net_cfg_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + TextBox{parent=net_c_3,x=1,y=1,text="Please set the network channels below."} + TextBox{parent=net_c_3,x=1,y=3,height=4,text="Each of the 5 uniquely named channels, including the 3 below, must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg} + + TextBox{parent=net_c_3,x=1,y=8,width=18,text="Supervisor Channel"} + local svr_chan = NumberField{parent=net_c_3,x=21,y=8,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_3,x=29,y=8,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg} + + TextBox{parent=net_c_3,x=1,y=10,width=19,text="Coordinator Channel"} + local crd_chan = NumberField{parent=net_c_3,x=21,y=10,width=7,default=ini_cfg.CRD_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_3,x=29,y=10,height=4,text="[CRD_CHANNEL]",fg_bg=g_lg_fg_bg} + + TextBox{parent=net_c_3,x=1,y=12,width=14,text="Pocket Channel"} + self.pkt_chan = NumberField{parent=net_c_3,x=21,y=12,width=7,default=ini_cfg.PKT_Channel,min=1,max=65535,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} + TextBox{parent=net_c_3,x=29,y=12,height=4,text="[PKT_CHANNEL]",fg_bg=g_lg_fg_bg} + + local chan_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Please set all channels.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_channels() - local svr_c, crd_c, pkt_c = tonumber(svr_chan.get_value()), tonumber(crd_chan.get_value()), tonumber(pkt_chan.get_value()) + local svr_c, crd_c, pkt_c = tonumber(svr_chan.get_value()), tonumber(crd_chan.get_value()), tonumber(self.pkt_chan.get_value()) + + if not tmp_cfg.API_Enabled then pkt_c = tmp_cfg.PKT_Channel or 16244 end + if svr_c ~= nil and crd_c ~= nil and pkt_c ~= nil then tmp_cfg.SVR_Channel, tmp_cfg.CRD_Channel, tmp_cfg.PKT_Channel = svr_c, crd_c, pkt_c - net_pane.set_value(2) + net_pane.set_value(4) chan_err.hide(true) else chan_err.show() end end - PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=net_c_2,x=1,y=1,text="Please set the connection timeouts below."} - TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally should not need to modify these. On slow servers, you can try to increase this to make the system wait longer before assuming a disconnection. The default for all is 5 seconds.",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_4,x=1,y=1,text="Please set the connection timeouts below."} + TextBox{parent=net_c_4,x=1,y=3,height=4,text="You generally should not need to modify these. On slow servers, you can try to increase this to make the system wait longer before assuming a disconnection. The default for all is 5 seconds.",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_2,x=1,y=8,width=19,text="Supervisor Timeout"} - local svr_timeout = NumberField{parent=net_c_2,x=20,y=8,width=7,default=ini_cfg.SVR_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} + TextBox{parent=net_c_4,x=1,y=8,width=19,text="Supervisor Timeout"} + local svr_timeout = NumberField{parent=net_c_4,x=20,y=8,width=7,default=ini_cfg.SVR_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} - TextBox{parent=net_c_2,x=1,y=10,width=14,text="Pocket Timeout"} - local api_timeout = NumberField{parent=net_c_2,x=20,y=10,width=7,default=ini_cfg.API_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} + TextBox{parent=net_c_4,x=1,y=10,width=14,text="Pocket Timeout"} + self.api_timeout = NumberField{parent=net_c_4,x=20,y=10,width=7,default=ini_cfg.API_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} - TextBox{parent=net_c_2,x=28,y=8,height=4,width=7,text="seconds\n\nseconds",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_4,x=28,y=8,height=4,width=7,text="seconds\n\nseconds",fg_bg=g_lg_fg_bg} - local ct_err = TextBox{parent=net_c_2,x=8,y=14,width=35,text="Please set all connection timeouts.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local ct_err = TextBox{parent=net_c_4,x=8,y=14,width=35,text="Please set all connection timeouts.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_timeouts() - local svr_cto, api_cto = tonumber(svr_timeout.get_value()), tonumber(api_timeout.get_value()) + local svr_cto, api_cto = tonumber(svr_timeout.get_value()), tonumber(self.api_timeout.get_value()) + + if not tmp_cfg.API_Enabled then api_cto = tmp_cfg.API_Timeout or 5 end + if svr_cto ~= nil and api_cto ~= nil then tmp_cfg.SVR_Timeout, tmp_cfg.API_Timeout = svr_cto, api_cto - net_pane.set_value(3) + + if tmp_cfg.WirelessModem then + net_pane.set_value(5) + else + tmp_cfg.TrustedRange = 0 + tmp_cfg.AuthKey = "" + + network.deinit_mac() + + -- prep supervisor connection screen + tool_ctl.init_sv_connect_ui() + + main_pane.set_value(3) + end + ct_err.hide(true) else ct_err.show() end end - PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_4,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_4,x=44,y=14,text="Next \x1a",callback=submit_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=net_c_3,x=1,y=1,text="Please set the trusted range below."} - TextBox{parent=net_c_3,x=1,y=3,height=3,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_3,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_5,x=1,y=1,text="Please set the wireless trusted range below."} + TextBox{parent=net_c_5,x=1,y=3,height=3,text="Setting this to a value larger than 0 prevents wireless connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_5,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg} - local range = NumberField{parent=net_c_3,x=1,y=10,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg} + local range = NumberField{parent=net_c_5,x=1,y=10,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg} - local tr_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Please set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local tr_err = TextBox{parent=net_c_5,x=8,y=14,width=35,text="Please set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_tr() local range_val = tonumber(range.get_value()) if range_val ~= nil then tmp_cfg.TrustedRange = range_val comms.set_trusted_range(range_val) - net_pane.set_value(4) + net_pane.set_value(6) tr_err.hide(true) else tr_err.show() end end - PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_5,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_5,x=44,y=14,text="Next \x1a",callback=submit_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=net_c_4,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."} - TextBox{parent=net_c_4,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_6,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."} + TextBox{parent=net_c_6,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for wireless security on multiplayer servers. All devices on the same wireless network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_4,x=1,y=11,text="Facility Auth Key"} - local key, _ = TextField{parent=net_c_4,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg} + TextBox{parent=net_c_6,x=1,y=11,text="Auth Key (Wireless Only, Not Used for Wired)"} + local key, _ = TextField{parent=net_c_6,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg} local function censor_key(enable) key.censor(tri(enable, "*", nil)) end - local hide_key = Checkbox{parent=net_c_4,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key} + local hide_key = Checkbox{parent=net_c_6,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key} hide_key.set_value(true) censor_key(true) - local key_err = TextBox{parent=net_c_4,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local key_err = TextBox{parent=net_c_6,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_auth() local v = key.get_value() @@ -174,8 +280,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) else key_err.show() end end - PushButton{parent=net_c_4,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_4,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_6,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_6,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} --#endregion @@ -370,11 +476,15 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) load_settings(settings_cfg, true) load_settings(ini_cfg) + try_set(wireless, ini_cfg.WirelessModem) + try_set(wired, ini_cfg.WiredModem ~= false) + try_set(self.wl_pref, ini_cfg.PreferWireless) + try_set(self.api_en, ini_cfg.API_Enabled) try_set(svr_chan, ini_cfg.SVR_Channel) try_set(crd_chan, ini_cfg.CRD_Channel) - try_set(pkt_chan, ini_cfg.PKT_Channel) + try_set(self.pkt_chan, ini_cfg.PKT_Channel) try_set(svr_timeout, ini_cfg.SVR_Timeout) - try_set(api_timeout, ini_cfg.API_Timeout) + try_set(self.api_timeout, ini_cfg.API_Timeout) try_set(range, ini_cfg.TrustedRange) try_set(key, ini_cfg.AuthKey) try_set(tool_ctl.num_units, ini_cfg.UnitCount) @@ -574,6 +684,59 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) end end + -- generate the list of available/assigned wired modems + function tool_ctl.gen_modem_list() + modem_list.remove_all() + + local enable = wired.get_value() + + local function select(iface) + tmp_cfg.WiredModem = iface + tool_ctl.gen_modem_list() + end + + local modems = ppm.get_wired_modem_list() + local missing = { tmp = true, ini = true } + + for iface, _ in pairs(modems) do + if ini_cfg.WiredModem == iface then missing.ini = false end + if tmp_cfg.WiredModem == iface then missing.tmp = false end + end + + if missing.tmp and tmp_cfg.WiredModem then + local line = Div{parent=modem_list,x=1,y=1,height=1} + + TextBox{parent=line,x=1,y=1,width=4,text="Used",fg_bg=cpair(tri(enable,colors.blue,colors.gray),colors.white)} + PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg}.disable() + TextBox{parent=line,x=15,y=1,text="[missing]",fg_bg=cpair(colors.red,colors.white)} + TextBox{parent=line,x=25,y=1,text=tmp_cfg.WiredModem} + end + + if missing.ini and ini_cfg.WiredModem and (tmp_cfg.WiredModem ~= ini_cfg.WiredModem) then + local line = Div{parent=modem_list,x=1,y=1,height=1} + local used = tmp_cfg.WiredModem == ini_cfg.WiredModem + + TextBox{parent=line,x=1,y=1,width=4,text=tri(used,"Used","----"),fg_bg=cpair(tri(used and enable,colors.blue,colors.gray),colors.white)} + local select_btn = PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()select(ini_cfg.WiredModem)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg} + TextBox{parent=line,x=15,y=1,text="[missing]",fg_bg=cpair(colors.red,colors.white)} + TextBox{parent=line,x=25,y=1,text=ini_cfg.WiredModem} + + if used or not enable then select_btn.disable() end + end + + -- list wired modems + for iface, _ in pairs(modems) do + local line = Div{parent=modem_list,x=1,y=1,height=1} + local used = tmp_cfg.WiredModem == iface + + TextBox{parent=line,x=1,y=1,width=4,text=tri(used,"Used","----"),fg_bg=cpair(tri(used and enable,colors.blue,colors.gray),colors.white)} + local select_btn = PushButton{parent=line,x=6,y=1,min_width=8,height=1,text="SELECT",callback=function()select(iface)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=g_lg_fg_bg} + TextBox{parent=line,x=15,y=1,text=iface} + + if used or not enable then select_btn.disable() end + end + end + --#endregion end diff --git a/coordinator/configure.lua b/coordinator/configure.lua index 474c727..d4c3bca 100644 --- a/coordinator/configure.lua +++ b/coordinator/configure.lua @@ -89,7 +89,9 @@ local tool_ctl = { is_int_min_max = nil, ---@type function update_mon_reqs = nil, ---@type function - gen_mon_list = function () end + + gen_mon_list = function () end, + gen_modem_list = function () end } ---@class crd_config @@ -104,6 +106,10 @@ local tmp_cfg = { MainDisplay = nil, ---@type string FlowDisplay = nil, ---@type string UnitDisplays = {}, ---@type string[] + WirelessModem = true, + WiredModem = false, ---@type string|false + PreferWireless = true, + API_Enabled = true, SVR_Channel = nil, ---@type integer CRD_Channel = nil, ---@type integer PKT_Channel = nil, ---@type integer @@ -136,6 +142,10 @@ local fields = { { "TempScale", "Temperature Scale", types.TEMP_SCALE.KELVIN }, { "EnergyScale", "Energy Scale", types.ENERGY_SCALE.FE }, { "DisableFlowView", "Disable Flow Monitor (legacy, discouraged)", false }, + { "WirelessModem", "Wireless/Ender Comms Modem", true }, + { "WiredModem", "Wired Comms Modem", false }, + { "PreferWireless", "Prefer Wireless Modem", true }, + { "API_Enabled", "Pocket API Connectivity", true }, { "SVR_Channel", "SVR Channel", 16240 }, { "CRD_Channel", "CRD Channel", 16243 }, { "PKT_Channel", "PKT Channel", 16244 }, @@ -327,6 +337,9 @@ function configurator.configure(start_code, message) -- copy in some important values to start with preset_monitor_fields() + -- this needs to be initialized as it is used before being set + tmp_cfg.WiredModem = ini_cfg.WiredModem + reset_term() ppm.mount_all() @@ -341,6 +354,7 @@ function configurator.configure(start_code, message) config_view(display) tool_ctl.gen_mon_list() + tool_ctl.gen_modem_list() while true do local event, param1, param2, param3, param4, param5 = util.pull_event() @@ -364,8 +378,10 @@ function configurator.configure(start_code, message) ---@diagnostic disable-next-line: discard-returns ppm.mount(param1) tool_ctl.gen_mon_list() + tool_ctl.gen_modem_list() elseif event == "monitor_resize" then tool_ctl.gen_mon_list() + tool_ctl.gen_modem_list() elseif event == "modem_message" then facility.receive_sv(param1, param2, param3, param4, param5) end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 1edf082..01b6d58 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") local threads = require("coordinator.threads") -local COORDINATOR_VERSION = "v1.6.16" +local COORDINATOR_VERSION = "v1.7.0" local CHUNK_LOAD_DELAY_S = 30.0 From 9a58bf1bb79d83e2ecb82edcef9378a9e0028bae Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 2 Nov 2025 17:44:24 -0500 Subject: [PATCH 51/90] #580 supervisor configurator fixes --- supervisor/config/system.lua | 9 ++++++++- supervisor/configure.lua | 6 +++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/supervisor/config/system.lua b/supervisor/config/system.lua index 281ffcf..608121c 100644 --- a/supervisor/config/system.lua +++ b/supervisor/config/system.lua @@ -203,6 +203,9 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit local function submit_channels() local svr_c, plc_c, rtu_c = tonumber(svr_chan.get_value()), tonumber(plc_chan.get_value()), tonumber(rtu_chan.get_value()) local crd_c, pkt_c = tonumber(crd_chan.get_value()), tonumber(self.pkt_chan.get_value()) + + if not tmp_cfg.PocketEnabled then pkt_c = tmp_cfg.PKT_Channel or 16244 end + if svr_c ~= nil and plc_c ~= nil and rtu_c ~= nil and crd_c ~= nil and pkt_c ~= nil then tmp_cfg.SVR_Channel, tmp_cfg.PLC_Channel, tmp_cfg.RTU_Channel = svr_c, plc_c, rtu_c tmp_cfg.CRD_Channel, tmp_cfg.PKT_Channel = crd_c, pkt_c @@ -235,17 +238,21 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit local function submit_timeouts() local plc_cto, rtu_cto, crd_cto, pkt_cto = tonumber(plc_timeout.get_value()), tonumber(rtu_timeout.get_value()), tonumber(crd_timeout.get_value()), tonumber(self.pkt_timeout.get_value()) + + if not tmp_cfg.PocketEnabled then pkt_cto = tmp_cfg.PKT_Timeout or 5 end + if plc_cto ~= nil and rtu_cto ~= nil and crd_cto ~= nil and pkt_cto ~= nil then tmp_cfg.PLC_Timeout, tmp_cfg.RTU_Timeout, tmp_cfg.CRD_Timeout, tmp_cfg.PKT_Timeout = plc_cto, rtu_cto, crd_cto, pkt_cto if tmp_cfg.WirelessModem then net_pane.set_value(5) - ct_err.hide(true) else tmp_cfg.TrustedRange = 0 tmp_cfg.AuthKey = "" main_pane.set_value(4) end + + ct_err.hide(true) else ct_err.show() end end diff --git a/supervisor/configure.lua b/supervisor/configure.lua index 43ef5da..83a9264 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -93,13 +93,13 @@ local tmp_cfg = { TankFluidTypes = {}, ---@type integer[] which type of fluid each tank in the tank list should be containing AuxiliaryCoolant = {}, ---@type boolean[] if a unit has auxiliary coolant ExtChargeIdling = false, - WirelessModem = true, ---@type boolean + WirelessModem = true, WiredModem = false, ---@type string|false PLC_Listen = 1, ---@type LISTEN_MODE RTU_Listen = 1, ---@type LISTEN_MODE CRD_Listen = 1, ---@type LISTEN_MODE - PocketEnabled = true, ---@type boolean - PocketTest = true, ---@type boolean + PocketEnabled = true, + PocketTest = true, SVR_Channel = nil, ---@type integer PLC_Channel = nil, ---@type integer RTU_Channel = nil, ---@type integer From 7745e94fbe447290309baf78b47db8fc8ef7e9f6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 2 Nov 2025 17:45:11 -0500 Subject: [PATCH 52/90] #580 rtu and plc configurator enhancements --- reactor-plc/config/system.lua | 45 ++++++++++++++++++++++------------- rtu/config/system.lua | 45 ++++++++++++++++++++++------------- 2 files changed, 58 insertions(+), 32 deletions(-) diff --git a/reactor-plc/config/system.lua b/reactor-plc/config/system.lua index 28959c7..d92df51 100644 --- a/reactor-plc/config/system.lua +++ b/reactor-plc/config/system.lua @@ -33,7 +33,9 @@ local self = { set_networked = nil, ---@type function bundled_emcool = nil, ---@type function + wireless = nil, ---@type Checkbox wl_pref = nil, ---@type Checkbox + wired = nil, ---@type Checkbox range = nil, ---@type NumberField show_auth_key = nil, ---@type function show_key_btn = nil, ---@type PushButton @@ -190,39 +192,50 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) TextBox{parent=net_c_1,x=1,y=1,text="Please select the network interface(s)."} TextBox{parent=net_c_1,x=41,y=1,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision - local function dis_pref(value) - if not value then - self.wl_pref.set_value(false) + local function en_dis_pref() + if self.wireless.get_value() and self.wired.get_value() then + self.wl_pref.enable() + else + self.wl_pref.set_value(self.wireless.get_value()) self.wl_pref.disable() - else self.wl_pref.enable() end + end end - local function on_wired_change(_) tool_ctl.gen_modem_list() end + local function on_wired_change(_) + en_dis_pref() + tool_ctl.gen_modem_list() + end - local wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=dis_pref} + self.wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=en_dis_pref} self.wl_pref = Checkbox{parent=net_c_1,x=30,y=3,label="Prefer Wireless",default=ini_cfg.PreferWireless,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} - local wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change} + self.wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change} TextBox{parent=net_c_1,x=3,y=6,text="MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)} TextBox{parent=net_c_1,x=3,y=7,text="connecting to peripherals will cause problems",fg_bg=g_lg_fg_bg} local modem_list = ListBox{parent=net_c_1,x=1,y=8,height=5,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} local modem_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} - dis_pref(ini_cfg.WirelessModem) + en_dis_pref() local function submit_interfaces() - tmp_cfg.WirelessModem = wireless.get_value() - tmp_cfg.PreferWireless = tmp_cfg.WirelessModem and self.wl_pref.get_value() + tmp_cfg.WirelessModem = self.wireless.get_value() - if not wired.get_value() then + if tmp_cfg.WirelessModem and tmp_cfg.WiredModem then + tmp_cfg.PreferWireless = self.wl_pref.get_value() + else + tmp_cfg.PreferWireless = tmp_cfg.WirelessModem + self.wl_pref.set_value(tmp_cfg.PreferWireless) + end + + if not self.wired.get_value() then tmp_cfg.WiredModem = false tool_ctl.gen_modem_list() end - if not (wired.get_value() or wireless.get_value()) then + if not (self.wired.get_value() or self.wireless.get_value()) then modem_err.set_value("Please select a modem type.") modem_err.show() - elseif wired.get_value() and type(tmp_cfg.WiredModem) ~= "string" then + elseif self.wired.get_value() and type(tmp_cfg.WiredModem) ~= "string" then modem_err.set_value("Please select a wired modem.") modem_err.show() else @@ -538,8 +551,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) try_set(bundled, ini_cfg.EmerCoolColor ~= nil) if ini_cfg.EmerCoolColor ~= nil then try_set(color, color_to_idx(ini_cfg.EmerCoolColor)) end try_set(invert, ini_cfg.EmerCoolInvert) - try_set(wireless, ini_cfg.WirelessModem) - try_set(wired, ini_cfg.WiredModem ~= false) + try_set(self.wireless, ini_cfg.WirelessModem) + try_set(self.wired, ini_cfg.WiredModem ~= false) try_set(self.wl_pref, ini_cfg.PreferWireless) try_set(svr_chan, ini_cfg.SVR_Channel) try_set(plc_chan, ini_cfg.PLC_Channel) @@ -697,7 +710,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) function tool_ctl.gen_modem_list() modem_list.remove_all() - local enable = wired.get_value() + local enable = self.wired.get_value() local function select(iface) tmp_cfg.WiredModem = iface diff --git a/rtu/config/system.lua b/rtu/config/system.lua index 24ad1fb..1e47791 100644 --- a/rtu/config/system.lua +++ b/rtu/config/system.lua @@ -30,7 +30,9 @@ local self = { importing_legacy = false, importing_any_dc = false, + wireless = nil, ---@type Checkbox wl_pref = nil, ---@type Checkbox + wired = nil, ---@type Checkbox range = nil, ---@type NumberField show_auth_key = nil, ---@type function show_key_btn = nil, ---@type PushButton @@ -101,39 +103,50 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) TextBox{parent=net_c_1,x=1,y=1,text="Please select the network interface(s)."} TextBox{parent=net_c_1,x=41,y=1,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision - local function dis_pref(value) - if not value then - self.wl_pref.set_value(false) + local function en_dis_pref() + if self.wireless.get_value() and self.wired.get_value() then + self.wl_pref.enable() + else + self.wl_pref.set_value(self.wireless.get_value()) self.wl_pref.disable() - else self.wl_pref.enable() end + end end - local function on_wired_change(_) tool_ctl.gen_modem_list() end + local function on_wired_change(_) + en_dis_pref() + tool_ctl.gen_modem_list() + end - local wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=dis_pref} + self.wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=en_dis_pref} self.wl_pref = Checkbox{parent=net_c_1,x=30,y=3,label="Prefer Wireless",default=ini_cfg.PreferWireless,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} - local wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change} + self.wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change} TextBox{parent=net_c_1,x=3,y=6,text="MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)} TextBox{parent=net_c_1,x=3,y=7,text="connecting to peripherals will cause problems",fg_bg=g_lg_fg_bg} local modem_list = ListBox{parent=net_c_1,x=1,y=8,height=5,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} local modem_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} - dis_pref(ini_cfg.WirelessModem) + en_dis_pref() local function submit_interfaces() - tmp_cfg.WirelessModem = wireless.get_value() - tmp_cfg.PreferWireless = tmp_cfg.WirelessModem and self.wl_pref.get_value() + tmp_cfg.WirelessModem = self.wireless.get_value() - if not wired.get_value() then + if tmp_cfg.WirelessModem and tmp_cfg.WiredModem then + tmp_cfg.PreferWireless = self.wl_pref.get_value() + else + tmp_cfg.PreferWireless = tmp_cfg.WirelessModem + self.wl_pref.set_value(tmp_cfg.PreferWireless) + end + + if not self.wired.get_value() then tmp_cfg.WiredModem = false tool_ctl.gen_modem_list() end - if not (wired.get_value() or wireless.get_value()) then + if not (self.wired.get_value() or self.wireless.get_value()) then modem_err.set_value("Please select a modem type.") modem_err.show() - elseif wired.get_value() and type(tmp_cfg.WiredModem) ~= "string" then + elseif self.wired.get_value() and type(tmp_cfg.WiredModem) ~= "string" then modem_err.set_value("Please select a wired modem.") modem_err.show() else @@ -447,8 +460,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) load_settings(ini_cfg) try_set(s_vol, ini_cfg.SpeakerVolume) - try_set(wireless, ini_cfg.WirelessModem) - try_set(wired, ini_cfg.WiredModem ~= false) + try_set(self.wireless, ini_cfg.WirelessModem) + try_set(self.wired, ini_cfg.WiredModem ~= false) try_set(self.wl_pref, ini_cfg.PreferWireless) try_set(svr_chan, ini_cfg.SVR_Channel) try_set(rtu_chan, ini_cfg.RTU_Channel) @@ -737,7 +750,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) function tool_ctl.gen_modem_list() modem_list.remove_all() - local enable = wired.get_value() + local enable = self.wired.get_value() local function select(iface) tmp_cfg.WiredModem = iface From 299c6bcf7ad9a39baade6699545ee8b7b8823186 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 2 Nov 2025 19:02:49 -0500 Subject: [PATCH 53/90] #580 coordinator config loading and checking --- coordinator/coordinator.lua | 11 +++++++++ reactor-plc/plc.lua | 14 +++++------ rtu/rtu.lua | 14 +++++------ supervisor/supervisor.lua | 48 ++++++++++++++++++------------------- 4 files changed, 49 insertions(+), 38 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index d5f930d..7999679 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -47,6 +47,10 @@ function coordinator.load_config() config.FlowDisplay = settings.get("FlowDisplay") config.UnitDisplays = settings.get("UnitDisplays") + config.WirelessModem = settings.get("WirelessModem") + config.WiredModem = settings.get("WiredModem") + config.PreferWireless = settings.get("PreferWireless") + config.API_Enabled = settings.get("API_Enabled") config.SVR_Channel = settings.get("SVR_Channel") config.CRD_Channel = settings.get("CRD_Channel") config.PKT_Channel = settings.get("PKT_Channel") @@ -80,6 +84,13 @@ function coordinator.load_config() cfv.assert_type_num(config.SpeakerVolume) cfv.assert_range(config.SpeakerVolume, 0, 3) + cfv.assert_type_bool(config.WirelessModem) + cfv.assert((config.WiredModem == false) or (type(config.WiredModem) == "string")) + cfv.assert(config.WirelessModem or (type(config.WiredModem) == "string")) + cfv.assert_type_bool(config.PreferWireless) + + cfv.assert_type_bool(config.API_Enabled) + cfv.assert_channel(config.SVR_Channel) cfv.assert_channel(config.CRD_Channel) cfv.assert_channel(config.PKT_Channel) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 3aa5b01..e388575 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -45,12 +45,12 @@ function plc.load_config() config.EmerCoolColor = settings.get("EmerCoolColor") config.EmerCoolInvert = settings.get("EmerCoolInvert") - 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.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") @@ -74,14 +74,14 @@ function plc.validate_config(cfg) cfv.assert_type_bool(cfg.EmerCoolEnable) 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_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_num(cfg.TrustedRange) cfv.assert_min(cfg.TrustedRange, 0) cfv.assert_type_str(cfg.AuthKey) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index a4c9657..3efbcf7 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -33,12 +33,12 @@ function rtu.load_config() config.SpeakerVolume = settings.get("SpeakerVolume") - config.SVR_Channel = settings.get("SVR_Channel") - config.RTU_Channel = settings.get("RTU_Channel") - config.ConnTimeout = settings.get("ConnTimeout") config.WirelessModem = settings.get("WirelessModem") config.WiredModem = settings.get("WiredModem") config.PreferWireless = settings.get("PreferWireless") + config.SVR_Channel = settings.get("SVR_Channel") + config.RTU_Channel = settings.get("RTU_Channel") + config.ConnTimeout = settings.get("ConnTimeout") config.TrustedRange = settings.get("TrustedRange") config.AuthKey = settings.get("AuthKey") @@ -60,14 +60,14 @@ function rtu.validate_config(cfg) cfv.assert_type_num(cfg.SpeakerVolume) cfv.assert_range(cfg.SpeakerVolume, 0, 3) - cfv.assert_channel(cfg.SVR_Channel) - cfv.assert_channel(cfg.RTU_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_channel(cfg.SVR_Channel) + cfv.assert_channel(cfg.RTU_Channel) + cfv.assert_type_num(cfg.ConnTimeout) + cfv.assert_min(cfg.ConnTimeout, 2) cfv.assert_type_num(cfg.TrustedRange) cfv.assert_min(cfg.TrustedRange, 0) cfv.assert_type_str(cfg.AuthKey) diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 3778f3a..88d7d25 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -53,6 +53,16 @@ function supervisor.load_config() config.AuxiliaryCoolant = settings.get("AuxiliaryCoolant") config.ExtChargeIdling = settings.get("ExtChargeIdling") + config.WirelessModem = settings.get("WirelessModem") + config.WiredModem = settings.get("WiredModem") + + config.PLC_Listen = settings.get("PLC_Listen") + config.RTU_Listen = settings.get("RTU_Listen") + config.CRD_Listen = settings.get("CRD_Listen") + + config.PocketEnabled = settings.get("PocketEnabled") + config.PocketTest = settings.get("PocketTest") + config.SVR_Channel = settings.get("SVR_Channel") config.PLC_Channel = settings.get("PLC_Channel") config.RTU_Channel = settings.get("RTU_Channel") @@ -64,16 +74,6 @@ function supervisor.load_config() config.CRD_Timeout = settings.get("CRD_Timeout") config.PKT_Timeout = settings.get("PKT_Timeout") - config.WirelessModem = settings.get("WirelessModem") - config.WiredModem = settings.get("WiredModem") - - config.PLC_Listen = settings.get("PLC_Listen") - config.RTU_Listen = settings.get("RTU_Listen") - config.CRD_Listen = settings.get("CRD_Listen") - - config.PocketEnabled = settings.get("PocketEnabled") - config.PocketTest = settings.get("PocketTest") - config.TrustedRange = settings.get("TrustedRange") config.AuthKey = settings.get("AuthKey") @@ -100,6 +100,20 @@ function supervisor.load_config() cfv.assert_type_bool(config.ExtChargeIdling) + cfv.assert_type_bool(config.WirelessModem) + cfv.assert((config.WiredModem == false) or (type(config.WiredModem) == "string")) + cfv.assert((config.WirelessModem == true) or (type(config.WiredModem) == "string")) + + cfv.assert_type_num(config.PLC_Listen) + cfv.assert_range(config.PLC_Listen, 0, 2) + cfv.assert_type_num(config.RTU_Listen) + cfv.assert_range(config.RTU_Listen, 0, 2) + cfv.assert_type_num(config.CRD_Listen) + cfv.assert_range(config.CRD_Listen, 0, 2) + + cfv.assert_type_bool(config.PocketEnabled) + cfv.assert_type_bool(config.PocketTest) + cfv.assert_channel(config.SVR_Channel) cfv.assert_channel(config.PLC_Channel) cfv.assert_channel(config.RTU_Channel) @@ -115,20 +129,6 @@ function supervisor.load_config() cfv.assert_type_num(config.PKT_Timeout) cfv.assert_min(config.PKT_Timeout, 2) - cfv.assert_type_bool(config.WirelessModem) - cfv.assert((config.WiredModem == false) or (type(config.WiredModem) == "string")) - cfv.assert((config.WirelessModem == true) or (type(config.WiredModem) == "string")) - - cfv.assert_type_num(config.PLC_Listen) - cfv.assert_range(config.PLC_Listen, 0, 2) - cfv.assert_type_num(config.RTU_Listen) - cfv.assert_range(config.RTU_Listen, 0, 2) - cfv.assert_type_num(config.CRD_Listen) - cfv.assert_range(config.CRD_Listen, 0, 2) - - cfv.assert_type_bool(config.PocketEnabled) - cfv.assert_type_bool(config.PocketTest) - cfv.assert_type_num(config.TrustedRange) cfv.assert_min(config.TrustedRange, 0) From 5abe687f69e95cd43b0dc361034b1d7193ab88f6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 3 Nov 2025 18:47:36 -0500 Subject: [PATCH 54/90] #634 coordinator backplane interface --- coordinator/backplane.lua | 228 ++++++++++++++++++++++++++++++++++++ coordinator/coordinator.lua | 86 +------------- coordinator/iocontrol.lua | 6 +- coordinator/renderer.lua | 22 ++-- coordinator/startup.lua | 189 ++++++++++++++---------------- 5 files changed, 333 insertions(+), 198 deletions(-) create mode 100644 coordinator/backplane.lua diff --git a/coordinator/backplane.lua b/coordinator/backplane.lua new file mode 100644 index 0000000..0647dd4 --- /dev/null +++ b/coordinator/backplane.lua @@ -0,0 +1,228 @@ +-- +-- Coordinator 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 coordinator = require("coordinator.coordinator") +local iocontrol = require("coordinator.iocontrol") +local sounder = require("coordinator.sounder") + +local println = util.println + +local log_sys = coordinator.log_sys +local log_boot = coordinator.log_boot +local log_comms = coordinator.log_comms + +---@class crd_backplane +local backplane = {} + +local _bp = { + smem = nil, ---@type crd_shared_memory + + wlan_pref = true, + lan_iface = "", + + act_nic = nil, ---@type nic + wd_nic = nil, ---@type nic|nil + wl_nic = nil, ---@type nic|nil + + speaker = nil, ---@type Speaker|nil + + ---@class crd_displays + displays = { + main = nil, ---@type Monitor|nil + main_iface = "", + flow = nil, ---@type Monitor|nil + flow_iface = "", + unit_displays = {}, ---@type Monitor[] + unit_ifaces = {} ---@type string[] + } +} + +-- initialize the display peripheral backplane +---@param config crd_config +---@return boolean success, string error_msg +function backplane.init_displays(config) + local displays = _bp.displays + + local w, h, _ + + log.info("BKPLN: DISPLAY INIT") + + -- monitor configuration verification + + local mon_cfv = util.new_validator() + + mon_cfv.assert_type_str(config.MainDisplay) + if not config.DisableFlowView then mon_cfv.assert_type_str(config.FlowDisplay) end + + mon_cfv.assert_eq(#config.UnitDisplays, config.UnitCount) + for i = 1, #config.UnitDisplays do + mon_cfv.assert_type_str(config.UnitDisplays[i]) + end + + if not mon_cfv.valid() then + return false, "Monitor configuration invalid." + end + + -- setup and check display peripherals + + -- main display + + local disp, iface = ppm.get_periph(config.MainDisplay), config.MainDisplay + + displays.main = disp + displays.main_iface = iface + + log.info("BKPLN: DISPLAY LINK_" .. util.trinary(disp, "UP", "DOWN") .. " MAIN/" .. iface) + + if not disp then + return false, "Main monitor is not connected." + end + + disp.setTextScale(0.5) + w, _ = ppm.monitor_block_size(disp.getSize()) + if w ~= 8 then + log.info("BKPLN: DISPLAY MAIN/" .. iface .. " BAD RESOLUTION") + return false, util.c("Main monitor width is incorrect (was ", w, ", must be 8).") + end + + -- flow display + + if not config.DisableFlowView then + disp, iface = ppm.get_periph(config.FlowDisplay), config.FlowDisplay + + displays.flow = disp + displays.flow_iface = iface + + log.info("BKPLN: DISPLAY LINK_" .. util.trinary(disp, "UP", "DOWN") .. " FLOW/" .. iface) + + if not disp then + return false, "Flow monitor is not connected." + end + + disp.setTextScale(0.5) + w, _ = ppm.monitor_block_size(disp.getSize()) + if w ~= 8 then + log.info("BKPLN: DISPLAY FLOW/" .. iface .. " BAD RESOLUTION") + return false, util.c("Flow monitor width is incorrect (was ", w, ", must be 8).") + end + end + + -- unit display(s) + + for i = 1, config.UnitCount do + disp, iface = ppm.get_periph(config.UnitDisplays[i]), config.UnitDisplays[i] + + displays.unit_displays[i] = disp + displays.unit_ifaces[i] = iface + + log.info("BKPLN: DISPLAY LINK_" .. util.trinary(disp, "UP", "DOWN") .. " UNIT_" .. i .. "/" .. iface) + + if not disp then + return false, "Unit " .. i .. " monitor is not connected." + end + + disp.setTextScale(0.5) + w, h = ppm.monitor_block_size(disp.getSize()) + if w ~= 4 or h ~= 4 then + log.info("BKPLN: DISPLAY UNIT_" .. i .. "/" .. iface .. " BAD RESOLUTION") + return false, util.c("Unit ", i, " monitor size is incorrect (was ", w, " by ", h,", must be 4 by 4).") + end + end + + log.info("BKPLN: DISPLAY INIT OK") + + return true, "" +end + +-- initialize the system peripheral backplane +---@param config crd_config +---@param __shared_memory crd_shared_memory +---@return boolean success +function backplane.init(config, __shared_memory) + _bp.smem = __shared_memory + _bp.wlan_pref = config.PreferWireless + _bp.lan_iface = config.WiredModem + + -- Modem Init + + -- init wired NIC + if type(config.WiredModem) == "string" then + local modem = ppm.get_modem(_bp.lan_iface) + local wd_nic = network.nic(modem) + + log.info("BKPLN: WIRED PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. _bp.lan_iface) + log_comms("wired comms modem " .. util.trinary(modem, "connected", "not found")) + + -- set this as active for now + _bp.act_nic = wd_nic + _bp.wd_nic = wd_nic + + iocontrol.fp_has_wd_modem(modem ~= nil) + end + + -- init wireless NIC(s) + if config.WirelessModem then + local modem, iface = ppm.get_wireless_modem() + local wl_nic = network.nic(modem) + + log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. iface) + log_comms("wireless comms modem " .. util.trinary(modem, "connected", "not found")) + + -- 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.act_nic = wl_nic + end + + _bp.wl_nic = wl_nic + + iocontrol.fp_has_wl_modem(modem ~= nil) + end + + -- at least one comms modem is required + if not ((_bp.wd_nic and _bp.wd_nic.is_connected()) or (_bp.wl_nic and _bp.wl_nic.is_connected())) then + log_comms("no comms modem found") + println("startup> no comms modem found") + log.warning("BKPLN: no comms modem on startup") + return false + end + + -- Speaker Init + + _bp.speaker = ppm.get_device("speaker") + + if not _bp.speaker then + log_boot("annunciator alarm speaker not found") + + println("startup> speaker not found") + log.fatal("BKPLN: no annunciator alarm speaker found") + + return false + else + log.info("BKPLN: SPEAKER LINK_UP " .. ppm.get_iface(_bp.speaker)) + log_boot("annunciator alarm speaker connected") + + local sounder_start = util.time_ms() + sounder.init(_bp.speaker, config.SpeakerVolume) + + log_boot("tone generation took " .. (util.time_ms() - sounder_start) .. "ms") + log_sys("annunciator alarm configured") + + iocontrol.fp_has_speaker(true) + end + + return true +end + +-- get the active NIC +---@return nic +function backplane.active_nic() return _bp.act_nic end + +function backplane.displays() return _bp.displays end + +return backplane diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 7999679..6edc504 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -29,11 +29,9 @@ local config = {} coordinator.config = config --- load the coordinator configuration
--- status of 0 is OK, 1 is bad config, 2 is bad monitor config ----@return 0|1|2 status, nil|monitors_struct|string monitors (or error message) +-- load the coordinator configuration function coordinator.load_config() - if not settings.load("/coordinator.settings") then return 1 end + if not settings.load("/coordinator.settings") then return false end config.UnitCount = settings.get("UnitCount") config.SpeakerVolume = settings.get("SpeakerVolume") @@ -121,85 +119,7 @@ function coordinator.load_config() cfv.assert_type_int(config.ColorMode) cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES) - -- Monitor Setup - - ---@class monitors_struct - local monitors = { - main = nil, ---@type Monitor|nil - main_name = "", - flow = nil, ---@type Monitor|nil - flow_name = "", - unit_displays = {}, ---@type Monitor[] - unit_name_map = {} ---@type string[] - } - - local mon_cfv = util.new_validator() - - -- get all interface names - local names = {} - for iface, _ in pairs(ppm.get_monitor_list()) do table.insert(names, iface) end - - local function setup_monitors() - mon_cfv.assert_type_str(config.MainDisplay) - if not config.DisableFlowView then mon_cfv.assert_type_str(config.FlowDisplay) end - mon_cfv.assert_eq(#config.UnitDisplays, config.UnitCount) - - if mon_cfv.valid() then - local w, h, _ - - if not util.table_contains(names, config.MainDisplay) then - return 2, "Main monitor is not connected." - end - - monitors.main = ppm.get_periph(config.MainDisplay) - monitors.main_name = config.MainDisplay - - monitors.main.setTextScale(0.5) - w, _ = ppm.monitor_block_size(monitors.main.getSize()) - if w ~= 8 then - return 2, util.c("Main monitor width is incorrect (was ", w, ", must be 8).") - end - - if not config.DisableFlowView then - if not util.table_contains(names, config.FlowDisplay) then - return 2, "Flow monitor is not connected." - end - - monitors.flow = ppm.get_periph(config.FlowDisplay) - monitors.flow_name = config.FlowDisplay - - monitors.flow.setTextScale(0.5) - w, _ = ppm.monitor_block_size(monitors.flow.getSize()) - if w ~= 8 then - return 2, util.c("Flow monitor width is incorrect (was ", w, ", must be 8).") - end - end - - for i = 1, config.UnitCount do - local display = config.UnitDisplays[i] - if type(display) ~= "string" or not util.table_contains(names, display) then - return 2, "Unit " .. i .. " monitor is not connected." - end - - monitors.unit_displays[i] = ppm.get_periph(display) - monitors.unit_name_map[i] = display - - monitors.unit_displays[i].setTextScale(0.5) - w, h = ppm.monitor_block_size(monitors.unit_displays[i].getSize()) - if w ~= 4 or h ~= 4 then - return 2, util.c("Unit ", i, " monitor size is incorrect (was ", w, " by ", h,", must be 4 by 4).") - end - end - else return 2, "Monitor configuration invalid." end - end - - if cfv.valid() then - local ok, result, message = pcall(setup_monitors) - assert(ok, util.c("fatal error while trying to verify monitors: ", result)) - if result == 2 then return 2, message end - else return 1 end - - return 0, monitors + return cfv.valid() end -- dmesg print wrapper diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 9a788db..305dff5 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -290,9 +290,13 @@ end -- toggle heartbeat indicator function iocontrol.heartbeat() io.fp.ps.toggle("heartbeat") end +-- report presence of the wired modem +---@param has_modem boolean +function iocontrol.fp_has_wd_modem(has_modem) io.fp.ps.publish("has_wd_modem", has_modem) end + -- report presence of the wireless modem ---@param has_modem boolean -function iocontrol.fp_has_modem(has_modem) io.fp.ps.publish("has_modem", has_modem) end +function iocontrol.fp_has_wl_modem(has_modem) io.fp.ps.publish("has_wl_modem", has_modem) end -- report presence of the speaker ---@param has_speaker boolean diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index d3ca0cd..6c96676 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -29,7 +29,7 @@ local renderer = {} -- render engine local engine = { color_mode = 1, ---@type COLOR_MODE - monitors = nil, ---@type monitors_struct|nil + monitors = nil, ---@type crd_displays|nil dmesg_window = nil, ---@type Window|nil ui_ready = false, fp_ready = false, @@ -83,7 +83,7 @@ function renderer.configure(config) end -- link to the monitor peripherals ----@param monitors monitors_struct +---@param monitors crd_displays function renderer.set_displays(monitors) engine.monitors = monitors @@ -336,18 +336,18 @@ function renderer.handle_reconnect(name, device) -- note: handle_resize is a more adaptive way of re-initializing a connected monitor -- since it can handle a monitor being reconnected that isn't the right size - if engine.monitors.main_name == name then + if engine.monitors.main_iface == name then is_used = true engine.monitors.main = device renderer.handle_resize(name) - elseif engine.monitors.flow_name == name then + elseif engine.monitors.flow_iface == name then is_used = true engine.monitors.flow = device renderer.handle_resize(name) else - for idx, monitor in ipairs(engine.monitors.unit_name_map) do + for idx, monitor in ipairs(engine.monitors.unit_ifaces) do if monitor == name then is_used = true engine.monitors.unit_displays[idx] = device @@ -372,7 +372,7 @@ function renderer.handle_resize(name) if not engine.monitors then return false, false end - if engine.monitors.main_name == name and engine.monitors.main then + if engine.monitors.main_iface == name and engine.monitors.main then local device = engine.monitors.main ---@type Monitor -- this is necessary if the bottom left block was broken and on reconnect @@ -415,7 +415,7 @@ function renderer.handle_resize(name) is_ok = false end else engine.dmesg_window.redraw() end - elseif engine.monitors.flow_name == name and engine.monitors.flow then + elseif engine.monitors.flow_iface == name and engine.monitors.flow then local device = engine.monitors.flow ---@type Monitor -- this is necessary if the bottom left block was broken and on reconnect @@ -452,7 +452,7 @@ function renderer.handle_resize(name) end end else - for idx, monitor in ipairs(engine.monitors.unit_name_map) do + for idx, monitor in ipairs(engine.monitors.unit_ifaces) do local device = engine.monitors.unit_displays[idx] if monitor == name and device then @@ -505,12 +505,12 @@ function renderer.handle_mouse(event) if engine.fp_ready and event.monitor == "terminal" then engine.ui.front_panel.handle_mouse(event) elseif engine.ui_ready then - if event.monitor == engine.monitors.main_name then + if event.monitor == engine.monitors.main_iface then if engine.ui.main_display then engine.ui.main_display.handle_mouse(event) end - elseif event.monitor == engine.monitors.flow_name then + elseif event.monitor == engine.monitors.flow_iface then if engine.ui.flow_display then engine.ui.flow_display.handle_mouse(event) end else - for id, monitor in ipairs(engine.monitors.unit_name_map) do + for id, monitor in ipairs(engine.monitors.unit_ifaces) do local display = engine.ui.unit_displays[id] if event.monitor == monitor and display then if display then display.handle_mouse(event) end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 01b6d58..b096b74 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -12,6 +12,7 @@ local network = require("scada-common.network") local ppm = require("scada-common.ppm") local util = require("scada-common.util") +local backplane = require("coordinator.backplane") local configure = require("coordinator.configure") local coordinator = require("coordinator.coordinator") local iocontrol = require("coordinator.iocontrol") @@ -36,45 +37,13 @@ local log_crypto = coordinator.log_crypto -- get configuration ---------------------------------------- --- mount connected devices (required for monitor setup) -ppm.mount_all() - -local wait_on_load = true -local loaded, monitors = coordinator.load_config() - --- if the computer just started, its chunk may have just loaded (...or the user rebooted) --- if monitor config failed, maybe an adjacent chunk containing all or part of a monitor has not loaded yet, so keep trying -while wait_on_load and loaded == 2 and os.clock() < CHUNK_LOAD_DELAY_S do - term.clear() - term.setCursorPos(1, 1) - println("There was a monitor configuration problem at boot.\n") - println("Startup will keep trying every 2s in case of chunk load delays.\n") - println(util.sprintf("The configurator will be started in %ds if all attempts fail.\n", math.max(0, CHUNK_LOAD_DELAY_S - os.clock()))) - println("(click to skip to the configurator)") - - local timer_id = util.start_timer(2) - - while true do - local event, param1 = util.pull_event() - if event == "timer" and param1 == timer_id then - -- remount and re-attempt - ppm.mount_all() - loaded, monitors = coordinator.load_config() - break - elseif event == "mouse_click" or event == "terminate" then - wait_on_load = false - break - end - end -end - -if loaded ~= 0 then +-- first pass configuration check before validating monitors +if not coordinator.load_config() then -- try to reconfigure (user action) - local success, error = configure.configure(loaded, monitors) + local success, error = configure.configure(1) if success then - loaded, monitors = coordinator.load_config() - if loaded ~= 0 then - println(util.trinary(loaded == 2, "monitor configuration invalid", "failed to load a valid configuration") .. ", please reconfigure") + if not coordinator.load_config() then + println("failed to load a valid configuration, please reconfigure") return end else @@ -83,9 +52,6 @@ if loaded ~= 0 then end end --- passed checks, good now ----@cast monitors monitors_struct - local config = coordinator.config ---------------------------------------- @@ -102,6 +68,64 @@ println(">> SCADA Coordinator " .. COORDINATOR_VERSION .. " <<") crash.set_env("coordinator", COORDINATOR_VERSION) crash.dbg_log_env() +---------------------------------------- +-- display init +---------------------------------------- + +-- mount connected devices (required for monitor setup) +ppm.mount_all() + +local wait_on_load = true + +local disp_ok, disp_err = backplane.init_displays(config) + +-- if the computer just started, its chunk may have just loaded (...or the user rebooted) +-- if monitor config failed, maybe an adjacent chunk containing all or part of a monitor has not loaded yet, so keep trying +while wait_on_load and (not disp_ok) and os.clock() < CHUNK_LOAD_DELAY_S do + term.clear() + term.setCursorPos(1, 1) + println("There was a monitor configuration problem at boot.\n") + println("Startup will keep trying every 2s in case of chunk load delays.\n") + println(util.sprintf("The configurator will be started in %ds if all attempts fail.\n", math.max(0, CHUNK_LOAD_DELAY_S - os.clock()))) + println("(click to skip to the configurator)") + + local timer_id = util.start_timer(2) + + while true do + local event, param1 = util.pull_event() + if event == "timer" and param1 == timer_id then + -- remount and re-attempt + ppm.mount_all() + disp_ok, disp_err = backplane.init_displays(config) + break + elseif event == "mouse_click" or event == "terminate" then + wait_on_load = false + break + end + end +end + +if not disp_ok then + -- try to reconfigure (user action) + local success, error = configure.configure(2, disp_err) + if success then + if not coordinator.load_config() then + println("failed to load a valid configuration, please reconfigure") + return + else + disp_ok, disp_err = backplane.init_displays(config) + + if not disp_ok then + println("monitor configuration invalid, please reconfigure") + return + end + end + else + println("configuration error: " .. error) + return + end +end + ---------------------------------------- -- main application ---------------------------------------- @@ -111,15 +135,12 @@ local function main() -- system startup ---------------------------------------- - -- log mounts now since mounting was done before logging was ready - ppm.log_mounts() - -- report versions/init fp PSIL iocontrol.init_fp(COORDINATOR_VERSION, comms.version) -- init renderer renderer.configure(config) - renderer.set_displays(monitors) + renderer.set_displays(backplane.displays()) renderer.init_displays() renderer.init_dmesg() @@ -130,6 +151,12 @@ local function main() log_sys("system start on " .. os.date("%c")) log_boot("starting " .. COORDINATOR_VERSION) + -- message authentication init + if type(config.AuthKey) == "string" and string.len(config.AuthKey) > 0 then + local init_time = network.init_mac(config.AuthKey) + log_crypto("HMAC init took " .. init_time .. "ms") + end + ---------------------------------------- -- memory allocation ---------------------------------------- @@ -149,15 +176,9 @@ local function main() shutdown = false }, - -- core coordinator devices - crd_dev = { - modem = ppm.get_wireless_modem(), - speaker = ppm.get_device("speaker") ---@type Speaker|nil - }, - -- system objects + ---@class crd_sys crd_sys = { - nic = nil, ---@type nic coord_comms = nil, ---@type coord_comms conn_watchdog = nil ---@type watchdog }, @@ -168,65 +189,17 @@ local function main() } } - local smem_dev = __shared_memory.crd_dev - local smem_sys = __shared_memory.crd_sys - + local smem_sys = __shared_memory.crd_sys local crd_state = __shared_memory.crd_state ---------------------------------------- - -- setup alarm sounder subsystem + -- init system ---------------------------------------- - if smem_dev.speaker == nil then - log_boot("annunciator alarm speaker not found") - println("startup> speaker not found") - log.fatal("no annunciator alarm speaker found") - return - else - local sounder_start = util.time_ms() - log_boot("annunciator alarm speaker connected") - sounder.init(smem_dev.speaker, config.SpeakerVolume) - log_boot("tone generation took " .. (util.time_ms() - sounder_start) .. "ms") - log_sys("annunciator alarm configured") - iocontrol.fp_has_speaker(true) - end + -- modem and speaker initialization + if not backplane.init(config, __shared_memory) then return end - ---------------------------------------- - -- setup communications - ---------------------------------------- - - -- message authentication init - if type(config.AuthKey) == "string" and string.len(config.AuthKey) > 0 then - local init_time = network.init_mac(config.AuthKey) - log_crypto("HMAC init took " .. init_time .. "ms") - end - - -- get the communications modem - if smem_dev.modem == nil then - log_comms("wireless modem not found") - println("startup> wireless modem not found") - log.fatal("no wireless modem on startup") - return - else - log_comms("wireless modem connected") - iocontrol.fp_has_modem(true) - end - - -- create connection watchdog - smem_sys.conn_watchdog = util.new_watchdog(config.SVR_Timeout) - smem_sys.conn_watchdog.cancel() - log.debug("startup> conn watchdog created") - - -- create network interface then setup comms - smem_sys.nic = network.nic(smem_dev.modem) - smem_sys.coord_comms = coordinator.comms(COORDINATOR_VERSION, smem_sys.nic, smem_sys.conn_watchdog) - log.debug("startup> comms init") - log_comms("comms initialized") - - ---------------------------------------- -- start front panel - ---------------------------------------- - log_render("starting front panel UI...") local fp_message @@ -238,6 +211,16 @@ local function main() return else log_render("front panel ready") end + -- create connection watchdog + smem_sys.conn_watchdog = util.new_watchdog(config.SVR_Timeout) + smem_sys.conn_watchdog.cancel() + log.debug("startup> conn watchdog created") + + -- setup comms + smem_sys.coord_comms = coordinator.comms(COORDINATOR_VERSION, backplane.active_nic(), smem_sys.conn_watchdog) + log.debug("startup> comms init") + log_comms("comms initialized") + ---------------------------------------- -- start system ---------------------------------------- From 6123d5dad7e338dd4abb84f029147f9836672857 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 3 Nov 2025 18:47:55 -0500 Subject: [PATCH 55/90] #580 minor updates for consistency --- reactor-plc/backplane.lua | 2 +- rtu/backplane.lua | 2 +- rtu/startup.lua | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/reactor-plc/backplane.lua b/reactor-plc/backplane.lua index e384e50..d63fa6e 100644 --- a/reactor-plc/backplane.lua +++ b/reactor-plc/backplane.lua @@ -71,7 +71,7 @@ function backplane.init(config, __shared_memory) -- comms modem is required if networked if not (plc_state.wd_modem or plc_state.wl_modem) then - println("startup> comms modem not found") + println("startup> no comms modem found") log.warning("BKPLN: no comms modem on startup") plc_state.degraded = true diff --git a/rtu/backplane.lua b/rtu/backplane.lua index 4415fe0..4282477 100644 --- a/rtu/backplane.lua +++ b/rtu/backplane.lua @@ -72,7 +72,7 @@ function backplane.init(config, __shared_memory) -- at least one comms modem is required if not ((_bp.wd_nic and _bp.wd_nic.is_connected()) or (_bp.wl_nic and _bp.wl_nic.is_connected())) then - println("startup> comms modem not found") + println("startup> no comms modem found") log.warning("BKPLN: no comms modem on startup") return false end diff --git a/rtu/startup.lua b/rtu/startup.lua index ad6172a..148d5f6 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -138,8 +138,7 @@ local function main() log.debug("startup> conn watchdog started") -- setup comms - local nic = backplane.active_nic() - smem_sys.rtu_comms = rtu.comms(RTU_VERSION, nic, smem_sys.conn_watchdog) + smem_sys.rtu_comms = rtu.comms(RTU_VERSION, backplane.active_nic(), smem_sys.conn_watchdog) log.debug("startup> comms init") -- init threads From 61305621c3c92158d4fcc1434229d54bec09f851 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 6 Nov 2025 22:48:42 +0000 Subject: [PATCH 56/90] RTU cleanup and fixes --- rtu/backplane.lua | 2 +- rtu/rtu.lua | 1 - rtu/startup.lua | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/rtu/backplane.lua b/rtu/backplane.lua index 4282477..e73fc7c 100644 --- a/rtu/backplane.lua +++ b/rtu/backplane.lua @@ -150,7 +150,7 @@ function backplane.detach(type, device, iface, print_no_fp) databus.tx_hw_wl_modem(true) elseif wd_nic and wd_nic.is_connected() then - _bp.act_nic = _bp.wd_nic + _bp.act_nic = wd_nic comms.switch_nic(_bp.act_nic) log.info("BKPLN: switched comms to wired modem") diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 3efbcf7..0a26693 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -326,7 +326,6 @@ function rtu.comms(version, nic, conn_watchdog) m_pkt.make(msg_type, msg) s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) ----@diagnostic disable-next-line: need-check-nil nic.transmit(config.SVR_Channel, config.RTU_Channel, s_pkt) self.seq_num = self.seq_num + 1 end diff --git a/rtu/startup.lua b/rtu/startup.lua index 148d5f6..6338a1c 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -121,7 +121,6 @@ local function main() log.debug("boot> running uinit()") if uinit(config, __shared_memory) then - -- start UI local message rtu_state.fp_ok, message = renderer.try_start_ui(config, units) From 44340f42d4e8c718c806d8440cfe34d81895334e Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 6 Nov 2025 22:49:22 +0000 Subject: [PATCH 57/90] consistent ordering of attach/detach functions --- rtu/backplane.lua | 134 +++++++++++++++++++++++----------------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/rtu/backplane.lua b/rtu/backplane.lua index e73fc7c..c0637c7 100644 --- a/rtu/backplane.lua +++ b/rtu/backplane.lua @@ -102,6 +102,73 @@ function backplane.active_nic() return _bp.act_nic end ---@return rtu_speaker_sounder[] function backplane.sounders() return _bp.sounders end +-- handle a backplane peripheral attach +---@param type string +---@param device table +---@param iface string +---@param print_no_fp function +function backplane.attach(type, device, iface, print_no_fp) + local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic + + local comms = _bp.smem.rtu_sys.rtu_comms + + if 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)) + + if wd_nic and (_bp.lan_iface == iface) then + -- connect this as the wired NIC + wd_nic.connect(device) + + log.info("BKPLN: WIRED PHY_UP " .. iface) + print_no_fp("wired comms modem reconnected") + + if (_bp.act_nic ~= wd_nic) and not _bp.wlan_pref then + -- switch back to preferred wired + _bp.act_nic = wd_nic + + comms.switch_nic(_bp.act_nic) + log.info("BKPLN: switched comms to wired modem (preferred)") + + databus.tx_hw_wd_modem(true) + end + elseif wl_nic and (not wl_nic.is_connected()) and m_is_wl then + -- connect this as the wireless NIC + wl_nic.connect(device) + + log.info("BKPLN: WIRELESS PHY_UP " .. iface) + + if (_bp.act_nic ~= wl_nic) and _bp.wlan_pref then + -- switch back to preferred wireless + _bp.act_nic = wl_nic + + comms.switch_nic(_bp.act_nic) + log.info("BKPLN: switched comms to wireless modem (preferred)") + + databus.tx_hw_wl_modem(true) + end + elseif 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 + elseif type == "speaker" then + ---@cast device Speaker + table.insert(_bp.sounders, rtu.init_sounder(device)) + + print_no_fp("a speaker was connected") + log.info(util.c("BKPLN: connected speaker ", iface)) + + databus.tx_hw_spkr_count(#_bp.sounders) + end +end + -- handle a backplane peripheral detach ---@param type string ---@param device table @@ -188,71 +255,4 @@ function backplane.detach(type, device, iface, print_no_fp) end end --- handle a backplane peripheral attach ----@param type string ----@param device table ----@param iface string ----@param print_no_fp function -function backplane.attach(type, device, iface, print_no_fp) - local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic - - local comms = _bp.smem.rtu_sys.rtu_comms - - if 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)) - - if wd_nic and (_bp.lan_iface == iface) then - -- connect this as the wired NIC - wd_nic.connect(device) - - log.info("BKPLN: WIRED PHY_UP " .. iface) - print_no_fp("wired comms modem reconnected") - - if (_bp.act_nic ~= wd_nic) and not _bp.wlan_pref then - -- switch back to preferred wired - _bp.act_nic = wd_nic - - comms.switch_nic(_bp.act_nic) - log.info("BKPLN: switched comms to wired modem (preferred)") - - databus.tx_hw_wd_modem(true) - end - elseif wl_nic and (not wl_nic.is_connected()) and m_is_wl then - -- connect this as the wireless NIC - wl_nic.connect(device) - - log.info("BKPLN: WIRELESS PHY_UP " .. iface) - - if (_bp.act_nic ~= wl_nic) and _bp.wlan_pref then - -- switch back to preferred wireless - _bp.act_nic = wl_nic - - comms.switch_nic(_bp.act_nic) - log.info("BKPLN: switched comms to wireless modem (preferred)") - - databus.tx_hw_wl_modem(true) - end - elseif 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 - elseif type == "speaker" then - ---@cast device Speaker - table.insert(_bp.sounders, rtu.init_sounder(device)) - - print_no_fp("a speaker was connected") - log.info(util.c("BKPLN: connected speaker ", iface)) - - databus.tx_hw_spkr_count(#_bp.sounders) - end -end - return backplane From b2baaa2090f542dbd958082929159abbfce95012 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 6 Nov 2025 22:54:48 +0000 Subject: [PATCH 58/90] reactor PLC queue types refactor and properly report wireless modem connected when another is found --- reactor-plc/backplane.lua | 6 ++++-- reactor-plc/startup.lua | 4 ++-- reactor-plc/threads.lua | 8 ++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/reactor-plc/backplane.lua b/reactor-plc/backplane.lua index d63fa6e..4092abe 100644 --- a/reactor-plc/backplane.lua +++ b/reactor-plc/backplane.lua @@ -118,7 +118,7 @@ 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 MQ__RPS_CMD = _bp.smem.q_types.MQ__RPS_CMD local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic @@ -217,7 +217,7 @@ end ---@param device table ---@param print_no_fp function function backplane.detach(iface, type, device, print_no_fp) - local MQ__RPS_CMD = _bp.smem.q_cmds.MQ__RPS_CMD + local MQ__RPS_CMD = _bp.smem.q_types.MQ__RPS_CMD local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic @@ -266,6 +266,8 @@ function backplane.detach(iface, type, device, print_no_fp) wl_nic.connect(modem) log.info("BKPLN: WIRELESS PHY_UP " .. m_iface) + + state.wl_modem = true elseif wd_nic and wd_nic.is_connected() then _bp.act_nic = wd_nic diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 393b89e..12c1f16 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -125,8 +125,8 @@ local function main() mq_comms_rx = mqueue.new() }, - -- message queue commands - q_cmds = { + -- message queue message types + q_types = { MQ__RPS_CMD = { SCRAM = 1, DEGRADED_SCRAM = 2, diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index cc0e631..7b45c31 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -48,8 +48,8 @@ function threads.thread__main(smem) local plc_comms = smem.plc_sys.plc_comms local conn_watchdog = smem.plc_sys.conn_watchdog - local MQ__RPS_CMD = smem.q_cmds.MQ__RPS_CMD - local MQ__COMM_CMD = smem.q_cmds.MQ__COMM_CMD + local MQ__RPS_CMD = smem.q_types.MQ__RPS_CMD + local MQ__COMM_CMD = smem.q_types.MQ__COMM_CMD -- start clock loop_clock.start() @@ -204,7 +204,7 @@ function threads.thread__rps(smem) local rps_queue = smem.q.mq_rps - local MQ__RPS_CMD = smem.q_cmds.MQ__RPS_CMD + local MQ__RPS_CMD = smem.q_types.MQ__RPS_CMD local was_linked = false local last_update = util.time() @@ -339,7 +339,7 @@ function threads.thread__comms_tx(smem) 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 MQ__COMM_CMD = smem.q_types.MQ__COMM_CMD local last_update = util.time() From 212e1f8fe810149338aa38fd102dc4799a07878a Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 7 Nov 2025 16:51:43 +0000 Subject: [PATCH 59/90] #634 coordinator backplane attach/detach --- coordinator/backplane.lua | 179 +++++++++++++++++++++++++++- coordinator/coordinator.lua | 32 ++++- coordinator/renderer.lua | 12 +- coordinator/session/apisessions.lua | 2 +- coordinator/startup.lua | 18 ++- coordinator/threads.lua | 108 ++++------------- 6 files changed, 246 insertions(+), 105 deletions(-) diff --git a/coordinator/backplane.lua b/coordinator/backplane.lua index 0647dd4..e0deec8 100644 --- a/coordinator/backplane.lua +++ b/coordinator/backplane.lua @@ -80,6 +80,8 @@ function backplane.init_displays(config) log.info("BKPLN: DISPLAY LINK_" .. util.trinary(disp, "UP", "DOWN") .. " MAIN/" .. iface) + iocontrol.fp_monitor_state("main", disp ~= nil) + if not disp then return false, "Main monitor is not connected." end @@ -101,6 +103,8 @@ function backplane.init_displays(config) log.info("BKPLN: DISPLAY LINK_" .. util.trinary(disp, "UP", "DOWN") .. " FLOW/" .. iface) + iocontrol.fp_monitor_state("flow", disp ~= nil) + if not disp then return false, "Flow monitor is not connected." end @@ -123,6 +127,8 @@ function backplane.init_displays(config) log.info("BKPLN: DISPLAY LINK_" .. util.trinary(disp, "UP", "DOWN") .. " UNIT_" .. i .. "/" .. iface) + iocontrol.fp_monitor_state(i, disp ~= nil) + if not disp then return false, "Unit " .. i .. " monitor is not connected." end @@ -181,6 +187,9 @@ function backplane.init(config, __shared_memory) _bp.wl_nic = wl_nic + wl_nic.closeAll() + wl_nic.open(config.CRD_Channel) + iocontrol.fp_has_wl_modem(modem ~= nil) end @@ -220,9 +229,177 @@ function backplane.init(config, __shared_memory) end -- get the active NIC ----@return nic function backplane.active_nic() return _bp.act_nic end +-- get the wireless NIC +function backplane.wireless_nic() return _bp.wl_nic end + +-- get the configured displays function backplane.displays() return _bp.displays end +-- handle a backplane peripheral attach +---@param type string +---@param device table +---@param iface string +function backplane.attach(type, device, iface) + local MQ__RENDER_DATA = _bp.smem.q_types.MQ__RENDER_DATA + + local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic + + local comms = _bp.smem.crd_sys.coord_comms + + if 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)) + + if wd_nic and (_bp.lan_iface == iface) then + -- connect this as the wired NIC + wd_nic.connect(device) + + log.info("BKPLN: WIRED PHY_UP " .. iface) + log_sys("wired comms modem reconnected") + + iocontrol.fp_has_wd_modem(true) + + if (_bp.act_nic ~= wd_nic) and not _bp.wlan_pref then + -- switch back to preferred wired + _bp.act_nic = wd_nic + + comms.switch_nic(_bp.act_nic) + log.info("BKPLN: switched comms to wired modem (preferred)") + end + elseif wl_nic and (not wl_nic.is_connected()) and m_is_wl then + -- connect this as the wireless NIC + wl_nic.connect(device) + + log.info("BKPLN: WIRELESS PHY_UP " .. iface) + log_sys("wireless comms modem reconnected") + + iocontrol.fp_has_wl_modem(true) + + if (_bp.act_nic ~= wl_nic) and _bp.wlan_pref then + -- switch back to preferred wireless + _bp.act_nic = wl_nic + + comms.switch_nic(_bp.act_nic) + log.info("BKPLN: switched comms to wireless modem (preferred)") + end + elseif wl_nic and m_is_wl then + -- the wireless NIC already has a modem + log_sys("standby wireless modem connected") + log.info("BKPLN: standby wireless modem connected") + else + log_sys("unassigned modem connected") + log.warning("BKPLN: unassigned modem connected") + end + elseif type == "monitor" then + ---@cast device Monitor + _bp.smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_CONNECT, { name = iface, device = device }) + elseif type == "speaker" then + ---@cast device Speaker + log_sys("alarm sounder speaker reconnected") + log.info("BKPLN: SPEAKER LINK_UP " .. iface) + + sounder.reconnect(device) + + iocontrol.fp_has_speaker(true) + end +end + +-- handle a backplane peripheral detach +---@param type string +---@param device table +---@param iface string +function backplane.detach(type, device, iface) + local MQ__RENDER_CMD = _bp.smem.q_types.MQ__RENDER_CMD + local MQ__RENDER_DATA = _bp.smem.q_types.MQ__RENDER_DATA + + local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic + + local comms = _bp.smem.crd_sys.coord_comms + + if 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_DETACH ", iface)) + + if wd_nic and wd_nic.is_modem(device) then + wd_nic.disconnect() + log.info("BKPLN: WIRED PHY_DOWN " .. iface) + + iocontrol.fp_has_wd_modem(false) + elseif wl_nic and wl_nic.is_modem(device) then + wl_nic.disconnect() + log.info("BKPLN: WIRELESS PHY_DOWN " .. iface) + + iocontrol.fp_has_wl_modem(false) + end + + -- we only care if this is our active comms modem + if _bp.act_nic.is_modem(device) then + log_sys("active comms modem disconnected") + log.warning("BKPLN: active comms modem disconnected") + + -- failover and try to find a new comms modem + if _bp.act_nic == wl_nic then + -- wireless active disconnected + -- try to find another wireless modem, otherwise switch to wired + local modem, m_iface = ppm.get_wireless_modem() + if wl_nic and modem then + log_sys("found another wireless modem, using it for comms") + log.info("BKPLN: found another wireless modem, using it for comms") + + wl_nic.connect(modem) + + log.info("BKPLN: WIRELESS PHY_UP " .. m_iface) + + iocontrol.fp_has_wl_modem(true) + elseif wd_nic and wd_nic.is_connected() then + _bp.act_nic = wd_nic + + comms.switch_nic(_bp.act_nic) + log.info("BKPLN: switched comms to wired modem") + else + -- close out main UI + _bp.smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI) + + -- alert user to status + log_sys("awaiting comms modem reconnect...") + end + elseif wl_nic and wl_nic.is_connected() then + -- wired active disconnected, wireless available + _bp.act_nic = wl_nic + + comms.switch_nic(_bp.act_nic) + log.info("BKPLN: switched comms to wireless modem") + else + -- wired active disconnected, wireless unavailable + end + elseif _bp.wl_nic and m_is_wl then + -- wireless, but not active + log_sys("standby wireless modem disconnected") + log.info("BKPLN: standby wireless modem disconnected") + else + log_sys("unassigned modem disconnected") + log.warning("BKPLN: unassigned modem disconnected") + end + elseif type == "monitor" then + ---@cast device Monitor + _bp.smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_DISCONNECT, device) + elseif type == "speaker" then + ---@cast device Speaker + log_sys("lost alarm sounder speaker") + + log.info("BKPLN: SPEAKER LINK_DOWN " .. iface) + + iocontrol.fp_has_speaker(false) + end +end + + return backplane diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 6edc504..324486e 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -163,9 +163,10 @@ end -- coordinator communications ---@nodiscard ---@param version string coordinator version ----@param nic nic network interface device +---@param nic nic active network interface device +---@param wl_nic nic|nil pocket wireless network interface device ---@param sv_watchdog watchdog -function coordinator.comms(version, nic, sv_watchdog) +function coordinator.comms(version, nic, wl_nic, sv_watchdog) local self = { sv_linked = false, sv_addr = comms.BROADCAST, @@ -180,14 +181,18 @@ function coordinator.comms(version, nic, sv_watchdog) est_task_done = nil } - comms.set_trusted_range(config.TrustedRange) + if config.WirelessModem then + comms.set_trusted_range(config.TrustedRange) + end -- configure network channels nic.closeAll() nic.open(config.CRD_Channel) -- pass config to apisessions - apisessions.init(nic, config) + if config.API_Enabled and wl_nic then + apisessions.init(wl_nic, config) + end -- PRIVATE FUNCTIONS -- @@ -224,7 +229,8 @@ function coordinator.comms(version, nic, sv_watchdog) m_pkt.make(MGMT_TYPE.ESTABLISH, { ack, data }) s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) - nic.transmit(config.PKT_Channel, config.CRD_Channel, s_pkt) +---@diagnostic disable-next-line: need-check-nil + wl_nic.transmit(config.PKT_Channel, config.CRD_Channel, s_pkt) self.last_api_est_acks[packet.src_addr()] = ack end @@ -245,6 +251,18 @@ function coordinator.comms(version, nic, sv_watchdog) ---@class coord_comms local public = {} + -- switch the current active NIC + ---@param _nic nic + function public.switch_nic(_nic) + nic.closeAll() + + -- configure receive channels + _nic.closeAll() + _nic.open(config.CRD_Channel) + + nic = _nic + end + -- try to connect to the supervisor if not already linked ---@param abort boolean? true to print out cancel info if not linked (use on program terminate) ---@return boolean ok, boolean start_ui @@ -399,7 +417,9 @@ function coordinator.comms(version, nic, sv_watchdog) if l_chan ~= config.CRD_Channel then log.debug("received packet on unconfigured channel " .. l_chan, true) elseif r_chan == config.PKT_Channel then - if not self.sv_linked then + if not config.API_Enabled then + -- log.debug("discarding pocket API packet due to the API being disabled") + elseif not self.sv_linked then log.debug("discarding pocket API packet before linked to supervisor") elseif protocol == PROTOCOL.SCADA_CRDN then ---@cast packet crdn_frame diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 6c96676..c6bb3f1 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -82,19 +82,11 @@ function renderer.configure(config) engine.disable_flow_view = config.DisableFlowView end --- link to the monitor peripherals +-- init all displays in use by the renderer ---@param monitors crd_displays -function renderer.set_displays(monitors) +function renderer.init_displays(monitors) engine.monitors = monitors - -- report to front panel as connected - iocontrol.fp_monitor_state("main", engine.monitors.main ~= nil) - iocontrol.fp_monitor_state("flow", engine.monitors.flow ~= nil) - for i = 1, #engine.monitors.unit_displays do iocontrol.fp_monitor_state(i, true) end -end - --- init all displays in use by the renderer -function renderer.init_displays() -- init main and flow monitors _init_display(engine.monitors.main) if not engine.disable_flow_view then _init_display(engine.monitors.flow) end diff --git a/coordinator/session/apisessions.lua b/coordinator/session/apisessions.lua index 30fee09..24191d2 100644 --- a/coordinator/session/apisessions.lua +++ b/coordinator/session/apisessions.lua @@ -69,7 +69,7 @@ end -- PUBLIC FUNCTIONS -- -- initialize apisessions ----@param nic nic network interface +---@param nic nic API network interface ---@param config crd_config coordinator config function apisessions.init(nic, config) self.nic = nic diff --git a/coordinator/startup.lua b/coordinator/startup.lua index b096b74..b2dd23c 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -140,8 +140,7 @@ local function main() -- init renderer renderer.configure(config) - renderer.set_displays(backplane.displays()) - renderer.init_displays() + renderer.init_displays(backplane.displays()) renderer.init_dmesg() -- lets get started! @@ -186,6 +185,19 @@ local function main() -- message queues q = { mq_render = mqueue.new() + }, + + -- message queue message types + q_types = { + MQ__RENDER_CMD = { + START_MAIN_UI = 1, + CLOSE_MAIN_UI = 2 + }, + MQ__RENDER_DATA = { + MON_CONNECT = 1, + MON_DISCONNECT = 2, + MON_RESIZE = 3 + } } } @@ -217,7 +229,7 @@ local function main() log.debug("startup> conn watchdog created") -- setup comms - smem_sys.coord_comms = coordinator.comms(COORDINATOR_VERSION, backplane.active_nic(), smem_sys.conn_watchdog) + smem_sys.coord_comms = coordinator.comms(COORDINATOR_VERSION, backplane.active_nic(), backplane.wireless_nic(), smem_sys.conn_watchdog) log.debug("startup> comms init") log_comms("comms initialized") diff --git a/coordinator/threads.lua b/coordinator/threads.lua index 8c6036a..f80f28f 100644 --- a/coordinator/threads.lua +++ b/coordinator/threads.lua @@ -4,6 +4,7 @@ local ppm = require("scada-common.ppm") local tcd = require("scada-common.tcd") local util = require("scada-common.util") +local backplane = require("coordinator.backplane") local coordinator = require("coordinator.coordinator") local iocontrol = require("coordinator.iocontrol") local process = require("coordinator.process") @@ -23,17 +24,6 @@ local threads = {} local MAIN_CLOCK = 0.5 -- 2Hz, 10 ticks local RENDER_SLEEP = 100 -- 100ms, 2 ticks -local MQ__RENDER_CMD = { - START_MAIN_UI = 1, - CLOSE_MAIN_UI = 2 -} - -local MQ__RENDER_DATA = { - MON_CONNECT = 1, - MON_DISCONNECT = 2, - MON_RESIZE = 3 -} - -- main thread ---@nodiscard ---@param smem crd_shared_memory @@ -54,10 +44,12 @@ function threads.thread__main(smem) log_sys("system started successfully") -- load in from shared memory - local crd_state = smem.crd_state - local nic = smem.crd_sys.nic - local coord_comms = smem.crd_sys.coord_comms - local conn_watchdog = smem.crd_sys.conn_watchdog + local crd_state = smem.crd_state + local coord_comms = smem.crd_sys.coord_comms + local conn_watchdog = smem.crd_sys.conn_watchdog + + local MQ__RENDER_CMD = smem.q_types.MQ__RENDER_CMD + local MQ__RENDER_DATA = smem.q_types.MQ__RENDER_DATA -- event loop while true do @@ -66,66 +58,13 @@ function threads.thread__main(smem) -- handle event if event == "peripheral_detach" then local type, device = ppm.handle_unmount(param1) - if type ~= nil and device ~= nil then - if type == "modem" then - ---@cast device Modem - -- we only really care if this is our wireless modem - -- if it is another modem, handle other peripheral losses separately - if nic.is_modem(device) then - nic.disconnect() - log_sys("comms modem disconnected") - - local other_modem = ppm.get_wireless_modem() - if other_modem then - log_sys("found another wireless modem, using it for comms") - nic.connect(other_modem) - else - -- close out main UI - smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI) - - -- alert user to status - log_sys("awaiting comms modem reconnect...") - - iocontrol.fp_has_modem(false) - end - else - log_sys("non-comms modem disconnected") - end - elseif type == "monitor" then - ---@cast device Monitor - smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_DISCONNECT, device) - elseif type == "speaker" then - ---@cast device Speaker - log_sys("lost alarm sounder speaker") - iocontrol.fp_has_speaker(false) - end + backplane.detach(type, device, param1) end elseif event == "peripheral" then local type, device = ppm.mount(param1) - if type ~= nil and device ~= nil then - if type == "modem" then - ---@cast device Modem - if device.isWireless() and not nic.is_connected() then - -- reconnected modem - log_sys("comms modem reconnected") - nic.connect(device) - iocontrol.fp_has_modem(true) - elseif device.isWireless() then - log.info("unused wireless modem reconnected") - else - log_sys("wired modem reconnected") - end - elseif type == "monitor" then - ---@cast device Monitor - smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_CONNECT, { name = param1, device = device }) - elseif type == "speaker" then - ---@cast device Speaker - log_sys("alarm sounder speaker reconnected") - sounder.reconnect(device) - iocontrol.fp_has_speaker(true) - end + backplane.attach(type, device, param1) end elseif event == "monitor_resize" then smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_RESIZE, param1) @@ -137,18 +76,16 @@ function threads.thread__main(smem) iocontrol.heartbeat() -- maintain connection - if nic.is_connected() then - local ok, start_ui = coord_comms.try_connect() - if not ok then - crd_state.link_fail = true - crd_state.shutdown = true - log_sys("supervisor connection failed, shutting down...") - log.fatal("failed to connect to supervisor") - break - elseif start_ui then - log_sys("supervisor connected, dispatching main UI start") - smem.q.mq_render.push_command(MQ__RENDER_CMD.START_MAIN_UI) - end + local ok, start_ui = coord_comms.try_connect() + if not ok then + crd_state.link_fail = true + crd_state.shutdown = true + log_sys("supervisor connection failed, shutting down...") + log.fatal("failed to connect to supervisor") + break + elseif start_ui then + log_sys("supervisor connected, dispatching main UI start") + smem.q.mq_render.push_command(MQ__RENDER_CMD.START_MAIN_UI) end -- iterate sessions and free any closed ones @@ -268,8 +205,11 @@ function threads.thread__render(smem) log.debug("render thread start") -- load in from shared memory - local crd_state = smem.crd_state - local render_queue = smem.q.mq_render + local crd_state = smem.crd_state + local render_queue = smem.q.mq_render + + local MQ__RENDER_CMD = smem.q_types.MQ__RENDER_CMD + local MQ__RENDER_DATA = smem.q_types.MQ__RENDER_DATA local last_update = util.time() From 9ff183b17da7e042ca7791641651f17ecf90e6dd Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 7 Nov 2025 16:52:01 +0000 Subject: [PATCH 60/90] #634 RTU gateway backplane fixes --- reactor-plc/backplane.lua | 1 - rtu/backplane.lua | 15 +++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/reactor-plc/backplane.lua b/reactor-plc/backplane.lua index 4092abe..1c03f24 100644 --- a/reactor-plc/backplane.lua +++ b/reactor-plc/backplane.lua @@ -109,7 +109,6 @@ function backplane.init(config, __shared_memory) end -- get the active NIC ----@return nic function backplane.active_nic() return _bp.act_nic end -- handle a backplane peripheral attach diff --git a/rtu/backplane.lua b/rtu/backplane.lua index c0637c7..258e9c5 100644 --- a/rtu/backplane.lua +++ b/rtu/backplane.lua @@ -95,11 +95,9 @@ function backplane.init(config, __shared_memory) end -- get the active NIC ----@return nic function backplane.active_nic() return _bp.act_nic end -- get the sounder interfaces ----@return rtu_speaker_sounder[] function backplane.sounders() return _bp.sounders end -- handle a backplane peripheral attach @@ -126,20 +124,23 @@ function backplane.attach(type, device, iface, print_no_fp) log.info("BKPLN: WIRED PHY_UP " .. iface) print_no_fp("wired comms modem reconnected") + databus.tx_hw_wd_modem(true) + if (_bp.act_nic ~= wd_nic) and not _bp.wlan_pref then -- switch back to preferred wired _bp.act_nic = wd_nic comms.switch_nic(_bp.act_nic) log.info("BKPLN: switched comms to wired modem (preferred)") - - databus.tx_hw_wd_modem(true) end elseif wl_nic and (not wl_nic.is_connected()) and m_is_wl then -- connect this as the wireless NIC wl_nic.connect(device) log.info("BKPLN: WIRELESS PHY_UP " .. iface) + print_no_fp("wireless comms modem reconnected") + + databus.tx_hw_wl_modem(true) if (_bp.act_nic ~= wl_nic) and _bp.wlan_pref then -- switch back to preferred wireless @@ -147,8 +148,6 @@ function backplane.attach(type, device, iface, print_no_fp) comms.switch_nic(_bp.act_nic) log.info("BKPLN: switched comms to wireless modem (preferred)") - - databus.tx_hw_wl_modem(true) end elseif wl_nic and m_is_wl then -- the wireless NIC already has a modem @@ -163,7 +162,7 @@ function backplane.attach(type, device, iface, print_no_fp) table.insert(_bp.sounders, rtu.init_sounder(device)) print_no_fp("a speaker was connected") - log.info(util.c("BKPLN: connected speaker ", iface)) + log.info("BKPLN: connected speaker " .. iface) databus.tx_hw_spkr_count(#_bp.sounders) end @@ -246,7 +245,7 @@ function backplane.detach(type, device, iface, print_no_fp) table.remove(_bp.sounders, i) print_no_fp("a speaker was disconnected") - log.warning(util.c("BKPLN: speaker ", iface, " disconnected")) + log.warning("BKPLN: speaker " .. iface .. " disconnected") databus.tx_hw_spkr_count(#_bp.sounders) break From 8c8d3faf7226f6bac78da6f5d57c085a0adc5223 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 7 Nov 2025 17:08:05 +0000 Subject: [PATCH 61/90] #634 moved most monitor disconnect handling to the backplane --- coordinator/backplane.lua | 35 ++++++++++++++++++++++++++++++++++- coordinator/renderer.lua | 30 ++++++++++-------------------- coordinator/threads.lua | 6 +----- 3 files changed, 45 insertions(+), 26 deletions(-) diff --git a/coordinator/backplane.lua b/coordinator/backplane.lua index e0deec8..4134b14 100644 --- a/coordinator/backplane.lua +++ b/coordinator/backplane.lua @@ -390,7 +390,40 @@ function backplane.detach(type, device, iface) end elseif type == "monitor" then ---@cast device Monitor - _bp.smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_DISCONNECT, device) + + local is_used = false + + log.info("BKPLN: MONITOR LINK_DOWN " .. iface) + + if _bp.displays.main == device then + is_used = true + + log.info("BKPLN: lost the main display") + iocontrol.fp_monitor_state("main", false) + elseif _bp.displays.flow == device then + is_used = true + + log.info("BKPLN: lost the flow display") + iocontrol.fp_monitor_state("flow", false) + else + for idx, monitor in pairs(_bp.displays.unit_displays) do + if monitor == device then + is_used = true + + log.info("BKPLN: lost the unit " .. idx .. " display") + iocontrol.fp_monitor_state(idx, false) + break + end + end + end + + -- notify renderer if it was using it + if is_used then + log_sys("lost a configured monitor") + _bp.smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_DISCONNECT, iface) + else + log_sys("lost an unused monitor") + end elseif type == "speaker" then ---@cast device Speaker log_sys("lost alarm sounder speaker") diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index c6bb3f1..f9dd9bd 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -267,53 +267,43 @@ function renderer.fp_ready() return engine.fp_ready end function renderer.ui_ready() return engine.ui_ready end -- handle a monitor peripheral being disconnected ----@param device Monitor monitor ----@return boolean is_used if the monitor is one of the configured monitors -function renderer.handle_disconnect(device) - local is_used = false - +---@param iface string monitor interface +function renderer.handle_disconnect(iface) if not engine.monitors then return false end - if engine.monitors.main == device then + if engine.monitors.main_iface == iface then if engine.ui.main_display ~= nil then -- delete element tree and clear root UI elements engine.ui.main_display.delete() + log_render("closed main view due to monitor disconnect") end - is_used = true engine.monitors.main = nil engine.ui.main_display = nil - - iocontrol.fp_monitor_state("main", false) - elseif engine.monitors.flow == device then + elseif engine.monitors.flow_iface == iface then if engine.ui.flow_display ~= nil then -- delete element tree and clear root UI elements engine.ui.flow_display.delete() + log_render("closed flow view due to monitor disconnect") end - is_used = true engine.monitors.flow = nil engine.ui.flow_display = nil - - iocontrol.fp_monitor_state("flow", false) else - for idx, monitor in pairs(engine.monitors.unit_displays) do - if monitor == device then + for idx, u_iface in pairs(engine.monitors.unit_ifaces) do + if u_iface == iface then if engine.ui.unit_displays[idx] ~= nil then + -- delete element tree and clear root UI elements engine.ui.unit_displays[idx].delete() + log_render("closed unit" .. idx .. "view due to monitor disconnect") end - is_used = true engine.monitors.unit_displays[idx] = nil engine.ui.unit_displays[idx] = nil - - iocontrol.fp_monitor_state(idx, false) break end end end - - return is_used end -- handle a monitor peripheral being reconnected diff --git a/coordinator/threads.lua b/coordinator/threads.lua index f80f28f..6e27b46 100644 --- a/coordinator/threads.lua +++ b/coordinator/threads.lua @@ -264,11 +264,7 @@ function threads.thread__render(smem) end elseif cmd.key == MQ__RENDER_DATA.MON_DISCONNECT then -- monitor disconnected - if renderer.handle_disconnect(cmd.val) then - log_sys("lost a configured monitor") - else - log_sys("lost an unused monitor") - end + renderer.handle_disconnect(cmd.val) elseif cmd.key == MQ__RENDER_DATA.MON_RESIZE then -- monitor resized local is_used, is_ok = renderer.handle_resize(cmd.val) From 25207f39c06e299e6e4b152c7592aef0e58c0b44 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 7 Nov 2025 17:43:12 +0000 Subject: [PATCH 62/90] #634 moved most monitor connect handling to the backplane and made display indicators on the front panel 3-state --- coordinator/backplane.lua | 61 ++++++++++++++++++++++----- coordinator/iocontrol.lua | 1 + coordinator/renderer.lua | 48 +++++---------------- coordinator/threads.lua | 6 +-- coordinator/ui/layout/front_panel.lua | 16 +++---- 5 files changed, 71 insertions(+), 61 deletions(-) diff --git a/coordinator/backplane.lua b/coordinator/backplane.lua index 4134b14..f0f7313 100644 --- a/coordinator/backplane.lua +++ b/coordinator/backplane.lua @@ -80,7 +80,7 @@ function backplane.init_displays(config) log.info("BKPLN: DISPLAY LINK_" .. util.trinary(disp, "UP", "DOWN") .. " MAIN/" .. iface) - iocontrol.fp_monitor_state("main", disp ~= nil) + iocontrol.fp_monitor_state("main", util.trinary(disp, 2, 1)) if not disp then return false, "Main monitor is not connected." @@ -103,7 +103,7 @@ function backplane.init_displays(config) log.info("BKPLN: DISPLAY LINK_" .. util.trinary(disp, "UP", "DOWN") .. " FLOW/" .. iface) - iocontrol.fp_monitor_state("flow", disp ~= nil) + iocontrol.fp_monitor_state("flow", util.trinary(disp, 2, 1)) if not disp then return false, "Flow monitor is not connected." @@ -127,7 +127,7 @@ function backplane.init_displays(config) log.info("BKPLN: DISPLAY LINK_" .. util.trinary(disp, "UP", "DOWN") .. " UNIT_" .. i .. "/" .. iface) - iocontrol.fp_monitor_state(i, disp ~= nil) + iocontrol.fp_monitor_state(i, util.trinary(disp, 2, 1)) if not disp then return false, "Unit " .. i .. " monitor is not connected." @@ -297,7 +297,46 @@ function backplane.attach(type, device, iface) end elseif type == "monitor" then ---@cast device Monitor - _bp.smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_CONNECT, { name = iface, device = device }) + + local is_used = false + + log.info("BKPLN: DISPLAY LINK_UP " .. iface) + + if _bp.displays.main_iface == iface then + is_used = true + + _bp.displays.main = device + + log.info("BKPLN: main display connected") + iocontrol.fp_monitor_state("main", 2) + elseif _bp.displays.flow_iface == iface then + is_used = true + + _bp.displays.flow = device + + log.info("BKPLN: flow display connected") + iocontrol.fp_monitor_state("flow", 2) + else + for idx, monitor in ipairs(_bp.displays.unit_ifaces) do + if monitor == iface then + is_used = true + + _bp.displays.unit_displays[idx] = device + + log.info("BKPLN: unit " .. idx .. " display connected") + iocontrol.fp_monitor_state(idx, 2) + break + end + end + end + + -- notify renderer if it is using it + if is_used then + log_sys(util.c("configured monitor ", iface, " connected")) + _bp.smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_CONNECT, iface) + else + log_sys(util.c("unused monitor ", iface, " connected")) + end elseif type == "speaker" then ---@cast device Speaker log_sys("alarm sounder speaker reconnected") @@ -393,25 +432,25 @@ function backplane.detach(type, device, iface) local is_used = false - log.info("BKPLN: MONITOR LINK_DOWN " .. iface) + log.info("BKPLN: DISPLAY LINK_DOWN " .. iface) if _bp.displays.main == device then is_used = true - log.info("BKPLN: lost the main display") - iocontrol.fp_monitor_state("main", false) + log.info("BKPLN: main display disconnected") + iocontrol.fp_monitor_state("main", 1) elseif _bp.displays.flow == device then is_used = true - log.info("BKPLN: lost the flow display") - iocontrol.fp_monitor_state("flow", false) + log.info("BKPLN: flow display disconnected") + iocontrol.fp_monitor_state("flow", 1) else for idx, monitor in pairs(_bp.displays.unit_displays) do if monitor == device then is_used = true - log.info("BKPLN: lost the unit " .. idx .. " display") - iocontrol.fp_monitor_state(idx, false) + log.info("BKPLN: unit " .. idx .. " display disconnected") + iocontrol.fp_monitor_state(idx, 1) break end end diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 305dff5..dd15b17 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -308,6 +308,7 @@ function iocontrol.fp_link_state(state) io.fp.ps.publish("link_state", state) en -- report monitor connection state ---@param id string|integer unit ID for unit monitor, "main" for main monitor, or "flow" for flow monitor +---@param connected 1|2|3 1 for disconnected, 2 for connected but no view (may not fit), 3 for connected with view shown function iocontrol.fp_monitor_state(id, connected) local name = nil diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index f9dd9bd..7d32a2c 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -308,39 +308,10 @@ end -- handle a monitor peripheral being reconnected ---@param name string monitor name ----@param device Monitor monitor ----@return boolean is_used if the monitor is one of the configured monitors -function renderer.handle_reconnect(name, device) - local is_used = false - - if not engine.monitors then return false end - +function renderer.handle_reconnect(name) -- note: handle_resize is a more adaptive way of re-initializing a connected monitor -- since it can handle a monitor being reconnected that isn't the right size - - if engine.monitors.main_iface == name then - is_used = true - engine.monitors.main = device - - renderer.handle_resize(name) - elseif engine.monitors.flow_iface == name then - is_used = true - engine.monitors.flow = device - - renderer.handle_resize(name) - else - for idx, monitor in ipairs(engine.monitors.unit_ifaces) do - if monitor == name then - is_used = true - engine.monitors.unit_displays[idx] = device - - renderer.handle_resize(name) - break - end - end - end - - return is_used + renderer.handle_resize(name) end -- handle a monitor being resized
@@ -372,7 +343,7 @@ function renderer.handle_resize(name) ui.main_display = nil end - iocontrol.fp_monitor_state("main", true) + iocontrol.fp_monitor_state("main", 2) engine.dmesg_window.setVisible(not engine.ui_ready) @@ -384,6 +355,8 @@ function renderer.handle_resize(name) end) if ok then + iocontrol.fp_monitor_state("main", 3) + log_render("main view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms") else if ui.main_display then @@ -393,7 +366,6 @@ function renderer.handle_resize(name) _print_too_small(device) - iocontrol.fp_monitor_state("main", false) is_ok = false end else engine.dmesg_window.redraw() end @@ -410,7 +382,7 @@ function renderer.handle_resize(name) ui.flow_display = nil end - iocontrol.fp_monitor_state("flow", true) + iocontrol.fp_monitor_state("flow", 2) if engine.ui_ready then local draw_start = util.time_ms() @@ -420,6 +392,8 @@ function renderer.handle_resize(name) end) if ok then + iocontrol.fp_monitor_state("flow", 3) + log_render("flow view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms") else if ui.flow_display then @@ -429,7 +403,6 @@ function renderer.handle_resize(name) _print_too_small(device) - iocontrol.fp_monitor_state("flow", false) is_ok = false end end @@ -448,7 +421,7 @@ function renderer.handle_resize(name) ui.unit_displays[idx] = nil end - iocontrol.fp_monitor_state(idx, true) + iocontrol.fp_monitor_state(idx, 2) if engine.ui_ready then local draw_start = util.time_ms() @@ -458,6 +431,8 @@ function renderer.handle_resize(name) end) if ok then + iocontrol.fp_monitor_state(idx, 3) + log_render("unit " .. idx .. " view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms") else if ui.unit_displays[idx] then @@ -467,7 +442,6 @@ function renderer.handle_resize(name) _print_too_small(device) - iocontrol.fp_monitor_state(idx, false) is_ok = false end end diff --git a/coordinator/threads.lua b/coordinator/threads.lua index 6e27b46..dc7f3f2 100644 --- a/coordinator/threads.lua +++ b/coordinator/threads.lua @@ -257,11 +257,7 @@ function threads.thread__render(smem) if cmd.key == MQ__RENDER_DATA.MON_CONNECT then -- monitor connected - if renderer.handle_reconnect(cmd.val.name, cmd.val.device) then - log_sys(util.c("configured monitor ", cmd.val.name, " reconnected")) - else - log_sys(util.c("unused monitor ", cmd.val.name, " connected")) - end + renderer.handle_reconnect(cmd.val) elseif cmd.key == MQ__RENDER_DATA.MON_DISCONNECT then -- monitor disconnected renderer.handle_disconnect(cmd.val) diff --git a/coordinator/ui/layout/front_panel.lua b/coordinator/ui/layout/front_panel.lua index 1b32501..5fcb7fc 100644 --- a/coordinator/ui/layout/front_panel.lua +++ b/coordinator/ui/layout/front_panel.lua @@ -114,19 +114,19 @@ local function init(panel, num_units) local comp_id = util.sprintf("(%d)", os.getComputerID()) TextBox{parent=system,x=9,y=4,width=6,text=comp_id,fg_bg=style.fp.disabled_fg} - local monitors = Div{parent=main_page,width=16,height=17,x=18,y=2} + local displays = Div{parent=main_page,width=16,height=17,x=18,y=2} - local main_monitor = LED{parent=monitors,label="MAIN MONITOR",colors=led_grn} - main_monitor.register(ps, "main_monitor", main_monitor.update) + local main_disp = LEDPair{parent=displays,label="MAIN DISPLAY",off=style.fp_ind_bkg,c1=colors.red,c2=colors.green} + main_disp.register(ps, "main_monitor", main_disp.update) - local flow_monitor = LED{parent=monitors,label="FLOW MONITOR",colors=led_grn} - flow_monitor.register(ps, "flow_monitor", flow_monitor.update) + local flow_disp = LEDPair{parent=displays,label="FLOW DISPLAY",off=style.fp_ind_bkg,c1=colors.red,c2=colors.green} + flow_disp.register(ps, "flow_monitor", flow_disp.update) - monitors.line_break() + displays.line_break() for i = 1, num_units do - local unit_monitor = LED{parent=monitors,label="UNIT "..i.." MONITOR",colors=led_grn} - unit_monitor.register(ps, "unit_monitor_" .. i, unit_monitor.update) + local unit_disp = LEDPair{parent=displays,label="UNIT "..i.." DISPLAY",off=style.fp_ind_bkg,c1=colors.red,c2=colors.green} + unit_disp.register(ps, "unit_monitor_" .. i, unit_disp.update) end -- From e0a0c34b545ef4d82b67b1261076338db98d9e51 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 7 Nov 2025 17:46:41 +0000 Subject: [PATCH 63/90] connected vs reconnected consistency --- coordinator/backplane.lua | 8 ++++---- reactor-plc/backplane.lua | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/coordinator/backplane.lua b/coordinator/backplane.lua index f0f7313..fa1208c 100644 --- a/coordinator/backplane.lua +++ b/coordinator/backplane.lua @@ -307,14 +307,14 @@ function backplane.attach(type, device, iface) _bp.displays.main = device - log.info("BKPLN: main display connected") + log.info("BKPLN: main display reconnected") iocontrol.fp_monitor_state("main", 2) elseif _bp.displays.flow_iface == iface then is_used = true _bp.displays.flow = device - log.info("BKPLN: flow display connected") + log.info("BKPLN: flow display reconnected") iocontrol.fp_monitor_state("flow", 2) else for idx, monitor in ipairs(_bp.displays.unit_ifaces) do @@ -323,7 +323,7 @@ function backplane.attach(type, device, iface) _bp.displays.unit_displays[idx] = device - log.info("BKPLN: unit " .. idx .. " display connected") + log.info("BKPLN: unit " .. idx .. " display reconnected") iocontrol.fp_monitor_state(idx, 2) break end @@ -332,7 +332,7 @@ function backplane.attach(type, device, iface) -- notify renderer if it is using it if is_used then - log_sys(util.c("configured monitor ", iface, " connected")) + log_sys(util.c("configured monitor ", iface, " reconnected")) _bp.smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_CONNECT, iface) else log_sys(util.c("unused monitor ", iface, " connected")) diff --git a/reactor-plc/backplane.lua b/reactor-plc/backplane.lua index 1c03f24..baebec3 100644 --- a/reactor-plc/backplane.lua +++ b/reactor-plc/backplane.lua @@ -132,8 +132,8 @@ function backplane.attach(iface, type, device, print_no_fp) dev.reactor = device state.no_reactor = false - print_no_fp("reactor reconnected") - log.info("BKPLN: reactor reconnected") + print_no_fp("reactor connected") + log.info("BKPLN: reactor connected") -- 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 @@ -166,7 +166,7 @@ function backplane.attach(iface, type, device, print_no_fp) wd_nic.connect(device) log.info("BKPLN: WIRED PHY_UP " .. iface) - print_no_fp("wired comms modem reconnected") + print_no_fp("wired comms modem connected") state.wd_modem = true @@ -182,7 +182,7 @@ function backplane.attach(iface, type, device, print_no_fp) wl_nic.connect(device) log.info("BKPLN: WIRELESS PHY_UP " .. iface) - print_no_fp("wireless comms modem reconnected") + print_no_fp("wireless comms modem connected") state.wl_modem = true From b9a9c018a103dfda8d0d769f87f9556b256081e4 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 7 Nov 2025 17:50:25 +0000 Subject: [PATCH 64/90] #634 RTU gateway backplane log message updates --- rtu/backplane.lua | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/rtu/backplane.lua b/rtu/backplane.lua index 258e9c5..1a384b0 100644 --- a/rtu/backplane.lua +++ b/rtu/backplane.lua @@ -84,9 +84,11 @@ function backplane.init(config, __shared_memory) for _, s in pairs(speakers) do local sounder = rtu.init_sounder(s) + log.info("BKPLN: SPEAKER LINK_UP " .. sounder.name) + table.insert(_bp.sounders, sounder) - log.debug(util.c("BKPLN: added speaker, attached as ", sounder.name)) + log.debug(util.c("BKPLN: added speaker sounder, attached as ", sounder.name)) end databus.tx_hw_spkr_count(#_bp.sounders) @@ -159,10 +161,13 @@ function backplane.attach(type, device, iface, print_no_fp) end elseif type == "speaker" then ---@cast device Speaker + + log.info("BKPLN: SPEAKER LINK_UP " .. iface) + table.insert(_bp.sounders, rtu.init_sounder(device)) print_no_fp("a speaker was connected") - log.info("BKPLN: connected speaker " .. iface) + log.info("BKPLN: setup speaker sounder for speaker " .. iface) databus.tx_hw_spkr_count(#_bp.sounders) end @@ -240,12 +245,15 @@ function backplane.detach(type, device, iface, print_no_fp) end elseif type == "speaker" then ---@cast device Speaker + + log.info("BKPLN: SPEAKER LINK_DOWN " .. iface) + for i = 1, #_bp.sounders do if _bp.sounders[i].speaker == device then table.remove(_bp.sounders, i) print_no_fp("a speaker was disconnected") - log.warning("BKPLN: speaker " .. iface .. " disconnected") + log.warning("BKPLN: speaker sounder " .. iface .. " disconnected") databus.tx_hw_spkr_count(#_bp.sounders) break From 55e4fed9d8e281ea031bd0c2d4cfbcddb3a58cf7 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 7 Nov 2025 18:51:42 +0000 Subject: [PATCH 65/90] luacheck fixes --- coordinator/coordinator.lua | 11 +++++------ coordinator/startup.lua | 3 ++- reactor-plc/plc.lua | 10 +++++----- rtu/rtu.lua | 10 +++++----- rtu/startup.lua | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 324486e..18d75ff 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -1,6 +1,5 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") -local ppm = require("scada-common.ppm") local util = require("scada-common.util") local types = require("scada-common.types") @@ -252,15 +251,15 @@ function coordinator.comms(version, nic, wl_nic, sv_watchdog) local public = {} -- switch the current active NIC - ---@param _nic nic - function public.switch_nic(_nic) + ---@param act_nic nic + function public.switch_nic(act_nic) nic.closeAll() -- configure receive channels - _nic.closeAll() - _nic.open(config.CRD_Channel) + act_nic.closeAll() + act_nic.open(config.CRD_Channel) - nic = _nic + nic = act_nic end -- try to connect to the supervisor if not already linked diff --git a/coordinator/startup.lua b/coordinator/startup.lua index b2dd23c..4c4233d 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -116,7 +116,8 @@ if not disp_ok then disp_ok, disp_err = backplane.init_displays(config) if not disp_ok then - println("monitor configuration invalid, please reconfigure") + println(disp_err) + println("please reconfigure") return end end diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index e388575..33eaf7b 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -833,15 +833,15 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) local public = {} -- switch the current active NIC - ---@param _nic nic - function public.switch_nic(_nic) + ---@param act_nic nic + function public.switch_nic(act_nic) nic.closeAll() -- configure receive channels - _nic.closeAll() - _nic.open(config.PLC_Channel) + act_nic.closeAll() + act_nic.open(config.PLC_Channel) - nic = _nic + nic = act_nic end -- reconnect a newly connected reactor diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 0a26693..379ef55 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -362,15 +362,15 @@ function rtu.comms(version, nic, conn_watchdog) local public = {} -- switch the current active NIC - ---@param _nic nic - function public.switch_nic(_nic) + ---@param act_nic nic + function public.switch_nic(act_nic) nic.closeAll() -- configure receive channels - _nic.closeAll() - _nic.open(config.RTU_Channel) + act_nic.closeAll() + act_nic.open(config.RTU_Channel) - nic = _nic + nic = act_nic end -- unlink from the server diff --git a/rtu/startup.lua b/rtu/startup.lua index 6338a1c..b6699ba 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -94,7 +94,7 @@ local function main() }, -- system objects - ---@class rtu_sys + ---@class rtu_sys rtu_sys = { rtu_comms = nil, ---@type rtu_comms conn_watchdog = nil, ---@type watchdog From 50dedaa7c8ff912116346309d68ebef146534859 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 7 Nov 2025 18:52:13 +0000 Subject: [PATCH 66/90] OS log tagging to match PLC --- coordinator/threads.lua | 14 +++++++------- rtu/threads.lua | 22 +++++++++++----------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/coordinator/threads.lua b/coordinator/threads.lua index dc7f3f2..86422db 100644 --- a/coordinator/threads.lua +++ b/coordinator/threads.lua @@ -34,7 +34,7 @@ function threads.thread__main(smem) -- execute thread function public.exec() iocontrol.fp_rt_status("main", true) - log.debug("main thread start") + log.debug("OS: main thread start") local loop_clock = util.new_clock(MAIN_CLOCK) @@ -143,10 +143,10 @@ function threads.thread__main(smem) -- check for termination request or UI crash if event == "terminate" or ppm.should_terminate() then crd_state.shutdown = true - log.info("terminate requested, main thread exiting") + log.info("OS: terminate requested, main thread exiting") elseif not crd_state.ui_ok then crd_state.shutdown = true - log.info("terminating due to fatal UI error") + log.info("OS: terminating due to fatal UI error") end if crd_state.shutdown then @@ -184,7 +184,7 @@ function threads.thread__main(smem) -- if status is true, then we are probably exiting, so this won't matter -- this thread cannot be slept because it will miss events (namely "terminate") if not crd_state.shutdown then - log.info("main thread restarting now...") + log.info("OS: main thread restarting now...") end end end @@ -202,7 +202,7 @@ function threads.thread__render(smem) -- execute thread function public.exec() iocontrol.fp_rt_status("render", true) - log.debug("render thread start") + log.debug("OS: render thread start") -- load in from shared memory local crd_state = smem.crd_state @@ -279,7 +279,7 @@ function threads.thread__render(smem) -- check for termination request if crd_state.shutdown then - log.info("render thread exiting") + log.info("OS: render thread exiting") break end @@ -301,7 +301,7 @@ function threads.thread__render(smem) iocontrol.fp_rt_status("render", false) if not crd_state.shutdown then - log.info("render thread restarting in 5 seconds...") + log.info("OS: render thread restarting in 5 seconds...") util.psleep(5) end end diff --git a/rtu/threads.lua b/rtu/threads.lua index 57fb288..b3e5d26 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -184,7 +184,7 @@ function threads.thread__main(smem) -- execute thread function public.exec() databus.tx_rt_status("main", true) - log.debug("main thread start") + log.debug("OS: main thread start") -- main loop clock local loop_clock = util.new_clock(MAIN_CLOCK) @@ -298,7 +298,7 @@ function threads.thread__main(smem) -- check for termination request if event == "terminate" or ppm.should_terminate() then rtu_state.shutdown = true - log.info("terminate requested, main thread exiting") + log.info("OS: terminate requested, main thread exiting") break end end @@ -317,7 +317,7 @@ function threads.thread__main(smem) databus.tx_rt_status("main", false) if not rtu_state.shutdown then - log.info("main thread restarting in 5 seconds...") + log.info("OS: main thread restarting in 5 seconds...") util.psleep(5) end end @@ -336,7 +336,7 @@ function threads.thread__comms(smem) -- execute thread function public.exec() databus.tx_rt_status("comms", true) - log.debug("comms thread start") + log.debug("OS: comms thread start") -- load in from shared memory local rtu_state = smem.rtu_state @@ -370,7 +370,7 @@ function threads.thread__comms(smem) -- max 100ms spent processing queue if util.time() - handle_start > 100 then - log.warning("comms thread exceeded 100ms queue process limit") + log.warning("OS: comms thread exceeded 100ms queue process limit") break end end @@ -381,7 +381,7 @@ function threads.thread__comms(smem) -- check for termination request if rtu_state.shutdown then rtu_comms.close(rtu_state) - log.info("comms thread exiting") + log.info("OS: comms thread exiting") break end @@ -403,7 +403,7 @@ function threads.thread__comms(smem) databus.tx_rt_status("comms", false) if not rtu_state.shutdown then - log.info("comms thread restarting in 5 seconds...") + log.info("OS: comms thread restarting in 5 seconds...") util.psleep(5) end end @@ -426,7 +426,7 @@ function threads.thread__unit_comms(smem, unit) -- execute thread function public.exec() databus.tx_rt_status("unit_" .. unit.uid, true) - log.debug(util.c("rtu unit thread start -> ", types.rtu_type_to_string(unit.type), " (", unit.name, ")")) + log.debug(util.c("OS: rtu unit thread start -> ", types.rtu_type_to_string(unit.type), " (", unit.name, ")")) -- load in from shared memory local rtu_state = smem.rtu_state @@ -443,7 +443,7 @@ function threads.thread__unit_comms(smem, unit) local short_name = util.c(types.rtu_type_to_string(unit.type), " (", unit.name, ")") if packet_queue == nil then - log.error("rtu unit thread created without a message queue, exiting...", true) + log.error("OS: rtu unit thread created without a message queue, exiting...", true) return end @@ -471,7 +471,7 @@ function threads.thread__unit_comms(smem, unit) -- check for termination request if rtu_state.shutdown then - log.info("rtu unit thread exiting -> " .. short_name) + log.info("OS: rtu unit thread exiting -> " .. short_name) break end @@ -536,7 +536,7 @@ function threads.thread__unit_comms(smem, unit) databus.tx_rt_status("unit_" .. unit.uid, false) if not rtu_state.shutdown then - log.info(util.c("rtu unit thread ", types.rtu_type_to_string(unit.type), " (", unit.name, ") restarting in 5 seconds...")) + log.info(util.c("OS: rtu unit thread ", types.rtu_type_to_string(unit.type), " (", unit.name, ") restarting in 5 seconds...")) util.psleep(5) end end From 2ecb662b0ae1665900485920ac7f7cd63c3052cc Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 7 Nov 2025 23:19:14 +0000 Subject: [PATCH 67/90] review fixes --- coordinator/configure.lua | 3 ++- coordinator/coordinator.lua | 8 ++++++-- coordinator/iocontrol.lua | 6 +++--- reactor-plc/configure.lua | 4 ++-- reactor-plc/databus.lua | 12 ++++++------ reactor-plc/startup.lua | 2 +- rtu/databus.lua | 22 +++++++++++----------- rtu/rtu.lua | 3 --- rtu/startup.lua | 2 +- supervisor/databus.lua | 16 ++++++++-------- supervisor/supervisor.lua | 10 ++++++---- 11 files changed, 46 insertions(+), 42 deletions(-) diff --git a/coordinator/configure.lua b/coordinator/configure.lua index d4c3bca..a6d63ca 100644 --- a/coordinator/configure.lua +++ b/coordinator/configure.lua @@ -36,7 +36,8 @@ local changes = { { "v1.2.12", { "Added main UI theme", "Added front panel UI theme", "Added color accessibility modes" } }, { "v1.3.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } }, { "v1.5.1", { "Added energy scale options" } }, - { "v1.6.13", { "Added option for Po/Pu pellet green/cyan pairing" } } + { "v1.6.13", { "Added option for Po/Pu pellet green/cyan pairing" } }, + { "v1.7.0", { "Added support for wired communications modems" } } } ---@class crd_configurator diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 18d75ff..2dc3468 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -193,7 +193,7 @@ function coordinator.comms(version, nic, wl_nic, sv_watchdog) apisessions.init(wl_nic, config) end - -- PRIVATE FUNCTIONS -- + --#region PRIVATE FUNCTIONS -- -- send a packet to the supervisor ---@param msg_type MGMT_TYPE|CRDN_TYPE @@ -245,7 +245,9 @@ function coordinator.comms(version, nic, wl_nic, sv_watchdog) _send_sv(PROTOCOL.SCADA_MGMT, MGMT_TYPE.KEEP_ALIVE, { srv_time, util.time() }) end - -- PUBLIC FUNCTIONS -- + --#endregion + + --#region PUBLIC FUNCTIONS -- ---@class coord_comms local public = {} @@ -734,6 +736,8 @@ function coordinator.comms(version, nic, wl_nic, sv_watchdog) ---@nodiscard function public.is_linked() return self.sv_linked end + --#endregion + return public end diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index dd15b17..0b93302 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -290,11 +290,11 @@ end -- toggle heartbeat indicator function iocontrol.heartbeat() io.fp.ps.toggle("heartbeat") end --- report presence of the wired modem +-- report presence of the wired comms modem ---@param has_modem boolean function iocontrol.fp_has_wd_modem(has_modem) io.fp.ps.publish("has_wd_modem", has_modem) end --- report presence of the wireless modem +-- report presence of the wireless comms modem ---@param has_modem boolean function iocontrol.fp_has_wl_modem(has_modem) io.fp.ps.publish("has_wl_modem", has_modem) end @@ -308,7 +308,7 @@ function iocontrol.fp_link_state(state) io.fp.ps.publish("link_state", state) en -- report monitor connection state ---@param id string|integer unit ID for unit monitor, "main" for main monitor, or "flow" for flow monitor ----@param connected 1|2|3 1 for disconnected, 2 for connected but no view (may not fit), 3 for connected with view shown +---@param connected 1|2|3 1 for disconnected, 2 for connected but no view (may not fit), 3 for connected with view rendered function iocontrol.fp_monitor_state(id, connected) local name = nil diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index 4eb728d..b80a866 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -3,7 +3,7 @@ -- local log = require("scada-common.log") -local ppm = require("scada-common.ppm") +local ppm = require("scada-common.ppm") local tcd = require("scada-common.tcd") local util = require("scada-common.util") @@ -35,7 +35,7 @@ local changes = { { "v1.6.15", { "Added front panel UI theme", "Added color accessibility modes" } }, { "v1.7.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } }, { "v1.8.21", { "Added option to invert emergency coolant redstone control" } }, - { "v1.9.1", { "Added support for wired communications modems" } } + { "v1.10.0", { "Added support for wired communications modems" } } } ---@class plc_configurator diff --git a/reactor-plc/databus.lua b/reactor-plc/databus.lua index 52f74ad..9975dac 100644 --- a/reactor-plc/databus.lua +++ b/reactor-plc/databus.lua @@ -33,7 +33,7 @@ function databus.rps_scram() dbus_iface.rps_scram() end -- transmit a command to the RPS to reset function databus.rps_reset() dbus_iface.rps_reset() end --- transmit firmware versions across the bus +-- transmit firmware versions ---@param plc_v string PLC version ---@param comms_v string comms version function databus.tx_versions(plc_v, comms_v) @@ -41,13 +41,13 @@ function databus.tx_versions(plc_v, comms_v) databus.ps.publish("comms_version", comms_v) end --- transmit unit ID across the bus +-- transmit unit ID ---@param id integer unit ID function databus.tx_id(id) databus.ps.publish("unit_id", id) end --- transmit hardware status across the bus +-- transmit hardware status ---@param plc_state plc_state function databus.tx_hw_status(plc_state) databus.ps.publish("degraded", plc_state.degraded) @@ -63,19 +63,19 @@ function databus.tx_rt_status(thread, ok) databus.ps.publish(util.c("routine__", thread), ok) end --- transmit supervisor link state across the bus +-- transmit supervisor link state ---@param state integer function databus.tx_link_state(state) databus.ps.publish("link_state", state) end --- transmit reactor enable state across the bus +-- transmit reactor enable state ---@param active any reactor active function databus.tx_reactor_state(active) databus.ps.publish("reactor_active", active == true) end --- transmit RPS data across the bus +-- transmit RPS data ---@param tripped boolean RPS tripped ---@param status boolean[] RPS status ---@param emer_cool_active boolean RPS activated the emergency coolant diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 12c1f16..92fbde2 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -19,7 +19,7 @@ local plc = require("reactor-plc.plc") local renderer = require("reactor-plc.renderer") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v1.9.1" +local R_PLC_VERSION = "v1.10.0" local println = util.println local println_ts = util.println_ts diff --git a/rtu/databus.lua b/rtu/databus.lua index aec06ae..ae11487 100644 --- a/rtu/databus.lua +++ b/rtu/databus.lua @@ -23,7 +23,7 @@ databus.RTU_HW_STATE = RTU_HW_STATE -- call to toggle heartbeat signal function databus.heartbeat() databus.ps.toggle("heartbeat") end --- transmit firmware versions across the bus +-- transmit firmware versions ---@param rtu_v string RTU version ---@param comms_v string comms version function databus.tx_versions(rtu_v, comms_v) @@ -31,32 +31,32 @@ function databus.tx_versions(rtu_v, comms_v) databus.ps.publish("comms_version", comms_v) end --- transmit hardware status for the wireless comms modem connection state ----@param has_modem boolean -function databus.tx_hw_wl_modem(has_modem) - databus.ps.publish("has_wl_modem", has_modem) -end - --- transmit hardware status for the wired comms modem connection state +-- transmit hardware status for the wired comms modem ---@param has_modem boolean function databus.tx_hw_wd_modem(has_modem) databus.ps.publish("has_wd_modem", has_modem) end +-- transmit hardware status for the wireless comms modem +---@param has_modem boolean +function databus.tx_hw_wl_modem(has_modem) + databus.ps.publish("has_wl_modem", has_modem) +end + -- transmit the number of speakers connected ---@param count integer function databus.tx_hw_spkr_count(count) databus.ps.publish("speaker_count", count) end --- transmit unit hardware type across the bus +-- transmit unit hardware type ---@param uid integer unit ID ---@param type RTU_UNIT_TYPE function databus.tx_unit_hw_type(uid, type) databus.ps.publish("unit_type_" .. uid, type) end --- transmit unit hardware status across the bus +-- transmit unit hardware status ---@param uid integer unit ID ---@param status RTU_HW_STATE function databus.tx_unit_hw_status(uid, status) @@ -70,7 +70,7 @@ function databus.tx_rt_status(thread, ok) databus.ps.publish(util.c("routine__", thread), ok) end --- transmit supervisor link state across the bus +-- transmit supervisor link state ---@param state integer function databus.tx_link_state(state) databus.ps.publish("link_state", state) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 379ef55..7baaed7 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -397,7 +397,6 @@ function rtu.comms(version, nic, conn_watchdog) s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable()) ----@diagnostic disable-next-line: need-check-nil nic.transmit(config.SVR_Channel, config.RTU_Channel, s_pkt) self.seq_num = self.seq_num + 1 end @@ -430,8 +429,6 @@ function rtu.comms(version, nic, conn_watchdog) ---@param distance integer ---@return modbus_frame|mgmt_frame|nil packet function public.parse_packet(side, sender, reply_to, message, distance) - -- unreachable if there isn't a nic ----@diagnostic disable-next-line: need-check-nil local s_pkt = nic.receive(side, sender, reply_to, message, distance) local pkt = nil diff --git a/rtu/startup.lua b/rtu/startup.lua index b6699ba..aed0932 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -118,7 +118,7 @@ local function main() -- modem and speaker initialization if not backplane.init(config, __shared_memory) then return end - log.debug("boot> running uinit()") + log.debug("startup> running uinit()") if uinit(config, __shared_memory) then -- start UI diff --git a/supervisor/databus.lua b/supervisor/databus.lua index 6841157..af90a2c 100644 --- a/supervisor/databus.lua +++ b/supervisor/databus.lua @@ -16,7 +16,7 @@ databus.ps = psil.create() -- call to toggle heartbeat signal function databus.heartbeat() databus.ps.toggle("heartbeat") end --- transmit firmware versions across the bus +-- transmit firmware versions ---@param sv_v string supervisor version ---@param comms_v string comms version function databus.tx_versions(sv_v, comms_v) @@ -24,18 +24,18 @@ function databus.tx_versions(sv_v, comms_v) databus.ps.publish("comms_version", comms_v) end --- transmit hardware status for the wireless comms modem connection state ----@param has_modem boolean -function databus.tx_hw_wl_modem(has_modem) - databus.ps.publish("has_wl_modem", has_modem) -end - --- transmit hardware status for the wired comms modem connection state +-- transmit hardware status for the wired comms modem ---@param has_modem boolean function databus.tx_hw_wd_modem(has_modem) databus.ps.publish("has_wd_modem", has_modem) end +-- transmit hardware status for the wireless comms modem +---@param has_modem boolean +function databus.tx_hw_wl_modem(has_modem) + databus.ps.publish("has_wl_modem", has_modem) +end + -- transmit PLC firmware version and session connection state ---@param reactor_id integer reactor unit ID ---@param fw string firmware version diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 88d7d25..f182578 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -104,11 +104,11 @@ function supervisor.load_config() cfv.assert((config.WiredModem == false) or (type(config.WiredModem) == "string")) cfv.assert((config.WirelessModem == true) or (type(config.WiredModem) == "string")) - cfv.assert_type_num(config.PLC_Listen) + cfv.assert_type_int(config.PLC_Listen) cfv.assert_range(config.PLC_Listen, 0, 2) - cfv.assert_type_num(config.RTU_Listen) + cfv.assert_type_int(config.RTU_Listen) cfv.assert_range(config.RTU_Listen, 0, 2) - cfv.assert_type_num(config.CRD_Listen) + cfv.assert_type_int(config.CRD_Listen) cfv.assert_range(config.CRD_Listen, 0, 2) cfv.assert_type_bool(config.PocketEnabled) @@ -398,6 +398,8 @@ function supervisor.comms(_version, fp_ok, facility) if nic then s_pkt = nic.receive(side, sender, reply_to, message, distance) + else + log.error("parse_packet(" .. side .. "): received a packet from an interface without a nic?") end if s_pkt then @@ -418,7 +420,7 @@ function supervisor.comms(_version, fp_ok, facility) local crdn_pkt = comms.crdn_packet() if crdn_pkt.decode(s_pkt) then pkt = crdn_pkt.get() end else - log.debug("receive[" .. side .. "] attempted parse of illegal packet type " .. s_pkt.protocol(), true) + log.debug("parse_packet(" .. side .. "): attempted parse of illegal packet type " .. s_pkt.protocol(), true) end end From 6774f606059454b99a214286dc70a0435c88d02e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Nov 2025 13:55:51 -0500 Subject: [PATCH 68/90] fixed coordinator bug with fp init, fixed incorrect comments --- coordinator/iocontrol.lua | 22 +++++++++++----------- coordinator/startup.lua | 4 ++-- reactor-plc/startup.lua | 2 +- rtu/startup.lua | 2 +- supervisor/startup.lua | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 0b93302..25f2616 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -34,18 +34,10 @@ local HIGH_RTT = 1500 -- 3.33x as long as expected w/ 0 ping local iocontrol = {} ---@class ioctl -local io = {} - --- initialize front panel PSIL ----@param firmware_v string coordinator version ----@param comms_v string comms version -function iocontrol.init_fp(firmware_v, comms_v) +local io = { ---@class ioctl_front_panel - io.fp = { ps = psil.create() } - - io.fp.ps.publish("version", firmware_v) - io.fp.ps.publish("comms_version", comms_v) -end + fp = { ps = psil.create() } +} -- initialize the coordinator IO controller ---@param conf facility_conf configuration @@ -290,6 +282,14 @@ end -- toggle heartbeat indicator function iocontrol.heartbeat() io.fp.ps.toggle("heartbeat") end +-- report versions to front panel +---@param firmware_v string coordinator version +---@param comms_v string comms version +function iocontrol.fp_versions(firmware_v, comms_v) + io.fp.ps.publish("version", firmware_v) + io.fp.ps.publish("comms_version", comms_v) +end + -- report presence of the wired comms modem ---@param has_modem boolean function iocontrol.fp_has_wd_modem(has_modem) io.fp.ps.publish("has_wd_modem", has_modem) end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 4c4233d..125609c 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -136,8 +136,8 @@ local function main() -- system startup ---------------------------------------- - -- report versions/init fp PSIL - iocontrol.init_fp(COORDINATOR_VERSION, comms.version) + -- report versions + iocontrol.fp_versions(COORDINATOR_VERSION, comms.version) -- init renderer renderer.configure(config) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 92fbde2..324cb68 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -67,7 +67,7 @@ local function main() -- startup ---------------------------------------- - -- record firmware versions and ID + -- report versions and ID databus.tx_versions(R_PLC_VERSION, comms.version) databus.tx_id(config.UnitID) diff --git a/rtu/startup.lua b/rtu/startup.lua index aed0932..322a9ae 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -69,7 +69,7 @@ local function main() -- startup ---------------------------------------- - -- record firmware versions and ID + -- report versions databus.tx_versions(RTU_VERSION, comms.version) -- mount connected devices diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 43d6003..1601f20 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -115,7 +115,7 @@ local function main() -- startup ---------------------------------------- - -- record firmware versions and ID + -- report versions databus.tx_versions(SUPERVISOR_VERSION, comms.version) -- mount connected devices From 138c10ad1f0647249a2b3b49578983dc5f99c42a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Nov 2025 13:56:37 -0500 Subject: [PATCH 69/90] #634 fixed monitor state indicators not always being updated --- coordinator/renderer.lua | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 7d32a2c..4722d81 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -191,6 +191,7 @@ function renderer.try_start_ui() if engine.monitors.main ~= nil then engine.ui.main_display = DisplayBox{window=engine.monitors.main,fg_bg=style.root} main_view(engine.ui.main_display) + iocontrol.fp_monitor_state("main", 3) util.nop() end @@ -198,6 +199,7 @@ function renderer.try_start_ui() if engine.monitors.flow ~= nil then engine.ui.flow_display = DisplayBox{window=engine.monitors.flow,fg_bg=style.root} flow_view(engine.ui.flow_display) + iocontrol.fp_monitor_state("flow", 3) util.nop() end @@ -205,6 +207,7 @@ function renderer.try_start_ui() for idx, display in pairs(engine.monitors.unit_displays) do engine.ui.unit_displays[idx] = DisplayBox{window=display,fg_bg=style.root} unit_view(engine.ui.unit_displays[idx], idx) + iocontrol.fp_monitor_state(idx, 3) util.nop() end end) @@ -231,9 +234,21 @@ function renderer.close_ui() end -- delete element trees - if engine.ui.main_display ~= nil then engine.ui.main_display.delete() end - if engine.ui.flow_display ~= nil then engine.ui.flow_display.delete() end - for _, display in pairs(engine.ui.unit_displays) do display.delete() end + + if engine.ui.main_display ~= nil then + engine.ui.main_display.delete() + iocontrol.fp_monitor_state("main", 2) + end + + if engine.ui.flow_display ~= nil then + engine.ui.flow_display.delete() + iocontrol.fp_monitor_state("flow", 2) + end + + for idx, display in pairs(engine.ui.unit_displays) do + display.delete() + iocontrol.fp_monitor_state(idx, 2) + end -- report ui as not ready engine.ui_ready = false From c0f9ba6ba607dbfabe1ee935f4390dae88debe4a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Nov 2025 14:00:07 -0500 Subject: [PATCH 70/90] #580 increased clarity of warning about connecting wired comms modem to peripherals --- coordinator/config/system.lua | 4 ++-- reactor-plc/config/system.lua | 4 ++-- rtu/config/system.lua | 4 ++-- supervisor/config/system.lua | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/coordinator/config/system.lua b/coordinator/config/system.lua index 7d3111d..b7d8d04 100644 --- a/coordinator/config/system.lua +++ b/coordinator/config/system.lua @@ -82,8 +82,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) local wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black)} TextBox{parent=net_c_1,x=24,y=3,text="(required for Pocket)",fg_bg=g_lg_fg_bg} local wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change} - TextBox{parent=net_c_1,x=3,y=6,text="MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)} - TextBox{parent=net_c_1,x=3,y=7,text="connecting to peripherals will cause problems",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_1,x=3,y=6,text="this one MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)} + TextBox{parent=net_c_1,x=3,y=7,text="connecting it to peripherals will cause issues",fg_bg=g_lg_fg_bg} local modem_list = ListBox{parent=net_c_1,x=1,y=8,height=5,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} local modem_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} diff --git a/reactor-plc/config/system.lua b/reactor-plc/config/system.lua index d92df51..5ca4085 100644 --- a/reactor-plc/config/system.lua +++ b/reactor-plc/config/system.lua @@ -209,8 +209,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) self.wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=en_dis_pref} self.wl_pref = Checkbox{parent=net_c_1,x=30,y=3,label="Prefer Wireless",default=ini_cfg.PreferWireless,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} self.wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change} - TextBox{parent=net_c_1,x=3,y=6,text="MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)} - TextBox{parent=net_c_1,x=3,y=7,text="connecting to peripherals will cause problems",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_1,x=3,y=6,text="this one MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)} + TextBox{parent=net_c_1,x=3,y=7,text="connecting it to peripherals will cause issues",fg_bg=g_lg_fg_bg} local modem_list = ListBox{parent=net_c_1,x=1,y=8,height=5,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} local modem_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} diff --git a/rtu/config/system.lua b/rtu/config/system.lua index 1e47791..e08220f 100644 --- a/rtu/config/system.lua +++ b/rtu/config/system.lua @@ -120,8 +120,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) self.wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=en_dis_pref} self.wl_pref = Checkbox{parent=net_c_1,x=30,y=3,label="Prefer Wireless",default=ini_cfg.PreferWireless,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} self.wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change} - TextBox{parent=net_c_1,x=3,y=6,text="MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)} - TextBox{parent=net_c_1,x=3,y=7,text="connecting to peripherals will cause problems",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_1,x=3,y=6,text="this one MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)} + TextBox{parent=net_c_1,x=3,y=7,text="connecting it to peripherals will cause issues",fg_bg=g_lg_fg_bg} local modem_list = ListBox{parent=net_c_1,x=1,y=8,height=5,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} local modem_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} diff --git a/supervisor/config/system.lua b/supervisor/config/system.lua index 608121c..43c8804 100644 --- a/supervisor/config/system.lua +++ b/supervisor/config/system.lua @@ -87,8 +87,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit local wireless = Checkbox{parent=net_c_1,x=1,y=3,label="Wireless/Ender Modem",default=ini_cfg.WirelessModem,box_fg_bg=cpair(colors.lightBlue,colors.black)} TextBox{parent=net_c_1,x=24,y=3,text="(required for Pocket)",fg_bg=g_lg_fg_bg} local wired = Checkbox{parent=net_c_1,x=1,y=5,label="Wired Modem",default=ini_cfg.WiredModem~=false,box_fg_bg=cpair(colors.lightBlue,colors.black),callback=on_wired_change} - TextBox{parent=net_c_1,x=3,y=6,text="MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)} - TextBox{parent=net_c_1,x=3,y=7,text="connecting to peripherals will cause problems",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_1,x=3,y=6,text="this one MUST ONLY connect to SCADA computers",fg_bg=cpair(colors.red,colors._INHERIT)} + TextBox{parent=net_c_1,x=3,y=7,text="connecting it to peripherals will cause issues",fg_bg=g_lg_fg_bg} local modem_list = ListBox{parent=net_c_1,x=1,y=8,height=5,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} local modem_err = TextBox{parent=net_c_1,x=8,y=14,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} From 699ba9f71e35e1282774d199ce45573accbf97fe Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Nov 2025 14:16:46 -0500 Subject: [PATCH 71/90] #580 coordinator front panel updates for wired modem and new labeling --- coordinator/renderer.lua | 8 ++-- coordinator/ui/layout/front_panel.lua | 59 +++++++++++++++------------ 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 4722d81..8f489b8 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -28,6 +28,7 @@ local renderer = {} -- render engine local engine = { + config = nil, ---@type crd_config color_mode = 1, ---@type COLOR_MODE monitors = nil, ---@type crd_displays|nil dmesg_window = nil, ---@type Window|nil @@ -76,10 +77,11 @@ end -- apply renderer configurations ---@param config crd_config function renderer.configure(config) - style.set_themes(config.MainTheme, config.FrontPanelTheme, config.ColorMode) - + engine.config = config engine.color_mode = config.ColorMode engine.disable_flow_view = config.DisableFlowView + + style.set_themes(config.MainTheme, config.FrontPanelTheme, config.ColorMode) end -- init all displays in use by the renderer @@ -130,7 +132,7 @@ function renderer.try_start_fp() -- show front panel view on terminal status, msg = pcall(function () engine.ui.front_panel = DisplayBox{window=term.current(),fg_bg=style.fp.root} - panel_view(engine.ui.front_panel, #engine.monitors.unit_displays) + panel_view(engine.ui.front_panel, engine.config) end) if status then diff --git a/coordinator/ui/layout/front_panel.lua b/coordinator/ui/layout/front_panel.lua index 5fcb7fc..9f0b765 100644 --- a/coordinator/ui/layout/front_panel.lua +++ b/coordinator/ui/layout/front_panel.lua @@ -17,6 +17,7 @@ local core = require("graphics.core") local Div = require("graphics.elements.Div") local ListBox = require("graphics.elements.ListBox") local MultiPane = require("graphics.elements.MultiPane") +local Rectangle = require("graphics.elements.Rectangle") local TextBox = require("graphics.elements.TextBox") local TabBar = require("graphics.elements.controls.TabBar") @@ -30,13 +31,16 @@ local LINK_STATE = types.PANEL_LINK_STATE local ALIGN = core.ALIGN local cpair = core.cpair +local border = core.border local led_grn = style.led_grn -- create new front panel view ---@param panel DisplayBox main displaybox ----@param num_units integer number of units (number of unit monitors) -local function init(panel, num_units) +---@param config crd_config configuration +local function init(panel, config) + local s_hi_box = style.fp_theme.highlight_box + local ps = iocontrol.get_db().fp.ps local term_w, term_h = term.getSize() @@ -60,7 +64,16 @@ local function init(panel, num_units) heartbeat.register(ps, "heartbeat", heartbeat.update) - local modem = LED{parent=system,label="MODEM",colors=led_grn} + if config.WirelessModem and config.WiredModem then + local wd_modem = LED{parent=system,label="WD MODEM",colors=led_grn} + local wl_modem = LED{parent=system,label="WL MODEM",colors=led_grn} + wd_modem.register(ps, "has_wd_modem", wd_modem.update) + wl_modem.register(ps, "has_wl_modem", wl_modem.update) + else + local modem = LED{parent=system,label="MODEM",colors=led_grn} + modem.register(ps, "has_modem", modem.update) + modem.register(ps, util.trinary(config.WirelessModem, "has_wl_modem", "has_wd_modem"), modem.update) + end if not style.colorblind then local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.yellow,colors.orange,style.fp_ind_bkg}} @@ -97,48 +110,44 @@ local function init(panel, num_units) system.line_break() - modem.register(ps, "has_modem", modem.update) - - local speaker = LED{parent=system,label="SPEAKER",colors=led_grn} - speaker.register(ps, "has_speaker", speaker.update) - - system.line_break() - local rt_main = LED{parent=system,label="RT MAIN",colors=led_grn} local rt_render = LED{parent=system,label="RT RENDER",colors=led_grn} rt_main.register(ps, "routine__main", rt_main.update) rt_render.register(ps, "routine__render", rt_render.update) ----@diagnostic disable-next-line: undefined-field - local comp_id = util.sprintf("(%d)", os.getComputerID()) - TextBox{parent=system,x=9,y=4,width=6,text=comp_id,fg_bg=style.fp.disabled_fg} + local hmi_devs = Div{parent=main_page,width=16,height=17,x=18,y=2} - local displays = Div{parent=main_page,width=16,height=17,x=18,y=2} + local speaker = LED{parent=hmi_devs,label="SPEAKER",colors=led_grn} + speaker.register(ps, "has_speaker", speaker.update) - local main_disp = LEDPair{parent=displays,label="MAIN DISPLAY",off=style.fp_ind_bkg,c1=colors.red,c2=colors.green} + hmi_devs.line_break() + + local main_disp = LEDPair{parent=hmi_devs,label="MAIN DISPLAY",off=style.fp_ind_bkg,c1=colors.red,c2=colors.green} main_disp.register(ps, "main_monitor", main_disp.update) - local flow_disp = LEDPair{parent=displays,label="FLOW DISPLAY",off=style.fp_ind_bkg,c1=colors.red,c2=colors.green} + local flow_disp = LEDPair{parent=hmi_devs,label="FLOW DISPLAY",off=style.fp_ind_bkg,c1=colors.red,c2=colors.green} flow_disp.register(ps, "flow_monitor", flow_disp.update) - displays.line_break() + hmi_devs.line_break() - for i = 1, num_units do - local unit_disp = LEDPair{parent=displays,label="UNIT "..i.." DISPLAY",off=style.fp_ind_bkg,c1=colors.red,c2=colors.green} + for i = 1, config.UnitCount do + local unit_disp = LEDPair{parent=hmi_devs,label="UNIT "..i.." DISPLAY",off=style.fp_ind_bkg,c1=colors.red,c2=colors.green} unit_disp.register(ps, "unit_monitor_" .. i, unit_disp.update) end -- - -- about footer + -- hardware labeling -- - local about = Div{parent=main_page,width=15,height=2,y=term_h-3,fg_bg=style.fp.disabled_fg} - local fw_v = TextBox{parent=about,text="FW: v00.00.00"} - local comms_v = TextBox{parent=about,text="NT: v00.00.00"} + local hw_labels = Rectangle{parent=main_page,y=term_h-7,width=15,height=5,border=border(1,s_hi_box.bkg,true),even_inner=true} - fw_v.register(ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end) - comms_v.register(ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end) +---@diagnostic disable-next-line: undefined-field + local comp_id = util.sprintf("%03d", os.getComputerID()) + + TextBox{parent=hw_labels,text="FW "..ps.get("version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="NT v"..ps.get("comms_version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="S/N CRD-"..comp_id,fg_bg=s_hi_box} -- -- page handling From 504dce64c2cb9582634d995811f354080dc9921d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Nov 2025 15:20:28 -0500 Subject: [PATCH 72/90] #580 review changes/fixes --- coordinator/backplane.lua | 15 ++++++---- coordinator/coordinator.lua | 14 +-------- coordinator/ui/layout/front_panel.lua | 1 - reactor-plc/backplane.lua | 41 ++++++++++++++++++--------- reactor-plc/plc.lua | 14 +-------- rtu/backplane.lua | 16 +++++++---- rtu/rtu.lua | 14 +-------- supervisor/backplane.lua | 39 +++++++++++++------------ 8 files changed, 71 insertions(+), 83 deletions(-) diff --git a/coordinator/backplane.lua b/coordinator/backplane.lua index fa1208c..cc773db 100644 --- a/coordinator/backplane.lua +++ b/coordinator/backplane.lua @@ -158,16 +158,18 @@ function backplane.init(config, __shared_memory) -- Modem Init -- init wired NIC - if type(config.WiredModem) == "string" then + if type(_bp.lan_iface) == "string" then local modem = ppm.get_modem(_bp.lan_iface) local wd_nic = network.nic(modem) log.info("BKPLN: WIRED PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. _bp.lan_iface) log_comms("wired comms modem " .. util.trinary(modem, "connected", "not found")) - -- set this as active for now - _bp.act_nic = wd_nic _bp.wd_nic = wd_nic + _bp.act_nic = wd_nic -- set this as active for now + + wd_nic.closeAll() + wd_nic.open(config.CRD_Channel) iocontrol.fp_has_wd_modem(modem ~= nil) end @@ -339,11 +341,13 @@ function backplane.attach(type, device, iface) end elseif type == "speaker" then ---@cast device Speaker - log_sys("alarm sounder speaker reconnected") + log.info("BKPLN: SPEAKER LINK_UP " .. iface) sounder.reconnect(device) + log_sys("alarm sounder speaker reconnected") + iocontrol.fp_has_speaker(true) end end @@ -465,10 +469,11 @@ function backplane.detach(type, device, iface) end elseif type == "speaker" then ---@cast device Speaker - log_sys("lost alarm sounder speaker") log.info("BKPLN: SPEAKER LINK_DOWN " .. iface) + log_sys("alarm sounder speaker disconnected") + iocontrol.fp_has_speaker(false) end end diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 2dc3468..fa065ce 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -184,10 +184,6 @@ function coordinator.comms(version, nic, wl_nic, sv_watchdog) comms.set_trusted_range(config.TrustedRange) end - -- configure network channels - nic.closeAll() - nic.open(config.CRD_Channel) - -- pass config to apisessions if config.API_Enabled and wl_nic then apisessions.init(wl_nic, config) @@ -254,15 +250,7 @@ function coordinator.comms(version, nic, wl_nic, sv_watchdog) -- switch the current active NIC ---@param act_nic nic - function public.switch_nic(act_nic) - nic.closeAll() - - -- configure receive channels - act_nic.closeAll() - act_nic.open(config.CRD_Channel) - - nic = act_nic - end + function public.switch_nic(act_nic) nic = act_nic end -- try to connect to the supervisor if not already linked ---@param abort boolean? true to print out cancel info if not linked (use on program terminate) diff --git a/coordinator/ui/layout/front_panel.lua b/coordinator/ui/layout/front_panel.lua index 9f0b765..78b034f 100644 --- a/coordinator/ui/layout/front_panel.lua +++ b/coordinator/ui/layout/front_panel.lua @@ -71,7 +71,6 @@ local function init(panel, config) wl_modem.register(ps, "has_wl_modem", wl_modem.update) else local modem = LED{parent=system,label="MODEM",colors=led_grn} - modem.register(ps, "has_modem", modem.update) modem.register(ps, util.trinary(config.WirelessModem, "has_wl_modem", "has_wd_modem"), modem.update) end diff --git a/reactor-plc/backplane.lua b/reactor-plc/backplane.lua index baebec3..8216dd4 100644 --- a/reactor-plc/backplane.lua +++ b/reactor-plc/backplane.lua @@ -39,17 +39,19 @@ function backplane.init(config, __shared_memory) if _bp.smem.networked then -- init wired NIC - if type(config.WiredModem) == "string" then + if type(_bp.lan_iface) == "string" then local modem = ppm.get_modem(_bp.lan_iface) local wd_nic = network.nic(modem) log.info("BKPLN: WIRED PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. _bp.lan_iface) - plc_state.wd_modem = wd_nic.is_connected() - - -- set this as active for now - _bp.act_nic = wd_nic _bp.wd_nic = wd_nic + _bp.act_nic = wd_nic -- set this as active for now + + wd_nic.closeAll() + wd_nic.open(config.PLC_Channel) + + plc_state.wd_modem = wd_nic.is_connected() end -- init wireless NIC(s) @@ -59,14 +61,17 @@ function backplane.init(config, __shared_memory) log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. iface) - plc_state.wl_modem = 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.act_nic = wl_nic end _bp.wl_nic = wl_nic + + wl_nic.closeAll() + wl_nic.open(config.PLC_Channel) + + plc_state.wl_modem = wl_nic.is_connected() end -- comms modem is required if networked @@ -86,6 +91,8 @@ function backplane.init(config, __shared_memory) -- we need a reactor, can at least do some things even if it isn't formed though if plc_state.no_reactor then + log.info("BKPLN: REACTOR LINK_DOWN") + println("startup> fission reactor not found") log.warning("BKPLN: no reactor on startup") @@ -97,14 +104,16 @@ function backplane.init(config, __shared_memory) 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") + log.info("BKPLN: REACTOR LINK_UP " .. ppm.get_iface(plc_dev.reactor)) + + if not plc_dev.reactor.isFormed() then + println("startup> fission reactor is not formed") + log.warning("BKPLN: reactor logic adapter detected, but reactor is not formed") + + plc_state.degraded = true + plc_state.reactor_formed = false + end end end @@ -129,6 +138,8 @@ function backplane.attach(iface, type, device, print_no_fp) if type ~= nil and device ~= nil then if state.no_reactor and (type == "fissionReactorLogicAdapter") then -- reconnected reactor + log.info("BKPLN: REACTOR LINK_UP " .. iface) + dev.reactor = device state.no_reactor = false @@ -225,6 +236,8 @@ function backplane.detach(iface, type, device, print_no_fp) local sys = _bp.smem.plc_sys if device == dev.reactor then + log.info("BKPLN: REACTOR LINK_DOWN " .. iface) + print_no_fp("reactor disconnected") log.warning("BKPLN: reactor disconnected") diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 33eaf7b..a630570 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -563,10 +563,6 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) comms.set_trusted_range(config.TrustedRange) end - -- configure network channels - nic.closeAll() - nic.open(config.PLC_Channel) - --#region PRIVATE FUNCTIONS -- -- send an RPLC packet @@ -834,15 +830,7 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) -- switch the current active NIC ---@param act_nic nic - function public.switch_nic(act_nic) - nic.closeAll() - - -- configure receive channels - act_nic.closeAll() - act_nic.open(config.PLC_Channel) - - nic = act_nic - end + function public.switch_nic(act_nic) nic = act_nic end -- reconnect a newly connected reactor ---@param new_reactor table diff --git a/rtu/backplane.lua b/rtu/backplane.lua index 1a384b0..dc30b7a 100644 --- a/rtu/backplane.lua +++ b/rtu/backplane.lua @@ -40,15 +40,17 @@ function backplane.init(config, __shared_memory) -- Modem Init -- init wired NIC - if type(config.WiredModem) == "string" then + if type(_bp.lan_iface) == "string" then local modem = ppm.get_modem(_bp.lan_iface) local 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.act_nic = wd_nic _bp.wd_nic = wd_nic + _bp.act_nic = wd_nic -- set this as active for now + + wd_nic.closeAll() + wd_nic.open(config.RTU_Channel) databus.tx_hw_wd_modem(modem ~= nil) end @@ -67,6 +69,9 @@ function backplane.init(config, __shared_memory) _bp.wl_nic = wl_nic + wl_nic.closeAll() + wl_nic.open(config.RTU_Channel) + databus.tx_hw_wl_modem(modem ~= nil) end @@ -82,10 +87,9 @@ function backplane.init(config, __shared_memory) -- find and setup all speakers local speakers = ppm.get_all_devices("speaker") for _, s in pairs(speakers) do + log.info("BKPLN: SPEAKER LINK_UP " .. ppm.get_iface(s)) + local sounder = rtu.init_sounder(s) - - log.info("BKPLN: SPEAKER LINK_UP " .. sounder.name) - table.insert(_bp.sounders, sounder) log.debug(util.c("BKPLN: added speaker sounder, attached as ", sounder.name)) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 7baaed7..2c4c9d4 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -310,10 +310,6 @@ function rtu.comms(version, nic, conn_watchdog) comms.set_trusted_range(config.TrustedRange) end - -- configure network channels - nic.closeAll() - nic.open(config.RTU_Channel) - --#region PRIVATE FUNCTIONS -- -- send a scada management packet @@ -363,15 +359,7 @@ function rtu.comms(version, nic, conn_watchdog) -- switch the current active NIC ---@param act_nic nic - function public.switch_nic(act_nic) - nic.closeAll() - - -- configure receive channels - act_nic.closeAll() - act_nic.open(config.RTU_Channel) - - nic = act_nic - end + function public.switch_nic(act_nic) nic = act_nic end -- unlink from the server ---@param rtu_state rtu_state diff --git a/supervisor/backplane.lua b/supervisor/backplane.lua index 69fcd94..7a37b34 100644 --- a/supervisor/backplane.lua +++ b/supervisor/backplane.lua @@ -23,31 +23,33 @@ local _bp = { nic_map = {} ---@type nic[] connected nics } +-- network interfaces indexed by peripheral names backplane.nics = _bp.nic_map -- initialize the system peripheral backplane ---@param config svr_config ---@return boolean success function backplane.init(config) - -- setup the wired modem, if configured - if type(config.WiredModem) == "string" then - _bp.lan_iface = config.WiredModem + _bp.lan_iface = config.WiredModem + + -- setup the wired modem, if configured + if type(_bp.lan_iface) == "string" then + local modem = ppm.get_modem(_bp.lan_iface) + local wd_nic = network.nic(modem) + + log.info("BKPLN: WIRED PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. _bp.lan_iface) - local modem = ppm.get_modem(_bp.lan_iface) if not (modem and _bp.lan_iface) then println("startup> wired comms modem not found") log.fatal("BKPLN: no wired comms modem on startup") return false end - local nic = network.nic(modem) - _bp.wd_nic = nic - _bp.nic_map[_bp.lan_iface] = nic + _bp.wd_nic = wd_nic + _bp.nic_map[_bp.lan_iface] = wd_nic - log.info("BKPLN: WIRED PHY_UP " .. _bp.lan_iface) - - nic.closeAll() - nic.open(config.SVR_Channel) + wd_nic.closeAll() + wd_nic.open(config.SVR_Channel) databus.tx_hw_wd_modem(true) end @@ -55,20 +57,21 @@ function backplane.init(config) -- setup the wireless modem, if configured if config.WirelessModem then local modem, iface = ppm.get_wireless_modem() + local wl_nic = network.nic(modem) + + log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. iface) + if not (modem and iface) then println("startup> wireless comms modem not found") log.fatal("BKPLN: no wireless comms modem on startup") return false end - local nic = network.nic(modem) - _bp.wl_nic = nic - _bp.nic_map[iface] = nic + _bp.wl_nic = wl_nic + _bp.nic_map[iface] = wl_nic - log.info("BKPLN: WIRELESS PHY_UP " .. iface) - - nic.closeAll() - nic.open(config.SVR_Channel) + wl_nic.closeAll() + wl_nic.open(config.SVR_Channel) databus.tx_hw_wl_modem(true) end From 569358a4e15cfefaa8cb8882c8f6010c0d43b8f3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Nov 2025 17:20:24 -0500 Subject: [PATCH 73/90] #642 updated reactor PLC connection test --- scada-common/comms.lua | 5 ++++- scada-common/types.lua | 2 +- scada-common/util.lua | 2 +- supervisor/session/svsessions.lua | 6 +++++- supervisor/supervisor.lua | 12 ++++++++---- 5 files changed, 19 insertions(+), 8 deletions(-) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index e3e7403..716ea1b 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -17,7 +17,7 @@ local max_distance = nil local comms = {} -- protocol/data versions (protocol/data independent changes tracked by util.lua version) -comms.version = "3.0.9" +comms.version = "3.1.0" comms.api_version = "0.0.10" ---@enum PROTOCOL @@ -147,6 +147,9 @@ comms.FAC_COMMAND = FAC_COMMAND -- destination broadcast address (to all devices) comms.BROADCAST = -1 +-- firmware version used to indicate an establish packet is a connection test +comms.CONN_TEST_FWV = "CONN_TEST" + ---@alias packet scada_packet|modbus_packet|rplc_packet|mgmt_packet|crdn_packet ---@alias frame modbus_frame|rplc_frame|mgmt_frame|crdn_frame diff --git a/scada-common/types.lua b/scada-common/types.lua index 32bf265..bb7c4a4 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -568,7 +568,7 @@ types.ALARM_STATE_NAMES = { ---| "websocket_failure" ---| "websocket_message" ---| "websocket_success" ----| "clock_start" (custom) +---| "conn_test_complete" (custom) ---@alias fluid ---| "mekanism:empty_gas" diff --git a/scada-common/util.lua b/scada-common/util.lua index fe661be..a5d4830 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -24,7 +24,7 @@ local t_pack = table.pack local util = {} -- scada-common version -util.version = "1.5.6" +util.version = "1.6.0" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 402ea58..d57b7bf 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -2,6 +2,7 @@ -- Supervisor Sessions Handler -- +local comms = require("scada-common.comms") local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") local types = require("scada-common.types") @@ -465,9 +466,12 @@ end ---@param i_seq_num integer initial (most recent) sequence number ---@param for_reactor integer unit ID ---@param version string PLC version ----@return integer|false session_id +---@return integer|boolean session_id session ID, false if unit is already connected, and true if this is a successful connection test function svsessions.establish_plc_session(nic, source_addr, i_seq_num, for_reactor, version) if svsessions.get_reactor_session(for_reactor) == nil and for_reactor >= 1 and for_reactor <= self.config.UnitCount then + -- don't actually establish this if it is a connection test + if version == comms.CONN_TEST_FWV then return true end + ---@class plc_session_struct local plc_s = { s_type = "plc", diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index f182578..a90693d 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -218,7 +218,7 @@ function supervisor.comms(_version, fp_ok, facility) -- drop if not listening elseif comms_v ~= comms.version then if last_ack ~= ESTABLISH_ACK.BAD_VERSION then - log.info(util.c("dropping PLC establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) + log.info(util.c("PLC_ESTABLISH: PLC [@", src_addr, "] dropping PLC establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) end _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) @@ -231,7 +231,7 @@ function supervisor.comms(_version, fp_ok, facility) if reactor_id < 1 or reactor_id > config.UnitCount then -- reactor index out of range if last_ack ~= ESTABLISH_ACK.DENY then - log.warning(util.c("PLC_ESTABLISH: denied assignment ", reactor_id, " outside of configured unit count ", config.UnitCount)) + log.warning(util.c("PLC_ESTABLISH: PLC [@", src_addr, "] denied assignment ", reactor_id, " outside of configured unit count ", config.UnitCount)) end _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) @@ -242,14 +242,18 @@ function supervisor.comms(_version, fp_ok, facility) if plc_id == false then -- reactor already has a PLC assigned if last_ack ~= ESTABLISH_ACK.COLLISION then - log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) + log.warning(util.c("PLC_ESTABLISH: PLC [@", src_addr, "] assignment collision with reactor ", reactor_id)) end _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.COLLISION) + elseif plc_id == true then + -- valid, but this was just a test + log.info(util.c("PLC_ESTABLISH: PLC [@", src_addr, "] sending connection test success response on ", nic.phy_name())) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) else -- got an ID; assigned to a reactor successfully println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected")) - log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id, " on ", nic.phy_name())) + log.info(util.c("PLC_ESTABLISH: PLC [@", src_addr, "] (", firmware_v, ") reactor unit ", reactor_id, " PLC connected with session ID ", plc_id, " on ", nic.phy_name())) _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) end end From cc36aafccdc6e834fcebcf4bf3c539abb4e7c35f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Nov 2025 17:20:51 -0500 Subject: [PATCH 74/90] #580 supervisor config check fix --- supervisor/supervisor.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index a90693d..96d2c98 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -105,11 +105,11 @@ function supervisor.load_config() cfv.assert((config.WirelessModem == true) or (type(config.WiredModem) == "string")) cfv.assert_type_int(config.PLC_Listen) - cfv.assert_range(config.PLC_Listen, 0, 2) + cfv.assert_range(config.PLC_Listen, 1, 3) cfv.assert_type_int(config.RTU_Listen) - cfv.assert_range(config.RTU_Listen, 0, 2) + cfv.assert_range(config.RTU_Listen, 1, 3) cfv.assert_type_int(config.CRD_Listen) - cfv.assert_range(config.CRD_Listen, 0, 2) + cfv.assert_range(config.CRD_Listen, 1, 3) cfv.assert_type_bool(config.PocketEnabled) cfv.assert_type_bool(config.PocketTest) From 6dea501946fdc01308c9852454ca1ed3c9231530 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Nov 2025 17:27:10 -0500 Subject: [PATCH 75/90] #642 reactor PLC self-check updates --- reactor-plc/config/check.lua | 122 ++++++++++++++++++++++++++--------- reactor-plc/configure.lua | 2 + supervisor/supervisor.lua | 2 +- 3 files changed, 93 insertions(+), 33 deletions(-) diff --git a/reactor-plc/config/check.lua b/reactor-plc/config/check.lua index 91b71e6..2a06a54 100644 --- a/reactor-plc/config/check.lua +++ b/reactor-plc/config/check.lua @@ -24,10 +24,12 @@ local ESTABLISH_ACK = comms.ESTABLISH_ACK local MGMT_TYPE = comms.MGMT_TYPE local self = { + checking_wl = true, + wd_modem = nil, ---@type Modem|nil + wl_modem = nil, ---@type Modem|nil + nic = nil, ---@type nic net_listen = false, - sv_addr = comms.BROADCAST, - sv_seq_num = util.time_ms() * 10, self_check_pass = true, @@ -48,7 +50,7 @@ local function check_complete() TextBox{parent=more,text="- ask for help on GitHub discussions or Discord"} end --- send a management packet to the supervisor +-- send a management packet to the supervisor (one-time broadcast) ---@param msg_type MGMT_TYPE ---@param msg table local function send_sv(msg_type, msg) @@ -56,10 +58,9 @@ local function send_sv(msg_type, msg) local pkt = comms.mgmt_packet() pkt.make(msg_type, msg) - s_pkt.make(self.sv_addr, self.sv_seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable()) + s_pkt.make(comms.BROADCAST, util.time_ms() * 10, PROTOCOL.SCADA_MGMT, pkt.raw_sendable()) self.nic.transmit(self.settings.SVR_Channel, self.settings.PLC_Channel, s_pkt) - self.sv_seq_num = self.sv_seq_num + 1 end -- handle an establish message from the supervisor @@ -75,10 +76,7 @@ local function handle_packet(packet) local est_ack = packet.data[1] if est_ack== ESTABLISH_ACK.ALLOW then - self.self_check_msg(nil, true, "") - self.sv_addr = packet.scada_frame.src_addr() - send_sv(MGMT_TYPE.CLOSE, {}) - if self.self_check_pass then check_complete() end + -- success elseif est_ack == ESTABLISH_ACK.DENY then error_msg = "error: supervisor connection denied" elseif est_ack == ESTABLISH_ACK.COLLISION then @@ -97,18 +95,20 @@ local function handle_packet(packet) end self.net_listen = false - self.run_test_btn.enable() if error_msg then self.self_check_msg(nil, false, error_msg) + else + self.self_check_msg(nil, true, "") end + + util.push_event("conn_test_complete", error_msg == nil) end -- handle supervisor connection failure local function handle_timeout() self.net_listen = false - self.run_test_btn.enable() - self.self_check_msg(nil, false, "make sure your supervisor is running, your channels are correct, trusted ranges are set properly (if enabled), facility keys match (if set), and if you are using wireless modems rather than ender modems, that your devices are close together in the same dimension") + util.push_event("conn_test_complete", false) end -- execute the self-check @@ -121,12 +121,20 @@ local function self_check() self.self_check_pass = true local cfg = self.settings - local modem = ppm.get_wireless_modem() + self.wd_modem = ppm.get_modem(cfg.WiredModem) + self.wl_modem = ppm.get_wireless_modem() local reactor = ppm.get_fission_reactor() local valid_cfg = plc.validate_config(cfg) + -- check for comms modems if cfg.Networked then - self.self_check_msg("> check wireless/ender modem connected...", modem ~= nil, "you must connect an ender or wireless modem to the reactor PLC") + if cfg.WiredModem then + self.self_check_msg("> check wired comms modem connected...", self.wd_modem, "please connect the wired comms modem " .. cfg.WiredModem) + end + + if cfg.WirelessModem then + self.self_check_msg("> check wireless/ender modem connected...", self.wl_modem, "please connect an ender or wireless modem for wireless comms") + end end self.self_check_msg("> check fission reactor connected...", reactor ~= nil, "please connect the reactor PLC to the reactor's fission reactor logic adapter") @@ -136,27 +144,37 @@ local function self_check() self.self_check_msg("> check configuration...", valid_cfg, "go through Configure System and apply settings to set any missing settings and repair any corrupted ones") - if cfg.Networked and valid_cfg and modem then - self.self_check_msg("> check supervisor connection...") + if cfg.Networked and valid_cfg then + self.checking_wl = true - -- init mac as needed - if cfg.AuthKey and string.len(cfg.AuthKey) >= 8 then - network.init_mac(cfg.AuthKey) + if cfg.WirelessModem and self.wl_modem then + self.self_check_msg("> check wireless supervisor connection...") + + -- init mac as needed + if cfg.AuthKey and string.len(cfg.AuthKey) >= 8 then + network.init_mac(cfg.AuthKey) + else + network.deinit_mac() + end + + comms.set_trusted_range(cfg.TrustedRange) + + self.nic = network.nic(self.wl_modem) + + self.nic.closeAll() + self.nic.open(cfg.PLC_Channel) + + self.net_listen = true + + send_sv(MGMT_TYPE.ESTABLISH, { comms.version, comms.CONN_TEST_FWV, DEVICE_TYPE.PLC, cfg.UnitID }) + + tcd.dispatch_unique(8, handle_timeout) + elseif cfg.WiredModem and self.wd_modem then + -- skip to wired + util.push_event("conn_test_complete", true) else - network.deinit_mac() + self.self_check_msg("> no modem, can't test supervisor connection", false) end - - self.nic = network.nic(modem) - - self.nic.closeAll() - self.nic.open(cfg.PLC_Channel) - - self.sv_addr = comms.BROADCAST - self.net_listen = true - - send_sv(MGMT_TYPE.ESTABLISH, { comms.version, "0.0.0", DEVICE_TYPE.PLC, cfg.UnitID }) - - tcd.dispatch_unique(8, handle_timeout) else if self.self_check_pass then check_complete() end self.run_test_btn.enable() @@ -240,4 +258,44 @@ function check.receive_sv(side, sender, reply_to, message, distance) end end +-- handle completed connection tests +---@param pass boolean +function check.conn_test_callback(pass) + local cfg = self.settings + + if self.checking_wl then + if not pass then + self.self_check_msg(nil, false, "make sure your supervisor is running, listening on the wireless interface, your channels are correct, trusted ranges are set properly (if enabled), facility keys match (if set), and if you are using wireless modems rather than ender modems, that your devices are close together in the same dimension") + end + + if cfg.WiredModem and self.wd_modem then + self.checking_wl = false + self.self_check_msg("> check wired supervisor connection...") + + comms.set_trusted_range(0) + + self.nic = network.nic(self.wd_modem) + + self.nic.closeAll() + self.nic.open(cfg.PLC_Channel) + + self.net_listen = true + + send_sv(MGMT_TYPE.ESTABLISH, { comms.version, comms.CONN_TEST_FWV, DEVICE_TYPE.PLC, cfg.UnitID }) + + tcd.dispatch_unique(8, handle_timeout) + else + if self.self_check_pass then check_complete() end + self.run_test_btn.enable() + end + else + if not pass then + self.self_check_msg(nil, false, "make sure your supervisor is running, listening on the wired interface, the wire is intact, and your channels are correct") + end + + if self.self_check_pass then check_complete() end + self.run_test_btn.enable() + end +end + return check diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index b80a866..acd8610 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -305,6 +305,8 @@ function configurator.configure(ask_config) display.handle_paste(param1) elseif event == "modem_message" then check.receive_sv(param1, param2, param3, param4, param5) + elseif event == "conn_test_complete" then + check.conn_test_callback(param1) elseif event == "peripheral_detach" then ---@diagnostic disable-next-line: discard-returns ppm.handle_unmount(param1) diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 96d2c98..b1b0447 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -262,7 +262,7 @@ function supervisor.comms(_version, fp_ok, facility) _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else - log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC channel")) + log.debug(util.c("PLC_ESTABLISH: illegal establish packet for device ", dev_type, " on PLC channel")) _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end end From 3f1cf217acbcb48cb97cbc65caf6800083ddb9ba Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Nov 2025 18:18:29 -0500 Subject: [PATCH 76/90] #580 fix to initial wireless modem DOWN message --- coordinator/backplane.lua | 3 ++- reactor-plc/backplane.lua | 3 ++- rtu/backplane.lua | 2 +- supervisor/backplane.lua | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/coordinator/backplane.lua b/coordinator/backplane.lua index cc773db..4d72e3f 100644 --- a/coordinator/backplane.lua +++ b/coordinator/backplane.lua @@ -179,12 +179,13 @@ function backplane.init(config, __shared_memory) local modem, iface = ppm.get_wireless_modem() local wl_nic = network.nic(modem) - log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. iface) + log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN") .. (iface or "")) log_comms("wireless comms modem " .. util.trinary(modem, "connected", "not found")) -- 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.act_nic = wl_nic + log.info("BKPLN: switched active to preferred wireless") end _bp.wl_nic = wl_nic diff --git a/reactor-plc/backplane.lua b/reactor-plc/backplane.lua index 8216dd4..ef2759e 100644 --- a/reactor-plc/backplane.lua +++ b/reactor-plc/backplane.lua @@ -59,11 +59,12 @@ function backplane.init(config, __shared_memory) local modem, iface = ppm.get_wireless_modem() local wl_nic = network.nic(modem) - log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. iface) + log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN") .. (iface or "")) -- 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.act_nic = wl_nic + log.info("BKPLN: switched active to preferred wireless") end _bp.wl_nic = wl_nic diff --git a/rtu/backplane.lua b/rtu/backplane.lua index dc30b7a..0d935d0 100644 --- a/rtu/backplane.lua +++ b/rtu/backplane.lua @@ -60,7 +60,7 @@ function backplane.init(config, __shared_memory) local modem, iface = ppm.get_wireless_modem() local wl_nic = network.nic(modem) - log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. iface) + log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN") .. (iface or "")) -- 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 diff --git a/supervisor/backplane.lua b/supervisor/backplane.lua index 7a37b34..58cd55b 100644 --- a/supervisor/backplane.lua +++ b/supervisor/backplane.lua @@ -59,7 +59,7 @@ function backplane.init(config) local modem, iface = ppm.get_wireless_modem() local wl_nic = network.nic(modem) - log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN ") .. iface) + log.info("BKPLN: WIRELESS PHY_" .. util.trinary(modem, "UP ", "DOWN") .. (iface or "")) if not (modem and iface) then println("startup> wireless comms modem not found") From 46b23414b0d7a7edb0def7f0c9426a1e29ad8920 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Nov 2025 18:22:27 -0500 Subject: [PATCH 77/90] #642 RTU gateway connection test --- rtu/config/check.lua | 122 ++++++++++++++++++++++++++++---------- rtu/configure.lua | 2 + supervisor/supervisor.lua | 24 +++++--- 3 files changed, 108 insertions(+), 40 deletions(-) diff --git a/rtu/config/check.lua b/rtu/config/check.lua index 2956560..59d2930 100644 --- a/rtu/config/check.lua +++ b/rtu/config/check.lua @@ -27,13 +27,17 @@ local ESTABLISH_ACK = comms.ESTABLISH_ACK local MGMT_TYPE = comms.MGMT_TYPE local self = { + checking_wl = true, + wd_modem = nil, ---@type Modem|nil + wl_modem = nil, ---@type Modem|nil + nic = nil, ---@type nic net_listen = false, - sv_addr = comms.BROADCAST, - sv_seq_num = util.time_ms() * 10, self_check_pass = true, + self_check_wireless = true, + settings = nil, ---@type rtu_config run_test_btn = nil, ---@type PushButton @@ -59,10 +63,9 @@ local function send_sv(msg_type, msg) local pkt = comms.mgmt_packet() pkt.make(msg_type, msg) - s_pkt.make(self.sv_addr, self.sv_seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable()) + s_pkt.make(comms.BROADCAST, util.time_ms() * 10, PROTOCOL.SCADA_MGMT, pkt.raw_sendable()) self.nic.transmit(self.settings.SVR_Channel, self.settings.RTU_Channel, s_pkt) - self.sv_seq_num = self.sv_seq_num + 1 end -- handle an establish message from the supervisor @@ -78,10 +81,7 @@ local function handle_packet(packet) local est_ack = packet.data[1] if est_ack== ESTABLISH_ACK.ALLOW then - self.self_check_msg(nil, true, "") - self.sv_addr = packet.scada_frame.src_addr() - send_sv(MGMT_TYPE.CLOSE, {}) - if self.self_check_pass then check_complete() end + -- OK elseif est_ack == ESTABLISH_ACK.DENY then error_msg = "error: supervisor connection denied" elseif est_ack == ESTABLISH_ACK.BAD_VERSION then @@ -98,18 +98,20 @@ local function handle_packet(packet) end self.net_listen = false - self.run_test_btn.enable() if error_msg then self.self_check_msg(nil, false, error_msg) + else + self.self_check_msg(nil, true, "") end + + util.push_event("conn_test_complete", error_msg == nil) end -- handle supervisor connection failure local function handle_timeout() self.net_listen = false - self.run_test_btn.enable() - self.self_check_msg(nil, false, "make sure your supervisor is running, your channels are correct, trusted ranges are set properly (if enabled), facility keys match (if set), and if you are using wireless modems rather than ender modems, that your devices are close together in the same dimension") + util.push_event("conn_test_complete", false) end @@ -129,10 +131,18 @@ local function self_check() self.self_check_pass = true local cfg = self.settings - local modem = ppm.get_wireless_modem() + self.wd_modem = ppm.get_modem(cfg.WiredModem) + self.wl_modem = ppm.get_wireless_modem() local valid_cfg = rtu.validate_config(cfg) - self.self_check_msg("> check wireless/ender modem connected...", modem ~= nil, "you must connect an ender or wireless modem to the RTU gateway") + if cfg.WiredModem then + self.self_check_msg("> check wired comms modem connected...", self.wd_modem, "please connect the wired comms modem " .. cfg.WiredModem) + end + + if cfg.WirelessModem then + self.self_check_msg("> check wireless/ender modem connected...", self.wl_modem, "please connect an ender or wireless modem for wireless comms") + end + self.self_check_msg("> check gateway configuration...", valid_cfg, "go through Configure Gateway and apply settings to set any missing settings and repair any corrupted ones") -- check redstone configurations @@ -211,27 +221,37 @@ local function self_check() end end - if valid_cfg and modem then - self.self_check_msg("> check supervisor connection...") + if valid_cfg then + self.checking_wl = true - -- init mac as needed - if cfg.AuthKey and string.len(cfg.AuthKey) >= 8 then - network.init_mac(cfg.AuthKey) + if cfg.WirelessModem and self.wl_modem then + self.self_check_msg("> check wireless supervisor connection...") + + -- init mac as needed + if cfg.AuthKey and string.len(cfg.AuthKey) >= 8 then + network.init_mac(cfg.AuthKey) + else + network.deinit_mac() + end + + comms.set_trusted_range(cfg.TrustedRange) + + self.nic = network.nic(self.wl_modem) + + self.nic.closeAll() + self.nic.open(cfg.RTU_Channel) + + self.net_listen = true + + send_sv(MGMT_TYPE.ESTABLISH, { comms.version, comms.CONN_TEST_FWV, DEVICE_TYPE.RTU, {} }) + + tcd.dispatch_unique(8, handle_timeout) + elseif cfg.WiredModem and self.wd_modem then + -- skip to wired + util.push_event("conn_test_complete", true) else - network.deinit_mac() + self.self_check_msg("> no modem, can't test supervisor connection", false) end - - self.nic = network.nic(modem) - - self.nic.closeAll() - self.nic.open(cfg.RTU_Channel) - - self.sv_addr = comms.BROADCAST - self.net_listen = true - - send_sv(MGMT_TYPE.ESTABLISH, { comms.version, "0.0.0", DEVICE_TYPE.RTU, {} }) - - tcd.dispatch_unique(8, handle_timeout) else if self.self_check_pass then check_complete() end self.run_test_btn.enable() @@ -315,4 +335,44 @@ function check.receive_sv(side, sender, reply_to, message, distance) end end +-- handle completed connection tests +---@param pass boolean +function check.conn_test_callback(pass) + local cfg = self.settings + + if self.checking_wl then + if not pass then + self.self_check_msg(nil, false, "make sure your supervisor is running, listening on the wireless interface, your channels are correct, trusted ranges are set properly (if enabled), facility keys match (if set), and if you are using wireless modems rather than ender modems, that your devices are close together in the same dimension") + end + + if cfg.WiredModem and self.wd_modem then + self.checking_wl = false + self.self_check_msg("> check wired supervisor connection...") + + comms.set_trusted_range(0) + + self.nic = network.nic(self.wd_modem) + + self.nic.closeAll() + self.nic.open(cfg.RTU_Channel) + + self.net_listen = true + + send_sv(MGMT_TYPE.ESTABLISH, { comms.version, comms.CONN_TEST_FWV, DEVICE_TYPE.RTU, {} }) + + tcd.dispatch_unique(8, handle_timeout) + else + if self.self_check_pass then check_complete() end + self.run_test_btn.enable() + end + else + if not pass then + self.self_check_msg(nil, false, "make sure your supervisor is running, listening on the wired interface, the wire is intact, and your channels are correct") + end + + if self.self_check_pass then check_complete() end + self.run_test_btn.enable() + end +end + return check diff --git a/rtu/configure.lua b/rtu/configure.lua index 6b119af..8a43fef 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -359,6 +359,8 @@ function configurator.configure(ask_config) display.handle_paste(param1) elseif event == "modem_message" then check.receive_sv(param1, param2, param3, param4, param5) + elseif event == "conn_test_complete" then + check.conn_test_callback(param1) elseif event == "peripheral_detach" then ---@diagnostic disable-next-line: discard-returns ppm.handle_unmount(param1) diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index b1b0447..9250e60 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -282,25 +282,31 @@ function supervisor.comms(_version, fp_ok, facility) -- drop if not listening elseif comms_v ~= comms.version then if last_ack ~= ESTABLISH_ACK.BAD_VERSION then - log.info(util.c("dropping RTU_GW establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) + log.info(util.c("RTU_GW_ESTABLISH: [@", src_addr, "] dropping RTU_GW establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) end _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) elseif dev_type == DEVICE_TYPE.RTU then if packet.length == 4 then - -- this is an RTU advertisement for a new session - local rtu_advert = packet.data[4] - local s_id = svsessions.establish_rtu_session(nic, src_addr, i_seq_num, rtu_advert, firmware_v) + if firmware_v ~= comms.CONN_TEST_FWV then + -- this is an RTU advertisement for a new session + local rtu_advert = packet.data[4] + local s_id = svsessions.establish_rtu_session(nic, src_addr, i_seq_num, rtu_advert, firmware_v) - println(util.c("RTU (", firmware_v, ") [@", src_addr, "] \xbb connected")) - log.info(util.c("RTU_GW_ESTABLISH: RTU_GW (",firmware_v, ") [@", src_addr, "] connected with session ID ", s_id, " on ", nic.phy_name())) - _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) + println(util.c("RTU (", firmware_v, ") [@", src_addr, "] \xbb connected")) + log.info(util.c("RTU_GW_ESTABLISH: [@", src_addr, "] RTU_GW (",firmware_v, ") connected with session ID ", s_id, " on ", nic.phy_name())) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) + else + -- valid, but this was just a test + log.info(util.c("RTU_GW_ESTABLISH: RTU_GW [@", src_addr, "] sending connection test success response on ", nic.phy_name())) + _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) + end else - log.debug("RTU_GW_ESTABLISH: packet length mismatch") + log.debug(util.c("RTU_GW_ESTABLISH: [@", src_addr, "] packet length mismatch")) _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else - log.debug(util.c("illegal establish packet for device ", dev_type, " on RTU channel")) + log.debug(util.c("RTU_GW_ESTABLISH: [@", src_addr, "] illegal establish packet for device ", dev_type, " on RTU channel")) _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end end From 32af935e9e9b2bf66522a0497eb52a360e732963 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Nov 2025 18:27:17 -0500 Subject: [PATCH 78/90] supervisor establish log message updates --- supervisor/supervisor.lua | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 9250e60..fedb35f 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -258,11 +258,11 @@ function supervisor.comms(_version, fp_ok, facility) end end else - log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type") + log.debug("PLC_ESTABLISH: [@" .. src_addr .. "] packet length mismatch/bad parameter type") _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else - log.debug(util.c("PLC_ESTABLISH: illegal establish packet for device ", dev_type, " on PLC channel")) + log.debug(util.c("PLC_ESTABLISH: [@", src_addr, "] illegal establish packet for device ", dev_type, " on PLC channel")) _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end end @@ -302,7 +302,7 @@ function supervisor.comms(_version, fp_ok, facility) _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) end else - log.debug(util.c("RTU_GW_ESTABLISH: [@", src_addr, "] packet length mismatch")) + log.debug("RTU_GW_ESTABLISH: [@" .. src_addr .. "] packet length mismatch") _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end else @@ -326,7 +326,7 @@ function supervisor.comms(_version, fp_ok, facility) -- drop if not listening elseif comms_v ~= comms.version then if last_ack ~= ESTABLISH_ACK.BAD_VERSION then - log.info(util.c("dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) + log.info(util.c("CRD_ESTABLISH: [@", src_addr, "] dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) end _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) @@ -336,18 +336,18 @@ function supervisor.comms(_version, fp_ok, facility) if s_id ~= false then println(util.c("CRD (", firmware_v, ") [@", src_addr, "] \xbb connected")) - log.info(util.c("CRD_ESTABLISH: CRD (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id, " on ", nic.phy_name())) + log.info(util.c("CRD_ESTABLISH: [@", src_addr, "] CRD (", firmware_v, ") connected with session ID ", s_id, " on ", nic.phy_name())) _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, facility.get_cooling_conf() }) else if last_ack ~= ESTABLISH_ACK.COLLISION then - log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator") + log.info("CRD_ESTABLISH: [@" .. src_addr .. "] denied new coordinator due to already being connected to another coordinator") end _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.COLLISION) end else - log.debug(util.c("illegal establish packet for device ", dev_type, " on CRD channel")) + log.debug(util.c("CRD_ESTABLISH: [@", src_addr, "] illegal establish packet for device ", dev_type, " on CRD channel")) _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end end @@ -367,7 +367,7 @@ function supervisor.comms(_version, fp_ok, facility) -- drop if not listening elseif comms_v ~= comms.version then if last_ack ~= ESTABLISH_ACK.BAD_VERSION then - log.info(util.c("dropping PKT establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) + log.info(util.c("PDG_ESTABLISH: [@", src_addr, "] dropping PKT establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) end _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) @@ -376,11 +376,11 @@ function supervisor.comms(_version, fp_ok, facility) local s_id = svsessions.establish_pdg_session(nic, src_addr, i_seq_num, firmware_v) println(util.c("PKT (", firmware_v, ") [@", src_addr, "] \xbb connected")) - log.info(util.c("PDG_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id, " on ", nic.phy_name())) + log.info(util.c("PDG_ESTABLISH: [@", src_addr, "] pocket (", firmware_v, ") connected with session ID ", s_id, " on ", nic.phy_name())) _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.ALLOW) else - log.debug(util.c("illegal establish packet for device ", dev_type, " on PKT channel")) + log.debug(util.c("PDG_ESTABLISH: [@", src_addr, "] illegal establish packet for device ", dev_type, " on PKT channel")) _send_establish(nic, packet.scada_frame, ESTABLISH_ACK.DENY) end From 6e26ca4fac54ebd42936e50a69653404f34e84ae Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Nov 2025 19:13:49 -0500 Subject: [PATCH 79/90] #634 fixed wired inactive disconnected logging as unassigned modem --- coordinator/backplane.lua | 4 ++++ reactor-plc/backplane.lua | 4 ++++ rtu/backplane.lua | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/coordinator/backplane.lua b/coordinator/backplane.lua index 4d72e3f..af04d20 100644 --- a/coordinator/backplane.lua +++ b/coordinator/backplane.lua @@ -294,6 +294,10 @@ function backplane.attach(type, device, iface) -- the wireless NIC already has a modem log_sys("standby wireless modem connected") log.info("BKPLN: standby wireless modem connected") + elseif wd_nic and wd_nic.is_modem(device) then + -- wired, but not active + log_sys("standby wired modem disconnected") + log.info("BKPLN: standby wired modem disconnected") else log_sys("unassigned modem connected") log.warning("BKPLN: unassigned modem connected") diff --git a/reactor-plc/backplane.lua b/reactor-plc/backplane.lua index ef2759e..62e429b 100644 --- a/reactor-plc/backplane.lua +++ b/reactor-plc/backplane.lua @@ -306,6 +306,10 @@ function backplane.detach(iface, type, device, print_no_fp) -- wireless, but not active print_no_fp("standby wireless modem disconnected") log.info("BKPLN: standby wireless modem disconnected") + elseif wd_nic and wd_nic.is_modem(device) then + -- wired, but not active + print_no_fp("standby wired modem disconnected") + log.info("BKPLN: standby wired modem disconnected") else print_no_fp("unassigned modem disconnected") log.warning("BKPLN: unassigned modem disconnected") diff --git a/rtu/backplane.lua b/rtu/backplane.lua index 0d935d0..990c0df 100644 --- a/rtu/backplane.lua +++ b/rtu/backplane.lua @@ -243,6 +243,10 @@ function backplane.detach(type, device, iface, print_no_fp) -- wireless, but not active print_no_fp("standby wireless modem disconnected") log.info("BKPLN: standby wireless modem disconnected") + elseif wd_nic and wd_nic.is_modem(device) then + -- wired, but not active + print_no_fp("standby wired modem disconnected") + log.info("BKPLN: standby wired modem disconnected") else print_no_fp("unassigned modem disconnected") log.warning("BKPLN: unassigned modem disconnected") From 76fc5751c9398991e16735d114a31f907be91e56 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Nov 2025 19:14:05 -0500 Subject: [PATCH 80/90] fixed reactor PLC reporting as degraded if everything is OK on boot --- reactor-plc/backplane.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/reactor-plc/backplane.lua b/reactor-plc/backplane.lua index 62e429b..ecc15eb 100644 --- a/reactor-plc/backplane.lua +++ b/reactor-plc/backplane.lua @@ -35,6 +35,8 @@ function backplane.init(config, __shared_memory) local plc_dev = __shared_memory.plc_dev local plc_state = __shared_memory.plc_state + plc_state.degraded = false + -- Modem Init if _bp.smem.networked then From 3987d337c4c6e30274b8fe2141c3329d3c5d5ee0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Nov 2025 19:14:34 -0500 Subject: [PATCH 81/90] #636 close connections on nic switch --- coordinator/coordinator.lua | 5 ++++- reactor-plc/plc.lua | 5 ++++- rtu/backplane.lua | 8 ++++---- rtu/rtu.lua | 6 +++++- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index fa065ce..4374b3c 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -250,7 +250,10 @@ function coordinator.comms(version, nic, wl_nic, sv_watchdog) -- switch the current active NIC ---@param act_nic nic - function public.switch_nic(act_nic) nic = act_nic end + function public.switch_nic(act_nic) + public.close() + nic = act_nic + end -- try to connect to the supervisor if not already linked ---@param abort boolean? true to print out cancel info if not linked (use on program terminate) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index a630570..f7f92b9 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -830,7 +830,10 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) -- switch the current active NIC ---@param act_nic nic - function public.switch_nic(act_nic) nic = act_nic end + function public.switch_nic(act_nic) + public.close() + nic = act_nic + end -- reconnect a newly connected reactor ---@param new_reactor table diff --git a/rtu/backplane.lua b/rtu/backplane.lua index 990c0df..92289b5 100644 --- a/rtu/backplane.lua +++ b/rtu/backplane.lua @@ -136,7 +136,7 @@ function backplane.attach(type, device, iface, print_no_fp) -- switch back to preferred wired _bp.act_nic = wd_nic - comms.switch_nic(_bp.act_nic) + comms.switch_nic(_bp.act_nic, _bp.smem.rtu_state) log.info("BKPLN: switched comms to wired modem (preferred)") end elseif wl_nic and (not wl_nic.is_connected()) and m_is_wl then @@ -152,7 +152,7 @@ function backplane.attach(type, device, iface, print_no_fp) -- switch back to preferred wireless _bp.act_nic = wl_nic - comms.switch_nic(_bp.act_nic) + comms.switch_nic(_bp.act_nic, _bp.smem.rtu_state) log.info("BKPLN: switched comms to wireless modem (preferred)") end elseif wl_nic and m_is_wl then @@ -227,14 +227,14 @@ function backplane.detach(type, device, iface, print_no_fp) elseif wd_nic and wd_nic.is_connected() then _bp.act_nic = wd_nic - comms.switch_nic(_bp.act_nic) + comms.switch_nic(_bp.act_nic, _bp.smem.rtu_state) log.info("BKPLN: switched comms to wired modem") end elseif wl_nic and wl_nic.is_connected() then -- wired active disconnected, wireless available _bp.act_nic = wl_nic - comms.switch_nic(_bp.act_nic) + comms.switch_nic(_bp.act_nic, _bp.smem.rtu_state) log.info("BKPLN: switched comms to wireless modem") else -- wired active disconnected, wireless unavailable diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 2c4c9d4..fa9908f 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -359,7 +359,11 @@ function rtu.comms(version, nic, conn_watchdog) -- switch the current active NIC ---@param act_nic nic - function public.switch_nic(act_nic) nic = act_nic end + ---@param rtu_state rtu_state + function public.switch_nic(act_nic, rtu_state) + public.close(rtu_state) + nic = act_nic + end -- unlink from the server ---@param rtu_state rtu_state From 1254d668a9803a2670359ab25a06c5d1c135b18b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Nov 2025 23:49:50 -0500 Subject: [PATCH 82/90] decrease reactor PLC reconnect time --- reactor-plc/threads.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 7b45c31..555be4f 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -34,10 +34,9 @@ function threads.thread__main(smem) databus.tx_rt_status("main", true) log.debug("OS: main thread start") - -- send status updates at 2Hz (every 10 server ticks) (every loop tick) - -- send link requests at 0.5Hz (every 40 server ticks) (every 8 loop ticks) - local LINK_TICKS = 8 + local LINK_TICKS = 2 local ticks_to_update = 0 + local loop_clock = util.new_clock(MAIN_CLOCK) -- load in from shared memory From 29513bac383ef5d9972d6806ee09f4620415a5bf Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 9 Nov 2025 00:06:02 -0500 Subject: [PATCH 83/90] #638 try reconnecting to both supervisor and coordinator at the same time --- pocket/pocket.lua | 16 +++++----------- pocket/startup.lua | 2 +- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 56614b7..bd4f9f6 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -515,24 +515,18 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) -- attempt to re-link if any of the dependent links aren't active function public.link_update() - if not self.sv.linked then + if not (self.sv.linked and self.api.linked) then if self.api.linked then iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY, false, nil) + elseif self.sv.linked then + iocontrol.report_link_state(LINK_STATE.SV_LINK_ONLY, nil, false) else iocontrol.report_link_state(LINK_STATE.UNLINKED, false, false) end if self.establish_delay_counter <= 0 then - _send_sv_establish() - self.establish_delay_counter = 4 - else - self.establish_delay_counter = self.establish_delay_counter - 1 - end - elseif not self.api.linked then - iocontrol.report_link_state(LINK_STATE.SV_LINK_ONLY, nil, false) - - if self.establish_delay_counter <= 0 then - _send_api_establish() + if not self.api.linked then _send_api_establish() end + if not self.sv.linked then _send_sv_establish() end self.establish_delay_counter = 4 else self.establish_delay_counter = self.establish_delay_counter - 1 diff --git a/pocket/startup.lua b/pocket/startup.lua index 7272ab8..a849b3a 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -22,7 +22,7 @@ local pocket = require("pocket.pocket") local renderer = require("pocket.renderer") local threads = require("pocket.threads") -local POCKET_VERSION = "v1.0.3" +local POCKET_VERSION = "v1.0.4" local println = util.println local println_ts = util.println_ts From fa551f0b4f2bbdd2e7ba071c2cdfad175d022a57 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 9 Nov 2025 00:06:36 -0500 Subject: [PATCH 84/90] #638 update info on testing denial and don't send close to unlinked hosts --- pocket/pocket.lua | 16 ++++++++++++---- pocket/ui/apps/alarm.lua | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pocket/pocket.lua b/pocket/pocket.lua index bd4f9f6..73b5161 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -491,20 +491,28 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) function public.close_sv() sv_watchdog.cancel() nav.unload_sv() - self.sv.linked = false + self.sv.r_seq_num = nil self.sv.addr = comms.BROADCAST - _send_sv(MGMT_TYPE.CLOSE, {}) + + if self.sv.linked then + self.sv.linked = false + _send_sv(MGMT_TYPE.CLOSE, {}) + end end -- close connection to coordinator API server function public.close_api() api_watchdog.cancel() nav.unload_api() - self.api.linked = false + self.api.r_seq_num = nil self.api.addr = comms.BROADCAST - _send_crd(MGMT_TYPE.CLOSE, {}) + + if self.api.linked then + self.api.linked = false + _send_crd(MGMT_TYPE.CLOSE, {}) + end end -- close the connections to the servers diff --git a/pocket/ui/apps/alarm.lua b/pocket/ui/apps/alarm.lua index a6dc573..b823a3d 100644 --- a/pocket/ui/apps/alarm.lua +++ b/pocket/ui/apps/alarm.lua @@ -163,7 +163,7 @@ local function new_view(root) TextBox{parent=info_div,x=2,y=1,text="This app provides tools to test alarm sounds by alarm and by tone (1-8)."} TextBox{parent=info_div,x=2,y=6,text="The system must be idle (all units stopped with no alarms active) for testing to run."} - TextBox{parent=info_div,x=2,y=12,text="Currently, testing will be denied unless you have a Facility Authentication Key set (this will change in the future)."} + TextBox{parent=info_div,x=2,y=12,text="Testing will be denied unless you enabled it in the Supervisor's configuration."} --#endregion From b020dde1225f68708f797d41061efc5320adfe86 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 9 Nov 2025 00:06:55 -0500 Subject: [PATCH 85/90] #638 fixed pocket test config checkbox initialization --- supervisor/config/system.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervisor/config/system.lua b/supervisor/config/system.lua index 43c8804..f61f21c 100644 --- a/supervisor/config/system.lua +++ b/supervisor/config/system.lua @@ -133,7 +133,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit TextBox{parent=net_c_2,y=8,text="With a wireless modem, configure Pocket access."} local pkt_en = Checkbox{parent=net_c_2,y=10,label="Enable Pocket Access",default=ini_cfg.PocketEnabled,callback=on_pocket_en,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} - self.pkt_test = Checkbox{parent=net_c_2,label="Enable Pocket Remote System Testing",default=ini_cfg.PocketEnabled,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} + self.pkt_test = Checkbox{parent=net_c_2,label="Enable Pocket Remote System Testing",default=ini_cfg.PocketTest,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} TextBox{parent=net_c_2,x=3,text="This allows remotely playing alarm sounds.",fg_bg=g_lg_fg_bg} local function submit_net_cfg_opts() From 21c36c70be899a414a51b897602635e33e268e44 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 9 Nov 2025 00:20:34 -0500 Subject: [PATCH 86/90] #634 removed checking wireless on disconnect due to peripheral already being gone, changed to checking if it belonged to the wireless nic --- coordinator/backplane.lua | 10 ++++++---- reactor-plc/backplane.lua | 12 +++++------- rtu/backplane.lua | 12 +++++------- supervisor/backplane.lua | 8 +------- 4 files changed, 17 insertions(+), 25 deletions(-) diff --git a/coordinator/backplane.lua b/coordinator/backplane.lua index af04d20..8526537 100644 --- a/coordinator/backplane.lua +++ b/coordinator/backplane.lua @@ -372,9 +372,7 @@ function backplane.detach(type, device, iface) if 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_DETACH ", iface)) + log.info(util.c("BKPLN: PHY_DETACH ", iface)) if wd_nic and wd_nic.is_modem(device) then wd_nic.disconnect() @@ -428,7 +426,11 @@ function backplane.detach(type, device, iface) else -- wired active disconnected, wireless unavailable end - elseif _bp.wl_nic and m_is_wl then + elseif wd_nic and wd_nic.is_modem(device) then + -- wired, but not active + log_sys("standby wired modem disconnected") + log.info("BKPLN: standby wired modem disconnected") + elseif wl_nic and wl_nic.is_modem(device) then -- wireless, but not active log_sys("standby wireless modem disconnected") log.info("BKPLN: standby wireless modem disconnected") diff --git a/reactor-plc/backplane.lua b/reactor-plc/backplane.lua index ecc15eb..07fd967 100644 --- a/reactor-plc/backplane.lua +++ b/reactor-plc/backplane.lua @@ -249,9 +249,7 @@ function backplane.detach(iface, type, device, print_no_fp) elseif _bp.smem.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_DETACH ", iface)) + log.info(util.c("BKPLN: PHY_DETACH ", iface)) if wd_nic and wd_nic.is_modem(device) then wd_nic.disconnect() @@ -304,14 +302,14 @@ function backplane.detach(iface, type, device, print_no_fp) state.degraded = true _bp.smem.q.mq_rps.push_command(MQ__RPS_CMD.DEGRADED_SCRAM) end - elseif wl_nic and m_is_wl then - -- wireless, but not active - print_no_fp("standby wireless modem disconnected") - log.info("BKPLN: standby wireless modem disconnected") elseif wd_nic and wd_nic.is_modem(device) then -- wired, but not active print_no_fp("standby wired modem disconnected") log.info("BKPLN: standby wired modem disconnected") + elseif wl_nic and wl_nic.is_modem(device) then + -- wireless, but not active + print_no_fp("standby wireless modem disconnected") + log.info("BKPLN: standby wireless modem disconnected") else print_no_fp("unassigned modem disconnected") log.warning("BKPLN: unassigned modem disconnected") diff --git a/rtu/backplane.lua b/rtu/backplane.lua index 92289b5..0027579 100644 --- a/rtu/backplane.lua +++ b/rtu/backplane.lua @@ -190,9 +190,7 @@ function backplane.detach(type, device, iface, print_no_fp) if 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_DETACH ", iface)) + log.info(util.c("BKPLN: PHY_DETACH ", iface)) if wd_nic and wd_nic.is_modem(device) then wd_nic.disconnect() @@ -239,14 +237,14 @@ function backplane.detach(type, device, iface, print_no_fp) else -- wired active disconnected, wireless unavailable end - elseif _bp.wl_nic and m_is_wl then - -- wireless, but not active - print_no_fp("standby wireless modem disconnected") - log.info("BKPLN: standby wireless modem disconnected") elseif wd_nic and wd_nic.is_modem(device) then -- wired, but not active print_no_fp("standby wired modem disconnected") log.info("BKPLN: standby wired modem disconnected") + elseif wl_nic and wl_nic.is_modem(device) then + -- wireless, but not active + print_no_fp("standby wireless modem disconnected") + log.info("BKPLN: standby wireless modem disconnected") else print_no_fp("unassigned modem disconnected") log.warning("BKPLN: unassigned modem disconnected") diff --git a/supervisor/backplane.lua b/supervisor/backplane.lua index 58cd55b..c88508b 100644 --- a/supervisor/backplane.lua +++ b/supervisor/backplane.lua @@ -129,9 +129,7 @@ function backplane.detach(iface, type, device, print_no_fp) if 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_DETACH ", iface)) + log.info(util.c("BKPLN: PHY_DETACH ", iface)) _bp.nic_map[iface] = nil @@ -159,10 +157,6 @@ function backplane.detach(iface, type, device, print_no_fp) else databus.tx_hw_wl_modem(false) end - elseif _bp.wl_nic and m_is_wl then - -- wireless, but not active - print_no_fp("standby wireless modem disconnected") - log.info("BKPLN: standby wireless modem disconnected") else print_no_fp("unassigned modem disconnected") log.warning("BKPLN: unassigned modem disconnected") From d4c5140003ed5a844a1ceb15004983f3846742cc Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 9 Nov 2025 00:34:45 -0500 Subject: [PATCH 87/90] #636 coordinator close UI on connection switch for now --- coordinator/backplane.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/coordinator/backplane.lua b/coordinator/backplane.lua index 8526537..310d042 100644 --- a/coordinator/backplane.lua +++ b/coordinator/backplane.lua @@ -245,6 +245,7 @@ function backplane.displays() return _bp.displays end ---@param device table ---@param iface string function backplane.attach(type, device, iface) + local MQ__RENDER_CMD = _bp.smem.q_types.MQ__RENDER_CMD local MQ__RENDER_DATA = _bp.smem.q_types.MQ__RENDER_DATA local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic @@ -271,6 +272,7 @@ function backplane.attach(type, device, iface) -- switch back to preferred wired _bp.act_nic = wd_nic + _bp.smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI) comms.switch_nic(_bp.act_nic) log.info("BKPLN: switched comms to wired modem (preferred)") end @@ -287,6 +289,7 @@ function backplane.attach(type, device, iface) -- switch back to preferred wireless _bp.act_nic = wl_nic + _bp.smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI) comms.switch_nic(_bp.act_nic) log.info("BKPLN: switched comms to wireless modem (preferred)") end @@ -408,6 +411,7 @@ function backplane.detach(type, device, iface) elseif wd_nic and wd_nic.is_connected() then _bp.act_nic = wd_nic + _bp.smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI) comms.switch_nic(_bp.act_nic) log.info("BKPLN: switched comms to wired modem") else @@ -421,6 +425,7 @@ function backplane.detach(type, device, iface) -- wired active disconnected, wireless available _bp.act_nic = wl_nic + _bp.smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI) comms.switch_nic(_bp.act_nic) log.info("BKPLN: switched comms to wireless modem") else From 12d688daec71e1c52579f250c792dde389659d85 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 9 Nov 2025 15:36:14 -0500 Subject: [PATCH 88/90] #580 added more new! tags and updated change logs --- coordinator/config/system.lua | 2 ++ coordinator/configure.lua | 2 +- supervisor/config/system.lua | 3 +++ supervisor/configure.lua | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/coordinator/config/system.lua b/coordinator/config/system.lua index b7d8d04..c7d8efb 100644 --- a/coordinator/config/system.lua +++ b/coordinator/config/system.lua @@ -127,9 +127,11 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) TextBox{parent=net_c_2,text="If you selected multiple interfaces, please specify if this device should prefer wireless or otherwise wired. The preferred interface is switched too when reconnected even if failover has succeeded onto the fallback interface."} self.wl_pref = Checkbox{parent=net_c_2,y=7,label="Prefer Wireless",default=ini_cfg.PreferWireless,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_2,x=19,y=7,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision TextBox{parent=net_c_2,y=9,text="With a wireless modem, configure Pocket access."} self.api_en = Checkbox{parent=net_c_2,y=11,label="Enable Pocket Access",default=ini_cfg.API_Enabled,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_2,x=24,y=11,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision local function submit_net_cfg_opts() if tmp_cfg.WirelessModem and tmp_cfg.WiredModem then diff --git a/coordinator/configure.lua b/coordinator/configure.lua index a6d63ca..b63883d 100644 --- a/coordinator/configure.lua +++ b/coordinator/configure.lua @@ -37,7 +37,7 @@ local changes = { { "v1.3.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } }, { "v1.5.1", { "Added energy scale options" } }, { "v1.6.13", { "Added option for Po/Pu pellet green/cyan pairing" } }, - { "v1.7.0", { "Added support for wired communications modems" } } + { "v1.7.0", { "Added support for wired communications modems", "Added option for allowing Pocket connections" } } } ---@class crd_configurator diff --git a/supervisor/config/system.lua b/supervisor/config/system.lua index f61f21c..16d938e 100644 --- a/supervisor/config/system.lua +++ b/supervisor/config/system.lua @@ -118,6 +118,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_interfaces,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} TextBox{parent=net_c_2,x=1,y=1,text="Please assign device connection interfaces if you selected multiple network interfaces."} + TextBox{parent=net_c_2,x=39,y=2,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision TextBox{parent=net_c_2,x=1,y=4,text="Reactor PLC\nRTU Gateway\nCoordinator",fg_bg=g_lg_fg_bg} local opts = { "Wireless", "Wired", "Both" } local plc_listen = Radio2D{parent=net_c_2,x=14,y=4,rows=1,columns=3,default=ini_cfg.PLC_Listen,options=opts,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lightBlue,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg} @@ -133,7 +134,9 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit TextBox{parent=net_c_2,y=8,text="With a wireless modem, configure Pocket access."} local pkt_en = Checkbox{parent=net_c_2,y=10,label="Enable Pocket Access",default=ini_cfg.PocketEnabled,callback=on_pocket_en,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_2,x=24,y=10,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision self.pkt_test = Checkbox{parent=net_c_2,label="Enable Pocket Remote System Testing",default=ini_cfg.PocketTest,box_fg_bg=cpair(colors.lightBlue,colors.black),disable_fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_2,x=39,y=11,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision TextBox{parent=net_c_2,x=3,text="This allows remotely playing alarm sounds.",fg_bg=g_lg_fg_bg} local function submit_net_cfg_opts() diff --git a/supervisor/configure.lua b/supervisor/configure.lua index 83a9264..bedb888 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -34,7 +34,7 @@ local changes = { { "v1.2.12", { "Added front panel UI theme", "Added color accessibility modes" } }, { "v1.3.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } }, { "v1.6.0", { "Added sodium emergency coolant option" } }, - { "v1.8.0", { "Added support for both wired and wireless networking" } } + { "v1.8.0", { "Added support for wired communications modems", "Added option for allowing Pocket connections", "Added option for allowing Pocket test commands" } } } ---@class svr_configurator From 82aff3b30bde1de60979bb1eeb53065e3143ea63 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 9 Nov 2025 15:42:14 -0500 Subject: [PATCH 89/90] #580 updated hardware labeling to not touch the edges --- coordinator/ui/layout/front_panel.lua | 8 ++++---- reactor-plc/panel/front_panel.lua | 6 +++--- rtu/panel/front_panel.lua | 8 ++++---- supervisor/panel/front_panel.lua | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/coordinator/ui/layout/front_panel.lua b/coordinator/ui/layout/front_panel.lua index 78b034f..b6a5c30 100644 --- a/coordinator/ui/layout/front_panel.lua +++ b/coordinator/ui/layout/front_panel.lua @@ -139,14 +139,14 @@ local function init(panel, config) -- hardware labeling -- - local hw_labels = Rectangle{parent=main_page,y=term_h-7,width=15,height=5,border=border(1,s_hi_box.bkg,true),even_inner=true} + local hw_labels = Rectangle{parent=main_page,x=2,y=term_h-7,width=14,height=5,border=border(1,s_hi_box.bkg,true),even_inner=true} ---@diagnostic disable-next-line: undefined-field local comp_id = util.sprintf("%03d", os.getComputerID()) - TextBox{parent=hw_labels,text="FW "..ps.get("version"),fg_bg=s_hi_box} - TextBox{parent=hw_labels,text="NT v"..ps.get("comms_version"),fg_bg=s_hi_box} - TextBox{parent=hw_labels,text="S/N CRD-"..comp_id,fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="FW "..ps.get("version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="NT v"..ps.get("comms_version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="SN "..comp_id.."-CRD",fg_bg=s_hi_box} -- -- page handling diff --git a/reactor-plc/panel/front_panel.lua b/reactor-plc/panel/front_panel.lua index 985d00c..95004d2 100644 --- a/reactor-plc/panel/front_panel.lua +++ b/reactor-plc/panel/front_panel.lua @@ -157,9 +157,9 @@ local function init(panel, config) ---@diagnostic disable-next-line: undefined-field local comp_id = util.sprintf("%03d", os.getComputerID()) - TextBox{parent=hw_labels,text="FW "..databus.ps.get("version"),fg_bg=s_hi_box} - TextBox{parent=hw_labels,text="NT v"..databus.ps.get("comms_version"),fg_bg=s_hi_box} - TextBox{parent=hw_labels,text="S/N PLC-"..comp_id,fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="FW "..databus.ps.get("version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="NT v"..databus.ps.get("comms_version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="SN "..comp_id.."-PLC",fg_bg=s_hi_box} -- -- rps list diff --git a/rtu/panel/front_panel.lua b/rtu/panel/front_panel.lua index 4166783..48401f3 100644 --- a/rtu/panel/front_panel.lua +++ b/rtu/panel/front_panel.lua @@ -114,14 +114,14 @@ local function init(panel, config, units) -- hardware labeling -- - local hw_labels = Rectangle{parent=panel,y=term_h-6,width=15,height=5,border=border(1,s_hi_box.bkg,true),even_inner=true} + local hw_labels = Rectangle{parent=panel,x=2,y=term_h-6,width=14,height=5,border=border(1,s_hi_box.bkg,true),even_inner=true} ---@diagnostic disable-next-line: undefined-field local comp_id = util.sprintf("%03d", os.getComputerID()) - TextBox{parent=hw_labels,text="FW "..databus.ps.get("version"),fg_bg=s_hi_box} - TextBox{parent=hw_labels,text="NT v"..databus.ps.get("comms_version"),fg_bg=s_hi_box} - TextBox{parent=hw_labels,text="S/N RTU-"..comp_id,fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="FW "..databus.ps.get("version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="NT v"..databus.ps.get("comms_version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="SN "..comp_id.."-RTU",fg_bg=s_hi_box} -- -- speaker count diff --git a/supervisor/panel/front_panel.lua b/supervisor/panel/front_panel.lua index b86520b..cae6bce 100644 --- a/supervisor/panel/front_panel.lua +++ b/supervisor/panel/front_panel.lua @@ -79,14 +79,14 @@ local function init(panel, config) -- hardware labeling -- - local hw_labels = Rectangle{parent=main_page,y=term_h-7,width=15,height=5,border=border(1,s_hi_box.bkg,true),even_inner=true} + local hw_labels = Rectangle{parent=main_page,x=2,y=term_h-7,width=14,height=5,border=border(1,s_hi_box.bkg,true),even_inner=true} ---@diagnostic disable-next-line: undefined-field local comp_id = util.sprintf("%03d", os.getComputerID()) - TextBox{parent=hw_labels,text="FW "..databus.ps.get("version"),fg_bg=s_hi_box} - TextBox{parent=hw_labels,text="NT v"..databus.ps.get("comms_version"),fg_bg=s_hi_box} - TextBox{parent=hw_labels,text="S/N SVR-"..comp_id,fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="FW "..databus.ps.get("version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="NT v"..databus.ps.get("comms_version"),fg_bg=s_hi_box} + TextBox{parent=hw_labels,text="SN "..comp_id.."-SVR",fg_bg=s_hi_box} -- -- page handling From 2b015759fd4cd6cf6f57197c8f90e6b6c4348726 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 9 Nov 2025 15:44:57 -0500 Subject: [PATCH 90/90] #580 close all channels on standby modems on connect --- coordinator/backplane.lua | 8 ++++---- reactor-plc/backplane.lua | 4 ++++ rtu/backplane.lua | 4 ++++ supervisor/backplane.lua | 4 ++++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/coordinator/backplane.lua b/coordinator/backplane.lua index 310d042..f53a764 100644 --- a/coordinator/backplane.lua +++ b/coordinator/backplane.lua @@ -295,13 +295,13 @@ function backplane.attach(type, device, iface) end elseif wl_nic and m_is_wl then -- the wireless NIC already has a modem + device.closeAll() + log_sys("standby wireless modem connected") log.info("BKPLN: standby wireless modem connected") - elseif wd_nic and wd_nic.is_modem(device) then - -- wired, but not active - log_sys("standby wired modem disconnected") - log.info("BKPLN: standby wired modem disconnected") else + device.closeAll() + log_sys("unassigned modem connected") log.warning("BKPLN: unassigned modem connected") end diff --git a/reactor-plc/backplane.lua b/reactor-plc/backplane.lua index 07fd967..44ae3ff 100644 --- a/reactor-plc/backplane.lua +++ b/reactor-plc/backplane.lua @@ -209,9 +209,13 @@ function backplane.attach(iface, type, device, print_no_fp) end elseif wl_nic and m_is_wl then -- the wireless NIC already has a modem + device.closeAll() + print_no_fp("standby wireless modem connected") log.info("BKPLN: standby wireless modem connected") else + device.closeAll() + print_no_fp("unassigned modem connected") log.warning("BKPLN: unassigned modem connected") end diff --git a/rtu/backplane.lua b/rtu/backplane.lua index 0027579..00da9bb 100644 --- a/rtu/backplane.lua +++ b/rtu/backplane.lua @@ -157,9 +157,13 @@ function backplane.attach(type, device, iface, print_no_fp) end elseif wl_nic and m_is_wl then -- the wireless NIC already has a modem + device.closeAll() + print_no_fp("standby wireless modem connected") log.info("BKPLN: standby wireless modem connected") else + device.closeAll() + print_no_fp("unassigned modem connected") log.warning("BKPLN: unassigned modem connected") end diff --git a/supervisor/backplane.lua b/supervisor/backplane.lua index c88508b..535571f 100644 --- a/supervisor/backplane.lua +++ b/supervisor/backplane.lua @@ -111,9 +111,13 @@ function backplane.attach(iface, type, device, print_no_fp) databus.tx_hw_wl_modem(true) elseif _bp.wl_nic and m_is_wl then -- the wireless NIC already has a modem + device.closeAll() + print_no_fp("standby wireless modem connected") log.info("BKPLN: standby wireless modem connected") else + device.closeAll() + print_no_fp("unassigned modem connected") log.warning("BKPLN: unassigned modem connected") end