diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 31fb9aa..f37f9d2 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -62,7 +62,6 @@ function iocontrol.init(conf, comms) waste_control = 0, a_group = 0, -- auto control group - a_limit = 0.0, -- auto control limit start = function () process.start(i) end, scram = function () process.scram(i) end, @@ -334,7 +333,7 @@ function iocontrol.update_unit_statuses(statuses) local unit = io.units[i] ---@type ioctl_entry local status = statuses[i] - if type(status) ~= "table" or #status ~= 5 then + if type(status) ~= "table" or #status ~= 6 then log.debug(log_header .. "invalid status entry in unit statuses (not a table or invalid length)") return false end @@ -343,6 +342,11 @@ function iocontrol.update_unit_statuses(statuses) local reactor_status = status[1] + if type(reactor_status) ~= "table" then + reactor_status = {} + log.debug(log_header .. "reactor status not a table") + end + if #reactor_status == 0 then unit.reactor_ps.publish("computed_status", 1) -- disconnected elseif #reactor_status == 3 then @@ -512,6 +516,11 @@ function iocontrol.update_unit_statuses(statuses) local annunciator = status[3] ---@type annunciator + if type(annunciator) ~= "table" then + annunciator = {} + log.debug(log_header .. "annunciator state not a table") + end + for key, val in pairs(annunciator) do if key == "TurbineTrip" then -- split up turbine trip table for all turbines and a general OR combination @@ -580,6 +589,20 @@ function iocontrol.update_unit_statuses(statuses) else log.debug(log_header .. "unit state not a table") end + + -- auto control state fields + + local auto_ctl_state = status[6] + + if type(auto_ctl_state) == "table" then + if #auto_ctl_state == 1 then + unit.reactor_ps.publish("burn_limit", auto_ctl_state[1]) + else + log.debug(log_header .. "auto control state length mismatch") + end + else + log.debug(log_header .. "auto control state not a table") + end end -- update alarm sounder diff --git a/coordinator/startup.lua b/coordinator/startup.lua index dcba605..1f1a829 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.7" +local COORDINATOR_VERSION = "beta-v0.8.8" local print = util.print local println = util.println diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua new file mode 100644 index 0000000..d8dece4 --- /dev/null +++ b/coordinator/ui/components/processctl.lua @@ -0,0 +1,65 @@ +local util = require("scada-common.util") + +local iocontrol = require("coordinator.iocontrol") + +local style = require("coordinator.ui.style") + +local core = require("graphics.core") + +local Div = require("graphics.elements.div") +local Rectangle = require("graphics.elements.rectangle") +local TextBox = require("graphics.elements.textbox") + +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") +local TriIndicatorLight = require("graphics.elements.indicators.trilight") +local VerticalBar = require("graphics.elements.indicators.vbar") + +local HazardButton = require("graphics.elements.controls.hazard_button") +local MultiButton = require("graphics.elements.controls.multi_button") +local PushButton = require("graphics.elements.controls.push_button") +local RadioButton = require("graphics.elements.controls.radio_button") +local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") + +local TEXT_ALIGN = core.graphics.TEXT_ALIGN + +local cpair = core.graphics.cpair +local border = core.graphics.border + +-- new process control view +---@param root graphics_element parent +---@param x integer top left x +---@param y integer top left y +local function new_view(root, x, y) + local facility = iocontrol.get_db().facility + local units = iocontrol.get_db().units + + local bw_fg_bg = cpair(colors.black, colors.white) + + local proc = Div{parent=root,width=60,height=24,x=x,y=y} + + local limits = Div{parent=proc,width=40,height=24,x=30,y=1} + + for i = 1, facility.num_units do + local unit = units[i] ---@type ioctl_entry + + local _y = ((i - 1) * 4) + 1 + + TextBox{parent=limits,x=1,y=_y+1,text="Unit "..i} + + local lim_ctl = Div{parent=limits,x=8,y=_y,width=20,height=3,fg_bg=cpair(colors.gray,colors.white)} + local burn_rate = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,max=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg} + + unit.reactor_ps.subscribe("max_burn", burn_rate.set_max) + unit.reactor_ps.subscribe("burn_limit", burn_rate.set_value) + + TextBox{parent=lim_ctl,x=9,y=2,text="mB/t"} + + local set_burn = function () unit.set_limit(burn_rate.get_value()) end + PushButton{parent=lim_ctl,x=14,y=2,text="SAVE",min_width=6,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} + end +end + +return new_view diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 119071e..a35bf9a 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -451,31 +451,25 @@ local function init(parent, id) TextBox{parent=main,text="AUTO CTRL",fg_bg=cpair(colors.black,colors.purple),alignment=TEXT_ALIGN.CENTER,width=13,height=1,x=32,y=36} local auto_ctl = Rectangle{parent=main,border=border(1,colors.purple,true),thin=true,width=13,height=15,x=32,y=37} - local auto_div = Div{parent=auto_ctl,width=13,height=15,x=2,y=1} + local auto_div = Div{parent=auto_ctl,width=13,height=15,x=1,y=1} - local ctl_opts = { "Manual", "Group A", "Group B", "Group C", "Group D" } + local ctl_opts = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" } - local a_prm, a_stb - local test = function (val) - a_prm.update(val ~= 1) - a_stb.update(val ~= 1) - end - - RadioButton{parent=auto_div,options=ctl_opts,callback=test,radio_colors=cpair(colors.blue,colors.white),radio_bg=colors.gray} + RadioButton{parent=auto_div,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.blue,colors.white),radio_bg=colors.gray} auto_div.line_break() - PushButton{parent=auto_div,text="SET",x=3,min_width=5,fg_bg=cpair(colors.black,colors.white),active_fg_bg=cpair(colors.white,colors.gray),callback=function()end} + PushButton{parent=auto_div,text="SET",x=4,min_width=5,fg_bg=cpair(colors.black,colors.white),active_fg_bg=cpair(colors.white,colors.gray),callback=function()end} auto_div.line_break() - TextBox{parent=auto_div,text="CRD Group",height=1,width=9,fg_bg=style.label} - local auto_grp = TextBox{parent=auto_div,text="Manual",height=1,width=9,fg_bg=bw_fg_bg} + TextBox{parent=auto_div,text="Prio. Group",height=1,width=11,fg_bg=style.label} + local auto_grp = TextBox{parent=auto_div,text="Manual",height=1,width=11,fg_bg=bw_fg_bg} auto_div.line_break() - a_prm = IndicatorLight{parent=auto_div,label="Primary",colors=cpair(colors.green,colors.gray)} - a_stb = IndicatorLight{parent=auto_div,label="Standby",colors=cpair(colors.white,colors.gray)} + local a_prm = IndicatorLight{parent=auto_div,label="Active",x=2,colors=cpair(colors.green,colors.gray)} + local a_stb = IndicatorLight{parent=auto_div,label="Standby",x=2,colors=cpair(colors.white,colors.gray)} return main end diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index 7c0f05c..8609f73 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -2,19 +2,22 @@ -- Main SCADA Coordinator GUI -- +local util = require("scada-common.util") + local iocontrol = require("coordinator.iocontrol") local sounder = require("coordinator.sounder") -local util = require("scada-common.util") local style = require("coordinator.ui.style") +local imatrix = require("coordinator.ui.components.imatrix") +local process_ctl = require("coordinator.ui.components.processctl") local unit_overview = require("coordinator.ui.components.unit_overview") -local imatrix = require("coordinator.ui.components.imatrix") local core = require("graphics.core") local ColorMap = require("graphics.elements.colormap") local DisplayBox = require("graphics.elements.displaybox") +local Div = require("graphics.elements.div") local TextBox = require("graphics.elements.textbox") local PushButton = require("graphics.elements.controls.push_button") @@ -75,34 +78,38 @@ local function init(monitor) cnc_y_start = cnc_y_start + 2 + local process = process_ctl(main, 2, cnc_y_start) + -- testing ---@fixme remove test code ColorMap{parent=main,x=2,y=(main.height()-1)} - PushButton{parent=main,x=2,y=cnc_y_start,text="TEST 1",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_1} - PushButton{parent=main,x=2,text="TEST 2",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_2} - PushButton{parent=main,x=2,text="TEST 3",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_3} - PushButton{parent=main,x=2,text="TEST 4",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_4} - PushButton{parent=main,x=2,text="TEST 5",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_5} - PushButton{parent=main,x=2,text="TEST 6",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_6} - PushButton{parent=main,x=2,text="TEST 7",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_7} - PushButton{parent=main,x=2,text="TEST 8",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_8} - PushButton{parent=main,x=2,text="STOP",min_width=8,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.stop} - PushButton{parent=main,x=2,text="PSCALE",min_width=8,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_power_scale} + local audio = Div{parent=main,width=34,height=15,x=95,y=cnc_y_start} - SwitchButton{parent=main,x=12,y=cnc_y_start,text="CONTAINMENT BREACH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_breach} - SwitchButton{parent=main,x=12,text="CONTAINMENT RADIATION",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rad} - SwitchButton{parent=main,x=12,text="REACTOR LOST",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_lost} - SwitchButton{parent=main,x=12,text="CRITICAL DAMAGE",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_crit} - SwitchButton{parent=main,x=12,text="REACTOR DAMAGE",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_dmg} - SwitchButton{parent=main,x=12,text="REACTOR OVER TEMP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_overtemp} - SwitchButton{parent=main,x=12,text="REACTOR HIGH TEMP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_hightemp} - SwitchButton{parent=main,x=12,text="REACTOR WASTE LEAK",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_wasteleak} - SwitchButton{parent=main,x=12,text="REACTOR WASTE HIGH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_highwaste} - SwitchButton{parent=main,x=12,text="RPS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rps} - SwitchButton{parent=main,x=12,text="RCS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rcs} - SwitchButton{parent=main,x=12,text="TURBINE TRIP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_turbinet} + PushButton{parent=audio,x=1,y=1,text="TEST 1",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_1} + PushButton{parent=audio,x=1,text="TEST 2",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_2} + PushButton{parent=audio,x=1,text="TEST 3",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_3} + PushButton{parent=audio,x=1,text="TEST 4",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_4} + PushButton{parent=audio,x=1,text="TEST 5",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_5} + PushButton{parent=audio,x=1,text="TEST 6",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_6} + PushButton{parent=audio,x=1,text="TEST 7",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_7} + PushButton{parent=audio,x=1,text="TEST 8",min_width=8,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_8} + PushButton{parent=audio,x=1,text="STOP",min_width=8,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.stop} + PushButton{parent=audio,x=1,text="PSCALE",min_width=8,fg_bg=cpair(colors.black,colors.blue),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_power_scale} + + SwitchButton{parent=audio,x=11,y=1,text="CONTAINMENT BREACH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_breach} + SwitchButton{parent=audio,x=11,text="CONTAINMENT RADIATION",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rad} + SwitchButton{parent=audio,x=11,text="REACTOR LOST",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_lost} + SwitchButton{parent=audio,x=11,text="CRITICAL DAMAGE",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_crit} + SwitchButton{parent=audio,x=11,text="REACTOR DAMAGE",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_dmg} + SwitchButton{parent=audio,x=11,text="REACTOR OVER TEMP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_overtemp} + SwitchButton{parent=audio,x=11,text="REACTOR HIGH TEMP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_hightemp} + SwitchButton{parent=audio,x=11,text="REACTOR WASTE LEAK",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_wasteleak} + SwitchButton{parent=audio,x=11,text="REACTOR WASTE HIGH",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_highwaste} + SwitchButton{parent=audio,x=11,text="RPS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rps} + SwitchButton{parent=audio,x=11,text="RCS TRANSIENT",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_rcs} + SwitchButton{parent=audio,x=11,text="TURBINE TRIP",min_width=23,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=sounder.test_turbinet} local imatrix_1 = imatrix(main, 131, cnc_y_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1]) diff --git a/graphics/elements/controls/spinbox_numeric.lua b/graphics/elements/controls/spinbox_numeric.lua index bfb1552..f76ad21 100644 --- a/graphics/elements/controls/spinbox_numeric.lua +++ b/graphics/elements/controls/spinbox_numeric.lua @@ -84,26 +84,22 @@ local function spinbox(args) -- print out the current value local function show_num() -- enforce limits - -- min 0 - if e.value < 0 then - for i = 1, #digits do digits[i] = 0 end - e.value = 0 - -- min configured - elseif (type(args.min) == "number") and (e.value < args.min) then - -- cap at min + if (type(args.min) == "number") and (e.value < args.min) then e.value = args.min set_digits() + elseif e.value < 0 then + e.value = 0 + set_digits() else - -- max printable if string.len(util.sprintf(fmt, e.value)) > args.width then - -- max out to all 9s + -- max printable exceeded, so max out to all 9s for i = 1, #digits do digits[i] = 9 end update_value() - -- max configured elseif (type(args.max) == "number") and (e.value > args.max) then - -- cap at max e.value = args.max set_digits() + else + set_digits() end end @@ -142,8 +138,6 @@ local function spinbox(args) ---@param val number number to show function e.set_value(val) e.value = val - - set_digits() show_num() end @@ -163,6 +157,10 @@ local function spinbox(args) show_num() end + -- default to zero, init digits table + e.value = 0 + set_digits() + return e.get() end diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 2e5fdb0..860fa1b 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -145,12 +145,18 @@ function coordinator.new_session(id, in_queue, out_queue, facility) for i = 1, #self.units do local unit = self.units[i] ---@type reactor_unit + + local auto_ctl = { + unit.get_control_inf().lim_br10 / 10 + } + status[unit.get_id()] = { unit.get_reactor_status(), unit.get_rtu_statuses(), unit.get_annunciator(), unit.get_alarms(), - unit.get_state() + unit.get_state(), + auto_ctl } end diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 076036e..073c1f2 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -301,6 +301,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) local public = {} -- ADD/LINK DEVICES -- + --#region -- link the PLC ---@param plc_session plc_session_struct @@ -365,7 +366,10 @@ function unit.new(for_reactor, num_boilers, num_turbines) util.filter_table(self.redstone, function (s) return s.get_session_id() ~= session end) end + --#endregion + -- AUTO CONTROL -- + --#region -- engage automatic control function public.a_engage() @@ -410,6 +414,8 @@ function unit.new(for_reactor, num_boilers, num_turbines) end end + --#endregion + -- UPDATE SESSION -- -- update (iterate) this unit @@ -501,7 +507,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- set the automatic control max burn rate for this unit ---@param limit number burn rate limit for auto control function public.set_burn_limit(limit) - if limit >= 0 then + if limit > 0 then self.db.control.lim_br10 = math.floor(limit * 10) if self.plc_i ~= nil then diff --git a/supervisor/startup.lua b/supervisor/startup.lua index a07c1e3..f05eaf8 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.9.4" +local SUPERVISOR_VERSION = "beta-v0.9.5" local print = util.print local println = util.println