#184 WIP supervisor front panel

This commit is contained in:
Mikayla Fischler 2023-05-05 13:04:13 -04:00
parent ff9a18a019
commit 2c7b98ba42
12 changed files with 365 additions and 66 deletions

44
supervisor/databus.lua Normal file
View File

@ -0,0 +1,44 @@
--
-- Data Bus - Central Communication Linking for Supervisor Front Panel
--
local psil = require("scada-common.psil")
local databus = {}
local dbus_iface = {
ps = psil.create(),
session_entries = { rtu = {}, plc = {}, coord = {}, diag = {} }
}
-- call to toggle heartbeat signal
function databus.heartbeat() dbus_iface.ps.toggle("heartbeat") end
-- transmit firmware versions across the bus
---@param plc_v string supervisor version
---@param comms_v string comms version
function databus.tx_versions(plc_v, comms_v)
dbus_iface.ps.publish("version", plc_v)
dbus_iface.ps.publish("comms_version", comms_v)
end
-- transmit hardware status for modem connection state
---@param has_modem boolean
function databus.tx_hw_modem(has_modem)
dbus_iface.ps.publish("has_modem", has_modem)
end
function databus.tx_svs_connection(type, data)
end
function databus.tx_svs_disconnection(type, data)
end
-- link a function to receive data from the bus
---@param field string field name
---@param func function function to link
function databus.rx_field(field, func)
dbus_iface.ps.subscribe(field, func)
end
return databus

View File

@ -0,0 +1,91 @@
--
-- Main SCADA Coordinator GUI
--
local util = require("scada-common.util")
local databus = require("supervisor.databus")
local style = require("supervisor.panel.style")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local MultiPane = require("graphics.elements.multipane")
local Rectangle = require("graphics.elements.rectangle")
local TextBox = require("graphics.elements.textbox")
local PushButton = require("graphics.elements.controls.push_button")
local TabBar = require("graphics.elements.controls.tabbar")
local LED = require("graphics.elements.indicators.led")
local LEDPair = require("graphics.elements.indicators.ledpair")
local RGBLED = require("graphics.elements.indicators.ledrgb")
local TEXT_ALIGN = core.graphics.TEXT_ALIGN
local cpair = core.graphics.cpair
local border = core.graphics.border
-- create new main view
---@param panel graphics_element main displaybox
local function init(panel)
TextBox{parent=panel,y=1,text="SCADA SUPERVISOR",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header}
local page_div = Div{parent=panel,x=1,y=3}
--
-- system indicators
--
local main_page = Div{parent=page_div,x=1,y=1}
local system = Div{parent=main_page,width=14,height=17,x=2,y=2}
local on = LED{parent=system,label="POWER",colors=cpair(colors.green,colors.red)}
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=cpair(colors.green,colors.green_off)}
on.update(true)
system.line_break()
databus.rx_field("heartbeat", heartbeat.update)
local modem = LED{parent=system,label="MODEM",colors=cpair(colors.green,colors.green_off)}
system.line_break()
databus.rx_field("has_modem", modem.update)
--
-- about footer
--
local about = Div{parent=main_page,width=15,height=3,x=1,y=16,fg_bg=cpair(colors.lightGray,colors.ivory)}
local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=TEXT_ALIGN.LEFT,height=1}
local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00",alignment=TEXT_ALIGN.LEFT,height=1}
databus.rx_field("version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
databus.rx_field("comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
--
-- page handling
--
local plc_list = Div{parent=page_div,x=1,y=1}
TextBox{parent=plc_list,x=2,y=2,text="v1.1.17 - PLC - UNIT 4 - :15004",alignment=TEXT_ALIGN.LEFT,height=1}
local panes = { main_page, plc_list, main_page, main_page, main_page }
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
local tabs = {
{ name = "Main", color = cpair(colors.black, colors.ivory) },
{ name = "PLCs", color = cpair(colors.black, colors.ivory) },
{ name = "RTUs", color = cpair(colors.black, colors.ivory) },
{ name = "CRDs", color = cpair(colors.black, colors.ivory) },
{ name = "PKTs", color = cpair(colors.black, colors.ivory) },
}
TabBar{parent=panel,y=2,tabs=tabs,min_width=10,callback=page_pane.set_value,fg_bg=cpair(colors.black,colors.white)}
end
return init

View File

@ -0,0 +1,41 @@
--
-- Graphics Style Options
--
local core = require("graphics.core")
local style = {}
local cpair = core.graphics.cpair
-- GLOBAL --
-- remap global colors
colors.ivory = colors.pink
colors.red_off = colors.brown
colors.yellow_off = colors.magenta
colors.green_off = colors.lime
style.root = cpair(colors.black, colors.ivory)
style.header = cpair(colors.black, colors.lightGray)
style.colors = {
{ c = colors.red, hex = 0xdf4949 }, -- RED ON
{ c = colors.orange, hex = 0xffb659 },
{ c = colors.yellow, hex = 0xf9fb53 }, -- YELLOW ON
{ c = colors.lime, hex = 0x16665a }, -- GREEN OFF
{ c = colors.green, hex = 0x6be551 }, -- GREEN ON
{ c = colors.cyan, hex = 0x34bac8 },
{ c = colors.lightBlue, hex = 0x6cc0f2 },
{ c = colors.blue, hex = 0x0096ff },
{ c = colors.purple, hex = 0xb156ee },
{ c = colors.pink, hex = 0xdcd9ca }, -- IVORY
{ c = colors.magenta, hex = 0x85862c }, -- YELLOW OFF
-- { c = colors.white, hex = 0xdcd9ca },
{ c = colors.lightGray, hex = 0xb1b8b3 },
{ c = colors.gray, hex = 0x575757 },
-- { c = colors.black, hex = 0x191919 },
{ c = colors.brown, hex = 0x672223 } -- RED OFF
}
return style

80
supervisor/renderer.lua Normal file
View File

@ -0,0 +1,80 @@
--
-- Graphics Rendering Control
--
local panel_view = require("supervisor.panel.front_panel")
local style = require("supervisor.panel.style")
local flasher = require("graphics.flasher")
local DisplayBox = require("graphics.elements.displaybox")
local renderer = {}
local ui = {
display = nil
}
-- start the UI
function renderer.start_ui()
if ui.display == nil then
-- reset terminal
term.setTextColor(colors.white)
term.setBackgroundColor(colors.black)
term.clear()
term.setCursorPos(1, 1)
-- set overridden colors
for i = 1, #style.colors do
term.setPaletteColor(style.colors[i].c, style.colors[i].hex)
end
-- init front panel view
ui.display = DisplayBox{window=term.current(),fg_bg=style.root}
panel_view(ui.display)
-- start flasher callback task
flasher.run()
end
end
-- close out the UI
function renderer.close_ui()
if ui.display ~= nil then
-- stop blinking indicators
flasher.clear()
-- hide to stop animation callbacks
ui.display.hide()
-- clear root UI elements
ui.display = nil
-- restore colors
for i = 1, #style.colors do
local r, g, b = term.nativePaletteColor(style.colors[i].c)
term.setPaletteColor(style.colors[i].c, r, g, b)
end
-- reset terminal
term.setTextColor(colors.white)
term.setBackgroundColor(colors.black)
term.clear()
term.setCursorPos(1, 1)
end
end
-- is the UI ready?
---@nodiscard
---@return boolean ready
function renderer.ui_ready() return ui.display ~= nil end
-- handle a mouse event
---@param event mouse_interaction
function renderer.handle_mouse(event)
if ui.display ~= nil then
ui.display.handle_mouse(event)
end
end
return renderer

View File

@ -18,7 +18,8 @@ local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
local SV_Q_DATA = svqtypes.SV_Q_DATA local SV_Q_DATA = svqtypes.SV_Q_DATA
local println = util.println -- local println = util.println
local println = function (str) end
-- retry time constants in ms -- retry time constants in ms
-- local INITIAL_WAIT = 1500 -- local INITIAL_WAIT = 1500

View File

@ -14,7 +14,8 @@ local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
local PLC_AUTO_ACK = comms.PLC_AUTO_ACK local PLC_AUTO_ACK = comms.PLC_AUTO_ACK
local UNIT_COMMAND = comms.UNIT_COMMAND local UNIT_COMMAND = comms.UNIT_COMMAND
local println = util.println -- local println = util.println
local println = function (str) end
-- retry time constants in ms -- retry time constants in ms
local INITIAL_WAIT = 1500 local INITIAL_WAIT = 1500

View File

@ -8,7 +8,8 @@ local pocket = {}
local PROTOCOL = comms.PROTOCOL local PROTOCOL = comms.PROTOCOL
local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
local println = util.println -- local println = util.println
local println = function (str) end
-- retry time constants in ms -- retry time constants in ms
-- local INITIAL_WAIT = 1500 -- local INITIAL_WAIT = 1500

View File

@ -22,7 +22,8 @@ local PROTOCOL = comms.PROTOCOL
local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
local println = util.println -- local println = util.println
local println = function (str) end
local PERIODICS = { local PERIODICS = {
KEEP_ALIVE = 2000 KEEP_ALIVE = 2000

View File

@ -22,6 +22,7 @@ local CRD_S_DATA = coordinator.CRD_S_DATA
local svsessions = {} local svsessions = {}
---@enum SESSION_TYPE
local SESSION_TYPE = { local SESSION_TYPE = {
RTU_SESSION = 0, -- RTU gateway RTU_SESSION = 0, -- RTU gateway
PLC_SESSION = 1, -- reactor PLC PLC_SESSION = 1, -- reactor PLC

View File

@ -5,16 +5,21 @@
require("/initenv").init_env() require("/initenv").init_env()
local crash = require("scada-common.crash") local crash = require("scada-common.crash")
local comms = require("scada-common.comms")
local log = require("scada-common.log") local log = require("scada-common.log")
local ppm = require("scada-common.ppm") local ppm = require("scada-common.ppm")
local util = require("scada-common.util") local util = require("scada-common.util")
local core = require("graphics.core")
local config = require("supervisor.config") local config = require("supervisor.config")
local databus = require("supervisor.databus")
local renderer = require("supervisor.renderer")
local supervisor = require("supervisor.supervisor") local supervisor = require("supervisor.supervisor")
local svsessions = require("supervisor.session.svsessions") local svsessions = require("supervisor.session.svsessions")
local SUPERVISOR_VERSION = "v0.15.5" local SUPERVISOR_VERSION = "v0.16.0"
local println = util.println local println = util.println
local println_ts = util.println_ts local println_ts = util.println_ts
@ -79,6 +84,9 @@ local function main()
-- startup -- startup
---------------------------------------- ----------------------------------------
-- record firmware versions and ID
databus.tx_versions(SUPERVISOR_VERSION, comms.version)
-- mount connected devices -- mount connected devices
ppm.mount_all() ppm.mount_all()
@ -89,84 +97,113 @@ local function main()
return return
end end
-- start comms, open all channels databus.tx_hw_modem(true)
local superv_comms = supervisor.comms(SUPERVISOR_VERSION, config.NUM_REACTORS, config.REACTOR_COOLING, modem,
config.SCADA_DEV_LISTEN, config.SCADA_SV_CTL_LISTEN, config.TRUSTED_RANGE)
-- base loop clock (6.67Hz, 3 ticks) -- start UI
local MAIN_CLOCK = 0.15 local fp_ok, message = pcall(renderer.start_ui)
local loop_clock = util.new_clock(MAIN_CLOCK)
-- start clock if not fp_ok then
loop_clock.start() renderer.close_ui()
println_ts(util.c("UI error: ", message))
log.error(util.c("GUI crashed with error ", message))
else
-- start comms, open all channels
local superv_comms = supervisor.comms(SUPERVISOR_VERSION, config.NUM_REACTORS, config.REACTOR_COOLING, modem,
config.SCADA_DEV_LISTEN, config.SCADA_SV_CTL_LISTEN, config.TRUSTED_RANGE)
-- event loop -- base loop clock (6.67Hz, 3 ticks)
while true do local MAIN_CLOCK = 0.15
local event, param1, param2, param3, param4, param5 = util.pull_event() local loop_clock = util.new_clock(MAIN_CLOCK)
-- handle event -- start clock
if event == "peripheral_detach" then loop_clock.start()
local type, device = ppm.handle_unmount(param1)
if type ~= nil and device ~= nil then -- event loop
if type == "modem" then while true do
-- we only care if this is our wireless modem local event, param1, param2, param3, param4, param5 = util.pull_event()
if device == modem then
println_ts("wireless modem disconnected!") -- handle event
log.warning("comms modem disconnected") if event == "peripheral_detach" then
else local type, device = ppm.handle_unmount(param1)
log.warning("non-comms modem disconnected")
if type ~= nil and device ~= nil then
if type == "modem" then
-- we only care if this is our wireless modem
if device == modem then
log.warning("comms modem disconnected")
databus.tx_hw_modem(false)
else
log.warning("non-comms modem disconnected")
end
end end
end end
end elseif event == "peripheral" then
elseif event == "peripheral" then local type, device = ppm.mount(param1)
local type, device = ppm.mount(param1)
if type ~= nil and device ~= nil then if type ~= nil and device ~= nil then
if type == "modem" then if type == "modem" then
if device.isWireless() then if device.isWireless() then
-- reconnected modem -- reconnected modem
modem = device modem = device
superv_comms.reconnect_modem(modem) superv_comms.reconnect_modem(modem)
println_ts("wireless modem reconnected.") log.info("comms modem reconnected")
log.info("comms modem reconnected")
else databus.tx_hw_modem(true)
log.info("wired modem reconnected") else
log.info("wired modem reconnected")
end
end end
end end
elseif event == "timer" and loop_clock.is_clock(param1) then
-- main loop tick
databus.heartbeat()
-- iterate sessions
svsessions.iterate_all()
-- free any closed sessions
svsessions.free_all_closed()
loop_clock.start()
elseif event == "timer" then
-- a non-clock timer event, check watchdogs
svsessions.check_all_watchdogs(param1)
elseif event == "modem_message" then
-- got a packet
local packet = superv_comms.parse_packet(param1, param2, param3, param4, param5)
superv_comms.handle_packet(packet)
elseif event == "mouse_click" then
-- handle a monitor touch event
renderer.handle_mouse(core.events.touch(param1, param2, param3))
log.debug(util.sprintf("mouse_click: %s [%d, %d]", param1, param2, param3))
elseif event == "mouse_drag" then
log.debug(util.sprintf("mouse_drag: %s [%d, %d]", param1, param2, param3))
elseif event == "mouse_scroll" then
log.debug(util.sprintf("mouse_scroll: %s [%d, %d]", param1, param2, param3))
elseif event == "mouse_up" then
log.debug(util.sprintf("mouse_up: %s [%d, %d]", param1, param2, param3))
end end
elseif event == "timer" and loop_clock.is_clock(param1) then
-- main loop tick
-- iterate sessions -- check for termination request
svsessions.iterate_all() if event == "terminate" or ppm.should_terminate() then
log.info("terminate requested, closing sessions...")
-- free any closed sessions svsessions.close_all()
svsessions.free_all_closed() log.info("sessions closed")
break
loop_clock.start() end
elseif event == "timer" then
-- a non-clock timer event, check watchdogs
svsessions.check_all_watchdogs(param1)
elseif event == "modem_message" then
-- got a packet
local packet = superv_comms.parse_packet(param1, param2, param3, param4, param5)
superv_comms.handle_packet(packet)
end end
-- check for termination request renderer.close_ui()
if event == "terminate" or ppm.should_terminate() then
println_ts("closing sessions...")
log.info("terminate requested, closing sessions...")
svsessions.close_all()
log.info("sessions closed")
break
end
end end
println_ts("exited") println_ts("exited")
log.info("exited") log.info("exited")
end end
if not xpcall(main, crash.handler) then crash.exit() else log.close() end if not xpcall(main, crash.handler) then
pcall(renderer.close_ui)
crash.exit()
else
log.close()
end

View File

@ -11,7 +11,8 @@ local DEVICE_TYPE = comms.DEVICE_TYPE
local ESTABLISH_ACK = comms.ESTABLISH_ACK local ESTABLISH_ACK = comms.ESTABLISH_ACK
local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
local println = util.println -- local println = util.println
local println = function (str) end
-- supervisory controller communications -- supervisory controller communications
---@nodiscard ---@nodiscard