Compare commits
48 Commits
v1.8.4-bet
...
v1.8.7-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfa1f4d0c6 | ||
|
|
737e0d72b0 | ||
|
|
6edeb3e3b8 | ||
|
|
fb00e98a5b | ||
|
|
4f952eff83 | ||
|
|
1eede97c08 | ||
|
|
95419562ee | ||
|
|
7b85d947c4 | ||
|
|
08e670091a | ||
|
|
1348b632a8 | ||
|
|
8cd5162362 | ||
|
|
1c410a89d8 | ||
|
|
6a931fced4 | ||
|
|
622e2eeb90 | ||
|
|
42cd9fff0c | ||
|
|
2a85a438ba | ||
|
|
338b3b1615 | ||
|
|
363f164f47 | ||
|
|
739f04ece9 | ||
|
|
c6ade68ce2 | ||
|
|
7d60e259e2 | ||
|
|
cd71c6a9c1 | ||
|
|
26fe130609 | ||
|
|
95f87b1b05 | ||
|
|
aebdf3e8df | ||
|
|
5d4fc36256 | ||
|
|
b799d785b9 | ||
|
|
d55442fa53 | ||
|
|
c870b749a4 | ||
|
|
4421cbc0c5 | ||
|
|
b6a3305f23 | ||
|
|
5680260136 | ||
|
|
bc66ea6ecb | ||
|
|
1b20218445 | ||
|
|
466e442353 | ||
|
|
9e6751f47f | ||
|
|
f868923905 | ||
|
|
5d3fd6d939 | ||
|
|
37659d687e | ||
|
|
f23b7e2c2f | ||
|
|
55ccdd63d4 | ||
|
|
5c88890ed4 | ||
|
|
fa0185c9a4 | ||
|
|
e1ed9a8e5e | ||
|
|
9f7e3bc282 | ||
|
|
4ec060ba24 | ||
|
|
b1f1753a8d | ||
|
|
94a62f8c31 |
21
ccmsi.lua
21
ccmsi.lua
@@ -18,7 +18,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
local function println(message) print(tostring(message)) end
|
||||
local function print(message) term.write(tostring(message)) end
|
||||
|
||||
local CCMSI_VERSION = "v1.11c"
|
||||
local CCMSI_VERSION = "v1.12a"
|
||||
|
||||
local install_dir = "/.install-cache"
|
||||
local manifest_path = "https://mikaylafischler.github.io/cc-mek-scada/manifests/"
|
||||
@@ -168,28 +168,26 @@ end
|
||||
|
||||
-- go through app/common directories to delete unused files
|
||||
local function clean(manifest)
|
||||
local root_ext = false
|
||||
local tree = gen_tree(manifest)
|
||||
|
||||
table.insert(tree, "install_manifest.json")
|
||||
table.insert(tree, "ccmsi.lua")
|
||||
table.insert(tree, "log.txt") ---@fixme fix after migration to settings files?
|
||||
|
||||
lgray()
|
||||
|
||||
local ls = fs.list("/")
|
||||
for _, val in pairs(ls) do
|
||||
if fs.isDir(val) then
|
||||
if tree[val] ~= nil then _clean_dir("/" .. val, tree[val]) end
|
||||
if #fs.list(val) == 0 then fs.delete(val);println("deleted " .. val) end
|
||||
if fs.isDriveRoot(val) then
|
||||
yellow();println("skipped mount '" .. val .. "'")
|
||||
elseif fs.isDir(val) then
|
||||
if tree[val] ~= nil then lgray();_clean_dir("/" .. val, tree[val])
|
||||
else white(); if ask_y_n("delete the unused directory '" .. val .. "'") then lgray();_clean_dir("/" .. val) end end
|
||||
if #fs.list(val) == 0 then fs.delete(val);lgray();println("deleted empty directory '" .. val .. "'") end
|
||||
elseif not _in_array(val, tree) and (string.find(val, ".settings") == nil) then
|
||||
root_ext = true
|
||||
yellow();println(val .. " not used")
|
||||
white();if ask_y_n("delete the unused file '" .. val .. "'") then fs.delete(val);lgray();println("deleted " .. val) end
|
||||
end
|
||||
end
|
||||
|
||||
white()
|
||||
if root_ext then println("Files in root directory won't be automatically deleted.") end
|
||||
end
|
||||
|
||||
-- get and validate command line options
|
||||
@@ -347,7 +345,8 @@ elseif mode == "install" or mode == "update" then
|
||||
if mode == "install" then
|
||||
println("Installing " .. app .. " files...")
|
||||
elseif mode == "update" then
|
||||
println("Updating " .. app .. " files... (keeping old config.lua)")
|
||||
if app == "coordinator" or app == "pocket" then println("Updating " .. app .. " files... (keeping old config.lua)")
|
||||
else println("Updating " .. app .. " files...") end
|
||||
end
|
||||
white()
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@ 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")
|
||||
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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -22,7 +22,7 @@ local sounder = require("coordinator.sounder")
|
||||
|
||||
local apisessions = require("coordinator.session.apisessions")
|
||||
|
||||
local COORDINATOR_VERSION = "v1.0.17"
|
||||
local COORDINATOR_VERSION = "v1.1.0"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
|
||||
@@ -257,7 +257,7 @@ local function init(parent, id)
|
||||
if unit.num_boilers > 0 then
|
||||
TextBox{parent=rcs_tags,x=1,text="B1",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local b1_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=ind_red}
|
||||
b1_wll.register(b_ps[1], "WasterLevelLow", b1_wll.update)
|
||||
b1_wll.register(b_ps[1], "WaterLevelLow", b1_wll.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="B1",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local b1_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=ind_yel}
|
||||
@@ -273,7 +273,7 @@ local function init(parent, id)
|
||||
|
||||
TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local b2_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=ind_red}
|
||||
b2_wll.register(b_ps[2], "WasterLevelLow", b2_wll.update)
|
||||
b2_wll.register(b_ps[2], "WaterLevelLow", b2_wll.update)
|
||||
|
||||
TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=bw_fg_bg}
|
||||
local b2_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=ind_yel}
|
||||
|
||||
@@ -7,7 +7,7 @@ local flasher = require("graphics.flasher")
|
||||
|
||||
local core = {}
|
||||
|
||||
core.version = "2.0.7"
|
||||
core.version = "2.1.0"
|
||||
|
||||
core.flasher = flasher
|
||||
core.events = events
|
||||
|
||||
@@ -24,7 +24,7 @@ local function checkbox(args)
|
||||
|
||||
args.can_focus = true
|
||||
args.height = 1
|
||||
args.width = 3 + string.len(args.label)
|
||||
args.width = 2 + string.len(args.label)
|
||||
|
||||
-- create new graphics element base object
|
||||
local e = element.new(args)
|
||||
|
||||
@@ -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
|
||||
@@ -50,7 +57,7 @@ local function number_field(args)
|
||||
if core.events.was_clicked(event.type) then
|
||||
e.take_focus()
|
||||
|
||||
if event.type == MOUSE_CLICK.UP then
|
||||
if event.type == MOUSE_CLICK.UP and e.in_frame_bounds(event.current.x, event.current.y) then
|
||||
ifield.move_cursor(event.current.x)
|
||||
end
|
||||
elseif event.type == MOUSE_CLICK.DOUBLE_CLICK then
|
||||
@@ -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 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()
|
||||
|
||||
@@ -45,7 +45,7 @@ local function text_field(args)
|
||||
if core.events.was_clicked(event.type) then
|
||||
e.take_focus()
|
||||
|
||||
if event.type == MOUSE_CLICK.UP then
|
||||
if event.type == MOUSE_CLICK.UP and e.in_frame_bounds(event.current.x, event.current.y) then
|
||||
ifield.move_cursor(event.current.x)
|
||||
end
|
||||
elseif event.type == MOUSE_CLICK.DOUBLE_CLICK then
|
||||
|
||||
@@ -2,9 +2,7 @@ return {
|
||||
-- initialize booted environment
|
||||
init_env = function ()
|
||||
local _require, _env = require("cc.require"), setmetatable({}, { __index = _ENV })
|
||||
-- overwrite require/package globals
|
||||
require, package = _require.make(_env, "/")
|
||||
-- reset terminal
|
||||
term.clear(); term.setCursorPos(1, 1)
|
||||
end
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
@@ -232,14 +233,14 @@ local function config_view(display)
|
||||
plc_pane.set_value(2)
|
||||
end
|
||||
|
||||
PushButton{parent=plc_c_1,x=1,y=14,min_width=6,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=plc_c_1,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_networked,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=plc_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=plc_c_1,x=44,y=14,text="Next \x1a",callback=submit_networked,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=plc_c_2,x=1,y=1,height=1,text="Please enter the reactor unit ID for this PLC."}
|
||||
TextBox{parent=plc_c_2,x=1,y=3,height=3,text="If this is a networked PLC, currently only IDs 1 through 4 are acceptable.",fg_bg=g_lg_fg_bg}
|
||||
|
||||
TextBox{parent=plc_c_2,x=1,y=6,height=1,text="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}
|
||||
|
||||
@@ -252,10 +253,10 @@ local function config_view(display)
|
||||
else u_id_err.show() end
|
||||
end
|
||||
|
||||
PushButton{parent=plc_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()plc_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=plc_c_2,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_id,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=plc_c_2,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=plc_c_2,x=44,y=14,text="Next \x1a",callback=submit_id,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=plc_c_3,x=1,y=1,height=4,text="When networked, the supervisor takes care of emergency coolant via RTUs. However, you can configure independent emergency coolant via the PLC. "}
|
||||
TextBox{parent=plc_c_3,x=1,y=1,height=4,text="When networked, the supervisor takes care of emergency coolant via RTUs. However, you can configure independent emergency coolant via the PLC."}
|
||||
TextBox{parent=plc_c_3,x=1,y=6,height=5,text="This independent control can be used with or without a supervisor. To configure, you would next select the interface of the redstone output connected to one or more mekanism pipes.",fg_bg=g_lg_fg_bg}
|
||||
|
||||
local en_em_cool = CheckBox{parent=plc_c_3,x=1,y=11,label="Enable PLC Emergency Coolant Control",default=ini_cfg.EmerCoolEnable,box_fg_bg=cpair(colors.orange,colors.black)}
|
||||
@@ -269,8 +270,8 @@ local function config_view(display)
|
||||
if tmp_cfg.EmerCoolEnable then plc_pane.set_value(4) else next_from_plc() end
|
||||
end
|
||||
|
||||
PushButton{parent=plc_c_3,x=1,y=14,min_width=6,text="\x1b Back",callback=function()plc_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=plc_c_3,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_en_emcool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=plc_c_3,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=plc_c_3,x=44,y=14,text="Next \x1a",callback=submit_en_emcool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=plc_c_4,x=1,y=1,height=1,text="Emergency Coolant Redstone Output Side"}
|
||||
local side = Radio2D{parent=plc_c_4,x=1,y=2,rows=2,columns=3,default=side_to_idx(ini_cfg.EmerCoolSide),options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.orange}
|
||||
@@ -286,8 +287,8 @@ local function config_view(display)
|
||||
next_from_plc()
|
||||
end
|
||||
|
||||
PushButton{parent=plc_c_4,x=1,y=14,min_width=6,text="\x1b Back",callback=function()plc_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=plc_c_4,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_emcool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=plc_c_4,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=plc_c_4,x=44,y=14,text="Next \x1a",callback=submit_emcool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
-- NET CONFIG
|
||||
|
||||
@@ -328,16 +329,16 @@ local function config_view(display)
|
||||
end
|
||||
end
|
||||
|
||||
PushButton{parent=net_c_1,x=1,y=14,min_width=6,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=net_c_1,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
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}
|
||||
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}
|
||||
@@ -359,8 +360,8 @@ local function config_view(display)
|
||||
end
|
||||
end
|
||||
|
||||
PushButton{parent=net_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=net_c_2,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
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}
|
||||
@@ -386,8 +387,8 @@ local function config_view(display)
|
||||
else key_err.show() end
|
||||
end
|
||||
|
||||
PushButton{parent=net_c_3,x=1,y=14,min_width=6,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=net_c_3,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
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
|
||||
|
||||
@@ -426,8 +427,8 @@ local function config_view(display)
|
||||
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,min_width=6,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,min_width=6,text="Next \x1a",callback=submit_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=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
|
||||
|
||||
@@ -494,7 +495,7 @@ local function config_view(display)
|
||||
end
|
||||
end
|
||||
|
||||
PushButton{parent=sum_c_1,x=1,y=14,min_width=6,text="\x1b Back",callback=back_from_settings,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
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}
|
||||
|
||||
@@ -541,7 +542,7 @@ local function config_view(display)
|
||||
end
|
||||
end
|
||||
|
||||
PushButton{parent=cl,x=1,y=14,min_width=6,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.5"
|
||||
local R_PLC_VERSION = "v1.6.9"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
|
||||
@@ -173,7 +173,8 @@ function threads.thread__main(smem, init)
|
||||
plc_state.degraded = true
|
||||
elseif networked and type == "modem" then
|
||||
-- we only care if this is our wireless modem
|
||||
if nic.is_modem(device) then
|
||||
-- note, check init_ok first since nic will be nil if it is false
|
||||
if plc_state.init_ok and nic.is_modem(device) then
|
||||
nic.disconnect()
|
||||
|
||||
println_ts("comms modem disconnected!")
|
||||
@@ -193,7 +194,7 @@ function threads.thread__main(smem, init)
|
||||
end
|
||||
end
|
||||
else
|
||||
log.warning("non-comms modem disconnected")
|
||||
log.warning("a modem was disconnected")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -235,7 +236,8 @@ function threads.thread__main(smem, init)
|
||||
rps.reset()
|
||||
end
|
||||
elseif networked and type == "modem" then
|
||||
if device.isWireless() and not nic.is_connected() then
|
||||
-- note, check init_ok first since nic will be nil if it is false
|
||||
if device.isWireless() and not (plc_state.init_ok and nic.is_connected()) then
|
||||
-- reconnected modem
|
||||
plc_dev.modem = device
|
||||
plc_state.no_modem = false
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
@@ -125,6 +127,7 @@ local tool_ctl = {
|
||||
importing_any_dc = false,
|
||||
peri_cfg_editing = false, ---@type string|false
|
||||
peri_cfg_manual = false,
|
||||
rs_cfg_port = IO.F_SCRAM, ---@type IO_PORT
|
||||
rs_cfg_editing = false, ---@type integer|false
|
||||
|
||||
view_gw_cfg = nil, ---@type graphics_element
|
||||
@@ -161,6 +164,7 @@ local tool_ctl = {
|
||||
rs_cfg_selection = nil, ---@type graphics_element
|
||||
rs_cfg_unit_l = nil, ---@type graphics_element
|
||||
rs_cfg_unit = nil, ---@type graphics_element
|
||||
rs_cfg_side_l = nil, ---@type graphics_element
|
||||
rs_cfg_color = nil, ---@type graphics_element
|
||||
rs_cfg_shortcut = nil ---@type graphics_element
|
||||
}
|
||||
@@ -170,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
|
||||
@@ -329,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}
|
||||
|
||||
@@ -344,8 +348,8 @@ local function config_view(display)
|
||||
else s_vol_err.show() end
|
||||
end
|
||||
|
||||
PushButton{parent=spkr_c,x=1,y=14,min_width=6,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=spkr_c,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_vol,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=spkr_c,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=spkr_c,x=44,y=14,text="Next \x1a",callback=submit_vol,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
|
||||
@@ -388,16 +392,16 @@ local function config_view(display)
|
||||
end
|
||||
end
|
||||
|
||||
PushButton{parent=net_c_1,x=1,y=14,min_width=6,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=net_c_1,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
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}
|
||||
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}
|
||||
@@ -419,8 +423,8 @@ local function config_view(display)
|
||||
end
|
||||
end
|
||||
|
||||
PushButton{parent=net_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()net_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=net_c_2,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_ct_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
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}
|
||||
@@ -446,8 +450,8 @@ local function config_view(display)
|
||||
else key_err.show() end
|
||||
end
|
||||
|
||||
PushButton{parent=net_c_3,x=1,y=14,min_width=6,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=net_c_3,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
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}
|
||||
|
||||
--#endregion
|
||||
|
||||
@@ -485,8 +489,8 @@ local function config_view(display)
|
||||
else path_err.show() end
|
||||
end
|
||||
|
||||
PushButton{parent=log_c_1,x=1,y=14,min_width=6,text="\x1b Back",callback=function()main_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=log_c_1,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_log,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
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}
|
||||
|
||||
--#endregion
|
||||
|
||||
@@ -567,7 +571,7 @@ local function config_view(display)
|
||||
else sum_pane.set_value(6) end
|
||||
end
|
||||
|
||||
PushButton{parent=sum_c_1,x=1,y=14,min_width=6,text="\x1b Back",callback=back_from_settings,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
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=function()save_and_continue(true)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
||||
tool_ctl.settings_confirm = PushButton{parent=sum_c_1,x=41,y=14,min_width=9,text="Confirm",callback=function()sum_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
||||
@@ -576,13 +580,13 @@ local function config_view(display)
|
||||
TextBox{parent=sum_c_2,x=1,y=1,height=1,text="The following peripherals will be imported:"}
|
||||
local peri_import_list = ListBox{parent=sum_c_2,x=1,y=3,height=10,width=51,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||
|
||||
PushButton{parent=sum_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()sum_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=sum_c_2,x=1,y=14,text="\x1b Back",callback=function()sum_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=sum_c_2,x=41,y=14,min_width=9,text="Confirm",callback=function()sum_pane.set_value(3)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=sum_c_3,x=1,y=1,height=1,text="The following redstone entries will be imported:"}
|
||||
local rs_import_list = ListBox{parent=sum_c_3,x=1,y=3,height=10,width=51,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||
|
||||
PushButton{parent=sum_c_3,x=1,y=14,min_width=6,text="\x1b Back",callback=function()sum_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=sum_c_3,x=1,y=14,text="\x1b Back",callback=function()sum_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=sum_c_3,x=43,y=14,min_width=7,text="Apply",callback=save_and_continue,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=sum_c_4,x=1,y=1,height=1,text="Settings saved!"}
|
||||
@@ -605,7 +609,7 @@ local function config_view(display)
|
||||
|
||||
TextBox{parent=sum_c_7,x=1,y=1,height=8,text="Warning!\n\nSome of the devices in your old config file aren't currently connected. If the device isn't connected, the options can't be properly validated. Please either connect your devices and try again or complete the import without validation on those entry's settings."}
|
||||
TextBox{parent=sum_c_7,x=1,y=10,height=3,text="Afterwards, either (a) edit then save entries for currently disconnected devices to properly configure or (b) delete those entries."}
|
||||
PushButton{parent=sum_c_7,x=1,y=14,min_width=6,text="\x1b Back",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=sum_c_7,x=1,y=14,text="\x1b Back",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=sum_c_7,x=41,y=14,min_width=9,text="Confirm",callback=function()sum_pane.set_value(1)end,fg_bg=cpair(colors.black,colors.orange),active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
@@ -627,7 +631,7 @@ local function config_view(display)
|
||||
end
|
||||
end
|
||||
|
||||
PushButton{parent=cl,x=1,y=14,min_width=6,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=cl,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
|
||||
@@ -664,7 +668,7 @@ local function config_view(display)
|
||||
end
|
||||
end
|
||||
|
||||
PushButton{parent=peri_c_1,x=1,y=14,min_width=6,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=peri_c_1,x=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=peri_c_1,x=8,y=14,min_width=16,text="Revert Changes",callback=peri_revert,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=peri_c_1,x=35,y=14,min_width=7,text="Add +",callback=function()peri_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=peri_c_1,x=43,y=14,min_width=7,text="Apply",callback=peri_apply,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
||||
@@ -673,13 +677,13 @@ local function config_view(display)
|
||||
|
||||
tool_ctl.ppm_devs = ListBox{parent=peri_c_2,x=1,y=3,height=10,width=51,scroll_height=1000,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||
|
||||
PushButton{parent=peri_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=peri_c_2,x=1,y=14,text="\x1b Back",callback=function()peri_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=peri_c_2,x=8,y=14,min_width=10,text="Manual +",callback=function()peri_pane.set_value(3)end,fg_bg=cpair(colors.black,colors.orange),active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=peri_c_2,x=26,y=14,min_width=24,text="I don't see my device!",callback=function()peri_pane.set_value(7)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=peri_c_7,x=1,y=1,height=10,text="Make sure your device is either touching the RTU or connected via wired modems. There should be a wired modem on a side of the RTU then one on the device, connected by a cable. The modem on the device needs to be right clicked to connect it (which will turn its border red), at which point the peripheral name will be shown in the chat."}
|
||||
TextBox{parent=peri_c_7,x=1,y=9,height=4,text="If it still does not show, it may not be compatible. Currently only Boilers, Turbines, Dynamic Tanks, SNAs, SPSs, Induction Matricies, and Environment Detectors are supported."}
|
||||
PushButton{parent=peri_c_7,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=peri_c_7,x=1,y=14,text="\x1b Back",callback=function()peri_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
local new_peri_attrs = { "", "" }
|
||||
local function new_peri(name, type)
|
||||
@@ -698,6 +702,7 @@ local function config_view(display)
|
||||
tool_ctl.p_idx.set_max(2)
|
||||
tool_ctl.p_unit.reposition(44, 4)
|
||||
tool_ctl.p_unit.enable()
|
||||
tool_ctl.p_unit.show()
|
||||
tool_ctl.p_assign_btn.hide(true)
|
||||
tool_ctl.p_assign_end.hide(true)
|
||||
tool_ctl.p_desc.reposition(1, 7)
|
||||
@@ -710,6 +715,7 @@ local function config_view(display)
|
||||
tool_ctl.p_idx.set_max(3)
|
||||
tool_ctl.p_unit.reposition(45, 4)
|
||||
tool_ctl.p_unit.enable()
|
||||
tool_ctl.p_unit.show()
|
||||
tool_ctl.p_assign_btn.hide(true)
|
||||
tool_ctl.p_assign_end.hide(true)
|
||||
tool_ctl.p_desc.reposition(1, 7)
|
||||
@@ -719,6 +725,7 @@ local function config_view(display)
|
||||
tool_ctl.p_prompt.set_value("This SNA is for reactor unit # .")
|
||||
tool_ctl.p_unit.reposition(31, 4)
|
||||
tool_ctl.p_unit.enable()
|
||||
tool_ctl.p_unit.show()
|
||||
tool_ctl.p_assign_btn.hide(true)
|
||||
tool_ctl.p_assign_end.hide(true)
|
||||
tool_ctl.p_desc_ext.set_value("Before adding lots of SNAs: multiply the \"PEAK\" rate on the flow monitor (after connecting at least 1 SNA) by 10 to get the mB/t of waste that they can process. Enough SNAs to provide 2x to 3x of your max burn rate should be a good margin to catch up after night or cloudy weather. Too many devices (such as SNAs) on one RTU can cause lag.")
|
||||
@@ -732,6 +739,8 @@ local function config_view(display)
|
||||
tool_ctl.p_idx.redraw()
|
||||
tool_ctl.p_idx.set_max(4)
|
||||
tool_ctl.p_unit.reposition(18, 6)
|
||||
tool_ctl.p_unit.enable()
|
||||
tool_ctl.p_unit.show()
|
||||
|
||||
if tool_ctl.p_assign_btn.get_value() == 1 then
|
||||
tool_ctl.p_idx.enable()
|
||||
@@ -754,6 +763,8 @@ local function config_view(display)
|
||||
tool_ctl.p_idx.redraw()
|
||||
tool_ctl.p_idx.set_max(99)
|
||||
tool_ctl.p_unit.reposition(18, 6)
|
||||
tool_ctl.p_unit.enable()
|
||||
tool_ctl.p_unit.show()
|
||||
if tool_ctl.p_assign_btn.get_value() == 1 then tool_ctl.p_unit.disable() else tool_ctl.p_unit.enable() end
|
||||
tool_ctl.p_desc.reposition(1, 8)
|
||||
tool_ctl.p_desc.set_value("You can connect more than one environment detector for a particular unit or the facility. In that case, the maximum radiation reading from those assigned to that particular unit or the facility will be used for alarms and display.")
|
||||
@@ -815,16 +826,16 @@ local function config_view(display)
|
||||
else man_p_err.show() end
|
||||
end
|
||||
|
||||
PushButton{parent=peri_c_3,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=peri_c_3,x=44,y=14,min_width=6,text="Next \x1a",callback=submit_manual_peri,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=peri_c_3,x=1,y=14,text="\x1b Back",callback=function()peri_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=peri_c_3,x=44,y=14,text="Next \x1a",callback=submit_manual_peri,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
tool_ctl.p_name_msg = TextBox{parent=peri_c_4,x=1,y=1,height=2,text=""}
|
||||
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)
|
||||
@@ -843,7 +854,7 @@ local function config_view(display)
|
||||
tool_ctl.p_desc = TextBox{parent=peri_c_4,x=1,y=7,height=6,text="",fg_bg=g_lg_fg_bg}
|
||||
tool_ctl.p_desc_ext = TextBox{parent=peri_c_4,x=1,y=6,height=7,text="",fg_bg=g_lg_fg_bg}
|
||||
|
||||
tool_ctl.p_err = TextBox{parent=peri_c_4,x=8,y=14,height=1,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||
tool_ctl.p_err = TextBox{parent=peri_c_4,x=8,y=14,height=1,width=32,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||
tool_ctl.p_err.hide(true)
|
||||
|
||||
local function back_from_peri_opts()
|
||||
@@ -872,7 +883,7 @@ local function config_view(display)
|
||||
if (peri_type == "dynamicValve" or peri_type == "environmentDetector") and for_facility then
|
||||
-- skip
|
||||
elseif not (util.is_int(u) and u > 0 and u < 5) then
|
||||
tool_ctl.p_err.set_value("Unit ID must be within 1 through 4.")
|
||||
tool_ctl.p_err.set_value("Unit ID must be within 1 to 4.")
|
||||
tool_ctl.p_err.show()
|
||||
return
|
||||
else unit = u end
|
||||
@@ -892,7 +903,7 @@ local function config_view(display)
|
||||
else index = idx end
|
||||
elseif peri_type == "dynamicValve" and for_facility then
|
||||
if not (util.is_int(idx) and idx > 0 and idx < 5) then
|
||||
tool_ctl.p_err.set_value("Index must be within 1 through 4.")
|
||||
tool_ctl.p_err.set_value("Index must be within 1 to 4.")
|
||||
tool_ctl.p_err.show()
|
||||
return
|
||||
else index = idx end
|
||||
@@ -920,19 +931,20 @@ local function config_view(display)
|
||||
|
||||
peri_pane.set_value(1)
|
||||
tool_ctl.gen_peri_summary(tmp_cfg)
|
||||
tool_ctl.update_peri_list()
|
||||
|
||||
tool_ctl.p_idx.set_value(1)
|
||||
end
|
||||
|
||||
PushButton{parent=peri_c_4,x=1,y=14,min_width=6,text="\x1b Back",callback=back_from_peri_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=peri_c_4,x=1,y=14,text="\x1b Back",callback=back_from_peri_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=peri_c_4,x=41,y=14,min_width=9,text="Confirm",callback=save_peri_entry,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=peri_c_5,x=1,y=1,height=1,text="Settings saved!"}
|
||||
PushButton{parent=peri_c_5,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=peri_c_5,x=1,y=14,text="\x1b Back",callback=function()peri_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=peri_c_5,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=peri_c_6,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
||||
PushButton{parent=peri_c_6,x=1,y=14,min_width=6,text="\x1b Back",callback=function()peri_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=peri_c_6,x=1,y=14,text="\x1b Back",callback=function()peri_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=peri_c_6,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
@@ -970,21 +982,20 @@ local function config_view(display)
|
||||
end
|
||||
end
|
||||
|
||||
PushButton{parent=rs_c_1,x=1,y=14,min_width=6,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=rs_c_1,x=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=rs_c_1,x=8,y=14,min_width=16,text="Revert Changes",callback=rs_revert,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=rs_c_1,x=35,y=14,min_width=7,text="New +",callback=function()rs_pane.set_value(2)end,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=rs_c_1,x=43,y=14,min_width=7,text="Apply",callback=rs_apply,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=rs_c_6,x=1,y=1,height=5,text="You already configured this input. There can only be one entry for each input.\n\nPlease select a different port."}
|
||||
PushButton{parent=rs_c_6,x=1,y=14,min_width=6,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=rs_c_6,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=rs_c_2,x=1,y=1,height=1,text="Select one of the below ports to use."}
|
||||
|
||||
local rs_ports = ListBox{parent=rs_c_2,x=1,y=3,height=10,width=51,scroll_height=200,fg_bg=bw_fg_bg,nav_fg_bg=g_lg_fg_bg,nav_active=cpair(colors.black,colors.gray)}
|
||||
|
||||
local new_rs_port = IO.F_SCRAM
|
||||
local function new_rs(port)
|
||||
if (rsio.get_io_mode(port) == rsio.IO_DIR.IN) then
|
||||
if (rsio.get_io_dir(port) == rsio.IO_DIR.IN) then
|
||||
for i = 1, #tmp_cfg.Redstone do
|
||||
if tmp_cfg.Redstone[i].port == port then
|
||||
rs_pane.set_value(6)
|
||||
@@ -1000,9 +1011,11 @@ local function config_view(display)
|
||||
if port == -1 then
|
||||
tool_ctl.rs_cfg_color.hide(true)
|
||||
tool_ctl.rs_cfg_shortcut.show()
|
||||
tool_ctl.rs_cfg_side_l.set_value("Output Side")
|
||||
text = "You selected the ALL_WASTE shortcut."
|
||||
else
|
||||
tool_ctl.rs_cfg_shortcut.hide(true)
|
||||
tool_ctl.rs_cfg_side_l.set_value(util.trinary(rsio.get_io_dir(port) == rsio.IO_DIR.IN, "Input Side", "Output Side"))
|
||||
tool_ctl.rs_cfg_color.show()
|
||||
text = "You selected " .. rsio.to_string(port) .. " (for "
|
||||
if PORT_DSGN[port] == 1 then
|
||||
@@ -1017,7 +1030,7 @@ local function config_view(display)
|
||||
end
|
||||
|
||||
tool_ctl.rs_cfg_selection.set_value(text)
|
||||
new_rs_port = port
|
||||
tool_ctl.rs_cfg_port = port
|
||||
rs_pane.set_value(3)
|
||||
end
|
||||
|
||||
@@ -1028,22 +1041,22 @@ local function config_view(display)
|
||||
TextBox{parent=all_w_macro,x=22,y=1,height=1,text="Create all 4 waste entries",fg_bg=cpair(colors.gray,colors.white)}
|
||||
for i = 1, rsio.NUM_PORTS do
|
||||
local name = rsio.to_string(i)
|
||||
local io_dir = util.trinary(rsio.get_io_mode(i) == rsio.IO_DIR.IN, "[in]", "[out]")
|
||||
local btn_color = util.trinary(rsio.get_io_mode(i) == rsio.IO_DIR.IN, colors.yellow, colors.lightBlue)
|
||||
local io_dir = util.trinary(rsio.get_io_dir(i) == rsio.IO_DIR.IN, "[in]", "[out]")
|
||||
local btn_color = util.trinary(rsio.get_io_dir(i) == rsio.IO_DIR.IN, colors.yellow, colors.lightBlue)
|
||||
local entry = Div{parent=rs_ports,height=1}
|
||||
PushButton{parent=entry,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">"..name,callback=function()new_rs(i)end,fg_bg=cpair(colors.black,btn_color),active_fg_bg=cpair(colors.white,colors.black)}
|
||||
TextBox{parent=entry,x=16,y=1,width=5,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
TextBox{parent=entry,x=22,y=1,height=1,text=PORT_DESC[i],fg_bg=cpair(colors.gray,colors.white)}
|
||||
end
|
||||
|
||||
PushButton{parent=rs_c_2,x=1,y=14,min_width=6,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=rs_c_2,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
tool_ctl.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=1,text=""}
|
||||
|
||||
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}
|
||||
|
||||
TextBox{parent=rs_c_3,x=1,y=3,width=11,height=1,text="Output Side"}
|
||||
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}
|
||||
|
||||
local function set_bundled(bundled)
|
||||
@@ -1057,7 +1070,7 @@ local function config_view(display)
|
||||
tool_ctl.rs_cfg_color = Radio2D{parent=rs_c_3,x=1,y=9,rows=4,columns=4,default=1,options=color_options,radio_colors=cpair(colors.lightGray,colors.black),color_map=color_options_map,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
||||
tool_ctl.rs_cfg_color.disable()
|
||||
|
||||
local rs_err = TextBox{parent=rs_c_3,x=8,y=14,height=1,width=35,text="Unit ID must be within 1 through 4.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||
local rs_err = TextBox{parent=rs_c_3,x=8,y=14,height=1,width=30,text="Unit ID must be within 1 to 4.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||
rs_err.hide(true)
|
||||
|
||||
local function back_from_rs_opts()
|
||||
@@ -1066,16 +1079,17 @@ local function config_view(display)
|
||||
end
|
||||
|
||||
local function save_rs_entry()
|
||||
local port = tool_ctl.rs_cfg_port
|
||||
local u = tonumber(tool_ctl.rs_cfg_unit.get_value())
|
||||
|
||||
if PORT_DSGN[new_rs_port] == 0 or (util.is_int(u) and u > 0 and u < 5) then
|
||||
if PORT_DSGN[port] == 0 or (util.is_int(u) and u > 0 and u < 5) then
|
||||
rs_err.hide(true)
|
||||
|
||||
if new_rs_port >= 0 then
|
||||
if port >= 0 then
|
||||
---@type rtu_rs_definition
|
||||
local def = {
|
||||
unit = util.trinary(PORT_DSGN[new_rs_port] == 1, u, nil),
|
||||
port = new_rs_port,
|
||||
unit = util.trinary(PORT_DSGN[port] == 1, u, nil),
|
||||
port = port,
|
||||
side = side_options_map[side.get_value()],
|
||||
color = util.trinary(bundled.get_value(), color_options_map[tool_ctl.rs_cfg_color.get_value()], nil)
|
||||
}
|
||||
@@ -1086,7 +1100,7 @@ local function config_view(display)
|
||||
def.port = tmp_cfg.Redstone[tool_ctl.rs_cfg_editing].port
|
||||
tmp_cfg.Redstone[tool_ctl.rs_cfg_editing] = def
|
||||
end
|
||||
elseif new_rs_port == -1 then
|
||||
elseif port == -1 then
|
||||
local default_sides = { "left", "back", "right", "front" }
|
||||
local default_colors = { colors.red, colors.orange, colors.yellow, colors.lime }
|
||||
for i = 0, 3 do
|
||||
@@ -1109,15 +1123,15 @@ local function config_view(display)
|
||||
else rs_err.show() end
|
||||
end
|
||||
|
||||
PushButton{parent=rs_c_3,x=1,y=14,min_width=6,text="\x1b Back",callback=back_from_rs_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=rs_c_3,x=1,y=14,text="\x1b Back",callback=back_from_rs_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=rs_c_3,x=41,y=14,min_width=9,text="Confirm",callback=save_rs_entry,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=rs_c_4,x=1,y=1,height=1,text="Settings saved!"}
|
||||
PushButton{parent=rs_c_4,x=1,y=14,min_width=6,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=rs_c_4,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=rs_c_4,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=rs_c_5,x=1,y=1,height=5,text="Failed to save the settings file.\n\nThere may not be enough space for the modification or server file permissions may be denying writes."}
|
||||
PushButton{parent=rs_c_5,x=1,y=14,min_width=6,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=rs_c_5,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=rs_c_5,x=44,y=14,min_width=6,text="Home",callback=function()tool_ctl.go_home()end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
@@ -1130,7 +1144,7 @@ local function config_view(display)
|
||||
|
||||
tool_ctl.importing_any_dc = false
|
||||
|
||||
tmp_cfg.SpeakerVolume = config.SOUNDER_VOLUME
|
||||
tmp_cfg.SpeakerVolume = config.SOUNDER_VOLUME or 1
|
||||
tmp_cfg.SVR_Channel = config.SVR_CHANNEL
|
||||
tmp_cfg.RTU_Channel = config.RTU_CHANNEL
|
||||
tmp_cfg.ConnTimeout = config.COMMS_TIMEOUT
|
||||
@@ -1226,7 +1240,7 @@ local function config_view(display)
|
||||
table.insert(tmp_cfg.Redstone, def)
|
||||
|
||||
local name = rsio.to_string(def.port)
|
||||
local io_dir = util.trinary(rsio.get_io_mode(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
|
||||
local io_dir = util.trinary(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b")
|
||||
local conn = def.side
|
||||
local unit = "facility"
|
||||
|
||||
@@ -1335,6 +1349,7 @@ local function config_view(display)
|
||||
local function delete_peri_entry(idx)
|
||||
table.remove(tmp_cfg.Peripherals, idx)
|
||||
tool_ctl.gen_peri_summary(tmp_cfg)
|
||||
tool_ctl.update_peri_list()
|
||||
end
|
||||
|
||||
-- generate the peripherals summary list
|
||||
@@ -1377,6 +1392,10 @@ local function config_view(display)
|
||||
local function edit_rs_entry(idx)
|
||||
local def = tmp_cfg.Redstone[idx] ---@type rtu_rs_definition
|
||||
|
||||
tool_ctl.rs_cfg_shortcut.hide(true)
|
||||
tool_ctl.rs_cfg_color.show()
|
||||
|
||||
tool_ctl.rs_cfg_port = def.port
|
||||
tool_ctl.rs_cfg_editing = idx
|
||||
|
||||
local text = "Editing " .. rsio.to_string(def.port) .. " (for "
|
||||
@@ -1400,6 +1419,7 @@ local function config_view(display)
|
||||
end
|
||||
|
||||
tool_ctl.rs_cfg_selection.set_value(text)
|
||||
tool_ctl.rs_cfg_side_l.set_value(util.trinary(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "Input Side", "Output Side"))
|
||||
side.set_value(side_to_idx(def.side))
|
||||
bundled.set_value(def.color ~= nil)
|
||||
tool_ctl.rs_cfg_color.set_value(value)
|
||||
|
||||
@@ -110,7 +110,7 @@ local function init(panel, units)
|
||||
|
||||
-- unit name identifier (type + index)
|
||||
local function get_name(t) return util.c(UNIT_TYPE_LABELS[t + 1], " ", util.trinary(util.is_int(unit.index), unit.index, "")) end
|
||||
local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=get_name(unit.type),height=1}
|
||||
local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=get_name(unit.type),width=15,height=1}
|
||||
|
||||
name_box.register(databus.ps, "unit_type_" .. i, function (t) name_box.set_value(get_name(t)) end)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.3"
|
||||
local RTU_VERSION = "v1.7.11"
|
||||
|
||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -127,7 +127,37 @@ local PORT_NAMES = {
|
||||
"U_EMER_COOL"
|
||||
}
|
||||
|
||||
local MODES = {
|
||||
IO_MODE.DIGITAL_IN, -- F_SCRAM
|
||||
IO_MODE.DIGITAL_IN, -- F_ACK
|
||||
IO_MODE.DIGITAL_IN, -- R_SCRAM
|
||||
IO_MODE.DIGITAL_IN, -- R_RESET
|
||||
IO_MODE.DIGITAL_IN, -- R_ENABLE
|
||||
IO_MODE.DIGITAL_IN, -- U_ACK
|
||||
IO_MODE.DIGITAL_OUT, -- F_ALARM
|
||||
IO_MODE.DIGITAL_OUT, -- F_ALARM_ANY
|
||||
IO_MODE.DIGITAL_OUT, -- WASTE_PU
|
||||
IO_MODE.DIGITAL_OUT, -- WASTE_PO
|
||||
IO_MODE.DIGITAL_OUT, -- WASTE_POPL
|
||||
IO_MODE.DIGITAL_OUT, -- WASTE_AM
|
||||
IO_MODE.DIGITAL_OUT, -- R_ACTIVE
|
||||
IO_MODE.DIGITAL_OUT, -- R_AUTO_CTRL
|
||||
IO_MODE.DIGITAL_OUT, -- R_SCRAMMED
|
||||
IO_MODE.DIGITAL_OUT, -- R_AUTO_SCRAM
|
||||
IO_MODE.DIGITAL_OUT, -- R_HIGH_DMG
|
||||
IO_MODE.DIGITAL_OUT, -- R_HIGH_TEMP
|
||||
IO_MODE.DIGITAL_OUT, -- R_LOW_COOLANT
|
||||
IO_MODE.DIGITAL_OUT, -- R_EXCESS_HC
|
||||
IO_MODE.DIGITAL_OUT, -- R_EXCESS_WS
|
||||
IO_MODE.DIGITAL_OUT, -- R_INSUFF_FUEL
|
||||
IO_MODE.DIGITAL_OUT, -- R_PLC_FAULT
|
||||
IO_MODE.DIGITAL_OUT, -- R_PLC_TIMEOUT
|
||||
IO_MODE.DIGITAL_OUT, -- U_ALARM
|
||||
IO_MODE.DIGITAL_OUT -- U_EMER_COOL
|
||||
}
|
||||
|
||||
assert(rsio.NUM_PORTS == #PORT_NAMES, "port names length incorrect")
|
||||
assert(rsio.NUM_PORTS == #MODES, "modes length incorrect")
|
||||
|
||||
-- port to string
|
||||
---@nodiscard
|
||||
@@ -209,45 +239,24 @@ local RS_DIO_MAP = {
|
||||
{ _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }
|
||||
}
|
||||
|
||||
assert(rsio.NUM_PORTS == #RS_DIO_MAP, "RS_DIO_MAP length incorrect")
|
||||
|
||||
-- get the I/O direction of a port
|
||||
---@nodiscard
|
||||
---@param port IO_PORT
|
||||
---@return IO_DIR
|
||||
function rsio.get_io_dir(port)
|
||||
if rsio.is_valid_port(port) then return RS_DIO_MAP[port].mode
|
||||
else return IO_DIR.IN end
|
||||
end
|
||||
|
||||
-- get the mode of a port
|
||||
---@nodiscard
|
||||
---@param port IO_PORT
|
||||
---@return IO_MODE
|
||||
function rsio.get_io_mode(port)
|
||||
local modes = {
|
||||
IO_MODE.DIGITAL_IN, -- F_SCRAM
|
||||
IO_MODE.DIGITAL_IN, -- F_ACK
|
||||
IO_MODE.DIGITAL_IN, -- R_SCRAM
|
||||
IO_MODE.DIGITAL_IN, -- R_RESET
|
||||
IO_MODE.DIGITAL_IN, -- R_ENABLE
|
||||
IO_MODE.DIGITAL_IN, -- U_ACK
|
||||
IO_MODE.DIGITAL_OUT, -- F_ALARM
|
||||
IO_MODE.DIGITAL_OUT, -- F_ALARM_ANY
|
||||
IO_MODE.DIGITAL_OUT, -- WASTE_PU
|
||||
IO_MODE.DIGITAL_OUT, -- WASTE_PO
|
||||
IO_MODE.DIGITAL_OUT, -- WASTE_POPL
|
||||
IO_MODE.DIGITAL_OUT, -- WASTE_AM
|
||||
IO_MODE.DIGITAL_OUT, -- R_ACTIVE
|
||||
IO_MODE.DIGITAL_OUT, -- R_AUTO_CTRL
|
||||
IO_MODE.DIGITAL_OUT, -- R_SCRAMMED
|
||||
IO_MODE.DIGITAL_OUT, -- R_AUTO_SCRAM
|
||||
IO_MODE.DIGITAL_OUT, -- R_HIGH_DMG
|
||||
IO_MODE.DIGITAL_OUT, -- R_HIGH_TEMP
|
||||
IO_MODE.DIGITAL_OUT, -- R_LOW_COOLANT
|
||||
IO_MODE.DIGITAL_OUT, -- R_EXCESS_HC
|
||||
IO_MODE.DIGITAL_OUT, -- R_EXCESS_WS
|
||||
IO_MODE.DIGITAL_OUT, -- R_INSUFF_FUEL
|
||||
IO_MODE.DIGITAL_OUT, -- R_PLC_FAULT
|
||||
IO_MODE.DIGITAL_OUT, -- R_PLC_TIMEOUT
|
||||
IO_MODE.DIGITAL_OUT, -- U_ALARM
|
||||
IO_MODE.DIGITAL_OUT -- U_EMER_COOL
|
||||
}
|
||||
|
||||
if util.is_int(port) and port > 0 and port <= #modes then
|
||||
return modes[port]
|
||||
else
|
||||
return IO_MODE.ANALOG_IN
|
||||
end
|
||||
if rsio.is_valid_port(port) then return MODES[port]
|
||||
else return IO_MODE.ANALOG_IN end
|
||||
end
|
||||
|
||||
--#endregion
|
||||
@@ -261,7 +270,7 @@ local RS_SIDES = rs.getSides()
|
||||
---@param port IO_PORT
|
||||
---@return boolean valid
|
||||
function rsio.is_valid_port(port)
|
||||
return util.is_int(port) and (port > 0) and (port <= IO_PORT.U_EMER_COOL)
|
||||
return util.is_int(port) and port > 0 and port <= rsio.NUM_PORTS
|
||||
end
|
||||
|
||||
-- check if a side is valid
|
||||
|
||||
@@ -16,13 +16,13 @@ local type = type
|
||||
|
||||
local t_concat = table.concat
|
||||
local t_insert = table.insert
|
||||
local t_unpack = table.unpack
|
||||
local t_pack = table.pack
|
||||
|
||||
---@class util
|
||||
local util = {}
|
||||
|
||||
-- scada-common version
|
||||
util.version = "1.1.9"
|
||||
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<br>
|
||||
-- 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
|
||||
@@ -104,17 +115,13 @@ end
|
||||
---@return table lines
|
||||
function util.strwrap(str, limit) return cc_strings.wrap(str, limit) end
|
||||
|
||||
-- luacheck: no unused args
|
||||
|
||||
-- concatenation with built-in to string
|
||||
---@nodiscard
|
||||
---@vararg any
|
||||
---@return string
|
||||
---@diagnostic disable-next-line: unused-vararg
|
||||
function util.concat(...)
|
||||
local strings = {}
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
for i = 1, arg.n do strings[i] = util.strval(arg[i]) end
|
||||
local args, strings = t_pack(...), {}
|
||||
for i = 1, args.n do strings[i] = util.strval(args[i]) end
|
||||
return t_concat(strings)
|
||||
end
|
||||
|
||||
@@ -125,10 +132,7 @@ util.c = util.concat
|
||||
---@nodiscard
|
||||
---@param format string
|
||||
---@vararg any
|
||||
---@diagnostic disable-next-line: unused-vararg
|
||||
function util.sprintf(format, ...) return string.format(format, t_unpack(arg)) end
|
||||
|
||||
-- luacheck: unused args
|
||||
function util.sprintf(format, ...) return string.format(format, ...) end
|
||||
|
||||
-- format a number string with commas as the thousands separator<br>
|
||||
-- subtracts from spaces at the start if present for each comma used
|
||||
|
||||
@@ -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...")
|
||||
|
||||
@@ -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
|
||||
1088
supervisor/configure.lua
Normal file
1088
supervisor/configure.lua
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local config = require("supervisor.config")
|
||||
local databus = require("supervisor.databus")
|
||||
local supervisor = require("supervisor.supervisor")
|
||||
|
||||
local pgi = require("supervisor.panel.pgi")
|
||||
local style = require("supervisor.panel.style")
|
||||
@@ -88,7 +88,7 @@ local function init(panel)
|
||||
local plc_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||
local plc_list = Div{parent=plc_page,x=2,y=2,width=49}
|
||||
|
||||
for i = 1, config.NUM_REACTORS do
|
||||
for i = 1, supervisor.config.UnitCount do
|
||||
local ps_prefix = "plc_" .. i .. "_"
|
||||
local plc_entry = Div{parent=plc_list,height=3,fg_bg=bw_fg_bg}
|
||||
|
||||
|
||||
@@ -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,7 +34,7 @@ svsessions.SESSION_TYPE = SESSION_TYPE
|
||||
local self = {
|
||||
nic = nil, ---@type nic|nil
|
||||
fp_ok = false,
|
||||
num_reactors = 0,
|
||||
config = nil, ---@type svr_config
|
||||
facility = nil, ---@type facility|nil
|
||||
sessions = { rtu = {}, plc = {}, crd = {}, pdg = {} },
|
||||
next_ids = { rtu = 0, plc = 0, crd = 0, pdg = 0 }
|
||||
@@ -60,7 +58,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 +133,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 +195,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
|
||||
@@ -280,14 +278,14 @@ 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",
|
||||
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 +294,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 +302,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 +332,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 +341,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 +372,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 +381,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 +415,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 +424,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 = {
|
||||
|
||||
@@ -14,70 +14,67 @@ 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.5"
|
||||
|
||||
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)
|
||||
cfv.assert_eq(#config.CoolingConfig, config.UnitCount)
|
||||
assert(cfv.valid(), "startup> the number of reactor cooling configurations is different than the number of units")
|
||||
|
||||
assert(cfv.valid(), "bad config file: missing/invalid fields")
|
||||
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
|
||||
|
||||
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")
|
||||
if config.FacilityTankMode > 0 then
|
||||
assert(config.UnitCount == #config.FacilityTankDefs, "startup> the number of facility tank definitions must be equal to the number of units in facility tank mode")
|
||||
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
----------------------------------------
|
||||
-- log init
|
||||
----------------------------------------
|
||||
|
||||
log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG == true)
|
||||
log.init(config.LogPath, config.LogMode, config.LogDebug)
|
||||
|
||||
log.info("========================================")
|
||||
log.info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION)
|
||||
@@ -102,8 +99,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" and string.len(config.AuthKey) > 0 then
|
||||
network.init_mac(config.AuthKey)
|
||||
end
|
||||
|
||||
-- get modem
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user