diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index f44becb..9a788db 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,10 @@ function iocontrol.update_facility_status(status) max_rad = rad_raw max_reading = radiation end + + if not rtu_faulted then + fac.rad_monitors[id] = { radiation = radiation, raw = rad_raw } + end end if any_conn then @@ -1099,9 +1109,12 @@ 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 - local radiation = envd[2] ---@type radiation_reading - local rad_raw = envd[3] ---@type number + unit.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 any_conn = true @@ -1109,6 +1122,10 @@ function iocontrol.update_unit_statuses(statuses) max_rad = rad_raw max_reading = radiation end + + if not rtu_faulted then + unit.rad_monitors[id] = { radiation = radiation, raw = rad_raw } + end 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..aea9699 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,59 @@ 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] + local max_rad = 0 + local connected = {} + + unit.radiation = types.new_zero_radiation_reading() + unit.rad_monitors = data[u_id] + + 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 + end + end + + unit.unit_ps.publish("radiation", unit.radiation) + unit.unit_ps.publish("radiation_monitors", textutils.serialize(connected)) + 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 + 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 + end + end + + fac.ps.publish("radiation", fac.radiation) + fac.ps.publish("radiation_monitors", textutils.serialize(connected)) +end + return function (io_obj) io = io_obj return iorx diff --git a/pocket/pocket.lua b/pocket/pocket.lua index 55cc829..cfa435a 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 @@ -369,8 +370,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 @@ -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/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 new file mode 100644 index 0000000..e40cb6d --- /dev/null +++ b/pocket/ui/apps/radiation.lua @@ -0,0 +1,219 @@ +-- +-- Radiation Monitor App +-- + +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 Rectangle = require("graphics.elements.Rectangle") +local TextBox = require("graphics.elements.TextBox") + +local WaitingAnim = require("graphics.elements.animations.Waiting") + +local RadIndicator = require("graphics.elements.indicators.RadIndicator") + +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 lu_col = style.label_unit_pair + +-- 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 + + -- 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_monitors", function (data) + local ids = textutils.unserialize(data) + + -- delete any disconnected monitors + for id, elem in pairs(elem_list) do + if not 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=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} + 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 + local u_pane = panes[i] + local u_div = Div{parent=u_pane} + 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,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) + + new_mon_list(u_div, u_ps) + 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 Monitoring",alignment=ALIGN.CENTER} + + 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) + + 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=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 + + --#endregion + + --#region overview page + + local f_pane = panes[db.facility.num_units + 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=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) + + new_mon_list(f_div, f_ps) + + --#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 diff --git a/scada-common/comms.lua b/scada-common/comms.lua index f827ab8..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 = { @@ -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