mirror of
https://github.com/MikaylaFischler/cc-mek-scada.git
synced 2024-08-30 18:22:34 +00:00
225 lines
7.8 KiB
Lua
225 lines
7.8 KiB
Lua
local types = require "scada-common.types"
|
|
local util = require "scada-common.util"
|
|
|
|
local unit = {}
|
|
|
|
---@alias TRI_FAIL integer
|
|
local TRI_FAIL = {
|
|
OK = 0,
|
|
PARTIAL = 1,
|
|
FULL = 2
|
|
}
|
|
|
|
-- create a new reactor unit
|
|
---@param for_reactor integer reactor unit number
|
|
---@param num_boilers integer number of boilers expected
|
|
---@param num_turbines integer number of turbines expected
|
|
unit.new = function (for_reactor, num_boilers, num_turbines)
|
|
local self = {
|
|
r_id = for_reactor,
|
|
plc_s = nil, ---@class plc_session
|
|
counts = { boilers = num_boilers, turbines = num_turbines },
|
|
turbines = {},
|
|
boilers = {},
|
|
energy_storage = {},
|
|
redstone = {},
|
|
deltas = {
|
|
last_reactor_temp = nil,
|
|
last_reactor_temp_time = 0
|
|
},
|
|
db = {
|
|
---@class annunciator
|
|
annunciator = {
|
|
-- reactor
|
|
PLCOnline = false,
|
|
ReactorTrip = false,
|
|
ManualReactorTrip = false,
|
|
RCPTrip = false,
|
|
RCSFlowLow = false,
|
|
ReactorTempHigh = false,
|
|
ReactorHighDeltaT = false,
|
|
HighStartupRate = false,
|
|
-- boiler
|
|
BoilerOnline = TRI_FAIL.OK,
|
|
HeatingRateLow = false,
|
|
BoilRateMismatch = false,
|
|
CoolantFeedMismatch = false,
|
|
-- turbine
|
|
TurbineOnline = TRI_FAIL.OK,
|
|
SteamFeedMismatch = false,
|
|
SteamDumpOpen = false,
|
|
TurbineOverSpeed = false,
|
|
TurbineTrip = false
|
|
}
|
|
}
|
|
}
|
|
|
|
---@class reactor_unit
|
|
local public = {}
|
|
|
|
-- PRIVATE FUNCTIONS --
|
|
|
|
-- update the annunciator
|
|
local _update_annunciator = function ()
|
|
-- check PLC status
|
|
self.db.annunciator.PLCOnline = (self.plc_s ~= nil) and (self.plc_s.open)
|
|
|
|
if self.plc_s ~= nil then
|
|
-------------
|
|
-- REACTOR --
|
|
-------------
|
|
|
|
local plc_db = self.plc_s.get_db()
|
|
|
|
-- compute deltas
|
|
local reactor_delta_t = 0
|
|
if self.deltas.last_reactor_temp ~= nil then
|
|
reactor_delta_t = (plc_db.mek_status.temp - self.deltas.last_reactor_temp) / (util.time_s() - self.deltas.last_reactor_temp_time)
|
|
else
|
|
self.deltas.last_reactor_temp = plc_db.mek_status.temp
|
|
self.deltas.last_reactor_temp_time = util.time_s()
|
|
end
|
|
|
|
-- update annunciator
|
|
self.db.annunciator.ReactorTrip = plc_db.rps_tripped
|
|
self.db.annunciator.ManualReactorTrip = plc_db.rps_trip_cause == types.rps_status_t.manual
|
|
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.ReactorTempHigh = plc_db.mek_status.temp > 1000
|
|
self.db.annunciator.ReactorHighDeltaT = reactor_delta_t > 100
|
|
-- @todo this is dependent on setup, i.e. how much coolant is buffered and the turbine setup
|
|
self.db.annunciator.HighStartupRate = not plc_db.control_state and plc_db.mek_status.burn_rate > 40
|
|
end
|
|
|
|
-------------
|
|
-- BOILERS --
|
|
-------------
|
|
|
|
-- check boiler online status
|
|
local connected_boilers = #self.boilers
|
|
if connected_boilers == 0 and self.num_boilers > 0 then
|
|
self.db.annunciator.BoilerOnline = TRI_FAIL.FULL
|
|
elseif connected_boilers > 0 and connected_boilers ~= self.num_boilers then
|
|
self.db.annunciator.BoilerOnline = TRI_FAIL.PARTIAL
|
|
else
|
|
self.db.annunciator.BoilerOnline = TRI_FAIL.OK
|
|
end
|
|
|
|
local total_boil_rate = 0.0
|
|
local no_boil_count = 0
|
|
for i = 1, #self.boilers do
|
|
local boiler = self.boilers[i].get_db() ---@type boiler_session_db
|
|
local boil_rate = boiler.state.boil_rate
|
|
if boil_rate == 0 then
|
|
no_boil_count = no_boil_count + 1
|
|
else
|
|
total_boil_rate = total_boil_rate + boiler.state.boil_rate
|
|
end
|
|
end
|
|
|
|
if no_boil_count == 0 and self.num_boilers > 0 then
|
|
self.db.annunciator.HeatingRateLow = TRI_FAIL.FULL
|
|
elseif no_boil_count > 0 and no_boil_count ~= self.num_boilers then
|
|
self.db.annunciator.HeatingRateLow = TRI_FAIL.PARTIAL
|
|
else
|
|
self.db.annunciator.HeatingRateLow = TRI_FAIL.OK
|
|
end
|
|
|
|
if self.plc_s ~= nil then
|
|
local expected_boil_rate = self.plc_s.get_db().mek_status.heating_rate / 10.0
|
|
self.db.annunciator.BoilRateMismatch = math.abs(expected_boil_rate - total_boil_rate) > 25.0
|
|
else
|
|
self.db.annunciator.BoilRateMismatch = false
|
|
end
|
|
|
|
--------------
|
|
-- TURBINES --
|
|
--------------
|
|
|
|
-- check turbine online status
|
|
local connected_turbines = #self.turbines
|
|
if connected_turbines == 0 and self.num_turbines > 0 then
|
|
self.db.annunciator.TurbineOnline = TRI_FAIL.FULL
|
|
elseif connected_turbines > 0 and connected_turbines ~= self.num_turbines then
|
|
self.db.annunciator.TurbineOnline = TRI_FAIL.PARTIAL
|
|
else
|
|
self.db.annunciator.TurbineOnline = TRI_FAIL.OK
|
|
end
|
|
|
|
--[[
|
|
Turbine Under/Over Speed
|
|
]]--
|
|
|
|
--[[
|
|
Turbine Trip
|
|
a turbine trip is when the turbine stops, which means we are no longer receiving water and lose the ability to cool
|
|
this can be identified by these conditions:
|
|
- the current flow rate is 0 mB/t and it should not be
|
|
- it should not be if the boiler or reactor has a non-zero heating rate
|
|
- can initially catch this by detecting a 0 flow rate with a non-zero input rate, but eventually the steam will fill up
|
|
- can later identified by presence of steam in tank with a 0 flow rate
|
|
]]--
|
|
end
|
|
|
|
-- unlink disconnected units
|
|
---@param sessions table
|
|
local _unlink_disconnected_units = function (sessions)
|
|
util.filter_table(sessions, function (u) return u.is_connected() end)
|
|
end
|
|
|
|
-- PUBLIC FUNCTIONS --
|
|
|
|
-- link the PLC
|
|
---@param plc_session plc_session_struct
|
|
public.link_plc_session = function (plc_session)
|
|
self.plc_s = plc_session
|
|
self.deltas.last_reactor_temp = self.plc_s.get_db().mek_status.temp
|
|
self.deltas.last_reactor_temp_time = util.time_s()
|
|
end
|
|
|
|
-- link a turbine RTU session
|
|
---@param turbine unit_session
|
|
public.add_turbine = function (turbine)
|
|
table.insert(self.turbines, turbine)
|
|
end
|
|
|
|
-- link a boiler RTU session
|
|
---@param boiler unit_session
|
|
public.add_boiler = function (boiler)
|
|
table.insert(self.boilers, boiler)
|
|
end
|
|
|
|
-- link a redstone RTU capability
|
|
public.add_redstone = function (field, accessor)
|
|
-- ensure field exists
|
|
if self.redstone[field] == nil then
|
|
self.redstone[field] = {}
|
|
end
|
|
|
|
-- insert into list
|
|
table.insert(self.redstone[field], accessor)
|
|
end
|
|
|
|
-- update (iterate) this session
|
|
public.update = function ()
|
|
-- unlink PLC if session was closed
|
|
if not self.plc_s.open then
|
|
self.plc_s = nil
|
|
end
|
|
|
|
-- unlink RTU unit sessions if they are closed
|
|
_unlink_disconnected_units(self.boilers)
|
|
_unlink_disconnected_units(self.turbines)
|
|
|
|
-- update annunciator logic
|
|
_update_annunciator()
|
|
end
|
|
|
|
-- get the annunciator status
|
|
public.get_annunciator = function () return self.db.annunciator end
|
|
|
|
return public
|
|
end
|
|
|
|
return unit
|