From f152c37ea9f1a3e6a84c99590a262e8bace832dd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 21 Feb 2024 20:33:07 -0500 Subject: [PATCH] #387 handle resizing, improved reconnect handling, fixed disconnect detection bug --- coordinator/renderer.lua | 278 ++++++++++++++++++++++++++++----------- coordinator/startup.lua | 11 +- scada-common/ppm.lua | 11 ++ scada-common/util.lua | 2 +- 4 files changed, 221 insertions(+), 81 deletions(-) diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 07282c4..fd08c6b 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -242,43 +242,43 @@ function renderer.ui_ready() return engine.ui_ready end 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 + if not engine.monitors then return false end - is_used = true - engine.monitors.primary = nil - engine.ui.main_display = nil + 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 - iocontrol.fp_monitor_state("main", false) - elseif engine.monitors.flow == device then - if engine.ui.flow_display ~= nil then - -- delete element tree and clear root UI elements - engine.ui.flow_display.delete() - end + is_used = true + engine.monitors.primary = nil + engine.ui.main_display = nil - is_used = true - engine.monitors.flow = nil - engine.ui.flow_display = nil + iocontrol.fp_monitor_state("main", false) + elseif engine.monitors.flow == device then + if engine.ui.flow_display ~= nil then + -- delete element tree and clear root UI elements + engine.ui.flow_display.delete() + end - iocontrol.fp_monitor_state("flow", 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.flow = nil + engine.ui.flow_display = nil - is_used = true - engine.monitors.unit_displays[idx] = nil - engine.ui.unit_displays[idx] = nil - - iocontrol.fp_monitor_state(idx, false) - break + iocontrol.fp_monitor_state("flow", 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 @@ -293,52 +293,32 @@ end 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 + if not engine.monitors then return false end - local disp_x, disp_y = engine.monitors.primary.getSize() - engine.dmesg_window.reposition(1, 1, disp_x, disp_y, engine.monitors.primary) + -- note: handle_resize is a more adaptive way of re-initializing a connected monitor + -- since it can handle a monitor being reconnected that isn't the right size - if engine.ui_ready and (engine.ui.main_display == nil) then - engine.dmesg_window.setVisible(false) + if engine.monitors.primary_name == name then + is_used = true + engine.monitors.primary = device - 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 + local disp_x, disp_y = engine.monitors.primary.getSize() + engine.dmesg_window.reposition(1, 1, disp_x, disp_y, engine.monitors.primary) - iocontrol.fp_monitor_state("main", true) - elseif engine.monitors.flow_name == name then - is_used = true - _init_display(device) - engine.monitors.flow = device + renderer.handle_resize(name) + elseif engine.monitors.flow_name == name then + is_used = true + engine.monitors.flow = device - if engine.ui_ready and (engine.ui.flow_display == nil) then - engine.ui.flow_display = DisplayBox{window=device,fg_bg=style.root} - flow_view(engine.ui.flow_display) - end + renderer.handle_resize(name) + else + for idx, monitor in ipairs(engine.monitors.unit_name_map) do + if monitor == name then + is_used = true + engine.monitors.unit_displays[idx] = device - iocontrol.fp_monitor_state("flow", 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 + renderer.handle_resize(name) + break end end end @@ -346,6 +326,151 @@ function renderer.handle_reconnect(name, device) return is_used end +-- handle a monitor being resized
+-- returns if this monitor is assigned + if the assigned screen still fits +---@param name string monitor name +---@return boolean is_used, boolean is_ok +function renderer.handle_resize(name) + local is_used = false + local is_ok = true + local ui = engine.ui + + if not engine.monitors then return false, false end + + if engine.monitors.primary_name == name and engine.monitors.primary then + local device = engine.monitors.primary ---@type table + + -- this is necessary if the bottom left block was broken and on reconnect + _init_display(device) + + is_used = true + + -- resize dmesg window if needed, but don't make it thinner + local disp_w, disp_h = engine.monitors.primary.getSize() + local dmsg_w, dmsg_h = engine.dmesg_window.getSize() + if disp_h ~= dmsg_h then + engine.dmesg_window = window.reposition(1, 1, math.max(disp_w, dmsg_w), disp_h, engine.monitors.primary) + end + + if ui.main_display then + ui.main_display.delete() + ui.main_display = nil + end + + iocontrol.fp_monitor_state("main", true) + + engine.dmesg_window.setVisible(not engine.ui_ready) + + if engine.ui_ready then + 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 ui.main_display then + ui.main_display.delete() + ui.main_display = nil + end + + device.setCursorPos(1, 1) + device.setBackgroundColor(colors.black) + device.setTextColor(colors.red) + device.clear() + device.write("monitor too small") + + iocontrol.fp_monitor_state("main", false) + is_ok = false + end + else engine.dmesg_window.redraw() end + elseif engine.monitors.flow_name == name and engine.monitors.flow then + local device = engine.monitors.flow ---@type table + + -- this is necessary if the bottom left block was broken and on reconnect + _init_display(device) + + is_used = true + + if ui.flow_display then + ui.flow_display.delete() + ui.flow_display = nil + end + + iocontrol.fp_monitor_state("flow", true) + + if engine.ui_ready then + engine.dmesg_window.setVisible(false) + + 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 ui.flow_display then + ui.flow_display.delete() + ui.flow_display = nil + end + + device.setCursorPos(1, 1) + device.setBackgroundColor(colors.black) + device.setTextColor(colors.red) + device.clear() + device.write("monitor too small") + + iocontrol.fp_monitor_state("flow", false) + is_ok = false + end + end + else + for idx, monitor in ipairs(engine.monitors.unit_name_map) do + local device = engine.monitors.unit_displays[idx] + + if monitor == name and device then + -- this is necessary if the bottom left block was broken and on reconnect + _init_display(device) + + is_used = true + + if ui.unit_displays[idx] then + ui.unit_displays[idx].delete() + ui.unit_displays[idx] = nil + end + + iocontrol.fp_monitor_state(idx, true) + + if engine.ui_ready then + engine.dmesg_window.setVisible(false) + + 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 ui.unit_displays[idx] then + ui.unit_displays[idx].delete() + ui.unit_displays[idx] = nil + end + + device.setCursorPos(1, 1) + device.setBackgroundColor(colors.black) + device.setTextColor(colors.red) + device.clear() + device.write("monitor too small") + + iocontrol.fp_monitor_state(idx, false) + is_ok = false + end + end + + break + end + end + end + + return is_used, is_ok +end -- handle a touch event ---@param event mouse_interaction|nil @@ -355,15 +480,14 @@ function renderer.handle_mouse(event) engine.ui.front_panel.handle_mouse(event) elseif engine.ui_ready then if event.monitor == engine.monitors.primary_name then - engine.ui.main_display.handle_mouse(event) + if engine.ui.main_display then engine.ui.main_display.handle_mouse(event) end elseif event.monitor == engine.monitors.flow_name then - engine.ui.flow_display.handle_mouse(event) + if engine.ui.flow_display then engine.ui.flow_display.handle_mouse(event) end else for id, monitor in ipairs(engine.monitors.unit_name_map) do - if event.monitor == monitor then - local layout = engine.ui.unit_displays[id] ---@type graphics_element - layout.handle_mouse(event) - break + local display = engine.ui.unit_displays[id] + if event.monitor == monitor and display then + if display then display.handle_mouse(event) end end end end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 5831b8a..b3ba2b8 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 = "v1.2.2" +local COORDINATOR_VERSION = "v1.2.3" local println = util.println local println_ts = util.println_ts @@ -79,8 +79,8 @@ local function main() -- system startup ---------------------------------------- - -- re-mount devices now that logging is ready - ppm.mount_all() + -- log mounts now since mounting was done before logging was ready + ppm.log_mounts() -- report versions/init fp PSIL iocontrol.init_fp(COORDINATOR_VERSION, comms.version) @@ -269,6 +269,11 @@ local function main() 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 diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index df64b68..6acb7aa 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -300,6 +300,17 @@ function ppm.handle_unmount(iface) return pm_type, pm_dev end +-- log all mounts, to be used if `ppm.mount_all` is called before logging is ready +function ppm.log_mounts() + for iface, mount in pairs(ppm_sys.mounts) do + log.info(util.c("PPM: had found a ", mount.type, " (", iface, ")")) + end + + if #ppm_sys.mounts == 0 then + log.warning("PPM: no devices had been found") + end +end + -- GENERAL ACCESSORS -- -- list all available peripherals diff --git a/scada-common/util.lua b/scada-common/util.lua index 10b4202..00d6e24 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.1.14" +util.version = "1.1.15" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50