2023-02-23 04:09:47 +00:00
|
|
|
--
|
|
|
|
-- I/O Control for Supervisor/Coordinator Integration
|
|
|
|
--
|
|
|
|
|
2022-12-04 18:59:10 +00:00
|
|
|
local log = require("scada-common.log")
|
|
|
|
local psil = require("scada-common.psil")
|
|
|
|
local types = require("scada-common.types")
|
|
|
|
local util = require("scada-common.util")
|
|
|
|
|
2022-12-18 18:56:04 +00:00
|
|
|
local process = require("coordinator.process")
|
2022-12-04 18:59:10 +00:00
|
|
|
local sounder = require("coordinator.sounder")
|
2022-09-21 19:53:51 +00:00
|
|
|
|
2023-07-10 03:31:56 +00:00
|
|
|
local pgi = require("coordinator.ui.pgi")
|
|
|
|
|
2022-12-04 18:59:10 +00:00
|
|
|
local ALARM_STATE = types.ALARM_STATE
|
2023-03-04 06:37:15 +00:00
|
|
|
local PROCESS = types.PROCESS
|
2022-12-04 18:59:10 +00:00
|
|
|
|
2023-07-10 03:31:56 +00:00
|
|
|
-- nominal RTT is ping (0ms to 10ms usually) + 500ms for CRD main loop tick
|
|
|
|
local WARN_RTT = 1000 -- 2x as long as expected w/ 0 ping
|
|
|
|
local HIGH_RTT = 1500 -- 3.33x as long as expected w/ 0 ping
|
|
|
|
|
2022-09-07 02:38:27 +00:00
|
|
|
local iocontrol = {}
|
2022-07-06 03:48:01 +00:00
|
|
|
|
2022-09-07 02:38:27 +00:00
|
|
|
---@class ioctl
|
|
|
|
local io = {}
|
2022-07-06 03:48:01 +00:00
|
|
|
|
2023-05-05 18:09:50 +00:00
|
|
|
-- luacheck: no unused args
|
2023-05-05 17:55:14 +00:00
|
|
|
|
2023-04-18 17:55:18 +00:00
|
|
|
-- placeholder acknowledge function for type hinting
|
|
|
|
---@param success boolean
|
|
|
|
---@diagnostic disable-next-line: unused-local
|
|
|
|
local function __generic_ack(success) end
|
|
|
|
|
2023-05-05 18:09:50 +00:00
|
|
|
-- luacheck: unused args
|
2023-05-05 17:55:14 +00:00
|
|
|
|
2023-07-10 03:31:56 +00:00
|
|
|
-- initialize front panel PSIL
|
|
|
|
---@param firmware_v string coordinator version
|
|
|
|
---@param comms_v string comms version
|
|
|
|
function iocontrol.init_fp(firmware_v, comms_v)
|
|
|
|
---@class ioctl_front_panel
|
|
|
|
io.fp = {
|
|
|
|
ps = psil.create()
|
|
|
|
}
|
|
|
|
|
|
|
|
io.fp.ps.publish("version", firmware_v)
|
|
|
|
io.fp.ps.publish("comms_version", comms_v)
|
|
|
|
end
|
|
|
|
|
2022-09-07 02:38:27 +00:00
|
|
|
-- initialize the coordinator IO controller
|
2022-07-06 03:48:01 +00:00
|
|
|
---@param conf facility_conf configuration
|
2022-09-21 19:53:51 +00:00
|
|
|
---@param comms coord_comms comms reference
|
|
|
|
function iocontrol.init(conf, comms)
|
2023-01-23 20:10:41 +00:00
|
|
|
---@class ioctl_facility
|
2022-09-07 02:38:27 +00:00
|
|
|
io.facility = {
|
2023-02-16 00:52:28 +00:00
|
|
|
num_units = conf.num_units, ---@type integer
|
2023-02-04 18:47:00 +00:00
|
|
|
all_sys_ok = false,
|
2023-02-16 00:52:28 +00:00
|
|
|
rtu_count = 0,
|
2023-02-04 18:47:00 +00:00
|
|
|
|
2023-02-03 03:58:51 +00:00
|
|
|
auto_ready = false,
|
2022-12-18 18:56:04 +00:00
|
|
|
auto_active = false,
|
2023-01-26 23:26:26 +00:00
|
|
|
auto_ramping = false,
|
2023-02-07 05:32:50 +00:00
|
|
|
auto_saturated = false,
|
2023-02-15 03:55:40 +00:00
|
|
|
|
2023-01-26 23:26:26 +00:00
|
|
|
auto_scram = false,
|
2023-02-15 03:55:40 +00:00
|
|
|
---@type ascram_status
|
|
|
|
ascram_status = {
|
|
|
|
matrix_dc = false,
|
|
|
|
matrix_fill = false,
|
|
|
|
crit_alarm = false,
|
|
|
|
radiation = false,
|
|
|
|
gen_fault = false
|
|
|
|
},
|
2023-01-26 23:26:26 +00:00
|
|
|
|
2023-07-08 20:57:13 +00:00
|
|
|
---@type WASTE_PRODUCT
|
|
|
|
auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
|
|
|
auto_pu_fallback_active = false,
|
|
|
|
|
2023-02-14 20:15:34 +00:00
|
|
|
radiation = types.new_zero_radiation_reading(),
|
2023-01-26 23:26:26 +00:00
|
|
|
|
2023-04-18 17:55:18 +00:00
|
|
|
save_cfg_ack = __generic_ack,
|
|
|
|
start_ack = __generic_ack,
|
|
|
|
stop_ack = __generic_ack,
|
|
|
|
scram_ack = __generic_ack,
|
|
|
|
ack_alarms_ack = __generic_ack,
|
2022-12-18 18:56:04 +00:00
|
|
|
|
2022-12-10 18:58:17 +00:00
|
|
|
ps = psil.create(),
|
|
|
|
|
|
|
|
induction_ps_tbl = {},
|
2022-12-18 18:56:04 +00:00
|
|
|
induction_data_tbl = {},
|
|
|
|
|
2023-07-08 20:57:13 +00:00
|
|
|
sps_ps_tbl = {},
|
|
|
|
sps_data_tbl = {},
|
|
|
|
|
2023-07-15 17:16:36 +00:00
|
|
|
tank_ps_tbl = {},
|
|
|
|
tank_data_tbl = {},
|
|
|
|
|
2022-12-18 18:56:04 +00:00
|
|
|
env_d_ps = psil.create(),
|
|
|
|
env_d_data = {}
|
2022-07-06 03:48:01 +00:00
|
|
|
}
|
|
|
|
|
2023-07-08 20:57:13 +00:00
|
|
|
-- create induction and SPS tables (currently only 1 of each is supported)
|
|
|
|
table.insert(io.facility.induction_ps_tbl, psil.create())
|
|
|
|
table.insert(io.facility.induction_data_tbl, {})
|
|
|
|
table.insert(io.facility.sps_ps_tbl, psil.create())
|
|
|
|
table.insert(io.facility.sps_data_tbl, {})
|
2022-12-10 18:58:17 +00:00
|
|
|
|
2022-09-07 02:38:27 +00:00
|
|
|
io.units = {}
|
2022-07-06 03:48:01 +00:00
|
|
|
for i = 1, conf.num_units do
|
2022-12-18 18:56:04 +00:00
|
|
|
local function ack(alarm) process.ack_alarm(i, alarm) end
|
|
|
|
local function reset(alarm) process.reset_alarm(i, alarm) end
|
2022-11-26 21:18:31 +00:00
|
|
|
|
2023-01-23 20:10:41 +00:00
|
|
|
---@class ioctl_unit
|
2022-07-06 03:48:01 +00:00
|
|
|
local entry = {
|
2023-02-14 03:11:31 +00:00
|
|
|
unit_id = i,
|
2022-07-06 03:48:01 +00:00
|
|
|
|
2022-09-07 15:10:20 +00:00
|
|
|
num_boilers = 0,
|
|
|
|
num_turbines = 0,
|
2023-07-08 20:57:13 +00:00
|
|
|
num_snas = 0,
|
2022-09-07 15:10:20 +00:00
|
|
|
|
2022-09-07 02:38:27 +00:00
|
|
|
control_state = false,
|
|
|
|
burn_rate_cmd = 0.0,
|
2023-02-14 20:15:34 +00:00
|
|
|
radiation = types.new_zero_radiation_reading(),
|
2023-07-08 20:57:13 +00:00
|
|
|
sna_prod_rate = 0.0,
|
|
|
|
|
|
|
|
waste_mode = types.WASTE_MODE.MANUAL_PLUTONIUM,
|
|
|
|
waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
2022-09-07 02:38:27 +00:00
|
|
|
|
2023-04-18 17:55:18 +00:00
|
|
|
-- auto control group
|
|
|
|
a_group = 0,
|
2022-12-18 18:56:04 +00:00
|
|
|
|
|
|
|
start = function () process.start(i) end,
|
|
|
|
scram = function () process.scram(i) end,
|
|
|
|
reset_rps = function () process.reset_rps(i) end,
|
|
|
|
ack_alarms = function () process.ack_all_alarms(i) end,
|
2023-07-08 20:57:13 +00:00
|
|
|
set_burn = function (rate) process.set_rate(i, rate) end, ---@param rate number burn rate
|
|
|
|
set_waste = function (mode) process.set_unit_waste(i, mode) end, ---@param mode WASTE_MODE waste processing mode
|
2022-12-18 18:56:04 +00:00
|
|
|
|
2023-07-08 20:57:13 +00:00
|
|
|
set_group = function (grp) process.set_group(i, grp) end, ---@param grp integer|0 group ID or 0 for manual
|
2022-11-26 21:18:31 +00:00
|
|
|
|
2023-04-18 17:55:18 +00:00
|
|
|
start_ack = __generic_ack,
|
|
|
|
scram_ack = __generic_ack,
|
|
|
|
reset_rps_ack = __generic_ack,
|
|
|
|
ack_alarms_ack = __generic_ack,
|
|
|
|
set_burn_ack = __generic_ack,
|
|
|
|
set_waste_ack = __generic_ack,
|
2022-11-26 21:18:31 +00:00
|
|
|
|
|
|
|
alarm_callbacks = {
|
2022-12-18 18:56:04 +00:00
|
|
|
c_breach = { ack = function () ack(1) end, reset = function () reset(1) end },
|
|
|
|
radiation = { ack = function () ack(2) end, reset = function () reset(2) end },
|
|
|
|
r_lost = { ack = function () ack(3) end, reset = function () reset(3) end },
|
|
|
|
dmg_crit = { ack = function () ack(4) end, reset = function () reset(4) end },
|
|
|
|
damage = { ack = function () ack(5) end, reset = function () reset(5) end },
|
|
|
|
over_temp = { ack = function () ack(6) end, reset = function () reset(6) end },
|
|
|
|
high_temp = { ack = function () ack(7) end, reset = function () reset(7) end },
|
|
|
|
waste_leak = { ack = function () ack(8) end, reset = function () reset(8) end },
|
|
|
|
waste_high = { ack = function () ack(9) end, reset = function () reset(9) end },
|
|
|
|
rps_trans = { ack = function () ack(10) end, reset = function () reset(10) end },
|
|
|
|
rcs_trans = { ack = function () ack(11) end, reset = function () reset(11) end },
|
|
|
|
t_trip = { ack = function () ack(12) end, reset = function () reset(12) end }
|
2022-11-26 21:18:31 +00:00
|
|
|
},
|
|
|
|
|
2022-12-04 18:59:10 +00:00
|
|
|
---@type alarms
|
|
|
|
alarms = {
|
2022-12-18 18:56:04 +00:00
|
|
|
ALARM_STATE.INACTIVE, -- containment breach
|
|
|
|
ALARM_STATE.INACTIVE, -- containment radiation
|
|
|
|
ALARM_STATE.INACTIVE, -- reactor lost
|
|
|
|
ALARM_STATE.INACTIVE, -- damage critical
|
|
|
|
ALARM_STATE.INACTIVE, -- reactor taking damage
|
|
|
|
ALARM_STATE.INACTIVE, -- reactor over temperature
|
|
|
|
ALARM_STATE.INACTIVE, -- reactor high temperature
|
|
|
|
ALARM_STATE.INACTIVE, -- waste leak
|
|
|
|
ALARM_STATE.INACTIVE, -- waste level high
|
|
|
|
ALARM_STATE.INACTIVE, -- RPS transient
|
|
|
|
ALARM_STATE.INACTIVE, -- RCS transient
|
|
|
|
ALARM_STATE.INACTIVE -- turbine trip
|
2022-12-04 18:59:10 +00:00
|
|
|
},
|
2022-07-06 03:48:01 +00:00
|
|
|
|
2023-04-18 17:55:18 +00:00
|
|
|
annunciator = {}, ---@type annunciator
|
2023-02-02 02:55:02 +00:00
|
|
|
|
2023-02-03 04:07:09 +00:00
|
|
|
unit_ps = psil.create(),
|
2023-04-18 17:55:18 +00:00
|
|
|
reactor_data = {}, ---@type reactor_db
|
2022-07-06 03:48:01 +00:00
|
|
|
|
|
|
|
boiler_ps_tbl = {},
|
|
|
|
boiler_data_tbl = {},
|
|
|
|
|
|
|
|
turbine_ps_tbl = {},
|
2023-07-15 17:16:36 +00:00
|
|
|
turbine_data_tbl = {},
|
|
|
|
|
|
|
|
tank_ps_tbl = {},
|
|
|
|
tank_data_tbl = {}
|
2022-07-06 03:48:01 +00:00
|
|
|
}
|
|
|
|
|
2022-10-20 16:22:03 +00:00
|
|
|
-- create boiler tables
|
2022-07-06 03:48:01 +00:00
|
|
|
for _ = 1, conf.defs[(i * 2) - 1] do
|
2022-09-21 19:53:51 +00:00
|
|
|
local data = {} ---@type boilerv_session_db
|
2022-07-06 03:48:01 +00:00
|
|
|
table.insert(entry.boiler_ps_tbl, psil.create())
|
|
|
|
table.insert(entry.boiler_data_tbl, data)
|
|
|
|
end
|
|
|
|
|
2022-10-20 16:22:03 +00:00
|
|
|
-- create turbine tables
|
2022-07-06 03:48:01 +00:00
|
|
|
for _ = 1, conf.defs[i * 2] do
|
2022-09-21 19:53:51 +00:00
|
|
|
local data = {} ---@type turbinev_session_db
|
2022-07-06 03:48:01 +00:00
|
|
|
table.insert(entry.turbine_ps_tbl, psil.create())
|
|
|
|
table.insert(entry.turbine_data_tbl, data)
|
|
|
|
end
|
|
|
|
|
2022-09-07 15:10:20 +00:00
|
|
|
entry.num_boilers = #entry.boiler_data_tbl
|
|
|
|
entry.num_turbines = #entry.turbine_data_tbl
|
|
|
|
|
2022-09-07 02:38:27 +00:00
|
|
|
table.insert(io.units, entry)
|
2022-07-06 03:48:01 +00:00
|
|
|
end
|
2023-01-23 20:10:41 +00:00
|
|
|
|
|
|
|
-- pass IO control here since it can't be require'd due to a require loop
|
|
|
|
process.init(io, comms)
|
2022-07-06 03:48:01 +00:00
|
|
|
end
|
|
|
|
|
2023-07-15 17:16:36 +00:00
|
|
|
--#region Front Panel PSIL
|
|
|
|
|
2023-07-10 03:31:56 +00:00
|
|
|
-- toggle heartbeat indicator
|
|
|
|
function iocontrol.heartbeat() io.fp.ps.toggle("heartbeat") end
|
|
|
|
|
|
|
|
-- report presence of the wireless modem
|
|
|
|
---@param has_modem boolean
|
|
|
|
function iocontrol.fp_has_modem(has_modem) io.fp.ps.publish("has_modem", has_modem) end
|
|
|
|
|
|
|
|
-- report presence of the speaker
|
|
|
|
---@param has_speaker boolean
|
|
|
|
function iocontrol.fp_has_speaker(has_speaker) io.fp.ps.publish("has_speaker", has_speaker) end
|
|
|
|
|
|
|
|
-- report supervisor link state
|
|
|
|
---@param state integer
|
|
|
|
function iocontrol.fp_link_state(state) io.fp.ps.publish("link_state", state) end
|
|
|
|
|
2023-07-12 00:32:10 +00:00
|
|
|
-- report monitor connection state
|
|
|
|
---@param id integer unit ID or 0 for main
|
|
|
|
function iocontrol.fp_monitor_state(id, connected)
|
|
|
|
local name = "main_monitor"
|
|
|
|
if id > 0 then name = "unit_monitor_" .. id end
|
|
|
|
io.fp.ps.publish(name, connected)
|
|
|
|
end
|
|
|
|
|
2023-07-10 03:31:56 +00:00
|
|
|
-- report PKT firmware version and PKT session connection state
|
|
|
|
---@param session_id integer PKT session
|
|
|
|
---@param fw string firmware version
|
|
|
|
---@param s_addr integer PKT computer ID
|
|
|
|
function iocontrol.fp_pkt_connected(session_id, fw, s_addr)
|
|
|
|
io.fp.ps.publish("pkt_" .. session_id .. "_fw", fw)
|
|
|
|
io.fp.ps.publish("pkt_" .. session_id .. "_addr", util.sprintf("@ C% 3d", s_addr))
|
|
|
|
pgi.create_pkt_entry(session_id)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- report PKT session disconnected
|
|
|
|
---@param session_id integer PKT session
|
|
|
|
function iocontrol.fp_pkt_disconnected(session_id)
|
|
|
|
pgi.delete_pkt_entry(session_id)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- transmit PKT session RTT
|
|
|
|
---@param session_id integer PKT session
|
|
|
|
---@param rtt integer round trip time
|
|
|
|
function iocontrol.fp_pkt_rtt(session_id, rtt)
|
|
|
|
io.fp.ps.publish("pkt_" .. session_id .. "_rtt", rtt)
|
|
|
|
|
|
|
|
if rtt > HIGH_RTT then
|
|
|
|
io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.red)
|
|
|
|
elseif rtt > WARN_RTT then
|
|
|
|
io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.yellow_hc)
|
|
|
|
else
|
|
|
|
io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.green)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-07-15 17:16:36 +00:00
|
|
|
--#endregion
|
|
|
|
|
|
|
|
--#region Builds
|
|
|
|
|
|
|
|
-- record and publish multiblock RTU build data
|
|
|
|
---@param id integer
|
|
|
|
---@param entry table
|
|
|
|
---@param data_tbl table
|
|
|
|
---@param ps_tbl table
|
|
|
|
---@param create boolean? true to create an entry if non exists, false to fail on missing
|
|
|
|
---@return boolean ok true if data saved, false if invalid ID
|
|
|
|
local function _record_multiblock_build(id, entry, data_tbl, ps_tbl, create)
|
|
|
|
local exists = type(data_tbl[id]) == "table"
|
|
|
|
if exists or create then
|
|
|
|
if not exists then
|
|
|
|
ps_tbl[id] = psil.create()
|
|
|
|
data_tbl[id] = {}
|
|
|
|
end
|
|
|
|
|
|
|
|
data_tbl[id].formed = entry[1] ---@type boolean
|
|
|
|
data_tbl[id].build = entry[2] ---@type table
|
|
|
|
|
|
|
|
ps_tbl[id].publish("formed", entry[1])
|
|
|
|
|
|
|
|
for key, val in pairs(data_tbl[id].build) do ps_tbl[id].publish(key, val) end
|
|
|
|
end
|
|
|
|
|
|
|
|
return exists or (create == true)
|
|
|
|
end
|
|
|
|
|
2022-12-10 18:58:17 +00:00
|
|
|
-- populate facility structure builds
|
|
|
|
---@param build table
|
2022-09-03 14:50:14 +00:00
|
|
|
---@return boolean valid
|
2022-12-10 18:58:17 +00:00
|
|
|
function iocontrol.record_facility_builds(build)
|
2023-02-23 04:09:47 +00:00
|
|
|
local valid = true
|
|
|
|
|
2022-12-10 18:58:17 +00:00
|
|
|
if type(build) == "table" then
|
|
|
|
local fac = io.facility
|
|
|
|
|
|
|
|
-- induction matricies
|
|
|
|
if type(build.induction) == "table" then
|
|
|
|
for id, matrix in pairs(build.induction) do
|
2023-07-15 17:16:36 +00:00
|
|
|
if not _record_multiblock_build(id, matrix, fac.induction_data_tbl, fac.induction_ps_tbl) then
|
2022-12-10 18:58:17 +00:00
|
|
|
log.debug(util.c("iocontrol.record_facility_builds: invalid induction matrix id ", id))
|
2023-02-23 04:09:47 +00:00
|
|
|
valid = false
|
2022-10-03 01:17:13 +00:00
|
|
|
end
|
2022-12-10 18:58:17 +00:00
|
|
|
end
|
|
|
|
end
|
2023-07-08 20:57:13 +00:00
|
|
|
|
|
|
|
-- SPS
|
|
|
|
if type(build.sps) == "table" then
|
|
|
|
for id, sps in pairs(build.sps) do
|
2023-07-15 17:16:36 +00:00
|
|
|
if not _record_multiblock_build(id, sps, fac.sps_data_tbl, fac.sps_ps_tbl) then
|
2023-07-08 20:57:13 +00:00
|
|
|
log.debug(util.c("iocontrol.record_facility_builds: invalid SPS id ", id))
|
|
|
|
valid = false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2023-07-15 17:16:36 +00:00
|
|
|
|
|
|
|
-- dynamic tanks
|
|
|
|
if type(build.tanks) == "table" then
|
|
|
|
for id, tank in pairs(build.tanks) do
|
|
|
|
_record_multiblock_build(id, tank, fac.tank_data_tbl, fac.tank_ps_tbl, true)
|
|
|
|
end
|
|
|
|
end
|
2022-12-10 18:58:17 +00:00
|
|
|
else
|
2023-02-23 04:09:47 +00:00
|
|
|
log.debug("facility builds not a table")
|
|
|
|
valid = false
|
2022-12-10 18:58:17 +00:00
|
|
|
end
|
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
return valid
|
2022-12-10 18:58:17 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- populate unit structure builds
|
|
|
|
---@param builds table
|
|
|
|
---@return boolean valid
|
|
|
|
function iocontrol.record_unit_builds(builds)
|
2023-02-23 04:09:47 +00:00
|
|
|
local valid = true
|
|
|
|
|
2022-12-10 18:58:17 +00:00
|
|
|
-- note: if not all units and RTUs are connected, some will be nil
|
|
|
|
for id, build in pairs(builds) do
|
2023-01-23 20:10:41 +00:00
|
|
|
local unit = io.units[id] ---@type ioctl_unit
|
2022-12-10 18:58:17 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
local log_header = util.c("iocontrol.record_unit_builds[UNIT ", id, "]: ")
|
|
|
|
|
2022-12-10 18:58:17 +00:00
|
|
|
if type(build) ~= "table" then
|
2023-02-23 04:09:47 +00:00
|
|
|
log.debug(log_header .. "build not a table")
|
|
|
|
valid = false
|
2022-12-10 18:58:17 +00:00
|
|
|
elseif type(unit) ~= "table" then
|
2023-02-23 04:09:47 +00:00
|
|
|
log.debug(log_header .. "invalid unit id")
|
|
|
|
valid = false
|
|
|
|
else
|
|
|
|
-- reactor build
|
|
|
|
if type(build.reactor) == "table" then
|
|
|
|
unit.reactor_data.mek_struct = build.reactor ---@type mek_struct
|
|
|
|
for key, val in pairs(unit.reactor_data.mek_struct) do
|
|
|
|
unit.unit_ps.publish(key, val)
|
|
|
|
end
|
2022-12-10 18:58:17 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
if (type(unit.reactor_data.mek_struct.length) == "number") and (unit.reactor_data.mek_struct.length ~= 0) and
|
|
|
|
(type(unit.reactor_data.mek_struct.width) == "number") and (unit.reactor_data.mek_struct.width ~= 0) then
|
|
|
|
unit.unit_ps.publish("size", { unit.reactor_data.mek_struct.length, unit.reactor_data.mek_struct.width })
|
|
|
|
end
|
2022-12-10 18:58:17 +00:00
|
|
|
end
|
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
-- boiler builds
|
|
|
|
if type(build.boilers) == "table" then
|
|
|
|
for b_id, boiler in pairs(build.boilers) do
|
2023-07-15 17:16:36 +00:00
|
|
|
if not _record_multiblock_build(b_id, boiler, unit.boiler_data_tbl, unit.boiler_ps_tbl) then
|
2023-02-23 04:09:47 +00:00
|
|
|
log.debug(util.c(log_header, "invalid boiler id ", b_id))
|
|
|
|
valid = false
|
2022-12-10 18:58:17 +00:00
|
|
|
end
|
2022-10-03 01:17:13 +00:00
|
|
|
end
|
2022-09-03 14:50:14 +00:00
|
|
|
end
|
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
-- turbine builds
|
|
|
|
if type(build.turbines) == "table" then
|
|
|
|
for t_id, turbine in pairs(build.turbines) do
|
2023-07-15 17:16:36 +00:00
|
|
|
if not _record_multiblock_build(t_id, turbine, unit.turbine_data_tbl, unit.turbine_ps_tbl) then
|
2023-02-23 04:09:47 +00:00
|
|
|
log.debug(util.c(log_header, "invalid turbine id ", t_id))
|
|
|
|
valid = false
|
2022-10-03 01:17:13 +00:00
|
|
|
end
|
2022-09-03 14:50:14 +00:00
|
|
|
end
|
|
|
|
end
|
2023-07-15 17:16:36 +00:00
|
|
|
|
|
|
|
-- dynamic tank builds
|
|
|
|
if type(build.tanks) == "table" then
|
|
|
|
for d_id, d_tank in pairs(build.tanks) do
|
|
|
|
_record_multiblock_build(d_id, d_tank, unit.tank_data_tbl, unit.tank_ps_tbl, true)
|
|
|
|
end
|
|
|
|
end
|
2022-12-10 18:58:17 +00:00
|
|
|
end
|
|
|
|
end
|
2022-09-03 14:50:14 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
return valid
|
2022-12-10 18:58:17 +00:00
|
|
|
end
|
2022-10-03 01:17:13 +00:00
|
|
|
|
2023-07-15 17:16:36 +00:00
|
|
|
--#endregion
|
|
|
|
|
|
|
|
--#region Statuses
|
|
|
|
|
|
|
|
-- record and publish multiblock status data
|
|
|
|
---@param entry any
|
|
|
|
---@param data imatrix_session_db|sps_session_db|dynamicv_session_db|turbinev_session_db|boilerv_session_db
|
|
|
|
---@param ps psil
|
|
|
|
---@return boolean is_faulted
|
|
|
|
local function _record_multiblock_status(entry, data, ps)
|
|
|
|
local is_faulted = entry[1] ---@type boolean
|
|
|
|
data.formed = entry[2] ---@type boolean
|
|
|
|
data.state = entry[3] ---@type table
|
|
|
|
data.tanks = entry[4] ---@type table
|
|
|
|
|
|
|
|
ps.publish("formed", data.formed)
|
|
|
|
ps.publish("faulted", is_faulted)
|
|
|
|
|
|
|
|
for key, val in pairs(data.state) do ps.publish(key, val) end
|
|
|
|
for key, val in pairs(data.tanks) do ps.publish(key, val) end
|
|
|
|
|
|
|
|
return is_faulted
|
|
|
|
end
|
|
|
|
|
2022-12-10 18:58:17 +00:00
|
|
|
-- update facility status
|
|
|
|
---@param status table
|
|
|
|
---@return boolean valid
|
|
|
|
function iocontrol.update_facility_status(status)
|
2023-02-23 04:09:47 +00:00
|
|
|
local valid = true
|
2022-12-10 18:58:17 +00:00
|
|
|
local log_header = util.c("iocontrol.update_facility_status: ")
|
2023-02-23 04:09:47 +00:00
|
|
|
|
2022-12-10 18:58:17 +00:00
|
|
|
if type(status) ~= "table" then
|
2023-02-23 04:09:47 +00:00
|
|
|
log.debug(util.c(log_header, "status not a table"))
|
|
|
|
valid = false
|
2022-12-10 18:58:17 +00:00
|
|
|
else
|
|
|
|
local fac = io.facility
|
2022-10-03 01:17:13 +00:00
|
|
|
|
2023-01-26 23:26:26 +00:00
|
|
|
-- auto control status information
|
|
|
|
|
|
|
|
local ctl_status = status[1]
|
|
|
|
|
2023-07-08 20:57:13 +00:00
|
|
|
if type(ctl_status) == "table" and #ctl_status == 16 then
|
2023-02-04 18:47:00 +00:00
|
|
|
fac.all_sys_ok = ctl_status[1]
|
|
|
|
fac.auto_ready = ctl_status[2]
|
2023-02-23 04:09:47 +00:00
|
|
|
|
|
|
|
if type(ctl_status[3]) == "number" then
|
2023-03-04 06:37:15 +00:00
|
|
|
fac.auto_active = ctl_status[3] > PROCESS.INACTIVE
|
2023-02-23 04:09:47 +00:00
|
|
|
else
|
|
|
|
fac.auto_active = false
|
|
|
|
valid = false
|
|
|
|
end
|
|
|
|
|
2023-02-04 18:47:00 +00:00
|
|
|
fac.auto_ramping = ctl_status[4]
|
2023-02-07 05:32:50 +00:00
|
|
|
fac.auto_saturated = ctl_status[5]
|
2023-02-15 03:55:40 +00:00
|
|
|
|
2023-02-07 05:32:50 +00:00
|
|
|
fac.auto_scram = ctl_status[6]
|
2023-02-15 03:55:40 +00:00
|
|
|
fac.ascram_status.matrix_dc = ctl_status[7]
|
|
|
|
fac.ascram_status.matrix_fill = ctl_status[8]
|
|
|
|
fac.ascram_status.crit_alarm = ctl_status[9]
|
|
|
|
fac.ascram_status.radiation = ctl_status[10]
|
|
|
|
fac.ascram_status.gen_fault = ctl_status[11]
|
|
|
|
|
|
|
|
fac.status_line_1 = ctl_status[12]
|
|
|
|
fac.status_line_2 = ctl_status[13]
|
2023-02-04 18:47:00 +00:00
|
|
|
|
|
|
|
fac.ps.publish("all_sys_ok", fac.all_sys_ok)
|
2023-02-03 03:58:51 +00:00
|
|
|
fac.ps.publish("auto_ready", fac.auto_ready)
|
2023-01-26 23:26:26 +00:00
|
|
|
fac.ps.publish("auto_active", fac.auto_active)
|
|
|
|
fac.ps.publish("auto_ramping", fac.auto_ramping)
|
2023-02-07 05:32:50 +00:00
|
|
|
fac.ps.publish("auto_saturated", fac.auto_saturated)
|
2023-01-26 23:26:26 +00:00
|
|
|
fac.ps.publish("auto_scram", fac.auto_scram)
|
2023-02-15 03:55:40 +00:00
|
|
|
fac.ps.publish("as_matrix_dc", fac.ascram_status.matrix_dc)
|
|
|
|
fac.ps.publish("as_matrix_fill", fac.ascram_status.matrix_fill)
|
|
|
|
fac.ps.publish("as_crit_alarm", fac.ascram_status.crit_alarm)
|
|
|
|
fac.ps.publish("as_radiation", fac.ascram_status.radiation)
|
|
|
|
fac.ps.publish("as_gen_fault", fac.ascram_status.gen_fault)
|
2023-02-02 02:55:02 +00:00
|
|
|
fac.ps.publish("status_line_1", fac.status_line_1)
|
|
|
|
fac.ps.publish("status_line_2", fac.status_line_2)
|
|
|
|
|
2023-02-15 03:55:40 +00:00
|
|
|
local group_map = ctl_status[14]
|
2023-02-02 02:55:02 +00:00
|
|
|
|
|
|
|
if (type(group_map) == "table") and (#group_map == fac.num_units) then
|
|
|
|
local names = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" }
|
|
|
|
for i = 1, #group_map do
|
2023-02-04 18:47:00 +00:00
|
|
|
io.units[i].a_group = group_map[i]
|
|
|
|
io.units[i].unit_ps.publish("auto_group_id", group_map[i])
|
2023-02-03 04:07:09 +00:00
|
|
|
io.units[i].unit_ps.publish("auto_group", names[group_map[i] + 1])
|
2023-02-02 02:55:02 +00:00
|
|
|
end
|
|
|
|
end
|
2023-07-08 20:57:13 +00:00
|
|
|
|
|
|
|
fac.auto_current_waste_product = ctl_status[15]
|
|
|
|
fac.auto_pu_fallback_active = ctl_status[16]
|
|
|
|
|
|
|
|
fac.ps.publish("current_waste_product", fac.auto_current_waste_product)
|
|
|
|
fac.ps.publish("pu_fallback_active", fac.auto_pu_fallback_active)
|
2023-01-26 23:26:26 +00:00
|
|
|
else
|
2023-02-07 05:32:50 +00:00
|
|
|
log.debug(log_header .. "control status not a table or length mismatch")
|
2023-02-23 04:09:47 +00:00
|
|
|
valid = false
|
2023-01-26 23:26:26 +00:00
|
|
|
end
|
|
|
|
|
2022-12-10 18:58:17 +00:00
|
|
|
-- RTU statuses
|
|
|
|
|
2023-01-26 23:26:26 +00:00
|
|
|
local rtu_statuses = status[2]
|
2022-12-10 18:58:17 +00:00
|
|
|
|
2023-02-16 00:52:28 +00:00
|
|
|
fac.rtu_count = 0
|
2023-02-23 04:09:47 +00:00
|
|
|
|
2022-12-10 18:58:17 +00:00
|
|
|
if type(rtu_statuses) == "table" then
|
2023-02-16 00:52:28 +00:00
|
|
|
-- connected RTU count
|
|
|
|
fac.rtu_count = rtu_statuses.count
|
|
|
|
|
2023-01-26 23:26:26 +00:00
|
|
|
-- power statistics
|
|
|
|
if type(rtu_statuses.power) == "table" then
|
2023-02-04 02:05:21 +00:00
|
|
|
fac.induction_ps_tbl[1].publish("avg_charge", rtu_statuses.power[1])
|
|
|
|
fac.induction_ps_tbl[1].publish("avg_inflow", rtu_statuses.power[2])
|
|
|
|
fac.induction_ps_tbl[1].publish("avg_outflow", rtu_statuses.power[3])
|
2023-01-26 23:26:26 +00:00
|
|
|
else
|
|
|
|
log.debug(log_header .. "power statistics list not a table")
|
2023-02-23 04:09:47 +00:00
|
|
|
valid = false
|
2023-01-26 23:26:26 +00:00
|
|
|
end
|
|
|
|
|
2022-12-10 18:58:17 +00:00
|
|
|
-- induction matricies statuses
|
|
|
|
if type(rtu_statuses.induction) == "table" then
|
|
|
|
for id = 1, #fac.induction_ps_tbl do
|
|
|
|
if rtu_statuses.induction[id] == nil then
|
|
|
|
-- disconnected
|
|
|
|
fac.induction_ps_tbl[id].publish("computed_status", 1)
|
2022-10-03 01:17:13 +00:00
|
|
|
end
|
2022-09-03 14:50:14 +00:00
|
|
|
end
|
2022-12-10 18:58:17 +00:00
|
|
|
|
|
|
|
for id, matrix in pairs(rtu_statuses.induction) do
|
|
|
|
if type(fac.induction_data_tbl[id]) == "table" then
|
2023-07-15 17:16:36 +00:00
|
|
|
local data = fac.induction_data_tbl[id] ---@type imatrix_session_db
|
|
|
|
local ps = fac.induction_ps_tbl[id] ---@type psil
|
2022-12-10 18:58:17 +00:00
|
|
|
|
2023-07-15 17:16:36 +00:00
|
|
|
local rtu_faulted = _record_multiblock_status(matrix, data, ps)
|
2022-12-10 18:58:17 +00:00
|
|
|
|
2023-07-15 17:16:36 +00:00
|
|
|
if rtu_faulted then
|
|
|
|
ps.publish("computed_status", 3) -- faulted
|
|
|
|
elseif data.formed then
|
|
|
|
if data.tanks.energy_fill >= 0.99 then
|
|
|
|
ps.publish("computed_status", 6) -- full
|
2022-12-10 18:58:17 +00:00
|
|
|
elseif data.tanks.energy_fill <= 0.01 then
|
2023-07-15 17:16:36 +00:00
|
|
|
ps.publish("computed_status", 5) -- empty
|
2022-12-10 18:58:17 +00:00
|
|
|
else
|
2023-07-15 17:16:36 +00:00
|
|
|
ps.publish("computed_status", 4) -- on-line
|
2022-12-10 18:58:17 +00:00
|
|
|
end
|
|
|
|
else
|
2023-07-15 17:16:36 +00:00
|
|
|
ps.publish("computed_status", 2) -- not formed
|
2022-12-10 18:58:17 +00:00
|
|
|
end
|
|
|
|
else
|
|
|
|
log.debug(util.c(log_header, "invalid induction matrix id ", id))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
log.debug(log_header .. "induction matrix list not a table")
|
2023-02-23 04:09:47 +00:00
|
|
|
valid = false
|
2022-09-03 14:50:14 +00:00
|
|
|
end
|
2023-02-14 03:11:31 +00:00
|
|
|
|
2023-07-08 20:57:13 +00:00
|
|
|
-- SPS statuses
|
|
|
|
if type(rtu_statuses.sps) == "table" then
|
|
|
|
for id = 1, #fac.sps_ps_tbl do
|
|
|
|
if rtu_statuses.sps[id] == nil then
|
|
|
|
-- disconnected
|
|
|
|
fac.sps_ps_tbl[id].publish("computed_status", 1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for id, sps in pairs(rtu_statuses.sps) do
|
|
|
|
if type(fac.sps_data_tbl[id]) == "table" then
|
2023-07-15 17:16:36 +00:00
|
|
|
local data = fac.sps_data_tbl[id] ---@type sps_session_db
|
|
|
|
local ps = fac.sps_ps_tbl[id] ---@type psil
|
2023-07-08 20:57:13 +00:00
|
|
|
|
2023-07-15 17:16:36 +00:00
|
|
|
local rtu_faulted = _record_multiblock_status(sps, data, ps)
|
2023-07-08 20:57:13 +00:00
|
|
|
|
2023-07-15 17:16:36 +00:00
|
|
|
if rtu_faulted then
|
|
|
|
ps.publish("computed_status", 3) -- faulted
|
|
|
|
elseif data.formed then
|
|
|
|
if data.state.process_rate > 0 then
|
|
|
|
ps.publish("computed_status", 5) -- active
|
2023-07-08 20:57:13 +00:00
|
|
|
else
|
2023-07-15 17:16:36 +00:00
|
|
|
ps.publish("computed_status", 4) -- idle
|
2023-07-08 20:57:13 +00:00
|
|
|
end
|
|
|
|
else
|
2023-07-15 17:16:36 +00:00
|
|
|
ps.publish("computed_status", 2) -- not formed
|
2023-07-08 20:57:13 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
io.facility.ps.publish("am_rate", data.state.process_rate * 1000)
|
|
|
|
else
|
|
|
|
log.debug(util.c(log_header, "invalid sps id ", id))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
log.debug(log_header .. "sps list not a table")
|
|
|
|
valid = false
|
|
|
|
end
|
|
|
|
|
2023-07-15 17:16:36 +00:00
|
|
|
-- dynamic tank statuses
|
|
|
|
if type(rtu_statuses.tanks) == "table" then
|
|
|
|
for id = 1, #fac.tank_ps_tbl do
|
|
|
|
if rtu_statuses.tanks[id] == nil then
|
|
|
|
-- disconnected
|
|
|
|
fac.tank_ps_tbl[id].publish("computed_status", 1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for id, tank in pairs(rtu_statuses.tanks) do
|
|
|
|
if type(fac.tank_data_tbl[id]) == "table" then
|
|
|
|
local data = fac.tank_data_tbl[id] ---@type dynamicv_session_db
|
|
|
|
local ps = fac.tank_ps_tbl[id] ---@type psil
|
|
|
|
|
|
|
|
local rtu_faulted = _record_multiblock_status(tank, data, ps)
|
|
|
|
|
|
|
|
if rtu_faulted then
|
|
|
|
ps.publish("computed_status", 3) -- faulted
|
|
|
|
elseif data.formed then
|
|
|
|
if data.tanks.fill >= 0.99 then
|
|
|
|
ps.publish("computed_status", 6) -- full
|
|
|
|
elseif data.tanks.fill < 0.20 then
|
|
|
|
ps.publish("computed_status", 5) -- low
|
|
|
|
else
|
|
|
|
ps.publish("computed_status", 4) -- on-line
|
|
|
|
end
|
|
|
|
else
|
|
|
|
ps.publish("computed_status", 2) -- not formed
|
|
|
|
end
|
|
|
|
else
|
|
|
|
log.debug(util.c(log_header, "invalid dynamic tank id ", id))
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
log.debug(log_header .. "dyanmic tank list not a table")
|
|
|
|
valid = false
|
|
|
|
end
|
|
|
|
|
2023-02-14 03:11:31 +00:00
|
|
|
-- environment detector status
|
|
|
|
if type(rtu_statuses.rad_mon) == "table" then
|
|
|
|
if #rtu_statuses.rad_mon > 0 then
|
|
|
|
local rad_mon = rtu_statuses.rad_mon[1]
|
|
|
|
local rtu_faulted = rad_mon[1] ---@type boolean
|
|
|
|
fac.radiation = rad_mon[2] ---@type number
|
|
|
|
|
2023-02-15 03:55:40 +00:00
|
|
|
fac.ps.publish("rad_computed_status", util.trinary(rtu_faulted, 2, 3))
|
2023-02-14 03:11:31 +00:00
|
|
|
fac.ps.publish("radiation", fac.radiation)
|
|
|
|
else
|
2023-02-16 00:52:28 +00:00
|
|
|
fac.radiation = types.new_zero_radiation_reading()
|
2023-02-15 03:55:40 +00:00
|
|
|
fac.ps.publish("rad_computed_status", 1)
|
2023-02-14 03:11:31 +00:00
|
|
|
end
|
|
|
|
else
|
|
|
|
log.debug(log_header .. "radiation monitor list not a table")
|
2023-02-23 04:09:47 +00:00
|
|
|
valid = false
|
2023-02-14 03:11:31 +00:00
|
|
|
end
|
2023-02-16 00:52:28 +00:00
|
|
|
else
|
|
|
|
log.debug(log_header .. "rtu statuses not a table")
|
2023-02-23 04:09:47 +00:00
|
|
|
valid = false
|
2022-09-03 14:50:14 +00:00
|
|
|
end
|
2023-02-23 04:09:47 +00:00
|
|
|
|
|
|
|
fac.ps.publish("rtu_count", fac.rtu_count)
|
2022-09-03 14:50:14 +00:00
|
|
|
end
|
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
return valid
|
2022-09-03 14:50:14 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- update unit statuses
|
|
|
|
---@param statuses table
|
|
|
|
---@return boolean valid
|
2022-12-10 18:58:17 +00:00
|
|
|
function iocontrol.update_unit_statuses(statuses)
|
2023-02-23 04:09:47 +00:00
|
|
|
local valid = true
|
|
|
|
|
2022-11-26 21:18:31 +00:00
|
|
|
if type(statuses) ~= "table" then
|
2022-12-10 18:58:17 +00:00
|
|
|
log.debug("iocontrol.update_unit_statuses: unit statuses not a table")
|
2023-02-23 04:09:47 +00:00
|
|
|
valid = false
|
2022-11-26 21:18:31 +00:00
|
|
|
elseif #statuses ~= #io.units then
|
2022-12-10 18:58:17 +00:00
|
|
|
log.debug("iocontrol.update_unit_statuses: number of provided unit statuses does not match expected number of units")
|
2023-02-23 04:09:47 +00:00
|
|
|
valid = false
|
2022-09-03 14:50:14 +00:00
|
|
|
else
|
2023-01-26 23:26:26 +00:00
|
|
|
local burn_rate_sum = 0.0
|
2023-07-08 20:57:13 +00:00
|
|
|
local sna_count_sum = 0
|
|
|
|
local pu_rate = 0.0
|
|
|
|
local po_rate = 0.0
|
2023-01-26 23:26:26 +00:00
|
|
|
|
2022-12-10 18:58:17 +00:00
|
|
|
-- get all unit statuses
|
2022-09-03 14:50:14 +00:00
|
|
|
for i = 1, #statuses do
|
2022-12-10 18:58:17 +00:00
|
|
|
local log_header = util.c("iocontrol.update_unit_statuses[unit ", i, "]: ")
|
2023-02-23 04:09:47 +00:00
|
|
|
|
2023-01-23 20:10:41 +00:00
|
|
|
local unit = io.units[i] ---@type ioctl_unit
|
2022-09-03 14:50:14 +00:00
|
|
|
local status = statuses[i]
|
|
|
|
|
2023-07-08 20:57:13 +00:00
|
|
|
local burn_rate = 0.0
|
|
|
|
|
2023-02-14 03:14:47 +00:00
|
|
|
if type(status) ~= "table" or #status ~= 5 then
|
2022-12-05 21:17:09 +00:00
|
|
|
log.debug(log_header .. "invalid status entry in unit statuses (not a table or invalid length)")
|
2023-02-23 04:09:47 +00:00
|
|
|
valid = false
|
|
|
|
else
|
|
|
|
-- reactor PLC status
|
|
|
|
local reactor_status = status[1]
|
2022-09-03 17:10:51 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
if type(reactor_status) ~= "table" then
|
|
|
|
reactor_status = {}
|
|
|
|
log.debug(log_header .. "reactor status not a table")
|
2023-01-26 23:26:26 +00:00
|
|
|
end
|
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
if #reactor_status == 0 then
|
|
|
|
unit.unit_ps.publish("computed_status", 1) -- disconnected
|
|
|
|
elseif #reactor_status == 3 then
|
|
|
|
local mek_status = reactor_status[1]
|
|
|
|
local rps_status = reactor_status[2]
|
|
|
|
local gen_status = reactor_status[3]
|
|
|
|
|
|
|
|
if #gen_status == 6 then
|
|
|
|
unit.reactor_data.last_status_update = gen_status[1]
|
|
|
|
unit.reactor_data.control_state = gen_status[2]
|
|
|
|
unit.reactor_data.rps_tripped = gen_status[3]
|
|
|
|
unit.reactor_data.rps_trip_cause = gen_status[4]
|
|
|
|
unit.reactor_data.no_reactor = gen_status[5]
|
|
|
|
unit.reactor_data.formed = gen_status[6]
|
2022-09-03 17:10:51 +00:00
|
|
|
else
|
2023-02-23 04:09:47 +00:00
|
|
|
log.debug(log_header .. "reactor general status length mismatch")
|
2022-09-03 17:10:51 +00:00
|
|
|
end
|
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
unit.reactor_data.rps_status = rps_status ---@type rps_status
|
|
|
|
unit.reactor_data.mek_status = mek_status ---@type mek_status
|
2022-09-05 23:40:20 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
-- if status hasn't been received, mek_status = {}
|
|
|
|
if type(unit.reactor_data.mek_status.act_burn_rate) == "number" then
|
2023-07-08 20:57:13 +00:00
|
|
|
burn_rate = unit.reactor_data.mek_status.act_burn_rate
|
|
|
|
burn_rate_sum = burn_rate_sum + burn_rate
|
2022-10-25 17:29:57 +00:00
|
|
|
end
|
2022-09-05 23:40:20 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
if unit.reactor_data.mek_status.status then
|
|
|
|
unit.unit_ps.publish("computed_status", 5) -- running
|
|
|
|
else
|
|
|
|
if unit.reactor_data.no_reactor then
|
|
|
|
unit.unit_ps.publish("computed_status", 3) -- faulted
|
|
|
|
elseif not unit.reactor_data.formed then
|
|
|
|
unit.unit_ps.publish("computed_status", 2) -- multiblock not formed
|
|
|
|
elseif unit.reactor_data.rps_status.force_dis then
|
|
|
|
unit.unit_ps.publish("computed_status", 7) -- reactor force disabled
|
|
|
|
elseif unit.reactor_data.rps_tripped and unit.reactor_data.rps_trip_cause ~= "manual" then
|
|
|
|
unit.unit_ps.publish("computed_status", 6) -- SCRAM
|
|
|
|
else
|
|
|
|
unit.unit_ps.publish("computed_status", 4) -- disabled
|
|
|
|
end
|
2022-10-25 17:29:57 +00:00
|
|
|
end
|
2022-09-03 17:10:51 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
for key, val in pairs(unit.reactor_data) do
|
|
|
|
if key ~= "rps_status" and key ~= "mek_struct" and key ~= "mek_status" then
|
|
|
|
unit.unit_ps.publish(key, val)
|
|
|
|
end
|
|
|
|
end
|
2022-09-03 17:10:51 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
if type(unit.reactor_data.rps_status) == "table" then
|
|
|
|
for key, val in pairs(unit.reactor_data.rps_status) do
|
|
|
|
unit.unit_ps.publish(key, val)
|
2022-10-25 17:29:57 +00:00
|
|
|
end
|
|
|
|
end
|
2022-09-03 17:10:51 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
if type(unit.reactor_data.mek_status) == "table" then
|
|
|
|
for key, val in pairs(unit.reactor_data.mek_status) do
|
|
|
|
unit.unit_ps.publish(key, val)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
log.debug(log_header .. "reactor status length mismatch")
|
|
|
|
valid = false
|
|
|
|
end
|
2022-12-10 18:58:17 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
-- RTU statuses
|
|
|
|
local rtu_statuses = status[2]
|
2022-12-10 18:58:17 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
if type(rtu_statuses) == "table" then
|
|
|
|
-- boiler statuses
|
|
|
|
if type(rtu_statuses.boilers) == "table" then
|
|
|
|
for id = 1, #unit.boiler_ps_tbl do
|
|
|
|
if rtu_statuses.boilers[i] == nil then
|
|
|
|
-- disconnected
|
|
|
|
unit.boiler_ps_tbl[id].publish("computed_status", 1)
|
|
|
|
end
|
|
|
|
end
|
2022-12-10 18:58:17 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
for id, boiler in pairs(rtu_statuses.boilers) do
|
|
|
|
if type(unit.boiler_data_tbl[id]) == "table" then
|
2023-07-15 17:16:36 +00:00
|
|
|
local data = unit.boiler_data_tbl[id] ---@type boilerv_session_db
|
|
|
|
local ps = unit.boiler_ps_tbl[id] ---@type psil
|
2023-02-23 04:09:47 +00:00
|
|
|
|
2023-07-15 17:16:36 +00:00
|
|
|
local rtu_faulted = _record_multiblock_status(boiler, data, ps)
|
2023-02-23 04:09:47 +00:00
|
|
|
|
|
|
|
if rtu_faulted then
|
2023-07-15 17:16:36 +00:00
|
|
|
ps.publish("computed_status", 3) -- faulted
|
2023-02-23 04:09:47 +00:00
|
|
|
elseif data.formed then
|
|
|
|
if data.state.boil_rate > 0 then
|
2023-07-15 17:16:36 +00:00
|
|
|
ps.publish("computed_status", 5) -- active
|
2023-02-23 04:09:47 +00:00
|
|
|
else
|
2023-07-15 17:16:36 +00:00
|
|
|
ps.publish("computed_status", 4) -- idle
|
2023-02-23 04:09:47 +00:00
|
|
|
end
|
2022-12-10 18:58:17 +00:00
|
|
|
else
|
2023-07-15 17:16:36 +00:00
|
|
|
ps.publish("computed_status", 2) -- not formed
|
2023-02-23 04:09:47 +00:00
|
|
|
end
|
|
|
|
else
|
|
|
|
log.debug(util.c(log_header, "invalid boiler id ", id))
|
|
|
|
valid = false
|
2022-12-10 18:58:17 +00:00
|
|
|
end
|
2022-10-25 17:29:57 +00:00
|
|
|
end
|
2023-02-23 04:09:47 +00:00
|
|
|
else
|
|
|
|
log.debug(log_header .. "boiler list not a table")
|
|
|
|
valid = false
|
2022-10-25 17:29:57 +00:00
|
|
|
end
|
2022-09-03 17:10:51 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
-- turbine statuses
|
|
|
|
if type(rtu_statuses.turbines) == "table" then
|
|
|
|
for id = 1, #unit.turbine_ps_tbl do
|
|
|
|
if rtu_statuses.turbines[i] == nil then
|
|
|
|
-- disconnected
|
|
|
|
unit.turbine_ps_tbl[id].publish("computed_status", 1)
|
|
|
|
end
|
2022-10-25 17:29:57 +00:00
|
|
|
end
|
2022-12-10 18:58:17 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
for id, turbine in pairs(rtu_statuses.turbines) do
|
|
|
|
if type(unit.turbine_data_tbl[id]) == "table" then
|
|
|
|
local data = unit.turbine_data_tbl[id] ---@type turbinev_session_db
|
2023-07-15 17:16:36 +00:00
|
|
|
local ps = unit.turbine_ps_tbl[id] ---@type psil
|
2023-02-23 04:09:47 +00:00
|
|
|
|
2023-07-15 17:16:36 +00:00
|
|
|
local rtu_faulted = _record_multiblock_status(turbine, data, ps)
|
2023-02-23 04:09:47 +00:00
|
|
|
|
|
|
|
if rtu_faulted then
|
2023-07-15 17:16:36 +00:00
|
|
|
ps.publish("computed_status", 3) -- faulted
|
2023-02-23 04:09:47 +00:00
|
|
|
elseif data.formed then
|
|
|
|
if data.tanks.energy_fill >= 0.99 then
|
2023-07-15 17:16:36 +00:00
|
|
|
ps.publish("computed_status", 6) -- trip
|
2023-02-23 04:09:47 +00:00
|
|
|
elseif data.state.flow_rate < 100 then
|
2023-07-15 17:16:36 +00:00
|
|
|
ps.publish("computed_status", 4) -- idle
|
2023-02-23 04:09:47 +00:00
|
|
|
else
|
2023-07-15 17:16:36 +00:00
|
|
|
ps.publish("computed_status", 5) -- active
|
2023-02-23 04:09:47 +00:00
|
|
|
end
|
2022-12-10 18:58:17 +00:00
|
|
|
else
|
2023-07-15 17:16:36 +00:00
|
|
|
ps.publish("computed_status", 2) -- not formed
|
2022-12-10 18:58:17 +00:00
|
|
|
end
|
2023-07-15 17:16:36 +00:00
|
|
|
else
|
|
|
|
log.debug(util.c(log_header, "invalid turbine id ", id))
|
|
|
|
valid = false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
else
|
|
|
|
log.debug(log_header .. "turbine list not a table")
|
|
|
|
valid = false
|
|
|
|
end
|
2022-09-03 17:10:51 +00:00
|
|
|
|
2023-07-15 17:16:36 +00:00
|
|
|
-- dynamic tank statuses
|
|
|
|
if type(rtu_statuses.tanks) == "table" then
|
|
|
|
for id = 1, #unit.tank_ps_tbl do
|
|
|
|
if rtu_statuses.tanks[i] == nil then
|
|
|
|
-- disconnected
|
|
|
|
unit.tank_ps_tbl[id].publish("computed_status", 1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for id, tank in pairs(rtu_statuses.tanks) do
|
|
|
|
if type(unit.tank_data_tbl[id]) == "table" then
|
|
|
|
local data = unit.tank_data_tbl[id] ---@type dynamicv_session_db
|
|
|
|
local ps = unit.tank_ps_tbl[id] ---@type psil
|
|
|
|
|
|
|
|
local rtu_faulted = _record_multiblock_status(tank, data, ps)
|
2022-09-03 17:10:51 +00:00
|
|
|
|
2023-07-15 17:16:36 +00:00
|
|
|
if rtu_faulted then
|
|
|
|
ps.publish("computed_status", 3) -- faulted
|
|
|
|
elseif data.formed then
|
|
|
|
if data.tanks.fill >= 0.99 then
|
|
|
|
ps.publish("computed_status", 6) -- full
|
|
|
|
elseif data.tanks.fill < 0.20 then
|
|
|
|
ps.publish("computed_status", 5) -- low
|
|
|
|
else
|
|
|
|
ps.publish("computed_status", 5) -- active
|
|
|
|
end
|
|
|
|
else
|
|
|
|
ps.publish("computed_status", 2) -- not formed
|
2023-02-23 04:09:47 +00:00
|
|
|
end
|
|
|
|
else
|
2023-07-15 17:16:36 +00:00
|
|
|
log.debug(util.c(log_header, "invalid dynamic tank id ", id))
|
2023-02-23 04:09:47 +00:00
|
|
|
valid = false
|
2022-12-10 18:58:17 +00:00
|
|
|
end
|
2022-10-25 17:29:57 +00:00
|
|
|
end
|
2023-02-23 04:09:47 +00:00
|
|
|
else
|
2023-07-15 17:16:36 +00:00
|
|
|
log.debug(log_header .. "dynamic tank list not a table")
|
2023-02-23 04:09:47 +00:00
|
|
|
valid = false
|
2022-10-25 17:29:57 +00:00
|
|
|
end
|
2023-02-14 03:11:31 +00:00
|
|
|
|
2023-07-08 20:57:13 +00:00
|
|
|
-- solar neutron activator status info
|
|
|
|
if type(rtu_statuses.sna) == "table" then
|
|
|
|
unit.num_snas = rtu_statuses.sna[1] ---@type integer
|
|
|
|
unit.sna_prod_rate = rtu_statuses.sna[2] ---@type number
|
|
|
|
|
|
|
|
unit.unit_ps.publish("sna_prod_rate", unit.sna_prod_rate)
|
|
|
|
|
|
|
|
sna_count_sum = sna_count_sum + unit.num_snas
|
|
|
|
else
|
|
|
|
log.debug(log_header .. "sna statistic list not a table")
|
|
|
|
valid = false
|
|
|
|
end
|
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
-- environment detector status
|
|
|
|
if type(rtu_statuses.rad_mon) == "table" then
|
|
|
|
if #rtu_statuses.rad_mon > 0 then
|
|
|
|
local rad_mon = rtu_statuses.rad_mon[1]
|
2023-04-12 20:02:29 +00:00
|
|
|
-- local rtu_faulted = rad_mon[1] ---@type boolean
|
|
|
|
unit.radiation = rad_mon[2] ---@type number
|
2023-02-14 03:11:31 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
unit.unit_ps.publish("radiation", unit.radiation)
|
|
|
|
else
|
|
|
|
unit.radiation = types.new_zero_radiation_reading()
|
|
|
|
end
|
2023-02-14 03:11:31 +00:00
|
|
|
else
|
2023-02-23 04:09:47 +00:00
|
|
|
log.debug(log_header .. "radiation monitor list not a table")
|
|
|
|
valid = false
|
2023-02-14 03:11:31 +00:00
|
|
|
end
|
|
|
|
else
|
2023-02-23 04:09:47 +00:00
|
|
|
log.debug(log_header .. "rtu list not a table")
|
|
|
|
valid = false
|
2023-02-14 03:11:31 +00:00
|
|
|
end
|
2022-12-05 21:17:09 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
-- annunciator
|
|
|
|
unit.annunciator = status[3]
|
2022-12-05 21:17:09 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
if type(unit.annunciator) ~= "table" then
|
|
|
|
unit.annunciator = {}
|
|
|
|
log.debug(log_header .. "annunciator state not a table")
|
|
|
|
valid = false
|
|
|
|
end
|
2023-01-15 18:11:46 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
for key, val in pairs(unit.annunciator) do
|
2023-04-03 21:18:30 +00:00
|
|
|
if key == "BoilerOnline" or key == "HeatingRateLow" or key == "WaterLevelLow" then
|
2023-02-23 04:09:47 +00:00
|
|
|
-- split up array for all boilers
|
|
|
|
for id = 1, #val do
|
|
|
|
unit.boiler_ps_tbl[id].publish(key, val[id])
|
|
|
|
end
|
2023-04-03 21:18:30 +00:00
|
|
|
elseif key == "TurbineOnline" or key == "SteamDumpOpen" or key == "TurbineOverSpeed" or
|
|
|
|
key == "GeneratorTrip" or key == "TurbineTrip" then
|
2023-02-23 04:09:47 +00:00
|
|
|
-- split up array for all turbines
|
|
|
|
for id = 1, #val do
|
|
|
|
unit.turbine_ps_tbl[id].publish(key, val[id])
|
|
|
|
end
|
|
|
|
elseif type(val) == "table" then
|
|
|
|
-- we missed one of the tables?
|
|
|
|
log.debug(log_header .. "unrecognized table found in annunciator list, this is a bug")
|
|
|
|
valid = false
|
|
|
|
else
|
|
|
|
-- non-table fields
|
|
|
|
unit.unit_ps.publish(key, val)
|
2022-12-05 21:17:09 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
-- alarms
|
|
|
|
local alarm_states = status[4]
|
2022-12-05 21:17:09 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
if type(alarm_states) == "table" then
|
|
|
|
for id = 1, #alarm_states do
|
|
|
|
local state = alarm_states[id]
|
2022-12-05 21:17:09 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
unit.alarms[id] = state
|
2022-12-05 21:17:09 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
if state == types.ALARM_STATE.TRIPPED or state == types.ALARM_STATE.ACKED then
|
|
|
|
unit.unit_ps.publish("Alarm_" .. id, 2)
|
|
|
|
elseif state == types.ALARM_STATE.RING_BACK then
|
|
|
|
unit.unit_ps.publish("Alarm_" .. id, 3)
|
|
|
|
else
|
|
|
|
unit.unit_ps.publish("Alarm_" .. id, 1)
|
|
|
|
end
|
2022-12-05 21:17:09 +00:00
|
|
|
end
|
2023-02-23 04:09:47 +00:00
|
|
|
else
|
|
|
|
log.debug(log_header .. "alarm states not a table")
|
|
|
|
valid = false
|
2022-12-05 21:17:09 +00:00
|
|
|
end
|
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
-- unit state fields
|
|
|
|
local unit_state = status[5]
|
2022-12-05 21:17:09 +00:00
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
if type(unit_state) == "table" then
|
2023-07-06 05:36:06 +00:00
|
|
|
if #unit_state == 6 then
|
2023-07-08 20:57:13 +00:00
|
|
|
unit.waste_mode = unit_state[5]
|
|
|
|
unit.waste_product = unit_state[6]
|
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
unit.unit_ps.publish("U_StatusLine1", unit_state[1])
|
|
|
|
unit.unit_ps.publish("U_StatusLine2", unit_state[2])
|
2023-07-06 05:36:06 +00:00
|
|
|
unit.unit_ps.publish("U_AutoReady", unit_state[3])
|
|
|
|
unit.unit_ps.publish("U_AutoDegraded", unit_state[4])
|
2023-07-08 20:57:13 +00:00
|
|
|
unit.unit_ps.publish("U_AutoWaste", unit.waste_mode == types.WASTE_MODE.AUTO)
|
|
|
|
unit.unit_ps.publish("U_WasteMode", unit.waste_mode)
|
|
|
|
unit.unit_ps.publish("U_WasteProduct", unit.waste_product)
|
2023-02-23 04:09:47 +00:00
|
|
|
else
|
|
|
|
log.debug(log_header .. "unit state length mismatch")
|
|
|
|
valid = false
|
|
|
|
end
|
2022-12-05 21:17:09 +00:00
|
|
|
else
|
2023-02-23 04:09:47 +00:00
|
|
|
log.debug(log_header .. "unit state not a table")
|
|
|
|
valid = false
|
2022-12-05 21:17:09 +00:00
|
|
|
end
|
2023-07-08 20:57:13 +00:00
|
|
|
|
|
|
|
-- determine waste production for this unit, add to statistics
|
|
|
|
local is_pu = unit.waste_product == types.WASTE_PRODUCT.PLUTONIUM
|
|
|
|
pu_rate = pu_rate + util.trinary(is_pu, burn_rate / 10.0, 0.0)
|
|
|
|
po_rate = po_rate + util.trinary(not is_pu, math.min(burn_rate / 10.0, unit.sna_prod_rate), 0.0)
|
2022-09-03 17:10:51 +00:00
|
|
|
end
|
2022-09-03 14:50:14 +00:00
|
|
|
end
|
2022-12-04 18:59:10 +00:00
|
|
|
|
2023-01-26 23:26:26 +00:00
|
|
|
io.facility.ps.publish("burn_sum", burn_rate_sum)
|
2023-07-08 20:57:13 +00:00
|
|
|
io.facility.ps.publish("sna_count", sna_count_sum)
|
|
|
|
io.facility.ps.publish("pu_rate", pu_rate)
|
|
|
|
io.facility.ps.publish("po_rate", po_rate)
|
2023-01-26 23:26:26 +00:00
|
|
|
|
2022-12-04 18:59:10 +00:00
|
|
|
-- update alarm sounder
|
|
|
|
sounder.eval(io.units)
|
2022-09-03 14:50:14 +00:00
|
|
|
end
|
|
|
|
|
2023-02-23 04:09:47 +00:00
|
|
|
return valid
|
2022-09-03 14:50:14 +00:00
|
|
|
end
|
|
|
|
|
2023-07-15 17:16:36 +00:00
|
|
|
--#endregion
|
|
|
|
|
2022-09-07 02:38:27 +00:00
|
|
|
-- get the IO controller database
|
|
|
|
function iocontrol.get_db() return io end
|
2022-07-10 20:15:30 +00:00
|
|
|
|
2022-09-07 02:38:27 +00:00
|
|
|
return iocontrol
|