From 5d4fc362561d1bb8824312db5aa290a6c4de5f91 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 18 Dec 2023 15:23:51 -0500 Subject: [PATCH 01/18] #308 WIP supervisor configurator --- configure.lua | 4 +- supervisor/configure.lua | 848 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 850 insertions(+), 2 deletions(-) create mode 100644 supervisor/configure.lua diff --git a/configure.lua b/configure.lua index 972d16e..b24a5e8 100644 --- a/configure.lua +++ b/configure.lua @@ -4,8 +4,8 @@ if fs.exists("reactor-plc/configure.lua") then require("reactor-plc.configure").configure() 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("supervisor/configure.lua") then + require("supervisor.configure").configure() elseif fs.exists("coordinator/startup.lua") then print("CONFIGURE> COORDINATOR CONFIGURATOR NOT YET IMPLEMENTED IN BETA") elseif fs.exists("pocket/startup.lua") then diff --git a/supervisor/configure.lua b/supervisor/configure.lua new file mode 100644 index 0000000..473f23e --- /dev/null +++ b/supervisor/configure.lua @@ -0,0 +1,848 @@ +-- +-- Configuration GUI +-- + +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") + +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 PipeNet = require("graphics.elements.pipenet") +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 tri = util.trinary + +local cpair = core.cpair + +local LEFT = core.ALIGN.LEFT +local CENTER = core.ALIGN.CENTER +local RIGHT = core.ALIGN.RIGHT + +log.init("log.txt", log.MODE.APPEND, true) + +-- changes to the config data/format to let the user know +local changes = {} + +---@class svr_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_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 = "", + + cooling_elems = {}, + tank_elems = {} +} + +---@class svr_config +local tmp_cfg = { + UnitCount = 1, + CoolingConfig = {}, + FacilityTankMode = 0, + FacilityTankDefs = nil, + SVR_Channel = nil, + PLC_Channel = nil, + RTU_Channel = nil, + CRD_Channel = nil, + PKT_Channel = nil, + PLC_Timeout = nil, + RTU_Timeout = nil, + CRD_Timeout = nil, + PKT_Timeout = nil, + TrustedRange = nil, + AuthKey = nil, + LogMode = 0, + LogPath = "", + LogDebug = false, +} + +---@class svr_config +local ini_cfg = {} +---@class svr_config +local settings_cfg = {} + +-- all settings fields, their nice names, and their default values +local fields = { + { "UnitCount", "Number of Reactors", 1 }, + { "CoolingConfig", "Cooling Configuration", {} }, + { "FacilityTankMode", "Facility Tank Mode", 0 }, + { "FacilityTankDefs", "Facility Tank Definitions", {} }, + { "SVR_Channel", "SVR Channel", 16240 }, + { "PLC_Channel", "PLC Channel", 16241 }, + { "RTU_Channel", "RTU Channel", 16242 }, + { "CRD_Channel", "CRD Channel", 16243 }, + { "PKT_Channel", "PKT Channel", 16244 }, + { "PLC_Timeout", "PLC Connection Timeout", 5 }, + { "RTU_Timeout", "RTU Connection Timeout", 5 }, + { "CRD_Timeout", "CRD Connection Timeout", 5 }, + { "PKT_Timeout", "PKT 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 } +} + +-- load data from the settings file +---@param target svr_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 + + local loaded = settings.load("/supervisor.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 +---@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="Supervisor 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 svr_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 main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,svr_cfg,net_cfg,log_cfg,summary,changelog}} + + -- MAIN PAGE + + local y_start = 5 + + TextBox{parent=main_page,x=2,y=2,height=2,text="Welcome to the Supervisor 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="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(settings_cfg) + tool_ctl.settings_apply.hide(true) + main_pane.set_value(5) + end + + if fs.exists("/supervisor/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=18,text="Configure System",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_cfg = PushButton{parent=main_page,x=2,y=y_start+2,min_width=20,text="View 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)} + + if not tool_ctl.has_config then tool_ctl.view_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} + + -- SUPERVISOR CONFIG + + local svr_c_1 = Div{parent=svr_cfg,x=2,y=4,width=49} + local svr_c_2 = Div{parent=svr_cfg,x=2,y=4,width=49} + local svr_c_3 = Div{parent=svr_cfg,x=2,y=4,width=49} + local svr_c_4 = Div{parent=svr_cfg,x=2,y=4,width=49} + local svr_c_5 = Div{parent=svr_cfg,x=2,y=4,width=49} + + local svr_pane = MultiPane{parent=svr_cfg,x=1,y=4,panes={svr_c_1,svr_c_2,svr_c_3,svr_c_4,svr_c_5}} + + TextBox{parent=svr_cfg,x=1,y=2,height=1,text=" Facility Configuration",fg_bg=cpair(colors.black,colors.green)} + + TextBox{parent=svr_c_1,x=1,y=1,height=3,text="Please enter the number of reactors you have, also referred to as reactor units or 'units' for short. A maximum of 4 is currently supported."} + local num_units = NumberField{parent=svr_c_1,x=1,y=5,width=5,max_digits=2,default=ini_cfg.UnitCount,min=1,max=4,fg_bg=bw_fg_bg} + TextBox{parent=svr_c_1,x=7,y=5,height=1,text="reactors"} + + local nu_error = TextBox{parent=svr_c_1,x=8,y=14,height=1,width=35,text="Please set the number of reactors.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + + local function submit_num_units() + local count = tonumber(num_units.get_value()) + if count ~= nil and count > 0 and count < 5 then + nu_error.hide(true) + tmp_cfg.UnitCount = count + + local confs = tool_ctl.cooling_elems + if count >= 2 then confs[2].line.show() else confs[2].line.hide(true) end + if count >= 3 then confs[3].line.show() else confs[3].line.hide(true) end + if count == 4 then confs[4].line.show() else confs[4].line.hide(true) end + + svr_pane.set_value(2) + else nu_error.show() end + end + + PushButton{parent=svr_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=svr_c_1,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_num_units,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + TextBox{parent=svr_c_2,x=1,y=1,height=4,text="Please provide the reactor cooling configuration below. This includes the number of turbines, boilers, and if that reactor has a connection to a dynamic tank for emergency coolant."} + TextBox{parent=svr_c_2,x=1,y=6,height=1,text="UNIT TURBINES BOILERS HAS TANK CONNECTION?",fg_bg=g_lg_fg_bg} + + for i = 1, 4 do + local num_t, num_b, has_t = 1, 0, false + + if ini_cfg.CoolingConfig[1] then + local conf = ini_cfg.CoolingConfig[1] + if util.is_int(conf.TurbineCount) then num_t = math.min(3, math.max(1, conf.TurbineCount or 1)) end + if util.is_int(conf.BoilerCount) then num_b = math.min(2, math.max(1, conf.BoilerCount or 0)) end + has_t = conf.TankConnection == true + end + + local line = Div{parent=svr_c_2,x=1,y=7+i,height=1} + + TextBox{parent=line,text="Unit "..i,width=6} + local turbines = NumberField{parent=line,x=9,y=1,width=5,max_digits=2,default=num_t,min=1,max=3,fg_bg=bw_fg_bg} + local boilers = NumberField{parent=line,x=20,y=1,width=5,max_digits=2,default=num_b,min=0,max=2,fg_bg=bw_fg_bg} + local tank = CheckBox{parent=line,x=30,y=1,label="Is Connected",default=has_t,box_fg_bg=cpair(colors.green,colors.black)} + + tool_ctl.cooling_elems[i] = { line = line, turbines = turbines, boilers = boilers, tank = tank } + end + + local cool_err = TextBox{parent=svr_c_2,x=8,y=14,height=1,width=33,text="Please fill out all fields.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + + local function submit_cooling() + local any_missing = false + for i = 1, tmp_cfg.UnitCount do + local conf = tool_ctl.cooling_elems[i] + any_missing = any_missing or (tonumber(conf.turbines.get_value()) == nil) + any_missing = any_missing or (tonumber(conf.boilers.get_value()) == nil) + end + + if any_missing then + cool_err.show() + else + local any_has_tank = false + tool_ctl.num_tank_conns = 0 + + tmp_cfg.CoolingConfig = {} + for i = 1, tmp_cfg.UnitCount do + local conf = tool_ctl.cooling_elems[i] + tmp_cfg.CoolingConfig[i] = { TurbineCount = tonumber(conf.turbines.get_value()), BoilerCount = tonumber(conf.boilers.get_value()), TankConnection = conf.tank.get_value() } + if conf.tank.get_value() then + any_has_tank = true + tool_ctl.num_tank_conns = tool_ctl.num_tank_conns + 1 + end + end + + for i = 1, 4 do + local elem = tool_ctl.tank_elems[i] + if i <= tmp_cfg.UnitCount then + elem.div.show() + if tmp_cfg.CoolingConfig[i].TankConnection then + elem.no_tank.hide() + elem.tank_opt.show() + else + elem.tank_opt.hide(true) + elem.no_tank.show() + end + else elem.div.hide(true) end + end + + -- if any_has_tank then svr_pane.set_value(3) else main_pane.set_value(3) end + svr_pane.set_value(3) + end + end + + PushButton{parent=svr_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()svr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=svr_c_2,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_cooling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + TextBox{parent=svr_c_3,x=1,y=1,height=6,text="You have set one or more of your units to use dynamic tanks for emergency coolant. You have two paths for configuration. The first is to assign dynamic tanks to reactor units; one tank per reactor, only connected to that reactor. RTU configurations must also assign it as such."} + TextBox{parent=svr_c_3,x=1,y=8,height=3,text="Alternatively, you can configure them as facility tanks to connect to multiple reactor units. These can intermingle with unit-specific tanks."} + + local en_fac_tanks = CheckBox{parent=svr_c_3,x=1,y=12,label="Use Facility Dynamic Tanks",default=ini_cfg.FacilityTankMode~=0,box_fg_bg=cpair(colors.green,colors.black)} + + local function submit_en_fac_tank() + -- if en_fac_tanks.get_value() then + -- assert(tool_ctl.num_tank_conns >= 1, "attempted to enable facility tanks with no tank connections assigned") + -- if tool_ctl.num_tank_conns == 1 then + -- -- nothing special for the user to do, set it automatically + -- tmp_cfg.FacilityTankMode = 1 + -- tmp_cfg.FacilityTankDefs = { 2 } + -- main_pane.set_value(3) + -- else + -- svr_pane.set_value(4) + -- end + -- else + -- tmp_cfg.FacilityTankMode = 0 + -- tmp_cfg.FacilityTankDefs = {} + -- main_pane.set_value(3) + -- end + + svr_pane.set_value(4) -- for testing + end + + PushButton{parent=svr_c_3,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=svr_c_3,x=44,y=14,text="Next \x1a",callback=submit_en_fac_tank,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + TextBox{parent=svr_c_4,x=1,y=1,height=4,text="Please set unit connections to dynamic tanks, selecting at least one facility tank. The layout for facility tanks will be configured next."} + + for i = 1, 4 do + local div = Div{parent=svr_c_4,x=1,y=3+(2*i),height=2} + + TextBox{parent=div,x=1,y=1,width=33,height=1,text="Unit "..i.." will be connected to..."} + TextBox{parent=div,x=6,y=2,width=3,height=1,text="..."} + local tank_opt = Radio2D{parent=div,x=10,y=2,rows=1,columns=2,default=2,options={"its own Unit Tank","a Facility Tank"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.green,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg} + local no_tank = TextBox{parent=div,x=9,y=2,width=34,height=1,text="no tank (as you set two steps ago)",fg_bg=cpair(colors.gray,colors.lightGray),hidden=true} + + tool_ctl.tank_elems[i] = { div = div, tank_opt = tank_opt, no_tank = no_tank } + end + + local tank_err = TextBox{parent=svr_c_4,x=8,y=14,height=1,width=33,text="You selected no facility tanks.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + + local function submit_tank_defs() + local any_fac = false + + tmp_cfg.FacilityTankDefs = {} + for i = 1, tmp_cfg.UnitCount do + if tmp_cfg.CoolingConfig[i].TankConnection then + tmp_cfg.FacilityTankDefs[i] = tool_ctl.tank_elems[i].tank_opt.get_value() + any_fac = any_fac or (tmp_cfg.FacilityTankDefs[i] == 2) + else tmp_cfg.FacilityTankDefs[i] = 0 end + end + + -- if any_fac then + -- tank_err.hide(true) + -- svr_pane.set_value(5) + -- else tank_err.show() end + svr_pane.set_value(5) + end + + PushButton{parent=svr_c_4,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=svr_c_4,x=44,y=14,text="Next \x1a",callback=submit_tank_defs,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + TextBox{parent=svr_c_5,x=1,y=1,height=1,text="Please select your dynamic tank layout."} + TextBox{parent=svr_c_5,x=12,y=3,height=1,text="Facility Tanks Unit Tanks",fg_bg=g_lg_fg_bg} + local vis = Div{parent=svr_c_5,x=15,y=5} + local tanks = TextBox{parent=vis,x=1,y=1,width=6,height=7,text="Tank A"} + local units = TextBox{parent=vis,x=14,y=1,width=6,height=7,text="Unit 1\n\nUnit 2\n\nUnit 3\n\nUnit 4"} + + for i = 1, 4 do + local line = Div{parent=vis,x=21,y=(i*2)-1,width=12,height=1} + TextBox{parent=line,width=5,height=1,text="\x8c\x8c\x8c\x8c\x8c",fg_bg=cpair(colors.blue,colors.lightGray)} + local label = TextBox{parent=line,x=7,y=1,width=6,height=1,text="Tank ?"} + end + + local m1_pipes = { core.pipe(0,0,4,0,colors.blue,true), core.pipe(3,0,4,2,colors.blue,true), core.pipe(3,2,4,4,colors.blue,true), core.pipe(3,4,4,6,colors.blue,true) } + local m2_pipes = { core.pipe(0,0,4,0,colors.blue,true), core.pipe(3,0,4,2,colors.blue,true), core.pipe(3,2,4,4,colors.blue,true), core.pipe(0,6,4,6,colors.blue,true) } + local m3_pipes = { core.pipe(0,0,4,0,colors.blue,true), core.pipe(3,0,4,2,colors.blue,true), core.pipe(0,4,4,4,colors.blue,true), core.pipe(3,4,4,6,colors.blue,true) } + local m4_pipes = { core.pipe(0,0,4,0,colors.blue,true), core.pipe(0,2,4,2,colors.blue,true), core.pipe(3,2,4,4,colors.blue,true), core.pipe(3,4,4,6,colors.blue,true) } + local m5_pipes = { core.pipe(0,0,4,0,colors.blue,true), core.pipe(3,0,4,2,colors.blue,true), core.pipe(0,4,4,4,colors.blue,true), core.pipe(0,6,4,6,colors.blue,true) } + local m6_pipes = { core.pipe(0,0,4,0,colors.blue,true), core.pipe(0,2,4,2,colors.blue,true), core.pipe(3,2,4,4,colors.blue,true), core.pipe(0,6,4,6,colors.blue,true) } + local m7_pipes = { core.pipe(0,0,4,0,colors.blue,true), core.pipe(0,2,4,2,colors.blue,true), core.pipe(0,4,4,4,colors.blue,true), core.pipe(3,4,4,6,colors.blue,true) } + local m8_pipes = { core.pipe(0,0,4,0,colors.blue,true), core.pipe(0,2,4,2,colors.blue,true), core.pipe(0,4,4,4,colors.blue,true), core.pipe(0,6,4,6,colors.blue,true) } + + local pnet_m1 = PipeNet{parent=vis,x=8,y=1,width=5,height=7,pipes=m1_pipes} + local pnet_m2 = PipeNet{parent=vis,x=8,y=1,width=5,height=7,pipes=m2_pipes} + local pnet_m3 = PipeNet{parent=vis,x=8,y=1,width=5,height=7,pipes=m3_pipes} + local pnet_m4 = PipeNet{parent=vis,x=8,y=1,width=5,height=7,pipes=m4_pipes} + local pnet_m5 = PipeNet{parent=vis,x=8,y=1,width=5,height=7,pipes=m5_pipes} + local pnet_m6 = PipeNet{parent=vis,x=8,y=1,width=5,height=7,pipes=m6_pipes} + local pnet_m7 = PipeNet{parent=vis,x=8,y=1,width=5,height=7,pipes=m7_pipes} + local pnet_m8 = PipeNet{parent=vis,x=8,y=1,width=5,height=7,pipes=m8_pipes} + + local pipe_pane = MultiPane{parent=vis,x=8,y=1,width=5,height=7,panes={pnet_m1,pnet_m2,pnet_m3,pnet_m4,pnet_m5,pnet_m6,pnet_m7,pnet_m8}} + + local hide_pipes_1u = Div{parent=vis,x=1,y=2,width=19,height=6,hidden=true} + + local function show_pipes(val) + local text = { + "Tank A", + "Tank A\n\n\n\n\n\nTank B", + "Tank A\n\n\n\nTank B", + "Tank A\n\nTank B", + "Tank A\n\n\n\nTank B\n\nTank C", + "Tank A\n\nTank B\n\n\n\nTank C", + "Tank A\n\nTank B\n\nTank C", + "Tank A\n\nTank B\n\nTank C\n\nTank D" + } + + tanks.set_value(text[val]) + pipe_pane.set_value(val) + end + + -- local ftm_modes_1u = { "Mode 1" } + -- local ftm_modes_2u = { "Mode 1", "Mode 4" } + -- local ftm_modes_3u = { "Mode 1", "Mode 3", "Mode 4", "Mode 7" } + local ftm_modes_4u = { "Mode 1", "Mode 2", "Mode 3", "Mode 4", "Mode 5", "Mode 6", "Mode 7", "Mode 8" } + -- local ftm_btn_2u = RadioButton{parent=svr_c_4,x=1,y=2,callback=show_pipes,default=math.min(1,ini_cfg.FacilityTankMode)+1,options=ftm_modes_2u,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.green} + -- local ftm_btn_3u = RadioButton{parent=svr_c_4,x=1,y=2,callback=show_pipes,default=math.min(1,ini_cfg.FacilityTankMode)+1,options=ftm_modes_3u,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.green} + local ftm_btn_4u = RadioButton{parent=svr_c_5,x=1,y=4,callback=show_pipes,default=math.min(1,ini_cfg.FacilityTankMode)+1,options=ftm_modes_4u,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.green} + + -- 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 + + -- local function submit_emcool() + -- tmp_cfg.EmerCoolSide = side_options_map[side.get_value()] + -- tmp_cfg.EmerCoolColor = util.trinary(bundled.get_value(), color_options_map[color.get_value()], nil) + -- next_from_plc() + -- end + + PushButton{parent=svr_c_5,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=svr_c_5,x=44,y=14,text="Next \x1a",callback=function()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + -- 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=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)} + + 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="Supervisor Channel"} + local svr_chan = NumberField{parent=net_c_1,x=1,y=9,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_1,x=9,y=9,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_1,x=1,y=11,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="[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="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + + local function submit_channels() + local svr_c = tonumber(svr_chan.get_value()) + local plc_c = tonumber(plc_chan.get_value()) + if svr_c ~= nil and plc_c ~= nil then + tmp_cfg.SVR_Channel = svr_c + tmp_cfg.PLC_Channel = plc_c + net_pane.set_value(2) + 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 PLC channel.") + chan_err.show() + end + end + + PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + 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="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="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="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="",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,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + 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="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="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,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + -- 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=" Logging Configuration",fg_bg=cpair(colors.black,colors.pink)} + + 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="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="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="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="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 + + local function back_from_log() + if tmp_cfg.Networked then main_pane.set_value(3) else main_pane.set_value(2) end + end + + PushButton{parent=log_c_1,x=1,y=14,text="\x1b Back",callback=back_from_log,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=log_c_1,x=44,y=14,text="Next \x1a",callback=submit_log,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + -- 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=" 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("reactor-plc.settings") then + -- load_settings(settings_cfg, true) + -- load_settings(ini_cfg) + + -- try_set(networked, ini_cfg.Networked) + -- try_set(u_id, ini_cfg.UnitID) + -- try_set(en_em_cool, ini_cfg.EmerCoolEnable) + -- try_set(side, side_to_idx(ini_cfg.EmerCoolSide)) + -- try_set(bundled, ini_cfg.EmerCoolColor ~= nil) + -- if ini_cfg.EmerCoolColor ~= nil then try_set(color, color_to_idx(ini_cfg.EmerCoolColor)) end + -- try_set(svr_chan, ini_cfg.SVR_Channel) + -- try_set(plc_chan, ini_cfg.PLC_Channel) + -- try_set(timeout, ini_cfg.ConnTimeout) + -- try_set(range, ini_cfg.TrustedRange) + -- try_set(key, ini_cfg.AuthKey) + -- try_set(mode, ini_cfg.LogMode) + -- 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) + -- else + -- sum_pane.set_value(2) + -- end + -- else + -- sum_pane.set_value(4) + -- end + end + + PushButton{parent=sum_c_1,x=1,y=14,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="Settings saved!"} + + local function go_home() + main_pane.set_value(1) + svr_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="The old config.lua file will now be deleted, then the configurator will exit."} + + local function delete_legacy() + fs.delete("/supervisor/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="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)} + + -- CONFIG CHANGE LOG + + local cl = Div{parent=changelog,x=2,y=4,width=49} + + 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)} + + 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,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + -- set tool functions now that we have the elements + + -- load a legacy config file + function tool_ctl.load_legacy() + local config = require("reactor-plc.config") + + tmp_cfg.Networked = config.NETWORKED + tmp_cfg.UnitID = config.REACTOR_ID + tmp_cfg.EmerCoolEnable = type(config.EMERGENCY_COOL) == "table" + + if tmp_cfg.EmerCoolEnable then + tmp_cfg.EmerCoolSide = config.EMERGENCY_COOL.side + tmp_cfg.EmerCoolColor = config.EMERGENCY_COOL.color + else + tmp_cfg.EmerCoolSide = nil + tmp_cfg.EmerCoolColor = nil + end + + tmp_cfg.SVR_Channel = config.SVR_CHANNEL + tmp_cfg.PLC_Channel = config.PLC_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 plc_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 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)) + 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 supervisor configurator +---@param ask_config? boolean indicate if this is being called by the supervisor startup app due to an invalid configuration +function configurator.configure(ask_config) + tool_ctl.ask_config = ask_config == true + + load_settings(settings_cfg, true) + tool_ctl.has_config = 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 + tcd.handle(param1) + elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then + 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 + 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 + 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 aebdf3e8df5fe6bb35ab843c3be4d1e9a64cc197 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 26 Dec 2023 13:11:46 -0500 Subject: [PATCH 02/18] fixed include ordering --- reactor-plc/configure.lua | 2 +- rtu/configure.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index 9f4784a..7b5ab4c 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -3,9 +3,9 @@ -- 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 rsio = require("scada-common.rsio") local core = require("graphics.core") diff --git a/rtu/configure.lua b/rtu/configure.lua index a77ab32..9bdb5ee 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -3,10 +3,10 @@ -- local log = require("scada-common.log") +local ppm = require("scada-common.ppm") local rsio = require("scada-common.rsio") local tcd = require("scada-common.tcd") local util = require("scada-common.util") -local ppm = require("scada-common.ppm") local core = require("graphics.core") From 95f87b1b0549907d95080cf698ac97c3dbe4d7e3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 26 Dec 2023 13:13:05 -0500 Subject: [PATCH 03/18] #308 significantly improved facility dynamic tank configuration visualization --- supervisor/configure.lua | 220 +++++++++++++++++++++++++++++---------- 1 file changed, 165 insertions(+), 55 deletions(-) diff --git a/supervisor/configure.lua b/supervisor/configure.lua index 473f23e..227644c 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -5,7 +5,6 @@ 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") @@ -13,7 +12,6 @@ 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 PipeNet = require("graphics.elements.pipenet") local TextBox = require("graphics.elements.textbox") local CheckBox = require("graphics.elements.controls.checkbox") @@ -86,7 +84,10 @@ local tool_ctl = { auth_key_value = "", cooling_elems = {}, - tank_elems = {} + tank_elems = {}, + + vis_ftanks = {}, + vis_utanks = {} } ---@class svr_config @@ -221,6 +222,7 @@ local function config_view(display) local function submit_num_units() local count = tonumber(num_units.get_value()) + count = 4 ---@fixme test code if count ~= nil and count > 0 and count < 5 then nu_error.hide(true) tmp_cfg.UnitCount = count @@ -241,13 +243,13 @@ local function config_view(display) TextBox{parent=svr_c_2,x=1,y=6,height=1,text="UNIT TURBINES BOILERS HAS TANK CONNECTION?",fg_bg=g_lg_fg_bg} for i = 1, 4 do - local num_t, num_b, has_t = 1, 0, false + local num_t, num_b, has_t = 1, 0, true ---@fixme test code false if ini_cfg.CoolingConfig[1] then local conf = ini_cfg.CoolingConfig[1] if util.is_int(conf.TurbineCount) then num_t = math.min(3, math.max(1, conf.TurbineCount or 1)) end if util.is_int(conf.BoilerCount) then num_b = math.min(2, math.max(1, conf.BoilerCount or 0)) end - has_t = conf.TankConnection == true + has_t = true ---@fixme test code conf.TankConnection == true end local line = Div{parent=svr_c_2,x=1,y=7+i,height=1} @@ -351,15 +353,37 @@ local function config_view(display) local tank_err = TextBox{parent=svr_c_4,x=8,y=14,height=1,width=33,text="You selected no facility tanks.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local function show_fconn(i) + if i > 1 then tool_ctl.vis_ftanks[i].pipe_conn.show() + else tool_ctl.vis_ftanks[i].line.show() end + end + + local function hide_fconn(i) + if i > 1 then tool_ctl.vis_ftanks[i].pipe_conn.hide(true) + else tool_ctl.vis_ftanks[i].line.hide(true) end + end + local function submit_tank_defs() local any_fac = false tmp_cfg.FacilityTankDefs = {} for i = 1, tmp_cfg.UnitCount do + local def = tmp_cfg.FacilityTankDefs[i] if tmp_cfg.CoolingConfig[i].TankConnection then - tmp_cfg.FacilityTankDefs[i] = tool_ctl.tank_elems[i].tank_opt.get_value() - any_fac = any_fac or (tmp_cfg.FacilityTankDefs[i] == 2) - else tmp_cfg.FacilityTankDefs[i] = 0 end + def = tool_ctl.tank_elems[i].tank_opt.get_value() + any_fac = any_fac or (def == 2) + else def = 0 end + + if def == 1 then + tool_ctl.vis_utanks[i].line.show() + tool_ctl.vis_utanks[i].label.set_value("Tank U" .. i) + hide_fconn(i) + else + if def == 2 then show_fconn(i) else hide_fconn(i) end + tool_ctl.vis_utanks[i].line.hide(true) + end + + tmp_cfg.FacilityTankDefs[i] = def end -- if any_fac then @@ -374,52 +398,147 @@ local function config_view(display) TextBox{parent=svr_c_5,x=1,y=1,height=1,text="Please select your dynamic tank layout."} TextBox{parent=svr_c_5,x=12,y=3,height=1,text="Facility Tanks Unit Tanks",fg_bg=g_lg_fg_bg} - local vis = Div{parent=svr_c_5,x=15,y=5} - local tanks = TextBox{parent=vis,x=1,y=1,width=6,height=7,text="Tank A"} - local units = TextBox{parent=vis,x=14,y=1,width=6,height=7,text="Unit 1\n\nUnit 2\n\nUnit 3\n\nUnit 4"} + --#region Tank Layout Visualizer + + local vis = Div{parent=svr_c_5,x=14,y=5} + + local units = TextBox{parent=vis,x=15,y=1,width=6,height=7,text="Unit 1\n\nUnit 2\n\nUnit 3\n\nUnit 4"} + + -- draw unit tanks and their pipes for i = 1, 4 do - local line = Div{parent=vis,x=21,y=(i*2)-1,width=12,height=1} - TextBox{parent=line,width=5,height=1,text="\x8c\x8c\x8c\x8c\x8c",fg_bg=cpair(colors.blue,colors.lightGray)} - local label = TextBox{parent=line,x=7,y=1,width=6,height=1,text="Tank ?"} + local line = Div{parent=vis,x=22,y=(i*2)-1,width=13,height=1} + TextBox{parent=line,width=5,height=1,text=string.rep("\x8c",5),fg_bg=cpair(colors.blue,colors.lightGray)} + local label = TextBox{parent=line,x=7,y=1,width=7,height=1,text="Tank ?"} + tool_ctl.vis_utanks[i] = { line = line, label = label } end - local m1_pipes = { core.pipe(0,0,4,0,colors.blue,true), core.pipe(3,0,4,2,colors.blue,true), core.pipe(3,2,4,4,colors.blue,true), core.pipe(3,4,4,6,colors.blue,true) } - local m2_pipes = { core.pipe(0,0,4,0,colors.blue,true), core.pipe(3,0,4,2,colors.blue,true), core.pipe(3,2,4,4,colors.blue,true), core.pipe(0,6,4,6,colors.blue,true) } - local m3_pipes = { core.pipe(0,0,4,0,colors.blue,true), core.pipe(3,0,4,2,colors.blue,true), core.pipe(0,4,4,4,colors.blue,true), core.pipe(3,4,4,6,colors.blue,true) } - local m4_pipes = { core.pipe(0,0,4,0,colors.blue,true), core.pipe(0,2,4,2,colors.blue,true), core.pipe(3,2,4,4,colors.blue,true), core.pipe(3,4,4,6,colors.blue,true) } - local m5_pipes = { core.pipe(0,0,4,0,colors.blue,true), core.pipe(3,0,4,2,colors.blue,true), core.pipe(0,4,4,4,colors.blue,true), core.pipe(0,6,4,6,colors.blue,true) } - local m6_pipes = { core.pipe(0,0,4,0,colors.blue,true), core.pipe(0,2,4,2,colors.blue,true), core.pipe(3,2,4,4,colors.blue,true), core.pipe(0,6,4,6,colors.blue,true) } - local m7_pipes = { core.pipe(0,0,4,0,colors.blue,true), core.pipe(0,2,4,2,colors.blue,true), core.pipe(0,4,4,4,colors.blue,true), core.pipe(3,4,4,6,colors.blue,true) } - local m8_pipes = { core.pipe(0,0,4,0,colors.blue,true), core.pipe(0,2,4,2,colors.blue,true), core.pipe(0,4,4,4,colors.blue,true), core.pipe(0,6,4,6,colors.blue,true) } + local ftank_1 = Div{parent=vis,x=1,y=1,width=13,height=1} + TextBox{parent=ftank_1,width=7,height=1,text="Tank F1"} + tool_ctl.vis_ftanks[1] = { + line = ftank_1, pipe_direct = TextBox{parent=ftank_1,x=9,y=1,width=5,text=string.rep("\x8c",5),fg_bg=cpair(colors.yellow,colors.lightGray)} + } - local pnet_m1 = PipeNet{parent=vis,x=8,y=1,width=5,height=7,pipes=m1_pipes} - local pnet_m2 = PipeNet{parent=vis,x=8,y=1,width=5,height=7,pipes=m2_pipes} - local pnet_m3 = PipeNet{parent=vis,x=8,y=1,width=5,height=7,pipes=m3_pipes} - local pnet_m4 = PipeNet{parent=vis,x=8,y=1,width=5,height=7,pipes=m4_pipes} - local pnet_m5 = PipeNet{parent=vis,x=8,y=1,width=5,height=7,pipes=m5_pipes} - local pnet_m6 = PipeNet{parent=vis,x=8,y=1,width=5,height=7,pipes=m6_pipes} - local pnet_m7 = PipeNet{parent=vis,x=8,y=1,width=5,height=7,pipes=m7_pipes} - local pnet_m8 = PipeNet{parent=vis,x=8,y=1,width=5,height=7,pipes=m8_pipes} + -- draw facility tank connections + for i = 2, 4 do + local line = Div{parent=vis,x=1,y=(i-1)*2,width=13,height=2} + local pipe_conn = TextBox{parent=line,x=13,y=2,width=1,height=1,text="\x8c",fg_bg=cpair(colors.red,colors.lightGray)} + local pipe_chain = TextBox{parent=line,x=12,y=1,width=1,height=2,text="\x95\n\x8d",fg_bg=cpair(colors.green,colors.lightGray)} + local pipe_direct = TextBox{parent=line,x=9,y=2,width=4,height=1,text="\x8c\x8c\x8c\x8c",fg_bg=cpair(colors.lightBlue,colors.lightGray),hidden=true} + local label = TextBox{parent=line,x=1,y=2,width=7,height=1,text="Tank F?"} + tool_ctl.vis_ftanks[i] = { line = line, pipe_conn = pipe_conn, pipe_chain = pipe_chain, pipe_direct = pipe_direct, label = label } + end - local pipe_pane = MultiPane{parent=vis,x=8,y=1,width=5,height=7,panes={pnet_m1,pnet_m2,pnet_m3,pnet_m4,pnet_m5,pnet_m6,pnet_m7,pnet_m8}} + local function show_pipes(mode) + -- is a facility tank connected to this unit + ---@param i integer unit 1 - 4 + ---@return boolean connected + local function is_ft(i) return tmp_cfg.FacilityTankDefs[i] == 2 end - local hide_pipes_1u = Div{parent=vis,x=1,y=2,width=19,height=6,hidden=true} + local next_idx = 1 - local function show_pipes(val) - local text = { - "Tank A", - "Tank A\n\n\n\n\n\nTank B", - "Tank A\n\n\n\nTank B", - "Tank A\n\nTank B", - "Tank A\n\n\n\nTank B\n\nTank C", - "Tank A\n\nTank B\n\n\n\nTank C", - "Tank A\n\nTank B\n\nTank C", - "Tank A\n\nTank B\n\nTank C\n\nTank D" - } + if is_ft(1) then + next_idx = 2 - tanks.set_value(text[val]) - pipe_pane.set_value(val) + if (mode == 1 and (is_ft(2) or is_ft(3) or is_ft(4))) or (mode == 2 and (is_ft(2) or is_ft(3))) or ((mode == 3 or mode == 5) and is_ft(2)) then + tool_ctl.vis_ftanks[1].pipe_direct.set_value("\x8c\x8c\x8c\x9c\x8c") + else + tool_ctl.vis_ftanks[1].pipe_direct.set_value(string.rep("\x8c",5)) + end + end + + local _2_12_need_passt = (mode == 1 and (is_ft(3) or is_ft(4))) or (mode == 2 and is_ft(3)) + local _2_46_need_chain = (mode == 4 and (is_ft(3) or is_ft(4))) or (mode == 6 and is_ft(3)) + + if is_ft(2) then + tool_ctl.vis_ftanks[2].label.set_value("Tank F" .. next_idx) + + if (mode < 4 or mode == 5) and is_ft(1) then + tool_ctl.vis_ftanks[2].label.hide(true) + tool_ctl.vis_ftanks[2].pipe_direct.hide(true) + if _2_12_need_passt then + tool_ctl.vis_ftanks[2].pipe_chain.set_value("\x95\n\x9d") + else + tool_ctl.vis_ftanks[2].pipe_chain.set_value("\x95\n\x8d") + end + tool_ctl.vis_ftanks[2].pipe_chain.show() + else + tool_ctl.vis_ftanks[2].label.show() + next_idx = next_idx + 1 + + tool_ctl.vis_ftanks[2].pipe_chain.hide(true) + if _2_12_need_passt or _2_46_need_chain then + tool_ctl.vis_ftanks[2].pipe_direct.set_value("\x8c\x8c\x8c\x9c") + else + tool_ctl.vis_ftanks[2].pipe_direct.set_value("\x8c\x8c\x8c\x8c") + end + tool_ctl.vis_ftanks[2].pipe_direct.show() + end + + tool_ctl.vis_ftanks[2].line.show() + elseif is_ft(1) and _2_12_need_passt then + tool_ctl.vis_ftanks[2].label.hide(true) + tool_ctl.vis_ftanks[2].pipe_direct.hide(true) + tool_ctl.vis_ftanks[2].pipe_chain.set_value("\x95\n\x95") + tool_ctl.vis_ftanks[2].pipe_chain.show() + tool_ctl.vis_ftanks[2].line.show() + else + tool_ctl.vis_ftanks[2].line.hide(true) + end + + if is_ft(3) then + tool_ctl.vis_ftanks[3].label.set_value("Tank F" .. next_idx) + + if (mode < 3 and (is_ft(1) or is_ft(2))) or ((mode == 4 or mode == 6) and is_ft(2)) then + tool_ctl.vis_ftanks[3].label.hide(true) + tool_ctl.vis_ftanks[3].pipe_direct.hide(true) + if (mode == 1 or mode == 4) and is_ft(4) then + tool_ctl.vis_ftanks[3].pipe_chain.set_value("\x95\n\x9d") + else + tool_ctl.vis_ftanks[3].pipe_chain.set_value("\x95\n\x8d") + end + tool_ctl.vis_ftanks[3].pipe_chain.show() + else + tool_ctl.vis_ftanks[3].label.show() + next_idx = next_idx + 1 + + tool_ctl.vis_ftanks[3].pipe_chain.hide(true) + if (mode == 1 or mode == 3 or mode == 4 or mode == 7) and is_ft(4) then + tool_ctl.vis_ftanks[3].pipe_direct.set_value("\x8c\x8c\x8c\x9c") + else + tool_ctl.vis_ftanks[3].pipe_direct.set_value("\x8c\x8c\x8c\x8c") + end + tool_ctl.vis_ftanks[3].pipe_direct.show() + end + + tool_ctl.vis_ftanks[3].line.show() + elseif (mode == 1 and is_ft(4) and (is_ft(1) or is_ft(2))) or (mode == 4 and is_ft(2) and is_ft(4)) then + tool_ctl.vis_ftanks[3].label.hide(true) + tool_ctl.vis_ftanks[3].pipe_direct.hide(true) + tool_ctl.vis_ftanks[3].pipe_chain.set_value("\x95\n\x95") + tool_ctl.vis_ftanks[3].pipe_chain.show() + tool_ctl.vis_ftanks[3].line.show() + else + tool_ctl.vis_ftanks[3].line.hide(true) + end + + if is_ft(4) then + tool_ctl.vis_ftanks[4].label.set_value("Tank F" .. next_idx) + + if (mode == 1 and (is_ft(1) or is_ft(2) or is_ft(3))) or ((mode == 3 or mode == 7) and is_ft(3)) or (mode == 4 and (is_ft(2) or is_ft(3))) then + tool_ctl.vis_ftanks[4].label.hide(true) + tool_ctl.vis_ftanks[4].pipe_direct.hide(true) + tool_ctl.vis_ftanks[4].pipe_chain.show() + else + tool_ctl.vis_ftanks[4].label.show() + tool_ctl.vis_ftanks[4].pipe_chain.hide(true) + tool_ctl.vis_ftanks[4].pipe_direct.show() + end + + tool_ctl.vis_ftanks[4].line.show() + else + tool_ctl.vis_ftanks[4].line.hide(true) + end end -- local ftm_modes_1u = { "Mode 1" } @@ -430,16 +549,7 @@ local function config_view(display) -- local ftm_btn_3u = RadioButton{parent=svr_c_4,x=1,y=2,callback=show_pipes,default=math.min(1,ini_cfg.FacilityTankMode)+1,options=ftm_modes_3u,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.green} local ftm_btn_4u = RadioButton{parent=svr_c_5,x=1,y=4,callback=show_pipes,default=math.min(1,ini_cfg.FacilityTankMode)+1,options=ftm_modes_4u,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.green} - -- 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 - - -- local function submit_emcool() - -- tmp_cfg.EmerCoolSide = side_options_map[side.get_value()] - -- tmp_cfg.EmerCoolColor = util.trinary(bundled.get_value(), color_options_map[color.get_value()], nil) - -- next_from_plc() - -- end + --#endregion PushButton{parent=svr_c_5,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=svr_c_5,x=44,y=14,text="Next \x1a",callback=function()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} From 26fe13060937fb58a5b81d40d45a38fe3d239557 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 28 Dec 2023 15:06:30 -0500 Subject: [PATCH 04/18] #308 supervisor configurator completed facility tank mode and network config pages --- supervisor/configure.lua | 223 +++++++++++++++++++++++---------------- 1 file changed, 132 insertions(+), 91 deletions(-) diff --git a/supervisor/configure.lua b/supervisor/configure.lua index 227644c..b96bc6b 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -209,8 +209,9 @@ local function config_view(display) local svr_c_3 = Div{parent=svr_cfg,x=2,y=4,width=49} local svr_c_4 = Div{parent=svr_cfg,x=2,y=4,width=49} local svr_c_5 = Div{parent=svr_cfg,x=2,y=4,width=49} + local svr_c_6 = Div{parent=svr_cfg,x=2,y=4,width=49} - local svr_pane = MultiPane{parent=svr_cfg,x=1,y=4,panes={svr_c_1,svr_c_2,svr_c_3,svr_c_4,svr_c_5}} + local svr_pane = MultiPane{parent=svr_cfg,x=1,y=4,panes={svr_c_1,svr_c_2,svr_c_3,svr_c_4,svr_c_5,svr_c_6}} TextBox{parent=svr_cfg,x=1,y=2,height=1,text=" Facility Configuration",fg_bg=cpair(colors.black,colors.green)} @@ -222,7 +223,6 @@ local function config_view(display) local function submit_num_units() local count = tonumber(num_units.get_value()) - count = 4 ---@fixme test code if count ~= nil and count > 0 and count < 5 then nu_error.hide(true) tmp_cfg.UnitCount = count @@ -243,13 +243,13 @@ local function config_view(display) TextBox{parent=svr_c_2,x=1,y=6,height=1,text="UNIT TURBINES BOILERS HAS TANK CONNECTION?",fg_bg=g_lg_fg_bg} for i = 1, 4 do - local num_t, num_b, has_t = 1, 0, true ---@fixme test code false + local num_t, num_b, has_t = 1, 0, false if ini_cfg.CoolingConfig[1] then local conf = ini_cfg.CoolingConfig[1] if util.is_int(conf.TurbineCount) then num_t = math.min(3, math.max(1, conf.TurbineCount or 1)) end if util.is_int(conf.BoilerCount) then num_b = math.min(2, math.max(1, conf.BoilerCount or 0)) end - has_t = true ---@fixme test code conf.TankConnection == true + has_t = conf.TankConnection == true end local line = Div{parent=svr_c_2,x=1,y=7+i,height=1} @@ -302,8 +302,7 @@ local function config_view(display) else elem.div.hide(true) end end - -- if any_has_tank then svr_pane.set_value(3) else main_pane.set_value(3) end - svr_pane.set_value(3) + if any_has_tank then svr_pane.set_value(3) else main_pane.set_value(3) end end end @@ -316,23 +315,15 @@ local function config_view(display) local en_fac_tanks = CheckBox{parent=svr_c_3,x=1,y=12,label="Use Facility Dynamic Tanks",default=ini_cfg.FacilityTankMode~=0,box_fg_bg=cpair(colors.green,colors.black)} local function submit_en_fac_tank() - -- if en_fac_tanks.get_value() then - -- assert(tool_ctl.num_tank_conns >= 1, "attempted to enable facility tanks with no tank connections assigned") - -- if tool_ctl.num_tank_conns == 1 then - -- -- nothing special for the user to do, set it automatically - -- tmp_cfg.FacilityTankMode = 1 - -- tmp_cfg.FacilityTankDefs = { 2 } - -- main_pane.set_value(3) - -- else - -- svr_pane.set_value(4) - -- end - -- else - -- tmp_cfg.FacilityTankMode = 0 - -- tmp_cfg.FacilityTankDefs = {} - -- main_pane.set_value(3) - -- end - - svr_pane.set_value(4) -- for testing + if en_fac_tanks.get_value() then + assert(tool_ctl.num_tank_conns >= 1, "attempted to enable facility tanks with no tank connections assigned") + svr_pane.set_value(4) + tmp_cfg.FacilityTankMode = math.min(8, math.max(1, tmp_cfg.FacilityTankMode)) + else + tmp_cfg.FacilityTankMode = 0 + tmp_cfg.FacilityTankDefs = {} + main_pane.set_value(3) + end end PushButton{parent=svr_c_3,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} @@ -386,11 +377,16 @@ local function config_view(display) tmp_cfg.FacilityTankDefs[i] = def end - -- if any_fac then - -- tank_err.hide(true) - -- svr_pane.set_value(5) - -- else tank_err.show() end - svr_pane.set_value(5) + for i = tmp_cfg.UnitCount + 1, 4 do + tool_ctl.vis_utanks[i].line.hide(true) + end + + tool_ctl.vis_draw(tmp_cfg.FacilityTankMode) + + if any_fac then + tank_err.hide(true) + svr_pane.set_value(5) + else tank_err.show() end end PushButton{parent=svr_c_4,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} @@ -401,9 +397,9 @@ local function config_view(display) --#region Tank Layout Visualizer - local vis = Div{parent=svr_c_5,x=14,y=5} + local vis = Div{parent=svr_c_5,x=14,y=5,height=7} - local units = TextBox{parent=vis,x=15,y=1,width=6,height=7,text="Unit 1\n\nUnit 2\n\nUnit 3\n\nUnit 4"} + local vis_unit_list = TextBox{parent=vis,x=15,y=1,width=6,height=7,text="Unit 1\n\nUnit 2\n\nUnit 3\n\nUnit 4"} -- draw unit tanks and their pipes for i = 1, 4 do @@ -429,12 +425,21 @@ local function config_view(display) tool_ctl.vis_ftanks[i] = { line = line, pipe_conn = pipe_conn, pipe_chain = pipe_chain, pipe_direct = pipe_direct, label = label } end - local function show_pipes(mode) + -- draw the pipe visualization + ---@param mode integer pipe mode + function tool_ctl.vis_draw(mode) -- is a facility tank connected to this unit ---@param i integer unit 1 - 4 ---@return boolean connected local function is_ft(i) return tmp_cfg.FacilityTankDefs[i] == 2 end + local u_text = "" + for i = 1, tmp_cfg.UnitCount do + u_text = u_text .. "Unit " .. i .. "\n\n" + end + + vis_unit_list.set_value(u_text) + local next_idx = 1 if is_ft(1) then @@ -541,106 +546,142 @@ local function config_view(display) end end - -- local ftm_modes_1u = { "Mode 1" } - -- local ftm_modes_2u = { "Mode 1", "Mode 4" } - -- local ftm_modes_3u = { "Mode 1", "Mode 3", "Mode 4", "Mode 7" } - local ftm_modes_4u = { "Mode 1", "Mode 2", "Mode 3", "Mode 4", "Mode 5", "Mode 6", "Mode 7", "Mode 8" } - -- local ftm_btn_2u = RadioButton{parent=svr_c_4,x=1,y=2,callback=show_pipes,default=math.min(1,ini_cfg.FacilityTankMode)+1,options=ftm_modes_2u,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.green} - -- local ftm_btn_3u = RadioButton{parent=svr_c_4,x=1,y=2,callback=show_pipes,default=math.min(1,ini_cfg.FacilityTankMode)+1,options=ftm_modes_3u,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.green} - local ftm_btn_4u = RadioButton{parent=svr_c_5,x=1,y=4,callback=show_pipes,default=math.min(1,ini_cfg.FacilityTankMode)+1,options=ftm_modes_4u,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.green} + local tank_modes = { "Mode 1", "Mode 2", "Mode 3", "Mode 4", "Mode 5", "Mode 6", "Mode 7", "Mode 8" } + local tank_mode = RadioButton{parent=svr_c_5,x=1,y=4,callback=tool_ctl.vis_draw,default=math.min(1,ini_cfg.FacilityTankMode)+1,options=tank_modes,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.green} --#endregion + local function submit_mode() + tmp_cfg.FacilityTankMode = tank_mode.get_value() + svr_pane.set_value(4) + end + PushButton{parent=svr_c_5,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=svr_c_5,x=44,y=14,text="Next \x1a",callback=function()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=svr_c_5,x=44,y=14,text="Next \x1a",callback=submit_mode,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + PushButton{parent=svr_c_5,x=8,y=14,min_width=7,text="About",callback=function()svr_pane.set_value(6)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg} + + TextBox{parent=svr_c_6,height=3,text="This visualization tool shows the pipe connections required for a particular dynamic tank configuration you have selected."} + TextBox{parent=svr_c_6,y=5,height=3,text="Some modes may look the same if you are not using 4 total reactor units. The wiki has details. Modes that look the same will function the same."} + TextBox{parent=svr_c_6,y=9,height=4,text="Examples: A U2 tank should be configured on an RTU as a dynamic tank for unit 2. An F3 tank should be configured on an RTU as the #3 dynamic tank for the facility."} + + PushButton{parent=svr_c_6,x=1,y=14,min_width=6,text="\x1b Back",callback=function()svr_pane.set_value(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} -- 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_c_4 = Div{parent=net_cfg,x=2,y=4,width=49} - local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3}} + local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4}} TextBox{parent=net_cfg,x=1,y=2,height=1,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)} 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=3,height=4,text="Each of the 5 uniquely named channels must be the same for each device in this SCADA network. For multiplayer servers, it is recommended to not use the default channels.",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_1,x=1,y=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="[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="[PLC_CHANNEL]",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_1,x=1,y=8,height=1,width=18,text="Supervisor Channel"} + local svr_chan = NumberField{parent=net_c_1,x=21,y=8,width=7,default=ini_cfg.SVR_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_1,x=29,y=8,height=4,text="[SVR_CHANNEL]",fg_bg=g_lg_fg_bg} - local 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} + TextBox{parent=net_c_1,x=1,y=9,height=1,width=11,text="PLC Channel"} + local plc_chan = NumberField{parent=net_c_1,x=21,y=9,width=7,default=ini_cfg.PLC_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_1,x=29,y=9,height=4,text="[PLC_CHANNEL]",fg_bg=g_lg_fg_bg} + + TextBox{parent=net_c_1,x=1,y=10,height=1,width=19,text="RTU Gateway Channel"} + local rtu_chan = NumberField{parent=net_c_1,x=21,y=10,width=7,default=ini_cfg.RTU_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_1,x=29,y=10,height=4,text="[RTU_CHANNEL]",fg_bg=g_lg_fg_bg} + + TextBox{parent=net_c_1,x=1,y=11,height=1,width=19,text="Coordinator Channel"} + local crd_chan = NumberField{parent=net_c_1,x=21,y=11,width=7,default=ini_cfg.CRD_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_1,x=29,y=11,height=4,text="[CRD_CHANNEL]",fg_bg=g_lg_fg_bg} + + TextBox{parent=net_c_1,x=1,y=12,height=1,width=14,text="Pocket Channel"} + local pkt_chan = NumberField{parent=net_c_1,x=21,y=12,width=7,default=ini_cfg.PKT_Channel,min=1,max=65535,fg_bg=bw_fg_bg} + TextBox{parent=net_c_1,x=29,y=12,height=4,text="[PKT_CHANNEL]",fg_bg=g_lg_fg_bg} + + local chan_err = TextBox{parent=net_c_1,x=8,y=14,height=1,width=35,text="Please set all channels.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_channels() - local svr_c = tonumber(svr_chan.get_value()) - local plc_c = tonumber(plc_chan.get_value()) - if svr_c ~= nil and plc_c ~= nil then - tmp_cfg.SVR_Channel = svr_c - tmp_cfg.PLC_Channel = plc_c + local svr_c, plc_c, rtu_c = tonumber(svr_chan.get_value()), tonumber(plc_chan.get_value()), tonumber(rtu_chan.get_value()) + local crd_c, pkt_c = tonumber(crd_chan.get_value()), tonumber(pkt_chan.get_value()) + if svr_c ~= nil and plc_c ~= nil and rtu_c ~= nil and crd_c ~= nil and pkt_c ~= nil then + tmp_cfg.SVR_Channel, tmp_cfg.PLC_Channel, tmp_cfg.RTU_Channel = svr_c, plc_c, rtu_c + tmp_cfg.CRD_Channel, tmp_cfg.PKT_Channel = crd_c, pkt_c net_pane.set_value(2) 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 PLC channel.") - chan_err.show() - end + else chan_err.show() end end PushButton{parent=net_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - 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="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=1,height=1,text="Please set the connection timeouts below."} + TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally should not need to modify these. On slow servers, you can try to increase this to make the system wait longer before assuming a disconnection. The default for all is 5 seconds.",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_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="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=8,height=1,width=11,text="PLC Timeout"} + local plc_timeout = NumberField{parent=net_c_2,x=21,y=8,width=7,default=ini_cfg.PLC_Timeout,min=2,max=25,fg_bg=bw_fg_bg} - 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} + TextBox{parent=net_c_2,x=1,y=9,height=1,width=19,text="RTU Gateway Timeout"} + local rtu_timeout = NumberField{parent=net_c_2,x=21,y=9,width=7,default=ini_cfg.RTU_Timeout,min=2,max=25,fg_bg=bw_fg_bg} - 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 + TextBox{parent=net_c_2,x=1,y=10,height=1,width=19,text="Coordinator Timeout"} + local crd_timeout = NumberField{parent=net_c_2,x=21,y=10,width=7,default=ini_cfg.CRD_Timeout,min=2,max=25,fg_bg=bw_fg_bg} + + TextBox{parent=net_c_2,x=1,y=11,height=1,width=14,text="Pocket Timeout"} + local pkt_timeout = NumberField{parent=net_c_2,x=21,y=11,width=7,default=ini_cfg.PKT_Timeout,min=2,max=25,fg_bg=bw_fg_bg} + + TextBox{parent=net_c_2,x=29,y=8,height=4,width=7,text="seconds\nseconds\nseconds\nseconds",fg_bg=g_lg_fg_bg} + + local ct_err = TextBox{parent=net_c_2,x=8,y=14,height=1,width=35,text="Please set all connection timeouts.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + + local function submit_timeouts() + local plc_cto, rtu_cto, crd_cto, pkt_cto = tonumber(plc_timeout.get_value()), tonumber(rtu_timeout.get_value()), tonumber(crd_timeout.get_value()), tonumber(pkt_timeout.get_value()) + if plc_cto ~= nil and rtu_cto ~= nil and crd_cto ~= nil and pkt_cto ~= nil then + tmp_cfg.PLC_Timeout, tmp_cfg.RTU_Timeout, tmp_cfg.CRD_Timeout, tmp_cfg.PKT_Timeout = plc_cto, rtu_cto, crd_cto, pkt_cto net_pane.set_value(3) - 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 + ct_err.hide(true) + else ct_err.show() end end PushButton{parent=net_c_2,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - TextBox{parent=net_c_3,x=1,y=1,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=1,height=1,text="Please set the trusted range below."} + TextBox{parent=net_c_3,x=1,y=3,height=3,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} + TextBox{parent=net_c_3,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg} - TextBox{parent=net_c_3,x=1,y=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 range = NumberField{parent=net_c_3,x=1,y=10,width=10,default=ini_cfg.TrustedRange,min=0,max_digits=20,allow_decimal=true,fg_bg=bw_fg_bg} + + local tr_err = TextBox{parent=net_c_3,x=8,y=14,height=1,width=35,text="Please set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + + local function submit_tr() + local range_val = tonumber(range.get_value()) + if range_val ~= nil then + tmp_cfg.TrustedRange = range_val + net_pane.set_value(4) + tr_err.hide(true) + else tr_err.show() end + end + + PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + TextBox{parent=net_c_4,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."} + TextBox{parent=net_c_4,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra compution (can slow things down).",fg_bg=g_lg_fg_bg} + + TextBox{parent=net_c_4,x=1,y=11,height=1,text="Facility Auth Key"} + local key, _, censor = TextField{parent=net_c_4,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg} local function censor_key(enable) 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} + local hide_key = CheckBox{parent=net_c_4,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key} hide_key.set_value(true) censor_key(true) - local key_err = TextBox{parent=net_c_3,x=8,y=14,height=1,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} + local key_err = TextBox{parent=net_c_4,x=8,y=14,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() @@ -651,8 +692,8 @@ local function config_view(display) else key_err.show() end end - PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_4,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_4,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} -- LOG CONFIG From cd71c6a9c1f242bf94d72f5f83523df4fec50d1d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 29 Dec 2023 00:19:17 -0500 Subject: [PATCH 05/18] #308 summary display of supervisor config --- supervisor/configure.lua | 42 ++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/supervisor/configure.lua b/supervisor/configure.lua index b96bc6b..c681534 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -95,7 +95,7 @@ local tmp_cfg = { UnitCount = 1, CoolingConfig = {}, FacilityTankMode = 0, - FacilityTankDefs = nil, + FacilityTankDefs = {}, SVR_Channel = nil, PLC_Channel = nil, RTU_Channel = nil, @@ -553,7 +553,7 @@ local function config_view(display) local function submit_mode() tmp_cfg.FacilityTankMode = tank_mode.get_value() - svr_pane.set_value(4) + main_pane.set_value(3) end PushButton{parent=svr_c_5,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} @@ -889,7 +889,7 @@ local function config_view(display) end -- generate the summary list - ---@param cfg plc_config + ---@param cfg svr_config function tool_ctl.gen_summary(cfg) setting_list.remove_all() @@ -907,9 +907,39 @@ local function config_view(display) 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 f[1] == "EmerCoolColor" and raw ~= nil then val = rsio.color_name(raw) end + if f[1] == "AuthKey" then val = string.rep("*", string.len(val)) + elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") + elseif f[1] == "CoolingConfig" and cfg.CoolingConfig then + val = "" + + for idx = 1, #cfg.CoolingConfig do + local ccfg = cfg.CoolingConfig[idx] + local b_plural = util.trinary(ccfg.BoilerCount == 1, "", "s") + local t_plural = util.trinary(ccfg.TurbineCount == 1, "", "s") + local tank = util.trinary(ccfg.TankConnection, "has tank conn", "no tank conn") + val = val .. util.trinary(idx == 1, "", "\n") .. + util.sprintf(" \x07 unit %d - %d boiler%s, %d turbine%s, %s", idx, ccfg.BoilerCount, b_plural, ccfg.TurbineCount, t_plural, tank) + end + + if val == "" then val = "no facility tanks" end + elseif f[1] == "FacilityTankMode" and raw == 0 then val = "0 (n/a unit mode)" + elseif f[1] == "FacilityTankDefs" and cfg.FacilityTankDefs then + val = "" + + for idx = 1, #cfg.FacilityTankDefs do + local t_mode = "not connected to a tank" + if cfg.FacilityTankDefs[idx] == 1 then + t_mode = "connected to its unit tank" + elseif cfg.FacilityTankDefs[idx] == 2 then + t_mode = "connected to a facility tank" + end + + val = val .. util.trinary(idx == 1, "", "\n") .. util.sprintf(" \x07 unit %d - %s", idx, t_mode) + end + + if val == "" then val = "no facility tanks" end + end + if val == "nil" then val = "" end local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white)) From 7d60e259e2680396853049f994a3861634b8947c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 29 Dec 2023 01:07:50 -0500 Subject: [PATCH 06/18] #308 supervisor configurator bugfixes and saving of settings --- supervisor/configure.lua | 110 +++++++++++++++++++++------------------ 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/supervisor/configure.lua b/supervisor/configure.lua index c681534..8fa9e4d 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -245,8 +245,8 @@ local function config_view(display) for i = 1, 4 do local num_t, num_b, has_t = 1, 0, false - if ini_cfg.CoolingConfig[1] then - local conf = ini_cfg.CoolingConfig[1] + if ini_cfg.CoolingConfig[i] then + local conf = ini_cfg.CoolingConfig[i] if util.is_int(conf.TurbineCount) then num_t = math.min(3, math.max(1, conf.TurbineCount or 1)) end if util.is_int(conf.BoilerCount) then num_b = math.min(2, math.max(1, conf.BoilerCount or 0)) end has_t = conf.TankConnection == true @@ -318,7 +318,7 @@ local function config_view(display) if en_fac_tanks.get_value() then assert(tool_ctl.num_tank_conns >= 1, "attempted to enable facility tanks with no tank connections assigned") svr_pane.set_value(4) - tmp_cfg.FacilityTankMode = math.min(8, math.max(1, tmp_cfg.FacilityTankMode)) + tmp_cfg.FacilityTankMode = util.trinary(tmp_cfg.FacilityTankMode == 0, 1, math.min(8, math.max(1, ini_cfg.FacilityTankMode))) else tmp_cfg.FacilityTankMode = 0 tmp_cfg.FacilityTankDefs = {} @@ -332,11 +332,12 @@ local function config_view(display) TextBox{parent=svr_c_4,x=1,y=1,height=4,text="Please set unit connections to dynamic tanks, selecting at least one facility tank. The layout for facility tanks will be configured next."} for i = 1, 4 do + local val = math.max(1, ini_cfg.FacilityTankDefs[i] or 2) local div = Div{parent=svr_c_4,x=1,y=3+(2*i),height=2} TextBox{parent=div,x=1,y=1,width=33,height=1,text="Unit "..i.." will be connected to..."} TextBox{parent=div,x=6,y=2,width=3,height=1,text="..."} - local tank_opt = Radio2D{parent=div,x=10,y=2,rows=1,columns=2,default=2,options={"its own Unit Tank","a Facility Tank"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.green,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg} + local tank_opt = Radio2D{parent=div,x=10,y=2,rows=1,columns=2,default=val,options={"its own Unit Tank","a Facility Tank"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.green,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg} local no_tank = TextBox{parent=div,x=9,y=2,width=34,height=1,text="no tank (as you set two steps ago)",fg_bg=cpair(colors.gray,colors.lightGray),hidden=true} tool_ctl.tank_elems[i] = { div = div, tank_opt = tank_opt, no_tank = no_tank } @@ -547,7 +548,7 @@ local function config_view(display) end local tank_modes = { "Mode 1", "Mode 2", "Mode 3", "Mode 4", "Mode 5", "Mode 6", "Mode 7", "Mode 8" } - local tank_mode = RadioButton{parent=svr_c_5,x=1,y=4,callback=tool_ctl.vis_draw,default=math.min(1,ini_cfg.FacilityTankMode)+1,options=tank_modes,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.green} + local tank_mode = RadioButton{parent=svr_c_5,x=1,y=4,callback=tool_ctl.vis_draw,default=math.max(1,ini_cfg.FacilityTankMode),options=tank_modes,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.green} --#endregion @@ -728,11 +729,7 @@ local function config_view(display) else path_err.show() end end - local function back_from_log() - if tmp_cfg.Networked then main_pane.set_value(3) else main_pane.set_value(2) end - end - - PushButton{parent=log_c_1,x=1,y=14,text="\x1b Back",callback=back_from_log,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=log_c_1,x=1,y=14,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,text="Next \x1a",callback=submit_log,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} -- SUMMARY OF CHANGES @@ -766,38 +763,51 @@ local function config_view(display) end local function save_and_continue() - -- for k, v in pairs(tmp_cfg) do settings.set(k, v) end + for k, v in pairs(tmp_cfg) do settings.set(k, v) end - -- if settings.save("reactor-plc.settings") then - -- load_settings(settings_cfg, true) - -- load_settings(ini_cfg) + if settings.save("supervisor.settings") then + load_settings(settings_cfg, true) + load_settings(ini_cfg) - -- try_set(networked, ini_cfg.Networked) - -- try_set(u_id, ini_cfg.UnitID) - -- try_set(en_em_cool, ini_cfg.EmerCoolEnable) - -- try_set(side, side_to_idx(ini_cfg.EmerCoolSide)) - -- try_set(bundled, ini_cfg.EmerCoolColor ~= nil) - -- if ini_cfg.EmerCoolColor ~= nil then try_set(color, color_to_idx(ini_cfg.EmerCoolColor)) end - -- try_set(svr_chan, ini_cfg.SVR_Channel) - -- try_set(plc_chan, ini_cfg.PLC_Channel) - -- try_set(timeout, ini_cfg.ConnTimeout) - -- try_set(range, ini_cfg.TrustedRange) - -- try_set(key, ini_cfg.AuthKey) - -- try_set(mode, ini_cfg.LogMode) - -- try_set(path, ini_cfg.LogPath) - -- try_set(en_dbg, ini_cfg.LogDebug) + try_set(num_units, ini_cfg.UnitCount) + try_set(tank_mode, ini_cfg.FacilityTankMode) + try_set(svr_chan, ini_cfg.SVR_Channel) + try_set(plc_chan, ini_cfg.PLC_Channel) + try_set(rtu_chan, ini_cfg.RTU_Channel) + try_set(crd_chan, ini_cfg.CRD_Channel) + try_set(pkt_chan, ini_cfg.PKT_Channel) + try_set(plc_timeout, ini_cfg.PLC_Timeout) + try_set(rtu_timeout, ini_cfg.RTU_Timeout) + try_set(crd_timeout, ini_cfg.CRD_Timeout) + try_set(pkt_timeout, ini_cfg.PKT_Timeout) + try_set(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) - -- tool_ctl.view_cfg.enable() + for i = 1, #ini_cfg.CoolingConfig do + local cfg, elems = ini_cfg.CoolingConfig[i], tool_ctl.cooling_elems[i] + try_set(elems.boilers, cfg.BoilerCount) + try_set(elems.turbines, cfg.TurbineCount) + try_set(elems.tank, cfg.TankConnection) + end - -- 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 + for i = 1, #ini_cfg.FacilityTankDefs do + try_set(tool_ctl.tank_elems[i].tank_opt, ini_cfg.FacilityTankDefs[i]) + end + + tool_ctl.view_cfg.enable() + + 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,text="\x1b Back",callback=back_from_settings,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} @@ -853,23 +863,23 @@ local function config_view(display) -- load a legacy config file function tool_ctl.load_legacy() - local config = require("reactor-plc.config") + local config = require("supervisor.config") - tmp_cfg.Networked = config.NETWORKED - tmp_cfg.UnitID = config.REACTOR_ID - tmp_cfg.EmerCoolEnable = type(config.EMERGENCY_COOL) == "table" + ---@todo finish - if tmp_cfg.EmerCoolEnable then - tmp_cfg.EmerCoolSide = config.EMERGENCY_COOL.side - tmp_cfg.EmerCoolColor = config.EMERGENCY_COOL.color - else - tmp_cfg.EmerCoolSide = nil - tmp_cfg.EmerCoolColor = nil - end + tmp_cfg.UnitCount = config.NUM_REACTORS tmp_cfg.SVR_Channel = config.SVR_CHANNEL tmp_cfg.PLC_Channel = config.PLC_CHANNEL - tmp_cfg.ConnTimeout = config.COMMS_TIMEOUT + tmp_cfg.RTU_Channel = config.RTU_CHANNEL + tmp_cfg.CRD_Channel = config.CRD_CHANNEL + tmp_cfg.PKT_Channel = config.PKT_CHANNEL + + tmp_cfg.PLC_Timeout = config.PLC_TIMEOUT + tmp_cfg.RTU_Timeout = config.RTU_TIMEOUT + tmp_cfg.CRD_Timeout = config.CRD_TIMEOUT + tmp_cfg.PKT_Timeout = config.PKT_TIMEOUT + tmp_cfg.TrustedRange = config.TRUSTED_RANGE tmp_cfg.AuthKey = config.AUTH_KEY or "" tmp_cfg.LogMode = config.LOG_MODE From c6ade68ce2d15e5e466823a8201024a3d2367d68 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 29 Dec 2023 12:40:48 -0500 Subject: [PATCH 07/18] #308 importing legacy config --- supervisor/configure.lua | 58 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/supervisor/configure.lua b/supervisor/configure.lua index 8fa9e4d..5639660 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -168,8 +168,9 @@ local function config_view(display) 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 import_err = Div{parent=root_pane_div,x=1,y=1} - local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,svr_cfg,net_cfg,log_cfg,summary,changelog}} + local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,svr_cfg,net_cfg,log_cfg,summary,changelog,import_err}} -- MAIN PAGE @@ -859,16 +860,67 @@ local function config_view(display) PushButton{parent=cl,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + -- IMPORT ERROR + + local i_err = Div{parent=import_err,x=2,y=4,width=49} + + TextBox{parent=import_err,x=1,y=2,height=1,text=" Import Error",fg_bg=cpair(colors.black,colors.red)} + TextBox{parent=i_err,x=1,y=1,height=1,text="There is a problem with your config.lua file:"} + + local import_err_msg = TextBox{parent=i_err,x=1,y=3,height=6,text=""} + + PushButton{parent=i_err,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=i_err,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)} + -- set tool functions now that we have the elements -- load a legacy config file function tool_ctl.load_legacy() local config = require("supervisor.config") - ---@todo finish - tmp_cfg.UnitCount = config.NUM_REACTORS + if config.REACTOR_COOLING == nil or tmp_cfg.UnitCount ~= #config.REACTOR_COOLING then + import_err_msg.set_value("Cooling configuration table length must match the number of units.") + main_pane.set_value(7) + return + end + + for i = 1, tmp_cfg.UnitCount do + local cfg = config.REACTOR_COOLING[i] + + if type(cfg) ~= "table" then + import_err_msg.set_value("Cooling configuration for unit " .. i .. " must be a table.") + main_pane.set_value(7) + return + end + + tmp_cfg.CoolingConfig[i] = { BoilerCount = cfg.BOILERS or 0, TurbineCount = cfg.TURBINES or 1, TankConnection = cfg.TANK or false } + end + + tmp_cfg.FacilityTankMode = config.FAC_TANK_MODE + + if not (util.is_int(tmp_cfg.FacilityTankMode) and tmp_cfg.FacilityTankMode >= 0 and tmp_cfg.FacilityTankMode <= 8) then + import_err_msg.set_value("Invalid tank mode present in config. FAC_TANK_MODE must be a number 0 through 8.") + main_pane.set_value(7) + return + end + + if config.FAC_TANK_MODE > 0 then + if config.FAC_TANK_DEFS == nil or tmp_cfg.UnitCount ~= #config.FAC_TANK_DEFS then + import_err_msg.set_value("Facility tank definitions table length must match the number of units when using facility tanks.") + main_pane.set_value(7) + return + end + + for i = 1, tmp_cfg.UnitCount do + tmp_cfg.FacilityTankDefs[i] = config.FAC_TANK_DEFS[i] + end + else + tmp_cfg.FacilityTankMode = 0 + tmp_cfg.FacilityTankDefs = {} + end + tmp_cfg.SVR_Channel = config.SVR_CHANNEL tmp_cfg.PLC_Channel = config.PLC_CHANNEL tmp_cfg.RTU_Channel = config.RTU_CHANNEL From 739f04ece9cff325be746d701802a641c39cb04e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 29 Dec 2023 13:58:28 -0500 Subject: [PATCH 08/18] #308 integrated new settings file with supervisor --- coordinator/iocontrol.lua | 10 +-- coordinator/startup.lua | 2 +- supervisor/configure.lua | 22 +++---- supervisor/facility.lua | 2 +- supervisor/session/svsessions.lua | 40 +++++------- supervisor/startup.lua | 80 +++++++++++------------ supervisor/supervisor.lua | 104 +++++++++++++++++++++++------- 7 files changed, 155 insertions(+), 105 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index c8a5f09..3e32d6d 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -110,9 +110,9 @@ function iocontrol.init(conf, comms) -- determine tank information if io.facility.tank_mode == 0 then io.facility.tank_defs = {} - -- on facility tank mode 0, setup tank defs to match unit TANK option + -- on facility tank mode 0, setup tank defs to match unit tank option for i = 1, conf.num_units do - io.facility.tank_defs[i] = util.trinary(conf.cooling.r_cool[i].TANK, 1, 0) + io.facility.tank_defs[i] = util.trinary(conf.cooling.r_cool[i].TankConnection, 1, 0) end io.facility.tank_list = { table.unpack(io.facility.tank_defs) } @@ -214,7 +214,7 @@ function iocontrol.init(conf, comms) num_boilers = 0, num_turbines = 0, num_snas = 0, - has_tank = conf.cooling.r_cool[i].TANK, + has_tank = conf.cooling.r_cool[i].TankConnection, control_state = false, burn_rate_cmd = 0.0, @@ -295,13 +295,13 @@ function iocontrol.init(conf, comms) end -- create boiler tables - for _ = 1, conf.cooling.r_cool[i].BOILERS do + for _ = 1, conf.cooling.r_cool[i].BoilerCount do table.insert(entry.boiler_ps_tbl, psil.create()) table.insert(entry.boiler_data_tbl, {}) end -- create turbine tables - for _ = 1, conf.cooling.r_cool[i].TURBINES do + for _ = 1, conf.cooling.r_cool[i].TurbineCount do table.insert(entry.turbine_ps_tbl, psil.create()) table.insert(entry.turbine_data_tbl, {}) end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 3df5ba2..3a52029 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.18" +local COORDINATOR_VERSION = "v1.1.0" local println = util.println local println_ts = util.println_ts diff --git a/supervisor/configure.lua b/supervisor/configure.lua index 5639660..5a48610 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -96,17 +96,17 @@ local tmp_cfg = { CoolingConfig = {}, FacilityTankMode = 0, FacilityTankDefs = {}, - SVR_Channel = nil, - PLC_Channel = nil, - RTU_Channel = nil, - CRD_Channel = nil, - PKT_Channel = nil, - PLC_Timeout = nil, - RTU_Timeout = nil, - CRD_Timeout = nil, - PKT_Timeout = nil, - TrustedRange = nil, - AuthKey = nil, + SVR_Channel = nil, ---@type integer + PLC_Channel = nil, ---@type integer + RTU_Channel = nil, ---@type integer + CRD_Channel = nil, ---@type integer + PKT_Channel = nil, ---@type integer + PLC_Timeout = nil, ---@type number + RTU_Timeout = nil, ---@type number + CRD_Timeout = nil, ---@type number + PKT_Timeout = nil, ---@type number + TrustedRange = nil, ---@type number + AuthKey = nil, ---@type string|nil LogMode = 0, LogPath = "", LogDebug = false, diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 81fd531..ce98d1f 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -135,7 +135,7 @@ function facility.new(num_reactors, cooling_conf) -- create units for i = 1, num_reactors do - table.insert(self.units, unit.new(i, cooling_conf.r_cool[i].BOILERS, cooling_conf.r_cool[i].TURBINES)) + table.insert(self.units, unit.new(i, cooling_conf.r_cool[i].BoilerCount, cooling_conf.r_cool[i].TurbineCount)) table.insert(self.group_map, 0) end diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 166a067..68d2587 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -2,16 +2,14 @@ local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") local util = require("scada-common.util") -local config = require("supervisor.config") local databus = require("supervisor.databus") local facility = require("supervisor.facility") -local svqtypes = require("supervisor.session.svqtypes") - local coordinator = require("supervisor.session.coordinator") local plc = require("supervisor.session.plc") local pocket = require("supervisor.session.pocket") local rtu = require("supervisor.session.rtu") +local svqtypes = require("supervisor.session.svqtypes") -- Supervisor Sessions Handler @@ -36,6 +34,7 @@ svsessions.SESSION_TYPE = SESSION_TYPE local self = { nic = nil, ---@type nic|nil fp_ok = false, + config = nil, ---@type svr_config num_reactors = 0, facility = nil, ---@type facility|nil sessions = { rtu = {}, plc = {}, crd = {}, pdg = {} }, @@ -60,7 +59,7 @@ local function _sv_handle_outq(session) if msg ~= nil then if msg.qtype == mqueue.TYPE.PACKET then -- handle a packet to be sent - self.nic.transmit(session.r_chan, config.SVR_CHANNEL, msg.message) + self.nic.transmit(session.r_chan, self.config.SVR_Channel, msg.message) elseif msg.qtype == mqueue.TYPE.COMMAND then -- handle instruction/notification elseif msg.qtype == mqueue.TYPE.DATA then @@ -135,7 +134,7 @@ local function _shutdown(session) while session.out_queue.ready() do local msg = session.out_queue.pop() if msg ~= nil and msg.qtype == mqueue.TYPE.PACKET then - self.nic.transmit(session.r_chan, config.SVR_CHANNEL, msg.message) + self.nic.transmit(session.r_chan, self.config.SVR_Channel, msg.message) end end @@ -197,13 +196,13 @@ end -- initialize svsessions ---@param nic nic network interface device ---@param fp_ok boolean front panel active ----@param num_reactors integer number of reactors +---@param config svr_config supervisor configuration ---@param cooling_conf sv_cooling_conf cooling configuration definition -function svsessions.init(nic, fp_ok, num_reactors, cooling_conf) +function svsessions.init(nic, fp_ok, config, cooling_conf) self.nic = nic self.fp_ok = fp_ok - self.num_reactors = num_reactors - self.facility = facility.new(num_reactors, cooling_conf) + self.config = config + self.facility = facility.new(config.UnitCount, cooling_conf) end -- find an RTU session by the computer ID @@ -287,7 +286,7 @@ function svsessions.establish_plc_session(source_addr, for_reactor, version) open = true, reactor = for_reactor, version = version, - r_chan = config.PLC_CHANNEL, + r_chan = self.config.PLC_Channel, s_addr = source_addr, in_queue = mqueue.new(), out_queue = mqueue.new(), @@ -296,8 +295,7 @@ function svsessions.establish_plc_session(source_addr, for_reactor, version) local id = self.next_ids.plc - plc_s.instance = plc.new_session(id, source_addr, for_reactor, plc_s.in_queue, plc_s.out_queue, - config.PLC_TIMEOUT, self.fp_ok) + plc_s.instance = plc.new_session(id, source_addr, for_reactor, plc_s.in_queue, plc_s.out_queue, self.config.PLC_Timeout, self.fp_ok) table.insert(self.sessions.plc, plc_s) local units = self.facility.get_units() @@ -305,8 +303,7 @@ function svsessions.establish_plc_session(source_addr, for_reactor, version) local mt = { ---@param s plc_session_struct - __tostring = function (s) return util.c("PLC [", s.instance.get_id(), "] for reactor #", s.reactor, - " (@", s.s_addr, ")") end + __tostring = function (s) return util.c("PLC [", s.instance.get_id(), "] for reactor #", s.reactor, " (@", s.s_addr, ")") end } setmetatable(plc_s, mt) @@ -336,7 +333,7 @@ function svsessions.establish_rtu_session(source_addr, advertisement, version) s_type = "rtu", open = true, version = version, - r_chan = config.RTU_CHANNEL, + r_chan = self.config.RTU_Channel, s_addr = source_addr, in_queue = mqueue.new(), out_queue = mqueue.new(), @@ -345,8 +342,7 @@ function svsessions.establish_rtu_session(source_addr, advertisement, version) local id = self.next_ids.rtu - rtu_s.instance = rtu.new_session(id, source_addr, rtu_s.in_queue, rtu_s.out_queue, config.RTU_TIMEOUT, - advertisement, self.facility, self.fp_ok) + rtu_s.instance = rtu.new_session(id, source_addr, rtu_s.in_queue, rtu_s.out_queue, self.config.RTU_Timeout, advertisement, self.facility, self.fp_ok) table.insert(self.sessions.rtu, rtu_s) local mt = { @@ -377,7 +373,7 @@ function svsessions.establish_crd_session(source_addr, version) s_type = "crd", open = true, version = version, - r_chan = config.CRD_CHANNEL, + r_chan = self.config.CRD_Channel, s_addr = source_addr, in_queue = mqueue.new(), out_queue = mqueue.new(), @@ -386,8 +382,7 @@ function svsessions.establish_crd_session(source_addr, version) local id = self.next_ids.crd - crd_s.instance = coordinator.new_session(id, source_addr, crd_s.in_queue, crd_s.out_queue, config.CRD_TIMEOUT, - self.facility, self.fp_ok) + crd_s.instance = coordinator.new_session(id, source_addr, crd_s.in_queue, crd_s.out_queue, self.config.CRD_Timeout, self.facility, self.fp_ok) table.insert(self.sessions.crd, crd_s) local mt = { @@ -421,7 +416,7 @@ function svsessions.establish_pdg_session(source_addr, version) s_type = "pkt", open = true, version = version, - r_chan = config.PKT_CHANNEL, + r_chan = self.config.PKT_Channel, s_addr = source_addr, in_queue = mqueue.new(), out_queue = mqueue.new(), @@ -430,8 +425,7 @@ function svsessions.establish_pdg_session(source_addr, version) local id = self.next_ids.pdg - pdg_s.instance = pocket.new_session(id, source_addr, pdg_s.in_queue, pdg_s.out_queue, config.PKT_TIMEOUT, self.facility, - self.fp_ok) + pdg_s.instance = pocket.new_session(id, source_addr, pdg_s.in_queue, pdg_s.out_queue, self.config.PKT_Timeout, self.facility, self.fp_ok) table.insert(self.sessions.pdg, pdg_s) local mt = { diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 2ff69ad..b88cd4f 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,70 +14,66 @@ local util = require("scada-common.util") local core = require("graphics.core") -local config = require("supervisor.config") +local configure = require("supervisor.configure") local databus = require("supervisor.databus") local renderer = require("supervisor.renderer") local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.1.0" +local SUPERVISOR_VERSION = "v1.2.0" local println = util.println local println_ts = util.println_ts ---------------------------------------- --- config validation +-- get configuration ---------------------------------------- +if not supervisor.load_config() then + -- try to reconfigure (user action) + local success, error = configure.configure(true) + if success then + assert(supervisor.load_config(), "failed to load valid supervisor configuration") + else + assert(success, "supervisor configuration error: " .. error) + end +end + +local config = supervisor.config + local cfv = util.new_validator() -cfv.assert_channel(config.SVR_CHANNEL) -cfv.assert_channel(config.PLC_CHANNEL) -cfv.assert_channel(config.RTU_CHANNEL) -cfv.assert_channel(config.CRD_CHANNEL) -cfv.assert_channel(config.PKT_CHANNEL) -cfv.assert_type_int(config.TRUSTED_RANGE) -cfv.assert_type_num(config.PLC_TIMEOUT) -cfv.assert_min(config.PLC_TIMEOUT, 2) -cfv.assert_type_num(config.RTU_TIMEOUT) -cfv.assert_min(config.RTU_TIMEOUT, 2) -cfv.assert_type_num(config.CRD_TIMEOUT) -cfv.assert_min(config.CRD_TIMEOUT, 2) -cfv.assert_type_num(config.PKT_TIMEOUT) -cfv.assert_min(config.PKT_TIMEOUT, 2) -cfv.assert_type_int(config.NUM_REACTORS) -cfv.assert_type_table(config.REACTOR_COOLING) -cfv.assert_type_int(config.FAC_TANK_MODE) -cfv.assert_type_table(config.FAC_TANK_DEFS) -cfv.assert_type_str(config.LOG_PATH) -cfv.assert_type_int(config.LOG_MODE) +assert((config.FacilityTankMode == 0) or (config.UnitCount == #config.FacilityTankDefs), + "startup> FacilityTankDefs length not equal to UnitCount") -assert(cfv.valid(), "bad config file: missing/invalid fields") +for i = 1, config.UnitCount do + local def = config.FacilityTankDefs[i] + cfv.assert_type_int(def) + cfv.assert_range(def, 0, 2) + assert(cfv.valid(), "startup> invalid facility tank definition for reactor unit " .. i) +end -assert((config.FAC_TANK_MODE == 0) or (config.NUM_REACTORS == #config.FAC_TANK_DEFS), - "bad config file: FAC_TANK_DEFS length not equal to NUM_REACTORS") +cfv.assert_eq(#config.CoolingConfig, config.UnitCount) +assert(cfv.valid(), "startup> the number of reactor cooling configurations is different than the number of units") -cfv.assert_eq(#config.REACTOR_COOLING, config.NUM_REACTORS) -assert(cfv.valid(), "config: number of cooling configs different than number of units") - -for i = 1, config.NUM_REACTORS do - cfv.assert_type_table(config.REACTOR_COOLING[i]) - assert(cfv.valid(), "config: missing cooling entry for reactor " .. i) - cfv.assert_type_int(config.REACTOR_COOLING[i].BOILERS) - cfv.assert_type_int(config.REACTOR_COOLING[i].TURBINES) - cfv.assert_type_bool(config.REACTOR_COOLING[i].TANK) - assert(cfv.valid(), "config: missing boilers/turbines for reactor " .. i) - cfv.assert_min(config.REACTOR_COOLING[i].BOILERS, 0) - cfv.assert_min(config.REACTOR_COOLING[i].TURBINES, 1) - assert(cfv.valid(), "config: bad number of boilers/turbines for reactor " .. i) +for i = 1, config.UnitCount do + cfv.assert_type_table(config.CoolingConfig[i]) + assert(cfv.valid(), "startup> missing cooling entry for reactor unit " .. i) + cfv.assert_type_int(config.CoolingConfig[i].BoilerCount) + cfv.assert_type_int(config.CoolingConfig[i].TurbineCount) + cfv.assert_type_bool(config.CoolingConfig[i].TankConnection) + assert(cfv.valid(), "startup> missing boiler/turbine/tank fields for reactor unit " .. i) + cfv.assert_range(config.CoolingConfig[i].BoilerCount, 0, 2) + cfv.assert_range(config.CoolingConfig[i].TurbineCount, 1, 3) + assert(cfv.valid(), "startup> out-of-range number of boilers and/or turbines provided for reactor unit " .. i) end ---------------------------------------- -- log init ---------------------------------------- -log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG == true) +log.init(config.LogPath, config.LogMode, config.LogDebug == true) log.info("========================================") log.info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION) @@ -102,8 +98,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 diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 498a51d..755893f 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -2,8 +2,6 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local util = require("scada-common.util") -local config = require("supervisor.config") - local svsessions = require("supervisor.session.svsessions") local supervisor = {} @@ -13,6 +11,77 @@ local DEVICE_TYPE = comms.DEVICE_TYPE local ESTABLISH_ACK = comms.ESTABLISH_ACK local MGMT_TYPE = comms.MGMT_TYPE +---@type svr_config +local config = {} + +supervisor.config = config + +-- load the supervisor configuration +function supervisor.load_config() + if not settings.load("/supervisor.settings") then return false end + + config.UnitCount = settings.get("UnitCount") + config.CoolingConfig = settings.get("CoolingConfig") + config.FacilityTankMode = settings.get("FacilityTankMode") + config.FacilityTankDefs = settings.get("FacilityTankDefs") + + config.SVR_Channel = settings.get("SVR_Channel") + config.PLC_Channel = settings.get("PLC_Channel") + config.RTU_Channel = settings.get("RTU_Channel") + config.CRD_Channel = settings.get("CRD_Channel") + config.PKT_Channel = settings.get("PKT_Channel") + + config.PLC_Timeout = settings.get("PLC_Timeout") + config.RTU_Timeout = settings.get("RTU_Timeout") + config.CRD_Timeout = settings.get("CRD_Timeout") + config.PKT_Timeout = settings.get("PKT_Timeout") + + 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_int(config.UnitCount) + cfv.assert_range(config.UnitCount, 1, 4) + + cfv.assert_type_table(config.CoolingConfig) + cfv.assert_type_table(config.FacilityTankDefs) + cfv.assert_type_int(config.FacilityTankMode) + cfv.assert_range(config.FacilityTankMode, 0, 8) + + cfv.assert_channel(config.SVR_Channel) + cfv.assert_channel(config.PLC_Channel) + cfv.assert_channel(config.RTU_Channel) + cfv.assert_channel(config.CRD_Channel) + cfv.assert_channel(config.PKT_Channel) + + cfv.assert_type_num(config.PLC_Timeout) + cfv.assert_min(config.PLC_Timeout, 2) + cfv.assert_type_num(config.RTU_Timeout) + cfv.assert_min(config.RTU_Timeout, 2) + cfv.assert_type_num(config.CRD_Timeout) + cfv.assert_min(config.CRD_Timeout, 2) + cfv.assert_type_num(config.PKT_Timeout) + cfv.assert_min(config.PKT_Timeout, 2) + + cfv.assert_type_num(config.TrustedRange) + + 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 + -- supervisory controller communications ---@nodiscard ---@param _version string supervisor version @@ -23,32 +92,23 @@ function supervisor.comms(_version, nic, fp_ok) -- print a log message to the terminal as long as the UI isn't running local function println(message) if not fp_ok then util.println_ts(message) end end - -- channel list from config - local svr_channel = config.SVR_CHANNEL - local plc_channel = config.PLC_CHANNEL - local rtu_channel = config.RTU_CHANNEL - local crd_channel = config.CRD_CHANNEL - local pkt_channel = config.PKT_CHANNEL - - -- configuration data - local num_reactors = config.NUM_REACTORS ---@class sv_cooling_conf - local cooling_conf = { r_cool = config.REACTOR_COOLING, fac_tank_mode = config.FAC_TANK_MODE, fac_tank_defs = config.FAC_TANK_DEFS } + local cooling_conf = { r_cool = config.CoolingConfig, fac_tank_mode = config.FacilityTankMode, fac_tank_defs = config.FacilityTankDefs } local self = { last_est_acks = {} } - comms.set_trusted_range(config.TRUSTED_RANGE) + comms.set_trusted_range(config.TrustedRange) -- PRIVATE FUNCTIONS -- -- configure modem channels nic.closeAll() - nic.open(svr_channel) + nic.open(config.SVR_Channel) -- pass modem, status, and config data to svsessions - svsessions.init(nic, fp_ok, num_reactors, cooling_conf) + svsessions.init(nic, fp_ok, config, cooling_conf) -- send an establish request response ---@param packet scada_packet @@ -61,7 +121,7 @@ function supervisor.comms(_version, nic, fp_ok) m_pkt.make(MGMT_TYPE.ESTABLISH, { ack, data }) s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) - nic.transmit(packet.remote_channel(), svr_channel, s_pkt) + nic.transmit(packet.remote_channel(), config.SVR_Channel, s_pkt) self.last_est_acks[packet.src_addr()] = ack end @@ -124,9 +184,9 @@ function supervisor.comms(_version, nic, fp_ok) local src_addr = packet.scada_frame.src_addr() local protocol = packet.scada_frame.protocol() - if l_chan ~= svr_channel then + if l_chan ~= config.SVR_Channel then log.debug("received packet on unconfigured channel " .. l_chan, true) - elseif r_chan == plc_channel then + elseif r_chan == config.PLC_Channel then -- look for an associated session local session = svsessions.find_plc_session(src_addr) @@ -201,7 +261,7 @@ function supervisor.comms(_version, nic, fp_ok) else log.debug(util.c("illegal packet type ", protocol, " on PLC channel")) end - elseif r_chan == rtu_channel then + elseif r_chan == config.RTU_Channel then -- look for an associated session local session = svsessions.find_rtu_session(src_addr) @@ -265,7 +325,7 @@ function supervisor.comms(_version, nic, fp_ok) else log.debug(util.c("illegal packet type ", protocol, " on RTU channel")) end - elseif r_chan == crd_channel then + elseif r_chan == config.CRD_Channel then -- look for an associated session local session = svsessions.find_crd_session(src_addr) @@ -299,7 +359,7 @@ function supervisor.comms(_version, nic, fp_ok) println(util.c("CRD (", firmware_v, ") [@", src_addr, "] \xbb connected")) log.info(util.c("CRD_ESTABLISH: coordinator (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW, { num_reactors, cooling_conf }) + _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, cooling_conf }) else if last_ack ~= ESTABLISH_ACK.COLLISION then log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator") @@ -332,7 +392,7 @@ function supervisor.comms(_version, nic, fp_ok) else log.debug(util.c("illegal packet type ", protocol, " on coordinator channel")) end - elseif r_chan == pkt_channel then + elseif r_chan == config.PKT_Channel then -- look for an associated session local session = svsessions.find_pdg_session(src_addr) From 363f164f472e145f801151257a5ed0035cb66b00 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 29 Dec 2023 14:12:54 -0500 Subject: [PATCH 09/18] #308 deleted old config.lua --- supervisor/config.lua | 56 ------------------------------------------- 1 file changed, 56 deletions(-) delete mode 100644 supervisor/config.lua diff --git a/supervisor/config.lua b/supervisor/config.lua deleted file mode 100644 index 5ea565b..0000000 --- a/supervisor/config.lua +++ /dev/null @@ -1,56 +0,0 @@ -local config = {} - --- supervisor comms channel -config.SVR_CHANNEL = 16240 --- PLC comms channel -config.PLC_CHANNEL = 16241 --- RTU/MODBUS comms channel -config.RTU_CHANNEL = 16242 --- coordinator comms channel -config.CRD_CHANNEL = 16243 --- pocket comms channel -config.PKT_CHANNEL = 16244 --- 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.PLC_TIMEOUT = 5 -config.RTU_TIMEOUT = 5 -config.CRD_TIMEOUT = 5 -config.PKT_TIMEOUT = 5 --- facility authentication key --- (do NOT use one of your passwords) --- this enables verifying that messages are authentic --- all devices on this network must use this key --- config.AUTH_KEY = "SCADAfacility123" - --- expected number of reactors -config.NUM_REACTORS = 4 --- expected number of devices for each unit -config.REACTOR_COOLING = { --- reactor unit 1 -{ BOILERS = 1, TURBINES = 1, TANK = false }, --- reactor unit 2 -{ BOILERS = 1, TURBINES = 1, TANK = false }, --- reactor unit 3 -{ BOILERS = 1, TURBINES = 1, TANK = false }, --- reactor unit 4 -{ BOILERS = 1, TURBINES = 1, TANK = false } -} --- advanced facility dynamic tank configuration --- (see wiki for details) --- by default, dynamic tanks are for each unit -config.FAC_TANK_MODE = 0 -config.FAC_TANK_DEFS = { 0, 0, 0, 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 - -return config From 338b3b161500f9a383699ac532c30e5307d1feca Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 29 Dec 2023 14:29:46 -0500 Subject: [PATCH 10/18] addressed luacheck warning --- supervisor/configure.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/supervisor/configure.lua b/supervisor/configure.lua index 5a48610..c7fe5f2 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -361,7 +361,8 @@ local function config_view(display) tmp_cfg.FacilityTankDefs = {} for i = 1, tmp_cfg.UnitCount do - local def = tmp_cfg.FacilityTankDefs[i] + local def + if tmp_cfg.CoolingConfig[i].TankConnection then def = tool_ctl.tank_elems[i].tank_opt.get_value() any_fac = any_fac or (def == 2) From 2a85a438ba18dcbcecd9b37ccbe6f5b3cb6d7e6a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 29 Dec 2023 14:33:22 -0500 Subject: [PATCH 11/18] #396 connection timeouts can now have a fractional part --- reactor-plc/configure.lua | 19 ++++++++++--------- reactor-plc/startup.lua | 2 +- rtu/configure.lua | 16 +++++++++------- rtu/startup.lua | 2 +- supervisor/configure.lua | 8 ++++---- 5 files changed, 25 insertions(+), 22 deletions(-) diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index 7b5ab4c..4e423be 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -34,7 +34,8 @@ local RIGHT = core.ALIGN.RIGHT -- changes to the config data/format to let the user know local changes = { - {"v1.6.2", { "AuthKey minimum length is now 8 (if set)" } } + {"v1.6.2", { "AuthKey minimum length is now 8 (if set)" } }, + {"v1.6.8", { "ConnTimeout can now have a fractional part" } } } ---@class plc_configurator @@ -92,13 +93,13 @@ local tmp_cfg = { Networked = false, UnitID = 0, EmerCoolEnable = false, - EmerCoolSide = nil, - EmerCoolColor = nil, - SVR_Channel = nil, - PLC_Channel = nil, - ConnTimeout = nil, - TrustedRange = nil, - AuthKey = nil, + EmerCoolSide = nil, ---@type string|nil + EmerCoolColor = nil, ---@type color|nil + SVR_Channel = nil, ---@type integer + PLC_Channel = nil, ---@type integer + ConnTimeout = nil, ---@type number + TrustedRange = nil, ---@type number + AuthKey = nil, ---@type string|nil LogMode = 0, LogPath = "", LogDebug = false, @@ -332,7 +333,7 @@ local function config_view(display) PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} 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} + local timeout = NumberField{parent=net_c_2,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_digits=6,allow_decimal=true,fg_bg=bw_fg_bg} TextBox{parent=net_c_2,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg} TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg} diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index c33a780..803fe99 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.7" +local R_PLC_VERSION = "v1.6.8" local println = util.println local println_ts = util.println_ts diff --git a/rtu/configure.lua b/rtu/configure.lua index 9bdb5ee..bee0ed6 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -72,7 +72,9 @@ 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 = {} +local changes = { + {"v1.7.9", { "ConnTimeout can now have a fractional part" } } +} ---@class rtu_rs_definition ---@field unit integer|nil @@ -172,11 +174,11 @@ local tmp_cfg = { SpeakerVolume = 1.0, Peripherals = {}, Redstone = {}, - SVR_Channel = nil, - RTU_Channel = nil, - ConnTimeout = nil, - TrustedRange = nil, - AuthKey = nil, + SVR_Channel = nil, ---@type integer + RTU_Channel = nil, ---@type integer + ConnTimeout = nil, ---@type number + TrustedRange = nil, ---@type number + AuthKey = nil, ---@type string|nil LogMode = 0, LogPath = "", LogDebug = false @@ -394,7 +396,7 @@ local function config_view(display) PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} 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} + local timeout = NumberField{parent=net_c_2,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_digits=6,allow_decimal=true,fg_bg=bw_fg_bg} TextBox{parent=net_c_2,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg} TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg} diff --git a/rtu/startup.lua b/rtu/startup.lua index 63a9165..1b25859 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.8" +local RTU_VERSION = "v1.7.9" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE diff --git a/supervisor/configure.lua b/supervisor/configure.lua index c7fe5f2..f3c0c5d 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -624,16 +624,16 @@ local function config_view(display) TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally should not need to modify these. On slow servers, you can try to increase this to make the system wait longer before assuming a disconnection. The default for all is 5 seconds.",fg_bg=g_lg_fg_bg} TextBox{parent=net_c_2,x=1,y=8,height=1,width=11,text="PLC Timeout"} - local plc_timeout = NumberField{parent=net_c_2,x=21,y=8,width=7,default=ini_cfg.PLC_Timeout,min=2,max=25,fg_bg=bw_fg_bg} + local plc_timeout = NumberField{parent=net_c_2,x=21,y=8,width=7,default=ini_cfg.PLC_Timeout,min=2,max=25,max_digits=6,allow_decimal=true,fg_bg=bw_fg_bg} TextBox{parent=net_c_2,x=1,y=9,height=1,width=19,text="RTU Gateway Timeout"} - local rtu_timeout = NumberField{parent=net_c_2,x=21,y=9,width=7,default=ini_cfg.RTU_Timeout,min=2,max=25,fg_bg=bw_fg_bg} + local rtu_timeout = NumberField{parent=net_c_2,x=21,y=9,width=7,default=ini_cfg.RTU_Timeout,min=2,max=25,max_digits=6,allow_decimal=true,fg_bg=bw_fg_bg} TextBox{parent=net_c_2,x=1,y=10,height=1,width=19,text="Coordinator Timeout"} - local crd_timeout = NumberField{parent=net_c_2,x=21,y=10,width=7,default=ini_cfg.CRD_Timeout,min=2,max=25,fg_bg=bw_fg_bg} + local crd_timeout = NumberField{parent=net_c_2,x=21,y=10,width=7,default=ini_cfg.CRD_Timeout,min=2,max=25,max_digits=6,allow_decimal=true,fg_bg=bw_fg_bg} TextBox{parent=net_c_2,x=1,y=11,height=1,width=14,text="Pocket Timeout"} - local pkt_timeout = NumberField{parent=net_c_2,x=21,y=11,width=7,default=ini_cfg.PKT_Timeout,min=2,max=25,fg_bg=bw_fg_bg} + local pkt_timeout = NumberField{parent=net_c_2,x=21,y=11,width=7,default=ini_cfg.PKT_Timeout,min=2,max=25,max_digits=6,allow_decimal=true,fg_bg=bw_fg_bg} TextBox{parent=net_c_2,x=29,y=8,height=4,width=7,text="seconds\nseconds\nseconds\nseconds",fg_bg=g_lg_fg_bg} From 42cd9fff0c19e61d81034c18d310a6f36a466b40 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 30 Dec 2023 14:41:03 -0500 Subject: [PATCH 12/18] improved number field precision handling and limited decimal precision of timeouts #396 --- graphics/core.lua | 2 +- graphics/elements/form/number_field.lua | 50 ++++++++++++++++++++++--- reactor-plc/configure.lua | 6 +-- rtu/configure.lua | 12 +++--- scada-common/util.lua | 13 ++++++- supervisor/configure.lua | 18 ++++----- 6 files changed, 74 insertions(+), 27 deletions(-) diff --git a/graphics/core.lua b/graphics/core.lua index b38d786..13f97ec 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -7,7 +7,7 @@ local flasher = require("graphics.flasher") local core = {} -core.version = "2.0.9" +core.version = "2.1.0" core.flasher = flasher core.events = events diff --git a/graphics/elements/form/number_field.lua b/graphics/elements/form/number_field.lua index ddc09be..cef9905 100644 --- a/graphics/elements/form/number_field.lua +++ b/graphics/elements/form/number_field.lua @@ -1,5 +1,7 @@ -- Numeric Value Entry Graphics Element +local util = require("scada-common.util") + local core = require("graphics.core") local element = require("graphics.element") @@ -8,9 +10,11 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK ---@class number_field_args ---@field default? number default value, defaults to 0 ----@field min? number minimum, forced on unfocus ----@field max? number maximum, forced on unfocus ----@field max_digits? integer maximum number of digits, defaults to width +---@field min? number minimum, enforced on unfocus +---@field max? number maximum, enforced on unfocus +---@field max_chars? integer maximum number of characters, defaults to width +---@field max_int_digits? integer maximum number of integer digits, enforced on unfocus +---@field max_frac_digits? integer maximum number of fractional digits, enforced on unfocus ---@field allow_decimal? boolean true to allow decimals ---@field allow_negative? boolean true to allow negative numbers ---@field dis_fg_bg? cpair foreground/background colors when disabled @@ -26,6 +30,9 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK ---@param args number_field_args ---@return graphics_element element, element_id id local function number_field(args) + element.assert(args.max_int_digits == nil or (util.is_int(args.max_int_digits) and args.max_int_digits > 0), "max_int_digits must be an integer greater than zero if supplied") + element.assert(args.max_frac_digits == nil or (util.is_int(args.max_frac_digits) and args.max_frac_digits > 0), "max_frac_digits must be an integer greater than zero if supplied") + args.height = 1 args.can_focus = true @@ -34,13 +41,13 @@ local function number_field(args) local has_decimal = false - args.max_digits = args.max_digits or e.frame.w + args.max_chars = args.max_chars or e.frame.w -- set initial value e.value = "" .. (args.default or 0) -- make an interactive field manager - local ifield = core.new_ifield(e, args.max_digits, args.fg_bg, args.dis_fg_bg) + local ifield = core.new_ifield(e, args.max_chars, args.fg_bg, args.dis_fg_bg) -- handle mouse interaction ---@param event mouse_interaction mouse event @@ -62,7 +69,7 @@ local function number_field(args) -- handle keyboard interaction ---@param event key_interaction key event function e.handle_key(event) - if event.type == KEY_CLICK.CHAR and string.len(e.value) < args.max_digits then + if event.type == KEY_CLICK.CHAR and string.len(e.value) < args.max_chars then if tonumber(event.name) then if e.value == 0 then e.value = "" end ifield.try_insert_char(event.name) @@ -127,6 +134,37 @@ local function number_field(args) local min = tonumber(args.min) if type(val) == "number" then + if util.is_int(args.max_int_digits) or args.max_frac_digits then + local str = e.value + local ceil = false + + if string.find(str, "-") then str = string.sub(e.value, 2) end + local parts = util.strtok(str, ".") + + if parts[1] and args.max_int_digits then + if string.len(parts[1]) > args.max_int_digits then + parts[1] = string.rep("9", args.max_int_digits) + ceil = true + end + end + + if args.allow_decimal and args.max_frac_digits then + if ceil then + parts[2] = string.rep("9", args.max_frac_digits) + elseif parts[2] and (string.len(parts[2]) > args.max_frac_digits) then + -- add a half of the highest precision fractional value in order to round using floor + local scaled = math.fmod(val, 1) * (10 ^ (args.max_frac_digits)) + local value = math.floor(scaled + 0.5) + local unscaled = value * (10 ^ (-args.max_frac_digits)) + parts[2] = string.sub(tostring(unscaled), 3) -- remove starting "0." + end + end + + if parts[2] then parts[2] = "." .. parts[2] else parts[2] = "" end + + val = tonumber((parts[1] or "") .. parts[2]) + end + if type(args.max) == "number" and val > max then e.value = "" .. max ifield.nav_start() diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index 4e423be..fbefb97 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -240,7 +240,7 @@ local function config_view(display) 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="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 = NumberField{parent=plc_c_2,x=7,y=6,width=5,max_chars=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="Please set a unit ID.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} @@ -333,12 +333,12 @@ local function config_view(display) PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} 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,max_digits=6,allow_decimal=true,fg_bg=bw_fg_bg} + local timeout = NumberField{parent=net_c_2,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} TextBox{parent=net_c_2,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg} TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg} TextBox{parent=net_c_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} + local range = NumberField{parent=net_c_2,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg} TextBox{parent=net_c_2,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} 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} diff --git a/rtu/configure.lua b/rtu/configure.lua index bee0ed6..2b6239e 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -333,7 +333,7 @@ local function config_view(display) 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} + local s_vol = NumberField{parent=spkr_c,x=1,y=8,width=9,max_chars=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="Note: alarm sine waves are at half scale so that multiple will be required to reach full scale.",fg_bg=g_lg_fg_bg} @@ -396,12 +396,12 @@ local function config_view(display) PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} 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,max_digits=6,allow_decimal=true,fg_bg=bw_fg_bg} + local timeout = NumberField{parent=net_c_2,x=1,y=2,width=7,default=ini_cfg.ConnTimeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} TextBox{parent=net_c_2,x=9,y=2,height=2,text="seconds (default 5)",fg_bg=g_lg_fg_bg} TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally do not want or need to modify this. On slow servers, you can increase this to make the system wait longer before assuming a disconnection.",fg_bg=g_lg_fg_bg} TextBox{parent=net_c_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} + local range = NumberField{parent=net_c_2,x=1,y=9,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg} TextBox{parent=net_c_2,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} 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} @@ -831,11 +831,11 @@ local function config_view(display) 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_idx = NumberField{parent=peri_c_4,x=14,y=4,width=4,max_chars=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=")"} - 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 = NumberField{parent=peri_c_4,x=44,y=4,width=4,max_chars=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) @@ -1054,7 +1054,7 @@ local function config_view(display) 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="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} + tool_ctl.rs_cfg_unit = NumberField{parent=rs_c_3,x=27,y=4,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg} tool_ctl.rs_cfg_side_l = 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} diff --git a/scada-common/util.lua b/scada-common/util.lua index da80782..28974de 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -22,7 +22,7 @@ local t_pack = table.pack local util = {} -- scada-common version -util.version = "1.1.11" +util.version = "1.1.12" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 @@ -78,6 +78,17 @@ function util.strval(val) else return tostring(val) end end +-- tokenize a string by a separator
+-- does not behave exactly like C's strtok +---@param str string string to tokenize +---@param sep string separator to tokenize by +---@return table token_list +function util.strtok(str, sep) + local list = {} + for part in string.gmatch(str, "([^" .. sep .. "]+)") do t_insert(list, part) end + return list +end + -- repeat a space n times ---@nodiscard ---@param n integer diff --git a/supervisor/configure.lua b/supervisor/configure.lua index f3c0c5d..144c592 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -31,8 +31,6 @@ local LEFT = core.ALIGN.LEFT local CENTER = core.ALIGN.CENTER local RIGHT = core.ALIGN.RIGHT -log.init("log.txt", log.MODE.APPEND, true) - -- changes to the config data/format to let the user know local changes = {} @@ -217,7 +215,7 @@ local function config_view(display) TextBox{parent=svr_cfg,x=1,y=2,height=1,text=" Facility Configuration",fg_bg=cpair(colors.black,colors.green)} TextBox{parent=svr_c_1,x=1,y=1,height=3,text="Please enter the number of reactors you have, also referred to as reactor units or 'units' for short. A maximum of 4 is currently supported."} - local num_units = NumberField{parent=svr_c_1,x=1,y=5,width=5,max_digits=2,default=ini_cfg.UnitCount,min=1,max=4,fg_bg=bw_fg_bg} + local num_units = NumberField{parent=svr_c_1,x=1,y=5,width=5,max_chars=2,default=ini_cfg.UnitCount,min=1,max=4,fg_bg=bw_fg_bg} TextBox{parent=svr_c_1,x=7,y=5,height=1,text="reactors"} local nu_error = TextBox{parent=svr_c_1,x=8,y=14,height=1,width=35,text="Please set the number of reactors.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} @@ -256,8 +254,8 @@ local function config_view(display) local line = Div{parent=svr_c_2,x=1,y=7+i,height=1} TextBox{parent=line,text="Unit "..i,width=6} - local turbines = NumberField{parent=line,x=9,y=1,width=5,max_digits=2,default=num_t,min=1,max=3,fg_bg=bw_fg_bg} - local boilers = NumberField{parent=line,x=20,y=1,width=5,max_digits=2,default=num_b,min=0,max=2,fg_bg=bw_fg_bg} + local turbines = NumberField{parent=line,x=9,y=1,width=5,max_chars=2,default=num_t,min=1,max=3,fg_bg=bw_fg_bg} + local boilers = NumberField{parent=line,x=20,y=1,width=5,max_chars=2,default=num_b,min=0,max=2,fg_bg=bw_fg_bg} local tank = CheckBox{parent=line,x=30,y=1,label="Is Connected",default=has_t,box_fg_bg=cpair(colors.green,colors.black)} tool_ctl.cooling_elems[i] = { line = line, turbines = turbines, boilers = boilers, tank = tank } @@ -624,16 +622,16 @@ local function config_view(display) TextBox{parent=net_c_2,x=1,y=3,height=4,text="You generally should not need to modify these. On slow servers, you can try to increase this to make the system wait longer before assuming a disconnection. The default for all is 5 seconds.",fg_bg=g_lg_fg_bg} TextBox{parent=net_c_2,x=1,y=8,height=1,width=11,text="PLC Timeout"} - local plc_timeout = NumberField{parent=net_c_2,x=21,y=8,width=7,default=ini_cfg.PLC_Timeout,min=2,max=25,max_digits=6,allow_decimal=true,fg_bg=bw_fg_bg} + local plc_timeout = NumberField{parent=net_c_2,x=21,y=8,width=7,default=ini_cfg.PLC_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} TextBox{parent=net_c_2,x=1,y=9,height=1,width=19,text="RTU Gateway Timeout"} - local rtu_timeout = NumberField{parent=net_c_2,x=21,y=9,width=7,default=ini_cfg.RTU_Timeout,min=2,max=25,max_digits=6,allow_decimal=true,fg_bg=bw_fg_bg} + local rtu_timeout = NumberField{parent=net_c_2,x=21,y=9,width=7,default=ini_cfg.RTU_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} TextBox{parent=net_c_2,x=1,y=10,height=1,width=19,text="Coordinator Timeout"} - local crd_timeout = NumberField{parent=net_c_2,x=21,y=10,width=7,default=ini_cfg.CRD_Timeout,min=2,max=25,max_digits=6,allow_decimal=true,fg_bg=bw_fg_bg} + local crd_timeout = NumberField{parent=net_c_2,x=21,y=10,width=7,default=ini_cfg.CRD_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} TextBox{parent=net_c_2,x=1,y=11,height=1,width=14,text="Pocket Timeout"} - local pkt_timeout = NumberField{parent=net_c_2,x=21,y=11,width=7,default=ini_cfg.PKT_Timeout,min=2,max=25,max_digits=6,allow_decimal=true,fg_bg=bw_fg_bg} + local pkt_timeout = NumberField{parent=net_c_2,x=21,y=11,width=7,default=ini_cfg.PKT_Timeout,min=2,max=25,max_chars=6,max_frac_digits=2,allow_decimal=true,fg_bg=bw_fg_bg} TextBox{parent=net_c_2,x=29,y=8,height=4,width=7,text="seconds\nseconds\nseconds\nseconds",fg_bg=g_lg_fg_bg} @@ -655,7 +653,7 @@ local function config_view(display) TextBox{parent=net_c_3,x=1,y=3,height=3,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} TextBox{parent=net_c_3,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg} - local range = NumberField{parent=net_c_3,x=1,y=10,width=10,default=ini_cfg.TrustedRange,min=0,max_digits=20,allow_decimal=true,fg_bg=bw_fg_bg} + local range = NumberField{parent=net_c_3,x=1,y=10,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg} local tr_err = TextBox{parent=net_c_3,x=8,y=14,height=1,width=35,text="Please set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} From 622e2eeb90b46501cb59fcc5519fab53d99f85fc Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 30 Dec 2023 14:51:25 -0500 Subject: [PATCH 13/18] more useful messages, incremented bootloader version --- configure.lua | 4 ++-- startup.lua | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/configure.lua b/configure.lua index b24a5e8..ab0b64e 100644 --- a/configure.lua +++ b/configure.lua @@ -7,9 +7,9 @@ elseif fs.exists("rtu/configure.lua") then elseif fs.exists("supervisor/configure.lua") then require("supervisor.configure").configure() elseif fs.exists("coordinator/startup.lua") then - print("CONFIGURE> COORDINATOR CONFIGURATOR NOT YET IMPLEMENTED IN BETA") + print("CONFIGURE> coordinator configurator not yet implemented (use 'edit coordinator/config.lua' to configure)") elseif fs.exists("pocket/startup.lua") then - print("CONFIGURE> POCKET CONFIGURATOR NOT YET IMPLEMENTED IN BETA") + print("CONFIGURE> pocket configurator not yet implemented (use 'edit pocket/config.lua' to configure)") else print("CONFIGURE> NO CONFIGURATOR FOUND") print("CONFIGURE> EXIT") diff --git a/startup.lua b/startup.lua index 3b2eef1..7dd95f6 100644 --- a/startup.lua +++ b/startup.lua @@ -2,7 +2,7 @@ local util = require("scada-common.util") local println = util.println -local BOOTLOADER_VERSION = "0.4" +local BOOTLOADER_VERSION = "0.5" println("SCADA BOOTLOADER V" .. BOOTLOADER_VERSION) println("BOOT> SCANNING FOR APPLICATIONS...") From 6a931fced4c39045d5156bc6673d056fc06aed5a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 30 Dec 2023 19:21:44 -0500 Subject: [PATCH 14/18] cleanup and fixes --- graphics/elements/form/number_field.lua | 2 +- supervisor/configure.lua | 46 +++++++++++-------------- supervisor/startup.lua | 4 +-- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/graphics/elements/form/number_field.lua b/graphics/elements/form/number_field.lua index cef9905..66731ad 100644 --- a/graphics/elements/form/number_field.lua +++ b/graphics/elements/form/number_field.lua @@ -134,7 +134,7 @@ local function number_field(args) local min = tonumber(args.min) if type(val) == "number" then - if util.is_int(args.max_int_digits) or args.max_frac_digits then + if args.max_int_digits or args.max_frac_digits then local str = e.value local ceil = false diff --git a/supervisor/configure.lua b/supervisor/configure.lua index 144c592..190fd9b 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -235,8 +235,8 @@ local function config_view(display) else nu_error.show() end end - PushButton{parent=svr_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=svr_c_1,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_num_units,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=svr_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=svr_c_1,x=44,y=14,text="Next \x1a",callback=submit_num_units,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} TextBox{parent=svr_c_2,x=1,y=1,height=4,text="Please provide the reactor cooling configuration below. This includes the number of turbines, boilers, and if that reactor has a connection to a dynamic tank for emergency coolant."} TextBox{parent=svr_c_2,x=1,y=6,height=1,text="UNIT TURBINES BOILERS HAS TANK CONNECTION?",fg_bg=g_lg_fg_bg} @@ -275,16 +275,12 @@ local function config_view(display) cool_err.show() else local any_has_tank = false - tool_ctl.num_tank_conns = 0 tmp_cfg.CoolingConfig = {} for i = 1, tmp_cfg.UnitCount do local conf = tool_ctl.cooling_elems[i] tmp_cfg.CoolingConfig[i] = { TurbineCount = tonumber(conf.turbines.get_value()), BoilerCount = tonumber(conf.boilers.get_value()), TankConnection = conf.tank.get_value() } - if conf.tank.get_value() then - any_has_tank = true - tool_ctl.num_tank_conns = tool_ctl.num_tank_conns + 1 - end + if conf.tank.get_value() then any_has_tank = true end end for i = 1, 4 do @@ -305,8 +301,8 @@ local function config_view(display) end end - PushButton{parent=svr_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()svr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=svr_c_2,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_cooling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=svr_c_2,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=svr_c_2,x=44,y=14,text="Next \x1a",callback=submit_cooling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} TextBox{parent=svr_c_3,x=1,y=1,height=6,text="You have set one or more of your units to use dynamic tanks for emergency coolant. You have two paths for configuration. The first is to assign dynamic tanks to reactor units; one tank per reactor, only connected to that reactor. RTU configurations must also assign it as such."} TextBox{parent=svr_c_3,x=1,y=8,height=3,text="Alternatively, you can configure them as facility tanks to connect to multiple reactor units. These can intermingle with unit-specific tanks."} @@ -315,7 +311,6 @@ local function config_view(display) local function submit_en_fac_tank() if en_fac_tanks.get_value() then - assert(tool_ctl.num_tank_conns >= 1, "attempted to enable facility tanks with no tank connections assigned") svr_pane.set_value(4) tmp_cfg.FacilityTankMode = util.trinary(tmp_cfg.FacilityTankMode == 0, 1, math.min(8, math.max(1, ini_cfg.FacilityTankMode))) else @@ -344,11 +339,6 @@ local function config_view(display) local tank_err = TextBox{parent=svr_c_4,x=8,y=14,height=1,width=33,text="You selected no facility tanks.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} - local function show_fconn(i) - if i > 1 then tool_ctl.vis_ftanks[i].pipe_conn.show() - else tool_ctl.vis_ftanks[i].line.show() end - end - local function hide_fconn(i) if i > 1 then tool_ctl.vis_ftanks[i].pipe_conn.hide(true) else tool_ctl.vis_ftanks[i].line.hide(true) end @@ -371,7 +361,10 @@ local function config_view(display) tool_ctl.vis_utanks[i].label.set_value("Tank U" .. i) hide_fconn(i) else - if def == 2 then show_fconn(i) else hide_fconn(i) end + if def == 2 then + if i > 1 then tool_ctl.vis_ftanks[i].pipe_conn.show() + else tool_ctl.vis_ftanks[i].line.show() end + else hide_fconn(i) end tool_ctl.vis_utanks[i].line.hide(true) end @@ -398,6 +391,8 @@ local function config_view(display) --#region Tank Layout Visualizer + local pipe_cpair = cpair(colors.blue,colors.lightGray) + local vis = Div{parent=svr_c_5,x=14,y=5,height=7} local vis_unit_list = TextBox{parent=vis,x=15,y=1,width=6,height=7,text="Unit 1\n\nUnit 2\n\nUnit 3\n\nUnit 4"} @@ -405,24 +400,25 @@ local function config_view(display) -- draw unit tanks and their pipes for i = 1, 4 do local line = Div{parent=vis,x=22,y=(i*2)-1,width=13,height=1} - TextBox{parent=line,width=5,height=1,text=string.rep("\x8c",5),fg_bg=cpair(colors.blue,colors.lightGray)} + TextBox{parent=line,width=5,height=1,text=string.rep("\x8c",5),fg_bg=pipe_cpair} local label = TextBox{parent=line,x=7,y=1,width=7,height=1,text="Tank ?"} tool_ctl.vis_utanks[i] = { line = line, label = label } end + -- draw facility tank connections + local ftank_1 = Div{parent=vis,x=1,y=1,width=13,height=1} TextBox{parent=ftank_1,width=7,height=1,text="Tank F1"} tool_ctl.vis_ftanks[1] = { - line = ftank_1, pipe_direct = TextBox{parent=ftank_1,x=9,y=1,width=5,text=string.rep("\x8c",5),fg_bg=cpair(colors.yellow,colors.lightGray)} + line = ftank_1, pipe_direct = TextBox{parent=ftank_1,x=9,y=1,width=5,text=string.rep("\x8c",5),fg_bg=pipe_cpair} } - -- draw facility tank connections for i = 2, 4 do local line = Div{parent=vis,x=1,y=(i-1)*2,width=13,height=2} - local pipe_conn = TextBox{parent=line,x=13,y=2,width=1,height=1,text="\x8c",fg_bg=cpair(colors.red,colors.lightGray)} - local pipe_chain = TextBox{parent=line,x=12,y=1,width=1,height=2,text="\x95\n\x8d",fg_bg=cpair(colors.green,colors.lightGray)} - local pipe_direct = TextBox{parent=line,x=9,y=2,width=4,height=1,text="\x8c\x8c\x8c\x8c",fg_bg=cpair(colors.lightBlue,colors.lightGray),hidden=true} - local label = TextBox{parent=line,x=1,y=2,width=7,height=1,text="Tank F?"} + local pipe_conn = TextBox{parent=line,x=13,y=2,width=1,height=1,text="\x8c",fg_bg=pipe_cpair} + local pipe_chain = TextBox{parent=line,x=12,y=1,width=1,height=2,text="",fg_bg=pipe_cpair} + local pipe_direct = TextBox{parent=line,x=9,y=2,width=4,height=1,text="",fg_bg=pipe_cpair} + local label = TextBox{parent=line,x=1,y=2,width=7,height=1,text=""} tool_ctl.vis_ftanks[i] = { line = line, pipe_conn = pipe_conn, pipe_chain = pipe_chain, pipe_direct = pipe_direct, label = label } end @@ -566,7 +562,7 @@ local function config_view(display) TextBox{parent=svr_c_6,y=5,height=3,text="Some modes may look the same if you are not using 4 total reactor units. The wiki has details. Modes that look the same will function the same."} TextBox{parent=svr_c_6,y=9,height=4,text="Examples: A U2 tank should be configured on an RTU as a dynamic tank for unit 2. An F3 tank should be configured on an RTU as the #3 dynamic tank for the facility."} - PushButton{parent=svr_c_6,x=1,y=14,min_width=6,text="\x1b Back",callback=function()svr_pane.set_value(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=svr_c_6,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} -- NET CONFIG @@ -983,7 +979,7 @@ local function config_view(display) end if val == "" then val = "no facility tanks" end - elseif f[1] == "FacilityTankMode" and raw == 0 then val = "0 (n/a unit mode)" + elseif f[1] == "FacilityTankMode" and raw == 0 then val = "0 (n/a, unit mode)" elseif f[1] == "FacilityTankDefs" and cfg.FacilityTankDefs then val = "" diff --git a/supervisor/startup.lua b/supervisor/startup.lua index b88cd4f..d4d2ea4 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -45,7 +45,7 @@ local config = supervisor.config local cfv = util.new_validator() assert((config.FacilityTankMode == 0) or (config.UnitCount == #config.FacilityTankDefs), - "startup> FacilityTankDefs length not equal to UnitCount") + "startup> the number of facility tank definitions must be equal to the number of units in facility tank mode") for i = 1, config.UnitCount do local def = config.FacilityTankDefs[i] @@ -73,7 +73,7 @@ end -- log init ---------------------------------------- -log.init(config.LogPath, config.LogMode, config.LogDebug == true) +log.init(config.LogPath, config.LogMode, config.LogDebug) log.info("========================================") log.info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION) From 1c410a89d896fe7c6b134245774fafae2a4a0ca3 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 30 Dec 2023 19:40:53 -0500 Subject: [PATCH 15/18] incremented comms version due to data change --- scada-common/comms.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 25fcea1..edcb7c3 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.2" +comms.version = "2.4.3" ---@enum PROTOCOL local PROTOCOL = { From 8cd51623628cc0b90eeed802f5eba3a343ded156 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 30 Dec 2023 20:18:58 -0500 Subject: [PATCH 16/18] fixed PLCs not connecting, fixed facility tank mode checkbox not changing after import, and reordered info on tank mode vis about page --- supervisor/configure.lua | 8 +++++--- supervisor/session/svsessions.lua | 3 +-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/supervisor/configure.lua b/supervisor/configure.lua index 190fd9b..bfe0ed7 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -307,7 +307,7 @@ local function config_view(display) TextBox{parent=svr_c_3,x=1,y=1,height=6,text="You have set one or more of your units to use dynamic tanks for emergency coolant. You have two paths for configuration. The first is to assign dynamic tanks to reactor units; one tank per reactor, only connected to that reactor. RTU configurations must also assign it as such."} TextBox{parent=svr_c_3,x=1,y=8,height=3,text="Alternatively, you can configure them as facility tanks to connect to multiple reactor units. These can intermingle with unit-specific tanks."} - local en_fac_tanks = CheckBox{parent=svr_c_3,x=1,y=12,label="Use Facility Dynamic Tanks",default=ini_cfg.FacilityTankMode~=0,box_fg_bg=cpair(colors.green,colors.black)} + local en_fac_tanks = CheckBox{parent=svr_c_3,x=1,y=12,label="Use Facility Dynamic Tanks",default=ini_cfg.FacilityTankMode>0,box_fg_bg=cpair(colors.green,colors.black)} local function submit_en_fac_tank() if en_fac_tanks.get_value() then @@ -559,8 +559,8 @@ local function config_view(display) PushButton{parent=svr_c_5,x=8,y=14,min_width=7,text="About",callback=function()svr_pane.set_value(6)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg} TextBox{parent=svr_c_6,height=3,text="This visualization tool shows the pipe connections required for a particular dynamic tank configuration you have selected."} - TextBox{parent=svr_c_6,y=5,height=3,text="Some modes may look the same if you are not using 4 total reactor units. The wiki has details. Modes that look the same will function the same."} - TextBox{parent=svr_c_6,y=9,height=4,text="Examples: A U2 tank should be configured on an RTU as a dynamic tank for unit 2. An F3 tank should be configured on an RTU as the #3 dynamic tank for the facility."} + TextBox{parent=svr_c_6,y=5,height=4,text="Examples: A U2 tank should be configured on an RTU as the dynamic tank for unit #2. An F3 tank should be configured on an RTU as the #3 dynamic tank for the facility."} + TextBox{parent=svr_c_6,y=10,height=3,text="Some modes may look the same if you are not using 4 total reactor units. The wiki has details. Modes that look the same will function the same.",fg_bg=g_lg_fg_bg} PushButton{parent=svr_c_6,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} @@ -793,6 +793,8 @@ local function config_view(display) try_set(tool_ctl.tank_elems[i].tank_opt, ini_cfg.FacilityTankDefs[i]) end + en_fac_tanks.set_value(ini_cfg.FacilityTankMode > 0) + tool_ctl.view_cfg.enable() if tool_ctl.importing_legacy then diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 68d2587..e191db6 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -35,7 +35,6 @@ local self = { nic = nil, ---@type nic|nil fp_ok = false, config = nil, ---@type svr_config - num_reactors = 0, facility = nil, ---@type facility|nil sessions = { rtu = {}, plc = {}, crd = {}, pdg = {} }, next_ids = { rtu = 0, plc = 0, crd = 0, pdg = 0 } @@ -279,7 +278,7 @@ end ---@param version string ---@return integer|false session_id function svsessions.establish_plc_session(source_addr, for_reactor, version) - if svsessions.get_reactor_session(for_reactor) == nil and for_reactor >= 1 and for_reactor <= self.num_reactors then + if svsessions.get_reactor_session(for_reactor) == nil and for_reactor >= 1 and for_reactor <= self.config.UnitCount then ---@class plc_session_struct local plc_s = { s_type = "plc", From 08e670091abdecc660aed72bab9f9f4987214e3f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 30 Dec 2023 20:25:57 -0500 Subject: [PATCH 17/18] #396 fixed fractional connection timeouts being treated as invalid --- reactor-plc/plc.lua | 2 +- reactor-plc/startup.lua | 2 +- rtu/rtu.lua | 2 +- rtu/startup.lua | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 9ad9c69..7287d3d 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -58,7 +58,7 @@ function plc.load_config() if config.Networked == true then cfv.assert_channel(config.SVR_Channel) cfv.assert_channel(config.PLC_Channel) - cfv.assert_type_int(config.ConnTimeout) + cfv.assert_type_num(config.ConnTimeout) cfv.assert_min(config.ConnTimeout, 2) cfv.assert_type_num(config.TrustedRange) cfv.assert_min(config.TrustedRange, 0) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 803fe99..1e066d0 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.8" +local R_PLC_VERSION = "v1.6.9" local println = util.println local println_ts = util.println_ts diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 20bdbe1..b76ce30 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -43,7 +43,7 @@ function rtu.load_config() 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_type_num(config.ConnTimeout) cfv.assert_min(config.ConnTimeout, 2) cfv.assert_type_num(config.TrustedRange) cfv.assert_min(config.TrustedRange, 0) diff --git a/rtu/startup.lua b/rtu/startup.lua index 1b25859..d05ffe8 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.9" +local RTU_VERSION = "v1.7.10" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE From 7b85d947c44a3ca2b7cf87147dcef1df1e18a271 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 30 Dec 2023 22:57:30 -0500 Subject: [PATCH 18/18] fixed supervisor always using MAC --- supervisor/startup.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index d4d2ea4..96cda57 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.2.0" +local SUPERVISOR_VERSION = "v1.2.1" local println = util.println local println_ts = util.println_ts @@ -98,7 +98,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