#580 work on supervisor wired modem configuration

This commit is contained in:
Mikayla Fischler 2025-06-15 15:43:04 -04:00
parent bee1cdf01c
commit 4a7fc6200e
9 changed files with 298 additions and 138 deletions

View File

@ -62,8 +62,10 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=49} local net_c_2 = Div{parent=net_cfg,x=2,y=4,width=49}
local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=49} local net_c_3 = Div{parent=net_cfg,x=2,y=4,width=49}
local net_c_4 = Div{parent=net_cfg,x=2,y=4,width=49} local net_c_4 = Div{parent=net_cfg,x=2,y=4,width=49}
local net_c_5 = Div{parent=net_cfg,x=2,y=4,width=49}
local net_c_6 = Div{parent=net_cfg,x=2,y=4,width=49}
local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4}} local net_pane = MultiPane{parent=net_cfg,x=1,y=4,panes={net_c_1,net_c_2,net_c_3,net_c_4,net_c_5,net_c_6}}
TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)} TextBox{parent=net_cfg,x=1,y=2,text=" Network Configuration",fg_bg=cpair(colors.black,colors.lightBlue)}
@ -137,40 +139,54 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
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=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_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=net_c_2,x=44,y=14,text="Next \x1a",callback=submit_timeouts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=net_c_3,x=1,y=1,text="Please set the trusted range below."} TextBox{parent=net_c_3,x=1,y=1,text="Please set the modem configuration below."}
TextBox{parent=net_c_3,x=1,y=3,height=3,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg} TextBox{parent=net_c_3,x=1,y=3,height=3,text="Communications with the coordinator,",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_3,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg} -- TextBox{parent=net_c_3,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg}
local range = NumberField{parent=net_c_3,x=1,y=10,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg} local use_wired = Checkbox{parent=net_c_3,x=1,y=12,label="Use Wired Modem",default=ini_cfg.ExtChargeIdling,box_fg_bg=cpair(colors.yellow,colors.black)}
local tr_err = TextBox{parent=net_c_3,x=8,y=14,width=35,text="Please set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local function submit_modems()
-- tmp_cfg. = use_wired.get_value()
net_pane.set_value(4)
end
PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_modems,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=net_c_5,x=1,y=1,text="Please set the trusted range below."}
TextBox{parent=net_c_5,x=1,y=3,height=3,text="Setting this to a value larger than 0 prevents connections with devices that many meters (blocks) away in any direction.",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_5,x=1,y=7,height=2,text="This is optional. You can disable this functionality by setting the value to 0.",fg_bg=g_lg_fg_bg}
local range = NumberField{parent=net_c_5,x=1,y=10,width=10,default=ini_cfg.TrustedRange,min=0,max_chars=20,allow_decimal=true,fg_bg=bw_fg_bg}
local tr_err = TextBox{parent=net_c_5,x=8,y=14,width=35,text="Please set the trusted range.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_tr() local function submit_tr()
local range_val = tonumber(range.get_value()) local range_val = tonumber(range.get_value())
if range_val ~= nil then if range_val ~= nil then
tmp_cfg.TrustedRange = range_val tmp_cfg.TrustedRange = range_val
net_pane.set_value(4) net_pane.set_value(6)
tr_err.hide(true) tr_err.hide(true)
else tr_err.show() end else tr_err.show() end
end end
PushButton{parent=net_c_3,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=net_c_5,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=net_c_3,x=44,y=14,text="Next \x1a",callback=submit_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=net_c_5,x=44,y=14,text="Next \x1a",callback=submit_tr,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
TextBox{parent=net_c_4,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."} TextBox{parent=net_c_6,x=1,y=1,height=2,text="Optionally, set the facility authentication key below. Do NOT use one of your passwords."}
TextBox{parent=net_c_4,x=1,y=4,height=6,text="This enables verifying that messages are authentic, so it is intended for security on multiplayer servers. All devices on the same network MUST use the same key if any device has a key. This does result in some extra computation (can slow things down).",fg_bg=g_lg_fg_bg} TextBox{parent=net_c_6,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 computation (can slow things down).",fg_bg=g_lg_fg_bg}
TextBox{parent=net_c_4,x=1,y=11,text="Facility Auth Key"} TextBox{parent=net_c_6,x=1,y=11,text="Facility Auth Key"}
local key, _ = TextField{parent=net_c_4,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg} local key, _ = TextField{parent=net_c_6,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg}
local function censor_key(enable) key.censor(tri(enable, "*", nil)) end local function censor_key(enable) key.censor(tri(enable, "*", nil)) end
local hide_key = Checkbox{parent=net_c_4,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key} local hide_key = Checkbox{parent=net_c_6,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key}
hide_key.set_value(true) hide_key.set_value(true)
censor_key(true) censor_key(true)
local key_err = TextBox{parent=net_c_4,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true} local key_err = TextBox{parent=net_c_6,x=8,y=14,width=35,text="Key must be at least 8 characters.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
local function submit_auth() local function submit_auth()
local v = key.get_value() local v = key.get_value()
@ -181,8 +197,8 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
else key_err.show() end else key_err.show() end
end end
PushButton{parent=net_c_4,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=net_c_6,x=1,y=14,text="\x1b Back",callback=function()net_pane.set_value(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=net_c_4,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=net_c_6,x=44,y=14,text="Next \x1a",callback=submit_auth,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion --#endregion

View File

@ -3,6 +3,7 @@
-- --
local log = require("scada-common.log") local log = require("scada-common.log")
local ppm = require("scada-common.ppm")
local tcd = require("scada-common.tcd") local tcd = require("scada-common.tcd")
local util = require("scada-common.util") local util = require("scada-common.util")
@ -96,8 +97,11 @@ local tmp_cfg = {
RTU_Timeout = nil, ---@type number RTU_Timeout = nil, ---@type number
CRD_Timeout = nil, ---@type number CRD_Timeout = nil, ---@type number
PKT_Timeout = nil, ---@type number PKT_Timeout = nil, ---@type number
WiredModem = false, ---@type string|false
WirelessModem = false, ---@type boolean
TrustedRange = nil, ---@type number TrustedRange = nil, ---@type number
AuthKey = nil, ---@type string|nil AuthKey = nil, ---@type string|nil
PocketTest = true, ---@type boolean
LogMode = 0, ---@type LOG_MODE LogMode = 0, ---@type LOG_MODE
LogPath = "", LogPath = "",
LogDebug = false, LogDebug = false,
@ -130,8 +134,11 @@ local fields = {
{ "RTU_Timeout", "RTU Connection Timeout", 5 }, { "RTU_Timeout", "RTU Connection Timeout", 5 },
{ "CRD_Timeout", "CRD Connection Timeout", 5 }, { "CRD_Timeout", "CRD Connection Timeout", 5 },
{ "PKT_Timeout", "PKT Connection Timeout", 5 }, { "PKT_Timeout", "PKT Connection Timeout", 5 },
{ "WiredModem", "Wired Modem", false },
{ "WirelessModem", "Pocket Wireless/Ender Modem", true },
{ "TrustedRange", "Trusted Range", 0 }, { "TrustedRange", "Trusted Range", 0 },
{ "AuthKey", "Facility Auth Key" , ""}, { "AuthKey", "Facility Auth Key" , "" },
{ "PocketTest", "Pocket Testing Features", true },
{ "LogMode", "Log Mode", log.MODE.APPEND }, { "LogMode", "Log Mode", log.MODE.APPEND },
{ "LogPath", "Log Path", "/log.txt" }, { "LogPath", "Log Path", "/log.txt" },
{ "LogDebug", "Log Debug Messages", false }, { "LogDebug", "Log Debug Messages", false },
@ -314,6 +321,14 @@ function configurator.configure(ask_config)
if k_e then display.handle_key(k_e) end if k_e then display.handle_key(k_e) end
elseif event == "paste" then elseif event == "paste" then
display.handle_paste(param1) display.handle_paste(param1)
elseif event == "peripheral_detach" then
---@diagnostic disable-next-line: discard-returns
ppm.handle_unmount(param1)
tool_ctl.gen_modem_list()
elseif event == "peripheral" then
---@diagnostic disable-next-line: discard-returns
ppm.mount(param1)
tool_ctl.gen_modem_list()
end end
if event == "terminate" then return end if event == "terminate" then return end

View File

@ -27,10 +27,16 @@ function databus.tx_versions(sv_v, comms_v)
databus.ps.publish("comms_version", comms_v) databus.ps.publish("comms_version", comms_v)
end end
-- transmit hardware status for modem connection state -- transmit hardware status for the core comms modem connection state
---@param has_modem boolean ---@param has_modem boolean
function databus.tx_hw_modem(has_modem) function databus.tx_hw_c_modem(has_modem)
databus.ps.publish("has_modem", has_modem) databus.ps.publish("has_c_modem", has_modem)
end
-- transmit hardware status for the pocket modem connection state
---@param has_modem boolean
function databus.tx_hw_p_modem(has_modem)
databus.ps.publish("has_p_modem", has_modem)
end end
-- transmit PLC firmware version and session connection state -- transmit PLC firmware version and session connection state

View File

@ -34,7 +34,8 @@ local ind_grn = style.ind_grn
-- create new front panel view -- create new front panel view
---@param panel DisplayBox main displaybox ---@param panel DisplayBox main displaybox
local function init(panel) ---@param wl_modem boolean if there is a separate wireless modem
local function init(panel, wl_modem)
local s_hi_box = style.theme.highlight_box local s_hi_box = style.theme.highlight_box
local s_hi_bright = style.theme.highlight_box_bright local s_hi_bright = style.theme.highlight_box_bright
@ -53,7 +54,7 @@ local function init(panel)
local main_page = Div{parent=page_div,x=1,y=1} local main_page = Div{parent=page_div,x=1,y=1}
local system = Div{parent=main_page,width=14,height=17,x=2,y=2} local system = Div{parent=main_page,width=18,height=17,x=2,y=2}
local on = LED{parent=system,label="STATUS",colors=cpair(colors.green,colors.red)} local on = LED{parent=system,label="STATUS",colors=cpair(colors.green,colors.red)}
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=ind_grn} local heartbeat = LED{parent=system,label="HEARTBEAT",colors=ind_grn}
@ -62,14 +63,21 @@ local function init(panel)
heartbeat.register(databus.ps, "heartbeat", heartbeat.update) heartbeat.register(databus.ps, "heartbeat", heartbeat.update)
local modem = LED{parent=system,label="MODEM",colors=ind_grn} local c_modem = LED{parent=system,label="MODEM"..util.trinary(wl_modem," A",""),colors=ind_grn}
system.line_break() system.line_break()
modem.register(databus.ps, "has_modem", modem.update) c_modem.register(databus.ps, "has_modem_a", c_modem.update)
if wl_modem then
local p_modem = LED{parent=system,label="MODEM B",colors=ind_grn}
system.line_break()
p_modem.register(databus.ps, "has_modem_b", p_modem.update)
end
---@diagnostic disable-next-line: undefined-field ---@diagnostic disable-next-line: undefined-field
local comp_id = util.sprintf("(%d)", os.getComputerID()) local comp_id = util.sprintf("(%d)", os.getComputerID())
TextBox{parent=system,x=9,y=4,width=6,text=comp_id,fg_bg=style.fp.disabled_fg} TextBox{parent=system,x=11,y=4,width=6,text=comp_id,fg_bg=style.fp.disabled_fg}
-- --
-- about footer -- about footer

159
supervisor/pcie.lua Normal file
View File

@ -0,0 +1,159 @@
--
-- PCIe - Borrowed the name of that protocol for fun (this manages physical peripherals)
--
local log = require("scada-common.log")
local network = require("scada-common.network")
local ppm = require("scada-common.ppm")
local databus = require("supervisor.databus")
local pcie_bus = {}
local bus = {
c_wired = false, ---@type string|false wired comms modem
c_nic = nil, ---@type nic core nic
p_nic = nil ---@type nic|nil pocket nic
}
-- network cards
---@class _svr_pcie_nic
---@field core nic the core comms NIC
---@field pocket nic the pocket NIC
pcie_bus.nic = {
-- close all channels then open a specified one on all nics
---@param channel integer
reset_open = function (channel)
bus.c_nic.closeAll()
bus.c_nic.open(channel)
if bus.p_nic then
bus.p_nic.closeAll()
bus.p_nic.open(channel)
end
end
}
-- initialize peripherals
---@param config svr_config
---@param println function
function pcie_bus.init(config, println)
-- setup networking peripheral(s)
local core_modem, core_iface = ppm.get_wireless_modem()
if type(config.WiredModem) == "string" then
bus.c_wired = config.WiredModem
core_modem = ppm.get_wired_modem(config.WiredModem)
end
if not (core_modem and core_iface) then
println("startup> core comms modem not found")
log.fatal("no core comms modem on startup")
return
end
bus.c_nic = network.nic(core_iface, core_modem)
if config.WirelessModem and config.WiredModem then
local pocket_modem, pocket_iface = ppm.get_wireless_modem()
if not (pocket_modem and pocket_iface) then
println("startup> pocket wireless modem not found")
log.fatal("no pocket wireless modem on startup")
return
end
bus.p_nic = network.nic(pocket_iface, pocket_modem)
end
pcie_bus.nic.core = bus.c_nic
pcie_bus.nic.pocket = bus.p_nic or bus.c_nic
databus.tx_hw_c_modem(true)
databus.tx_hw_p_modem(config.WirelessModem)
end
-- handle the connecting of a device
---@param iface string
---@param type string
---@param device table
---@param println function
function pcie_bus.connect(iface, type, device, println)
if type == "modem" then
---@cast device Modem
if device.isWireless() then
if not (bus.c_wired or bus.c_nic.is_connected()) then
-- reconnected comms modem
bus.c_nic.connect(device)
println("core comms modem reconnected")
log.info("core comms modem reconnected")
databus.tx_hw_c_modem(true)
elseif bus.p_nic and not bus.p_nic.is_connected() then
-- reconnected pocket modem
bus.p_nic.connect(device)
println("pocket modem reconnected")
log.info("pocket modem reconnected")
databus.tx_hw_p_modem(true)
else
log.info("unused wireless modem reconnected")
end
elseif iface == bus.c_wired then
-- reconnected wired comms modem
bus.c_nic.connect(device)
println("core comms modem reconnected")
log.info("core comms modem reconnected")
databus.tx_hw_c_modem(true)
else
log.info("wired modem reconnected")
end
end
end
-- handle the removal of a device
---@param type string
---@param device table
---@param println function
function pcie_bus.remove(type, device, println)
if type == "modem" then
---@cast device Modem
if bus.c_nic.is_modem(device) then
bus.c_nic.disconnect()
println("core comms modem disconnected")
log.warning("core comms modem disconnected")
local other_modem = ppm.get_wireless_modem()
if other_modem and not bus.c_wired then
log.info("found another wireless modem, using it for comms")
bus.c_nic.connect(other_modem)
else
databus.tx_hw_c_modem(false)
end
elseif bus.p_nic and bus.p_nic.is_modem(device) then
bus.p_nic.disconnect()
println("pocket modem disconnected")
log.warning("pocket modem disconnected")
local other_modem = ppm.get_wireless_modem()
if other_modem then
log.info("found another wireless modem, using it for pocket comms")
bus.p_nic.connect(other_modem)
else
databus.tx_hw_p_modem(false)
end
else
log.warning("non-comms modem disconnected")
end
end
end
-- check if a dedicated pocket nic is in use
function pcie_bus.has_pocket_nic() return bus.p_nic ~= nil end
return pcie_bus

View File

@ -19,10 +19,11 @@ local ui = {
} }
-- try to start the UI -- try to start the UI
---@param wl_modem boolean if there is a separate wireless modem to display the status of
---@param theme FP_THEME front panel theme ---@param theme FP_THEME front panel theme
---@param color_mode COLOR_MODE color mode ---@param color_mode COLOR_MODE color mode
---@return boolean success, any error_msg ---@return boolean success, any error_msg
function renderer.try_start_ui(theme, color_mode) function renderer.try_start_ui(wl_modem, theme, color_mode)
local status, msg = true, nil local status, msg = true, nil
if ui.display == nil then if ui.display == nil then
@ -49,7 +50,7 @@ function renderer.try_start_ui(theme, color_mode)
-- init front panel view -- init front panel view
status, msg = pcall(function () status, msg = pcall(function ()
ui.display = DisplayBox{window=term.current(),fg_bg=style.fp.root} ui.display = DisplayBox{window=term.current(),fg_bg=style.fp.root}
panel_view(ui.display) panel_view(ui.display, wl_modem)
end) end)
if status then if status then

View File

@ -6,6 +6,7 @@ local log = require("scada-common.log")
local mqueue = require("scada-common.mqueue") local mqueue = require("scada-common.mqueue")
local types = require("scada-common.types") local types = require("scada-common.types")
local util = require("scada-common.util") local util = require("scada-common.util")
local pcie = require("supervisor.pcie")
local databus = require("supervisor.databus") local databus = require("supervisor.databus")
@ -41,20 +42,17 @@ svsessions.SESSION_TYPE = SESSION_TYPE
local self = { local self = {
-- references to supervisor state and other data -- references to supervisor state and other data
nic = nil, ---@type nic|nil
fp_ok = false, fp_ok = false,
config = nil, ---@type svr_config config = nil, ---@type svr_config|nil
facility = nil, ---@type facility|nil facility = nil, ---@type facility|nil
plc_ini_reset = {}, plc_ini_reset = {},
-- lists of connected sessions -- lists of connected sessions
---@diagnostic disable: missing-fields
sessions = { sessions = {
rtu = {}, ---@type rtu_session_struct rtu = {}, ---@type rtu_session_struct[]
plc = {}, ---@type plc_session_struct plc = {}, ---@type plc_session_struct[]
crd = {}, ---@type crd_session_struct crd = {}, ---@type crd_session_struct[]
pdg = {} ---@type pdg_session_struct pdg = {} ---@type pdg_session_struct[]
}, },
---@diagnostic enable: missing-fields
-- next session IDs -- next session IDs
next_ids = { rtu = 0, plc = 0, crd = 0, pdg = 0 }, next_ids = { rtu = 0, plc = 0, crd = 0, pdg = 0 },
-- rtu device tracking and invalid assignment detection -- rtu device tracking and invalid assignment detection
@ -83,7 +81,9 @@ 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, self.config.SVR_Channel, msg.message) if session.r_chan == self.config.PKT_Channel then
pcie.nic.pocket.transmit(session.r_chan, self.config.SVR_Channel, msg.message)
else pcie.nic.core.transmit(session.r_chan, self.config.SVR_Channel, msg.message) end
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
@ -139,12 +139,9 @@ end
local function _iterate(sessions) local function _iterate(sessions)
for i = 1, #sessions do for i = 1, #sessions do
local session = sessions[i] local session = sessions[i]
if session.open and session.instance.iterate() then if session.open and session.instance.iterate() then
_sv_handle_outq(session) _sv_handle_outq(session)
else else session.open = false end
session.open = false
end
end end
end end
@ -158,7 +155,9 @@ 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, self.config.SVR_Channel, msg.message) if session.r_chan == self.config.PKT_Channel then
pcie.nic.pocket.transmit(session.r_chan, self.config.SVR_Channel, msg.message)
else pcie.nic.core.transmit(session.r_chan, self.config.SVR_Channel, msg.message) end
end end
end end
@ -358,12 +357,10 @@ function svsessions.check_rtu_id(unit, list, max)
end end
-- initialize svsessions -- initialize svsessions
---@param nic nic network interface device
---@param fp_ok boolean front panel active ---@param fp_ok boolean front panel active
---@param config svr_config supervisor configuration ---@param config svr_config supervisor configuration
---@param facility facility ---@param facility facility
function svsessions.init(nic, fp_ok, config, facility) function svsessions.init(fp_ok, config, facility)
self.nic = nic
self.fp_ok = fp_ok self.fp_ok = fp_ok
self.config = config self.config = config
self.facility = facility self.facility = facility

View File

@ -3,6 +3,7 @@
-- --
require("/initenv").init_env() require("/initenv").init_env()
local pcie = require("supervisor.pcie")
local crash = require("scada-common.crash") local crash = require("scada-common.crash")
local comms = require("scada-common.comms") local comms = require("scada-common.comms")
@ -125,18 +126,11 @@ local function main()
network.init_mac(config.AuthKey) network.init_mac(config.AuthKey)
end end
-- get modem -- hardware bus initialization
local modem = ppm.get_wireless_modem() pcie.init(config, println)
if modem == nil then
println("startup> wireless modem not found")
log.fatal("no wireless modem on startup")
return
end
databus.tx_hw_modem(true)
-- start UI -- start UI
local fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode) local fp_ok, message = renderer.try_start_ui(pcie.has_pocket_nic(), config.FrontPanelTheme, config.ColorMode)
if not fp_ok then if not fp_ok then
println_ts(util.c("UI error: ", message)) println_ts(util.c("UI error: ", message))
@ -150,8 +144,7 @@ local function main()
local sv_facility = facility.new(config) local sv_facility = facility.new(config)
-- create network interface then setup comms -- create network interface then setup comms
local nic = network.nic(modem) local superv_comms = supervisor.comms(SUPERVISOR_VERSION, fp_ok, sv_facility)
local superv_comms = supervisor.comms(SUPERVISOR_VERSION, nic, fp_ok, sv_facility)
-- base loop clock (6.67Hz, 3 ticks) -- base loop clock (6.67Hz, 3 ticks)
local MAIN_CLOCK = 0.15 local MAIN_CLOCK = 0.15
@ -173,49 +166,13 @@ local function main()
-- handle event -- handle event
if event == "peripheral_detach" then if event == "peripheral_detach" then
local type, device = ppm.handle_unmount(param1) local type, device = ppm.handle_unmount(param1)
if type ~= nil and device ~= nil then if type ~= nil and device ~= nil then
if type == "modem" then pcie.remove(type, device, println_ts)
---@cast device Modem
-- we only care if this is our wireless modem
if nic.is_modem(device) then
nic.disconnect()
println_ts("wireless modem disconnected!")
log.warning("comms modem disconnected")
local other_modem = ppm.get_wireless_modem()
if other_modem then
log.info("found another wireless modem, using it for comms")
nic.connect(other_modem)
else
databus.tx_hw_modem(false)
end
else
log.warning("non-comms modem disconnected")
end
end
end end
elseif event == "peripheral" then elseif event == "peripheral" then
local type, device = ppm.mount(param1) local type, device = ppm.mount(param1)
if type ~= nil and device ~= nil then if type ~= nil and device ~= nil then
if type == "modem" then pcie.connect(param1, type, device, println_ts)
---@cast device Modem
if device.isWireless() and not nic.is_connected() then
-- reconnected modem
nic.connect(device)
println_ts("wireless modem reconnected.")
log.info("comms modem reconnected")
databus.tx_hw_modem(true)
elseif device.isWireless() then
log.info("unused wireless modem reconnected")
else
log.info("wired modem reconnected")
end
end
end end
elseif event == "timer" and loop_clock.is_clock(param1) then elseif event == "timer" and loop_clock.is_clock(param1) then
-- main loop tick -- main loop tick

View File

@ -1,6 +1,7 @@
local comms = require("scada-common.comms") 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 pcie = require("supervisor.pcie")
local themes = require("graphics.themes") local themes = require("graphics.themes")
@ -123,11 +124,10 @@ end
-- supervisory controller communications -- supervisory controller communications
---@nodiscard ---@nodiscard
---@param _version string supervisor version ---@param _version string supervisor version
---@param nic nic network interface device
---@param fp_ok boolean if the front panel UI is running ---@param fp_ok boolean if the front panel UI is running
---@param facility facility facility instance ---@param facility facility facility instance
---@diagnostic disable-next-line: unused-local ---@diagnostic disable-next-line: unused-local
function supervisor.comms(_version, nic, fp_ok, facility) function supervisor.comms(_version, fp_ok, facility)
-- 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
@ -137,20 +137,24 @@ function supervisor.comms(_version, nic, fp_ok, facility)
comms.set_trusted_range(config.TrustedRange) comms.set_trusted_range(config.TrustedRange)
-- PRIVATE FUNCTIONS --
-- configure modem channels -- configure modem channels
nic.closeAll() pcie.nic.reset_open(config.SVR_Channel)
nic.open(config.SVR_Channel)
-- pass system data and objects to svsessions -- pass system data and objects to svsessions
svsessions.init(nic, fp_ok, config, facility) svsessions.init(fp_ok, config, facility)
-- get nic references
local c_nic = pcie.nic.core
local p_nic = pcie.nic.pocket
-- PRIVATE FUNCTIONS --
-- send an establish request response -- send an establish request response
---@param nic nic
---@param packet scada_packet ---@param packet scada_packet
---@param ack ESTABLISH_ACK ---@param ack ESTABLISH_ACK
---@param data? any optional data ---@param data? any optional data
local function _send_establish(packet, ack, data) local function _send_establish(nic, packet, ack, data)
local s_pkt = comms.scada_packet() local s_pkt = comms.scada_packet()
local m_pkt = comms.mgmt_packet() local m_pkt = comms.mgmt_packet()
@ -175,36 +179,33 @@ function supervisor.comms(_version, nic, fp_ok, facility)
---@param distance integer ---@param distance integer
---@return modbus_frame|rplc_frame|mgmt_frame|crdn_frame|nil packet ---@return modbus_frame|rplc_frame|mgmt_frame|crdn_frame|nil packet
function public.parse_packet(side, sender, reply_to, message, distance) function public.parse_packet(side, sender, reply_to, message, distance)
local s_pkt = nic.receive(side, sender, reply_to, message, distance)
local pkt = nil local pkt = nil
local s_pkt = c_nic.receive(side, sender, reply_to, message, distance)
if p_nic and not s_pkt then
-- try for it being from the pocket modem
s_pkt = p_nic.receive(side, sender, reply_to, message, distance)
end
if s_pkt then if s_pkt then
-- get as MODBUS TCP packet -- get as MODBUS TCP packet
if s_pkt.protocol() == PROTOCOL.MODBUS_TCP then if s_pkt.protocol() == PROTOCOL.MODBUS_TCP then
local m_pkt = comms.modbus_packet() local m_pkt = comms.modbus_packet()
if m_pkt.decode(s_pkt) then if m_pkt.decode(s_pkt) then pkt = m_pkt.get() end
pkt = m_pkt.get()
end
-- get as RPLC packet -- get as RPLC packet
elseif s_pkt.protocol() == PROTOCOL.RPLC then elseif s_pkt.protocol() == PROTOCOL.RPLC then
local rplc_pkt = comms.rplc_packet() local rplc_pkt = comms.rplc_packet()
if rplc_pkt.decode(s_pkt) then if rplc_pkt.decode(s_pkt) then pkt = rplc_pkt.get() end
pkt = rplc_pkt.get()
end
-- get as SCADA management packet -- get as SCADA management packet
elseif s_pkt.protocol() == PROTOCOL.SCADA_MGMT then elseif s_pkt.protocol() == PROTOCOL.SCADA_MGMT then
local mgmt_pkt = comms.mgmt_packet() local mgmt_pkt = comms.mgmt_packet()
if mgmt_pkt.decode(s_pkt) then if mgmt_pkt.decode(s_pkt) then pkt = mgmt_pkt.get() end
pkt = mgmt_pkt.get()
end
-- get as coordinator packet -- get as coordinator packet
elseif s_pkt.protocol() == PROTOCOL.SCADA_CRDN then elseif s_pkt.protocol() == PROTOCOL.SCADA_CRDN then
local crdn_pkt = comms.crdn_packet() local crdn_pkt = comms.crdn_packet()
if crdn_pkt.decode(s_pkt) then if crdn_pkt.decode(s_pkt) then pkt = crdn_pkt.get() end
pkt = crdn_pkt.get()
end
else else
log.debug("attempted parse of illegal packet type " .. s_pkt.protocol(), true) log.debug("receive[" .. side .. "] attempted parse of illegal packet type " .. s_pkt.protocol(), true)
end end
end end
@ -257,7 +258,7 @@ function supervisor.comms(_version, nic, fp_ok, facility)
log.info(util.c("dropping PLC establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) log.info(util.c("dropping PLC establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
end end
_send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
elseif dev_type == DEVICE_TYPE.PLC then elseif dev_type == DEVICE_TYPE.PLC then
-- PLC linking request -- PLC linking request
if packet.length == 4 and type(packet.data[4]) == "number" then if packet.length == 4 and type(packet.data[4]) == "number" then
@ -270,7 +271,7 @@ function supervisor.comms(_version, nic, fp_ok, facility)
log.warning(util.c("PLC_ESTABLISH: denied assignment ", reactor_id, " outside of configured unit count ", config.UnitCount)) log.warning(util.c("PLC_ESTABLISH: denied assignment ", reactor_id, " outside of configured unit count ", config.UnitCount))
end end
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY)
else else
-- try to establish the session -- try to establish the session
local plc_id = svsessions.establish_plc_session(src_addr, i_seq_num, reactor_id, firmware_v) local plc_id = svsessions.establish_plc_session(src_addr, i_seq_num, reactor_id, firmware_v)
@ -281,25 +282,25 @@ function supervisor.comms(_version, nic, fp_ok, facility)
log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id))
end end
_send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION) _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.COLLISION)
else else
-- got an ID; assigned to a reactor successfully -- got an ID; assigned to a reactor successfully
println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected")) println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected"))
log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id)) log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id))
_send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.ALLOW)
end end
end end
else else
log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type") log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type")
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY)
end end
else else
log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC channel")) log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC channel"))
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY)
end end
else else
log.debug("invalid establish packet (on PLC channel)") log.debug("invalid establish packet (on PLC channel)")
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY)
end end
else else
-- any other packet should be session related, discard it -- any other packet should be session related, discard it
@ -343,7 +344,7 @@ function supervisor.comms(_version, nic, fp_ok, facility)
log.info(util.c("dropping RTU establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) log.info(util.c("dropping RTU establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
end end
_send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
elseif dev_type == DEVICE_TYPE.RTU then elseif dev_type == DEVICE_TYPE.RTU then
if packet.length == 4 then if packet.length == 4 then
-- this is an RTU advertisement for a new session -- this is an RTU advertisement for a new session
@ -352,18 +353,18 @@ function supervisor.comms(_version, nic, fp_ok, facility)
println(util.c("RTU (", firmware_v, ") [@", src_addr, "] \xbb connected")) println(util.c("RTU (", firmware_v, ") [@", src_addr, "] \xbb connected"))
log.info(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) log.info(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [@", src_addr, "] connected with session ID ", s_id))
_send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.ALLOW)
else else
log.debug("RTU_ESTABLISH: packet length mismatch") log.debug("RTU_ESTABLISH: packet length mismatch")
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY)
end end
else else
log.debug(util.c("illegal establish packet for device ", dev_type, " on RTU channel")) log.debug(util.c("illegal establish packet for device ", dev_type, " on RTU channel"))
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY)
end end
else else
log.debug("invalid establish packet (on RTU channel)") log.debug("invalid establish packet (on RTU channel)")
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY)
end end
else else
-- any other packet should be session related, discard it -- any other packet should be session related, discard it
@ -397,7 +398,7 @@ function supervisor.comms(_version, nic, fp_ok, facility)
log.info(util.c("dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) log.info(util.c("dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
end end
_send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
elseif dev_type == DEVICE_TYPE.CRD then elseif dev_type == DEVICE_TYPE.CRD then
-- this is an attempt to establish a new coordinator session -- this is an attempt to establish a new coordinator session
local s_id = svsessions.establish_crd_session(src_addr, i_seq_num, firmware_v) local s_id = svsessions.establish_crd_session(src_addr, i_seq_num, firmware_v)
@ -406,21 +407,21 @@ function supervisor.comms(_version, nic, fp_ok, facility)
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, { config.UnitCount, facility.get_cooling_conf() }) _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, facility.get_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")
end end
_send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION) _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.COLLISION)
end end
else else
log.debug(util.c("illegal establish packet for device ", dev_type, " on coordinator channel")) log.debug(util.c("illegal establish packet for device ", dev_type, " on coordinator channel"))
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY)
end end
else else
log.debug("CRD_ESTABLISH: establish packet length mismatch") log.debug("CRD_ESTABLISH: establish packet length mismatch")
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) _send_establish(c_nic, packet.scada_frame, ESTABLISH_ACK.DENY)
end end
else else
-- any other packet should be session related, discard it -- any other packet should be session related, discard it
@ -464,7 +465,7 @@ function supervisor.comms(_version, nic, fp_ok, facility)
log.info(util.c("dropping PDG establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) log.info(util.c("dropping PDG establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
end end
_send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) _send_establish(p_nic or c_nic, packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
elseif dev_type == DEVICE_TYPE.PKT then elseif dev_type == DEVICE_TYPE.PKT then
-- this is an attempt to establish a new pocket diagnostic session -- this is an attempt to establish a new pocket diagnostic session
local s_id = svsessions.establish_pdg_session(src_addr, i_seq_num, firmware_v) local s_id = svsessions.establish_pdg_session(src_addr, i_seq_num, firmware_v)
@ -472,14 +473,14 @@ function supervisor.comms(_version, nic, fp_ok, facility)
println(util.c("PKT (", firmware_v, ") [@", src_addr, "] \xbb connected")) println(util.c("PKT (", firmware_v, ") [@", src_addr, "] \xbb connected"))
log.info(util.c("PDG_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) log.info(util.c("PDG_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id))
_send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) _send_establish(p_nic or c_nic, packet.scada_frame, ESTABLISH_ACK.ALLOW)
else else
log.debug(util.c("illegal establish packet for device ", dev_type, " on pocket channel")) log.debug(util.c("illegal establish packet for device ", dev_type, " on pocket channel"))
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) _send_establish(p_nic or c_nic, packet.scada_frame, ESTABLISH_ACK.DENY)
end end
else else
log.debug("PDG_ESTABLISH: establish packet length mismatch") log.debug("PDG_ESTABLISH: establish packet length mismatch")
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) _send_establish(p_nic or c_nic, packet.scada_frame, ESTABLISH_ACK.DENY)
end end
else else
-- any other packet should be session related, discard it -- any other packet should be session related, discard it