mirror of
https://github.com/MikaylaFischler/cc-mek-scada.git
synced 2024-08-30 18:22:34 +00:00
Merge pull request #246 from MikaylaFischler/front-panels
Supervisor Front Panel
This commit is contained in:
commit
2427561dc5
@ -20,7 +20,7 @@ local sounder = require("coordinator.sounder")
|
||||
|
||||
local apisessions = require("coordinator.session.apisessions")
|
||||
|
||||
local COORDINATOR_VERSION = "v0.15.2"
|
||||
local COORDINATOR_VERSION = "v0.15.6"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
|
@ -33,7 +33,7 @@ local period = core.flasher.PERIOD
|
||||
---@param x integer top left x
|
||||
---@param y integer top left y
|
||||
local function new_view(root, x, y)
|
||||
assert(root.height() >= (y + 24), "main display not of sufficient vertical resolution (add an additional row of monitors)")
|
||||
assert(root.get_height() >= (y + 24), "main display not of sufficient vertical resolution (add an additional row of monitors)")
|
||||
|
||||
local facility = iocontrol.get_db().facility
|
||||
local units = iocontrol.get_db().units
|
||||
|
@ -38,7 +38,7 @@ local function make(parent, x, y, unit)
|
||||
height = 17
|
||||
end
|
||||
|
||||
assert(parent.height() >= (y + height), "main display not of sufficient vertical resolution (add an additional row of monitors)")
|
||||
assert(parent.get_height() >= (y + height), "main display not of sufficient vertical resolution (add an additional row of monitors)")
|
||||
|
||||
-- bounding box div
|
||||
local root = Div{parent=parent,x=x,y=y,width=80,height=height}
|
||||
|
@ -32,7 +32,7 @@ local function init(main)
|
||||
local header = TextBox{parent=main,y=1,text="Nuclear Generation Facility SCADA Coordinator",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header}
|
||||
local ping = DataIndicator{parent=main,x=1,y=1,label="SVTT",format="%d",value=0,unit="ms",lu_colors=cpair(colors.lightGray, colors.white),width=12,fg_bg=style.header}
|
||||
-- max length example: "01:23:45 AM - Wednesday, September 28 2022"
|
||||
local datetime = TextBox{parent=main,x=(header.width()-42),y=1,text="",alignment=TEXT_ALIGN.RIGHT,width=42,height=1,fg_bg=style.header}
|
||||
local datetime = TextBox{parent=main,x=(header.get_width()-42),y=1,text="",alignment=TEXT_ALIGN.RIGHT,width=42,height=1,fg_bg=style.header}
|
||||
|
||||
ping.register(facility.ps, "sv_ping", ping.update)
|
||||
datetime.register(facility.ps, "date_time", datetime.set_value)
|
||||
@ -45,12 +45,12 @@ local function init(main)
|
||||
-- unit overviews
|
||||
if facility.num_units >= 1 then
|
||||
uo_1 = unit_overview(main, 2, 3, units[1])
|
||||
row_1_height = uo_1.height()
|
||||
row_1_height = uo_1.get_height()
|
||||
end
|
||||
|
||||
if facility.num_units >= 2 then
|
||||
uo_2 = unit_overview(main, 84, 3, units[2])
|
||||
row_1_height = math.max(row_1_height, uo_2.height())
|
||||
row_1_height = math.max(row_1_height, uo_2.get_height())
|
||||
end
|
||||
|
||||
cnc_y_start = cnc_y_start + row_1_height + 1
|
||||
@ -60,11 +60,11 @@ local function init(main)
|
||||
local row_2_offset = cnc_y_start
|
||||
|
||||
uo_3 = unit_overview(main, 2, row_2_offset, units[3])
|
||||
cnc_y_start = row_2_offset + uo_3.height() + 1
|
||||
cnc_y_start = row_2_offset + uo_3.get_height() + 1
|
||||
|
||||
if facility.num_units == 4 then
|
||||
uo_4 = unit_overview(main, 84, row_2_offset, units[4])
|
||||
cnc_y_start = math.max(cnc_y_start, row_2_offset + uo_4.height() + 1)
|
||||
cnc_y_start = math.max(cnc_y_start, row_2_offset + uo_4.get_height() + 1)
|
||||
end
|
||||
end
|
||||
|
||||
@ -73,11 +73,11 @@ local function init(main)
|
||||
cnc_y_start = cnc_y_start
|
||||
|
||||
-- induction matrix and process control interfaces are 24 tall + space needed for divider
|
||||
local cnc_bottom_align_start = main.height() - 26
|
||||
local cnc_bottom_align_start = main.get_height() - 26
|
||||
|
||||
assert(cnc_bottom_align_start >= cnc_y_start, "main display not of sufficient vertical resolution (add an additional row of monitors)")
|
||||
|
||||
TextBox{parent=main,y=cnc_bottom_align_start,text=util.strrep("\x8c", header.width()),alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=cpair(colors.lightGray,colors.gray)}
|
||||
TextBox{parent=main,y=cnc_bottom_align_start,text=util.strrep("\x8c", header.get_width()),alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=cpair(colors.lightGray,colors.gray)}
|
||||
|
||||
cnc_bottom_align_start = cnc_bottom_align_start + 2
|
||||
|
||||
|
@ -12,12 +12,11 @@ local element = {}
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer next line if omitted
|
||||
---@field offset_x? integer 0 if omitted
|
||||
---@field offset_y? integer 0 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
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
---@alias graphics_args graphics_args_generic
|
||||
---|waiting_args
|
||||
@ -46,6 +45,7 @@ local element = {}
|
||||
---|colormap_args
|
||||
---|displaybox_args
|
||||
---|div_args
|
||||
---|listbox_args
|
||||
---|multipane_args
|
||||
---|pipenet_args
|
||||
---|rectangle_args
|
||||
@ -62,26 +62,26 @@ local element = {}
|
||||
---@param args graphics_args arguments
|
||||
function element.new(args)
|
||||
local self = {
|
||||
id = -1,
|
||||
id = nil, ---@type element_id|nil
|
||||
elem_type = debug.getinfo(2).name,
|
||||
define_completed = false,
|
||||
p_window = nil, ---@type table
|
||||
position = { x = 1, y = 1 }, ---@type coordinate_2d
|
||||
child_offset = { x = 0, y = 0 },
|
||||
bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1 }, ---@class element_bounds
|
||||
next_y = 1,
|
||||
children = {},
|
||||
subscriptions = {},
|
||||
mt = {}
|
||||
}
|
||||
|
||||
---@class graphics_template
|
||||
---@class graphics_base
|
||||
local protected = {
|
||||
enabled = true,
|
||||
value = nil, ---@type any
|
||||
window = nil, ---@type table
|
||||
value = nil, ---@type any
|
||||
window = nil, ---@type table
|
||||
content_window = nil, ---@type table|nil
|
||||
fg_bg = core.cpair(colors.white, colors.black),
|
||||
frame = core.gframe(1, 1, 1, 1)
|
||||
frame = core.gframe(1, 1, 1, 1),
|
||||
children = {}
|
||||
}
|
||||
|
||||
local name_brief = "graphics.element{" .. self.elem_type .. "}: "
|
||||
@ -101,10 +101,8 @@ function element.new(args)
|
||||
-------------------------
|
||||
|
||||
-- prepare the template
|
||||
---@param offset_x integer x offset
|
||||
---@param offset_y integer y offset
|
||||
---@param next_y integer next line if no y was provided
|
||||
function protected.prepare_template(offset_x, offset_y, next_y)
|
||||
function protected.prepare_template(next_y)
|
||||
-- get frame coordinates/size
|
||||
if args.gframe ~= nil then
|
||||
protected.frame.x = args.gframe.x
|
||||
@ -114,36 +112,18 @@ function element.new(args)
|
||||
else
|
||||
local w, h = self.p_window.getSize()
|
||||
protected.frame.x = args.x or 1
|
||||
|
||||
if args.parent ~= nil then
|
||||
protected.frame.y = args.y or (next_y - offset_y)
|
||||
else
|
||||
protected.frame.y = args.y or next_y
|
||||
end
|
||||
|
||||
protected.frame.y = args.y or next_y
|
||||
protected.frame.w = args.width or w
|
||||
protected.frame.h = args.height or h
|
||||
end
|
||||
|
||||
-- inner offsets
|
||||
if args.offset_x ~= nil then self.child_offset.x = args.offset_x end
|
||||
if args.offset_y ~= nil then self.child_offset.y = args.offset_y end
|
||||
|
||||
-- adjust window frame if applicable
|
||||
local f = protected.frame
|
||||
local x = f.x
|
||||
local y = f.y
|
||||
|
||||
-- apply offsets
|
||||
if args.parent ~= nil then
|
||||
-- constrain to parent inner width/height
|
||||
local w, h = self.p_window.getSize()
|
||||
f.w = math.min(f.w, w - ((2 * offset_x) + (f.x - 1)))
|
||||
f.h = math.min(f.h, h - ((2 * offset_y) + (f.y - 1)))
|
||||
|
||||
-- offset x/y
|
||||
f.x = x + offset_x
|
||||
f.y = y + offset_y
|
||||
f.w = math.min(f.w, w - (f.x - 1))
|
||||
f.h = math.min(f.h, h - (f.y - 1))
|
||||
end
|
||||
|
||||
-- check frame
|
||||
@ -153,7 +133,7 @@ function element.new(args)
|
||||
assert(f.h >= 1, name_brief .. "frame height not >= 1")
|
||||
|
||||
-- create window
|
||||
protected.window = window.create(self.p_window, f.x, f.y, f.w, f.h, true)
|
||||
protected.window = window.create(self.p_window, f.x, f.y, f.w, f.h, args.hidden ~= true)
|
||||
|
||||
-- init colors
|
||||
if args.fg_bg ~= nil then
|
||||
@ -198,15 +178,15 @@ function element.new(args)
|
||||
-- luacheck: push ignore
|
||||
---@diagnostic disable: unused-local, unused-vararg
|
||||
|
||||
-- dynamically insert a child element
|
||||
---@param id string|integer element identifier
|
||||
---@param elem graphics_element element
|
||||
function protected.insert(id, elem)
|
||||
-- handle a child element having been added
|
||||
---@param id element_id element identifier
|
||||
---@param child graphics_element child element
|
||||
function protected.on_added(id, child)
|
||||
end
|
||||
|
||||
-- dynamically remove a child element
|
||||
---@param id string|integer element identifier
|
||||
function protected.remove(id)
|
||||
-- handle a child element having been removed
|
||||
---@param id element_id element identifier
|
||||
function protected.on_removed(id)
|
||||
end
|
||||
|
||||
-- handle a mouse event
|
||||
@ -279,6 +259,14 @@ function element.new(args)
|
||||
---@return graphics_element element, element_id id
|
||||
function protected.get() return public, self.id end
|
||||
|
||||
-- report completion of element instantiation and get the public interface
|
||||
---@nodiscard
|
||||
---@return graphics_element element, element_id id
|
||||
function protected.complete()
|
||||
if args.parent ~= nil then args.parent.__child_ready(self.id, public) end
|
||||
return public, self.id
|
||||
end
|
||||
|
||||
-----------
|
||||
-- SETUP --
|
||||
-----------
|
||||
@ -294,7 +282,8 @@ function element.new(args)
|
||||
|
||||
-- prepare the template
|
||||
if args.parent == nil then
|
||||
protected.prepare_template(0, 0, 1)
|
||||
self.id = args.id or "__ROOT__"
|
||||
protected.prepare_template(1)
|
||||
else
|
||||
self.id = args.parent.__add_child(args.id, protected)
|
||||
end
|
||||
@ -305,11 +294,21 @@ function element.new(args)
|
||||
|
||||
-- get the window object
|
||||
---@nodiscard
|
||||
function public.window() return protected.window end
|
||||
function public.window() return protected.content_window or protected.window end
|
||||
|
||||
-- delete this element (hide and unsubscribe from PSIL)
|
||||
function public.delete()
|
||||
-- hide + stop animations
|
||||
local fg_bg = protected.fg_bg
|
||||
|
||||
if args.parent ~= nil then
|
||||
-- grab parent fg/bg so we can clear cleanly as a child element
|
||||
fg_bg = args.parent.get_fg_bg()
|
||||
end
|
||||
|
||||
-- clear, hide, and stop animations
|
||||
protected.window.setBackgroundColor(fg_bg.bkg)
|
||||
protected.window.setTextColor(fg_bg.fgd)
|
||||
protected.window.clear()
|
||||
public.hide()
|
||||
|
||||
-- unsubscribe from PSIL
|
||||
@ -319,9 +318,14 @@ function element.new(args)
|
||||
end
|
||||
|
||||
-- delete all children
|
||||
for k, v in pairs(self.children) do
|
||||
for k, v in pairs(protected.children) do
|
||||
v.delete()
|
||||
self.children[k] = nil
|
||||
protected.children[k] = nil
|
||||
end
|
||||
|
||||
if args.parent ~= nil then
|
||||
-- remove self from parent
|
||||
args.parent.__remove_child(self.id)
|
||||
end
|
||||
end
|
||||
|
||||
@ -330,41 +334,53 @@ function element.new(args)
|
||||
-- add a child element
|
||||
---@nodiscard
|
||||
---@param key string|nil id
|
||||
---@param child graphics_template
|
||||
---@param child graphics_base
|
||||
---@return integer|string key
|
||||
function public.__add_child(key, child)
|
||||
-- offset first automatic placement
|
||||
if self.next_y <= self.child_offset.y then
|
||||
self.next_y = self.child_offset.y + 1
|
||||
end
|
||||
|
||||
child.prepare_template(self.child_offset.x, self.child_offset.y, self.next_y)
|
||||
child.prepare_template(self.next_y)
|
||||
|
||||
self.next_y = child.frame.y + child.frame.h
|
||||
|
||||
local child_element = child.get()
|
||||
|
||||
if key == nil then
|
||||
table.insert(self.children, child_element)
|
||||
return #self.children
|
||||
table.insert(protected.children, child_element)
|
||||
return #protected.children
|
||||
else
|
||||
self.children[key] = child_element
|
||||
protected.children[key] = child_element
|
||||
return key
|
||||
end
|
||||
end
|
||||
|
||||
-- remove a child element
|
||||
---@param key element_id id
|
||||
function public.__remove_child(key)
|
||||
if protected.children[key] ~= nil then
|
||||
protected.on_removed(key)
|
||||
protected.children[key] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- actions to take upon a child element becoming ready (initial draw/construction completed)
|
||||
---@param key element_id id
|
||||
---@param child graphics_element
|
||||
function public.__child_ready(key, child)
|
||||
protected.on_added(key, child)
|
||||
end
|
||||
|
||||
-- get a child element
|
||||
---@nodiscard
|
||||
---@param id element_id
|
||||
---@return graphics_element
|
||||
function public.get_child(id) return self.children[id] end
|
||||
function public.get_child(id) return protected.children[id] end
|
||||
|
||||
-- remove a child element
|
||||
---@param id element_id
|
||||
function public.remove(id)
|
||||
if self.children[id] ~= nil then
|
||||
self.children[id].delete()
|
||||
self.children[id] = nil
|
||||
if protected.children[id] ~= nil then
|
||||
protected.children[id].delete()
|
||||
protected.on_removed(id)
|
||||
protected.children[id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
@ -373,37 +389,18 @@ function element.new(args)
|
||||
---@param id element_id
|
||||
---@return graphics_element|nil element
|
||||
function public.get_element_by_id(id)
|
||||
if self.children[id] == nil then
|
||||
for _, child in pairs(self.children) do
|
||||
if protected.children[id] == nil then
|
||||
for _, child in pairs(protected.children) do
|
||||
local elem = child.get_element_by_id(id)
|
||||
if elem ~= nil then return elem end
|
||||
end
|
||||
else
|
||||
return self.children[id]
|
||||
return protected.children[id]
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
-- DYNAMIC CHILD ELEMENTS --
|
||||
|
||||
-- insert an element as a contained child<br>
|
||||
-- this is intended to be used dynamically, and depends on the target element type.<br>
|
||||
-- not all elements support dynamic children.
|
||||
---@param id string|integer element identifier
|
||||
---@param elem graphics_element element
|
||||
function public.insert_element(id, elem)
|
||||
protected.insert(id, elem)
|
||||
end
|
||||
|
||||
-- remove an element from contained children<br>
|
||||
-- this is intended to be used dynamically, and depends on the target element type.<br>
|
||||
-- not all elements support dynamic children.
|
||||
---@param id string|integer element identifier
|
||||
function public.remove_element(id)
|
||||
protected.remove(id)
|
||||
end
|
||||
|
||||
-- AUTO-PLACEMENT --
|
||||
|
||||
-- skip a line for automatically placed elements
|
||||
@ -437,14 +434,14 @@ function element.new(args)
|
||||
-- get element width
|
||||
---@nodiscard
|
||||
---@return integer width
|
||||
function public.width()
|
||||
function public.get_width()
|
||||
return protected.frame.w
|
||||
end
|
||||
|
||||
-- get element height
|
||||
---@nodiscard
|
||||
---@return integer height
|
||||
function public.height()
|
||||
function public.get_height()
|
||||
return protected.frame.h
|
||||
end
|
||||
|
||||
@ -519,7 +516,7 @@ function element.new(args)
|
||||
|
||||
-- handle the mouse event then pass to children
|
||||
protected.handle_mouse(event_T)
|
||||
for _, child in pairs(self.children) do child.handle_mouse(event_T) end
|
||||
for _, child in pairs(protected.children) do child.handle_mouse(event_T) end
|
||||
end
|
||||
end
|
||||
|
||||
@ -545,27 +542,61 @@ function element.new(args)
|
||||
ps.subscribe(key, func)
|
||||
end
|
||||
|
||||
-- VISIBILITY --
|
||||
-- VISIBILITY & ANIMATIONS --
|
||||
|
||||
-- show the element
|
||||
function public.show()
|
||||
-- show the element and enables animations by default
|
||||
---@param animate? boolean true (default) to automatically resume animations
|
||||
function public.show(animate)
|
||||
protected.window.setVisible(true)
|
||||
protected.start_anim()
|
||||
for _, child in pairs(self.children) do child.show() end
|
||||
if animate ~= false then public.animate_all() end
|
||||
end
|
||||
|
||||
-- hide the element
|
||||
-- hide the element and disables animations<br>
|
||||
-- this alone does not cause an element to be fully hidden, it only prevents updates from being shown<br>
|
||||
---@see graphics_element.content_redraw
|
||||
function public.hide()
|
||||
protected.stop_anim()
|
||||
for _, child in pairs(self.children) do child.hide() end
|
||||
public.freeze_all() -- stop animations for efficiency/performance
|
||||
protected.window.setVisible(false)
|
||||
end
|
||||
|
||||
-- start/resume animation(s)
|
||||
function public.animate()
|
||||
protected.start_anim()
|
||||
end
|
||||
|
||||
-- start/resume animation(s) for this element and all its children<br>
|
||||
-- only animates if a window is visible
|
||||
function public.animate_all()
|
||||
if protected.window.isVisible() then
|
||||
public.animate()
|
||||
for _, child in pairs(protected.children) do child.animate_all() end
|
||||
end
|
||||
end
|
||||
|
||||
-- freeze animation(s)
|
||||
function public.freeze()
|
||||
protected.stop_anim()
|
||||
end
|
||||
|
||||
-- freeze animation(s) for this element and all its children
|
||||
function public.freeze_all()
|
||||
public.freeze()
|
||||
for _, child in pairs(protected.children) do child.freeze_all() end
|
||||
end
|
||||
|
||||
-- re-draw the element
|
||||
function public.redraw()
|
||||
protected.window.redraw()
|
||||
end
|
||||
|
||||
-- if a content window is set, clears it then re-draws all children
|
||||
function public.content_redraw()
|
||||
if protected.content_window ~= nil then
|
||||
protected.content_window.clear()
|
||||
for _, child in pairs(protected.children) do child.redraw() end
|
||||
end
|
||||
end
|
||||
|
||||
return protected
|
||||
end
|
||||
|
||||
|
@ -10,6 +10,7 @@ local element = require("graphics.element")
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new waiting animation element
|
||||
---@param args waiting_args
|
||||
@ -102,7 +103,7 @@ local function waiting(args)
|
||||
|
||||
e.start_anim()
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return waiting
|
||||
|
@ -9,6 +9,7 @@ local element = require("graphics.element")
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new color map
|
||||
---@param args colormap_args
|
||||
@ -27,7 +28,7 @@ local function colormap(args)
|
||||
e.window.setCursorPos(1, 1)
|
||||
e.window.blit(spaces, bkg, bkg)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return colormap
|
||||
|
@ -16,6 +16,7 @@ local element = require("graphics.element")
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new hazard button
|
||||
---@param args hazard_button_args
|
||||
@ -198,7 +199,7 @@ local function hazard_button(args)
|
||||
-- initial draw of border
|
||||
draw_border(args.accent)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return hazard_button
|
||||
|
@ -23,6 +23,7 @@ local element = require("graphics.element")
|
||||
---@field y? integer 1 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
|
||||
|
||||
-- new multi button (latch selection, exclusively one button at a time)
|
||||
---@param args multi_button_args
|
||||
@ -130,7 +131,7 @@ local function multi_button(args)
|
||||
-- initial draw
|
||||
draw()
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return multi_button
|
||||
|
@ -19,6 +19,7 @@ local CLICK_TYPE = core.events.CLICK_TYPE
|
||||
---@field y? integer 1 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
|
||||
|
||||
-- new push button
|
||||
---@param args push_button_args
|
||||
@ -120,7 +121,7 @@ local function push_button(args)
|
||||
-- initial draw
|
||||
draw()
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return push_button
|
||||
|
@ -15,6 +15,7 @@ local element = require("graphics.element")
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new radio button list (latch selection, exclusively one button at a time)
|
||||
---@param args radio_button_args
|
||||
@ -103,7 +104,7 @@ local function radio_button(args)
|
||||
-- initial draw
|
||||
draw()
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return radio_button
|
||||
|
@ -20,6 +20,7 @@ local CLICK_TYPE = core.events.CLICK_TYPE
|
||||
---@field y? integer 1 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
|
||||
|
||||
-- new sidebar tab selector
|
||||
---@param args sidebar_args
|
||||
@ -115,7 +116,7 @@ local function sidebar(args)
|
||||
-- initial draw
|
||||
draw(false)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return sidebar
|
||||
|
@ -18,6 +18,7 @@ local element = require("graphics.element")
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new spinbox control (minimum value is 0)
|
||||
---@param args spinbox_args
|
||||
@ -188,7 +189,7 @@ local function spinbox(args)
|
||||
e.value = 0
|
||||
set_digits()
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return spinbox
|
||||
|
@ -15,6 +15,7 @@ local element = require("graphics.element")
|
||||
---@field y? integer 1 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
|
||||
|
||||
-- new switch button (latch high/low)
|
||||
---@param args switch_button_args
|
||||
@ -86,7 +87,7 @@ local function switch_button(args)
|
||||
draw_state()
|
||||
end
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return switch_button
|
||||
|
@ -21,6 +21,7 @@ local element = require("graphics.element")
|
||||
---@field y? integer 1 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
|
||||
|
||||
-- new tab selector
|
||||
---@param args tabbar_args
|
||||
@ -124,7 +125,7 @@ local function tabbar(args)
|
||||
-- initial draw
|
||||
draw()
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return tabbar
|
||||
|
@ -4,19 +4,22 @@ local element = require("graphics.element")
|
||||
|
||||
---@class displaybox_args
|
||||
---@field window table
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 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
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new root display box
|
||||
---@nodiscard
|
||||
---@param args displaybox_args
|
||||
---@return graphics_element element, element_id id
|
||||
local function displaybox(args)
|
||||
-- create new graphics element base object
|
||||
return element.new(args).get()
|
||||
return element.new(args).complete()
|
||||
end
|
||||
|
||||
return displaybox
|
||||
|
@ -11,6 +11,7 @@ local element = require("graphics.element")
|
||||
---@field height? integer parent height if omitted
|
||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new div element
|
||||
---@nodiscard
|
||||
@ -18,7 +19,7 @@ local element = require("graphics.element")
|
||||
---@return graphics_element element, element_id id
|
||||
local function div(args)
|
||||
-- create new graphics element base object
|
||||
return element.new(args).get()
|
||||
return element.new(args).complete()
|
||||
end
|
||||
|
||||
return div
|
||||
|
@ -18,6 +18,7 @@ local flasher = require("graphics.flasher")
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new alarm indicator light
|
||||
---@nodiscard
|
||||
@ -108,7 +109,7 @@ local function alarm_indicator_light(args)
|
||||
e.on_update(1)
|
||||
e.window.write(args.label)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return alarm_indicator_light
|
||||
|
@ -163,7 +163,7 @@ local function core_map(args)
|
||||
-- initial draw
|
||||
e.on_update(0)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return core_map
|
||||
|
@ -17,6 +17,7 @@ local element = require("graphics.element")
|
||||
---@field y? integer 1 if omitted
|
||||
---@field width integer length
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new data indicator
|
||||
---@nodiscard
|
||||
@ -43,8 +44,9 @@ local function data(args)
|
||||
e.window.setCursorPos(1, 1)
|
||||
e.window.write(args.label)
|
||||
|
||||
local label_len = string.len(args.label)
|
||||
local data_start = 1
|
||||
local value_color = e.fg_bg.fgd
|
||||
local label_len = string.len(args.label)
|
||||
local data_start = 1
|
||||
local clear_width = args.width
|
||||
|
||||
if label_len > 0 then
|
||||
@ -64,7 +66,7 @@ local function data(args)
|
||||
-- write data
|
||||
local data_str = util.sprintf(args.format, value)
|
||||
e.window.setCursorPos(data_start, 1)
|
||||
e.window.setTextColor(e.fg_bg.fgd)
|
||||
e.window.setTextColor(value_color)
|
||||
if args.commas then
|
||||
e.window.write(util.comma_format(data_str))
|
||||
else
|
||||
@ -84,10 +86,17 @@ local function data(args)
|
||||
---@param val any new value
|
||||
function e.set_value(val) e.on_update(val) end
|
||||
|
||||
-- change the foreground color of the value, or all text if no label/unit colors provided
|
||||
---@param c color
|
||||
function e.recolor(c)
|
||||
value_color = c
|
||||
e.on_update(e.value)
|
||||
end
|
||||
|
||||
-- initial value draw
|
||||
e.on_update(args.value)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return data
|
||||
|
@ -15,6 +15,7 @@ local element = require("graphics.element")
|
||||
---@field height? integer parent height if omitted
|
||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new horizontal bar
|
||||
---@nodiscard
|
||||
@ -119,7 +120,7 @@ local function hbar(args)
|
||||
-- initialize to 0
|
||||
e.on_update(0)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return hbar
|
||||
|
@ -18,6 +18,7 @@ local element = require("graphics.element")
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new icon indicator
|
||||
---@nodiscard
|
||||
@ -68,7 +69,7 @@ local function icon(args)
|
||||
-- initial icon draw
|
||||
e.on_update(args.value or 1)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return icon
|
||||
|
@ -16,6 +16,7 @@ local flasher = require("graphics.flasher")
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new indicator LED
|
||||
---@nodiscard
|
||||
@ -94,7 +95,7 @@ local function indicator_led(args)
|
||||
e.window.write(args.label)
|
||||
end
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return indicator_led
|
||||
|
@ -18,6 +18,7 @@ local flasher = require("graphics.flasher")
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new dual LED indicator light
|
||||
---@nodiscard
|
||||
@ -108,7 +109,7 @@ local function indicator_led_pair(args)
|
||||
e.window.write(args.label)
|
||||
end
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return indicator_led_pair
|
||||
|
@ -11,6 +11,7 @@ local element = require("graphics.element")
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new RGB LED indicator light
|
||||
---@nodiscard
|
||||
@ -53,7 +54,7 @@ local function indicator_led_rgb(args)
|
||||
e.window.write(args.label)
|
||||
end
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return indicator_led_rgb
|
||||
|
@ -16,6 +16,7 @@ local flasher = require("graphics.flasher")
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new indicator light
|
||||
---@nodiscard
|
||||
@ -92,7 +93,7 @@ local function indicator_light(args)
|
||||
e.window.setCursorPos(3, 1)
|
||||
e.window.write(args.label)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return indicator_light
|
||||
|
@ -16,6 +16,7 @@ local element = require("graphics.element")
|
||||
---@field y? integer 1 if omitted
|
||||
---@field width integer length
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new power indicator
|
||||
---@nodiscard
|
||||
@ -79,7 +80,7 @@ local function power(args)
|
||||
-- initial value draw
|
||||
e.on_update(args.value)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return power
|
||||
|
@ -17,6 +17,7 @@ local element = require("graphics.element")
|
||||
---@field y? integer 1 if omitted
|
||||
---@field width integer length
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new radiation indicator
|
||||
---@nodiscard
|
||||
@ -84,7 +85,7 @@ local function rad(args)
|
||||
-- initial value draw
|
||||
e.on_update(types.new_zero_radiation_reading())
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return rad
|
||||
|
@ -18,6 +18,7 @@ local element = require("graphics.element")
|
||||
---@field y? integer 1 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
|
||||
|
||||
-- new state indicator
|
||||
---@nodiscard
|
||||
@ -74,7 +75,7 @@ local function state_indicator(args)
|
||||
-- initial draw
|
||||
e.on_update(args.value or 1)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return state_indicator
|
||||
|
@ -18,6 +18,7 @@ local flasher = require("graphics.flasher")
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new tri-state indicator light
|
||||
---@nodiscard
|
||||
@ -105,7 +106,7 @@ local function tristate_indicator_light(args)
|
||||
e.on_update(1)
|
||||
e.window.write(args.label)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return tristate_indicator_light
|
||||
|
@ -13,6 +13,7 @@ local element = require("graphics.element")
|
||||
---@field height? integer parent height if omitted
|
||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new vertical bar
|
||||
---@nodiscard
|
||||
@ -99,7 +100,7 @@ local function vbar(args)
|
||||
---@param val number 0.0 to 1.0
|
||||
function e.set_value(val) e.on_update(val) end
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return vbar
|
||||
|
283
graphics/elements/listbox.lua
Normal file
283
graphics/elements/listbox.lua
Normal file
@ -0,0 +1,283 @@
|
||||
-- Scroll-able List Box Display Graphics Element
|
||||
|
||||
local tcd = require("scada-common.tcallbackdsp")
|
||||
|
||||
local core = require("graphics.core")
|
||||
local element = require("graphics.element")
|
||||
|
||||
local CLICK_TYPE = core.events.CLICK_TYPE
|
||||
|
||||
---@class listbox_args
|
||||
---@field scroll_height integer height of internal scrolling container (must fit all elements vertically tiled)
|
||||
---@field item_pad? integer spacing (lines) between items in the list (default 0)
|
||||
---@field nav_fg_bg? cpair foreground/background colors for scroll arrows and bar area
|
||||
---@field nav_active? cpair active colors for bar held down or arrow held down
|
||||
---@field parent graphics_element
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 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
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
---@class listbox_item
|
||||
---@field id string|integer element ID
|
||||
---@field e graphics_element element
|
||||
---@field y integer y position
|
||||
---@field h integer element height
|
||||
|
||||
-- new listbox element
|
||||
---@nodiscard
|
||||
---@param args listbox_args
|
||||
---@return graphics_element element, element_id id
|
||||
local function listbox(args)
|
||||
-- create new graphics element base object
|
||||
local e = element.new(args)
|
||||
|
||||
-- create content window for child elements
|
||||
local scroll_frame = window.create(e.window, 1, 1, e.frame.w - 1, args.scroll_height, false)
|
||||
e.content_window = scroll_frame
|
||||
|
||||
-- item list and scroll management
|
||||
local list = {}
|
||||
local item_pad = args.item_pad or 0
|
||||
local scroll_offset = 0
|
||||
local content_height = 0
|
||||
local max_down_scroll = 0
|
||||
-- bar control/tracking variables
|
||||
local max_bar_height = e.frame.h - 2
|
||||
local bar_height = 0 -- full height of bar
|
||||
local bar_bounds = { 0, 0 } -- top and bottom of bar
|
||||
local bar_is_scaled = false -- if the scrollbar doesn't have a 1:1 ratio with lines
|
||||
local holding_bar = false -- bar is being held by mouse
|
||||
local bar_grip_pos = 0 -- where the bar was gripped by mouse down
|
||||
local mouse_last_y = 0 -- last reported y coordinate of drag
|
||||
|
||||
-- draw scroll bar arrows, optionally showing one of them as pressed
|
||||
---@param pressed_arrow? 1|0|-1 arrow to show as pressed (1 = scroll up, 0 = neither, -1 = scroll down)
|
||||
local function draw_arrows(pressed_arrow)
|
||||
local nav_fg_bg = args.nav_fg_bg or e.fg_bg
|
||||
local active_fg_bg = args.nav_active or nav_fg_bg
|
||||
|
||||
-- draw up/down arrows
|
||||
if pressed_arrow == 1 then
|
||||
e.window.setTextColor(active_fg_bg.fgd)
|
||||
e.window.setBackgroundColor(active_fg_bg.bkg)
|
||||
e.window.setCursorPos(e.frame.w, 1)
|
||||
e.window.write("\x1e")
|
||||
e.window.setTextColor(nav_fg_bg.fgd)
|
||||
e.window.setBackgroundColor(nav_fg_bg.bkg)
|
||||
e.window.setCursorPos(e.frame.w, e.frame.h)
|
||||
e.window.write("\x1f")
|
||||
elseif pressed_arrow == -1 then
|
||||
e.window.setTextColor(nav_fg_bg.fgd)
|
||||
e.window.setBackgroundColor(nav_fg_bg.bkg)
|
||||
e.window.setCursorPos(e.frame.w, 1)
|
||||
e.window.write("\x1e")
|
||||
e.window.setTextColor(active_fg_bg.fgd)
|
||||
e.window.setBackgroundColor(active_fg_bg.bkg)
|
||||
e.window.setCursorPos(e.frame.w, e.frame.h)
|
||||
e.window.write("\x1f")
|
||||
else
|
||||
e.window.setTextColor(nav_fg_bg.fgd)
|
||||
e.window.setBackgroundColor(nav_fg_bg.bkg)
|
||||
e.window.setCursorPos(e.frame.w, 1)
|
||||
e.window.write("\x1e")
|
||||
e.window.setCursorPos(e.frame.w, e.frame.h)
|
||||
e.window.write("\x1f")
|
||||
end
|
||||
|
||||
e.window.setTextColor(e.fg_bg.fgd)
|
||||
e.window.setBackgroundColor(e.fg_bg.bkg)
|
||||
end
|
||||
|
||||
-- render the scroll bar and re-cacluate height & bounds
|
||||
local function draw_bar()
|
||||
local offset = 2 + math.abs(scroll_offset)
|
||||
|
||||
bar_height = math.min(max_bar_height + max_down_scroll, max_bar_height)
|
||||
|
||||
if bar_height < 1 then
|
||||
bar_is_scaled = true
|
||||
-- can't do a 1:1 ratio
|
||||
-- use minimum size bar with scaled offset
|
||||
local scroll_progress = scroll_offset / max_down_scroll
|
||||
offset = 2 + math.floor(scroll_progress * (max_bar_height - 1))
|
||||
bar_height = 1
|
||||
else
|
||||
bar_is_scaled = false
|
||||
end
|
||||
|
||||
bar_bounds = { offset, (bar_height + offset) - 1 }
|
||||
|
||||
for i = 2, e.frame.h - 1 do
|
||||
if (i >= offset and i < (bar_height + offset)) and (bar_height ~= max_bar_height) then
|
||||
if args.nav_fg_bg ~= nil then
|
||||
e.window.setBackgroundColor(args.nav_fg_bg.fgd)
|
||||
else
|
||||
e.window.setBackgroundColor(e.fg_bg.fgd)
|
||||
end
|
||||
else
|
||||
if args.nav_fg_bg ~= nil then
|
||||
e.window.setBackgroundColor(args.nav_fg_bg.bkg)
|
||||
else
|
||||
e.window.setBackgroundColor(e.fg_bg.bkg)
|
||||
end
|
||||
end
|
||||
|
||||
e.window.setCursorPos(e.frame.w, i)
|
||||
e.window.write(" ")
|
||||
end
|
||||
|
||||
e.window.setBackgroundColor(e.fg_bg.bkg)
|
||||
end
|
||||
|
||||
-- update item y positions and move elements
|
||||
local function update_positions()
|
||||
local next_y = 1
|
||||
|
||||
scroll_frame.setVisible(false)
|
||||
scroll_frame.setBackgroundColor(e.fg_bg.bkg)
|
||||
scroll_frame.setTextColor(e.fg_bg.fgd)
|
||||
scroll_frame.clear()
|
||||
|
||||
for i = 1, #list do
|
||||
local item = list[i] ---@type listbox_item
|
||||
item.y = next_y
|
||||
next_y = next_y + item.h + item_pad
|
||||
item.e.reposition(1, item.y)
|
||||
item.e.show()
|
||||
end
|
||||
|
||||
content_height = next_y
|
||||
max_down_scroll = math.min(-1 * (content_height - (e.frame.h + 1 + item_pad)), 0)
|
||||
if scroll_offset < max_down_scroll then scroll_offset = max_down_scroll end
|
||||
|
||||
scroll_frame.reposition(1, 1 + scroll_offset)
|
||||
scroll_frame.setVisible(true)
|
||||
|
||||
draw_bar()
|
||||
end
|
||||
|
||||
-- determine where to scroll to based on a scrollbar being dragged without a 1:1 relationship
|
||||
---@param direction -1|1 negative 1 to scroll up by one, positive 1 to scroll down by one
|
||||
local function scaled_bar_scroll(direction)
|
||||
local scroll_progress = scroll_offset / max_down_scroll
|
||||
local bar_position = math.floor(scroll_progress * (max_bar_height - 1))
|
||||
|
||||
-- check what moving the scroll bar up or down would mean for the scroll progress
|
||||
scroll_progress = (bar_position + direction) / (max_bar_height - 1)
|
||||
|
||||
return math.max(math.floor(scroll_progress * max_down_scroll), max_down_scroll)
|
||||
end
|
||||
|
||||
-- scroll down the list
|
||||
local function scroll_down(scaled)
|
||||
if scroll_offset > max_down_scroll then
|
||||
if scaled then
|
||||
scroll_offset = scaled_bar_scroll(1)
|
||||
else
|
||||
scroll_offset = scroll_offset - 1
|
||||
end
|
||||
|
||||
update_positions()
|
||||
end
|
||||
end
|
||||
|
||||
-- scroll up the list
|
||||
local function scroll_up(scaled)
|
||||
if scroll_offset < 0 then
|
||||
if scaled then
|
||||
scroll_offset = scaled_bar_scroll(-1)
|
||||
else
|
||||
scroll_offset = scroll_offset + 1
|
||||
end
|
||||
|
||||
update_positions()
|
||||
end
|
||||
end
|
||||
|
||||
-- handle a child element having been added to the list
|
||||
---@param id element_id element identifier
|
||||
---@param child graphics_element child element
|
||||
function e.on_added(id, child)
|
||||
table.insert(list, { id = id, e = child, y = 0, h = child.get_height() })
|
||||
update_positions()
|
||||
end
|
||||
|
||||
-- handle a child element having been removed from the list
|
||||
---@param id element_id element identifier
|
||||
function e.on_removed(id)
|
||||
for idx, elem in ipairs(list) do
|
||||
if elem.id == id then
|
||||
table.remove(list, idx)
|
||||
update_positions()
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- handle mouse interaction
|
||||
---@param event mouse_interaction mouse event
|
||||
function e.handle_mouse(event)
|
||||
if e.enabled then
|
||||
if event.type == CLICK_TYPE.TAP then
|
||||
if event.current.x == e.frame.w then
|
||||
if event.current.y == 1 or event.current.y < bar_bounds[1] then
|
||||
draw_arrows(1)
|
||||
scroll_up()
|
||||
if args.nav_active ~= nil then tcd.dispatch(0.25, function () draw_arrows(0) end) end
|
||||
elseif event.current.y == e.frame.h or event.current.y > bar_bounds[2] then
|
||||
draw_arrows(-1)
|
||||
scroll_down()
|
||||
if args.nav_active ~= nil then tcd.dispatch(0.25, function () draw_arrows(0) end) end
|
||||
end
|
||||
end
|
||||
elseif event.type == CLICK_TYPE.DOWN then
|
||||
if event.current.x == e.frame.w then
|
||||
if event.current.y == 1 or event.current.y < bar_bounds[1] then
|
||||
draw_arrows(1)
|
||||
scroll_up()
|
||||
elseif event.current.y == e.frame.h or event.current.y > bar_bounds[2] then
|
||||
draw_arrows(-1)
|
||||
scroll_down()
|
||||
else
|
||||
-- clicked on bar
|
||||
holding_bar = true
|
||||
bar_grip_pos = event.current.y - bar_bounds[1]
|
||||
mouse_last_y = event.current.y
|
||||
end
|
||||
end
|
||||
elseif event.type == CLICK_TYPE.UP then
|
||||
holding_bar = false
|
||||
draw_arrows(0)
|
||||
elseif event.type == CLICK_TYPE.DRAG then
|
||||
if holding_bar then
|
||||
-- if mouse is within vertical frame, including the grip point
|
||||
if event.current.y > (1 + bar_grip_pos) and event.current.y <= ((e.frame.h - bar_height) + bar_grip_pos) then
|
||||
if event.current.y < mouse_last_y then
|
||||
scroll_up(bar_is_scaled)
|
||||
elseif event.current.y > mouse_last_y then
|
||||
scroll_down(bar_is_scaled)
|
||||
end
|
||||
|
||||
mouse_last_y = event.current.y
|
||||
end
|
||||
end
|
||||
elseif event.type == CLICK_TYPE.SCROLL_DOWN then
|
||||
scroll_down()
|
||||
elseif event.type == CLICK_TYPE.SCROLL_UP then
|
||||
scroll_up()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
draw_arrows(0)
|
||||
draw_bar()
|
||||
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return listbox
|
@ -12,6 +12,7 @@ local element = require("graphics.element")
|
||||
---@field height? integer parent height if omitted
|
||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new multipane element
|
||||
---@nodiscard
|
||||
@ -36,7 +37,7 @@ local function multipane(args)
|
||||
|
||||
e.set_value(1)
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return multipane
|
||||
|
@ -12,6 +12,7 @@ local element = require("graphics.element")
|
||||
---@field id? string element id
|
||||
---@field x? integer 1 if omitted
|
||||
---@field y? integer 1 if omitted
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new pipe network
|
||||
---@param args pipenet_args
|
||||
@ -141,7 +142,7 @@ local function pipenet(args)
|
||||
|
||||
end
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return pipenet
|
||||
|
@ -16,6 +16,7 @@ local element = require("graphics.element")
|
||||
---@field height? integer parent height if omitted
|
||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new rectangle
|
||||
---@param args rectangle_args
|
||||
@ -30,27 +31,35 @@ local function rectangle(args)
|
||||
end
|
||||
|
||||
-- offset children
|
||||
local offset_x = 0
|
||||
local offset_y = 0
|
||||
if args.border ~= nil then
|
||||
args.offset_x = args.border.width
|
||||
args.offset_y = args.border.width
|
||||
offset_x = args.border.width
|
||||
offset_y = args.border.width
|
||||
|
||||
-- slightly different y offset if the border is set to even
|
||||
if args.border.even then
|
||||
local width_x2 = (2 * args.border.width)
|
||||
args.offset_y = math.floor(width_x2 / 3) + util.trinary(width_x2 % 3 > 0, 1, 0)
|
||||
offset_y = math.floor(width_x2 / 3) + util.trinary(width_x2 % 3 > 0, 1, 0)
|
||||
end
|
||||
end
|
||||
|
||||
-- create new graphics element base object
|
||||
local e = element.new(args)
|
||||
|
||||
-- create content window for child elements
|
||||
e.content_window = window.create(e.window, 1 + offset_x, 1 + offset_y, e.frame.w - (2 * offset_x), e.frame.h - (2 * offset_y))
|
||||
e.content_window.setBackgroundColor(e.fg_bg.bkg)
|
||||
e.content_window.setTextColor(e.fg_bg.fgd)
|
||||
e.content_window.clear()
|
||||
|
||||
-- draw bordered box if requested
|
||||
-- element constructor will have drawn basic colored rectangle regardless
|
||||
if args.border ~= nil then
|
||||
e.window.setCursorPos(1, 1)
|
||||
|
||||
local border_width = args.offset_x
|
||||
local border_height = args.offset_y
|
||||
local border_width = offset_x
|
||||
local border_height = offset_y
|
||||
local border_blit = colors.toBlit(args.border.color)
|
||||
local width_x2 = border_width * 2
|
||||
local inner_width = e.frame.w - width_x2
|
||||
@ -177,7 +186,7 @@ local function rectangle(args)
|
||||
end
|
||||
end
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return rectangle
|
||||
|
@ -18,6 +18,7 @@ local TEXT_ALIGN = core.TEXT_ALIGN
|
||||
---@field height? integer parent height if omitted
|
||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new text box
|
||||
---@param args textbox_args
|
||||
@ -64,7 +65,7 @@ local function textbox(args)
|
||||
display_text(val)
|
||||
end
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return textbox
|
||||
|
@ -16,6 +16,7 @@ local element = require("graphics.element")
|
||||
---@field height? integer parent height if omitted
|
||||
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||
---@field fg_bg? cpair foreground/background colors
|
||||
---@field hidden? boolean true to hide on initial draw
|
||||
|
||||
-- new tiling box
|
||||
---@param args tiling_args
|
||||
@ -81,7 +82,7 @@ local function tiling(args)
|
||||
if inner_width % 2 == 0 then alternator = not alternator end
|
||||
end
|
||||
|
||||
return e.get()
|
||||
return e.complete()
|
||||
end
|
||||
|
||||
return tiling
|
||||
|
1
install_manifest.json
Normal file
1
install_manifest.json
Normal file
File diff suppressed because one or more lines are too long
@ -17,7 +17,7 @@ local coreio = require("pocket.coreio")
|
||||
local pocket = require("pocket.pocket")
|
||||
local renderer = require("pocket.renderer")
|
||||
|
||||
local POCKET_VERSION = "alpha-v0.3.2"
|
||||
local POCKET_VERSION = "alpha-v0.3.6"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@ -120,54 +120,54 @@ local function main()
|
||||
conn_wd.sv.feed()
|
||||
conn_wd.api.feed()
|
||||
log.debug("startup> conn watchdog started")
|
||||
end
|
||||
|
||||
-- main event loop
|
||||
while ui_ok do
|
||||
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||
-- main event loop
|
||||
while true do
|
||||
local event, param1, param2, param3, param4, param5 = util.pull_event()
|
||||
|
||||
-- handle event
|
||||
if event == "timer" then
|
||||
if loop_clock.is_clock(param1) then
|
||||
-- main loop tick
|
||||
-- handle event
|
||||
if event == "timer" then
|
||||
if loop_clock.is_clock(param1) then
|
||||
-- main loop tick
|
||||
|
||||
-- relink if necessary
|
||||
pocket_comms.link_update()
|
||||
-- relink if necessary
|
||||
pocket_comms.link_update()
|
||||
|
||||
loop_clock.start()
|
||||
elseif conn_wd.sv.is_timer(param1) then
|
||||
-- supervisor watchdog timeout
|
||||
log.info("supervisor server timeout")
|
||||
pocket_comms.close_sv()
|
||||
elseif conn_wd.api.is_timer(param1) then
|
||||
-- coordinator watchdog timeout
|
||||
log.info("coordinator api server timeout")
|
||||
pocket_comms.close_api()
|
||||
else
|
||||
-- a non-clock/main watchdog timer event
|
||||
-- notify timer callback dispatcher
|
||||
tcallbackdsp.handle(param1)
|
||||
loop_clock.start()
|
||||
elseif conn_wd.sv.is_timer(param1) then
|
||||
-- supervisor watchdog timeout
|
||||
log.info("supervisor server timeout")
|
||||
pocket_comms.close_sv()
|
||||
elseif conn_wd.api.is_timer(param1) then
|
||||
-- coordinator watchdog timeout
|
||||
log.info("coordinator api server timeout")
|
||||
pocket_comms.close_api()
|
||||
else
|
||||
-- a non-clock/main watchdog timer event
|
||||
-- notify timer callback dispatcher
|
||||
tcallbackdsp.handle(param1)
|
||||
end
|
||||
elseif event == "modem_message" then
|
||||
-- got a packet
|
||||
local packet = pocket_comms.parse_packet(param1, param2, param3, param4, param5)
|
||||
pocket_comms.handle_packet(packet)
|
||||
elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" then
|
||||
-- handle a monitor touch 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 server connections...")
|
||||
pocket_comms.close()
|
||||
log.info("connections closed")
|
||||
break
|
||||
end
|
||||
elseif event == "modem_message" then
|
||||
-- got a packet
|
||||
local packet = pocket_comms.parse_packet(param1, param2, param3, param4, param5)
|
||||
pocket_comms.handle_packet(packet)
|
||||
elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" then
|
||||
-- handle a monitor touch 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 server connections...")
|
||||
pocket_comms.close()
|
||||
log.info("connections closed")
|
||||
break
|
||||
end
|
||||
renderer.close_ui()
|
||||
end
|
||||
|
||||
renderer.close_ui()
|
||||
|
||||
println_ts("exited")
|
||||
log.info("exited")
|
||||
end
|
||||
|
@ -25,7 +25,7 @@ local function init(parent, y, is_api)
|
||||
-- bounding box div
|
||||
local box = Div{parent=root,x=1,y=y,height=5}
|
||||
|
||||
local waiting_x = math.floor(parent.width() / 2) - 1
|
||||
local waiting_x = math.floor(parent.get_width() / 2) - 1
|
||||
|
||||
if is_api then
|
||||
WaitingAnim{parent=box,x=waiting_x,y=1,fg_bg=cpair(colors.blue,style.root.bkg)}
|
||||
|
@ -8,11 +8,11 @@ local style = require("pocket.ui.style")
|
||||
|
||||
local conn_waiting = require("pocket.ui.components.conn_waiting")
|
||||
|
||||
local home_page = require("pocket.ui.components.home_page")
|
||||
local unit_page = require("pocket.ui.components.unit_page")
|
||||
local reactor_page = require("pocket.ui.components.reactor_page")
|
||||
local boiler_page = require("pocket.ui.components.boiler_page")
|
||||
local turbine_page = require("pocket.ui.components.turbine_page")
|
||||
local home_page = require("pocket.ui.pages.home_page")
|
||||
local unit_page = require("pocket.ui.pages.unit_page")
|
||||
local reactor_page = require("pocket.ui.pages.reactor_page")
|
||||
local boiler_page = require("pocket.ui.pages.boiler_page")
|
||||
local turbine_page = require("pocket.ui.pages.turbine_page")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
|
@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc")
|
||||
local renderer = require("reactor-plc.renderer")
|
||||
local threads = require("reactor-plc.threads")
|
||||
|
||||
local R_PLC_VERSION = "v1.3.2"
|
||||
local R_PLC_VERSION = "v1.3.6"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
|
@ -28,7 +28,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.2.2"
|
||||
local RTU_VERSION = "v1.2.6"
|
||||
|
||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
|
||||
@ -457,9 +457,9 @@ local function main()
|
||||
if not rtu_state.fp_ok then
|
||||
renderer.close_ui()
|
||||
println_ts(util.c("UI error: ", message))
|
||||
println("init> running without front panel")
|
||||
println("startup> running without front panel")
|
||||
log.error(util.c("GUI crashed with error ", message))
|
||||
log.info("init> running in headless mode without front panel")
|
||||
log.info("startup> running in headless mode without front panel")
|
||||
end
|
||||
|
||||
-- start connection watchdog
|
||||
|
174
supervisor/databus.lua
Normal file
174
supervisor/databus.lua
Normal file
@ -0,0 +1,174 @@
|
||||
--
|
||||
-- Data Bus - Central Communication Linking for Supervisor Front Panel
|
||||
--
|
||||
|
||||
local psil = require("scada-common.psil")
|
||||
|
||||
local pgi = require("supervisor.panel.pgi")
|
||||
|
||||
local databus = {}
|
||||
|
||||
-- databus PSIL
|
||||
databus.ps = psil.create()
|
||||
|
||||
-- call to toggle heartbeat signal
|
||||
function databus.heartbeat() databus.ps.toggle("heartbeat") end
|
||||
|
||||
-- transmit firmware versions across the bus
|
||||
---@param sv_v string supervisor version
|
||||
---@param comms_v string comms version
|
||||
function databus.tx_versions(sv_v, comms_v)
|
||||
databus.ps.publish("version", sv_v)
|
||||
databus.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)
|
||||
databus.ps.publish("has_modem", has_modem)
|
||||
end
|
||||
|
||||
-- transmit PLC firmware version and session connection state
|
||||
---@param reactor_id integer reactor unit ID
|
||||
---@param fw string firmware version
|
||||
---@param channel integer PLC remote port
|
||||
function databus.tx_plc_connected(reactor_id, fw, channel)
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_fw", fw)
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_conn", true)
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_chan", tostring(channel))
|
||||
end
|
||||
|
||||
-- transmit PLC disconnected
|
||||
---@param reactor_id integer reactor unit ID
|
||||
function databus.tx_plc_disconnected(reactor_id)
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_fw", " ------- ")
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_conn", false)
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_chan", " --- ")
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt", 0)
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.lightGray)
|
||||
end
|
||||
|
||||
-- transmit PLC session RTT
|
||||
---@param reactor_id integer reactor unit ID
|
||||
---@param rtt integer round trip time
|
||||
function databus.tx_plc_rtt(reactor_id, rtt)
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt", rtt)
|
||||
|
||||
if rtt > 700 then
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.red)
|
||||
elseif rtt > 300 then
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.yellow_hc)
|
||||
else
|
||||
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.green)
|
||||
end
|
||||
end
|
||||
|
||||
-- transmit RTU firmware version and session connection state
|
||||
---@param session_id integer RTU session
|
||||
---@param fw string firmware version
|
||||
---@param channel integer RTU remote port
|
||||
function databus.tx_rtu_connected(session_id, fw, channel)
|
||||
databus.ps.publish("rtu_" .. session_id .. "_fw", fw)
|
||||
databus.ps.publish("rtu_" .. session_id .. "_chan", tostring(channel))
|
||||
pgi.create_rtu_entry(session_id)
|
||||
end
|
||||
|
||||
-- transmit RTU disconnected
|
||||
---@param session_id integer RTU session
|
||||
function databus.tx_rtu_disconnected(session_id)
|
||||
pgi.delete_rtu_entry(session_id)
|
||||
end
|
||||
|
||||
-- transmit RTU session RTT
|
||||
---@param session_id integer RTU session
|
||||
---@param rtt integer round trip time
|
||||
function databus.tx_rtu_rtt(session_id, rtt)
|
||||
databus.ps.publish("rtu_" .. session_id .. "_rtt", rtt)
|
||||
|
||||
if rtt > 700 then
|
||||
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.red)
|
||||
elseif rtt > 300 then
|
||||
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.yellow_hc)
|
||||
else
|
||||
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.green)
|
||||
end
|
||||
end
|
||||
|
||||
-- transmit RTU session unit count
|
||||
---@param session_id integer RTU session
|
||||
---@param units integer unit count
|
||||
function databus.tx_rtu_units(session_id, units)
|
||||
databus.ps.publish("rtu_" .. session_id .. "_units", units)
|
||||
end
|
||||
|
||||
-- transmit coordinator firmware version and session connection state
|
||||
---@param fw string firmware version
|
||||
---@param channel integer coordinator remote port
|
||||
function databus.tx_crd_connected(fw, channel)
|
||||
databus.ps.publish("crd_fw", fw)
|
||||
databus.ps.publish("crd_conn", true)
|
||||
databus.ps.publish("crd_chan", tostring(channel))
|
||||
end
|
||||
|
||||
-- transmit coordinator disconnected
|
||||
function databus.tx_crd_disconnected()
|
||||
databus.ps.publish("crd_fw", " ------- ")
|
||||
databus.ps.publish("crd_conn", false)
|
||||
databus.ps.publish("crd_chan", "---")
|
||||
databus.ps.publish("crd_rtt", 0)
|
||||
databus.ps.publish("crd_rtt_color", colors.lightGray)
|
||||
end
|
||||
|
||||
-- transmit coordinator session RTT
|
||||
---@param rtt integer round trip time
|
||||
function databus.tx_crd_rtt(rtt)
|
||||
databus.ps.publish("crd_rtt", rtt)
|
||||
|
||||
if rtt > 700 then
|
||||
databus.ps.publish("crd_rtt_color", colors.red)
|
||||
elseif rtt > 300 then
|
||||
databus.ps.publish("crd_rtt_color", colors.yellow_hc)
|
||||
else
|
||||
databus.ps.publish("crd_rtt_color", colors.green)
|
||||
end
|
||||
end
|
||||
|
||||
-- transmit PKT firmware version and PDG session connection state
|
||||
---@param session_id integer PDG session
|
||||
---@param fw string firmware version
|
||||
---@param channel integer PDG remote port
|
||||
function databus.tx_pdg_connected(session_id, fw, channel)
|
||||
databus.ps.publish("pdg_" .. session_id .. "_fw", fw)
|
||||
databus.ps.publish("pdg_" .. session_id .. "_chan", tostring(channel))
|
||||
pgi.create_pdg_entry(session_id)
|
||||
end
|
||||
|
||||
-- transmit PDG session disconnected
|
||||
---@param session_id integer PDG session
|
||||
function databus.tx_pdg_disconnected(session_id)
|
||||
pgi.delete_pdg_entry(session_id)
|
||||
end
|
||||
|
||||
-- transmit PDG session RTT
|
||||
---@param session_id integer PDG session
|
||||
---@param rtt integer round trip time
|
||||
function databus.tx_pdg_rtt(session_id, rtt)
|
||||
databus.ps.publish("pdg_" .. session_id .. "_rtt", rtt)
|
||||
|
||||
if rtt > 700 then
|
||||
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.red)
|
||||
elseif rtt > 300 then
|
||||
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.yellow_hc)
|
||||
else
|
||||
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.green)
|
||||
end
|
||||
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)
|
||||
databus.ps.subscribe(field, func)
|
||||
end
|
||||
|
||||
return databus
|
48
supervisor/panel/components/pdg_entry.lua
Normal file
48
supervisor/panel/components/pdg_entry.lua
Normal file
@ -0,0 +1,48 @@
|
||||
--
|
||||
-- Pocket Diagnostics Connection Entry
|
||||
--
|
||||
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local databus = require("supervisor.databus")
|
||||
|
||||
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 diagnostics list entry
|
||||
---@param parent graphics_element parent
|
||||
---@param id integer PDG session ID
|
||||
local function init(parent, id)
|
||||
-- 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 = "pdg_" .. id .. "_"
|
||||
|
||||
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
local pdg_chan = TextBox{parent=entry,x=1,y=2,text=" :00000",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)}
|
||||
pdg_chan.register(databus.ps, ps_prefix .. "chan", function (channel) pdg_chan.set_value(util.sprintf(" :%05d", channel)) end)
|
||||
|
||||
TextBox{parent=entry,x=10,y=2,text="FW:",width=3,height=1}
|
||||
local pdg_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
pdg_fw_v.register(databus.ps, ps_prefix .. "fw", pdg_fw_v.set_value)
|
||||
|
||||
TextBox{parent=entry,x=35,y=2,text="RTT:",width=4,height=1}
|
||||
local pdg_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)}
|
||||
pdg_rtt.register(databus.ps, ps_prefix .. "rtt", pdg_rtt.update)
|
||||
pdg_rtt.register(databus.ps, ps_prefix .. "rtt_color", pdg_rtt.recolor)
|
||||
|
||||
return root
|
||||
end
|
||||
|
||||
return init
|
52
supervisor/panel/components/rtu_entry.lua
Normal file
52
supervisor/panel/components/rtu_entry.lua
Normal file
@ -0,0 +1,52 @@
|
||||
--
|
||||
-- RTU Connection Entry
|
||||
--
|
||||
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local databus = require("supervisor.databus")
|
||||
|
||||
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 an RTU list entry
|
||||
---@param parent graphics_element parent
|
||||
---@param id integer RTU session ID
|
||||
local function init(parent, id)
|
||||
-- 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 = "rtu_" .. id .. "_"
|
||||
|
||||
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
local rtu_chan = TextBox{parent=entry,x=1,y=2,text=" :00000",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)}
|
||||
rtu_chan.register(databus.ps, ps_prefix .. "chan", function (channel) rtu_chan.set_value(util.sprintf(" :%05d", channel)) end)
|
||||
|
||||
TextBox{parent=entry,x=10,y=2,text="UNITS:",width=7,height=1}
|
||||
local unit_count = DataIndicator{parent=entry,x=17,y=2,label="",unit="",format="%2d",value=0,width=2,fg_bg=cpair(colors.gray,colors.white)}
|
||||
unit_count.register(databus.ps, ps_prefix .. "units", unit_count.set_value)
|
||||
|
||||
TextBox{parent=entry,x=21,y=2,text="FW:",width=3,height=1}
|
||||
local rtu_fw_v = TextBox{parent=entry,x=25,y=2,text=" ------- ",width=9,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
rtu_fw_v.register(databus.ps, ps_prefix .. "fw", rtu_fw_v.set_value)
|
||||
|
||||
TextBox{parent=entry,x=36,y=2,text="RTT:",width=4,height=1}
|
||||
local rtu_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)}
|
||||
rtu_rtt.register(databus.ps, ps_prefix .. "rtt", rtu_rtt.update)
|
||||
rtu_rtt.register(databus.ps, ps_prefix .. "rtt_color", rtu_rtt.recolor)
|
||||
|
||||
return root
|
||||
end
|
||||
|
||||
return init
|
160
supervisor/panel/front_panel.lua
Normal file
160
supervisor/panel/front_panel.lua
Normal file
@ -0,0 +1,160 @@
|
||||
--
|
||||
-- Main SCADA Coordinator GUI
|
||||
--
|
||||
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local config = require("supervisor.config")
|
||||
local databus = require("supervisor.databus")
|
||||
|
||||
local pgi = require("supervisor.panel.pgi")
|
||||
local style = require("supervisor.panel.style")
|
||||
|
||||
local pdg_entry = require("supervisor.panel.components.pdg_entry")
|
||||
local rtu_entry = require("supervisor.panel.components.rtu_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 DataIndicator = require("graphics.elements.indicators.data")
|
||||
|
||||
local TEXT_ALIGN = core.TEXT_ALIGN
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
-- 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="STATUS",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()
|
||||
|
||||
heartbeat.register(databus.ps, "heartbeat", heartbeat.update)
|
||||
|
||||
local modem = LED{parent=system,label="MODEM",colors=cpair(colors.green,colors.green_off)}
|
||||
system.line_break()
|
||||
|
||||
modem.register(databus.ps, "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}
|
||||
|
||||
fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end)
|
||||
comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
|
||||
|
||||
--
|
||||
-- page handling
|
||||
--
|
||||
|
||||
-- plc page
|
||||
|
||||
local plc_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||
local plc_list = Div{parent=plc_page,x=2,y=2,width=49}
|
||||
|
||||
for i = 1, config.NUM_REACTORS do
|
||||
local ps_prefix = "plc_" .. i .. "_"
|
||||
local plc_entry = Div{parent=plc_list,height=3,fg_bg=cpair(colors.black,colors.white)}
|
||||
|
||||
TextBox{parent=plc_entry,x=1,y=1,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
TextBox{parent=plc_entry,x=1,y=2,text="UNIT "..i,alignment=TEXT_ALIGN.CENTER,width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
TextBox{parent=plc_entry,x=1,y=3,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
|
||||
|
||||
local conn = LED{parent=plc_entry,x=10,y=2,label="CONN",colors=cpair(colors.green,colors.green_off)}
|
||||
conn.register(databus.ps, ps_prefix .. "conn", conn.update)
|
||||
|
||||
local plc_chan = TextBox{parent=plc_entry,x=17,y=2,text=" --- ",width=5,height=1,fg_bg=cpair(colors.gray,colors.white)}
|
||||
plc_chan.register(databus.ps, ps_prefix .. "chan", plc_chan.set_value)
|
||||
|
||||
TextBox{parent=plc_entry,x=23,y=2,text="FW:",width=3,height=1}
|
||||
local plc_fw_v = TextBox{parent=plc_entry,x=27,y=2,text=" ------- ",width=9,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
plc_fw_v.register(databus.ps, ps_prefix .. "fw", plc_fw_v.set_value)
|
||||
|
||||
TextBox{parent=plc_entry,x=37,y=2,text="RTT:",width=4,height=1}
|
||||
local plc_rtt = DataIndicator{parent=plc_entry,x=42,y=2,label="",unit="",format="%4d",value=0,width=4,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
TextBox{parent=plc_entry,x=47,y=2,text="ms",width=4,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
plc_rtt.register(databus.ps, ps_prefix .. "rtt", plc_rtt.update)
|
||||
plc_rtt.register(databus.ps, ps_prefix .. "rtt_color", plc_rtt.recolor)
|
||||
|
||||
plc_list.line_break()
|
||||
end
|
||||
|
||||
-- rtu page
|
||||
|
||||
local rtu_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||
local rtu_list = ListBox{parent=rtu_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=rtu_list,height=1,hidden=true} -- padding
|
||||
|
||||
-- coordinator page
|
||||
|
||||
local crd_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||
local crd_box = Div{parent=crd_page,x=2,y=2,width=49,height=4,fg_bg=cpair(colors.black,colors.white)}
|
||||
|
||||
local crd_conn = LED{parent=crd_box,x=2,y=2,label="CONNECTION",colors=cpair(colors.green,colors.green_off)}
|
||||
crd_conn.register(databus.ps, "crd_conn", crd_conn.update)
|
||||
|
||||
TextBox{parent=crd_box,x=4,y=3,text="CHANNEL ",width=8,height=1,fg_bg=cpair(colors.gray,colors.white)}
|
||||
local crd_chan = TextBox{parent=crd_box,x=12,y=3,text="---",width=5,height=1,fg_bg=cpair(colors.gray,colors.white)}
|
||||
crd_chan.register(databus.ps, "crd_chan", crd_chan.set_value)
|
||||
|
||||
TextBox{parent=crd_box,x=22,y=2,text="FW:",width=3,height=1}
|
||||
local crd_fw_v = TextBox{parent=crd_box,x=26,y=2,text=" ------- ",width=9,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
crd_fw_v.register(databus.ps, "crd_fw", crd_fw_v.set_value)
|
||||
|
||||
TextBox{parent=crd_box,x=36,y=2,text="RTT:",width=4,height=1}
|
||||
local crd_rtt = DataIndicator{parent=crd_box,x=41,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
TextBox{parent=crd_box,x=47,y=2,text="ms",width=4,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
|
||||
crd_rtt.register(databus.ps, "crd_rtt", crd_rtt.update)
|
||||
crd_rtt.register(databus.ps, "crd_rtt_color", crd_rtt.recolor)
|
||||
|
||||
-- pocket diagnostics page
|
||||
|
||||
local pkt_page = Div{parent=page_div,x=1,y=1,hidden=true}
|
||||
local pdg_list = ListBox{parent=pkt_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=pdg_list,height=1,hidden=true} -- padding
|
||||
|
||||
-- assemble page panes
|
||||
|
||||
local panes = { main_page, plc_page, rtu_page, crd_page, pkt_page }
|
||||
|
||||
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
||||
|
||||
local tabs = {
|
||||
{ name = "SVR", color = cpair(colors.black, colors.ivory) },
|
||||
{ name = "PLC", color = cpair(colors.black, colors.ivory) },
|
||||
{ name = "RTU", color = cpair(colors.black, colors.ivory) },
|
||||
{ name = "CRD", color = cpair(colors.black, colors.ivory) },
|
||||
{ name = "PKT", 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 RTU/PDG list management to PGI
|
||||
pgi.link_elements(rtu_list, rtu_entry, pdg_list, pdg_entry)
|
||||
end
|
||||
|
||||
return init
|
93
supervisor/panel/pgi.lua
Normal file
93
supervisor/panel/pgi.lua
Normal file
@ -0,0 +1,93 @@
|
||||
--
|
||||
-- Protected Graphics Interface
|
||||
--
|
||||
|
||||
local log = require("scada-common.log")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local pgi = {}
|
||||
|
||||
local data = {
|
||||
rtu_list = nil, ---@type nil|graphics_element
|
||||
pdg_list = nil, ---@type nil|graphics_element
|
||||
rtu_entry = nil, ---@type function
|
||||
pdg_entry = nil, ---@type function
|
||||
-- session entries
|
||||
s_entries = { rtu = {}, pdg = {} }
|
||||
}
|
||||
|
||||
-- link list boxes
|
||||
---@param rtu_list graphics_element RTU list element
|
||||
---@param rtu_entry function RTU entry constructor
|
||||
---@param pdg_list graphics_element pocket diagnostics list element
|
||||
---@param pdg_entry function pocket diagnostics entry constructor
|
||||
function pgi.link_elements(rtu_list, rtu_entry, pdg_list, pdg_entry)
|
||||
data.rtu_list = rtu_list
|
||||
data.pdg_list = pdg_list
|
||||
data.rtu_entry = rtu_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)
|
||||
if data.rtu_list ~= nil and data.rtu_entry ~= nil then
|
||||
local success, result = pcall(data.rtu_entry, data.rtu_list, session_id)
|
||||
|
||||
if success then
|
||||
data.s_entries.rtu[session_id] = result
|
||||
else
|
||||
log.error(util.c("PGI: failed to create RTU entry (", result, ")"), true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- delete an RTU entry from the RTU list
|
||||
---@param session_id integer RTU session
|
||||
function pgi.delete_rtu_entry(session_id)
|
||||
if data.s_entries.rtu[session_id] ~= nil then
|
||||
local success, result = pcall(data.s_entries.rtu[session_id].delete)
|
||||
data.s_entries.rtu[session_id] = nil
|
||||
|
||||
if not success then
|
||||
log.error(util.c("PGI: failed to delete RTU entry (", result, ")"), true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- add a PDG entry to the PDG list
|
||||
---@param session_id integer pocket diagnostics session
|
||||
function pgi.create_pdg_entry(session_id)
|
||||
if data.pdg_list ~= nil and data.pdg_entry ~= nil then
|
||||
local success, result = pcall(data.pdg_entry, data.pdg_list, session_id)
|
||||
|
||||
if success then
|
||||
data.s_entries.pdg[session_id] = result
|
||||
else
|
||||
log.error(util.c("PGI: failed to create PDG entry (", result, ")"), true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- delete a PDG entry from the PDG list
|
||||
---@param session_id integer pocket diagnostics session
|
||||
function pgi.delete_pdg_entry(session_id)
|
||||
if data.s_entries.pdg[session_id] ~= nil then
|
||||
local success, result = pcall(data.s_entries.pdg[session_id].delete)
|
||||
data.s_entries.pdg[session_id] = nil
|
||||
|
||||
if not success then
|
||||
log.error(util.c("PGI: failed to delete PDG entry (", result, ")"), true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return pgi
|
42
supervisor/panel/style.lua
Normal file
42
supervisor/panel/style.lua
Normal file
@ -0,0 +1,42 @@
|
||||
--
|
||||
-- Graphics Style Options
|
||||
--
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local style = {}
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
-- GLOBAL --
|
||||
|
||||
-- remap global colors
|
||||
colors.ivory = colors.pink
|
||||
colors.yellow_hc = colors.purple
|
||||
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 = 0x0008fe }, -- LCD BLUE
|
||||
{ c = colors.purple, hex = 0xe3bc2a }, -- YELLOW HIGH CONTRAST
|
||||
{ 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
|
84
supervisor/renderer.lua
Normal file
84
supervisor/renderer.lua
Normal file
@ -0,0 +1,84 @@
|
||||
--
|
||||
-- Graphics Rendering Control
|
||||
--
|
||||
|
||||
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")
|
||||
|
||||
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()
|
||||
|
||||
-- disable PGI
|
||||
pgi.unlink()
|
||||
|
||||
-- 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|nil
|
||||
function renderer.handle_mouse(event)
|
||||
if ui.display ~= nil and event ~= nil then
|
||||
ui.display.handle_mouse(event)
|
||||
end
|
||||
end
|
||||
|
||||
return renderer
|
@ -4,6 +4,8 @@ local mqueue = require("scada-common.mqueue")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local databus = require("supervisor.databus")
|
||||
|
||||
local svqtypes = require("supervisor.session.svqtypes")
|
||||
|
||||
local coordinator = {}
|
||||
@ -18,8 +20,6 @@ local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||
|
||||
local SV_Q_DATA = svqtypes.SV_Q_DATA
|
||||
|
||||
local println = util.println
|
||||
|
||||
-- retry time constants in ms
|
||||
-- local INITIAL_WAIT = 1500
|
||||
local RETRY_PERIOD = 1000
|
||||
@ -49,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 = {
|
||||
@ -84,6 +88,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility)
|
||||
local function _close()
|
||||
self.conn_watchdog.cancel()
|
||||
self.connected = false
|
||||
databus.tx_crd_disconnected()
|
||||
end
|
||||
|
||||
-- send a CRDN packet
|
||||
@ -205,6 +210,8 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility)
|
||||
|
||||
-- log.debug(log_header .. "COORD RTT = " .. self.last_rtt .. "ms")
|
||||
-- log.debug(log_header .. "COORD TT = " .. (srv_now - coord_send) .. "ms")
|
||||
|
||||
databus.tx_crd_rtt(self.last_rtt)
|
||||
else
|
||||
log.debug(log_header .. "SCADA keep alive packet length mismatch")
|
||||
end
|
||||
|
@ -4,6 +4,8 @@ local mqueue = require("scada-common.mqueue")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local databus = require("supervisor.databus")
|
||||
|
||||
local svqtypes = require("supervisor.session.svqtypes")
|
||||
|
||||
local plc = {}
|
||||
@ -14,8 +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
|
||||
|
||||
-- retry time constants in ms
|
||||
local INITIAL_WAIT = 1500
|
||||
local INITIAL_AUTO_WAIT = 1000
|
||||
@ -49,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 = {
|
||||
@ -235,6 +239,7 @@ function plc.new_session(id, reactor_id, in_queue, out_queue, timeout)
|
||||
local function _close()
|
||||
self.conn_watchdog.cancel()
|
||||
self.connected = false
|
||||
databus.tx_plc_disconnected(reactor_id)
|
||||
end
|
||||
|
||||
-- send an RPLC packet
|
||||
@ -485,6 +490,8 @@ function plc.new_session(id, reactor_id, in_queue, out_queue, timeout)
|
||||
|
||||
-- log.debug(log_header .. "PLC RTT = " .. self.last_rtt .. "ms")
|
||||
-- log.debug(log_header .. "PLC TT = " .. (srv_now - plc_send) .. "ms")
|
||||
|
||||
databus.tx_plc_rtt(reactor_id, self.last_rtt)
|
||||
else
|
||||
log.debug(log_header .. "SCADA keep alive packet length mismatch")
|
||||
end
|
||||
|
@ -1,15 +1,14 @@
|
||||
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 databus = require("supervisor.databus")
|
||||
|
||||
local pocket = {}
|
||||
|
||||
local PROTOCOL = comms.PROTOCOL
|
||||
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
|
||||
@ -33,8 +32,12 @@ 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)
|
||||
local log_header = "diag_session(" .. id .. "): "
|
||||
---@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 = {
|
||||
-- connection properties
|
||||
@ -55,18 +58,19 @@ function pocket.new_session(id, in_queue, out_queue, timeout)
|
||||
acks = {
|
||||
},
|
||||
-- session database
|
||||
---@class diag_db
|
||||
---@class pdg_db
|
||||
sDB = {
|
||||
}
|
||||
}
|
||||
|
||||
---@class diag_session
|
||||
---@class pdg_session
|
||||
local public = {}
|
||||
|
||||
-- mark this diagnostics session as closed, stop watchdog
|
||||
local function _close()
|
||||
self.conn_watchdog.cancel()
|
||||
self.connected = false
|
||||
databus.tx_pdg_disconnected(id)
|
||||
end
|
||||
|
||||
-- send a SCADA management packet
|
||||
@ -106,16 +110,18 @@ function pocket.new_session(id, in_queue, out_queue, timeout)
|
||||
-- keep alive reply
|
||||
if pkt.length == 2 then
|
||||
local srv_start = pkt.data[1]
|
||||
-- local diag_send = pkt.data[2]
|
||||
-- local pdg_send = pkt.data[2]
|
||||
local srv_now = util.time()
|
||||
self.last_rtt = srv_now - srv_start
|
||||
|
||||
if self.last_rtt > 750 then
|
||||
log.warning(log_header .. "DIAG KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)")
|
||||
log.warning(log_header .. "PDG KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)")
|
||||
end
|
||||
|
||||
-- log.debug(log_header .. "DIAG RTT = " .. self.last_rtt .. "ms")
|
||||
-- log.debug(log_header .. "DIAG TT = " .. (srv_now - diag_send) .. "ms")
|
||||
-- log.debug(log_header .. "PDG RTT = " .. self.last_rtt .. "ms")
|
||||
-- log.debug(log_header .. "PDG TT = " .. (srv_now - pdg_send) .. "ms")
|
||||
|
||||
databus.tx_pdg_rtt(id, self.last_rtt)
|
||||
else
|
||||
log.debug(log_header .. "SCADA keep alive packet length mismatch")
|
||||
end
|
||||
|
@ -4,6 +4,8 @@ local mqueue = require("scada-common.mqueue")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local databus = require("supervisor.databus")
|
||||
|
||||
local svqtypes = require("supervisor.session.svqtypes")
|
||||
|
||||
-- supervisor rtu sessions (svrs)
|
||||
@ -22,8 +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 PERIODICS = {
|
||||
KEEP_ALIVE = 2000
|
||||
}
|
||||
@ -36,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 = {
|
||||
@ -66,6 +70,8 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
|
||||
|
||||
-- parse the recorded advertisement and create unit sub-sessions
|
||||
local function _handle_advertisement()
|
||||
local unit_count = 0
|
||||
|
||||
_reset_config()
|
||||
|
||||
for i = 1, #self.fac_units do
|
||||
@ -172,18 +178,22 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
|
||||
|
||||
if unit ~= nil then
|
||||
self.units[i] = unit
|
||||
unit_count = unit_count + 1
|
||||
elseif u_type ~= RTU_UNIT_TYPE.VIRTUAL then
|
||||
_reset_config()
|
||||
log.error(util.c(log_header, "bad advertisement: error occured while creating a unit (type is ", type_string, ")"))
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
databus.tx_rtu_units(id, unit_count)
|
||||
end
|
||||
|
||||
-- mark this RTU session as closed, stop watchdog
|
||||
local function _close()
|
||||
self.conn_watchdog.cancel()
|
||||
self.connected = false
|
||||
databus.tx_rtu_disconnected(id)
|
||||
|
||||
-- mark all RTU unit sessions as closed so the reactor unit knows
|
||||
for _, unit in pairs(self.units) do unit.close() end
|
||||
@ -254,6 +264,8 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
|
||||
|
||||
-- log.debug(log_header .. "RTU RTT = " .. self.last_rtt .. "ms")
|
||||
-- log.debug(log_header .. "RTU TT = " .. (srv_now - rtu_send) .. "ms")
|
||||
|
||||
databus.tx_rtu_rtt(id, self.last_rtt)
|
||||
else
|
||||
log.debug(log_header .. "SCADA keep alive packet length mismatch")
|
||||
end
|
||||
|
@ -3,6 +3,7 @@ local mqueue = require("scada-common.mqueue")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local config = require("supervisor.config")
|
||||
local databus = require("supervisor.databus")
|
||||
local facility = require("supervisor.facility")
|
||||
|
||||
local svqtypes = require("supervisor.session.svqtypes")
|
||||
@ -22,24 +23,26 @@ local CRD_S_DATA = coordinator.CRD_S_DATA
|
||||
|
||||
local svsessions = {}
|
||||
|
||||
---@enum SESSION_TYPE
|
||||
local SESSION_TYPE = {
|
||||
RTU_SESSION = 0, -- RTU gateway
|
||||
PLC_SESSION = 1, -- reactor PLC
|
||||
COORD_SESSION = 2, -- coordinator
|
||||
DIAG_SESSION = 3 -- pocket diagnostics
|
||||
PDG_SESSION = 3 -- pocket diagnostics
|
||||
}
|
||||
|
||||
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 = {}, diag = {} },
|
||||
next_ids = { rtu = 0, plc = 0, coord = 0, diag = 0 }
|
||||
sessions = { rtu = {}, plc = {}, coord = {}, pdg = {} },
|
||||
next_ids = { rtu = 0, plc = 0, coord = 0, pdg = 0 }
|
||||
}
|
||||
|
||||
---@alias sv_session_structs plc_session_struct|rtu_session_struct|coord_session_struct|diag_session_struct
|
||||
---@alias sv_session_structs plc_session_struct|rtu_session_struct|coord_session_struct|pdg_session_struct
|
||||
|
||||
-- PRIVATE FUNCTIONS --
|
||||
|
||||
@ -194,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
|
||||
@ -249,14 +254,14 @@ end
|
||||
-- find a coordinator or diagnostic access session by the remote port
|
||||
---@nodiscard
|
||||
---@param remote_port integer
|
||||
---@return coord_session_struct|diag_session_struct|nil
|
||||
---@return coord_session_struct|pdg_session_struct|nil
|
||||
function svsessions.find_svctl_session(remote_port)
|
||||
-- check coordinator sessions
|
||||
local session = _find_session(self.sessions.coord, remote_port)
|
||||
|
||||
-- check diagnostic sessions
|
||||
if session == nil then session = _find_session(self.sessions.diag, remote_port) end
|
||||
---@cast session coord_session_struct|diag_session_struct|nil
|
||||
if session == nil then session = _find_session(self.sessions.pdg, remote_port) end
|
||||
---@cast session coord_session_struct|pdg_session_struct|nil
|
||||
|
||||
return session
|
||||
end
|
||||
@ -306,13 +311,17 @@ 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)
|
||||
|
||||
self.next_ids.plc = self.next_ids.plc + 1
|
||||
|
||||
@ -344,11 +353,14 @@ 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)
|
||||
|
||||
databus.tx_rtu_connected(self.next_ids.rtu, version, remote_port)
|
||||
|
||||
self.next_ids.rtu = self.next_ids.rtu + 1
|
||||
|
||||
-- success
|
||||
@ -375,11 +387,14 @@ 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)
|
||||
|
||||
databus.tx_crd_connected(version, remote_port)
|
||||
|
||||
self.next_ids.coord = self.next_ids.coord + 1
|
||||
|
||||
-- success
|
||||
@ -396,9 +411,9 @@ end
|
||||
---@param remote_port integer
|
||||
---@param version string
|
||||
---@return integer|false session_id
|
||||
function svsessions.establish_diag_session(local_port, remote_port, version)
|
||||
---@class diag_session_struct
|
||||
local diag_s = {
|
||||
function svsessions.establish_pdg_session(local_port, remote_port, version)
|
||||
---@class pdg_session_struct
|
||||
local pdg_s = {
|
||||
s_type = "pkt",
|
||||
open = true,
|
||||
version = version,
|
||||
@ -406,18 +421,20 @@ function svsessions.establish_diag_session(local_port, remote_port, version)
|
||||
r_port = remote_port,
|
||||
in_queue = mqueue.new(),
|
||||
out_queue = mqueue.new(),
|
||||
instance = nil ---@type diag_session
|
||||
instance = nil ---@type pdg_session
|
||||
}
|
||||
|
||||
diag_s.instance = pocket.new_session(self.next_ids.diag, diag_s.in_queue, diag_s.out_queue, config.PKT_TIMEOUT)
|
||||
table.insert(self.sessions.diag, diag_s)
|
||||
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.diag)
|
||||
log.debug("established new pocket diagnostics session to " .. remote_port .. " with ID " .. self.next_ids.pdg)
|
||||
|
||||
self.next_ids.diag = self.next_ids.diag + 1
|
||||
databus.tx_pdg_connected(self.next_ids.pdg, version, remote_port)
|
||||
|
||||
self.next_ids.pdg = self.next_ids.pdg + 1
|
||||
|
||||
-- success
|
||||
return diag_s.instance.get_id()
|
||||
return pdg_s.instance.get_id()
|
||||
end
|
||||
|
||||
-- attempt to identify which session's watchdog timer fired
|
||||
|
@ -5,16 +5,22 @@
|
||||
require("/initenv").init_env()
|
||||
|
||||
local crash = require("scada-common.crash")
|
||||
local comms = require("scada-common.comms")
|
||||
local log = require("scada-common.log")
|
||||
local ppm = require("scada-common.ppm")
|
||||
local tcd = require("scada-common.tcallbackdsp")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local config = require("supervisor.config")
|
||||
local databus = require("supervisor.databus")
|
||||
local renderer = require("supervisor.renderer")
|
||||
local supervisor = require("supervisor.supervisor")
|
||||
|
||||
local svsessions = require("supervisor.session.svsessions")
|
||||
|
||||
local SUPERVISOR_VERSION = "v0.15.9"
|
||||
local SUPERVISOR_VERSION = "v0.16.6"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
@ -79,6 +85,9 @@ local function main()
|
||||
-- startup
|
||||
----------------------------------------
|
||||
|
||||
-- record firmware versions and ID
|
||||
databus.tx_versions(SUPERVISOR_VERSION, comms.version)
|
||||
|
||||
-- mount connected devices
|
||||
ppm.mount_all()
|
||||
|
||||
@ -89,9 +98,23 @@ local function main()
|
||||
return
|
||||
end
|
||||
|
||||
databus.tx_hw_modem(true)
|
||||
|
||||
-- start UI
|
||||
local fp_ok, message = pcall(renderer.start_ui)
|
||||
|
||||
if not fp_ok then
|
||||
renderer.close_ui()
|
||||
println_ts(util.c("UI error: ", message))
|
||||
log.error(util.c("GUI crashed with error ", message))
|
||||
else
|
||||
-- redefine println_ts local to not print as we have the front panel running
|
||||
println_ts = function (_) end
|
||||
end
|
||||
|
||||
-- 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)
|
||||
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
|
||||
@ -100,6 +123,9 @@ local function main()
|
||||
-- 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()
|
||||
@ -114,6 +140,7 @@ local function main()
|
||||
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
|
||||
@ -131,6 +158,8 @@ local function main()
|
||||
|
||||
println_ts("wireless modem reconnected.")
|
||||
log.info("comms modem reconnected")
|
||||
|
||||
databus.tx_hw_modem(true)
|
||||
else
|
||||
log.info("wired modem reconnected")
|
||||
end
|
||||
@ -139,6 +168,9 @@ local function main()
|
||||
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()
|
||||
|
||||
@ -149,10 +181,16 @@ local function main()
|
||||
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
|
||||
@ -165,8 +203,15 @@ local function main()
|
||||
end
|
||||
end
|
||||
|
||||
println_ts("exited")
|
||||
renderer.close_ui()
|
||||
|
||||
util.println_ts("exited")
|
||||
log.info("exited")
|
||||
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
|
||||
|
@ -11,8 +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
|
||||
|
||||
-- supervisory controller communications
|
||||
---@nodiscard
|
||||
---@param _version string supervisor version
|
||||
@ -22,8 +20,12 @@ local println = util.println
|
||||
---@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 = {}
|
||||
}
|
||||
@ -41,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
|
||||
@ -304,7 +306,7 @@ function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_liste
|
||||
end
|
||||
elseif dev_type == DEVICE_TYPE.PKT then
|
||||
-- this is an attempt to establish a new pocket diagnostic session
|
||||
local s_id = svsessions.establish_diag_session(l_port, r_port, firmware_v)
|
||||
local s_id = svsessions.establish_pdg_session(l_port, r_port, firmware_v)
|
||||
|
||||
println(util.c("PKT (", firmware_v, ") [:", r_port, "] \xbb connected"))
|
||||
log.info(util.c("SVCTL_ESTABLISH: pocket (", firmware_v, ") [:", r_port, "] connected with session ID ", s_id))
|
||||
|
Loading…
Reference in New Issue
Block a user