2022-07-06 03:48:01 +00:00
|
|
|
local psil = require("scada-common.psil")
|
2022-09-03 14:50:14 +00:00
|
|
|
local log = require("scada-common.log")
|
2022-07-06 03:48:01 +00:00
|
|
|
|
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
|
|
|
|
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-07 02:38:27 +00:00
|
|
|
function iocontrol.init(conf)
|
|
|
|
io.facility = {
|
2022-07-06 03:48:01 +00:00
|
|
|
scram = false,
|
|
|
|
num_units = conf.num_units,
|
|
|
|
ps = psil.create()
|
|
|
|
}
|
|
|
|
|
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-09-07 02:38:27 +00:00
|
|
|
---@class ioctl_entry
|
2022-07-06 03:48:01 +00:00
|
|
|
local entry = {
|
|
|
|
unit_id = i, ---@type integer
|
|
|
|
initialized = false,
|
|
|
|
|
2022-09-07 15:10:20 +00:00
|
|
|
num_boilers = 0,
|
|
|
|
num_turbines = 0,
|
|
|
|
|
2022-09-07 02:38:27 +00:00
|
|
|
control_state = false,
|
|
|
|
burn_rate_cmd = 0.0,
|
|
|
|
waste_control = 0,
|
|
|
|
|
|
|
|
---@fixme debug stubs to be linked into comms later?
|
|
|
|
start = function () print("UNIT " .. i .. ": start") end,
|
|
|
|
scram = function () print("UNIT " .. i .. ": SCRAM") end,
|
|
|
|
set_burn = function (rate) print("UNIT " .. i .. ": set burn rate to " .. rate) end,
|
2022-07-06 03:48:01 +00:00
|
|
|
|
|
|
|
reactor_ps = psil.create(),
|
|
|
|
reactor_data = {}, ---@type reactor_db
|
|
|
|
|
|
|
|
boiler_ps_tbl = {},
|
|
|
|
boiler_data_tbl = {},
|
|
|
|
|
|
|
|
turbine_ps_tbl = {},
|
|
|
|
turbine_data_tbl = {}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _ = 1, conf.defs[(i * 2) - 1] do
|
|
|
|
local data = {} ---@type boiler_session_db|boilerv_session_db
|
|
|
|
table.insert(entry.boiler_ps_tbl, psil.create())
|
|
|
|
table.insert(entry.boiler_data_tbl, data)
|
|
|
|
end
|
|
|
|
|
|
|
|
for _ = 1, conf.defs[i * 2] do
|
|
|
|
local data = {} ---@type turbine_session_db|turbinev_session_db
|
|
|
|
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
|
|
|
|
end
|
|
|
|
|
2022-09-03 14:50:14 +00:00
|
|
|
-- populate structure builds
|
|
|
|
---@param builds table
|
|
|
|
---@return boolean valid
|
2022-09-07 14:25:22 +00:00
|
|
|
function iocontrol.record_builds(builds)
|
2022-09-07 02:38:27 +00:00
|
|
|
if #builds ~= #io.units then
|
2022-09-03 14:50:14 +00:00
|
|
|
log.error("number of provided unit builds does not match expected number of units")
|
|
|
|
return false
|
|
|
|
else
|
|
|
|
for i = 1, #builds do
|
2022-09-07 02:38:27 +00:00
|
|
|
local unit = io.units[i] ---@type ioctl_entry
|
2022-09-03 14:50:14 +00:00
|
|
|
local build = builds[i]
|
|
|
|
|
|
|
|
-- reactor build
|
|
|
|
unit.reactor_data.mek_struct = build.reactor
|
|
|
|
for key, val in pairs(unit.reactor_data.mek_struct) do
|
|
|
|
unit.reactor_ps.publish(key, val)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- boiler builds
|
|
|
|
for id, boiler in pairs(build.boilers) do
|
|
|
|
unit.boiler_data_tbl[id] = {
|
|
|
|
formed = boiler[2], ---@type boolean|nil
|
|
|
|
build = boiler[1] ---@type table
|
|
|
|
}
|
|
|
|
|
|
|
|
unit.boiler_ps_tbl[id].publish("formed", boiler[2])
|
|
|
|
|
|
|
|
for key, val in pairs(unit.boiler_data_tbl[id].build) do
|
2022-09-07 14:25:22 +00:00
|
|
|
unit.boiler_ps_tbl[id].publish(key, val)
|
2022-09-03 14:50:14 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- turbine builds
|
|
|
|
for id, turbine in pairs(build.turbines) do
|
|
|
|
unit.turbine_data_tbl[id] = {
|
|
|
|
formed = turbine[2], ---@type boolean|nil
|
|
|
|
build = turbine[1] ---@type table
|
|
|
|
}
|
|
|
|
|
|
|
|
unit.turbine_ps_tbl[id].publish("formed", turbine[2])
|
|
|
|
|
|
|
|
for key, val in pairs(unit.turbine_data_tbl[id].build) do
|
2022-09-07 14:25:22 +00:00
|
|
|
unit.turbine_ps_tbl[id].publish(key, val)
|
2022-09-03 14:50:14 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- update unit statuses
|
|
|
|
---@param statuses table
|
|
|
|
---@return boolean valid
|
2022-09-07 02:38:27 +00:00
|
|
|
function iocontrol.update_statuses(statuses)
|
|
|
|
if #statuses ~= #io.units then
|
2022-09-03 14:50:14 +00:00
|
|
|
log.error("number of provided unit statuses does not match expected number of units")
|
|
|
|
return false
|
|
|
|
else
|
|
|
|
for i = 1, #statuses do
|
2022-09-07 02:38:27 +00:00
|
|
|
local unit = io.units[i] ---@type ioctl_entry
|
2022-09-03 14:50:14 +00:00
|
|
|
local status = statuses[i]
|
|
|
|
|
2022-09-03 17:10:51 +00:00
|
|
|
-- reactor PLC status
|
2022-09-03 14:50:14 +00:00
|
|
|
|
|
|
|
local reactor_status = status[1]
|
2022-09-03 17:10:51 +00:00
|
|
|
|
|
|
|
if #reactor_status == 0 then
|
|
|
|
unit.reactor_ps.publish("computed_status", 1) -- disconnected
|
|
|
|
else
|
|
|
|
local mek_status = reactor_status[1]
|
|
|
|
local rps_status = reactor_status[2]
|
|
|
|
local gen_status = reactor_status[3]
|
|
|
|
|
|
|
|
unit.reactor_data.last_status_update = gen_status[1]
|
|
|
|
unit.reactor_data.control_state = gen_status[2]
|
2022-09-08 18:49:01 +00:00
|
|
|
unit.reactor_data.rps_tripped = gen_status[3]
|
|
|
|
unit.reactor_data.rps_trip_cause = gen_status[4]
|
|
|
|
unit.reactor_data.degraded = gen_status[5]
|
2022-09-03 17:10:51 +00:00
|
|
|
|
|
|
|
unit.reactor_data.rps_status = rps_status ---@type rps_status
|
|
|
|
unit.reactor_data.mek_status = mek_status ---@type mek_status
|
|
|
|
|
|
|
|
if unit.reactor_data.mek_status.status then
|
|
|
|
unit.reactor_ps.publish("computed_status", 3) -- running
|
|
|
|
else
|
|
|
|
if unit.reactor_data.degraded then
|
|
|
|
unit.reactor_ps.publish("computed_status", 5) -- faulted
|
|
|
|
elseif unit.reactor_data.rps_tripped and unit.reactor_data.rps_trip_cause ~= "manual" then
|
|
|
|
unit.reactor_ps.publish("computed_status", 4) -- SCRAM
|
|
|
|
else
|
|
|
|
unit.reactor_ps.publish("computed_status", 2) -- disabled
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for key, val in pairs(unit.reactor_data) do
|
2022-09-05 23:40:20 +00:00
|
|
|
if key ~= "rps_status" and key ~= "mek_struct" and key ~= "mek_status" then
|
2022-09-03 17:10:51 +00:00
|
|
|
unit.reactor_ps.publish(key, val)
|
|
|
|
end
|
2022-09-03 14:50:14 +00:00
|
|
|
end
|
2022-09-05 23:40:20 +00:00
|
|
|
|
|
|
|
for key, val in pairs(unit.reactor_data.rps_status) do
|
|
|
|
unit.reactor_ps.publish(key, val)
|
|
|
|
end
|
|
|
|
|
|
|
|
for key, val in pairs(unit.reactor_data.mek_status) do
|
|
|
|
unit.reactor_ps.publish(key, val)
|
|
|
|
end
|
2022-09-03 14:50:14 +00:00
|
|
|
end
|
|
|
|
|
2022-09-07 02:38:27 +00:00
|
|
|
-- annunciator
|
|
|
|
|
|
|
|
local annunciator = status[2] ---@type annunciator
|
|
|
|
|
|
|
|
for key, val in pairs(annunciator) do
|
|
|
|
if key == "TurbineTrip" then
|
2022-09-07 14:25:22 +00:00
|
|
|
-- split up turbine trip table for all turbines and a general OR combination
|
2022-09-07 02:38:27 +00:00
|
|
|
local trips = val
|
|
|
|
local any = false
|
|
|
|
|
2022-09-07 14:25:22 +00:00
|
|
|
for id = 1, #trips do
|
|
|
|
any = any or trips[id]
|
|
|
|
unit.turbine_ps_tbl[id].publish(key, trips[id])
|
2022-09-07 02:38:27 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
unit.reactor_ps.publish("TurbineTrip", any)
|
2022-09-07 14:25:22 +00:00
|
|
|
elseif key == "BoilerOnline" or key == "HeatingRateLow" then
|
|
|
|
-- split up array for all boilers
|
|
|
|
for id = 1, #val do
|
|
|
|
unit.boiler_ps_tbl[id].publish(key, val[id])
|
|
|
|
end
|
|
|
|
elseif key == "TurbineOnline" or key == "SteamDumpOpen" or key == "TurbineOverSpeed" then
|
|
|
|
-- 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.error("unrecognized table found in annunciator list, this is a bug", true)
|
2022-09-07 02:38:27 +00:00
|
|
|
else
|
2022-09-07 14:25:22 +00:00
|
|
|
-- non-table fields
|
2022-09-07 02:38:27 +00:00
|
|
|
unit.reactor_ps.publish(key, val)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-09-03 17:10:51 +00:00
|
|
|
-- RTU statuses
|
|
|
|
|
2022-09-07 02:38:27 +00:00
|
|
|
local rtu_statuses = status[3]
|
2022-09-03 17:10:51 +00:00
|
|
|
|
2022-09-03 14:50:14 +00:00
|
|
|
-- boiler statuses
|
|
|
|
|
2022-09-03 17:10:51 +00:00
|
|
|
for id = 1, #unit.boiler_data_tbl do
|
|
|
|
if rtu_statuses.boilers[i] == nil then
|
|
|
|
-- disconnected
|
2022-09-07 14:25:22 +00:00
|
|
|
unit.boiler_ps_tbl[id].publish("computed_status", 1)
|
2022-09-03 17:10:51 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for id, boiler in pairs(rtu_statuses.boilers) do
|
|
|
|
unit.boiler_data_tbl[id].state = boiler[1] ---@type table
|
|
|
|
unit.boiler_data_tbl[id].tanks = boiler[2] ---@type table
|
|
|
|
|
|
|
|
local data = unit.boiler_data_tbl[id] ---@type boiler_session_db|boilerv_session_db
|
|
|
|
|
|
|
|
if data.state.boil_rate > 0 then
|
2022-09-07 14:25:22 +00:00
|
|
|
unit.boiler_ps_tbl[id].publish("computed_status", 3) -- active
|
2022-09-03 17:10:51 +00:00
|
|
|
else
|
2022-09-07 14:25:22 +00:00
|
|
|
unit.boiler_ps_tbl[id].publish("computed_status", 2) -- idle
|
2022-09-03 17:10:51 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
for key, val in pairs(unit.boiler_data_tbl[id].state) do
|
2022-09-07 14:25:22 +00:00
|
|
|
unit.boiler_ps_tbl[id].publish(key, val)
|
2022-09-03 17:10:51 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
for key, val in pairs(unit.boiler_data_tbl[id].tanks) do
|
2022-09-07 14:25:22 +00:00
|
|
|
unit.boiler_ps_tbl[id].publish(key, val)
|
2022-09-03 17:10:51 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-09-03 14:50:14 +00:00
|
|
|
-- turbine statuses
|
2022-09-03 17:10:51 +00:00
|
|
|
|
|
|
|
for id = 1, #unit.turbine_ps_tbl do
|
|
|
|
if rtu_statuses.turbines[i] == nil then
|
|
|
|
-- disconnected
|
2022-09-07 14:25:22 +00:00
|
|
|
unit.turbine_ps_tbl[id].publish("computed_status", 1)
|
2022-09-03 17:10:51 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
for id, turbine in pairs(rtu_statuses.turbines) do
|
|
|
|
unit.turbine_data_tbl[id].state = turbine[1] ---@type table
|
|
|
|
unit.turbine_data_tbl[id].tanks = turbine[2] ---@type table
|
|
|
|
|
|
|
|
local data = unit.turbine_data_tbl[id] ---@type turbine_session_db|turbinev_session_db
|
|
|
|
|
|
|
|
if data.tanks.steam_fill >= 0.99 then
|
2022-09-07 14:25:22 +00:00
|
|
|
unit.turbine_ps_tbl[id].publish("computed_status", 4) -- trip
|
2022-09-03 17:10:51 +00:00
|
|
|
elseif data.state.flow_rate < 100 then
|
2022-09-07 14:25:22 +00:00
|
|
|
unit.turbine_ps_tbl[id].publish("computed_status", 2) -- idle
|
2022-09-03 17:10:51 +00:00
|
|
|
else
|
2022-09-07 14:25:22 +00:00
|
|
|
unit.turbine_ps_tbl[id].publish("computed_status", 3) -- active
|
2022-09-03 17:10:51 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
for key, val in pairs(unit.turbine_data_tbl[id].state) do
|
2022-09-07 14:25:22 +00:00
|
|
|
unit.turbine_ps_tbl[id].publish(key, val)
|
2022-09-03 17:10:51 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
for key, val in pairs(unit.turbine_data_tbl[id].tanks) do
|
2022-09-07 14:25:22 +00:00
|
|
|
unit.turbine_ps_tbl[id].publish(key, val)
|
2022-09-03 17:10:51 +00:00
|
|
|
end
|
|
|
|
end
|
2022-09-03 14:50:14 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
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
|