#634 coordinator backplane attach/detach
This commit is contained in:
@@ -80,6 +80,8 @@ function backplane.init_displays(config)
|
||||
|
||||
log.info("BKPLN: DISPLAY LINK_" .. util.trinary(disp, "UP", "DOWN") .. " MAIN/" .. iface)
|
||||
|
||||
iocontrol.fp_monitor_state("main", disp ~= nil)
|
||||
|
||||
if not disp then
|
||||
return false, "Main monitor is not connected."
|
||||
end
|
||||
@@ -101,6 +103,8 @@ function backplane.init_displays(config)
|
||||
|
||||
log.info("BKPLN: DISPLAY LINK_" .. util.trinary(disp, "UP", "DOWN") .. " FLOW/" .. iface)
|
||||
|
||||
iocontrol.fp_monitor_state("flow", disp ~= nil)
|
||||
|
||||
if not disp then
|
||||
return false, "Flow monitor is not connected."
|
||||
end
|
||||
@@ -123,6 +127,8 @@ function backplane.init_displays(config)
|
||||
|
||||
log.info("BKPLN: DISPLAY LINK_" .. util.trinary(disp, "UP", "DOWN") .. " UNIT_" .. i .. "/" .. iface)
|
||||
|
||||
iocontrol.fp_monitor_state(i, disp ~= nil)
|
||||
|
||||
if not disp then
|
||||
return false, "Unit " .. i .. " monitor is not connected."
|
||||
end
|
||||
@@ -181,6 +187,9 @@ function backplane.init(config, __shared_memory)
|
||||
|
||||
_bp.wl_nic = wl_nic
|
||||
|
||||
wl_nic.closeAll()
|
||||
wl_nic.open(config.CRD_Channel)
|
||||
|
||||
iocontrol.fp_has_wl_modem(modem ~= nil)
|
||||
end
|
||||
|
||||
@@ -220,9 +229,177 @@ function backplane.init(config, __shared_memory)
|
||||
end
|
||||
|
||||
-- get the active NIC
|
||||
---@return nic
|
||||
function backplane.active_nic() return _bp.act_nic end
|
||||
|
||||
-- get the wireless NIC
|
||||
function backplane.wireless_nic() return _bp.wl_nic end
|
||||
|
||||
-- get the configured displays
|
||||
function backplane.displays() return _bp.displays end
|
||||
|
||||
-- handle a backplane peripheral attach
|
||||
---@param type string
|
||||
---@param device table
|
||||
---@param iface string
|
||||
function backplane.attach(type, device, iface)
|
||||
local MQ__RENDER_DATA = _bp.smem.q_types.MQ__RENDER_DATA
|
||||
|
||||
local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic
|
||||
|
||||
local comms = _bp.smem.crd_sys.coord_comms
|
||||
|
||||
if type == "modem" then
|
||||
---@cast device Modem
|
||||
|
||||
local m_is_wl = device.isWireless()
|
||||
|
||||
log.info(util.c("BKPLN: ", util.trinary(m_is_wl, "WIRELESS", "WIRED"), " PHY_ATTACH ", iface))
|
||||
|
||||
if wd_nic and (_bp.lan_iface == iface) then
|
||||
-- connect this as the wired NIC
|
||||
wd_nic.connect(device)
|
||||
|
||||
log.info("BKPLN: WIRED PHY_UP " .. iface)
|
||||
log_sys("wired comms modem reconnected")
|
||||
|
||||
iocontrol.fp_has_wd_modem(true)
|
||||
|
||||
if (_bp.act_nic ~= wd_nic) and not _bp.wlan_pref then
|
||||
-- switch back to preferred wired
|
||||
_bp.act_nic = wd_nic
|
||||
|
||||
comms.switch_nic(_bp.act_nic)
|
||||
log.info("BKPLN: switched comms to wired modem (preferred)")
|
||||
end
|
||||
elseif wl_nic and (not wl_nic.is_connected()) and m_is_wl then
|
||||
-- connect this as the wireless NIC
|
||||
wl_nic.connect(device)
|
||||
|
||||
log.info("BKPLN: WIRELESS PHY_UP " .. iface)
|
||||
log_sys("wireless comms modem reconnected")
|
||||
|
||||
iocontrol.fp_has_wl_modem(true)
|
||||
|
||||
if (_bp.act_nic ~= wl_nic) and _bp.wlan_pref then
|
||||
-- switch back to preferred wireless
|
||||
_bp.act_nic = wl_nic
|
||||
|
||||
comms.switch_nic(_bp.act_nic)
|
||||
log.info("BKPLN: switched comms to wireless modem (preferred)")
|
||||
end
|
||||
elseif wl_nic and m_is_wl then
|
||||
-- the wireless NIC already has a modem
|
||||
log_sys("standby wireless modem connected")
|
||||
log.info("BKPLN: standby wireless modem connected")
|
||||
else
|
||||
log_sys("unassigned modem connected")
|
||||
log.warning("BKPLN: unassigned modem connected")
|
||||
end
|
||||
elseif type == "monitor" then
|
||||
---@cast device Monitor
|
||||
_bp.smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_CONNECT, { name = iface, device = device })
|
||||
elseif type == "speaker" then
|
||||
---@cast device Speaker
|
||||
log_sys("alarm sounder speaker reconnected")
|
||||
log.info("BKPLN: SPEAKER LINK_UP " .. iface)
|
||||
|
||||
sounder.reconnect(device)
|
||||
|
||||
iocontrol.fp_has_speaker(true)
|
||||
end
|
||||
end
|
||||
|
||||
-- handle a backplane peripheral detach
|
||||
---@param type string
|
||||
---@param device table
|
||||
---@param iface string
|
||||
function backplane.detach(type, device, iface)
|
||||
local MQ__RENDER_CMD = _bp.smem.q_types.MQ__RENDER_CMD
|
||||
local MQ__RENDER_DATA = _bp.smem.q_types.MQ__RENDER_DATA
|
||||
|
||||
local wl_nic, wd_nic = _bp.wl_nic, _bp.wd_nic
|
||||
|
||||
local comms = _bp.smem.crd_sys.coord_comms
|
||||
|
||||
if type == "modem" then
|
||||
---@cast device Modem
|
||||
|
||||
local m_is_wl = device.isWireless()
|
||||
|
||||
log.info(util.c("BKPLN: ", util.trinary(m_is_wl, "WIRELESS", "WIRED"), " PHY_DETACH ", iface))
|
||||
|
||||
if wd_nic and wd_nic.is_modem(device) then
|
||||
wd_nic.disconnect()
|
||||
log.info("BKPLN: WIRED PHY_DOWN " .. iface)
|
||||
|
||||
iocontrol.fp_has_wd_modem(false)
|
||||
elseif wl_nic and wl_nic.is_modem(device) then
|
||||
wl_nic.disconnect()
|
||||
log.info("BKPLN: WIRELESS PHY_DOWN " .. iface)
|
||||
|
||||
iocontrol.fp_has_wl_modem(false)
|
||||
end
|
||||
|
||||
-- we only care if this is our active comms modem
|
||||
if _bp.act_nic.is_modem(device) then
|
||||
log_sys("active comms modem disconnected")
|
||||
log.warning("BKPLN: active comms modem disconnected")
|
||||
|
||||
-- failover and try to find a new comms modem
|
||||
if _bp.act_nic == wl_nic then
|
||||
-- wireless active disconnected
|
||||
-- try to find another wireless modem, otherwise switch to wired
|
||||
local modem, m_iface = ppm.get_wireless_modem()
|
||||
if wl_nic and modem then
|
||||
log_sys("found another wireless modem, using it for comms")
|
||||
log.info("BKPLN: found another wireless modem, using it for comms")
|
||||
|
||||
wl_nic.connect(modem)
|
||||
|
||||
log.info("BKPLN: WIRELESS PHY_UP " .. m_iface)
|
||||
|
||||
iocontrol.fp_has_wl_modem(true)
|
||||
elseif wd_nic and wd_nic.is_connected() then
|
||||
_bp.act_nic = wd_nic
|
||||
|
||||
comms.switch_nic(_bp.act_nic)
|
||||
log.info("BKPLN: switched comms to wired modem")
|
||||
else
|
||||
-- close out main UI
|
||||
_bp.smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI)
|
||||
|
||||
-- alert user to status
|
||||
log_sys("awaiting comms modem reconnect...")
|
||||
end
|
||||
elseif wl_nic and wl_nic.is_connected() then
|
||||
-- wired active disconnected, wireless available
|
||||
_bp.act_nic = wl_nic
|
||||
|
||||
comms.switch_nic(_bp.act_nic)
|
||||
log.info("BKPLN: switched comms to wireless modem")
|
||||
else
|
||||
-- wired active disconnected, wireless unavailable
|
||||
end
|
||||
elseif _bp.wl_nic and m_is_wl then
|
||||
-- wireless, but not active
|
||||
log_sys("standby wireless modem disconnected")
|
||||
log.info("BKPLN: standby wireless modem disconnected")
|
||||
else
|
||||
log_sys("unassigned modem disconnected")
|
||||
log.warning("BKPLN: unassigned modem disconnected")
|
||||
end
|
||||
elseif type == "monitor" then
|
||||
---@cast device Monitor
|
||||
_bp.smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_DISCONNECT, device)
|
||||
elseif type == "speaker" then
|
||||
---@cast device Speaker
|
||||
log_sys("lost alarm sounder speaker")
|
||||
|
||||
log.info("BKPLN: SPEAKER LINK_DOWN " .. iface)
|
||||
|
||||
iocontrol.fp_has_speaker(false)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return backplane
|
||||
|
||||
@@ -163,9 +163,10 @@ end
|
||||
-- coordinator communications
|
||||
---@nodiscard
|
||||
---@param version string coordinator version
|
||||
---@param nic nic network interface device
|
||||
---@param nic nic active network interface device
|
||||
---@param wl_nic nic|nil pocket wireless network interface device
|
||||
---@param sv_watchdog watchdog
|
||||
function coordinator.comms(version, nic, sv_watchdog)
|
||||
function coordinator.comms(version, nic, wl_nic, sv_watchdog)
|
||||
local self = {
|
||||
sv_linked = false,
|
||||
sv_addr = comms.BROADCAST,
|
||||
@@ -180,14 +181,18 @@ function coordinator.comms(version, nic, sv_watchdog)
|
||||
est_task_done = nil
|
||||
}
|
||||
|
||||
comms.set_trusted_range(config.TrustedRange)
|
||||
if config.WirelessModem then
|
||||
comms.set_trusted_range(config.TrustedRange)
|
||||
end
|
||||
|
||||
-- configure network channels
|
||||
nic.closeAll()
|
||||
nic.open(config.CRD_Channel)
|
||||
|
||||
-- pass config to apisessions
|
||||
apisessions.init(nic, config)
|
||||
if config.API_Enabled and wl_nic then
|
||||
apisessions.init(wl_nic, config)
|
||||
end
|
||||
|
||||
-- PRIVATE FUNCTIONS --
|
||||
|
||||
@@ -224,7 +229,8 @@ function coordinator.comms(version, nic, sv_watchdog)
|
||||
m_pkt.make(MGMT_TYPE.ESTABLISH, { ack, data })
|
||||
s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
nic.transmit(config.PKT_Channel, config.CRD_Channel, s_pkt)
|
||||
---@diagnostic disable-next-line: need-check-nil
|
||||
wl_nic.transmit(config.PKT_Channel, config.CRD_Channel, s_pkt)
|
||||
self.last_api_est_acks[packet.src_addr()] = ack
|
||||
end
|
||||
|
||||
@@ -245,6 +251,18 @@ function coordinator.comms(version, nic, sv_watchdog)
|
||||
---@class coord_comms
|
||||
local public = {}
|
||||
|
||||
-- switch the current active NIC
|
||||
---@param _nic nic
|
||||
function public.switch_nic(_nic)
|
||||
nic.closeAll()
|
||||
|
||||
-- configure receive channels
|
||||
_nic.closeAll()
|
||||
_nic.open(config.CRD_Channel)
|
||||
|
||||
nic = _nic
|
||||
end
|
||||
|
||||
-- try to connect to the supervisor if not already linked
|
||||
---@param abort boolean? true to print out cancel info if not linked (use on program terminate)
|
||||
---@return boolean ok, boolean start_ui
|
||||
@@ -399,7 +417,9 @@ function coordinator.comms(version, nic, sv_watchdog)
|
||||
if l_chan ~= config.CRD_Channel then
|
||||
log.debug("received packet on unconfigured channel " .. l_chan, true)
|
||||
elseif r_chan == config.PKT_Channel then
|
||||
if not self.sv_linked then
|
||||
if not config.API_Enabled then
|
||||
-- log.debug("discarding pocket API packet due to the API being disabled")
|
||||
elseif not self.sv_linked then
|
||||
log.debug("discarding pocket API packet before linked to supervisor")
|
||||
elseif protocol == PROTOCOL.SCADA_CRDN then
|
||||
---@cast packet crdn_frame
|
||||
|
||||
@@ -82,19 +82,11 @@ function renderer.configure(config)
|
||||
engine.disable_flow_view = config.DisableFlowView
|
||||
end
|
||||
|
||||
-- link to the monitor peripherals
|
||||
-- init all displays in use by the renderer
|
||||
---@param monitors crd_displays
|
||||
function renderer.set_displays(monitors)
|
||||
function renderer.init_displays(monitors)
|
||||
engine.monitors = monitors
|
||||
|
||||
-- report to front panel as connected
|
||||
iocontrol.fp_monitor_state("main", engine.monitors.main ~= nil)
|
||||
iocontrol.fp_monitor_state("flow", engine.monitors.flow ~= nil)
|
||||
for i = 1, #engine.monitors.unit_displays do iocontrol.fp_monitor_state(i, true) end
|
||||
end
|
||||
|
||||
-- init all displays in use by the renderer
|
||||
function renderer.init_displays()
|
||||
-- init main and flow monitors
|
||||
_init_display(engine.monitors.main)
|
||||
if not engine.disable_flow_view then _init_display(engine.monitors.flow) end
|
||||
|
||||
@@ -69,7 +69,7 @@ end
|
||||
-- PUBLIC FUNCTIONS --
|
||||
|
||||
-- initialize apisessions
|
||||
---@param nic nic network interface
|
||||
---@param nic nic API network interface
|
||||
---@param config crd_config coordinator config
|
||||
function apisessions.init(nic, config)
|
||||
self.nic = nic
|
||||
|
||||
@@ -140,8 +140,7 @@ local function main()
|
||||
|
||||
-- init renderer
|
||||
renderer.configure(config)
|
||||
renderer.set_displays(backplane.displays())
|
||||
renderer.init_displays()
|
||||
renderer.init_displays(backplane.displays())
|
||||
renderer.init_dmesg()
|
||||
|
||||
-- lets get started!
|
||||
@@ -186,6 +185,19 @@ local function main()
|
||||
-- message queues
|
||||
q = {
|
||||
mq_render = mqueue.new()
|
||||
},
|
||||
|
||||
-- message queue message types
|
||||
q_types = {
|
||||
MQ__RENDER_CMD = {
|
||||
START_MAIN_UI = 1,
|
||||
CLOSE_MAIN_UI = 2
|
||||
},
|
||||
MQ__RENDER_DATA = {
|
||||
MON_CONNECT = 1,
|
||||
MON_DISCONNECT = 2,
|
||||
MON_RESIZE = 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +229,7 @@ local function main()
|
||||
log.debug("startup> conn watchdog created")
|
||||
|
||||
-- setup comms
|
||||
smem_sys.coord_comms = coordinator.comms(COORDINATOR_VERSION, backplane.active_nic(), smem_sys.conn_watchdog)
|
||||
smem_sys.coord_comms = coordinator.comms(COORDINATOR_VERSION, backplane.active_nic(), backplane.wireless_nic(), smem_sys.conn_watchdog)
|
||||
log.debug("startup> comms init")
|
||||
log_comms("comms initialized")
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ local ppm = require("scada-common.ppm")
|
||||
local tcd = require("scada-common.tcd")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local backplane = require("coordinator.backplane")
|
||||
local coordinator = require("coordinator.coordinator")
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
local process = require("coordinator.process")
|
||||
@@ -23,17 +24,6 @@ local threads = {}
|
||||
local MAIN_CLOCK = 0.5 -- 2Hz, 10 ticks
|
||||
local RENDER_SLEEP = 100 -- 100ms, 2 ticks
|
||||
|
||||
local MQ__RENDER_CMD = {
|
||||
START_MAIN_UI = 1,
|
||||
CLOSE_MAIN_UI = 2
|
||||
}
|
||||
|
||||
local MQ__RENDER_DATA = {
|
||||
MON_CONNECT = 1,
|
||||
MON_DISCONNECT = 2,
|
||||
MON_RESIZE = 3
|
||||
}
|
||||
|
||||
-- main thread
|
||||
---@nodiscard
|
||||
---@param smem crd_shared_memory
|
||||
@@ -54,10 +44,12 @@ function threads.thread__main(smem)
|
||||
log_sys("system started successfully")
|
||||
|
||||
-- load in from shared memory
|
||||
local crd_state = smem.crd_state
|
||||
local nic = smem.crd_sys.nic
|
||||
local coord_comms = smem.crd_sys.coord_comms
|
||||
local conn_watchdog = smem.crd_sys.conn_watchdog
|
||||
local crd_state = smem.crd_state
|
||||
local coord_comms = smem.crd_sys.coord_comms
|
||||
local conn_watchdog = smem.crd_sys.conn_watchdog
|
||||
|
||||
local MQ__RENDER_CMD = smem.q_types.MQ__RENDER_CMD
|
||||
local MQ__RENDER_DATA = smem.q_types.MQ__RENDER_DATA
|
||||
|
||||
-- event loop
|
||||
while true do
|
||||
@@ -66,66 +58,13 @@ function threads.thread__main(smem)
|
||||
-- handle event
|
||||
if event == "peripheral_detach" then
|
||||
local type, device = ppm.handle_unmount(param1)
|
||||
|
||||
if type ~= nil and device ~= nil then
|
||||
if type == "modem" then
|
||||
---@cast device Modem
|
||||
-- we only really care if this is our wireless modem
|
||||
-- if it is another modem, handle other peripheral losses separately
|
||||
if nic.is_modem(device) then
|
||||
nic.disconnect()
|
||||
log_sys("comms modem disconnected")
|
||||
|
||||
local other_modem = ppm.get_wireless_modem()
|
||||
if other_modem then
|
||||
log_sys("found another wireless modem, using it for comms")
|
||||
nic.connect(other_modem)
|
||||
else
|
||||
-- close out main UI
|
||||
smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI)
|
||||
|
||||
-- alert user to status
|
||||
log_sys("awaiting comms modem reconnect...")
|
||||
|
||||
iocontrol.fp_has_modem(false)
|
||||
end
|
||||
else
|
||||
log_sys("non-comms modem disconnected")
|
||||
end
|
||||
elseif type == "monitor" then
|
||||
---@cast device Monitor
|
||||
smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_DISCONNECT, device)
|
||||
elseif type == "speaker" then
|
||||
---@cast device Speaker
|
||||
log_sys("lost alarm sounder speaker")
|
||||
iocontrol.fp_has_speaker(false)
|
||||
end
|
||||
backplane.detach(type, device, param1)
|
||||
end
|
||||
elseif event == "peripheral" then
|
||||
local type, device = ppm.mount(param1)
|
||||
|
||||
if type ~= nil and device ~= nil then
|
||||
if type == "modem" then
|
||||
---@cast device Modem
|
||||
if device.isWireless() and not nic.is_connected() then
|
||||
-- reconnected modem
|
||||
log_sys("comms modem reconnected")
|
||||
nic.connect(device)
|
||||
iocontrol.fp_has_modem(true)
|
||||
elseif device.isWireless() then
|
||||
log.info("unused wireless modem reconnected")
|
||||
else
|
||||
log_sys("wired modem reconnected")
|
||||
end
|
||||
elseif type == "monitor" then
|
||||
---@cast device Monitor
|
||||
smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_CONNECT, { name = param1, device = device })
|
||||
elseif type == "speaker" then
|
||||
---@cast device Speaker
|
||||
log_sys("alarm sounder speaker reconnected")
|
||||
sounder.reconnect(device)
|
||||
iocontrol.fp_has_speaker(true)
|
||||
end
|
||||
backplane.attach(type, device, param1)
|
||||
end
|
||||
elseif event == "monitor_resize" then
|
||||
smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_RESIZE, param1)
|
||||
@@ -137,18 +76,16 @@ function threads.thread__main(smem)
|
||||
iocontrol.heartbeat()
|
||||
|
||||
-- maintain connection
|
||||
if nic.is_connected() then
|
||||
local ok, start_ui = coord_comms.try_connect()
|
||||
if not ok then
|
||||
crd_state.link_fail = true
|
||||
crd_state.shutdown = true
|
||||
log_sys("supervisor connection failed, shutting down...")
|
||||
log.fatal("failed to connect to supervisor")
|
||||
break
|
||||
elseif start_ui then
|
||||
log_sys("supervisor connected, dispatching main UI start")
|
||||
smem.q.mq_render.push_command(MQ__RENDER_CMD.START_MAIN_UI)
|
||||
end
|
||||
local ok, start_ui = coord_comms.try_connect()
|
||||
if not ok then
|
||||
crd_state.link_fail = true
|
||||
crd_state.shutdown = true
|
||||
log_sys("supervisor connection failed, shutting down...")
|
||||
log.fatal("failed to connect to supervisor")
|
||||
break
|
||||
elseif start_ui then
|
||||
log_sys("supervisor connected, dispatching main UI start")
|
||||
smem.q.mq_render.push_command(MQ__RENDER_CMD.START_MAIN_UI)
|
||||
end
|
||||
|
||||
-- iterate sessions and free any closed ones
|
||||
@@ -268,8 +205,11 @@ function threads.thread__render(smem)
|
||||
log.debug("render thread start")
|
||||
|
||||
-- load in from shared memory
|
||||
local crd_state = smem.crd_state
|
||||
local render_queue = smem.q.mq_render
|
||||
local crd_state = smem.crd_state
|
||||
local render_queue = smem.q.mq_render
|
||||
|
||||
local MQ__RENDER_CMD = smem.q_types.MQ__RENDER_CMD
|
||||
local MQ__RENDER_DATA = smem.q_types.MQ__RENDER_DATA
|
||||
|
||||
local last_update = util.time()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user