Merge pull request #404 from MikaylaFischler/devel

2023.12.31 Release
This commit is contained in:
Mikayla 2023-12-31 21:34:40 -05:00 committed by GitHub
commit bfa1f4d0c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1334 additions and 201 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 println(message) print(tostring(message)) end
local function print(message) term.write(tostring(message)) end local function print(message) term.write(tostring(message)) end
local CCMSI_VERSION = "v1.12" local CCMSI_VERSION = "v1.12a"
local install_dir = "/.install-cache" local install_dir = "/.install-cache"
local manifest_path = "https://mikaylafischler.github.io/cc-mek-scada/manifests/" local manifest_path = "https://mikaylafischler.github.io/cc-mek-scada/manifests/"
@ -345,8 +345,7 @@ elseif mode == "install" or mode == "update" then
if mode == "install" then if mode == "install" then
println("Installing " .. app .. " files...") println("Installing " .. app .. " files...")
elseif mode == "update" then elseif mode == "update" then
if app == "supervisor" or app == "coordinator" or app == "pocket" then if app == "coordinator" or app == "pocket" then println("Updating " .. app .. " files... (keeping old config.lua)")
println("Updating " .. app .. " files... (keeping old config.lua)")
else println("Updating " .. app .. " files...") end else println("Updating " .. app .. " files...") end
end end
white() white()

View File

@ -4,12 +4,12 @@ if fs.exists("reactor-plc/configure.lua") then
require("reactor-plc.configure").configure() require("reactor-plc.configure").configure()
elseif fs.exists("rtu/configure.lua") then elseif fs.exists("rtu/configure.lua") then
require("rtu.configure").configure() require("rtu.configure").configure()
elseif fs.exists("supervisor/startup.lua") then elseif fs.exists("supervisor/configure.lua") then
print("CONFIGURE> SUPERVISOR CONFIGURATOR NOT YET IMPLEMENTED IN BETA") require("supervisor.configure").configure()
elseif fs.exists("coordinator/startup.lua") then 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 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 else
print("CONFIGURE> NO CONFIGURATOR FOUND") print("CONFIGURE> NO CONFIGURATOR FOUND")
print("CONFIGURE> EXIT") print("CONFIGURE> EXIT")

View File

@ -110,9 +110,9 @@ function iocontrol.init(conf, comms)
-- determine tank information -- determine tank information
if io.facility.tank_mode == 0 then if io.facility.tank_mode == 0 then
io.facility.tank_defs = {} 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 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 end
io.facility.tank_list = { table.unpack(io.facility.tank_defs) } io.facility.tank_list = { table.unpack(io.facility.tank_defs) }
@ -214,7 +214,7 @@ function iocontrol.init(conf, comms)
num_boilers = 0, num_boilers = 0,
num_turbines = 0, num_turbines = 0,
num_snas = 0, num_snas = 0,
has_tank = conf.cooling.r_cool[i].TANK, has_tank = conf.cooling.r_cool[i].TankConnection,
control_state = false, control_state = false,
burn_rate_cmd = 0.0, burn_rate_cmd = 0.0,
@ -295,13 +295,13 @@ function iocontrol.init(conf, comms)
end end
-- create boiler tables -- 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_ps_tbl, psil.create())
table.insert(entry.boiler_data_tbl, {}) table.insert(entry.boiler_data_tbl, {})
end end
-- create turbine tables -- 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_ps_tbl, psil.create())
table.insert(entry.turbine_data_tbl, {}) table.insert(entry.turbine_data_tbl, {})
end end

View File

@ -22,7 +22,7 @@ local sounder = require("coordinator.sounder")
local apisessions = require("coordinator.session.apisessions") local apisessions = require("coordinator.session.apisessions")
local COORDINATOR_VERSION = "v1.0.18" local COORDINATOR_VERSION = "v1.1.0"
local println = util.println local println = util.println
local println_ts = util.println_ts local println_ts = util.println_ts

View File

@ -7,7 +7,7 @@ local flasher = require("graphics.flasher")
local core = {} local core = {}
core.version = "2.0.9" core.version = "2.1.0"
core.flasher = flasher core.flasher = flasher
core.events = events core.events = events

View File

@ -1,5 +1,7 @@
-- Numeric Value Entry Graphics Element -- Numeric Value Entry Graphics Element
local util = require("scada-common.util")
local core = require("graphics.core") local core = require("graphics.core")
local element = require("graphics.element") local element = require("graphics.element")
@ -8,9 +10,11 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK
---@class number_field_args ---@class number_field_args
---@field default? number default value, defaults to 0 ---@field default? number default value, defaults to 0
---@field min? number minimum, forced on unfocus ---@field min? number minimum, enforced on unfocus
---@field max? number maximum, forced on unfocus ---@field max? number maximum, enforced on unfocus
---@field max_digits? integer maximum number of digits, defaults to width ---@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_decimal? boolean true to allow decimals
---@field allow_negative? boolean true to allow negative numbers ---@field allow_negative? boolean true to allow negative numbers
---@field dis_fg_bg? cpair foreground/background colors when disabled ---@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 ---@param args number_field_args
---@return graphics_element element, element_id id ---@return graphics_element element, element_id id
local function number_field(args) 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.height = 1
args.can_focus = true args.can_focus = true
@ -34,13 +41,13 @@ local function number_field(args)
local has_decimal = false 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 -- set initial value
e.value = "" .. (args.default or 0) e.value = "" .. (args.default or 0)
-- make an interactive field manager -- 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 -- handle mouse interaction
---@param event mouse_interaction mouse event ---@param event mouse_interaction mouse event
@ -62,7 +69,7 @@ local function number_field(args)
-- handle keyboard interaction -- handle keyboard interaction
---@param event key_interaction key event ---@param event key_interaction key event
function e.handle_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 tonumber(event.name) then
if e.value == 0 then e.value = "" end if e.value == 0 then e.value = "" end
ifield.try_insert_char(event.name) ifield.try_insert_char(event.name)
@ -127,6 +134,37 @@ local function number_field(args)
local min = tonumber(args.min) local min = tonumber(args.min)
if type(val) == "number" then 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 if type(args.max) == "number" and val > max then
e.value = "" .. max e.value = "" .. max
ifield.nav_start() ifield.nav_start()

View File

@ -3,9 +3,9 @@
-- --
local log = require("scada-common.log") local log = require("scada-common.log")
local rsio = require("scada-common.rsio")
local tcd = require("scada-common.tcd") local tcd = require("scada-common.tcd")
local util = require("scada-common.util") local util = require("scada-common.util")
local rsio = require("scada-common.rsio")
local core = require("graphics.core") 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 -- changes to the config data/format to let the user know
local changes = { 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 ---@class plc_configurator
@ -92,13 +93,13 @@ local tmp_cfg = {
Networked = false, Networked = false,
UnitID = 0, UnitID = 0,
EmerCoolEnable = false, EmerCoolEnable = false,
EmerCoolSide = nil, EmerCoolSide = nil, ---@type string|nil
EmerCoolColor = nil, EmerCoolColor = nil, ---@type color|nil
SVR_Channel = nil, SVR_Channel = nil, ---@type integer
PLC_Channel = nil, PLC_Channel = nil, ---@type integer
ConnTimeout = nil, ConnTimeout = nil, ---@type number
TrustedRange = nil, TrustedRange = nil, ---@type number
AuthKey = nil, AuthKey = nil, ---@type string|nil
LogMode = 0, LogMode = 0,
LogPath = "", LogPath = "",
LogDebug = false, LogDebug = false,
@ -239,7 +240,7 @@ local function config_view(display)
TextBox{parent=plc_c_2,x=1,y=3,height=3,text="If this is a networked PLC, currently only IDs 1 through 4 are acceptable.",fg_bg=g_lg_fg_bg} TextBox{parent=plc_c_2,x=1,y=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 #"} 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} 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}
@ -332,12 +333,12 @@ local function config_view(display)
PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} 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"} 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=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=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"} 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} TextBox{parent=net_c_2,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
local p2_err = TextBox{parent=net_c_2,x=8,y=14,height=1,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local 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}

View File

@ -58,7 +58,7 @@ function plc.load_config()
if config.Networked == true then if config.Networked == true then
cfv.assert_channel(config.SVR_Channel) cfv.assert_channel(config.SVR_Channel)
cfv.assert_channel(config.PLC_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_min(config.ConnTimeout, 2)
cfv.assert_type_num(config.TrustedRange) cfv.assert_type_num(config.TrustedRange)
cfv.assert_min(config.TrustedRange, 0) 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 renderer = require("reactor-plc.renderer")
local threads = require("reactor-plc.threads") local threads = require("reactor-plc.threads")
local R_PLC_VERSION = "v1.6.7" local R_PLC_VERSION = "v1.6.9"
local println = util.println local println = util.println
local println_ts = util.println_ts local println_ts = util.println_ts

View File

@ -3,10 +3,10 @@
-- --
local log = require("scada-common.log") local log = require("scada-common.log")
local ppm = require("scada-common.ppm")
local rsio = require("scada-common.rsio") local rsio = require("scada-common.rsio")
local tcd = require("scada-common.tcd") local tcd = require("scada-common.tcd")
local util = require("scada-common.util") local util = require("scada-common.util")
local ppm = require("scada-common.ppm")
local core = require("graphics.core") local core = require("graphics.core")
@ -72,7 +72,9 @@ assert(#PORT_DESC == rsio.NUM_PORTS)
assert(#PORT_DSGN == rsio.NUM_PORTS) assert(#PORT_DSGN == rsio.NUM_PORTS)
-- changes to the config data/format to let the user know -- 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 ---@class rtu_rs_definition
---@field unit integer|nil ---@field unit integer|nil
@ -172,11 +174,11 @@ local tmp_cfg = {
SpeakerVolume = 1.0, SpeakerVolume = 1.0,
Peripherals = {}, Peripherals = {},
Redstone = {}, Redstone = {},
SVR_Channel = nil, SVR_Channel = nil, ---@type integer
RTU_Channel = nil, RTU_Channel = nil, ---@type integer
ConnTimeout = nil, ConnTimeout = nil, ---@type number
TrustedRange = nil, TrustedRange = nil, ---@type number
AuthKey = nil, AuthKey = nil, ---@type string|nil
LogMode = 0, LogMode = 0,
LogPath = "", LogPath = "",
LogDebug = false LogDebug = false
@ -331,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=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."} 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} 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}
@ -394,12 +396,12 @@ local function config_view(display)
PushButton{parent=net_c_1,x=44,y=14,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} 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"} 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=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=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"} 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} TextBox{parent=net_c_2,x=1,y=10,height=4,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
local p2_err = TextBox{parent=net_c_2,x=8,y=14,height=1,width=35,text="",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local 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}
@ -829,11 +831,11 @@ local function config_view(display)
tool_ctl.p_name_msg = TextBox{parent=peri_c_4,x=1,y=1,height=2,text=""} tool_ctl.p_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_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_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_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() tool_ctl.p_unit.disable()
function tool_ctl.p_assign(opt) function tool_ctl.p_assign(opt)
@ -1052,7 +1054,7 @@ local function config_view(display)
tool_ctl.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=1,text=""} tool_ctl.rs_cfg_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_l = TextBox{parent=rs_c_3,x=27,y=3,width=7,height=1,text="Unit ID"}
tool_ctl.rs_cfg_unit = NumberField{parent=rs_c_3,x=27,y=4,width=10,max_digits=2,min=1,max=4,fg_bg=bw_fg_bg} tool_ctl.rs_cfg_unit = NumberField{parent=rs_c_3,x=27,y=4,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg}
tool_ctl.rs_cfg_side_l = TextBox{parent=rs_c_3,x=1,y=3,width=11,height=1,text="Output Side"} 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 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}
@ -1142,7 +1144,7 @@ local function config_view(display)
tool_ctl.importing_any_dc = false 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.SVR_Channel = config.SVR_CHANNEL
tmp_cfg.RTU_Channel = config.RTU_CHANNEL tmp_cfg.RTU_Channel = config.RTU_CHANNEL
tmp_cfg.ConnTimeout = config.COMMS_TIMEOUT tmp_cfg.ConnTimeout = config.COMMS_TIMEOUT

View File

@ -43,7 +43,7 @@ function rtu.load_config()
cfv.assert_type_num(config.SpeakerVolume) cfv.assert_type_num(config.SpeakerVolume)
cfv.assert_channel(config.SVR_Channel) cfv.assert_channel(config.SVR_Channel)
cfv.assert_channel(config.RTU_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_min(config.ConnTimeout, 2)
cfv.assert_type_num(config.TrustedRange) cfv.assert_type_num(config.TrustedRange)
cfv.assert_min(config.TrustedRange, 0) 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 sps_rtu = require("rtu.dev.sps_rtu")
local turbinev_rtu = require("rtu.dev.turbinev_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu")
local RTU_VERSION = "v1.7.8" local RTU_VERSION = "v1.7.11"
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE

View File

@ -17,7 +17,7 @@ local max_distance = nil
local comms = {} local comms = {}
-- protocol/data version (protocol/data independent changes tracked by util.lua version) -- protocol/data version (protocol/data independent changes tracked by util.lua version)
comms.version = "2.4.2" comms.version = "2.4.3"
---@enum PROTOCOL ---@enum PROTOCOL
local PROTOCOL = { local PROTOCOL = {

View File

@ -22,7 +22,7 @@ local t_pack = table.pack
local util = {} local util = {}
-- scada-common version -- scada-common version
util.version = "1.1.11" util.version = "1.1.12"
util.TICK_TIME_S = 0.05 util.TICK_TIME_S = 0.05
util.TICK_TIME_MS = 50 util.TICK_TIME_MS = 50
@ -78,6 +78,17 @@ function util.strval(val)
else return tostring(val) end else return tostring(val) end
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 -- repeat a space n times
---@nodiscard ---@nodiscard
---@param n integer ---@param n integer

View File

@ -2,7 +2,7 @@ local util = require("scada-common.util")
local println = util.println local println = util.println
local BOOTLOADER_VERSION = "0.4" local BOOTLOADER_VERSION = "0.5"
println("SCADA BOOTLOADER V" .. BOOTLOADER_VERSION) println("SCADA BOOTLOADER V" .. BOOTLOADER_VERSION)
println("BOOT> SCANNING FOR APPLICATIONS...") 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 -- create units
for i = 1, num_reactors do 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) table.insert(self.group_map, 0)
end end

View File

@ -4,8 +4,8 @@
local util = require("scada-common.util") local util = require("scada-common.util")
local config = require("supervisor.config")
local databus = require("supervisor.databus") local databus = require("supervisor.databus")
local supervisor = require("supervisor.supervisor")
local pgi = require("supervisor.panel.pgi") local pgi = require("supervisor.panel.pgi")
local style = require("supervisor.panel.style") 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_page = Div{parent=page_div,x=1,y=1,hidden=true}
local plc_list = Div{parent=plc_page,x=2,y=2,width=49} 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 ps_prefix = "plc_" .. i .. "_"
local plc_entry = Div{parent=plc_list,height=3,fg_bg=bw_fg_bg} 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 mqueue = require("scada-common.mqueue")
local util = require("scada-common.util") local util = require("scada-common.util")
local config = require("supervisor.config")
local databus = require("supervisor.databus") local databus = require("supervisor.databus")
local facility = require("supervisor.facility") local facility = require("supervisor.facility")
local svqtypes = require("supervisor.session.svqtypes")
local coordinator = require("supervisor.session.coordinator") local coordinator = require("supervisor.session.coordinator")
local plc = require("supervisor.session.plc") local plc = require("supervisor.session.plc")
local pocket = require("supervisor.session.pocket") local pocket = require("supervisor.session.pocket")
local rtu = require("supervisor.session.rtu") local rtu = require("supervisor.session.rtu")
local svqtypes = require("supervisor.session.svqtypes")
-- Supervisor Sessions Handler -- Supervisor Sessions Handler
@ -36,7 +34,7 @@ svsessions.SESSION_TYPE = SESSION_TYPE
local self = { local self = {
nic = nil, ---@type nic|nil nic = nil, ---@type nic|nil
fp_ok = false, fp_ok = false,
num_reactors = 0, config = nil, ---@type svr_config
facility = nil, ---@type facility|nil facility = nil, ---@type facility|nil
sessions = { rtu = {}, plc = {}, crd = {}, pdg = {} }, sessions = { rtu = {}, plc = {}, crd = {}, pdg = {} },
next_ids = { rtu = 0, plc = 0, crd = 0, pdg = 0 } 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 ~= nil then
if msg.qtype == mqueue.TYPE.PACKET then if msg.qtype == mqueue.TYPE.PACKET then
-- handle a packet to be sent -- 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 elseif msg.qtype == mqueue.TYPE.COMMAND then
-- handle instruction/notification -- handle instruction/notification
elseif msg.qtype == mqueue.TYPE.DATA then elseif msg.qtype == mqueue.TYPE.DATA then
@ -135,7 +133,7 @@ local function _shutdown(session)
while session.out_queue.ready() do while session.out_queue.ready() do
local msg = session.out_queue.pop() local msg = session.out_queue.pop()
if msg ~= nil and msg.qtype == mqueue.TYPE.PACKET then 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
end end
@ -197,13 +195,13 @@ end
-- initialize svsessions -- initialize svsessions
---@param nic nic network interface device ---@param nic nic network interface device
---@param fp_ok boolean front panel active ---@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 ---@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.nic = nic
self.fp_ok = fp_ok self.fp_ok = fp_ok
self.num_reactors = num_reactors self.config = config
self.facility = facility.new(num_reactors, cooling_conf) self.facility = facility.new(config.UnitCount, cooling_conf)
end end
-- find an RTU session by the computer ID -- find an RTU session by the computer ID
@ -280,14 +278,14 @@ end
---@param version string ---@param version string
---@return integer|false session_id ---@return integer|false session_id
function svsessions.establish_plc_session(source_addr, for_reactor, version) 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 ---@class plc_session_struct
local plc_s = { local plc_s = {
s_type = "plc", s_type = "plc",
open = true, open = true,
reactor = for_reactor, reactor = for_reactor,
version = version, version = version,
r_chan = config.PLC_CHANNEL, r_chan = self.config.PLC_Channel,
s_addr = source_addr, s_addr = source_addr,
in_queue = mqueue.new(), in_queue = mqueue.new(),
out_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 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, 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)
config.PLC_TIMEOUT, self.fp_ok)
table.insert(self.sessions.plc, plc_s) table.insert(self.sessions.plc, plc_s)
local units = self.facility.get_units() local units = self.facility.get_units()
@ -305,8 +302,7 @@ function svsessions.establish_plc_session(source_addr, for_reactor, version)
local mt = { local mt = {
---@param s plc_session_struct ---@param s plc_session_struct
__tostring = function (s) return util.c("PLC [", s.instance.get_id(), "] for reactor #", s.reactor, __tostring = function (s) return util.c("PLC [", s.instance.get_id(), "] for reactor #", s.reactor, " (@", s.s_addr, ")") end
" (@", s.s_addr, ")") end
} }
setmetatable(plc_s, mt) setmetatable(plc_s, mt)
@ -336,7 +332,7 @@ function svsessions.establish_rtu_session(source_addr, advertisement, version)
s_type = "rtu", s_type = "rtu",
open = true, open = true,
version = version, version = version,
r_chan = config.RTU_CHANNEL, r_chan = self.config.RTU_Channel,
s_addr = source_addr, s_addr = source_addr,
in_queue = mqueue.new(), in_queue = mqueue.new(),
out_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 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, 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)
advertisement, self.facility, self.fp_ok)
table.insert(self.sessions.rtu, rtu_s) table.insert(self.sessions.rtu, rtu_s)
local mt = { local mt = {
@ -377,7 +372,7 @@ function svsessions.establish_crd_session(source_addr, version)
s_type = "crd", s_type = "crd",
open = true, open = true,
version = version, version = version,
r_chan = config.CRD_CHANNEL, r_chan = self.config.CRD_Channel,
s_addr = source_addr, s_addr = source_addr,
in_queue = mqueue.new(), in_queue = mqueue.new(),
out_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 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, 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)
self.facility, self.fp_ok)
table.insert(self.sessions.crd, crd_s) table.insert(self.sessions.crd, crd_s)
local mt = { local mt = {
@ -421,7 +415,7 @@ function svsessions.establish_pdg_session(source_addr, version)
s_type = "pkt", s_type = "pkt",
open = true, open = true,
version = version, version = version,
r_chan = config.PKT_CHANNEL, r_chan = self.config.PKT_Channel,
s_addr = source_addr, s_addr = source_addr,
in_queue = mqueue.new(), in_queue = mqueue.new(),
out_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 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, 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)
self.fp_ok)
table.insert(self.sessions.pdg, pdg_s) table.insert(self.sessions.pdg, pdg_s)
local mt = { local mt = {

View File

@ -14,70 +14,67 @@ local util = require("scada-common.util")
local core = require("graphics.core") local core = require("graphics.core")
local config = require("supervisor.config") local configure = require("supervisor.configure")
local databus = require("supervisor.databus") local databus = require("supervisor.databus")
local renderer = require("supervisor.renderer") local renderer = require("supervisor.renderer")
local supervisor = require("supervisor.supervisor") local supervisor = require("supervisor.supervisor")
local svsessions = require("supervisor.session.svsessions") local svsessions = require("supervisor.session.svsessions")
local SUPERVISOR_VERSION = "v1.1.0" local SUPERVISOR_VERSION = "v1.2.5"
local println = util.println local println = util.println
local println_ts = util.println_ts 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() local cfv = util.new_validator()
cfv.assert_channel(config.SVR_CHANNEL) cfv.assert_eq(#config.CoolingConfig, config.UnitCount)
cfv.assert_channel(config.PLC_CHANNEL) assert(cfv.valid(), "startup> the number of reactor cooling configurations is different than the number of units")
cfv.assert_channel(config.RTU_CHANNEL)
cfv.assert_channel(config.CRD_CHANNEL)
cfv.assert_channel(config.PKT_CHANNEL)
cfv.assert_type_int(config.TRUSTED_RANGE)
cfv.assert_type_num(config.PLC_TIMEOUT)
cfv.assert_min(config.PLC_TIMEOUT, 2)
cfv.assert_type_num(config.RTU_TIMEOUT)
cfv.assert_min(config.RTU_TIMEOUT, 2)
cfv.assert_type_num(config.CRD_TIMEOUT)
cfv.assert_min(config.CRD_TIMEOUT, 2)
cfv.assert_type_num(config.PKT_TIMEOUT)
cfv.assert_min(config.PKT_TIMEOUT, 2)
cfv.assert_type_int(config.NUM_REACTORS)
cfv.assert_type_table(config.REACTOR_COOLING)
cfv.assert_type_int(config.FAC_TANK_MODE)
cfv.assert_type_table(config.FAC_TANK_DEFS)
cfv.assert_type_str(config.LOG_PATH)
cfv.assert_type_int(config.LOG_MODE)
assert(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), if config.FacilityTankMode > 0 then
"bad config file: FAC_TANK_DEFS length not equal to NUM_REACTORS") 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) for i = 1, config.UnitCount do
assert(cfv.valid(), "config: number of cooling configs different than number of units") local def = config.FacilityTankDefs[i]
cfv.assert_type_int(def)
for i = 1, config.NUM_REACTORS do cfv.assert_range(def, 0, 2)
cfv.assert_type_table(config.REACTOR_COOLING[i]) assert(cfv.valid(), "startup> invalid facility tank definition for reactor unit " .. i)
assert(cfv.valid(), "config: missing cooling entry for reactor " .. i) end
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)
end end
---------------------------------------- ----------------------------------------
-- log init -- 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("========================================")
log.info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION) log.info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION)
@ -102,8 +99,8 @@ local function main()
ppm.mount_all() ppm.mount_all()
-- message authentication init -- message authentication init
if type(config.AUTH_KEY) == "string" then if type(config.AuthKey) == "string" and string.len(config.AuthKey) > 0 then
network.init_mac(config.AUTH_KEY) network.init_mac(config.AuthKey)
end end
-- get modem -- get modem

View File

@ -2,8 +2,6 @@ local comms = require("scada-common.comms")
local log = require("scada-common.log") local log = require("scada-common.log")
local util = require("scada-common.util") local util = require("scada-common.util")
local config = require("supervisor.config")
local svsessions = require("supervisor.session.svsessions") local svsessions = require("supervisor.session.svsessions")
local supervisor = {} local supervisor = {}
@ -13,6 +11,77 @@ local DEVICE_TYPE = comms.DEVICE_TYPE
local ESTABLISH_ACK = comms.ESTABLISH_ACK local ESTABLISH_ACK = comms.ESTABLISH_ACK
local MGMT_TYPE = comms.MGMT_TYPE 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 -- supervisory controller communications
---@nodiscard ---@nodiscard
---@param _version string supervisor version ---@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 -- 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 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 ---@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 = { local self = {
last_est_acks = {} last_est_acks = {}
} }
comms.set_trusted_range(config.TRUSTED_RANGE) comms.set_trusted_range(config.TrustedRange)
-- PRIVATE FUNCTIONS -- -- PRIVATE FUNCTIONS --
-- configure modem channels -- configure modem channels
nic.closeAll() nic.closeAll()
nic.open(svr_channel) nic.open(config.SVR_Channel)
-- pass modem, status, and config data to svsessions -- 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 -- send an establish request response
---@param packet scada_packet ---@param packet scada_packet
@ -61,7 +121,7 @@ function supervisor.comms(_version, nic, fp_ok)
m_pkt.make(MGMT_TYPE.ESTABLISH, { ack, data }) 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()) 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 self.last_est_acks[packet.src_addr()] = ack
end end
@ -124,9 +184,9 @@ function supervisor.comms(_version, nic, fp_ok)
local src_addr = packet.scada_frame.src_addr() local src_addr = packet.scada_frame.src_addr()
local protocol = packet.scada_frame.protocol() 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) 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 -- look for an associated session
local session = svsessions.find_plc_session(src_addr) local session = svsessions.find_plc_session(src_addr)
@ -201,7 +261,7 @@ function supervisor.comms(_version, nic, fp_ok)
else else
log.debug(util.c("illegal packet type ", protocol, " on PLC channel")) log.debug(util.c("illegal packet type ", protocol, " on PLC channel"))
end end
elseif r_chan == rtu_channel then elseif r_chan == config.RTU_Channel then
-- look for an associated session -- look for an associated session
local session = svsessions.find_rtu_session(src_addr) local session = svsessions.find_rtu_session(src_addr)
@ -265,7 +325,7 @@ function supervisor.comms(_version, nic, fp_ok)
else else
log.debug(util.c("illegal packet type ", protocol, " on RTU channel")) log.debug(util.c("illegal packet type ", protocol, " on RTU channel"))
end end
elseif r_chan == crd_channel then elseif r_chan == config.CRD_Channel then
-- look for an associated session -- look for an associated session
local session = svsessions.find_crd_session(src_addr) 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")) 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)) 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 else
if last_ack ~= ESTABLISH_ACK.COLLISION then if last_ack ~= ESTABLISH_ACK.COLLISION then
log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator") 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 else
log.debug(util.c("illegal packet type ", protocol, " on coordinator channel")) log.debug(util.c("illegal packet type ", protocol, " on coordinator channel"))
end end
elseif r_chan == pkt_channel then elseif r_chan == config.PKT_Channel then
-- look for an associated session -- look for an associated session
local session = svsessions.find_pdg_session(src_addr) local session = svsessions.find_pdg_session(src_addr)