#249 coordinator handle monitor disconnects/reconnects

This commit is contained in:
Mikayla Fischler 2023-07-11 20:32:10 -04:00
parent 0d6022f5e3
commit 55ff9dad4b
4 changed files with 156 additions and 54 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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
--