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)
|
unit.set_burn_ack(ack)
|
||||||
elseif cmd == CRDN_COMMANDS.SET_WASTE then
|
elseif cmd == CRDN_COMMANDS.SET_WASTE then
|
||||||
unit.set_waste_ack(ack)
|
unit.set_waste_ack(ack)
|
||||||
|
elseif cmd == CRDN_COMMANDS.ACK_ALL_ALARMS then
|
||||||
|
unit.ack_alarms_ack(ack)
|
||||||
else
|
else
|
||||||
log.debug(util.c("received command ack with unknown command ", cmd))
|
log.debug(util.c("received command ack with unknown command ", cmd))
|
||||||
end
|
end
|
||||||
@ -474,6 +476,10 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa
|
|||||||
else
|
else
|
||||||
log.debug("supervisor connection denied")
|
log.debug("supervisor connection denied")
|
||||||
end
|
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
|
else
|
||||||
log.debug("SCADA_MGMT establish packet length mismatch")
|
log.debug("SCADA_MGMT establish packet length mismatch")
|
||||||
end
|
end
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
local comms = require("scada-common.comms")
|
local comms = require("scada-common.comms")
|
||||||
local log = require("scada-common.log")
|
local log = require("scada-common.log")
|
||||||
local psil = require("scada-common.psil")
|
local psil = require("scada-common.psil")
|
||||||
|
local types = require("scada-common.types")
|
||||||
local util = require("scada-common.util")
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local CRDN_COMMANDS = comms.CRDN_COMMANDS
|
local CRDN_COMMANDS = comms.CRDN_COMMANDS
|
||||||
@ -22,9 +23,19 @@ function iocontrol.init(conf, comms)
|
|||||||
|
|
||||||
io.units = {}
|
io.units = {}
|
||||||
for i = 1, conf.num_units do
|
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
|
---@class ioctl_entry
|
||||||
local entry = {
|
local entry = {
|
||||||
unit_id = i, ---@type integer
|
unit_id = i, ---@type integer
|
||||||
initialized = false,
|
initialized = false,
|
||||||
|
|
||||||
num_boilers = 0,
|
num_boilers = 0,
|
||||||
@ -37,17 +48,36 @@ function iocontrol.init(conf, comms)
|
|||||||
start = function () end,
|
start = function () end,
|
||||||
scram = function () end,
|
scram = function () end,
|
||||||
reset_rps = function () end,
|
reset_rps = function () end,
|
||||||
set_burn = function (rate) end,
|
ack_alarms = function () end,
|
||||||
set_waste = function (mode) end,
|
set_burn = function (rate) end, ---@param rate number
|
||||||
|
set_waste = function (mode) end, ---@param mode integer
|
||||||
|
|
||||||
start_ack = function (success) end,
|
start_ack = function (success) end, ---@param success boolean
|
||||||
scram_ack = function (success) end,
|
scram_ack = function (success) end, ---@param success boolean
|
||||||
reset_rps_ack = function (success) end,
|
reset_rps_ack = function (success) end, ---@param success boolean
|
||||||
set_burn_ack = function (success) end,
|
ack_alarms_ack = function (success) end,---@param success boolean
|
||||||
set_waste_ack = function (success) end,
|
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_ps = psil.create(),
|
||||||
reactor_data = {}, ---@type reactor_db
|
reactor_data = {}, ---@type reactor_db
|
||||||
|
|
||||||
boiler_ps_tbl = {},
|
boiler_ps_tbl = {},
|
||||||
boiler_data_tbl = {},
|
boiler_data_tbl = {},
|
||||||
@ -73,6 +103,11 @@ function iocontrol.init(conf, comms)
|
|||||||
log.debug(util.c("UNIT[", i, "]: RESET_RPS"))
|
log.debug(util.c("UNIT[", i, "]: RESET_RPS"))
|
||||||
end
|
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)
|
function entry.set_burn(rate)
|
||||||
comms.send_command(CRDN_COMMANDS.SET_BURN, i, rate)
|
comms.send_command(CRDN_COMMANDS.SET_BURN, i, rate)
|
||||||
log.debug(util.c("UNIT[", i, "]: SET_BURN = ", rate))
|
log.debug(util.c("UNIT[", i, "]: SET_BURN = ", rate))
|
||||||
@ -167,7 +202,10 @@ end
|
|||||||
---@param statuses table
|
---@param statuses table
|
||||||
---@return boolean valid
|
---@return boolean valid
|
||||||
function iocontrol.update_statuses(statuses)
|
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")
|
log.error("number of provided unit statuses does not match expected number of units")
|
||||||
return false
|
return false
|
||||||
else
|
else
|
||||||
@ -175,6 +213,11 @@ function iocontrol.update_statuses(statuses)
|
|||||||
local unit = io.units[i] ---@type ioctl_entry
|
local unit = io.units[i] ---@type ioctl_entry
|
||||||
local status = statuses[i]
|
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
|
-- reactor PLC status
|
||||||
|
|
||||||
local reactor_status = status[1]
|
local reactor_status = status[1]
|
||||||
@ -253,7 +296,7 @@ function iocontrol.update_statuses(statuses)
|
|||||||
end
|
end
|
||||||
|
|
||||||
unit.reactor_ps.publish("TurbineTrip", any)
|
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
|
-- split up array for all boilers
|
||||||
for id = 1, #val do
|
for id = 1, #val do
|
||||||
unit.boiler_ps_tbl[id].publish(key, val[id])
|
unit.boiler_ps_tbl[id].publish(key, val[id])
|
||||||
@ -272,9 +315,25 @@ function iocontrol.update_statuses(statuses)
|
|||||||
end
|
end
|
||||||
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
|
-- RTU statuses
|
||||||
|
|
||||||
local rtu_statuses = status[3]
|
local rtu_statuses = status[4]
|
||||||
|
|
||||||
if type(rtu_statuses) == "table" then
|
if type(rtu_statuses) == "table" then
|
||||||
if type(rtu_statuses.boilers) == "table" then
|
if type(rtu_statuses.boilers) == "table" then
|
||||||
|
@ -17,7 +17,7 @@ local config = require("coordinator.config")
|
|||||||
local coordinator = require("coordinator.coordinator")
|
local coordinator = require("coordinator.coordinator")
|
||||||
local renderer = require("coordinator.renderer")
|
local renderer = require("coordinator.renderer")
|
||||||
|
|
||||||
local COORDINATOR_VERSION = "alpha-v0.6.17"
|
local COORDINATOR_VERSION = "alpha-v0.7.0"
|
||||||
|
|
||||||
local print = util.print
|
local print = util.print
|
||||||
local println = util.println
|
local println = util.println
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
-- Reactor Unit SCADA Coordinator GUI
|
-- Reactor Unit SCADA Coordinator GUI
|
||||||
--
|
--
|
||||||
|
|
||||||
local tcallbackdsp = require("scada-common.tcallbackdsp")
|
|
||||||
|
|
||||||
local iocontrol = require("coordinator.iocontrol")
|
local iocontrol = require("coordinator.iocontrol")
|
||||||
|
|
||||||
local style = require("coordinator.ui.style")
|
local style = require("coordinator.ui.style")
|
||||||
@ -12,8 +10,8 @@ local core = require("graphics.core")
|
|||||||
|
|
||||||
local Div = require("graphics.elements.div")
|
local Div = require("graphics.elements.div")
|
||||||
local TextBox = require("graphics.elements.textbox")
|
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 CoreMap = require("graphics.elements.indicators.coremap")
|
||||||
local DataIndicator = require("graphics.elements.indicators.data")
|
local DataIndicator = require("graphics.elements.indicators.data")
|
||||||
local IndicatorLight = require("graphics.elements.indicators.light")
|
local IndicatorLight = require("graphics.elements.indicators.light")
|
||||||
@ -30,6 +28,29 @@ local cpair = core.graphics.cpair
|
|||||||
|
|
||||||
local period = core.flasher.PERIOD
|
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
|
-- create a unit view
|
||||||
---@param parent graphics_element parent
|
---@param parent graphics_element parent
|
||||||
---@param id integer
|
---@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}
|
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 hzd_fg_bg = cpair(colors.white, colors.gray)
|
||||||
local lu_cpair = cpair(colors.gray, colors.gray)
|
local lu_cpair = cpair(colors.gray, colors.gray)
|
||||||
|
|
||||||
|
-----------------------------
|
||||||
-- main stats and core map --
|
-- main stats and core map --
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
local core_map = CoreMap{parent=main,x=2,y=3,reactor_l=18,reactor_w=18}
|
local core_map = CoreMap{parent=main,x=2,y=3,reactor_l=18,reactor_w=18}
|
||||||
r_ps.subscribe("temp", core_map.update)
|
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}
|
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()
|
main.line_break()
|
||||||
|
|
||||||
|
-----------------
|
||||||
-- annunciator --
|
-- 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
|
-- connectivity/basic state
|
||||||
local plc_online = IndicatorLight{parent=annunciator,label="PLC Online",colors=cpair(colors.green,colors.red)}
|
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 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_active = IndicatorLight{parent=annunciator,label="Active",colors=cpair(colors.green,colors.gray)}
|
||||||
---@todo auto control as info sent here
|
---@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("PLCOnline", plc_online.update)
|
||||||
r_ps.subscribe("PLCHeartbeat", plc_hbeat.update)
|
r_ps.subscribe("PLCHeartbeat", plc_hbeat.update)
|
||||||
@ -103,23 +128,25 @@ local function init(parent, id)
|
|||||||
|
|
||||||
annunciator.line_break()
|
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_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_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_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_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_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_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_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_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_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("ReactorSCRAM", r_scram.update)
|
||||||
r_ps.subscribe("ManualReactorSCRAM", r_mscrm.update)
|
r_ps.subscribe("ManualReactorSCRAM", r_mscrm.update)
|
||||||
r_ps.subscribe("AutoReactorSCRAM", r_ascrm.update)
|
r_ps.subscribe("AutoReactorSCRAM", r_ascrm.update)
|
||||||
r_ps.subscribe("RCPTrip", r_rtrip.update)
|
r_ps.subscribe("RCPTrip", r_rtrip.update)
|
||||||
r_ps.subscribe("RCSFlowLow", r_cflow.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("ReactorTempHigh", r_temp.update)
|
||||||
r_ps.subscribe("ReactorHighDeltaT", r_rhdt.update)
|
r_ps.subscribe("ReactorHighDeltaT", r_rhdt.update)
|
||||||
r_ps.subscribe("FuelInputRateLow", r_firl.update)
|
r_ps.subscribe("FuelInputRateLow", r_firl.update)
|
||||||
@ -128,14 +155,24 @@ local function init(parent, id)
|
|||||||
|
|
||||||
annunciator.line_break()
|
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_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_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_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_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_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_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_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}
|
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()
|
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_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_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)}
|
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()
|
annunciator.line_break()
|
||||||
|
|
||||||
-- machine-specific indicators
|
-- boiler annunciator panel(s)
|
||||||
|
|
||||||
|
local tag_y = 1
|
||||||
|
|
||||||
if unit.num_boilers > 0 then
|
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)}
|
local b1_hr = IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)}
|
||||||
b_ps[1].subscribe("HeatingRateLow", b1_hr.update)
|
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
|
end
|
||||||
if unit.num_boilers > 1 then
|
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)}
|
local b2_hr = IndicatorLight{parent=annunciator,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)}
|
||||||
b_ps[2].subscribe("HeatingRateLow", b2_hr.update)
|
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
|
end
|
||||||
|
|
||||||
if unit.num_boilers > 0 then
|
if unit.num_boilers > 0 then
|
||||||
@ -185,7 +242,14 @@ local function init(parent, id)
|
|||||||
annunciator.line_break()
|
annunciator.line_break()
|
||||||
end
|
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}
|
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)
|
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)}
|
local t1_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)}
|
||||||
t_ps[1].subscribe("TurbineOverSpeed", t1_tos.update)
|
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}
|
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)
|
t_ps[1].subscribe("TurbineTrip", t1_trp.update)
|
||||||
|
TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.cyan)}
|
||||||
main.line_break()
|
|
||||||
annunciator.line_break()
|
|
||||||
|
|
||||||
if unit.num_turbines > 1 then
|
if unit.num_turbines > 1 then
|
||||||
TextBox{parent=main,x=32,text="T2",width=2,height=1,fg_bg=cpair(colors.black, colors.white)}
|
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)}
|
local t2_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)}
|
||||||
t_ps[2].subscribe("TurbineOverSpeed", t2_tos.update)
|
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}
|
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)
|
t_ps[2].subscribe("TurbineTrip", t2_trp.update)
|
||||||
|
TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.cyan)}
|
||||||
main.line_break()
|
|
||||||
annunciator.line_break()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if unit.num_turbines > 2 then
|
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)}
|
local t3_tos = IndicatorLight{parent=annunciator,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)}
|
||||||
t_ps[3].subscribe("TurbineOverSpeed", t3_tos.update)
|
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}
|
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)
|
t_ps[3].subscribe("TurbineTrip", t3_trp.update)
|
||||||
|
TextBox{parent=main,x=34,y=tag_y,text="\x95",width=1,height=1,fg_bg=cpair(colors.lightGray, colors.cyan)}
|
||||||
annunciator.line_break()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
annunciator.line_break()
|
||||||
|
|
||||||
---@todo radiation monitor
|
---@todo radiation monitor
|
||||||
IndicatorLight{parent=annunciator,label="Radiation Monitor",colors=cpair(colors.green,colors.gray)}
|
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 --
|
-- 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_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)}
|
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 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 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 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 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 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 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.start_ack = start.on_response
|
||||||
unit.scram_ack = scram.on_response
|
unit.scram_ack = scram.on_response
|
||||||
unit.reset_rps_ack = reset.on_response
|
unit.reset_rps_ack = reset.on_response
|
||||||
|
unit.ack_alarms_ack = ack_a.on_response
|
||||||
|
|
||||||
local function start_button_en_check()
|
local function start_button_en_check()
|
||||||
if (unit.reactor_data ~= nil) and (unit.reactor_data.mek_status ~= nil) then
|
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", start_button_en_check)
|
||||||
r_ps.subscribe("rps_tripped", function (active) if active then reset.enable() else reset.disable() end end)
|
r_ps.subscribe("rps_tripped", function (active) if active then reset.enable() else reset.disable() end end)
|
||||||
|
|
||||||
local opts = {
|
TextBox{parent=main,x=2,y=30,text="Idle",width=29,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)}
|
||||||
{
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
---@todo waste selection
|
local waste_sel = Div{parent=main,x=2,y=50,width=29,height=2,fg_bg=cpair(colors.black, colors.white)}
|
||||||
local waste_sel = Div{parent=main,x=2,y=48,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}
|
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
|
return main
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ local element = {}
|
|||||||
---|push_button_args
|
---|push_button_args
|
||||||
---|spinbox_args
|
---|spinbox_args
|
||||||
---|switch_button_args
|
---|switch_button_args
|
||||||
|
---|alarm_indicator_light
|
||||||
---|core_map_args
|
---|core_map_args
|
||||||
---|data_indicator_args
|
---|data_indicator_args
|
||||||
---|hbar_args
|
---|hbar_args
|
||||||
@ -302,6 +303,18 @@ function element.new(args)
|
|||||||
---@return cpair fg_bg
|
---@return cpair fg_bg
|
||||||
function public.get_fg_bg() return protected.fg_bg end
|
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
|
-- get element width
|
||||||
---@return integer width
|
---@return integer width
|
||||||
function public.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
|
-- Tri-State Indicator Light Graphics Element
|
||||||
|
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
local element = require("graphics.element")
|
local element = require("graphics.element")
|
||||||
|
local flasher = require("graphics.flasher")
|
||||||
|
|
||||||
---@class tristate_indicator_light_args
|
---@class tristate_indicator_light_args
|
||||||
---@field label string indicator label
|
---@field label string indicator label
|
||||||
@ -8,13 +11,15 @@ local element = require("graphics.element")
|
|||||||
---@field c2 color color for state 2
|
---@field c2 color color for state 2
|
||||||
---@field c3 color color for state 3
|
---@field c3 color color for state 3
|
||||||
---@field min_label_width? integer label length if omitted
|
---@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 parent graphics_element
|
||||||
---@field id? string element id
|
---@field id? string element id
|
||||||
---@field x? integer 1 if omitted
|
---@field x? integer 1 if omitted
|
||||||
---@field y? integer 1 if omitted
|
---@field y? integer 1 if omitted
|
||||||
---@field fg_bg? cpair foreground/background colors
|
---@field fg_bg? cpair foreground/background colors
|
||||||
|
|
||||||
-- new indicator light
|
-- new tri-state indicator light
|
||||||
---@param args tristate_indicator_light_args
|
---@param args tristate_indicator_light_args
|
||||||
---@return graphics_element element, element_id id
|
---@return graphics_element element, element_id id
|
||||||
local function tristate_indicator_light(args)
|
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.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")
|
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
|
-- single line
|
||||||
args.height = 1
|
args.height = 1
|
||||||
|
|
||||||
-- determine width
|
-- determine width
|
||||||
args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2
|
args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2
|
||||||
|
|
||||||
|
-- flasher state
|
||||||
|
local flash_on = true
|
||||||
|
|
||||||
-- blit translations
|
-- blit translations
|
||||||
local c1 = colors.toBlit(args.c1)
|
local c1 = colors.toBlit(args.c1)
|
||||||
local c2 = colors.toBlit(args.c2)
|
local c2 = colors.toBlit(args.c2)
|
||||||
@ -37,12 +49,45 @@ local function tristate_indicator_light(args)
|
|||||||
-- create new graphics element base object
|
-- create new graphics element base object
|
||||||
local e = element.new(args)
|
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
|
-- on state change
|
||||||
---@param new_state integer indicator state
|
---@param new_state integer indicator state
|
||||||
function e.on_update(new_state)
|
function e.on_update(new_state)
|
||||||
|
local was_off = e.value <= 1
|
||||||
|
|
||||||
e.value = new_state
|
e.value = new_state
|
||||||
e.window.setCursorPos(1, 1)
|
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)
|
e.window.blit(" \x95", "0" .. c2, c2 .. e.fg_bg.blit_bkg)
|
||||||
elseif new_state == 3 then
|
elseif new_state == 3 then
|
||||||
e.window.blit(" \x95", "0" .. c3, c3 .. e.fg_bg.blit_bkg)
|
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
|
function e.set_value(val) e.on_update(val) end
|
||||||
|
|
||||||
-- write label and initial indicator light
|
-- write label and initial indicator light
|
||||||
e.on_update(0)
|
e.on_update(1)
|
||||||
e.window.write(args.label)
|
e.window.write(args.label)
|
||||||
|
|
||||||
return e.get()
|
return e.get()
|
||||||
|
@ -14,7 +14,7 @@ local config = require("reactor-plc.config")
|
|||||||
local plc = require("reactor-plc.plc")
|
local plc = require("reactor-plc.plc")
|
||||||
local threads = require("reactor-plc.threads")
|
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 print = util.print
|
||||||
local println = util.println
|
local println = util.println
|
||||||
|
@ -412,7 +412,7 @@ function rtu.comms(version, modem, local_port, server_port, conn_watchdog)
|
|||||||
else
|
else
|
||||||
-- establish denied
|
-- establish denied
|
||||||
public.unlink(rtu_state)
|
public.unlink(rtu_state)
|
||||||
println_ts("supervisor connection")
|
println_ts("supervisor connection denied")
|
||||||
log.warning("supervisor connection denied by remote host")
|
log.warning("supervisor connection denied by remote host")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
@ -25,7 +25,7 @@ local sna_rtu = require("rtu.dev.sna_rtu")
|
|||||||
local sps_rtu = require("rtu.dev.sps_rtu")
|
local sps_rtu = require("rtu.dev.sps_rtu")
|
||||||
local turbinev_rtu = require("rtu.dev.turbinev_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
|
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
|
local insert = table.insert
|
||||||
|
|
||||||
comms.version = "1.0.0"
|
comms.version = "1.0.1"
|
||||||
|
|
||||||
---@alias PROTOCOLS integer
|
---@alias PROTOCOLS integer
|
||||||
local PROTOCOLS = {
|
local PROTOCOLS = {
|
||||||
@ -74,7 +74,10 @@ local CRDN_COMMANDS = {
|
|||||||
START = 1, -- start the reactor
|
START = 1, -- start the reactor
|
||||||
RESET_RPS = 2, -- reset the RPS
|
RESET_RPS = 2, -- reset the RPS
|
||||||
SET_BURN = 3, -- set the burn rate
|
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
|
---@alias CAPI_TYPES integer
|
||||||
|
@ -35,6 +35,76 @@ types.TRI_FAIL = {
|
|||||||
FULL = 2
|
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 --
|
-- STRING TYPES --
|
||||||
|
|
||||||
---@alias os_event
|
---@alias os_event
|
||||||
|
@ -129,7 +129,7 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units)
|
|||||||
|
|
||||||
for i = 1, #self.units do
|
for i = 1, #self.units do
|
||||||
local unit = self.units[i] ---@type reactor_unit
|
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
|
end
|
||||||
|
|
||||||
_send(SCADA_CRDN_TYPES.UNIT_STATUSES, status)
|
_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
|
-- continue if valid unit id
|
||||||
if util.is_int(uid) and uid > 0 and uid <= #self.units then
|
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
|
if cmd == CRDN_COMMANDS.START then
|
||||||
self.out_q.push_data(SV_Q_DATA.START, data)
|
self.out_q.push_data(SV_Q_DATA.START, data)
|
||||||
elseif cmd == CRDN_COMMANDS.SCRAM then
|
elseif cmd == CRDN_COMMANDS.SCRAM then
|
||||||
@ -209,6 +211,21 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units)
|
|||||||
else
|
else
|
||||||
log.debug(log_header .. "CRDN command unit set waste missing option")
|
log.debug(log_header .. "CRDN command unit set waste missing option")
|
||||||
end
|
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
|
else
|
||||||
log.debug(log_header .. "CRDN command unknown")
|
log.debug(log_header .. "CRDN command unknown")
|
||||||
end
|
end
|
||||||
|
@ -4,9 +4,15 @@ local log = require("scada-common.log")
|
|||||||
|
|
||||||
local unit = {}
|
local unit = {}
|
||||||
|
|
||||||
|
local ALARM = types.ALARM
|
||||||
|
local PRIO = types.ALARM_PRIORITY
|
||||||
|
local ALARM_STATE = types.ALARM_STATE
|
||||||
|
|
||||||
local TRI_FAIL = types.TRI_FAIL
|
local TRI_FAIL = types.TRI_FAIL
|
||||||
local DUMPING_MODE = types.DUMPING_MODE
|
local DUMPING_MODE = types.DUMPING_MODE
|
||||||
|
|
||||||
|
local FLOW_STABILITY_DELAY_MS = 15000
|
||||||
|
|
||||||
local DT_KEYS = {
|
local DT_KEYS = {
|
||||||
ReactorTemp = "RTP",
|
ReactorTemp = "RTP",
|
||||||
ReactorFuel = "RFL",
|
ReactorFuel = "RFL",
|
||||||
@ -21,6 +27,32 @@ local DT_KEYS = {
|
|||||||
TurbinePower = "TPR"
|
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
|
-- create a new reactor unit
|
||||||
---@param for_reactor integer reactor unit number
|
---@param for_reactor integer reactor unit number
|
||||||
---@param num_boilers integer number of boilers expected
|
---@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,
|
r_id = for_reactor,
|
||||||
plc_s = nil, ---@class plc_session_struct
|
plc_s = nil, ---@class plc_session_struct
|
||||||
plc_i = nil, ---@class plc_session
|
plc_i = nil, ---@class plc_session
|
||||||
counts = { boilers = num_boilers, turbines = num_turbines },
|
|
||||||
turbines = {},
|
turbines = {},
|
||||||
boilers = {},
|
boilers = {},
|
||||||
redstone = {},
|
redstone = {},
|
||||||
deltas = {},
|
deltas = {},
|
||||||
last_heartbeat = 0,
|
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 = {
|
db = {
|
||||||
---@class annunciator
|
---@class annunciator
|
||||||
annunciator = {
|
annunciator = {
|
||||||
@ -47,6 +116,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
|||||||
AutoReactorSCRAM = false,
|
AutoReactorSCRAM = false,
|
||||||
RCPTrip = false,
|
RCPTrip = false,
|
||||||
RCSFlowLow = false,
|
RCSFlowLow = false,
|
||||||
|
CoolantLevelLow = false,
|
||||||
ReactorTempHigh = false,
|
ReactorTempHigh = false,
|
||||||
ReactorHighDeltaT = false,
|
ReactorHighDeltaT = false,
|
||||||
FuelInputRateLow = false,
|
FuelInputRateLow = false,
|
||||||
@ -55,6 +125,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
|||||||
-- boiler
|
-- boiler
|
||||||
BoilerOnline = {},
|
BoilerOnline = {},
|
||||||
HeatingRateLow = {},
|
HeatingRateLow = {},
|
||||||
|
WaterLevelLow = {},
|
||||||
BoilRateMismatch = false,
|
BoilRateMismatch = false,
|
||||||
CoolantFeedMismatch = false,
|
CoolantFeedMismatch = false,
|
||||||
-- turbine
|
-- turbine
|
||||||
@ -64,6 +135,21 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
|||||||
SteamDumpOpen = {},
|
SteamDumpOpen = {},
|
||||||
TurbineOverSpeed = {},
|
TurbineOverSpeed = {},
|
||||||
TurbineTrip = {}
|
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
|
||||||
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
|
-- update all delta computations
|
||||||
local function _dt__compute_all()
|
local function _dt__compute_all()
|
||||||
if self.plc_s ~= nil then
|
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
|
if self.plc_s ~= nil then
|
||||||
local plc_db = self.plc_i.get_db()
|
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
|
-- heartbeat blink about every second
|
||||||
if self.last_heartbeat + 1000 < plc_db.last_status_update then
|
if self.last_heartbeat + 1000 < plc_db.last_status_update then
|
||||||
self.db.annunciator.PLCHeartbeat = not self.db.annunciator.PLCHeartbeat
|
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
|
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 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
|
total_boil_rate = plc_db.mek_status.heating_rate
|
||||||
end
|
end
|
||||||
|
else
|
||||||
|
self.plc_cache.ok = false
|
||||||
end
|
end
|
||||||
|
|
||||||
-------------
|
-------------
|
||||||
@ -211,13 +400,13 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
|||||||
-------------
|
-------------
|
||||||
|
|
||||||
-- clear boiler online flags
|
-- 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
|
-- aggregated statistics
|
||||||
local boiler_steam_dt_sum = 0.0
|
local boiler_steam_dt_sum = 0.0
|
||||||
local boiler_water_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
|
-- go through boilers for stats and online
|
||||||
for i = 1, #self.boilers do
|
for i = 1, #self.boilers do
|
||||||
local session = self.boilers[i] ---@type unit_session
|
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
|
-- check coolant feed mismatch if using boilers, otherwise calculate with reactor
|
||||||
local cfmismatch = false
|
local cfmismatch = false
|
||||||
|
|
||||||
if self.counts.boilers > 0 then
|
if num_boilers > 0 then
|
||||||
for i = 1, #self.boilers do
|
for i = 1, #self.boilers do
|
||||||
local boiler = self.boilers[i] ---@type unit_session
|
local boiler = self.boilers[i] ---@type unit_session
|
||||||
local idx = boiler.get_device_idx()
|
local idx = boiler.get_device_idx()
|
||||||
@ -290,7 +479,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
|||||||
--------------
|
--------------
|
||||||
|
|
||||||
-- clear turbine online flags
|
-- 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
|
-- aggregated statistics
|
||||||
local total_flow_rate = 0
|
local total_flow_rate = 0
|
||||||
@ -359,6 +548,75 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
|||||||
end
|
end
|
||||||
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
|
-- unlink disconnected units
|
||||||
---@param sessions table
|
---@param sessions table
|
||||||
local function _unlink_disconnected_units(sessions)
|
local function _unlink_disconnected_units(sessions)
|
||||||
@ -372,6 +630,7 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
|||||||
-- link the PLC
|
-- link the PLC
|
||||||
---@param plc_session plc_session_struct
|
---@param plc_session plc_session_struct
|
||||||
function public.link_plc_session(plc_session)
|
function public.link_plc_session(plc_session)
|
||||||
|
self.had_reactor = true
|
||||||
self.plc_s = plc_session
|
self.plc_s = plc_session
|
||||||
self.plc_i = plc_session.instance
|
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
|
-- unlink PLC if session was closed
|
||||||
if self.plc_s ~= nil and not self.plc_s.open then
|
if self.plc_s ~= nil and not self.plc_s.open then
|
||||||
self.plc_s = nil
|
self.plc_s = nil
|
||||||
|
self.plc_i = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
-- unlink RTU unit sessions if they are closed
|
-- 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 logic
|
||||||
_update_annunciator()
|
_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
|
end
|
||||||
|
|
||||||
-- READ STATES/PROPERTIES --
|
-- READ STATES/PROPERTIES --
|
||||||
@ -526,6 +816,9 @@ function unit.new(for_reactor, num_boilers, num_turbines)
|
|||||||
-- get the annunciator status
|
-- get the annunciator status
|
||||||
function public.get_annunciator() return self.db.annunciator end
|
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
|
-- get the reactor ID
|
||||||
function public.get_id() return self.r_id end
|
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 config = require("supervisor.config")
|
||||||
local supervisor = require("supervisor.supervisor")
|
local supervisor = require("supervisor.supervisor")
|
||||||
|
|
||||||
local SUPERVISOR_VERSION = "beta-v0.7.10"
|
local SUPERVISOR_VERSION = "beta-v0.8.0"
|
||||||
|
|
||||||
local print = util.print
|
local print = util.print
|
||||||
local println = util.println
|
local println = util.println
|
||||||
|
Loading…
x
Reference in New Issue
Block a user