diff --git a/scada-common/types.lua b/scada-common/types.lua index 41946eb..23a4006 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -8,7 +8,7 @@ local types = {} -- CLASSES -- ---@class tank_fluid ----@field name string +---@field name fluid ---@field amount integer -- create a new tank fluid @@ -222,6 +222,19 @@ types.ALARM_STATE = { ---| "sys_fail" ---| "force_disabled" +---@alias fluid +---| "mekanism:empty_gas" +---| "minecraft:water" +---| "mekanism:sodium" +---| "mekanism:superheated_sodium" + +types.fluid = { + empty_gas = "mekanism:empty_gas", + water = "minecraft:water", + sodium = "mekanism:sodium", + superheated_sodium = "mekanism:superheated_sodium" +} + ---@alias rtu_t string types.rtu_t = { redstone = "redstone", diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 1b2634c..2dd07d5 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -301,6 +301,7 @@ function facility.new(num_reactors, cooling_conf) if (self.mode ~= PROCESS.MATRIX_FAULT_IDLE) and (self.mode ~= PROCESS.SYSTEM_ALARM_IDLE) then -- auto clear ASCRAM self.ascram = false + self.ascram_reason = AUTO_SCRAM.NONE end local blade_count = nil @@ -372,12 +373,18 @@ function facility.new(num_reactors, cooling_conf) if self.mode == PROCESS.INACTIVE then if not self.units_ready then self.status_text = { "NOT READY", "assigned units not ready" } - elseif self.start_fail == START_STATUS.NO_UNITS and assign_count == 0 then - self.status_text = { "START FAILED", "no units were assigned" } - elseif self.start_fail == START_STATUS.BLADE_MISMATCH then - self.status_text = { "START FAILED", "turbine blade count mismatch" } else - self.status_text = { "IDLE", "control disengaged" } + -- clear ASCRAM once ready + self.ascram = false + self.ascram_reason = AUTO_SCRAM.NONE + + if self.start_fail == START_STATUS.NO_UNITS and assign_count == 0 then + self.status_text = { "START FAILED", "no units were assigned" } + elseif self.start_fail == START_STATUS.BLADE_MISMATCH then + self.status_text = { "START FAILED", "turbine blade count mismatch" } + else + self.status_text = { "IDLE", "control disengaged" } + end end elseif self.mode == PROCESS.MAX_BURN then -- run units at their limits @@ -522,7 +529,7 @@ function facility.new(num_reactors, cooling_conf) next_mode = self.return_mode log.info("FAC: exiting matrix fault idle state due to fault resolution") elseif self.ascram_reason == AUTO_SCRAM.CRIT_ALARM then - next_mode = PROCESS.INACTIVE + next_mode = PROCESS.SYSTEM_ALARM_IDLE log.info("FAC: exiting matrix fault idle state due to critical unit alarm") end elseif self.mode == PROCESS.SYSTEM_ALARM_IDLE then @@ -545,55 +552,54 @@ function facility.new(num_reactors, cooling_conf) local astatus = self.ascram_status - if (self.mode ~= PROCESS.INACTIVE) and (self.mode ~= PROCESS.SYSTEM_ALARM_IDLE) then - if self.induction[1] ~= nil then - local matrix = self.induction[1] ---@type unit_session - local db = matrix.get_db() ---@type imatrix_session_db + if self.induction[1] ~= nil then + local matrix = self.induction[1] ---@type unit_session + local db = matrix.get_db() ---@type imatrix_session_db - -- clear matrix disconnected - if astatus.matrix_dc then - astatus.matrix_dc = false - log.info("FAC: induction matrix reconnected, clearing ASCRAM condition") - end - - -- 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) - - if was_fill and not astatus.matrix_fill then - log.info("FAC: charge state of induction matrix entered acceptable range <= " .. (RE_ENABLE_CHARGE * 100) .. "%") - end - - -- check for critical unit alarms - for i = 1, #self.units do - local u = self.units[i] ---@type reactor_unit - - if u.has_critical_alarm() then - log.info(util.c("FAC: emergency exit of process control due to critical unit alarm (unit ", u.get_id(), ")")) - break - end - end - - -- check for facility radiation - if self.envd[1] ~= nil then - 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 - 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 - end - - -- system not ready, will need to restart GEN_RATE mode - -- clears when we enter the fault waiting state - astatus.gen_fault = self.mode == PROCESS.GEN_RATE and not self.units_ready - else - astatus.matrix_dc = true + -- clear matrix disconnected + if astatus.matrix_dc then + astatus.matrix_dc = false + log.info("FAC: induction matrix reconnected, clearing ASCRAM condition") end - -- log.debug(util.c("dc: ", astatus.matrix_dc, " fill: ", astatus.matrix_fill, " crit: ", astatus.crit_alarm, " gen: ", astatus.gen_fault)) + -- 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) + if was_fill and not astatus.matrix_fill then + log.info("FAC: charge state of induction matrix entered acceptable range <= " .. (RE_ENABLE_CHARGE * 100) .. "%") + end + + -- check for critical unit alarms + astatus.crit_alarm = false + for i = 1, #self.units do + local u = self.units[i] ---@type reactor_unit + + if u.has_critical_alarm() then + astatus.crit_alarm = true + break + end + end + + -- check for facility radiation + if self.envd[1] ~= nil then + 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 + 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 + end + + -- system not ready, will need to restart GEN_RATE mode + -- clears when we enter the fault waiting state + astatus.gen_fault = self.mode == PROCESS.GEN_RATE and not self.units_ready + else + astatus.matrix_dc = true + end + + if (self.mode ~= PROCESS.INACTIVE) and (self.mode ~= PROCESS.SYSTEM_ALARM_IDLE) then local scram = astatus.matrix_dc or astatus.matrix_fill or astatus.crit_alarm or astatus.gen_fault if scram and not self.ascram then @@ -611,6 +617,7 @@ function facility.new(num_reactors, cooling_conf) self.status_text = { "AUTOMATIC SCRAM", "critical unit alarm tripped" } log.info("FAC: automatic SCRAM due to critical unit alarm") + log.warning("FAC: emergency exit of process control due to critical unit alarm") elseif astatus.radiation then next_mode = PROCESS.SYSTEM_ALARM_IDLE self.ascram_reason = AUTO_SCRAM.RADIATION diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 4289b17..8097f83 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.11.11" +local SUPERVISOR_VERSION = "beta-v0.11.12" local print = util.print local println = util.println diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 6cece31..ec27d14 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -109,12 +109,26 @@ function logic.update_annunciator(self) self.db.annunciator.AutoReactorSCRAM = plc_db.rps_trip_cause == types.rps_status_t.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 = plc_db.mek_status.ccool_fill < 0.75 or plc_db.mek_status.hcool_fill > 0.25 + self.db.annunciator.CoolantLevelLow = plc_db.mek_status.ccool_fill < 0.5 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 - ---@todo this is dependent on setup, i.e. how much coolant is buffered and the turbine setup - self.db.annunciator.HighStartupRate = not plc_db.mek_status.status and plc_db.mek_status.burn_rate > 40 + + -- this warning applies when no coolant is buffered (which we can't easily determine without running) + --[[ + logic is that each tick, the heating rate worth of coolant steps between: + reactor tank + reactor heated coolant outflow tube + boiler/turbine tank + reactor cooled coolant return tube + so if there is a tick where coolant is no longer present in the reactor, then bad things happen. + such as when a burn rate consumes half the coolant in the tank, meaning that: + 50% at some point will be in the boiler, and 50% in a tube, so that leaves 0% in the reactor + ]]-- + local heating_rate_conv = util.trinary(plc_db.mek_status.ccool_type == types.fluid.sodium, 200000, 20000) + local high_rate = (plc_db.mek_status.ccool_amnt / (plc_db.mek_status.burn_rate * heating_rate_conv)) < 4 + self.db.annunciator.HighStartupRate = not plc_db.mek_status.status and high_rate -- if no boilers, use reactor heating rate to check for boil rate mismatch if num_boilers == 0 then