diff --git a/build/safemin.py b/build/safemin.py index 2576159..1e89307 100644 --- a/build/safemin.py +++ b/build/safemin.py @@ -28,6 +28,9 @@ def minify(path: str): contents = f.read() f.close() + # remove --[[@as type]] hints before anything, since it would detect as multiline comments + contents = re.sub(r' --+\[.+]]', '', contents) + if re.search(r'--+\[+', contents) != None: # absolutely not dealing with lua multiline comments # - there are more important things to do diff --git a/ccmsi.lua b/ccmsi.lua index e1ec80c..ffdc521 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -15,7 +15,7 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]]-- -local CCMSI_VERSION = "v1.19" +local CCMSI_VERSION = "v1.20" local install_dir = "/.install-cache" local manifest_path = "https://mikaylafischler.github.io/cc-mek-scada/manifests/" @@ -149,16 +149,16 @@ local function get_remote_manifest() end -- record the local installation manifest -local function write_install_manifest(manifest, dependencies) +local function write_install_manifest(manifest, deps) local versions = {} for key, value in pairs(manifest.versions) do - local is_dependency = false - for _, dependency in pairs(dependencies) do - if (key == "bootloader" and dependency == "system") or key == dependency then - is_dependency = true;break + local is_dep = false + for _, dep in pairs(deps) do + if (key == "bootloader" and dep == "system") or key == dep then + is_dep = true;break end end - if key == app or key == "comms" or is_dependency then versions[key] = value end + if key == app or key == "comms" or is_dep then versions[key] = value end end manifest.versions = versions @@ -383,8 +383,10 @@ if mode == "check" then yellow();println("\nA different version of the installer is available, it is recommended to update (use 'ccmsi update installer').");white() end elseif mode == "install" or mode == "update" then + local ok, r_manifest, l_manifest + local update_installer = app == "installer" - local ok, manifest = get_remote_manifest() + ok, r_manifest = get_remote_manifest() if not ok then return end local ver = { @@ -397,27 +399,27 @@ elseif mode == "install" or mode == "update" then } -- try to find local versions - local local_ok, lmnf = read_local_manifest() - if not local_ok then - if mode == "update" then + ok, l_manifest = read_local_manifest() + if mode == "update" and not update_installer then + if not ok then red();println("Failed to load local installation information, cannot update.");white() return - end - elseif not update_installer then - ver.boot.v_local = lmnf.versions.bootloader - ver.app.v_local = lmnf.versions[app] - ver.comms.v_local = lmnf.versions.comms - ver.common.v_local = lmnf.versions.common - ver.graphics.v_local = lmnf.versions.graphics - ver.lockbox.v_local = lmnf.versions.lockbox + else + ver.boot.v_local = l_manifest.versions.bootloader + ver.app.v_local = l_manifest.versions[app] + ver.comms.v_local = l_manifest.versions.comms + ver.common.v_local = l_manifest.versions.common + ver.graphics.v_local = l_manifest.versions.graphics + ver.lockbox.v_local = l_manifest.versions.lockbox - if lmnf.versions[app] == nil then - red();println("Another application is already installed, please uninstall it before installing a new application.");white() - return + if l_manifest.versions[app] == nil then + red();println("Another application is already installed, please uninstall it before installing a new application.");white() + return + end end end - if manifest.versions.installer ~= CCMSI_VERSION then + if r_manifest.versions.installer ~= CCMSI_VERSION then if not update_installer then yellow();println("A different version of the installer is available, it is recommended to update to it.");white() end if update_installer or ask_y_n("Would you like to update now", true) then lgray();println("GET ccmsi.lua") @@ -440,12 +442,12 @@ elseif mode == "install" or mode == "update" then return end - ver.boot.v_remote = manifest.versions.bootloader - ver.app.v_remote = manifest.versions[app] - ver.comms.v_remote = manifest.versions.comms - ver.common.v_remote = manifest.versions.common - ver.graphics.v_remote = manifest.versions.graphics - ver.lockbox.v_remote = manifest.versions.lockbox + ver.boot.v_remote = r_manifest.versions.bootloader + ver.app.v_remote = r_manifest.versions[app] + ver.comms.v_remote = r_manifest.versions.comms + ver.common.v_remote = r_manifest.versions.common + ver.graphics.v_remote = r_manifest.versions.graphics + ver.lockbox.v_remote = r_manifest.versions.lockbox green() if mode == "install" then print("Installing ") else print("Updating ") end @@ -461,36 +463,33 @@ elseif mode == "install" or mode == "update" then ver.graphics.changed = show_pkg_change("graphics", ver.graphics) ver.lockbox.changed = show_pkg_change("lockbox", ver.lockbox) - -------------------------- - -- START INSTALL/UPDATE -- - -------------------------- + -- start install/update - local space_required = manifest.sizes.manifest - local space_available = fs.getFreeSpace("/") + local space_req = r_manifest.sizes.manifest + local space_avail = fs.getFreeSpace("/") - local single_file_mode = false - local file_list = manifest.files - local size_list = manifest.sizes - local dependencies = manifest.depends[app] + local file_list = r_manifest.files + local size_list = r_manifest.sizes + local deps = r_manifest.depends[app] - table.insert(dependencies, app) + table.insert(deps, app) -- helper function to check if a dependency is unchanged - local function unchanged(dependency) - if dependency == "system" then return not ver.boot.changed - elseif dependency == "graphics" then return not ver.graphics.changed - elseif dependency == "lockbox" then return not ver.lockbox.changed - elseif dependency == "common" then return not (ver.common.changed or ver.comms.changed) - elseif dependency == app then return not ver.app.changed + local function unchanged(dep) + if dep == "system" then return not ver.boot.changed + elseif dep == "graphics" then return not ver.graphics.changed + elseif dep == "lockbox" then return not ver.lockbox.changed + elseif dep == "common" then return not (ver.common.changed or ver.comms.changed) + elseif dep == app then return not ver.app.changed else return true end end local any_change = false - for _, dependency in pairs(dependencies) do - local size = size_list[dependency] - space_required = space_required + size - any_change = any_change or not unchanged(dependency) + for _, dep in pairs(deps) do + local size = size_list[dep] + space_req = space_req + size + any_change = any_change or not unchanged(dep) end if mode == "update" and not any_change then @@ -501,10 +500,7 @@ elseif mode == "install" or mode == "update" then -- ask for confirmation if not ask_y_n("Continue", false) then return end - -- check space constraints - if space_available < space_required then - single_file_mode = true - end + local single_file_mode = space_avail < space_req local success = true @@ -548,7 +544,7 @@ elseif mode == "install" or mode == "update" then success = false return end - clean(manifest) + clean(r_manifest) sf_install(3) elseif attempt == 3 then yellow() @@ -574,30 +570,30 @@ elseif mode == "install" or mode == "update" then local abort_attempt = false success = true - for _, dependency in pairs(dependencies) do - if mode == "update" and unchanged(dependency) then - pkg_message("skipping install of unchanged package", dependency) + for _, dep in pairs(deps) do + if mode == "update" and unchanged(dep) then + pkg_message("skipping install of unchanged package", dep) else - pkg_message("installing package", dependency) + pkg_message("installing package", dep) lgray() -- beginning on the second try, delete the directory before starting if attempt >= 2 then - if dependency == "system" then - elseif dependency == "common" then + if dep == "system" then + elseif dep == "common" then if fs.exists("/scada-common") then fs.delete("/scada-common") println("deleted /scada-common") end else - if fs.exists("/"..dependency) then - fs.delete("/"..dependency) - println("deleted /"..dependency) + if fs.exists("/"..dep) then + fs.delete("/"..dep) + println("deleted /"..dep) end end end - local files = file_list[dependency] + local files = file_list[dep] for _, file in pairs(files) do println("GET "..file) mitigate_case(file) @@ -620,14 +616,14 @@ elseif mode == "install" or mode == "update" then if fs.exists(install_dir) then fs.delete(install_dir);fs.makeDir(install_dir) end -- download all dependencies - for _, dependency in pairs(dependencies) do - if mode == "update" and unchanged(dependency) then - pkg_message("skipping download of unchanged package", dependency) + for _, dep in pairs(deps) do + if mode == "update" and unchanged(dep) then + pkg_message("skipping download of unchanged package", dep) else - pkg_message("downloading package", dependency) + pkg_message("downloading package", dep) lgray() - local files = file_list[dependency] + local files = file_list[dep] for _, file in pairs(files) do println("GET "..file) local dl_stat = http_get_file(file, install_dir.."/") @@ -650,14 +646,14 @@ elseif mode == "install" or mode == "update" then -- copy in downloaded files (installation) if success then - for _, dependency in pairs(dependencies) do - if mode == "update" and unchanged(dependency) then - pkg_message("skipping install of unchanged package", dependency) + for _, dep in pairs(deps) do + if mode == "update" and unchanged(dep) then + pkg_message("skipping install of unchanged package", dep) else - pkg_message("installing package", dependency) + pkg_message("installing package", dep) lgray() - local files = file_list[dependency] + local files = file_list[dep] for _, file in pairs(files) do local temp_file = install_dir.."/"..file if fs.exists(file) then fs.delete(file) end @@ -671,13 +667,13 @@ elseif mode == "install" or mode == "update" then end if success then - write_install_manifest(manifest, dependencies) + write_install_manifest(r_manifest, deps) green() if mode == "install" then println("Installation completed successfully.") else println("Update completed successfully.") end white();println("Ready to clean up unused files, press any key to continue...") - any_key();clean(manifest) + any_key();clean(r_manifest) white();println("Done.") else red() @@ -712,14 +708,14 @@ elseif mode == "uninstall" then clean(manifest) local file_list = manifest.files - local dependencies = manifest.depends[app] + local deps = manifest.depends[app] - table.insert(dependencies, app) + table.insert(deps, app) -- delete all installed files lgray() - for _, dependency in pairs(dependencies) do - local files = file_list[dependency] + for _, dep in pairs(deps) do + local files = file_list[dep] for _, file in pairs(files) do if fs.exists(file) then fs.delete(file);println("deleted "..file) end end diff --git a/configure.lua b/configure.lua index ce6ed40..36aba55 100644 --- a/configure.lua +++ b/configure.lua @@ -1,11 +1,12 @@ print("CONFIGURE> SCANNING FOR CONFIGURATOR...") -if fs.exists("reactor-plc/configure.lua") then require("reactor-plc.configure").configure() -elseif fs.exists("rtu/configure.lua") then require("rtu.configure").configure() -elseif fs.exists("supervisor/configure.lua") then require("supervisor.configure").configure() -elseif fs.exists("coordinator/configure.lua") then require("coordinator.configure").configure() -elseif fs.exists("pocket/configure.lua") then require("pocket.configure").configure() -else - print("CONFIGURE> NO CONFIGURATOR FOUND") - print("CONFIGURE> EXIT") +for _, app in ipairs({ "reactor-plc", "rtu", "supervisor", "coordinator", "pocket" }) do + if fs.exists(app .. "/configure.lua") then + local _, _, launch = require(app .. ".configure").configure() + if launch then shell.execute("/startup") end + return + end end + +print("CONFIGURE> NO CONFIGURATOR FOUND") +print("CONFIGURE> EXIT") diff --git a/coordinator/configure.lua b/coordinator/configure.lua index 3c9bfe7..453cc44 100644 --- a/coordinator/configure.lua +++ b/coordinator/configure.lua @@ -58,6 +58,7 @@ style.btn_dis_fg_bg = cpair(colors.lightGray,colors.white) local tool_ctl = { sv_cool_conf = nil, ---@type [ integer, integer ][] list of boiler & turbine counts + launch_startup = false, start_fail = 0, fail_message = "", has_config = false, @@ -236,9 +237,17 @@ local function config_view(display) main_pane.set_value(8) end + local function startup() + tool_ctl.launch_startup = true + exit() + end + PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg} - tool_ctl.color_cfg = PushButton{parent=main_page,x=23,y=17,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} - PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(10)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + local start_btn = PushButton{parent=main_page,x=42,y=17,min_width=9,text="Startup",callback=startup,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} + tool_ctl.color_cfg = PushButton{parent=main_page,x=36,y=y_start,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=cpair(colors.lightGray,colors.white)} + PushButton{parent=main_page,x=39,y=y_start+2,min_width=12,text="Change Log",callback=function()main_pane.set_value(10)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + if tool_ctl.start_fail ~= 0 then start_btn.disable() end if not tool_ctl.has_config then tool_ctl.view_cfg.disable() @@ -372,7 +381,7 @@ function configurator.configure(start_code, message) println("configurator error: " .. error) end - return status, error + return status, error, tool_ctl.launch_startup end return configurator diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 8d57bed..7108c01 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -94,7 +94,7 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale) auto_scram = false, ---@type ascram_status ascram_status = { - matrix_dc = false, + matrix_fault = false, matrix_fill = false, crit_alarm = false, radiation = false, @@ -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, @@ -540,7 +543,7 @@ function iocontrol.update_facility_status(status) fac.auto_saturated = ctl_status[5] fac.auto_scram = ctl_status[6] - fac.ascram_status.matrix_dc = ctl_status[7] + fac.ascram_status.matrix_fault = ctl_status[7] fac.ascram_status.matrix_fill = ctl_status[8] fac.ascram_status.crit_alarm = ctl_status[9] fac.ascram_status.radiation = ctl_status[10] @@ -555,7 +558,7 @@ function iocontrol.update_facility_status(status) fac.ps.publish("auto_ramping", fac.auto_ramping) fac.ps.publish("auto_saturated", fac.auto_saturated) fac.ps.publish("auto_scram", fac.auto_scram) - fac.ps.publish("as_matrix_dc", fac.ascram_status.matrix_dc) + fac.ps.publish("as_matrix_fault", fac.ascram_status.matrix_fault) fac.ps.publish("as_matrix_fill", fac.ascram_status.matrix_fill) fac.ps.publish("as_crit_alarm", fac.ascram_status.crit_alarm) fac.ps.publish("as_radiation", fac.ascram_status.radiation) @@ -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 772b8b6..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.13" +local COORDINATOR_VERSION = "v1.5.16" local CHUNK_LOAD_DELAY_S = 30.0 diff --git a/coordinator/ui/components/process_ctl.lua b/coordinator/ui/components/process_ctl.lua index a70bcd6..f649ac5 100644 --- a/coordinator/ui/components/process_ctl.lua +++ b/coordinator/ui/components/process_ctl.lua @@ -94,14 +94,14 @@ local function new_view(root, x, y) main.line_break() local auto_scram = IndicatorLight{parent=main,label="Automatic SCRAM",colors=ind_red,flash=true,period=period.BLINK_250_MS} - local matrix_dc = IndicatorLight{parent=main,label="Matrix Disconnected",colors=ind_yel,flash=true,period=period.BLINK_500_MS} + local matrix_flt = IndicatorLight{parent=main,label="Induction Matrix Fault",colors=ind_yel,flash=true,period=period.BLINK_500_MS} local matrix_fill = IndicatorLight{parent=main,label="Matrix Charge High",colors=ind_red,flash=true,period=period.BLINK_500_MS} local unit_crit = IndicatorLight{parent=main,label="Unit Critical Alarm",colors=ind_red,flash=true,period=period.BLINK_250_MS} local fac_rad_h = IndicatorLight{parent=main,label="Facility Radiation High",colors=ind_red,flash=true,period=period.BLINK_250_MS} local gen_fault = IndicatorLight{parent=main,label="Gen. Control Fault",colors=ind_yel,flash=true,period=period.BLINK_500_MS} auto_scram.register(facility.ps, "auto_scram", auto_scram.update) - matrix_dc.register(facility.ps, "as_matrix_dc", matrix_dc.update) + matrix_flt.register(facility.ps, "as_matrix_fault", matrix_flt.update) matrix_fill.register(facility.ps, "as_matrix_fill", matrix_fill.update) unit_crit.register(facility.ps, "as_crit_alarm", unit_crit.update) fac_rad_h.register(facility.ps, "as_radiation", fac_rad_h.update) diff --git a/graphics/core.lua b/graphics/core.lua index 5439603..e993308 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -7,7 +7,7 @@ local flasher = require("graphics.flasher") local core = {} -core.version = "2.4.5" +core.version = "2.4.6" core.flasher = flasher core.events = events diff --git a/graphics/elements/controls/Checkbox.lua b/graphics/elements/controls/Checkbox.lua index 26d4faf..1a36a78 100644 --- a/graphics/elements/controls/Checkbox.lua +++ b/graphics/elements/controls/Checkbox.lua @@ -6,6 +6,7 @@ local element = require("graphics.element") ---@class checkbox_args ---@field label string checkbox text ---@field box_fg_bg cpair colors for checkbox +---@field disable_fg_bg? cpair text colors when disabled ---@field default? boolean default value ---@field callback? function function to call on press ---@field parent graphics_element @@ -35,20 +36,27 @@ return function (args) local function draw() e.w_set_cur(1, 1) + local fgd, bkg = args.box_fg_bg.fgd, args.box_fg_bg.bkg + + if (not e.enabled) and type(args.disable_fg_bg) == "table" then + fgd = args.disable_fg_bg.bkg + bkg = args.disable_fg_bg.fgd + end + if e.value then -- show as selected - e.w_set_fgd(args.box_fg_bg.bkg) - e.w_set_bkg(args.box_fg_bg.fgd) + e.w_set_fgd(bkg) + e.w_set_bkg(fgd) e.w_write("\x88") - e.w_set_fgd(args.box_fg_bg.fgd) + e.w_set_fgd(fgd) e.w_set_bkg(e.fg_bg.bkg) e.w_write("\x95") else -- show as unselected e.w_set_fgd(e.fg_bg.bkg) - e.w_set_bkg(args.box_fg_bg.bkg) + e.w_set_bkg(bkg) e.w_write("\x88") - e.w_set_fgd(args.box_fg_bg.bkg) + e.w_set_fgd(bkg) e.w_set_bkg(e.fg_bg.bkg) e.w_write("\x95") end @@ -57,16 +65,18 @@ return function (args) -- write label text local function draw_label() if e.enabled and e.is_focused() then - e.w_set_cur(3, 1) e.w_set_fgd(e.fg_bg.bkg) e.w_set_bkg(e.fg_bg.fgd) - e.w_write(args.label) + elseif (not e.enabled) and type(args.disable_fg_bg) == "table" then + e.w_set_fgd(args.disable_fg_bg.fgd) + e.w_set_bkg(args.disable_fg_bg.bkg) else - e.w_set_cur(3, 1) e.w_set_fgd(e.fg_bg.fgd) e.w_set_bkg(e.fg_bg.bkg) - e.w_write(args.label) end + + e.w_set_cur(3, 1) + e.w_write(args.label) end -- handle mouse interaction @@ -98,20 +108,20 @@ return function (args) draw() end - -- handle focus - e.on_focused = draw_label - e.on_unfocused = draw_label - - -- handle enable - e.on_enabled = draw_label - e.on_disabled = draw_label - -- element redraw function e.redraw() draw() draw_label() end + -- handle focus + e.on_focused = draw_label + e.on_unfocused = draw_label + + -- handle enable + e.on_enabled = e.redraw + e.on_disabled = e.redraw + ---@class Checkbox:graphics_element local Checkbox, id = e.complete(true) diff --git a/pocket/configure.lua b/pocket/configure.lua index 69a99c7..175be03 100644 --- a/pocket/configure.lua +++ b/pocket/configure.lua @@ -50,6 +50,7 @@ style.btn_dis_fg_bg = cpair(colors.lightGray, colors.white) ---@class _pkt_cfg_tool_ctl local tool_ctl = { + launch_startup = false, ask_config = false, has_config = false, viewing_config = false, @@ -162,8 +163,16 @@ local function config_view(display) if not tool_ctl.has_config then tool_ctl.view_cfg.disable() end + local function startup() + tool_ctl.launch_startup = true + exit() + end + PushButton{parent=main_page,x=2,y=18,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg} - PushButton{parent=main_page,x=14,y=18,min_width=12,text="Change Log",callback=function()main_pane.set_value(6)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + local start_btn = PushButton{parent=main_page,x=17,y=18,min_width=9,text="Startup",callback=startup,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} + PushButton{parent=main_page,x=2,y=y_start+4,min_width=12,text="Change Log",callback=function()main_pane.set_value(6)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + if tool_ctl.ask_config then start_btn.disable() end --#endregion @@ -254,7 +263,7 @@ function configurator.configure(ask_config) println("configurator error: " .. error) end - return status, error + return status, error, tool_ctl.launch_startup end return configurator diff --git a/pocket/iocontrol.lua b/pocket/iocontrol.lua index e72a573..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 @@ -148,7 +149,7 @@ function iocontrol.init_fac(conf) auto_scram = false, ---@type ascram_status ascram_status = { - matrix_dc = false, + matrix_fault = false, matrix_fill = false, crit_alarm = false, radiation = false, @@ -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, @@ -908,7 +912,7 @@ function iocontrol.record_process_data(data) fac.ps.publish("auto_saturated", fac.auto_saturated) fac.ps.publish("auto_scram", fac.auto_scram) - fac.ps.publish("as_matrix_dc", fac.ascram_status.matrix_dc) + fac.ps.publish("as_matrix_fault", fac.ascram_status.matrix_fault) fac.ps.publish("as_matrix_fill", fac.ascram_status.matrix_fill) fac.ps.publish("as_crit_alarm", fac.ascram_status.crit_alarm) fac.ps.publish("as_radiation", fac.ascram_status.radiation) @@ -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 0f6fe40..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.7-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/process.lua b/pocket/ui/apps/process.lua index deb7b1f..593e74c 100644 --- a/pocket/ui/apps/process.lua +++ b/pocket/ui/apps/process.lua @@ -269,7 +269,7 @@ local function new_view(root) local auto_scram = IconIndicator{parent=a_div,y=3,label="Automatic SCRAM",states=red_ind_s} TextBox{parent=a_div,y=5,text="Induction Matrix",fg_bg=label_fg_bg} - local matrix_dc = IconIndicator{parent=a_div,label="Disconnected",states=yel_ind_s} + local matrix_flt = IconIndicator{parent=a_div,label="Matrix Fault",states=yel_ind_s} local matrix_fill = IconIndicator{parent=a_div,label="Charge High",states=red_ind_s} TextBox{parent=a_div,y=9,text="Assigned Units",fg_bg=label_fg_bg} @@ -282,7 +282,7 @@ local function new_view(root) local gen_fault = IconIndicator{parent=a_div,label="Control Fault",states=yel_ind_s} auto_scram.register(f_ps, "auto_scram", auto_scram.update) - matrix_dc.register(f_ps, "as_matrix_dc", matrix_dc.update) + matrix_flt.register(f_ps, "as_matrix_fault", matrix_flt.update) matrix_fill.register(f_ps, "as_matrix_fill", matrix_fill.update) unit_crit.register(f_ps, "as_crit_alarm", unit_crit.update) fac_rad_h.register(f_ps, "as_radiation", fac_rad_h.update) 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/docs.lua b/pocket/ui/docs.lua index 0d4e889..f87b249 100644 --- a/pocket/ui/docs.lua +++ b/pocket/ui/docs.lua @@ -148,7 +148,7 @@ doc("auto_ramping", "Process Ramping", "Automatic process control is performing doc("auto_saturated", "Min/Max Burn Rate", "Auto control has either commanded 0 mB/t or the maximum total burn rate available (from assigned units).") sect("Automatic SCRAM") doc("auto_scram", "Automatic SCRAM", "Automatic control system SCRAM'ed the assigned reactors due to a safety hazard, shown by the below indicators.") -doc("as_matrix_dc", "Matrix Disconnected", "Automatic SCRAM occurred due to loss of induction matrix connection.") +doc("as_matrix_fault", "Matrix Fault", "Automatic SCRAM occurred due to the loss of the induction matrix connection, or the matrix being unformed or faulted.") doc("as_matrix_fill", "Matrix Charge High", "Automatic SCRAM occurred due to induction matrix charge exceeding acceptable limit.") doc("as_crit_alarm", "Unit Critical Alarm", "Automatic SCRAM occurred due to critical level unit alarm(s).") doc("as_radiation", "Facility Radiation High", "Automatic SCRAM occurred due to high facility radiation levels.") 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/reactor-plc/configure.lua b/reactor-plc/configure.lua index 828c2e6..db0d507 100644 --- a/reactor-plc/configure.lua +++ b/reactor-plc/configure.lua @@ -53,6 +53,7 @@ style.btn_dis_fg_bg = cpair(colors.lightGray, colors.white) ---@class _plc_cfg_tool_ctl local tool_ctl = { + launch_startup = false, ask_config = false, has_config = false, viewing_config = false, @@ -184,10 +185,18 @@ local function config_view(display) main_pane.set_value(5) end + local function startup() + tool_ctl.launch_startup = true + exit() + end + PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg} - PushButton{parent=main_page,x=10,y=17,min_width=12,text="Self-Check",callback=function()main_pane.set_value(8)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} - tool_ctl.color_cfg = PushButton{parent=main_page,x=23,y=17,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} - PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + local start_btn = PushButton{parent=main_page,x=42,y=17,min_width=9,text="Startup",callback=startup,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} + PushButton{parent=main_page,x=39,y=y_start,min_width=12,text="Self-Check",callback=function()main_pane.set_value(8)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} + tool_ctl.color_cfg = PushButton{parent=main_page,x=36,y=y_start+2,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} + PushButton{parent=main_page,x=39,y=y_start+4,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + if tool_ctl.ask_config then start_btn.disable() end if not tool_ctl.has_config then tool_ctl.view_cfg.disable() @@ -293,7 +302,7 @@ function configurator.configure(ask_config) println("configurator error: " .. error) end - return status, error + return status, error, tool_ctl.launch_startup end return configurator diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 67dc290..6a6643a 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.12" +local R_PLC_VERSION = "v1.8.13" local println = util.println local println_ts = util.println_ts diff --git a/rtu/config/redstone.lua b/rtu/config/redstone.lua index d21dcb7..5cb07ad 100644 --- a/rtu/config/redstone.lua +++ b/rtu/config/redstone.lua @@ -32,15 +32,16 @@ local IO_MODE = rsio.IO_MODE local LEFT = core.ALIGN.LEFT local self = { - rs_cfg_port = 1, ---@type IO_PORT - rs_cfg_editing = false, ---@type integer|false + rs_cfg_port = 1, ---@type IO_PORT + rs_cfg_editing = false, ---@type integer|false - rs_cfg_selection = nil, ---@type TextBox - rs_cfg_unit_l = nil, ---@type TextBox - rs_cfg_unit = nil, ---@type NumberField - rs_cfg_side_l = nil, ---@type TextBox - rs_cfg_color = nil, ---@type Radio2D - rs_cfg_shortcut = nil ---@type TextBox + rs_cfg_selection = nil, ---@type TextBox + rs_cfg_unit_l = nil, ---@type TextBox + rs_cfg_unit = nil, ---@type NumberField + rs_cfg_side_l = nil, ---@type TextBox + rs_cfg_bundled = nil, ---@type Checkbox + rs_cfg_color = nil, ---@type Radio2D + rs_cfg_shortcut = nil ---@type TextBox } -- rsio port descriptions @@ -195,6 +196,15 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style) local io_mode = rsio.get_io_mode(port) local inv = tri(rsio.digital_is_active(port, IO_LVL.LOW) == true, "inverted ", "") + if rsio.is_analog(port) then + self.rs_cfg_bundled.set_value(false) + self.rs_cfg_bundled.disable() + self.rs_cfg_color.disable() + else + self.rs_cfg_bundled.enable() + if self.rs_cfg_bundled.get_value() then self.rs_cfg_color.enable() else self.rs_cfg_color.disable() end + end + if io_mode == IO_MODE.DIGITAL_IN then io_type = inv .. "digital input " elseif io_mode == IO_MODE.DIGITAL_OUT then @@ -263,7 +273,7 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style) self.rs_cfg_shortcut = TextBox{parent=rs_c_3,x=1,y=9,height=4,text="This shortcut will add entries for each of the 4 waste outputs. If you select bundled, 4 colors will be assigned to the selected side. Otherwise, 4 default sides will be used."} self.rs_cfg_shortcut.hide(true) - local bundled = Checkbox{parent=rs_c_3,x=1,y=7,label="Is Bundled?",default=false,box_fg_bg=cpair(colors.red,colors.black),callback=set_bundled} + self.rs_cfg_bundled = Checkbox{parent=rs_c_3,x=1,y=7,label="Is Bundled?",default=false,box_fg_bg=cpair(colors.red,colors.black),callback=set_bundled,disable_fg_bg=g_lg_fg_bg} self.rs_cfg_color = Radio2D{parent=rs_c_3,x=1,y=9,rows=4,columns=4,default=1,options=color_options,radio_colors=cpair(colors.lightGray,colors.black),color_map=color_options_map,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg} self.rs_cfg_color.disable() @@ -288,7 +298,7 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style) unit = tri(PORT_DSGN[port] == 1, u, nil), port = port, side = side_options_map[side.get_value()], - color = tri(bundled.get_value(), color_options_map[self.rs_cfg_color.get_value()], nil) + color = tri(self.rs_cfg_bundled.get_value() and rsio.is_digital(port), color_options_map[self.rs_cfg_color.get_value()], nil) } if self.rs_cfg_editing == false then @@ -304,8 +314,8 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style) table.insert(tmp_cfg.Redstone, { unit = tri(PORT_DSGN[IO.WASTE_PU + i] == 1, u, nil), port = IO.WASTE_PU + i, - side = tri(bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]), - color = tri(bundled.get_value(), default_colors[i + 1], nil) + side = tri(self.rs_cfg_bundled.get_value(), side_options_map[side.get_value()], default_sides[i + 1]), + color = tri(self.rs_cfg_bundled.get_value(), default_colors[i + 1], nil) }) end end @@ -314,7 +324,7 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style) tool_ctl.gen_rs_summary() side.set_value(1) - bundled.set_value(false) + self.rs_cfg_bundled.set_value(false) self.rs_cfg_color.set_value(1) self.rs_cfg_color.disable() else rs_err.show() end @@ -356,6 +366,14 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style) text = text .. "the facility)." end + if rsio.is_analog(def.port) then + self.rs_cfg_bundled.set_value(false) + self.rs_cfg_bundled.disable() + else + self.rs_cfg_bundled.enable() + self.rs_cfg_bundled.set_value(def.color ~= nil) + end + local value = 1 if def.color ~= nil then value = color_to_idx(def.color) @@ -367,7 +385,6 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style) self.rs_cfg_selection.set_value(text) self.rs_cfg_side_l.set_value(tri(rsio.get_io_dir(def.port) == rsio.IO_DIR.IN, "Input Side", "Output Side")) side.set_value(side_to_idx(def.side)) - bundled.set_value(def.color ~= nil) self.rs_cfg_color.set_value(value) rs_pane.set_value(3) end diff --git a/rtu/configure.lua b/rtu/configure.lua index d4453d1..e623731 100644 --- a/rtu/configure.lua +++ b/rtu/configure.lua @@ -55,6 +55,7 @@ style.btn_dis_fg_bg = cpair(colors.lightGray, colors.white) ---@class _rtu_cfg_tool_ctl local tool_ctl = { + launch_startup = false, ask_config = false, has_config = false, viewing_config = false, @@ -218,9 +219,17 @@ local function config_view(display) main_pane.set_value(5) end + local function startup() + tool_ctl.launch_startup = true + exit() + end + PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg} - tool_ctl.color_cfg = PushButton{parent=main_page,x=23,y=17,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} - PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + local start_btn = PushButton{parent=main_page,x=42,y=17,min_width=9,text="Startup",callback=startup,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} + tool_ctl.color_cfg = PushButton{parent=main_page,x=36,y=y_start,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} + PushButton{parent=main_page,x=39,y=y_start+2,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + if tool_ctl.ask_config then start_btn.disable() end if not tool_ctl.has_config then tool_ctl.view_gw_cfg.disable() @@ -346,7 +355,7 @@ function configurator.configure(ask_config) println("configurator error: " .. error) end - return status, error + return status, error, tool_ctl.launch_startup end return configurator diff --git a/rtu/startup.lua b/rtu/startup.lua index f5ec27f..fbfec17 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -31,7 +31,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "v1.10.14" +local RTU_VERSION = "v1.10.16" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_HW_STATE = databus.RTU_HW_STATE 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/startup.lua b/startup.lua index 662d989..97122b6 100644 --- a/startup.lua +++ b/startup.lua @@ -1,4 +1,4 @@ -local BOOTLOADER_VERSION = "1.1" +local BOOTLOADER_VERSION = "1.2" print("SCADA BOOTLOADER V" .. BOOTLOADER_VERSION) print("BOOT> SCANNING FOR APPLICATIONS...") diff --git a/supervisor/configure.lua b/supervisor/configure.lua index aaab65e..0b1b558 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -51,6 +51,7 @@ style.btn_dis_fg_bg = cpair(colors.lightGray, colors.white) ---@class _svr_cfg_tool_ctl local tool_ctl = { + launch_startup = false, ask_config = false, has_config = false, viewing_config = false, @@ -201,9 +202,17 @@ local function config_view(display) main_pane.set_value(5) end + local function startup() + tool_ctl.launch_startup = true + exit() + end + PushButton{parent=main_page,x=2,y=17,min_width=6,text="Exit",callback=exit,fg_bg=cpair(colors.black,colors.red),active_fg_bg=btn_act_fg_bg} - tool_ctl.color_cfg = PushButton{parent=main_page,x=23,y=17,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} - PushButton{parent=main_page,x=39,y=17,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + local start_btn = PushButton{parent=main_page,x=42,y=17,min_width=9,text="Startup",callback=startup,fg_bg=cpair(colors.black,colors.green),active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} + tool_ctl.color_cfg = PushButton{parent=main_page,x=36,y=y_start,min_width=15,text="Color Options",callback=jump_color,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg,dis_fg_bg=btn_dis_fg_bg} + PushButton{parent=main_page,x=39,y=y_start+2,min_width=12,text="Change Log",callback=function()main_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + + if tool_ctl.ask_config then start_btn.disable() end if not tool_ctl.has_config then tool_ctl.view_cfg.disable() @@ -308,7 +317,7 @@ function configurator.configure(ask_config) println("configurator error: " .. error) end - return status, error + return status, error, tool_ctl.launch_startup end return configurator diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 4b2b38c..604771a 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -17,7 +17,7 @@ local WASTE = types.WASTE_PRODUCT ---@enum AUTO_SCRAM local AUTO_SCRAM = { NONE = 0, - MATRIX_DC = 1, + MATRIX_FAULT = 1, MATRIX_FILL = 2, CRIT_ALARM = 3, RADIATION = 4, @@ -81,7 +81,7 @@ function facility.new(config) ascram_reason = AUTO_SCRAM.NONE, ---@class ascram_status ascram_status = { - matrix_dc = false, + matrix_fault = false, matrix_fill = false, crit_alarm = false, radiation = false, @@ -91,8 +91,8 @@ function facility.new(config) charge_conversion = 1.0, time_start = 0.0, initial_ramp = true, - waiting_on_ramp = false, - waiting_on_stable = false, + waiting_on_ramp = false, -- waiting on auto ramping + waiting_on_stable = false, -- waiting on gen rate stabilization accumulator = 0.0, saturated = false, last_update = 0, @@ -599,7 +599,7 @@ function facility.new(config) self.waiting_on_ramp or self.waiting_on_stable, self.at_max_burn or self.saturated, self.ascram, - astat.matrix_dc, + astat.matrix_fault, astat.matrix_fill, astat.crit_alarm, astat.radiation, diff --git a/supervisor/facility_update.lua b/supervisor/facility_update.lua index 6375077..5e6fa07 100644 --- a/supervisor/facility_update.lua +++ b/supervisor/facility_update.lua @@ -341,9 +341,17 @@ function update.auto_control(ExtChargeIdling) if state_changed then self.time_start = now self.saturated = true + self.waiting_on_ramp = true - self.status_text = { "MONITORED MODE", "running reactors at limit" } + self.status_text = { "MONITORED MODE", "ramping reactors to limit" } log.info("FAC: MAX_BURN process mode started") + elseif self.waiting_on_ramp then + if all_units_ramped() then + self.waiting_on_ramp = false + + self.status_text = { "MONITORED MODE", "running reactors at limit" } + log.info("FAC: MAX_BURN process mode initial ramp completed") + end end allocate_burn_rate(self.max_burn_combined, true) @@ -351,8 +359,17 @@ function update.auto_control(ExtChargeIdling) -- a total aggregate burn rate if state_changed then self.time_start = now - self.status_text = { "BURN RATE MODE", "running" } + self.waiting_on_ramp = true + + self.status_text = { "BURN RATE MODE", "ramping to target" } log.info("FAC: BURN_RATE process mode started") + elseif self.waiting_on_ramp then + if all_units_ramped() then + self.waiting_on_ramp = false + + self.status_text = { "BURN RATE MODE", "running" } + log.info("FAC: BURN_RATE process mode initial ramp completed") + end end local unallocated = allocate_burn_rate(self.burn_target, true) @@ -511,13 +528,19 @@ function update.auto_safety() local astatus = self.ascram_status + -- matrix related checks if self.induction[1] ~= nil then local db = self.induction[1].get_db() - -- clear matrix disconnected - if astatus.matrix_dc then - astatus.matrix_dc = false - log.info("FAC: induction matrix reconnected, clearing ASCRAM condition") + -- check for unformed or faulted state + local i_ok = db.formed and not self.induction[1].is_faulted() + + -- clear matrix fault if ok again + if astatus.matrix_fault and i_ok then + astatus.matrix_fault = false + log.info("FAC: induction matrix OK, clearing ASCRAM condition") + else + astatus.matrix_fault = not i_ok end -- check matrix fill too high @@ -528,42 +551,42 @@ function update.auto_safety() log.info(util.c("FAC: charge state of induction matrix entered acceptable range <= ", ALARM_LIMS.CHARGE_RE_ENABLE * 100, "%")) end - -- check for critical unit alarms - astatus.crit_alarm = false - for i = 1, #self.units do - local u = self.units[i] - - if u.has_alarm_min_prio(PRIO.CRITICAL) then - astatus.crit_alarm = true - break - end - end - - -- check for facility radiation - if #self.envd > 0 then - local max_rad = 0 - - for i = 1, #self.envd do - local envd = self.envd[i] - local e_db = envd.get_db() - if e_db.radiation_raw > max_rad then max_rad = e_db.radiation_raw end - end - - astatus.radiation = max_rad >= ALARM_LIMS.FAC_HIGH_RAD - else - -- don't clear, if it is true then we lost it with high radiation, so just keep alarming - -- operator can restart the system or hit the stop/reset button - end - -- system not ready, will need to restart GEN_RATE mode -- clears when we enter the fault waiting state astatus.gen_fault = self.mode == PROCESS.GEN_RATE and not self.units_ready else - astatus.matrix_dc = true + astatus.matrix_fault = true + end + + -- check for critical unit alarms + astatus.crit_alarm = false + for i = 1, #self.units do + local u = self.units[i] + + if u.has_alarm_min_prio(PRIO.CRITICAL) then + astatus.crit_alarm = true + break + end + end + + -- check for facility radiation + if #self.envd > 0 then + local max_rad = 0 + + for i = 1, #self.envd do + local envd = self.envd[i] + local e_db = envd.get_db() + if e_db.radiation_raw > max_rad then max_rad = e_db.radiation_raw end + end + + astatus.radiation = max_rad >= ALARM_LIMS.FAC_HIGH_RAD + else + -- don't clear, if it is true then we lost it with high radiation, so just keep alarming + -- operator can restart the system or hit the stop/reset button end if (self.mode ~= PROCESS.INACTIVE) and (self.mode ~= PROCESS.SYSTEM_ALARM_IDLE) then - local scram = astatus.matrix_dc or astatus.matrix_fill or astatus.crit_alarm or astatus.gen_fault + local scram = astatus.matrix_fault or astatus.matrix_fill or astatus.crit_alarm or astatus.gen_fault if scram and not self.ascram then -- SCRAM all units @@ -587,14 +610,14 @@ function update.auto_safety() self.status_text = { "AUTOMATIC SCRAM", "facility radiation high" } log.info("FAC: automatic SCRAM due to high facility radiation") - elseif astatus.matrix_dc then + elseif astatus.matrix_fault then next_mode = PROCESS.MATRIX_FAULT_IDLE - self.ascram_reason = AUTO_SCRAM.MATRIX_DC - self.status_text = { "AUTOMATIC SCRAM", "induction matrix disconnected" } + self.ascram_reason = AUTO_SCRAM.MATRIX_FAULT + self.status_text = { "AUTOMATIC SCRAM", "induction matrix fault" } if self.mode ~= PROCESS.MATRIX_FAULT_IDLE then self.return_mode = self.mode end - log.info("FAC: automatic SCRAM due to induction matrix disconnection") + log.info("FAC: automatic SCRAM due to induction matrix disconnected, unformed, or faulted") elseif astatus.matrix_fill then next_mode = PROCESS.MATRIX_FAULT_IDLE self.ascram_reason = AUTO_SCRAM.MATRIX_FILL 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 537b263..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.10" +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 }