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/reactor-plc/config/system.lua b/reactor-plc/config/system.lua index 909dda7..8d37809 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,21 @@ 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=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} + 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 +470,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 +543,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..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.20" +local R_PLC_VERSION = "v1.8.22" local println = util.println local println_ts = util.println_ts 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 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