#184 support supervisor running without front panel, halved heartbeat blink rate

This commit is contained in:
Mikayla Fischler 2023-06-03 15:45:48 -04:00
parent 69df5edbeb
commit 529371a0fd
11 changed files with 152 additions and 121 deletions

1
install_manifest.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -18,21 +18,16 @@ local core = require("graphics.core")
local Div = require("graphics.elements.div")
local ListBox = require("graphics.elements.listbox")
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 DataIndicator = require("graphics.elements.indicators.data")
local TEXT_ALIGN = core.TEXT_ALIGN
local cpair = core.cpair
local border = core.border
-- create new main view
---@param panel graphics_element main displaybox

View File

@ -28,6 +28,14 @@ function pgi.link_elements(rtu_list, rtu_entry, pdg_list, pdg_entry)
data.pdg_entry = pdg_entry
end
-- unlink all fields, disabling the PGI
function pgi.unlink()
data.rtu_list = nil
data.pdg_list = nil
data.rtu_entry = nil
data.pdg_entry = nil
end
-- add an RTU entry to the RTU list
---@param session_id integer RTU session
function pgi.create_rtu_entry(session_id)

View File

@ -3,6 +3,7 @@
--
local panel_view = require("supervisor.panel.front_panel")
local pgi = require("supervisor.panel.pgi")
local style = require("supervisor.panel.style")
local flasher = require("graphics.flasher")
@ -44,6 +45,9 @@ function renderer.close_ui()
-- stop blinking indicators
flasher.clear()
-- disable PGI
pgi.unlink()
-- hide to stop animation callbacks
ui.display.hide()

View File

@ -20,9 +20,6 @@ local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
local SV_Q_DATA = svqtypes.SV_Q_DATA
-- local println = util.println
local println = function (str) end
-- retry time constants in ms
-- local INITIAL_WAIT = 1500
local RETRY_PERIOD = 1000
@ -52,7 +49,11 @@ local PERIODICS = {
---@param out_queue mqueue out message queue
---@param timeout number communications timeout
---@param facility facility facility data table
function coordinator.new_session(id, in_queue, out_queue, timeout, facility)
---@param fp_ok boolean if the front panel UI is running
function coordinator.new_session(id, in_queue, out_queue, timeout, facility, fp_ok)
-- print a log message to the terminal as long as the UI isn't running
local function println(message) if not fp_ok then util.println_ts(message) end end
local log_header = "crdn_session(" .. id .. "): "
local self = {

View File

@ -16,9 +16,6 @@ local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
local PLC_AUTO_ACK = comms.PLC_AUTO_ACK
local UNIT_COMMAND = comms.UNIT_COMMAND
-- local println = util.println
local println = function (str) end
-- retry time constants in ms
local INITIAL_WAIT = 1500
local INITIAL_AUTO_WAIT = 1000
@ -52,7 +49,11 @@ local PERIODICS = {
---@param in_queue mqueue in message queue
---@param out_queue mqueue out message queue
---@param timeout number communications timeout
function plc.new_session(id, reactor_id, in_queue, out_queue, timeout)
---@param fp_ok boolean if the front panel UI is running
function plc.new_session(id, reactor_id, in_queue, out_queue, timeout, fp_ok)
-- print a log message to the terminal as long as the UI isn't running
local function println(message) if not fp_ok then util.println_ts(message) end end
local log_header = "plc_session(" .. id .. "): "
local self = {

View File

@ -9,9 +9,6 @@ local pocket = {}
local PROTOCOL = comms.PROTOCOL
local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
-- local println = util.println
local println = function (str) end
-- retry time constants in ms
-- local INITIAL_WAIT = 1500
-- local RETRY_PERIOD = 1000
@ -35,7 +32,11 @@ local PERIODICS = {
---@param in_queue mqueue in message queue
---@param out_queue mqueue out message queue
---@param timeout number communications timeout
function pocket.new_session(id, in_queue, out_queue, timeout)
---@param fp_ok boolean if the front panel UI is running
function pocket.new_session(id, in_queue, out_queue, timeout, fp_ok)
-- print a log message to the terminal as long as the UI isn't running
local function println(message) if not fp_ok then util.println_ts(message) end end
local log_header = "pdg_session(" .. id .. "): "
local self = {

View File

@ -24,9 +24,6 @@ local PROTOCOL = comms.PROTOCOL
local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
-- local println = util.println
local println = function (str) end
local PERIODICS = {
KEEP_ALIVE = 2000
}
@ -39,7 +36,11 @@ local PERIODICS = {
---@param timeout number communications timeout
---@param advertisement table RTU device advertisement
---@param facility facility facility data table
function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facility)
---@param fp_ok boolean if the front panel UI is running
function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facility, fp_ok)
-- print a log message to the terminal as long as the UI isn't running
local function println(message) if not fp_ok then util.println_ts(message) end end
local log_header = "rtu_session(" .. id .. "): "
local self = {

View File

@ -35,6 +35,7 @@ svsessions.SESSION_TYPE = SESSION_TYPE
local self = {
modem = nil, ---@type table|nil
fp_ok = false,
num_reactors = 0,
facility = nil, ---@type facility|nil
sessions = { rtu = {}, plc = {}, coord = {}, pdg = {} },
@ -196,11 +197,13 @@ end
-- PUBLIC FUNCTIONS --
-- initialize svsessions
---@param modem table
---@param num_reactors integer
---@param cooling_conf table
function svsessions.init(modem, num_reactors, cooling_conf)
---@param modem table modem device
---@param fp_ok boolean front panel active
---@param num_reactors integer number of reactors
---@param cooling_conf table cooling configuration definition
function svsessions.init(modem, fp_ok, num_reactors, cooling_conf)
self.modem = modem
self.fp_ok = fp_ok
self.num_reactors = num_reactors
self.facility = facility.new(num_reactors, cooling_conf)
end
@ -308,13 +311,15 @@ function svsessions.establish_plc_session(local_port, remote_port, for_reactor,
instance = nil ---@type plc_session
}
plc_s.instance = plc.new_session(self.next_ids.plc, for_reactor, plc_s.in_queue, plc_s.out_queue, config.PLC_TIMEOUT)
plc_s.instance = plc.new_session(self.next_ids.plc, for_reactor, plc_s.in_queue, plc_s.out_queue,
config.PLC_TIMEOUT, self.fp_ok)
table.insert(self.sessions.plc, plc_s)
local units = self.facility.get_units()
units[for_reactor].link_plc_session(plc_s)
log.debug(util.c("established new PLC session to ", remote_port, " with ID ", self.next_ids.plc, " for reactor ", for_reactor))
log.debug(util.c("established new PLC session to ", remote_port, " with ID ", self.next_ids.plc,
" for reactor ", for_reactor))
databus.tx_plc_connected(for_reactor, version, remote_port)
@ -348,7 +353,8 @@ function svsessions.establish_rtu_session(local_port, remote_port, advertisement
instance = nil ---@type rtu_session
}
rtu_s.instance = rtu.new_session(self.next_ids.rtu, rtu_s.in_queue, rtu_s.out_queue, config.RTU_TIMEOUT, advertisement, self.facility)
rtu_s.instance = rtu.new_session(self.next_ids.rtu, rtu_s.in_queue, rtu_s.out_queue, config.RTU_TIMEOUT, advertisement,
self.facility, self.fp_ok)
table.insert(self.sessions.rtu, rtu_s)
log.debug("established new RTU session to " .. remote_port .. " with ID " .. self.next_ids.rtu)
@ -381,7 +387,8 @@ function svsessions.establish_coord_session(local_port, remote_port, version)
instance = nil ---@type coord_session
}
coord_s.instance = coordinator.new_session(self.next_ids.coord, coord_s.in_queue, coord_s.out_queue, config.CRD_TIMEOUT, self.facility)
coord_s.instance = coordinator.new_session(self.next_ids.coord, coord_s.in_queue, coord_s.out_queue, config.CRD_TIMEOUT,
self.facility, self.fp_ok)
table.insert(self.sessions.coord, coord_s)
log.debug("established new coordinator session to " .. remote_port .. " with ID " .. self.next_ids.coord)
@ -417,7 +424,7 @@ function svsessions.establish_pdg_session(local_port, remote_port, version)
instance = nil ---@type pdg_session
}
pdg_s.instance = pocket.new_session(self.next_ids.pdg, pdg_s.in_queue, pdg_s.out_queue, config.PKT_TIMEOUT)
pdg_s.instance = pocket.new_session(self.next_ids.pdg, pdg_s.in_queue, pdg_s.out_queue, config.PKT_TIMEOUT, self.fp_ok)
table.insert(self.sessions.pdg, pdg_s)
log.debug("established new pocket diagnostics session to " .. remote_port .. " with ID " .. self.next_ids.pdg)

View File

@ -108,93 +108,104 @@ local function main()
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)
-- base loop clock (6.67Hz, 3 ticks)
local MAIN_CLOCK = 0.15
local loop_clock = util.new_clock(MAIN_CLOCK)
-- start clock
loop_clock.start()
-- 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 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
elseif event == "peripheral" then
local type, device = ppm.mount(param1)
if type ~= nil and device ~= nil then
if type == "modem" then
if device.isWireless() then
-- reconnected modem
modem = device
superv_comms.reconnect_modem(modem)
log.info("comms modem reconnected")
databus.tx_hw_modem(true)
else
log.info("wired modem reconnected")
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)
-- notify timer callback dispatcher
tcd.handle(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" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" then
-- handle a mouse event
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
end
-- check for termination request
if event == "terminate" or ppm.should_terminate() then
log.info("terminate requested, closing sessions...")
svsessions.close_all()
log.info("sessions closed")
break
end
end
renderer.close_ui()
-- redefine println_ts local to not print as we have the front panel running
println_ts = function (_) end
end
println_ts("exited")
-- 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, fp_ok)
-- base loop clock (6.67Hz, 3 ticks)
local MAIN_CLOCK = 0.15
local loop_clock = util.new_clock(MAIN_CLOCK)
-- start clock
loop_clock.start()
-- halve the rate heartbeat LED flash
local heartbeat_toggle = true
-- 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 care if this is our wireless modem
if device == modem then
println_ts("wireless modem disconnected!")
log.warning("comms modem disconnected")
databus.tx_hw_modem(false)
else
log.warning("non-comms modem disconnected")
end
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() then
-- reconnected modem
modem = device
superv_comms.reconnect_modem(modem)
println_ts("wireless modem reconnected.")
log.info("comms modem reconnected")
databus.tx_hw_modem(true)
else
log.info("wired modem reconnected")
end
end
end
elseif event == "timer" and loop_clock.is_clock(param1) then
-- main loop tick
if heartbeat_toggle then databus.heartbeat() end
heartbeat_toggle = not heartbeat_toggle
-- 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)
-- notify timer callback dispatcher
tcd.handle(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" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" then
-- handle a mouse event
renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
end
-- check for termination request
if event == "terminate" or ppm.should_terminate() then
println_ts("closing sesssions...")
log.info("terminate requested, closing sessions...")
svsessions.close_all()
log.info("sessions closed")
break
end
end
renderer.close_ui()
util.println_ts("exited")
log.info("exited")
end

View File

@ -11,9 +11,6 @@ local DEVICE_TYPE = comms.DEVICE_TYPE
local ESTABLISH_ACK = comms.ESTABLISH_ACK
local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
-- local println = util.println
local println = function (str) end
-- supervisory controller communications
---@nodiscard
---@param _version string supervisor version
@ -23,8 +20,12 @@ local println = function (str) end
---@param dev_listen integer listening port for PLC/RTU devices
---@param svctl_listen integer listening port for supervisor access
---@param range integer trusted device connection range
---@param fp_ok boolean if the front panel UI is running
---@diagnostic disable-next-line: unused-local
function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_listen, svctl_listen, range)
function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_listen, svctl_listen, range, fp_ok)
-- print a log message to the terminal as long as the UI isn't running
local function println(message) if not fp_ok then util.println_ts(message) end end
local self = {
last_est_acks = {}
}
@ -42,8 +43,8 @@ function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_liste
_conf_channels()
-- link modem to svsessions
svsessions.init(modem, num_reactors, cooling_conf)
-- pass modem, status, and config data to svsessions
svsessions.init(modem, fp_ok, num_reactors, cooling_conf)
-- send an establish request response to a PLC/RTU
---@param dest integer