diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index be76052..7108c01 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -105,6 +105,7 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale) auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM, auto_pu_fallback_active = false, auto_sps_disabled = false, + waste_stats = { 0, 0, 0, 0, 0, 0 }, -- waste in, pu, po, po pellets, am, spent waste radiation = types.new_zero_radiation_reading(), @@ -118,6 +119,7 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale) induction_ps_tbl = {}, ---@type psil[] induction_data_tbl = {}, ---@type imatrix_session_db[] + sps_status = 1, sps_ps_tbl = {}, ---@type psil[] sps_data_tbl = {}, ---@type sps_session_db[] @@ -174,6 +176,7 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale) waste_mode = types.WASTE_MODE.MANUAL_PLUTONIUM, waste_product = types.WASTE_PRODUCT.PLUTONIUM, + waste_stats = { 0, 0, 0 }, -- plutonium, polonium, po pellets last_rate_change_ms = 0, turbine_flow_stable = false, @@ -662,6 +665,8 @@ function iocontrol.update_facility_status(status) -- SPS statuses if type(rtu_statuses.sps) == "table" then + local sps_status = 1 + for id = 1, #fac.sps_ps_tbl do if rtu_statuses.sps[id] == nil then -- disconnected @@ -677,22 +682,21 @@ function iocontrol.update_facility_status(status) local rtu_faulted = _record_multiblock_status(sps, data, ps) if rtu_faulted then - ps.publish("computed_status", 3) -- faulted + sps_status = 3 -- faulted elseif data.formed then - if data.state.process_rate > 0 then - ps.publish("computed_status", 5) -- active - else - ps.publish("computed_status", 4) -- idle - end - else - ps.publish("computed_status", 2) -- not formed - end + -- active / idle + sps_status = util.trinary(data.state.process_rate > 0, 5, 4) + else sps_status = 2 end -- not formed + + ps.publish("computed_status", sps_status) io.facility.ps.publish("am_rate", data.state.process_rate * 1000) else log.debug(util.c(log_header, "invalid sps id ", id)) end end + + io.facility.sps_status = sps_status else log.debug(log_header .. "sps list not a table") valid = false @@ -1192,6 +1196,7 @@ function iocontrol.update_unit_statuses(statuses) local u_spent_rate = waste_rate local u_pu_rate = util.trinary(is_pu, waste_rate, 0.0) local u_po_rate = unit.sna_out_rate + local u_po_pl_rate = 0 unit.unit_ps.publish("pu_rate", u_pu_rate) unit.unit_ps.publish("po_rate", u_po_rate) @@ -1202,6 +1207,7 @@ function iocontrol.update_unit_statuses(statuses) u_spent_rate = u_po_rate unit.unit_ps.publish("po_pl_rate", u_po_rate) unit.unit_ps.publish("po_am_rate", 0) + u_po_pl_rate = u_po_rate po_pl_rate = po_pl_rate + u_po_rate elseif unit.waste_product == types.WASTE_PRODUCT.ANTI_MATTER then u_spent_rate = 0 @@ -1213,6 +1219,8 @@ function iocontrol.update_unit_statuses(statuses) unit.unit_ps.publish("po_am_rate", 0) end + unit.waste_stats = { u_pu_rate, u_po_rate, u_po_pl_rate } + unit.unit_ps.publish("ws_rate", u_spent_rate) pu_rate = pu_rate + u_pu_rate @@ -1221,6 +1229,8 @@ function iocontrol.update_unit_statuses(statuses) end end + io.facility.waste_stats = { burn_rate_sum, pu_rate, po_rate, po_pl_rate, po_am_rate, spent_rate } + io.facility.ps.publish("burn_sum", burn_rate_sum) io.facility.ps.publish("sna_count", sna_count_sum) io.facility.ps.publish("pu_rate", pu_rate) diff --git a/coordinator/process.lua b/coordinator/process.lua index 78be172..1f2b914 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -445,36 +445,21 @@ end ---@param product WASTE_PRODUCT waste product for auto control function process.set_process_waste(product) pctl.comms.send_fac_command(F_CMD.SET_WASTE_MODE, product) - log.debug(util.c("PROCESS: SET WASTE ", product)) - - -- update config table and save - pctl.control_states.process.waste_product = product - _write_auto_config() end -- set automatic process control plutonium fallback ---@param enabled boolean whether to enable plutonium fallback function process.set_pu_fallback(enabled) pctl.comms.send_fac_command(F_CMD.SET_PU_FB, enabled) - log.debug(util.c("PROCESS: SET PU FALLBACK ", enabled)) - - -- update config table and save - pctl.control_states.process.pu_fallback = enabled - _write_auto_config() end -- set automatic process control SPS usage at low power ---@param enabled boolean whether to enable SPS usage at low power function process.set_sps_low_power(enabled) pctl.comms.send_fac_command(F_CMD.SET_SPS_LP, enabled) - log.debug(util.c("PROCESS: SET SPS LOW POWER ", enabled)) - - -- update config table and save - pctl.control_states.process.sps_low_power = enabled - _write_auto_config() end -- save process control settings @@ -527,21 +512,30 @@ end -- record waste product settting after attempting to change it ---@param response WASTE_PRODUCT supervisor waste product settting function process.waste_ack_handle(response) + -- update config table and save pctl.control_states.process.waste_product = response + _write_auto_config() + pctl.io.facility.ps.publish("process_waste_product", response) end -- record plutonium fallback settting after attempting to change it ---@param response boolean supervisor plutonium fallback settting function process.pu_fb_ack_handle(response) + -- update config table and save pctl.control_states.process.pu_fallback = response + _write_auto_config() + pctl.io.facility.ps.publish("process_pu_fallback", response) end -- record SPS low power settting after attempting to change it ---@param response boolean supervisor SPS low power settting function process.sps_lp_ack_handle(response) + -- update config table and save pctl.control_states.process.sps_low_power = response + _write_auto_config() + pctl.io.facility.ps.publish("process_sps_low_power", response) end diff --git a/coordinator/session/pocket.lua b/coordinator/session/pocket.lua index cb38a61..6d60ab0 100644 --- a/coordinator/session/pocket.lua +++ b/coordinator/session/pocket.lua @@ -1,6 +1,7 @@ local comms = require("scada-common.comms") local log = require("scada-common.log") local mqueue = require("scada-common.mqueue") +local types = require("scada-common.types") local util = require("scada-common.util") local iocontrol = require("coordinator.iocontrol") @@ -14,6 +15,9 @@ local MGMT_TYPE = comms.MGMT_TYPE local FAC_COMMAND = comms.FAC_COMMAND local UNIT_COMMAND = comms.UNIT_COMMAND +local AUTO_GROUP = types.AUTO_GROUP +local WASTE_MODE = types.WASTE_MODE + -- retry time constants in ms -- local INITIAL_WAIT = 1500 -- local RETRY_PERIOD = 1000 @@ -166,8 +170,26 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) log.info(log_tag .. "FAC ACK ALL ALARMS") self.proc_handle.fac_ack_alarms() elseif cmd == FAC_COMMAND.SET_WASTE_MODE then + if pkt.length == 2 then + log.info(util.c(log_tag, " SET WASTE ", pkt.data[2])) + process.set_process_waste(pkt.data[2]) + else + log.debug(log_tag .. "CRDN set waste mode packet length mismatch") + end elseif cmd == FAC_COMMAND.SET_PU_FB then + if pkt.length == 2 then + log.info(util.c(log_tag, " SET PU FALLBACK ", pkt.data[2])) + process.set_pu_fallback(pkt.data[2] == true) + else + log.debug(log_tag .. "CRDN set pu fallback packet length mismatch") + end elseif cmd == FAC_COMMAND.SET_SPS_LP then + if pkt.length == 2 then + log.info(util.c(log_tag, " SET SPS LOW POWER ", pkt.data[2])) + process.set_sps_low_power(pkt.data[2] == true) + else + log.debug(log_tag .. "CRDN set sps low power packet length mismatch") + end else log.debug(log_tag .. "CRDN facility command unknown") end @@ -192,20 +214,28 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) log.info(util.c(log_tag, "UNIT[", uid, "] RESET RPS")) self.proc_handle.reset_rps(uid) elseif cmd == UNIT_COMMAND.SET_BURN then - if pkt.length == 3 then + if (pkt.length == 3) and (type(pkt.data[3]) == "number") then log.info(util.c(log_tag, "UNIT[", uid, "] SET BURN ", pkt.data[3])) process.set_rate(uid, pkt.data[3]) else log.debug(log_tag .. "CRDN unit command burn rate missing option") end elseif cmd == UNIT_COMMAND.SET_WASTE then + if (pkt.length == 3) and (type(pkt.data[3]) == "number") and + (pkt.data[3] >= WASTE_MODE.AUTO) and (pkt.data[3] <= WASTE_MODE.MANUAL_ANTI_MATTER) then + log.info(util.c(log_tag, "UNIT[", id, "] SET WASTE ", pkt.data[3])) + process.set_unit_waste(uid, pkt.data[3]) + else + log.debug(log_tag .. "CRDN unit command set waste missing/invalid option") + end elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then log.info(util.c(log_tag, "UNIT[", uid, "] ACK ALL ALARMS")) self.proc_handle.ack_all_alarms(uid) elseif cmd == UNIT_COMMAND.ACK_ALARM then elseif cmd == UNIT_COMMAND.RESET_ALARM then elseif cmd == UNIT_COMMAND.SET_GROUP then - if pkt.length == 3 then + if (pkt.length == 3) and (type(pkt.data[3]) == "number") and + (pkt.data[3] >= AUTO_GROUP.MANUAL) and (pkt.data[3] <= AUTO_GROUP.BACKUP) then log.info(util.c(log_tag, "UNIT[", uid, "] SET GROUP ", pkt.data[3])) process.set_group(uid, pkt.data[3]) else @@ -275,7 +305,6 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) u.annunciator.AutoControl, u.a_group } - end _send(CRDN_TYPE.API_GET_CTRL, data) @@ -310,6 +339,47 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout) } _send(CRDN_TYPE.API_GET_PROC, data) + elseif pkt.type == CRDN_TYPE.API_GET_WASTE then + local data = {} + + local fac = db.facility + local proc = process.get_control_states().process + + -- unit data + for i = 1, #db.units do + local u = db.units[i] + + data[i] = { + u.waste_mode, + u.waste_product, + u.num_snas, + u.sna_peak_rate, + u.sna_max_rate, + u.sna_out_rate, + u.waste_stats + } + end + + local process_rate = 0 + + if fac.sps_data_tbl[1].state then + process_rate = fac.sps_data_tbl[1].state.process_rate + end + + -- facility data + data[#db.units + 1] = { + fac.auto_current_waste_product, + fac.auto_pu_fallback_active, + fac.auto_sps_disabled, + proc.waste_product, + proc.pu_fallback, + proc.sps_low_power, + fac.waste_stats, + fac.sps_status, + process_rate + } + + _send(CRDN_TYPE.API_GET_WASTE, 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 b6ee0ed..52ce676 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") local threads = require("coordinator.threads") -local COORDINATOR_VERSION = "v1.5.15" +local COORDINATOR_VERSION = "v1.5.16" local CHUNK_LOAD_DELAY_S = 30.0 diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index 41f6aef..0a3af09 100644 --- a/pocket/iocontrol.lua +++ b/pocket/iocontrol.lua @@ -94,7 +94,8 @@ function iocontrol.init_core(pkt_comms, nav, cfg) io.api = { 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_proc = function () comms.api__get_process() end, + get_waste = function () comms.api__get_waste() end } end @@ -158,6 +159,8 @@ function iocontrol.init_fac(conf) ---@type WASTE_PRODUCT auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM, auto_pu_fallback_active = false, + auto_sps_disabled = false, + waste_stats = { 0, 0, 0, 0, 0, 0 }, -- waste in, pu, po, po pellets, am, spent waste radiation = types.new_zero_radiation_reading(), @@ -217,6 +220,7 @@ function iocontrol.init_fac(conf) last_rate_change_ms = 0, turbine_flow_stable = false, + waste_stats = { 0, 0, 0 }, -- plutonium, polonium, po pellets -- auto control group a_group = types.AUTO_GROUP.MANUAL, @@ -920,6 +924,65 @@ function iocontrol.record_process_data(data) fac.ps.publish("process_gen_target", f_data[5][4]) end +-- update waste app with unit data from API_GET_WASTE +---@param data table +function iocontrol.record_waste_data(data) + -- get unit data + for u_id = 1, #io.units do + local unit = io.units[u_id] + local u_data = data[u_id] + + unit.waste_mode = u_data[1] + unit.waste_product = u_data[2] + unit.num_snas = u_data[3] + unit.sna_peak_rate = u_data[4] + unit.sna_max_rate = u_data[5] + unit.sna_out_rate = u_data[6] + unit.waste_stats = u_data[7] + + unit.unit_ps.publish("U_AutoWaste", unit.waste_mode == types.WASTE_MODE.AUTO) + unit.unit_ps.publish("U_WasteMode", unit.waste_mode) + unit.unit_ps.publish("U_WasteProduct", unit.waste_product) + + unit.unit_ps.publish("sna_count", unit.num_snas) + unit.unit_ps.publish("sna_peak_rate", unit.sna_peak_rate) + unit.unit_ps.publish("sna_max_rate", unit.sna_max_rate) + unit.unit_ps.publish("sna_out_rate", unit.sna_out_rate) + + unit.unit_ps.publish("pu_rate", unit.waste_stats[1]) + unit.unit_ps.publish("po_rate", unit.waste_stats[2]) + unit.unit_ps.publish("po_pl_rate", unit.waste_stats[3]) + end + + -- get facility data + local fac = io.facility + local f_data = data[#io.units + 1] + + fac.auto_current_waste_product = f_data[1] + fac.auto_pu_fallback_active = f_data[2] + fac.auto_sps_disabled = f_data[3] + + fac.ps.publish("current_waste_product", fac.auto_current_waste_product) + fac.ps.publish("pu_fallback_active", fac.auto_pu_fallback_active) + fac.ps.publish("sps_disabled_low_power", fac.auto_sps_disabled) + + fac.ps.publish("process_waste_product", f_data[4]) + fac.ps.publish("process_pu_fallback", f_data[5]) + fac.ps.publish("process_sps_low_power", f_data[6]) + + fac.waste_stats = f_data[7] + + fac.ps.publish("burn_sum", fac.waste_stats[1]) + fac.ps.publish("pu_rate", fac.waste_stats[2]) + fac.ps.publish("po_rate", fac.waste_stats[3]) + fac.ps.publish("po_pl_rate", fac.waste_stats[4]) + fac.ps.publish("po_am_rate", fac.waste_stats[5]) + fac.ps.publish("spent_waste_rate", fac.waste_stats[6]) + + fac.ps.publish("sps_computed_status", f_data[8]) + fac.ps.publish("sps_process_rate", f_data[9]) +end + -- get the IO controller database function iocontrol.get_db() return io end diff --git a/pocket/pocket.lua b/pocket/pocket.lua index de6e6b2..461f57a 100644 --- a/pocket/pocket.lua +++ b/pocket/pocket.lua @@ -89,13 +89,14 @@ local APP_ID = { UNITS = 3, CONTROL = 4, PROCESS = 5, - GUIDE = 6, - ABOUT = 7, + WASTE = 6, + GUIDE = 7, + ABOUT = 8, -- diagnostic app pages - ALARMS = 8, + ALARMS = 9, -- other - DUMMY = 9, - NUM_APPS = 9 + DUMMY = 10, + NUM_APPS = 10 } pocket.APP_ID = APP_ID @@ -264,7 +265,8 @@ function pocket.init_nav(smem) -- open an app ---@param app_id POCKET_APP_ID - function nav.open_app(app_id) + ---@param on_loaded? function + function nav.open_app(app_id, on_loaded) -- reset help return on navigating out of an app if app_id == APP_ID.ROOT then self.help_return = nil end @@ -277,7 +279,7 @@ function pocket.init_nav(smem) app = self.apps[app_id] else self.loader_return = nil end - if not app.loaded then smem.q.mq_render.push_data(MQ__RENDER_DATA.LOAD_APP, app_id) end + if not app.loaded then smem.q.mq_render.push_data(MQ__RENDER_DATA.LOAD_APP, { app_id, on_loaded }) end self.cur_app = app_id self.pane.set_value(app_id) @@ -360,10 +362,10 @@ function pocket.init_nav(smem) function nav.open_help(key) self.help_return = self.cur_app - nav.open_app(APP_ID.GUIDE) - - local load = self.help_map[key] - if load then load() end + nav.open_app(APP_ID.GUIDE, function () + local show = self.help_map[key] + if show then show() end + end) end -- link the help map from the guide app @@ -565,6 +567,11 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) if self.api.linked then _send_api(CRDN_TYPE.API_GET_PROC, {}) end end + -- coordinator get waste app data + function public.api__get_waste() + if self.api.linked then _send_api(CRDN_TYPE.API_GET_WASTE, {}) end + end + -- send a facility command ---@param cmd FAC_COMMAND command ---@param option any? optional option options for the optional options (like waste mode) @@ -733,6 +740,10 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav) if _check_length(packet, #iocontrol.get_db().units + 1) then iocontrol.record_process_data(packet.data) end + elseif packet.type == CRDN_TYPE.API_GET_WASTE then + if _check_length(packet, #iocontrol.get_db().units + 1) then + iocontrol.record_waste_data(packet.data) + end else _fail_type(packet) end else log.debug("discarding coordinator SCADA_CRDN packet before linked") diff --git a/pocket/process.lua b/pocket/process.lua index d0a3241..cada3da 100644 --- a/pocket/process.lua +++ b/pocket/process.lua @@ -85,6 +85,14 @@ function process.set_group(unit_id, group_id) log.debug(util.c("PROCESS: UNIT[", unit_id, "] SET GROUP ", group_id)) end +-- set waste mode +---@param id integer unit ID +---@param mode integer waste mode +function process.set_unit_waste(id, mode) + self.comms.send_unit_command(U_CMD.SET_WASTE, id, mode) + log.debug(util.c("PROCESS: UNIT[", id, "] SET WASTE ", mode)) +end + -- acknowledge all alarms ---@param id integer unit ID function process.ack_all_alarms(id) @@ -131,6 +139,27 @@ function process.process_stop() log.debug("PROCESS: STOP AUTO CTRL") end +-- set automatic process control waste mode +---@param product WASTE_PRODUCT waste product for auto control +function process.set_process_waste(product) + self.comms.send_fac_command(F_CMD.SET_WASTE_MODE, product) + log.debug(util.c("PROCESS: SET WASTE ", product)) +end + +-- set automatic process control plutonium fallback +---@param enabled boolean whether to enable plutonium fallback +function process.set_pu_fallback(enabled) + self.comms.send_fac_command(F_CMD.SET_PU_FB, enabled) + log.debug(util.c("PROCESS: SET PU FALLBACK ", enabled)) +end + +-- set automatic process control SPS usage at low power +---@param enabled boolean whether to enable SPS usage at low power +function process.set_sps_low_power(enabled) + self.comms.send_fac_command(F_CMD.SET_SPS_LP, enabled) + log.debug(util.c("PROCESS: SET SPS LOW POWER ", enabled)) +end + -- #endregion --------------------------------- diff --git a/pocket/startup.lua b/pocket/startup.lua index 29a84e3..0eea7b0 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -20,7 +20,7 @@ local pocket = require("pocket.pocket") local renderer = require("pocket.renderer") local threads = require("pocket.threads") -local POCKET_VERSION = "v0.12.9-alpha" +local POCKET_VERSION = "v0.12.10-alpha" local println = util.println local println_ts = util.println_ts diff --git a/pocket/threads.lua b/pocket/threads.lua index 32120b2..e5d0ea7 100644 --- a/pocket/threads.lua +++ b/pocket/threads.lua @@ -165,15 +165,18 @@ function threads.thread__render(smem) local cmd = msg.message ---@type queue_data if cmd.key == MQ__RENDER_DATA.LOAD_APP then - log.debug("RENDER: load app " .. cmd.val) + log.debug("RENDER: load app " .. cmd.val[1]) local draw_start = util.time_ms() - pkt_state.ui_ok, pkt_state.ui_error = pcall(function () nav.load_app(cmd.val) end) + pkt_state.ui_ok, pkt_state.ui_error = pcall(function () nav.load_app(cmd.val[1]) end) if not pkt_state.ui_ok then log.fatal(util.c("RENDER: app load failed with error ", pkt_state.ui_error)) else log.debug("RENDER: app loaded in " .. (util.time_ms() - draw_start) .. "ms") + + -- call the on loaded function if provided + if type(cmd.val[2]) == "function" then cmd.val[2]() end end end elseif msg.qtype == mqueue.TYPE.PACKET then diff --git a/pocket/ui/apps/waste.lua b/pocket/ui/apps/waste.lua new file mode 100644 index 0000000..24e62ae --- /dev/null +++ b/pocket/ui/apps/waste.lua @@ -0,0 +1,310 @@ +-- +-- Waste Control Page +-- + +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 TextBox = require("graphics.elements.TextBox") + +local WaitingAnim = require("graphics.elements.animations.Waiting") + +local Checkbox = require("graphics.elements.controls.Checkbox") +local PushButton = require("graphics.elements.controls.PushButton") +local RadioButton = require("graphics.elements.controls.RadioButton") + +local DataIndicator = require("graphics.elements.indicators.DataIndicator") +local IconIndicator = require("graphics.elements.indicators.IconIndicator") +local StateIndicator = require("graphics.elements.indicators.StateIndicator") + +local ALIGN = core.ALIGN +local cpair = core.cpair + +local APP_ID = pocket.APP_ID + +local label_fg_bg = style.label +local text_fg = style.text_fg + +local lu_col = style.label_unit_pair + +local yel_ind_s = style.icon_states.yel_ind_s +local wht_ind_s = style.icon_states.wht_ind_s + +-- new waste control 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.WASTE, 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.brown,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[] + local u_pages = {} ---@type nav_tree_page[] + + 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_waste() + last_update = util.time_ms() + end + end + + --#region unit waste options/statistics + + for i = 1, db.facility.num_units do + local u_pane = Div{parent=page_div} + 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 + + table.insert(panes, u_div) + + local u_page = app.new_page(nil, #panes) + u_page.tasks = { update } + + table.insert(u_pages, u_page) + + TextBox{parent=u_div,y=1,text="Reactor Unit #"..i,alignment=ALIGN.CENTER} + + local function set_waste(mode) process.set_unit_waste(i, mode) end + + local waste_prod = StateIndicator{parent=u_div,x=16,y=3,states=style.waste.states_abbrv,value=1,min_width=6} + local waste_mode = RadioButton{parent=u_div,y=3,options=style.waste.unit_opts,callback=set_waste,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.white} + + waste_prod.register(u_ps, "U_WasteProduct", waste_prod.update) + waste_mode.register(u_ps, "U_WasteMode", waste_mode.set_value) + + TextBox{parent=u_div,y=8,text="Plutonium (Pellets)",fg_bg=label_fg_bg} + local pu = DataIndicator{parent=u_div,label="",format="%16.3f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg} + TextBox{parent=u_div,y=11,text="Polonium",fg_bg=label_fg_bg} + local po = DataIndicator{parent=u_div,label="",format="%16.3f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg} + TextBox{parent=u_div,y=14,text="Polonium (Pellets)",fg_bg=label_fg_bg} + local popl = DataIndicator{parent=u_div,label="",format="%16.3f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg} + + pu.register(u_ps, "pu_rate", pu.update) + po.register(u_ps, "po_rate", po.update) + popl.register(u_ps, "po_pl_rate", popl.update) + + local sna_div = Div{parent=u_pane,x=2,width=page_div.get_width()-2} + table.insert(panes, sna_div) + + local sps_page = app.new_page(u_page, #panes) + sps_page.tasks = { update } + + PushButton{parent=u_div,x=6,y=18,text="SNA DATA",min_width=12,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=sps_page.nav_to} + PushButton{parent=sna_div,x=9,y=18,text="BACK",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=u_page.nav_to} + + TextBox{parent=sna_div,y=1,text="Unit "..i.." SNAs",alignment=ALIGN.CENTER} + TextBox{parent=sna_div,y=3,text="Connected",fg_bg=label_fg_bg} + local count = DataIndicator{parent=sna_div,x=20,y=3,label="",format="%2d",value=0,unit="",lu_colors=lu_col,width=2,fg_bg=text_fg} + + TextBox{parent=sna_div,y=5,text="Peak Possible Rate\n In\n Out",fg_bg=label_fg_bg} + local peak_i = DataIndicator{parent=sna_div,x=6,y=6,label="",format="%11.2f",value=0,unit="mB/t",lu_colors=lu_col,width=17,fg_bg=text_fg} + local peak_o = DataIndicator{parent=sna_div,x=6,label="",format="%11.2f",value=0,unit="mB/t",lu_colors=lu_col,width=17,fg_bg=text_fg} + + TextBox{parent=sna_div,y=9,text="Current Maximum Rate\n In\n Out",fg_bg=label_fg_bg} + local max_i = DataIndicator{parent=sna_div,x=6,y=10,label="",format="%11.2f",value=0,unit="mB/t",lu_colors=lu_col,width=17,fg_bg=text_fg} + local max_o = DataIndicator{parent=sna_div,x=6,label="",format="%11.2f",value=0,unit="mB/t",lu_colors=lu_col,width=17,fg_bg=text_fg} + + TextBox{parent=sna_div,y=13,text="Current Rate\n In\n Out",fg_bg=label_fg_bg} + local cur_i = DataIndicator{parent=sna_div,x=6,y=14,label="",format="%11.2f",value=0,unit="mB/t",lu_colors=lu_col,width=17,fg_bg=text_fg} + local cur_o = DataIndicator{parent=sna_div,x=6,label="",format="%11.2f",value=0,unit="mB/t",lu_colors=lu_col,width=17,fg_bg=text_fg} + + count.register(u_ps, "sna_count", count.update) + peak_i.register(u_ps, "sna_peak_rate", function (x) peak_i.update(x * 10) end) + peak_o.register(u_ps, "sna_peak_rate", peak_o.update) + max_i.register(u_ps, "sna_max_rate", function (x) max_i.update(x * 10) end) + max_o.register(u_ps, "sna_max_rate", max_o.update) + cur_i.register(u_ps, "sna_out_rate", function (x) cur_i.update(x * 10) end) + cur_o.register(u_ps, "sna_out_rate", cur_o.update) + end + + --#endregion + + --#region waste control page + + local c_pane = Div{parent=page_div} + local c_div = Div{parent=c_pane,x=2,width=main.get_width()-2} + table.insert(panes, c_div) + + local wst_ctrl = app.new_page(nil, #panes) + wst_ctrl.tasks = { update } + + TextBox{parent=c_div,y=1,text="Waste Control",alignment=ALIGN.CENTER} + + local status = StateIndicator{parent=c_div,x=3,y=3,states=style.waste.states,value=1,min_width=17} + local waste_prod = RadioButton{parent=c_div,y=5,options=style.waste.options,callback=process.set_process_waste,radio_colors=cpair(colors.lightGray,colors.gray),select_color=colors.white} + + status.register(f_ps, "current_waste_product", status.update) + waste_prod.register(f_ps, "process_waste_product", waste_prod.set_value) + + local fb_active = IconIndicator{parent=c_div,y=9,label="Fallback Active",states=wht_ind_s} + local sps_disabled = IconIndicator{parent=c_div,y=10,label="SPS Disabled LC",states=yel_ind_s} + + fb_active.register(f_ps, "pu_fallback_active", fb_active.update) + sps_disabled.register(f_ps, "sps_disabled_low_power", sps_disabled.update) + + TextBox{parent=c_div,y=12,text="Nuclear Waste In",fg_bg=label_fg_bg} + local sum_raw_waste = DataIndicator{parent=c_div,label="",format="%16.3f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg} + + sum_raw_waste.register(f_ps, "burn_sum", sum_raw_waste.update) + + TextBox{parent=c_div,y=15,text="Spent Waste Out",fg_bg=label_fg_bg} + local sum_sp_waste = DataIndicator{parent=c_div,label="",format="%16.3f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg} + + sum_sp_waste.register(f_ps, "spent_waste_rate", sum_sp_waste.update) + + local stats_div = Div{parent=c_pane,x=2,width=page_div.get_width()-2} + table.insert(panes, stats_div) + + local stats_page = app.new_page(wst_ctrl, #panes) + stats_page.tasks = { update } + + PushButton{parent=c_div,x=6,y=18,text="PROD RATES",min_width=12,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=stats_page.nav_to} + PushButton{parent=stats_div,x=9,y=18,text="BACK",min_width=6,fg_bg=cpair(colors.lightGray,colors.gray),active_fg_bg=cpair(colors.gray,colors.lightGray),callback=wst_ctrl.nav_to} + + TextBox{parent=stats_div,y=1,text="Production Rates",alignment=ALIGN.CENTER} + + TextBox{parent=stats_div,y=3,text="Plutonium (Pellets)",fg_bg=label_fg_bg} + local pu = DataIndicator{parent=stats_div,label="",format="%16.3f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg} + TextBox{parent=stats_div,y=6,text="Polonium",fg_bg=label_fg_bg} + local po = DataIndicator{parent=stats_div,label="",format="%16.3f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg} + TextBox{parent=stats_div,y=9,text="Polonium (Pellets)",fg_bg=label_fg_bg} + local popl = DataIndicator{parent=stats_div,label="",format="%16.3f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg} + + pu.register(f_ps, "pu_rate", pu.update) + po.register(f_ps, "po_rate", po.update) + popl.register(f_ps, "po_pl_rate", popl.update) + + TextBox{parent=stats_div,y=12,text="Antimatter",fg_bg=label_fg_bg} + local am = DataIndicator{parent=stats_div,label="",format="%16d",value=0,unit="\xb5B/t",lu_colors=lu_col,width=21,fg_bg=text_fg} + + am.register(f_ps, "sps_process_rate", function (r) am.update(r * 1000) end) + + --#endregion + + --#region waste options page + + local o_pane = Div{parent=page_div} + local o_div = Div{parent=o_pane,x=2,width=main.get_width()-2} + table.insert(panes, o_pane) + + local opt_page = app.new_page(nil, #panes) + opt_page.tasks = { update } + + TextBox{parent=o_div,y=1,text="Waste Options",alignment=ALIGN.CENTER} + + local pu_fallback = Checkbox{parent=o_div,x=2,y=3,label="Pu Fallback",callback=process.set_pu_fallback,box_fg_bg=cpair(colors.white,colors.gray)} + + TextBox{parent=o_div,x=2,y=5,height=3,text="Switch to Pu when SNAs cannot keep up with waste.",fg_bg=label_fg_bg} + + local lc_sps = Checkbox{parent=o_div,x=2,y=9,label="Low Charge SPS",callback=process.set_sps_low_power,box_fg_bg=cpair(colors.white,colors.gray)} + + TextBox{parent=o_div,x=2,y=11,height=3,text="Use SPS at low charge, otherwise switches to Po.",fg_bg=label_fg_bg} + + pu_fallback.register(f_ps, "process_pu_fallback", pu_fallback.set_value) + lc_sps.register(f_ps, "process_sps_low_power", lc_sps.set_value) + + --#endregion + + --#region SPS page + + local s_pane = Div{parent=page_div} + local s_div = Div{parent=s_pane,x=2,width=main.get_width()-2} + table.insert(panes, s_pane) + + local sps_page = app.new_page(nil, #panes) + sps_page.tasks = { update } + + TextBox{parent=s_div,y=1,text="Facility SPS",alignment=ALIGN.CENTER} + + local sps_status = StateIndicator{parent=s_div,x=5,y=3,states=style.sps.states,value=1,min_width=12} + + sps_status.register(f_ps, "sps_computed_status", sps_status.update) + + TextBox{parent=s_div,y=5,text="Input Rate",width=10,fg_bg=label_fg_bg} + local sps_in = DataIndicator{parent=s_div,label="",format="%16.2f",value=0,unit="mB/t",lu_colors=lu_col,width=21,fg_bg=text_fg} + + sps_in.register(f_ps, "po_am_rate", sps_in.update) + + TextBox{parent=s_div,y=8,text="Production Rate",width=15,fg_bg=label_fg_bg} + local sps_rate = DataIndicator{parent=s_div,label="",format="%16d",value=0,unit="\xb5B/t",lu_colors=lu_col,width=21,fg_bg=text_fg} + + sps_rate.register(f_ps, "sps_process_rate", function (r) sps_rate.update(r * 1000) end) + + --#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 = "WST", color = core.cpair(colors.black, colors.brown), callback = wst_ctrl.nav_to }, + { label = "OPT", color = core.cpair(colors.black, colors.white), callback = opt_page.nav_to }, + { label = "SPS", color = core.cpair(colors.black, colors.purple), callback = sps_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 = u_pages[i].nav_to }) + end + + app.set_sidebar(list) + + -- done, show the app + wst_ctrl.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 1596485..99b6ab3 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -15,6 +15,7 @@ local loader_app = require("pocket.ui.apps.loader") local process_app = require("pocket.ui.apps.process") 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") local home_page = require("pocket.ui.pages.home_page") @@ -66,6 +67,7 @@ local function init(main) unit_app(page_div) control_app(page_div) process_app(page_div) + waste_app(page_div) guide_app(page_div) loader_app(page_div) sys_apps(page_div) diff --git a/pocket/ui/pages/home_page.lua b/pocket/ui/pages/home_page.lua index 1e478dc..345c9ce 100644 --- a/pocket/ui/pages/home_page.lua +++ b/pocket/ui/pages/home_page.lua @@ -49,7 +49,7 @@ local function new_view(root) App{parent=apps_1,x=9,y=2,text="F",title="Facil",callback=function()open(APP_ID.DUMMY)end,app_fg_bg=cpair(colors.black,colors.orange),active_fg_bg=active_fg_bg} App{parent=apps_1,x=16,y=2,text="\x15",title="Control",callback=function()open(APP_ID.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.DUMMY)end,app_fg_bg=cpair(colors.black,colors.brown),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} diff --git a/pocket/ui/style.lua b/pocket/ui/style.lua index ff7fc9b..aba5d19 100644 --- a/pocket/ui/style.lua +++ b/pocket/ui/style.lua @@ -214,4 +214,66 @@ style.imatrix = { } } +style.sps = { + -- SPS states + states = { + { + color = cpair(colors.black, colors.yellow), + text = "OFF-LINE" + }, + { + color = cpair(colors.black, colors.orange), + text = "NOT FORMED" + }, + { + color = cpair(colors.black, colors.orange), + text = "RTU FAULT" + }, + { + color = cpair(colors.white, colors.gray), + text = "IDLE" + }, + { + color = cpair(colors.black, colors.green), + text = "ACTIVE" + } + } +} + +style.waste = { + -- auto waste processing states + states = { + { + color = cpair(colors.black, colors.green), + text = "PLUTONIUM" + }, + { + color = cpair(colors.black, colors.cyan), + text = "POLONIUM" + }, + { + color = cpair(colors.black, colors.purple), + text = "ANTI MATTER" + } + }, + states_abbrv = { + { + color = cpair(colors.black, colors.green), + text = "Pu" + }, + { + color = cpair(colors.black, colors.cyan), + text = "Po" + }, + { + color = cpair(colors.black, colors.purple), + text = "AM" + } + }, + -- process radio button options + options = { "Plutonium", "Polonium", "Antimatter" }, + -- unit waste selection + unit_opts = { "Auto", "Plutonium", "Polonium", "Antimatter" } +} + return style diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 1775248..e31aa42 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.1" -comms.api_version = "0.0.6" +comms.version = "3.0.2" +comms.api_version = "0.0.7" ---@enum PROTOCOL local PROTOCOL = { @@ -68,8 +68,9 @@ local CRDN_TYPE = { UNIT_CMD = 6, -- command a reactor unit API_GET_FAC = 7, -- API: get all the facility data API_GET_UNIT = 8, -- API: get reactor unit data - API_GET_CTRL = 9, -- API: get data used for the control app - API_GET_PROC = 10 -- API: get data used for the process app + API_GET_CTRL = 9, -- API: get data for the control app + API_GET_PROC = 10, -- API: get data for the process app + API_GET_WASTE = 11 -- API: get data for the waste app } ---@enum ESTABLISH_ACK diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 771e210..e9c585d 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -278,13 +278,13 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim end elseif cmd == FAC_COMMAND.SET_PU_FB then if pkt.length == 2 then - _send(CRDN_TYPE.FAC_CMD, { cmd, facility.set_pu_fallback(pkt.data[2]) }) + _send(CRDN_TYPE.FAC_CMD, { cmd, facility.set_pu_fallback(pkt.data[2] == true) }) else log.debug(log_tag .. "CRDN set pu fallback packet length mismatch") end elseif cmd == FAC_COMMAND.SET_SPS_LP then if pkt.length == 2 then - _send(CRDN_TYPE.FAC_CMD, { cmd, facility.set_sps_low_power(pkt.data[2]) }) + _send(CRDN_TYPE.FAC_CMD, { cmd, facility.set_sps_low_power(pkt.data[2] == true) }) else log.debug(log_tag .. "CRDN set sps low power packet length mismatch") end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 4d31d6a..ff2ec5b 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -22,7 +22,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.5.15" +local SUPERVISOR_VERSION = "v1.5.17" local println = util.println local println_ts = util.println_ts diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 16f065a..ebb4111 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -986,7 +986,8 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) local db = self.snas[i].get_db() total_peak = total_peak + db.state.peak_production total_avail = total_avail + db.state.production_rate - total_out = total_out + math.min(db.tanks.input.amount / 10, db.state.production_rate) + local out_from_in = util.trinary(db.tanks.input.amount >= 10, db.tanks.input.amount / 10, 0) + total_out = total_out + math.min(out_from_in, db.state.production_rate) end status.sna = { #self.snas, total_peak, total_avail, total_out }