mirror of
https://github.com/MikaylaFischler/cc-mek-scada.git
synced 2024-08-30 18:22:34 +00:00
Merge pull request #252 from MikaylaFischler/225-consolidate-network-channels
225 Consolidate Network Channels
This commit is contained in:
commit
0b5ee8eabc
@ -1,11 +1,11 @@
|
||||
local config = {}
|
||||
|
||||
-- port of the SCADA supervisor
|
||||
config.SCADA_SV_PORT = 16100
|
||||
-- port to listen to incoming packets from supervisor
|
||||
config.SCADA_SV_CTL_LISTEN = 16101
|
||||
-- listen port for SCADA coordinator API access
|
||||
config.SCADA_API_LISTEN = 16200
|
||||
-- supervisor comms channel
|
||||
config.SVR_CHANNEL = 16240
|
||||
-- coordinator comms channel
|
||||
config.CRD_CHANNEL = 16243
|
||||
-- pocket comms channel
|
||||
config.PKT_CHANNEL = 16244
|
||||
-- max trusted modem message distance (0 to disable check)
|
||||
config.TRUSTED_RANGE = 0
|
||||
-- time in seconds (>= 2) before assuming a remote device is no longer active
|
||||
|
@ -213,14 +213,15 @@ end
|
||||
---@nodiscard
|
||||
---@param version string coordinator version
|
||||
---@param modem table modem device
|
||||
---@param sv_port integer port of configured supervisor
|
||||
---@param sv_listen integer listening port for supervisor replys
|
||||
---@param api_listen integer listening port for pocket API
|
||||
---@param crd_channel integer port of configured supervisor
|
||||
---@param svr_channel integer listening port for supervisor replys
|
||||
---@param pkt_channel integer listening port for pocket API
|
||||
---@param range integer trusted device connection range
|
||||
---@param sv_watchdog watchdog
|
||||
function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range, sv_watchdog)
|
||||
function coordinator.comms(version, modem, crd_channel, svr_channel, pkt_channel, range, sv_watchdog)
|
||||
local self = {
|
||||
sv_linked = false,
|
||||
sv_addr = comms.BROADCAST,
|
||||
sv_seq_num = 0,
|
||||
sv_r_seq_num = nil,
|
||||
sv_config_err = false,
|
||||
@ -236,8 +237,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
-- configure modem channels
|
||||
local function _conf_channels()
|
||||
modem.closeAll()
|
||||
modem.open(sv_listen)
|
||||
modem.open(api_listen)
|
||||
modem.open(crd_channel)
|
||||
end
|
||||
|
||||
_conf_channels()
|
||||
@ -261,23 +261,24 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
end
|
||||
|
||||
pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.sv_seq_num, protocol, pkt.raw_sendable())
|
||||
s_pkt.make(self.sv_addr, self.sv_seq_num, protocol, pkt.raw_sendable())
|
||||
|
||||
modem.transmit(sv_port, sv_listen, s_pkt.raw_sendable())
|
||||
modem.transmit(svr_channel, crd_channel, s_pkt.raw_sendable())
|
||||
self.sv_seq_num = self.sv_seq_num + 1
|
||||
end
|
||||
|
||||
-- send an API establish request response
|
||||
---@param dest integer
|
||||
---@param msg table
|
||||
local function _send_api_establish_ack(seq_id, dest, msg)
|
||||
---@param packet scada_packet
|
||||
---@param ack ESTABLISH_ACK
|
||||
local function _send_api_establish_ack(packet, ack)
|
||||
local s_pkt = comms.scada_packet()
|
||||
local m_pkt = comms.mgmt_packet()
|
||||
|
||||
m_pkt.make(SCADA_MGMT_TYPE.ESTABLISH, msg)
|
||||
s_pkt.make(seq_id, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
m_pkt.make(SCADA_MGMT_TYPE.ESTABLISH, { ack })
|
||||
s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
modem.transmit(dest, api_listen, s_pkt.raw_sendable())
|
||||
modem.transmit(pkt_channel, crd_channel, s_pkt.raw_sendable())
|
||||
self.last_api_est_acks[packet.src_addr()] = ack
|
||||
end
|
||||
|
||||
-- attempt connection establishment
|
||||
@ -307,7 +308,9 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
-- close the connection to the server
|
||||
function public.close()
|
||||
sv_watchdog.cancel()
|
||||
self.sv_addr = comms.BROADCAST
|
||||
self.sv_linked = false
|
||||
self.sv_r_seq_num = nil
|
||||
_send_sv(PROTOCOL.SCADA_MGMT, SCADA_MGMT_TYPE.CLOSE, {})
|
||||
end
|
||||
|
||||
@ -436,15 +439,18 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
---@param packet mgmt_frame|crdn_frame|capi_frame|nil
|
||||
function public.handle_packet(packet)
|
||||
if packet ~= nil then
|
||||
local l_port = packet.scada_frame.local_port()
|
||||
local r_port = packet.scada_frame.remote_port()
|
||||
local l_chan = packet.scada_frame.local_channel()
|
||||
local r_chan = packet.scada_frame.remote_channel()
|
||||
local src_addr = packet.scada_frame.src_addr()
|
||||
local protocol = packet.scada_frame.protocol()
|
||||
|
||||
if l_port == api_listen then
|
||||
if l_chan ~= crd_channel then
|
||||
log.debug("received packet on unconfigured channel " .. l_chan, true)
|
||||
elseif r_chan == pkt_channel then
|
||||
if protocol == PROTOCOL.COORD_API then
|
||||
---@cast packet capi_frame
|
||||
-- look for an associated session
|
||||
local session = apisessions.find_session(r_port)
|
||||
local session = apisessions.find_session(src_addr)
|
||||
|
||||
-- API packet
|
||||
if session ~= nil then
|
||||
@ -457,7 +463,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
elseif protocol == PROTOCOL.SCADA_MGMT then
|
||||
---@cast packet mgmt_frame
|
||||
-- look for an associated session
|
||||
local session = apisessions.find_session(r_port)
|
||||
local session = apisessions.find_session(src_addr)
|
||||
|
||||
-- SCADA management packet
|
||||
if session ~= nil then
|
||||
@ -465,8 +471,6 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
session.in_queue.push_packet(packet)
|
||||
elseif packet.type == SCADA_MGMT_TYPE.ESTABLISH then
|
||||
-- establish a new session
|
||||
local next_seq_id = packet.scada_frame.seq_num() + 1
|
||||
|
||||
-- validate packet and continue
|
||||
if packet.length == 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then
|
||||
local comms_v = packet.data[1]
|
||||
@ -474,42 +478,43 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
local dev_type = packet.data[3]
|
||||
|
||||
if comms_v ~= comms.version then
|
||||
if self.last_api_est_acks[r_port] ~= ESTABLISH_ACK.BAD_VERSION then
|
||||
if self.last_api_est_acks[src_addr] ~= ESTABLISH_ACK.BAD_VERSION then
|
||||
log.info(util.c("dropping API establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
|
||||
self.last_api_est_acks[r_port] = ESTABLISH_ACK.BAD_VERSION
|
||||
end
|
||||
|
||||
_send_api_establish_ack(next_seq_id, r_port, { ESTABLISH_ACK.BAD_VERSION })
|
||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
||||
elseif dev_type == DEVICE_TYPE.PKT then
|
||||
-- pocket linking request
|
||||
local id = apisessions.establish_session(l_port, r_port, firmware_v)
|
||||
println(util.c("API: pocket (", firmware_v, ") [:", r_port, "] connected with session ID ", id))
|
||||
coordinator.log_comms(util.c("API: pocket (", firmware_v, ") [:", r_port, "] connected with session ID ", id))
|
||||
local id = apisessions.establish_session(src_addr, firmware_v)
|
||||
println(util.c("[API] pocket (", firmware_v, ") [@", src_addr, "] \xbb connected"))
|
||||
coordinator.log_comms(util.c("API_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", id))
|
||||
|
||||
_send_api_establish_ack(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW })
|
||||
self.last_api_est_acks[r_port] = ESTABLISH_ACK.ALLOW
|
||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
||||
else
|
||||
log.debug(util.c("illegal establish packet for device ", dev_type, " on API listening channel"))
|
||||
_send_api_establish_ack(next_seq_id, r_port, { ESTABLISH_ACK.DENY })
|
||||
log.debug(util.c("API_ESTABLISH: illegal establish packet for device ", dev_type, " on pocket channel"))
|
||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||
end
|
||||
else
|
||||
log.debug("invalid establish packet (on API listening channel)")
|
||||
_send_api_establish_ack(next_seq_id, r_port, { ESTABLISH_ACK.DENY })
|
||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||
end
|
||||
else
|
||||
-- any other packet should be session related, discard it
|
||||
log.debug(util.c(r_port, "->", l_port, ": discarding SCADA_MGMT packet without a known session"))
|
||||
log.debug(util.c("discarding pocket SCADA_MGMT packet without a known session from computer ", src_addr))
|
||||
end
|
||||
else
|
||||
log.debug("illegal packet type " .. protocol .. " on api listening channel", true)
|
||||
log.debug("illegal packet type " .. protocol .. " on pocket channel", true)
|
||||
end
|
||||
elseif l_port == sv_listen then
|
||||
elseif r_chan == svr_channel then
|
||||
-- check sequence number
|
||||
if self.sv_r_seq_num == nil then
|
||||
self.sv_r_seq_num = packet.scada_frame.seq_num()
|
||||
elseif self.connected and ((self.sv_r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
|
||||
log.warning("sequence out-of-order: last = " .. self.sv_r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||
return
|
||||
elseif self.sv_linked and src_addr ~= self.sv_addr then
|
||||
log.debug("received packet from unknown computer " .. src_addr .. " while linked; channel in use by another system?")
|
||||
return
|
||||
else
|
||||
self.sv_r_seq_num = packet.scada_frame.seq_num()
|
||||
end
|
||||
@ -660,6 +665,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
-- init io controller
|
||||
iocontrol.init(conf, public)
|
||||
|
||||
self.sv_addr = src_addr
|
||||
self.sv_linked = true
|
||||
self.sv_config_err = false
|
||||
else
|
||||
@ -705,10 +711,10 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
local trip_time = util.time() - timestamp
|
||||
|
||||
if trip_time > 750 then
|
||||
log.warning("coord KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)")
|
||||
log.warning("coordinator KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)")
|
||||
end
|
||||
|
||||
-- log.debug("coord RTT = " .. trip_time .. "ms")
|
||||
-- log.debug("coordinator RTT = " .. trip_time .. "ms")
|
||||
|
||||
iocontrol.get_db().facility.ps.publish("sv_ping", trip_time)
|
||||
|
||||
@ -719,7 +725,9 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
elseif packet.type == SCADA_MGMT_TYPE.CLOSE then
|
||||
-- handle session close
|
||||
sv_watchdog.cancel()
|
||||
self.sv_addr = comms.BROADCAST
|
||||
self.sv_linked = false
|
||||
self.sv_r_seq_num = nil
|
||||
println_ts("server connection closed by remote host")
|
||||
log.info("server connection closed by remote host")
|
||||
else
|
||||
@ -732,7 +740,7 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
|
||||
log.debug("illegal packet type " .. protocol .. " on supervisor listening channel", true)
|
||||
end
|
||||
else
|
||||
log.debug("received packet on unconfigured channel " .. l_port, true)
|
||||
log.debug("received packet for unknown channel " .. r_chan, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -5,7 +5,7 @@ local util = require("scada-common.util")
|
||||
|
||||
local config = require("coordinator.config")
|
||||
|
||||
local api = require("coordinator.session.api")
|
||||
local pocket = require("coordinator.session.pocket")
|
||||
|
||||
local apisessions = {}
|
||||
|
||||
@ -18,7 +18,7 @@ local self = {
|
||||
-- PRIVATE FUNCTIONS --
|
||||
|
||||
-- handle a session output queue
|
||||
---@param session api_session_struct
|
||||
---@param session pkt_session_struct
|
||||
local function _api_handle_outq(session)
|
||||
-- record handler start time
|
||||
local handle_start = util.time()
|
||||
@ -31,7 +31,7 @@ local function _api_handle_outq(session)
|
||||
if msg ~= nil then
|
||||
if msg.qtype == mqueue.TYPE.PACKET then
|
||||
-- handle a packet to be sent
|
||||
self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable())
|
||||
self.modem.transmit(config.PKT_CHANNEL, config.CRD_CHANNEL, msg.message.raw_sendable())
|
||||
elseif msg.qtype == mqueue.TYPE.COMMAND then
|
||||
-- handle instruction/notification
|
||||
elseif msg.qtype == mqueue.TYPE.DATA then
|
||||
@ -41,15 +41,15 @@ local function _api_handle_outq(session)
|
||||
|
||||
-- max 100ms spent processing queue
|
||||
if util.time() - handle_start > 100 then
|
||||
log.warning("API out queue handler exceeded 100ms queue process limit")
|
||||
log.warning(util.c("offending session: port ", session.r_port))
|
||||
log.warning("[API] out queue handler exceeded 100ms queue process limit")
|
||||
log.warning(util.c("[API] offending session: ", session))
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- cleanly close a session
|
||||
---@param session api_session_struct
|
||||
---@param session pkt_session_struct
|
||||
local function _shutdown(session)
|
||||
session.open = false
|
||||
session.instance.close()
|
||||
@ -58,11 +58,11 @@ local function _shutdown(session)
|
||||
while session.out_queue.ready() do
|
||||
local msg = session.out_queue.pop()
|
||||
if msg ~= nil and msg.qtype == mqueue.TYPE.PACKET then
|
||||
self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable())
|
||||
self.modem.transmit(config.PKT_CHANNEL, config.CRD_CHANNEL, msg.message.raw_sendable())
|
||||
end
|
||||
end
|
||||
|
||||
log.debug(util.c("closed API session ", session.instance.get_id(), " on remote port ", session.r_port))
|
||||
log.debug(util.c("[API] closed session ", session))
|
||||
end
|
||||
|
||||
-- PUBLIC FUNCTIONS --
|
||||
@ -81,54 +81,60 @@ end
|
||||
|
||||
-- find a session by remote port
|
||||
---@nodiscard
|
||||
---@param port integer
|
||||
---@return api_session_struct|nil
|
||||
function apisessions.find_session(port)
|
||||
---@param source_addr integer
|
||||
---@return pkt_session_struct|nil
|
||||
function apisessions.find_session(source_addr)
|
||||
for i = 1, #self.sessions do
|
||||
if self.sessions[i].r_port == port then return self.sessions[i] end
|
||||
if self.sessions[i].s_addr == source_addr then return self.sessions[i] end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- establish a new API session
|
||||
---@nodiscard
|
||||
---@param local_port integer
|
||||
---@param remote_port integer
|
||||
---@param source_addr integer
|
||||
---@param version string
|
||||
---@return integer session_id
|
||||
function apisessions.establish_session(local_port, remote_port, version)
|
||||
---@class api_session_struct
|
||||
local api_s = {
|
||||
function apisessions.establish_session(source_addr, version)
|
||||
---@class pkt_session_struct
|
||||
local pkt_s = {
|
||||
open = true,
|
||||
version = version,
|
||||
l_port = local_port,
|
||||
r_port = remote_port,
|
||||
s_addr = source_addr,
|
||||
in_queue = mqueue.new(),
|
||||
out_queue = mqueue.new(),
|
||||
instance = nil ---@type api_session
|
||||
instance = nil ---@type pkt_session
|
||||
}
|
||||
|
||||
api_s.instance = api.new_session(self.next_id, api_s.in_queue, api_s.out_queue, config.API_TIMEOUT)
|
||||
table.insert(self.sessions, api_s)
|
||||
local id = self.next_id
|
||||
|
||||
log.debug(util.c("established new API session to ", remote_port, " with ID ", self.next_id))
|
||||
pkt_s.instance = pocket.new_session(id, source_addr, pkt_s.in_queue, pkt_s.out_queue, config.API_TIMEOUT)
|
||||
table.insert(self.sessions, pkt_s)
|
||||
|
||||
self.next_id = self.next_id + 1
|
||||
local mt = {
|
||||
---@param s pkt_session_struct
|
||||
__tostring = function (s) return util.c("PKT [", id, "] (@", s.s_addr, ")") end
|
||||
}
|
||||
|
||||
setmetatable(pkt_s, mt)
|
||||
|
||||
log.debug(util.c("[API] established new session: ", pkt_s))
|
||||
|
||||
self.next_id = id + 1
|
||||
|
||||
-- success
|
||||
return api_s.instance.get_id()
|
||||
return pkt_s.instance.get_id()
|
||||
end
|
||||
|
||||
-- attempt to identify which session's watchdog timer fired
|
||||
---@param timer_event number
|
||||
function apisessions.check_all_watchdogs(timer_event)
|
||||
for i = 1, #self.sessions do
|
||||
local session = self.sessions[i] ---@type api_session_struct
|
||||
local session = self.sessions[i] ---@type pkt_session_struct
|
||||
if session.open then
|
||||
local triggered = session.instance.check_wd(timer_event)
|
||||
if triggered then
|
||||
log.debug(util.c("watchdog closing API session ", session.instance.get_id(),
|
||||
" on remote port ", session.r_port, "..."))
|
||||
log.debug(util.c("[API] watchdog closing session ", session, "..."))
|
||||
_shutdown(session)
|
||||
end
|
||||
end
|
||||
@ -138,7 +144,7 @@ end
|
||||
-- iterate all the API sessions
|
||||
function apisessions.iterate_all()
|
||||
for i = 1, #self.sessions do
|
||||
local session = self.sessions[i] ---@type api_session_struct
|
||||
local session = self.sessions[i] ---@type pkt_session_struct
|
||||
|
||||
if session.open and session.instance.iterate() then
|
||||
_api_handle_outq(session)
|
||||
@ -152,10 +158,9 @@ end
|
||||
function apisessions.free_all_closed()
|
||||
local f = function (session) return session.open end
|
||||
|
||||
---@param session api_session_struct
|
||||
---@param session pkt_session_struct
|
||||
local on_delete = function (session)
|
||||
log.debug(util.c("free'ing closed API session ", session.instance.get_id(),
|
||||
" on remote port ", session.r_port))
|
||||
log.debug(util.c("[API] free'ing closed session ", session))
|
||||
end
|
||||
|
||||
util.filter_table(self.sessions, f, on_delete)
|
||||
@ -164,7 +169,7 @@ end
|
||||
-- close all open connections
|
||||
function apisessions.close_all()
|
||||
for i = 1, #self.sessions do
|
||||
local session = self.sessions[i] ---@type api_session_struct
|
||||
local session = self.sessions[i] ---@type pkt_session_struct
|
||||
if session.open then _shutdown(session) end
|
||||
end
|
||||
|
||||
|
@ -3,7 +3,7 @@ local log = require("scada-common.log")
|
||||
local mqueue = require("scada-common.mqueue")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local api = {}
|
||||
local pocket = {}
|
||||
|
||||
local PROTOCOL = comms.PROTOCOL
|
||||
-- local CAPI_TYPE = comms.CAPI_TYPE
|
||||
@ -21,8 +21,8 @@ local API_S_CMDS = {
|
||||
local API_S_DATA = {
|
||||
}
|
||||
|
||||
api.API_S_CMDS = API_S_CMDS
|
||||
api.API_S_DATA = API_S_DATA
|
||||
pocket.API_S_CMDS = API_S_CMDS
|
||||
pocket.API_S_DATA = API_S_DATA
|
||||
|
||||
local PERIODICS = {
|
||||
KEEP_ALIVE = 2000
|
||||
@ -31,11 +31,12 @@ local PERIODICS = {
|
||||
-- pocket API session
|
||||
---@nodiscard
|
||||
---@param id integer session ID
|
||||
---@param s_addr integer device source address
|
||||
---@param in_queue mqueue in message queue
|
||||
---@param out_queue mqueue out message queue
|
||||
---@param timeout number communications timeout
|
||||
function api.new_session(id, in_queue, out_queue, timeout)
|
||||
local log_header = "api_session(" .. id .. "): "
|
||||
function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
||||
local log_header = "pkt_session(" .. id .. "): "
|
||||
|
||||
local self = {
|
||||
-- connection properties
|
||||
@ -61,10 +62,10 @@ function api.new_session(id, in_queue, out_queue, timeout)
|
||||
}
|
||||
}
|
||||
|
||||
---@class api_session
|
||||
---@class pkt_session
|
||||
local public = {}
|
||||
|
||||
-- mark this API session as closed, stop watchdog
|
||||
-- mark this pocket session as closed, stop watchdog
|
||||
local function _close()
|
||||
self.conn_watchdog.cancel()
|
||||
self.connected = false
|
||||
@ -92,7 +93,7 @@ function api.new_session(id, in_queue, out_queue, timeout)
|
||||
local m_pkt = comms.mgmt_packet()
|
||||
|
||||
m_pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
s_pkt.make(s_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
out_queue.push_packet(s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
@ -134,11 +135,11 @@ function api.new_session(id, in_queue, out_queue, timeout)
|
||||
self.last_rtt = srv_now - srv_start
|
||||
|
||||
if self.last_rtt > 750 then
|
||||
log.warning(log_header .. "API KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)")
|
||||
log.warning(log_header .. "PKT KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)")
|
||||
end
|
||||
|
||||
-- log.debug(log_header .. "API RTT = " .. self.last_rtt .. "ms")
|
||||
-- log.debug(log_header .. "API TT = " .. (srv_now - api_send) .. "ms")
|
||||
-- log.debug(log_header .. "PKT RTT = " .. self.last_rtt .. "ms")
|
||||
-- log.debug(log_header .. "PKT TT = " .. (srv_now - api_send) .. "ms")
|
||||
else
|
||||
log.debug(log_header .. "SCADA keep alive packet length mismatch")
|
||||
end
|
||||
@ -171,7 +172,7 @@ function api.new_session(id, in_queue, out_queue, timeout)
|
||||
function public.close()
|
||||
_close()
|
||||
_send_mgmt(SCADA_MGMT_TYPE.CLOSE, {})
|
||||
println("connection to API session " .. id .. " closed by server")
|
||||
println("connection to pocket session " .. id .. " closed by server")
|
||||
log.info(log_header .. "session closed by server")
|
||||
end
|
||||
|
||||
@ -210,7 +211,7 @@ function api.new_session(id, in_queue, out_queue, timeout)
|
||||
|
||||
-- exit if connection was closed
|
||||
if not self.connected then
|
||||
println("connection to API session " .. id .. " closed by remote host")
|
||||
println("connection to pocket session " .. id .. " closed by remote host")
|
||||
log.info(log_header .. "session closed by remote host")
|
||||
return self.connected
|
||||
end
|
||||
@ -246,4 +247,4 @@ function api.new_session(id, in_queue, out_queue, timeout)
|
||||
return public
|
||||
end
|
||||
|
||||
return api
|
||||
return pocket
|
@ -20,7 +20,7 @@ local sounder = require("coordinator.sounder")
|
||||
|
||||
local apisessions = require("coordinator.session.apisessions")
|
||||
|
||||
local COORDINATOR_VERSION = "v0.15.8"
|
||||
local COORDINATOR_VERSION = "v0.16.0"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@ -37,9 +37,9 @@ local log_comms_connecting = coordinator.log_comms_connecting
|
||||
|
||||
local cfv = util.new_validator()
|
||||
|
||||
cfv.assert_port(config.SCADA_SV_PORT)
|
||||
cfv.assert_port(config.SCADA_SV_CTL_LISTEN)
|
||||
cfv.assert_port(config.SCADA_API_LISTEN)
|
||||
cfv.assert_channel(config.SVR_CHANNEL)
|
||||
cfv.assert_channel(config.CRD_CHANNEL)
|
||||
cfv.assert_channel(config.PKT_CHANNEL)
|
||||
cfv.assert_type_int(config.TRUSTED_RANGE)
|
||||
cfv.assert_type_num(config.SV_TIMEOUT)
|
||||
cfv.assert_min(config.SV_TIMEOUT, 2)
|
||||
@ -148,8 +148,8 @@ local function main()
|
||||
log.debug("startup> conn watchdog created")
|
||||
|
||||
-- start comms, open all channels
|
||||
local coord_comms = coordinator.comms(COORDINATOR_VERSION, modem, config.SCADA_SV_PORT, config.SCADA_SV_CTL_LISTEN,
|
||||
config.SCADA_API_LISTEN, config.TRUSTED_RANGE, conn_watchdog)
|
||||
local coord_comms = coordinator.comms(COORDINATOR_VERSION, modem, config.CRD_CHANNEL, config.SVR_CHANNEL,
|
||||
config.PKT_CHANNEL, config.TRUSTED_RANGE, conn_watchdog)
|
||||
log.debug("startup> comms init")
|
||||
log_comms("comms initialized")
|
||||
|
||||
@ -163,7 +163,7 @@ local function main()
|
||||
|
||||
-- attempt to connect to the supervisor or exit
|
||||
local function init_connect_sv()
|
||||
local tick_waiting, task_done = log_comms_connecting("attempting to connect to configured supervisor on channel " .. config.SCADA_SV_PORT)
|
||||
local tick_waiting, task_done = log_comms_connecting("attempting to connect to configured supervisor on channel " .. config.SVR_CHANNEL)
|
||||
|
||||
-- attempt to establish a connection with the supervisory computer
|
||||
if not coord_comms.sv_connect(60, tick_waiting, task_done) then
|
||||
|
@ -1,11 +1,11 @@
|
||||
local config = {}
|
||||
|
||||
-- port of the SCADA supervisor
|
||||
config.SCADA_SV_PORT = 16100
|
||||
-- port for SCADA coordinator API access
|
||||
config.SCADA_API_PORT = 16200
|
||||
-- port to listen to incoming packets FROM servers
|
||||
config.LISTEN_PORT = 16201
|
||||
-- supervisor comms channel
|
||||
config.SVR_CHANNEL = 16240
|
||||
-- coordinator comms channel
|
||||
config.CRD_CHANNEL = 16243
|
||||
-- pocket comms channel
|
||||
config.PKT_CHANNEL = 16244
|
||||
-- max trusted modem message distance (0 to disable check)
|
||||
config.TRUSTED_RANGE = 0
|
||||
-- time in seconds (>= 2) before assuming a remote device is no longer active
|
||||
|
@ -18,22 +18,24 @@ local pocket = {}
|
||||
---@nodiscard
|
||||
---@param version string pocket version
|
||||
---@param modem table modem device
|
||||
---@param local_port integer local pocket port
|
||||
---@param sv_port integer port of supervisor
|
||||
---@param api_port integer port of coordinator API
|
||||
---@param pkt_channel integer pocket comms channel
|
||||
---@param svr_channel integer supervisor access channel
|
||||
---@param crd_channel integer coordinator access channel
|
||||
---@param range integer trusted device connection range
|
||||
---@param sv_watchdog watchdog
|
||||
---@param api_watchdog watchdog
|
||||
function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_watchdog, api_watchdog)
|
||||
function pocket.comms(version, modem, pkt_channel, svr_channel, crd_channel, range, sv_watchdog, api_watchdog)
|
||||
local self = {
|
||||
sv = {
|
||||
linked = false,
|
||||
addr = comms.BROADCAST,
|
||||
seq_num = 0,
|
||||
r_seq_num = nil, ---@type nil|integer
|
||||
last_est_ack = ESTABLISH_ACK.ALLOW
|
||||
},
|
||||
api = {
|
||||
linked = false,
|
||||
addr = comms.BROADCAST,
|
||||
seq_num = 0,
|
||||
r_seq_num = nil, ---@type nil|integer
|
||||
last_est_ack = ESTABLISH_ACK.ALLOW
|
||||
@ -48,7 +50,7 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
-- configure modem channels
|
||||
local function _conf_channels()
|
||||
modem.closeAll()
|
||||
modem.open(local_port)
|
||||
modem.open(pkt_channel)
|
||||
end
|
||||
|
||||
_conf_channels()
|
||||
@ -61,9 +63,9 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
local pkt = comms.mgmt_packet()
|
||||
|
||||
pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.sv.seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
|
||||
s_pkt.make(self.sv.addr, self.sv.seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
|
||||
|
||||
modem.transmit(sv_port, local_port, s_pkt.raw_sendable())
|
||||
modem.transmit(svr_channel, pkt_channel, s_pkt.raw_sendable())
|
||||
self.sv.seq_num = self.sv.seq_num + 1
|
||||
end
|
||||
|
||||
@ -75,9 +77,9 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
local pkt = comms.mgmt_packet()
|
||||
|
||||
pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.api.seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
|
||||
s_pkt.make(self.api.addr, self.api.seq_num, PROTOCOL.SCADA_MGMT, pkt.raw_sendable())
|
||||
|
||||
modem.transmit(api_port, local_port, s_pkt.raw_sendable())
|
||||
modem.transmit(crd_channel, pkt_channel, s_pkt.raw_sendable())
|
||||
self.api.seq_num = self.api.seq_num + 1
|
||||
end
|
||||
|
||||
@ -89,9 +91,9 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
-- local pkt = comms.capi_packet()
|
||||
|
||||
-- pkt.make(msg_type, msg)
|
||||
-- s_pkt.make(self.api.seq_num, PROTOCOL.COORD_API, pkt.raw_sendable())
|
||||
-- s_pkt.make(self.api.addr, self.api.seq_num, PROTOCOL.COORD_API, pkt.raw_sendable())
|
||||
|
||||
-- modem.transmit(api_port, local_port, s_pkt.raw_sendable())
|
||||
-- modem.transmit(crd_channel, pkt_channel, s_pkt.raw_sendable())
|
||||
-- self.api.seq_num = self.api.seq_num + 1
|
||||
-- end
|
||||
|
||||
@ -133,6 +135,8 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
function public.close_sv()
|
||||
sv_watchdog.cancel()
|
||||
self.sv.linked = false
|
||||
self.sv.r_seq_num = nil
|
||||
self.sv.addr = comms.BROADCAST
|
||||
_send_sv(SCADA_MGMT_TYPE.CLOSE, {})
|
||||
end
|
||||
|
||||
@ -140,6 +144,8 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
function public.close_api()
|
||||
api_watchdog.cancel()
|
||||
self.api.linked = false
|
||||
self.api.r_seq_num = nil
|
||||
self.api.addr = comms.BROADCAST
|
||||
_send_crd(SCADA_MGMT_TYPE.CLOSE, {})
|
||||
end
|
||||
|
||||
@ -214,18 +220,23 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
---@param packet mgmt_frame|capi_frame|nil
|
||||
function public.handle_packet(packet)
|
||||
if packet ~= nil then
|
||||
local l_port = packet.scada_frame.local_port()
|
||||
local r_port = packet.scada_frame.remote_port()
|
||||
local l_chan = packet.scada_frame.local_channel()
|
||||
local r_chan = packet.scada_frame.remote_channel()
|
||||
local protocol = packet.scada_frame.protocol()
|
||||
local src_addr = packet.scada_frame.src_addr()
|
||||
|
||||
if l_port ~= local_port then
|
||||
log.debug("received packet on unconfigured channel " .. l_port, true)
|
||||
elseif r_port == api_port then
|
||||
if l_chan ~= pkt_channel then
|
||||
log.debug("received packet on unconfigured channel " .. l_chan, true)
|
||||
elseif r_chan == crd_channel then
|
||||
-- check sequence number
|
||||
if self.api.r_seq_num == nil then
|
||||
self.api.r_seq_num = packet.scada_frame.seq_num()
|
||||
elseif self.connected and ((self.api.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
|
||||
log.warning("sequence out-of-order: last = " .. self.api.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||
log.warning("sequence out-of-order (API): last = " .. self.api.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||
return
|
||||
elseif self.api.linked and (src_addr ~= self.api.addr) then
|
||||
log.debug("received packet from unknown computer " .. src_addr .. " while linked (API expected " .. self.api.addr ..
|
||||
"); channel in use by another system?")
|
||||
return
|
||||
else
|
||||
self.api.r_seq_num = packet.scada_frame.seq_num()
|
||||
@ -247,6 +258,7 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
log.info("coordinator connection established")
|
||||
self.establish_delay_counter = 0
|
||||
self.api.linked = true
|
||||
self.api.addr = src_addr
|
||||
|
||||
if self.sv.linked then
|
||||
coreio.report_link_state(LINK_STATE.LINKED)
|
||||
@ -294,6 +306,8 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
-- handle session close
|
||||
api_watchdog.cancel()
|
||||
self.api.linked = false
|
||||
self.api.r_seq_num = nil
|
||||
self.api.addr = comms.BROADCAST
|
||||
log.info("coordinator server connection closed by remote host")
|
||||
else
|
||||
log.debug("received unknown SCADA_MGMT packet type " .. packet.type .. " from coordinator")
|
||||
@ -304,12 +318,16 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
else
|
||||
log.debug("illegal packet type " .. protocol .. " from coordinator", true)
|
||||
end
|
||||
elseif r_port == sv_port then
|
||||
elseif r_chan == svr_channel then
|
||||
-- check sequence number
|
||||
if self.sv.r_seq_num == nil then
|
||||
self.sv.r_seq_num = packet.scada_frame.seq_num()
|
||||
elseif self.connected and ((self.sv.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
|
||||
log.warning("sequence out-of-order: last = " .. self.sv.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||
log.warning("sequence out-of-order (SVR): last = " .. self.sv.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||
return
|
||||
elseif self.sv.linked and (src_addr ~= self.sv.addr) then
|
||||
log.debug("received packet from unknown computer " .. src_addr .. " while linked (SVR expected " .. self.sv.addr ..
|
||||
"); channel in use by another system?")
|
||||
return
|
||||
else
|
||||
self.sv.r_seq_num = packet.scada_frame.seq_num()
|
||||
@ -330,6 +348,7 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
log.info("supervisor connection established")
|
||||
self.establish_delay_counter = 0
|
||||
self.sv.linked = true
|
||||
self.sv.addr = src_addr
|
||||
|
||||
if self.api.linked then
|
||||
coreio.report_link_state(LINK_STATE.LINKED)
|
||||
@ -377,6 +396,8 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
-- handle session close
|
||||
sv_watchdog.cancel()
|
||||
self.sv.linked = false
|
||||
self.sv.r_seq_num = nil
|
||||
self.sv.addr = comms.BROADCAST
|
||||
log.info("supervisor server connection closed by remote host")
|
||||
else
|
||||
log.debug("received unknown SCADA_MGMT packet type " .. packet.type .. " from supervisor")
|
||||
@ -388,7 +409,7 @@ function pocket.comms(version, modem, local_port, sv_port, api_port, range, sv_w
|
||||
log.debug("illegal packet type " .. protocol .. " from supervisor", true)
|
||||
end
|
||||
else
|
||||
log.debug("received packet from unconfigured channel " .. r_port, true)
|
||||
log.debug("received packet from unconfigured channel " .. r_chan, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -17,7 +17,7 @@ local coreio = require("pocket.coreio")
|
||||
local pocket = require("pocket.pocket")
|
||||
local renderer = require("pocket.renderer")
|
||||
|
||||
local POCKET_VERSION = "alpha-v0.3.7"
|
||||
local POCKET_VERSION = "alpha-v0.4.4"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@ -28,9 +28,9 @@ local println_ts = util.println_ts
|
||||
|
||||
local cfv = util.new_validator()
|
||||
|
||||
cfv.assert_port(config.SCADA_SV_PORT)
|
||||
cfv.assert_port(config.SCADA_API_PORT)
|
||||
cfv.assert_port(config.LISTEN_PORT)
|
||||
cfv.assert_channel(config.SVR_CHANNEL)
|
||||
cfv.assert_channel(config.CRD_CHANNEL)
|
||||
cfv.assert_channel(config.PKT_CHANNEL)
|
||||
cfv.assert_type_int(config.TRUSTED_RANGE)
|
||||
cfv.assert_type_num(config.COMMS_TIMEOUT)
|
||||
cfv.assert_min(config.COMMS_TIMEOUT, 2)
|
||||
@ -89,8 +89,8 @@ local function main()
|
||||
log.debug("startup> conn watchdogs created")
|
||||
|
||||
-- start comms, open all channels
|
||||
local pocket_comms = pocket.comms(POCKET_VERSION, modem, config.LISTEN_PORT, config.SCADA_SV_PORT,
|
||||
config.SCADA_API_PORT, config.TRUSTED_RANGE, conn_wd.sv, conn_wd.api)
|
||||
local pocket_comms = pocket.comms(POCKET_VERSION, modem, config.PKT_CHANNEL, config.SVR_CHANNEL,
|
||||
config.CRD_CHANNEL, config.TRUSTED_RANGE, conn_wd.sv, conn_wd.api)
|
||||
log.debug("startup> comms init")
|
||||
|
||||
-- base loop clock (2Hz, 10 ticks)
|
||||
|
@ -9,10 +9,10 @@ config.REACTOR_ID = 1
|
||||
-- when emergency coolant is needed due to low coolant
|
||||
-- config.EMERGENCY_COOL = { side = "right", color = nil }
|
||||
|
||||
-- port to send packets TO server
|
||||
config.SERVER_PORT = 16000
|
||||
-- port to listen to incoming packets FROM server
|
||||
config.LISTEN_PORT = 14001
|
||||
-- supervisor comms channel
|
||||
config.SVR_CHANNEL = 16240
|
||||
-- PLC comms channel
|
||||
config.PLC_CHANNEL = 16241
|
||||
-- max trusted modem message distance (0 to disable check)
|
||||
config.TRUSTED_RANGE = 0
|
||||
-- time in seconds (>= 2) before assuming a remote device is no longer active
|
||||
|
@ -2,6 +2,7 @@
|
||||
-- Main SCADA Coordinator GUI
|
||||
--
|
||||
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local config = require("reactor-plc.config")
|
||||
@ -49,7 +50,7 @@ local function init(panel)
|
||||
local reactor = LEDPair{parent=system,label="REACTOR",off=colors.red,c1=colors.yellow,c2=colors.green}
|
||||
local modem = LED{parent=system,label="MODEM",colors=cpair(colors.green,colors.green_off)}
|
||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,colors.gray}}
|
||||
network.update(5)
|
||||
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
system.line_break()
|
||||
|
||||
reactor.register(databus.ps, "reactor_dev_state", reactor.update)
|
||||
@ -69,6 +70,10 @@ local function init(panel)
|
||||
rt_cmrx.register(databus.ps, "routine__comms_rx", rt_cmrx.update)
|
||||
rt_sctl.register(databus.ps, "routine__spctl", rt_sctl.update)
|
||||
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
||||
TextBox{parent=system,x=9,y=5,width=6,height=1,text=comp_id,fg_bg=cpair(colors.lightGray,colors.ivory)}
|
||||
|
||||
--
|
||||
-- status & controls
|
||||
--
|
||||
|
@ -446,14 +446,15 @@ end
|
||||
---@param id integer reactor ID
|
||||
---@param version string PLC version
|
||||
---@param modem table modem device
|
||||
---@param local_port integer local listening port
|
||||
---@param server_port integer remote server port
|
||||
---@param plc_channel integer PLC comms channel
|
||||
---@param svr_channel integer supervisor server channel
|
||||
---@param range integer trusted device connection range
|
||||
---@param reactor table reactor device
|
||||
---@param rps rps RPS reference
|
||||
---@param conn_watchdog watchdog watchdog reference
|
||||
function plc.comms(id, version, modem, local_port, server_port, range, reactor, rps, conn_watchdog)
|
||||
function plc.comms(id, version, modem, plc_channel, svr_channel, range, reactor, rps, conn_watchdog)
|
||||
local self = {
|
||||
sv_addr = comms.BROADCAST,
|
||||
seq_num = 0,
|
||||
r_seq_num = nil,
|
||||
scrammed = false,
|
||||
@ -472,7 +473,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
-- configure modem channels
|
||||
local function _conf_channels()
|
||||
modem.closeAll()
|
||||
modem.open(local_port)
|
||||
modem.open(plc_channel)
|
||||
end
|
||||
|
||||
_conf_channels()
|
||||
@ -485,9 +486,9 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
local r_pkt = comms.rplc_packet()
|
||||
|
||||
r_pkt.make(id, msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.RPLC, r_pkt.raw_sendable())
|
||||
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.RPLC, r_pkt.raw_sendable())
|
||||
|
||||
modem.transmit(server_port, local_port, s_pkt.raw_sendable())
|
||||
modem.transmit(svr_channel, plc_channel, s_pkt.raw_sendable())
|
||||
self.seq_num = self.seq_num + 1
|
||||
end
|
||||
|
||||
@ -499,9 +500,9 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
local m_pkt = comms.mgmt_packet()
|
||||
|
||||
m_pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
modem.transmit(server_port, local_port, s_pkt.raw_sendable())
|
||||
modem.transmit(svr_channel, plc_channel, s_pkt.raw_sendable())
|
||||
self.seq_num = self.seq_num + 1
|
||||
end
|
||||
|
||||
@ -667,9 +668,11 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
|
||||
-- unlink from the server
|
||||
function public.unlink()
|
||||
self.sv_addr = comms.BROADCAST
|
||||
self.linked = false
|
||||
self.r_seq_num = nil
|
||||
self.status_cache = nil
|
||||
databus.tx_link_state(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
end
|
||||
|
||||
-- close the connection to the server
|
||||
@ -731,7 +734,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
end
|
||||
end
|
||||
|
||||
-- parse an RPLC packet
|
||||
-- parse a packet
|
||||
---@nodiscard
|
||||
---@param side string
|
||||
---@param sender integer
|
||||
@ -760,14 +763,14 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
pkt = mgmt_pkt.get()
|
||||
end
|
||||
else
|
||||
log.debug("illegal packet type " .. s_pkt.protocol(), true)
|
||||
log.debug("unsupported packet type " .. s_pkt.protocol(), true)
|
||||
end
|
||||
end
|
||||
|
||||
return pkt
|
||||
end
|
||||
|
||||
-- handle an RPLC packet
|
||||
-- handle RPLC and MGMT packets
|
||||
---@param packet rplc_frame|mgmt_frame packet frame
|
||||
---@param plc_state plc_state PLC state
|
||||
---@param setpoints setpoints setpoint control table
|
||||
@ -775,16 +778,22 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
-- print a log message to the terminal as long as the UI isn't running
|
||||
local function println_ts(message) if not plc_state.fp_ok then util.println_ts(message) end end
|
||||
|
||||
local l_port = packet.scada_frame.local_port()
|
||||
local protocol = packet.scada_frame.protocol()
|
||||
local l_chan = packet.scada_frame.local_channel()
|
||||
local src_addr = packet.scada_frame.src_addr()
|
||||
|
||||
-- handle packets now that we have prints setup
|
||||
if l_port == local_port then
|
||||
if l_chan == plc_channel then
|
||||
-- check sequence number
|
||||
if self.r_seq_num == nil then
|
||||
self.r_seq_num = packet.scada_frame.seq_num()
|
||||
elseif self.linked and ((self.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
|
||||
log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||
return
|
||||
elseif self.linked and (src_addr ~= self.sv_addr) then
|
||||
log.debug("received packet from unknown computer " .. src_addr .. " while linked (expected " .. self.sv_addr ..
|
||||
"); channel in use by another system?")
|
||||
return
|
||||
else
|
||||
self.r_seq_num = packet.scada_frame.seq_num()
|
||||
end
|
||||
@ -792,11 +801,10 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
-- feed the watchdog first so it doesn't uhh...eat our packets :)
|
||||
conn_watchdog.feed()
|
||||
|
||||
local protocol = packet.scada_frame.protocol()
|
||||
|
||||
-- handle packet
|
||||
if protocol == PROTOCOL.RPLC then
|
||||
---@cast packet rplc_frame
|
||||
-- if linked, only accept packets from configured supervisor
|
||||
if self.linked then
|
||||
if packet.type == RPLC_TYPE.STATUS then
|
||||
-- request of full status, clear cache first
|
||||
@ -933,6 +941,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
end
|
||||
elseif protocol == PROTOCOL.SCADA_MGMT then
|
||||
---@cast packet mgmt_frame
|
||||
-- if linked, only accept packets from configured supervisor
|
||||
if self.linked then
|
||||
if packet.type == SCADA_MGMT_TYPE.ESTABLISH then
|
||||
-- link request confirmation
|
||||
@ -945,22 +954,26 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
self.status_cache = nil
|
||||
_send_struct()
|
||||
public.send_status(plc_state.no_reactor, plc_state.reactor_formed)
|
||||
log.debug("re-sent initial status data")
|
||||
elseif est_ack == ESTABLISH_ACK.DENY then
|
||||
println_ts("received unsolicited link denial, unlinking")
|
||||
log.warning("unsolicited establish request denied")
|
||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||
println_ts("received unsolicited link collision, unlinking")
|
||||
log.warning("unsolicited establish request collision")
|
||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||
println_ts("received unsolicited link version mismatch, unlinking")
|
||||
log.warning("unsolicited establish request version mismatch")
|
||||
log.debug("re-sent initial status data due to re-establish")
|
||||
else
|
||||
println_ts("invalid unsolicited link response")
|
||||
log.debug("unsolicited unknown establish request response")
|
||||
end
|
||||
if est_ack == ESTABLISH_ACK.DENY then
|
||||
println_ts("received unsolicited link denial, unlinking")
|
||||
log.warning("unsolicited establish request denied")
|
||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||
println_ts("received unsolicited link collision, unlinking")
|
||||
log.warning("unsolicited establish request collision")
|
||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||
println_ts("received unsolicited link version mismatch, unlinking")
|
||||
log.warning("unsolicited establish request version mismatch")
|
||||
else
|
||||
println_ts("invalid unsolicited link response")
|
||||
log.debug("unsolicited unknown establish request response")
|
||||
end
|
||||
|
||||
self.linked = est_ack == ESTABLISH_ACK.ALLOW
|
||||
-- unlink
|
||||
self.sv_addr = comms.BROADCAST
|
||||
self.linked = false
|
||||
end
|
||||
|
||||
-- clear this since this is for something that was unsolicited
|
||||
self.last_est_ack = ESTABLISH_ACK.ALLOW
|
||||
@ -980,7 +993,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
log.warning("PLC KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)")
|
||||
end
|
||||
|
||||
-- log.debug("RPLC RTT = " .. trip_time .. "ms")
|
||||
-- log.debug("PLC RTT = " .. trip_time .. "ms")
|
||||
|
||||
_send_keep_alive_ack(timestamp)
|
||||
else
|
||||
@ -1002,9 +1015,11 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
|
||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||
println_ts("linked!")
|
||||
log.info("supervisor establish request approved, PLC is linked")
|
||||
log.info("supervisor establish request approved, linked to SV (CID#" .. src_addr .. ")")
|
||||
|
||||
-- reset remote sequence number and cache
|
||||
-- link + reset remote sequence number and cache
|
||||
self.sv_addr = src_addr
|
||||
self.linked = true
|
||||
self.r_seq_num = nil
|
||||
self.status_cache = nil
|
||||
|
||||
@ -1012,23 +1027,28 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
public.send_status(plc_state.no_reactor, plc_state.reactor_formed)
|
||||
|
||||
log.debug("sent initial status data")
|
||||
elseif self.last_est_ack ~= est_ack then
|
||||
if est_ack == ESTABLISH_ACK.DENY then
|
||||
println_ts("link request denied, retrying...")
|
||||
log.info("supervisor establish request denied, retrying")
|
||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||
println_ts("reactor PLC ID collision (check config), retrying...")
|
||||
log.warning("establish request collision, retrying")
|
||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||
println_ts("supervisor version mismatch (try updating), retrying...")
|
||||
log.warning("establish request version mismatch, retrying")
|
||||
else
|
||||
println_ts("invalid link response, bad channel? retrying...")
|
||||
log.error("unknown establish request response, retrying")
|
||||
else
|
||||
if self.last_est_ack ~= est_ack then
|
||||
if est_ack == ESTABLISH_ACK.DENY then
|
||||
println_ts("link request denied, retrying...")
|
||||
log.info("supervisor establish request denied, retrying")
|
||||
elseif est_ack == ESTABLISH_ACK.COLLISION then
|
||||
println_ts("reactor PLC ID collision (check config), retrying...")
|
||||
log.warning("establish request collision, retrying")
|
||||
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
|
||||
println_ts("supervisor version mismatch (try updating), retrying...")
|
||||
log.warning("establish request version mismatch, retrying")
|
||||
else
|
||||
println_ts("invalid link response, bad channel? retrying...")
|
||||
log.error("unknown establish request response, retrying")
|
||||
end
|
||||
end
|
||||
|
||||
-- unlink
|
||||
self.sv_addr = comms.BROADCAST
|
||||
self.linked = false
|
||||
end
|
||||
|
||||
self.linked = est_ack == ESTABLISH_ACK.ALLOW
|
||||
self.last_est_ack = est_ack
|
||||
|
||||
-- report link state
|
||||
@ -1044,7 +1064,7 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor,
|
||||
log.error("illegal packet type " .. protocol, true)
|
||||
end
|
||||
else
|
||||
log.debug("received packet on unconfigured channel " .. l_port, true)
|
||||
log.debug("received packet on unconfigured channel " .. l_chan, true)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc")
|
||||
local renderer = require("reactor-plc.renderer")
|
||||
local threads = require("reactor-plc.threads")
|
||||
|
||||
local R_PLC_VERSION = "v1.3.7"
|
||||
local R_PLC_VERSION = "v1.4.5"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@ -31,8 +31,8 @@ local cfv = util.new_validator()
|
||||
|
||||
cfv.assert_type_bool(config.NETWORKED)
|
||||
cfv.assert_type_int(config.REACTOR_ID)
|
||||
cfv.assert_port(config.SERVER_PORT)
|
||||
cfv.assert_port(config.LISTEN_PORT)
|
||||
cfv.assert_channel(config.SVR_CHANNEL)
|
||||
cfv.assert_channel(config.PLC_CHANNEL)
|
||||
cfv.assert_type_int(config.TRUSTED_RANGE)
|
||||
cfv.assert_type_num(config.COMMS_TIMEOUT)
|
||||
cfv.assert_min(config.COMMS_TIMEOUT, 2)
|
||||
@ -197,7 +197,7 @@ local function main()
|
||||
log.debug("init> conn watchdog started")
|
||||
|
||||
-- start comms
|
||||
smem_sys.plc_comms = plc.comms(config.REACTOR_ID, R_PLC_VERSION, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT,
|
||||
smem_sys.plc_comms = plc.comms(config.REACTOR_ID, R_PLC_VERSION, smem_dev.modem, config.PLC_CHANNEL, config.SVR_CHANNEL,
|
||||
config.TRUSTED_RANGE, smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog)
|
||||
log.debug("init> comms init")
|
||||
else
|
||||
|
@ -2,11 +2,11 @@ local rsio = require("scada-common.rsio")
|
||||
|
||||
local config = {}
|
||||
|
||||
-- port to send packets TO server
|
||||
config.SERVER_PORT = 16000
|
||||
-- port to listen to incoming packets FROM server
|
||||
config.LISTEN_PORT = 15001
|
||||
-- max trusted modem message distance (< 1 to disable check)
|
||||
-- supervisor comms channel
|
||||
config.SVR_CHANNEL = 16240
|
||||
-- RTU/MODBUS comms channel
|
||||
config.RTU_CHANNEL = 16242
|
||||
-- max trusted modem message distance (0 to disable check)
|
||||
config.TRUSTED_RANGE = 0
|
||||
-- time in seconds (>= 2) before assuming a remote device is no longer active
|
||||
config.COMMS_TIMEOUT = 5
|
||||
|
@ -2,6 +2,7 @@
|
||||
-- Main SCADA Coordinator GUI
|
||||
--
|
||||
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local databus = require("rtu.databus")
|
||||
@ -53,7 +54,7 @@ local function init(panel, units)
|
||||
|
||||
local modem = LED{parent=system,label="MODEM",colors=cpair(colors.green,colors.green_off)}
|
||||
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,colors.gray}}
|
||||
network.update(5)
|
||||
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
system.line_break()
|
||||
|
||||
modem.register(databus.ps, "has_modem", modem.update)
|
||||
@ -66,6 +67,10 @@ local function init(panel, units)
|
||||
rt_main.register(databus.ps, "routine__main", rt_main.update)
|
||||
rt_comm.register(databus.ps, "routine__comms", rt_comm.update)
|
||||
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
||||
TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=cpair(colors.lightGray,colors.ivory)}
|
||||
|
||||
--
|
||||
-- about label
|
||||
--
|
||||
|
35
rtu/rtu.lua
35
rtu/rtu.lua
@ -159,12 +159,13 @@ end
|
||||
---@nodiscard
|
||||
---@param version string RTU version
|
||||
---@param modem table modem device
|
||||
---@param local_port integer local listening port
|
||||
---@param server_port integer remote server port
|
||||
---@param rtu_channel integer PLC comms channel
|
||||
---@param svr_channel integer supervisor server channel
|
||||
---@param range integer trusted device connection range
|
||||
---@param conn_watchdog watchdog watchdog reference
|
||||
function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog)
|
||||
function rtu.comms(version, modem, rtu_channel, svr_channel, range, conn_watchdog)
|
||||
local self = {
|
||||
sv_addr = comms.BROADCAST,
|
||||
seq_num = 0,
|
||||
r_seq_num = nil,
|
||||
txn_id = 0,
|
||||
@ -180,7 +181,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog
|
||||
-- configure modem channels
|
||||
local function _conf_channels()
|
||||
modem.closeAll()
|
||||
modem.open(local_port)
|
||||
modem.open(rtu_channel)
|
||||
end
|
||||
|
||||
_conf_channels()
|
||||
@ -193,9 +194,9 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog
|
||||
local m_pkt = comms.mgmt_packet()
|
||||
|
||||
m_pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
modem.transmit(server_port, local_port, s_pkt.raw_sendable())
|
||||
modem.transmit(svr_channel, rtu_channel, s_pkt.raw_sendable())
|
||||
self.seq_num = self.seq_num + 1
|
||||
end
|
||||
|
||||
@ -238,8 +239,8 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog
|
||||
---@param m_pkt modbus_packet
|
||||
function public.send_modbus(m_pkt)
|
||||
local s_pkt = comms.scada_packet()
|
||||
s_pkt.make(self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable())
|
||||
modem.transmit(server_port, local_port, s_pkt.raw_sendable())
|
||||
s_pkt.make(self.sv_addr, self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable())
|
||||
modem.transmit(svr_channel, rtu_channel, s_pkt.raw_sendable())
|
||||
self.seq_num = self.seq_num + 1
|
||||
end
|
||||
|
||||
@ -254,7 +255,9 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog
|
||||
---@param rtu_state rtu_state
|
||||
function public.unlink(rtu_state)
|
||||
rtu_state.linked = false
|
||||
self.sv_addr = comms.BROADCAST
|
||||
self.r_seq_num = nil
|
||||
databus.tx_link_state(types.PANEL_LINK_STATE.DISCONNECTED)
|
||||
end
|
||||
|
||||
-- close the connection to the server
|
||||
@ -327,13 +330,21 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog
|
||||
-- print a log message to the terminal as long as the UI isn't running
|
||||
local function println_ts(message) if not rtu_state.fp_ok then util.println_ts(message) end end
|
||||
|
||||
if packet.scada_frame.local_port() == local_port then
|
||||
local protocol = packet.scada_frame.protocol()
|
||||
local l_chan = packet.scada_frame.local_channel()
|
||||
local src_addr = packet.scada_frame.src_addr()
|
||||
|
||||
if l_chan == rtu_channel then
|
||||
-- check sequence number
|
||||
if self.r_seq_num == nil then
|
||||
self.r_seq_num = packet.scada_frame.seq_num()
|
||||
elseif rtu_state.linked and ((self.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
|
||||
log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
|
||||
return
|
||||
elseif rtu_state.linked and (src_addr ~= self.sv_addr) then
|
||||
log.debug("received packet from unknown computer " .. src_addr .. " while linked (expected " .. self.sv_addr ..
|
||||
"); channel in use by another system?")
|
||||
return
|
||||
else
|
||||
self.r_seq_num = packet.scada_frame.seq_num()
|
||||
end
|
||||
@ -341,8 +352,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog
|
||||
-- feed watchdog on valid sequence number
|
||||
conn_watchdog.feed()
|
||||
|
||||
local protocol = packet.scada_frame.protocol()
|
||||
|
||||
-- handle packet
|
||||
if protocol == PROTOCOL.MODBUS_TCP then
|
||||
---@cast packet modbus_frame
|
||||
if rtu_state.linked then
|
||||
@ -398,6 +408,7 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog
|
||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||
-- establish allowed
|
||||
rtu_state.linked = true
|
||||
self.sv_addr = packet.scada_frame.src_addr()
|
||||
self.r_seq_num = nil
|
||||
println_ts("supervisor connection established")
|
||||
log.info("supervisor connection established")
|
||||
@ -461,6 +472,8 @@ function rtu.comms(version, modem, local_port, server_port, range, conn_watchdog
|
||||
-- should be unreachable assuming packet is from parse_packet()
|
||||
log.error("illegal packet type " .. protocol, true)
|
||||
end
|
||||
else
|
||||
log.debug("received packet on unconfigured channel " .. l_chan, true)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -28,7 +28,7 @@ local sna_rtu = require("rtu.dev.sna_rtu")
|
||||
local sps_rtu = require("rtu.dev.sps_rtu")
|
||||
local turbinev_rtu = require("rtu.dev.turbinev_rtu")
|
||||
|
||||
local RTU_VERSION = "v1.2.8"
|
||||
local RTU_VERSION = "v1.3.5"
|
||||
|
||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
|
||||
@ -42,8 +42,8 @@ local println_ts = util.println_ts
|
||||
|
||||
local cfv = util.new_validator()
|
||||
|
||||
cfv.assert_port(config.SERVER_PORT)
|
||||
cfv.assert_port(config.LISTEN_PORT)
|
||||
cfv.assert_channel(config.SVR_CHANNEL)
|
||||
cfv.assert_channel(config.RTU_CHANNEL)
|
||||
cfv.assert_type_int(config.TRUSTED_RANGE)
|
||||
cfv.assert_type_num(config.COMMS_TIMEOUT)
|
||||
cfv.assert_min(config.COMMS_TIMEOUT, 2)
|
||||
@ -467,7 +467,7 @@ local function main()
|
||||
log.debug("startup> conn watchdog started")
|
||||
|
||||
-- setup comms
|
||||
smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_dev.modem, config.LISTEN_PORT, config.SERVER_PORT,
|
||||
smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_dev.modem, config.RTU_CHANNEL, config.SVR_CHANNEL,
|
||||
config.TRUSTED_RANGE, smem_sys.conn_watchdog)
|
||||
log.debug("startup> comms init")
|
||||
|
||||
|
@ -4,14 +4,17 @@
|
||||
|
||||
local log = require("scada-common.log")
|
||||
|
||||
local insert = table.insert
|
||||
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
local C_ID = os.getComputerID() ---@type integer computer ID
|
||||
|
||||
local max_distance = nil ---@type number|nil maximum acceptable transmission distance
|
||||
|
||||
---@class comms
|
||||
local comms = {}
|
||||
|
||||
local insert = table.insert
|
||||
|
||||
local max_distance = nil
|
||||
|
||||
comms.version = "1.4.1"
|
||||
comms.version = "2.0.0"
|
||||
|
||||
---@enum PROTOCOL
|
||||
local PROTOCOL = {
|
||||
@ -122,27 +125,28 @@ comms.PLC_AUTO_ACK = PLC_AUTO_ACK
|
||||
comms.UNIT_COMMAND = UNIT_COMMAND
|
||||
comms.FAC_COMMAND = FAC_COMMAND
|
||||
|
||||
-- destination broadcast address (to all devices)
|
||||
comms.BROADCAST = -1
|
||||
|
||||
---@alias packet scada_packet|modbus_packet|rplc_packet|mgmt_packet|crdn_packet|capi_packet
|
||||
---@alias frame modbus_frame|rplc_frame|mgmt_frame|crdn_frame|capi_frame
|
||||
|
||||
-- configure the maximum allowable message receive distance<br>
|
||||
-- packets received with distances greater than this will be silently discarded
|
||||
---@param distance integer max modem message distance (less than 1 disables the limit)
|
||||
---@param distance integer max modem message distance (0 disables the limit)
|
||||
function comms.set_trusted_range(distance)
|
||||
if distance < 1 then
|
||||
max_distance = nil
|
||||
else
|
||||
max_distance = distance
|
||||
end
|
||||
if distance == 0 then max_distance = nil else max_distance = distance end
|
||||
end
|
||||
|
||||
-- generic SCADA packet object
|
||||
---@nodiscard
|
||||
function comms.scada_packet()
|
||||
local self = {
|
||||
modem_msg_in = nil,
|
||||
modem_msg_in = nil, ---@type modem_message|nil
|
||||
valid = false,
|
||||
raw = { -1, PROTOCOL.SCADA_MGMT, {} },
|
||||
raw = {},
|
||||
src_addr = comms.BROADCAST,
|
||||
dest_addr = comms.BROADCAST,
|
||||
seq_num = -1,
|
||||
protocol = PROTOCOL.SCADA_MGMT,
|
||||
length = 0,
|
||||
@ -153,34 +157,40 @@ function comms.scada_packet()
|
||||
local public = {}
|
||||
|
||||
-- make a SCADA packet
|
||||
---@param seq_num integer
|
||||
---@param dest_addr integer destination computer address (ID)
|
||||
---@param seq_num integer sequence number
|
||||
---@param protocol PROTOCOL
|
||||
---@param payload table
|
||||
function public.make(seq_num, protocol, payload)
|
||||
function public.make(dest_addr, seq_num, protocol, payload)
|
||||
self.valid = true
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
self.src_addr = C_ID
|
||||
self.dest_addr = dest_addr
|
||||
self.seq_num = seq_num
|
||||
self.protocol = protocol
|
||||
self.length = #payload
|
||||
self.payload = payload
|
||||
self.raw = { self.seq_num, self.protocol, self.payload }
|
||||
self.raw = { self.src_addr, self.dest_addr, self.seq_num, self.protocol, self.payload }
|
||||
end
|
||||
|
||||
-- parse in a modem message as a SCADA packet
|
||||
---@param side string modem side
|
||||
---@param sender integer sender port
|
||||
---@param reply_to integer reply port
|
||||
---@param sender integer sender channel
|
||||
---@param reply_to integer reply channel
|
||||
---@param message any message body
|
||||
---@param distance integer transmission distance
|
||||
---@return boolean valid valid message received
|
||||
function public.receive(side, sender, reply_to, message, distance)
|
||||
---@class modem_message
|
||||
self.modem_msg_in = {
|
||||
iface = side,
|
||||
s_port = sender,
|
||||
r_port = reply_to,
|
||||
s_channel = sender,
|
||||
r_channel = reply_to,
|
||||
msg = message,
|
||||
dist = distance
|
||||
}
|
||||
|
||||
self.valid = false
|
||||
self.raw = self.modem_msg_in.msg
|
||||
|
||||
if (type(max_distance) == "number") and (distance > max_distance) then
|
||||
@ -188,20 +198,31 @@ function comms.scada_packet()
|
||||
-- log.debug("comms.scada_packet.receive(): discarding packet with distance " .. distance .. " outside of trusted range")
|
||||
else
|
||||
if type(self.raw) == "table" then
|
||||
if #self.raw >= 3 then
|
||||
self.seq_num = self.raw[1]
|
||||
self.protocol = self.raw[2]
|
||||
if #self.raw == 5 then
|
||||
self.src_addr = self.raw[1]
|
||||
self.dest_addr = self.raw[2]
|
||||
self.seq_num = self.raw[3]
|
||||
self.protocol = self.raw[4]
|
||||
|
||||
-- element 3 must be a table
|
||||
if type(self.raw[3]) == "table" then
|
||||
self.length = #self.raw[3]
|
||||
self.payload = self.raw[3]
|
||||
-- element 5 must be a table
|
||||
if type(self.raw[5]) == "table" then
|
||||
self.length = #self.raw[5]
|
||||
self.payload = self.raw[5]
|
||||
end
|
||||
else
|
||||
self.src_addr = nil
|
||||
self.dest_addr = nil
|
||||
self.seq_num = nil
|
||||
self.protocol = nil
|
||||
self.length = 0
|
||||
self.payload = {}
|
||||
end
|
||||
|
||||
self.valid = type(self.seq_num) == "number" and
|
||||
type(self.protocol) == "number" and
|
||||
type(self.payload) == "table"
|
||||
-- check if this packet is destined for this device
|
||||
local is_destination = (self.dest_addr == comms.BROADCAST) or (self.dest_addr == C_ID)
|
||||
|
||||
self.valid = is_destination and type(self.src_addr) == "number" and type(self.dest_addr) == "number" and
|
||||
type(self.seq_num) == "number" and type(self.protocol) == "number" and type(self.payload) == "table"
|
||||
end
|
||||
end
|
||||
|
||||
@ -216,13 +237,17 @@ function comms.scada_packet()
|
||||
function public.raw_sendable() return self.raw end
|
||||
|
||||
---@nodiscard
|
||||
function public.local_port() return self.modem_msg_in.s_port end
|
||||
function public.local_channel() return self.modem_msg_in.s_channel end
|
||||
---@nodiscard
|
||||
function public.remote_port() return self.modem_msg_in.r_port end
|
||||
function public.remote_channel() return self.modem_msg_in.r_channel end
|
||||
|
||||
---@nodiscard
|
||||
function public.is_valid() return self.valid end
|
||||
|
||||
---@nodiscard
|
||||
function public.src_addr() return self.src_addr end
|
||||
---@nodiscard
|
||||
function public.dest_addr() return self.dest_addr end
|
||||
---@nodiscard
|
||||
function public.seq_num() return self.seq_num end
|
||||
---@nodiscard
|
||||
|
@ -74,6 +74,15 @@ function types.new_zero_coordinate() return { x = 0, y = 0, z = 0 } end
|
||||
-- ENUMERATION TYPES --
|
||||
--#region
|
||||
|
||||
---@enum PANEL_LINK_STATE
|
||||
types.PANEL_LINK_STATE = {
|
||||
LINKED = 1,
|
||||
DENIED = 2,
|
||||
COLLISION = 3,
|
||||
BAD_VERSION = 4,
|
||||
DISCONNECTED = 5
|
||||
}
|
||||
|
||||
---@enum RTU_UNIT_TYPE
|
||||
types.RTU_UNIT_TYPE = {
|
||||
VIRTUAL = 0, -- virtual device
|
||||
|
@ -65,7 +65,8 @@ end
|
||||
---@return string
|
||||
function util.strval(val)
|
||||
local t = type(val)
|
||||
if t == "table" or t == "function" then
|
||||
-- this depends on Lua short-circuiting the or check for metatables (note: metatables won't have metatables)
|
||||
if (t == "table" and (getmetatable(val) == nil or getmetatable(val).__tostring == nil)) or t == "function" then
|
||||
return "[" .. tostring(val) .. "]"
|
||||
else
|
||||
return tostring(val)
|
||||
@ -539,7 +540,7 @@ function util.new_validator()
|
||||
function public.assert_range(check, min, max) valid = valid and check >= min and check <= max end
|
||||
function public.assert_range_ex(check, min, max) valid = valid and check > min and check < max end
|
||||
|
||||
function public.assert_port(port) valid = valid and type(port) == "number" and port >= 0 and port <= 65535 end
|
||||
function public.assert_channel(channel) valid = valid and type(channel) == "number" and channel >= 0 and channel <= 65535 end
|
||||
|
||||
-- check if all assertions passed successfully
|
||||
---@nodiscard
|
||||
|
@ -1,9 +1,15 @@
|
||||
local config = {}
|
||||
|
||||
-- scada network listen for PLC's and RTU's
|
||||
config.SCADA_DEV_LISTEN = 16000
|
||||
-- listen port for SCADA supervisor access
|
||||
config.SCADA_SV_CTL_LISTEN = 16100
|
||||
-- supervisor comms channel
|
||||
config.SVR_CHANNEL = 16240
|
||||
-- PLC comms channel
|
||||
config.PLC_CHANNEL = 16241
|
||||
-- RTU/MODBUS comms channel
|
||||
config.RTU_CHANNEL = 16242
|
||||
-- coordinator comms channel
|
||||
config.CRD_CHANNEL = 16243
|
||||
-- pocket comms channel
|
||||
config.PKT_CHANNEL = 16244
|
||||
-- max trusted modem message distance (0 to disable check)
|
||||
config.TRUSTED_RANGE = 0
|
||||
-- time in seconds (>= 2) before assuming a remote device is no longer active
|
||||
|
@ -3,9 +3,14 @@
|
||||
--
|
||||
|
||||
local psil = require("scada-common.psil")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local pgi = require("supervisor.panel.pgi")
|
||||
|
||||
-- nominal RTT is ping (0ms to 10ms usually) + 150ms for SV main loop tick
|
||||
local WARN_RTT = 300 -- 2x as long as expected w/ 0 ping
|
||||
local HIGH_RTT = 500 -- 3.33x as long as expected w/ 0 ping
|
||||
|
||||
local databus = {}
|
||||
|
||||
-- databus PSIL
|
||||
@ -31,11 +36,11 @@ end
|
||||
-- transmit PLC firmware version and session connection state
|
||||
---@param reactor_id integer reactor unit ID
|
||||
---@param fw string firmware version
|
||||
---@param channel integer PLC remote port
|
||||
function databus.tx_plc_connected(reactor_id, fw, channel)
|
||||
---@param s_addr integer PLC computer ID
|
||||
function databus.tx_plc_connected(reactor_id, fw, s_addr)
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_fw", fw)
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_conn", true)
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_chan", tostring(channel))
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_addr", util.sprintf("@% 4d", s_addr))
|
||||
end
|
||||
|
||||
-- transmit PLC disconnected
|
||||
@ -43,7 +48,7 @@ end
|
||||
function databus.tx_plc_disconnected(reactor_id)
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_fw", " ------- ")
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_conn", false)
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_chan", " --- ")
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_addr", " --- ")
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt", 0)
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.lightGray)
|
||||
end
|
||||
@ -54,9 +59,9 @@ end
|
||||
function databus.tx_plc_rtt(reactor_id, rtt)
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt", rtt)
|
||||
|
||||
if rtt > 700 then
|
||||
if rtt > HIGH_RTT then
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.red)
|
||||
elseif rtt > 300 then
|
||||
elseif rtt > WARN_RTT then
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.yellow_hc)
|
||||
else
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.green)
|
||||
@ -66,10 +71,10 @@ end
|
||||
-- transmit RTU firmware version and session connection state
|
||||
---@param session_id integer RTU session
|
||||
---@param fw string firmware version
|
||||
---@param channel integer RTU remote port
|
||||
function databus.tx_rtu_connected(session_id, fw, channel)
|
||||
---@param s_addr integer RTU computer ID
|
||||
function databus.tx_rtu_connected(session_id, fw, s_addr)
|
||||
databus.ps.publish("rtu_" .. session_id .. "_fw", fw)
|
||||
databus.ps.publish("rtu_" .. session_id .. "_chan", tostring(channel))
|
||||
databus.ps.publish("rtu_" .. session_id .. "_addr", util.sprintf("@ C% 3d", s_addr))
|
||||
pgi.create_rtu_entry(session_id)
|
||||
end
|
||||
|
||||
@ -85,9 +90,9 @@ end
|
||||
function databus.tx_rtu_rtt(session_id, rtt)
|
||||
databus.ps.publish("rtu_" .. session_id .. "_rtt", rtt)
|
||||
|
||||
if rtt > 700 then
|
||||
if rtt > HIGH_RTT then
|
||||
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.red)
|
||||
elseif rtt > 300 then
|
||||
elseif rtt > WARN_RTT then
|
||||
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.yellow_hc)
|
||||
else
|
||||
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.green)
|
||||
@ -103,18 +108,18 @@ end
|
||||
|
||||
-- transmit coordinator firmware version and session connection state
|
||||
---@param fw string firmware version
|
||||
---@param channel integer coordinator remote port
|
||||
function databus.tx_crd_connected(fw, channel)
|
||||
---@param s_addr integer coordinator computer ID
|
||||
function databus.tx_crd_connected(fw, s_addr)
|
||||
databus.ps.publish("crd_fw", fw)
|
||||
databus.ps.publish("crd_conn", true)
|
||||
databus.ps.publish("crd_chan", tostring(channel))
|
||||
databus.ps.publish("crd_addr", tostring(s_addr))
|
||||
end
|
||||
|
||||
-- transmit coordinator disconnected
|
||||
function databus.tx_crd_disconnected()
|
||||
databus.ps.publish("crd_fw", " ------- ")
|
||||
databus.ps.publish("crd_conn", false)
|
||||
databus.ps.publish("crd_chan", "---")
|
||||
databus.ps.publish("crd_addr", "---")
|
||||
databus.ps.publish("crd_rtt", 0)
|
||||
databus.ps.publish("crd_rtt_color", colors.lightGray)
|
||||
end
|
||||
@ -124,9 +129,9 @@ end
|
||||
function databus.tx_crd_rtt(rtt)
|
||||
databus.ps.publish("crd_rtt", rtt)
|
||||
|
||||
if rtt > 700 then
|
||||
if rtt > HIGH_RTT then
|
||||
databus.ps.publish("crd_rtt_color", colors.red)
|
||||
elseif rtt > 300 then
|
||||
elseif rtt > WARN_RTT then
|
||||
databus.ps.publish("crd_rtt_color", colors.yellow_hc)
|
||||
else
|
||||
databus.ps.publish("crd_rtt_color", colors.green)
|
||||
@ -136,10 +141,10 @@ end
|
||||
-- transmit PKT firmware version and PDG session connection state
|
||||
---@param session_id integer PDG session
|
||||
---@param fw string firmware version
|
||||
---@param channel integer PDG remote port
|
||||
function databus.tx_pdg_connected(session_id, fw, channel)
|
||||
---@param s_addr integer PDG computer ID
|
||||
function databus.tx_pdg_connected(session_id, fw, s_addr)
|
||||
databus.ps.publish("pdg_" .. session_id .. "_fw", fw)
|
||||
databus.ps.publish("pdg_" .. session_id .. "_chan", tostring(channel))
|
||||
databus.ps.publish("pdg_" .. session_id .. "_addr", util.sprintf("@ C% 3d", s_addr))
|
||||
pgi.create_pdg_entry(session_id)
|
||||
end
|
||||
|
||||
@ -155,9 +160,9 @@ end
|
||||
function databus.tx_pdg_rtt(session_id, rtt)
|
||||
databus.ps.publish("pdg_" .. session_id .. "_rtt", rtt)
|
||||
|
||||
if rtt > 700 then
|
||||
if rtt > HIGH_RTT then
|
||||
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.red)
|
||||
elseif rtt > 300 then
|
||||
elseif rtt > WARN_RTT then
|
||||
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.yellow_hc)
|
||||
else
|
||||
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.green)
|
||||
|
@ -2,8 +2,6 @@
|
||||
-- Pocket Diagnostics Connection Entry
|
||||
--
|
||||
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local databus = require("supervisor.databus")
|
||||
|
||||
local core = require("graphics.core")
|
||||
@ -28,9 +26,9 @@ local function init(parent, id)
|
||||
local ps_prefix = "pdg_" .. id .. "_"
|
||||
|
||||
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
local pdg_chan = TextBox{parent=entry,x=1,y=2,text=" :00000",alignment=TEXT_ALIGN.CENTER,width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray),nav_active=cpair(colors.gray,colors.black)}
|
||||
local pdg_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=TEXT_ALIGN.CENTER,width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray),nav_active=cpair(colors.gray,colors.black)}
|
||||
TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
pdg_chan.register(databus.ps, ps_prefix .. "chan", function (channel) pdg_chan.set_value(util.sprintf(" :%05d", channel)) end)
|
||||
pdg_addr.register(databus.ps, ps_prefix .. "addr", pdg_addr.set_value)
|
||||
|
||||
TextBox{parent=entry,x=10,y=2,text="FW:",width=3,height=1}
|
||||
local pdg_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
|
@ -2,8 +2,6 @@
|
||||
-- RTU Connection Entry
|
||||
--
|
||||
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local databus = require("supervisor.databus")
|
||||
|
||||
local core = require("graphics.core")
|
||||
@ -28,9 +26,9 @@ local function init(parent, id)
|
||||
local ps_prefix = "rtu_" .. id .. "_"
|
||||
|
||||
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
local rtu_chan = TextBox{parent=entry,x=1,y=2,text=" :00000",alignment=TEXT_ALIGN.CENTER,width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray),nav_active=cpair(colors.gray,colors.black)}
|
||||
local rtu_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=TEXT_ALIGN.CENTER,width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray),nav_active=cpair(colors.gray,colors.black)}
|
||||
TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
rtu_chan.register(databus.ps, ps_prefix .. "chan", function (channel) rtu_chan.set_value(util.sprintf(" :%05d", channel)) end)
|
||||
rtu_addr.register(databus.ps, ps_prefix .. "addr", rtu_addr.set_value)
|
||||
|
||||
TextBox{parent=entry,x=10,y=2,text="UNITS:",width=7,height=1}
|
||||
local unit_count = DataIndicator{parent=entry,x=17,y=2,label="",unit="",format="%2d",value=0,width=2,fg_bg=cpair(colors.gray,colors.white)}
|
||||
|
@ -56,6 +56,10 @@ local function init(panel)
|
||||
|
||||
modem.register(databus.ps, "has_modem", modem.update)
|
||||
|
||||
---@diagnostic disable-next-line: undefined-field
|
||||
local comp_id = util.sprintf("(%d)", os.getComputerID())
|
||||
TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=cpair(colors.lightGray,colors.ivory)}
|
||||
|
||||
--
|
||||
-- about footer
|
||||
--
|
||||
@ -84,11 +88,11 @@ local function init(panel)
|
||||
TextBox{parent=plc_entry,x=1,y=2,text="UNIT "..i,alignment=TEXT_ALIGN.CENTER,width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
TextBox{parent=plc_entry,x=1,y=3,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
|
||||
local conn = LED{parent=plc_entry,x=10,y=2,label="CONN",colors=cpair(colors.green,colors.green_off)}
|
||||
local conn = LED{parent=plc_entry,x=10,y=2,label="LINK",colors=cpair(colors.green,colors.green_off)}
|
||||
conn.register(databus.ps, ps_prefix .. "conn", conn.update)
|
||||
|
||||
local plc_chan = TextBox{parent=plc_entry,x=17,y=2,text=" --- ",width=5,height=1,fg_bg=cpair(colors.gray,colors.white)}
|
||||
plc_chan.register(databus.ps, ps_prefix .. "chan", plc_chan.set_value)
|
||||
local plc_addr = TextBox{parent=plc_entry,x=17,y=2,text=" --- ",width=5,height=1,fg_bg=cpair(colors.gray,colors.white)}
|
||||
plc_addr.register(databus.ps, ps_prefix .. "addr", plc_addr.set_value)
|
||||
|
||||
TextBox{parent=plc_entry,x=23,y=2,text="FW:",width=3,height=1}
|
||||
local plc_fw_v = TextBox{parent=plc_entry,x=27,y=2,text=" ------- ",width=9,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
@ -117,9 +121,9 @@ local function init(panel)
|
||||
local crd_conn = LED{parent=crd_box,x=2,y=2,label="CONNECTION",colors=cpair(colors.green,colors.green_off)}
|
||||
crd_conn.register(databus.ps, "crd_conn", crd_conn.update)
|
||||
|
||||
TextBox{parent=crd_box,x=4,y=3,text="CHANNEL ",width=8,height=1,fg_bg=cpair(colors.gray,colors.white)}
|
||||
local crd_chan = TextBox{parent=crd_box,x=12,y=3,text="---",width=5,height=1,fg_bg=cpair(colors.gray,colors.white)}
|
||||
crd_chan.register(databus.ps, "crd_chan", crd_chan.set_value)
|
||||
TextBox{parent=crd_box,x=4,y=3,text="COMPUTER",width=8,height=1,fg_bg=cpair(colors.gray,colors.white)}
|
||||
local crd_addr = TextBox{parent=crd_box,x=13,y=3,text="---",width=5,height=1,fg_bg=cpair(colors.gray,colors.white)}
|
||||
crd_addr.register(databus.ps, "crd_addr", crd_addr.set_value)
|
||||
|
||||
TextBox{parent=crd_box,x=22,y=2,text="FW:",width=3,height=1}
|
||||
local crd_fw_v = TextBox{parent=crd_box,x=26,y=2,text=" ------- ",width=9,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
|
@ -45,12 +45,13 @@ local PERIODICS = {
|
||||
-- coordinator supervisor session
|
||||
---@nodiscard
|
||||
---@param id integer session ID
|
||||
---@param s_addr integer device source address
|
||||
---@param in_queue mqueue in message queue
|
||||
---@param out_queue mqueue out message queue
|
||||
---@param timeout number communications timeout
|
||||
---@param facility facility facility data table
|
||||
---@param fp_ok boolean if the front panel UI is running
|
||||
function coordinator.new_session(id, in_queue, out_queue, timeout, facility, fp_ok)
|
||||
function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facility, fp_ok)
|
||||
-- print a log message to the terminal as long as the UI isn't running
|
||||
local function println(message) if not fp_ok then util.println_ts(message) end end
|
||||
|
||||
@ -99,7 +100,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility, fp_
|
||||
local c_pkt = comms.crdn_packet()
|
||||
|
||||
c_pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.SCADA_CRDN, c_pkt.raw_sendable())
|
||||
s_pkt.make(s_addr, self.seq_num, PROTOCOL.SCADA_CRDN, c_pkt.raw_sendable())
|
||||
|
||||
out_queue.push_packet(s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
@ -113,7 +114,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility, fp_
|
||||
local m_pkt = comms.mgmt_packet()
|
||||
|
||||
m_pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
s_pkt.make(s_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
out_queue.push_packet(s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
@ -334,7 +335,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility, fp_
|
||||
end
|
||||
end
|
||||
|
||||
---@class coord_session
|
||||
---@class crd_session
|
||||
local public = {}
|
||||
|
||||
-- get the session ID
|
||||
|
@ -45,12 +45,13 @@ local PERIODICS = {
|
||||
-- PLC supervisor session
|
||||
---@nodiscard
|
||||
---@param id integer session ID
|
||||
---@param s_addr integer device source address
|
||||
---@param reactor_id integer reactor ID
|
||||
---@param in_queue mqueue in message queue
|
||||
---@param out_queue mqueue out message queue
|
||||
---@param timeout number communications timeout
|
||||
---@param fp_ok boolean if the front panel UI is running
|
||||
function plc.new_session(id, reactor_id, in_queue, out_queue, timeout, fp_ok)
|
||||
function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, fp_ok)
|
||||
-- print a log message to the terminal as long as the UI isn't running
|
||||
local function println(message) if not fp_ok then util.println_ts(message) end end
|
||||
|
||||
@ -250,7 +251,7 @@ function plc.new_session(id, reactor_id, in_queue, out_queue, timeout, fp_ok)
|
||||
local r_pkt = comms.rplc_packet()
|
||||
|
||||
r_pkt.make(reactor_id, msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.RPLC, r_pkt.raw_sendable())
|
||||
s_pkt.make(s_addr, self.seq_num, PROTOCOL.RPLC, r_pkt.raw_sendable())
|
||||
|
||||
out_queue.push_packet(s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
@ -264,7 +265,7 @@ function plc.new_session(id, reactor_id, in_queue, out_queue, timeout, fp_ok)
|
||||
local m_pkt = comms.mgmt_packet()
|
||||
|
||||
m_pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
s_pkt.make(s_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
out_queue.push_packet(s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
|
@ -29,11 +29,12 @@ local PERIODICS = {
|
||||
-- pocket diagnostics session
|
||||
---@nodiscard
|
||||
---@param id integer session ID
|
||||
---@param s_addr integer device source address
|
||||
---@param in_queue mqueue in message queue
|
||||
---@param out_queue mqueue out message queue
|
||||
---@param timeout number communications timeout
|
||||
---@param fp_ok boolean if the front panel UI is running
|
||||
function pocket.new_session(id, in_queue, out_queue, timeout, fp_ok)
|
||||
function pocket.new_session(id, s_addr, in_queue, out_queue, timeout, fp_ok)
|
||||
-- print a log message to the terminal as long as the UI isn't running
|
||||
local function println(message) if not fp_ok then util.println_ts(message) end end
|
||||
|
||||
@ -81,7 +82,7 @@ function pocket.new_session(id, in_queue, out_queue, timeout, fp_ok)
|
||||
local m_pkt = comms.mgmt_packet()
|
||||
|
||||
m_pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
s_pkt.make(s_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
out_queue.push_packet(s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
|
@ -31,13 +31,14 @@ local PERIODICS = {
|
||||
-- create a new RTU session
|
||||
---@nodiscard
|
||||
---@param id integer session ID
|
||||
---@param s_addr integer device source address
|
||||
---@param in_queue mqueue in message queue
|
||||
---@param out_queue mqueue out message queue
|
||||
---@param timeout number communications timeout
|
||||
---@param advertisement table RTU device advertisement
|
||||
---@param facility facility facility data table
|
||||
---@param fp_ok boolean if the front panel UI is running
|
||||
function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facility, fp_ok)
|
||||
function rtu.new_session(id, s_addr, in_queue, out_queue, timeout, advertisement, facility, fp_ok)
|
||||
-- print a log message to the terminal as long as the UI isn't running
|
||||
local function println(message) if not fp_ok then util.println_ts(message) end end
|
||||
|
||||
@ -204,7 +205,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
|
||||
local function _send_modbus(m_pkt)
|
||||
local s_pkt = comms.scada_packet()
|
||||
|
||||
s_pkt.make(self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable())
|
||||
s_pkt.make(s_addr, self.seq_num, PROTOCOL.MODBUS_TCP, m_pkt.raw_sendable())
|
||||
|
||||
out_queue.push_packet(s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
@ -218,7 +219,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
|
||||
local m_pkt = comms.mgmt_packet()
|
||||
|
||||
m_pkt.make(msg_type, msg)
|
||||
s_pkt.make(self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
s_pkt.make(s_addr, self.seq_num, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
out_queue.push_packet(s_pkt)
|
||||
self.seq_num = self.seq_num + 1
|
||||
|
@ -27,7 +27,7 @@ local svsessions = {}
|
||||
local SESSION_TYPE = {
|
||||
RTU_SESSION = 0, -- RTU gateway
|
||||
PLC_SESSION = 1, -- reactor PLC
|
||||
COORD_SESSION = 2, -- coordinator
|
||||
CRD_SESSION = 2, -- coordinator
|
||||
PDG_SESSION = 3 -- pocket diagnostics
|
||||
}
|
||||
|
||||
@ -38,11 +38,11 @@ local self = {
|
||||
fp_ok = false,
|
||||
num_reactors = 0,
|
||||
facility = nil, ---@type facility|nil
|
||||
sessions = { rtu = {}, plc = {}, coord = {}, pdg = {} },
|
||||
next_ids = { rtu = 0, plc = 0, coord = 0, pdg = 0 }
|
||||
sessions = { rtu = {}, plc = {}, crd = {}, pdg = {} },
|
||||
next_ids = { rtu = 0, plc = 0, crd = 0, pdg = 0 }
|
||||
}
|
||||
|
||||
---@alias sv_session_structs plc_session_struct|rtu_session_struct|coord_session_struct|pdg_session_struct
|
||||
---@alias sv_session_structs plc_session_struct|rtu_session_struct|crd_session_struct|pdg_session_struct
|
||||
|
||||
-- PRIVATE FUNCTIONS --
|
||||
|
||||
@ -60,7 +60,7 @@ local function _sv_handle_outq(session)
|
||||
if msg ~= nil then
|
||||
if msg.qtype == mqueue.TYPE.PACKET then
|
||||
-- handle a packet to be sent
|
||||
self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable())
|
||||
self.modem.transmit(session.r_chan, config.SVR_CHANNEL, msg.message.raw_sendable())
|
||||
elseif msg.qtype == mqueue.TYPE.COMMAND then
|
||||
-- handle instruction/notification
|
||||
elseif msg.qtype == mqueue.TYPE.DATA then
|
||||
@ -81,11 +81,11 @@ local function _sv_handle_outq(session)
|
||||
elseif cmd.key == SV_Q_DATA.SET_BURN and type(cmd.val) == "table" and #cmd.val == 2 then
|
||||
plc_s.in_queue.push_data(PLC_S_DATA.BURN_RATE, cmd.val[2])
|
||||
else
|
||||
log.debug(util.c("unknown PLC SV queue command ", cmd.key))
|
||||
log.debug(util.c("[SVS] unknown PLC SV queue command ", cmd.key))
|
||||
end
|
||||
end
|
||||
else
|
||||
local crd_s = svsessions.get_coord_session()
|
||||
local crd_s = svsessions.get_crd_session()
|
||||
if crd_s ~= nil then
|
||||
if cmd.key == SV_Q_DATA.CRDN_ACK then
|
||||
-- ack to be sent to coordinator
|
||||
@ -104,8 +104,8 @@ local function _sv_handle_outq(session)
|
||||
|
||||
-- max 100ms spent processing queue
|
||||
if util.time() - handle_start > 100 then
|
||||
log.warning("supervisor out queue handler exceeded 100ms queue process limit")
|
||||
log.warning(util.c("offending session: port ", session.r_port, " type '", session.s_type, "'"))
|
||||
log.warning("[SVS] supervisor out queue handler exceeded 100ms queue process limit")
|
||||
log.warning(util.c("[SVS] offending session: ", session))
|
||||
break
|
||||
end
|
||||
end
|
||||
@ -131,15 +131,15 @@ local function _shutdown(session)
|
||||
session.open = false
|
||||
session.instance.close()
|
||||
|
||||
-- send packets in out queue (namely the close packet)
|
||||
-- send packets in out queue (for the close packet)
|
||||
while session.out_queue.ready() do
|
||||
local msg = session.out_queue.pop()
|
||||
if msg ~= nil and msg.qtype == mqueue.TYPE.PACKET then
|
||||
self.modem.transmit(session.r_port, session.l_port, msg.message.raw_sendable())
|
||||
self.modem.transmit(session.r_chan, config.SVR_CHANNEL, msg.message.raw_sendable())
|
||||
end
|
||||
end
|
||||
|
||||
log.debug(util.c("closed ", session.s_type, " session ", session.instance.get_id(), " on remote port ", session.r_port))
|
||||
log.debug(util.c("[SVS] closed session ", session))
|
||||
end
|
||||
|
||||
-- close connections
|
||||
@ -160,8 +160,7 @@ local function _check_watchdogs(sessions, timer_event)
|
||||
if session.open then
|
||||
local triggered = session.instance.check_wd(timer_event)
|
||||
if triggered then
|
||||
log.debug(util.c("watchdog closing ", session.s_type, " session ", session.instance.get_id(),
|
||||
" on remote port ", session.r_port, "..."))
|
||||
log.debug(util.c("[SVS] watchdog closing session ", session, "..."))
|
||||
_shutdown(session)
|
||||
end
|
||||
end
|
||||
@ -175,21 +174,20 @@ local function _free_closed(sessions)
|
||||
|
||||
---@param session sv_session_structs
|
||||
local on_delete = function (session)
|
||||
log.debug(util.c("free'ing closed ", session.s_type, " session ", session.instance.get_id(),
|
||||
" on remote port ", session.r_port))
|
||||
log.debug(util.c("[SVS] free'ing closed session ", session))
|
||||
end
|
||||
|
||||
util.filter_table(sessions, f, on_delete)
|
||||
end
|
||||
|
||||
-- find a session by remote port
|
||||
-- find a session by computer ID
|
||||
---@nodiscard
|
||||
---@param list table
|
||||
---@param port integer
|
||||
---@param s_addr integer
|
||||
---@return sv_session_structs|nil
|
||||
local function _find_session(list, port)
|
||||
local function _find_session(list, s_addr)
|
||||
for i = 1, #list do
|
||||
if list[i].r_port == port then return list[i] end
|
||||
if list[i].s_addr == s_addr then return list[i] end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
@ -214,63 +212,55 @@ function svsessions.relink_modem(modem)
|
||||
self.modem = modem
|
||||
end
|
||||
|
||||
-- find an RTU session by the remote port
|
||||
-- find an RTU session by the computer ID
|
||||
---@nodiscard
|
||||
---@param remote_port integer
|
||||
---@param source_addr integer
|
||||
---@return rtu_session_struct|nil
|
||||
function svsessions.find_rtu_session(remote_port)
|
||||
function svsessions.find_rtu_session(source_addr)
|
||||
-- check RTU sessions
|
||||
local session = _find_session(self.sessions.rtu, remote_port)
|
||||
local session = _find_session(self.sessions.rtu, source_addr)
|
||||
---@cast session rtu_session_struct|nil
|
||||
return session
|
||||
end
|
||||
|
||||
-- find a PLC session by the remote port
|
||||
-- find a PLC session by the computer ID
|
||||
---@nodiscard
|
||||
---@param remote_port integer
|
||||
---@param source_addr integer
|
||||
---@return plc_session_struct|nil
|
||||
function svsessions.find_plc_session(remote_port)
|
||||
function svsessions.find_plc_session(source_addr)
|
||||
-- check PLC sessions
|
||||
local session = _find_session(self.sessions.plc, remote_port)
|
||||
local session = _find_session(self.sessions.plc, source_addr)
|
||||
---@cast session plc_session_struct|nil
|
||||
return session
|
||||
end
|
||||
|
||||
-- find a PLC/RTU session by the remote port
|
||||
-- find a coordinator session by the computer ID
|
||||
---@nodiscard
|
||||
---@param remote_port integer
|
||||
---@return plc_session_struct|rtu_session_struct|nil
|
||||
function svsessions.find_device_session(remote_port)
|
||||
-- check RTU sessions
|
||||
local session = _find_session(self.sessions.rtu, remote_port)
|
||||
|
||||
-- check PLC sessions
|
||||
if session == nil then session = _find_session(self.sessions.plc, remote_port) end
|
||||
---@cast session plc_session_struct|rtu_session_struct|nil
|
||||
|
||||
---@param source_addr integer
|
||||
---@return crd_session_struct|nil
|
||||
function svsessions.find_crd_session(source_addr)
|
||||
-- check coordinator sessions
|
||||
local session = _find_session(self.sessions.crd, source_addr)
|
||||
---@cast session crd_session_struct|nil
|
||||
return session
|
||||
end
|
||||
|
||||
-- find a coordinator or diagnostic access session by the remote port
|
||||
-- find a pocket diagnostics session by the computer ID
|
||||
---@nodiscard
|
||||
---@param remote_port integer
|
||||
---@return coord_session_struct|pdg_session_struct|nil
|
||||
function svsessions.find_svctl_session(remote_port)
|
||||
-- check coordinator sessions
|
||||
local session = _find_session(self.sessions.coord, remote_port)
|
||||
|
||||
---@param source_addr integer
|
||||
---@return pdg_session_struct|nil
|
||||
function svsessions.find_pdg_session(source_addr)
|
||||
-- check diagnostic sessions
|
||||
if session == nil then session = _find_session(self.sessions.pdg, remote_port) end
|
||||
---@cast session coord_session_struct|pdg_session_struct|nil
|
||||
|
||||
local session = _find_session(self.sessions.pdg, source_addr)
|
||||
---@cast session pdg_session_struct|nil
|
||||
return session
|
||||
end
|
||||
|
||||
-- get the a coordinator session if exists
|
||||
---@nodiscard
|
||||
---@return coord_session_struct|nil
|
||||
function svsessions.get_coord_session()
|
||||
return self.sessions.coord[1]
|
||||
---@return crd_session_struct|nil
|
||||
function svsessions.get_crd_session()
|
||||
return self.sessions.crd[1]
|
||||
end
|
||||
|
||||
-- get a session by reactor ID
|
||||
@ -291,12 +281,11 @@ end
|
||||
|
||||
-- establish a new PLC session
|
||||
---@nodiscard
|
||||
---@param local_port integer
|
||||
---@param remote_port integer
|
||||
---@param source_addr integer
|
||||
---@param for_reactor integer
|
||||
---@param version string
|
||||
---@return integer|false session_id
|
||||
function svsessions.establish_plc_session(local_port, remote_port, for_reactor, version)
|
||||
function svsessions.establish_plc_session(source_addr, for_reactor, version)
|
||||
if svsessions.get_reactor_session(for_reactor) == nil and for_reactor >= 1 and for_reactor <= self.num_reactors then
|
||||
---@class plc_session_struct
|
||||
local plc_s = {
|
||||
@ -304,26 +293,34 @@ function svsessions.establish_plc_session(local_port, remote_port, for_reactor,
|
||||
open = true,
|
||||
reactor = for_reactor,
|
||||
version = version,
|
||||
l_port = local_port,
|
||||
r_port = remote_port,
|
||||
r_chan = config.PLC_CHANNEL,
|
||||
s_addr = source_addr,
|
||||
in_queue = mqueue.new(),
|
||||
out_queue = mqueue.new(),
|
||||
instance = nil ---@type plc_session
|
||||
}
|
||||
|
||||
plc_s.instance = plc.new_session(self.next_ids.plc, for_reactor, plc_s.in_queue, plc_s.out_queue,
|
||||
config.PLC_TIMEOUT, self.fp_ok)
|
||||
local id = self.next_ids.plc
|
||||
|
||||
plc_s.instance = plc.new_session(id, source_addr, for_reactor, plc_s.in_queue, plc_s.out_queue,
|
||||
config.PLC_TIMEOUT, self.fp_ok)
|
||||
table.insert(self.sessions.plc, plc_s)
|
||||
|
||||
local units = self.facility.get_units()
|
||||
units[for_reactor].link_plc_session(plc_s)
|
||||
|
||||
log.debug(util.c("established new PLC session to ", remote_port, " with ID ", self.next_ids.plc,
|
||||
" for reactor ", for_reactor))
|
||||
local mt = {
|
||||
---@param s plc_session_struct
|
||||
__tostring = function (s) return util.c("PLC [", s.instance.get_id(), "] for reactor #", s.reactor,
|
||||
" (@", s.s_addr, ")") end
|
||||
}
|
||||
|
||||
databus.tx_plc_connected(for_reactor, version, remote_port)
|
||||
setmetatable(plc_s, mt)
|
||||
|
||||
self.next_ids.plc = self.next_ids.plc + 1
|
||||
databus.tx_plc_connected(for_reactor, version, source_addr)
|
||||
log.debug(util.c("[SVS] established new session: ", plc_s))
|
||||
|
||||
self.next_ids.plc = id + 1
|
||||
|
||||
-- success
|
||||
return plc_s.instance.get_id()
|
||||
@ -335,70 +332,84 @@ end
|
||||
|
||||
-- establish a new RTU session
|
||||
---@nodiscard
|
||||
---@param local_port integer
|
||||
---@param remote_port integer
|
||||
---@param source_addr integer
|
||||
---@param advertisement table
|
||||
---@param version string
|
||||
---@return integer session_id
|
||||
function svsessions.establish_rtu_session(local_port, remote_port, advertisement, version)
|
||||
function svsessions.establish_rtu_session(source_addr, advertisement, version)
|
||||
---@class rtu_session_struct
|
||||
local rtu_s = {
|
||||
s_type = "rtu",
|
||||
open = true,
|
||||
version = version,
|
||||
l_port = local_port,
|
||||
r_port = remote_port,
|
||||
r_chan = config.RTU_CHANNEL,
|
||||
s_addr = source_addr,
|
||||
in_queue = mqueue.new(),
|
||||
out_queue = mqueue.new(),
|
||||
instance = nil ---@type rtu_session
|
||||
}
|
||||
|
||||
rtu_s.instance = rtu.new_session(self.next_ids.rtu, rtu_s.in_queue, rtu_s.out_queue, config.RTU_TIMEOUT, advertisement,
|
||||
self.facility, self.fp_ok)
|
||||
local id = self.next_ids.rtu
|
||||
|
||||
rtu_s.instance = rtu.new_session(id, source_addr, rtu_s.in_queue, rtu_s.out_queue, config.RTU_TIMEOUT,
|
||||
advertisement, self.facility, self.fp_ok)
|
||||
table.insert(self.sessions.rtu, rtu_s)
|
||||
|
||||
log.debug("established new RTU session to " .. remote_port .. " with ID " .. self.next_ids.rtu)
|
||||
local mt = {
|
||||
---@param s rtu_session_struct
|
||||
__tostring = function (s) return util.c("RTU [", s.instance.get_id(), "] (@", s.s_addr, ")") end
|
||||
}
|
||||
|
||||
databus.tx_rtu_connected(self.next_ids.rtu, version, remote_port)
|
||||
setmetatable(rtu_s, mt)
|
||||
|
||||
self.next_ids.rtu = self.next_ids.rtu + 1
|
||||
databus.tx_rtu_connected(id, version, source_addr)
|
||||
log.debug(util.c("[SVS] established new session: ", rtu_s))
|
||||
|
||||
self.next_ids.rtu = id + 1
|
||||
|
||||
-- success
|
||||
return rtu_s.instance.get_id()
|
||||
return id
|
||||
end
|
||||
|
||||
-- establish a new coordinator session
|
||||
---@nodiscard
|
||||
---@param local_port integer
|
||||
---@param remote_port integer
|
||||
---@param source_addr integer
|
||||
---@param version string
|
||||
---@return integer|false session_id
|
||||
function svsessions.establish_coord_session(local_port, remote_port, version)
|
||||
if svsessions.get_coord_session() == nil then
|
||||
---@class coord_session_struct
|
||||
local coord_s = {
|
||||
function svsessions.establish_crd_session(source_addr, version)
|
||||
if svsessions.get_crd_session() == nil then
|
||||
---@class crd_session_struct
|
||||
local crd_s = {
|
||||
s_type = "crd",
|
||||
open = true,
|
||||
version = version,
|
||||
l_port = local_port,
|
||||
r_port = remote_port,
|
||||
r_chan = config.CRD_CHANNEL,
|
||||
s_addr = source_addr,
|
||||
in_queue = mqueue.new(),
|
||||
out_queue = mqueue.new(),
|
||||
instance = nil ---@type coord_session
|
||||
instance = nil ---@type crd_session
|
||||
}
|
||||
|
||||
coord_s.instance = coordinator.new_session(self.next_ids.coord, coord_s.in_queue, coord_s.out_queue, config.CRD_TIMEOUT,
|
||||
local id = self.next_ids.crd
|
||||
|
||||
crd_s.instance = coordinator.new_session(id, source_addr, crd_s.in_queue, crd_s.out_queue, config.CRD_TIMEOUT,
|
||||
self.facility, self.fp_ok)
|
||||
table.insert(self.sessions.coord, coord_s)
|
||||
table.insert(self.sessions.crd, crd_s)
|
||||
|
||||
log.debug("established new coordinator session to " .. remote_port .. " with ID " .. self.next_ids.coord)
|
||||
local mt = {
|
||||
---@param s crd_session_struct
|
||||
__tostring = function (s) return util.c("CRD [", s.instance.get_id(), "] (@", s.s_addr, ")") end
|
||||
}
|
||||
|
||||
databus.tx_crd_connected(version, remote_port)
|
||||
setmetatable(crd_s, mt)
|
||||
|
||||
self.next_ids.coord = self.next_ids.coord + 1
|
||||
databus.tx_crd_connected(version, source_addr)
|
||||
log.debug(util.c("[SVS] established new session: ", crd_s))
|
||||
|
||||
self.next_ids.crd = id + 1
|
||||
|
||||
-- success
|
||||
return coord_s.instance.get_id()
|
||||
return id
|
||||
else
|
||||
-- we already have a coordinator linked
|
||||
return false
|
||||
@ -407,34 +418,41 @@ end
|
||||
|
||||
-- establish a new pocket diagnostics session
|
||||
---@nodiscard
|
||||
---@param local_port integer
|
||||
---@param remote_port integer
|
||||
---@param source_addr integer
|
||||
---@param version string
|
||||
---@return integer|false session_id
|
||||
function svsessions.establish_pdg_session(local_port, remote_port, version)
|
||||
function svsessions.establish_pdg_session(source_addr, version)
|
||||
---@class pdg_session_struct
|
||||
local pdg_s = {
|
||||
s_type = "pkt",
|
||||
open = true,
|
||||
version = version,
|
||||
l_port = local_port,
|
||||
r_port = remote_port,
|
||||
r_chan = config.PKT_CHANNEL,
|
||||
s_addr = source_addr,
|
||||
in_queue = mqueue.new(),
|
||||
out_queue = mqueue.new(),
|
||||
instance = nil ---@type pdg_session
|
||||
}
|
||||
|
||||
pdg_s.instance = pocket.new_session(self.next_ids.pdg, pdg_s.in_queue, pdg_s.out_queue, config.PKT_TIMEOUT, self.fp_ok)
|
||||
local id = self.next_ids.pdg
|
||||
|
||||
pdg_s.instance = pocket.new_session(id, source_addr, pdg_s.in_queue, pdg_s.out_queue, config.PKT_TIMEOUT, self.fp_ok)
|
||||
table.insert(self.sessions.pdg, pdg_s)
|
||||
|
||||
log.debug("established new pocket diagnostics session to " .. remote_port .. " with ID " .. self.next_ids.pdg)
|
||||
local mt = {
|
||||
---@param s pdg_session_struct
|
||||
__tostring = function (s) return util.c("PDG [", s.instance.get_id(), "] (@", s.s_addr, ")") end
|
||||
}
|
||||
|
||||
databus.tx_pdg_connected(self.next_ids.pdg, version, remote_port)
|
||||
setmetatable(pdg_s, mt)
|
||||
|
||||
self.next_ids.pdg = self.next_ids.pdg + 1
|
||||
databus.tx_pdg_connected(id, version, source_addr)
|
||||
log.debug(util.c("[SVS] established new session: ", pdg_s))
|
||||
|
||||
self.next_ids.pdg = id + 1
|
||||
|
||||
-- success
|
||||
return pdg_s.instance.get_id()
|
||||
return id
|
||||
end
|
||||
|
||||
-- attempt to identify which session's watchdog timer fired
|
||||
@ -466,9 +484,7 @@ end
|
||||
-- close all open connections
|
||||
function svsessions.close_all()
|
||||
-- close sessions
|
||||
for _, list in pairs(self.sessions) do
|
||||
_close(list)
|
||||
end
|
||||
for _, list in pairs(self.sessions) do _close(list) end
|
||||
|
||||
-- free sessions
|
||||
svsessions.free_all_closed()
|
||||
|
@ -20,7 +20,7 @@ local supervisor = require("supervisor.supervisor")
|
||||
|
||||
local svsessions = require("supervisor.session.svsessions")
|
||||
|
||||
local SUPERVISOR_VERSION = "v0.16.8"
|
||||
local SUPERVISOR_VERSION = "v0.17.5"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@ -31,8 +31,11 @@ local println_ts = util.println_ts
|
||||
|
||||
local cfv = util.new_validator()
|
||||
|
||||
cfv.assert_port(config.SCADA_DEV_LISTEN)
|
||||
cfv.assert_port(config.SCADA_SV_CTL_LISTEN)
|
||||
cfv.assert_channel(config.SVR_CHANNEL)
|
||||
cfv.assert_channel(config.PLC_CHANNEL)
|
||||
cfv.assert_channel(config.RTU_CHANNEL)
|
||||
cfv.assert_channel(config.CRD_CHANNEL)
|
||||
cfv.assert_channel(config.PKT_CHANNEL)
|
||||
cfv.assert_type_int(config.TRUSTED_RANGE)
|
||||
cfv.assert_type_num(config.PLC_TIMEOUT)
|
||||
cfv.assert_min(config.PLC_TIMEOUT, 2)
|
||||
@ -112,9 +115,8 @@ local function main()
|
||||
println_ts = function (_) end
|
||||
end
|
||||
|
||||
-- start comms, open all channels
|
||||
local superv_comms = supervisor.comms(SUPERVISOR_VERSION, config.NUM_REACTORS, config.REACTOR_COOLING, modem,
|
||||
config.SCADA_DEV_LISTEN, config.SCADA_SV_CTL_LISTEN, config.TRUSTED_RANGE, fp_ok)
|
||||
-- start comms
|
||||
local superv_comms = supervisor.comms(SUPERVISOR_VERSION, modem, fp_ok)
|
||||
|
||||
-- base loop clock (6.67Hz, 3 ticks)
|
||||
local MAIN_CLOCK = 0.15
|
||||
|
@ -2,6 +2,8 @@ local comms = require("scada-common.comms")
|
||||
local log = require("scada-common.log")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local config = require("supervisor.config")
|
||||
|
||||
local svsessions = require("supervisor.session.svsessions")
|
||||
|
||||
local supervisor = {}
|
||||
@ -14,31 +16,36 @@ local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
|
||||
-- supervisory controller communications
|
||||
---@nodiscard
|
||||
---@param _version string supervisor version
|
||||
---@param num_reactors integer number of reactors
|
||||
---@param cooling_conf table cooling configuration table
|
||||
---@param modem table modem device
|
||||
---@param dev_listen integer listening port for PLC/RTU devices
|
||||
---@param svctl_listen integer listening port for supervisor access
|
||||
---@param range integer trusted device connection range
|
||||
---@param fp_ok boolean if the front panel UI is running
|
||||
---@diagnostic disable-next-line: unused-local
|
||||
function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_listen, svctl_listen, range, fp_ok)
|
||||
function supervisor.comms(_version, modem, fp_ok)
|
||||
-- print a log message to the terminal as long as the UI isn't running
|
||||
local function println(message) if not fp_ok then util.println_ts(message) end end
|
||||
|
||||
-- channel list from config
|
||||
local svr_channel = config.SVR_CHANNEL
|
||||
local plc_channel = config.PLC_CHANNEL
|
||||
local rtu_channel = config.RTU_CHANNEL
|
||||
local crd_channel = config.CRD_CHANNEL
|
||||
local pkt_channel = config.PKT_CHANNEL
|
||||
|
||||
-- configuration data
|
||||
local num_reactors = config.NUM_REACTORS
|
||||
local cooling_conf = config.REACTOR_COOLING
|
||||
|
||||
local self = {
|
||||
last_est_acks = {}
|
||||
}
|
||||
|
||||
comms.set_trusted_range(range)
|
||||
comms.set_trusted_range(config.TRUSTED_RANGE)
|
||||
|
||||
-- PRIVATE FUNCTIONS --
|
||||
|
||||
-- configure modem channels
|
||||
local function _conf_channels()
|
||||
modem.closeAll()
|
||||
modem.open(dev_listen)
|
||||
modem.open(svctl_listen)
|
||||
modem.open(svr_channel)
|
||||
end
|
||||
|
||||
_conf_channels()
|
||||
@ -46,31 +53,19 @@ function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_liste
|
||||
-- pass modem, status, and config data to svsessions
|
||||
svsessions.init(modem, fp_ok, num_reactors, cooling_conf)
|
||||
|
||||
-- send an establish request response to a PLC/RTU
|
||||
---@param dest integer
|
||||
---@param msg table
|
||||
local function _send_dev_establish(seq_id, dest, msg)
|
||||
-- send an establish request response
|
||||
---@param packet scada_packet
|
||||
---@param ack ESTABLISH_ACK
|
||||
---@param data? any optional data
|
||||
local function _send_establish(packet, ack, data)
|
||||
local s_pkt = comms.scada_packet()
|
||||
local m_pkt = comms.mgmt_packet()
|
||||
|
||||
m_pkt.make(SCADA_MGMT_TYPE.ESTABLISH, msg)
|
||||
s_pkt.make(seq_id, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
m_pkt.make(SCADA_MGMT_TYPE.ESTABLISH, { ack, data })
|
||||
s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||
|
||||
modem.transmit(dest, dev_listen, s_pkt.raw_sendable())
|
||||
end
|
||||
|
||||
-- send supervisor control access connection establish response
|
||||
---@param seq_id integer
|
||||
---@param dest integer
|
||||
---@param msg table
|
||||
local function _send_svctl_establish(seq_id, dest, msg)
|
||||
local s_pkt = comms.scada_packet()
|
||||
local c_pkt = comms.mgmt_packet()
|
||||
|
||||
c_pkt.make(SCADA_MGMT_TYPE.ESTABLISH, msg)
|
||||
s_pkt.make(seq_id, PROTOCOL.SCADA_MGMT, c_pkt.raw_sendable())
|
||||
|
||||
modem.transmit(dest, svctl_listen, s_pkt.raw_sendable())
|
||||
modem.transmit(packet.remote_channel(), svr_channel, s_pkt.raw_sendable())
|
||||
self.last_est_acks[packet.src_addr()] = ack
|
||||
end
|
||||
|
||||
-- PUBLIC FUNCTIONS --
|
||||
@ -138,17 +133,94 @@ function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_liste
|
||||
---@param packet modbus_frame|rplc_frame|mgmt_frame|crdn_frame|nil
|
||||
function public.handle_packet(packet)
|
||||
if packet ~= nil then
|
||||
local l_port = packet.scada_frame.local_port()
|
||||
local r_port = packet.scada_frame.remote_port()
|
||||
local l_chan = packet.scada_frame.local_channel()
|
||||
local r_chan = packet.scada_frame.remote_channel()
|
||||
local src_addr = packet.scada_frame.src_addr()
|
||||
local protocol = packet.scada_frame.protocol()
|
||||
|
||||
-- device (RTU/PLC) listening channel
|
||||
if l_port == dev_listen then
|
||||
if l_chan ~= svr_channel then
|
||||
log.debug("received packet on unconfigured channel " .. l_chan, true)
|
||||
elseif r_chan == plc_channel then
|
||||
-- look for an associated session
|
||||
local session = svsessions.find_plc_session(src_addr)
|
||||
|
||||
if protocol == PROTOCOL.RPLC then
|
||||
---@cast packet rplc_frame
|
||||
-- reactor PLC packet
|
||||
if session ~= nil then
|
||||
-- pass the packet onto the session handler
|
||||
session.in_queue.push_packet(packet)
|
||||
else
|
||||
-- unknown session, force a re-link
|
||||
log.debug("PLC_ESTABLISH: no session but not an establish, forcing relink")
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||
end
|
||||
elseif protocol == PROTOCOL.SCADA_MGMT then
|
||||
---@cast packet mgmt_frame
|
||||
-- SCADA management packet
|
||||
if session ~= nil then
|
||||
-- pass the packet onto the session handler
|
||||
session.in_queue.push_packet(packet)
|
||||
elseif packet.type == SCADA_MGMT_TYPE.ESTABLISH then
|
||||
-- establish a new session
|
||||
local last_ack = self.last_est_acks[src_addr]
|
||||
|
||||
-- validate packet and continue
|
||||
if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then
|
||||
local comms_v = packet.data[1]
|
||||
local firmware_v = packet.data[2]
|
||||
local dev_type = packet.data[3]
|
||||
|
||||
if comms_v ~= comms.version then
|
||||
if last_ack ~= ESTABLISH_ACK.BAD_VERSION then
|
||||
log.info(util.c("dropping PLC establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
|
||||
end
|
||||
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
||||
elseif dev_type == DEVICE_TYPE.PLC then
|
||||
-- PLC linking request
|
||||
if packet.length == 4 and type(packet.data[4]) == "number" then
|
||||
local reactor_id = packet.data[4]
|
||||
local plc_id = svsessions.establish_plc_session(src_addr, reactor_id, firmware_v)
|
||||
|
||||
if plc_id == false then
|
||||
-- reactor already has a PLC assigned
|
||||
if last_ack ~= ESTABLISH_ACK.COLLISION then
|
||||
log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id))
|
||||
end
|
||||
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION)
|
||||
else
|
||||
-- got an ID; assigned to a reactor successfully
|
||||
println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected"))
|
||||
log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id))
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
||||
end
|
||||
else
|
||||
log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type")
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||
end
|
||||
else
|
||||
log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC channel"))
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||
end
|
||||
else
|
||||
log.debug("invalid establish packet (on PLC channel)")
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||
end
|
||||
else
|
||||
-- any other packet should be session related, discard it
|
||||
log.debug(util.c("discarding PLC SCADA_MGMT packet without a known session from computer ", src_addr))
|
||||
end
|
||||
else
|
||||
log.debug(util.c("illegal packet type ", protocol, " on PLC channel"))
|
||||
end
|
||||
elseif r_chan == rtu_channel then
|
||||
-- look for an associated session
|
||||
local session = svsessions.find_rtu_session(src_addr)
|
||||
|
||||
if protocol == PROTOCOL.MODBUS_TCP then
|
||||
---@cast packet modbus_frame
|
||||
-- look for an associated session
|
||||
local session = svsessions.find_rtu_session(r_port)
|
||||
|
||||
-- MODBUS response
|
||||
if session ~= nil then
|
||||
-- pass the packet onto the session handler
|
||||
@ -157,105 +229,59 @@ function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_liste
|
||||
-- any other packet should be session related, discard it
|
||||
log.debug("discarding MODBUS_TCP packet without a known session")
|
||||
end
|
||||
elseif protocol == PROTOCOL.RPLC then
|
||||
---@cast packet rplc_frame
|
||||
-- look for an associated session
|
||||
local session = svsessions.find_plc_session(r_port)
|
||||
|
||||
-- reactor PLC packet
|
||||
if session ~= nil then
|
||||
-- pass the packet onto the session handler
|
||||
session.in_queue.push_packet(packet)
|
||||
else
|
||||
-- unknown session, force a re-link
|
||||
log.debug("PLC_ESTABLISH: no session but not an establish, forcing relink")
|
||||
_send_dev_establish(packet.scada_frame.seq_num() + 1, r_port, { ESTABLISH_ACK.DENY })
|
||||
end
|
||||
elseif protocol == PROTOCOL.SCADA_MGMT then
|
||||
---@cast packet mgmt_frame
|
||||
-- look for an associated session
|
||||
local session = svsessions.find_device_session(r_port)
|
||||
|
||||
-- SCADA management packet
|
||||
if session ~= nil then
|
||||
-- pass the packet onto the session handler
|
||||
session.in_queue.push_packet(packet)
|
||||
elseif packet.type == SCADA_MGMT_TYPE.ESTABLISH then
|
||||
-- establish a new session
|
||||
local next_seq_id = packet.scada_frame.seq_num() + 1
|
||||
local last_ack = self.last_est_acks[src_addr]
|
||||
|
||||
-- validate packet and continue
|
||||
if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then
|
||||
local comms_v = packet.data[1]
|
||||
local comms_v = packet.data[1]
|
||||
local firmware_v = packet.data[2]
|
||||
local dev_type = packet.data[3]
|
||||
local dev_type = packet.data[3]
|
||||
|
||||
if comms_v ~= comms.version then
|
||||
if self.last_est_acks[r_port] ~= ESTABLISH_ACK.BAD_VERSION then
|
||||
log.info(util.c("dropping device establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
|
||||
self.last_est_acks[r_port] = ESTABLISH_ACK.BAD_VERSION
|
||||
if last_ack ~= ESTABLISH_ACK.BAD_VERSION then
|
||||
log.info(util.c("dropping RTU establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
|
||||
end
|
||||
|
||||
_send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.BAD_VERSION })
|
||||
elseif dev_type == DEVICE_TYPE.PLC then
|
||||
-- PLC linking request
|
||||
if packet.length == 4 and type(packet.data[4]) == "number" then
|
||||
local reactor_id = packet.data[4]
|
||||
local plc_id = svsessions.establish_plc_session(l_port, r_port, reactor_id, firmware_v)
|
||||
|
||||
if plc_id == false then
|
||||
-- reactor already has a PLC assigned
|
||||
if self.last_est_acks[r_port] ~= ESTABLISH_ACK.COLLISION then
|
||||
log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id))
|
||||
self.last_est_acks[r_port] = ESTABLISH_ACK.COLLISION
|
||||
end
|
||||
|
||||
_send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.COLLISION })
|
||||
else
|
||||
-- got an ID; assigned to a reactor successfully
|
||||
println(util.c("PLC (", firmware_v, ") [:", r_port, "] \xbb reactor ", reactor_id, " connected"))
|
||||
log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [:", r_port, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id))
|
||||
|
||||
_send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW })
|
||||
self.last_est_acks[r_port] = ESTABLISH_ACK.ALLOW
|
||||
end
|
||||
else
|
||||
log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type")
|
||||
_send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY })
|
||||
end
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
||||
elseif dev_type == DEVICE_TYPE.RTU then
|
||||
if packet.length == 4 then
|
||||
-- this is an RTU advertisement for a new session
|
||||
local rtu_advert = packet.data[4]
|
||||
local s_id = svsessions.establish_rtu_session(l_port, r_port, rtu_advert, firmware_v)
|
||||
local s_id = svsessions.establish_rtu_session(src_addr, rtu_advert, firmware_v)
|
||||
|
||||
println(util.c("RTU (", firmware_v, ") [:", r_port, "] \xbb connected"))
|
||||
log.info(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [:", r_port, "] connected with session ID ", s_id))
|
||||
|
||||
_send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW })
|
||||
println(util.c("RTU (", firmware_v, ") [@", src_addr, "] \xbb connected"))
|
||||
log.info(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [@", src_addr, "] connected with session ID ", s_id))
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
||||
else
|
||||
log.debug("RTU_ESTABLISH: packet length mismatch")
|
||||
_send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY })
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||
end
|
||||
else
|
||||
log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC/RTU listening channel"))
|
||||
_send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY })
|
||||
log.debug(util.c("illegal establish packet for device ", dev_type, " on RTU channel"))
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||
end
|
||||
else
|
||||
log.debug("invalid establish packet (on PLC/RTU listening channel)")
|
||||
_send_dev_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY })
|
||||
log.debug("invalid establish packet (on RTU channel)")
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||
end
|
||||
else
|
||||
-- any other packet should be session related, discard it
|
||||
log.debug(util.c(r_port, "->", l_port, ": discarding SCADA_MGMT packet without a known session"))
|
||||
log.debug(util.c("discarding RTU SCADA_MGMT packet without a known session from computer ", src_addr))
|
||||
end
|
||||
else
|
||||
log.debug("illegal packet type " .. protocol .. " on device listening channel")
|
||||
log.debug(util.c("illegal packet type ", protocol, " on RTU channel"))
|
||||
end
|
||||
-- coordinator listening channel
|
||||
elseif l_port == svctl_listen then
|
||||
elseif r_chan == crd_channel then
|
||||
-- look for an associated session
|
||||
local session = svsessions.find_svctl_session(r_port)
|
||||
local session = svsessions.find_crd_session(src_addr)
|
||||
|
||||
if protocol == PROTOCOL.SCADA_MGMT then
|
||||
---@cast packet mgmt_frame
|
||||
@ -265,65 +291,53 @@ function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_liste
|
||||
session.in_queue.push_packet(packet)
|
||||
elseif packet.type == SCADA_MGMT_TYPE.ESTABLISH then
|
||||
-- establish a new session
|
||||
local next_seq_id = packet.scada_frame.seq_num() + 1
|
||||
local last_ack = self.last_est_acks[src_addr]
|
||||
|
||||
-- validate packet and continue
|
||||
if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then
|
||||
local comms_v = packet.data[1]
|
||||
local comms_v = packet.data[1]
|
||||
local firmware_v = packet.data[2]
|
||||
local dev_type = packet.data[3]
|
||||
local dev_type = packet.data[3]
|
||||
|
||||
if comms_v ~= comms.version then
|
||||
if self.last_est_acks[r_port] ~= ESTABLISH_ACK.BAD_VERSION then
|
||||
if last_ack ~= ESTABLISH_ACK.BAD_VERSION then
|
||||
log.info(util.c("dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
|
||||
self.last_est_acks[r_port] = ESTABLISH_ACK.BAD_VERSION
|
||||
end
|
||||
|
||||
_send_svctl_establish(next_seq_id, r_port, { ESTABLISH_ACK.BAD_VERSION })
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
||||
elseif dev_type == DEVICE_TYPE.CRDN then
|
||||
-- this is an attempt to establish a new coordinator session
|
||||
local s_id = svsessions.establish_coord_session(l_port, r_port, firmware_v)
|
||||
local s_id = svsessions.establish_crd_session(src_addr, firmware_v)
|
||||
|
||||
if s_id ~= false then
|
||||
local config = { num_reactors }
|
||||
local cfg = { num_reactors }
|
||||
for i = 1, #cooling_conf do
|
||||
table.insert(config, cooling_conf[i].BOILERS)
|
||||
table.insert(config, cooling_conf[i].TURBINES)
|
||||
table.insert(cfg, cooling_conf[i].BOILERS)
|
||||
table.insert(cfg, cooling_conf[i].TURBINES)
|
||||
end
|
||||
|
||||
println(util.c("CRD (", firmware_v, ") [:", r_port, "] \xbb connected"))
|
||||
log.info(util.c("SVCTL_ESTABLISH: coordinator (", firmware_v, ") [:", r_port, "] connected with session ID ", s_id))
|
||||
println(util.c("CRD (", firmware_v, ") [@", src_addr, "] \xbb connected"))
|
||||
log.info(util.c("CRD_ESTABLISH: coordinator (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id))
|
||||
|
||||
_send_svctl_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW, config })
|
||||
self.last_est_acks[r_port] = ESTABLISH_ACK.ALLOW
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW, cfg)
|
||||
else
|
||||
if self.last_est_acks[r_port] ~= ESTABLISH_ACK.COLLISION then
|
||||
log.info("SVCTL_ESTABLISH: denied new coordinator due to already being connected to another coordinator")
|
||||
self.last_est_acks[r_port] = ESTABLISH_ACK.COLLISION
|
||||
if last_ack ~= ESTABLISH_ACK.COLLISION then
|
||||
log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator")
|
||||
end
|
||||
|
||||
_send_svctl_establish(next_seq_id, r_port, { ESTABLISH_ACK.COLLISION })
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION)
|
||||
end
|
||||
elseif dev_type == DEVICE_TYPE.PKT then
|
||||
-- this is an attempt to establish a new pocket diagnostic session
|
||||
local s_id = svsessions.establish_pdg_session(l_port, r_port, firmware_v)
|
||||
|
||||
println(util.c("PKT (", firmware_v, ") [:", r_port, "] \xbb connected"))
|
||||
log.info(util.c("SVCTL_ESTABLISH: pocket (", firmware_v, ") [:", r_port, "] connected with session ID ", s_id))
|
||||
|
||||
_send_svctl_establish(next_seq_id, r_port, { ESTABLISH_ACK.ALLOW })
|
||||
self.last_est_acks[r_port] = ESTABLISH_ACK.ALLOW
|
||||
else
|
||||
log.debug(util.c("illegal establish packet for device ", dev_type, " on SVCTL listening channel"))
|
||||
_send_svctl_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY })
|
||||
log.debug(util.c("illegal establish packet for device ", dev_type, " on coordinator channel"))
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||
end
|
||||
else
|
||||
log.debug("SVCTL_ESTABLISH: establish packet length mismatch")
|
||||
_send_svctl_establish(next_seq_id, r_port, { ESTABLISH_ACK.DENY })
|
||||
log.debug("CRD_ESTABLISH: establish packet length mismatch")
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||
end
|
||||
else
|
||||
-- any other packet should be session related, discard it
|
||||
log.debug(r_port .. "->" .. l_port .. ": discarding SCADA_MGMT packet without a known session")
|
||||
log.debug(util.c("discarding coordinator SCADA_MGMT packet without a known session from computer ", src_addr))
|
||||
end
|
||||
elseif protocol == PROTOCOL.SCADA_CRDN then
|
||||
---@cast packet crdn_frame
|
||||
@ -333,13 +347,72 @@ function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_liste
|
||||
session.in_queue.push_packet(packet)
|
||||
else
|
||||
-- any other packet should be session related, discard it
|
||||
log.debug(r_port .. "->" .. l_port .. ": discarding SCADA_CRDN packet without a known session")
|
||||
log.debug(util.c("discarding coordinator SCADA_CRDN packet without a known session from computer ", src_addr))
|
||||
end
|
||||
else
|
||||
log.debug("illegal packet type " .. protocol .. " on coordinator listening channel")
|
||||
log.debug(util.c("illegal packet type ", protocol, " on coordinator channel"))
|
||||
end
|
||||
elseif r_chan == pkt_channel then
|
||||
-- look for an associated session
|
||||
local session = svsessions.find_pdg_session(src_addr)
|
||||
|
||||
if protocol == PROTOCOL.SCADA_MGMT then
|
||||
---@cast packet mgmt_frame
|
||||
-- SCADA management packet
|
||||
if session ~= nil then
|
||||
-- pass the packet onto the session handler
|
||||
session.in_queue.push_packet(packet)
|
||||
elseif packet.type == SCADA_MGMT_TYPE.ESTABLISH then
|
||||
-- establish a new session
|
||||
local last_ack = self.last_est_acks[src_addr]
|
||||
|
||||
-- validate packet and continue
|
||||
if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then
|
||||
local comms_v = packet.data[1]
|
||||
local firmware_v = packet.data[2]
|
||||
local dev_type = packet.data[3]
|
||||
|
||||
if comms_v ~= comms.version then
|
||||
if last_ack ~= ESTABLISH_ACK.BAD_VERSION then
|
||||
log.info(util.c("dropping PDG establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")"))
|
||||
end
|
||||
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
||||
elseif dev_type == DEVICE_TYPE.PKT then
|
||||
-- this is an attempt to establish a new pocket diagnostic session
|
||||
local s_id = svsessions.establish_pdg_session(src_addr, firmware_v)
|
||||
|
||||
println(util.c("PKT (", firmware_v, ") [@", src_addr, "] \xbb connected"))
|
||||
log.info(util.c("PDG_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id))
|
||||
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
||||
else
|
||||
log.debug(util.c("illegal establish packet for device ", dev_type, " on pocket channel"))
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||
end
|
||||
else
|
||||
log.debug("PDG_ESTABLISH: establish packet length mismatch")
|
||||
_send_establish(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||
end
|
||||
else
|
||||
-- any other packet should be session related, discard it
|
||||
log.debug(util.c("discarding pocket SCADA_MGMT packet without a known session from computer ", src_addr))
|
||||
end
|
||||
elseif protocol == PROTOCOL.SCADA_CRDN then
|
||||
---@cast packet crdn_frame
|
||||
-- coordinator packet
|
||||
if session ~= nil then
|
||||
-- pass the packet onto the session handler
|
||||
session.in_queue.push_packet(packet)
|
||||
else
|
||||
-- any other packet should be session related, discard it
|
||||
log.debug(util.c("discarding pocket SCADA_CRDN packet without a known session from computer ", src_addr))
|
||||
end
|
||||
else
|
||||
log.debug(util.c("illegal packet type ", protocol, " on pocket channel"))
|
||||
end
|
||||
else
|
||||
log.debug("received packet on unconfigured channel " .. l_port, true)
|
||||
log.debug("received packet for unknown channel " .. r_chan, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -38,7 +38,7 @@ local pkt = comms.modbus_packet()
|
||||
---@diagnostic disable-next-line: param-type-mismatch
|
||||
pkt.make(1, 2, 7, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
|
||||
local spkt = comms.scada_packet()
|
||||
spkt.make(1, 1, pkt.raw_sendable())
|
||||
spkt.make(0, 1, 1, pkt.raw_sendable())
|
||||
|
||||
start = util.time()
|
||||
local data = textutils.serialize(spkt.raw_sendable(), { allow_repetitions = true, compact = true })
|
||||
|
Loading…
Reference in New Issue
Block a user