From 8b65956dcccf8238a028f39ba8e7c33ac96d61f7 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 15 Oct 2023 13:26:49 -0400 Subject: [PATCH 01/36] #306 base RTU configurator --- configure.lua | 4 +- rtu/configure.lua | 648 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 650 insertions(+), 2 deletions(-) create mode 100644 rtu/configure.lua diff --git a/configure.lua b/configure.lua index 5234f1d..972d16e 100644 --- a/configure.lua +++ b/configure.lua @@ -2,8 +2,8 @@ print("CONFIGURE> SCANNING FOR CONFIGURATOR...") if fs.exists("reactor-plc/configure.lua") then require("reactor-plc.configure").configure() -elseif fs.exists("rtu/startup.lua") then - print("CONFIGURE> RTU CONFIGURATOR NOT YET IMPLEMENTED IN BETA") +elseif fs.exists("rtu/configure.lua") then + require("rtu.configure").configure() elseif fs.exists("supervisor/startup.lua") then print("CONFIGURE> SUPERVISOR CONFIGURATOR NOT YET IMPLEMENTED IN BETA") elseif fs.exists("coordinator/startup.lua") then diff --git a/rtu/configure.lua b/rtu/configure.lua new file mode 100644 index 0000000..8a43558 --- /dev/null +++ b/rtu/configure.lua @@ -0,0 +1,648 @@ +-- +-- Configuration GUI +-- + +local log = require("scada-common.log") +local rsio = require("scada-common.rsio") +local tcd = require("scada-common.tcd") +local util = require("scada-common.util") + +local core = require("graphics.core") + +local DisplayBox = require("graphics.elements.displaybox") +local Div = require("graphics.elements.div") +local ListBox = require("graphics.elements.listbox") +local MultiPane = require("graphics.elements.multipane") +local TextBox = require("graphics.elements.textbox") + +local CheckBox = require("graphics.elements.controls.checkbox") +local PushButton = require("graphics.elements.controls.push_button") +local Radio2D = require("graphics.elements.controls.radio_2d") +local RadioButton = require("graphics.elements.controls.radio_button") + +local NumberField = require("graphics.elements.form.number_field") +local TextField = require("graphics.elements.form.text_field") + +local println = util.println + +local cpair = core.cpair + +local LEFT = core.ALIGN.LEFT +local CENTER = core.ALIGN.CENTER +local RIGHT = core.ALIGN.RIGHT + +-- changes to the config data/format to let the user know +local changes = {} + +---@class rtu_configurator +local configurator = {} + +local style = {} + +style.root = cpair(colors.black, colors.lightGray) +style.header = cpair(colors.white, colors.gray) + +style.colors = { + { c = colors.red, hex = 0xdf4949 }, + { c = colors.orange, hex = 0xffb659 }, + { c = colors.yellow, hex = 0xfffc79 }, + { c = colors.lime, hex = 0x80ff80 }, + { c = colors.green, hex = 0x4aee8a }, + { c = colors.cyan, hex = 0x34bac8 }, + { c = colors.lightBlue, hex = 0x6cc0f2 }, + { c = colors.blue, hex = 0x0096ff }, + { c = colors.purple, hex = 0xb156ee }, + { c = colors.pink, hex = 0xf26ba2 }, + { c = colors.magenta, hex = 0xf9488a }, + { c = colors.lightGray, hex = 0xcacaca }, + { c = colors.gray, hex = 0x575757 } +} + +local bw_fg_bg = cpair(colors.black, colors.white) +local g_lg_fg_bg = cpair(colors.gray, colors.lightGray) +local nav_fg_bg = bw_fg_bg +local btn_act_fg_bg = cpair(colors.white, colors.gray) + +local tool_ctl = { + ask_config = false, + has_config = false, + viewing_config = false, + importing_legacy = false, + + view_gw_cfg = nil, ---@type graphics_element + dev_cfg = nil, ---@type graphics_element + rs_cfg = nil, ---@type graphics_element + settings_apply = nil, ---@type graphics_element + + gen_summary = nil, ---@type function + show_current_cfg = nil, ---@type function + load_legacy = nil, ---@type function + + show_auth_key = nil, ---@type function + show_key_btn = nil, ---@type graphics_element + auth_key_textbox = nil, ---@type graphics_element + auth_key_value = "" +} + +---@class rtu_config +local tmp_cfg = { + SpeakerVolume = 1.0, + Peripherals = {}, + Redstone = {}, + SVR_Channel = nil, + RTU_Channel = nil, + ConnTimeout = nil, + TrustedRange = nil, + AuthKey = nil, + LogMode = 0, + LogPath = "", + LogDebug = false +} + +---@class rtu_config +local ini_cfg = {} + +local fields = { + { "SpeakerVolume", "Speaker Volume" }, + { "SVR_Channel", "SVR Channel" }, + { "RTU_Channel", "RTU Channel" }, + { "ConnTimeout", "Connection Timeout" }, + { "TrustedRange", "Trusted Range" }, + { "AuthKey", "Facility Auth Key" }, + { "LogMode", "Log Mode" }, + { "LogPath", "Log Path" }, + { "LogDebug","Log Debug Messages" } +} + +local side_options = { "Top", "Bottom", "Left", "Right", "Front", "Back" } +local side_options_map = { "top", "bottom", "left", "right", "front", "back" } +local color_options = { "Red", "Orange", "Yellow", "Lime", "Green", "Cyan", "Light Blue", "Blue", "Purple", "Magenta", "Pink", "White", "Light Gray", "Gray", "Black", "Brown" } +local color_options_map = { colors.red, colors.orange, colors.yellow, colors.lime, colors.green, colors.cyan, colors.lightBlue, colors.blue, colors.purple, colors.magenta, colors.pink, colors.white, colors.lightGray, colors.gray, colors.black, colors.brown } + +local color_name_map = { + [colors.red] = "red", + [colors.orange] = "orange", + [colors.yellow] = "yellow", + [colors.lime] = "lime", + [colors.green] = "green", + [colors.cyan] = "cyan", + [colors.lightBlue] = "lightBlue", + [colors.blue] = "blue", + [colors.purple] = "purple", + [colors.magenta] = "magenta", + [colors.pink] = "pink", + [colors.white] = "white", + [colors.lightGray] = "lightGray", + [colors.gray] = "gray", + [colors.black] = "black", + [colors.brown] = "brown" +} + +-- convert text representation to index +---@param side string +local function side_to_idx(side) + for k, v in ipairs(side_options_map) do + if v == side then return k end + end +end + +-- convert color to index +---@param color color +local function color_to_idx(color) + for k, v in ipairs(color_options_map) do + if v == color then return k end + end +end + +-- load data from the settings file +---@param target rtu_config +local function load_settings(target) + target.SpeakerVolume = settings.get("SpeakerVolume", 1.0) + target.Peripherals = settings.get("Peripherals", {}) + target.Redstone = settings.get("Redstone", {}) + + target.SVR_Channel = settings.get("SVR_Channel", 16240) + target.RTU_Channel = settings.get("RTU_Channel", 16242) + target.ConnTimeout = settings.get("ConnTimeout", 5) + target.TrustedRange = settings.get("TrustedRange", 0) + target.AuthKey = settings.get("AuthKey", "") + target.LogMode = settings.get("LogMode", log.MODE.APPEND) + target.LogPath = settings.get("LogPath", "/log.txt") + target.LogDebug = settings.get("LogDebug", false) +end + +-- create the config view +---@param display graphics_element +local function config_view(display) +---@diagnostic disable-next-line: undefined-field + local function exit() os.queueEvent("terminate") end + + TextBox{parent=display,y=1,text="RTU Gateway Configurator",alignment=CENTER,height=1,fg_bg=style.header} + + local root_pane_div = Div{parent=display,x=1,y=2} + + local main_page = Div{parent=root_pane_div,x=1,y=1} + local spkr_cfg = Div{parent=root_pane_div,x=1,y=1} + local net_cfg = Div{parent=root_pane_div,x=1,y=1} + local log_cfg = Div{parent=root_pane_div,x=1,y=1} + local summary = Div{parent=root_pane_div,x=1,y=1} + 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 main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,spkr_cfg,net_cfg,log_cfg,summary,changelog,peri_cfg,rs_cfg}} + + --#region MAIN PAGE + + local y_start = 5 + + TextBox{parent=main_page,x=2,y=2,height=2,text_align=CENTER,text="Welcome to the RTU gateway configurator! Please select one of the following options."} + + if tool_ctl.ask_config then + TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text_align=CENTER,text="Notice: This device has no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)} + y_start = y_start + 5 + end + + local function view_config() + tool_ctl.viewing_config = true + tool_ctl.gen_summary(ini_cfg) + tool_ctl.settings_apply.hide(true) + main_pane.set_value(5) + end + + if fs.exists("/rtu/config.lua") then + PushButton{parent=main_page,x=2,y=y_start,min_width=28,text="Import Legacy 'config.lua'",callback=function()tool_ctl.load_legacy()end,fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=btn_act_fg_bg} + y_start = y_start + 2 + end + + PushButton{parent=main_page,x=2,y=y_start,min_width=19,text="Configure Gateway",callback=function()main_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg} + tool_ctl.view_gw_cfg = PushButton{parent=main_page,x=2,y=y_start+2,min_width=28,text="View Gateway Configuration",callback=view_config,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} + tool_ctl.dev_cfg = PushButton{parent=main_page,x=2,y=y_start+4,min_width=18,text="RTU Unit Devices",callback=function()main_pane.set_value(7)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} + tool_ctl.rs_cfg = PushButton{parent=main_page,x=2,y=y_start+6,min_width=22,text="Redstone Connections",callback=function()main_pane.set_value(8)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} + + if not tool_ctl.has_config then + tool_ctl.view_gw_cfg.disable() + -- tool_ctl.dev_cfg.disable() + -- tool_ctl.rs_cfg.disable() + end + + 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} + PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(6)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + --#endregion + + --#region SPEAKER CONFIG + + local spkr_c = Div{parent=spkr_cfg,x=2,y=4,width=49} + + TextBox{parent=spkr_cfg,x=1,y=2,height=1,text_align=CENTER,text=" Speaker Configuration",fg_bg=cpair(colors.black,colors.cyan)} + + TextBox{parent=spkr_c,x=1,y=1,height=2,text_align=CENTER,text="Speakers can be connected to this RTU gateway without RTU unit configuration entries."} + TextBox{parent=spkr_c,x=1,y=4,height=3,text_align=CENTER,text="You can change the speaker audio volume from the default. The range is 0.0 to 3.0, where 1.0 is standard volume."} + + local s_vol = NumberField{parent=spkr_c,x=1,y=8,width=9,max_digits=7,allow_decimal=true,default=ini_cfg.SpeakerVolume,min=0,max=3,fg_bg=bw_fg_bg} + + TextBox{parent=spkr_c,x=1,y=10,height=3,text_align=CENTER,text="Note: alarm sine waves are at half scale so that multiple will be required to reach full scale.",fg_bg=g_lg_fg_bg} + + local s_vol_err = TextBox{parent=spkr_c,x=8,y=14,height=1,width=35,text_align=LEFT,text="Please set a volume.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + + local function submit_vol() + local vol = tonumber(s_vol.get_value()) + if vol ~= nil then + s_vol_err.hide(true) + tmp_cfg.SpeakerVolume = vol + main_pane.set_value(3) + else s_vol_err.show() end + end + + PushButton{parent=spkr_c,x=1,y=14,min_width=6,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=spkr_c,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_vol,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + --#endregion + + --#region NET CONFIG + + 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_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3}} + + TextBox{parent=net_cfg,x=1,y=2,height=1,text_align=CENTER,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)} + + TextBox{parent=net_c_1,x=1,y=1,height=1,text_align=CENTER,text="Please set the network channels below."} + TextBox{parent=net_c_1,x=1,y=3,height=4,text_align=CENTER,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=8,height=1,text_align=CENTER,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_align=CENTER,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_1,x=1,y=11,height=1,text_align=CENTER,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_align=CENTER,text="[RTU_CHANNEL]",fg_bg=g_lg_fg_bg} + + local chan_err = TextBox{parent=net_c_1,x=8,y=14,height=1,width=35,text_align=LEFT,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + + local function submit_channels() + local svr_c = tonumber(svr_chan.get_value()) + local rtu_c = tonumber(rtu_chan.get_value()) + 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) + chan_err.hide(true) + elseif svr_c == nil then + chan_err.set_value("Please set the supervisor channel.") + chan_err.show() + else + chan_err.set_value("Please set the RTU channel.") + chan_err.show() + end + end + + PushButton{parent=net_c_1,x=1,y=14,min_width=6,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,min_width=6,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,height=1,text_align=CENTER,text="Connection Timeout"} + local timeout = NumberField{parent=net_c_2,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,fg_bg=bw_fg_bg} + TextBox{parent=net_c_2,x=9,y=2,height=2,text_align=CENTER,text="seconds (default 5)",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_2,x=1,y=3,height=4,text_align=CENTER,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,height=1,text_align=CENTER,text="Trusted Range"} + local range = NumberField{parent=net_c_2,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_digits=20,allow_decimal=true,fg_bg=bw_fg_bg} + TextBox{parent=net_c_2,x=1,y=10,height=4,text_align=CENTER,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,height=1,width=35,text_align=LEFT,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() + else + p2_err.set_value("Please set the trusted range.") + p2_err.show() + end + end + + PushButton{parent=net_c_2,x=1,y=14,min_width=6,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,min_width=6,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_align=CENTER,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_align=CENTER,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 compution (can slow things down).",fg_bg=g_lg_fg_bg} + + TextBox{parent=net_c_3,x=1,y=11,height=1,text_align=CENTER,text="Facility Auth Key"} + local key, _, censor = 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} + + local function censor_key(enable) censor(util.trinary(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} + + hide_key.set_value(true) + censor_key(true) + + local key_err = TextBox{parent=net_c_3,x=8,y=14,height=1,width=35,text_align=LEFT,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() + if string.len(v) == 0 or string.len(v) >= 8 then + tmp_cfg.AuthKey = key.get_value() + main_pane.set_value(4) + key_err.hide(true) + else key_err.show() end + end + + PushButton{parent=net_c_3,x=1,y=14,min_width=6,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,min_width=6,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + --#endregion + + --#region LOG CONFIG + + local log_c_1 = Div{parent=log_cfg,x=2,y=4,width=49} + + TextBox{parent=log_cfg,x=1,y=2,height=1,text_align=CENTER,text=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)} + + TextBox{parent=log_c_1,x=1,y=1,height=1,text_align=CENTER,text="Please configure logging below."} + + TextBox{parent=log_c_1,x=1,y=3,height=1,text_align=CENTER,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} + + TextBox{parent=log_c_1,x=1,y=7,height=1,text_align=CENTER,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} + + local en_dbg = CheckBox{parent=log_c_1,x=1,y=10,default=ini_cfg.LogDebug,label="Enable Logging Debug Messages",box_fg_bg=cpair(colors.pink,colors.black)} + TextBox{parent=log_c_1,x=3,y=11,height=2,text_align=CENTER,text="This results in much larger log files. It is best to only use this when there is a problem.",fg_bg=g_lg_fg_bg} + + local path_err = TextBox{parent=log_c_1,x=8,y=14,height=1,width=35,text_align=LEFT,text="Please provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + + local function submit_log() + if path.get_value() ~= "" then + path_err.hide(true) + tmp_cfg.LogMode = mode.get_value() - 1 + tmp_cfg.LogPath = path.get_value() + tmp_cfg.LogDebug = en_dbg.get_value() + tool_ctl.gen_summary(tmp_cfg) + tool_ctl.viewing_config = false + tool_ctl.importing_legacy = false + tool_ctl.settings_apply.show() + main_pane.set_value(5) + else path_err.show() end + end + + PushButton{parent=log_c_1,x=1,y=14,min_width=6,text="\x1b Back",callback=function()main_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=log_c_1,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_log,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + --#endregion + + --#region SUMMARY OF CHANGES + + local sum_c_1 = Div{parent=summary,x=2,y=4,width=49} + local sum_c_2 = Div{parent=summary,x=2,y=4,width=49} + local sum_c_3 = Div{parent=summary,x=2,y=4,width=49} + local sum_c_4 = Div{parent=summary,x=2,y=4,width=49} + + local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4}} + + TextBox{parent=summary,x=1,y=2,height=1,text_align=CENTER,text=" Summary",fg_bg=cpair(colors.black,colors.green)} + + local setting_list = ListBox{parent=sum_c_1,x=1,y=1,height=12,width=51,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} + + local function back_from_settings() + if tool_ctl.viewing_config or tool_ctl.importing_legacy then + main_pane.set_value(1) + tool_ctl.viewing_config = false + tool_ctl.importing_legacy = false + tool_ctl.settings_apply.show() + else + main_pane.set_value(4) + end + end + + ---@param element graphics_element + ---@param data any + local function try_set(element, data) + if data ~= nil then element.set_value(data) end + end + + local function save_and_continue() + for k, v in pairs(tmp_cfg) do settings.set(k, v) end + + if settings.save("rtu.settings") then + load_settings(ini_cfg) + + try_set(s_vol, ini_cfg.SpeakerVolume) + 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(key, ini_cfg.AuthKey) + try_set(mode, ini_cfg.LogMode) + try_set(path, ini_cfg.LogPath) + try_set(en_dbg, ini_cfg.LogDebug) + + if tool_ctl.importing_legacy then + tool_ctl.importing_legacy = false + sum_pane.set_value(3) + else sum_pane.set_value(2) end + else sum_pane.set_value(4) end + end + + PushButton{parent=sum_c_1,x=1,y=14,min_width=6,text="\x1b Back",callback=back_from_settings,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + tool_ctl.show_key_btn = PushButton{parent=sum_c_1,x=8,y=14,min_width=17,text="Unhide Auth Key",callback=function()tool_ctl.show_auth_key()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} + tool_ctl.settings_apply = PushButton{parent=sum_c_1,x=43,y=14,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg} + + TextBox{parent=sum_c_2,x=1,y=1,height=1,text_align=CENTER,text="Settings saved!"} + + local function go_home() + main_pane.set_value(1) + net_pane.set_value(1) + sum_pane.set_value(1) + end + + PushButton{parent=sum_c_2,x=1,y=14,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=sum_c_2,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} + + TextBox{parent=sum_c_3,x=1,y=1,height=2,text_align=CENTER,text="The old config.lua file will now be deleted, then the configurator will exit."} + + local function delete_legacy() + fs.delete("/rtu/config.lua") + exit() + end + + PushButton{parent=sum_c_3,x=1,y=14,min_width=8,text="Cancel",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=sum_c_3,x=44,y=14,min_width=6,text="OK",callback=delete_legacy,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.gray)} + + TextBox{parent=sum_c_4,x=1,y=1,height=5,text_align=CENTER,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."} + + PushButton{parent=sum_c_4,x=1,y=14,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=sum_c_4,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} + + --#endregion + + --#region CONFIG CHANGE LOG + + local cl = Div{parent=changelog,x=2,y=4,width=49} + + TextBox{parent=changelog,x=1,y=2,height=1,text_align=CENTER,text=" Config Change Log",fg_bg=bw_fg_bg} + + local c_log = ListBox{parent=cl,x=1,y=1,height=12,width=51,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} + + for _, change in ipairs(changes) do + TextBox{parent=c_log,text=change[1],height=1,fg_bg=bw_fg_bg} + for _, v in ipairs(change[2]) do + local e = Div{parent=c_log,height=#util.strwrap(v,46)} + TextBox{parent=e,y=1,x=1,text="- ",height=1,fg_bg=cpair(colors.gray,colors.white)} + TextBox{parent=e,y=1,x=3,text=v,height=e.get_height(),fg_bg=cpair(colors.gray,colors.white)} + end + end + + PushButton{parent=cl,x=1,y=14,min_width=6,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + --#endregion + + -- set tool functions now that we have the elements + + -- load a legacy config file + function tool_ctl.load_legacy() + local config = require("rtu.config") + + tmp_cfg.SpeakerVolume = config.SOUNDER_VOLUME + tmp_cfg.SVR_Channel = config.SVR_CHANNEL + tmp_cfg.RTU_Channel = config.RTU_CHANNEL + tmp_cfg.ConnTimeout = config.COMMS_TIMEOUT + tmp_cfg.TrustedRange = config.TRUSTED_RANGE + tmp_cfg.AuthKey = config.AUTH_KEY or "" + tmp_cfg.LogMode = config.LOG_MODE + tmp_cfg.LogPath = config.LOG_PATH + tmp_cfg.LogDebug = config.LOG_DEBUG or false + + tool_ctl.gen_summary(tmp_cfg) + sum_pane.set_value(1) + main_pane.set_value(5) + tool_ctl.importing_legacy = true + end + + -- expose the auth key on the summary page + function tool_ctl.show_auth_key() + tool_ctl.show_key_btn.disable() + tool_ctl.auth_key_textbox.set_value(tool_ctl.auth_key_value) + end + + -- generate the summary list + ---@param cfg rtu_config + function tool_ctl.gen_summary(cfg) + setting_list.remove_all() + + local alternate = false + local inner_width = setting_list.get_width() - 1 + + tool_ctl.show_key_btn.enable() + tool_ctl.auth_key_value = cfg.AuthKey or "" -- to show auth key + + for i = 1, #fields do + local f = fields[i] + local height = 1 + local label_w = string.len(f[2]) + local val_max_w = (inner_width - label_w) + 1 + local raw = cfg[f[1]] + local val = util.strval(raw) + + if f[1] == "AuthKey" then val = string.rep("*", string.len(val)) end + if f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") end + if val == "nil" then val = "n/a" end + + local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white)) + alternate = not alternate + + if string.len(val) > val_max_w then + local lines = util.strwrap(val, inner_width) + height = #lines + 1 + end + + local line = Div{parent=setting_list,height=height,fg_bg=c} + TextBox{parent=line,text=f[2],width=string.len(f[2]),fg_bg=cpair(colors.black,line.get_fg_bg().bkg)} + + local textbox + if height > 1 then + textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1,alignment=LEFT} + else + textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT} + end + + if f[1] == "AuthKey" then tool_ctl.auth_key_textbox = textbox end + end + end +end + +-- reset terminal screen +local function reset_term() + term.setTextColor(colors.white) + term.setBackgroundColor(colors.black) + term.clear() + term.setCursorPos(1, 1) +end + +-- run the RTU gateway configurator +---@param ask_config? boolean indicate if this is being called by the RTU startup app due to an invalid configuration +function configurator.configure(ask_config) + tool_ctl.ask_config = ask_config == true + tool_ctl.has_config = settings.load("/rtu.settings") + + load_settings(ini_cfg) + + reset_term() + + -- set overridden colors + for i = 1, #style.colors do + term.setPaletteColor(style.colors[i].c, style.colors[i].hex) + end + + local status, error = pcall(function () + local display = DisplayBox{window=term.current(),fg_bg=style.root} + config_view(display) + + while true do + local event, param1, param2, param3 = util.pull_event() + + -- handle event + if event == "timer" then + -- notify timer callback dispatcher + tcd.handle(param1) + 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 + local m_e = core.events.new_mouse_event(event, param1, param2, param3) + if m_e then display.handle_mouse(m_e) end + elseif event == "char" or event == "key" or event == "key_up" then + -- handle a key event + local k_e = core.events.new_key_event(event, param1, param2) + if k_e then display.handle_key(k_e) end + elseif event == "paste" then + -- handle a paste event + display.handle_paste(param1) + end + + if event == "terminate" then return end + end + end) + + -- restore colors + for i = 1, #style.colors do + local r, g, b = term.nativePaletteColor(style.colors[i].c) + term.setPaletteColor(style.colors[i].c, r, g, b) + end + + reset_term() + if not status then + println("configurator error: " .. error) + end + + return status, error +end + +return configurator From 43e545b6ae5097673825b7eec3b32903a4377639 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 15 Oct 2023 16:49:03 -0400 Subject: [PATCH 02/36] fixed unfocus all --- graphics/core.lua | 2 +- graphics/element.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/graphics/core.lua b/graphics/core.lua index d6518ba..db2585b 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -7,7 +7,7 @@ local flasher = require("graphics.flasher") local core = {} -core.version = "2.0.2" +core.version = "2.0.3" core.flasher = flasher core.events = events diff --git a/graphics/element.lua b/graphics/element.lua index dcdccc4..1044799 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -666,7 +666,7 @@ function element.new(args, child_offset_x, child_offset_y) -- unfocus this element and all its children function public.unfocus_all() public.unfocus() - for _, child in pairs(protected.children) do child.get().unfocus() end + for _, child in pairs(protected.children) do child.get().unfocus_all() end end -- custom recolor command, varies by element if implemented From 01caca48dc26265812a09be7781c5ec03a8b2f63 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 15 Oct 2023 17:02:48 -0400 Subject: [PATCH 03/36] listbox improvements, tabbing while staying in frame (autoscroll) --- graphics/core.lua | 2 +- graphics/element.lua | 20 ++++++++++++++--- graphics/elements/listbox.lua | 41 ++++++++++++++++++++++++++++++----- 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/graphics/core.lua b/graphics/core.lua index db2585b..9e31101 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -7,7 +7,7 @@ local flasher = require("graphics.flasher") local core = {} -core.version = "2.0.3" +core.version = "2.0.4" core.flasher = flasher core.events = events diff --git a/graphics/element.lua b/graphics/element.lua index 1044799..517e230 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -105,6 +105,7 @@ function element.new(args, child_offset_x, child_offset_y) value = nil, ---@type any window = nil, ---@type table content_window = nil, ---@type table|nil + mouse_window_shift = { x = 0, y = 0 }, fg_bg = core.cpair(colors.white, colors.black), frame = core.gframe(1, 1, 1, 1), children = {}, @@ -344,6 +345,10 @@ function element.new(args, child_offset_x, child_offset_y) -- handle this element having been unfocused function protected.on_unfocused() end + -- handle this element having had a child focused + ---@param child graphics_element + function protected.on_child_focused(child) end + -- handle this element having been shown function protected.on_shown() end @@ -520,6 +525,13 @@ function element.new(args, child_offset_x, child_offset_y) else args.parent.__focus_child(child) end end + -- a child was focused, used to make sure it is actually visible to the user in the content frame + ---@param child graphics_element + function public.__child_focused(child) + protected.on_child_focused(child) + if not self.is_root then args.parent.__child_focused(public) end + end + -- get a child element ---@nodiscard ---@param id element_id @@ -652,6 +664,7 @@ function element.new(args, child_offset_x, child_offset_y) if args.can_focus and protected.enabled and not self.focused then self.focused = true protected.on_focused() + if not self.is_root then args.parent.__child_focused(public) end end end @@ -704,10 +717,11 @@ function element.new(args, child_offset_x, child_offset_y) end local event_T = events.mouse_transposed(event, self.position.x, self.position.y) - - -- handle the mouse event then pass to children protected.handle_mouse(event_T) - for _, child in pairs(protected.children) do child.get().handle_mouse(event_T) end + + -- shift child event if the content window has moved then pass to children + local c_event_T = events.mouse_transposed(event_T, protected.mouse_window_shift.x + 1, protected.mouse_window_shift.y + 1) + for _, child in pairs(protected.children) do child.get().handle_mouse(c_event_T) end elseif event.type == events.MOUSE_CLICK.DOWN or event.type == events.MOUSE_CLICK.TAP then -- clicked out, unfocus this element and children public.unfocus_all() diff --git a/graphics/elements/listbox.lua b/graphics/elements/listbox.lua index d138e19..a8f9929 100644 --- a/graphics/elements/listbox.lua +++ b/graphics/elements/listbox.lua @@ -158,6 +158,9 @@ local function listbox(args) scroll_frame.reposition(1, 1 + scroll_offset) scroll_frame.setVisible(true) + -- shift mouse events + e.mouse_window_shift.y = scroll_offset + draw_bar() end @@ -219,6 +222,28 @@ local function listbox(args) end end + -- handle a child in the list being focused, make sure it is visible + function e.on_child_focused(child) + for i = 1, #list do + local item = list[i] ---@type listbox_item + if item.e == child then + if (item.y + scroll_offset) <= 0 then + scroll_offset = 1 - item.y + update_positions() + draw_bar() + elseif (item.y + scroll_offset) == 1 then + -- do nothing, it's right at the top (if the bottom doesn't fit we can't easily fix that) + elseif ((item.h + item.y - 1) + scroll_offset) > e.frame.h then + scroll_offset = 1 - ((item.h + item.y) - e.frame.h) + update_positions() + draw_bar() + end + + return + end + end + end + -- handle mouse interaction ---@param event mouse_interaction mouse event function e.handle_mouse(event) @@ -226,23 +251,27 @@ local function listbox(args) if event.type == MOUSE_CLICK.TAP then if event.current.x == e.frame.w then if event.current.y == 1 or event.current.y < bar_bounds[1] then - draw_arrows(1) scroll_up() - if args.nav_active ~= nil then tcd.dispatch(0.25, function () draw_arrows(0) end) end + if event.current.y == 1 then + draw_arrows(1) + if args.nav_active ~= nil then tcd.dispatch(0.25, function () draw_arrows(0) end) end + end elseif event.current.y == e.frame.h or event.current.y > bar_bounds[2] then - draw_arrows(-1) scroll_down() - if args.nav_active ~= nil then tcd.dispatch(0.25, function () draw_arrows(0) end) end + if event.current.y == e.frame.h then + draw_arrows(-1) + if args.nav_active ~= nil then tcd.dispatch(0.25, function () draw_arrows(0) end) end + end end end elseif event.type == MOUSE_CLICK.DOWN then if event.current.x == e.frame.w then if event.current.y == 1 or event.current.y < bar_bounds[1] then - draw_arrows(1) scroll_up() + if event.current.y == 1 then draw_arrows(1) end elseif event.current.y == e.frame.h or event.current.y > bar_bounds[2] then - draw_arrows(-1) scroll_down() + if event.current.y == e.frame.h then draw_arrows(-1) end else -- clicked on bar holding_bar = true From d77a527b15610f3bc5884f1d397b3149e19339fe Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 19 Oct 2023 23:20:04 -0400 Subject: [PATCH 04/36] added text alignment to push buttons and added keyboard events to listbox --- graphics/core.lua | 2 +- graphics/elements/controls/push_button.lua | 12 +++++++++- graphics/elements/form/number_field.lua | 3 +++ graphics/elements/listbox.lua | 27 +++++++++++++++++++++- 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/graphics/core.lua b/graphics/core.lua index 9e31101..a6fead5 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -7,7 +7,7 @@ local flasher = require("graphics.flasher") local core = {} -core.version = "2.0.4" +core.version = "2.0.5" core.flasher = flasher core.events = events diff --git a/graphics/elements/controls/push_button.lua b/graphics/elements/controls/push_button.lua index 59e2b9e..88c8a5d 100644 --- a/graphics/elements/controls/push_button.lua +++ b/graphics/elements/controls/push_button.lua @@ -5,6 +5,8 @@ local tcd = require("scada-common.tcd") local core = require("graphics.core") local element = require("graphics.element") +local ALIGN = core.ALIGN + local MOUSE_CLICK = core.events.MOUSE_CLICK local KEY_CLICK = core.events.KEY_CLICK @@ -12,6 +14,7 @@ local KEY_CLICK = core.events.KEY_CLICK ---@field text string button text ---@field callback function function to call on touch ---@field min_width? integer text length if omitted +---@field alignment? ALIGN text align if min width > length ---@field active_fg_bg? cpair foreground/background colors when pressed ---@field dis_fg_bg? cpair foreground/background colors when disabled ---@field parent graphics_element @@ -31,6 +34,7 @@ local function push_button(args) element.assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0), "min_width must be nil or a number > 0") local text_width = string.len(args.text) + local alignment = args.alignment or ALIGN.CENTER -- set automatic settings args.can_focus = true @@ -41,9 +45,15 @@ local function push_button(args) -- create new graphics element base object local e = element.new(args) - local h_pad = math.floor((e.frame.w - text_width) / 2) + 1 + local h_pad = 1 local v_pad = math.floor(e.frame.h / 2) + 1 + if alignment == ALIGN.CENTER then + h_pad = math.floor((e.frame.w - text_width) / 2) + 1 + elseif alignment == ALIGN.RIGHT then + h_pad = (e.frame.w - text_width) + 1 + end + -- draw the button function e.redraw() e.window.clear() diff --git a/graphics/elements/form/number_field.lua b/graphics/elements/form/number_field.lua index 53fc473..7e1afd8 100644 --- a/graphics/elements/form/number_field.lua +++ b/graphics/elements/form/number_field.lua @@ -133,6 +133,9 @@ local function number_field(args) elseif type(args.min) == "number" and val < min then e.value = "" .. min ifield.nav_start() + else + e.value = "" .. val + ifield.nav_end() end else e.value = "" diff --git a/graphics/elements/listbox.lua b/graphics/elements/listbox.lua index a8f9929..3da9ac6 100644 --- a/graphics/elements/listbox.lua +++ b/graphics/elements/listbox.lua @@ -5,6 +5,7 @@ local tcd = require("scada-common.tcd") local core = require("graphics.core") local element = require("graphics.element") +local KEY_CLICK = core.events.KEY_CLICK local MOUSE_CLICK = core.events.MOUSE_CLICK ---@class listbox_args @@ -33,6 +34,8 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK ---@param args listbox_args ---@return graphics_element element, element_id id local function listbox(args) + args.can_focus = true + -- create new graphics element base object local e = element.new(args) @@ -128,7 +131,7 @@ local function listbox(args) end e.w_set_cur(e.frame.w, i) - e.w_write(" ") + if e.is_focused() then e.w_write("\x7f") else e.w_write(" ") end end e.w_set_bkg(e.fg_bg.bkg) @@ -222,6 +225,10 @@ local function listbox(args) end end + -- handle focus + e.on_focused = draw_bar + e.on_unfocused = draw_bar + -- handle a child in the list being focused, make sure it is visible function e.on_child_focused(child) for i = 1, #list do @@ -303,6 +310,24 @@ local function listbox(args) end end + -- handle keyboard interaction + ---@param event key_interaction key event + function e.handle_key(event) + if event.type == KEY_CLICK.DOWN or event.type == KEY_CLICK.HELD then + if event.key == keys.up then + scroll_up() + elseif event.key == keys.down then + scroll_down() + elseif event.key == keys.home then + scroll_offset = 0 + update_positions() + elseif event.key == keys["end"] then + scroll_offset = max_down_scroll + update_positions() + end + end + end + -- element redraw function e.redraw() draw_arrows(0) From ff4a5a68d9f8da1645658bba981a3ce56826c432 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 19 Oct 2023 23:20:41 -0400 Subject: [PATCH 05/36] reactor PLC configurator emercoolcolor correction --- reactor-plc/configure.lua | 2 +- reactor-plc/startup.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index 4347a7f..be041d5 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -301,7 +301,7 @@ local function config_view(display) local function submit_emcool() tmp_cfg.EmerCoolSide = side_options_map[side.get_value()] - tmp_cfg.EmerCoolColor = color_options_map[color.get_value()] + tmp_cfg.EmerCoolColor = util.trinary(bundled.get_value(), color_options_map[color.get_value()], nil) next_from_plc() end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 9f6a8a9..683b405 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.6.2" +local R_PLC_VERSION = "v1.6.3" local println = util.println local println_ts = util.println_ts From 4869c00c0ead99e109bad5ee57f6521ba76f7a6b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 19 Oct 2023 23:22:04 -0400 Subject: [PATCH 06/36] added side type alias and added some validation to RSIO --- scada-common/rsio.lua | 75 +++++++++++++++++++++++++----------------- scada-common/types.lua | 8 +++++ scada-common/util.lua | 2 +- 3 files changed, 53 insertions(+), 32 deletions(-) diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 4d98c2f..92ee280 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -82,46 +82,59 @@ rsio.IO_LVL = IO_LVL rsio.IO_DIR = IO_DIR rsio.IO_MODE = IO_MODE rsio.IO = IO_PORT +rsio.NUM_PORTS = IO_PORT.U_EMER_COOL + +-- self checks + +local dup_chk = {} +for _, v in pairs(IO_PORT) do + assert(dup_chk[v] ~= true, "duplicate in port list") + dup_chk[v] = true +end + +assert(#dup_chk == rsio.NUM_PORTS, "port list malformed") --#endregion --#region Utility Functions +local PORT_NAMES = { + "F_SCRAM", + "F_ACK", + "R_SCRAM", + "R_RESET", + "R_ENABLE", + "U_ACK", + "F_ALARM", + "F_ALARM_ANY", + "WASTE_PU", + "WASTE_PO", + "WASTE_POPL", + "WASTE_AM", + "R_ACTIVE", + "R_AUTO_CTRL", + "R_SCRAMMED", + "R_AUTO_SCRAM", + "R_HIGH_DMG", + "R_HIGH_TEMP", + "R_LOW_COOLANT", + "R_EXCESS_HC", + "R_EXCESS_WS", + "R_INSUFF_FUEL", + "R_PLC_FAULT", + "R_PLC_TIMEOUT", + "U_ALARM", + "U_EMER_COOL" +} + +assert(rsio.NUM_PORTS == #PORT_NAMES, "port names length incorrect") + -- port to string ---@nodiscard ---@param port IO_PORT function rsio.to_string(port) - local names = { - "F_SCRAM", - "F_ACK", - "R_SCRAM", - "R_RESET", - "R_ENABLE", - "U_ACK", - "F_ALARM", - "F_ALARM_ANY", - "WASTE_PU", - "WASTE_PO", - "WASTE_POPL", - "WASTE_AM", - "R_ACTIVE", - "R_AUTO_CTRL", - "R_SCRAMMED", - "R_AUTO_SCRAM", - "R_HIGH_DMG", - "R_HIGH_TEMP", - "R_LOW_COOLANT", - "R_EXCESS_HC", - "R_EXCESS_WS", - "R_INSUFF_FUEL", - "R_PLC_FAULT", - "R_PLC_TIMEOUT", - "U_ALARM", - "U_EMER_COOL" - } - - if util.is_int(port) and port > 0 and port <= #names then - return names[port] + if util.is_int(port) and port > 0 and port <= #PORT_NAMES then + return PORT_NAMES[port] else return "UNKNOWN" end diff --git a/scada-common/types.lua b/scada-common/types.lua index da872a2..2a1c17c 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -252,6 +252,14 @@ types.ALARM_STATE_NAMES = { -- STRING TYPES -- --#region +---@alias side +---|"top" +---|"bottom" +---|"left" +---|"right" +---|"front" +---|"back" + ---@alias os_event ---| "alarm" ---| "char" diff --git a/scada-common/util.lua b/scada-common/util.lua index eb3fdab..01d9adf 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -18,7 +18,7 @@ local type = type local util = {} -- scada-common version -util.version = "1.1.5" +util.version = "1.1.6" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 From 7b374f86182051833984575492d2fef57c71fb23 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 19 Oct 2023 23:35:18 -0400 Subject: [PATCH 07/36] rtu redstone configuration --- rtu/configure.lua | 330 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 319 insertions(+), 11 deletions(-) diff --git a/rtu/configure.lua b/rtu/configure.lua index 8a43558..393e087 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -27,13 +27,57 @@ local println = util.println local cpair = core.cpair +local IO = rsio.IO + local LEFT = core.ALIGN.LEFT local CENTER = core.ALIGN.CENTER local RIGHT = core.ALIGN.RIGHT +-- rsio port descriptions +local PORT_DESC = { + "Facility SCRAM", + "Facility Acknowledge", + "Reactor SCRAM", + "Reactor RPS Reset", + "Reactor Enable", + "Unit Acknowledge", + "Facility Alarm (high prio)", + "Facility Alarm (any)", + "Waste Plutonium Valve", + "Waste Polonium Valve", + "Waste Po Pellets Valve", + "Waste Antimatter Valve", + "Reactor Active", + "Reactor in Auto Control", + "RPS Tripped", + "RPS Auto SCRAM", + "RPS High Damage", + "RPS High Temperature", + "RPS Low Coolant", + "RPS Excess Heated Coolant", + "RPS Excess Waste", + "RPS Insufficient Fuel", + "RPS PLC Fault", + "RPS Supervisor Timeout", + "Unit Alarm", + "Unit Emergency Cool. Valve" +} + +-- designation (0 = facility, 1 = unit) +local PORT_DSGN = { [-1] = 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } + +assert(#PORT_DESC == rsio.NUM_PORTS) +assert(#PORT_DSGN == rsio.NUM_PORTS) + -- changes to the config data/format to let the user know local changes = {} +---@class rtu_rs_definition +---@field unit integer|nil +---@field port IO_PORT +---@field side side +---@field color color|nil + ---@class rtu_configurator local configurator = {} @@ -68,20 +112,29 @@ local tool_ctl = { has_config = false, viewing_config = false, importing_legacy = false, + rs_cfg_editing = false, ---@type integer|false view_gw_cfg = nil, ---@type graphics_element dev_cfg = nil, ---@type graphics_element rs_cfg = nil, ---@type graphics_element settings_apply = nil, ---@type graphics_element + go_home = nil, ---@type function gen_summary = nil, ---@type function show_current_cfg = nil, ---@type function load_legacy = nil, ---@type function + gen_rs_summary = nil, ---@type function show_auth_key = nil, ---@type function show_key_btn = nil, ---@type graphics_element auth_key_textbox = nil, ---@type graphics_element - auth_key_value = "" + auth_key_value = "", + + rs_cfg_selection = nil, ---@type graphics_element + rs_cfg_unit_l = nil, ---@type graphics_element + rs_cfg_unit = nil, ---@type graphics_element + rs_cfg_color = nil, ---@type graphics_element + rs_cfg_shortcut = nil ---@type graphics_element } ---@class rtu_config @@ -154,6 +207,15 @@ local function color_to_idx(color) end end +-- deep copy a redstone definitions table +local function deep_copy_rs(data) + local array = {} + for _, d in ipairs(data) do + table.insert(array, { unit = d.unit, port = d.port, side = d.side, color = d.color }) + end + return array +end + -- load data from the settings file ---@param target rtu_config local function load_settings(target) @@ -215,10 +277,15 @@ local function config_view(display) y_start = y_start + 2 end + local function show_rs_conns() + tool_ctl.gen_rs_summary(ini_cfg) + main_pane.set_value(8) + end + PushButton{parent=main_page,x=2,y=y_start,min_width=19,text="Configure Gateway",callback=function()main_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg} tool_ctl.view_gw_cfg = PushButton{parent=main_page,x=2,y=y_start+2,min_width=28,text="View Gateway Configuration",callback=view_config,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} tool_ctl.dev_cfg = PushButton{parent=main_page,x=2,y=y_start+4,min_width=18,text="RTU Unit Devices",callback=function()main_pane.set_value(7)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} - tool_ctl.rs_cfg = PushButton{parent=main_page,x=2,y=y_start+6,min_width=22,text="Redstone Connections",callback=function()main_pane.set_value(8)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} + tool_ctl.rs_cfg = PushButton{parent=main_page,x=2,y=y_start+6,min_width=22,text="Redstone Connections",callback=show_rs_conns,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} if not tool_ctl.has_config then tool_ctl.view_gw_cfg.disable() @@ -459,13 +526,7 @@ local function config_view(display) TextBox{parent=sum_c_2,x=1,y=1,height=1,text_align=CENTER,text="Settings saved!"} - local function go_home() - main_pane.set_value(1) - net_pane.set_value(1) - sum_pane.set_value(1) - end - - PushButton{parent=sum_c_2,x=1,y=14,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=sum_c_2,x=1,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=sum_c_2,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} TextBox{parent=sum_c_3,x=1,y=1,height=2,text_align=CENTER,text="The old config.lua file will now be deleted, then the configurator will exit."} @@ -475,12 +536,12 @@ local function config_view(display) exit() end - PushButton{parent=sum_c_3,x=1,y=14,min_width=8,text="Cancel",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=sum_c_3,x=1,y=14,min_width=8,text="Cancel",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=sum_c_3,x=44,y=14,min_width=6,text="OK",callback=delete_legacy,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.gray)} TextBox{parent=sum_c_4,x=1,y=1,height=5,text_align=CENTER,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."} - PushButton{parent=sum_c_4,x=1,y=14,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=sum_c_4,x=1,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=sum_c_4,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} --#endregion @@ -506,6 +567,179 @@ local function config_view(display) --#endregion + --#region REDSTONE + + local rs_c_1 = Div{parent=rs_cfg,x=2,y=4,width=49} + local rs_c_2 = Div{parent=rs_cfg,x=2,y=4,width=49} + local rs_c_3 = Div{parent=rs_cfg,x=2,y=4,width=49} + local rs_c_4 = Div{parent=rs_cfg,x=2,y=4,width=49} + local rs_c_5 = Div{parent=rs_cfg,x=2,y=4,width=49} + + local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5}} + + TextBox{parent=rs_cfg,x=1,y=2,height=1,text_align=CENTER,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)} + + TextBox{parent=rs_c_1,x=1,y=1,height=1,text=" port side/color unit/facility",fg_bg=g_lg_fg_bg} + local rs_list = ListBox{parent=rs_c_1,x=1,y=2,height=11,width=51,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} + + local function rs_revert() + tmp_cfg.Redstone = deep_copy_rs(ini_cfg.Redstone) + tool_ctl.gen_rs_summary(tmp_cfg) + end + + local function rs_apply() + settings.set("Redstone", tmp_cfg.Redstone) + + if settings.save("rtu.settings") then + load_settings(ini_cfg) + rs_pane.set_value(4) + else + rs_pane.set_value(5) + end + end + + PushButton{parent=rs_c_1,x=1,y=14,min_width=6,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=rs_c_1,x=8,y=14,min_width=16,text="Revert Changes",callback=rs_revert,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg} + PushButton{parent=rs_c_1,x=35,y=14,min_width=7,text="New +",callback=function()rs_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg} + PushButton{parent=rs_c_1,x=43,y=14,min_width=7,text="Apply",callback=rs_apply,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg} + + TextBox{parent=rs_c_2,x=1,y=1,height=1,text="Select one of the below ports to use."} + + local rs_ports = ListBox{parent=rs_c_2,x=1,y=3,height=10,width=51,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} + + local new_rs_port = IO.F_SCRAM + local function new_rs(port) + tool_ctl.rs_cfg_editing = false + + local text + + if port == -1 then + tool_ctl.rs_cfg_color.hide(true) + tool_ctl.rs_cfg_shortcut.show() + text = "You selected the ALL_WASTE shortcut." + else + tool_ctl.rs_cfg_shortcut.hide(true) + tool_ctl.rs_cfg_color.show() + text = "You selected " .. rsio.to_string(port) .. " (for " + if PORT_DSGN[port] == 1 then + text = text .. "a unit)." + tool_ctl.rs_cfg_unit_l.show() + tool_ctl.rs_cfg_unit.show() + else + tool_ctl.rs_cfg_unit_l.hide(true) + tool_ctl.rs_cfg_unit.hide(true) + text = text .. "the facility)." + end + end + + tool_ctl.rs_cfg_selection.set_value(text) + new_rs_port = port + rs_pane.set_value(3) + end + + -- add entries to redstone option list + local all_w_macro = Div{parent=rs_ports,height=1} + PushButton{parent=all_w_macro,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">ALL_WASTE",callback=function()new_rs(-1)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.black)} + TextBox{parent=all_w_macro,x=16,y=1,width=5,height=1,text="[n/a]",fg_bg=cpair(colors.lightGray,colors.white)} + TextBox{parent=all_w_macro,x=22,y=1,height=1,text="Create all 4 waste entries",fg_bg=cpair(colors.gray,colors.white)} + for i = 1, rsio.NUM_PORTS do + local name = rsio.to_string(i) + local io_dir = util.trinary(rsio.get_io_mode(i) == rsio.IO_DIR.IN, "[in]", "[out]") + local btn_color = util.trinary(rsio.get_io_mode(i) == rsio.IO_DIR.IN, colors.yellow, colors.lightBlue) + local entry = Div{parent=rs_ports,height=1} + PushButton{parent=entry,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">"..name,callback=function()new_rs(i)end,fg_bg=cpair(colors.black,btn_color),active_fg_bg=cpair(colors.white,colors.black)} + TextBox{parent=entry,x=16,y=1,width=5,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)} + TextBox{parent=entry,x=22,y=1,height=1,text=PORT_DESC[i],fg_bg=cpair(colors.gray,colors.white)} + end + + PushButton{parent=rs_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + tool_ctl.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=1,text_align=CENTER,text=""} + + tool_ctl.rs_cfg_unit_l = TextBox{parent=rs_c_3,x=27,y=3,width=7,height=1,text_align=CENTER,text="Unit ID"} + tool_ctl.rs_cfg_unit = NumberField{parent=rs_c_3,x=27,y=4,width=10,max_digits=2,min=1,max=4,fg_bg=bw_fg_bg} + + TextBox{parent=rs_c_3,x=1,y=3,width=11,height=1,text_align=CENTER,text="Output Side"} + local side = Radio2D{parent=rs_c_3,x=1,y=4,rows=2,columns=3,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red} + + local function set_bundled(bundled) + if bundled then tool_ctl.rs_cfg_color.enable() else tool_ctl.rs_cfg_color.disable() end + end + + tool_ctl.rs_cfg_shortcut = TextBox{parent=rs_c_3,x=1,y=9,height=4,text="This shortcut will add entries for each of the 4 waste outputs. If you select bundled, 4 colors will be assigned to the selected side. Otherwise, 4 default sides will be used."} + tool_ctl.rs_cfg_shortcut.hide(true) + + local bundled = CheckBox{parent=rs_c_3,x=1,y=7,label="Is Bundled?",default=false,box_fg_bg=cpair(colors.red,colors.black),callback=set_bundled} + tool_ctl.rs_cfg_color = Radio2D{parent=rs_c_3,x=1,y=9,rows=4,columns=4,default=1,options=color_options,radio_colors=cpair(colors.lightGray,colors.black),color_map=color_options_map,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg} + tool_ctl.rs_cfg_color.disable() + + local rs_err = TextBox{parent=rs_c_3,x=8,y=14,height=1,width=35,text_align=LEFT,text="Unit ID must be within 1 through 4.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + rs_err.hide(true) + + local function back_from_rs_opts() + rs_err.hide(true) + if tool_ctl.rs_cfg_editing ~= false then rs_pane.set_value(1) else rs_pane.set_value(2) end + end + + local function save_rs_entry() + local u = tonumber(tool_ctl.rs_cfg_unit.get_value()) + + if PORT_DSGN[new_rs_port] == 0 or (util.is_int(u) and u > 0 and u < 5) then + rs_err.hide(true) + + if new_rs_port >= 0 then + ---@type rtu_rs_definition + local def = { + unit = util.trinary(PORT_DSGN[new_rs_port] == 1, u, nil), + port = new_rs_port, + side = side_options_map[side.get_value()], + color = util.trinary(bundled.get_value(), color_options_map[tool_ctl.rs_cfg_color.get_value()], nil) + } + + if tool_ctl.rs_cfg_editing == false then + table.insert(tmp_cfg.Redstone, def) + else + def.port = tmp_cfg.Redstone[tool_ctl.rs_cfg_editing].port + tmp_cfg.Redstone[tool_ctl.rs_cfg_editing] = def + end + elseif new_rs_port == -1 then + local default_sides = { "left", "back", "right", "front" } + local default_colors = { colors.red, colors.orange, colors.yellow, colors.lime } + for i = 0, 3 do + table.insert(tmp_cfg.Redstone, { + unit = util.trinary(PORT_DSGN[IO.WASTE_PU + i] == 1, u, nil), + port = IO.WASTE_PU + i, + side = util.trinary(bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]), + color = util.trinary(bundled.get_value(), default_colors[i + 1], nil) + }) + end + end + + rs_pane.set_value(1) + tool_ctl.gen_rs_summary(tmp_cfg) + + side.set_value(1) + bundled.set_value(false) + tool_ctl.rs_cfg_color.set_value(1) + tool_ctl.rs_cfg_color.disable() + else + rs_err.show() + end + end + + PushButton{parent=rs_c_3,x=1,y=14,min_width=6,text="\x1b Back",callback=back_from_rs_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=rs_c_3,x=44,y=14,min_width=6,text="Save",callback=save_rs_entry,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg} + + TextBox{parent=rs_c_4,x=1,y=1,height=1,text_align=CENTER,text="Settings saved!"} + PushButton{parent=rs_c_4,x=1,y=14,min_width=6,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=rs_c_4,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + TextBox{parent=rs_c_5,x=1,y=1,height=5,text_align=CENTER,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."} + PushButton{parent=rs_c_5,x=1,y=14,min_width=6,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=rs_c_5,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + --#endregion + -- set tool functions now that we have the elements -- load a legacy config file @@ -528,6 +762,14 @@ local function config_view(display) tool_ctl.importing_legacy = true end + -- go back to the home page + function tool_ctl.go_home() + main_pane.set_value(1) + net_pane.set_value(1) + sum_pane.set_value(1) + rs_pane.set_value(1) + end + -- expose the auth key on the summary page function tool_ctl.show_auth_key() tool_ctl.show_key_btn.disable() @@ -578,6 +820,70 @@ local function config_view(display) if f[1] == "AuthKey" then tool_ctl.auth_key_textbox = textbox end end end + + local function edit_rs_entry(idx) + local def = tmp_cfg.Redstone[idx] ---@type rtu_rs_definition + + tool_ctl.rs_cfg_editing = idx + + local text = "Editing " .. rsio.to_string(def.port) .. " (for " + if PORT_DSGN[def.port] == 1 then + text = text .. "a unit)." + tool_ctl.rs_cfg_unit_l.show() + tool_ctl.rs_cfg_unit.show() + tool_ctl.rs_cfg_unit.set_value(def.unit or 1) + else + tool_ctl.rs_cfg_unit_l.hide(true) + tool_ctl.rs_cfg_unit.hide(true) + text = text .. "the facility)." + end + + local value = 1 + if def.color ~= nil then + value = color_to_idx(def.color) + tool_ctl.rs_cfg_color.enable() + else + tool_ctl.rs_cfg_color.disable() + end + + tool_ctl.rs_cfg_selection.set_value(text) + side.set_value(side_to_idx(def.side)) + bundled.set_value(def.color ~= nil) + tool_ctl.rs_cfg_color.set_value(value) + rs_pane.set_value(3) + end + + local function delete_rs_entry(idx) + table.remove(tmp_cfg.Redstone, idx) + tool_ctl.gen_rs_summary(tmp_cfg) + end + + -- generate the redstone summary list + ---@param cfg rtu_config + function tool_ctl.gen_rs_summary(cfg) + rs_list.remove_all() + + for i = 1, #cfg.Redstone do + local def = cfg.Redstone[i] ---@type rtu_rs_definition + + local name = rsio.to_string(def.port) + local io_dir = util.trinary(rsio.get_io_mode(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b") + local conn = def.side + local unit = util.strval(def.unit or "F") + + if def.color ~= nil then + conn = def.side .. "/" .. color_name_map[def.color] + end + + local entry = Div{parent=rs_list,height=1} + TextBox{parent=entry,x=1,y=1,width=1,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)} + TextBox{parent=entry,x=2,y=1,width=14,height=1,text=name} + TextBox{parent=entry,x=16,y=1,width=string.len(conn),height=1,text=conn,fg_bg=cpair(colors.gray,colors.white)} + TextBox{parent=entry,x=33,y=1,width=1,height=1,text=unit,fg_bg=cpair(colors.gray,colors.white)} + PushButton{parent=entry,x=35,y=1,min_width=6,height=1,text="EDIT",callback=function()edit_rs_entry(i)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg} + PushButton{parent=entry,x=41,y=1,min_width=8,height=1,text="DELETE",callback=function()delete_rs_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg} + end + end end -- reset terminal screen @@ -596,6 +902,8 @@ function configurator.configure(ask_config) load_settings(ini_cfg) + tmp_cfg.Redstone = deep_copy_rs(ini_cfg.Redstone) + reset_term() -- set overridden colors From d477b33774e4ef3e22e970ac2df20d7e6ec3a125 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 21 Oct 2023 13:58:42 -0400 Subject: [PATCH 08/36] fixed reposition not repositioning frame for mouse events --- graphics/core.lua | 2 +- graphics/element.lua | 23 ++++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/graphics/core.lua b/graphics/core.lua index a6fead5..abafb44 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -7,7 +7,7 @@ local flasher = require("graphics.flasher") local core = {} -core.version = "2.0.5" +core.version = "2.0.6" core.flasher = flasher core.events = events diff --git a/graphics/element.lua b/graphics/element.lua index 517e230..6debaf4 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -91,6 +91,8 @@ function element.new(args, child_offset_x, child_offset_y) p_window = nil, ---@type table position = events.new_coord_2d(1, 1), bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1 }, ---@class element_bounds + offset_x = 0, + offset_y = 0, next_y = 1, -- next child y coordinate next_id = 0, -- next child ID subscriptions = {}, @@ -194,6 +196,10 @@ function element.new(args, child_offset_x, child_offset_y) ---@param offset_y integer y offset for mouse events ---@param next_y integer next line if no y was provided function protected.prepare_template(offset_x, offset_y, next_y) + -- record offsets in case there is a reposition + self.offset_x = offset_x + self.offset_y = offset_y + -- get frame coordinates/size if args.gframe ~= nil then protected.frame.x = args.gframe.x @@ -694,7 +700,22 @@ function element.new(args, child_offset_x, child_offset_y) -- offsets relative to parent frame are where (1, 1) would be on top of the parent's top left corner ---@param x integer x position relative to parent frame ---@param y integer y position relative to parent frame - function public.reposition(x, y) protected.window.reposition(x, y) end + function public.reposition(x, y) + protected.window.reposition(x, y) + + -- record position + self.position.x, self.position.y = protected.window.getPosition() + + -- shift per parent child offset + self.position.x = self.position.x + self.offset_x + self.position.y = self.position.y + self.offset_y + + -- calculate mouse event bounds + self.bounds.x1 = self.position.x + self.bounds.x2 = self.position.x + protected.frame.w - 1 + self.bounds.y1 = self.position.y + self.bounds.y2 = self.position.y + protected.frame.h - 1 + end -- FUNCTION CALLBACKS -- From 1075d66122d55d8a353c224de4bf353d0a18177c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Nov 2023 12:48:06 -0400 Subject: [PATCH 09/36] graphics bugfix with disabled input fields --- graphics/core.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphics/core.lua b/graphics/core.lua index abafb44..437f66f 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -7,7 +7,7 @@ local flasher = require("graphics.flasher") local core = {} -core.version = "2.0.6" +core.version = "2.0.7" core.flasher = flasher core.events = events @@ -173,7 +173,7 @@ function core.new_ifield(e, max_len, fg_bg, dis_fg_bg) if e.enabled then e.w_set_bkg(fg_bg.bkg) e.w_set_fgd(fg_bg.fgd) - else + elseif dis_fg_bg ~= nil then e.w_set_bkg(dis_fg_bg.bkg) e.w_set_fgd(dis_fg_bg.fgd) end From 3065e2bece3d49d32ddf212518ace22cf08873ea Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Nov 2023 12:49:14 -0400 Subject: [PATCH 10/36] plc configurator clear settings when loading settings and show actual current settings on view --- reactor-plc/configure.lua | 83 +++++++++++++++------------------------ reactor-plc/startup.lua | 2 +- 2 files changed, 33 insertions(+), 52 deletions(-) diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index be041d5..f4aa27d 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -23,6 +23,7 @@ local NumberField = require("graphics.elements.form.number_field") local TextField = require("graphics.elements.form.text_field") local println = util.println +local tri = util.trinary local cpair = core.cpair @@ -104,46 +105,31 @@ local tmp_cfg = { ---@class plc_config local ini_cfg = {} +---@class plc_config +local settings_cfg = {} +-- all settings fields, their nice names, and their default values local fields = { - { "Networked", "Networked" }, - { "UnitID", "Unit ID" }, - { "EmerCoolEnable", "Emergency Coolant" }, - { "EmerCoolSide", "Emergency Coolant Side" }, - { "EmerCoolColor", "Emergency Coolant Color" }, - { "SVR_Channel", "SVR Channel" }, - { "PLC_Channel", "PLC Channel" }, - { "ConnTimeout", "Connection Timeout" }, - { "TrustedRange", "Trusted Range" }, - { "AuthKey", "Facility Auth Key" }, - { "LogMode", "Log Mode" }, - { "LogPath", "Log Path" }, - { "LogDebug","Log Debug Messages" } + { "Networked", "Networked", false }, + { "UnitID", "Unit ID", 1 }, + { "EmerCoolEnable", "Emergency Coolant", false }, + { "EmerCoolSide", "Emergency Coolant Side", nil }, + { "EmerCoolColor", "Emergency Coolant Color", nil }, + { "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 }, + { "LogPath", "Log Path", "/log.txt" }, + { "LogDebug","Log Debug Messages", false } } local side_options = { "Top", "Bottom", "Left", "Right", "Front", "Back" } local side_options_map = { "top", "bottom", "left", "right", "front", "back" } local color_options = { "Red", "Orange", "Yellow", "Lime", "Green", "Cyan", "Light Blue", "Blue", "Purple", "Magenta", "Pink", "White", "Light Gray", "Gray", "Black", "Brown" } local color_options_map = { colors.red, colors.orange, colors.yellow, colors.lime, colors.green, colors.cyan, colors.lightBlue, colors.blue, colors.purple, colors.magenta, colors.pink, colors.white, colors.lightGray, colors.gray, colors.black, colors.brown } - -local color_name_map = { - [colors.red] = "red", - [colors.orange] = "orange", - [colors.yellow] = "yellow", - [colors.lime] = "lime", - [colors.green] = "green", - [colors.cyan] = "cyan", - [colors.lightBlue] = "lightBlue", - [colors.blue] = "blue", - [colors.purple] = "purple", - [colors.magenta] = "magenta", - [colors.pink] = "pink", - [colors.white] = "white", - [colors.lightGray] = "lightGray", - [colors.gray] = "gray", - [colors.black] = "black", - [colors.brown] = "brown" -} +local color_name_map = { [colors.red] = "red", [colors.orange] = "orange", [colors.yellow] = "yellow", [colors.lime] = "lime", [colors.green] = "green", [colors.cyan] = "cyan", [colors.lightBlue] = "lightBlue", [colors.blue] = "blue", [colors.purple] = "purple", [colors.magenta] = "magenta", [colors.pink] = "pink", [colors.white] = "white", [colors.lightGray] = "lightGray", [colors.gray] = "gray", [colors.black] = "black", [colors.brown] = "brown" } -- convert text representation to index ---@param side string @@ -163,20 +149,15 @@ end -- load data from the settings file ---@param target plc_config -local function load_settings(target) - target.Networked = settings.get("Networked", false) - target.UnitID = settings.get("UnitID", 1) - target.EmerCoolEnable = settings.get("EmerCoolEnable", false) - target.EmerCoolSide = settings.get("EmerCoolSide", nil) - target.EmerCoolColor = settings.get("EmerCoolColor", nil) - target.SVR_Channel = settings.get("SVR_Channel", 16240) - target.PLC_Channel = settings.get("PLC_Channel", 16241) - target.ConnTimeout = settings.get("ConnTimeout", 5) - target.TrustedRange = settings.get("TrustedRange", 0) - target.AuthKey = settings.get("AuthKey", "") - target.LogMode = settings.get("LogMode", log.MODE.APPEND) - target.LogPath = settings.get("LogPath", "/log.txt") - target.LogDebug = settings.get("LogDebug", false) +---@param raw boolean? true to not use default values +local function load_settings(target, raw) + for _, v in pairs(fields) do settings.unset(v[1]) end + + local loaded = settings.load("/reactor-plc.settings") + + for _, v in pairs(fields) do target[v[1]] = settings.get(v[1], tri(raw, nil, v[3])) end + + return loaded end -- create the config view @@ -212,7 +193,7 @@ local function config_view(display) local function view_config() tool_ctl.viewing_config = true - tool_ctl.gen_summary(ini_cfg) + tool_ctl.gen_summary(settings_cfg) tool_ctl.settings_apply.hide(true) main_pane.set_value(5) end @@ -483,6 +464,7 @@ local function config_view(display) if settings.save("reactor-plc.settings") then load_settings(ini_cfg) + load_settings(settings_cfg, true) try_set(networked, ini_cfg.Networked) try_set(u_id, ini_cfg.UnitID) @@ -628,7 +610,7 @@ local function config_view(display) if f[1] == "AuthKey" then val = string.rep("*", string.len(val)) end if f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") end if f[1] == "EmerCoolColor" and raw ~= nil then val = color_name_map[raw] end - if val == "nil" then val = "n/a" end + if val == "nil" then val = "" end local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white)) alternate = not alternate @@ -665,9 +647,8 @@ end ---@param ask_config? boolean indicate if this is being called by the PLC startup app due to an invalid configuration function configurator.configure(ask_config) tool_ctl.ask_config = ask_config == true - tool_ctl.has_config = settings.load("/reactor-plc.settings") - - load_settings(ini_cfg) + tool_ctl.has_config = load_settings(ini_cfg) + load_settings(settings_cfg, true) reset_term() diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 683b405..e8705a2 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.6.3" +local R_PLC_VERSION = "v1.6.4" local println = util.println local println_ts = util.println_ts From 7ef363a3c2d5d57b763f00375105a3a48efc3aca Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Nov 2023 12:49:54 -0400 Subject: [PATCH 11/36] fixed installer typo --- ccmsi.lua | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ccmsi.lua b/ccmsi.lua index 8b8fa6d..0993382 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -18,7 +18,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local CCMSI_VERSION = "v1.11a" +local CCMSI_VERSION = "v1.11b" local install_dir = "/.install-cache" local manifest_path = "https://mikaylafischler.github.io/cc-mek-scada/manifests/" @@ -32,6 +32,7 @@ local function red() term.setTextColor(colors.red) end local function orange() term.setTextColor(colors.orange) end local function yellow() term.setTextColor(colors.yellow) end local function green() term.setTextColor(colors.green) end +local function cyan() term.setTextColor(colors.cyan) end local function blue() term.setTextColor(colors.blue) end local function white() term.setTextColor(colors.white) end local function lgray() term.setTextColor(colors.lightGray) end @@ -199,7 +200,7 @@ if #opts == 0 or opts[1] == "help" then println("usage: ccmsi ") println("") lgray() - println(" check - check latest versions avilable") + println(" check - check latest versions available") yellow() println(" ccmsi check for target") lgray() @@ -266,13 +267,11 @@ if mode == "check" then blue();print(local_manifest.versions[key]) if value ~= local_manifest.versions[key] then white();print(" (") - term.setTextColor(colors.cyan) - print(value);white();println(" available)") + cyan();print(value);white();println(" available)") else green();println(" (up to date)") end else lgray();print("not installed");white();print(" (latest ") - term.setTextColor(colors.cyan) - print(value);white();println(")") + cyan();print(value);white();println(")") end end From 25f68f338cf30e15a5614ba731fec77d759642f3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Nov 2023 12:51:24 -0400 Subject: [PATCH 12/36] bootloader cleanup and added license to installer downloads --- imgen.py | 2 +- startup.lua | 22 ++++++++++------------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/imgen.py b/imgen.py index 31a61e0..a854b1f 100644 --- a/imgen.py +++ b/imgen.py @@ -60,7 +60,7 @@ def make_manifest(size): }, "files" : { # common files - "system" : [ "initenv.lua", "startup.lua", "configure.lua" ], + "system" : [ "initenv.lua", "startup.lua", "configure.lua", "LICENSE" ], "common" : list_files("./scada-common"), "graphics" : list_files("./graphics"), "lockbox" : list_files("./lockbox"), diff --git a/startup.lua b/startup.lua index d330066..3b2eef1 100644 --- a/startup.lua +++ b/startup.lua @@ -1,30 +1,28 @@ local util = require("scada-common.util") -local BOOTLOADER_VERSION = "0.3" - local println = util.println -local println_ts = util.println_ts + +local BOOTLOADER_VERSION = "0.4" println("SCADA BOOTLOADER V" .. BOOTLOADER_VERSION) +println("BOOT> SCANNING FOR APPLICATIONS...") -local exit_code ---@type boolean - -println_ts("BOOT> SCANNING FOR APPLICATIONS...") +local exit_code if fs.exists("reactor-plc/startup.lua") then - println("BOOT> FOUND REACTOR PLC CODE: EXEC STARTUP") + println("BOOT> EXEC REACTOR PLC STARTUP") exit_code = shell.execute("reactor-plc/startup") elseif fs.exists("rtu/startup.lua") then - println("BOOT> FOUND RTU CODE: EXEC STARTUP") + println("BOOT> EXEC RTU STARTUP") exit_code = shell.execute("rtu/startup") elseif fs.exists("supervisor/startup.lua") then - println("BOOT> FOUND SUPERVISOR CODE: EXEC STARTUP") + println("BOOT> EXEC SUPERVISOR STARTUP") exit_code = shell.execute("supervisor/startup") elseif fs.exists("coordinator/startup.lua") then - println("BOOT> FOUND COORDINATOR CODE: EXEC STARTUP") + println("BOOT> EXEC COORDINATOR STARTUP") exit_code = shell.execute("coordinator/startup") elseif fs.exists("pocket/startup.lua") then - println("BOOT> FOUND POCKET CODE: EXEC STARTUP") + println("BOOT> EXEC POCKET STARTUP") exit_code = shell.execute("pocket/startup") else println("BOOT> NO SCADA STARTUP FOUND") @@ -32,6 +30,6 @@ else return false end -if not exit_code then println_ts("BOOT> APPLICATION CRASHED") end +if not exit_code then println("BOOT> APPLICATION CRASHED") end return exit_code From eff344483464d1e1de1fda8d32a7fb1998546007 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Nov 2023 12:56:49 -0400 Subject: [PATCH 13/36] added type def to ppm and return a copy of the peripherals list rather than the table itself --- scada-common/ppm.lua | 14 +++++++++----- scada-common/util.lua | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 6af4e2f..d54fec8 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -161,10 +161,10 @@ local function peri_init(iface) setmetatable(self.device, mt) - return { - type = self.type, - dev = self.device - } + ---@class ppm_entry + local entry = { type = self.type, dev = self.device } + + return entry end ---------------------- @@ -310,7 +310,11 @@ function ppm.list_avail() return peripheral.getNames() end -- list mounted peripherals ---@nodiscard ---@return table mounts -function ppm.list_mounts() return ppm_sys.mounts end +function ppm.list_mounts() + local list = {} + for k, v in pairs(ppm_sys.mounts) do list[k] = v end + return list +end -- get a mounted peripheral side/interface by device table ---@nodiscard diff --git a/scada-common/util.lua b/scada-common/util.lua index 01d9adf..1ab8cd7 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -18,7 +18,7 @@ local type = type local util = {} -- scada-common version -util.version = "1.1.6" +util.version = "1.1.7" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 From 45c8a8d8a942a58cc4a6cddc4c20d795625fc19b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Nov 2023 13:29:38 -0400 Subject: [PATCH 14/36] added peripheral connections to rtu configurator --- rtu/configure.lua | 517 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 444 insertions(+), 73 deletions(-) diff --git a/rtu/configure.lua b/rtu/configure.lua index 393e087..f399146 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -6,6 +6,7 @@ local log = require("scada-common.log") local rsio = require("scada-common.rsio") local tcd = require("scada-common.tcd") local util = require("scada-common.util") +local ppm = require("scada-common.ppm") local core = require("graphics.core") @@ -24,6 +25,7 @@ local NumberField = require("graphics.elements.form.number_field") local TextField = require("graphics.elements.form.text_field") local println = util.println +local tri = util.trinary local cpair = core.cpair @@ -78,6 +80,15 @@ local changes = {} ---@field side side ---@field color color|nil +---@class rtu_peri_definition +---@field unit integer|nil +---@field index integer|nil +---@field name string + +local RTU_DEV_TYPES = { "boilerValve", "turbineValve", "dynamicValve", "inductionPort", "spsPort", "solarNeutronActivator", "environmentDetector" } +local NEEDS_UNIT = { "boilerValve", "turbineValve", "dynamicValve", "solarNeutronActivator", "environmentDetector" } +local NEEDS_IDX = { "boilerValve", "turbineValve", "dynamicValve" } + ---@class rtu_configurator local configurator = {} @@ -112,29 +123,44 @@ local tool_ctl = { has_config = false, viewing_config = false, importing_legacy = false, - rs_cfg_editing = false, ---@type integer|false + peri_cfg_editing = false, ---@type string|false + peri_cfg_manual = false, + rs_cfg_editing = false, ---@type integer|false - view_gw_cfg = nil, ---@type graphics_element - dev_cfg = nil, ---@type graphics_element - rs_cfg = nil, ---@type graphics_element - settings_apply = nil, ---@type graphics_element + view_gw_cfg = nil, ---@type graphics_element + dev_cfg = nil, ---@type graphics_element + rs_cfg = nil, ---@type graphics_element + settings_apply = nil, ---@type graphics_element - go_home = nil, ---@type function - gen_summary = nil, ---@type function - show_current_cfg = nil, ---@type function - load_legacy = nil, ---@type function - gen_rs_summary = nil, ---@type function + go_home = nil, ---@type function + gen_summary = nil, ---@type function + show_current_cfg = nil, ---@type function + load_legacy = nil, ---@type function + p_assign = nil, ---@type function + update_peri_list = nil, ---@type function + gen_peri_summary = nil, ---@type function + gen_rs_summary = nil, ---@type function - show_auth_key = nil, ---@type function - show_key_btn = nil, ---@type graphics_element - auth_key_textbox = nil, ---@type graphics_element + show_auth_key = nil, ---@type function + show_key_btn = nil, ---@type graphics_element + auth_key_textbox = nil, ---@type graphics_element auth_key_value = "", - rs_cfg_selection = nil, ---@type graphics_element - rs_cfg_unit_l = nil, ---@type graphics_element - rs_cfg_unit = nil, ---@type graphics_element - rs_cfg_color = nil, ---@type graphics_element - rs_cfg_shortcut = nil ---@type graphics_element + ppm_devs = nil, ---@type graphics_element + p_name_msg = nil, ---@type graphics_element + p_prompt = nil, ---@type graphics_element + p_idx = nil, ---@type graphics_element + p_unit = nil, ---@type graphics_element + p_assign_btn = nil, ---@type graphics_element + p_assign_end = nil, ---@type graphics_element + p_desc = nil, ---@type graphics_element + p_desc_ext = nil, ---@type graphics_element + + rs_cfg_selection = nil, ---@type graphics_element + rs_cfg_unit_l = nil, ---@type graphics_element + rs_cfg_unit = nil, ---@type graphics_element + rs_cfg_color = nil, ---@type graphics_element + rs_cfg_shortcut = nil ---@type graphics_element } ---@class rtu_config @@ -154,42 +180,26 @@ local tmp_cfg = { ---@class rtu_config local ini_cfg = {} +---@class rtu_config +local settings_cfg = {} local fields = { - { "SpeakerVolume", "Speaker Volume" }, - { "SVR_Channel", "SVR Channel" }, - { "RTU_Channel", "RTU Channel" }, - { "ConnTimeout", "Connection Timeout" }, - { "TrustedRange", "Trusted Range" }, - { "AuthKey", "Facility Auth Key" }, - { "LogMode", "Log Mode" }, - { "LogPath", "Log Path" }, - { "LogDebug","Log Debug Messages" } + { "SpeakerVolume", "Speaker Volume", 1.0 }, + { "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 }, + { "LogPath", "Log Path", "/log.txt" }, + { "LogDebug","Log Debug Messages", false } } local side_options = { "Top", "Bottom", "Left", "Right", "Front", "Back" } local side_options_map = { "top", "bottom", "left", "right", "front", "back" } local color_options = { "Red", "Orange", "Yellow", "Lime", "Green", "Cyan", "Light Blue", "Blue", "Purple", "Magenta", "Pink", "White", "Light Gray", "Gray", "Black", "Brown" } local color_options_map = { colors.red, colors.orange, colors.yellow, colors.lime, colors.green, colors.cyan, colors.lightBlue, colors.blue, colors.purple, colors.magenta, colors.pink, colors.white, colors.lightGray, colors.gray, colors.black, colors.brown } - -local color_name_map = { - [colors.red] = "red", - [colors.orange] = "orange", - [colors.yellow] = "yellow", - [colors.lime] = "lime", - [colors.green] = "green", - [colors.cyan] = "cyan", - [colors.lightBlue] = "lightBlue", - [colors.blue] = "blue", - [colors.purple] = "purple", - [colors.magenta] = "magenta", - [colors.pink] = "pink", - [colors.white] = "white", - [colors.lightGray] = "lightGray", - [colors.gray] = "gray", - [colors.black] = "black", - [colors.brown] = "brown" -} +local color_name_map = { [colors.red] = "red", [colors.orange] = "orange", [colors.yellow] = "yellow", [colors.lime] = "lime", [colors.green] = "green", [colors.cyan] = "cyan", [colors.lightBlue] = "lightBlue", [colors.blue] = "blue", [colors.purple] = "purple", [colors.magenta] = "magenta", [colors.pink] = "pink", [colors.white] = "white", [colors.lightGray] = "lightGray", [colors.gray] = "gray", [colors.black] = "black", [colors.brown] = "brown" } -- convert text representation to index ---@param side string @@ -207,30 +217,34 @@ local function color_to_idx(color) end end --- deep copy a redstone definitions table +-- deep copy peripherals defs +local function deep_copy_peri(data) + local array = {} + for _, d in ipairs(data) do table.insert(array, { unit = d.unit, index = d.index, name = d.name }) end + return array + end + +-- deep copy redstone defs local function deep_copy_rs(data) local array = {} - for _, d in ipairs(data) do - table.insert(array, { unit = d.unit, port = d.port, side = d.side, color = d.color }) - end + for _, d in ipairs(data) do table.insert(array, { unit = d.unit, port = d.port, side = d.side, color = d.color }) end return array end -- load data from the settings file ---@param target rtu_config -local function load_settings(target) - target.SpeakerVolume = settings.get("SpeakerVolume", 1.0) - target.Peripherals = settings.get("Peripherals", {}) - target.Redstone = settings.get("Redstone", {}) +---@param raw boolean? true to not use default values +local function load_settings(target, raw) + for _, v in pairs(fields) do settings.unset(v[1]) end - target.SVR_Channel = settings.get("SVR_Channel", 16240) - target.RTU_Channel = settings.get("RTU_Channel", 16242) - target.ConnTimeout = settings.get("ConnTimeout", 5) - target.TrustedRange = settings.get("TrustedRange", 0) - target.AuthKey = settings.get("AuthKey", "") - target.LogMode = settings.get("LogMode", log.MODE.APPEND) - target.LogPath = settings.get("LogPath", "/log.txt") - target.LogDebug = settings.get("LogDebug", false) + local loaded = settings.load("/rtu.settings") + + for _, v in pairs(fields) do target[v[1]] = settings.get(v[1], tri(raw, nil, v[3])) end + + target.Peripherals = settings.get("Peripherals", tri(raw, nil, {})) + target.Redstone = settings.get("Redstone", tri(raw, nil, {})) + + return loaded end -- create the config view @@ -267,7 +281,7 @@ local function config_view(display) local function view_config() tool_ctl.viewing_config = true - tool_ctl.gen_summary(ini_cfg) + tool_ctl.gen_summary(settings_cfg) tool_ctl.settings_apply.hide(true) main_pane.set_value(5) end @@ -277,6 +291,11 @@ local function config_view(display) y_start = y_start + 2 end + local function show_peri_conns() + tool_ctl.gen_peri_summary(ini_cfg) + main_pane.set_value(7) + end + local function show_rs_conns() tool_ctl.gen_rs_summary(ini_cfg) main_pane.set_value(8) @@ -284,7 +303,7 @@ local function config_view(display) PushButton{parent=main_page,x=2,y=y_start,min_width=19,text="Configure Gateway",callback=function()main_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg} tool_ctl.view_gw_cfg = PushButton{parent=main_page,x=2,y=y_start+2,min_width=28,text="View Gateway Configuration",callback=view_config,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} - tool_ctl.dev_cfg = PushButton{parent=main_page,x=2,y=y_start+4,min_width=18,text="RTU Unit Devices",callback=function()main_pane.set_value(7)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} + tool_ctl.dev_cfg = PushButton{parent=main_page,x=2,y=y_start+4,min_width=24,text="Peripheral Connections",callback=show_peri_conns,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} tool_ctl.rs_cfg = PushButton{parent=main_page,x=2,y=y_start+6,min_width=22,text="Redstone Connections",callback=show_rs_conns,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} if not tool_ctl.has_config then @@ -502,6 +521,7 @@ local function config_view(display) if settings.save("rtu.settings") then load_settings(ini_cfg) + load_settings(settings_cfg, true) try_set(s_vol, ini_cfg.SpeakerVolume) try_set(svr_chan, ini_cfg.SVR_Channel) @@ -513,6 +533,9 @@ local function config_view(display) try_set(path, ini_cfg.LogPath) try_set(en_dbg, ini_cfg.LogDebug) + tool_ctl.dev_cfg.enable() + tool_ctl.rs_cfg.enable() + if tool_ctl.importing_legacy then tool_ctl.importing_legacy = false sum_pane.set_value(3) @@ -567,6 +590,299 @@ local function config_view(display) --#endregion + --#region DEVICES + + local peri_c_1 = Div{parent=peri_cfg,x=2,y=4,width=49} + local peri_c_2 = Div{parent=peri_cfg,x=2,y=4,width=49} + local peri_c_3 = Div{parent=peri_cfg,x=2,y=4,width=49} + local peri_c_4 = Div{parent=peri_cfg,x=2,y=4,width=49} + local peri_c_5 = Div{parent=peri_cfg,x=2,y=4,width=49} + local peri_c_6 = Div{parent=peri_cfg,x=2,y=4,width=49} + local peri_c_7 = Div{parent=peri_cfg,x=2,y=4,width=49} + + local peri_pane = MultiPane{parent=peri_cfg,x=1,y=4,panes={peri_c_1,peri_c_2,peri_c_3,peri_c_4,peri_c_5,peri_c_6,peri_c_7}} + + TextBox{parent=peri_cfg,x=1,y=2,height=1,text_align=CENTER,text=" Peripheral Connections",fg_bg=cpair(colors.black,colors.purple)} + + local peri_list = ListBox{parent=peri_c_1,x=1,y=1,height=12,width=51,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} + + local function peri_revert() + tmp_cfg.Peripherals = deep_copy_peri(ini_cfg.Peripherals) + tool_ctl.gen_peri_summary(tmp_cfg) + end + + local function peri_apply() + settings.set("Peripherals", tmp_cfg.Peripherals) + + if settings.save("rtu.settings") then + load_settings(ini_cfg) + load_settings(settings_cfg, true) + peri_pane.set_value(5) + else + peri_pane.set_value(6) + end + end + + PushButton{parent=peri_c_1,x=1,y=14,min_width=6,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=peri_c_1,x=8,y=14,min_width=16,text="Revert Changes",callback=peri_revert,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg} + PushButton{parent=peri_c_1,x=35,y=14,min_width=7,text="Add +",callback=function()peri_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg} + PushButton{parent=peri_c_1,x=43,y=14,min_width=7,text="Apply",callback=peri_apply,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg} + + TextBox{parent=peri_c_2,x=1,y=1,height=1,text="Select one of the below devices to use."} + + tool_ctl.ppm_devs = ListBox{parent=peri_c_2,x=1,y=3,height=10,width=51,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} + + PushButton{parent=peri_c_2,x=8,y=14,min_width=10,text="Manual +",callback=function()peri_pane.set_value(3)end,fg_bg=cpair(colors.black,colors.orange),active_fg_bg=btn_act_fg_bg} + PushButton{parent=peri_c_2,x=26,y=14,min_width=24,text="I don't see my device!",callback=function()peri_pane.set_value(7)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg} + TextBox{parent=peri_c_7,x=1,y=1,height=10,text="Make sure your device is either touching the RTU, or connected via wired modems. There should be a wired modem on a side of the RTU then one on the device, connected by a cable. The modem on the device needs to be right clicked to connect it (which will turn its border red), at which point the peripheral name will be shown in the chat."} + TextBox{parent=peri_c_7,x=1,y=9,height=4,text="If it still does not show, it may not be compatible. Currently only Boilers, Turbines, Dynamic Tanks, SNAs, SPSs, Induction Matricies, and Environment Detectors are supported."} + PushButton{parent=peri_c_7,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + local new_peri_attrs = { "", "" } + local function new_peri(name, type) + new_peri_attrs = { name, type } + tool_ctl.peri_cfg_editing = false + + tool_ctl.p_name_msg.set_value("Configuring peripheral on '" .. name .. "':") + tool_ctl.p_desc_ext.set_value("") + + if type == "boilerValve" then + tool_ctl.p_prompt.set_value("This is the # boiler for reactor unit # .") + tool_ctl.p_idx.show() + tool_ctl.p_idx.redraw() + tool_ctl.p_idx.enable() + tool_ctl.p_idx.set_max(2) + tool_ctl.p_unit.reposition(44, 4) + tool_ctl.p_unit.enable() + tool_ctl.p_assign_btn.hide(true) + tool_ctl.p_assign_end.hide(true) + tool_ctl.p_desc.reposition(1, 7) + tool_ctl.p_desc.set_value("Each unit can have at most 2 boilers. Boiler #1 shows up first on the main display, followed by boiler #2 below it. These numberings are independent of which RTU they are connected to. For example, one RTU can have boiler #1 and another can have #2, but both cannot have #1.") + elseif type == "turbineValve" then + tool_ctl.p_prompt.set_value("This is the # turbine for reactor unit # .") + tool_ctl.p_idx.show() + tool_ctl.p_idx.redraw() + tool_ctl.p_idx.enable() + tool_ctl.p_idx.set_max(3) + tool_ctl.p_unit.reposition(45, 4) + tool_ctl.p_unit.enable() + tool_ctl.p_assign_btn.hide(true) + tool_ctl.p_assign_end.hide(true) + tool_ctl.p_desc.reposition(1, 7) + tool_ctl.p_desc.set_value("Each unit can have at most 3 turbines. Turbine #1 shows up first on the main display, followed by #2 then #3 below it. These numberings are independent of which RTU they are connected to. For example, one RTU can have turbine #1 and another can have #2, but both cannot have #1.") + elseif type == "solarNeutronActivator" then + tool_ctl.p_idx.hide() + tool_ctl.p_prompt.set_value("This SNA is for reactor unit # .") + tool_ctl.p_unit.reposition(31, 4) + tool_ctl.p_unit.enable() + tool_ctl.p_assign_btn.hide(true) + tool_ctl.p_assign_end.hide(true) + tool_ctl.p_desc_ext.set_value("Before adding lots of SNAs: multiply the \"PEAK\" rate on the flow monitor (after connecting at least 1 SNA) by 10 to get the mB/t of waste that they can process. Enough SNAs to provide 2x to 3x of your max burn rate should be a good margin to catch up after night or cloudy weather. Too many devices (such as SNAs) on one RTU can cause lag.") + elseif type == "dynamicValve" then + tool_ctl.p_prompt.set_value("This is the # dynamic tank for...") + tool_ctl.p_idx.show() + tool_ctl.p_idx.redraw() + tool_ctl.p_idx.set_max(4) + tool_ctl.p_unit.reposition(18, 6) + + if tool_ctl.p_assign_btn.get_value() == 1 then + tool_ctl.p_idx.enable() + tool_ctl.p_unit.disable() + else + tool_ctl.p_idx.set_value(1) + tool_ctl.p_idx.disable() + tool_ctl.p_unit.enable() + end + + tool_ctl.p_assign_btn.show() + tool_ctl.p_assign_btn.redraw() + tool_ctl.p_assign_end.show() + tool_ctl.p_assign_end.redraw() + tool_ctl.p_desc.reposition(1, 8) + tool_ctl.p_desc.set_value("Each reactor unit can have at most 1 tank and the facility can have at most 4. Each facility tank must have a unique # 1 through 4, regardless of where it is connected. Only a total of 4 tanks can be displayed on the flow monitor.") + elseif type == "environmentDetector" then + tool_ctl.p_idx.hide() + tool_ctl.p_prompt.set_value("This will be an environment detector for...") + tool_ctl.p_unit.reposition(18, 6) + if tool_ctl.p_assign_btn.get_value() == 1 then tool_ctl.p_unit.disable() else tool_ctl.p_unit.enable() end + tool_ctl.p_assign_btn.show() + tool_ctl.p_assign_btn.redraw() + tool_ctl.p_assign_end.show() + tool_ctl.p_assign_end.redraw() + tool_ctl.p_desc.reposition(1, 8) + tool_ctl.p_desc.set_value("You can connect more than one environment detector for a particular unit or the facility, in which case the maximum radiation reading from those assigned to that particular unit or the facility will be used.") + elseif type == "inductionPort" or type == "spsPort" then + local dev = util.trinary(type == "inductionPort", "induction matrix", "SPS") + tool_ctl.p_idx.hide(true) + tool_ctl.p_unit.hide(true) + tool_ctl.p_prompt.set_value("This will be the " .. dev .. " for the facility.") + tool_ctl.p_assign_btn.hide(true) + tool_ctl.p_assign_end.hide(true) + tool_ctl.p_desc.reposition(1, 7) + tool_ctl.p_desc.set_value("There can only be one of these devices per SCADA network, so it will be assigned as the sole " .. dev .. " for the facility. There must only be one of these across all the RTUs you have.") + else + assert(false, "invalid peripheral type after type validation") + end + + peri_pane.set_value(4) + end + + -- update peripherals list + function tool_ctl.update_peri_list() + local alternate = true + local mounts = ppm.list_mounts() + + -- filter out in-use peripherals + for _, v in ipairs(tmp_cfg.Peripherals) do mounts[v.name] = nil end + + tool_ctl.ppm_devs.remove_all() + for name, entry in pairs(mounts) do + if util.table_contains(RTU_DEV_TYPES, entry.type) then + local bkg = util.trinary(alternate, colors.white, colors.lightGray) + + ---@cast entry ppm_entry + local line = Div{parent=tool_ctl.ppm_devs,height=2,fg_bg=cpair(colors.black,bkg)} + PushButton{parent=line,x=1,y=1,min_width=9,alignment=LEFT,height=1,text="> SELECT",callback=function()tool_ctl.peri_cfg_manual=false;new_peri(name,entry.type)end,fg_bg=cpair(colors.black,colors.purple),active_fg_bg=cpair(colors.white,colors.black)} + TextBox{parent=line,x=11,y=1,height=1,text=name,fg_bg=cpair(colors.black,bkg)} + TextBox{parent=line,x=11,y=2,height=1,text=entry.type,fg_bg=cpair(colors.gray,bkg)} + + alternate = not alternate + end + end + end + + tool_ctl.update_peri_list() + + PushButton{parent=peri_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + TextBox{parent=peri_c_3,x=1,y=1,height=4,text_align=CENTER,text="This feature is intended for advanced users. If you are clicking this just because your device is not shown, follow the connection instructions in 'I don't see my device!'."} + TextBox{parent=peri_c_3,x=1,y=6,height=4,text_align=CENTER,text="Peripheral Name"} + local p_name = TextField{parent=peri_c_3,x=1,y=7,width=49,height=1,max_len=128,fg_bg=bw_fg_bg} + local p_type = Radio2D{parent=peri_c_3,x=1,y=9,rows=4,columns=2,default=1,options=RTU_DEV_TYPES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.purple} + local man_p_err = TextBox{parent=peri_c_3,x=8,y=14,height=1,width=35,text_align=LEFT,text="Please enter a peripheral name.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + man_p_err.hide(true) + + local function submit_manual_peri() + local name = p_name.get_value() + if string.len(name) > 0 then + tool_ctl.entering_manual = true + man_p_err.hide(true) + new_peri(name, RTU_DEV_TYPES[p_type.get_value()]) + else man_p_err.show() end + end + + PushButton{parent=peri_c_3,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=peri_c_3,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_manual_peri,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + tool_ctl.p_name_msg = TextBox{parent=peri_c_4,x=1,y=1,height=2,text_align=CENTER,text=""} + tool_ctl.p_prompt = TextBox{parent=peri_c_4,x=1,y=4,height=2,text_align=CENTER,text=""} + tool_ctl.p_idx = NumberField{parent=peri_c_4,x=14,y=4,width=4,max_digits=2,min=1,max=2,default=1,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} + tool_ctl.p_assign_btn = RadioButton{parent=peri_c_4,x=1,y=5,default=1,options={"the facility.","a unit. (unit #"},callback=function(v)tool_ctl.p_assign(v)end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.purple} + tool_ctl.p_assign_end = TextBox{parent=peri_c_4,x=22,y=6,height=6,width=1,text_align=LEFT,text=")"} + + tool_ctl.p_unit = NumberField{parent=peri_c_4,x=44,y=4,width=4,max_digits=2,min=1,max=4,default=1,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} + tool_ctl.p_unit.disable() + + function tool_ctl.p_assign(opt) + if opt == 1 then + tool_ctl.p_unit.disable() + tool_ctl.p_idx.enable() + else + tool_ctl.p_unit.enable() + tool_ctl.p_idx.set_value(1) + tool_ctl.p_idx.disable() + end + end + + tool_ctl.p_desc = TextBox{parent=peri_c_4,x=1,y=7,height=6,text_align=LEFT,text="",fg_bg=g_lg_fg_bg} + tool_ctl.p_desc_ext = TextBox{parent=peri_c_4,x=1,y=6,height=7,text_align=LEFT,text="",fg_bg=g_lg_fg_bg} + + local p_err = TextBox{parent=peri_c_4,x=8,y=14,height=1,width=35,text_align=LEFT,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + p_err.hide(true) + + local function back_from_peri_opts() + if tool_ctl.peri_cfg_editing ~= false then + peri_pane.set_value(1) + elseif tool_ctl.entering_manual then + peri_pane.set_value(3) + else + peri_pane.set_value(2) + end + + tool_ctl.entering_manual = false + end + + local function save_peri_entry() + local peri_name = new_peri_attrs[1] + local peri_type = new_peri_attrs[2] + + local unit, index = nil, nil + + local for_facility = tool_ctl.p_assign_btn.get_value() == 1 + local u = tonumber(tool_ctl.p_unit.get_value()) + local idx = tonumber(tool_ctl.p_idx.get_value()) + + if util.table_contains(NEEDS_UNIT, peri_type) then + if (peri_type == "dynamicValve" or peri_type == "environmentDetector") and for_facility then + -- skip + elseif not (util.is_int(u) and u > 0 and u < 5) then + p_err.set_value("Unit ID must be within 1 through 4.") + p_err.show() + return + else unit = u end + end + + if peri_type == "boilerValve" then + if not (idx == 1 or idx == 2) then + p_err.set_value("Index must be 1 or 2.") + p_err.show() + return + else index = idx end + elseif peri_type == "turbineValve" then + if not (idx == 1 or idx == 2 or idx == 3) then + p_err.set_value("Index must be 1, 2, or 3.") + p_err.show() + return + else index = idx end + elseif peri_type == "dynamicValve" and not for_facility then + if not (util.is_int(idx) and idx > 0 and idx < 5) then + p_err.set_value("Index must be within 1 through 4.") + p_err.show() + return + else index = idx end + end + + p_err.hide(true) + + ---@type rtu_peri_definition + local def = { name = peri_name, unit = unit, index = index } + + if tool_ctl.peri_cfg_editing == false then + table.insert(tmp_cfg.Peripherals, def) + else + def.name = tmp_cfg.Peripherals[tool_ctl.peri_cfg_editing].name + tmp_cfg.Peripherals[tool_ctl.peri_cfg_editing] = def + end + + peri_pane.set_value(1) + tool_ctl.gen_peri_summary(tmp_cfg) + + tool_ctl.p_idx.set_value(1) + end + + PushButton{parent=peri_c_4,x=1,y=14,min_width=6,text="\x1b Back",callback=back_from_peri_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=peri_c_4,x=41,y=14,min_width=9,text="Confirm",callback=save_peri_entry,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg} + + TextBox{parent=peri_c_5,x=1,y=1,height=1,text_align=CENTER,text="Settings saved!"} + PushButton{parent=peri_c_5,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=peri_c_5,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + TextBox{parent=peri_c_6,x=1,y=1,height=5,text_align=CENTER,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."} + PushButton{parent=peri_c_6,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=peri_c_6,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + --#endregion + --#region REDSTONE local rs_c_1 = Div{parent=rs_cfg,x=2,y=4,width=49} @@ -592,6 +908,7 @@ local function config_view(display) if settings.save("rtu.settings") then load_settings(ini_cfg) + load_settings(settings_cfg, true) rs_pane.set_value(4) else rs_pane.set_value(5) @@ -722,13 +1039,11 @@ local function config_view(display) bundled.set_value(false) tool_ctl.rs_cfg_color.set_value(1) tool_ctl.rs_cfg_color.disable() - else - rs_err.show() - end + else rs_err.show() end end PushButton{parent=rs_c_3,x=1,y=14,min_width=6,text="\x1b Back",callback=back_from_rs_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=rs_c_3,x=44,y=14,min_width=6,text="Save",callback=save_rs_entry,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg} + PushButton{parent=rs_c_3,x=41,y=14,min_width=9,text="Confirm",callback=save_rs_entry,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg} TextBox{parent=rs_c_4,x=1,y=1,height=1,text_align=CENTER,text="Settings saved!"} PushButton{parent=rs_c_4,x=1,y=14,min_width=6,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} @@ -767,6 +1082,7 @@ local function config_view(display) main_pane.set_value(1) net_pane.set_value(1) sum_pane.set_value(1) + peri_pane.set_value(1) rs_pane.set_value(1) end @@ -797,7 +1113,7 @@ local function config_view(display) if f[1] == "AuthKey" then val = string.rep("*", string.len(val)) end if f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") end - if val == "nil" then val = "n/a" end + if val == "nil" then val = "" end local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white)) alternate = not alternate @@ -880,10 +1196,57 @@ local function config_view(display) TextBox{parent=entry,x=2,y=1,width=14,height=1,text=name} TextBox{parent=entry,x=16,y=1,width=string.len(conn),height=1,text=conn,fg_bg=cpair(colors.gray,colors.white)} TextBox{parent=entry,x=33,y=1,width=1,height=1,text=unit,fg_bg=cpair(colors.gray,colors.white)} - PushButton{parent=entry,x=35,y=1,min_width=6,height=1,text="EDIT",callback=function()edit_rs_entry(i)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg} + PushButton{parent=entry,x=35,y=1,min_width=6,height=1,text="EDIT",callback=function()edit_rs_entry(i)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg} PushButton{parent=entry,x=41,y=1,min_width=8,height=1,text="DELETE",callback=function()delete_rs_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg} end end + + local function edit_peri_entry(idx, name, type) + new_peri(name, type) + tool_ctl.peri_cfg_editing = idx -- must be after new_peri + end + + local function delete_peri_entry(idx) + table.remove(tmp_cfg.Peripherals, idx) + tool_ctl.gen_peri_summary(tmp_cfg) + end + + -- generate the peripherals summary list + ---@param cfg rtu_config + function tool_ctl.gen_peri_summary(cfg) + peri_list.remove_all() + + for i = 1, #cfg.Peripherals do + local def = cfg.Peripherals[i] ---@type rtu_peri_definition + + local t = ppm.get_type(def.name) + local t_str = " (connect to edit)" + local disconnected = t == nil + + if not disconnected then t_str = "[" .. t .. "]" end + + local desc = " \x1a " + + if type(def.index) == "number" then + desc = desc .. "#" .. def.index .. " " + end + + if type(def.unit) == "number" then + desc = desc .. "for unit " .. def.unit + else + desc = desc .. "for the facility" + end + + local entry = Div{parent=peri_list,height=3} + TextBox{parent=entry,x=1,y=1,height=1,text="@ "..def.name,fg_bg=cpair(colors.black,colors.white)} + TextBox{parent=entry,x=1,y=2,height=1,text=" \x1a "..t_str,fg_bg=cpair(colors.gray,colors.white)} + TextBox{parent=entry,x=1,y=3,height=1,text=desc,fg_bg=cpair(colors.gray,colors.white)} + local edit_btn = PushButton{parent=entry,x=41,y=2,min_width=8,height=1,text="EDIT",callback=function()edit_peri_entry(i,def.name,t)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} + PushButton{parent=entry,x=41,y=3,min_width=8,height=1,text="DELETE",callback=function()delete_peri_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg} + + if disconnected then edit_btn.disable() end + end + end end -- reset terminal screen @@ -898,14 +1261,16 @@ end ---@param ask_config? boolean indicate if this is being called by the RTU startup app due to an invalid configuration function configurator.configure(ask_config) tool_ctl.ask_config = ask_config == true - tool_ctl.has_config = settings.load("/rtu.settings") - - load_settings(ini_cfg) + tool_ctl.has_config = load_settings(ini_cfg) + load_settings(settings_cfg, true) + tmp_cfg.Peripherals = deep_copy_peri(ini_cfg.Peripherals) tmp_cfg.Redstone = deep_copy_rs(ini_cfg.Redstone) reset_term() + ppm.mount_all() + -- set overridden colors for i = 1, #style.colors do term.setPaletteColor(style.colors[i].c, style.colors[i].hex) @@ -933,6 +1298,12 @@ function configurator.configure(ask_config) elseif event == "paste" then -- handle a paste event display.handle_paste(param1) + elseif event == "peripheral_detach" then + ppm.handle_unmount(param1) + tool_ctl.update_peri_list() + elseif event == "peripheral" then + ppm.mount(param1) + tool_ctl.update_peri_list() end if event == "terminate" then return end From 4c646249ad92e77d1adffe4f288fc7b32d2b7903 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Nov 2023 14:57:17 -0400 Subject: [PATCH 15/36] plc configurator cleanup --- reactor-plc/configure.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index f4aa27d..689d368 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -519,7 +519,6 @@ local function config_view(display) PushButton{parent=sum_c_3,x=44,y=14,min_width=6,text="OK",callback=delete_legacy,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.gray)} TextBox{parent=sum_c_4,x=1,y=1,height=5,text_align=CENTER,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."} - PushButton{parent=sum_c_4,x=1,y=14,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=sum_c_4,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} From 16258a26313ead85dc057610a722c1c04fb69c66 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 Nov 2023 15:06:29 -0400 Subject: [PATCH 16/36] rtu configurator config import --- rtu/configure.lua | 101 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 82 insertions(+), 19 deletions(-) diff --git a/rtu/configure.lua b/rtu/configure.lua index f399146..9135f3c 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -131,6 +131,7 @@ local tool_ctl = { dev_cfg = nil, ---@type graphics_element rs_cfg = nil, ---@type graphics_element settings_apply = nil, ---@type graphics_element + settings_confirm = nil, ---@type graphics_element go_home = nil, ---@type function gen_summary = nil, ---@type function @@ -283,6 +284,7 @@ local function config_view(display) tool_ctl.viewing_config = true tool_ctl.gen_summary(settings_cfg) tool_ctl.settings_apply.hide(true) + tool_ctl.settings_confirm.hide(true) main_pane.set_value(5) end @@ -308,8 +310,8 @@ local function config_view(display) if not tool_ctl.has_config then tool_ctl.view_gw_cfg.disable() - -- tool_ctl.dev_cfg.disable() - -- tool_ctl.rs_cfg.disable() + tool_ctl.dev_cfg.disable() + tool_ctl.rs_cfg.disable() end 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} @@ -477,6 +479,7 @@ local function config_view(display) tool_ctl.viewing_config = false tool_ctl.importing_legacy = false tool_ctl.settings_apply.show() + tool_ctl.settings_confirm.hide(true) main_pane.set_value(5) else path_err.show() end end @@ -492,8 +495,10 @@ local function config_view(display) local sum_c_2 = Div{parent=summary,x=2,y=4,width=49} local sum_c_3 = Div{parent=summary,x=2,y=4,width=49} local sum_c_4 = Div{parent=summary,x=2,y=4,width=49} + local sum_c_5 = Div{parent=summary,x=2,y=4,width=49} + local sum_c_6 = Div{parent=summary,x=2,y=4,width=49} - local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4}} + local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4,sum_c_5,sum_c_6}} TextBox{parent=summary,x=1,y=2,height=1,text_align=CENTER,text=" Summary",fg_bg=cpair(colors.black,colors.green)} @@ -504,7 +509,6 @@ local function config_view(display) main_pane.set_value(1) tool_ctl.viewing_config = false tool_ctl.importing_legacy = false - tool_ctl.settings_apply.show() else main_pane.set_value(4) end @@ -538,34 +542,46 @@ local function config_view(display) if tool_ctl.importing_legacy then tool_ctl.importing_legacy = false - sum_pane.set_value(3) - else sum_pane.set_value(2) end + sum_pane.set_value(5) else sum_pane.set_value(4) end + else sum_pane.set_value(6) end end PushButton{parent=sum_c_1,x=1,y=14,min_width=6,text="\x1b Back",callback=back_from_settings,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} tool_ctl.show_key_btn = PushButton{parent=sum_c_1,x=8,y=14,min_width=17,text="Unhide Auth Key",callback=function()tool_ctl.show_auth_key()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} tool_ctl.settings_apply = PushButton{parent=sum_c_1,x=43,y=14,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg} + tool_ctl.settings_confirm = PushButton{parent=sum_c_1,x=41,y=14,min_width=9,text="Confirm",callback=function()sum_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg} + tool_ctl.settings_confirm.hide() - TextBox{parent=sum_c_2,x=1,y=1,height=1,text_align=CENTER,text="Settings saved!"} + TextBox{parent=sum_c_2,x=1,y=1,height=1,text_align=CENTER,text="The following peripherals will be imported:"} + local peri_import_list = ListBox{parent=sum_c_2,x=1,y=3,height=10,width=51,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} - PushButton{parent=sum_c_2,x=1,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=sum_c_2,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} + PushButton{parent=sum_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()sum_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=sum_c_2,x=41,y=14,min_width=9,text="Confirm",callback=function()sum_pane.set_value(3)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg} - TextBox{parent=sum_c_3,x=1,y=1,height=2,text_align=CENTER,text="The old config.lua file will now be deleted, then the configurator will exit."} + TextBox{parent=sum_c_3,x=1,y=1,height=1,text_align=CENTER,text="The following redstone entries will be imported:"} + local rs_import_list = ListBox{parent=sum_c_3,x=1,y=3,height=10,width=51,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} + + PushButton{parent=sum_c_3,x=1,y=14,min_width=6,text="\x1b Back",callback=function()sum_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=sum_c_3,x=43,y=14,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg} + + TextBox{parent=sum_c_4,x=1,y=1,height=1,text_align=CENTER,text="Settings saved!"} + PushButton{parent=sum_c_4,x=1,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=sum_c_4,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} + + TextBox{parent=sum_c_5,x=1,y=1,height=2,text_align=CENTER,text="The old config.lua file will now be deleted, then the configurator will exit."} local function delete_legacy() fs.delete("/rtu/config.lua") exit() end - PushButton{parent=sum_c_3,x=1,y=14,min_width=8,text="Cancel",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=sum_c_3,x=44,y=14,min_width=6,text="OK",callback=delete_legacy,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.gray)} + PushButton{parent=sum_c_5,x=1,y=14,min_width=8,text="Cancel",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=sum_c_5,x=44,y=14,min_width=6,text="OK",callback=delete_legacy,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.gray)} - TextBox{parent=sum_c_4,x=1,y=1,height=5,text_align=CENTER,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."} - - PushButton{parent=sum_c_4,x=1,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=sum_c_4,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} + TextBox{parent=sum_c_6,x=1,y=1,height=5,text_align=CENTER,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."} + PushButton{parent=sum_c_6,x=1,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=sum_c_6,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} --#endregion @@ -1071,9 +1087,58 @@ local function config_view(display) tmp_cfg.LogPath = config.LOG_PATH tmp_cfg.LogDebug = config.LOG_DEBUG or false + peri_import_list.remove_all() + for _, entry in ipairs(config.RTU_DEVICES) do + if entry.for_reactor == 0 then entry.for_reactor = nil end + + local def = { name = entry.name, unit = entry.for_reactor, index = entry.index } + table.insert(tmp_cfg.Peripherals, def) + + local desc = " \x1a " + + if type(def.index) == "number" then + desc = desc .. "#" .. def.index .. " " + end + + if type(def.unit) == "number" then + desc = desc .. "for unit " .. def.unit + else + desc = desc .. "for the facility" + end + + local line = Div{parent=peri_import_list,height=2} + TextBox{parent=line,x=1,y=1,height=1,text="@ "..def.name,fg_bg=cpair(colors.black,colors.white)} + TextBox{parent=line,x=1,y=2,height=1,text=desc,fg_bg=cpair(colors.gray,colors.white)} + end + + rs_import_list.remove_all() + for _, entry in ipairs(config.RTU_REDSTONE) do + if entry.for_reactor == 0 then entry.for_reactor = nil end + for _, io_entry in ipairs(entry.io) do + local def = { unit = entry.for_reactor, port = io_entry.port, side = io_entry.side, color = io_entry.bundled_color } + table.insert(tmp_cfg.Redstone, def) + + local name = rsio.to_string(def.port) + local io_dir = util.trinary(rsio.get_io_mode(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b") + local conn = def.side + local unit = "facility" + + if def.unit then unit = "unit " .. def.unit end + if def.color ~= nil then conn = def.side .. "/" .. color_name_map[def.color] end + + local line = Div{parent=rs_import_list,height=1} + TextBox{parent=line,x=1,y=1,width=1,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)} + TextBox{parent=line,x=2,y=1,width=14,height=1,text=name} + TextBox{parent=line,x=18,y=1,width=string.len(conn),height=1,text=conn,fg_bg=cpair(colors.gray,colors.white)} + TextBox{parent=line,x=40,y=1,height=1,text=unit,fg_bg=cpair(colors.gray,colors.white)} + end + end + tool_ctl.gen_summary(tmp_cfg) sum_pane.set_value(1) main_pane.set_value(5) + tool_ctl.settings_apply.hide(true) + tool_ctl.settings_confirm.show() tool_ctl.importing_legacy = true end @@ -1187,9 +1252,7 @@ local function config_view(display) local conn = def.side local unit = util.strval(def.unit or "F") - if def.color ~= nil then - conn = def.side .. "/" .. color_name_map[def.color] - end + if def.color ~= nil then conn = def.side .. "/" .. color_name_map[def.color] end local entry = Div{parent=rs_list,height=1} TextBox{parent=entry,x=1,y=1,width=1,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)} From 32653c3b8a184e2d45afda1d8a607eac367e563f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 5 Nov 2023 13:23:22 -0500 Subject: [PATCH 17/36] param type change and added validator.assert --- scada-common/rsio.lua | 2 +- scada-common/util.lua | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 92ee280..c3294cb 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -279,7 +279,7 @@ end -- check if a color is a valid single color ---@nodiscard ----@param color integer +---@param color any ---@return boolean valid function rsio.is_color(color) return util.is_int(color) and (color > 0) and (_B_AND(color, (color - 1)) == 0) diff --git a/scada-common/util.lua b/scada-common/util.lua index 1ab8cd7..3623583 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -18,7 +18,7 @@ local type = type local util = {} -- scada-common version -util.version = "1.1.7" +util.version = "1.1.8" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 @@ -482,6 +482,7 @@ function util.new_validator() function public.assert_type_str(value) valid = valid and type(value) == "string" end function public.assert_type_table(value) valid = valid and type(value) == "table" end + function public.assert(check) valid = valid and (check == true) end function public.assert_eq(check, expect) valid = valid and check == expect end function public.assert_min(check, min) valid = valid and check >= min end function public.assert_min_ex(check, min) valid = valid and check > min end From 9e13a3a4676b2d8ac8182c395c715c812e25405c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 5 Nov 2023 13:23:45 -0500 Subject: [PATCH 18/36] added ability to view reactor PLC config after importing if cancelled before deleting --- reactor-plc/configure.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index 689d368..f8e403f 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -481,6 +481,8 @@ local function config_view(display) try_set(path, ini_cfg.LogPath) try_set(en_dbg, ini_cfg.LogDebug) + tool_ctl.view_cfg.enable() + if tool_ctl.importing_legacy then tool_ctl.importing_legacy = false sum_pane.set_value(3) From 1b5e8cb69cfd9f651df4d4e72d0d9a105dd9be86 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 6 Nov 2023 09:25:44 -0500 Subject: [PATCH 19/36] #306 RTU integration with new settings --- rtu/configure.lua | 173 +++++++++++--------- rtu/panel/front_panel.lua | 8 +- rtu/rtu.lua | 63 ++++++-- rtu/startup.lua | 331 ++++++++++++++++++++------------------ rtu/threads.lua | 265 +++++++++++++++++++----------- scada-common/rsio.lua | 12 ++ 6 files changed, 511 insertions(+), 341 deletions(-) diff --git a/rtu/configure.lua b/rtu/configure.lua index 9135f3c..106724e 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -156,6 +156,7 @@ local tool_ctl = { p_assign_end = nil, ---@type graphics_element p_desc = nil, ---@type graphics_element p_desc_ext = nil, ---@type graphics_element + p_err = nil, ---@type graphics_element rs_cfg_selection = nil, ---@type graphics_element rs_cfg_unit_l = nil, ---@type graphics_element @@ -200,7 +201,6 @@ local side_options = { "Top", "Bottom", "Left", "Right", "Front", "Back" } local side_options_map = { "top", "bottom", "left", "right", "front", "back" } local color_options = { "Red", "Orange", "Yellow", "Lime", "Green", "Cyan", "Light Blue", "Blue", "Purple", "Magenta", "Pink", "White", "Light Gray", "Gray", "Black", "Brown" } local color_options_map = { colors.red, colors.orange, colors.yellow, colors.lime, colors.green, colors.cyan, colors.lightBlue, colors.blue, colors.purple, colors.magenta, colors.pink, colors.white, colors.lightGray, colors.gray, colors.black, colors.brown } -local color_name_map = { [colors.red] = "red", [colors.orange] = "orange", [colors.yellow] = "yellow", [colors.lime] = "lime", [colors.green] = "green", [colors.cyan] = "cyan", [colors.lightBlue] = "lightBlue", [colors.blue] = "blue", [colors.purple] = "purple", [colors.magenta] = "magenta", [colors.pink] = "pink", [colors.white] = "white", [colors.lightGray] = "lightGray", [colors.gray] = "gray", [colors.black] = "black", [colors.brown] = "brown" } -- convert text representation to index ---@param side string @@ -223,7 +223,7 @@ local function deep_copy_peri(data) local array = {} for _, d in ipairs(data) do table.insert(array, { unit = d.unit, index = d.index, name = d.name }) end return array - end +end -- deep copy redstone defs local function deep_copy_rs(data) @@ -236,7 +236,7 @@ end ---@param target rtu_config ---@param raw boolean? true to not use default values local function load_settings(target, raw) - for _, v in pairs(fields) do settings.unset(v[1]) end + for k, _ in pairs(tmp_cfg) do settings.unset(k) end local loaded = settings.load("/rtu.settings") @@ -271,13 +271,14 @@ local function config_view(display) --#region MAIN PAGE - local y_start = 5 - - TextBox{parent=main_page,x=2,y=2,height=2,text_align=CENTER,text="Welcome to the RTU gateway configurator! Please select one of the following options."} + local y_start = 2 if tool_ctl.ask_config then TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text_align=CENTER,text="Notice: This device has no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)} y_start = y_start + 5 + else + TextBox{parent=main_page,x=2,y=2,height=2,text_align=CENTER,text="Welcome to the RTU gateway configurator! Please select one of the following options."} + y_start = y_start + 3 end local function view_config() @@ -520,8 +521,11 @@ local function config_view(display) if data ~= nil then element.set_value(data) end end - local function save_and_continue() - for k, v in pairs(tmp_cfg) do settings.set(k, v) end + ---@param exclude_conns boolean? true to exclude saving peripheral/redstone connections + local function save_and_continue(exclude_conns) + for k, v in pairs(tmp_cfg) do + if not (exclude_conns and (k == "Peripherals" or k == "Redstone")) then settings.set(k, v) end + end if settings.save("rtu.settings") then load_settings(ini_cfg) @@ -537,19 +541,27 @@ local function config_view(display) try_set(path, ini_cfg.LogPath) try_set(en_dbg, ini_cfg.LogDebug) + if not exclude_conns then + tmp_cfg.Peripherals = deep_copy_peri(ini_cfg.Peripherals) + tmp_cfg.Redstone = deep_copy_rs(ini_cfg.Redstone) + + tool_ctl.update_peri_list() + end + tool_ctl.dev_cfg.enable() tool_ctl.rs_cfg.enable() + tool_ctl.view_gw_cfg.enable() if tool_ctl.importing_legacy then tool_ctl.importing_legacy = false sum_pane.set_value(5) - else sum_pane.set_value(4) end + else sum_pane.set_value(4) end else sum_pane.set_value(6) end end PushButton{parent=sum_c_1,x=1,y=14,min_width=6,text="\x1b Back",callback=back_from_settings,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} tool_ctl.show_key_btn = PushButton{parent=sum_c_1,x=8,y=14,min_width=17,text="Unhide Auth Key",callback=function()tool_ctl.show_auth_key()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} - tool_ctl.settings_apply = PushButton{parent=sum_c_1,x=43,y=14,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg} + tool_ctl.settings_apply = PushButton{parent=sum_c_1,x=43,y=14,min_width=7,text="Apply",callback=function()save_and_continue(true)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg} tool_ctl.settings_confirm = PushButton{parent=sum_c_1,x=41,y=14,min_width=9,text="Confirm",callback=function()sum_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg} tool_ctl.settings_confirm.hide() @@ -648,8 +660,10 @@ local function config_view(display) tool_ctl.ppm_devs = ListBox{parent=peri_c_2,x=1,y=3,height=10,width=51,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} + PushButton{parent=peri_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=peri_c_2,x=8,y=14,min_width=10,text="Manual +",callback=function()peri_pane.set_value(3)end,fg_bg=cpair(colors.black,colors.orange),active_fg_bg=btn_act_fg_bg} PushButton{parent=peri_c_2,x=26,y=14,min_width=24,text="I don't see my device!",callback=function()peri_pane.set_value(7)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg} + TextBox{parent=peri_c_7,x=1,y=1,height=10,text="Make sure your device is either touching the RTU, or connected via wired modems. There should be a wired modem on a side of the RTU then one on the device, connected by a cable. The modem on the device needs to be right clicked to connect it (which will turn its border red), at which point the peripheral name will be shown in the chat."} TextBox{parent=peri_c_7,x=1,y=9,height=4,text="If it still does not show, it may not be compatible. Currently only Boilers, Turbines, Dynamic Tanks, SNAs, SPSs, Induction Matricies, and Environment Detectors are supported."} PushButton{parent=peri_c_7,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} @@ -659,6 +673,7 @@ local function config_view(display) new_peri_attrs = { name, type } tool_ctl.peri_cfg_editing = false + tool_ctl.p_err.hide(true) tool_ctl.p_name_msg.set_value("Configuring peripheral on '" .. name .. "':") tool_ctl.p_desc_ext.set_value("") @@ -769,8 +784,6 @@ local function config_view(display) tool_ctl.update_peri_list() - PushButton{parent=peri_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=peri_c_3,x=1,y=1,height=4,text_align=CENTER,text="This feature is intended for advanced users. If you are clicking this just because your device is not shown, follow the connection instructions in 'I don't see my device!'."} TextBox{parent=peri_c_3,x=1,y=6,height=4,text_align=CENTER,text="Peripheral Name"} local p_name = TextField{parent=peri_c_3,x=1,y=7,width=49,height=1,max_len=128,fg_bg=bw_fg_bg} @@ -813,8 +826,8 @@ local function config_view(display) tool_ctl.p_desc = TextBox{parent=peri_c_4,x=1,y=7,height=6,text_align=LEFT,text="",fg_bg=g_lg_fg_bg} tool_ctl.p_desc_ext = TextBox{parent=peri_c_4,x=1,y=6,height=7,text_align=LEFT,text="",fg_bg=g_lg_fg_bg} - local p_err = TextBox{parent=peri_c_4,x=8,y=14,height=1,width=35,text_align=LEFT,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} - p_err.hide(true) + tool_ctl.p_err = TextBox{parent=peri_c_4,x=8,y=14,height=1,width=35,text_align=LEFT,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + tool_ctl.p_err.hide(true) local function back_from_peri_opts() if tool_ctl.peri_cfg_editing ~= false then @@ -842,33 +855,33 @@ local function config_view(display) if (peri_type == "dynamicValve" or peri_type == "environmentDetector") and for_facility then -- skip elseif not (util.is_int(u) and u > 0 and u < 5) then - p_err.set_value("Unit ID must be within 1 through 4.") - p_err.show() + tool_ctl.p_err.set_value("Unit ID must be within 1 through 4.") + tool_ctl.p_err.show() return else unit = u end end if peri_type == "boilerValve" then if not (idx == 1 or idx == 2) then - p_err.set_value("Index must be 1 or 2.") - p_err.show() + tool_ctl.p_err.set_value("Index must be 1 or 2.") + tool_ctl.p_err.show() return else index = idx end elseif peri_type == "turbineValve" then if not (idx == 1 or idx == 2 or idx == 3) then - p_err.set_value("Index must be 1, 2, or 3.") - p_err.show() + tool_ctl.p_err.set_value("Index must be 1, 2, or 3.") + tool_ctl.p_err.show() return else index = idx end - elseif peri_type == "dynamicValve" and not for_facility then + elseif peri_type == "dynamicValve" and for_facility then if not (util.is_int(idx) and idx > 0 and idx < 5) then - p_err.set_value("Index must be within 1 through 4.") - p_err.show() + tool_ctl.p_err.set_value("Index must be within 1 through 4.") + tool_ctl.p_err.show() return else index = idx end end - p_err.hide(true) + tool_ctl.p_err.hide(true) ---@type rtu_peri_definition local def = { name = peri_name, unit = unit, index = index } @@ -1124,7 +1137,7 @@ local function config_view(display) local unit = "facility" if def.unit then unit = "unit " .. def.unit end - if def.color ~= nil then conn = def.side .. "/" .. color_name_map[def.color] end + if def.color ~= nil then conn = def.side .. "/" .. rsio.color_name(def.color) end local line = Div{parent=rs_import_list,height=1} TextBox{parent=line,x=1,y=1,width=1,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)} @@ -1202,6 +1215,67 @@ local function config_view(display) end end + ---@param def rtu_peri_definition + ---@param idx integer + ---@param type string + local function edit_peri_entry(idx, def, type) + -- set inputs BEFORE calling new_peri() + if def.index ~= nil then tool_ctl.p_idx.set_value(def.index) end + if def.unit == nil then + tool_ctl.p_assign_btn.set_value(1) + else + tool_ctl.p_unit.set_value(def.unit) + tool_ctl.p_assign_btn.set_value(2) + end + + new_peri(def.name, type) + + -- set editing mode AFTER new_peri() + tool_ctl.peri_cfg_editing = idx + end + + local function delete_peri_entry(idx) + table.remove(tmp_cfg.Peripherals, idx) + tool_ctl.gen_peri_summary(tmp_cfg) + end + + -- generate the peripherals summary list + ---@param cfg rtu_config + function tool_ctl.gen_peri_summary(cfg) + peri_list.remove_all() + + for i = 1, #cfg.Peripherals do + local def = cfg.Peripherals[i] ---@type rtu_peri_definition + + local t = ppm.get_type(def.name) + local t_str = " (connect to edit)" + local disconnected = t == nil + + if not disconnected then t_str = "[" .. t .. "]" end + + local desc = " \x1a " + + if type(def.index) == "number" then + desc = desc .. "#" .. def.index .. " " + end + + if type(def.unit) == "number" then + desc = desc .. "for unit " .. def.unit + else + desc = desc .. "for the facility" + end + + local entry = Div{parent=peri_list,height=3} + TextBox{parent=entry,x=1,y=1,height=1,text="@ "..def.name,fg_bg=cpair(colors.black,colors.white)} + TextBox{parent=entry,x=1,y=2,height=1,text=" \x1a "..t_str,fg_bg=cpair(colors.gray,colors.white)} + TextBox{parent=entry,x=1,y=3,height=1,text=desc,fg_bg=cpair(colors.gray,colors.white)} + local edit_btn = PushButton{parent=entry,x=41,y=2,min_width=8,height=1,text="EDIT",callback=function()edit_peri_entry(i,def,t or "")end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} + PushButton{parent=entry,x=41,y=3,min_width=8,height=1,text="DELETE",callback=function()delete_peri_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg} + + if disconnected then edit_btn.disable() end + end + end + local function edit_rs_entry(idx) local def = tmp_cfg.Redstone[idx] ---@type rtu_rs_definition @@ -1252,7 +1326,7 @@ local function config_view(display) local conn = def.side local unit = util.strval(def.unit or "F") - if def.color ~= nil then conn = def.side .. "/" .. color_name_map[def.color] end + if def.color ~= nil then conn = def.side .. "/" .. rsio.color_name(def.color) end local entry = Div{parent=rs_list,height=1} TextBox{parent=entry,x=1,y=1,width=1,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)} @@ -1263,53 +1337,6 @@ local function config_view(display) PushButton{parent=entry,x=41,y=1,min_width=8,height=1,text="DELETE",callback=function()delete_rs_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg} end end - - local function edit_peri_entry(idx, name, type) - new_peri(name, type) - tool_ctl.peri_cfg_editing = idx -- must be after new_peri - end - - local function delete_peri_entry(idx) - table.remove(tmp_cfg.Peripherals, idx) - tool_ctl.gen_peri_summary(tmp_cfg) - end - - -- generate the peripherals summary list - ---@param cfg rtu_config - function tool_ctl.gen_peri_summary(cfg) - peri_list.remove_all() - - for i = 1, #cfg.Peripherals do - local def = cfg.Peripherals[i] ---@type rtu_peri_definition - - local t = ppm.get_type(def.name) - local t_str = " (connect to edit)" - local disconnected = t == nil - - if not disconnected then t_str = "[" .. t .. "]" end - - local desc = " \x1a " - - if type(def.index) == "number" then - desc = desc .. "#" .. def.index .. " " - end - - if type(def.unit) == "number" then - desc = desc .. "for unit " .. def.unit - else - desc = desc .. "for the facility" - end - - local entry = Div{parent=peri_list,height=3} - TextBox{parent=entry,x=1,y=1,height=1,text="@ "..def.name,fg_bg=cpair(colors.black,colors.white)} - TextBox{parent=entry,x=1,y=2,height=1,text=" \x1a "..t_str,fg_bg=cpair(colors.gray,colors.white)} - TextBox{parent=entry,x=1,y=3,height=1,text=desc,fg_bg=cpair(colors.gray,colors.white)} - local edit_btn = PushButton{parent=entry,x=41,y=2,min_width=8,height=1,text="EDIT",callback=function()edit_peri_entry(i,def.name,t)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} - PushButton{parent=entry,x=41,y=3,min_width=8,height=1,text="DELETE",callback=function()delete_peri_entry(i)end,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg} - - if disconnected then edit_btn.disable() end - end - end end -- reset terminal screen diff --git a/rtu/panel/front_panel.lua b/rtu/panel/front_panel.lua index 2e5537b..acf45d6 100644 --- a/rtu/panel/front_panel.lua +++ b/rtu/panel/front_panel.lua @@ -109,12 +109,10 @@ local function init(panel, units) unit_hw.register(databus.ps, "unit_hw_" .. i, unit_hw.update) -- unit name identifier (type + index) - local name = util.c(UNIT_TYPE_LABELS[unit.type + 1], " ", unit.index) - local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=name,height=1} + local function get_name(t) return util.c(UNIT_TYPE_LABELS[t + 1], " ", util.trinary(util.is_int(unit.index), unit.index, "")) end + local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=get_name(unit.type),height=1} - name_box.register(databus.ps, "unit_type_" .. i, function (t) - name_box.set_value(util.c(UNIT_TYPE_LABELS[t + 1], " ", unit.index)) - end) + name_box.register(databus.ps, "unit_type_" .. i, function (t) name_box.set_value(get_name(t)) end) -- assignment (unit # or facility) local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 242bd18..e019e23 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -5,7 +5,6 @@ local log = require("scada-common.log") local types = require("scada-common.types") local util = require("scada-common.util") -local config = require("rtu.config") local databus = require("rtu.databus") local modbus = require("rtu.modbus") @@ -17,6 +16,51 @@ local ESTABLISH_ACK = comms.ESTABLISH_ACK local MGMT_TYPE = comms.MGMT_TYPE local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE +---@type rtu_config +local config = {} + +rtu.config = config + +-- load the RTU configuration +function rtu.load_config() + if not settings.load("/rtu.settings") then return false end + + config.Peripherals = settings.get("Peripherals") + config.Redstone = settings.get("Redstone") + + 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.TrustedRange = settings.get("TrustedRange") + config.AuthKey = settings.get("AuthKey") + config.LogMode = settings.get("LogMode") + config.LogPath = settings.get("LogPath") + config.LogDebug = settings.get("LogDebug") + + local cfv = util.new_validator() + + cfv.assert_type_num(config.SpeakerVolume) + cfv.assert_channel(config.SVR_Channel) + cfv.assert_channel(config.RTU_Channel) + cfv.assert_type_int(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) + + if type(config.AuthKey) == "string" then + local len = string.len(config.AuthKey) + cfv.assert_eq(len == 0 or len >= 8, true) + end + + cfv.assert_type_int(config.LogMode) + cfv.assert_type_str(config.LogPath) + cfv.assert_type_bool(config.LogDebug) + + return cfv.valid() +end + -- create a new RTU unit ---@nodiscard function rtu.init_unit() @@ -175,7 +219,7 @@ function rtu.init_sounder(speaker) function spkr_ctl.continue() if spkr_ctl.playing then if spkr_ctl.speaker ~= nil and spkr_ctl.stream.has_next_block() then - local success = spkr_ctl.speaker.playAudio(spkr_ctl.stream.get_next_block(), config.SOUNDER_VOLUME) + local success = spkr_ctl.speaker.playAudio(spkr_ctl.stream.get_next_block(), config.SpeakerVolume) if not success then log.error(util.c("rtu_sounder(", spkr_ctl.name, "): error playing audio")) end end end @@ -203,11 +247,8 @@ end ---@nodiscard ---@param version string RTU version ---@param nic nic network interface device ----@param rtu_channel integer PLC comms channel ----@param svr_channel integer supervisor server channel ----@param range integer trusted device connection range ---@param conn_watchdog watchdog watchdog reference -function rtu.comms(version, nic, rtu_channel, svr_channel, range, conn_watchdog) +function rtu.comms(version, nic, conn_watchdog) local self = { sv_addr = comms.BROADCAST, seq_num = 0, @@ -218,13 +259,13 @@ function rtu.comms(version, nic, rtu_channel, svr_channel, range, conn_watchdog) local insert = table.insert - comms.set_trusted_range(range) + comms.set_trusted_range(config.TrustedRange) -- PRIVATE FUNCTIONS -- -- configure modem channels nic.closeAll() - nic.open(rtu_channel) + nic.open(config.RTU_Channel) -- send a scada management packet ---@param msg_type MGMT_TYPE @@ -236,7 +277,7 @@ function rtu.comms(version, nic, rtu_channel, svr_channel, range, conn_watchdog) m_pkt.make(msg_type, msg) s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) - nic.transmit(svr_channel, rtu_channel, s_pkt) + nic.transmit(config.SVR_Channel, config.RTU_Channel, s_pkt) self.seq_num = self.seq_num + 1 end @@ -280,7 +321,7 @@ function rtu.comms(version, nic, rtu_channel, svr_channel, range, conn_watchdog) 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(svr_channel, rtu_channel, s_pkt) + nic.transmit(config.SVR_Channel, config.RTU_Channel, s_pkt) self.seq_num = self.seq_num + 1 end @@ -365,7 +406,7 @@ function rtu.comms(version, nic, rtu_channel, svr_channel, range, conn_watchdog) local l_chan = packet.scada_frame.local_channel() local src_addr = packet.scada_frame.src_addr() - if l_chan == rtu_channel then + if l_chan == config.RTU_Channel then -- check sequence number if self.r_seq_num == nil then self.r_seq_num = packet.scada_frame.seq_num() diff --git a/rtu/startup.lua b/rtu/startup.lua index f40df57..6ad5401 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -15,7 +15,7 @@ local rsio = require("scada-common.rsio") local types = require("scada-common.types") local util = require("scada-common.util") -local config = require("rtu.config") +local configure = require("rtu.configure") local databus = require("rtu.databus") local modbus = require("rtu.modbus") local renderer = require("rtu.renderer") @@ -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.6.6" +local RTU_VERSION = "v1.7.0" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE @@ -40,27 +40,26 @@ local println = util.println local println_ts = util.println_ts ---------------------------------------- --- config validation +-- get configuration ---------------------------------------- -local cfv = util.new_validator() +if not rtu.load_config() then + -- try to reconfigure (user action) + local success, error = configure.configure(true) + if success then + assert(rtu.load_config(), "failed to load valid RTU configuration") + else + assert(success, "RTU configuration error: " .. error) + end +end -cfv.assert_channel(config.SVR_CHANNEL) -cfv.assert_channel(config.RTU_CHANNEL) -cfv.assert_type_int(config.TRUSTED_RANGE) -cfv.assert_type_num(config.COMMS_TIMEOUT) -cfv.assert_min(config.COMMS_TIMEOUT, 2) -cfv.assert_type_str(config.LOG_PATH) -cfv.assert_type_int(config.LOG_MODE) -cfv.assert_type_table(config.RTU_DEVICES) -cfv.assert_type_table(config.RTU_REDSTONE) -assert(cfv.valid(), "bad config file: missing/invalid fields") +local config = rtu.config ---------------------------------------- -- log init ---------------------------------------- -log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG == true) +log.init(config.LogPath, config.LogMode, config.LogDebug) log.info("========================================") log.info("BOOTING rtu.startup " .. RTU_VERSION) @@ -85,8 +84,8 @@ local function main() ppm.mount_all() -- message authentication init - if type(config.AUTH_KEY) == "string" then - network.init_mac(config.AUTH_KEY) + if type(config.AuthKey) == "string" then + network.init_mac(config.AuthKey) end -- get modem @@ -139,150 +138,133 @@ local function main() local units = __shared_memory.rtu_sys.units - local rtu_redstone = config.RTU_REDSTONE - local rtu_devices = config.RTU_DEVICES + local rtu_redstone = config.Redstone + local rtu_devices = config.Peripherals - -- configure RTU gateway based on config file definitions - local function configure() + -- configure RTU gateway based on settings file definitions + local function sys_config() -- redstone interfaces + local rs_rtus = {} + + -- go through redstone definitions list for entry_idx = 1, #rtu_redstone do - local rs_rtu = redstone_rtu.new() - local io_table = rtu_redstone[entry_idx].io ---@type table - local io_reactor = rtu_redstone[entry_idx].for_reactor ---@type integer + local entry = rtu_redstone[entry_idx] ---@type rtu_rs_definition + local assignment = "" + local for_reactor = entry.unit + local iface_name = util.trinary(entry.color ~= nil, util.c(entry.side, "/", rsio.color_name(entry.color)), entry.side) - -- CHECK: reactor ID must be >= to 1 - if (not util.is_int(io_reactor)) or (io_reactor < 0) then - local message = util.c("configure> redstone entry #", entry_idx, " : ", io_reactor, " isn't an integer >= 0") - println(message) - log.fatal(message) - return false - end - - -- CHECK: io table exists - if type(io_table) ~= "table" then - local message = util.c("configure> redstone entry #", entry_idx, " no IO table found") - println(message) - log.fatal(message) - return false - end - - local capabilities = {} - - log.debug(util.c("configure> starting redstone RTU I/O linking for reactor ", io_reactor, "...")) - - local continue = true - - -- CHECK: no duplicate entries - for i = 1, #units do - local unit = units[i] ---@type rtu_unit_registry_entry - if unit.reactor == io_reactor and unit.type == RTU_UNIT_TYPE.REDSTONE then - -- duplicate entry - local message = util.c("configure> skipping definition block #", entry_idx, " for reactor ", io_reactor, - " with already defined redstone I/O") - println(message) - log.warning(message) - continue = false - break + if util.is_int(entry.unit) and entry.unit > 0 and entry.unit < 5 then + ---@cast for_reactor integer + assignment = "reactor unit " .. entry.unit + if rs_rtus[for_reactor] == nil then + log.debug(util.c("configure> allocated redstone RTU for reactor unit ", entry.unit)) + rs_rtus[for_reactor] = { rtu = redstone_rtu.new(), capabilities = {} } end + elseif entry.unit == nil then + assignment = "facility" + for_reactor = 0 + if rs_rtus[for_reactor] == nil then + log.debug(util.c("configure> allocated redstone RTU for the facility")) + rs_rtus[for_reactor] = { rtu = redstone_rtu.new(), capabilities = {} } + end + else + local message = util.c("configure> invalid unit assignment at block index #", entry_idx) + println(message) + log.fatal(message) + return false end - -- not a duplicate - if continue then - for i = 1, #io_table do - local valid = false - local conf = io_table[i] + -- 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 - -- verify configuration - if rsio.is_valid_port(conf.port) and rsio.is_valid_side(conf.side) then - if conf.bundled_color then - valid = rsio.is_color(conf.bundled_color) - else - valid = true - end - end + local rs_rtu = rs_rtus[for_reactor].rtu + local capabilities = rs_rtus[for_reactor].capabilities - if not valid then - local message = util.c("configure> invalid redstone definition at index ", i, " in definition block #", entry_idx, - " (for reactor ", io_reactor, ")") + if not valid then + local message = util.c("configure> invalid redstone definition at block index #", entry_idx) + println(message) + log.fatal(message) + 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(capabilities, entry.port) then + local message = util.c("configure> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name) println(message) - log.fatal(message) - return false + log.warning(message) else - -- link redstone in RTU - local mode = rsio.get_io_mode(conf.port) - if mode == rsio.IO_MODE.DIGITAL_IN then - -- can't have duplicate inputs - if util.table_contains(capabilities, conf.port) then - local message = util.c("configure> skipping duplicate input for port ", rsio.to_string(conf.port), " on side ", conf.side) - println(message) - log.warning(message) - else - rs_rtu.link_di(conf.side, conf.bundled_color) - end - elseif mode == rsio.IO_MODE.DIGITAL_OUT then - rs_rtu.link_do(conf.side, conf.bundled_color) - elseif mode == rsio.IO_MODE.ANALOG_IN then - -- can't have duplicate inputs - if util.table_contains(capabilities, conf.port) then - local message = util.c("configure> skipping duplicate input for port ", rsio.to_string(conf.port), " on side ", conf.side) - println(message) - log.warning(message) - else - rs_rtu.link_ai(conf.side) - end - elseif mode == rsio.IO_MODE.ANALOG_OUT then - rs_rtu.link_ao(conf.side) - else - -- should be unreachable code, we already validated ports - log.error("configure> fell through if chain attempting to identify IO mode", true) - println("configure> encountered a software error, check logs") - return false - end - - table.insert(capabilities, conf.port) - - log.debug(util.c("configure> linked redstone ", #capabilities, ": ", rsio.to_string(conf.port), - " (", conf.side, ") for reactor ", io_reactor)) + rs_rtu.link_di(entry.side, entry.color) end + elseif mode == rsio.IO_MODE.DIGITAL_OUT then + rs_rtu.link_do(entry.side, entry.color) + elseif mode == rsio.IO_MODE.ANALOG_IN then + -- can't have duplicate inputs + if util.table_contains(capabilities, entry.port) then + local message = util.c("configure> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name) + println(message) + log.warning(message) + else + rs_rtu.link_ai(entry.side) + end + elseif mode == rsio.IO_MODE.ANALOG_OUT then + rs_rtu.link_ao(entry.side) + else + -- should be unreachable code, we already validated ports + log.error("configure> fell through if chain attempting to identify IO mode at block index #" .. entry_idx, true) + println("configure> encountered a software error, check logs") + return false end - ---@class rtu_unit_registry_entry - local unit = { - uid = 0, ---@type integer - name = "redstone_io", ---@type string - type = RTU_UNIT_TYPE.REDSTONE, ---@type RTU_UNIT_TYPE - index = entry_idx, ---@type integer - reactor = io_reactor, ---@type integer - device = capabilities, ---@type table use device field for redstone ports - is_multiblock = false, ---@type boolean - formed = nil, ---@type boolean|nil - hw_state = RTU_UNIT_HW_STATE.OK, ---@type RTU_UNIT_HW_STATE - rtu = rs_rtu, ---@type rtu_device|rtu_rs_device - modbus_io = modbus.new(rs_rtu, false), - pkt_queue = nil, ---@type mqueue|nil - thread = nil ---@type parallel_thread|nil - } + table.insert(capabilities, entry.port) - table.insert(units, unit) - - local for_message = "facility" - if io_reactor > 0 then - for_message = util.c("reactor ", io_reactor) - end - - log.info(util.c("configure> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for ", for_message)) - - unit.uid = #units - - databus.tx_unit_hw_status(unit.uid, unit.hw_state) + log.debug(util.c("configure> linked redstone ", #capabilities, ": ", rsio.to_string(entry.port), " (", iface_name, ") for ", assignment)) end end + -- create unit entries for redstone RTUs + for for_reactor, def in pairs(rs_rtus) do + ---@class rtu_unit_registry_entry + local unit = { + uid = 0, ---@type integer + name = "redstone_io", ---@type string + type = RTU_UNIT_TYPE.REDSTONE, ---@type RTU_UNIT_TYPE + index = false, ---@type integer|false + reactor = for_reactor, ---@type integer + device = def.capabilities, ---@type table use device field for redstone ports + is_multiblock = false, ---@type boolean + formed = nil, ---@type boolean|nil + hw_state = RTU_UNIT_HW_STATE.OK, ---@type RTU_UNIT_HW_STATE + rtu = def.rtu, ---@type rtu_device|rtu_rs_device + modbus_io = modbus.new(def.rtu, false), + pkt_queue = nil, ---@type mqueue|nil + thread = nil ---@type parallel_thread|nil + } + + table.insert(units, unit) + + local for_message = "facility" + if util.is_int(for_reactor) then + for_message = util.c("reactor unit ", for_reactor) + end + + log.info(util.c("configure> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for ", for_message)) + + unit.uid = #units + + databus.tx_unit_hw_status(unit.uid, unit.hw_state) + end + -- mounted peripherals for i = 1, #rtu_devices do - local name = rtu_devices[i].name - local index = rtu_devices[i].index - local for_reactor = rtu_devices[i].for_reactor + 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 @@ -292,20 +274,37 @@ local function main() return false end - -- CHECK: index is an integer >= 1 - if (not util.is_int(index)) or (index <= 0) then - local message = util.c("configure> device entry #", i, ": index ", index, " isn't an integer >= 1") + -- CHECK: index type + if (index ~= nil) and (not util.is_int(index)) then + local message = util.c("configure> device entry #", i, ": index ", index, " isn't valid") println(message) log.fatal(message) return false end + -- CHECK: index range + local function validate_index(min, max) + if (util.is_int(index) and index < min) and (max == nil or index > max) then + local message = util.c("configure> device entry #", i, ": index ", index, " isn't >= ", min, " and <= ", max) + println(message) + log.fatal(message) + return false + else return true end + end + -- CHECK: reactor is an integer >= 0 - if (not util.is_int(for_reactor)) or (for_reactor < 0) then - local message = util.c("configure> device entry #", i, ": reactor ", for_reactor, " isn't an integer >= 0") - println(message) - log.fatal(message) - return false + local function validate_assign(for_facility) + if for_facility and for_reactor ~= 0 then + local message = util.c("configure> device entry #", i, ": must only be for the facility") + println(message) + log.fatal(message) + 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("configure> device entry #", i, ": unit assignment ", for_reactor, " isn't vaild") + println(message) + log.fatal(message) + return false + else return true end end local device = ppm.get_periph(name) @@ -330,6 +329,9 @@ local function main() 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 @@ -342,6 +344,9 @@ local function main() 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 @@ -354,6 +359,14 @@ local function main() 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 @@ -366,6 +379,8 @@ local function main() end elseif type == "inductionPort" then -- induction matrix multiblock + 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 @@ -378,6 +393,8 @@ local function main() 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 @@ -390,10 +407,14 @@ local function main() 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" then -- advanced peripherals environment detector + 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 @@ -423,7 +444,7 @@ local function main() uid = 0, ---@type integer name = name, ---@type string type = rtu_type, ---@type RTU_UNIT_TYPE - index = index, ---@type integer + index = index or false, ---@type integer|false reactor = for_reactor, ---@type integer device = device, ---@type table is_multiblock = is_multiblock, ---@type boolean @@ -465,7 +486,6 @@ local function main() databus.tx_unit_hw_status(rtu_unit.uid, rtu_unit.hw_state) end - -- we made it through all that trusting-user-to-write-a-config-file chaos return true end @@ -475,9 +495,9 @@ local function main() local rtu_state = __shared_memory.rtu_state - log.debug("boot> running configure()") + log.debug("boot> running sys_config()") - if configure() then + if sys_config() then -- start UI local message rtu_state.fp_ok, message = renderer.try_start_ui(units) @@ -502,12 +522,11 @@ local function main() databus.tx_hw_spkr_count(#__shared_memory.rtu_dev.sounders) -- start connection watchdog - smem_sys.conn_watchdog = util.new_watchdog(config.COMMS_TIMEOUT) + smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout) log.debug("startup> conn watchdog started") -- setup comms - smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_sys.nic, config.RTU_CHANNEL, config.SVR_CHANNEL, - config.TRUSTED_RANGE, smem_sys.conn_watchdog) + smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_sys.nic, smem_sys.conn_watchdog) log.debug("startup> comms init") -- init threads diff --git a/rtu/threads.lua b/rtu/threads.lua index d76d062..02d809a 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -28,6 +28,174 @@ local UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks) local COMMS_SLEEP = 100 -- (100ms, 2 ticks) +---@param smem rtu_shared_memory +---@param println_ts function +---@param iface string +---@param type string +---@param device table +---@param unit rtu_unit_registry_entry +local function handle_unit_mount(smem, println_ts, iface, type, device, unit) + local sys = smem.rtu_sys + + -- find disconnected device to reconnect + -- note: cannot check isFormed as that would yield this coroutine and consume events + if unit.name == iface then + local resend_advert = false + local faulted = false + local unknown = false + local invalid = false + + -- found, re-link + unit.device = device + + if unit.type == RTU_UNIT_TYPE.VIRTUAL then + resend_advert = true + if type == "boilerValve" then + -- boiler multiblock + if unit.reactor < 1 or unit.reactor > 4 then + invalid = true + log.error(util.c("boiler '", unit.name, "' cannot init, not assigned to a valid unit in config")) + end + + if (unit.index == false) or unit.index < 1 or unit.index > 2 then + invalid = true + log.error(util.c("boiler '", unit.name, "' cannot init, invalid index provided in config")) + end + + unit.type = RTU_UNIT_TYPE.BOILER_VALVE + elseif type == "turbineValve" then + -- turbine multiblock + if unit.reactor < 1 or unit.reactor > 4 then + invalid = true + log.error(util.c("turbine '", unit.name, "' cannot init, not assigned to a valid unit in config")) + end + + if (unit.index == false) or unit.index < 1 or unit.index > 3 then + invalid = true + log.error(util.c("turbine '", unit.name, "' cannot init, invalid index provided in config")) + end + + unit.type = RTU_UNIT_TYPE.TURBINE_VALVE + elseif type == "dynamicValve" then + -- dynamic tank multiblock + if unit.reactor < 0 or unit.reactor > 4 then + invalid = true + log.error(util.c("dynamic tank '", unit.name, "' cannot init, no valid assignment provided in config")) + end + + if (unit.reactor == 0 and ((unit.index == false) or unit.index < 1 or unit.index > 4)) or + (unit.reactor > 0 and unit.index ~= 1) then + invalid = true + log.error(util.c("dynamic tank '", unit.name, "' cannot init, invalid index provided in config")) + end + + unit.type = RTU_UNIT_TYPE.DYNAMIC_VALVE + elseif type == "inductionPort" then + -- induction matrix multiblock + if unit.reactor ~= 0 then + invalid = true + log.error(util.c("induction matrix '", unit.name, "' cannot init, not assigned to facility in config")) + end + + unit.type = RTU_UNIT_TYPE.IMATRIX + elseif type == "spsPort" then + -- SPS multiblock + if unit.reactor ~= 0 then + invalid = true + log.error(util.c("SPS '", unit.name, "' cannot init, not assigned to facility in config")) + end + + unit.type = RTU_UNIT_TYPE.SPS + elseif type == "solarNeutronActivator" then + -- SNA + if unit.reactor < 1 or unit.reactor > 4 then + invalid = true + log.error(util.c("SNA '", unit.name, "' cannot init, not assigned to a valid unit in config")) + end + + unit.type = RTU_UNIT_TYPE.SNA + elseif type == "environmentDetector" then + -- advanced peripherals environment detector + if unit.reactor < 0 or unit.reactor > 4 then + invalid = true + log.error(util.c("environment detector '", unit.name, "' cannot init, no valid assignment provided in config")) + end + + unit.type = RTU_UNIT_TYPE.ENV_DETECTOR + else + resend_advert = false + log.error(util.c("virtual device '", unit.name, "' cannot init to an unknown type (", type, ")")) + end + + databus.tx_unit_hw_type(unit.uid, unit.type) + end + + -- if disconnected on startup, config wouldn't have been validated + -- checking now that it has connected; the config isn't valid, so don't connect it + if invalid then + unit.hw_state = UNIT_HW_STATE.OFFLINE + databus.tx_unit_hw_status(unit.uid, unit.hw_state) + return + end + + -- note for multiblock structures: if not formed, indexing the multiblock functions results in a PPM fault + + if unit.type == RTU_UNIT_TYPE.BOILER_VALVE then + unit.rtu, faulted = boilerv_rtu.new(device) + unit.formed = util.trinary(faulted, false, nil) + elseif unit.type == RTU_UNIT_TYPE.TURBINE_VALVE then + unit.rtu, faulted = turbinev_rtu.new(device) + unit.formed = util.trinary(faulted, false, nil) + elseif unit.type == RTU_UNIT_TYPE.DYNAMIC_VALVE then + unit.rtu, faulted = dynamicv_rtu.new(device) + unit.formed = util.trinary(faulted, false, nil) + elseif unit.type == RTU_UNIT_TYPE.IMATRIX then + unit.rtu, faulted = imatrix_rtu.new(device) + unit.formed = util.trinary(faulted, false, nil) + elseif unit.type == RTU_UNIT_TYPE.SPS then + unit.rtu, faulted = sps_rtu.new(device) + unit.formed = util.trinary(faulted, false, nil) + elseif unit.type == RTU_UNIT_TYPE.SNA then + unit.rtu, faulted = sna_rtu.new(device) + elseif unit.type == RTU_UNIT_TYPE.ENV_DETECTOR then + unit.rtu, faulted = envd_rtu.new(device) + else + unknown = true + log.error(util.c("failed to identify reconnected RTU unit type (", unit.name, ")"), true) + end + + if unit.is_multiblock then + unit.hw_state = UNIT_HW_STATE.UNFORMED + if unit.formed == false then + log.info(util.c("assuming ", unit.name, " is not formed due to PPM faults while initializing")) + end + elseif faulted then + unit.hw_state = UNIT_HW_STATE.FAULTED + elseif not unknown then + unit.hw_state = UNIT_HW_STATE.OK + else + unit.hw_state = UNIT_HW_STATE.OFFLINE + end + + databus.tx_unit_hw_status(unit.uid, unit.hw_state) + + if not unknown then + unit.modbus_io = modbus.new(unit.rtu, true) + + local type_name = types.rtu_type_to_string(unit.type) + local message = util.c("reconnected the ", type_name, " on interface ", unit.name) + println_ts(message) + log.info(message) + + if resend_advert then + sys.rtu_comms.send_advertisement(sys.units) + else + sys.rtu_comms.send_remounted(unit.uid) + end + end + end +end + -- main thread ---@nodiscard ---@param smem rtu_shared_memory @@ -180,102 +348,7 @@ function threads.thread__main(smem) else -- relink lost peripheral to correct unit entry for i = 1, #units do - local unit = units[i] ---@type rtu_unit_registry_entry - - -- find disconnected device to reconnect - -- note: cannot check isFormed as that would yield this coroutine and consume events - if unit.name == param1 then - local resend_advert = false - local faulted = false - local unknown = false - - -- found, re-link - unit.device = device - - if unit.type == RTU_UNIT_TYPE.VIRTUAL then - resend_advert = true - if type == "boilerValve" then - -- boiler multiblock - unit.type = RTU_UNIT_TYPE.BOILER_VALVE - elseif type == "turbineValve" then - -- turbine multiblock - unit.type = RTU_UNIT_TYPE.TURBINE_VALVE - elseif type == "inductionPort" then - -- induction matrix multiblock - unit.type = RTU_UNIT_TYPE.IMATRIX - elseif type == "spsPort" then - -- SPS multiblock - unit.type = RTU_UNIT_TYPE.SPS - elseif type == "solarNeutronActivator" then - -- SNA - unit.type = RTU_UNIT_TYPE.SNA - elseif type == "environmentDetector" then - -- advanced peripherals environment detector - unit.type = RTU_UNIT_TYPE.ENV_DETECTOR - else - resend_advert = false - log.error(util.c("virtual device '", unit.name, "' cannot init to an unknown type (", type, ")")) - end - - databus.tx_unit_hw_type(unit.uid, unit.type) - end - - -- note for multiblock structures: if not formed, indexing the multiblock functions results in a PPM fault - - if unit.type == RTU_UNIT_TYPE.BOILER_VALVE then - unit.rtu, faulted = boilerv_rtu.new(device) - unit.formed = util.trinary(faulted, false, nil) - elseif unit.type == RTU_UNIT_TYPE.TURBINE_VALVE then - unit.rtu, faulted = turbinev_rtu.new(device) - unit.formed = util.trinary(faulted, false, nil) - elseif unit.type == RTU_UNIT_TYPE.DYNAMIC_VALVE then - unit.rtu, faulted = dynamicv_rtu.new(device) - unit.formed = util.trinary(faulted, false, nil) - elseif unit.type == RTU_UNIT_TYPE.IMATRIX then - unit.rtu, faulted = imatrix_rtu.new(device) - unit.formed = util.trinary(faulted, false, nil) - elseif unit.type == RTU_UNIT_TYPE.SPS then - unit.rtu, faulted = sps_rtu.new(device) - unit.formed = util.trinary(faulted, false, nil) - elseif unit.type == RTU_UNIT_TYPE.SNA then - unit.rtu, faulted = sna_rtu.new(device) - elseif unit.type == RTU_UNIT_TYPE.ENV_DETECTOR then - unit.rtu, faulted = envd_rtu.new(device) - else - unknown = true - log.error(util.c("failed to identify reconnected RTU unit type (", unit.name, ")"), true) - end - - if unit.is_multiblock then - unit.hw_state = UNIT_HW_STATE.UNFORMED - if unit.formed == false then - log.info(util.c("assuming ", unit.name, " is not formed due to PPM faults while initializing")) - end - elseif faulted then - unit.hw_state = UNIT_HW_STATE.FAULTED - elseif not unknown then - unit.hw_state = UNIT_HW_STATE.OK - else - unit.hw_state = UNIT_HW_STATE.OFFLINE - end - - databus.tx_unit_hw_status(unit.uid, unit.hw_state) - - if not unknown then - unit.modbus_io = modbus.new(unit.rtu, true) - - local type_name = types.rtu_type_to_string(unit.type) - local message = util.c("reconnected the ", type_name, " on interface ", unit.name) - println_ts(message) - log.info(message) - - if resend_advert then - rtu_comms.send_advertisement(units) - else - rtu_comms.send_remounted(unit.uid) - end - end - end + handle_unit_mount(smem, println_ts, param1, type, device, units[i]) end end end diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index c3294cb..4585bfd 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -285,6 +285,18 @@ function rsio.is_color(color) return util.is_int(color) and (color > 0) and (_B_AND(color, (color - 1)) == 0) end +-- color to string +---@nodiscard +---@param color color +---@return string +function rsio.color_name(color) + local color_name_map = { [colors.red] = "red", [colors.orange] = "orange", [colors.yellow] = "yellow", [colors.lime] = "lime", [colors.green] = "green", [colors.cyan] = "cyan", [colors.lightBlue] = "lightBlue", [colors.blue] = "blue", [colors.purple] = "purple", [colors.magenta] = "magenta", [colors.pink] = "pink", [colors.white] = "white", [colors.lightGray] = "lightGray", [colors.gray] = "gray", [colors.black] = "black", [colors.brown] = "brown" } + + if rsio.is_color(color) then + return color_name_map[color] + else return "unknown" end +end + --#endregion --#region Digital I/O From dc0408881e624f74477415ae65267b939c7e85d9 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 6 Nov 2023 10:11:57 -0500 Subject: [PATCH 20/36] #145 use rsio.color_name on PLC configurator --- reactor-plc/configure.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index f8e403f..6547d53 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -5,6 +5,7 @@ local log = require("scada-common.log") local tcd = require("scada-common.tcd") local util = require("scada-common.util") +local rsio = require("scada-common.rsio") local core = require("graphics.core") @@ -129,7 +130,6 @@ local side_options = { "Top", "Bottom", "Left", "Right", "Front", "Back" } local side_options_map = { "top", "bottom", "left", "right", "front", "back" } local color_options = { "Red", "Orange", "Yellow", "Lime", "Green", "Cyan", "Light Blue", "Blue", "Purple", "Magenta", "Pink", "White", "Light Gray", "Gray", "Black", "Brown" } local color_options_map = { colors.red, colors.orange, colors.yellow, colors.lime, colors.green, colors.cyan, colors.lightBlue, colors.blue, colors.purple, colors.magenta, colors.pink, colors.white, colors.lightGray, colors.gray, colors.black, colors.brown } -local color_name_map = { [colors.red] = "red", [colors.orange] = "orange", [colors.yellow] = "yellow", [colors.lime] = "lime", [colors.green] = "green", [colors.cyan] = "cyan", [colors.lightBlue] = "lightBlue", [colors.blue] = "blue", [colors.purple] = "purple", [colors.magenta] = "magenta", [colors.pink] = "pink", [colors.white] = "white", [colors.lightGray] = "lightGray", [colors.gray] = "gray", [colors.black] = "black", [colors.brown] = "brown" } -- convert text representation to index ---@param side string @@ -610,7 +610,7 @@ local function config_view(display) if f[1] == "AuthKey" then val = string.rep("*", string.len(val)) end if f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") end - if f[1] == "EmerCoolColor" and raw ~= nil then val = color_name_map[raw] end + if f[1] == "EmerCoolColor" and raw ~= nil then val = rsio.color_name(raw) end if val == "nil" then val = "" end local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white)) From 838f80c30cf3bf4a523a4bac3dbdba16655edf29 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 6 Nov 2023 10:21:42 -0500 Subject: [PATCH 21/36] #306 #362 supervisor updates for RTU config changes --- scada-common/comms.lua | 2 +- scada-common/types.lua | 2 +- supervisor/facility.lua | 14 +-- supervisor/session/rtu.lua | 4 +- supervisor/session/rtu/boilerv.lua | 2 +- supervisor/session/rtu/dynamicv.lua | 2 +- supervisor/session/rtu/envd.lua | 2 +- supervisor/session/rtu/imatrix.lua | 2 +- supervisor/session/rtu/redstone.lua | 3 +- supervisor/session/rtu/sna.lua | 2 +- supervisor/session/rtu/sps.lua | 2 +- supervisor/session/rtu/turbinev.lua | 2 +- supervisor/session/rtu/unit_session.lua | 2 +- supervisor/startup.lua | 2 +- supervisor/unit.lua | 2 +- supervisor/unitlogic.lua | 152 +++++++++++------------- 16 files changed, 94 insertions(+), 103 deletions(-) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 07abdb8..25fcea1 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -17,7 +17,7 @@ local max_distance = nil local comms = {} -- protocol/data version (protocol/data independent changes tracked by util.lua version) -comms.version = "2.4.1" +comms.version = "2.4.2" ---@enum PROTOCOL local PROTOCOL = { diff --git a/scada-common/types.lua b/scada-common/types.lua index 2a1c17c..d6fc7af 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -63,7 +63,7 @@ function types.new_zero_coordinate() return { x = 0, y = 0, z = 0 } end ---@class rtu_advertisement ---@field type RTU_UNIT_TYPE ----@field index integer +---@field index integer|false ---@field reactor integer ---@field rsio table|nil diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 708522f..28bd6e2 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -1093,22 +1093,22 @@ function facility.new(num_reactors, cooling_conf) build.induction = {} for i = 1, #self.induction do local matrix = self.induction[i] ---@type unit_session - build.induction[matrix.get_device_idx()] = { matrix.get_db().formed, matrix.get_db().build } + build.induction[i] = { matrix.get_db().formed, matrix.get_db().build } end end if all or type == RTU_UNIT_TYPE.SPS then build.sps = {} for i = 1, #self.sps do - local sps = self.sps[i] ---@type unit_session - build.sps[sps.get_device_idx()] = { sps.get_db().formed, sps.get_db().build } + local sps = self.sps[i] ---@type unit_session + build.sps[i] = { sps.get_db().formed, sps.get_db().build } end end if all or type == RTU_UNIT_TYPE.DYNAMIC_VALVE then build.tanks = {} for i = 1, #self.tanks do - local tank = self.tanks[i] ---@type unit_session + local tank = self.tanks[i] ---@type unit_session build.tanks[tank.get_device_idx()] = { tank.get_db().formed, tank.get_db().build } end end @@ -1160,7 +1160,7 @@ function facility.new(num_reactors, cooling_conf) for i = 1, #self.induction do local matrix = self.induction[i] ---@type unit_session local db = matrix.get_db() ---@type imatrix_session_db - status.induction[matrix.get_device_idx()] = { matrix.is_faulted(), db.formed, db.state, db.tanks } + status.induction[i] = { matrix.is_faulted(), db.formed, db.state, db.tanks } end -- status of sps @@ -1168,7 +1168,7 @@ function facility.new(num_reactors, cooling_conf) for i = 1, #self.sps do local sps = self.sps[i] ---@type unit_session local db = sps.get_db() ---@type sps_session_db - status.sps[sps.get_device_idx()] = { sps.is_faulted(), db.formed, db.state, db.tanks } + status.sps[i] = { sps.is_faulted(), db.formed, db.state, db.tanks } end -- status of dynamic tanks @@ -1183,7 +1183,7 @@ function facility.new(num_reactors, cooling_conf) status.rad_mon = {} for i = 1, #self.envd do local envd = self.envd[i] ---@type unit_session - status.rad_mon[envd.get_device_idx()] = { envd.is_faulted(), envd.get_db().radiation } + status.rad_mon[i] = { envd.is_faulted(), envd.get_db().radiation } end return status diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 41c3373..6bebb87 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -100,7 +100,7 @@ function rtu.new_session(id, s_addr, in_queue, out_queue, timeout, advertisement -- validate unit advertisement local advert_validator = util.new_validator() - advert_validator.assert_type_int(unit_advert.index) + advert_validator.assert(util.is_int(unit_advert.index) or (unit_advert.index == false)) advert_validator.assert_type_int(unit_advert.reactor) if u_type == RTU_UNIT_TYPE.REDSTONE then @@ -108,7 +108,7 @@ function rtu.new_session(id, s_addr, in_queue, out_queue, timeout, advertisement end if advert_validator.valid() then - advert_validator.assert_min(unit_advert.index, 1) + if util.is_int(unit_advert.index) then advert_validator.assert_min(unit_advert.index, 1) end advert_validator.assert_min(unit_advert.reactor, 0) advert_validator.assert_max(unit_advert.reactor, #self.fac_units) if not advert_validator.valid() then u_type = false end diff --git a/supervisor/session/rtu/boilerv.lua b/supervisor/session/rtu/boilerv.lua index 2f3c231..5af6f83 100644 --- a/supervisor/session/rtu/boilerv.lua +++ b/supervisor/session/rtu/boilerv.lua @@ -43,7 +43,7 @@ function boilerv.new(session_id, unit_id, advert, out_queue) return nil end - local log_tag = "session.rtu(" .. session_id .. ").boilerv(" .. advert.index .. "): " + local log_tag = util.c("session.rtu(", session_id, ").boilerv(", advert.index, ")[@", unit_id, "]: ") local self = { session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), diff --git a/supervisor/session/rtu/dynamicv.lua b/supervisor/session/rtu/dynamicv.lua index d019da4..68cd6e9 100644 --- a/supervisor/session/rtu/dynamicv.lua +++ b/supervisor/session/rtu/dynamicv.lua @@ -55,7 +55,7 @@ function dynamicv.new(session_id, unit_id, advert, out_queue) return nil end - local log_tag = "session.rtu(" .. session_id .. ").dynamicv(" .. advert.index .. "): " + local log_tag = util.c("session.rtu(", session_id, ").dynamicv(", advert.index, ")[@", unit_id, "]: ") local self = { session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), diff --git a/supervisor/session/rtu/envd.lua b/supervisor/session/rtu/envd.lua index 3b4b666..cf342b7 100644 --- a/supervisor/session/rtu/envd.lua +++ b/supervisor/session/rtu/envd.lua @@ -34,7 +34,7 @@ function envd.new(session_id, unit_id, advert, out_queue) return nil end - local log_tag = "session.rtu(" .. session_id .. ").envd(" .. advert.index .. "): " + local log_tag = util.c("session.rtu(", session_id, ").envd[@", unit_id, "]: ") local self = { session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), diff --git a/supervisor/session/rtu/imatrix.lua b/supervisor/session/rtu/imatrix.lua index 0b120b4..b3c23d8 100644 --- a/supervisor/session/rtu/imatrix.lua +++ b/supervisor/session/rtu/imatrix.lua @@ -43,7 +43,7 @@ function imatrix.new(session_id, unit_id, advert, out_queue) return nil end - local log_tag = "session.rtu(" .. session_id .. ").imatrix(" .. advert.index .. "): " + local log_tag = util.c("session.rtu(", session_id, ").imatrix[@", unit_id, "]: ") local self = { session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index 25b7284..f142023 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -56,8 +56,7 @@ function redstone.new(session_id, unit_id, advert, out_queue) return nil end - -- for redstone, use unit ID not device index - local log_tag = "session.rtu(" .. session_id .. ").redstone(" .. unit_id .. "): " + local log_tag = util.c("session.rtu(", session_id, ").redstone[@", unit_id, "]: ") local self = { session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), diff --git a/supervisor/session/rtu/sna.lua b/supervisor/session/rtu/sna.lua index 006222b..cc5a6fc 100644 --- a/supervisor/session/rtu/sna.lua +++ b/supervisor/session/rtu/sna.lua @@ -40,7 +40,7 @@ function sna.new(session_id, unit_id, advert, out_queue) return nil end - local log_tag = "session.rtu(" .. session_id .. ").sna(" .. advert.index .. "): " + local log_tag = util.c("session.rtu(", session_id, ").sna[@", unit_id, "]: ") local self = { session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), diff --git a/supervisor/session/rtu/sps.lua b/supervisor/session/rtu/sps.lua index da036cd..e53a540 100644 --- a/supervisor/session/rtu/sps.lua +++ b/supervisor/session/rtu/sps.lua @@ -43,7 +43,7 @@ function sps.new(session_id, unit_id, advert, out_queue) return nil end - local log_tag = "session.rtu(" .. session_id .. ").sps(" .. advert.index .. "): " + local log_tag = util.c("session.rtu(", session_id, ").sps[@", unit_id, "]: ") local self = { session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), diff --git a/supervisor/session/rtu/turbinev.lua b/supervisor/session/rtu/turbinev.lua index 4cf32c4..1112106 100644 --- a/supervisor/session/rtu/turbinev.lua +++ b/supervisor/session/rtu/turbinev.lua @@ -55,7 +55,7 @@ function turbinev.new(session_id, unit_id, advert, out_queue) return nil end - local log_tag = "session.rtu(" .. session_id .. ").turbinev(" .. advert.index .. "): " + local log_tag = util.c("session.rtu(", session_id, ").turbinev(", advert.index, ")[@", unit_id, "]: ") local self = { session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), diff --git a/supervisor/session/rtu/unit_session.lua b/supervisor/session/rtu/unit_session.lua index 3d27fa4..0a2964a 100644 --- a/supervisor/session/rtu/unit_session.lua +++ b/supervisor/session/rtu/unit_session.lua @@ -152,7 +152,7 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t function public.get_unit_id() return unit_id end -- get the device index ---@nodiscard - function public.get_device_idx() return self.device_index end + function public.get_device_idx() return self.device_index or 0 end -- get the reactor ID ---@nodiscard function public.get_reactor() return self.reactor end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index ec27930..2ff69ad 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.0.9" +local SUPERVISOR_VERSION = "v1.1.0" local println = util.println local println_ts = util.println_ts diff --git a/supervisor/unit.lua b/supervisor/unit.lua index ad6c3b0..eff23de 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -867,7 +867,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) status.rad_mon = {} for i = 1, #self.envd do local envd = self.envd[i] ---@type unit_session - status.rad_mon[envd.get_device_idx()] = { envd.is_faulted(), envd.get_db().radiation } + status.rad_mon[i] = { envd.is_faulted(), envd.get_db().radiation } end return status diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index f16f197..e207c44 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -47,8 +47,9 @@ function logic.update_annunciator(self) local num_boilers = self.num_boilers local num_turbines = self.num_turbines + local annunc = self.db.annunciator - self.db.annunciator.RCSFault = false + annunc.RCSFault = false -- variables for boiler, or reactor if no boilers used local total_boil_rate = 0.0 @@ -57,14 +58,14 @@ function logic.update_annunciator(self) -- REACTOR -- ------------- - self.db.annunciator.AutoControl = self.auto_engaged + annunc.AutoControl = self.auto_engaged -- check PLC status - self.db.annunciator.PLCOnline = self.plc_i ~= nil + annunc.PLCOnline = self.plc_i ~= nil - local plc_ready = self.db.annunciator.PLCOnline + local plc_ready = annunc.PLCOnline - if self.db.annunciator.PLCOnline then + if plc_ready then local plc_db = self.plc_i.get_db() -- update ready state @@ -110,29 +111,29 @@ function logic.update_annunciator(self) -- heartbeat blink about every second if self.last_heartbeat + 1000 < plc_db.last_status_update then - self.db.annunciator.PLCHeartbeat = not self.db.annunciator.PLCHeartbeat + annunc.PLCHeartbeat = not annunc.PLCHeartbeat self.last_heartbeat = plc_db.last_status_update end local flow_low = util.trinary(plc_db.mek_status.ccool_type == types.FLUID.SODIUM, ANNUNC_LIMS.RCSFlowLow_NA, ANNUNC_LIMS.RCSFlowLow_H2O) -- update other annunciator fields - self.db.annunciator.ReactorSCRAM = plc_db.rps_tripped - self.db.annunciator.ManualReactorSCRAM = plc_db.rps_trip_cause == types.RPS_TRIP_CAUSE.MANUAL - self.db.annunciator.AutoReactorSCRAM = plc_db.rps_trip_cause == types.RPS_TRIP_CAUSE.AUTOMATIC - self.db.annunciator.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.low_cool) - self.db.annunciator.RCSFlowLow = _get_dt(DT_KEYS.ReactorCCool) < flow_low - self.db.annunciator.CoolantLevelLow = plc_db.mek_status.ccool_fill < ANNUNC_LIMS.CoolantLevelLow - self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > ANNUNC_LIMS.ReactorTempHigh - self.db.annunciator.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > ANNUNC_LIMS.ReactorHighDeltaT - self.db.annunciator.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < -1.0 or plc_db.mek_status.fuel_fill <= ANNUNC_LIMS.FuelLevelLow - self.db.annunciator.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 1.0 or plc_db.mek_status.waste_fill >= ANNUNC_LIMS.WasteLevelHigh + annunc.ReactorSCRAM = plc_db.rps_tripped + annunc.ManualReactorSCRAM = plc_db.rps_trip_cause == types.RPS_TRIP_CAUSE.MANUAL + annunc.AutoReactorSCRAM = plc_db.rps_trip_cause == types.RPS_TRIP_CAUSE.AUTOMATIC + annunc.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.low_cool) + annunc.RCSFlowLow = _get_dt(DT_KEYS.ReactorCCool) < flow_low + annunc.CoolantLevelLow = plc_db.mek_status.ccool_fill < ANNUNC_LIMS.CoolantLevelLow + annunc.ReactorTempHigh = plc_db.mek_status.temp > ANNUNC_LIMS.ReactorTempHigh + annunc.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > ANNUNC_LIMS.ReactorHighDeltaT + annunc.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < -1.0 or plc_db.mek_status.fuel_fill <= ANNUNC_LIMS.FuelLevelLow + annunc.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 1.0 or plc_db.mek_status.waste_fill >= ANNUNC_LIMS.WasteLevelHigh local heating_rate_conv = util.trinary(plc_db.mek_status.ccool_type == types.FLUID.SODIUM, 200000, 20000) local high_rate = plc_db.mek_status.burn_rate >= (plc_db.mek_status.ccool_amnt * 0.27 / heating_rate_conv) -- this advisory applies when no coolant is buffered (which we can't easily determine)
-- it's a rough estimation, see GitHub cc-mek-scada/wiki/High-Rate-Calculation - self.db.annunciator.HighStartupRate = not plc_db.mek_status.status and high_rate + annunc.HighStartupRate = not plc_db.mek_status.status and high_rate -- if no boilers, use reactor heating rate to check for boil rate mismatch if num_boilers == 0 then @@ -146,21 +147,25 @@ function logic.update_annunciator(self) -- MISC RTUs -- --------------- - self.db.annunciator.RadiationMonitor = 1 - self.db.annunciator.RadiationWarning = false + local max_rad, any_faulted = 0, false + for i = 1, #self.envd do - local envd = self.envd[i] ---@type unit_session - self.db.annunciator.RadiationMonitor = util.trinary(envd.is_faulted(), 2, 3) - self.db.annunciator.RadiationWarning = envd.get_db().radiation_raw >= ANNUNC_LIMS.RadiationWarning - break + local envd = self.envd[i] ---@type unit_session + local db = envd.get_db() ---@type envd_session_db + any_faulted = any_faulted or envd.is_faulted() + if db.radiation_raw > max_rad then max_rad = db.radiation_raw end end - self.db.annunciator.EmergencyCoolant = 1 + annunc.RadiationMonitor = util.trinary(#self.envd == 0, 1, util.trinary(any_faulted, 2, 3)) + annunc.RadiationWarning = max_rad >= ANNUNC_LIMS.RadiationWarning + + annunc.EmergencyCoolant = 1 + for i = 1, #self.redstone do - local db = self.redstone[i].get_db() ---@type redstone_session_db - local io = db.io[IO.U_EMER_COOL] ---@type rs_db_dig_io|nil + local db = self.redstone[i].get_db() ---@type redstone_session_db + local io = db.io[IO.U_EMER_COOL] ---@type rs_db_dig_io|nil if io ~= nil then - self.db.annunciator.EmergencyCoolant = util.trinary(io.read(), 3, 2) + annunc.EmergencyCoolant = util.trinary(io.read(), 3, 2) break end end @@ -172,7 +177,7 @@ function logic.update_annunciator(self) local boilers_ready = num_boilers == #self.boilers -- clear boiler online flags - for i = 1, num_boilers do self.db.annunciator.BoilerOnline[i] = false end + for i = 1, num_boilers do annunc.BoilerOnline[i] = false end -- aggregated statistics local boiler_steam_dt_sum = 0.0 @@ -185,7 +190,7 @@ function logic.update_annunciator(self) local boiler = session.get_db() ---@type boilerv_session_db local idx = session.get_device_idx() - self.db.annunciator.RCSFault = self.db.annunciator.RCSFault or (not boiler.formed) or session.is_faulted() + annunc.RCSFault = annunc.RCSFault or (not boiler.formed) or session.is_faulted() -- update ready state -- - must be formed @@ -199,8 +204,8 @@ function logic.update_annunciator(self) boiler_steam_dt_sum = _get_dt(DT_KEYS.BoilerSteam .. idx) boiler_water_dt_sum = _get_dt(DT_KEYS.BoilerWater .. idx) - self.db.annunciator.BoilerOnline[idx] = true - self.db.annunciator.WaterLevelLow[idx] = boiler.tanks.water_fill < ANNUNC_LIMS.WaterLevelLow + annunc.BoilerOnline[idx] = true + annunc.WaterLevelLow[idx] = boiler.tanks.water_fill < ANNUNC_LIMS.WaterLevelLow end -- check heating rate low @@ -209,14 +214,14 @@ function logic.update_annunciator(self) -- check for inactive boilers while reactor is active for i = 1, #self.boilers do - local boiler = self.boilers[i] ---@type unit_session + local boiler = self.boilers[i] ---@type unit_session local idx = boiler.get_device_idx() - local db = boiler.get_db() ---@type boilerv_session_db + local db = boiler.get_db() ---@type boilerv_session_db if r_db.mek_status.status then - self.db.annunciator.HeatingRateLow[idx] = db.state.boil_rate == 0 + annunc.HeatingRateLow[idx] = db.state.boil_rate == 0 else - self.db.annunciator.HeatingRateLow[idx] = false + annunc.HeatingRateLow[idx] = false end end end @@ -234,9 +239,9 @@ function logic.update_annunciator(self) if num_boilers > 0 then for i = 1, #self.boilers do - local boiler = self.boilers[i] ---@type unit_session + local boiler = self.boilers[i] ---@type unit_session local idx = boiler.get_device_idx() - local db = boiler.get_db() ---@type boilerv_session_db + local db = boiler.get_db() ---@type boilerv_session_db local gaining_hc = _get_dt(DT_KEYS.BoilerHCool .. idx) > 10.0 or db.tanks.hcool_fill == 1 @@ -256,7 +261,7 @@ function logic.update_annunciator(self) cfmismatch = cfmismatch or _get_dt(DT_KEYS.ReactorCCool) < -10.0 or (gaining_hc and r_db.mek_status.ccool_fill == 0) end - self.db.annunciator.CoolantFeedMismatch = cfmismatch + annunc.CoolantFeedMismatch = cfmismatch -------------- -- TURBINES -- @@ -265,7 +270,7 @@ function logic.update_annunciator(self) local turbines_ready = num_turbines == #self.turbines -- clear turbine online flags - for i = 1, num_turbines do self.db.annunciator.TurbineOnline[i] = false end + for i = 1, num_turbines do annunc.TurbineOnline[i] = false end -- aggregated statistics local total_flow_rate = 0 @@ -277,10 +282,10 @@ function logic.update_annunciator(self) -- go through turbines for stats and online for i = 1, #self.turbines do - local session = self.turbines[i] ---@type unit_session - local turbine = session.get_db() ---@type turbinev_session_db + local session = self.turbines[i] ---@type unit_session + local turbine = session.get_db() ---@type turbinev_session_db - self.db.annunciator.RCSFault = self.db.annunciator.RCSFault or (not turbine.formed) or session.is_faulted() + annunc.RCSFault = annunc.RCSFault or (not turbine.formed) or session.is_faulted() -- update ready state -- - must be formed @@ -295,59 +300,44 @@ function logic.update_annunciator(self) max_water_return_rate = max_water_return_rate + turbine.build.max_water_output self.db.control.blade_count = self.db.control.blade_count + turbine.build.blades - self.db.annunciator.TurbineOnline[session.get_device_idx()] = true + annunc.TurbineOnline[session.get_device_idx()] = true end -- check for boil rate mismatch (> 4% error) either between reactor and turbine or boiler and turbine - self.db.annunciator.BoilRateMismatch = math.abs(total_boil_rate - total_input_rate) > (0.04 * total_boil_rate) + annunc.BoilRateMismatch = math.abs(total_boil_rate - total_input_rate) > (0.04 * total_boil_rate) -- check for steam feed mismatch and max return rate local steam_dt_max = util.trinary(num_boilers == 0, ANNUNC_LIMS.SFM_MaxSteamDT_H20, ANNUNC_LIMS.SFM_MaxSteamDT_NA) local water_dt_min = util.trinary(num_boilers == 0, ANNUNC_LIMS.SFM_MinWaterDT_H20, ANNUNC_LIMS.SFM_MinWaterDT_NA) local sfmismatch = math.abs(total_flow_rate - total_input_rate) > ANNUNC_LIMS.SteamFeedMismatch sfmismatch = sfmismatch or boiler_steam_dt_sum > steam_dt_max or boiler_water_dt_sum < water_dt_min - self.db.annunciator.SteamFeedMismatch = sfmismatch - self.db.annunciator.MaxWaterReturnFeed = max_water_return_rate == total_flow_rate and total_flow_rate ~= 0 + annunc.SteamFeedMismatch = sfmismatch + annunc.MaxWaterReturnFeed = max_water_return_rate == total_flow_rate and total_flow_rate ~= 0 -- turbine safety checks for i = 1, #self.turbines do - local turbine = self.turbines[i] ---@type unit_session - local db = turbine.get_db() ---@type turbinev_session_db + local turbine = self.turbines[i] ---@type unit_session + local db = turbine.get_db() ---@type turbinev_session_db local idx = turbine.get_device_idx() -- check if steam dumps are open if db.state.dumping_mode == DUMPING_MODE.IDLE then - self.db.annunciator.SteamDumpOpen[idx] = TRI_FAIL.OK + annunc.SteamDumpOpen[idx] = TRI_FAIL.OK elseif db.state.dumping_mode == DUMPING_MODE.DUMPING_EXCESS then - self.db.annunciator.SteamDumpOpen[idx] = TRI_FAIL.PARTIAL + annunc.SteamDumpOpen[idx] = TRI_FAIL.PARTIAL else - self.db.annunciator.SteamDumpOpen[idx] = TRI_FAIL.FULL + annunc.SteamDumpOpen[idx] = TRI_FAIL.FULL end -- check if turbines are at max speed but not keeping up - self.db.annunciator.TurbineOverSpeed[idx] = (db.state.flow_rate == db.build.max_flow_rate) and (_get_dt(DT_KEYS.TurbineSteam .. idx) > 0.0) + annunc.TurbineOverSpeed[idx] = (db.state.flow_rate == db.build.max_flow_rate) and (_get_dt(DT_KEYS.TurbineSteam .. idx) > 0.0) - --[[ - Generator Trip - a generator trip is when a generator suddenly and unexpectedly loses it's external load, which occurs when a power plant - is disconnected from the grid. in our case, this is when the turbine is disconnected, or what it's connected to becomes - fully charged. this is identified by detecting if: - - the internal power storage of the turbine is increasing AND - - there is at least 5% energy fill (preventing false trips with periodic power extraction from other mods) - this would then mean there is no external load and there will be a turbine trip soon if this is not resolved - ]]-- - self.db.annunciator.GeneratorTrip[idx] = (_get_dt(DT_KEYS.TurbinePower .. idx) > 0.0) and (db.tanks.energy_fill > 0.05) + -- see notes at cc-mek-scada/wiki/Annunciator-Panels#Generator-Trip + annunc.GeneratorTrip[idx] = (_get_dt(DT_KEYS.TurbinePower .. idx) > 0.0) and (db.tanks.energy_fill > 0.05) - --[[ - Turbine Trip - a turbine trip is when the turbine stops, which means we are no longer receiving water and lose the ability to cool. - this can be identified by these conditions: - - the current flow rate is 0 mB/t and it should not be - - can initially catch this by detecting a 0 flow rate with a non-zero input rate, but eventually the steam will fill up - - can later identified by presence of steam in tank with a 0 flow rate - ]]-- + -- see notes at cc-mek-scada/wiki/Annunciator-Panels#Turbine-Trip local has_steam = db.state.steam_input_rate > 0 or db.tanks.steam_fill > 0.01 - self.db.annunciator.TurbineTrip[idx] = has_steam and db.state.flow_rate == 0 + annunc.TurbineTrip[idx] = has_steam and db.state.flow_rate == 0 end -- update auto control ready state for this unit @@ -577,6 +567,7 @@ end ---@param self _unit_self unit instance function logic.update_status_text(self) local AISTATE = self.types.AISTATE + local annunc = self.db.annunciator -- check if an alarm is active (tripped or ack'd) ---@nodiscard @@ -666,13 +657,13 @@ function logic.update_status_text(self) if plc_db.mek_status.status then self.status_text[1] = "ACTIVE" - if self.db.annunciator.ReactorHighDeltaT then + if annunc.ReactorHighDeltaT then self.status_text[2] = "core temperature rising" - elseif self.db.annunciator.ReactorTempHigh then + elseif annunc.ReactorTempHigh then self.status_text[2] = "core temp high, system nominal" - elseif self.db.annunciator.FuelInputRateLow then + elseif annunc.FuelInputRateLow then self.status_text[2] = "insufficient fuel input rate" - elseif self.db.annunciator.WasteLineOcclusion then + elseif annunc.WasteLineOcclusion then self.status_text[2] = "insufficient waste output rate" elseif (util.time_ms() - self.last_rate_change_ms) <= FLOW_STABILITY_DELAY_MS then self.status_text[2] = "awaiting flow stability" @@ -711,7 +702,7 @@ function logic.update_status_text(self) end self.status_text = { "RPS SCRAM", cause } - elseif self.db.annunciator.RadiationWarning then + elseif annunc.RadiationWarning then -- elevated, non-hazardous level of radiation is low priority, so display it now if everything else was fine self.status_text = { "RADIATION DETECTED", "elevated level of radiation" } else @@ -726,7 +717,7 @@ function logic.update_status_text(self) self.status_text[2] = "core hot" end end - elseif self.db.annunciator.RadiationWarning then + elseif annunc.RadiationWarning then -- in case PLC was disconnected but radiation is present self.status_text = { "RADIATION DETECTED", "elevated level of radiation" } else @@ -738,6 +729,7 @@ end ---@param self _unit_self unit instance function logic.handle_redstone(self) local AISTATE = self.types.AISTATE + local annunc = self.db.annunciator -- check if an alarm is active (tripped or ack'd) ---@nodiscard @@ -806,7 +798,7 @@ function logic.handle_redstone(self) ----------------------- local enable_emer_cool = self.plc_cache.rps_status.low_cool or - (self.auto_engaged and self.db.annunciator.CoolantLevelLow and is_active(self.alarms.ReactorOverTemp)) + (self.auto_engaged and annunc.CoolantLevelLow and is_active(self.alarms.ReactorOverTemp)) -- don't turn off emergency coolant on sufficient coolant level since it might drop again -- turn off once system is OK again @@ -822,7 +814,7 @@ function logic.handle_redstone(self) end end - if self.db.annunciator.EmergencyCoolant > 1 and self.emcool_opened then + if annunc.EmergencyCoolant > 1 and self.emcool_opened then log.info(util.c("UNIT ", self.r_id, " emergency coolant valve closed")) log.info(util.c("UNIT ", self.r_id, " turbines set to not dump steam")) end @@ -849,7 +841,7 @@ function logic.handle_redstone(self) end end - if self.db.annunciator.EmergencyCoolant > 1 and not self.emcool_opened then + if annunc.EmergencyCoolant > 1 and not self.emcool_opened then log.info(util.c("UNIT ", self.r_id, " emergency coolant valve opened")) log.info(util.c("UNIT ", self.r_id, " turbines set to dump excess steam")) end From 1ba178eae852952120840263673ce4a141032451 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 6 Nov 2023 10:22:52 -0500 Subject: [PATCH 22/36] #306 delete legacy RTU config --- rtu/config.lua | 73 -------------------------------------------------- 1 file changed, 73 deletions(-) delete mode 100644 rtu/config.lua diff --git a/rtu/config.lua b/rtu/config.lua deleted file mode 100644 index 98ca5df..0000000 --- a/rtu/config.lua +++ /dev/null @@ -1,73 +0,0 @@ -local rsio = require("scada-common.rsio") - -local config = {} - --- supervisor comms channel -config.SVR_CHANNEL = 16240 --- RTU/MODBUS comms channel -config.RTU_CHANNEL = 16242 --- max trusted modem message distance (0 to disable check) -config.TRUSTED_RANGE = 0 --- time in seconds (>= 2) before assuming a remote device is no longer active -config.COMMS_TIMEOUT = 5 --- facility authentication key (do NOT use one of your passwords) --- this enables verifying that messages are authentic --- all devices on the same network must use the same key --- config.AUTH_KEY = "SCADAfacility123" - --- alarm sounder volume (0.0 to 3.0, 1.0 being standard max volume, this is the option given to to speaker.play()) --- note: alarm sine waves are at half saturation, so that multiple will be required to reach full scale -config.SOUNDER_VOLUME = 1.0 - --- log path -config.LOG_PATH = "/log.txt" --- log mode --- 0 = APPEND (adds to existing file on start) --- 1 = NEW (replaces existing file on start) -config.LOG_MODE = 0 --- true to log verbose debug messages -config.LOG_DEBUG = false - --- RTU peripheral devices (named: side/network device name) -config.RTU_DEVICES = { - { - name = "boilerValve_0", - index = 1, - for_reactor = 1 - }, - { - name = "turbineValve_0", - index = 1, - for_reactor = 1 - } -} --- RTU redstone interface definitions -config.RTU_REDSTONE = { - -- { - -- for_reactor = 1, - -- io = { - -- { - -- port = rsio.IO.WASTE_PO, - -- side = "top", - -- bundled_color = colors.red - -- }, - -- { - -- port = rsio.IO.WASTE_PU, - -- side = "top", - -- bundled_color = colors.orange - -- }, - -- { - -- port = rsio.IO.WASTE_POPL, - -- side = "top", - -- bundled_color = colors.yellow - -- }, - -- { - -- port = rsio.IO.WASTE_AM, - -- side = "top", - -- bundled_color = colors.lime - -- } - -- } - -- } -} - -return config From f2f5c3201f73504baac43283e5cc3742ed26de72 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 Nov 2023 11:54:47 -0500 Subject: [PATCH 23/36] #362 taking max of connected radiation monitors --- coordinator/iocontrol.lua | 69 ++++++++++++++++++++--------- rtu/configure.lua | 25 +++++++---- rtu/startup.lua | 3 +- rtu/threads.lua | 5 +++ supervisor/facility.lua | 36 ++++++--------- supervisor/session/rtu/boilerv.lua | 7 ++- supervisor/session/rtu/dynamicv.lua | 7 ++- supervisor/session/rtu/envd.lua | 9 ++-- supervisor/session/rtu/imatrix.lua | 7 ++- supervisor/session/rtu/redstone.lua | 2 +- supervisor/session/rtu/sna.lua | 2 +- supervisor/session/rtu/sps.lua | 2 +- supervisor/session/rtu/turbinev.lua | 7 ++- supervisor/unit.lua | 3 +- 14 files changed, 116 insertions(+), 68 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 5ccffae..96911e7 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -469,7 +469,7 @@ function iocontrol.record_unit_builds(builds) -- note: if not all units and RTUs are connected, some will be nil for id, build in pairs(builds) do - local unit = io.units[id] ---@type ioctl_unit + local unit = io.units[id] ---@type ioctl_unit local log_header = util.c("iocontrol.record_unit_builds[UNIT ", id, "]: ") @@ -694,8 +694,8 @@ function iocontrol.update_facility_status(status) for id, sps in pairs(rtu_statuses.sps) do if type(fac.sps_data_tbl[id]) == "table" then - local data = fac.sps_data_tbl[id] ---@type sps_session_db - local ps = fac.sps_ps_tbl[id] ---@type psil + local data = fac.sps_data_tbl[id] ---@type sps_session_db + local ps = fac.sps_ps_tbl[id] ---@type psil local rtu_faulted = _record_multiblock_status(sps, data, ps) @@ -732,8 +732,8 @@ function iocontrol.update_facility_status(status) for id, tank in pairs(rtu_statuses.tanks) do if type(fac.tank_data_tbl[id]) == "table" then - local data = fac.tank_data_tbl[id] ---@type dynamicv_session_db - local ps = fac.tank_ps_tbl[id] ---@type psil + local data = fac.tank_data_tbl[id] ---@type dynamicv_session_db + local ps = fac.tank_ps_tbl[id] ---@type psil local rtu_faulted = _record_multiblock_status(tank, data, ps) @@ -762,12 +762,23 @@ function iocontrol.update_facility_status(status) -- environment detector status if type(rtu_statuses.rad_mon) == "table" then if #rtu_statuses.rad_mon > 0 then - local rad_mon = rtu_statuses.rad_mon[1] - local rtu_faulted = rad_mon[1] ---@type boolean - fac.radiation = rad_mon[2] ---@type number + local max_rad, max_reading, any_faulted = 0, types.new_zero_radiation_reading(), false - fac.ps.publish("rad_computed_status", util.trinary(rtu_faulted, 2, 3)) - fac.ps.publish("radiation", fac.radiation) + for i = 1, #rtu_statuses.rad_mon do + local rad_mon = rtu_statuses.rad_mon[i] + local rtu_faulted = rad_mon[1] ---@type boolean + local radiation = rad_mon[2] ---@type radiation_reading + local rad_raw = rad_mon[3] ---@type number + + any_faulted = any_faulted or rtu_faulted + if rad_raw > max_rad then + max_rad = rad_raw + max_reading = radiation + end + end + + fac.radiation = max_reading + fac.ps.publish("rad_computed_status", util.trinary(any_faulted, 2, 3)) else fac.radiation = types.new_zero_radiation_reading() fac.ps.publish("rad_computed_status", 1) @@ -776,6 +787,8 @@ function iocontrol.update_facility_status(status) log.debug(log_header .. "radiation monitor list not a table") valid = false end + + fac.ps.publish("radiation", fac.radiation) else log.debug(log_header .. "rtu statuses not a table") valid = false @@ -917,8 +930,8 @@ function iocontrol.update_unit_statuses(statuses) for id, boiler in pairs(rtu_statuses.boilers) do if type(unit.boiler_data_tbl[id]) == "table" then - local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db - local ps = unit.boiler_ps_tbl[id] ---@type psil + local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db + local ps = unit.boiler_ps_tbl[id] ---@type psil local rtu_faulted = _record_multiblock_status(boiler, data, ps) @@ -960,8 +973,8 @@ function iocontrol.update_unit_statuses(statuses) for id, turbine in pairs(rtu_statuses.turbines) do if type(unit.turbine_data_tbl[id]) == "table" then - local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db - local ps = unit.turbine_ps_tbl[id] ---@type psil + local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db + local ps = unit.turbine_ps_tbl[id] ---@type psil local rtu_faulted = _record_multiblock_status(turbine, data, ps) @@ -1033,9 +1046,9 @@ function iocontrol.update_unit_statuses(statuses) -- solar neutron activator status info if type(rtu_statuses.sna) == "table" then - unit.num_snas = rtu_statuses.sna[1] ---@type integer - unit.sna_prod_rate = rtu_statuses.sna[2] ---@type number - unit.sna_peak_rate = rtu_statuses.sna[3] ---@type number + unit.num_snas = rtu_statuses.sna[1] ---@type integer + unit.sna_prod_rate = rtu_statuses.sna[2] ---@type number + unit.sna_peak_rate = rtu_statuses.sna[3] ---@type number unit.unit_ps.publish("sna_count", unit.num_snas) unit.unit_ps.publish("sna_prod_rate", unit.sna_prod_rate) @@ -1049,12 +1062,22 @@ function iocontrol.update_unit_statuses(statuses) -- environment detector status if type(rtu_statuses.rad_mon) == "table" then - if #rtu_statuses.rad_mon > 0 then - local rad_mon = rtu_statuses.rad_mon[1] - -- local rtu_faulted = rad_mon[1] ---@type boolean - unit.radiation = rad_mon[2] ---@type number + local max_rad, max_reading = 0, types.new_zero_radiation_reading() - unit.unit_ps.publish("radiation", unit.radiation) + if #rtu_statuses.rad_mon > 0 then + + for id = 1, #rtu_statuses.rad_mon do + local rad_mon = rtu_statuses.rad_mon[id] + local radiation = rad_mon[2] ---@type radiation_reading + local rad_raw = rad_mon[3] ---@type number + + if rad_raw > max_rad then + max_rad = rad_raw + max_reading = radiation + end + end + + unit.radiation = max_reading else unit.radiation = types.new_zero_radiation_reading() end @@ -1062,6 +1085,8 @@ function iocontrol.update_unit_statuses(statuses) log.debug(log_header .. "radiation monitor list not a table") valid = false end + + unit.unit_ps.publish("radiation", unit.radiation) else log.debug(log_header .. "rtu list not a table") valid = false diff --git a/rtu/configure.lua b/rtu/configure.lua index 106724e..0c19800 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -87,7 +87,6 @@ local changes = {} local RTU_DEV_TYPES = { "boilerValve", "turbineValve", "dynamicValve", "inductionPort", "spsPort", "solarNeutronActivator", "environmentDetector" } local NEEDS_UNIT = { "boilerValve", "turbineValve", "dynamicValve", "solarNeutronActivator", "environmentDetector" } -local NEEDS_IDX = { "boilerValve", "turbineValve", "dynamicValve" } ---@class rtu_configurator local configurator = {} @@ -732,8 +731,10 @@ local function config_view(display) tool_ctl.p_desc.reposition(1, 8) tool_ctl.p_desc.set_value("Each reactor unit can have at most 1 tank and the facility can have at most 4. Each facility tank must have a unique # 1 through 4, regardless of where it is connected. Only a total of 4 tanks can be displayed on the flow monitor.") elseif type == "environmentDetector" then - tool_ctl.p_idx.hide() - tool_ctl.p_prompt.set_value("This will be an environment detector for...") + tool_ctl.p_prompt.set_value("This is the # environment detector for...") + tool_ctl.p_idx.show() + tool_ctl.p_idx.redraw() + tool_ctl.p_idx.set_max(99) tool_ctl.p_unit.reposition(18, 6) if tool_ctl.p_assign_btn.get_value() == 1 then tool_ctl.p_unit.disable() else tool_ctl.p_unit.enable() end tool_ctl.p_assign_btn.show() @@ -741,12 +742,12 @@ local function config_view(display) tool_ctl.p_assign_end.show() tool_ctl.p_assign_end.redraw() tool_ctl.p_desc.reposition(1, 8) - tool_ctl.p_desc.set_value("You can connect more than one environment detector for a particular unit or the facility, in which case the maximum radiation reading from those assigned to that particular unit or the facility will be used.") + tool_ctl.p_desc.set_value("You can connect more than one environment detector for a particular unit or the facility. In that case, the maximum radiation reading from those assigned to that particular unit or the facility will be used for alarms and display.") elseif type == "inductionPort" or type == "spsPort" then local dev = util.trinary(type == "inductionPort", "induction matrix", "SPS") tool_ctl.p_idx.hide(true) tool_ctl.p_unit.hide(true) - tool_ctl.p_prompt.set_value("This will be the " .. dev .. " for the facility.") + tool_ctl.p_prompt.set_value("This is the " .. dev .. " for the facility.") tool_ctl.p_assign_btn.hide(true) tool_ctl.p_assign_end.hide(true) tool_ctl.p_desc.reposition(1, 7) @@ -815,11 +816,13 @@ local function config_view(display) function tool_ctl.p_assign(opt) if opt == 1 then tool_ctl.p_unit.disable() - tool_ctl.p_idx.enable() + if new_peri_attrs[2] == "dynamicValve" then tool_ctl.p_idx.enable() end else tool_ctl.p_unit.enable() - tool_ctl.p_idx.set_value(1) - tool_ctl.p_idx.disable() + if new_peri_attrs[2] == "dynamicValve" then + tool_ctl.p_idx.set_value(1) + tool_ctl.p_idx.disable() + end end end @@ -879,6 +882,12 @@ local function config_view(display) tool_ctl.p_err.show() return else index = idx end + elseif peri_type == "environmentDetector" then + if not (util.is_int(idx) and idx > 0) then + tool_ctl.p_err.set_value("Index must be greater than 0.") + tool_ctl.p_err.show() + return + else index = idx end end tool_ctl.p_err.hide(true) diff --git a/rtu/startup.lua b/rtu/startup.lua index 6ad5401..2ce87d4 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -284,7 +284,7 @@ local function main() -- CHECK: index range local function validate_index(min, max) - if (util.is_int(index) and index < min) and (max == nil or index > max) then + if (util.is_int(index) and index < min) and (max ~= nil and index > max) then local message = util.c("configure> device entry #", i, ": index ", index, " isn't >= ", min, " and <= ", max) println(message) log.fatal(message) @@ -413,6 +413,7 @@ local function main() rtu_iface, faulted = sna_rtu.new(device) elseif type == "environmentDetector" 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 diff --git a/rtu/threads.lua b/rtu/threads.lua index 02d809a..2a93638 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -121,6 +121,11 @@ local function handle_unit_mount(smem, println_ts, iface, type, device, unit) log.error(util.c("environment detector '", unit.name, "' cannot init, no valid assignment provided in config")) end + if (unit.index == false) or unit.index < 1 then + invalid = true + log.error(util.c("environment detector '", unit.name, "' cannot init, invalid index provided in config")) + end + unit.type = RTU_UNIT_TYPE.ENV_DETECTOR else resend_advert = false diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 28bd6e2..791aabd 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -232,9 +232,7 @@ function facility.new(num_reactors, cooling_conf) -- link a redstone RTU session ---@param rs_unit unit_session - function public.add_redstone(rs_unit) - table.insert(self.redstone, rs_unit) - end + function public.add_redstone(rs_unit) table.insert(self.redstone, rs_unit) end -- link an induction matrix RTU session ---@param imatrix unit_session @@ -258,23 +256,11 @@ function facility.new(num_reactors, cooling_conf) -- link a dynamic tank RTU session ---@param dynamic_tank unit_session - ---@return boolean linked dynamic tank accepted (max 1) - function public.add_tank(dynamic_tank) - if #self.tanks == 0 then - table.insert(self.tanks, dynamic_tank) - return true - else return false end - end + function public.add_tank(dynamic_tank) table.insert(self.tanks, dynamic_tank) end -- link an environment detector RTU session ---@param envd unit_session - ---@return boolean linked environment detector accepted (max 1) - function public.add_envd(envd) - if #self.envd == 0 then - table.insert(self.envd, envd) - return true - else return false end - end + function public.add_envd(envd) table.insert(self.envd, envd) end -- purge devices associated with the given RTU session ID ---@param session integer RTU session ID @@ -643,11 +629,16 @@ function facility.new(num_reactors, cooling_conf) end -- check for facility radiation - if self.envd[1] ~= nil then - local envd = self.envd[1] ---@type unit_session - local e_db = envd.get_db() ---@type envd_session_db + if #self.envd > 0 then + local max_rad = 0 - astatus.radiation = e_db.radiation_raw > ALARM_LIMS.FAC_HIGH_RAD + for i = 1, #self.envd do + local envd = self.envd[i] ---@type unit_session + local e_db = envd.get_db() ---@type envd_session_db + if e_db.radiation_raw > max_rad then max_rad = e_db.radiation_raw end + end + + astatus.radiation = max_rad > ALARM_LIMS.FAC_HIGH_RAD else -- don't clear, if it is true then we lost it with high radiation, so just keep alarming -- operator can restart the system or hit the stop/reset button @@ -1183,7 +1174,8 @@ function facility.new(num_reactors, cooling_conf) status.rad_mon = {} for i = 1, #self.envd do local envd = self.envd[i] ---@type unit_session - status.rad_mon[i] = { envd.is_faulted(), envd.get_db().radiation } + local db = envd.get_db() ---@type envd_session_db + status.rad_mon[envd.get_device_idx()] = { envd.is_faulted(), db.radiation, db.radiation_raw } end return status diff --git a/supervisor/session/rtu/boilerv.lua b/supervisor/session/rtu/boilerv.lua index 5af6f83..33759f2 100644 --- a/supervisor/session/rtu/boilerv.lua +++ b/supervisor/session/rtu/boilerv.lua @@ -37,9 +37,12 @@ local PERIODICS = { ---@param advert rtu_advertisement RTU advertisement table ---@param out_queue mqueue RTU unit message out queue function boilerv.new(session_id, unit_id, advert, out_queue) - -- type check + -- checks if advert.type ~= RTU_UNIT_TYPE.BOILER_VALVE then - log.error("attempt to instantiate boilerv RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.") + log.error("attempt to instantiate boilerv RTU for type " .. types.rtu_type_to_string(advert.type)) + return nil + elseif not util.is_int(advert.index) then + log.error("attempt to instantiate boilerv RTU without index") return nil end diff --git a/supervisor/session/rtu/dynamicv.lua b/supervisor/session/rtu/dynamicv.lua index 68cd6e9..b1e5b4a 100644 --- a/supervisor/session/rtu/dynamicv.lua +++ b/supervisor/session/rtu/dynamicv.lua @@ -49,9 +49,12 @@ local PERIODICS = { ---@param advert rtu_advertisement RTU advertisement table ---@param out_queue mqueue RTU unit message out queue function dynamicv.new(session_id, unit_id, advert, out_queue) - -- type check + -- checks if advert.type ~= RTU_UNIT_TYPE.DYNAMIC_VALVE then - log.error("attempt to instantiate dynamicv RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.") + log.error("attempt to instantiate dynamicv RTU for type " .. types.rtu_type_to_string(advert.type)) + return nil + elseif not util.is_int(advert.index) then + log.error("attempt to instantiate dynamicv RTU without index") return nil end diff --git a/supervisor/session/rtu/envd.lua b/supervisor/session/rtu/envd.lua index cf342b7..8eacf1d 100644 --- a/supervisor/session/rtu/envd.lua +++ b/supervisor/session/rtu/envd.lua @@ -28,13 +28,16 @@ local PERIODICS = { ---@param advert rtu_advertisement ---@param out_queue mqueue function envd.new(session_id, unit_id, advert, out_queue) - -- type check + -- checks if advert.type ~= RTU_UNIT_TYPE.ENV_DETECTOR then - log.error("attempt to instantiate envd RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.") + log.error("attempt to instantiate envd RTU for type " .. types.rtu_type_to_string(advert.type)) + return nil + elseif not util.is_int(advert.index) then + log.error("attempt to instantiate envd RTU without index") return nil end - local log_tag = util.c("session.rtu(", session_id, ").envd[@", unit_id, "]: ") + local log_tag = util.c("session.rtu(", session_id, ").envd(", advert.index, ")[@", unit_id, "]: ") local self = { session = unit_session.new(session_id, unit_id, advert, out_queue, log_tag, TXN_TAGS), diff --git a/supervisor/session/rtu/imatrix.lua b/supervisor/session/rtu/imatrix.lua index b3c23d8..1bcf286 100644 --- a/supervisor/session/rtu/imatrix.lua +++ b/supervisor/session/rtu/imatrix.lua @@ -37,9 +37,12 @@ local PERIODICS = { ---@param advert rtu_advertisement RTU advertisement table ---@param out_queue mqueue RTU unit message out queue function imatrix.new(session_id, unit_id, advert, out_queue) - -- type check + -- checks if advert.type ~= RTU_UNIT_TYPE.IMATRIX then - log.error("attempt to instantiate imatrix RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.") + log.error("attempt to instantiate imatrix RTU for type " .. types.rtu_type_to_string(advert.type)) + return nil + elseif not util.is_int(advert.index) then + log.error("attempt to instantiate dynamicv RTU without index") return nil end diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index f142023..b248902 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -52,7 +52,7 @@ local PERIODICS = { function redstone.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPE.REDSTONE then - log.error("attempt to instantiate redstone RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.") + log.error("attempt to instantiate redstone RTU for type " .. types.rtu_type_to_string(advert.type)) return nil end diff --git a/supervisor/session/rtu/sna.lua b/supervisor/session/rtu/sna.lua index cc5a6fc..39ab1d0 100644 --- a/supervisor/session/rtu/sna.lua +++ b/supervisor/session/rtu/sna.lua @@ -36,7 +36,7 @@ local PERIODICS = { function sna.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPE.SNA then - log.error("attempt to instantiate sna RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.") + log.error("attempt to instantiate sna RTU for type " .. types.rtu_type_to_string(advert.type)) return nil end diff --git a/supervisor/session/rtu/sps.lua b/supervisor/session/rtu/sps.lua index e53a540..3143658 100644 --- a/supervisor/session/rtu/sps.lua +++ b/supervisor/session/rtu/sps.lua @@ -39,7 +39,7 @@ local PERIODICS = { function sps.new(session_id, unit_id, advert, out_queue) -- type check if advert.type ~= RTU_UNIT_TYPE.SPS then - log.error("attempt to instantiate sps RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.") + log.error("attempt to instantiate sps RTU for type " .. types.rtu_type_to_string(advert.type)) return nil end diff --git a/supervisor/session/rtu/turbinev.lua b/supervisor/session/rtu/turbinev.lua index 1112106..e6c08f5 100644 --- a/supervisor/session/rtu/turbinev.lua +++ b/supervisor/session/rtu/turbinev.lua @@ -49,9 +49,12 @@ local PERIODICS = { ---@param advert rtu_advertisement RTU advertisement table ---@param out_queue mqueue RTU unit message out queue function turbinev.new(session_id, unit_id, advert, out_queue) - -- type check + -- checks if advert.type ~= RTU_UNIT_TYPE.TURBINE_VALVE then - log.error("attempt to instantiate turbinev RTU for type '" .. types.rtu_type_to_string(advert.type) .. "'. this is a bug.") + log.error("attempt to instantiate turbinev RTU for type " .. types.rtu_type_to_string(advert.type)) + return nil + elseif not util.is_int(advert.index) then + log.error("attempt to instantiate turbinev RTU without index") return nil end diff --git a/supervisor/unit.lua b/supervisor/unit.lua index eff23de..999f30b 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -867,7 +867,8 @@ function unit.new(reactor_id, num_boilers, num_turbines) status.rad_mon = {} for i = 1, #self.envd do local envd = self.envd[i] ---@type unit_session - status.rad_mon[i] = { envd.is_faulted(), envd.get_db().radiation } + local db = envd.get_db() ---@type envd_session_db + status.rad_mon[envd.get_device_idx()] = { envd.is_faulted(), db.radiation, db.radiation_raw } end return status From cb049ebf41ab8129a4c62ab7a9fab305e4f2c7df Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 Nov 2023 11:57:05 -0500 Subject: [PATCH 24/36] #194 changed 'newer' to 'different' in ccmsi --- ccmsi.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ccmsi.lua b/ccmsi.lua index 0993382..19ccebc 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -18,7 +18,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local CCMSI_VERSION = "v1.11b" +local CCMSI_VERSION = "v1.11c" local install_dir = "/.install-cache" local manifest_path = "https://mikaylafischler.github.io/cc-mek-scada/manifests/" @@ -276,7 +276,7 @@ if mode == "check" then end if manifest.versions.installer ~= local_manifest.versions.installer then - yellow();println("\nA newer version of the installer is available, it is recommended to update (use 'ccmsi update installer').");white() + yellow();println("\nA different version of the installer is available, it is recommended to update (use 'ccmsi update installer').");white() end elseif mode == "install" or mode == "update" then local update_installer = app == "installer" @@ -314,7 +314,7 @@ elseif mode == "install" or mode == "update" then end if manifest.versions.installer ~= CCMSI_VERSION then - if not update_installer then yellow();println("A newer version of the installer is available, it is recommended to update to it.");white() end + if not update_installer then yellow();println("A different version of the installer is available, it is recommended to update to it.");white() end if update_installer or ask_y_n("Would you like to update now") then lgray();println("GET ccmsi.lua") local dl, err = http.get(repo_path .. "ccmsi.lua") From 78ad6d5457445af847d0ff4108c6ce602e4bdd01 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 Nov 2023 12:00:42 -0500 Subject: [PATCH 25/36] luacheck fix --- rtu/startup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtu/startup.lua b/rtu/startup.lua index 2ce87d4..afee7af 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -149,7 +149,7 @@ local function main() -- go through redstone definitions list for entry_idx = 1, #rtu_redstone do local entry = rtu_redstone[entry_idx] ---@type rtu_rs_definition - local assignment = "" + local assignment local for_reactor = entry.unit local iface_name = util.trinary(entry.color ~= nil, util.c(entry.side, "/", rsio.color_name(entry.color)), entry.side) From 68754977b0ce99aa33dd53d5e0f41c50ac4a99f0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 Nov 2023 14:21:48 -0500 Subject: [PATCH 26/36] cleanup and fixes --- coordinator/iocontrol.lua | 8 ++-- coordinator/startup.lua | 2 +- rtu/threads.lua | 66 ++++++++---------------------- supervisor/facility.lua | 2 +- supervisor/session/rtu/imatrix.lua | 3 -- 5 files changed, 23 insertions(+), 58 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 96911e7..91ecc4d 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -783,12 +783,12 @@ function iocontrol.update_facility_status(status) fac.radiation = types.new_zero_radiation_reading() fac.ps.publish("rad_computed_status", 1) end + + fac.ps.publish("radiation", fac.radiation) else log.debug(log_header .. "radiation monitor list not a table") valid = false end - - fac.ps.publish("radiation", fac.radiation) else log.debug(log_header .. "rtu statuses not a table") valid = false @@ -1081,12 +1081,12 @@ function iocontrol.update_unit_statuses(statuses) else unit.radiation = types.new_zero_radiation_reading() end + + unit.unit_ps.publish("radiation", unit.radiation) else log.debug(log_header .. "radiation monitor list not a table") valid = false end - - unit.unit_ps.publish("radiation", unit.radiation) else log.debug(log_header .. "rtu list not a table") valid = false diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 0ce23ba..7811904 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -22,7 +22,7 @@ local sounder = require("coordinator.sounder") local apisessions = require("coordinator.session.apisessions") -local COORDINATOR_VERSION = "v1.0.16" +local COORDINATOR_VERSION = "v1.0.17" local println = util.println local println_ts = util.println_ts diff --git a/rtu/threads.lua b/rtu/threads.lua index 2a93638..f3147e7 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -40,10 +40,12 @@ local function handle_unit_mount(smem, println_ts, iface, type, device, unit) -- find disconnected device to reconnect -- note: cannot check isFormed as that would yield this coroutine and consume events if unit.name == iface then - local resend_advert = false - local faulted = false - local unknown = false - local invalid = false + local resend_advert, faulted, unknown, invalid = false, false, false, false + + local function fail(msg) + invalid = true + log.error(msg .. " in config") + end -- found, re-link unit.device = device @@ -52,79 +54,45 @@ local function handle_unit_mount(smem, println_ts, iface, type, device, unit) resend_advert = true if type == "boilerValve" then -- boiler multiblock - if unit.reactor < 1 or unit.reactor > 4 then - invalid = true - log.error(util.c("boiler '", unit.name, "' cannot init, not assigned to a valid unit in config")) - end - - if (unit.index == false) or unit.index < 1 or unit.index > 2 then - invalid = true - log.error(util.c("boiler '", unit.name, "' cannot init, invalid index provided in config")) - end + if unit.reactor < 1 or unit.reactor > 4 then fail(util.c("boiler '", unit.name, "' cannot init, not assigned to a valid unit")) end + if (unit.index == false) or unit.index < 1 or unit.index > 2 then fail(util.c("boiler '", unit.name, "' cannot init, invalid index provided")) end unit.type = RTU_UNIT_TYPE.BOILER_VALVE elseif type == "turbineValve" then -- turbine multiblock - if unit.reactor < 1 or unit.reactor > 4 then - invalid = true - log.error(util.c("turbine '", unit.name, "' cannot init, not assigned to a valid unit in config")) - end - - if (unit.index == false) or unit.index < 1 or unit.index > 3 then - invalid = true - log.error(util.c("turbine '", unit.name, "' cannot init, invalid index provided in config")) - end + if unit.reactor < 1 or unit.reactor > 4 then fail(util.c("turbine '", unit.name, "' cannot init, not assigned to a valid unit")) end + if (unit.index == false) or unit.index < 1 or unit.index > 3 then fail(util.c("turbine '", unit.name, "' cannot init, invalid index provided")) end unit.type = RTU_UNIT_TYPE.TURBINE_VALVE elseif type == "dynamicValve" then -- dynamic tank multiblock - if unit.reactor < 0 or unit.reactor > 4 then - invalid = true - log.error(util.c("dynamic tank '", unit.name, "' cannot init, no valid assignment provided in config")) - end + if unit.reactor < 0 or unit.reactor > 4 then fail(util.c("dynamic tank '", unit.name, "' cannot init, no valid assignment provided")) end if (unit.reactor == 0 and ((unit.index == false) or unit.index < 1 or unit.index > 4)) or (unit.reactor > 0 and unit.index ~= 1) then - invalid = true - log.error(util.c("dynamic tank '", unit.name, "' cannot init, invalid index provided in config")) + fail(util.c("dynamic tank '", unit.name, "' cannot init, invalid index provided")) end unit.type = RTU_UNIT_TYPE.DYNAMIC_VALVE elseif type == "inductionPort" then -- induction matrix multiblock - if unit.reactor ~= 0 then - invalid = true - log.error(util.c("induction matrix '", unit.name, "' cannot init, not assigned to facility in config")) - end + if unit.reactor ~= 0 then fail(util.c("induction matrix '", unit.name, "' cannot init, not assigned to facility")) end unit.type = RTU_UNIT_TYPE.IMATRIX elseif type == "spsPort" then -- SPS multiblock - if unit.reactor ~= 0 then - invalid = true - log.error(util.c("SPS '", unit.name, "' cannot init, not assigned to facility in config")) - end + if unit.reactor ~= 0 then fail(util.c("SPS '", unit.name, "' cannot init, not assigned to facility")) end unit.type = RTU_UNIT_TYPE.SPS elseif type == "solarNeutronActivator" then -- SNA - if unit.reactor < 1 or unit.reactor > 4 then - invalid = true - log.error(util.c("SNA '", unit.name, "' cannot init, not assigned to a valid unit in config")) - end + if unit.reactor < 1 or unit.reactor > 4 then fail(util.c("SNA '", unit.name, "' cannot init, not assigned to a valid unit")) end unit.type = RTU_UNIT_TYPE.SNA elseif type == "environmentDetector" then -- advanced peripherals environment detector - if unit.reactor < 0 or unit.reactor > 4 then - invalid = true - log.error(util.c("environment detector '", unit.name, "' cannot init, no valid assignment provided in config")) - end - - if (unit.index == false) or unit.index < 1 then - invalid = true - log.error(util.c("environment detector '", unit.name, "' cannot init, invalid index provided in config")) - end + if unit.reactor < 0 or unit.reactor > 4 then fail(util.c("environment detector '", unit.name, "' cannot init, no valid assignment provided")) end + if (unit.index == false) or unit.index < 1 then fail(util.c("environment detector '", unit.name, "' cannot init, invalid index provided")) end unit.type = RTU_UNIT_TYPE.ENV_DETECTOR else diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 791aabd..7578cbd 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -638,7 +638,7 @@ function facility.new(num_reactors, cooling_conf) if e_db.radiation_raw > max_rad then max_rad = e_db.radiation_raw end end - astatus.radiation = max_rad > ALARM_LIMS.FAC_HIGH_RAD + astatus.radiation = max_rad >= ALARM_LIMS.FAC_HIGH_RAD else -- don't clear, if it is true then we lost it with high radiation, so just keep alarming -- operator can restart the system or hit the stop/reset button diff --git a/supervisor/session/rtu/imatrix.lua b/supervisor/session/rtu/imatrix.lua index 1bcf286..5a6880e 100644 --- a/supervisor/session/rtu/imatrix.lua +++ b/supervisor/session/rtu/imatrix.lua @@ -41,9 +41,6 @@ function imatrix.new(session_id, unit_id, advert, out_queue) if advert.type ~= RTU_UNIT_TYPE.IMATRIX then log.error("attempt to instantiate imatrix RTU for type " .. types.rtu_type_to_string(advert.type)) return nil - elseif not util.is_int(advert.index) then - log.error("attempt to instantiate dynamicv RTU without index") - return nil end local log_tag = util.c("session.rtu(", session_id, ").imatrix[@", unit_id, "]: ") From 41ad8d8edb4a6851ce202c0e483e91d3c0e53aeb Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 Nov 2023 14:35:53 -0500 Subject: [PATCH 27/36] #306 prevent duplicate redstone inputs --- rtu/configure.lua | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/rtu/configure.lua b/rtu/configure.lua index 0c19800..5522098 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -928,8 +928,9 @@ local function config_view(display) local rs_c_3 = Div{parent=rs_cfg,x=2,y=4,width=49} local rs_c_4 = Div{parent=rs_cfg,x=2,y=4,width=49} local rs_c_5 = Div{parent=rs_cfg,x=2,y=4,width=49} + local rs_c_6 = Div{parent=rs_cfg,x=2,y=4,width=49} - local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5}} + local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5,rs_c_6}} TextBox{parent=rs_cfg,x=1,y=2,height=1,text_align=CENTER,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)} @@ -958,12 +959,24 @@ local function config_view(display) PushButton{parent=rs_c_1,x=35,y=14,min_width=7,text="New +",callback=function()rs_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg} PushButton{parent=rs_c_1,x=43,y=14,min_width=7,text="Apply",callback=rs_apply,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg} + TextBox{parent=rs_c_6,x=1,y=1,height=5,text_align=CENTER,text="You already configured this input. There can only be one entry for each input.\n\nPlease select a different port."} + PushButton{parent=rs_c_6,x=1,y=14,min_width=6,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + TextBox{parent=rs_c_2,x=1,y=1,height=1,text="Select one of the below ports to use."} local rs_ports = ListBox{parent=rs_c_2,x=1,y=3,height=10,width=51,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} local new_rs_port = IO.F_SCRAM local function new_rs(port) + if (rsio.get_io_mode(port) == rsio.IO_DIR.IN) then + for i = 1, #tmp_cfg.Redstone do + if tmp_cfg.Redstone[i].port == port then + rs_pane.set_value(6) + return + end + end + end + tool_ctl.rs_cfg_editing = false local text From 76403b4ddcf33511ba9a3af6b39a3b1ed51ed406 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 Nov 2023 15:38:25 -0500 Subject: [PATCH 28/36] cleanup and grammar --- reactor-plc/configure.lua | 4 ---- rtu/configure.lua | 6 +----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index 6547d53..dc3e728 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -667,18 +667,14 @@ function configurator.configure(ask_config) -- handle event if event == "timer" then - -- notify timer callback dispatcher tcd.handle(param1) 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 local m_e = core.events.new_mouse_event(event, param1, param2, param3) if m_e then display.handle_mouse(m_e) end elseif event == "char" or event == "key" or event == "key_up" then - -- handle a key event local k_e = core.events.new_key_event(event, param1, param2) if k_e then display.handle_key(k_e) end elseif event == "paste" then - -- handle a paste event display.handle_paste(param1) end diff --git a/rtu/configure.lua b/rtu/configure.lua index 5522098..30eb531 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -663,7 +663,7 @@ local function config_view(display) PushButton{parent=peri_c_2,x=8,y=14,min_width=10,text="Manual +",callback=function()peri_pane.set_value(3)end,fg_bg=cpair(colors.black,colors.orange),active_fg_bg=btn_act_fg_bg} PushButton{parent=peri_c_2,x=26,y=14,min_width=24,text="I don't see my device!",callback=function()peri_pane.set_value(7)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg} - TextBox{parent=peri_c_7,x=1,y=1,height=10,text="Make sure your device is either touching the RTU, or connected via wired modems. There should be a wired modem on a side of the RTU then one on the device, connected by a cable. The modem on the device needs to be right clicked to connect it (which will turn its border red), at which point the peripheral name will be shown in the chat."} + TextBox{parent=peri_c_7,x=1,y=1,height=10,text="Make sure your device is either touching the RTU or connected via wired modems. There should be a wired modem on a side of the RTU then one on the device, connected by a cable. The modem on the device needs to be right clicked to connect it (which will turn its border red), at which point the peripheral name will be shown in the chat."} TextBox{parent=peri_c_7,x=1,y=9,height=4,text="If it still does not show, it may not be compatible. Currently only Boilers, Turbines, Dynamic Tanks, SNAs, SPSs, Induction Matricies, and Environment Detectors are supported."} PushButton{parent=peri_c_7,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} @@ -1397,18 +1397,14 @@ function configurator.configure(ask_config) -- handle event if event == "timer" then - -- notify timer callback dispatcher tcd.handle(param1) 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 local m_e = core.events.new_mouse_event(event, param1, param2, param3) if m_e then display.handle_mouse(m_e) end elseif event == "char" or event == "key" or event == "key_up" then - -- handle a key event local k_e = core.events.new_key_event(event, param1, param2) if k_e then display.handle_key(k_e) end elseif event == "paste" then - -- handle a paste event display.handle_paste(param1) elseif event == "peripheral_detach" then ppm.handle_unmount(param1) From 6e92097544da86dd16e5417bf611d6dca3056516 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 Nov 2023 16:06:16 -0500 Subject: [PATCH 29/36] fixed util.concat handling of nil parameters --- scada-common/util.lua | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/scada-common/util.lua b/scada-common/util.lua index 3623583..8257a2d 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -14,11 +14,15 @@ local print = print local tostring = tostring local type = type +local t_concat = table.concat +local t_insert = table.insert +local t_unpack = table.unpack + ---@class util local util = {} -- scada-common version -util.version = "1.1.8" +util.version = "1.1.9" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 @@ -70,7 +74,7 @@ function util.strval(val) if t == "string" then return val end -- this depends on Lua short-circuiting the or check for metatables (note: metatables won't have metatables) if (t == "table" and (getmetatable(val) == nil or getmetatable(val).__tostring == nil)) or t == "function" then - return table.concat{"[", tostring(val), "]"} + return t_concat{"[", tostring(val), "]"} else return tostring(val) end end @@ -90,7 +94,7 @@ function util.pad(str, n) local lpad = math.floor((n - len) / 2) local rpad = (n - len) - lpad - return table.concat{util.spaces(lpad), str, util.spaces(rpad)} + return t_concat{util.spaces(lpad), str, util.spaces(rpad)} end -- wrap a string into a table of lines @@ -109,8 +113,9 @@ function util.strwrap(str, limit) return cc_strings.wrap(str, limit) end ---@diagnostic disable-next-line: unused-vararg function util.concat(...) local strings = {} - for i = 1, #arg do strings[i] = util.strval(arg[i]) end - return table.concat(strings) +---@diagnostic disable-next-line: undefined-field + for i = 1, arg.n do strings[i] = util.strval(arg[i]) end + return t_concat(strings) end -- alias @@ -121,7 +126,7 @@ util.c = util.concat ---@param format string ---@vararg any ---@diagnostic disable-next-line: unused-vararg -function util.sprintf(format, ...) return string.format(format, table.unpack(arg)) end +function util.sprintf(format, ...) return string.format(format, t_unpack(arg)) end -- luacheck: unused args @@ -185,7 +190,7 @@ function util.mov_avg(length, default) ---@param x number value function public.reset(x) data = {} - for _ = 1, length do table.insert(data, x) end + for _ = 1, length do t_insert(data, x) end end -- record a new value From d7b1f9cc7ed9d65a51729b00f582e7c6aa5a488d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 Nov 2023 16:55:24 -0500 Subject: [PATCH 30/36] #306 #362 bugfixes --- coordinator/iocontrol.lua | 51 +++++++++++++++---------------- rtu/configure.lua | 18 ++++++----- rtu/startup.lua | 63 ++++++++++++++++++++------------------- supervisor/facility.lua | 4 +-- supervisor/unit.lua | 4 +-- 5 files changed, 72 insertions(+), 68 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 91ecc4d..c8a5f09 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -760,23 +760,24 @@ function iocontrol.update_facility_status(status) end -- environment detector status - if type(rtu_statuses.rad_mon) == "table" then - if #rtu_statuses.rad_mon > 0 then - local max_rad, max_reading, any_faulted = 0, types.new_zero_radiation_reading(), false + if type(rtu_statuses.envds) == "table" then + local max_rad, max_reading, any_conn, any_faulted = 0, types.new_zero_radiation_reading(), false, false - for i = 1, #rtu_statuses.rad_mon do - local rad_mon = rtu_statuses.rad_mon[i] - local rtu_faulted = rad_mon[1] ---@type boolean - local radiation = rad_mon[2] ---@type radiation_reading - local rad_raw = rad_mon[3] ---@type number + for _, envd in pairs(rtu_statuses.envds) do + local rtu_faulted = envd[1] ---@type boolean + local radiation = envd[2] ---@type radiation_reading + local rad_raw = envd[3] ---@type number - any_faulted = any_faulted or rtu_faulted - if rad_raw > max_rad then - max_rad = rad_raw - max_reading = radiation - end + any_conn = true + any_faulted = any_faulted or rtu_faulted + + if rad_raw > max_rad then + max_rad = rad_raw + max_reading = radiation end + end + if any_conn then fac.radiation = max_reading fac.ps.publish("rad_computed_status", util.trinary(any_faulted, 2, 3)) else @@ -786,7 +787,7 @@ function iocontrol.update_facility_status(status) fac.ps.publish("radiation", fac.radiation) else - log.debug(log_header .. "radiation monitor list not a table") + log.debug(log_header .. "environment detector list not a table") valid = false end else @@ -1061,22 +1062,22 @@ function iocontrol.update_unit_statuses(statuses) end -- environment detector status - if type(rtu_statuses.rad_mon) == "table" then - local max_rad, max_reading = 0, types.new_zero_radiation_reading() + if type(rtu_statuses.envds) == "table" then + local max_rad, max_reading, any_conn = 0, types.new_zero_radiation_reading(), false - if #rtu_statuses.rad_mon > 0 then + for _, envd in pairs(rtu_statuses.envds) do + local radiation = envd[2] ---@type radiation_reading + local rad_raw = envd[3] ---@type number - for id = 1, #rtu_statuses.rad_mon do - local rad_mon = rtu_statuses.rad_mon[id] - local radiation = rad_mon[2] ---@type radiation_reading - local rad_raw = rad_mon[3] ---@type number + any_conn = true - if rad_raw > max_rad then - max_rad = rad_raw - max_reading = radiation - end + if rad_raw > max_rad then + max_rad = rad_raw + max_reading = radiation end + end + if any_conn then unit.radiation = max_reading else unit.radiation = types.new_zero_radiation_reading() diff --git a/rtu/configure.lua b/rtu/configure.lua index 30eb531..0f3b4a6 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -710,6 +710,10 @@ local function config_view(display) tool_ctl.p_desc_ext.set_value("Before adding lots of SNAs: multiply the \"PEAK\" rate on the flow monitor (after connecting at least 1 SNA) by 10 to get the mB/t of waste that they can process. Enough SNAs to provide 2x to 3x of your max burn rate should be a good margin to catch up after night or cloudy weather. Too many devices (such as SNAs) on one RTU can cause lag.") elseif type == "dynamicValve" then tool_ctl.p_prompt.set_value("This is the # dynamic tank for...") + tool_ctl.p_assign_btn.show() + tool_ctl.p_assign_btn.redraw() + tool_ctl.p_assign_end.show() + tool_ctl.p_assign_end.redraw() tool_ctl.p_idx.show() tool_ctl.p_idx.redraw() tool_ctl.p_idx.set_max(4) @@ -724,23 +728,19 @@ local function config_view(display) tool_ctl.p_unit.enable() end - tool_ctl.p_assign_btn.show() - tool_ctl.p_assign_btn.redraw() - tool_ctl.p_assign_end.show() - tool_ctl.p_assign_end.redraw() tool_ctl.p_desc.reposition(1, 8) tool_ctl.p_desc.set_value("Each reactor unit can have at most 1 tank and the facility can have at most 4. Each facility tank must have a unique # 1 through 4, regardless of where it is connected. Only a total of 4 tanks can be displayed on the flow monitor.") elseif type == "environmentDetector" then tool_ctl.p_prompt.set_value("This is the # environment detector for...") + tool_ctl.p_assign_btn.show() + tool_ctl.p_assign_btn.redraw() + tool_ctl.p_assign_end.show() + tool_ctl.p_assign_end.redraw() tool_ctl.p_idx.show() tool_ctl.p_idx.redraw() tool_ctl.p_idx.set_max(99) tool_ctl.p_unit.reposition(18, 6) if tool_ctl.p_assign_btn.get_value() == 1 then tool_ctl.p_unit.disable() else tool_ctl.p_unit.enable() end - tool_ctl.p_assign_btn.show() - tool_ctl.p_assign_btn.redraw() - tool_ctl.p_assign_end.show() - tool_ctl.p_assign_end.redraw() tool_ctl.p_desc.reposition(1, 8) tool_ctl.p_desc.set_value("You can connect more than one environment detector for a particular unit or the facility. In that case, the maximum radiation reading from those assigned to that particular unit or the facility will be used for alarms and display.") elseif type == "inductionPort" or type == "spsPort" then @@ -882,6 +882,8 @@ local function config_view(display) tool_ctl.p_err.show() return else index = idx end + elseif peri_type == "dynamicValve" then + index = 1 elseif peri_type == "environmentDetector" then if not (util.is_int(idx) and idx > 0) then tool_ctl.p_err.set_value("Index must be greater than 0.") diff --git a/rtu/startup.lua b/rtu/startup.lua index afee7af..5e8b304 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -157,18 +157,18 @@ local function main() ---@cast for_reactor integer assignment = "reactor unit " .. entry.unit if rs_rtus[for_reactor] == nil then - log.debug(util.c("configure> allocated redstone RTU for reactor unit ", entry.unit)) + log.debug(util.c("sys_config> allocated redstone RTU for reactor unit ", entry.unit)) rs_rtus[for_reactor] = { rtu = redstone_rtu.new(), capabilities = {} } end elseif entry.unit == nil then assignment = "facility" for_reactor = 0 if rs_rtus[for_reactor] == nil then - log.debug(util.c("configure> allocated redstone RTU for the facility")) + log.debug(util.c("sys_config> allocated redstone RTU for the facility")) rs_rtus[for_reactor] = { rtu = redstone_rtu.new(), capabilities = {} } end else - local message = util.c("configure> invalid unit assignment at block index #", entry_idx) + local message = util.c("sys_config> invalid unit assignment at block index #", entry_idx) println(message) log.fatal(message) return false @@ -184,7 +184,7 @@ local function main() local capabilities = rs_rtus[for_reactor].capabilities if not valid then - local message = util.c("configure> invalid redstone definition at block index #", entry_idx) + local message = util.c("sys_config> invalid redstone definition at block index #", entry_idx) println(message) log.fatal(message) return false @@ -194,7 +194,7 @@ local function main() if mode == rsio.IO_MODE.DIGITAL_IN then -- can't have duplicate inputs if util.table_contains(capabilities, entry.port) then - local message = util.c("configure> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name) + local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name) println(message) log.warning(message) else @@ -205,7 +205,7 @@ local function main() elseif mode == rsio.IO_MODE.ANALOG_IN then -- can't have duplicate inputs if util.table_contains(capabilities, entry.port) then - local message = util.c("configure> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name) + local message = util.c("sys_config> skipping duplicate input for port ", rsio.to_string(entry.port), " on side ", iface_name) println(message) log.warning(message) else @@ -215,14 +215,14 @@ local function main() rs_rtu.link_ao(entry.side) else -- should be unreachable code, we already validated ports - log.error("configure> fell through if chain attempting to identify IO mode at block index #" .. entry_idx, true) - println("configure> encountered a software error, check logs") + log.error("sys_config> fell through if chain attempting to identify IO mode at block index #" .. entry_idx, true) + println("sys_config> encountered a software error, check logs") return false end table.insert(capabilities, entry.port) - log.debug(util.c("configure> linked redstone ", #capabilities, ": ", rsio.to_string(entry.port), " (", iface_name, ") for ", assignment)) + log.debug(util.c("sys_config> linked redstone ", #capabilities, ": ", rsio.to_string(entry.port), " (", iface_name, ") for ", assignment)) end end @@ -252,7 +252,7 @@ local function main() for_message = util.c("reactor unit ", for_reactor) end - log.info(util.c("configure> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for ", for_message)) + log.info(util.c("sys_config> initialized RTU unit #", #units, ": redstone_io (redstone) [1] for ", for_message)) unit.uid = #units @@ -268,7 +268,7 @@ local function main() -- CHECK: name is a string if type(name) ~= "string" then - local message = util.c("configure> device entry #", i, ": device ", name, " isn't a string") + local message = util.c("sys_config> device entry #", i, ": device ", name, " isn't a string") println(message) log.fatal(message) return false @@ -276,7 +276,7 @@ local function main() -- CHECK: index type if (index ~= nil) and (not util.is_int(index)) then - local message = util.c("configure> device entry #", i, ": index ", index, " isn't valid") + local message = util.c("sys_config> device entry #", i, ": index ", index, " isn't valid") println(message) log.fatal(message) return false @@ -284,8 +284,9 @@ local function main() -- CHECK: index range local function validate_index(min, max) - if (util.is_int(index) and index < min) and (max ~= nil and index > max) then - local message = util.c("configure> device entry #", i, ": index ", index, " isn't >= ", min, " and <= ", 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 println(message) log.fatal(message) return false @@ -295,12 +296,12 @@ 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("configure> device entry #", i, ": must only be for the facility") + local message = util.c("sys_config> device entry #", i, ": must only be for the facility") println(message) log.fatal(message) 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("configure> device entry #", i, ": unit assignment ", for_reactor, " isn't vaild") + local message = util.c("sys_config> device entry #", i, ": unit assignment ", for_reactor, " isn't vaild") println(message) log.fatal(message) return false @@ -317,7 +318,7 @@ local function main() local faulted = nil ---@type boolean|nil if device == nil then - local message = util.c("configure> '", name, "' not found, using placeholder") + local message = util.c("sys_config> '", name, "' not found, using placeholder") println(message) log.warning(message) @@ -338,8 +339,8 @@ local function main() formed = device.isFormed() if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then - println_ts(util.c("configure> failed to check if '", name, "' is formed")) - log.fatal(util.c("configure> failed to check if '", name, "' is a formed boiler multiblock")) + println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) + log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed boiler multiblock")) return false end elseif type == "turbineValve" then @@ -353,8 +354,8 @@ local function main() formed = device.isFormed() if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then - println_ts(util.c("configure> failed to check if '", name, "' is formed")) - log.fatal(util.c("configure> failed to check if '", name, "' is a formed turbine multiblock")) + println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) + log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed turbine multiblock")) return false end elseif type == "dynamicValve" then @@ -373,8 +374,8 @@ local function main() formed = device.isFormed() if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then - println_ts(util.c("configure> failed to check if '", name, "' is formed")) - log.fatal(util.c("configure> failed to check if '", name, "' is a formed dynamic tank multiblock")) + println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) + log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed dynamic tank multiblock")) return false end elseif type == "inductionPort" then @@ -387,8 +388,8 @@ local function main() formed = device.isFormed() if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then - println_ts(util.c("configure> failed to check if '", name, "' is formed")) - log.fatal(util.c("configure> failed to check if '", name, "' is a formed induction matrix multiblock")) + println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) + log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed induction matrix multiblock")) return false end elseif type == "spsPort" then @@ -401,8 +402,8 @@ local function main() formed = device.isFormed() if formed == ppm.UNDEFINED_FIELD or formed == ppm.ACCESS_FAULT then - println_ts(util.c("configure> failed to check if '", name, "' is formed")) - log.fatal(util.c("configure> failed to check if '", name, "' is a formed SPS multiblock")) + println_ts(util.c("sys_config> failed to check if '", name, "' is formed")) + log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed SPS multiblock")) return false end elseif type == "solarNeutronActivator" then @@ -423,7 +424,7 @@ local function main() rtu_type = RTU_UNIT_TYPE.VIRTUAL rtu_iface = rtu.init_unit().interface() else - local message = util.c("configure> device '", name, "' is not a known type (", type, ")") + local message = util.c("sys_config> device '", name, "' is not a known type (", type, ")") println_ts(message) log.fatal(message) return false @@ -431,12 +432,12 @@ local function main() if is_multiblock then if not formed then - log.info(util.c("configure> device '", name, "' is not formed")) + log.info(util.c("sys_config> device '", name, "' is not formed")) 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("configure> device '", name, "' is formed, but initialization had one or more faults: marked as unformed")) + log.warning(util.c("sys_config> device '", name, "' is formed, but initialization had one or more faults: marked as unformed")) end end @@ -466,7 +467,7 @@ local function main() for_message = util.c("reactor ", for_reactor) end - log.info(util.c("configure> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ") [", index, "] for ", for_message)) + log.info(util.c("sys_config> initialized RTU unit #", #units, ": ", name, " (", types.rtu_type_to_string(rtu_type), ") [", index, "] for ", for_message)) rtu_unit.uid = #units diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 7578cbd..81fd531 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -1171,11 +1171,11 @@ function facility.new(num_reactors, cooling_conf) end -- radiation monitors (environment detectors) - status.rad_mon = {} + status.envds = {} for i = 1, #self.envd do local envd = self.envd[i] ---@type unit_session local db = envd.get_db() ---@type envd_session_db - status.rad_mon[envd.get_device_idx()] = { envd.is_faulted(), db.radiation, db.radiation_raw } + status.envds[envd.get_device_idx()] = { envd.is_faulted(), db.radiation, db.radiation_raw } end return status diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 999f30b..645f4e9 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -864,11 +864,11 @@ function unit.new(reactor_id, num_boilers, num_turbines) status.sna = { #self.snas, public.get_sna_rate(), total_peak } -- radiation monitors (environment detectors) - status.rad_mon = {} + status.envds = {} for i = 1, #self.envd do local envd = self.envd[i] ---@type unit_session local db = envd.get_db() ---@type envd_session_db - status.rad_mon[envd.get_device_idx()] = { envd.is_faulted(), db.radiation, db.radiation_raw } + status.envds[envd.get_device_idx()] = { envd.is_faulted(), db.radiation, db.radiation_raw } end return status From a38ccf3dcc35bd238800f0ad78ada071a1d53446 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 Nov 2023 18:27:24 -0500 Subject: [PATCH 31/36] #145 #306 improvements and fixes, better peripheral import --- reactor-plc/configure.lua | 84 ++++++++-------- rtu/configure.lua | 206 +++++++++++++++++++++++++------------- 2 files changed, 179 insertions(+), 111 deletions(-) diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index dc3e728..928b082 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -184,10 +184,10 @@ local function config_view(display) local y_start = 5 - TextBox{parent=main_page,x=2,y=2,height=2,text_align=CENTER,text="Welcome to the Reactor PLC configurator! Please select one of the following options."} + TextBox{parent=main_page,x=2,y=2,height=2,text="Welcome to the Reactor PLC configurator! Please select one of the following options."} if tool_ctl.ask_config then - TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text_align=CENTER,text="Notice: This device has no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)} + TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device has no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)} y_start = y_start + 5 end @@ -220,10 +220,10 @@ local function config_view(display) local plc_pane = MultiPane{parent=plc_cfg,x=1,y=4,panes={plc_c_1,plc_c_2,plc_c_3,plc_c_4}} - TextBox{parent=plc_cfg,x=1,y=2,height=1,text_align=CENTER,text=" PLC Configuration",fg_bg=cpair(colors.black,colors.orange)} + TextBox{parent=plc_cfg,x=1,y=2,height=1,text=" PLC Configuration",fg_bg=cpair(colors.black,colors.orange)} - TextBox{parent=plc_c_1,x=1,y=1,height=1,text_align=CENTER,text="Would you like to set this PLC as networked?"} - TextBox{parent=plc_c_1,x=1,y=3,height=4,text_align=CENTER,text="If you have a supervisor, select the box. You will later be prompted to select the network configuration. If you instead want to use this as a standalone safety system, don't select the box.",fg_bg=g_lg_fg_bg} + TextBox{parent=plc_c_1,x=1,y=1,height=1,text="Would you like to set this PLC as networked?"} + TextBox{parent=plc_c_1,x=1,y=3,height=4,text="If you have a supervisor, select the box. You will later be prompted to select the network configuration. If you instead want to use this as a standalone safety system, don't select the box.",fg_bg=g_lg_fg_bg} local networked = CheckBox{parent=plc_c_1,x=1,y=8,label="Networked",default=ini_cfg.Networked,box_fg_bg=cpair(colors.orange,colors.black)} @@ -235,13 +235,13 @@ local function config_view(display) PushButton{parent=plc_c_1,x=1,y=14,min_width=6,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=plc_c_1,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_networked,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=plc_c_2,x=1,y=1,height=1,text_align=CENTER,text="Please enter the reactor unit ID for this PLC."} - TextBox{parent=plc_c_2,x=1,y=3,height=3,text_align=CENTER,text="If this is a networked PLC, currently only IDs 1 through 4 are acceptable.",fg_bg=g_lg_fg_bg} + TextBox{parent=plc_c_2,x=1,y=1,height=1,text="Please enter the reactor unit ID for this PLC."} + TextBox{parent=plc_c_2,x=1,y=3,height=3,text="If this is a networked PLC, currently only IDs 1 through 4 are acceptable.",fg_bg=g_lg_fg_bg} - TextBox{parent=plc_c_2,x=1,y=6,height=1,text_align=CENTER,text="Unit #"} + TextBox{parent=plc_c_2,x=1,y=6,height=1,text="Unit #"} local u_id = NumberField{parent=plc_c_2,x=7,y=6,width=5,max_digits=3,default=ini_cfg.UnitID,min=1,fg_bg=bw_fg_bg} - local u_id_err = TextBox{parent=plc_c_2,x=8,y=14,height=1,width=35,text_align=LEFT,text="Please set a unit ID.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local u_id_err = TextBox{parent=plc_c_2,x=8,y=14,height=1,width=35,text="Please set a unit ID.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_id() local unit_id = tonumber(u_id.get_value()) @@ -255,8 +255,8 @@ local function config_view(display) PushButton{parent=plc_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()plc_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=plc_c_2,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_id,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=plc_c_3,x=1,y=1,height=4,text_align=CENTER,text="When networked, the supervisor takes care of emergency coolant via RTUs. However, you can configure independent emergency coolant via the PLC. "} - TextBox{parent=plc_c_3,x=1,y=6,height=5,text_align=CENTER,text="This independent control can be used with or without a supervisor. To configure, you would next select the interface of the redstone output connected to one or more mekanism pipes.",fg_bg=g_lg_fg_bg} + TextBox{parent=plc_c_3,x=1,y=1,height=4,text="When networked, the supervisor takes care of emergency coolant via RTUs. However, you can configure independent emergency coolant via the PLC. "} + TextBox{parent=plc_c_3,x=1,y=6,height=5,text="This independent control can be used with or without a supervisor. To configure, you would next select the interface of the redstone output connected to one or more mekanism pipes.",fg_bg=g_lg_fg_bg} local en_em_cool = CheckBox{parent=plc_c_3,x=1,y=11,label="Enable PLC Emergency Coolant Control",default=ini_cfg.EmerCoolEnable,box_fg_bg=cpair(colors.orange,colors.black)} @@ -272,10 +272,10 @@ local function config_view(display) PushButton{parent=plc_c_3,x=1,y=14,min_width=6,text="\x1b Back",callback=function()plc_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=plc_c_3,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_en_emcool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=plc_c_4,x=1,y=1,height=1,text_align=CENTER,text="Emergency Coolant Redstone Output Side"} + TextBox{parent=plc_c_4,x=1,y=1,height=1,text="Emergency Coolant Redstone Output Side"} local side = Radio2D{parent=plc_c_4,x=1,y=2,rows=2,columns=3,default=side_to_idx(ini_cfg.EmerCoolSide),options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.orange} - TextBox{parent=plc_c_4,x=1,y=5,height=1,text_align=CENTER,text="Bundled Redstone Configuration"} + TextBox{parent=plc_c_4,x=1,y=5,height=1,text="Bundled Redstone Configuration"} local bundled = CheckBox{parent=plc_c_4,x=1,y=6,label="Is Bundled?",default=ini_cfg.EmerCoolColor~=nil,box_fg_bg=cpair(colors.orange,colors.black),callback=function(v)tool_ctl.bundled_emcool(v)end} local color = Radio2D{parent=plc_c_4,x=1,y=8,rows=4,columns=4,default=color_to_idx(ini_cfg.EmerCoolColor),options=color_options,radio_colors=cpair(colors.lightGray,colors.black),color_map=color_options_map,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg} if ini_cfg.EmerCoolColor == nil then color.disable() end @@ -297,19 +297,19 @@ local function config_view(display) local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3}} - TextBox{parent=net_cfg,x=1,y=2,height=1,text_align=CENTER,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)} + TextBox{parent=net_cfg,x=1,y=2,height=1,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)} - TextBox{parent=net_c_1,x=1,y=1,height=1,text_align=CENTER,text="Please set the network channels below."} - TextBox{parent=net_c_1,x=1,y=3,height=4,text_align=CENTER,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,height=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=8,height=1,text_align=CENTER,text="Supervisor Channel"} + TextBox{parent=net_c_1,x=1,y=8,height=1,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_align=CENTER,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_1,x=1,y=11,height=1,text_align=CENTER,text="PLC Channel"} + 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,height=1,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_align=CENTER,text="[PLC_CHANNEL]",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_1,x=9,y=12,height=4,text="[PLC_CHANNEL]",fg_bg=g_lg_fg_bg} - local chan_err = TextBox{parent=net_c_1,x=8,y=14,height=1,width=35,text_align=LEFT,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local chan_err = TextBox{parent=net_c_1,x=8,y=14,height=1,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_channels() local svr_c = tonumber(svr_chan.get_value()) @@ -331,16 +331,16 @@ local function config_view(display) PushButton{parent=net_c_1,x=1,y=14,min_width=6,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,min_width=6,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,height=1,text_align=CENTER,text="Connection Timeout"} + TextBox{parent=net_c_2,x=1,y=1,height=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,fg_bg=bw_fg_bg} - TextBox{parent=net_c_2,x=9,y=2,height=2,text_align=CENTER,text="seconds (default 5)",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_2,x=1,y=3,height=4,text_align=CENTER,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=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_2,x=1,y=8,height=1,text_align=CENTER,text="Trusted Range"} + TextBox{parent=net_c_2,x=1,y=8,height=1,text="Trusted Range"} local range = NumberField{parent=net_c_2,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_digits=20,allow_decimal=true,fg_bg=bw_fg_bg} - TextBox{parent=net_c_2,x=1,y=10,height=4,text_align=CENTER,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_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} - local p2_err = TextBox{parent=net_c_2,x=8,y=14,height=1,width=35,text_align=LEFT,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local p2_err = TextBox{parent=net_c_2,x=8,y=14,height=1,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_ct_tr() local timeout_val = tonumber(timeout.get_value()) @@ -362,10 +362,10 @@ local function config_view(display) PushButton{parent=net_c_2,x=1,y=14,min_width=6,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,min_width=6,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_align=CENTER,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_align=CENTER,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 compution (can slow things down).",fg_bg=g_lg_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 compution (can slow things down).",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_3,x=1,y=11,height=1,text_align=CENTER,text="Facility Auth Key"} + TextBox{parent=net_c_3,x=1,y=11,height=1,text="Facility Auth Key"} local key, _, censor = 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} local function censor_key(enable) censor(util.trinary(enable, "*", nil)) end @@ -375,7 +375,7 @@ local function config_view(display) hide_key.set_value(true) censor_key(true) - local key_err = TextBox{parent=net_c_3,x=8,y=14,height=1,width=35,text_align=LEFT,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local key_err = TextBox{parent=net_c_3,x=8,y=14,height=1,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() @@ -393,20 +393,20 @@ local function config_view(display) local log_c_1 = Div{parent=log_cfg,x=2,y=4,width=49} - TextBox{parent=log_cfg,x=1,y=2,height=1,text_align=CENTER,text=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)} + TextBox{parent=log_cfg,x=1,y=2,height=1,text=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)} - TextBox{parent=log_c_1,x=1,y=1,height=1,text_align=CENTER,text="Please configure logging below."} + TextBox{parent=log_c_1,x=1,y=1,height=1,text="Please configure logging below."} - TextBox{parent=log_c_1,x=1,y=3,height=1,text_align=CENTER,text="Log File Mode"} + TextBox{parent=log_c_1,x=1,y=3,height=1,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} - TextBox{parent=log_c_1,x=1,y=7,height=1,text_align=CENTER,text="Log File Path"} + TextBox{parent=log_c_1,x=1,y=7,height=1,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} local en_dbg = CheckBox{parent=log_c_1,x=1,y=10,default=ini_cfg.LogDebug,label="Enable Logging Debug Messages",box_fg_bg=cpair(colors.pink,colors.black)} - TextBox{parent=log_c_1,x=3,y=11,height=2,text_align=CENTER,text="This results in much larger log files. It is best to only use this when there is a problem.",fg_bg=g_lg_fg_bg} + TextBox{parent=log_c_1,x=3,y=11,height=2,text="This results in much larger log files. It is best to only use this when there is a problem.",fg_bg=g_lg_fg_bg} - local path_err = TextBox{parent=log_c_1,x=8,y=14,height=1,width=35,text_align=LEFT,text="Please provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local path_err = TextBox{parent=log_c_1,x=8,y=14,height=1,width=35,text="Please provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_log() if path.get_value() ~= "" then @@ -438,7 +438,7 @@ local function config_view(display) local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4}} - TextBox{parent=summary,x=1,y=2,height=1,text_align=CENTER,text=" Summary",fg_bg=cpair(colors.black,colors.green)} + TextBox{parent=summary,x=1,y=2,height=1,text=" Summary",fg_bg=cpair(colors.black,colors.green)} local setting_list = ListBox{parent=sum_c_1,x=1,y=1,height=12,width=51,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} @@ -498,7 +498,7 @@ local function config_view(display) tool_ctl.show_key_btn = PushButton{parent=sum_c_1,x=8,y=14,min_width=17,text="Unhide Auth Key",callback=function()tool_ctl.show_auth_key()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} tool_ctl.settings_apply = PushButton{parent=sum_c_1,x=43,y=14,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg} - TextBox{parent=sum_c_2,x=1,y=1,height=1,text_align=CENTER,text="Settings saved!"} + TextBox{parent=sum_c_2,x=1,y=1,height=1,text="Settings saved!"} local function go_home() main_pane.set_value(1) @@ -510,7 +510,7 @@ local function config_view(display) PushButton{parent=sum_c_2,x=1,y=14,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=sum_c_2,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} - TextBox{parent=sum_c_3,x=1,y=1,height=2,text_align=CENTER,text="The old config.lua file will now be deleted, then the configurator will exit."} + TextBox{parent=sum_c_3,x=1,y=1,height=2,text="The old config.lua file will now be deleted, then the configurator will exit."} local function delete_legacy() fs.delete("/reactor-plc/config.lua") @@ -520,7 +520,7 @@ local function config_view(display) PushButton{parent=sum_c_3,x=1,y=14,min_width=8,text="Cancel",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=sum_c_3,x=44,y=14,min_width=6,text="OK",callback=delete_legacy,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.gray)} - TextBox{parent=sum_c_4,x=1,y=1,height=5,text_align=CENTER,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."} + TextBox{parent=sum_c_4,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."} PushButton{parent=sum_c_4,x=1,y=14,min_width=6,text="Home",callback=go_home,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=sum_c_4,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} @@ -528,7 +528,7 @@ local function config_view(display) local cl = Div{parent=changelog,x=2,y=4,width=49} - TextBox{parent=changelog,x=1,y=2,height=1,text_align=CENTER,text=" Config Change Log",fg_bg=bw_fg_bg} + TextBox{parent=changelog,x=1,y=2,height=1,text=" Config Change Log",fg_bg=bw_fg_bg} local c_log = ListBox{parent=cl,x=1,y=1,height=12,width=51,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} diff --git a/rtu/configure.lua b/rtu/configure.lua index 0f3b4a6..31b28a8 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -122,6 +122,7 @@ local tool_ctl = { has_config = false, viewing_config = false, importing_legacy = false, + importing_any_dc = false, peri_cfg_editing = false, ---@type string|false peri_cfg_manual = false, rs_cfg_editing = false, ---@type integer|false @@ -273,10 +274,10 @@ local function config_view(display) local y_start = 2 if tool_ctl.ask_config then - TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text_align=CENTER,text="Notice: This device has no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)} + TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device has no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)} y_start = y_start + 5 else - TextBox{parent=main_page,x=2,y=2,height=2,text_align=CENTER,text="Welcome to the RTU gateway configurator! Please select one of the following options."} + TextBox{parent=main_page,x=2,y=2,height=2,text="Welcome to the RTU gateway configurator! Please select one of the following options."} y_start = y_start + 3 end @@ -323,16 +324,16 @@ local function config_view(display) local spkr_c = Div{parent=spkr_cfg,x=2,y=4,width=49} - TextBox{parent=spkr_cfg,x=1,y=2,height=1,text_align=CENTER,text=" Speaker Configuration",fg_bg=cpair(colors.black,colors.cyan)} + TextBox{parent=spkr_cfg,x=1,y=2,height=1,text=" Speaker Configuration",fg_bg=cpair(colors.black,colors.cyan)} - TextBox{parent=spkr_c,x=1,y=1,height=2,text_align=CENTER,text="Speakers can be connected to this RTU gateway without RTU unit configuration entries."} - TextBox{parent=spkr_c,x=1,y=4,height=3,text_align=CENTER,text="You can change the speaker audio volume from the default. The range is 0.0 to 3.0, where 1.0 is standard volume."} + TextBox{parent=spkr_c,x=1,y=1,height=2,text="Speakers can be connected to this RTU gateway without RTU unit configuration entries."} + TextBox{parent=spkr_c,x=1,y=4,height=3,text="You can change the speaker audio volume from the default. The range is 0.0 to 3.0, where 1.0 is standard volume."} local s_vol = NumberField{parent=spkr_c,x=1,y=8,width=9,max_digits=7,allow_decimal=true,default=ini_cfg.SpeakerVolume,min=0,max=3,fg_bg=bw_fg_bg} - TextBox{parent=spkr_c,x=1,y=10,height=3,text_align=CENTER,text="Note: alarm sine waves are at half scale so that multiple will be required to reach full scale.",fg_bg=g_lg_fg_bg} + TextBox{parent=spkr_c,x=1,y=10,height=3,text="Note: alarm sine waves are at half scale so that multiple will be required to reach full scale.",fg_bg=g_lg_fg_bg} - local s_vol_err = TextBox{parent=spkr_c,x=8,y=14,height=1,width=35,text_align=LEFT,text="Please set a volume.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local s_vol_err = TextBox{parent=spkr_c,x=8,y=14,height=1,width=35,text="Please set a volume.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_vol() local vol = tonumber(s_vol.get_value()) @@ -356,19 +357,19 @@ local function config_view(display) local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3}} - TextBox{parent=net_cfg,x=1,y=2,height=1,text_align=CENTER,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)} + TextBox{parent=net_cfg,x=1,y=2,height=1,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)} - TextBox{parent=net_c_1,x=1,y=1,height=1,text_align=CENTER,text="Please set the network channels below."} - TextBox{parent=net_c_1,x=1,y=3,height=4,text_align=CENTER,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,height=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=8,height=1,text_align=CENTER,text="Supervisor Channel"} + TextBox{parent=net_c_1,x=1,y=8,height=1,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_align=CENTER,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_1,x=1,y=11,height=1,text_align=CENTER,text="RTU Channel"} + 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,height=1,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_align=CENTER,text="[RTU_CHANNEL]",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_1,x=9,y=12,height=4,text="[RTU_CHANNEL]",fg_bg=g_lg_fg_bg} - local chan_err = TextBox{parent=net_c_1,x=8,y=14,height=1,width=35,text_align=LEFT,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local chan_err = TextBox{parent=net_c_1,x=8,y=14,height=1,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_channels() local svr_c = tonumber(svr_chan.get_value()) @@ -390,16 +391,16 @@ local function config_view(display) PushButton{parent=net_c_1,x=1,y=14,min_width=6,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,min_width=6,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,height=1,text_align=CENTER,text="Connection Timeout"} + TextBox{parent=net_c_2,x=1,y=1,height=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,fg_bg=bw_fg_bg} - TextBox{parent=net_c_2,x=9,y=2,height=2,text_align=CENTER,text="seconds (default 5)",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_2,x=1,y=3,height=4,text_align=CENTER,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=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_2,x=1,y=8,height=1,text_align=CENTER,text="Trusted Range"} + TextBox{parent=net_c_2,x=1,y=8,height=1,text="Trusted Range"} local range = NumberField{parent=net_c_2,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_digits=20,allow_decimal=true,fg_bg=bw_fg_bg} - TextBox{parent=net_c_2,x=1,y=10,height=4,text_align=CENTER,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_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} - local p2_err = TextBox{parent=net_c_2,x=8,y=14,height=1,width=35,text_align=LEFT,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local p2_err = TextBox{parent=net_c_2,x=8,y=14,height=1,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_ct_tr() local timeout_val = tonumber(timeout.get_value()) @@ -421,10 +422,10 @@ local function config_view(display) PushButton{parent=net_c_2,x=1,y=14,min_width=6,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,min_width=6,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_align=CENTER,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_align=CENTER,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 compution (can slow things down).",fg_bg=g_lg_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 compution (can slow things down).",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_3,x=1,y=11,height=1,text_align=CENTER,text="Facility Auth Key"} + TextBox{parent=net_c_3,x=1,y=11,height=1,text="Facility Auth Key"} local key, _, censor = 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} local function censor_key(enable) censor(util.trinary(enable, "*", nil)) end @@ -434,7 +435,7 @@ local function config_view(display) hide_key.set_value(true) censor_key(true) - local key_err = TextBox{parent=net_c_3,x=8,y=14,height=1,width=35,text_align=LEFT,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local key_err = TextBox{parent=net_c_3,x=8,y=14,height=1,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() @@ -454,20 +455,20 @@ local function config_view(display) local log_c_1 = Div{parent=log_cfg,x=2,y=4,width=49} - TextBox{parent=log_cfg,x=1,y=2,height=1,text_align=CENTER,text=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)} + TextBox{parent=log_cfg,x=1,y=2,height=1,text=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)} - TextBox{parent=log_c_1,x=1,y=1,height=1,text_align=CENTER,text="Please configure logging below."} + TextBox{parent=log_c_1,x=1,y=1,height=1,text="Please configure logging below."} - TextBox{parent=log_c_1,x=1,y=3,height=1,text_align=CENTER,text="Log File Mode"} + TextBox{parent=log_c_1,x=1,y=3,height=1,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} - TextBox{parent=log_c_1,x=1,y=7,height=1,text_align=CENTER,text="Log File Path"} + TextBox{parent=log_c_1,x=1,y=7,height=1,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} local en_dbg = CheckBox{parent=log_c_1,x=1,y=10,default=ini_cfg.LogDebug,label="Enable Logging Debug Messages",box_fg_bg=cpair(colors.pink,colors.black)} - TextBox{parent=log_c_1,x=3,y=11,height=2,text_align=CENTER,text="This results in much larger log files. It is best to only use this when there is a problem.",fg_bg=g_lg_fg_bg} + TextBox{parent=log_c_1,x=3,y=11,height=2,text="This results in much larger log files. It is best to only use this when there is a problem.",fg_bg=g_lg_fg_bg} - local path_err = TextBox{parent=log_c_1,x=8,y=14,height=1,width=35,text_align=LEFT,text="Please provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local path_err = TextBox{parent=log_c_1,x=8,y=14,height=1,width=35,text="Please provide a log file path.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_log() if path.get_value() ~= "" then @@ -497,21 +498,25 @@ local function config_view(display) local sum_c_4 = Div{parent=summary,x=2,y=4,width=49} local sum_c_5 = Div{parent=summary,x=2,y=4,width=49} local sum_c_6 = Div{parent=summary,x=2,y=4,width=49} + local sum_c_7 = Div{parent=summary,x=2,y=4,width=49} - local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4,sum_c_5,sum_c_6}} + local sum_pane = MultiPane{parent=summary,x=1,y=4,panes={sum_c_1,sum_c_2,sum_c_3,sum_c_4,sum_c_5,sum_c_6,sum_c_7}} - TextBox{parent=summary,x=1,y=2,height=1,text_align=CENTER,text=" Summary",fg_bg=cpair(colors.black,colors.green)} + TextBox{parent=summary,x=1,y=2,height=1,text=" Summary",fg_bg=cpair(colors.black,colors.green)} local setting_list = ListBox{parent=sum_c_1,x=1,y=1,height=12,width=51,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} local function back_from_settings() if tool_ctl.viewing_config or tool_ctl.importing_legacy then - main_pane.set_value(1) + if tool_ctl.importing_legacy and tool_ctl.importing_any_dc then + sum_pane.set_value(7) + else + tool_ctl.importing_legacy = false + tool_ctl.go_home() + end + tool_ctl.viewing_config = false - tool_ctl.importing_legacy = false - else - main_pane.set_value(4) - end + else main_pane.set_value(4) end end ---@param element graphics_element @@ -564,23 +569,23 @@ local function config_view(display) tool_ctl.settings_confirm = PushButton{parent=sum_c_1,x=41,y=14,min_width=9,text="Confirm",callback=function()sum_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg} tool_ctl.settings_confirm.hide() - TextBox{parent=sum_c_2,x=1,y=1,height=1,text_align=CENTER,text="The following peripherals will be imported:"} + TextBox{parent=sum_c_2,x=1,y=1,height=1,text="The following peripherals will be imported:"} local peri_import_list = ListBox{parent=sum_c_2,x=1,y=3,height=10,width=51,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} PushButton{parent=sum_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()sum_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=sum_c_2,x=41,y=14,min_width=9,text="Confirm",callback=function()sum_pane.set_value(3)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg} - TextBox{parent=sum_c_3,x=1,y=1,height=1,text_align=CENTER,text="The following redstone entries will be imported:"} + TextBox{parent=sum_c_3,x=1,y=1,height=1,text="The following redstone entries will be imported:"} local rs_import_list = ListBox{parent=sum_c_3,x=1,y=3,height=10,width=51,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} PushButton{parent=sum_c_3,x=1,y=14,min_width=6,text="\x1b Back",callback=function()sum_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=sum_c_3,x=43,y=14,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg} - TextBox{parent=sum_c_4,x=1,y=1,height=1,text_align=CENTER,text="Settings saved!"} + TextBox{parent=sum_c_4,x=1,y=1,height=1,text="Settings saved!"} PushButton{parent=sum_c_4,x=1,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=sum_c_4,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} - TextBox{parent=sum_c_5,x=1,y=1,height=2,text_align=CENTER,text="The old config.lua file will now be deleted, then the configurator will exit."} + TextBox{parent=sum_c_5,x=1,y=1,height=2,text="The old config.lua file will now be deleted, then the configurator will exit."} local function delete_legacy() fs.delete("/rtu/config.lua") @@ -590,17 +595,22 @@ local function config_view(display) PushButton{parent=sum_c_5,x=1,y=14,min_width=8,text="Cancel",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=sum_c_5,x=44,y=14,min_width=6,text="OK",callback=delete_legacy,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.gray)} - TextBox{parent=sum_c_6,x=1,y=1,height=5,text_align=CENTER,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."} + TextBox{parent=sum_c_6,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."} PushButton{parent=sum_c_6,x=1,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=sum_c_6,x=44,y=14,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} + TextBox{parent=sum_c_7,x=1,y=1,height=8,text="Warning!\n\nSome of the devices in your old config file aren't currently connected. If the device isn't connected, the options can't be properly validated. Please either connect your devices and try again or complete the import without validation on those entry's settings."} + TextBox{parent=sum_c_7,x=1,y=10,height=3,text="Afterwards, either (a) edit then save entries for currently disconnected devices to properly configure or (b) delete those entries."} + PushButton{parent=sum_c_7,x=1,y=14,min_width=6,text="\x1b Back",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=sum_c_7,x=41,y=14,min_width=9,text="Confirm",callback=function()sum_pane.set_value(1)end,fg_bg=cpair(colors.black,colors.orange),active_fg_bg=btn_act_fg_bg} + --#endregion --#region CONFIG CHANGE LOG local cl = Div{parent=changelog,x=2,y=4,width=49} - TextBox{parent=changelog,x=1,y=2,height=1,text_align=CENTER,text=" Config Change Log",fg_bg=bw_fg_bg} + TextBox{parent=changelog,x=1,y=2,height=1,text=" Config Change Log",fg_bg=bw_fg_bg} local c_log = ListBox{parent=cl,x=1,y=1,height=12,width=51,scroll_height=100,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} @@ -629,7 +639,7 @@ local function config_view(display) local peri_pane = MultiPane{parent=peri_cfg,x=1,y=4,panes={peri_c_1,peri_c_2,peri_c_3,peri_c_4,peri_c_5,peri_c_6,peri_c_7}} - TextBox{parent=peri_cfg,x=1,y=2,height=1,text_align=CENTER,text=" Peripheral Connections",fg_bg=cpair(colors.black,colors.purple)} + TextBox{parent=peri_cfg,x=1,y=2,height=1,text=" Peripheral Connections",fg_bg=cpair(colors.black,colors.purple)} local peri_list = ListBox{parent=peri_c_1,x=1,y=1,height=12,width=51,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} @@ -785,11 +795,11 @@ local function config_view(display) tool_ctl.update_peri_list() - TextBox{parent=peri_c_3,x=1,y=1,height=4,text_align=CENTER,text="This feature is intended for advanced users. If you are clicking this just because your device is not shown, follow the connection instructions in 'I don't see my device!'."} - TextBox{parent=peri_c_3,x=1,y=6,height=4,text_align=CENTER,text="Peripheral Name"} + TextBox{parent=peri_c_3,x=1,y=1,height=4,text="This feature is intended for advanced users. If you are clicking this just because your device is not shown, follow the connection instructions in 'I don't see my device!'."} + TextBox{parent=peri_c_3,x=1,y=6,height=4,text="Peripheral Name"} local p_name = TextField{parent=peri_c_3,x=1,y=7,width=49,height=1,max_len=128,fg_bg=bw_fg_bg} local p_type = Radio2D{parent=peri_c_3,x=1,y=9,rows=4,columns=2,default=1,options=RTU_DEV_TYPES,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.purple} - local man_p_err = TextBox{parent=peri_c_3,x=8,y=14,height=1,width=35,text_align=LEFT,text="Please enter a peripheral name.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local man_p_err = TextBox{parent=peri_c_3,x=8,y=14,height=1,width=35,text="Please enter a peripheral name.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} man_p_err.hide(true) local function submit_manual_peri() @@ -804,11 +814,11 @@ local function config_view(display) PushButton{parent=peri_c_3,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=peri_c_3,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_manual_peri,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - tool_ctl.p_name_msg = TextBox{parent=peri_c_4,x=1,y=1,height=2,text_align=CENTER,text=""} - tool_ctl.p_prompt = TextBox{parent=peri_c_4,x=1,y=4,height=2,text_align=CENTER,text=""} + tool_ctl.p_name_msg = TextBox{parent=peri_c_4,x=1,y=1,height=2,text=""} + tool_ctl.p_prompt = TextBox{parent=peri_c_4,x=1,y=4,height=2,text=""} tool_ctl.p_idx = NumberField{parent=peri_c_4,x=14,y=4,width=4,max_digits=2,min=1,max=2,default=1,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} tool_ctl.p_assign_btn = RadioButton{parent=peri_c_4,x=1,y=5,default=1,options={"the facility.","a unit. (unit #"},callback=function(v)tool_ctl.p_assign(v)end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.purple} - tool_ctl.p_assign_end = TextBox{parent=peri_c_4,x=22,y=6,height=6,width=1,text_align=LEFT,text=")"} + tool_ctl.p_assign_end = TextBox{parent=peri_c_4,x=22,y=6,height=6,width=1,text=")"} tool_ctl.p_unit = NumberField{parent=peri_c_4,x=44,y=4,width=4,max_digits=2,min=1,max=4,default=1,fg_bg=bw_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} tool_ctl.p_unit.disable() @@ -826,10 +836,10 @@ local function config_view(display) end end - tool_ctl.p_desc = TextBox{parent=peri_c_4,x=1,y=7,height=6,text_align=LEFT,text="",fg_bg=g_lg_fg_bg} - tool_ctl.p_desc_ext = TextBox{parent=peri_c_4,x=1,y=6,height=7,text_align=LEFT,text="",fg_bg=g_lg_fg_bg} + tool_ctl.p_desc = TextBox{parent=peri_c_4,x=1,y=7,height=6,text="",fg_bg=g_lg_fg_bg} + tool_ctl.p_desc_ext = TextBox{parent=peri_c_4,x=1,y=6,height=7,text="",fg_bg=g_lg_fg_bg} - tool_ctl.p_err = TextBox{parent=peri_c_4,x=8,y=14,height=1,width=35,text_align=LEFT,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + tool_ctl.p_err = TextBox{parent=peri_c_4,x=8,y=14,height=1,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} tool_ctl.p_err.hide(true) local function back_from_peri_opts() @@ -913,11 +923,11 @@ local function config_view(display) PushButton{parent=peri_c_4,x=1,y=14,min_width=6,text="\x1b Back",callback=back_from_peri_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=peri_c_4,x=41,y=14,min_width=9,text="Confirm",callback=save_peri_entry,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg} - TextBox{parent=peri_c_5,x=1,y=1,height=1,text_align=CENTER,text="Settings saved!"} + TextBox{parent=peri_c_5,x=1,y=1,height=1,text="Settings saved!"} PushButton{parent=peri_c_5,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=peri_c_5,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=peri_c_6,x=1,y=1,height=5,text_align=CENTER,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."} + TextBox{parent=peri_c_6,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."} PushButton{parent=peri_c_6,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=peri_c_6,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} @@ -934,7 +944,7 @@ local function config_view(display) local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5,rs_c_6}} - TextBox{parent=rs_cfg,x=1,y=2,height=1,text_align=CENTER,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)} + TextBox{parent=rs_cfg,x=1,y=2,height=1,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)} TextBox{parent=rs_c_1,x=1,y=1,height=1,text=" port side/color unit/facility",fg_bg=g_lg_fg_bg} local rs_list = ListBox{parent=rs_c_1,x=1,y=2,height=11,width=51,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} @@ -961,7 +971,7 @@ local function config_view(display) PushButton{parent=rs_c_1,x=35,y=14,min_width=7,text="New +",callback=function()rs_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg} PushButton{parent=rs_c_1,x=43,y=14,min_width=7,text="Apply",callback=rs_apply,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg} - TextBox{parent=rs_c_6,x=1,y=1,height=5,text_align=CENTER,text="You already configured this input. There can only be one entry for each input.\n\nPlease select a different port."} + TextBox{parent=rs_c_6,x=1,y=1,height=5,text="You already configured this input. There can only be one entry for each input.\n\nPlease select a different port."} PushButton{parent=rs_c_6,x=1,y=14,min_width=6,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} TextBox{parent=rs_c_2,x=1,y=1,height=1,text="Select one of the below ports to use."} @@ -1024,12 +1034,12 @@ local function config_view(display) PushButton{parent=rs_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - tool_ctl.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=1,text_align=CENTER,text=""} + tool_ctl.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=1,text=""} - tool_ctl.rs_cfg_unit_l = TextBox{parent=rs_c_3,x=27,y=3,width=7,height=1,text_align=CENTER,text="Unit ID"} + tool_ctl.rs_cfg_unit_l = TextBox{parent=rs_c_3,x=27,y=3,width=7,height=1,text="Unit ID"} tool_ctl.rs_cfg_unit = NumberField{parent=rs_c_3,x=27,y=4,width=10,max_digits=2,min=1,max=4,fg_bg=bw_fg_bg} - TextBox{parent=rs_c_3,x=1,y=3,width=11,height=1,text_align=CENTER,text="Output Side"} + TextBox{parent=rs_c_3,x=1,y=3,width=11,height=1,text="Output Side"} local side = Radio2D{parent=rs_c_3,x=1,y=4,rows=2,columns=3,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red} local function set_bundled(bundled) @@ -1043,7 +1053,7 @@ local function config_view(display) tool_ctl.rs_cfg_color = Radio2D{parent=rs_c_3,x=1,y=9,rows=4,columns=4,default=1,options=color_options,radio_colors=cpair(colors.lightGray,colors.black),color_map=color_options_map,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg} tool_ctl.rs_cfg_color.disable() - local rs_err = TextBox{parent=rs_c_3,x=8,y=14,height=1,width=35,text_align=LEFT,text="Unit ID must be within 1 through 4.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local rs_err = TextBox{parent=rs_c_3,x=8,y=14,height=1,width=35,text="Unit ID must be within 1 through 4.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} rs_err.hide(true) local function back_from_rs_opts() @@ -1098,11 +1108,11 @@ local function config_view(display) PushButton{parent=rs_c_3,x=1,y=14,min_width=6,text="\x1b Back",callback=back_from_rs_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=rs_c_3,x=41,y=14,min_width=9,text="Confirm",callback=save_rs_entry,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg} - TextBox{parent=rs_c_4,x=1,y=1,height=1,text_align=CENTER,text="Settings saved!"} + TextBox{parent=rs_c_4,x=1,y=1,height=1,text="Settings saved!"} PushButton{parent=rs_c_4,x=1,y=14,min_width=6,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=rs_c_4,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=rs_c_5,x=1,y=1,height=5,text_align=CENTER,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."} + TextBox{parent=rs_c_5,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."} PushButton{parent=rs_c_5,x=1,y=14,min_width=6,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=rs_c_5,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} @@ -1114,6 +1124,8 @@ local function config_view(display) function tool_ctl.load_legacy() local config = require("rtu.config") + tool_ctl.importing_any_dc = false + tmp_cfg.SpeakerVolume = config.SOUNDER_VOLUME tmp_cfg.SVR_Channel = config.SVR_CHANNEL tmp_cfg.RTU_Channel = config.RTU_CHANNEL @@ -1124,11 +1136,62 @@ local function config_view(display) tmp_cfg.LogPath = config.LOG_PATH tmp_cfg.LogDebug = config.LOG_DEBUG or false + local mounts = ppm.list_mounts() + peri_import_list.remove_all() for _, entry in ipairs(config.RTU_DEVICES) do - if entry.for_reactor == 0 then entry.for_reactor = nil end + local for_facility = entry.for_reactor == 0 + local ini_unit = util.trinary(for_facility, nil, entry.for_reactor) + + local def = { name = entry.name, unit = ini_unit, index = entry.index } + local mount = mounts[def.name] ---@type ppm_entry|nil + + local status = " \x1a not connected, please re-config later" + local color = colors.orange + + if mount ~= nil then + -- lets make sure things are valid + local unit, index, err = nil, nil, false + local u, idx = def.unit, def.index + + if util.table_contains(NEEDS_UNIT, mount.type) then + if (mount.type == "dynamicValve" or mount.type == "environmentDetector") and for_facility then + -- skip + elseif not (util.is_int(u) and u > 0 and u < 5) then + err = true + else unit = u end + end + + if mount.type == "boilerValve" then + if not (idx == 1 or idx == 2) then + err = true + else index = idx end + elseif mount.type == "turbineValve" then + if not (idx == 1 or idx == 2 or idx == 3) then + err = true + else index = idx end + elseif mount.type == "dynamicValve" and for_facility then + if not (util.is_int(idx) and idx > 0 and idx < 5) then + err = true + else index = idx end + elseif mount.type == "dynamicValve" then + index = 1 + elseif mount.type == "environmentDetector" then + if not (util.is_int(idx) and idx > 0) then + err = true + else index = idx end + end + + if err then + status = " \x1a invalid, please re-config later" + else + def.index = index + def.unit = unit + status = " \x1a validated" + color = colors.green + end + else tool_ctl.importing_any_dc = true end - local def = { name = entry.name, unit = entry.for_reactor, index = entry.index } table.insert(tmp_cfg.Peripherals, def) local desc = " \x1a " @@ -1143,9 +1206,10 @@ local function config_view(display) desc = desc .. "for the facility" end - local line = Div{parent=peri_import_list,height=2} + local line = Div{parent=peri_import_list,height=3} TextBox{parent=line,x=1,y=1,height=1,text="@ "..def.name,fg_bg=cpair(colors.black,colors.white)} - TextBox{parent=line,x=1,y=2,height=1,text=desc,fg_bg=cpair(colors.gray,colors.white)} + TextBox{parent=line,x=1,y=2,height=1,text=status,fg_bg=cpair(color,colors.white)} + TextBox{parent=line,x=1,y=3,height=1,text=desc,fg_bg=cpair(colors.gray,colors.white)} end rs_import_list.remove_all() @@ -1172,7 +1236,7 @@ local function config_view(display) end tool_ctl.gen_summary(tmp_cfg) - sum_pane.set_value(1) + if tool_ctl.importing_any_dc then sum_pane.set_value(7) else sum_pane.set_value(1) end main_pane.set_value(5) tool_ctl.settings_apply.hide(true) tool_ctl.settings_confirm.show() @@ -1181,6 +1245,10 @@ local function config_view(display) -- go back to the home page function tool_ctl.go_home() + tool_ctl.viewing_config = false + tool_ctl.importing_legacy = false + tool_ctl.importing_any_dc = false + main_pane.set_value(1) net_pane.set_value(1) sum_pane.set_value(1) From 885932afe1e9a2d6cc56145b6ae4885edd024d88 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 Nov 2023 18:35:46 -0500 Subject: [PATCH 32/36] don't try to log if log.init wasn't called --- scada-common/log.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scada-common/log.lua b/scada-common/log.lua index 8a42e45..f876eaa 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -13,6 +13,7 @@ local MODE = { APPEND = 0, NEW = 1 } log.MODE = MODE local logger = { + not_ready = true, path = "/log.txt", mode = MODE.APPEND, debug = false, @@ -32,6 +33,8 @@ local free_space = fs.getFreeSpace -- private log write function ---@param msg string local function _log(msg) + if logger.not_ready then return end + local out_of_space = false local time_stamp = os.date("[%c] ") local stamped = time_stamp .. util.strval(msg) @@ -94,6 +97,8 @@ function log.init(path, write_mode, include_debug, dmesg_redirect) else logger.dmesg_out = term.current() end + + logger.not_ready = false end -- close the log file handle From 785dea6545970819e3ded2d828f06bdc782607d6 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 Nov 2023 18:36:16 -0500 Subject: [PATCH 33/36] #306 fixed incorrect screenflow and changed peripheral import validation symbols --- rtu/configure.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rtu/configure.lua b/rtu/configure.lua index 31b28a8..c674826 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -578,7 +578,7 @@ local function config_view(display) TextBox{parent=sum_c_3,x=1,y=1,height=1,text="The following redstone entries will be imported:"} local rs_import_list = ListBox{parent=sum_c_3,x=1,y=3,height=10,width=51,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)} - PushButton{parent=sum_c_3,x=1,y=14,min_width=6,text="\x1b Back",callback=function()sum_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=sum_c_3,x=1,y=14,min_width=6,text="\x1b Back",callback=function()sum_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=sum_c_3,x=43,y=14,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg} TextBox{parent=sum_c_4,x=1,y=1,height=1,text="Settings saved!"} @@ -1146,7 +1146,7 @@ local function config_view(display) local def = { name = entry.name, unit = ini_unit, index = entry.index } local mount = mounts[def.name] ---@type ppm_entry|nil - local status = " \x1a not connected, please re-config later" + local status = " \x13 not connected, please re-config later" local color = colors.orange if mount ~= nil then @@ -1183,11 +1183,11 @@ local function config_view(display) end if err then - status = " \x1a invalid, please re-config later" + status = " \x13 invalid, please re-config later" else def.index = index def.unit = unit - status = " \x1a validated" + status = " \x04 validated" color = colors.green end else tool_ctl.importing_any_dc = true end From 4a2199fa13ec793a84a612d698f1ccc3a43edd62 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 14 Nov 2023 19:40:29 -0500 Subject: [PATCH 34/36] readme update --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c1405dc..2f727be 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,9 @@ Mod Requirements: Mod Recommendations: - Advanced Peripherals (adds the capability to detect environmental radiation levels) +- Immersive Engineering (provides bundled redstone, though any mod containing bundled redstone will do) -v10.1+ is required due the complete support of CC:Tweaked added in Mekanism v10.1 +v10.1+ is required due to the complete support of CC:Tweaked added in Mekanism v10.1 ## Installation From 8fe0321ac0dfed45fc3cd79885a1fc9074e5160e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 14 Nov 2023 19:40:55 -0500 Subject: [PATCH 35/36] fixed RTU authkey check --- rtu/startup.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtu/startup.lua b/rtu/startup.lua index 5e8b304..58ed080 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.7.0" +local RTU_VERSION = "v1.7.1" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE @@ -84,7 +84,7 @@ local function main() ppm.mount_all() -- message authentication init - if type(config.AuthKey) == "string" then + if type(config.AuthKey) == "string" and string.len(config.AuthKey) > 0 then network.init_mac(config.AuthKey) end From 1fdf012f651d978980df42c541a13d445066473e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 14 Nov 2023 22:00:01 -0500 Subject: [PATCH 36/36] properly clear peripherals and redstone when importing --- rtu/configure.lua | 2 ++ rtu/startup.lua | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/rtu/configure.lua b/rtu/configure.lua index c674826..4a55b0a 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -1135,6 +1135,8 @@ local function config_view(display) tmp_cfg.LogMode = config.LOG_MODE tmp_cfg.LogPath = config.LOG_PATH tmp_cfg.LogDebug = config.LOG_DEBUG or false + tmp_cfg.Peripherals = {} + tmp_cfg.Redstone = {} local mounts = ppm.list_mounts() diff --git a/rtu/startup.lua b/rtu/startup.lua index 58ed080..e31e678 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.7.1" +local RTU_VERSION = "v1.7.2" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE