#200 work on pocket comms for unit data

This commit is contained in:
Mikayla 2024-04-13 00:41:47 +00:00
parent 0365ea5e8a
commit 99213da760
5 changed files with 178 additions and 51 deletions

View File

@ -470,10 +470,11 @@ function coordinator.comms(version, nic, sv_watchdog)
elseif packet.type == MGMT_TYPE.ESTABLISH then
-- establish a new session
-- 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]
if packet.length == 4 then
local comms_v = util.strval(packet.data[1])
local firmware_v = util.strval(packet.data[2])
local dev_type = packet.data[3]
local api_v = util.strval(packet.data[4])
if comms_v ~= comms.version then
if self.last_api_est_acks[src_addr] ~= ESTABLISH_ACK.BAD_VERSION then
@ -481,6 +482,12 @@ function coordinator.comms(version, nic, sv_watchdog)
end
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
elseif api_v ~= comms.api_version then
if self.last_api_est_acks[src_addr] ~= ESTABLISH_ACK.BAD_API_VERSION then
log.info(util.c("dropping API establish packet with incorrect api version v", comms_v, " (expected v", comms.version, ")"))
end
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_API_VERSION)
elseif dev_type == DEVICE_TYPE.PKT then
-- pocket linking request
local id = apisessions.establish_session(src_addr, firmware_v)

View File

@ -126,19 +126,15 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
if pkt.type == CRDN_TYPE.API_GET_FAC then
local fac = db.facility
---@class api_fac
local data = {
num_units = fac.num_units,
num_tanks = util.table_len(fac.tank_data_tbl),
tank_mode = fac.tank_mode,
tank_defs = fac.tank_defs,
sys_ok = fac.all_sys_ok,
rtu_count = fac.rtu_count,
radiation = fac.radiation,
auto = { fac.auto_ready, fac.auto_active, fac.auto_ramping, fac.auto_saturated },
waste = { fac.auto_current_waste_product, fac.auto_pu_fallback_active },
has_matrix = fac.induction_data_tbl[1] ~= nil,
has_sps = fac.sps_data_tbl[1] ~= nil,
fac.all_sys_ok,
fac.rtu_count,
fac.radiation,
{ fac.auto_ready, fac.auto_active, fac.auto_ramping, fac.auto_saturated },
{ fac.auto_current_waste_product, fac.auto_pu_fallback_active },
util.table_len(fac.tank_data_tbl),
fac.induction_data_tbl[1] ~= nil,
fac.sps_data_tbl[1] ~= nil,
}
_send(CRDN_TYPE.API_GET_FAC, data)

View File

@ -161,7 +161,60 @@ function iocontrol.init_core(comms)
end
-- initialize facility-dependent components of pocket iocontrol
function iocontrol.init_fac() end
---@param conf facility_conf configuration
---@param comms pocket_comms comms reference
---@param temp_scale 1|2|3|4 temperature unit (1 = K, 2 = C, 3 = F, 4 = R)
function iocontrol.init_fac(conf, comms, temp_scale)
-- temperature unit label and conversion function (from Kelvin)
if temp_scale == 2 then
io.temp_label = "\xb0C"
io.temp_convert = function (t) return t - 273.15 end
elseif temp_scale == 3 then
io.temp_label = "\xb0F"
io.temp_convert = function (t) return (1.8 * (t - 273.15)) + 32 end
elseif temp_scale == 4 then
io.temp_label = "\xb0R"
io.temp_convert = function (t) return 1.8 * t end
else
io.temp_label = "K"
io.temp_convert = function (t) return t end
end
-- facility data structure
---@class pioctl_facility
io.facility = {
num_units = conf.num_units,
tank_mode = conf.cooling.fac_tank_mode,
tank_defs = conf.cooling.fac_tank_defs,
all_sys_ok = false,
rtu_count = 0,
auto_ready = false,
auto_active = false,
auto_ramping = false,
auto_saturated = false,
---@type WASTE_PRODUCT
auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM,
auto_pu_fallback_active = false,
radiation = types.new_zero_radiation_reading(),
ps = psil.create(),
induction_ps_tbl = {},
induction_data_tbl = {},
sps_ps_tbl = {},
sps_data_tbl = {},
tank_ps_tbl = {},
tank_data_tbl = {},
env_d_ps = psil.create(),
env_d_data = {}
}
end
-- set network link state
---@param state POCKET_LINK_STATE
@ -203,6 +256,39 @@ function iocontrol.report_crd_tt(trip_time)
io.ps.publish("crd_conn_quality", state)
end
-- populate facility data from API_GET_FAC
---@param data table
---@return boolean valid
function iocontrol.record_facility_data(data)
local valid = true
local fac = io.facility
fac.all_sys_ok = data[1]
fac.rtu_count = data[2]
fac.radiation = data[3]
-- auto control
if type(data[4]) == "table" and #data[4] == 4 then
fac.auto_ready = data[4][1]
fac.auto_active = data[4][2]
fac.auto_ramping = data[4][3]
fac.auto_saturated = data[4][4]
end
-- waste
if type(data[5]) == "table" and #data[5] == 2 then
fac.auto_current_waste_product = data[5][1]
fac.auto_pu_fallback_active = data[5][2]
end
fac.num_tanks = data[6]
fac.has_imatrix = data[7]
fac.has_sps = data[8]
return valid
end
-- get the IO controller database
function iocontrol.get_db() return io end

View File

@ -8,6 +8,7 @@ local PROTOCOL = comms.PROTOCOL
local DEVICE_TYPE = comms.DEVICE_TYPE
local ESTABLISH_ACK = comms.ESTABLISH_ACK
local MGMT_TYPE = comms.MGMT_TYPE
local CRDN_TYPE = comms.CRDN_TYPE
local LINK_STATE = iocontrol.LINK_STATE
@ -246,6 +247,25 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
return pkt
end
---@param packet mgmt_frame|crdn_frame
---@param length integer
---@param max integer?
---@return boolean
local function _check_length(packet, length, max)
local ok = util.trinary(max == nil, packet.length == length, packet.length >= length and packet.length <= max)
if not ok then
local fmt = "[comms] RX_PACKET{r_chan=%d,proto=%d,type=%d}: packet length mismatch -> expect %d != actual %d"
log.debug(util.sprintf(fmt, packet.scada_frame.remote_channel(), packet.scada_frame.protocol(), packet.type))
end
return ok
end
---@param packet mgmt_frame|crdn_frame
local function _fail_type(packet)
local fmt = "[comms] RX_PACKET{r_chan=%d,proto=%d,type=%d}: unrecognized packet type"
log.debug(util.sprintf(fmt, packet.scada_frame.remote_channel(), packet.scada_frame.protocol(), packet.type))
end
-- handle a packet
---@param packet mgmt_frame|crdn_frame|nil
function public.handle_packet(packet)
@ -277,12 +297,24 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
-- feed watchdog on valid sequence number
api_watchdog.feed()
if protocol == PROTOCOL.SCADA_MGMT then
if protocol == PROTOCOL.SCADA_CRDN then
---@cast packet crdn_frame
if self.api.linked then
if packet.type == CRDN_TYPE.API_GET_FAC then
if _check_length(packet, 11) then
iocontrol.record_facility_data(packet.data)
end
elseif packet.type == CRDN_TYPE.API_GET_UNITS then
else _fail_type(packet) end
else
log.debug("discarding coordinator SCADA_CRDN packet before linked")
end
elseif protocol == PROTOCOL.SCADA_MGMT then
---@cast packet mgmt_frame
if self.api.linked then
if packet.type == MGMT_TYPE.KEEP_ALIVE then
-- keep alive request received, echo back
if packet.length == 1 then
if _check_length(packet, 1) then
local timestamp = packet.data[1]
local trip_time = util.time() - timestamp
@ -295,8 +327,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
_send_api_keep_alive_ack(timestamp)
iocontrol.report_crd_tt(trip_time)
else
log.debug("coordinator SCADA keep alive packet length mismatch")
end
elseif packet.type == MGMT_TYPE.CLOSE then
-- handle session close
@ -305,15 +335,23 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
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")
end
else _fail_type(packet) end
elseif packet.type == MGMT_TYPE.ESTABLISH then
-- connection with coordinator established
if packet.length == 1 then
if _check_length(packet, 1, 2) then
local est_ack = packet.data[1]
if est_ack == ESTABLISH_ACK.ALLOW then
if packet.length == 2 then
local fac_config = packet.data[2]
if type(fac_config) == "table" and #fac_config == 2 then
-- get configuration
local conf = { num_units = fac_config[1], cooling = fac_config[2] }
---@todo
iocontrol.init_fac(conf, public, 0)
log.info("coordinator connection established")
self.establish_delay_counter = 0
self.api.linked = true
@ -324,6 +362,12 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
else
iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY)
end
else
log.debug("invalid facility configuration table received from coordinator, establish failed")
end
else
log.debug("received coordinator establish allow without facility configuration")
end
elseif est_ack == ESTABLISH_ACK.DENY then
if self.api.last_est_ack ~= est_ack then
log.info("coordinator connection denied")
@ -336,13 +380,15 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
if self.api.last_est_ack ~= est_ack then
log.info("coordinator comms version mismatch")
end
elseif est_ack == ESTABLISH_ACK.BAD_API_VERSION then
if self.api.last_est_ack ~= est_ack then
log.info("coordinator api version mismatch")
end
else
log.debug("coordinator SCADA_MGMT establish packet reply unsupported")
end
self.api.last_est_ack = est_ack
else
log.debug("coordinator SCADA_MGMT establish packet length mismatch")
end
else
log.debug("discarding coordinator non-link SCADA_MGMT packet before linked")
@ -374,7 +420,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
if self.sv.linked then
if packet.type == MGMT_TYPE.KEEP_ALIVE then
-- keep alive request received, echo back
if packet.length == 1 then
if _check_length(packet, 1) then
local timestamp = packet.data[1]
local trip_time = util.time() - timestamp
@ -387,8 +433,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
_send_sv_keep_alive_ack(timestamp)
iocontrol.report_svr_tt(trip_time)
else
log.debug("supervisor SCADA keep alive packet length mismatch")
end
elseif packet.type == MGMT_TYPE.CLOSE then
-- handle session close
@ -398,12 +442,10 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
self.sv.addr = comms.BROADCAST
log.info("supervisor server connection closed by remote host")
elseif packet.type == MGMT_TYPE.DIAG_TONE_GET then
if packet.length == 8 then
if _check_length(packet, 8) then
for i = 1, #packet.data do
diag.tone_test.tone_indicators[i].update(packet.data[i] == true)
end
else
log.debug("supervisor SCADA diag alarm states packet length mismatch")
end
elseif packet.type == MGMT_TYPE.DIAG_TONE_SET then
if packet.length == 1 and packet.data[1] == false then
@ -442,12 +484,10 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
else
log.debug("supervisor SCADA diag alarm set packet length/type mismatch")
end
else
log.debug("received unknown SCADA_MGMT packet type " .. packet.type .. " from supervisor")
end
else _fail_type(packet) end
elseif packet.type == MGMT_TYPE.ESTABLISH then
-- connection with supervisor established
if packet.length == 1 then
if _check_length(packet, 1) then
local est_ack = packet.data[1]
if est_ack == ESTABLISH_ACK.ALLOW then
@ -478,15 +518,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
end
self.sv.last_est_ack = est_ack
else
log.debug("supervisor SCADA_MGMT establish packet length mismatch")
end
else
log.debug("discarding supervisor non-link SCADA_MGMT packet before linked")
end
else
log.debug("illegal packet type " .. protocol .. " from supervisor", true)
end
else _fail_type(packet) end
else
log.debug("received packet from unconfigured channel " .. r_chan, true)
end

View File

@ -16,8 +16,9 @@ local max_distance = nil
---@class comms
local comms = {}
-- protocol/data version (protocol/data independent changes tracked by util.lua version)
-- protocol/data versions (protocol/data independent changes tracked by util.lua version)
comms.version = "2.4.5"
comms.api_version = "0.0.1"
---@enum PROTOCOL
local PROTOCOL = {
@ -74,7 +75,8 @@ 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
BAD_VERSION = 3, -- link denied due to comms version mismatch
BAD_API_VERSION = 4 -- link denied due to api version mismatch
}
---@enum DEVICE_TYPE device types for establish messages