From d374967cb772c888e1ea02e90499b32632a9e6c3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 3 Apr 2025 22:56:54 -0400 Subject: [PATCH 1/8] #614 fixed reactor PLC self check when configured as not networked --- reactor-plc/config/check.lua | 9 ++++++--- reactor-plc/startup.lua | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/reactor-plc/config/check.lua b/reactor-plc/config/check.lua index 7a964fe..43f28ba 100644 --- a/reactor-plc/config/check.lua +++ b/reactor-plc/config/check.lua @@ -84,7 +84,7 @@ local function handle_packet(packet) elseif est_ack == ESTABLISH_ACK.COLLISION then error_msg = "another reactor PLC is connected with this reactor unit ID" elseif est_ack == ESTABLISH_ACK.BAD_VERSION then - error_msg = "reactor PLC comms version does not match supervisor comms version, make sure both devices are up-to-date (ccmsi update ...)" + error_msg = "reactor PLC comms version does not match supervisor comms version, make sure both devices are up-to-date (ccmsi update)" else error_msg = "error: invalid reply from supervisor" end @@ -124,7 +124,10 @@ local function self_check() local reactor = ppm.get_fission_reactor() local valid_cfg = plc.validate_config(self.settings) - self.self_check_msg("> check wireless/ender modem connected...", modem ~= nil, "you must connect an ender or wireless modem to the reactor PLC") + if self.settings.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") + end + self.self_check_msg("> check fission reactor connected...", reactor ~= nil, "please connect the reactor PLC to the reactor's fission reactor logic adapter") self.self_check_msg("> check fission reactor formed...") -- this consumes events, but that is fine here @@ -132,7 +135,7 @@ 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 valid_cfg and modem then + if self.settings.Networked and valid_cfg and modem then self.self_check_msg("> check supervisor connection...") -- init mac as needed diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 49d60a5..c085e95 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.19" +local R_PLC_VERSION = "v1.8.20" local println = util.println local println_ts = util.println_ts From c6a7de26692d0ab2ded14b6edab77520aa858bce Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 3 Apr 2025 23:06:45 -0400 Subject: [PATCH 2/8] #364 base RTU gateway self checks --- rtu/config/check.lua | 232 +++++++++++++++++++++++++++++++++++++++++++ rtu/configure.lua | 19 +++- rtu/rtu.lua | 48 +++++---- rtu/startup.lua | 2 +- 4 files changed, 275 insertions(+), 26 deletions(-) create mode 100644 rtu/config/check.lua diff --git a/rtu/config/check.lua b/rtu/config/check.lua new file mode 100644 index 0000000..861895d --- /dev/null +++ b/rtu/config/check.lua @@ -0,0 +1,232 @@ +local comms = require("scada-common.comms") +local network = require("scada-common.network") +local ppm = require("scada-common.ppm") +local tcd = require("scada-common.tcd") +local util = require("scada-common.util") + +local rtu = require("rtu.rtu") + +local core = require("graphics.core") + +local Div = require("graphics.elements.Div") +local ListBox = require("graphics.elements.ListBox") +local TextBox = require("graphics.elements.TextBox") + +local PushButton = require("graphics.elements.controls.PushButton") + +local tri = util.trinary + +local cpair = core.cpair + +local PROTOCOL = comms.PROTOCOL +local DEVICE_TYPE = comms.DEVICE_TYPE +local ESTABLISH_ACK = comms.ESTABLISH_ACK +local MGMT_TYPE = comms.MGMT_TYPE + +local self = { + nic = nil, ---@type nic + net_listen = false, + sv_addr = comms.BROADCAST, + sv_seq_num = util.time_ms() * 10, + + self_check_pass = true, + + settings = nil, ---@type rtu_config + + run_test_btn = nil, ---@type PushButton + sc_log = nil, ---@type ListBox + self_check_msg = nil ---@type function +} + +-- report successful completion of the check +local function check_complete() + TextBox{parent=self.sc_log,text="> all tests passed!",fg_bg=cpair(colors.blue,colors._INHERIT)} + TextBox{parent=self.sc_log,text=""} + local more = Div{parent=self.sc_log,height=3,fg_bg=cpair(colors.gray,colors._INHERIT)} + TextBox{parent=more,text="if you still have a problem:"} + TextBox{parent=more,text="- check the wiki on GitHub"} + TextBox{parent=more,text="- ask for help on GitHub discussions or Discord"} +end + +-- send a management packet to the supervisor +---@param msg_type MGMT_TYPE +---@param msg table +local function send_sv(msg_type, msg) + local s_pkt = comms.scada_packet() + 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()) + + 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 +---@param packet mgmt_frame +local function handle_packet(packet) + local error_msg = nil + + if packet.scada_frame.local_channel() ~= self.settings.RTU_Channel then + error_msg = "error: unknown receive channel" + elseif packet.scada_frame.remote_channel() == self.settings.SVR_Channel and packet.scada_frame.protocol() == PROTOCOL.SCADA_MGMT then + if packet.type == MGMT_TYPE.ESTABLISH then + if packet.length == 1 then + 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 + elseif est_ack == ESTABLISH_ACK.DENY then + error_msg = "error: supervisor connection denied" + elseif est_ack == ESTABLISH_ACK.BAD_VERSION then + error_msg = "RTU gateway comms version does not match supervisor comms version, make sure both devices are up-to-date (ccmsi update)" + else + error_msg = "error: invalid reply from supervisor" + end + else + error_msg = "error: invalid reply length from supervisor" + end + else + error_msg = "error: didn't get an establish reply from supervisor" + end + end + + self.net_listen = false + self.run_test_btn.enable() + + if error_msg then + self.self_check_msg(nil, false, error_msg) + end +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") +end + +-- execute the self-check +local function self_check() + self.run_test_btn.disable() + + self.sc_log.remove_all() + ppm.mount_all() + + self.self_check_pass = true + + local modem = ppm.get_wireless_modem() + local valid_cfg = rtu.validate_config(self.settings) + + self.self_check_msg("> check wireless/ender modem connected...", modem ~= nil, "you must connect an ender or wireless modem to the RTU gateway") + + 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 valid_cfg and modem then + self.self_check_msg("> check supervisor connection...") + + -- init mac as needed + if self.settings.AuthKey and string.len(self.settings.AuthKey) >= 8 then + network.init_mac(self.settings.AuthKey) + else + network.deinit_mac() + end + + self.nic = network.nic(modem) + + self.nic.closeAll() + self.nic.open(self.settings.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() + end +end + +-- exit self check back home +---@param main_pane MultiPane +local function exit_self_check(main_pane) + tcd.abort(handle_timeout) + self.net_listen = false + self.run_test_btn.enable() + self.sc_log.remove_all() + main_pane.set_value(1) +end + +local check = {} + +-- create the self-check view +---@param main_pane MultiPane +---@param settings_cfg rtu_config +---@param check_sys Div +---@param style { [string]: cpair } +function check.create(main_pane, settings_cfg, check_sys, style) + local bw_fg_bg = style.bw_fg_bg + local g_lg_fg_bg = style.g_lg_fg_bg + local nav_fg_bg = style.nav_fg_bg + local btn_act_fg_bg = style.btn_act_fg_bg + local btn_dis_fg_bg = style.btn_dis_fg_bg + + self.settings = settings_cfg + + local sc = Div{parent=check_sys,x=2,y=4,width=49} + + TextBox{parent=check_sys,x=1,y=2,text=" RTU Gateway Self-Check",fg_bg=bw_fg_bg} + + self.sc_log = ListBox{parent=sc,x=1,y=1,height=12,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 last_check = { nil, nil } + + function self.self_check_msg(msg, success, fail_msg) + if type(msg) == "string" then + last_check[1] = Div{parent=self.sc_log,height=1} + local e = TextBox{parent=last_check[1],text=msg,fg_bg=bw_fg_bg} + last_check[2] = e.get_x()+string.len(msg) + end + + if type(fail_msg) == "string" then + TextBox{parent=last_check[1],x=last_check[2],y=1,text=tri(success,"PASS","FAIL"),fg_bg=tri(success,cpair(colors.green,colors._INHERIT),cpair(colors.red,colors._INHERIT))} + + if not success then + local fail = Div{parent=self.sc_log,height=#util.strwrap(fail_msg, 46)} + TextBox{parent=fail,x=3,text=fail_msg,fg_bg=cpair(colors.gray,colors.white)} + end + + self.self_check_pass = self.self_check_pass and success + end + end + + PushButton{parent=sc,x=1,y=14,text="\x1b Back",callback=function()exit_self_check(main_pane)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + self.run_test_btn = PushButton{parent=sc,x=40,y=14,min_width=10,text="Run Test",callback=function()self_check()end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} +end + +-- handle incoming modem messages +---@param side string +---@param sender integer +---@param reply_to integer +---@param message any +---@param distance integer +function check.receive_sv(side, sender, reply_to, message, distance) + if self.nic ~= nil and self.net_listen then + local s_pkt = self.nic.receive(side, sender, reply_to, message, distance) + + if s_pkt and s_pkt.protocol() == PROTOCOL.SCADA_MGMT then + local mgmt_pkt = comms.mgmt_packet() + if mgmt_pkt.decode(s_pkt) then + tcd.abort(handle_timeout) + handle_packet(mgmt_pkt.get()) + end + end + end +end + +return check diff --git a/rtu/configure.lua b/rtu/configure.lua index 9b775a0..9db05bb 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -7,6 +7,7 @@ local ppm = require("scada-common.ppm") local tcd = require("scada-common.tcd") local util = require("scada-common.util") +local check = require("rtu.config.check") local peripherals = require("rtu.config.peripherals") local redstone = require("rtu.config.redstone") local system = require("rtu.config.system") @@ -169,8 +170,9 @@ local function config_view(display) local changelog = Div{parent=root_pane_div,x=1,y=1} local peri_cfg = Div{parent=root_pane_div,x=1,y=1} local rs_cfg = Div{parent=root_pane_div,x=1,y=1} + local check_sys = Div{parent=root_pane_div,x=1,y=1} - local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,spkr_cfg,net_cfg,log_cfg,clr_cfg,summary,changelog,peri_cfg,rs_cfg}} + local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,spkr_cfg,net_cfg,log_cfg,clr_cfg,summary,changelog,peri_cfg,rs_cfg,check_sys}} --#region Main Page @@ -226,8 +228,9 @@ local function config_view(display) PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg} local start_btn = PushButton{parent=main_page,x=42,y=17,min_width=9,text="Startup",callback=startup,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} - tool_ctl.color_cfg = PushButton{parent=main_page,x=36,y=y_start,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} - PushButton{parent=main_page,x=39,y=y_start+2,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=main_page,x=39,y=y_start,min_width=12,text="Self-Check",callback=function()main_pane.set_value(10)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} + tool_ctl.color_cfg = PushButton{parent=main_page,x=36,y=y_start+2,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} + PushButton{parent=main_page,x=39,y=y_start+4,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} if tool_ctl.ask_config then start_btn.disable() end @@ -283,6 +286,12 @@ local function config_view(display) PushButton{parent=cl,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} --#endregion + + --#region Self-Check + + check.create(main_pane, settings_cfg, check_sys, style) + + --#endregion end -- reset terminal screen @@ -317,7 +326,7 @@ function configurator.configure(ask_config) config_view(display) while true do - local event, param1, param2, param3 = util.pull_event() + local event, param1, param2, param3, param4, param5 = util.pull_event() -- handle event if event == "timer" then @@ -330,6 +339,8 @@ 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 == "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) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 2fce541..9c8e38b 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -46,36 +46,42 @@ function rtu.load_config() config.FrontPanelTheme = settings.get("FrontPanelTheme") config.ColorMode = settings.get("ColorMode") + return rtu.validate_config(config) +end + +-- validate a RTU gateway configuration +---@param cfg rtu_config +function rtu.validate_config(cfg) local cfv = util.new_validator() - cfv.assert_type_num(config.SpeakerVolume) - cfv.assert_range(config.SpeakerVolume, 0, 3) + cfv.assert_type_num(cfg.SpeakerVolume) + cfv.assert_range(cfg.SpeakerVolume, 0, 3) - cfv.assert_channel(config.SVR_Channel) - cfv.assert_channel(config.RTU_Channel) - cfv.assert_type_num(config.ConnTimeout) - cfv.assert_min(config.ConnTimeout, 2) - cfv.assert_type_num(config.TrustedRange) - cfv.assert_min(config.TrustedRange, 0) - cfv.assert_type_str(config.AuthKey) + 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) - if type(config.AuthKey) == "string" then - local len = string.len(config.AuthKey) + if type(cfg.AuthKey) == "string" then + local len = string.len(cfg.AuthKey) cfv.assert(len == 0 or len >= 8) end - cfv.assert_type_int(config.LogMode) - cfv.assert_range(config.LogMode, 0, 1) - cfv.assert_type_str(config.LogPath) - cfv.assert_type_bool(config.LogDebug) + cfv.assert_type_int(cfg.LogMode) + cfv.assert_range(cfg.LogMode, 0, 1) + cfv.assert_type_str(cfg.LogPath) + cfv.assert_type_bool(cfg.LogDebug) - cfv.assert_type_int(config.FrontPanelTheme) - cfv.assert_range(config.FrontPanelTheme, 1, 2) - cfv.assert_type_int(config.ColorMode) - cfv.assert_range(config.ColorMode, 1, themes.COLOR_MODE.NUM_MODES) + cfv.assert_type_int(cfg.FrontPanelTheme) + cfv.assert_range(cfg.FrontPanelTheme, 1, 2) + cfv.assert_type_int(cfg.ColorMode) + cfv.assert_range(cfg.ColorMode, 1, themes.COLOR_MODE.NUM_MODES) - cfv.assert_type_table(config.Peripherals) - cfv.assert_type_table(config.Redstone) + cfv.assert_type_table(cfg.Peripherals) + cfv.assert_type_table(cfg.Redstone) return cfv.valid() end diff --git a/rtu/startup.lua b/rtu/startup.lua index dfef1b7..01a05a9 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.6" +local RTU_VERSION = "v1.11.7" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_HW_STATE = databus.RTU_HW_STATE From ad834218c28d28532d11beb3e868ce63f130cb26 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 3 Apr 2025 23:21:06 -0400 Subject: [PATCH 3/8] #364 check all configured RTU peripherals in self check --- rtu/config/check.lua | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/rtu/config/check.lua b/rtu/config/check.lua index 861895d..d0ff05c 100644 --- a/rtu/config/check.lua +++ b/rtu/config/check.lua @@ -118,19 +118,25 @@ local function self_check() self.self_check_pass = true + local cfg = self.settings local modem = ppm.get_wireless_modem() - local valid_cfg = rtu.validate_config(self.settings) + 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") + self.self_check_msg("> check configuration...", valid_cfg, "go through Configure Gateway and apply settings to set any missing settings and repair any corrupted ones (you may need to re-apply Peripheral and Redstone configurations as well)") - 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") + -- check configured peripherals + for i = 1, #cfg.Peripherals do + local peri = cfg.Peripherals[i] + self.self_check_msg("> check " .. peri.name .. " connected...", ppm.get_device(peri.name), "please connect this device via a wired modem or direct contact and ensure the configuration matches what it connects as") + end if valid_cfg and modem then self.self_check_msg("> check supervisor connection...") -- init mac as needed - if self.settings.AuthKey and string.len(self.settings.AuthKey) >= 8 then - network.init_mac(self.settings.AuthKey) + if cfg.AuthKey and string.len(cfg.AuthKey) >= 8 then + network.init_mac(cfg.AuthKey) else network.deinit_mac() end @@ -138,7 +144,7 @@ local function self_check() self.nic = network.nic(modem) self.nic.closeAll() - self.nic.open(self.settings.RTU_Channel) + self.nic.open(cfg.RTU_Channel) self.sv_addr = comms.BROADCAST self.net_listen = true From cd654fb9b8e947e68601c7cfb7cfac9507200f47 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 3 Apr 2025 23:21:31 -0400 Subject: [PATCH 4/8] shorter variable for self.settings in PLC self check --- reactor-plc/config/check.lua | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/reactor-plc/config/check.lua b/reactor-plc/config/check.lua index 43f28ba..91b71e6 100644 --- a/reactor-plc/config/check.lua +++ b/reactor-plc/config/check.lua @@ -120,11 +120,12 @@ local function self_check() self.self_check_pass = true + local cfg = self.settings local modem = ppm.get_wireless_modem() local reactor = ppm.get_fission_reactor() - local valid_cfg = plc.validate_config(self.settings) + local valid_cfg = plc.validate_config(cfg) - if self.settings.Networked then + 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") end @@ -135,12 +136,12 @@ 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 self.settings.Networked and valid_cfg and modem then + if cfg.Networked and valid_cfg and modem then self.self_check_msg("> check supervisor connection...") -- init mac as needed - if self.settings.AuthKey and string.len(self.settings.AuthKey) >= 8 then - network.init_mac(self.settings.AuthKey) + if cfg.AuthKey and string.len(cfg.AuthKey) >= 8 then + network.init_mac(cfg.AuthKey) else network.deinit_mac() end @@ -148,12 +149,12 @@ local function self_check() self.nic = network.nic(modem) self.nic.closeAll() - self.nic.open(self.settings.PLC_Channel) + 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, self.settings.UnitID }) + send_sv(MGMT_TYPE.ESTABLISH, { comms.version, "0.0.0", DEVICE_TYPE.PLC, cfg.UnitID }) tcd.dispatch_unique(8, handle_timeout) else From 2b3099ac59f73a7542159c82b3ee837d0c48bca4 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 4 Apr 2025 00:17:40 -0400 Subject: [PATCH 5/8] #364 check validity of redstone and peripheral entries and check redstone side/color combos are not repeated --- rtu/config/check.lua | 59 ++++++++++++++++++++++++++++++++++++++--- rtu/config/redstone.lua | 9 +++++++ 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/rtu/config/check.lua b/rtu/config/check.lua index d0ff05c..ff3a2e3 100644 --- a/rtu/config/check.lua +++ b/rtu/config/check.lua @@ -1,11 +1,14 @@ local comms = require("scada-common.comms") local network = require("scada-common.network") local ppm = require("scada-common.ppm") +local rsio = require("scada-common.rsio") local tcd = require("scada-common.tcd") local util = require("scada-common.util") local rtu = require("rtu.rtu") +local redstone = require("rtu.config.redstone") + local core = require("graphics.core") local Div = require("graphics.elements.Div") @@ -109,6 +112,13 @@ local function handle_timeout() 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") end + +-- check if a value is an integer within a range (inclusive) +---@param x any +---@param min integer +---@param max integer +local function is_int_min_max(x, min, max) return util.is_int(x) and x >= min and x <= max end + -- execute the self-check local function self_check() self.run_test_btn.disable() @@ -123,12 +133,53 @@ local function self_check() 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") - self.self_check_msg("> check configuration...", valid_cfg, "go through Configure Gateway and apply settings to set any missing settings and repair any corrupted ones (you may need to re-apply Peripheral and Redstone configurations as well)") + 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 configured peripherals + -- check redstone configurations + local ifaces = {} + for i = 1, #cfg.Redstone do + local entry = cfg.Redstone[i] + local ident = entry.side .. tri(entry.color, ":" .. rsio.color_name(entry.color), "") + local dupe = util.table_contains(ifaces, ident) + + self.self_check_msg("> check redstone " .. ident .. " unique...", not dupe, "only one port should be set to a side/color combination") + self.self_check_msg("> check redstone " .. ident .. " valid...", redstone.validate(entry), "configuration invalid, please re-configure redstone entry") + + table.insert(ifaces, ident) + end + + -- check peripheral configurations for i = 1, #cfg.Peripherals do - local peri = cfg.Peripherals[i] - self.self_check_msg("> check " .. peri.name .. " connected...", ppm.get_device(peri.name), "please connect this device via a wired modem or direct contact and ensure the configuration matches what it connects as") + local entry = cfg.Peripherals[i] + local valid = false + + if type(entry.name) == "string" then + self.self_check_msg("> check " .. entry.name .. " connected...", ppm.get_periph(entry.name), "please connect this device via a wired modem or direct contact and ensure the configuration matches what it connects as") + + local p_type = ppm.get_type(entry.name) + + if p_type == "boilerValve" then + valid = is_int_min_max(entry.index, 0, 2) and is_int_min_max(entry.unit, 1, 4) + elseif p_type == "turbineValve" then + valid = is_int_min_max(entry.index, 1, 3) and is_int_min_max(entry.unit, 1, 4) + elseif p_type == "solarNeutronActivator" then + valid = is_int_min_max(entry.unit, 1, 4) + elseif p_type == "dynamicValve" then + valid = (entry.unit == 0 and is_int_min_max(entry.index, 1, 4)) or is_int_min_max(entry.unit, 1, 4) + elseif p_type == "environmentDetector" then + valid = is_int_min_max(entry.unit, 0, 4) and util.is_int(entry.index) + else + valid = true + + if p_type ~= nil and not (p_type == "inductionPort" or p_type == "spsPort") then + self.self_check_msg("> check " .. entry.name .. " valid...", false, "unrecognized device type") + end + end + end + + if not valid then + self.self_check_msg("> check " .. entry.name .. " valid...", false, "configuration invalid, please re-configure peripheral entry") + end end if valid_cfg and modem then diff --git a/rtu/config/redstone.lua b/rtu/config/redstone.lua index 2d9e9f5..2ec8cab 100644 --- a/rtu/config/redstone.lua +++ b/rtu/config/redstone.lua @@ -107,6 +107,15 @@ end local redstone = {} +-- validate a redstone entry +---@param def rtu_rs_definition +function redstone.validate(def) + return tri(PORT_DSGN[def.port] == 1, util.is_int(def.unit) and def.unit > 0 and def.unit <= 4, def.unit == nil) and + rsio.is_valid_port(def.port) and + rsio.is_valid_side(def.side) and + (def.color == nil or (rsio.is_digital(def.port) and rsio.is_color(def.color))) +end + -- create the redstone configuration view ---@param tool_ctl _rtu_cfg_tool_ctl ---@param main_pane MultiPane From b4a9366f73ff35fcec70a6a65afdee8b10df9298 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 5 Apr 2025 21:00:16 -0400 Subject: [PATCH 6/8] #364 fixes to redstone and peripheral checks --- rtu/config/check.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rtu/config/check.lua b/rtu/config/check.lua index ff3a2e3..48d021f 100644 --- a/rtu/config/check.lua +++ b/rtu/config/check.lua @@ -159,15 +159,15 @@ local function self_check() local p_type = ppm.get_type(entry.name) if p_type == "boilerValve" then - valid = is_int_min_max(entry.index, 0, 2) and is_int_min_max(entry.unit, 1, 4) + valid = is_int_min_max(entry.index, 1, 2) and is_int_min_max(entry.unit, 1, 4) elseif p_type == "turbineValve" then valid = is_int_min_max(entry.index, 1, 3) and is_int_min_max(entry.unit, 1, 4) elseif p_type == "solarNeutronActivator" then valid = is_int_min_max(entry.unit, 1, 4) elseif p_type == "dynamicValve" then - valid = (entry.unit == 0 and is_int_min_max(entry.index, 1, 4)) or is_int_min_max(entry.unit, 1, 4) + valid = (entry.unit == nil and is_int_min_max(entry.index, 1, 4)) or is_int_min_max(entry.unit, 1, 4) elseif p_type == "environmentDetector" then - valid = is_int_min_max(entry.unit, 0, 4) and util.is_int(entry.index) + valid = (entry.unit == nil or is_int_min_max(entry.unit, 1, 4)) and util.is_int(entry.index) else valid = true From 1b692b5b9a888cccb1264927f10760c3666ab152 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 5 Apr 2025 21:14:33 -0400 Subject: [PATCH 7/8] #364 fail self check if using a side for both bundled and unbundled redstone --- rtu/config/check.lua | 6 ++++++ rtu/rtu.lua | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/rtu/config/check.lua b/rtu/config/check.lua index 48d021f..2a77f39 100644 --- a/rtu/config/check.lua +++ b/rtu/config/check.lua @@ -137,14 +137,20 @@ local function self_check() -- check redstone configurations local ifaces = {} + local bundled_sides = {} for i = 1, #cfg.Redstone do local entry = cfg.Redstone[i] local ident = entry.side .. tri(entry.color, ":" .. rsio.color_name(entry.color), "") local dupe = util.table_contains(ifaces, ident) + local mixed = (bundled_sides[entry.side] and (entry.color == nil)) or (bundled_sides[entry.side] == false and (entry.color ~= nil)) + + local mixed_msg = util.trinary(bundled_sides[entry.side], "this side has bundled entry(s) but this entry is not bundled", "this side has non-bundled entry(s) but this entry is bundled") self.self_check_msg("> check redstone " .. ident .. " unique...", not dupe, "only one port should be set to a side/color combination") + self.self_check_msg("> check redstone " .. ident .. " bundle...", not mixed, mixed_msg .. ", which will not work") self.self_check_msg("> check redstone " .. ident .. " valid...", redstone.validate(entry), "configuration invalid, please re-configure redstone entry") + bundled_sides[entry.side] = bundled_sides[entry.side] or entry.color ~= nil table.insert(ifaces, ident) end diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 9c8e38b..d7a576e 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -49,7 +49,7 @@ function rtu.load_config() return rtu.validate_config(config) end --- validate a RTU gateway configuration +-- validate an RTU gateway configuration ---@param cfg rtu_config function rtu.validate_config(cfg) local cfv = util.new_validator() From 0da944c3ea9da8ea44a80bf312a5422d676749a9 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 5 Apr 2025 21:17:19 -0400 Subject: [PATCH 8/8] #364 updated scroll height max and reduced duplicate text --- rtu/config/check.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rtu/config/check.lua b/rtu/config/check.lua index 2a77f39..6818ba6 100644 --- a/rtu/config/check.lua +++ b/rtu/config/check.lua @@ -144,10 +144,10 @@ local function self_check() local dupe = util.table_contains(ifaces, ident) local mixed = (bundled_sides[entry.side] and (entry.color == nil)) or (bundled_sides[entry.side] == false and (entry.color ~= nil)) - local mixed_msg = util.trinary(bundled_sides[entry.side], "this side has bundled entry(s) but this entry is not bundled", "this side has non-bundled entry(s) but this entry is bundled") + local mixed_msg = util.trinary(bundled_sides[entry.side], "bundled entry(s) but this entry is not", "non-bundled entry(s) but this entry is") self.self_check_msg("> check redstone " .. ident .. " unique...", not dupe, "only one port should be set to a side/color combination") - self.self_check_msg("> check redstone " .. ident .. " bundle...", not mixed, mixed_msg .. ", which will not work") + self.self_check_msg("> check redstone " .. ident .. " bundle...", not mixed, "this side has " .. mixed_msg .. " bundled, which will not work") self.self_check_msg("> check redstone " .. ident .. " valid...", redstone.validate(entry), "configuration invalid, please re-configure redstone entry") bundled_sides[entry.side] = bundled_sides[entry.side] or entry.color ~= nil @@ -245,7 +245,7 @@ function check.create(main_pane, settings_cfg, check_sys, style) TextBox{parent=check_sys,x=1,y=2,text=" RTU Gateway Self-Check",fg_bg=bw_fg_bg} - self.sc_log = ListBox{parent=sc,x=1,y=1,height=12,width=49,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} + self.sc_log = ListBox{parent=sc,x=1,y=1,height=12,width=49,scroll_height=500,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} local last_check = { nil, nil }