From 55ff9dad4bddad3fed64f50b66442fa3d5cfb023 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 11 Jul 2023 20:32:10 -0400 Subject: [PATCH] #249 coordinator handle monitor disconnects/reconnects --- coordinator/iocontrol.lua | 8 ++ coordinator/renderer.lua | 167 +++++++++++++++++++------- coordinator/startup.lua | 20 +-- coordinator/ui/layout/front_panel.lua | 15 ++- 4 files changed, 156 insertions(+), 54 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 921c626..d199e0c 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -223,6 +223,14 @@ function iocontrol.fp_has_speaker(has_speaker) io.fp.ps.publish("has_speaker", h ---@param state integer function iocontrol.fp_link_state(state) io.fp.ps.publish("link_state", state) end +-- report monitor connection state +---@param id integer unit ID or 0 for main +function iocontrol.fp_monitor_state(id, connected) + local name = "main_monitor" + if id > 0 then name = "unit_monitor_" .. id end + io.fp.ps.publish(name, connected) +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/renderer.lua b/coordinator/renderer.lua index 16ea6a9..5a6a605 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -5,6 +5,8 @@ local log = require("scada-common.log") local util = require("scada-common.util") +local iocontrol = require("coordinator.iocontrol") + local style = require("coordinator.ui.style") local pgi = require("coordinator.ui.pgi") @@ -48,24 +50,12 @@ end -- link to the monitor peripherals ---@param monitors monitors_struct -function renderer.set_displays(monitors) engine.monitors = monitors end +function renderer.set_displays(monitors) + engine.monitors = monitors --- check if the renderer is configured to use a given monitor peripheral ----@nodiscard ----@param periph table peripheral ----@return boolean is_used -function renderer.is_monitor_used(periph) - if engine.monitors ~= nil then - if engine.monitors.primary == periph then - return true - else - for _, monitor in ipairs(engine.monitors.unit_displays) do - if monitor == periph then return true end - end - end - end - - return false + -- report to front panel as connected + iocontrol.fp_monitor_state(0, true) + for i = 1, #engine.monitors.unit_displays do iocontrol.fp_monitor_state(i, true) end end -- init all displays in use by the renderer @@ -127,7 +117,7 @@ function renderer.start_fp() if not engine.fp_ready then -- show front panel view on terminal engine.ui.front_panel = DisplayBox{window=term.native(),fg_bg=style.fp.root} - panel_view(engine.ui.front_panel) + panel_view(engine.ui.front_panel, #engine.monitors.unit_displays) -- start flasher callback task flasher.run() @@ -137,30 +127,6 @@ function renderer.start_fp() end end --- start the coordinator GUI -function renderer.start_ui() - if not engine.ui_ready then - -- hide dmesg - engine.dmesg_window.setVisible(false) - - -- show main view on main monitor - engine.ui.main_display = DisplayBox{window=engine.monitors.primary,fg_bg=style.root} - main_view(engine.ui.main_display) - - -- show unit views on unit displays - for i = 1, #engine.monitors.unit_displays do - engine.ui.unit_displays[i] = DisplayBox{window=engine.monitors.unit_displays[i],fg_bg=style.root} - unit_view(engine.ui.unit_displays[i], i) - end - - -- start flasher callback task - flasher.run() - - -- report ui as ready - engine.ui_ready = true - end -end - -- close out the front panel function renderer.close_fp() if engine.fp_ready then @@ -191,6 +157,32 @@ function renderer.close_fp() end end +-- start the coordinator GUI +function renderer.start_ui() + if not engine.ui_ready then + -- hide dmesg + engine.dmesg_window.setVisible(false) + + -- show main view on main monitor + if engine.monitors.primary ~= nil then + engine.ui.main_display = DisplayBox{window=engine.monitors.primary,fg_bg=style.root} + main_view(engine.ui.main_display) + end + + -- show unit views on unit displays + for idx, display in pairs(engine.monitors.unit_displays) do + engine.ui.unit_displays[idx] = DisplayBox{window=display,fg_bg=style.root} + unit_view(engine.ui.unit_displays[idx], idx) + end + + -- start flasher callback task + flasher.run() + + -- report ui as ready + engine.ui_ready = true + end +end + -- close out the UI function renderer.close_ui() if not engine.fp_ready then @@ -200,7 +192,7 @@ function renderer.close_ui() -- delete element trees if engine.ui.main_display ~= nil then engine.ui.main_display.delete() end - for _, display in ipairs(engine.ui.unit_displays) do display.delete() end + for _, display in pairs(engine.ui.unit_displays) do display.delete() end -- report ui as not ready engine.ui_ready = false @@ -227,6 +219,95 @@ function renderer.fp_ready() return engine.fp_ready end ---@return boolean ready function renderer.ui_ready() return engine.ui_ready end +-- handle a monitor peripheral being disconnected +---@param device table monitor +---@return boolean is_used if the monitor is one of the configured monitors +function renderer.handle_disconnect(device) + local is_used = false + + if engine.monitors ~= nil then + if engine.monitors.primary == device then + if engine.ui.main_display ~= nil then + -- delete element tree and clear root UI elements + engine.ui.main_display.delete() + end + + is_used = true + engine.monitors.primary = nil + engine.ui.main_display = nil + + iocontrol.fp_monitor_state(0, false) + else + for idx, monitor in pairs(engine.monitors.unit_displays) do + if monitor == device then + if engine.ui.unit_displays[idx] ~= nil then + engine.ui.unit_displays[idx].delete() + end + + is_used = true + engine.monitors.unit_displays[idx] = nil + engine.ui.unit_displays[idx] = nil + + iocontrol.fp_monitor_state(idx, false) + break + end + end + end + end + + return is_used +end + +-- handle a monitor peripheral being reconnected +---@param name string monitor name +---@param device table monitor +---@return boolean is_used if the monitor is one of the configured monitors +function renderer.handle_reconnect(name, device) + local is_used = false + + if engine.monitors ~= nil then + if engine.monitors.primary_name == name then + is_used = true + _init_display(device) + engine.monitors.primary = device + + local disp_x, disp_y = engine.monitors.primary.getSize() + engine.dmesg_window.reposition(1, 1, disp_x, disp_y, engine.monitors.primary) + + if engine.ui_ready and (engine.ui.main_display == nil) then + engine.dmesg_window.setVisible(false) + + engine.ui.main_display = DisplayBox{window=device,fg_bg=style.root} + main_view(engine.ui.main_display) + else + engine.dmesg_window.setVisible(true) + engine.dmesg_window.redraw() + end + + iocontrol.fp_monitor_state(0, true) + else + for idx, monitor in ipairs(engine.monitors.unit_name_map) do + if monitor == name then + is_used = true + _init_display(device) + engine.monitors.unit_displays[idx] = device + + if engine.ui_ready and (engine.ui.unit_displays[idx] == nil) then + engine.ui.unit_displays[idx] = DisplayBox{window=device,fg_bg=style.root} + unit_view(engine.ui.unit_displays[idx], idx) + end + + iocontrol.fp_monitor_state(idx, true) + break + end + end + end + end + + return is_used +end + + -- handle a touch event ---@param event mouse_interaction|nil function renderer.handle_mouse(event) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 69f3891..6f279b0 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -22,7 +22,7 @@ local sounder = require("coordinator.sounder") local apisessions = require("coordinator.session.apisessions") -local COORDINATOR_VERSION = "v0.19.3" +local COORDINATOR_VERSION = "v0.20.0" local println = util.println local println_ts = util.println_ts @@ -251,13 +251,10 @@ local function main() log_sys("non-comms modem disconnected") end elseif type == "monitor" then - if renderer.is_monitor_used(device) then - ---@todo will be handled properly in #249 - -- "halt and catch fire" style handling - log_sys("lost a configured monitor, system will now exit") - break + if renderer.handle_disconnect(device) then + log_sys("lost a configured monitor") else - log_sys("lost unused monitor, ignoring") + log_sys("lost an unused monitor") end elseif type == "speaker" then log_sys("lost alarm sounder speaker") @@ -277,9 +274,12 @@ local function main() else log_sys("wired modem reconnected") end - -- elseif type == "monitor" then - ---@todo will be handled properly in #249 - -- not supported, system will exit on loss of in-use monitors + 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) diff --git a/coordinator/ui/layout/front_panel.lua b/coordinator/ui/layout/front_panel.lua index 176299e..207e213 100644 --- a/coordinator/ui/layout/front_panel.lua +++ b/coordinator/ui/layout/front_panel.lua @@ -30,7 +30,8 @@ local cpair = core.cpair -- create new front panel view ---@param panel graphics_element main displaybox -local function init(panel) +---@param num_units integer number of units (number of unit monitors) +local function init(panel, num_units) local ps = iocontrol.get_db().fp.ps TextBox{parent=panel,y=1,text="SCADA COORDINATOR",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.fp.header} @@ -67,6 +68,18 @@ local function init(panel) local comp_id = util.sprintf("(%d)", os.getComputerID()) TextBox{parent=system,x=9,y=4,width=6,height=1,text=comp_id,fg_bg=cpair(colors.lightGray,colors.ivory)} + local monitors = Div{parent=main_page,width=16,height=17,x=18,y=2} + + local main_monitor = LED{parent=monitors,label="MAIN MONITOR",colors=cpair(colors.green,colors.green_off)} + main_monitor.register(ps, "main_monitor", main_monitor.update) + + monitors.line_break() + + for i = 1, num_units do + local unit_monitor = LED{parent=monitors,label="UNIT "..i.." MONITOR",colors=cpair(colors.green,colors.green_off)} + unit_monitor.register(ps, "unit_monitor_" .. i, unit_monitor.update) + end + -- -- about footer --