From 38a1a4282cea2c931e19cde0eecb7b21b67aa578 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 12 Oct 2024 15:30:14 -0400 Subject: [PATCH] #398 #355 pocket process control UI --- coordinator/ui/components/process_ctl.lua | 6 +- graphics/core.lua | 2 +- graphics/elements/controls/RadioButton.lua | 16 +- pocket/ui/apps/process.lua | 208 +++++++++++++++++++-- pocket/ui/main.lua | 2 + pocket/ui/style.lua | 10 + 6 files changed, 223 insertions(+), 21 deletions(-) diff --git a/coordinator/ui/components/process_ctl.lua b/coordinator/ui/components/process_ctl.lua index f49964b..a70bcd6 100644 --- a/coordinator/ui/components/process_ctl.lua +++ b/coordinator/ui/components/process_ctl.lua @@ -264,10 +264,8 @@ local function new_view(root, x, y) local limits = {} for i = 1, #rate_limits do limits[i] = rate_limits[i].get_value() end - process.save(mode.get_value(), b_target.get_value(), - db.energy_convert_to_fe(c_target.get_value()), - db.energy_convert_to_fe(g_target.get_value()), - limits) + process.save(mode.get_value(), b_target.get_value(), db.energy_convert_to_fe(c_target.get_value()), + db.energy_convert_to_fe(g_target.get_value()), limits) end -- start automatic control after saving process control settings diff --git a/graphics/core.lua b/graphics/core.lua index cbedccf..1e9f81b 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -7,7 +7,7 @@ local flasher = require("graphics.flasher") local core = {} -core.version = "2.4.3" +core.version = "2.4.4" core.flasher = flasher core.events = events diff --git a/graphics/elements/controls/RadioButton.lua b/graphics/elements/controls/RadioButton.lua index a2acffc..5c94316 100644 --- a/graphics/elements/controls/RadioButton.lua +++ b/graphics/elements/controls/RadioButton.lua @@ -11,6 +11,7 @@ local KEY_CLICK = core.events.KEY_CLICK ---@field options table button options ---@field radio_colors cpair radio button colors (inner & outer) ---@field select_color color color for radio button border when selected +---@field dis_fg_bg? cpair foreground/background colors when disabled ---@field default? integer default state, defaults to options[1] ---@field min_width? integer text length + 2 if omitted ---@field callback? function function to call on touch @@ -64,6 +65,10 @@ return function (args) local inner_color = util.trinary(e.value == i, args.radio_colors.color_b, args.radio_colors.color_a) local outer_color = util.trinary(e.value == i, args.select_color, args.radio_colors.color_b) + if e.value == i and args.dis_fg_bg and not e.enabled then + outer_color = args.radio_colors.color_a + end + e.w_set_cur(1, i) e.w_set_fgd(inner_color) @@ -75,9 +80,14 @@ return function (args) e.w_write("\x95") -- write button text - if i == focused_opt and e.is_focused() and e.enabled then - e.w_set_fgd(e.fg_bg.bkg) - e.w_set_bkg(e.fg_bg.fgd) + if args.dis_fg_bg and not e.enabled then + e.w_set_fgd(args.dis_fg_bg.fgd) + e.w_set_bkg(args.dis_fg_bg.bkg) + elseif i == focused_opt and e.is_focused() then + if e.enabled then + e.w_set_fgd(e.fg_bg.bkg) + e.w_set_bkg(e.fg_bg.fgd) + end else e.w_set_fgd(e.fg_bg.fgd) e.w_set_bkg(e.fg_bg.bkg) diff --git a/pocket/ui/apps/process.lua b/pocket/ui/apps/process.lua index d544f17..bfe0f1f 100644 --- a/pocket/ui/apps/process.lua +++ b/pocket/ui/apps/process.lua @@ -15,12 +15,13 @@ local core = require("graphics.core") local Div = require("graphics.elements.Div") local MultiPane = require("graphics.elements.MultiPane") +local Rectangle = require("graphics.elements.Rectangle") local TextBox = require("graphics.elements.TextBox") local WaitingAnim = require("graphics.elements.animations.Waiting") local HazardButton = require("graphics.elements.controls.HazardButton") -local PushButton = require("graphics.elements.controls.PushButton") +local RadioButton = require("graphics.elements.controls.RadioButton") local NumberField = require("graphics.elements.form.NumberField") @@ -31,12 +32,16 @@ local AUTO_GROUP = types.AUTO_GROUP local ALIGN = core.ALIGN local cpair = core.cpair +local border = core.border local APP_ID = pocket.APP_ID local lu_col = style.label_unit_pair local text_fg = style.text_fg -local mode_states = style.icon_states.mode_states +local red_ind_s = style.icon_states.red_ind_s +local yel_ind_s = style.icon_states.yel_ind_s +local grn_ind_s = style.icon_states.grn_ind_s +local wht_ind_s = style.icon_states.wht_ind_s local hzd_fg_bg = cpair(colors.white, colors.gray) local dis_colors = cpair(colors.white, colors.lightGray) @@ -67,15 +72,17 @@ local function new_view(root) -- load the app (create the elements) local function load() + local f_ps = db.facility.ps + page_div = Div{parent=main,y=2,width=main.get_width()} local panes = {} ---@type Div[] -- create all page divs - -- for _ = 1, db.facility.num_units + 1 do - -- local div = Div{parent=page_div} - -- table.insert(panes, div) - -- end + for _ = 1, db.facility.num_units + 3 do + local div = Div{parent=page_div} + table.insert(panes, div) + end local last_update = 0 -- refresh data callback, every 500ms it will re-send the query @@ -86,6 +93,10 @@ local function new_view(root) end end + --#region unit settings/status + + local rate_limits = {} + for i = 1, db.facility.num_units do local u_pane = panes[i] local u_div = Div{parent=u_pane,x=2,width=main.get_width()-2} @@ -97,17 +108,186 @@ local function new_view(root) TextBox{parent=u_div,y=1,text="Reactor Unit #"..i,alignment=ALIGN.CENTER} - u_div.line_break() + TextBox{parent=u_div,y=3,text="Auto Rate Limit",fg_bg=cpair(colors.lightGray,colors.black)} + rate_limits[i] = NumberField{parent=u_div,x=1,y=4,width=16,default=0.01,min=0.01,max_frac_digits=2,max_chars=8,allow_decimal=true,align_right=true,fg_bg=cpair(colors.white,colors.gray),dis_fg_bg=cpair(colors.gray,colors.lightGray)} + TextBox{parent=u_div,x=18,y=4,text="mB/t",width=4,fg_bg=cpair(colors.lightGray,colors.black)} + + rate_limits[i].register(unit.unit_ps, "max_burn", rate_limits[i].set_max) + rate_limits[i].register(unit.unit_ps, "burn_limit", rate_limits[i].set_value) + + local ready = IconIndicator{parent=u_div,y=6,label="Auto Ready",states=grn_ind_s} + local a_stb = IconIndicator{parent=u_div,label="Auto Standby",states=wht_ind_s} + local degraded = IconIndicator{parent=u_div,label="Unit Degraded",states=red_ind_s} + + ready.register(u_ps, "U_AutoReady", ready.update) + degraded.register(u_ps, "U_AutoDegraded", degraded.update) + + -- update standby indicator + a_stb.register(u_ps, "status", function (active) + a_stb.update(unit.annunciator.AutoControl and (not active)) + end) + a_stb.register(u_ps, "AutoControl", function (auto_active) + if auto_active then + a_stb.update(unit.reactor_data.mek_status.status == false) + else a_stb.update(false) end + end) + + local function _set_group(value) process.set_group(i, value) end + + local group = RadioButton{parent=u_div,y=10,options=types.AUTO_GROUP_NAMES,callback=_set_group,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.purple,dis_fg_bg=cpair(colors.gray,colors.black)} + + -- can't change group if auto is engaged regardless of if this unit is part of auto control + group.register(f_ps, "auto_active", function (auto_active) + if auto_active then group.disable() else group.enable() end + end) + + group.register(u_ps, "auto_group_id", function (gid) group.set_value(gid + 1) end) + + TextBox{parent=u_div,y=16,text="Assigned Group",fg_bg=style.label} + local auto_grp = TextBox{parent=u_div,text="Manual",width=11,fg_bg=cpair(colors.white,colors.gray)} + + auto_grp.register(u_ps, "auto_group", auto_grp.set_value) util.nop() end - -- main process page + --#endregion - -- local f_pane = panes[db.facility.num_units + 1] - -- local f_div = Div{parent=f_pane,x=2,width=main.get_width()-2} + --#region process control options page - -- app.new_page(nil, db.facility.num_units + 1) + local o_pane = panes[db.facility.num_units + 2] + local o_div = Div{parent=o_pane,x=2,width=main.get_width()-2} + + local opt_page = app.new_page(nil, db.facility.num_units + 2) + + TextBox{parent=o_div,y=1,text="Process Options",alignment=ALIGN.CENTER} + + local ctl_opts = { "Monitored Max Burn", "Combined Burn Rate", "Charge Level", "Generation Rate" } + local mode = RadioButton{parent=o_div,x=1,y=3,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.purple} + + mode.register(f_ps, "process_mode", mode.set_value) + + TextBox{parent=o_div,y=9,text="Burn Rate Target",fg_bg=cpair(colors.lightGray,colors.black)} + local b_target = NumberField{parent=o_div,x=1,y=10,width=15,default=0.01,min=0.01,max_frac_digits=2,max_chars=8,allow_decimal=true,align_right=true,fg_bg=cpair(colors.white,colors.gray),dis_fg_bg=cpair(colors.gray,colors.lightGray)} + TextBox{parent=o_div,x=17,y=10,text="mB/t",fg_bg=cpair(colors.lightGray,colors.black)} + + TextBox{parent=o_div,y=12,text="Charge Level Target",fg_bg=cpair(colors.lightGray,colors.black)} + local c_target = NumberField{parent=o_div,x=1,y=13,width=15,default=0,min=0,max_chars=16,align_right=true,fg_bg=cpair(colors.white,colors.gray),dis_fg_bg=cpair(colors.gray,colors.lightGray)} + TextBox{parent=o_div,x=17,y=13,text="M"..db.energy_label,fg_bg=cpair(colors.lightGray,colors.black)} + + TextBox{parent=o_div,y=15,text="Generation Target",fg_bg=cpair(colors.lightGray,colors.black)} + local g_target = NumberField{parent=o_div,x=1,y=16,width=15,default=0,min=0,max_chars=16,align_right=true,fg_bg=cpair(colors.white,colors.gray),dis_fg_bg=cpair(colors.gray,colors.lightGray)} + TextBox{parent=o_div,x=17,y=16,text="k"..db.energy_label.."/t",fg_bg=cpair(colors.lightGray,colors.black)} + + b_target.register(f_ps, "process_burn_target", b_target.set_value) + c_target.register(f_ps, "process_charge_target", c_target.set_value) + g_target.register(f_ps, "process_gen_target", g_target.set_value) + + --#endregion + + --#region process control page + + local c_pane = panes[db.facility.num_units + 1] + local c_div = Div{parent=c_pane,x=2,width=main.get_width()-2} + + local proc_ctrl = app.new_page(nil, db.facility.num_units + 1) + + TextBox{parent=c_div,y=1,text="Process Control",alignment=ALIGN.CENTER} + + local u_stat = Rectangle{parent=c_div,border=border(1,colors.gray,true),thin=true,width=21,height=5,x=1,y=3,fg_bg=cpair(colors.black,colors.lightGray)} + local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",alignment=ALIGN.CENTER} + local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data...",height=2,alignment=ALIGN.CENTER,fg_bg=cpair(colors.gray,colors.lightGray)} + + stat_line_1.register(f_ps, "status_line_1", stat_line_1.set_value) + stat_line_2.register(f_ps, "status_line_2", stat_line_2.set_value) + + local function _start_auto() + local limits = {} + for i = 1, #rate_limits do limits[i] = rate_limits[i].get_value() end + + process.process_start(mode.get_value(), b_target.get_value(), db.energy_convert_to_fe(c_target.get_value()), + db.energy_convert_to_fe(g_target.get_value()), limits) + end + + local start = HazardButton{parent=c_div,x=2,y=9,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=_start_auto,timeout=3,fg_bg=hzd_fg_bg} + local stop = HazardButton{parent=c_div,x=13,y=9,text="STOP",accent=colors.red,dis_colors=dis_colors,callback=process.process_stop,timeout=3,fg_bg=hzd_fg_bg} + + db.facility.start_ack = start.on_response + db.facility.stop_ack = stop.on_response + + start.register(f_ps, "auto_ready", function (ready) + if ready and (not db.facility.auto_active) then start.enable() else start.disable() end + end) + + local auto_ready = IconIndicator{parent=c_div,y=14,label="Units Ready",states=grn_ind_s} + local auto_act = IconIndicator{parent=c_div,label="Process Active",states=grn_ind_s} + local auto_ramp = IconIndicator{parent=c_div,label="Process Ramping",states=wht_ind_s} + local auto_sat = IconIndicator{parent=c_div,label="Min/Max Burn Rate",states=yel_ind_s} + + auto_ready.register(f_ps, "auto_ready", auto_ready.update) + auto_act.register(f_ps, "auto_active", auto_act.update) + auto_ramp.register(f_ps, "auto_ramping", auto_ramp.update) + auto_sat.register(f_ps, "auto_saturated", auto_sat.update) + + -- REGISTER_NOTE: for optimization/brevity, due to not deleting anything but the whole element tree when it comes + -- to the process control display and coordinator GUI as a whole, child elements will not directly be registered here + -- (preventing garbage collection until the parent 'proc' is deleted) + page_div.register(f_ps, "auto_active", function (active) + if active then + b_target.disable() + c_target.disable() + g_target.disable() + + mode.disable() + start.disable() + + for i = 1, #rate_limits do rate_limits[i].disable() end + else + b_target.enable() + c_target.enable() + g_target.enable() + + mode.enable() + if db.facility.auto_ready then start.enable() end + + for i = 1, #rate_limits do rate_limits[i].enable() end + end + end) + + --#endregion + + --#region auto-SCRAM annunciator page + + local a_pane = panes[db.facility.num_units + 3] + local a_div = Div{parent=a_pane,x=2,width=main.get_width()-2} + + local annunc_page = app.new_page(nil, db.facility.num_units + 3) + + TextBox{parent=a_div,y=1,text="Automatic SCRAM",alignment=ALIGN.CENTER} + + local auto_scram = IconIndicator{parent=a_div,y=3,label="Automatic SCRAM",states=red_ind_s} + + TextBox{parent=a_div,y=5,text="Induction Matrix",fg_bg=cpair(colors.lightGray,colors.black)} + local matrix_dc = IconIndicator{parent=a_div,label="Disconnected",states=yel_ind_s} + local matrix_fill = IconIndicator{parent=a_div,label="Charge High",states=red_ind_s} + + TextBox{parent=a_div,y=9,text="Assigned Units",fg_bg=cpair(colors.lightGray,colors.black)} + local unit_crit = IconIndicator{parent=a_div,label="Critical Alarm",states=red_ind_s} + + TextBox{parent=a_div,y=12,text="Facility",fg_bg=cpair(colors.lightGray,colors.black)} + local fac_rad_h = IconIndicator{parent=a_div,label="Radiation High",states=red_ind_s} + + TextBox{parent=a_div,y=15,text="Generation Rate Mode",fg_bg=cpair(colors.lightGray,colors.black)} + local gen_fault = IconIndicator{parent=a_div,label="Control Fault",states=yel_ind_s} + + auto_scram.register(f_ps, "auto_scram", auto_scram.update) + matrix_dc.register(f_ps, "as_matrix_dc", matrix_dc.update) + matrix_fill.register(f_ps, "as_matrix_fill", matrix_fill.update) + unit_crit.register(f_ps, "as_crit_alarm", unit_crit.update) + fac_rad_h.register(f_ps, "as_radiation", fac_rad_h.update) + gen_fault.register(f_ps, "as_gen_fault", gen_fault.update) + + --#endregion -- setup multipane local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes} @@ -117,8 +297,9 @@ local function new_view(root) local list = { { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home }, - { label = " \x17 ", color = core.cpair(colors.black, colors.orange), callback = function () app.switcher(db.facility.num_units + 1) end }, - { label = "SET", color = core.cpair(colors.black, colors.orange), callback = function () app.switcher(db.facility.num_units + 1) end } + { label = " \x17 ", color = core.cpair(colors.black, colors.purple), callback = proc_ctrl.nav_to }, + { label = " \x13 ", color = core.cpair(colors.black, colors.red), callback = annunc_page.nav_to }, + { label = "OPT", color = core.cpair(colors.black, colors.yellow), callback = opt_page.nav_to } } for i = 1, db.facility.num_units do @@ -128,6 +309,7 @@ local function new_view(root) app.set_sidebar(list) -- done, show the app + proc_ctrl.nav_to() load_pane.set_value(2) end diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index 20774a7..1596485 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -12,6 +12,7 @@ local diag_apps = require("pocket.ui.apps.diag_apps") local dummy_app = require("pocket.ui.apps.dummy_app") local guide_app = require("pocket.ui.apps.guide") local loader_app = require("pocket.ui.apps.loader") +local process_app = require("pocket.ui.apps.process") local sys_apps = require("pocket.ui.apps.sys_apps") local unit_app = require("pocket.ui.apps.unit") @@ -64,6 +65,7 @@ local function init(main) home_page(page_div) unit_app(page_div) control_app(page_div) + process_app(page_div) guide_app(page_div) loader_app(page_div) sys_apps(page_div) diff --git a/pocket/ui/style.lua b/pocket/ui/style.lua index dc26755..4a06de7 100644 --- a/pocket/ui/style.lua +++ b/pocket/ui/style.lua @@ -73,6 +73,16 @@ states.yel_ind_s = { { color = cpair(colors.black, colors.yellow), symbol = "-" } } +states.grn_ind_s = { + { color = cpair(colors.black, colors.lightGray), symbol = "\x07" }, + { color = cpair(colors.black, colors.green), symbol = "+" } +} + +states.wht_ind_s = { + { color = cpair(colors.black, colors.lightGray), symbol = "\x07" }, + { color = cpair(colors.black, colors.white), symbol = "+" } +} + style.icon_states = states -- MAIN LAYOUT --