From 0d090fe9e2f5cca4fd169d76abac704885395bbe Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 11 May 2022 12:31:19 -0400 Subject: [PATCH] #47 supervisor luadoc, bugfixes --- supervisor/session/plc.lua | 183 +++++++++++++++++------------- supervisor/session/rtu.lua | 2 +- supervisor/session/svsessions.lua | 32 ++++-- supervisor/startup.lua | 5 +- supervisor/supervisor.lua | 35 ++++-- supervisor/unit.lua | 30 +++-- 6 files changed, 176 insertions(+), 111 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index f6a12ca..42bb51c 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -37,6 +37,10 @@ local PERIODICS = { } -- PLC supervisor session +---@param id integer +---@param for_reactor integer +---@param in_queue mqueue +---@param out_queue mqueue plc.new_session = function (id, for_reactor, in_queue, out_queue) local log_header = "plc_session(" .. id .. "): " @@ -78,6 +82,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) rps_reset = true }, -- session database + ---@class reactor_db sDB = { last_status_update = 0, control_state = false, @@ -85,6 +90,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) degraded = false, rps_tripped = false, rps_trip_cause = "ok", + ---@class rps_status rps_status = { dmg_crit = false, ex_hcool = false, @@ -94,45 +100,52 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) no_cool = false, timed_out = false }, + ---@class mek_status mek_status = { - heating_rate = 0, + heating_rate = 0.0, status = false, - burn_rate = 0, - act_burn_rate = 0, - temp = 0, - damage = 0, - boil_eff = 0, - env_loss = 0, + burn_rate = 0.0, + act_burn_rate = 0.0, + temp = 0.0, + damage = 0.0, + boil_eff = 0.0, + env_loss = 0.0, - fuel = 0, - fuel_need = 0, - fuel_fill = 0, - waste = 0, - waste_need = 0, - waste_fill = 0, + fuel = 0.0, + fuel_need = 0.0, + fuel_fill = 0.0, + waste = 0.0, + waste_need = 0.0, + waste_fill = 0.0, cool_type = "?", - cool_amnt = 0, - cool_need = 0, - cool_fill = 0, + cool_amnt = 0.0, + cool_need = 0.0, + cool_fill = 0.0, hcool_type = "?", - hcool_amnt = 0, - hcool_need = 0, - hcool_fill = 0 + hcool_amnt = 0.0, + hcool_need = 0.0, + hcool_fill = 0.0 }, + ---@class mek_struct mek_struct = { - heat_cap = 0, - fuel_asm = 0, - fuel_sa = 0, - fuel_cap = 0, - waste_cap = 0, - cool_cap = 0, - hcool_cap = 0, - max_burn = 0 + heat_cap = 0.0, + fuel_asm = 0.0, + fuel_sa = 0.0, + fuel_cap = 0.0, + waste_cap = 0.0, + cool_cap = 0.0, + hcool_cap = 0.0, + max_burn = 0.0 } } } + ---@class plc_session + local public = {} + + -- copy in the RPS status + ---@param rps_status table local _copy_rps_status = function (rps_status) self.sDB.rps_status.dmg_crit = rps_status[1] self.sDB.rps_status.ex_hcool = rps_status[2] @@ -143,6 +156,8 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) self.sDB.rps_status.timed_out = rps_status[7] end + -- copy in the reactor status + ---@param mek_data table local _copy_status = function (mek_data) -- copy status information self.sDB.mek_status.status = mek_data[1] @@ -174,6 +189,8 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end end + -- copy in the reactor structure + ---@param mek_data table local _copy_struct = function (mek_data) self.sDB.mek_struct.heat_cap = mek_data[1] self.sDB.mek_struct.fuel_asm = mek_data[2] @@ -186,6 +203,8 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end -- send an RPLC packet + ---@param msg_type RPLC_TYPES + ---@param msg table local _send = function (msg_type, msg) local s_pkt = comms.scada_packet() local r_pkt = comms.rplc_packet() @@ -198,6 +217,8 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end -- send a SCADA management packet + ---@param msg_type SCADA_MGMT_TYPES + ---@param msg table local _send_mgmt = function (msg_type, msg) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() @@ -210,6 +231,8 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end -- get an ACK status + ---@param pkt rplc_frame + ---@return boolean|nil ack local _get_ack = function (pkt) if pkt.length == 1 then return pkt.data[1] @@ -220,6 +243,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end -- handle a packet + ---@param pkt rplc_frame local _handle_packet = function (pkt) -- check sequence number if self.r_seq_num == nil then @@ -378,13 +402,13 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) -- PUBLIC FUNCTIONS -- -- get the session ID - local get_id = function () return self.id end + public.get_id = function () return self.id end -- get the session database - local get_db = function () return self.sDB end + public.get_db = function () return self.sDB end -- get the reactor structure - local get_struct = function () + public.get_struct = function () if self.received_struct then return self.sDB.mek_struct else @@ -393,7 +417,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end -- get the reactor structure - local get_status = function () + public.get_status = function () if self.received_status_cache then return self.sDB.mek_status else @@ -402,12 +426,12 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end -- check if a timer matches this session's watchdog - local check_wd = function (timer) + public.check_wd = function (timer) return self.plc_conn_watchdog.is_timer(timer) end -- close the connection - local close = function () + public.close = function () self.plc_conn_watchdog.cancel() self.connected = false _send_mgmt(SCADA_MGMT_TYPES.CLOSE, {}) @@ -416,7 +440,8 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) end -- iterate the session - local iterate = function () + ---@return boolean connected + public.iterate = function () if self.connected then ------------------ -- handle queue -- @@ -428,45 +453,47 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) -- get a new message to process local message = self.in_q.pop() - if message.qtype == mqueue.TYPE.PACKET then - -- handle a packet - _handle_packet(message.message) - elseif message.qtype == mqueue.TYPE.COMMAND then - -- handle instruction - local cmd = message.message - if cmd == PLC_S_CMDS.ENABLE then - -- enable reactor - self.acks.enable = false - self.retry_times.enable_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.RPS_ENABLE, {}) - elseif cmd == PLC_S_CMDS.SCRAM then - -- SCRAM reactor - self.acks.scram = false - self.retry_times.scram_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.RPS_SCRAM, {}) - elseif cmd == PLC_S_CMDS.RPS_RESET then - -- reset RPS - self.acks.rps_reset = false - self.retry_times.rps_reset_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.RPS_RESET, {}) - end - elseif message.qtype == mqueue.TYPE.DATA then - -- instruction with body - local cmd = message.message - if cmd.key == PLC_S_DATA.BURN_RATE then - -- update burn rate - self.commanded_burn_rate = cmd.val - self.ramping_rate = false - self.acks.burn_rate = false - self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) - elseif cmd.key == PLC_S_DATA.RAMP_BURN_RATE then - -- ramp to burn rate - self.commanded_burn_rate = cmd.val - self.ramping_rate = true - self.acks.burn_rate = false - self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT - _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + if message ~= nil then + if message.qtype == mqueue.TYPE.PACKET then + -- handle a packet + _handle_packet(message.message) + elseif message.qtype == mqueue.TYPE.COMMAND then + -- handle instruction + local cmd = message.message + if cmd == PLC_S_CMDS.ENABLE then + -- enable reactor + self.acks.enable = false + self.retry_times.enable_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.RPS_ENABLE, {}) + elseif cmd == PLC_S_CMDS.SCRAM then + -- SCRAM reactor + self.acks.scram = false + self.retry_times.scram_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.RPS_SCRAM, {}) + elseif cmd == PLC_S_CMDS.RPS_RESET then + -- reset RPS + self.acks.rps_reset = false + self.retry_times.rps_reset_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.RPS_RESET, {}) + end + elseif message.qtype == mqueue.TYPE.DATA then + -- instruction with body + local cmd = message.message + if cmd.key == PLC_S_DATA.BURN_RATE then + -- update burn rate + self.commanded_burn_rate = cmd.val + self.ramping_rate = false + self.acks.burn_rate = false + self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + elseif cmd.key == PLC_S_DATA.RAMP_BURN_RATE then + -- ramp to burn rate + self.commanded_burn_rate = cmd.val + self.ramping_rate = true + self.acks.burn_rate = false + self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT + _send(RPLC_TYPES.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) + end end end @@ -567,15 +594,7 @@ plc.new_session = function (id, for_reactor, in_queue, out_queue) return self.connected end - return { - get_id = get_id, - get_db = get_db, - get_struct = get_struct, - get_status = get_status, - check_wd = check_wd, - close = close, - iterate = iterate - } + return public end return plc diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index a360be9..eed5cdc 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -100,7 +100,7 @@ rtu.new_session = function (id, in_queue, out_queue) elseif pkt.type == SCADA_MGMT_TYPES.RTU_ADVERT then -- RTU unit advertisement for i = 1, pkt.length do - local unit = pkt.data[i] + local unit = pkt.data[i] ---@type rtu_advertisement end else log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index e596985..37fc89e 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -31,16 +31,17 @@ local self = { -- PRIVATE FUNCTIONS -- -- iterate all the given sessions +---@param sessions table local function _iterate(sessions) for i = 1, #sessions do - local session = sessions[i] + local session = sessions[i] ---@type plc_session_struct if session.open then local ok = session.instance.iterate() if ok then -- send packets in out queue while session.out_queue.ready() do local msg = session.out_queue.pop() - if msg.qtype == mqueue.TYPE.PACKET then + if msg ~= nil and msg.qtype == mqueue.TYPE.PACKET then self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) end end @@ -52,6 +53,7 @@ local function _iterate(sessions) end -- cleanly close a session +---@param session plc_session_struct local function _shutdown(session) session.open = false session.instance.close() @@ -59,7 +61,7 @@ local function _shutdown(session) -- send packets in out queue (namely the close packet) while session.out_queue.ready() do local msg = session.out_queue.pop() - if msg.qtype == mqueue.TYPE.PACKET then + if msg ~= nil and msg.qtype == mqueue.TYPE.PACKET then self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable()) end end @@ -68,9 +70,10 @@ local function _shutdown(session) end -- close connections +---@param sessions table local function _close(sessions) for i = 1, #sessions do - local session = sessions[i] + local session = sessions[i] ---@type plc_session_struct if session.open then _shutdown(session) end @@ -78,9 +81,11 @@ local function _close(sessions) end -- check if a watchdog timer event matches that of one of the provided sessions +---@param sessions table +---@param timer_event number local function _check_watchdogs(sessions, timer_event) for i = 1, #sessions do - local session = sessions[i] + local session = sessions[i] ---@type plc_session_struct if session.open then local triggered = session.instance.check_wd(timer_event) if triggered then @@ -92,10 +97,11 @@ local function _check_watchdogs(sessions, timer_event) end -- delete any closed sessions +---@param sessions table local function _free_closed(sessions) local move_to = 1 for i = 1, #sessions do - local session = sessions[i] + local session = sessions[i] ---@type plc_session_struct if session ~= nil then if session.open then if sessions[move_to] == nil then @@ -113,11 +119,15 @@ end -- PUBLIC FUNCTIONS -- +-- link the modem +---@param modem table svsessions.link_modem = function (modem) self.modem = modem end -- find a session by the remote port +---@param remote_port integer +---@return plc_session_struct|nil svsessions.find_session = function (remote_port) -- check RTU sessions for i = 1, #self.rtu_sessions do @@ -144,6 +154,8 @@ svsessions.find_session = function (remote_port) end -- get a session by reactor ID +---@param reactor integer +---@return plc_session_struct session svsessions.get_reactor_session = function (reactor) local session = nil @@ -157,8 +169,13 @@ svsessions.get_reactor_session = function (reactor) end -- establish a new PLC session +---@param local_port integer +---@param remote_port integer +---@param for_reactor integer +---@return integer|false session_id svsessions.establish_plc_session = function (local_port, remote_port, for_reactor) - if svsessions.get_reactor_session(for_reactor) == nil then + if svsessions.get_reactor_session(for_reactor) == nil then + ---@class plc_session_struct local plc_s = { open = true, reactor = for_reactor, @@ -185,6 +202,7 @@ svsessions.establish_plc_session = function (local_port, remote_port, for_reacto end -- attempt to identify which session's watchdog timer fired +---@param timer_event number svsessions.check_all_watchdogs = function (timer_event) -- check RTU session watchdogs _check_watchdogs(self.rtu_sessions, timer_event) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index be9549e..5923887 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -6,15 +6,12 @@ local log = require("scada-common.log") local ppm = require("scada-common.ppm") local util = require("scada-common.util") -local coordinator = require("supervisor.session.coordinator") -local plc = require("supervisor.session.plc") -local rtu = require("supervisor.session.rtu") local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "alpha-v0.3.4" +local SUPERVISOR_VERSION = "alpha-v0.3.5" local print = util.print local println = util.println diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index f9df71e..a534882 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -20,6 +20,10 @@ local print_ts = util.print_ts local println_ts = util.println_ts -- supervisory controller communications +---@param num_reactors integer +---@param modem table +---@param dev_listen integer +---@param coord_listen integer supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) local self = { ln_seq_num = 0, @@ -30,6 +34,9 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) reactor_struct_cache = nil } + ---@class superv_comms + local public = {} + -- PRIVATE FUNCTIONS -- -- open all channels @@ -50,6 +57,8 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) svsessions.link_modem(self.modem) -- send PLC link request responses + ---@param dest integer + ---@param msg table local _send_plc_linking = function (dest, msg) local s_pkt = comms.scada_packet() local r_pkt = comms.rplc_packet() @@ -64,14 +73,22 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) -- PUBLIC FUNCTIONS -- -- reconnect a newly connected modem - local reconnect_modem = function (modem) + ---@param modem table +---@diagnostic disable-next-line: redefined-local + public.reconnect_modem = function (modem) self.modem = modem svsessions.link_modem(self.modem) _open_channels() end -- parse a packet - local parse_packet = function(side, sender, reply_to, message, distance) + ---@param side string + ---@param sender integer + ---@param reply_to integer + ---@param message any + ---@param distance integer + ---@return modbus_frame|rplc_frame|mgmt_frame|coord_frame|nil packet + public.parse_packet = function(side, sender, reply_to, message, distance) local pkt = nil local s_pkt = comms.scada_packet() @@ -111,7 +128,9 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) return pkt end - local handle_packet = function(packet) + -- handle a packet + ---@param packet modbus_frame|rplc_frame|mgmt_frame|coord_frame + public.handle_packet = function(packet) if packet ~= nil then local l_port = packet.scada_frame.local_port() local r_port = packet.scada_frame.remote_port() @@ -126,7 +145,7 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) -- MODBUS response elseif protocol == PROTOCOLS.RPLC then -- reactor PLC packet - if session then + if session ~= nil then if packet.type == RPLC_TYPES.LINK_REQ then -- new device on this port? that's a collision log.debug("PLC_LNK: request from existing connection received on " .. r_port .. ", responding with collision") @@ -162,7 +181,7 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) end elseif protocol == PROTOCOLS.SCADA_MGMT then -- SCADA management packet - if session then + if session ~= nil then -- pass the packet onto the session handler session.in_queue.push_packet(packet) end @@ -184,11 +203,7 @@ supervisor.comms = function (num_reactors, modem, dev_listen, coord_listen) end end - return { - reconnect_modem = reconnect_modem, - parse_packet = parse_packet, - handle_packet = handle_packet - } + return public end return supervisor diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 95aa5f1..a246ebc 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -1,8 +1,8 @@ local unit = {} +-- create a new reactor unit +---@param for_reactor integer unit.new = function (for_reactor) - local public = {} - local self = { r_id = for_reactor, plc_s = nil, @@ -11,6 +11,7 @@ unit.new = function (for_reactor) energy_storage = {}, redstone = {}, db = { + ---@class annunciator annunciator = { -- RPS -- reactor @@ -37,18 +38,36 @@ unit.new = function (for_reactor) } } + ---@class reactor_unit + local public = {} + + -- PRIVATE FUNCTIONS -- + + -- update the annunciator + local _update_annunciator = function () + self.db.annunciator.PLCOnline = (self.plc_s ~= nil) and (self.plc_s.open) + self.db.annunciator.ReactorTrip = false + end + + -- PUBLIC FUNCTIONS -- + + -- link the PLC + ---@param plc_session plc_session_struct public.link_plc_session = function (plc_session) self.plc_s = plc_session end + -- link a turbine RTU public.add_turbine = function (turbine) table.insert(self.turbines, turbine) end + -- link a boiler RTU public.add_boiler = function (boiler) table.insert(self.boilers, boiler) end + -- link a redstone RTU capability public.add_redstone = function (field, accessor) -- ensure field exists if self.redstone[field] == nil then @@ -59,11 +78,7 @@ unit.new = function (for_reactor) table.insert(self.redstone[field], accessor) end - local _update_annunciator = function () - self.db.annunciator.PLCOnline = (self.plc_s ~= nil) and (self.plc_s.open) - self.db.annunciator.ReactorTrip = false - end - + -- update (iterate) this session public.update = function () -- unlink PLC if session was closed if not self.plc_s.open then @@ -74,6 +89,7 @@ unit.new = function (for_reactor) _update_annunciator() end + -- get the annunciator status public.get_annunciator = function () return self.db.annunciator end return public