#74 work on coordinator comms

This commit is contained in:
Mikayla Fischler 2022-06-15 15:35:34 -04:00
parent 2e4a533148
commit b628472d81
2 changed files with 179 additions and 5 deletions

View File

@ -6,13 +6,19 @@ local util = require("scada-common.util")
local dialog = require("coordinator.util.dialog")
local coordinator = {}
local print = util.print
local println = util.println
local print_ts = util.print_ts
local println_ts = util.println_ts
local coordinator = {}
local PROTOCOLS = comms.PROTOCOLS
local SCADA_MGMT_TYPES = comms.SCADA_MGMT_TYPES
local COORD_TYPES = comms.COORD_TYPES
-- request the user to select a monitor
---@param names table available monitors
local function ask_monitor(names)
println("available monitors:")
for i = 1, #names do
@ -30,6 +36,8 @@ local function ask_monitor(names)
return iface
end
-- configure monitor layout
---@param num_units integer number of units expected
function coordinator.configure_monitors(num_units)
---@class monitors_struct
local monitors = {
@ -135,10 +143,162 @@ function coordinator.configure_monitors(num_units)
end
-- coordinator communications
function coordinator.coord_comms()
---@param conn_watchdog watchdog
function coordinator.coord_comms(version, num_reactors, modem, sv_port, sv_listen, api_listen, conn_watchdog)
local self = {
reactor_struct_cache = nil
seq_num = 0,
r_seq_num = nil,
modem = modem,
connected = false
}
---@class coord_comms
local public = {}
-- PRIVATE FUNCTIONS --
-- open all channels
local function _open_channels()
if not self.modem.isOpen(sv_listen) then
self.modem.open(sv_listen)
end
if not self.modem.isOpen(api_listen) then
self.modem.open(api_listen)
end
end
-- open at construct time
_open_channels()
-- send a coordinator packet
---@param msg_type COORD_TYPES
---@param msg string
local function _send(msg_type, msg)
local s_pkt = comms.scada_packet()
local c_pkt = comms.coord_packet()
c_pkt.make(msg_type, msg)
s_pkt.make(self.seq_num, PROTOCOLS.COORD_DATA, c_pkt.raw_sendable())
self.modem.transmit(sv_port, sv_listen, s_pkt.raw_sendable())
self.seq_num = self.seq_num + 1
end
-- send a SCADA management packet
---@param msg_type SCADA_MGMT_TYPES
---@param msg string
local function _send_mgmt(msg_type, msg)
local s_pkt = comms.scada_packet()
local m_pkt = comms.mgmt_packet()
m_pkt.make(msg_type, msg)
s_pkt.make(self.seq_num, PROTOCOLS.SCADA_MGMT, m_pkt.raw_sendable())
self.modem.transmit(sv_port, sv_listen, s_pkt.raw_sendable())
self.seq_num = self.seq_num + 1
end
-- PUBLIC FUNCTIONS --
-- reconnect a newly connected modem
---@param modem table
---@diagnostic disable-next-line: redefined-local
function public.reconnect_modem(modem)
self.modem = modem
_open_channels()
end
-- parse a packet
---@param side string
---@param sender integer
---@param reply_to integer
---@param message any
---@param distance integer
---@return mgmt_frame|coord_frame|capi_frame|nil packet
function public.parse_packet(side, sender, reply_to, message, distance)
local pkt = nil
local s_pkt = comms.scada_packet()
-- parse packet as generic SCADA packet
s_pkt.receive(side, sender, reply_to, message, distance)
if s_pkt.is_valid() then
-- get as SCADA management packet
if s_pkt.protocol() == PROTOCOLS.SCADA_MGMT then
local mgmt_pkt = comms.mgmt_packet()
if mgmt_pkt.decode(s_pkt) then
pkt = mgmt_pkt.get()
end
-- get as coordinator packet
elseif s_pkt.protocol() == PROTOCOLS.COORD_DATA then
local coord_pkt = comms.coord_packet()
if coord_pkt.decode(s_pkt) then
pkt = coord_pkt.get()
end
-- get as coordinator API packet
elseif s_pkt.protocol() == PROTOCOLS.COORD_API then
local capi_pkt = comms.capi_packet()
if capi_pkt.decode(s_pkt) then
pkt = capi_pkt.get()
end
else
log.debug("attempted parse of illegal packet type " .. s_pkt.protocol(), true)
end
end
return pkt
end
-- handle a packet
---@param packet mgmt_frame|coord_frame|capi_frame
function public.handle_packet(packet)
if packet ~= nil then
-- check sequence number
if self.r_seq_num == nil then
self.r_seq_num = packet.scada_frame.seq_num()
elseif self.connected and self.r_seq_num >= packet.scada_frame.seq_num() then
log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
return
else
self.r_seq_num = packet.scada_frame.seq_num()
end
-- feed watchdog on valid sequence number
conn_watchdog.feed()
local protocol = packet.scada_frame.protocol()
-- handle packet
if protocol == PROTOCOLS.COORD_DATA then
if packet.type == COORD_TYPES.ESTABLISH then
elseif packet.type == COORD_TYPES.QUERY_UNIT then
elseif packet.type == COORD_TYPES.QUERY_FACILITY then
elseif packet.type == COORD_TYPES.COMMAND_UNIT then
elseif packet.type == COORD_TYPES.ALARM then
else
log.warning("received unknown coordinator data packet type " .. packet.type)
end
elseif protocol == PROTOCOLS.COORD_API then
elseif protocol == PROTOCOLS.SCADA_MGMT then
if packet.type == SCADA_MGMT_TYPES.KEEP_ALIVE then
-- keep alive response received
elseif packet.type == SCADA_MGMT_TYPES.CLOSE then
-- handle session close
conn_watchdog.cancel()
println_ts("server connection closed by remote host")
log.warning("server connection closed by remote host")
else
log.warning("received unknown SCADA_MGMT packet type " .. packet.type)
end
else
-- should be unreachable assuming packet is from parse_packet()
log.error("illegal packet type " .. protocol, true)
end
end
end
return public
end
return coordinator

View File

@ -48,6 +48,15 @@ local SCADA_MGMT_TYPES = {
REMOTE_LINKED = 3 -- remote device linked
}
---@alias COORD_TYPES integer
local COORD_TYPES = {
ESTABLISH = 0, -- initial greeting
QUERY_UNIT = 1, -- query the state of a unit
QUERY_FACILITY = 2, -- query general facility status
COMMAND_UNIT = 3, -- command a reactor unit
ALARM = 4 -- alarm signaling
}
---@alias RTU_UNIT_TYPES integer
local RTU_UNIT_TYPES = {
REDSTONE = 0, -- redstone I/O
@ -66,6 +75,7 @@ comms.PROTOCOLS = PROTOCOLS
comms.RPLC_TYPES = RPLC_TYPES
comms.RPLC_LINKING = RPLC_LINKING
comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES
comms.COORD_TYPES = COORD_TYPES
comms.RTU_UNIT_TYPES = RTU_UNIT_TYPES
-- generic SCADA packet object
@ -436,9 +446,13 @@ function comms.coord_packet()
---@class coord_packet
local public = {}
-- check that type is known
local function _coord_type_valid()
-- @todo
return false
return self.type == COORD_TYPES.ESTABLISH or
self.type == COORD_TYPES.QUERY_UNIT or
self.type == COORD_TYPES.QUERY_FACILITY or
self.type == COORD_TYPES.COMMAND_UNIT or
self.type == COORD_TYPES.ALARM
end
-- make a coordinator packet