mirror of
https://github.com/MikaylaFischler/cc-mek-scada.git
synced 2024-08-30 18:22:34 +00:00
Merge pull request #472 from MikaylaFischler/pocket-alpha-dev
Work on Pocket Alpha App
This commit is contained in:
commit
3c5857cd68
@ -279,11 +279,12 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
-- send an API establish request response
|
-- send an API establish request response
|
||||||
---@param packet scada_packet
|
---@param packet scada_packet
|
||||||
---@param ack ESTABLISH_ACK
|
---@param ack ESTABLISH_ACK
|
||||||
local function _send_api_establish_ack(packet, ack)
|
---@param data any?
|
||||||
|
local function _send_api_establish_ack(packet, ack, data)
|
||||||
local s_pkt = comms.scada_packet()
|
local s_pkt = comms.scada_packet()
|
||||||
local m_pkt = comms.mgmt_packet()
|
local m_pkt = comms.mgmt_packet()
|
||||||
|
|
||||||
m_pkt.make(MGMT_TYPE.ESTABLISH, { ack })
|
m_pkt.make(MGMT_TYPE.ESTABLISH, { ack, data })
|
||||||
s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
s_pkt.make(packet.src_addr(), packet.seq_num() + 1, PROTOCOL.SCADA_MGMT, m_pkt.raw_sendable())
|
||||||
|
|
||||||
nic.transmit(config.PKT_Channel, config.CRD_Channel, s_pkt)
|
nic.transmit(config.PKT_Channel, config.CRD_Channel, s_pkt)
|
||||||
@ -470,10 +471,11 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
||||||
-- establish a new session
|
-- establish a new session
|
||||||
-- validate packet and continue
|
-- validate packet and continue
|
||||||
if packet.length == 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then
|
if packet.length == 4 then
|
||||||
local comms_v = packet.data[1]
|
local comms_v = util.strval(packet.data[1])
|
||||||
local firmware_v = packet.data[2]
|
local firmware_v = util.strval(packet.data[2])
|
||||||
local dev_type = packet.data[3]
|
local dev_type = packet.data[3]
|
||||||
|
local api_v = util.strval(packet.data[4])
|
||||||
|
|
||||||
if comms_v ~= comms.version then
|
if comms_v ~= comms.version then
|
||||||
if self.last_api_est_acks[src_addr] ~= ESTABLISH_ACK.BAD_VERSION then
|
if self.last_api_est_acks[src_addr] ~= ESTABLISH_ACK.BAD_VERSION then
|
||||||
@ -481,12 +483,19 @@ function coordinator.comms(version, nic, sv_watchdog)
|
|||||||
end
|
end
|
||||||
|
|
||||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION)
|
||||||
|
elseif api_v ~= comms.api_version then
|
||||||
|
if self.last_api_est_acks[src_addr] ~= ESTABLISH_ACK.BAD_API_VERSION then
|
||||||
|
log.info(util.c("dropping API establish packet with incorrect api version v", api_v, " (expected v", comms.api_version, ")"))
|
||||||
|
end
|
||||||
|
|
||||||
|
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_API_VERSION)
|
||||||
elseif dev_type == DEVICE_TYPE.PKT then
|
elseif dev_type == DEVICE_TYPE.PKT then
|
||||||
-- pocket linking request
|
-- pocket linking request
|
||||||
local id = apisessions.establish_session(src_addr, firmware_v)
|
local id = apisessions.establish_session(src_addr, firmware_v)
|
||||||
coordinator.log_comms(util.c("API_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", id))
|
coordinator.log_comms(util.c("API_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", id))
|
||||||
|
|
||||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.ALLOW)
|
local conf = iocontrol.get_db().facility.conf
|
||||||
|
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.ALLOW, { conf.num_units, conf.cooling })
|
||||||
else
|
else
|
||||||
log.debug(util.c("API_ESTABLISH: illegal establish packet for device ", dev_type, " on pocket channel"))
|
log.debug(util.c("API_ESTABLISH: illegal establish packet for device ", dev_type, " on pocket channel"))
|
||||||
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.DENY)
|
_send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.DENY)
|
||||||
|
@ -67,6 +67,7 @@ function iocontrol.init(conf, comms, temp_scale)
|
|||||||
-- facility data structure
|
-- facility data structure
|
||||||
---@class ioctl_facility
|
---@class ioctl_facility
|
||||||
io.facility = {
|
io.facility = {
|
||||||
|
conf = conf,
|
||||||
num_units = conf.num_units,
|
num_units = conf.num_units,
|
||||||
tank_mode = conf.cooling.fac_tank_mode,
|
tank_mode = conf.cooling.fac_tank_mode,
|
||||||
tank_defs = conf.cooling.fac_tank_defs,
|
tank_defs = conf.cooling.fac_tank_defs,
|
||||||
@ -279,18 +280,18 @@ function iocontrol.init(conf, comms, temp_scale)
|
|||||||
|
|
||||||
---@type alarms
|
---@type alarms
|
||||||
alarms = {
|
alarms = {
|
||||||
ALARM_STATE.INACTIVE, -- containment breach
|
ALARM_STATE.INACTIVE, -- containment breach
|
||||||
ALARM_STATE.INACTIVE, -- containment radiation
|
ALARM_STATE.INACTIVE, -- containment radiation
|
||||||
ALARM_STATE.INACTIVE, -- reactor lost
|
ALARM_STATE.INACTIVE, -- reactor lost
|
||||||
ALARM_STATE.INACTIVE, -- damage critical
|
ALARM_STATE.INACTIVE, -- damage critical
|
||||||
ALARM_STATE.INACTIVE, -- reactor taking damage
|
ALARM_STATE.INACTIVE, -- reactor taking damage
|
||||||
ALARM_STATE.INACTIVE, -- reactor over temperature
|
ALARM_STATE.INACTIVE, -- reactor over temperature
|
||||||
ALARM_STATE.INACTIVE, -- reactor high temperature
|
ALARM_STATE.INACTIVE, -- reactor high temperature
|
||||||
ALARM_STATE.INACTIVE, -- waste leak
|
ALARM_STATE.INACTIVE, -- waste leak
|
||||||
ALARM_STATE.INACTIVE, -- waste level high
|
ALARM_STATE.INACTIVE, -- waste level high
|
||||||
ALARM_STATE.INACTIVE, -- RPS transient
|
ALARM_STATE.INACTIVE, -- RPS transient
|
||||||
ALARM_STATE.INACTIVE, -- RCS transient
|
ALARM_STATE.INACTIVE, -- RCS transient
|
||||||
ALARM_STATE.INACTIVE -- turbine trip
|
ALARM_STATE.INACTIVE -- turbine trip
|
||||||
},
|
},
|
||||||
|
|
||||||
annunciator = {}, ---@type annunciator
|
annunciator = {}, ---@type annunciator
|
||||||
|
@ -8,7 +8,7 @@ local iocontrol = require("coordinator.iocontrol")
|
|||||||
local pocket = {}
|
local pocket = {}
|
||||||
|
|
||||||
local PROTOCOL = comms.PROTOCOL
|
local PROTOCOL = comms.PROTOCOL
|
||||||
-- local CRDN_TYPE = comms.CRDN_TYPE
|
local CRDN_TYPE = comms.CRDN_TYPE
|
||||||
local MGMT_TYPE = comms.MGMT_TYPE
|
local MGMT_TYPE = comms.MGMT_TYPE
|
||||||
|
|
||||||
-- retry time constants in ms
|
-- retry time constants in ms
|
||||||
@ -73,18 +73,18 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- send a CRDN packet
|
-- send a CRDN packet
|
||||||
-----@param msg_type CRDN_TYPE
|
---@param msg_type CRDN_TYPE
|
||||||
-----@param msg table
|
---@param msg table
|
||||||
-- local function _send(msg_type, msg)
|
local function _send(msg_type, msg)
|
||||||
-- local s_pkt = comms.scada_packet()
|
local s_pkt = comms.scada_packet()
|
||||||
-- local c_pkt = comms.crdn_packet()
|
local c_pkt = comms.crdn_packet()
|
||||||
|
|
||||||
-- c_pkt.make(msg_type, msg)
|
c_pkt.make(msg_type, msg)
|
||||||
-- s_pkt.make(self.seq_num, PROTOCOL.SCADA_CRDN, c_pkt.raw_sendable())
|
s_pkt.make(s_addr, self.seq_num, PROTOCOL.SCADA_CRDN, c_pkt.raw_sendable())
|
||||||
|
|
||||||
-- out_queue.push_packet(s_pkt)
|
out_queue.push_packet(s_pkt)
|
||||||
-- self.seq_num = self.seq_num + 1
|
self.seq_num = self.seq_num + 1
|
||||||
-- end
|
end
|
||||||
|
|
||||||
-- send a SCADA management packet
|
-- send a SCADA management packet
|
||||||
---@param msg_type MGMT_TYPE
|
---@param msg_type MGMT_TYPE
|
||||||
@ -120,8 +120,39 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout)
|
|||||||
if pkt.scada_frame.protocol() == PROTOCOL.SCADA_CRDN then
|
if pkt.scada_frame.protocol() == PROTOCOL.SCADA_CRDN then
|
||||||
---@cast pkt crdn_frame
|
---@cast pkt crdn_frame
|
||||||
|
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
-- handle packet by type
|
-- handle packet by type
|
||||||
if pkt.type == nil then
|
if pkt.type == CRDN_TYPE.API_GET_FAC then
|
||||||
|
local fac = db.facility
|
||||||
|
|
||||||
|
local data = {
|
||||||
|
fac.all_sys_ok,
|
||||||
|
fac.rtu_count,
|
||||||
|
fac.radiation,
|
||||||
|
{ fac.auto_ready, fac.auto_active, fac.auto_ramping, fac.auto_saturated },
|
||||||
|
{ fac.auto_current_waste_product, fac.auto_pu_fallback_active },
|
||||||
|
util.table_len(fac.tank_data_tbl),
|
||||||
|
fac.induction_data_tbl[1] ~= nil,
|
||||||
|
fac.sps_data_tbl[1] ~= nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
_send(CRDN_TYPE.API_GET_FAC, data)
|
||||||
|
elseif pkt.type == CRDN_TYPE.API_GET_UNITS then
|
||||||
|
local data = {}
|
||||||
|
|
||||||
|
for i = 1, #db.units do
|
||||||
|
local u = db.units[i] ---@type ioctl_unit
|
||||||
|
table.insert(data, {
|
||||||
|
u.unit_id,
|
||||||
|
u.num_boilers,
|
||||||
|
u.num_turbines,
|
||||||
|
u.num_snas,
|
||||||
|
u.has_tank
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
_send(CRDN_TYPE.API_GET_UNITS, data)
|
||||||
else
|
else
|
||||||
log.debug(log_header .. "handler received unsupported CRDN packet type " .. pkt.type)
|
log.debug(log_header .. "handler received unsupported CRDN packet type " .. pkt.type)
|
||||||
end
|
end
|
||||||
|
@ -7,7 +7,7 @@ local flasher = require("graphics.flasher")
|
|||||||
|
|
||||||
local core = {}
|
local core = {}
|
||||||
|
|
||||||
core.version = "2.2.2"
|
core.version = "2.2.3"
|
||||||
|
|
||||||
core.flasher = flasher
|
core.flasher = flasher
|
||||||
core.events = events
|
core.events = events
|
||||||
|
@ -49,9 +49,11 @@ local element = {}
|
|||||||
---|indicator_light_args
|
---|indicator_light_args
|
||||||
---|power_indicator_args
|
---|power_indicator_args
|
||||||
---|rad_indicator_args
|
---|rad_indicator_args
|
||||||
|
---|signal_bar_args
|
||||||
---|state_indicator_args
|
---|state_indicator_args
|
||||||
---|tristate_indicator_light_args
|
---|tristate_indicator_light_args
|
||||||
---|vbar_args
|
---|vbar_args
|
||||||
|
---|app_multipane_args
|
||||||
---|colormap_args
|
---|colormap_args
|
||||||
---|displaybox_args
|
---|displaybox_args
|
||||||
---|div_args
|
---|div_args
|
||||||
|
109
graphics/elements/appmultipane.lua
Normal file
109
graphics/elements/appmultipane.lua
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
-- App Page Multi-Pane Display Graphics Element
|
||||||
|
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
local element = require("graphics.element")
|
||||||
|
local events = require("graphics.events")
|
||||||
|
|
||||||
|
local MOUSE_CLICK = core.events.MOUSE_CLICK
|
||||||
|
|
||||||
|
---@class app_multipane_args
|
||||||
|
---@field panes table panes to swap between
|
||||||
|
---@field nav_colors cpair on/off colors (a/b respectively) for page navigator
|
||||||
|
---@field scroll_nav boolean? true to allow scrolling to change the active pane
|
||||||
|
---@field drag_nav boolean? true to allow mouse dragging to change the active pane (on mouse up)
|
||||||
|
---@field callback function? function to call when pane is changed by mouse interaction
|
||||||
|
---@field parent graphics_element
|
||||||
|
---@field id? string element id
|
||||||
|
---@field x? integer 1 if omitted
|
||||||
|
---@field y? integer auto incremented if omitted
|
||||||
|
---@field width? integer parent width if omitted
|
||||||
|
---@field height? integer parent height if omitted
|
||||||
|
---@field gframe? graphics_frame frame instead of x/y/width/height
|
||||||
|
---@field fg_bg? cpair foreground/background colors
|
||||||
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
|
-- new app multipane element
|
||||||
|
---@nodiscard
|
||||||
|
---@param args app_multipane_args
|
||||||
|
---@return graphics_element element, element_id id
|
||||||
|
local function multipane(args)
|
||||||
|
element.assert(type(args.panes) == "table", "panes is a required field")
|
||||||
|
|
||||||
|
-- create new graphics element base object
|
||||||
|
local e = element.new(args)
|
||||||
|
|
||||||
|
e.value = 1
|
||||||
|
|
||||||
|
local nav_x_start = math.floor((e.frame.w / 2) - (#args.panes / 2)) + 1
|
||||||
|
local nav_x_end = math.floor((e.frame.w / 2) - (#args.panes / 2)) + #args.panes
|
||||||
|
|
||||||
|
-- show the selected pane
|
||||||
|
function e.redraw()
|
||||||
|
for i = 1, #args.panes do args.panes[i].hide() end
|
||||||
|
args.panes[e.value].show()
|
||||||
|
|
||||||
|
-- draw page indicator dots
|
||||||
|
for i = 1, #args.panes do
|
||||||
|
e.w_set_cur(nav_x_start + (i - 1), e.frame.h)
|
||||||
|
e.w_set_fgd(util.trinary(i == e.value, args.nav_colors.color_a, args.nav_colors.color_b))
|
||||||
|
e.w_write("\x07")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- handle mouse interaction
|
||||||
|
---@param event mouse_interaction mouse event
|
||||||
|
function e.handle_mouse(event)
|
||||||
|
local initial = e.value
|
||||||
|
|
||||||
|
if e.enabled then
|
||||||
|
if event.current.y == e.frame.h and event.current.x >= nav_x_start and event.current.x <= nav_x_end then
|
||||||
|
local id = event.current.x - nav_x_start + 1
|
||||||
|
|
||||||
|
if event.type == MOUSE_CLICK.TAP then
|
||||||
|
e.set_value(id)
|
||||||
|
elseif event.type == MOUSE_CLICK.UP then
|
||||||
|
e.set_value(id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if args.scroll_nav then
|
||||||
|
if event.type == events.MOUSE_CLICK.SCROLL_DOWN then
|
||||||
|
e.set_value(e.value + 1)
|
||||||
|
elseif event.type == events.MOUSE_CLICK.SCROLL_UP then
|
||||||
|
e.set_value(e.value - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if args.drag_nav then
|
||||||
|
local x1, x2 = event.initial.x, event.current.x
|
||||||
|
if event.type == events.MOUSE_CLICK.UP and e.in_frame_bounds(x1, event.initial.y) and e.in_frame_bounds(x1, event.current.y) then
|
||||||
|
if x2 > x1 then
|
||||||
|
e.set_value(e.value - 1)
|
||||||
|
elseif x2 < x1 then
|
||||||
|
e.set_value(e.value + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if e.value ~= initial and type(args.callback) == "function" then args.callback(e.value) end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- select which pane is shown
|
||||||
|
---@param value integer pane to show
|
||||||
|
function e.set_value(value)
|
||||||
|
if (e.value ~= value) and (value > 0) and (value <= #args.panes) then
|
||||||
|
e.value = value
|
||||||
|
e.redraw()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- initial draw
|
||||||
|
e.redraw()
|
||||||
|
|
||||||
|
return e.complete()
|
||||||
|
end
|
||||||
|
|
||||||
|
return multipane
|
@ -65,7 +65,7 @@ local function switch_button(args)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- set the value
|
-- set the value (does not call the callback)
|
||||||
---@param val boolean new value
|
---@param val boolean new value
|
||||||
function e.set_value(val)
|
function e.set_value(val)
|
||||||
e.value = val
|
e.value = val
|
||||||
|
85
graphics/elements/indicators/signal.lua
Normal file
85
graphics/elements/indicators/signal.lua
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
-- Signal Bars Graphics Element
|
||||||
|
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local element = require("graphics.element")
|
||||||
|
|
||||||
|
---@class signal_bar_args
|
||||||
|
---@field compact? boolean true to use a single character (works better against edges that extend out colors)
|
||||||
|
---@field colors_low_med? cpair color a for low signal quality, color b for medium signal quality
|
||||||
|
---@field disconnect_color? color color for the 'x' on disconnect
|
||||||
|
---@field parent graphics_element
|
||||||
|
---@field id? string element id
|
||||||
|
---@field x? integer 1 if omitted
|
||||||
|
---@field y? integer auto incremented if omitted
|
||||||
|
---@field fg_bg? cpair foreground/background colors (foreground is used for high signal quality)
|
||||||
|
---@field hidden? boolean true to hide on initial draw
|
||||||
|
|
||||||
|
-- new signal bar
|
||||||
|
---@nodiscard
|
||||||
|
---@param args signal_bar_args
|
||||||
|
---@return graphics_element element, element_id id
|
||||||
|
local function signal_bar(args)
|
||||||
|
args.height = 1
|
||||||
|
args.width = util.trinary(args.compact, 1, 2)
|
||||||
|
|
||||||
|
-- create new graphics element base object
|
||||||
|
local e = element.new(args)
|
||||||
|
|
||||||
|
e.value = 0
|
||||||
|
|
||||||
|
local blit_bkg = args.fg_bg.blit_bkg
|
||||||
|
local blit_0, blit_1, blit_2, blit_3 = args.fg_bg.blit_fgd, args.fg_bg.blit_fgd, args.fg_bg.blit_fgd, args.fg_bg.blit_fgd
|
||||||
|
|
||||||
|
if type(args.colors_low_med) == "table" then
|
||||||
|
blit_1 = args.colors_low_med.blit_a or blit_1
|
||||||
|
blit_2 = args.colors_low_med.blit_b or blit_2
|
||||||
|
end
|
||||||
|
|
||||||
|
if util.is_int(args.disconnect_color) then blit_0 = colors.toBlit(args.disconnect_color) end
|
||||||
|
|
||||||
|
-- on state change (0 = offline, 1 through 3 = low to high signal)
|
||||||
|
---@param new_state integer signal state
|
||||||
|
function e.on_update(new_state)
|
||||||
|
e.value = new_state
|
||||||
|
e.redraw()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- set signal state (0 = offline, 1 through 3 = low to high signal)
|
||||||
|
---@param val integer signal state
|
||||||
|
function e.set_value(val) e.on_update(val) end
|
||||||
|
|
||||||
|
-- draw label and signal bar
|
||||||
|
function e.redraw()
|
||||||
|
e.w_set_cur(1, 1)
|
||||||
|
|
||||||
|
if args.compact then
|
||||||
|
if e.value == 1 then
|
||||||
|
e.w_blit("\x90", blit_1, blit_bkg)
|
||||||
|
elseif e.value == 2 then
|
||||||
|
e.w_blit("\x94", blit_2, blit_bkg)
|
||||||
|
elseif e.value == 3 then
|
||||||
|
e.w_blit("\x95", blit_3, blit_bkg)
|
||||||
|
else
|
||||||
|
e.w_blit("x", blit_0, blit_bkg)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if e.value == 1 then
|
||||||
|
e.w_blit("\x9f ", blit_bkg .. blit_bkg, blit_1 .. blit_bkg)
|
||||||
|
elseif e.value == 2 then
|
||||||
|
e.w_blit("\x9f\x94", blit_bkg .. blit_2, blit_2 .. blit_bkg)
|
||||||
|
elseif e.value == 3 then
|
||||||
|
e.w_blit("\x9f\x81", blit_bkg .. blit_bkg, blit_3 .. blit_3)
|
||||||
|
else
|
||||||
|
e.w_blit(" x", blit_0 .. blit_0, blit_bkg .. blit_bkg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- initial draw
|
||||||
|
e.redraw()
|
||||||
|
|
||||||
|
return e.complete()
|
||||||
|
end
|
||||||
|
|
||||||
|
return signal_bar
|
@ -2,18 +2,17 @@
|
|||||||
-- I/O Control for Pocket Integration with Supervisor & Coordinator
|
-- I/O Control for Pocket Integration with Supervisor & Coordinator
|
||||||
--
|
--
|
||||||
|
|
||||||
local psil = require("scada-common.psil")
|
local log = require("scada-common.log")
|
||||||
|
local psil = require("scada-common.psil")
|
||||||
local types = require("scada-common.types")
|
local types = require("scada-common.types")
|
||||||
|
|
||||||
local ALARM = types.ALARM
|
local ALARM = types.ALARM
|
||||||
|
|
||||||
local iocontrol = {}
|
---@todo nominal trip time is ping (0ms to 10ms usually)
|
||||||
|
local WARN_TT = 40
|
||||||
|
local HIGH_TT = 80
|
||||||
|
|
||||||
---@class pocket_ioctl
|
local iocontrol = {}
|
||||||
local io = {
|
|
||||||
ps = psil.create()
|
|
||||||
}
|
|
||||||
|
|
||||||
---@enum POCKET_LINK_STATE
|
---@enum POCKET_LINK_STATE
|
||||||
local LINK_STATE = {
|
local LINK_STATE = {
|
||||||
@ -23,23 +22,175 @@ local LINK_STATE = {
|
|||||||
LINKED = 3
|
LINKED = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
---@enum NAV_PAGE
|
iocontrol.LINK_STATE = LINK_STATE
|
||||||
local NAV_PAGE = {
|
|
||||||
HOME = 1,
|
---@enum POCKET_APP_ID
|
||||||
|
local APP_ID = {
|
||||||
|
ROOT = 1,
|
||||||
|
-- main app page
|
||||||
UNITS = 2,
|
UNITS = 2,
|
||||||
REACTORS = 3,
|
ABOUT = 3,
|
||||||
BOILERS = 4,
|
-- diag app page
|
||||||
TURBINES = 5,
|
ALARMS = 4,
|
||||||
DIAG = 6,
|
-- other
|
||||||
D_ALARMS = 7
|
DUMMY = 5,
|
||||||
|
NUM_APPS = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
iocontrol.LINK_STATE = LINK_STATE
|
iocontrol.APP_ID = APP_ID
|
||||||
iocontrol.NAV_PAGE = NAV_PAGE
|
|
||||||
|
---@class pocket_ioctl
|
||||||
|
local io = {
|
||||||
|
version = "unknown",
|
||||||
|
ps = psil.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
---@class nav_tree_page
|
||||||
|
---@field _p nav_tree_page|nil page's parent
|
||||||
|
---@field _c table page's children
|
||||||
|
---@field nav_to function function to navigate to this page
|
||||||
|
---@field switcher function|nil function to switch between children
|
||||||
|
---@field tasks table tasks to run while viewing this page
|
||||||
|
|
||||||
|
-- allocate the page navigation system
|
||||||
|
function iocontrol.alloc_nav()
|
||||||
|
local self = {
|
||||||
|
pane = nil, ---@type graphics_element
|
||||||
|
apps = {},
|
||||||
|
containers = {},
|
||||||
|
cur_app = APP_ID.ROOT
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cur_page = self.root
|
||||||
|
|
||||||
|
---@class pocket_nav
|
||||||
|
io.nav = {}
|
||||||
|
|
||||||
|
-- set the root pane element to switch between apps with
|
||||||
|
---@param root_pane graphics_element
|
||||||
|
function io.nav.set_pane(root_pane)
|
||||||
|
self.pane = root_pane
|
||||||
|
end
|
||||||
|
|
||||||
|
-- register an app
|
||||||
|
---@param app_id POCKET_APP_ID app ID
|
||||||
|
---@param container graphics_element element that contains this app (usually a Div)
|
||||||
|
---@param pane graphics_element? multipane if this is a simple paned app, then nav_to must be a number
|
||||||
|
function io.nav.register_app(app_id, container, pane)
|
||||||
|
---@class pocket_app
|
||||||
|
local app = {
|
||||||
|
root = { _p = nil, _c = {}, nav_to = function () end, tasks = {} }, ---@type nav_tree_page
|
||||||
|
cur_page = nil, ---@type nav_tree_page
|
||||||
|
pane = pane,
|
||||||
|
paned_pages = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
-- delayed set of the pane if it wasn't ready at the start
|
||||||
|
---@param root_pane graphics_element multipane
|
||||||
|
function app.set_root_pane(root_pane)
|
||||||
|
app.pane = root_pane
|
||||||
|
end
|
||||||
|
|
||||||
|
-- if a pane was provided, this will switch between numbered pages
|
||||||
|
---@param idx integer page index
|
||||||
|
function app.switcher(idx)
|
||||||
|
if app.paned_pages[idx] then
|
||||||
|
app.paned_pages[idx].nav_to()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create a new page entry in the app's page navigation tree
|
||||||
|
---@param parent nav_tree_page? a parent page or nil to set this as the root
|
||||||
|
---@param nav_to function|integer function to navigate to this page or pane index
|
||||||
|
---@return nav_tree_page new_page this new page
|
||||||
|
function app.new_page(parent, nav_to)
|
||||||
|
---@type nav_tree_page
|
||||||
|
local page = { _p = parent, _c = {}, nav_to = function () end, switcher = function () end, tasks = {} }
|
||||||
|
|
||||||
|
if parent == nil then
|
||||||
|
app.root = page
|
||||||
|
if app.cur_page == nil then app.cur_page = page end
|
||||||
|
end
|
||||||
|
|
||||||
|
if type(nav_to) == "number" then
|
||||||
|
app.paned_pages[nav_to] = page
|
||||||
|
|
||||||
|
function page.nav_to()
|
||||||
|
app.cur_page = page
|
||||||
|
if app.pane then app.pane.set_value(nav_to) end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
function page.nav_to()
|
||||||
|
app.cur_page = page
|
||||||
|
nav_to()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- switch between children
|
||||||
|
---@param id integer child ID
|
||||||
|
function page.switcher(id) if page._c[id] then page._c[id].nav_to() end end
|
||||||
|
|
||||||
|
if parent ~= nil then
|
||||||
|
table.insert(page._p._c, page)
|
||||||
|
end
|
||||||
|
|
||||||
|
return page
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get the currently active page
|
||||||
|
function app.get_current_page() return app.cur_page end
|
||||||
|
|
||||||
|
-- attempt to navigate up the tree
|
||||||
|
---@return boolean success true if successfully navigated up
|
||||||
|
function app.nav_up()
|
||||||
|
local parent = app.cur_page._p
|
||||||
|
if parent then parent.nav_to() end
|
||||||
|
return parent ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
self.apps[app_id] = app
|
||||||
|
self.containers[app_id] = container
|
||||||
|
|
||||||
|
return app
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get a list of the app containers (usually Div elements)
|
||||||
|
function io.nav.get_containers() return self.containers end
|
||||||
|
|
||||||
|
-- open a given app
|
||||||
|
---@param app_id POCKET_APP_ID
|
||||||
|
function io.nav.open_app(app_id)
|
||||||
|
if self.apps[app_id] then
|
||||||
|
self.cur_app = app_id
|
||||||
|
self.pane.set_value(app_id)
|
||||||
|
else
|
||||||
|
log.debug("tried to open unknown app")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- get the currently active page
|
||||||
|
---@return nav_tree_page
|
||||||
|
function io.nav.get_current_page()
|
||||||
|
return self.apps[self.cur_app].get_current_page()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- attempt to navigate up
|
||||||
|
function io.nav.nav_up()
|
||||||
|
local app = self.apps[self.cur_app] ---@type pocket_app
|
||||||
|
log.debug("attempting app nav up for app " .. self.cur_app)
|
||||||
|
|
||||||
|
if not app.nav_up() then
|
||||||
|
log.debug("internal app nav up failed, going to home screen")
|
||||||
|
io.nav.open_app(APP_ID.ROOT)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
-- initialize facility-independent components of pocket iocontrol
|
-- initialize facility-independent components of pocket iocontrol
|
||||||
---@param comms pocket_comms
|
---@param comms pocket_comms
|
||||||
function iocontrol.init_core(comms)
|
function iocontrol.init_core(comms)
|
||||||
|
iocontrol.alloc_nav()
|
||||||
|
|
||||||
---@class pocket_ioctl_diag
|
---@class pocket_ioctl_diag
|
||||||
io.diag = {}
|
io.diag = {}
|
||||||
|
|
||||||
@ -76,29 +227,135 @@ function iocontrol.init_core(comms)
|
|||||||
alarm_buttons = {},
|
alarm_buttons = {},
|
||||||
tone_indicators = {} -- indicators to update from supervisor tone states
|
tone_indicators = {} -- indicators to update from supervisor tone states
|
||||||
}
|
}
|
||||||
|
|
||||||
---@class pocket_nav
|
|
||||||
io.nav = {
|
|
||||||
page = NAV_PAGE.HOME, ---@type NAV_PAGE
|
|
||||||
sub_pages = { NAV_PAGE.HOME, NAV_PAGE.UNITS, NAV_PAGE.REACTORS, NAV_PAGE.BOILERS, NAV_PAGE.TURBINES, NAV_PAGE.DIAG },
|
|
||||||
tasks = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
-- add a task to be performed periodically while on a given page
|
|
||||||
---@param page NAV_PAGE page to add task to
|
|
||||||
---@param task function function to execute
|
|
||||||
function io.nav.register_task(page, task)
|
|
||||||
if io.nav.tasks[page] == nil then io.nav.tasks[page] = {} end
|
|
||||||
table.insert(io.nav.tasks[page], task)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- initialize facility-dependent components of pocket iocontrol
|
-- initialize facility-dependent components of pocket iocontrol
|
||||||
function iocontrol.init_fac() end
|
---@param conf facility_conf configuration
|
||||||
|
---@param temp_scale 1|2|3|4 temperature unit (1 = K, 2 = C, 3 = F, 4 = R)
|
||||||
|
function iocontrol.init_fac(conf, temp_scale)
|
||||||
|
-- temperature unit label and conversion function (from Kelvin)
|
||||||
|
if temp_scale == 2 then
|
||||||
|
io.temp_label = "\xb0C"
|
||||||
|
io.temp_convert = function (t) return t - 273.15 end
|
||||||
|
elseif temp_scale == 3 then
|
||||||
|
io.temp_label = "\xb0F"
|
||||||
|
io.temp_convert = function (t) return (1.8 * (t - 273.15)) + 32 end
|
||||||
|
elseif temp_scale == 4 then
|
||||||
|
io.temp_label = "\xb0R"
|
||||||
|
io.temp_convert = function (t) return 1.8 * t end
|
||||||
|
else
|
||||||
|
io.temp_label = "K"
|
||||||
|
io.temp_convert = function (t) return t end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- facility data structure
|
||||||
|
---@class pioctl_facility
|
||||||
|
io.facility = {
|
||||||
|
num_units = conf.num_units,
|
||||||
|
tank_mode = conf.cooling.fac_tank_mode,
|
||||||
|
tank_defs = conf.cooling.fac_tank_defs,
|
||||||
|
all_sys_ok = false,
|
||||||
|
rtu_count = 0,
|
||||||
|
|
||||||
|
auto_ready = false,
|
||||||
|
auto_active = false,
|
||||||
|
auto_ramping = false,
|
||||||
|
auto_saturated = false,
|
||||||
|
|
||||||
|
---@type WASTE_PRODUCT
|
||||||
|
auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM,
|
||||||
|
auto_pu_fallback_active = false,
|
||||||
|
|
||||||
|
radiation = types.new_zero_radiation_reading(),
|
||||||
|
|
||||||
|
ps = psil.create(),
|
||||||
|
|
||||||
|
induction_ps_tbl = {},
|
||||||
|
induction_data_tbl = {},
|
||||||
|
|
||||||
|
sps_ps_tbl = {},
|
||||||
|
sps_data_tbl = {},
|
||||||
|
|
||||||
|
tank_ps_tbl = {},
|
||||||
|
tank_data_tbl = {},
|
||||||
|
|
||||||
|
env_d_ps = psil.create(),
|
||||||
|
env_d_data = {}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
-- set network link state
|
-- set network link state
|
||||||
---@param state POCKET_LINK_STATE
|
---@param state POCKET_LINK_STATE
|
||||||
function iocontrol.report_link_state(state) io.ps.publish("link_state", state) end
|
function iocontrol.report_link_state(state)
|
||||||
|
io.ps.publish("link_state", state)
|
||||||
|
|
||||||
|
if state == LINK_STATE.API_LINK_ONLY or state == LINK_STATE.UNLINKED then
|
||||||
|
io.ps.publish("svr_conn_quality", 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
if state == LINK_STATE.SV_LINK_ONLY or state == LINK_STATE.UNLINKED then
|
||||||
|
io.ps.publish("crd_conn_quality", 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- determine supervisor connection quality (trip time)
|
||||||
|
---@param trip_time integer
|
||||||
|
function iocontrol.report_svr_tt(trip_time)
|
||||||
|
local state = 3
|
||||||
|
if trip_time > HIGH_TT then
|
||||||
|
state = 1
|
||||||
|
elseif trip_time > WARN_TT then
|
||||||
|
state = 2
|
||||||
|
end
|
||||||
|
|
||||||
|
io.ps.publish("svr_conn_quality", state)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- determine coordinator connection quality (trip time)
|
||||||
|
---@param trip_time integer
|
||||||
|
function iocontrol.report_crd_tt(trip_time)
|
||||||
|
local state = 3
|
||||||
|
if trip_time > HIGH_TT then
|
||||||
|
state = 1
|
||||||
|
elseif trip_time > WARN_TT then
|
||||||
|
state = 2
|
||||||
|
end
|
||||||
|
|
||||||
|
io.ps.publish("crd_conn_quality", state)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- populate facility data from API_GET_FAC
|
||||||
|
---@param data table
|
||||||
|
---@return boolean valid
|
||||||
|
function iocontrol.record_facility_data(data)
|
||||||
|
local valid = true
|
||||||
|
|
||||||
|
local fac = io.facility
|
||||||
|
|
||||||
|
fac.all_sys_ok = data[1]
|
||||||
|
fac.rtu_count = data[2]
|
||||||
|
fac.radiation = data[3]
|
||||||
|
|
||||||
|
-- auto control
|
||||||
|
if type(data[4]) == "table" and #data[4] == 4 then
|
||||||
|
fac.auto_ready = data[4][1]
|
||||||
|
fac.auto_active = data[4][2]
|
||||||
|
fac.auto_ramping = data[4][3]
|
||||||
|
fac.auto_saturated = data[4][4]
|
||||||
|
end
|
||||||
|
|
||||||
|
-- waste
|
||||||
|
if type(data[5]) == "table" and #data[5] == 2 then
|
||||||
|
fac.auto_current_waste_product = data[5][1]
|
||||||
|
fac.auto_pu_fallback_active = data[5][2]
|
||||||
|
end
|
||||||
|
|
||||||
|
fac.num_tanks = data[6]
|
||||||
|
fac.has_imatrix = data[7]
|
||||||
|
fac.has_sps = data[8]
|
||||||
|
|
||||||
|
return valid
|
||||||
|
end
|
||||||
|
|
||||||
-- get the IO controller database
|
-- get the IO controller database
|
||||||
function iocontrol.get_db() return io end
|
function iocontrol.get_db() return io end
|
||||||
|
@ -8,6 +8,7 @@ local PROTOCOL = comms.PROTOCOL
|
|||||||
local DEVICE_TYPE = comms.DEVICE_TYPE
|
local DEVICE_TYPE = comms.DEVICE_TYPE
|
||||||
local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
||||||
local MGMT_TYPE = comms.MGMT_TYPE
|
local MGMT_TYPE = comms.MGMT_TYPE
|
||||||
|
local CRDN_TYPE = comms.CRDN_TYPE
|
||||||
|
|
||||||
local LINK_STATE = iocontrol.LINK_STATE
|
local LINK_STATE = iocontrol.LINK_STATE
|
||||||
|
|
||||||
@ -125,7 +126,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
|
|
||||||
-- attempt coordinator API connection establishment
|
-- attempt coordinator API connection establishment
|
||||||
local function _send_api_establish()
|
local function _send_api_establish()
|
||||||
_send_crd(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PKT })
|
_send_crd(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PKT, comms.api_version })
|
||||||
end
|
end
|
||||||
|
|
||||||
-- keep alive ack to supervisor
|
-- keep alive ack to supervisor
|
||||||
@ -246,6 +247,25 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
return pkt
|
return pkt
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param packet mgmt_frame|crdn_frame
|
||||||
|
---@param length integer
|
||||||
|
---@param max integer?
|
||||||
|
---@return boolean
|
||||||
|
local function _check_length(packet, length, max)
|
||||||
|
local ok = util.trinary(max == nil, packet.length == length, packet.length >= length and packet.length <= (max or 0))
|
||||||
|
if not ok then
|
||||||
|
local fmt = "[comms] RX_PACKET{r_chan=%d,proto=%d,type=%d}: packet length mismatch -> expect %d != actual %d"
|
||||||
|
log.debug(util.sprintf(fmt, packet.scada_frame.remote_channel(), packet.scada_frame.protocol(), packet.type))
|
||||||
|
end
|
||||||
|
return ok
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param packet mgmt_frame|crdn_frame
|
||||||
|
local function _fail_type(packet)
|
||||||
|
local fmt = "[comms] RX_PACKET{r_chan=%d,proto=%d,type=%d}: unrecognized packet type"
|
||||||
|
log.debug(util.sprintf(fmt, packet.scada_frame.remote_channel(), packet.scada_frame.protocol(), packet.type))
|
||||||
|
end
|
||||||
|
|
||||||
-- handle a packet
|
-- handle a packet
|
||||||
---@param packet mgmt_frame|crdn_frame|nil
|
---@param packet mgmt_frame|crdn_frame|nil
|
||||||
function public.handle_packet(packet)
|
function public.handle_packet(packet)
|
||||||
@ -268,7 +288,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
return
|
return
|
||||||
elseif self.api.linked and (src_addr ~= self.api.addr) then
|
elseif self.api.linked and (src_addr ~= self.api.addr) then
|
||||||
log.debug("received packet from unknown computer " .. src_addr .. " while linked (API expected " .. self.api.addr ..
|
log.debug("received packet from unknown computer " .. src_addr .. " while linked (API expected " .. self.api.addr ..
|
||||||
"); channel in use by another system?")
|
"); channel in use by another system?")
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
self.api.r_seq_num = packet.scada_frame.seq_num()
|
self.api.r_seq_num = packet.scada_frame.seq_num()
|
||||||
@ -277,12 +297,24 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
-- feed watchdog on valid sequence number
|
-- feed watchdog on valid sequence number
|
||||||
api_watchdog.feed()
|
api_watchdog.feed()
|
||||||
|
|
||||||
if protocol == PROTOCOL.SCADA_MGMT then
|
if protocol == PROTOCOL.SCADA_CRDN then
|
||||||
|
---@cast packet crdn_frame
|
||||||
|
if self.api.linked then
|
||||||
|
if packet.type == CRDN_TYPE.API_GET_FAC then
|
||||||
|
if _check_length(packet, 11) then
|
||||||
|
iocontrol.record_facility_data(packet.data)
|
||||||
|
end
|
||||||
|
elseif packet.type == CRDN_TYPE.API_GET_UNITS then
|
||||||
|
else _fail_type(packet) end
|
||||||
|
else
|
||||||
|
log.debug("discarding coordinator SCADA_CRDN packet before linked")
|
||||||
|
end
|
||||||
|
elseif protocol == PROTOCOL.SCADA_MGMT then
|
||||||
---@cast packet mgmt_frame
|
---@cast packet mgmt_frame
|
||||||
if self.api.linked then
|
if self.api.linked then
|
||||||
if packet.type == MGMT_TYPE.KEEP_ALIVE then
|
if packet.type == MGMT_TYPE.KEEP_ALIVE then
|
||||||
-- keep alive request received, echo back
|
-- keep alive request received, echo back
|
||||||
if packet.length == 1 then
|
if _check_length(packet, 1) then
|
||||||
local timestamp = packet.data[1]
|
local timestamp = packet.data[1]
|
||||||
local trip_time = util.time() - timestamp
|
local trip_time = util.time() - timestamp
|
||||||
|
|
||||||
@ -290,11 +322,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
log.warning("pocket coordinator KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)")
|
log.warning("pocket coordinator KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- log.debug("pocket coordinator RTT = " .. trip_time .. "ms")
|
-- log.debug("pocket coordinator TT = " .. trip_time .. "ms")
|
||||||
|
|
||||||
_send_api_keep_alive_ack(timestamp)
|
_send_api_keep_alive_ack(timestamp)
|
||||||
else
|
|
||||||
log.debug("coordinator SCADA keep alive packet length mismatch")
|
iocontrol.report_crd_tt(trip_time)
|
||||||
end
|
end
|
||||||
elseif packet.type == MGMT_TYPE.CLOSE then
|
elseif packet.type == MGMT_TYPE.CLOSE then
|
||||||
-- handle session close
|
-- handle session close
|
||||||
@ -303,24 +335,38 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
self.api.r_seq_num = nil
|
self.api.r_seq_num = nil
|
||||||
self.api.addr = comms.BROADCAST
|
self.api.addr = comms.BROADCAST
|
||||||
log.info("coordinator server connection closed by remote host")
|
log.info("coordinator server connection closed by remote host")
|
||||||
else
|
else _fail_type(packet) end
|
||||||
log.debug("received unknown SCADA_MGMT packet type " .. packet.type .. " from coordinator")
|
|
||||||
end
|
|
||||||
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
||||||
-- connection with coordinator established
|
-- connection with coordinator established
|
||||||
if packet.length == 1 then
|
if _check_length(packet, 1, 2) then
|
||||||
local est_ack = packet.data[1]
|
local est_ack = packet.data[1]
|
||||||
|
|
||||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||||
log.info("coordinator connection established")
|
if packet.length == 2 then
|
||||||
self.establish_delay_counter = 0
|
local fac_config = packet.data[2]
|
||||||
self.api.linked = true
|
|
||||||
self.api.addr = src_addr
|
|
||||||
|
|
||||||
if self.sv.linked then
|
if type(fac_config) == "table" and #fac_config == 2 then
|
||||||
iocontrol.report_link_state(LINK_STATE.LINKED)
|
-- get configuration
|
||||||
|
local conf = { num_units = fac_config[1], cooling = fac_config[2] }
|
||||||
|
|
||||||
|
---@todo unit options
|
||||||
|
iocontrol.init_fac(conf, 1)
|
||||||
|
|
||||||
|
log.info("coordinator connection established")
|
||||||
|
self.establish_delay_counter = 0
|
||||||
|
self.api.linked = true
|
||||||
|
self.api.addr = src_addr
|
||||||
|
|
||||||
|
if self.sv.linked then
|
||||||
|
iocontrol.report_link_state(LINK_STATE.LINKED)
|
||||||
|
else
|
||||||
|
iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
log.debug("invalid facility configuration table received from coordinator, establish failed")
|
||||||
|
end
|
||||||
else
|
else
|
||||||
iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY)
|
log.debug("received coordinator establish allow without facility configuration")
|
||||||
end
|
end
|
||||||
elseif est_ack == ESTABLISH_ACK.DENY then
|
elseif est_ack == ESTABLISH_ACK.DENY then
|
||||||
if self.api.last_est_ack ~= est_ack then
|
if self.api.last_est_ack ~= est_ack then
|
||||||
@ -334,13 +380,15 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
if self.api.last_est_ack ~= est_ack then
|
if self.api.last_est_ack ~= est_ack then
|
||||||
log.info("coordinator comms version mismatch")
|
log.info("coordinator comms version mismatch")
|
||||||
end
|
end
|
||||||
|
elseif est_ack == ESTABLISH_ACK.BAD_API_VERSION then
|
||||||
|
if self.api.last_est_ack ~= est_ack then
|
||||||
|
log.info("coordinator api version mismatch")
|
||||||
|
end
|
||||||
else
|
else
|
||||||
log.debug("coordinator SCADA_MGMT establish packet reply unsupported")
|
log.debug("coordinator SCADA_MGMT establish packet reply unsupported")
|
||||||
end
|
end
|
||||||
|
|
||||||
self.api.last_est_ack = est_ack
|
self.api.last_est_ack = est_ack
|
||||||
else
|
|
||||||
log.debug("coordinator SCADA_MGMT establish packet length mismatch")
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug("discarding coordinator non-link SCADA_MGMT packet before linked")
|
log.debug("discarding coordinator non-link SCADA_MGMT packet before linked")
|
||||||
@ -372,7 +420,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
if self.sv.linked then
|
if self.sv.linked then
|
||||||
if packet.type == MGMT_TYPE.KEEP_ALIVE then
|
if packet.type == MGMT_TYPE.KEEP_ALIVE then
|
||||||
-- keep alive request received, echo back
|
-- keep alive request received, echo back
|
||||||
if packet.length == 1 then
|
if _check_length(packet, 1) then
|
||||||
local timestamp = packet.data[1]
|
local timestamp = packet.data[1]
|
||||||
local trip_time = util.time() - timestamp
|
local trip_time = util.time() - timestamp
|
||||||
|
|
||||||
@ -380,11 +428,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
log.warning("pocket supervisor KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)")
|
log.warning("pocket supervisor KEEP_ALIVE trip time > 750ms (" .. trip_time .. "ms)")
|
||||||
end
|
end
|
||||||
|
|
||||||
-- log.debug("pocket supervisor RTT = " .. trip_time .. "ms")
|
-- log.debug("pocket supervisor TT = " .. trip_time .. "ms")
|
||||||
|
|
||||||
_send_sv_keep_alive_ack(timestamp)
|
_send_sv_keep_alive_ack(timestamp)
|
||||||
else
|
|
||||||
log.debug("supervisor SCADA keep alive packet length mismatch")
|
iocontrol.report_svr_tt(trip_time)
|
||||||
end
|
end
|
||||||
elseif packet.type == MGMT_TYPE.CLOSE then
|
elseif packet.type == MGMT_TYPE.CLOSE then
|
||||||
-- handle session close
|
-- handle session close
|
||||||
@ -394,12 +442,10 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
self.sv.addr = comms.BROADCAST
|
self.sv.addr = comms.BROADCAST
|
||||||
log.info("supervisor server connection closed by remote host")
|
log.info("supervisor server connection closed by remote host")
|
||||||
elseif packet.type == MGMT_TYPE.DIAG_TONE_GET then
|
elseif packet.type == MGMT_TYPE.DIAG_TONE_GET then
|
||||||
if packet.length == 8 then
|
if _check_length(packet, 8) then
|
||||||
for i = 1, #packet.data do
|
for i = 1, #packet.data do
|
||||||
diag.tone_test.tone_indicators[i].update(packet.data[i] == true)
|
diag.tone_test.tone_indicators[i].update(packet.data[i] == true)
|
||||||
end
|
end
|
||||||
else
|
|
||||||
log.debug("supervisor SCADA diag alarm states packet length mismatch")
|
|
||||||
end
|
end
|
||||||
elseif packet.type == MGMT_TYPE.DIAG_TONE_SET then
|
elseif packet.type == MGMT_TYPE.DIAG_TONE_SET then
|
||||||
if packet.length == 1 and packet.data[1] == false then
|
if packet.length == 1 and packet.data[1] == false then
|
||||||
@ -438,12 +484,10 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
else
|
else
|
||||||
log.debug("supervisor SCADA diag alarm set packet length/type mismatch")
|
log.debug("supervisor SCADA diag alarm set packet length/type mismatch")
|
||||||
end
|
end
|
||||||
else
|
else _fail_type(packet) end
|
||||||
log.debug("received unknown SCADA_MGMT packet type " .. packet.type .. " from supervisor")
|
|
||||||
end
|
|
||||||
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
elseif packet.type == MGMT_TYPE.ESTABLISH then
|
||||||
-- connection with supervisor established
|
-- connection with supervisor established
|
||||||
if packet.length == 1 then
|
if _check_length(packet, 1) then
|
||||||
local est_ack = packet.data[1]
|
local est_ack = packet.data[1]
|
||||||
|
|
||||||
if est_ack == ESTABLISH_ACK.ALLOW then
|
if est_ack == ESTABLISH_ACK.ALLOW then
|
||||||
@ -474,15 +518,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
end
|
end
|
||||||
|
|
||||||
self.sv.last_est_ack = est_ack
|
self.sv.last_est_ack = est_ack
|
||||||
else
|
|
||||||
log.debug("supervisor SCADA_MGMT establish packet length mismatch")
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
log.debug("discarding supervisor non-link SCADA_MGMT packet before linked")
|
log.debug("discarding supervisor non-link SCADA_MGMT packet before linked")
|
||||||
end
|
end
|
||||||
else
|
else _fail_type(packet) end
|
||||||
log.debug("illegal packet type " .. protocol .. " from supervisor", true)
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
log.debug("received packet from unconfigured channel " .. r_chan, true)
|
log.debug("received packet from unconfigured channel " .. r_chan, true)
|
||||||
end
|
end
|
||||||
@ -500,5 +540,4 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog)
|
|||||||
return public
|
return public
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
return pocket
|
return pocket
|
||||||
|
@ -18,7 +18,7 @@ local iocontrol = require("pocket.iocontrol")
|
|||||||
local pocket = require("pocket.pocket")
|
local pocket = require("pocket.pocket")
|
||||||
local renderer = require("pocket.renderer")
|
local renderer = require("pocket.renderer")
|
||||||
|
|
||||||
local POCKET_VERSION = "v0.7.3-alpha"
|
local POCKET_VERSION = "v0.8.0-alpha"
|
||||||
|
|
||||||
local println = util.println
|
local println = util.println
|
||||||
local println_ts = util.println_ts
|
local println_ts = util.println_ts
|
||||||
@ -68,6 +68,9 @@ local function main()
|
|||||||
-- mount connected devices
|
-- mount connected devices
|
||||||
ppm.mount_all()
|
ppm.mount_all()
|
||||||
|
|
||||||
|
-- record version for GUI
|
||||||
|
iocontrol.get_db().version = POCKET_VERSION
|
||||||
|
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
-- setup communications & clocks
|
-- setup communications & clocks
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
@ -131,7 +134,7 @@ local function main()
|
|||||||
-- start connection watchdogs
|
-- start connection watchdogs
|
||||||
conn_wd.sv.feed()
|
conn_wd.sv.feed()
|
||||||
conn_wd.api.feed()
|
conn_wd.api.feed()
|
||||||
log.debug("startup> conn watchdog started")
|
log.debug("startup> conn watchdogs started")
|
||||||
|
|
||||||
local io_db = iocontrol.get_db()
|
local io_db = iocontrol.get_db()
|
||||||
local nav = io_db.nav
|
local nav = io_db.nav
|
||||||
@ -149,11 +152,8 @@ local function main()
|
|||||||
pocket_comms.link_update()
|
pocket_comms.link_update()
|
||||||
|
|
||||||
-- update any tasks for the active page
|
-- update any tasks for the active page
|
||||||
if (type(nav.tasks[nav.page]) == "table") then
|
local page_tasks = nav.get_current_page().tasks
|
||||||
for i = 1, #nav.tasks[nav.page] do
|
for i = 1, #page_tasks do page_tasks[i]() end
|
||||||
nav.tasks[nav.page][i]()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
loop_clock.start()
|
loop_clock.start()
|
||||||
elseif conn_wd.sv.is_timer(param1) then
|
elseif conn_wd.sv.is_timer(param1) then
|
||||||
|
@ -1,58 +1,39 @@
|
|||||||
|
--
|
||||||
|
-- Diagnostic Apps
|
||||||
|
--
|
||||||
|
|
||||||
local iocontrol = require("pocket.iocontrol")
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
|
|
||||||
local Div = require("graphics.elements.div")
|
local Div = require("graphics.elements.div")
|
||||||
local MultiPane = require("graphics.elements.multipane")
|
|
||||||
local TextBox = require("graphics.elements.textbox")
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
local IndicatorLight = require("graphics.elements.indicators.light")
|
local IndicatorLight = require("graphics.elements.indicators.light")
|
||||||
|
|
||||||
local App = require("graphics.elements.controls.app")
|
|
||||||
local Checkbox = require("graphics.elements.controls.checkbox")
|
local Checkbox = require("graphics.elements.controls.checkbox")
|
||||||
local PushButton = require("graphics.elements.controls.push_button")
|
local PushButton = require("graphics.elements.controls.push_button")
|
||||||
local SwitchButton = require("graphics.elements.controls.switch_button")
|
local SwitchButton = require("graphics.elements.controls.switch_button")
|
||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
local NAV_PAGE = iocontrol.NAV_PAGE
|
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
local ALIGN = core.ALIGN
|
||||||
|
|
||||||
-- new diagnostics page view
|
-- create diagnostic app pages
|
||||||
---@param root graphics_element parent
|
---@param root graphics_element parent
|
||||||
local function new_view(root)
|
local function create_pages(root)
|
||||||
local db = iocontrol.get_db()
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
local main = Div{parent=root,x=1,y=1}
|
|
||||||
|
|
||||||
local diag_home = Div{parent=main,x=1,y=1}
|
|
||||||
|
|
||||||
TextBox{parent=diag_home,text="Diagnostic Apps",x=1,y=2,height=1,alignment=ALIGN.CENTER}
|
|
||||||
|
|
||||||
local alarm_test = Div{parent=main,x=1,y=1}
|
|
||||||
|
|
||||||
local panes = { diag_home, alarm_test }
|
|
||||||
|
|
||||||
local page_pane = MultiPane{parent=main,x=1,y=1,panes=panes}
|
|
||||||
|
|
||||||
local function navigate_diag()
|
|
||||||
page_pane.set_value(1)
|
|
||||||
db.nav.page = NAV_PAGE.DIAG
|
|
||||||
db.nav.sub_pages[NAV_PAGE.DIAG] = NAV_PAGE.DIAG
|
|
||||||
end
|
|
||||||
|
|
||||||
local function navigate_alarm()
|
|
||||||
page_pane.set_value(2)
|
|
||||||
db.nav.page = NAV_PAGE.D_ALARMS
|
|
||||||
db.nav.sub_pages[NAV_PAGE.DIAG] = NAV_PAGE.D_ALARMS
|
|
||||||
end
|
|
||||||
|
|
||||||
------------------------
|
------------------------
|
||||||
-- Alarm Testing Page --
|
-- Alarm Testing Page --
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
db.nav.register_task(NAV_PAGE.D_ALARMS, db.diag.tone_test.get_tone_states)
|
local alarm_test = Div{parent=root,x=1,y=1}
|
||||||
|
|
||||||
|
local alarm_app = db.nav.register_app(iocontrol.APP_ID.ALARMS, alarm_test)
|
||||||
|
|
||||||
|
local page = alarm_app.new_page(nil, function () end)
|
||||||
|
page.tasks = { db.diag.tone_test.get_tone_states }
|
||||||
|
|
||||||
local ttest = db.diag.tone_test
|
local ttest = db.diag.tone_test
|
||||||
|
|
||||||
@ -67,8 +48,6 @@ local function new_view(root)
|
|||||||
|
|
||||||
ttest.ready_warn = TextBox{parent=audio,y=2,text="",height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.yellow,colors.black)}
|
ttest.ready_warn = TextBox{parent=audio,y=2,text="",height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.yellow,colors.black)}
|
||||||
|
|
||||||
PushButton{parent=audio,x=13,y=18,text="\x11 BACK",min_width=8,fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=c_wht_gray,callback=navigate_diag}
|
|
||||||
|
|
||||||
local tones = Div{parent=audio,x=2,y=3,height=10,width=8,fg_bg=cpair(colors.black,colors.yellow)}
|
local tones = Div{parent=audio,x=2,y=3,height=10,width=8,fg_bg=cpair(colors.black,colors.yellow)}
|
||||||
|
|
||||||
TextBox{parent=tones,text="Tones",height=1,alignment=ALIGN.CENTER,fg_bg=audio.get_fg_bg()}
|
TextBox{parent=tones,text="Tones",height=1,alignment=ALIGN.CENTER,fg_bg=audio.get_fg_bg()}
|
||||||
@ -132,16 +111,6 @@ local function new_view(root)
|
|||||||
local t_8 = IndicatorLight{parent=states,x=6,label="8",colors=c_blue_gray}
|
local t_8 = IndicatorLight{parent=states,x=6,label="8",colors=c_blue_gray}
|
||||||
|
|
||||||
ttest.tone_indicators = { t_1, t_2, t_3, t_4, t_5, t_6, t_7, t_8 }
|
ttest.tone_indicators = { t_1, t_2, t_3, t_4, t_5, t_6, t_7, t_8 }
|
||||||
|
|
||||||
--------------
|
|
||||||
-- App List --
|
|
||||||
--------------
|
|
||||||
|
|
||||||
App{parent=diag_home,x=3,y=4,text="\x0f",title="Alarm",callback=navigate_alarm,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.white,colors.gray)}
|
|
||||||
App{parent=diag_home,x=10,y=4,text="\x1e",title="LoopT",callback=function()end,app_fg_bg=cpair(colors.black,colors.cyan)}
|
|
||||||
App{parent=diag_home,x=17,y=4,text="@",title="Comps",callback=function()end,app_fg_bg=cpair(colors.black,colors.orange)}
|
|
||||||
|
|
||||||
return main
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return new_view
|
return create_pages
|
24
pocket/ui/apps/dummy_app.lua
Normal file
24
pocket/ui/apps/dummy_app.lua
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
--
|
||||||
|
-- Placeholder App
|
||||||
|
--
|
||||||
|
|
||||||
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
|
||||||
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local Div = require("graphics.elements.div")
|
||||||
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
|
-- create placeholder app page
|
||||||
|
---@param root graphics_element parent
|
||||||
|
local function create_pages(root)
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
|
local main = Div{parent=root,x=1,y=1}
|
||||||
|
|
||||||
|
db.nav.register_app(iocontrol.APP_ID.DUMMY, main).new_page(nil, function () end)
|
||||||
|
|
||||||
|
TextBox{parent=main,text="This app is not implemented yet.",x=1,y=2,alignment=core.ALIGN.CENTER}
|
||||||
|
end
|
||||||
|
|
||||||
|
return create_pages
|
102
pocket/ui/apps/sys_apps.lua
Normal file
102
pocket/ui/apps/sys_apps.lua
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
--
|
||||||
|
-- System Apps
|
||||||
|
--
|
||||||
|
|
||||||
|
local comms = require("scada-common.comms")
|
||||||
|
local lockbox = require("lockbox")
|
||||||
|
local util = require("scada-common.util")
|
||||||
|
|
||||||
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
|
||||||
|
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 PushButton = require("graphics.elements.controls.push_button")
|
||||||
|
|
||||||
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
|
|
||||||
|
-- create system app pages
|
||||||
|
---@param root graphics_element parent
|
||||||
|
local function create_pages(root)
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
|
----------------
|
||||||
|
-- About Page --
|
||||||
|
----------------
|
||||||
|
|
||||||
|
local about_root = Div{parent=root,x=1,y=1}
|
||||||
|
|
||||||
|
local about_app = db.nav.register_app(iocontrol.APP_ID.ABOUT, about_root)
|
||||||
|
|
||||||
|
local about_page = about_app.new_page(nil, 1)
|
||||||
|
local fw_page = about_app.new_page(about_page, 2)
|
||||||
|
local hw_page = about_app.new_page(about_page, 3)
|
||||||
|
|
||||||
|
local about = Div{parent=about_root,x=1,y=2}
|
||||||
|
|
||||||
|
TextBox{parent=about,y=1,text="System Information",height=1,alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
local btn_fg_bg = cpair(colors.lightBlue, colors.black)
|
||||||
|
local btn_active = cpair(colors.white, colors.black)
|
||||||
|
local label = cpair(colors.lightGray, colors.black)
|
||||||
|
|
||||||
|
PushButton{parent=about,x=2,y=3,text="Firmware >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fw_page.nav_to}
|
||||||
|
PushButton{parent=about,x=2,y=4,text="Host Details >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=hw_page.nav_to}
|
||||||
|
|
||||||
|
local fw_div = Div{parent=about_root,x=1,y=2}
|
||||||
|
TextBox{parent=fw_div,y=1,text="Firmware Versions",height=1,alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
PushButton{parent=fw_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
|
||||||
|
|
||||||
|
local fw_list_box = ListBox{parent=fw_div,x=1,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)}
|
||||||
|
|
||||||
|
local fw_list = Div{parent=fw_list_box,x=1,y=2,height=18}
|
||||||
|
|
||||||
|
TextBox{parent=fw_list,x=2,text="Pocket Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
|
||||||
|
TextBox{parent=fw_list,x=2,text=db.version,height=1,alignment=ALIGN.LEFT}
|
||||||
|
|
||||||
|
fw_list.line_break()
|
||||||
|
TextBox{parent=fw_list,x=2,text="Comms Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
|
||||||
|
TextBox{parent=fw_list,x=2,text=comms.version,height=1,alignment=ALIGN.LEFT}
|
||||||
|
|
||||||
|
fw_list.line_break()
|
||||||
|
TextBox{parent=fw_list,x=2,text="API Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
|
||||||
|
TextBox{parent=fw_list,x=2,text=comms.api_version,height=1,alignment=ALIGN.LEFT}
|
||||||
|
|
||||||
|
fw_list.line_break()
|
||||||
|
TextBox{parent=fw_list,x=2,text="Common Lib Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
|
||||||
|
TextBox{parent=fw_list,x=2,text=util.version,height=1,alignment=ALIGN.LEFT}
|
||||||
|
|
||||||
|
fw_list.line_break()
|
||||||
|
TextBox{parent=fw_list,x=2,text="Graphics Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
|
||||||
|
TextBox{parent=fw_list,x=2,text=core.version,height=1,alignment=ALIGN.LEFT}
|
||||||
|
|
||||||
|
fw_list.line_break()
|
||||||
|
TextBox{parent=fw_list,x=2,text="Lockbox Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
|
||||||
|
TextBox{parent=fw_list,x=2,text=lockbox.version,height=1,alignment=ALIGN.LEFT}
|
||||||
|
|
||||||
|
local hw_div = Div{parent=about_root,x=1,y=2}
|
||||||
|
TextBox{parent=hw_div,y=1,text="Host Versions",height=1,alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
PushButton{parent=hw_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to}
|
||||||
|
|
||||||
|
hw_div.line_break()
|
||||||
|
TextBox{parent=hw_div,x=2,text="Lua Version",height=1,alignment=ALIGN.LEFT,fg_bg=label}
|
||||||
|
TextBox{parent=hw_div,x=2,text=_VERSION,height=1,alignment=ALIGN.LEFT}
|
||||||
|
|
||||||
|
hw_div.line_break()
|
||||||
|
TextBox{parent=hw_div,x=2,text="Environment",height=1,alignment=ALIGN.LEFT,fg_bg=label}
|
||||||
|
TextBox{parent=hw_div,x=2,text=_HOST,height=6,alignment=ALIGN.LEFT}
|
||||||
|
|
||||||
|
local root_pane = MultiPane{parent=about_root,x=1,y=1,panes={about,fw_div,hw_div}}
|
||||||
|
|
||||||
|
about_app.set_root_pane(root_pane)
|
||||||
|
end
|
||||||
|
|
||||||
|
return create_pages
|
@ -4,27 +4,29 @@
|
|||||||
|
|
||||||
local iocontrol = require("pocket.iocontrol")
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
|
||||||
local style = require("pocket.ui.style")
|
local diag_apps = require("pocket.ui.apps.diag_apps")
|
||||||
|
local dummy_app = require("pocket.ui.apps.dummy_app")
|
||||||
|
local sys_apps = require("pocket.ui.apps.sys_apps")
|
||||||
|
|
||||||
local conn_waiting = require("pocket.ui.components.conn_waiting")
|
local conn_waiting = require("pocket.ui.components.conn_waiting")
|
||||||
|
|
||||||
local boiler_page = require("pocket.ui.pages.boiler_page")
|
|
||||||
local diag_page = require("pocket.ui.pages.diag_page")
|
|
||||||
local home_page = require("pocket.ui.pages.home_page")
|
local home_page = require("pocket.ui.pages.home_page")
|
||||||
local reactor_page = require("pocket.ui.pages.reactor_page")
|
|
||||||
local turbine_page = require("pocket.ui.pages.turbine_page")
|
|
||||||
local unit_page = require("pocket.ui.pages.unit_page")
|
local unit_page = require("pocket.ui.pages.unit_page")
|
||||||
|
|
||||||
|
local style = require("pocket.ui.style")
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local core = require("graphics.core")
|
||||||
|
|
||||||
local Div = require("graphics.elements.div")
|
local Div = require("graphics.elements.div")
|
||||||
local MultiPane = require("graphics.elements.multipane")
|
local MultiPane = require("graphics.elements.multipane")
|
||||||
local TextBox = require("graphics.elements.textbox")
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
|
local PushButton = require("graphics.elements.controls.push_button")
|
||||||
local Sidebar = require("graphics.elements.controls.sidebar")
|
local Sidebar = require("graphics.elements.controls.sidebar")
|
||||||
|
|
||||||
|
local SignalBar = require("graphics.elements.indicators.signal")
|
||||||
|
|
||||||
local LINK_STATE = iocontrol.LINK_STATE
|
local LINK_STATE = iocontrol.LINK_STATE
|
||||||
local NAV_PAGE = iocontrol.NAV_PAGE
|
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
local ALIGN = core.ALIGN
|
||||||
|
|
||||||
@ -33,26 +35,27 @@ local cpair = core.cpair
|
|||||||
-- create new main view
|
-- create new main view
|
||||||
---@param main graphics_element main displaybox
|
---@param main graphics_element main displaybox
|
||||||
local function init(main)
|
local function init(main)
|
||||||
local nav = iocontrol.get_db().nav
|
local db = iocontrol.get_db()
|
||||||
local ps = iocontrol.get_db().ps
|
|
||||||
|
|
||||||
-- window header message
|
-- window header message
|
||||||
TextBox{parent=main,y=1,text="",alignment=ALIGN.LEFT,height=1,fg_bg=style.header}
|
TextBox{parent=main,y=1,text="DEV ALPHA APP S C ",alignment=ALIGN.LEFT,height=1,fg_bg=style.header}
|
||||||
|
local svr_conn = SignalBar{parent=main,y=1,x=22,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)}
|
||||||
|
local crd_conn = SignalBar{parent=main,y=1,x=26,compact=true,colors_low_med=cpair(colors.red,colors.yellow),disconnect_color=colors.lightGray,fg_bg=cpair(colors.green,colors.gray)}
|
||||||
|
|
||||||
--
|
db.ps.subscribe("svr_conn_quality", svr_conn.set_value)
|
||||||
-- root panel panes (connection screens + main screen)
|
db.ps.subscribe("crd_conn_quality", crd_conn.set_value)
|
||||||
--
|
|
||||||
|
--#region root panel panes (connection screens + main screen)
|
||||||
|
|
||||||
local root_pane_div = Div{parent=main,x=1,y=2}
|
local root_pane_div = Div{parent=main,x=1,y=2}
|
||||||
|
|
||||||
local conn_sv_wait = conn_waiting(root_pane_div, 6, false)
|
local conn_sv_wait = conn_waiting(root_pane_div, 6, false)
|
||||||
local conn_api_wait = conn_waiting(root_pane_div, 6, true)
|
local conn_api_wait = conn_waiting(root_pane_div, 6, true)
|
||||||
local main_pane = Div{parent=main,x=1,y=2}
|
local main_pane = Div{parent=main,x=1,y=2}
|
||||||
local root_panes = { conn_sv_wait, conn_api_wait, main_pane }
|
|
||||||
|
|
||||||
local root_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes=root_panes}
|
local root_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={conn_sv_wait,conn_api_wait,main_pane}}
|
||||||
|
|
||||||
root_pane.register(ps, "link_state", function (state)
|
root_pane.register(db.ps, "link_state", function (state)
|
||||||
if state == LINK_STATE.UNLINKED or state == LINK_STATE.API_LINK_ONLY then
|
if state == LINK_STATE.UNLINKED or state == LINK_STATE.API_LINK_ONLY then
|
||||||
root_pane.set_value(1)
|
root_pane.set_value(1)
|
||||||
elseif state == LINK_STATE.SV_LINK_ONLY then
|
elseif state == LINK_STATE.SV_LINK_ONLY then
|
||||||
@ -62,62 +65,33 @@ local function init(main)
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
--
|
--#endregion
|
||||||
-- main page panel panes & sidebar
|
|
||||||
--
|
--#region main page panel panes & sidebar
|
||||||
|
|
||||||
local page_div = Div{parent=main_pane,x=4,y=1}
|
local page_div = Div{parent=main_pane,x=4,y=1}
|
||||||
|
|
||||||
local sidebar_tabs = {
|
local sidebar_tabs = {
|
||||||
{
|
{ char = "#", color = cpair(colors.black, colors.green) }
|
||||||
char = "#",
|
|
||||||
color = cpair(colors.black,colors.green)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
char = "U",
|
|
||||||
color = cpair(colors.black,colors.yellow)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
char = "R",
|
|
||||||
color = cpair(colors.black,colors.cyan)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
char = "B",
|
|
||||||
color = cpair(colors.black,colors.lightGray)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
char = "T",
|
|
||||||
color = cpair(colors.black,colors.white)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
char = "D",
|
|
||||||
color = cpair(colors.black,colors.orange)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
local panes = { home_page(page_div), unit_page(page_div), reactor_page(page_div), boiler_page(page_div), turbine_page(page_div), diag_page(page_div) }
|
home_page(page_div)
|
||||||
|
unit_page(page_div)
|
||||||
|
|
||||||
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
|
diag_apps(page_div)
|
||||||
|
sys_apps(page_div)
|
||||||
|
dummy_app(page_div)
|
||||||
|
|
||||||
local function navigate_sidebar(page)
|
assert(#db.nav.get_containers() == iocontrol.APP_ID.NUM_APPS, "app IDs were not sequential or some apps weren't registered")
|
||||||
if page == 1 then
|
|
||||||
nav.page = nav.sub_pages[NAV_PAGE.HOME]
|
|
||||||
elseif page == 2 then
|
|
||||||
nav.page = nav.sub_pages[NAV_PAGE.UNITS]
|
|
||||||
elseif page == 3 then
|
|
||||||
nav.page = nav.sub_pages[NAV_PAGE.REACTORS]
|
|
||||||
elseif page == 4 then
|
|
||||||
nav.page = nav.sub_pages[NAV_PAGE.BOILERS]
|
|
||||||
elseif page == 5 then
|
|
||||||
nav.page = nav.sub_pages[NAV_PAGE.TURBINES]
|
|
||||||
elseif page == 6 then
|
|
||||||
nav.page = nav.sub_pages[NAV_PAGE.DIAG]
|
|
||||||
end
|
|
||||||
|
|
||||||
page_pane.set_value(page)
|
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=db.nav.get_containers()}
|
||||||
end
|
db.nav.set_pane(page_pane)
|
||||||
|
|
||||||
Sidebar{parent=main_pane,x=1,y=1,tabs=sidebar_tabs,fg_bg=cpair(colors.white,colors.gray),callback=navigate_sidebar}
|
Sidebar{parent=main_pane,x=1,y=1,tabs=sidebar_tabs,fg_bg=cpair(colors.white,colors.gray),callback=db.nav.open_app}
|
||||||
|
|
||||||
|
PushButton{parent=main_pane,x=1,y=19,text="\x1b",min_width=3,fg_bg=cpair(colors.white,colors.gray),active_fg_bg=cpair(colors.gray,colors.black),callback=db.nav.nav_up}
|
||||||
|
|
||||||
|
--#endregion
|
||||||
end
|
end
|
||||||
|
|
||||||
return init
|
return init
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
-- local style = require("pocket.ui.style")
|
|
||||||
|
|
||||||
local core = require("graphics.core")
|
|
||||||
|
|
||||||
local Div = require("graphics.elements.div")
|
|
||||||
local TextBox = require("graphics.elements.textbox")
|
|
||||||
|
|
||||||
-- local cpair = core.cpair
|
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
|
||||||
|
|
||||||
-- new boiler page view
|
|
||||||
---@param root graphics_element parent
|
|
||||||
local function new_view(root)
|
|
||||||
local main = Div{parent=root,x=1,y=1}
|
|
||||||
|
|
||||||
TextBox{parent=main,text="BOILERS",x=1,y=1,height=1,alignment=ALIGN.CENTER}
|
|
||||||
|
|
||||||
return main
|
|
||||||
end
|
|
||||||
|
|
||||||
return new_view
|
|
@ -1,21 +1,59 @@
|
|||||||
local core = require("graphics.core")
|
--
|
||||||
|
-- Main Home Page
|
||||||
|
--
|
||||||
|
|
||||||
local Div = require("graphics.elements.div")
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
|
||||||
local App = require("graphics.elements.controls.app")
|
local core = require("graphics.core")
|
||||||
|
|
||||||
|
local AppMultiPane = require("graphics.elements.appmultipane")
|
||||||
|
local Div = require("graphics.elements.div")
|
||||||
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
|
local App = require("graphics.elements.controls.app")
|
||||||
|
|
||||||
local cpair = core.cpair
|
local cpair = core.cpair
|
||||||
|
|
||||||
|
local APP_ID = iocontrol.APP_ID
|
||||||
|
|
||||||
|
local ALIGN = core.ALIGN
|
||||||
|
|
||||||
-- new home page view
|
-- new home page view
|
||||||
---@param root graphics_element parent
|
---@param root graphics_element parent
|
||||||
local function new_view(root)
|
local function new_view(root)
|
||||||
local main = Div{parent=root,x=1,y=1}
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
App{parent=main,x=3,y=2,text="\x17",title="PRC",callback=function()end,app_fg_bg=cpair(colors.black,colors.purple)}
|
local main = Div{parent=root,x=1,y=1,height=19}
|
||||||
App{parent=main,x=10,y=2,text="\x15",title="CTL",callback=function()end,app_fg_bg=cpair(colors.black,colors.green)}
|
|
||||||
App{parent=main,x=17,y=2,text="\x08",title="DEV",callback=function()end,app_fg_bg=cpair(colors.black,colors.lightGray)}
|
local app = db.nav.register_app(iocontrol.APP_ID.ROOT, main)
|
||||||
App{parent=main,x=3,y=7,text="\x7f",title="Waste",callback=function()end,app_fg_bg=cpair(colors.black,colors.brown)}
|
|
||||||
App{parent=main,x=10,y=7,text="\xb6",title="Guide",callback=function()end,app_fg_bg=cpair(colors.black,colors.cyan)}
|
local apps_1 = Div{parent=main,x=1,y=1,height=15}
|
||||||
|
local apps_2 = Div{parent=main,x=1,y=1,height=15}
|
||||||
|
|
||||||
|
local panes = { apps_1, apps_2 }
|
||||||
|
|
||||||
|
local app_pane = AppMultiPane{parent=main,x=1,y=1,height=18,panes=panes,active_color=colors.lightGray,nav_colors=cpair(colors.lightGray,colors.gray),scroll_nav=true,drag_nav=true,callback=app.switcher}
|
||||||
|
|
||||||
|
app.set_root_pane(app_pane)
|
||||||
|
app.new_page(app.new_page(nil, 1), 2)
|
||||||
|
|
||||||
|
local function open(id) db.nav.open_app(id) end
|
||||||
|
|
||||||
|
local active_fg_bg = cpair(colors.white,colors.gray)
|
||||||
|
|
||||||
|
App{parent=apps_1,x=3,y=2,text="U",title="Units",callback=function()open(APP_ID.UNITS)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg}
|
||||||
|
App{parent=apps_1,x=10,y=2,text="\x17",title="PRC",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.purple),active_fg_bg=active_fg_bg}
|
||||||
|
App{parent=apps_1,x=17,y=2,text="\x15",title="CTL",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.green),active_fg_bg=active_fg_bg}
|
||||||
|
App{parent=apps_1,x=3,y=7,text="\x08",title="DEV",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=active_fg_bg}
|
||||||
|
App{parent=apps_1,x=10,y=7,text="\x7f",title="Waste",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.brown),active_fg_bg=active_fg_bg}
|
||||||
|
App{parent=apps_1,x=17,y=7,text="\xb6",title="Guide",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg}
|
||||||
|
App{parent=apps_1,x=3,y=12,text="?",title="About",callback=function()open(APP_ID.ABOUT)end,app_fg_bg=cpair(colors.black,colors.white),active_fg_bg=active_fg_bg}
|
||||||
|
|
||||||
|
TextBox{parent=apps_2,text="Diagnostic Apps",x=1,y=2,height=1,alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
App{parent=apps_2,x=3,y=4,text="\x0f",title="Alarm",callback=function()open(APP_ID.ALARMS)end,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=active_fg_bg}
|
||||||
|
App{parent=apps_2,x=10,y=4,text="\x1e",title="LoopT",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg}
|
||||||
|
App{parent=apps_2,x=17,y=4,text="@",title="Comps",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg}
|
||||||
|
|
||||||
return main
|
return main
|
||||||
end
|
end
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
-- local style = require("pocket.ui.style")
|
|
||||||
|
|
||||||
local core = require("graphics.core")
|
|
||||||
|
|
||||||
local Div = require("graphics.elements.div")
|
|
||||||
local TextBox = require("graphics.elements.textbox")
|
|
||||||
|
|
||||||
-- local cpair = core.cpair
|
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
|
||||||
|
|
||||||
-- new reactor page view
|
|
||||||
---@param root graphics_element parent
|
|
||||||
local function new_view(root)
|
|
||||||
local main = Div{parent=root,x=1,y=1}
|
|
||||||
|
|
||||||
TextBox{parent=main,text="REACTOR",x=1,y=1,height=1,alignment=ALIGN.CENTER}
|
|
||||||
|
|
||||||
return main
|
|
||||||
end
|
|
||||||
|
|
||||||
return new_view
|
|
@ -1,22 +0,0 @@
|
|||||||
-- local style = require("pocket.ui.style")
|
|
||||||
|
|
||||||
local core = require("graphics.core")
|
|
||||||
|
|
||||||
local Div = require("graphics.elements.div")
|
|
||||||
local TextBox = require("graphics.elements.textbox")
|
|
||||||
|
|
||||||
-- local cpair = core.cpair
|
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
|
||||||
|
|
||||||
-- new turbine page view
|
|
||||||
---@param root graphics_element parent
|
|
||||||
local function new_view(root)
|
|
||||||
local main = Div{parent=root,x=1,y=1}
|
|
||||||
|
|
||||||
TextBox{parent=main,text="TURBINES",x=1,y=1,height=1,alignment=ALIGN.CENTER}
|
|
||||||
|
|
||||||
return main
|
|
||||||
end
|
|
||||||
|
|
||||||
return new_view
|
|
@ -1,20 +1,29 @@
|
|||||||
-- local style = require("pocket.ui.style")
|
--
|
||||||
|
-- Unit Overview Page
|
||||||
|
--
|
||||||
|
|
||||||
local core = require("graphics.core")
|
local iocontrol = require("pocket.iocontrol")
|
||||||
|
|
||||||
local Div = require("graphics.elements.div")
|
local core = require("graphics.core")
|
||||||
local TextBox = require("graphics.elements.textbox")
|
|
||||||
|
|
||||||
-- local cpair = core.cpair
|
local Div = require("graphics.elements.div")
|
||||||
|
local TextBox = require("graphics.elements.textbox")
|
||||||
|
|
||||||
local ALIGN = core.ALIGN
|
local ALIGN = core.ALIGN
|
||||||
|
|
||||||
-- new unit page view
|
-- new unit page view
|
||||||
---@param root graphics_element parent
|
---@param root graphics_element parent
|
||||||
local function new_view(root)
|
local function new_view(root)
|
||||||
|
local db = iocontrol.get_db()
|
||||||
|
|
||||||
local main = Div{parent=root,x=1,y=1}
|
local main = Div{parent=root,x=1,y=1}
|
||||||
|
|
||||||
TextBox{parent=main,text="UNITS",x=1,y=1,height=1,alignment=ALIGN.CENTER}
|
local app = db.nav.register_app(iocontrol.APP_ID.UNITS, main)
|
||||||
|
app.new_page(nil, function () end)
|
||||||
|
|
||||||
|
TextBox{parent=main,y=2,text="UNITS",height=1,alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
|
TextBox{parent=main,y=4,text="work in progress",height=1,alignment=ALIGN.CENTER}
|
||||||
|
|
||||||
return main
|
return main
|
||||||
end
|
end
|
||||||
|
@ -16,8 +16,9 @@ local max_distance = nil
|
|||||||
---@class comms
|
---@class comms
|
||||||
local comms = {}
|
local comms = {}
|
||||||
|
|
||||||
-- protocol/data version (protocol/data independent changes tracked by util.lua version)
|
-- protocol/data versions (protocol/data independent changes tracked by util.lua version)
|
||||||
comms.version = "2.4.5"
|
comms.version = "2.5.0"
|
||||||
|
comms.api_version = "0.0.1"
|
||||||
|
|
||||||
---@enum PROTOCOL
|
---@enum PROTOCOL
|
||||||
local PROTOCOL = {
|
local PROTOCOL = {
|
||||||
@ -64,7 +65,9 @@ local CRDN_TYPE = {
|
|||||||
FAC_CMD = 3, -- faility command
|
FAC_CMD = 3, -- faility command
|
||||||
UNIT_BUILDS = 4, -- build of each reactor unit (reactor + RTUs)
|
UNIT_BUILDS = 4, -- build of each reactor unit (reactor + RTUs)
|
||||||
UNIT_STATUSES = 5, -- state of each of the reactor units
|
UNIT_STATUSES = 5, -- state of each of the reactor units
|
||||||
UNIT_CMD = 6 -- command a reactor unit
|
UNIT_CMD = 6, -- command a reactor unit
|
||||||
|
API_GET_FAC = 7, -- API: get all the facility data
|
||||||
|
API_GET_UNITS = 8 -- API: get all the reactor unit data
|
||||||
}
|
}
|
||||||
|
|
||||||
---@enum ESTABLISH_ACK
|
---@enum ESTABLISH_ACK
|
||||||
@ -72,7 +75,8 @@ local ESTABLISH_ACK = {
|
|||||||
ALLOW = 0, -- link approved
|
ALLOW = 0, -- link approved
|
||||||
DENY = 1, -- link denied
|
DENY = 1, -- link denied
|
||||||
COLLISION = 2, -- link denied due to existing active link
|
COLLISION = 2, -- link denied due to existing active link
|
||||||
BAD_VERSION = 3 -- link denied due to comms version mismatch
|
BAD_VERSION = 3, -- link denied due to comms version mismatch
|
||||||
|
BAD_API_VERSION = 4 -- link denied due to api version mismatch
|
||||||
}
|
}
|
||||||
|
|
||||||
---@enum DEVICE_TYPE device types for establish messages
|
---@enum DEVICE_TYPE device types for establish messages
|
||||||
|
Loading…
Reference in New Issue
Block a user