diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 44f35c8..b79166f 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -23,7 +23,9 @@ local io = {} function iocontrol.init(conf, comms) ---@class ioctl_facility io.facility = { + num_units = conf.num_units, ---@type integer all_sys_ok = false, + rtu_count = 0, auto_ready = false, auto_active = false, @@ -42,8 +44,6 @@ function iocontrol.init(conf, comms) radiation = types.new_zero_radiation_reading(), - num_units = conf.num_units, ---@type integer - save_cfg_ack = function (success) end, ---@param success boolean start_ack = function (success) end, ---@param success boolean stop_ack = function (success) end, ---@param success boolean @@ -336,7 +336,12 @@ function iocontrol.update_facility_status(status) local rtu_statuses = status[2] + fac.rtu_count = 0 if type(rtu_statuses) == "table" then + -- connected RTU count + fac.rtu_count = rtu_statuses.count + fac.ps.publish("rtu_count", fac.rtu_count) + -- power statistics if type(rtu_statuses.power) == "table" then fac.induction_ps_tbl[1].publish("avg_charge", rtu_statuses.power[1]) @@ -406,13 +411,15 @@ function iocontrol.update_facility_status(status) fac.ps.publish("rad_computed_status", util.trinary(rtu_faulted, 2, 3)) fac.ps.publish("radiation", fac.radiation) else - fac.radiation = { radiation = 0, unit = "nSv" } + fac.radiation = types.new_zero_radiation_reading() fac.ps.publish("rad_computed_status", 1) end else log.debug(log_header .. "radiation monitor list not a table") return false end + else + log.debug(log_header .. "rtu statuses not a table") end end @@ -626,11 +633,9 @@ function iocontrol.update_unit_statuses(statuses) local rtu_faulted = rad_mon[1] ---@type boolean unit.radiation = rad_mon[2] ---@type number - unit.unit_ps.publish("rad_computed_status", util.trinary(rtu_faulted, 2, 3)) unit.unit_ps.publish("radiation", unit.radiation) else - unit.radiation = { radiation = 0, unit = "nSv" } - unit.unit_ps.publish("rad_computed_status", 1) + unit.radiation = types.new_zero_radiation_reading() end else log.debug(log_header .. "radiation monitor list not a table") diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 872b3ce..c146477 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.9.12" +local COORDINATOR_VERSION = "beta-v0.9.13" local print = util.print local println = util.println diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index 98d04dd..17b3e66 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -90,10 +90,14 @@ local function new_view(root, x, y) facility.ps.subscribe("as_radiation", fac_rad_h.update) facility.ps.subscribe("as_gen_fault", gen_fault.update) - TextBox{parent=main,y=23,text="Radiation",height=1,width=21,fg_bg=style.label} + TextBox{parent=main,y=23,text="Radiation",height=1,width=13,fg_bg=style.label} local radiation = RadIndicator{parent=main,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} facility.ps.subscribe("radiation", radiation.update) + TextBox{parent=main,x=15,y=23,text="Linked RTUs",height=1,width=11,fg_bg=style.label} + local rtu_count = DataIndicator{parent=main,x=15,y=24,label="",format="%11d",value=0,lu_colors=lu_cpair,width=11,fg_bg=bw_fg_bg} + facility.ps.subscribe("rtu_count", rtu_count.update) + --------------------- -- process control -- --------------------- diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index ad66532..f5716b5 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -157,28 +157,29 @@ local function init(parent, id) local annunciator = Div{parent=main,width=23,height=18,x=22,y=3} - -- connectivity/basic state + -- connectivity local plc_online = IndicatorLight{parent=annunciator,label="PLC Online",colors=cpair(colors.green,colors.red)} local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=cpair(colors.white,colors.gray)} - local r_active = IndicatorLight{parent=annunciator,label="Active",colors=cpair(colors.green,colors.gray)} - local r_auto = IndicatorLight{parent=annunciator,label="Automatic Control",colors=cpair(colors.blue,colors.gray)} - - annunciator.line_break() - - local rad_mon = TriIndicatorLight{parent=annunciator,label="Radiation Monitor",c1=colors.gray,c2=colors.yellow,c3=colors.green} + local rad_mon = TriIndicatorLight{parent=annunciator,label="Radiation Monitor",c1=colors.gray,c2=colors.yellow,c3=colors.green} u_ps.subscribe("PLCOnline", plc_online.update) u_ps.subscribe("PLCHeartbeat", plc_hbeat.update) - u_ps.subscribe("status", r_active.update) - u_ps.subscribe("AutoControl", r_auto.update) - u_ps.subscribe("rad_computed_status", rad_mon.update) + u_ps.subscribe("RadiationMonitor", rad_mon.update) annunciator.line_break() - -- non-RPS reactor annunciator panel + -- operating state + local r_active = IndicatorLight{parent=annunciator,label="Active",colors=cpair(colors.green,colors.gray)} + local r_auto = IndicatorLight{parent=annunciator,label="Automatic Control",colors=cpair(colors.white,colors.gray)} + + u_ps.subscribe("status", r_active.update) + u_ps.subscribe("AutoControl", r_auto.update) + + -- main unit transient/warning annunciator panel local r_scram = IndicatorLight{parent=annunciator,label="Reactor SCRAM",colors=cpair(colors.red,colors.gray)} local r_mscrm = IndicatorLight{parent=annunciator,label="Manual Reactor SCRAM",colors=cpair(colors.red,colors.gray)} local r_ascrm = IndicatorLight{parent=annunciator,label="Auto Reactor SCRAM",colors=cpair(colors.red,colors.gray)} + local rad_wrn = IndicatorLight{parent=annunciator,label="Radiation Warning",colors=cpair(colors.yellow,colors.gray)} local r_rtrip = IndicatorLight{parent=annunciator,label="RCP Trip",colors=cpair(colors.red,colors.gray)} local r_cflow = IndicatorLight{parent=annunciator,label="RCS Flow Low",colors=cpair(colors.yellow,colors.gray)} local r_clow = IndicatorLight{parent=annunciator,label="Coolant Level Low",colors=cpair(colors.yellow,colors.gray)} @@ -191,6 +192,7 @@ local function init(parent, id) u_ps.subscribe("ReactorSCRAM", r_scram.update) u_ps.subscribe("ManualReactorSCRAM", r_mscrm.update) u_ps.subscribe("AutoReactorSCRAM", r_ascrm.update) + u_ps.subscribe("RadiationWarning", rad_wrn.update) u_ps.subscribe("RCPTrip", r_rtrip.update) u_ps.subscribe("RCSFlowLow", r_cflow.update) u_ps.subscribe("CoolantLevelLow", r_clow.update) @@ -233,14 +235,18 @@ local function init(parent, id) TextBox{parent=main,text="REACTOR COOLANT SYSTEM",fg_bg=cpair(colors.black,colors.blue),alignment=TEXT_ALIGN.CENTER,width=33,height=1,x=46,y=22} local rcs = Rectangle{parent=main,border=border(1,colors.blue,true),thin=true,width=33,height=24,x=46,y=23} local rcs_annunc = Div{parent=rcs,width=27,height=22,x=2,y=1} - local rcs_tags = Div{parent=rcs,width=2,height=22,x=29,y=1} + local rcs_tags = Div{parent=rcs,width=2,height=13,x=29,y=9} - local c_cfm = IndicatorLight{parent=rcs_annunc,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} - local c_brm = IndicatorLight{parent=rcs_annunc,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)} - local c_sfm = IndicatorLight{parent=rcs_annunc,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} - local c_mwrf = IndicatorLight{parent=rcs_annunc,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)} - local c_tbnt = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local c_flt = IndicatorLight{parent=rcs_annunc,label="RCS Hardware Fault",colors=cpair(colors.yellow,colors.gray)} + local c_emg = TriIndicatorLight{parent=rcs_annunc,label="Emergency Coolant",c1=colors.gray,c2=colors.white,c3=colors.yellow} + local c_cfm = IndicatorLight{parent=rcs_annunc,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_brm = IndicatorLight{parent=rcs_annunc,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_sfm = IndicatorLight{parent=rcs_annunc,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} + local c_mwrf = IndicatorLight{parent=rcs_annunc,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)} + local c_tbnt = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + u_ps.subscribe("RCSFault", c_flt.update) + u_ps.subscribe("EmergencyCoolant", c_emg.update) u_ps.subscribe("CoolantFeedMismatch", c_cfm.update) u_ps.subscribe("BoilRateMismatch", c_brm.update) u_ps.subscribe("SteamFeedMismatch", c_sfm.update) @@ -252,7 +258,7 @@ local function init(parent, id) -- boiler annunciator panel(s) if unit.num_boilers > 0 then - TextBox{parent=rcs_tags,x=1,y=7,text="B1",width=2,height=1,fg_bg=bw_fg_bg} + TextBox{parent=rcs_tags,x=1,text="B1",width=2,height=1,fg_bg=bw_fg_bg} local b1_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=cpair(colors.red,colors.gray)} b_ps[1].subscribe("WasterLevelLow", b1_wll.update) @@ -273,7 +279,7 @@ local function init(parent, id) -- turbine annunciator panels if unit.num_boilers == 0 then - TextBox{parent=rcs_tags,y=7,text="T1",width=2,height=1,fg_bg=bw_fg_bg} + TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg} else rcs_tags.line_break() rcs_annunc.line_break() @@ -292,9 +298,6 @@ local function init(parent, id) t_ps[1].subscribe("TurbineTrip", t1_trp.update) if unit.num_turbines > 1 then - rcs_tags.line_break() - rcs_annunc.line_break() - TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg} local t2_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} t_ps[2].subscribe("SteamDumpOpen", function (val) t2_sdo.update(val + 1) end) @@ -309,9 +312,6 @@ local function init(parent, id) end if unit.num_turbines > 2 then - rcs_tags.line_break() - rcs_annunc.line_break() - TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg} local t3_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} t_ps[3].subscribe("SteamDumpOpen", function (val) t3_sdo.update(val + 1) end) diff --git a/graphics/elements/indicators/rad.lua b/graphics/elements/indicators/rad.lua index a27d0a4..86ec856 100644 --- a/graphics/elements/indicators/rad.lua +++ b/graphics/elements/indicators/rad.lua @@ -1,5 +1,6 @@ -- Radiation Indicator Graphics Element +local types = require("scada-common.types") local util = require("scada-common.util") local element = require("graphics.element") @@ -80,7 +81,7 @@ local function rad(args) function e.set_value(val) e.on_update(val) end -- initial value draw - e.on_update({ radiation = 0, unit = "nSv" }) + e.on_update(types.new_zero_radiation_reading()) return e.get() end diff --git a/scada-common/util.lua b/scada-common/util.lua index 56cacd7..afe3c59 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -14,7 +14,7 @@ util.TICK_TIME_MS = 50 --#region -- trinary operator ----@param cond boolean condition +---@param cond boolean|nil condition ---@param a any return if true ---@param b any return if false ---@return any value diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index 0818210..b621e67 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -60,6 +60,7 @@ function facility.new(num_reactors, cooling_conf) envd = {}, status_text = { "START UP", "initializing..." }, all_sys_ok = false, + rtu_conn_count = 0, -- process control units_ready = false, mode = PROCESS.INACTIVE, @@ -224,6 +225,12 @@ function facility.new(num_reactors, cooling_conf) -- UPDATE -- + -- supervisor sessions reporting the list of active RTU sessions + ---@param rtu_sessions table session list of all connected RTUs + function public.report_rtus(rtu_sessions) + self.rtu_conn_count = #rtu_sessions + end + -- update (iterate) the facility management function public.update() -- unlink RTU unit sessions if they are closed @@ -801,6 +808,9 @@ function facility.new(num_reactors, cooling_conf) function public.get_rtu_statuses() local status = {} + -- total count of all connected RTUs in the facility + status.count = self.rtu_conn_count + -- power averages from induction matricies status.power = { self.avg_charge.compute(), diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index e7ce735..91457c7 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -144,7 +144,7 @@ end ---@param sessions table local function _close(sessions) for i = 1, #sessions do - local session = sessions[i] ---@type plc_session_struct + local session = sessions[i] ---@type plc_session_struct|rtu_session_struct if session.open then _shutdown(session) end @@ -156,7 +156,7 @@ end ---@param timer_event number local function _check_watchdogs(sessions, timer_event) for i = 1, #sessions do - local session = sessions[i] ---@type plc_session_struct + local session = sessions[i] ---@type plc_session_struct|rtu_session_struct if session.open then local triggered = session.instance.check_wd(timer_event) if triggered then @@ -400,6 +400,9 @@ function svsessions.iterate_all() -- iterate coordinator sessions _iterate(self.coord_sessions) + -- report RTU sessions to facility + self.facility.report_rtus(self.rtu_sessions) + -- iterate facility self.facility.update() diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index bb95fcc..98cdd5b 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -163,10 +163,12 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- reactor PLCOnline = false, PLCHeartbeat = false, -- alternate true/false to blink, each time there is a keep_alive + RadiationMonitor = 1, AutoControl = false, ReactorSCRAM = false, ManualReactorSCRAM = false, AutoReactorSCRAM = false, + RadiationWarning = false, RCPTrip = false, RCSFlowLow = false, CoolantLevelLow = false, @@ -175,16 +177,19 @@ function unit.new(for_reactor, num_boilers, num_turbines) FuelInputRateLow = false, WasteLineOcclusion = false, HighStartupRate = false, - -- boiler + -- cooling + RCSFault = false, + EmergencyCoolant = 1, + CoolantFeedMismatch = false, + BoilRateMismatch = false, + SteamFeedMismatch = false, + MaxWaterReturnFeed = false, + -- boilers BoilerOnline = {}, HeatingRateLow = {}, WaterLevelLow = {}, - BoilRateMismatch = false, - CoolantFeedMismatch = false, - -- turbine + -- turbines TurbineOnline = {}, - SteamFeedMismatch = false, - MaxWaterReturnFeed = false, SteamDumpOpen = {}, TurbineOverSpeed = {}, TurbineTrip = {} diff --git a/supervisor/session/unitlogic.lua b/supervisor/session/unitlogic.lua index 3da3652..647def8 100644 --- a/supervisor/session/unitlogic.lua +++ b/supervisor/session/unitlogic.lua @@ -1,4 +1,5 @@ local log = require("scada-common.log") +local rsio = require("scada-common.rsio") local types = require("scada-common.types") local util = require("scada-common.util") @@ -7,6 +8,8 @@ local ALARM_STATE = types.ALARM_STATE local TRI_FAIL = types.TRI_FAIL local DUMPING_MODE = types.DUMPING_MODE +local IO = rsio.IO + local aistate_string = { "INACTIVE", "TRIPPING", @@ -19,6 +22,7 @@ local aistate_string = { -- 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 ---@class unit_logic_extension @@ -33,6 +37,8 @@ function logic.update_annunciator(self) local num_boilers = self.num_boilers local num_turbines = self.num_turbines + self.db.annunciator.RCSFault = false + -- variables for boiler, or reactor if no boilers used local total_boil_rate = 0.0 @@ -114,6 +120,29 @@ function logic.update_annunciator(self) self.plc_cache.ok = false end + --------------- + -- MISC RTUs -- + --------------- + + self.db.annunciator.RadiationMonitor = 1 + self.db.annunciator.RadiationWarning = false + 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 + break + end + + self.db.annunciator.EmergencyCoolant = 1 + for i = 1, #self.redstone do + local db = self.redstone[i].get_db() ---@type redstone_session_db + local io = db.io[IO.U_EMER_COOL] ---@type rs_db_dig_io|nil + if io ~= nil then + self.db.annunciator.EmergencyCoolant = util.trinary(io.read(), 3, 2) + break + end + end + ------------- -- BOILERS -- ------------- @@ -133,6 +162,8 @@ function logic.update_annunciator(self) local session = self.boilers[i] ---@type unit_session local boiler = session.get_db() ---@type boilerv_session_db + self.db.annunciator.RCSFault = self.db.annunciator.RCSFault or (not boiler.formed) or session.is_faulted() + -- update ready state -- - must be formed -- - must have received build, state, and tanks at least once @@ -225,6 +256,8 @@ function logic.update_annunciator(self) local session = self.turbines[i] ---@type unit_session local turbine = session.get_db() ---@type turbinev_session_db + self.db.annunciator.RCSFault = self.db.annunciator.RCSFault or (not turbine.formed) or session.is_faulted() + -- update ready state -- - must be formed -- - must have received build, state, and tanks at least once diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 3e92d15..40f4659 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.7" +local SUPERVISOR_VERSION = "beta-v0.11.8" local print = util.print local println = util.println