From 13509136b8eb3f136773aa5caf0df02a33ef34aa Mon Sep 17 00:00:00 2001 From: Mikayla Date: Sat, 19 Apr 2025 22:20:57 +0000 Subject: [PATCH 01/10] cleanup --- pocket/pocket.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 7bfe669..bcd5186 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -365,8 +365,7 @@ function pocket.init_nav(smem) self.help_return = self.cur_app nav.open_app(APP_ID.GUIDE, function () - local show = self.help_map[key] - if show then show() end + if self.help_map[key] then self.help_map[key]() end end) end From 79d63fce78c6f3316200fbe3b91b2a92d9db7895 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 24 May 2025 17:17:57 -0400 Subject: [PATCH 02/10] #593 start of radiation monitor app --- pocket/pocket.lua | 7 +- pocket/ui/apps/radiation.lua | 219 ++++++++++++++++++++++++++++++++++ pocket/ui/main.lua | 2 + pocket/ui/pages/home_page.lua | 19 +-- 4 files changed, 236 insertions(+), 11 deletions(-) create mode 100644 pocket/ui/apps/radiation.lua diff --git a/pocket/pocket.lua b/pocket/pocket.lua index c6f833b..c30422a 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -96,11 +96,12 @@ local APP_ID = { WASTE = 7, GUIDE = 8, ABOUT = 9, + RADMON = 10, -- diagnostic app pages - ALARMS = 10, + ALARMS = 11, -- other - DUMMY = 11, - NUM_APPS = 11 + DUMMY = 12, + NUM_APPS = 12 } pocket.APP_ID = APP_ID diff --git a/pocket/ui/apps/radiation.lua b/pocket/ui/apps/radiation.lua new file mode 100644 index 0000000..5c14a4a --- /dev/null +++ b/pocket/ui/apps/radiation.lua @@ -0,0 +1,219 @@ +-- +-- Radiation Monitor App +-- + +local types = require("scada-common.types") +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 MultiPane = require("graphics.elements.MultiPane") +local Rectangle = require("graphics.elements.Rectangle") +local TextBox = require("graphics.elements.TextBox") + +local WaitingAnim = require("graphics.elements.animations.Waiting") + +local HazardButton = require("graphics.elements.controls.HazardButton") +local RadioButton = require("graphics.elements.controls.RadioButton") + +local NumberField = require("graphics.elements.form.NumberField") + +local IconIndicator = require("graphics.elements.indicators.IconIndicator") + +local ALIGN = core.ALIGN +local cpair = core.cpair +local border = core.border + +local APP_ID = pocket.APP_ID + +local label_fg_bg = style.label +local text_fg = style.text_fg + +local field_fg_bg = style.field +local field_dis_fg_bg = style.field_disable + +local red_ind_s = style.icon_states.red_ind_s +local yel_ind_s = style.icon_states.yel_ind_s +local grn_ind_s = style.icon_states.grn_ind_s +local wht_ind_s = style.icon_states.wht_ind_s + +local hzd_fg_bg = style.hzd_fg_bg +local dis_colors = cpair(colors.white, colors.lightGray) + +-- new radiation monitor page view +---@param root Container 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.RADMON, 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.yellow,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 = db.nav.go_home } }) + + local page_div = nil ---@type Div|nil + + -- load the app (create the elements) + local function load() + local f_ps = db.facility.ps + + page_div = Div{parent=main,y=2,width=main.get_width()} + + local panes = {} ---@type Div[] + + -- create all page divs + for _ = 1, db.facility.num_units + 2 do + local div = Div{parent=page_div} + table.insert(panes, div) + end + + local last_update = 0 + -- refresh data callback, every 500ms it will re-send the query + local function update() + if util.time_ms() - last_update >= 500 then + -- db.api.get_rad() + last_update = util.time_ms() + end + end + + --#region unit radiation monitors + + 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] + local u_ps = unit.unit_ps + + local u_page = app.new_page(nil, i) + u_page.tasks = { update } + + TextBox{parent=u_div,y=1,text="Unit #"..i.." Monitors",alignment=ALIGN.CENTER} + + -- TextBox{parent=u_div,y=3,text="Auto Rate Limit",fg_bg=label_fg_bg} + -- rate_limits[i] = NumberField{parent=u_div,x=1,y=4,width=16,default=0.01,min=0.01,max_frac_digits=2,max_chars=8,allow_decimal=true,align_right=true,fg_bg=field_fg_bg,dis_fg_bg=field_dis_fg_bg} + -- TextBox{parent=u_div,x=18,y=4,text="mB/t",width=4,fg_bg=label_fg_bg} + + -- rate_limits[i].register(unit.unit_ps, "max_burn", rate_limits[i].set_max) + -- rate_limits[i].register(unit.unit_ps, "burn_limit", rate_limits[i].set_value) + + -- local ready = IconIndicator{parent=u_div,y=6,label="Auto Ready",states=grn_ind_s} + -- local a_stb = IconIndicator{parent=u_div,label="Auto Standby",states=wht_ind_s} + -- local degraded = IconIndicator{parent=u_div,label="Unit Degraded",states=red_ind_s} + + -- ready.register(u_ps, "U_AutoReady", ready.update) + -- degraded.register(u_ps, "U_AutoDegraded", degraded.update) + + -- -- update standby indicator + -- a_stb.register(u_ps, "status", function (active) + -- a_stb.update(unit.annunciator.AutoControl and (not active)) + -- end) + -- a_stb.register(u_ps, "AutoControl", function (auto_active) + -- if auto_active then + -- a_stb.update(unit.reactor_data.mek_status.status == false) + -- else a_stb.update(false) end + -- end) + + -- local function _set_group(value) process.set_group(i, value - 1) end + + -- local group = RadioButton{parent=u_div,y=10,options=types.AUTO_GROUP_NAMES,callback=_set_group,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.purple,dis_fg_bg=style.btn_disable} + + -- -- can't change group if auto is engaged regardless of if this unit is part of auto control + -- group.register(f_ps, "auto_active", function (auto_active) + -- if auto_active then group.disable() else group.enable() end + -- end) + + -- group.register(u_ps, "auto_group_id", function (gid) group.set_value(gid + 1) end) + + -- TextBox{parent=u_div,y=16,text="Assigned Group",fg_bg=style.label} + -- local auto_grp = TextBox{parent=u_div,text="Manual",width=11,fg_bg=text_fg} + + -- auto_grp.register(u_ps, "auto_group", auto_grp.set_value) + + util.nop() + end + + --#endregion + + --#region overview page + + local s_pane = panes[db.facility.num_units + 1] + local s_div = Div{parent=s_pane,x=2,width=main.get_width()-2} + + local stat_page = app.new_page(nil, db.facility.num_units + 1) + stat_page.tasks = { update } + + TextBox{parent=s_div,y=1,text="Radiation Monitors",alignment=ALIGN.CENTER} + + --#endregion + + --#region overview page + + local f_pane = panes[db.facility.num_units + 2] + local f_div = Div{parent=f_pane,x=2,width=main.get_width()-2} + + local fac_page = app.new_page(nil, db.facility.num_units + 2) + fac_page.tasks = { update } + + TextBox{parent=f_div,y=1,text="Facility Monitors",alignment=ALIGN.CENTER} + + --#endregion + + -- setup multipane + local u_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes} + app.set_root_pane(u_pane) + + -- setup sidebar + + local list = { + { label = " # ", tall = true, color = core.cpair(colors.black, colors.green), callback = db.nav.go_home }, + { label = " \x1e ", color = core.cpair(colors.black, colors.blue), callback = stat_page.nav_to }, + { label = "FAC", color = core.cpair(colors.black, colors.yellow), callback = fac_page.nav_to } + } + + 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) + + -- done, show the app + stat_page.nav_to() + 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 = db.nav.go_home } }) + 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 588cf0b..e033777 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -14,6 +14,7 @@ local facil_app = require("pocket.ui.apps.facility") local guide_app = require("pocket.ui.apps.guide") local loader_app = require("pocket.ui.apps.loader") local process_app = require("pocket.ui.apps.process") +local rad_app = require("pocket.ui.apps.radiation") local sys_apps = require("pocket.ui.apps.sys_apps") local unit_app = require("pocket.ui.apps.unit") local waste_app = require("pocket.ui.apps.waste") @@ -71,6 +72,7 @@ local function init(main) process_app(page_div) waste_app(page_div) guide_app(page_div) + rad_app(page_div) loader_app(page_div) sys_apps(page_div) diag_apps(page_div) diff --git a/pocket/ui/pages/home_page.lua b/pocket/ui/pages/home_page.lua index ada6e92..80fdd3f 100644 --- a/pocket/ui/pages/home_page.lua +++ b/pocket/ui/pages/home_page.lua @@ -29,8 +29,9 @@ local function new_view(root) local apps_1 = Div{parent=main,x=1,y=1,height=15} local apps_2 = Div{parent=main,x=1,y=1,height=15} + local apps_3 = Div{parent=main,x=1,y=1,height=15} - local panes = { apps_1, apps_2 } + local panes = { apps_1, apps_2, apps_3 } local app_pane = AppMultiPane{parent=main,x=1,y=1,height=18,panes=panes,active_color=colors.lightGray,nav_colors=cpair(colors.lightGray,colors.gray),scroll_nav=true,drag_nav=true,callback=app.switcher} @@ -50,15 +51,17 @@ local function new_view(root) 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.PROCESS)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.WASTE)end,app_fg_bg=cpair(colors.black,colors.brown),active_fg_bg=active_fg_bg} - App{parent=apps_1,x=16,y=7,text="\x08",title="Devices",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=active_fg_bg} - App{parent=apps_1,x=2,y=12,text="\xb6",title="Guide",callback=function()open(APP_ID.GUIDE)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg} - App{parent=apps_1,x=9,y=12,text="?",title="About",callback=function()open(APP_ID.ABOUT)end,app_fg_bg=cpair(colors.black,colors.white),active_fg_bg=active_fg_bg} + App{parent=apps_1,x=16,y=7,text="\xb6",title="Guide",callback=function()open(APP_ID.GUIDE)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg} + App{parent=apps_1,x=2,y=12,text="?",title="About",callback=function()open(APP_ID.ABOUT)end,app_fg_bg=cpair(colors.black,colors.white),active_fg_bg=active_fg_bg} - TextBox{parent=apps_2,text="Diagnostic Apps",x=1,y=2,alignment=ALIGN.CENTER} + App{parent=apps_2,x=2,y=2,text="\x08",title="Devices",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.lightGray),active_fg_bg=active_fg_bg} + App{parent=apps_2,x=9,y=2,text="\x1e",title="Rad",callback=function()open(APP_ID.RADMON)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg} - App{parent=apps_2,x=2,y=4,text="\x0f",title="Alarm",callback=function()open(APP_ID.ALARMS)end,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=active_fg_bg} - App{parent=apps_2,x=9,y=4,text="\x1e",title="LoopT",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.cyan),active_fg_bg=active_fg_bg} - App{parent=apps_2,x=16,y=4,text="@",title="Comps",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg} + TextBox{parent=apps_3,text="Diagnostic Apps",x=1,y=2,alignment=ALIGN.CENTER} + + App{parent=apps_3,x=2,y=4,text="\x0f",title="Alarm",callback=function()open(APP_ID.ALARMS)end,app_fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=active_fg_bg} + App{parent=apps_3,x=9,y=4,text="@",title="Comps",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg} + App{parent=apps_3,x=16,y=4,text="R",title="RS Test",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.red),active_fg_bg=active_fg_bg} return main end From 9e59883a844b2c3fdf414172e643d52573cf8bd3 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 3 Jun 2025 14:10:30 +0000 Subject: [PATCH 03/10] #593 radiation monitor data comms --- coordinator/iocontrol.lua | 20 ++++++++++++++---- coordinator/session/pocket.lua | 7 +++++++ coordinator/startup.lua | 2 +- pocket/iocontrol.lua | 11 +++++++--- pocket/iorx.lua | 37 +++++++++++++++++++++++++++++++++- pocket/pocket.lua | 9 +++++++++ pocket/ui/apps/radiation.lua | 2 +- scada-common/comms.lua | 3 ++- 8 files changed, 80 insertions(+), 11 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index f44becb..fbca953 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -132,7 +132,9 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale) sps_data_tbl = {}, ---@type sps_session_db[] tank_ps_tbl = {}, ---@type psil[] - tank_data_tbl = {} ---@type dynamicv_session_db[] + tank_data_tbl = {}, ---@type dynamicv_session_db[] + + rad_monitors = {} ---@type { radiation: radiation_reading, raw: number }[] } -- create induction and SPS tables (currently only 1 of each is supported) @@ -242,7 +244,9 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale) turbine_data_tbl = {}, ---@type turbinev_session_db[] tank_ps_tbl = {}, ---@type psil[] - tank_data_tbl = {} ---@type dynamicv_session_db[] + tank_data_tbl = {}, ---@type dynamicv_session_db[] + + rad_monitors = {} ---@type { radiation: radiation_reading, raw: number }[] } -- on other facility modes, overwrite unit TANK option with facility tank defs @@ -797,7 +801,9 @@ function iocontrol.update_facility_status(status) if type(rtu_statuses.envds) == "table" then local max_rad, max_reading, any_conn, any_faulted = 0, types.new_zero_radiation_reading(), false, false - for _, envd in pairs(rtu_statuses.envds) do + fac.rad_monitors = {} + + for id, envd in pairs(rtu_statuses.envds) do local rtu_faulted = envd[1] ---@type boolean local radiation = envd[2] ---@type radiation_reading local rad_raw = envd[3] ---@type number @@ -809,6 +815,8 @@ function iocontrol.update_facility_status(status) max_rad = rad_raw max_reading = radiation end + + fac.rad_monitors[id] = { radiation = radiation, raw = rad_raw } end if any_conn then @@ -1099,7 +1107,9 @@ function iocontrol.update_unit_statuses(statuses) if type(rtu_statuses.envds) == "table" then local max_rad, max_reading, any_conn = 0, types.new_zero_radiation_reading(), false - for _, envd in pairs(rtu_statuses.envds) do + unit.rad_monitors = {} + + for id, envd in pairs(rtu_statuses.envds) do local radiation = envd[2] ---@type radiation_reading local rad_raw = envd[3] ---@type number @@ -1109,6 +1119,8 @@ function iocontrol.update_unit_statuses(statuses) max_rad = rad_raw max_reading = radiation end + + unit.rad_monitors[id] = { radiation = radiation, raw = rad_raw } end if any_conn then diff --git a/coordinator/session/pocket.lua b/coordinator/session/pocket.lua index a12870a..7c2a72a 100644 --- a/coordinator/session/pocket.lua +++ b/coordinator/session/pocket.lua @@ -427,6 +427,13 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) } _send(CRDN_TYPE.API_GET_WASTE, data) + elseif pkt.type == CRDN_TYPE.API_GET_RAD then + local data = {} + + for i = 1, #db.units do data[i] = db.units[i].rad_monitors end + data[#db.units + 1] = db.facility.rad_monitors + + _send(CRDN_TYPE.API_GET_RAD, data) else log.debug(log_tag .. "handler received unsupported CRDN packet type " .. pkt.type) end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index cd1213c..1edf082 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.6.15" +local COORDINATOR_VERSION = "v1.6.16" local CHUNK_LOAD_DELAY_S = 30.0 diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 5378329..fe2955c 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -98,7 +98,8 @@ function iocontrol.init_core(pkt_comms, nav, cfg) get_unit = function (unit) comms.api__get_unit(unit) end, get_ctrl = function () comms.api__get_control() end, get_proc = function () comms.api__get_process() end, - get_waste = function () comms.api__get_waste() end + get_waste = function () comms.api__get_waste() end, + get_rad = function () comms.api__get_rad() end } end @@ -184,7 +185,9 @@ function iocontrol.init_fac(conf) sps_data_tbl = {}, ---@type sps_session_db[] tank_ps_tbl = {}, ---@type psil[] - tank_data_tbl = {} ---@type dynamicv_session_db[] + tank_data_tbl = {}, ---@type dynamicv_session_db[] + + rad_monitors = {} ---@type { radiation: radiation_reading, raw: number }[] } -- create induction and SPS tables (currently only 1 of each is supported) @@ -264,7 +267,9 @@ function iocontrol.init_fac(conf) turbine_data_tbl = {}, ---@type turbinev_session_db[] tank_ps_tbl = {}, ---@type psil[] - tank_data_tbl = {} ---@type dynamicv_session_db[] + tank_data_tbl = {}, ---@type dynamicv_session_db[] + + rad_monitors = {} ---@type { radiation: radiation_reading, raw: number }[] } -- on other facility modes, overwrite unit TANK option with facility tank defs diff --git a/pocket/iorx.lua b/pocket/iorx.lua index cdb5d91..2b2cebe 100644 --- a/pocket/iorx.lua +++ b/pocket/iorx.lua @@ -658,7 +658,6 @@ function iorx.record_waste_data(data) fac.ps.publish("sps_process_rate", f_data[9]) end - -- update facility app with facility and unit data from API_GET_FAC_DTL ---@param data table function iorx.record_fac_detail_data(data) @@ -819,6 +818,42 @@ function iorx.record_fac_detail_data(data) s_ps.publish("SPSStateStatus", s_stat) end +-- update the radiation monitor app with radiation monitor data from API_GET_RAD +---@param data table +function iorx.record_radiation_data(data) + -- unit radiation monitors + + for u_id = 1, #io.units do + local unit = io.units[u_id] + + unit.radiation = types.new_zero_radiation_reading() + unit.rad_monitors = data[u_id] + + local max_rad = 0 + for _, mon in pairs(unit.rad_monitors) do + if mon.raw > max_rad then + max_rad = mon.raw + unit.radiation = mon.radiation + end + end + end + + -- facility radiation monitors + + local fac = io.facility + + fac.radiation = types.new_zero_radiation_reading() + fac.rad_monitors = data[#io.units + 1] + + local max_rad = 0 + for _, mon in pairs(fac.rad_monitors) do + if mon.raw > max_rad then + max_rad = mon.raw + fac.radiation = mon.radiation + end + end +end + return function (io_obj) io = io_obj return iorx diff --git a/pocket/pocket.lua b/pocket/pocket.lua index c30422a..cfa435a 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -583,6 +583,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) if self.api.linked then _send_api(CRDN_TYPE.API_GET_WASTE, {}) end end + -- coordinator get radiation app data + function public.api__get_rad() + if self.api.linked then _send_api(CRDN_TYPE.API_GET_RAD, {}) end + end + -- send a facility command ---@param cmd FAC_COMMAND command ---@param option any? optional option options for the optional options (like waste mode) @@ -759,6 +764,10 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) if _check_length(packet, #iocontrol.get_db().units + 1) then iocontrol.rx.record_waste_data(packet.data) end + elseif packet.type == CRDN_TYPE.API_GET_RAD then + if _check_length(packet, #iocontrol.get_db().units + 1) then + iocontrol.rx.record_radiation_data(packet.data) + end else _fail_type(packet) end else log.debug("discarding coordinator SCADA_CRDN packet before linked") diff --git a/pocket/ui/apps/radiation.lua b/pocket/ui/apps/radiation.lua index 5c14a4a..2801aba 100644 --- a/pocket/ui/apps/radiation.lua +++ b/pocket/ui/apps/radiation.lua @@ -86,7 +86,7 @@ local function new_view(root) -- refresh data callback, every 500ms it will re-send the query local function update() if util.time_ms() - last_update >= 500 then - -- db.api.get_rad() + db.api.get_rad() last_update = util.time_ms() end end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index f827ab8..75a99ad 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -72,7 +72,8 @@ local CRDN_TYPE = { API_GET_UNIT = 10, -- API: get reactor unit data API_GET_CTRL = 11, -- API: get data for the control app API_GET_PROC = 12, -- API: get data for the process app - API_GET_WASTE = 13 -- API: get data for the waste app + API_GET_WASTE = 13, -- API: get data for the waste app + API_GET_RAD = 14 -- API: get data for the radiation monitor app } ---@enum ESTABLISH_ACK From e1ac42f5f8974e4dc0108f18accbe386d0154bdf Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 15 Jun 2025 18:24:36 -0400 Subject: [PATCH 04/10] #593 structure and graphics elements for the radiation monitor app --- pocket/iorx.lua | 6 ++ pocket/ui/apps/radiation.lua | 106 ++++++++++++++++------------------- 2 files changed, 54 insertions(+), 58 deletions(-) diff --git a/pocket/iorx.lua b/pocket/iorx.lua index 2b2cebe..90db0ae 100644 --- a/pocket/iorx.lua +++ b/pocket/iorx.lua @@ -836,6 +836,9 @@ function iorx.record_radiation_data(data) unit.radiation = mon.radiation end end + + unit.unit_ps.publish("radiation", unit.radiation) + unit.unit_ps.publish("radiation_monitors", textutils.serialize(unit.rad_monitors)) end -- facility radiation monitors @@ -852,6 +855,9 @@ function iorx.record_radiation_data(data) fac.radiation = mon.radiation end end + + fac.ps.publish("radiation", fac.radiation) + fac.ps.publish("radiation_monitors", textutils.serialize(fac.rad_monitors)) end return function (io_obj) diff --git a/pocket/ui/apps/radiation.lua b/pocket/ui/apps/radiation.lua index 2801aba..b4b7482 100644 --- a/pocket/ui/apps/radiation.lua +++ b/pocket/ui/apps/radiation.lua @@ -2,30 +2,24 @@ -- Radiation Monitor App -- -local types = require("scada-common.types") 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 Rectangle = require("graphics.elements.Rectangle") local TextBox = require("graphics.elements.TextBox") local WaitingAnim = require("graphics.elements.animations.Waiting") -local HazardButton = require("graphics.elements.controls.HazardButton") -local RadioButton = require("graphics.elements.controls.RadioButton") - -local NumberField = require("graphics.elements.form.NumberField") - -local IconIndicator = require("graphics.elements.indicators.IconIndicator") +local RadIndicator = require("graphics.elements.indicators.RadIndicator") local ALIGN = core.ALIGN local cpair = core.cpair @@ -34,19 +28,9 @@ local border = core.border local APP_ID = pocket.APP_ID local label_fg_bg = style.label +local lu_col = style.label_unit_pair local text_fg = style.text_fg -local field_fg_bg = style.field -local field_dis_fg_bg = style.field_disable - -local red_ind_s = style.icon_states.red_ind_s -local yel_ind_s = style.icon_states.yel_ind_s -local grn_ind_s = style.icon_states.grn_ind_s -local wht_ind_s = style.icon_states.wht_ind_s - -local hzd_fg_bg = style.hzd_fg_bg -local dis_colors = cpair(colors.white, colors.lightGray) - -- new radiation monitor page view ---@param root Container parent local function new_view(root) @@ -95,7 +79,7 @@ local function new_view(root) 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 u_div = Div{parent=u_pane} local unit = db.units[i] local u_ps = unit.unit_ps @@ -104,47 +88,33 @@ local function new_view(root) TextBox{parent=u_div,y=1,text="Unit #"..i.." Monitors",alignment=ALIGN.CENTER} - -- TextBox{parent=u_div,y=3,text="Auto Rate Limit",fg_bg=label_fg_bg} - -- rate_limits[i] = NumberField{parent=u_div,x=1,y=4,width=16,default=0.01,min=0.01,max_frac_digits=2,max_chars=8,allow_decimal=true,align_right=true,fg_bg=field_fg_bg,dis_fg_bg=field_dis_fg_bg} - -- TextBox{parent=u_div,x=18,y=4,text="mB/t",width=4,fg_bg=label_fg_bg} + TextBox{parent=u_div,x=2,y=3,text="Max Radiation",fg_bg=style.label} + local radiation = RadIndicator{parent=u_div,x=2,label="",format="%17.3f",lu_colors=lu_col,width=21} + radiation.register(u_ps, "radiation", radiation.update) - -- rate_limits[i].register(unit.unit_ps, "max_burn", rate_limits[i].set_max) - -- rate_limits[i].register(unit.unit_ps, "burn_limit", rate_limits[i].set_value) + local mon_list = ListBox{parent=u_div,y=6,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} - -- local ready = IconIndicator{parent=u_div,y=6,label="Auto Ready",states=grn_ind_s} - -- local a_stb = IconIndicator{parent=u_div,label="Auto Standby",states=wht_ind_s} - -- local degraded = IconIndicator{parent=u_div,label="Unit Degraded",states=red_ind_s} + -- local last_list = {} + -- local new_list = {} - -- ready.register(u_ps, "U_AutoReady", ready.update) - -- degraded.register(u_ps, "U_AutoDegraded", degraded.update) + mon_list.register(u_ps, "radiation", function () + -- for k, _ in pairs(unit.rad_monitors) do + -- if last_list[k] then last_list[k] = nil end + -- end - -- -- update standby indicator - -- a_stb.register(u_ps, "status", function (active) - -- a_stb.update(unit.annunciator.AutoControl and (not active)) - -- end) - -- a_stb.register(u_ps, "AutoControl", function (auto_active) - -- if auto_active then - -- a_stb.update(unit.reactor_data.mek_status.status == false) - -- else a_stb.update(false) end - -- end) + -- re-create due to new items + -- if util.table_len(last_list) > 0 then + mon_list.remove_all() - -- local function _set_group(value) process.set_group(i, value - 1) end - - -- local group = RadioButton{parent=u_div,y=10,options=types.AUTO_GROUP_NAMES,callback=_set_group,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.purple,dis_fg_bg=style.btn_disable} - - -- -- can't change group if auto is engaged regardless of if this unit is part of auto control - -- group.register(f_ps, "auto_active", function (auto_active) - -- if auto_active then group.disable() else group.enable() end - -- end) - - -- group.register(u_ps, "auto_group_id", function (gid) group.set_value(gid + 1) end) - - -- TextBox{parent=u_div,y=16,text="Assigned Group",fg_bg=style.label} - -- local auto_grp = TextBox{parent=u_div,text="Manual",width=11,fg_bg=text_fg} - - -- auto_grp.register(u_ps, "auto_group", auto_grp.set_value) - - util.nop() + for k, v in pairs(unit.rad_monitors) do + local mon_div = Div{parent=mon_list,height=4} + local mon_rect = Rectangle{parent=mon_div,height=5,x=2,width=20,border=border(1,colors.gray,true),thin=true,fg_bg=cpair(colors.black,colors.lightGray)} + TextBox{parent=mon_rect,text="Env. Detector "..k} + local mon_rad = RadIndicator{parent=mon_rect,x=2,label="",format="%13.3f",value=v.radiation,lu_colors=cpair(colors.gray,colors.gray),width=18} + -- radiation.register(u_ps, "radiation", mon_rad.update) + end + -- end + end) end --#endregion @@ -157,20 +127,40 @@ local function new_view(root) local stat_page = app.new_page(nil, db.facility.num_units + 1) stat_page.tasks = { update } - TextBox{parent=s_div,y=1,text="Radiation Monitors",alignment=ALIGN.CENTER} + TextBox{parent=s_div,y=1,text=" Radiation Monitoring",alignment=ALIGN.CENTER} + + TextBox{parent=s_div,y=3,text="Max Facility Rad.",fg_bg=style.label} + local s_f_rad = RadIndicator{parent=s_div,label="",format="%17.3f",lu_colors=lu_col,width=21} + s_f_rad.register(f_ps, "radiation", s_f_rad.update) + + for i = 1, db.facility.num_units do + local unit = db.units[i] + local u_ps = unit.unit_ps + + s_div.line_break() + TextBox{parent=s_div,text="Max Unit "..i.." Radiation",fg_bg=style.label} + local s_u_rad = RadIndicator{parent=s_div,label="",format="%17.3f",lu_colors=lu_col,width=21} + s_u_rad.register(u_ps, "radiation", s_u_rad.update) + end --#endregion --#region overview page local f_pane = panes[db.facility.num_units + 2] - local f_div = Div{parent=f_pane,x=2,width=main.get_width()-2} + local f_div = Div{parent=f_pane,width=main.get_width()} local fac_page = app.new_page(nil, db.facility.num_units + 2) fac_page.tasks = { update } TextBox{parent=f_div,y=1,text="Facility Monitors",alignment=ALIGN.CENTER} + TextBox{parent=f_div,x=2,y=3,text="Max Radiation",fg_bg=style.label} + local f_rad = RadIndicator{parent=f_div,x=2,label="",format="%17.3f",lu_colors=lu_col,width=21} + f_rad.register(f_ps, "radiation", f_rad.update) + + local f_mon_list = ListBox{parent=f_div,y=6,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + --#endregion -- setup multipane From 6d3b35a41d57b8fa0bb1307dd05c28b761345ec8 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 16 Jun 2025 15:07:56 +0000 Subject: [PATCH 05/10] #621 support environment_detector peripheral type --- rtu/config/check.lua | 2 +- rtu/config/peripherals.lua | 10 +++++----- rtu/config/system.lua | 4 ++-- rtu/startup.lua | 4 ++-- rtu/threads.lua | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/rtu/config/check.lua b/rtu/config/check.lua index bc3f6d5..fbc7c4c 100644 --- a/rtu/config/check.lua +++ b/rtu/config/check.lua @@ -195,7 +195,7 @@ local function self_check() valid = is_int_min_max(entry.unit, 1, 4) elseif p_type == "dynamicValve" then valid = (entry.unit == nil and is_int_min_max(entry.index, 1, 4)) or is_int_min_max(entry.unit, 1, 4) - elseif p_type == "environmentDetector" then + elseif p_type == "environmentDetector" or p_type == "environment_detector" then valid = (entry.unit == nil or is_int_min_max(entry.unit, 1, 4)) and util.is_int(entry.index) else valid = true diff --git a/rtu/config/peripherals.lua b/rtu/config/peripherals.lua index e214379..cbb3a06 100644 --- a/rtu/config/peripherals.lua +++ b/rtu/config/peripherals.lua @@ -43,8 +43,8 @@ local self = { local peripherals = {} -local RTU_DEV_TYPES = { "boilerValve", "turbineValve", "dynamicValve", "inductionPort", "spsPort", "solarNeutronActivator", "environmentDetector" } -local NEEDS_UNIT = { "boilerValve", "turbineValve", "dynamicValve", "solarNeutronActivator", "environmentDetector" } +local RTU_DEV_TYPES = { "boilerValve", "turbineValve", "dynamicValve", "inductionPort", "spsPort", "solarNeutronActivator", "environmentDetector", "environment_detector" } +local NEEDS_UNIT = { "boilerValve", "turbineValve", "dynamicValve", "solarNeutronActivator", "environmentDetector", "environment_detector" } -- create the peripherals configuration view ---@param tool_ctl _rtu_cfg_tool_ctl @@ -165,7 +165,7 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style) end self.p_desc.set_value("Each reactor unit can have at most 1 tank and the facility can have at most 4. Each facility tank must have a unique # 1 through 4, regardless of where it is connected. Only a total of 4 tanks can be displayed on the flow monitor.") - elseif type == "environmentDetector" then + elseif type == "environmentDetector" or type == "environment_detector" then reposition("This is the below system's # env. detector.", 29, 99, 17, 6, 8) self.p_assign_btn.show() self.p_assign_btn.redraw() @@ -281,7 +281,7 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style) local idx = tonumber(self.p_idx.get_value()) if util.table_contains(NEEDS_UNIT, peri_type) then - if (peri_type == "dynamicValve" or peri_type == "environmentDetector") and for_facility then + if (peri_type == "dynamicValve" or peri_type == "environmentDetector" or peri_type == "environment_detector") and for_facility then -- skip elseif not (util.is_int(u) and u > 0 and u < 5) then self.p_err.set_value("Unit ID must be within 1 to 4.") @@ -310,7 +310,7 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style) else index = idx end elseif peri_type == "dynamicValve" then index = 1 - elseif peri_type == "environmentDetector" then + elseif peri_type == "environmentDetector" or peri_type == "environment_detector" then if not (util.is_int(idx) and idx > 0) then self.p_err.set_value("Index must be greater than 0.") self.p_err.show() diff --git a/rtu/config/system.lua b/rtu/config/system.lua index d29b1ec..27beef7 100644 --- a/rtu/config/system.lua +++ b/rtu/config/system.lua @@ -506,7 +506,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) local u, idx = def.unit, def.index if util.table_contains(NEEDS_UNIT, mount.type) then - if (mount.type == "dynamicValve" or mount.type == "environmentDetector") and for_facility then + if (mount.type == "dynamicValve" or mount.type == "environmentDetector" or mount.type == "environment_detector") and for_facility then -- skip elseif not (util.is_int(u) and u > 0 and u < 5) then err = true @@ -527,7 +527,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) else index = idx end elseif mount.type == "dynamicValve" then index = 1 - elseif mount.type == "environmentDetector" then + elseif mount.type == "environmentDetector" or mount.type == "environment_detector" then if not (util.is_int(idx) and idx > 0) then err = true else index = idx end diff --git a/rtu/startup.lua b/rtu/startup.lua index d3812c6..d7cfe97 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.12.1" +local RTU_VERSION = "v1.12.2" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_HW_STATE = databus.RTU_HW_STATE @@ -472,7 +472,7 @@ local function main() rtu_type = RTU_UNIT_TYPE.SNA rtu_iface, faulted = sna_rtu.new(device) - elseif type == "environmentDetector" then + elseif type == "environmentDetector" or type == "environment_detector" then -- advanced peripherals environment detector if not validate_index(1) then return false end if not validate_assign(entry.unit == nil) then return false end diff --git a/rtu/threads.lua b/rtu/threads.lua index e036a4e..ac875aa 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -89,7 +89,7 @@ local function handle_unit_mount(smem, println_ts, iface, type, device, unit) if unit.reactor < 1 or unit.reactor > 4 then fail(util.c("SNA '", unit.name, "' cannot init, not assigned to a valid unit")) end unit.type = RTU_UNIT_TYPE.SNA - elseif type == "environmentDetector" then + elseif type == "environmentDetector" or type == "environment_detector" then -- advanced peripherals environment detector if unit.reactor < 0 or unit.reactor > 4 then fail(util.c("environment detector '", unit.name, "' cannot init, no valid assignment provided")) end if (unit.index == false) or unit.index < 1 then fail(util.c("environment detector '", unit.name, "' cannot init, invalid index provided")) end From a629c04d11011f9e97133ec5dd41bbdb1e7dd900 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 16 Jun 2025 19:21:16 +0000 Subject: [PATCH 06/10] #593 radiation monitor lists --- pocket/iorx.lua | 23 ++++++++++---- pocket/ui/apps/radiation.lua | 59 +++++++++++++++++++++--------------- 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/pocket/iorx.lua b/pocket/iorx.lua index 90db0ae..aea9699 100644 --- a/pocket/iorx.lua +++ b/pocket/iorx.lua @@ -824,13 +824,18 @@ function iorx.record_radiation_data(data) -- unit radiation monitors for u_id = 1, #io.units do - local unit = io.units[u_id] + local unit = io.units[u_id] + local max_rad = 0 + local connected = {} unit.radiation = types.new_zero_radiation_reading() unit.rad_monitors = data[u_id] - local max_rad = 0 - for _, mon in pairs(unit.rad_monitors) do + for id, mon in pairs(unit.rad_monitors) do + table.insert(connected, id) + + unit.unit_ps.publish("radiation@" .. id, mon.radiation) + if mon.raw > max_rad then max_rad = mon.raw unit.radiation = mon.radiation @@ -838,7 +843,7 @@ function iorx.record_radiation_data(data) end unit.unit_ps.publish("radiation", unit.radiation) - unit.unit_ps.publish("radiation_monitors", textutils.serialize(unit.rad_monitors)) + unit.unit_ps.publish("radiation_monitors", textutils.serialize(connected)) end -- facility radiation monitors @@ -849,7 +854,13 @@ function iorx.record_radiation_data(data) fac.rad_monitors = data[#io.units + 1] local max_rad = 0 - for _, mon in pairs(fac.rad_monitors) do + local connected = {} + + for id, mon in pairs(fac.rad_monitors) do + table.insert(connected, id) + + fac.ps.publish("radiation@" .. id, mon.radiation) + if mon.raw > max_rad then max_rad = mon.raw fac.radiation = mon.radiation @@ -857,7 +868,7 @@ function iorx.record_radiation_data(data) end fac.ps.publish("radiation", fac.radiation) - fac.ps.publish("radiation_monitors", textutils.serialize(fac.rad_monitors)) + fac.ps.publish("radiation_monitors", textutils.serialize(connected)) end return function (io_obj) diff --git a/pocket/ui/apps/radiation.lua b/pocket/ui/apps/radiation.lua index b4b7482..d0fccd8 100644 --- a/pocket/ui/apps/radiation.lua +++ b/pocket/ui/apps/radiation.lua @@ -75,6 +75,39 @@ local function new_view(root) end end + -- create a new radiation monitor list + ---@param parent Container + ---@param ps psil + local function new_mon_list(parent, ps) + local mon_list = ListBox{parent=parent,y=6,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + + local elem_list = {} ---@type graphics_element[] + + mon_list.register(ps, "radiation", function (data) + local ids = textutils.unserialize(data) + + -- delete any disconnected monitors + for id, elem in pairs(elem_list) do + if util.table_contains(ids, id) then + elem.delete() + elem_list[id] = nil + end + end + + -- add newly connected monitors + for _, id in pairs(ids) do + if not elem_list[id] then + elem_list[id] = Div{parent=mon_list,height=4} + local mon_rect = Rectangle{parent=elem_list[id],height=5,x=2,width=20,border=border(1,colors.gray,true),thin=true,fg_bg=cpair(colors.black,colors.lightGray)} + + TextBox{parent=mon_rect,text="Env. Detector "..id} + local mon_rad = RadIndicator{parent=mon_rect,x=2,label="",format="%13.3f",lu_colors=cpair(colors.gray,colors.gray),width=18} + mon_rad.register(ps, "radiation@" .. id, mon_rad.update) + end + end + end) + end + --#region unit radiation monitors for i = 1, db.facility.num_units do @@ -92,29 +125,7 @@ local function new_view(root) local radiation = RadIndicator{parent=u_div,x=2,label="",format="%17.3f",lu_colors=lu_col,width=21} radiation.register(u_ps, "radiation", radiation.update) - local mon_list = ListBox{parent=u_div,y=6,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} - - -- local last_list = {} - -- local new_list = {} - - mon_list.register(u_ps, "radiation", function () - -- for k, _ in pairs(unit.rad_monitors) do - -- if last_list[k] then last_list[k] = nil end - -- end - - -- re-create due to new items - -- if util.table_len(last_list) > 0 then - mon_list.remove_all() - - for k, v in pairs(unit.rad_monitors) do - local mon_div = Div{parent=mon_list,height=4} - local mon_rect = Rectangle{parent=mon_div,height=5,x=2,width=20,border=border(1,colors.gray,true),thin=true,fg_bg=cpair(colors.black,colors.lightGray)} - TextBox{parent=mon_rect,text="Env. Detector "..k} - local mon_rad = RadIndicator{parent=mon_rect,x=2,label="",format="%13.3f",value=v.radiation,lu_colors=cpair(colors.gray,colors.gray),width=18} - -- radiation.register(u_ps, "radiation", mon_rad.update) - end - -- end - end) + new_mon_list(u_div, u_ps) end --#endregion @@ -159,7 +170,7 @@ local function new_view(root) local f_rad = RadIndicator{parent=f_div,x=2,label="",format="%17.3f",lu_colors=lu_col,width=21} f_rad.register(f_ps, "radiation", f_rad.update) - local f_mon_list = ListBox{parent=f_div,y=6,scroll_height=100,nav_fg_bg=cpair(colors.lightGray,colors.gray),nav_active=cpair(colors.white,colors.gray)} + new_mon_list(f_div, f_ps) --#endregion From a104d8ba836ea8894c6b1fdfb3da3fd5e980f9d5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 21 Jun 2025 10:45:43 -0400 Subject: [PATCH 07/10] #593 radiation app fixes/cleanup --- coordinator/iocontrol.lua | 4 +++- pocket/ui/apps/radiation.lua | 21 ++++++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index fbca953..66e6e5c 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -816,7 +816,9 @@ function iocontrol.update_facility_status(status) max_reading = radiation end - fac.rad_monitors[id] = { radiation = radiation, raw = rad_raw } + if not rtu_faulted then + fac.rad_monitors[id] = { radiation = radiation, raw = rad_raw } + end end if any_conn then diff --git a/pocket/ui/apps/radiation.lua b/pocket/ui/apps/radiation.lua index d0fccd8..8676f13 100644 --- a/pocket/ui/apps/radiation.lua +++ b/pocket/ui/apps/radiation.lua @@ -27,9 +27,8 @@ local border = core.border local APP_ID = pocket.APP_ID -local label_fg_bg = style.label -local lu_col = style.label_unit_pair -local text_fg = style.text_fg +local label_fg_bg = style.label +local lu_col = style.label_unit_pair -- new radiation monitor page view ---@param root Container parent @@ -83,12 +82,12 @@ local function new_view(root) local elem_list = {} ---@type graphics_element[] - mon_list.register(ps, "radiation", function (data) + mon_list.register(ps, "radiation_monitors", function (data) local ids = textutils.unserialize(data) -- delete any disconnected monitors for id, elem in pairs(elem_list) do - if util.table_contains(ids, id) then + if not util.table_contains(ids, id) then elem.delete() elem_list[id] = nil end @@ -97,8 +96,8 @@ local function new_view(root) -- add newly connected monitors for _, id in pairs(ids) do if not elem_list[id] then - elem_list[id] = Div{parent=mon_list,height=4} - local mon_rect = Rectangle{parent=elem_list[id],height=5,x=2,width=20,border=border(1,colors.gray,true),thin=true,fg_bg=cpair(colors.black,colors.lightGray)} + elem_list[id] = Div{parent=mon_list,height=5} + local mon_rect = Rectangle{parent=elem_list[id],height=4,x=2,width=20,border=border(1,colors.gray,true),thin=true,fg_bg=cpair(colors.black,colors.lightGray)} TextBox{parent=mon_rect,text="Env. Detector "..id} local mon_rad = RadIndicator{parent=mon_rect,x=2,label="",format="%13.3f",lu_colors=cpair(colors.gray,colors.gray),width=18} @@ -121,7 +120,7 @@ local function new_view(root) TextBox{parent=u_div,y=1,text="Unit #"..i.." Monitors",alignment=ALIGN.CENTER} - TextBox{parent=u_div,x=2,y=3,text="Max Radiation",fg_bg=style.label} + TextBox{parent=u_div,x=2,y=3,text="Max Radiation",fg_bg=label_fg_bg} local radiation = RadIndicator{parent=u_div,x=2,label="",format="%17.3f",lu_colors=lu_col,width=21} radiation.register(u_ps, "radiation", radiation.update) @@ -140,7 +139,7 @@ local function new_view(root) TextBox{parent=s_div,y=1,text=" Radiation Monitoring",alignment=ALIGN.CENTER} - TextBox{parent=s_div,y=3,text="Max Facility Rad.",fg_bg=style.label} + TextBox{parent=s_div,y=3,text="Max Facility Rad.",fg_bg=label_fg_bg} local s_f_rad = RadIndicator{parent=s_div,label="",format="%17.3f",lu_colors=lu_col,width=21} s_f_rad.register(f_ps, "radiation", s_f_rad.update) @@ -149,7 +148,7 @@ local function new_view(root) local u_ps = unit.unit_ps s_div.line_break() - TextBox{parent=s_div,text="Max Unit "..i.." Radiation",fg_bg=style.label} + TextBox{parent=s_div,text="Max Unit "..i.." Radiation",fg_bg=label_fg_bg} local s_u_rad = RadIndicator{parent=s_div,label="",format="%17.3f",lu_colors=lu_col,width=21} s_u_rad.register(u_ps, "radiation", s_u_rad.update) end @@ -166,7 +165,7 @@ local function new_view(root) TextBox{parent=f_div,y=1,text="Facility Monitors",alignment=ALIGN.CENTER} - TextBox{parent=f_div,x=2,y=3,text="Max Radiation",fg_bg=style.label} + TextBox{parent=f_div,x=2,y=3,text="Max Radiation",fg_bg=label_fg_bg} local f_rad = RadIndicator{parent=f_div,x=2,label="",format="%17.3f",lu_colors=lu_col,width=21} f_rad.register(f_ps, "radiation", f_rad.update) From abe0c455349c2790c0eb188f2e9e9d022c50e3d4 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 21 Jun 2025 14:58:45 -0400 Subject: [PATCH 08/10] cleanup, #593 fixes, version increment --- coordinator/iocontrol.lua | 9 ++++++--- pocket/startup.lua | 2 +- pocket/ui/apps/radiation.lua | 22 +++++++++++----------- scada-common/comms.lua | 4 ++-- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 66e6e5c..9a788db 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -1112,8 +1112,9 @@ function iocontrol.update_unit_statuses(statuses) unit.rad_monitors = {} for id, envd in pairs(rtu_statuses.envds) do - local radiation = envd[2] ---@type radiation_reading - local rad_raw = envd[3] ---@type number + local rtu_faulted = envd[1] ---@type boolean + local radiation = envd[2] ---@type radiation_reading + local rad_raw = envd[3] ---@type number any_conn = true @@ -1122,7 +1123,9 @@ function iocontrol.update_unit_statuses(statuses) max_reading = radiation end - unit.rad_monitors[id] = { radiation = radiation, raw = rad_raw } + if not rtu_faulted then + unit.rad_monitors[id] = { radiation = radiation, raw = rad_raw } + end end if any_conn then diff --git a/pocket/startup.lua b/pocket/startup.lua index 71e2a73..b2f7874 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -22,7 +22,7 @@ local pocket = require("pocket.pocket") local renderer = require("pocket.renderer") local threads = require("pocket.threads") -local POCKET_VERSION = "v0.13.4-beta" +local POCKET_VERSION = "v0.13.5-beta" local println = util.println local println_ts = util.println_ts diff --git a/pocket/ui/apps/radiation.lua b/pocket/ui/apps/radiation.lua index 8676f13..e40cb6d 100644 --- a/pocket/ui/apps/radiation.lua +++ b/pocket/ui/apps/radiation.lua @@ -2,24 +2,24 @@ -- Radiation Monitor App -- -local util = require("scada-common.util") +local util = require("scada-common.util") -local iocontrol = require("pocket.iocontrol") -local pocket = require("pocket.pocket") +local iocontrol = require("pocket.iocontrol") +local pocket = require("pocket.pocket") -local style = require("pocket.ui.style") +local style = require("pocket.ui.style") -local core = require("graphics.core") +local core = require("graphics.core") -local Div = require("graphics.elements.Div") +local Div = require("graphics.elements.Div") local ListBox = require("graphics.elements.ListBox") -local MultiPane = require("graphics.elements.MultiPane") -local Rectangle = require("graphics.elements.Rectangle") -local TextBox = require("graphics.elements.TextBox") +local MultiPane = require("graphics.elements.MultiPane") +local Rectangle = require("graphics.elements.Rectangle") +local TextBox = require("graphics.elements.TextBox") -local WaitingAnim = require("graphics.elements.animations.Waiting") +local WaitingAnim = require("graphics.elements.animations.Waiting") -local RadIndicator = require("graphics.elements.indicators.RadIndicator") +local RadIndicator = require("graphics.elements.indicators.RadIndicator") local ALIGN = core.ALIGN local cpair = core.cpair diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 75a99ad..2f3ef71 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -17,8 +17,8 @@ local max_distance = nil local comms = {} -- protocol/data versions (protocol/data independent changes tracked by util.lua version) -comms.version = "3.0.6" -comms.api_version = "0.0.9" +comms.version = "3.0.7" +comms.api_version = "0.0.10" ---@enum PROTOCOL local PROTOCOL = { From 2998371b890cf73a1d3291a949e3a9185f17f1dd Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Fri, 27 Jun 2025 11:46:50 -0400 Subject: [PATCH 09/10] #623 PLC option to invert emergency coolant control --- reactor-plc/config/system.lua | 13 ++++++++++++- reactor-plc/configure.lua | 5 ++++- reactor-plc/plc.lua | 5 ++++- reactor-plc/startup.lua | 2 +- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/reactor-plc/config/system.lua b/reactor-plc/config/system.lua index 909dda7..594a7e5 100644 --- a/reactor-plc/config/system.lua +++ b/reactor-plc/config/system.lua @@ -82,8 +82,9 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) local plc_c_2 = Div{parent=plc_cfg,x=2,y=4,width=49} local plc_c_3 = Div{parent=plc_cfg,x=2,y=4,width=49} local plc_c_4 = Div{parent=plc_cfg,x=2,y=4,width=49} + local plc_c_5 = Div{parent=plc_cfg,x=2,y=4,width=49} - local plc_pane = MultiPane{parent=plc_cfg,x=1,y=4,panes={plc_c_1,plc_c_2,plc_c_3,plc_c_4}} + local plc_pane = MultiPane{parent=plc_cfg,x=1,y=4,panes={plc_c_1,plc_c_2,plc_c_3,plc_c_4,plc_c_5}} TextBox{parent=plc_cfg,x=1,y=2,text=" PLC Configuration",fg_bg=cpair(colors.black,colors.orange)} @@ -152,13 +153,20 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) function self.bundled_emcool(en) if en then color.enable() else color.disable() end end + TextBox{parent=plc_c_5,x=1,y=1,height=5,text="Advanced Options"} + local invert = Checkbox{parent=plc_c_5,x=1,y=3,label="Invert",default=ini_cfg.EmerCoolInvert,box_fg_bg=cpair(colors.orange,colors.black),callback=function()end} + TextBox{parent=plc_c_5,x=3,y=4,height=4,text="Digital I/O is already inverted (or not) based on intended use. If you have a non-standard setup, you can use this option to avoid needing a redstone inverter.",fg_bg=cpair(colors.gray,colors.lightGray)} + PushButton{parent=plc_c_5,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + local function submit_emcool() tmp_cfg.EmerCoolSide = side_options_map[side.get_value()] tmp_cfg.EmerCoolColor = util.trinary(bundled.get_value(), color_options_map[color.get_value()], nil) + tmp_cfg.EmerCoolInvert = invert.get_value() next_from_plc() end PushButton{parent=plc_c_4,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=plc_c_4,x=33,y=14,min_width=10,text="Advanced",callback=function()plc_pane.set_value(5)end,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} PushButton{parent=plc_c_4,x=44,y=14,text="Next \x1a",callback=submit_emcool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} --#endregion @@ -461,6 +469,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) try_set(side, side_to_idx(ini_cfg.EmerCoolSide)) try_set(bundled, ini_cfg.EmerCoolColor ~= nil) if ini_cfg.EmerCoolColor ~= nil then try_set(color, color_to_idx(ini_cfg.EmerCoolColor)) end + try_set(invert, ini_cfg.EmerCoolInvert) try_set(svr_chan, ini_cfg.SVR_Channel) try_set(plc_chan, ini_cfg.PLC_Channel) try_set(timeout, ini_cfg.ConnTimeout) @@ -533,9 +542,11 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) if tmp_cfg.EmerCoolEnable then tmp_cfg.EmerCoolSide = config.EMERGENCY_COOL.side tmp_cfg.EmerCoolColor = config.EMERGENCY_COOL.color + tmp_cfg.EmerCoolInvert = false else tmp_cfg.EmerCoolSide = nil tmp_cfg.EmerCoolColor = nil + tmp_cfg.EmerCoolInvert = false end tmp_cfg.SVR_Channel = config.SVR_CHANNEL diff --git a/reactor-plc/configure.lua b/reactor-plc/configure.lua index b2baa6d..fbc25c1 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -32,7 +32,8 @@ local changes = { { "v1.6.2", { "AuthKey minimum length is now 8 (if set)" } }, { "v1.6.8", { "ConnTimeout can now have a fractional part" } }, { "v1.6.15", { "Added front panel UI theme", "Added color accessibility modes" } }, - { "v1.7.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } } + { "v1.7.3", { "Added standard with black off state color mode", "Added blue indicator color modes" } }, + { "v1.8.21", { "Added option to invert emergency coolant redstone control" } } } ---@class plc_configurator @@ -76,6 +77,7 @@ local tmp_cfg = { EmerCoolEnable = false, EmerCoolSide = nil, ---@type string|nil EmerCoolColor = nil, ---@type color|nil + EmerCoolInvert = false, ---@type boolean SVR_Channel = nil, ---@type integer PLC_Channel = nil, ---@type integer ConnTimeout = nil, ---@type number @@ -100,6 +102,7 @@ local fields = { { "EmerCoolEnable", "Emergency Coolant", false }, { "EmerCoolSide", "Emergency Coolant Side", nil }, { "EmerCoolColor", "Emergency Coolant Color", nil }, + { "EmerCoolInvert", "Emergency Coolant Invert", false }, { "SVR_Channel", "SVR Channel", 16240 }, { "PLC_Channel", "PLC Channel", 16241 }, { "ConnTimeout", "Connection Timeout", 5 }, diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 73b95ce..a2929cb 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -43,6 +43,7 @@ function plc.load_config() config.EmerCoolEnable = settings.get("EmerCoolEnable") config.EmerCoolSide = settings.get("EmerCoolSide") config.EmerCoolColor = settings.get("EmerCoolColor") + config.EmerCoolInvert = settings.get("EmerCoolInvert") config.SVR_Channel = settings.get("SVR_Channel") config.PLC_Channel = settings.get("PLC_Channel") @@ -98,6 +99,7 @@ function plc.validate_config(cfg) if cfg.EmerCoolEnable then cfv.assert_eq(rsio.is_valid_side(cfg.EmerCoolSide), true) cfv.assert_eq(cfg.EmerCoolColor == nil or rsio.is_color(cfg.EmerCoolColor), true) + cfv.assert_type_bool(cfg.EmerCoolInvert) end return cfv.valid() @@ -166,7 +168,8 @@ function plc.rps_init(reactor, is_formed) local function _set_emer_cool(state) -- check if this was configured: if it's a table, fields have already been validated. if config.EmerCoolEnable then - local level = rsio.digital_write_active(rsio.IO.U_EMER_COOL, state) + -- use ~= as XOR for simple inversion + local level = rsio.digital_write_active(rsio.IO.U_EMER_COOL, config.EmerCoolInvert ~= state) if level ~= false then if rsio.is_color(config.EmerCoolColor) then diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index c085e95..af54333 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.20" +local R_PLC_VERSION = "v1.8.21" local println = util.println local println_ts = util.println_ts From acaa9369f451a5519d95949cca002ec5f541b131 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 29 Jun 2025 16:32:40 -0400 Subject: [PATCH 10/10] added new! tags to reactor PLC configurator --- reactor-plc/config/system.lua | 1 + reactor-plc/startup.lua | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/reactor-plc/config/system.lua b/reactor-plc/config/system.lua index 594a7e5..8d37809 100644 --- a/reactor-plc/config/system.lua +++ b/reactor-plc/config/system.lua @@ -155,6 +155,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) TextBox{parent=plc_c_5,x=1,y=1,height=5,text="Advanced Options"} local invert = Checkbox{parent=plc_c_5,x=1,y=3,label="Invert",default=ini_cfg.EmerCoolInvert,box_fg_bg=cpair(colors.orange,colors.black),callback=function()end} + TextBox{parent=plc_c_5,x=10,y=3,text="new!",fg_bg=cpair(colors.red,colors._INHERIT)} ---@todo remove NEW tag on next revision TextBox{parent=plc_c_5,x=3,y=4,height=4,text="Digital I/O is already inverted (or not) based on intended use. If you have a non-standard setup, you can use this option to avoid needing a redstone inverter.",fg_bg=cpair(colors.gray,colors.lightGray)} PushButton{parent=plc_c_5,x=1,y=14,text="\x1b Back",callback=function()plc_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index af54333..ddf289a 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.21" +local R_PLC_VERSION = "v1.8.22" local println = util.println local println_ts = util.println_ts