Merge pull request #279 from MikaylaFischler/265-coordinator-front-panel

265 coordinator front panel
This commit is contained in:
Mikayla 2023-07-11 15:37:17 -04:00 committed by GitHub
commit 9bb2a99be5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 697 additions and 296 deletions

View File

@ -2,6 +2,7 @@ local comms = require("scada-common.comms")
local log = require("scada-common.log")
local ppm = require("scada-common.ppm")
local util = require("scada-common.util")
local types = require("scada-common.types")
local iocontrol = require("coordinator.iocontrol")
local process = require("coordinator.process")
@ -12,7 +13,6 @@ local dialog = require("coordinator.ui.dialog")
local print = util.print
local println = util.println
local println_ts = util.println_ts
local PROTOCOL = comms.PROTOCOL
local DEVICE_TYPE = comms.DEVICE_TYPE
@ -22,6 +22,8 @@ local SCADA_CRDN_TYPE = comms.SCADA_CRDN_TYPE
local UNIT_COMMAND = comms.UNIT_COMMAND
local FAC_COMMAND = comms.FAC_COMMAND
local LINK_TIMEOUT = 60.0
local coordinator = {}
-- request the user to select a monitor
@ -227,9 +229,12 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
sv_seq_num = 0,
sv_r_seq_num = nil,
sv_config_err = false,
connected = false,
last_est_ack = ESTABLISH_ACK.ALLOW,
last_api_est_acks = {}
last_api_est_acks = {},
est_start = 0,
est_last = 0,
est_tick_waiting = nil,
est_task_done = nil
}
comms.set_trusted_range(range)
@ -295,73 +300,73 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
---@class coord_comms
local public = {}
-- try to connect to the supervisor if not already linked
---@param abort boolean? true to print out cancel info if not linked (use on program terminate)
---@return boolean ok, boolean start_ui
function public.try_connect(abort)
local ok = true
local start_ui = false
if not self.sv_linked then
if self.est_tick_waiting == nil then
self.est_start = util.time_s()
self.est_last = self.est_start
self.est_tick_waiting, self.est_task_done =
coordinator.log_comms_connecting("attempting to connect to configured supervisor on channel " .. svr_channel)
_send_establish()
else
self.est_tick_waiting(math.max(0, LINK_TIMEOUT - (util.time_s() - self.est_start)))
end
if abort or (util.time_s() - self.est_start) >= LINK_TIMEOUT then
self.est_task_done(false)
if abort then
coordinator.log_comms("supervisor connection attempt cancelled by user")
elseif self.sv_config_err then
coordinator.log_comms("supervisor cooling configuration invalid, check supervisor config file")
elseif not self.sv_linked then
if self.last_est_ack == ESTABLISH_ACK.DENY then
coordinator.log_comms("supervisor connection attempt denied")
elseif self.last_est_ack == ESTABLISH_ACK.COLLISION then
coordinator.log_comms("supervisor connection failed due to collision")
elseif self.last_est_ack == ESTABLISH_ACK.BAD_VERSION then
coordinator.log_comms("supervisor connection failed due to version mismatch")
else
coordinator.log_comms("supervisor connection failed with no valid response")
end
end
ok = false
elseif self.sv_config_err then
coordinator.log_comms("supervisor cooling configuration invalid, check supervisor config file")
ok = false
elseif (util.time_s() - self.est_last) > 1.0 then
_send_establish()
self.est_last = util.time_s()
end
elseif self.est_tick_waiting ~= nil then
self.est_task_done(true)
self.est_tick_waiting = nil
self.est_task_done = nil
start_ui = true
end
return ok, start_ui
end
-- close the connection to the server
function public.close()
sv_watchdog.cancel()
self.sv_addr = comms.BROADCAST
self.sv_linked = false
self.sv_r_seq_num = nil
iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED)
_send_sv(PROTOCOL.SCADA_MGMT, SCADA_MGMT_TYPE.CLOSE, {})
end
-- attempt to connect to the subervisor
---@nodiscard
---@param timeout_s number timeout in seconds
---@param tick_dmesg_waiting function callback to tick dmesg waiting
---@param task_done function callback to show done on dmesg
---@return boolean sv_linked true if connected, false otherwise
--- EVENT_CONSUMER: this function consumes events
function public.sv_connect(timeout_s, tick_dmesg_waiting, task_done)
local clock = util.new_clock(1)
local start = util.time_s()
local terminated = false
_send_establish()
clock.start()
while (util.time_s() - start) < timeout_s and (not self.sv_linked) and (not self.sv_config_err) do
local event, p1, p2, p3, p4, p5 = util.pull_event()
if event == "timer" and clock.is_clock(p1) then
-- timed out attempt, try again
tick_dmesg_waiting(math.max(0, timeout_s - (util.time_s() - start)))
_send_establish()
clock.start()
elseif event == "timer" then
-- keep checking watchdog timers
apisessions.check_all_watchdogs(p1)
elseif event == "modem_message" then
-- handle message
local packet = public.parse_packet(p1, p2, p3, p4, p5)
public.handle_packet(packet)
elseif event == "terminate" then
terminated = true
break
end
end
task_done(self.sv_linked)
if terminated then
coordinator.log_comms("supervisor connection attempt cancelled by user")
elseif self.sv_config_err then
coordinator.log_comms("supervisor cooling configuration invalid, check supervisor config file")
elseif not self.sv_linked then
if self.last_est_ack == ESTABLISH_ACK.DENY then
coordinator.log_comms("supervisor connection attempt denied")
elseif self.last_est_ack == ESTABLISH_ACK.COLLISION then
coordinator.log_comms("supervisor connection failed due to collision")
elseif self.last_est_ack == ESTABLISH_ACK.BAD_VERSION then
coordinator.log_comms("supervisor connection failed due to version mismatch")
else
coordinator.log_comms("supervisor connection failed with no valid response")
end
end
return self.sv_linked
end
-- send a facility command
---@param cmd FAC_COMMAND command
---@param option any? optional option options for the optional options (like waste mode)
@ -425,7 +430,10 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
-- handle a packet
---@param packet mgmt_frame|crdn_frame|capi_frame|nil
---@return boolean close_ui
function public.handle_packet(packet)
local was_linked = self.sv_linked
if packet ~= nil then
local l_chan = packet.scada_frame.local_channel()
local r_chan = packet.scada_frame.remote_channel()
@ -435,7 +443,9 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
if l_chan ~= crd_channel then
log.debug("received packet on unconfigured channel " .. l_chan, true)
elseif r_chan == pkt_channel then
if protocol == PROTOCOL.COORD_API then
if not self.sv_linked then
log.debug("discarding pocket API packet before linked to supervisor")
elseif protocol == PROTOCOL.COORD_API then
---@cast packet capi_frame
-- look for an associated session
local session = apisessions.find_session(src_addr)
@ -474,7 +484,6 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
elseif dev_type == DEVICE_TYPE.PKT then
-- pocket linking request
local id = apisessions.establish_session(src_addr, firmware_v)
println(util.c("[API] pocket (", firmware_v, ") [@", src_addr, "] \xbb connected"))
coordinator.log_comms(util.c("API_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", id))
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.ALLOW)
@ -497,12 +506,12 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
-- check sequence number
if self.sv_r_seq_num == nil then
self.sv_r_seq_num = packet.scada_frame.seq_num()
elseif self.connected and ((self.sv_r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
elseif self.sv_linked and ((self.sv_r_seq_num + 1) ~= packet.scada_frame.seq_num()) then
log.warning("sequence out-of-order: last = " .. self.sv_r_seq_num .. ", new = " .. packet.scada_frame.seq_num())
return
return false
elseif self.sv_linked and src_addr ~= self.sv_addr then
log.debug("received packet from unknown computer " .. src_addr .. " while linked; channel in use by another system?")
return
return false
else
self.sv_r_seq_num = packet.scada_frame.seq_num()
end
@ -632,70 +641,7 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
end
elseif protocol == PROTOCOL.SCADA_MGMT then
---@cast packet mgmt_frame
if packet.type == SCADA_MGMT_TYPE.ESTABLISH then
-- connection with supervisor established
if packet.length == 2 then
local est_ack = packet.data[1]
local config = packet.data[2]
if est_ack == ESTABLISH_ACK.ALLOW then
if type(config) == "table" and #config > 1 then
-- get configuration
---@class facility_conf
local conf = {
num_units = config[1], ---@type integer
defs = {} -- boilers and turbines
}
if (#config - 1) == (conf.num_units * 2) then
-- record sequence of pairs of [#boilers, #turbines] per unit
for i = 2, #config do
table.insert(conf.defs, config[i])
end
-- init io controller
iocontrol.init(conf, public)
self.sv_addr = src_addr
self.sv_linked = true
self.sv_config_err = false
else
self.sv_config_err = true
log.warning("invalid supervisor configuration definitions received, establish failed")
end
else
log.debug("invalid supervisor configuration table received, establish failed")
end
else
log.debug("SCADA_MGMT establish packet reply (len = 2) unsupported")
end
self.last_est_ack = est_ack
elseif packet.length == 1 then
local est_ack = packet.data[1]
if est_ack == ESTABLISH_ACK.DENY then
if self.last_est_ack ~= est_ack then
log.info("supervisor connection denied")
end
elseif est_ack == ESTABLISH_ACK.COLLISION then
if self.last_est_ack ~= est_ack then
log.warning("supervisor connection denied due to collision")
end
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
if self.last_est_ack ~= est_ack then
log.warning("supervisor comms version mismatch")
end
else
log.debug("SCADA_MGMT establish packet reply (len = 1) unsupported")
end
self.last_est_ack = est_ack
else
log.debug("SCADA_MGMT establish packet length mismatch")
end
elseif self.sv_linked then
if self.sv_linked then
if packet.type == SCADA_MGMT_TYPE.KEEP_ALIVE then
-- keep alive request received, echo back
if packet.length == 1 then
@ -720,11 +666,83 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
self.sv_addr = comms.BROADCAST
self.sv_linked = false
self.sv_r_seq_num = nil
println_ts("server connection closed by remote host")
iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED)
log.info("server connection closed by remote host")
else
log.debug("received unknown SCADA_MGMT packet type " .. packet.type)
end
elseif packet.type == SCADA_MGMT_TYPE.ESTABLISH then
-- connection with supervisor established
if packet.length == 2 then
local est_ack = packet.data[1]
local config = packet.data[2]
if est_ack == ESTABLISH_ACK.ALLOW then
-- reset to disconnected before validating
iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED)
if type(config) == "table" and #config > 1 then
-- get configuration
---@class facility_conf
local conf = {
num_units = config[1], ---@type integer
defs = {} -- boilers and turbines
}
if (#config - 1) == (conf.num_units * 2) then
-- record sequence of pairs of [#boilers, #turbines] per unit
for i = 2, #config do
table.insert(conf.defs, config[i])
end
-- init io controller
iocontrol.init(conf, public)
self.sv_addr = src_addr
self.sv_linked = true
self.sv_r_seq_num = nil
self.sv_config_err = false
iocontrol.fp_link_state(types.PANEL_LINK_STATE.LINKED)
else
self.sv_config_err = true
log.warning("invalid supervisor configuration definitions received, establish failed")
end
else
log.debug("invalid supervisor configuration table received, establish failed")
end
else
log.debug("SCADA_MGMT establish packet reply (len = 2) unsupported")
end
self.last_est_ack = est_ack
elseif packet.length == 1 then
local est_ack = packet.data[1]
if est_ack == ESTABLISH_ACK.DENY then
if self.last_est_ack ~= est_ack then
iocontrol.fp_link_state(types.PANEL_LINK_STATE.DENIED)
log.info("supervisor connection denied")
end
elseif est_ack == ESTABLISH_ACK.COLLISION then
if self.last_est_ack ~= est_ack then
iocontrol.fp_link_state(types.PANEL_LINK_STATE.COLLISION)
log.warning("supervisor connection denied due to collision")
end
elseif est_ack == ESTABLISH_ACK.BAD_VERSION then
if self.last_est_ack ~= est_ack then
iocontrol.fp_link_state(types.PANEL_LINK_STATE.BAD_VERSION)
log.warning("supervisor comms version mismatch")
end
else
log.debug("SCADA_MGMT establish packet reply (len = 1) unsupported")
end
self.last_est_ack = est_ack
else
log.debug("SCADA_MGMT establish packet length mismatch")
end
else
log.debug("discarding non-link SCADA_MGMT packet before linked")
end
@ -735,6 +753,8 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel,
log.debug("received packet for unknown channel " .. r_chan, true)
end
end
return was_linked and not self.sv_linked
end
-- check if the coordinator is still linked to the supervisor

View File

@ -10,9 +10,15 @@ local util = require("scada-common.util")
local process = require("coordinator.process")
local sounder = require("coordinator.sounder")
local pgi = require("coordinator.ui.pgi")
local ALARM_STATE = types.ALARM_STATE
local PROCESS = types.PROCESS
-- nominal RTT is ping (0ms to 10ms usually) + 500ms for CRD main loop tick
local WARN_RTT = 1000 -- 2x as long as expected w/ 0 ping
local HIGH_RTT = 1500 -- 3.33x as long as expected w/ 0 ping
local iocontrol = {}
---@class ioctl
@ -27,6 +33,19 @@ local function __generic_ack(success) end
-- luacheck: unused args
-- initialize front panel PSIL
---@param firmware_v string coordinator version
---@param comms_v string comms version
function iocontrol.init_fp(firmware_v, comms_v)
---@class ioctl_front_panel
io.fp = {
ps = psil.create()
}
io.fp.ps.publish("version", firmware_v)
io.fp.ps.publish("comms_version", comms_v)
end
-- initialize the coordinator IO controller
---@param conf facility_conf configuration
---@param comms coord_comms comms reference
@ -189,6 +208,52 @@ function iocontrol.init(conf, comms)
process.init(io, comms)
end
-- toggle heartbeat indicator
function iocontrol.heartbeat() io.fp.ps.toggle("heartbeat") end
-- report presence of the wireless modem
---@param has_modem boolean
function iocontrol.fp_has_modem(has_modem) io.fp.ps.publish("has_modem", has_modem) end
-- report presence of the speaker
---@param has_speaker boolean
function iocontrol.fp_has_speaker(has_speaker) io.fp.ps.publish("has_speaker", has_speaker) end
-- report supervisor link state
---@param state integer
function iocontrol.fp_link_state(state) io.fp.ps.publish("link_state", state) end
-- report PKT firmware version and PKT session connection state
---@param session_id integer PKT session
---@param fw string firmware version
---@param s_addr integer PKT computer ID
function iocontrol.fp_pkt_connected(session_id, fw, s_addr)
io.fp.ps.publish("pkt_" .. session_id .. "_fw", fw)
io.fp.ps.publish("pkt_" .. session_id .. "_addr", util.sprintf("@ C% 3d", s_addr))
pgi.create_pkt_entry(session_id)
end
-- report PKT session disconnected
---@param session_id integer PKT session
function iocontrol.fp_pkt_disconnected(session_id)
pgi.delete_pkt_entry(session_id)
end
-- transmit PKT session RTT
---@param session_id integer PKT session
---@param rtt integer round trip time
function iocontrol.fp_pkt_rtt(session_id, rtt)
io.fp.ps.publish("pkt_" .. session_id .. "_rtt", rtt)
if rtt > HIGH_RTT then
io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.red)
elseif rtt > WARN_RTT then
io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.yellow_hc)
else
io.fp.ps.publish("pkt_" .. session_id .. "_rtt_color", colors.green)
end
end
-- populate facility structure builds
---@param build table
---@return boolean valid

View File

@ -6,7 +6,9 @@ local log = require("scada-common.log")
local util = require("scada-common.util")
local style = require("coordinator.ui.style")
local pgi = require("coordinator.ui.pgi")
local panel_view = require("coordinator.ui.layout.front_panel")
local main_view = require("coordinator.ui.layout.main_view")
local unit_view = require("coordinator.ui.layout.unit_view")
@ -21,7 +23,9 @@ local engine = {
monitors = nil, ---@type monitors_struct|nil
dmesg_window = nil, ---@type table|nil
ui_ready = false,
fp_ready = false,
ui = {
front_panel = nil, ---@type graphics_element|nil
main_display = nil, ---@type graphics_element|nil
unit_displays = {}
}
@ -44,9 +48,7 @@ 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 end
-- check if the renderer is configured to use a given monitor peripheral
---@nodiscard
@ -75,6 +77,17 @@ function renderer.init_displays()
for _, monitor in ipairs(engine.monitors.unit_displays) do
_init_display(monitor)
end
-- init terminal
term.setTextColor(colors.white)
term.setBackgroundColor(colors.black)
term.clear()
term.setCursorPos(1, 1)
-- set overridden colors
for i = 1, #style.fp.colors do
term.setPaletteColor(style.fp.colors[i].c, style.fp.colors[i].hex)
end
end
-- check main display width
@ -109,6 +122,21 @@ function renderer.init_dmesg()
log.direct_dmesg(engine.dmesg_window)
end
-- start the coordinator front panel
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)
-- start flasher callback task
flasher.run()
-- report front panel as ready
engine.fp_ready = true
end
end
-- start the coordinator GUI
function renderer.start_ui()
if not engine.ui_ready then
@ -133,10 +161,42 @@ function renderer.start_ui()
end
end
-- close out the front panel
function renderer.close_fp()
if engine.fp_ready then
if not engine.ui_ready then
-- stop blinking indicators
flasher.clear()
end
-- disable PGI
pgi.unlink()
-- hide to stop animation callbacks and clear root UI elements
engine.ui.front_panel.hide()
engine.ui.front_panel = nil
engine.fp_ready = false
-- 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
-- close out the UI
function renderer.close_ui()
-- stop blinking indicators
flasher.clear()
if not engine.fp_ready then
-- stop blinking indicators
flasher.clear()
end
-- delete element trees
if engine.ui.main_display ~= nil then engine.ui.main_display.delete() end
@ -157,6 +217,11 @@ function renderer.close_ui()
engine.dmesg_window.redraw()
end
-- is the front panel ready?
---@nodiscard
---@return boolean ready
function renderer.fp_ready() return engine.fp_ready end
-- is the UI ready?
---@nodiscard
---@return boolean ready
@ -165,14 +230,19 @@ function renderer.ui_ready() return engine.ui_ready end
-- handle a touch event
---@param event mouse_interaction|nil
function renderer.handle_mouse(event)
if engine.ui_ready and event ~= nil then
if event.monitor == engine.monitors.primary_name then
engine.ui.main_display.handle_mouse(event)
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)
if event ~= nil then
if engine.fp_ready and event.monitor == "terminal" then
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)
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
end
end
end
end

View File

@ -1,11 +1,12 @@
local log = require("scada-common.log")
local mqueue = require("scada-common.mqueue")
local util = require("scada-common.util")
local log = require("scada-common.log")
local mqueue = require("scada-common.mqueue")
local util = require("scada-common.util")
local config = require("coordinator.config")
local config = require("coordinator.config")
local iocontrol = require("coordinator.iocontrol")
local pocket = require("coordinator.session.pocket")
local pocket = require("coordinator.session.pocket")
local apisessions = {}
@ -112,6 +113,7 @@ function apisessions.establish_session(source_addr, version)
setmetatable(pkt_s, mt)
iocontrol.fp_pkt_connected(id, version, source_addr)
log.debug(util.c("[API] established new session: ", pkt_s))
self.next_id = id + 1

View File

@ -1,7 +1,9 @@
local comms = require("scada-common.comms")
local log = require("scada-common.log")
local mqueue = require("scada-common.mqueue")
local util = require("scada-common.util")
local comms = require("scada-common.comms")
local log = require("scada-common.log")
local mqueue = require("scada-common.mqueue")
local util = require("scada-common.util")
local iocontrol = require("coordinator.iocontrol")
local pocket = {}
@ -9,8 +11,6 @@ local PROTOCOL = comms.PROTOCOL
-- local CAPI_TYPE = comms.CAPI_TYPE
local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
local println = util.println
-- retry time constants in ms
-- local INITIAL_WAIT = 1500
-- local RETRY_PERIOD = 1000
@ -69,6 +69,7 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
local function _close()
self.conn_watchdog.cancel()
self.connected = false
iocontrol.fp_pkt_disconnected(id)
end
-- send a CAPI packet
@ -140,6 +141,8 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
-- log.debug(log_header .. "PKT RTT = " .. self.last_rtt .. "ms")
-- log.debug(log_header .. "PKT TT = " .. (srv_now - api_send) .. "ms")
iocontrol.fp_pkt_rtt(id, self.last_rtt)
else
log.debug(log_header .. "SCADA keep alive packet length mismatch")
end
@ -172,7 +175,6 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
function public.close()
_close()
_send_mgmt(SCADA_MGMT_TYPE.CLOSE, {})
println("connection to pocket session " .. id .. " closed by server")
log.info(log_header .. "session closed by server")
end
@ -211,7 +213,6 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
-- exit if connection was closed
if not self.connected then
println("connection to pocket session " .. id .. " closed by remote host")
log.info(log_header .. "session closed by remote host")
return self.connected
end

View File

@ -4,6 +4,7 @@
require("/initenv").init_env()
local comms = require("scada-common.comms")
local crash = require("scada-common.crash")
local log = require("scada-common.log")
local network = require("scada-common.network")
@ -21,7 +22,7 @@ local sounder = require("coordinator.sounder")
local apisessions = require("coordinator.session.apisessions")
local COORDINATOR_VERSION = "v0.18.0"
local COORDINATOR_VERSION = "v0.19.1"
local println = util.println
local println_ts = util.println_ts
@ -30,7 +31,6 @@ local log_graphics = coordinator.log_graphics
local log_sys = coordinator.log_sys
local log_boot = coordinator.log_boot
local log_comms = coordinator.log_comms
local log_comms_connecting = coordinator.log_comms_connecting
local log_crypto = coordinator.log_crypto
----------------------------------------
@ -80,6 +80,9 @@ local function main()
-- mount connected devices
ppm.mount_all()
-- report versions/init fp PSIL
iocontrol.init_fp(COORDINATOR_VERSION, comms.version)
-- setup monitors
local configured, monitors = coordinator.configure_monitors(config.NUM_UNITS)
if not configured or monitors == nil then
@ -127,6 +130,7 @@ local function main()
sounder.init(speaker, config.SOUNDER_VOLUME)
log_boot("tone generation took " .. (util.time_ms() - sounder_start) .. "ms")
log_sys("annunciator alarm configured")
iocontrol.fp_has_speaker(true)
end
----------------------------------------
@ -148,6 +152,7 @@ local function main()
return
else
log_comms("wireless modem connected")
iocontrol.fp_has_modem(true)
end
-- create connection watchdog
@ -167,76 +172,54 @@ local function main()
local loop_clock = util.new_clock(MAIN_CLOCK)
----------------------------------------
-- connect to the supervisor
-- start front panel & UI start function
----------------------------------------
-- attempt to connect to the supervisor or exit
local function init_connect_sv()
local tick_waiting, task_done = log_comms_connecting("attempting to connect to configured supervisor on channel " .. config.SVR_CHANNEL)
log_graphics("starting front panel UI...")
-- attempt to establish a connection with the supervisory computer
if not coord_comms.sv_connect(60, tick_waiting, task_done) then
log_sys("supervisor connection failed, shutting down...")
log.fatal("failed to connect to supervisor")
return false
end
return true
end
if not init_connect_sv() then
println("startup> failed to connect to supervisor")
log_sys("system shutdown")
local fp_ok, fp_message = pcall(renderer.start_fp)
if not fp_ok then
renderer.close_fp()
log_graphics(util.c("front panel UI error: ", fp_message))
println_ts("front panel UI creation failed")
log.fatal(util.c("front panel GUI render failed with error ", fp_message))
return
else
log_sys("supervisor connected, proceeding to UI start")
end
else log_graphics("front panel ready") end
----------------------------------------
-- start the UI
----------------------------------------
-- start up the UI
-- start up the main UI
---@return boolean ui_ok started ok
local function init_start_ui()
log_graphics("starting UI...")
local function start_main_ui()
log_graphics("starting main UI...")
local draw_start = util.time_ms()
local ui_ok, message = pcall(renderer.start_ui)
local ui_ok, ui_message = pcall(renderer.start_ui)
if not ui_ok then
renderer.close_ui()
log_graphics(util.c("UI crashed: ", message))
println_ts("UI crashed")
log.fatal(util.c("GUI crashed with error ", message))
log_graphics(util.c("main UI error: ", ui_message))
log.fatal(util.c("main GUI render failed with error ", ui_message))
else
log_graphics("first UI draw took " .. (util.time_ms() - draw_start) .. "ms")
-- start clock
loop_clock.start()
log_graphics("main UI draw took " .. (util.time_ms() - draw_start) .. "ms")
end
return ui_ok
end
local ui_ok = init_start_ui()
----------------------------------------
-- main event loop
----------------------------------------
local link_failed = false
local ui_ok = true
local date_format = util.trinary(config.TIME_24_HOUR, "%X \x04 %A, %B %d %Y", "%r \x04 %A, %B %d %Y")
if ui_ok then
-- start connection watchdog
conn_watchdog.feed()
log.debug("startup> conn watchdog started")
-- start clock
loop_clock.start()
log_sys("system started successfully")
end
log_sys("system started successfully")
-- main event loop
while ui_ok do
while true do
local event, param1, param2, param3, param4, param5 = util.pull_event()
-- handle event
@ -250,13 +233,14 @@ local function main()
if nic.is_modem(device) then
nic.disconnect()
log_sys("comms modem disconnected")
println_ts("wireless modem disconnected!")
-- close out UI
-- close out main UI
renderer.close_ui()
-- alert user to status
log_sys("awaiting comms modem reconnect...")
iocontrol.fp_has_modem(false)
else
log_sys("non-comms modem disconnected")
end
@ -264,17 +248,14 @@ local function main()
if renderer.is_monitor_used(device) then
---@todo will be handled properly in #249
-- "halt and catch fire" style handling
local msg = "lost a configured monitor, system will now exit"
println_ts(msg)
log_sys(msg)
log_sys("lost a configured monitor, system will now exit")
break
else
log_sys("lost unused monitor, ignoring")
end
elseif type == "speaker" then
local msg = "lost alarm sounder speaker"
println_ts(msg)
log_sys(msg)
log_sys("lost alarm sounder speaker")
iocontrol.fp_has_speaker(false)
end
end
elseif event == "peripheral" then
@ -284,14 +265,9 @@ local function main()
if type == "modem" then
if device.isWireless() then
-- reconnected modem
nic.connect(device)
log_sys("comms modem reconnected")
println_ts("wireless modem reconnected.")
-- re-init system
if not init_connect_sv() then break end
ui_ok = init_start_ui()
nic.connect(device)
iocontrol.fp_has_modem(true)
else
log_sys("wired modem reconnected")
end
@ -299,16 +275,33 @@ local function main()
---@todo will be handled properly in #249
-- not supported, system will exit on loss of in-use monitors
elseif type == "speaker" then
local msg = "alarm sounder speaker reconnected"
println_ts(msg)
log_sys(msg)
log_sys("alarm sounder speaker reconnected")
sounder.reconnect(device)
iocontrol.fp_has_speaker(true)
end
end
elseif event == "timer" then
if loop_clock.is_clock(param1) then
-- main loop tick
-- toggle heartbeat
iocontrol.heartbeat()
-- maintain connection
if nic.connected() then
local ok, start_ui = coord_comms.try_connect()
if not ok then
link_failed = true
log_sys("supervisor connection failed, shutting down...")
log.fatal("failed to connect to supervisor")
break
elseif start_ui then
log_sys("supervisor connected, proceeding to main UI start")
ui_ok = start_main_ui()
if not ui_ok then break end
end
end
-- iterate sessions
apisessions.iterate_all()
@ -316,25 +309,19 @@ local function main()
apisessions.free_all_closed()
-- update date and time string for main display
iocontrol.get_db().facility.ps.publish("date_time", os.date(date_format))
if coord_comms.is_linked() then
iocontrol.get_db().facility.ps.publish("date_time", os.date(date_format))
end
loop_clock.start()
elseif conn_watchdog.is_timer(param1) then
-- supervisor watchdog timeout
local msg = "supervisor server timeout"
log_comms(msg)
println_ts(msg)
log_comms("supervisor server timeout")
-- close connection, UI, and stop sounder
-- close connection, main UI, and stop sounder
coord_comms.close()
renderer.close_ui()
sounder.stop()
if nic.connected() then
-- try to re-connect to the supervisor
if not init_connect_sv() then break end
ui_ok = init_start_ui()
end
else
-- a non-clock/main watchdog timer event
@ -347,25 +334,19 @@ local function main()
elseif event == "modem_message" then
-- got a packet
local packet = coord_comms.parse_packet(param1, param2, param3, param4, param5)
coord_comms.handle_packet(packet)
-- check if it was a disconnect
if not coord_comms.is_linked() then
-- handle then check if it was a disconnect
if coord_comms.handle_packet(packet) then
log_comms("supervisor closed connection")
-- close connection, UI, and stop sounder
-- close connection, main UI, and stop sounder
coord_comms.close()
renderer.close_ui()
sounder.stop()
if nic.connected() then
-- try to re-connect to the supervisor
if not init_connect_sv() then break end
ui_ok = init_start_ui()
end
end
elseif event == "monitor_touch" then
-- handle a monitor touch event
elseif event == "monitor_touch" or 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))
elseif event == "speaker_audio_empty" then
-- handle speaker buffer emptied
@ -374,10 +355,17 @@ local function main()
-- check for termination request
if event == "terminate" or ppm.should_terminate() then
println_ts("terminate requested, closing connections...")
log_comms("terminate requested, closing supervisor connection...")
-- handle supervisor connection
link_failed = coord_comms.try_connect(true)
if coord_comms.is_linked() then
log_comms("terminate requested, closing supervisor connection...")
else link_failed = true end
coord_comms.close()
log_comms("supervisor connection closed")
-- handle API sessions
log_comms("closing api sessions...")
apisessions.close_all()
log_comms("api sessions closed")
@ -386,15 +374,20 @@ local function main()
end
renderer.close_ui()
renderer.close_fp()
sounder.stop()
log_sys("system shutdown")
if link_failed then println_ts("failed to connect to supervisor") end
if not ui_ok then println_ts("main UI creation failed") end
println_ts("exited")
log.info("exited")
end
if not xpcall(main, crash.handler) then
pcall(renderer.close_ui)
pcall(renderer.close_fp)
pcall(sounder.stop)
crash.exit()
else

View File

@ -0,0 +1,48 @@
--
-- Pocket Connection Entry
--
local iocontrol = require("coordinator.iocontrol")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
local DataIndicator = require("graphics.elements.indicators.data")
local TEXT_ALIGN = core.TEXT_ALIGN
local cpair = core.cpair
-- create a pocket list entry
---@param parent graphics_element parent
---@param id integer PKT session ID
local function init(parent, id)
local ps = iocontrol.get_db().fp.ps
-- root div
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2,hidden=true}
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=cpair(colors.black,colors.white)}
local ps_prefix = "pkt_" .. id .. "_"
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
local pkt_addr = TextBox{parent=entry,x=1,y=2,text="@ C ??",alignment=TEXT_ALIGN.CENTER,width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray),nav_active=cpair(colors.gray,colors.black)}
TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
pkt_addr.register(ps, ps_prefix .. "addr", pkt_addr.set_value)
TextBox{parent=entry,x=10,y=2,text="FW:",width=3,height=1}
local pkt_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
pkt_fw_v.register(ps, ps_prefix .. "fw", pkt_fw_v.set_value)
TextBox{parent=entry,x=35,y=2,text="RTT:",width=4,height=1}
local pkt_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=cpair(colors.lightGray,colors.white)}
TextBox{parent=entry,x=46,y=2,text="ms",width=4,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
pkt_rtt.register(ps, ps_prefix .. "rtt", pkt_rtt.update)
pkt_rtt.register(ps, ps_prefix .. "rtt_color", pkt_rtt.recolor)
return root
end
return init

View File

@ -0,0 +1,108 @@
--
-- Coordinator Front Panel GUI
--
local types = require("scada-common.types")
local util = require("scada-common.util")
local iocontrol = require("coordinator.iocontrol")
local pgi = require("coordinator.ui.pgi")
local style = require("coordinator.ui.style")
local pkt_entry = require("coordinator.ui.components.pkt_entry")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local ListBox = require("graphics.elements.listbox")
local MultiPane = require("graphics.elements.multipane")
local TextBox = require("graphics.elements.textbox")
local TabBar = require("graphics.elements.controls.tabbar")
local LED = require("graphics.elements.indicators.led")
local RGBLED = require("graphics.elements.indicators.ledrgb")
local TEXT_ALIGN = core.TEXT_ALIGN
local cpair = core.cpair
-- create new front panel view
---@param panel graphics_element main displaybox
local function init(panel)
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}
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 status = LED{parent=system,label="STATUS",colors=cpair(colors.green,colors.red)}
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=cpair(colors.green,colors.green_off)}
status.update(true)
system.line_break()
heartbeat.register(ps, "heartbeat", heartbeat.update)
local modem = LED{parent=system,label="MODEM",colors=cpair(colors.green,colors.green_off)}
local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,colors.gray}}
network.update(types.PANEL_LINK_STATE.DISCONNECTED)
system.line_break()
modem.register(ps, "has_modem", modem.update)
network.register(ps, "link_state", network.update)
local speaker = LED{parent=system,label="SPEAKER",colors=cpair(colors.green,colors.green_off)}
speaker.register(ps, "has_speaker", speaker.update)
---@diagnostic disable-next-line: undefined-field
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)}
--
-- 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}
fw_v.register(ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
comms_v.register(ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
--
-- page handling
--
-- API page
local api_page = Div{parent=page_div,x=1,y=1,hidden=true}
local api_list = ListBox{parent=api_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=cpair(colors.black,colors.ivory),nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
local _ = Div{parent=api_list,height=1,hidden=true} -- padding
-- assemble page panes
local panes = { main_page, api_page }
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
local tabs = {
{ name = "CRD", color = cpair(colors.black, colors.ivory) },
{ name = "API", color = cpair(colors.black, colors.ivory) },
}
TabBar{parent=panel,y=2,tabs=tabs,min_width=9,callback=page_pane.set_value,fg_bg=cpair(colors.black,colors.white)}
-- link pocket API list management to PGI
pgi.link_elements(api_list, pkt_entry)
end
return init

58
coordinator/ui/pgi.lua Normal file
View File

@ -0,0 +1,58 @@
--
-- Protected Graphics Interface
--
local log = require("scada-common.log")
local util = require("scada-common.util")
local pgi = {}
local data = {
pkt_list = nil, ---@type nil|graphics_element
pkt_entry = nil, ---@type function
-- session entries
s_entries = { pkt = {} }
}
-- link list boxes
---@param pkt_list graphics_element pocket list element
---@param pkt_entry function pocket entry constructor
function pgi.link_elements(pkt_list, pkt_entry)
data.pkt_list = pkt_list
data.pkt_entry = pkt_entry
end
-- unlink all fields, disabling the PGI
function pgi.unlink()
data.pkt_list = nil
data.pkt_entry = nil
end
-- add a PKT entry to the PKT list
---@param session_id integer pocket session
function pgi.create_pkt_entry(session_id)
if data.pkt_list ~= nil and data.pkt_entry ~= nil then
local success, result = pcall(data.pkt_entry, data.pkt_list, session_id)
if success then
data.s_entries.pkt[session_id] = result
else
log.error(util.c("PGI: failed to create PKT entry (", result, ")"), true)
end
end
end
-- delete a PKT entry from the PKT list
---@param session_id integer pocket session
function pgi.delete_pkt_entry(session_id)
if data.s_entries.pkt[session_id] ~= nil then
local success, result = pcall(data.s_entries.pkt[session_id].delete)
data.s_entries.pkt[session_id] = nil
if not success then
log.error(util.c("PGI: failed to delete PKT entry (", result, ")"), true)
end
end
end
return pgi

View File

@ -10,6 +10,40 @@ local cpair = core.cpair
-- GLOBAL --
-- add color mappings for front panel
colors.ivory = colors.pink
colors.red_off = colors.brown
colors.yellow_off = colors.magenta
colors.green_off = colors.lime
-- front panel styling
style.fp = {}
style.fp.root = cpair(colors.black, colors.ivory)
style.fp.header = cpair(colors.black, colors.lightGray)
style.fp.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
}
-- main GUI styling
style.root = cpair(colors.black, colors.lightGray)
style.header = cpair(colors.white, colors.gray)
style.label = cpair(colors.gray, colors.lightGray)

View File

@ -7,7 +7,7 @@ local flasher = require("graphics.flasher")
local core = {}
core.version = "1.0.0"
core.version = "1.0.1"
core.flasher = flasher
core.events = events

View File

@ -8,7 +8,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -8,7 +8,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field hidden? boolean true to hide on initial draw
-- new color map

View File

@ -10,7 +10,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -14,7 +14,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -20,7 +20,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field height? integer parent height if omitted
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -16,7 +16,7 @@ local CLICK_TYPE = core.events.CLICK_TYPE
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field height? integer parent height if omitted
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -13,7 +13,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -17,7 +17,7 @@ local CLICK_TYPE = core.events.CLICK_TYPE
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field height? integer parent height if omitted
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -16,7 +16,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -12,7 +12,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field height? integer parent height if omitted
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -18,7 +18,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field width? integer parent width if omitted
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -6,7 +6,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field width? integer parent width if omitted
---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height

View File

@ -16,7 +16,7 @@ local flasher = require("graphics.flasher")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -11,7 +11,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
-- new core map box
---@nodiscard

View File

@ -14,7 +14,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field width integer length
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -10,7 +10,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field width? integer parent width if omitted
---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height

View File

@ -16,7 +16,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -14,7 +14,7 @@ local flasher = require("graphics.flasher")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -16,7 +16,7 @@ local flasher = require("graphics.flasher")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -9,7 +9,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -14,7 +14,7 @@ local flasher = require("graphics.flasher")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -13,7 +13,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field width integer length
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -14,7 +14,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field width integer length
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -15,7 +15,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field height? integer 1 if omitted, must be an odd number
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -16,7 +16,7 @@ local flasher = require("graphics.flasher")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw

View File

@ -8,7 +8,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field width? integer parent width if omitted
---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height

View File

@ -15,7 +15,7 @@ local CLICK_TYPE = core.events.CLICK_TYPE
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field width? integer parent width if omitted
---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height

View File

@ -7,7 +7,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field width? integer parent width if omitted
---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height

View File

@ -11,7 +11,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field hidden? boolean true to hide on initial draw
-- new pipe network

View File

@ -11,7 +11,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field width? integer parent width if omitted
---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height

View File

@ -13,7 +13,7 @@ local TEXT_ALIGN = core.TEXT_ALIGN
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field width? integer parent width if omitted
---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height

View File

@ -11,7 +11,7 @@ local element = require("graphics.element")
---@field parent graphics_element
---@field id? string element id
---@field x? integer 1 if omitted
---@field y? integer 1 if omitted
---@field y? integer auto incremented if omitted
---@field width? integer parent width if omitted
---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height

View File

@ -43,8 +43,10 @@ end
-- start/resume the flasher periodic
function flasher.run()
active = true
callback_250ms()
if not active then
active = true
callback_250ms()
end
end
-- clear all blinking indicators and stop the flasher periodic

File diff suppressed because one or more lines are too long

View File

@ -18,7 +18,7 @@ local coreio = require("pocket.coreio")
local pocket = require("pocket.pocket")
local renderer = require("pocket.renderer")
local POCKET_VERSION = "alpha-v0.5.1"
local POCKET_VERSION = "alpha-v0.5.2"
local println = util.println
local println_ts = util.println_ts
@ -112,7 +112,7 @@ local function main()
if not ui_ok then
renderer.close_ui()
println(util.c("UI error: ", message))
log.error(util.c("startup> GUI crashed with error ", message))
log.error(util.c("startup> GUI render failed with error ", message))
else
-- start clock
loop_clock.start()

View File

@ -1,5 +1,5 @@
--
-- Main SCADA Coordinator GUI
-- Reactor PLC Front Panel GUI
--
local types = require("scada-common.types")
@ -28,7 +28,7 @@ local TEXT_ALIGN = core.TEXT_ALIGN
local cpair = core.cpair
local border = core.border
-- create new main view
-- create new front panel view
---@param panel graphics_element main displaybox
local function init(panel)
local header = TextBox{parent=panel,y=1,text="REACTOR PLC - UNIT ?",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header}

View File

@ -19,7 +19,7 @@ local plc = require("reactor-plc.plc")
local renderer = require("reactor-plc.renderer")
local threads = require("reactor-plc.threads")
local R_PLC_VERSION = "v1.5.0"
local R_PLC_VERSION = "v1.5.1"
local println = util.println
local println_ts = util.println_ts
@ -190,7 +190,7 @@ local function main()
renderer.close_ui()
println_ts(util.c("UI error: ", message))
println("init> running without front panel")
log.error(util.c("GUI crashed with error ", message))
log.error(util.c("front panel GUI render failed with error ", message))
log.info("init> running in headless mode without front panel")
end
end

View File

@ -1,5 +1,5 @@
--
-- Main SCADA Coordinator GUI
-- RTU Front Panel GUI
--
local types = require("scada-common.types")
@ -33,7 +33,7 @@ local UNIT_TYPE_LABELS = {
}
-- create new main view
-- create new front panel view
---@param panel graphics_element main displaybox
---@param units table unit list
local function init(panel, units)

View File

@ -29,7 +29,7 @@ local sna_rtu = require("rtu.dev.sna_rtu")
local sps_rtu = require("rtu.dev.sps_rtu")
local turbinev_rtu = require("rtu.dev.turbinev_rtu")
local RTU_VERSION = "v1.4.0"
local RTU_VERSION = "v1.4.1"
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
@ -464,7 +464,7 @@ local function main()
renderer.close_ui()
println_ts(util.c("UI error: ", message))
println("startup> running without front panel")
log.error(util.c("GUI crashed with error ", message))
log.error(util.c("front panel GUI render failed with error ", message))
log.info("startup> running in headless mode without front panel")
end

View File

@ -1,5 +1,5 @@
--
-- Main SCADA Coordinator GUI
-- Supervisor Front Panel GUI
--
local util = require("scada-common.util")
@ -29,7 +29,7 @@ local TEXT_ALIGN = core.TEXT_ALIGN
local cpair = core.cpair
-- create new main view
-- create new front panel 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}

View File

@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor")
local svsessions = require("supervisor.session.svsessions")
local SUPERVISOR_VERSION = "v0.19.1"
local SUPERVISOR_VERSION = "v0.19.2"
local println = util.println
local println_ts = util.println_ts
@ -116,7 +116,7 @@ local function main()
if not fp_ok then
renderer.close_ui()
println_ts(util.c("UI error: ", message))
log.error(util.c("GUI crashed with error ", message))
log.error(util.c("front panel GUI render failed with error ", message))
else
-- redefine println_ts local to not print as we have the front panel running
println_ts = function (_) end