cc-mek-scada/scada-common/comms.lua

709 lines
20 KiB
Lua
Raw Normal View History

--
-- Communications
--
2023-04-16 23:50:16 +00:00
local log = require("scada-common.log")
2022-05-09 13:34:26 +00:00
2022-05-10 21:06:27 +00:00
---@class comms
local comms = {}
2022-05-07 17:39:12 +00:00
local insert = table.insert
local max_distance = nil
comms.version = "1.4.0"
2023-02-21 16:05:57 +00:00
---@enum PROTOCOL
local PROTOCOL = {
2022-03-15 16:02:31 +00:00
MODBUS_TCP = 0, -- our "MODBUS TCP"-esque protocol
RPLC = 1, -- reactor PLC protocol
SCADA_MGMT = 2, -- SCADA supervisor management, device advertisements, etc
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
}
2023-02-21 16:05:57 +00:00
---@enum RPLC_TYPE
local RPLC_TYPE = {
STATUS = 0, -- reactor/system status
MEK_STRUCT = 1, -- mekanism build structure
MEK_BURN_RATE = 2, -- set burn rate
RPS_ENABLE = 3, -- enable reactor
RPS_SCRAM = 4, -- SCRAM reactor (manual request)
RPS_ASCRAM = 5, -- SCRAM reactor (automatic request)
RPS_STATUS = 6, -- RPS status
RPS_ALARM = 7, -- RPS alarm broadcast
RPS_RESET = 8, -- clear RPS trip (if in bad state, will trip immediately)
RPS_AUTO_RESET = 9, -- clear RPS trip if it is just a timeout or auto scram
AUTO_BURN_RATE = 10 -- set an automatic burn rate, PLC will respond with status, enable toggle speed limited
2022-03-15 16:02:31 +00:00
}
2023-02-21 16:05:57 +00:00
---@enum SCADA_MGMT_TYPE
local SCADA_MGMT_TYPE = {
ESTABLISH = 0, -- establish new connection
KEEP_ALIVE = 1, -- keep alive packet w/ RTT
CLOSE = 2, -- close a connection
RTU_ADVERT = 3, -- RTU capability advertisement
RTU_DEV_REMOUNT = 4 -- RTU multiblock possbily changed (formed, unformed) due to PPM remount
2022-03-15 16:02:31 +00:00
}
2023-02-21 16:05:57 +00:00
---@enum SCADA_CRDN_TYPE
local SCADA_CRDN_TYPE = {
INITIAL_BUILDS = 0, -- initial, complete builds packet to the coordinator
FAC_BUILDS = 1, -- facility RTU builds
FAC_STATUS = 2, -- state of facility and facility devices
FAC_CMD = 3, -- faility command
UNIT_BUILDS = 4, -- build of each reactor unit (reactor + RTUs)
UNIT_STATUSES = 5, -- state of each of the reactor units
UNIT_CMD = 6 -- command a reactor unit
2022-06-15 19:35:34 +00:00
}
2023-02-21 16:05:57 +00:00
---@enum CAPI_TYPE
local CAPI_TYPE = {
}
2023-02-21 15:31:05 +00:00
---@enum ESTABLISH_ACK
local ESTABLISH_ACK = {
ALLOW = 0, -- link approved
DENY = 1, -- link denied
COLLISION = 2, -- link denied due to existing active link
BAD_VERSION = 3 -- link denied due to comms version mismatch
}
2023-02-21 16:05:57 +00:00
---@enum DEVICE_TYPE
local DEVICE_TYPE = {
PLC = 0, -- PLC device type for establish
RTU = 1, -- RTU device type for establish
SV = 2, -- supervisor device type for establish
2023-04-16 23:50:16 +00:00
CRDN = 3, -- coordinator device type for establish
PKT = 4 -- pocket device type for establish
}
2023-02-21 15:31:05 +00:00
---@enum PLC_AUTO_ACK
local PLC_AUTO_ACK = {
FAIL = 0, -- failed to set burn rate/burn rate invalid
DIRECT_SET_OK = 1, -- successfully set burn rate
RAMP_SET_OK = 2, -- successfully started burn rate ramping
ZERO_DIS_OK = 3 -- successfully disabled reactor with < 0.01 burn rate
}
2023-02-21 16:05:57 +00:00
---@enum FAC_COMMAND
local FAC_COMMAND = {
SCRAM_ALL = 0, -- SCRAM all reactors
STOP = 1, -- stop automatic control
2023-02-07 23:44:34 +00:00
START = 2, -- start automatic control
ACK_ALL_ALARMS = 3 -- acknowledge all alarms on all units
}
2023-02-21 16:05:57 +00:00
---@enum UNIT_COMMAND
local UNIT_COMMAND = {
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
ACK_ALL_ALARMS = 5, -- ack all active alarms
ACK_ALARM = 6, -- ack a particular alarm
RESET_ALARM = 7, -- reset a particular alarm
SET_GROUP = 8 -- assign this unit to a group
}
2023-02-21 16:05:57 +00:00
comms.PROTOCOL = PROTOCOL
2023-02-21 16:05:57 +00:00
comms.RPLC_TYPE = RPLC_TYPE
comms.SCADA_MGMT_TYPE = SCADA_MGMT_TYPE
comms.SCADA_CRDN_TYPE = SCADA_CRDN_TYPE
comms.CAPI_TYPE = CAPI_TYPE
comms.ESTABLISH_ACK = ESTABLISH_ACK
2023-02-21 16:05:57 +00:00
comms.DEVICE_TYPE = DEVICE_TYPE
comms.PLC_AUTO_ACK = PLC_AUTO_ACK
2023-02-21 16:05:57 +00:00
comms.UNIT_COMMAND = UNIT_COMMAND
comms.FAC_COMMAND = FAC_COMMAND
---@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
2023-02-21 16:05:57 +00:00
-- 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)
function comms.set_trusted_range(distance)
if distance < 1 then
max_distance = nil
else
max_distance = distance
end
end
-- generic SCADA packet object
2023-02-21 15:31:05 +00:00
---@nodiscard
2022-05-31 20:09:06 +00:00
function comms.scada_packet()
local self = {
modem_msg_in = nil,
2022-01-22 19:26:25 +00:00
valid = false,
raw = { -1, PROTOCOL.SCADA_MGMT, {} },
seq_num = -1,
protocol = PROTOCOL.SCADA_MGMT,
length = 0,
payload = {}
}
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
2023-02-21 16:05:57 +00:00
---@param protocol PROTOCOL
2022-05-10 21:06:27 +00:00
---@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
2023-02-21 15:31:05 +00:00
---@param side string modem side
---@param sender integer sender port
---@param reply_to integer reply port
---@param message any message body
---@param distance integer transmission distance
---@return boolean valid valid message received
2022-05-31 20:09:06 +00:00
function public.receive(side, sender, reply_to, message, distance)
self.modem_msg_in = {
iface = side,
s_port = sender,
r_port = reply_to,
msg = message,
dist = distance
}
self.raw = self.modem_msg_in.msg
if (type(max_distance) == "number") and (distance > max_distance) then
-- outside of maximum allowable transmission distance
-- log.debug("comms.scada_packet.receive(): discarding packet with distance " .. distance .. " outside of trusted range")
else
2023-02-07 23:44:34 +00:00
if type(self.raw) == "table" then
if #self.raw >= 3 then
self.seq_num = self.raw[1]
self.protocol = self.raw[2]
-- element 3 must be a table
if type(self.raw[3]) == "table" then
self.length = #self.raw[3]
self.payload = self.raw[3]
end
end
2022-06-05 17:21:02 +00:00
2023-02-07 23:44:34 +00:00
self.valid = type(self.seq_num) == "number" and
type(self.protocol) == "number" and
type(self.payload) == "table"
end
end
2022-04-22 19:43:25 +00:00
return self.valid
end
2022-04-22 19:43:25 +00:00
-- public accessors --
2023-02-21 15:31:05 +00:00
---@nodiscard
2022-05-31 20:09:06 +00:00
function public.modem_event() return self.modem_msg_in end
2023-02-21 15:31:05 +00:00
---@nodiscard
2022-05-31 20:09:06 +00:00
function public.raw_sendable() return self.raw end
2022-05-10 21:06:27 +00:00
2023-02-21 15:31:05 +00:00
---@nodiscard
2022-05-31 20:09:06 +00:00
function public.local_port() return self.modem_msg_in.s_port end
2023-02-21 15:31:05 +00:00
---@nodiscard
2022-05-31 20:09:06 +00:00
function public.remote_port() return self.modem_msg_in.r_port end
2022-05-10 21:06:27 +00:00
2023-02-21 15:31:05 +00:00
---@nodiscard
2022-05-31 20:09:06 +00:00
function public.is_valid() return self.valid end
2022-05-10 21:06:27 +00:00
2023-02-21 15:31:05 +00:00
---@nodiscard
2022-05-31 20:09:06 +00:00
function public.seq_num() return self.seq_num end
2023-02-21 15:31:05 +00:00
---@nodiscard
2022-05-31 20:09:06 +00:00
function public.protocol() return self.protocol end
2023-02-21 15:31:05 +00:00
---@nodiscard
2022-05-31 20:09:06 +00:00
function public.length() return self.length end
2023-02-21 15:31:05 +00:00
---@nodiscard
2022-05-31 20:09:06 +00:00
function public.data() return self.payload end
2022-05-10 21:06:27 +00:00
return public
end
2023-02-21 15:31:05 +00:00
-- MODBUS packet<br>
2022-04-23 00:21:28 +00:00
-- modeled after MODBUS TCP packet
2023-02-21 15:31:05 +00:00
---@nodiscard
2022-05-31 20:09:06 +00:00
function comms.modbus_packet()
local self = {
frame = nil,
raw = {},
txn_id = -1,
length = 0,
unit_id = -1,
2023-02-22 04:50:43 +00:00
func_code = 0x80,
data = {}
}
2022-05-10 21:06:27 +00:00
---@class modbus_packet
local public = {}
-- 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
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)
if frame then
self.frame = frame
2023-02-21 16:05:57 +00:00
if frame.protocol() == PROTOCOL.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
log.debug("attempted MODBUS_TCP parse of incorrect protocol " .. frame.protocol(), true)
2022-04-22 19:43:25 +00:00
return false
end
else
log.debug("nil frame encountered", true)
return false
end
end
2022-04-22 19:43:25 +00:00
-- get raw to send
2023-02-21 15:31:05 +00:00
---@nodiscard
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
2023-02-21 15:31:05 +00:00
---@nodiscard
2022-05-31 20:09:06 +00:00
function public.get()
2022-05-11 01:51:04 +00:00
---@class modbus_frame
local frame = {
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
end
2022-05-10 21:06:27 +00:00
return public
end
-- reactor PLC packet
2023-02-21 15:31:05 +00:00
---@nodiscard
2022-05-31 20:09:06 +00:00
function comms.rplc_packet()
local self = {
frame = nil,
raw = {},
id = 0,
2023-02-21 21:57:33 +00:00
type = 0, ---@type RPLC_TYPE
length = 0,
data = {}
}
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()
2023-02-21 16:05:57 +00:00
return self.type == RPLC_TYPE.STATUS or
self.type == RPLC_TYPE.MEK_STRUCT or
self.type == RPLC_TYPE.MEK_BURN_RATE or
self.type == RPLC_TYPE.RPS_ENABLE or
self.type == RPLC_TYPE.RPS_SCRAM or
self.type == RPLC_TYPE.RPS_ASCRAM or
self.type == RPLC_TYPE.RPS_STATUS or
self.type == RPLC_TYPE.RPS_ALARM or
self.type == RPLC_TYPE.RPS_RESET or
self.type == RPLC_TYPE.RPS_AUTO_RESET or
self.type == RPLC_TYPE.AUTO_BURN_RATE
end
-- make an RPLC packet
2022-05-10 21:06:27 +00:00
---@param id integer
2023-02-21 16:05:57 +00:00
---@param packet_type RPLC_TYPE
2022-05-10 21:06:27 +00:00
---@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
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)
if frame then
self.frame = frame
2023-02-21 16:05:57 +00:00
if frame.protocol() == PROTOCOL.RPLC then
2022-04-22 19:43:25 +00:00
local ok = frame.length() >= 2
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) })
ok = _rplc_type_valid()
end
2022-06-05 17:21:02 +00:00
ok = ok and type(self.id) == "number"
return ok
else
log.debug("attempted RPLC parse of incorrect protocol " .. frame.protocol(), true)
return false
end
else
log.debug("nil frame encountered", true)
return false
end
end
2022-04-22 19:43:25 +00:00
-- get raw to send
2023-02-21 15:31:05 +00:00
---@nodiscard
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
2023-02-21 15:31:05 +00:00
---@nodiscard
2022-05-31 20:09:06 +00:00
function public.get()
2022-05-11 01:51:04 +00:00
---@class rplc_frame
local frame = {
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
end
2022-05-10 21:06:27 +00:00
return public
end
-- SCADA management packet
2023-02-21 15:31:05 +00:00
---@nodiscard
2022-05-31 20:09:06 +00:00
function comms.mgmt_packet()
local self = {
frame = nil,
raw = {},
2023-02-21 21:57:33 +00:00
type = 0, ---@type SCADA_MGMT_TYPE
length = 0,
data = {}
}
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()
2023-02-21 16:05:57 +00:00
return self.type == SCADA_MGMT_TYPE.ESTABLISH or
self.type == SCADA_MGMT_TYPE.KEEP_ALIVE or
self.type == SCADA_MGMT_TYPE.CLOSE or
self.type == SCADA_MGMT_TYPE.REMOTE_LINKED or
self.type == SCADA_MGMT_TYPE.RTU_ADVERT or
self.type == SCADA_MGMT_TYPE.RTU_DEV_REMOUNT
end
-- make a SCADA management packet
2023-02-21 16:05:57 +00:00
---@param packet_type SCADA_MGMT_TYPE
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.mgmt_packet.make(): data not table")
2022-04-22 19:43:25 +00:00
end
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)
if frame then
self.frame = frame
2023-02-21 16:05:57 +00:00
if frame.protocol() == PROTOCOL.SCADA_MGMT then
local ok = frame.length() >= 1
2022-05-10 21:06:27 +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) })
ok = _scada_type_valid()
end
2022-05-10 21:06:27 +00:00
return ok
else
log.debug("attempted SCADA_MGMT parse of incorrect protocol " .. frame.protocol(), true)
2022-06-05 17:21:02 +00:00
return false
end
else
log.debug("nil frame encountered", true)
return false
end
end
2022-04-22 19:43:25 +00:00
-- get raw to send
2023-02-21 15:31:05 +00:00
---@nodiscard
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
2023-02-21 15:31:05 +00:00
---@nodiscard
2022-05-31 20:09:06 +00:00
function public.get()
2022-05-11 01:51:04 +00:00
---@class mgmt_frame
local frame = {
scada_frame = self.frame,
type = self.type,
length = self.length,
data = self.data
}
2022-05-11 01:51:04 +00:00
return frame
end
2022-05-10 21:06:27 +00:00
return public
end
2022-04-22 15:07:59 +00:00
-- SCADA coordinator packet
2023-02-21 15:31:05 +00:00
---@nodiscard
function comms.crdn_packet()
2022-04-22 15:07:59 +00:00
local self = {
frame = nil,
raw = {},
2023-02-21 21:57:33 +00:00
type = 0, ---@type SCADA_CRDN_TYPE
length = 0,
data = {}
2022-04-22 15:07:59 +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
2023-02-21 15:31:05 +00:00
---@nodiscard
local function _crdn_type_valid()
2023-02-21 16:05:57 +00:00
return self.type == SCADA_CRDN_TYPE.INITIAL_BUILDS or
self.type == SCADA_CRDN_TYPE.FAC_BUILDS or
self.type == SCADA_CRDN_TYPE.FAC_STATUS or
self.type == SCADA_CRDN_TYPE.FAC_CMD or
self.type == SCADA_CRDN_TYPE.UNIT_BUILDS or
self.type == SCADA_CRDN_TYPE.UNIT_STATUSES or
self.type == SCADA_CRDN_TYPE.UNIT_CMD
2022-04-22 15:07:59 +00:00
end
-- make a coordinator packet
2023-02-21 16:05:57 +00:00
---@param packet_type SCADA_CRDN_TYPE
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.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
2023-02-21 16:05:57 +00:00
if frame.protocol() == PROTOCOL.SCADA_CRDN then
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) })
ok = _crdn_type_valid()
2022-04-22 15:07:59 +00:00
end
return ok
else
log.debug("attempted SCADA_CRDN parse of incorrect protocol " .. frame.protocol(), true)
2022-04-22 15:07:59 +00:00
return false
end
else
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
2023-02-21 15:31:05 +00:00
---@nodiscard
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
2023-02-21 15:31:05 +00:00
---@nodiscard
2022-05-31 20:09:06 +00:00
function public.get()
---@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
2023-02-21 21:57:33 +00:00
---@todo implement for pocket access, set enum type for self.type
2023-02-21 15:31:05 +00:00
---@nodiscard
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 = {},
2023-02-21 21:57:33 +00:00
type = 0,
length = 0,
data = {}
2022-04-22 19:43:25 +00:00
}
2022-05-10 21:06:27 +00:00
---@class capi_packet
local public = {}
local function _capi_type_valid()
2023-02-21 15:31:05 +00:00
---@todo
2022-04-22 19:43:25 +00:00
return false
end
2022-05-10 21:06:27 +00:00
-- make a coordinator API packet
2023-02-21 16:05:57 +00:00
---@param packet_type CAPI_TYPE
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
2023-02-21 16:05:57 +00:00
if frame.protocol() == PROTOCOL.COORD_API then
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) })
ok = _capi_type_valid()
2022-04-22 19:43:25 +00:00
end
return ok
else
log.debug("attempted COORD_API parse of incorrect protocol " .. frame.protocol(), true)
2022-04-22 19:43:25 +00:00
return false
end
else
log.debug("nil frame encountered", true)
2022-04-22 19:43:25 +00:00
return false
end
end
-- get raw to send
2023-02-21 15:31:05 +00:00
---@nodiscard
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
2023-02-21 15:31:05 +00:00
---@nodiscard
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
return comms