Merge pull request #609 from MikaylaFischler/480-auxiliarybackup-water-control

480 auxiliary backup water control
This commit is contained in:
Mikayla 2025-02-25 16:44:23 -05:00 committed by GitHub
commit 122fa1a7a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 214 additions and 53 deletions

View File

@ -164,6 +164,7 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
num_turbines = 0, num_turbines = 0,
num_snas = 0, num_snas = 0,
has_tank = conf.cooling.r_cool[i].TankConnection, has_tank = conf.cooling.r_cool[i].TankConnection,
aux_coolant = conf.cooling.aux_coolant[i],
status_lines = { "", "" }, status_lines = { "", "" },
@ -1214,7 +1215,7 @@ function iocontrol.update_unit_statuses(statuses)
local valve_states = status[6] local valve_states = status[6]
if type(valve_states) == "table" then if type(valve_states) == "table" then
if #valve_states == 5 then if #valve_states == 6 then
unit.unit_ps.publish("V_pu_conn", valve_states[1] > 0) unit.unit_ps.publish("V_pu_conn", valve_states[1] > 0)
unit.unit_ps.publish("V_pu_state", valve_states[1] == 2) unit.unit_ps.publish("V_pu_state", valve_states[1] == 2)
unit.unit_ps.publish("V_po_conn", valve_states[2] > 0) unit.unit_ps.publish("V_po_conn", valve_states[2] > 0)
@ -1225,6 +1226,8 @@ function iocontrol.update_unit_statuses(statuses)
unit.unit_ps.publish("V_am_state", valve_states[4] == 2) unit.unit_ps.publish("V_am_state", valve_states[4] == 2)
unit.unit_ps.publish("V_emc_conn", valve_states[5] > 0) unit.unit_ps.publish("V_emc_conn", valve_states[5] > 0)
unit.unit_ps.publish("V_emc_state", valve_states[5] == 2) unit.unit_ps.publish("V_emc_state", valve_states[5] == 2)
unit.unit_ps.publish("V_aux_conn", valve_states[6] > 0)
unit.unit_ps.publish("V_aux_state", valve_states[6] == 2)
else else
log.debug(log_header .. "valve states length mismatch") log.debug(log_header .. "valve states length mismatch")
valid = false valid = false

View File

@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer")
local sounder = require("coordinator.sounder") local sounder = require("coordinator.sounder")
local threads = require("coordinator.threads") local threads = require("coordinator.threads")
local COORDINATOR_VERSION = "v1.6.8" local COORDINATOR_VERSION = "v1.6.9"
local CHUNK_LOAD_DELAY_S = 30.0 local CHUNK_LOAD_DELAY_S = 30.0

View File

@ -59,7 +59,7 @@ local function make(parent, x, y, wide, unit_id)
local tank_conns = facility.tank_conns local tank_conns = facility.tank_conns
local tank_types = facility.tank_fluid_types local tank_types = facility.tank_fluid_types
local v_start = 1 + ((unit.unit_id - 1) * 5) local v_start = 1 + ((unit.unit_id - 1) * 6)
local prv_start = 1 + ((unit.unit_id - 1) * 3) local prv_start = 1 + ((unit.unit_id - 1) * 3)
local v_fields = { "pu", "po", "pl", "am" } local v_fields = { "pu", "po", "pl", "am" }
local v_names = { local v_names = {
@ -96,9 +96,17 @@ local function make(parent, x, y, wide, unit_id)
table.insert(rc_pipes, pipe(0, 3, _wide(28, 19), 3, colors.orange, true)) table.insert(rc_pipes, pipe(0, 3, _wide(28, 19), 3, colors.orange, true))
table.insert(rc_pipes, pipe(_wide(46, 39), 1, _wide(72, 58), 1, colors.blue, true)) table.insert(rc_pipes, pipe(_wide(46, 39), 1, _wide(72, 58), 1, colors.blue, true))
table.insert(rc_pipes, pipe(_wide(46, 39), 3, _wide(72, 58), 3, colors.white, true)) table.insert(rc_pipes, pipe(_wide(46, 39), 3, _wide(72, 58), 3, colors.white, true))
if unit.aux_coolant then
table.insert(rc_pipes, pipe(_wide(51, 41), 0, _wide(51, 41), 1, colors.blue, true))
end
else else
table.insert(rc_pipes, pipe(0, 1, _wide(72, 58), 1, colors.blue, true)) table.insert(rc_pipes, pipe(0, 1, _wide(72, 58), 1, colors.blue, true))
table.insert(rc_pipes, pipe(0, 3, _wide(72, 58), 3, colors.white, true)) table.insert(rc_pipes, pipe(0, 3, _wide(72, 58), 3, colors.white, true))
if unit.aux_coolant then
table.insert(rc_pipes, pipe(8, 0, 8, 1, colors.blue, true))
end
end end
if unit.has_tank then if unit.has_tank then

View File

@ -286,7 +286,7 @@ local function init(main)
TextBox{parent=main,x=12,y=vy,text="\x10\x11",fg_bg=text_col,width=2} TextBox{parent=main,x=12,y=vy,text="\x10\x11",fg_bg=text_col,width=2}
local conn = IndicatorLight{parent=main,x=9,y=vy+1,label=util.sprintf("PV%02d-EMC", i * 5),colors=style.ind_grn} local conn = IndicatorLight{parent=main,x=9,y=vy+1,label=util.sprintf("PV%02d-EMC", (i * 6) - 1),colors=style.ind_grn}
local open = IndicatorLight{parent=main,x=9,y=vy+2,label="OPEN",colors=style.ind_wht} local open = IndicatorLight{parent=main,x=9,y=vy+2,label="OPEN",colors=style.ind_wht}
conn.register(units[i].unit_ps, "V_emc_conn", conn.update) conn.register(units[i].unit_ps, "V_emc_conn", conn.update)
@ -294,6 +294,33 @@ local function init(main)
end end
end end
------------------------------
-- auxiliary coolant valves --
------------------------------
for i = 1, facility.num_units do
if units[i].aux_coolant then
local vx
local vy = 3 + y_ofs(i)
if #emcool_pipes == 0 then
vx = util.trinary(units[i].num_boilers == 0, 36, 79)
else
vx = util.trinary(units[i].num_boilers == 0, 58, 91)
end
PipeNetwork{parent=main,x=vx-6,y=vy,pipes={pipe(0,1,9,0,colors.blue,true)},bg=style.theme.bg}
TextBox{parent=main,x=vx,y=vy,text="\x10\x11",fg_bg=text_col,width=2}
local conn = IndicatorLight{parent=main,x=vx-3,y=vy+1,label=util.sprintf("PV%02d-AUX", i * 6),colors=style.ind_grn}
local open = IndicatorLight{parent=main,x=vx-3,y=vy+2,label="OPEN",colors=style.ind_wht}
conn.register(units[i].unit_ps, "V_aux_conn", conn.update)
open.register(units[i].unit_ps, "V_aux_state", open.update)
end
end
------------------- -------------------
-- dynamic tanks -- -- dynamic tanks --
------------------- -------------------

View File

@ -74,11 +74,12 @@ local PORT_DESC_MAP = {
{ IO.R_PLC_FAULT, "RPS PLC Fault" }, { IO.R_PLC_FAULT, "RPS PLC Fault" },
{ IO.R_PLC_TIMEOUT, "RPS Supervisor Timeout" }, { IO.R_PLC_TIMEOUT, "RPS Supervisor Timeout" },
{ IO.U_ALARM, "Unit Alarm" }, { IO.U_ALARM, "Unit Alarm" },
{ IO.U_EMER_COOL, "Unit Emergency Cool. Valve" } { IO.U_EMER_COOL, "Unit Emergency Cool. Valve" },
{ IO.U_AUX_COOL, "Unit Auxiliary Cool. Valve" }
} }
-- designation (0 = facility, 1 = unit) -- designation (0 = facility, 1 = unit)
local PORT_DSGN = { [-1] = 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 } local PORT_DSGN = { [-1] = 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1 }
assert(#PORT_DESC_MAP == rsio.NUM_PORTS) assert(#PORT_DESC_MAP == rsio.NUM_PORTS)
assert(#PORT_DSGN == rsio.NUM_PORTS) assert(#PORT_DSGN == rsio.NUM_PORTS)

View File

@ -31,7 +31,7 @@ local sna_rtu = require("rtu.dev.sna_rtu")
local sps_rtu = require("rtu.dev.sps_rtu") local sps_rtu = require("rtu.dev.sps_rtu")
local turbinev_rtu = require("rtu.dev.turbinev_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu")
local RTU_VERSION = "v1.11.4" local RTU_VERSION = "v1.11.5"
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
local RTU_HW_STATE = databus.RTU_HW_STATE local RTU_HW_STATE = databus.RTU_HW_STATE

View File

@ -72,6 +72,8 @@ local rs = {}
rs.IMATRIX_CHARGE_LOW = 0.05 -- activation threshold (less than) for F_MATRIX_LOW rs.IMATRIX_CHARGE_LOW = 0.05 -- activation threshold (less than) for F_MATRIX_LOW
rs.IMATRIX_CHARGE_HIGH = 0.95 -- activation threshold (greater than) for F_MATRIX_HIGH rs.IMATRIX_CHARGE_HIGH = 0.95 -- activation threshold (greater than) for F_MATRIX_HIGH
rs.AUX_COOL_ENABLE = 0.60 -- actiation threshold (less than or equal) for U_AUX_COOL
rs.AUX_COOL_DISABLE = 1.00 -- deactivation threshold (greater than or equal) for U_AUX_COOL
constants.RS_THRESHOLDS = rs constants.RS_THRESHOLDS = rs

View File

@ -78,6 +78,7 @@ local IO_PORT = {
-- unit outputs -- unit outputs
U_ALARM = 25, -- active high, unit alarm U_ALARM = 25, -- active high, unit alarm
U_EMER_COOL = 26, -- active low, emergency coolant control U_EMER_COOL = 26, -- active low, emergency coolant control
U_AUX_COOL = 30, -- active low, auxiliary coolant control
-- analog outputs -- -- analog outputs --
@ -90,8 +91,8 @@ rsio.IO_DIR = IO_DIR
rsio.IO_MODE = IO_MODE rsio.IO_MODE = IO_MODE
rsio.IO = IO_PORT rsio.IO = IO_PORT
rsio.NUM_PORTS = 29 rsio.NUM_PORTS = 30
rsio.NUM_DIG_PORTS = 28 rsio.NUM_DIG_PORTS = 29
rsio.NUM_ANA_PORTS = 1 rsio.NUM_ANA_PORTS = 1
-- self checks -- self checks
@ -149,6 +150,7 @@ local MODES = {
[IO.R_PLC_TIMEOUT] = IO_MODE.DIGITAL_OUT, [IO.R_PLC_TIMEOUT] = IO_MODE.DIGITAL_OUT,
[IO.U_ALARM] = IO_MODE.DIGITAL_OUT, [IO.U_ALARM] = IO_MODE.DIGITAL_OUT,
[IO.U_EMER_COOL] = IO_MODE.DIGITAL_OUT, [IO.U_EMER_COOL] = IO_MODE.DIGITAL_OUT,
[IO.U_AUX_COOL] = IO_MODE.DIGITAL_OUT,
[IO.F_MATRIX_CHG] = IO_MODE.ANALOG_OUT [IO.F_MATRIX_CHG] = IO_MODE.ANALOG_OUT
} }
@ -208,10 +210,11 @@ local RS_DIO_MAP = {
[IO.R_PLC_TIMEOUT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.R_PLC_TIMEOUT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
[IO.U_ALARM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.U_ALARM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT },
[IO.U_EMER_COOL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT } [IO.U_EMER_COOL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT },
[IO.U_AUX_COOL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }
} }
assert(rsio.NUM_DIG_PORTS == #RS_DIO_MAP, "RS_DIO_MAP length incorrect") assert(rsio.NUM_DIG_PORTS == util.table_len(RS_DIO_MAP), "RS_DIO_MAP length incorrect")
-- get the I/O direction of a port -- get the I/O direction of a port
---@nodiscard ---@nodiscard

View File

@ -24,7 +24,7 @@ local t_pack = table.pack
local util = {} local util = {}
-- scada-common version -- scada-common version
util.version = "1.4.10" util.version = "1.4.12"
util.TICK_TIME_S = 0.05 util.TICK_TIME_S = 0.05
util.TICK_TIME_MS = 50 util.TICK_TIME_MS = 50

View File

@ -185,8 +185,9 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
local fac_c_6 = Div{parent=fac_cfg,x=2,y=4,width=49} local fac_c_6 = Div{parent=fac_cfg,x=2,y=4,width=49}
local fac_c_7 = Div{parent=fac_cfg,x=2,y=4,width=49} local fac_c_7 = Div{parent=fac_cfg,x=2,y=4,width=49}
local fac_c_8 = Div{parent=fac_cfg,x=2,y=4,width=49} local fac_c_8 = Div{parent=fac_cfg,x=2,y=4,width=49}
local fac_c_9 = Div{parent=fac_cfg,x=2,y=4,width=49}
local fac_pane = MultiPane{parent=fac_cfg,x=1,y=4,panes={fac_c_1,fac_c_2,fac_c_3,fac_c_4,fac_c_5,fac_c_6,fac_c_7, fac_c_8}} local fac_pane = MultiPane{parent=fac_cfg,x=1,y=4,panes={fac_c_1,fac_c_2,fac_c_3,fac_c_4,fac_c_5,fac_c_6,fac_c_7,fac_c_8,fac_c_9}}
TextBox{parent=fac_cfg,x=1,y=2,text=" Facility Configuration",fg_bg=cpair(colors.black,colors.yellow)} TextBox{parent=fac_cfg,x=1,y=2,text=" Facility Configuration",fg_bg=cpair(colors.black,colors.yellow)}
@ -205,10 +206,18 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
nu_error.hide(true) nu_error.hide(true)
tmp_cfg.UnitCount = count tmp_cfg.UnitCount = count
local confs = tool_ctl.cooling_elems local c_confs = tool_ctl.cooling_elems
if count >= 2 then confs[2].line.show() else confs[2].line.hide(true) end local a_confs = tool_ctl.aux_cool_elems
if count >= 3 then confs[3].line.show() else confs[3].line.hide(true) end
if count == 4 then confs[4].line.show() else confs[4].line.hide(true) end for i = 2, 4 do
if count >= i then
c_confs[i].line.show()
a_confs[i].line.show()
else
c_confs[i].line.hide(true)
a_confs[i].line.hide(true)
end
end
fac_pane.set_value(2) fac_pane.set_value(2)
else nu_error.show() end else nu_error.show() end
@ -285,6 +294,14 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
else elem.div.hide(true) end else elem.div.hide(true) end
end end
if not any_has_tank then
tmp_cfg.FacilityTankMode = 0
tmp_cfg.FacilityTankDefs = {}
tmp_cfg.FacilityTankList = {}
tmp_cfg.FacilityTankConns = {}
tmp_cfg.TankFluidTypes = {}
end
if any_has_tank then fac_pane.set_value(3) else main_pane.set_value(3) end if any_has_tank then fac_pane.set_value(3) else main_pane.set_value(3) end
end end
end end
@ -672,25 +689,48 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
PushButton{parent=fac_c_7,x=1,y=14,text="\x1b Back",callback=back_from_fluids,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=fac_c_7,x=1,y=14,text="\x1b Back",callback=back_from_fluids,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=fac_c_7,x=44,y=14,text="Next \x1a",callback=submit_tank_fluids,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=fac_c_7,x=44,y=14,text="Next \x1a",callback=submit_tank_fluids,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion
--#region Auxiliary Coolant
TextBox{parent=fac_c_8,height=5,text="Auxiliary water coolant can be enabled for units to provide extra water during turbine ramp-up. For water cooled reactors, this goes to the reactor. For sodium cooled reactors, water goes to the boiler."}
for i = 1, 4 do
local line = Div{parent=fac_c_8,x=1,y=7+i,height=1}
TextBox{parent=line,text="Unit "..i.." -",width=8}
local aux_cool = Checkbox{parent=line,x=10,y=1,label="Has Auxiliary Coolant",default=ini_cfg.AuxiliaryCoolant[i],box_fg_bg=cpair(colors.yellow,colors.black)}
tool_ctl.aux_cool_elems[i] = { line = line, enable = aux_cool }
end
local function submit_aux_cool()
tmp_cfg.AuxiliaryCoolant = {}
for i = 1, tmp_cfg.UnitCount do
tmp_cfg.AuxiliaryCoolant[i] = tool_ctl.aux_cool_elems[i].enable.get_value()
end
fac_pane.set_value(9)
end
PushButton{parent=fac_c_8,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=fac_c_8,x=44,y=14,text="Next \x1a",callback=submit_aux_cool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion --#endregion
--#region Extended Idling --#region Extended Idling
TextBox{parent=fac_c_8,height=6,text="Charge control provides automatic control to maintain an induction matrix charge level. In order to have smoother control, reactors that were activated will be held on at 0.01 mB/t for a short period before allowing them to turn off. This minimizes overshooting the charge target."} TextBox{parent=fac_c_9,height=6,text="Charge control provides automatic control to maintain an induction matrix charge level. In order to have smoother control, reactors that were activated will be held on at 0.01 mB/t for a short period before allowing them to turn off. This minimizes overshooting the charge target."}
TextBox{parent=fac_c_8,y=8,height=3,text="You can extend this to a full minute to minimize reactors flickering on/off, but there may be more overshoot of the target."} TextBox{parent=fac_c_9,y=8,height=3,text="You can extend this to a full minute to minimize reactors flickering on/off, but there may be more overshoot of the target."}
local ext_idling = Checkbox{parent=fac_c_8,x=1,y=12,label="Enable Extended Idling",default=ini_cfg.ExtChargeIdling,box_fg_bg=cpair(colors.yellow,colors.black)} local ext_idling = Checkbox{parent=fac_c_9,x=1,y=12,label="Enable Extended Idling",default=ini_cfg.ExtChargeIdling,box_fg_bg=cpair(colors.yellow,colors.black)}
local function back_from_idling()
fac_pane.set_value(tri(tmp_cfg.FacilityTankMode == 0, 3, 7))
end
local function submit_idling() local function submit_idling()
tmp_cfg.ExtChargeIdling = ext_idling.get_value() tmp_cfg.ExtChargeIdling = ext_idling.get_value()
main_pane.set_value(3) main_pane.set_value(3)
end end
PushButton{parent=fac_c_8,x=1,y=14,text="\x1b Back",callback=back_from_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=fac_c_9,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(8)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
PushButton{parent=fac_c_8,x=44,y=14,text="Next \x1a",callback=submit_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=fac_c_9,x=44,y=14,text="Next \x1a",callback=submit_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
--#endregion --#endregion

View File

@ -402,6 +402,10 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
try_set(tool_ctl.tank_elems[i].tank_opt, ini_cfg.FacilityTankDefs[i]) try_set(tool_ctl.tank_elems[i].tank_opt, ini_cfg.FacilityTankDefs[i])
end end
for i = 1, #ini_cfg.AuxiliaryCoolant do
try_set(tool_ctl.aux_cool_elems[i].enable, ini_cfg.AuxiliaryCoolant[i])
end
tool_ctl.en_fac_tanks.set_value(ini_cfg.FacilityTankMode > 0) tool_ctl.en_fac_tanks.set_value(ini_cfg.FacilityTankMode > 0)
tool_ctl.view_cfg.enable() tool_ctl.view_cfg.enable()
@ -588,7 +592,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
end end
if val == "" then val = "no facility tanks" end if val == "" then val = "no facility tanks" end
elseif f[1] == "FacilityTankMode" and raw == 0 then val = "0 (n/a, unit mode)" elseif f[1] == "FacilityTankMode" and raw == 0 then val = "no facility tanks"
elseif f[1] == "FacilityTankDefs" and type(cfg.FacilityTankDefs) == "table" then elseif f[1] == "FacilityTankDefs" and type(cfg.FacilityTankDefs) == "table" then
local tank_name_list = { table.unpack(cfg.FacilityTankList) } ---@type (string|integer)[] local tank_name_list = { table.unpack(cfg.FacilityTankList) } ---@type (string|integer)[]
local next_f = 1 local next_f = 1
@ -647,6 +651,16 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
end end
if val == "" then val = "no emergency coolant tanks" end if val == "" then val = "no emergency coolant tanks" end
elseif f[1] == "AuxiliaryCoolant" then
val = ""
for idx = 1, #cfg.AuxiliaryCoolant do
if cfg.AuxiliaryCoolant[idx] then
val = val .. tri(val == "", "", "\n") .. util.sprintf(" \x07 auxiliary coolant for unit %d", idx)
end
end
if val == "" then val = "no auxiliary coolant" end
end end
if not skip then if not skip then

View File

@ -72,7 +72,8 @@ local tool_ctl = {
load_legacy = nil, ---@type function load_legacy = nil, ---@type function
cooling_elems = {}, ---@type { line: Div, turbines: NumberField, boilers: NumberField, tank: Checkbox }[] cooling_elems = {}, ---@type { line: Div, turbines: NumberField, boilers: NumberField, tank: Checkbox }[]
tank_elems = {} ---@type { div: Div, tank_opt: Radio2D, no_tank: TextBox }[] tank_elems = {}, ---@type { div: Div, tank_opt: Radio2D, no_tank: TextBox }[]
aux_cool_elems = {} ---@type { line: Div, enable: Checkbox }[]
} }
---@class svr_config ---@class svr_config
@ -84,6 +85,7 @@ local tmp_cfg = {
FacilityTankList = {}, ---@type integer[] list of tanks by slot (0 = none or covered by an above tank, 1 = unit tank, 2 = facility tank) FacilityTankList = {}, ---@type integer[] list of tanks by slot (0 = none or covered by an above tank, 1 = unit tank, 2 = facility tank)
FacilityTankConns = {}, ---@type integer[] map of unit tank connections (indicies are units, values are tank indicies in the tank list) FacilityTankConns = {}, ---@type integer[] map of unit tank connections (indicies are units, values are tank indicies in the tank list)
TankFluidTypes = {}, ---@type integer[] which type of fluid each tank in the tank list should be containing TankFluidTypes = {}, ---@type integer[] which type of fluid each tank in the tank list should be containing
AuxiliaryCoolant = {}, ---@type boolean[] if a unit has auxiliary coolant
ExtChargeIdling = false, ExtChargeIdling = false,
SVR_Channel = nil, ---@type integer SVR_Channel = nil, ---@type integer
PLC_Channel = nil, ---@type integer PLC_Channel = nil, ---@type integer
@ -117,6 +119,7 @@ local fields = {
{ "FacilityTankList", "Facility Tank List", {} }, -- hidden { "FacilityTankList", "Facility Tank List", {} }, -- hidden
{ "FacilityTankConns", "Facility Tank Connections", {} }, -- hidden { "FacilityTankConns", "Facility Tank Connections", {} }, -- hidden
{ "TankFluidTypes", "Tank Fluid Types", {} }, { "TankFluidTypes", "Tank Fluid Types", {} },
{ "AuxiliaryCoolant", "Auxiliary Water Coolant", {} },
{ "ExtChargeIdling", "Extended Charge Idling", false }, { "ExtChargeIdling", "Extended Charge Idling", false },
{ "SVR_Channel", "SVR Channel", 16240 }, { "SVR_Channel", "SVR Channel", 16240 },
{ "PLC_Channel", "PLC Channel", 16241 }, { "PLC_Channel", "PLC Channel", 16241 },

View File

@ -64,7 +64,8 @@ function facility.new(config)
fac_tank_defs = config.FacilityTankDefs, fac_tank_defs = config.FacilityTankDefs,
fac_tank_list = config.FacilityTankList, fac_tank_list = config.FacilityTankList,
fac_tank_conns = config.FacilityTankConns, fac_tank_conns = config.FacilityTankConns,
tank_fluid_types = config.TankFluidTypes tank_fluid_types = config.TankFluidTypes,
aux_coolant = config.AuxiliaryCoolant
}, },
-- rtus -- rtus
rtu_gw_conn_count = 0, rtu_gw_conn_count = 0,
@ -147,7 +148,7 @@ function facility.new(config)
-- create units -- create units
for i = 1, config.UnitCount do for i = 1, config.UnitCount do
table.insert(self.units, unit.new(i, self.cooling_conf.r_cool[i].BoilerCount, self.cooling_conf.r_cool[i].TurbineCount, config.ExtChargeIdling)) table.insert(self.units, unit.new(i, self.cooling_conf.r_cool[i].BoilerCount, self.cooling_conf.r_cool[i].TurbineCount, config.ExtChargeIdling, self.cooling_conf.aux_coolant[i]))
table.insert(self.group_map, AUTO_GROUP.MANUAL) table.insert(self.group_map, AUTO_GROUP.MANUAL)
table.insert(self.last_unit_states, false) table.insert(self.last_unit_states, false)
end end

View File

@ -10,6 +10,7 @@ local log = require("scada-common.log")
local network = require("scada-common.network") local network = require("scada-common.network")
local ppm = require("scada-common.ppm") local ppm = require("scada-common.ppm")
local tcd = require("scada-common.tcd") local tcd = require("scada-common.tcd")
local types = require("scada-common.types")
local util = require("scada-common.util") local util = require("scada-common.util")
local core = require("graphics.core") local core = require("graphics.core")
@ -22,7 +23,7 @@ local supervisor = require("supervisor.supervisor")
local svsessions = require("supervisor.session.svsessions") local svsessions = require("supervisor.session.svsessions")
local SUPERVISOR_VERSION = "v1.6.6" local SUPERVISOR_VERSION = "v1.6.7"
local println = util.println local println = util.println
local println_ts = util.println_ts local println_ts = util.println_ts
@ -72,6 +73,21 @@ if config.FacilityTankMode > 0 then
cfv.assert_type_int(def) cfv.assert_type_int(def)
cfv.assert_range(def, 0, 2) cfv.assert_range(def, 0, 2)
assert(cfv.valid(), "startup> invalid facility tank definition for reactor unit " .. i) assert(cfv.valid(), "startup> invalid facility tank definition for reactor unit " .. i)
local entry = config.FacilityTankList[i]
cfv.assert_type_int(entry)
cfv.assert_range(entry, 0, 2)
assert(cfv.valid(), "startup> invalid facility tank list entry for tank " .. i)
local conn = config.FacilityTankConns[i]
cfv.assert_type_int(conn)
cfv.assert_range(conn, 0, #config.FacilityTankDefs)
assert(cfv.valid(), "startup> invalid facility tank connection for reactor unit " .. i)
local type = config.TankFluidTypes[i]
cfv.assert_type_int(type)
cfv.assert_range(type, 0, types.COOLANT_TYPE.SODIUM)
assert(cfv.valid(), "startup> invalid tank fluid type for tank " .. i)
end end
end end

View File

@ -44,6 +44,7 @@ function supervisor.load_config()
config.FacilityTankList = settings.get("FacilityTankList") config.FacilityTankList = settings.get("FacilityTankList")
config.FacilityTankConns = settings.get("FacilityTankConns") config.FacilityTankConns = settings.get("FacilityTankConns")
config.TankFluidTypes = settings.get("TankFluidTypes") config.TankFluidTypes = settings.get("TankFluidTypes")
config.AuxiliaryCoolant = settings.get("AuxiliaryCoolant")
config.ExtChargeIdling = settings.get("ExtChargeIdling") config.ExtChargeIdling = settings.get("ExtChargeIdling")
config.SVR_Channel = settings.get("SVR_Channel") config.SVR_Channel = settings.get("SVR_Channel")
@ -78,6 +79,7 @@ function supervisor.load_config()
cfv.assert_type_table(config.FacilityTankList) cfv.assert_type_table(config.FacilityTankList)
cfv.assert_type_table(config.FacilityTankConns) cfv.assert_type_table(config.FacilityTankConns)
cfv.assert_type_table(config.TankFluidTypes) cfv.assert_type_table(config.TankFluidTypes)
cfv.assert_type_table(config.AuxiliaryCoolant)
cfv.assert_range(config.FacilityTankMode, 0, 8) cfv.assert_range(config.FacilityTankMode, 0, 8)
cfv.assert_type_bool(config.ExtChargeIdling) cfv.assert_type_bool(config.ExtChargeIdling)

View File

@ -66,7 +66,8 @@ local unit = {}
---@param num_boilers integer number of boilers expected ---@param num_boilers integer number of boilers expected
---@param num_turbines integer number of turbines expected ---@param num_turbines integer number of turbines expected
---@param ext_idle boolean extended idling mode ---@param ext_idle boolean extended idling mode
function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) ---@param aux_coolant boolean if this unit has auxiliary coolant
function unit.new(reactor_id, num_boilers, num_turbines, ext_idle, aux_coolant)
-- time (ms) to idle for auto idling -- time (ms) to idle for auto idling
local IDLE_TIME = util.trinary(ext_idle, 60000, 10000) local IDLE_TIME = util.trinary(ext_idle, 60000, 10000)
@ -79,6 +80,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
plc_i = nil, ---@type plc_session plc_i = nil, ---@type plc_session
num_boilers = num_boilers, num_boilers = num_boilers,
num_turbines = num_turbines, num_turbines = num_turbines,
aux_coolant = aux_coolant,
types = { DT_KEYS = DT_KEYS, AISTATE = AISTATE }, types = { DT_KEYS = DT_KEYS, AISTATE = AISTATE },
-- rtus -- rtus
rtu_list = {}, ---@type unit_session[][] rtu_list = {}, ---@type unit_session[][]
@ -92,7 +94,8 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
io_ctl = nil, ---@type rs_controller io_ctl = nil, ---@type rs_controller
---@diagnostic disable-next-line: missing-fields ---@diagnostic disable-next-line: missing-fields
valves = {}, ---@type unit_valves valves = {}, ---@type unit_valves
emcool_opened = false, em_cool_opened = false,
aux_cool_opened = false,
-- auto control -- auto control
auto_engaged = false, auto_engaged = false,
auto_idle = false, auto_idle = false,
@ -111,6 +114,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
damage_est_last = 0, damage_est_last = 0,
waste_product = WASTE.PLUTONIUM, ---@type WASTE_PRODUCT waste_product = WASTE.PLUTONIUM, ---@type WASTE_PRODUCT
status_text = { "UNKNOWN", "awaiting connection..." }, status_text = { "UNKNOWN", "awaiting connection..." },
enable_aux_cool = false,
-- logic for alarms -- logic for alarms
had_reactor = false, had_reactor = false,
turbine_flow_stable = false, turbine_flow_stable = false,
@ -373,6 +377,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
local waste_po = _make_valve_iface(IO.WASTE_POPL) local waste_po = _make_valve_iface(IO.WASTE_POPL)
local waste_sps = _make_valve_iface(IO.WASTE_AM) local waste_sps = _make_valve_iface(IO.WASTE_AM)
local emer_cool = _make_valve_iface(IO.U_EMER_COOL) local emer_cool = _make_valve_iface(IO.U_EMER_COOL)
local aux_cool = _make_valve_iface(IO.U_AUX_COOL)
---@class unit_valves ---@class unit_valves
self.valves = { self.valves = {
@ -380,7 +385,8 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
waste_sna = waste_sna, waste_sna = waste_sna,
waste_po = waste_po, waste_po = waste_po,
waste_sps = waste_sps, waste_sps = waste_sps,
emer_cool = emer_cool emer_cool = emer_cool,
aux_cool = aux_cool
} }
-- route reactor waste for a given waste product -- route reactor waste for a given waste product
@ -606,7 +612,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
if #self.redstone > 0 then if #self.redstone > 0 then
logic.handle_redstone(self) logic.handle_redstone(self)
elseif not self.plc_cache.rps_trip then elseif not self.plc_cache.rps_trip then
self.emcool_opened = false self.em_cool_opened = false
end end
end end
@ -724,7 +730,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
-- queue a command to clear timeout/auto-scram if set -- queue a command to clear timeout/auto-scram if set
function public.auto_cond_rps_reset() function public.auto_cond_rps_reset()
if self.plc_s ~= nil and self.plc_i ~= nil and (not self.auto_was_alarmed) and (not self.emcool_opened) then if self.plc_s ~= nil and self.plc_i ~= nil and (not self.auto_was_alarmed) and (not self.em_cool_opened) then
local rps = self.plc_i.get_rps() local rps = self.plc_i.get_rps()
if rps.timeout or rps.automatic then if rps.timeout or rps.automatic then
self.plc_i.auto_lock(true) -- if it timed out/restarted, auto lock was lost, so re-lock it self.plc_i.auto_lock(true) -- if it timed out/restarted, auto lock was lost, so re-lock it
@ -865,7 +871,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
-- check if emergency coolant activation has been tripped -- check if emergency coolant activation has been tripped
---@nodiscard ---@nodiscard
function public.is_emer_cool_tripped() return self.emcool_opened end function public.is_emer_cool_tripped() return self.em_cool_opened end
-- get build properties of machines -- get build properties of machines
-- --
@ -1059,7 +1065,8 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
v.waste_sna.check(), v.waste_sna.check(),
v.waste_po.check(), v.waste_po.check(),
v.waste_sps.check(), v.waste_sps.check(),
v.emer_cool.check() v.emer_cool.check(),
v.aux_cool.check()
} }
end end

View File

@ -35,6 +35,7 @@ local FLOW_STABILITY_DELAY_MS = const.FLOW_STABILITY_DELAY_MS
local ANNUNC_LIMS = const.ANNUNCIATOR_LIMITS local ANNUNC_LIMS = const.ANNUNCIATOR_LIMITS
local ALARM_LIMS = const.ALARM_LIMITS local ALARM_LIMS = const.ALARM_LIMITS
local RS_THRESH = const.RS_THRESHOLDS
---@class unit_logic_extension ---@class unit_logic_extension
local logic = {} local logic = {}
@ -54,6 +55,10 @@ function logic.update_annunciator(self)
-- variables for boiler, or reactor if no boilers used -- variables for boiler, or reactor if no boilers used
local total_boil_rate = 0.0 local total_boil_rate = 0.0
-- auxiliary coolant control
local need_aux_cool = false
local dis_aux_cool = true
--#region Reactor --#region Reactor
annunc.AutoControl = self.auto_engaged annunc.AutoControl = self.auto_engaged
@ -148,6 +153,9 @@ function logic.update_annunciator(self)
-- if no boilers, use reactor heating rate to check for boil rate mismatch -- if no boilers, use reactor heating rate to check for boil rate mismatch
if num_boilers == 0 then if num_boilers == 0 then
total_boil_rate = plc_db.mek_status.heating_rate total_boil_rate = plc_db.mek_status.heating_rate
need_aux_cool = plc_db.mek_status.ccool_fill <= RS_THRESH.AUX_COOL_ENABLE
dis_aux_cool = plc_db.mek_status.ccool_fill >= RS_THRESH.AUX_COOL_DISABLE
end end
else else
self.plc_cache.ok = false self.plc_cache.ok = false
@ -215,6 +223,9 @@ function logic.update_annunciator(self)
annunc.BoilerOnline[idx] = true annunc.BoilerOnline[idx] = true
annunc.WaterLevelLow[idx] = boiler.tanks.water_fill < ANNUNC_LIMS.WaterLevelLow annunc.WaterLevelLow[idx] = boiler.tanks.water_fill < ANNUNC_LIMS.WaterLevelLow
need_aux_cool = need_aux_cool or (boiler.tanks.water_fill <= RS_THRESH.AUX_COOL_ENABLE)
dis_aux_cool = dis_aux_cool and (boiler.tanks.water_fill >= RS_THRESH.AUX_COOL_DISABLE)
end end
-- check heating rate low -- check heating rate low
@ -341,11 +352,11 @@ function logic.update_annunciator(self)
end end
if rotation_stable then if rotation_stable then
log.debug(util.c("UNIT ", self.r_id, ": turbine ", idx, " reached rotational stability (", rotation, ")")) log.debug(util.c("UNIT ", self.r_id, " turbine ", idx, " reached rotational stability (", rotation, ")"))
end end
if flow_stable then if flow_stable then
log.debug(util.c("UNIT ", self.r_id, ": turbine ", idx, " reached flow stability (", turbine.state.flow_rate, " mB/t)")) log.debug(util.c("UNIT ", self.r_id, " turbine ", idx, " reached flow stability (", turbine.state.flow_rate, " mB/t)"))
end end
turbines_stable = turbines_stable and (rotation_stable or flow_stable) turbines_stable = turbines_stable and (rotation_stable or flow_stable)
@ -357,7 +368,7 @@ function logic.update_annunciator(self)
turbines_stable = false turbines_stable = false
log.debug(util.c("UNIT ", self.r_id, ": turbine ", idx, " reset stability (new rate ", turbine.state.steam_input_rate, " != ", last.input_rate," mB/t)")) log.debug(util.c("UNIT ", self.r_id, " turbine ", idx, " reset stability (new rate ", turbine.state.steam_input_rate, " != ", last.input_rate," mB/t)"))
end end
last.input_rate = turbine.state.steam_input_rate last.input_rate = turbine.state.steam_input_rate
@ -406,6 +417,12 @@ function logic.update_annunciator(self)
-- update auto control ready state for this unit -- update auto control ready state for this unit
self.db.control.ready = plc_ready and boilers_ready and turbines_ready self.db.control.ready = plc_ready and boilers_ready and turbines_ready
-- update auxiliary coolant command
if plc_ready then
self.enable_aux_cool = self.plc_i.get_db().mek_status.status and
(self.enable_aux_cool or need_aux_cool) and not (dis_aux_cool and self.turbine_flow_stable)
else self.enable_aux_cool = false end
end end
-- update an alarm state given conditions -- update an alarm state given conditions
@ -728,7 +745,7 @@ function logic.update_status_text(self)
self.status_text = { "RCS TRANSIENT", "check coolant system" } self.status_text = { "RCS TRANSIENT", "check coolant system" }
-- elseif is_active(self.alarms.RPSTransient) then -- elseif is_active(self.alarms.RPSTransient) then
-- RPS status handled when checking reactor status -- RPS status handled when checking reactor status
elseif self.emcool_opened then elseif self.em_cool_opened then
self.status_text = { "EMERGENCY COOLANT OPENED", "reset RPS to close valve" } self.status_text = { "EMERGENCY COOLANT OPENED", "reset RPS to close valve" }
-- connection dependent states -- connection dependent states
elseif self.plc_i ~= nil then elseif self.plc_i ~= nil then
@ -886,7 +903,7 @@ function logic.handle_redstone(self)
(annunc.CoolantLevelLow or (boiler_water_low and rps.ex_hcool)) and (annunc.CoolantLevelLow or (boiler_water_low and rps.ex_hcool)) and
is_active(self.alarms.ReactorOverTemp)) is_active(self.alarms.ReactorOverTemp))
if enable_emer_cool and not self.emcool_opened then if enable_emer_cool and not self.em_cool_opened then
log.debug(util.c(">> Emergency Coolant Enable Detail Report (Unit ", self.r_id, ") <<")) log.debug(util.c(">> Emergency Coolant Enable Detail Report (Unit ", self.r_id, ") <<"))
log.debug(util.c("| CoolantLevelLow[", annunc.CoolantLevelLow, "] CoolantLevelLowLow[", rps.low_cool, "] ExcessHeatedCoolant[", rps.ex_hcool, "]")) log.debug(util.c("| CoolantLevelLow[", annunc.CoolantLevelLow, "] CoolantLevelLowLow[", rps.low_cool, "] ExcessHeatedCoolant[", rps.ex_hcool, "]"))
log.debug(util.c("| ReactorOverTemp[", AISTATE_NAMES[self.alarms.ReactorOverTemp.state], "]")) log.debug(util.c("| ReactorOverTemp[", AISTATE_NAMES[self.alarms.ReactorOverTemp.state], "]"))
@ -910,13 +927,13 @@ function logic.handle_redstone(self)
end end
end end
if annunc.EmergencyCoolant > 1 and self.emcool_opened then if annunc.EmergencyCoolant > 1 and self.em_cool_opened then
log.info(util.c("UNIT ", self.r_id, " emergency coolant valve closed")) log.info(util.c("UNIT ", self.r_id, " emergency coolant valve closed"))
log.info(util.c("UNIT ", self.r_id, " turbines set to not dump steam")) log.info(util.c("UNIT ", self.r_id, " turbines set to not dump steam"))
end end
self.emcool_opened = false self.em_cool_opened = false
elseif enable_emer_cool or self.emcool_opened then elseif enable_emer_cool or self.em_cool_opened then
-- set turbines to dump excess steam -- set turbines to dump excess steam
for i = 1, #self.turbines do for i = 1, #self.turbines do
local session = self.turbines[i] local session = self.turbines[i]
@ -937,16 +954,33 @@ function logic.handle_redstone(self)
end end
end end
if annunc.EmergencyCoolant > 1 and not self.emcool_opened then if annunc.EmergencyCoolant > 1 and not self.em_cool_opened then
log.info(util.c("UNIT ", self.r_id, " emergency coolant valve opened")) log.info(util.c("UNIT ", self.r_id, " emergency coolant valve opened"))
log.info(util.c("UNIT ", self.r_id, " turbines set to dump excess steam")) log.info(util.c("UNIT ", self.r_id, " turbines set to dump excess steam"))
end end
self.emcool_opened = true self.em_cool_opened = true
end end
-- set valve state always -- set valve state always
if self.emcool_opened then self.valves.emer_cool.open() else self.valves.emer_cool.close() end if self.em_cool_opened then self.valves.emer_cool.open() else self.valves.emer_cool.close() end
-----------------------
-- Auxiliary Coolant --
-----------------------
if self.aux_coolant then
if self.enable_aux_cool and (not self.aux_cool_opened) then
log.info(util.c("UNIT ", self.r_id, " auxiliary coolant valve opened"))
self.aux_cool_opened = true
elseif (not self.enable_aux_cool) and self.aux_cool_opened then
log.info(util.c("UNIT ", self.r_id, " auxiliary coolant valve closed"))
self.aux_cool_opened = false
end
-- set valve state always
if self.aux_cool_opened then self.valves.aux_cool.open() else self.valves.aux_cool.close() end
end
end end
return logic return logic