Compare commits

...

48 Commits

Author SHA1 Message Date
Mikayla
bfa1f4d0c6 Merge pull request #404 from MikaylaFischler/devel
2023.12.31 Release
2023-12-31 21:34:40 -05:00
Mikayla Fischler
737e0d72b0 changed supervisor facility config color theme as green is for the summary already 2023-12-31 15:41:28 -05:00
Mikayla Fischler
6edeb3e3b8 add default value to sounder volume for very old RTU config imports 2023-12-31 15:00:38 -05:00
Mikayla Fischler
fb00e98a5b more supervisor configurator bugfixes 2023-12-31 15:00:08 -05:00
Mikayla Fischler
4f952eff83 fixed supervisor incorrectly trying to validate tank defs when tank mode is zero 2023-12-31 14:14:35 -05:00
Mikayla Fischler
1eede97c08 fixed supervisor not using proper config on front panel 2023-12-31 14:04:22 -05:00
Mikayla Fischler
95419562ee no longer mention config.lua for supervisor update 2023-12-31 13:17:49 -05:00
Mikayla Fischler
7b85d947c4 fixed supervisor always using MAC 2023-12-30 22:57:30 -05:00
Mikayla Fischler
08e670091a #396 fixed fractional connection timeouts being treated as invalid 2023-12-30 20:25:57 -05:00
Mikayla
1348b632a8 Merge pull request #397 from MikaylaFischler/145-graphical-configure-utilities
Supervisor Configurator
2023-12-30 20:19:34 -05:00
Mikayla Fischler
8cd5162362 fixed PLCs not connecting, fixed facility tank mode checkbox not changing after import, and reordered info on tank mode vis about page 2023-12-30 20:18:58 -05:00
Mikayla Fischler
1c410a89d8 incremented comms version due to data change 2023-12-30 19:40:53 -05:00
Mikayla Fischler
6a931fced4 cleanup and fixes 2023-12-30 19:21:44 -05:00
Mikayla Fischler
622e2eeb90 more useful messages, incremented bootloader version 2023-12-30 14:51:25 -05:00
Mikayla Fischler
42cd9fff0c improved number field precision handling and limited decimal precision of timeouts #396 2023-12-30 14:41:03 -05:00
Mikayla Fischler
2a85a438ba #396 connection timeouts can now have a fractional part 2023-12-29 14:33:22 -05:00
Mikayla Fischler
338b3b1615 addressed luacheck warning 2023-12-29 14:29:46 -05:00
Mikayla Fischler
363f164f47 #308 deleted old config.lua 2023-12-29 14:12:54 -05:00
Mikayla Fischler
739f04ece9 #308 integrated new settings file with supervisor 2023-12-29 13:58:28 -05:00
Mikayla Fischler
c6ade68ce2 #308 importing legacy config 2023-12-29 12:40:48 -05:00
Mikayla Fischler
7d60e259e2 #308 supervisor configurator bugfixes and saving of settings 2023-12-29 01:07:50 -05:00
Mikayla Fischler
cd71c6a9c1 #308 summary display of supervisor config 2023-12-29 00:19:17 -05:00
Mikayla Fischler
26fe130609 #308 supervisor configurator completed facility tank mode and network config pages 2023-12-28 15:06:30 -05:00
Mikayla Fischler
95f87b1b05 #308 significantly improved facility dynamic tank configuration visualization 2023-12-26 13:13:05 -05:00
Mikayla Fischler
aebdf3e8df fixed include ordering 2023-12-26 13:11:46 -05:00
Mikayla Fischler
5d4fc36256 #308 WIP supervisor configurator 2023-12-18 15:23:51 -05:00
Mikayla
b799d785b9 Merge pull request #394 from MikaylaFischler/devel
2023.12.17 Release
2023-12-17 21:02:32 -05:00
Mikayla
d55442fa53 Merge pull request #393 from MikaylaFischler/145-graphical-configure-utilities
Bring in changes from 145 branch to devel for release
2023-12-17 20:51:05 -05:00
Mikayla Fischler
c870b749a4 cleanup 2023-12-17 20:48:02 -05:00
Mikayla Fischler
4421cbc0c5 fixed input/output side text being sometimes wrong on rtu configurator redstone editing 2023-12-17 20:47:17 -05:00
Mikayla Fischler
b6a3305f23 minor minification 2023-12-17 20:43:40 -05:00
Mikayla Fischler
5680260136 use existing is_valid_port rather than repeating the code 2023-12-17 20:43:08 -05:00
Mikayla Fischler
bc66ea6ecb #381 fixed plc main thread crash on modem connect after boot with no modem 2023-12-17 20:28:26 -05:00
Mikayla Fischler
1b20218445 #194 #382 ccmsi no longer deletes drive mounts and now prompts to delete unknown files/folders in root 2023-12-17 20:10:11 -05:00
Mikayla Fischler
466e442353 #389 added width to RTU front panel entry name box 2023-12-17 19:39:00 -05:00
Mikayla Fischler
9e6751f47f #391 fixed editing of redstone entries 2023-12-17 19:32:01 -05:00
Mikayla Fischler
f868923905 #392 fixed typo preventing water level low indicator from working 2023-12-17 18:04:09 -05:00
Mikayla Fischler
5d3fd6d939 #390 fixed not being able to edit entries after using ALL_WASTE shortcut 2023-12-17 17:46:18 -05:00
Mikayla Fischler
37659d687e #388 fixed peripherals list not updating on add/delete of config entry 2023-12-17 17:22:29 -05:00
Mikayla Fischler
f23b7e2c2f fixed out of bounds coordinates crashing GUI for form fields 2023-12-17 12:56:08 -05:00
Mikayla Fischler
55ccdd63d4 don't mention config.lua on update for apps that don't have it 2023-12-17 12:55:00 -05:00
Mikayla Fischler
5c88890ed4 removed redundant min_width values 2023-12-14 20:51:54 -05:00
Mikayla Fischler
fa0185c9a4 fixed checkbox width 2023-12-13 12:20:12 -05:00
Mikayla Fischler
e1ed9a8e5e fixed error messages not fitting and say input side when configuring inputs on RTU configurator 2023-11-29 22:25:34 -05:00
Mikayla
9f7e3bc282 Merge pull request #383 from MikaylaFischler/devel
2023.11.28 Beta Hotfix
2023-11-28 20:35:56 -05:00
Mikayla Fischler
4ec060ba24 #378 fixed unit input not being re-shown on RTU configurator 2023-11-28 18:05:07 -05:00
Mikayla
b1f1753a8d Merge pull request #379 from MikaylaFischler/377-compatibility-fixes-for-lua-5.2
#377 switched to using ... for vararg
2023-11-28 17:54:36 -05:00
Mikayla Fischler
94a62f8c31 #377 switched to using ... for vararg 2023-11-27 19:32:52 -05:00
29 changed files with 1483 additions and 330 deletions

View File

@@ -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()

View File

@@ -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")

View File

@@ -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

View File

@@ -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

View File

@@ -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}

View File

@@ -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

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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 = {

View File

@@ -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

View File

@@ -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

View File

@@ -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...")

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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}

View File

@@ -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 = {

View File

@@ -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

View File

@@ -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)