#118 safety/constants common file

This commit is contained in:
Mikayla Fischler 2023-02-25 02:25:35 -05:00
parent 16d6372d7b
commit 4f285cf2b5
5 changed files with 108 additions and 57 deletions

View File

@ -1,4 +1,5 @@
local comms = require("scada-common.comms")
local const = require("scada-common.constants")
local log = require("scada-common.log")
local ppm = require("scada-common.ppm")
local types = require("scada-common.types")
@ -15,6 +16,8 @@ local RPLC_TYPE = comms.RPLC_TYPE
local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
local AUTO_ACK = comms.PLC_AUTO_ACK
local RPS_LIMITS = const.RPS_LIMITS
local print = util.print
local println = util.println
local print_ts = util.print_ts
@ -25,16 +28,6 @@ local println_ts = util.println_ts
local PCALL_SCRAM_MSG = "pcall: Scram requires the reactor to be active."
local PCALL_START_MSG = "pcall: Reactor is already active."
--#region RPS SAFETY CONSTANTS
local MAX_DAMAGE_PERCENT = 90
local MAX_DAMAGE_TEMPERATURE = 1200
local MIN_COOLANT_FILL = 0.10
local MAX_WASTE_FILL = 0.8
local MAX_HEATED_COLLANT_FILL = 0.95
--#endregion END RPS SAFETY CONSTANTS
-- RPS: Reactor Protection System<br>
-- identifies dangerous states and SCRAMs reactor if warranted<br>
-- autonomous from main SCADA supervisor/coordinator control
@ -118,7 +111,7 @@ function plc.rps_init(reactor, is_formed)
-- lost the peripheral or terminated, handled later
_set_fault()
elseif not self.state[state_keys.dmg_crit] then
self.state[state_keys.dmg_crit] = damage_percent >= MAX_DAMAGE_PERCENT
self.state[state_keys.dmg_crit] = damage_percent >= RPS_LIMITS.MAX_DAMAGE_PERCENT
end
end
@ -130,7 +123,7 @@ function plc.rps_init(reactor, is_formed)
-- lost the peripheral or terminated, handled later
_set_fault()
elseif not self.state[state_keys.high_temp] then
self.state[state_keys.high_temp] = temp >= MAX_DAMAGE_TEMPERATURE
self.state[state_keys.high_temp] = temp >= RPS_LIMITS.MAX_DAMAGE_TEMPERATURE
end
end
@ -141,7 +134,7 @@ function plc.rps_init(reactor, is_formed)
-- lost the peripheral or terminated, handled later
_set_fault()
elseif not self.state[state_keys.no_coolant] then
self.state[state_keys.no_coolant] = coolant_filled < MIN_COOLANT_FILL
self.state[state_keys.no_coolant] = coolant_filled < RPS_LIMITS.MIN_COOLANT_FILL
end
end
@ -152,7 +145,7 @@ function plc.rps_init(reactor, is_formed)
-- lost the peripheral or terminated, handled later
_set_fault()
elseif not self.state[state_keys.ex_waste] then
self.state[state_keys.ex_waste] = w_filled > MAX_WASTE_FILL
self.state[state_keys.ex_waste] = w_filled > RPS_LIMITS.MAX_WASTE_FILL
end
end
@ -163,7 +156,7 @@ function plc.rps_init(reactor, is_formed)
-- lost the peripheral or terminated, handled later
_set_fault()
elseif not self.state[state_keys.ex_hcoolant] then
self.state[state_keys.ex_hcoolant] = hc_filled > MAX_HEATED_COLLANT_FILL
self.state[state_keys.ex_hcoolant] = hc_filled > RPS_LIMITS.MAX_HEATED_COLLANT_FILL
end
end
@ -174,7 +167,7 @@ function plc.rps_init(reactor, is_formed)
-- lost the peripheral or terminated, handled later
_set_fault()
elseif not self.state[state_keys.no_fuel] then
self.state[state_keys.no_fuel] = fuel == 0
self.state[state_keys.no_fuel] = fuel <= RPS_LIMITS.NO_FUEL_FILL
end
end

View File

@ -0,0 +1,71 @@
--
-- System and Safety Constants
--
-- Notes on Radiation
-- - background radiation 0.0000001 Sv/h (99.99 nSv/h)
-- - "green tint" radiation 0.00001 Sv/h (10 uSv/h)
-- - damaging radiation 0.00006 Sv/h (60 uSv/h)
local constants = {}
--#region Reactor Protection System (on the PLC) Limits
local rps = {}
rps.MAX_DAMAGE_PERCENT = 90 -- damage >= 90%
rps.MAX_DAMAGE_TEMPERATURE = 1200 -- temp >= 1200K
rps.MIN_COOLANT_FILL = 0.10 -- fill < 10%
rps.MAX_WASTE_FILL = 0.8 -- fill > 80%
rps.MAX_HEATED_COLLANT_FILL = 0.95 -- fill > 95%
rps.NO_FUEL_FILL = 0.0 -- fill <= 0%
constants.RPS_LIMITS = rps
--#endregion
--#region Annunciator Limits
local annunc = {}
annunc.RCSFlowLow = -2.0 -- flow < -2.0 mB/s
annunc.CoolantLevelLow = 0.4 -- fill < 40%
annunc.ReactorTempHigh = 1000 -- temp > 1000K
annunc.ReactorHighDeltaT = 50 -- rate > 50K/s
annunc.FuelLevelLow = 0.05 -- fill <= 5%
annunc.WasteLevelHigh = 0.85 -- fill >= 85%
annunc.SteamFeedMismatch = 10 -- ±10mB difference between total coolant flow and total steam input rate
annunc.RadiationWarning = 0.00001 -- 10 uSv/h
constants.ANNUNCIATOR_LIMITS = annunc
--#endregion
--#region Supervisor Alarm Limits
local alarms = {}
-- unit alarms
alarms.HIGH_TEMP = 1150 -- temp >= 1150K
alarms.HIGH_WASTE = 0.5 -- fill > 50%
alarms.HIGH_RADIATION = 0.00005 -- 50 uSv/h, not yet damaging but this isn't good
-- facility alarms
alarms.CHARGE_HIGH = 1.0 -- once at or above 100% charge
alarms.CHARGE_RE_ENABLE = 0.95 -- once below 95% charge
alarms.FAC_HIGH_RAD = 0.00001 -- 10 uSv/h
constants.ALARM_LIMITS = alarms
--#endregion
--#region Supervisor Constants
-- milliseconds until turbine flow is assumed to be stable enough to enable coolant checks
constants.FLOW_STABILITY_DELAY_MS = 15000
--#endregion
return constants

View File

@ -1,3 +1,4 @@
local const = require("scada-common.constants")
local log = require("scada-common.log")
local rsio = require("scada-common.rsio")
local types = require("scada-common.types")
@ -16,15 +17,9 @@ local IO = rsio.IO
-- 2856 FE per blade per 1 mB, 285.6 FE per blade per 0.1 mB (minimum)
local POWER_PER_BLADE = util.joules_to_fe(7140)
local FLOW_STABILITY_DELAY_S = unit.FLOW_STABILITY_DELAY_MS / 1000
local FLOW_STABILITY_DELAY_S = const.FLOW_STABILITY_DELAY_MS / 1000
-- background radiation 0.0000001 Sv/h (99.99 nSv/h)
-- "green tint" radiation 0.00001 Sv/h (10 uSv/h)
-- damaging radiation 0.00006 Sv/h (60 uSv/h)
local RADIATION_ALARM_LEVEL = 0.00001
local HIGH_CHARGE = 1.0
local RE_ENABLE_CHARGE = 0.95
local ALARM_LIMS = const.ALARM_LIMITS
local AUTO_SCRAM = {
NONE = 0,
@ -563,10 +558,10 @@ function facility.new(num_reactors, cooling_conf)
-- check matrix fill too high
local was_fill = astatus.matrix_fill
astatus.matrix_fill = (db.tanks.energy_fill >= HIGH_CHARGE) or (astatus.matrix_fill and db.tanks.energy_fill > RE_ENABLE_CHARGE)
astatus.matrix_fill = (db.tanks.energy_fill >= ALARM_LIMS.CHARGE_HIGH) or (astatus.matrix_fill and db.tanks.energy_fill > ALARM_LIMS.CHARGE_RE_ENABLE)
if was_fill and not astatus.matrix_fill then
log.info("FAC: charge state of induction matrix entered acceptable range <= " .. (RE_ENABLE_CHARGE * 100) .. "%")
log.info("FAC: charge state of induction matrix entered acceptable range <= " .. (ALARM_LIMS.CHARGE_RE_ENABLE * 100) .. "%")
end
-- check for critical unit alarms
@ -585,7 +580,7 @@ function facility.new(num_reactors, cooling_conf)
local envd = self.envd[1] ---@type unit_session
local e_db = envd.get_db() ---@type envd_session_db
astatus.radiation = e_db.radiation_raw > RADIATION_ALARM_LEVEL
astatus.radiation = e_db.radiation_raw > ALARM_LIMS.FAC_HIGH_RAD
else
-- don't clear, if it is true then we lost it with high radiation, so just keep alarming
-- operator can restart the system or hit the stop/reset button

View File

@ -12,8 +12,6 @@ local rsctl = require("supervisor.session.rsctl")
local unit = {}
local WASTE_MODE = types.WASTE_MODE
local DUMPING_MODE = types.DUMPING_MODE
local ALARM = types.ALARM
local PRIO = types.ALARM_PRIORITY
local ALARM_STATE = types.ALARM_STATE
@ -23,8 +21,6 @@ local PLC_S_CMDS = plc.PLC_S_CMDS
local IO = rsio.IO
local FLOW_STABILITY_DELAY_MS = 15000
local DT_KEYS = {
ReactorBurnR = "RBR",
ReactorTemp = "RTP",
@ -50,8 +46,6 @@ local AISTATE = {
RING_BACK_TRIPPING = 6
}
unit.FLOW_STABILITY_DELAY_MS = FLOW_STABILITY_DELAY_MS
---@class alarm_def
---@field state ALARM_INT_STATE internal alarm state
---@field trip_time integer time (ms) when first tripped
@ -73,7 +67,6 @@ function unit.new(reactor_id, num_boilers, num_turbines)
num_boilers = num_boilers,
num_turbines = num_turbines,
types = { DT_KEYS = DT_KEYS, AISTATE = AISTATE },
defs = { FLOW_STABILITY_DELAY_MS = FLOW_STABILITY_DELAY_MS },
-- rtus
redstone = {},
boilers = {},

View File

@ -1,3 +1,4 @@
local const = require("scada-common.constants")
local log = require("scada-common.log")
local rsio = require("scada-common.rsio")
local types = require("scada-common.types")
@ -5,11 +6,10 @@ local util = require("scada-common.util")
local plc = require("supervisor.session.plc")
local TRI_FAIL = types.TRI_FAIL
local TRI_FAIL = types.TRI_FAIL
local DUMPING_MODE = types.DUMPING_MODE
local PRIO = types.ALARM_PRIORITY
local ALARM_STATE = types.ALARM_STATE
local PRIO = types.ALARM_PRIORITY
local ALARM_STATE = types.ALARM_STATE
local IO = rsio.IO
@ -24,11 +24,10 @@ local AISTATE_NAMES = {
"RING_BACK_TRIPPING"
}
-- background radiation 0.0000001 Sv/h (99.99 nSv/h)
-- "green tint" radiation 0.00001 Sv/h (10 uSv/h)
-- damaging radiation 0.00006 Sv/h (60 uSv/h)
local RADIATION_ALERT_LEVEL = 0.00001 -- 10 uSv/h
local RADIATION_ALARM_LEVEL = 0.00005 -- 50 uSv/h, not yet damaging but this isn't good
local FLOW_STABILITY_DELAY_MS = const.FLOW_STABILITY_DELAY_MS
local ANNUNC_LIMS = const.ANNUNCIATOR_LIMITS
local ALARM_LIMS = const.ALARM_LIMITS
---@class unit_logic_extension
local logic = {}
@ -111,12 +110,12 @@ function logic.update_annunciator(self)
self.db.annunciator.ManualReactorSCRAM = plc_db.rps_trip_cause == types.RPS_TRIP_CAUSE.MANUAL
self.db.annunciator.AutoReactorSCRAM = plc_db.rps_trip_cause == types.RPS_TRIP_CAUSE.AUTOMATIC
self.db.annunciator.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.no_cool)
self.db.annunciator.RCSFlowLow = _get_dt(DT_KEYS.ReactorCCool) < -2.0
self.db.annunciator.CoolantLevelLow = plc_db.mek_status.ccool_fill < 0.4
self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > 1000
self.db.annunciator.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > 100
self.db.annunciator.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < -1.0 or plc_db.mek_status.fuel_fill <= 0.01
self.db.annunciator.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 1.0 or plc_db.mek_status.waste_fill >= 0.85
self.db.annunciator.RCSFlowLow = _get_dt(DT_KEYS.ReactorCCool) < ANNUNC_LIMS.RCSFlowLow
self.db.annunciator.CoolantLevelLow = plc_db.mek_status.ccool_fill < ANNUNC_LIMS.CoolantLevelLow
self.db.annunciator.ReactorTempHigh = plc_db.mek_status.temp > ANNUNC_LIMS.ReactorTempHigh
self.db.annunciator.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > ANNUNC_LIMS.ReactorHighDeltaT
self.db.annunciator.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < -1.0 or plc_db.mek_status.fuel_fill <= ANNUNC_LIMS.FuelLevelLow
self.db.annunciator.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 1.0 or plc_db.mek_status.waste_fill >= ANNUNC_LIMS.WasteLevelHigh
-- this warning applies when no coolant is buffered (which we can't easily determine without running)
--[[
@ -150,7 +149,7 @@ function logic.update_annunciator(self)
for i = 1, #self.envd do
local envd = self.envd[i] ---@type unit_session
self.db.annunciator.RadiationMonitor = util.trinary(envd.is_faulted(), 2, 3)
self.db.annunciator.RadiationWarning = envd.get_db().radiation_raw > RADIATION_ALERT_LEVEL
self.db.annunciator.RadiationWarning = envd.get_db().radiation_raw > ANNUNC_LIMS.RadiationWarning
break
end
@ -299,7 +298,7 @@ function logic.update_annunciator(self)
self.db.annunciator.BoilRateMismatch = math.abs(total_boil_rate - total_input_rate) > (0.04 * total_boil_rate)
-- check for steam feed mismatch and max return rate
local sfmismatch = math.abs(total_flow_rate - total_input_rate) > 10
local sfmismatch = math.abs(total_flow_rate - total_input_rate) > ANNUNC_LIMS.SteamFeedMismatch
sfmismatch = sfmismatch or boiler_steam_dt_sum > 2.0 or boiler_water_dt_sum < -2.0
self.db.annunciator.SteamFeedMismatch = sfmismatch
self.db.annunciator.MaxWaterReturnFeed = max_water_return_rate == total_flow_rate and total_flow_rate ~= 0
@ -449,7 +448,7 @@ function logic.update_alarms(self)
-- Containment Radiation
local rad_alarm = false
for i = 1, #self.envd do
rad_alarm = self.envd[i].get_db().radiation_raw > RADIATION_ALARM_LEVEL
rad_alarm = self.envd[i].get_db().radiation_raw > ALARM_LIMS.HIGH_RADIATION
break
end
_update_alarm_state(self, rad_alarm, self.alarms.ContainmentRadiation)
@ -469,14 +468,14 @@ function logic.update_alarms(self)
_update_alarm_state(self, (plc_cache.temp >= 1200) or rps_high_temp, self.alarms.ReactorOverTemp)
-- High Temperature
_update_alarm_state(self, plc_cache.temp > 1150, self.alarms.ReactorHighTemp)
_update_alarm_state(self, plc_cache.temp >= ALARM_LIMS.HIGH_TEMP, self.alarms.ReactorHighTemp)
-- Waste Leak
_update_alarm_state(self, plc_cache.waste >= 0.99, self.alarms.ReactorWasteLeak)
_update_alarm_state(self, plc_cache.waste >= 1.0, self.alarms.ReactorWasteLeak)
-- High Waste
local rps_high_waste = plc_cache.rps_status.ex_waste and not self.last_rps_trips.ex_waste
_update_alarm_state(self, (plc_cache.waste > 0.50) or rps_high_waste, self.alarms.ReactorHighWaste)
_update_alarm_state(self, (plc_cache.waste > ALARM_LIMS.HIGH_WASTE) or rps_high_waste, self.alarms.ReactorHighWaste)
-- RPS Transient (excludes timeouts and manual trips)
local rps_alarm = false
@ -501,7 +500,7 @@ function logic.update_alarms(self)
-- annunciator indicators for these states may not indicate a real issue when:
-- > flow is ramping up right after reactor start
-- > flow is ramping down after reactor shutdown
if ((util.time_ms() - self.last_rate_change_ms) > self.defs.FLOW_STABILITY_DELAY_MS) and plc_cache.active then
if ((util.time_ms() - self.last_rate_change_ms) > FLOW_STABILITY_DELAY_MS) and plc_cache.active then
rcs_trans = rcs_trans or annunc.BoilRateMismatch or annunc.CoolantFeedMismatch or annunc.SteamFeedMismatch
end
@ -621,7 +620,7 @@ function logic.update_status_text(self)
self.status_text[2] = "insufficient fuel input rate"
elseif self.db.annunciator.WasteLineOcclusion then
self.status_text[2] = "insufficient waste output rate"
elseif (util.time_ms() - self.last_rate_change_ms) <= self.defs.FLOW_STABILITY_DELAY_MS then
elseif (util.time_ms() - self.last_rate_change_ms) <= FLOW_STABILITY_DELAY_MS then
self.status_text[2] = "awaiting flow stability"
else
self.status_text[2] = "system nominal"