From 610fb12bb348a13fc44cc37c7d5eff3ba7086ce1 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 26 Aug 2024 13:52:47 +0000 Subject: [PATCH 01/40] actions dependency version updates --- .github/workflows/check.yml | 2 +- .github/workflows/manifest.yml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 9caba85..8adb100 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v3.5.1 + uses: actions/checkout@v4 - name: Luacheck uses: lunarmodules/luacheck@v1.1.0 with: diff --git a/.github/workflows/manifest.yml b/.github/workflows/manifest.yml index 2e5c5f8..35bce7a 100644 --- a/.github/workflows/manifest.yml +++ b/.github/workflows/manifest.yml @@ -29,13 +29,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup Pages - uses: actions/configure-pages@v3 + uses: actions/configure-pages@v5 - name: Setup Python - uses: actions/setup-python@v3.1.3 + uses: actions/setup-python@v5 # Generate manifest + shields files for main branch - name: Checkout main id: checkout-main - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: 'main' clean: false @@ -54,7 +54,7 @@ jobs: - name: Checkout devel id: checkout-devel if: success() || failure() - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: 'devel' clean: false @@ -69,11 +69,11 @@ jobs: - name: Upload artifacts id: upload-artifacts if: ${{ (success() || failure()) && (steps.manifest-main.outcome == 'success' || steps.manifest-latest.outcome == 'success' || steps.manifest-devel.outcome == 'success') }} - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v3 with: # Upload manifest JSON path: 'deploy/' - name: Deploy to GitHub Pages if: ${{ (success() || failure()) && steps.upload-artifacts.outcome == 'success' }} id: deployment - uses: actions/deploy-pages@v2 + uses: actions/deploy-pages@v4 From 705494bb7ee76946d0c3293d62e0e11809a4b044 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 26 Aug 2024 13:55:13 +0000 Subject: [PATCH 02/40] specify python version --- .github/workflows/manifest.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/manifest.yml b/.github/workflows/manifest.yml index 35bce7a..b9a91db 100644 --- a/.github/workflows/manifest.yml +++ b/.github/workflows/manifest.yml @@ -32,6 +32,8 @@ jobs: uses: actions/configure-pages@v5 - name: Setup Python uses: actions/setup-python@v5 + with: + python-version: '3.10' # Generate manifest + shields files for main branch - name: Checkout main id: checkout-main From f4be6519e84704b5f87e780b22e300b14eb21201 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 26 Aug 2024 20:30:30 -0400 Subject: [PATCH 03/40] refactoring and removed unused set_waste_ack --- coordinator/coordinator.lua | 6 +++--- coordinator/iocontrol.lua | 1 - coordinator/process.lua | 6 +++--- supervisor/facility.lua | 2 +- supervisor/session/coordinator.lua | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index e6c7671..7d5ce0f 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -386,7 +386,7 @@ function coordinator.comms(version, nic, sv_watchdog) end -- send the auto process control configuration with a start command - ---@param auto_cfg coord_auto_config configuration + ---@param auto_cfg sys_auto_config configuration function public.send_auto_start(auto_cfg) _send_sv(PROTOCOL.SCADA_CRDN, CRDN_TYPE.FAC_CMD, { FAC_COMMAND.START, auto_cfg.mode, auto_cfg.burn_target, auto_cfg.charge_target, auto_cfg.gen_target, auto_cfg.limits @@ -632,11 +632,11 @@ function coordinator.comms(version, nic, sv_watchdog) elseif cmd == UNIT_COMMAND.SET_BURN then unit.set_burn_ack(ack) elseif cmd == UNIT_COMMAND.SET_WASTE then - unit.set_waste_ack(ack) + -- updated by unit updates elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then unit.ack_alarms_ack(ack) elseif cmd == UNIT_COMMAND.SET_GROUP then - -- UI will be updated to display current group if changed successfully + -- updated by unit updates else log.debug(util.c("received unit command ack with unknown command ", cmd)) end diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 7444ef7..636a8b7 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -200,7 +200,6 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale) reset_rps_ack = __generic_ack, ack_alarms_ack = __generic_ack, set_burn_ack = __generic_ack, - set_waste_ack = __generic_ack, alarm_callbacks = { c_breach = { ack = function () ack(1) end, reset = function () reset(1) end }, diff --git a/coordinator/process.lua b/coordinator/process.lua index 0615e30..277b83f 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -19,9 +19,9 @@ local process = {} local self = { io = nil, ---@type ioctl comms = nil, ---@type coord_comms - ---@class coord_control_states + ---@class sys_control_states control_states = { - ---@class coord_auto_config + ---@class sys_auto_config process = { mode = PROCESS.INACTIVE, burn_target = 0.0, @@ -55,7 +55,7 @@ function process.init(iocontrol, coord_comms) end local ctrl_states = settings.get("ControlStates", {}) - local config = ctrl_states.process ---@type coord_auto_config + local config = ctrl_states.process ---@type sys_auto_config -- facility auto control configuration if type(config) == "table" then diff --git a/supervisor/facility.lua b/supervisor/facility.lua index e25c8c8..88486bc 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -379,7 +379,7 @@ function facility.new(config) function public.auto_stop() self.mode = PROCESS.INACTIVE end -- set automatic control configuration and start the process - ---@param auto_cfg coord_auto_config configuration + ---@param auto_cfg sys_auto_config configuration ---@return table response ready state (successfully started) and current configuration (after updating) function public.auto_start(auto_cfg) local charge_scaler = 1000000 -- convert MFE to FE diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 99aa027..4d0dcd6 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -245,7 +245,7 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim _send(CRDN_TYPE.FAC_CMD, { cmd, true }) elseif cmd == FAC_COMMAND.START then if pkt.length == 6 then - ---@type coord_auto_config + ---@type sys_auto_config local config = { mode = pkt.data[2], burn_target = pkt.data[3], From 61ff055d60f579580cad99d135b0b14f1c5b4f95 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 26 Aug 2024 20:31:36 -0400 Subject: [PATCH 04/40] allow right alignment for numeric inputs --- graphics/core.lua | 37 +++++++++++++++++++------ graphics/elements/form/number_field.lua | 11 ++++++-- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/graphics/core.lua b/graphics/core.lua index 394cbb3..c07b281 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -123,15 +123,17 @@ end -- Interactive Field Manager ----@param e graphics_base ----@param max_len any ----@param fg_bg any ----@param dis_fg_bg any -function core.new_ifield(e, max_len, fg_bg, dis_fg_bg) +---@param e graphics_base element +---@param max_len any max value length +---@param fg_bg any enabled fg/bg +---@param dis_fg_bg any disabled fg/bg +---@param align_right any true to align content right while unfocused +function core.new_ifield(e, max_len, fg_bg, dis_fg_bg, align_right) local self = { frame_start = 1, visible_text = e.value, cursor_pos = string.len(e.value) + 1, + align_offset = 0, selected_all = false } @@ -186,7 +188,12 @@ function core.new_ifield(e, max_len, fg_bg, dis_fg_bg) e.w_write(string.rep(" ", e.frame.w)) e.w_set_cur(1, 1) - local function _write() + local function _write(align_r) + if align_r and string.len(self.visible_text) <=e.frame.w then + self.align_offset = (e.frame.w - string.len(self.visible_text)) + e.w_set_cur((e.frame.w - string.len(self.visible_text)) + 1, 1) + end + if self.censor then e.w_write(string.rep(self.censor, string.len(self.visible_text))) else @@ -226,15 +233,27 @@ function core.new_ifield(e, max_len, fg_bg, dis_fg_bg) self.selected_all = false -- write text without cursor - _write() + _write(align_right) end end - -- move cursor to x + -- get an x value to pass to move_cursor taking into account right alignment offset present when unfocused ---@param x integer + function public.get_cursor_align_shift(x) + return math.max(0, x - self.align_offset) + end + + -- move cursor to x + ---@param x integer x position or 0 to jump to the end function public.move_cursor(x) self.selected_all = false - self.cursor_pos = math.min(x, string.len(self.visible_text) + 1) + + if x <= 0 then + self.cursor_pos = string.len(self.visible_text) + 1 + else + self.cursor_pos = math.min(x, string.len(self.visible_text) + 1) + end + public.show() end diff --git a/graphics/elements/form/number_field.lua b/graphics/elements/form/number_field.lua index ef383ce..01a4dad 100644 --- a/graphics/elements/form/number_field.lua +++ b/graphics/elements/form/number_field.lua @@ -17,6 +17,7 @@ local MOUSE_CLICK = core.events.MOUSE_CLICK ---@field max_frac_digits? integer maximum number of fractional digits, enforced on unfocus ---@field allow_decimal? boolean true to allow decimals ---@field allow_negative? boolean true to allow negative numbers +---@field align_right? boolean true to align right while unfocused ---@field dis_fg_bg? cpair foreground/background colors when disabled ---@field parent graphics_element ---@field id? string element id @@ -47,7 +48,7 @@ local function number_field(args) e.value = "" .. (args.default or 0) -- make an interactive field manager - local ifield = core.new_ifield(e, args.max_chars, args.fg_bg, args.dis_fg_bg) + local ifield = core.new_ifield(e, args.max_chars, args.fg_bg, args.dis_fg_bg, args.align_right) -- handle mouse interaction ---@param event mouse_interaction mouse event @@ -55,10 +56,16 @@ local function number_field(args) -- only handle if on an increment or decrement arrow if e.enabled and e.in_frame_bounds(event.current.x, event.current.y) then if core.events.was_clicked(event.type) then + local x = event.current.x + + if not e.is_focused() then + x = ifield.get_cursor_align_shift(x) + end + e.take_focus() if event.type == MOUSE_CLICK.UP then - ifield.move_cursor(event.current.x) + ifield.move_cursor(x) end elseif event.type == MOUSE_CLICK.DOUBLE_CLICK then ifield.select_all() From 11e9c11cf7366f2163436bcad424ee5649dfcf98 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 27 Aug 2024 23:00:29 -0400 Subject: [PATCH 05/40] GitHub and Discord links in pocket guide --- pocket/ui/apps/guide.lua | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index a4daf8a..f737aeb 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -78,6 +78,7 @@ local function new_view(root) 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 lnk_page = app.new_page(main_page, 7) local home = Div{parent=page_div,x=2} local search = Div{parent=page_div,x=2} @@ -85,7 +86,8 @@ local function new_view(root) 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,width=p_width} - local panes = { home, search, use, uis, fps, gls } + local lnk = Div{parent=page_div,x=2,width=p_width} + local panes = { home, search, use, uis, fps, gls, lnk } local doc_map = {} local search_db = {} @@ -100,6 +102,7 @@ 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} PushButton{parent=home,text="Glossary >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=gls_page.nav_to} + PushButton{parent=home,y=10,text="Wiki and Discord >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=lnk_page.nav_to} TextBox{parent=search,y=1,text="Search",alignment=ALIGN.CENTER} @@ -214,6 +217,19 @@ local function new_view(root) 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} + TextBox{parent=lnk,y=1,text="Wiki and Discord",alignment=ALIGN.CENTER} + PushButton{parent=lnk,x=1,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} + + lnk.line_break() + TextBox{parent=lnk,text="GitHub",alignment=ALIGN.LEFT,fg_bg=cpair(colors.lightGray,colors.black)} + TextBox{parent=lnk,text="https://github.com/MikaylaFischler/cc-mek-scada"} + lnk.line_break() + TextBox{parent=lnk,text="Wiki",alignment=ALIGN.LEFT,fg_bg=cpair(colors.lightGray,colors.black)} + TextBox{parent=lnk,text="https://github.com/MikaylaFischler/cc-mek-scada/wiki"} + lnk.line_break() + TextBox{parent=lnk,text="Discord",alignment=ALIGN.LEFT,fg_bg=cpair(colors.lightGray,colors.black)} + TextBox{parent=lnk,text="discord.gg/R9NSCkhcwt"} + -- setup multipane local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes} app.set_root_pane(u_pane) From c299dce8ef32c26c33fe5230fe582dd40649224e Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 27 Aug 2024 23:02:31 -0400 Subject: [PATCH 06/40] #498 work on pocket control app and support process code --- pocket/iocontrol.lua | 30 ++- pocket/pocket.lua | 36 +++- pocket/process.lua | 342 ++++++++++++++++++++++++++++++++++ pocket/ui/apps/control.lua | 199 ++++++++++++++++++++ pocket/ui/main.lua | 2 + pocket/ui/pages/home_page.lua | 2 +- 6 files changed, 604 insertions(+), 7 deletions(-) create mode 100644 pocket/process.lua create mode 100644 pocket/ui/apps/control.lua diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index d7964fb..0e97602 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -8,6 +8,8 @@ local psil = require("scada-common.psil") local types = require("scada-common.types") local util = require("scada-common.util") +local process = require("pocket.process") + local ALARM = types.ALARM local ALARM_STATE = types.ALARM_STATE @@ -38,6 +40,15 @@ local io = { ps = psil.create() } +-- luacheck: no unused args + +-- placeholder acknowledge function for type hinting +---@param success boolean +---@diagnostic disable-next-line: unused-local +local function __generic_ack(success) end + +-- luacheck: unused args + local config = nil ---@type pkt_config -- initialize facility-independent components of pocket iocontrol @@ -91,6 +102,9 @@ function iocontrol.init_core(comms, nav, cfg) io.api = { get_unit = function (unit) comms.api__get_unit(unit) end } + + -- pass IO control here since it can't be require'd due to a require loop + process.init(io, comms) end -- initialize facility-dependent components of pocket iocontrol @@ -300,6 +314,20 @@ function iocontrol.init_fac(conf) -- auto control group a_group = 0, + start = function () process.start(i) end, + scram = function () process.scram(i) end, + reset_rps = function () process.reset_rps(i) end, + ack_alarms = function () process.ack_all_alarms(i) end, + set_burn = function (rate) process.set_rate(i, rate) end, ---@param rate number burn rate + set_waste = function (mode) process.set_unit_waste(i, mode) end, ---@param mode WASTE_MODE waste processing mode + + set_group = function (grp) process.set_group(i, grp) end, ---@param grp integer|0 group ID or 0 for manual + + start_ack = __generic_ack, + scram_ack = __generic_ack, + reset_rps_ack = __generic_ack, + ack_alarms_ack = __generic_ack, + ---@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 }, @@ -689,7 +717,7 @@ function iocontrol.record_unit_data(data) --#region Status Information Display - local ecam = {} -- aviation reference :) back to VATSIM I go... + local ecam = {} -- aviation reference :) -- local function red(text) return { text = text, color = colors.red } end local function white(text) return { text = text, color = colors.white } end diff --git a/pocket/pocket.lua b/pocket/pocket.lua index a8867fb..060329b 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -9,6 +9,8 @@ local DEVICE_TYPE = comms.DEVICE_TYPE local ESTABLISH_ACK = comms.ESTABLISH_ACK local MGMT_TYPE = comms.MGMT_TYPE local CRDN_TYPE = comms.CRDN_TYPE +local UNIT_COMMAND = comms.UNIT_COMMAND +local FAC_COMMAND = comms.FAC_COMMAND local LINK_STATE = iocontrol.LINK_STATE @@ -84,13 +86,14 @@ local APP_ID = { LOADER = 2, -- main app pages UNITS = 3, - GUIDE = 4, - ABOUT = 5, + CONTROL = 4, + GUIDE = 5, + ABOUT = 6, -- diag app page - ALARMS = 6, + ALARMS = 7, -- other - DUMMY = 7, - NUM_APPS = 7 + DUMMY = 8, + NUM_APPS = 8 } pocket.APP_ID = APP_ID @@ -541,6 +544,29 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) if self.api.linked then _send_api(CRDN_TYPE.API_GET_UNIT, { unit }) end end + -- send a facility command + ---@param cmd FAC_COMMAND command + ---@param option any? optional option options for the optional options (like waste mode) + function public.send_fac_command(cmd, option) + _send_api(CRDN_TYPE.FAC_CMD, { cmd, option }) + end + + -- send the auto process control configuration with a start command + ---@param auto_cfg sys_auto_config configuration + function public.send_auto_start(auto_cfg) + _send_api(CRDN_TYPE.FAC_CMD, { + FAC_COMMAND.START, auto_cfg.mode, auto_cfg.burn_target, auto_cfg.charge_target, auto_cfg.gen_target, auto_cfg.limits + }) + end + + -- send a unit command + ---@param cmd UNIT_COMMAND command + ---@param unit integer unit ID + ---@param option any? optional option options for the optional options (like burn rate) + function public.send_unit_command(cmd, unit, option) + _send_api(CRDN_TYPE.UNIT_CMD, { cmd, unit, option }) + end + -- parse a packet ---@param side string ---@param sender integer diff --git a/pocket/process.lua b/pocket/process.lua new file mode 100644 index 0000000..06578d5 --- /dev/null +++ b/pocket/process.lua @@ -0,0 +1,342 @@ +-- +-- Process Control Management +-- + +local comms = require("scada-common.comms") +local log = require("scada-common.log") +local types = require("scada-common.types") +local util = require("scada-common.util") + +local FAC_COMMAND = comms.FAC_COMMAND +local UNIT_COMMAND = comms.UNIT_COMMAND + +local PROCESS = types.PROCESS +local PRODUCT = types.WASTE_PRODUCT + +---@class pocket_process_controller +local process = {} + +local self = { + io = nil, ---@type ioctl + comms = nil, ---@type pocket_comms + ---@class sys_control_states + control_states = { + ---@class sys_auto_config + process = { + mode = PROCESS.INACTIVE, + burn_target = 0.0, + charge_target = 0.0, + gen_target = 0.0, + limits = {}, + waste_product = PRODUCT.PLUTONIUM, + pu_fallback = false, + sps_low_power = false + }, + waste_modes = {}, + priority_groups = {} + } +} + +-------------------------- +-- UNIT COMMAND CONTROL -- +-------------------------- + +-- initialize the process controller +---@param iocontrol pocket_ioctl iocontrl system +---@param pocket_comms pocket_comms pocket communications +function process.init(iocontrol, pocket_comms) + self.io = iocontrol + self.comms = pocket_comms + + local ctl_proc = self.control_states.process + + for i = 1, self.io.facility.num_units do + ctl_proc.limits[i] = 0.1 + end + + local ctrl_states = settings.get("ControlStates", {}) + local config = ctrl_states.process ---@type sys_auto_config + + -- facility auto control configuration + if type(config) == "table" then + ctl_proc.mode = config.mode + ctl_proc.burn_target = config.burn_target + ctl_proc.charge_target = config.charge_target + ctl_proc.gen_target = config.gen_target + 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) + self.io.facility.ps.publish("process_charge_target", self.io.energy_convert_from_fe(ctl_proc.charge_target)) + self.io.facility.ps.publish("process_gen_target", self.io.energy_convert_from_fe(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 + unit.unit_ps.publish("burn_limit", ctl_proc.limits[id]) + end + + log.info("PROCESS: loaded auto control settings") + + -- 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 + local waste_modes = ctrl_states.waste_modes ---@type table|nil + if type(waste_modes) == "table" then + for id, mode in pairs(waste_modes) do + self.control_states.waste_modes[id] = mode + self.comms.send_unit_command(UNIT_COMMAND.SET_WASTE, id, mode) + end + + log.info("PROCESS: loaded unit waste mode settings") + end + + -- unit priority groups + local prio_groups = ctrl_states.priority_groups ---@type table|nil + if type(prio_groups) == "table" then + for id, group in pairs(prio_groups) do + self.control_states.priority_groups[id] = group + self.comms.send_unit_command(UNIT_COMMAND.SET_GROUP, id, group) + end + + log.info("PROCESS: loaded priority groups settings") + end +end + +function process.init_fac_data() +end + +-- facility SCRAM command +function process.fac_scram() + self.comms.send_fac_command(FAC_COMMAND.SCRAM_ALL) + log.debug("PROCESS: FAC SCRAM ALL") +end + +-- facility alarm acknowledge command +function process.fac_ack_alarms() + self.comms.send_fac_command(FAC_COMMAND.ACK_ALL_ALARMS) + log.debug("PROCESS: FAC ACK ALL ALARMS") +end + +-- start reactor +---@param id integer unit ID +function process.start(id) + self.io.units[id].control_state = true + self.comms.send_unit_command(UNIT_COMMAND.START, id) + log.debug(util.c("PROCESS: UNIT[", id, "] START")) +end + +-- SCRAM reactor +---@param id integer unit ID +function process.scram(id) + self.io.units[id].control_state = false + self.comms.send_unit_command(UNIT_COMMAND.SCRAM, id) + log.debug(util.c("PROCESS: UNIT[", id, "] SCRAM")) +end + +-- reset reactor protection system +---@param id integer unit ID +function process.reset_rps(id) + self.comms.send_unit_command(UNIT_COMMAND.RESET_RPS, id) + log.debug(util.c("PROCESS: UNIT[", id, "] RESET RPS")) +end + +-- set burn rate +---@param id integer unit ID +---@param rate number burn rate +function process.set_rate(id, rate) + self.comms.send_unit_command(UNIT_COMMAND.SET_BURN, id, rate) + log.debug(util.c("PROCESS: UNIT[", id, "] SET BURN ", rate)) +end + +-- set waste mode +---@param id integer unit ID +---@param mode integer waste mode +function process.set_unit_waste(id, mode) + -- publish so that if it fails then it gets reset + self.io.units[id].unit_ps.publish("U_WasteMode", mode) + + self.comms.send_unit_command(UNIT_COMMAND.SET_WASTE, id, mode) + log.debug(util.c("PROCESS: UNIT[", id, "] SET WASTE ", mode)) + + self.control_states.waste_modes[id] = mode + settings.set("ControlStates", self.control_states) + + if not settings.save("/coordinator.settings") then + log.error("process.set_unit_waste(): failed to save coordinator settings file") + end +end + +-- acknowledge all alarms +---@param id integer unit ID +function process.ack_all_alarms(id) + self.comms.send_unit_command(UNIT_COMMAND.ACK_ALL_ALARMS, id) + log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALL ALARMS")) +end + +-- acknowledge an alarm +---@param id integer unit ID +---@param alarm integer alarm ID +function process.ack_alarm(id, alarm) + self.comms.send_unit_command(UNIT_COMMAND.ACK_ALARM, id, alarm) + log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALARM ", alarm)) +end + +-- reset an alarm +---@param id integer unit ID +---@param alarm integer alarm ID +function process.reset_alarm(id, alarm) + self.comms.send_unit_command(UNIT_COMMAND.RESET_ALARM, id, alarm) + log.debug(util.c("PROCESS: UNIT[", id, "] RESET ALARM ", alarm)) +end + +-- assign a unit to a group +---@param unit_id integer unit ID +---@param group_id integer|0 group ID or 0 for independent +function process.set_group(unit_id, group_id) + self.comms.send_unit_command(UNIT_COMMAND.SET_GROUP, unit_id, group_id) + log.debug(util.c("PROCESS: UNIT[", unit_id, "] SET GROUP ", group_id)) + + self.control_states.priority_groups[unit_id] = group_id + settings.set("ControlStates", self.control_states) + + if not settings.save("/coordinator.settings") then + log.error("process.set_group(): failed to save coordinator settings file") + end +end + +-------------------------- +-- AUTO PROCESS CONTROL -- +-------------------------- + +-- write auto process control to config file +local function _write_auto_config() + -- save config + settings.set("ControlStates", self.control_states) + local saved = settings.save("/coordinator.settings") + if not saved then + log.warning("process._write_auto_config(): failed to save coordinator settings file") + end + + return saved +end + +-- stop automatic process control +function process.stop_auto() + self.comms.send_fac_command(FAC_COMMAND.STOP) + log.debug("PROCESS: STOP AUTO CTL") +end + +-- start automatic process control +function process.start_auto() + self.comms.send_auto_start(self.control_states.process) + log.debug("PROCESS: START AUTO CTL") +end + +-- set automatic process control waste mode +---@param product WASTE_PRODUCT waste product for auto control +function process.set_process_waste(product) + self.comms.send_fac_command(FAC_COMMAND.SET_WASTE_MODE, product) + + log.debug(util.c("PROCESS: SET WASTE ", product)) + + -- update config table and save + self.control_states.process.waste_product = product + _write_auto_config() +end + +-- set automatic process control plutonium fallback +---@param enabled boolean whether to enable plutonium fallback +function process.set_pu_fallback(enabled) + self.comms.send_fac_command(FAC_COMMAND.SET_PU_FB, enabled) + + log.debug(util.c("PROCESS: SET PU FALLBACK ", enabled)) + + -- update config table and save + self.control_states.process.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 +---@param charge_target number charge target +---@param gen_target number generation rate target +---@param limits table unit burn rate limits +function process.save(mode, burn_target, charge_target, gen_target, limits) + log.debug("PROCESS: SAVE") + + -- update config table + local ctl_proc = self.control_states.process + ctl_proc.mode = mode + ctl_proc.burn_target = burn_target + ctl_proc.charge_target = charge_target + ctl_proc.gen_target = gen_target + ctl_proc.limits = limits + + -- save config + self.io.facility.save_cfg_ack(_write_auto_config()) +end + +-- handle a start command acknowledgement +---@param response table ack and configuration reply +function process.start_ack_handle(response) + local ack = response[1] + + local ctl_proc = self.control_states.process + ctl_proc.mode = response[2] + ctl_proc.burn_target = response[3] + ctl_proc.charge_target = response[4] + ctl_proc.gen_target = response[5] + + for i = 1, math.min(#response[6], self.io.facility.num_units) do + ctl_proc.limits[i] = response[6][i] + + local unit = self.io.units[i] ---@type ioctl_unit + unit.unit_ps.publish("burn_limit", ctl_proc.limits[i]) + end + + self.io.facility.ps.publish("process_mode", ctl_proc.mode) + self.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target) + self.io.facility.ps.publish("process_charge_target", self.io.energy_convert_from_fe(ctl_proc.charge_target)) + self.io.facility.ps.publish("process_gen_target", self.io.energy_convert_from_fe(ctl_proc.gen_target)) + + self.io.facility.start_ack(ack) +end + +-- record waste product state after attempting to change it +---@param response WASTE_PRODUCT supervisor waste product state +function process.waste_ack_handle(response) + self.control_states.process.waste_product = response + self.io.facility.ps.publish("process_waste_product", response) +end + +-- record plutonium fallback state after attempting to change it +---@param response boolean supervisor plutonium fallback state +function process.pu_fb_ack_handle(response) + self.control_states.process.pu_fallback = response + self.io.facility.ps.publish("process_pu_fallback", response) +end + +return process diff --git a/pocket/ui/apps/control.lua b/pocket/ui/apps/control.lua new file mode 100644 index 0000000..cc533d2 --- /dev/null +++ b/pocket/ui/apps/control.lua @@ -0,0 +1,199 @@ +-- +-- Unit Control Page +-- + +local util = require("scada-common.util") + +local iocontrol = require("pocket.iocontrol") +local pocket = require("pocket.pocket") + +local style = require("pocket.ui.style") + +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 WaitingAnim = require("graphics.elements.animations.waiting") + +local HazardButton = require("graphics.elements.controls.hazard_button") +local PushButton = require("graphics.elements.controls.push_button") + +local NumberField = require("graphics.elements.form.number_field") + +local DataIndicator = require("graphics.elements.indicators.data") +local IconIndicator = require("graphics.elements.indicators.icon") + +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 +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 hzd_fg_bg = cpair(colors.white, colors.gray) +local dis_colors = cpair(colors.white, colors.lightGray) + +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 = "+" } +} + +-- new unit control page view +---@param root graphics_element parent +local function new_view(root) + local db = iocontrol.get_db() + + local frame = Div{parent=root,x=1,y=1} + + local app = db.nav.register_app(APP_ID.CONTROL, frame, nil, false, true) + + 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...",alignment=ALIGN.CENTER} + WaitingAnim{parent=load_div,x=math.floor(main.get_width()/2)-1,y=8,fg_bg=cpair(colors.green,colors._INHERIT)} + + local load_pane = MultiPane{parent=main,x=1,y=1,panes={load_div,main}} + + 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.green, colors.black) + local btn_active = cpair(colors.white, colors.black) + + local page_div = nil ---@type nil|graphics_element + + -- set sidebar to display unit-specific fields based on a specified unit + local function set_sidebar() + local list = { + { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end }, + } + + for i = 1, db.facility.num_units do + table.insert(list, { label = "U-" .. i, color = core.cpair(colors.black, colors.lightGray), callback = function () app.switcher(i) end }) + end + + app.set_sidebar(list) + end + + -- load the app (create the elements) + local function load() + page_div = Div{parent=main,y=2,width=main.get_width()} + + local panes = {} + + local active_unit = 1 + + -- create all page divs + for _ = 1, db.facility.num_units do + local div = Div{parent=page_div} + table.insert(panes, div) + end + + -- previous unit + local function prev(x) + active_unit = util.trinary(x == 1, db.facility.num_units, x - 1) + app.switcher(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) + end + + for i = 1, db.facility.num_units do + 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 + + -- 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 + db.api.get_unit(i) + last_update = util.time_ms() + end + end + + --#region 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,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 rate = DataIndicator{parent=u_div,y=3,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=6,label="Control State",states=mode_states} + + rate.register(u_ps, "act_burn_rate", rate.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() + + TextBox{parent=u_div,y=8,text="CMD",width=4,fg_bg=cpair(colors.lightGray,colors.black)} + TextBox{parent=u_div,x=14,y=8,text="mB/t",width=4,fg_bg=cpair(colors.lightGray,colors.black)} + local burn_cmd = NumberField{parent=u_div,x=5,y=8,width=8,default=0.01,min=0.01,max_frac_digits=2,max_chars=8,allow_decimal=true,align_right=true,fg_bg=cpair(colors.white,colors.gray)} + + local set_burn = function () unit.set_burn(burn_cmd.get_value()) end + local set_burn_btn = PushButton{parent=u_div,x=19,y=8,text="SET",min_width=5,fg_bg=cpair(colors.green,colors.black),active_fg_bg=cpair(colors.white,colors.black),dis_fg_bg=cpair(colors.gray,colors.black),callback=set_burn} + + burn_cmd.register(u_ps, "max_burn", burn_cmd.set_max) + + local start = HazardButton{parent=u_div,x=2,y=11,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=hzd_fg_bg} + local ack_a = HazardButton{parent=u_div,x=12,y=11,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=unit.ack_alarms,fg_bg=hzd_fg_bg} + local scram = HazardButton{parent=u_div,x=2,y=15,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=hzd_fg_bg} + local reset = HazardButton{parent=u_div,x=12,y=15,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,fg_bg=hzd_fg_bg} + + --#endregion + + util.nop() + end + + -- setup multipane + local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes} + app.set_root_pane(u_pane) + + set_sidebar() + + -- done, show the app + load_pane.set_value(2) + end + + -- 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 + +return new_view diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index 080371c..bf66f9b 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -7,6 +7,7 @@ local util = require("scada-common.util") local iocontrol = require("pocket.iocontrol") local pocket = require("pocket.pocket") +local control_app = require("pocket.ui.apps.control") 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") @@ -62,6 +63,7 @@ local function init(main) -- create all the apps & pages home_page(page_div) unit_app(page_div) + control_app(page_div) guide_app(page_div) loader_app(page_div) sys_apps(page_div) diff --git a/pocket/ui/pages/home_page.lua b/pocket/ui/pages/home_page.lua index b143728..38385be 100644 --- a/pocket/ui/pages/home_page.lua +++ b/pocket/ui/pages/home_page.lua @@ -47,7 +47,7 @@ local function new_view(root) 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=16,y=2,text="\x15",title="Control",callback=function()open(APP_ID.CONTROL)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} From 0f40c1d7f250283f4779277fe00eb219113ed2ba Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 27 Aug 2024 23:03:42 -0400 Subject: [PATCH 07/40] removed unused set burn ack --- coordinator/coordinator.lua | 2 +- coordinator/iocontrol.lua | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 7d5ce0f..a71ca42 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -630,7 +630,7 @@ function coordinator.comms(version, nic, sv_watchdog) elseif cmd == UNIT_COMMAND.RESET_RPS then unit.reset_rps_ack(ack) elseif cmd == UNIT_COMMAND.SET_BURN then - unit.set_burn_ack(ack) + -- this also doesn't exist elseif cmd == UNIT_COMMAND.SET_WASTE then -- updated by unit updates elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 636a8b7..ff59be1 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -199,7 +199,6 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale) scram_ack = __generic_ack, reset_rps_ack = __generic_ack, ack_alarms_ack = __generic_ack, - set_burn_ack = __generic_ack, alarm_callbacks = { c_breach = { ack = function () ack(1) end, reset = function () reset(1) end }, From fbbd7e1ccdd4e60504976ca98f90e411ee16a4eb Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 27 Aug 2024 23:05:46 -0400 Subject: [PATCH 08/40] WIP rearchitecting process command orchestration --- coordinator/iocontrol.lua | 11 +- coordinator/process.lua | 281 +++++++++++++--------- coordinator/ui/components/process_ctl.lua | 4 +- 3 files changed, 181 insertions(+), 115 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index ff59be1..de2b8cc 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -82,6 +82,9 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale) io.energy_convert_to_fe = util.joules_to_fe_rf end + -- coordinator's process handle + io.process = process.create_handle() + -- facility data structure ---@class ioctl_facility io.facility = { @@ -186,10 +189,10 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale) -- auto control group a_group = 0, - start = function () process.start(i) end, - scram = function () process.scram(i) end, - reset_rps = function () process.reset_rps(i) end, - ack_alarms = function () process.ack_all_alarms(i) end, + start = function () io.process.start(i) end, + scram = function () io.process.scram(i) end, + reset_rps = function () io.process.reset_rps(i) end, + ack_alarms = function () io.process.ack_all_alarms(i) end, set_burn = function (rate) process.set_rate(i, rate) end, ---@param rate number burn rate set_waste = function (mode) process.set_unit_waste(i, mode) end, ---@param mode WASTE_MODE waste processing mode diff --git a/coordinator/process.lua b/coordinator/process.lua index 277b83f..32c8e37 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -7,16 +7,18 @@ local log = require("scada-common.log") local types = require("scada-common.types") local util = require("scada-common.util") -local FAC_COMMAND = comms.FAC_COMMAND -local UNIT_COMMAND = comms.UNIT_COMMAND +local F_CMD = comms.FAC_COMMAND +local U_CMD = comms.UNIT_COMMAND local PROCESS = types.PROCESS local PRODUCT = types.WASTE_PRODUCT +local REQUEST_TIMEOUT_MS = 5000 + ---@class process_controller local process = {} -local self = { +local pctl = { io = nil, ---@type ioctl comms = nil, ---@type coord_comms ---@class sys_control_states @@ -34,23 +36,36 @@ local self = { }, waste_modes = {}, priority_groups = {} - } + }, + next_handle = 0, + commands = { unit = {}, fac = {} } } --------------------------- --- UNIT COMMAND CONTROL -- --------------------------- +for _, v in pairs(U_CMD) do pctl.commands.unit[v] = { active = false, timeout = 0, requestors = {} } end +for _, v in pairs(F_CMD) do pctl.commands.fac[v] = { active = false, timeout = 0, requestors = {} } end + +-- write auto process control to config file +local function _write_auto_config() + -- save config + settings.set("ControlStates", pctl.control_states) + local saved = settings.save("/coordinator.settings") + if not saved then + log.warning("process._write_auto_config(): failed to save coordinator settings file") + end + + return saved +end -- initialize the process controller ---@param iocontrol ioctl iocontrl system ---@param coord_comms coord_comms coordinator communications function process.init(iocontrol, coord_comms) - self.io = iocontrol - self.comms = coord_comms + pctl.io = iocontrol + pctl.comms = coord_comms - local ctl_proc = self.control_states.process + local ctl_proc = pctl.control_states.process - for i = 1, self.io.facility.num_units do + for i = 1, pctl.io.facility.num_units do ctl_proc.limits[i] = 0.1 end @@ -68,33 +83,33 @@ function process.init(iocontrol, coord_comms) 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) - self.io.facility.ps.publish("process_charge_target", self.io.energy_convert_from_fe(ctl_proc.charge_target)) - self.io.facility.ps.publish("process_gen_target", self.io.energy_convert_from_fe(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) + pctl.io.facility.ps.publish("process_mode", ctl_proc.mode) + pctl.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target) + pctl.io.facility.ps.publish("process_charge_target", pctl.io.energy_convert_from_fe(ctl_proc.charge_target)) + pctl.io.facility.ps.publish("process_gen_target", pctl.io.energy_convert_from_fe(ctl_proc.gen_target)) + pctl.io.facility.ps.publish("process_waste_product", ctl_proc.waste_product) + pctl.io.facility.ps.publish("process_pu_fallback", ctl_proc.pu_fallback) + pctl.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 + for id = 1, math.min(#ctl_proc.limits, pctl.io.facility.num_units) do + local unit = pctl.io.units[id] ---@type ioctl_unit unit.unit_ps.publish("burn_limit", ctl_proc.limits[id]) end log.info("PROCESS: loaded auto control settings") -- 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) + pctl.comms.send_fac_command(F_CMD.SET_WASTE_MODE, ctl_proc.waste_product) + pctl.comms.send_fac_command(F_CMD.SET_PU_FB, ctl_proc.pu_fallback) + pctl.comms.send_fac_command(F_CMD.SET_SPS_LP, ctl_proc.sps_low_power) end -- unit waste states local waste_modes = ctrl_states.waste_modes ---@type table|nil if type(waste_modes) == "table" then for id, mode in pairs(waste_modes) do - self.control_states.waste_modes[id] = mode - self.comms.send_unit_command(UNIT_COMMAND.SET_WASTE, id, mode) + pctl.control_states.waste_modes[id] = mode + pctl.comms.send_unit_command(U_CMD.SET_WASTE, id, mode) end log.info("PROCESS: loaded unit waste mode settings") @@ -104,54 +119,119 @@ function process.init(iocontrol, coord_comms) local prio_groups = ctrl_states.priority_groups ---@type table|nil if type(prio_groups) == "table" then for id, group in pairs(prio_groups) do - self.control_states.priority_groups[id] = group - self.comms.send_unit_command(UNIT_COMMAND.SET_GROUP, id, group) + pctl.control_states.priority_groups[id] = group + pctl.comms.send_unit_command(U_CMD.SET_GROUP, id, group) end log.info("PROCESS: loaded priority groups settings") end end --- facility SCRAM command -function process.fac_scram() - self.comms.send_fac_command(FAC_COMMAND.SCRAM_ALL) - log.debug("PROCESS: FAC SCRAM ALL") +-- create a handle to process control for usage of commands that get acknowledgements +function process.create_handle() + local self = { + id = pctl.next_handle + } + + pctl.next_handle = pctl.next_handle + 1 + + ---@class process_handle + local handle = {} + + local function request(cmd) + local new = not cmd.active + + if new then + cmd.active = true + cmd.timeout = util.time_ms() + REQUEST_TIMEOUT_MS + end + + cmd.requstors[self.id] = true + + return new + end + + local function u_request(cmd_id) return request(pctl.commands.unit[cmd_id]) end + local function f_request(cmd_id) return request(pctl.commands.fac[cmd_id]) end + + --#region Facility Commands + + -- facility SCRAM command + function handle.fac_scram() + if f_request(F_CMD.SCRAM_ALL) then + pctl.comms.send_fac_command(F_CMD.SCRAM_ALL) + log.debug("PROCESS: FAC SCRAM ALL") + end + end + + -- facility alarm acknowledge command + function handle.fac_ack_alarms() + if f_request(F_CMD.ACK_ALL_ALARMS) then + pctl.comms.send_fac_command(F_CMD.ACK_ALL_ALARMS) + log.debug("PROCESS: FAC ACK ALL ALARMS") + end + end + + --#endregion + + --#region Unit Commands + + -- start a reactor + ---@param id integer unit ID + function handle.start(id) + if u_request(U_CMD.START) then + pctl.io.units[id].control_state = true + pctl.comms.send_unit_command(U_CMD.START, id) + log.debug(util.c("PROCESS: UNIT[", id, "] START")) + end + end + + -- SCRAM reactor + ---@param id integer unit ID + function handle.scram(id) + if u_request(U_CMD.SCRAM) then + pctl.io.units[id].control_state = false + pctl.comms.send_unit_command(U_CMD.SCRAM, id) + log.debug(util.c("PROCESS: UNIT[", id, "] SCRAM")) + end + end + + -- reset reactor protection system + ---@param id integer unit ID + function handle.reset_rps(id) + if u_request(U_CMD.RESET_RPS) then + pctl.comms.send_unit_command(U_CMD.RESET_RPS, id) + log.debug(util.c("PROCESS: UNIT[", id, "] RESET RPS")) + end + end + + -- acknowledge all alarms + ---@param id integer unit ID + function handle.ack_all_alarms(id) + if u_request(U_CMD.ACK_ALL_ALARMS) then + pctl.comms.send_unit_command(U_CMD.ACK_ALL_ALARMS, id) + log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALL ALARMS")) + end + end + + --#endregion + + return handle end --- facility alarm acknowledge command -function process.fac_ack_alarms() - self.comms.send_fac_command(FAC_COMMAND.ACK_ALL_ALARMS) - log.debug("PROCESS: FAC ACK ALL ALARMS") +function process.clear_timed_out() end --- start reactor ----@param id integer unit ID -function process.start(id) - self.io.units[id].control_state = true - self.comms.send_unit_command(UNIT_COMMAND.START, id) - log.debug(util.c("PROCESS: UNIT[", id, "] START")) +function process.handle_ack() end --- SCRAM reactor ----@param id integer unit ID -function process.scram(id) - self.io.units[id].control_state = false - self.comms.send_unit_command(UNIT_COMMAND.SCRAM, id) - log.debug(util.c("PROCESS: UNIT[", id, "] SCRAM")) -end - --- reset reactor protection system ----@param id integer unit ID -function process.reset_rps(id) - self.comms.send_unit_command(UNIT_COMMAND.RESET_RPS, id) - log.debug(util.c("PROCESS: UNIT[", id, "] RESET RPS")) -end +--#region One-Way Commands (no acknowledgements) -- set burn rate ---@param id integer unit ID ---@param rate number burn rate function process.set_rate(id, rate) - self.comms.send_unit_command(UNIT_COMMAND.SET_BURN, id, rate) + pctl.comms.send_unit_command(U_CMD.SET_BURN, id, rate) log.debug(util.c("PROCESS: UNIT[", id, "] SET BURN ", rate)) end @@ -160,31 +240,24 @@ end ---@param mode integer waste mode function process.set_unit_waste(id, mode) -- publish so that if it fails then it gets reset - self.io.units[id].unit_ps.publish("U_WasteMode", mode) + pctl.io.units[id].unit_ps.publish("U_WasteMode", mode) - self.comms.send_unit_command(UNIT_COMMAND.SET_WASTE, id, mode) + pctl.comms.send_unit_command(U_CMD.SET_WASTE, id, mode) log.debug(util.c("PROCESS: UNIT[", id, "] SET WASTE ", mode)) - self.control_states.waste_modes[id] = mode - settings.set("ControlStates", self.control_states) + pctl.control_states.waste_modes[id] = mode + settings.set("ControlStates", pctl.control_states) if not settings.save("/coordinator.settings") then log.error("process.set_unit_waste(): failed to save coordinator settings file") end end --- acknowledge all alarms ----@param id integer unit ID -function process.ack_all_alarms(id) - self.comms.send_unit_command(UNIT_COMMAND.ACK_ALL_ALARMS, id) - log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALL ALARMS")) -end - -- acknowledge an alarm ---@param id integer unit ID ---@param alarm integer alarm ID function process.ack_alarm(id, alarm) - self.comms.send_unit_command(UNIT_COMMAND.ACK_ALARM, id, alarm) + pctl.comms.send_unit_command(U_CMD.ACK_ALARM, id, alarm) log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALARM ", alarm)) end @@ -192,7 +265,7 @@ end ---@param id integer unit ID ---@param alarm integer alarm ID function process.reset_alarm(id, alarm) - self.comms.send_unit_command(UNIT_COMMAND.RESET_ALARM, id, alarm) + pctl.comms.send_unit_command(U_CMD.RESET_ALARM, id, alarm) log.debug(util.c("PROCESS: UNIT[", id, "] RESET ALARM ", alarm)) end @@ -200,78 +273,68 @@ end ---@param unit_id integer unit ID ---@param group_id integer|0 group ID or 0 for independent function process.set_group(unit_id, group_id) - self.comms.send_unit_command(UNIT_COMMAND.SET_GROUP, unit_id, group_id) + pctl.comms.send_unit_command(U_CMD.SET_GROUP, unit_id, group_id) log.debug(util.c("PROCESS: UNIT[", unit_id, "] SET GROUP ", group_id)) - self.control_states.priority_groups[unit_id] = group_id - settings.set("ControlStates", self.control_states) + pctl.control_states.priority_groups[unit_id] = group_id + settings.set("ControlStates", pctl.control_states) if not settings.save("/coordinator.settings") then log.error("process.set_group(): failed to save coordinator settings file") end end +--#endregion + -------------------------- -- AUTO PROCESS CONTROL -- -------------------------- --- write auto process control to config file -local function _write_auto_config() - -- save config - settings.set("ControlStates", self.control_states) - local saved = settings.save("/coordinator.settings") - if not saved then - log.warning("process._write_auto_config(): failed to save coordinator settings file") - end - - return saved +-- start automatic process control +function process.start_auto() + pctl.comms.send_auto_start(pctl.control_states.process) + log.debug("PROCESS: START AUTO CTL") end -- stop automatic process control function process.stop_auto() - self.comms.send_fac_command(FAC_COMMAND.STOP) + pctl.comms.send_fac_command(F_CMD.STOP) log.debug("PROCESS: STOP AUTO CTL") end --- start automatic process control -function process.start_auto() - self.comms.send_auto_start(self.control_states.process) - log.debug("PROCESS: START AUTO CTL") -end - -- set automatic process control waste mode ---@param product WASTE_PRODUCT waste product for auto control function process.set_process_waste(product) - self.comms.send_fac_command(FAC_COMMAND.SET_WASTE_MODE, product) + pctl.comms.send_fac_command(F_CMD.SET_WASTE_MODE, product) log.debug(util.c("PROCESS: SET WASTE ", product)) -- update config table and save - self.control_states.process.waste_product = product + pctl.control_states.process.waste_product = product _write_auto_config() end -- set automatic process control plutonium fallback ---@param enabled boolean whether to enable plutonium fallback function process.set_pu_fallback(enabled) - self.comms.send_fac_command(FAC_COMMAND.SET_PU_FB, enabled) + pctl.comms.send_fac_command(F_CMD.SET_PU_FB, enabled) log.debug(util.c("PROCESS: SET PU FALLBACK ", enabled)) -- update config table and save - self.control_states.process.pu_fallback = enabled + pctl.control_states.process.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) + pctl.comms.send_fac_command(F_CMD.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 + pctl.control_states.process.sps_low_power = enabled _write_auto_config() end @@ -285,7 +348,7 @@ function process.save(mode, burn_target, charge_target, gen_target, limits) log.debug("PROCESS: SAVE") -- update config table - local ctl_proc = self.control_states.process + local ctl_proc = pctl.control_states.process ctl_proc.mode = mode ctl_proc.burn_target = burn_target ctl_proc.charge_target = charge_target @@ -293,7 +356,7 @@ function process.save(mode, burn_target, charge_target, gen_target, limits) ctl_proc.limits = limits -- save config - self.io.facility.save_cfg_ack(_write_auto_config()) + pctl.io.facility.save_cfg_ack(_write_auto_config()) end -- handle a start command acknowledgement @@ -301,39 +364,39 @@ end function process.start_ack_handle(response) local ack = response[1] - local ctl_proc = self.control_states.process + local ctl_proc = pctl.control_states.process ctl_proc.mode = response[2] ctl_proc.burn_target = response[3] ctl_proc.charge_target = response[4] ctl_proc.gen_target = response[5] - for i = 1, math.min(#response[6], self.io.facility.num_units) do + for i = 1, math.min(#response[6], pctl.io.facility.num_units) do ctl_proc.limits[i] = response[6][i] - local unit = self.io.units[i] ---@type ioctl_unit + local unit = pctl.io.units[i] ---@type ioctl_unit unit.unit_ps.publish("burn_limit", ctl_proc.limits[i]) end - self.io.facility.ps.publish("process_mode", ctl_proc.mode) - self.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target) - self.io.facility.ps.publish("process_charge_target", self.io.energy_convert_from_fe(ctl_proc.charge_target)) - self.io.facility.ps.publish("process_gen_target", self.io.energy_convert_from_fe(ctl_proc.gen_target)) + pctl.io.facility.ps.publish("process_mode", ctl_proc.mode) + pctl.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target) + pctl.io.facility.ps.publish("process_charge_target", pctl.io.energy_convert_from_fe(ctl_proc.charge_target)) + pctl.io.facility.ps.publish("process_gen_target", pctl.io.energy_convert_from_fe(ctl_proc.gen_target)) - self.io.facility.start_ack(ack) + pctl.io.facility.start_ack(ack) end -- record waste product state after attempting to change it ---@param response WASTE_PRODUCT supervisor waste product state function process.waste_ack_handle(response) - self.control_states.process.waste_product = response - self.io.facility.ps.publish("process_waste_product", response) + pctl.control_states.process.waste_product = response + pctl.io.facility.ps.publish("process_waste_product", response) end -- record plutonium fallback state after attempting to change it ---@param response boolean supervisor plutonium fallback state function process.pu_fb_ack_handle(response) - self.control_states.process.pu_fallback = response - self.io.facility.ps.publish("process_pu_fallback", response) + pctl.control_states.process.pu_fallback = response + pctl.io.facility.ps.publish("process_pu_fallback", response) end return process diff --git a/coordinator/ui/components/process_ctl.lua b/coordinator/ui/components/process_ctl.lua index e1f3614..9517823 100644 --- a/coordinator/ui/components/process_ctl.lua +++ b/coordinator/ui/components/process_ctl.lua @@ -63,8 +63,8 @@ local function new_view(root, x, y) local main = Div{parent=root,width=128,height=24,x=x,y=y} - local scram = HazardButton{parent=main,x=1,y=1,text="FAC SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=process.fac_scram,fg_bg=hzd_fg_bg} - local ack_a = HazardButton{parent=main,x=16,y=1,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=process.fac_ack_alarms,fg_bg=hzd_fg_bg} + local scram = HazardButton{parent=main,x=1,y=1,text="FAC SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=db.process.fac_scram,fg_bg=hzd_fg_bg} + local ack_a = HazardButton{parent=main,x=16,y=1,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=db.process.fac_ack_alarms,fg_bg=hzd_fg_bg} facility.scram_ack = scram.on_response facility.ack_alarms_ack = ack_a.on_response From 8a0d05c94bca4a905c2b6338e75e3756484a087b Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 28 Aug 2024 03:12:38 +0000 Subject: [PATCH 09/40] #403 guide additions for front panel docs --- pocket/ui/apps/guide.lua | 22 ++++++++--- pocket/ui/docs.lua | 82 ++++++++++++++++++++++++++++++++-------- 2 files changed, 82 insertions(+), 22 deletions(-) diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index f737aeb..ea494cf 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -3,6 +3,7 @@ -- local util = require("scada-common.util") +local log = require("scada-common.log") local iocontrol = require("pocket.iocontrol") local pocket = require("pocket.pocket") @@ -120,11 +121,13 @@ local function new_view(root) search_results.remove_all() - if string.len(query) < 3 then - TextBox{parent=search_results,text="Search requires at least 3 characters."} + if string.len(query) < 2 then + TextBox{parent=search_results,text="Search requires at least 2 characters."} return end + local start = util.time_ms() + for _, entry in ipairs(search_db) do local s_start, _ = string.find(entry[1], query, 1, true) @@ -153,6 +156,8 @@ local function new_view(root) end end + log.debug("App.Guide: search for \"" .. query .. "\" completed in " .. (util.time_ms() - start) .. "ms") + if empty then TextBox{parent=search_results,text="No results found."} end @@ -191,7 +196,8 @@ local function new_view(root) 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, 170) - local fac_annunc_page = guide_section(sect_construct_data, annunc_page, "Facility", docs.annunc.unit.fac_section, 100) + + local fac_annunc_page = guide_section(sect_construct_data, annunc_page, "Facility", docs.annunc.facility.main_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} @@ -202,9 +208,13 @@ local function new_view(root) TextBox{parent=fps,y=1,text="Front Panels",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,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() + local fp_common_page = guide_section(sect_construct_data, fps_page, "Common Items", docs.fp.common, 100) + local fp_rplc_page = guide_section(sect_construct_data, fps_page, "Reactor PLC", docs.fp.r_plc, 100) + local fp_rtu_page = guide_section(sect_construct_data, fps_page, "RTU Gateway", docs.fp.rtu_gw, 100) + + PushButton{parent=fps,y=3,text="Common Items >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_common_page.nav_to} + PushButton{parent=fps,text="Reactor PLC >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_rplc_page.nav_to} + PushButton{parent=fps,text="RTU Gateway >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_rtu_page.nav_to} 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() diff --git a/pocket/ui/docs.lua b/pocket/ui/docs.lua index 9ebab56..b4add81 100644 --- a/pocket/ui/docs.lua +++ b/pocket/ui/docs.lua @@ -1,3 +1,5 @@ +local const = require("scada-common.constants") + local docs = {} local target @@ -28,7 +30,10 @@ doc("TurbineTripAlarm", "Turbine Trip", "A turbine stopped rotating, likely due docs.annunc = { unit = { - main_section = {}, rps_section = {}, rcs_section = {}, fac_section = {} + main_section = {}, rps_section = {}, rcs_section = {} + }, + facility = { + main_section = {} } } @@ -78,21 +83,65 @@ doc("TurbineOverSpeed", "Turbine Over Speed", "The turbine is at steam capacity, 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.") -target = docs.annunc.unit.fac_section -doc("?", "Unit Systems Online", "All unit systems (reactors, boilers, and turbines) are connected.") -doc("?", "Radiation Monitor", "At least one facility radiation monitor is connected") -doc("?", "Induction Matrix", "The induction matrix is connected.") -doc("?", "SPS Connected", "Indicates if the super-critical phase shifter is connected.") -doc("?", "Configured Units Ready", "All units assigned to automatic control are ready to run automatic control.") -doc("?", "Process Active", "Automatic process control is active.") -doc("?", "Process Ramping", "Automatic process control is performing an initial ramp-up of the reactors for later PID control (generation and charge mode).") -doc("?", "Min/Max Burn Rate", "Auto control has either commanded 0 mB/t or the maximum total burn rate available (from assigned units).") -doc("?", "Automatic SCRAM", "Automatic control system SCRAM'ed the assigned reactors due to a safety hazard, shown by the below indicators.") -doc("?", "Matrix Disconnected", "Automatic SCRAM occurred due to loss of induction matrix connection.") -doc("?", "Matrix Charge High", "Automatic SCRAM occurred due to induction matrix charge exceeding acceptable limit.") -doc("?", "Unit Critical Alarm", "Automatic SCRAM occurred due to critical level unit alarm(s).") -doc("?", "Facility Radiation High", "Automatic SCRAM occurred due to high facility radiation levels.") -doc("?", "Gen. Control Fault", "Automatic SCRAM occurred due to assigned units being degraded/no longer ready during generation mode. The system will automatically resume (starting with initial ramp) once the problem is resolved.") +target = docs.annunc.facility.main_section +doc("all_sys_ok", "Unit Systems Online", "All unit systems (reactors, boilers, and turbines) are connected.") +doc("rad_computed_status", "Radiation Monitor", "At least one facility radiation monitor is connected") +doc("im_computed_status", "Induction Matrix", "The induction matrix is connected.") +doc("sps_computed_status", "SPS Connected", "Indicates if the super-critical phase shifter is connected.") +doc("auto_ready", "Configured Units Ready", "All units assigned to automatic control are ready to run automatic control.") +doc("auto_active", "Process Active", "Automatic process control is active.") +doc("auto_ramping", "Process Ramping", "Automatic process control is performing an initial ramp-up of the reactors for later PID control (generation and charge mode).") +doc("auto_saturated", "Min/Max Burn Rate", "Auto control has either commanded 0 mB/t or the maximum total burn rate available (from assigned units).") +doc("auto_scram", "Automatic SCRAM", "Automatic control system SCRAM'ed the assigned reactors due to a safety hazard, shown by the below indicators.") +doc("as_matrix_dc", "Matrix Disconnected", "Automatic SCRAM occurred due to loss of induction matrix connection.") +doc("as_matrix_fill", "Matrix Charge High", "Automatic SCRAM occurred due to induction matrix charge exceeding acceptable limit.") +doc("as_crit_alarm", "Unit Critical Alarm", "Automatic SCRAM occurred due to critical level unit alarm(s).") +doc("as_radiation", "Facility Radiation High", "Automatic SCRAM occurred due to high facility radiation levels.") +doc("as_gen_fault", "Gen. Control Fault", "Automatic SCRAM occurred due to assigned units being degraded/no longer ready during generation mode. The system will automatically resume (starting with initial ramp) once the problem is resolved.") + +docs.fp = { + common = {}, r_plc = {}, rtu_gw = {} +} + +--comp id "This must never be the identical between devices, and that can only happen if you duplicate a computer (such as middle-click on it and place it elsewhere in creative mode)." + +target = docs.fp.common +doc("fp_status", "STATUS", "This is always lit, except on the Reactor PLC. For that, it is green once initialized and OK (has all its peripherals) and red if something is wrong, in which case you should refer to the other indicator lights.") +doc("fp_heartbeat", "HEARTBEAT", "This alternates between lit and unlit as the main loop on the device runs. If this freezes, something is wrong and the logs will indicate why.") +doc("fp_modem", "MODEM", "This lights up if the wireless/ender modem is connected. In parentheses is the unique computer ID of this device, which will show up in places such as the supervisor's connection lists.") +doc("fp_modem", "NETWORK", "This is present when in standard color modes and indicates the network status using multiple colors. Off is no link, green is linked, red is link denied, orange is mismatching comms versions, and yellow is Reactor PLC-specific, indicating a unit ID collision (duplicate unit IDs in use).") +doc("fp_nt_linked", "NT LINKED", "(color accessibility modes only) This lights up once the device is linked to the supervisor.") +doc("fp_nt_version", "NT VERSION", "(color accessibility modes only) This lights up if the communications versions of the supervisor and this device do not match. Make sure everything is up-to-date.") +doc("fp_fw", "FW", "Firmware application version of this device.") +doc("fp_nt", "NT", "Network (comms) version this device has. These must match between devices in order for them to connect.") + +target = docs.fp.r_plc +doc("fp_nt_collision", "NT COLLISION", "(color accessibility modes only) This lights up if the Reactor PLC reactor unit ID conflicts with (is a duplicate of) another already connected Reactor PLC.") +doc("fp_rplc_rt_main", "RT MAIN", "This lights up as long as the device's main loop co-routine is running, which it should be as long as STATUS is green.") +doc("fp_rplc_rt_rps", "RT RPS", "This should always be lit up if a reactor is connected as it indicates the RPS co-routine is running, otherwise safety checks will not be running.") +doc("fp_rplc_rt_ctx", "RT COMMS TX", "This should always be lit if the Reactor PLC is not running in standalone mode, as it indicates the communications transmission co-routine is running.") +doc("fp_rplc_rt_crx", "RT COMMS RX", "This should always be lit if the Reactor PLC is not running in standalone mode, as it indicates the communications receiver/handler co-routine is running.") +doc("fp_rplc_rt_spctl", "RT SPCTL", "This should always be lit if the Reactor PLC is not running in standalone mode, as it indicates the process setpoint controller co-routine is running.") +doc("fp_rct_active", "RCT ACTIVE", "The reactor is active (running).") +doc("fp_emer_cool", "EMER COOLANT", "This is only present if PLC-controlled emergency coolant is configured on that device. When lit, it indicates that it has been activated.") +doc("fp_rps_trip", "RPS TRIP", "Flashes when the RPS has SCRAM'd the reactor due to a safety trip.") +doc("fp_rps_man", "MANUAL", "The RPS was tripped manually (SCRAM by user, not via the Mekanism Reactor UI).") +doc("fp_rps_auto", "AUTOMATIC", "The RPS was tripped by the supervisor automatically.") +doc("fp_rps_to", "TIMEOUT", "The RPS tripped due to losing the supervisor connection.") +doc("fp_rps_pflt", "PLC FAULT", "The RPS tripped due to a peripheral error.") +doc("fp_rps_rflt", "RCT FAULT", "The RPS tripped due to the reactor not being formed.") +doc("fp_rps_temp", "HI DAMAGE", "The RPS tripped due to being >= " .. const.RPS_LIMITS.MAX_DAMAGE_PERCENT .. "% damaged.") +doc("fp_rps_temp", "HI TEMP", "The RPS tripped due to high reactor temperature (>= " .. const.RPS_LIMITS.MAX_DAMAGE_TEMPERATURE .. "K).") +doc("fp_rps_fuel", "LO FUEL", "The RPS tripped due to having no fuel.") +doc("fp_rps_waste", "HI WASTE", "The RPS tripped due to having high levels of waste (> " .. const.RPS_LIMITS.MAX_WASTE_FILL .. "%).") +doc("fp_rps_ccool", "LO CCOOLANT", "The RPS tripped due to having low levels of cooled coolant (< " .. const.RPS_LIMITS.MIN_COOLANT_FILL .. "%).") +doc("fp_rps_ccool", "HI HCOOLANT", "The RPS tripped due to having high levels of heated coolant (> " .. const.RPS_LIMITS.MAX_HEATED_COLLANT_FILL .. "%).") + +target = docs.fp.rtu_gw +doc("fp_rtu_rt_main", "RT MAIN", "This indicates if the device's main loop co-routine is running.") +doc("fp_rtu_rt_comms", "RT COMMS", "This indicates if the communications handler co-routine is running.") +doc("fp_rtu_rt", "RT", "In each RTU entry row, an RT light indicates if the co-routine for that RTU unit is running. This is never lit for redstone units.") +doc("fp_rtu_rt", "Device Status", "In each RTU entry row, the light to the left of the device name indicates its peripheral status.") docs.glossary = { abbvs = {}, terms = {} @@ -113,6 +162,7 @@ doc("G_PPM", "PPM", "Protected Peripheral Manager. This is an abstraction layer 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", "RT", "co-RouTine. This is used to identify the status of core Lua co-routines on front panels.") 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.") From 097edc5bf91a4d749e96616fb64ad02b9949fcde Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Tue, 27 Aug 2024 23:21:49 -0400 Subject: [PATCH 10/40] adjusted guide section heights and moved process init to have facility access --- pocket/iocontrol.lua | 12 +++++++----- pocket/ui/apps/guide.lua | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 0e97602..158c0ec 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -50,12 +50,14 @@ local function __generic_ack(success) end -- luacheck: unused args local config = nil ---@type pkt_config +local comms = nil ---@type pocket_comms -- initialize facility-independent components of pocket iocontrol ----@param comms pocket_comms +---@param pkt_comms pocket_comms ---@param nav pocket_nav ---@param cfg pkt_config -function iocontrol.init_core(comms, nav, cfg) +function iocontrol.init_core(pkt_comms, nav, cfg) + comms = pkt_comms config = cfg io.nav = nav @@ -102,9 +104,6 @@ function iocontrol.init_core(comms, nav, cfg) io.api = { get_unit = function (unit) comms.api__get_unit(unit) end } - - -- pass IO control here since it can't be require'd due to a require loop - process.init(io, comms) end -- initialize facility-dependent components of pocket iocontrol @@ -374,6 +373,9 @@ function iocontrol.init_fac(conf) table.insert(io.units, entry) end + + -- pass IO control here since it can't be require'd due to a require loop + process.init(io, comms) end -- set network link state diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index ea494cf..2e93eb7 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -209,7 +209,7 @@ local function new_view(root) PushButton{parent=fps,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} local fp_common_page = guide_section(sect_construct_data, fps_page, "Common Items", docs.fp.common, 100) - local fp_rplc_page = guide_section(sect_construct_data, fps_page, "Reactor PLC", docs.fp.r_plc, 100) + local fp_rplc_page = guide_section(sect_construct_data, fps_page, "Reactor PLC", docs.fp.r_plc, 150) local fp_rtu_page = guide_section(sect_construct_data, fps_page, "RTU Gateway", docs.fp.rtu_gw, 100) PushButton{parent=fps,y=3,text="Common Items >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_common_page.nav_to} @@ -221,7 +221,7 @@ local function new_view(root) TextBox{parent=gls,y=1,text="Glossary",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, 130) + local gls_abbv_page = guide_section(sect_construct_data, gls_page, "Abbreviations", docs.glossary.abbvs, 140) 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} From 035a26cc079e5edf064951514817c3a357dee98d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 28 Aug 2024 21:01:04 -0400 Subject: [PATCH 11/40] #543 reset remote sequence numbers when linking --- coordinator/coordinator.lua | 1 + coordinator/startup.lua | 2 +- pocket/pocket.lua | 2 ++ pocket/startup.lua | 2 +- reactor-plc/plc.lua | 1 + reactor-plc/startup.lua | 2 +- rtu/rtu.lua | 1 + rtu/startup.lua | 2 +- 8 files changed, 9 insertions(+), 4 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index e6c7671..651a5bc 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -296,6 +296,7 @@ function coordinator.comms(version, nic, sv_watchdog) -- attempt connection establishment local function _send_establish() + self.sv_r_seq_num = nil _send_sv(PROTOCOL.SCADA_MGMT, MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.CRD }) end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 8fb6173..4be0949 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.6" +local COORDINATOR_VERSION = "v1.5.7" local CHUNK_LOAD_DELAY_S = 30.0 diff --git a/pocket/pocket.lua b/pocket/pocket.lua index a8867fb..db0ae8b 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -439,11 +439,13 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) -- attempt supervisor connection establishment local function _send_sv_establish() + self.sv.r_seq_num = nil _send_sv(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PKT }) end -- attempt coordinator API connection establishment local function _send_api_establish() + self.api.r_seq_num = nil _send_crd(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PKT, comms.api_version }) end diff --git a/pocket/startup.lua b/pocket/startup.lua index 9bb49d7..a6953b1 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.11.8-alpha" +local POCKET_VERSION = "v0.11.9-alpha" local println = util.println local println_ts = util.println_ts diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 36cb8ed..eee2bc6 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -735,6 +735,7 @@ function plc.comms(version, nic, reactor, rps, conn_watchdog) -- attempt to establish link with supervisor function public.send_link_req() + self.r_seq_num = nil _send_mgmt(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.PLC, config.UnitID }) end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index f1cdae2..0f83ef4 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.8.6" +local R_PLC_VERSION = "v1.8.7" local println = util.println local println_ts = util.println_ts diff --git a/rtu/rtu.lua b/rtu/rtu.lua index daf9f22..dc398b2 100644 --- a/rtu/rtu.lua +++ b/rtu/rtu.lua @@ -378,6 +378,7 @@ function rtu.comms(version, nic, conn_watchdog) -- send establish request (includes advertisement) ---@param units table function public.send_establish(units) + self.r_seq_num = nil _send(MGMT_TYPE.ESTABLISH, { comms.version, version, DEVICE_TYPE.RTU, _generate_advertisement(units) }) end diff --git a/rtu/startup.lua b/rtu/startup.lua index b978164..57911a0 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.6" +local RTU_VERSION = "v1.10.7" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE From 3c10e28d03af8e39efd9ac68a87e0005a8d8db92 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Thu, 29 Aug 2024 01:19:26 +0000 Subject: [PATCH 12/40] #403 guide lists --- pocket/ui/docs.lua | 23 +++++++++++ pocket/ui/pages/guide_section.lua | 64 +++++++++++++++++++++++-------- 2 files changed, 72 insertions(+), 15 deletions(-) diff --git a/pocket/ui/docs.lua b/pocket/ui/docs.lua index b4add81..d7796c1 100644 --- a/pocket/ui/docs.lua +++ b/pocket/ui/docs.lua @@ -2,14 +2,36 @@ local const = require("scada-common.constants") local docs = {} +---@enum DOC_LIST_TYPE +local DOC_LIST_TYPE = { + BULLET = 1, + NUMBERED = 2, + INDICATOR = 3, + LED = 4 +} + +docs.DOC_LIST_TYPE = DOC_LIST_TYPE + local target +---@param key string item identifier for linking +---@param name string item name for display +---@param desc string text body local function doc(key, name, desc) ---@class pocket_doc_item local item = { key = key, name = name, desc = desc } table.insert(target, item) end +---@param type DOC_LIST_TYPE +---@param items table +---@param colors table|nil colors for indicators or nil for normal lists +local function list(type, items, colors) + ---@class pocket_doc_list + local list_def = { type = type, items = items, colors = colors } + table.insert(target, list_def) +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 = {} @@ -110,6 +132,7 @@ doc("fp_status", "STATUS", "This is always lit, except on the Reactor PLC. For t doc("fp_heartbeat", "HEARTBEAT", "This alternates between lit and unlit as the main loop on the device runs. If this freezes, something is wrong and the logs will indicate why.") doc("fp_modem", "MODEM", "This lights up if the wireless/ender modem is connected. In parentheses is the unique computer ID of this device, which will show up in places such as the supervisor's connection lists.") doc("fp_modem", "NETWORK", "This is present when in standard color modes and indicates the network status using multiple colors. Off is no link, green is linked, red is link denied, orange is mismatching comms versions, and yellow is Reactor PLC-specific, indicating a unit ID collision (duplicate unit IDs in use).") +list(DOC_LIST_TYPE.LED, { "not linked", "linked" }, { colors.gray, colors.green }) doc("fp_nt_linked", "NT LINKED", "(color accessibility modes only) This lights up once the device is linked to the supervisor.") doc("fp_nt_version", "NT VERSION", "(color accessibility modes only) This lights up if the communications versions of the supervisor and this device do not match. Make sure everything is up-to-date.") doc("fp_fw", "FW", "Firmware application version of this device.") diff --git a/pocket/ui/pages/guide_section.lua b/pocket/ui/pages/guide_section.lua index 04d3c09..dba8487 100644 --- a/pocket/ui/pages/guide_section.lua +++ b/pocket/ui/pages/guide_section.lua @@ -1,6 +1,8 @@ local log = require("scada-common.log") local util = require("scada-common.util") +local docs = require("pocket.ui.docs") + local core = require("graphics.core") local Div = require("graphics.elements.div") @@ -9,9 +11,14 @@ local TextBox = require("graphics.elements.textbox") local PushButton = require("graphics.elements.controls.push_button") +local IndicatorLight = require("graphics.elements.indicators.light") +local LED = require("graphics.elements.indicators.led") + local ALIGN = core.ALIGN local cpair = core.cpair +local LIST_TYPE = docs.DOC_LIST_TYPE + -- new guide documentation section ---@param data _guide_section_constructor_data ---@param base_page nav_tree_page @@ -40,24 +47,51 @@ return function (data, base_page, title, items, scroll_height) local _end for i = 1, #items do - local item = items[i] ---@type pocket_doc_item + local item = items[i] ---@type pocket_doc_item|pocket_doc_list - 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} + if item.type == nil then + ---@cast item pocket_doc_item - local function view() - _end.focus() - view_page.nav_to() - anchor.focus() + 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() + view_page.nav_to() + anchor.focus() + end + + doc_map[item.key] = 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} + + if i % 12 == 0 then util.nop() end + else + ---@cast item pocket_doc_list + + if item.type == LIST_TYPE.BULLET then + for _, li in ipairs(item.items) do + TextBox{parent=def_list,x=2,text="\x07 "..li} + end + elseif item.type == LIST_TYPE.NUMBERED then + local width = string.len("" .. #item.items) + for idx, li in ipairs(item.items) do + TextBox{parent=def_list,x=2,text=util.sprintf("%" .. width .. "d. %s", idx, li)} + end + elseif item.type == LIST_TYPE.INDICATOR then + for idx, li in ipairs(item.items) do + local _ = IndicatorLight{parent=def_list,x=2,label=li,colors=cpair(colors.black,item.colors[idx])} + end + elseif item.type == LIST_TYPE.LED then + for idx, li in ipairs(item.items) do + local _ = LED{parent=def_list,x=2,label=li,colors=cpair(colors.black,item.colors[idx])} + end + end + + local _ = Div{parent=def_list,height=1} end - - doc_map[item.key] = 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} - - if i % 12 == 0 then util.nop() end end log.debug("guide section " .. title .. " generated with final height ".. _end.get_y()) From 7683293c5ef322116c51390e229da5876fb8519f Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 28 Aug 2024 22:52:55 -0400 Subject: [PATCH 13/40] #403 additional guide section doc types and some more documentation --- pocket/ui/docs.lua | 43 ++++++++++++++++++++++++------- pocket/ui/pages/guide_section.lua | 43 ++++++++++++++++++++----------- 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/pocket/ui/docs.lua b/pocket/ui/docs.lua index d7796c1..0adb128 100644 --- a/pocket/ui/docs.lua +++ b/pocket/ui/docs.lua @@ -2,6 +2,13 @@ local const = require("scada-common.constants") local docs = {} +---@enum DOC_ITEM_TYPE +local DOC_ITEM_TYPE = { + SECTION = 1, + TEXT = 2, + LIST = 3 +} + ---@enum DOC_LIST_TYPE local DOC_LIST_TYPE = { BULLET = 1, @@ -10,16 +17,30 @@ local DOC_LIST_TYPE = { LED = 4 } +docs.DOC_ITEM_TYPE = DOC_ITEM_TYPE docs.DOC_LIST_TYPE = DOC_LIST_TYPE local target ---@param key string item identifier for linking ---@param name string item name for display ----@param desc string text body -local function doc(key, name, desc) - ---@class pocket_doc_item - local item = { key = key, name = name, desc = desc } +---@param text_a string text body, or the subtitle/note if text_b is specified +---@param text_b? string text body if subtitle/note was specified +local function doc(key, name, text_a, text_b) + if text_b == nil then + text_b = text_a +---@diagnostic disable-next-line: cast-local-type + text_a = nil + end + + ---@class pocket_doc_sect + local item = { type = DOC_ITEM_TYPE.SECTION, key = key, name = name, subtitle = text_a, body = text_b } + table.insert(target, item) +end + +local function text(body) + ---@class pocket_doc_text + local item = { type = DOC_ITEM_TYPE.TEXT, text = body } table.insert(target, item) end @@ -28,7 +49,7 @@ end ---@param colors table|nil colors for indicators or nil for normal lists local function list(type, items, colors) ---@class pocket_doc_list - local list_def = { type = type, items = items, colors = colors } + local list_def = { type = DOC_ITEM_TYPE.LIST, list_type = type, items = items, colors = colors } table.insert(target, list_def) end @@ -131,15 +152,16 @@ target = docs.fp.common doc("fp_status", "STATUS", "This is always lit, except on the Reactor PLC. For that, it is green once initialized and OK (has all its peripherals) and red if something is wrong, in which case you should refer to the other indicator lights.") doc("fp_heartbeat", "HEARTBEAT", "This alternates between lit and unlit as the main loop on the device runs. If this freezes, something is wrong and the logs will indicate why.") doc("fp_modem", "MODEM", "This lights up if the wireless/ender modem is connected. In parentheses is the unique computer ID of this device, which will show up in places such as the supervisor's connection lists.") -doc("fp_modem", "NETWORK", "This is present when in standard color modes and indicates the network status using multiple colors. Off is no link, green is linked, red is link denied, orange is mismatching comms versions, and yellow is Reactor PLC-specific, indicating a unit ID collision (duplicate unit IDs in use).") -list(DOC_LIST_TYPE.LED, { "not linked", "linked" }, { colors.gray, colors.green }) -doc("fp_nt_linked", "NT LINKED", "(color accessibility modes only) This lights up once the device is linked to the supervisor.") -doc("fp_nt_version", "NT VERSION", "(color accessibility modes only) This lights up if the communications versions of the supervisor and this device do not match. Make sure everything is up-to-date.") +doc("fp_modem", "NETWORK", "This is present when in standard color modes and indicates the network status using multiple colors.") +list(DOC_LIST_TYPE.LED, { "not linked", "linked", "link denied", "bad comms version", "duplicate PLC" }, { colors.gray, colors.green, colors.red, colors.orange, colors.yellow }) +text("You can fix \"bad comms version\" by ensuring all devices are up-to-date, as this indicates a communications protocol version mismatch. Note that yellow is Reactor PLC-specific, indicating duplicate unit IDs in use.") +doc("fp_nt_linked", "NT LINKED", "(color accessibility modes only)", "This indicates the device is linked to the supervisor.") +doc("fp_nt_version", "NT VERSION", "(color accessibility modes only)", "This indicates the communications versions of the supervisor and this device do not match. Make sure everything is up-to-date.") doc("fp_fw", "FW", "Firmware application version of this device.") doc("fp_nt", "NT", "Network (comms) version this device has. These must match between devices in order for them to connect.") target = docs.fp.r_plc -doc("fp_nt_collision", "NT COLLISION", "(color accessibility modes only) This lights up if the Reactor PLC reactor unit ID conflicts with (is a duplicate of) another already connected Reactor PLC.") +doc("fp_nt_collision", "NT COLLISION", "(color accessibility modes only)", "This indicates the Reactor PLC unit ID is a duplicate of another already connected Reactor PLC.") doc("fp_rplc_rt_main", "RT MAIN", "This lights up as long as the device's main loop co-routine is running, which it should be as long as STATUS is green.") doc("fp_rplc_rt_rps", "RT RPS", "This should always be lit up if a reactor is connected as it indicates the RPS co-routine is running, otherwise safety checks will not be running.") doc("fp_rplc_rt_ctx", "RT COMMS TX", "This should always be lit if the Reactor PLC is not running in standalone mode, as it indicates the communications transmission co-routine is running.") @@ -165,6 +187,7 @@ doc("fp_rtu_rt_main", "RT MAIN", "This indicates if the device's main loop co-ro doc("fp_rtu_rt_comms", "RT COMMS", "This indicates if the communications handler co-routine is running.") doc("fp_rtu_rt", "RT", "In each RTU entry row, an RT light indicates if the co-routine for that RTU unit is running. This is never lit for redstone units.") doc("fp_rtu_rt", "Device Status", "In each RTU entry row, the light to the left of the device name indicates its peripheral status.") +list(DOC_LIST_TYPE.LED, { "disconnected", "faulted", "unformed", "ok" }, { colors.red, colors.orange, colors.yellow, colors.green }) docs.glossary = { abbvs = {}, terms = {} diff --git a/pocket/ui/pages/guide_section.lua b/pocket/ui/pages/guide_section.lua index dba8487..6f8a47f 100644 --- a/pocket/ui/pages/guide_section.lua +++ b/pocket/ui/pages/guide_section.lua @@ -17,6 +17,7 @@ local LED = require("graphics.elements.indicators.led") local ALIGN = core.ALIGN local cpair = core.cpair +local DOC_TYPE = docs.DOC_ITEM_TYPE local LIST_TYPE = docs.DOC_LIST_TYPE -- new guide documentation section @@ -47,13 +48,19 @@ return function (data, base_page, title, items, scroll_height) local _end for i = 1, #items do - local item = items[i] ---@type pocket_doc_item|pocket_doc_list + local item = items[i] ---@type pocket_doc_sect|pocket_doc_text|pocket_doc_list - if item.type == nil then - ---@cast item pocket_doc_item + if item.type == DOC_TYPE.SECTION then + ---@cast item pocket_doc_sect 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} + + if item.subtitle then + TextBox{parent=def_list,text=item.subtitle,fg_bg=cpair(colors.gray,colors.black)} + end + + TextBox{parent=def_list,text=item.body} + _end = Div{parent=def_list,height=1,can_focus=true} local function view() @@ -66,32 +73,38 @@ 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,fg_bg=cpair(colors.blue,colors.black),active_fg_bg=btn_active,callback=view} - - if i % 12 == 0 then util.nop() end - else + elseif item.type == DOC_TYPE.TEXT then + ---@cast item pocket_doc_text + TextBox{parent=def_list,text=item.text} + local _ = Div{parent=def_list,height=1} + elseif item.type == DOC_TYPE.LIST then ---@cast item pocket_doc_list - if item.type == LIST_TYPE.BULLET then + local container = Div{parent=def_list,height=#item.items} + + if item.list_type == LIST_TYPE.BULLET then for _, li in ipairs(item.items) do - TextBox{parent=def_list,x=2,text="\x07 "..li} + TextBox{parent=container,x=2,text="\x07 "..li} end - elseif item.type == LIST_TYPE.NUMBERED then + elseif item.list_type == LIST_TYPE.NUMBERED then local width = string.len("" .. #item.items) for idx, li in ipairs(item.items) do - TextBox{parent=def_list,x=2,text=util.sprintf("%" .. width .. "d. %s", idx, li)} + TextBox{parent=container,x=2,text=util.sprintf("%" .. width .. "d. %s", idx, li)} end - elseif item.type == LIST_TYPE.INDICATOR then + elseif item.list_type == LIST_TYPE.INDICATOR then for idx, li in ipairs(item.items) do - local _ = IndicatorLight{parent=def_list,x=2,label=li,colors=cpair(colors.black,item.colors[idx])} + local _ = IndicatorLight{parent=container,x=2,label=li,colors=cpair(colors.black,item.colors[idx])} end - elseif item.type == LIST_TYPE.LED then + elseif item.list_type == LIST_TYPE.LED then for idx, li in ipairs(item.items) do - local _ = LED{parent=def_list,x=2,label=li,colors=cpair(colors.black,item.colors[idx])} + local _ = LED{parent=container,x=2,label=li,colors=cpair(colors.black,item.colors[idx])} end end local _ = Div{parent=def_list,height=1} end + + if i % 12 == 0 then util.nop() end end log.debug("guide section " .. title .. " generated with final height ".. _end.get_y()) From 75c77cc5b5b7a1485e2e0335d55f28613424cff5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 28 Aug 2024 23:02:08 -0400 Subject: [PATCH 14/40] #403 weight exact matches over start of key matches --- pocket/ui/apps/guide.lua | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index 2e93eb7..0067083 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -117,7 +117,7 @@ local function new_view(root) function func_ref.run_search() local query = string.lower(query_field.get_value()) - local s_results = { {}, {}, {} } + local s_results = { {}, {}, {}, {} } search_results.remove_all() @@ -129,24 +129,29 @@ local function new_view(root) local start = util.time_ms() for _, entry in ipairs(search_db) do - local s_start, _ = string.find(entry[1], query, 1, true) + local s_start, s_end = 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) + if s_end == string.len(entry[1]) then + -- best match: full match + table.insert(s_results[1], entry) + else + -- very good match, start of key + table.insert(s_results[2], entry) + end elseif string.sub(query, s_start - 1, s_start) == " " then -- start of word, good match - table.insert(s_results[2], entry) + table.insert(s_results[3], entry) else -- basic match in content - table.insert(s_results[3], entry) + table.insert(s_results[4], entry) end end local empty = true - for tier = 1, 3 do + for tier = 1, 4 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)} From ee922a3aedd6f82b10570e31ec93c9c2b80f5919 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 29 Aug 2024 19:56:36 -0400 Subject: [PATCH 15/40] #403 fixed section focusing --- pocket/ui/pages/guide_section.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pocket/ui/pages/guide_section.lua b/pocket/ui/pages/guide_section.lua index 6f8a47f..a317174 100644 --- a/pocket/ui/pages/guide_section.lua +++ b/pocket/ui/pages/guide_section.lua @@ -76,7 +76,7 @@ return function (data, base_page, title, items, scroll_height) elseif item.type == DOC_TYPE.TEXT then ---@cast item pocket_doc_text TextBox{parent=def_list,text=item.text} - local _ = Div{parent=def_list,height=1} + _end = Div{parent=def_list,height=1,can_focus=true} elseif item.type == DOC_TYPE.LIST then ---@cast item pocket_doc_list @@ -101,7 +101,7 @@ return function (data, base_page, title, items, scroll_height) end end - local _ = Div{parent=def_list,height=1} + _end = Div{parent=def_list,height=1,can_focus=true} end if i % 12 == 0 then util.nop() end From db94ac7ff5335e13dbf3a72fcc1936e26f39c361 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 29 Aug 2024 20:56:20 -0400 Subject: [PATCH 16/40] #403 section headers and details on RTU front panel --- graphics/element.lua | 9 +++++++++ pocket/ui/apps/guide.lua | 4 ++-- pocket/ui/docs.lua | 31 +++++++++++++++++++++++++++---- pocket/ui/pages/guide_section.lua | 22 +++++++++++++++++++++- 4 files changed, 59 insertions(+), 7 deletions(-) diff --git a/graphics/element.lua b/graphics/element.lua index 758a7b5..1e94edd 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -574,6 +574,15 @@ function element.new(args, constraint, child_offset_x, child_offset_y) ---@return graphics_element function public.get_child(id) return protected.children[protected.child_id_map[id]].get() end + -- get all children + ---@nodiscard + ---@return table children table of graphics_element objects + function public.get_children() + local list = {} + for k, v in pairs(protected.children) do list[k] = v.get() end + return list + end + -- remove a child element ---@param id element_id function public.remove(id) diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index 0067083..50e58b9 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -202,7 +202,7 @@ local function new_view(root) 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, 170) - local fac_annunc_page = guide_section(sect_construct_data, annunc_page, "Facility", docs.annunc.facility.main_section, 100) + local fac_annunc_page = guide_section(sect_construct_data, annunc_page, "Facility", docs.annunc.facility.main_section, 110) 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} @@ -215,7 +215,7 @@ local function new_view(root) local fp_common_page = guide_section(sect_construct_data, fps_page, "Common Items", docs.fp.common, 100) local fp_rplc_page = guide_section(sect_construct_data, fps_page, "Reactor PLC", docs.fp.r_plc, 150) - local fp_rtu_page = guide_section(sect_construct_data, fps_page, "RTU Gateway", docs.fp.rtu_gw, 100) + local fp_rtu_page = guide_section(sect_construct_data, fps_page, "RTU Gateway", docs.fp.rtu_gw, 70) PushButton{parent=fps,y=3,text="Common Items >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_common_page.nav_to} PushButton{parent=fps,text="Reactor PLC >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_rplc_page.nav_to} diff --git a/pocket/ui/docs.lua b/pocket/ui/docs.lua index 0adb128..c815897 100644 --- a/pocket/ui/docs.lua +++ b/pocket/ui/docs.lua @@ -5,8 +5,9 @@ local docs = {} ---@enum DOC_ITEM_TYPE local DOC_ITEM_TYPE = { SECTION = 1, - TEXT = 2, - LIST = 3 + SUBSECTION = 2, + TEXT = 3, + LIST = 4 } ---@enum DOC_LIST_TYPE @@ -22,6 +23,12 @@ docs.DOC_LIST_TYPE = DOC_LIST_TYPE local target +local function sect(name) + ---@class pocket_doc_sect + local item = { type = DOC_ITEM_TYPE.SECTION, name = name } + table.insert(target, item) +end + ---@param key string item identifier for linking ---@param name string item name for display ---@param text_a string text body, or the subtitle/note if text_b is specified @@ -33,8 +40,8 @@ local function doc(key, name, text_a, text_b) text_a = nil end - ---@class pocket_doc_sect - local item = { type = DOC_ITEM_TYPE.SECTION, key = key, name = name, subtitle = text_a, body = text_b } + ---@class pocket_doc_subsect + local item = { type = DOC_ITEM_TYPE.SUBSECTION, key = key, name = name, subtitle = text_a, body = text_b } table.insert(target, item) end @@ -81,10 +88,12 @@ docs.annunc = { } target = docs.annunc.unit.main_section +sect("Unit Status") 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.") +sect("Safety Status") 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.") @@ -127,14 +136,17 @@ doc("GeneratorTrip", "Generator Trip", "The turbine is no longer outputting powe 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.") target = docs.annunc.facility.main_section +sect("Connectivity") doc("all_sys_ok", "Unit Systems Online", "All unit systems (reactors, boilers, and turbines) are connected.") doc("rad_computed_status", "Radiation Monitor", "At least one facility radiation monitor is connected") doc("im_computed_status", "Induction Matrix", "The induction matrix is connected.") doc("sps_computed_status", "SPS Connected", "Indicates if the super-critical phase shifter is connected.") +sect("Automatic Control") doc("auto_ready", "Configured Units Ready", "All units assigned to automatic control are ready to run automatic control.") doc("auto_active", "Process Active", "Automatic process control is active.") doc("auto_ramping", "Process Ramping", "Automatic process control is performing an initial ramp-up of the reactors for later PID control (generation and charge mode).") doc("auto_saturated", "Min/Max Burn Rate", "Auto control has either commanded 0 mB/t or the maximum total burn rate available (from assigned units).") +sect("Automatic SCRAM") doc("auto_scram", "Automatic SCRAM", "Automatic control system SCRAM'ed the assigned reactors due to a safety hazard, shown by the below indicators.") doc("as_matrix_dc", "Matrix Disconnected", "Automatic SCRAM occurred due to loss of induction matrix connection.") doc("as_matrix_fill", "Matrix Charge High", "Automatic SCRAM occurred due to induction matrix charge exceeding acceptable limit.") @@ -149,27 +161,34 @@ docs.fp = { --comp id "This must never be the identical between devices, and that can only happen if you duplicate a computer (such as middle-click on it and place it elsewhere in creative mode)." target = docs.fp.common +sect("Core Status") doc("fp_status", "STATUS", "This is always lit, except on the Reactor PLC. For that, it is green once initialized and OK (has all its peripherals) and red if something is wrong, in which case you should refer to the other indicator lights.") doc("fp_heartbeat", "HEARTBEAT", "This alternates between lit and unlit as the main loop on the device runs. If this freezes, something is wrong and the logs will indicate why.") +sect("Network") doc("fp_modem", "MODEM", "This lights up if the wireless/ender modem is connected. In parentheses is the unique computer ID of this device, which will show up in places such as the supervisor's connection lists.") doc("fp_modem", "NETWORK", "This is present when in standard color modes and indicates the network status using multiple colors.") list(DOC_LIST_TYPE.LED, { "not linked", "linked", "link denied", "bad comms version", "duplicate PLC" }, { colors.gray, colors.green, colors.red, colors.orange, colors.yellow }) text("You can fix \"bad comms version\" by ensuring all devices are up-to-date, as this indicates a communications protocol version mismatch. Note that yellow is Reactor PLC-specific, indicating duplicate unit IDs in use.") doc("fp_nt_linked", "NT LINKED", "(color accessibility modes only)", "This indicates the device is linked to the supervisor.") doc("fp_nt_version", "NT VERSION", "(color accessibility modes only)", "This indicates the communications versions of the supervisor and this device do not match. Make sure everything is up-to-date.") +sect("Versions") doc("fp_fw", "FW", "Firmware application version of this device.") doc("fp_nt", "NT", "Network (comms) version this device has. These must match between devices in order for them to connect.") target = docs.fp.r_plc +sect("Network") doc("fp_nt_collision", "NT COLLISION", "(color accessibility modes only)", "This indicates the Reactor PLC unit ID is a duplicate of another already connected Reactor PLC.") +sect("Co-Routine States") doc("fp_rplc_rt_main", "RT MAIN", "This lights up as long as the device's main loop co-routine is running, which it should be as long as STATUS is green.") doc("fp_rplc_rt_rps", "RT RPS", "This should always be lit up if a reactor is connected as it indicates the RPS co-routine is running, otherwise safety checks will not be running.") doc("fp_rplc_rt_ctx", "RT COMMS TX", "This should always be lit if the Reactor PLC is not running in standalone mode, as it indicates the communications transmission co-routine is running.") doc("fp_rplc_rt_crx", "RT COMMS RX", "This should always be lit if the Reactor PLC is not running in standalone mode, as it indicates the communications receiver/handler co-routine is running.") doc("fp_rplc_rt_spctl", "RT SPCTL", "This should always be lit if the Reactor PLC is not running in standalone mode, as it indicates the process setpoint controller co-routine is running.") +sect("Status") doc("fp_rct_active", "RCT ACTIVE", "The reactor is active (running).") doc("fp_emer_cool", "EMER COOLANT", "This is only present if PLC-controlled emergency coolant is configured on that device. When lit, it indicates that it has been activated.") doc("fp_rps_trip", "RPS TRIP", "Flashes when the RPS has SCRAM'd the reactor due to a safety trip.") +sect("RPS Conditions") doc("fp_rps_man", "MANUAL", "The RPS was tripped manually (SCRAM by user, not via the Mekanism Reactor UI).") doc("fp_rps_auto", "AUTOMATIC", "The RPS was tripped by the supervisor automatically.") doc("fp_rps_to", "TIMEOUT", "The RPS tripped due to losing the supervisor connection.") @@ -183,11 +202,15 @@ doc("fp_rps_ccool", "LO CCOOLANT", "The RPS tripped due to having low levels of doc("fp_rps_ccool", "HI HCOOLANT", "The RPS tripped due to having high levels of heated coolant (> " .. const.RPS_LIMITS.MAX_HEATED_COLLANT_FILL .. "%).") target = docs.fp.rtu_gw +sect("Co-Routine States") doc("fp_rtu_rt_main", "RT MAIN", "This indicates if the device's main loop co-routine is running.") doc("fp_rtu_rt_comms", "RT COMMS", "This indicates if the communications handler co-routine is running.") +sect("Device List") doc("fp_rtu_rt", "RT", "In each RTU entry row, an RT light indicates if the co-routine for that RTU unit is running. This is never lit for redstone units.") doc("fp_rtu_rt", "Device Status", "In each RTU entry row, the light to the left of the device name indicates its peripheral status.") list(DOC_LIST_TYPE.LED, { "disconnected", "faulted", "unformed", "ok" }, { colors.red, colors.orange, colors.yellow, colors.green }) +text("Note that disconnected devices lack detailed information and will not be modifiable in configuration until re-connected.") +doc("fp_rtu_rt", "Device Assignment", "In each RTU entry row, the device identification is to the right of the status light. This begins with the device type and its index followed by its assignment after the \x1a, which is a unit or the facility (FACIL). Unit 1's 3rd turbine would show up as 'TURBINE 3 \x1a UNIT 1'.") docs.glossary = { abbvs = {}, terms = {} diff --git a/pocket/ui/pages/guide_section.lua b/pocket/ui/pages/guide_section.lua index a317174..326cbd5 100644 --- a/pocket/ui/pages/guide_section.lua +++ b/pocket/ui/pages/guide_section.lua @@ -48,11 +48,29 @@ return function (data, base_page, title, items, scroll_height) local _end for i = 1, #items do - local item = items[i] ---@type pocket_doc_sect|pocket_doc_text|pocket_doc_list + local item = items[i] ---@type pocket_doc_sect|pocket_doc_subsect|pocket_doc_text|pocket_doc_list if item.type == DOC_TYPE.SECTION then ---@cast item pocket_doc_sect + local anchor = TextBox{parent=def_list,text=item.name,anchor=true,fg_bg=cpair(colors.green,colors.black)} + + _end = Div{parent=def_list,height=1,can_focus=true} + + local function view() + _end.focus() + view_page.nav_to() + anchor.focus() + end + + if #name_list.get_children() > 0 then + local _ = Div{parent=name_list,height=1} + end + + PushButton{parent=name_list,text=item.name,fg_bg=cpair(colors.green,colors.black),active_fg_bg=btn_active,callback=view} + elseif item.type == DOC_TYPE.SUBSECTION then + ---@cast item pocket_doc_subsect + local anchor = TextBox{parent=def_list,text=item.name,anchor=true,fg_bg=cpair(colors.blue,colors.black)} if item.subtitle then @@ -75,7 +93,9 @@ return function (data, base_page, title, items, scroll_height) PushButton{parent=name_list,text=item.name,fg_bg=cpair(colors.blue,colors.black),active_fg_bg=btn_active,callback=view} elseif item.type == DOC_TYPE.TEXT then ---@cast item pocket_doc_text + TextBox{parent=def_list,text=item.text} + _end = Div{parent=def_list,height=1,can_focus=true} elseif item.type == DOC_TYPE.LIST then ---@cast item pocket_doc_list From d7ea68ed3a9b0b931c2ec55c7565c59fc29dd18c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 29 Aug 2024 22:49:20 -0400 Subject: [PATCH 17/40] #403 reactor PLC docs --- pocket/ui/apps/guide.lua | 2 +- pocket/ui/docs.lua | 10 +++++++--- pocket/ui/pages/guide_section.lua | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index 50e58b9..d1fc2c8 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -214,7 +214,7 @@ local function new_view(root) PushButton{parent=fps,x=2,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} local fp_common_page = guide_section(sect_construct_data, fps_page, "Common Items", docs.fp.common, 100) - local fp_rplc_page = guide_section(sect_construct_data, fps_page, "Reactor PLC", docs.fp.r_plc, 150) + local fp_rplc_page = guide_section(sect_construct_data, fps_page, "Reactor PLC", docs.fp.r_plc, 180) local fp_rtu_page = guide_section(sect_construct_data, fps_page, "RTU Gateway", docs.fp.rtu_gw, 70) PushButton{parent=fps,y=3,text="Common Items >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_common_page.nav_to} diff --git a/pocket/ui/docs.lua b/pocket/ui/docs.lua index c815897..17c47d0 100644 --- a/pocket/ui/docs.lua +++ b/pocket/ui/docs.lua @@ -162,9 +162,9 @@ docs.fp = { target = docs.fp.common sect("Core Status") -doc("fp_status", "STATUS", "This is always lit, except on the Reactor PLC. For that, it is green once initialized and OK (has all its peripherals) and red if something is wrong, in which case you should refer to the other indicator lights.") +doc("fp_status", "STATUS", "This is always lit, except on the Reactor PLC (see Reactor PLC section).") doc("fp_heartbeat", "HEARTBEAT", "This alternates between lit and unlit as the main loop on the device runs. If this freezes, something is wrong and the logs will indicate why.") -sect("Network") +sect("Hardware & Network") doc("fp_modem", "MODEM", "This lights up if the wireless/ender modem is connected. In parentheses is the unique computer ID of this device, which will show up in places such as the supervisor's connection lists.") doc("fp_modem", "NETWORK", "This is present when in standard color modes and indicates the network status using multiple colors.") list(DOC_LIST_TYPE.LED, { "not linked", "linked", "link denied", "bad comms version", "duplicate PLC" }, { colors.gray, colors.green, colors.red, colors.orange, colors.yellow }) @@ -176,7 +176,11 @@ doc("fp_fw", "FW", "Firmware application version of this device.") doc("fp_nt", "NT", "Network (comms) version this device has. These must match between devices in order for them to connect.") target = docs.fp.r_plc -sect("Network") +sect("Core Status") +doc("fp_status", "STATUS", "This is green once the PLC is initialized and OK (has all its peripherals) and red if something is wrong, in which case you should refer to the other indicator lights (REACTOR & MODEM).") +sect("Hardware & Network") +doc("fp_rplc_reactor", "REACTOR", "This indicates the status of the connected reactor peripheral.") +list(DOC_LIST_TYPE.LED, { "disconnected", "unformed", "ok" }, { colors.red, colors.yellow, colors.green }) doc("fp_nt_collision", "NT COLLISION", "(color accessibility modes only)", "This indicates the Reactor PLC unit ID is a duplicate of another already connected Reactor PLC.") sect("Co-Routine States") doc("fp_rplc_rt_main", "RT MAIN", "This lights up as long as the device's main loop co-routine is running, which it should be as long as STATUS is green.") diff --git a/pocket/ui/pages/guide_section.lua b/pocket/ui/pages/guide_section.lua index 326cbd5..55a094c 100644 --- a/pocket/ui/pages/guide_section.lua +++ b/pocket/ui/pages/guide_section.lua @@ -42,7 +42,7 @@ return function (data, base_page, title, items, scroll_height) TextBox{parent=section_view_div,y=1,text=title,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=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 name_list = ListBox{parent=section_div,x=1,y=3,scroll_height=40,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 From 07c3b3ec635994beab112ad81bc0030774984895 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 31 Aug 2024 00:17:39 -0400 Subject: [PATCH 18/40] #403 improved guide UI and added supervisor front panel docs --- pocket/ui/apps/guide.lua | 5 +++-- pocket/ui/docs.lua | 28 +++++++++++++++++++++++- pocket/ui/pages/guide_section.lua | 36 ++++++++++++++++++++----------- 3 files changed, 54 insertions(+), 15 deletions(-) diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index d1fc2c8..deb1bb4 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -215,12 +215,13 @@ local function new_view(root) local fp_common_page = guide_section(sect_construct_data, fps_page, "Common Items", docs.fp.common, 100) local fp_rplc_page = guide_section(sect_construct_data, fps_page, "Reactor PLC", docs.fp.r_plc, 180) - local fp_rtu_page = guide_section(sect_construct_data, fps_page, "RTU Gateway", docs.fp.rtu_gw, 70) + local fp_rtu_page = guide_section(sect_construct_data, fps_page, "RTU Gateway", docs.fp.rtu_gw, 100) + local fp_supervisor_page = guide_section(sect_construct_data, fps_page, "Supervisor", docs.fp.supervisor, 160) PushButton{parent=fps,y=3,text="Common Items >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_common_page.nav_to} PushButton{parent=fps,text="Reactor PLC >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_rplc_page.nav_to} PushButton{parent=fps,text="RTU Gateway >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_rtu_page.nav_to} - 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="Supervisor >",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=fp_supervisor_page.nav_to} 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",alignment=ALIGN.CENTER} diff --git a/pocket/ui/docs.lua b/pocket/ui/docs.lua index 17c47d0..c047712 100644 --- a/pocket/ui/docs.lua +++ b/pocket/ui/docs.lua @@ -155,7 +155,7 @@ doc("as_radiation", "Facility Radiation High", "Automatic SCRAM occurred due to doc("as_gen_fault", "Gen. Control Fault", "Automatic SCRAM occurred due to assigned units being degraded/no longer ready during generation mode. The system will automatically resume (starting with initial ramp) once the problem is resolved.") docs.fp = { - common = {}, r_plc = {}, rtu_gw = {} + common = {}, r_plc = {}, rtu_gw = {}, supervisor = {} } --comp id "This must never be the identical between devices, and that can only happen if you duplicate a computer (such as middle-click on it and place it elsewhere in creative mode)." @@ -216,6 +216,32 @@ list(DOC_LIST_TYPE.LED, { "disconnected", "faulted", "unformed", "ok" }, { color text("Note that disconnected devices lack detailed information and will not be modifiable in configuration until re-connected.") doc("fp_rtu_rt", "Device Assignment", "In each RTU entry row, the device identification is to the right of the status light. This begins with the device type and its index followed by its assignment after the \x1a, which is a unit or the facility (FACIL). Unit 1's 3rd turbine would show up as 'TURBINE 3 \x1a UNIT 1'.") +target = docs.fp.supervisor +sect("Round Trip Times") +doc("fp_sv_fw", "RTT", "Each connection has a round trip time, or RTT. Since the supervisor updates at a rate of 150ms, RTTs from ~150ms to ~300ms are typical. Higher RTTs indicate lag, and if they end up in the thousands there will be performance problems.") +list(DOC_LIST_TYPE.BULLET, { "green: <=300ms", "yellow: <=500ms ", "red: >500ms" }) +sect("SVR Tab") +text("This tab includes information about the supervisor, covered by 'Common Items'.") +sect("PLC Tab") +text("This tab lists the expected PLC connections based on the number of configured units. Status information about each connection is shown when linked.") +doc("fp_sv_link", "LINK", "This indicates if the reactor PLC is linked.") +doc("fp_sv_p_cmpid", "PLC Computer ID", "This shows the computer ID of the reactor PLC, or --- if disconnected.") +doc("fp_sv_p_fw", "PLC FW", "This shows the firmware version of the reactor PLC.") +sect("RTU Tab") +text("As RTU gateways connect to the supervisor, they will show up here along with some information.") +doc("fp_sv_r_cmpid", "RTU Computer ID", "At the start of the entry is an @ sign followed by the computer ID of the RTU gateway.") +doc("fp_sv_r_units", "UNITS", "This is a count of the number of RTUs configured on the RTU gateway (each line on the RTU gateway's front panel).") +doc("fp_sv_r_fw", "RTU FW", "This shows the firmware version of the RTU gateway.") +sect("PKT Tab") +text("As pocket computers connect to the supervisor, they will show up here along with some information. The properties listed are the same as with RTU gateways (except for UNITS), so they will not be further described here.") +sect("DEV Tab") +text("If nothing is connected, this will list all the expected RTU devices that aren't found. This page should be blank if everything is connected and configured correctly. If not, it will list certain types of detectable problems.") +doc("fp_sv_d_miss", "MISSING", "These items list missing devices, with the details that should be used in the RTU's configuration.") +doc("fp_sv_d_oor", "BAD INDEX", "If you have a configuration entry that has an index outside of the maximum number of devices configured on the supervisor, this will show up indicating what entry is incorrect. For example, if you specified a unit has 2 turbines and a #3 connected, it would show up here as out of range.") +doc("fp_sv_d_dupe", "DUPLICATE", "If a device tries to connect that is configured the same as another, it will be rejected and show up here. If you try to connect two #1 turbines for a unit, that would fail and one would appear here.") +sect("INF Tab") +text("This tab gives information about the other tabs, along with extra details on the DEV tab.") + docs.glossary = { abbvs = {}, terms = {} } diff --git a/pocket/ui/pages/guide_section.lua b/pocket/ui/pages/guide_section.lua index 55a094c..d3dba70 100644 --- a/pocket/ui/pages/guide_section.lua +++ b/pocket/ui/pages/guide_section.lua @@ -42,10 +42,11 @@ return function (data, base_page, title, items, scroll_height) TextBox{parent=section_view_div,y=1,text=title,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=section_div,x=1,y=3,scroll_height=40,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=60,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 + local sect_id = 1 + local page_end for i = 1, #items do local item = items[i] ---@type pocket_doc_sect|pocket_doc_subsect|pocket_doc_text|pocket_doc_list @@ -53,12 +54,17 @@ return function (data, base_page, title, items, scroll_height) if item.type == DOC_TYPE.SECTION then ---@cast item pocket_doc_sect - local anchor = TextBox{parent=def_list,text=item.name,anchor=true,fg_bg=cpair(colors.green,colors.black)} + local title_text = sect_id.."." + local title_offs = string.len(title_text) + 2 - _end = Div{parent=def_list,height=1,can_focus=true} + local sect_title = Div{parent=def_list,height=1} + TextBox{parent=sect_title,x=1,text=title_text,fg_bg=cpair(colors.lightGray,colors.black)} + local anchor = TextBox{parent=sect_title,x=title_offs,y=1,text=item.name,anchor=true,fg_bg=cpair(colors.green,colors.black)} + + page_end = Div{parent=def_list,height=1,can_focus=true} local function view() - _end.focus() + page_end.focus() view_page.nav_to() anchor.focus() end @@ -67,7 +73,11 @@ return function (data, base_page, title, items, scroll_height) local _ = Div{parent=name_list,height=1} end - PushButton{parent=name_list,text=item.name,fg_bg=cpair(colors.green,colors.black),active_fg_bg=btn_active,callback=view} + local name_title = Div{parent=name_list,height=1} + TextBox{parent=name_title,x=1,text=title_text,fg_bg=cpair(colors.lightGray,colors.black)} + PushButton{parent=name_title,x=title_offs,y=1,text=item.name,alignment=ALIGN.LEFT,fg_bg=cpair(colors.green,colors.black),active_fg_bg=btn_active,callback=view} + + sect_id = sect_id + 1 elseif item.type == DOC_TYPE.SUBSECTION then ---@cast item pocket_doc_subsect @@ -79,10 +89,10 @@ return function (data, base_page, title, items, scroll_height) TextBox{parent=def_list,text=item.body} - _end = Div{parent=def_list,height=1,can_focus=true} + page_end = Div{parent=def_list,height=1,can_focus=true} local function view() - _end.focus() + page_end.focus() view_page.nav_to() anchor.focus() end @@ -90,13 +100,15 @@ return function (data, base_page, title, items, scroll_height) doc_map[item.key] = 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} + local name_entry = Div{parent=name_list,height=#util.strwrap(item.name,name_list.get_width()-3)} + TextBox{parent=name_entry,x=1,text="\x10",fg_bg=cpair(colors.gray,colors.black)} + PushButton{parent=name_entry,x=3,y=1,text=item.name,alignment=ALIGN.LEFT,fg_bg=cpair(colors.blue,colors.black),active_fg_bg=btn_active,callback=view} elseif item.type == DOC_TYPE.TEXT then ---@cast item pocket_doc_text TextBox{parent=def_list,text=item.text} - _end = Div{parent=def_list,height=1,can_focus=true} + page_end = Div{parent=def_list,height=1,can_focus=true} elseif item.type == DOC_TYPE.LIST then ---@cast item pocket_doc_list @@ -121,13 +133,13 @@ return function (data, base_page, title, items, scroll_height) end end - _end = Div{parent=def_list,height=1,can_focus=true} + page_end = Div{parent=def_list,height=1,can_focus=true} end if i % 12 == 0 then util.nop() end end - log.debug("guide section " .. title .. " generated with final height ".. _end.get_y()) + log.debug("guide section " .. title .. " generated with final height ".. page_end.get_y()) util.nop() From f8bd79a234a98e07f0987393d5747ef904e2b734 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Mon, 2 Sep 2024 22:25:33 -0400 Subject: [PATCH 19/40] #498 work on command handling --- coordinator/process.lua | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/coordinator/process.lua b/coordinator/process.lua index 32c8e37..896573f 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -41,9 +41,6 @@ local pctl = { commands = { unit = {}, fac = {} } } -for _, v in pairs(U_CMD) do pctl.commands.unit[v] = { active = false, timeout = 0, requestors = {} } end -for _, v in pairs(F_CMD) do pctl.commands.fac[v] = { active = false, timeout = 0, requestors = {} } end - -- write auto process control to config file local function _write_auto_config() -- save config @@ -63,6 +60,13 @@ function process.init(iocontrol, coord_comms) pctl.io = iocontrol pctl.comms = coord_comms + -- create command handling objects + for _, v in pairs(F_CMD) do pctl.commands.fac[v] = { active = false, timeout = 0, requestors = {} } end + for i = 1, pctl.io.facility.num_units do + pctl.commands.unit[i] = {} + for _, v in pairs(U_CMD) do pctl.commands.unit[i][v] = { active = false, timeout = 0, requestors = {} } end + end + local ctl_proc = pctl.control_states.process for i = 1, pctl.io.facility.num_units do @@ -151,7 +155,7 @@ function process.create_handle() return new end - local function u_request(cmd_id) return request(pctl.commands.unit[cmd_id]) end + local function u_request(u_id, cmd_id) return request(pctl.commands.unit[u_id][cmd_id]) end local function f_request(cmd_id) return request(pctl.commands.fac[cmd_id]) end --#region Facility Commands @@ -179,7 +183,7 @@ function process.create_handle() -- start a reactor ---@param id integer unit ID function handle.start(id) - if u_request(U_CMD.START) then + if u_request(id, U_CMD.START) then pctl.io.units[id].control_state = true pctl.comms.send_unit_command(U_CMD.START, id) log.debug(util.c("PROCESS: UNIT[", id, "] START")) @@ -189,7 +193,7 @@ function process.create_handle() -- SCRAM reactor ---@param id integer unit ID function handle.scram(id) - if u_request(U_CMD.SCRAM) then + if u_request(id, U_CMD.SCRAM) then pctl.io.units[id].control_state = false pctl.comms.send_unit_command(U_CMD.SCRAM, id) log.debug(util.c("PROCESS: UNIT[", id, "] SCRAM")) @@ -199,7 +203,7 @@ function process.create_handle() -- reset reactor protection system ---@param id integer unit ID function handle.reset_rps(id) - if u_request(U_CMD.RESET_RPS) then + if u_request(id, U_CMD.RESET_RPS) then pctl.comms.send_unit_command(U_CMD.RESET_RPS, id) log.debug(util.c("PROCESS: UNIT[", id, "] RESET RPS")) end @@ -208,7 +212,7 @@ function process.create_handle() -- acknowledge all alarms ---@param id integer unit ID function handle.ack_all_alarms(id) - if u_request(U_CMD.ACK_ALL_ALARMS) then + if u_request(id, U_CMD.ACK_ALL_ALARMS) then pctl.comms.send_unit_command(U_CMD.ACK_ALL_ALARMS, id) log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALL ALARMS")) end @@ -222,7 +226,15 @@ end function process.clear_timed_out() end -function process.handle_ack() +---@param command FAC_COMMAND command +function process.fac_ack(command) + local cmd_req = pctl.commands.fac[command] +end + +---@param unit integer unit ID +---@param command UNIT_COMMAND command +function process.unit_ack(unit, command) + local cmd_req = pctl.commands.unit[unit][command] end --#region One-Way Commands (no acknowledgements) From b5b67b425ac99ac4e0643bdbcefdd26546227431 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 4 Sep 2024 21:12:43 +0000 Subject: [PATCH 20/40] #498 work on command acknowledgement handling --- coordinator/coordinator.lua | 4 +- coordinator/process.lua | 103 ++++++++++++++++++---- coordinator/ui/components/process_ctl.lua | 4 +- 3 files changed, 92 insertions(+), 19 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index a0d7169..000ce5f 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -576,7 +576,7 @@ function coordinator.comms(version, nic, sv_watchdog) local ack = packet.data[2] == true if cmd == FAC_COMMAND.SCRAM_ALL then - iocontrol.get_db().facility.scram_ack(ack) + process.fac_ack(cmd, ack) elseif cmd == FAC_COMMAND.STOP then iocontrol.get_db().facility.stop_ack(ack) elseif cmd == FAC_COMMAND.START then @@ -586,7 +586,7 @@ function coordinator.comms(version, nic, sv_watchdog) log.debug("SCADA_CRDN process start (with configuration) ack echo packet length mismatch") end elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then - iocontrol.get_db().facility.ack_alarms_ack(ack) + process.fac_ack(cmd, ack) elseif cmd == FAC_COMMAND.SET_WASTE_MODE then process.waste_ack_handle(packet.data[2]) elseif cmd == FAC_COMMAND.SET_PU_FB then diff --git a/coordinator/process.lua b/coordinator/process.lua index 896573f..06078e2 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -13,7 +13,7 @@ local U_CMD = comms.UNIT_COMMAND local PROCESS = types.PROCESS local PRODUCT = types.WASTE_PRODUCT -local REQUEST_TIMEOUT_MS = 5000 +local REQUEST_TIMEOUT_MS = 10000 ---@class process_controller local process = {} @@ -41,6 +41,11 @@ local pctl = { commands = { unit = {}, fac = {} } } +---@class process_command_state +---@field active boolean +---@field timeout integer +---@field requestors table + -- write auto process control to config file local function _write_auto_config() -- save config @@ -142,7 +147,7 @@ function process.create_handle() ---@class process_handle local handle = {} - local function request(cmd) + local function request(cmd, ack) local new = not cmd.active if new then @@ -150,19 +155,19 @@ function process.create_handle() cmd.timeout = util.time_ms() + REQUEST_TIMEOUT_MS end - cmd.requstors[self.id] = true + table.insert(cmd.requstors, ack) return new end - local function u_request(u_id, cmd_id) return request(pctl.commands.unit[u_id][cmd_id]) end - local function f_request(cmd_id) return request(pctl.commands.fac[cmd_id]) end + local function u_request(u_id, cmd_id, ack) return request(pctl.commands.unit[u_id][cmd_id], ack) end + local function f_request(cmd_id, ack) return request(pctl.commands.fac[cmd_id], ack) end --#region Facility Commands -- facility SCRAM command function handle.fac_scram() - if f_request(F_CMD.SCRAM_ALL) then + if f_request(F_CMD.SCRAM_ALL, handle.on_fac_scram_ack) then pctl.comms.send_fac_command(F_CMD.SCRAM_ALL) log.debug("PROCESS: FAC SCRAM ALL") end @@ -170,12 +175,26 @@ function process.create_handle() -- facility alarm acknowledge command function handle.fac_ack_alarms() - if f_request(F_CMD.ACK_ALL_ALARMS) then + if f_request(F_CMD.ACK_ALL_ALARMS, handle.on_fac_ack_alarms_ack) then pctl.comms.send_fac_command(F_CMD.ACK_ALL_ALARMS) log.debug("PROCESS: FAC ACK ALL ALARMS") end end + -- luacheck: no unused args + + -- facility SCRAM ack, override to implement + ---@param success boolean + ---@diagnostic disable-next-line: unused-local + function handle.on_fac_scram_ack(success) end + + -- facility acknowledge all alarms ack, override to implement + ---@param success boolean + ---@diagnostic disable-next-line: unused-local + function handle.on_fac_ack_alarms_ack(success) end + + -- luacheck: unused args + --#endregion --#region Unit Commands @@ -183,7 +202,7 @@ function process.create_handle() -- start a reactor ---@param id integer unit ID function handle.start(id) - if u_request(id, U_CMD.START) then + if u_request(id, U_CMD.START, handle.on_unit_start_ack) then pctl.io.units[id].control_state = true pctl.comms.send_unit_command(U_CMD.START, id) log.debug(util.c("PROCESS: UNIT[", id, "] START")) @@ -193,7 +212,7 @@ function process.create_handle() -- SCRAM reactor ---@param id integer unit ID function handle.scram(id) - if u_request(id, U_CMD.SCRAM) then + if u_request(id, U_CMD.SCRAM, handle.on_unit_scram_ack) then pctl.io.units[id].control_state = false pctl.comms.send_unit_command(U_CMD.SCRAM, id) log.debug(util.c("PROCESS: UNIT[", id, "] SCRAM")) @@ -203,7 +222,7 @@ function process.create_handle() -- reset reactor protection system ---@param id integer unit ID function handle.reset_rps(id) - if u_request(id, U_CMD.RESET_RPS) then + if u_request(id, U_CMD.RESET_RPS, handle.on_unit_rps_reset_ack) then pctl.comms.send_unit_command(U_CMD.RESET_RPS, id) log.debug(util.c("PROCESS: UNIT[", id, "] RESET RPS")) end @@ -212,29 +231,83 @@ function process.create_handle() -- acknowledge all alarms ---@param id integer unit ID function handle.ack_all_alarms(id) - if u_request(id, U_CMD.ACK_ALL_ALARMS) then + if u_request(id, U_CMD.ACK_ALL_ALARMS, handle.on_unit_ack_alarms_ack) then pctl.comms.send_unit_command(U_CMD.ACK_ALL_ALARMS, id) log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALL ALARMS")) end end + -- luacheck: no unused args + + -- unit start ack, override to implement + ---@param success boolean + ---@diagnostic disable-next-line: unused-local + function handle.on_unit_start_ack(success) end + + -- unit SCRAM ack, override to implement + ---@param success boolean + ---@diagnostic disable-next-line: unused-local + function handle.on_unit_scram_ack(success) end + + -- unit RPS reset ack, override to implement + ---@param success boolean + ---@diagnostic disable-next-line: unused-local + function handle.on_unit_rps_reset_ack(success) end + + -- unit acknowledge all alarms ack, override to implement + ---@param success boolean + ---@diagnostic disable-next-line: unused-local + function handle.on_unit_ack_alarms_ack(success) end + + -- luacheck: unused args + --#endregion return handle end function process.clear_timed_out() + local now = util.time_ms() + local objs = { pctl.commands.fac, table.unpack(pctl.commands.unit) } + + for _, obj in pairs(objs) do + ---@cast obj process_command_state + + -- cancel expired requests + if obj.active and now > obj.timeout then + obj.active = false + obj.requestors = {} + end + end end +-- handle a command acknowledgement +---@param cmd_state process_command_state +---@param success boolean if the command was successful +local function cmd_ack(cmd_state, success) + if cmd_state.active then + cmd_state.active = false + + -- call all acknowledge callback functions + for i = 1, #cmd_state.requestors do + cmd_state.requestors[i](success) + end + end +end + +-- handle a facility command acknowledgement ---@param command FAC_COMMAND command -function process.fac_ack(command) - local cmd_req = pctl.commands.fac[command] +---@param success boolean if the command was successful +function process.fac_ack(command, success) + cmd_ack(pctl.commands.fac[command], success) end +-- handle a unit command acknowledgement ---@param unit integer unit ID ---@param command UNIT_COMMAND command -function process.unit_ack(unit, command) - local cmd_req = pctl.commands.unit[unit][command] +---@param success boolean if the command was successful +function process.unit_ack(unit, command, success) + cmd_ack(pctl.commands.unit[unit][command], success) end --#region One-Way Commands (no acknowledgements) diff --git a/coordinator/ui/components/process_ctl.lua b/coordinator/ui/components/process_ctl.lua index 9517823..cbfa420 100644 --- a/coordinator/ui/components/process_ctl.lua +++ b/coordinator/ui/components/process_ctl.lua @@ -66,8 +66,8 @@ local function new_view(root, x, y) local scram = HazardButton{parent=main,x=1,y=1,text="FAC SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=db.process.fac_scram,fg_bg=hzd_fg_bg} local ack_a = HazardButton{parent=main,x=16,y=1,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=db.process.fac_ack_alarms,fg_bg=hzd_fg_bg} - facility.scram_ack = scram.on_response - facility.ack_alarms_ack = ack_a.on_response + db.process.on_fac_scram_ack = scram.on_response + db.process.on_fac_ack_alarms_ack = ack_a.on_response local all_ok = IndicatorLight{parent=main,y=5,label="Unit Systems Online",colors=ind_grn} local rad_mon = TriIndicatorLight{parent=main,label="Radiation Monitor",c1=style.ind_bkg,c2=ind_yel.fgd,c3=ind_grn.fgd} From dbd79cbc4f6d57c6d2d0b86c6fc433bce1799843 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 5 Sep 2024 21:49:47 -0400 Subject: [PATCH 21/40] #498 coordinator process handle system for manual controls --- coordinator/coordinator.lua | 10 +- coordinator/iocontrol.lua | 13 +-- coordinator/process.lua | 109 ++++++++++++---------- coordinator/threads.lua | 4 + coordinator/ui/components/process_ctl.lua | 4 +- coordinator/ui/components/unit_detail.lua | 8 +- 6 files changed, 81 insertions(+), 67 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 000ce5f..6e06fed 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -591,6 +591,8 @@ function coordinator.comms(version, nic, sv_watchdog) process.waste_ack_handle(packet.data[2]) elseif cmd == FAC_COMMAND.SET_PU_FB then process.pu_fb_ack_handle(packet.data[2]) + elseif cmd == FAC_COMMAND.SET_SPS_LP then + ---@todo else log.debug(util.c("received facility command ack with unknown command ", cmd)) end @@ -625,17 +627,17 @@ function coordinator.comms(version, nic, sv_watchdog) if unit ~= nil then if cmd == UNIT_COMMAND.SCRAM then - unit.scram_ack(ack) + process.unit_ack(unit_id, cmd, ack) elseif cmd == UNIT_COMMAND.START then - unit.start_ack(ack) + process.unit_ack(unit_id, cmd, ack) elseif cmd == UNIT_COMMAND.RESET_RPS then - unit.reset_rps_ack(ack) + process.unit_ack(unit_id, cmd, ack) elseif cmd == UNIT_COMMAND.SET_BURN then -- this also doesn't exist elseif cmd == UNIT_COMMAND.SET_WASTE then -- updated by unit updates elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then - unit.ack_alarms_ack(ack) + process.unit_ack(unit_id, cmd, ack) elseif cmd == UNIT_COMMAND.SET_GROUP then -- updated by unit updates else diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index de2b8cc..956cf41 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -82,9 +82,6 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale) io.energy_convert_to_fe = util.joules_to_fe_rf end - -- coordinator's process handle - io.process = process.create_handle() - -- facility data structure ---@class ioctl_facility io.facility = { @@ -121,8 +118,6 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale) save_cfg_ack = __generic_ack, start_ack = __generic_ack, stop_ack = __generic_ack, - scram_ack = __generic_ack, - ack_alarms_ack = __generic_ack, alarm_tones = { false, false, false, false, false, false, false, false }, @@ -198,11 +193,6 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale) set_group = function (grp) process.set_group(i, grp) end, ---@param grp integer|0 group ID or 0 for manual - start_ack = __generic_ack, - scram_ack = __generic_ack, - reset_rps_ack = __generic_ack, - ack_alarms_ack = __generic_ack, - alarm_callbacks = { c_breach = { ack = function () ack(1) end, reset = function () reset(1) end }, radiation = { ack = function () ack(2) end, reset = function () reset(2) end }, @@ -282,6 +272,9 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale) -- pass IO control here since it can't be require'd due to a require loop process.init(io, comms) + + -- coordinator's process handle + io.process = process.create_handle() end --#region Front Panel PSIL diff --git a/coordinator/process.lua b/coordinator/process.lua index 06078e2..9df2884 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -19,8 +19,8 @@ local REQUEST_TIMEOUT_MS = 10000 local process = {} local pctl = { - io = nil, ---@type ioctl - comms = nil, ---@type coord_comms + io = nil, ---@type ioctl + comms = nil, ---@type coord_comms ---@class sys_control_states control_states = { ---@class sys_auto_config @@ -37,14 +37,16 @@ local pctl = { waste_modes = {}, priority_groups = {} }, - next_handle = 0, - commands = { unit = {}, fac = {} } + commands = { + unit = {}, ---@type process_command_state[][] + fac = {} ---@type process_command_state[] + } } ---@class process_command_state ----@field active boolean ----@field timeout integer ----@field requestors table +---@field active boolean if this command is live +---@field timeout integer expiration time of this command request +---@field requestors table list of callbacks from the requestors -- write auto process control to config file local function _write_auto_config() @@ -138,15 +140,12 @@ end -- create a handle to process control for usage of commands that get acknowledgements function process.create_handle() - local self = { - id = pctl.next_handle - } - - pctl.next_handle = pctl.next_handle + 1 - ---@class process_handle local handle = {} + -- add this handle to the requestors and activate the command if inactive + ---@param cmd process_command_state + ---@param ack function local function request(cmd, ack) local new = not cmd.active @@ -155,7 +154,7 @@ function process.create_handle() cmd.timeout = util.time_ms() + REQUEST_TIMEOUT_MS end - table.insert(cmd.requstors, ack) + table.insert(cmd.requestors, ack) return new end @@ -167,7 +166,7 @@ function process.create_handle() -- facility SCRAM command function handle.fac_scram() - if f_request(F_CMD.SCRAM_ALL, handle.on_fac_scram_ack) then + if f_request(F_CMD.SCRAM_ALL, handle.fac_ack.on_scram) then pctl.comms.send_fac_command(F_CMD.SCRAM_ALL) log.debug("PROCESS: FAC SCRAM ALL") end @@ -175,23 +174,25 @@ function process.create_handle() -- facility alarm acknowledge command function handle.fac_ack_alarms() - if f_request(F_CMD.ACK_ALL_ALARMS, handle.on_fac_ack_alarms_ack) then + if f_request(F_CMD.ACK_ALL_ALARMS, handle.fac_ack.on_ack_alarms) then pctl.comms.send_fac_command(F_CMD.ACK_ALL_ALARMS) log.debug("PROCESS: FAC ACK ALL ALARMS") end end + handle.fac_ack = {} + -- luacheck: no unused args -- facility SCRAM ack, override to implement ---@param success boolean ---@diagnostic disable-next-line: unused-local - function handle.on_fac_scram_ack(success) end + function handle.fac_ack.on_scram(success) end -- facility acknowledge all alarms ack, override to implement ---@param success boolean ---@diagnostic disable-next-line: unused-local - function handle.on_fac_ack_alarms_ack(success) end + function handle.fac_ack.on_ack_alarms(success) end -- luacheck: unused args @@ -202,7 +203,7 @@ function process.create_handle() -- start a reactor ---@param id integer unit ID function handle.start(id) - if u_request(id, U_CMD.START, handle.on_unit_start_ack) then + if u_request(id, U_CMD.START, handle.unit_ack[id].on_start) then pctl.io.units[id].control_state = true pctl.comms.send_unit_command(U_CMD.START, id) log.debug(util.c("PROCESS: UNIT[", id, "] START")) @@ -212,7 +213,7 @@ function process.create_handle() -- SCRAM reactor ---@param id integer unit ID function handle.scram(id) - if u_request(id, U_CMD.SCRAM, handle.on_unit_scram_ack) then + if u_request(id, U_CMD.SCRAM, handle.unit_ack[id].on_scram) then pctl.io.units[id].control_state = false pctl.comms.send_unit_command(U_CMD.SCRAM, id) log.debug(util.c("PROCESS: UNIT[", id, "] SCRAM")) @@ -222,7 +223,7 @@ function process.create_handle() -- reset reactor protection system ---@param id integer unit ID function handle.reset_rps(id) - if u_request(id, U_CMD.RESET_RPS, handle.on_unit_rps_reset_ack) then + if u_request(id, U_CMD.RESET_RPS, handle.unit_ack[id].on_rps_reset) then pctl.comms.send_unit_command(U_CMD.RESET_RPS, id) log.debug(util.c("PROCESS: UNIT[", id, "] RESET RPS")) end @@ -231,52 +232,64 @@ function process.create_handle() -- acknowledge all alarms ---@param id integer unit ID function handle.ack_all_alarms(id) - if u_request(id, U_CMD.ACK_ALL_ALARMS, handle.on_unit_ack_alarms_ack) then + if u_request(id, U_CMD.ACK_ALL_ALARMS, handle.unit_ack[id].on_ack_alarms) then pctl.comms.send_unit_command(U_CMD.ACK_ALL_ALARMS, id) log.debug(util.c("PROCESS: UNIT[", id, "] ACK ALL ALARMS")) end end - -- luacheck: no unused args + -- unit command acknowledgement callbacks, indexed by unit ID + ---@type process_unit_ack[] + handle.unit_ack = {} - -- unit start ack, override to implement - ---@param success boolean - ---@diagnostic disable-next-line: unused-local - function handle.on_unit_start_ack(success) end + for u = 1, pctl.io.facility.num_units do + handle.unit_ack[u] = {} - -- unit SCRAM ack, override to implement - ---@param success boolean - ---@diagnostic disable-next-line: unused-local - function handle.on_unit_scram_ack(success) end + ---@class process_unit_ack + local u_ack = handle.unit_ack[u] - -- unit RPS reset ack, override to implement - ---@param success boolean - ---@diagnostic disable-next-line: unused-local - function handle.on_unit_rps_reset_ack(success) end + -- luacheck: no unused args - -- unit acknowledge all alarms ack, override to implement - ---@param success boolean - ---@diagnostic disable-next-line: unused-local - function handle.on_unit_ack_alarms_ack(success) end + -- unit start ack, override to implement + ---@param success boolean + ---@diagnostic disable-next-line: unused-local + function u_ack.on_start(success) end - -- luacheck: unused args + -- unit SCRAM ack, override to implement + ---@param success boolean + ---@diagnostic disable-next-line: unused-local + function u_ack.on_scram(success) end + + -- unit RPS reset ack, override to implement + ---@param success boolean + ---@diagnostic disable-next-line: unused-local + function u_ack.on_rps_reset(success) end + + -- unit acknowledge all alarms ack, override to implement + ---@param success boolean + ---@diagnostic disable-next-line: unused-local + function u_ack.on_ack_alarms(success) end + + -- luacheck: unused args + end --#endregion return handle end +-- clear outstanding process commands that have timed out function process.clear_timed_out() local now = util.time_ms() local objs = { pctl.commands.fac, table.unpack(pctl.commands.unit) } for _, obj in pairs(objs) do - ---@cast obj process_command_state - -- cancel expired requests - if obj.active and now > obj.timeout then - obj.active = false - obj.requestors = {} + for _, cmd in pairs(obj) do + if cmd.active and now > cmd.timeout then + cmd.active = false + cmd.requestors = {} + end end end end @@ -303,11 +316,13 @@ function process.fac_ack(command, success) end -- handle a unit command acknowledgement ----@param unit integer unit ID +---@param unit any unit ID (invalid index protected) ---@param command UNIT_COMMAND command ---@param success boolean if the command was successful function process.unit_ack(unit, command, success) - cmd_ack(pctl.commands.unit[unit][command], success) + if pctl.commands.unit[unit] then + cmd_ack(pctl.commands.unit[unit][command], success) + end end --#region One-Way Commands (no acknowledgements) diff --git a/coordinator/threads.lua b/coordinator/threads.lua index 20ba7ae..cefe705 100644 --- a/coordinator/threads.lua +++ b/coordinator/threads.lua @@ -6,6 +6,7 @@ local util = require("scada-common.util") local coordinator = require("coordinator.coordinator") local iocontrol = require("coordinator.iocontrol") +local process = require("coordinator.process") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") @@ -147,6 +148,9 @@ function threads.thread__main(smem) apisessions.iterate_all() apisessions.free_all_closed() + -- clear timed out process commands + process.clear_timed_out() + if renderer.ui_ready() then -- update clock used on main and flow monitors iocontrol.get_db().facility.ps.publish("date_time", os.date(smem.date_format)) diff --git a/coordinator/ui/components/process_ctl.lua b/coordinator/ui/components/process_ctl.lua index cbfa420..85e0436 100644 --- a/coordinator/ui/components/process_ctl.lua +++ b/coordinator/ui/components/process_ctl.lua @@ -66,8 +66,8 @@ local function new_view(root, x, y) local scram = HazardButton{parent=main,x=1,y=1,text="FAC SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=db.process.fac_scram,fg_bg=hzd_fg_bg} local ack_a = HazardButton{parent=main,x=16,y=1,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=db.process.fac_ack_alarms,fg_bg=hzd_fg_bg} - db.process.on_fac_scram_ack = scram.on_response - db.process.on_fac_ack_alarms_ack = ack_a.on_response + db.process.fac_ack.on_scram = scram.on_response + db.process.fac_ack.on_ack_alarms = ack_a.on_response local all_ok = IndicatorLight{parent=main,y=5,label="Unit Systems Online",colors=ind_grn} local rad_mon = TriIndicatorLight{parent=main,label="Radiation Monitor",c1=style.ind_bkg,c2=ind_yel.fgd,c3=ind_grn.fgd} diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 479c099..d898d34 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -373,10 +373,10 @@ local function init(parent, id) local scram = HazardButton{parent=main,x=2,y=32,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=hzd_fg_bg} local reset = HazardButton{parent=main,x=22,y=32,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,fg_bg=hzd_fg_bg} - unit.start_ack = start.on_response - unit.scram_ack = scram.on_response - unit.reset_rps_ack = reset.on_response - unit.ack_alarms_ack = ack_a.on_response + db.process.unit_ack[id].on_start = start.on_response + db.process.unit_ack[id].on_scram = scram.on_response + db.process.unit_ack[id].on_rps_reset = reset.on_response + db.process.unit_ack[id].on_ack_alarms = ack_a.on_response local function start_button_en_check() if (unit.reactor_data ~= nil) and (unit.reactor_data.mek_status ~= nil) then From 66fae0695cb90e4fcf5c887e2bfd57990d3e4b60 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 5 Sep 2024 22:01:58 -0400 Subject: [PATCH 22/40] #498 handle pocket manual unit commands --- coordinator/session/pocket.lua | 74 +++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/coordinator/session/pocket.lua b/coordinator/session/pocket.lua index 5b5e7ce..097241f 100644 --- a/coordinator/session/pocket.lua +++ b/coordinator/session/pocket.lua @@ -4,12 +4,14 @@ local mqueue = require("scada-common.mqueue") local util = require("scada-common.util") local iocontrol = require("coordinator.iocontrol") +local process = require("coordinator.process") local pocket = {} local PROTOCOL = comms.PROTOCOL local CRDN_TYPE = comms.CRDN_TYPE local MGMT_TYPE = comms.MGMT_TYPE +local UNIT_COMMAND = comms.UNIT_COMMAND -- retry time constants in ms -- local INITIAL_WAIT = 1500 @@ -46,6 +48,8 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) connected = true, conn_watchdog = util.new_watchdog(timeout), last_rtt = 0, + -- process accessor handle + proc_handle = process.create_handle(), -- periodic messages periodics = { last_update = 0, @@ -101,6 +105,14 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) self.seq_num = self.seq_num + 1 end + -- link callback transmissions + for u = 1, iocontrol.get_db().facility.num_units do + self.proc_handle.unit_ack[u].on_start = function (success) _send(CRDN_TYPE.UNIT_CMD, { UNIT_COMMAND.START, u, success }) end + self.proc_handle.unit_ack[u].on_scram = function (success) _send(CRDN_TYPE.UNIT_CMD, { UNIT_COMMAND.SCRAM, u, success }) end + self.proc_handle.unit_ack[u].on_rps_reset = function (success) _send(CRDN_TYPE.UNIT_CMD, { UNIT_COMMAND.RESET_RPS, u, success }) end + self.proc_handle.unit_ack[u].on_ack_alarms = function (success) _send(CRDN_TYPE.UNIT_CMD, { UNIT_COMMAND.ACK_ALL_ALARMS, u, success }) end + end + -- handle a packet ---@param pkt mgmt_frame|crdn_frame local function _handle_packet(pkt) @@ -122,7 +134,67 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) local db = iocontrol.get_db() -- handle packet by type - if pkt.type == CRDN_TYPE.API_GET_FAC then + if pkt.type == CRDN_TYPE.UNIT_CMD then + if pkt.length >= 2 then + -- get command and unit id + local cmd = pkt.data[1] + local uid = pkt.data[2] + + -- continue if valid unit id + if util.is_int(uid) and uid > 0 and uid <= #db.units then + if cmd == UNIT_COMMAND.START then + log.info(util.c(log_header, "UNIT[", uid, "] START")) + self.proc_handle.start(uid) + elseif cmd == UNIT_COMMAND.SCRAM then + log.info(util.c(log_header, "UNIT[", uid, "] SCRAM")) + self.proc_handle.scram(uid) + elseif cmd == UNIT_COMMAND.RESET_RPS then + log.info(util.c(log_header, "UNIT[", uid, "] RESET RPS")) + self.proc_handle.reset_rps(uid) + elseif cmd == UNIT_COMMAND.SET_BURN then + if pkt.length == 3 then + log.info(util.c(log_header, "UNIT[", uid, "] SET BURN ", pkt.data[3])) + process.set_rate(uid, pkt.data[3]) + else + log.debug(log_header .. "CRDN unit command burn rate missing option") + end + elseif cmd == UNIT_COMMAND.SET_WASTE then + -- if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] > 0) and (pkt.data[3] <= 4) then + -- process.set_unit_waste(uid, pkt.data[3]) + -- else + -- log.debug(log_header .. "CRDN unit command set waste missing/invalid option") + -- end + elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then + log.info(util.c(log_header, "UNIT[", uid, "] ACK ALL ALARMS")) + self.proc_handle.ack_all_alarms(uid) + elseif cmd == UNIT_COMMAND.ACK_ALARM then + -- if pkt.length == 3 then + -- process.ack_alarm(uid, pkt.data[3]) + -- else + -- log.debug(log_header .. "CRDN unit command ack alarm missing alarm id") + -- end + elseif cmd == UNIT_COMMAND.RESET_ALARM then + -- if pkt.length == 3 then + -- process.reset_alarm(uid, pkt.data[3]) + -- else + -- log.debug(log_header .. "CRDN unit command reset alarm missing alarm id") + -- end + elseif cmd == UNIT_COMMAND.SET_GROUP then + -- if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] >= 0) and (pkt.data[3] <= 4) then + -- process.set_group(uid, pkt.data[3]) + -- else + -- log.debug(log_header .. "CRDN unit command set group missing group id") + -- end + else + log.debug(log_header .. "CRDN unit command unknown") + end + else + log.debug(log_header .. "CRDN unit command invalid") + end + else + log.debug(log_header .. "CRDN unit command packet length mismatch") + end + elseif pkt.type == CRDN_TYPE.API_GET_FAC then local fac = db.facility local data = { From ab11ff03b5b6cb9e996ae074190613d0e43b21c4 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 5 Sep 2024 22:18:59 -0400 Subject: [PATCH 23/40] #498 functioning pocket manual unit controls --- coordinator/process.lua | 6 ++---- pocket/pocket.lua | 31 ++++++++++++++++++++++++++++++- pocket/ui/apps/control.lua | 22 ++++++++++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/coordinator/process.lua b/coordinator/process.lua index 9df2884..d74ea97 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -316,13 +316,11 @@ function process.fac_ack(command, success) end -- handle a unit command acknowledgement ----@param unit any unit ID (invalid index protected) +---@param unit integer unit ID ---@param command UNIT_COMMAND command ---@param success boolean if the command was successful function process.unit_ack(unit, command, success) - if pctl.commands.unit[unit] then - cmd_ack(pctl.commands.unit[unit][command], success) - end + cmd_ack(pctl.commands.unit[unit][command], success) end --#region One-Way Commands (no acknowledgements) diff --git a/pocket/pocket.lua b/pocket/pocket.lua index f53a60e..907b162 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -654,7 +654,36 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) if protocol == PROTOCOL.SCADA_CRDN then ---@cast packet crdn_frame if self.api.linked then - if packet.type == CRDN_TYPE.API_GET_FAC then + if packet.type == CRDN_TYPE.UNIT_CMD then + -- unit command acknowledgement + if packet.length == 3 then + local cmd = packet.data[1] + local unit_id = packet.data[2] + local ack = packet.data[3] == true + + local unit = iocontrol.get_db().units[unit_id] ---@type pioctl_unit + + if unit ~= nil then + if cmd == UNIT_COMMAND.SCRAM then + unit.scram_ack(ack) + elseif cmd == UNIT_COMMAND.START then + unit.start_ack(ack) + elseif cmd == UNIT_COMMAND.RESET_RPS then + unit.reset_rps_ack(ack) + elseif cmd == UNIT_COMMAND.SET_BURN then + -- this also doesn't exist + elseif cmd == UNIT_COMMAND.SET_WASTE then + -- updated by unit updates + elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then + unit.ack_alarms_ack(ack) + elseif cmd == UNIT_COMMAND.SET_GROUP then + -- updated by unit updates + else + log.debug(util.c("received unit command ack with unknown command ", cmd)) + end + end + end + elseif packet.type == CRDN_TYPE.API_GET_FAC then if _check_length(packet, 11) then iocontrol.record_facility_data(packet.data) end diff --git a/pocket/ui/apps/control.lua b/pocket/ui/apps/control.lua index cc533d2..c76b95a 100644 --- a/pocket/ui/apps/control.lua +++ b/pocket/ui/apps/control.lua @@ -154,6 +154,7 @@ local function new_view(root) local set_burn = function () unit.set_burn(burn_cmd.get_value()) end local set_burn_btn = PushButton{parent=u_div,x=19,y=8,text="SET",min_width=5,fg_bg=cpair(colors.green,colors.black),active_fg_bg=cpair(colors.white,colors.black),dis_fg_bg=cpair(colors.gray,colors.black),callback=set_burn} + burn_cmd.register(u_ps, "burn_rate", burn_cmd.set_value) burn_cmd.register(u_ps, "max_burn", burn_cmd.set_max) local start = HazardButton{parent=u_div,x=2,y=11,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=hzd_fg_bg} @@ -161,6 +162,27 @@ local function new_view(root) local scram = HazardButton{parent=u_div,x=2,y=15,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=hzd_fg_bg} local reset = HazardButton{parent=u_div,x=12,y=15,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,fg_bg=hzd_fg_bg} + unit.start_ack = start.on_response + unit.ack_alarms_ack = ack_a.on_response + unit.scram_ack = scram.on_response + unit.reset_rps_ack = reset.on_response + + local function start_button_en_check() + if (unit.reactor_data ~= nil) and (unit.reactor_data.mek_status ~= nil) then + local can_start = (not unit.reactor_data.mek_status.status) and + (not unit.reactor_data.rps_tripped) and + (unit.a_group == 0) + if can_start then start.enable() else start.disable() end + end + end + + start.register(u_ps, "status", start_button_en_check) + start.register(u_ps, "rps_tripped", start_button_en_check) + -- start.register(u_ps, "auto_group_id", start_button_en_check) + -- start.register(u_ps, "AutoControl", start_button_en_check) + + reset.register(u_ps, "rps_tripped", function (active) if active then reset.enable() else reset.disable() end end) + --#endregion util.nop() From bf10b3241e169c86931a1d3e733d09ecf522a1f9 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 5 Sep 2024 22:19:24 -0400 Subject: [PATCH 24/40] #403 RPS FP indicator doc updates --- pocket/ui/docs.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pocket/ui/docs.lua b/pocket/ui/docs.lua index c047712..38a9472 100644 --- a/pocket/ui/docs.lua +++ b/pocket/ui/docs.lua @@ -198,12 +198,12 @@ doc("fp_rps_auto", "AUTOMATIC", "The RPS was tripped by the supervisor automatic doc("fp_rps_to", "TIMEOUT", "The RPS tripped due to losing the supervisor connection.") doc("fp_rps_pflt", "PLC FAULT", "The RPS tripped due to a peripheral error.") doc("fp_rps_rflt", "RCT FAULT", "The RPS tripped due to the reactor not being formed.") -doc("fp_rps_temp", "HI DAMAGE", "The RPS tripped due to being >= " .. const.RPS_LIMITS.MAX_DAMAGE_PERCENT .. "% damaged.") -doc("fp_rps_temp", "HI TEMP", "The RPS tripped due to high reactor temperature (>= " .. const.RPS_LIMITS.MAX_DAMAGE_TEMPERATURE .. "K).") +doc("fp_rps_temp", "HI DAMAGE", "The RPS tripped due to being >=" .. const.RPS_LIMITS.MAX_DAMAGE_PERCENT .. "% damaged.") +doc("fp_rps_temp", "HI TEMP", "The RPS tripped due to high reactor temperature (>=" .. const.RPS_LIMITS.MAX_DAMAGE_TEMPERATURE .. "K).") doc("fp_rps_fuel", "LO FUEL", "The RPS tripped due to having no fuel.") -doc("fp_rps_waste", "HI WASTE", "The RPS tripped due to having high levels of waste (> " .. const.RPS_LIMITS.MAX_WASTE_FILL .. "%).") -doc("fp_rps_ccool", "LO CCOOLANT", "The RPS tripped due to having low levels of cooled coolant (< " .. const.RPS_LIMITS.MIN_COOLANT_FILL .. "%).") -doc("fp_rps_ccool", "HI HCOOLANT", "The RPS tripped due to having high levels of heated coolant (> " .. const.RPS_LIMITS.MAX_HEATED_COLLANT_FILL .. "%).") +doc("fp_rps_waste", "HI WASTE", "The RPS tripped due to having high levels of waste (>" .. (const.RPS_LIMITS.MAX_WASTE_FILL * 100) .. "%).") +doc("fp_rps_ccool", "LO CCOOLANT", "The RPS tripped due to having low levels of cooled coolant (<" .. (const.RPS_LIMITS.MIN_COOLANT_FILL * 100) .. "%).") +doc("fp_rps_ccool", "HI HCOOLANT", "The RPS tripped due to having high levels of heated coolant (>" .. (const.RPS_LIMITS.MAX_HEATED_COLLANT_FILL * 100) .. "%).") target = docs.fp.rtu_gw sect("Co-Routine States") From 8ffbbb5ac9809927c53457bbe4837a84731bf726 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 6 Sep 2024 21:11:56 -0400 Subject: [PATCH 25/40] #498 supervisor block disallowed commands based on state, removed unused acks --- coordinator/coordinator.lua | 8 +------- supervisor/facility.lua | 12 ++++++++++-- supervisor/session/coordinator.lua | 24 ++++++++++++++++++------ supervisor/session/plc.lua | 7 ------- 4 files changed, 29 insertions(+), 22 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 6e06fed..baca753 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -632,16 +632,10 @@ function coordinator.comms(version, nic, sv_watchdog) process.unit_ack(unit_id, cmd, ack) elseif cmd == UNIT_COMMAND.RESET_RPS then process.unit_ack(unit_id, cmd, ack) - elseif cmd == UNIT_COMMAND.SET_BURN then - -- this also doesn't exist - elseif cmd == UNIT_COMMAND.SET_WASTE then - -- updated by unit updates elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then process.unit_ack(unit_id, cmd, ack) - elseif cmd == UNIT_COMMAND.SET_GROUP then - -- updated by unit updates else - log.debug(util.c("received unit command ack with unknown command ", cmd)) + log.debug(util.c("received unsupported unit command ack for command ", cmd)) end else log.debug(util.c("received unit command ack with unknown unit ", unit_id)) diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 88486bc..9948023 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -73,8 +73,8 @@ function facility.new(config) burn_target = 0.1, -- burn rate target for aggregate burn mode charge_setpoint = 0, -- FE charge target setpoint gen_rate_setpoint = 0, -- FE/t charge rate target setpoint - group_map = {}, -- units -> group IDs - prio_defs = { {}, {}, {}, {} }, -- priority definitions (each level is a table of units) + group_map = {}, ---@type integer[] units -> group IDs + prio_defs = { {}, {}, {}, {} }, ---@type reactor_unit[][] priority definitions (each level is a table of units) at_max_burn = false, ascram = false, ascram_reason = AUTO_SCRAM.NONE, @@ -375,6 +375,9 @@ function facility.new(config) end end + -- check automatic control mode + function public.auto_is_active() return self.mode ~= PROCESS.INACTIVE end + -- stop auto control function public.auto_stop() self.mode = PROCESS.INACTIVE end @@ -469,6 +472,11 @@ function facility.new(config) end end + -- get the automatic control group of a unit + ---@param unit_id integer unit ID + ---@nodiscard + function public.get_group(unit_id) return self.group_map[unit_id] end + -- set waste production ---@param product WASTE_PRODUCT target product ---@return WASTE_PRODUCT product newly set value, if valid diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 4d0dcd6..bd93c1c 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -241,8 +241,13 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim facility.scram_all() _send(CRDN_TYPE.FAC_CMD, { cmd, true }) elseif cmd == FAC_COMMAND.STOP then - facility.auto_stop() - _send(CRDN_TYPE.FAC_CMD, { cmd, true }) + local was_active = facility.auto_is_active() + + if was_active then + facility.auto_stop() + end + + _send(CRDN_TYPE.FAC_CMD, { cmd, was_active }) elseif cmd == FAC_COMMAND.START then if pkt.length == 6 then ---@type sys_auto_config @@ -299,17 +304,25 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim -- continue if valid unit id if util.is_int(uid) and uid > 0 and uid <= #self.units then - local unit = self.units[uid] ---@type reactor_unit + local unit = self.units[uid] ---@type reactor_unit + local manual = facility.get_group(uid) == 0 if cmd == UNIT_COMMAND.START then - out_queue.push_data(SV_Q_DATA.START, data) + if manual then + out_queue.push_data(SV_Q_DATA.START, data) + else + -- denied + _send(CRDN_TYPE.UNIT_CMD, { cmd, uid, false }) + end elseif cmd == UNIT_COMMAND.SCRAM then out_queue.push_data(SV_Q_DATA.SCRAM, data) elseif cmd == UNIT_COMMAND.RESET_RPS then out_queue.push_data(SV_Q_DATA.RESET_RPS, data) elseif cmd == UNIT_COMMAND.SET_BURN then if pkt.length == 3 then - out_queue.push_data(SV_Q_DATA.SET_BURN, data) + if manual then + out_queue.push_data(SV_Q_DATA.SET_BURN, data) + end else log.debug(log_tag .. "CRDN unit command burn rate missing option") end @@ -337,7 +350,6 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim elseif cmd == UNIT_COMMAND.SET_GROUP then if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] >= 0) and (pkt.data[3] <= 4) then facility.set_group(unit.get_id(), pkt.data[3]) - _send(CRDN_TYPE.UNIT_CMD, { cmd, uid, pkt.data[3] }) else log.debug(log_tag .. "CRDN unit command set group missing group id") end diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index f0394e0..8850a16 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -395,13 +395,6 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue, elseif ack == false then log.debug(log_tag .. "burn rate update failed!") end - - -- send acknowledgement to coordinator - out_queue.push_data(svqtypes.SV_Q_DATA.CRDN_ACK, { - unit = reactor_id, - cmd = UNIT_COMMAND.SET_BURN, - ack = ack - }) elseif pkt.type == RPLC_TYPE.RPS_ENABLE then -- enable acknowledgement local ack = _get_ack(pkt) From d6a9f9c5f3373c53524810d9a41079978eb7a6f8 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 6 Sep 2024 21:26:41 -0400 Subject: [PATCH 26/40] #498 clear requestors on ack --- coordinator/process.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/coordinator/process.lua b/coordinator/process.lua index d74ea97..abb1bc2 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -305,6 +305,8 @@ local function cmd_ack(cmd_state, success) for i = 1, #cmd_state.requestors do cmd_state.requestors[i](success) end + + cmd_state.requestors = {} end end From 5b311fcfbc0d98ecbdbca28fc430619b95812704 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 6 Sep 2024 23:31:01 -0400 Subject: [PATCH 27/40] #498 pocket facility scram and ack all alarms --- coordinator/session/pocket.lua | 86 ++++++++++++++++++---------------- pocket/iocontrol.lua | 5 ++ pocket/pocket.lua | 31 ++++++++---- pocket/ui/apps/control.lua | 20 +++++++- 4 files changed, 92 insertions(+), 50 deletions(-) diff --git a/coordinator/session/pocket.lua b/coordinator/session/pocket.lua index 097241f..5f5038f 100644 --- a/coordinator/session/pocket.lua +++ b/coordinator/session/pocket.lua @@ -11,6 +11,7 @@ local pocket = {} local PROTOCOL = comms.PROTOCOL local CRDN_TYPE = comms.CRDN_TYPE local MGMT_TYPE = comms.MGMT_TYPE +local FAC_COMMAND = comms.FAC_COMMAND local UNIT_COMMAND = comms.UNIT_COMMAND -- retry time constants in ms @@ -39,7 +40,7 @@ local PERIODICS = { ---@param out_queue mqueue out message queue ---@param timeout number communications timeout function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) - local log_header = "pkt_session(" .. id .. "): " + local log_tag = "pkt_session(" .. id .. "): " local self = { -- connection properties @@ -106,6 +107,10 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) end -- link callback transmissions + + self.proc_handle.fac_ack.on_scram = function (success) _send(CRDN_TYPE.FAC_CMD, { FAC_COMMAND.SCRAM, success }) end + self.proc_handle.fac_ack.on_ack_alarms = function (success) _send(CRDN_TYPE.FAC_CMD, { FAC_COMMAND.ACK_ALL_ALARMS, success }) end + for u = 1, iocontrol.get_db().facility.num_units do self.proc_handle.unit_ack[u].on_start = function (success) _send(CRDN_TYPE.UNIT_CMD, { UNIT_COMMAND.START, u, success }) end self.proc_handle.unit_ack[u].on_scram = function (success) _send(CRDN_TYPE.UNIT_CMD, { UNIT_COMMAND.SCRAM, u, success }) end @@ -118,7 +123,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) local function _handle_packet(pkt) -- check sequence number if self.r_seq_num ~= pkt.scada_frame.seq_num() then - log.warning(log_header .. "sequence out-of-order: next = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) + log.warning(log_tag .. "sequence out-of-order: next = " .. self.r_seq_num .. ", new = " .. pkt.scada_frame.seq_num()) return else self.r_seq_num = pkt.scada_frame.seq_num() + 1 @@ -134,7 +139,28 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) local db = iocontrol.get_db() -- handle packet by type - if pkt.type == CRDN_TYPE.UNIT_CMD then + if pkt.type == CRDN_TYPE.FAC_CMD then + if pkt.length >= 1 then + local cmd = pkt.data[1] + + if cmd == FAC_COMMAND.SCRAM_ALL then + log.info(log_tag .. "FAC SCRAM ALL") + self.proc_handle.fac_scram() + elseif cmd == FAC_COMMAND.STOP then + elseif cmd == FAC_COMMAND.START then + elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then + log.info(log_tag .. "FAC ACK ALL ALARMS") + self.proc_handle.fac_ack_alarms() + elseif cmd == FAC_COMMAND.SET_WASTE_MODE then + elseif cmd == FAC_COMMAND.SET_PU_FB then + elseif cmd == FAC_COMMAND.SET_SPS_LP then + else + log.debug(log_tag .. "CRDN facility command unknown") + end + else + log.debug(log_tag .. "CRDN facility command packet length mismatch") + end + elseif pkt.type == CRDN_TYPE.UNIT_CMD then if pkt.length >= 2 then -- get command and unit id local cmd = pkt.data[1] @@ -143,56 +169,36 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) -- continue if valid unit id if util.is_int(uid) and uid > 0 and uid <= #db.units then if cmd == UNIT_COMMAND.START then - log.info(util.c(log_header, "UNIT[", uid, "] START")) + log.info(util.c(log_tag, "UNIT[", uid, "] START")) self.proc_handle.start(uid) elseif cmd == UNIT_COMMAND.SCRAM then - log.info(util.c(log_header, "UNIT[", uid, "] SCRAM")) + log.info(util.c(log_tag, "UNIT[", uid, "] SCRAM")) self.proc_handle.scram(uid) elseif cmd == UNIT_COMMAND.RESET_RPS then - log.info(util.c(log_header, "UNIT[", uid, "] RESET RPS")) + log.info(util.c(log_tag, "UNIT[", uid, "] RESET RPS")) self.proc_handle.reset_rps(uid) elseif cmd == UNIT_COMMAND.SET_BURN then if pkt.length == 3 then - log.info(util.c(log_header, "UNIT[", uid, "] SET BURN ", pkt.data[3])) + log.info(util.c(log_tag, "UNIT[", uid, "] SET BURN ", pkt.data[3])) process.set_rate(uid, pkt.data[3]) else - log.debug(log_header .. "CRDN unit command burn rate missing option") + log.debug(log_tag .. "CRDN unit command burn rate missing option") end elseif cmd == UNIT_COMMAND.SET_WASTE then - -- if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] > 0) and (pkt.data[3] <= 4) then - -- process.set_unit_waste(uid, pkt.data[3]) - -- else - -- log.debug(log_header .. "CRDN unit command set waste missing/invalid option") - -- end elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then - log.info(util.c(log_header, "UNIT[", uid, "] ACK ALL ALARMS")) + log.info(util.c(log_tag, "UNIT[", uid, "] ACK ALL ALARMS")) self.proc_handle.ack_all_alarms(uid) elseif cmd == UNIT_COMMAND.ACK_ALARM then - -- if pkt.length == 3 then - -- process.ack_alarm(uid, pkt.data[3]) - -- else - -- log.debug(log_header .. "CRDN unit command ack alarm missing alarm id") - -- end elseif cmd == UNIT_COMMAND.RESET_ALARM then - -- if pkt.length == 3 then - -- process.reset_alarm(uid, pkt.data[3]) - -- else - -- log.debug(log_header .. "CRDN unit command reset alarm missing alarm id") - -- end elseif cmd == UNIT_COMMAND.SET_GROUP then - -- if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] >= 0) and (pkt.data[3] <= 4) then - -- process.set_group(uid, pkt.data[3]) - -- else - -- log.debug(log_header .. "CRDN unit command set group missing group id") - -- end else - log.debug(log_header .. "CRDN unit command unknown") + log.debug(log_tag .. "CRDN unit command unknown") end else - log.debug(log_header .. "CRDN unit command invalid") + log.debug(log_tag .. "CRDN unit command invalid") end else - log.debug(log_header .. "CRDN unit command packet length mismatch") + log.debug(log_tag .. "CRDN unit command packet length mismatch") end elseif pkt.type == CRDN_TYPE.API_GET_FAC then local fac = db.facility @@ -232,7 +238,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) end end else - log.debug(log_header .. "handler received unsupported CRDN packet type " .. pkt.type) + log.debug(log_tag .. "handler received unsupported CRDN packet type " .. pkt.type) end elseif pkt.scada_frame.protocol() == PROTOCOL.SCADA_MGMT then ---@cast pkt mgmt_frame @@ -245,7 +251,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) self.last_rtt = srv_now - srv_start if self.last_rtt > 750 then - log.warning(log_header .. "PKT KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)") + log.warning(log_tag .. "PKT KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)") end -- log.debug(log_header .. "PKT RTT = " .. self.last_rtt .. "ms") @@ -253,7 +259,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) iocontrol.fp_pkt_rtt(id, self.last_rtt) else - log.debug(log_header .. "SCADA keep alive packet length mismatch") + log.debug(log_tag .. "SCADA keep alive packet length mismatch") end elseif pkt.type == MGMT_TYPE.CLOSE then -- close the session @@ -261,9 +267,9 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) elseif pkt.type == MGMT_TYPE.ESTABLISH then -- something is wrong, kill the session _close() - log.warning(log_header .. "terminated session due to an unexpected ESTABLISH packet") + log.warning(log_tag .. "terminated session due to an unexpected ESTABLISH packet") else - log.debug(log_header .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) + log.debug(log_tag .. "handler received unsupported SCADA_MGMT packet type " .. pkt.type) end end end @@ -288,7 +294,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) function public.close() _close() _send_mgmt(MGMT_TYPE.CLOSE, {}) - log.info(log_header .. "session closed by server") + log.info(log_tag .. "session closed by server") end -- iterate the session @@ -319,14 +325,14 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) -- max 100ms spent processing queue if util.time() - handle_start > 100 then - log.warning(log_header .. "exceeded 100ms queue process limit") + log.warning(log_tag .. "exceeded 100ms queue process limit") break end end -- exit if connection was closed if not self.connected then - log.info(log_header .. "session closed by remote host") + log.info(log_tag .. "session closed by remote host") return self.connected end diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 158c0ec..59323c7 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -167,6 +167,11 @@ function iocontrol.init_fac(conf) radiation = types.new_zero_radiation_reading(), + start_ack = __generic_ack, + stop_ack = __generic_ack, + scram_ack = __generic_ack, + ack_alarms_ack = __generic_ack, + ps = psil.create(), induction_ps_tbl = {}, diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 907b162..396f23d 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -654,7 +654,28 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) if protocol == PROTOCOL.SCADA_CRDN then ---@cast packet crdn_frame if self.api.linked then - if packet.type == CRDN_TYPE.UNIT_CMD then + if packet.type == CRDN_TYPE.FAC_CMD then + -- facility command acknowledgement + if packet.length >= 2 then + local cmd = packet.data[1] + local ack = packet.data[2] == true + + if cmd == FAC_COMMAND.SCRAM_ALL then + iocontrol.get_db().facility.scram_ack(ack) + elseif cmd == FAC_COMMAND.STOP then + elseif cmd == FAC_COMMAND.START then + elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then + iocontrol.get_db().facility.ack_alarms_ack(ack) + elseif cmd == FAC_COMMAND.SET_WASTE_MODE then + elseif cmd == FAC_COMMAND.SET_PU_FB then + elseif cmd == FAC_COMMAND.SET_SPS_LP then + else + log.debug(util.c("received facility command ack with unknown command ", cmd)) + end + else + log.debug("SCADA_CRDN facility command ack packet length mismatch") + end + elseif packet.type == CRDN_TYPE.UNIT_CMD then -- unit command acknowledgement if packet.length == 3 then local cmd = packet.data[1] @@ -670,16 +691,10 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) unit.start_ack(ack) elseif cmd == UNIT_COMMAND.RESET_RPS then unit.reset_rps_ack(ack) - elseif cmd == UNIT_COMMAND.SET_BURN then - -- this also doesn't exist - elseif cmd == UNIT_COMMAND.SET_WASTE then - -- updated by unit updates elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then unit.ack_alarms_ack(ack) - elseif cmd == UNIT_COMMAND.SET_GROUP then - -- updated by unit updates else - log.debug(util.c("received unit command ack with unknown command ", cmd)) + log.debug(util.c("received unsupported unit command ack for command ", cmd)) end end end diff --git a/pocket/ui/apps/control.lua b/pocket/ui/apps/control.lua index c76b95a..2247a31 100644 --- a/pocket/ui/apps/control.lua +++ b/pocket/ui/apps/control.lua @@ -6,13 +6,13 @@ local util = require("scada-common.util") local iocontrol = require("pocket.iocontrol") local pocket = require("pocket.pocket") +local process = require("pocket.process") local style = require("pocket.ui.style") 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") @@ -77,6 +77,7 @@ local function new_view(root) local function set_sidebar() local list = { { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = function () db.nav.open_app(APP_ID.ROOT) end }, + { label = "FAC", color = core.cpair(colors.black, colors.orange), callback = function () app.switcher(db.facility.num_units + 1) end } } for i = 1, db.facility.num_units do @@ -95,7 +96,7 @@ local function new_view(root) local active_unit = 1 -- create all page divs - for _ = 1, db.facility.num_units do + for _ = 1, db.facility.num_units + 1 do local div = Div{parent=page_div} table.insert(panes, div) end @@ -188,6 +189,21 @@ local function new_view(root) util.nop() end + -- facility controls + + local f_pane = panes[db.facility.num_units + 1] + local f_div = Div{parent=f_pane,x=2,width=main.get_width()-2} + + app.new_page(nil, db.facility.num_units + 1) + + TextBox{parent=f_div,y=1,text="Facility Commands",alignment=ALIGN.CENTER} + + local scram = HazardButton{parent=f_div,x=5,y=6,text="FAC SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=process.fac_scram,fg_bg=hzd_fg_bg} + local ack_a = HazardButton{parent=f_div,x=7,y=11,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=process.fac_ack_alarms,fg_bg=hzd_fg_bg} + + db.facility.scram_ack = scram.on_response + db.facility.ack_alarms_ack = ack_a.on_response + -- setup multipane local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes} app.set_root_pane(u_pane) From 13bb6cb02668b653e5a4ffce63ab37b5c27a7d7c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 7 Sep 2024 01:11:13 -0400 Subject: [PATCH 28/40] #498 fixed wrong facility SCRAM ack --- coordinator/session/pocket.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coordinator/session/pocket.lua b/coordinator/session/pocket.lua index 5f5038f..84f3678 100644 --- a/coordinator/session/pocket.lua +++ b/coordinator/session/pocket.lua @@ -108,7 +108,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) -- link callback transmissions - self.proc_handle.fac_ack.on_scram = function (success) _send(CRDN_TYPE.FAC_CMD, { FAC_COMMAND.SCRAM, success }) end + self.proc_handle.fac_ack.on_scram = function (success) _send(CRDN_TYPE.FAC_CMD, { FAC_COMMAND.SCRAM_ALL, success }) end self.proc_handle.fac_ack.on_ack_alarms = function (success) _send(CRDN_TYPE.FAC_CMD, { FAC_COMMAND.ACK_ALL_ALARMS, success }) end for u = 1, iocontrol.get_db().facility.num_units do From 6ff096fd3142994ac1bf7e0bf542e3836a7ae8fe Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 7 Sep 2024 21:39:16 -0400 Subject: [PATCH 29/40] #498 auto control mode based UI disabling and increased timeouts --- coordinator/session/pocket.lua | 1 + graphics/elements/controls/hazard_button.lua | 7 +++++-- pocket/iocontrol.lua | 22 +++++++++++++------- pocket/pocket.lua | 4 ++-- pocket/ui/apps/control.lua | 22 +++++++++++++------- 5 files changed, 37 insertions(+), 19 deletions(-) diff --git a/coordinator/session/pocket.lua b/coordinator/session/pocket.lua index 84f3678..ae8f767 100644 --- a/coordinator/session/pocket.lua +++ b/coordinator/session/pocket.lua @@ -224,6 +224,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) u.unit_id, u.connected, u.rtu_hw, + u.a_group, u.alarms, u.annunciator, u.reactor_data, diff --git a/graphics/elements/controls/hazard_button.lua b/graphics/elements/controls/hazard_button.lua index 7c2306f..e43f438 100644 --- a/graphics/elements/controls/hazard_button.lua +++ b/graphics/elements/controls/hazard_button.lua @@ -10,6 +10,7 @@ local element = require("graphics.element") ---@field accent color accent color for hazard border ---@field dis_colors? cpair text color and border color when disabled ---@field callback function function to call on touch +---@field timeout? integer override for the default 1.5 second timeout, in seconds ---@field parent graphics_element ---@field id? string element id ---@field x? integer 1 if omitted @@ -28,6 +29,8 @@ local function hazard_button(args) args.height = 3 args.width = string.len(args.text) + 4 + local timeout = args.timeout or 1.5 + -- create new graphics element base object local e = element.new(args) @@ -149,8 +152,8 @@ local function hazard_button(args) tcd.abort(on_success) tcd.abort(on_failure) - -- 1.5 second timeout - tcd.dispatch(1.5, on_timeout) + -- operation timeout handling + tcd.dispatch(timeout, on_timeout) args.callback() end diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 59323c7..61f761d 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -22,6 +22,8 @@ local TEMP_UNITS = types.TEMP_SCALE_UNITS local WARN_TT = 40 local HIGH_TT = 80 +local GROUP_NAMES = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" } + local iocontrol = {} ---@enum POCKET_LINK_STATE @@ -493,11 +495,15 @@ function iocontrol.record_unit_data(data) unit.connected = data[2] unit.rtu_hw = data[3] - unit.alarms = data[4] + unit.a_group = data[4] + unit.alarms = data[5] + + unit.unit_ps.publish("auto_group_id", unit.a_group) + unit.unit_ps.publish("auto_group", GROUP_NAMES[unit.a_group + 1]) --#region Annunciator - unit.annunciator = data[5] + unit.annunciator = data[6] local rcs_disconn, rcs_warn, rcs_hazard = false, false, false @@ -575,7 +581,7 @@ function iocontrol.record_unit_data(data) --#region Reactor Data - unit.reactor_data = data[6] + unit.reactor_data = data[7] local control_status = 1 local reactor_status = 1 @@ -647,7 +653,7 @@ function iocontrol.record_unit_data(data) --#region RTU Devices - unit.boiler_data_tbl = data[7] + unit.boiler_data_tbl = data[8] for id = 1, #unit.boiler_data_tbl do local boiler = unit.boiler_data_tbl[id] ---@type boilerv_session_db @@ -680,7 +686,7 @@ function iocontrol.record_unit_data(data) ps.publish("BoilerStateStatus", computed_status) end - unit.turbine_data_tbl = data[8] + unit.turbine_data_tbl = data[9] for id = 1, #unit.turbine_data_tbl do local turbine = unit.turbine_data_tbl[id] ---@type turbinev_session_db @@ -715,10 +721,10 @@ function iocontrol.record_unit_data(data) ps.publish("TurbineStateStatus", computed_status) end - unit.tank_data_tbl = data[9] + unit.tank_data_tbl = data[10] - unit.last_rate_change_ms = data[10] - unit.turbine_flow_stable = data[11] + unit.last_rate_change_ms = data[11] + unit.turbine_flow_stable = data[12] --#endregion diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 396f23d..f8293a2 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -609,7 +609,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) 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, length, packet.scada_frame.length())) + log.debug(util.sprintf(fmt, packet.scada_frame.remote_channel(), packet.scada_frame.protocol(), packet.type, length, packet.length)) end return ok end @@ -703,7 +703,7 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) iocontrol.record_facility_data(packet.data) end elseif packet.type == CRDN_TYPE.API_GET_UNIT then - if _check_length(packet, 11) and type(packet.data[1]) == "number" and iocontrol.get_db().units[packet.data[1]] then + if _check_length(packet, 12) 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/apps/control.lua b/pocket/ui/apps/control.lua index 2247a31..aa05ca9 100644 --- a/pocket/ui/apps/control.lua +++ b/pocket/ui/apps/control.lua @@ -150,18 +150,26 @@ local function new_view(root) TextBox{parent=u_div,y=8,text="CMD",width=4,fg_bg=cpair(colors.lightGray,colors.black)} TextBox{parent=u_div,x=14,y=8,text="mB/t",width=4,fg_bg=cpair(colors.lightGray,colors.black)} - local burn_cmd = NumberField{parent=u_div,x=5,y=8,width=8,default=0.01,min=0.01,max_frac_digits=2,max_chars=8,allow_decimal=true,align_right=true,fg_bg=cpair(colors.white,colors.gray)} + local burn_cmd = NumberField{parent=u_div,x=5,y=8,width=8,default=0.01,min=0.01,max_frac_digits=2,max_chars=8,allow_decimal=true,align_right=true,fg_bg=cpair(colors.white,colors.gray),dis_fg_bg=cpair(colors.gray,colors.lightGray)} local set_burn = function () unit.set_burn(burn_cmd.get_value()) end local set_burn_btn = PushButton{parent=u_div,x=19,y=8,text="SET",min_width=5,fg_bg=cpair(colors.green,colors.black),active_fg_bg=cpair(colors.white,colors.black),dis_fg_bg=cpair(colors.gray,colors.black),callback=set_burn} + -- enable/disable controls based on group assignment (start button is separate) + burn_cmd.register(u_ps, "auto_group_id", function (gid) + if gid == 0 then burn_cmd.enable() else burn_cmd.disable() end + end) + set_burn_btn.register(u_ps, "auto_group_id", function (gid) + if gid == 0 then set_burn_btn.enable() else set_burn_btn.disable() end + end) + burn_cmd.register(u_ps, "burn_rate", burn_cmd.set_value) burn_cmd.register(u_ps, "max_burn", burn_cmd.set_max) - local start = HazardButton{parent=u_div,x=2,y=11,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=hzd_fg_bg} - local ack_a = HazardButton{parent=u_div,x=12,y=11,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=unit.ack_alarms,fg_bg=hzd_fg_bg} - local scram = HazardButton{parent=u_div,x=2,y=15,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=hzd_fg_bg} - local reset = HazardButton{parent=u_div,x=12,y=15,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,fg_bg=hzd_fg_bg} + local start = HazardButton{parent=u_div,x=2,y=11,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,timeout=3,fg_bg=hzd_fg_bg} + local ack_a = HazardButton{parent=u_div,x=12,y=11,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=unit.ack_alarms,timeout=3,fg_bg=hzd_fg_bg} + local scram = HazardButton{parent=u_div,x=2,y=15,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,timeout=3,fg_bg=hzd_fg_bg} + local reset = HazardButton{parent=u_div,x=12,y=15,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,timeout=3,fg_bg=hzd_fg_bg} unit.start_ack = start.on_response unit.ack_alarms_ack = ack_a.on_response @@ -179,8 +187,8 @@ local function new_view(root) start.register(u_ps, "status", start_button_en_check) start.register(u_ps, "rps_tripped", start_button_en_check) - -- start.register(u_ps, "auto_group_id", start_button_en_check) - -- start.register(u_ps, "AutoControl", start_button_en_check) + start.register(u_ps, "auto_group_id", start_button_en_check) + start.register(u_ps, "AutoControl", start_button_en_check) reset.register(u_ps, "rps_tripped", function (active) if active then reset.enable() else reset.disable() end end) From a7b3a2a0b86b6914d2768da3b1ed2db6766994a2 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 7 Sep 2024 23:08:49 -0400 Subject: [PATCH 30/40] removed unused variables --- pocket/ui/apps/control.lua | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pocket/ui/apps/control.lua b/pocket/ui/apps/control.lua index aa05ca9..99d363a 100644 --- a/pocket/ui/apps/control.lua +++ b/pocket/ui/apps/control.lua @@ -31,24 +31,13 @@ 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 -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 hzd_fg_bg = cpair(colors.white, colors.gray) local dis_colors = cpair(colors.white, colors.lightGray) -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 = "+" } -} - -- new unit control page view ---@param root graphics_element parent local function new_view(root) From 2e978db85932baefb45ed92b80b47a69efef91fc Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 8 Sep 2024 13:23:37 -0400 Subject: [PATCH 31/40] cleanup and version increments --- coordinator/session/pocket.lua | 8 ++++---- coordinator/startup.lua | 2 +- graphics/core.lua | 2 +- graphics/elements/controls/hazard_button.lua | 2 +- supervisor/session/coordinator.lua | 6 +++--- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/coordinator/session/pocket.lua b/coordinator/session/pocket.lua index ae8f767..0205397 100644 --- a/coordinator/session/pocket.lua +++ b/coordinator/session/pocket.lua @@ -168,12 +168,12 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) -- continue if valid unit id if util.is_int(uid) and uid > 0 and uid <= #db.units then - if cmd == UNIT_COMMAND.START then - log.info(util.c(log_tag, "UNIT[", uid, "] START")) - self.proc_handle.start(uid) - elseif cmd == UNIT_COMMAND.SCRAM then + if cmd == UNIT_COMMAND.SCRAM then log.info(util.c(log_tag, "UNIT[", uid, "] SCRAM")) self.proc_handle.scram(uid) + elseif cmd == UNIT_COMMAND.START then + log.info(util.c(log_tag, "UNIT[", uid, "] START")) + self.proc_handle.start(uid) elseif cmd == UNIT_COMMAND.RESET_RPS then log.info(util.c(log_tag, "UNIT[", uid, "] RESET RPS")) self.proc_handle.reset_rps(uid) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 4be0949..52ed4a6 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.7" +local COORDINATOR_VERSION = "v1.5.8" local CHUNK_LOAD_DELAY_S = 30.0 diff --git a/graphics/core.lua b/graphics/core.lua index c07b281..fe3b291 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -7,7 +7,7 @@ local flasher = require("graphics.flasher") local core = {} -core.version = "2.3.3" +core.version = "2.3.4" core.flasher = flasher core.events = events diff --git a/graphics/elements/controls/hazard_button.lua b/graphics/elements/controls/hazard_button.lua index e43f438..5a3d37f 100644 --- a/graphics/elements/controls/hazard_button.lua +++ b/graphics/elements/controls/hazard_button.lua @@ -152,7 +152,7 @@ local function hazard_button(args) tcd.abort(on_success) tcd.abort(on_failure) - -- operation timeout handling + -- operation timeout animation tcd.dispatch(timeout, on_timeout) args.callback() diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index bd93c1c..a2044fc 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -307,15 +307,15 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim local unit = self.units[uid] ---@type reactor_unit local manual = facility.get_group(uid) == 0 - if cmd == UNIT_COMMAND.START then + if cmd == UNIT_COMMAND.SCRAM then + out_queue.push_data(SV_Q_DATA.SCRAM, data) + elseif cmd == UNIT_COMMAND.START then if manual then out_queue.push_data(SV_Q_DATA.START, data) else -- denied _send(CRDN_TYPE.UNIT_CMD, { cmd, uid, false }) end - elseif cmd == UNIT_COMMAND.SCRAM then - out_queue.push_data(SV_Q_DATA.SCRAM, data) elseif cmd == UNIT_COMMAND.RESET_RPS then out_queue.push_data(SV_Q_DATA.RESET_RPS, data) elseif cmd == UNIT_COMMAND.SET_BURN then From 402d8607b6262ab2dbafa3c99b0dc80e3271de8d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 8 Sep 2024 13:26:43 -0400 Subject: [PATCH 32/40] added AUTO_GROUP enum --- coordinator/iocontrol.lua | 5 ++--- coordinator/ui/components/unit_detail.lua | 12 ++++++------ pocket/iocontrol.lua | 15 ++++++--------- pocket/ui/apps/control.lua | 9 ++++++--- scada-common/types.lua | 17 +++++++++++++++++ supervisor/facility.lua | 13 +++++++------ 6 files changed, 44 insertions(+), 27 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 956cf41..37230b3 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -182,7 +182,7 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale) turbine_flow_stable = false, -- auto control group - a_group = 0, + a_group = types.AUTO_GROUP.MANUAL, start = function () io.process.start(i) end, scram = function () io.process.scram(i) end, @@ -569,11 +569,10 @@ function iocontrol.update_facility_status(status) local group_map = ctl_status[14] if (type(group_map) == "table") and (#group_map == fac.num_units) then - local names = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" } for i = 1, #group_map do io.units[i].a_group = group_map[i] io.units[i].unit_ps.publish("auto_group_id", group_map[i]) - io.units[i].unit_ps.publish("auto_group", names[group_map[i] + 1]) + io.units[i].unit_ps.publish("auto_group", types.AUTO_GROUP_NAMES[group_map[i] + 1]) end end diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index d898d34..fcfd062 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -29,6 +29,8 @@ 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 AUTO_GROUP = types.AUTO_GROUP + local ALIGN = core.ALIGN local cpair = core.cpair @@ -382,7 +384,7 @@ local function init(parent, id) if (unit.reactor_data ~= nil) and (unit.reactor_data.mek_status ~= nil) then local can_start = (not unit.reactor_data.mek_status.status) and (not unit.reactor_data.rps_tripped) and - (unit.a_group == 0) + (unit.a_group == AUTO_GROUP.MANUAL) if can_start then start.enable() else start.disable() end end end @@ -486,9 +488,7 @@ local function init(parent, id) local auto_ctl = Rectangle{parent=main,border=border(1,colors.purple,true),thin=true,width=13,height=15,x=32,y=37} local auto_div = Div{parent=auto_ctl,width=13,height=15,x=1,y=1} - local ctl_opts = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" } - - local group = RadioButton{parent=auto_div,options=ctl_opts,callback=function()end,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.purple} + local group = RadioButton{parent=auto_div,options=types.AUTO_GROUP_NAMES,callback=function()end,radio_colors=cpair(style.theme.accent_dark,style.theme.accent_light),select_color=colors.purple} group.register(u_ps, "auto_group_id", function (gid) group.set_value(gid + 1) end) @@ -523,10 +523,10 @@ local function init(parent, id) -- enable/disable controls based on group assignment (start button is separate) burn_rate.register(u_ps, "auto_group_id", function (gid) - if gid == 0 then burn_rate.enable() else burn_rate.disable() end + if gid == AUTO_GROUP.MANUAL then burn_rate.enable() else burn_rate.disable() end end) set_burn_btn.register(u_ps, "auto_group_id", function (gid) - if gid == 0 then set_burn_btn.enable() else set_burn_btn.disable() end + if gid == AUTO_GROUP.MANUAL then set_burn_btn.enable() else set_burn_btn.disable() end end) -- can't change group if auto is engaged regardless of if this unit is part of auto control diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 61f761d..d71911f 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -2,11 +2,10 @@ -- 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") -local util = require("scada-common.util") +local const = require("scada-common.constants") +local psil = require("scada-common.psil") +local types = require("scada-common.types") +local util = require("scada-common.util") local process = require("pocket.process") @@ -22,8 +21,6 @@ local TEMP_UNITS = types.TEMP_SCALE_UNITS local WARN_TT = 40 local HIGH_TT = 80 -local GROUP_NAMES = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" } - local iocontrol = {} ---@enum POCKET_LINK_STATE @@ -318,7 +315,7 @@ function iocontrol.init_fac(conf) turbine_flow_stable = false, -- auto control group - a_group = 0, + a_group = types.AUTO_GROUP.MANUAL, start = function () process.start(i) end, scram = function () process.scram(i) end, @@ -499,7 +496,7 @@ function iocontrol.record_unit_data(data) unit.alarms = data[5] unit.unit_ps.publish("auto_group_id", unit.a_group) - unit.unit_ps.publish("auto_group", GROUP_NAMES[unit.a_group + 1]) + unit.unit_ps.publish("auto_group", types.AUTO_GROUP_NAMES[unit.a_group + 1]) --#region Annunciator diff --git a/pocket/ui/apps/control.lua b/pocket/ui/apps/control.lua index 99d363a..6cd1a76 100644 --- a/pocket/ui/apps/control.lua +++ b/pocket/ui/apps/control.lua @@ -2,6 +2,7 @@ -- Unit Control Page -- +local types = require("scada-common.types") local util = require("scada-common.util") local iocontrol = require("pocket.iocontrol") @@ -26,6 +27,8 @@ local NumberField = require("graphics.elements.form.number_field") local DataIndicator = require("graphics.elements.indicators.data") local IconIndicator = require("graphics.elements.indicators.icon") +local AUTO_GROUP = types.AUTO_GROUP + local ALIGN = core.ALIGN local cpair = core.cpair @@ -146,10 +149,10 @@ local function new_view(root) -- enable/disable controls based on group assignment (start button is separate) burn_cmd.register(u_ps, "auto_group_id", function (gid) - if gid == 0 then burn_cmd.enable() else burn_cmd.disable() end + if gid == AUTO_GROUP.MANUAL then burn_cmd.enable() else burn_cmd.disable() end end) set_burn_btn.register(u_ps, "auto_group_id", function (gid) - if gid == 0 then set_burn_btn.enable() else set_burn_btn.disable() end + if gid == AUTO_GROUP.MANUAL then set_burn_btn.enable() else set_burn_btn.disable() end end) burn_cmd.register(u_ps, "burn_rate", burn_cmd.set_value) @@ -169,7 +172,7 @@ local function new_view(root) if (unit.reactor_data ~= nil) and (unit.reactor_data.mek_status ~= nil) then local can_start = (not unit.reactor_data.mek_status.status) and (not unit.reactor_data.rps_tripped) and - (unit.a_group == 0) + (unit.a_group == AUTO_GROUP.MANUAL) if can_start then start.enable() else start.disable() end end end diff --git a/scada-common/types.lua b/scada-common/types.lua index c27d2d7..2bf6ae1 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -209,6 +209,23 @@ types.PROCESS_NAMES = { "GEN_RATE_FAULT_IDLE" } +---@enum AUTO_GROUP +types.AUTO_GROUP = { + MANUAL = 0, + PRIMARY = 1, + SECONDARY = 2, + TERTIARY = 3, + BACKUP = 4 +} + +types.AUTO_GROUP_NAMES = { + "Manual", + "Primary", + "Secondary", + "Tertiary", + "Backup" +} + ---@enum WASTE_MODE types.WASTE_MODE = { AUTO = 1, diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 9948023..7b786a2 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -8,6 +8,7 @@ local fac_update = require("supervisor.facility_update") local rsctl = require("supervisor.session.rsctl") local svsessions = require("supervisor.session.svsessions") +local AUTO_GROUP = types.AUTO_GROUP local PROCESS = types.PROCESS local RTU_ID_FAIL = types.RTU_ID_FAIL local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE @@ -73,7 +74,7 @@ function facility.new(config) burn_target = 0.1, -- burn rate target for aggregate burn mode charge_setpoint = 0, -- FE charge target setpoint gen_rate_setpoint = 0, -- FE/t charge rate target setpoint - group_map = {}, ---@type integer[] units -> group IDs + group_map = {}, ---@type AUTO_GROUP[] units -> group IDs prio_defs = { {}, {}, {}, {} }, ---@type reactor_unit[][] priority definitions (each level is a table of units) at_max_burn = false, ascram = false, @@ -130,7 +131,7 @@ function facility.new(config) for i = 1, config.UnitCount do table.insert(self.units, unit.new(i, self.cooling_conf.r_cool[i].BoilerCount, self.cooling_conf.r_cool[i].TurbineCount, config.ExtChargeIdling)) - table.insert(self.group_map, 0) + table.insert(self.group_map, AUTO_GROUP.MANUAL) end -- list for RTU session management @@ -454,19 +455,19 @@ function facility.new(config) -- set the automatic control group of a unit ---@param unit_id integer unit ID - ---@param group integer group ID or 0 for independent + ---@param group AUTO_GROUP group ID or 0 for independent function public.set_group(unit_id, group) - if (group >= 0 and group <= 4) and (unit_id > 0 and unit_id <= config.UnitCount) and self.mode == PROCESS.INACTIVE then + if (group >= AUTO_GROUP.MANUAL and group <= AUTO_GROUP.BACKUP) and (unit_id > 0 and unit_id <= config.UnitCount) and self.mode == PROCESS.INACTIVE then -- remove from old group if previously assigned local old_group = self.group_map[unit_id] - if old_group ~= 0 then + if old_group ~= AUTO_GROUP.MANUAL then util.filter_table(self.prio_defs[old_group], function (u) return u.get_id() ~= unit_id end) end self.group_map[unit_id] = group -- add to group if not independent - if group > 0 then + if group > AUTO_GROUP.MANUAL then table.insert(self.prio_defs[group], self.units[unit_id]) end end From 2933b24318922be800a20bbfcc5e897ee6c377b8 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 8 Sep 2024 16:05:20 -0400 Subject: [PATCH 33/40] cleanup --- coordinator/configure.lua | 3 +- pocket/configure.lua | 3 +- pocket/pocket.lua | 8 - pocket/process.lua | 225 +---------------------------- pocket/startup.lua | 2 +- pocket/ui/apps/control.lua | 8 +- pocket/ui/apps/guide.lua | 6 +- pocket/ui/docs.lua | 2 +- pocket/ui/pages/guide_section.lua | 18 +-- reactor-plc/config/system.lua | 3 +- reactor-plc/startup.lua | 2 +- rtu/configure.lua | 2 +- rtu/startup.lua | 2 +- supervisor/configure.lua | 3 +- supervisor/session/coordinator.lua | 12 +- supervisor/startup.lua | 2 +- 16 files changed, 35 insertions(+), 266 deletions(-) diff --git a/coordinator/configure.lua b/coordinator/configure.lua index 100b079..31d359c 100644 --- a/coordinator/configure.lua +++ b/coordinator/configure.lua @@ -38,7 +38,6 @@ local MGMT_TYPE = comms.MGMT_TYPE local cpair = core.cpair -local LEFT = core.ALIGN.LEFT local CENTER = core.ALIGN.CENTER local RIGHT = core.ALIGN.RIGHT @@ -1401,7 +1400,7 @@ local function config_view(display) local textbox if height > 1 then - textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1,alignment=LEFT} + textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1} else textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT} end diff --git a/pocket/configure.lua b/pocket/configure.lua index 783cf9a..0e91caa 100644 --- a/pocket/configure.lua +++ b/pocket/configure.lua @@ -27,7 +27,6 @@ local tri = util.trinary local cpair = core.cpair -local LEFT = core.ALIGN.LEFT local CENTER = core.ALIGN.CENTER local RIGHT = core.ALIGN.RIGHT @@ -536,7 +535,7 @@ local function config_view(display) local textbox if height > 1 then - textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1,alignment=LEFT} + textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1} else textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT} end diff --git a/pocket/pocket.lua b/pocket/pocket.lua index f8293a2..80a2cb5 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -553,14 +553,6 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) _send_api(CRDN_TYPE.FAC_CMD, { cmd, option }) end - -- send the auto process control configuration with a start command - ---@param auto_cfg sys_auto_config configuration - function public.send_auto_start(auto_cfg) - _send_api(CRDN_TYPE.FAC_CMD, { - FAC_COMMAND.START, auto_cfg.mode, auto_cfg.burn_target, auto_cfg.charge_target, auto_cfg.gen_target, auto_cfg.limits - }) - end - -- send a unit command ---@param cmd UNIT_COMMAND command ---@param unit integer unit ID diff --git a/pocket/process.lua b/pocket/process.lua index 06578d5..6d3dee2 100644 --- a/pocket/process.lua +++ b/pocket/process.lua @@ -17,102 +17,16 @@ local PRODUCT = types.WASTE_PRODUCT local process = {} local self = { - io = nil, ---@type ioctl - comms = nil, ---@type pocket_comms - ---@class sys_control_states - control_states = { - ---@class sys_auto_config - process = { - mode = PROCESS.INACTIVE, - burn_target = 0.0, - charge_target = 0.0, - gen_target = 0.0, - limits = {}, - waste_product = PRODUCT.PLUTONIUM, - pu_fallback = false, - sps_low_power = false - }, - waste_modes = {}, - priority_groups = {} - } + io = nil, ---@type ioctl + comms = nil ---@type pocket_comms } --------------------------- --- UNIT COMMAND CONTROL -- --------------------------- - -- initialize the process controller ---@param iocontrol pocket_ioctl iocontrl system ---@param pocket_comms pocket_comms pocket communications function process.init(iocontrol, pocket_comms) self.io = iocontrol self.comms = pocket_comms - - local ctl_proc = self.control_states.process - - for i = 1, self.io.facility.num_units do - ctl_proc.limits[i] = 0.1 - end - - local ctrl_states = settings.get("ControlStates", {}) - local config = ctrl_states.process ---@type sys_auto_config - - -- facility auto control configuration - if type(config) == "table" then - ctl_proc.mode = config.mode - ctl_proc.burn_target = config.burn_target - ctl_proc.charge_target = config.charge_target - ctl_proc.gen_target = config.gen_target - 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) - self.io.facility.ps.publish("process_charge_target", self.io.energy_convert_from_fe(ctl_proc.charge_target)) - self.io.facility.ps.publish("process_gen_target", self.io.energy_convert_from_fe(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 - unit.unit_ps.publish("burn_limit", ctl_proc.limits[id]) - end - - log.info("PROCESS: loaded auto control settings") - - -- 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 - local waste_modes = ctrl_states.waste_modes ---@type table|nil - if type(waste_modes) == "table" then - for id, mode in pairs(waste_modes) do - self.control_states.waste_modes[id] = mode - self.comms.send_unit_command(UNIT_COMMAND.SET_WASTE, id, mode) - end - - log.info("PROCESS: loaded unit waste mode settings") - end - - -- unit priority groups - local prio_groups = ctrl_states.priority_groups ---@type table|nil - if type(prio_groups) == "table" then - for id, group in pairs(prio_groups) do - self.control_states.priority_groups[id] = group - self.comms.send_unit_command(UNIT_COMMAND.SET_GROUP, id, group) - end - - log.info("PROCESS: loaded priority groups settings") - end -end - -function process.init_fac_data() end -- facility SCRAM command @@ -169,11 +83,6 @@ function process.set_unit_waste(id, mode) log.debug(util.c("PROCESS: UNIT[", id, "] SET WASTE ", mode)) self.control_states.waste_modes[id] = mode - settings.set("ControlStates", self.control_states) - - if not settings.save("/coordinator.settings") then - log.error("process.set_unit_waste(): failed to save coordinator settings file") - end end -- acknowledge all alarms @@ -207,136 +116,6 @@ function process.set_group(unit_id, group_id) log.debug(util.c("PROCESS: UNIT[", unit_id, "] SET GROUP ", group_id)) self.control_states.priority_groups[unit_id] = group_id - settings.set("ControlStates", self.control_states) - - if not settings.save("/coordinator.settings") then - log.error("process.set_group(): failed to save coordinator settings file") - end -end - --------------------------- --- AUTO PROCESS CONTROL -- --------------------------- - --- write auto process control to config file -local function _write_auto_config() - -- save config - settings.set("ControlStates", self.control_states) - local saved = settings.save("/coordinator.settings") - if not saved then - log.warning("process._write_auto_config(): failed to save coordinator settings file") - end - - return saved -end - --- stop automatic process control -function process.stop_auto() - self.comms.send_fac_command(FAC_COMMAND.STOP) - log.debug("PROCESS: STOP AUTO CTL") -end - --- start automatic process control -function process.start_auto() - self.comms.send_auto_start(self.control_states.process) - log.debug("PROCESS: START AUTO CTL") -end - --- set automatic process control waste mode ----@param product WASTE_PRODUCT waste product for auto control -function process.set_process_waste(product) - self.comms.send_fac_command(FAC_COMMAND.SET_WASTE_MODE, product) - - log.debug(util.c("PROCESS: SET WASTE ", product)) - - -- update config table and save - self.control_states.process.waste_product = product - _write_auto_config() -end - --- set automatic process control plutonium fallback ----@param enabled boolean whether to enable plutonium fallback -function process.set_pu_fallback(enabled) - self.comms.send_fac_command(FAC_COMMAND.SET_PU_FB, enabled) - - log.debug(util.c("PROCESS: SET PU FALLBACK ", enabled)) - - -- update config table and save - self.control_states.process.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 ----@param charge_target number charge target ----@param gen_target number generation rate target ----@param limits table unit burn rate limits -function process.save(mode, burn_target, charge_target, gen_target, limits) - log.debug("PROCESS: SAVE") - - -- update config table - local ctl_proc = self.control_states.process - ctl_proc.mode = mode - ctl_proc.burn_target = burn_target - ctl_proc.charge_target = charge_target - ctl_proc.gen_target = gen_target - ctl_proc.limits = limits - - -- save config - self.io.facility.save_cfg_ack(_write_auto_config()) -end - --- handle a start command acknowledgement ----@param response table ack and configuration reply -function process.start_ack_handle(response) - local ack = response[1] - - local ctl_proc = self.control_states.process - ctl_proc.mode = response[2] - ctl_proc.burn_target = response[3] - ctl_proc.charge_target = response[4] - ctl_proc.gen_target = response[5] - - for i = 1, math.min(#response[6], self.io.facility.num_units) do - ctl_proc.limits[i] = response[6][i] - - local unit = self.io.units[i] ---@type ioctl_unit - unit.unit_ps.publish("burn_limit", ctl_proc.limits[i]) - end - - self.io.facility.ps.publish("process_mode", ctl_proc.mode) - self.io.facility.ps.publish("process_burn_target", ctl_proc.burn_target) - self.io.facility.ps.publish("process_charge_target", self.io.energy_convert_from_fe(ctl_proc.charge_target)) - self.io.facility.ps.publish("process_gen_target", self.io.energy_convert_from_fe(ctl_proc.gen_target)) - - self.io.facility.start_ack(ack) -end - --- record waste product state after attempting to change it ----@param response WASTE_PRODUCT supervisor waste product state -function process.waste_ack_handle(response) - self.control_states.process.waste_product = response - self.io.facility.ps.publish("process_waste_product", response) -end - --- record plutonium fallback state after attempting to change it ----@param response boolean supervisor plutonium fallback state -function process.pu_fb_ack_handle(response) - self.control_states.process.pu_fallback = response - self.io.facility.ps.publish("process_pu_fallback", response) end return process diff --git a/pocket/startup.lua b/pocket/startup.lua index a6953b1..675f384 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.11.9-alpha" +local POCKET_VERSION = "v0.12.0-alpha" local println = util.println local println_ts = util.println_ts diff --git a/pocket/ui/apps/control.lua b/pocket/ui/apps/control.lua index 6cd1a76..4066603 100644 --- a/pocket/ui/apps/control.lua +++ b/pocket/ui/apps/control.lua @@ -120,8 +120,6 @@ local function new_view(root) end end - --#region Main Unit Overview - local u_page = app.new_page(nil, i) u_page.tasks = { update } @@ -184,8 +182,6 @@ local function new_view(root) reset.register(u_ps, "rps_tripped", function (active) if active then reset.enable() else reset.disable() end end) - --#endregion - util.nop() end @@ -198,8 +194,8 @@ local function new_view(root) TextBox{parent=f_div,y=1,text="Facility Commands",alignment=ALIGN.CENTER} - local scram = HazardButton{parent=f_div,x=5,y=6,text="FAC SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=process.fac_scram,fg_bg=hzd_fg_bg} - local ack_a = HazardButton{parent=f_div,x=7,y=11,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=process.fac_ack_alarms,fg_bg=hzd_fg_bg} + local scram = HazardButton{parent=f_div,x=5,y=6,text="FAC SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=process.fac_scram,timeout=3,fg_bg=hzd_fg_bg} + local ack_a = HazardButton{parent=f_div,x=7,y=11,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=process.fac_ack_alarms,timeout=3,fg_bg=hzd_fg_bg} db.facility.scram_ack = scram.on_response db.facility.ack_alarms_ack = ack_a.on_response diff --git a/pocket/ui/apps/guide.lua b/pocket/ui/apps/guide.lua index deb1bb4..768824a 100644 --- a/pocket/ui/apps/guide.lua +++ b/pocket/ui/apps/guide.lua @@ -237,13 +237,13 @@ local function new_view(root) PushButton{parent=lnk,x=1,y=1,text="<",fg_bg=btn_fg_bg,active_fg_bg=btn_active,callback=main_page.nav_to} lnk.line_break() - TextBox{parent=lnk,text="GitHub",alignment=ALIGN.LEFT,fg_bg=cpair(colors.lightGray,colors.black)} + TextBox{parent=lnk,text="GitHub",fg_bg=cpair(colors.lightGray,colors.black)} TextBox{parent=lnk,text="https://github.com/MikaylaFischler/cc-mek-scada"} lnk.line_break() - TextBox{parent=lnk,text="Wiki",alignment=ALIGN.LEFT,fg_bg=cpair(colors.lightGray,colors.black)} + TextBox{parent=lnk,text="Wiki",fg_bg=cpair(colors.lightGray,colors.black)} TextBox{parent=lnk,text="https://github.com/MikaylaFischler/cc-mek-scada/wiki"} lnk.line_break() - TextBox{parent=lnk,text="Discord",alignment=ALIGN.LEFT,fg_bg=cpair(colors.lightGray,colors.black)} + TextBox{parent=lnk,text="Discord",fg_bg=cpair(colors.lightGray,colors.black)} TextBox{parent=lnk,text="discord.gg/R9NSCkhcwt"} -- setup multipane diff --git a/pocket/ui/docs.lua b/pocket/ui/docs.lua index 38a9472..dd7a648 100644 --- a/pocket/ui/docs.lua +++ b/pocket/ui/docs.lua @@ -1,4 +1,4 @@ -local const = require("scada-common.constants") +local const = require("scada-common.constants") local docs = {} diff --git a/pocket/ui/pages/guide_section.lua b/pocket/ui/pages/guide_section.lua index d3dba70..c14a6ab 100644 --- a/pocket/ui/pages/guide_section.lua +++ b/pocket/ui/pages/guide_section.lua @@ -1,18 +1,18 @@ -local log = require("scada-common.log") -local util = require("scada-common.util") +local log = require("scada-common.log") +local util = require("scada-common.util") -local docs = require("pocket.ui.docs") +local docs = require("pocket.ui.docs") -local core = require("graphics.core") +local core = require("graphics.core") -local Div = require("graphics.elements.div") -local ListBox = require("graphics.elements.listbox") -local TextBox = require("graphics.elements.textbox") +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 PushButton = require("graphics.elements.controls.push_button") local IndicatorLight = require("graphics.elements.indicators.light") -local LED = require("graphics.elements.indicators.led") +local LED = require("graphics.elements.indicators.led") local ALIGN = core.ALIGN local cpair = core.cpair diff --git a/reactor-plc/config/system.lua b/reactor-plc/config/system.lua index 1486170..f612f9e 100644 --- a/reactor-plc/config/system.lua +++ b/reactor-plc/config/system.lua @@ -22,7 +22,6 @@ local IndLight = require("graphics.elements.indicators.light") local cpair = core.cpair -local LEFT = core.ALIGN.LEFT local RIGHT = core.ALIGN.RIGHT local self = { @@ -606,7 +605,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) local textbox if height > 1 then - textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1,alignment=LEFT} + textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1} else textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT} end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 0f83ef4..cb40ead 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.8.7" +local R_PLC_VERSION = "v1.8.8" local println = util.println local println_ts = util.println_ts diff --git a/rtu/configure.lua b/rtu/configure.lua index 943c248..0c571de 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -1457,7 +1457,7 @@ local function config_view(display) local textbox if height > 1 then - textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1,alignment=LEFT} + textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1} else textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT} end diff --git a/rtu/startup.lua b/rtu/startup.lua index 57911a0..cb0d4ea 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.7" +local RTU_VERSION = "v1.10.8" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE diff --git a/supervisor/configure.lua b/supervisor/configure.lua index 59be74f..0c90538 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -30,7 +30,6 @@ local tri = util.trinary local cpair = core.cpair -local LEFT = core.ALIGN.LEFT local CENTER = core.ALIGN.CENTER local RIGHT = core.ALIGN.RIGHT @@ -1174,7 +1173,7 @@ local function config_view(display) local textbox if height > 1 then - textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1,alignment=LEFT} + textbox = TextBox{parent=line,x=1,y=2,text=val,height=height-1} else textbox = TextBox{parent=line,x=label_w+1,y=1,text=val,alignment=RIGHT} end diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index a2044fc..8b9c0d9 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -1,6 +1,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") +local types = require("scada-common.types") local util = require("scada-common.util") local databus = require("supervisor.databus") @@ -15,6 +16,9 @@ local CRDN_TYPE = comms.CRDN_TYPE local UNIT_COMMAND = comms.UNIT_COMMAND local FAC_COMMAND = comms.FAC_COMMAND +local AUTO_GROUP = types.AUTO_GROUP +local WASTE_MODE = types.WASTE_MODE + local SV_Q_DATA = svqtypes.SV_Q_DATA -- retry time constants in ms @@ -305,7 +309,7 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim -- continue if valid unit id if util.is_int(uid) and uid > 0 and uid <= #self.units then local unit = self.units[uid] ---@type reactor_unit - local manual = facility.get_group(uid) == 0 + local manual = facility.get_group(uid) == AUTO_GROUP.MANUAL if cmd == UNIT_COMMAND.SCRAM then out_queue.push_data(SV_Q_DATA.SCRAM, data) @@ -327,7 +331,8 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim log.debug(log_tag .. "CRDN unit command burn rate missing option") end elseif cmd == UNIT_COMMAND.SET_WASTE then - if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] > 0) and (pkt.data[3] <= 4) then + if (pkt.length == 3) and (type(pkt.data[3]) == "number") and + (pkt.data[3] >= WASTE_MODE.AUTO) and (pkt.data[3] <= WASTE_MODE.MANUAL_ANTI_MATTER) then unit.set_waste_mode(pkt.data[3]) else log.debug(log_tag .. "CRDN unit command set waste missing/invalid option") @@ -348,7 +353,8 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim log.debug(log_tag .. "CRDN unit command reset alarm missing alarm id") end elseif cmd == UNIT_COMMAND.SET_GROUP then - if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] >= 0) and (pkt.data[3] <= 4) then + if (pkt.length == 3) and (type(pkt.data[3]) == "number") and + (pkt.data[3] >= AUTO_GROUP.MANUAL) and (pkt.data[3] <= AUTO_GROUP.BACKUP) then facility.set_group(unit.get_id(), pkt.data[3]) else log.debug(log_tag .. "CRDN unit command set group missing group id") diff --git a/supervisor/startup.lua b/supervisor/startup.lua index ddb044b..4f6726c 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -22,7 +22,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.5.2" +local SUPERVISOR_VERSION = "v1.5.3" local println = util.println local println_ts = util.println_ts From a1494b4afd88731845f14067ab6b569a3b134b98 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 8 Sep 2024 16:11:46 -0400 Subject: [PATCH 34/40] luacheck fix --- pocket/process.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pocket/process.lua b/pocket/process.lua index 6d3dee2..8739c6c 100644 --- a/pocket/process.lua +++ b/pocket/process.lua @@ -4,15 +4,11 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") -local types = require("scada-common.types") local util = require("scada-common.util") local FAC_COMMAND = comms.FAC_COMMAND local UNIT_COMMAND = comms.UNIT_COMMAND -local PROCESS = types.PROCESS -local PRODUCT = types.WASTE_PRODUCT - ---@class pocket_process_controller local process = {} From 06933b2fb7df5cdda3b44b587c31e474da1d28ba Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 8 Sep 2024 16:27:50 -0400 Subject: [PATCH 35/40] removed more unused pocket code --- pocket/process.lua | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/pocket/process.lua b/pocket/process.lua index 8739c6c..454caff 100644 --- a/pocket/process.lua +++ b/pocket/process.lua @@ -68,19 +68,6 @@ function process.set_rate(id, rate) log.debug(util.c("PROCESS: UNIT[", id, "] SET BURN ", rate)) end --- set waste mode ----@param id integer unit ID ----@param mode integer waste mode -function process.set_unit_waste(id, mode) - -- publish so that if it fails then it gets reset - self.io.units[id].unit_ps.publish("U_WasteMode", mode) - - self.comms.send_unit_command(UNIT_COMMAND.SET_WASTE, id, mode) - log.debug(util.c("PROCESS: UNIT[", id, "] SET WASTE ", mode)) - - self.control_states.waste_modes[id] = mode -end - -- acknowledge all alarms ---@param id integer unit ID function process.ack_all_alarms(id) @@ -104,14 +91,4 @@ function process.reset_alarm(id, alarm) log.debug(util.c("PROCESS: UNIT[", id, "] RESET ALARM ", alarm)) end --- assign a unit to a group ----@param unit_id integer unit ID ----@param group_id integer|0 group ID or 0 for independent -function process.set_group(unit_id, group_id) - self.comms.send_unit_command(UNIT_COMMAND.SET_GROUP, unit_id, group_id) - log.debug(util.c("PROCESS: UNIT[", unit_id, "] SET GROUP ", group_id)) - - self.control_states.priority_groups[unit_id] = group_id -end - return process From f56d68d972352349b3325914286ebbb6c8b2bf00 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 8 Sep 2024 16:35:02 -0400 Subject: [PATCH 36/40] removed unused iocontrol functions --- pocket/iocontrol.lua | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index d71911f..b04f64d 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -321,10 +321,7 @@ function iocontrol.init_fac(conf) scram = function () process.scram(i) end, reset_rps = function () process.reset_rps(i) end, ack_alarms = function () process.ack_all_alarms(i) end, - set_burn = function (rate) process.set_rate(i, rate) end, ---@param rate number burn rate - set_waste = function (mode) process.set_unit_waste(i, mode) end, ---@param mode WASTE_MODE waste processing mode - - set_group = function (grp) process.set_group(i, grp) end, ---@param grp integer|0 group ID or 0 for manual + set_burn = function (rate) process.set_rate(i, rate) end, ---@param rate number burn rate start_ack = __generic_ack, scram_ack = __generic_ack, From 35134822a963caae08bfb6bd7a6cbd84465d0653 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 8 Sep 2024 16:49:23 -0400 Subject: [PATCH 37/40] coordinator handle SPS low power ack --- coordinator/coordinator.lua | 2 +- coordinator/process.lua | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index baca753..bb8bcee 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -592,7 +592,7 @@ function coordinator.comms(version, nic, sv_watchdog) elseif cmd == FAC_COMMAND.SET_PU_FB then process.pu_fb_ack_handle(packet.data[2]) elseif cmd == FAC_COMMAND.SET_SPS_LP then - ---@todo + process.sps_lp_ack_handle(packet.data[2]) else log.debug(util.c("received facility command ack with unknown command ", cmd)) end diff --git a/coordinator/process.lua b/coordinator/process.lua index abb1bc2..5f7f010 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -485,18 +485,25 @@ function process.start_ack_handle(response) pctl.io.facility.start_ack(ack) end --- record waste product state after attempting to change it ----@param response WASTE_PRODUCT supervisor waste product state +-- record waste product settting after attempting to change it +---@param response WASTE_PRODUCT supervisor waste product settting function process.waste_ack_handle(response) pctl.control_states.process.waste_product = response pctl.io.facility.ps.publish("process_waste_product", response) end --- record plutonium fallback state after attempting to change it ----@param response boolean supervisor plutonium fallback state +-- record plutonium fallback settting after attempting to change it +---@param response boolean supervisor plutonium fallback settting function process.pu_fb_ack_handle(response) pctl.control_states.process.pu_fallback = response pctl.io.facility.ps.publish("process_pu_fallback", response) end +-- record SPS low power settting after attempting to change it +---@param response boolean supervisor SPS low power settting +function process.sps_lp_ack_handle(response) + pctl.control_states.process.sps_low_power = response + pctl.io.facility.ps.publish("process_sps_low_power", response) +end + return process From 356657c9c077ed241851638bb002c5eb06bc9ccb Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 8 Sep 2024 17:17:30 -0400 Subject: [PATCH 38/40] incremented API version --- scada-common/comms.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scada-common/comms.lua b/scada-common/comms.lua index fa6f6ff..6a82228 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 = "3.0.0" -comms.api_version = "0.0.3" +comms.api_version = "0.0.4" ---@enum PROTOCOL local PROTOCOL = { From 753f062bfce04963c69c0a3f7c6f1affea354476 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 10 Sep 2024 19:44:40 +0000 Subject: [PATCH 39/40] #403 doc spelling fix --- pocket/startup.lua | 2 +- pocket/ui/docs.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pocket/startup.lua b/pocket/startup.lua index 675f384..28b37d9 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.12.0-alpha" +local POCKET_VERSION = "v0.12.1-alpha" local println = util.println local println_ts = util.println_ts diff --git a/pocket/ui/docs.lua b/pocket/ui/docs.lua index dd7a648..0d4e889 100644 --- a/pocket/ui/docs.lua +++ b/pocket/ui/docs.lua @@ -254,7 +254,7 @@ doc("G_DBG", "DBG", "Debug. Abbreviation for the debugging sessions from pocket doc("G_FP", "FP", "Front Panel. See Terminology.") doc("G_Hi", "Hi", "High.") doc("G_Lo", "Lo", "Low.") -doc("G_PID", "PID", "A Proportional Integral Derivitave closed-loop controller.") +doc("G_PID", "PID", "A Proportional Integral Derivative closed-loop controller.") 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 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.") From 48fa715aaab9cec34d43e8271d9f78d49054ff8d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Wed, 11 Sep 2024 21:08:15 -0400 Subject: [PATCH 40/40] incremented util version --- scada-common/util.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scada-common/util.lua b/scada-common/util.lua index ff27d55..36a73fc 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -24,7 +24,7 @@ local t_pack = table.pack local util = {} -- scada-common version -util.version = "1.4.3" +util.version = "1.4.4" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50