diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 87d403d..503d454 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -285,7 +285,18 @@ function facility.new(config) -- link an environment detector RTU session ---@param envd unit_session - function public.add_envd(envd) table.insert(self.envd, envd) end + ---@return boolean linked environment detector accepted + function public.add_envd(envd) + local fail_code, fail_str = svsessions.check_rtu_id(envd, self.envd, 99) + + if fail_code == 0 then + table.insert(self.envd, envd) + else + log.warning(util.c("FAC: rejected environment detector linking due to failure code ", fail_code, " (", fail_str, ")")) + end + + return fail_code == 0 + end -- purge devices associated with the given RTU session ID ---@param session integer RTU session ID @@ -575,6 +586,22 @@ function facility.new(config) } end + -- check which RTUs are connected + ---@nodiscard + function public.check_rtu_conns() + local conns = {} + + conns.induction = #self.induction > 0 + conns.sps = #self.sps > 0 + + conns.tanks = {} + for i = 1, #self.tanks do + conns.tanks[self.tanks[i].get_device_idx()] = true + end + + return conns + end + -- get RTU statuses ---@nodiscard function public.get_rtu_statuses() diff --git a/supervisor/panel/components/chk_entry.lua b/supervisor/panel/components/chk_entry.lua index 0b893a5..63b4f40 100644 --- a/supervisor/panel/components/chk_entry.lua +++ b/supervisor/panel/components/chk_entry.lua @@ -2,14 +2,12 @@ -- RTU ID Check Failure Entry -- -local databus = require("supervisor.databus") +local style = require("supervisor.panel.style") -local style = require("supervisor.panel.style") +local core = require("graphics.core") -local core = require("graphics.core") - -local Div = require("graphics.elements.div") -local TextBox = require("graphics.elements.textbox") +local Div = require("graphics.elements.div") +local TextBox = require("graphics.elements.textbox") local ALIGN = core.ALIGN @@ -17,9 +15,9 @@ local cpair = core.cpair -- create an ID check list entry ---@param parent graphics_element parent ----@param unit unit_session RTU session +---@param msg string message ---@param fail_code integer failure code -local function init(parent, unit, fail_code, cmp_id) +local function init(parent, msg, fail_code, cmp_id) local s_hi_box = style.theme.highlight_box local label_fg = style.fp.label_fg @@ -42,17 +40,13 @@ local function init(parent, unit, fail_code, cmp_id) TextBox{parent=entry,text="",width=11,fg_bg=cpair(colors.black,colors.yellow)} end - if fail_code ~= 4 and cmp_id then - local rtu_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=ALIGN.CENTER,width=8,fg_bg=s_hi_box,nav_active=cpair(colors.gray,colors.black)} + if fail_code == 4 then + TextBox{parent=entry,x=13,y=2,text=msg} + else + TextBox{parent=entry,x=13,y=2,text="@ C "..cmp_id,alignment=ALIGN.CENTER,width=8,fg_bg=s_hi_box,nav_active=cpair(colors.gray,colors.black)} + TextBox{parent=entry,x=21,y=2,text=msg} end - if fail_code ~= 4 and cmp_id then - local rtu_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=ALIGN.CENTER,width=8,fg_bg=s_hi_box,nav_active=cpair(colors.gray,colors.black)} - end - - TextBox{parent=entry,x=21,y=2,text="FW:",width=3} - local rtu_fw_v = TextBox{parent=entry,x=25,y=2,text=" ------- ",width=9,fg_bg=label_fg} - return root end diff --git a/supervisor/panel/pgi.lua b/supervisor/panel/pgi.lua index c9776b5..15cfd1c 100644 --- a/supervisor/panel/pgi.lua +++ b/supervisor/panel/pgi.lua @@ -15,7 +15,7 @@ local data = { pdg_entry = nil, ---@type function chk_entry = nil, ---@type function -- list entries - entries = { rtu = {}, pdg = {}, chk = {} } + entries = { rtu = {}, pdg = {}, chk = {}, missing = {} } } -- link list boxes @@ -111,14 +111,15 @@ end -- add a device ID check failure entry to the CHK list ---@param unit unit_session RTU session ---@param fail_code integer failure code ----@param cmp_id integer|nil computer ID if this isn't a 'missing' entry -function pgi.create_chk_entry(unit, fail_code, cmp_id) +---@param cmp_id integer computer ID +---@param msg string description to show the user +function pgi.create_chk_entry(unit, fail_code, cmp_id, msg) local gw_session = unit.get_session_id() if data.chk_list ~= nil and data.chk_entry ~= nil then if not data.entries.chk[gw_session] then data.entries.chk[gw_session] = {} end - local success, result = pcall(data.chk_entry, data.chk_list, unit, fail_code, cmd_id) + local success, result = pcall(data.chk_entry, data.chk_list, msg, fail_code, cmp_id) if success then data.entries.chk[gw_session][unit.get_unit_id()] = result @@ -149,4 +150,36 @@ function pgi.delete_chk_entry(unit) end end +-- add a device ID missing entry to the CHK list +---@param message string missing device message +function pgi.create_missing_entry(message) + if data.chk_list ~= nil and data.chk_entry ~= nil then + local success, result = pcall(data.chk_entry, data.chk_list, message, 4, -1) + + if success then + data.entries.missing[message] = result + log.debug(util.c("PGI: created missing CHK entry (", message, ")")) + else + log.error(util.c("PGI: failed to create missing CHK entry (", result, ")"), true) + end + end +end + +-- delete a device ID missing entry from the CHK list +---@param message string missing device message +function pgi.delete_missing_entry(message) + if data.entries.missing[message] ~= nil then + local success, result = pcall(data.entries.missing[message].delete) + data.entries.missing[message] = nil + + if success then + log.debug(util.c("PGI: deleted missing CHK entry \"", message, "\"")) + else + log.error(util.c("PGI: failed to delete missing CHK entry (", result, ")"), true) + end + else + log.warning(util.c("PGI: tried to delete unknown missing CHK entry \"", message, "\"")) + end +end + return pgi diff --git a/supervisor/session/rtu/unit_session.lua b/supervisor/session/rtu/unit_session.lua index 18ac1c9..4f516c8 100644 --- a/supervisor/session/rtu/unit_session.lua +++ b/supervisor/session/rtu/unit_session.lua @@ -150,6 +150,9 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t -- get the unit ID ---@nodiscard function public.get_unit_id() return unit_id end + -- get the RTU type + ---@nodiscard + function public.get_unit_type() return advert.type end -- get the device index ---@nodiscard function public.get_device_idx() return self.device_index or 0 end diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 904a29a..caed09f 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -1,5 +1,10 @@ +-- +-- Supervisor Sessions Handler +-- + local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") +local types = require("scada-common.types") local util = require("scada-common.util") local databus = require("supervisor.databus") @@ -12,12 +17,13 @@ local pocket = require("supervisor.session.pocket") local rtu = require("supervisor.session.rtu") local svqtypes = require("supervisor.session.svqtypes") --- Supervisor Sessions Handler +local RTU_TYPES = types.RTU_UNIT_TYPE -local SV_Q_DATA = svqtypes.SV_Q_DATA +local SV_Q_DATA = svqtypes.SV_Q_DATA local PLC_S_CMDS = plc.PLC_S_CMDS local PLC_S_DATA = plc.PLC_S_DATA + local CRD_S_DATA = coordinator.CRD_S_DATA local svsessions = {} @@ -192,18 +198,71 @@ local function _find_session(list, s_addr) return nil end +-- periodically remove disconnected RTU gateway's RTU ID warnings and update the missing device list local function _update_dev_dbg() + -- remove disconnected units from check failures lists + local f = function (unit) return unit.is_connected() end util.filter_table(self.dev_dbg.duplicate, f, pgi.delete_chk_entry) util.filter_table(self.dev_dbg.out_of_range, f, pgi.delete_chk_entry) - local conns = self.dev_dbg.connected - local units = self.facility.get_units() - for i = 1, #units do - local unit = units[i] ---@type reactor_unit - local rtus = unit.check_rtu_conns() + -- update missing list + local conns = self.dev_dbg.connected + local units = self.facility.get_units() + local rtu_conns = self.facility.check_rtu_conns() + + local function report(disconnected, msg) + if disconnected then pgi.create_missing_entry(msg) else pgi.delete_missing_entry(msg) end + end + + -- look for disconnected facility RTUs + + if rtu_conns.induction ~= conns.induction then + report(conns.induction, util.c("the facility's induction matrix")) + conns.induction = rtu_conns.induction + end + + if rtu_conns.sps ~= conns.sps then + report(conns.sps, util.c("the facility's SPS")) + conns.sps = rtu_conns.sps + end + + for i = 1, #conns.tanks do + if (rtu_conns.tanks[i] or false) ~= conns.tanks[i] then + report(conns.tanks[i], util.c("the facility's #", i, " dynamic tank")) + conns.tanks[i] = rtu_conns.tanks[i] or false + end + end + + -- look for disconnected unit RTUs + + for u = 1, #units do + local u_conns = conns.units[u] + + rtu_conns = units[u].check_rtu_conns() + + for i = 1, #u_conns.boilers do + if (rtu_conns.boilers[i] or false) ~= u_conns.boilers[i] then + report(u_conns.boilers[i], util.c("unit ", u, "'s #", i, " boiler")) + u_conns.boilers[i] = rtu_conns.boilers[i] or false + end + end + + for i = 1, #u_conns.turbines do + if (rtu_conns.turbines[i] or false) ~= u_conns.turbines[i] then + report(u_conns.turbines[i], util.c("unit ", u, "'s #", i, " turbine")) + u_conns.turbines[i] = rtu_conns.turbines[i] or false + end + end + + for i = 1, #u_conns.tanks do + if (rtu_conns.tanks[i] or false) ~= u_conns.tanks[i] then + report(u_conns.tanks[i], util.c("unit ", u, "'s dynamic tank")) + u_conns.tanks[i] = rtu_conns.tanks[i] or false + end + end end end @@ -211,6 +270,7 @@ end --#region PUBLIC FUNCTIONS +-- on attempted link of an RTU to a facility or unit object, verify its ID and report a problem if it can't be accepted ---@param unit unit_session RTU session ---@param list table table of RTU sessions ---@param max integer max of this type of RTU @@ -240,7 +300,7 @@ function svsessions.check_rtu_id(unit, list, max) -- add to the list for the user if fail_code > 0 and fail_code ~= 3 then - local cmp_id + local cmp_id = -1 for i = 1, #self.sessions.rtu do if self.sessions.rtu[i].instance.get_id() == unit.get_session_id() then @@ -249,7 +309,42 @@ function svsessions.check_rtu_id(unit, list, max) end end - pgi.create_chk_entry(unit, fail_code, cmp_id) + local r_id = unit.get_reactor() + local idx = unit.get_device_idx() + local type = unit.get_unit_type() + local msg = "? (error)" + + if r_id == 0 then + msg = "the facility's " + + if type == RTU_TYPES.IMATRIX then + msg = msg .. "induction matrix" + elseif type == RTU_TYPES.SPS then + msg = msg .. "SPS" + elseif type == RTU_TYPES.DYNAMIC_VALVE then + msg = util.c(msg, "#", idx, " dynamic tank") + elseif type == RTU_TYPES.ENV_DETECTOR then + msg = util.c(msg, "#", idx, " environment detector") + else + msg = msg .. " ? (error)" + end + else + msg = util.c("unit ", r_id, "'s ") + + if type == RTU_TYPES.BOILER_VALVE then + msg = util.c(msg, "#", idx, " boiler") + elseif type == RTU_TYPES.TURBINE_VALVE then + msg = util.c(msg, "#", idx, " turbine") + elseif type == RTU_TYPES.DYNAMIC_VALVE then + msg = msg .. "dynamic tank" + elseif type == RTU_TYPES.ENV_DETECTOR then + msg = util.c(msg, "#", idx, " environment detector") + else + msg = msg .. " ? (error)" + end + end + + pgi.create_chk_entry(unit, fail_code, cmp_id, msg) end return fail_code, fail_str @@ -266,10 +361,29 @@ function svsessions.init(nic, fp_ok, config, facility) self.config = config self.facility = facility - -- initialize connection tracking table - self.dev_dbg.connected = { imatrix = nil, sps = nil, tanks = {}, units = {} } + -- initialize connection tracking table by setting all expected devices to true + -- if connections are missing, missing entries will then be created on the next update + + self.dev_dbg.connected = { induction = true, sps = true, tanks = {}, units = {} } + + local cool_conf = facility.get_cooling_conf() + + for i = 1, #cool_conf.fac_tank_list do + self.dev_dbg.connected.tanks[i] = true + end + for i = 1, config.UnitCount do - self.dev_dbg.connected.units[i] = { boilers = {}, turbines = {}, tanks = {} } + local r_cool = cool_conf.r_cool[i] + local conns = { boilers = {}, turbines = {}, tanks = {} } + + for b = 1, r_cool.BoilerCount do conns.boilers[b] = true end + for t = 1, r_cool.TurbineCount do conns.boilers[t] = true end + + if r_cool.TankConnection and cool_conf.fac_tank_defs[i] == 1 then + conns.tanks[1] = true + end + + self.dev_dbg.connected.units[i] = conns end end diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 3578926..9ee6706 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -501,7 +501,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) -- link an environment detector RTU session ---@param envd unit_session - ---@return boolean linked environment detector accepted (max 1) + ---@return boolean linked environment detector accepted function public.add_envd(envd) local fail_code, fail_str = svsessions.check_rtu_id(envd, self.envd, 99)