From 1a40321c0ff45e3b34a647d5b07d3756f77b4dc9 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 22 Dec 2023 16:12:47 +0000 Subject: [PATCH 01/41] #395 pocket navigation system --- pocket/iocontrol.lua | 76 ++++++++++++++++++++++---------- pocket/pocket.lua | 1 - pocket/startup.lua | 11 ++--- pocket/ui/main.lua | 71 +++++++---------------------- pocket/ui/pages/boiler_page.lua | 16 ++++--- pocket/ui/pages/diag_page.lua | 26 +++++------ pocket/ui/pages/home_page.lua | 16 +++++-- pocket/ui/pages/reactor_page.lua | 16 ++++--- pocket/ui/pages/turbine_page.lua | 16 ++++--- pocket/ui/pages/unit_page.lua | 16 ++++--- 10 files changed, 140 insertions(+), 125 deletions(-) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 30645ae..687181d 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -23,19 +23,16 @@ local LINK_STATE = { LINKED = 3 } ----@enum NAV_PAGE -local NAV_PAGE = { - HOME = 1, - UNITS = 2, - REACTORS = 3, - BOILERS = 4, - TURBINES = 5, - DIAG = 6, - D_ALARMS = 7 -} - iocontrol.LINK_STATE = LINK_STATE -iocontrol.NAV_PAGE = NAV_PAGE + +---@class nav_tree_node +---@field _p nav_tree_node|nil page's parent +---@field _c table page's children +---@field pane_elem graphics_element|nil multipane for this branch +---@field pane_id integer this page's ID in it's contained pane +---@field switcher function|nil function to switch this page's active multipane +---@field nav_to function function to navigate to this page +---@field tasks table tasks to run on this page -- initialize facility-independent components of pocket iocontrol ---@param comms pocket_comms @@ -76,21 +73,54 @@ function iocontrol.init_core(comms) alarm_buttons = {}, tone_indicators = {} -- indicators to update from supervisor tone states } +end - ---@class pocket_nav - io.nav = { - page = NAV_PAGE.HOME, ---@type NAV_PAGE - sub_pages = { NAV_PAGE.HOME, NAV_PAGE.UNITS, NAV_PAGE.REACTORS, NAV_PAGE.BOILERS, NAV_PAGE.TURBINES, NAV_PAGE.DIAG }, - tasks = {} +-- initialize the page navigation tree +function iocontrol.init_nav(root_pane) + local self = { + root = { _p = nil, _c = {}, pane_id = 0, pane_elem = root_pane, nav_to = function () end, tasks = {} }, ---@type nav_tree_node + cur_page = nil ---@type nav_tree_node } - -- add a task to be performed periodically while on a given page - ---@param page NAV_PAGE page to add task to - ---@param task function function to execute - function io.nav.register_task(page, task) - if io.nav.tasks[page] == nil then io.nav.tasks[page] = {} end - table.insert(io.nav.tasks[page], task) + function self.root.switcher(pane_id) + if self.root._c[pane_id] then self.root._c[pane_id].nav_to() end end + + ---@class pocket_nav + io.nav = {} + + -- create a new page entry in the page navigation tree + ---@param parent nav_tree_node? a parent page or nil to use the root + ---@param pane_id integer the pane number for this page in it's parent's multipane + ---@param pane graphics_element? this page's multipane, if it has children + ---@return nav_tree_node new_page this new page + function io.nav.new_page(parent, pane_id, pane) + local page = { _p = parent or self.root, _c = {}, pane_id = pane_id, pane_elem = pane, tasks = {} } + page._p._c[pane_id] = page + + function page.nav_to() + if page._p.pane_elem then page._p.pane_elem.set_value(page.pane_id) end + self.cur_page = page + end + + if pane then + function page.switcher() if page._c[pane_id] then page._c[pane_id].nav_to() end end + end + + return page + end + + -- get the currently active page + function io.nav.get_current_page() return self.cur_page end + + -- attempt to navigate up the tree + function io.nav.nav_up() + local parent = self.cur_page._p + -- if a parent is defined and this element is not root + if parent and parent.pane_id ~= 0 then self.cur_page = parent end + end + + return self.root end -- initialize facility-dependent components of pocket iocontrol diff --git a/pocket/pocket.lua b/pocket/pocket.lua index b2da51d..1005c2a 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -460,5 +460,4 @@ function pocket.comms(version, nic, pkt_channel, svr_channel, crd_channel, range return public end - return pocket diff --git a/pocket/startup.lua b/pocket/startup.lua index 4929955..073db2f 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -18,7 +18,7 @@ local iocontrol = require("pocket.iocontrol") local pocket = require("pocket.pocket") local renderer = require("pocket.renderer") -local POCKET_VERSION = "v0.6.3-alpha" +local POCKET_VERSION = "v0.6.4-alpha" local println = util.println local println_ts = util.println_ts @@ -128,7 +128,7 @@ local function main() -- start connection watchdogs conn_wd.sv.feed() conn_wd.api.feed() - log.debug("startup> conn watchdog started") + log.debug("startup> conn watchdogs started") local io_db = iocontrol.get_db() local nav = io_db.nav @@ -146,11 +146,8 @@ local function main() pocket_comms.link_update() -- update any tasks for the active page - if (type(nav.tasks[nav.page]) == "table") then - for i = 1, #nav.tasks[nav.page] do - nav.tasks[nav.page][i]() - end - end + local page_tasks = nav.get_current_page().tasks + for i = 1, #page_tasks do page_tasks[i]() end loop_clock.start() elseif conn_wd.sv.is_timer(param1) then diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index 4cd70c6..1d1c2ef 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -24,7 +24,6 @@ local TextBox = require("graphics.elements.textbox") local Sidebar = require("graphics.elements.controls.sidebar") local LINK_STATE = iocontrol.LINK_STATE -local NAV_PAGE = iocontrol.NAV_PAGE local ALIGN = core.ALIGN @@ -33,24 +32,20 @@ local cpair = core.cpair -- create new main view ---@param main graphics_element main displaybox local function init(main) - local nav = iocontrol.get_db().nav - local ps = iocontrol.get_db().ps + local ps = iocontrol.get_db().ps -- window header message TextBox{parent=main,y=1,text="",alignment=ALIGN.LEFT,height=1,fg_bg=style.header} - -- - -- root panel panes (connection screens + main screen) - -- + --#region root panel panes (connection screens + main screen) local root_pane_div = Div{parent=main,x=1,y=2} local conn_sv_wait = conn_waiting(root_pane_div, 6, false) local conn_api_wait = conn_waiting(root_pane_div, 6, true) local main_pane = Div{parent=main,x=1,y=2} - local root_panes = { conn_sv_wait, conn_api_wait, main_pane } - local root_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes=root_panes} + local root_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={conn_sv_wait,conn_api_wait,main_pane}} root_pane.register(ps, "link_state", function (state) if state == LINK_STATE.UNLINKED or state == LINK_STATE.API_LINK_ONLY then @@ -62,62 +57,28 @@ local function init(main) end end) - -- - -- main page panel panes & sidebar - -- + --#endregion + + --#region main page panel panes & sidebar local page_div = Div{parent=main_pane,x=4,y=1} local sidebar_tabs = { - { - char = "#", - color = cpair(colors.black,colors.green) - }, - { - char = "U", - color = cpair(colors.black,colors.yellow) - }, - { - char = "R", - color = cpair(colors.black,colors.cyan) - }, - { - char = "B", - color = cpair(colors.black,colors.lightGray) - }, - { - char = "T", - color = cpair(colors.black,colors.white) - }, - { - char = "D", - color = cpair(colors.black,colors.orange) - } + { char = "#", color = cpair(colors.black,colors.green) }, + { char = "U", color = cpair(colors.black,colors.yellow) }, + { char = "R", color = cpair(colors.black,colors.cyan) }, + { char = "B", color = cpair(colors.black,colors.lightGray) }, + { char = "T", color = cpair(colors.black,colors.white) }, + { char = "D", color = cpair(colors.black,colors.orange) } } - local panes = { home_page(page_div), unit_page(page_div), reactor_page(page_div), boiler_page(page_div), turbine_page(page_div), diag_page(page_div) } + local page_pane = MultiPane{parent=page_div,x=1,y=1,panes={home_page(page_div),unit_page(page_div),reactor_page(page_div),boiler_page(page_div),turbine_page(page_div),diag_page(page_div)}} - local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes} + local base = iocontrol.init_nav(page_pane) - local function navigate_sidebar(page) - if page == 1 then - nav.page = nav.sub_pages[NAV_PAGE.HOME] - elseif page == 2 then - nav.page = nav.sub_pages[NAV_PAGE.UNITS] - elseif page == 3 then - nav.page = nav.sub_pages[NAV_PAGE.REACTORS] - elseif page == 4 then - nav.page = nav.sub_pages[NAV_PAGE.BOILERS] - elseif page == 5 then - nav.page = nav.sub_pages[NAV_PAGE.TURBINES] - elseif page == 6 then - nav.page = nav.sub_pages[NAV_PAGE.DIAG] - end + Sidebar{parent=main_pane,x=1,y=1,tabs=sidebar_tabs,fg_bg=cpair(colors.white,colors.gray),callback=base.switcher} - page_pane.set_value(page) - end - - Sidebar{parent=main_pane,x=1,y=1,tabs=sidebar_tabs,fg_bg=cpair(colors.white,colors.gray),callback=navigate_sidebar} + --#endregion end return init diff --git a/pocket/ui/pages/boiler_page.lua b/pocket/ui/pages/boiler_page.lua index ff92b05..c374fdf 100644 --- a/pocket/ui/pages/boiler_page.lua +++ b/pocket/ui/pages/boiler_page.lua @@ -1,17 +1,23 @@ --- local style = require("pocket.ui.style") +-- +-- Boiler Detail Page +-- -local core = require("graphics.core") +local iocontrol = require("pocket.iocontrol") -local Div = require("graphics.elements.div") -local TextBox = require("graphics.elements.textbox") +local core = require("graphics.core") --- local cpair = core.cpair +local Div = require("graphics.elements.div") +local TextBox = require("graphics.elements.textbox") local ALIGN = core.ALIGN -- new boiler page view ---@param root graphics_element parent local function new_view(root) + local db = iocontrol.get_db() + + db.nav.new_page(nil, 4) + local main = Div{parent=root,x=1,y=1} TextBox{parent=main,text="BOILERS",x=1,y=1,height=1,alignment=ALIGN.CENTER} diff --git a/pocket/ui/pages/diag_page.lua b/pocket/ui/pages/diag_page.lua index c28aba9..495fd14 100644 --- a/pocket/ui/pages/diag_page.lua +++ b/pocket/ui/pages/diag_page.lua @@ -1,3 +1,7 @@ +-- +-- Diagnostic Tools +-- + local iocontrol = require("pocket.iocontrol") local core = require("graphics.core") @@ -15,8 +19,6 @@ local SwitchButton = require("graphics.elements.controls.switch_button") local cpair = core.cpair -local NAV_PAGE = iocontrol.NAV_PAGE - local ALIGN = core.ALIGN -- new diagnostics page view @@ -36,23 +38,15 @@ local function new_view(root) local page_pane = MultiPane{parent=main,x=1,y=1,panes=panes} - local function navigate_diag() - page_pane.set_value(1) - db.nav.page = NAV_PAGE.DIAG - db.nav.sub_pages[NAV_PAGE.DIAG] = NAV_PAGE.DIAG - end - - local function navigate_alarm() - page_pane.set_value(2) - db.nav.page = NAV_PAGE.D_ALARMS - db.nav.sub_pages[NAV_PAGE.DIAG] = NAV_PAGE.D_ALARMS - end + local npage_diag = db.nav.new_page(nil, 6, page_pane) + local npage_home = db.nav.new_page(npage_diag, 1) + local npage_alarm = db.nav.new_page(npage_diag, 2) ------------------------ -- Alarm Testing Page -- ------------------------ - db.nav.register_task(NAV_PAGE.D_ALARMS, db.diag.tone_test.get_tone_states) + table.insert(npage_alarm.tasks, db.diag.tone_test.get_tone_states) local ttest = db.diag.tone_test @@ -67,7 +61,7 @@ local function new_view(root) ttest.ready_warn = TextBox{parent=audio,y=2,text="",height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.yellow,colors.black)} - PushButton{parent=audio,x=13,y=18,text="\x11 BACK",min_width=8,fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=c_wht_gray,callback=navigate_diag} + PushButton{parent=audio,x=13,y=18,text="\x11 BACK",min_width=8,fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=c_wht_gray,callback=npage_home.nav_to} local tones = Div{parent=audio,x=2,y=3,height=10,width=8,fg_bg=cpair(colors.black,colors.yellow)} @@ -137,7 +131,7 @@ local function new_view(root) -- App List -- -------------- - App{parent=diag_home,x=3,y=4,text="\x0f",title="Alarm",callback=navigate_alarm,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} + App{parent=diag_home,x=3,y=4,text="\x0f",title="Alarm",callback=npage_alarm.nav_to,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} App{parent=diag_home,x=10,y=4,text="\x1e",title="LoopT",callback=function()end,app_fg_bg=cpair(colors.black,colors.cyan)} App{parent=diag_home,x=17,y=4,text="@",title="Comps",callback=function()end,app_fg_bg=cpair(colors.black,colors.orange)} diff --git a/pocket/ui/pages/home_page.lua b/pocket/ui/pages/home_page.lua index d192796..bf1bb4c 100644 --- a/pocket/ui/pages/home_page.lua +++ b/pocket/ui/pages/home_page.lua @@ -1,14 +1,24 @@ -local core = require("graphics.core") +-- +-- Main Home Page +-- -local Div = require("graphics.elements.div") +local iocontrol = require("pocket.iocontrol") -local App = require("graphics.elements.controls.app") +local core = require("graphics.core") + +local Div = require("graphics.elements.div") + +local App = require("graphics.elements.controls.app") local cpair = core.cpair -- new home page view ---@param root graphics_element parent local function new_view(root) + local db = iocontrol.get_db() + + db.nav.new_page(nil, 1) + local main = Div{parent=root,x=1,y=1} App{parent=main,x=3,y=2,text="\x17",title="PRC",callback=function()end,app_fg_bg=cpair(colors.black,colors.purple)} diff --git a/pocket/ui/pages/reactor_page.lua b/pocket/ui/pages/reactor_page.lua index ae11436..95d5b9e 100644 --- a/pocket/ui/pages/reactor_page.lua +++ b/pocket/ui/pages/reactor_page.lua @@ -1,17 +1,23 @@ --- local style = require("pocket.ui.style") +-- +-- Reactor Detail Page +-- -local core = require("graphics.core") +local iocontrol = require("pocket.iocontrol") -local Div = require("graphics.elements.div") -local TextBox = require("graphics.elements.textbox") +local core = require("graphics.core") --- local cpair = core.cpair +local Div = require("graphics.elements.div") +local TextBox = require("graphics.elements.textbox") local ALIGN = core.ALIGN -- new reactor page view ---@param root graphics_element parent local function new_view(root) + local db = iocontrol.get_db() + + db.nav.new_page(nil, 3) + local main = Div{parent=root,x=1,y=1} TextBox{parent=main,text="REACTOR",x=1,y=1,height=1,alignment=ALIGN.CENTER} diff --git a/pocket/ui/pages/turbine_page.lua b/pocket/ui/pages/turbine_page.lua index 41d656c..266653c 100644 --- a/pocket/ui/pages/turbine_page.lua +++ b/pocket/ui/pages/turbine_page.lua @@ -1,17 +1,23 @@ --- local style = require("pocket.ui.style") +-- +-- Turbine Detail Page +-- -local core = require("graphics.core") +local iocontrol = require("pocket.iocontrol") -local Div = require("graphics.elements.div") -local TextBox = require("graphics.elements.textbox") +local core = require("graphics.core") --- local cpair = core.cpair +local Div = require("graphics.elements.div") +local TextBox = require("graphics.elements.textbox") local ALIGN = core.ALIGN -- new turbine page view ---@param root graphics_element parent local function new_view(root) + local db = iocontrol.get_db() + + db.nav.new_page(nil, 5) + local main = Div{parent=root,x=1,y=1} TextBox{parent=main,text="TURBINES",x=1,y=1,height=1,alignment=ALIGN.CENTER} diff --git a/pocket/ui/pages/unit_page.lua b/pocket/ui/pages/unit_page.lua index de61e81..72b4da2 100644 --- a/pocket/ui/pages/unit_page.lua +++ b/pocket/ui/pages/unit_page.lua @@ -1,17 +1,23 @@ --- local style = require("pocket.ui.style") +-- +-- Unit Overview Page +-- -local core = require("graphics.core") +local iocontrol = require("pocket.iocontrol") -local Div = require("graphics.elements.div") -local TextBox = require("graphics.elements.textbox") +local core = require("graphics.core") --- local cpair = core.cpair +local Div = require("graphics.elements.div") +local TextBox = require("graphics.elements.textbox") local ALIGN = core.ALIGN -- new unit page view ---@param root graphics_element parent local function new_view(root) + local db = iocontrol.get_db() + + db.nav.new_page(nil, 2) + local main = Div{parent=root,x=1,y=1} TextBox{parent=main,text="UNITS",x=1,y=1,height=1,alignment=ALIGN.CENTER} From c624baed2cbfecaf8dbcc7b45db435876f065f9e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 22 Dec 2023 12:46:36 -0500 Subject: [PATCH 02/41] #395 fixes to new pocket navigation --- pocket/iocontrol.lua | 109 ++++++++++++++++++++++++------------------- 1 file changed, 61 insertions(+), 48 deletions(-) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 687181d..e5f8273 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -12,6 +12,7 @@ local iocontrol = {} ---@class pocket_ioctl local io = { + nav_root = nil, ---@type nav_tree_node ps = psil.create() } @@ -34,9 +35,69 @@ iocontrol.LINK_STATE = LINK_STATE ---@field nav_to function function to navigate to this page ---@field tasks table tasks to run on this page +-- allocate the page navigation tree system
+-- navigation is not ready until init_nav has been called +function iocontrol.alloc_nav() + local self = { + root = { _p = nil, _c = {}, pane_id = 0, pane_elem = nil, nav_to = function () end, tasks = {} }, ---@type nav_tree_node + cur_page = nil ---@type nav_tree_node + } + + function self.root.switcher(pane_id) + if self.root._c[pane_id] then self.root._c[pane_id].nav_to() end + end + + self.cur_page = self.root + + ---@class pocket_nav + io.nav = {} + + -- create a new page entry in the page navigation tree + ---@param parent nav_tree_node? a parent page or nil to use the root + ---@param pane_id integer the pane number for this page in it's parent's multipane + ---@param pane graphics_element? this page's multipane, if it has children + ---@return nav_tree_node new_page this new page + function io.nav.new_page(parent, pane_id, pane) + local page = { _p = parent or self.root, _c = {}, pane_id = pane_id, pane_elem = pane, tasks = {} } + page._p._c[pane_id] = page + + function page.nav_to() + if page._p.pane_elem then page._p.pane_elem.set_value(page.pane_id) end + self.cur_page = page + end + + if pane then + function page.switcher() if page._c[pane_id] then page._c[pane_id].nav_to() end end + end + + return page + end + + -- get the currently active page + function io.nav.get_current_page() return self.cur_page end + + -- attempt to navigate up the tree + function io.nav.nav_up() + local parent = self.cur_page._p + -- if a parent is defined and this element is not root + if parent and parent.pane_id ~= 0 then self.cur_page = parent end + end + + io.nav_root = self.root +end + +-- complete initialization of navigation by providing the root muiltipane +---@param root_pane graphics_element navigation root multipane +function iocontrol.init_nav(root_pane) + io.nav_root.pane_elem = root_pane + return io.nav_root +end + -- initialize facility-independent components of pocket iocontrol ---@param comms pocket_comms function iocontrol.init_core(comms) + iocontrol.alloc_nav() + ---@class pocket_ioctl_diag io.diag = {} @@ -75,54 +136,6 @@ function iocontrol.init_core(comms) } end --- initialize the page navigation tree -function iocontrol.init_nav(root_pane) - local self = { - root = { _p = nil, _c = {}, pane_id = 0, pane_elem = root_pane, nav_to = function () end, tasks = {} }, ---@type nav_tree_node - cur_page = nil ---@type nav_tree_node - } - - function self.root.switcher(pane_id) - if self.root._c[pane_id] then self.root._c[pane_id].nav_to() end - end - - ---@class pocket_nav - io.nav = {} - - -- create a new page entry in the page navigation tree - ---@param parent nav_tree_node? a parent page or nil to use the root - ---@param pane_id integer the pane number for this page in it's parent's multipane - ---@param pane graphics_element? this page's multipane, if it has children - ---@return nav_tree_node new_page this new page - function io.nav.new_page(parent, pane_id, pane) - local page = { _p = parent or self.root, _c = {}, pane_id = pane_id, pane_elem = pane, tasks = {} } - page._p._c[pane_id] = page - - function page.nav_to() - if page._p.pane_elem then page._p.pane_elem.set_value(page.pane_id) end - self.cur_page = page - end - - if pane then - function page.switcher() if page._c[pane_id] then page._c[pane_id].nav_to() end end - end - - return page - end - - -- get the currently active page - function io.nav.get_current_page() return self.cur_page end - - -- attempt to navigate up the tree - function io.nav.nav_up() - local parent = self.cur_page._p - -- if a parent is defined and this element is not root - if parent and parent.pane_id ~= 0 then self.cur_page = parent end - end - - return self.root -end - -- initialize facility-dependent components of pocket iocontrol function iocontrol.init_fac() end From 0395aa95b651f5a9b153391c1ecd1a7423734683 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 22 Dec 2023 22:30:22 -0500 Subject: [PATCH 03/41] indicate pocket app is a work in progress --- pocket/ui/main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index 1d1c2ef..d42bc60 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -35,7 +35,7 @@ local function init(main) local ps = iocontrol.get_db().ps -- window header message - TextBox{parent=main,y=1,text="",alignment=ALIGN.LEFT,height=1,fg_bg=style.header} + TextBox{parent=main,y=1,text="ALPHA APP - INCOMPLETE",alignment=ALIGN.CENTER,height=1,fg_bg=style.header} --#region root panel panes (connection screens + main screen) From 03de90c3d85d2a3fc8e8838d0de5d4165343d31d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 31 Dec 2023 12:58:24 -0500 Subject: [PATCH 04/41] fixes to page navigation nav_up --- pocket/iocontrol.lua | 26 +++++++++++++++++++++++--- pocket/ui/main.lua | 7 +++++-- pocket/ui/pages/diag_page.lua | 2 +- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index e5f8273..d698d9e 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -47,6 +47,18 @@ function iocontrol.alloc_nav() if self.root._c[pane_id] then self.root._c[pane_id].nav_to() end end + -- find the pane this element belongs to + ---@param parent nav_tree_node + local function _find_pane(parent) + if parent == nil then + return nil + elseif parent.pane_elem then + return parent.pane_elem + else + return _find_pane(parent._p) + end + end + self.cur_page = self.root ---@class pocket_nav @@ -62,7 +74,8 @@ function iocontrol.alloc_nav() page._p._c[pane_id] = page function page.nav_to() - if page._p.pane_elem then page._p.pane_elem.set_value(page.pane_id) end + local p_pane = _find_pane(page._p) + if p_pane then p_pane.set_value(page.pane_id) end self.cur_page = page end @@ -80,7 +93,7 @@ function iocontrol.alloc_nav() function io.nav.nav_up() local parent = self.cur_page._p -- if a parent is defined and this element is not root - if parent and parent.pane_id ~= 0 then self.cur_page = parent end + if parent then parent.nav_to() end end io.nav_root = self.root @@ -88,8 +101,15 @@ end -- complete initialization of navigation by providing the root muiltipane ---@param root_pane graphics_element navigation root multipane -function iocontrol.init_nav(root_pane) +---@param default_page integer? page to nagivate to if nav_up is called on a base node +function iocontrol.init_nav(root_pane, default_page) io.nav_root.pane_elem = root_pane + + ---@todo keep this? + -- if default_page ~= nil then + -- io.nav_root.nav_to = function() io.nav_root.switcher(default_page) end + -- end + return io.nav_root end diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index d42bc60..49f375d 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -21,6 +21,7 @@ local Div = require("graphics.elements.div") local MultiPane = require("graphics.elements.multipane") local TextBox = require("graphics.elements.textbox") +local PushButton = require("graphics.elements.controls.push_button") local Sidebar = require("graphics.elements.controls.sidebar") local LINK_STATE = iocontrol.LINK_STATE @@ -32,7 +33,7 @@ local cpair = core.cpair -- create new main view ---@param main graphics_element main displaybox local function init(main) - local ps = iocontrol.get_db().ps + local db = iocontrol.get_db() -- window header message TextBox{parent=main,y=1,text="ALPHA APP - INCOMPLETE",alignment=ALIGN.CENTER,height=1,fg_bg=style.header} @@ -47,7 +48,7 @@ local function init(main) local root_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={conn_sv_wait,conn_api_wait,main_pane}} - root_pane.register(ps, "link_state", function (state) + root_pane.register(db.ps, "link_state", function (state) if state == LINK_STATE.UNLINKED or state == LINK_STATE.API_LINK_ONLY then root_pane.set_value(1) elseif state == LINK_STATE.SV_LINK_ONLY then @@ -78,6 +79,8 @@ local function init(main) Sidebar{parent=main_pane,x=1,y=1,tabs=sidebar_tabs,fg_bg=cpair(colors.white,colors.gray),callback=base.switcher} + PushButton{parent=main_pane,x=1,y=19,text="\x1b",min_width=3,fg_bg=cpair(colors.white,colors.gray),active_fg_bg=cpair(colors.gray,colors.black),callback=db.nav.nav_up} + --#endregion end diff --git a/pocket/ui/pages/diag_page.lua b/pocket/ui/pages/diag_page.lua index 495fd14..d328b9e 100644 --- a/pocket/ui/pages/diag_page.lua +++ b/pocket/ui/pages/diag_page.lua @@ -40,7 +40,7 @@ local function new_view(root) local npage_diag = db.nav.new_page(nil, 6, page_pane) local npage_home = db.nav.new_page(npage_diag, 1) - local npage_alarm = db.nav.new_page(npage_diag, 2) + local npage_alarm = db.nav.new_page(npage_home, 2) ------------------------ -- Alarm Testing Page -- From d5d818e625f0d521a23250ae87f5ab074b3d7393 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 14 Jan 2024 14:20:59 -0500 Subject: [PATCH 05/41] pocket signal bars --- graphics/core.lua | 2 +- graphics/element.lua | 1 + graphics/elements/controls/switch_button.lua | 2 +- graphics/elements/indicators/signal.lua | 72 ++++++++++++++++++++ pocket/iocontrol.lua | 54 +++++++++++++-- pocket/pocket.lua | 8 ++- pocket/ui/main.lua | 9 ++- 7 files changed, 136 insertions(+), 12 deletions(-) create mode 100644 graphics/elements/indicators/signal.lua diff --git a/graphics/core.lua b/graphics/core.lua index 13f97ec..8089ca1 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -7,7 +7,7 @@ local flasher = require("graphics.flasher") local core = {} -core.version = "2.1.0" +core.version = "2.2.0" core.flasher = flasher core.events = events diff --git a/graphics/element.lua b/graphics/element.lua index 6debaf4..e1420b7 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -49,6 +49,7 @@ local element = {} ---|indicator_light_args ---|power_indicator_args ---|rad_indicator_args +---|signal_bar_args ---|state_indicator_args ---|tristate_indicator_light_args ---|vbar_args diff --git a/graphics/elements/controls/switch_button.lua b/graphics/elements/controls/switch_button.lua index f649b10..1dcd1db 100644 --- a/graphics/elements/controls/switch_button.lua +++ b/graphics/elements/controls/switch_button.lua @@ -65,7 +65,7 @@ local function switch_button(args) end end - -- set the value + -- set the value (does not call the callback) ---@param val boolean new value function e.set_value(val) e.value = val diff --git a/graphics/elements/indicators/signal.lua b/graphics/elements/indicators/signal.lua new file mode 100644 index 0000000..fb21ca0 --- /dev/null +++ b/graphics/elements/indicators/signal.lua @@ -0,0 +1,72 @@ +-- Signal Bars Graphics Element + +local util = require("scada-common.util") + +local element = require("graphics.element") + +---@class signal_bar_args +---@field colors_low_med? cpair color a for low signal quality, color b for medium signal quality +---@field disconnect_color? color color for the 'x' on disconnect +---@field parent graphics_element +---@field id? string element id +---@field x? integer 1 if omitted +---@field y? integer auto incremented if omitted +---@field fg_bg? cpair foreground/background colors (foreground is used for high signal quality) +---@field hidden? boolean true to hide on initial draw + +-- new signal bar +---@nodiscard +---@param args signal_bar_args +---@return graphics_element element, element_id id +local function signal_bar(args) + args.height = 1 + args.width = 2 + + -- create new graphics element base object + local e = element.new(args) + + e.value = 0 + + local blit_bkg = args.fg_bg.blit_bkg + local blit_0, blit_1, blit_2, blit_3 = args.fg_bg.blit_fgd, args.fg_bg.blit_fgd, args.fg_bg.blit_fgd, args.fg_bg.blit_fgd + + if type(args.colors_low_med) == "table" then + blit_1 = args.colors_low_med.blit_a or blit_1 + blit_2 = args.colors_low_med.blit_b or blit_2 + end + + if util.is_int(args.disconnect_color) then blit_0 = colors.toBlit(args.disconnect_color) end + + -- on state change (0 = offline, 1 through 3 = low to high signal) + ---@param new_state integer signal state + function e.on_update(new_state) + e.value = new_state + e.redraw() + end + + -- set signal state (0 = offline, 1 through 3 = low to high signal) + ---@param val integer signal state + function e.set_value(val) e.on_update(val) end + + -- draw label and signal bar + function e.redraw() + e.w_set_cur(1, 1) + + if e.value == 1 then + e.w_blit("\x9f ", blit_bkg .. blit_bkg, blit_1 .. blit_bkg) + elseif e.value == 2 then + e.w_blit("\x9f\x94", blit_bkg .. blit_2, blit_2 .. blit_bkg) + elseif e.value == 3 then + e.w_blit("\x9f\x81", blit_bkg .. blit_bkg, blit_3 .. blit_3) + else + e.w_blit(" x", blit_0 .. blit_0, blit_bkg .. blit_bkg) + end + end + + -- initial draw + e.redraw() + + return e.complete() +end + +return signal_bar diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index d698d9e..38d1d20 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -8,13 +8,11 @@ local types = require("scada-common.types") local ALARM = types.ALARM -local iocontrol = {} +---@todo nominal trip time is ping (0ms to 10ms usually) +local WARN_TT = 40 +local HIGH_TT = 80 ----@class pocket_ioctl -local io = { - nav_root = nil, ---@type nav_tree_node - ps = psil.create() -} +local iocontrol = {} ---@enum POCKET_LINK_STATE local LINK_STATE = { @@ -26,6 +24,12 @@ local LINK_STATE = { iocontrol.LINK_STATE = LINK_STATE +---@class pocket_ioctl +local io = { + nav_root = nil, ---@type nav_tree_node + ps = psil.create() +} + ---@class nav_tree_node ---@field _p nav_tree_node|nil page's parent ---@field _c table page's children @@ -161,7 +165,43 @@ function iocontrol.init_fac() end -- set network link state ---@param state POCKET_LINK_STATE -function iocontrol.report_link_state(state) io.ps.publish("link_state", state) end +function iocontrol.report_link_state(state) + io.ps.publish("link_state", state) + + if state == LINK_STATE.API_LINK_ONLY or state == LINK_STATE.UNLINKED then + io.ps.publish("svr_conn_quality", 0) + end + + if state == LINK_STATE.SV_LINK_ONLY or state == LINK_STATE.UNLINKED then + io.ps.publish("crd_conn_quality", 0) + end +end + +-- determine supervisor connection quality (trip time) +---@param trip_time integer +function iocontrol.report_svr_tt(trip_time) + local state = 3 + if trip_time > HIGH_TT then + state = 1 + elseif trip_time > WARN_TT then + state = 2 + end + + io.ps.publish("svr_conn_quality", state) +end + +-- determine coordinator connection quality (trip time) +---@param trip_time integer +function iocontrol.report_crd_tt(trip_time) + local state = 3 + if trip_time > HIGH_TT then + state = 1 + elseif trip_time > WARN_TT then + state = 2 + end + + io.ps.publish("crd_conn_quality", state) +end -- get the IO controller database function iocontrol.get_db() return io end diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 1005c2a..e2afe84 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -250,9 +250,11 @@ function pocket.comms(version, nic, pkt_channel, svr_channel, crd_channel, range log.warning("pocket coordinator KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)") end - -- log.debug("pocket coordinator RTT = " .. trip_time .. "ms") + log.debug("pocket coordinator TT = " .. trip_time .. "ms") _send_api_keep_alive_ack(timestamp) + + iocontrol.report_crd_tt(trip_time) else log.debug("coordinator SCADA keep alive packet length mismatch") end @@ -340,9 +342,11 @@ function pocket.comms(version, nic, pkt_channel, svr_channel, crd_channel, range log.warning("pocket supervisor KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)") end - -- log.debug("pocket supervisor RTT = " .. trip_time .. "ms") + log.debug("pocket supervisor TT = " .. trip_time .. "ms") _send_sv_keep_alive_ack(timestamp) + + iocontrol.report_svr_tt(trip_time) else log.debug("supervisor SCADA keep alive packet length mismatch") end diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index 49f375d..5f848b2 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -24,6 +24,8 @@ local TextBox = require("graphics.elements.textbox") local PushButton = require("graphics.elements.controls.push_button") local Sidebar = require("graphics.elements.controls.sidebar") +local SignalBar = require("graphics.elements.indicators.signal") + local LINK_STATE = iocontrol.LINK_STATE local ALIGN = core.ALIGN @@ -36,7 +38,12 @@ local function init(main) local db = iocontrol.get_db() -- window header message - TextBox{parent=main,y=1,text="ALPHA APP - INCOMPLETE",alignment=ALIGN.CENTER,height=1,fg_bg=style.header} + TextBox{parent=main,y=1,text="DEV ALPHA APP S C ",alignment=ALIGN.LEFT,height=1,fg_bg=style.header} + local svr_conn = SignalBar{parent=main,y=1,x=20,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.white,colors.gray)} + local crd_conn = SignalBar{parent=main,y=1,x=24,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.white,colors.gray)} + + db.ps.subscribe("svr_conn_quality", svr_conn.set_value) + db.ps.subscribe("crd_conn_quality", crd_conn.set_value) --#region root panel panes (connection screens + main screen) From 526a54903e01236996ca96b154a96e17c156fed1 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 15 Jan 2024 17:40:43 -0500 Subject: [PATCH 06/41] #410 moved diagnostic apps to main app list and added app page nav --- graphics/element.lua | 1 + .../elements/controls/app_page_selector.lua | 75 +++++++++++++++++++ .../diag_page.lua => apps/diag_apps.lua} | 41 ++-------- pocket/ui/main.lua | 6 +- pocket/ui/pages/home_page.lua | 56 +++++++++++--- 5 files changed, 130 insertions(+), 49 deletions(-) create mode 100644 graphics/elements/controls/app_page_selector.lua rename pocket/ui/{pages/diag_page.lua => apps/diag_apps.lua} (79%) diff --git a/graphics/element.lua b/graphics/element.lua index e1420b7..9280d4c 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -29,6 +29,7 @@ local element = {} ---|checkbox_args ---|hazard_button_args ---|multi_button_args +---|app_page_selector_args ---|push_button_args ---|radio_2d_args ---|radio_button_args diff --git a/graphics/elements/controls/app_page_selector.lua b/graphics/elements/controls/app_page_selector.lua new file mode 100644 index 0000000..1a57bff --- /dev/null +++ b/graphics/elements/controls/app_page_selector.lua @@ -0,0 +1,75 @@ +-- App Page Selector Graphics Element + +local util = require("scada-common.util") + +local core = require("graphics.core") +local element = require("graphics.element") + +local MOUSE_CLICK = core.events.MOUSE_CLICK + +---@class app_page_selector_args +---@field page_count integer number of pages (will become this element's width) +---@field active_color color on/off colors (a/b respectively) +---@field callback function function to call on touch +---@field parent graphics_element +---@field id? string element id +---@field x? integer 1 if omitted +---@field y? integer auto incremented if omitted +---@field fg_bg? cpair foreground/background colors +---@field hidden? boolean true to hide on initial draw + +-- new app page selector +---@param args app_page_selector_args +---@return graphics_element element, element_id id +local function app_page_selector(args) + element.assert(util.is_int(args.page_count), "page_count is a required field") + element.assert(util.is_int(args.active_color), "active_color is a required field") + element.assert(type(args.callback) == "function", "callback is a required field") + + args.height = 1 + args.width = args.page_count + + -- create new graphics element base object + local e = element.new(args) + + e.value = 1 + + -- draw dot selectors + function e.redraw() + for i = 1, args.page_count do + e.w_set_cur(i, 1) + e.w_set_fgd(util.trinary(i == e.value, args.active_color, e.fg_bg.fgd)) + e.w_write("\x07") + end + end + + -- handle mouse interaction + ---@param event mouse_interaction mouse event + function e.handle_mouse(event) + if e.enabled then + if event.type == MOUSE_CLICK.TAP then + e.set_value(event.current.x) + args.callback(e.value) + elseif event.type == MOUSE_CLICK.UP then + if e.in_frame_bounds(event.current.x, event.current.y) then + e.set_value(event.current.x) + args.callback(e.value) + end + end + end + end + + -- set the value (does not call the callback) + ---@param val integer new value + function e.set_value(val) + e.value = val + e.redraw() + end + + -- initial draw + e.redraw() + + return e.complete() +end + +return app_page_selector diff --git a/pocket/ui/pages/diag_page.lua b/pocket/ui/apps/diag_apps.lua similarity index 79% rename from pocket/ui/pages/diag_page.lua rename to pocket/ui/apps/diag_apps.lua index d328b9e..f0d1114 100644 --- a/pocket/ui/pages/diag_page.lua +++ b/pocket/ui/apps/diag_apps.lua @@ -1,5 +1,5 @@ -- --- Diagnostic Tools +-- Diagnostic Apps -- local iocontrol = require("pocket.iocontrol") @@ -7,12 +7,10 @@ local iocontrol = require("pocket.iocontrol") local core = require("graphics.core") local Div = require("graphics.elements.div") -local MultiPane = require("graphics.elements.multipane") local TextBox = require("graphics.elements.textbox") local IndicatorLight = require("graphics.elements.indicators.light") -local App = require("graphics.elements.controls.app") local Checkbox = require("graphics.elements.controls.checkbox") local PushButton = require("graphics.elements.controls.push_button") local SwitchButton = require("graphics.elements.controls.switch_button") @@ -21,32 +19,17 @@ local cpair = core.cpair local ALIGN = core.ALIGN --- new diagnostics page view +-- create diagnostic app pages ---@param root graphics_element parent -local function new_view(root) +local function create_pages(root) local db = iocontrol.get_db() - local main = Div{parent=root,x=1,y=1} - - local diag_home = Div{parent=main,x=1,y=1} - - TextBox{parent=diag_home,text="Diagnostic Apps",x=1,y=2,height=1,alignment=ALIGN.CENTER} - - local alarm_test = Div{parent=main,x=1,y=1} - - local panes = { diag_home, alarm_test } - - local page_pane = MultiPane{parent=main,x=1,y=1,panes=panes} - - local npage_diag = db.nav.new_page(nil, 6, page_pane) - local npage_home = db.nav.new_page(npage_diag, 1) - local npage_alarm = db.nav.new_page(npage_home, 2) - ------------------------ -- Alarm Testing Page -- ------------------------ - table.insert(npage_alarm.tasks, db.diag.tone_test.get_tone_states) + local alarm_test = Div{parent=root,x=1,y=1} + local alarm_tasks = { db.diag.tone_test.get_tone_states } local ttest = db.diag.tone_test @@ -61,8 +44,6 @@ local function new_view(root) ttest.ready_warn = TextBox{parent=audio,y=2,text="",height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.yellow,colors.black)} - PushButton{parent=audio,x=13,y=18,text="\x11 BACK",min_width=8,fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=c_wht_gray,callback=npage_home.nav_to} - local tones = Div{parent=audio,x=2,y=3,height=10,width=8,fg_bg=cpair(colors.black,colors.yellow)} TextBox{parent=tones,text="Tones",height=1,alignment=ALIGN.CENTER,fg_bg=audio.get_fg_bg()} @@ -127,15 +108,7 @@ local function new_view(root) ttest.tone_indicators = { t_1, t_2, t_3, t_4, t_5, t_6, t_7, t_8 } - -------------- - -- App List -- - -------------- - - App{parent=diag_home,x=3,y=4,text="\x0f",title="Alarm",callback=npage_alarm.nav_to,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} - App{parent=diag_home,x=10,y=4,text="\x1e",title="LoopT",callback=function()end,app_fg_bg=cpair(colors.black,colors.cyan)} - App{parent=diag_home,x=17,y=4,text="@",title="Comps",callback=function()end,app_fg_bg=cpair(colors.black,colors.orange)} - - return main + return { Alarm = { e = alarm_test, tasks = alarm_tasks } } end -return new_view +return create_pages diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index 5f848b2..a886491 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -9,7 +9,6 @@ local style = require("pocket.ui.style") local conn_waiting = require("pocket.ui.components.conn_waiting") local boiler_page = require("pocket.ui.pages.boiler_page") -local diag_page = require("pocket.ui.pages.diag_page") local home_page = require("pocket.ui.pages.home_page") local reactor_page = require("pocket.ui.pages.reactor_page") local turbine_page = require("pocket.ui.pages.turbine_page") @@ -76,11 +75,10 @@ local function init(main) { char = "U", color = cpair(colors.black,colors.yellow) }, { char = "R", color = cpair(colors.black,colors.cyan) }, { char = "B", color = cpair(colors.black,colors.lightGray) }, - { char = "T", color = cpair(colors.black,colors.white) }, - { char = "D", color = cpair(colors.black,colors.orange) } + { char = "T", color = cpair(colors.black,colors.white) } } - local page_pane = MultiPane{parent=page_div,x=1,y=1,panes={home_page(page_div),unit_page(page_div),reactor_page(page_div),boiler_page(page_div),turbine_page(page_div),diag_page(page_div)}} + local page_pane = MultiPane{parent=page_div,x=1,y=1,panes={home_page(page_div),unit_page(page_div),reactor_page(page_div),boiler_page(page_div),turbine_page(page_div)}} local base = iocontrol.init_nav(page_pane) diff --git a/pocket/ui/pages/home_page.lua b/pocket/ui/pages/home_page.lua index bf1bb4c..c4c1e4a 100644 --- a/pocket/ui/pages/home_page.lua +++ b/pocket/ui/pages/home_page.lua @@ -2,30 +2,64 @@ -- Main Home Page -- -local iocontrol = require("pocket.iocontrol") +local iocontrol = require("pocket.iocontrol") -local core = require("graphics.core") +local diag_apps = require("pocket.ui.apps.diag_apps") -local Div = require("graphics.elements.div") +local core = require("graphics.core") -local App = require("graphics.elements.controls.app") +local Div = require("graphics.elements.div") +local MultiPane = require("graphics.elements.multipane") +local TextBox = require("graphics.elements.textbox") + +local AppPageSel = require("graphics.elements.controls.app_page_selector") +local App = require("graphics.elements.controls.app") local cpair = core.cpair +local ALIGN = core.ALIGN + -- new home page view ---@param root graphics_element parent local function new_view(root) local db = iocontrol.get_db() - db.nav.new_page(nil, 1) - local main = Div{parent=root,x=1,y=1} - App{parent=main,x=3,y=2,text="\x17",title="PRC",callback=function()end,app_fg_bg=cpair(colors.black,colors.purple)} - App{parent=main,x=10,y=2,text="\x15",title="CTL",callback=function()end,app_fg_bg=cpair(colors.black,colors.green)} - App{parent=main,x=17,y=2,text="\x08",title="DEV",callback=function()end,app_fg_bg=cpair(colors.black,colors.lightGray)} - App{parent=main,x=3,y=7,text="\x7f",title="Waste",callback=function()end,app_fg_bg=cpair(colors.black,colors.brown)} - App{parent=main,x=10,y=7,text="\xb6",title="Guide",callback=function()end,app_fg_bg=cpair(colors.black,colors.cyan)} + local apps = Div{parent=main,x=1,y=1,height=19} + + local apps_1 = Div{parent=apps,x=1,y=1,height=15} + local apps_2 = Div{parent=apps,x=1,y=1,height=15} + + local panes = { apps_1, apps_2 } + + local app_pane = MultiPane{parent=apps,x=1,y=1,panes=panes,height=15} + + AppPageSel{parent=apps,x=11,y=18,page_count=2,active_color=colors.lightGray,callback=app_pane.set_value,fg_bg=cpair(colors.gray,colors.black)} + + local d_apps = diag_apps(main) + + local page_panes = { apps, d_apps.Alarm.e } + + local page_pane = MultiPane{parent=main,x=1,y=1,panes=page_panes} + + local npage_home = db.nav.new_page(nil, 1, page_pane) + local npage_apps = db.nav.new_page(npage_home, 1) + + local npage_alarm = db.nav.new_page(npage_apps, 2) + npage_alarm.tasks = d_apps.Alarm.tasks + + App{parent=apps_1,x=3,y=2,text="\x17",title="PRC",callback=function()end,app_fg_bg=cpair(colors.black,colors.purple)} + App{parent=apps_1,x=10,y=2,text="\x15",title="CTL",callback=function()end,app_fg_bg=cpair(colors.black,colors.green)} + App{parent=apps_1,x=17,y=2,text="\x08",title="DEV",callback=function()end,app_fg_bg=cpair(colors.black,colors.lightGray)} + App{parent=apps_1,x=3,y=7,text="\x7f",title="Waste",callback=function()end,app_fg_bg=cpair(colors.black,colors.brown)} + App{parent=apps_1,x=10,y=7,text="\xb6",title="Guide",callback=function()end,app_fg_bg=cpair(colors.black,colors.cyan)} + + TextBox{parent=apps_2,text="Diagnostic Apps",x=1,y=2,height=1,alignment=ALIGN.CENTER} + + App{parent=apps_2,x=3,y=4,text="\x0f",title="Alarm",callback=npage_alarm.nav_to,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} + App{parent=apps_2,x=10,y=4,text="\x1e",title="LoopT",callback=function()end,app_fg_bg=cpair(colors.black,colors.cyan)} + App{parent=apps_2,x=17,y=4,text="@",title="Comps",callback=function()end,app_fg_bg=cpair(colors.black,colors.orange)} return main end From f0f2aadf53f720ec2cd064f238cc47e99582363b Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 7 Mar 2024 17:27:25 +0000 Subject: [PATCH 07/41] #200 work on pocket comms --- coordinator/iocontrol.lua | 15 +-------- coordinator/session/pocket.lua | 57 +++++++++++++++++++++++++++------- scada-common/comms.lua | 4 ++- 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 312d194..dda31fb 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -278,20 +278,7 @@ function iocontrol.init(conf, comms, temp_scale) }, ---@type alarms - alarms = { - ALARM_STATE.INACTIVE, -- containment breach - ALARM_STATE.INACTIVE, -- containment radiation - ALARM_STATE.INACTIVE, -- reactor lost - ALARM_STATE.INACTIVE, -- damage critical - ALARM_STATE.INACTIVE, -- reactor taking damage - ALARM_STATE.INACTIVE, -- reactor over temperature - ALARM_STATE.INACTIVE, -- reactor high temperature - ALARM_STATE.INACTIVE, -- waste leak - ALARM_STATE.INACTIVE, -- waste level high - ALARM_STATE.INACTIVE, -- RPS transient - ALARM_STATE.INACTIVE, -- RCS transient - ALARM_STATE.INACTIVE -- turbine trip - }, + alarms = { 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 }, annunciator = {}, ---@type annunciator diff --git a/coordinator/session/pocket.lua b/coordinator/session/pocket.lua index a19f7c3..18fccc7 100644 --- a/coordinator/session/pocket.lua +++ b/coordinator/session/pocket.lua @@ -8,7 +8,7 @@ local iocontrol = require("coordinator.iocontrol") local pocket = {} local PROTOCOL = comms.PROTOCOL --- local CRDN_TYPE = comms.CRDN_TYPE +local CRDN_TYPE = comms.CRDN_TYPE local MGMT_TYPE = comms.MGMT_TYPE -- retry time constants in ms @@ -73,18 +73,18 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout) end -- send a CRDN packet - -----@param msg_type CRDN_TYPE - -----@param msg table - -- local function _send(msg_type, msg) - -- local s_pkt = comms.scada_packet() - -- local c_pkt = comms.crdn_packet() + ---@param msg_type CRDN_TYPE + ---@param msg table + local function _send(msg_type, msg) + local s_pkt = comms.scada_packet() + local c_pkt = comms.crdn_packet() - -- c_pkt.make(msg_type, msg) - -- s_pkt.make(self.seq_num, PROTOCOL.SCADA_CRDN, c_pkt.raw_sendable()) + c_pkt.make(msg_type, msg) + s_pkt.make(s_addr, self.seq_num, PROTOCOL.SCADA_CRDN, c_pkt.raw_sendable()) - -- out_queue.push_packet(s_pkt) - -- self.seq_num = self.seq_num + 1 - -- end + out_queue.push_packet(s_pkt) + self.seq_num = self.seq_num + 1 + end -- send a SCADA management packet ---@param msg_type MGMT_TYPE @@ -120,8 +120,41 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout) if pkt.scada_frame.protocol() == PROTOCOL.SCADA_CRDN then ---@cast pkt crdn_frame + local db = iocontrol.get_db() + -- handle packet by type - if pkt.type == nil then + if pkt.type == CRDN_TYPE.API_GET_FAC then + local fac = db.facility + + ---@class api_fac + local data = { + num_units = fac.num_units, + num_tanks = util.table_len(fac.tank_data_tbl), + tank_mode = fac.tank_mode, + tank_defs = fac.tank_defs, + sys_ok = fac.all_sys_ok, + rtu_count = fac.rtu_count, + radiation = fac.radiation, + auto = { fac.auto_ready, fac.auto_active, fac.auto_ramping, fac.auto_saturated }, + waste = { fac.auto_current_waste_product, fac.auto_pu_fallback_active }, + has_matrix = fac.induction_data_tbl[1] ~= nil, + has_sps = fac.sps_data_tbl[1] ~= nil, + } + + _send(CRDN_TYPE.API_GET_FAC, data) + elseif pkt.type == CRDN_TYPE.API_GET_UNITS then + local data = {} + + for i = 1, #db.units do + local u = db.units[i] ---@type ioctl_unit + table.insert(data, { + u.unit_id, + u.num_boilers, + u.num_turbines, + u.num_snas, + u.has_tank + }) + end else log.debug(log_header .. "handler received unsupported CRDN packet type " .. pkt.type) end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 180e9c8..86f98ed 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -64,7 +64,9 @@ local CRDN_TYPE = { FAC_CMD = 3, -- faility command UNIT_BUILDS = 4, -- build of each reactor unit (reactor + RTUs) UNIT_STATUSES = 5, -- state of each of the reactor units - UNIT_CMD = 6 -- command a reactor unit + UNIT_CMD = 6, -- command a reactor unit + API_GET_FAC = 7, -- API: get all the facility data + API_GET_UNITS = 8 -- API: get all the reactor unit data } ---@enum ESTABLISH_ACK From 5c0e2c32ee11a7c57866398d3fce46e1b6923be9 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 7 Apr 2024 18:04:11 -0400 Subject: [PATCH 08/41] #461 prevent log spam in standalone mode when RPS trips continuously --- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 6ad612b..d610639 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc") local renderer = require("reactor-plc.renderer") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v1.7.4" +local R_PLC_VERSION = "v1.7.5" local println = util.println local println_ts = util.println_ts diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index afe6acb..8071fd8 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -368,9 +368,9 @@ function threads.thread__rps(smem) end end - -- if we are in standalone mode, continuously reset RPS + -- if we are in standalone mode and the front panel isn't working, continuously reset RPS -- RPS will trip again if there are faults, but if it isn't cleared, the user can't re-enable - if not networked then rps.reset(true) end + if not (networked or smem.plc_state.fp_ok) then rps.reset(true) end -- check safety (SCRAM occurs if tripped) if not plc_state.no_reactor then From 31a663e86b446b4e08f983b8d03ca7ec1870d751 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 7 Apr 2024 19:37:06 -0400 Subject: [PATCH 09/41] #460 threaded coordinator and moved UI start to render thread --- coordinator/iocontrol.lua | 7 + coordinator/startup.lua | 285 +++++---------------- coordinator/threads.lua | 346 ++++++++++++++++++++++++++ coordinator/ui/layout/front_panel.lua | 8 + 4 files changed, 428 insertions(+), 218 deletions(-) create mode 100644 coordinator/threads.lua diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 0cb50e4..9be448a 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -376,6 +376,13 @@ function iocontrol.fp_monitor_state(id, connected) end end +-- report thread (routine) statuses +---@param thread string thread name +---@param ok boolean thread state +function iocontrol.fp_rt_status(thread, ok) + io.fp.ps.publish(util.c("routine__", thread), ok) +end + -- report PKT firmware version and PKT session connection state ---@param session_id integer PKT session ---@param fw string firmware version diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 152d37b..750dde0 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -7,22 +7,19 @@ require("/initenv").init_env() local comms = require("scada-common.comms") local crash = require("scada-common.crash") local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") local network = require("scada-common.network") local ppm = require("scada-common.ppm") -local tcd = require("scada-common.tcd") local util = require("scada-common.util") -local core = require("graphics.core") - local configure = require("coordinator.configure") local coordinator = require("coordinator.coordinator") local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") +local threads = require("coordinator.threads") -local apisessions = require("coordinator.session.apisessions") - -local COORDINATOR_VERSION = "v1.3.5" +local COORDINATOR_VERSION = "v1.4.0" local CHUNK_LOAD_DELAY_S = 30.0 @@ -133,12 +130,54 @@ local function main() log_sys("system start on " .. os.date("%c")) log_boot("starting " .. COORDINATOR_VERSION) + ---------------------------------------- + -- memory allocation + ---------------------------------------- + + -- shared memory across threads + ---@class crd_shared_memory + local __shared_memory = { + -- time and date format for display + date_format = util.trinary(config.Time24Hour, "%X \x04 %A, %B %d %Y", "%r \x04 %A, %B %d %Y"), + + -- coordinator system state flags + ---@class crd_state + crd_state = { + fp_ok = false, + ui_ok = false, + link_fail = false, + shutdown = false + }, + + -- core coordinator devices + crd_dev = { + speaker = ppm.get_device("speaker"), + modem = ppm.get_wireless_modem() + }, + + -- system objects + crd_sys = { + nic = nil, ---@type nic + coord_comms = nil, ---@type coord_comms + conn_watchdog = nil ---@type watchdog + }, + + -- message queues + q = { + mq_render = mqueue.new() + } + } + + local smem_dev = __shared_memory.crd_dev + local smem_sys = __shared_memory.crd_sys + + local crd_state = __shared_memory.crd_state + ---------------------------------------- -- setup alarm sounder subsystem ---------------------------------------- - local speaker = ppm.get_device("speaker") - if speaker == nil then + if smem_dev.speaker == nil then log_boot("annunciator alarm speaker not found") println("startup> speaker not found") log.fatal("no annunciator alarm speaker found") @@ -146,7 +185,7 @@ local function main() else local sounder_start = util.time_ms() log_boot("annunciator alarm speaker connected") - sounder.init(speaker, config.SpeakerVolume) + sounder.init(smem_dev.speaker, config.SpeakerVolume) log_boot("tone generation took " .. (util.time_ms() - sounder_start) .. "ms") log_sys("annunciator alarm configured") iocontrol.fp_has_speaker(true) @@ -163,8 +202,7 @@ local function main() end -- get the communications modem - local modem = ppm.get_wireless_modem() - if modem == nil then + if smem_dev.modem == nil then log_comms("wireless modem not found") println("startup> wireless modem not found") log.fatal("no wireless modem on startup") @@ -175,243 +213,54 @@ local function main() end -- create connection watchdog - local conn_watchdog = util.new_watchdog(config.SVR_Timeout) - conn_watchdog.cancel() + smem_sys.conn_watchdog = util.new_watchdog(config.SVR_Timeout) + smem_sys.conn_watchdog.cancel() log.debug("startup> conn watchdog created") -- create network interface then setup comms - local nic = network.nic(modem) - local coord_comms = coordinator.comms(COORDINATOR_VERSION, nic, conn_watchdog) + smem_sys.nic = network.nic(smem_dev.modem) + smem_sys.coord_comms = coordinator.comms(COORDINATOR_VERSION, smem_sys.nic, smem_sys.conn_watchdog) log.debug("startup> comms init") log_comms("comms initialized") - -- base loop clock (2Hz, 10 ticks) - local MAIN_CLOCK = 0.5 - local loop_clock = util.new_clock(MAIN_CLOCK) - ---------------------------------------- - -- start front panel & UI start function + -- start front panel ---------------------------------------- log_graphics("starting front panel UI...") - local fp_ok, fp_message = renderer.try_start_fp() - if not fp_ok then + local fp_message + crd_state.fp_ok, fp_message = renderer.try_start_fp() + if not crd_state.fp_ok then log_graphics(util.c("front panel UI error: ", fp_message)) println_ts("front panel UI creation failed") log.fatal(util.c("front panel GUI render failed with error ", fp_message)) return else log_graphics("front panel ready") end - -- start up the main UI - ---@return boolean ui_ok started ok - local function start_main_ui() - log_graphics("starting main UI...") - - local draw_start = util.time_ms() - - local ui_ok, ui_message = renderer.try_start_ui() - if not ui_ok then - log_graphics(util.c("main UI error: ", ui_message)) - log.fatal(util.c("main GUI render failed with error ", ui_message)) - else - log_graphics("main UI draw took " .. (util.time_ms() - draw_start) .. "ms") - end - - return ui_ok - end - ---------------------------------------- - -- main event loop + -- start system ---------------------------------------- - local link_failed = false - local ui_ok = true - local date_format = util.trinary(config.Time24Hour, "%X \x04 %A, %B %d %Y", "%r \x04 %A, %B %d %Y") + -- init threads + local main_thread = threads.thread__main(__shared_memory) + local render_thread = threads.thread__render(__shared_memory) - -- start clock - loop_clock.start() + log.info("startup> completed") - log_sys("system started successfully") - - -- main event loop - while true do - local event, param1, param2, param3, param4, param5 = util.pull_event() - - -- handle event - if event == "peripheral_detach" then - local type, device = ppm.handle_unmount(param1) - - if type ~= nil and device ~= nil then - if type == "modem" then - -- we only really care if this is our wireless modem - -- if it is another modem, handle other peripheral losses separately - if nic.is_modem(device) then - nic.disconnect() - log_sys("comms modem disconnected") - - local other_modem = ppm.get_wireless_modem() - if other_modem then - log_sys("found another wireless modem, using it for comms") - nic.connect(other_modem) - else - -- close out main UI - renderer.close_ui() - - -- alert user to status - log_sys("awaiting comms modem reconnect...") - - iocontrol.fp_has_modem(false) - end - else - log_sys("non-comms modem disconnected") - end - elseif type == "monitor" then - if renderer.handle_disconnect(device) then - log_sys("lost a configured monitor") - else - log_sys("lost an unused monitor") - end - elseif type == "speaker" then - log_sys("lost alarm sounder speaker") - iocontrol.fp_has_speaker(false) - end - end - elseif event == "peripheral" then - local type, device = ppm.mount(param1) - - if type ~= nil and device ~= nil then - if type == "modem" then - if device.isWireless() and not nic.is_connected() then - -- reconnected modem - log_sys("comms modem reconnected") - nic.connect(device) - iocontrol.fp_has_modem(true) - elseif device.isWireless() then - log.info("unused wireless modem reconnected") - else - log_sys("wired modem reconnected") - end - elseif type == "monitor" then - if renderer.handle_reconnect(param1, device) then - log_sys(util.c("configured monitor ", param1, " reconnected")) - else - log_sys(util.c("unused monitor ", param1, " connected")) - end - elseif type == "speaker" then - log_sys("alarm sounder speaker reconnected") - sounder.reconnect(device) - iocontrol.fp_has_speaker(true) - end - end - elseif event == "monitor_resize" then - local is_used, is_ok = renderer.handle_resize(param1) - if is_used then - log_sys(util.c("configured monitor ", param1, " resized, ", util.trinary(is_ok, "display still fits", "display no longer fits"))) - end - elseif event == "timer" then - if loop_clock.is_clock(param1) then - -- main loop tick - - -- toggle heartbeat - iocontrol.heartbeat() - - -- maintain connection - if nic.is_connected() then - local ok, start_ui = coord_comms.try_connect() - if not ok then - link_failed = true - log_sys("supervisor connection failed, shutting down...") - log.fatal("failed to connect to supervisor") - break - elseif start_ui then - log_sys("supervisor connected, proceeding to main UI start") - ui_ok = start_main_ui() - if not ui_ok then break end - end - end - - -- iterate sessions - apisessions.iterate_all() - - -- free any closed sessions - apisessions.free_all_closed() - - -- update date and time string for main display - if coord_comms.is_linked() then - iocontrol.get_db().facility.ps.publish("date_time", os.date(date_format)) - end - - loop_clock.start() - elseif conn_watchdog.is_timer(param1) then - -- supervisor watchdog timeout - log_comms("supervisor server timeout") - - -- close connection, main UI, and stop sounder - coord_comms.close() - renderer.close_ui() - sounder.stop() - else - -- a non-clock/main watchdog timer event - - -- check API watchdogs - apisessions.check_all_watchdogs(param1) - - -- notify timer callback dispatcher - tcd.handle(param1) - end - elseif event == "modem_message" then - -- got a packet - local packet = coord_comms.parse_packet(param1, param2, param3, param4, param5) - - -- handle then check if it was a disconnect - if coord_comms.handle_packet(packet) then - log_comms("supervisor closed connection") - - -- close connection, main UI, and stop sounder - coord_comms.close() - renderer.close_ui() - sounder.stop() - end - elseif event == "monitor_touch" or event == "mouse_click" or event == "mouse_up" or - event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then - -- handle a mouse event - renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3)) - elseif event == "speaker_audio_empty" then - -- handle speaker buffer emptied - sounder.continue() - end - - -- check for termination request - if event == "terminate" or ppm.should_terminate() then - -- handle supervisor connection - coord_comms.try_connect(true) - - if coord_comms.is_linked() then - log_comms("terminate requested, closing supervisor connection...") - else link_failed = true end - - coord_comms.close() - log_comms("supervisor connection closed") - - -- handle API sessions - log_comms("closing api sessions...") - apisessions.close_all() - log_comms("api sessions closed") - break - end - end + -- run threads + parallel.waitForAll(main_thread.p_exec, render_thread.p_exec) renderer.close_ui() renderer.close_fp() sounder.stop() log_sys("system shutdown") - if link_failed then println_ts("failed to connect to supervisor") end - if not ui_ok then println_ts("main UI creation failed") end + if crd_state.link_fail then println_ts("failed to connect to supervisor") end + if not crd_state.ui_ok then println_ts("main UI creation failed") end -- close on error exit (such as UI error) - if coord_comms.is_linked() then coord_comms.close() end + if smem_sys.coord_comms.is_linked() then smem_sys.coord_comms.close() end println_ts("exited") log.info("exited") diff --git a/coordinator/threads.lua b/coordinator/threads.lua new file mode 100644 index 0000000..6ec7d18 --- /dev/null +++ b/coordinator/threads.lua @@ -0,0 +1,346 @@ +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local ppm = require("scada-common.ppm") +local tcd = require("scada-common.tcd") +local util = require("scada-common.util") + +local coordinator = require("coordinator.coordinator") +local iocontrol = require("coordinator.iocontrol") +local renderer = require("coordinator.renderer") +local sounder = require("coordinator.sounder") + +local apisessions = require("coordinator.session.apisessions") + +local core = require("graphics.core") + +local log_graphics = coordinator.log_graphics +local log_sys = coordinator.log_sys +local log_comms = coordinator.log_comms + +local threads = {} + +local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks) +local RENDER_SLEEP = 100 -- (100ms, 2 ticks) + +local MQ__RENDER_CMD = { + START_MAIN_UI = 1, + MON_DISCONNECT = 2, + MON_CONNECT = 3, + MON_RESIZE = 4, + UPDATE = 5 +} + +-- main thread +---@nodiscard +---@param smem crd_shared_memory +function threads.thread__main(smem) + ---@class parallel_thread + local public = {} + + -- execute thread + function public.exec() + iocontrol.fp_rt_status("main", true) + log.debug("main thread start") + + local loop_clock = util.new_clock(MAIN_CLOCK) + + -- start clock + loop_clock.start() + + log_sys("system started successfully") + + -- load in from shared memory + local crd_state = smem.crd_state + local nic = smem.crd_sys.nic + local coord_comms = smem.crd_sys.coord_comms + local conn_watchdog = smem.crd_sys.conn_watchdog + + -- event loop + while true do + local event, param1, param2, param3, param4, param5 = util.pull_event() + + -- handle event + if event == "peripheral_detach" then + local type, device = ppm.handle_unmount(param1) + + if type ~= nil and device ~= nil then + if type == "modem" then + -- we only really care if this is our wireless modem + -- if it is another modem, handle other peripheral losses separately + if nic.is_modem(device) then + nic.disconnect() + log_sys("comms modem disconnected") + + local other_modem = ppm.get_wireless_modem() + if other_modem then + log_sys("found another wireless modem, using it for comms") + nic.connect(other_modem) + else + -- close out main UI + renderer.close_ui() + + -- alert user to status + log_sys("awaiting comms modem reconnect...") + + iocontrol.fp_has_modem(false) + end + else + log_sys("non-comms modem disconnected") + end + elseif type == "monitor" then + if renderer.handle_disconnect(device) then + log_sys("lost a configured monitor") + else + log_sys("lost an unused monitor") + end + elseif type == "speaker" then + log_sys("lost alarm sounder speaker") + iocontrol.fp_has_speaker(false) + end + end + elseif event == "peripheral" then + local type, device = ppm.mount(param1) + + if type ~= nil and device ~= nil then + if type == "modem" then + if device.isWireless() and not nic.is_connected() then + -- reconnected modem + log_sys("comms modem reconnected") + nic.connect(device) + iocontrol.fp_has_modem(true) + elseif device.isWireless() then + log.info("unused wireless modem reconnected") + else + log_sys("wired modem reconnected") + end + elseif type == "monitor" then + if renderer.handle_reconnect(param1, device) then + log_sys(util.c("configured monitor ", param1, " reconnected")) + else + log_sys(util.c("unused monitor ", param1, " connected")) + end + elseif type == "speaker" then + log_sys("alarm sounder speaker reconnected") + sounder.reconnect(device) + iocontrol.fp_has_speaker(true) + end + end + elseif event == "monitor_resize" then + local is_used, is_ok = renderer.handle_resize(param1) + if is_used then + log_sys(util.c("configured monitor ", param1, " resized, ", util.trinary(is_ok, "display still fits", "display no longer fits"))) + end + elseif event == "timer" then + if loop_clock.is_clock(param1) then + -- main loop tick + + -- toggle heartbeat + iocontrol.heartbeat() + + -- maintain connection + if nic.is_connected() then + local ok, start_ui = coord_comms.try_connect() + if not ok then + crd_state.link_fail = true + log_sys("supervisor connection failed, shutting down...") + log.fatal("failed to connect to supervisor") + break + elseif start_ui then + log_sys("supervisor connected, dispatching main UI start") + smem.q.mq_render.push_command(MQ__RENDER_CMD.START_MAIN_UI) + --@todo if not ui_ok then break end + end + end + + -- iterate sessions + apisessions.iterate_all() + + -- free any closed sessions + apisessions.free_all_closed() + + -- update date and time string for main display + if coord_comms.is_linked() then + iocontrol.get_db().facility.ps.publish("date_time", os.date(smem.date_format)) + end + + loop_clock.start() + elseif conn_watchdog.is_timer(param1) then + -- supervisor watchdog timeout + log_comms("supervisor server timeout") + + -- close connection, main UI, and stop sounder + coord_comms.close() + renderer.close_ui() + sounder.stop() + else + -- a non-clock/main watchdog timer event + + -- check API watchdogs + apisessions.check_all_watchdogs(param1) + + -- notify timer callback dispatcher + tcd.handle(param1) + end + elseif event == "modem_message" then + -- got a packet + local packet = coord_comms.parse_packet(param1, param2, param3, param4, param5) + + -- handle then check if it was a disconnect + if coord_comms.handle_packet(packet) then + log_comms("supervisor closed connection") + + -- close connection, main UI, and stop sounder + coord_comms.close() + renderer.close_ui() + sounder.stop() + end + elseif event == "monitor_touch" or event == "mouse_click" or event == "mouse_up" or + event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then + -- handle a mouse event + renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3)) + elseif event == "speaker_audio_empty" then + -- handle speaker buffer emptied + sounder.continue() + end + + -- check for termination request + if event == "terminate" or ppm.should_terminate() then + crd_state.shutdown = true + log.info("terminate requested, main thread exiting") + + -- handle closing supervisor connection + coord_comms.try_connect(true) + + if coord_comms.is_linked() then + log_comms("terminate requested, closing supervisor connection...") + else crd_state.link_fail = true end + + coord_comms.close() + log_comms("supervisor connection closed") + + -- handle API sessions + log_comms("closing api sessions...") + apisessions.close_all() + log_comms("api sessions closed") + break + end + end + end + + -- execute the thread in a protected mode, retrying it on return if not shutting down + function public.p_exec() + local crd_state = smem.crd_state + + while not crd_state.shutdown do + local status, result = pcall(public.exec) + if status == false then + log.fatal(util.strval(result)) + end + + iocontrol.fp_rt_status("main", false) + + -- if status is true, then we are probably exiting, so this won't matter + -- if not, we need to restart the clock + -- this thread cannot be slept because it will miss events (namely "terminate" otherwise) + if not crd_state.shutdown then + log.info("main thread restarting now...") + end + end + end + + return public +end + +-- coordinator renderer thread +---@nodiscard +---@param smem crd_shared_memory +function threads.thread__render(smem) + ---@class parallel_thread + local public = {} + + -- execute thread + function public.exec() + iocontrol.fp_rt_status("render", true) + log.debug("render thread start") + + -- load in from shared memory + local crd_state = smem.crd_state + local render_queue = smem.q.mq_render + + local last_update = util.time() + + -- thread loop + while true do + -- check for messages in the message queue + while render_queue.ready() and not crd_state.shutdown do + local msg = render_queue.pop() + + if msg ~= nil then + if msg.qtype == mqueue.TYPE.COMMAND then + -- received a command + if msg.message == MQ__RENDER_CMD.START_MAIN_UI then + -- start up the main UI + log_graphics("starting main UI...") + + local draw_start = util.time_ms() + + local ui_message + crd_state.ui_ok, ui_message = renderer.try_start_ui() + if not crd_state.ui_ok then + log_graphics(util.c("main UI error: ", ui_message)) + log.fatal(util.c("main GUI render failed with error ", ui_message)) + else + log_graphics("main UI draw took " .. (util.time_ms() - draw_start) .. "ms") + end + elseif msg.message == MQ__RENDER_CMD.MON_DISCONNECT then + -- monitor lost + elseif msg.message == MQ__RENDER_CMD.MON_CONNECT then + -- monitor connected + elseif msg.message == MQ__RENDER_CMD.UPDATE then + -- new data + end + elseif msg.qtype == mqueue.TYPE.DATA then + -- received data + elseif msg.qtype == mqueue.TYPE.PACKET then + -- received a packet + end + end + + -- quick yield + util.nop() + end + + -- check for termination request + if crd_state.shutdown then + log.info("render thread exiting") + break + end + + -- delay before next check + last_update = util.adaptive_delay(RENDER_SLEEP, last_update) + end + end + + -- execute the thread in a protected mode, retrying it on return if not shutting down + function public.p_exec() + local crd_state = smem.crd_state + + while not crd_state.shutdown do + local status, result = pcall(public.exec) + if status == false then + log.fatal(util.strval(result)) + end + + iocontrol.fp_rt_status("render", false) + + if not crd_state.shutdown then + log.info("render thread restarting in 5 seconds...") + util.psleep(5) + end + end + end + + return public +end + +return threads diff --git a/coordinator/ui/layout/front_panel.lua b/coordinator/ui/layout/front_panel.lua index 7124083..7f2738a 100644 --- a/coordinator/ui/layout/front_panel.lua +++ b/coordinator/ui/layout/front_panel.lua @@ -100,6 +100,14 @@ local function init(panel, num_units) local speaker = LED{parent=system,label="SPEAKER",colors=led_grn} speaker.register(ps, "has_speaker", speaker.update) + system.line_break() + + local rt_main = LED{parent=system,label="RT MAIN",colors=led_grn} + local rt_render = LED{parent=system,label="RT RENDER",colors=led_grn} + + rt_main.register(ps, "routine__main", rt_main.update) + rt_render.register(ps, "routine__render", rt_render.update) + ---@diagnostic disable-next-line: undefined-field local comp_id = util.sprintf("(%d)", os.getComputerID()) TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=style.fp.disabled_fg} From 92a4277f730b556b990af27f191f21bf5af32a97 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 7 Apr 2024 19:54:22 -0400 Subject: [PATCH 10/41] #460 moved connect/disconnect/resize to render thread --- coordinator/threads.lua | 52 ++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/coordinator/threads.lua b/coordinator/threads.lua index 6ec7d18..255241e 100644 --- a/coordinator/threads.lua +++ b/coordinator/threads.lua @@ -24,8 +24,8 @@ local RENDER_SLEEP = 100 -- (100ms, 2 ticks) local MQ__RENDER_CMD = { START_MAIN_UI = 1, - MON_DISCONNECT = 2, - MON_CONNECT = 3, + MON_CONNECT = 2, + MON_DISCONNECT = 3, MON_RESIZE = 4, UPDATE = 5 } @@ -51,8 +51,8 @@ function threads.thread__main(smem) -- load in from shared memory local crd_state = smem.crd_state - local nic = smem.crd_sys.nic - local coord_comms = smem.crd_sys.coord_comms + local nic = smem.crd_sys.nic + local coord_comms = smem.crd_sys.coord_comms local conn_watchdog = smem.crd_sys.conn_watchdog -- event loop @@ -88,11 +88,7 @@ function threads.thread__main(smem) log_sys("non-comms modem disconnected") end elseif type == "monitor" then - if renderer.handle_disconnect(device) then - log_sys("lost a configured monitor") - else - log_sys("lost an unused monitor") - end + smem.q.mq_render.push_data(MQ__RENDER_CMD.MON_DISCONNECT, device) elseif type == "speaker" then log_sys("lost alarm sounder speaker") iocontrol.fp_has_speaker(false) @@ -114,11 +110,7 @@ function threads.thread__main(smem) log_sys("wired modem reconnected") end elseif type == "monitor" then - if renderer.handle_reconnect(param1, device) then - log_sys(util.c("configured monitor ", param1, " reconnected")) - else - log_sys(util.c("unused monitor ", param1, " connected")) - end + smem.q.mq_render.push_data(MQ__RENDER_CMD.MON_CONNECT, { name = param1, device = device }) elseif type == "speaker" then log_sys("alarm sounder speaker reconnected") sounder.reconnect(device) @@ -126,10 +118,7 @@ function threads.thread__main(smem) end end elseif event == "monitor_resize" then - local is_used, is_ok = renderer.handle_resize(param1) - if is_used then - log_sys(util.c("configured monitor ", param1, " resized, ", util.trinary(is_ok, "display still fits", "display no longer fits"))) - end + smem.q.mq_render.push_data(MQ__RENDER_CMD.MON_RESIZE, param1) elseif event == "timer" then if loop_clock.is_clock(param1) then -- main loop tick @@ -292,15 +281,34 @@ function threads.thread__render(smem) else log_graphics("main UI draw took " .. (util.time_ms() - draw_start) .. "ms") end - elseif msg.message == MQ__RENDER_CMD.MON_DISCONNECT then - -- monitor lost - elseif msg.message == MQ__RENDER_CMD.MON_CONNECT then - -- monitor connected elseif msg.message == MQ__RENDER_CMD.UPDATE then -- new data end elseif msg.qtype == mqueue.TYPE.DATA then -- received data + local cmd = msg.message ---@type queue_data + + if cmd.key == MQ__RENDER_CMD.MON_CONNECT then + -- monitor connected + if renderer.handle_reconnect(cmd.val.name, cmd.val.device) then + log_sys(util.c("configured monitor ", cmd.val.name, " reconnected")) + else + log_sys(util.c("unused monitor ", cmd.val.name, " connected")) + end + elseif cmd.key == MQ__RENDER_CMD.MON_DISCONNECT then + -- monitor disconnected + if renderer.handle_disconnect(cmd.val) then + log_sys("lost a configured monitor") + else + log_sys("lost an unused monitor") + end + elseif cmd.key == MQ__RENDER_CMD.MON_RESIZE then + -- monitor resized + local is_used, is_ok = renderer.handle_resize(cmd.val) + if is_used then + log_sys(util.c("configured monitor ", cmd.val, " resized, ", util.trinary(is_ok, "display fits", "display does not fit"))) + end + end elseif msg.qtype == mqueue.TYPE.PACKET then -- received a packet end From 1504247b33270992fceefd8439e182b2dab9b4c0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 7 Apr 2024 20:21:57 -0400 Subject: [PATCH 11/41] #460 added yields throughout main UI rendering --- coordinator/renderer.lua | 4 ++++ coordinator/ui/components/unit_detail.lua | 2 ++ coordinator/ui/layout/flow_view.lua | 3 +++ coordinator/ui/layout/main_view.lua | 7 +++++++ 4 files changed, 16 insertions(+) diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index d9e4b58..0e34ee1 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -3,6 +3,7 @@ -- local log = require("scada-common.log") +local util = require("scada-common.util") local iocontrol = require("coordinator.iocontrol") @@ -195,18 +196,21 @@ function renderer.try_start_ui() if engine.monitors.main ~= nil then engine.ui.main_display = DisplayBox{window=engine.monitors.main,fg_bg=style.root} main_view(engine.ui.main_display) + util.nop() 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) + util.nop() 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} unit_view(engine.ui.unit_displays[idx], idx) + util.nop() end end) diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 2264b00..fe36a85 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -352,6 +352,8 @@ local function init(parent, id) t3_trp.register(t_ps[3], "TurbineTrip", t3_trp.update) end + util.nop() + ---------------------- -- reactor controls -- ---------------------- diff --git a/coordinator/ui/layout/flow_view.lua b/coordinator/ui/layout/flow_view.lua index 98d16e9..06edf27 100644 --- a/coordinator/ui/layout/flow_view.lua +++ b/coordinator/ui/layout/flow_view.lua @@ -250,6 +250,7 @@ local function init(main) local y_offset = y_ofs(i) unit_flow(main, flow_x, 5 + y_offset, #water_pipes == 0, units[i]) table.insert(po_pipes, pipe(0, 3 + y_offset, 4, 0, colors.cyan, true, true)) + util.nop() end PipeNetwork{parent=main,x=139,y=15,pipes=po_pipes,bg=style.theme.bg} @@ -335,6 +336,8 @@ local function init(main) end end + util.nop() + --------- -- SPS -- --------- diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index f31c200..cba1747 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -3,6 +3,7 @@ -- local iocontrol = require("coordinator.iocontrol") +local util = require("scada-common.util") local style = require("coordinator.ui.style") @@ -53,6 +54,8 @@ local function init(main) cnc_y_start = cnc_y_start + row_1_height + 1 + util.nop() + if facility.num_units >= 3 then -- base offset 3, spacing 1, max height of units 1 and 2 local row_2_offset = cnc_y_start @@ -64,6 +67,8 @@ local function init(main) uo_4 = unit_overview(main, 84, row_2_offset, units[4]) cnc_y_start = math.max(cnc_y_start, row_2_offset + uo_4.get_height() + 1) end + + util.nop() end -- command & control @@ -79,6 +84,8 @@ local function init(main) process_ctl(main, 2, cnc_bottom_align_start) + util.nop() + imatrix(main, 131, cnc_bottom_align_start, facility.induction_data_tbl[1], facility.induction_ps_tbl[1]) end From eab1ffbe039bd3960b90174d6bf80f4ec4cbcd25 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 7 Apr 2024 20:24:27 -0400 Subject: [PATCH 12/41] #460 cleanup and moved date and time update to be behind UI ready rather than linked --- coordinator/threads.lua | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/coordinator/threads.lua b/coordinator/threads.lua index 255241e..7c849b5 100644 --- a/coordinator/threads.lua +++ b/coordinator/threads.lua @@ -141,14 +141,12 @@ function threads.thread__main(smem) end end - -- iterate sessions + -- iterate sessions and free any closed sessions apisessions.iterate_all() - - -- free any closed sessions apisessions.free_all_closed() - -- update date and time string for main display - if coord_comms.is_linked() then + if renderer.ui_ready() then + -- update clock used on main and flow monitors iocontrol.get_db().facility.ps.publish("date_time", os.date(smem.date_format)) end @@ -253,7 +251,7 @@ function threads.thread__render(smem) log.debug("render thread start") -- load in from shared memory - local crd_state = smem.crd_state + local crd_state = smem.crd_state local render_queue = smem.q.mq_render local last_update = util.time() From 45573be8c76aa6de63ec0bf7c922ed3dd478657d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 7 Apr 2024 20:47:31 -0400 Subject: [PATCH 13/41] #460 renderer logging --- coordinator/coordinator.lua | 4 ++-- coordinator/renderer.lua | 46 ++++++++++++++++++++++--------------- coordinator/startup.lua | 10 ++++---- coordinator/threads.lua | 8 +++---- 4 files changed, 38 insertions(+), 30 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index a8d6e5e..40d71d8 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -192,7 +192,7 @@ end ---@return function? update, function? done local function log_dmesg(message, dmesg_tag, working) local colors = { - GRAPHICS = colors.green, + RENDER = colors.green, SYSTEM = colors.cyan, BOOT = colors.blue, COMMS = colors.purple, @@ -206,7 +206,7 @@ local function log_dmesg(message, dmesg_tag, working) end end -function coordinator.log_graphics(message) log_dmesg(message, "GRAPHICS") end +function coordinator.log_render(message) log_dmesg(message, "RENDER") end function coordinator.log_sys(message) log_dmesg(message, "SYSTEM") end function coordinator.log_boot(message) log_dmesg(message, "BOOT") end function coordinator.log_comms(message) log_dmesg(message, "COMMS") end diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 0e34ee1..6920845 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -2,23 +2,26 @@ -- Graphics Rendering Control -- -local log = require("scada-common.log") -local util = require("scada-common.util") +local log = require("scada-common.log") +local util = require("scada-common.util") -local iocontrol = require("coordinator.iocontrol") +local coordinator = require("coordinator.coordinator") +local iocontrol = require("coordinator.iocontrol") -local style = require("coordinator.ui.style") -local pgi = require("coordinator.ui.pgi") +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") +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") -local core = require("graphics.core") -local flasher = require("graphics.flasher") +local core = require("graphics.core") +local flasher = require("graphics.flasher") -local DisplayBox = require("graphics.elements.displaybox") +local DisplayBox = require("graphics.elements.displaybox") + +local log_render = coordinator.log_render ---@class coord_renderer local renderer = {} @@ -387,12 +390,15 @@ function renderer.handle_resize(name) engine.dmesg_window.setVisible(not engine.ui_ready) if engine.ui_ready then + local draw_start = util.time_ms() local ok = pcall(function () ui.main_display = DisplayBox{window=device,fg_bg=style.root} main_view(ui.main_display) end) - if not ok then + if ok then + log_render("main view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms") + else if ui.main_display then ui.main_display.delete() ui.main_display = nil @@ -420,14 +426,15 @@ function renderer.handle_resize(name) iocontrol.fp_monitor_state("flow", true) if engine.ui_ready then - engine.dmesg_window.setVisible(false) - + local draw_start = util.time_ms() local ok = pcall(function () ui.flow_display = DisplayBox{window=device,fg_bg=style.root} flow_view(ui.flow_display) end) - if not ok then + if ok then + log_render("flow view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms") + else if ui.flow_display then ui.flow_display.delete() ui.flow_display = nil @@ -457,14 +464,15 @@ function renderer.handle_resize(name) iocontrol.fp_monitor_state(idx, true) if engine.ui_ready then - engine.dmesg_window.setVisible(false) - + local draw_start = util.time_ms() local ok = pcall(function () ui.unit_displays[idx] = DisplayBox{window=device,fg_bg=style.root} unit_view(ui.unit_displays[idx], idx) end) - if not ok then + if ok then + log_render("unit " .. idx .. " view re-draw completed in " .. (util.time_ms() - draw_start) .. "ms") + else if ui.unit_displays[idx] then ui.unit_displays[idx].delete() ui.unit_displays[idx] = nil diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 750dde0..2ed9fdb 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -26,7 +26,7 @@ local CHUNK_LOAD_DELAY_S = 30.0 local println = util.println local println_ts = util.println_ts -local log_graphics = coordinator.log_graphics +local log_render = coordinator.log_render local log_sys = coordinator.log_sys local log_boot = coordinator.log_boot local log_comms = coordinator.log_comms @@ -126,7 +126,7 @@ local function main() -- lets get started! log.info("monitors ready, dmesg output incoming...") - log_graphics("displays connected and reset") + log_render("displays connected and reset") log_sys("system start on " .. os.date("%c")) log_boot("starting " .. COORDINATOR_VERSION) @@ -227,16 +227,16 @@ local function main() -- start front panel ---------------------------------------- - log_graphics("starting front panel UI...") + log_render("starting front panel UI...") local fp_message crd_state.fp_ok, fp_message = renderer.try_start_fp() if not crd_state.fp_ok then - log_graphics(util.c("front panel UI error: ", fp_message)) + log_render(util.c("front panel UI error: ", fp_message)) println_ts("front panel UI creation failed") log.fatal(util.c("front panel GUI render failed with error ", fp_message)) return - else log_graphics("front panel ready") end + else log_render("front panel ready") end ---------------------------------------- -- start system diff --git a/coordinator/threads.lua b/coordinator/threads.lua index 7c849b5..7a433f5 100644 --- a/coordinator/threads.lua +++ b/coordinator/threads.lua @@ -13,7 +13,7 @@ local apisessions = require("coordinator.session.apisessions") local core = require("graphics.core") -local log_graphics = coordinator.log_graphics +local log_render = coordinator.log_render local log_sys = coordinator.log_sys local log_comms = coordinator.log_comms @@ -267,17 +267,17 @@ function threads.thread__render(smem) -- received a command if msg.message == MQ__RENDER_CMD.START_MAIN_UI then -- start up the main UI - log_graphics("starting main UI...") + log_render("starting main UI...") local draw_start = util.time_ms() local ui_message crd_state.ui_ok, ui_message = renderer.try_start_ui() if not crd_state.ui_ok then - log_graphics(util.c("main UI error: ", ui_message)) + log_render(util.c("main UI error: ", ui_message)) log.fatal(util.c("main GUI render failed with error ", ui_message)) else - log_graphics("main UI draw took " .. (util.time_ms() - draw_start) .. "ms") + log_render("main UI draw took " .. (util.time_ms() - draw_start) .. "ms") end elseif msg.message == MQ__RENDER_CMD.UPDATE then -- new data From f734c4307b11145c35ec34d1304284e26a992015 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 7 Apr 2024 20:55:07 -0400 Subject: [PATCH 14/41] #460 exit on UI crash --- coordinator/startup.lua | 2 +- coordinator/threads.lua | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 2ed9fdb..8b42414 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -144,7 +144,7 @@ local function main() ---@class crd_state crd_state = { fp_ok = false, - ui_ok = false, + ui_ok = true, -- default true, used to abort on fail link_fail = false, shutdown = false }, diff --git a/coordinator/threads.lua b/coordinator/threads.lua index 7a433f5..8fac12b 100644 --- a/coordinator/threads.lua +++ b/coordinator/threads.lua @@ -190,16 +190,21 @@ function threads.thread__main(smem) sounder.continue() end - -- check for termination request + -- check for termination request or UI crash if event == "terminate" or ppm.should_terminate() then crd_state.shutdown = true log.info("terminate requested, main thread exiting") + elseif not crd_state.ui_ok then + crd_state.shutdown = true + log.info("terminating due to fatal UI error") + end + if crd_state.shutdown then -- handle closing supervisor connection coord_comms.try_connect(true) if coord_comms.is_linked() then - log_comms("terminate requested, closing supervisor connection...") + log_comms("closing supervisor connection...") else crd_state.link_fail = true end coord_comms.close() From cc50e4a0201c56a584fb011dafb6b1db11598e53 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 7 Apr 2024 22:33:48 -0400 Subject: [PATCH 15/41] #460 removed coordinator connecting grace period --- supervisor/session/coordinator.lua | 13 +------------ supervisor/startup.lua | 2 +- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 98edaca..c0e6482 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -17,9 +17,6 @@ local FAC_COMMAND = comms.FAC_COMMAND local SV_Q_DATA = svqtypes.SV_Q_DATA --- grace period in seconds for coordinator to finish UI draw to prevent timeout -local WATCHDOG_GRACE = 20.0 - -- retry time constants in ms -- local INITIAL_WAIT = 1500 local RETRY_PERIOD = 1000 @@ -360,15 +357,7 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil -- check if a timer matches this session's watchdog ---@nodiscard function public.check_wd(timer) - local is_wd = self.conn_watchdog.is_timer(timer) and self.connected - - -- if we are waiting for initial coordinator UI draw, don't close yet - if is_wd and (util.time_s() - self.establish_time) <= WATCHDOG_GRACE then - self.conn_watchdog.feed() - is_wd = false - end - - return is_wd + return self.conn_watchdog.is_timer(timer) and self.connected end -- close the connection diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 806f7b0..b63b399 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.3.4" +local SUPERVISOR_VERSION = "v1.3.5" local println = util.println local println_ts = util.println_ts From 98c37caecd70847048b934de8e4da88c83c20d31 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 8 Apr 2024 23:59:16 -0400 Subject: [PATCH 16/41] #460 cleanup --- coordinator/startup.lua | 8 ++++---- coordinator/threads.lua | 31 ++++++++++++++--------------- coordinator/ui/layout/main_view.lua | 3 ++- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 8b42414..236fa79 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -23,13 +23,13 @@ local COORDINATOR_VERSION = "v1.4.0" local CHUNK_LOAD_DELAY_S = 30.0 -local println = util.println +local println = util.println local println_ts = util.println_ts local log_render = coordinator.log_render -local log_sys = coordinator.log_sys -local log_boot = coordinator.log_boot -local log_comms = coordinator.log_comms +local log_sys = coordinator.log_sys +local log_boot = coordinator.log_boot +local log_comms = coordinator.log_comms local log_crypto = coordinator.log_crypto ---------------------------------------- diff --git a/coordinator/threads.lua b/coordinator/threads.lua index 8fac12b..d684fc0 100644 --- a/coordinator/threads.lua +++ b/coordinator/threads.lua @@ -14,8 +14,8 @@ local apisessions = require("coordinator.session.apisessions") local core = require("graphics.core") local log_render = coordinator.log_render -local log_sys = coordinator.log_sys -local log_comms = coordinator.log_comms +local log_sys = coordinator.log_sys +local log_comms = coordinator.log_comms local threads = {} @@ -23,11 +23,13 @@ local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks) local RENDER_SLEEP = 100 -- (100ms, 2 ticks) local MQ__RENDER_CMD = { - START_MAIN_UI = 1, - MON_CONNECT = 2, - MON_DISCONNECT = 3, - MON_RESIZE = 4, - UPDATE = 5 + START_MAIN_UI = 1 +} + +local MQ__RENDER_DATA = { + MON_CONNECT = 1, + MON_DISCONNECT = 2, + MON_RESIZE = 3 } -- main thread @@ -88,7 +90,7 @@ function threads.thread__main(smem) log_sys("non-comms modem disconnected") end elseif type == "monitor" then - smem.q.mq_render.push_data(MQ__RENDER_CMD.MON_DISCONNECT, device) + smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_DISCONNECT, device) elseif type == "speaker" then log_sys("lost alarm sounder speaker") iocontrol.fp_has_speaker(false) @@ -110,7 +112,7 @@ function threads.thread__main(smem) log_sys("wired modem reconnected") end elseif type == "monitor" then - smem.q.mq_render.push_data(MQ__RENDER_CMD.MON_CONNECT, { name = param1, device = device }) + smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_CONNECT, { name = param1, device = device }) elseif type == "speaker" then log_sys("alarm sounder speaker reconnected") sounder.reconnect(device) @@ -118,7 +120,7 @@ function threads.thread__main(smem) end end elseif event == "monitor_resize" then - smem.q.mq_render.push_data(MQ__RENDER_CMD.MON_RESIZE, param1) + smem.q.mq_render.push_data(MQ__RENDER_DATA.MON_RESIZE, param1) elseif event == "timer" then if loop_clock.is_clock(param1) then -- main loop tick @@ -137,7 +139,6 @@ function threads.thread__main(smem) elseif start_ui then log_sys("supervisor connected, dispatching main UI start") smem.q.mq_render.push_command(MQ__RENDER_CMD.START_MAIN_UI) - --@todo if not ui_ok then break end end end @@ -284,28 +285,26 @@ function threads.thread__render(smem) else log_render("main UI draw took " .. (util.time_ms() - draw_start) .. "ms") end - elseif msg.message == MQ__RENDER_CMD.UPDATE then - -- new data end elseif msg.qtype == mqueue.TYPE.DATA then -- received data local cmd = msg.message ---@type queue_data - if cmd.key == MQ__RENDER_CMD.MON_CONNECT then + if cmd.key == MQ__RENDER_DATA.MON_CONNECT then -- monitor connected if renderer.handle_reconnect(cmd.val.name, cmd.val.device) then log_sys(util.c("configured monitor ", cmd.val.name, " reconnected")) else log_sys(util.c("unused monitor ", cmd.val.name, " connected")) end - elseif cmd.key == MQ__RENDER_CMD.MON_DISCONNECT then + elseif cmd.key == MQ__RENDER_DATA.MON_DISCONNECT then -- monitor disconnected if renderer.handle_disconnect(cmd.val) then log_sys("lost a configured monitor") else log_sys("lost an unused monitor") end - elseif cmd.key == MQ__RENDER_CMD.MON_RESIZE then + elseif cmd.key == MQ__RENDER_DATA.MON_RESIZE then -- monitor resized local is_used, is_ok = renderer.handle_resize(cmd.val) if is_used then diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index cba1747..5343139 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -2,9 +2,10 @@ -- Main SCADA Coordinator GUI -- -local iocontrol = require("coordinator.iocontrol") local util = require("scada-common.util") +local iocontrol = require("coordinator.iocontrol") + local style = require("coordinator.ui.style") local imatrix = require("coordinator.ui.components.imatrix") From cc3174ee769e196553a2996b3c140996990773fc Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 9 Apr 2024 00:08:47 -0400 Subject: [PATCH 17/41] comments and whitespace --- coordinator/threads.lua | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/coordinator/threads.lua b/coordinator/threads.lua index d684fc0..3b04b5b 100644 --- a/coordinator/threads.lua +++ b/coordinator/threads.lua @@ -142,7 +142,7 @@ function threads.thread__main(smem) end end - -- iterate sessions and free any closed sessions + -- iterate sessions and free any closed ones apisessions.iterate_all() apisessions.free_all_closed() @@ -183,7 +183,7 @@ function threads.thread__main(smem) sounder.stop() end elseif event == "monitor_touch" or event == "mouse_click" or event == "mouse_up" or - event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then + event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then -- handle a mouse event renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3)) elseif event == "speaker_audio_empty" then @@ -233,8 +233,7 @@ function threads.thread__main(smem) iocontrol.fp_rt_status("main", false) -- if status is true, then we are probably exiting, so this won't matter - -- if not, we need to restart the clock - -- this thread cannot be slept because it will miss events (namely "terminate" otherwise) + -- this thread cannot be slept because it will miss events (namely "terminate") if not crd_state.shutdown then log.info("main thread restarting now...") end @@ -244,7 +243,7 @@ function threads.thread__main(smem) return public end --- coordinator renderer thread +-- coordinator renderer thread, tasked with long duration re-draws ---@nodiscard ---@param smem crd_shared_memory function threads.thread__render(smem) From a22f5562cf16daff7f802c9891a0b45f320c242b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 9 Apr 2024 22:19:53 -0400 Subject: [PATCH 18/41] only ramp up on reactor plc --- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index d610639..8931652 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc") local renderer = require("reactor-plc.renderer") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v1.7.5" +local R_PLC_VERSION = "v1.7.6" local println = util.println local println_ts = util.println_ts diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 8071fd8..6ccf43e 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -662,8 +662,9 @@ function threads.thread__setpoint_control(smem) if (type(cur_burn_rate) == "number") and (setpoints.burn_rate ~= cur_burn_rate) and rps.is_active() then last_burn_sp = setpoints.burn_rate - -- update without ramp if <= 2.5 mB/t change - running = math.abs(setpoints.burn_rate - cur_burn_rate) > 2.5 + -- update without ramp if <= 2.5 mB/t increase + -- no need to ramp down, as the ramp up poses the safety risks + running = (setpoints.burn_rate - cur_burn_rate) > 2.5 if running then log.debug(util.c("SPCTL: starting burn rate ramp from ", cur_burn_rate, " mB/t to ", setpoints.burn_rate, " mB/t")) From 4fcd375ee2d5d17904b5fd696543243adbc81633 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 9 Apr 2024 22:20:46 -0400 Subject: [PATCH 19/41] show average charge rather than current charge below charge target --- coordinator/startup.lua | 2 +- coordinator/ui/components/process_ctl.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 236fa79..1ddc61a 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") local threads = require("coordinator.threads") -local COORDINATOR_VERSION = "v1.4.0" +local COORDINATOR_VERSION = "v1.4.1" local CHUNK_LOAD_DELAY_S = 30.0 diff --git a/coordinator/ui/components/process_ctl.lua b/coordinator/ui/components/process_ctl.lua index fb0c939..430409b 100644 --- a/coordinator/ui/components/process_ctl.lua +++ b/coordinator/ui/components/process_ctl.lua @@ -145,7 +145,7 @@ local function new_view(root, x, y) local cur_charge = DataIndicator{parent=targets,x=9,y=9,label="",format="%19d",value=0,unit="MFE",commas=true,lu_colors=black,width=23,fg_bg=blk_brn} c_target.register(facility.ps, "process_charge_target", c_target.set_value) - cur_charge.register(facility.induction_ps_tbl[1], "energy", function (j) cur_charge.update(util.joules_to_fe(j) / 1000000) end) + cur_charge.register(facility.induction_ps_tbl[1], "avg_charge", function (fe) cur_charge.update(fe / 1000000) end) local gen_tag = Div{parent=targets,x=1,y=11,width=8,height=4,fg_bg=blk_pur} TextBox{parent=gen_tag,x=2,y=2,text="Gen. Target",width=7,height=2} From 65d43d55c7223fc117d6a79e50d86f2885e1fa1d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 9 Apr 2024 22:23:43 -0400 Subject: [PATCH 20/41] #366 idling and charge level PD gain changes --- supervisor/facility.lua | 38 +++++++++++++++++-------- supervisor/startup.lua | 2 +- supervisor/unit.lua | 61 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 85 insertions(+), 16 deletions(-) diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 43c6cc4..737f6f9 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -50,9 +50,9 @@ local START_STATUS = { BLADE_MISMATCH = 2 } -local charge_Kp = 0.275 +local charge_Kp = 0.15 local charge_Ki = 0.0 -local charge_Kd = 4.5 +local charge_Kd = 0.6 local rate_Kp = 2.45 local rate_Ki = 0.4825 @@ -225,6 +225,14 @@ function facility.new(num_reactors, cooling_conf) return unallocated, false end + -- set idle state of all assigned reactors + ---@param idle boolean idle state + local function _set_idling(idle) + for i = 1, #self.prio_defs do + for _, u in pairs(self.prio_defs[i]) do u.auto_set_idle(idle) end + end + end + -- PUBLIC FUNCTIONS -- ---@class facility @@ -325,8 +333,9 @@ function facility.new(num_reactors, cooling_conf) --#region - local avg_charge = self.avg_charge.compute() - local avg_inflow = self.avg_inflow.compute() + local avg_charge = self.avg_charge.compute() + local avg_inflow = self.avg_inflow.compute() + local avg_outflow = self.avg_outflow.compute() local now = util.time_s() @@ -390,6 +399,7 @@ function facility.new(num_reactors, cooling_conf) -- disable reactors and disengage auto control for _, u in pairs(self.prio_defs[i]) do u.disable() + u.auto_set_idle(false) u.auto_disengage() end end @@ -460,6 +470,9 @@ function facility.new(num_reactors, cooling_conf) self.last_error = 0 self.accumulator = 0 + -- enabling idling on all assigned units + _set_idling(true) + self.status_text = { "CHARGE MODE", "running control loop" } log.info("FAC: CHARGE mode starting PID control") elseif self.last_update ~= charge_update then @@ -475,9 +488,9 @@ function facility.new(num_reactors, cooling_conf) local integral = self.accumulator local derivative = (error - self.last_error) / (now - self.last_time) - local P = (charge_Kp * error) - local I = (charge_Ki * integral) - local D = (charge_Kd * derivative) + local P = charge_Kp * error + local I = charge_Ki * integral + local D = charge_Kd * derivative local output = P + I + D @@ -486,7 +499,10 @@ function facility.new(num_reactors, cooling_conf) self.saturated = output ~= out_c - -- log.debug(util.sprintf("CHARGE[%f] { CHRG[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%d] }", + -- stop idling early if the output is zero, we are at or above the setpoint, and are not losing charge + _set_idling(not ((out_c == 0) and (error <= 0) and (avg_outflow < avg_inflow))) + + -- log.debug(util.sprintf("CHARGE[%f] { CHRG[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%f] }", -- runtime, avg_charge, error, integral, output, out_c, P, I, D)) _allocate_burn_rate(out_c, true) @@ -544,9 +560,9 @@ function facility.new(num_reactors, cooling_conf) local integral = self.accumulator local derivative = (error - self.last_error) / (now - self.last_time) - local P = (rate_Kp * error) - local I = (rate_Ki * integral) - local D = (rate_Kd * derivative) + local P = rate_Kp * error + local I = rate_Ki * integral + local D = rate_Kd * derivative -- velocity (rate) (derivative of charge level => rate) feed forward local FF = self.gen_rate_setpoint / self.charge_conversion diff --git a/supervisor/startup.lua b/supervisor/startup.lua index b63b399..1694ebe 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.3.5" +local SUPERVISOR_VERSION = "v1.3.6" local println = util.println local println_ts = util.println_ts diff --git a/supervisor/unit.lua b/supervisor/unit.lua index afdf6f3..10e028a 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -8,9 +8,6 @@ local logic = require("supervisor.unitlogic") local plc = require("supervisor.session.plc") local rsctl = require("supervisor.session.rsctl") ----@class reactor_control_unit -local unit = {} - local WASTE_MODE = types.WASTE_MODE local WASTE = types.WASTE_PRODUCT local ALARM = types.ALARM @@ -55,6 +52,14 @@ local AISTATE = { ---@field id ALARM alarm ID ---@field tier integer alarm urgency tier (0 = highest) +-- burn rate to idle at +local IDLE_RATE = 0.01 +-- time (ms) to idle +local IDLE_TIME = 15000 + +---@class reactor_control_unit +local unit = {} + -- create a new reactor unit ---@nodiscard ---@param reactor_id integer reactor unit number @@ -83,6 +88,9 @@ function unit.new(reactor_id, num_boilers, num_turbines) emcool_opened = false, -- auto control auto_engaged = false, + auto_idle = false, + auto_idling = false, + auto_idle_start = 0, auto_was_alarmed = false, ramp_target_br100 = 0, -- state tracking @@ -578,6 +586,23 @@ function unit.new(reactor_id, num_boilers, num_turbines) end end + -- set automatic control idling mode to change behavior when given a burn rate command of zero
+ -- - enabling will hold the reactor at 0.01 mB/t for a period before disabling when commanded zero + -- - disabling will stop the reactor when commanded zero + ---@param idle boolean true to enable, false to disable and stop right away + function public.auto_set_idle(idle) + if not (idle and self.auto_idle) then + self.auto_idling = false + self.auto_idle_start = 0 + end + + if idle ~= self.auto_idle then + log.debug(util.c("UNIT ", self.r_id, ": idling mode changed to ", idle)) + end + + self.auto_idle = idle + end + -- get the actual limit of this unit
-- if it is degraded or not ready, the limit will be 0 ---@nodiscard @@ -597,7 +622,35 @@ function unit.new(reactor_id, num_boilers, num_turbines) if self.auto_engaged then if self.plc_i ~= nil then log.debug(util.c("UNIT ", self.r_id, ": commit br100 of ", self.db.control.br100, " with ramp set to ", ramp)) - self.plc_i.auto_set_burn(self.db.control.br100 / 100, ramp) + + local rate = self.db.control.br100 / 100 + + if self.auto_idle then + if rate <= IDLE_RATE then + if self.auto_idle_start == 0 then + self.auto_idling = true + self.auto_idle_start = util.time_ms() + log.info(util.c("UNIT ", self.r_id, ": started idling at ", IDLE_RATE, " mB/t")) + + rate = IDLE_RATE + elseif (util.time_ms() - self.auto_idle_start) > IDLE_TIME then + if self.auto_idling then + self.auto_idling = false + log.info(util.c("UNIT ", self.r_id, ": completed idling period")) + end + else + log.debug(util.c("UNIT ", self.r_id, ": continuing idle at ", IDLE_RATE, " mB/t")) + + rate = IDLE_RATE + end + else + self.auto_idling = false + self.auto_idle_start = 0 + end + end + + self.plc_i.auto_set_burn(rate, ramp) + if ramp then self.ramp_target_br100 = self.db.control.br100 end end end From 612a06ba9851766a8183a132edfac9266d049793 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 9 Apr 2024 22:24:03 -0400 Subject: [PATCH 21/41] use os.clock rather than unix time for control --- supervisor/facility.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 737f6f9..b7900f7 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -337,7 +337,7 @@ function facility.new(num_reactors, cooling_conf) local avg_inflow = self.avg_inflow.compute() local avg_outflow = self.avg_outflow.compute() - local now = util.time_s() + local now = os.clock() local state_changed = self.mode ~= self.last_mode local next_mode = self.mode From d0b50c834ca93c0066f9ada545689a101484d9cb Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 10 Apr 2024 20:49:14 -0400 Subject: [PATCH 22/41] fixed coordinator not exiting on failed connection --- coordinator/startup.lua | 2 +- coordinator/threads.lua | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 1ddc61a..0d89a87 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") local threads = require("coordinator.threads") -local COORDINATOR_VERSION = "v1.4.1" +local COORDINATOR_VERSION = "v1.4.2" local CHUNK_LOAD_DELAY_S = 30.0 diff --git a/coordinator/threads.lua b/coordinator/threads.lua index 3b04b5b..1bd8fce 100644 --- a/coordinator/threads.lua +++ b/coordinator/threads.lua @@ -133,6 +133,7 @@ function threads.thread__main(smem) local ok, start_ui = coord_comms.try_connect() if not ok then crd_state.link_fail = true + crd_state.shutdown = true log_sys("supervisor connection failed, shutting down...") log.fatal("failed to connect to supervisor") break From a6a1a619549e3d902d6243ce83b18b976a5708fc Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 10 Apr 2024 21:30:51 -0400 Subject: [PATCH 23/41] #470 reworked flow stability logic --- scada-common/constants.lua | 4 +- supervisor/unit.lua | 6 ++- supervisor/unitlogic.lua | 102 +++++++++++++++++++++++++++++++++---- 3 files changed, 100 insertions(+), 12 deletions(-) diff --git a/scada-common/constants.lua b/scada-common/constants.lua index eaf6dd3..8f5d12f 100644 --- a/scada-common/constants.lua +++ b/scada-common/constants.lua @@ -68,8 +68,8 @@ constants.ALARM_LIMITS = alarms --#region Supervisor Constants --- milliseconds until turbine flow is assumed to be stable enough to enable coolant checks -constants.FLOW_STABILITY_DELAY_MS = 15000 +-- milliseconds until coolant flow is assumed to be stable enough to enable certain coolant checks +constants.FLOW_STABILITY_DELAY_MS = 10000 -- Notes on Radiation -- - background radiation 0.0000001 Sv/h (99.99 nSv/h) diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 10e028a..1472fa7 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -106,6 +106,8 @@ function unit.new(reactor_id, num_boilers, num_turbines) status_text = { "UNKNOWN", "awaiting connection..." }, -- logic for alarms had_reactor = false, + turbine_flow_stable = false, + turbine_stability_data = {}, last_rate_change_ms = 0, ---@type rps_status last_rps_trips = { @@ -253,12 +255,14 @@ function unit.new(reactor_id, num_boilers, num_turbines) end -- init turbine table fields - for _ = 1, num_turbines do + for t = 1, num_turbines do table.insert(self.db.annunciator.TurbineOnline, false) table.insert(self.db.annunciator.SteamDumpOpen, TRI_FAIL.OK) table.insert(self.db.annunciator.TurbineOverSpeed, false) table.insert(self.db.annunciator.GeneratorTrip, false) table.insert(self.db.annunciator.TurbineTrip, false) + + self.turbine_stability_data[t] = { time_state = 0, time_tanks = 0, rotation = 1 } end -- PRIVATE FUNCTIONS -- diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 134652c..bc60dfa 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -39,6 +39,21 @@ local ALARM_LIMS = const.ALARM_LIMITS ---@class unit_logic_extension local logic = {} +-- compute Mekanism's rotation rate for a turbine +---@param turbine turbinev_session_db +local function turbine_rotation(turbine) + local build = turbine.build + + local inner_vol = build.steam_cap / 64000 + local disp_rate = (build.dispersers * 1280) * inner_vol + local vent_rate = build.vents * 32000 + + local max_rate = math.min(disp_rate, vent_rate) + local flow = math.min(max_rate, turbine.tanks.steam.amount) + + return (flow * (turbine.tanks.steam.amount / build.steam_cap)) / max_rate +end + -- update the annunciator ---@param self _unit_self function logic.update_annunciator(self) @@ -81,6 +96,11 @@ function logic.update_annunciator(self) -- some alarms wait until the burn rate has stabilized, so keep track of that if math.abs(_get_dt(DT_KEYS.ReactorBurnR)) > 0 then self.last_rate_change_ms = util.time_ms() + self.turbine_flow_stable = false + + for t = 1, self.num_turbines do + self.turbine_stability_data[t] = { time_state = 0, time_tanks = 0, rotation = 1 } + end end -- record reactor stats @@ -274,6 +294,7 @@ function logic.update_annunciator(self) local total_flow_rate = 0 local total_input_rate = 0 local max_water_return_rate = 0 + local turbines_stable = true -- recompute blade count on the chance that it may have changed self.db.control.blade_count = 0 @@ -282,12 +303,14 @@ function logic.update_annunciator(self) for i = 1, #self.turbines do local session = self.turbines[i] ---@type unit_session local turbine = session.get_db() ---@type turbinev_session_db + local idx = session.get_device_idx() annunc.RCSFault = annunc.RCSFault or (not turbine.formed) or session.is_faulted() + annunc.TurbineOnline[idx] = true -- update ready state - -- - must be formed - -- - must have received build, state, and tanks at least once + -- - must be formed + -- - must have received build, state, and tanks at least once turbines_ready = turbines_ready and turbine.formed and (turbine.build.last_update > 0) and (turbine.state.last_update > 0) and @@ -296,11 +319,56 @@ function logic.update_annunciator(self) total_flow_rate = total_flow_rate + turbine.state.flow_rate total_input_rate = total_input_rate + turbine.state.steam_input_rate max_water_return_rate = max_water_return_rate + turbine.build.max_water_output + self.db.control.blade_count = self.db.control.blade_count + turbine.build.blades - annunc.TurbineOnline[session.get_device_idx()] = true + local last = self.turbine_stability_data[i] + + if (not self.turbine_flow_stable) and (turbine.state.steam_input_rate > 0) then + local rotation = turbine_rotation(turbine) + local rotation_stable = false + + -- see if data updated, and if so, check rotation speed change + -- minimal change indicates the turbine is converging on a flow rate + if last.time_tanks < turbine.tanks.last_update then + if last.time_tanks > 0 then + rotation_stable = math.abs(rotation - last.rotation) < 0.00000004 + end + + last.time_tanks = turbine.tanks.last_update + last.rotation = rotation + end + + -- flow is stable if the flow rate is at the input rate or at the max (±1 mB/t) + local flow_stable = false + if last.time_state < turbine.state.last_update then + if (last.time_state > 0) and (turbine.state.flow_rate > 0) then + flow_stable = math.abs(turbine.state.flow_rate - math.min(turbine.state.steam_input_rate, turbine.build.max_flow_rate)) < 2 + end + + last.time_state = turbine.state.last_update + end + + if rotation_stable then + log.debug(util.c("UNIT ", self.r_id, ": turbine ", idx, " reached rotational stability (", rotation, ")")) + end + + if flow_stable then + log.debug(util.c("UNIT ", self.r_id, ": turbine ", idx, " reached flow stability (", turbine.state.flow_rate, " mB/t)")) + end + + turbines_stable = turbines_stable and (rotation_stable or flow_stable) + else + last.time_state = 0 + last.time_tanks = 0 + last.rotation = 1 + + turbines_stable = false + end end + self.turbine_flow_stable = self.turbine_flow_stable or turbines_stable + -- check for boil rate mismatch (> 4% error) either between reactor and turbine or boiler and turbine annunc.BoilRateMismatch = math.abs(total_boil_rate - total_input_rate) > (0.04 * total_boil_rate) @@ -508,11 +576,25 @@ function logic.update_alarms(self) local rcs_trans = any_low or any_over or gen_trip or annunc.RCPTrip or annunc.MaxWaterReturnFeed - -- annunciator indicators for these states may not indicate a real issue when: - -- > flow is ramping up right after reactor start - -- > flow is ramping down after reactor shutdown - if ((util.time_ms() - self.last_rate_change_ms) > FLOW_STABILITY_DELAY_MS) and plc_cache.active then - rcs_trans = rcs_trans or annunc.RCSFlowLow or annunc.BoilRateMismatch or annunc.CoolantFeedMismatch or annunc.SteamFeedMismatch + if plc_cache.active then + -- these conditions may not indicate an issue when flow is changing after a burn rate change + if self.num_boilers == 0 then + if (util.time_ms() - self.last_rate_change_ms) > FLOW_STABILITY_DELAY_MS then + rcs_trans = rcs_trans or annunc.BoilRateMismatch + end + + if self.turbine_flow_stable then + rcs_trans = rcs_trans or annunc.RCSFlowLow or annunc.CoolantFeedMismatch or annunc.SteamFeedMismatch + end + else + if (util.time_ms() - self.last_rate_change_ms) > FLOW_STABILITY_DELAY_MS then + rcs_trans = rcs_trans or annunc.RCSFlowLow or annunc.BoilRateMismatch or annunc.CoolantFeedMismatch + end + + if self.turbine_flow_stable then + rcs_trans = rcs_trans or annunc.SteamFeedMismatch + end + end end if _update_alarm_state(self, rcs_trans, self.alarms.RCSTransient) then @@ -666,7 +748,9 @@ function logic.update_status_text(self) elseif annunc.WasteLineOcclusion then self.status_text[2] = "insufficient waste output rate" elseif (util.time_ms() - self.last_rate_change_ms) <= FLOW_STABILITY_DELAY_MS then - self.status_text[2] = "awaiting flow stability" + self.status_text[2] = "awaiting coolant flow stability" + elseif not self.turbine_flow_stable then + self.status_text[2] = "awaiting turbine flow stability" else self.status_text[2] = "system nominal" end From dfc1ee64974ff03d5dfc46eb8c5e4755382376f4 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 10 Apr 2024 22:16:41 -0400 Subject: [PATCH 24/41] cleanup and constants --- scada-common/constants.lua | 23 ++++++++++++++++++----- supervisor/facility.lua | 2 +- supervisor/unit.lua | 18 ++++++++++++------ supervisor/unitlogic.lua | 6 +++--- 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/scada-common/constants.lua b/scada-common/constants.lua index 8f5d12f..20925bd 100644 --- a/scada-common/constants.lua +++ b/scada-common/constants.lua @@ -75,12 +75,25 @@ constants.FLOW_STABILITY_DELAY_MS = 10000 -- - background radiation 0.0000001 Sv/h (99.99 nSv/h) -- - "green tint" radiation 0.00001 Sv/h (10 uSv/h) -- - damaging radiation 0.00006 Sv/h (60 uSv/h) -constants.LOW_RADIATION = 0.00001 -constants.HAZARD_RADIATION = 0.00006 -constants.HIGH_RADIATION = 0.001 +constants.LOW_RADIATION = 0.00001 +constants.HAZARD_RADIATION = 0.00006 +constants.HIGH_RADIATION = 0.001 constants.VERY_HIGH_RADIATION = 0.1 -constants.SEVERE_RADIATION = 8.0 -constants.EXTREME_RADIATION = 100.0 +constants.SEVERE_RADIATION = 8.0 +constants.EXTREME_RADIATION = 100.0 + +--#endregion + +--#region Mekanism Configuration Constants + +---@class _mek_constants +local mek = {} + +mek.TURBINE_GAS_PER_TANK = 64000 -- mekanism: turbineGasPerTank +mek.TURBINE_DISPERSER_FLOW = 1280 -- mekanism: turbineDisperserGasFlow +mek.TURBINE_VENT_FLOW = 32000 -- mekanism: turbineVentGasFlow + +constants.mek = mek --#endregion diff --git a/supervisor/facility.lua b/supervisor/facility.lua index b7900f7..40e8002 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -500,7 +500,7 @@ function facility.new(num_reactors, cooling_conf) self.saturated = output ~= out_c -- stop idling early if the output is zero, we are at or above the setpoint, and are not losing charge - _set_idling(not ((out_c == 0) and (error <= 0) and (avg_outflow < avg_inflow))) + _set_idling(not ((out_c == 0) and (error <= 0) and (avg_outflow <= 0))) -- log.debug(util.sprintf("CHARGE[%f] { CHRG[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%f] }", -- runtime, avg_charge, error, integral, output, out_c, P, I, D)) diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 1472fa7..88fcfb9 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -255,14 +255,13 @@ function unit.new(reactor_id, num_boilers, num_turbines) end -- init turbine table fields - for t = 1, num_turbines do + for _ = 1, num_turbines do table.insert(self.db.annunciator.TurbineOnline, false) table.insert(self.db.annunciator.SteamDumpOpen, TRI_FAIL.OK) table.insert(self.db.annunciator.TurbineOverSpeed, false) table.insert(self.db.annunciator.GeneratorTrip, false) table.insert(self.db.annunciator.TurbineTrip, false) - - self.turbine_stability_data[t] = { time_state = 0, time_tanks = 0, rotation = 1 } + table.insert(self.turbine_stability_data, { time_state = 0, time_tanks = 0, rotation = 1 }) end -- PRIVATE FUNCTIONS -- @@ -542,6 +541,13 @@ function unit.new(reactor_id, num_boilers, num_turbines) -- re-engage auto lock if it reconnected without it if self.auto_engaged and not self.plc_i.is_auto_locked() then self.plc_i.auto_lock(true) end + + -- stop idling when completed + if self.auto_idling and ((util.time_ms() - self.auto_idle_start) > IDLE_TIME) then + log.info(util.c("UNIT ", self.r_id, ": completed idling period")) + self.auto_idling = false + self.plc_i.auto_set_burn(0, false) + end end -- update deltas @@ -591,9 +597,9 @@ function unit.new(reactor_id, num_boilers, num_turbines) end -- set automatic control idling mode to change behavior when given a burn rate command of zero
- -- - enabling will hold the reactor at 0.01 mB/t for a period before disabling when commanded zero - -- - disabling will stop the reactor when commanded zero - ---@param idle boolean true to enable, false to disable and stop right away + -- - enabling it will hold the reactor at 0.01 mB/t for a period when commanded zero before disabling + -- - disabling it will stop the reactor when commanded zero + ---@param idle boolean true to enable, false to disable (and stop) function public.auto_set_idle(idle) if not (idle and self.auto_idle) then self.auto_idling = false diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index bc60dfa..4be004b 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -44,9 +44,9 @@ local logic = {} local function turbine_rotation(turbine) local build = turbine.build - local inner_vol = build.steam_cap / 64000 - local disp_rate = (build.dispersers * 1280) * inner_vol - local vent_rate = build.vents * 32000 + local inner_vol = build.steam_cap / const.mek.TURBINE_GAS_PER_TANK + local disp_rate = (build.dispersers * const.mek.TURBINE_DISPERSER_FLOW) * inner_vol + local vent_rate = build.vents * const.mek.TURBINE_VENT_FLOW local max_rate = math.min(disp_rate, vent_rate) local flow = math.min(max_rate, turbine.tanks.steam.amount) From 2aa52d2e2c0ea5548f6c21703f29eea34048d03e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 11 Apr 2024 20:30:54 -0400 Subject: [PATCH 25/41] simplified a config validation assert --- coordinator/coordinator.lua | 2 +- reactor-plc/plc.lua | 2 +- reactor-plc/startup.lua | 2 +- rtu/rtu.lua | 2 +- rtu/startup.lua | 2 +- supervisor/supervisor.lua | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 40d71d8..de92ef0 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -89,7 +89,7 @@ function coordinator.load_config() if type(config.AuthKey) == "string" then local len = string.len(config.AuthKey) - cfv.assert_eq(len == 0 or len >= 8, true) + cfv.assert(len == 0 or len >= 8) end cfv.assert_type_int(config.LogMode) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index b5c299d..fb906a3 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -74,7 +74,7 @@ function plc.load_config() if type(config.AuthKey) == "string" then local len = string.len(config.AuthKey) - cfv.assert_eq(len == 0 or len >= 8, true) + cfv.assert(len == 0 or len >= 8) end end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 8931652..09994cb 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc") local renderer = require("reactor-plc.renderer") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v1.7.6" +local R_PLC_VERSION = "v1.7.7" local println = util.println local println_ts = util.println_ts diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 7e00b98..71cea40 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -60,7 +60,7 @@ function rtu.load_config() if type(config.AuthKey) == "string" then local len = string.len(config.AuthKey) - cfv.assert_eq(len == 0 or len >= 8, true) + cfv.assert(len == 0 or len >= 8) end cfv.assert_type_int(config.LogMode) diff --git a/rtu/startup.lua b/rtu/startup.lua index 4d7ecbd..9470124 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -31,7 +31,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "v1.9.3" +local RTU_VERSION = "v1.9.4" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 3d6d7c7..75d71d8 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -78,7 +78,7 @@ function supervisor.load_config() if type(config.AuthKey) == "string" then local len = string.len(config.AuthKey) - cfv.assert_eq(len == 0 or len >= 8, true) + cfv.assert(len == 0 or len >= 8) end cfv.assert_type_int(config.LogMode) From 878c3b92e19287f84bcfb0d609548547c46e7d6a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 12 Apr 2024 00:13:05 -0400 Subject: [PATCH 26/41] #366 added idling to config and adjusted/fixed some behaviors --- supervisor/configure.lua | 26 +++++++++++++++-- supervisor/facility.lua | 46 ++++++++++++++++--------------- supervisor/session/svsessions.lua | 2 +- supervisor/supervisor.lua | 3 ++ supervisor/unit.lua | 12 ++++---- supervisor/unitlogic.lua | 2 +- 6 files changed, 59 insertions(+), 32 deletions(-) diff --git a/supervisor/configure.lua b/supervisor/configure.lua index cd790e7..8cad070 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -91,6 +91,7 @@ local tmp_cfg = { CoolingConfig = {}, FacilityTankMode = 0, FacilityTankDefs = {}, + ExtChargeIdling = false, SVR_Channel = nil, ---@type integer PLC_Channel = nil, ---@type integer RTU_Channel = nil, ---@type integer @@ -120,6 +121,7 @@ local fields = { { "CoolingConfig", "Cooling Configuration", {} }, { "FacilityTankMode", "Facility Tank Mode", 0 }, { "FacilityTankDefs", "Facility Tank Definitions", {} }, + { "ExtChargeIdling", "Extended Charge Idling", false }, { "SVR_Channel", "SVR Channel", 16240 }, { "PLC_Channel", "PLC Channel", 16241 }, { "RTU_Channel", "RTU Channel", 16242 }, @@ -222,8 +224,9 @@ local function config_view(display) local svr_c_4 = Div{parent=svr_cfg,x=2,y=4,width=49} local svr_c_5 = Div{parent=svr_cfg,x=2,y=4,width=49} local svr_c_6 = Div{parent=svr_cfg,x=2,y=4,width=49} + local svr_c_7 = Div{parent=svr_cfg,x=2,y=4,width=49} - local svr_pane = MultiPane{parent=svr_cfg,x=1,y=4,panes={svr_c_1,svr_c_2,svr_c_3,svr_c_4,svr_c_5,svr_c_6}} + local svr_pane = MultiPane{parent=svr_cfg,x=1,y=4,panes={svr_c_1,svr_c_2,svr_c_3,svr_c_4,svr_c_5,svr_c_6,svr_c_7}} TextBox{parent=svr_cfg,x=1,y=2,height=1,text=" Facility Configuration",fg_bg=cpair(colors.black,colors.yellow)} @@ -329,7 +332,7 @@ local function config_view(display) else tmp_cfg.FacilityTankMode = 0 tmp_cfg.FacilityTankDefs = {} - main_pane.set_value(3) + svr_pane.set_value(7) end end @@ -563,7 +566,7 @@ local function config_view(display) local function submit_mode() tmp_cfg.FacilityTankMode = tank_mode.get_value() - main_pane.set_value(3) + svr_pane.set_value(7) end PushButton{parent=svr_c_5,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} @@ -577,6 +580,23 @@ local function config_view(display) PushButton{parent=svr_c_6,x=1,y=14,text="\x1b Back",callback=function()svr_pane.set_value(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + TextBox{parent=svr_c_7,height=6,text="Charge control provides automatic control to maintain an induction matrix charge level. In order to have smoother control, reactors that were activated will be held on at 0.01 mB/t for a short period before allowing them to turn off. This minimizes overshooting the charge target."} + TextBox{parent=svr_c_7,y=8,height=3,text="You can extend this to a full minute to minimize reactors flickering on/off, but there may be more overshoot of the target."} + + local ext_idling = CheckBox{parent=svr_c_7,x=1,y=12,label="Enable Extended Idling",default=ini_cfg.ExtChargeIdling,box_fg_bg=cpair(colors.yellow,colors.black)} + + local function back_from_idling() + svr_pane.set_value(util.trinary(tmp_cfg.FacilityTankMode == 0, 3, 5)) + end + + local function submit_idling() + tmp_cfg.ExtChargeIdling = ext_idling.get_value() + main_pane.set_value(3) + end + + PushButton{parent=svr_c_7,x=1,y=14,text="\x1b Back",callback=back_from_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=svr_c_7,x=44,y=14,text="Next \x1a",callback=submit_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + --#endregion --#region Network diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 40e8002..33cd267 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -23,7 +23,7 @@ local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local WASTE_MODE = types.WASTE_MODE local WASTE = types.WASTE_PRODUCT -local IO = rsio.IO +local IO = rsio.IO local DTV_RTU_S_DATA = qtypes.DTV_RTU_S_DATA @@ -63,9 +63,9 @@ local facility = {} -- create a new facility management object ---@nodiscard ----@param num_reactors integer number of reactor units +---@param config svr_config supervisor configuration ---@param cooling_conf sv_cooling_conf cooling configurations of reactor units -function facility.new(num_reactors, cooling_conf) +function facility.new(config, cooling_conf) local self = { units = {}, status_text = { "START UP", "initializing..." }, @@ -134,8 +134,8 @@ function facility.new(num_reactors, cooling_conf) } -- create units - for i = 1, num_reactors do - table.insert(self.units, unit.new(i, cooling_conf.r_cool[i].BoilerCount, cooling_conf.r_cool[i].TurbineCount)) + for i = 1, config.UnitCount do + table.insert(self.units, unit.new(i, cooling_conf.r_cool[i].BoilerCount, cooling_conf.r_cool[i].TurbineCount, config.ExtChargeIdling)) table.insert(self.group_map, 0) end @@ -499,8 +499,10 @@ function facility.new(num_reactors, cooling_conf) self.saturated = output ~= out_c - -- stop idling early if the output is zero, we are at or above the setpoint, and are not losing charge - _set_idling(not ((out_c == 0) and (error <= 0) and (avg_outflow <= 0))) + if not config.ExtChargeIdling then + -- stop idling early if the output is zero, we are at or above the setpoint, and are not losing charge + _set_idling(not ((out_c == 0) and (error <= 0) and (avg_outflow <= 0))) + end -- log.debug(util.sprintf("CHARGE[%f] { CHRG[%f] ERR[%f] INT[%f] => OUT[%f] OUT_C[%f] <= P[%f] I[%f] D[%f] }", -- runtime, avg_charge, error, integral, output, out_c, P, I, D)) @@ -952,41 +954,41 @@ function facility.new(num_reactors, cooling_conf) function public.auto_stop() self.mode = PROCESS.INACTIVE end -- set automatic control configuration and start the process - ---@param config coord_auto_config configuration + ---@param auto_cfg coord_auto_config configuration ---@return table response ready state (successfully started) and current configuration (after updating) - function public.auto_start(config) + function public.auto_start(auto_cfg) local charge_scaler = 1000000 -- convert MFE to FE local gen_scaler = 1000 -- convert kFE to FE local ready = false -- load up current limits local limits = {} - for i = 1, num_reactors do + for i = 1, config.UnitCount do local u = self.units[i] ---@type reactor_unit limits[i] = u.get_control_inf().lim_br100 * 100 end -- only allow changes if not running if self.mode == PROCESS.INACTIVE then - if (type(config.mode) == "number") and (config.mode > PROCESS.INACTIVE) and (config.mode <= PROCESS.GEN_RATE) then - self.mode_set = config.mode + if (type(auto_cfg.mode) == "number") and (auto_cfg.mode > PROCESS.INACTIVE) and (auto_cfg.mode <= PROCESS.GEN_RATE) then + self.mode_set = auto_cfg.mode end - if (type(config.burn_target) == "number") and config.burn_target >= 0.1 then - self.burn_target = config.burn_target + if (type(auto_cfg.burn_target) == "number") and auto_cfg.burn_target >= 0.1 then + self.burn_target = auto_cfg.burn_target end - if (type(config.charge_target) == "number") and config.charge_target >= 0 then - self.charge_setpoint = config.charge_target * charge_scaler + if (type(auto_cfg.charge_target) == "number") and auto_cfg.charge_target >= 0 then + self.charge_setpoint = auto_cfg.charge_target * charge_scaler end - if (type(config.gen_target) == "number") and config.gen_target >= 0 then - self.gen_rate_setpoint = config.gen_target * gen_scaler + if (type(auto_cfg.gen_target) == "number") and auto_cfg.gen_target >= 0 then + self.gen_rate_setpoint = auto_cfg.gen_target * gen_scaler end - if (type(config.limits) == "table") and (#config.limits == num_reactors) then - for i = 1, num_reactors do - local limit = config.limits[i] + if (type(auto_cfg.limits) == "table") and (#auto_cfg.limits == config.UnitCount) then + for i = 1, config.UnitCount do + local limit = auto_cfg.limits[i] if (type(limit) == "number") and (limit >= 0.1) then limits[i] = limit @@ -1026,7 +1028,7 @@ function facility.new(num_reactors, cooling_conf) ---@param unit_id integer unit ID ---@param group integer group ID or 0 for independent function public.set_group(unit_id, group) - if (group >= 0 and group <= 4) and (unit_id > 0 and unit_id <= num_reactors) and self.mode == PROCESS.INACTIVE then + if (group >= 0 and group <= 4) and (unit_id > 0 and unit_id <= config.UnitCount) and self.mode == PROCESS.INACTIVE then -- remove from old group if previously assigned local old_group = self.group_map[unit_id] if old_group ~= 0 then diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index e191db6..7300162 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -201,7 +201,7 @@ function svsessions.init(nic, fp_ok, config, cooling_conf) self.nic = nic self.fp_ok = fp_ok self.config = config - self.facility = facility.new(config.UnitCount, cooling_conf) + self.facility = facility.new(config, cooling_conf) end -- find an RTU session by the computer ID diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 75d71d8..1d79e72 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -26,6 +26,7 @@ function supervisor.load_config() config.CoolingConfig = settings.get("CoolingConfig") config.FacilityTankMode = settings.get("FacilityTankMode") config.FacilityTankDefs = settings.get("FacilityTankDefs") + config.ExtChargeIdling = settings.get("ExtChargeIdling") config.SVR_Channel = settings.get("SVR_Channel") config.PLC_Channel = settings.get("PLC_Channel") @@ -58,6 +59,8 @@ function supervisor.load_config() cfv.assert_type_int(config.FacilityTankMode) cfv.assert_range(config.FacilityTankMode, 0, 8) + cfv.assert_type_bool(config.ExtChargeIdling) + cfv.assert_channel(config.SVR_Channel) cfv.assert_channel(config.PLC_Channel) cfv.assert_channel(config.RTU_Channel) diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 88fcfb9..6fa4d0a 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -54,8 +54,6 @@ local AISTATE = { -- burn rate to idle at local IDLE_RATE = 0.01 --- time (ms) to idle -local IDLE_TIME = 15000 ---@class reactor_control_unit local unit = {} @@ -65,7 +63,11 @@ local unit = {} ---@param reactor_id integer reactor unit number ---@param num_boilers integer number of boilers expected ---@param num_turbines integer number of turbines expected -function unit.new(reactor_id, num_boilers, num_turbines) +---@param ext_idle boolean extended idling mode +function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) + -- time (ms) to idle for auto idling + local IDLE_TIME = util.trinary(ext_idle, 60000, 10000) + ---@class _unit_self local self = { r_id = reactor_id, @@ -543,7 +545,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) if self.auto_engaged and not self.plc_i.is_auto_locked() then self.plc_i.auto_lock(true) end -- stop idling when completed - if self.auto_idling and ((util.time_ms() - self.auto_idle_start) > IDLE_TIME) then + if self.auto_idling and (((util.time_ms() - self.auto_idle_start) > IDLE_TIME) or not self.auto_idle) then log.info(util.c("UNIT ", self.r_id, ": completed idling period")) self.auto_idling = false self.plc_i.auto_set_burn(0, false) @@ -601,7 +603,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) -- - disabling it will stop the reactor when commanded zero ---@param idle boolean true to enable, false to disable (and stop) function public.auto_set_idle(idle) - if not (idle and self.auto_idle) then + if idle and not self.auto_idle then self.auto_idling = false self.auto_idle_start = 0 end diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 4be004b..ad2b522 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -332,7 +332,7 @@ function logic.update_annunciator(self) -- minimal change indicates the turbine is converging on a flow rate if last.time_tanks < turbine.tanks.last_update then if last.time_tanks > 0 then - rotation_stable = math.abs(rotation - last.rotation) < 0.00000004 + rotation_stable = math.abs(rotation - last.rotation) < 0.00000003 end last.time_tanks = turbine.tanks.last_update From c243d064efc8237d42afbe3eec2a946f27585d34 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 12 Apr 2024 00:18:35 -0400 Subject: [PATCH 27/41] fixed incorrect render behavior on quick supervisor reconnects --- coordinator/threads.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/coordinator/threads.lua b/coordinator/threads.lua index 1bd8fce..e7c2d8c 100644 --- a/coordinator/threads.lua +++ b/coordinator/threads.lua @@ -272,6 +272,13 @@ function threads.thread__render(smem) if msg.qtype == mqueue.TYPE.COMMAND then -- received a command if msg.message == MQ__RENDER_CMD.START_MAIN_UI then + -- stop the UI if it was already started + -- this may occur on a quick supervisor disconnect -> connect + if renderer.ui_ready() then + log_render("closing main UI before executing new request to start") + renderer.close_ui() + end + -- start up the main UI log_render("starting main UI...") From 23ca5fb69e508be2b3c91b5fb382b0fc86e33e61 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 12 Apr 2024 20:06:39 -0400 Subject: [PATCH 28/41] clear flow monitor on coordinator ui close --- coordinator/renderer.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 6920845..f2d2418 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -254,6 +254,11 @@ function renderer.close_ui() -- clear unit monitors for _, monitor in ipairs(engine.monitors.unit_displays) do monitor.clear() end + if not engine.disable_flow_view then + -- clear flow monitor + engine.monitors.flow.clear() + end + -- re-draw dmesg engine.dmesg_window.setVisible(true) engine.dmesg_window.redraw() From 99213da760ad07a96c61c742b99430392d9fe77d Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sat, 13 Apr 2024 00:41:47 +0000 Subject: [PATCH 29/41] #200 work on pocket comms for unit data --- coordinator/coordinator.lua | 13 ++++- coordinator/session/pocket.lua | 20 +++---- pocket/iocontrol.lua | 88 +++++++++++++++++++++++++++- pocket/pocket.lua | 102 ++++++++++++++++++++++----------- scada-common/comms.lua | 6 +- 5 files changed, 178 insertions(+), 51 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index a8d6e5e..9eb5450 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -470,10 +470,11 @@ function coordinator.comms(version, nic, sv_watchdog) elseif packet.type == MGMT_TYPE.ESTABLISH then -- establish a new session -- validate packet and continue - if packet.length == 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then - local comms_v = packet.data[1] - local firmware_v = packet.data[2] + if packet.length == 4 then + local comms_v = util.strval(packet.data[1]) + local firmware_v = util.strval(packet.data[2]) local dev_type = packet.data[3] + local api_v = util.strval(packet.data[4]) if comms_v ~= comms.version then if self.last_api_est_acks[src_addr] ~= ESTABLISH_ACK.BAD_VERSION then @@ -481,6 +482,12 @@ function coordinator.comms(version, nic, sv_watchdog) end _send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + elseif api_v ~= comms.api_version then + if self.last_api_est_acks[src_addr] ~= ESTABLISH_ACK.BAD_API_VERSION then + log.info(util.c("dropping API establish packet with incorrect api version v", comms_v, " (expected v", comms.version, ")")) + end + + _send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_API_VERSION) elseif dev_type == DEVICE_TYPE.PKT then -- pocket linking request local id = apisessions.establish_session(src_addr, firmware_v) diff --git a/coordinator/session/pocket.lua b/coordinator/session/pocket.lua index 18fccc7..83ebdce 100644 --- a/coordinator/session/pocket.lua +++ b/coordinator/session/pocket.lua @@ -126,19 +126,15 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout) if pkt.type == CRDN_TYPE.API_GET_FAC then local fac = db.facility - ---@class api_fac local data = { - num_units = fac.num_units, - num_tanks = util.table_len(fac.tank_data_tbl), - tank_mode = fac.tank_mode, - tank_defs = fac.tank_defs, - sys_ok = fac.all_sys_ok, - rtu_count = fac.rtu_count, - radiation = fac.radiation, - auto = { fac.auto_ready, fac.auto_active, fac.auto_ramping, fac.auto_saturated }, - waste = { fac.auto_current_waste_product, fac.auto_pu_fallback_active }, - has_matrix = fac.induction_data_tbl[1] ~= nil, - has_sps = fac.sps_data_tbl[1] ~= nil, + fac.all_sys_ok, + fac.rtu_count, + fac.radiation, + { fac.auto_ready, fac.auto_active, fac.auto_ramping, fac.auto_saturated }, + { fac.auto_current_waste_product, fac.auto_pu_fallback_active }, + util.table_len(fac.tank_data_tbl), + fac.induction_data_tbl[1] ~= nil, + fac.sps_data_tbl[1] ~= nil, } _send(CRDN_TYPE.API_GET_FAC, data) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 38d1d20..7efc7a5 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -161,7 +161,60 @@ function iocontrol.init_core(comms) end -- initialize facility-dependent components of pocket iocontrol -function iocontrol.init_fac() end +---@param conf facility_conf configuration +---@param comms pocket_comms comms reference +---@param temp_scale 1|2|3|4 temperature unit (1 = K, 2 = C, 3 = F, 4 = R) +function iocontrol.init_fac(conf, comms, temp_scale) + -- temperature unit label and conversion function (from Kelvin) + if temp_scale == 2 then + io.temp_label = "\xb0C" + io.temp_convert = function (t) return t - 273.15 end + elseif temp_scale == 3 then + io.temp_label = "\xb0F" + io.temp_convert = function (t) return (1.8 * (t - 273.15)) + 32 end + elseif temp_scale == 4 then + io.temp_label = "\xb0R" + io.temp_convert = function (t) return 1.8 * t end + else + io.temp_label = "K" + io.temp_convert = function (t) return t end + end + + -- facility data structure + ---@class pioctl_facility + io.facility = { + num_units = conf.num_units, + tank_mode = conf.cooling.fac_tank_mode, + tank_defs = conf.cooling.fac_tank_defs, + all_sys_ok = false, + rtu_count = 0, + + auto_ready = false, + auto_active = false, + auto_ramping = false, + auto_saturated = false, + + ---@type WASTE_PRODUCT + auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM, + auto_pu_fallback_active = false, + + radiation = types.new_zero_radiation_reading(), + + ps = psil.create(), + + induction_ps_tbl = {}, + induction_data_tbl = {}, + + sps_ps_tbl = {}, + sps_data_tbl = {}, + + tank_ps_tbl = {}, + tank_data_tbl = {}, + + env_d_ps = psil.create(), + env_d_data = {} + } +end -- set network link state ---@param state POCKET_LINK_STATE @@ -203,6 +256,39 @@ function iocontrol.report_crd_tt(trip_time) io.ps.publish("crd_conn_quality", state) end +-- populate facility data from API_GET_FAC +---@param data table +---@return boolean valid +function iocontrol.record_facility_data(data) + local valid = true + + local fac = io.facility + + fac.all_sys_ok = data[1] + fac.rtu_count = data[2] + fac.radiation = data[3] + + -- auto control + if type(data[4]) == "table" and #data[4] == 4 then + fac.auto_ready = data[4][1] + fac.auto_active = data[4][2] + fac.auto_ramping = data[4][3] + fac.auto_saturated = data[4][4] + end + + -- waste + if type(data[5]) == "table" and #data[5] == 2 then + fac.auto_current_waste_product = data[5][1] + fac.auto_pu_fallback_active = data[5][2] + end + + fac.num_tanks = data[6] + fac.has_imatrix = data[7] + fac.has_sps = data[8] + + return valid +end + -- get the IO controller database function iocontrol.get_db() return io end diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 6f5b73c..402bd19 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -8,6 +8,7 @@ local PROTOCOL = comms.PROTOCOL local DEVICE_TYPE = comms.DEVICE_TYPE local ESTABLISH_ACK = comms.ESTABLISH_ACK local MGMT_TYPE = comms.MGMT_TYPE +local CRDN_TYPE = comms.CRDN_TYPE local LINK_STATE = iocontrol.LINK_STATE @@ -246,6 +247,25 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) return pkt end + ---@param packet mgmt_frame|crdn_frame + ---@param length integer + ---@param max integer? + ---@return boolean + local function _check_length(packet, length, max) + local ok = util.trinary(max == nil, packet.length == length, packet.length >= length and packet.length <= max) + if not ok then + local fmt = "[comms] RX_PACKET{r_chan=%d,proto=%d,type=%d}: packet length mismatch -> expect %d != actual %d" + log.debug(util.sprintf(fmt, packet.scada_frame.remote_channel(), packet.scada_frame.protocol(), packet.type)) + end + return ok + end + + ---@param packet mgmt_frame|crdn_frame + local function _fail_type(packet) + local fmt = "[comms] RX_PACKET{r_chan=%d,proto=%d,type=%d}: unrecognized packet type" + log.debug(util.sprintf(fmt, packet.scada_frame.remote_channel(), packet.scada_frame.protocol(), packet.type)) + end + -- handle a packet ---@param packet mgmt_frame|crdn_frame|nil function public.handle_packet(packet) @@ -268,7 +288,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) return elseif self.api.linked and (src_addr ~= self.api.addr) then log.debug("received packet from unknown computer " .. src_addr .. " while linked (API expected " .. self.api.addr .. - "); channel in use by another system?") + "); channel in use by another system?") return else self.api.r_seq_num = packet.scada_frame.seq_num() @@ -277,12 +297,24 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) -- feed watchdog on valid sequence number api_watchdog.feed() - if protocol == PROTOCOL.SCADA_MGMT then + if protocol == PROTOCOL.SCADA_CRDN then + ---@cast packet crdn_frame + if self.api.linked then + if packet.type == CRDN_TYPE.API_GET_FAC then + if _check_length(packet, 11) then + iocontrol.record_facility_data(packet.data) + end + elseif packet.type == CRDN_TYPE.API_GET_UNITS then + else _fail_type(packet) end + else + log.debug("discarding coordinator SCADA_CRDN packet before linked") + end + elseif protocol == PROTOCOL.SCADA_MGMT then ---@cast packet mgmt_frame if self.api.linked then if packet.type == MGMT_TYPE.KEEP_ALIVE then -- keep alive request received, echo back - if packet.length == 1 then + if _check_length(packet, 1) then local timestamp = packet.data[1] local trip_time = util.time() - timestamp @@ -295,8 +327,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) _send_api_keep_alive_ack(timestamp) iocontrol.report_crd_tt(trip_time) - else - log.debug("coordinator SCADA keep alive packet length mismatch") end elseif packet.type == MGMT_TYPE.CLOSE then -- handle session close @@ -305,24 +335,38 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) self.api.r_seq_num = nil self.api.addr = comms.BROADCAST log.info("coordinator server connection closed by remote host") - else - log.debug("received unknown SCADA_MGMT packet type " .. packet.type .. " from coordinator") - end + else _fail_type(packet) end elseif packet.type == MGMT_TYPE.ESTABLISH then -- connection with coordinator established - if packet.length == 1 then + if _check_length(packet, 1, 2) then local est_ack = packet.data[1] if est_ack == ESTABLISH_ACK.ALLOW then - log.info("coordinator connection established") - self.establish_delay_counter = 0 - self.api.linked = true - self.api.addr = src_addr + if packet.length == 2 then + local fac_config = packet.data[2] - if self.sv.linked then - iocontrol.report_link_state(LINK_STATE.LINKED) + if type(fac_config) == "table" and #fac_config == 2 then + -- get configuration + local conf = { num_units = fac_config[1], cooling = fac_config[2] } + + ---@todo + iocontrol.init_fac(conf, public, 0) + + log.info("coordinator connection established") + self.establish_delay_counter = 0 + self.api.linked = true + self.api.addr = src_addr + + if self.sv.linked then + iocontrol.report_link_state(LINK_STATE.LINKED) + else + iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY) + end + else + log.debug("invalid facility configuration table received from coordinator, establish failed") + end else - iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY) + log.debug("received coordinator establish allow without facility configuration") end elseif est_ack == ESTABLISH_ACK.DENY then if self.api.last_est_ack ~= est_ack then @@ -336,13 +380,15 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) if self.api.last_est_ack ~= est_ack then log.info("coordinator comms version mismatch") end + elseif est_ack == ESTABLISH_ACK.BAD_API_VERSION then + if self.api.last_est_ack ~= est_ack then + log.info("coordinator api version mismatch") + end else log.debug("coordinator SCADA_MGMT establish packet reply unsupported") end self.api.last_est_ack = est_ack - else - log.debug("coordinator SCADA_MGMT establish packet length mismatch") end else log.debug("discarding coordinator non-link SCADA_MGMT packet before linked") @@ -374,7 +420,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) if self.sv.linked then if packet.type == MGMT_TYPE.KEEP_ALIVE then -- keep alive request received, echo back - if packet.length == 1 then + if _check_length(packet, 1) then local timestamp = packet.data[1] local trip_time = util.time() - timestamp @@ -387,8 +433,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) _send_sv_keep_alive_ack(timestamp) iocontrol.report_svr_tt(trip_time) - else - log.debug("supervisor SCADA keep alive packet length mismatch") end elseif packet.type == MGMT_TYPE.CLOSE then -- handle session close @@ -398,12 +442,10 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) self.sv.addr = comms.BROADCAST log.info("supervisor server connection closed by remote host") elseif packet.type == MGMT_TYPE.DIAG_TONE_GET then - if packet.length == 8 then + if _check_length(packet, 8) then for i = 1, #packet.data do diag.tone_test.tone_indicators[i].update(packet.data[i] == true) end - else - log.debug("supervisor SCADA diag alarm states packet length mismatch") end elseif packet.type == MGMT_TYPE.DIAG_TONE_SET then if packet.length == 1 and packet.data[1] == false then @@ -442,12 +484,10 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) else log.debug("supervisor SCADA diag alarm set packet length/type mismatch") end - else - log.debug("received unknown SCADA_MGMT packet type " .. packet.type .. " from supervisor") - end + else _fail_type(packet) end elseif packet.type == MGMT_TYPE.ESTABLISH then -- connection with supervisor established - if packet.length == 1 then + if _check_length(packet, 1) then local est_ack = packet.data[1] if est_ack == ESTABLISH_ACK.ALLOW then @@ -478,15 +518,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) end self.sv.last_est_ack = est_ack - else - log.debug("supervisor SCADA_MGMT establish packet length mismatch") end else log.debug("discarding supervisor non-link SCADA_MGMT packet before linked") end - else - log.debug("illegal packet type " .. protocol .. " from supervisor", true) - end + else _fail_type(packet) end else log.debug("received packet from unconfigured channel " .. r_chan, true) end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 86f98ed..9e2148b 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -16,8 +16,9 @@ local max_distance = nil ---@class comms local comms = {} --- protocol/data version (protocol/data independent changes tracked by util.lua version) +-- protocol/data versions (protocol/data independent changes tracked by util.lua version) comms.version = "2.4.5" +comms.api_version = "0.0.1" ---@enum PROTOCOL local PROTOCOL = { @@ -74,7 +75,8 @@ local ESTABLISH_ACK = { ALLOW = 0, -- link approved DENY = 1, -- link denied COLLISION = 2, -- link denied due to existing active link - BAD_VERSION = 3 -- link denied due to comms version mismatch + BAD_VERSION = 3, -- link denied due to comms version mismatch + BAD_API_VERSION = 4 -- link denied due to api version mismatch } ---@enum DEVICE_TYPE device types for establish messages From 2b4309afa77906aa284ade1047b2eefee83b1095 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 13 Apr 2024 11:02:41 -0400 Subject: [PATCH 30/41] pocket coordinator linking fixes --- coordinator/coordinator.lua | 8 +++++--- coordinator/iocontrol.lua | 1 + pocket/pocket.lua | 8 ++++---- pocket/startup.lua | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index c877eb6..f4c8b69 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -279,11 +279,12 @@ function coordinator.comms(version, nic, sv_watchdog) -- send an API establish request response ---@param packet scada_packet ---@param ack ESTABLISH_ACK - local function _send_api_establish_ack(packet, ack) + ---@param data any? + local function _send_api_establish_ack(packet, ack, data) local s_pkt = comms.scada_packet() local m_pkt = comms.mgmt_packet() - m_pkt.make(MGMT_TYPE.ESTABLISH, { ack }) + m_pkt.make(MGMT_TYPE.ESTABLISH, { ack, data }) s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable()) nic.transmit(config.PKT_Channel, config.CRD_Channel, s_pkt) @@ -493,7 +494,8 @@ function coordinator.comms(version, nic, sv_watchdog) local id = apisessions.establish_session(src_addr, firmware_v) coordinator.log_comms(util.c("API_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", id)) - _send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.ALLOW) + local conf = iocontrol.get_db().facility.conf + _send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.ALLOW, { conf.num_units, conf.cooling }) else log.debug(util.c("API_ESTABLISH: illegal establish packet for device ", dev_type, " on pocket channel")) _send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.DENY) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 2bd3a95..c91e24d 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -67,6 +67,7 @@ function iocontrol.init(conf, comms, temp_scale) -- facility data structure ---@class ioctl_facility io.facility = { + conf = conf, num_units = conf.num_units, tank_mode = conf.cooling.fac_tank_mode, tank_defs = conf.cooling.fac_tank_defs, diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 402bd19..0d3b8da 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -126,7 +126,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) -- attempt coordinator API connection establishment local function _send_api_establish() - _send_crd(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PKT }) + _send_crd(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PKT, comms.api_version }) end -- keep alive ack to supervisor @@ -252,7 +252,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) ---@param max integer? ---@return boolean local function _check_length(packet, length, max) - local ok = util.trinary(max == nil, packet.length == length, packet.length >= length and packet.length <= max) + local ok = util.trinary(max == nil, packet.length == length, packet.length >= length and packet.length <= (max or 0)) if not ok then local fmt = "[comms] RX_PACKET{r_chan=%d,proto=%d,type=%d}: packet length mismatch -> expect %d != actual %d" log.debug(util.sprintf(fmt, packet.scada_frame.remote_channel(), packet.scada_frame.protocol(), packet.type)) @@ -322,7 +322,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) log.warning("pocket coordinator KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)") end - log.debug("pocket coordinator TT = " .. trip_time .. "ms") + -- log.debug("pocket coordinator TT = " .. trip_time .. "ms") _send_api_keep_alive_ack(timestamp) @@ -428,7 +428,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) log.warning("pocket supervisor KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)") end - log.debug("pocket supervisor TT = " .. trip_time .. "ms") + -- log.debug("pocket supervisor TT = " .. trip_time .. "ms") _send_sv_keep_alive_ack(timestamp) diff --git a/pocket/startup.lua b/pocket/startup.lua index 8400e3b..622ee78 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -18,7 +18,7 @@ local iocontrol = require("pocket.iocontrol") local pocket = require("pocket.pocket") local renderer = require("pocket.renderer") -local POCKET_VERSION = "v0.7.4-alpha" +local POCKET_VERSION = "v0.7.5-alpha" local println = util.println local println_ts = util.println_ts From 23b31e004978a3dc88327b315838106c831af221 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 13 Apr 2024 14:47:20 -0400 Subject: [PATCH 31/41] #410 pocket nav overhaul --- graphics/element.lua | 2 +- graphics/elements/appmultipane.lua | 110 +++++++++++ .../elements/controls/app_page_selector.lua | 75 ------- pocket/iocontrol.lua | 184 ++++++++++++------ pocket/ui/apps/diag_apps.lua | 8 +- .../boiler_page.lua => apps/dummy_app.lua} | 16 +- pocket/ui/main.lua | 27 +-- pocket/ui/pages/home_page.lua | 60 +++--- pocket/ui/pages/reactor_page.lua | 28 --- pocket/ui/pages/turbine_page.lua | 28 --- pocket/ui/pages/unit_page.lua | 5 +- 11 files changed, 288 insertions(+), 255 deletions(-) create mode 100644 graphics/elements/appmultipane.lua delete mode 100644 graphics/elements/controls/app_page_selector.lua rename pocket/ui/{pages/boiler_page.lua => apps/dummy_app.lua} (53%) delete mode 100644 pocket/ui/pages/reactor_page.lua delete mode 100644 pocket/ui/pages/turbine_page.lua diff --git a/graphics/element.lua b/graphics/element.lua index 4522f49..c7cc056 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -29,7 +29,6 @@ local element = {} ---|checkbox_args ---|hazard_button_args ---|multi_button_args ----|app_page_selector_args ---|push_button_args ---|radio_2d_args ---|radio_button_args @@ -54,6 +53,7 @@ local element = {} ---|state_indicator_args ---|tristate_indicator_light_args ---|vbar_args +---|app_multipane_args ---|colormap_args ---|displaybox_args ---|div_args diff --git a/graphics/elements/appmultipane.lua b/graphics/elements/appmultipane.lua new file mode 100644 index 0000000..0795e43 --- /dev/null +++ b/graphics/elements/appmultipane.lua @@ -0,0 +1,110 @@ +-- App Page Multi-Pane Display Graphics Element + +local util = require("scada-common.util") + +local core = require("graphics.core") +local element = require("graphics.element") +local events = require("graphics.events") + +local MOUSE_CLICK = core.events.MOUSE_CLICK + +---@class app_multipane_args +---@field panes table panes to swap between +---@field nav_colors cpair on/off colors (a/b respectively) for page navigator +---@field scroll_nav boolean? true to allow scrolling to change the active pane +---@field drag_nav boolean? true to allow mouse dragging to change the active pane (on mouse up) +---@field callback function? function to call when scrolling or dragging changes the pane +---@field parent graphics_element +---@field id? string element id +---@field x? integer 1 if omitted +---@field y? integer auto incremented if omitted +---@field width? integer parent width if omitted +---@field height? integer parent height if omitted +---@field gframe? graphics_frame frame instead of x/y/width/height +---@field fg_bg? cpair foreground/background colors +---@field hidden? boolean true to hide on initial draw + +-- new app multipane element +---@nodiscard +---@param args app_multipane_args +---@return graphics_element element, element_id id +local function multipane(args) + element.assert(type(args.panes) == "table", "panes is a required field") + + -- create new graphics element base object + local e = element.new(args) + + e.value = 1 + + local nav_x_start = math.floor((e.frame.w / 2) - (#args.panes / 2)) + 1 + local nav_x_end = math.floor((e.frame.w / 2) - (#args.panes / 2)) + #args.panes + + -- show the selected pane + function e.redraw() + for i = 1, #args.panes do args.panes[i].hide() end + args.panes[e.value].show() + + for i = 1, #args.panes do + e.w_set_cur(nav_x_start + (i - 1), e.frame.h) + e.w_set_fgd(util.trinary(i == e.value, args.nav_colors.color_a, args.nav_colors.color_b)) + e.w_write("\x07") + end + end + + -- handle mouse interaction + ---@param event mouse_interaction mouse event + function e.handle_mouse(event) + local initial = e.value + + if e.enabled then + if event.current.y == e.frame.h and event.current.x >= nav_x_start and event.current.x <= nav_x_end then + local id = event.current.x - nav_x_start + 1 + + if event.type == MOUSE_CLICK.TAP then + e.set_value(id) + args.callback(e.value) + elseif event.type == MOUSE_CLICK.UP then + e.set_value(id) + args.callback(e.value) + end + end + end + + if args.scroll_nav then + if event.type == events.MOUSE_CLICK.SCROLL_UP then + e.set_value(e.value + 1) + elseif event.type == events.MOUSE_CLICK.SCROLL_DOWN then + e.set_value(e.value - 1) + end + end + + if args.drag_nav then + local x1, x2 = event.initial.x, event.current.x + if event.type == events.MOUSE_CLICK.UP and e.in_frame_bounds(x1, event.initial.y) and e.in_frame_bounds(x1, event.current.y) then + if x2 > x1 then + e.set_value(e.value - 1) + elseif x2 < x1 then + e.set_value(e.value + 1) + end + end + end + + if e.value ~= initial and type(args.callback) == "function" then args.callback(e.value) end + end + + -- select which pane is shown + ---@param value integer pane to show + function e.set_value(value) + if (e.value ~= value) and (value > 0) and (value <= #args.panes) then + e.value = value + e.redraw() + end + end + + -- initial draw + e.redraw() + + return e.complete() +end + +return multipane diff --git a/graphics/elements/controls/app_page_selector.lua b/graphics/elements/controls/app_page_selector.lua deleted file mode 100644 index 1a57bff..0000000 --- a/graphics/elements/controls/app_page_selector.lua +++ /dev/null @@ -1,75 +0,0 @@ --- App Page Selector Graphics Element - -local util = require("scada-common.util") - -local core = require("graphics.core") -local element = require("graphics.element") - -local MOUSE_CLICK = core.events.MOUSE_CLICK - ----@class app_page_selector_args ----@field page_count integer number of pages (will become this element's width) ----@field active_color color on/off colors (a/b respectively) ----@field callback function function to call on touch ----@field parent graphics_element ----@field id? string element id ----@field x? integer 1 if omitted ----@field y? integer auto incremented if omitted ----@field fg_bg? cpair foreground/background colors ----@field hidden? boolean true to hide on initial draw - --- new app page selector ----@param args app_page_selector_args ----@return graphics_element element, element_id id -local function app_page_selector(args) - element.assert(util.is_int(args.page_count), "page_count is a required field") - element.assert(util.is_int(args.active_color), "active_color is a required field") - element.assert(type(args.callback) == "function", "callback is a required field") - - args.height = 1 - args.width = args.page_count - - -- create new graphics element base object - local e = element.new(args) - - e.value = 1 - - -- draw dot selectors - function e.redraw() - for i = 1, args.page_count do - e.w_set_cur(i, 1) - e.w_set_fgd(util.trinary(i == e.value, args.active_color, e.fg_bg.fgd)) - e.w_write("\x07") - end - end - - -- handle mouse interaction - ---@param event mouse_interaction mouse event - function e.handle_mouse(event) - if e.enabled then - if event.type == MOUSE_CLICK.TAP then - e.set_value(event.current.x) - args.callback(e.value) - elseif event.type == MOUSE_CLICK.UP then - if e.in_frame_bounds(event.current.x, event.current.y) then - e.set_value(event.current.x) - args.callback(e.value) - end - end - end - end - - -- set the value (does not call the callback) - ---@param val integer new value - function e.set_value(val) - e.value = val - e.redraw() - end - - -- initial draw - e.redraw() - - return e.complete() -end - -return app_page_selector diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 7efc7a5..1da4fc6 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -3,6 +3,7 @@ -- local psil = require("scada-common.psil") +local log = require("scada-common.log") local types = require("scada-common.types") @@ -24,97 +25,154 @@ local LINK_STATE = { iocontrol.LINK_STATE = LINK_STATE +---@enum POCKET_APP_ID +local APP_ID = { + ROOT = 1, + UNITS = 2, + ALARMS = 3, + DUMMY = 4, + NUM_APPS = 4 +} + +iocontrol.APP_ID = APP_ID + ---@class pocket_ioctl local io = { - nav_root = nil, ---@type nav_tree_node ps = psil.create() } ----@class nav_tree_node ----@field _p nav_tree_node|nil page's parent +---@class nav_tree_page +---@field _p nav_tree_page|nil page's parent ---@field _c table page's children ----@field pane_elem graphics_element|nil multipane for this branch ----@field pane_id integer this page's ID in it's contained pane ----@field switcher function|nil function to switch this page's active multipane ---@field nav_to function function to navigate to this page ----@field tasks table tasks to run on this page +---@field switcher function|nil function to switch between children +---@field tasks table tasks to run while viewing this page --- allocate the page navigation tree system
--- navigation is not ready until init_nav has been called +-- allocate the page navigation system function iocontrol.alloc_nav() local self = { - root = { _p = nil, _c = {}, pane_id = 0, pane_elem = nil, nav_to = function () end, tasks = {} }, ---@type nav_tree_node - cur_page = nil ---@type nav_tree_node + pane = nil, ---@type graphics_element + apps = {}, + containers = {}, + cur_app = APP_ID.ROOT } - function self.root.switcher(pane_id) - if self.root._c[pane_id] then self.root._c[pane_id].nav_to() end - end - - -- find the pane this element belongs to - ---@param parent nav_tree_node - local function _find_pane(parent) - if parent == nil then - return nil - elseif parent.pane_elem then - return parent.pane_elem - else - return _find_pane(parent._p) - end - end - self.cur_page = self.root ---@class pocket_nav io.nav = {} - -- create a new page entry in the page navigation tree - ---@param parent nav_tree_node? a parent page or nil to use the root - ---@param pane_id integer the pane number for this page in it's parent's multipane - ---@param pane graphics_element? this page's multipane, if it has children - ---@return nav_tree_node new_page this new page - function io.nav.new_page(parent, pane_id, pane) - local page = { _p = parent or self.root, _c = {}, pane_id = pane_id, pane_elem = pane, tasks = {} } - page._p._c[pane_id] = page + -- set the root pane element to switch between apps with + ---@param root_pane graphics_element + function io.nav.set_pane(root_pane) + self.pane = root_pane + end - function page.nav_to() - local p_pane = _find_pane(page._p) - if p_pane then p_pane.set_value(page.pane_id) end - self.cur_page = page + -- register an app + ---@param app_id POCKET_APP_ID app ID + ---@param container graphics_element element that contains this app (usually a Div) + ---@param pane graphics_element? multipane if this is a simple paned app, then nav_to must be a number + function io.nav.register_app(app_id, container, pane) + ---@class pocket_app + local app = { + root = { _p = nil, _c = {}, nav_to = function () end, tasks = {} }, ---@type nav_tree_page + cur_page = nil, ---@type nav_tree_page + paned_pages = {} + } + + -- if a pane was provided, this will switch between numbered pages + ---@param idx integer page index + function app.switcher(idx) + if app.paned_pages[idx] then + app.paned_pages[idx].nav_to() + end end - if pane then - function page.switcher() if page._c[pane_id] then page._c[pane_id].nav_to() end end + -- create a new page entry in the page navigation tree + ---@param parent nav_tree_page? a parent page or nil to set this as the root + ---@param nav_to function|integer function to navigate to this page or pane index + ---@return nav_tree_page new_page this new page + function app.new_page(parent, nav_to) + ---@type nav_tree_page + local page = { _p = parent, _c = {}, nav_to = function () end, switcher = function () end, tasks = {} } + + if parent == nil then + app.root = page + if app.cur_page == nil then app.cur_page = page end + end + + if type(nav_to) == "number" then + app.paned_pages[nav_to] = page + + function page.nav_to() + app.cur_page = page + if pane then pane.set_value(nav_to) end + end + else + function page.nav_to() + app.cur_page = page + nav_to() + end + end + + -- switch between children + ---@param id integer child ID + function page.switcher(id) if page._c[id] then page._c[id].nav_to() end end + + if parent ~= nil then + table.insert(page._p._c, page) + end + + return page end - return page + -- get the currently active page + function app.get_current_page() return app.cur_page end + + -- attempt to navigate up the tree + ---@return boolean success true if successfully navigated up + function app.nav_up() + local parent = app.cur_page._p + if parent then parent.nav_to() end + return parent ~= nil + end + + self.apps[app_id] = app + self.containers[app_id] = container + + return app + end + + -- get a list of the app containers (usually Div elements) + function io.nav.get_containers() return self.containers end + + -- open a given app + ---@param app_id POCKET_APP_ID + function io.nav.open_app(app_id) + if self.apps[app_id] then + self.cur_app = app_id + self.pane.set_value(app_id) + else + log.debug("tried to open unknown app") + end end -- get the currently active page - function io.nav.get_current_page() return self.cur_page end - - -- attempt to navigate up the tree - function io.nav.nav_up() - local parent = self.cur_page._p - -- if a parent is defined and this element is not root - if parent then parent.nav_to() end + ---@return nav_tree_page + function io.nav.get_current_page() + return self.apps[self.cur_app].get_current_page() end - io.nav_root = self.root -end + -- attempt to navigate up + function io.nav.nav_up() + local app = self.apps[self.cur_app] ---@type pocket_app + log.debug("attempting app nav up for app " .. self.cur_app) --- complete initialization of navigation by providing the root muiltipane ----@param root_pane graphics_element navigation root multipane ----@param default_page integer? page to nagivate to if nav_up is called on a base node -function iocontrol.init_nav(root_pane, default_page) - io.nav_root.pane_elem = root_pane - - ---@todo keep this? - -- if default_page ~= nil then - -- io.nav_root.nav_to = function() io.nav_root.switcher(default_page) end - -- end - - return io.nav_root + if not app.nav_up() then + log.debug("internal app nav up failed, going to home screen") + io.nav.open_app(APP_ID.ROOT) + end + end end -- initialize facility-independent components of pocket iocontrol diff --git a/pocket/ui/apps/diag_apps.lua b/pocket/ui/apps/diag_apps.lua index f0d1114..ec8b5e6 100644 --- a/pocket/ui/apps/diag_apps.lua +++ b/pocket/ui/apps/diag_apps.lua @@ -29,7 +29,11 @@ local function create_pages(root) ------------------------ local alarm_test = Div{parent=root,x=1,y=1} - local alarm_tasks = { db.diag.tone_test.get_tone_states } + + local alarm_app = db.nav.register_app(iocontrol.APP_ID.ALARMS, alarm_test) + + local page = alarm_app.new_page(nil, function () end) + page.tasks = { db.diag.tone_test.get_tone_states } local ttest = db.diag.tone_test @@ -107,8 +111,6 @@ local function create_pages(root) local t_8 = IndicatorLight{parent=states,x=6,label="8",colors=c_blue_gray} ttest.tone_indicators = { t_1, t_2, t_3, t_4, t_5, t_6, t_7, t_8 } - - return { Alarm = { e = alarm_test, tasks = alarm_tasks } } end return create_pages diff --git a/pocket/ui/pages/boiler_page.lua b/pocket/ui/apps/dummy_app.lua similarity index 53% rename from pocket/ui/pages/boiler_page.lua rename to pocket/ui/apps/dummy_app.lua index c374fdf..fe21db3 100644 --- a/pocket/ui/pages/boiler_page.lua +++ b/pocket/ui/apps/dummy_app.lua @@ -1,5 +1,5 @@ -- --- Boiler Detail Page +-- Placeholder App -- local iocontrol = require("pocket.iocontrol") @@ -9,20 +9,16 @@ local core = require("graphics.core") local Div = require("graphics.elements.div") local TextBox = require("graphics.elements.textbox") -local ALIGN = core.ALIGN - --- new boiler page view +-- create placeholder app page ---@param root graphics_element parent -local function new_view(root) +local function create_pages(root) local db = iocontrol.get_db() - db.nav.new_page(nil, 4) - local main = Div{parent=root,x=1,y=1} - TextBox{parent=main,text="BOILERS",x=1,y=1,height=1,alignment=ALIGN.CENTER} + db.nav.register_app(iocontrol.APP_ID.DUMMY, main).new_page(nil, function () end) - return main + TextBox{parent=main,text="This app is not implemented yet.",x=1,y=2,alignment=core.ALIGN.CENTER} end -return new_view +return create_pages diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index a886491..110998f 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -4,16 +4,16 @@ local iocontrol = require("pocket.iocontrol") -local style = require("pocket.ui.style") +local diag_apps = require("pocket.ui.apps.diag_apps") +local dummy_app = require("pocket.ui.apps.dummy_app") local conn_waiting = require("pocket.ui.components.conn_waiting") -local boiler_page = require("pocket.ui.pages.boiler_page") local home_page = require("pocket.ui.pages.home_page") -local reactor_page = require("pocket.ui.pages.reactor_page") -local turbine_page = require("pocket.ui.pages.turbine_page") local unit_page = require("pocket.ui.pages.unit_page") +local style = require("pocket.ui.style") + local core = require("graphics.core") local Div = require("graphics.elements.div") @@ -71,18 +71,21 @@ local function init(main) local page_div = Div{parent=main_pane,x=4,y=1} local sidebar_tabs = { - { char = "#", color = cpair(colors.black,colors.green) }, - { char = "U", color = cpair(colors.black,colors.yellow) }, - { char = "R", color = cpair(colors.black,colors.cyan) }, - { char = "B", color = cpair(colors.black,colors.lightGray) }, - { char = "T", color = cpair(colors.black,colors.white) } + { char = "#", color = cpair(colors.black,colors.green) } } - local page_pane = MultiPane{parent=page_div,x=1,y=1,panes={home_page(page_div),unit_page(page_div),reactor_page(page_div),boiler_page(page_div),turbine_page(page_div)}} + home_page(page_div) + unit_page(page_div) - local base = iocontrol.init_nav(page_pane) + diag_apps(page_div) + dummy_app(page_div) - Sidebar{parent=main_pane,x=1,y=1,tabs=sidebar_tabs,fg_bg=cpair(colors.white,colors.gray),callback=base.switcher} + assert(#db.nav.get_containers() == iocontrol.APP_ID.NUM_APPS, "app IDs were not sequential or some apps weren't registered") + + local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=db.nav.get_containers()} + db.nav.set_pane(page_pane) + + Sidebar{parent=main_pane,x=1,y=1,tabs=sidebar_tabs,fg_bg=cpair(colors.white,colors.gray),callback=db.nav.open_app} PushButton{parent=main_pane,x=1,y=19,text="\x1b",min_width=3,fg_bg=cpair(colors.white,colors.gray),active_fg_bg=cpair(colors.gray,colors.black),callback=db.nav.nav_up} diff --git a/pocket/ui/pages/home_page.lua b/pocket/ui/pages/home_page.lua index c4c1e4a..677be18 100644 --- a/pocket/ui/pages/home_page.lua +++ b/pocket/ui/pages/home_page.lua @@ -2,21 +2,20 @@ -- Main Home Page -- -local iocontrol = require("pocket.iocontrol") +local iocontrol = require("pocket.iocontrol") -local diag_apps = require("pocket.ui.apps.diag_apps") +local core = require("graphics.core") -local core = require("graphics.core") +local AppMultiPane = require("graphics.elements.appmultipane") +local Div = require("graphics.elements.div") +local TextBox = require("graphics.elements.textbox") -local Div = require("graphics.elements.div") -local MultiPane = require("graphics.elements.multipane") -local TextBox = require("graphics.elements.textbox") - -local AppPageSel = require("graphics.elements.controls.app_page_selector") -local App = require("graphics.elements.controls.app") +local App = require("graphics.elements.controls.app") local cpair = core.cpair +local APP_ID = iocontrol.APP_ID + local ALIGN = core.ALIGN -- new home page view @@ -24,42 +23,37 @@ local ALIGN = core.ALIGN local function new_view(root) local db = iocontrol.get_db() - local main = Div{parent=root,x=1,y=1} + local main = Div{parent=root,x=1,y=1,height=19} - local apps = Div{parent=main,x=1,y=1,height=19} - - local apps_1 = Div{parent=apps,x=1,y=1,height=15} - local apps_2 = Div{parent=apps,x=1,y=1,height=15} + local apps_1 = Div{parent=main,x=1,y=1,height=15} + local apps_2 = Div{parent=main,x=1,y=1,height=15} local panes = { apps_1, apps_2 } - local app_pane = MultiPane{parent=apps,x=1,y=1,panes=panes,height=15} + local f_ref = {} + local app_pane = AppMultiPane{parent=main,x=1,y=1,height=18,panes=panes,active_color=colors.lightGray,nav_colors=cpair(colors.lightGray,colors.gray),scroll_nav=true,drag_nav=true,callback=function(v)f_ref.callback(v)end} - AppPageSel{parent=apps,x=11,y=18,page_count=2,active_color=colors.lightGray,callback=app_pane.set_value,fg_bg=cpair(colors.gray,colors.black)} + local app = db.nav.register_app(iocontrol.APP_ID.ROOT, main, app_pane) + f_ref.callback = app.switcher - local d_apps = diag_apps(main) + app.new_page(app.new_page(nil, 1), 2) - local page_panes = { apps, d_apps.Alarm.e } + local function open(id) db.nav.open_app(id) end - local page_pane = MultiPane{parent=main,x=1,y=1,panes=page_panes} + local active_fg_bg = cpair(colors.white,colors.gray) - local npage_home = db.nav.new_page(nil, 1, page_pane) - local npage_apps = db.nav.new_page(npage_home, 1) - - local npage_alarm = db.nav.new_page(npage_apps, 2) - npage_alarm.tasks = d_apps.Alarm.tasks - - App{parent=apps_1,x=3,y=2,text="\x17",title="PRC",callback=function()end,app_fg_bg=cpair(colors.black,colors.purple)} - App{parent=apps_1,x=10,y=2,text="\x15",title="CTL",callback=function()end,app_fg_bg=cpair(colors.black,colors.green)} - App{parent=apps_1,x=17,y=2,text="\x08",title="DEV",callback=function()end,app_fg_bg=cpair(colors.black,colors.lightGray)} - App{parent=apps_1,x=3,y=7,text="\x7f",title="Waste",callback=function()end,app_fg_bg=cpair(colors.black,colors.brown)} - App{parent=apps_1,x=10,y=7,text="\xb6",title="Guide",callback=function()end,app_fg_bg=cpair(colors.black,colors.cyan)} + App{parent=apps_1,x=3,y=2,text="U",title="Units",callback=function()open(APP_ID.UNITS)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg} + App{parent=apps_1,x=10,y=2,text="\x17",title="PRC",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.purple),active_fg_bg=active_fg_bg} + App{parent=apps_1,x=17,y=2,text="\x15",title="CTL",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.green),active_fg_bg=active_fg_bg} + App{parent=apps_1,x=3,y=7,text="\x08",title="DEV",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=active_fg_bg} + App{parent=apps_1,x=10,y=7,text="\x7f",title="Waste",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.brown),active_fg_bg=active_fg_bg} + App{parent=apps_1,x=17,y=7,text="\xb6",title="Guide",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg} TextBox{parent=apps_2,text="Diagnostic Apps",x=1,y=2,height=1,alignment=ALIGN.CENTER} - App{parent=apps_2,x=3,y=4,text="\x0f",title="Alarm",callback=npage_alarm.nav_to,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)} - App{parent=apps_2,x=10,y=4,text="\x1e",title="LoopT",callback=function()end,app_fg_bg=cpair(colors.black,colors.cyan)} - App{parent=apps_2,x=17,y=4,text="@",title="Comps",callback=function()end,app_fg_bg=cpair(colors.black,colors.orange)} + App{parent=apps_2,x=3,y=4,text="\x0f",title="Alarm",callback=function()open(APP_ID.ALARMS)end,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=active_fg_bg} + App{parent=apps_2,x=10,y=4,text="\x1e",title="LoopT",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg} + App{parent=apps_2,x=17,y=4,text="@",title="Comps",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg} return main end diff --git a/pocket/ui/pages/reactor_page.lua b/pocket/ui/pages/reactor_page.lua deleted file mode 100644 index 95d5b9e..0000000 --- a/pocket/ui/pages/reactor_page.lua +++ /dev/null @@ -1,28 +0,0 @@ --- --- Reactor Detail Page --- - -local iocontrol = require("pocket.iocontrol") - -local core = require("graphics.core") - -local Div = require("graphics.elements.div") -local TextBox = require("graphics.elements.textbox") - -local ALIGN = core.ALIGN - --- new reactor page view ----@param root graphics_element parent -local function new_view(root) - local db = iocontrol.get_db() - - db.nav.new_page(nil, 3) - - local main = Div{parent=root,x=1,y=1} - - TextBox{parent=main,text="REACTOR",x=1,y=1,height=1,alignment=ALIGN.CENTER} - - return main -end - -return new_view diff --git a/pocket/ui/pages/turbine_page.lua b/pocket/ui/pages/turbine_page.lua deleted file mode 100644 index 266653c..0000000 --- a/pocket/ui/pages/turbine_page.lua +++ /dev/null @@ -1,28 +0,0 @@ --- --- Turbine Detail Page --- - -local iocontrol = require("pocket.iocontrol") - -local core = require("graphics.core") - -local Div = require("graphics.elements.div") -local TextBox = require("graphics.elements.textbox") - -local ALIGN = core.ALIGN - --- new turbine page view ----@param root graphics_element parent -local function new_view(root) - local db = iocontrol.get_db() - - db.nav.new_page(nil, 5) - - local main = Div{parent=root,x=1,y=1} - - TextBox{parent=main,text="TURBINES",x=1,y=1,height=1,alignment=ALIGN.CENTER} - - return main -end - -return new_view diff --git a/pocket/ui/pages/unit_page.lua b/pocket/ui/pages/unit_page.lua index 72b4da2..fc22e4e 100644 --- a/pocket/ui/pages/unit_page.lua +++ b/pocket/ui/pages/unit_page.lua @@ -16,10 +16,11 @@ local ALIGN = core.ALIGN local function new_view(root) local db = iocontrol.get_db() - db.nav.new_page(nil, 2) - local main = Div{parent=root,x=1,y=1} + local app = db.nav.register_app(iocontrol.APP_ID.UNITS, main) + app.new_page(nil, function () end) + TextBox{parent=main,text="UNITS",x=1,y=1,height=1,alignment=ALIGN.CENTER} return main From f22d699baf30905eec6e951440d1e6db4104c9cf Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 13 Apr 2024 16:14:38 -0400 Subject: [PATCH 32/41] flipped app pane scroll direction --- graphics/elements/appmultipane.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphics/elements/appmultipane.lua b/graphics/elements/appmultipane.lua index 0795e43..fc56441 100644 --- a/graphics/elements/appmultipane.lua +++ b/graphics/elements/appmultipane.lua @@ -71,9 +71,9 @@ local function multipane(args) end if args.scroll_nav then - if event.type == events.MOUSE_CLICK.SCROLL_UP then + if event.type == events.MOUSE_CLICK.SCROLL_DOWN then e.set_value(e.value + 1) - elseif event.type == events.MOUSE_CLICK.SCROLL_DOWN then + elseif event.type == events.MOUSE_CLICK.SCROLL_UP then e.set_value(e.value - 1) end end From 57dc20692b09b238b6bc35dead741656043bd194 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 13 Apr 2024 16:15:20 -0400 Subject: [PATCH 33/41] need to use a compact signal bar due to how edge extensions work --- graphics/elements/indicators/signal.lua | 29 ++++++++++++++++++------- pocket/ui/main.lua | 6 ++--- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/graphics/elements/indicators/signal.lua b/graphics/elements/indicators/signal.lua index fb21ca0..7e9c1b8 100644 --- a/graphics/elements/indicators/signal.lua +++ b/graphics/elements/indicators/signal.lua @@ -5,6 +5,7 @@ local util = require("scada-common.util") local element = require("graphics.element") ---@class signal_bar_args +---@field compact? boolean true to use a single character (works better against edges that extend out colors) ---@field colors_low_med? cpair color a for low signal quality, color b for medium signal quality ---@field disconnect_color? color color for the 'x' on disconnect ---@field parent graphics_element @@ -20,7 +21,7 @@ local element = require("graphics.element") ---@return graphics_element element, element_id id local function signal_bar(args) args.height = 1 - args.width = 2 + args.width = util.trinary(args.compact, 1, 2) -- create new graphics element base object local e = element.new(args) @@ -52,14 +53,26 @@ local function signal_bar(args) function e.redraw() e.w_set_cur(1, 1) - if e.value == 1 then - e.w_blit("\x9f ", blit_bkg .. blit_bkg, blit_1 .. blit_bkg) - elseif e.value == 2 then - e.w_blit("\x9f\x94", blit_bkg .. blit_2, blit_2 .. blit_bkg) - elseif e.value == 3 then - e.w_blit("\x9f\x81", blit_bkg .. blit_bkg, blit_3 .. blit_3) + if args.compact then + if e.value == 1 then + e.w_blit("\x90", blit_1, blit_bkg) + elseif e.value == 2 then + e.w_blit("\x94", blit_2, blit_bkg) + elseif e.value == 3 then + e.w_blit("\x95", blit_3, blit_bkg) + else + e.w_blit("x", blit_0, blit_bkg) + end else - e.w_blit(" x", blit_0 .. blit_0, blit_bkg .. blit_bkg) + if e.value == 1 then + e.w_blit("\x9f ", blit_bkg .. blit_bkg, blit_1 .. blit_bkg) + elseif e.value == 2 then + e.w_blit("\x9f\x94", blit_bkg .. blit_2, blit_2 .. blit_bkg) + elseif e.value == 3 then + e.w_blit("\x9f\x81", blit_bkg .. blit_bkg, blit_3 .. blit_3) + else + e.w_blit(" x", blit_0 .. blit_0, blit_bkg .. blit_bkg) + end end end diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index 110998f..1667693 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -37,9 +37,9 @@ local function init(main) local db = iocontrol.get_db() -- window header message - TextBox{parent=main,y=1,text="DEV ALPHA APP S C ",alignment=ALIGN.LEFT,height=1,fg_bg=style.header} - local svr_conn = SignalBar{parent=main,y=1,x=20,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.white,colors.gray)} - local crd_conn = SignalBar{parent=main,y=1,x=24,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.white,colors.gray)} + TextBox{parent=main,y=1,text="DEV ALPHA APP S C ",alignment=ALIGN.LEFT,height=1,fg_bg=style.header} + local svr_conn = SignalBar{parent=main,y=1,x=22,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)} + local crd_conn = SignalBar{parent=main,y=1,x=26,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)} db.ps.subscribe("svr_conn_quality", svr_conn.set_value) db.ps.subscribe("crd_conn_quality", crd_conn.set_value) From 9fe34648c2393dec4a151cfde43655e434e30d3a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 13 Apr 2024 16:18:27 -0400 Subject: [PATCH 34/41] added system information/about app --- pocket/iocontrol.lua | 25 ++++++--- pocket/startup.lua | 3 + pocket/ui/apps/sys_apps.lua | 102 ++++++++++++++++++++++++++++++++++ pocket/ui/main.lua | 2 + pocket/ui/pages/home_page.lua | 10 ++-- 5 files changed, 130 insertions(+), 12 deletions(-) create mode 100644 pocket/ui/apps/sys_apps.lua diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 1da4fc6..491ef05 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -2,9 +2,8 @@ -- I/O Control for Pocket Integration with Supervisor & Coordinator -- -local psil = require("scada-common.psil") -local log = require("scada-common.log") - +local log = require("scada-common.log") +local psil = require("scada-common.psil") local types = require("scada-common.types") local ALARM = types.ALARM @@ -28,16 +27,21 @@ iocontrol.LINK_STATE = LINK_STATE ---@enum POCKET_APP_ID local APP_ID = { ROOT = 1, + -- main app page UNITS = 2, - ALARMS = 3, - DUMMY = 4, - NUM_APPS = 4 + ABOUT = 3, + -- diag app page + ALARMS = 4, + -- other + DUMMY = 5, + NUM_APPS = 5 } iocontrol.APP_ID = APP_ID ---@class pocket_ioctl local io = { + version = "unknown", ps = psil.create() } @@ -77,9 +81,16 @@ function iocontrol.alloc_nav() local app = { root = { _p = nil, _c = {}, nav_to = function () end, tasks = {} }, ---@type nav_tree_page cur_page = nil, ---@type nav_tree_page + pane = pane, paned_pages = {} } + -- delayed set of the pane if it wasn't ready at the start + ---@param root_pane graphics_element multipane + function app.set_root_pane(root_pane) + app.pane = root_pane + end + -- if a pane was provided, this will switch between numbered pages ---@param idx integer page index function app.switcher(idx) @@ -106,7 +117,7 @@ function iocontrol.alloc_nav() function page.nav_to() app.cur_page = page - if pane then pane.set_value(nav_to) end + if app.pane then app.pane.set_value(nav_to) end end else function page.nav_to() diff --git a/pocket/startup.lua b/pocket/startup.lua index 622ee78..3f1d8c3 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -68,6 +68,9 @@ local function main() -- mount connected devices ppm.mount_all() + -- record version for GUI + iocontrol.get_db().version = POCKET_VERSION + ---------------------------------------- -- setup communications & clocks ---------------------------------------- diff --git a/pocket/ui/apps/sys_apps.lua b/pocket/ui/apps/sys_apps.lua new file mode 100644 index 0000000..a48f85f --- /dev/null +++ b/pocket/ui/apps/sys_apps.lua @@ -0,0 +1,102 @@ +-- +-- System Apps +-- + +local comms = require("scada-common.comms") +local lockbox = require("lockbox") +local util = require("scada-common.util") + +local iocontrol = require("pocket.iocontrol") + +local core = require("graphics.core") + +local Div = require("graphics.elements.div") +local ListBox = require("graphics.elements.listbox") +local MultiPane = require("graphics.elements.multipane") +local TextBox = require("graphics.elements.textbox") + +local PushButton = require("graphics.elements.controls.push_button") + +local cpair = core.cpair + +local ALIGN = core.ALIGN + +-- create system app pages +---@param root graphics_element parent +local function create_pages(root) + local db = iocontrol.get_db() + + ---------------- + -- About Page -- + ---------------- + + local about_root = Div{parent=root,x=1,y=1} + + local about_app = db.nav.register_app(iocontrol.APP_ID.ABOUT, about_root) + + local about_page = about_app.new_page(nil, 1) + local fw_page = about_app.new_page(about_page, 2) + local hw_page = about_app.new_page(about_page, 3) + + local about = Div{parent=about_root,x=1,y=2} + + TextBox{parent=about,y=1,text="System Information",height=1,alignment=ALIGN.CENTER} + + local btn_fg_bg = cpair(colors.lightBlue, colors.black) + local btn_active = cpair(colors.white, colors.black) + local label = cpair(colors.lightGray, colors.black) + + PushButton{parent=about,x=2,y=3,text="Firmware >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fw_page.nav_to} + PushButton{parent=about,x=2,y=4,text="Host Details >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=hw_page.nav_to} + + local fw_div = Div{parent=about_root,x=1,y=2} + TextBox{parent=fw_div,y=1,text="Firmware Versions",height=1,alignment=ALIGN.CENTER} + + PushButton{parent=fw_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to} + + local fw_list_box = ListBox{parent=fw_div,x=1,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + + local fw_list = Div{parent=fw_list_box,x=1,y=2,height=18} + + TextBox{parent=fw_list,x=2,text="Pocket Version",height=1,alignment=ALIGN.LEFT,fg_bg=label} + TextBox{parent=fw_list,x=2,text=db.version,height=1,alignment=ALIGN.LEFT} + + fw_list.line_break() + TextBox{parent=fw_list,x=2,text="Comms Version",height=1,alignment=ALIGN.LEFT,fg_bg=label} + TextBox{parent=fw_list,x=2,text=comms.version,height=1,alignment=ALIGN.LEFT} + + fw_list.line_break() + TextBox{parent=fw_list,x=2,text="API Version",height=1,alignment=ALIGN.LEFT,fg_bg=label} + TextBox{parent=fw_list,x=2,text=comms.api_version,height=1,alignment=ALIGN.LEFT} + + fw_list.line_break() + TextBox{parent=fw_list,x=2,text="Common Lib Version",height=1,alignment=ALIGN.LEFT,fg_bg=label} + TextBox{parent=fw_list,x=2,text=util.version,height=1,alignment=ALIGN.LEFT} + + fw_list.line_break() + TextBox{parent=fw_list,x=2,text="Graphics Version",height=1,alignment=ALIGN.LEFT,fg_bg=label} + TextBox{parent=fw_list,x=2,text=core.version,height=1,alignment=ALIGN.LEFT} + + fw_list.line_break() + TextBox{parent=fw_list,x=2,text="Lockbox Version",height=1,alignment=ALIGN.LEFT,fg_bg=label} + TextBox{parent=fw_list,x=2,text=lockbox.version,height=1,alignment=ALIGN.LEFT} + + local hw_div = Div{parent=about_root,x=1,y=2} + TextBox{parent=hw_div,y=1,text="Host Versions",height=1,alignment=ALIGN.CENTER} + + PushButton{parent=hw_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to} + + hw_div.line_break() + TextBox{parent=hw_div,x=2,text="Lua Version",height=1,alignment=ALIGN.LEFT,fg_bg=label} + TextBox{parent=hw_div,x=2,text=_VERSION,height=1,alignment=ALIGN.LEFT} + + hw_div.line_break() + TextBox{parent=hw_div,x=2,text="Environment",height=1,alignment=ALIGN.LEFT,fg_bg=label} + TextBox{parent=hw_div,x=2,text=_HOST,height=6,alignment=ALIGN.LEFT} + + local root_pane = MultiPane{parent=about_root,x=1,y=1,panes={about,fw_div,hw_div}} + + about_app.set_root_pane(root_pane) +end + +return create_pages diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index 1667693..6bd2479 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -6,6 +6,7 @@ local iocontrol = require("pocket.iocontrol") local diag_apps = require("pocket.ui.apps.diag_apps") local dummy_app = require("pocket.ui.apps.dummy_app") +local sys_apps = require("pocket.ui.apps.sys_apps") local conn_waiting = require("pocket.ui.components.conn_waiting") @@ -78,6 +79,7 @@ local function init(main) unit_page(page_div) diag_apps(page_div) + sys_apps(page_div) dummy_app(page_div) assert(#db.nav.get_containers() == iocontrol.APP_ID.NUM_APPS, "app IDs were not sequential or some apps weren't registered") diff --git a/pocket/ui/pages/home_page.lua b/pocket/ui/pages/home_page.lua index 677be18..0ea7c3f 100644 --- a/pocket/ui/pages/home_page.lua +++ b/pocket/ui/pages/home_page.lua @@ -25,17 +25,16 @@ local function new_view(root) local main = Div{parent=root,x=1,y=1,height=19} + local app = db.nav.register_app(iocontrol.APP_ID.ROOT, main) + local apps_1 = Div{parent=main,x=1,y=1,height=15} local apps_2 = Div{parent=main,x=1,y=1,height=15} local panes = { apps_1, apps_2 } - local f_ref = {} - local app_pane = AppMultiPane{parent=main,x=1,y=1,height=18,panes=panes,active_color=colors.lightGray,nav_colors=cpair(colors.lightGray,colors.gray),scroll_nav=true,drag_nav=true,callback=function(v)f_ref.callback(v)end} - - local app = db.nav.register_app(iocontrol.APP_ID.ROOT, main, app_pane) - f_ref.callback = app.switcher + local app_pane = AppMultiPane{parent=main,x=1,y=1,height=18,panes=panes,active_color=colors.lightGray,nav_colors=cpair(colors.lightGray,colors.gray),scroll_nav=true,drag_nav=true,callback=app.switcher} + app.set_root_pane(app_pane) app.new_page(app.new_page(nil, 1), 2) local function open(id) db.nav.open_app(id) end @@ -48,6 +47,7 @@ local function new_view(root) App{parent=apps_1,x=3,y=7,text="\x08",title="DEV",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=active_fg_bg} App{parent=apps_1,x=10,y=7,text="\x7f",title="Waste",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.brown),active_fg_bg=active_fg_bg} App{parent=apps_1,x=17,y=7,text="\xb6",title="Guide",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg} + App{parent=apps_1,x=3,y=12,text="?",title="About",callback=function()open(APP_ID.ABOUT)end,app_fg_bg=cpair(colors.black,colors.white),active_fg_bg=active_fg_bg} TextBox{parent=apps_2,text="Diagnostic Apps",x=1,y=2,height=1,alignment=ALIGN.CENTER} From 0ca002316fa17ad647c65668070a21ea03028934 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 14 Apr 2024 15:28:38 -0400 Subject: [PATCH 35/41] work on pocket, bumped comms version for PR --- pocket/pocket.lua | 4 ++-- pocket/ui/main.lua | 2 +- pocket/ui/pages/unit_page.lua | 4 +++- scada-common/comms.lua | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 0d3b8da..711f409 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -349,8 +349,8 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) -- get configuration local conf = { num_units = fac_config[1], cooling = fac_config[2] } - ---@todo - iocontrol.init_fac(conf, public, 0) + ---@todo unit options + iocontrol.init_fac(conf, public, 1) log.info("coordinator connection established") self.establish_delay_counter = 0 diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index 6bd2479..02e33fd 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -72,7 +72,7 @@ local function init(main) local page_div = Div{parent=main_pane,x=4,y=1} local sidebar_tabs = { - { char = "#", color = cpair(colors.black,colors.green) } + { char = "#", color = cpair(colors.black, colors.green) } } home_page(page_div) diff --git a/pocket/ui/pages/unit_page.lua b/pocket/ui/pages/unit_page.lua index fc22e4e..273ad27 100644 --- a/pocket/ui/pages/unit_page.lua +++ b/pocket/ui/pages/unit_page.lua @@ -21,7 +21,9 @@ local function new_view(root) local app = db.nav.register_app(iocontrol.APP_ID.UNITS, main) app.new_page(nil, function () end) - TextBox{parent=main,text="UNITS",x=1,y=1,height=1,alignment=ALIGN.CENTER} + TextBox{parent=main,y=2,text="UNITS",height=1,alignment=ALIGN.CENTER} + + TextBox{parent=main,y=4,text="work in progress",height=1,alignment=ALIGN.CENTER} return main end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 9e2148b..fffaaee 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -17,7 +17,7 @@ local max_distance = nil local comms = {} -- protocol/data versions (protocol/data independent changes tracked by util.lua version) -comms.version = "2.4.5" +comms.version = "2.5.0" comms.api_version = "0.0.1" ---@enum PROTOCOL From d2df7db18b680f2eddcc82d22e1c0a0f20324930 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 14 Apr 2024 15:30:13 -0400 Subject: [PATCH 36/41] luacheck fixes --- pocket/iocontrol.lua | 3 +-- pocket/pocket.lua | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 491ef05..0f779c1 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -231,9 +231,8 @@ end -- initialize facility-dependent components of pocket iocontrol ---@param conf facility_conf configuration ----@param comms pocket_comms comms reference ---@param temp_scale 1|2|3|4 temperature unit (1 = K, 2 = C, 3 = F, 4 = R) -function iocontrol.init_fac(conf, comms, temp_scale) +function iocontrol.init_fac(conf, temp_scale) -- temperature unit label and conversion function (from Kelvin) if temp_scale == 2 then io.temp_label = "\xb0C" diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 711f409..63149e8 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -350,7 +350,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) local conf = { num_units = fac_config[1], cooling = fac_config[2] } ---@todo unit options - iocontrol.init_fac(conf, public, 1) + iocontrol.init_fac(conf, 1) log.info("coordinator connection established") self.establish_delay_counter = 0 From 473b0dd10da49583701c24a8dd5a19073d9b185f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 14 Apr 2024 16:16:18 -0400 Subject: [PATCH 37/41] fixes and comments --- coordinator/coordinator.lua | 2 +- coordinator/iocontrol.lua | 15 ++++++++++++++- coordinator/session/pocket.lua | 2 ++ graphics/elements/appmultipane.lua | 5 ++--- pocket/iocontrol.lua | 2 +- pocket/startup.lua | 2 +- 6 files changed, 21 insertions(+), 7 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index f4c8b69..e72cccf 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -485,7 +485,7 @@ function coordinator.comms(version, nic, sv_watchdog) _send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) elseif api_v ~= comms.api_version then if self.last_api_est_acks[src_addr] ~= ESTABLISH_ACK.BAD_API_VERSION then - log.info(util.c("dropping API establish packet with incorrect api version v", comms_v, " (expected v", comms.version, ")")) + log.info(util.c("dropping API establish packet with incorrect api version v", api_v, " (expected v", comms.api_version, ")")) end _send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_API_VERSION) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index c91e24d..1de337f 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -279,7 +279,20 @@ function iocontrol.init(conf, comms, temp_scale) }, ---@type alarms - alarms = { 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 }, + alarms = { + ALARM_STATE.INACTIVE, -- containment breach + ALARM_STATE.INACTIVE, -- containment radiation + ALARM_STATE.INACTIVE, -- reactor lost + ALARM_STATE.INACTIVE, -- damage critical + ALARM_STATE.INACTIVE, -- reactor taking damage + ALARM_STATE.INACTIVE, -- reactor over temperature + ALARM_STATE.INACTIVE, -- reactor high temperature + ALARM_STATE.INACTIVE, -- waste leak + ALARM_STATE.INACTIVE, -- waste level high + ALARM_STATE.INACTIVE, -- RPS transient + ALARM_STATE.INACTIVE, -- RCS transient + ALARM_STATE.INACTIVE -- turbine trip + }, annunciator = {}, ---@type annunciator diff --git a/coordinator/session/pocket.lua b/coordinator/session/pocket.lua index 83ebdce..7101297 100644 --- a/coordinator/session/pocket.lua +++ b/coordinator/session/pocket.lua @@ -151,6 +151,8 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout) u.has_tank }) end + + _send(CRDN_TYPE.API_GET_UNITS, data) else log.debug(log_header .. "handler received unsupported CRDN packet type " .. pkt.type) end diff --git a/graphics/elements/appmultipane.lua b/graphics/elements/appmultipane.lua index fc56441..5973182 100644 --- a/graphics/elements/appmultipane.lua +++ b/graphics/elements/appmultipane.lua @@ -13,7 +13,7 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK ---@field nav_colors cpair on/off colors (a/b respectively) for page navigator ---@field scroll_nav boolean? true to allow scrolling to change the active pane ---@field drag_nav boolean? true to allow mouse dragging to change the active pane (on mouse up) ----@field callback function? function to call when scrolling or dragging changes the pane +---@field callback function? function to call when pane is changed by mouse interaction ---@field parent graphics_element ---@field id? string element id ---@field x? integer 1 if omitted @@ -44,6 +44,7 @@ local function multipane(args) for i = 1, #args.panes do args.panes[i].hide() end args.panes[e.value].show() + -- draw page indicator dots for i = 1, #args.panes do e.w_set_cur(nav_x_start + (i - 1), e.frame.h) e.w_set_fgd(util.trinary(i == e.value, args.nav_colors.color_a, args.nav_colors.color_b)) @@ -62,10 +63,8 @@ local function multipane(args) if event.type == MOUSE_CLICK.TAP then e.set_value(id) - args.callback(e.value) elseif event.type == MOUSE_CLICK.UP then e.set_value(id) - args.callback(e.value) end end end diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 0f779c1..30f4879 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -99,7 +99,7 @@ function iocontrol.alloc_nav() end end - -- create a new page entry in the page navigation tree + -- create a new page entry in the app's page navigation tree ---@param parent nav_tree_page? a parent page or nil to set this as the root ---@param nav_to function|integer function to navigate to this page or pane index ---@return nav_tree_page new_page this new page diff --git a/pocket/startup.lua b/pocket/startup.lua index 3f1d8c3..67e2f79 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -18,7 +18,7 @@ local iocontrol = require("pocket.iocontrol") local pocket = require("pocket.pocket") local renderer = require("pocket.renderer") -local POCKET_VERSION = "v0.7.5-alpha" +local POCKET_VERSION = "v0.8.0-alpha" local println = util.println local println_ts = util.println_ts From 4c9efc78b1ff2406a7f8bae35fe03e9bdfceff63 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 14 Apr 2024 16:30:50 -0400 Subject: [PATCH 38/41] bumped up scada-common version --- scada-common/util.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scada-common/util.lua b/scada-common/util.lua index 395be22..5433ab9 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -22,7 +22,7 @@ local t_pack = table.pack local util = {} -- scada-common version -util.version = "1.2.0" +util.version = "1.2.1" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 From 48b91ac80884ddaf3e1fb2b3e0d5efdcb9d36096 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 14 Apr 2024 19:08:19 -0400 Subject: [PATCH 39/41] fixed reactor-plc configurator auth key displaying *** when not networked --- reactor-plc/configure.lua | 4 ++-- reactor-plc/startup.lua | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index 43db0a9..482a629 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -729,7 +729,7 @@ local function config_view(display) local alternate = false local inner_width = setting_list.get_width() - 1 - tool_ctl.show_key_btn.enable() + if cfg.AuthKey then tool_ctl.show_key_btn.enable() else tool_ctl.show_key_btn.disable() end tool_ctl.auth_key_value = cfg.AuthKey or "" -- to show auth key for i = 1, #fields do @@ -740,7 +740,7 @@ local function config_view(display) local raw = cfg[f[1]] local val = util.strval(raw) - if f[1] == "AuthKey" then val = string.rep("*", string.len(val)) + if f[1] == "AuthKey" and raw then val = string.rep("*", string.len(val)) elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") elseif f[1] == "EmerCoolColor" and raw ~= nil then val = rsio.color_name(raw) elseif f[1] == "FrontPanelTheme" then diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 09994cb..cf5fcbe 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc") local renderer = require("reactor-plc.renderer") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v1.7.7" +local R_PLC_VERSION = "v1.7.8" local println = util.println local println_ts = util.println_ts From 0b60dc9fa49bd6b8238a1e46e1b245ca662d5223 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 14 Apr 2024 19:24:12 -0400 Subject: [PATCH 40/41] #474 run main reactor-plc loop clock when not networked --- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 25 +++++++++++-------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index cf5fcbe..024daeb 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc") local renderer = require("reactor-plc.renderer") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v1.7.8" +local R_PLC_VERSION = "v1.7.9" local println = util.println local println_ts = util.println_ts diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 6ccf43e..22e1d86 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -71,22 +71,19 @@ function threads.thread__main(smem, init) -- blink heartbeat indicator databus.heartbeat() - -- core clock tick - if networked then - -- start next clock timer - loop_clock.start() + -- start next clock timer + loop_clock.start() - -- send updated data - if nic.is_connected() then - if plc_comms.is_linked() then - smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS) + -- send updated data + if networked and nic.is_connected() then + if plc_comms.is_linked() then + smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS) + else + if ticks_to_update == 0 then + plc_comms.send_link_req() + ticks_to_update = LINK_TICKS else - if ticks_to_update == 0 then - plc_comms.send_link_req() - ticks_to_update = LINK_TICKS - else - ticks_to_update = ticks_to_update - 1 - end + ticks_to_update = ticks_to_update - 1 end end end From 0d8d7aeb15c4b64a765b9dc745d6124985b72474 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 15 Apr 2024 10:20:48 -0400 Subject: [PATCH 41/41] readme updates --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e8f901f..567fea7 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ Configurable ComputerCraft SCADA system for multi-reactor control of Mekanism fi ![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/MikaylaFischler/cc-mek-scada/check.yml?branch=main&label=main) ![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/MikaylaFischler/cc-mek-scada/check.yml?branch=devel&label=devel) -### [Join](https://discord.gg/R9NSCkhcwt) the Discord! +### Join [the Discord](https://discord.gg/R9NSCkhcwt)! -![Discord](https://img.shields.io/discord/1129075839288496259) +![Discord](https://img.shields.io/discord/1129075839288496259?logo=Discord&logoColor=white&label=discord) ## Released Component Versions @@ -46,6 +46,12 @@ You can install this on a ComputerCraft computer using either: * `wget https://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/main/ccmsi.lua` * `pastebin get sqUN6VUb ccmsi.lua` +## Contributing + +Please reach out to me via Discord or email (or GitHub in some way) if you are thinking of making any contributions at this time. I started this project as a challenge for myself and have been enjoying having something I can work on in my own way. + +Once this is out of beta I will be more open to contributions, but for now I am hoping to keep them to a minimum as the remaining challenges are ones I am looking forward to solving. + ## [SCADA](https://en.wikipedia.org/wiki/SCADA) > Supervisory control and data acquisition (SCADA) is a control system architecture comprising computers, networked data communications and graphical user interfaces for high-level supervision of machines and processes. It also covers sensors and other devices, such as programmable logic controllers, which interface with process plant or machinery.