diff --git a/scada-common/network.lua b/scada-common/network.lua index 279a806..7080553 100644 --- a/scada-common/network.lua +++ b/scada-common/network.lua @@ -104,6 +104,8 @@ function network.nic(modem) modem = reconnected_modem self.connected = true + modem.closeAll() + -- open previously opened channels for _, channel in ipairs(self.channels) do modem.open(channel) diff --git a/scada-common/util.lua b/scada-common/util.lua index 031eaa0..c889037 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -24,7 +24,7 @@ local t_pack = table.pack local util = {} -- scada-common version -util.version = "1.5.4" +util.version = "1.5.5" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 diff --git a/supervisor/backplane.lua b/supervisor/backplane.lua new file mode 100644 index 0000000..7736290 --- /dev/null +++ b/supervisor/backplane.lua @@ -0,0 +1,183 @@ +-- +-- Supervisor System Core Peripheral Backplane +-- + +local log = require("scada-common.log") +local network = require("scada-common.network") +local ppm = require("scada-common.ppm") +local util = require("scada-common.util") + +local databus = require("supervisor.databus") + +---@class supervisor_backplane +local backplane = {} + +local _bp = { + config = nil, ---@type svr_config + lan_iface = false, ---@type string|false wired comms modem name + + wd_nic = nil, ---@type nic|nil wired nic + wl_nic = nil, ---@type nic|nil wireless nic + nic_map = {} +} + +backplane.nics = _bp.nic_map + +-- initialize the system peripheral backplane +---@param config svr_config +---@param println function +---@return boolean success +function backplane.init(config, println) + -- setup the wired modem, if configured + if type(config.WiredModem) == "string" then + _bp.lan_iface = config.WiredModem + + local modem = ppm.get_modem(_bp.lan_iface) + if not (modem and _bp.lan_iface) then + println("startup> wired comms modem not found") + log.fatal("no wired comms modem on startup") + return false + end + + local nic = network.nic(modem) + _bp.wd_nic = nic + _bp.nic_map[_bp.lan_iface] = nic + + nic.closeAll() + + if config.PLC_Listen > 0 then nic.open(config.PLC_Channel) end + if config.RTU_Listen > 0 then nic.open(config.RTU_Channel) end + if config.CRD_Listen > 0 then nic.open(config.CRD_Channel) end + + databus.tx_hw_wd_modem(true) + end + + -- setup the wireless modem, if configured + if config.WirelessModem then + local modem, iface = ppm.get_wireless_modem() + if not (modem and iface) then + println("startup> wireless comms modem not found") + log.fatal("no wireless comms modem on startup") + return false + end + + local nic = network.nic(modem) + _bp.wl_nic = nic + _bp.nic_map[iface] = nic + + nic.closeAll() + + if config.PLC_Listen % 2 == 0 then nic.open(config.PLC_Channel) end + if config.RTU_Listen % 2 == 0 then nic.open(config.RTU_Channel) end + if config.CRD_Listen % 2 == 0 then nic.open(config.CRD_Channel) end + if config.PocketEnabled then nic.open(config.PKT_Channel) end + + databus.tx_hw_wl_modem(true) + end + + if not ((type(config.WiredModem) == "string" or config.WirelessModem)) then + println("startup> no modems configured") + log.fatal("no modems configured") + return false + end + + return true +end + +-- handle a backplane peripheral attach +---@param iface string +---@param type string +---@param device table +---@param println function +function backplane.attach(iface, type, device, println) + 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)) + + local is_wd = _bp.wd_nic and (_bp.lan_iface == iface) + local is_wl = _bp.wl_nic and (not _bp.wl_nic.is_connected()) and m_is_wl + + if is_wd then + -- connect this as the wired NIC + _bp.wd_nic.connect(device) + + log.info("BKPLN: WIRED PHY_UP " .. iface) + println("wired comms modem reconnected") + + databus.tx_hw_wd_modem(true) + elseif is_wl then + -- connect this as the wireless NIC + _bp.wl_nic.connect(device) + _bp.nic_map[iface] = _bp.wl_nic + + log.info("BKPLN: WIRELESS PHY_UP " .. iface) + println("wireless comms modem reconnected") + + databus.tx_hw_wl_modem(true) + elseif _bp.wl_nic and m_is_wl then + -- the wireless NIC already has a modem + println("standby wireless modem connected") + log.info("BKPLN: standby wireless modem connected") + else + println("unassigned modem connected") + log.warning("BKPLN: unassigned modem connected") + end + end +end + +-- handle a backplane peripheral detach +---@param iface string +---@param type string +---@param device table +---@param println function +function backplane.detach(iface, type, device, println) + if type == "modem" then + ---@cast device Modem + + local m_is_wl = device.isWireless() + local was_wd = _bp.wd_nic and _bp.wd_nic.is_modem(device) + local was_wl = _bp.wl_nic and _bp.wl_nic.is_modem(device) + + log.info(util.c("BKPLN: ", util.trinary(m_is_wl, "WIRELESS", "WIRED"), " PHY_DETACH ", iface)) + + _bp.nic_map[iface] = nil + + if _bp.wd_nic and was_wd then + _bp.wd_nic.disconnect() + log.info("BKPLN: WIRED PHY_DOWN " .. iface) + + println("wired modem disconnected") + log.warning("BKPLN: wired comms modem disconnected") + + databus.tx_hw_wd_modem(false) + elseif _bp.wl_nic and was_wl then + _bp.wl_nic.disconnect() + log.info("BKPLN: WIRELESS PHY_DOWN " .. iface) + + println("wireless comms modem disconnected") + log.warning("BKPLN: wireless comms modem disconnected") + + local modem, m_iface = ppm.get_wireless_modem() + if modem then + log.info("BKPLN: found another wireless modem, using it for comms") + + _bp.wl_nic.connect(modem) + log.info("BKPLN: WIRELESS PHY_UP " .. m_iface) + else + databus.tx_hw_wl_modem(false) + end + elseif _bp.wl_nic and m_is_wl then + -- wireless, but not active + println("standby wireless modem disconnected") + log.info("BKPLN: standby wireless modem disconnected") + else + println("unassigned modem disconnected") + log.warning("BKPLN: unassigned modem disconnected") + end + end +end + +return backplane diff --git a/supervisor/pcie.lua b/supervisor/pcie.lua deleted file mode 100644 index e98fa57..0000000 --- a/supervisor/pcie.lua +++ /dev/null @@ -1,177 +0,0 @@ --- --- 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 = { - wired_modem = false, ---@type string|false wired comms modem name - wl_nic = nil, ---@type nic|nil wireless nic - wd_nic = nil ---@type nic|nil wired nic -} - --- network cards ----@class _svr_pcie_nic ----@field wl nic|nil the wireless comms NIC ----@field wd nic|nil the wired comms NIC -pcie_bus.nic = { - -- close all channels and then open the configured channels on the appropriate nic(s) - ---@param config svr_config - reset_open = function (config) - if bus.wl_nic then - bus.wl_nic.closeAll() - - if config.PLC_Listen % 2 == 0 then bus.wl_nic.open(config.PLC_Channel) end - if config.RTU_Listen % 2 == 0 then bus.wl_nic.open(config.RTU_Channel) end - if config.CRD_Listen % 2 == 0 then bus.wl_nic.open(config.CRD_Channel) end - if config.PocketEnabled then bus.wl_nic.open(config.PKT_Channel) end - end - - if bus.wd_nic then - bus.wd_nic.closeAll() - - if config.PLC_Listen > 0 then bus.wd_nic.open(config.PLC_Channel) end - if config.RTU_Listen > 0 then bus.wd_nic.open(config.RTU_Channel) end - if config.CRD_Listen > 0 then bus.wd_nic.open(config.CRD_Channel) end - end - end, - -- get the requested nic by interface - ---@param iface string - ---@return nic|nil - get = function(iface) - local dev = ppm.get_device(iface) - - if dev then - if bus.wl_nic and bus.wl_nic.is_modem(dev) then return bus.wl_nic end - if bus.wd_nic and bus.wd_nic.is_modem(dev) then return bus.wd_nic end - end - - return nil - end, - -- cards by interface - ---@type { string: nic } - cards = {} -} - --- initialize peripherals ----@param config svr_config ----@param println function ----@return boolean success -function pcie_bus.init(config, println) - -- setup networking peripheral(s) - if type(config.WiredModem) == "string" then - bus.wired_modem = config.WiredModem - - local wired_modem = ppm.get_modem(bus.wired_modem) - - if not (wired_modem and bus.wired_modem) then - println("startup> wired comms modem not found") - log.fatal("no wired comms modem on startup") - return false - end - - bus.wd_nic = network.nic(wired_modem) - pcie_bus.nic.cards[bus.wired_modem] = bus.wd_nic - end - - if config.WirelessModem then - local wireless_modem, wireless_iface = ppm.get_wireless_modem() - - if not (wireless_modem and wireless_iface) then - println("startup> wireless comms modem not found") - log.fatal("no wireless comms modem on startup") - return false - end - - bus.wl_nic = network.nic(wireless_modem) - pcie_bus.nic.cards[wireless_iface] = bus.wl_nic - end - - pcie_bus.nic.wl = bus.wl_nic - pcie_bus.nic.wd = bus.wd_nic - - databus.tx_hw_wl_modem(true) - databus.tx_hw_wd_modem(config.WirelessModem) - - return true -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 bus.wl_nic and not bus.wl_nic.is_connected() then - -- reconnected wireless comms modem - bus.wl_nic.connect(device) - pcie_bus.nic.cards[iface] = bus.wl_nic - - println("wireless comms modem reconnected") - log.info("wireless comms modem reconnected") - - databus.tx_hw_wl_modem(true) - else - log.info("unused wireless modem reconnected") - end - elseif bus.wd_nic and (iface == bus.wired_modem) then - -- reconnected wired comms modem - bus.wd_nic.connect(device) - pcie_bus.nic.cards[iface] = bus.wd_nic - - println("wired comms modem reconnected") - log.info("wired comms modem reconnected") - - databus.tx_hw_wl_modem(true) - else - log.info("wired modem reconnected") - end - end -end - --- handle the removal of a device ----@param iface string ----@param type string ----@param device table ----@param println function -function pcie_bus.remove(iface, type, device, println) - if type == "modem" then - pcie_bus.nic.cards[iface] = nil - - ---@cast device Modem - if bus.wl_nic and bus.wl_nic.is_modem(device) then - bus.wl_nic.disconnect() - - println("wireless comms modem disconnected") - log.warning("wireless comms modem disconnected") - - local other_modem = ppm.get_wireless_modem() - if other_modem then - log.info("found another wireless modem, using it for comms") - bus.wl_nic.connect(other_modem) - else - databus.tx_hw_wl_modem(false) - end - elseif bus.wd_nic and bus.wd_nic.is_modem(device) then - bus.wd_nic.disconnect() - - println("wired modem disconnected") - log.warning("wired modem disconnected") - - databus.tx_hw_wd_modem(false) - else - log.warning("non-comms modem disconnected") - end - end -end - -return pcie_bus diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 3246051..280695d 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -3,7 +3,6 @@ -- require("/initenv").init_env() -local pcie = require("supervisor.pcie") local crash = require("scada-common.crash") local comms = require("scada-common.comms") @@ -16,6 +15,7 @@ local util = require("scada-common.util") local core = require("graphics.core") +local backplane = require("supervisor.backplane") local configure = require("supervisor.configure") local databus = require("supervisor.databus") local facility = require("supervisor.facility") @@ -24,7 +24,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.7.1" +local SUPERVISOR_VERSION = "v1.8.0" local println = util.println local println_ts = util.println_ts @@ -126,8 +126,8 @@ local function main() network.init_mac(config.AuthKey) end - -- hardware bus initialization - if not pcie.init(config, println) then return end + -- hardware backplane initialization + if not backplane.init(config, println) then return end -- start UI local fp_ok, message = renderer.try_start_ui(config) @@ -167,12 +167,12 @@ local function main() if event == "peripheral_detach" then local type, device = ppm.handle_unmount(param1) if type ~= nil and device ~= nil then - pcie.remove(param1, type, device, println_ts) + backplane.detach(param1, type, device, println_ts) end elseif event == "peripheral" then local type, device = ppm.mount(param1) if type ~= nil and device ~= nil then - pcie.connect(param1, type, device, println_ts) + backplane.attach(param1, type, device, println_ts) end elseif event == "timer" and loop_clock.is_clock(param1) then -- main loop tick diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 6662f62..5388d0a 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -1,10 +1,11 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local util = require("scada-common.util") -local pcie = require("supervisor.pcie") local themes = require("graphics.themes") +local backplane = require("supervisor.backplane") + local svsessions = require("supervisor.session.svsessions") local supervisor = {} @@ -198,8 +199,11 @@ function supervisor.comms(_version, fp_ok, facility) ---@param distance integer ---@return modbus_frame|rplc_frame|mgmt_frame|crdn_frame|nil packet function public.parse_packet(side, sender, reply_to, message, distance) - local pkt, nic = nil, pcie.nic.cards[side] - local s_pkt = nic.receive(side, sender, reply_to, message, distance) + local pkt, s_pkt, nic = nil, nil, backplane.nics[side] + + if nic then + s_pkt = nic.receive(side, sender, reply_to, message, distance) + end if s_pkt then -- get as MODBUS TCP packet @@ -229,7 +233,7 @@ function supervisor.comms(_version, fp_ok, facility) -- handle a packet ---@param packet modbus_frame|rplc_frame|mgmt_frame|crdn_frame function public.handle_packet(packet) - local nic = pcie.nic.get(packet.scada_frame.interface()) + local nic = backplane.nics[packet.scada_frame.interface()] local l_chan = packet.scada_frame.local_channel() local r_chan = packet.scada_frame.remote_channel() local src_addr = packet.scada_frame.src_addr()