From d9efd5b8d2851f353916218dcad39c2e6fe5c855 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 20 Apr 2024 16:32:18 -0400 Subject: [PATCH 01/87] #412 updates to RSIO for induction matrix low, high, and analog charge level --- rtu/configure.lua | 75 ++++++------ rtu/startup.lua | 2 +- scada-common/constants.lua | 12 ++ scada-common/rsio.lua | 214 ++++++++++++++++------------------- scada-common/util.lua | 2 +- supervisor/facility.lua | 14 ++- supervisor/session/rsctl.lua | 25 +++- supervisor/startup.lua | 2 +- test/rstest.lua | 79 ++++++------- 9 files changed, 226 insertions(+), 199 deletions(-) diff --git a/rtu/configure.lua b/rtu/configure.lua index 7758dc2..5dc440a 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -2,6 +2,7 @@ -- Configuration GUI -- +local constants = require("scada-common.constants") local log = require("scada-common.log") local ppm = require("scada-common.ppm") local rsio = require("scada-common.rsio") @@ -39,39 +40,42 @@ local CENTER = core.ALIGN.CENTER local RIGHT = core.ALIGN.RIGHT -- rsio port descriptions -local PORT_DESC = { - "Facility SCRAM", - "Facility Acknowledge", - "Reactor SCRAM", - "Reactor RPS Reset", - "Reactor Enable", - "Unit Acknowledge", - "Facility Alarm (high prio)", - "Facility Alarm (any)", - "Waste Plutonium Valve", - "Waste Polonium Valve", - "Waste Po Pellets Valve", - "Waste Antimatter Valve", - "Reactor Active", - "Reactor in Auto Control", - "RPS Tripped", - "RPS Auto SCRAM", - "RPS High Damage", - "RPS High Temperature", - "RPS Low Coolant", - "RPS Excess Heated Coolant", - "RPS Excess Waste", - "RPS Insufficient Fuel", - "RPS PLC Fault", - "RPS Supervisor Timeout", - "Unit Alarm", - "Unit Emergency Cool. Valve" +local PORT_DESC_MAP = { + { IO.F_SCRAM, "Facility SCRAM" }, + { IO.F_ACK, "Facility Acknowledge" }, + { IO.R_SCRAM, "Reactor SCRAM" }, + { IO.R_RESET, "Reactor RPS Reset" }, + { IO.R_ENABLE, "Reactor Enable" }, + { IO.U_ACK, "Unit Acknowledge" }, + { IO.F_ALARM, "Facility Alarm (high prio)" }, + { IO.F_ALARM_ANY, "Facility Alarm (any)" }, + { IO.F_MATRIX_LOW, "Induction Matrix < " .. (100 * constants.RS_THRESHOLDS.IMATRIX_CHARGE_LOW) .. "%" }, + { IO.F_MATRIX_HIGH, "Induction Matrix > " .. (100 * constants.RS_THRESHOLDS.IMATRIX_CHARGE_HIGH) .. "%" }, + { IO.F_MATRIX_CHG, "Induction Matrix Charge %" }, + { IO.WASTE_PU, "Waste Plutonium Valve" }, + { IO.WASTE_PO, "Waste Polonium Valve" }, + { IO.WASTE_POPL, "Waste Po Pellets Valve" }, + { IO.WASTE_AM, "Waste Antimatter Valve" }, + { IO.R_ACTIVE, "Reactor Active" }, + { IO.R_AUTO_CTRL, "Reactor in Auto Control" }, + { IO.R_SCRAMMED, "RPS Tripped" }, + { IO.R_AUTO_SCRAM, "RPS Auto SCRAM" }, + { IO.R_HIGH_DMG, "RPS High Damage" }, + { IO.R_HIGH_TEMP, "RPS High Temperature" }, + { IO.R_LOW_COOLANT, "RPS Low Coolant" }, + { IO.R_EXCESS_HC, "RPS Excess Heated Coolant" }, + { IO.R_EXCESS_WS, "RPS Excess Waste" }, + { IO.R_INSUFF_FUEL, "RPS Insufficient Fuel" }, + { IO.R_PLC_FAULT, "RPS PLC Fault" }, + { IO.R_PLC_TIMEOUT, "RPS Supervisor Timeout" }, + { IO.U_ALARM, "Unit Alarm" }, + { IO.U_EMER_COOL, "Unit Emergency Cool. Valve" } } -- designation (0 = facility, 1 = unit) -local PORT_DSGN = { [-1] = 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } +local PORT_DSGN = { [-1] = 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 } -assert(#PORT_DESC == rsio.NUM_PORTS) +assert(#PORT_DESC_MAP == rsio.NUM_PORTS) assert(#PORT_DSGN == rsio.NUM_PORTS) -- changes to the config data/format to let the user know @@ -1167,14 +1171,17 @@ local function config_view(display) PushButton{parent=all_w_macro,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">ALL_WASTE",callback=function()new_rs(-1)end,fg_bg=cpair(colors.black,colors.green),active_fg_bg=cpair(colors.white,colors.black)} TextBox{parent=all_w_macro,x=16,y=1,width=5,height=1,text="[n/a]",fg_bg=cpair(colors.lightGray,colors.white)} TextBox{parent=all_w_macro,x=22,y=1,height=1,text="Create all 4 waste entries",fg_bg=cpair(colors.gray,colors.white)} + for i = 1, rsio.NUM_PORTS do - local name = rsio.to_string(i) - local io_dir = util.trinary(rsio.get_io_dir(i) == rsio.IO_DIR.IN, "[in]", "[out]") - local btn_color = util.trinary(rsio.get_io_dir(i) == rsio.IO_DIR.IN, colors.yellow, colors.lightBlue) + local p = PORT_DESC_MAP[i][1] + local name = rsio.to_string(p) + local io_dir = util.trinary(rsio.get_io_dir(p) == rsio.IO_DIR.IN, "[in]", "[out]") + local btn_color = util.trinary(rsio.get_io_dir(p) == rsio.IO_DIR.IN, colors.yellow, colors.lightBlue) + local entry = Div{parent=rs_ports,height=1} - PushButton{parent=entry,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">"..name,callback=function()new_rs(i)end,fg_bg=cpair(colors.black,btn_color),active_fg_bg=cpair(colors.white,colors.black)} + PushButton{parent=entry,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">"..name,callback=function()new_rs(p)end,fg_bg=cpair(colors.black,btn_color),active_fg_bg=cpair(colors.white,colors.black)} TextBox{parent=entry,x=16,y=1,width=5,height=1,text=io_dir,fg_bg=cpair(colors.lightGray,colors.white)} - TextBox{parent=entry,x=22,y=1,height=1,text=PORT_DESC[i],fg_bg=cpair(colors.gray,colors.white)} + TextBox{parent=entry,x=22,y=1,height=1,text=PORT_DESC_MAP[i][2],fg_bg=cpair(colors.gray,colors.white)} end PushButton{parent=rs_c_2,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} diff --git a/rtu/startup.lua b/rtu/startup.lua index 9470124..027549c 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -31,7 +31,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.9.4" +local RTU_VERSION = "v1.9.5" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE diff --git a/scada-common/constants.lua b/scada-common/constants.lua index 20925bd..678ea98 100644 --- a/scada-common/constants.lua +++ b/scada-common/constants.lua @@ -66,6 +66,18 @@ constants.ALARM_LIMITS = alarms --#endregion +--#region Supervisor Redstone Activation Thresholds + +---@class _rs_threshold_constants +local rs = {} + +rs.IMATRIX_CHARGE_LOW = 0.05 -- activation threshold (less than) for F_MATRIX_LOW +rs.IMATRIX_CHARGE_HIGH = 0.95 -- activation threshold (greater than) for F_MATRIX_HIGH + +constants.RS_THRESHOLDS = rs + +--#endregion + --#region Supervisor Constants -- milliseconds until coolant flow is assumed to be stable enough to enable certain coolant checks diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 6d1e688..8f4c558 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -52,6 +52,8 @@ local IO_PORT = { -- facility F_ALARM = 7, -- active high, facility-wide alarm (any high priority unit alarm) F_ALARM_ANY = 8, -- active high, any alarm regardless of priority + F_MATRIX_LOW = 27, -- active high, induction matrix charge less than + F_MATRIX_HIGH = 28, -- active high, induction matrix charge high -- waste WASTE_PU = 9, -- active low, waste -> plutonium -> pellets route @@ -75,17 +77,27 @@ local IO_PORT = { -- unit outputs U_ALARM = 25, -- active high, unit alarm - U_EMER_COOL = 26 -- active low, emergency coolant control + U_EMER_COOL = 26, -- active low, emergency coolant control + + -- analog outputs -- + + -- facility + F_MATRIX_CHG = 29 -- analog charge level of the induction matrix } rsio.IO_LVL = IO_LVL rsio.IO_DIR = IO_DIR rsio.IO_MODE = IO_MODE rsio.IO = IO_PORT -rsio.NUM_PORTS = IO_PORT.U_EMER_COOL + +rsio.NUM_PORTS = 29 +rsio.NUM_DIG_PORTS = 28 +rsio.NUM_ANA_PORTS = 1 -- self checks +assert(rsio.NUM_PORTS == (rsio.NUM_DIG_PORTS + rsio.NUM_ANA_PORTS), "port counts inconsistent") + local dup_chk = {} for _, v in pairs(IO_PORT) do assert(dup_chk[v] ~= true, "duplicate in port list") @@ -96,64 +108,45 @@ assert(#dup_chk == rsio.NUM_PORTS, "port list malformed") --#endregion ---#region Utility Functions +--#region Utility Functions and Attribute Tables -local PORT_NAMES = { - "F_SCRAM", - "F_ACK", - "R_SCRAM", - "R_RESET", - "R_ENABLE", - "U_ACK", - "F_ALARM", - "F_ALARM_ANY", - "WASTE_PU", - "WASTE_PO", - "WASTE_POPL", - "WASTE_AM", - "R_ACTIVE", - "R_AUTO_CTRL", - "R_SCRAMMED", - "R_AUTO_SCRAM", - "R_HIGH_DMG", - "R_HIGH_TEMP", - "R_LOW_COOLANT", - "R_EXCESS_HC", - "R_EXCESS_WS", - "R_INSUFF_FUEL", - "R_PLC_FAULT", - "R_PLC_TIMEOUT", - "U_ALARM", - "U_EMER_COOL" -} +local IO = IO_PORT +-- list of all port names +local PORT_NAMES = {} +for k, v in pairs(IO) do PORT_NAMES[v] = k end + +-- list of all port I/O modes local MODES = { - IO_MODE.DIGITAL_IN, -- F_SCRAM - IO_MODE.DIGITAL_IN, -- F_ACK - IO_MODE.DIGITAL_IN, -- R_SCRAM - IO_MODE.DIGITAL_IN, -- R_RESET - IO_MODE.DIGITAL_IN, -- R_ENABLE - IO_MODE.DIGITAL_IN, -- U_ACK - IO_MODE.DIGITAL_OUT, -- F_ALARM - IO_MODE.DIGITAL_OUT, -- F_ALARM_ANY - IO_MODE.DIGITAL_OUT, -- WASTE_PU - IO_MODE.DIGITAL_OUT, -- WASTE_PO - IO_MODE.DIGITAL_OUT, -- WASTE_POPL - IO_MODE.DIGITAL_OUT, -- WASTE_AM - IO_MODE.DIGITAL_OUT, -- R_ACTIVE - IO_MODE.DIGITAL_OUT, -- R_AUTO_CTRL - IO_MODE.DIGITAL_OUT, -- R_SCRAMMED - IO_MODE.DIGITAL_OUT, -- R_AUTO_SCRAM - IO_MODE.DIGITAL_OUT, -- R_HIGH_DMG - IO_MODE.DIGITAL_OUT, -- R_HIGH_TEMP - IO_MODE.DIGITAL_OUT, -- R_LOW_COOLANT - IO_MODE.DIGITAL_OUT, -- R_EXCESS_HC - IO_MODE.DIGITAL_OUT, -- R_EXCESS_WS - IO_MODE.DIGITAL_OUT, -- R_INSUFF_FUEL - IO_MODE.DIGITAL_OUT, -- R_PLC_FAULT - IO_MODE.DIGITAL_OUT, -- R_PLC_TIMEOUT - IO_MODE.DIGITAL_OUT, -- U_ALARM - IO_MODE.DIGITAL_OUT -- U_EMER_COOL + [IO.F_SCRAM] = IO_MODE.DIGITAL_IN, + [IO.F_ACK] = IO_MODE.DIGITAL_IN, + [IO.R_SCRAM] = IO_MODE.DIGITAL_IN, + [IO.R_RESET] = IO_MODE.DIGITAL_IN, + [IO.R_ENABLE] = IO_MODE.DIGITAL_IN, + [IO.U_ACK] = IO_MODE.DIGITAL_IN, + [IO.F_ALARM] = IO_MODE.DIGITAL_OUT, + [IO.F_ALARM_ANY] = IO_MODE.DIGITAL_OUT, + [IO.F_MATRIX_LOW] = IO_MODE.DIGITAL_OUT, + [IO.F_MATRIX_HIGH] = IO_MODE.DIGITAL_OUT, + [IO.WASTE_PU] = IO_MODE.DIGITAL_OUT, + [IO.WASTE_PO] = IO_MODE.DIGITAL_OUT, + [IO.WASTE_POPL] = IO_MODE.DIGITAL_OUT, + [IO.WASTE_AM] = IO_MODE.DIGITAL_OUT, + [IO.R_ACTIVE] = IO_MODE.DIGITAL_OUT, + [IO.R_AUTO_CTRL] = IO_MODE.DIGITAL_OUT, + [IO.R_SCRAMMED] = IO_MODE.DIGITAL_OUT, + [IO.R_AUTO_SCRAM] = IO_MODE.DIGITAL_OUT, + [IO.R_HIGH_DMG] = IO_MODE.DIGITAL_OUT, + [IO.R_HIGH_TEMP] = IO_MODE.DIGITAL_OUT, + [IO.R_LOW_COOLANT] = IO_MODE.DIGITAL_OUT, + [IO.R_EXCESS_HC] = IO_MODE.DIGITAL_OUT, + [IO.R_EXCESS_WS] = IO_MODE.DIGITAL_OUT, + [IO.R_INSUFF_FUEL] = IO_MODE.DIGITAL_OUT, + [IO.R_PLC_FAULT] = IO_MODE.DIGITAL_OUT, + [IO.R_PLC_TIMEOUT] = IO_MODE.DIGITAL_OUT, + [IO.U_ALARM] = IO_MODE.DIGITAL_OUT, + [IO.U_EMER_COOL] = IO_MODE.DIGITAL_OUT, + [IO.F_MATRIX_CHG] = IO_MODE.ANALOG_OUT } assert(rsio.NUM_PORTS == #PORT_NAMES, "port names length incorrect") @@ -179,74 +172,51 @@ local function _O_ACTIVE_LOW(active) if active then return IO_LVL.LOW else retur -- I/O mappings to I/O function and I/O mode local RS_DIO_MAP = { - -- F_SCRAM - { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN }, - -- F_ACK - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN }, + [IO.F_SCRAM] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN }, + [IO.F_ACK] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN }, - -- R_SCRAM - { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN }, - -- R_RESET - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN }, - -- R_ENABLE - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN }, + [IO.R_SCRAM] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.IN }, + [IO.R_RESET] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN }, + [IO.R_ENABLE] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN }, - -- U_ACK - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN }, + [IO.U_ACK] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.IN }, - -- F_ALARM - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- F_ALARM_ANY - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + [IO.F_ALARM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + [IO.F_ALARM_ANY] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + [IO.F_MATRIX_LOW] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + [IO.F_MATRIX_HIGH] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- WASTE_PU - { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, - -- WASTE_PO - { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, - -- WASTE_POPL - { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, - -- WASTE_AM - { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, + [IO.WASTE_PU] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, + [IO.WASTE_PO] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, + [IO.WASTE_POPL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, + [IO.WASTE_AM] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, - -- R_ACTIVE - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- R_AUTO_CTRL - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- R_SCRAMMED - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- R_AUTO_SCRAM - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- R_HIGH_DMG - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- R_HIGH_TEMP - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- R_LOW_COOLANT - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- R_EXCESS_HC - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- R_EXCESS_WS - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- R_INSUFF_FUEL - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- R_PLC_FAULT - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- R_PLC_TIMEOUT - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + [IO.R_ACTIVE] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + [IO.R_AUTO_CTRL] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + [IO.R_SCRAMMED] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + [IO.R_AUTO_SCRAM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + [IO.R_HIGH_DMG] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + [IO.R_HIGH_TEMP] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + [IO.R_LOW_COOLANT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + [IO.R_EXCESS_HC] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + [IO.R_EXCESS_WS] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + [IO.R_INSUFF_FUEL] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + [IO.R_PLC_FAULT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + [IO.R_PLC_TIMEOUT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- U_ALARM - { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - -- U_EMER_COOL - { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT } + [IO.U_ALARM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, + [IO.U_EMER_COOL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT } } -assert(rsio.NUM_PORTS == #RS_DIO_MAP, "RS_DIO_MAP length incorrect") +assert(rsio.NUM_DIG_PORTS == #RS_DIO_MAP, "RS_DIO_MAP length incorrect") -- get the I/O direction of a port ---@nodiscard ---@param port IO_PORT ---@return IO_DIR function rsio.get_io_dir(port) - if rsio.is_valid_port(port) then return RS_DIO_MAP[port].mode + if rsio.is_valid_port(port) then + return util.trinary(MODES[port] == IO_MODE.DIGITAL_OUT or MODES[port] == IO_MODE.ANALOG_OUT, IO_DIR.OUT, IO_DIR.IN) else return IO_DIR.IN end end @@ -310,6 +280,13 @@ end --#region Digital I/O +-- check if a port is digital +---@nodiscard +---@param port IO_PORT +function rsio.is_digital(port) + return rsio.is_valid_port(port) and (MODES[port] == IO_MODE.DIGITAL_IN or MODES[port] == IO_MODE.DIGITAL_OUT) +end + -- get digital I/O level reading from a redstone boolean input value ---@nodiscard ---@param rs_value boolean raw value from redstone @@ -330,7 +307,7 @@ function rsio.digital_write(level) return level == IO_LVL.HIGH end ---@param active boolean state to convert to logic level ---@return IO_LVL|false function rsio.digital_write_active(port, active) - if (not util.is_int(port)) or (port < IO_PORT.F_ALARM) or (port > IO_PORT.U_EMER_COOL) then + if not rsio.is_digital(port) then return false else return RS_DIO_MAP[port]._out(active) @@ -343,9 +320,7 @@ end ---@param level IO_LVL logic level ---@return boolean|nil state true for active, false for inactive, or nil if invalid port or level provided function rsio.digital_is_active(port, level) - if not util.is_int(port) then - return nil - elseif level == IO_LVL.FLOATING or level == IO_LVL.DISCONNECT then + if (not rsio.is_digital(port)) or level == IO_LVL.FLOATING or level == IO_LVL.DISCONNECT then return nil else return RS_DIO_MAP[port]._in(level) @@ -356,6 +331,13 @@ end --#region Analog I/O +-- check if a port is analog +---@nodiscard +---@param port IO_PORT +function rsio.is_analog(port) + return rsio.is_valid_port(port) and (MODES[port] == IO_MODE.ANALOG_IN or MODES[port] == IO_MODE.ANALOG_OUT) +end + -- read an analog value scaled from min to max ---@nodiscard ---@param rs_value number redstone reading (0 to 15) @@ -372,7 +354,7 @@ end ---@param value number value to write (from min to max range) ---@param min number minimum of range ---@param max number maximum of range ----@return number rs_value scaled redstone reading (0 to 15) +---@return integer rs_value scaled redstone reading (0 to 15) function rsio.analog_write(value, min, max) local scaled_value = (value - min) / (max - min) return math.floor(scaled_value * 15) diff --git a/scada-common/util.lua b/scada-common/util.lua index 98a1667..2f92424 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -22,7 +22,7 @@ local t_pack = table.pack local util = {} -- scada-common version -util.version = "1.2.2" +util.version = "1.3.0" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 33cd267..d7058a8 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -300,8 +300,8 @@ function facility.new(config, cooling_conf) -- calculate moving averages for induction matrix if self.induction[1] ~= nil then - local matrix = self.induction[1] ---@type unit_session - local db = matrix.get_db() ---@type imatrix_session_db + local matrix = self.induction[1] ---@type unit_session + local db = matrix.get_db() ---@type imatrix_session_db charge_update = db.tanks.last_update rate_update = db.state.last_update @@ -774,6 +774,16 @@ function facility.new(config, cooling_conf) self.io_ctl.digital_write(IO.F_ALARM, has_prio_alarm) self.io_ctl.digital_write(IO.F_ALARM_ANY, has_any_alarm) + + -- update induction matrix related outputs + if self.induction[1] ~= nil then + local matrix = self.induction[1] ---@type unit_session + local db = matrix.get_db() ---@type imatrix_session_db + + self.io_ctl.digital_write(IO.F_MATRIX_LOW, db.tanks.energy_fill < const.RS_THRESHOLDS.IMATRIX_CHARGE_LOW) + self.io_ctl.digital_write(IO.F_MATRIX_HIGH, db.tanks.energy_fill > const.RS_THRESHOLDS.IMATRIX_CHARGE_HIGH) + self.io_ctl.analog_write(IO.F_MATRIX_CHG, db.tanks.energy_fill, 0, 1) + end end --#endregion diff --git a/supervisor/session/rsctl.lua b/supervisor/session/rsctl.lua index 1bdef5a..a369937 100644 --- a/supervisor/session/rsctl.lua +++ b/supervisor/session/rsctl.lua @@ -2,6 +2,8 @@ -- Redstone RTU Session I/O Controller -- +local rsio = require("scada-common.rsio") + local rsctl = {} -- create a new redstone RTU I/O controller @@ -16,7 +18,7 @@ function rsctl.new(redstone_rtus) ---@return boolean function public.is_connected(port) for i = 1, #redstone_rtus do - local db = redstone_rtus[i].get_db() ---@type redstone_session_db + local db = redstone_rtus[i].get_db() ---@type redstone_session_db if db.io[port] ~= nil then return true end end @@ -28,8 +30,8 @@ function rsctl.new(redstone_rtus) ---@param value boolean function public.digital_write(port, value) for i = 1, #redstone_rtus do - local db = redstone_rtus[i].get_db() ---@type redstone_session_db - local io = db.io[port] ---@type rs_db_dig_io|nil + local db = redstone_rtus[i].get_db() ---@type redstone_session_db + local io = db.io[port] ---@type rs_db_dig_io|nil if io ~= nil then io.write(value) end end end @@ -40,12 +42,25 @@ function rsctl.new(redstone_rtus) ---@return boolean|nil function public.digital_read(port) for i = 1, #redstone_rtus do - local db = redstone_rtus[i].get_db() ---@type redstone_session_db - local io = db.io[port] ---@type rs_db_dig_io|nil + local db = redstone_rtus[i].get_db() ---@type redstone_session_db + local io = db.io[port] ---@type rs_db_dig_io|nil if io ~= nil then return io.read() end end end + -- write to an analog redstone port (applies to all RTUs) + ---@param port IO_PORT + ---@param value number value + ---@param min number minimum value for scaling 0 to 15 + ---@param max number maximum value for scaling 0 to 15 + function public.analog_write(port, value, min, max) + for i = 1, #redstone_rtus do + local db = redstone_rtus[i].get_db() ---@type redstone_session_db + local io = db.io[port] ---@type rs_db_ana_io|nil + if io ~= nil then io.write(rsio.analog_write(value, min, max)) end + end + end + return public end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 1694ebe..2a2202c 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.3.6" +local SUPERVISOR_VERSION = "v1.3.7" local println = util.println local println_ts = util.println_ts diff --git a/test/rstest.lua b/test/rstest.lua index e322e28..ba0b156 100644 --- a/test/rstest.lua +++ b/test/rstest.lua @@ -1,16 +1,28 @@ require("/initenv").init_env() -local rsio = require("scada-common.rsio") -local util = require("scada-common.util") +local rsio = require("scada-common.rsio") +local util = require("scada-common.util") local testutils = require("test.testutils") +local IO = rsio.IO +local IO_LVL = rsio.IO_LVL +local IO_MODE = rsio.IO_MODE + local print = util.print local println = util.println -local IO = rsio.IO -local IO_LVL = rsio.IO_LVL -local IO_MODE = rsio.IO_MODE +-- list of inverted digital signals
+-- just using the key for a quick lookup, value need to be not nil +local DIG_INV = { + [IO.F_SCRAM] = 0, + [IO.R_SCRAM] = 0, + [IO.WASTE_PU] = 0, + [IO.WASTE_PO] = 0, + [IO.WASTE_POPL] = 0, + [IO.WASTE_AM] = 0, + [IO.U_EMER_COOL] = 0 +} println("starting RSIO tester") println("") @@ -50,8 +62,8 @@ testutils.pause() println(">>> checking invalid ports:") -testutils.test_func("rsio.to_string", rsio.to_string, { -1, 100, false }, "") -testutils.test_func_nil("rsio.to_string", rsio.to_string, "") +testutils.test_func("rsio.to_string", rsio.to_string, { -1, 100, false }, "UNKNOWN") +testutils.test_func_nil("rsio.to_string", rsio.to_string, "UNKNOWN") testutils.test_func("rsio.get_io_mode", rsio.get_io_mode, { -1, 100, false }, IO_MODE.ANALOG_IN) testutils.test_func_nil("rsio.get_io_mode", rsio.get_io_mode, IO_MODE.ANALOG_IN) @@ -100,46 +112,35 @@ println(">>> checking port I/O:") print("rsio.digital_is_active(...): ") --- check input ports -assert(rsio.digital_is_active(IO.F_SCRAM, IO_LVL.LOW) == true, "IO_F_SCRAM_HIGH") -assert(rsio.digital_is_active(IO.F_SCRAM, IO_LVL.HIGH) == false, "IO_F_SCRAM_LOW") -assert(rsio.digital_is_active(IO.R_SCRAM, IO_LVL.LOW) == true, "IO_R_SCRAM_HIGH") -assert(rsio.digital_is_active(IO.R_SCRAM, IO_LVL.HIGH) == false, "IO_R_SCRAM_LOW") -assert(rsio.digital_is_active(IO.R_ENABLE, IO_LVL.LOW) == false, "IO_R_ENABLE_HIGH") -assert(rsio.digital_is_active(IO.R_ENABLE, IO_LVL.HIGH) == true, "IO_R_ENABLE_LOW") +-- check all digital ports +for i = 1, rsio.NUM_PORTS do + if rsio.get_io_mode(i) == IO_MODE.DIGITAL_IN or rsio.get_io_mode(i) == IO_MODE.DIGITAL_OUT then + local high = DIG_INV[i] == nil + assert(rsio.digital_is_active(i, IO_LVL.LOW) == not high, "IO_" .. rsio.to_string(i) .. "_LOW") + assert(rsio.digital_is_active(i, IO_LVL.HIGH) == high, "IO_" .. rsio.to_string(i) .. "_HIGH") + end +end --- non-inputs should always return LOW -assert(rsio.digital_is_active(IO.F_ALARM, IO_LVL.LOW) == false, "IO_OUT_READ_LOW") -assert(rsio.digital_is_active(IO.F_ALARM, IO_LVL.HIGH) == false, "IO_OUT_READ_HIGH") +assert(rsio.digital_is_active(IO.F_MATRIX_CHG, IO_LVL.LOW) == nil, "ANA_DIG_READ_LOW") +assert(rsio.digital_is_active(IO.F_MATRIX_CHG, IO_LVL.HIGH) == nil, "ANA_DIG_READ_HIGH") println("PASS") --- check output ports +-- check digital write -print("rsio.digital_write(...): ") +print("rsio.digital_write_active(...): ") --- check output ports -assert(rsio.digital_write_active(IO.F_ALARM, true) == IO_LVL.LOW, "IO_F_ALARM_LOW") -assert(rsio.digital_write_active(IO.F_ALARM, true) == IO_LVL.HIGH, "IO_F_ALARM_HIGH") -assert(rsio.digital_write_active(IO.WASTE_PU, true) == IO_LVL.HIGH, "IO_WASTE_PU_HIGH") -assert(rsio.digital_write_active(IO.WASTE_PU, true) == IO_LVL.LOW, "IO_WASTE_PU_LOW") -assert(rsio.digital_write_active(IO.WASTE_PO, true) == IO_LVL.HIGH, "IO_WASTE_PO_HIGH") -assert(rsio.digital_write_active(IO.WASTE_PO, true) == IO_LVL.LOW, "IO_WASTE_PO_LOW") -assert(rsio.digital_write_active(IO.WASTE_POPL, true) == IO_LVL.HIGH, "IO_WASTE_POPL_HIGH") -assert(rsio.digital_write_active(IO.WASTE_POPL, true) == IO_LVL.LOW, "IO_WASTE_POPL_LOW") -assert(rsio.digital_write_active(IO.WASTE_AM, true) == IO_LVL.HIGH, "IO_WASTE_AM_HIGH") -assert(rsio.digital_write_active(IO.WASTE_AM, true) == IO_LVL.LOW, "IO_WASTE_AM_LOW") - --- check all reactor output ports (all are active high) -for i = IO.R_ALARM, (IO.R_PLC_TIMEOUT - IO.R_ALARM + 1) do - assert(rsio.to_string(i) ~= "", "REACTOR_IO_BAD_PORT") - assert(rsio.digital_write_active(i, false) == IO_LVL.LOW, "IO_" .. rsio.to_string(i) .. "_LOW") - assert(rsio.digital_write_active(i, true) == IO_LVL.HIGH, "IO_" .. rsio.to_string(i) .. "_HIGH") +-- check all digital ports +for i = 1, rsio.NUM_PORTS do + if rsio.get_io_mode(i) == IO_MODE.DIGITAL_IN or rsio.get_io_mode(i) == IO_MODE.DIGITAL_OUT then + local high = DIG_INV[i] == nil + assert(rsio.digital_write_active(i, not high) == IO_LVL.LOW, "IO_" .. rsio.to_string(i) .. "_LOW") + assert(rsio.digital_write_active(i, high) == IO_LVL.HIGH, "IO_" .. rsio.to_string(i) .. "_HIGH") + end end --- non-outputs should always return false -assert(rsio.digital_write_active(IO.F_SCRAM, false) == IO_LVL.LOW, "IO_IN_WRITE_FALSE") -assert(rsio.digital_write_active(IO.F_SCRAM, true) == IO_LVL.LOW, "IO_IN_WRITE_TRUE") +assert(rsio.digital_write_active(IO.F_MATRIX_CHG, true) == false, "ANA_DIG_WRITE_TRUE") +assert(rsio.digital_write_active(IO.F_MATRIX_CHG, false) == false, "ANA_DIG_WRITE_FALSE") println("PASS") From fb85c2f05bb342c8b6f5d85dabc7ac1775337751 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 21 Apr 2024 13:54:14 -0400 Subject: [PATCH 02/87] RTU configurator updates for redstone I/O clarity --- rtu/configure.lua | 74 +++++++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/rtu/configure.lua b/rtu/configure.lua index 5dc440a..efebdee 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -34,6 +34,8 @@ local tri = util.trinary local cpair = core.cpair local IO = rsio.IO +local IO_LVL = rsio.IO_LVL +local IO_MODE = rsio.IO_MODE local LEFT = core.ALIGN.LEFT local CENTER = core.ALIGN.CENTER @@ -446,7 +448,7 @@ local function config_view(display) TextBox{parent=net_c_3,x=1,y=11,height=1,text="Facility Auth Key"} local key, _, censor = TextField{parent=net_c_3,x=1,y=12,max_len=64,value=ini_cfg.AuthKey,width=32,height=1,fg_bg=bw_fg_bg} - local function censor_key(enable) censor(util.trinary(enable, "*", nil)) end + local function censor_key(enable) censor(tri(enable, "*", nil)) end local hide_key = CheckBox{parent=net_c_3,x=34,y=12,label="Hide",box_fg_bg=cpair(colors.lightBlue,colors.black),callback=censor_key} @@ -559,7 +561,7 @@ local function config_view(display) PushButton{parent=clr_c_2,x=44,y=14,min_width=6,text="Done",callback=function()clr_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} local function back_from_colors() - main_pane.set_value(util.trinary(tool_ctl.jumped_to_color, 1, 4)) + main_pane.set_value(tri(tool_ctl.jumped_to_color, 1, 4)) tool_ctl.jumped_to_color = false recolor(1) end @@ -901,7 +903,7 @@ local function config_view(display) tool_ctl.p_desc.reposition(1, 8) tool_ctl.p_desc.set_value("You can connect more than one environment detector for a particular unit or the facility. In that case, the maximum radiation reading from those assigned to that particular unit or the facility will be used for alarms and display.") elseif type == "inductionPort" or type == "spsPort" then - local dev = util.trinary(type == "inductionPort", "induction matrix", "SPS") + local dev = tri(type == "inductionPort", "induction matrix", "SPS") tool_ctl.p_idx.hide(true) tool_ctl.p_unit.hide(true) tool_ctl.p_prompt.set_value("This is the " .. dev .. " for the facility.") @@ -927,7 +929,7 @@ local function config_view(display) tool_ctl.ppm_devs.remove_all() for name, entry in pairs(mounts) do if util.table_contains(RTU_DEV_TYPES, entry.type) then - local bkg = util.trinary(alternate, colors.white, colors.lightGray) + local bkg = tri(alternate, colors.white, colors.lightGray) ---@cast entry ppm_entry local line = Div{parent=tool_ctl.ppm_devs,height=2,fg_bg=cpair(colors.black,bkg)} @@ -1089,8 +1091,9 @@ local function config_view(display) local rs_c_4 = Div{parent=rs_cfg,x=2,y=4,width=49} local rs_c_5 = Div{parent=rs_cfg,x=2,y=4,width=49} local rs_c_6 = Div{parent=rs_cfg,x=2,y=4,width=49} + local rs_c_7 = Div{parent=rs_cfg,x=2,y=4,width=49} - local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5,rs_c_6}} + local rs_pane = MultiPane{parent=rs_cfg,x=1,y=4,panes={rs_c_1,rs_c_2,rs_c_3,rs_c_4,rs_c_5,rs_c_6,rs_c_7}} TextBox{parent=rs_cfg,x=1,y=2,height=1,text=" Redstone Connections",fg_bg=cpair(colors.black,colors.red)} @@ -1147,9 +1150,23 @@ local function config_view(display) text = "You selected the ALL_WASTE shortcut." else tool_ctl.rs_cfg_shortcut.hide(true) - tool_ctl.rs_cfg_side_l.set_value(util.trinary(rsio.get_io_dir(port) == rsio.IO_DIR.IN, "Input Side", "Output Side")) + tool_ctl.rs_cfg_side_l.set_value(tri(rsio.get_io_dir(port) == rsio.IO_DIR.IN, "Input Side", "Output Side")) tool_ctl.rs_cfg_color.show() - text = "You selected " .. rsio.to_string(port) .. " (for " + + local io_type = "analog input " + local io_mode = rsio.get_io_mode(port) + local inv = tri(rsio.digital_is_active(port, IO_LVL.LOW) == true, "inverted ", "") + + if io_mode == IO_MODE.DIGITAL_IN then + io_type = inv .. "digital input " + elseif io_mode == IO_MODE.DIGITAL_OUT then + io_type = inv .. "digital output " + elseif io_mode == IO_MODE.ANALOG_OUT then + io_type = "analog output " + end + + text = "You selected the " .. io_type .. rsio.to_string(port) .. " (for " + if PORT_DSGN[port] == 1 then text = text .. "a unit)." tool_ctl.rs_cfg_unit_l.show() @@ -1175,8 +1192,8 @@ local function config_view(display) for i = 1, rsio.NUM_PORTS do local p = PORT_DESC_MAP[i][1] local name = rsio.to_string(p) - local io_dir = util.trinary(rsio.get_io_dir(p) == rsio.IO_DIR.IN, "[in]", "[out]") - local btn_color = util.trinary(rsio.get_io_dir(p) == rsio.IO_DIR.IN, colors.yellow, colors.lightBlue) + local io_dir = tri(rsio.get_io_dir(p) == rsio.IO_DIR.IN, "[in]", "[out]") + local btn_color = tri(rsio.get_io_dir(p) == rsio.IO_DIR.IN, colors.yellow, colors.lightBlue) local entry = Div{parent=rs_ports,height=1} PushButton{parent=entry,x=1,y=1,min_width=14,alignment=LEFT,height=1,text=">"..name,callback=function()new_rs(p)end,fg_bg=cpair(colors.black,btn_color),active_fg_bg=cpair(colors.white,colors.black)} @@ -1186,13 +1203,20 @@ local function config_view(display) PushButton{parent=rs_c_2,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - tool_ctl.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=1,text=""} + tool_ctl.rs_cfg_selection = TextBox{parent=rs_c_3,x=1,y=1,height=2,text=""} - tool_ctl.rs_cfg_unit_l = TextBox{parent=rs_c_3,x=27,y=3,width=7,height=1,text="Unit ID"} - tool_ctl.rs_cfg_unit = NumberField{parent=rs_c_3,x=27,y=4,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg} + PushButton{parent=rs_c_3,x=36,y=3,text="What's that?",min_width=14,callback=function()rs_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - tool_ctl.rs_cfg_side_l = TextBox{parent=rs_c_3,x=1,y=3,width=11,height=1,text="Output Side"} - local side = Radio2D{parent=rs_c_3,x=1,y=4,rows=2,columns=3,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red} + TextBox{parent=rs_c_7,x=1,y=1,height=4,text="(Normal) Digital Input: On if there is a redstone signal, off otherwise\nInverted Digital Input: On without a redstone signal, off otherwise"} + TextBox{parent=rs_c_7,x=1,y=6,height=4,text="(Normal) Digital Output: Redstone signal to 'turn it on', none to 'turn it off'\nInverted Digital Output: No redstone signal to 'turn it on', redstone signal to 'turn it off'"} + TextBox{parent=rs_c_7,x=1,y=11,height=2,text="Analog Input: 0-15 redstone power level input\nAnalog Output: 0-15 scaled redstone power level output"} + PushButton{parent=rs_c_7,x=1,y=14,text="\x1b Back",callback=function()rs_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + tool_ctl.rs_cfg_side_l = TextBox{parent=rs_c_3,x=1,y=4,width=11,height=1,text="Output Side"} + local side = Radio2D{parent=rs_c_3,x=1,y=5,rows=1,columns=6,default=1,options=side_options,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.red} + + tool_ctl.rs_cfg_unit_l = TextBox{parent=rs_c_3,x=25,y=7,width=7,height=1,text="Unit ID"} + tool_ctl.rs_cfg_unit = NumberField{parent=rs_c_3,x=33,y=7,width=10,max_chars=2,min=1,max=4,fg_bg=bw_fg_bg} local function set_bundled(bundled) if bundled then tool_ctl.rs_cfg_color.enable() else tool_ctl.rs_cfg_color.disable() end @@ -1223,10 +1247,10 @@ local function config_view(display) if port >= 0 then ---@type rtu_rs_definition local def = { - unit = util.trinary(PORT_DSGN[port] == 1, u, nil), + unit = tri(PORT_DSGN[port] == 1, u, nil), port = port, side = side_options_map[side.get_value()], - color = util.trinary(bundled.get_value(), color_options_map[tool_ctl.rs_cfg_color.get_value()], nil) + color = tri(bundled.get_value(), color_options_map[tool_ctl.rs_cfg_color.get_value()], nil) } if tool_ctl.rs_cfg_editing == false then @@ -1240,10 +1264,10 @@ local function config_view(display) local default_colors = { colors.red, colors.orange, colors.yellow, colors.lime } for i = 0, 3 do table.insert(tmp_cfg.Redstone, { - unit = util.trinary(PORT_DSGN[IO.WASTE_PU + i] == 1, u, nil), + unit = tri(PORT_DSGN[IO.WASTE_PU + i] == 1, u, nil), port = IO.WASTE_PU + i, - side = util.trinary(bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]), - color = util.trinary(bundled.get_value(), default_colors[i + 1], nil) + side = tri(bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]), + color = tri(bundled.get_value(), default_colors[i + 1], nil) }) end end @@ -1296,7 +1320,7 @@ local function config_view(display) peri_import_list.remove_all() for _, entry in ipairs(config.RTU_DEVICES) do local for_facility = entry.for_reactor == 0 - local ini_unit = util.trinary(for_facility, nil, entry.for_reactor) + local ini_unit = tri(for_facility, nil, entry.for_reactor) local def = { name = entry.name, unit = ini_unit, index = entry.index } local mount = mounts[def.name] ---@type ppm_entry|nil @@ -1375,7 +1399,7 @@ local function config_view(display) table.insert(tmp_cfg.Redstone, def) local name = rsio.to_string(def.port) - local io_dir = util.trinary(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b") + local io_dir = tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b") local conn = def.side local unit = "facility" @@ -1438,7 +1462,7 @@ local function config_view(display) local val = util.strval(raw) if f[1] == "AuthKey" then val = string.rep("*", string.len(val)) - elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") + elseif f[1] == "LogMode" then val = tri(raw == log.MODE.APPEND, "append", "replace") elseif f[1] == "FrontPanelTheme" then val = util.strval(themes.fp_theme_name(raw)) elseif f[1] == "ColorMode" then @@ -1447,7 +1471,7 @@ local function config_view(display) if val == "nil" then val = "" end - local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white)) + local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white)) alternate = not alternate if string.len(val) > val_max_w then @@ -1561,7 +1585,7 @@ local function config_view(display) end tool_ctl.rs_cfg_selection.set_value(text) - tool_ctl.rs_cfg_side_l.set_value(util.trinary(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "Input Side", "Output Side")) + tool_ctl.rs_cfg_side_l.set_value(tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "Input Side", "Output Side")) side.set_value(side_to_idx(def.side)) bundled.set_value(def.color ~= nil) tool_ctl.rs_cfg_color.set_value(value) @@ -1582,7 +1606,7 @@ local function config_view(display) local def = cfg.Redstone[i] ---@type rtu_rs_definition local name = rsio.to_string(def.port) - local io_dir = util.trinary(rsio.get_io_mode(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b") + local io_dir = tri(rsio.get_io_mode(def.port) == rsio.IO_DIR.IN, "\x1a", "\x1b") local conn = def.side local unit = util.strval(def.unit or "F") From 35bf56663f45e54b393e00a439a7eba030222273 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 27 Apr 2024 16:27:01 -0400 Subject: [PATCH 03/87] #469 induction matrix charge ETAs and misc cleanup/updates --- coordinator/iocontrol.lua | 24 +++++-- coordinator/startup.lua | 2 +- coordinator/ui/components/imatrix.lua | 98 ++++++++++++++++++++------- scada-common/comms.lua | 2 +- scada-common/util.lua | 11 ++- supervisor/facility.lua | 51 ++++++++++---- supervisor/startup.lua | 2 +- supervisor/unit.lua | 4 +- 8 files changed, 142 insertions(+), 52 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 1de337f..7fc2707 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -663,10 +663,26 @@ function iocontrol.update_facility_status(status) fac.rtu_count = rtu_statuses.count -- power statistics - if type(rtu_statuses.power) == "table" then - fac.induction_ps_tbl[1].publish("avg_charge", rtu_statuses.power[1]) - fac.induction_ps_tbl[1].publish("avg_inflow", rtu_statuses.power[2]) - fac.induction_ps_tbl[1].publish("avg_outflow", rtu_statuses.power[3]) + if type(rtu_statuses.power) == "table" and #rtu_statuses.power == 4 then + local data = fac.induction_data_tbl[1] ---@type imatrix_session_db + local ps = fac.induction_ps_tbl[1] ---@type psil + + local chg = tonumber(rtu_statuses.power[1]) + local in_f = tonumber(rtu_statuses.power[2]) + local out_f = tonumber(rtu_statuses.power[3]) + local eta = tonumber(rtu_statuses.power[4]) + + ps.publish("avg_charge", chg) + ps.publish("avg_inflow", in_f) + ps.publish("avg_outflow", out_f) + ps.publish("eta_ms", eta) + + ps.publish("is_charging", in_f > out_f) + ps.publish("is_discharging", out_f > in_f) + + if data and data.build then + ps.publish("at_max_io", in_f >= data.build.transfer_cap or out_f >= data.build.transfer_cap) + end else log.debug(log_header .. "power statistics list not a table") valid = false diff --git a/coordinator/startup.lua b/coordinator/startup.lua index a50da5b..b3df178 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") local threads = require("coordinator.threads") -local COORDINATOR_VERSION = "v1.4.3" +local COORDINATOR_VERSION = "v1.4.4" local CHUNK_LOAD_DELAY_S = 30.0 diff --git a/coordinator/ui/components/imatrix.lua b/coordinator/ui/components/imatrix.lua index 2b80350..60a745a 100644 --- a/coordinator/ui/components/imatrix.lua +++ b/coordinator/ui/components/imatrix.lua @@ -9,6 +9,7 @@ local Rectangle = require("graphics.elements.rectangle") local TextBox = require("graphics.elements.textbox") local DataIndicator = require("graphics.elements.indicators.data") +local IndicatorLight = require("graphics.elements.indicators.light") local PowerIndicator = require("graphics.elements.indicators.power") local StateIndicator = require("graphics.elements.indicators.state") local VerticalBar = require("graphics.elements.indicators.vbar") @@ -26,9 +27,13 @@ local ALIGN = core.ALIGN ---@param ps psil ps interface ---@param id number? matrix ID local function new_view(root, x, y, data, ps, id) + local label_fg = style.theme.label_fg local text_fg = style.theme.text_fg local lu_col = style.lu_colors + local ind_yel = style.ind_yel + local ind_wht = style.ind_wht + local title = "INDUCTION MATRIX" if type(id) == "number" then title = title .. id end @@ -42,45 +47,47 @@ local function new_view(root, x, y, data, ps, id) local rect = Rectangle{parent=matrix,border=border(1,colors.gray,true),width=33,height=22,x=1,y=3} - local status = StateIndicator{parent=rect,x=10,y=1,states=style.imatrix.states,value=1,min_width=14} - local energy = PowerIndicator{parent=rect,x=7,y=3,lu_colors=lu_col,label="Energy: ",format="%8.2f",value=0,width=26,fg_bg=text_fg} - local capacity = PowerIndicator{parent=rect,x=7,y=4,lu_colors=lu_col,label="Capacity:",format="%8.2f",value=0,width=26,fg_bg=text_fg} - local input = PowerIndicator{parent=rect,x=7,y=5,lu_colors=lu_col,label="Input: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg} - local output = PowerIndicator{parent=rect,x=7,y=6,lu_colors=lu_col,label="Output: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg} - - local avg_chg = PowerIndicator{parent=rect,x=7,y=8,lu_colors=lu_col,label="Avg. Chg:",format="%8.2f",value=0,width=26,fg_bg=text_fg} - local avg_in = PowerIndicator{parent=rect,x=7,y=9,lu_colors=lu_col,label="Avg. In: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg} - local avg_out = PowerIndicator{parent=rect,x=7,y=10,lu_colors=lu_col,label="Avg. Out:",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg} + local status = StateIndicator{parent=rect,x=10,y=1,states=style.imatrix.states,value=1,min_width=14} + local capacity = PowerIndicator{parent=rect,x=7,y=3,lu_colors=lu_col,label="Capacity:",format="%8.2f",value=0,width=26,fg_bg=text_fg} + local energy = PowerIndicator{parent=rect,x=7,y=4,lu_colors=lu_col,label="Energy: ",format="%8.2f",value=0,width=26,fg_bg=text_fg} + local avg_chg = PowerIndicator{parent=rect,x=7,y=5,lu_colors=lu_col,label="\xb7Average:",format="%8.2f",value=0,width=26,fg_bg=text_fg} + local input = PowerIndicator{parent=rect,x=7,y=6,lu_colors=lu_col,label="Input: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg} + local avg_in = PowerIndicator{parent=rect,x=7,y=7,lu_colors=lu_col,label="\xb7Average:",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg} + local output = PowerIndicator{parent=rect,x=7,y=8,lu_colors=lu_col,label="Output: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg} + local avg_out = PowerIndicator{parent=rect,x=7,y=9,lu_colors=lu_col,label="\xb7Average:",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg} + local trans_cap = PowerIndicator{parent=rect,x=7,y=10,lu_colors=lu_col,label="Max I/O: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg} status.register(ps, "computed_status", status.update) - energy.register(ps, "energy", function (val) energy.update(util.joules_to_fe(val)) end) capacity.register(ps, "max_energy", function (val) capacity.update(util.joules_to_fe(val)) end) - input.register(ps, "last_input", function (val) input.update(util.joules_to_fe(val)) end) - output.register(ps, "last_output", function (val) output.update(util.joules_to_fe(val)) end) - + energy.register(ps, "energy", function (val) energy.update(util.joules_to_fe(val)) end) avg_chg.register(ps, "avg_charge", avg_chg.update) + input.register(ps, "last_input", function (val) input.update(util.joules_to_fe(val)) end) avg_in.register(ps, "avg_inflow", avg_in.update) + output.register(ps, "last_output", function (val) output.update(util.joules_to_fe(val)) end) avg_out.register(ps, "avg_outflow", avg_out.update) + trans_cap.register(ps, "transfer_cap", function (val) trans_cap.update(util.joules_to_fe(val)) end) - local fill = DataIndicator{parent=rect,x=11,y=12,lu_colors=lu_col,label="Fill:",unit="%",format="%8.2f",value=0,width=18,fg_bg=text_fg} - - local cells = DataIndicator{parent=rect,x=11,y=14,lu_colors=lu_col,label="Cells: ",format="%7d",value=0,width=18,fg_bg=text_fg} - local providers = DataIndicator{parent=rect,x=11,y=15,lu_colors=lu_col,label="Providers:",format="%7d",value=0,width=18,fg_bg=text_fg} - - TextBox{parent=rect,text="Transfer Capacity",x=11,y=17,height=1,width=17,fg_bg=style.theme.label_fg} - local trans_cap = PowerIndicator{parent=rect,x=19,y=18,lu_colors=lu_col,label="",format="%5.2f",rate=true,value=0,width=12,fg_bg=text_fg} + local fill = DataIndicator{parent=rect,x=11,y=12,lu_colors=lu_col,label="Fill: ",format="%7.2f",unit="%",value=0,width=20,fg_bg=text_fg} + local cells = DataIndicator{parent=rect,x=11,y=13,lu_colors=lu_col,label="Cells: ",format="%7d",value=0,width=18,fg_bg=text_fg} + local providers = DataIndicator{parent=rect,x=11,y=14,lu_colors=lu_col,label="Providers:",format="%7d",value=0,width=18,fg_bg=text_fg} + fill.register(ps, "energy_fill", function (val) fill.update(val * 100) end) cells.register(ps, "cells", cells.update) providers.register(ps, "providers", providers.update) - fill.register(ps, "energy_fill", function (val) fill.update(val * 100) end) - trans_cap.register(ps, "transfer_cap", function (val) trans_cap.update(util.joules_to_fe(val)) end) + + local chging = IndicatorLight{parent=rect,x=11,y=16,label="Charging",colors=ind_wht} + local dischg = IndicatorLight{parent=rect,x=11,y=17,label="Discharging",colors=ind_wht} + local max_io = IndicatorLight{parent=rect,x=11,y=18,label="Max I/O Rate",colors=ind_yel} + + chging.register(ps, "is_charging", chging.update) + dischg.register(ps, "is_discharging", dischg.update) + max_io.register(ps, "at_max_io", max_io.update) local charge = VerticalBar{parent=rect,x=2,y=2,fg_bg=cpair(colors.green,colors.gray),height=17,width=4} local in_cap = VerticalBar{parent=rect,x=7,y=12,fg_bg=cpair(colors.red,colors.gray),height=7,width=1} local out_cap = VerticalBar{parent=rect,x=9,y=12,fg_bg=cpair(colors.blue,colors.gray),height=7,width=1} - TextBox{parent=rect,text="FILL",x=2,y=20,height=1,width=4,fg_bg=text_fg} - TextBox{parent=rect,text="I/O",x=7,y=20,height=1,width=3,fg_bg=text_fg} + TextBox{parent=rect,text="FILL I/O",x=2,y=20,height=1,width=8,fg_bg=label_fg} local function calc_saturation(val) if (type(data.build) == "table") and (type(data.build.transfer_cap) == "number") and (data.build.transfer_cap > 0) then @@ -91,6 +98,49 @@ local function new_view(root, x, y, data, ps, id) charge.register(ps, "energy_fill", charge.update) in_cap.register(ps, "last_input", function (val) in_cap.update(calc_saturation(val)) end) out_cap.register(ps, "last_output", function (val) out_cap.update(calc_saturation(val)) end) + + local eta = TextBox{parent=rect,x=11,y=20,width=20,height=1,text="ETA Unknown",alignment=ALIGN.CENTER,fg_bg=style.theme.field_box} + + eta.register(ps, "eta_mss", function (eta_ms) + local str, pre = "", util.trinary(eta_ms >= 0, "Full in ", "Empty in ") + + local seconds = math.abs(eta_ms) / 1000 + local minutes = seconds / 60 + local hours = minutes / 60 + local days = hours / 24 + + if math.abs(eta_ms) < 1000 or (eta_ms ~= eta_ms) then + -- really small or NaN + str = "No ETA" + elseif days < 1000 then + days = math.floor(days) + hours = math.floor(hours % 24) + minutes = math.floor(minutes % 60) + seconds = math.floor(seconds % 60) + + if days > 0 then + str = days .. "d" + elseif hours > 0 then + str = hours .. "h " .. minutes .. "m" + elseif minutes > 0 then + str = minutes .. "m " .. seconds .. "s" + elseif seconds > 0 then + str = seconds .. "s" + end + + str = pre .. str + else + local years = math.floor(days / 365.25) + + if years <= 99999999 then + str = pre .. years .. "y" + else + str = pre .. "eras" + end + end + + eta.set_value(str) + end) end return new_view diff --git a/scada-common/comms.lua b/scada-common/comms.lua index fffaaee..cce000c 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -17,7 +17,7 @@ local max_distance = nil local comms = {} -- protocol/data versions (protocol/data independent changes tracked by util.lua version) -comms.version = "2.5.0" +comms.version = "2.5.1" comms.api_version = "0.0.1" ---@enum PROTOCOL diff --git a/scada-common/util.lua b/scada-common/util.lua index 2f92424..d5c85fa 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -22,7 +22,7 @@ local t_pack = table.pack local util = {} -- scada-common version -util.version = "1.3.0" +util.version = "1.3.1" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 @@ -181,8 +181,7 @@ function util.round(x) return math.floor(x + 0.5) end -- get a new moving average object ---@nodiscard ---@param length integer history length ----@param default number value to fill history with for first call to compute() -function util.mov_avg(length, default) +function util.mov_avg(length) local data = {} local index = 1 local last_t = 0 ---@type number|nil @@ -215,12 +214,10 @@ function util.mov_avg(length, default) ---@return number average function public.compute() local sum = 0 - for i = 1, length do sum = sum + data[i] end - return sum / length + for i = 1, #data do sum = sum + data[i] end + return sum / #data end - public.reset(default) - return public end diff --git a/supervisor/facility.lua b/supervisor/facility.lua index d7058a8..0fd0ed9 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -128,9 +128,13 @@ function facility.new(config, cooling_conf) test_alarm_states = {}, -- statistics im_stat_init = false, - avg_charge = util.mov_avg(3, 0.0), - avg_inflow = util.mov_avg(6, 0.0), - avg_outflow = util.mov_avg(6, 0.0) + avg_charge = util.mov_avg(3), -- 3 seconds + avg_inflow = util.mov_avg(6), -- 3 seconds + avg_outflow = util.mov_avg(6), -- 3 seconds + -- induction matrix charge delta stats + avg_net = util.mov_avg(60), -- 60 seconds + charge_last = 0, + charge_last_t = 0 } -- create units @@ -307,15 +311,32 @@ function facility.new(config, cooling_conf) rate_update = db.state.last_update if (charge_update > 0) and (rate_update > 0) then + local energy = util.joules_to_fe(db.tanks.energy) + local input = util.joules_to_fe(db.state.last_input) + local output = util.joules_to_fe(db.state.last_output) + if self.im_stat_init then - self.avg_charge.record(util.joules_to_fe(db.tanks.energy), charge_update) - self.avg_inflow.record(util.joules_to_fe(db.state.last_input), rate_update) - self.avg_outflow.record(util.joules_to_fe(db.state.last_output), rate_update) + self.avg_charge.record(energy, charge_update) + self.avg_inflow.record(input, rate_update) + self.avg_outflow.record(output, rate_update) + + if charge_update ~= self.charge_last_t then + local delta = (energy - self.charge_last) / (charge_update - self.charge_last_t) + + self.charge_last = energy + self.charge_last_t = charge_update + + self.avg_net.record(delta, charge_update) + end else self.im_stat_init = true - self.avg_charge.reset(util.joules_to_fe(db.tanks.energy)) - self.avg_inflow.reset(util.joules_to_fe(db.state.last_input)) - self.avg_outflow.reset(util.joules_to_fe(db.state.last_output)) + + self.avg_charge.reset(energy) + self.avg_inflow.reset(input) + self.avg_outflow.reset(output) + + self.charge_last = energy + self.charge_last_t = charge_update end end else @@ -1193,15 +1214,21 @@ function facility.new(config, cooling_conf) status.power = { self.avg_charge.compute(), self.avg_inflow.compute(), - self.avg_outflow.compute() + self.avg_outflow.compute(), + 0 } -- status of induction matricies (including tanks) status.induction = {} for i = 1, #self.induction do - local matrix = self.induction[i] ---@type unit_session - local db = matrix.get_db() ---@type imatrix_session_db + local matrix = self.induction[i] ---@type unit_session + local db = matrix.get_db() ---@type imatrix_session_db + status.induction[i] = { matrix.is_faulted(), db.formed, db.state, db.tanks } + + local fe_per_ms = self.avg_net.compute() + local remaining = util.joules_to_fe(util.trinary(fe_per_ms >= 0, db.tanks.energy_need, db.tanks.energy)) + status.power[4] = remaining / fe_per_ms end -- status of sps diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 2a2202c..d15ddc5 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.3.7" +local SUPERVISOR_VERSION = "v1.3.8" local println = util.println local println_ts = util.println_ts diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 6fa4d0a..f0dccf2 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -71,8 +71,8 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) ---@class _unit_self local self = { r_id = reactor_id, - plc_s = nil, ---@class plc_session_struct - plc_i = nil, ---@class plc_session + plc_s = nil, ---@type plc_session_struct + plc_i = nil, ---@type plc_session num_boilers = num_boilers, num_turbines = num_turbines, types = { DT_KEYS = DT_KEYS, AISTATE = AISTATE }, From 826086951e211fdefbcc3159fb91dea03b491b84 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 27 Apr 2024 19:50:35 -0400 Subject: [PATCH 04/87] return zero on mov_avg compute if no samples --- scada-common/util.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scada-common/util.lua b/scada-common/util.lua index d5c85fa..3b876e3 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -213,8 +213,13 @@ function util.mov_avg(length) ---@nodiscard ---@return number average function public.compute() + if #data == 0 then return 0 end + local sum = 0 - for i = 1, #data do sum = sum + data[i] end + for i = 1, #data do + sum = sum + data[i] + end + return sum / #data end From 6f768ef6b3aea88defdd8c1944e7c419a8c0d5d0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 28 Apr 2024 01:26:44 -0400 Subject: [PATCH 05/87] #469 made ETA tolerant to induction matrix capacity changes --- scada-common/util.lua | 10 +++++++--- supervisor/facility.lua | 15 ++++++++++----- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/scada-common/util.lua b/scada-common/util.lua index 3b876e3..d29a85e 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -189,11 +189,15 @@ function util.mov_avg(length) ---@class moving_average local public = {} - -- reset all to a given value - ---@param x number value + -- reset all to a given value, or clear all data if no value is given + ---@param x number? value function public.reset(x) + index = 1 data = {} - for _ = 1, length do t_insert(data, x) end + + if x then + for _ = 1, length do t_insert(data, x) end + end end -- record a new value diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 0fd0ed9..51283d1 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -133,6 +133,7 @@ function facility.new(config, cooling_conf) avg_outflow = util.mov_avg(6), -- 3 seconds -- induction matrix charge delta stats avg_net = util.mov_avg(60), -- 60 seconds + last_capacity = 0, charge_last = 0, charge_last_t = 0 } @@ -326,7 +327,13 @@ function facility.new(config, cooling_conf) self.charge_last = energy self.charge_last_t = charge_update - self.avg_net.record(delta, charge_update) + -- if the capacity changed, toss out existing data + if db.build.max_energy ~= self.last_capacity then + self.last_capacity = db.build.max_energy + self.avg_net.reset() + else + self.avg_net.record(delta, charge_update) + end end else self.im_stat_init = true @@ -641,8 +648,7 @@ function facility.new(config, cooling_conf) local astatus = self.ascram_status if self.induction[1] ~= nil then - local matrix = self.induction[1] ---@type unit_session - local db = matrix.get_db() ---@type imatrix_session_db + local db = self.induction[1].get_db() ---@type imatrix_session_db -- clear matrix disconnected if astatus.matrix_dc then @@ -798,8 +804,7 @@ function facility.new(config, cooling_conf) -- update induction matrix related outputs if self.induction[1] ~= nil then - local matrix = self.induction[1] ---@type unit_session - local db = matrix.get_db() ---@type imatrix_session_db + local db = self.induction[1].get_db() ---@type imatrix_session_db self.io_ctl.digital_write(IO.F_MATRIX_LOW, db.tanks.energy_fill < const.RS_THRESHOLDS.IMATRIX_CHARGE_LOW) self.io_ctl.digital_write(IO.F_MATRIX_HIGH, db.tanks.energy_fill > const.RS_THRESHOLDS.IMATRIX_CHARGE_HIGH) From 50bf057ca61584d7f2fb68aeaeb6cf91343d1e37 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 28 Apr 2024 02:01:21 -0400 Subject: [PATCH 06/87] #412 optionally disable SPS at low power --- coordinator/iocontrol.lua | 5 +++- coordinator/process.lua | 18 +++++++++++- coordinator/ui/components/process_ctl.lua | 34 +++++++++-------------- scada-common/comms.lua | 3 +- supervisor/facility.lua | 33 ++++++++++++++++++++-- supervisor/session/coordinator.lua | 6 ++++ 6 files changed, 72 insertions(+), 27 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 7fc2707..5edd724 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -92,6 +92,7 @@ function iocontrol.init(conf, comms, temp_scale) ---@type WASTE_PRODUCT auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM, auto_pu_fallback_active = false, + auto_sps_disabled = false, radiation = types.new_zero_radiation_reading(), @@ -593,7 +594,7 @@ function iocontrol.update_facility_status(status) local ctl_status = status[1] - if type(ctl_status) == "table" and #ctl_status == 16 then + if type(ctl_status) == "table" and #ctl_status == 17 then fac.all_sys_ok = ctl_status[1] fac.auto_ready = ctl_status[2] @@ -644,9 +645,11 @@ function iocontrol.update_facility_status(status) fac.auto_current_waste_product = ctl_status[15] fac.auto_pu_fallback_active = ctl_status[16] + fac.auto_sps_disabled = ctl_status[17] fac.ps.publish("current_waste_product", fac.auto_current_waste_product) fac.ps.publish("pu_fallback_active", fac.auto_pu_fallback_active) + fac.ps.publish("sps_disabled_low_power", fac.auto_sps_disabled) else log.debug(log_header .. "control status not a table or length mismatch") valid = false diff --git a/coordinator/process.lua b/coordinator/process.lua index 581fcd9..1016e62 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -29,7 +29,8 @@ local self = { gen_target = 0.0, limits = {}, waste_product = PRODUCT.PLUTONIUM, - pu_fallback = false + pu_fallback = false, + sps_low_power = false }, waste_modes = {}, priority_groups = {} @@ -65,6 +66,7 @@ function process.init(iocontrol, coord_comms) ctl_proc.limits = config.limits ctl_proc.waste_product = config.waste_product ctl_proc.pu_fallback = config.pu_fallback + ctl_proc.sps_low_power = config.sps_low_power self.io.facility.ps.publish("process_mode", ctl_proc.mode) self.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target) @@ -72,6 +74,7 @@ function process.init(iocontrol, coord_comms) self.io.facility.ps.publish("process_gen_target", ctl_proc.gen_target) self.io.facility.ps.publish("process_waste_product", ctl_proc.waste_product) self.io.facility.ps.publish("process_pu_fallback", ctl_proc.pu_fallback) + self.io.facility.ps.publish("process_sps_low_power", ctl_proc.sps_low_power) for id = 1, math.min(#ctl_proc.limits, self.io.facility.num_units) do local unit = self.io.units[id] ---@type ioctl_unit @@ -83,6 +86,7 @@ function process.init(iocontrol, coord_comms) -- notify supervisor of auto waste config self.comms.send_fac_command(FAC_COMMAND.SET_WASTE_MODE, ctl_proc.waste_product) self.comms.send_fac_command(FAC_COMMAND.SET_PU_FB, ctl_proc.pu_fallback) + self.comms.send_fac_command(FAC_COMMAND.SET_SPS_LP, ctl_proc.sps_low_power) end -- unit waste states @@ -259,6 +263,18 @@ function process.set_pu_fallback(enabled) _write_auto_config() end +-- set automatic process control SPS usage at low power +---@param enabled boolean whether to enable SPS usage at low power +function process.set_sps_low_power(enabled) + self.comms.send_fac_command(FAC_COMMAND.SET_SPS_LP, enabled) + + log.debug(util.c("PROCESS: SET SPS LOW POWER ", enabled)) + + -- update config table and save + self.control_states.process.sps_low_power = enabled + _write_auto_config() +end + -- save process control settings ---@param mode PROCESS control mode ---@param burn_target number burn rate target diff --git a/coordinator/ui/components/process_ctl.lua b/coordinator/ui/components/process_ctl.lua index 430409b..92ed8bd 100644 --- a/coordinator/ui/components/process_ctl.lua +++ b/coordinator/ui/components/process_ctl.lua @@ -341,31 +341,23 @@ local function new_view(root, x, y) status.register(facility.ps, "current_waste_product", status.update) local waste_prod = RadioButton{parent=rect,x=2,y=3,options=style.waste.options,callback=process.set_process_waste,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.brown} - local pu_fallback = Checkbox{parent=rect,x=2,y=7,label="Pu Fallback",callback=process.set_pu_fallback,box_fg_bg=cpair(colors.green,style.theme.checkbox_bg)} - waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value) - pu_fallback.register(facility.ps, "process_pu_fallback", pu_fallback.set_value) + local fb_active = IndicatorLight{parent=rect,x=2,y=7,label="Fallback Active",colors=ind_wht} + local sps_disabled = IndicatorLight{parent=rect,x=2,y=8,label="SPS Disabled LC",colors=ind_yel} - local fb_active = IndicatorLight{parent=rect,x=2,y=9,label="Fallback Active",colors=ind_wht} + local pu_fallback = Checkbox{parent=rect,x=2,y=10,label="Pu Fallback",callback=process.set_pu_fallback,box_fg_bg=cpair(colors.brown,style.theme.checkbox_bg)} + + TextBox{parent=rect,x=2,y=12,height=3,text="Switch to Pu when SNAs cannot keep up with waste.",fg_bg=style.label} + + local lc_sps = Checkbox{parent=rect,x=2,y=16,label="Low Charge SPS",callback=process.set_sps_low_power,box_fg_bg=cpair(colors.brown,style.theme.checkbox_bg)} + + TextBox{parent=rect,x=2,y=18,height=3,text="Use SPS at low charge, otherwise switches to Po.",fg_bg=style.label} fb_active.register(facility.ps, "pu_fallback_active", fb_active.update) - - TextBox{parent=rect,x=2,y=11,text="Plutonium Rate",height=1,width=17,fg_bg=style.label} - local pu_rate = DataIndicator{parent=rect,x=2,label="",unit="mB/t",format="%12.2f",value=0,lu_colors=lu_cpair,fg_bg=s_field,width=17} - - TextBox{parent=rect,x=2,y=14,text="Polonium Rate",height=1,width=17,fg_bg=style.label} - local po_rate = DataIndicator{parent=rect,x=2,label="",unit="mB/t",format="%12.2f",value=0,lu_colors=lu_cpair,fg_bg=s_field,width=17} - - TextBox{parent=rect,x=2,y=17,text="Antimatter Rate",height=1,width=17,fg_bg=style.label} - local am_rate = DataIndicator{parent=rect,x=2,label="",unit="\xb5B/t",format="%12d",value=0,lu_colors=lu_cpair,fg_bg=s_field,width=17} - - pu_rate.register(facility.ps, "pu_rate", pu_rate.update) - po_rate.register(facility.ps, "po_rate", po_rate.update) - am_rate.register(facility.ps, "am_rate", am_rate.update) - - local sna_count = DataIndicator{parent=rect,x=2,y=20,label="Linked SNAs:",format="%4d",value=0,lu_colors=lu_cpair,width=17} - - sna_count.register(facility.ps, "sna_count", sna_count.update) + sps_disabled.register(facility.ps, "sps_disabled_low_power", sps_disabled.update) + waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value) + pu_fallback.register(facility.ps, "process_pu_fallback", pu_fallback.set_value) + lc_sps.register(facility.ps, "process_sps_low_power", lc_sps.set_value) end return new_view diff --git a/scada-common/comms.lua b/scada-common/comms.lua index cce000c..e107d33 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -97,7 +97,8 @@ local FAC_COMMAND = { START = 2, -- start automatic process control ACK_ALL_ALARMS = 3, -- acknowledge all alarms on all units SET_WASTE_MODE = 4, -- set automatic waste processing mode - SET_PU_FB = 5 -- set plutonium fallback mode + SET_PU_FB = 5, -- set plutonium fallback mode + SET_SPS_LP = 6 -- set SPS at low power mode } ---@enum UNIT_COMMAND diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 51283d1..94d6af1 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -120,6 +120,8 @@ function facility.new(config, cooling_conf) waste_product = WASTE.PLUTONIUM, current_waste_product = WASTE.PLUTONIUM, pu_fallback = false, + sps_low_power = false, + disabled_sps = false, -- alarm tones tone_states = {}, test_tone_set = false, @@ -840,9 +842,25 @@ function facility.new(config, cooling_conf) end -- update waste product - if self.waste_product == WASTE.PLUTONIUM or (self.pu_fallback and insufficent_po_rate) then + + self.current_waste_product = self.waste_product + + if (not self.sps_low_power) and (self.waste_product == WASTE.ANTI_MATTER) and (self.induction[1] ~= nil) then + local db = self.induction[1].get_db() ---@type imatrix_session_db + + if db.tanks.energy_fill >= 0.15 then + self.disabled_sps = false + elseif self.disabled_sps or ((db.tanks.last_update > 0) and (db.tanks.energy_fill < 0.1)) then + self.disabled_sps = true + self.current_waste_product = WASTE.POLONIUM + end + else + self.disabled_sps = false + end + + if self.pu_fallback and insufficent_po_rate then self.current_waste_product = WASTE.PLUTONIUM - else self.current_waste_product = self.waste_product end + end -- make sure dynamic tanks are allowing outflow if required -- set all, rather than trying to determine which is for which (simpler & safer) @@ -1099,6 +1117,14 @@ function facility.new(config, cooling_conf) return self.pu_fallback end + -- enable/disable SPS at low power + ---@param enabled boolean requested state + ---@return boolean enabled newly set value + function public.set_sps_low_power(enabled) + self.sps_low_power = enabled == true + return self.sps_low_power + end + --#endregion --#region Diagnostic Testing @@ -1203,7 +1229,8 @@ function facility.new(config, cooling_conf) self.status_text[2], self.group_map, self.current_waste_product, - (self.current_waste_product == WASTE.PLUTONIUM) and (self.waste_product ~= WASTE.PLUTONIUM) + self.pu_fallback and (self.current_waste_product == WASTE.PLUTONIUM) and (self.waste_product ~= WASTE.PLUTONIUM), + self.disabled_sps } end diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index c0e6482..9c9284f 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -270,6 +270,12 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil else log.debug(log_header .. "CRDN set pu fallback packet length mismatch") end + elseif cmd == FAC_COMMAND.SET_SPS_LP then + if pkt.length == 2 then + _send(CRDN_TYPE.FAC_CMD, { cmd, facility.set_sps_low_power(pkt.data[2]) }) + else + log.debug(log_header .. "CRDN set sps low power packet length mismatch") + end else log.debug(log_header .. "CRDN facility command unknown") end From 165d1497f86544728ed252378c9e9e67e70cef93 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 28 Apr 2024 02:01:40 -0400 Subject: [PATCH 07/87] reverted test change that got committed --- coordinator/ui/components/imatrix.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coordinator/ui/components/imatrix.lua b/coordinator/ui/components/imatrix.lua index 60a745a..b116e3d 100644 --- a/coordinator/ui/components/imatrix.lua +++ b/coordinator/ui/components/imatrix.lua @@ -101,7 +101,7 @@ local function new_view(root, x, y, data, ps, id) local eta = TextBox{parent=rect,x=11,y=20,width=20,height=1,text="ETA Unknown",alignment=ALIGN.CENTER,fg_bg=style.theme.field_box} - eta.register(ps, "eta_mss", function (eta_ms) + eta.register(ps, "eta_ms", function (eta_ms) local str, pre = "", util.trinary(eta_ms >= 0, "Full in ", "Empty in ") local seconds = math.abs(eta_ms) / 1000 From d35b824458d3fbd2b78f55c05c0a321e6210aad0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 28 Apr 2024 13:08:16 -0400 Subject: [PATCH 08/87] luacheck fix and cleanup --- coordinator/ui/components/process_ctl.lua | 8 +++++--- scada-common/rsio.lua | 2 +- scada-common/util.lua | 2 +- supervisor/facility.lua | 22 +++++++++++----------- supervisor/startup.lua | 2 +- test/rstest.lua | 2 +- 6 files changed, 20 insertions(+), 18 deletions(-) diff --git a/coordinator/ui/components/process_ctl.lua b/coordinator/ui/components/process_ctl.lua index 92ed8bd..6435de7 100644 --- a/coordinator/ui/components/process_ctl.lua +++ b/coordinator/ui/components/process_ctl.lua @@ -342,9 +342,14 @@ local function new_view(root, x, y) local waste_prod = RadioButton{parent=rect,x=2,y=3,options=style.waste.options,callback=process.set_process_waste,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.brown} + waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value) + local fb_active = IndicatorLight{parent=rect,x=2,y=7,label="Fallback Active",colors=ind_wht} local sps_disabled = IndicatorLight{parent=rect,x=2,y=8,label="SPS Disabled LC",colors=ind_yel} + fb_active.register(facility.ps, "pu_fallback_active", fb_active.update) + sps_disabled.register(facility.ps, "sps_disabled_low_power", sps_disabled.update) + local pu_fallback = Checkbox{parent=rect,x=2,y=10,label="Pu Fallback",callback=process.set_pu_fallback,box_fg_bg=cpair(colors.brown,style.theme.checkbox_bg)} TextBox{parent=rect,x=2,y=12,height=3,text="Switch to Pu when SNAs cannot keep up with waste.",fg_bg=style.label} @@ -353,9 +358,6 @@ local function new_view(root, x, y) TextBox{parent=rect,x=2,y=18,height=3,text="Use SPS at low charge, otherwise switches to Po.",fg_bg=style.label} - fb_active.register(facility.ps, "pu_fallback_active", fb_active.update) - sps_disabled.register(facility.ps, "sps_disabled_low_power", sps_disabled.update) - waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value) pu_fallback.register(facility.ps, "process_pu_fallback", pu_fallback.set_value) lc_sps.register(facility.ps, "process_sps_low_power", lc_sps.set_value) end diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index 8f4c558..fb3a50a 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -52,7 +52,7 @@ local IO_PORT = { -- facility F_ALARM = 7, -- active high, facility-wide alarm (any high priority unit alarm) F_ALARM_ANY = 8, -- active high, any alarm regardless of priority - F_MATRIX_LOW = 27, -- active high, induction matrix charge less than + F_MATRIX_LOW = 27, -- active high, induction matrix charge low F_MATRIX_HIGH = 28, -- active high, induction matrix charge high -- waste diff --git a/scada-common/util.lua b/scada-common/util.lua index d29a85e..0fe636d 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -22,7 +22,7 @@ local t_pack = table.pack local util = {} -- scada-common version -util.version = "1.3.1" +util.version = "1.3.0" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 94d6af1..937a8e7 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -135,9 +135,9 @@ function facility.new(config, cooling_conf) avg_outflow = util.mov_avg(6), -- 3 seconds -- induction matrix charge delta stats avg_net = util.mov_avg(60), -- 60 seconds - last_capacity = 0, - charge_last = 0, - charge_last_t = 0 + imtx_last_capacity = 0, + imtx_last_charge = 0, + imtx_last_charge_t = 0 } -- create units @@ -323,15 +323,15 @@ function facility.new(config, cooling_conf) self.avg_inflow.record(input, rate_update) self.avg_outflow.record(output, rate_update) - if charge_update ~= self.charge_last_t then - local delta = (energy - self.charge_last) / (charge_update - self.charge_last_t) + if charge_update ~= self.imtx_last_charge_t then + local delta = (energy - self.imtx_last_charge) / (charge_update - self.imtx_last_charge_t) - self.charge_last = energy - self.charge_last_t = charge_update + self.imtx_last_charge = energy + self.imtx_last_charge_t = charge_update -- if the capacity changed, toss out existing data - if db.build.max_energy ~= self.last_capacity then - self.last_capacity = db.build.max_energy + if db.build.max_energy ~= self.imtx_last_capacity then + self.imtx_last_capacity = db.build.max_energy self.avg_net.reset() else self.avg_net.record(delta, charge_update) @@ -344,8 +344,8 @@ function facility.new(config, cooling_conf) self.avg_inflow.reset(input) self.avg_outflow.reset(output) - self.charge_last = energy - self.charge_last_t = charge_update + self.imtx_last_charge = energy + self.imtx_last_charge_t = charge_update end end else diff --git a/supervisor/startup.lua b/supervisor/startup.lua index d15ddc5..2a2202c 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.3.8" +local SUPERVISOR_VERSION = "v1.3.7" local println = util.println local println_ts = util.println_ts diff --git a/test/rstest.lua b/test/rstest.lua index ba0b156..612d0d8 100644 --- a/test/rstest.lua +++ b/test/rstest.lua @@ -13,7 +13,7 @@ local print = util.print local println = util.println -- list of inverted digital signals
--- just using the key for a quick lookup, value need to be not nil +-- only using the key for a quick lookup, value just can't be nil local DIG_INV = { [IO.F_SCRAM] = 0, [IO.R_SCRAM] = 0, From eb45ff899bdd3b70293e2803317ef1ef0c9fb27d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 29 Apr 2024 22:03:54 -0400 Subject: [PATCH 09/87] #455 calculate reactor temp high limit --- scada-common/constants.lua | 10 ++++++---- supervisor/session/plc.lua | 23 +++++++++++++++++++++-- supervisor/startup.lua | 2 +- supervisor/unit.lua | 3 ++- supervisor/unitlogic.lua | 19 ++++++++++++++----- 5 files changed, 44 insertions(+), 13 deletions(-) diff --git a/scada-common/constants.lua b/scada-common/constants.lua index 678ea98..b2755b9 100644 --- a/scada-common/constants.lua +++ b/scada-common/constants.lua @@ -29,7 +29,7 @@ local annunc = {} annunc.RCSFlowLow_H2O = -3.2 -- flow < -3.2 mB/s annunc.RCSFlowLow_NA = -2.0 -- flow < -2.0 mB/s annunc.CoolantLevelLow = 0.4 -- fill < 40% -annunc.ReactorTempHigh = 1000 -- temp > 1000K +annunc.OpTempTolerance = 5 -- high temp if >= operational temp + X annunc.ReactorHighDeltaT = 50 -- rate > 50K/s annunc.FuelLevelLow = 0.05 -- fill <= 5% annunc.WasteLevelHigh = 0.80 -- fill >= 80% @@ -101,9 +101,11 @@ constants.EXTREME_RADIATION = 100.0 ---@class _mek_constants local mek = {} -mek.TURBINE_GAS_PER_TANK = 64000 -- mekanism: turbineGasPerTank -mek.TURBINE_DISPERSER_FLOW = 1280 -- mekanism: turbineDisperserGasFlow -mek.TURBINE_VENT_FLOW = 32000 -- mekanism: turbineVentGasFlow +mek.BASE_BOIL_TEMP = 373.15 -- mekanism: HeatUtils.BASE_BOIL_TEMP +mek.JOULES_PER_MB = 1000000 -- mekanism: energyPerFissionFuel +mek.TURBINE_GAS_PER_TANK = 64000 -- mekanism: turbineGasPerTank +mek.TURBINE_DISPERSER_FLOW = 1280 -- mekanism: turbineDisperserGasFlow +mek.TURBINE_VENT_FLOW = 32000 -- mekanism: turbineVentGasFlow constants.mek = mek diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 5bb097e..e058816 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -1,4 +1,5 @@ local comms = require("scada-common.comms") +local const = require("scada-common.constants") local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") local types = require("scada-common.types") @@ -105,6 +106,8 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f formed = false, rps_tripped = false, rps_trip_cause = "ok", ---@type rps_trip_cause + max_op_temp_H2O = 1200, + max_op_temp_Na = 1200, ---@class rps_status rps_status = { high_dmg = false, @@ -138,11 +141,11 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f waste = 0, waste_need = 0, waste_fill = 0.0, - ccool_type = "?", + ccool_type = types.FLUID.EMPTY_GAS, ---@type fluid ccool_amnt = 0, ccool_need = 0, ccool_fill = 0.0, - hcool_type = "?", + hcool_type = types.FLUID.EMPTY_GAS, ---@type fluid hcool_amnt = 0, hcool_need = 0, hcool_fill = 0.0 @@ -169,6 +172,21 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f ---@class plc_session local public = {} + -- compute maximum expected operational temperatures for high temp warnings + local function _compute_op_temps() + local JOULES_PER_MB = const.mek.JOULES_PER_MB + local BASE_BOIL_TEMP = const.mek.BASE_BOIL_TEMP + + local heat_cap = self.sDB.mek_struct.heat_cap + local max_burn = self.sDB.mek_struct.max_burn + + self.sDB.max_op_temp_H2O = max_burn * 2 * (JOULES_PER_MB * heat_cap ^ -1) + BASE_BOIL_TEMP + self.sDB.max_op_temp_Na = max_burn * (JOULES_PER_MB * heat_cap ^ -1) + BASE_BOIL_TEMP + + log.info(util.sprintf(log_header .. "computed maximum operational temperatures %.3f (H2O) and %.3f (Na)", + self.sDB.max_op_temp_H2O, self.sDB.max_op_temp_Na)) + end + -- copy in the RPS status ---@param rps_status table local function _copy_rps_status(rps_status) @@ -351,6 +369,7 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f local status = pcall(_copy_struct, pkt.data) if status then -- copied in structure data OK + _compute_op_temps() self.received_struct = true out_queue.push_data(svqtypes.SV_Q_DATA.PLC_BUILD_CHANGED, reactor_id) else diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 2a2202c..d15ddc5 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.3.7" +local SUPERVISOR_VERSION = "v1.3.8" local println = util.println local println_ts = util.println_ts diff --git a/supervisor/unit.lua b/supervisor/unit.lua index f0dccf2..8747b61 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -147,7 +147,8 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) }, damage = 0, temp = 0, - waste = 0 + waste = 0, + high_temp_lim = 1150 }, ---@class alarm_monitors alarms = { diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index ad2b522..3fe2ebc 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -133,7 +133,15 @@ function logic.update_annunciator(self) self.last_heartbeat = plc_db.last_status_update end - local flow_low = util.trinary(plc_db.mek_status.ccool_type == types.FLUID.SODIUM, ANNUNC_LIMS.RCSFlowLow_NA, ANNUNC_LIMS.RCSFlowLow_H2O) + local flow_low = ANNUNC_LIMS.RCSFlowLow_H2O + local high_temp = plc_db.max_op_temp_H2O + + if plc_db.mek_status.ccool_type == types.FLUID.SODIUM then + flow_low = ANNUNC_LIMS.RCSFlowLow_NA + high_temp = plc_db.max_op_temp_Na + end + + self.plc_cache.high_temp_lim = math.min(high_temp + ANNUNC_LIMS.OpTempTolerance, 1200) -- update other annunciator fields annunc.ReactorSCRAM = plc_db.rps_tripped @@ -142,7 +150,7 @@ function logic.update_annunciator(self) annunc.RCPTrip = plc_db.rps_tripped and (plc_db.rps_status.ex_hcool or plc_db.rps_status.low_cool) annunc.RCSFlowLow = _get_dt(DT_KEYS.ReactorCCool) < flow_low annunc.CoolantLevelLow = plc_db.mek_status.ccool_fill < ANNUNC_LIMS.CoolantLevelLow - annunc.ReactorTempHigh = plc_db.mek_status.temp > ANNUNC_LIMS.ReactorTempHigh + annunc.ReactorTempHigh = plc_db.mek_status.temp >= self.plc_cache.high_temp_lim annunc.ReactorHighDeltaT = _get_dt(DT_KEYS.ReactorTemp) > ANNUNC_LIMS.ReactorHighDeltaT annunc.FuelInputRateLow = _get_dt(DT_KEYS.ReactorFuel) < -1.0 or plc_db.mek_status.fuel_fill <= ANNUNC_LIMS.FuelLevelLow annunc.WasteLineOcclusion = _get_dt(DT_KEYS.ReactorWaste) > 1.0 or plc_db.mek_status.waste_fill >= ANNUNC_LIMS.WasteLevelHigh @@ -542,7 +550,8 @@ function logic.update_alarms(self) end -- High Temperature - _update_alarm_state(self, plc_cache.temp >= ALARM_LIMS.HIGH_TEMP, self.alarms.ReactorHighTemp) + local high_temp = math.min(math.max(self.plc_cache.high_temp_lim, 1100), 1199.995) + _update_alarm_state(self, plc_cache.temp >= high_temp, self.alarms.ReactorHighTemp) -- Waste Leak _update_alarm_state(self, plc_cache.waste >= 1.0, self.alarms.ReactorWasteLeak) @@ -718,11 +727,11 @@ function logic.update_status_text(self) self.status_text[2] = "elevated level of radiation" end elseif is_active(self.alarms.ReactorOverTemp) then - self.status_text = { "CORE OVER TEMP", "reactor core temperature >=1200K" } + self.status_text = { "CORE OVER TEMP", "reactor core temp damaging" } elseif is_active(self.alarms.ReactorWasteLeak) then self.status_text = { "WASTE LEAK", "radioactive waste leak detected" } elseif is_active(self.alarms.ReactorHighTemp) then - self.status_text = { "CORE TEMP HIGH", "reactor core temperature >1150K" } + self.status_text = { "CORE TEMP HIGH", "reactor core temperature high" } elseif is_active(self.alarms.ReactorHighWaste) then self.status_text = { "WASTE LEVEL HIGH", "waste accumulating in reactor" } elseif is_active(self.alarms.TurbineTrip) then From f621ff2482fef3cdc134dfaaad850ff1c6b04a57 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 30 Apr 2024 18:54:01 -0400 Subject: [PATCH 10/87] added some value inits and unit labels --- supervisor/facility.lua | 2 ++ supervisor/session/plc.lua | 2 +- supervisor/startup.lua | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 937a8e7..efbc7d5 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -343,7 +343,9 @@ function facility.new(config, cooling_conf) self.avg_charge.reset(energy) self.avg_inflow.reset(input) self.avg_outflow.reset(output) + self.avg_net.reset() + self.imtx_last_capacity = db.build.max_energy self.imtx_last_charge = energy self.imtx_last_charge_t = charge_update end diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index e058816..a863fe2 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -183,7 +183,7 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f self.sDB.max_op_temp_H2O = max_burn * 2 * (JOULES_PER_MB * heat_cap ^ -1) + BASE_BOIL_TEMP self.sDB.max_op_temp_Na = max_burn * (JOULES_PER_MB * heat_cap ^ -1) + BASE_BOIL_TEMP - log.info(util.sprintf(log_header .. "computed maximum operational temperatures %.3f (H2O) and %.3f (Na)", + log.info(util.sprintf(log_header .. "computed maximum operational temperatures %.3fK (H2O) and %.3fK (Na)", self.sDB.max_op_temp_H2O, self.sDB.max_op_temp_Na)) end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index d15ddc5..0c78ff7 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.3.8" +local SUPERVISOR_VERSION = "v1.3.9" local println = util.println local println_ts = util.println_ts From f958b0e3b787ed2289d88fb9778611223c2947b2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 30 Apr 2024 20:27:04 -0400 Subject: [PATCH 11/87] fixed at max i/o indicator --- coordinator/iocontrol.lua | 3 ++- coordinator/startup.lua | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 5edd724..9aa3dbc 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -684,7 +684,8 @@ function iocontrol.update_facility_status(status) ps.publish("is_discharging", out_f > in_f) if data and data.build then - ps.publish("at_max_io", in_f >= data.build.transfer_cap or out_f >= data.build.transfer_cap) + local cap = util.joules_to_fe(data.build.transfer_cap) + ps.publish("at_max_io", in_f >= cap or out_f >= cap) end else log.debug(log_header .. "power statistics list not a table") diff --git a/coordinator/startup.lua b/coordinator/startup.lua index b3df178..f4de35d 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") local threads = require("coordinator.threads") -local COORDINATOR_VERSION = "v1.4.4" +local COORDINATOR_VERSION = "v1.4.5" local CHUNK_LOAD_DELAY_S = 30.0 From 25dc47d520d6dfb3c2f0de40c81f861f933a1a0d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 30 Apr 2024 20:28:07 -0400 Subject: [PATCH 12/87] fixed recording bad stats on induction matrix faults --- supervisor/facility.lua | 32 +++++++++++++++++++++++++++----- supervisor/startup.lua | 2 +- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/supervisor/facility.lua b/supervisor/facility.lua index efbc7d5..8fb3746 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -137,7 +137,9 @@ function facility.new(config, cooling_conf) avg_net = util.mov_avg(60), -- 60 seconds imtx_last_capacity = 0, imtx_last_charge = 0, - imtx_last_charge_t = 0 + imtx_last_charge_t = 0, + -- track faulted induction matrix update times to reject + imtx_faulted_times = { 0, 0, 0 } } -- create units @@ -310,10 +312,26 @@ function facility.new(config, cooling_conf) local matrix = self.induction[1] ---@type unit_session local db = matrix.get_db() ---@type imatrix_session_db - charge_update = db.tanks.last_update + local build_update = db.build.last_update rate_update = db.state.last_update + charge_update = db.tanks.last_update - if (charge_update > 0) and (rate_update > 0) then + local has_data = build_update > 0 and rate_update > 0 and charge_update > 0 + + if matrix.is_faulted() then + -- a fault occured, cannot reliably update stats + has_data = false + self.im_stat_init = false + self.imtx_faulted_times = { build_update, rate_update, charge_update } + elseif not self.im_stat_init then + -- prevent operation with partially invalid data + -- all fields must have updated since the last fault + has_data = self.imtx_faulted_times[1] < build_update and + self.imtx_faulted_times[2] < rate_update and + self.imtx_faulted_times[3] < charge_update + end + + if has_data then local energy = util.joules_to_fe(db.tanks.energy) local input = util.joules_to_fe(db.state.last_input) local output = util.joules_to_fe(db.state.last_output) @@ -349,6 +367,10 @@ function facility.new(config, cooling_conf) self.imtx_last_charge = energy self.imtx_last_charge_t = charge_update end + else + -- prevent use by control systems + rate_update = 0 + charge_update = 0 end else self.im_stat_init = false @@ -507,7 +529,7 @@ function facility.new(config, cooling_conf) self.status_text = { "CHARGE MODE", "running control loop" } log.info("FAC: CHARGE mode starting PID control") - elseif self.last_update ~= charge_update then + elseif self.last_update < charge_update then -- convert to kFE to make constants not microscopic local error = util.round((self.charge_setpoint - avg_charge) / 1000) / 1000 @@ -581,7 +603,7 @@ function facility.new(config, cooling_conf) self.status_text = { "GENERATION MODE", "running control loop" } log.info("FAC: GEN_RATE process mode initial hold completed, starting PID control") end - elseif self.last_update ~= rate_update then + elseif self.last_update < rate_update then -- convert to MFE (in rounded kFE) to make constants not microscopic local error = util.round((self.gen_rate_setpoint - avg_inflow) / 1000) / 1000 diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 0c78ff7..86fd13b 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.3.9" +local SUPERVISOR_VERSION = "v1.3.10" local println = util.println local println_ts = util.println_ts From 3bfcb1d83cdf6f6c53c1bc209a28dc0d188ffc28 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 4 May 2024 13:50:53 -0400 Subject: [PATCH 13/87] #485 fixed assertion with height auto incrementing y when inheriting height --- graphics/core.lua | 2 +- graphics/element.lua | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/graphics/core.lua b/graphics/core.lua index b4e1d94..830ca69 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -7,7 +7,7 @@ local flasher = require("graphics.flasher") local core = {} -core.version = "2.2.3" +core.version = "2.2.4" core.flasher = flasher core.events = events diff --git a/graphics/element.lua b/graphics/element.lua index c7cc056..ef05911 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -3,6 +3,7 @@ -- local util = require("scada-common.util") +local log = require("scada-common.log") local core = require("graphics.core") @@ -198,6 +199,9 @@ function element.new(args, child_offset_x, child_offset_y) ---@param offset_y integer y offset for mouse events ---@param next_y integer next line if no y was provided function protected.prepare_template(offset_x, offset_y, next_y) + -- don't auto incrememnt y if inheriting height, that would cause an assertion + next_y = util.trinary(args.height == nil, 1, next_y) + -- record offsets in case there is a reposition self.offset_x = offset_x self.offset_y = offset_y From 0c6c7bf9a5969a866e1adf0882934c545dd0c11d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 9 May 2024 23:05:55 -0400 Subject: [PATCH 14/87] #200 work in progress unit views and sidebar/app updates --- graphics/elements/controls/app.lua | 10 +- graphics/elements/controls/sidebar.lua | 96 ++++++++--- pocket/iocontrol.lua | 219 ++++++++++++++++++++++++- pocket/ui/apps/dummy_app.lua | 2 + pocket/ui/main.lua | 12 +- pocket/ui/pages/home_page.lua | 25 +-- pocket/ui/pages/unit_page.lua | 125 +++++++++++++- 7 files changed, 435 insertions(+), 54 deletions(-) diff --git a/graphics/elements/controls/app.lua b/graphics/elements/controls/app.lua index f610393..4ac936d 100644 --- a/graphics/elements/controls/app.lua +++ b/graphics/elements/controls/app.lua @@ -30,7 +30,7 @@ local function app_button(args) element.assert(type(args.app_fg_bg) == "table", "app_fg_bg is a required field") args.height = 4 - args.width = 5 + args.width = 7 -- create new graphics element base object local e = element.new(args) @@ -46,7 +46,7 @@ local function app_button(args) end -- draw icon - e.w_set_cur(1, 1) + e.w_set_cur(2, 1) e.w_set_fgd(fgd) e.w_set_bkg(bkg) e.w_write("\x9f\x83\x83\x83") @@ -55,16 +55,16 @@ local function app_button(args) e.w_write("\x90") e.w_set_fgd(fgd) e.w_set_bkg(bkg) - e.w_set_cur(1, 2) + e.w_set_cur(2, 2) e.w_write("\x95 ") e.w_set_fgd(bkg) e.w_set_bkg(fgd) e.w_write("\x95") - e.w_set_cur(1, 3) + e.w_set_cur(2, 3) e.w_write("\x82\x8f\x8f\x8f\x81") -- write the icon text - e.w_set_cur(3, 2) + e.w_set_cur(4, 2) e.w_set_fgd(fgd) e.w_set_bkg(bkg) e.w_write(args.text) diff --git a/graphics/elements/controls/sidebar.lua b/graphics/elements/controls/sidebar.lua index fef8a8a..56fc89c 100644 --- a/graphics/elements/controls/sidebar.lua +++ b/graphics/elements/controls/sidebar.lua @@ -8,13 +8,7 @@ local element = require("graphics.element") local MOUSE_CLICK = core.events.MOUSE_CLICK ----@class sidebar_tab ----@field char string character identifier ----@field color cpair tab colors (fg/bg) - ---@class sidebar_args ----@field tabs table sidebar tab options ----@field callback function function to call on tab change ---@field parent graphics_element ---@field id? string element id ---@field x? integer 1 if omitted @@ -27,21 +21,16 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK ---@param args sidebar_args ---@return graphics_element element, element_id id local function sidebar(args) - element.assert(type(args.tabs) == "table", "tabs is a required field") - element.assert(#args.tabs > 0, "at least one tab is required") - element.assert(type(args.callback) == "function", "callback is a required field") - args.width = 3 -- create new graphics element base object local e = element.new(args) - element.assert(e.frame.h >= (#args.tabs * 3), "height insufficent to display all tabs") - -- default to 1st tab e.value = 1 local was_pressed = false + local tabs = {} -- show the button state ---@param pressed? boolean if the currently selected tab should appear as actively pressed @@ -51,10 +40,18 @@ local function sidebar(args) was_pressed = pressed pressed_idx = pressed_idx or e.value - for i = 1, #args.tabs do - local tab = args.tabs[i] ---@type sidebar_tab + -- clear + e.w_set_fgd(e.fg_bg.fgd) + e.w_set_bkg(e.fg_bg.bkg) + for y = 1, e.frame.h do + e.w_set_cur(1, y) + e.w_write(" ") + end - local y = ((i - 1) * 3) + 1 + -- draw tabs + for i = 1, #tabs do + local tab = tabs[i] ---@type sidebar_tab + local y = tab.y_start e.w_set_cur(1, y) @@ -66,13 +63,29 @@ local function sidebar(args) e.w_set_bkg(tab.color.bkg) end - e.w_write(" ") - e.w_set_cur(1, y + 1) - if e.value == i then - e.w_write(" " .. tab.char .. "\x10") - else e.w_write(" " .. tab.char .. " ") end - e.w_set_cur(1, y + 2) - e.w_write(" ") + if tab.tall then + e.w_write(" ") + e.w_set_cur(1, y + 1) + end + + e.w_write(tab.label) + + if tab.tall then + e.w_set_cur(1, y + 2) + e.w_write(" ") + end + end + end + + -- determine which tab was pressed + ---@param y integer y coordinate + local function find_tab(y) + for i = 1, #tabs do + local tab = tabs[i] ---@type sidebar_tab + + if y >= tab.y_start and y <= tab.y_end then + return i + end end end @@ -81,23 +94,23 @@ local function sidebar(args) function e.handle_mouse(event) -- determine what was pressed if e.enabled then - local cur_idx = math.ceil(event.current.y / 3) - local ini_idx = math.ceil(event.initial.y / 3) + local cur_idx = find_tab(event.current.y) + local ini_idx = find_tab(event.initial.y) - if args.tabs[cur_idx] ~= nil then + if tabs[cur_idx] ~= nil then if event.type == MOUSE_CLICK.TAP then e.value = cur_idx draw(true) -- show as unpressed in 0.25 seconds tcd.dispatch(0.25, function () draw(false) end) - args.callback(e.value) + tabs[cur_idx].callback() elseif event.type == MOUSE_CLICK.DOWN then draw(true, cur_idx) elseif event.type == MOUSE_CLICK.UP then if cur_idx == ini_idx and e.in_frame_bounds(event.current.x, event.current.y) then e.value = cur_idx draw(false) - args.callback(e.value) + tabs[cur_idx].callback() else draw(false) end end elseif event.type == MOUSE_CLICK.UP then @@ -113,6 +126,35 @@ local function sidebar(args) draw(false) end + -- update the sidebar nav options + ---@param items table sidebar entries + function e.on_update(items) + local next_y = 1 + + tabs = {} + + for i = 1, #items do + local item = items[i] + local height = util.trinary(item.tall, 3, 1) + + ---@class sidebar_tab + local entry = { + y_start = next_y, + y_end = next_y + height - 1, + tall = item.tall, + label = item.label, + color = item.color, + callback = item.callback + } + + next_y = next_y + height + + tabs[i] = entry + end + + draw() + end + -- element redraw e.redraw = draw diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 30f4879..cc50df6 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -5,8 +5,10 @@ local log = require("scada-common.log") local psil = require("scada-common.psil") local types = require("scada-common.types") +local util = require("scada-common.util") local ALARM = types.ALARM +local ALARM_STATE = types.ALARM_STATE ---@todo nominal trip time is ping (0ms to 10ms usually) local WARN_TT = 40 @@ -72,6 +74,10 @@ function iocontrol.alloc_nav() self.pane = root_pane end + function io.nav.set_sidebar(sidebar) + self.sidebar = sidebar + end + -- register an app ---@param app_id POCKET_APP_ID app ID ---@param container graphics_element element that contains this app (usually a Div) @@ -79,18 +85,37 @@ function iocontrol.alloc_nav() function io.nav.register_app(app_id, container, pane) ---@class pocket_app local app = { + loaded = false, + load = nil, root = { _p = nil, _c = {}, nav_to = function () end, tasks = {} }, ---@type nav_tree_page cur_page = nil, ---@type nav_tree_page pane = pane, - paned_pages = {} + paned_pages = {}, + sidebar_items = {} } + app.load = function () app.loaded = true end + -- 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 + function app.set_sidebar(items) + app.sidebar_items = items + if self.sidebar then self.sidebar.update(items) end + end + + -- function to run on initial load into memory + ---@param on_load function callback + function app.set_on_load(on_load) + app.load = function () + on_load() + app.loaded = true + end + end + -- if a pane was provided, this will switch between numbered pages ---@param idx integer page index function app.switcher(idx) @@ -160,9 +185,16 @@ function iocontrol.alloc_nav() -- open a given app ---@param app_id POCKET_APP_ID function io.nav.open_app(app_id) - if self.apps[app_id] then + local app = self.apps[app_id] ---@type pocket_app + if app then + if not app.loaded then app.load() end + self.cur_app = app_id self.pane.set_value(app_id) + + if #app.sidebar_items > 0 then + self.sidebar.update(app.sidebar_items) + end else log.debug("tried to open unknown app") end @@ -262,6 +294,16 @@ function iocontrol.init_fac(conf, temp_scale) auto_ramping = false, auto_saturated = false, + auto_scram = false, + ---@type ascram_status + ascram_status = { + matrix_dc = false, + matrix_fill = false, + crit_alarm = false, + radiation = false, + gen_fault = false + }, + ---@type WASTE_PRODUCT auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM, auto_pu_fallback_active = false, @@ -282,6 +324,179 @@ function iocontrol.init_fac(conf, temp_scale) env_d_ps = psil.create(), env_d_data = {} } + + -- create induction and SPS tables (currently only 1 of each is supported) + table.insert(io.facility.induction_ps_tbl, psil.create()) + table.insert(io.facility.induction_data_tbl, {}) + table.insert(io.facility.sps_ps_tbl, psil.create()) + table.insert(io.facility.sps_data_tbl, {}) + + -- determine tank information + if io.facility.tank_mode == 0 then + io.facility.tank_defs = {} + -- on facility tank mode 0, setup tank defs to match unit tank option + for i = 1, conf.num_units do + io.facility.tank_defs[i] = util.trinary(conf.cooling.r_cool[i].TankConnection, 1, 0) + end + + io.facility.tank_list = { table.unpack(io.facility.tank_defs) } + else + -- decode the layout of tanks from the connections definitions + local tank_mode = io.facility.tank_mode + local tank_defs = io.facility.tank_defs + local tank_list = { table.unpack(tank_defs) } + + local function calc_fdef(start_idx, end_idx) + local first = 4 + for i = start_idx, end_idx do + if io.facility.tank_defs[i] == 2 then + if i < first then first = i end + end + end + return first + end + + if tank_mode == 1 then + -- (1) 1 total facility tank (A A A A) + local first_fdef = calc_fdef(1, #tank_defs) + for i = 1, #tank_defs do + if i > first_fdef and tank_defs[i] == 2 then + tank_list[i] = 0 + end + end + elseif tank_mode == 2 then + -- (2) 2 total facility tanks (A A A B) + local first_fdef = calc_fdef(1, math.min(3, #tank_defs)) + for i = 1, #tank_defs do + if (i ~= 4) and (i > first_fdef) and (tank_defs[i] == 2) then + tank_list[i] = 0 + end + end + elseif tank_mode == 3 then + -- (3) 2 total facility tanks (A A B B) + for _, a in pairs({ 1, 3 }) do + local b = a + 1 + if (tank_defs[a] == 2) and (tank_defs[b] == 2) then + tank_list[b] = 0 + end + end + elseif tank_mode == 4 then + -- (4) 2 total facility tanks (A B B B) + local first_fdef = calc_fdef(2, #tank_defs) + for i = 1, #tank_defs do + if (i ~= 1) and (i > first_fdef) and (tank_defs[i] == 2) then + tank_list[i] = 0 + end + end + elseif tank_mode == 5 then + -- (5) 3 total facility tanks (A A B C) + local first_fdef = calc_fdef(1, math.min(2, #tank_defs)) + for i = 1, #tank_defs do + if (not (i == 3 or i == 4)) and (i > first_fdef) and (tank_defs[i] == 2) then + tank_list[i] = 0 + end + end + elseif tank_mode == 6 then + -- (6) 3 total facility tanks (A B B C) + local first_fdef = calc_fdef(2, math.min(3, #tank_defs)) + for i = 1, #tank_defs do + if (not (i == 1 or i == 4)) and (i > first_fdef) and (tank_defs[i] == 2) then + tank_list[i] = 0 + end + end + elseif tank_mode == 7 then + -- (7) 3 total facility tanks (A B C C) + local first_fdef = calc_fdef(3, #tank_defs) + for i = 1, #tank_defs do + if (not (i == 1 or i == 2)) and (i > first_fdef) and (tank_defs[i] == 2) then + tank_list[i] = 0 + end + end + end + + io.facility.tank_list = tank_list + end + + -- create facility tank tables + for i = 1, #io.facility.tank_list do + if io.facility.tank_list[i] == 2 then + table.insert(io.facility.tank_ps_tbl, psil.create()) + table.insert(io.facility.tank_data_tbl, {}) + end + end + + -- create unit data structures + io.units = {} + for i = 1, conf.num_units do + ---@class pioctl_unit + local entry = { + unit_id = i, + + num_boilers = 0, + num_turbines = 0, + num_snas = 0, + has_tank = conf.cooling.r_cool[i].TankConnection, + + control_state = false, + burn_rate_cmd = 0.0, + radiation = types.new_zero_radiation_reading(), + + sna_peak_rate = 0.0, + sna_max_rate = 0.0, + sna_out_rate = 0.0, + + waste_mode = types.WASTE_MODE.MANUAL_PLUTONIUM, + waste_product = types.WASTE_PRODUCT.PLUTONIUM, + + -- auto control group + a_group = 0, + + ---@type alarms + alarms = { ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE }, + + annunciator = {}, ---@type annunciator + + unit_ps = psil.create(), + reactor_data = {}, ---@type reactor_db + + boiler_ps_tbl = {}, + boiler_data_tbl = {}, + + turbine_ps_tbl = {}, + turbine_data_tbl = {}, + + tank_ps_tbl = {}, + tank_data_tbl = {} + } + + -- on other facility modes, overwrite unit TANK option with facility tank defs + if io.facility.tank_mode ~= 0 then + entry.has_tank = conf.cooling.fac_tank_defs[i] > 0 + end + + -- create boiler tables + for _ = 1, conf.cooling.r_cool[i].BoilerCount do + table.insert(entry.boiler_ps_tbl, psil.create()) + table.insert(entry.boiler_data_tbl, {}) + end + + -- create turbine tables + for _ = 1, conf.cooling.r_cool[i].TurbineCount do + table.insert(entry.turbine_ps_tbl, psil.create()) + table.insert(entry.turbine_data_tbl, {}) + end + + -- create tank tables + if io.facility.tank_defs[i] == 1 then + table.insert(entry.tank_ps_tbl, psil.create()) + table.insert(entry.tank_data_tbl, {}) + end + + entry.num_boilers = #entry.boiler_data_tbl + entry.num_turbines = #entry.turbine_data_tbl + + table.insert(io.units, entry) + end end -- set network link state diff --git a/pocket/ui/apps/dummy_app.lua b/pocket/ui/apps/dummy_app.lua index fe21db3..d7845a7 100644 --- a/pocket/ui/apps/dummy_app.lua +++ b/pocket/ui/apps/dummy_app.lua @@ -19,6 +19,8 @@ local function create_pages(root) 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} + + TextBox{parent=main,text=" pretend something cool is here \x03",x=1,y=10,alignment=core.ALIGN.CENTER,fg_bg=core.cpair(colors.gray,colors.black)} end return create_pages diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index 02e33fd..cbbeaa3 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -71,10 +71,6 @@ local function init(main) local page_div = Div{parent=main_pane,x=4,y=1} - local sidebar_tabs = { - { char = "#", color = cpair(colors.black, colors.green) } - } - home_page(page_div) unit_page(page_div) @@ -84,13 +80,13 @@ local function init(main) assert(#db.nav.get_containers() == iocontrol.APP_ID.NUM_APPS, "app IDs were not sequential or some apps weren't registered") - local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=db.nav.get_containers()} - 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=db.nav.open_app} + db.nav.set_pane(MultiPane{parent=page_div,x=1,y=1,panes=db.nav.get_containers()}) + db.nav.set_sidebar(Sidebar{parent=main_pane,x=1,y=1,height=18,fg_bg=cpair(colors.white,colors.gray)}) 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} + db.nav.open_app(iocontrol.APP_ID.ROOT) + --#endregion end diff --git a/pocket/ui/pages/home_page.lua b/pocket/ui/pages/home_page.lua index 0ea7c3f..483a881 100644 --- a/pocket/ui/pages/home_page.lua +++ b/pocket/ui/pages/home_page.lua @@ -39,21 +39,26 @@ local function new_view(root) local function open(id) db.nav.open_app(id) end + app.set_sidebar({ + { label = " #\x10", tall = true, color = core.cpair(colors.black, colors.green), callback = function () open(APP_ID.ROOT) 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} + App{parent=apps_1,x=2,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=9,y=2,text="F",title="Facil",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg} + App{parent=apps_1,x=16,y=2,text="\x15",title="Control",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=2,y=7,text="\x17",title="Process",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=9,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=16,y=7,text="\x08",title="Devices",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=2,y=12,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=9,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} + App{parent=apps_2,x=2,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=9,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=16,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 end diff --git a/pocket/ui/pages/unit_page.lua b/pocket/ui/pages/unit_page.lua index 273ad27..53dcce8 100644 --- a/pocket/ui/pages/unit_page.lua +++ b/pocket/ui/pages/unit_page.lua @@ -3,13 +3,31 @@ -- local iocontrol = require("pocket.iocontrol") +local util = require("scada-common.util") local core = require("graphics.core") local Div = require("graphics.elements.div") +local MultiPane = require("graphics.elements.multipane") local TextBox = require("graphics.elements.textbox") +local AlarmLight = require("graphics.elements.indicators.alight") +local CoreMap = require("graphics.elements.indicators.coremap") +local DataIndicator = require("graphics.elements.indicators.data") +local IconIndicator = require("graphics.elements.indicators.icon") +local IndicatorLight = require("graphics.elements.indicators.light") +local RadIndicator = require("graphics.elements.indicators.rad") +local TriIndicatorLight = require("graphics.elements.indicators.trilight") +local VerticalBar = require("graphics.elements.indicators.vbar") + +local HazardButton = require("graphics.elements.controls.hazard_button") +local MultiButton = require("graphics.elements.controls.multi_button") +local PushButton = require("graphics.elements.controls.push_button") +local RadioButton = require("graphics.elements.controls.radio_button") +local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") + local ALIGN = core.ALIGN +local cpair = core.cpair -- new unit page view ---@param root graphics_element parent @@ -21,9 +39,112 @@ local function new_view(root) 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=2,text="Units App",height=1,alignment=ALIGN.CENTER} - TextBox{parent=main,y=4,text="work in progress",height=1,alignment=ALIGN.CENTER} + TextBox{parent=main,y=4,text="Loading...",height=1,alignment=ALIGN.CENTER} + + local page_div = Div{parent=main,x=2,y=2,width=main.get_width()-2} + + local btn_fg_bg = cpair(colors.yellow, colors.black) + local btn_active = cpair(colors.white, colors.black) + local label = cpair(colors.lightGray, colors.black) + + local function set_sidebar(unit) + app.set_sidebar({ + { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(iocontrol.APP_ID.ROOT) end }, + { label = "U#" .. unit, color = core.cpair(colors.black, colors.yellow), callback = function () end }, + { label = "RPS", color = core.cpair(colors.black, colors.red), callback = function () end }, + { label = "RCS", color = core.cpair(colors.black, colors.blue), callback = function () end }, + { label = " R ", tall = true, color = core.cpair(colors.black, colors.orange), callback = function () end }, + }) + end + + local function load() + local u_pages = {} + + local active_unit = 1 + set_sidebar(active_unit) + + for _ = 1, db.facility.num_units do + local div = Div{parent=page_div} + table.insert(u_pages, div) + end + + local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=u_pages} + + local function prev(x) + active_unit = util.trinary(x == 1, db.facility.num_units, x - 1) + u_pane.set_value(active_unit) + set_sidebar(active_unit) + end + + local function next(x) + active_unit = util.trinary(x == db.facility.num_units, 1, x + 1) + u_pane.set_value(active_unit) + set_sidebar(active_unit) + end + + for i = 1, db.facility.num_units do + local u_div = u_pages[i] ---@type graphics_element + local unit = db.units[i] ---@type pioctl_unit + + TextBox{parent=u_div,y=1,text="Reactor Unit #"..i,height=1,alignment=ALIGN.CENTER} + PushButton{parent=u_div,x=1,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()prev(i)end} + PushButton{parent=u_div,x=21,y=1,text=">",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()next(i)end} + + local type = util.trinary(unit.num_boilers > 0, "Sodium Cooled Reactor", "Boiling Water Reactor") + TextBox{parent=u_div,y=3,text=type,height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.gray,colors.black)} + + local lu_col = cpair(colors.lightGray, colors.lightGray) + local text_fg = cpair(colors.white, colors._INHERIT) + + local rate = DataIndicator{parent=u_div,y=5,lu_colors=lu_col,label="Rate",unit="mB/t",format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg} + local temp = DataIndicator{parent=u_div,lu_colors=lu_col,label="Temp",unit="K",format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg} + + local basic_states = { + { color = cpair(colors.black,colors.lightGray), symbol = "\x07" }, + { color = cpair(colors.black,colors.red), symbol = "-" }, + { color = cpair(colors.black,colors.yellow), symbol = "\x1e" }, + { color = cpair(colors.black,colors.green), symbol = "+" } + } + + local mode_states = { + { color = cpair(colors.black,colors.lightGray), symbol = "\x07" }, + { color = cpair(colors.black,colors.red), symbol = "-" }, + { color = cpair(colors.black,colors.green), symbol = "+" }, + { color = cpair(colors.black,colors.purple), symbol = "A" } + } + + local ctrl = IconIndicator{parent=u_div,x=1,y=8,label="Control State",states=mode_states} + + ctrl.update(i+1) + + u_div.line_break() + + local rct = IconIndicator{parent=u_div,x=1,label="Fission Reactor",states=basic_states} + local rps = IconIndicator{parent=u_div,x=1,label="Protection System",states=basic_states} + + u_div.line_break() + + local rcs = IconIndicator{parent=u_div,x=1,label="Coolant System",states=basic_states} + + for b = 1, unit.num_boilers do + local blr = IconIndicator{parent=u_div,x=1,label="Boiler "..b,states=basic_states} + blr.update(b+2) + end + + for t = 1, unit.num_turbines do + local trb = IconIndicator{parent=u_div,x=1,label="Turbine "..t,states=basic_states} + trb.update(t) + end + + rct.update(4) + rps.update(3) + rcs.update(3) + end + end + + app.set_on_load(load) return main end From 3cd832ca20de7cf862ef89dc2c05dc91a84ee06a Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 10 May 2024 19:17:52 -0400 Subject: [PATCH 15/87] sidebar updates --- graphics/elements/controls/sidebar.lua | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/graphics/elements/controls/sidebar.lua b/graphics/elements/controls/sidebar.lua index 56fc89c..58e8b13 100644 --- a/graphics/elements/controls/sidebar.lua +++ b/graphics/elements/controls/sidebar.lua @@ -96,21 +96,23 @@ local function sidebar(args) if e.enabled then local cur_idx = find_tab(event.current.y) local ini_idx = find_tab(event.initial.y) + local tab = tabs[cur_idx] - if tabs[cur_idx] ~= nil then + -- handle press if a callback was provided + if tab ~= nil and type(tab.callback) == "function" then if event.type == MOUSE_CLICK.TAP then e.value = cur_idx draw(true) -- show as unpressed in 0.25 seconds tcd.dispatch(0.25, function () draw(false) end) - tabs[cur_idx].callback() + tab.callback() elseif event.type == MOUSE_CLICK.DOWN then draw(true, cur_idx) elseif event.type == MOUSE_CLICK.UP then if cur_idx == ini_idx and e.in_frame_bounds(event.current.x, event.current.y) then e.value = cur_idx draw(false) - tabs[cur_idx].callback() + tab.callback() else draw(false) end end elseif event.type == MOUSE_CLICK.UP then @@ -126,7 +128,7 @@ local function sidebar(args) draw(false) end - -- update the sidebar nav options + -- update the sidebar navigation options ---@param items table sidebar entries function e.on_update(items) local next_y = 1 @@ -139,12 +141,12 @@ local function sidebar(args) ---@class sidebar_tab local entry = { - y_start = next_y, - y_end = next_y + height - 1, - tall = item.tall, - label = item.label, - color = item.color, - callback = item.callback + y_start = next_y, ---@type integer + y_end = next_y + height - 1, ---@type integer + tall = item.tall, ---@type boolean + label = item.label, ---@type string + color = item.color, ---@type cpair + callback = item.callback ---@type function|nil } next_y = next_y + height From 0cb964a1777700dd73f61f29ec19c508fafde36e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 10 May 2024 19:18:21 -0400 Subject: [PATCH 16/87] diagnostic disables --- coordinator/configure.lua | 2 ++ rtu/configure.lua | 2 ++ 2 files changed, 4 insertions(+) diff --git a/coordinator/configure.lua b/coordinator/configure.lua index 54fb854..e763a27 100644 --- a/coordinator/configure.lua +++ b/coordinator/configure.lua @@ -1449,9 +1449,11 @@ function configurator.configure(start_code, message) elseif event == "paste" then display.handle_paste(param1) elseif event == "peripheral_detach" then +---@diagnostic disable-next-line: discard-returns ppm.handle_unmount(param1) tool_ctl.gen_mon_list() elseif event == "peripheral" then +---@diagnostic disable-next-line: discard-returns ppm.mount(param1) tool_ctl.gen_mon_list() elseif event == "monitor_resize" then diff --git a/rtu/configure.lua b/rtu/configure.lua index efebdee..9012705 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -1669,9 +1669,11 @@ function configurator.configure(ask_config) elseif event == "paste" then display.handle_paste(param1) elseif event == "peripheral_detach" then +---@diagnostic disable-next-line: discard-returns ppm.handle_unmount(param1) tool_ctl.update_peri_list() elseif event == "peripheral" then +---@diagnostic disable-next-line: discard-returns ppm.mount(param1) tool_ctl.update_peri_list() end From c181142f754e4c9243af000151c3cc2c69baff4e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 11 May 2024 15:03:14 -0400 Subject: [PATCH 17/87] added network details to about app --- pocket/iocontrol.lua | 9 +++++- pocket/pocket.lua | 6 ++-- pocket/ui/apps/sys_apps.lua | 55 +++++++++++++++++++++++++++++++++---- 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index cc50df6..403dde7 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -501,7 +501,9 @@ end -- set network link state ---@param state POCKET_LINK_STATE -function iocontrol.report_link_state(state) +---@param sv_addr integer? supervisor address if linked +---@param api_addr integer? coordinator address if linked +function iocontrol.report_link_state(state, sv_addr, api_addr) io.ps.publish("link_state", state) if state == LINK_STATE.API_LINK_ONLY or state == LINK_STATE.UNLINKED then @@ -511,6 +513,11 @@ function iocontrol.report_link_state(state) if state == LINK_STATE.SV_LINK_ONLY or state == LINK_STATE.UNLINKED then io.ps.publish("crd_conn_quality", 0) end + + if state == LINK_STATE.LINKED then + io.ps.publish("sv_addr", sv_addr) + io.ps.publish("api_addr", api_addr) + end end -- determine supervisor connection quality (trip time) diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 63149e8..7499fd5 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -192,7 +192,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) end else -- linked, all good! - iocontrol.report_link_state(LINK_STATE.LINKED) + iocontrol.report_link_state(LINK_STATE.LINKED, self.sv.addr, self.api.addr) end end @@ -358,7 +358,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) self.api.addr = src_addr if self.sv.linked then - iocontrol.report_link_state(LINK_STATE.LINKED) + iocontrol.report_link_state(LINK_STATE.LINKED, self.sv.addr, self.api.addr) else iocontrol.report_link_state(LINK_STATE.API_LINK_ONLY) end @@ -497,7 +497,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) self.sv.addr = src_addr if self.api.linked then - iocontrol.report_link_state(LINK_STATE.LINKED) + iocontrol.report_link_state(LINK_STATE.LINKED, self.sv.addr, self.api.addr) else iocontrol.report_link_state(LINK_STATE.SV_LINK_ONLY) end diff --git a/pocket/ui/apps/sys_apps.lua b/pocket/ui/apps/sys_apps.lua index a48f85f..aa20a8d 100644 --- a/pocket/ui/apps/sys_apps.lua +++ b/pocket/ui/apps/sys_apps.lua @@ -3,10 +3,12 @@ -- local comms = require("scada-common.comms") -local lockbox = require("lockbox") local util = require("scada-common.util") +local lockbox = require("lockbox") + local iocontrol = require("pocket.iocontrol") +local pocket = require("pocket.pocket") local core = require("graphics.core") @@ -35,8 +37,9 @@ local function create_pages(root) 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 nt_page = about_app.new_page(about_page, 2) + local fw_page = about_app.new_page(about_page, 3) + local hw_page = about_app.new_page(about_page, 4) local about = Div{parent=about_root,x=1,y=2} @@ -46,8 +49,42 @@ local function create_pages(root) 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} + PushButton{parent=about,x=2,y=3,text="Network >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=nt_page.nav_to} + PushButton{parent=about,x=2,y=4,text="Firmware >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fw_page.nav_to} + PushButton{parent=about,x=2,y=5,text="Host Details >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=hw_page.nav_to} + + --#region Network Details + + local config = pocket.config + + local nt_div = Div{parent=about_root,x=1,y=2} + TextBox{parent=nt_div,y=1,text="Network Details",height=1,alignment=ALIGN.CENTER} + + PushButton{parent=nt_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=about_page.nav_to} + + TextBox{parent=nt_div,x=2,y=3,text="Pocket Address",height=1,alignment=ALIGN.LEFT,fg_bg=label} +---@diagnostic disable-next-line: undefined-field + TextBox{parent=nt_div,x=2,text=util.c(os.getComputerID(),":",config.PKT_Channel),height=1,alignment=ALIGN.LEFT} + + nt_div.line_break() + TextBox{parent=nt_div,x=2,text="Supervisor Address",height=1,alignment=ALIGN.LEFT,fg_bg=label} + local sv = TextBox{parent=nt_div,x=2,text="",height=1,alignment=ALIGN.LEFT} + + nt_div.line_break() + TextBox{parent=nt_div,x=2,text="Coordinator Address",height=1,alignment=ALIGN.LEFT,fg_bg=label} + local coord = TextBox{parent=nt_div,x=2,text="",height=1,alignment=ALIGN.LEFT} + + sv.register(db.ps, "sv_addr", function (addr) sv.set_value(util.c(addr, ":", config.SVR_Channel)) end) + coord.register(db.ps, "api_addr", function (addr) coord.set_value(util.c(addr, ":", config.CRD_Channel)) end) + + nt_div.line_break() + TextBox{parent=nt_div,x=2,text="Message Authentication",height=1,alignment=ALIGN.LEFT,fg_bg=label} + local auth = util.trinary(type(config.AuthKey) == "string" and string.len(config.AuthKey) > 0, "HMAC-MD5", "None") + TextBox{parent=nt_div,x=2,text=auth,height=1,alignment=ALIGN.LEFT} + + --#endregion + + --#region Firmware Versions 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} @@ -81,6 +118,10 @@ local function create_pages(root) 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} + --#endregion + + --#region Host Versions + 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} @@ -94,7 +135,9 @@ local function create_pages(root) 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}} + --#endregion + + local root_pane = MultiPane{parent=about_root,x=1,y=1,panes={about,nt_div,fw_div,hw_div}} about_app.set_root_pane(root_pane) end From 6a8ed311f3718c9e02bcd4594386a8e90bd9e00f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 11 May 2024 19:19:52 -0400 Subject: [PATCH 18/87] #200 functioning pocket unit overview --- coordinator/iocontrol.lua | 19 ++++- coordinator/session/pocket.lua | 31 +++++---- pocket/iocontrol.lua | 124 +++++++++++++++++++++++++++++++-- pocket/pocket.lua | 24 ++++++- pocket/ui/pages/unit_page.lua | 98 ++++++++++++++++---------- scada-common/comms.lua | 4 +- 6 files changed, 242 insertions(+), 58 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 9aa3dbc..bf73e1f 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -228,6 +228,8 @@ function iocontrol.init(conf, comms, temp_scale) ---@class ioctl_unit local entry = { unit_id = i, + connected = false, + rtu_hw = { boilers = {}, turbines = {} }, num_boilers = 0, num_turbines = 0, @@ -319,12 +321,14 @@ function iocontrol.init(conf, comms, temp_scale) for _ = 1, conf.cooling.r_cool[i].BoilerCount do table.insert(entry.boiler_ps_tbl, psil.create()) table.insert(entry.boiler_data_tbl, {}) + table.insert(entry.rtu_hw.boilers, { connected = false, faulted = false }) end -- create turbine tables for _ = 1, conf.cooling.r_cool[i].TurbineCount do table.insert(entry.turbine_ps_tbl, psil.create()) table.insert(entry.turbine_data_tbl, {}) + table.insert(entry.rtu_hw.turbines, { connected = false, faulted = false }) end -- create tank tables @@ -897,6 +901,7 @@ function iocontrol.update_unit_statuses(statuses) end if #reactor_status == 0 then + unit.connected = false unit.unit_ps.publish("computed_status", 1) -- disconnected elseif #reactor_status == 3 then local mek_status = reactor_status[1] @@ -956,6 +961,8 @@ function iocontrol.update_unit_statuses(statuses) unit.unit_ps.publish(key, val) end end + + unit.connected = true else log.debug(log_header .. "reactor status length mismatch") valid = false @@ -970,7 +977,10 @@ function iocontrol.update_unit_statuses(statuses) local boil_sum = 0 for id = 1, #unit.boiler_ps_tbl do - if rtu_statuses.boilers[id] == nil then + local connected = rtu_statuses.boilers[id] ~= nil + unit.rtu_hw.boilers[id].connected = connected + + if not connected then -- disconnected unit.boiler_ps_tbl[id].publish("computed_status", 1) end @@ -982,6 +992,7 @@ function iocontrol.update_unit_statuses(statuses) local ps = unit.boiler_ps_tbl[id] ---@type psil local rtu_faulted = _record_multiblock_status(boiler, data, ps) + unit.rtu_hw.boilers[id].faulted = rtu_faulted if rtu_faulted then ps.publish("computed_status", 3) -- faulted @@ -1013,7 +1024,10 @@ function iocontrol.update_unit_statuses(statuses) local flow_sum = 0 for id = 1, #unit.turbine_ps_tbl do - if rtu_statuses.turbines[id] == nil then + local connected = rtu_statuses.turbines[id] ~= nil + unit.rtu_hw.turbines[id].connected = connected + + if not connected then -- disconnected unit.turbine_ps_tbl[id].publish("computed_status", 1) end @@ -1025,6 +1039,7 @@ function iocontrol.update_unit_statuses(statuses) local ps = unit.turbine_ps_tbl[id] ---@type psil local rtu_faulted = _record_multiblock_status(turbine, data, ps) + unit.rtu_hw.turbines[id].faulted = rtu_faulted if rtu_faulted then ps.publish("computed_status", 3) -- faulted diff --git a/coordinator/session/pocket.lua b/coordinator/session/pocket.lua index 7101297..09ec15c 100644 --- a/coordinator/session/pocket.lua +++ b/coordinator/session/pocket.lua @@ -138,21 +138,26 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout) } _send(CRDN_TYPE.API_GET_FAC, data) - elseif pkt.type == CRDN_TYPE.API_GET_UNITS then - local data = {} + elseif pkt.type == CRDN_TYPE.API_GET_UNIT then + if pkt.length == 1 and type(pkt.data[1]) == "number" then + local u = db.units[pkt.data[1]] ---@type ioctl_unit - 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 - }) + if u then + local data = { + u.unit_id, + u.connected, + u.rtu_hw, + u.alarms, + u.annunciator, + u.reactor_data, + u.boiler_data_tbl, + u.turbine_data_tbl, + u.tank_data_tbl + } + + _send(CRDN_TYPE.API_GET_UNIT, data) + end end - - _send(CRDN_TYPE.API_GET_UNITS, data) else log.debug(log_header .. "handler received unsupported CRDN packet type " .. pkt.type) end diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 403dde7..c171e9f 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -87,7 +87,6 @@ function iocontrol.alloc_nav() local app = { loaded = false, load = nil, - root = { _p = nil, _c = {}, nav_to = function () end, tasks = {} }, ---@type nav_tree_page cur_page = nil, ---@type nav_tree_page pane = pane, paned_pages = {}, @@ -132,9 +131,8 @@ function iocontrol.alloc_nav() ---@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 + if parent == nil and app.cur_page == nil then + app.cur_page = page end if type(nav_to) == "number" then @@ -259,6 +257,12 @@ function iocontrol.init_core(comms) alarm_buttons = {}, tone_indicators = {} -- indicators to update from supervisor tone states } + + -- API access + ---@class pocket_ioctl_api + io.api = { + get_unit = function (unit) comms.api__get_unit(unit) end + } end -- initialize facility-dependent components of pocket iocontrol @@ -431,6 +435,8 @@ function iocontrol.init_fac(conf, temp_scale) ---@class pioctl_unit local entry = { unit_id = i, + connected = false, + rtu_hw = {}, num_boilers = 0, num_turbines = 0, @@ -579,6 +585,116 @@ function iocontrol.record_facility_data(data) return valid end +-- update unit status data from API_GET_UNIT +---@param data table +function iocontrol.record_unit_data(data) + if type(data[1]) == "number" and io.units[data[1]] then + local unit = io.units[data[1]] ---@type pioctl_unit + + unit.connected = data[2] + unit.rtu_hw = data[3] + unit.alarms = data[4] + unit.annunciator = data[5] + + unit.reactor_data = data[6] + + local control_status = 1 + local reactor_status = 1 + local rps_status = 1 + + if not unit.connected then + -- disconnected + reactor_status = 1 + else + -- update RPS status + if unit.reactor_data.rps_tripped then + control_status = 2 + rps_status = util.trinary(unit.reactor_data.rps_trip_cause ~= "manual", 3, 2) + else rps_status = 4 end + + -- update reactor/control status + if unit.reactor_data.mek_status.status then + reactor_status = 4 + control_status = util.trinary(unit.annunciator.AutoControl, 4, 3) + else + if unit.reactor_data.no_reactor then + reactor_status = 2 + elseif not unit.reactor_data.formed or unit.reactor_data.rps_status.force_dis then + reactor_status = 3 + else + reactor_status = 4 + end + end + + for key, val in pairs(unit.reactor_data) do + if key ~= "rps_status" and key ~= "mek_struct" and key ~= "mek_status" then + unit.unit_ps.publish(key, val) + end + end + + if type(unit.reactor_data.rps_status) == "table" then + for key, val in pairs(unit.reactor_data.rps_status) do + unit.unit_ps.publish(key, val) + end + end + + if type(unit.reactor_data.mek_status) == "table" then + for key, val in pairs(unit.reactor_data.mek_status) do + unit.unit_ps.publish(key, val) + end + end + end + + unit.unit_ps.publish("U_ControlStatus", control_status) + unit.unit_ps.publish("U_ReactorStatus", reactor_status) + unit.unit_ps.publish("U_RPS", rps_status) + + unit.boiler_data_tbl = data[7] + + for id = 1, #unit.boiler_data_tbl do + local boiler = unit.boiler_data_tbl[id] ---@type boilerv_session_db + local ps = unit.boiler_ps_tbl[id] ---@type psil + + local boiler_status = 1 + + if unit.rtu_hw.boilers[id].connected then + if unit.rtu_hw.boilers[id].faulted then + boiler_status = 3 + elseif boiler.formed then + boiler_status = 4 + else + boiler_status = 2 + end + end + + ps.publish("BoilerStatus", boiler_status) + end + + unit.turbine_data_tbl = data[8] + + for id = 1, #unit.turbine_data_tbl do + local turbine = unit.turbine_data_tbl[id] ---@type turbinev_session_db + local ps = unit.turbine_ps_tbl[id] ---@type psil + + local turbine_status = 1 + + if unit.rtu_hw.turbines[id].connected then + if unit.rtu_hw.turbines[id].faulted then + turbine_status = 3 + elseif turbine.formed then + turbine_status = 4 + else + turbine_status = 2 + end + end + + ps.publish("TurbineStatus", turbine_status) + end + + unit.tank_data_tbl = data[9] + end +end + -- get the IO controller database function iocontrol.get_db() return io end diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 7499fd5..0482386 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -119,6 +119,20 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) self.api.seq_num = self.api.seq_num + 1 end + -- send an API packet to the coordinator + ---@param msg_type CRDN_TYPE + ---@param msg table + local function _send_api(msg_type, msg) + local s_pkt = comms.scada_packet() + local pkt = comms.crdn_packet() + + pkt.make(msg_type, msg) + s_pkt.make(self.api.addr, self.api.seq_num, PROTOCOL.SCADA_CRDN, pkt.raw_sendable()) + + nic.transmit(config.CRD_Channel, config.PKT_Channel, s_pkt) + self.api.seq_num = self.api.seq_num + 1 + end + -- attempt supervisor connection establishment local function _send_sv_establish() _send_sv(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PKT }) @@ -215,6 +229,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) if self.sv.linked then _send_sv(MGMT_TYPE.DIAG_ALARM_SET, { id, state }) end end + -- coordinator get unit data + function public.api__get_unit(unit) + if self.api.linked then _send_api(CRDN_TYPE.API_GET_UNIT, { unit }) end + end + -- parse a packet ---@param side string ---@param sender integer @@ -304,7 +323,10 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) if _check_length(packet, 11) then iocontrol.record_facility_data(packet.data) end - elseif packet.type == CRDN_TYPE.API_GET_UNITS then + elseif packet.type == CRDN_TYPE.API_GET_UNIT then + if _check_length(packet, 9) then + iocontrol.record_unit_data(packet.data) + end else _fail_type(packet) end else log.debug("discarding coordinator SCADA_CRDN packet before linked") diff --git a/pocket/ui/pages/unit_page.lua b/pocket/ui/pages/unit_page.lua index 53dcce8..f17e16d 100644 --- a/pocket/ui/pages/unit_page.lua +++ b/pocket/ui/pages/unit_page.lua @@ -2,13 +2,15 @@ -- Unit Overview Page -- -local iocontrol = require("pocket.iocontrol") local util = require("scada-common.util") +local log = require("scada-common.log") + +local iocontrol = require("pocket.iocontrol") local core = require("graphics.core") 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 AlarmLight = require("graphics.elements.indicators.alight") @@ -29,6 +31,20 @@ local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") local ALIGN = core.ALIGN local cpair = core.cpair +local basic_states = { + { color = cpair(colors.black, colors.lightGray), symbol = "\x07" }, + { color = cpair(colors.black, colors.red), symbol = "-" }, + { color = cpair(colors.black, colors.yellow), symbol = "\x1e" }, + { color = cpair(colors.black, colors.green), symbol = "+" } +} + +local mode_states = { + { color = cpair(colors.black, colors.lightGray), symbol = "\x07" }, + { color = cpair(colors.black, colors.red), symbol = "-" }, + { color = cpair(colors.black, colors.green), symbol = "+" }, + { color = cpair(colors.black, colors.purple), symbol = "A" } +} + -- new unit page view ---@param root graphics_element parent local function new_view(root) @@ -37,29 +53,41 @@ local function new_view(root) local main = Div{parent=root,x=1,y=1} local app = db.nav.register_app(iocontrol.APP_ID.UNITS, main) - app.new_page(nil, function () end) TextBox{parent=main,y=2,text="Units App",height=1,alignment=ALIGN.CENTER} TextBox{parent=main,y=4,text="Loading...",height=1,alignment=ALIGN.CENTER} - local page_div = Div{parent=main,x=2,y=2,width=main.get_width()-2} - local btn_fg_bg = cpair(colors.yellow, colors.black) local btn_active = cpair(colors.white, colors.black) local label = cpair(colors.lightGray, colors.black) - local function set_sidebar(unit) - app.set_sidebar({ + local function set_sidebar(id) + local unit = db.units[id] ---@type pioctl_unit + + local list = { { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(iocontrol.APP_ID.ROOT) end }, - { label = "U#" .. unit, color = core.cpair(colors.black, colors.yellow), callback = function () end }, - { label = "RPS", color = core.cpair(colors.black, colors.red), callback = function () end }, - { label = "RCS", color = core.cpair(colors.black, colors.blue), callback = function () end }, - { label = " R ", tall = true, color = core.cpair(colors.black, colors.orange), callback = function () end }, - }) + { label = "U-" .. id, color = core.cpair(colors.black, colors.yellow) }, + { label = " \x13 ", color = core.cpair(colors.black, colors.red), callback = function () end }, + { label = " R ", tall = true, color = core.cpair(colors.black, colors.lightGray), callback = function () end }, + { label = "RPS", color = core.cpair(colors.black, colors.cyan), callback = function () end }, + { label = "RCS", tall = true, color = core.cpair(colors.black, colors.blue), callback = function () end }, + } + + for i = 1, unit.num_boilers do + table.insert(list, { label = "B-" .. i, color = core.cpair(colors.black, colors.lightBlue), callback = function () end }) + end + + for i = 1, unit.num_turbines do + table.insert(list, { label = "T-" .. i, color = core.cpair(colors.black, colors.white), callback = function () end }) + end + + app.set_sidebar(list) end local function load() + local page_div = Div{parent=main,x=2,y=2,width=main.get_width()-2} + local u_pages = {} local active_unit = 1 @@ -71,16 +99,17 @@ local function new_view(root) end local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=u_pages} + app.set_root_pane(u_pane) local function prev(x) active_unit = util.trinary(x == 1, db.facility.num_units, x - 1) - u_pane.set_value(active_unit) + app.switcher(active_unit) set_sidebar(active_unit) end local function next(x) active_unit = util.trinary(x == db.facility.num_units, 1, x + 1) - u_pane.set_value(active_unit) + app.switcher(active_unit) set_sidebar(active_unit) end @@ -88,6 +117,16 @@ local function new_view(root) local u_div = u_pages[i] ---@type graphics_element local unit = db.units[i] ---@type pioctl_unit + local last_update = 0 + local function update() + if util.time_ms() - last_update >= 500 then + db.api.get_unit(i) + last_update = util.time_ms() + end + end + + app.new_page(nil, i).tasks = { update } + TextBox{parent=u_div,y=1,text="Reactor Unit #"..i,height=1,alignment=ALIGN.CENTER} PushButton{parent=u_div,x=1,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()prev(i)end} PushButton{parent=u_div,x=21,y=1,text=">",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()next(i)end} @@ -101,46 +140,33 @@ local function new_view(root) local rate = DataIndicator{parent=u_div,y=5,lu_colors=lu_col,label="Rate",unit="mB/t",format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg} local temp = DataIndicator{parent=u_div,lu_colors=lu_col,label="Temp",unit="K",format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg} - local basic_states = { - { color = cpair(colors.black,colors.lightGray), symbol = "\x07" }, - { color = cpair(colors.black,colors.red), symbol = "-" }, - { color = cpair(colors.black,colors.yellow), symbol = "\x1e" }, - { color = cpair(colors.black,colors.green), symbol = "+" } - } - - local mode_states = { - { color = cpair(colors.black,colors.lightGray), symbol = "\x07" }, - { color = cpair(colors.black,colors.red), symbol = "-" }, - { color = cpair(colors.black,colors.green), symbol = "+" }, - { color = cpair(colors.black,colors.purple), symbol = "A" } - } - local ctrl = IconIndicator{parent=u_div,x=1,y=8,label="Control State",states=mode_states} - ctrl.update(i+1) + rate.register(unit.unit_ps, "act_burn_rate", rate.update) + temp.register(unit.unit_ps, "temp", temp.update) + ctrl.register(unit.unit_ps, "U_ControlStatus", ctrl.update) u_div.line_break() local rct = IconIndicator{parent=u_div,x=1,label="Fission Reactor",states=basic_states} local rps = IconIndicator{parent=u_div,x=1,label="Protection System",states=basic_states} + rct.register(unit.unit_ps, "U_ReactorStatus", rct.update) + rps.register(unit.unit_ps, "U_RPS", rps.update) + u_div.line_break() local rcs = IconIndicator{parent=u_div,x=1,label="Coolant System",states=basic_states} for b = 1, unit.num_boilers do local blr = IconIndicator{parent=u_div,x=1,label="Boiler "..b,states=basic_states} - blr.update(b+2) + blr.register(unit.boiler_ps_tbl[b], "BoilerStatus", blr.update) end for t = 1, unit.num_turbines do - local trb = IconIndicator{parent=u_div,x=1,label="Turbine "..t,states=basic_states} - trb.update(t) + local tbn = IconIndicator{parent=u_div,x=1,label="Turbine "..t,states=basic_states} + tbn.register(unit.turbine_ps_tbl[t], "TurbineStatus", tbn.update) end - - rct.update(4) - rps.update(3) - rcs.update(3) end end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index e107d33..5e364e8 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -18,7 +18,7 @@ local comms = {} -- protocol/data versions (protocol/data independent changes tracked by util.lua version) comms.version = "2.5.1" -comms.api_version = "0.0.1" +comms.api_version = "0.0.2" ---@enum PROTOCOL local PROTOCOL = { @@ -67,7 +67,7 @@ local CRDN_TYPE = { UNIT_STATUSES = 5, -- state of each of the reactor units 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 + API_GET_UNIT = 8 -- API: get reactor unit data } ---@enum ESTABLISH_ACK From 7f009f9c866b3712503414f037efe2d9dc5cb99b Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 May 2024 13:28:34 -0400 Subject: [PATCH 19/87] #200 RPS view on pocket --- graphics/elements/indicators/icon.lua | 11 ++- pocket/iocontrol.lua | 2 +- pocket/ui/main.lua | 2 +- pocket/ui/pages/unit_page.lua | 112 ++++++++++++++++++++------ 4 files changed, 98 insertions(+), 29 deletions(-) diff --git a/graphics/elements/indicators/icon.lua b/graphics/elements/indicators/icon.lua index 5c2fc4e..402eb85 100644 --- a/graphics/elements/indicators/icon.lua +++ b/graphics/elements/indicators/icon.lua @@ -1,6 +1,7 @@ -- Icon Indicator Graphics Element local element = require("graphics.element") +local util = require("scada-common.util") ---@class icon_sym_color ---@field color cpair @@ -9,7 +10,7 @@ local element = require("graphics.element") ---@class icon_indicator_args ---@field label string indicator label ---@field states table state color and symbol table ----@field value? integer default state, defaults to 1 +---@field value? integer|boolean default state, defaults to 1 (true = 2, false = 1) ---@field min_label_width? integer label length if omitted ---@field parent graphics_element ---@field id? string element id @@ -33,6 +34,7 @@ local function icon(args) local e = element.new(args) e.value = args.value or 1 + if e.value == true then e.value = 2 end -- state blit strings local state_blit_cmds = {} @@ -47,8 +49,11 @@ local function icon(args) end -- on state change - ---@param new_state integer indicator state + ---@param new_state integer|boolean indicator state function e.on_update(new_state) + new_state = new_state or 1 + if new_state == true then new_state = 2 end + local blit_cmd = state_blit_cmds[new_state] e.value = new_state e.w_set_cur(1, 1) @@ -56,7 +61,7 @@ local function icon(args) end -- set indicator state - ---@param val integer indicator state + ---@param val integer|boolean indicator state function e.set_value(val) e.on_update(val) end -- element redraw diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index c171e9f..30bcbec 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -609,7 +609,7 @@ function iocontrol.record_unit_data(data) -- update RPS status if unit.reactor_data.rps_tripped then control_status = 2 - rps_status = util.trinary(unit.reactor_data.rps_trip_cause ~= "manual", 3, 2) + rps_status = util.trinary(unit.reactor_data.rps_trip_cause == "manual", 3, 2) else rps_status = 4 end -- update reactor/control status diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index cbbeaa3..87960f2 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -38,7 +38,7 @@ local function init(main) local db = iocontrol.get_db() -- window header message - TextBox{parent=main,y=1,text="DEV ALPHA APP S C ",alignment=ALIGN.LEFT,height=1,fg_bg=style.header} + TextBox{parent=main,y=1,text="WIP 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)} diff --git a/pocket/ui/pages/unit_page.lua b/pocket/ui/pages/unit_page.lua index f17e16d..e9d1816 100644 --- a/pocket/ui/pages/unit_page.lua +++ b/pocket/ui/pages/unit_page.lua @@ -45,6 +45,16 @@ local mode_states = { { color = cpair(colors.black, colors.purple), symbol = "A" } } +local red_ind_s = { + { color = cpair(colors.black, colors.lightGray), symbol = "+" }, + { color = cpair(colors.black, colors.red), symbol = "-" } +} + +local yel_ind_s = { + { color = cpair(colors.black, colors.lightGray), symbol = "+" }, + { color = cpair(colors.black, colors.yellow), symbol = "-" } +} + -- new unit page view ---@param root graphics_element parent local function new_view(root) @@ -60,27 +70,28 @@ local function new_view(root) local btn_fg_bg = cpair(colors.yellow, colors.black) local btn_active = cpair(colors.white, colors.black) - local label = cpair(colors.lightGray, colors.black) + + local nav_links = {} local function set_sidebar(id) - local unit = db.units[id] ---@type pioctl_unit + -- local unit = db.units[id] ---@type pioctl_unit local list = { { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(iocontrol.APP_ID.ROOT) end }, - { label = "U-" .. id, color = core.cpair(colors.black, colors.yellow) }, + { label = "U-" .. id, color = core.cpair(colors.black, colors.yellow), callback = function () app.switcher(id) end }, { label = " \x13 ", color = core.cpair(colors.black, colors.red), callback = function () end }, - { label = " R ", tall = true, color = core.cpair(colors.black, colors.lightGray), callback = function () end }, - { label = "RPS", color = core.cpair(colors.black, colors.cyan), callback = function () end }, + { label = "RPS", tall = true, color = core.cpair(colors.black, colors.cyan), callback = nav_links[id].rps }, + -- { label = " R ", color = core.cpair(colors.black, colors.lightGray), callback = function () end }, { label = "RCS", tall = true, color = core.cpair(colors.black, colors.blue), callback = function () end }, } - for i = 1, unit.num_boilers do - table.insert(list, { label = "B-" .. i, color = core.cpair(colors.black, colors.lightBlue), callback = function () end }) - end + -- for i = 1, unit.num_boilers do + -- table.insert(list, { label = "B-" .. i, color = core.cpair(colors.black, colors.lightBlue), callback = function () end }) + -- end - for i = 1, unit.num_turbines do - table.insert(list, { label = "T-" .. i, color = core.cpair(colors.black, colors.white), callback = function () end }) - end + -- for i = 1, unit.num_turbines do + -- table.insert(list, { label = "T-" .. i, color = core.cpair(colors.black, colors.white), callback = function () end }) + -- end app.set_sidebar(list) end @@ -88,25 +99,25 @@ local function new_view(root) local function load() local page_div = Div{parent=main,x=2,y=2,width=main.get_width()-2} - local u_pages = {} + local panes = {} local active_unit = 1 - set_sidebar(active_unit) + -- create all page divs for _ = 1, db.facility.num_units do local div = Div{parent=page_div} - table.insert(u_pages, div) + table.insert(panes, div) + table.insert(nav_links, {}) end - local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=u_pages} - app.set_root_pane(u_pane) - + -- previous unit local function prev(x) active_unit = util.trinary(x == 1, db.facility.num_units, x - 1) app.switcher(active_unit) set_sidebar(active_unit) end + -- next unit local function next(x) active_unit = util.trinary(x == db.facility.num_units, 1, x + 1) app.switcher(active_unit) @@ -114,9 +125,11 @@ local function new_view(root) end for i = 1, db.facility.num_units do - local u_div = u_pages[i] ---@type graphics_element + local u_div = panes[i] ---@type graphics_element local unit = db.units[i] ---@type pioctl_unit + local u_ps = unit.unit_ps + -- refresh data callback, every 500ms it will re-send the query local last_update = 0 local function update() if util.time_ms() - last_update >= 500 then @@ -125,7 +138,10 @@ local function new_view(root) end end - app.new_page(nil, i).tasks = { update } + -- Main Unit Overview + + local u_page = app.new_page(nil, i) + u_page.tasks = { update } TextBox{parent=u_div,y=1,text="Reactor Unit #"..i,height=1,alignment=ALIGN.CENTER} PushButton{parent=u_div,x=1,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()prev(i)end} @@ -142,17 +158,17 @@ local function new_view(root) local ctrl = IconIndicator{parent=u_div,x=1,y=8,label="Control State",states=mode_states} - rate.register(unit.unit_ps, "act_burn_rate", rate.update) - temp.register(unit.unit_ps, "temp", temp.update) - ctrl.register(unit.unit_ps, "U_ControlStatus", ctrl.update) + rate.register(u_ps, "act_burn_rate", rate.update) + temp.register(u_ps, "temp", temp.update) + ctrl.register(u_ps, "U_ControlStatus", ctrl.update) u_div.line_break() local rct = IconIndicator{parent=u_div,x=1,label="Fission Reactor",states=basic_states} local rps = IconIndicator{parent=u_div,x=1,label="Protection System",states=basic_states} - rct.register(unit.unit_ps, "U_ReactorStatus", rct.update) - rps.register(unit.unit_ps, "U_RPS", rps.update) + rct.register(u_ps, "U_ReactorStatus", rct.update) + rps.register(u_ps, "U_RPS", rps.update) u_div.line_break() @@ -167,7 +183,55 @@ local function new_view(root) local tbn = IconIndicator{parent=u_div,x=1,label="Turbine "..t,states=basic_states} tbn.register(unit.turbine_ps_tbl[t], "TurbineStatus", tbn.update) end + + -- RPS Tab + + local rps_div = Div{parent=page_div} + table.insert(panes, rps_div) + + TextBox{parent=rps_div,y=1,text="Protection System",height=1,alignment=ALIGN.CENTER} + + local r_trip = IconIndicator{parent=rps_div,x=1,y=3,label="RPS Trip",states=basic_states} + r_trip.register(u_ps, "U_RPS", r_trip.update) + + local r_mscrm = IconIndicator{parent=rps_div,x=1,y=5,label="Manual SCRAM",states=red_ind_s} + local r_ascrm = IconIndicator{parent=rps_div,x=1,label="Automatic SCRAM",states=red_ind_s} + local rps_tmo = IconIndicator{parent=rps_div,x=1,label="Timeout",states=yel_ind_s} + local rps_flt = IconIndicator{parent=rps_div,x=1,label="PLC Fault",states=yel_ind_s} + local rps_sfl = IconIndicator{parent=rps_div,x=1,label="RCT Fault",states=red_ind_s} + + r_mscrm.register(u_ps, "manual", r_mscrm.update) + r_ascrm.register(u_ps, "automatic", r_ascrm.update) + rps_tmo.register(u_ps, "timeout", rps_tmo.update) + rps_flt.register(u_ps, "fault", rps_flt.update) + rps_sfl.register(u_ps, "sys_fail", rps_sfl.update) + + rps_div.line_break() + local rps_dmg = IconIndicator{parent=rps_div,x=1,label="High Damage",states=red_ind_s} + local rps_tmp = IconIndicator{parent=rps_div,x=1,label="High Temperature",states=red_ind_s} + local rps_nof = IconIndicator{parent=rps_div,x=1,label="Low Fuel",states=yel_ind_s} + local rps_exw = IconIndicator{parent=rps_div,x=1,label="High Waste",states=yel_ind_s} + local rps_loc = IconIndicator{parent=rps_div,x=1,label="Low Coolant",states=yel_ind_s} + local rps_exh = IconIndicator{parent=rps_div,x=1,label="High Hot Coolant",states=yel_ind_s} + + rps_dmg.register(u_ps, "high_dmg", rps_dmg.update) + rps_tmp.register(u_ps, "high_temp", rps_tmp.update) + rps_nof.register(u_ps, "no_fuel", rps_nof.update) + rps_exw.register(u_ps, "ex_waste", rps_exw.update) + rps_loc.register(u_ps, "low_cool", rps_loc.update) + rps_exh.register(u_ps, "ex_hcool", rps_exh.update) + + local rps_page = app.new_page(u_page, #panes) + rps_page.tasks = { update } + + nav_links[i].rps = rps_page.nav_to end + + -- setup multipane + local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes} + app.set_root_pane(u_pane) + + set_sidebar(active_unit) end app.set_on_load(load) From b99cf19be0571e765a2b5dd721ca27a14e25b521 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 May 2024 13:36:46 -0400 Subject: [PATCH 20/87] #200 placeholder for alarm page, start of RCS page --- pocket/ui/pages/unit_page.lua | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/pocket/ui/pages/unit_page.lua b/pocket/ui/pages/unit_page.lua index e9d1816..0540760 100644 --- a/pocket/ui/pages/unit_page.lua +++ b/pocket/ui/pages/unit_page.lua @@ -79,7 +79,7 @@ local function new_view(root) local list = { { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(iocontrol.APP_ID.ROOT) end }, { label = "U-" .. id, color = core.cpair(colors.black, colors.yellow), callback = function () app.switcher(id) end }, - { label = " \x13 ", color = core.cpair(colors.black, colors.red), callback = function () end }, + { label = " \x13 ", color = core.cpair(colors.black, colors.red), callback = nav_links[id].alarm }, { label = "RPS", tall = true, color = core.cpair(colors.black, colors.cyan), callback = nav_links[id].rps }, -- { label = " R ", color = core.cpair(colors.black, colors.lightGray), callback = function () end }, { label = "RCS", tall = true, color = core.cpair(colors.black, colors.blue), callback = function () end }, @@ -184,11 +184,30 @@ local function new_view(root) tbn.register(unit.turbine_ps_tbl[t], "TurbineStatus", tbn.update) end + -- Alarms Tab + + local alm_div = Div{parent=page_div} + table.insert(panes, alm_div) + + local alm_page = app.new_page(u_page, #panes) + alm_page.tasks = { update } + + nav_links[i].alarm = alm_page.nav_to + + TextBox{parent=alm_div,y=1,text="Unit Alarms",height=1,alignment=ALIGN.CENTER} + + TextBox{parent=alm_div,y=3,text="work in progress",height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.gray,colors.black)} + -- RPS Tab local rps_div = Div{parent=page_div} table.insert(panes, rps_div) + local rps_page = app.new_page(u_page, #panes) + rps_page.tasks = { update } + + nav_links[i].rps = rps_page.nav_to + TextBox{parent=rps_div,y=1,text="Protection System",height=1,alignment=ALIGN.CENTER} local r_trip = IconIndicator{parent=rps_div,x=1,y=3,label="RPS Trip",states=basic_states} @@ -221,10 +240,18 @@ local function new_view(root) rps_loc.register(u_ps, "low_cool", rps_loc.update) rps_exh.register(u_ps, "ex_hcool", rps_exh.update) - local rps_page = app.new_page(u_page, #panes) - rps_page.tasks = { update } + -- RCS Tab + + local rcs_div = Div{parent=page_div} + table.insert(panes, rcs_div) + + local rcs_page = app.new_page(u_page, #panes) + rcs_page.tasks = { update } + + nav_links[i].rcs = rcs_page.nav_to + + TextBox{parent=rcs_div,y=1,text="Coolant System",height=1,alignment=ALIGN.CENTER} - nav_links[i].rps = rps_page.nav_to end -- setup multipane From 3e6a0a88698f0251a06148f1c469f72396f57711 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 May 2024 14:20:48 -0400 Subject: [PATCH 21/87] #200 updated RPS indicator text --- pocket/ui/pages/unit_page.lua | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pocket/ui/pages/unit_page.lua b/pocket/ui/pages/unit_page.lua index 0540760..c01ac4b 100644 --- a/pocket/ui/pages/unit_page.lua +++ b/pocket/ui/pages/unit_page.lua @@ -216,8 +216,8 @@ local function new_view(root) local r_mscrm = IconIndicator{parent=rps_div,x=1,y=5,label="Manual SCRAM",states=red_ind_s} local r_ascrm = IconIndicator{parent=rps_div,x=1,label="Automatic SCRAM",states=red_ind_s} local rps_tmo = IconIndicator{parent=rps_div,x=1,label="Timeout",states=yel_ind_s} - local rps_flt = IconIndicator{parent=rps_div,x=1,label="PLC Fault",states=yel_ind_s} - local rps_sfl = IconIndicator{parent=rps_div,x=1,label="RCT Fault",states=red_ind_s} + local rps_flt = IconIndicator{parent=rps_div,x=1,label="PPM Fault",states=yel_ind_s} + local rps_sfl = IconIndicator{parent=rps_div,x=1,label="Not Formed",states=red_ind_s} r_mscrm.register(u_ps, "manual", r_mscrm.update) r_ascrm.register(u_ps, "automatic", r_ascrm.update) @@ -226,12 +226,12 @@ local function new_view(root) rps_sfl.register(u_ps, "sys_fail", rps_sfl.update) rps_div.line_break() - local rps_dmg = IconIndicator{parent=rps_div,x=1,label="High Damage",states=red_ind_s} - local rps_tmp = IconIndicator{parent=rps_div,x=1,label="High Temperature",states=red_ind_s} - local rps_nof = IconIndicator{parent=rps_div,x=1,label="Low Fuel",states=yel_ind_s} - local rps_exw = IconIndicator{parent=rps_div,x=1,label="High Waste",states=yel_ind_s} - local rps_loc = IconIndicator{parent=rps_div,x=1,label="Low Coolant",states=yel_ind_s} - local rps_exh = IconIndicator{parent=rps_div,x=1,label="High Hot Coolant",states=yel_ind_s} + local rps_dmg = IconIndicator{parent=rps_div,x=1,label="Reactor Damage Hi",states=red_ind_s} + local rps_tmp = IconIndicator{parent=rps_div,x=1,label="Temp. Critical",states=red_ind_s} + local rps_nof = IconIndicator{parent=rps_div,x=1,label="Fuel Level Lo",states=yel_ind_s} + local rps_exw = IconIndicator{parent=rps_div,x=1,label="Waste Level Hi",states=yel_ind_s} + local rps_loc = IconIndicator{parent=rps_div,x=1,label="Coolant Lo Lo",states=yel_ind_s} + local rps_exh = IconIndicator{parent=rps_div,x=1,label="Heated Coolant Hi",states=yel_ind_s} rps_dmg.register(u_ps, "high_dmg", rps_dmg.update) rps_tmp.register(u_ps, "high_temp", rps_tmp.update) From afed6f514d711e6fd33aebd9d031f2fce592be02 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 May 2024 15:05:36 -0400 Subject: [PATCH 22/87] removed stray space in annunciator Coolant Level Low --- coordinator/ui/components/unit_detail.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index fe36a85..2c26b98 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -183,7 +183,7 @@ local function init(parent, id) local rad_wrn = IndicatorLight{parent=annunciator,label="Radiation Warning",colors=ind_yel} local r_rtrip = IndicatorLight{parent=annunciator,label="RCP Trip",colors=ind_red} local r_cflow = IndicatorLight{parent=annunciator,label="RCS Flow Low",colors=ind_yel} - local r_clow = IndicatorLight{parent=annunciator,label="Coolant Level Low",colors=ind_yel} + local r_clow = IndicatorLight{parent=annunciator,label="Coolant Level Low",colors=ind_yel} local r_temp = IndicatorLight{parent=annunciator,label="Reactor Temp. High",colors=ind_red} local r_rhdt = IndicatorLight{parent=annunciator,label="Reactor High Delta T",colors=ind_yel} local r_firl = IndicatorLight{parent=annunciator,label="Fuel Input Rate Low",colors=ind_yel} From 2b0a53629239b41c0ce62f8155148db70f55bd2e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 May 2024 15:14:58 -0400 Subject: [PATCH 23/87] #200 pocket RCS overview --- pocket/iocontrol.lua | 74 +++++++++++++++++++++++ pocket/ui/pages/unit_page.lua | 108 +++++++++++++++++++++++++--------- 2 files changed, 154 insertions(+), 28 deletions(-) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 30bcbec..ce76dc8 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -594,8 +594,80 @@ function iocontrol.record_unit_data(data) unit.connected = data[2] unit.rtu_hw = data[3] unit.alarms = data[4] + + --#region Annunciator + unit.annunciator = data[5] + local rcs_disconn, rcs_warn, rcs_hazard = false, false, false + + for key, val in pairs(unit.annunciator) do + if key == "BoilerOnline" or key == "TurbineOnline" then + -- split up online arrays + local every = true + for id = 1, #val do + every = every and val[id] + unit.boiler_ps_tbl[id].publish(key, val[id]) + end + + if not every then rcs_disconn = true end + + unit.unit_ps.publish("U_" .. key, every) + elseif key == "HeatingRateLow" or key == "WaterLevelLow" then + -- split up array for all boilers + local any = false + for id = 1, #val do + any = any or val[id] + unit.boiler_ps_tbl[id].publish(key, val[id]) + end + + if key == "HeatingRateLow" and any then + rcs_warn = true + elseif key == "WaterLevelLow" and any then + rcs_hazard = true + end + + unit.unit_ps.publish("U_" .. key, any) + elseif key == "SteamDumpOpen" or key == "TurbineOverSpeed" or key == "GeneratorTrip" or key == "TurbineTrip" then + -- split up array for all turbines + local any = false + for id = 1, #val do + any = any or val[id] + unit.turbine_ps_tbl[id].publish(key, val[id]) + end + + if key == "GeneratorTrip" and any then + rcs_warn = true + elseif (key == "TurbineOverSpeed" or key == "TurbineTrip") and any then + rcs_hazard = true + end + + unit.unit_ps.publish("U_" .. key, any) + else + -- non-table fields + unit.unit_ps.publish(key, val) + end + end + + local anc = unit.annunciator + rcs_warn = rcs_warn or anc.RCSFlowLow or anc.CoolantLevelLow or anc.RCPTrip or anc.RCSFault or anc.MaxWaterReturnFeed or + anc.CoolantFeedMismatch or anc.BoilRateMismatch or anc.SteamFeedMismatch or anc.MaxWaterReturnFeed + + local rcs_status = 4 + if rcs_hazard then + rcs_status = 2 + elseif rcs_warn then + rcs_status = 3 + elseif rcs_disconn then + rcs_status = 1 + end + + unit.unit_ps.publish("U_RCS", rcs_status) + + --#endregion + + --#region Reactor Data + unit.reactor_data = data[6] local control_status = 1 @@ -649,6 +721,8 @@ function iocontrol.record_unit_data(data) unit.unit_ps.publish("U_ReactorStatus", reactor_status) unit.unit_ps.publish("U_RPS", rps_status) + --#endregion + unit.boiler_data_tbl = data[7] for id = 1, #unit.boiler_data_tbl do diff --git a/pocket/ui/pages/unit_page.lua b/pocket/ui/pages/unit_page.lua index c01ac4b..da123e0 100644 --- a/pocket/ui/pages/unit_page.lua +++ b/pocket/ui/pages/unit_page.lua @@ -3,7 +3,7 @@ -- local util = require("scada-common.util") -local log = require("scada-common.log") +-- local log = require("scada-common.log") local iocontrol = require("pocket.iocontrol") @@ -13,20 +13,12 @@ local Div = require("graphics.elements.div") local MultiPane = require("graphics.elements.multipane") local TextBox = require("graphics.elements.textbox") -local AlarmLight = require("graphics.elements.indicators.alight") -local CoreMap = require("graphics.elements.indicators.coremap") local DataIndicator = require("graphics.elements.indicators.data") local IconIndicator = require("graphics.elements.indicators.icon") -local IndicatorLight = require("graphics.elements.indicators.light") -local RadIndicator = require("graphics.elements.indicators.rad") -local TriIndicatorLight = require("graphics.elements.indicators.trilight") -local VerticalBar = require("graphics.elements.indicators.vbar") +-- local RadIndicator = require("graphics.elements.indicators.rad") +-- local VerticalBar = require("graphics.elements.indicators.vbar") -local HazardButton = require("graphics.elements.controls.hazard_button") -local MultiButton = require("graphics.elements.controls.multi_button") local PushButton = require("graphics.elements.controls.push_button") -local RadioButton = require("graphics.elements.controls.radio_button") -local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") local ALIGN = core.ALIGN local cpair = core.cpair @@ -45,6 +37,12 @@ local mode_states = { { color = cpair(colors.black, colors.purple), symbol = "A" } } +local emc_ind_s = { + { color = cpair(colors.black, colors.gray), symbol = "-" }, + { color = cpair(colors.black, colors.white), symbol = "\x07" }, + { color = cpair(colors.black, colors.green), symbol = "+" } +} + local red_ind_s = { { color = cpair(colors.black, colors.lightGray), symbol = "+" }, { color = cpair(colors.black, colors.red), symbol = "-" } @@ -70,6 +68,7 @@ local function new_view(root) local btn_fg_bg = cpair(colors.yellow, colors.black) local btn_active = cpair(colors.white, colors.black) + -- local label = cpair(colors.lightGray, colors.black) local nav_links = {} @@ -82,7 +81,7 @@ local function new_view(root) { label = " \x13 ", color = core.cpair(colors.black, colors.red), callback = nav_links[id].alarm }, { label = "RPS", tall = true, color = core.cpair(colors.black, colors.cyan), callback = nav_links[id].rps }, -- { label = " R ", color = core.cpair(colors.black, colors.lightGray), callback = function () end }, - { label = "RCS", tall = true, color = core.cpair(colors.black, colors.blue), callback = function () end }, + { label = "RCS", tall = true, color = core.cpair(colors.black, colors.blue), callback = nav_links[id].rcs }, } -- for i = 1, unit.num_boilers do @@ -138,7 +137,7 @@ local function new_view(root) end end - -- Main Unit Overview + --#region Main Unit Overview local u_page = app.new_page(nil, i) u_page.tasks = { update } @@ -173,6 +172,7 @@ local function new_view(root) u_div.line_break() local rcs = IconIndicator{parent=u_div,x=1,label="Coolant System",states=basic_states} + rcs.register(u_ps, "U_RCS", rcs.update) for b = 1, unit.num_boilers do local blr = IconIndicator{parent=u_div,x=1,label="Boiler "..b,states=basic_states} @@ -184,7 +184,9 @@ local function new_view(root) tbn.register(unit.turbine_ps_tbl[t], "TurbineStatus", tbn.update) end - -- Alarms Tab + --#endregion + + --#region Alarms Tab local alm_div = Div{parent=page_div} table.insert(panes, alm_div) @@ -198,7 +200,9 @@ local function new_view(root) TextBox{parent=alm_div,y=3,text="work in progress",height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.gray,colors.black)} - -- RPS Tab + --#endregion + + --#region RPS Tab local rps_div = Div{parent=page_div} table.insert(panes, rps_div) @@ -210,14 +214,14 @@ local function new_view(root) TextBox{parent=rps_div,y=1,text="Protection System",height=1,alignment=ALIGN.CENTER} - local r_trip = IconIndicator{parent=rps_div,x=1,y=3,label="RPS Trip",states=basic_states} + local r_trip = IconIndicator{parent=rps_div,y=3,label="RPS Trip",states=basic_states} r_trip.register(u_ps, "U_RPS", r_trip.update) - local r_mscrm = IconIndicator{parent=rps_div,x=1,y=5,label="Manual SCRAM",states=red_ind_s} - local r_ascrm = IconIndicator{parent=rps_div,x=1,label="Automatic SCRAM",states=red_ind_s} - local rps_tmo = IconIndicator{parent=rps_div,x=1,label="Timeout",states=yel_ind_s} - local rps_flt = IconIndicator{parent=rps_div,x=1,label="PPM Fault",states=yel_ind_s} - local rps_sfl = IconIndicator{parent=rps_div,x=1,label="Not Formed",states=red_ind_s} + local r_mscrm = IconIndicator{parent=rps_div,y=5,label="Manual SCRAM",states=red_ind_s} + local r_ascrm = IconIndicator{parent=rps_div,label="Automatic SCRAM",states=red_ind_s} + local rps_tmo = IconIndicator{parent=rps_div,label="Timeout",states=yel_ind_s} + local rps_flt = IconIndicator{parent=rps_div,label="PPM Fault",states=yel_ind_s} + local rps_sfl = IconIndicator{parent=rps_div,label="Not Formed",states=red_ind_s} r_mscrm.register(u_ps, "manual", r_mscrm.update) r_ascrm.register(u_ps, "automatic", r_ascrm.update) @@ -226,12 +230,12 @@ local function new_view(root) rps_sfl.register(u_ps, "sys_fail", rps_sfl.update) rps_div.line_break() - local rps_dmg = IconIndicator{parent=rps_div,x=1,label="Reactor Damage Hi",states=red_ind_s} - local rps_tmp = IconIndicator{parent=rps_div,x=1,label="Temp. Critical",states=red_ind_s} - local rps_nof = IconIndicator{parent=rps_div,x=1,label="Fuel Level Lo",states=yel_ind_s} - local rps_exw = IconIndicator{parent=rps_div,x=1,label="Waste Level Hi",states=yel_ind_s} - local rps_loc = IconIndicator{parent=rps_div,x=1,label="Coolant Lo Lo",states=yel_ind_s} - local rps_exh = IconIndicator{parent=rps_div,x=1,label="Heated Coolant Hi",states=yel_ind_s} + local rps_dmg = IconIndicator{parent=rps_div,label="Reactor Damage Hi",states=red_ind_s} + local rps_tmp = IconIndicator{parent=rps_div,label="Temp. Critical",states=red_ind_s} + local rps_nof = IconIndicator{parent=rps_div,label="Fuel Level Lo",states=yel_ind_s} + local rps_exw = IconIndicator{parent=rps_div,label="Waste Level Hi",states=yel_ind_s} + local rps_loc = IconIndicator{parent=rps_div,label="Coolant Lo Lo",states=yel_ind_s} + local rps_exh = IconIndicator{parent=rps_div,label="Heated Coolant Hi",states=yel_ind_s} rps_dmg.register(u_ps, "high_dmg", rps_dmg.update) rps_tmp.register(u_ps, "high_temp", rps_tmp.update) @@ -240,7 +244,9 @@ local function new_view(root) rps_loc.register(u_ps, "low_cool", rps_loc.update) rps_exh.register(u_ps, "ex_hcool", rps_exh.update) - -- RCS Tab + --#endregion + + --#region RCS Tab local rcs_div = Div{parent=page_div} table.insert(panes, rcs_div) @@ -252,6 +258,52 @@ local function new_view(root) TextBox{parent=rcs_div,y=1,text="Coolant System",height=1,alignment=ALIGN.CENTER} + local r_rtrip = IconIndicator{parent=rcs_div,y=3,label="RCP Trip",states=red_ind_s} + local r_cflow = IconIndicator{parent=rcs_div,label="RCS Flow Lo",states=yel_ind_s} + local r_clow = IconIndicator{parent=rcs_div,label="Coolant Level Lo",states=yel_ind_s} + + r_rtrip.register(u_ps, "RCPTrip", r_rtrip.update) + r_cflow.register(u_ps, "RCSFlowLow", r_cflow.update) + r_clow.register(u_ps, "CoolantLevelLow", r_clow.update) + + local c_flt = IconIndicator{parent=rcs_div,label="RCS HW Fault",states=yel_ind_s} + local c_emg = IconIndicator{parent=rcs_div,label="Emergency Coolant",states=emc_ind_s} + local c_mwrf = IconIndicator{parent=rcs_div,label="Max Water Return",states=yel_ind_s} + + c_flt.register(u_ps, "RCSFault", c_flt.update) + c_emg.register(u_ps, "EmergencyCoolant", c_emg.update) + c_mwrf.register(u_ps, "MaxWaterReturnFeed", c_mwrf.update) + + -- rcs_div.line_break() + -- TextBox{parent=rcs_div,text="Mismatches",height=1,alignment=ALIGN.CENTER,fg_bg=label} + local c_cfm = IconIndicator{parent=rcs_div,label="Coolant Feed",states=yel_ind_s} + local c_brm = IconIndicator{parent=rcs_div,label="Boil Rate",states=yel_ind_s} + local c_sfm = IconIndicator{parent=rcs_div,label="Steam Feed",states=yel_ind_s} + + c_cfm.register(u_ps, "CoolantFeedMismatch", c_cfm.update) + c_brm.register(u_ps, "BoilRateMismatch", c_brm.update) + c_sfm.register(u_ps, "SteamFeedMismatch", c_sfm.update) + + rcs_div.line_break() + -- TextBox{parent=rcs_div,text="Aggregate Checks",height=1,alignment=ALIGN.CENTER,fg_bg=label} + + if unit.num_boilers > 0 then + local wll = IconIndicator{parent=rcs_div,label="Boiler Water Lo",states=red_ind_s} + local hrl = IconIndicator{parent=rcs_div,label="Heating Rate Lo",states=yel_ind_s} + + wll.register(u_ps, "U_WaterLevelLow", wll.update) + hrl.register(u_ps, "U_HeatingRateLow", hrl.update) + end + + local tospd = IconIndicator{parent=rcs_div,label="TRB Over Speed",states=red_ind_s} + local gtrip = IconIndicator{parent=rcs_div,label="Generator Trip",states=yel_ind_s} + local ttrip = IconIndicator{parent=rcs_div,label="Turbine Trip",states=red_ind_s} + + tospd.register(u_ps, "U_TurbineOverSpeed", tospd.update) + gtrip.register(u_ps, "U_GeneratorTrip", gtrip.update) + ttrip.register(u_ps, "U_TurbineTrip", ttrip.update) + + --#endregion end -- setup multipane From be560cd532747cc8b313e667c7532b045f7843ea Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 May 2024 15:19:01 -0400 Subject: [PATCH 24/87] luacheck fixes --- graphics/element.lua | 1 - graphics/elements/indicators/icon.lua | 1 - pocket/iocontrol.lua | 5 +---- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/graphics/element.lua b/graphics/element.lua index ef05911..731a22c 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -3,7 +3,6 @@ -- local util = require("scada-common.util") -local log = require("scada-common.log") local core = require("graphics.core") diff --git a/graphics/elements/indicators/icon.lua b/graphics/elements/indicators/icon.lua index 402eb85..15aef3a 100644 --- a/graphics/elements/indicators/icon.lua +++ b/graphics/elements/indicators/icon.lua @@ -1,7 +1,6 @@ -- Icon Indicator Graphics Element local element = require("graphics.element") -local util = require("scada-common.util") ---@class icon_sym_color ---@field color cpair diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index ce76dc8..b33c262 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -674,10 +674,7 @@ function iocontrol.record_unit_data(data) local reactor_status = 1 local rps_status = 1 - if not unit.connected then - -- disconnected - reactor_status = 1 - else + if unit.connected then -- update RPS status if unit.reactor_data.rps_tripped then control_status = 2 From 76e85da9d55504dc0bbe38035cda4cb7487f22c0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 12 May 2024 18:19:21 -0400 Subject: [PATCH 25/87] version increments and small fix --- coordinator/startup.lua | 2 +- pocket/iocontrol.lua | 3 ++- pocket/startup.lua | 2 +- rtu/startup.lua | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index f4de35d..0b8a06c 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") local threads = require("coordinator.threads") -local COORDINATOR_VERSION = "v1.4.5" +local COORDINATOR_VERSION = "v1.4.6" local CHUNK_LOAD_DELAY_S = 30.0 diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index b33c262..f6cbadb 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -650,7 +650,8 @@ function iocontrol.record_unit_data(data) end local anc = unit.annunciator - rcs_warn = rcs_warn or anc.RCSFlowLow or anc.CoolantLevelLow or anc.RCPTrip or anc.RCSFault or anc.MaxWaterReturnFeed or + rcs_hazard = rcs_hazard or anc.RCPTrip + rcs_warn = rcs_warn or anc.RCSFlowLow or anc.CoolantLevelLow or anc.RCSFault or anc.MaxWaterReturnFeed or anc.CoolantFeedMismatch or anc.BoilRateMismatch or anc.SteamFeedMismatch or anc.MaxWaterReturnFeed local rcs_status = 4 diff --git a/pocket/startup.lua b/pocket/startup.lua index 67e2f79..34cc9f7 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -18,7 +18,7 @@ local iocontrol = require("pocket.iocontrol") local pocket = require("pocket.pocket") local renderer = require("pocket.renderer") -local POCKET_VERSION = "v0.8.0-alpha" +local POCKET_VERSION = "v0.9.0-alpha" local println = util.println local println_ts = util.println_ts diff --git a/rtu/startup.lua b/rtu/startup.lua index 027549c..ae2e72b 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -31,7 +31,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.9.5" +local RTU_VERSION = "v1.9.6" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE From 8968ebede30cfbb1e4cf1feccdcd8fb8ce061b1c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 14 May 2024 20:00:34 -0400 Subject: [PATCH 26/87] #486 fixed pcall messages for newer CC versions --- reactor-plc/plc.lua | 8 ++++---- reactor-plc/startup.lua | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index fb906a3..2fb869a 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -25,8 +25,8 @@ local RPS_LIMITS = const.RPS_LIMITS -- I sure hope the devs don't change this error message, not that it would have safety implications -- I wish they didn't change it to be like this -local PCALL_SCRAM_MSG = "pcall: Scram requires the reactor to be active." -local PCALL_START_MSG = "pcall: Reactor is already active." +local PCALL_SCRAM_MSG = "Scram requires the reactor to be active." +local PCALL_START_MSG = "Reactor is already active." ---@type plc_config local config = {} @@ -307,7 +307,7 @@ function plc.rps_init(reactor, is_formed) log.info("RPS: reactor SCRAM") reactor.scram() - if reactor.__p_is_faulted() and (reactor.__p_last_fault() ~= PCALL_SCRAM_MSG) then + if reactor.__p_is_faulted() and not string.find(reactor.__p_last_fault(), PCALL_SCRAM_MSG) then log.error("RPS: failed reactor SCRAM") return false else @@ -325,7 +325,7 @@ function plc.rps_init(reactor, is_formed) log.info("RPS: reactor start") reactor.activate() - if reactor.__p_is_faulted() and (reactor.__p_last_fault() ~= PCALL_START_MSG) then + if reactor.__p_is_faulted() and not string.find(reactor.__p_last_fault(), PCALL_START_MSG) then log.error("RPS: failed reactor start") else self.reactor_enabled = true diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 18ea091..1b8b2d6 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -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.7.10" +local R_PLC_VERSION = "v1.7.11" local println = util.println local println_ts = util.println_ts From 41b7a68f3e92bd7c74f24209e9ff503e861b8d83 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 14 May 2024 20:10:21 -0400 Subject: [PATCH 27/87] #487 stop retrying failed disable when needing to enable --- supervisor/session/plc.lua | 5 ++++- supervisor/startup.lua | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index a863fe2..a4a8271 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -658,6 +658,7 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f local cmd = message.message if cmd == PLC_S_CMDS.ENABLE then -- enable reactor + self.acks.disable = true if not self.auto_lock then _send(RPLC_TYPE.RPS_ENABLE, {}) end @@ -714,6 +715,7 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f self.auto_cmd_token = 0 self.ramping_rate = true self.acks.burn_rate = false + self.acks.disable = true self.retry_times.burn_rate_req = util.time() + INITIAL_WAIT _send(RPLC_TYPE.MEK_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate }) end @@ -721,13 +723,14 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f elseif cmd.key == PLC_S_DATA.AUTO_BURN_RATE then -- set automatic burn rate if self.auto_lock then - cmd.val = math.floor(cmd.val * 100) / 100 -- round to 100ths place + cmd.val = math.floor(cmd.val * 100) / 100 -- round to 100ths place if cmd.val >= 0 and cmd.val <= self.sDB.mek_struct.max_burn then self.auto_cmd_token = util.time_ms() self.commanded_burn_rate = cmd.val -- this is only for manual control, only retry auto ramps self.acks.burn_rate = not self.ramping_rate + self.acks.disable = true self.retry_times.burn_rate_req = util.time() + INITIAL_AUTO_WAIT _send(RPLC_TYPE.AUTO_BURN_RATE, { self.commanded_burn_rate, self.ramping_rate, self.auto_cmd_token }) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 86fd13b..f83e973 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.3.10" +local SUPERVISOR_VERSION = "v1.3.11" local println = util.println local println_ts = util.println_ts From 9b8947fba2905969a2b891770c721c1b1c6a2237 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 16 May 2024 22:06:53 -0400 Subject: [PATCH 28/87] #491 fixed ps table indexing for boiler/turbine online --- pocket/iocontrol.lua | 10 ++++++++-- pocket/startup.lua | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index f6cbadb..ba283a0 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -603,11 +603,17 @@ function iocontrol.record_unit_data(data) for key, val in pairs(unit.annunciator) do if key == "BoilerOnline" or key == "TurbineOnline" then - -- split up online arrays local every = true + + -- split up online arrays for id = 1, #val do every = every and val[id] - unit.boiler_ps_tbl[id].publish(key, val[id]) + + if key == "BoilerOnline" then + unit.boiler_ps_tbl[id].publish(key, val[id]) + else + unit.turbine_ps_tbl[id].publish(key, val[id]) + end end if not every then rcs_disconn = true end diff --git a/pocket/startup.lua b/pocket/startup.lua index 34cc9f7..8ba8d3e 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -18,7 +18,7 @@ local iocontrol = require("pocket.iocontrol") local pocket = require("pocket.pocket") local renderer = require("pocket.renderer") -local POCKET_VERSION = "v0.9.0-alpha" +local POCKET_VERSION = "v0.9.1-alpha" local println = util.println local println_ts = util.println_ts From a268a770f2d017f72daa1670060300b29faa1b18 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 22 May 2024 21:55:59 +0000 Subject: [PATCH 29/87] #200 pocket alarm/status informational display, ECAM style --- coordinator/iocontrol.lua | 7 +- coordinator/session/pocket.lua | 4 +- pocket/iocontrol.lua | 512 +++++++++++++++++++++++---------- pocket/pocket.lua | 2 +- pocket/ui/main.lua | 2 +- pocket/ui/pages/unit_page.lua | 30 +- supervisor/unit.lua | 6 +- 7 files changed, 400 insertions(+), 163 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index bf73e1f..08fad13 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -247,6 +247,9 @@ function iocontrol.init(conf, comms, temp_scale) waste_mode = types.WASTE_MODE.MANUAL_PLUTONIUM, waste_product = types.WASTE_PRODUCT.PLUTONIUM, + last_rate_change_ms = 0, + turbine_flow_stable = false, + -- auto control group a_group = 0, @@ -1214,9 +1217,11 @@ function iocontrol.update_unit_statuses(statuses) local unit_state = status[5] if type(unit_state) == "table" then - if #unit_state == 6 then + if #unit_state == 8 then unit.waste_mode = unit_state[5] unit.waste_product = unit_state[6] + unit.last_rate_change_ms = unit_state[7] + unit.turbine_flow_stable = unit_state[8] unit.unit_ps.publish("U_StatusLine1", unit_state[1]) unit.unit_ps.publish("U_StatusLine2", unit_state[2]) diff --git a/coordinator/session/pocket.lua b/coordinator/session/pocket.lua index 09ec15c..cd03fc1 100644 --- a/coordinator/session/pocket.lua +++ b/coordinator/session/pocket.lua @@ -152,7 +152,9 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout) u.reactor_data, u.boiler_data_tbl, u.turbine_data_tbl, - u.tank_data_tbl + u.tank_data_tbl, + u.last_rate_change_ms, + u.turbine_flow_stable } _send(CRDN_TYPE.API_GET_UNIT, data) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index f6cbadb..b88fb39 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -2,6 +2,7 @@ -- I/O Control for Pocket Integration with Supervisor & Coordinator -- +local const = require("scada-common.constants") local log = require("scada-common.log") local psil = require("scada-common.psil") local types = require("scada-common.types") @@ -454,6 +455,9 @@ function iocontrol.init_fac(conf, temp_scale) waste_mode = types.WASTE_MODE.MANUAL_PLUTONIUM, waste_product = types.WASTE_PRODUCT.PLUTONIUM, + last_rate_change_ms = 0, + turbine_flow_stable = false, + -- auto control group a_group = 0, @@ -585,186 +589,384 @@ function iocontrol.record_facility_data(data) return valid end +local function tripped(state) return state == ALARM_STATE.TRIPPED or state == ALARM_STATE.ACKED end + -- update unit status data from API_GET_UNIT ---@param data table function iocontrol.record_unit_data(data) - if type(data[1]) == "number" and io.units[data[1]] then - local unit = io.units[data[1]] ---@type pioctl_unit + local unit = io.units[data[1]] ---@type pioctl_unit - unit.connected = data[2] - unit.rtu_hw = data[3] - unit.alarms = data[4] + unit.connected = data[2] + unit.rtu_hw = data[3] + unit.alarms = data[4] - --#region Annunciator + --#region Annunciator - unit.annunciator = data[5] + unit.annunciator = data[5] - local rcs_disconn, rcs_warn, rcs_hazard = false, false, false + local rcs_disconn, rcs_warn, rcs_hazard = false, false, false - for key, val in pairs(unit.annunciator) do - if key == "BoilerOnline" or key == "TurbineOnline" then - -- split up online arrays - local every = true - for id = 1, #val do - every = every and val[id] - unit.boiler_ps_tbl[id].publish(key, val[id]) - end + for key, val in pairs(unit.annunciator) do + if key == "BoilerOnline" or key == "TurbineOnline" then + -- split up online arrays + local every = true + for id = 1, #val do + every = every and val[id] + unit.boiler_ps_tbl[id].publish(key, val[id]) + end - if not every then rcs_disconn = true end + if not every then rcs_disconn = true end - unit.unit_ps.publish("U_" .. key, every) - elseif key == "HeatingRateLow" or key == "WaterLevelLow" then - -- split up array for all boilers - local any = false - for id = 1, #val do - any = any or val[id] - unit.boiler_ps_tbl[id].publish(key, val[id]) - end + unit.unit_ps.publish("U_" .. key, every) + elseif key == "HeatingRateLow" or key == "WaterLevelLow" then + -- split up array for all boilers + local any = false + for id = 1, #val do + any = any or val[id] + unit.boiler_ps_tbl[id].publish(key, val[id]) + end - if key == "HeatingRateLow" and any then - rcs_warn = true - elseif key == "WaterLevelLow" and any then - rcs_hazard = true - end + if key == "HeatingRateLow" and any then + rcs_warn = true + elseif key == "WaterLevelLow" and any then + rcs_hazard = true + end - unit.unit_ps.publish("U_" .. key, any) - elseif key == "SteamDumpOpen" or key == "TurbineOverSpeed" or key == "GeneratorTrip" or key == "TurbineTrip" then - -- split up array for all turbines - local any = false - for id = 1, #val do - any = any or val[id] - unit.turbine_ps_tbl[id].publish(key, val[id]) - end + unit.unit_ps.publish("U_" .. key, any) + elseif key == "SteamDumpOpen" or key == "TurbineOverSpeed" or key == "GeneratorTrip" or key == "TurbineTrip" then + -- split up array for all turbines + local any = false + for id = 1, #val do + any = any or val[id] + unit.turbine_ps_tbl[id].publish(key, val[id]) + end - if key == "GeneratorTrip" and any then - rcs_warn = true - elseif (key == "TurbineOverSpeed" or key == "TurbineTrip") and any then - rcs_hazard = true - end + if key == "GeneratorTrip" and any then + rcs_warn = true + elseif (key == "TurbineOverSpeed" or key == "TurbineTrip") and any then + rcs_hazard = true + end - unit.unit_ps.publish("U_" .. key, any) + unit.unit_ps.publish("U_" .. key, any) + else + -- non-table fields + unit.unit_ps.publish(key, val) + end + end + + local anc = unit.annunciator + rcs_hazard = rcs_hazard or anc.RCPTrip + rcs_warn = rcs_warn or anc.RCSFlowLow or anc.CoolantLevelLow or anc.RCSFault or anc.MaxWaterReturnFeed or + anc.CoolantFeedMismatch or anc.BoilRateMismatch or anc.SteamFeedMismatch or anc.MaxWaterReturnFeed + + local rcs_status = 4 + if rcs_hazard then + rcs_status = 2 + elseif rcs_warn then + rcs_status = 3 + elseif rcs_disconn then + rcs_status = 1 + end + + unit.unit_ps.publish("U_RCS", rcs_status) + + --#endregion + + --#region Reactor Data + + unit.reactor_data = data[6] + + local control_status = 1 + local reactor_status = 1 + local rps_status = 1 + + if unit.connected then + -- update RPS status + if unit.reactor_data.rps_tripped then + control_status = 2 + rps_status = util.trinary(unit.reactor_data.rps_trip_cause == "manual", 3, 2) + else rps_status = 4 end + + -- update reactor/control status + if unit.reactor_data.mek_status.status then + reactor_status = 4 + control_status = util.trinary(unit.annunciator.AutoControl, 4, 3) + else + if unit.reactor_data.no_reactor then + reactor_status = 2 + elseif not unit.reactor_data.formed or unit.reactor_data.rps_status.force_dis then + reactor_status = 3 else - -- non-table fields + reactor_status = 4 + end + end + + for key, val in pairs(unit.reactor_data) do + if key ~= "rps_status" and key ~= "mek_struct" and key ~= "mek_status" then unit.unit_ps.publish(key, val) end end - local anc = unit.annunciator - rcs_hazard = rcs_hazard or anc.RCPTrip - rcs_warn = rcs_warn or anc.RCSFlowLow or anc.CoolantLevelLow or anc.RCSFault or anc.MaxWaterReturnFeed or - anc.CoolantFeedMismatch or anc.BoilRateMismatch or anc.SteamFeedMismatch or anc.MaxWaterReturnFeed - - local rcs_status = 4 - if rcs_hazard then - rcs_status = 2 - elseif rcs_warn then - rcs_status = 3 - elseif rcs_disconn then - rcs_status = 1 - end - - unit.unit_ps.publish("U_RCS", rcs_status) - - --#endregion - - --#region Reactor Data - - unit.reactor_data = data[6] - - local control_status = 1 - local reactor_status = 1 - local rps_status = 1 - - if unit.connected then - -- update RPS status - if unit.reactor_data.rps_tripped then - control_status = 2 - rps_status = util.trinary(unit.reactor_data.rps_trip_cause == "manual", 3, 2) - else rps_status = 4 end - - -- update reactor/control status - if unit.reactor_data.mek_status.status then - reactor_status = 4 - control_status = util.trinary(unit.annunciator.AutoControl, 4, 3) - else - if unit.reactor_data.no_reactor then - reactor_status = 2 - elseif not unit.reactor_data.formed or unit.reactor_data.rps_status.force_dis then - reactor_status = 3 - else - reactor_status = 4 - end - end - - for key, val in pairs(unit.reactor_data) do - if key ~= "rps_status" and key ~= "mek_struct" and key ~= "mek_status" then - unit.unit_ps.publish(key, val) - end - end - - if type(unit.reactor_data.rps_status) == "table" then - for key, val in pairs(unit.reactor_data.rps_status) do - unit.unit_ps.publish(key, val) - end - end - - if type(unit.reactor_data.mek_status) == "table" then - for key, val in pairs(unit.reactor_data.mek_status) do - unit.unit_ps.publish(key, val) - end + if type(unit.reactor_data.rps_status) == "table" then + for key, val in pairs(unit.reactor_data.rps_status) do + unit.unit_ps.publish(key, val) end end - unit.unit_ps.publish("U_ControlStatus", control_status) - unit.unit_ps.publish("U_ReactorStatus", reactor_status) - unit.unit_ps.publish("U_RPS", rps_status) - - --#endregion - - unit.boiler_data_tbl = data[7] - - for id = 1, #unit.boiler_data_tbl do - local boiler = unit.boiler_data_tbl[id] ---@type boilerv_session_db - local ps = unit.boiler_ps_tbl[id] ---@type psil - - local boiler_status = 1 - - if unit.rtu_hw.boilers[id].connected then - if unit.rtu_hw.boilers[id].faulted then - boiler_status = 3 - elseif boiler.formed then - boiler_status = 4 - else - boiler_status = 2 - end + if type(unit.reactor_data.mek_status) == "table" then + for key, val in pairs(unit.reactor_data.mek_status) do + unit.unit_ps.publish(key, val) end - - ps.publish("BoilerStatus", boiler_status) end - - unit.turbine_data_tbl = data[8] - - for id = 1, #unit.turbine_data_tbl do - local turbine = unit.turbine_data_tbl[id] ---@type turbinev_session_db - local ps = unit.turbine_ps_tbl[id] ---@type psil - - local turbine_status = 1 - - if unit.rtu_hw.turbines[id].connected then - if unit.rtu_hw.turbines[id].faulted then - turbine_status = 3 - elseif turbine.formed then - turbine_status = 4 - else - turbine_status = 2 - end - end - - ps.publish("TurbineStatus", turbine_status) - end - - unit.tank_data_tbl = data[9] end + + unit.unit_ps.publish("U_ControlStatus", control_status) + unit.unit_ps.publish("U_ReactorStatus", reactor_status) + unit.unit_ps.publish("U_RPS", rps_status) + + --#endregion + + --#region RTU Devices + + unit.boiler_data_tbl = data[7] + + for id = 1, #unit.boiler_data_tbl do + local boiler = unit.boiler_data_tbl[id] ---@type boilerv_session_db + local ps = unit.boiler_ps_tbl[id] ---@type psil + + local boiler_status = 1 + + if unit.rtu_hw.boilers[id].connected then + if unit.rtu_hw.boilers[id].faulted then + boiler_status = 3 + elseif boiler.formed then + boiler_status = 4 + else + boiler_status = 2 + end + end + + ps.publish("BoilerStatus", boiler_status) + end + + unit.turbine_data_tbl = data[8] + + for id = 1, #unit.turbine_data_tbl do + local turbine = unit.turbine_data_tbl[id] ---@type turbinev_session_db + local ps = unit.turbine_ps_tbl[id] ---@type psil + + local turbine_status = 1 + + if unit.rtu_hw.turbines[id].connected then + if unit.rtu_hw.turbines[id].faulted then + turbine_status = 3 + elseif turbine.formed then + turbine_status = 4 + else + turbine_status = 2 + end + end + + ps.publish("TurbineStatus", turbine_status) + end + + unit.tank_data_tbl = data[9] + + --#endregion + + --#region Advanced Alarm Information Display + + local ecam = {} -- aviation reference :) back to VATSIM I go... + + if tripped(unit.alarms[ALARM.ContainmentBreach]) then + local items = { + { text = "REACTOR EXPLOSION", color = colors.white }, + { text = "WEAR HAZMAT SUIT", color = colors.blue } + } + + table.insert(ecam, { color = colors.red, text = "CONTAINMENT BREACH", help = "ContainmentBreach", items = items }) + end + + if tripped(unit.alarms[ALARM.ContainmentRadiation]) then + local items = { + { text = "RADIATION DETECTED", color = colors.white }, + { text = "WEAR HAZMAT SUIT", color = colors.blue }, + { text = "RESOLVE LEAK", color = colors.blue }, + { text = "AWAIT SAFE LEVELS", color = colors.white } + } + + table.insert(ecam, { color = colors.red, text = "RADIATION LEAK", help = "ContainmentRadiation", items = items }) + end + + if tripped(unit.alarms[ALARM.CriticalDamage]) then + local items = { + { text = "MELTDOWN IMMINENT", color = colors.white }, + { text = "CHECK PLC", color = colors.blue } + } + + table.insert(ecam, { color = colors.red, text = "RCT DAMAGE CRITICAL", help = "CriticalDamage", items = items }) + end + + if tripped(unit.alarms[ALARM.ReactorLost]) then + local items = { + { text = "REACTOR OFF-LINE", color = colors.white }, + { text = "CHECK PLC", color = colors.blue } + } + + table.insert(ecam, { color = colors.red, text = "REACTOR CONN LOST", help = "ReactorLost", items = items }) + end + + if tripped(unit.alarms[ALARM.ReactorDamage]) then + local items = { + { text = "REACTOR DAMAGED", color = colors.white }, + { text = "CHECK RCS", color = colors.blue }, + { text = "AWAIT DMG REDUCED", color = colors.blue } + } + + table.insert(ecam, { color = colors.red, text = "REACTOR DAMAGE", help = "ReactorDamage", items = items }) + end + + if tripped(unit.alarms[ALARM.ReactorOverTemp]) then + local items = { + { text = "AOA DAMAGE TEMP", color = colors.white }, + { text = "CHECK RCS", color = colors.blue }, + { text = "AWAIT COOLDOWN", color = colors.blue } + } + + table.insert(ecam, { color = colors.red, text = "REACTOR OVER TEMP", help = "ReactorOverTemp", items = items }) + end + + if tripped(unit.alarms[ALARM.ReactorHighTemp]) then + local items = { + { text = "OVER EXPECTED TEMP", color = colors.white }, + { text = "CHECK RCS", color = colors.blue } + } + + table.insert(ecam, { color = colors.yellow, text = "REACTOR HIGH TEMP", help = "ReactorHighTemp", items = items}) + end + + if tripped(unit.alarms[ALARM.ReactorWasteLeak]) then + local items = { + { text = "CHECK WASTE OUTPUT", color = colors.blue }, + { text = "DO NOT ENABLE RCT" } + } + + table.insert(ecam, { color = colors.red, text = "REACTOR WASTE LEAK", help = "ReactorWasteLeak", items = items}) + end + + if tripped(unit.alarms[ALARM.ReactorHighWaste]) then + local items = {{ text = "CHECK WASTE OUTPUT", color = colors.white }} + table.insert(ecam, { color = colors.yellow, text = "REACTOR WASTE HIGH", help = "ReactorHighWaste", items = items}) + end + + if tripped(unit.alarms[ALARM.RPSTransient]) then + local items = {} + local stat = unit.reactor_data.rps_status + + local function insert(cond, key, text, color) if cond[key] then table.insert(items, { text = text, help = key, color = color }) end end + + table.insert(items, { text = "REACTOR SCRAMMED", color = colors.white }) + insert(stat, "high_dmg", "HIGH DAMAGE", colors.red) + insert(stat, "high_temp", "HIGH TEMPERATURE", colors.red) + insert(stat, "low_cool", "CRIT LOW COOLANT") + insert(stat, "ex_waste", "EXCESS WASTE") + insert(stat, "ex_hcool", "EXCESS HEATED COOL") + insert(stat, "no_fuel", "NO FUEL") + insert(stat, "fault", "HARDWARE FAULT") + insert(stat, "timeout", "SUPERVISOR DISCONN") + insert(stat, "manual", "MANUAL SCRAM", colors.white) + insert(stat, "automatic", "AUTOMATIC SCRAM") + insert(stat, "sys_fail", "NOT FORMED", colors.red) + insert(stat, "force_dis", "FORCE DISABLED", colors.red) + + table.insert(ecam, { color = colors.yellow, text = "RPS TRANSIENT", help = "RPSTransient", items = items}) + end + + if tripped(unit.alarms[ALARM.RCSTransient]) then + local items = {} + local annunc = unit.annunciator + + local function insert(cond, key, text, color) + if cond == true or (type(cond) == "table" and cond[key]) then table.insert(items, { text = text, help = key, color = color }) end + end + + insert(annunc, "RCPTrip", "RCP TRIP", colors.red) + insert(annunc, "CoolantLevelLow", "LOW COOLANT") + + if unit.num_boilers == 0 then + if (util.time_ms() - unit.last_rate_change_ms) > const.FLOW_STABILITY_DELAY_MS then + insert(annunc, "BoilRateMismatch", "BOIL RATE MISMATCH") + end + + if unit.turbine_flow_stable then + insert(annunc, "RCSFlowLow", "RCS FLOW LOW") + insert(annunc, "CoolantFeedMismatch", "COOL FEED MISMATCH") + insert(annunc, "SteamFeedMismatch", "STM FEED MISMATCH") + end + else + if (util.time_ms() - unit.last_rate_change_ms) > const.FLOW_STABILITY_DELAY_MS then + insert(annunc, "RCSFlowLow", "RCS FLOW LOW") + insert(annunc, "BoilRateMismatch", "BOIL RATE MISMATCH") + insert(annunc, "CoolantFeedMismatch", "COOL FEED MISMATCH") + end + + if unit.turbine_flow_stable then + insert(annunc, "SteamFeedMismatch", "STM FEED MISMATCH") + end + end + + insert(annunc, "MaxWaterReturnFeed", "MAX WTR RTRN FEED") + + for k, v in ipairs(annunc.WaterLevelLow) do insert(v, "WaterLevelLow", "BOILER " .. k .. " WTR LOW", colors.red) end + for k, v in ipairs(annunc.HeatingRateLow) do insert(v, "HeatingRateLow", "BOILER " .. k .. " HEAT RATE") end + for k, v in ipairs(annunc.TurbineOverSpeed) do insert(v, "TurbineOverSpeed", "TURBINE " .. k .. " OVERSPD", colors.red) end + for k, v in ipairs(annunc.GeneratorTrip) do insert(v, "GeneratorTrip", "TURBINE " .. k .. " GEN TRIP") end + + table.insert(ecam, { color = colors.yellow, text = "RCS TRANSIENT", help = "RCSTransient", items = items}) + end + + if tripped(unit.alarms[ALARM.TurbineTrip]) then + local items = {} + + for k, v in ipairs(unit.annunciator.TurbineTrip) do + if v then table.insert(items, { text = "TURBINE " .. k .. " TRIP", help = "TurbineTrip" }) end + end + + table.insert(items, { text = "CHECK ENERGY OUT", color = colors.blue }) + + table.insert(ecam, { color = colors.red, text = "TURBINE TRIP", help = "TurbineTripAlarm", items = items}) + end + + if not (tripped(unit.alarms[ALARM.ReactorLost]) or unit.connected) then + local items = {{ text = "CHECK PLC", color = colors.blue }} + table.insert(ecam, { color = colors.yellow, text = "REACTOR OFF-LINE", items = items }) + end + + for k, v in ipairs(unit.annunciator.BoilerOnline) do + if not v then + local items = {{ text = "CHECK RTU", color = colors.blue }} + table.insert(ecam, { color = colors.yellow, text = "BOILER " .. k .. " OFF-LINE", items = items}) + end + end + + for k, v in ipairs(unit.annunciator.TurbineOnline) do + if not v then + local items = {{ text = "CHECK RTU", color = colors.blue }} + table.insert(ecam, { color = colors.yellow, text = "TURBINE " .. k .. " OFF-LINE", items = items}) + end + end + + -- if no alarms, put some basic status messages in + if #ecam == 0 then + table.insert(ecam, { color = colors.green, text = "REACTOR " .. util.trinary(unit.reactor_data.mek_status.status, "NOMINAL", "IDLE"), items = {}}) + + local plural = util.trinary(unit.num_turbines > 1, "S", "") + table.insert(ecam, { color = colors.green, text = "TURBINE" .. plural .. util.trinary(unit.turbine_flow_stable, " STABLE", " STABILIZING"), items = {}}) + end + + unit.unit_ps.publish("U_ECAM", textutils.serialize(ecam)) + + --#endregion end -- get the IO controller database diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 0482386..a0f3f5a 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -324,7 +324,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) iocontrol.record_facility_data(packet.data) end elseif packet.type == CRDN_TYPE.API_GET_UNIT then - if _check_length(packet, 9) then + if _check_length(packet, 11) and type(packet.data[1]) == "number" and iocontrol.get_db().units[packet.data[1]] then iocontrol.record_unit_data(packet.data) end else _fail_type(packet) end diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index 87960f2..392efa2 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -38,7 +38,7 @@ local function init(main) local db = iocontrol.get_db() -- window header message - TextBox{parent=main,y=1,text="WIP ALPHA APP S C ",alignment=ALIGN.LEFT,height=1,fg_bg=style.header} + TextBox{parent=main,y=1,text="WIP 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)} diff --git a/pocket/ui/pages/unit_page.lua b/pocket/ui/pages/unit_page.lua index da123e0..8291be5 100644 --- a/pocket/ui/pages/unit_page.lua +++ b/pocket/ui/pages/unit_page.lua @@ -10,6 +10,7 @@ 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") @@ -196,9 +197,34 @@ local function new_view(root) nav_links[i].alarm = alm_page.nav_to - TextBox{parent=alm_div,y=1,text="Unit Alarms",height=1,alignment=ALIGN.CENTER} + TextBox{parent=alm_div,y=1,text="ECAM :)",height=1,alignment=ALIGN.CENTER} - TextBox{parent=alm_div,y=3,text="work in progress",height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.gray,colors.black)} + local ecam_disp = ListBox{parent=alm_div,x=2,y=3,scroll_height=500,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + + ecam_disp.register(u_ps, "U_ECAM", function (data) + local ecam = textutils.unserialize(data) + + ecam_disp.remove_all() + for _, entry in ipairs(ecam) do + local div = Div{parent=ecam_disp,fg_bg=cpair(entry.color,colors.black)} + local text = TextBox{parent=div,text=entry.text} + + if entry.help then + PushButton{parent=div,x=20,y=text.get_y(),text="?",callback=function()db.nav.open_help(entry.help)end,fg_bg=cpair(colors.gray,colors.black)} + end + + for _, item in ipairs(entry.items) do + local fg_bg = nil + if item.color then fg_bg = cpair(item.color, colors.black) end + + text = TextBox{parent=div,x=3,text=item.text,fg_bg=fg_bg} + + if item.help then + PushButton{parent=div,x=20,y=text.get_y(),text="?",callback=function()db.nav.open_help(item.help)end,fg_bg=cpair(colors.gray,colors.black)} + end + end + end + end) --#endregion diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 8747b61..bf3e8b1 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -164,7 +164,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) ReactorDamage = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorDamage, tier = PRIO.EMERGENCY }, -- reactor >1200K ReactorOverTemp = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorOverTemp, tier = PRIO.URGENT }, - -- reactor >=1150K + -- reactor >= computed high temp limit ReactorHighTemp = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 1, id = ALARM.ReactorHighTemp, tier = PRIO.TIMELY }, -- waste = 100% ReactorWasteLeak = { state = AISTATE.INACTIVE, trip_time = 0, hold_time = 0, id = ALARM.ReactorWasteLeak, tier = PRIO.EMERGENCY }, @@ -976,7 +976,9 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) self.db.control.ready, self.db.control.degraded, self.db.control.waste_mode, - self.waste_product + self.waste_product, + self.last_rate_change_ms, + self.turbine_flow_stable } end From 0e81391144103b82ac71a697ab38360da38582ce Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 22 May 2024 21:45:52 -0400 Subject: [PATCH 30/87] #200 fixes to alarm/info display --- pocket/iocontrol.lua | 2 +- pocket/pocket.lua | 2 +- pocket/ui/pages/unit_page.lua | 27 ++++++++++++++++----------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 4500daa..485628f 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -786,7 +786,7 @@ function iocontrol.record_unit_data(data) if tripped(unit.alarms[ALARM.ContainmentBreach]) then local items = { - { text = "REACTOR EXPLOSION", color = colors.white }, + { text = "REACTOR MELTDOWN", color = colors.white }, { text = "WEAR HAZMAT SUIT", color = colors.blue } } diff --git a/pocket/pocket.lua b/pocket/pocket.lua index a0f3f5a..f330ab2 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -274,7 +274,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) 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)) + log.debug(util.sprintf(fmt, packet.scada_frame.remote_channel(), packet.scada_frame.protocol(), packet.type, length, packet.scada_frame.length())) end return ok end diff --git a/pocket/ui/pages/unit_page.lua b/pocket/ui/pages/unit_page.lua index 8291be5..f6ad149 100644 --- a/pocket/ui/pages/unit_page.lua +++ b/pocket/ui/pages/unit_page.lua @@ -97,7 +97,7 @@ local function new_view(root) end local function load() - local page_div = Div{parent=main,x=2,y=2,width=main.get_width()-2} + local page_div = Div{parent=main,y=2,width=main.get_width()} local panes = {} @@ -125,7 +125,8 @@ local function new_view(root) end for i = 1, db.facility.num_units do - local u_div = panes[i] ---@type graphics_element + local u_pane = panes[i] + local u_div = Div{parent=u_pane,x=2,width=main.get_width()-2} local unit = db.units[i] ---@type pioctl_unit local u_ps = unit.unit_ps @@ -197,7 +198,7 @@ local function new_view(root) nav_links[i].alarm = alm_page.nav_to - TextBox{parent=alm_div,y=1,text="ECAM :)",height=1,alignment=ALIGN.CENTER} + TextBox{parent=alm_div,y=1,text="Status Info Display",height=1,alignment=ALIGN.CENTER} local ecam_disp = ListBox{parent=alm_div,x=2,y=3,scroll_height=500,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} @@ -206,23 +207,25 @@ local function new_view(root) ecam_disp.remove_all() for _, entry in ipairs(ecam) do - local div = Div{parent=ecam_disp,fg_bg=cpair(entry.color,colors.black)} - local text = TextBox{parent=div,text=entry.text} + local div = Div{parent=ecam_disp,height=1+#entry.items,fg_bg=cpair(entry.color,colors.black)} + local text = TextBox{parent=div,height=1,text=entry.text} if entry.help then - PushButton{parent=div,x=20,y=text.get_y(),text="?",callback=function()db.nav.open_help(entry.help)end,fg_bg=cpair(colors.gray,colors.black)} + PushButton{parent=div,x=21,y=text.get_y(),text="?",callback=function()db.nav.open_help(entry.help)end,fg_bg=cpair(colors.gray,colors.black)} end for _, item in ipairs(entry.items) do local fg_bg = nil if item.color then fg_bg = cpair(item.color, colors.black) end - text = TextBox{parent=div,x=3,text=item.text,fg_bg=fg_bg} + text = TextBox{parent=div,x=3,height=1,text=item.text,fg_bg=fg_bg} if item.help then - PushButton{parent=div,x=20,y=text.get_y(),text="?",callback=function()db.nav.open_help(item.help)end,fg_bg=cpair(colors.gray,colors.black)} + PushButton{parent=div,x=21,y=text.get_y(),text="?",callback=function()db.nav.open_help(item.help)end,fg_bg=cpair(colors.gray,colors.black)} end end + + ecam_disp.line_break() end end) @@ -230,7 +233,8 @@ local function new_view(root) --#region RPS Tab - local rps_div = Div{parent=page_div} + local rps_pane = Div{parent=page_div} + local rps_div = Div{parent=rps_pane,x=2,width=main.get_width()-2} table.insert(panes, rps_div) local rps_page = app.new_page(u_page, #panes) @@ -274,8 +278,9 @@ local function new_view(root) --#region RCS Tab - local rcs_div = Div{parent=page_div} - table.insert(panes, rcs_div) + local rcs_pane = Div{parent=page_div} + local rcs_div = Div{parent=rcs_pane,x=2,width=main.get_width()-2} + table.insert(panes, rcs_pane) local rcs_page = app.new_page(u_page, #panes) rcs_page.tasks = { update } From e6d6353d059ea7548c429969006fd36c9ccfcd79 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 27 May 2024 19:31:24 -0400 Subject: [PATCH 31/87] added temperature units to pocket and to common types --- coordinator/configure.lua | 5 +-- coordinator/iocontrol.lua | 15 +++++---- coordinator/startup.lua | 2 +- pocket/configure.lua | 61 ++++++++++++++++++++++++++--------- pocket/iocontrol.lua | 15 +++++---- pocket/pocket.lua | 8 +++-- pocket/startup.lua | 2 +- pocket/ui/pages/unit_page.lua | 4 +-- scada-common/types.lua | 22 +++++++++++++ scada-common/util.lua | 2 +- 10 files changed, 97 insertions(+), 39 deletions(-) diff --git a/coordinator/configure.lua b/coordinator/configure.lua index e763a27..f57f11e 100644 --- a/coordinator/configure.lua +++ b/coordinator/configure.lua @@ -7,6 +7,7 @@ local log = require("scada-common.log") local network = require("scada-common.network") local ppm = require("scada-common.ppm") local tcd = require("scada-common.tcd") +local types = require("scada-common.types") local util = require("scada-common.util") local themes = require("graphics.themes") @@ -756,7 +757,7 @@ local function config_view(display) local clock_fmt = RadioButton{parent=crd_c_1,x=1,y=5,default=util.trinary(ini_cfg.Time24Hour,1,2),options={"24-Hour","12-Hour"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} TextBox{parent=crd_c_1,x=1,y=8,height=1,text="Temperature Scale"} - local temp_scale = RadioButton{parent=crd_c_1,x=1,y=9,default=ini_cfg.TempScale,options={"Kelvin","Celsius","Fahrenheit","Rankine"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} + local temp_scale = RadioButton{parent=crd_c_1,x=1,y=9,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} local function submit_ui_opts() tmp_cfg.Time24Hour = clock_fmt.get_value() == 1 @@ -1356,7 +1357,7 @@ local function config_view(display) if f[1] == "AuthKey" then val = string.rep("*", string.len(val)) elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") elseif f[1] == "TempScale" then - if raw == 1 then val = "Kelvin" elseif raw == 2 then val = "Celsius" elseif raw == 3 then val = "Fahrenheit" elseif raw == 4 then val = "Rankine" end + val = types.TEMP_SCALE_NAMES[raw] elseif f[1] == "MainTheme" then val = util.strval(themes.ui_theme_name(raw)) elseif f[1] == "FrontPanelTheme" then diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 08fad13..5d8428f 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -14,6 +14,8 @@ local pgi = require("coordinator.ui.pgi") local ALARM_STATE = types.ALARM_STATE local PROCESS = types.PROCESS +local TEMP_SCALE = types.TEMP_SCALE +local TEMP_UNITS = types.TEMP_SCALE_UNITS -- nominal RTT is ping (0ms to 10ms usually) + 500ms for CRD main loop tick local WARN_RTT = 1000 -- 2x as long as expected w/ 0 ping @@ -47,17 +49,16 @@ end -- initialize the coordinator IO controller ---@param conf facility_conf configuration ---@param comms coord_comms comms reference ----@param temp_scale integer temperature unit (1 = K, 2 = C, 3 = F, 4 = R) +---@param temp_scale TEMP_SCALE temperature unit function iocontrol.init(conf, comms, temp_scale) + io.temp_label = TEMP_UNITS[temp_scale] + -- temperature unit label and conversion function (from Kelvin) - if temp_scale == 2 then - io.temp_label = "\xb0C" + if temp_scale == TEMP_SCALE.CELSIUS then io.temp_convert = function (t) return t - 273.15 end - elseif temp_scale == 3 then - io.temp_label = "\xb0F" + elseif temp_scale == TEMP_SCALE.FAHRENHEIT then io.temp_convert = function (t) return (1.8 * (t - 273.15)) + 32 end - elseif temp_scale == 4 then - io.temp_label = "\xb0R" + elseif temp_scale == TEMP_SCALE.RANKINE then io.temp_convert = function (t) return 1.8 * t end else io.temp_label = "K" diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 0b8a06c..f152bc8 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") local threads = require("coordinator.threads") -local COORDINATOR_VERSION = "v1.4.6" +local COORDINATOR_VERSION = "v1.4.7" local CHUNK_LOAD_DELAY_S = 30.0 diff --git a/pocket/configure.lua b/pocket/configure.lua index 80f91bc..d16309c 100644 --- a/pocket/configure.lua +++ b/pocket/configure.lua @@ -3,7 +3,7 @@ -- local log = require("scada-common.log") -local tcd = require("scada-common.tcd") +local types = require("scada-common.types") local util = require("scada-common.util") local core = require("graphics.core") @@ -32,7 +32,9 @@ local CENTER = core.ALIGN.CENTER local RIGHT = core.ALIGN.RIGHT -- changes to the config data/format to let the user know -local changes = {} +local changes = { + { "v0.9.2", { "Added temperature scale options" } } +} ---@class pkt_configurator local configurator = {} @@ -73,6 +75,7 @@ local tool_ctl = { ---@class pkt_config local tmp_cfg = { + TempScale = 1, SVR_Channel = nil, ---@type integer CRD_Channel = nil, ---@type integer PKT_Channel = nil, ---@type integer @@ -91,6 +94,7 @@ local settings_cfg = {} -- all settings fields, their nice names, and their default values local fields = { + { "TempScale", "Temperature Scale", 1 }, { "SVR_Channel", "SVR Channel", 16240 }, { "CRD_Channel", "CRD Channel", 16243 }, { "PKT_Channel", "PKT Channel", 16244 }, @@ -126,12 +130,13 @@ local function config_view(display) local root_pane_div = Div{parent=display,x=1,y=2} local main_page = Div{parent=root_pane_div,x=1,y=1} + local ui_cfg = Div{parent=root_pane_div,x=1,y=1} local net_cfg = Div{parent=root_pane_div,x=1,y=1} local log_cfg = Div{parent=root_pane_div,x=1,y=1} local summary = Div{parent=root_pane_div,x=1,y=1} local changelog = Div{parent=root_pane_div,x=1,y=1} - local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,net_cfg,log_cfg,summary,changelog}} + local main_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={main_page,ui_cfg,net_cfg,log_cfg,summary,changelog}} -- Main Page @@ -148,7 +153,7 @@ local function config_view(display) tool_ctl.viewing_config = true tool_ctl.gen_summary(settings_cfg) tool_ctl.settings_apply.hide(true) - main_pane.set_value(4) + main_pane.set_value(5) end if fs.exists("/pocket/config.lua") then @@ -162,7 +167,28 @@ local function config_view(display) if not tool_ctl.has_config then tool_ctl.view_cfg.disable() end PushButton{parent=main_page,x=2,y=18,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg} - PushButton{parent=main_page,x=14,y=18,min_width=12,text="Change Log",callback=function()main_pane.set_value(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=main_page,x=14,y=18,min_width=12,text="Change Log",callback=function()main_pane.set_value(6)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + --#region Pocket UI + + local ui_c_1 = Div{parent=ui_cfg,x=2,y=4,width=24} + + TextBox{parent=ui_cfg,x=1,y=2,height=1,text=" Pocket UI",fg_bg=cpair(colors.black,colors.lime)} + + TextBox{parent=ui_c_1,x=1,y=1,height=3,text="You may use the options below to customize formats."} + + TextBox{parent=ui_c_1,x=1,y=5,height=1,text="Temperature Scale"} + local temp_scale = RadioButton{parent=ui_c_1,x=1,y=6,default=ini_cfg.TempScale,options=types.TEMP_SCALE_NAMES,callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime} + + local function submit_ui_opts() + tmp_cfg.TempScale = temp_scale.get_value() + main_pane.set_value(3) + end + + PushButton{parent=ui_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=ui_c_1,x=19,y=15,text="Next \x1a",callback=submit_ui_opts,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + --#endregion --#region Network @@ -201,7 +227,7 @@ local function config_view(display) else chan_err.show() end end - PushButton{parent=net_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=net_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=net_c_1,x=19,y=15,text="Next \x1a",callback=submit_channels,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} TextBox{parent=net_c_2,x=1,y=1,height=1,text="Set connection timeout."} @@ -268,7 +294,7 @@ local function config_view(display) local v = key.get_value() if string.len(v) == 0 or string.len(v) >= 8 then tmp_cfg.AuthKey = key.get_value() - main_pane.set_value(3) + main_pane.set_value(4) key_err.hide(true) else key_err.show() end end @@ -306,11 +332,11 @@ local function config_view(display) tool_ctl.viewing_config = false tool_ctl.importing_legacy = false tool_ctl.settings_apply.show() - main_pane.set_value(4) + main_pane.set_value(5) else path_err.show() end end - PushButton{parent=log_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=log_c_1,x=1,y=15,text="\x1b Back",callback=function()main_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=log_c_1,x=19,y=15,text="Next \x1a",callback=submit_log,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} --#endregion @@ -335,7 +361,7 @@ local function config_view(display) tool_ctl.importing_legacy = false tool_ctl.settings_apply.show() else - main_pane.set_value(3) + main_pane.set_value(4) end end @@ -444,7 +470,7 @@ local function config_view(display) tool_ctl.gen_summary(tmp_cfg) sum_pane.set_value(1) - main_pane.set_value(4) + main_pane.set_value(5) tool_ctl.importing_legacy = true end @@ -473,8 +499,13 @@ local function config_view(display) local raw = cfg[f[1]] local val = util.strval(raw) - if f[1] == "AuthKey" then val = string.rep("*", string.len(val)) - elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace") end + if f[1] == "AuthKey" then + val = string.rep("*", string.len(val)) + elseif f[1] == "LogMode" then + val = util.trinary(raw == log.MODE.APPEND, "append", "replace") + elseif f[1] == "TempScale" then + val = types.TEMP_SCALE_NAMES[raw] + end if val == "nil" then val = "" end @@ -532,9 +563,7 @@ function configurator.configure(ask_config) local event, param1, param2, param3 = util.pull_event() -- handle event - if event == "timer" then - tcd.handle(param1) - elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then + if event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then local m_e = core.events.new_mouse_event(event, param1, param2, param3) if m_e then display.handle_mouse(m_e) end elseif event == "char" or event == "key" or event == "key_up" then diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 485628f..331a863 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -10,6 +10,8 @@ local util = require("scada-common.util") local ALARM = types.ALARM local ALARM_STATE = types.ALARM_STATE +local TEMP_SCALE = types.TEMP_SCALE +local TEMP_UNITS = types.TEMP_SCALE_UNITS ---@todo nominal trip time is ping (0ms to 10ms usually) local WARN_TT = 40 @@ -268,17 +270,16 @@ end -- initialize facility-dependent components of pocket iocontrol ---@param conf facility_conf configuration ----@param temp_scale 1|2|3|4 temperature unit (1 = K, 2 = C, 3 = F, 4 = R) +---@param temp_scale TEMP_SCALE temperature unit function iocontrol.init_fac(conf, temp_scale) + io.temp_label = TEMP_UNITS[temp_scale] + -- temperature unit label and conversion function (from Kelvin) - if temp_scale == 2 then - io.temp_label = "\xb0C" + if temp_scale == TEMP_SCALE.CELSIUS then io.temp_convert = function (t) return t - 273.15 end - elseif temp_scale == 3 then - io.temp_label = "\xb0F" + elseif temp_scale == TEMP_SCALE.FAHRENHEIT then io.temp_convert = function (t) return (1.8 * (t - 273.15)) + 32 end - elseif temp_scale == 4 then - io.temp_label = "\xb0R" + elseif temp_scale == TEMP_SCALE.RANKINE then io.temp_convert = function (t) return 1.8 * t end else io.temp_label = "K" diff --git a/pocket/pocket.lua b/pocket/pocket.lua index f330ab2..88aafb3 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -23,6 +23,8 @@ pocket.config = config function pocket.load_config() if not settings.load("/pocket.settings") then return false end + config.TempScale = settings.get("TempScale") + config.SVR_Channel = settings.get("SVR_Channel") config.CRD_Channel = settings.get("CRD_Channel") config.PKT_Channel = settings.get("PKT_Channel") @@ -36,6 +38,9 @@ function pocket.load_config() local cfv = util.new_validator() + cfv.assert_type_int(config.TempScale) + cfv.assert_range(config.TempScale, 1, 4) + cfv.assert_channel(config.SVR_Channel) cfv.assert_channel(config.CRD_Channel) cfv.assert_channel(config.PKT_Channel) @@ -371,8 +376,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) -- get configuration local conf = { num_units = fac_config[1], cooling = fac_config[2] } - ---@todo unit options - iocontrol.init_fac(conf, 1) + iocontrol.init_fac(conf, config.TempScale) log.info("coordinator connection established") self.establish_delay_counter = 0 diff --git a/pocket/startup.lua b/pocket/startup.lua index 8ba8d3e..c9f8c8d 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -18,7 +18,7 @@ local iocontrol = require("pocket.iocontrol") local pocket = require("pocket.pocket") local renderer = require("pocket.renderer") -local POCKET_VERSION = "v0.9.1-alpha" +local POCKET_VERSION = "v0.9.2-alpha" local println = util.println local println_ts = util.println_ts diff --git a/pocket/ui/pages/unit_page.lua b/pocket/ui/pages/unit_page.lua index f6ad149..222d18a 100644 --- a/pocket/ui/pages/unit_page.lua +++ b/pocket/ui/pages/unit_page.lua @@ -155,12 +155,12 @@ local function new_view(root) local text_fg = cpair(colors.white, colors._INHERIT) local rate = DataIndicator{parent=u_div,y=5,lu_colors=lu_col,label="Rate",unit="mB/t",format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg} - local temp = DataIndicator{parent=u_div,lu_colors=lu_col,label="Temp",unit="K",format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg} + local temp = DataIndicator{parent=u_div,lu_colors=lu_col,label="Temp",unit=db.temp_label,format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg} local ctrl = IconIndicator{parent=u_div,x=1,y=8,label="Control State",states=mode_states} rate.register(u_ps, "act_burn_rate", rate.update) - temp.register(u_ps, "temp", temp.update) + temp.register(u_ps, "temp", function (t) temp.update(db.temp_convert(t)) end) ctrl.register(u_ps, "U_ControlStatus", ctrl.update) u_div.line_break() diff --git a/scada-common/types.lua b/scada-common/types.lua index d6fc7af..aeeca16 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -74,6 +74,28 @@ function types.new_zero_coordinate() return { x = 0, y = 0, z = 0 } end -- ENUMERATION TYPES -- --#region +---@enum TEMP_SCALE +types.TEMP_SCALE = { + KELVIN = 1, + CELSIUS = 2, + FAHRENHEIT = 3, + RANKINE = 4 +} + +types.TEMP_SCALE_NAMES = { + "Kelvin", + "Celsius", + "Fahrenheit", + "Rankine" +} + +types.TEMP_SCALE_UNITS = { + "K", + "\xb0C", + "\xb0F", + "\xb0R" +} + ---@enum PANEL_LINK_STATE types.PANEL_LINK_STATE = { LINKED = 1, diff --git a/scada-common/util.lua b/scada-common/util.lua index 0fe636d..d29a85e 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -22,7 +22,7 @@ local t_pack = table.pack local util = {} -- scada-common version -util.version = "1.3.0" +util.version = "1.3.1" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 From 946c28c9292334b05bb982ebb555d295fa49acaa Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 27 May 2024 23:49:53 -0400 Subject: [PATCH 32/87] record additional reactor unit data --- pocket/iocontrol.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 331a863..7bf559f 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -779,6 +779,9 @@ function iocontrol.record_unit_data(data) unit.tank_data_tbl = data[9] + unit.last_rate_change_ms = data[10] + unit.turbine_flow_stable = data[11] + --#endregion --#region Advanced Alarm Information Display From 30c921565879d6d4d042bea5bc11837148c7c275 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 27 May 2024 23:53:35 -0400 Subject: [PATCH 33/87] #202 pocket reactor view --- pocket/ui/pages/unit_page.lua | 137 ++++++++++++++++++++++++++++++++-- 1 file changed, 130 insertions(+), 7 deletions(-) diff --git a/pocket/ui/pages/unit_page.lua b/pocket/ui/pages/unit_page.lua index 222d18a..4f576fb 100644 --- a/pocket/ui/pages/unit_page.lua +++ b/pocket/ui/pages/unit_page.lua @@ -2,6 +2,7 @@ -- Unit Overview Page -- +local types = require("scada-common.types") local util = require("scada-common.util") -- local log = require("scada-common.log") @@ -17,7 +18,7 @@ local TextBox = require("graphics.elements.textbox") local DataIndicator = require("graphics.elements.indicators.data") local IconIndicator = require("graphics.elements.indicators.icon") -- local RadIndicator = require("graphics.elements.indicators.rad") --- local VerticalBar = require("graphics.elements.indicators.vbar") +local VerticalBar = require("graphics.elements.indicators.vbar") local PushButton = require("graphics.elements.controls.push_button") @@ -69,7 +70,7 @@ local function new_view(root) local btn_fg_bg = cpair(colors.yellow, colors.black) local btn_active = cpair(colors.white, colors.black) - -- local label = cpair(colors.lightGray, colors.black) + local label = cpair(colors.lightGray, colors.black) local nav_links = {} @@ -81,7 +82,7 @@ local function new_view(root) { label = "U-" .. id, color = core.cpair(colors.black, colors.yellow), callback = function () app.switcher(id) end }, { label = " \x13 ", color = core.cpair(colors.black, colors.red), callback = nav_links[id].alarm }, { label = "RPS", tall = true, color = core.cpair(colors.black, colors.cyan), callback = nav_links[id].rps }, - -- { label = " R ", color = core.cpair(colors.black, colors.lightGray), callback = function () end }, + { label = " R ", color = core.cpair(colors.black, colors.lightGray), callback = nav_links[id].reactor }, { label = "RCS", tall = true, color = core.cpair(colors.black, colors.blue), callback = nav_links[id].rcs }, } @@ -154,7 +155,7 @@ local function new_view(root) local lu_col = cpair(colors.lightGray, colors.lightGray) local text_fg = cpair(colors.white, colors._INHERIT) - local rate = DataIndicator{parent=u_div,y=5,lu_colors=lu_col,label="Rate",unit="mB/t",format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg} + local rate = DataIndicator{parent=u_div,y=5,lu_colors=lu_col,label="Burn",unit="mB/t",format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg} local temp = DataIndicator{parent=u_div,lu_colors=lu_col,label="Temp",unit=db.temp_label,format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg} local ctrl = IconIndicator{parent=u_div,x=1,y=8,label="Control State",states=mode_states} @@ -195,12 +196,11 @@ local function new_view(root) local alm_page = app.new_page(u_page, #panes) alm_page.tasks = { update } - nav_links[i].alarm = alm_page.nav_to TextBox{parent=alm_div,y=1,text="Status Info Display",height=1,alignment=ALIGN.CENTER} - local ecam_disp = ListBox{parent=alm_div,x=2,y=3,scroll_height=500,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + local ecam_disp = ListBox{parent=alm_div,x=2,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} ecam_disp.register(u_ps, "U_ECAM", function (data) local ecam = textutils.unserialize(data) @@ -239,7 +239,6 @@ local function new_view(root) local rps_page = app.new_page(u_page, #panes) rps_page.tasks = { update } - nav_links[i].rps = rps_page.nav_to TextBox{parent=rps_div,y=1,text="Protection System",height=1,alignment=ALIGN.CENTER} @@ -276,6 +275,130 @@ local function new_view(root) --#endregion + --#region Reactor Tab + + local rct_pane = Div{parent=page_div} + local rct_div = Div{parent=rct_pane,x=2,width=main.get_width()-2} + table.insert(panes, rct_div) + + local rct_page = app.new_page(u_page, #panes) + rct_page.tasks = { update } + nav_links[i].reactor = rct_page.nav_to + + TextBox{parent=rct_div,y=1,text="Fission Reactor",height=1,alignment=ALIGN.CENTER} + + local fuel = VerticalBar{parent=rct_div,x=1,y=4,fg_bg=cpair(colors.lightGray,colors.gray),height=5,width=1} + local ccool = VerticalBar{parent=rct_div,x=3,y=4,fg_bg=cpair(colors.blue,colors.gray),height=5,width=1} + local hcool = VerticalBar{parent=rct_div,x=19,y=4,fg_bg=cpair(colors.white,colors.gray),height=5,width=1} + local waste = VerticalBar{parent=rct_div,x=21,y=4,fg_bg=cpair(colors.brown,colors.gray),height=5,width=1} + + TextBox{parent=rct_div,text="F",x=1,y=3,width=1,height=1,fg_bg=label} + TextBox{parent=rct_div,text="C",x=3,y=3,width=1,height=1,fg_bg=label} + TextBox{parent=rct_div,text="H",x=19,y=3,width=1,height=1,fg_bg=label} + TextBox{parent=rct_div,text="W",x=21,y=3,width=1,height=1,fg_bg=label} + + fuel.register(u_ps, "fuel_fill", fuel.update) + ccool.register(u_ps, "ccool_fill", ccool.update) + hcool.register(u_ps, "hcool_fill", hcool.update) + waste.register(u_ps, "waste_fill", waste.update) + + ccool.register(u_ps, "ccool_type", function (type) + if type == types.FLUID.SODIUM then + ccool.recolor(cpair(colors.lightBlue, colors.gray)) + else + ccool.recolor(cpair(colors.blue, colors.gray)) + end + end) + + hcool.register(u_ps, "hcool_type", function (type) + if type == types.FLUID.SUPERHEATED_SODIUM then + hcool.recolor(cpair(colors.orange, colors.gray)) + else + hcool.recolor(cpair(colors.white, colors.gray)) + end + end) + + TextBox{parent=rct_div,text="Burn Rate",x=5,y=5,width=13,height=1,fg_bg=label} + local burn_rate = DataIndicator{parent=rct_div,x=5,y=6,lu_colors=lu_col,label="",unit="mB/t",format="%8.2f",value=1024.99,commas=true,width=13,fg_bg=text_fg} + TextBox{parent=rct_div,text="Temperature",x=5,y=7,width=13,height=1,fg_bg=label} + local t_prec = util.trinary(db.temp_label == types.TEMP_SCALE_UNITS[types.TEMP_SCALE.KELVIN], 11, 10) + local core_temp = DataIndicator{parent=rct_div,x=5,y=8,lu_colors=lu_col,label="",unit=db.temp_label,format="%"..t_prec..".2f",value=17802.03,commas=true,width=13,fg_bg=text_fg} + + local r_state = IconIndicator{parent=rct_div,x=7,y=3,label="State",states=mode_states} + + burn_rate.register(u_ps, "act_burn_rate", burn_rate.update) + core_temp.register(u_ps, "temp", function (t) core_temp.update(db.temp_convert(t)) end) + r_state.register(u_ps, "U_ControlStatus", r_state.update) + + local r_temp = IconIndicator{parent=rct_div,y=10,label="Reactor Temp. Hi",states=red_ind_s} + local r_rhdt = IconIndicator{parent=rct_div,label="Hi Delta Temp.",states=yel_ind_s} + local r_firl = IconIndicator{parent=rct_div,label="Fuel Rate Lo",states=yel_ind_s} + local r_wloc = IconIndicator{parent=rct_div,label="Waste Line Occl.",states=yel_ind_s} + local r_hsrt = IconIndicator{parent=rct_div,label="Hi Startup Rate",states=yel_ind_s} + + r_temp.register(u_ps, "ReactorTempHigh", r_temp.update) + r_rhdt.register(u_ps, "ReactorHighDeltaT", r_rhdt.update) + r_firl.register(u_ps, "FuelInputRateLow", r_firl.update) + r_wloc.register(u_ps, "WasteLineOcclusion", r_wloc.update) + r_hsrt.register(u_ps, "HighStartupRate", r_hsrt.update) + + TextBox{parent=rct_div,text="HR",x=1,y=16,width=4,height=1,fg_bg=label} + local heating_r = DataIndicator{parent=rct_div,x=6,y=16,lu_colors=lu_col,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=text_fg} + TextBox{parent=rct_div,text="DMG",x=1,y=17,width=4,height=1,fg_bg=label} + local damage_p = DataIndicator{parent=rct_div,x=6,y=17,lu_colors=lu_col,label="",unit="%",format="%11.2f",value=0,width=16,fg_bg=text_fg} + + heating_r.register(u_ps, "heating_rate", heating_r.update) + damage_p.register(u_ps, "damage", damage_p.update) + + local rct_ext_div = Div{parent=rct_pane,x=2,width=main.get_width()-2} + table.insert(panes, rct_ext_div) + + local rct_ext_page = app.new_page(rct_page, #panes) + rct_ext_page.tasks = { update } + + PushButton{parent=rct_div,x=9,y=18,text="MORE",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=rct_ext_page.nav_to} + PushButton{parent=rct_ext_div,x=9,y=18,text="BACK",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=rct_page.nav_to} + + TextBox{parent=rct_ext_div,y=1,text="More Reactor Info",height=1,alignment=ALIGN.CENTER} + + TextBox{parent=rct_ext_div,text="Fuel Tank",x=1,y=3,width=9,height=1,fg_bg=label} + local fuel_p = DataIndicator{parent=rct_ext_div,x=14,y=3,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} + local fuel_amnt = DataIndicator{parent=rct_ext_div,x=1,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + + fuel_p.register(u_ps, "fuel_fill", function (x) fuel_p.update(x * 100) end) + fuel_amnt.register(u_ps, "fuel", fuel_amnt.update) + + TextBox{parent=rct_ext_div,text="Cool Coolant",x=1,y=6,width=12,height=1,fg_bg=label} + local cooled_p = DataIndicator{parent=rct_ext_div,x=14,y=6,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} + local ccool_amnt = DataIndicator{parent=rct_ext_div,x=1,y=7,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + + cooled_p.register(u_ps, "ccool_fill", function (x) cooled_p.update(x * 100) end) + ccool_amnt.register(u_ps, "ccool_amnt", ccool_amnt.update) + + TextBox{parent=rct_ext_div,text="Hot Coolant",x=1,y=9,width=12,height=1,fg_bg=label} + local heated_p = DataIndicator{parent=rct_ext_div,x=14,y=9,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} + local hcool_amnt = DataIndicator{parent=rct_ext_div,x=1,y=10,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + + heated_p.register(u_ps, "hcool_fill", function (x) heated_p.update(x * 100) end) + hcool_amnt.register(u_ps, "hcool_amnt", hcool_amnt.update) + + TextBox{parent=rct_ext_div,text="Waste Tank",x=1,y=12,width=10,height=1,fg_bg=label} + local waste_p = DataIndicator{parent=rct_ext_div,x=14,y=12,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} + local waste_amnt = DataIndicator{parent=rct_ext_div,x=1,y=13,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + + waste_p.register(u_ps, "waste_fill", function (x) waste_p.update(x * 100) end) + waste_amnt.register(u_ps, "waste", waste_amnt.update) + + TextBox{parent=rct_ext_div,text="Boil Eff.",x=1,y=15,width=9,height=1,fg_bg=label} + TextBox{parent=rct_ext_div,text="Env. Loss",x=1,y=16,width=9,height=1,fg_bg=label} + local boil_eff = DataIndicator{parent=rct_ext_div,x=11,y=15,lu_colors=lu_col,label="",unit="%",format="%9.2f",value=0,width=11,fg_bg=text_fg} + local env_loss = DataIndicator{parent=rct_ext_div,x=11,y=16,lu_colors=lu_col,label="",unit="",format="%11.8f",value=0,width=11,fg_bg=text_fg} + + boil_eff.register(u_ps, "boil_eff", function (x) boil_eff.update(x * 100) end) + env_loss.register(u_ps, "env_loss", env_loss.update) + + --#endregion + --#region RCS Tab local rcs_pane = Div{parent=page_div} From 3181ab96f165d9ab46cd75cc05d06cf90ba9c993 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 29 May 2024 22:08:36 -0400 Subject: [PATCH 34/87] #206 work on boiler view and reorganized app code --- pocket/iocontrol.lua | 10 + .../ui/{pages/unit_page.lua => apps/unit.lua} | 188 +++--------------- pocket/ui/main.lua | 4 +- pocket/ui/pages/unit_boiler.lua | 79 ++++++++ pocket/ui/pages/unit_reactor.lua | 159 +++++++++++++++ pocket/ui/style.lua | 38 +++- 6 files changed, 320 insertions(+), 158 deletions(-) rename pocket/ui/{pages/unit_page.lua => apps/unit.lua} (58%) create mode 100644 pocket/ui/pages/unit_boiler.lua create mode 100644 pocket/ui/pages/unit_reactor.lua diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 7bf559f..c74c6a7 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -592,6 +592,14 @@ end local function tripped(state) return state == ALARM_STATE.TRIPPED or state == ALARM_STATE.ACKED end +local function _record_multiblock_status(faulted, data, ps) + ps.publish("formed", data.formed) + ps.publish("faulted", faulted) + + for key, val in pairs(data.state) do ps.publish(key, val) end + for key, val in pairs(data.tanks) do ps.publish(key, val) end +end + -- update unit status data from API_GET_UNIT ---@param data table function iocontrol.record_unit_data(data) @@ -751,6 +759,8 @@ function iocontrol.record_unit_data(data) else boiler_status = 2 end + + _record_multiblock_status(unit.rtu_hw.boilers[id].faulted, boiler, ps) end ps.publish("BoilerStatus", boiler_status) diff --git a/pocket/ui/pages/unit_page.lua b/pocket/ui/apps/unit.lua similarity index 58% rename from pocket/ui/pages/unit_page.lua rename to pocket/ui/apps/unit.lua index 4f576fb..51c27b8 100644 --- a/pocket/ui/pages/unit_page.lua +++ b/pocket/ui/apps/unit.lua @@ -2,12 +2,16 @@ -- Unit Overview Page -- -local types = require("scada-common.types") local util = require("scada-common.util") -- local log = require("scada-common.log") local iocontrol = require("pocket.iocontrol") +local style = require("pocket.ui.style") + +local boiler = require("pocket.ui.pages.unit_boiler") +local reactor = require("pocket.ui.pages.unit_reactor") + local core = require("graphics.core") local Div = require("graphics.elements.div") @@ -18,26 +22,20 @@ local TextBox = require("graphics.elements.textbox") local DataIndicator = require("graphics.elements.indicators.data") local IconIndicator = require("graphics.elements.indicators.icon") -- local RadIndicator = require("graphics.elements.indicators.rad") -local VerticalBar = require("graphics.elements.indicators.vbar") +-- local VerticalBar = require("graphics.elements.indicators.vbar") local PushButton = require("graphics.elements.controls.push_button") local ALIGN = core.ALIGN local cpair = core.cpair -local basic_states = { - { color = cpair(colors.black, colors.lightGray), symbol = "\x07" }, - { color = cpair(colors.black, colors.red), symbol = "-" }, - { color = cpair(colors.black, colors.yellow), symbol = "\x1e" }, - { color = cpair(colors.black, colors.green), symbol = "+" } -} - -local mode_states = { - { color = cpair(colors.black, colors.lightGray), symbol = "\x07" }, - { color = cpair(colors.black, colors.red), symbol = "-" }, - { color = cpair(colors.black, colors.green), symbol = "+" }, - { color = cpair(colors.black, colors.purple), symbol = "A" } -} +-- local label = style.label +local lu_col = style.label_unit_pair +local text_fg = style.text_fg +local basic_states = style.icon_states.basic_states +local mode_states = style.icon_states.mode_states +local red_ind_s = style.icon_states.red_ind_s +local yel_ind_s = style.icon_states.yel_ind_s local emc_ind_s = { { color = cpair(colors.black, colors.gray), symbol = "-" }, @@ -45,16 +43,6 @@ local emc_ind_s = { { color = cpair(colors.black, colors.green), symbol = "+" } } -local red_ind_s = { - { color = cpair(colors.black, colors.lightGray), symbol = "+" }, - { color = cpair(colors.black, colors.red), symbol = "-" } -} - -local yel_ind_s = { - { color = cpair(colors.black, colors.lightGray), symbol = "+" }, - { color = cpair(colors.black, colors.yellow), symbol = "-" } -} - -- new unit page view ---@param root graphics_element parent local function new_view(root) @@ -70,12 +58,11 @@ local function new_view(root) local btn_fg_bg = cpair(colors.yellow, colors.black) local btn_active = cpair(colors.white, colors.black) - local label = cpair(colors.lightGray, colors.black) local nav_links = {} local function set_sidebar(id) - -- local unit = db.units[id] ---@type pioctl_unit + local unit = db.units[id] ---@type pioctl_unit local list = { { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(iocontrol.APP_ID.ROOT) end }, @@ -86,13 +73,13 @@ local function new_view(root) { label = "RCS", tall = true, color = core.cpair(colors.black, colors.blue), callback = nav_links[id].rcs }, } - -- for i = 1, unit.num_boilers do - -- table.insert(list, { label = "B-" .. i, color = core.cpair(colors.black, colors.lightBlue), callback = function () end }) - -- end + for i = 1, unit.num_boilers do + table.insert(list, { label = "B-" .. i, color = core.cpair(colors.black, colors.lightBlue), callback = nav_links[id].boiler[i] }) + end - -- for i = 1, unit.num_turbines do - -- table.insert(list, { label = "T-" .. i, color = core.cpair(colors.black, colors.white), callback = function () end }) - -- end + for i = 1, unit.num_turbines do + table.insert(list, { label = "T-" .. i, color = core.cpair(colors.black, colors.white), callback = function () end }) + end app.set_sidebar(list) end @@ -152,9 +139,6 @@ local function new_view(root) local type = util.trinary(unit.num_boilers > 0, "Sodium Cooled Reactor", "Boiling Water Reactor") TextBox{parent=u_div,y=3,text=type,height=1,alignment=ALIGN.CENTER,fg_bg=cpair(colors.gray,colors.black)} - local lu_col = cpair(colors.lightGray, colors.lightGray) - local text_fg = cpair(colors.white, colors._INHERIT) - local rate = DataIndicator{parent=u_div,y=5,lu_colors=lu_col,label="Burn",unit="mB/t",format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg} local temp = DataIndicator{parent=u_div,lu_colors=lu_col,label="Temp",unit=db.temp_label,format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg} @@ -277,125 +261,7 @@ local function new_view(root) --#region Reactor Tab - local rct_pane = Div{parent=page_div} - local rct_div = Div{parent=rct_pane,x=2,width=main.get_width()-2} - table.insert(panes, rct_div) - - local rct_page = app.new_page(u_page, #panes) - rct_page.tasks = { update } - nav_links[i].reactor = rct_page.nav_to - - TextBox{parent=rct_div,y=1,text="Fission Reactor",height=1,alignment=ALIGN.CENTER} - - local fuel = VerticalBar{parent=rct_div,x=1,y=4,fg_bg=cpair(colors.lightGray,colors.gray),height=5,width=1} - local ccool = VerticalBar{parent=rct_div,x=3,y=4,fg_bg=cpair(colors.blue,colors.gray),height=5,width=1} - local hcool = VerticalBar{parent=rct_div,x=19,y=4,fg_bg=cpair(colors.white,colors.gray),height=5,width=1} - local waste = VerticalBar{parent=rct_div,x=21,y=4,fg_bg=cpair(colors.brown,colors.gray),height=5,width=1} - - TextBox{parent=rct_div,text="F",x=1,y=3,width=1,height=1,fg_bg=label} - TextBox{parent=rct_div,text="C",x=3,y=3,width=1,height=1,fg_bg=label} - TextBox{parent=rct_div,text="H",x=19,y=3,width=1,height=1,fg_bg=label} - TextBox{parent=rct_div,text="W",x=21,y=3,width=1,height=1,fg_bg=label} - - fuel.register(u_ps, "fuel_fill", fuel.update) - ccool.register(u_ps, "ccool_fill", ccool.update) - hcool.register(u_ps, "hcool_fill", hcool.update) - waste.register(u_ps, "waste_fill", waste.update) - - ccool.register(u_ps, "ccool_type", function (type) - if type == types.FLUID.SODIUM then - ccool.recolor(cpair(colors.lightBlue, colors.gray)) - else - ccool.recolor(cpair(colors.blue, colors.gray)) - end - end) - - hcool.register(u_ps, "hcool_type", function (type) - if type == types.FLUID.SUPERHEATED_SODIUM then - hcool.recolor(cpair(colors.orange, colors.gray)) - else - hcool.recolor(cpair(colors.white, colors.gray)) - end - end) - - TextBox{parent=rct_div,text="Burn Rate",x=5,y=5,width=13,height=1,fg_bg=label} - local burn_rate = DataIndicator{parent=rct_div,x=5,y=6,lu_colors=lu_col,label="",unit="mB/t",format="%8.2f",value=1024.99,commas=true,width=13,fg_bg=text_fg} - TextBox{parent=rct_div,text="Temperature",x=5,y=7,width=13,height=1,fg_bg=label} - local t_prec = util.trinary(db.temp_label == types.TEMP_SCALE_UNITS[types.TEMP_SCALE.KELVIN], 11, 10) - local core_temp = DataIndicator{parent=rct_div,x=5,y=8,lu_colors=lu_col,label="",unit=db.temp_label,format="%"..t_prec..".2f",value=17802.03,commas=true,width=13,fg_bg=text_fg} - - local r_state = IconIndicator{parent=rct_div,x=7,y=3,label="State",states=mode_states} - - burn_rate.register(u_ps, "act_burn_rate", burn_rate.update) - core_temp.register(u_ps, "temp", function (t) core_temp.update(db.temp_convert(t)) end) - r_state.register(u_ps, "U_ControlStatus", r_state.update) - - local r_temp = IconIndicator{parent=rct_div,y=10,label="Reactor Temp. Hi",states=red_ind_s} - local r_rhdt = IconIndicator{parent=rct_div,label="Hi Delta Temp.",states=yel_ind_s} - local r_firl = IconIndicator{parent=rct_div,label="Fuel Rate Lo",states=yel_ind_s} - local r_wloc = IconIndicator{parent=rct_div,label="Waste Line Occl.",states=yel_ind_s} - local r_hsrt = IconIndicator{parent=rct_div,label="Hi Startup Rate",states=yel_ind_s} - - r_temp.register(u_ps, "ReactorTempHigh", r_temp.update) - r_rhdt.register(u_ps, "ReactorHighDeltaT", r_rhdt.update) - r_firl.register(u_ps, "FuelInputRateLow", r_firl.update) - r_wloc.register(u_ps, "WasteLineOcclusion", r_wloc.update) - r_hsrt.register(u_ps, "HighStartupRate", r_hsrt.update) - - TextBox{parent=rct_div,text="HR",x=1,y=16,width=4,height=1,fg_bg=label} - local heating_r = DataIndicator{parent=rct_div,x=6,y=16,lu_colors=lu_col,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=text_fg} - TextBox{parent=rct_div,text="DMG",x=1,y=17,width=4,height=1,fg_bg=label} - local damage_p = DataIndicator{parent=rct_div,x=6,y=17,lu_colors=lu_col,label="",unit="%",format="%11.2f",value=0,width=16,fg_bg=text_fg} - - heating_r.register(u_ps, "heating_rate", heating_r.update) - damage_p.register(u_ps, "damage", damage_p.update) - - local rct_ext_div = Div{parent=rct_pane,x=2,width=main.get_width()-2} - table.insert(panes, rct_ext_div) - - local rct_ext_page = app.new_page(rct_page, #panes) - rct_ext_page.tasks = { update } - - PushButton{parent=rct_div,x=9,y=18,text="MORE",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=rct_ext_page.nav_to} - PushButton{parent=rct_ext_div,x=9,y=18,text="BACK",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=rct_page.nav_to} - - TextBox{parent=rct_ext_div,y=1,text="More Reactor Info",height=1,alignment=ALIGN.CENTER} - - TextBox{parent=rct_ext_div,text="Fuel Tank",x=1,y=3,width=9,height=1,fg_bg=label} - local fuel_p = DataIndicator{parent=rct_ext_div,x=14,y=3,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} - local fuel_amnt = DataIndicator{parent=rct_ext_div,x=1,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} - - fuel_p.register(u_ps, "fuel_fill", function (x) fuel_p.update(x * 100) end) - fuel_amnt.register(u_ps, "fuel", fuel_amnt.update) - - TextBox{parent=rct_ext_div,text="Cool Coolant",x=1,y=6,width=12,height=1,fg_bg=label} - local cooled_p = DataIndicator{parent=rct_ext_div,x=14,y=6,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} - local ccool_amnt = DataIndicator{parent=rct_ext_div,x=1,y=7,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} - - cooled_p.register(u_ps, "ccool_fill", function (x) cooled_p.update(x * 100) end) - ccool_amnt.register(u_ps, "ccool_amnt", ccool_amnt.update) - - TextBox{parent=rct_ext_div,text="Hot Coolant",x=1,y=9,width=12,height=1,fg_bg=label} - local heated_p = DataIndicator{parent=rct_ext_div,x=14,y=9,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} - local hcool_amnt = DataIndicator{parent=rct_ext_div,x=1,y=10,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} - - heated_p.register(u_ps, "hcool_fill", function (x) heated_p.update(x * 100) end) - hcool_amnt.register(u_ps, "hcool_amnt", hcool_amnt.update) - - TextBox{parent=rct_ext_div,text="Waste Tank",x=1,y=12,width=10,height=1,fg_bg=label} - local waste_p = DataIndicator{parent=rct_ext_div,x=14,y=12,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} - local waste_amnt = DataIndicator{parent=rct_ext_div,x=1,y=13,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} - - waste_p.register(u_ps, "waste_fill", function (x) waste_p.update(x * 100) end) - waste_amnt.register(u_ps, "waste", waste_amnt.update) - - TextBox{parent=rct_ext_div,text="Boil Eff.",x=1,y=15,width=9,height=1,fg_bg=label} - TextBox{parent=rct_ext_div,text="Env. Loss",x=1,y=16,width=9,height=1,fg_bg=label} - local boil_eff = DataIndicator{parent=rct_ext_div,x=11,y=15,lu_colors=lu_col,label="",unit="%",format="%9.2f",value=0,width=11,fg_bg=text_fg} - local env_loss = DataIndicator{parent=rct_ext_div,x=11,y=16,lu_colors=lu_col,label="",unit="",format="%11.8f",value=0,width=11,fg_bg=text_fg} - - boil_eff.register(u_ps, "boil_eff", function (x) boil_eff.update(x * 100) end) - env_loss.register(u_ps, "env_loss", env_loss.update) + nav_links[i].reactor = reactor(app, u_page, panes, page_div, u_ps, update) --#endregion @@ -458,6 +324,18 @@ local function new_view(root) ttrip.register(u_ps, "U_TurbineTrip", ttrip.update) --#endregion + + --#region Boiler Tabs + + local blr_pane = Div{parent=page_div} + nav_links[i].boiler = {} + + for b_id = 1, unit.num_boilers do + local ps = unit.boiler_ps_tbl[b_id] + nav_links[i].boiler[b_id] = boiler(app, u_page, panes, blr_pane, b_id, ps, update) + end + + --#endregion end -- setup multipane diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index 392efa2..eaecc1e 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -7,11 +7,11 @@ local iocontrol = require("pocket.iocontrol") 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 unit_app = require("pocket.ui.apps.unit") local conn_waiting = require("pocket.ui.components.conn_waiting") local home_page = require("pocket.ui.pages.home_page") -local unit_page = require("pocket.ui.pages.unit_page") local style = require("pocket.ui.style") @@ -72,8 +72,8 @@ local function init(main) local page_div = Div{parent=main_pane,x=4,y=1} home_page(page_div) - unit_page(page_div) + unit_app(page_div) diag_apps(page_div) sys_apps(page_div) dummy_app(page_div) diff --git a/pocket/ui/pages/unit_boiler.lua b/pocket/ui/pages/unit_boiler.lua new file mode 100644 index 0000000..9f8ee62 --- /dev/null +++ b/pocket/ui/pages/unit_boiler.lua @@ -0,0 +1,79 @@ +local types = require("scada-common.types") +local util = require("scada-common.util") + +local iocontrol = require("pocket.iocontrol") + +local style = require("pocket.ui.style") + +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 IconIndicator = require("graphics.elements.indicators.icon") +local VerticalBar = require("graphics.elements.indicators.vbar") + +local PushButton = require("graphics.elements.controls.push_button") + +local ALIGN = core.ALIGN +local cpair = core.cpair + +local label = style.label +local lu_col = style.label_unit_pair +local text_fg = style.text_fg +local basic_states = style.icon_states.basic_states +local red_ind_s = style.icon_states.red_ind_s +local yel_ind_s = style.icon_states.yel_ind_s + +-- create a boiler view in the unit app +---@param app pocket_app +---@param u_page nav_tree_page +---@param panes table +---@param blr_pane graphics_element +---@param b_id integer boiler ID +---@param ps psil +---@param update function +return function (app, u_page, panes, blr_pane, b_id, ps, update) + local db = iocontrol.get_db() + + local blr_div = Div{parent=blr_pane,x=2,width=blr_pane.get_width()-2} + table.insert(panes, blr_div) + + local blr_page = app.new_page(u_page, #panes) + blr_page.tasks = { update } + + TextBox{parent=blr_div,y=1,text="Boiler "..b_id,height=1,alignment=ALIGN.CENTER} + + local hcool = VerticalBar{parent=blr_div,x=1,y=4,fg_bg=cpair(colors.orange,colors.gray),height=5,width=1} + local water = VerticalBar{parent=blr_div,x=3,y=4,fg_bg=cpair(colors.blue,colors.gray),height=5,width=1} + local steam = VerticalBar{parent=blr_div,x=19,y=4,fg_bg=cpair(colors.white,colors.gray),height=5,width=1} + local ccool = VerticalBar{parent=blr_div,x=21,y=4,fg_bg=cpair(colors.lightBlue,colors.gray),height=5,width=1} + + TextBox{parent=blr_div,text="H",x=1,y=3,width=1,height=1,fg_bg=label} + TextBox{parent=blr_div,text="W",x=3,y=3,width=1,height=1,fg_bg=label} + TextBox{parent=blr_div,text="S",x=19,y=3,width=1,height=1,fg_bg=label} + TextBox{parent=blr_div,text="C",x=21,y=3,width=1,height=1,fg_bg=label} + + hcool.register(ps, "hcool_fill", hcool.update) + water.register(ps, "water_fill", water.update) + steam.register(ps, "steam_fill", steam.update) + ccool.register(ps, "ccool_fill", ccool.update) + + TextBox{parent=blr_div,text="Temperature",x=5,y=5,width=13,height=1,fg_bg=label} + local t_prec = util.trinary(db.temp_label == types.TEMP_SCALE_UNITS[types.TEMP_SCALE.KELVIN], 11, 10) + local temp = DataIndicator{parent=blr_div,x=5,y=6,lu_colors=lu_col,label="",unit=db.temp_label,format="%"..t_prec..".2f",value=17802.03,commas=true,width=13,fg_bg=text_fg} + + local state = IconIndicator{parent=blr_div,x=7,y=3,label="State",states=basic_states} + + temp.register(ps, "temperature", function (t) temp.update(db.temp_convert(t)) end) + state.register(ps, "BoilerStatus", state.update) + + local b_wll = IconIndicator{parent=blr_div,y=10,label="Water Level Lo",states=red_ind_s} + local b_hr = IconIndicator{parent=blr_div,label="Heating Rate Lo",states=yel_ind_s} + + b_wll.register(ps, "WaterLevelLow", b_wll.update) + b_hr.register(ps, "HeatingRateLow", b_hr.update) + + return blr_page.nav_to +end diff --git a/pocket/ui/pages/unit_reactor.lua b/pocket/ui/pages/unit_reactor.lua new file mode 100644 index 0000000..8fe6213 --- /dev/null +++ b/pocket/ui/pages/unit_reactor.lua @@ -0,0 +1,159 @@ +local types = require("scada-common.types") +local util = require("scada-common.util") + +local iocontrol = require("pocket.iocontrol") + +local style = require("pocket.ui.style") + +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 IconIndicator = require("graphics.elements.indicators.icon") +local VerticalBar = require("graphics.elements.indicators.vbar") + +local PushButton = require("graphics.elements.controls.push_button") + +local ALIGN = core.ALIGN +local cpair = core.cpair + +local label = style.label +local lu_col = style.label_unit_pair +local text_fg = style.text_fg +local mode_states = style.icon_states.mode_states +local red_ind_s = style.icon_states.red_ind_s +local yel_ind_s = style.icon_states.yel_ind_s + +-- create a reactor view in the unit app +---@param app pocket_app +---@param u_page nav_tree_page +---@param panes table +---@param page_div graphics_element +---@param u_ps psil +---@param update function +return function (app, u_page, panes, page_div, u_ps, update) + local db = iocontrol.get_db() + + local rct_pane = Div{parent=page_div} + local rct_div = Div{parent=rct_pane,x=2,width=page_div.get_width()-2} + table.insert(panes, rct_div) + + local rct_page = app.new_page(u_page, #panes) + rct_page.tasks = { update } + + TextBox{parent=rct_div,y=1,text="Fission Reactor",height=1,alignment=ALIGN.CENTER} + + local fuel = VerticalBar{parent=rct_div,x=1,y=4,fg_bg=cpair(colors.lightGray,colors.gray),height=5,width=1} + local ccool = VerticalBar{parent=rct_div,x=3,y=4,fg_bg=cpair(colors.blue,colors.gray),height=5,width=1} + local hcool = VerticalBar{parent=rct_div,x=19,y=4,fg_bg=cpair(colors.white,colors.gray),height=5,width=1} + local waste = VerticalBar{parent=rct_div,x=21,y=4,fg_bg=cpair(colors.brown,colors.gray),height=5,width=1} + + TextBox{parent=rct_div,text="F",x=1,y=3,width=1,height=1,fg_bg=label} + TextBox{parent=rct_div,text="C",x=3,y=3,width=1,height=1,fg_bg=label} + TextBox{parent=rct_div,text="H",x=19,y=3,width=1,height=1,fg_bg=label} + TextBox{parent=rct_div,text="W",x=21,y=3,width=1,height=1,fg_bg=label} + + fuel.register(u_ps, "fuel_fill", fuel.update) + ccool.register(u_ps, "ccool_fill", ccool.update) + hcool.register(u_ps, "hcool_fill", hcool.update) + waste.register(u_ps, "waste_fill", waste.update) + + ccool.register(u_ps, "ccool_type", function (type) + if type == types.FLUID.SODIUM then + ccool.recolor(cpair(colors.lightBlue, colors.gray)) + else + ccool.recolor(cpair(colors.blue, colors.gray)) + end + end) + + hcool.register(u_ps, "hcool_type", function (type) + if type == types.FLUID.SUPERHEATED_SODIUM then + hcool.recolor(cpair(colors.orange, colors.gray)) + else + hcool.recolor(cpair(colors.white, colors.gray)) + end + end) + + TextBox{parent=rct_div,text="Burn Rate",x=5,y=5,width=13,height=1,fg_bg=label} + local burn_rate = DataIndicator{parent=rct_div,x=5,y=6,lu_colors=lu_col,label="",unit="mB/t",format="%8.2f",value=0,commas=true,width=13,fg_bg=text_fg} + TextBox{parent=rct_div,text="Temperature",x=5,y=7,width=13,height=1,fg_bg=label} + local t_prec = util.trinary(db.temp_label == types.TEMP_SCALE_UNITS[types.TEMP_SCALE.KELVIN], 11, 10) + local core_temp = DataIndicator{parent=rct_div,x=5,y=8,lu_colors=lu_col,label="",unit=db.temp_label,format="%"..t_prec..".2f",value=0,commas=true,width=13,fg_bg=text_fg} + + local state = IconIndicator{parent=rct_div,x=7,y=3,label="State",states=mode_states} + + burn_rate.register(u_ps, "act_burn_rate", burn_rate.update) + core_temp.register(u_ps, "temp", function (t) core_temp.update(db.temp_convert(t)) end) + state.register(u_ps, "U_ControlStatus", state.update) + + local r_temp = IconIndicator{parent=rct_div,y=10,label="Reactor Temp. Hi",states=red_ind_s} + local r_rhdt = IconIndicator{parent=rct_div,label="Hi Delta Temp.",states=yel_ind_s} + local r_firl = IconIndicator{parent=rct_div,label="Fuel Rate Lo",states=yel_ind_s} + local r_wloc = IconIndicator{parent=rct_div,label="Waste Line Occl.",states=yel_ind_s} + local r_hsrt = IconIndicator{parent=rct_div,label="Hi Startup Rate",states=yel_ind_s} + + r_temp.register(u_ps, "ReactorTempHigh", r_temp.update) + r_rhdt.register(u_ps, "ReactorHighDeltaT", r_rhdt.update) + r_firl.register(u_ps, "FuelInputRateLow", r_firl.update) + r_wloc.register(u_ps, "WasteLineOcclusion", r_wloc.update) + r_hsrt.register(u_ps, "HighStartupRate", r_hsrt.update) + + TextBox{parent=rct_div,text="HR",x=1,y=16,width=4,height=1,fg_bg=label} + local heating_r = DataIndicator{parent=rct_div,x=6,y=16,lu_colors=lu_col,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=text_fg} + TextBox{parent=rct_div,text="DMG",x=1,y=17,width=4,height=1,fg_bg=label} + local damage_p = DataIndicator{parent=rct_div,x=6,y=17,lu_colors=lu_col,label="",unit="%",format="%11.2f",value=0,width=16,fg_bg=text_fg} + + heating_r.register(u_ps, "heating_rate", heating_r.update) + damage_p.register(u_ps, "damage", damage_p.update) + + local rct_ext_div = Div{parent=rct_pane,x=2,width=page_div.get_width()-2} + table.insert(panes, rct_ext_div) + + local rct_ext_page = app.new_page(rct_page, #panes) + rct_ext_page.tasks = { update } + + PushButton{parent=rct_div,x=9,y=18,text="MORE",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=rct_ext_page.nav_to} + PushButton{parent=rct_ext_div,x=9,y=18,text="BACK",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=rct_page.nav_to} + + TextBox{parent=rct_ext_div,y=1,text="More Reactor Info",height=1,alignment=ALIGN.CENTER} + + TextBox{parent=rct_ext_div,text="Fuel Tank",x=1,y=3,width=9,height=1,fg_bg=label} + local fuel_p = DataIndicator{parent=rct_ext_div,x=14,y=3,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} + local fuel_amnt = DataIndicator{parent=rct_ext_div,x=1,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + + fuel_p.register(u_ps, "fuel_fill", function (x) fuel_p.update(x * 100) end) + fuel_amnt.register(u_ps, "fuel", fuel_amnt.update) + + TextBox{parent=rct_ext_div,text="Cool Coolant",x=1,y=6,width=12,height=1,fg_bg=label} + local cooled_p = DataIndicator{parent=rct_ext_div,x=14,y=6,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} + local ccool_amnt = DataIndicator{parent=rct_ext_div,x=1,y=7,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + + cooled_p.register(u_ps, "ccool_fill", function (x) cooled_p.update(x * 100) end) + ccool_amnt.register(u_ps, "ccool_amnt", ccool_amnt.update) + + TextBox{parent=rct_ext_div,text="Hot Coolant",x=1,y=9,width=12,height=1,fg_bg=label} + local heated_p = DataIndicator{parent=rct_ext_div,x=14,y=9,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} + local hcool_amnt = DataIndicator{parent=rct_ext_div,x=1,y=10,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + + heated_p.register(u_ps, "hcool_fill", function (x) heated_p.update(x * 100) end) + hcool_amnt.register(u_ps, "hcool_amnt", hcool_amnt.update) + + TextBox{parent=rct_ext_div,text="Waste Tank",x=1,y=12,width=10,height=1,fg_bg=label} + local waste_p = DataIndicator{parent=rct_ext_div,x=14,y=12,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} + local waste_amnt = DataIndicator{parent=rct_ext_div,x=1,y=13,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + + waste_p.register(u_ps, "waste_fill", function (x) waste_p.update(x * 100) end) + waste_amnt.register(u_ps, "waste", waste_amnt.update) + + TextBox{parent=rct_ext_div,text="Boil Eff.",x=1,y=15,width=9,height=1,fg_bg=label} + TextBox{parent=rct_ext_div,text="Env. Loss",x=1,y=16,width=9,height=1,fg_bg=label} + local boil_eff = DataIndicator{parent=rct_ext_div,x=11,y=15,lu_colors=lu_col,label="",unit="%",format="%9.2f",value=0,width=11,fg_bg=text_fg} + local env_loss = DataIndicator{parent=rct_ext_div,x=11,y=16,lu_colors=lu_col,label="",unit="",format="%11.8f",value=0,width=11,fg_bg=text_fg} + + boil_eff.register(u_ps, "boil_eff", function (x) boil_eff.update(x * 100) end) + env_loss.register(u_ps, "env_loss", env_loss.update) + + return rct_page.nav_to +end diff --git a/pocket/ui/style.lua b/pocket/ui/style.lua index 2fb7526..f91d7b8 100644 --- a/pocket/ui/style.lua +++ b/pocket/ui/style.lua @@ -12,7 +12,9 @@ local cpair = core.cpair style.root = cpair(colors.white, colors.black) style.header = cpair(colors.white, colors.gray) -style.label = cpair(colors.gray, colors.lightGray) +style.text_fg = cpair(colors.white, colors._INHERIT) +style.label = cpair(colors.lightGray, colors.black) +style.label_unit_pair = cpair(colors.lightGray, colors.lightGray) style.colors = { { c = colors.red, hex = 0xdf4949 }, @@ -33,6 +35,40 @@ style.colors = { -- { c = colors.brown, hex = 0x7f664c } } +local states = {} + +states.basic_states = { + { color = cpair(colors.black, colors.lightGray), symbol = "\x07" }, + { color = cpair(colors.black, colors.red), symbol = "-" }, + { color = cpair(colors.black, colors.yellow), symbol = "\x1e" }, + { color = cpair(colors.black, colors.green), symbol = "+" } +} + +states.mode_states = { + { color = cpair(colors.black, colors.lightGray), symbol = "\x07" }, + { color = cpair(colors.black, colors.red), symbol = "-" }, + { color = cpair(colors.black, colors.green), symbol = "+" }, + { color = cpair(colors.black, colors.purple), symbol = "A" } +} + +states.emc_ind_s = { + { color = cpair(colors.black, colors.gray), symbol = "-" }, + { color = cpair(colors.black, colors.white), symbol = "\x07" }, + { color = cpair(colors.black, colors.green), symbol = "+" } +} + +states.red_ind_s = { + { color = cpair(colors.black, colors.lightGray), symbol = "+" }, + { color = cpair(colors.black, colors.red), symbol = "-" } +} + +states.yel_ind_s = { + { color = cpair(colors.black, colors.lightGray), symbol = "+" }, + { color = cpair(colors.black, colors.yellow), symbol = "-" } +} + +style.icon_states = states + -- MAIN LAYOUT -- style.reactor = { From 0fa0324940f93e8dc942fcfb8a2f94b80288383c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 31 May 2024 18:16:04 -0400 Subject: [PATCH 35/87] #206 pocket boiler view --- pocket/iocontrol.lua | 10 ++++++ pocket/ui/pages/unit_boiler.lua | 62 ++++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index c74c6a7..83b807c 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -750,20 +750,30 @@ function iocontrol.record_unit_data(data) local ps = unit.boiler_ps_tbl[id] ---@type psil local boiler_status = 1 + local computed_status = 1 if unit.rtu_hw.boilers[id].connected then if unit.rtu_hw.boilers[id].faulted then boiler_status = 3 + computed_status = 3 elseif boiler.formed then boiler_status = 4 + + if boiler.state.boil_rate > 0 then + computed_status = 5 + else + computed_status = 4 + end else boiler_status = 2 + computed_status = 2 end _record_multiblock_status(unit.rtu_hw.boilers[id].faulted, boiler, ps) end ps.publish("BoilerStatus", boiler_status) + ps.publish("BoilerStateStatus", computed_status) end unit.turbine_data_tbl = data[8] diff --git a/pocket/ui/pages/unit_boiler.lua b/pocket/ui/pages/unit_boiler.lua index 9f8ee62..37f4715 100644 --- a/pocket/ui/pages/unit_boiler.lua +++ b/pocket/ui/pages/unit_boiler.lua @@ -11,6 +11,7 @@ local Div = require("graphics.elements.div") local TextBox = require("graphics.elements.textbox") local DataIndicator = require("graphics.elements.indicators.data") +local StateIndicator = require("graphics.elements.indicators.state") local IconIndicator = require("graphics.elements.indicators.icon") local VerticalBar = require("graphics.elements.indicators.vbar") @@ -22,7 +23,6 @@ local cpair = core.cpair local label = style.label local lu_col = style.label_unit_pair local text_fg = style.text_fg -local basic_states = style.icon_states.basic_states local red_ind_s = style.icon_states.red_ind_s local yel_ind_s = style.icon_states.yel_ind_s @@ -43,7 +43,9 @@ return function (app, u_page, panes, blr_pane, b_id, ps, update) local blr_page = app.new_page(u_page, #panes) blr_page.tasks = { update } - TextBox{parent=blr_div,y=1,text="Boiler "..b_id,height=1,alignment=ALIGN.CENTER} + TextBox{parent=blr_div,y=1,text="Boiler "..b_id,width=8,height=1} + local status = StateIndicator{parent=blr_div,x=10,y=1,states=style.boiler.states,value=1,min_width=12} + status.register(ps, "BoilerStateStatus", status.update) local hcool = VerticalBar{parent=blr_div,x=1,y=4,fg_bg=cpair(colors.orange,colors.gray),height=5,width=1} local water = VerticalBar{parent=blr_div,x=3,y=4,fg_bg=cpair(colors.blue,colors.gray),height=5,width=1} @@ -64,10 +66,7 @@ return function (app, u_page, panes, blr_pane, b_id, ps, update) local t_prec = util.trinary(db.temp_label == types.TEMP_SCALE_UNITS[types.TEMP_SCALE.KELVIN], 11, 10) local temp = DataIndicator{parent=blr_div,x=5,y=6,lu_colors=lu_col,label="",unit=db.temp_label,format="%"..t_prec..".2f",value=17802.03,commas=true,width=13,fg_bg=text_fg} - local state = IconIndicator{parent=blr_div,x=7,y=3,label="State",states=basic_states} - temp.register(ps, "temperature", function (t) temp.update(db.temp_convert(t)) end) - state.register(ps, "BoilerStatus", state.update) local b_wll = IconIndicator{parent=blr_div,y=10,label="Water Level Lo",states=red_ind_s} local b_hr = IconIndicator{parent=blr_div,label="Heating Rate Lo",states=yel_ind_s} @@ -75,5 +74,58 @@ return function (app, u_page, panes, blr_pane, b_id, ps, update) b_wll.register(ps, "WaterLevelLow", b_wll.update) b_hr.register(ps, "HeatingRateLow", b_hr.update) + TextBox{parent=blr_div,text="Boil Rate",x=1,y=13,width=12,height=1,fg_bg=label} + local boil_r = DataIndicator{parent=blr_div,x=6,y=14,lu_colors=lu_col,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=16,fg_bg=text_fg} + + boil_r.register(ps, "boil_rate", boil_r.update) + + local blr_ext_div = Div{parent=blr_pane,x=2,width=blr_pane.get_width()-2} + table.insert(panes, blr_ext_div) + + local blr_ext_page = app.new_page(blr_page, #panes) + blr_ext_page.tasks = { update } + + PushButton{parent=blr_div,x=9,y=18,text="MORE",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=blr_ext_page.nav_to} + PushButton{parent=blr_ext_div,x=9,y=18,text="BACK",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=blr_page.nav_to} + + TextBox{parent=blr_ext_div,y=1,text="More Boiler Info",height=1,alignment=ALIGN.CENTER} + + local function update_amount(indicator) + return function (x) indicator.update(x.amount) end + end + + TextBox{parent=blr_ext_div,text="Hot Coolant",x=1,y=3,width=12,height=1,fg_bg=label} + local heated_p = DataIndicator{parent=blr_ext_div,x=14,y=3,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} + local hcool_amnt = DataIndicator{parent=blr_ext_div,x=1,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + + heated_p.register(ps, "hcool_fill", function (x) heated_p.update(x * 100) end) + hcool_amnt.register(ps, "hcool", update_amount(hcool_amnt)) + + TextBox{parent=blr_ext_div,text="Water Tank",x=1,y=6,width=9,height=1,fg_bg=label} + local fuel_p = DataIndicator{parent=blr_ext_div,x=14,y=6,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} + local fuel_amnt = DataIndicator{parent=blr_ext_div,x=1,y=7,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + + fuel_p.register(ps, "water_fill", function (x) fuel_p.update(x * 100) end) + fuel_amnt.register(ps, "water", update_amount(fuel_amnt)) + + TextBox{parent=blr_ext_div,text="Steam Tank",x=1,y=9,width=10,height=1,fg_bg=label} + local steam_p = DataIndicator{parent=blr_ext_div,x=14,y=9,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} + local steam_amnt = DataIndicator{parent=blr_ext_div,x=1,y=10,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + + steam_p.register(ps, "steam_fill", function (x) steam_p.update(x * 100) end) + steam_amnt.register(ps, "steam", update_amount(steam_amnt)) + + TextBox{parent=blr_ext_div,text="Cool Coolant",x=1,y=12,width=12,height=1,fg_bg=label} + local cooled_p = DataIndicator{parent=blr_ext_div,x=14,y=12,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} + local ccool_amnt = DataIndicator{parent=blr_ext_div,x=1,y=13,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + + cooled_p.register(ps, "ccool_fill", function (x) cooled_p.update(x * 100) end) + ccool_amnt.register(ps, "ccool", update_amount(ccool_amnt)) + + TextBox{parent=blr_ext_div,text="Env. Loss",x=1,y=15,width=9,height=1,fg_bg=label} + local env_loss = DataIndicator{parent=blr_ext_div,x=11,y=15,lu_colors=lu_col,label="",unit="",format="%11.8f",value=0,width=11,fg_bg=text_fg} + + env_loss.register(ps, "env_loss", env_loss.update) + return blr_page.nav_to end From ac2d189c1a6850da00d2cb4a9febbf727f2b19db Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 31 May 2024 19:25:36 -0400 Subject: [PATCH 36/87] reactor and boiler view fixes --- pocket/ui/pages/unit_boiler.lua | 10 +++++----- pocket/ui/pages/unit_reactor.lua | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pocket/ui/pages/unit_boiler.lua b/pocket/ui/pages/unit_boiler.lua index 37f4715..c161248 100644 --- a/pocket/ui/pages/unit_boiler.lua +++ b/pocket/ui/pages/unit_boiler.lua @@ -64,7 +64,7 @@ return function (app, u_page, panes, blr_pane, b_id, ps, update) TextBox{parent=blr_div,text="Temperature",x=5,y=5,width=13,height=1,fg_bg=label} local t_prec = util.trinary(db.temp_label == types.TEMP_SCALE_UNITS[types.TEMP_SCALE.KELVIN], 11, 10) - local temp = DataIndicator{parent=blr_div,x=5,y=6,lu_colors=lu_col,label="",unit=db.temp_label,format="%"..t_prec..".2f",value=17802.03,commas=true,width=13,fg_bg=text_fg} + local temp = DataIndicator{parent=blr_div,x=5,y=6,lu_colors=lu_col,label="",unit=db.temp_label,format="%"..t_prec..".2f",value=0,commas=true,width=13,fg_bg=text_fg} temp.register(ps, "temperature", function (t) temp.update(db.temp_convert(t)) end) @@ -96,28 +96,28 @@ return function (app, u_page, panes, blr_pane, b_id, ps, update) TextBox{parent=blr_ext_div,text="Hot Coolant",x=1,y=3,width=12,height=1,fg_bg=label} local heated_p = DataIndicator{parent=blr_ext_div,x=14,y=3,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} - local hcool_amnt = DataIndicator{parent=blr_ext_div,x=1,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + local hcool_amnt = DataIndicator{parent=blr_ext_div,x=1,y=4,lu_colors=lu_col,label="",unit="mB",format="%18.0f",value=0,commas=true,width=21,fg_bg=text_fg} heated_p.register(ps, "hcool_fill", function (x) heated_p.update(x * 100) end) hcool_amnt.register(ps, "hcool", update_amount(hcool_amnt)) TextBox{parent=blr_ext_div,text="Water Tank",x=1,y=6,width=9,height=1,fg_bg=label} local fuel_p = DataIndicator{parent=blr_ext_div,x=14,y=6,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} - local fuel_amnt = DataIndicator{parent=blr_ext_div,x=1,y=7,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + local fuel_amnt = DataIndicator{parent=blr_ext_div,x=1,y=7,lu_colors=lu_col,label="",unit="mB",format="%18.0f",value=0,commas=true,width=21,fg_bg=text_fg} fuel_p.register(ps, "water_fill", function (x) fuel_p.update(x * 100) end) fuel_amnt.register(ps, "water", update_amount(fuel_amnt)) TextBox{parent=blr_ext_div,text="Steam Tank",x=1,y=9,width=10,height=1,fg_bg=label} local steam_p = DataIndicator{parent=blr_ext_div,x=14,y=9,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} - local steam_amnt = DataIndicator{parent=blr_ext_div,x=1,y=10,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + local steam_amnt = DataIndicator{parent=blr_ext_div,x=1,y=10,lu_colors=lu_col,label="",unit="mB",format="%18.0f",value=0,commas=true,width=21,fg_bg=text_fg} steam_p.register(ps, "steam_fill", function (x) steam_p.update(x * 100) end) steam_amnt.register(ps, "steam", update_amount(steam_amnt)) TextBox{parent=blr_ext_div,text="Cool Coolant",x=1,y=12,width=12,height=1,fg_bg=label} local cooled_p = DataIndicator{parent=blr_ext_div,x=14,y=12,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} - local ccool_amnt = DataIndicator{parent=blr_ext_div,x=1,y=13,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + local ccool_amnt = DataIndicator{parent=blr_ext_div,x=1,y=13,lu_colors=lu_col,label="",unit="mB",format="%18.0f",value=0,commas=true,width=21,fg_bg=text_fg} cooled_p.register(ps, "ccool_fill", function (x) cooled_p.update(x * 100) end) ccool_amnt.register(ps, "ccool", update_amount(ccool_amnt)) diff --git a/pocket/ui/pages/unit_reactor.lua b/pocket/ui/pages/unit_reactor.lua index 8fe6213..a6fa011 100644 --- a/pocket/ui/pages/unit_reactor.lua +++ b/pocket/ui/pages/unit_reactor.lua @@ -121,28 +121,28 @@ return function (app, u_page, panes, page_div, u_ps, update) TextBox{parent=rct_ext_div,text="Fuel Tank",x=1,y=3,width=9,height=1,fg_bg=label} local fuel_p = DataIndicator{parent=rct_ext_div,x=14,y=3,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} - local fuel_amnt = DataIndicator{parent=rct_ext_div,x=1,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + local fuel_amnt = DataIndicator{parent=rct_ext_div,x=1,y=4,lu_colors=lu_col,label="",unit="mB",format="%18.0f",value=0,commas=true,width=21,fg_bg=text_fg} fuel_p.register(u_ps, "fuel_fill", function (x) fuel_p.update(x * 100) end) fuel_amnt.register(u_ps, "fuel", fuel_amnt.update) TextBox{parent=rct_ext_div,text="Cool Coolant",x=1,y=6,width=12,height=1,fg_bg=label} local cooled_p = DataIndicator{parent=rct_ext_div,x=14,y=6,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} - local ccool_amnt = DataIndicator{parent=rct_ext_div,x=1,y=7,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + local ccool_amnt = DataIndicator{parent=rct_ext_div,x=1,y=7,lu_colors=lu_col,label="",unit="mB",format="%18.0f",value=0,commas=true,width=21,fg_bg=text_fg} cooled_p.register(u_ps, "ccool_fill", function (x) cooled_p.update(x * 100) end) ccool_amnt.register(u_ps, "ccool_amnt", ccool_amnt.update) TextBox{parent=rct_ext_div,text="Hot Coolant",x=1,y=9,width=12,height=1,fg_bg=label} local heated_p = DataIndicator{parent=rct_ext_div,x=14,y=9,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} - local hcool_amnt = DataIndicator{parent=rct_ext_div,x=1,y=10,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + local hcool_amnt = DataIndicator{parent=rct_ext_div,x=1,y=10,lu_colors=lu_col,label="",unit="mB",format="%18.0f",value=0,commas=true,width=21,fg_bg=text_fg} heated_p.register(u_ps, "hcool_fill", function (x) heated_p.update(x * 100) end) hcool_amnt.register(u_ps, "hcool_amnt", hcool_amnt.update) TextBox{parent=rct_ext_div,text="Waste Tank",x=1,y=12,width=10,height=1,fg_bg=label} local waste_p = DataIndicator{parent=rct_ext_div,x=14,y=12,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} - local waste_amnt = DataIndicator{parent=rct_ext_div,x=1,y=13,lu_colors=lu_col,label="",unit="mB/t",format="%16.0f",value=0,commas=true,width=21,fg_bg=text_fg} + local waste_amnt = DataIndicator{parent=rct_ext_div,x=1,y=13,lu_colors=lu_col,label="",unit="mB",format="%18.0f",value=0,commas=true,width=21,fg_bg=text_fg} waste_p.register(u_ps, "waste_fill", function (x) waste_p.update(x * 100) end) waste_amnt.register(u_ps, "waste", waste_amnt.update) From be6c3755a4b2660ec45fe5f36dfffec3f55b10bb Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 1 Jun 2024 00:50:19 -0400 Subject: [PATCH 37/87] #207 pocket turbine view --- pocket/iocontrol.lua | 14 ++++ pocket/ui/apps/unit.lua | 17 ++++- pocket/ui/pages/unit_boiler.lua | 12 ++-- pocket/ui/pages/unit_turbine.lua | 117 +++++++++++++++++++++++++++++++ pocket/ui/style.lua | 6 ++ scada-common/util.lua | 20 +++++- supervisor/startup.lua | 2 +- supervisor/unitlogic.lua | 17 +---- 8 files changed, 179 insertions(+), 26 deletions(-) create mode 100644 pocket/ui/pages/unit_turbine.lua diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 83b807c..c3ecc35 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -783,18 +783,32 @@ function iocontrol.record_unit_data(data) local ps = unit.turbine_ps_tbl[id] ---@type psil local turbine_status = 1 + local computed_status = 1 if unit.rtu_hw.turbines[id].connected then if unit.rtu_hw.turbines[id].faulted then turbine_status = 3 + computed_status = 3 elseif turbine.formed then turbine_status = 4 + + if turbine.tanks.energy_fill >= 0.99 then + computed_status = 6 + elseif turbine.state.flow_rate < 100 then + computed_status = 4 + else + computed_status = 5 + end else turbine_status = 2 + computed_status = 2 end + + _record_multiblock_status(unit.rtu_hw.turbines[id].faulted, turbine, ps) end ps.publish("TurbineStatus", turbine_status) + ps.publish("TurbineStateStatus", computed_status) end unit.tank_data_tbl = data[9] diff --git a/pocket/ui/apps/unit.lua b/pocket/ui/apps/unit.lua index 51c27b8..a6f28b3 100644 --- a/pocket/ui/apps/unit.lua +++ b/pocket/ui/apps/unit.lua @@ -11,6 +11,7 @@ local style = require("pocket.ui.style") local boiler = require("pocket.ui.pages.unit_boiler") local reactor = require("pocket.ui.pages.unit_reactor") +local turbine = require("pocket.ui.pages.unit_turbine") local core = require("graphics.core") @@ -74,11 +75,11 @@ local function new_view(root) } for i = 1, unit.num_boilers do - table.insert(list, { label = "B-" .. i, color = core.cpair(colors.black, colors.lightBlue), callback = nav_links[id].boiler[i] }) + table.insert(list, { label = "B-" .. i, color = core.cpair(colors.black, colors.lightGray), callback = nav_links[id].boiler[i] }) end for i = 1, unit.num_turbines do - table.insert(list, { label = "T-" .. i, color = core.cpair(colors.black, colors.white), callback = function () end }) + table.insert(list, { label = "T-" .. i, color = core.cpair(colors.black, colors.lightGray), callback = nav_links[id].turbine[i] }) end app.set_sidebar(list) @@ -336,6 +337,18 @@ local function new_view(root) end --#endregion + + --#region Turbine Tabs + + local tbn_pane = Div{parent=page_div} + nav_links[i].turbine = {} + + for t_id = 1, unit.num_turbines do + local ps = unit.turbine_ps_tbl[t_id] + nav_links[i].turbine[t_id] = turbine(app, u_page, panes, tbn_pane, i, t_id, ps, update) + end + + --#endregion end -- setup multipane diff --git a/pocket/ui/pages/unit_boiler.lua b/pocket/ui/pages/unit_boiler.lua index c161248..e15ae40 100644 --- a/pocket/ui/pages/unit_boiler.lua +++ b/pocket/ui/pages/unit_boiler.lua @@ -20,11 +20,11 @@ local PushButton = require("graphics.elements.controls.push_button") local ALIGN = core.ALIGN local cpair = core.cpair -local label = style.label -local lu_col = style.label_unit_pair -local text_fg = style.text_fg -local red_ind_s = style.icon_states.red_ind_s -local yel_ind_s = style.icon_states.yel_ind_s +local label = style.label +local lu_col = style.label_unit_pair +local text_fg = style.text_fg +local red_ind_s = style.icon_states.red_ind_s +local yel_ind_s = style.icon_states.yel_ind_s -- create a boiler view in the unit app ---@param app pocket_app @@ -43,7 +43,7 @@ return function (app, u_page, panes, blr_pane, b_id, ps, update) local blr_page = app.new_page(u_page, #panes) blr_page.tasks = { update } - TextBox{parent=blr_div,y=1,text="Boiler "..b_id,width=8,height=1} + TextBox{parent=blr_div,y=1,text="BLR #"..b_id,width=8,height=1} local status = StateIndicator{parent=blr_div,x=10,y=1,states=style.boiler.states,value=1,min_width=12} status.register(ps, "BoilerStateStatus", status.update) diff --git a/pocket/ui/pages/unit_turbine.lua b/pocket/ui/pages/unit_turbine.lua new file mode 100644 index 0000000..b4d9a2e --- /dev/null +++ b/pocket/ui/pages/unit_turbine.lua @@ -0,0 +1,117 @@ +local util = require("scada-common.util") + +local iocontrol = require("pocket.iocontrol") + +local style = require("pocket.ui.style") + +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 IconIndicator = require("graphics.elements.indicators.icon") +local PowerIndicator = require("graphics.elements.indicators.power") +local StateIndicator = require("graphics.elements.indicators.state") +local VerticalBar = require("graphics.elements.indicators.vbar") + +local PushButton = require("graphics.elements.controls.push_button") + +local ALIGN = core.ALIGN +local cpair = core.cpair + +local label = style.label +local lu_col = style.label_unit_pair +local text_fg = style.text_fg +local tri_ind_s = style.icon_states.tri_ind_s +local red_ind_s = style.icon_states.red_ind_s +local yel_ind_s = style.icon_states.yel_ind_s + +-- create a turbine view in the unit app +---@param app pocket_app +---@param u_page nav_tree_page +---@param panes table +---@param tbn_pane graphics_element +---@param u_id integer unit ID +---@param t_id integer turbine ID +---@param ps psil +---@param update function +return function (app, u_page, panes, tbn_pane, u_id, t_id, ps, update) + local db = iocontrol.get_db() + + local tbn_div = Div{parent=tbn_pane,x=2,width=tbn_pane.get_width()-2} + table.insert(panes, tbn_div) + + local tbn_page = app.new_page(u_page, #panes) + tbn_page.tasks = { update } + + TextBox{parent=tbn_div,y=1,text="TRBN #"..t_id,width=8,height=1} + local status = StateIndicator{parent=tbn_div,x=10,y=1,states=style.turbine.states,value=1,min_width=12} + status.register(ps, "TurbineStateStatus", status.update) + + local steam = VerticalBar{parent=tbn_div,x=1,y=4,fg_bg=cpair(colors.white,colors.gray),height=5,width=1} + local ccool = VerticalBar{parent=tbn_div,x=21,y=4,fg_bg=cpair(colors.green,colors.gray),height=5,width=1} + + TextBox{parent=tbn_div,text="S",x=1,y=3,width=1,height=1,fg_bg=label} + TextBox{parent=tbn_div,text="E",x=21,y=3,width=1,height=1,fg_bg=label} + + steam.register(ps, "steam_fill", steam.update) + ccool.register(ps, "energy_fill", ccool.update) + + TextBox{parent=tbn_div,text="Production",x=3,y=3,width=17,height=1,fg_bg=label} + local prod_rate = PowerIndicator{parent=tbn_div,x=3,y=4,lu_colors=lu_col,label="",format="%11.2f",value=0,rate=true,width=17,fg_bg=text_fg} + TextBox{parent=tbn_div,text="Flow Rate",x=3,y=5,width=17,height=1,fg_bg=label} + local flow_rate = DataIndicator{parent=tbn_div,x=3,y=6,lu_colors=lu_col,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=17,fg_bg=text_fg} + TextBox{parent=tbn_div,text="Steam Input Rate",x=3,y=7,width=17,height=1,fg_bg=label} + local input_rate = DataIndicator{parent=tbn_div,x=3,y=8,lu_colors=lu_col,label="",unit="mB/t",format="%11.0f",value=0,commas=true,width=17,fg_bg=text_fg} + + prod_rate.register(ps, "prod_rate", function (val) prod_rate.update(util.joules_to_fe(val)) end) + flow_rate.register(ps, "flow_rate", flow_rate.update) + input_rate.register(ps, "steam_input_rate", input_rate.update) + + local t_sdo = IconIndicator{parent=tbn_div,y=10,label="Steam Dumping",states=tri_ind_s} + local t_tos = IconIndicator{parent=tbn_div,label="Over Speed",states=red_ind_s} + local t_gtrp = IconIndicator{parent=tbn_div,label="Generator Trip",states=yel_ind_s} + local t_trp = IconIndicator{parent=tbn_div,label="Turbine Trip",states=red_ind_s} + + t_sdo.register(ps, "SteamDumpOpen", t_sdo.update) + t_tos.register(ps, "TurbineOverSpeed", t_tos.update) + t_gtrp.register(ps, "GeneratorTrip", t_gtrp.update) + t_trp.register(ps, "TurbineTrip", t_trp.update) + + + local tbn_ext_div = Div{parent=tbn_pane,x=2,width=tbn_pane.get_width()-2} + table.insert(panes, tbn_ext_div) + + local tbn_ext_page = app.new_page(tbn_page, #panes) + tbn_ext_page.tasks = { update } + + PushButton{parent=tbn_div,x=9,y=18,text="MORE",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=tbn_ext_page.nav_to} + PushButton{parent=tbn_ext_div,x=9,y=18,text="BACK",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=tbn_page.nav_to} + + TextBox{parent=tbn_ext_div,y=1,text="More Turbine Info",height=1,alignment=ALIGN.CENTER} + + TextBox{parent=tbn_ext_div,text="Steam Tank",x=1,y=3,width=10,height=1,fg_bg=label} + local steam_p = DataIndicator{parent=tbn_ext_div,x=14,y=3,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} + local steam_amnt = DataIndicator{parent=tbn_ext_div,x=1,y=4,lu_colors=lu_col,label="",unit="mB",format="%18.0f",value=0,commas=true,width=21,fg_bg=text_fg} + + steam_p.register(ps, "steam_fill", function (x) steam_p.update(x * 100) end) + steam_amnt.register(ps, "steam", function (x) steam_amnt.update(x.amount) end) + + TextBox{parent=tbn_ext_div,text="Energy Fill",x=1,y=6,width=12,height=1,fg_bg=label} + local charge_p = DataIndicator{parent=tbn_ext_div,x=14,y=6,lu_colors=lu_col,label="",unit="%",format="%6.2f",value=0,width=8,fg_bg=text_fg} + local charge_amnt = PowerIndicator{parent=tbn_ext_div,x=1,y=7,lu_colors=lu_col,label="",format="%17.4f",value=0,width=21,fg_bg=text_fg} + + charge_p.register(ps, "energy_fill", function (x) charge_p.update(x * 100) end) + charge_amnt.register(ps, "energy", charge_amnt.update) + + TextBox{parent=tbn_ext_div,text="Rotation Rate",x=1,y=9,width=13,height=1,fg_bg=label} + local rotation = DataIndicator{parent=tbn_ext_div,x=1,y=10,lu_colors=lu_col,label="",unit="",format="%21.12f",value=0,width=21,fg_bg=text_fg} + + rotation.register(ps, "steam", function () + local status, result = pcall(function () return util.turbine_rotation(db.units[u_id].turbine_data_tbl[t_id]) end) + if status then rotation.update(result) end + end) + + return tbn_page.nav_to +end diff --git a/pocket/ui/style.lua b/pocket/ui/style.lua index f91d7b8..ad4a231 100644 --- a/pocket/ui/style.lua +++ b/pocket/ui/style.lua @@ -57,6 +57,12 @@ states.emc_ind_s = { { color = cpair(colors.black, colors.green), symbol = "+" } } +states.tri_ind_s = { + { color = cpair(colors.black, colors.lightGray), symbol = "+" }, + { color = cpair(colors.black, colors.yellow), symbol = "\x1e" }, + { color = cpair(colors.black, colors.red), symbol = "-" } +} + states.red_ind_s = { { color = cpair(colors.black, colors.lightGray), symbol = "+" }, { color = cpair(colors.black, colors.red), symbol = "-" } diff --git a/scada-common/util.lua b/scada-common/util.lua index d29a85e..344ffd8 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -4,6 +4,8 @@ local cc_strings = require("cc.strings") +local const = require("scada-common.constants") + local math = math local string = string local table = table @@ -368,7 +370,7 @@ end --#endregion ---#region MEKANISM POWER +--#region MEKANISM MATH -- convert Joules to FE ---@nodiscard @@ -434,6 +436,22 @@ function util.power_format(fe, combine_label, format) end end +-- compute Mekanism's rotation rate for a turbine +---@nodiscard +---@param turbine turbinev_session_db turbine data +function util.turbine_rotation(turbine) + local build = turbine.build + + local inner_vol = build.steam_cap / const.mek.TURBINE_GAS_PER_TANK + local disp_rate = (build.dispersers * const.mek.TURBINE_DISPERSER_FLOW) * inner_vol + local vent_rate = build.vents * const.mek.TURBINE_VENT_FLOW + + local max_rate = math.min(disp_rate, vent_rate) + local flow = math.min(max_rate, turbine.tanks.steam.amount) + + return (flow * (turbine.tanks.steam.amount / build.steam_cap)) / max_rate +end + --#endregion --#region UTILITY CLASSES diff --git a/supervisor/startup.lua b/supervisor/startup.lua index f83e973..89c38d8 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.3.11" +local SUPERVISOR_VERSION = "v1.3.12" local println = util.println local println_ts = util.println_ts diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index 3fe2ebc..20b00ff 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -39,21 +39,6 @@ local ALARM_LIMS = const.ALARM_LIMITS ---@class unit_logic_extension local logic = {} --- compute Mekanism's rotation rate for a turbine ----@param turbine turbinev_session_db -local function turbine_rotation(turbine) - local build = turbine.build - - local inner_vol = build.steam_cap / const.mek.TURBINE_GAS_PER_TANK - local disp_rate = (build.dispersers * const.mek.TURBINE_DISPERSER_FLOW) * inner_vol - local vent_rate = build.vents * const.mek.TURBINE_VENT_FLOW - - local max_rate = math.min(disp_rate, vent_rate) - local flow = math.min(max_rate, turbine.tanks.steam.amount) - - return (flow * (turbine.tanks.steam.amount / build.steam_cap)) / max_rate -end - -- update the annunciator ---@param self _unit_self function logic.update_annunciator(self) @@ -333,7 +318,7 @@ function logic.update_annunciator(self) local last = self.turbine_stability_data[i] if (not self.turbine_flow_stable) and (turbine.state.steam_input_rate > 0) then - local rotation = turbine_rotation(turbine) + local rotation = util.turbine_rotation(turbine) local rotation_stable = false -- see if data updated, and if so, check rotation speed change From c1c49ea3fbe736ad0d0dfd7709b2ebdf3953d55f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 2 Jun 2024 16:06:32 -0400 Subject: [PATCH 38/87] #200 unit app updates --- pocket/iocontrol.lua | 19 ++++++++++++++-- pocket/ui/pages/unit_reactor.lua | 38 ++++++++++++++++---------------- pocket/ui/style.lua | 4 ++-- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index c3ecc35..f46532c 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -693,24 +693,38 @@ function iocontrol.record_unit_data(data) local control_status = 1 local reactor_status = 1 + local reactor_state = 1 local rps_status = 1 if unit.connected then -- update RPS status if unit.reactor_data.rps_tripped then control_status = 2 - rps_status = util.trinary(unit.reactor_data.rps_trip_cause == "manual", 3, 2) + + if unit.reactor_data.rps_trip_cause == "manual" then + reactor_state = 4 -- disabled + rps_status = 3 + else + reactor_state = 6 -- SCRAM + rps_status = 2 + end else rps_status = 4 end -- update reactor/control status if unit.reactor_data.mek_status.status then reactor_status = 4 + reactor_state = 5 -- running control_status = util.trinary(unit.annunciator.AutoControl, 4, 3) else if unit.reactor_data.no_reactor then reactor_status = 2 - elseif not unit.reactor_data.formed or unit.reactor_data.rps_status.force_dis then + reactor_state = 3 -- faulted + elseif not unit.reactor_data.formed then reactor_status = 3 + reactor_state = 2 -- not formed + elseif unit.reactor_data.rps_status.force_dis then + reactor_status = 3 + reactor_state = 7 -- force disabled else reactor_status = 4 end @@ -737,6 +751,7 @@ function iocontrol.record_unit_data(data) unit.unit_ps.publish("U_ControlStatus", control_status) unit.unit_ps.publish("U_ReactorStatus", reactor_status) + unit.unit_ps.publish("U_ReactorStateStatus", reactor_state) unit.unit_ps.publish("U_RPS", rps_status) --#endregion diff --git a/pocket/ui/pages/unit_reactor.lua b/pocket/ui/pages/unit_reactor.lua index a6fa011..6990e92 100644 --- a/pocket/ui/pages/unit_reactor.lua +++ b/pocket/ui/pages/unit_reactor.lua @@ -1,20 +1,21 @@ -local types = require("scada-common.types") -local util = require("scada-common.util") +local types = require("scada-common.types") +local util = require("scada-common.util") -local iocontrol = require("pocket.iocontrol") +local iocontrol = require("pocket.iocontrol") -local style = require("pocket.ui.style") +local style = require("pocket.ui.style") -local core = require("graphics.core") +local core = require("graphics.core") -local Div = require("graphics.elements.div") -local TextBox = require("graphics.elements.textbox") +local Div = require("graphics.elements.div") +local TextBox = require("graphics.elements.textbox") -local DataIndicator = require("graphics.elements.indicators.data") -local IconIndicator = require("graphics.elements.indicators.icon") -local VerticalBar = require("graphics.elements.indicators.vbar") +local DataIndicator = require("graphics.elements.indicators.data") +local StateIndicator = require("graphics.elements.indicators.state") +local IconIndicator = require("graphics.elements.indicators.icon") +local VerticalBar = require("graphics.elements.indicators.vbar") -local PushButton = require("graphics.elements.controls.push_button") +local PushButton = require("graphics.elements.controls.push_button") local ALIGN = core.ALIGN local cpair = core.cpair @@ -43,7 +44,9 @@ return function (app, u_page, panes, page_div, u_ps, update) local rct_page = app.new_page(u_page, #panes) rct_page.tasks = { update } - TextBox{parent=rct_div,y=1,text="Fission Reactor",height=1,alignment=ALIGN.CENTER} + TextBox{parent=rct_div,y=1,text="Reactor",width=8,height=1} + local status = StateIndicator{parent=rct_div,x=10,y=1,states=style.reactor.states,value=1,min_width=12} + status.register(u_ps, "U_ReactorStateStatus", status.update) local fuel = VerticalBar{parent=rct_div,x=1,y=4,fg_bg=cpair(colors.lightGray,colors.gray),height=5,width=1} local ccool = VerticalBar{parent=rct_div,x=3,y=4,fg_bg=cpair(colors.blue,colors.gray),height=5,width=1} @@ -76,17 +79,14 @@ return function (app, u_page, panes, page_div, u_ps, update) end end) - TextBox{parent=rct_div,text="Burn Rate",x=5,y=5,width=13,height=1,fg_bg=label} - local burn_rate = DataIndicator{parent=rct_div,x=5,y=6,lu_colors=lu_col,label="",unit="mB/t",format="%8.2f",value=0,commas=true,width=13,fg_bg=text_fg} - TextBox{parent=rct_div,text="Temperature",x=5,y=7,width=13,height=1,fg_bg=label} + TextBox{parent=rct_div,text="Burn Rate",x=5,y=4,width=13,height=1,fg_bg=label} + local burn_rate = DataIndicator{parent=rct_div,x=5,y=5,lu_colors=lu_col,label="",unit="mB/t",format="%8.2f",value=0,commas=true,width=13,fg_bg=text_fg} + TextBox{parent=rct_div,text="Temperature",x=5,y=6,width=13,height=1,fg_bg=label} local t_prec = util.trinary(db.temp_label == types.TEMP_SCALE_UNITS[types.TEMP_SCALE.KELVIN], 11, 10) - local core_temp = DataIndicator{parent=rct_div,x=5,y=8,lu_colors=lu_col,label="",unit=db.temp_label,format="%"..t_prec..".2f",value=0,commas=true,width=13,fg_bg=text_fg} - - local state = IconIndicator{parent=rct_div,x=7,y=3,label="State",states=mode_states} + local core_temp = DataIndicator{parent=rct_div,x=5,y=7,lu_colors=lu_col,label="",unit=db.temp_label,format="%"..t_prec..".2f",value=0,commas=true,width=13,fg_bg=text_fg} burn_rate.register(u_ps, "act_burn_rate", burn_rate.update) core_temp.register(u_ps, "temp", function (t) core_temp.update(db.temp_convert(t)) end) - state.register(u_ps, "U_ControlStatus", state.update) local r_temp = IconIndicator{parent=rct_div,y=10,label="Reactor Temp. Hi",states=red_ind_s} local r_rhdt = IconIndicator{parent=rct_div,label="Hi Delta Temp.",states=yel_ind_s} diff --git a/pocket/ui/style.lua b/pocket/ui/style.lua index ad4a231..dc26755 100644 --- a/pocket/ui/style.lua +++ b/pocket/ui/style.lua @@ -82,7 +82,7 @@ style.reactor = { states = { { color = cpair(colors.black, colors.yellow), - text = "PLC OFF-LINE" + text = "OFF-LINE" }, { color = cpair(colors.black, colors.orange), @@ -106,7 +106,7 @@ style.reactor = { }, { color = cpair(colors.black, colors.red), - text = "FORCE DISABLED" + text = "FORCE DSBL" } } } From db901129f9b8821c1cff6a3178d0f91e96064232 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 2 Jun 2024 22:00:42 -0400 Subject: [PATCH 39/87] #200 status display updates --- pocket/iocontrol.lua | 64 +++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 40 deletions(-) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index f46532c..e65ea03 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -837,49 +837,41 @@ function iocontrol.record_unit_data(data) local ecam = {} -- aviation reference :) back to VATSIM I go... - if tripped(unit.alarms[ALARM.ContainmentBreach]) then - local items = { - { text = "REACTOR MELTDOWN", color = colors.white }, - { text = "WEAR HAZMAT SUIT", color = colors.blue } - } + local function red(text) return { text = text, color = colors.red } end + local function white(text) return { text = text, color = colors.white } end + local function blue(text) return { text = text, color = colors.blue } end + if tripped(unit.alarms[ALARM.ContainmentBreach]) then + local items = { white("REACTOR MELTDOWN"), blue("DON HAZMAT SUIT") } table.insert(ecam, { color = colors.red, text = "CONTAINMENT BREACH", help = "ContainmentBreach", items = items }) end if tripped(unit.alarms[ALARM.ContainmentRadiation]) then local items = { - { text = "RADIATION DETECTED", color = colors.white }, - { text = "WEAR HAZMAT SUIT", color = colors.blue }, - { text = "RESOLVE LEAK", color = colors.blue }, - { text = "AWAIT SAFE LEVELS", color = colors.white } + white("RADIATION DETECTED"), + blue("DON HAZMAT SUIT"), + blue("RESOLVE LEAK"), + blue("AWAIT SAFE LEVELS") } table.insert(ecam, { color = colors.red, text = "RADIATION LEAK", help = "ContainmentRadiation", items = items }) end if tripped(unit.alarms[ALARM.CriticalDamage]) then - local items = { - { text = "MELTDOWN IMMINENT", color = colors.white }, - { text = "CHECK PLC", color = colors.blue } - } - + local items = { white("MELTDOWN IMMINENT"), blue("EVACUATE") } table.insert(ecam, { color = colors.red, text = "RCT DAMAGE CRITICAL", help = "CriticalDamage", items = items }) end if tripped(unit.alarms[ALARM.ReactorLost]) then - local items = { - { text = "REACTOR OFF-LINE", color = colors.white }, - { text = "CHECK PLC", color = colors.blue } - } - + local items = { white("REACTOR OFF-LINE"), blue("CHECK PLC") } table.insert(ecam, { color = colors.red, text = "REACTOR CONN LOST", help = "ReactorLost", items = items }) end if tripped(unit.alarms[ALARM.ReactorDamage]) then local items = { - { text = "REACTOR DAMAGED", color = colors.white }, - { text = "CHECK RCS", color = colors.blue }, - { text = "AWAIT DMG REDUCED", color = colors.blue } + white("REACTOR DAMAGED"), + blue("CHECK RCS"), + blue("AWAIT DMG REDUCED") } table.insert(ecam, { color = colors.red, text = "REACTOR DAMAGE", help = "ReactorDamage", items = items }) @@ -887,34 +879,27 @@ function iocontrol.record_unit_data(data) if tripped(unit.alarms[ALARM.ReactorOverTemp]) then local items = { - { text = "AOA DAMAGE TEMP", color = colors.white }, - { text = "CHECK RCS", color = colors.blue }, - { text = "AWAIT COOLDOWN", color = colors.blue } + white("HIT DAMAGING TEMP"), + blue("CHECK RCS"), + blue("AWAIT COOLDOWN") } table.insert(ecam, { color = colors.red, text = "REACTOR OVER TEMP", help = "ReactorOverTemp", items = items }) end if tripped(unit.alarms[ALARM.ReactorHighTemp]) then - local items = { - { text = "OVER EXPECTED TEMP", color = colors.white }, - { text = "CHECK RCS", color = colors.blue } - } - + local items = { white("OVER EXPECTED TEMP"), blue("CHECK RCS") } table.insert(ecam, { color = colors.yellow, text = "REACTOR HIGH TEMP", help = "ReactorHighTemp", items = items}) end if tripped(unit.alarms[ALARM.ReactorWasteLeak]) then - local items = { - { text = "CHECK WASTE OUTPUT", color = colors.blue }, - { text = "DO NOT ENABLE RCT" } - } + local items = { white("AT WASTE CAPACITY"), blue("CHECK WASTE OUTPUT"), red("DO NOT ENABLE RCT") } table.insert(ecam, { color = colors.red, text = "REACTOR WASTE LEAK", help = "ReactorWasteLeak", items = items}) end if tripped(unit.alarms[ALARM.ReactorHighWaste]) then - local items = {{ text = "CHECK WASTE OUTPUT", color = colors.white }} + local items = { blue("CHECK WASTE OUTPUT") } table.insert(ecam, { color = colors.yellow, text = "REACTOR WASTE HIGH", help = "ReactorHighWaste", items = items}) end @@ -991,26 +976,25 @@ function iocontrol.record_unit_data(data) if v then table.insert(items, { text = "TURBINE " .. k .. " TRIP", help = "TurbineTrip" }) end end - table.insert(items, { text = "CHECK ENERGY OUT", color = colors.blue }) - + table.insert(items, blue("CHECK ENERGY OUT")) table.insert(ecam, { color = colors.red, text = "TURBINE TRIP", help = "TurbineTripAlarm", items = items}) end if not (tripped(unit.alarms[ALARM.ReactorLost]) or unit.connected) then - local items = {{ text = "CHECK PLC", color = colors.blue }} + local items = { blue("CHECK PLC") } table.insert(ecam, { color = colors.yellow, text = "REACTOR OFF-LINE", items = items }) end for k, v in ipairs(unit.annunciator.BoilerOnline) do if not v then - local items = {{ text = "CHECK RTU", color = colors.blue }} + local items = { blue("CHECK RTU") } table.insert(ecam, { color = colors.yellow, text = "BOILER " .. k .. " OFF-LINE", items = items}) end end for k, v in ipairs(unit.annunciator.TurbineOnline) do if not v then - local items = {{ text = "CHECK RTU", color = colors.blue }} + local items = { blue("CHECK RTU") } table.insert(ecam, { color = colors.yellow, text = "TURBINE " .. k .. " OFF-LINE", items = items}) end end From a2af0d382936c157fdd2f448cd98cac31c9e0421 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 4 Jun 2024 00:21:54 +0000 Subject: [PATCH 40/87] #403 work on pocket guide app --- pocket/iocontrol.lua | 9 ++-- pocket/ui/apps/guide.lua | 108 +++++++++++++++++++++++++++++++++++++++ pocket/ui/docs/docs.lua | 55 ++++++++++++++++++++ 3 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 pocket/ui/apps/guide.lua create mode 100644 pocket/ui/docs/docs.lua diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index c74c6a7..223998a 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -34,12 +34,13 @@ local APP_ID = { ROOT = 1, -- main app page UNITS = 2, - ABOUT = 3, + GUIDE = 3, + ABOUT = 4, -- diag app page - ALARMS = 4, + ALARMS = 5, -- other - DUMMY = 5, - NUM_APPS = 5 + DUMMY = 6, + NUM_APPS = 6 } iocontrol.APP_ID = APP_ID diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua new file mode 100644 index 0000000..1ad1b3b --- /dev/null +++ b/pocket/ui/apps/guide.lua @@ -0,0 +1,108 @@ +-- +-- System Guide +-- + +-- local util = require("scada-common.util") +-- local log = require("scada-common.log") + +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 ALIGN = core.ALIGN +local cpair = core.cpair + +-- new system guide view +---@param root graphics_element parent +local function new_view(root) + local db = iocontrol.get_db() + + local main = Div{parent=root,x=1,y=1} + + local app = db.nav.register_app(iocontrol.APP_ID.GUIDE, main) + + TextBox{parent=main,y=2,text="Guide",height=1,alignment=ALIGN.CENTER} + TextBox{parent=main,y=4,text="Loading...",height=1,alignment=ALIGN.CENTER} + + local btn_fg_bg = cpair(colors.cyan, colors.black) + local btn_active = cpair(colors.white, colors.black) + + local list = { + { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(iocontrol.APP_ID.ROOT) end }, + { label = "Use", tall = true, color = core.cpair(colors.black, colors.purple), callback = function () app.switcher(1) end }, + { label = "UIs", tall = true, color = core.cpair(colors.black, colors.blue), callback = function () app.switcher(2) end }, + { label = "FPs", tall = true, color = core.cpair(colors.black, colors.lightGray), callback = function () app.switcher(3) end } + } + + app.set_sidebar(list) + + local function load() + local page_div = Div{parent=main,y=2} + local p_width = page_div.get_width() - 2 + local sub_panes = {} + + local main_page = app.new_page(nil, 1) + local use_page = app.new_page(main_page, 2) + local uis_page = app.new_page(main_page, 3) + local fps_page = app.new_page(main_page, 4) + + local home = Div{parent=page_div,x=2,width=p_width} + + TextBox{parent=home,y=1,text="cc-mek-scada Guide",height=1,alignment=ALIGN.CENTER} + + PushButton{parent=home,y=3,text="System Usage >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=use_page.nav_to} + PushButton{parent=home,text="Operator UIs >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=uis_page.nav_to} + PushButton{parent=home,text="Front Panels >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fps_page.nav_to} + + local use = Div{parent=page_div,x=2,width=p_width} + + TextBox{parent=use,y=1,text="System Usage",height=1,alignment=ALIGN.CENTER} + + PushButton{parent=use,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} + + PushButton{parent=use,y=3,text="Configuring Devices >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + PushButton{parent=use,text="Connecting Devices >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + PushButton{parent=use,text="Manual Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + PushButton{parent=use,text="Automatic Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + PushButton{parent=use,text="Waste Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + + local uis = Div{parent=page_div,x=2,width=p_width} + + TextBox{parent=uis,y=1,text="Operator UIs",height=1,alignment=ALIGN.CENTER} + + PushButton{parent=uis,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} + + PushButton{parent=uis,y=3,text="Annunciators >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + PushButton{parent=uis,text="Pocket UI >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + PushButton{parent=uis,text="Coordinator UI >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + + local fps = Div{parent=page_div,x=2,width=p_width} + + TextBox{parent=fps,y=1,text="Front Panels",height=1,alignment=ALIGN.CENTER} + + PushButton{parent=fps,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} + + PushButton{parent=fps,y=3,text="Common Items >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + PushButton{parent=fps,text="Reactor PLC >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + PushButton{parent=fps,text="RTU Gateway >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + PushButton{parent=fps,text="Supervisor >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + PushButton{parent=fps,text="Coordinator >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + + -- setup multipane + local u_pane = MultiPane{parent=page_div,x=1,y=1,panes={home,use,uis,fps,table.unpack(sub_panes)}} + app.set_root_pane(u_pane) + end + + app.set_on_load(load) + + return main +end + +return new_view diff --git a/pocket/ui/docs/docs.lua b/pocket/ui/docs/docs.lua new file mode 100644 index 0000000..b51c1ef --- /dev/null +++ b/pocket/ui/docs/docs.lua @@ -0,0 +1,55 @@ +local docs = {} + +local target + +local function doc(key, name, desc) + table.insert(target, { key = key, name = name, desc = desc }) +end + +docs.alarms = {} + +target = docs.alarms +doc("ContainmentBreach", "Containment Breach", "Reactor disconnected or indicated unformed while being at or above 100% damage; explosion likely occurred.") +doc("ContainmentRadiation", "Containment Radiation", "Environment detector(s) assigned to the unit have observed high levels of radiation.") +doc("ReactorLost", "Reactor Lost", "Reactor PLC has stopped communicating with the supervisor.") +doc("CriticalDamage", "Damage Critical", "Reactor damage has reached or exceeded 100%, so it may explode at any moment.") +doc("ReactorDamage", "Reactor Damage", "Reactor temperature causing increasing damage to reactor casing.") +doc("ReactorOverTemp", "Reactor Over Temp", "Reactor temperature is at or above maximum safe temperature and is now taking damage.") +doc("ReactorHighTemp", "Reactor High Temp", "Reactor temperature is above expected operating levels and may exceed maximum safe temperature soon.") +doc("ReactorWasteLeak", "Reactor Waste Leak", "The reactor is full of spent waste and will now emit radiation if additional waste is generated.") +doc("ReactorHighWaste", "Reactor High Waste", "Reactor waste levels are high and may leak soon.") +doc("RPSTransient", "RPS Transient", "Reactor protection system was activated.") +doc("RCSTransient", "RCS Transient", "Something is wrong with the reactor coolant system, check RCS indicators.") +doc("TurbineTripAlarm", "Turbine Trip", "A turbine stopped rotating, likely due to having full energy storage.") + +docs.annunc = { + unit = { + main_section = {}, + rps_section = {}, + rcs_section = {} + } +} + +target = docs.annunc.unit.main_section +doc("PLCOnline", "PLC Online", "Indicates if the fission reactor PLC is connected.") +doc("PLCHeartbeat", "PLC Heartbeat", "An indicator of status data being live. As status messages are received from the PLC, this light will turn on and off. If it gets stuck, the supervisor has stopped receiving data.") +doc("RadiationMonitor", "Radiation Monitor", "Indicates if at least once environment detector is connected and assigned to this unit.") +doc("AutoControl", "Automatic Control", "Indicates if the reactor is under the control of one of the automatic control modes.") +doc("ReactorSCRAM", "Reactor SCRAM", "Indicates if the reactor protection system is holding the reactor SCRAM'd.") +doc("ManualReactorSCRAM", "Manual Reactor SCRAM", "Indicates if the operator (you) initiated a SCRAM.") +doc("AutoReactorSCRAM", "Auto Reactor SCRAM", "Indicates if the automatic control system initiated a SCRAM. The main view screen will have an indicator as to why.") +doc("RadiationWarning", "Radiation Warning", "Indicates if radiation levels are above normal. There is likely a leak somewhere, so that should be identified and fixed.") +doc("RCPTrip", "RCP Trip", "Reactor coolant pump tripped. This is a technical concept not directly mapping to mekansim, so in this case it indicates if there is either high heated coolant or low cooled coolant causing an RPS trip. Check the coolant system if this occurs.") +doc("RCSFlowLow", "RCS Flow Low", "Indicates if the reactor coolant system flow is low. This is observed when the cooled coolant level in the reactor is dropping. This can occur while a turbine spins up, but if it persists, check that the cooling system is operating properly.") +doc("CoolantLevelLow", "Coolant Level Low", "Indicates if the reactor coolant level is lower than it should be. Check the coolant system.") +doc("ReactorTempHigh", "Reactor Temp. High", "Indicates if the reactor temperature is above expected maximum operating temperature. This is not yet damaging, but should be attended to. Check coolant system.") +doc("ReactorHighDeltaT", "Reactor High Delta T", "Indicates if the reactor temperature is climbing rapidly. This can occur when a reactor is starting up, but it is a concern if it happens uncontrolled while the burn rate is not increasing.") +doc("FuelInputRateLow", "Fuel Input Rate Low", "Indicates if the fissile fuel levels in the reactor are dropping or are very low. Ensure a steady supply of fuel is entering the reactor.") +doc("WasteLineOcclusion", "Waste Line Occlusion", "Waste levels in the reactor are increasing. Ensure your waste processing system is operating at a sufficient rate for your burn rate.") +doc("HighStartupRate", "Startup Rate High", "This is a rough calculation of if your burn rate is high enough to cause a loss of coolant. A burn rate above this is likely to cause that, but it could occur at even higher or even lower rates depending on your setup (such as pipes, water supplies, and boiler tanks).") + +target = docs.annunc.unit.rps_section +doc("rps_tripped", "RPS Trip", "Indicates if the reactor protection system has caused a SCRAM.") +doc("high_dmg", "Damage Level High", "Indicates if the RPS tripped due to significant reactor damage.") + +return docs From 4d87887709299714eb6d7439488a5f88350a5951 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 3 Jun 2024 20:52:59 -0400 Subject: [PATCH 41/87] #403 pocket guide fixes --- pocket/ui/apps/guide.lua | 19 ++++++++----------- pocket/ui/main.lua | 8 ++++++-- pocket/ui/pages/home_page.lua | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index 1ad1b3b..9fd9628 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -36,9 +36,9 @@ local function new_view(root) local list = { { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(iocontrol.APP_ID.ROOT) end }, - { label = "Use", tall = true, color = core.cpair(colors.black, colors.purple), callback = function () app.switcher(1) end }, - { label = "UIs", tall = true, color = core.cpair(colors.black, colors.blue), callback = function () app.switcher(2) end }, - { label = "FPs", tall = true, color = core.cpair(colors.black, colors.lightGray), callback = function () app.switcher(3) end } + { label = "Use", color = core.cpair(colors.black, colors.purple), callback = function () app.switcher(2) end }, + { label = "UIs", color = core.cpair(colors.black, colors.blue), callback = function () app.switcher(3) end }, + { label = "FPs", color = core.cpair(colors.black, colors.lightGray), callback = function () app.switcher(4) end } } app.set_sidebar(list) @@ -46,7 +46,6 @@ local function new_view(root) local function load() local page_div = Div{parent=main,y=2} local p_width = page_div.get_width() - 2 - local sub_panes = {} local main_page = app.new_page(nil, 1) local use_page = app.new_page(main_page, 2) @@ -54,6 +53,10 @@ local function new_view(root) local fps_page = app.new_page(main_page, 4) local home = Div{parent=page_div,x=2,width=p_width} + local use = Div{parent=page_div,x=2,width=p_width} + local uis = Div{parent=page_div,x=2,width=p_width} + local fps = Div{parent=page_div,x=2,width=p_width} + local panes = { home, use, uis, fps } TextBox{parent=home,y=1,text="cc-mek-scada Guide",height=1,alignment=ALIGN.CENTER} @@ -61,8 +64,6 @@ local function new_view(root) PushButton{parent=home,text="Operator UIs >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=uis_page.nav_to} PushButton{parent=home,text="Front Panels >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fps_page.nav_to} - local use = Div{parent=page_div,x=2,width=p_width} - TextBox{parent=use,y=1,text="System Usage",height=1,alignment=ALIGN.CENTER} PushButton{parent=use,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} @@ -73,8 +74,6 @@ local function new_view(root) PushButton{parent=use,text="Automatic Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} PushButton{parent=use,text="Waste Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} - local uis = Div{parent=page_div,x=2,width=p_width} - TextBox{parent=uis,y=1,text="Operator UIs",height=1,alignment=ALIGN.CENTER} PushButton{parent=uis,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} @@ -83,8 +82,6 @@ local function new_view(root) PushButton{parent=uis,text="Pocket UI >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} PushButton{parent=uis,text="Coordinator UI >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} - local fps = Div{parent=page_div,x=2,width=p_width} - TextBox{parent=fps,y=1,text="Front Panels",height=1,alignment=ALIGN.CENTER} PushButton{parent=fps,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} @@ -96,7 +93,7 @@ local function new_view(root) PushButton{parent=fps,text="Coordinator >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} -- setup multipane - local u_pane = MultiPane{parent=page_div,x=1,y=1,panes={home,use,uis,fps,table.unpack(sub_panes)}} + local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes} app.set_root_pane(u_pane) end diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index eaecc1e..9f33e1f 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -2,10 +2,13 @@ -- Pocket GUI Root -- +local util = require("scada-common.util") + local iocontrol = require("pocket.iocontrol") local diag_apps = require("pocket.ui.apps.diag_apps") local dummy_app = require("pocket.ui.apps.dummy_app") +local guide_app = require("pocket.ui.apps.guide") local sys_apps = require("pocket.ui.apps.sys_apps") local unit_app = require("pocket.ui.apps.unit") @@ -74,11 +77,12 @@ local function init(main) home_page(page_div) unit_app(page_div) - diag_apps(page_div) + guide_app(page_div) sys_apps(page_div) + diag_apps(page_div) dummy_app(page_div) - assert(#db.nav.get_containers() == iocontrol.APP_ID.NUM_APPS, "app IDs were not sequential or some apps weren't registered") + assert(util.table_len(db.nav.get_containers()) == iocontrol.APP_ID.NUM_APPS, "app IDs were not sequential or some apps weren't registered") db.nav.set_pane(MultiPane{parent=page_div,x=1,y=1,panes=db.nav.get_containers()}) db.nav.set_sidebar(Sidebar{parent=main_pane,x=1,y=1,height=18,fg_bg=cpair(colors.white,colors.gray)}) diff --git a/pocket/ui/pages/home_page.lua b/pocket/ui/pages/home_page.lua index 483a881..8854614 100644 --- a/pocket/ui/pages/home_page.lua +++ b/pocket/ui/pages/home_page.lua @@ -51,7 +51,7 @@ local function new_view(root) App{parent=apps_1,x=2,y=7,text="\x17",title="Process",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=9,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=16,y=7,text="\x08",title="Devices",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=2,y=12,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=2,y=12,text="\xb6",title="Guide",callback=function()open(APP_ID.GUIDE)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg} App{parent=apps_1,x=9,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} From 25ebf2c8c73105018ece879c2976ff36c0afa029 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 5 Jun 2024 00:31:06 +0000 Subject: [PATCH 42/87] graphics automatic constraints --- graphics/core.lua | 2 +- graphics/element.lua | 10 +++++++++- graphics/elements/rectangle.lua | 2 +- graphics/elements/textbox.lua | 14 ++++++++++++-- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/graphics/core.lua b/graphics/core.lua index 830ca69..f6a647a 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -7,7 +7,7 @@ local flasher = require("graphics.flasher") local core = {} -core.version = "2.2.4" +core.version = "2.3.0" core.flasher = flasher core.events = events diff --git a/graphics/element.lua b/graphics/element.lua index 731a22c..6f859ab 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -82,9 +82,10 @@ end -- a base graphics element, should not be created on its own ---@nodiscard ---@param args graphics_args arguments +---@param constraint? function apply a dimensional constraint based on proposed dimensions function(frame) -> width, height ---@param child_offset_x? integer mouse event offset x ---@param child_offset_y? integer mouse event offset y -function element.new(args, child_offset_x, child_offset_y) +function element.new(args, constraint, child_offset_x, child_offset_y) local self = { id = nil, ---@type element_id|nil is_root = args.parent == nil, @@ -226,6 +227,13 @@ function element.new(args, child_offset_x, child_offset_y) local w, h = self.p_window.getSize() f.w = math.min(f.w, w - (f.x - 1)) f.h = math.min(f.h, h - (f.y - 1)) + + if type(constraint) == "function" then + -- constrain per provided constraint function (can only get smaller than available space) + w, h = constraint(w, h) + f.w = math.min(f.w, w) + f.h = math.min(f.h, h) + end end -- check frame diff --git a/graphics/elements/rectangle.lua b/graphics/elements/rectangle.lua index 252790b..eceb9bd 100644 --- a/graphics/elements/rectangle.lua +++ b/graphics/elements/rectangle.lua @@ -45,7 +45,7 @@ local function rectangle(args) end -- create new graphics element base object - local e = element.new(args, offset_x, offset_y) + local e = element.new(args, nil, offset_x, offset_y) -- 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)) diff --git a/graphics/elements/textbox.lua b/graphics/elements/textbox.lua index 07a8736..5a02579 100644 --- a/graphics/elements/textbox.lua +++ b/graphics/elements/textbox.lua @@ -10,12 +10,13 @@ local ALIGN = core.ALIGN ---@class textbox_args ---@field text string text to show ---@field alignment? ALIGN text alignment, left by default +---@field anchor? boolean true to use this as an anchor, making it focusable ---@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 height? integer minimum necessary height for wrapped text 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 @@ -26,8 +27,17 @@ local ALIGN = core.ALIGN local function textbox(args) element.assert(type(args.text) == "string", "text is a required field") + if args.anchor == true then args.can_focus = true end + + -- regex to identify entries without a height currently: ^.*TextBox\{((?!height=).)*$ + -- provide a constraint condition to element creation to prevent an pointlessly tall text box + ---@param frame graphics_frame + local function constrain(frame) + return frame.w, math.max(args.height, math.max(1, #util.strwrap(args.text, frame.w))) + end + -- create new graphics element base object - local e = element.new(args) + local e = element.new(args, constrain) e.value = args.text From b9030d6bed192bb8f8a43831e2e20c8532840b2f Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 5 Jun 2024 00:31:29 +0000 Subject: [PATCH 43/87] #403 work on guide app --- pocket/ui/apps/guide.lua | 40 ++++++++++++++++++++++++++++++++--- pocket/ui/{docs => }/docs.lua | 15 ++++++++++++- 2 files changed, 51 insertions(+), 4 deletions(-) rename pocket/ui/{docs => }/docs.lua (89%) diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index 9fd9628..a6edc87 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -7,10 +7,13 @@ local iocontrol = require("pocket.iocontrol") +local docs = require("pocket.ui.docs") +local style = require("pocket.ui.style") + local core = require("graphics.core") local Div = require("graphics.elements.div") --- local ListBox = require("graphics.elements.listbox") +local ListBox = require("graphics.elements.listbox") local MultiPane = require("graphics.elements.multipane") local TextBox = require("graphics.elements.textbox") @@ -19,6 +22,10 @@ local PushButton = require("graphics.elements.controls.push_button") local ALIGN = core.ALIGN local cpair = core.cpair +local label = style.label +-- local lu_col = style.label_unit_pair +local text_fg = style.text_fg + -- new system guide view ---@param root graphics_element parent local function new_view(root) @@ -51,18 +58,23 @@ local function new_view(root) local use_page = app.new_page(main_page, 2) local uis_page = app.new_page(main_page, 3) local fps_page = app.new_page(main_page, 4) + local gls_page = app.new_page(main_page, 5) local home = Div{parent=page_div,x=2,width=p_width} local use = Div{parent=page_div,x=2,width=p_width} local uis = Div{parent=page_div,x=2,width=p_width} local fps = Div{parent=page_div,x=2,width=p_width} - local panes = { home, use, uis, fps } + local gls = Div{parent=page_div,x=2} + local panes = { home, use, uis, fps, gls } + + local doc_map = {} TextBox{parent=home,y=1,text="cc-mek-scada Guide",height=1,alignment=ALIGN.CENTER} PushButton{parent=home,y=3,text="System Usage >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=use_page.nav_to} PushButton{parent=home,text="Operator UIs >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=uis_page.nav_to} PushButton{parent=home,text="Front Panels >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fps_page.nav_to} + PushButton{parent=home,text="Glossary >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_page.nav_to} TextBox{parent=use,y=1,text="System Usage",height=1,alignment=ALIGN.CENTER} @@ -78,7 +90,16 @@ local function new_view(root) PushButton{parent=uis,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} - PushButton{parent=uis,y=3,text="Annunciators >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + local alarms_page = app.new_page(uis_page, #panes + 1) + local alarms_div = Div{parent=page_div,x=2} + table.insert(panes, alarms_div) + + local annunc_page = app.new_page(uis_page, #panes + 1) + local annunc_div = Div{parent=page_div,x=2} + table.insert(panes, annunc_div) + + PushButton{parent=uis,y=3,text="Alarms >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=alarms_page.nav_to} + PushButton{parent=uis,text="Annunciators >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=annunc_page.nav_to} PushButton{parent=uis,text="Pocket UI >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} PushButton{parent=uis,text="Coordinator UI >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} @@ -92,6 +113,19 @@ local function new_view(root) PushButton{parent=fps,text="Supervisor >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} PushButton{parent=fps,text="Coordinator >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + TextBox{parent=gls,y=1,text="Glossary",height=1,alignment=ALIGN.CENTER} + + PushButton{parent=gls,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} + + local gls_list_box = ListBox{parent=gls,x=2,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + + for i = 1, #docs.glossary do + local item = docs.glossary[i] ---@type pocket_doc_item + doc_map[item.key] = TextBox{parent=gls_list_box,text=item.name,anchor=true} + TextBox{parent=gls_list_box,text=item.desc,fg_bg=label} + gls_list_box.line_break() + end + -- setup multipane local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes} app.set_root_pane(u_pane) diff --git a/pocket/ui/docs/docs.lua b/pocket/ui/docs.lua similarity index 89% rename from pocket/ui/docs/docs.lua rename to pocket/ui/docs.lua index b51c1ef..69b31d9 100644 --- a/pocket/ui/docs/docs.lua +++ b/pocket/ui/docs.lua @@ -3,7 +3,9 @@ local docs = {} local target local function doc(key, name, desc) - table.insert(target, { key = key, name = name, desc = desc }) + ---@class pocket_doc_item + local item = { key = key, name = name, desc = desc } + table.insert(target, item) end docs.alarms = {} @@ -52,4 +54,15 @@ target = docs.annunc.unit.rps_section doc("rps_tripped", "RPS Trip", "Indicates if the reactor protection system has caused a SCRAM.") doc("high_dmg", "Damage Level High", "Indicates if the RPS tripped due to significant reactor damage.") +docs.glossary = {} + +target = docs.glossary +doc("G_Nominal", "Nominal", "") +doc("G_RCS", "RCS", "Reactor Cooling System: the combination of all machines used to cool the reactor.") +doc("G_RPS", "RPS", "Reactor Protection System: a component of the reactor PLC responsible for keeping the reactor safe.") +doc("G_Transient", "Transient", "") +doc("G_Trip", "Trip", "A checked condition has occurred, also known as 'tripped'.") + +target = docs.annunc.unit.main_section + return docs From 58fb35e85b112b4c96e0611014f4dde680e9bddd Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 5 Jun 2024 00:31:47 +0000 Subject: [PATCH 44/87] keyboard and paste support for pocket --- pocket/renderer.lua | 16 ++++++++++++++++ pocket/startup.lua | 8 +++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/pocket/renderer.lua b/pocket/renderer.lua index 892fc92..bc16037 100644 --- a/pocket/renderer.lua +++ b/pocket/renderer.lua @@ -92,4 +92,20 @@ function renderer.handle_mouse(event) end end +-- handle a keyboard event +---@param event key_interaction|nil +function renderer.handle_key(event) + if ui.display ~= nil and event ~= nil then + ui.display.handle_key(event) + end +end + +-- handle a paste event +---@param text string +function renderer.handle_paste(text) + if ui.display ~= nil then + ui.display.handle_paste(text) + end +end + return renderer diff --git a/pocket/startup.lua b/pocket/startup.lua index c9f8c8d..335bdc1 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -175,8 +175,14 @@ local function main() pocket_comms.handle_packet(packet) elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then - -- handle a monitor touch event + -- handle a mouse event renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3)) + elseif event == "char" or event == "key" or event == "key_up" then + -- handle a keyboard event + renderer.handle_key(core.events.new_key_event(event, param1, param2)) + elseif event == "paste" then + -- handle a paste event + renderer.handle_paste(param1) end -- check for termination request From 9404b50a8c6a931e629d81a6b7e947335c99fba2 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 5 Jun 2024 22:07:38 +0000 Subject: [PATCH 45/87] #403 additional work on guide app --- pocket/ui/apps/guide.lua | 67 +++++++++++++++++++++++++++++++++++----- pocket/ui/docs.lua | 43 +++++++++++++++++++------- 2 files changed, 91 insertions(+), 19 deletions(-) diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index a6edc87..b4300fa 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -114,16 +114,69 @@ local function new_view(root) PushButton{parent=fps,text="Coordinator >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} TextBox{parent=gls,y=1,text="Glossary",height=1,alignment=ALIGN.CENTER} - PushButton{parent=gls,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} - local gls_list_box = ListBox{parent=gls,x=2,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + local gls_abb_page = app.new_page(gls_page, #panes + 1) + local gls_abb_div = Div{parent=page_div,x=2} + table.insert(panes, gls_abb_div) + TextBox{parent=gls_abb_div,y=1,text="Abbreviations",height=1,alignment=ALIGN.CENTER} + PushButton{parent=gls_abb_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_page.nav_to} - for i = 1, #docs.glossary do - local item = docs.glossary[i] ---@type pocket_doc_item - doc_map[item.key] = TextBox{parent=gls_list_box,text=item.name,anchor=true} - TextBox{parent=gls_list_box,text=item.desc,fg_bg=label} - gls_list_box.line_break() + local gls_abb_view_page = app.new_page(gls_abb_page, #panes + 1) + local gls_abb_view_div = Div{parent=page_div,x=2} + table.insert(panes, gls_abb_view_div) + TextBox{parent=gls_abb_view_div,y=1,text="Abbreviations",height=1,alignment=ALIGN.CENTER} + PushButton{parent=gls_abb_view_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_abb_page.nav_to} + + local gls_term_page = app.new_page(gls_page, #panes + 1) + local gls_term_div = Div{parent=page_div,x=2} + table.insert(panes, gls_term_div) + TextBox{parent=gls_term_div,y=1,text="Terminology",height=1,alignment=ALIGN.CENTER} + PushButton{parent=gls_term_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_page.nav_to} + + local gls_term_view_page = app.new_page(gls_term_page, #panes + 1) + local gls_term_view_div = Div{parent=page_div,x=2} + table.insert(panes, gls_term_view_div) + TextBox{parent=gls_term_view_div,y=1,text="Terminology",height=1,alignment=ALIGN.CENTER} + PushButton{parent=gls_term_view_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_term_page.nav_to} + + PushButton{parent=gls,y=3,text="Abbreviations >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_abb_page.nav_to} + PushButton{parent=gls,text="Terminology >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_term_page.nav_to} + + local abb_name_list = ListBox{parent=gls_abb_div,x=2,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + local abb_def_list = ListBox{parent=gls_abb_view_div,x=2,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + + for i = 1, #docs.glossary.abbvs do + local item = docs.glossary.abbvs[i] ---@type pocket_doc_item + + doc_map[item.key] = TextBox{parent=abb_def_list,text=item.name,anchor=true,cpair(colors.blue,colors.black)} + TextBox{parent=abb_def_list,text=item.desc,fg_bg=label} + TextBox{parent=abb_def_list,text="",fg_bg=label} + + local function view() + gls_abb_view_page.nav_to() + doc_map[item.key].focus() + end + + PushButton{parent=abb_name_list,text=item.name,fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=view} + end + + local term_name_list = ListBox{parent=gls_term_div,x=2,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + local term_def_list = ListBox{parent=gls_term_view_div,x=2,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + + for i = 1, #docs.glossary.terms do + local item = docs.glossary.terms[i] ---@type pocket_doc_item + + doc_map[item.key] = TextBox{parent=term_def_list,text=item.name,anchor=true,cpair(colors.blue,colors.black)} + TextBox{parent=term_def_list,text=item.desc,fg_bg=label} + TextBox{parent=term_def_list,text="",fg_bg=label} + + local function view() + gls_term_view_page.nav_to() + doc_map[item.key].focus() + end + + PushButton{parent=term_name_list,text=item.name,fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=view} end -- setup multipane diff --git a/pocket/ui/docs.lua b/pocket/ui/docs.lua index 69b31d9..d38d4ce 100644 --- a/pocket/ui/docs.lua +++ b/pocket/ui/docs.lua @@ -26,9 +26,7 @@ doc("TurbineTripAlarm", "Turbine Trip", "A turbine stopped rotating, likely due docs.annunc = { unit = { - main_section = {}, - rps_section = {}, - rcs_section = {} + main_section = {}, rps_section = {}, rcs_section = {} } } @@ -54,15 +52,36 @@ target = docs.annunc.unit.rps_section doc("rps_tripped", "RPS Trip", "Indicates if the reactor protection system has caused a SCRAM.") doc("high_dmg", "Damage Level High", "Indicates if the RPS tripped due to significant reactor damage.") -docs.glossary = {} - -target = docs.glossary -doc("G_Nominal", "Nominal", "") -doc("G_RCS", "RCS", "Reactor Cooling System: the combination of all machines used to cool the reactor.") -doc("G_RPS", "RPS", "Reactor Protection System: a component of the reactor PLC responsible for keeping the reactor safe.") -doc("G_Transient", "Transient", "") -doc("G_Trip", "Trip", "A checked condition has occurred, also known as 'tripped'.") - target = docs.annunc.unit.main_section +docs.glossary = { + abbvs = {}, terms = {} +} + +target = docs.glossary.abbvs +doc("G_ACK", "ACK", "Alarm ACKnowledge. This indicates you understand an alarm occured and would like to stop the audio tone(s).") +doc("G_CRD", "CRD", "Coordinator. Abbreviation for the coordinator computer.") +doc("G_DBG", "DBG", "Debug. Abbreviation for the debugging sessions from pocket computers found on the supervisor's front panel.") +doc("G_PKT", "PKT", "Pocket. Abbreviation for the pocket computer.") +doc("G_PLC", "PLC", "Programmable Logic Controller. A device that not only reports data and controls outputs, but also can make decisions on its own.") +doc("G_PPM", "PPM", "Protected Peripheral Manager. This is an abstraction layer created for this project that prevents peripheral calls from crashing applications.") +doc("G_RCP", "RCP", "Reactor Coolant Pump. This is from real-world terminology with water-cooled reactors, but in this system it just relates to the functioning of reactor coolant flow.") +doc("G_RCS", "RCS", "Reactor Cooling System. The combination of all machines used to cool the reactor.") +doc("G_RPS", "RPS", "Reactor Protection System. A component of the reactor PLC responsible for keeping the reactor safe.") +doc("G_RTU", "RTU", "Remote Terminal Unit. Provides monitoring to and basic output from a SCADA system, interfacing with various types of devices/controls.") +doc("G_SCADA", "SCADA", "Supervisory Control and Data Acquisition. A control systems architecture used in many different process control applications.") +doc("G_SVR", "SVR", "Supervisor. Abbreviation for the supervisory computer.") + +target = docs.glossary.term +doc("G_Fault", "Fault", "Something has gone wrong and/or failed to function.") +doc("G_FrontPanel", "Front Panel", "A basic interface on the front of a device for viewing and sometimes modifying its state. This is what you see when looking at a computer running one of the SCADA applications.") +doc("G_Nominal", "Nominal", "Normal operation. Everything operating as intended.") +doc("G_Ringback", "Ringback", "An indication that an alarm had gone off so that you are aware, even if the alarm condition is no longer met.") +doc("G_SCRAM", "SCRAM", "Emergency shut-down of a reactor by stopping the fission reactor.") +doc("G_Transient", "Transient", "A temporary change in state from normal operation. Coolant levels dropping or core temperature rising above nominal values would be examples of transients.") +doc("G_Trip", "Trip", "A checked condition has occurred, also known as 'tripped'.") +doc("G_Tripped", "Tripped", "An alarm condition has been met and is still met.") +doc("G_Tripping", "Tripping", "An alarm condition is met but has not met the minimum time before a condition is deemed a problem.") +doc("G_TurbineTrip", "Turbine Trip", "The turbine stops, which prevents heated coolant from being properly cooled. In Mekanism, this would occur when a turbine cannot generate any more energy due to filling its buffer and having no output with any storage for energy left.") + return docs From e37b8758cd12c3fe2e059b9fe0bb4e0d50698152 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 5 Jun 2024 19:38:02 -0400 Subject: [PATCH 46/87] #403 guide fixes and focusing improvements --- pocket/ui/apps/guide.lua | 26 ++++++++++++++++---------- pocket/ui/docs.lua | 6 +++--- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index b4300fa..1d0a0a6 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -143,40 +143,46 @@ local function new_view(root) PushButton{parent=gls,y=3,text="Abbreviations >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_abb_page.nav_to} PushButton{parent=gls,text="Terminology >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_term_page.nav_to} - local abb_name_list = ListBox{parent=gls_abb_div,x=2,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} - local abb_def_list = ListBox{parent=gls_abb_view_div,x=2,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + local abb_name_list = ListBox{parent=gls_abb_div,x=1,y=3,scroll_height=20,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + local abb_def_list = ListBox{parent=gls_abb_view_div,x=1,y=3,scroll_height=101,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + + local _end = nil for i = 1, #docs.glossary.abbvs do local item = docs.glossary.abbvs[i] ---@type pocket_doc_item - doc_map[item.key] = TextBox{parent=abb_def_list,text=item.name,anchor=true,cpair(colors.blue,colors.black)} + doc_map[item.key] = TextBox{parent=abb_def_list,text=item.name,anchor=true,fg_bg=cpair(colors.blue,colors.black)} TextBox{parent=abb_def_list,text=item.desc,fg_bg=label} - TextBox{parent=abb_def_list,text="",fg_bg=label} + _end = Div{parent=abb_def_list,height=1,can_focus=true} local function view() + _end.focus() gls_abb_view_page.nav_to() doc_map[item.key].focus() end - PushButton{parent=abb_name_list,text=item.name,fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=view} + PushButton{parent=abb_name_list,text=item.name,fg_bg=cpair(colors.blue,colors.black),active_fg_bg=btn_active,callback=view} end - local term_name_list = ListBox{parent=gls_term_div,x=2,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} - local term_def_list = ListBox{parent=gls_term_view_div,x=2,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + local term_name_list = ListBox{parent=gls_term_div,x=1,y=3,scroll_height=20,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + local term_def_list = ListBox{parent=gls_term_view_div,x=1,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + + local _end_b = nil for i = 1, #docs.glossary.terms do local item = docs.glossary.terms[i] ---@type pocket_doc_item - doc_map[item.key] = TextBox{parent=term_def_list,text=item.name,anchor=true,cpair(colors.blue,colors.black)} + doc_map[item.key] = TextBox{parent=term_def_list,text=item.name,anchor=true,fg_bg=cpair(colors.blue,colors.black)} TextBox{parent=term_def_list,text=item.desc,fg_bg=label} - TextBox{parent=term_def_list,text="",fg_bg=label} + _end_b = Div{parent=term_def_list,height=1,can_focus=true} local function view() + _end_b.focus() gls_term_view_page.nav_to() doc_map[item.key].focus() end - PushButton{parent=term_name_list,text=item.name,fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=view} + PushButton{parent=term_name_list,text=item.name,fg_bg=cpair(colors.blue,colors.black),active_fg_bg=btn_active,callback=view} end -- setup multipane diff --git a/pocket/ui/docs.lua b/pocket/ui/docs.lua index d38d4ce..50e20fa 100644 --- a/pocket/ui/docs.lua +++ b/pocket/ui/docs.lua @@ -72,16 +72,16 @@ doc("G_RTU", "RTU", "Remote Terminal Unit. Provides monitoring to and basic outp doc("G_SCADA", "SCADA", "Supervisory Control and Data Acquisition. A control systems architecture used in many different process control applications.") doc("G_SVR", "SVR", "Supervisor. Abbreviation for the supervisory computer.") -target = docs.glossary.term +target = docs.glossary.terms doc("G_Fault", "Fault", "Something has gone wrong and/or failed to function.") doc("G_FrontPanel", "Front Panel", "A basic interface on the front of a device for viewing and sometimes modifying its state. This is what you see when looking at a computer running one of the SCADA applications.") doc("G_Nominal", "Nominal", "Normal operation. Everything operating as intended.") doc("G_Ringback", "Ringback", "An indication that an alarm had gone off so that you are aware, even if the alarm condition is no longer met.") -doc("G_SCRAM", "SCRAM", "Emergency shut-down of a reactor by stopping the fission reactor.") +doc("G_SCRAM", "SCRAM", "[Emergency] shut-down of a reactor by stopping the fission reactor. In Mekanism and here, it isn't always for an emergency.") doc("G_Transient", "Transient", "A temporary change in state from normal operation. Coolant levels dropping or core temperature rising above nominal values would be examples of transients.") doc("G_Trip", "Trip", "A checked condition has occurred, also known as 'tripped'.") doc("G_Tripped", "Tripped", "An alarm condition has been met and is still met.") doc("G_Tripping", "Tripping", "An alarm condition is met but has not met the minimum time before a condition is deemed a problem.") -doc("G_TurbineTrip", "Turbine Trip", "The turbine stops, which prevents heated coolant from being properly cooled. In Mekanism, this would occur when a turbine cannot generate any more energy due to filling its buffer and having no output with any storage for energy left.") +doc("G_TurbineTrip", "Turbine Trip", "The turbine stopped, which prevents heated coolant from being properly cooled. In Mekanism, this would occur when a turbine cannot generate any more energy due to filling its buffer and having no output with any storage for energy left.") return docs From 375b7f680ef60d386a931060613329eb55a3bf36 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 5 Jun 2024 19:38:22 -0400 Subject: [PATCH 47/87] fixes to graphics constraint logic --- graphics/element.lua | 2 +- graphics/elements/textbox.lua | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/graphics/element.lua b/graphics/element.lua index 6f859ab..d086408 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -230,7 +230,7 @@ function element.new(args, constraint, child_offset_x, child_offset_y) if type(constraint) == "function" then -- constrain per provided constraint function (can only get smaller than available space) - w, h = constraint(w, h) + w, h = constraint(f) f.w = math.min(f.w, w) f.h = math.min(f.h, h) end diff --git a/graphics/elements/textbox.lua b/graphics/elements/textbox.lua index 5a02579..0761471 100644 --- a/graphics/elements/textbox.lua +++ b/graphics/elements/textbox.lua @@ -33,7 +33,13 @@ local function textbox(args) -- provide a constraint condition to element creation to prevent an pointlessly tall text box ---@param frame graphics_frame local function constrain(frame) - return frame.w, math.max(args.height, math.max(1, #util.strwrap(args.text, frame.w))) + local new_height = math.max(1, #util.strwrap(args.text, frame.w)) + + if args.height then + new_height = math.max(frame.h, new_height) + end + + return frame.w, new_height end -- create new graphics element base object From 5e70e4131e50637dad5aba0c35cf6c3cca351999 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 7 Jun 2024 02:30:55 +0000 Subject: [PATCH 48/87] #403 more work on guide and help linking --- pocket/iocontrol.lua | 16 +++- pocket/ui/apps/guide.lua | 125 ++++++++++-------------------- pocket/ui/docs.lua | 27 ++++++- pocket/ui/pages/guide_section.lua | 58 ++++++++++++++ 4 files changed, 137 insertions(+), 89 deletions(-) create mode 100644 pocket/ui/pages/guide_section.lua diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 67ea84c..9ff06a4 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -64,6 +64,7 @@ function iocontrol.alloc_nav() pane = nil, ---@type graphics_element apps = {}, containers = {}, + help_map = {}, cur_app = APP_ID.ROOT } @@ -181,9 +182,6 @@ function iocontrol.alloc_nav() 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) @@ -202,6 +200,9 @@ function iocontrol.alloc_nav() end end + -- get a list of the app containers (usually Div elements) + function io.nav.get_containers() return self.containers end + -- get the currently active page ---@return nav_tree_page function io.nav.get_current_page() @@ -218,6 +219,15 @@ function iocontrol.alloc_nav() io.nav.open_app(APP_ID.ROOT) end end + + function io.nav.open_help(key) + io.nav.open_app(APP_ID.GUIDE) + + local load = self.help_map[key] + if load then load() end + end + + function io.nav.link_help(map) self.help_map = map end end -- initialize facility-independent components of pocket iocontrol diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index 1d0a0a6..5d5b4ed 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -10,6 +10,8 @@ local iocontrol = require("pocket.iocontrol") local docs = require("pocket.ui.docs") local style = require("pocket.ui.style") +local guide_section = require("pocket.ui.pages.guide_section") + local core = require("graphics.core") local Div = require("graphics.elements.div") @@ -40,6 +42,7 @@ local function new_view(root) local btn_fg_bg = cpair(colors.cyan, colors.black) local btn_active = cpair(colors.white, colors.black) + local btn_disable = cpair(colors.gray, colors.black) local list = { { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(iocontrol.APP_ID.ROOT) end }, @@ -68,6 +71,10 @@ local function new_view(root) local panes = { home, use, uis, fps, gls } local doc_map = {} + local search_map = {} + + ---@class _guide_section_constructor_data + local sect_construct_data = { app, page_div, panes, doc_map, search_map, btn_fg_bg, btn_active } TextBox{parent=home,y=1,text="cc-mek-scada Guide",height=1,alignment=ALIGN.CENTER} @@ -77,117 +84,65 @@ local function new_view(root) PushButton{parent=home,text="Glossary >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_page.nav_to} TextBox{parent=use,y=1,text="System Usage",height=1,alignment=ALIGN.CENTER} - PushButton{parent=use,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} - PushButton{parent=use,y=3,text="Configuring Devices >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} - PushButton{parent=use,text="Connecting Devices >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} - PushButton{parent=use,text="Manual Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} - PushButton{parent=use,text="Automatic Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} - PushButton{parent=use,text="Waste Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + PushButton{parent=use,y=3,text="Configuring Devices >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable() + PushButton{parent=use,text="Connecting Devices >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable() + PushButton{parent=use,text="Manual Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable() + PushButton{parent=use,text="Automatic Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable() + PushButton{parent=use,text="Waste Control >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable() TextBox{parent=uis,y=1,text="Operator UIs",height=1,alignment=ALIGN.CENTER} - PushButton{parent=uis,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} - local alarms_page = app.new_page(uis_page, #panes + 1) - local alarms_div = Div{parent=page_div,x=2} - table.insert(panes, alarms_div) - local annunc_page = app.new_page(uis_page, #panes + 1) local annunc_div = Div{parent=page_div,x=2} table.insert(panes, annunc_div) + local alarms_page = guide_section(sect_construct_data, uis_page, "Alarms", docs.alarms) + PushButton{parent=uis,y=3,text="Alarms >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=alarms_page.nav_to} PushButton{parent=uis,text="Annunciators >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=annunc_page.nav_to} - PushButton{parent=uis,text="Pocket UI >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} - PushButton{parent=uis,text="Coordinator UI >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + PushButton{parent=uis,text="Pocket UI >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable() + PushButton{parent=uis,text="Coordinator UI >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable() + + TextBox{parent=annunc_div,y=1,text="Annunciators",height=1,alignment=ALIGN.CENTER} + PushButton{parent=annunc_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=uis_page.nav_to} + + local unit_gen_page = guide_section(sect_construct_data, annunc_page, "Unit General", docs.annunc.unit.main_section) + local unit_rps_page = guide_section(sect_construct_data, annunc_page, "Unit RPS", docs.annunc.unit.rps_section) + local unit_rcs_page = guide_section(sect_construct_data, annunc_page, "Unit RCS", docs.annunc.unit.rcs_section) + + PushButton{parent=annunc_div,y=3,text="Unit General >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_gen_page.nav_to} + PushButton{parent=annunc_div,text="Unit RPS >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_rps_page.nav_to} + PushButton{parent=annunc_div,text="Unit RCS >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_rcs_page.nav_to} + PushButton{parent=annunc_div,text="Facility General >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable() + PushButton{parent=annunc_div,text="Waste & Valves >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable() TextBox{parent=fps,y=1,text="Front Panels",height=1,alignment=ALIGN.CENTER} - PushButton{parent=fps,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} - PushButton{parent=fps,y=3,text="Common Items >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} - PushButton{parent=fps,text="Reactor PLC >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} - PushButton{parent=fps,text="RTU Gateway >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} - PushButton{parent=fps,text="Supervisor >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} - PushButton{parent=fps,text="Coordinator >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + PushButton{parent=fps,y=3,text="Common Items >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable() + PushButton{parent=fps,text="Reactor PLC >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable() + PushButton{parent=fps,text="RTU Gateway >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable() + PushButton{parent=fps,text="Supervisor >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable() + PushButton{parent=fps,text="Coordinator >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,dis_fg_bg=btn_disable,callback=function()end}.disable() TextBox{parent=gls,y=1,text="Glossary",height=1,alignment=ALIGN.CENTER} PushButton{parent=gls,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} - local gls_abb_page = app.new_page(gls_page, #panes + 1) - local gls_abb_div = Div{parent=page_div,x=2} - table.insert(panes, gls_abb_div) - TextBox{parent=gls_abb_div,y=1,text="Abbreviations",height=1,alignment=ALIGN.CENTER} - PushButton{parent=gls_abb_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_page.nav_to} + local gls_abbv_page = guide_section(sect_construct_data, gls_page, "Abbreviations", docs.glossary.abbvs) + local gls_term_page = guide_section(sect_construct_data, gls_page, "Terminology", docs.glossary.terms) - local gls_abb_view_page = app.new_page(gls_abb_page, #panes + 1) - local gls_abb_view_div = Div{parent=page_div,x=2} - table.insert(panes, gls_abb_view_div) - TextBox{parent=gls_abb_view_div,y=1,text="Abbreviations",height=1,alignment=ALIGN.CENTER} - PushButton{parent=gls_abb_view_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_abb_page.nav_to} - - local gls_term_page = app.new_page(gls_page, #panes + 1) - local gls_term_div = Div{parent=page_div,x=2} - table.insert(panes, gls_term_div) - TextBox{parent=gls_term_div,y=1,text="Terminology",height=1,alignment=ALIGN.CENTER} - PushButton{parent=gls_term_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_page.nav_to} - - local gls_term_view_page = app.new_page(gls_term_page, #panes + 1) - local gls_term_view_div = Div{parent=page_div,x=2} - table.insert(panes, gls_term_view_div) - TextBox{parent=gls_term_view_div,y=1,text="Terminology",height=1,alignment=ALIGN.CENTER} - PushButton{parent=gls_term_view_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_term_page.nav_to} - - PushButton{parent=gls,y=3,text="Abbreviations >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_abb_page.nav_to} + PushButton{parent=gls,y=3,text="Abbreviations >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_abbv_page.nav_to} PushButton{parent=gls,text="Terminology >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_term_page.nav_to} - local abb_name_list = ListBox{parent=gls_abb_div,x=1,y=3,scroll_height=20,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} - local abb_def_list = ListBox{parent=gls_abb_view_div,x=1,y=3,scroll_height=101,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} - - local _end = nil - - for i = 1, #docs.glossary.abbvs do - local item = docs.glossary.abbvs[i] ---@type pocket_doc_item - - doc_map[item.key] = TextBox{parent=abb_def_list,text=item.name,anchor=true,fg_bg=cpair(colors.blue,colors.black)} - TextBox{parent=abb_def_list,text=item.desc,fg_bg=label} - _end = Div{parent=abb_def_list,height=1,can_focus=true} - - local function view() - _end.focus() - gls_abb_view_page.nav_to() - doc_map[item.key].focus() - end - - PushButton{parent=abb_name_list,text=item.name,fg_bg=cpair(colors.blue,colors.black),active_fg_bg=btn_active,callback=view} - end - - local term_name_list = ListBox{parent=gls_term_div,x=1,y=3,scroll_height=20,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} - local term_def_list = ListBox{parent=gls_term_view_div,x=1,y=3,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} - - local _end_b = nil - - for i = 1, #docs.glossary.terms do - local item = docs.glossary.terms[i] ---@type pocket_doc_item - - doc_map[item.key] = TextBox{parent=term_def_list,text=item.name,anchor=true,fg_bg=cpair(colors.blue,colors.black)} - TextBox{parent=term_def_list,text=item.desc,fg_bg=label} - _end_b = Div{parent=term_def_list,height=1,can_focus=true} - - local function view() - _end_b.focus() - gls_term_view_page.nav_to() - doc_map[item.key].focus() - end - - PushButton{parent=term_name_list,text=item.name,fg_bg=cpair(colors.blue,colors.black),active_fg_bg=btn_active,callback=view} - end - -- setup multipane local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes} app.set_root_pane(u_pane) + + -- link help resources + db.nav.link_help(doc_map) end app.set_on_load(load) diff --git a/pocket/ui/docs.lua b/pocket/ui/docs.lua index 50e20fa..24090ec 100644 --- a/pocket/ui/docs.lua +++ b/pocket/ui/docs.lua @@ -50,9 +50,32 @@ doc("HighStartupRate", "Startup Rate High", "This is a rough calculation of if y target = docs.annunc.unit.rps_section doc("rps_tripped", "RPS Trip", "Indicates if the reactor protection system has caused a SCRAM.") +doc("manual", "Manual Reactor SCRAM", "Indicates if the operator (you) tripped the RPS by pressing SCRAM.") +doc("automatic", "Auto Reactor SCRAM", "Indicates if the automatic control system tripped the RPS.") +doc("sys_fail", "System Failure", "Indicates if the RPS tripped due to the reactor not being formed.") doc("high_dmg", "Damage Level High", "Indicates if the RPS tripped due to significant reactor damage.") +doc("ex_waste", "Excess Waste", "Indicates if the RPS tripped due to very high waste levels.") +doc("ex_hcool", "Excess Heated Coolant", "Indicates if the RPS tripped due to very high waste levels.") +doc("high_temp", "Temperature High", "Indicates if the RPS tripped due to reaching damaging temperatures.") +doc("low_cool", "Coolant Level Low Low", "Indicates if the RPS tripped due to very low coolant levels that result in the temperature uncontrollably rising.") +doc("no_fuel", "No Fuel", "Indicates if the RPS tripped due to no fuel being available.") +doc("fault", "PPM Fault", "Indicates if the RPS tripped due to a peripheral access fault.") +doc("timeout", "Connection Timeout", "Indicates if the RPS tripped due to losing connection with the supervisory computer.") +doc("sys_fail", "System Failure", "Indicates if the RPS tripped due to the reactor not being formed.") -target = docs.annunc.unit.main_section +target = docs.annunc.unit.rcs_section +doc("RCSFault", "RCS Hardware Fault", "Indicates if one or more of the RCS devices have a peripheral fault.") +doc("EmergencyCoolant", "Emergency Coolant", "Off if no emergency coolant redstone is configured, white when it is configured but not in use, and green/blue when it is activated.") +doc("CoolantFeedMismatch", "Coolant Feed Mismatch", "The coolant system is accumulating heated coolant or losing cooled coolant, likely due to one of the machines not keeping up with the needs of the reactor.") +doc("BoilRateMismatch", "Boil Rate Mismatch", "The total heating rate of the reactor is more than 4% off from the steam input rate of the turbines OR for sodium setups, the boiler boil rates are more than 4% off from the steam input rate of the turbines.") +doc("SteamFeedMismatch", "Steam Feed Mismatch", "There is an above tolerance difference between turbine flow and steam input rates or the reactor/boilers are gaining steam or losing water.") +doc("MaxWaterReturnFeed", "Max Water Return Feed", "The turbines are condensing the max rate of water that they can per the structure build.") +doc("WaterLevelLow", "Water Level Low", "The water level in the boiler is low.") +doc("HeatingRateLow", "Heating Rate Low", "The boiler is not hot enough to boil water, but it is receiving heated coolant.") +doc("SteamDumpOpen", "Steam Relief Valve Open", "This turns yellow if the turbine is set to dumping excess and red if it is set to dumping all. 'Relief Valve' in this case is that setting allowing the venting of steam.") +doc("TurbineOverSpeed", "Turbine Over Speed", "The turbine is at steam capacity, but not tripped. You may need more turbines if they can't keep up.") +doc("GeneratorTrip", "Generator Trip", "The turbine is no longer outputting power due to it having nowhere to go. Likely due to full power storage.") +doc("TurbineTrip", "Turbine Trip", "The turbine has reached its maximum power charge and has stopped rotating, and as a result stopped cooling steam to water.") docs.glossary = { abbvs = {}, terms = {} @@ -62,6 +85,7 @@ target = docs.glossary.abbvs doc("G_ACK", "ACK", "Alarm ACKnowledge. This indicates you understand an alarm occured and would like to stop the audio tone(s).") doc("G_CRD", "CRD", "Coordinator. Abbreviation for the coordinator computer.") doc("G_DBG", "DBG", "Debug. Abbreviation for the debugging sessions from pocket computers found on the supervisor's front panel.") +doc("G_FP", "FP", "Front Panel. See Terminology.") doc("G_PKT", "PKT", "Pocket. Abbreviation for the pocket computer.") doc("G_PLC", "PLC", "Programmable Logic Controller. A device that not only reports data and controls outputs, but also can make decisions on its own.") doc("G_PPM", "PPM", "Protected Peripheral Manager. This is an abstraction layer created for this project that prevents peripheral calls from crashing applications.") @@ -71,6 +95,7 @@ doc("G_RPS", "RPS", "Reactor Protection System. A component of the reactor PLC r doc("G_RTU", "RTU", "Remote Terminal Unit. Provides monitoring to and basic output from a SCADA system, interfacing with various types of devices/controls.") doc("G_SCADA", "SCADA", "Supervisory Control and Data Acquisition. A control systems architecture used in many different process control applications.") doc("G_SVR", "SVR", "Supervisor. Abbreviation for the supervisory computer.") +doc("G_UI", "UI", "User Interface.") target = docs.glossary.terms doc("G_Fault", "Fault", "Something has gone wrong and/or failed to function.") diff --git a/pocket/ui/pages/guide_section.lua b/pocket/ui/pages/guide_section.lua new file mode 100644 index 0000000..344f6c7 --- /dev/null +++ b/pocket/ui/pages/guide_section.lua @@ -0,0 +1,58 @@ +local core = require("graphics.core") + +local Div = require("graphics.elements.div") +local ListBox = require("graphics.elements.listbox") +local TextBox = require("graphics.elements.textbox") + +local PushButton = require("graphics.elements.controls.push_button") + +local ALIGN = core.ALIGN +local cpair = core.cpair + +-- new guide documentation section +---@param data _guide_section_constructor_data +---@param base_page nav_tree_page +---@param title string +---@param items table +---@return nav_tree_page +return function (data, base_page, title, items) + local app, page_div, panes, doc_map, search_map, btn_fg_bg, btn_active = table.unpack(data) + + local section_page = app.new_page(base_page, #panes + 1) + local gls_term_div = Div{parent=page_div,x=2} + table.insert(panes, gls_term_div) + TextBox{parent=gls_term_div,y=1,text=title,height=1,alignment=ALIGN.CENTER} + PushButton{parent=gls_term_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=base_page.nav_to} + + local gls_term_view_page = app.new_page(section_page, #panes + 1) + local gls_term_view_div = Div{parent=page_div,x=2} + table.insert(panes, gls_term_view_div) + TextBox{parent=gls_term_view_div,y=1,text=title,height=1,alignment=ALIGN.CENTER} + PushButton{parent=gls_term_view_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=section_page.nav_to} + + local name_list = ListBox{parent=gls_term_div,x=1,y=3,scroll_height=30,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + local def_list = ListBox{parent=gls_term_view_div,x=1,y=3,scroll_height=120,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + + local _end = nil + + for i = 1, #items do + local item = items[i] ---@type pocket_doc_item + + local anchor = TextBox{parent=def_list,text=item.name,anchor=true,fg_bg=cpair(colors.blue,colors.black)} + TextBox{parent=def_list,text=item.desc} + _end = Div{parent=def_list,height=1,can_focus=true} + + local function view() + _end.focus() + gls_term_view_page.nav_to() + anchor.focus() + end + + doc_map[item.key] = view + search_map[item.name] = view + + PushButton{parent=name_list,text=item.name,fg_bg=cpair(colors.blue,colors.black),active_fg_bg=btn_active,callback=view} + end + + return section_page +end From b457edbc71ea4b174a1c099e1a787f1256f5f425 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 6 Jun 2024 22:54:26 -0400 Subject: [PATCH 49/87] #403 variable sizing on listbox heights for sections --- pocket/ui/apps/guide.lua | 30 +++++++++++++++--------------- pocket/ui/pages/guide_section.lua | 5 +++-- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index 5d5b4ed..f30d927 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -5,21 +5,21 @@ -- local util = require("scada-common.util") -- local log = require("scada-common.log") -local iocontrol = require("pocket.iocontrol") +local iocontrol = require("pocket.iocontrol") -local docs = require("pocket.ui.docs") -local style = require("pocket.ui.style") +local docs = require("pocket.ui.docs") +local style = require("pocket.ui.style") local guide_section = require("pocket.ui.pages.guide_section") -local core = require("graphics.core") +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 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 PushButton = require("graphics.elements.controls.push_button") local ALIGN = core.ALIGN local cpair = core.cpair @@ -99,7 +99,7 @@ local function new_view(root) local annunc_div = Div{parent=page_div,x=2} table.insert(panes, annunc_div) - local alarms_page = guide_section(sect_construct_data, uis_page, "Alarms", docs.alarms) + local alarms_page = guide_section(sect_construct_data, uis_page, "Alarms", docs.alarms, 100) PushButton{parent=uis,y=3,text="Alarms >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=alarms_page.nav_to} PushButton{parent=uis,text="Annunciators >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=annunc_page.nav_to} @@ -109,9 +109,9 @@ local function new_view(root) TextBox{parent=annunc_div,y=1,text="Annunciators",height=1,alignment=ALIGN.CENTER} PushButton{parent=annunc_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=uis_page.nav_to} - local unit_gen_page = guide_section(sect_construct_data, annunc_page, "Unit General", docs.annunc.unit.main_section) - local unit_rps_page = guide_section(sect_construct_data, annunc_page, "Unit RPS", docs.annunc.unit.rps_section) - local unit_rcs_page = guide_section(sect_construct_data, annunc_page, "Unit RCS", docs.annunc.unit.rcs_section) + local unit_gen_page = guide_section(sect_construct_data, annunc_page, "Unit General", docs.annunc.unit.main_section, 200) + local unit_rps_page = guide_section(sect_construct_data, annunc_page, "Unit RPS", docs.annunc.unit.rps_section, 100) + local unit_rcs_page = guide_section(sect_construct_data, annunc_page, "Unit RCS", docs.annunc.unit.rcs_section, 100) PushButton{parent=annunc_div,y=3,text="Unit General >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_gen_page.nav_to} PushButton{parent=annunc_div,text="Unit RPS >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_rps_page.nav_to} @@ -131,8 +131,8 @@ local function new_view(root) TextBox{parent=gls,y=1,text="Glossary",height=1,alignment=ALIGN.CENTER} PushButton{parent=gls,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} - local gls_abbv_page = guide_section(sect_construct_data, gls_page, "Abbreviations", docs.glossary.abbvs) - local gls_term_page = guide_section(sect_construct_data, gls_page, "Terminology", docs.glossary.terms) + local gls_abbv_page = guide_section(sect_construct_data, gls_page, "Abbreviations", docs.glossary.abbvs, 120) + local gls_term_page = guide_section(sect_construct_data, gls_page, "Terminology", docs.glossary.terms, 100) PushButton{parent=gls,y=3,text="Abbreviations >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_abbv_page.nav_to} PushButton{parent=gls,text="Terminology >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_term_page.nav_to} diff --git a/pocket/ui/pages/guide_section.lua b/pocket/ui/pages/guide_section.lua index 344f6c7..b788a2a 100644 --- a/pocket/ui/pages/guide_section.lua +++ b/pocket/ui/pages/guide_section.lua @@ -14,8 +14,9 @@ local cpair = core.cpair ---@param base_page nav_tree_page ---@param title string ---@param items table +---@param scroll_height integer ---@return nav_tree_page -return function (data, base_page, title, items) +return function (data, base_page, title, items, scroll_height) local app, page_div, panes, doc_map, search_map, btn_fg_bg, btn_active = table.unpack(data) local section_page = app.new_page(base_page, #panes + 1) @@ -31,7 +32,7 @@ return function (data, base_page, title, items) PushButton{parent=gls_term_view_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=section_page.nav_to} local name_list = ListBox{parent=gls_term_div,x=1,y=3,scroll_height=30,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} - local def_list = ListBox{parent=gls_term_view_div,x=1,y=3,scroll_height=120,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + local def_list = ListBox{parent=gls_term_view_div,x=1,y=3,scroll_height=scroll_height,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} local _end = nil From e88e1afcc47460ee0be2c9e214f71b006fecc83d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 6 Jun 2024 23:05:22 -0400 Subject: [PATCH 50/87] #403 started work on guide searching --- pocket/ui/apps/guide.lua | 30 ++++++++++++++++++++++-------- pocket/ui/pages/guide_section.lua | 20 ++++++++++---------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index f30d927..07f22d1 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -6,6 +6,7 @@ -- local log = require("scada-common.log") local iocontrol = require("pocket.iocontrol") +local TextField = require("graphics.elements.form.text_field") local docs = require("pocket.ui.docs") local style = require("pocket.ui.style") @@ -46,9 +47,10 @@ local function new_view(root) local list = { { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(iocontrol.APP_ID.ROOT) end }, - { label = "Use", color = core.cpair(colors.black, colors.purple), callback = function () app.switcher(2) end }, - { label = "UIs", color = core.cpair(colors.black, colors.blue), callback = function () app.switcher(3) end }, - { label = "FPs", color = core.cpair(colors.black, colors.lightGray), callback = function () app.switcher(4) end } + { label = "\x14_?", color = core.cpair(colors.black, colors.cyan), callback = function () app.switcher(2) end }, + -- { label = "Use", color = core.cpair(colors.black, colors.purple), callback = function () app.switcher(2) end }, + -- { label = "UIs", color = core.cpair(colors.black, colors.blue), callback = function () app.switcher(3) end }, + -- { label = "FPs", color = core.cpair(colors.black, colors.lightGray), callback = function () app.switcher(4) end } } app.set_sidebar(list) @@ -58,17 +60,19 @@ local function new_view(root) local p_width = page_div.get_width() - 2 local main_page = app.new_page(nil, 1) - local use_page = app.new_page(main_page, 2) - local uis_page = app.new_page(main_page, 3) - local fps_page = app.new_page(main_page, 4) - local gls_page = app.new_page(main_page, 5) + local search_page = app.new_page(main_page, 2) + local use_page = app.new_page(main_page, 3) + local uis_page = app.new_page(main_page, 4) + local fps_page = app.new_page(main_page, 5) + local gls_page = app.new_page(main_page, 6) local home = Div{parent=page_div,x=2,width=p_width} + local search = Div{parent=page_div,x=2} local use = Div{parent=page_div,x=2,width=p_width} local uis = Div{parent=page_div,x=2,width=p_width} local fps = Div{parent=page_div,x=2,width=p_width} local gls = Div{parent=page_div,x=2} - local panes = { home, use, uis, fps, gls } + local panes = { home, search, use, uis, fps, gls } local doc_map = {} local search_map = {} @@ -83,6 +87,16 @@ local function new_view(root) PushButton{parent=home,text="Front Panels >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fps_page.nav_to} PushButton{parent=home,text="Glossary >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_page.nav_to} + TextBox{parent=search,y=1,text="Search",height=1,alignment=ALIGN.CENTER} + PushButton{parent=search,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} + + TextField{parent=search,x=1,y=3,width=18,fg_bg=cpair(colors.white,colors.gray)} + PushButton{parent=search,x=20,y=3,text="GO",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + + local search_results = ListBox{parent=search,x=1,y=5,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + + TextBox{parent=search_results,text="Click 'GO' to search..."} + TextBox{parent=use,y=1,text="System Usage",height=1,alignment=ALIGN.CENTER} PushButton{parent=use,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} diff --git a/pocket/ui/pages/guide_section.lua b/pocket/ui/pages/guide_section.lua index b788a2a..dbb7348 100644 --- a/pocket/ui/pages/guide_section.lua +++ b/pocket/ui/pages/guide_section.lua @@ -20,19 +20,19 @@ return function (data, base_page, title, items, scroll_height) local app, page_div, panes, doc_map, search_map, btn_fg_bg, btn_active = table.unpack(data) local section_page = app.new_page(base_page, #panes + 1) - local gls_term_div = Div{parent=page_div,x=2} - table.insert(panes, gls_term_div) - TextBox{parent=gls_term_div,y=1,text=title,height=1,alignment=ALIGN.CENTER} - PushButton{parent=gls_term_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=base_page.nav_to} + local section_div = Div{parent=page_div,x=2} + table.insert(panes, section_div) + TextBox{parent=section_div,y=1,text=title,height=1,alignment=ALIGN.CENTER} + PushButton{parent=section_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=base_page.nav_to} local gls_term_view_page = app.new_page(section_page, #panes + 1) - local gls_term_view_div = Div{parent=page_div,x=2} - table.insert(panes, gls_term_view_div) - TextBox{parent=gls_term_view_div,y=1,text=title,height=1,alignment=ALIGN.CENTER} - PushButton{parent=gls_term_view_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=section_page.nav_to} + local section_view_div = Div{parent=page_div,x=2} + table.insert(panes, section_view_div) + TextBox{parent=section_view_div,y=1,text=title,height=1,alignment=ALIGN.CENTER} + PushButton{parent=section_view_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=section_page.nav_to} - local name_list = ListBox{parent=gls_term_div,x=1,y=3,scroll_height=30,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} - local def_list = ListBox{parent=gls_term_view_div,x=1,y=3,scroll_height=scroll_height,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + local name_list = ListBox{parent=section_div,x=1,y=3,scroll_height=30,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + local def_list = ListBox{parent=section_view_div,x=1,y=3,scroll_height=scroll_height,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} local _end = nil From 83ba6e3961501ab4dd74a3db6d24266ff54a3773 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 6 Jun 2024 23:10:19 -0400 Subject: [PATCH 51/87] #403 updated search navigation --- pocket/ui/apps/guide.lua | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index 07f22d1..84d506d 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -47,10 +47,8 @@ local function new_view(root) local list = { { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(iocontrol.APP_ID.ROOT) end }, - { label = "\x14_?", color = core.cpair(colors.black, colors.cyan), callback = function () app.switcher(2) end }, - -- { label = "Use", color = core.cpair(colors.black, colors.purple), callback = function () app.switcher(2) end }, - -- { label = "UIs", color = core.cpair(colors.black, colors.blue), callback = function () app.switcher(3) end }, - -- { label = "FPs", color = core.cpair(colors.black, colors.lightGray), callback = function () app.switcher(4) end } + { label = " \x14 ", color = core.cpair(colors.black, colors.cyan), callback = function () app.switcher(1) end }, + { label = "__?", color = core.cpair(colors.black, colors.lightGray), callback = function () app.switcher(2) end } } app.set_sidebar(list) @@ -66,7 +64,7 @@ local function new_view(root) local fps_page = app.new_page(main_page, 5) local gls_page = app.new_page(main_page, 6) - local home = Div{parent=page_div,x=2,width=p_width} + local home = Div{parent=page_div,x=2} local search = Div{parent=page_div,x=2} local use = Div{parent=page_div,x=2,width=p_width} local uis = Div{parent=page_div,x=2,width=p_width} @@ -82,13 +80,13 @@ local function new_view(root) TextBox{parent=home,y=1,text="cc-mek-scada Guide",height=1,alignment=ALIGN.CENTER} - PushButton{parent=home,y=3,text="System Usage >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=use_page.nav_to} + PushButton{parent=home,y=3,text="Search >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=search_page.nav_to} + PushButton{parent=home,y=5,text="System Usage >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=use_page.nav_to} PushButton{parent=home,text="Operator UIs >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=uis_page.nav_to} PushButton{parent=home,text="Front Panels >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fps_page.nav_to} PushButton{parent=home,text="Glossary >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_page.nav_to} TextBox{parent=search,y=1,text="Search",height=1,alignment=ALIGN.CENTER} - PushButton{parent=search,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} TextField{parent=search,x=1,y=3,width=18,fg_bg=cpair(colors.white,colors.gray)} PushButton{parent=search,x=20,y=3,text="GO",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} From 09c44a6969998e6011821b55bf87b3a2b226b9e4 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 12 Jun 2024 19:59:19 -0400 Subject: [PATCH 52/87] multi-line push button and keep focus on keyboard select --- graphics/element.lua | 2 +- graphics/elements/controls/push_button.lua | 45 ++++++++++++++-------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/graphics/element.lua b/graphics/element.lua index d086408..7475dc1 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -200,7 +200,7 @@ function element.new(args, constraint, child_offset_x, child_offset_y) ---@param next_y integer next line if no y was provided function protected.prepare_template(offset_x, offset_y, next_y) -- don't auto incrememnt y if inheriting height, that would cause an assertion - next_y = util.trinary(args.height == nil, 1, next_y) + next_y = util.trinary(args.height == nil and constraint == nil, 1, next_y) -- record offsets in case there is a reposition self.offset_x = offset_x diff --git a/graphics/elements/controls/push_button.lua b/graphics/elements/controls/push_button.lua index 88c8a5d..f060901 100644 --- a/graphics/elements/controls/push_button.lua +++ b/graphics/elements/controls/push_button.lua @@ -1,6 +1,7 @@ -- Button Graphics Element local tcd = require("scada-common.tcd") +local util = require("scada-common.util") local core = require("graphics.core") local element = require("graphics.element") @@ -21,7 +22,6 @@ local KEY_CLICK = core.events.KEY_CLICK ---@field id? string element id ---@field x? integer 1 if omitted ---@field y? integer auto incremented if omitted ----@field height? integer parent height if omitted ---@field fg_bg? cpair foreground/background colors ---@field hidden? boolean true to hide on initial draw @@ -38,29 +38,40 @@ local function push_button(args) -- set automatic settings args.can_focus = true - args.height = 1 args.min_width = args.min_width or 0 args.width = math.max(text_width, args.min_width) - -- create new graphics element base object - local e = element.new(args) - - local h_pad = 1 - local v_pad = math.floor(e.frame.h / 2) + 1 - - if alignment == ALIGN.CENTER then - h_pad = math.floor((e.frame.w - text_width) / 2) + 1 - elseif alignment == ALIGN.RIGHT then - h_pad = (e.frame.w - text_width) + 1 + -- provide a constraint condition to element creation to prefer a single line button + ---@param frame graphics_frame + local function constrain(frame) + return frame.w, math.max(1, #util.strwrap(args.text, frame.w)) end + -- create new graphics element base object + local e = element.new(args, constrain) + + local text_lines = util.strwrap(args.text, e.frame.w) + -- draw the button function e.redraw() e.window.clear() - -- write the button text - e.w_set_cur(h_pad, v_pad) - e.w_write(args.text) + for i = 1, #text_lines do + if i > e.frame.h then break end + + local len = string.len(text_lines[i]) + + -- use cursor position to align this line + if alignment == ALIGN.CENTER then + e.w_set_cur(math.floor((e.frame.w - len) / 2) + 1, i) + elseif alignment == ALIGN.RIGHT then + e.w_set_cur((e.frame.w - len) + 1, i) + else + e.w_set_cur(1, i) + end + + e.w_write(text_lines[i]) + end end -- draw the button as pressed (if active_fg_bg set) @@ -109,7 +120,9 @@ local function push_button(args) if event.type == KEY_CLICK.DOWN then if event.key == keys.space or event.key == keys.enter or event.key == keys.numPadEnter then args.callback() - e.defocus() + -- visualize click without unfocusing + show_unpressed() + if args.active_fg_bg ~= nil then tcd.dispatch(0.25, show_pressed) end end end end From 356caf7b4d4fecb65686ae3faa8281e446866a69 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 12 Jun 2024 20:01:31 -0400 Subject: [PATCH 53/87] #403 guide searching --- pocket/ui/apps/guide.lua | 61 +++++++++++++++++++++++++++---- pocket/ui/docs.lua | 2 +- pocket/ui/pages/guide_section.lua | 6 +-- 3 files changed, 58 insertions(+), 11 deletions(-) diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index 84d506d..35868d8 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -2,11 +2,11 @@ -- System Guide -- --- local util = require("scada-common.util") +local util = require("scada-common.util") -- local log = require("scada-common.log") local iocontrol = require("pocket.iocontrol") -local TextField = require("graphics.elements.form.text_field") +local TextField = require("graphics.elements.form.text_field") local docs = require("pocket.ui.docs") local style = require("pocket.ui.style") @@ -73,10 +73,10 @@ local function new_view(root) local panes = { home, search, use, uis, fps, gls } local doc_map = {} - local search_map = {} + local search_db = {} ---@class _guide_section_constructor_data - local sect_construct_data = { app, page_div, panes, doc_map, search_map, btn_fg_bg, btn_active } + local sect_construct_data = { app, page_div, panes, doc_map, search_db, btn_fg_bg, btn_active } TextBox{parent=home,y=1,text="cc-mek-scada Guide",height=1,alignment=ALIGN.CENTER} @@ -88,10 +88,57 @@ local function new_view(root) TextBox{parent=search,y=1,text="Search",height=1,alignment=ALIGN.CENTER} - TextField{parent=search,x=1,y=3,width=18,fg_bg=cpair(colors.white,colors.gray)} - PushButton{parent=search,x=20,y=3,text="GO",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()end} + local query_field = TextField{parent=search,x=1,y=3,width=18,fg_bg=cpair(colors.white,colors.gray)} - local search_results = ListBox{parent=search,x=1,y=5,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + local func_ref = {} + + PushButton{parent=search,x=20,y=3,text="GO",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=function()func_ref.run_search()end} + + local search_results = ListBox{parent=search,x=1,y=5,scroll_height=200,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + + function func_ref.run_search() + local query = string.lower(query_field.get_value()) + local s_results = { {}, {}, {} } + + search_results.remove_all() + + if string.len(query) < 3 then + TextBox{parent=search_results,text=util.trinary(string.len(query)==0,"Click 'GO' to search...","Search requires at least 3 characters.")} + return + end + + for _, entry in ipairs(search_db) do + local s_start, _ = string.find(entry[1], query, 1, true) + + if s_start == nil then + elseif s_start == 1 then + -- best match, start of key + table.insert(s_results[1], entry) + elseif string.sub(query, s_start - 1, s_start) == " " then + -- start of word, good match + table.insert(s_results[2], entry) + else + -- basic match in content + table.insert(s_results[3], entry) + end + end + + local empty = true + + for tier = 1, 3 do + for idx = 1, #s_results[tier] do + local entry = s_results[tier][idx] + TextBox{parent=search_results,text=entry[3].." >",fg_bg=cpair(colors.gray,colors.black)} + PushButton{parent=search_results,text=entry[2],alignment=ALIGN.LEFT,fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=entry[4]} + + empty = false + end + end + + if empty then + TextBox{parent=search_results,text="No results found."} + end + end TextBox{parent=search_results,text="Click 'GO' to search..."} diff --git a/pocket/ui/docs.lua b/pocket/ui/docs.lua index 24090ec..936b2e8 100644 --- a/pocket/ui/docs.lua +++ b/pocket/ui/docs.lua @@ -55,7 +55,7 @@ doc("automatic", "Auto Reactor SCRAM", "Indicates if the automatic control syste doc("sys_fail", "System Failure", "Indicates if the RPS tripped due to the reactor not being formed.") doc("high_dmg", "Damage Level High", "Indicates if the RPS tripped due to significant reactor damage.") doc("ex_waste", "Excess Waste", "Indicates if the RPS tripped due to very high waste levels.") -doc("ex_hcool", "Excess Heated Coolant", "Indicates if the RPS tripped due to very high waste levels.") +doc("ex_hcool", "Excess Heated Coolant", "Indicates if the RPS tripped due to very high heated coolant levels.") doc("high_temp", "Temperature High", "Indicates if the RPS tripped due to reaching damaging temperatures.") doc("low_cool", "Coolant Level Low Low", "Indicates if the RPS tripped due to very low coolant levels that result in the temperature uncontrollably rising.") doc("no_fuel", "No Fuel", "Indicates if the RPS tripped due to no fuel being available.") diff --git a/pocket/ui/pages/guide_section.lua b/pocket/ui/pages/guide_section.lua index dbb7348..1cbc8ec 100644 --- a/pocket/ui/pages/guide_section.lua +++ b/pocket/ui/pages/guide_section.lua @@ -17,7 +17,7 @@ local cpair = core.cpair ---@param scroll_height integer ---@return nav_tree_page return function (data, base_page, title, items, scroll_height) - local app, page_div, panes, doc_map, search_map, btn_fg_bg, btn_active = table.unpack(data) + local app, page_div, panes, doc_map, search_db, btn_fg_bg, btn_active = table.unpack(data) local section_page = app.new_page(base_page, #panes + 1) local section_div = Div{parent=page_div,x=2} @@ -50,9 +50,9 @@ return function (data, base_page, title, items, scroll_height) end doc_map[item.key] = view - search_map[item.name] = view + table.insert(search_db, { string.lower(item.name), item.name, title, view }) - PushButton{parent=name_list,text=item.name,fg_bg=cpair(colors.blue,colors.black),active_fg_bg=btn_active,callback=view} + PushButton{parent=name_list,text=item.name,alignment=ALIGN.LEFT,fg_bg=cpair(colors.blue,colors.black),active_fg_bg=btn_active,callback=view} end return section_page From 95b93eb795f52861cd0a9f40d030d76578119844 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 12 Jun 2024 20:10:16 -0400 Subject: [PATCH 54/87] #403 nav up button sends back to prior app if open_help was used --- pocket/iocontrol.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 9ff06a4..96bc0a8 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -65,6 +65,7 @@ function iocontrol.alloc_nav() apps = {}, containers = {}, help_map = {}, + help_return = nil, cur_app = APP_ID.ROOT } @@ -185,6 +186,9 @@ function iocontrol.alloc_nav() -- open a given app ---@param app_id POCKET_APP_ID function io.nav.open_app(app_id) + -- reset help return on navigating out of an app + if app_id == APP_ID.ROOT then self.help_return = nil end + local app = self.apps[app_id] ---@type pocket_app if app then if not app.loaded then app.load() end @@ -211,6 +215,13 @@ function iocontrol.alloc_nav() -- attempt to navigate up function io.nav.nav_up() + -- return out of help if opened with open_help + if self.help_return then + io.nav.open_app(self.help_return) + self.help_return = nil + return + end + local app = self.apps[self.cur_app] ---@type pocket_app log.debug("attempting app nav up for app " .. self.cur_app) @@ -220,7 +231,10 @@ function iocontrol.alloc_nav() end end + -- open the help app, to show the reference for a key function io.nav.open_help(key) + self.help_return = self.cur_app + io.nav.open_app(APP_ID.GUIDE) local load = self.help_map[key] From ff8ae5e6094e18107c2902664bea012743807f34 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 12 Jun 2024 20:17:31 -0400 Subject: [PATCH 55/87] #200 updated status info display fields --- pocket/iocontrol.lua | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 96bc0a8..5020399 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -894,9 +894,7 @@ function iocontrol.record_unit_data(data) if tripped(unit.alarms[ALARM.ReactorDamage]) then local items = { - white("REACTOR DAMAGED"), - blue("CHECK RCS"), - blue("AWAIT DMG REDUCED") + white("REACTOR DAMAGED"), blue("CHECK RCS"), blue("AWAIT DMG REDUCED") } table.insert(ecam, { color = colors.red, text = "REACTOR DAMAGE", help = "ReactorDamage", items = items }) @@ -904,9 +902,7 @@ function iocontrol.record_unit_data(data) if tripped(unit.alarms[ALARM.ReactorOverTemp]) then local items = { - white("HIT DAMAGING TEMP"), - blue("CHECK RCS"), - blue("AWAIT COOLDOWN") + white("DAMAGING TEMP"), blue("CHECK RCS"), blue("AWAIT COOLDOWN") } table.insert(ecam, { color = colors.red, text = "REACTOR OVER TEMP", help = "ReactorOverTemp", items = items }) @@ -918,7 +914,7 @@ function iocontrol.record_unit_data(data) end if tripped(unit.alarms[ALARM.ReactorWasteLeak]) then - local items = { white("AT WASTE CAPACITY"), blue("CHECK WASTE OUTPUT"), red("DO NOT ENABLE RCT") } + local items = { white("AT WASTE CAPACITY"), blue("CHECK WASTE OUTPUT"), blue("KEEP RCT DISABLED") } table.insert(ecam, { color = colors.red, text = "REACTOR WASTE LEAK", help = "ReactorWasteLeak", items = items}) end @@ -934,7 +930,7 @@ function iocontrol.record_unit_data(data) local function insert(cond, key, text, color) if cond[key] then table.insert(items, { text = text, help = key, color = color }) end end - table.insert(items, { text = "REACTOR SCRAMMED", color = colors.white }) + table.insert(items, white("REACTOR SCRAMMED")) insert(stat, "high_dmg", "HIGH DAMAGE", colors.red) insert(stat, "high_temp", "HIGH TEMPERATURE", colors.red) insert(stat, "low_cool", "CRIT LOW COOLANT") @@ -947,6 +943,8 @@ function iocontrol.record_unit_data(data) insert(stat, "automatic", "AUTOMATIC SCRAM") insert(stat, "sys_fail", "NOT FORMED", colors.red) insert(stat, "force_dis", "FORCE DISABLED", colors.red) + table.insert(items, blue("RESOLVE PROBLEM")) + table.insert(items, blue("RESET RPS")) table.insert(ecam, { color = colors.yellow, text = "RPS TRANSIENT", help = "RPSTransient", items = items}) end @@ -959,6 +957,8 @@ function iocontrol.record_unit_data(data) if cond == true or (type(cond) == "table" and cond[key]) then table.insert(items, { text = text, help = key, color = color }) end end + table.insert(items, white("COOLANT PROBLEM")) + insert(annunc, "RCPTrip", "RCP TRIP", colors.red) insert(annunc, "CoolantLevelLow", "LOW COOLANT") @@ -986,10 +986,8 @@ function iocontrol.record_unit_data(data) insert(annunc, "MaxWaterReturnFeed", "MAX WTR RTRN FEED") - for k, v in ipairs(annunc.WaterLevelLow) do insert(v, "WaterLevelLow", "BOILER " .. k .. " WTR LOW", colors.red) end - for k, v in ipairs(annunc.HeatingRateLow) do insert(v, "HeatingRateLow", "BOILER " .. k .. " HEAT RATE") end - for k, v in ipairs(annunc.TurbineOverSpeed) do insert(v, "TurbineOverSpeed", "TURBINE " .. k .. " OVERSPD", colors.red) end - for k, v in ipairs(annunc.GeneratorTrip) do insert(v, "GeneratorTrip", "TURBINE " .. k .. " GEN TRIP") end + + table.insert(items, blue("CHECK COOLING SYS")) table.insert(ecam, { color = colors.yellow, text = "RCS TRANSIENT", help = "RCSTransient", items = items}) end From f8a5dd9c321db2b3985b7d9e3d9bec639d05ffef Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 12 Jun 2024 20:20:30 -0400 Subject: [PATCH 56/87] #200 additional fields for info display --- pocket/iocontrol.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 5020399..f48ce25 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -986,6 +986,10 @@ function iocontrol.record_unit_data(data) insert(annunc, "MaxWaterReturnFeed", "MAX WTR RTRN FEED") + for k, v in ipairs(annunc.WaterLevelLow) do insert(v, "WaterLevelLow", "BOILER " .. k .. " WTR LOW", colors.red) end + for k, v in ipairs(annunc.HeatingRateLow) do insert(v, "HeatingRateLow", "BOILER " .. k .. " HEAT RATE") end + for k, v in ipairs(annunc.TurbineOverSpeed) do insert(v, "TurbineOverSpeed", "TURBINE " .. k .. " OVERSPD", colors.red) end + for k, v in ipairs(annunc.GeneratorTrip) do insert(v, "GeneratorTrip", "TURBINE " .. k .. " GEN TRIP") end table.insert(items, blue("CHECK COOLING SYS")) From 5848c2ac1a3898b19803c9524407f6d9f9089bc7 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 12 Jun 2024 20:22:41 -0400 Subject: [PATCH 57/87] test code for debugging --- pocket/iocontrol.lua | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index f48ce25..669891b 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -862,10 +862,31 @@ function iocontrol.record_unit_data(data) local ecam = {} -- aviation reference :) back to VATSIM I go... - local function red(text) return { text = text, color = colors.red } end + -- local function red(text) return { text = text, color = colors.red } end local function white(text) return { text = text, color = colors.white } end local function blue(text) return { text = text, color = colors.blue } end + -- unit.reactor_data.rps_status = { + -- high_dmg = false, + -- high_temp = false, + -- low_cool = false, + -- ex_waste = false, + -- ex_hcool = false, + -- no_fuel = false, + -- fault = false, + -- timeout = false, + -- manual = false, + -- automatic = false, + -- sys_fail = false, + -- force_dis = false + -- } + + -- if unit.reactor_data.rps_status then + -- for k, v in pairs(unit.alarms) do + -- unit.alarms[k] = ALARM_STATE.TRIPPED + -- end + -- end + if tripped(unit.alarms[ALARM.ContainmentBreach]) then local items = { white("REACTOR MELTDOWN"), blue("DON HAZMAT SUIT") } table.insert(ecam, { color = colors.red, text = "CONTAINMENT BREACH", help = "ContainmentBreach", items = items }) @@ -928,6 +949,10 @@ function iocontrol.record_unit_data(data) local items = {} local stat = unit.reactor_data.rps_status + -- for k, _ in pairs(stat) do + -- stat[k] = true + -- end + local function insert(cond, key, text, color) if cond[key] then table.insert(items, { text = text, help = key, color = color }) end end table.insert(items, white("REACTOR SCRAMMED")) @@ -953,6 +978,15 @@ function iocontrol.record_unit_data(data) local items = {} local annunc = unit.annunciator + -- for k, v in pairs(annunc) do + -- if type(v) == "boolean" then annunc[k] = true end + -- if type(v) == "table" then + -- for a, _ in pairs(v) do + -- v[a] = true + -- end + -- end + -- end + local function insert(cond, key, text, color) if cond == true or (type(cond) == "table" and cond[key]) then table.insert(items, { text = text, help = key, color = color }) end end From e851a5275f5e5a6a12e9197fc8dc53f31a23b756 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 13 Jun 2024 16:45:44 +0000 Subject: [PATCH 58/87] #496 pocket threading --- coordinator/threads.lua | 2 +- pocket/startup.lua | 154 ++++++++++++---------------- pocket/threads.lua | 216 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 281 insertions(+), 91 deletions(-) create mode 100644 pocket/threads.lua diff --git a/coordinator/threads.lua b/coordinator/threads.lua index e7c2d8c..20ba7ae 100644 --- a/coordinator/threads.lua +++ b/coordinator/threads.lua @@ -244,7 +244,7 @@ function threads.thread__main(smem) return public end --- coordinator renderer thread, tasked with long duration re-draws +-- coordinator renderer thread, tasked with long duration draws ---@nodiscard ---@param smem crd_shared_memory function threads.thread__render(smem) diff --git a/pocket/startup.lua b/pocket/startup.lua index 335bdc1..0c08061 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -6,19 +6,18 @@ require("/initenv").init_env() local crash = require("scada-common.crash") local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") local network = require("scada-common.network") local ppm = require("scada-common.ppm") -local tcd = require("scada-common.tcd") local util = require("scada-common.util") -local core = require("graphics.core") - local configure = require("pocket.configure") local iocontrol = require("pocket.iocontrol") local pocket = require("pocket.pocket") local renderer = require("pocket.renderer") +local threads = require("pocket.threads") -local POCKET_VERSION = "v0.9.2-alpha" +local POCKET_VERSION = "v0.10.0-alpha" local println = util.println local println_ts = util.println_ts @@ -71,6 +70,44 @@ local function main() -- record version for GUI iocontrol.get_db().version = POCKET_VERSION + ---------------------------------------- + -- memory allocation + ---------------------------------------- + + -- shared memory across threads + ---@class pkt_shared_memory + local __shared_memory = { + -- pocket system state flags + ---@class pkt_state + pkt_state = { + ui_ok = false, + shutdown = false + }, + + -- core pocket devices + pkt_dev = { + modem = ppm.get_wireless_modem() + }, + + -- system objects + pkt_sys = { + nic = nil, ---@type nic + pocket_comms = nil, ---@type pocket_comms + sv_wd = nil, ---@type watchdog + api_wd = nil ---@type watchdog + }, + + -- message queues + q = { + mq_render = mqueue.new() + } + } + + local smem_dev = __shared_memory.pkt_dev + local smem_sys = __shared_memory.pkt_sys + + local pkt_state = __shared_memory.pkt_state + ---------------------------------------- -- setup communications & clocks ---------------------------------------- @@ -83,118 +120,55 @@ local function main() iocontrol.report_link_state(iocontrol.LINK_STATE.UNLINKED) -- get the communications modem - local modem = ppm.get_wireless_modem() - if modem == nil then + if smem_dev.modem == nil then println("startup> wireless modem not found: please craft the pocket computer with a wireless modem") log.fatal("startup> no wireless modem on startup") return end -- create connection watchdogs - local conn_wd = { - sv = util.new_watchdog(config.ConnTimeout), - api = util.new_watchdog(config.ConnTimeout) - } - - conn_wd.sv.cancel() - conn_wd.api.cancel() - + smem_sys.sv_wd = util.new_watchdog(config.ConnTimeout) + smem_sys.sv_wd.cancel() + smem_sys.api_wd = util.new_watchdog(config.ConnTimeout) + smem_sys.api_wd.cancel() log.debug("startup> conn watchdogs created") -- create network interface then setup comms - local nic = network.nic(modem) - local pocket_comms = pocket.comms(POCKET_VERSION, nic, conn_wd.sv, conn_wd.api) + smem_sys.nic = network.nic(smem_dev.modem) + smem_sys.pocket_comms = pocket.comms(POCKET_VERSION, smem_sys.nic, smem_sys.sv_wd, smem_sys.api_wd) log.debug("startup> comms init") - -- base loop clock (2Hz, 10 ticks) - local MAIN_CLOCK = 0.5 - local loop_clock = util.new_clock(MAIN_CLOCK) - -- init I/O control - iocontrol.init_core(pocket_comms) + iocontrol.init_core(smem_sys.pocket_comms) ---------------------------------------- -- start the UI ---------------------------------------- - local ui_ok, message = renderer.try_start_ui() - if not ui_ok then - println(util.c("UI error: ", message)) - log.error(util.c("startup> GUI render failed with error ", message)) - else - -- start clock - loop_clock.start() + local ui_message + pkt_state.ui_ok, ui_message = renderer.try_start_ui() + if not pkt_state.ui_ok then + println(util.c("UI error: ", ui_message)) + log.error(util.c("startup> GUI render failed with error ", ui_message)) end ---------------------------------------- - -- main event loop + -- start system ---------------------------------------- - if ui_ok then - -- start connection watchdogs - conn_wd.sv.feed() - conn_wd.api.feed() - log.debug("startup> conn watchdogs started") + if pkt_state.ui_ok then + -- init threads + local main_thread = threads.thread__main(__shared_memory) + local render_thread = threads.thread__render(__shared_memory) - local io_db = iocontrol.get_db() - local nav = io_db.nav + log.info("startup> completed") - -- 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 - - -- relink if necessary - pocket_comms.link_update() - - -- update any tasks for the active page - local page_tasks = nav.get_current_page().tasks - for i = 1, #page_tasks do page_tasks[i]() end - - 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 - tcd.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" or - event == "double_click" then - -- handle a mouse event - renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3)) - elseif event == "char" or event == "key" or event == "key_up" then - -- handle a keyboard event - renderer.handle_key(core.events.new_key_event(event, param1, param2)) - elseif event == "paste" then - -- handle a paste event - renderer.handle_paste(param1) - 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 - end + -- run threads + parallel.waitForAll(main_thread.p_exec, render_thread.p_exec) renderer.close_ui() + else + println_ts("UI creation failed") end println_ts("exited") diff --git a/pocket/threads.lua b/pocket/threads.lua new file mode 100644 index 0000000..22a4576 --- /dev/null +++ b/pocket/threads.lua @@ -0,0 +1,216 @@ +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local ppm = require("scada-common.ppm") +local tcd = require("scada-common.tcd") +local util = require("scada-common.util") + +local iocontrol = require("pocket.iocontrol") +local renderer = require("pocket.renderer") + +local core = require("graphics.core") + +local threads = {} + +local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks) +local RENDER_SLEEP = 100 -- (100ms, 2 ticks) + +local MQ__RENDER_CMD = { + UNLOAD_SV_APPS = 1, + UNLOAD_API_APPS = 2 +} + +local MQ__RENDER_DATA = { + LOAD_APP = 1 +} + +-- main thread +---@nodiscard +---@param smem pkt_shared_memory +function threads.thread__main(smem) + ---@class parallel_thread + local public = {} + + -- execute thread + function public.exec() + log.debug("main thread start") + + local loop_clock = util.new_clock(MAIN_CLOCK) + + -- start clock + loop_clock.start() + + -- load in from shared memory + local pkt_state = smem.pkt_state + local pocket_comms = smem.pkt_sys.pocket_comms + local sv_wd = smem.pkt_sys.sv_wd + local api_wd = smem.pkt_sys.api_wd + + -- start connection watchdogs + sv_wd.feed() + api_wd.feed() + log.debug("startup> conn watchdogs started") + + local io_db = iocontrol.get_db() + local nav = io_db.nav + + -- 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 + + -- relink if necessary + pocket_comms.link_update() + + -- update any tasks for the active page + local page_tasks = nav.get_current_page().tasks + for i = 1, #page_tasks do page_tasks[i]() end + + loop_clock.start() + elseif sv_wd.is_timer(param1) then + -- supervisor watchdog timeout + log.info("supervisor server timeout") + pocket_comms.close_sv() + elseif api_wd.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 + tcd.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" or + event == "double_click" then + -- handle a mouse event + renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3)) + elseif event == "char" or event == "key" or event == "key_up" then + -- handle a keyboard event + renderer.handle_key(core.events.new_key_event(event, param1, param2)) + elseif event == "paste" then + -- handle a paste event + renderer.handle_paste(param1) + end + + -- check for termination request or UI crash + if event == "terminate" or ppm.should_terminate() then + log.info("terminate requested, main thread exiting") + pkt_state.shutdown = true + elseif not pkt_state.ui_ok then + pkt_state.shutdown = true + log.info("terminating due to fatal UI error") + end + + if pkt_state.shutdown then + log.info("closing server connections...") + pocket_comms.close() + log.info("connections closed") + break + end + end + end + + -- execute the thread in a protected mode, retrying it on return if not shutting down + function public.p_exec() + local pkt_state = smem.pkt_state + + while not pkt_state.shutdown do + local status, result = pcall(public.exec) + if status == false then + log.fatal(util.strval(result)) + end + + -- if status is true, then we are probably exiting, so this won't matter + -- this thread cannot be slept because it will miss events (namely "terminate") + if not pkt_state.shutdown then + log.info("main thread restarting now...") + end + end + end + + return public +end + +-- pocket renderer thread, tasked with long duration draws +---@nodiscard +---@param smem pkt_shared_memory +function threads.thread__render(smem) + ---@class parallel_thread + local public = {} + + -- execute thread + function public.exec() + log.debug("render thread start") + + -- load in from shared memory + local pkt_state = smem.pkt_state + local render_queue = smem.q.mq_render + + local last_update = util.time() + + -- thread loop + while true do + -- check for messages in the message queue + while render_queue.ready() and not pkt_state.shutdown do + local msg = render_queue.pop() + + if msg ~= nil then + if msg.qtype == mqueue.TYPE.COMMAND then + -- received a command + if msg.message == MQ__RENDER_CMD.UNLOAD_SV_APPS then + elseif msg.message == MQ__RENDER_CMD.UNLOAD_API_APPS then + end + elseif msg.qtype == mqueue.TYPE.DATA then + -- received data + local cmd = msg.message ---@type queue_data + + if cmd.key == MQ__RENDER_DATA.LOAD_APP then + end + elseif msg.qtype == mqueue.TYPE.PACKET then + -- received a packet + end + end + + -- quick yield + util.nop() + end + + -- check for termination request + if pkt_state.shutdown then + log.info("render thread exiting") + break + end + + -- delay before next check + last_update = util.adaptive_delay(RENDER_SLEEP, last_update) + end + end + + -- execute the thread in a protected mode, retrying it on return if not shutting down + function public.p_exec() + local pkt_state = smem.pkt_state + + while not pkt_state.shutdown do + local status, result = pcall(public.exec) + if status == false then + log.fatal(util.strval(result)) + end + + if not pkt_state.shutdown then + log.info("render thread restarting in 5 seconds...") + util.psleep(5) + end + end + end + + return public +end + +return threads From 38457cfbbc69bfb3f57ccf41130db0ee5b88a501 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 13 Jun 2024 20:34:39 -0400 Subject: [PATCH 59/87] enforce pocket computer requirement --- pocket/startup.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pocket/startup.lua b/pocket/startup.lua index 0c08061..6ce61a2 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -2,6 +2,9 @@ -- SCADA System Access on a Pocket Computer -- +---@diagnostic disable-next-line: undefined-global +local _is_pocket_env = pocket or periphemu + require("/initenv").init_env() local crash = require("scada-common.crash") @@ -22,6 +25,12 @@ local POCKET_VERSION = "v0.10.0-alpha" local println = util.println local println_ts = util.println_ts +-- check environment (allows Pocket or CraftOS-PC) +if not _is_pocket_env then + println("You can only use this application on a pocket computer.") + return +end + ---------------------------------------- -- get configuration ---------------------------------------- From def5b49327386622c065e682a367fe9d5511b27f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 13 Jun 2024 21:43:56 -0400 Subject: [PATCH 60/87] #496 threaded app loading --- pocket/iocontrol.lua | 216 +--------------------------- pocket/pocket.lua | 231 ++++++++++++++++++++++++++++++ pocket/startup.lua | 10 +- pocket/threads.lua | 29 ++-- pocket/ui/apps/diag_apps.lua | 6 +- pocket/ui/apps/dummy_app.lua | 5 +- pocket/ui/apps/guide.lua | 37 +++-- pocket/ui/apps/sys_apps.lua | 5 +- pocket/ui/apps/unit.lua | 60 +++++--- pocket/ui/docs.lua | 2 +- pocket/ui/main.lua | 12 +- pocket/ui/pages/guide_section.lua | 6 + pocket/ui/pages/home_page.lua | 8 +- 13 files changed, 352 insertions(+), 275 deletions(-) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 669891b..1d2b77c 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -29,225 +29,17 @@ local LINK_STATE = { iocontrol.LINK_STATE = LINK_STATE ----@enum POCKET_APP_ID -local APP_ID = { - ROOT = 1, - -- main app page - UNITS = 2, - GUIDE = 3, - ABOUT = 4, - -- diag app page - ALARMS = 5, - -- other - DUMMY = 6, - NUM_APPS = 6 -} - -iocontrol.APP_ID = APP_ID - ---@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 = {}, - help_map = {}, - help_return = nil, - 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 - - function io.nav.set_sidebar(sidebar) - self.sidebar = sidebar - 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 = { - loaded = false, - load = nil, - cur_page = nil, ---@type nav_tree_page - pane = pane, - paned_pages = {}, - sidebar_items = {} - } - - app.load = function () app.loaded = true end - - -- 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 - - function app.set_sidebar(items) - app.sidebar_items = items - if self.sidebar then self.sidebar.update(items) end - end - - -- function to run on initial load into memory - ---@param on_load function callback - function app.set_on_load(on_load) - app.load = function () - on_load() - app.loaded = true - end - 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 and app.cur_page == nil then - app.cur_page = page - 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 - - -- open a given app - ---@param app_id POCKET_APP_ID - function io.nav.open_app(app_id) - -- reset help return on navigating out of an app - if app_id == APP_ID.ROOT then self.help_return = nil end - - local app = self.apps[app_id] ---@type pocket_app - if app then - if not app.loaded then app.load() end - - self.cur_app = app_id - self.pane.set_value(app_id) - - if #app.sidebar_items > 0 then - self.sidebar.update(app.sidebar_items) - end - else - log.debug("tried to open unknown app") - end - end - - -- get a list of the app containers (usually Div elements) - function io.nav.get_containers() return self.containers 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() - -- return out of help if opened with open_help - if self.help_return then - io.nav.open_app(self.help_return) - self.help_return = nil - return - end - - 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 - - -- open the help app, to show the reference for a key - function io.nav.open_help(key) - self.help_return = self.cur_app - - io.nav.open_app(APP_ID.GUIDE) - - local load = self.help_map[key] - if load then load() end - end - - function io.nav.link_help(map) self.help_map = map end -end - -- initialize facility-independent components of pocket iocontrol ---@param comms pocket_comms -function iocontrol.init_core(comms) - iocontrol.alloc_nav() +---@param nav pocket_nav +function iocontrol.init_core(comms, nav) + io.nav = nav ---@class pocket_ioctl_diag io.diag = {} @@ -858,7 +650,7 @@ function iocontrol.record_unit_data(data) --#endregion - --#region Advanced Alarm Information Display + --#region Status Information Display local ecam = {} -- aviation reference :) back to VATSIM I go... diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 88aafb3..6cc66d1 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -14,6 +14,18 @@ local LINK_STATE = iocontrol.LINK_STATE local pocket = {} +local MQ__RENDER_CMD = { + UNLOAD_SV_APPS = 1, + UNLOAD_API_APPS = 2 +} + +local MQ__RENDER_DATA = { + LOAD_APP = 1 +} + +pocket.MQ__RENDER_CMD = MQ__RENDER_CMD +pocket.MQ__RENDER_DATA = MQ__RENDER_DATA + ---@type pkt_config local config = {} @@ -63,6 +75,225 @@ function pocket.load_config() return cfv.valid() end +---@enum POCKET_APP_ID +local APP_ID = { + ROOT = 1, + -- main app pages + UNITS = 2, + GUIDE = 3, + ABOUT = 4, + -- diag app page + ALARMS = 5, + -- other + DUMMY = 6, + NUM_APPS = 6 +} + +pocket.APP_ID = APP_ID + +---@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 +---@param render_queue mqueue +function pocket.init_nav(render_queue) + local self = { + pane = nil, ---@type graphics_element + apps = {}, + containers = {}, + help_map = {}, + help_return = nil, + cur_app = APP_ID.ROOT + } + + self.cur_page = self.root + + ---@class pocket_nav + local nav = {} + + -- set the root pane element to switch between apps with + ---@param root_pane graphics_element + function nav.set_pane(root_pane) + self.pane = root_pane + end + + function nav.set_sidebar(sidebar) + self.sidebar = sidebar + 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 nav.register_app(app_id, container, pane) + ---@class pocket_app + local app = { + loaded = false, + load = nil, + cur_page = nil, ---@type nav_tree_page + pane = pane, + paned_pages = {}, + sidebar_items = {} + } + + app.load = function () app.loaded = true end + + -- 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 + + function app.set_sidebar(items) + app.sidebar_items = items + if self.sidebar then self.sidebar.update(items) end + end + + -- function to run on initial load into memory + ---@param on_load function callback + function app.set_on_load(on_load) + app.load = function () + on_load() + app.loaded = true + end + 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 and app.cur_page == nil then + app.cur_page = page + 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 + + -- open a given app + ---@param app_id POCKET_APP_ID + function nav.open_app(app_id) + -- reset help return on navigating out of an app + if app_id == APP_ID.ROOT then self.help_return = nil end + + local app = self.apps[app_id] ---@type pocket_app + if app then + if not app.loaded then render_queue.push_data(MQ__RENDER_DATA.LOAD_APP, app_id) end + + self.cur_app = app_id + self.pane.set_value(app_id) + + if #app.sidebar_items > 0 then + self.sidebar.update(app.sidebar_items) + end + else + log.debug("tried to open unknown app") + end + end + + -- load a given app + ---@param app_id POCKET_APP_ID + function nav.load_app(app_id) + self.apps[app_id].load() + end + + -- get a list of the app containers (usually Div elements) + function nav.get_containers() return self.containers end + + -- get the currently active page + ---@return nav_tree_page + function nav.get_current_page() + return self.apps[self.cur_app].get_current_page() + end + + -- attempt to navigate up + function nav.nav_up() + -- return out of help if opened with open_help + if self.help_return then + nav.open_app(self.help_return) + self.help_return = nil + return + end + + 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") + nav.open_app(APP_ID.ROOT) + end + end + + -- open the help app, to show the reference for a key + function nav.open_help(key) + self.help_return = self.cur_app + + nav.open_app(APP_ID.GUIDE) + + local load = self.help_map[key] + if load then load() end + end + + -- link the help map from the guide app + function nav.link_help(map) self.help_map = map end + + return nav +end + -- pocket coordinator + supervisor communications ---@nodiscard ---@param version string pocket version diff --git a/pocket/startup.lua b/pocket/startup.lua index 6ce61a2..a55d376 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -103,7 +103,8 @@ local function main() nic = nil, ---@type nic pocket_comms = nil, ---@type pocket_comms sv_wd = nil, ---@type watchdog - api_wd = nil ---@type watchdog + api_wd = nil, ---@type watchdog + nav = nil ---@type pocket_nav }, -- message queues @@ -118,7 +119,7 @@ local function main() local pkt_state = __shared_memory.pkt_state ---------------------------------------- - -- setup communications & clocks + -- setup system ---------------------------------------- -- message authentication init @@ -147,8 +148,9 @@ local function main() smem_sys.pocket_comms = pocket.comms(POCKET_VERSION, smem_sys.nic, smem_sys.sv_wd, smem_sys.api_wd) log.debug("startup> comms init") - -- init I/O control - iocontrol.init_core(smem_sys.pocket_comms) + -- init nav and I/O handler + smem_sys.nav = pocket.init_nav(__shared_memory.q.mq_render) + iocontrol.init_core(smem_sys.pocket_comms, smem_sys.nav) ---------------------------------------- -- start the UI diff --git a/pocket/threads.lua b/pocket/threads.lua index 22a4576..6c241f7 100644 --- a/pocket/threads.lua +++ b/pocket/threads.lua @@ -4,7 +4,7 @@ local ppm = require("scada-common.ppm") local tcd = require("scada-common.tcd") local util = require("scada-common.util") -local iocontrol = require("pocket.iocontrol") +local pocket = require("pocket.pocket") local renderer = require("pocket.renderer") local core = require("graphics.core") @@ -14,14 +14,8 @@ local threads = {} local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks) local RENDER_SLEEP = 100 -- (100ms, 2 ticks) -local MQ__RENDER_CMD = { - UNLOAD_SV_APPS = 1, - UNLOAD_API_APPS = 2 -} - -local MQ__RENDER_DATA = { - LOAD_APP = 1 -} +local MQ__RENDER_CMD = pocket.MQ__RENDER_CMD +local MQ__RENDER_DATA = pocket.MQ__RENDER_DATA -- main thread ---@nodiscard @@ -44,15 +38,13 @@ function threads.thread__main(smem) local pocket_comms = smem.pkt_sys.pocket_comms local sv_wd = smem.pkt_sys.sv_wd local api_wd = smem.pkt_sys.api_wd + local nav = smem.pkt_sys.nav -- start connection watchdogs sv_wd.feed() api_wd.feed() log.debug("startup> conn watchdogs started") - local io_db = iocontrol.get_db() - local nav = io_db.nav - -- event loop while true do local event, param1, param2, param3, param4, param5 = util.pull_event() @@ -152,9 +144,12 @@ function threads.thread__render(smem) -- load in from shared memory local pkt_state = smem.pkt_state local render_queue = smem.q.mq_render + local nav = smem.pkt_sys.nav local last_update = util.time() + local ui_message + -- thread loop while true do -- check for messages in the message queue @@ -172,6 +167,16 @@ function threads.thread__render(smem) local cmd = msg.message ---@type queue_data if cmd.key == MQ__RENDER_DATA.LOAD_APP then + log.debug("RENDER: load app " .. cmd.val) + + local draw_start = util.time_ms() + + pkt_state.ui_ok, ui_message = pcall(function () nav.load_app(cmd.val) end) + if not pkt_state.ui_ok then + log.fatal(util.c("RENDER: app load failed with error ", ui_message)) + else + log.debug("RENDER: app loaded in " .. (util.time_ms() - draw_start) .. "ms") + end end elseif msg.qtype == mqueue.TYPE.PACKET then -- received a packet diff --git a/pocket/ui/apps/diag_apps.lua b/pocket/ui/apps/diag_apps.lua index ec8b5e6..be63e95 100644 --- a/pocket/ui/apps/diag_apps.lua +++ b/pocket/ui/apps/diag_apps.lua @@ -3,6 +3,7 @@ -- local iocontrol = require("pocket.iocontrol") +local pocket = require("pocket.pocket") local core = require("graphics.core") @@ -15,9 +16,10 @@ local Checkbox = require("graphics.elements.controls.checkbox") local PushButton = require("graphics.elements.controls.push_button") local SwitchButton = require("graphics.elements.controls.switch_button") +local ALIGN = core.ALIGN local cpair = core.cpair -local ALIGN = core.ALIGN +local APP_ID = pocket.APP_ID -- create diagnostic app pages ---@param root graphics_element parent @@ -30,7 +32,7 @@ local function create_pages(root) local alarm_test = Div{parent=root,x=1,y=1} - local alarm_app = db.nav.register_app(iocontrol.APP_ID.ALARMS, alarm_test) + local alarm_app = db.nav.register_app(APP_ID.ALARMS, alarm_test) local page = alarm_app.new_page(nil, function () end) page.tasks = { db.diag.tone_test.get_tone_states } diff --git a/pocket/ui/apps/dummy_app.lua b/pocket/ui/apps/dummy_app.lua index d7845a7..6e92493 100644 --- a/pocket/ui/apps/dummy_app.lua +++ b/pocket/ui/apps/dummy_app.lua @@ -3,12 +3,15 @@ -- local iocontrol = require("pocket.iocontrol") +local pocket = require("pocket.pocket") local core = require("graphics.core") local Div = require("graphics.elements.div") local TextBox = require("graphics.elements.textbox") +local APP_ID = pocket.APP_ID + -- create placeholder app page ---@param root graphics_element parent local function create_pages(root) @@ -16,7 +19,7 @@ local function create_pages(root) local main = Div{parent=root,x=1,y=1} - db.nav.register_app(iocontrol.APP_ID.DUMMY, main).new_page(nil, function () end) + db.nav.register_app(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} diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index 35868d8..61e5884 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -3,13 +3,12 @@ -- local util = require("scada-common.util") --- local log = require("scada-common.log") local iocontrol = require("pocket.iocontrol") -local TextField = require("graphics.elements.form.text_field") +local pocket = require("pocket.pocket") local docs = require("pocket.ui.docs") -local style = require("pocket.ui.style") +-- local style = require("pocket.ui.style") local guide_section = require("pocket.ui.pages.guide_section") @@ -20,33 +19,44 @@ local ListBox = require("graphics.elements.listbox") local MultiPane = require("graphics.elements.multipane") local TextBox = require("graphics.elements.textbox") +local WaitingAnim = require("graphics.elements.animations.waiting") + local PushButton = require("graphics.elements.controls.push_button") +local TextField = require("graphics.elements.form.text_field") + local ALIGN = core.ALIGN local cpair = core.cpair -local label = style.label --- local lu_col = style.label_unit_pair -local text_fg = style.text_fg +local APP_ID = pocket.APP_ID + +-- local label = style.label +-- local lu_col = style.label_unit_pair +-- local text_fg = style.text_fg -- new system guide view ---@param root graphics_element parent local function new_view(root) local db = iocontrol.get_db() - local main = Div{parent=root,x=1,y=1} + local frame = Div{parent=root,x=1,y=1} - local app = db.nav.register_app(iocontrol.APP_ID.GUIDE, main) + local app = db.nav.register_app(APP_ID.GUIDE, frame) - TextBox{parent=main,y=2,text="Guide",height=1,alignment=ALIGN.CENTER} - TextBox{parent=main,y=4,text="Loading...",height=1,alignment=ALIGN.CENTER} + local load_div = Div{parent=frame,x=1,y=1} + local main = Div{parent=frame,x=1,y=1} + + TextBox{parent=load_div,y=12,text="Loading...",height=1,alignment=ALIGN.CENTER} + WaitingAnim{parent=load_div,x=math.floor(main.get_width()/2)-1,y=8,fg_bg=cpair(colors.cyan,colors._INHERIT)} + + local load_pane = MultiPane{parent=main,x=1,y=1,panes={load_div,main}} local btn_fg_bg = cpair(colors.cyan, colors.black) local btn_active = cpair(colors.white, colors.black) local btn_disable = cpair(colors.gray, colors.black) local list = { - { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(iocontrol.APP_ID.ROOT) end }, + { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end }, { label = " \x14 ", color = core.cpair(colors.black, colors.cyan), callback = function () app.switcher(1) end }, { label = "__?", color = core.cpair(colors.black, colors.lightGray), callback = function () app.switcher(2) end } } @@ -142,6 +152,8 @@ local function new_view(root) TextBox{parent=search_results,text="Click 'GO' to search..."} + util.nop() + TextBox{parent=use,y=1,text="System Usage",height=1,alignment=ALIGN.CENTER} PushButton{parent=use,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} @@ -202,6 +214,9 @@ local function new_view(root) -- link help resources db.nav.link_help(doc_map) + + -- done, show the app + load_pane.set_value(2) end app.set_on_load(load) diff --git a/pocket/ui/apps/sys_apps.lua b/pocket/ui/apps/sys_apps.lua index aa20a8d..85f8ee0 100644 --- a/pocket/ui/apps/sys_apps.lua +++ b/pocket/ui/apps/sys_apps.lua @@ -19,9 +19,10 @@ local TextBox = require("graphics.elements.textbox") local PushButton = require("graphics.elements.controls.push_button") +local ALIGN = core.ALIGN local cpair = core.cpair -local ALIGN = core.ALIGN +local APP_ID = pocket.APP_ID -- create system app pages ---@param root graphics_element parent @@ -34,7 +35,7 @@ local function create_pages(root) 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_app = db.nav.register_app(APP_ID.ABOUT, about_root) local about_page = about_app.new_page(nil, 1) local nt_page = about_app.new_page(about_page, 2) diff --git a/pocket/ui/apps/unit.lua b/pocket/ui/apps/unit.lua index a6f28b3..6c39785 100644 --- a/pocket/ui/apps/unit.lua +++ b/pocket/ui/apps/unit.lua @@ -2,34 +2,39 @@ -- Unit Overview Page -- -local util = require("scada-common.util") --- local log = require("scada-common.log") +local util = require("scada-common.util") +-- local log = require("scada-common.log") -local iocontrol = require("pocket.iocontrol") +local iocontrol = require("pocket.iocontrol") +local pocket = require("pocket.pocket") -local style = require("pocket.ui.style") +local style = require("pocket.ui.style") -local boiler = require("pocket.ui.pages.unit_boiler") -local reactor = require("pocket.ui.pages.unit_reactor") -local turbine = require("pocket.ui.pages.unit_turbine") +local boiler = require("pocket.ui.pages.unit_boiler") +local reactor = require("pocket.ui.pages.unit_reactor") +local turbine = require("pocket.ui.pages.unit_turbine") -local core = require("graphics.core") +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 Div = require("graphics.elements.div") +local ListBox = require("graphics.elements.listbox") +local MultiPane = require("graphics.elements.multipane") +local TextBox = require("graphics.elements.textbox") -local DataIndicator = require("graphics.elements.indicators.data") -local IconIndicator = require("graphics.elements.indicators.icon") --- local RadIndicator = require("graphics.elements.indicators.rad") --- local VerticalBar = require("graphics.elements.indicators.vbar") +local WaitingAnim = require("graphics.elements.animations.waiting") -local PushButton = require("graphics.elements.controls.push_button") +local DataIndicator = require("graphics.elements.indicators.data") +local IconIndicator = require("graphics.elements.indicators.icon") +-- local RadIndicator = require("graphics.elements.indicators.rad") +-- local VerticalBar = require("graphics.elements.indicators.vbar") + +local PushButton = require("graphics.elements.controls.push_button") local ALIGN = core.ALIGN local cpair = core.cpair +local APP_ID = pocket.APP_ID + -- local label = style.label local lu_col = style.label_unit_pair local text_fg = style.text_fg @@ -49,11 +54,17 @@ local emc_ind_s = { local function new_view(root) local db = iocontrol.get_db() - local main = Div{parent=root,x=1,y=1} + local frame = Div{parent=root,x=1,y=1} - local app = db.nav.register_app(iocontrol.APP_ID.UNITS, main) + local app = db.nav.register_app(APP_ID.UNITS, frame) - TextBox{parent=main,y=2,text="Units App",height=1,alignment=ALIGN.CENTER} + local load_div = Div{parent=frame,x=1,y=1} + local main = Div{parent=frame,x=1,y=1} + + TextBox{parent=load_div,y=12,text="Loading...",height=1,alignment=ALIGN.CENTER} + WaitingAnim{parent=load_div,x=math.floor(main.get_width()/2)-1,y=8,fg_bg=cpair(colors.yellow,colors._INHERIT)} + + local load_pane = MultiPane{parent=main,x=1,y=1,panes={load_div,main}} TextBox{parent=main,y=4,text="Loading...",height=1,alignment=ALIGN.CENTER} @@ -66,7 +77,7 @@ local function new_view(root) local unit = db.units[id] ---@type pioctl_unit local list = { - { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(iocontrol.APP_ID.ROOT) end }, + { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end }, { label = "U-" .. id, color = core.cpair(colors.black, colors.yellow), callback = function () app.switcher(id) end }, { label = " \x13 ", color = core.cpair(colors.black, colors.red), callback = nav_links[id].alarm }, { label = "RPS", tall = true, color = core.cpair(colors.black, colors.cyan), callback = nav_links[id].rps }, @@ -174,6 +185,8 @@ local function new_view(root) --#endregion + util.nop() + --#region Alarms Tab local alm_div = Div{parent=page_div} @@ -349,6 +362,8 @@ local function new_view(root) end --#endregion + + util.nop() end -- setup multipane @@ -356,6 +371,9 @@ local function new_view(root) app.set_root_pane(u_pane) set_sidebar(active_unit) + + -- done, show the app + load_pane.set_value(2) end app.set_on_load(load) diff --git a/pocket/ui/docs.lua b/pocket/ui/docs.lua index 936b2e8..eed9777 100644 --- a/pocket/ui/docs.lua +++ b/pocket/ui/docs.lua @@ -102,7 +102,7 @@ doc("G_Fault", "Fault", "Something has gone wrong and/or failed to function.") doc("G_FrontPanel", "Front Panel", "A basic interface on the front of a device for viewing and sometimes modifying its state. This is what you see when looking at a computer running one of the SCADA applications.") doc("G_Nominal", "Nominal", "Normal operation. Everything operating as intended.") doc("G_Ringback", "Ringback", "An indication that an alarm had gone off so that you are aware, even if the alarm condition is no longer met.") -doc("G_SCRAM", "SCRAM", "[Emergency] shut-down of a reactor by stopping the fission reactor. In Mekanism and here, it isn't always for an emergency.") +doc("G_SCRAM", "SCRAM", "[Emergency] shut-down of a reactor by stopping the fission. In Mekanism and here, it isn't always for an emergency.") doc("G_Transient", "Transient", "A temporary change in state from normal operation. Coolant levels dropping or core temperature rising above nominal values would be examples of transients.") doc("G_Trip", "Trip", "A checked condition has occurred, also known as 'tripped'.") doc("G_Tripped", "Tripped", "An alarm condition has been met and is still met.") diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index 9f33e1f..83e5716 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -5,6 +5,7 @@ local util = require("scada-common.util") local iocontrol = require("pocket.iocontrol") +local pocket = require("pocket.pocket") local diag_apps = require("pocket.ui.apps.diag_apps") local dummy_app = require("pocket.ui.apps.dummy_app") @@ -29,11 +30,12 @@ local Sidebar = require("graphics.elements.controls.sidebar") local SignalBar = require("graphics.elements.indicators.signal") +local ALIGN = core.ALIGN +local cpair = core.cpair + local LINK_STATE = iocontrol.LINK_STATE -local ALIGN = core.ALIGN - -local cpair = core.cpair +local APP_ID = pocket.APP_ID -- create new main view ---@param main graphics_element main displaybox @@ -82,14 +84,14 @@ local function init(main) diag_apps(page_div) dummy_app(page_div) - assert(util.table_len(db.nav.get_containers()) == iocontrol.APP_ID.NUM_APPS, "app IDs were not sequential or some apps weren't registered") + assert(util.table_len(db.nav.get_containers()) == APP_ID.NUM_APPS, "app IDs were not sequential or some apps weren't registered") db.nav.set_pane(MultiPane{parent=page_div,x=1,y=1,panes=db.nav.get_containers()}) db.nav.set_sidebar(Sidebar{parent=main_pane,x=1,y=1,height=18,fg_bg=cpair(colors.white,colors.gray)}) 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} - db.nav.open_app(iocontrol.APP_ID.ROOT) + db.nav.open_app(APP_ID.ROOT) --#endregion end diff --git a/pocket/ui/pages/guide_section.lua b/pocket/ui/pages/guide_section.lua index 1cbc8ec..a9e9596 100644 --- a/pocket/ui/pages/guide_section.lua +++ b/pocket/ui/pages/guide_section.lua @@ -1,3 +1,5 @@ +local util = require("scada-common.util") + local core = require("graphics.core") local Div = require("graphics.elements.div") @@ -53,7 +55,11 @@ return function (data, base_page, title, items, scroll_height) table.insert(search_db, { string.lower(item.name), item.name, title, view }) PushButton{parent=name_list,text=item.name,alignment=ALIGN.LEFT,fg_bg=cpair(colors.blue,colors.black),active_fg_bg=btn_active,callback=view} + + if i % 12 == 0 then util.nop() end end + util.nop() + return section_page end diff --git a/pocket/ui/pages/home_page.lua b/pocket/ui/pages/home_page.lua index 8854614..4b7fded 100644 --- a/pocket/ui/pages/home_page.lua +++ b/pocket/ui/pages/home_page.lua @@ -3,6 +3,7 @@ -- local iocontrol = require("pocket.iocontrol") +local pocket = require("pocket.pocket") local core = require("graphics.core") @@ -12,11 +13,10 @@ local TextBox = require("graphics.elements.textbox") local App = require("graphics.elements.controls.app") +local ALIGN = core.ALIGN local cpair = core.cpair -local APP_ID = iocontrol.APP_ID - -local ALIGN = core.ALIGN +local APP_ID = pocket.APP_ID -- new home page view ---@param root graphics_element parent @@ -25,7 +25,7 @@ local function new_view(root) local main = Div{parent=root,x=1,y=1,height=19} - local app = db.nav.register_app(iocontrol.APP_ID.ROOT, main) + local app = db.nav.register_app(APP_ID.ROOT, main) local apps_1 = Div{parent=main,x=1,y=1,height=15} local apps_2 = Div{parent=main,x=1,y=1,height=15} From 0b97d4d4b0b655324b37f0d0c1cf30e4fffce573 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 13 Jun 2024 21:52:13 -0400 Subject: [PATCH 61/87] updated header message --- pocket/ui/main.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index 83e5716..40b1104 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -43,7 +43,7 @@ local function init(main) local db = iocontrol.get_db() -- window header message - TextBox{parent=main,y=1,text="WIP ALPHA APP S C ",alignment=ALIGN.LEFT,height=1,fg_bg=style.header} + TextBox{parent=main,y=1,text="EARLY ACCESS ALPHA 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)} From 00cacd6d0af79013f10a65000a1e86eec74df11c Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 14 Jun 2024 16:10:04 +0000 Subject: [PATCH 62/87] #497 unload apps when required connections are lost --- pocket/pocket.lua | 63 +++++++++++++++++++++++++++++++++------- pocket/startup.lua | 7 +++-- pocket/threads.lua | 16 +++++----- pocket/ui/apps/guide.lua | 36 ++++++++++++++++++----- pocket/ui/apps/unit.lua | 26 ++++++++++++++--- 5 files changed, 114 insertions(+), 34 deletions(-) diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 6cc66d1..ab3783a 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -102,7 +102,8 @@ pocket.APP_ID = APP_ID ---@param render_queue mqueue function pocket.init_nav(render_queue) local self = { - pane = nil, ---@type graphics_element + pane = nil, ---@type graphics_element + sidebar = nil, ---@type graphics_element apps = {}, containers = {}, help_map = {}, @@ -117,23 +118,22 @@ function pocket.init_nav(render_queue) -- set the root pane element to switch between apps with ---@param root_pane graphics_element - function nav.set_pane(root_pane) - self.pane = root_pane - end + function nav.set_pane(root_pane) self.pane = root_pane end - function nav.set_sidebar(sidebar) - self.sidebar = sidebar - end + -- link sidebar element + ---@param sidebar graphics_element + function nav.set_sidebar(sidebar) self.sidebar = sidebar 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 nav.register_app(app_id, container, pane) + ---@param require_sv boolean? true or false/nil otherwise to specifiy if this app should be unloaded when the supervisor connection is lost + ---@param require_api boolean? true or false/nil otherwise to specifiy if this app should be unloaded when the api connection is lost + function nav.register_app(app_id, container, pane, require_sv, require_api) ---@class pocket_app local app = { loaded = false, - load = nil, cur_page = nil, ---@type nav_tree_page pane = pane, paned_pages = {}, @@ -141,6 +141,11 @@ function pocket.init_nav(render_queue) } app.load = function () app.loaded = true end + app.unload = function () app.loaded = false end + + -- check which connections this requires + ---@return boolean requires_sv, boolean requires_api + function app.check_requires() return require_sv or false, require_api or false end -- delayed set of the pane if it wasn't ready at the start ---@param root_pane graphics_element multipane @@ -155,13 +160,22 @@ function pocket.init_nav(render_queue) -- function to run on initial load into memory ---@param on_load function callback - function app.set_on_load(on_load) + function app.set_load(on_load) app.load = function () on_load() app.loaded = true end end + -- function to run to close out the app + ---@param on_unload function callback + function app.set_unload(on_unload) + app.unload = function () + on_unload() + app.loaded = false + end + end + -- if a pane was provided, this will switch between numbered pages ---@param idx integer page index function app.switcher(idx) @@ -207,6 +221,12 @@ function pocket.init_nav(render_queue) return page end + -- delete paned pages and clear the current page + function app.delete_pages() + app.paned_pages = {} + app.cur_page = nil + end + -- get the currently active page function app.get_current_page() return app.cur_page end @@ -251,6 +271,22 @@ function pocket.init_nav(render_queue) self.apps[app_id].load() end + -- unload api-dependent apps + function nav.unload_api() + for _, app in pairs(self.apps) do + local _, api = app.check_requires() + if app.loaded and api then app.unload() end + end + end + + -- unload supervisor-dependent apps + function nav.unload_sv() + for _, app in pairs(self.apps) do + local sv, _ = app.check_requires() + if app.loaded and sv then app.unload() end + end + end + -- get a list of the app containers (usually Div elements) function nav.get_containers() return self.containers end @@ -300,7 +336,8 @@ end ---@param nic nic network interface device ---@param sv_watchdog watchdog ---@param api_watchdog watchdog -function pocket.comms(version, nic, sv_watchdog, api_watchdog) +---@param nav pocket_nav +function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) local self = { sv = { linked = false, @@ -399,6 +436,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) -- close connection to the supervisor function public.close_sv() sv_watchdog.cancel() + nav.unload_sv() self.sv.linked = false self.sv.r_seq_num = nil self.sv.addr = comms.BROADCAST @@ -408,6 +446,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) -- close connection to coordinator API server function public.close_api() api_watchdog.cancel() + nav.unload_api() self.api.linked = false self.api.r_seq_num = nil self.api.addr = comms.BROADCAST @@ -589,6 +628,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) elseif packet.type == MGMT_TYPE.CLOSE then -- handle session close api_watchdog.cancel() + nav.unload_api() self.api.linked = false self.api.r_seq_num = nil self.api.addr = comms.BROADCAST @@ -694,6 +734,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog) elseif packet.type == MGMT_TYPE.CLOSE then -- handle session close sv_watchdog.cancel() + nav.unload_sv() self.sv.linked = false self.sv.r_seq_num = nil self.sv.addr = comms.BROADCAST diff --git a/pocket/startup.lua b/pocket/startup.lua index a55d376..0d2be64 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -122,6 +122,8 @@ local function main() -- setup system ---------------------------------------- + smem_sys.nav = pocket.init_nav(__shared_memory.q.mq_render) + -- message authentication init if type(config.AuthKey) == "string" and string.len(config.AuthKey) > 0 then network.init_mac(config.AuthKey) @@ -145,11 +147,10 @@ local function main() -- create network interface then setup comms smem_sys.nic = network.nic(smem_dev.modem) - smem_sys.pocket_comms = pocket.comms(POCKET_VERSION, smem_sys.nic, smem_sys.sv_wd, smem_sys.api_wd) + smem_sys.pocket_comms = pocket.comms(POCKET_VERSION, smem_sys.nic, smem_sys.sv_wd, smem_sys.api_wd, smem_sys.nav) log.debug("startup> comms init") - -- init nav and I/O handler - smem_sys.nav = pocket.init_nav(__shared_memory.q.mq_render) + -- init I/O control iocontrol.init_core(smem_sys.pocket_comms, smem_sys.nav) ---------------------------------------- diff --git a/pocket/threads.lua b/pocket/threads.lua index 6c241f7..5e05940 100644 --- a/pocket/threads.lua +++ b/pocket/threads.lua @@ -1,13 +1,13 @@ -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local ppm = require("scada-common.ppm") -local tcd = require("scada-common.tcd") -local util = require("scada-common.util") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local ppm = require("scada-common.ppm") +local tcd = require("scada-common.tcd") +local util = require("scada-common.util") -local pocket = require("pocket.pocket") -local renderer = require("pocket.renderer") +local pocket = require("pocket.pocket") +local renderer = require("pocket.renderer") -local core = require("graphics.core") +local core = require("graphics.core") local threads = {} diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index 61e5884..2fea06a 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -55,16 +55,21 @@ local function new_view(root) local btn_active = cpair(colors.white, colors.black) local btn_disable = cpair(colors.gray, colors.black) - local list = { - { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end }, - { label = " \x14 ", color = core.cpair(colors.black, colors.cyan), callback = function () app.switcher(1) end }, - { label = "__?", color = core.cpair(colors.black, colors.lightGray), callback = function () app.switcher(2) end } - } + app.set_sidebar({{ label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end }}) - app.set_sidebar(list) + local page_div = nil ---@type nil|graphics_element + -- load the app (create the elements) local function load() - local page_div = Div{parent=main,y=2} + local list = { + { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end }, + { label = " \x14 ", color = core.cpair(colors.black, colors.cyan), callback = function () app.switcher(1) end }, + { label = "__?", color = core.cpair(colors.black, colors.lightGray), callback = function () app.switcher(2) end } + } + + app.set_sidebar(list) + + page_div = Div{parent=main,y=2} local p_width = page_div.get_width() - 2 local main_page = app.new_page(nil, 1) @@ -219,7 +224,22 @@ local function new_view(root) load_pane.set_value(2) end - app.set_on_load(load) + -- delete the elements and switch back to the loading screen + local function unload() + if page_div then + page_div.delete() + page_div = nil + end + + app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end } }) + app.delete_pages() + + -- show loading screen + load_pane.set_value(1) + end + + app.set_load(load) + app.set_unload(unload) return main end diff --git a/pocket/ui/apps/unit.lua b/pocket/ui/apps/unit.lua index 6c39785..8a11d88 100644 --- a/pocket/ui/apps/unit.lua +++ b/pocket/ui/apps/unit.lua @@ -56,7 +56,7 @@ local function new_view(root) local frame = Div{parent=root,x=1,y=1} - local app = db.nav.register_app(APP_ID.UNITS, frame) + local app = db.nav.register_app(APP_ID.UNITS, frame, nil, false, true) local load_div = Div{parent=frame,x=1,y=1} local main = Div{parent=frame,x=1,y=1} @@ -66,13 +66,15 @@ local function new_view(root) local load_pane = MultiPane{parent=main,x=1,y=1,panes={load_div,main}} - TextBox{parent=main,y=4,text="Loading...",height=1,alignment=ALIGN.CENTER} + app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end } }) local btn_fg_bg = cpair(colors.yellow, colors.black) local btn_active = cpair(colors.white, colors.black) local nav_links = {} + local page_div = nil ---@type nil|graphics_element + -- set sidebar to display unit-specific fields based on a specified unit local function set_sidebar(id) local unit = db.units[id] ---@type pioctl_unit @@ -96,8 +98,9 @@ local function new_view(root) app.set_sidebar(list) end + -- load the app (create the elements) local function load() - local page_div = Div{parent=main,y=2,width=main.get_width()} + page_div = Div{parent=main,y=2,width=main.get_width()} local panes = {} @@ -376,7 +379,22 @@ local function new_view(root) load_pane.set_value(2) end - app.set_on_load(load) + -- delete the elements and switch back to the loading screen + local function unload() + if page_div then + page_div.delete() + page_div = nil + end + + app.set_sidebar({ { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end } }) + app.delete_pages() + + -- show loading screen + load_pane.set_value(1) + end + + app.set_load(load) + app.set_unload(unload) return main end From 697a3d6f6b7275dfd3532b66bb6e7b5263cd0f33 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 14 Jun 2024 16:14:24 +0000 Subject: [PATCH 63/87] luacheck fixes --- pocket/iocontrol.lua | 2 +- pocket/startup.lua | 2 +- pocket/ui/pages/guide_section.lua | 2 +- pocket/ui/pages/unit_reactor.lua | 11 +++++------ pocket/ui/pages/unit_turbine.lua | 4 ++-- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 1d2b77c..3d52e68 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -3,7 +3,7 @@ -- local const = require("scada-common.constants") -local log = require("scada-common.log") +-- local log = require("scada-common.log") local psil = require("scada-common.psil") local types = require("scada-common.types") local util = require("scada-common.util") diff --git a/pocket/startup.lua b/pocket/startup.lua index 0d2be64..33832da 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -3,7 +3,7 @@ -- ---@diagnostic disable-next-line: undefined-global -local _is_pocket_env = pocket or periphemu +local _is_pocket_env = pocket ~= nil or periphemu ~= nil require("/initenv").init_env() diff --git a/pocket/ui/pages/guide_section.lua b/pocket/ui/pages/guide_section.lua index a9e9596..63e2333 100644 --- a/pocket/ui/pages/guide_section.lua +++ b/pocket/ui/pages/guide_section.lua @@ -36,7 +36,7 @@ return function (data, base_page, title, items, scroll_height) local name_list = ListBox{parent=section_div,x=1,y=3,scroll_height=30,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} local def_list = ListBox{parent=section_view_div,x=1,y=3,scroll_height=scroll_height,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} - local _end = nil + local _end for i = 1, #items do local item = items[i] ---@type pocket_doc_item diff --git a/pocket/ui/pages/unit_reactor.lua b/pocket/ui/pages/unit_reactor.lua index 6990e92..7446d78 100644 --- a/pocket/ui/pages/unit_reactor.lua +++ b/pocket/ui/pages/unit_reactor.lua @@ -20,12 +20,11 @@ local PushButton = require("graphics.elements.controls.push_button") local ALIGN = core.ALIGN local cpair = core.cpair -local label = style.label -local lu_col = style.label_unit_pair -local text_fg = style.text_fg -local mode_states = style.icon_states.mode_states -local red_ind_s = style.icon_states.red_ind_s -local yel_ind_s = style.icon_states.yel_ind_s +local label = style.label +local lu_col = style.label_unit_pair +local text_fg = style.text_fg +local red_ind_s = style.icon_states.red_ind_s +local yel_ind_s = style.icon_states.yel_ind_s -- create a reactor view in the unit app ---@param app pocket_app diff --git a/pocket/ui/pages/unit_turbine.lua b/pocket/ui/pages/unit_turbine.lua index b4d9a2e..5a7763b 100644 --- a/pocket/ui/pages/unit_turbine.lua +++ b/pocket/ui/pages/unit_turbine.lua @@ -109,8 +109,8 @@ return function (app, u_page, panes, tbn_pane, u_id, t_id, ps, update) local rotation = DataIndicator{parent=tbn_ext_div,x=1,y=10,lu_colors=lu_col,label="",unit="",format="%21.12f",value=0,width=21,fg_bg=text_fg} rotation.register(ps, "steam", function () - local status, result = pcall(function () return util.turbine_rotation(db.units[u_id].turbine_data_tbl[t_id]) end) - if status then rotation.update(result) end + local ok, result = pcall(function () return util.turbine_rotation(db.units[u_id].turbine_data_tbl[t_id]) end) + if ok then rotation.update(result) end end) return tbn_page.nav_to From fb1f85a626c25e553fabf1c66f43c78d2def3f66 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 14 Jun 2024 16:16:41 +0000 Subject: [PATCH 64/87] possible luacheck suppression --- pocket/startup.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pocket/startup.lua b/pocket/startup.lua index 33832da..f88c2e6 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -2,8 +2,11 @@ -- SCADA System Access on a Pocket Computer -- + +-- luacheck: no undefined ---@diagnostic disable-next-line: undefined-global -local _is_pocket_env = pocket ~= nil or periphemu ~= nil +local _is_pocket_env = pocket or periphemu +-- luacheck: undefined require("/initenv").init_env() From 14736e414fd99190232635542cd7eb8d8292a5d9 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 14 Jun 2024 16:20:53 +0000 Subject: [PATCH 65/87] luacheck ignore --- pocket/startup.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pocket/startup.lua b/pocket/startup.lua index f88c2e6..03eefec 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -2,11 +2,8 @@ -- SCADA System Access on a Pocket Computer -- - --- luacheck: no undefined ---@diagnostic disable-next-line: undefined-global -local _is_pocket_env = pocket or periphemu --- luacheck: undefined +local _is_pocket_env = pocket or periphemu -- luacheck: ignore pocket require("/initenv").init_env() From c66ad44adb7c103d66f30a67e3aba700649cdc45 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 14 Jun 2024 16:32:25 +0000 Subject: [PATCH 66/87] pocket cleanup --- pocket/pocket.lua | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/pocket/pocket.lua b/pocket/pocket.lua index ab3783a..f32ef1a 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -127,9 +127,9 @@ function pocket.init_nav(render_queue) -- 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 - ---@param require_sv boolean? true or false/nil otherwise to specifiy if this app should be unloaded when the supervisor connection is lost - ---@param require_api boolean? true or false/nil otherwise to specifiy if this app should be unloaded when the api connection is lost + ---@param pane? graphics_element multipane if this is a simple paned app, then nav_to must be a number + ---@param require_sv? boolean true to specifiy if this app should be unloaded when the supervisor connection is lost + ---@param require_api? boolean true to specifiy if this app should be unloaded when the api connection is lost function nav.register_app(app_id, container, pane, require_sv, require_api) ---@class pocket_app local app = { @@ -153,6 +153,8 @@ function pocket.init_nav(render_queue) app.pane = root_pane end + -- configure the sidebar + ---@param items table function app.set_sidebar(items) app.sidebar_items = items if self.sidebar then self.sidebar.update(items) end @@ -185,7 +187,7 @@ function pocket.init_nav(render_queue) 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 parent nav_tree_page|nil 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) @@ -244,7 +246,7 @@ function pocket.init_nav(render_queue) return app end - -- open a given app + -- open an app ---@param app_id POCKET_APP_ID function nav.open_app(app_id) -- reset help return on navigating out of an app @@ -296,7 +298,8 @@ function pocket.init_nav(render_queue) return self.apps[self.cur_app].get_current_page() end - -- attempt to navigate up + -- attempt to navigate up within the active app, otherwise open home page
+ -- except, this will go back to a prior app if leaving the help app after open_help was used function nav.nav_up() -- return out of help if opened with open_help if self.help_return then From 87a91e309d315e46d9be4a94b7b633c4721d0d96 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 14 Jun 2024 21:09:14 +0000 Subject: [PATCH 67/87] #403 guide updates --- pocket/ui/docs.lua | 111 +++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 55 deletions(-) diff --git a/pocket/ui/docs.lua b/pocket/ui/docs.lua index eed9777..40507a1 100644 --- a/pocket/ui/docs.lua +++ b/pocket/ui/docs.lua @@ -8,21 +8,23 @@ local function doc(key, name, desc) table.insert(target, item) end +-- important to note in the future: The PLC should always be in a chunk with the reactor to ensure it can protect it on chunk load if you do not keep it all chunk loaded + docs.alarms = {} target = docs.alarms -doc("ContainmentBreach", "Containment Breach", "Reactor disconnected or indicated unformed while being at or above 100% damage; explosion likely occurred.") +doc("ContainmentBreach", "Containment Breach", "Reactor disconnected or indicated unformed while being at or above 100% damage; explosion assumed.") doc("ContainmentRadiation", "Containment Radiation", "Environment detector(s) assigned to the unit have observed high levels of radiation.") doc("ReactorLost", "Reactor Lost", "Reactor PLC has stopped communicating with the supervisor.") -doc("CriticalDamage", "Damage Critical", "Reactor damage has reached or exceeded 100%, so it may explode at any moment.") -doc("ReactorDamage", "Reactor Damage", "Reactor temperature causing increasing damage to reactor casing.") -doc("ReactorOverTemp", "Reactor Over Temp", "Reactor temperature is at or above maximum safe temperature and is now taking damage.") +doc("CriticalDamage", "Damage Critical", "Reactor damage has reached or exceeded 100%, so it will explode at any moment.") +doc("ReactorDamage", "Reactor Damage", "Reactor temperature causing increasing damage to the reactor casing.") +doc("ReactorOverTemp", "Reactor Over Temp", "Reactor temperature is at or above maximum safe temperature, so it is now taking damage.") doc("ReactorHighTemp", "Reactor High Temp", "Reactor temperature is above expected operating levels and may exceed maximum safe temperature soon.") -doc("ReactorWasteLeak", "Reactor Waste Leak", "The reactor is full of spent waste and will now emit radiation if additional waste is generated.") +doc("ReactorWasteLeak", "Reactor Waste Leak", "The reactor is full of spent waste so it will now emit radiation if additional waste is generated.") doc("ReactorHighWaste", "Reactor High Waste", "Reactor waste levels are high and may leak soon.") doc("RPSTransient", "RPS Transient", "Reactor protection system was activated.") -doc("RCSTransient", "RCS Transient", "Something is wrong with the reactor coolant system, check RCS indicators.") -doc("TurbineTripAlarm", "Turbine Trip", "A turbine stopped rotating, likely due to having full energy storage.") +doc("RCSTransient", "RCS Transient", "Something is wrong with the reactor coolant system, check RCS indicators for details.") +doc("TurbineTripAlarm", "Turbine Trip", "A turbine stopped rotating, likely due to having full energy storage. This will prevent cooling, so it needs to be resolved before using that unit.") docs.annunc = { unit = { @@ -31,69 +33,68 @@ docs.annunc = { } target = docs.annunc.unit.main_section -doc("PLCOnline", "PLC Online", "Indicates if the fission reactor PLC is connected.") -doc("PLCHeartbeat", "PLC Heartbeat", "An indicator of status data being live. As status messages are received from the PLC, this light will turn on and off. If it gets stuck, the supervisor has stopped receiving data.") -doc("RadiationMonitor", "Radiation Monitor", "Indicates if at least once environment detector is connected and assigned to this unit.") -doc("AutoControl", "Automatic Control", "Indicates if the reactor is under the control of one of the automatic control modes.") -doc("ReactorSCRAM", "Reactor SCRAM", "Indicates if the reactor protection system is holding the reactor SCRAM'd.") -doc("ManualReactorSCRAM", "Manual Reactor SCRAM", "Indicates if the operator (you) initiated a SCRAM.") -doc("AutoReactorSCRAM", "Auto Reactor SCRAM", "Indicates if the automatic control system initiated a SCRAM. The main view screen will have an indicator as to why.") -doc("RadiationWarning", "Radiation Warning", "Indicates if radiation levels are above normal. There is likely a leak somewhere, so that should be identified and fixed.") -doc("RCPTrip", "RCP Trip", "Reactor coolant pump tripped. This is a technical concept not directly mapping to mekansim, so in this case it indicates if there is either high heated coolant or low cooled coolant causing an RPS trip. Check the coolant system if this occurs.") -doc("RCSFlowLow", "RCS Flow Low", "Indicates if the reactor coolant system flow is low. This is observed when the cooled coolant level in the reactor is dropping. This can occur while a turbine spins up, but if it persists, check that the cooling system is operating properly.") -doc("CoolantLevelLow", "Coolant Level Low", "Indicates if the reactor coolant level is lower than it should be. Check the coolant system.") -doc("ReactorTempHigh", "Reactor Temp. High", "Indicates if the reactor temperature is above expected maximum operating temperature. This is not yet damaging, but should be attended to. Check coolant system.") -doc("ReactorHighDeltaT", "Reactor High Delta T", "Indicates if the reactor temperature is climbing rapidly. This can occur when a reactor is starting up, but it is a concern if it happens uncontrolled while the burn rate is not increasing.") -doc("FuelInputRateLow", "Fuel Input Rate Low", "Indicates if the fissile fuel levels in the reactor are dropping or are very low. Ensure a steady supply of fuel is entering the reactor.") +doc("PLCOnline", "PLC Online", "Indicates if the fission reactor PLC is connected. If it isn't, check that your PLC is on and configured properly.") +doc("PLCHeartbeat", "PLC Heartbeat", "An indicator of status data being live. As status messages are received from the PLC, this light will turn on and off. If it gets stuck, the supervisor has stopped receiving data or a screen has frozen.") +doc("RadiationMonitor", "Radiation Monitor", "On if at least one environment detector is connected and assigned to this unit.") +doc("AutoControl", "Automatic Control", "On if the reactor is under the control of one of the automatic control modes.") +doc("ReactorSCRAM", "Reactor SCRAM", "On if the reactor protection system is holding the reactor SCRAM'd.") +doc("ManualReactorSCRAM", "Manual Reactor SCRAM", "On if the operator (you) initiated a SCRAM.") +doc("AutoReactorSCRAM", "Auto Reactor SCRAM", "On if the automatic control system initiated a SCRAM. The main view screen annunciator will have an indication as to why.") +doc("RadiationWarning", "Radiation Warning", "On if radiation levels are above normal. There is likely a leak somewhere, so that should be identified and fixed. Hazmat suit recommended.") +doc("RCPTrip", "RCP Trip", "Reactor coolant pump tripped. This is a technical concept not directly mapping to Mekansim. Here, it indicates if there is either high heated coolant or low cooled coolant that caused an RPS trip. Check the coolant system if this occurs.") +doc("RCSFlowLow", "RCS Flow Low", "Indicates if the reactor coolant system flow is low. This is observed when the cooled coolant level in the reactor is dropping. This can occur while a turbine spins up, but if it persists, check that the cooling system is operating properly. This can occur with smaller boilers or when using pipes and not having enough.") +doc("CoolantLevelLow", "Coolant Level Low", "On if the reactor coolant level is lower than it should be. Check the coolant system.") +doc("ReactorTempHigh", "Reactor Temp. High", "On if the reactor temperature is above expected maximum operating temperature. This is not yet damaging, but should be attended to. Check coolant system.") +doc("ReactorHighDeltaT", "Reactor High Delta T", "On if the reactor temperature is climbing rapidly. This can occur when a reactor is starting up, but it is a concern if it happens while the burn rate is not increasing.") +doc("FuelInputRateLow", "Fuel Input Rate Low", "On if the fissile fuel levels in the reactor are dropping or very low. Ensure a steady supply of fuel is entering the reactor.") doc("WasteLineOcclusion", "Waste Line Occlusion", "Waste levels in the reactor are increasing. Ensure your waste processing system is operating at a sufficient rate for your burn rate.") -doc("HighStartupRate", "Startup Rate High", "This is a rough calculation of if your burn rate is high enough to cause a loss of coolant. A burn rate above this is likely to cause that, but it could occur at even higher or even lower rates depending on your setup (such as pipes, water supplies, and boiler tanks).") +doc("HighStartupRate", "Startup Rate High", "This is a rough calculation of if your burn rate is high enough to cause a loss of coolant on startup. A burn rate above this is likely to cause that, but it could occur at even higher or even lower rates depending on your setup (such as pipes, water supplies, and boiler tanks).") target = docs.annunc.unit.rps_section doc("rps_tripped", "RPS Trip", "Indicates if the reactor protection system has caused a SCRAM.") doc("manual", "Manual Reactor SCRAM", "Indicates if the operator (you) tripped the RPS by pressing SCRAM.") doc("automatic", "Auto Reactor SCRAM", "Indicates if the automatic control system tripped the RPS.") -doc("sys_fail", "System Failure", "Indicates if the RPS tripped due to the reactor not being formed.") -doc("high_dmg", "Damage Level High", "Indicates if the RPS tripped due to significant reactor damage.") -doc("ex_waste", "Excess Waste", "Indicates if the RPS tripped due to very high waste levels.") -doc("ex_hcool", "Excess Heated Coolant", "Indicates if the RPS tripped due to very high heated coolant levels.") -doc("high_temp", "Temperature High", "Indicates if the RPS tripped due to reaching damaging temperatures.") -doc("low_cool", "Coolant Level Low Low", "Indicates if the RPS tripped due to very low coolant levels that result in the temperature uncontrollably rising.") -doc("no_fuel", "No Fuel", "Indicates if the RPS tripped due to no fuel being available.") -doc("fault", "PPM Fault", "Indicates if the RPS tripped due to a peripheral access fault.") -doc("timeout", "Connection Timeout", "Indicates if the RPS tripped due to losing connection with the supervisory computer.") -doc("sys_fail", "System Failure", "Indicates if the RPS tripped due to the reactor not being formed.") +doc("high_dmg", "Damage Level High", "Indicates if the RPS tripped due to significant reactor damage. Await damage levels to lower.") +doc("ex_waste", "Excess Waste", "Indicates if the RPS tripped due to very high waste levels. Ensure waste processing system is keeping up.") +doc("ex_hcool", "Excess Heated Coolant", "Indicates if the RPS tripped due to very high heated coolant levels. Check that the cooling system is able to keep up with heated coolant flow.") +doc("high_temp", "Temperature High", "Indicates if the RPS tripped due to reaching damaging temperatures. Await damage levels to lower.") +doc("low_cool", "Coolant Level Low Low", "Indicates if the RPS tripped due to very low coolant levels that result in the temperature uncontrollably rising. Ensure that the cooling system can provide sufficient cooled coolant flow.") +doc("no_fuel", "No Fuel", "Indicates if the RPS tripped due to no fuel being available. Check fuel input.") +doc("fault", "PPM Fault", "Indicates if the RPS tripped due to a peripheral access fault. Something went wrong interfacing with the reactor, try restarting the PLC.") +doc("timeout", "Connection Timeout", "Indicates if the RPS tripped due to losing connection with the supervisory computer. Check that your PLC and supervisor remain chunk loaded.") +doc("sys_fail", "System Failure", "Indicates if the RPS tripped due to the reactor not being formed. Ensure that the multi-block is formed.") target = docs.annunc.unit.rcs_section -doc("RCSFault", "RCS Hardware Fault", "Indicates if one or more of the RCS devices have a peripheral fault.") -doc("EmergencyCoolant", "Emergency Coolant", "Off if no emergency coolant redstone is configured, white when it is configured but not in use, and green/blue when it is activated.") -doc("CoolantFeedMismatch", "Coolant Feed Mismatch", "The coolant system is accumulating heated coolant or losing cooled coolant, likely due to one of the machines not keeping up with the needs of the reactor.") -doc("BoilRateMismatch", "Boil Rate Mismatch", "The total heating rate of the reactor is more than 4% off from the steam input rate of the turbines OR for sodium setups, the boiler boil rates are more than 4% off from the steam input rate of the turbines.") -doc("SteamFeedMismatch", "Steam Feed Mismatch", "There is an above tolerance difference between turbine flow and steam input rates or the reactor/boilers are gaining steam or losing water.") -doc("MaxWaterReturnFeed", "Max Water Return Feed", "The turbines are condensing the max rate of water that they can per the structure build.") -doc("WaterLevelLow", "Water Level Low", "The water level in the boiler is low.") -doc("HeatingRateLow", "Heating Rate Low", "The boiler is not hot enough to boil water, but it is receiving heated coolant.") -doc("SteamDumpOpen", "Steam Relief Valve Open", "This turns yellow if the turbine is set to dumping excess and red if it is set to dumping all. 'Relief Valve' in this case is that setting allowing the venting of steam.") +doc("RCSFault", "RCS Hardware Fault", "Indicates if one or more of the RCS devices have a peripheral fault. Check that your machines are formed. If this persists, try rebooting affected RTUs.") +doc("EmergencyCoolant", "Emergency Coolant", "Off if no emergency coolant redstone is configured, white when it is configured but not in use, and green/blue when it is activated. This is based on an RTU having a redstone emergency coolant output configured for this unit.") +doc("CoolantFeedMismatch", "Coolant Feed Mismatch", "The coolant system is accumulating heated coolant or losing cooled coolant, likely due to one of the machines not keeping up with the needs of the reactor. The flow monitor can help figure out where the problem is.") +doc("BoilRateMismatch", "Boil Rate Mismatch", "The total heating rate of the reactor exceed the tolerance from the steam input rate of the turbines OR for sodium setups, the boiler boil rates exceed the tolerance from the steam input rate of the turbines. The flow monitor can help figure out where the problem is.") +doc("SteamFeedMismatch", "Steam Feed Mismatch", "There is an above tolerance difference between turbine flow and steam input rates or the reactor/boilers are gaining steam or losing water. The flow monitor can help figure out where the problem is.") +doc("MaxWaterReturnFeed", "Max Water Return Feed", "The turbines are condensing the max rate of water that they can per the structure build. If water return is insufficient, add more saturating condensers to your turbine(s).") +doc("WaterLevelLow", "Water Level Low", "The water level in the boiler is low. A larger boiler water tank may help, or you can feed additional water into the boiler from elsewhere.") +doc("HeatingRateLow", "Heating Rate Low", "The boiler is not hot enough to boil water, but it is receiving heated coolant. This is almost never a safety concern.") +doc("SteamDumpOpen", "Steam Relief Valve Open", "This turns yellow if the turbine is set to dumping excess and red if it is set to dumping [all]. 'Relief Valve' in this case is that setting allowing the venting of steam. You should never have this set to dumping [all]. Emergency coolant activation from the supervisor will automatically set it to dumping excess to ensure there is no backup of steam as water is added.") doc("TurbineOverSpeed", "Turbine Over Speed", "The turbine is at steam capacity, but not tripped. You may need more turbines if they can't keep up.") -doc("GeneratorTrip", "Generator Trip", "The turbine is no longer outputting power due to it having nowhere to go. Likely due to full power storage.") -doc("TurbineTrip", "Turbine Trip", "The turbine has reached its maximum power charge and has stopped rotating, and as a result stopped cooling steam to water.") +doc("GeneratorTrip", "Generator Trip", "The turbine is no longer outputting power due to it having nowhere to go. Likely due to full power storage. This will lead to a Turbine Trip if not addressed.") +doc("TurbineTrip", "Turbine Trip", "The turbine has reached its maximum power charge and has stopped rotating, and as a result stopped cooling steam to water. Ensure the turbine has somewhere to output power, as this is the most common cause of reactor meltdowns. However, the likelihood of a meltdown with this system in place is much lower, especially with emergency coolant helping during turbine trips.") docs.glossary = { abbvs = {}, terms = {} } target = docs.glossary.abbvs -doc("G_ACK", "ACK", "Alarm ACKnowledge. This indicates you understand an alarm occured and would like to stop the audio tone(s).") +doc("G_ACK", "ACK", "Alarm ACKnowledge. Pressing this acknowledges that you understand an alarm occurred and would like to stop the audio tone(s).") doc("G_CRD", "CRD", "Coordinator. Abbreviation for the coordinator computer.") doc("G_DBG", "DBG", "Debug. Abbreviation for the debugging sessions from pocket computers found on the supervisor's front panel.") doc("G_FP", "FP", "Front Panel. See Terminology.") doc("G_PKT", "PKT", "Pocket. Abbreviation for the pocket computer.") -doc("G_PLC", "PLC", "Programmable Logic Controller. A device that not only reports data and controls outputs, but also can make decisions on its own.") +doc("G_PLC", "PLC", "Programmable Logic Controller. A device that not only reports data and controls outputs, but can also make decisions on its own.") doc("G_PPM", "PPM", "Protected Peripheral Manager. This is an abstraction layer created for this project that prevents peripheral calls from crashing applications.") -doc("G_RCP", "RCP", "Reactor Coolant Pump. This is from real-world terminology with water-cooled reactors, but in this system it just relates to the functioning of reactor coolant flow.") -doc("G_RCS", "RCS", "Reactor Cooling System. The combination of all machines used to cool the reactor.") +doc("G_RCP", "RCP", "Reactor Coolant Pump. This is from real-world terminology with water-cooled (boiling water and pressurized water) reactors, but in this system it just reflects to the functioning of reactor coolant flow. See the annunciator page on it for more information.") +doc("G_RCS", "RCS", "Reactor Cooling System. The combination of all machines used to cool the reactor (turbines, boilers, dynamic tanks).") doc("G_RPS", "RPS", "Reactor Protection System. A component of the reactor PLC responsible for keeping the reactor safe.") -doc("G_RTU", "RTU", "Remote Terminal Unit. Provides monitoring to and basic output from a SCADA system, interfacing with various types of devices/controls.") -doc("G_SCADA", "SCADA", "Supervisory Control and Data Acquisition. A control systems architecture used in many different process control applications.") +doc("G_RTU", "RTU", "Remote Terminal Unit. Provides monitoring to and basic output from a SCADA system, interfacing with various types of devices/interfaces.") +doc("G_SCADA", "SCADA", "Supervisory Control and Data Acquisition. A control systems architecture used in a wide variety process control applications.") doc("G_SVR", "SVR", "Supervisor. Abbreviation for the supervisory computer.") doc("G_UI", "UI", "User Interface.") @@ -101,12 +102,12 @@ target = docs.glossary.terms doc("G_Fault", "Fault", "Something has gone wrong and/or failed to function.") doc("G_FrontPanel", "Front Panel", "A basic interface on the front of a device for viewing and sometimes modifying its state. This is what you see when looking at a computer running one of the SCADA applications.") doc("G_Nominal", "Nominal", "Normal operation. Everything operating as intended.") -doc("G_Ringback", "Ringback", "An indication that an alarm had gone off so that you are aware, even if the alarm condition is no longer met.") +doc("G_Ringback", "Ringback", "An indication that an alarm had gone off but is no longer having its trip condition(s) met. This is to make you are aware that it occurred.") doc("G_SCRAM", "SCRAM", "[Emergency] shut-down of a reactor by stopping the fission. In Mekanism and here, it isn't always for an emergency.") -doc("G_Transient", "Transient", "A temporary change in state from normal operation. Coolant levels dropping or core temperature rising above nominal values would be examples of transients.") -doc("G_Trip", "Trip", "A checked condition has occurred, also known as 'tripped'.") -doc("G_Tripped", "Tripped", "An alarm condition has been met and is still met.") -doc("G_Tripping", "Tripping", "An alarm condition is met but has not met the minimum time before a condition is deemed a problem.") -doc("G_TurbineTrip", "Turbine Trip", "The turbine stopped, which prevents heated coolant from being properly cooled. In Mekanism, this would occur when a turbine cannot generate any more energy due to filling its buffer and having no output with any storage for energy left.") +doc("G_Transient", "Transient", "A temporary change in state from normal operation. Coolant levels dropping or core temperature rising above nominal values are examples of transients.") +doc("G_Trip", "Trip", "A checked condition had occurred, see 'Tripped'.") +doc("G_Tripped", "Tripped", "An alarm condition has been met, and is still met.") +doc("G_Tripping", "Tripping", "Alarm condition(s) is/are met, but has/have not reached the minimum time before the condition(s) is/are deemed a problem.") +doc("G_TurbineTrip", "Turbine Trip", "The turbine stopped, which prevents heated coolant from being cooled. In Mekanism, this would occur when a turbine cannot generate any more energy due to filling its buffer and having no output with any remaining energy capacity.") return docs From 1c719ad67b45286d6b324fc2777523adb5563662 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Fri, 14 Jun 2024 21:10:42 +0000 Subject: [PATCH 68/87] cleanup for pull request --- graphics/elements/textbox.lua | 1 - pocket/iocontrol.lua | 19 +++++-------------- pocket/threads.lua | 4 ++-- pocket/ui/apps/guide.lua | 4 ++-- pocket/ui/apps/unit.lua | 7 ++----- pocket/ui/pages/guide_section.lua | 4 ++-- pocket/ui/pages/unit_boiler.lua | 26 +++++++++++++------------- pocket/ui/pages/unit_reactor.lua | 4 ++-- pocket/ui/pages/unit_turbine.lua | 11 +++++------ 9 files changed, 33 insertions(+), 47 deletions(-) diff --git a/graphics/elements/textbox.lua b/graphics/elements/textbox.lua index 0761471..2a61860 100644 --- a/graphics/elements/textbox.lua +++ b/graphics/elements/textbox.lua @@ -29,7 +29,6 @@ local function textbox(args) if args.anchor == true then args.can_focus = true end - -- regex to identify entries without a height currently: ^.*TextBox\{((?!height=).)*$ -- provide a constraint condition to element creation to prevent an pointlessly tall text box ---@param frame graphics_frame local function constrain(frame) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 3d52e68..da9c0d7 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -489,7 +489,7 @@ function iocontrol.record_unit_data(data) local anc = unit.annunciator rcs_hazard = rcs_hazard or anc.RCPTrip rcs_warn = rcs_warn or anc.RCSFlowLow or anc.CoolantLevelLow or anc.RCSFault or anc.MaxWaterReturnFeed or - anc.CoolantFeedMismatch or anc.BoilRateMismatch or anc.SteamFeedMismatch or anc.MaxWaterReturnFeed + anc.CoolantFeedMismatch or anc.BoilRateMismatch or anc.SteamFeedMismatch local rcs_status = 4 if rcs_hazard then @@ -530,7 +530,7 @@ function iocontrol.record_unit_data(data) -- update reactor/control status if unit.reactor_data.mek_status.status then reactor_status = 4 - reactor_state = 5 -- running + reactor_state = 5 -- running control_status = util.trinary(unit.annunciator.AutoControl, 4, 3) else if unit.reactor_data.no_reactor then @@ -706,18 +706,12 @@ function iocontrol.record_unit_data(data) end if tripped(unit.alarms[ALARM.ReactorDamage]) then - local items = { - white("REACTOR DAMAGED"), blue("CHECK RCS"), blue("AWAIT DMG REDUCED") - } - + local items = { white("REACTOR DAMAGED"), blue("CHECK RCS"), blue("AWAIT DMG REDUCED") } table.insert(ecam, { color = colors.red, text = "REACTOR DAMAGE", help = "ReactorDamage", items = items }) end if tripped(unit.alarms[ALARM.ReactorOverTemp]) then - local items = { - white("DAMAGING TEMP"), blue("CHECK RCS"), blue("AWAIT COOLDOWN") - } - + local items = { white("DAMAGING TEMP"), blue("CHECK RCS"), blue("AWAIT COOLDOWN") } table.insert(ecam, { color = colors.red, text = "REACTOR OVER TEMP", help = "ReactorOverTemp", items = items }) end @@ -728,7 +722,6 @@ function iocontrol.record_unit_data(data) if tripped(unit.alarms[ALARM.ReactorWasteLeak]) then local items = { white("AT WASTE CAPACITY"), blue("CHECK WASTE OUTPUT"), blue("KEEP RCT DISABLED") } - table.insert(ecam, { color = colors.red, text = "REACTOR WASTE LEAK", help = "ReactorWasteLeak", items = items}) end @@ -741,9 +734,7 @@ function iocontrol.record_unit_data(data) local items = {} local stat = unit.reactor_data.rps_status - -- for k, _ in pairs(stat) do - -- stat[k] = true - -- end + -- for k, _ in pairs(stat) do stat[k] = true end local function insert(cond, key, text, color) if cond[key] then table.insert(items, { text = text, help = key, color = color }) end end diff --git a/pocket/threads.lua b/pocket/threads.lua index 5e05940..8cdb7ff 100644 --- a/pocket/threads.lua +++ b/pocket/threads.lua @@ -80,7 +80,7 @@ function threads.thread__main(smem) 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" or - event == "double_click" then + event == "double_click" then -- handle a mouse event renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3)) elseif event == "char" or event == "key" or event == "key_up" then @@ -143,8 +143,8 @@ function threads.thread__render(smem) -- load in from shared memory local pkt_state = smem.pkt_state - local render_queue = smem.q.mq_render local nav = smem.pkt_sys.nav + local render_queue = smem.q.mq_render local last_update = util.time() diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index 2fea06a..bda49a7 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -84,7 +84,7 @@ local function new_view(root) local use = Div{parent=page_div,x=2,width=p_width} local uis = Div{parent=page_div,x=2,width=p_width} local fps = Div{parent=page_div,x=2,width=p_width} - local gls = Div{parent=page_div,x=2} + local gls = Div{parent=page_div,x=2,width=p_width} local panes = { home, search, use, uis, fps, gls } local doc_map = {} @@ -118,7 +118,7 @@ local function new_view(root) search_results.remove_all() if string.len(query) < 3 then - TextBox{parent=search_results,text=util.trinary(string.len(query)==0,"Click 'GO' to search...","Search requires at least 3 characters.")} + TextBox{parent=search_results,text="Search requires at least 3 characters."} return end diff --git a/pocket/ui/apps/unit.lua b/pocket/ui/apps/unit.lua index 8a11d88..1481a2b 100644 --- a/pocket/ui/apps/unit.lua +++ b/pocket/ui/apps/unit.lua @@ -3,7 +3,6 @@ -- local util = require("scada-common.util") --- local log = require("scada-common.log") local iocontrol = require("pocket.iocontrol") local pocket = require("pocket.pocket") @@ -23,12 +22,10 @@ local TextBox = require("graphics.elements.textbox") local WaitingAnim = require("graphics.elements.animations.waiting") +local PushButton = require("graphics.elements.controls.push_button") + local DataIndicator = require("graphics.elements.indicators.data") local IconIndicator = require("graphics.elements.indicators.icon") --- local RadIndicator = require("graphics.elements.indicators.rad") --- local VerticalBar = require("graphics.elements.indicators.vbar") - -local PushButton = require("graphics.elements.controls.push_button") local ALIGN = core.ALIGN local cpair = core.cpair diff --git a/pocket/ui/pages/guide_section.lua b/pocket/ui/pages/guide_section.lua index 63e2333..77941a8 100644 --- a/pocket/ui/pages/guide_section.lua +++ b/pocket/ui/pages/guide_section.lua @@ -27,7 +27,7 @@ return function (data, base_page, title, items, scroll_height) TextBox{parent=section_div,y=1,text=title,height=1,alignment=ALIGN.CENTER} PushButton{parent=section_div,x=3,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=base_page.nav_to} - local gls_term_view_page = app.new_page(section_page, #panes + 1) + local view_page = app.new_page(section_page, #panes + 1) local section_view_div = Div{parent=page_div,x=2} table.insert(panes, section_view_div) TextBox{parent=section_view_div,y=1,text=title,height=1,alignment=ALIGN.CENTER} @@ -47,7 +47,7 @@ return function (data, base_page, title, items, scroll_height) local function view() _end.focus() - gls_term_view_page.nav_to() + view_page.nav_to() anchor.focus() end diff --git a/pocket/ui/pages/unit_boiler.lua b/pocket/ui/pages/unit_boiler.lua index e15ae40..86b963a 100644 --- a/pocket/ui/pages/unit_boiler.lua +++ b/pocket/ui/pages/unit_boiler.lua @@ -1,21 +1,21 @@ -local types = require("scada-common.types") -local util = require("scada-common.util") +local types = require("scada-common.types") +local util = require("scada-common.util") -local iocontrol = require("pocket.iocontrol") +local iocontrol = require("pocket.iocontrol") -local style = require("pocket.ui.style") +local style = require("pocket.ui.style") -local core = require("graphics.core") +local core = require("graphics.core") -local Div = require("graphics.elements.div") -local TextBox = require("graphics.elements.textbox") +local Div = require("graphics.elements.div") +local TextBox = require("graphics.elements.textbox") -local DataIndicator = require("graphics.elements.indicators.data") +local PushButton = require("graphics.elements.controls.push_button") + +local DataIndicator = require("graphics.elements.indicators.data") local StateIndicator = require("graphics.elements.indicators.state") -local IconIndicator = require("graphics.elements.indicators.icon") -local VerticalBar = require("graphics.elements.indicators.vbar") - -local PushButton = require("graphics.elements.controls.push_button") +local IconIndicator = require("graphics.elements.indicators.icon") +local VerticalBar = require("graphics.elements.indicators.vbar") local ALIGN = core.ALIGN local cpair = core.cpair @@ -69,7 +69,7 @@ return function (app, u_page, panes, blr_pane, b_id, ps, update) temp.register(ps, "temperature", function (t) temp.update(db.temp_convert(t)) end) local b_wll = IconIndicator{parent=blr_div,y=10,label="Water Level Lo",states=red_ind_s} - local b_hr = IconIndicator{parent=blr_div,label="Heating Rate Lo",states=yel_ind_s} + local b_hr = IconIndicator{parent=blr_div,label="Heating Rate Lo",states=yel_ind_s} b_wll.register(ps, "WaterLevelLow", b_wll.update) b_hr.register(ps, "HeatingRateLow", b_hr.update) diff --git a/pocket/ui/pages/unit_reactor.lua b/pocket/ui/pages/unit_reactor.lua index 7446d78..b7fb23f 100644 --- a/pocket/ui/pages/unit_reactor.lua +++ b/pocket/ui/pages/unit_reactor.lua @@ -10,13 +10,13 @@ local core = require("graphics.core") local Div = require("graphics.elements.div") local TextBox = require("graphics.elements.textbox") +local PushButton = require("graphics.elements.controls.push_button") + local DataIndicator = require("graphics.elements.indicators.data") local StateIndicator = require("graphics.elements.indicators.state") local IconIndicator = require("graphics.elements.indicators.icon") local VerticalBar = require("graphics.elements.indicators.vbar") -local PushButton = require("graphics.elements.controls.push_button") - local ALIGN = core.ALIGN local cpair = core.cpair diff --git a/pocket/ui/pages/unit_turbine.lua b/pocket/ui/pages/unit_turbine.lua index 5a7763b..03cd865 100644 --- a/pocket/ui/pages/unit_turbine.lua +++ b/pocket/ui/pages/unit_turbine.lua @@ -9,14 +9,14 @@ local core = require("graphics.core") local Div = require("graphics.elements.div") local TextBox = require("graphics.elements.textbox") +local PushButton = require("graphics.elements.controls.push_button") + local DataIndicator = require("graphics.elements.indicators.data") local IconIndicator = require("graphics.elements.indicators.icon") local PowerIndicator = require("graphics.elements.indicators.power") local StateIndicator = require("graphics.elements.indicators.state") local VerticalBar = require("graphics.elements.indicators.vbar") -local PushButton = require("graphics.elements.controls.push_button") - local ALIGN = core.ALIGN local cpair = core.cpair @@ -70,16 +70,15 @@ return function (app, u_page, panes, tbn_pane, u_id, t_id, ps, update) input_rate.register(ps, "steam_input_rate", input_rate.update) local t_sdo = IconIndicator{parent=tbn_div,y=10,label="Steam Dumping",states=tri_ind_s} - local t_tos = IconIndicator{parent=tbn_div,label="Over Speed",states=red_ind_s} - local t_gtrp = IconIndicator{parent=tbn_div,label="Generator Trip",states=yel_ind_s} - local t_trp = IconIndicator{parent=tbn_div,label="Turbine Trip",states=red_ind_s} + local t_tos = IconIndicator{parent=tbn_div,label="Over Speed",states=red_ind_s} + local t_gtrp = IconIndicator{parent=tbn_div,label="Generator Trip",states=yel_ind_s} + local t_trp = IconIndicator{parent=tbn_div,label="Turbine Trip",states=red_ind_s} t_sdo.register(ps, "SteamDumpOpen", t_sdo.update) t_tos.register(ps, "TurbineOverSpeed", t_tos.update) t_gtrp.register(ps, "GeneratorTrip", t_gtrp.update) t_trp.register(ps, "TurbineTrip", t_trp.update) - local tbn_ext_div = Div{parent=tbn_pane,x=2,width=tbn_pane.get_width()-2} table.insert(panes, tbn_ext_div) From ea8f62dea63d6e2779674e1240ecb4c41889dd56 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 14 Jun 2024 17:38:45 -0400 Subject: [PATCH 69/87] #497 exit app if it is unloaded --- pocket/pocket.lua | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pocket/pocket.lua b/pocket/pocket.lua index f32ef1a..0afc068 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -275,17 +275,23 @@ function pocket.init_nav(render_queue) -- unload api-dependent apps function nav.unload_api() - for _, app in pairs(self.apps) do + for id, app in pairs(self.apps) do local _, api = app.check_requires() - if app.loaded and api then app.unload() end + if app.loaded and api then + if id == self.cur_app then nav.open_app(APP_ID.ROOT) end + app.unload() + end end end -- unload supervisor-dependent apps function nav.unload_sv() - for _, app in pairs(self.apps) do + for id, app in pairs(self.apps) do local sv, _ = app.check_requires() - if app.loaded and sv then app.unload() end + if app.loaded and sv then + if id == self.cur_app then nav.open_app(APP_ID.ROOT) end + app.unload() + end end end From 219f02b1882cf9b46f9c980bb32161e4983fc8d0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 14 Jun 2024 17:42:03 -0400 Subject: [PATCH 70/87] print render crash cause to user --- pocket/startup.lua | 5 +++++ pocket/threads.lua | 6 ++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pocket/startup.lua b/pocket/startup.lua index 03eefec..f597779 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -90,6 +90,7 @@ local function main() ---@class pkt_state pkt_state = { ui_ok = false, + ui_error = nil, shutdown = false }, @@ -179,6 +180,10 @@ local function main() parallel.waitForAll(main_thread.p_exec, render_thread.p_exec) renderer.close_ui() + + if not pkt_state.ui_ok then + println(util.c("App crashed with error: ", pkt_state.ui_error)) + end else println_ts("UI creation failed") end diff --git a/pocket/threads.lua b/pocket/threads.lua index 8cdb7ff..32120b2 100644 --- a/pocket/threads.lua +++ b/pocket/threads.lua @@ -148,8 +148,6 @@ function threads.thread__render(smem) local last_update = util.time() - local ui_message - -- thread loop while true do -- check for messages in the message queue @@ -171,9 +169,9 @@ function threads.thread__render(smem) local draw_start = util.time_ms() - pkt_state.ui_ok, ui_message = pcall(function () nav.load_app(cmd.val) end) + pkt_state.ui_ok, pkt_state.ui_error = pcall(function () nav.load_app(cmd.val) end) if not pkt_state.ui_ok then - log.fatal(util.c("RENDER: app load failed with error ", ui_message)) + log.fatal(util.c("RENDER: app load failed with error ", pkt_state.ui_error)) else log.debug("RENDER: app loaded in " .. (util.time_ms() - draw_start) .. "ms") end From 9fe0669fda44a5813de97b58207f41249b522da8 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 14 Jun 2024 17:49:43 -0400 Subject: [PATCH 71/87] updated guide section heights and added a debug message to track height usage --- pocket/startup.lua | 2 +- pocket/ui/apps/guide.lua | 4 ++-- pocket/ui/pages/guide_section.lua | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pocket/startup.lua b/pocket/startup.lua index f597779..b56da4f 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -182,7 +182,7 @@ local function main() renderer.close_ui() if not pkt_state.ui_ok then - println(util.c("App crashed with error: ", pkt_state.ui_error)) + println(util.c("UI crashed with error: ", pkt_state.ui_error)) end else println_ts("UI creation failed") diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index bda49a7..b174805 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -185,9 +185,9 @@ local function new_view(root) TextBox{parent=annunc_div,y=1,text="Annunciators",height=1,alignment=ALIGN.CENTER} PushButton{parent=annunc_div,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=uis_page.nav_to} - local unit_gen_page = guide_section(sect_construct_data, annunc_page, "Unit General", docs.annunc.unit.main_section, 200) + local unit_gen_page = guide_section(sect_construct_data, annunc_page, "Unit General", docs.annunc.unit.main_section, 170) local unit_rps_page = guide_section(sect_construct_data, annunc_page, "Unit RPS", docs.annunc.unit.rps_section, 100) - local unit_rcs_page = guide_section(sect_construct_data, annunc_page, "Unit RCS", docs.annunc.unit.rcs_section, 100) + local unit_rcs_page = guide_section(sect_construct_data, annunc_page, "Unit RCS", docs.annunc.unit.rcs_section, 170) PushButton{parent=annunc_div,y=3,text="Unit General >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_gen_page.nav_to} PushButton{parent=annunc_div,text="Unit RPS >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=unit_rps_page.nav_to} diff --git a/pocket/ui/pages/guide_section.lua b/pocket/ui/pages/guide_section.lua index 77941a8..fed6e6e 100644 --- a/pocket/ui/pages/guide_section.lua +++ b/pocket/ui/pages/guide_section.lua @@ -1,3 +1,4 @@ +local log = require("scada-common.log") local util = require("scada-common.util") local core = require("graphics.core") @@ -57,8 +58,11 @@ return function (data, base_page, title, items, scroll_height) PushButton{parent=name_list,text=item.name,alignment=ALIGN.LEFT,fg_bg=cpair(colors.blue,colors.black),active_fg_bg=btn_active,callback=view} if i % 12 == 0 then util.nop() end + end + log.debug("guide section " .. title .. " generated with final height ".. _end.get_y()) + util.nop() return section_page From 4a39ed9d38984dfb16236e3a357cd090836515f5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 14 Jun 2024 17:50:16 -0400 Subject: [PATCH 72/87] removed stray newline --- pocket/ui/pages/guide_section.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/pocket/ui/pages/guide_section.lua b/pocket/ui/pages/guide_section.lua index fed6e6e..e73567b 100644 --- a/pocket/ui/pages/guide_section.lua +++ b/pocket/ui/pages/guide_section.lua @@ -58,7 +58,6 @@ return function (data, base_page, title, items, scroll_height) PushButton{parent=name_list,text=item.name,alignment=ALIGN.LEFT,fg_bg=cpair(colors.blue,colors.black),active_fg_bg=btn_active,callback=view} if i % 12 == 0 then util.nop() end - end log.debug("guide section " .. title .. " generated with final height ".. _end.get_y()) From f64db664483fe25b7f384f4f5e9483faf04629d8 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 14 Jun 2024 17:58:39 -0400 Subject: [PATCH 73/87] comms version updates --- scada-common/comms.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 5e364e8..3e65ed6 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -17,8 +17,8 @@ local max_distance = nil local comms = {} -- protocol/data versions (protocol/data independent changes tracked by util.lua version) -comms.version = "2.5.1" -comms.api_version = "0.0.2" +comms.version = "2.5.2" +comms.api_version = "0.0.3" ---@enum PROTOCOL local PROTOCOL = { From 4a7028f401e38b41f7f115964e666ed2fb6b38de Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 27 Jun 2024 19:57:43 -0400 Subject: [PATCH 74/87] #497 instantly launch pocket program, block network dependent apps until connected --- pocket/iocontrol.lua | 6 ++-- pocket/pocket.lua | 46 +++++++++++++++++++------ pocket/startup.lua | 2 +- pocket/ui/apps/diag_apps.lua | 2 +- pocket/ui/apps/loader.lua | 49 +++++++++++++++++++++++++++ pocket/ui/components/conn_waiting.lua | 6 ++-- pocket/ui/main.lua | 30 +++------------- 7 files changed, 95 insertions(+), 46 deletions(-) create mode 100644 pocket/ui/apps/loader.lua diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index da9c0d7..38e642e 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -342,10 +342,8 @@ function iocontrol.report_link_state(state, sv_addr, api_addr) io.ps.publish("crd_conn_quality", 0) end - if state == LINK_STATE.LINKED then - io.ps.publish("sv_addr", sv_addr) - io.ps.publish("api_addr", api_addr) - end + if sv_addr then io.ps.publish("sv_addr", sv_addr) end + if api_addr then io.ps.publish("api_addr", api_addr) end end -- determine supervisor connection quality (trip time) diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 0afc068..e73c3b1 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -78,15 +78,16 @@ end ---@enum POCKET_APP_ID local APP_ID = { ROOT = 1, + LOADER = 2, -- main app pages - UNITS = 2, - GUIDE = 3, - ABOUT = 4, + UNITS = 3, + GUIDE = 4, + ABOUT = 5, -- diag app page - ALARMS = 5, + ALARMS = 6, -- other - DUMMY = 6, - NUM_APPS = 6 + DUMMY = 7, + NUM_APPS = 7 } pocket.APP_ID = APP_ID @@ -98,9 +99,9 @@ pocket.APP_ID = APP_ID ---@field switcher function|nil function to switch between children ---@field tasks table tasks to run while viewing this page --- allocate the page navigation system ----@param render_queue mqueue -function pocket.init_nav(render_queue) +-- initialize the page navigation system +---@param smem pkt_shared_memory +function pocket.init_nav(smem) local self = { pane = nil, ---@type graphics_element sidebar = nil, ---@type graphics_element @@ -108,6 +109,7 @@ function pocket.init_nav(render_queue) containers = {}, help_map = {}, help_return = nil, + loader_return = nil, cur_app = APP_ID.ROOT } @@ -143,10 +145,13 @@ function pocket.init_nav(render_queue) app.load = function () app.loaded = true end app.unload = function () app.loaded = false end - -- check which connections this requires + -- check which connections this requires (for unload) ---@return boolean requires_sv, boolean requires_api function app.check_requires() return require_sv or false, require_api or false end + -- check if any connection is required (for load) + function app.requires_conn() return require_sv or require_api or false end + -- 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) @@ -254,7 +259,14 @@ function pocket.init_nav(render_queue) local app = self.apps[app_id] ---@type pocket_app if app then - if not app.loaded then render_queue.push_data(MQ__RENDER_DATA.LOAD_APP, app_id) end + if app.requires_conn() and not smem.pkt_sys.pocket_comms.is_linked() then + -- bring up the app loader + self.loader_return = app_id + app_id = APP_ID.LOADER + app = self.apps[app_id] + else self.loader_return = nil end + + if not app.loaded then smem.q.mq_render.push_data(MQ__RENDER_DATA.LOAD_APP, app_id) end self.cur_app = app_id self.pane.set_value(app_id) @@ -267,6 +279,14 @@ function pocket.init_nav(render_queue) end end + -- open the app that was blocked on connecting + function nav.on_loader_connected() + if self.loader_return then + nav.open_app(self.loader_return) + end + end + + -- load a given app ---@param app_id POCKET_APP_ID function nav.load_app(app_id) @@ -844,6 +864,10 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) ---@nodiscard function public.is_api_linked() return self.api.linked end + -- check if we are still linked with the supervisor and coordinator + ---@nodiscard + function public.is_linked() return self.sv.linked and self.api.linked end + return public end diff --git a/pocket/startup.lua b/pocket/startup.lua index b56da4f..a80a4ca 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -123,7 +123,7 @@ local function main() -- setup system ---------------------------------------- - smem_sys.nav = pocket.init_nav(__shared_memory.q.mq_render) + smem_sys.nav = pocket.init_nav(__shared_memory) -- message authentication init if type(config.AuthKey) == "string" and string.len(config.AuthKey) > 0 then diff --git a/pocket/ui/apps/diag_apps.lua b/pocket/ui/apps/diag_apps.lua index be63e95..27ef837 100644 --- a/pocket/ui/apps/diag_apps.lua +++ b/pocket/ui/apps/diag_apps.lua @@ -32,7 +32,7 @@ local function create_pages(root) local alarm_test = Div{parent=root,x=1,y=1} - local alarm_app = db.nav.register_app(APP_ID.ALARMS, alarm_test) + local alarm_app = db.nav.register_app(APP_ID.ALARMS, alarm_test, nil, true) local page = alarm_app.new_page(nil, function () end) page.tasks = { db.diag.tone_test.get_tone_states } diff --git a/pocket/ui/apps/loader.lua b/pocket/ui/apps/loader.lua new file mode 100644 index 0000000..8ea72f0 --- /dev/null +++ b/pocket/ui/apps/loader.lua @@ -0,0 +1,49 @@ +-- +-- Loading Screen App +-- + +local iocontrol = require("pocket.iocontrol") +local pocket = require("pocket.pocket") + +local conn_waiting = require("pocket.ui.components.conn_waiting") + +local core = require("graphics.core") + +local Div = require("graphics.elements.div") +local MultiPane = require("graphics.elements.multipane") +local TextBox = require("graphics.elements.textbox") + +local APP_ID = pocket.APP_ID + +local LINK_STATE = iocontrol.LINK_STATE + +-- create the connecting to SV & API 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(APP_ID.LOADER, main).new_page(nil, function () end) + + local conn_sv_wait = conn_waiting(main, 6, false) + local conn_api_wait = conn_waiting(main, 6, true) + local main_pane = Div{parent=main,x=1,y=2} + + local root_pane = MultiPane{parent=main,x=1,y=1,panes={conn_sv_wait,conn_api_wait,main_pane}} + + root_pane.register(db.ps, "link_state", function (state) + if state == LINK_STATE.UNLINKED or state == LINK_STATE.API_LINK_ONLY then + root_pane.set_value(1) + elseif state == LINK_STATE.SV_LINK_ONLY then + root_pane.set_value(2) + else + root_pane.set_value(3) + db.nav.on_loader_connected() + end + end) + + TextBox{parent=main_pane,text="Connected!",x=1,y=6,alignment=core.ALIGN.CENTER} +end + +return create_pages diff --git a/pocket/ui/components/conn_waiting.lua b/pocket/ui/components/conn_waiting.lua index 9cbe1b3..6b69650 100644 --- a/pocket/ui/components/conn_waiting.lua +++ b/pocket/ui/components/conn_waiting.lua @@ -23,16 +23,16 @@ local function init(parent, y, is_api) local root = Div{parent=parent,x=1,y=1} -- bounding box div - local box = Div{parent=root,x=1,y=y,height=5} + local box = Div{parent=root,x=1,y=y,height=6} 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)} - TextBox{parent=box,text="Connecting to API",alignment=ALIGN.CENTER,y=5,height=1,fg_bg=cpair(colors.white,style.root.bkg)} + TextBox{parent=box,text="Connecting to API",alignment=ALIGN.CENTER,y=5,fg_bg=cpair(colors.white,style.root.bkg)} else WaitingAnim{parent=box,x=waiting_x,y=1,fg_bg=cpair(colors.green,style.root.bkg)} - TextBox{parent=box,text="Connecting to Supervisor",alignment=ALIGN.CENTER,y=5,height=1,fg_bg=cpair(colors.white,style.root.bkg)} + TextBox{parent=box,text="Connecting to Supervisor",alignment=ALIGN.CENTER,y=5,fg_bg=cpair(colors.white,style.root.bkg)} end return root diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index 40b1104..524a67c 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -10,11 +10,10 @@ local pocket = require("pocket.pocket") local diag_apps = require("pocket.ui.apps.diag_apps") local dummy_app = require("pocket.ui.apps.dummy_app") local guide_app = require("pocket.ui.apps.guide") +local loader_app = require("pocket.ui.apps.loader") local sys_apps = require("pocket.ui.apps.sys_apps") local unit_app = require("pocket.ui.apps.unit") -local conn_waiting = require("pocket.ui.components.conn_waiting") - local home_page = require("pocket.ui.pages.home_page") local style = require("pocket.ui.style") @@ -33,8 +32,6 @@ local SignalBar = require("graphics.elements.indicators.signal") local ALIGN = core.ALIGN local cpair = core.cpair -local LINK_STATE = iocontrol.LINK_STATE - local APP_ID = pocket.APP_ID -- create new main view @@ -42,6 +39,8 @@ local APP_ID = pocket.APP_ID local function init(main) local db = iocontrol.get_db() + local main_pane = Div{parent=main,x=1,y=2} + -- window header message TextBox{parent=main,y=1,text="EARLY ACCESS ALPHA 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)} @@ -50,28 +49,6 @@ local function init(main) db.ps.subscribe("svr_conn_quality", svr_conn.set_value) 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 conn_sv_wait = conn_waiting(root_pane_div, 6, false) - local conn_api_wait = conn_waiting(root_pane_div, 6, true) - local main_pane = Div{parent=main,x=1,y=2} - - local root_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes={conn_sv_wait,conn_api_wait,main_pane}} - - root_pane.register(db.ps, "link_state", function (state) - if state == LINK_STATE.UNLINKED or state == LINK_STATE.API_LINK_ONLY then - root_pane.set_value(1) - elseif state == LINK_STATE.SV_LINK_ONLY then - root_pane.set_value(2) - else - root_pane.set_value(3) - end - end) - - --#endregion - --#region main page panel panes & sidebar local page_div = Div{parent=main_pane,x=4,y=1} @@ -80,6 +57,7 @@ local function init(main) unit_app(page_div) guide_app(page_div) + loader_app(page_div) sys_apps(page_div) diag_apps(page_div) dummy_app(page_div) From fc42049aa02e409bc2496085d44117c639634f5d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 27 Jun 2024 19:57:55 -0400 Subject: [PATCH 75/87] removed deprecated high temp constant --- scada-common/constants.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/scada-common/constants.lua b/scada-common/constants.lua index b2755b9..0472a6c 100644 --- a/scada-common/constants.lua +++ b/scada-common/constants.lua @@ -52,7 +52,6 @@ local alarms = {} -- unit alarms -alarms.HIGH_TEMP = 1150 -- temp >= 1150K alarms.HIGH_WASTE = 0.85 -- fill > 85% alarms.HIGH_RADIATION = 0.00005 -- 50 uSv/h, not yet damaging but this isn't good From 2bc20ec312e04273bc6a20f95289591a9913cff5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 27 Jun 2024 20:01:53 -0400 Subject: [PATCH 76/87] cleanup --- pocket/pocket.lua | 1 - pocket/ui/main.lua | 9 +++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/pocket/pocket.lua b/pocket/pocket.lua index e73c3b1..5e6798e 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -286,7 +286,6 @@ function pocket.init_nav(smem) end end - -- load a given app ---@param app_id POCKET_APP_ID function nav.load_app(app_id) diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index 524a67c..4a33327 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -41,7 +41,7 @@ local function init(main) local main_pane = Div{parent=main,x=1,y=2} - -- window header message + -- window header message and connection status TextBox{parent=main,y=1,text="EARLY ACCESS ALPHA 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)} @@ -49,12 +49,10 @@ local function init(main) db.ps.subscribe("svr_conn_quality", svr_conn.set_value) db.ps.subscribe("crd_conn_quality", crd_conn.set_value) - --#region main page panel panes & sidebar - local page_div = Div{parent=main_pane,x=4,y=1} + -- create all the apps & pages home_page(page_div) - unit_app(page_div) guide_app(page_div) loader_app(page_div) @@ -62,6 +60,7 @@ local function init(main) diag_apps(page_div) dummy_app(page_div) + -- verify all apps were created assert(util.table_len(db.nav.get_containers()) == APP_ID.NUM_APPS, "app IDs were not sequential or some apps weren't registered") db.nav.set_pane(MultiPane{parent=page_div,x=1,y=1,panes=db.nav.get_containers()}) @@ -70,8 +69,6 @@ local function init(main) 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} db.nav.open_app(APP_ID.ROOT) - - --#endregion end return init From 897a3ed22d9f336641c071c9f7b97faae4c9f3cd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 27 Jun 2024 21:03:53 -0400 Subject: [PATCH 77/87] #502 much needed refresh and cleanup of PLC struct and status packet handling --- reactor-plc/plc.lua | 52 ++++++------- reactor-plc/startup.lua | 2 +- supervisor/session/plc.lua | 148 +++++++++++++++++++------------------ supervisor/startup.lua | 2 +- 4 files changed, 100 insertions(+), 104 deletions(-) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 2fb869a..e3a2578 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -571,33 +571,17 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) self.seq_num = self.seq_num + 1 end - -- variable reactor status information, excluding heating rate + -- dynamic reactor status information, excluding heating rate ---@return table data_table, boolean faulted - local function _reactor_status() + local function _get_reactor_status() local fuel = nil local waste = nil local coolant = nil local hcoolant = nil - local data_table = { - false, -- getStatus - 0, -- getBurnRate - 0, -- getActualBurnRate - 0, -- getTemperature - 0, -- getDamagePercent - 0, -- getBoilEfficiency - 0, -- getEnvironmentalLoss - 0, -- fuel_amnt - 0, -- getFuelFilledPercentage - 0, -- waste_amnt - 0, -- getWasteFilledPercentage - "", -- coolant_name - 0, -- coolant_amnt - 0, -- getCoolantFilledPercentage - "", -- hcoolant_name - 0, -- hcoolant_amnt - 0 -- getHeatedCoolantFilledPercentage - } + local data_table = {} + + reactor.__p_disable_afc() local tasks = { function () data_table[1] = reactor.getStatus() end, @@ -637,30 +621,32 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) data_table[16] = hcoolant.amount end + reactor.__p_enable_afc() + return data_table, reactor.__p_is_faulted() end -- update the status cache if changed ---@return boolean changed local function _update_status_cache() - local status, faulted = _reactor_status() + local status, faulted = _get_reactor_status() local changed = false - if self.status_cache ~= nil then - if not faulted then + if not faulted then + if self.status_cache ~= nil then for i = 1, #status do if status[i] ~= self.status_cache[i] then changed = true break end end + else + changed = true end - else - changed = true - end - if changed and not faulted then - self.status_cache = status + if changed then + self.status_cache = status + end end return changed @@ -679,9 +665,11 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) _send(msg_type, { status }) end - -- send structure properties (these should not change, server will cache these) + -- send static structure properties, cached by server local function _send_struct() - local mek_data = { false, 0, 0, 0, types.new_zero_coordinate(), types.new_zero_coordinate(), 0, 0, 0, 0, 0, 0, 0, 0 } + local mek_data = {} + + reactor.__p_disable_afc() local tasks = { function () mek_data[1] = reactor.getLength() end, @@ -705,6 +693,8 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) _send(RPLC_TYPE.MEK_STRUCT, mek_data) self.resend_build = false end + + reactor.__p_enable_afc() end -- PUBLIC FUNCTIONS -- diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 1b8b2d6..fc92536 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -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.7.11" +local R_PLC_VERSION = "v1.7.12" local println = util.println local println_ts = util.println_ts diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index a4a8271..2956554 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -209,52 +209,89 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f -- copy in the reactor status ---@param mek_data table local function _copy_status(mek_data) + local stat = self.sDB.mek_status + local struct = self.sDB.mek_struct + -- copy status information - self.sDB.mek_status.status = mek_data[1] - self.sDB.mek_status.burn_rate = mek_data[2] - self.sDB.mek_status.act_burn_rate = mek_data[3] - self.sDB.mek_status.temp = mek_data[4] - self.sDB.mek_status.damage = mek_data[5] - self.sDB.mek_status.boil_eff = mek_data[6] - self.sDB.mek_status.env_loss = mek_data[7] + stat.status = mek_data[1] + stat.burn_rate = mek_data[2] + stat.act_burn_rate = mek_data[3] + stat.temp = mek_data[4] + stat.damage = mek_data[5] + stat.boil_eff = mek_data[6] + stat.env_loss = mek_data[7] -- copy container information - self.sDB.mek_status.fuel = mek_data[8] - self.sDB.mek_status.fuel_fill = mek_data[9] - self.sDB.mek_status.waste = mek_data[10] - self.sDB.mek_status.waste_fill = mek_data[11] - self.sDB.mek_status.ccool_type = mek_data[12] - self.sDB.mek_status.ccool_amnt = mek_data[13] - self.sDB.mek_status.ccool_fill = mek_data[14] - self.sDB.mek_status.hcool_type = mek_data[15] - self.sDB.mek_status.hcool_amnt = mek_data[16] - self.sDB.mek_status.hcool_fill = mek_data[17] + stat.fuel = mek_data[8] + stat.fuel_fill = mek_data[9] + stat.waste = mek_data[10] + stat.waste_fill = mek_data[11] + stat.ccool_type = mek_data[12] + stat.ccool_amnt = mek_data[13] + stat.ccool_fill = mek_data[14] + stat.hcool_type = mek_data[15] + stat.hcool_amnt = mek_data[16] + stat.hcool_fill = mek_data[17] -- update computable fields if we have our structure if self.received_struct then - self.sDB.mek_status.fuel_need = self.sDB.mek_struct.fuel_cap - self.sDB.mek_status.fuel_fill - self.sDB.mek_status.waste_need = self.sDB.mek_struct.waste_cap - self.sDB.mek_status.waste_fill - self.sDB.mek_status.cool_need = self.sDB.mek_struct.ccool_cap - self.sDB.mek_status.ccool_fill - self.sDB.mek_status.hcool_need = self.sDB.mek_struct.hcool_cap - self.sDB.mek_status.hcool_fill + stat.fuel_need = struct.fuel_cap - stat.fuel_fill + stat.waste_need = struct.waste_cap - stat.waste_fill + stat.cool_need = struct.ccool_cap - stat.ccool_fill + stat.hcool_need = struct.hcool_cap - stat.hcool_fill end end -- copy in the reactor structure ---@param mek_data table local function _copy_struct(mek_data) - self.sDB.mek_struct.length = mek_data[1] - self.sDB.mek_struct.width = mek_data[2] - self.sDB.mek_struct.height = mek_data[3] - self.sDB.mek_struct.min_pos = mek_data[4] - self.sDB.mek_struct.max_pos = mek_data[5] - self.sDB.mek_struct.heat_cap = mek_data[6] - self.sDB.mek_struct.fuel_asm = mek_data[7] - self.sDB.mek_struct.fuel_sa = mek_data[8] - self.sDB.mek_struct.fuel_cap = mek_data[9] - self.sDB.mek_struct.waste_cap = mek_data[10] - self.sDB.mek_struct.ccool_cap = mek_data[11] - self.sDB.mek_struct.hcool_cap = mek_data[12] - self.sDB.mek_struct.max_burn = mek_data[13] + local struct = self.sDB.mek_struct + + struct.length = mek_data[1] + struct.width = mek_data[2] + struct.height = mek_data[3] + struct.min_pos = mek_data[4] + struct.max_pos = mek_data[5] + struct.heat_cap = mek_data[6] + struct.fuel_asm = mek_data[7] + struct.fuel_sa = mek_data[8] + struct.fuel_cap = mek_data[9] + struct.waste_cap = mek_data[10] + struct.ccool_cap = mek_data[11] + struct.hcool_cap = mek_data[12] + struct.max_burn = mek_data[13] + end + + -- handle a reactor status packet + ---@param pkt rplc_frame + local function _handle_status(pkt) + local valid = (type(pkt.data[1]) == "number") and (type(pkt.data[2]) == "boolean") and + (type(pkt.data[3]) == "boolean") and (type(pkt.data[4]) == "boolean") and + (type(pkt.data[5]) == "number") + + if valid then + self.sDB.last_status_update = pkt.data[1] + self.sDB.control_state = pkt.data[2] + self.sDB.no_reactor = pkt.data[3] + self.sDB.formed = pkt.data[4] + self.sDB.auto_ack_token = pkt.data[5] + + if (not self.sDB.no_reactor) and self.sDB.formed and (type(pkt.data[6]) == "number") then + self.sDB.mek_status.heating_rate = pkt.data[6] or 0.0 + + -- attempt to read mek_data table + if type(pkt.data[7]) == "table" then + if #pkt.data[7] == 17 then + _copy_status(pkt.data[7]) + self.received_status_cache = true + else + log.error(log_header .. "RPLC status packet reactor data length mismatch") + end + end + end + else + log.debug(log_header .. "RPLC status packet invalid") + end end -- mark this PLC session as closed, stop watchdog @@ -334,48 +371,17 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f if pkt.type == RPLC_TYPE.STATUS then -- status packet received, update data if pkt.length >= 5 then - if (type(pkt.data[1]) == "number") and (type(pkt.data[2]) == "boolean") and (type(pkt.data[3]) == "boolean") and - (type(pkt.data[4]) == "boolean") and (type(pkt.data[5]) == "number") then - self.sDB.last_status_update = pkt.data[1] - self.sDB.control_state = pkt.data[2] - self.sDB.no_reactor = pkt.data[3] - self.sDB.formed = pkt.data[4] - self.sDB.auto_ack_token = pkt.data[5] - - if (not self.sDB.no_reactor) and self.sDB.formed and (type(pkt.data[6]) == "number") then - self.sDB.mek_status.heating_rate = pkt.data[6] or 0.0 - - -- attempt to read mek_data table - if type(pkt.data[7]) == "table" then - local status = pcall(_copy_status, pkt.data[7]) - if status then - -- copied in status data OK - self.received_status_cache = true - else - -- error copying status data - log.error(log_header .. "failed to parse status packet data") - end - end - end - else - log.debug(log_header .. "RPLC status packet invalid") - end + _handle_status(pkt) else log.debug(log_header .. "RPLC status packet length mismatch") end elseif pkt.type == RPLC_TYPE.MEK_STRUCT then -- received reactor structure, record it - if pkt.length == 14 then - local status = pcall(_copy_struct, pkt.data) - if status then - -- copied in structure data OK - _compute_op_temps() - self.received_struct = true - out_queue.push_data(svqtypes.SV_Q_DATA.PLC_BUILD_CHANGED, reactor_id) - else - -- error copying structure data - log.error(log_header .. "failed to parse struct packet data") - end + if pkt.length == 13 then + _copy_struct(pkt.data) + _compute_op_temps() + self.received_struct = true + out_queue.push_data(svqtypes.SV_Q_DATA.PLC_BUILD_CHANGED, reactor_id) else log.debug(log_header .. "RPLC struct packet length mismatch") end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 89c38d8..47c8122 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.3.12" +local SUPERVISOR_VERSION = "v1.3.13" local println = util.println local println_ts = util.println_ts From 4cdbe3b07f5ec07599dbf5c09b586f0119ce6226 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 27 Jun 2024 21:05:53 -0400 Subject: [PATCH 78/87] some more cleanup --- supervisor/session/plc.lua | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 2956554..75ceb69 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -190,20 +190,23 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f -- copy in the RPS status ---@param rps_status table local function _copy_rps_status(rps_status) - self.sDB.rps_tripped = rps_status[1] - self.sDB.rps_trip_cause = rps_status[2] - self.sDB.rps_status.high_dmg = rps_status[3] - self.sDB.rps_status.high_temp = rps_status[4] - self.sDB.rps_status.low_cool = rps_status[5] - self.sDB.rps_status.ex_waste = rps_status[6] - self.sDB.rps_status.ex_hcool = rps_status[7] - self.sDB.rps_status.no_fuel = rps_status[8] - self.sDB.rps_status.fault = rps_status[9] - self.sDB.rps_status.timeout = rps_status[10] - self.sDB.rps_status.manual = rps_status[11] - self.sDB.rps_status.automatic = rps_status[12] - self.sDB.rps_status.sys_fail = rps_status[13] - self.sDB.rps_status.force_dis = rps_status[14] + local rps = self.sDB.rps_status + + self.sDB.rps_tripped = rps_status[1] + self.sDB.rps_trip_cause = rps_status[2] + + rps.high_dmg = rps_status[3] + rps.high_temp = rps_status[4] + rps.low_cool = rps_status[5] + rps.ex_waste = rps_status[6] + rps.ex_hcool = rps_status[7] + rps.no_fuel = rps_status[8] + rps.fault = rps_status[9] + rps.timeout = rps_status[10] + rps.manual = rps_status[11] + rps.automatic = rps_status[12] + rps.sys_fail = rps_status[13] + rps.force_dis = rps_status[14] end -- copy in the reactor status From d2bc4f6bc06e171753b125c0bd317ddd9e3a2976 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sat, 29 Jun 2024 02:27:55 +0000 Subject: [PATCH 79/87] #488 HMAC acceleration and seq_num changes --- coordinator/coordinator.lua | 16 +- coordinator/session/apisessions.lua | 9 +- coordinator/session/pocket.lua | 14 +- coordinator/startup.lua | 2 +- pocket/pocket.lua | 26 +- pocket/startup.lua | 2 +- reactor-plc/plc.lua | 15 +- reactor-plc/startup.lua | 2 +- rtu/rtu.lua | 13 +- rtu/startup.lua | 2 +- scada-common/comms.lua | 17 +- scada-common/network.lua | 21 +- supervisor/session/coordinator.lua | 14 +- supervisor/session/plc.lua | 14 +- supervisor/session/pocket.lua | 14 +- supervisor/session/rtu.lua | 14 +- supervisor/session/svsessions.lua | 42 +-- supervisor/startup.lua | 4 +- supervisor/supervisor.lua | 499 ++++++++++++++-------------- 19 files changed, 355 insertions(+), 385 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index f6a2018..9e98a33 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -232,8 +232,7 @@ function coordinator.comms(version, nic, sv_watchdog) local self = { sv_linked = false, sv_addr = comms.BROADCAST, - sv_seq_num = 0, - sv_r_seq_num = nil, + sv_seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate sv_config_err = false, last_est_ack = ESTABLISH_ACK.ALLOW, last_api_est_acks = {}, @@ -370,7 +369,6 @@ function coordinator.comms(version, nic, sv_watchdog) sv_watchdog.cancel() self.sv_addr = comms.BROADCAST self.sv_linked = false - self.sv_r_seq_num = nil iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED) _send_sv(PROTOCOL.SCADA_MGMT, MGMT_TYPE.CLOSE, {}) end @@ -492,7 +490,7 @@ function coordinator.comms(version, nic, sv_watchdog) _send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_API_VERSION) elseif dev_type == DEVICE_TYPE.PKT then -- pocket linking request - local id = apisessions.establish_session(src_addr, firmware_v) + local id = apisessions.establish_session(src_addr, packet.scada_frame.seq_num() + 1, firmware_v) coordinator.log_comms(util.c("API_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", id)) local conf = iocontrol.get_db().facility.conf @@ -514,16 +512,14 @@ function coordinator.comms(version, nic, sv_watchdog) end elseif r_chan == config.SVR_Channel then -- check sequence number - if self.sv_r_seq_num == nil then - self.sv_r_seq_num = packet.scada_frame.seq_num() - elseif self.sv_linked and ((self.sv_r_seq_num + 1) ~= packet.scada_frame.seq_num()) then - log.warning("sequence out-of-order: last = " .. self.sv_r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + if self.sv_seq_num ~= packet.scada_frame.seq_num() then + log.warning("sequence out-of-order: last = " .. self.sv_seq_num .. ", new = " .. packet.scada_frame.seq_num()) return false elseif self.sv_linked and src_addr ~= self.sv_addr then log.debug("received packet from unknown computer " .. src_addr .. " while linked; channel in use by another system?") return false else - self.sv_r_seq_num = packet.scada_frame.seq_num() + self.sv_seq_num = packet.scada_frame.seq_num() + 1 end -- feed watchdog on valid sequence number @@ -675,7 +671,6 @@ function coordinator.comms(version, nic, sv_watchdog) sv_watchdog.cancel() self.sv_addr = comms.BROADCAST self.sv_linked = false - self.sv_r_seq_num = nil iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED) log.info("server connection closed by remote host") else @@ -706,7 +701,6 @@ function coordinator.comms(version, nic, sv_watchdog) self.sv_addr = src_addr self.sv_linked = true - self.sv_r_seq_num = nil self.sv_config_err = false iocontrol.fp_link_state(types.PANEL_LINK_STATE.LINKED) diff --git a/coordinator/session/apisessions.lua b/coordinator/session/apisessions.lua index 516b91b..4daa45d 100644 --- a/coordinator/session/apisessions.lua +++ b/coordinator/session/apisessions.lua @@ -89,10 +89,11 @@ end -- establish a new API session ---@nodiscard ----@param source_addr integer ----@param version string +---@param source_addr integer pocket computer ID +---@param i_seq_num integer initial sequence number to use next +---@param version string pocket version ---@return integer session_id -function apisessions.establish_session(source_addr, version) +function apisessions.establish_session(source_addr, i_seq_num, version) ---@class pkt_session_struct local pkt_s = { open = true, @@ -105,7 +106,7 @@ function apisessions.establish_session(source_addr, version) local id = self.next_id - pkt_s.instance = pocket.new_session(id, source_addr, pkt_s.in_queue, pkt_s.out_queue, self.config.API_Timeout) + pkt_s.instance = pocket.new_session(id, source_addr, i_seq_num, pkt_s.in_queue, pkt_s.out_queue, self.config.API_Timeout) table.insert(self.sessions, pkt_s) local mt = { diff --git a/coordinator/session/pocket.lua b/coordinator/session/pocket.lua index cd03fc1..f2f59e4 100644 --- a/coordinator/session/pocket.lua +++ b/coordinator/session/pocket.lua @@ -32,16 +32,16 @@ local PERIODICS = { ---@nodiscard ---@param id integer session ID ---@param s_addr integer device source address +---@param i_seq_num integer initial sequence number ---@param in_queue mqueue in message queue ---@param out_queue mqueue out message queue ---@param timeout number communications timeout -function pocket.new_session(id, s_addr, in_queue, out_queue, timeout) +function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) local log_header = "pkt_session(" .. id .. "): " local self = { -- connection properties - seq_num = 0, - r_seq_num = nil, + seq_num = i_seq_num, connected = true, conn_watchdog = util.new_watchdog(timeout), last_rtt = 0, @@ -104,13 +104,11 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout) ---@param pkt mgmt_frame|crdn_frame local function _handle_packet(pkt) -- check sequence number - if self.r_seq_num == nil then - self.r_seq_num = pkt.scada_frame.seq_num() - elseif (self.r_seq_num + 1) ~= pkt.scada_frame.seq_num() then - log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + if self.seq_num ~= pkt.scada_frame.seq_num() then + log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num()) return else - self.r_seq_num = pkt.scada_frame.seq_num() + self.seq_num = pkt.scada_frame.seq_num() + 1 end -- feed watchdog diff --git a/coordinator/startup.lua b/coordinator/startup.lua index f152bc8..9e20631 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") local threads = require("coordinator.threads") -local COORDINATOR_VERSION = "v1.4.7" +local COORDINATOR_VERSION = "v1.5.0" local CHUNK_LOAD_DELAY_S = 30.0 diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 5e6798e..fb4f41a 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -370,15 +370,13 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) sv = { linked = false, addr = comms.BROADCAST, - seq_num = 0, - r_seq_num = nil, ---@type nil|integer + seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate last_est_ack = ESTABLISH_ACK.ALLOW }, api = { linked = false, addr = comms.BROADCAST, - seq_num = 0, - r_seq_num = nil, ---@type nil|integer + seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate last_est_ack = ESTABLISH_ACK.ALLOW }, establish_delay_counter = 0 @@ -466,7 +464,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) sv_watchdog.cancel() nav.unload_sv() self.sv.linked = false - self.sv.r_seq_num = nil self.sv.addr = comms.BROADCAST _send_sv(MGMT_TYPE.CLOSE, {}) end @@ -476,7 +473,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) api_watchdog.cancel() nav.unload_api() self.api.linked = false - self.api.r_seq_num = nil self.api.addr = comms.BROADCAST _send_crd(MGMT_TYPE.CLOSE, {}) end @@ -603,17 +599,15 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) log.debug("received packet on unconfigured channel " .. l_chan, true) elseif r_chan == config.CRD_Channel then -- check sequence number - if self.api.r_seq_num == nil then - self.api.r_seq_num = packet.scada_frame.seq_num() - elseif self.connected and ((self.api.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then - log.warning("sequence out-of-order (API): last = " .. self.api.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + if self.api.seq_num ~= packet.scada_frame.seq_num() then + log.warning("sequence out-of-order (API): last = " .. self.api.seq_num .. ", new = " .. packet.scada_frame.seq_num()) return 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 .. "); channel in use by another system?") return else - self.api.r_seq_num = packet.scada_frame.seq_num() + self.api.seq_num = packet.scada_frame.seq_num() + 1 end -- feed watchdog on valid sequence number @@ -658,7 +652,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) api_watchdog.cancel() nav.unload_api() self.api.linked = false - self.api.r_seq_num = nil self.api.addr = comms.BROADCAST log.info("coordinator server connection closed by remote host") else _fail_type(packet) end @@ -723,17 +716,15 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) end elseif r_chan == config.SVR_Channel then -- check sequence number - if self.sv.r_seq_num == nil then - self.sv.r_seq_num = packet.scada_frame.seq_num() - elseif self.connected and ((self.sv.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then - log.warning("sequence out-of-order (SVR): last = " .. self.sv.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + if self.sv.seq_num ~= packet.scada_frame.seq_num() then + log.warning("sequence out-of-order (SVR): last = " .. self.sv.seq_num .. ", new = " .. packet.scada_frame.seq_num()) return elseif self.sv.linked and (src_addr ~= self.sv.addr) then log.debug("received packet from unknown computer " .. src_addr .. " while linked (SVR expected " .. self.sv.addr .. "); channel in use by another system?") return else - self.sv.r_seq_num = packet.scada_frame.seq_num() + self.sv.seq_num = packet.scada_frame.seq_num() + 1 end -- feed watchdog on valid sequence number @@ -764,7 +755,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) sv_watchdog.cancel() nav.unload_sv() self.sv.linked = false - self.sv.r_seq_num = nil self.sv.addr = comms.BROADCAST log.info("supervisor server connection closed by remote host") elseif packet.type == MGMT_TYPE.DIAG_TONE_GET then diff --git a/pocket/startup.lua b/pocket/startup.lua index a80a4ca..2701efb 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -20,7 +20,7 @@ local pocket = require("pocket.pocket") local renderer = require("pocket.renderer") local threads = require("pocket.threads") -local POCKET_VERSION = "v0.10.0-alpha" +local POCKET_VERSION = "v0.11.0-alpha" local println = util.println local println_ts = util.println_ts diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 2fb869a..df116cf 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -524,8 +524,7 @@ end function plc.comms(version, nic, reactor, rps, conn_watchdog) local self = { sv_addr = comms.BROADCAST, - seq_num = 0, - r_seq_num = nil, + seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate scrammed = false, linked = false, last_est_ack = ESTABLISH_ACK.ALLOW, @@ -725,7 +724,6 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) function public.unlink() self.sv_addr = comms.BROADCAST self.linked = false - self.r_seq_num = nil self.status_cache = nil databus.tx_link_state(types.PANEL_LINK_STATE.DISCONNECTED) end @@ -834,17 +832,15 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) -- handle packets now that we have prints setup if l_chan == config.PLC_Channel then -- check sequence number - if self.r_seq_num == nil then - self.r_seq_num = packet.scada_frame.seq_num() - elseif self.linked and ((self.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then - log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + if self.seq_num ~= packet.scada_frame.seq_num() then + log.warning("sequence out-of-order: last = " .. self.seq_num .. ", new = " .. packet.scada_frame.seq_num()) return elseif self.linked and (src_addr ~= self.sv_addr) then log.debug("received packet from unknown computer " .. src_addr .. " while linked (expected " .. self.sv_addr .. "); channel in use by another system?") return else - self.r_seq_num = packet.scada_frame.seq_num() + self.seq_num = packet.scada_frame.seq_num() + 1 end -- feed the watchdog first so it doesn't uhh...eat our packets :) @@ -1030,10 +1026,9 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) println_ts("linked!") log.info("supervisor establish request approved, linked to SV (CID#" .. src_addr .. ")") - -- link + reset remote sequence number and cache + -- link + reset cache self.sv_addr = src_addr self.linked = true - self.r_seq_num = nil self.status_cache = nil if plc_state.reactor_formed then _send_struct() end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 1b8b2d6..6f6a27b 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -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.7.11" +local R_PLC_VERSION = "v1.8.0" local println = util.println local println_ts = util.println_ts diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 71cea40..0278f42 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -284,8 +284,7 @@ end function rtu.comms(version, nic, conn_watchdog) local self = { sv_addr = comms.BROADCAST, - seq_num = 0, - r_seq_num = nil, + seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate txn_id = 0, last_est_ack = ESTABLISH_ACK.ALLOW } @@ -363,7 +362,6 @@ function rtu.comms(version, nic, conn_watchdog) function public.unlink(rtu_state) rtu_state.linked = false self.sv_addr = comms.BROADCAST - self.r_seq_num = nil databus.tx_link_state(types.PANEL_LINK_STATE.DISCONNECTED) end @@ -441,17 +439,15 @@ function rtu.comms(version, nic, conn_watchdog) if l_chan == config.RTU_Channel then -- check sequence number - if self.r_seq_num == nil then - self.r_seq_num = packet.scada_frame.seq_num() - elseif rtu_state.linked and ((self.r_seq_num + 1) ~= packet.scada_frame.seq_num()) then - log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + if self.seq_num ~= packet.scada_frame.seq_num() then + log.warning("sequence out-of-order: last = " .. self.seq_num .. ", new = " .. packet.scada_frame.seq_num()) return elseif rtu_state.linked and (src_addr ~= self.sv_addr) then log.debug("received packet from unknown computer " .. src_addr .. " while linked (expected " .. self.sv_addr .. "); channel in use by another system?") return else - self.r_seq_num = packet.scada_frame.seq_num() + self.seq_num = packet.scada_frame.seq_num() + 1 end -- feed watchdog on valid sequence number @@ -556,7 +552,6 @@ function rtu.comms(version, nic, conn_watchdog) -- establish allowed rtu_state.linked = true self.sv_addr = packet.scada_frame.src_addr() - self.r_seq_num = nil println_ts("supervisor connection established") log.info("supervisor connection established") else diff --git a/rtu/startup.lua b/rtu/startup.lua index ae2e72b..00f27c7 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -31,7 +31,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.9.6" +local RTU_VERSION = "v1.10.0" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 3e65ed6..e31c60b 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -17,7 +17,7 @@ local max_distance = nil local comms = {} -- protocol/data versions (protocol/data independent changes tracked by util.lua version) -comms.version = "2.5.2" +comms.version = "3.0.0" comms.api_version = "0.0.3" ---@enum PROTOCOL @@ -240,6 +240,8 @@ function comms.scada_packet() ---@nodiscard function public.modem_event() return self.modem_msg_in end ---@nodiscard + function public.raw_header() return { self.src_addr, self.dest_addr, self.seq_num, self.protocol } end + ---@nodiscard function public.raw_sendable() return self.raw end ---@nodiscard @@ -278,7 +280,7 @@ function comms.authd_packet() src_addr = comms.BROADCAST, dest_addr = comms.BROADCAST, mac = "", - payload = "" + payload = nil } ---@class authd_packet @@ -286,14 +288,13 @@ function comms.authd_packet() -- make an authenticated SCADA packet ---@param s_packet scada_packet scada packet to authenticate - ---@param mac function message authentication function + ---@param mac function message authentication hash function function public.make(s_packet, mac) self.valid = true self.src_addr = s_packet.src_addr() self.dest_addr = s_packet.dest_addr() - self.payload = textutils.serialize(s_packet.raw_sendable(), { allow_repetitions = true, compact = true }) - self.mac = mac(self.payload) - self.raw = { self.src_addr, self.dest_addr, self.mac, self.payload } + self.mac = mac(textutils.serialize(s_packet.raw_header(), { allow_repetitions = true, compact = true })) + self.raw = { self.src_addr, self.dest_addr, self.mac, s_packet.data() } end -- parse in a modem message as an authenticated SCADA packet @@ -330,14 +331,14 @@ function comms.authd_packet() self.src_addr = nil self.dest_addr = nil self.mac = "" - self.payload = "" + self.payload = {} end -- check if this packet is destined for this device local is_destination = (self.dest_addr == comms.BROADCAST) or (self.dest_addr == COMPUTER_ID) self.valid = is_destination and type(self.src_addr) == "number" and type(self.dest_addr) == "number" and - type(self.mac) == "string" and type(self.payload) == "string" + type(self.mac) == "string" and type(self.payload) == "table" end end diff --git a/scada-common/network.lua b/scada-common/network.lua index bdaf5c3..4dce34e 100644 --- a/scada-common/network.lua +++ b/scada-common/network.lua @@ -114,7 +114,7 @@ function network.nic(modem) modem.open(channel) end - -- link all public functions except for transmit + -- link all public functions except for transmit, open, and close for key, func in pairs(modem) do if key ~= "transmit" and key ~= "open" and key ~= "close" and key ~= "closeAll" then public[key] = func end end @@ -184,7 +184,7 @@ function network.nic(modem) ---@cast tx_packet authd_packet tx_packet.make(packet, compute_hmac) - -- log.debug("crypto.modem.transmit: data processing took " .. (util.time_ms() - start) .. "ms") + -- log.debug("network.modem.transmit: data processing took " .. (util.time_ms() - start) .. "ms") end modem.transmit(dest_channel, local_channel, tx_packet.raw_sendable()) @@ -211,17 +211,18 @@ function network.nic(modem) a_packet.receive(side, sender, reply_to, message, distance) if a_packet.is_valid() then - -- local start = util.time_ms() - local packet_hmac = a_packet.mac() - local msg = a_packet.data() - local computed_hmac = compute_hmac(msg) + s_packet.receive(side, sender, reply_to, a_packet.data(), distance) - if packet_hmac == computed_hmac then - -- log.debug("crypto.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms") - s_packet.receive(side, sender, reply_to, textutils.unserialize(msg), distance) + if s_packet.is_valid() then + -- local start = util.time_ms() + local computed_hmac = compute_hmac(textutils.serialize(s_packet.raw_header(), { allow_repetitions = true, compact = true })) + + if a_packet.mac() == computed_hmac then + -- log.debug("network.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms") s_packet.stamp_authenticated() else - -- log.debug("crypto.modem.receive: HMAC failed verification in " .. (util.time_ms() - start) .. "ms") + -- log.debug("network.modem.receive: HMAC failed verification in " .. (util.time_ms() - start) .. "ms") + end end end else diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 9c9284f..f449650 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -43,12 +43,13 @@ local PERIODICS = { ---@nodiscard ---@param id integer session ID ---@param s_addr integer device source address +---@param i_seq_num integer initial sequence number ---@param in_queue mqueue in message queue ---@param out_queue mqueue out message queue ---@param timeout number communications timeout ---@param facility facility facility data table ---@param fp_ok boolean if the front panel UI is running -function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facility, fp_ok) +function coordinator.new_session(id, s_addr, i_seq_num, 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 @@ -57,8 +58,7 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil local self = { units = facility.get_units(), -- connection properties - seq_num = 0, - r_seq_num = nil, + seq_num = i_seq_num, connected = true, conn_watchdog = util.new_watchdog(timeout), establish_time = util.time_s(), @@ -182,13 +182,11 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil ---@param pkt mgmt_frame|crdn_frame local function _handle_packet(pkt) -- check sequence number - if self.r_seq_num == nil then - self.r_seq_num = pkt.scada_frame.seq_num() - elseif (self.r_seq_num + 1) ~= pkt.scada_frame.seq_num() then - log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + if self.seq_num ~= pkt.scada_frame.seq_num() then + log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num()) return else - self.r_seq_num = pkt.scada_frame.seq_num() + self.seq_num = pkt.scada_frame.seq_num() + 1 end -- feed watchdog diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index a4a8271..63293e4 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -48,12 +48,13 @@ local PERIODICS = { ---@nodiscard ---@param id integer session ID ---@param s_addr integer device source address +---@param i_seq_num integer initial sequence number ---@param reactor_id integer reactor ID ---@param in_queue mqueue in message queue ---@param out_queue mqueue out message queue ---@param timeout number communications timeout ---@param fp_ok boolean if the front panel UI is running -function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, fp_ok) +function plc.new_session(id, s_addr, i_seq_num, 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 @@ -66,8 +67,7 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f ramping_rate = false, auto_lock = false, -- connection properties - seq_num = 0, - r_seq_num = nil, + seq_num = i_seq_num, connected = true, received_struct = false, received_status_cache = false, @@ -309,13 +309,11 @@ function plc.new_session(id, s_addr, reactor_id, in_queue, out_queue, timeout, f ---@param pkt mgmt_frame|rplc_frame local function _handle_packet(pkt) -- check sequence number - if self.r_seq_num == nil then - self.r_seq_num = pkt.scada_frame.seq_num() - elseif (self.r_seq_num + 1) ~= pkt.scada_frame.seq_num() then - log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + if self.seq_num ~= pkt.scada_frame.seq_num() then + log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num()) return else - self.r_seq_num = pkt.scada_frame.seq_num() + self.seq_num = pkt.scada_frame.seq_num() + 1 end -- process packet diff --git a/supervisor/session/pocket.lua b/supervisor/session/pocket.lua index 48756ea..145add2 100644 --- a/supervisor/session/pocket.lua +++ b/supervisor/session/pocket.lua @@ -30,12 +30,13 @@ local PERIODICS = { ---@nodiscard ---@param id integer session ID ---@param s_addr integer device source address +---@param i_seq_num integer initial sequence number ---@param in_queue mqueue in message queue ---@param out_queue mqueue out message queue ---@param timeout number communications timeout ---@param facility facility facility data table ---@param fp_ok boolean if the front panel UI is running -function pocket.new_session(id, s_addr, in_queue, out_queue, timeout, facility, fp_ok) +function pocket.new_session(id, s_addr, i_seq_num, 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 @@ -43,8 +44,7 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout, facility, local self = { -- connection properties - seq_num = 0, - r_seq_num = nil, + seq_num = i_seq_num, connected = true, conn_watchdog = util.new_watchdog(timeout), last_rtt = 0, @@ -93,13 +93,11 @@ function pocket.new_session(id, s_addr, in_queue, out_queue, timeout, facility, ---@param pkt mgmt_frame local function _handle_packet(pkt) -- check sequence number - if self.r_seq_num == nil then - self.r_seq_num = pkt.scada_frame.seq_num() - elseif (self.r_seq_num + 1) ~= pkt.scada_frame.seq_num() then - log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + if self.seq_num ~= pkt.scada_frame.seq_num() then + log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num()) return else - self.r_seq_num = pkt.scada_frame.seq_num() + self.seq_num = pkt.scada_frame.seq_num() + 1 end -- feed watchdog diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 6bebb87..789e649 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -34,13 +34,14 @@ local PERIODICS = { ---@nodiscard ---@param id integer session ID ---@param s_addr integer device source address +---@param i_seq_num integer initial sequence number ---@param in_queue mqueue in message queue ---@param out_queue mqueue out message queue ---@param timeout number communications timeout ---@param advertisement table RTU device advertisement ---@param facility facility facility data table ---@param fp_ok boolean if the front panel UI is running -function rtu.new_session(id, s_addr, in_queue, out_queue, timeout, advertisement, facility, fp_ok) +function rtu.new_session(id, s_addr, i_seq_num, 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 @@ -51,8 +52,7 @@ function rtu.new_session(id, s_addr, in_queue, out_queue, timeout, advertisement advert = advertisement, fac_units = facility.get_units(), -- connection properties - seq_num = 0, - r_seq_num = nil, + seq_num = i_seq_num, connected = true, conn_watchdog = util.new_watchdog(timeout), last_rtt = 0, @@ -240,13 +240,11 @@ function rtu.new_session(id, s_addr, in_queue, out_queue, timeout, advertisement ---@param pkt modbus_frame|mgmt_frame local function _handle_packet(pkt) -- check sequence number - if self.r_seq_num == nil then - self.r_seq_num = pkt.scada_frame.seq_num() - elseif (self.r_seq_num + 1) ~= pkt.scada_frame.seq_num() then - log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + if self.seq_num ~= pkt.scada_frame.seq_num() then + log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num()) return else - self.r_seq_num = pkt.scada_frame.seq_num() + self.seq_num = pkt.scada_frame.seq_num() + 1 end -- feed watchdog diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 7300162..c7c24fc 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -273,11 +273,12 @@ end -- establish a new PLC session ---@nodiscard ----@param source_addr integer ----@param for_reactor integer ----@param version string +---@param source_addr integer PLC computer ID +---@param i_seq_num integer initial sequence number to use next +---@param for_reactor integer unit ID +---@param version string PLC version ---@return integer|false session_id -function svsessions.establish_plc_session(source_addr, for_reactor, version) +function svsessions.establish_plc_session(source_addr, i_seq_num, for_reactor, version) if svsessions.get_reactor_session(for_reactor) == nil and for_reactor >= 1 and for_reactor <= self.config.UnitCount then ---@class plc_session_struct local plc_s = { @@ -294,7 +295,7 @@ function svsessions.establish_plc_session(source_addr, for_reactor, version) local id = self.next_ids.plc - plc_s.instance = plc.new_session(id, source_addr, for_reactor, plc_s.in_queue, plc_s.out_queue, self.config.PLC_Timeout, self.fp_ok) + plc_s.instance = plc.new_session(id, source_addr, i_seq_num, for_reactor, plc_s.in_queue, plc_s.out_queue, self.config.PLC_Timeout, self.fp_ok) table.insert(self.sessions.plc, plc_s) local units = self.facility.get_units() @@ -320,13 +321,14 @@ function svsessions.establish_plc_session(source_addr, for_reactor, version) end end --- establish a new RTU session +-- establish a new RTU gateway session ---@nodiscard ----@param source_addr integer ----@param advertisement table ----@param version string +---@param source_addr integer RTU gateway computer ID +---@param i_seq_num integer initial sequence number to use next +---@param advertisement table RTU capability advertisement +---@param version string RTU gateway version ---@return integer session_id -function svsessions.establish_rtu_session(source_addr, advertisement, version) +function svsessions.establish_rtu_session(source_addr, i_seq_num, advertisement, version) ---@class rtu_session_struct local rtu_s = { s_type = "rtu", @@ -341,7 +343,7 @@ function svsessions.establish_rtu_session(source_addr, advertisement, version) local id = self.next_ids.rtu - rtu_s.instance = rtu.new_session(id, source_addr, rtu_s.in_queue, rtu_s.out_queue, self.config.RTU_Timeout, advertisement, self.facility, self.fp_ok) + rtu_s.instance = rtu.new_session(id, source_addr, i_seq_num, rtu_s.in_queue, rtu_s.out_queue, self.config.RTU_Timeout, advertisement, self.facility, self.fp_ok) table.insert(self.sessions.rtu, rtu_s) local mt = { @@ -362,10 +364,11 @@ end -- establish a new coordinator session ---@nodiscard ----@param source_addr integer ----@param version string +---@param source_addr integer coordinator computer ID +---@param i_seq_num integer initial sequence number to use next +---@param version string coordinator version ---@return integer|false session_id -function svsessions.establish_crd_session(source_addr, version) +function svsessions.establish_crd_session(source_addr, i_seq_num, version) if svsessions.get_crd_session() == nil then ---@class crd_session_struct local crd_s = { @@ -381,7 +384,7 @@ function svsessions.establish_crd_session(source_addr, version) local id = self.next_ids.crd - crd_s.instance = coordinator.new_session(id, source_addr, crd_s.in_queue, crd_s.out_queue, self.config.CRD_Timeout, self.facility, self.fp_ok) + crd_s.instance = coordinator.new_session(id, source_addr, i_seq_num, crd_s.in_queue, crd_s.out_queue, self.config.CRD_Timeout, self.facility, self.fp_ok) table.insert(self.sessions.crd, crd_s) local mt = { @@ -406,10 +409,11 @@ end -- establish a new pocket diagnostics session ---@nodiscard ----@param source_addr integer ----@param version string +---@param source_addr integer pocket computer ID +---@param i_seq_num integer initial sequence number to use next +---@param version string pocket version ---@return integer|false session_id -function svsessions.establish_pdg_session(source_addr, version) +function svsessions.establish_pdg_session(source_addr, i_seq_num, version) ---@class pdg_session_struct local pdg_s = { s_type = "pkt", @@ -424,7 +428,7 @@ function svsessions.establish_pdg_session(source_addr, version) local id = self.next_ids.pdg - pdg_s.instance = pocket.new_session(id, source_addr, pdg_s.in_queue, pdg_s.out_queue, self.config.PKT_Timeout, self.facility, self.fp_ok) + pdg_s.instance = pocket.new_session(id, source_addr, i_seq_num, pdg_s.in_queue, pdg_s.out_queue, self.config.PKT_Timeout, self.facility, self.fp_ok) table.insert(self.sessions.pdg, pdg_s) local mt = { diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 89c38d8..edc60f1 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.3.12" +local SUPERVISOR_VERSION = "v1.4.0" local println = util.println local println_ts = util.println_ts @@ -214,7 +214,7 @@ local function main() elseif event == "modem_message" then -- got a packet local packet = superv_comms.parse_packet(param1, param2, param3, param4, param5) - superv_comms.handle_packet(packet) + if packet then superv_comms.handle_packet(packet) end elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" or event == "double_click" then -- handle a mouse event diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 1d79e72..2d69b7c 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -191,283 +191,282 @@ function supervisor.comms(_version, nic, fp_ok) end -- handle a packet - ---@param packet modbus_frame|rplc_frame|mgmt_frame|crdn_frame|nil + ---@param packet modbus_frame|rplc_frame|mgmt_frame|crdn_frame function public.handle_packet(packet) - if packet ~= nil then - local l_chan = packet.scada_frame.local_channel() - local r_chan = packet.scada_frame.remote_channel() - local src_addr = packet.scada_frame.src_addr() - local protocol = packet.scada_frame.protocol() + local l_chan = packet.scada_frame.local_channel() + local r_chan = packet.scada_frame.remote_channel() + local src_addr = packet.scada_frame.src_addr() + local protocol = packet.scada_frame.protocol() + local i_seq_num = packet.scada_frame.seq_num() + 1 - if l_chan ~= config.SVR_Channel then - log.debug("received packet on unconfigured channel " .. l_chan, true) - elseif r_chan == config.PLC_Channel then - -- look for an associated session - local session = svsessions.find_plc_session(src_addr) + if l_chan ~= config.SVR_Channel then + log.debug("received packet on unconfigured channel " .. l_chan, true) + elseif r_chan == config.PLC_Channel then + -- look for an associated session + local session = svsessions.find_plc_session(src_addr) - if protocol == PROTOCOL.RPLC then - ---@cast packet rplc_frame - -- reactor PLC packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - else - -- any other packet should be session related, discard it - log.debug("discarding RPLC packet without a known session") - end - elseif protocol == PROTOCOL.SCADA_MGMT then - ---@cast packet mgmt_frame - -- SCADA management packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - elseif packet.type == MGMT_TYPE.ESTABLISH then - -- establish a new session - local last_ack = self.last_est_acks[src_addr] - - -- validate packet and continue - if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then - local comms_v = packet.data[1] - local firmware_v = packet.data[2] - local dev_type = packet.data[3] - - if comms_v ~= comms.version then - if last_ack ~= ESTABLISH_ACK.BAD_VERSION then - log.info(util.c("dropping PLC establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) - end - - _send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) - elseif dev_type == DEVICE_TYPE.PLC then - -- PLC linking request - if packet.length == 4 and type(packet.data[4]) == "number" then - local reactor_id = packet.data[4] - local plc_id = svsessions.establish_plc_session(src_addr, reactor_id, firmware_v) - - if plc_id == false then - -- reactor already has a PLC assigned - if last_ack ~= ESTABLISH_ACK.COLLISION then - log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) - end - - _send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION) - else - -- got an ID; assigned to a reactor successfully - println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected")) - log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id)) - _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) - end - else - log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type") - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) - end - else - log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC channel")) - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) - end - else - log.debug("invalid establish packet (on PLC channel)") - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) - end - else - -- any other packet should be session related, discard it - log.debug(util.c("discarding PLC SCADA_MGMT packet without a known session from computer ", src_addr)) - end + if protocol == PROTOCOL.RPLC then + ---@cast packet rplc_frame + -- reactor PLC packet + if session ~= nil then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) else - log.debug(util.c("illegal packet type ", protocol, " on PLC channel")) + -- any other packet should be session related, discard it + log.debug("discarding RPLC packet without a known session") end - elseif r_chan == config.RTU_Channel then - -- look for an associated session - local session = svsessions.find_rtu_session(src_addr) + elseif protocol == PROTOCOL.SCADA_MGMT then + ---@cast packet mgmt_frame + -- SCADA management packet + if session ~= nil then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + elseif packet.type == MGMT_TYPE.ESTABLISH then + -- establish a new session + local last_ack = self.last_est_acks[src_addr] - if protocol == PROTOCOL.MODBUS_TCP then - ---@cast packet modbus_frame - -- MODBUS response - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - else - -- any other packet should be session related, discard it - log.debug("discarding MODBUS_TCP packet without a known session") - end - elseif protocol == PROTOCOL.SCADA_MGMT then - ---@cast packet mgmt_frame - -- SCADA management packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - elseif packet.type == MGMT_TYPE.ESTABLISH then - -- establish a new session - local last_ack = self.last_est_acks[src_addr] + -- validate packet and continue + if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then + local comms_v = packet.data[1] + local firmware_v = packet.data[2] + local dev_type = packet.data[3] - -- validate packet and continue - if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then - local comms_v = packet.data[1] - local firmware_v = packet.data[2] - local dev_type = packet.data[3] - - if comms_v ~= comms.version then - if last_ack ~= ESTABLISH_ACK.BAD_VERSION then - log.info(util.c("dropping RTU establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) - end - - _send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) - elseif dev_type == DEVICE_TYPE.RTU then - if packet.length == 4 then - -- this is an RTU advertisement for a new session - local rtu_advert = packet.data[4] - local s_id = svsessions.establish_rtu_session(src_addr, rtu_advert, firmware_v) - - println(util.c("RTU (", firmware_v, ") [@", src_addr, "] \xbb connected")) - log.info(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) - else - log.debug("RTU_ESTABLISH: packet length mismatch") - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) - end - else - log.debug(util.c("illegal establish packet for device ", dev_type, " on RTU channel")) - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + if comms_v ~= comms.version then + if last_ack ~= ESTABLISH_ACK.BAD_VERSION then + log.info(util.c("dropping PLC establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) end - else - log.debug("invalid establish packet (on RTU channel)") - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) - end - else - -- any other packet should be session related, discard it - log.debug(util.c("discarding RTU SCADA_MGMT packet without a known session from computer ", src_addr)) - end - else - log.debug(util.c("illegal packet type ", protocol, " on RTU channel")) - end - elseif r_chan == config.CRD_Channel then - -- look for an associated session - local session = svsessions.find_crd_session(src_addr) - if protocol == PROTOCOL.SCADA_MGMT then - ---@cast packet mgmt_frame - -- SCADA management packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - elseif packet.type == MGMT_TYPE.ESTABLISH then - -- establish a new session - local last_ack = self.last_est_acks[src_addr] + _send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + elseif dev_type == DEVICE_TYPE.PLC then + -- PLC linking request + if packet.length == 4 and type(packet.data[4]) == "number" then + local reactor_id = packet.data[4] + local plc_id = svsessions.establish_plc_session(src_addr, i_seq_num, reactor_id, firmware_v) - -- validate packet and continue - if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then - local comms_v = packet.data[1] - local firmware_v = packet.data[2] - local dev_type = packet.data[3] - - if comms_v ~= comms.version then - if last_ack ~= ESTABLISH_ACK.BAD_VERSION then - log.info(util.c("dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) - end - - _send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) - elseif dev_type == DEVICE_TYPE.CRD then - -- this is an attempt to establish a new coordinator session - local s_id = svsessions.establish_crd_session(src_addr, firmware_v) - - if s_id ~= false then - println(util.c("CRD (", firmware_v, ") [@", src_addr, "] \xbb connected")) - log.info(util.c("CRD_ESTABLISH: coordinator (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - - _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, cooling_conf }) - else + if plc_id == false then + -- reactor already has a PLC assigned if last_ack ~= ESTABLISH_ACK.COLLISION then - log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator") + log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) end _send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION) + else + -- got an ID; assigned to a reactor successfully + println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected")) + log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id)) + _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) end else - log.debug(util.c("illegal establish packet for device ", dev_type, " on coordinator channel")) + log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type") _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) end else - log.debug("CRD_ESTABLISH: establish packet length mismatch") + log.debug(util.c("illegal establish packet for device ", dev_type, " on PLC channel")) _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) end else - -- any other packet should be session related, discard it - log.debug(util.c("discarding coordinator SCADA_MGMT packet without a known session from computer ", src_addr)) - end - elseif protocol == PROTOCOL.SCADA_CRDN then - ---@cast packet crdn_frame - -- coordinator packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - else - -- any other packet should be session related, discard it - log.debug(util.c("discarding coordinator SCADA_CRDN packet without a known session from computer ", src_addr)) + log.debug("invalid establish packet (on PLC channel)") + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) end else - log.debug(util.c("illegal packet type ", protocol, " on coordinator channel")) - end - elseif r_chan == config.PKT_Channel then - -- look for an associated session - local session = svsessions.find_pdg_session(src_addr) - - if protocol == PROTOCOL.SCADA_MGMT then - ---@cast packet mgmt_frame - -- SCADA management packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - elseif packet.type == MGMT_TYPE.ESTABLISH then - -- establish a new session - local last_ack = self.last_est_acks[src_addr] - - -- validate packet and continue - if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then - local comms_v = packet.data[1] - local firmware_v = packet.data[2] - local dev_type = packet.data[3] - - if comms_v ~= comms.version then - if last_ack ~= ESTABLISH_ACK.BAD_VERSION then - log.info(util.c("dropping PDG establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) - end - - _send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) - elseif dev_type == DEVICE_TYPE.PKT then - -- this is an attempt to establish a new pocket diagnostic session - local s_id = svsessions.establish_pdg_session(src_addr, firmware_v) - - println(util.c("PKT (", firmware_v, ") [@", src_addr, "] \xbb connected")) - log.info(util.c("PDG_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - - _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) - else - log.debug(util.c("illegal establish packet for device ", dev_type, " on pocket channel")) - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) - end - else - log.debug("PDG_ESTABLISH: establish packet length mismatch") - _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) - end - else - -- any other packet should be session related, discard it - log.debug(util.c("discarding pocket SCADA_MGMT packet without a known session from computer ", src_addr)) - end - elseif protocol == PROTOCOL.SCADA_CRDN then - ---@cast packet crdn_frame - -- coordinator packet - if session ~= nil then - -- pass the packet onto the session handler - session.in_queue.push_packet(packet) - else - -- any other packet should be session related, discard it - log.debug(util.c("discarding pocket SCADA_CRDN packet without a known session from computer ", src_addr)) - end - else - log.debug(util.c("illegal packet type ", protocol, " on pocket channel")) + -- any other packet should be session related, discard it + log.debug(util.c("discarding PLC SCADA_MGMT packet without a known session from computer ", src_addr)) end else - log.debug("received packet for unknown channel " .. r_chan, true) + log.debug(util.c("illegal packet type ", protocol, " on PLC channel")) end + elseif r_chan == config.RTU_Channel then + -- look for an associated session + local session = svsessions.find_rtu_session(src_addr) + + if protocol == PROTOCOL.MODBUS_TCP then + ---@cast packet modbus_frame + -- MODBUS response + if session ~= nil then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + else + -- any other packet should be session related, discard it + log.debug("discarding MODBUS_TCP packet without a known session") + end + elseif protocol == PROTOCOL.SCADA_MGMT then + ---@cast packet mgmt_frame + -- SCADA management packet + if session ~= nil then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + elseif packet.type == MGMT_TYPE.ESTABLISH then + -- establish a new session + local last_ack = self.last_est_acks[src_addr] + + -- validate packet and continue + if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then + local comms_v = packet.data[1] + local firmware_v = packet.data[2] + local dev_type = packet.data[3] + + if comms_v ~= comms.version then + if last_ack ~= ESTABLISH_ACK.BAD_VERSION then + log.info(util.c("dropping RTU establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) + end + + _send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + elseif dev_type == DEVICE_TYPE.RTU then + if packet.length == 4 then + -- this is an RTU advertisement for a new session + local rtu_advert = packet.data[4] + local s_id = svsessions.establish_rtu_session(src_addr, i_seq_num, rtu_advert, firmware_v) + + println(util.c("RTU (", firmware_v, ") [@", src_addr, "] \xbb connected")) + log.info(util.c("RTU_ESTABLISH: RTU (",firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) + _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) + else + log.debug("RTU_ESTABLISH: packet length mismatch") + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + log.debug(util.c("illegal establish packet for device ", dev_type, " on RTU channel")) + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + log.debug("invalid establish packet (on RTU channel)") + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + -- any other packet should be session related, discard it + log.debug(util.c("discarding RTU SCADA_MGMT packet without a known session from computer ", src_addr)) + end + else + log.debug(util.c("illegal packet type ", protocol, " on RTU channel")) + end + elseif r_chan == config.CRD_Channel then + -- look for an associated session + local session = svsessions.find_crd_session(src_addr) + + if protocol == PROTOCOL.SCADA_MGMT then + ---@cast packet mgmt_frame + -- SCADA management packet + if session ~= nil then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + elseif packet.type == MGMT_TYPE.ESTABLISH then + -- establish a new session + local last_ack = self.last_est_acks[src_addr] + + -- validate packet and continue + if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then + local comms_v = packet.data[1] + local firmware_v = packet.data[2] + local dev_type = packet.data[3] + + if comms_v ~= comms.version then + if last_ack ~= ESTABLISH_ACK.BAD_VERSION then + log.info(util.c("dropping coordinator establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) + end + + _send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + elseif dev_type == DEVICE_TYPE.CRD then + -- this is an attempt to establish a new coordinator session + local s_id = svsessions.establish_crd_session(src_addr, i_seq_num, firmware_v) + + if s_id ~= false then + println(util.c("CRD (", firmware_v, ") [@", src_addr, "] \xbb connected")) + log.info(util.c("CRD_ESTABLISH: coordinator (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) + + _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW, { config.UnitCount, cooling_conf }) + else + if last_ack ~= ESTABLISH_ACK.COLLISION then + log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator") + end + + _send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION) + end + else + log.debug(util.c("illegal establish packet for device ", dev_type, " on coordinator channel")) + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + log.debug("CRD_ESTABLISH: establish packet length mismatch") + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + -- any other packet should be session related, discard it + log.debug(util.c("discarding coordinator SCADA_MGMT packet without a known session from computer ", src_addr)) + end + elseif protocol == PROTOCOL.SCADA_CRDN then + ---@cast packet crdn_frame + -- coordinator packet + if session ~= nil then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + else + -- any other packet should be session related, discard it + log.debug(util.c("discarding coordinator SCADA_CRDN packet without a known session from computer ", src_addr)) + end + else + log.debug(util.c("illegal packet type ", protocol, " on coordinator channel")) + end + elseif r_chan == config.PKT_Channel then + -- look for an associated session + local session = svsessions.find_pdg_session(src_addr) + + if protocol == PROTOCOL.SCADA_MGMT then + ---@cast packet mgmt_frame + -- SCADA management packet + if session ~= nil then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + elseif packet.type == MGMT_TYPE.ESTABLISH then + -- establish a new session + local last_ack = self.last_est_acks[src_addr] + + -- validate packet and continue + if packet.length >= 3 and type(packet.data[1]) == "string" and type(packet.data[2]) == "string" then + local comms_v = packet.data[1] + local firmware_v = packet.data[2] + local dev_type = packet.data[3] + + if comms_v ~= comms.version then + if last_ack ~= ESTABLISH_ACK.BAD_VERSION then + log.info(util.c("dropping PDG establish packet with incorrect comms version v", comms_v, " (expected v", comms.version, ")")) + end + + _send_establish(packet.scada_frame, ESTABLISH_ACK.BAD_VERSION) + elseif dev_type == DEVICE_TYPE.PKT then + -- this is an attempt to establish a new pocket diagnostic session + local s_id = svsessions.establish_pdg_session(src_addr, i_seq_num, firmware_v) + + println(util.c("PKT (", firmware_v, ") [@", src_addr, "] \xbb connected")) + log.info(util.c("PDG_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) + + _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) + else + log.debug(util.c("illegal establish packet for device ", dev_type, " on pocket channel")) + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + log.debug("PDG_ESTABLISH: establish packet length mismatch") + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) + end + else + -- any other packet should be session related, discard it + log.debug(util.c("discarding pocket SCADA_MGMT packet without a known session from computer ", src_addr)) + end + elseif protocol == PROTOCOL.SCADA_CRDN then + ---@cast packet crdn_frame + -- coordinator packet + if session ~= nil then + -- pass the packet onto the session handler + session.in_queue.push_packet(packet) + else + -- any other packet should be session related, discard it + log.debug(util.c("discarding pocket SCADA_CRDN packet without a known session from computer ", src_addr)) + end + else + log.debug(util.c("illegal packet type ", protocol, " on pocket channel")) + end + else + log.debug("received packet for unknown channel " .. r_chan, true) end end From bc76c01aa56375185f9355e62fe3c2370b6b6fe0 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 29 Jun 2024 12:18:55 -0400 Subject: [PATCH 80/87] #504 fixed reactor idle status on pocket display --- pocket/iocontrol.lua | 5 ++++- pocket/startup.lua | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 38e642e..de69dd3 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -523,7 +523,10 @@ function iocontrol.record_unit_data(data) reactor_state = 6 -- SCRAM rps_status = 2 end - else rps_status = 4 end + else + rps_status = 4 + reactor_state = 4 + end -- update reactor/control status if unit.reactor_data.mek_status.status then diff --git a/pocket/startup.lua b/pocket/startup.lua index a80a4ca..7e5a742 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -20,7 +20,7 @@ local pocket = require("pocket.pocket") local renderer = require("pocket.renderer") local threads = require("pocket.threads") -local POCKET_VERSION = "v0.10.0-alpha" +local POCKET_VERSION = "v0.10.1-alpha" local println = util.println local println_ts = util.println_ts From 55dc203cdd713ad21ca42bc27dfd2e2d9a28fca4 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 29 Jun 2024 12:47:56 -0400 Subject: [PATCH 81/87] increment reactor plc version to 1.8.0 --- reactor-plc/startup.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index fc92536..6f6a27b 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -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.7.12" +local R_PLC_VERSION = "v1.8.0" local println = util.println local println_ts = util.println_ts From 2de30ef06448e8f81af7bdacba4bcf86c79ebc05 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 29 Jun 2024 14:10:58 -0400 Subject: [PATCH 82/87] #488 fixes to sequence number changes and auth packet data --- coordinator/coordinator.lua | 13 +++++++++---- coordinator/session/pocket.lua | 9 +++++---- pocket/pocket.lua | 22 ++++++++++++++++------ reactor-plc/plc.lua | 10 +++++++--- rtu/rtu.lua | 10 +++++++--- scada-common/comms.lua | 2 +- scada-common/network.lua | 10 +++++----- supervisor/session/coordinator.lua | 9 +++++---- supervisor/session/plc.lua | 9 +++++---- supervisor/session/pocket.lua | 9 +++++---- supervisor/session/rtu.lua | 9 +++++---- supervisor/session/svsessions.lua | 8 ++++---- supervisor/supervisor.lua | 2 +- 13 files changed, 75 insertions(+), 47 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 9e98a33..27543ec 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -233,6 +233,7 @@ function coordinator.comms(version, nic, sv_watchdog) sv_linked = false, sv_addr = comms.BROADCAST, sv_seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate + sv_r_seq_num = nil, ---@type nil|integer sv_config_err = false, last_est_ack = ESTABLISH_ACK.ALLOW, last_api_est_acks = {}, @@ -369,6 +370,7 @@ function coordinator.comms(version, nic, sv_watchdog) sv_watchdog.cancel() self.sv_addr = comms.BROADCAST self.sv_linked = false + self.sv_r_seq_num = nil iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED) _send_sv(PROTOCOL.SCADA_MGMT, MGMT_TYPE.CLOSE, {}) end @@ -490,7 +492,7 @@ function coordinator.comms(version, nic, sv_watchdog) _send_api_establish_ack(packet.scada_frame, ESTABLISH_ACK.BAD_API_VERSION) elseif dev_type == DEVICE_TYPE.PKT then -- pocket linking request - local id = apisessions.establish_session(src_addr, packet.scada_frame.seq_num() + 1, firmware_v) + local id = apisessions.establish_session(src_addr, packet.scada_frame.seq_num(), firmware_v) coordinator.log_comms(util.c("API_ESTABLISH: pocket (", firmware_v, ") [@", src_addr, "] connected with session ID ", id)) local conf = iocontrol.get_db().facility.conf @@ -512,14 +514,16 @@ function coordinator.comms(version, nic, sv_watchdog) end elseif r_chan == config.SVR_Channel then -- check sequence number - if self.sv_seq_num ~= packet.scada_frame.seq_num() then - log.warning("sequence out-of-order: last = " .. self.sv_seq_num .. ", new = " .. packet.scada_frame.seq_num()) + if self.sv_r_seq_num == nil then + self.sv_r_seq_num = packet.scada_frame.seq_num() + 1 + elseif self.sv_r_seq_num ~= packet.scada_frame.seq_num() then + log.warning("sequence out-of-order: last = " .. self.sv_r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) return false elseif self.sv_linked and src_addr ~= self.sv_addr then log.debug("received packet from unknown computer " .. src_addr .. " while linked; channel in use by another system?") return false else - self.sv_seq_num = packet.scada_frame.seq_num() + 1 + self.sv_r_seq_num = packet.scada_frame.seq_num() + 1 end -- feed watchdog on valid sequence number @@ -671,6 +675,7 @@ function coordinator.comms(version, nic, sv_watchdog) sv_watchdog.cancel() self.sv_addr = comms.BROADCAST self.sv_linked = false + self.sv_r_seq_num = nil iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED) log.info("server connection closed by remote host") else diff --git a/coordinator/session/pocket.lua b/coordinator/session/pocket.lua index f2f59e4..c554c64 100644 --- a/coordinator/session/pocket.lua +++ b/coordinator/session/pocket.lua @@ -41,7 +41,8 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) local self = { -- connection properties - seq_num = i_seq_num, + seq_num = i_seq_num + 2, -- next after the establish approval was sent + r_seq_num = i_seq_num + 1, connected = true, conn_watchdog = util.new_watchdog(timeout), last_rtt = 0, @@ -104,11 +105,11 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) ---@param pkt mgmt_frame|crdn_frame local function _handle_packet(pkt) -- check sequence number - if self.seq_num ~= pkt.scada_frame.seq_num() then - log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + if self.r_seq_num ~= pkt.scada_frame.seq_num() then + log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) return else - self.seq_num = pkt.scada_frame.seq_num() + 1 + self.r_seq_num = pkt.scada_frame.seq_num() + 1 end -- feed watchdog diff --git a/pocket/pocket.lua b/pocket/pocket.lua index fb4f41a..82b87b5 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -371,12 +371,14 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) linked = false, addr = comms.BROADCAST, seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate + r_seq_num = nil, ---@type nil|integer last_est_ack = ESTABLISH_ACK.ALLOW }, api = { linked = false, addr = comms.BROADCAST, seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate + r_seq_num = nil, ---@type nil|integer last_est_ack = ESTABLISH_ACK.ALLOW }, establish_delay_counter = 0 @@ -465,6 +467,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) nav.unload_sv() self.sv.linked = false self.sv.addr = comms.BROADCAST + self.sv.r_seq_num = nil _send_sv(MGMT_TYPE.CLOSE, {}) end @@ -474,6 +477,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) nav.unload_api() self.api.linked = false self.api.addr = comms.BROADCAST + self.api.r_seq_num = nil _send_crd(MGMT_TYPE.CLOSE, {}) end @@ -599,15 +603,17 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) log.debug("received packet on unconfigured channel " .. l_chan, true) elseif r_chan == config.CRD_Channel then -- check sequence number - if self.api.seq_num ~= packet.scada_frame.seq_num() then - log.warning("sequence out-of-order (API): last = " .. self.api.seq_num .. ", new = " .. packet.scada_frame.seq_num()) + if self.api.r_seq_num == nil then + self.api.r_seq_num = packet.scada_frame.seq_num() + 1 + elseif self.api.r_seq_num ~= packet.scada_frame.seq_num() then + log.warning("sequence out-of-order (API): last = " .. self.api.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) return 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 .. "); channel in use by another system?") return else - self.api.seq_num = packet.scada_frame.seq_num() + 1 + self.api.r_seq_num = packet.scada_frame.seq_num() + 1 end -- feed watchdog on valid sequence number @@ -653,6 +659,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) nav.unload_api() self.api.linked = false self.api.addr = comms.BROADCAST + self.api.r_seq_num = nil log.info("coordinator server connection closed by remote host") else _fail_type(packet) end elseif packet.type == MGMT_TYPE.ESTABLISH then @@ -716,15 +723,17 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) end elseif r_chan == config.SVR_Channel then -- check sequence number - if self.sv.seq_num ~= packet.scada_frame.seq_num() then - log.warning("sequence out-of-order (SVR): last = " .. self.sv.seq_num .. ", new = " .. packet.scada_frame.seq_num()) + if self.sv.r_seq_num == nil then + self.sv.r_seq_num = packet.scada_frame.seq_num() + 1 + elseif self.sv.r_seq_num ~= packet.scada_frame.seq_num() then + log.warning("sequence out-of-order (SVR): last = " .. self.sv.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) return elseif self.sv.linked and (src_addr ~= self.sv.addr) then log.debug("received packet from unknown computer " .. src_addr .. " while linked (SVR expected " .. self.sv.addr .. "); channel in use by another system?") return else - self.sv.seq_num = packet.scada_frame.seq_num() + 1 + self.sv.r_seq_num = packet.scada_frame.seq_num() + 1 end -- feed watchdog on valid sequence number @@ -756,6 +765,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) nav.unload_sv() self.sv.linked = false self.sv.addr = comms.BROADCAST + self.sv.r_seq_num = nil log.info("supervisor server connection closed by remote host") elseif packet.type == MGMT_TYPE.DIAG_TONE_GET then if _check_length(packet, 8) then diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 3eead7e..3a61e37 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -525,6 +525,7 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) local self = { sv_addr = comms.BROADCAST, seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate + r_seq_num = nil, ---@type nil|integer scrammed = false, linked = false, last_est_ack = ESTABLISH_ACK.ALLOW, @@ -715,6 +716,7 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) self.sv_addr = comms.BROADCAST self.linked = false self.status_cache = nil + self.r_seq_num = nil databus.tx_link_state(types.PANEL_LINK_STATE.DISCONNECTED) end @@ -822,15 +824,17 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) -- handle packets now that we have prints setup if l_chan == config.PLC_Channel then -- check sequence number - if self.seq_num ~= packet.scada_frame.seq_num() then - log.warning("sequence out-of-order: last = " .. self.seq_num .. ", new = " .. packet.scada_frame.seq_num()) + if self.r_seq_num == nil then + self.r_seq_num = packet.scada_frame.seq_num() + 1 + elseif self.r_seq_num ~= packet.scada_frame.seq_num() then + log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) return elseif self.linked and (src_addr ~= self.sv_addr) then log.debug("received packet from unknown computer " .. src_addr .. " while linked (expected " .. self.sv_addr .. "); channel in use by another system?") return else - self.seq_num = packet.scada_frame.seq_num() + 1 + self.r_seq_num = packet.scada_frame.seq_num() + 1 end -- feed the watchdog first so it doesn't uhh...eat our packets :) diff --git a/rtu/rtu.lua b/rtu/rtu.lua index 0278f42..231c261 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -285,6 +285,7 @@ function rtu.comms(version, nic, conn_watchdog) local self = { sv_addr = comms.BROADCAST, seq_num = util.time_ms() * 10, -- unique per peer, restarting will not re-use seq nums due to message rate + r_seq_num = nil, ---@type nil|integer txn_id = 0, last_est_ack = ESTABLISH_ACK.ALLOW } @@ -362,6 +363,7 @@ function rtu.comms(version, nic, conn_watchdog) function public.unlink(rtu_state) rtu_state.linked = false self.sv_addr = comms.BROADCAST + self.r_seq_num = nil databus.tx_link_state(types.PANEL_LINK_STATE.DISCONNECTED) end @@ -439,15 +441,17 @@ function rtu.comms(version, nic, conn_watchdog) if l_chan == config.RTU_Channel then -- check sequence number - if self.seq_num ~= packet.scada_frame.seq_num() then - log.warning("sequence out-of-order: last = " .. self.seq_num .. ", new = " .. packet.scada_frame.seq_num()) + if self.r_seq_num == nil then + self.r_seq_num = packet.scada_frame.seq_num() + 1 + elseif self.r_seq_num ~= packet.scada_frame.seq_num() then + log.warning("sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. packet.scada_frame.seq_num()) return elseif rtu_state.linked and (src_addr ~= self.sv_addr) then log.debug("received packet from unknown computer " .. src_addr .. " while linked (expected " .. self.sv_addr .. "); channel in use by another system?") return else - self.seq_num = packet.scada_frame.seq_num() + 1 + self.r_seq_num = packet.scada_frame.seq_num() + 1 end -- feed watchdog on valid sequence number diff --git a/scada-common/comms.lua b/scada-common/comms.lua index e31c60b..bfedd2c 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -294,7 +294,7 @@ function comms.authd_packet() self.src_addr = s_packet.src_addr() self.dest_addr = s_packet.dest_addr() self.mac = mac(textutils.serialize(s_packet.raw_header(), { allow_repetitions = true, compact = true })) - self.raw = { self.src_addr, self.dest_addr, self.mac, s_packet.data() } + self.raw = { self.src_addr, self.dest_addr, self.mac, s_packet.raw_sendable() } end -- parse in a modem message as an authenticated SCADA packet diff --git a/scada-common/network.lua b/scada-common/network.lua index 4dce34e..c34a1d5 100644 --- a/scada-common/network.lua +++ b/scada-common/network.lua @@ -80,7 +80,7 @@ end ---@param modem table modem to use function network.nic(modem) local self = { - connected = true, -- used to avoid costly MAC calculations if modem isn't even present + connected = true, -- used to avoid costly MAC calculations if modem isn't even present channels = {} } @@ -175,7 +175,7 @@ function network.nic(modem) ---@param packet scada_packet packet function public.transmit(dest_channel, local_channel, packet) if self.connected then - local tx_packet = packet ---@type authd_packet|scada_packet + local tx_packet = packet ---@type authd_packet|scada_packet if c_eng.hmac ~= nil then -- local start = util.time_ms() @@ -214,13 +214,13 @@ function network.nic(modem) s_packet.receive(side, sender, reply_to, a_packet.data(), distance) if s_packet.is_valid() then - -- local start = util.time_ms() + -- local start = util.time_ms() local computed_hmac = compute_hmac(textutils.serialize(s_packet.raw_header(), { allow_repetitions = true, compact = true })) if a_packet.mac() == computed_hmac then -- log.debug("network.modem.receive: HMAC verified in " .. (util.time_ms() - start) .. "ms") - s_packet.stamp_authenticated() - else + s_packet.stamp_authenticated() + else -- log.debug("network.modem.receive: HMAC failed verification in " .. (util.time_ms() - start) .. "ms") end end diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index f449650..a135a44 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -58,7 +58,8 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim local self = { units = facility.get_units(), -- connection properties - seq_num = i_seq_num, + seq_num = i_seq_num + 2, -- next after the establish approval was sent + r_seq_num = i_seq_num + 1, connected = true, conn_watchdog = util.new_watchdog(timeout), establish_time = util.time_s(), @@ -182,11 +183,11 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim ---@param pkt mgmt_frame|crdn_frame local function _handle_packet(pkt) -- check sequence number - if self.seq_num ~= pkt.scada_frame.seq_num() then - log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + if self.r_seq_num ~= pkt.scada_frame.seq_num() then + log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) return else - self.seq_num = pkt.scada_frame.seq_num() + 1 + self.r_seq_num = pkt.scada_frame.seq_num() + 1 end -- feed watchdog diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index fb5ca1b..4aad6d3 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -67,7 +67,8 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue, ramping_rate = false, auto_lock = false, -- connection properties - seq_num = i_seq_num, + seq_num = i_seq_num + 2, -- next after the establish approval was sent + r_seq_num = i_seq_num + 1, connected = true, received_struct = false, received_status_cache = false, @@ -349,11 +350,11 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue, ---@param pkt mgmt_frame|rplc_frame local function _handle_packet(pkt) -- check sequence number - if self.seq_num ~= pkt.scada_frame.seq_num() then - log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + if self.r_seq_num ~= pkt.scada_frame.seq_num() then + log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) return else - self.seq_num = pkt.scada_frame.seq_num() + 1 + self.r_seq_num = pkt.scada_frame.seq_num() + 1 end -- process packet diff --git a/supervisor/session/pocket.lua b/supervisor/session/pocket.lua index 145add2..94a4467 100644 --- a/supervisor/session/pocket.lua +++ b/supervisor/session/pocket.lua @@ -44,7 +44,8 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, local self = { -- connection properties - seq_num = i_seq_num, + seq_num = i_seq_num + 2, -- next after the establish approval was sent + r_seq_num = i_seq_num + 1, connected = true, conn_watchdog = util.new_watchdog(timeout), last_rtt = 0, @@ -93,11 +94,11 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ---@param pkt mgmt_frame local function _handle_packet(pkt) -- check sequence number - if self.seq_num ~= pkt.scada_frame.seq_num() then - log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + if self.r_seq_num ~= pkt.scada_frame.seq_num() then + log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) return else - self.seq_num = pkt.scada_frame.seq_num() + 1 + self.r_seq_num = pkt.scada_frame.seq_num() + 1 end -- feed watchdog diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 789e649..756ee01 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -52,7 +52,8 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad advert = advertisement, fac_units = facility.get_units(), -- connection properties - seq_num = i_seq_num, + seq_num = i_seq_num + 2, -- next after the establish approval was sent + r_seq_num = i_seq_num + 1, connected = true, conn_watchdog = util.new_watchdog(timeout), last_rtt = 0, @@ -240,11 +241,11 @@ function rtu.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout, ad ---@param pkt modbus_frame|mgmt_frame local function _handle_packet(pkt) -- check sequence number - if self.seq_num ~= pkt.scada_frame.seq_num() then - log.warning(log_header .. "sequence out-of-order: last = " .. self.seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + if self.r_seq_num ~= pkt.scada_frame.seq_num() then + log.warning(log_header .. "sequence out-of-order: last = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) return else - self.seq_num = pkt.scada_frame.seq_num() + 1 + self.r_seq_num = pkt.scada_frame.seq_num() + 1 end -- feed watchdog diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index c7c24fc..002dd56 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -274,7 +274,7 @@ end -- establish a new PLC session ---@nodiscard ---@param source_addr integer PLC computer ID ----@param i_seq_num integer initial sequence number to use next +---@param i_seq_num integer initial (most recent) sequence number ---@param for_reactor integer unit ID ---@param version string PLC version ---@return integer|false session_id @@ -324,7 +324,7 @@ end -- establish a new RTU gateway session ---@nodiscard ---@param source_addr integer RTU gateway computer ID ----@param i_seq_num integer initial sequence number to use next +---@param i_seq_num integer initial (most recent) sequence number ---@param advertisement table RTU capability advertisement ---@param version string RTU gateway version ---@return integer session_id @@ -365,7 +365,7 @@ end -- establish a new coordinator session ---@nodiscard ---@param source_addr integer coordinator computer ID ----@param i_seq_num integer initial sequence number to use next +---@param i_seq_num integer initial (most recent) sequence number ---@param version string coordinator version ---@return integer|false session_id function svsessions.establish_crd_session(source_addr, i_seq_num, version) @@ -410,7 +410,7 @@ end -- establish a new pocket diagnostics session ---@nodiscard ---@param source_addr integer pocket computer ID ----@param i_seq_num integer initial sequence number to use next +---@param i_seq_num integer initial (most recent) sequence number ---@param version string pocket version ---@return integer|false session_id function svsessions.establish_pdg_session(source_addr, i_seq_num, version) diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 2d69b7c..69a98f5 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -197,7 +197,7 @@ function supervisor.comms(_version, nic, fp_ok) local r_chan = packet.scada_frame.remote_channel() local src_addr = packet.scada_frame.src_addr() local protocol = packet.scada_frame.protocol() - local i_seq_num = packet.scada_frame.seq_num() + 1 + local i_seq_num = packet.scada_frame.seq_num() if l_chan ~= config.SVR_Channel then log.debug("received packet on unconfigured channel " .. l_chan, true) From 8e14fa1591a8ab9aefc1a141c90e86910abc1e49 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sat, 29 Jun 2024 18:30:32 +0000 Subject: [PATCH 83/87] disable a diagnostic message in ccmsi --- ccmsi.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/ccmsi.lua b/ccmsi.lua index cc4a5d0..86f2094 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -138,6 +138,7 @@ local function gen_tree(manifest) for i = 1, #list do local split = {} +---@diagnostic disable-next-line: discard-returns string.gsub(list[i], "([^/]+)", function(c) split[#split + 1] = c end) if #split == 1 then table.insert(tree, list[i]) else table.insert(tree, _tree_add(tree, split)) end From f2937b47e9c41cf1f8906b13567c9292bbb91db7 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 29 Jun 2024 14:49:26 -0400 Subject: [PATCH 84/87] cleanup --- coordinator/session/apisessions.lua | 2 +- reactor-plc/plc.lua | 2 +- scada-common/comms.lua | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coordinator/session/apisessions.lua b/coordinator/session/apisessions.lua index 4daa45d..5c4a38a 100644 --- a/coordinator/session/apisessions.lua +++ b/coordinator/session/apisessions.lua @@ -90,7 +90,7 @@ end -- establish a new API session ---@nodiscard ---@param source_addr integer pocket computer ID ----@param i_seq_num integer initial sequence number to use next +---@param i_seq_num integer initial (most recent) sequence number ---@param version string pocket version ---@return integer session_id function apisessions.establish_session(source_addr, i_seq_num, version) diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 3a61e37..c89974f 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -715,8 +715,8 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) function public.unlink() self.sv_addr = comms.BROADCAST self.linked = false - self.status_cache = nil self.r_seq_num = nil + self.status_cache = nil databus.tx_link_state(types.PANEL_LINK_STATE.DISCONNECTED) end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index bfedd2c..fa6f6ff 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -280,7 +280,7 @@ function comms.authd_packet() src_addr = comms.BROADCAST, dest_addr = comms.BROADCAST, mac = "", - payload = nil + payload = {} } ---@class authd_packet From c05a45c29aeefbb39245a35c06f2ba42db4b0c76 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 29 Jun 2024 14:51:15 -0400 Subject: [PATCH 85/87] more cleanup --- pocket/pocket.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 82b87b5..fab0827 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -466,8 +466,8 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) sv_watchdog.cancel() nav.unload_sv() self.sv.linked = false - self.sv.addr = comms.BROADCAST self.sv.r_seq_num = nil + self.sv.addr = comms.BROADCAST _send_sv(MGMT_TYPE.CLOSE, {}) end @@ -476,8 +476,8 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) api_watchdog.cancel() nav.unload_api() self.api.linked = false - self.api.addr = comms.BROADCAST self.api.r_seq_num = nil + self.api.addr = comms.BROADCAST _send_crd(MGMT_TYPE.CLOSE, {}) end @@ -658,8 +658,8 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) api_watchdog.cancel() nav.unload_api() self.api.linked = false - self.api.addr = comms.BROADCAST self.api.r_seq_num = nil + self.api.addr = comms.BROADCAST log.info("coordinator server connection closed by remote host") else _fail_type(packet) end elseif packet.type == MGMT_TYPE.ESTABLISH then @@ -764,8 +764,8 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) sv_watchdog.cancel() nav.unload_sv() self.sv.linked = false - self.sv.addr = comms.BROADCAST self.sv.r_seq_num = nil + self.sv.addr = comms.BROADCAST log.info("supervisor server connection closed by remote host") elseif packet.type == MGMT_TYPE.DIAG_TONE_GET then if _check_length(packet, 8) then From a4add9370c538061803d704986f7708425196f9e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 29 Jun 2024 15:08:11 -0400 Subject: [PATCH 86/87] RTU modem init consistency and cleanup --- coordinator/startup.lua | 6 +++--- rtu/startup.lua | 36 +++++++++++++++++++----------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 9e20631..1960e58 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") local threads = require("coordinator.threads") -local COORDINATOR_VERSION = "v1.5.0" +local COORDINATOR_VERSION = "v1.5.1" local CHUNK_LOAD_DELAY_S = 30.0 @@ -151,8 +151,8 @@ local function main() -- core coordinator devices crd_dev = { - speaker = ppm.get_device("speaker"), - modem = ppm.get_wireless_modem() + modem = ppm.get_wireless_modem(), + speaker = ppm.get_device("speaker") }, -- system objects diff --git a/rtu/startup.lua b/rtu/startup.lua index 00f27c7..c5000ea 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -31,7 +31,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.10.0" +local RTU_VERSION = "v1.10.1" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE @@ -93,14 +93,6 @@ local function main() network.init_mac(config.AuthKey) end - -- get modem - local modem = ppm.get_wireless_modem() - if modem == nil then - println("boot> wireless modem not found") - log.fatal("no wireless modem on startup") - return - end - -- generate alarm tones audio.generate_tones() @@ -116,14 +108,15 @@ local function main() -- RTU gateway devices (not RTU units) rtu_dev = { + modem = ppm.get_wireless_modem(), sounders = {} }, -- system objects rtu_sys = { - nic = network.nic(modem), - rtu_comms = nil, ---@type rtu_comms - conn_watchdog = nil, ---@type watchdog + nic = nil, ---@type nic + rtu_comms = nil, ---@type rtu_comms + conn_watchdog = nil, ---@type watchdog units = {} }, @@ -134,8 +127,9 @@ local function main() } local smem_sys = __shared_memory.rtu_sys + local smem_dev = __shared_memory.rtu_dev - databus.tx_hw_modem(true) + local rtu_state = __shared_memory.rtu_state ---------------------------------------- -- interpret config and init units @@ -501,8 +495,6 @@ local function main() -- start system ---------------------------------------- - local rtu_state = __shared_memory.rtu_state - log.debug("boot> running sys_config()") if sys_config() then @@ -517,23 +509,33 @@ local function main() log.info("startup> running in headless mode without front panel") end + -- check modem + if smem_dev.modem == nil then + println("startup> wireless modem not found") + log.fatal("no wireless modem on startup") + return + end + + databus.tx_hw_modem(true) + -- find and setup all speakers local speakers = ppm.get_all_devices("speaker") for _, s in pairs(speakers) do local sounder = rtu.init_sounder(s) - table.insert(__shared_memory.rtu_dev.sounders, sounder) + table.insert(smem_dev.sounders, sounder) log.debug(util.c("startup> added speaker, attached as ", sounder.name)) end - databus.tx_hw_spkr_count(#__shared_memory.rtu_dev.sounders) + databus.tx_hw_spkr_count(#smem_dev.sounders) -- start connection watchdog smem_sys.conn_watchdog = util.new_watchdog(config.ConnTimeout) log.debug("startup> conn watchdog started") -- setup comms + smem_sys.nic = network.nic(smem_dev.modem) smem_sys.rtu_comms = rtu.comms(RTU_VERSION, smem_sys.nic, smem_sys.conn_watchdog) log.debug("startup> comms init") From e868fd33973a51c8aee221545e3dbd6816ef2074 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 29 Jun 2024 15:11:16 -0400 Subject: [PATCH 87/87] cleanup of bootloader --- startup.lua | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/startup.lua b/startup.lua index 811f510..662d989 100644 --- a/startup.lua +++ b/startup.lua @@ -1,35 +1,31 @@ -local util = require("scada-common.util") +local BOOTLOADER_VERSION = "1.1" -local println = util.println - -local BOOTLOADER_VERSION = "1.0" - -println("SCADA BOOTLOADER V" .. BOOTLOADER_VERSION) -println("BOOT> SCANNING FOR APPLICATIONS...") +print("SCADA BOOTLOADER V" .. BOOTLOADER_VERSION) +print("BOOT> SCANNING FOR APPLICATIONS...") local exit_code if fs.exists("reactor-plc/startup.lua") then - println("BOOT> EXEC REACTOR PLC STARTUP") + print("BOOT> EXEC REACTOR PLC STARTUP") exit_code = shell.execute("reactor-plc/startup") elseif fs.exists("rtu/startup.lua") then - println("BOOT> EXEC RTU STARTUP") + print("BOOT> EXEC RTU STARTUP") exit_code = shell.execute("rtu/startup") elseif fs.exists("supervisor/startup.lua") then - println("BOOT> EXEC SUPERVISOR STARTUP") + print("BOOT> EXEC SUPERVISOR STARTUP") exit_code = shell.execute("supervisor/startup") elseif fs.exists("coordinator/startup.lua") then - println("BOOT> EXEC COORDINATOR STARTUP") + print("BOOT> EXEC COORDINATOR STARTUP") exit_code = shell.execute("coordinator/startup") elseif fs.exists("pocket/startup.lua") then - println("BOOT> EXEC POCKET STARTUP") + print("BOOT> EXEC POCKET STARTUP") exit_code = shell.execute("pocket/startup") else - println("BOOT> NO SCADA STARTUP FOUND") - println("BOOT> EXIT") + print("BOOT> NO SCADA STARTUP FOUND") + print("BOOT> EXIT") return false end -if not exit_code then println("BOOT> APPLICATION CRASHED") end +if not exit_code then print("BOOT> APPLICATION CRASHED") end return exit_code