357 lines
13 KiB
Lua
357 lines
13 KiB
Lua
--
|
|
-- I/O Control for Pocket Integration with Supervisor & Coordinator
|
|
--
|
|
|
|
local psil = require("scada-common.psil")
|
|
local types = require("scada-common.types")
|
|
local util = require("scada-common.util")
|
|
|
|
local iorx = require("pocket.iorx")
|
|
local process = require("pocket.process")
|
|
|
|
local ALARM = types.ALARM
|
|
local ALARM_STATE = types.ALARM_STATE
|
|
|
|
local ENERGY_SCALE = types.ENERGY_SCALE
|
|
local ENERGY_UNITS = types.ENERGY_SCALE_UNITS
|
|
local TEMP_SCALE = types.TEMP_SCALE
|
|
local TEMP_UNITS = types.TEMP_SCALE_UNITS
|
|
|
|
---@todo nominal trip time is ping (0ms to 10ms usually)
|
|
local WARN_TT = 40
|
|
local HIGH_TT = 80
|
|
|
|
local iocontrol = {}
|
|
|
|
---@enum POCKET_LINK_STATE
|
|
local LINK_STATE = {
|
|
UNLINKED = 0,
|
|
SV_LINK_ONLY = 1,
|
|
API_LINK_ONLY = 2,
|
|
LINKED = 3
|
|
}
|
|
|
|
iocontrol.LINK_STATE = LINK_STATE
|
|
|
|
---@class pocket_ioctl
|
|
local io = {
|
|
version = "unknown",
|
|
ps = psil.create()
|
|
}
|
|
|
|
local config = nil ---@type pkt_config
|
|
local comms = nil ---@type pocket_comms
|
|
|
|
-- initialize facility-independent components of pocket iocontrol
|
|
---@param pkt_comms pocket_comms
|
|
---@param nav pocket_nav
|
|
---@param cfg pkt_config
|
|
function iocontrol.init_core(pkt_comms, nav, cfg)
|
|
comms = pkt_comms
|
|
config = cfg
|
|
|
|
iocontrol.rx = iorx(io)
|
|
|
|
io.nav = nav
|
|
|
|
---@class pocket_ioctl_diag
|
|
io.diag = {}
|
|
|
|
-- alarm testing
|
|
io.diag.tone_test = {
|
|
test_1 = function (state) comms.diag__set_alarm_tone(1, state) end,
|
|
test_2 = function (state) comms.diag__set_alarm_tone(2, state) end,
|
|
test_3 = function (state) comms.diag__set_alarm_tone(3, state) end,
|
|
test_4 = function (state) comms.diag__set_alarm_tone(4, state) end,
|
|
test_5 = function (state) comms.diag__set_alarm_tone(5, state) end,
|
|
test_6 = function (state) comms.diag__set_alarm_tone(6, state) end,
|
|
test_7 = function (state) comms.diag__set_alarm_tone(7, state) end,
|
|
test_8 = function (state) comms.diag__set_alarm_tone(8, state) end,
|
|
stop_tones = function () comms.diag__set_alarm_tone(0, false) end,
|
|
|
|
test_breach = function (state) comms.diag__set_alarm(ALARM.ContainmentBreach, state) end,
|
|
test_rad = function (state) comms.diag__set_alarm(ALARM.ContainmentRadiation, state) end,
|
|
test_lost = function (state) comms.diag__set_alarm(ALARM.ReactorLost, state) end,
|
|
test_crit = function (state) comms.diag__set_alarm(ALARM.CriticalDamage, state) end,
|
|
test_dmg = function (state) comms.diag__set_alarm(ALARM.ReactorDamage, state) end,
|
|
test_overtemp = function (state) comms.diag__set_alarm(ALARM.ReactorOverTemp, state) end,
|
|
test_hightemp = function (state) comms.diag__set_alarm(ALARM.ReactorHighTemp, state) end,
|
|
test_wasteleak = function (state) comms.diag__set_alarm(ALARM.ReactorWasteLeak, state) end,
|
|
test_highwaste = function (state) comms.diag__set_alarm(ALARM.ReactorHighWaste, state) end,
|
|
test_rps = function (state) comms.diag__set_alarm(ALARM.RPSTransient, state) end,
|
|
test_rcs = function (state) comms.diag__set_alarm(ALARM.RCSTransient, state) end,
|
|
test_turbinet = function (state) comms.diag__set_alarm(ALARM.TurbineTrip, state) end,
|
|
stop_alarms = function () comms.diag__set_alarm(0, false) end,
|
|
|
|
get_tone_states = function () comms.diag__get_alarm_tones() end,
|
|
|
|
ready_warn = nil, ---@type TextBox
|
|
tone_buttons = {}, ---@type SwitchButton[]
|
|
alarm_buttons = {}, ---@type Checkbox[]
|
|
tone_indicators = {} ---@type IndicatorLight[] indicators to update from supervisor tone states
|
|
}
|
|
|
|
-- API access
|
|
---@class pocket_ioctl_api
|
|
io.api = {
|
|
get_unit = function (unit) comms.api__get_unit(unit) end,
|
|
get_ctrl = function () comms.api__get_control() end,
|
|
get_proc = function () comms.api__get_process() end,
|
|
get_waste = function () comms.api__get_waste() end
|
|
}
|
|
end
|
|
|
|
-- initialize facility-dependent components of pocket iocontrol
|
|
---@param conf facility_conf facility configuration
|
|
function iocontrol.init_fac(conf)
|
|
local temp_scale, energy_scale = config.TempScale, config.EnergyScale
|
|
io.temp_label = TEMP_UNITS[temp_scale]
|
|
io.energy_label = ENERGY_UNITS[energy_scale]
|
|
|
|
-- temperature unit label and conversion function (from Kelvin)
|
|
if temp_scale == TEMP_SCALE.CELSIUS then
|
|
io.temp_convert = function (t) return t - 273.15 end
|
|
elseif temp_scale == TEMP_SCALE.FAHRENHEIT then
|
|
io.temp_convert = function (t) return (1.8 * (t - 273.15)) + 32 end
|
|
elseif temp_scale == TEMP_SCALE.RANKINE then
|
|
io.temp_convert = function (t) return 1.8 * t end
|
|
else
|
|
io.temp_label = "K"
|
|
io.temp_convert = function (t) return t end
|
|
end
|
|
|
|
-- energy unit label and conversion function (from Joules unless otherwise specified)
|
|
if energy_scale == ENERGY_SCALE.FE or energy_scale == ENERGY_SCALE.RF then
|
|
io.energy_convert = util.joules_to_fe_rf
|
|
io.energy_convert_from_fe = function (t) return t end
|
|
io.energy_convert_to_fe = function (t) return t end
|
|
else
|
|
io.energy_label = "J"
|
|
io.energy_convert = function (t) return t end
|
|
io.energy_convert_from_fe = util.fe_rf_to_joules
|
|
io.energy_convert_to_fe = util.joules_to_fe_rf
|
|
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,
|
|
|
|
status_lines = { "", "" },
|
|
|
|
auto_ready = false,
|
|
auto_active = false,
|
|
auto_ramping = false,
|
|
auto_saturated = false,
|
|
|
|
auto_scram = false,
|
|
---@type ascram_status
|
|
ascram_status = {
|
|
matrix_fault = false,
|
|
matrix_fill = false,
|
|
crit_alarm = false,
|
|
radiation = false,
|
|
gen_fault = false
|
|
},
|
|
|
|
---@type WASTE_PRODUCT
|
|
auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
|
auto_pu_fallback_active = false,
|
|
auto_sps_disabled = false,
|
|
waste_stats = { 0, 0, 0, 0, 0, 0 }, -- waste in, pu, po, po pellets, am, spent waste
|
|
|
|
radiation = types.new_zero_radiation_reading(),
|
|
|
|
start_ack = nil, ---@type fun(success: boolean)
|
|
stop_ack = nil, ---@type fun(success: boolean)
|
|
scram_ack = nil, ---@type fun(success: boolean)
|
|
ack_alarms_ack = nil, ---@type fun(success: boolean)
|
|
|
|
ps = psil.create(),
|
|
|
|
induction_ps_tbl = {}, ---@type psil[]
|
|
induction_data_tbl = {}, ---@type imatrix_session_db[]
|
|
|
|
sps_ps_tbl = {}, ---@type psil[]
|
|
sps_data_tbl = {}, ---@type sps_session_db[]
|
|
|
|
tank_ps_tbl = {}, ---@type psil[]
|
|
tank_data_tbl = {} ---@type dynamicv_session_db[]
|
|
}
|
|
|
|
-- 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, {})
|
|
|
|
-- create unit data structures
|
|
io.units = {} ---@type pioctl_unit[]
|
|
for i = 1, conf.num_units do
|
|
---@class pioctl_unit
|
|
local entry = {
|
|
unit_id = i,
|
|
connected = false,
|
|
---@type { boilers: { connected: boolean, faulted: boolean }[], turbines: { connected: boolean, faulted: boolean }[] }
|
|
rtu_hw = {},
|
|
|
|
num_boilers = 0,
|
|
num_turbines = 0,
|
|
num_snas = 0,
|
|
has_tank = conf.cooling.r_cool[i].TankConnection,
|
|
|
|
status_lines = { "", "" },
|
|
|
|
auto_ready = false,
|
|
auto_degraded = false,
|
|
|
|
control_state = false,
|
|
burn_rate_cmd = 0.0,
|
|
radiation = types.new_zero_radiation_reading(),
|
|
|
|
sna_peak_rate = 0.0,
|
|
sna_max_rate = 0.0,
|
|
sna_out_rate = 0.0,
|
|
|
|
waste_mode = types.WASTE_MODE.MANUAL_PLUTONIUM,
|
|
waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
|
|
|
last_rate_change_ms = 0,
|
|
turbine_flow_stable = false,
|
|
waste_stats = { 0, 0, 0 }, -- plutonium, polonium, po pellets
|
|
|
|
-- auto control group
|
|
a_group = types.AUTO_GROUP.MANUAL,
|
|
|
|
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,
|
|
set_burn = function (rate) process.set_rate(i, rate) end, ---@param rate number burn rate
|
|
|
|
start_ack = nil, ---@type fun(success: boolean)
|
|
scram_ack = nil, ---@type fun(success: boolean)
|
|
reset_rps_ack = nil, ---@type fun(success: boolean)
|
|
ack_alarms_ack = nil, ---@type fun(success: boolean)
|
|
|
|
---@type { [ALARM]: ALARM_STATE }
|
|
alarms = { ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE },
|
|
|
|
---@diagnostic disable-next-line: missing-fields
|
|
annunciator = {}, ---@type annunciator
|
|
|
|
unit_ps = psil.create(),
|
|
reactor_data = types.new_reactor_db(),
|
|
|
|
boiler_ps_tbl = {}, ---@type psil[]
|
|
boiler_data_tbl = {}, ---@type boilerv_session_db[]
|
|
|
|
turbine_ps_tbl = {}, ---@type psil[]
|
|
turbine_data_tbl = {}, ---@type turbinev_session_db[]
|
|
|
|
tank_ps_tbl = {}, ---@type psil[]
|
|
tank_data_tbl = {} ---@type dynamicv_session_db[]
|
|
}
|
|
|
|
-- on other facility modes, overwrite unit TANK option with facility tank defs
|
|
if io.facility.tank_mode ~= 0 then
|
|
entry.has_tank = conf.cooling.fac_tank_defs[i] > 0
|
|
end
|
|
|
|
-- create boiler tables
|
|
for _ = 1, conf.cooling.r_cool[i].BoilerCount do
|
|
table.insert(entry.boiler_ps_tbl, psil.create())
|
|
table.insert(entry.boiler_data_tbl, {})
|
|
end
|
|
|
|
-- create turbine tables
|
|
for _ = 1, conf.cooling.r_cool[i].TurbineCount do
|
|
table.insert(entry.turbine_ps_tbl, psil.create())
|
|
table.insert(entry.turbine_data_tbl, {})
|
|
end
|
|
|
|
-- create tank tables
|
|
if io.facility.tank_defs[i] == 1 then
|
|
table.insert(entry.tank_ps_tbl, psil.create())
|
|
table.insert(entry.tank_data_tbl, {})
|
|
end
|
|
|
|
entry.num_boilers = #entry.boiler_data_tbl
|
|
entry.num_turbines = #entry.turbine_data_tbl
|
|
|
|
table.insert(io.units, entry)
|
|
end
|
|
|
|
-- pass IO control here since it can't be require'd due to a require loop
|
|
process.init(io, comms)
|
|
end
|
|
|
|
-- set network link state
|
|
---@param state POCKET_LINK_STATE
|
|
---@param sv_addr integer|false|nil supervisor address if linked, nil if unchanged, false if unlinked
|
|
---@param api_addr integer|false|nil coordinator address if linked, nil if unchanged, false if unlinked
|
|
function iocontrol.report_link_state(state, sv_addr, api_addr)
|
|
io.ps.publish("link_state", state)
|
|
|
|
if state == LINK_STATE.API_LINK_ONLY or state == LINK_STATE.UNLINKED then
|
|
io.ps.publish("svr_conn_quality", 0)
|
|
end
|
|
|
|
if state == LINK_STATE.SV_LINK_ONLY or state == LINK_STATE.UNLINKED then
|
|
io.ps.publish("crd_conn_quality", 0)
|
|
end
|
|
|
|
if sv_addr then
|
|
io.ps.publish("sv_addr", util.c(sv_addr, ":", config.SVR_Channel))
|
|
elseif sv_addr == false then
|
|
io.ps.publish("sv_addr", "unknown (not linked)")
|
|
end
|
|
|
|
if api_addr then
|
|
io.ps.publish("api_addr", util.c(api_addr, ":", config.CRD_Channel))
|
|
elseif api_addr == false then
|
|
io.ps.publish("api_addr", "unknown (not linked)")
|
|
end
|
|
end
|
|
|
|
-- show the reason the supervisor connection isn't linking
|
|
function iocontrol.report_svr_link_error(msg) io.ps.publish("svr_link_msg", msg) end
|
|
|
|
-- show the reason the coordinator api connection isn't linking
|
|
function iocontrol.report_crd_link_error(msg) io.ps.publish("api_link_msg", msg) end
|
|
|
|
-- determine supervisor connection quality (trip time)
|
|
---@param trip_time integer
|
|
function iocontrol.report_svr_tt(trip_time)
|
|
local state = 3
|
|
if trip_time > HIGH_TT then
|
|
state = 1
|
|
elseif trip_time > WARN_TT then
|
|
state = 2
|
|
end
|
|
|
|
io.ps.publish("svr_conn_quality", state)
|
|
end
|
|
|
|
-- determine coordinator connection quality (trip time)
|
|
---@param trip_time integer
|
|
function iocontrol.report_crd_tt(trip_time)
|
|
local state = 3
|
|
if trip_time > HIGH_TT then
|
|
state = 1
|
|
elseif trip_time > WARN_TT then
|
|
state = 2
|
|
end
|
|
|
|
io.ps.publish("crd_conn_quality", state)
|
|
end
|
|
|
|
-- get the IO controller database
|
|
function iocontrol.get_db() return io end
|
|
|
|
return iocontrol
|