mirror of
https://github.com/MikaylaFischler/cc-mek-scada.git
synced 2024-08-30 18:22:34 +00:00
#10 #133 alarm system logic and display, change to comms to support alarm actions, get_x get_y to graphics elements, bugfixes to coord establish and rtu establish, flashing trilight and alarm light indicators
This commit is contained in:
parent
f68c38ccee
commit
d4ae18eee7
@ -421,6 +421,8 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa
|
||||
unit.set_burn_ack(ack)
|
||||
elseif cmd == CRDN_COMMANDS.SET_WASTE then
|
||||
unit.set_waste_ack(ack)
|
||||
elseif cmd == CRDN_COMMANDS.ACK_ALL_ALARMS then
|
||||
unit.ack_alarms_ack(ack)
|
||||
else
|
||||
log.debug(util.c("received command ack with unknown command ", cmd))
|
||||
end
|
||||
@ -474,6 +476,10 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa
|
||||
else
|
||||
log.debug("supervisor connection denied")
|
||||
end
|
||||
elseif packet.length == 1 and packet.data[1] == ESTABLISH_ACK.DENY then
|
||||
log.debug("supervisor connection denied")
|
||||
elseif packet.length == 1 and packet.data[1] == ESTABLISH_ACK.COLLISION then
|
||||
log.debug("supervisor connection denied due to collision")
|
||||
else
|
||||
log.debug("SCADA_MGMT establish packet length mismatch")
|
||||
end
|
||||
|
@ -1,6 +1,7 @@
|
||||
local comms = require("scada-common.comms")
|
||||
local log = require("scada-common.log")
|
||||
local psil = require("scada-common.psil")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local CRDN_COMMANDS = comms.CRDN_COMMANDS
|
||||
@ -22,9 +23,19 @@ function iocontrol.init(conf, comms)
|
||||
|
||||
io.units = {}
|
||||
for i = 1, conf.num_units do
|
||||
local function ack(alarm)
|
||||
comms.send_command(CRDN_COMMANDS.ACK_ALARM, i, alarm)
|
||||
log.debug(util.c("UNIT[", i, "]: ACK ALARM ", alarm))
|
||||
end
|
||||
|
||||
local function reset(alarm)
|
||||
comms.send_command(CRDN_COMMANDS.RESET_ALARM, i, alarm)
|
||||
log.debug(util.c("UNIT[", i, "]: RESET ALARM ", alarm))
|
||||
end
|
||||
|
||||
---@class ioctl_entry
|
||||
local entry = {
|
||||
unit_id = i, ---@type integer
|
||||
unit_id = i, ---@type integer
|
||||
initialized = false,
|
||||
|
||||
num_boilers = 0,
|
||||
@ -37,17 +48,36 @@ function iocontrol.init(conf, comms)
|
||||
start = function () end,
|
||||
scram = function () end,
|
||||
reset_rps = function () end,
|
||||
set_burn = function (rate) end,
|
||||
set_waste = function (mode) end,
|
||||
ack_alarms = function () end,
|
||||
set_burn = function (rate) end, ---@param rate number
|
||||
set_waste = function (mode) end, ---@param mode integer
|
||||
|
||||
start_ack = function (success) end,
|
||||
scram_ack = function (success) end,
|
||||
reset_rps_ack = function (success) end,
|
||||
set_burn_ack = function (success) end,
|
||||
set_waste_ack = function (success) end,
|
||||
start_ack = function (success) end, ---@param success boolean
|
||||
scram_ack = function (success) end, ---@param success boolean
|
||||
reset_rps_ack = function (success) end, ---@param success boolean
|
||||
ack_alarms_ack = function (success) end,---@param success boolean
|
||||
set_burn_ack = function (success) end, ---@param success boolean
|
||||
set_waste_ack = function (success) end, ---@param success boolean
|
||||
|
||||
alarm_callbacks = {
|
||||
c_breach = { ack = function () ack(1) end, reset = function () reset(1) end },
|
||||
radiation = { ack = function () ack(2) end, reset = function () reset(2) end },
|
||||
dmg_crit = { ack = function () ack(3) end, reset = function () reset(3) end },
|
||||
r_lost = { ack = function () ack(4) end, reset = function () reset(4) end },
|
||||
damage = { ack = function () ack(5) end, reset = function () reset(5) end },
|
||||
over_temp = { ack = function () ack(6) end, reset = function () reset(6) end },
|
||||
high_temp = { ack = function () ack(7) end, reset = function () reset(7) end },
|
||||
waste_leak = { ack = function () ack(8) end, reset = function () reset(8) end },
|
||||
waste_high = { ack = function () ack(9) end, reset = function () reset(9) end },
|
||||
rps_trans = { ack = function () ack(10) end, reset = function () reset(10) end },
|
||||
rcs_trans = { ack = function () ack(11) end, reset = function () reset(11) end },
|
||||
t_trip = { ack = function () ack(12) end, reset = function () reset(12) end }
|
||||
},
|
||||
|
||||
alarms = {}, ---@type alarms
|
||||
|
||||
reactor_ps = psil.create(),
|
||||
reactor_data = {}, ---@type reactor_db
|
||||
reactor_data = {}, ---@type reactor_db
|
||||
|
||||
boiler_ps_tbl = {},
|
||||
boiler_data_tbl = {},
|
||||
@ -73,6 +103,11 @@ function iocontrol.init(conf, comms)
|
||||
log.debug(util.c("UNIT[", i, "]: RESET_RPS"))
|
||||
end
|
||||
|
||||
function entry.ack_alarms()
|
||||
comms.send_command(CRDN_COMMANDS.ACK_ALL_ALARMS, i)
|
||||
log.debug(util.c("UNIT[", i, "]: ACK_ALL_ALARMS"))
|
||||
end
|
||||
|
||||
function entry.set_burn(rate)
|
||||
comms.send_command(CRDN_COMMANDS.SET_BURN, i, rate)
|
||||
log.debug(util.c("UNIT[", i, "]: SET_BURN = ", rate))
|
||||
@ -167,7 +202,10 @@ end
|
||||
---@param statuses table
|
||||
---@return boolean valid
|
||||
function iocontrol.update_statuses(statuses)
|
||||
if #statuses ~= #io.units then
|
||||
if type(statuses) ~= "table" then
|
||||
log.error("unit statuses not a table")
|
||||
return false
|
||||
elseif #statuses ~= #io.units then
|
||||
log.error("number of provided unit statuses does not match expected number of units")
|
||||
return false
|
||||
else
|
||||
@ -175,6 +213,11 @@ function iocontrol.update_statuses(statuses)
|
||||
local unit = io.units[i] ---@type ioctl_entry
|
||||
local status = statuses[i]
|
||||
|
||||
if type(status) ~= "table" or #status ~= 4 then
|
||||
log.error("invalid status entry in unit statuses (not a table or invalid length)")
|
||||
return false
|
||||
end
|
||||
|
||||
-- reactor PLC status
|
||||
|
||||
local reactor_status = status[1]
|
||||
@ -253,7 +296,7 @@ function iocontrol.update_statuses(statuses)
|
||||
end
|
||||
|
||||
unit.reactor_ps.publish("TurbineTrip", any)
|
||||
elseif key == "BoilerOnline" or key == "HeatingRateLow" then
|
||||
elseif key == "BoilerOnline" or key == "HeatingRateLow" or key == "WaterLevelLow" then
|
||||
-- split up array for all boilers
|
||||
for id = 1, #val do
|
||||
unit.boiler_ps_tbl[id].publish(key, val[id])
|
||||
@ -272,9 +315,25 @@ function iocontrol.update_statuses(statuses)
|
||||
end
|
||||
end
|
||||
|
||||
-- alarms
|
||||
|
||||
local alarm_states = status[3]
|
||||
|
||||
for id = 1, #alarm_states do
|
||||
local state = alarm_states[id]
|
||||
|
||||
if state == types.ALARM_STATE.TRIPPED or state == types.ALARM_STATE.ACKED then
|
||||
unit.reactor_ps.publish("ALM" .. id, 2)
|
||||
elseif state == types.ALARM_STATE.RING_BACK then
|
||||
unit.reactor_ps.publish("ALM" .. id, 3)
|
||||
else
|
||||
unit.reactor_ps.publish("ALM" .. id, 1)
|
||||
end
|
||||
end
|
||||
|
||||
-- RTU statuses
|
||||
|
||||
local rtu_statuses = status[3]
|
||||
local rtu_statuses = status[4]
|
||||
|
||||
if type(rtu_statuses) == "table" then
|
||||
if type(rtu_statuses.boilers) == "table" then
|
||||
|
@ -17,7 +17,7 @@ local config = require("coordinator.config")
|
||||
local coordinator = require("coordinator.coordinator")
|
||||
local renderer = require("coordinator.renderer")
|
||||
|
||||
local COORDINATOR_VERSION = "alpha-v0.6.17"
|
||||
local COORDINATOR_VERSION = "alpha-v0.7.0"
|
||||
|
||||
local print = util.print
|
||||
local println = util.println
|
||||
|
@ -2,8 +2,6 @@
|
||||
-- Reactor Unit SCADA Coordinator GUI
|
||||
--
|
||||
|
||||
local tcallbackdsp = require("scada-common.tcallbackdsp")
|
||||
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
|
||||
local style = require("coordinator.ui.style")
|
||||
@ -12,8 +10,8 @@ local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.div")
|
||||
local TextBox = require("graphics.elements.textbox")
|
||||
local ColorMap = require("graphics.elements.colormap")
|
||||
|
||||
local AlarmLight = require("graphics.elements.indicators.alight")
|
||||
local CoreMap = require("graphics.elements.indicators.coremap")
|
||||
local DataIndicator = require("graphics.elements.indicators.data")
|
||||
local IndicatorLight = require("graphics.elements.indicators.light")
|
||||
@ -30,6 +28,29 @@ local cpair = core.graphics.cpair
|
||||
|
||||
local period = core.flasher.PERIOD
|
||||
|
||||
local waste_opts = {
|
||||
{
|
||||
text = "Auto",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.white, colors.gray)
|
||||
},
|
||||
{
|
||||
text = "Pu",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.black, colors.green)
|
||||
},
|
||||
{
|
||||
text = "Po",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.black, colors.cyan)
|
||||
},
|
||||
{
|
||||
text = "AM",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.black, colors.purple)
|
||||
}
|
||||
}
|
||||
|
||||
-- create a unit view
|
||||
---@param parent graphics_element parent
|
||||
---@param id integer
|
||||
@ -43,10 +64,12 @@ local function init(parent, id)
|
||||
|
||||
TextBox{parent=main,text="Reactor Unit #" .. id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header}
|
||||
|
||||
local scram_fg_bg = cpair(colors.white, colors.gray)
|
||||
local lu_cpair = cpair(colors.gray, colors.gray)
|
||||
local hzd_fg_bg = cpair(colors.white, colors.gray)
|
||||
local lu_cpair = cpair(colors.gray, colors.gray)
|
||||
|
||||
-----------------------------
|
||||
-- main stats and core map --
|
||||
-----------------------------
|
||||
|
||||
local core_map = CoreMap{parent=main,x=2,y=3,reactor_l=18,reactor_w=18}
|
||||
r_ps.subscribe("temp", core_map.update)
|
||||
@ -84,18 +107,20 @@ local function init(parent, id)
|
||||
DataIndicator{parent=main,x=21,label="",format="%6.2f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=12,fg_bg=stat_fg_bg}
|
||||
main.line_break()
|
||||
|
||||
-----------------
|
||||
-- annunciator --
|
||||
-----------------
|
||||
|
||||
local annunciator = Div{parent=main,x=34,y=3}
|
||||
-- annunciator colors (generally) per IAEA-TECDOC-812 recommendations
|
||||
|
||||
-- annunciator colors per IAEA-TECDOC-812 recommendations
|
||||
local annunciator = Div{parent=main,x=35,y=3}
|
||||
|
||||
-- connectivity/basic state
|
||||
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)}
|
||||
---@todo auto control as info sent here
|
||||
local r_auto = IndicatorLight{parent=annunciator,label="Auto Control",colors=cpair(colors.blue,colors.gray)}
|
||||
local r_auto = IndicatorLight{parent=annunciator,label="Auto. Control",colors=cpair(colors.blue,colors.gray)}
|
||||
|
||||
r_ps.subscribe("PLCOnline", plc_online.update)
|
||||
r_ps.subscribe("PLCHeartbeat", plc_hbeat.update)
|
||||
@ -103,23 +128,25 @@ local function init(parent, id)
|
||||
|
||||
annunciator.line_break()
|
||||
|
||||
-- annunciator fields
|
||||
-- non-RPS reactor 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 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)}
|
||||
local r_temp = IndicatorLight{parent=annunciator,label="Reactor Temp. High",colors=cpair(colors.red,colors.gray)}
|
||||
local r_rhdt = IndicatorLight{parent=annunciator,label="Reactor High Delta T",colors=cpair(colors.yellow,colors.gray)}
|
||||
local r_firl = IndicatorLight{parent=annunciator,label="Fuel Input Rate Low",colors=cpair(colors.yellow,colors.gray)}
|
||||
local r_wloc = IndicatorLight{parent=annunciator,label="Waste Line Occlusion",colors=cpair(colors.yellow,colors.gray)}
|
||||
local r_hsrt = IndicatorLight{parent=annunciator,label="High Startup Rate",colors=cpair(colors.yellow,colors.gray)}
|
||||
local r_hsrt = IndicatorLight{parent=annunciator,label="Startup Rate High",colors=cpair(colors.yellow,colors.gray)}
|
||||
|
||||
r_ps.subscribe("ReactorSCRAM", r_scram.update)
|
||||
r_ps.subscribe("ManualReactorSCRAM", r_mscrm.update)
|
||||
r_ps.subscribe("AutoReactorSCRAM", r_ascrm.update)
|
||||
r_ps.subscribe("RCPTrip", r_rtrip.update)
|
||||
r_ps.subscribe("RCSFlowLow", r_cflow.update)
|
||||
r_ps.subscribe("CoolantLevelLow", r_clow.update)
|
||||
r_ps.subscribe("ReactorTempHigh", r_temp.update)
|
||||
r_ps.subscribe("ReactorHighDeltaT", r_rhdt.update)
|
||||
r_ps.subscribe("FuelInputRateLow", r_firl.update)
|
||||
@ -128,14 +155,24 @@ local function init(parent, id)
|
||||
|
||||
annunciator.line_break()
|
||||
|
||||
-- RPS
|
||||
-- RPS annunciator panel
|
||||
TextBox{parent=main,x=34,y=20,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)}
|
||||
TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)}
|
||||
TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)}
|
||||
TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)}
|
||||
TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)}
|
||||
TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)}
|
||||
TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)}
|
||||
TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)}
|
||||
TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)}
|
||||
TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.brown)}
|
||||
local rps_trp = IndicatorLight{parent=annunciator,label="RPS Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
local rps_dmg = IndicatorLight{parent=annunciator,label="Damage Critical",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
local rps_exh = IndicatorLight{parent=annunciator,label="Excess Heated Coolant",colors=cpair(colors.yellow,colors.gray)}
|
||||
local rps_exw = IndicatorLight{parent=annunciator,label="Excess Waste",colors=cpair(colors.yellow,colors.gray)}
|
||||
local rps_tmp = IndicatorLight{parent=annunciator,label="High Core Temp",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
local rps_tmp = IndicatorLight{parent=annunciator,label="Core Temp. High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
local rps_nof = IndicatorLight{parent=annunciator,label="No Fuel",colors=cpair(colors.yellow,colors.gray)}
|
||||
local rps_noc = IndicatorLight{parent=annunciator,label="No Coolant",colors=cpair(colors.yellow,colors.gray)}
|
||||
local rps_noc = IndicatorLight{parent=annunciator,label="Coolant Level Low Low",colors=cpair(colors.yellow,colors.gray)}
|
||||
local rps_flt = IndicatorLight{parent=annunciator,label="PPM Fault",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS}
|
||||
local rps_tmo = IndicatorLight{parent=annunciator,label="Timeout",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS}
|
||||
local rps_sfl = IndicatorLight{parent=annunciator,label="System Failure",colors=cpair(colors.orange,colors.gray),flash=true,period=period.BLINK_500_MS}
|
||||
@ -153,7 +190,12 @@ local function init(parent, id)
|
||||
|
||||
annunciator.line_break()
|
||||
|
||||
-- cooling
|
||||
-- cooling annunciator panel
|
||||
TextBox{parent=main,x=34,y=31,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)}
|
||||
TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)}
|
||||
TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)}
|
||||
TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)}
|
||||
TextBox{parent=main,x=34,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.cyan)}
|
||||
local c_cfm = IndicatorLight{parent=annunciator,label="Coolant Feed Mismatch",colors=cpair(colors.yellow,colors.gray)}
|
||||
local c_brm = IndicatorLight{parent=annunciator,label="Boil Rate Mismatch",colors=cpair(colors.yellow,colors.gray)}
|
||||
local c_sfm = IndicatorLight{parent=annunciator,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)}
|
||||
@ -168,16 +210,31 @@ local function init(parent, id)
|
||||
|
||||
annunciator.line_break()
|
||||
|
||||
-- machine-specific indicators
|
||||
-- boiler annunciator panel(s)
|
||||
|
||||
local tag_y = 1
|
||||
|
||||
if unit.num_boilers > 0 then
|
||||
TextBox{parent=main,x=32,y=36,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}
|
||||
tag_y = TextBox{parent=main,x=32,y=37,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y()
|
||||
local b1_wll = IndicatorLight{parent=annunciator,label="Water Level Low",colors=cpair(colors.red,colors.gray)}
|
||||
b_ps[1].subscribe("WasterLevelLow", b1_wll.update)
|
||||
TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)}
|
||||
|
||||
tag_y = TextBox{parent=main,x=32,text="B1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y()
|
||||
local b1_hr = IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)}
|
||||
b_ps[1].subscribe("HeatingRateLow", b1_hr.update)
|
||||
TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)}
|
||||
end
|
||||
if unit.num_boilers > 1 then
|
||||
TextBox{parent=main,x=32,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}
|
||||
tag_y = TextBox{parent=main,x=32,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y()
|
||||
local b2_wll = IndicatorLight{parent=annunciator,label="Water Level Low",colors=cpair(colors.red,colors.gray)}
|
||||
b_ps[2].subscribe("WasterLevelLow", b2_wll.update)
|
||||
TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)}
|
||||
|
||||
tag_y = TextBox{parent=main,x=32,text="B2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y()
|
||||
local b2_hr = IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)}
|
||||
b_ps[2].subscribe("HeatingRateLow", b2_hr.update)
|
||||
TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.blue)}
|
||||
end
|
||||
|
||||
if unit.num_boilers > 0 then
|
||||
@ -185,7 +242,14 @@ local function init(parent, id)
|
||||
annunciator.line_break()
|
||||
end
|
||||
|
||||
TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}
|
||||
-- turbine annunciator panels
|
||||
|
||||
if unit.num_boilers == 0 then
|
||||
TextBox{parent=main,x=32,y=37,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}
|
||||
else
|
||||
TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}
|
||||
end
|
||||
|
||||
local t1_sdo = TriIndicatorLight{parent=annunciator,label="Steam Dump Open",c1=colors.gray,c2=colors.yellow,c3=colors.red}
|
||||
t_ps[1].subscribe("SteamDumpOpen", function (val) t1_sdo.update(val + 1) end)
|
||||
|
||||
@ -193,12 +257,10 @@ local function init(parent, id)
|
||||
local t1_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)}
|
||||
t_ps[1].subscribe("TurbineOverSpeed", t1_tos.update)
|
||||
|
||||
TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}
|
||||
tag_y = TextBox{parent=main,x=32,text="T1",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y()
|
||||
local t1_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
t_ps[1].subscribe("TurbineTrip", t1_trp.update)
|
||||
|
||||
main.line_break()
|
||||
annunciator.line_break()
|
||||
TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.cyan)}
|
||||
|
||||
if unit.num_turbines > 1 then
|
||||
TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}
|
||||
@ -209,12 +271,10 @@ local function init(parent, id)
|
||||
local t2_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)}
|
||||
t_ps[2].subscribe("TurbineOverSpeed", t2_tos.update)
|
||||
|
||||
TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}
|
||||
tag_y = TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y()
|
||||
local t2_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
t_ps[2].subscribe("TurbineTrip", t2_trp.update)
|
||||
|
||||
main.line_break()
|
||||
annunciator.line_break()
|
||||
TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.cyan)}
|
||||
end
|
||||
|
||||
if unit.num_turbines > 2 then
|
||||
@ -226,18 +286,20 @@ local function init(parent, id)
|
||||
local t3_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)}
|
||||
t_ps[3].subscribe("TurbineOverSpeed", t3_tos.update)
|
||||
|
||||
TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}
|
||||
tag_y = TextBox{parent=main,x=32,text="T3",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}.get_y()
|
||||
local t3_trp = IndicatorLight{parent=annunciator,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
t_ps[3].subscribe("TurbineTrip", t3_trp.update)
|
||||
|
||||
annunciator.line_break()
|
||||
TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.cyan)}
|
||||
end
|
||||
|
||||
annunciator.line_break()
|
||||
|
||||
---@todo radiation monitor
|
||||
IndicatorLight{parent=annunciator,label="Radiation Monitor",colors=cpair(colors.green,colors.gray)}
|
||||
IndicatorLight{parent=annunciator,label="Radiation Alarm",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS}
|
||||
|
||||
----------------------
|
||||
-- reactor controls --
|
||||
----------------------
|
||||
|
||||
local burn_control = Div{parent=main,x=2,y=22,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)}
|
||||
local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)}
|
||||
@ -251,13 +313,15 @@ local function init(parent, id)
|
||||
|
||||
local dis_colors = cpair(colors.white, colors.lightGray)
|
||||
|
||||
local start = HazardButton{parent=main,x=2,y=26,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=scram_fg_bg}
|
||||
local scram = HazardButton{parent=main,x=12,y=26,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=scram_fg_bg}
|
||||
local reset = HazardButton{parent=main,x=22,y=26,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,fg_bg=scram_fg_bg}
|
||||
local start = HazardButton{parent=main,x=22,y=22,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=hzd_fg_bg}
|
||||
local ack_a = HazardButton{parent=main,x=12,y=26,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=unit.ack_alarms,fg_bg=hzd_fg_bg}
|
||||
local scram = HazardButton{parent=main,x=2,y=26,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=hzd_fg_bg}
|
||||
local reset = HazardButton{parent=main,x=22,y=26,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,fg_bg=hzd_fg_bg}
|
||||
|
||||
unit.start_ack = start.on_response
|
||||
unit.scram_ack = scram.on_response
|
||||
unit.reset_rps_ack = reset.on_response
|
||||
unit.ack_alarms_ack = ack_a.on_response
|
||||
|
||||
local function start_button_en_check()
|
||||
if (unit.reactor_data ~= nil) and (unit.reactor_data.mek_status ~= nil) then
|
||||
@ -270,35 +334,89 @@ local function init(parent, id)
|
||||
r_ps.subscribe("rps_tripped", start_button_en_check)
|
||||
r_ps.subscribe("rps_tripped", function (active) if active then reset.enable() else reset.disable() end end)
|
||||
|
||||
local opts = {
|
||||
{
|
||||
text = "Auto",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.white, colors.gray)
|
||||
},
|
||||
{
|
||||
text = "Pu",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.black, colors.lime)
|
||||
},
|
||||
{
|
||||
text = "Po",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.black, colors.cyan)
|
||||
},
|
||||
{
|
||||
text = "AM",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.black, colors.purple)
|
||||
}
|
||||
}
|
||||
TextBox{parent=main,x=2,y=30,text="Idle",width=29,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)}
|
||||
|
||||
---@todo waste selection
|
||||
local waste_sel = Div{parent=main,x=2,y=48,width=29,height=2,fg_bg=cpair(colors.black, colors.white)}
|
||||
local waste_sel = Div{parent=main,x=2,y=50,width=29,height=2,fg_bg=cpair(colors.black, colors.white)}
|
||||
|
||||
MultiButton{parent=waste_sel,x=1,y=1,options=opts,callback=unit.set_waste,min_width=6,fg_bg=cpair(colors.black, colors.white)}
|
||||
MultiButton{parent=waste_sel,x=1,y=1,options=waste_opts,callback=unit.set_waste,min_width=6,fg_bg=cpair(colors.black, colors.white)}
|
||||
TextBox{parent=waste_sel,text="Waste Processing",alignment=TEXT_ALIGN.CENTER,x=1,y=1,height=1}
|
||||
|
||||
----------------------
|
||||
-- alarm management --
|
||||
----------------------
|
||||
|
||||
local alarm_panel = Div{parent=main,x=2,y=32,width=29,height=16,fg_bg=cpair(colors.black,colors.white)}
|
||||
|
||||
local a_brc = AlarmLight{parent=alarm_panel,x=6,y=2,label="Containment Breach",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS}
|
||||
local a_rad = AlarmLight{parent=alarm_panel,x=6,label="Containment Radiation",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS}
|
||||
local a_dmg = AlarmLight{parent=alarm_panel,x=6,label="Critical Damage",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS}
|
||||
alarm_panel.line_break()
|
||||
local a_rcl = AlarmLight{parent=alarm_panel,x=6,label="Reactor Lost",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS}
|
||||
local a_rcd = AlarmLight{parent=alarm_panel,x=6,label="Reactor Damage",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS}
|
||||
local a_rot = AlarmLight{parent=alarm_panel,x=6,label="Reactor Over Temp",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS}
|
||||
local a_rht = AlarmLight{parent=alarm_panel,x=6,label="Reactor High Temp",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS}
|
||||
local a_rwl = AlarmLight{parent=alarm_panel,x=6,label="Reactor Waste Leak",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS}
|
||||
local a_rwh = AlarmLight{parent=alarm_panel,x=6,label="Reactor Waste High",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS}
|
||||
alarm_panel.line_break()
|
||||
local a_rps = AlarmLight{parent=alarm_panel,x=6,label="RPS Transient",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS}
|
||||
local a_clt = AlarmLight{parent=alarm_panel,x=6,label="RCS Transient",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS}
|
||||
local a_tbt = AlarmLight{parent=alarm_panel,x=6,label="Turbine Trip",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS}
|
||||
|
||||
r_ps.subscribe("ALM1", a_brc.update)
|
||||
r_ps.subscribe("ALM2", a_rad.update)
|
||||
r_ps.subscribe("ALM4", a_dmg.update)
|
||||
|
||||
r_ps.subscribe("ALM3", a_rcl.update)
|
||||
r_ps.subscribe("ALM5", a_rcd.update)
|
||||
r_ps.subscribe("ALM6", a_rot.update)
|
||||
r_ps.subscribe("ALM7", a_rht.update)
|
||||
r_ps.subscribe("ALM8", a_rwl.update)
|
||||
r_ps.subscribe("ALM9", a_rwh.update)
|
||||
|
||||
r_ps.subscribe("ALM10", a_rps.update)
|
||||
r_ps.subscribe("ALM11", a_clt.update)
|
||||
r_ps.subscribe("ALM12", a_tbt.update)
|
||||
|
||||
-- ack's and resets
|
||||
|
||||
local c = unit.alarm_callbacks
|
||||
local ack_fg_bg = cpair(colors.black, colors.orange)
|
||||
local rst_fg_bg = cpair(colors.black, colors.lime)
|
||||
local active_fg_bg = cpair(colors.white, colors.gray)
|
||||
|
||||
PushButton{parent=alarm_panel,x=2,y=2,text="\x13",min_width=1,callback=c.c_breach.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=4,y=2,text="R",min_width=1,callback=c.c_breach.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=2,y=3,text="\x13",min_width=1,callback=c.radiation.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=4,y=3,text="R",min_width=1,callback=c.radiation.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=2,y=4,text="\x13",min_width=1,callback=c.dmg_crit.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=4,y=4,text="R",min_width=1,callback=c.dmg_crit.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg}
|
||||
|
||||
PushButton{parent=alarm_panel,x=2,y=6,text="\x13",min_width=1,callback=c.r_lost.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=4,y=6,text="R",min_width=1,callback=c.r_lost.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=2,y=7,text="\x13",min_width=1,callback=c.damage.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=4,y=7,text="R",min_width=1,callback=c.damage.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=2,y=8,text="\x13",min_width=1,callback=c.over_temp.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=4,y=8,text="R",min_width=1,callback=c.over_temp.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=2,y=9,text="\x13",min_width=1,callback=c.high_temp.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=4,y=9,text="R",min_width=1,callback=c.high_temp.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=2,y=10,text="\x13",min_width=1,callback=c.waste_leak.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=4,y=10,text="R",min_width=1,callback=c.waste_leak.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=2,y=11,text="\x13",min_width=1,callback=c.waste_high.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=4,y=11,text="R",min_width=1,callback=c.waste_high.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg}
|
||||
|
||||
PushButton{parent=alarm_panel,x=2,y=13,text="\x13",min_width=1,callback=c.rps_trans.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=4,y=13,text="R",min_width=1,callback=c.rps_trans.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=2,y=14,text="\x13",min_width=1,callback=c.rcs_trans.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=4,y=14,text="R",min_width=1,callback=c.rcs_trans.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=2,y=15,text="\x13",min_width=1,callback=c.t_trip.ack,fg_bg=ack_fg_bg,active_fg_bg=active_fg_bg}
|
||||
PushButton{parent=alarm_panel,x=4,y=15,text="R",min_width=1,callback=c.t_trip.reset,fg_bg=rst_fg_bg,active_fg_bg=active_fg_bg}
|
||||
|
||||
-- color tags
|
||||
|
||||
TextBox{parent=alarm_panel,x=5,y=13,text="\x95",width=1,height=1,fg_bg=cpair(colors.white, colors.brown)}
|
||||
TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(colors.white, colors.blue)}
|
||||
TextBox{parent=alarm_panel,x=5,text="\x95",width=1,height=1,fg_bg=cpair(colors.white, colors.cyan)}
|
||||
|
||||
return main
|
||||
end
|
||||
|
||||
|
@ -26,6 +26,7 @@ local element = {}
|
||||
---|push_button_args
|
||||
---|spinbox_args
|
||||
---|switch_button_args
|
||||
---|alarm_indicator_light
|
||||
---|core_map_args
|
||||
---|data_indicator_args
|
||||
---|hbar_args
|
||||
@ -302,6 +303,18 @@ function element.new(args)
|
||||
---@return cpair fg_bg
|
||||
function public.get_fg_bg() return protected.fg_bg end
|
||||
|
||||
-- get element x
|
||||
---@return integer x
|
||||
function public.get_x()
|
||||
return protected.frame.x
|
||||
end
|
||||
|
||||
-- get element y
|
||||
---@return integer y
|
||||
function public.get_y()
|
||||
return protected.frame.y
|
||||
end
|
||||
|
||||
-- get element width
|
||||
---@return integer width
|
||||
function public.width()
|
||||
|
113
graphics/elements/indicators/alight.lua
Normal file
113
graphics/elements/indicators/alight.lua
Normal file
@ -0,0 +1,113 @@
|
||||
-- Tri-State Alarm Indicator Light Graphics Element
|
||||
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local element = require("graphics.element")
|
||||
local flasher = require("graphics.flasher")
|
||||
|
||||
---@class alarm_indicator_light
|
||||
---@field label string indicator label
|
||||
---@field c1 color color for off state
|
||||
---@field c2 color color for alarm state
|
||||
---@field c3 color color for ring-back state
|
||||
---@field min_label_width? integer label length if omitted
|
||||
---@field flash? boolean whether to flash on alarm state rather than stay on
|
||||
---@field period? PERIOD flash period
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
|
||||
-- new alarm indicator light
|
||||
---@param args alarm_indicator_light
|
||||
---@return graphics_element element, element_id id
|
||||
local function alarm_indicator_light(args)
|
||||
assert(type(args.label) == "string", "graphics.elements.indicators.alight: label is a required field")
|
||||
assert(type(args.c1) == "number", "graphics.elements.indicators.alight: c1 is a required field")
|
||||
assert(type(args.c2) == "number", "graphics.elements.indicators.alight: c2 is a required field")
|
||||
assert(type(args.c3) == "number", "graphics.elements.indicators.alight: c3 is a required field")
|
||||
|
||||
if args.flash then
|
||||
assert(util.is_int(args.period), "graphics.elements.indicators.alight: period is a required field if flash is enabled")
|
||||
end
|
||||
|
||||
-- single line
|
||||
args.height = 1
|
||||
|
||||
-- determine width
|
||||
args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2
|
||||
|
||||
-- flasher state
|
||||
local flash_on = true
|
||||
|
||||
-- blit translations
|
||||
local c1 = colors.toBlit(args.c1)
|
||||
local c2 = colors.toBlit(args.c2)
|
||||
local c3 = colors.toBlit(args.c3)
|
||||
|
||||
-- create new graphics element base object
|
||||
local e = element.new(args)
|
||||
|
||||
-- called by flasher when enabled
|
||||
local function flash_callback()
|
||||
e.window.setCursorPos(1, 1)
|
||||
|
||||
if flash_on then
|
||||
if e.value == 2 then
|
||||
e.window.blit(" \x95", "0" .. c2, c2 .. e.fg_bg.blit_bkg)
|
||||
end
|
||||
else
|
||||
if e.value == 3 then
|
||||
e.window.blit(" \x95", "0" .. c3, c3 .. e.fg_bg.blit_bkg)
|
||||
else
|
||||
e.window.blit(" \x95", "0" .. c1, c1 .. e.fg_bg.blit_bkg)
|
||||
end
|
||||
end
|
||||
|
||||
flash_on = not flash_on
|
||||
end
|
||||
|
||||
-- on state change
|
||||
---@param new_state integer indicator state
|
||||
function e.on_update(new_state)
|
||||
local was_off = e.value ~= 2
|
||||
|
||||
e.value = new_state
|
||||
e.window.setCursorPos(1, 1)
|
||||
|
||||
if args.flash then
|
||||
if was_off and (new_state == 2) then
|
||||
flash_on = true
|
||||
flasher.start(flash_callback, args.period)
|
||||
elseif new_state ~= 2 then
|
||||
flash_on = false
|
||||
flasher.stop(flash_callback)
|
||||
|
||||
if new_state == 3 then
|
||||
e.window.blit(" \x95", "0" .. c3, c3 .. e.fg_bg.blit_bkg)
|
||||
else
|
||||
e.window.blit(" \x95", "0" .. c1, c1 .. e.fg_bg.blit_bkg)
|
||||
end
|
||||
end
|
||||
elseif new_state == 2 then
|
||||
e.window.blit(" \x95", "0" .. c2, c2 .. e.fg_bg.blit_bkg)
|
||||
elseif new_state == 3 then
|
||||
e.window.blit(" \x95", "0" .. c3, c3 .. e.fg_bg.blit_bkg)
|
||||
else
|
||||
e.window.blit(" \x95", "0" .. c1, c1 .. e.fg_bg.blit_bkg)
|
||||
end
|
||||
end
|
||||
|
||||
-- set indicator state
|
||||
---@param val integer indicator state
|
||||
function e.set_value(val) e.on_update(val) end
|
||||
|
||||
-- write label and initial indicator light
|
||||
e.on_update(1)
|
||||
e.window.write(args.label)
|
||||
|
||||
return e.get()
|
||||
end
|
||||
|
||||
return alarm_indicator_light
|
@ -1,6 +1,9 @@
|
||||
-- Tri-State Indicator Light Graphics Element
|
||||
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local element = require("graphics.element")
|
||||
local flasher = require("graphics.flasher")
|
||||
|
||||
---@class tristate_indicator_light_args
|
||||
---@field label string indicator label
|
||||
@ -8,13 +11,15 @@ local element = require("graphics.element")
|
||||
---@field c2 color color for state 2
|
||||
---@field c3 color color for state 3
|
||||
---@field min_label_width? integer label length if omitted
|
||||
---@field flash? boolean whether to flash on state 2 or 3 rather than stay on
|
||||
---@field period? PERIOD flash period
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
|
||||
-- new indicator light
|
||||
-- new tri-state indicator light
|
||||
---@param args tristate_indicator_light_args
|
||||
---@return graphics_element element, element_id id
|
||||
local function tristate_indicator_light(args)
|
||||
@ -23,12 +28,19 @@ local function tristate_indicator_light(args)
|
||||
assert(type(args.c2) == "number", "graphics.elements.indicators.trilight: c2 is a required field")
|
||||
assert(type(args.c3) == "number", "graphics.elements.indicators.trilight: c3 is a required field")
|
||||
|
||||
if args.flash then
|
||||
assert(util.is_int(args.period), "graphics.elements.indicators.trilight: period is a required field if flash is enabled")
|
||||
end
|
||||
|
||||
-- single line
|
||||
args.height = 1
|
||||
|
||||
-- determine width
|
||||
args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2
|
||||
|
||||
-- flasher state
|
||||
local flash_on = true
|
||||
|
||||
-- blit translations
|
||||
local c1 = colors.toBlit(args.c1)
|
||||
local c2 = colors.toBlit(args.c2)
|
||||
@ -37,12 +49,45 @@ local function tristate_indicator_light(args)
|
||||
-- create new graphics element base object
|
||||
local e = element.new(args)
|
||||
|
||||
-- init value for initial check in on_update
|
||||
e.value = 1
|
||||
|
||||
-- called by flasher when enabled
|
||||
local function flash_callback()
|
||||
e.window.setCursorPos(1, 1)
|
||||
|
||||
if flash_on then
|
||||
if e.value == 2 then
|
||||
e.window.blit(" \x95", "0" .. c2, c2 .. e.fg_bg.blit_bkg)
|
||||
elseif e.value == 3 then
|
||||
e.window.blit(" \x95", "0" .. c3, c3 .. e.fg_bg.blit_bkg)
|
||||
end
|
||||
else
|
||||
e.window.blit(" \x95", "0" .. c1, c1 .. e.fg_bg.blit_bkg)
|
||||
end
|
||||
|
||||
flash_on = not flash_on
|
||||
end
|
||||
|
||||
-- on state change
|
||||
---@param new_state integer indicator state
|
||||
function e.on_update(new_state)
|
||||
local was_off = e.value <= 1
|
||||
|
||||
e.value = new_state
|
||||
e.window.setCursorPos(1, 1)
|
||||
if new_state == 2 then
|
||||
|
||||
if args.flash then
|
||||
if was_off and (new_state > 1) then
|
||||
flash_on = true
|
||||
flasher.start(flash_callback, args.period)
|
||||
elseif new_state <= 1 then
|
||||
flash_on = false
|
||||
flasher.stop(flash_callback)
|
||||
|
||||
e.window.blit(" \x95", "0" .. c1, c1 .. e.fg_bg.blit_bkg)
|
||||
end
|
||||
elseif new_state == 2 then
|
||||
e.window.blit(" \x95", "0" .. c2, c2 .. e.fg_bg.blit_bkg)
|
||||
elseif new_state == 3 then
|
||||
e.window.blit(" \x95", "0" .. c3, c3 .. e.fg_bg.blit_bkg)
|
||||
@ -56,7 +101,7 @@ local function tristate_indicator_light(args)
|
||||
function e.set_value(val) e.on_update(val) end
|
||||
|
||||
-- write label and initial indicator light
|
||||
e.on_update(0)
|
||||
e.on_update(1)
|
||||
e.window.write(args.label)
|
||||
|
||||
return e.get()
|
||||
|
@ -14,7 +14,7 @@ local config = require("reactor-plc.config")
|
||||
local plc = require("reactor-plc.plc")
|
||||
local threads = require("reactor-plc.threads")
|
||||
|
||||
local R_PLC_VERSION = "beta-v0.9.7"
|
||||
local R_PLC_VERSION = "beta-v0.9.8"
|
||||
|
||||
local print = util.print
|
||||
local println = util.println
|
||||
|
@ -412,7 +412,7 @@ function rtu.comms(version, modem, local_port, server_port, conn_watchdog)
|
||||
else
|
||||
-- establish denied
|
||||
public.unlink(rtu_state)
|
||||
println_ts("supervisor connection")
|
||||
println_ts("supervisor connection denied")
|
||||
log.warning("supervisor connection denied by remote host")
|
||||
end
|
||||
else
|
||||
|
@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu")
|
||||
local sps_rtu = require("rtu.dev.sps_rtu")
|
||||
local turbinev_rtu = require("rtu.dev.turbinev_rtu")
|
||||
|
||||
local RTU_VERSION = "beta-v0.9.5"
|
||||
local RTU_VERSION = "beta-v0.9.6"
|
||||
|
||||
local rtu_t = types.rtu_t
|
||||
|
||||
|
@ -1,73 +0,0 @@
|
||||
local util = require("scada-common.util")
|
||||
|
||||
---@class alarm
|
||||
local alarm = {}
|
||||
|
||||
---@alias SEVERITY integer
|
||||
SEVERITY = {
|
||||
INFO = 0, -- basic info message
|
||||
WARNING = 1, -- warning about some abnormal state
|
||||
ALERT = 2, -- important device state changes
|
||||
FACILITY = 3, -- facility-wide alert
|
||||
SAFETY = 4, -- safety alerts
|
||||
EMERGENCY = 5 -- critical safety alarm
|
||||
}
|
||||
|
||||
alarm.SEVERITY = SEVERITY
|
||||
|
||||
-- severity integer to string
|
||||
---@param severity SEVERITY
|
||||
function alarm.severity_to_string(severity)
|
||||
if severity == SEVERITY.INFO then
|
||||
return "INFO"
|
||||
elseif severity == SEVERITY.WARNING then
|
||||
return "WARNING"
|
||||
elseif severity == SEVERITY.ALERT then
|
||||
return "ALERT"
|
||||
elseif severity == SEVERITY.FACILITY then
|
||||
return "FACILITY"
|
||||
elseif severity == SEVERITY.SAFETY then
|
||||
return "SAFETY"
|
||||
elseif severity == SEVERITY.EMERGENCY then
|
||||
return "EMERGENCY"
|
||||
else
|
||||
return "UNKNOWN"
|
||||
end
|
||||
end
|
||||
|
||||
-- create a new scada alarm entry
|
||||
---@param severity SEVERITY
|
||||
---@param device string
|
||||
---@param message string
|
||||
function alarm.scada_alarm(severity, device, message)
|
||||
local self = {
|
||||
time = util.time(),
|
||||
ts_string = os.date("[%H:%M:%S]"),
|
||||
severity = severity,
|
||||
device = device,
|
||||
message = message
|
||||
}
|
||||
|
||||
---@class scada_alarm
|
||||
local public = {}
|
||||
|
||||
-- format the alarm as a string
|
||||
---@return string message
|
||||
function public.format()
|
||||
return self.ts_string .. " [" .. alarm.severity_to_string(self.severity) .. "] (" .. self.device .. ") >> " .. self.message
|
||||
end
|
||||
|
||||
-- get alarm properties
|
||||
function public.properties()
|
||||
return {
|
||||
time = self.time,
|
||||
severity = self.severity,
|
||||
device = self.device,
|
||||
message = self.message
|
||||
}
|
||||
end
|
||||
|
||||
return public
|
||||
end
|
||||
|
||||
return alarm
|
@ -12,7 +12,7 @@ local rtu_t = types.rtu_t
|
||||
|
||||
local insert = table.insert
|
||||
|
||||
comms.version = "1.0.0"
|
||||
comms.version = "1.0.1"
|
||||
|
||||
---@alias PROTOCOLS integer
|
||||
local PROTOCOLS = {
|
||||
@ -74,7 +74,10 @@ local CRDN_COMMANDS = {
|
||||
START = 1, -- start the reactor
|
||||
RESET_RPS = 2, -- reset the RPS
|
||||
SET_BURN = 3, -- set the burn rate
|
||||
SET_WASTE = 4 -- set the waste processing mode
|
||||
SET_WASTE = 4, -- set the waste processing mode
|
||||
ACK_ALL_ALARMS = 5, -- ack all active alarms
|
||||
ACK_ALARM = 6, -- ack a particular alarm
|
||||
RESET_ALARM = 7 -- reset a particular alarm
|
||||
}
|
||||
|
||||
---@alias CAPI_TYPES integer
|
||||
|
@ -35,6 +35,76 @@ types.TRI_FAIL = {
|
||||
FULL = 2
|
||||
}
|
||||
|
||||
---@alias ALARM integer
|
||||
types.ALARM = {
|
||||
ContainmentBreach = 1,
|
||||
ContainmentRadiation = 2,
|
||||
ReactorLost = 3,
|
||||
CriticalDamage = 4,
|
||||
ReactorDamage = 5,
|
||||
ReactorOverTemp = 6,
|
||||
ReactorHighTemp = 7,
|
||||
ReactorWasteLeak = 8,
|
||||
ReactorHighWaste = 9,
|
||||
RPSTransient = 10,
|
||||
RCSTransient = 11,
|
||||
TurbineTrip = 12
|
||||
}
|
||||
|
||||
types.alarm_string = {
|
||||
"ContainmentBreach",
|
||||
"ContainmentRadiation",
|
||||
"ReactorLost",
|
||||
"CriticalDamage",
|
||||
"ReactorDamage",
|
||||
"ReactorOverTemp",
|
||||
"ReactorHighTemp",
|
||||
"ReactorWasteLeak",
|
||||
"ReactorHighWaste",
|
||||
"RPSTransient",
|
||||
"RCSTransient",
|
||||
"TurbineTrip"
|
||||
}
|
||||
|
||||
---@alias ALARM_PRIORITY integer
|
||||
types.ALARM_PRIORITY = {
|
||||
CRITICAL = 0,
|
||||
EMERGENCY = 1,
|
||||
URGENT = 2,
|
||||
TIMELY = 3
|
||||
}
|
||||
|
||||
types.alarm_prio_string = {
|
||||
"CRITICAL",
|
||||
"EMERGENCY",
|
||||
"URGENT",
|
||||
"TIMELY"
|
||||
}
|
||||
|
||||
-- map alarms to alarm priority
|
||||
types.ALARM_PRIO_MAP = {
|
||||
types.ALARM_PRIORITY.CRITICAL,
|
||||
types.ALARM_PRIORITY.CRITICAL,
|
||||
types.ALARM_PRIORITY.URGENT,
|
||||
types.ALARM_PRIORITY.CRITICAL,
|
||||
types.ALARM_PRIORITY.EMERGENCY,
|
||||
types.ALARM_PRIORITY.URGENT,
|
||||
types.ALARM_PRIORITY.TIMELY,
|
||||
types.ALARM_PRIORITY.EMERGENCY,
|
||||
types.ALARM_PRIORITY.TIMELY,
|
||||
types.ALARM_PRIORITY.URGENT,
|
||||
types.ALARM_PRIORITY.TIMELY,
|
||||
types.ALARM_PRIORITY.URGENT
|
||||
}
|
||||
|
||||
---@alias ALARM_STATE integer
|
||||
types.ALARM_STATE = {
|
||||
INACTIVE = 0,
|
||||
TRIPPED = 1,
|
||||
ACKED = 2,
|
||||
RING_BACK = 3
|
||||
}
|
||||
|
||||
-- STRING TYPES --
|
||||
|
||||
---@alias os_event
|
||||
|
@ -129,7 +129,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units)
|
||||
|
||||
for i = 1, #self.units do
|
||||
local unit = self.units[i] ---@type reactor_unit
|
||||
status[unit.get_id()] = { unit.get_reactor_status(), unit.get_annunciator(), unit.get_rtu_statuses() }
|
||||
status[unit.get_id()] = { unit.get_reactor_status(), unit.get_annunciator(), unit.get_alarms(), unit.get_rtu_statuses() }
|
||||
end
|
||||
|
||||
_send(SCADA_CRDN_TYPES.UNIT_STATUSES, status)
|
||||
@ -191,6 +191,8 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units)
|
||||
|
||||
-- continue if valid unit id
|
||||
if util.is_int(uid) and uid > 0 and uid <= #self.units then
|
||||
local unit = self.units[uid] ---@type reactor_unit
|
||||
|
||||
if cmd == CRDN_COMMANDS.START then
|
||||
self.out_q.push_data(SV_Q_DATA.START, data)
|
||||
elseif cmd == CRDN_COMMANDS.SCRAM then
|
||||
@ -209,6 +211,21 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units)
|
||||
else
|
||||
log.debug(log_header .. "CRDN command unit set waste missing option")
|
||||
end
|
||||
elseif cmd == CRDN_COMMANDS.ACK_ALL_ALARMS then
|
||||
unit.ack_all()
|
||||
_send(SCADA_CRDN_TYPES.COMMAND_UNIT, { cmd, uid, true })
|
||||
elseif cmd == CRDN_COMMANDS.ACK_ALARM then
|
||||
if pkt.length == 3 then
|
||||
unit.ack_alarm(pkt.data[3])
|
||||
else
|
||||
log.debug(log_header .. "CRDN command unit ack alarm missing id")
|
||||
end
|
||||
elseif cmd == CRDN_COMMANDS.RESET_ALARM then
|
||||
if pkt.length == 3 then
|
||||
unit.reset_alarm(pkt.data[3])
|
||||
else
|
||||
log.debug(log_header .. "CRDN command unit reset alarm missing id")
|
||||
end
|
||||
else
|
||||
log.debug(log_header .. "CRDN command unknown")
|
||||
end
|
||||
|
@ -4,9 +4,15 @@ local log = require("scada-common.log")
|
||||
|
||||
local unit = {}
|
||||
|
||||
local ALARM = types.ALARM
|
||||
local PRIO = types.ALARM_PRIORITY
|
||||
local ALARM_STATE = types.ALARM_STATE
|
||||
|
||||
local TRI_FAIL = types.TRI_FAIL
|
||||
local DUMPING_MODE = types.DUMPING_MODE
|
||||
|
||||
local FLOW_STABILITY_DELAY_MS = 15000
|
||||
|
||||
local DT_KEYS = {
|
||||
ReactorTemp = "RTP",
|
||||
ReactorFuel = "RFL",
|
||||
@ -21,6 +27,32 @@ local DT_KEYS = {
|
||||
TurbinePower = "TPR"
|
||||
}
|
||||
|
||||
---@alias ALARM_INT_STATE integer
|
||||
local AISTATE = {
|
||||
INACTIVE = 0,
|
||||
TRIPPING = 1,
|
||||
TRIPPED = 2,
|
||||
ACKED = 3,
|
||||
RING_BACK = 4,
|
||||
RING_BACK_TRIPPING = 5
|
||||
}
|
||||
|
||||
local aistate_string = {
|
||||
"INACTIVE",
|
||||
"TRIPPING",
|
||||
"TRIPPED",
|
||||
"ACKED",
|
||||
"RING_BACK",
|
||||
"RING_BACK_TRIPPING"
|
||||
}
|
||||
|
||||
---@class alarm_def
|
||||
---@field state ALARM_INT_STATE internal alarm state
|
||||
---@field trip_time integer time (ms) when first tripped
|
||||
---@field hold_time integer time (s) to hold before tripping
|
||||
---@field id ALARM alarm ID
|
||||
---@field tier integer alarm urgency tier (0 = highest)
|
||||
|
||||
-- create a new reactor unit
|
||||
---@param for_reactor integer reactor unit number
|
||||
---@param num_boilers integer number of boilers expected
|
||||
@ -30,12 +62,49 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
r_id = for_reactor,
|
||||
plc_s = nil, ---@class plc_session_struct
|
||||
plc_i = nil, ---@class plc_session
|
||||
counts = { boilers = num_boilers, turbines = num_turbines },
|
||||
turbines = {},
|
||||
boilers = {},
|
||||
redstone = {},
|
||||
deltas = {},
|
||||
last_heartbeat = 0,
|
||||
-- logic for alarms
|
||||
had_reactor = false,
|
||||
start_time = 0,
|
||||
plc_cache = {
|
||||
ok = false,
|
||||
rps_trip = false,
|
||||
rps_status = {}, ---@type rps_status
|
||||
damage = 0,
|
||||
temp = 0,
|
||||
waste = 0
|
||||
},
|
||||
---@class alarm_monitors
|
||||
alarms = {
|
||||
-- reactor lost under the condition of meltdown imminent
|
||||
ContainmentBreach = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ContainmentBreach, tier = PRIO.CRITICAL },
|
||||
-- radiation monitor alarm for this unit
|
||||
ContainmentRadiation = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ContainmentRadiation, tier = PRIO.CRITICAL },
|
||||
-- reactor offline after being online
|
||||
ReactorLost = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorLost, tier = PRIO.URGENT },
|
||||
-- damage >100%
|
||||
CriticalDamage = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.CriticalDamage, tier = PRIO.CRITICAL },
|
||||
-- reactor damage increasing
|
||||
ReactorDamage = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorDamage, tier = PRIO.EMERGENCY },
|
||||
-- reactor >1200K
|
||||
ReactorOverTemp = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorOverTemp, tier = PRIO.URGENT },
|
||||
-- reactor >1100K
|
||||
ReactorHighTemp = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 2, id = ALARM.ReactorHighTemp, tier = PRIO.TIMELY },
|
||||
-- waste = 100%
|
||||
ReactorWasteLeak = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorWasteLeak, tier = PRIO.EMERGENCY },
|
||||
-- waste >85%
|
||||
ReactorHighWaste = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 2, id = ALARM.ReactorHighWaste, tier = PRIO.TIMELY },
|
||||
-- RPS trip occured
|
||||
RPSTransient = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.RPSTransient, tier = PRIO.URGENT },
|
||||
-- BoilRateMismatch, CoolantFeedMismatch, SteamFeedMismatch, MaxWaterReturnFeed
|
||||
RCSTransient = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 5, id = ALARM.RCSTransient, tier = PRIO.TIMELY },
|
||||
-- "It's just a routine turbin' trip!" -Bill Gibson
|
||||
TurbineTrip = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.TurbineTrip, tier = PRIO.URGENT }
|
||||
},
|
||||
db = {
|
||||
---@class annunciator
|
||||
annunciator = {
|
||||
@ -47,6 +116,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
AutoReactorSCRAM = false,
|
||||
RCPTrip = false,
|
||||
RCSFlowLow = false,
|
||||
CoolantLevelLow = false,
|
||||
ReactorTempHigh = false,
|
||||
ReactorHighDeltaT = false,
|
||||
FuelInputRateLow = false,
|
||||
@ -55,6 +125,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
-- boiler
|
||||
BoilerOnline = {},
|
||||
HeatingRateLow = {},
|
||||
WaterLevelLow = {},
|
||||
BoilRateMismatch = false,
|
||||
CoolantFeedMismatch = false,
|
||||
-- turbine
|
||||
@ -64,6 +135,21 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
SteamDumpOpen = {},
|
||||
TurbineOverSpeed = {},
|
||||
TurbineTrip = {}
|
||||
},
|
||||
---@class alarms
|
||||
alarm_states = {
|
||||
ALARM_STATE.INACTIVE,
|
||||
ALARM_STATE.INACTIVE,
|
||||
ALARM_STATE.INACTIVE,
|
||||
ALARM_STATE.INACTIVE,
|
||||
ALARM_STATE.INACTIVE,
|
||||
ALARM_STATE.INACTIVE,
|
||||
ALARM_STATE.INACTIVE,
|
||||
ALARM_STATE.INACTIVE,
|
||||
ALARM_STATE.INACTIVE,
|
||||
ALARM_STATE.INACTIVE,
|
||||
ALARM_STATE.INACTIVE,
|
||||
ALARM_STATE.INACTIVE
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -125,6 +211,92 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
end
|
||||
end
|
||||
|
||||
-- update an alarm state given conditions
|
||||
---@param tripped boolean if the alarm condition is still active
|
||||
---@param alarm alarm_def alarm table
|
||||
local function _update_alarm_state(tripped, alarm)
|
||||
local int_state = alarm.state
|
||||
local ext_state = self.db.alarm_states[alarm.id]
|
||||
|
||||
-- alarm inactive
|
||||
if int_state == AISTATE.INACTIVE then
|
||||
if tripped then
|
||||
alarm.trip_time = util.time_ms()
|
||||
if alarm.hold_time > 0 then
|
||||
alarm.state = AISTATE.TRIPPING
|
||||
self.db.alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||
else
|
||||
alarm.state = AISTATE.TRIPPED
|
||||
self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED
|
||||
log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.alarm_string[alarm.id], "): TRIPPED [PRIORITY ",
|
||||
types.alarm_prio_string[alarm.tier + 1],"]"))
|
||||
end
|
||||
else
|
||||
alarm.trip_time = util.time_ms()
|
||||
self.db.alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||
end
|
||||
-- alarm condition met, but not yet for required hold time
|
||||
elseif (int_state == AISTATE.TRIPPING) or (int_state == AISTATE.RING_BACK_TRIPPING) then
|
||||
if tripped then
|
||||
local elapsed = util.time_ms() - alarm.trip_time
|
||||
if elapsed > (alarm.hold_time * 1000) then
|
||||
alarm.state = AISTATE.TRIPPED
|
||||
self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED
|
||||
log.info(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.alarm_string[alarm.id], "): TRIPPED [PRIORITY ",
|
||||
types.alarm_prio_string[alarm.tier + 1],"]"))
|
||||
end
|
||||
elseif int_state == AISTATE.RING_BACK_TRIPPING then
|
||||
alarm.trip_time = 0
|
||||
alarm.state = AISTATE.RING_BACK
|
||||
self.db.alarm_states[alarm.id] = ALARM_STATE.RING_BACK
|
||||
else
|
||||
alarm.trip_time = 0
|
||||
alarm.state = AISTATE.INACTIVE
|
||||
self.db.alarm_states[alarm.id] = ALARM_STATE.INACTIVE
|
||||
end
|
||||
-- alarm tripped and alarming
|
||||
elseif int_state == AISTATE.TRIPPED then
|
||||
if tripped then
|
||||
if ext_state == ALARM_STATE.ACKED then
|
||||
-- was acked by coordinator
|
||||
alarm.state = AISTATE.ACKED
|
||||
end
|
||||
else
|
||||
alarm.state = AISTATE.RING_BACK
|
||||
self.db.alarm_states[alarm.id] = ALARM_STATE.RING_BACK
|
||||
end
|
||||
-- alarm acknowledged but still tripped
|
||||
elseif int_state == AISTATE.ACKED then
|
||||
if not tripped then
|
||||
alarm.state = AISTATE.RING_BACK
|
||||
self.db.alarm_states[alarm.id] = ALARM_STATE.RING_BACK
|
||||
end
|
||||
-- alarm no longer tripped, operator must reset to clear
|
||||
elseif int_state == AISTATE.RING_BACK then
|
||||
if tripped then
|
||||
alarm.trip_time = util.time_ms()
|
||||
if alarm.hold_time > 0 then
|
||||
alarm.state = AISTATE.RING_BACK_TRIPPING
|
||||
else
|
||||
alarm.state = AISTATE.TRIPPED
|
||||
self.db.alarm_states[alarm.id] = ALARM_STATE.TRIPPED
|
||||
end
|
||||
elseif ext_state == ALARM_STATE.INACTIVE then
|
||||
-- was reset by coordinator
|
||||
alarm.state = AISTATE.INACTIVE
|
||||
alarm.trip_time = 0
|
||||
end
|
||||
else
|
||||
log.error(util.c("invalid alarm state for unit ", self.r_id, " alarm ", alarm.id), true)
|
||||
end
|
||||
|
||||
-- check for state change
|
||||
if alarm.state ~= int_state then
|
||||
local change_str = util.c(aistate_string[int_state + 1], " -> ", aistate_string[alarm.state + 1])
|
||||
log.debug(util.c("UNIT ", self.r_id, " ALARM ", alarm.id, " (", types.alarm_string[alarm.id], "): ", change_str))
|
||||
end
|
||||
end
|
||||
|
||||
-- update all delta computations
|
||||
local function _dt__compute_all()
|
||||
if self.plc_s ~= nil then
|
||||
@ -181,6 +353,21 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
if self.plc_s ~= nil then
|
||||
local plc_db = self.plc_i.get_db()
|
||||
|
||||
-- record reactor start time (some alarms are delayed during reactor heatup)
|
||||
if self.start_time == 0 and plc_db.mek_status.status then
|
||||
self.start_time = util.time_ms()
|
||||
elseif not plc_db.mek_status.status then
|
||||
self.start_time = 0
|
||||
end
|
||||
|
||||
-- record reactor stats
|
||||
self.plc_cache.ok = not (plc_db.rps_status.fault or plc_db.rps_status.sys_fail or plc_db.rps_status.force_dis)
|
||||
self.plc_cache.rps_trip = plc_db.rps_tripped
|
||||
self.plc_cache.rps_status = plc_db.rps_status
|
||||
self.plc_cache.damage = plc_db.mek_status.damage
|
||||
self.plc_cache.temp = plc_db.mek_status.temp
|
||||
self.plc_cache.waste = plc_db.mek_status.waste_fill
|
||||
|
||||
-- heartbeat blink about every second
|
||||
if self.last_heartbeat + 1000 < plc_db.last_status_update then
|
||||
self.db.annunciator.PLCHeartbeat = not self.db.annunciator.PLCHeartbeat
|
||||
@ -201,9 +388,11 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
self.db.annunciator.HighStartupRate = not plc_db.mek_status.status and plc_db.mek_status.burn_rate > 40
|
||||
|
||||
-- if no boilers, use reactor heating rate to check for boil rate mismatch
|
||||
if self.counts.boilers == 0 then
|
||||
if num_boilers == 0 then
|
||||
total_boil_rate = plc_db.mek_status.heating_rate
|
||||
end
|
||||
else
|
||||
self.plc_cache.ok = false
|
||||
end
|
||||
|
||||
-------------
|
||||
@ -211,13 +400,13 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
-------------
|
||||
|
||||
-- clear boiler online flags
|
||||
for i = 1, self.counts.boilers do self.db.annunciator.BoilerOnline[i] = false end
|
||||
for i = 1, num_boilers do self.db.annunciator.BoilerOnline[i] = false end
|
||||
|
||||
-- aggregated statistics
|
||||
local boiler_steam_dt_sum = 0.0
|
||||
local boiler_water_dt_sum = 0.0
|
||||
|
||||
if self.counts.boilers > 0 then
|
||||
if num_boilers > 0 then
|
||||
-- go through boilers for stats and online
|
||||
for i = 1, #self.boilers do
|
||||
local session = self.boilers[i] ---@type unit_session
|
||||
@ -259,7 +448,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
-- check coolant feed mismatch if using boilers, otherwise calculate with reactor
|
||||
local cfmismatch = false
|
||||
|
||||
if self.counts.boilers > 0 then
|
||||
if num_boilers > 0 then
|
||||
for i = 1, #self.boilers do
|
||||
local boiler = self.boilers[i] ---@type unit_session
|
||||
local idx = boiler.get_device_idx()
|
||||
@ -290,7 +479,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
--------------
|
||||
|
||||
-- clear turbine online flags
|
||||
for i = 1, self.counts.turbines do self.db.annunciator.TurbineOnline[i] = false end
|
||||
for i = 1, num_turbines do self.db.annunciator.TurbineOnline[i] = false end
|
||||
|
||||
-- aggregated statistics
|
||||
local total_flow_rate = 0
|
||||
@ -359,6 +548,75 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
end
|
||||
end
|
||||
|
||||
-- evaluate alarm conditions
|
||||
local function _update_alarms()
|
||||
local annunc = self.db.annunciator
|
||||
local plc_cache = self.plc_cache
|
||||
|
||||
-- Containment Breach
|
||||
-- lost plc with critical damage (rip plc, you will be missed)
|
||||
_update_alarm_state((not plc_cache.ok) and (plc_cache.damage > 99), self.alarms.ContainmentBreach)
|
||||
|
||||
-- Containment Radiation
|
||||
---@todo containment radiation alarm
|
||||
_update_alarm_state(false, self.alarms.ContainmentRadiation)
|
||||
|
||||
-- Reactor Lost
|
||||
_update_alarm_state(self.had_reactor and self.plc_s == nil, self.alarms.ReactorLost)
|
||||
|
||||
-- Critical Damage
|
||||
_update_alarm_state(plc_cache.damage >= 100, self.alarms.CriticalDamage)
|
||||
|
||||
-- Reactor Damage
|
||||
_update_alarm_state(plc_cache.damage > 0, self.alarms.ReactorDamage)
|
||||
|
||||
-- Over-Temperature
|
||||
_update_alarm_state(plc_cache.temp >= 1200, self.alarms.ReactorOverTemp)
|
||||
|
||||
-- High Temperature
|
||||
_update_alarm_state(plc_cache.temp > 1150, self.alarms.ReactorHighTemp)
|
||||
|
||||
-- Waste Leak
|
||||
_update_alarm_state(plc_cache.waste >= 0.99, self.alarms.ReactorWasteLeak)
|
||||
|
||||
-- High Waste
|
||||
_update_alarm_state(plc_cache.waste > 0.50, self.alarms.ReactorHighWaste)
|
||||
|
||||
-- RPS Transient (excludes timeouts and manual trips)
|
||||
local rps_alarm = false
|
||||
if plc_cache.rps_status.manual ~= nil then
|
||||
if plc_cache.rps_trip then
|
||||
for key, val in pairs(plc_cache.rps_status) do
|
||||
if key ~= "manual" and key ~= "timeout" then
|
||||
rps_alarm = rps_alarm or val
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
_update_alarm_state(rps_alarm, self.alarms.RPSTransient)
|
||||
|
||||
-- RCS Transient
|
||||
local any_low = false
|
||||
local any_over = false
|
||||
for i = 1, #annunc.WaterLevelLow do any_low = any_low or annunc.WaterLevelLow[i] end
|
||||
for i = 1, #annunc.TurbineOverSpeed do any_over = any_over or annunc.TurbineOverSpeed[i] end
|
||||
|
||||
local rcs_trans = any_low or any_over or annunc.RCPTrip or annunc.RCSFlowLow or annunc.MaxWaterReturnFeed
|
||||
|
||||
-- flow is ramping up right after reactor start, annunciator indicators for these states may not indicate a real issue
|
||||
if util.time_ms() - self.start_time > FLOW_STABILITY_DELAY_MS then
|
||||
rcs_trans = rcs_trans or annunc.BoilRateMismatch or annunc.CoolantFeedMismatch or annunc.SteamFeedMismatch
|
||||
end
|
||||
|
||||
_update_alarm_state(rcs_trans, self.alarms.RCSTransient)
|
||||
|
||||
-- Turbine Trip
|
||||
local any_trip = false
|
||||
for i = 1, #annunc.TurbineTrip do any_trip = any_trip or annunc.TurbineTrip[i] end
|
||||
_update_alarm_state(any_trip, self.alarms.TurbineTrip)
|
||||
end
|
||||
|
||||
-- unlink disconnected units
|
||||
---@param sessions table
|
||||
local function _unlink_disconnected_units(sessions)
|
||||
@ -372,6 +630,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
-- link the PLC
|
||||
---@param plc_session plc_session_struct
|
||||
function public.link_plc_session(plc_session)
|
||||
self.had_reactor = true
|
||||
self.plc_s = plc_session
|
||||
self.plc_i = plc_session.instance
|
||||
|
||||
@ -443,6 +702,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
-- unlink PLC if session was closed
|
||||
if self.plc_s ~= nil and not self.plc_s.open then
|
||||
self.plc_s = nil
|
||||
self.plc_i = nil
|
||||
end
|
||||
|
||||
-- unlink RTU unit sessions if they are closed
|
||||
@ -451,6 +711,36 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
|
||||
-- update annunciator logic
|
||||
_update_annunciator()
|
||||
|
||||
-- update alarm status
|
||||
_update_alarms()
|
||||
end
|
||||
|
||||
-- ACK/RESET ALARMS --
|
||||
|
||||
-- acknowledge all alarms (if possible)
|
||||
function public.ack_all()
|
||||
for i = 1, #self.db.alarm_states do
|
||||
if self.db.alarm_states[i] == ALARM_STATE.TRIPPED then
|
||||
self.db.alarm_states[i] = ALARM_STATE.ACKED
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- acknowledge an alarm (if possible)
|
||||
---@param id ALARM alarm ID
|
||||
function public.ack_alarm(id)
|
||||
if (type(id) == "number") and (self.db.alarm_states[id] == ALARM_STATE.TRIPPED) then
|
||||
self.db.alarm_states[id] = ALARM_STATE.ACKED
|
||||
end
|
||||
end
|
||||
|
||||
-- reset an alarm (if possible)
|
||||
---@param id ALARM alarm ID
|
||||
function public.reset_alarm(id)
|
||||
if (type(id) == "number") and (self.db.alarm_states[id] == ALARM_STATE.RING_BACK) then
|
||||
self.db.alarm_states[id] = ALARM_STATE.INACTIVE
|
||||
end
|
||||
end
|
||||
|
||||
-- READ STATES/PROPERTIES --
|
||||
@ -526,6 +816,9 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
||||
-- get the annunciator status
|
||||
function public.get_annunciator() return self.db.annunciator end
|
||||
|
||||
-- get the alarm states
|
||||
function public.get_alarms() return self.db.alarm_states end
|
||||
|
||||
-- get the reactor ID
|
||||
function public.get_id() return self.r_id end
|
||||
|
||||
|
@ -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.7.10"
|
||||
local SUPERVISOR_VERSION = "beta-v0.8.0"
|
||||
|
||||
local print = util.print
|
||||
local println = util.println
|
||||
|
Loading…
Reference in New Issue
Block a user