#232 WIP coordinator flow view

This commit is contained in:
Mikayla Fischler 2023-08-09 23:26:06 -04:00
parent 7bd8f34773
commit e0809f52a6
7 changed files with 492 additions and 82 deletions

View File

@ -55,6 +55,8 @@ function coordinator.configure_monitors(num_units)
local monitors = {
primary = nil,
primary_name = "",
flow = nil,
flow_name = "",
unit_displays = {},
unit_name_map = {}
}
@ -69,8 +71,8 @@ function coordinator.configure_monitors(num_units)
table.insert(available, iface)
end
-- we need a certain number of monitors (1 per unit + 1 primary display)
local num_displays_needed = num_units + 1
-- we need a certain number of monitors (1 per unit + 1 primary display + 1 flow display)
local num_displays_needed = num_units + 2
if #names < num_displays_needed then
local message = "not enough monitors connected (need " .. num_displays_needed .. ")"
println(message)
@ -83,10 +85,12 @@ function coordinator.configure_monitors(num_units)
log.warning("configure_monitors(): failed to load coordinator settings file (may not exist yet)")
else
local _primary = settings.get("PRIMARY_DISPLAY")
local _flow = settings.get("FLOW_DISPLAY")
local _unitd = settings.get("UNIT_DISPLAYS")
-- filter out already assigned monitors
util.filter_table(available, function (x) return x ~= _primary end)
util.filter_table(available, function (x) return x ~= _flow end)
if type(_unitd) == "table" then
util.filter_table(available, function (x) return not util.table_contains(_unitd, x) end)
end
@ -106,7 +110,6 @@ function coordinator.configure_monitors(num_units)
end
while iface_primary_display == nil and #available > 0 do
-- lets get a monitor
iface_primary_display = ask_monitor(available)
end
@ -118,6 +121,31 @@ function coordinator.configure_monitors(num_units)
monitors.primary = ppm.get_periph(iface_primary_display)
monitors.primary_name = iface_primary_display
--------------------------
-- FLOW MONITOR DISPLAY --
--------------------------
local iface_flow_display = settings.get("FLOW_DISPLAY") ---@type boolean|string|nil
if not util.table_contains(names, iface_flow_display) then
println("flow monitor display is not connected")
local response = dialog.ask_y_n("would you like to change it", true)
if response == false then return false end
iface_flow_display = nil
end
while iface_flow_display == nil and #available > 0 do
iface_flow_display = ask_monitor(available)
end
if type(iface_flow_display) ~= "string" then return false end
settings.set("FLOW_DISPLAY", iface_flow_display)
util.filter_table(available, function (x) return x ~= iface_flow_display end)
monitors.flow = ppm.get_periph(iface_flow_display)
monitors.flow_name = iface_flow_display
-------------------
-- UNIT DISPLAYS --
-------------------
@ -130,7 +158,6 @@ function coordinator.configure_monitors(num_units)
local display = nil
while display == nil and #available > 0 do
-- lets get a monitor
println("please select monitor for unit #" .. i)
display = ask_monitor(available)
end
@ -152,7 +179,6 @@ function coordinator.configure_monitors(num_units)
end
while display == nil and #available > 0 do
-- lets get a monitor
display = ask_monitor(available)
end

View File

@ -10,6 +10,7 @@ local iocontrol = require("coordinator.iocontrol")
local style = require("coordinator.ui.style")
local pgi = require("coordinator.ui.pgi")
local flow_view = require("coordinator.ui.layout.flow_view")
local panel_view = require("coordinator.ui.layout.front_panel")
local main_view = require("coordinator.ui.layout.main_view")
local unit_view = require("coordinator.ui.layout.unit_view")
@ -29,6 +30,7 @@ local engine = {
ui = {
front_panel = nil, ---@type graphics_element|nil
main_display = nil, ---@type graphics_element|nil
flow_display = nil, ---@type graphics_element|nil
unit_displays = {}
}
}
@ -60,8 +62,9 @@ end
-- init all displays in use by the renderer
function renderer.init_displays()
-- init primary monitor
-- init primary and flow monitors
_init_display(engine.monitors.primary)
_init_display(engine.monitors.flow)
-- init unit displays
for _, monitor in ipairs(engine.monitors.unit_displays) do
@ -169,6 +172,12 @@ function renderer.start_ui()
main_view(engine.ui.main_display)
end
-- show flow view on flow monitor
if engine.monitors.flow ~= nil then
engine.ui.flow_display = DisplayBox{window=engine.monitors.flow,fg_bg=style.root}
flow_view(engine.ui.flow_display)
end
-- show unit views on unit displays
for idx, display in pairs(engine.monitors.unit_displays) do
engine.ui.unit_displays[idx] = DisplayBox{window=display,fg_bg=style.root}
@ -192,6 +201,7 @@ function renderer.close_ui()
-- delete element trees
if engine.ui.main_display ~= nil then engine.ui.main_display.delete() end
if engine.ui.flow_display ~= nil then engine.ui.flow_display.delete() end
for _, display in pairs(engine.ui.unit_displays) do display.delete() end
-- report ui as not ready
@ -199,6 +209,7 @@ function renderer.close_ui()
-- clear root UI elements
engine.ui.main_display = nil
engine.ui.flow_display = nil
engine.ui.unit_displays = {}
-- clear unit monitors
@ -317,6 +328,8 @@ function renderer.handle_mouse(event)
elseif engine.ui_ready then
if event.monitor == engine.monitors.primary_name then
engine.ui.main_display.handle_mouse(event)
elseif event.monitor == engine.monitors.flow_name then
engine.ui.flow_display.handle_mouse(event)
else
for id, monitor in ipairs(engine.monitors.unit_name_map) do
if event.monitor == monitor then

View File

@ -22,7 +22,7 @@ local sounder = require("coordinator.sounder")
local apisessions = require("coordinator.session.apisessions")
local COORDINATOR_VERSION = "v0.21.2"
local COORDINATOR_VERSION = "v1.0.0"
local println = util.println
local println_ts = util.println_ts

View File

@ -0,0 +1,159 @@
--
-- Basic Unit Flow Overview
--
local util = require("scada-common.util")
local core = require("graphics.core")
local style = require("coordinator.ui.style")
local reactor_view = require("coordinator.ui.components.reactor")
local boiler_view = require("coordinator.ui.components.boiler")
local turbine_view = require("coordinator.ui.components.turbine")
local Div = require("graphics.elements.div")
local PipeNetwork = require("graphics.elements.pipenet")
local TextBox = require("graphics.elements.textbox")
local Rectangle = require("graphics.elements.rectangle")
local DataIndicator = require("graphics.elements.indicators.data")
local HorizontalBar = require("graphics.elements.indicators.hbar")
local StateIndicator = require("graphics.elements.indicators.state")
local IndicatorLight = require("graphics.elements.indicators.light")
local TriIndicatorLight = require("graphics.elements.indicators.trilight")
local VerticalBar = require("graphics.elements.indicators.vbar")
local cpair = core.cpair
local border = core.border
local TEXT_ALIGN = core.TEXT_ALIGN
local pipe = core.pipe
-- make a new unit overview window
---@param parent graphics_element parent
---@param x integer top left x
---@param y integer top left y
---@param unit ioctl_unit unit database entry
local function make(parent, x, y, unit)
local height = 16
local v_start = 1 + ((unit.unit_id - 1) * 4)
local v_names = {
util.sprintf("PV%02d-PU", v_start),
util.sprintf("PV%02d-PO", v_start + 1),
util.sprintf("PV%02d-PL", v_start + 2),
util.sprintf("PV%02d-AM", v_start + 3)
}
assert(parent.get_height() >= (y + height), "flow display not of sufficient vertical resolution (add an additional row of monitors) " .. y .. "," .. parent.get_height())
-- bounding box div
local root = Div{parent=parent,x=x,y=y,width=114,height=height}
local text_fg_bg = cpair(colors.black, colors.white)
local lu_col = cpair(colors.gray, colors.gray)
-------------
-- REACTOR --
-------------
local reactor = Rectangle{parent=root,x=1,y=1,border=border(1, colors.gray, true),width=19,height=5,fg_bg=cpair(colors.white,colors.gray)}
TextBox{parent=reactor,y=1,text="FISSION REACTOR",alignment=TEXT_ALIGN.CENTER,height=1}
TextBox{parent=reactor,y=3,text="UNIT #"..unit.unit_id,alignment=TEXT_ALIGN.CENTER,height=1}
TextBox{parent=root,x=19,y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=cpair(colors.lightGray,colors.gray)}
TextBox{parent=root,x=4,y=5,text="\x19",width=1,height=1,fg_bg=cpair(colors.lightGray,colors.gray)}
local rc_pipes = {}
table.insert(rc_pipes, pipe(0, 1, 19, 1, colors.lightBlue, true))
table.insert(rc_pipes, pipe(0, 3, 19, 3, colors.orange, true))
table.insert(rc_pipes, pipe(39, 1, 58, 1, colors.blue, true))
table.insert(rc_pipes, pipe(39, 3, 58, 3, colors.white, true))
table.insert(rc_pipes, pipe(78, 0, 83, 0, colors.white, true))
table.insert(rc_pipes, pipe(78, 2, 83, 2, colors.white, true))
table.insert(rc_pipes, pipe(78, 4, 83, 4, colors.white, true))
PipeNetwork{parent=root,x=20,y=1,pipes=rc_pipes,bg=colors.lightGray}
local hc_rate = DataIndicator{parent=root,x=22,y=3,lu_colors=lu_col,label="",unit="mB/t",format="%11.0f",value=287000000,commas=true,width=16,fg_bg=text_fg_bg}
local cc_rate = DataIndicator{parent=root,x=22,y=5,lu_colors=lu_col,label="",unit="mB/t",format="%11.0f",value=287000000,commas=true,width=16,fg_bg=text_fg_bg}
local boiler = Rectangle{parent=root,x=40,y=1,border=border(1, colors.gray, true),width=19,height=5,fg_bg=cpair(colors.white,colors.gray)}
TextBox{parent=boiler,y=1,text="THERMO-ELECTRIC",alignment=TEXT_ALIGN.CENTER,height=1}
TextBox{parent=boiler,y=3,text="BOILERS",alignment=TEXT_ALIGN.CENTER,height=1}
TextBox{parent=root,x=40,y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=cpair(colors.lightGray,colors.gray)}
TextBox{parent=root,x=58,y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=cpair(colors.lightGray,colors.gray)}
local wt_rate = DataIndicator{parent=root,x=61,y=3,lu_colors=lu_col,label="",unit="mB/t",format="%11.0f",value=287000000,commas=true,width=16,fg_bg=text_fg_bg}
local st_rate = DataIndicator{parent=root,x=61,y=5,lu_colors=lu_col,label="",unit="mB/t",format="%11.0f",value=287000000,commas=true,width=16,fg_bg=text_fg_bg}
local turbine = Rectangle{parent=root,x=79,y=1,border=border(1, colors.gray, true),width=19,height=5,fg_bg=cpair(colors.white,colors.gray)}
TextBox{parent=turbine,y=1,text="STEAM TURBINE",alignment=TEXT_ALIGN.CENTER,height=1}
TextBox{parent=turbine,y=3,text="GENERATORS",alignment=TEXT_ALIGN.CENTER,height=1}
TextBox{parent=root,x=79,y=2,text="\x1a \x80 \x1b",width=1,height=3,fg_bg=cpair(colors.lightGray,colors.gray)}
TextBox{parent=root,x=101,y=3,text="\x10\x11",fg_bg=cpair(colors.black,colors.lightGray),width=2,height=1}
TextBox{parent=root,x=103,y=3,text="\x7f",fg_bg=cpair(colors.white,colors.lightGray),width=1,height=1}
local conn = TriIndicatorLight{parent=root,x=106,y=1,label="PRV01",c1=colors.gray,c2=colors.yellow,c3=colors.red}
local conn = TriIndicatorLight{parent=root,x=106,y=3,label="PRV02",c1=colors.gray,c2=colors.yellow,c3=colors.red}
local conn = TriIndicatorLight{parent=root,x=106,y=5,label="PRV03",c1=colors.gray,c2=colors.yellow,c3=colors.red}
local waste = Div{parent=root,x=3,y=6}
local waste_pipes = {
pipe(0, 0, 16, 1, colors.brown, true),
pipe(12, 1, 16, 5, colors.brown, true),
pipe(18, 1, 44, 1, colors.brown, true),
pipe(18, 5, 23, 5, colors.brown, true),
pipe(52, 1, 80, 1, colors.green, true),
pipe(42, 4, 60, 4, colors.cyan, true),
pipe(56, 4, 60, 8, colors.cyan, true),
pipe(62, 4, 80, 4, colors.cyan, true),
pipe(62, 8, 110, 8, colors.cyan, true),
pipe(93, 1, 94, 3, colors.black, true, true),
pipe(93, 4, 109, 6, colors.black, true, true),
pipe(109, 6, 107, 6, colors.black, true, true)
}
PipeNetwork{parent=waste,x=2,y=1,pipes=waste_pipes,bg=colors.lightGray}
local function _valve(vx, vy, n)
TextBox{parent=waste,x=vx,y=vy,text="\x10\x11",fg_bg=cpair(colors.black,colors.lightGray),width=2,height=1}
local conn = IndicatorLight{parent=waste,x=vx-3,y=vy+1,label=v_names[n],colors=cpair(colors.green,colors.gray)}
local state = IndicatorLight{parent=waste,x=vx-3,y=vy+2,label="STATE",colors=cpair(colors.white,colors.white)}
end
local function _machine(mx, my, name)
local l = string.len(name) + 2
TextBox{parent=waste,x=mx,y=my,text=util.strrep("\x8f",l),alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.lightGray,colors.gray),width=l,height=1}
TextBox{parent=waste,x=mx,y=my+1,text=name,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.white,colors.gray),width=l,height=1}
end
local waste_rate = DataIndicator{parent=waste,x=1,y=3,lu_colors=lu_col,label="",unit="mB/t",format="%7.2f",value=1234.56,width=12,fg_bg=text_fg_bg}
local pu_rate = DataIndicator{parent=waste,x=70,y=3,lu_colors=lu_col,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=text_fg_bg}
local po_rate = DataIndicator{parent=waste,x=45,y=6,lu_colors=lu_col,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=text_fg_bg}
local popl_rate = DataIndicator{parent=waste,x=70,y=6,lu_colors=lu_col,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=text_fg_bg}
local poam_rate = DataIndicator{parent=waste,x=70,y=10,lu_colors=lu_col,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=text_fg_bg}
local spent_rate = DataIndicator{parent=waste,x=99,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=text_fg_bg}
_valve(18, 2, 1); _valve(18, 6, 2); _valve(62, 5, 3); _valve(62, 9, 4)
_machine(45, 1, "CENTRIFUGE \x1a"); _machine(83, 1, "PRC [Pu] \x1a"); _machine(83, 4, "PRC [Po] \x1a"); _machine(94, 6, "SPENT WASTE \x1b")
TextBox{parent=waste,x=25,y=3,text="SNAs [Po]",alignment=TEXT_ALIGN.CENTER,width=19,height=1,fg_bg=cpair(colors.white,colors.gray)}
local sna_po = Rectangle{parent=waste,x=25,y=4,border=border(1, colors.gray, true),width=19,height=7,thin=true,fg_bg=cpair(colors.black,colors.white)}
local sna_act = IndicatorLight{parent=sna_po,label="ACTIVE",colors=cpair(colors.green,colors.red)}
local sna_cnt = DataIndicator{parent=sna_po,x=12,y=1,lu_colors=lu_col,label="CNT",unit="",format="%2d",value=99,width=7,fg_bg=text_fg_bg}
local sna_pk = DataIndicator{parent=sna_po,y=3,lu_colors=lu_col,label="PEAK",unit="mB/t",format="%7.2f",value=1000,width=17,fg_bg=text_fg_bg}
local sna_max = DataIndicator{parent=sna_po,lu_colors=lu_col,label="MAX ",unit="mB/t",format="%7.2f",value=1000,width=17,fg_bg=text_fg_bg}
local sna_in = DataIndicator{parent=sna_po,lu_colors=lu_col,label="IN ",unit="mB/t",format="%7.2f",value=1000,width=17,fg_bg=text_fg_bg}
return root
end
return make

View File

@ -0,0 +1,41 @@
--
-- Flow Monitor GUI
--
local util = require("scada-common.util")
local iocontrol = require("coordinator.iocontrol")
local style = require("coordinator.ui.style")
local flow_overview = require("coordinator.ui.components.flow_overview")
local core = require("graphics.core")
local TextBox = require("graphics.elements.textbox")
local DataIndicator = require("graphics.elements.indicators.data")
local TEXT_ALIGN = core.TEXT_ALIGN
local cpair = core.cpair
-- create new flow view
---@param main graphics_element main displaybox
local function init(main)
local facility = iocontrol.get_db().facility
local units = iocontrol.get_db().units
-- window header message
local header = TextBox{parent=main,y=1,text="Facility Coolant and Waste Flow Monitor",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header}
-- max length example: "01:23:45 AM - Wednesday, September 28 2022"
local datetime = TextBox{parent=main,x=(header.get_width()-42),y=1,text="",alignment=TEXT_ALIGN.RIGHT,width=42,height=1,fg_bg=style.header}
datetime.register(facility.ps, "date_time", datetime.set_value)
for i = 1, 4 do
flow_overview(main, 25, 5 + ((i - 1) * 20), units[i])
end
end
return init

View File

@ -7,7 +7,7 @@ local flasher = require("graphics.flasher")
local core = {}
core.version = "1.0.2"
core.version = "1.0.3"
core.flasher = flasher
core.events = events

View File

@ -1,6 +1,7 @@
-- Pipe Graphics Element
local util = require("scada-common.util")
local log = require("scada-common.log")
local core = require("graphics.core")
local element = require("graphics.element")
@ -14,6 +15,12 @@ local element = require("graphics.element")
---@field y? integer auto incremented if omitted
---@field hidden? boolean true to hide on initial draw
---@class _pipe_map_entry
---@field atr boolean align top right (or bottom left for false)
---@field thin boolean thin pipe or not
---@field fg string foreground blit
---@field bg string background blit
-- new pipe network
---@param args pipenet_args
---@return graphics_element element, element_id id
@ -44,6 +51,14 @@ local function pipenet(args)
-- create new graphics element base object
local e = element.new(args)
-- determine if there are any thin pipes involved
local any_thin = false
for p = 1, #args.pipes do
any_thin = args.pipes[p].thin
if any_thin then break end
end
if not any_thin then
-- draw all pipes
for p = 1, #args.pipes do
local pipe = args.pipes[p] ---@type pipe
@ -54,6 +69,11 @@ local function pipenet(args)
local x_step = util.trinary(pipe.x1 >= pipe.x2, -1, 1)
local y_step = util.trinary(pipe.y1 >= pipe.y2, -1, 1)
if pipe.thin then
x_step = util.trinary(pipe.x1 == pipe.x2, 0, x_step)
y_step = util.trinary(pipe.y1 == pipe.y2, 0, y_step)
end
e.window.setCursorPos(x, y)
local c = core.cpair(pipe.color, e.fg_bg.bkg)
@ -106,8 +126,10 @@ local function pipenet(args)
-- corner
if y_step < 0 then
e.window.blit("\x97", c.blit_bkg, c.blit_fgd)
else
elseif y_step > 0 then
e.window.blit("\x8d", c.blit_fgd, c.blit_bkg)
else
e.window.blit("\x8c", c.blit_fgd, c.blit_bkg)
end
else
e.window.blit("\x95", c.blit_fgd, c.blit_bkg)
@ -139,7 +161,156 @@ local function pipenet(args)
end
end
end
end
else
-- build map if using thin pipes, easist way to check adjacent blocks (cannot 'cheat' like with standard width)
local map = {}
-- allocate map
for x = 1, args.width do
table.insert(map, {})
for _ = 1, args.height do table.insert(map[x], false) end
end
-- build map
for p = 1, #args.pipes do
local pipe = args.pipes[p] ---@type pipe
local x = 1 + pipe.x1
local y = 1 + pipe.y1
local x_step = util.trinary(pipe.x1 >= pipe.x2, -1, 1)
local y_step = util.trinary(pipe.y1 >= pipe.y2, -1, 1)
local entry = { atr = pipe.align_tr, thin = pipe.thin, fg = colors.toBlit(pipe.color), bg = e.fg_bg.blit_bkg }
if pipe.align_tr then
-- cross width then height
for _ = 1, pipe.w do
map[x][y] = entry
x = x + x_step
end
x = x - x_step -- back up one
for _ = 1, pipe.h do
map[x][y] = entry
y = y + y_step
end
else
-- cross height then width
for _ = 1, pipe.h do
map[x][y] = entry
y = y + y_step
end
y = y - y_step -- back up one
for _ = 1, pipe.w do
map[x][y] = entry
x = x + x_step
end
end
end
-- for x = 1, args.width do
-- for y = 1, args.height do
-- local entry = map[x][y] ---@type _pipe_map_entry|false
-- if entry == false then
-- e.window.setCursorPos(x, y)
-- e.window.blit("x", "f", "e")
-- end
-- end
-- end
-- render
for x = 1, args.width do
for y = 1, args.height do
local entry = map[x][y] ---@type _pipe_map_entry|false
local char = ""
local invert = false
if entry ~= false then
local function check(cx, cy)
return (map[cx] ~= nil) and (map[cx][cy] ~= nil) and (map[cx][cy] ~= false) and (map[cx][cy].fg == entry.fg)
end
if entry.thin then
if check(x - 1, y) then -- if left
if check(x, y - 1) then -- if above
if check(x + 1, y) then -- if right
if check(x, y + 1) then -- if below
char = util.trinary(entry.atr, "\x91", "\x9d")
invert = entry.atr
else -- not below
char = util.trinary(entry.atr, "\x8e", "\x8d")
end
else -- not right
if check(x, y + 1) then -- if below
char = util.trinary(entry.atr, "\x91", "\x95")
invert = entry.atr
else -- not below
char = util.trinary(entry.atr, "\x8e", "\x85")
end
end
elseif check(x, y + 1) then-- not above, if below
if check(x + 1, y) then -- if right
char = util.trinary(entry.atr, "\x93", "\x9c")
invert = entry.atr
else -- not right
char = util.trinary(entry.atr, "\x93", "\x94")
invert = entry.atr
end
else -- not above, not below
char = "\x8c"
end
elseif check(x + 1, y) then -- not left, if right
if check(x, y - 1) then -- if above
if check(x, y + 1) then -- if below
char = util.trinary(entry.atr, "\x95", "\x9d")
invert = entry.atr
else -- not below
char = util.trinary(entry.atr, "\x8a", "\x8d")
end
else -- not above
if check(x, y + 1) then -- if below
char = util.trinary(entry.atr, "\x97", "\x9c")
invert = entry.atr
else -- not below
char = "\x8c"
end
end
else -- not left, not right
char = "\x95"
invert = entry.atr
end
else
if check(x, y - 1) then -- above
-- not below and (if left or right)
if (not check(x, y + 1)) and (check(x - 1, y) or check(x + 1, y)) then
char = util.trinary(entry.atr, "\x8f", "\x83")
invert = not entry.atr
else -- not above w/ sides only
char = " "
invert = true
end
elseif check(x, y + 1) then -- not above, if below
char = util.trinary(entry.atr, "\x8f", "\x83")
invert = not entry.atr
else -- not above, not below
end
end
e.window.setCursorPos(x, y)
if invert then
e.window.blit(char, entry.bg, entry.fg)
else
e.window.blit(char, entry.fg, entry.bg)
end
end
end
end
end
return e.complete()