2022-05-04 17:37:01 +00:00
|
|
|
--
|
|
|
|
-- Communications
|
|
|
|
--
|
|
|
|
|
2022-05-31 20:09:06 +00:00
|
|
|
local log = require("scada-common.log")
|
2022-05-09 13:34:26 +00:00
|
|
|
local types = require("scada-common.types")
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
---@class comms
|
2022-05-04 17:37:01 +00:00
|
|
|
local comms = {}
|
|
|
|
|
2022-05-09 13:34:26 +00:00
|
|
|
local rtu_t = types.rtu_t
|
2022-05-07 17:39:12 +00:00
|
|
|
local insert = table.insert
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
---@alias PROTOCOLS integer
|
2022-05-04 17:37:01 +00:00
|
|
|
local PROTOCOLS = {
|
2022-03-15 16:02:31 +00:00
|
|
|
MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol
|
|
|
|
RPLC = 1, -- reactor PLC protocol
|
2022-04-22 15:15:16 +00:00
|
|
|
SCADA_MGMT = 2, -- SCADA supervisor management, device advertisements, etc
|
2022-07-07 04:34:42 +00:00
|
|
|
SCADA_CRDN = 3, -- data/control packets for coordinators to/from supervisory controllers
|
2022-04-22 19:43:25 +00:00
|
|
|
COORD_API = 4 -- data/control packets for pocket computers to/from coordinators
|
2022-01-14 21:32:20 +00:00
|
|
|
}
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
---@alias RPLC_TYPES integer
|
2022-05-04 17:37:01 +00:00
|
|
|
local RPLC_TYPES = {
|
2022-05-09 19:00:16 +00:00
|
|
|
LINK_REQ = 0, -- linking requests
|
|
|
|
STATUS = 1, -- reactor/system status
|
|
|
|
MEK_STRUCT = 2, -- mekanism build structure
|
|
|
|
MEK_BURN_RATE = 3, -- set burn rate
|
|
|
|
RPS_ENABLE = 4, -- enable reactor
|
|
|
|
RPS_SCRAM = 5, -- SCRAM reactor
|
|
|
|
RPS_STATUS = 6, -- RPS status
|
|
|
|
RPS_ALARM = 7, -- RPS alarm broadcast
|
|
|
|
RPS_RESET = 8 -- clear RPS trip (if in bad state, will trip immediately)
|
2022-01-22 19:26:25 +00:00
|
|
|
}
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
---@alias RPLC_LINKING integer
|
2022-05-04 17:37:01 +00:00
|
|
|
local RPLC_LINKING = {
|
2022-03-15 16:02:31 +00:00
|
|
|
ALLOW = 0, -- link approved
|
|
|
|
DENY = 1, -- link denied
|
|
|
|
COLLISION = 2 -- link denied due to existing active link
|
|
|
|
}
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
---@alias SCADA_MGMT_TYPES integer
|
2022-05-04 17:37:01 +00:00
|
|
|
local SCADA_MGMT_TYPES = {
|
2022-05-09 19:00:16 +00:00
|
|
|
KEEP_ALIVE = 0, -- keep alive packet w/ RTT
|
2022-05-02 15:42:24 +00:00
|
|
|
CLOSE = 1, -- close a connection
|
2022-05-09 19:00:16 +00:00
|
|
|
RTU_ADVERT = 2, -- RTU capability advertisement
|
|
|
|
REMOTE_LINKED = 3 -- remote device linked
|
2022-03-15 16:02:31 +00:00
|
|
|
}
|
|
|
|
|
2022-07-07 04:34:42 +00:00
|
|
|
---@alias SCADA_CRDN_TYPES integer
|
|
|
|
local SCADA_CRDN_TYPES = {
|
2022-06-15 19:35:34 +00:00
|
|
|
ESTABLISH = 0, -- initial greeting
|
2022-08-28 16:57:36 +00:00
|
|
|
STRUCT_BUILDS = 1, -- mekanism structure builds
|
|
|
|
UNIT_STATUSES = 2, -- state of reactor units
|
2022-06-15 19:35:34 +00:00
|
|
|
COMMAND_UNIT = 3, -- command a reactor unit
|
|
|
|
ALARM = 4 -- alarm signaling
|
|
|
|
}
|
|
|
|
|
2022-09-21 19:53:51 +00:00
|
|
|
---@alias CRDN_COMMANDS integer
|
|
|
|
local CRDN_COMMANDS = {
|
|
|
|
SCRAM = 0, -- SCRAM the reactor
|
|
|
|
START = 1, -- start the reactor
|
|
|
|
RESET_RPS = 2, -- reset the RPS
|
|
|
|
SET_BURN = 3, -- set the burn rate
|
|
|
|
SET_WASTE = 4 -- set the waste processing mode
|
|
|
|
}
|
|
|
|
|
2022-06-25 17:39:47 +00:00
|
|
|
---@alias CAPI_TYPES integer
|
|
|
|
local CAPI_TYPES = {
|
|
|
|
ESTABLISH = 0 -- initial greeting
|
|
|
|
}
|
|
|
|
|
2022-05-13 14:27:57 +00:00
|
|
|
---@alias RTU_UNIT_TYPES integer
|
|
|
|
local RTU_UNIT_TYPES = {
|
2022-05-09 13:34:26 +00:00
|
|
|
REDSTONE = 0, -- redstone I/O
|
2022-09-21 19:53:51 +00:00
|
|
|
BOILER_VALVE = 1, -- boiler mekanism 10.1+
|
|
|
|
TURBINE_VALVE = 2, -- turbine, mekanism 10.1+
|
|
|
|
IMATRIX = 3, -- induction matrix
|
|
|
|
SPS = 4, -- SPS
|
|
|
|
SNA = 5, -- SNA
|
|
|
|
ENV_DETECTOR = 6 -- environment detector
|
2022-01-22 19:26:25 +00:00
|
|
|
}
|
|
|
|
|
2022-05-04 17:37:01 +00:00
|
|
|
comms.PROTOCOLS = PROTOCOLS
|
|
|
|
comms.RPLC_TYPES = RPLC_TYPES
|
|
|
|
comms.RPLC_LINKING = RPLC_LINKING
|
|
|
|
comms.SCADA_MGMT_TYPES = SCADA_MGMT_TYPES
|
2022-07-07 04:34:42 +00:00
|
|
|
comms.SCADA_CRDN_TYPES = SCADA_CRDN_TYPES
|
2022-09-21 19:53:51 +00:00
|
|
|
comms.CRDN_COMMANDS = CRDN_COMMANDS
|
|
|
|
comms.CAPI_TYPES = CAPI_TYPES
|
2022-05-13 14:27:57 +00:00
|
|
|
comms.RTU_UNIT_TYPES = RTU_UNIT_TYPES
|
2022-05-04 17:37:01 +00:00
|
|
|
|
2022-09-21 19:53:51 +00:00
|
|
|
---@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
|
|
|
|
|
2022-01-14 21:32:20 +00:00
|
|
|
-- generic SCADA packet object
|
2022-05-31 20:09:06 +00:00
|
|
|
function comms.scada_packet()
|
2022-01-14 21:32:20 +00:00
|
|
|
local self = {
|
|
|
|
modem_msg_in = nil,
|
2022-01-22 19:26:25 +00:00
|
|
|
valid = false,
|
2022-04-22 19:43:25 +00:00
|
|
|
raw = nil,
|
2022-03-14 18:19:14 +00:00
|
|
|
seq_num = nil,
|
2022-01-14 21:32:20 +00:00
|
|
|
protocol = nil,
|
2022-01-22 19:26:25 +00:00
|
|
|
length = nil,
|
2022-04-22 19:43:25 +00:00
|
|
|
payload = nil
|
2022-01-14 21:32:20 +00:00
|
|
|
}
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
---@class scada_packet
|
|
|
|
local public = {}
|
|
|
|
|
2022-04-22 19:43:25 +00:00
|
|
|
-- make a SCADA packet
|
2022-05-10 21:06:27 +00:00
|
|
|
---@param seq_num integer
|
|
|
|
---@param protocol PROTOCOLS
|
|
|
|
---@param payload table
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.make(seq_num, protocol, payload)
|
2022-01-22 19:26:25 +00:00
|
|
|
self.valid = true
|
2022-03-14 18:19:14 +00:00
|
|
|
self.seq_num = seq_num
|
2022-01-22 19:26:25 +00:00
|
|
|
self.protocol = protocol
|
|
|
|
self.length = #payload
|
2022-04-22 19:43:25 +00:00
|
|
|
self.payload = payload
|
|
|
|
self.raw = { self.seq_num, self.protocol, self.payload }
|
2022-01-22 19:26:25 +00:00
|
|
|
end
|
|
|
|
|
2022-04-22 19:43:25 +00:00
|
|
|
-- parse in a modem message as a SCADA packet
|
2022-05-10 21:06:27 +00:00
|
|
|
---@param side string
|
|
|
|
---@param sender integer
|
|
|
|
---@param reply_to integer
|
|
|
|
---@param message any
|
|
|
|
---@param distance integer
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.receive(side, sender, reply_to, message, distance)
|
2022-01-14 21:32:20 +00:00
|
|
|
self.modem_msg_in = {
|
|
|
|
iface = side,
|
|
|
|
s_port = sender,
|
|
|
|
r_port = reply_to,
|
|
|
|
msg = message,
|
|
|
|
dist = distance
|
|
|
|
}
|
|
|
|
|
|
|
|
self.raw = self.modem_msg_in.msg
|
|
|
|
|
2022-04-26 01:00:50 +00:00
|
|
|
if type(self.raw) == "table" then
|
|
|
|
if #self.raw >= 3 then
|
|
|
|
self.seq_num = self.raw[1]
|
|
|
|
self.protocol = self.raw[2]
|
|
|
|
self.length = #self.raw[3]
|
|
|
|
self.payload = self.raw[3]
|
|
|
|
end
|
2022-06-05 17:21:02 +00:00
|
|
|
|
|
|
|
self.valid = type(self.seq_num) == "number" and
|
|
|
|
type(self.protocol) == "number" and
|
|
|
|
type(self.payload) == "table"
|
2022-01-14 21:32:20 +00:00
|
|
|
end
|
2022-04-22 19:43:25 +00:00
|
|
|
|
|
|
|
return self.valid
|
2022-01-14 21:32:20 +00:00
|
|
|
end
|
|
|
|
|
2022-04-22 19:43:25 +00:00
|
|
|
-- public accessors --
|
|
|
|
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.modem_event() return self.modem_msg_in end
|
|
|
|
function public.raw_sendable() return self.raw end
|
2022-05-10 21:06:27 +00:00
|
|
|
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.local_port() return self.modem_msg_in.s_port end
|
|
|
|
function public.remote_port() return self.modem_msg_in.r_port end
|
2022-05-10 21:06:27 +00:00
|
|
|
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.is_valid() return self.valid end
|
2022-05-10 21:06:27 +00:00
|
|
|
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.seq_num() return self.seq_num end
|
|
|
|
function public.protocol() return self.protocol end
|
|
|
|
function public.length() return self.length end
|
|
|
|
function public.data() return self.payload end
|
2022-05-10 21:06:27 +00:00
|
|
|
|
|
|
|
return public
|
2022-01-14 21:32:20 +00:00
|
|
|
end
|
2022-04-02 12:28:43 +00:00
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
-- MODBUS packet
|
2022-04-23 00:21:28 +00:00
|
|
|
-- modeled after MODBUS TCP packet
|
2022-05-31 20:09:06 +00:00
|
|
|
function comms.modbus_packet()
|
2022-04-21 14:26:02 +00:00
|
|
|
local self = {
|
|
|
|
frame = nil,
|
2022-04-22 19:43:25 +00:00
|
|
|
raw = nil,
|
2022-05-10 15:35:52 +00:00
|
|
|
txn_id = nil,
|
|
|
|
length = nil,
|
|
|
|
unit_id = nil,
|
|
|
|
func_code = nil,
|
|
|
|
data = nil
|
2022-04-21 14:26:02 +00:00
|
|
|
}
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
---@class modbus_packet
|
|
|
|
local public = {}
|
|
|
|
|
2022-04-21 14:26:02 +00:00
|
|
|
-- make a MODBUS packet
|
2022-05-10 21:06:27 +00:00
|
|
|
---@param txn_id integer
|
|
|
|
---@param unit_id integer
|
|
|
|
---@param func_code MODBUS_FCODE
|
|
|
|
---@param data table
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.make(txn_id, unit_id, func_code, data)
|
2022-06-05 17:21:02 +00:00
|
|
|
if type(data) == "table" then
|
|
|
|
self.txn_id = txn_id
|
|
|
|
self.length = #data
|
|
|
|
self.unit_id = unit_id
|
|
|
|
self.func_code = func_code
|
|
|
|
self.data = data
|
|
|
|
|
|
|
|
-- populate raw array
|
|
|
|
self.raw = { self.txn_id, self.unit_id, self.func_code }
|
|
|
|
for i = 1, self.length do
|
|
|
|
insert(self.raw, data[i])
|
|
|
|
end
|
|
|
|
else
|
|
|
|
log.error("comms.modbus_packet.make(): data not table")
|
2022-04-22 19:43:25 +00:00
|
|
|
end
|
2022-04-21 14:26:02 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- decode a MODBUS packet from a SCADA frame
|
2022-05-10 21:06:27 +00:00
|
|
|
---@param frame scada_packet
|
|
|
|
---@return boolean success
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.decode(frame)
|
2022-04-21 14:26:02 +00:00
|
|
|
if frame then
|
|
|
|
self.frame = frame
|
|
|
|
|
2022-04-22 19:43:25 +00:00
|
|
|
if frame.protocol() == PROTOCOLS.MODBUS_TCP then
|
2022-04-23 00:21:28 +00:00
|
|
|
local size_ok = frame.length() >= 3
|
2022-05-10 21:06:27 +00:00
|
|
|
|
2022-04-22 19:43:25 +00:00
|
|
|
if size_ok then
|
|
|
|
local data = frame.data()
|
2022-05-10 21:06:27 +00:00
|
|
|
public.make(data[1], data[2], data[3], { table.unpack(data, 4, #data) })
|
2022-04-22 19:43:25 +00:00
|
|
|
end
|
2022-05-10 21:06:27 +00:00
|
|
|
|
2022-06-05 17:21:02 +00:00
|
|
|
local valid = type(self.txn_id) == "number" and
|
|
|
|
type(self.unit_id) == "number" and
|
|
|
|
type(self.func_code) == "number"
|
|
|
|
|
|
|
|
return size_ok and valid
|
2022-04-22 19:43:25 +00:00
|
|
|
else
|
2022-05-04 17:37:01 +00:00
|
|
|
log.debug("attempted MODBUS_TCP parse of incorrect protocol " .. frame.protocol(), true)
|
2022-04-22 19:43:25 +00:00
|
|
|
return false
|
2022-04-21 14:26:02 +00:00
|
|
|
end
|
|
|
|
else
|
2022-05-04 17:37:01 +00:00
|
|
|
log.debug("nil frame encountered", true)
|
2022-04-21 14:26:02 +00:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-04-22 19:43:25 +00:00
|
|
|
-- get raw to send
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.raw_sendable() return self.raw end
|
2022-04-22 19:43:25 +00:00
|
|
|
|
2022-05-26 23:37:19 +00:00
|
|
|
-- get this packet as a frame with an immutable relation to this object
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.get()
|
2022-05-11 01:51:04 +00:00
|
|
|
---@class modbus_frame
|
|
|
|
local frame = {
|
2022-04-21 14:26:02 +00:00
|
|
|
scada_frame = self.frame,
|
|
|
|
txn_id = self.txn_id,
|
|
|
|
length = self.length,
|
|
|
|
unit_id = self.unit_id,
|
|
|
|
func_code = self.func_code,
|
|
|
|
data = self.data
|
|
|
|
}
|
2022-05-11 01:51:04 +00:00
|
|
|
|
|
|
|
return frame
|
2022-04-21 14:26:02 +00:00
|
|
|
end
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
return public
|
2022-04-21 14:26:02 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- reactor PLC packet
|
2022-05-31 20:09:06 +00:00
|
|
|
function comms.rplc_packet()
|
2022-04-21 14:26:02 +00:00
|
|
|
local self = {
|
|
|
|
frame = nil,
|
2022-04-22 19:43:25 +00:00
|
|
|
raw = nil,
|
2022-04-21 14:26:02 +00:00
|
|
|
id = nil,
|
|
|
|
type = nil,
|
|
|
|
length = nil,
|
|
|
|
body = nil
|
|
|
|
}
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
---@class rplc_packet
|
|
|
|
local public = {}
|
|
|
|
|
2022-04-22 19:43:25 +00:00
|
|
|
-- check that type is known
|
2022-05-31 20:09:06 +00:00
|
|
|
local function _rplc_type_valid()
|
2022-05-10 21:06:27 +00:00
|
|
|
return self.type == RPLC_TYPES.LINK_REQ or
|
2022-04-21 14:26:02 +00:00
|
|
|
self.type == RPLC_TYPES.STATUS or
|
|
|
|
self.type == RPLC_TYPES.MEK_STRUCT or
|
|
|
|
self.type == RPLC_TYPES.MEK_BURN_RATE or
|
2022-05-05 17:14:14 +00:00
|
|
|
self.type == RPLC_TYPES.RPS_ENABLE or
|
|
|
|
self.type == RPLC_TYPES.RPS_SCRAM or
|
2022-05-05 15:55:04 +00:00
|
|
|
self.type == RPLC_TYPES.RPS_ALARM or
|
|
|
|
self.type == RPLC_TYPES.RPS_STATUS or
|
|
|
|
self.type == RPLC_TYPES.RPS_RESET
|
2022-04-21 14:26:02 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- make an RPLC packet
|
2022-05-10 21:06:27 +00:00
|
|
|
---@param id integer
|
|
|
|
---@param packet_type RPLC_TYPES
|
|
|
|
---@param data table
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.make(id, packet_type, data)
|
2022-06-05 17:21:02 +00:00
|
|
|
if type(data) == "table" then
|
|
|
|
-- packet accessor properties
|
|
|
|
self.id = id
|
|
|
|
self.type = packet_type
|
|
|
|
self.length = #data
|
|
|
|
self.data = data
|
|
|
|
|
|
|
|
-- populate raw array
|
|
|
|
self.raw = { self.id, self.type }
|
|
|
|
for i = 1, #data do
|
|
|
|
insert(self.raw, data[i])
|
|
|
|
end
|
|
|
|
else
|
|
|
|
log.error("comms.rplc_packet.make(): data not table")
|
2022-04-22 19:43:25 +00:00
|
|
|
end
|
2022-04-21 14:26:02 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- decode an RPLC packet from a SCADA frame
|
2022-05-10 21:06:27 +00:00
|
|
|
---@param frame scada_packet
|
|
|
|
---@return boolean success
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.decode(frame)
|
2022-04-21 14:26:02 +00:00
|
|
|
if frame then
|
|
|
|
self.frame = frame
|
|
|
|
|
2022-04-22 19:43:25 +00:00
|
|
|
if frame.protocol() == PROTOCOLS.RPLC then
|
|
|
|
local ok = frame.length() >= 2
|
2022-04-21 14:26:02 +00:00
|
|
|
|
|
|
|
if ok then
|
2022-04-22 19:43:25 +00:00
|
|
|
local data = frame.data()
|
2022-05-10 21:06:27 +00:00
|
|
|
public.make(data[1], data[2], { table.unpack(data, 3, #data) })
|
2022-04-21 14:26:02 +00:00
|
|
|
ok = _rplc_type_valid()
|
|
|
|
end
|
|
|
|
|
2022-06-05 17:21:02 +00:00
|
|
|
ok = ok and type(self.id) == "number"
|
|
|
|
|
2022-04-21 14:26:02 +00:00
|
|
|
return ok
|
|
|
|
else
|
2022-05-04 17:37:01 +00:00
|
|
|
log.debug("attempted RPLC parse of incorrect protocol " .. frame.protocol(), true)
|
2022-04-21 14:26:02 +00:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
else
|
2022-05-04 17:37:01 +00:00
|
|
|
log.debug("nil frame encountered", true)
|
2022-04-21 14:26:02 +00:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-04-22 19:43:25 +00:00
|
|
|
-- get raw to send
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.raw_sendable() return self.raw end
|
2022-04-22 19:43:25 +00:00
|
|
|
|
2022-05-26 23:37:19 +00:00
|
|
|
-- get this packet as a frame with an immutable relation to this object
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.get()
|
2022-05-11 01:51:04 +00:00
|
|
|
---@class rplc_frame
|
|
|
|
local frame = {
|
2022-04-21 14:26:02 +00:00
|
|
|
scada_frame = self.frame,
|
|
|
|
id = self.id,
|
|
|
|
type = self.type,
|
|
|
|
length = self.length,
|
|
|
|
data = self.data
|
|
|
|
}
|
2022-05-11 01:51:04 +00:00
|
|
|
|
|
|
|
return frame
|
2022-04-21 14:26:02 +00:00
|
|
|
end
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
return public
|
2022-04-21 14:26:02 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- SCADA management packet
|
2022-05-31 20:09:06 +00:00
|
|
|
function comms.mgmt_packet()
|
2022-04-02 12:28:43 +00:00
|
|
|
local self = {
|
|
|
|
frame = nil,
|
2022-04-22 19:43:25 +00:00
|
|
|
raw = nil,
|
2022-04-02 12:28:43 +00:00
|
|
|
type = nil,
|
|
|
|
length = nil,
|
|
|
|
data = nil
|
|
|
|
}
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
---@class mgmt_packet
|
|
|
|
local public = {}
|
|
|
|
|
2022-04-22 19:43:25 +00:00
|
|
|
-- check that type is known
|
2022-05-31 20:09:06 +00:00
|
|
|
local function _scada_type_valid()
|
2022-05-10 21:06:27 +00:00
|
|
|
return self.type == SCADA_MGMT_TYPES.KEEP_ALIVE or
|
2022-05-02 21:34:24 +00:00
|
|
|
self.type == SCADA_MGMT_TYPES.CLOSE or
|
2022-04-02 12:28:43 +00:00
|
|
|
self.type == SCADA_MGMT_TYPES.REMOTE_LINKED or
|
2022-05-10 21:06:27 +00:00
|
|
|
self.type == SCADA_MGMT_TYPES.RTU_ADVERT
|
2022-04-02 12:28:43 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- make a SCADA management packet
|
2022-05-10 21:06:27 +00:00
|
|
|
---@param packet_type SCADA_MGMT_TYPES
|
|
|
|
---@param data table
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.make(packet_type, data)
|
2022-06-05 17:21:02 +00:00
|
|
|
if type(data) == "table" then
|
|
|
|
-- packet accessor properties
|
|
|
|
self.type = packet_type
|
|
|
|
self.length = #data
|
|
|
|
self.data = data
|
|
|
|
|
|
|
|
-- populate raw array
|
|
|
|
self.raw = { self.type }
|
|
|
|
for i = 1, #data do
|
|
|
|
insert(self.raw, data[i])
|
|
|
|
end
|
|
|
|
else
|
|
|
|
log.error("comms.mgmt_packet.make(): data not table")
|
2022-04-22 19:43:25 +00:00
|
|
|
end
|
2022-04-02 12:28:43 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- decode a SCADA management packet from a SCADA frame
|
2022-05-10 21:06:27 +00:00
|
|
|
---@param frame scada_packet
|
|
|
|
---@return boolean success
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.decode(frame)
|
2022-04-02 12:28:43 +00:00
|
|
|
if frame then
|
|
|
|
self.frame = frame
|
|
|
|
|
2022-04-22 19:43:25 +00:00
|
|
|
if frame.protocol() == PROTOCOLS.SCADA_MGMT then
|
2022-05-02 21:34:24 +00:00
|
|
|
local ok = frame.length() >= 1
|
2022-05-10 21:06:27 +00:00
|
|
|
|
2022-04-02 12:28:43 +00:00
|
|
|
if ok then
|
2022-04-22 19:43:25 +00:00
|
|
|
local data = frame.data()
|
2022-05-10 21:06:27 +00:00
|
|
|
public.make(data[1], { table.unpack(data, 2, #data) })
|
2022-04-02 12:28:43 +00:00
|
|
|
ok = _scada_type_valid()
|
|
|
|
end
|
2022-05-10 21:06:27 +00:00
|
|
|
|
2022-04-02 12:28:43 +00:00
|
|
|
return ok
|
|
|
|
else
|
2022-05-04 17:37:01 +00:00
|
|
|
log.debug("attempted SCADA_MGMT parse of incorrect protocol " .. frame.protocol(), true)
|
2022-06-05 17:21:02 +00:00
|
|
|
return false
|
2022-04-02 12:28:43 +00:00
|
|
|
end
|
|
|
|
else
|
2022-05-04 17:37:01 +00:00
|
|
|
log.debug("nil frame encountered", true)
|
2022-04-02 12:28:43 +00:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-04-22 19:43:25 +00:00
|
|
|
-- get raw to send
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.raw_sendable() return self.raw end
|
2022-04-22 19:43:25 +00:00
|
|
|
|
2022-05-26 23:37:19 +00:00
|
|
|
-- get this packet as a frame with an immutable relation to this object
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.get()
|
2022-05-11 01:51:04 +00:00
|
|
|
---@class mgmt_frame
|
|
|
|
local frame = {
|
2022-04-02 12:28:43 +00:00
|
|
|
scada_frame = self.frame,
|
|
|
|
type = self.type,
|
|
|
|
length = self.length,
|
|
|
|
data = self.data
|
|
|
|
}
|
2022-05-11 01:51:04 +00:00
|
|
|
|
|
|
|
return frame
|
2022-04-02 12:28:43 +00:00
|
|
|
end
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
return public
|
2022-04-02 12:28:43 +00:00
|
|
|
end
|
2022-04-22 15:07:59 +00:00
|
|
|
|
|
|
|
-- SCADA coordinator packet
|
2022-07-07 04:34:42 +00:00
|
|
|
function comms.crdn_packet()
|
2022-04-22 15:07:59 +00:00
|
|
|
local self = {
|
|
|
|
frame = nil,
|
2022-04-22 19:43:25 +00:00
|
|
|
raw = nil,
|
2022-04-22 15:07:59 +00:00
|
|
|
type = nil,
|
|
|
|
length = nil,
|
|
|
|
data = nil
|
|
|
|
}
|
|
|
|
|
2022-07-07 04:34:42 +00:00
|
|
|
---@class crdn_packet
|
2022-05-10 21:06:27 +00:00
|
|
|
local public = {}
|
|
|
|
|
2022-06-15 19:35:34 +00:00
|
|
|
-- check that type is known
|
2022-07-07 04:34:42 +00:00
|
|
|
local function _crdn_type_valid()
|
|
|
|
return self.type == SCADA_CRDN_TYPES.ESTABLISH or
|
2022-08-28 16:57:36 +00:00
|
|
|
self.type == SCADA_CRDN_TYPES.STRUCT_BUILDS or
|
|
|
|
self.type == SCADA_CRDN_TYPES.UNIT_STATUSES or
|
2022-07-07 04:34:42 +00:00
|
|
|
self.type == SCADA_CRDN_TYPES.COMMAND_UNIT or
|
|
|
|
self.type == SCADA_CRDN_TYPES.ALARM
|
2022-04-22 15:07:59 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- make a coordinator packet
|
2022-07-07 04:34:42 +00:00
|
|
|
---@param packet_type SCADA_CRDN_TYPES
|
2022-05-10 21:06:27 +00:00
|
|
|
---@param data table
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.make(packet_type, data)
|
2022-06-05 17:21:02 +00:00
|
|
|
if type(data) == "table" then
|
|
|
|
-- packet accessor properties
|
|
|
|
self.type = packet_type
|
|
|
|
self.length = #data
|
|
|
|
self.data = data
|
|
|
|
|
|
|
|
-- populate raw array
|
|
|
|
self.raw = { self.type }
|
|
|
|
for i = 1, #data do
|
|
|
|
insert(self.raw, data[i])
|
|
|
|
end
|
|
|
|
else
|
2022-07-07 04:34:42 +00:00
|
|
|
log.error("comms.crdn_packet.make(): data not table")
|
2022-04-22 19:43:25 +00:00
|
|
|
end
|
2022-04-22 15:07:59 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- decode a coordinator packet from a SCADA frame
|
2022-05-10 21:06:27 +00:00
|
|
|
---@param frame scada_packet
|
|
|
|
---@return boolean success
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.decode(frame)
|
2022-04-22 15:07:59 +00:00
|
|
|
if frame then
|
|
|
|
self.frame = frame
|
|
|
|
|
2022-07-07 04:34:42 +00:00
|
|
|
if frame.protocol() == PROTOCOLS.SCADA_CRDN then
|
2022-05-02 21:34:24 +00:00
|
|
|
local ok = frame.length() >= 1
|
2022-04-22 15:07:59 +00:00
|
|
|
|
|
|
|
if ok then
|
2022-04-22 19:43:25 +00:00
|
|
|
local data = frame.data()
|
2022-05-10 21:06:27 +00:00
|
|
|
public.make(data[1], { table.unpack(data, 2, #data) })
|
2022-07-07 04:34:42 +00:00
|
|
|
ok = _crdn_type_valid()
|
2022-04-22 15:07:59 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
return ok
|
|
|
|
else
|
2022-07-07 04:34:42 +00:00
|
|
|
log.debug("attempted SCADA_CRDN parse of incorrect protocol " .. frame.protocol(), true)
|
2022-04-22 15:07:59 +00:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
else
|
2022-05-04 17:37:01 +00:00
|
|
|
log.debug("nil frame encountered", true)
|
2022-04-22 15:07:59 +00:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-04-22 19:43:25 +00:00
|
|
|
-- get raw to send
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.raw_sendable() return self.raw end
|
2022-04-22 19:43:25 +00:00
|
|
|
|
2022-05-26 23:37:19 +00:00
|
|
|
-- get this packet as a frame with an immutable relation to this object
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.get()
|
2022-07-07 04:34:42 +00:00
|
|
|
---@class crdn_frame
|
2022-05-11 01:51:04 +00:00
|
|
|
local frame = {
|
2022-04-22 19:43:25 +00:00
|
|
|
scada_frame = self.frame,
|
|
|
|
type = self.type,
|
|
|
|
length = self.length,
|
|
|
|
data = self.data
|
|
|
|
}
|
2022-05-11 01:51:04 +00:00
|
|
|
|
|
|
|
return frame
|
2022-04-22 19:43:25 +00:00
|
|
|
end
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
return public
|
2022-04-22 19:43:25 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- coordinator API (CAPI) packet
|
|
|
|
-- @todo
|
2022-05-31 20:09:06 +00:00
|
|
|
function comms.capi_packet()
|
2022-04-22 19:43:25 +00:00
|
|
|
local self = {
|
|
|
|
frame = nil,
|
|
|
|
raw = nil,
|
|
|
|
type = nil,
|
|
|
|
length = nil,
|
|
|
|
data = nil
|
|
|
|
}
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
---@class capi_packet
|
|
|
|
local public = {}
|
|
|
|
|
2022-07-07 04:34:42 +00:00
|
|
|
local function _capi_type_valid()
|
2022-04-22 19:43:25 +00:00
|
|
|
-- @todo
|
|
|
|
return false
|
|
|
|
end
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
-- make a coordinator API packet
|
2022-06-25 17:39:47 +00:00
|
|
|
---@param packet_type CAPI_TYPES
|
2022-05-10 21:06:27 +00:00
|
|
|
---@param data table
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.make(packet_type, data)
|
2022-06-05 17:21:02 +00:00
|
|
|
if type(data) == "table" then
|
|
|
|
-- packet accessor properties
|
|
|
|
self.type = packet_type
|
|
|
|
self.length = #data
|
|
|
|
self.data = data
|
|
|
|
|
|
|
|
-- populate raw array
|
|
|
|
self.raw = { self.type }
|
|
|
|
for i = 1, #data do
|
|
|
|
insert(self.raw, data[i])
|
|
|
|
end
|
|
|
|
else
|
|
|
|
log.error("comms.capi_packet.make(): data not table")
|
2022-04-22 19:43:25 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
-- decode a coordinator API packet from a SCADA frame
|
|
|
|
---@param frame scada_packet
|
|
|
|
---@return boolean success
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.decode(frame)
|
2022-04-22 19:43:25 +00:00
|
|
|
if frame then
|
|
|
|
self.frame = frame
|
|
|
|
|
|
|
|
if frame.protocol() == PROTOCOLS.COORD_API then
|
2022-05-02 21:34:24 +00:00
|
|
|
local ok = frame.length() >= 1
|
2022-04-22 19:43:25 +00:00
|
|
|
|
|
|
|
if ok then
|
|
|
|
local data = frame.data()
|
2022-05-10 21:06:27 +00:00
|
|
|
public.make(data[1], { table.unpack(data, 2, #data) })
|
2022-07-07 04:34:42 +00:00
|
|
|
ok = _capi_type_valid()
|
2022-04-22 19:43:25 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
return ok
|
|
|
|
else
|
2022-05-04 17:37:01 +00:00
|
|
|
log.debug("attempted COORD_API parse of incorrect protocol " .. frame.protocol(), true)
|
2022-04-22 19:43:25 +00:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
else
|
2022-05-04 17:37:01 +00:00
|
|
|
log.debug("nil frame encountered", true)
|
2022-04-22 19:43:25 +00:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- get raw to send
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.raw_sendable() return self.raw end
|
2022-04-22 19:43:25 +00:00
|
|
|
|
2022-05-26 23:37:19 +00:00
|
|
|
-- get this packet as a frame with an immutable relation to this object
|
2022-05-31 20:09:06 +00:00
|
|
|
function public.get()
|
2022-05-11 01:51:04 +00:00
|
|
|
---@class capi_frame
|
|
|
|
local frame = {
|
2022-04-22 15:07:59 +00:00
|
|
|
scada_frame = self.frame,
|
|
|
|
type = self.type,
|
|
|
|
length = self.length,
|
|
|
|
data = self.data
|
|
|
|
}
|
2022-05-11 01:51:04 +00:00
|
|
|
|
|
|
|
return frame
|
2022-04-22 15:07:59 +00:00
|
|
|
end
|
|
|
|
|
2022-05-10 21:06:27 +00:00
|
|
|
return public
|
2022-04-22 15:07:59 +00:00
|
|
|
end
|
2022-05-04 17:37:01 +00:00
|
|
|
|
2022-05-13 15:08:22 +00:00
|
|
|
-- convert rtu_t to RTU unit type
|
2022-05-10 21:06:27 +00:00
|
|
|
---@param type rtu_t
|
2022-05-13 14:27:57 +00:00
|
|
|
---@return RTU_UNIT_TYPES|nil
|
2022-05-31 20:09:06 +00:00
|
|
|
function comms.rtu_t_to_unit_type(type)
|
2022-05-09 13:34:26 +00:00
|
|
|
if type == rtu_t.redstone then
|
2022-05-13 14:27:57 +00:00
|
|
|
return RTU_UNIT_TYPES.REDSTONE
|
2022-05-09 13:34:26 +00:00
|
|
|
elseif type == rtu_t.boiler_valve then
|
2022-05-13 14:27:57 +00:00
|
|
|
return RTU_UNIT_TYPES.BOILER_VALVE
|
2022-05-09 13:34:26 +00:00
|
|
|
elseif type == rtu_t.turbine_valve then
|
2022-05-13 14:27:57 +00:00
|
|
|
return RTU_UNIT_TYPES.TURBINE_VALVE
|
2022-05-09 13:34:26 +00:00
|
|
|
elseif type == rtu_t.induction_matrix then
|
2022-05-13 14:27:57 +00:00
|
|
|
return RTU_UNIT_TYPES.IMATRIX
|
2022-05-09 13:34:26 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
2022-05-13 15:08:22 +00:00
|
|
|
-- convert RTU unit type to rtu_t
|
|
|
|
---@param utype RTU_UNIT_TYPES
|
2022-05-10 21:06:27 +00:00
|
|
|
---@return rtu_t|nil
|
2022-05-31 20:09:06 +00:00
|
|
|
function comms.advert_type_to_rtu_t(utype)
|
2022-05-13 15:08:22 +00:00
|
|
|
if utype == RTU_UNIT_TYPES.REDSTONE then
|
2022-05-09 13:34:26 +00:00
|
|
|
return rtu_t.redstone
|
2022-05-13 15:08:22 +00:00
|
|
|
elseif utype == RTU_UNIT_TYPES.BOILER_VALVE then
|
2022-05-09 13:34:26 +00:00
|
|
|
return rtu_t.boiler_valve
|
2022-05-13 15:08:22 +00:00
|
|
|
elseif utype == RTU_UNIT_TYPES.TURBINE_VALVE then
|
2022-05-09 13:34:26 +00:00
|
|
|
return rtu_t.turbine_valve
|
2022-05-13 15:08:22 +00:00
|
|
|
elseif utype == RTU_UNIT_TYPES.IMATRIX then
|
2022-05-09 13:34:26 +00:00
|
|
|
return rtu_t.induction_matrix
|
|
|
|
end
|
|
|
|
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
2022-05-04 17:37:01 +00:00
|
|
|
return comms
|