diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 0054436..bc97c99 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -95,9 +95,9 @@ function coordinator.load_config() ---@class monitors_struct local monitors = { - primary = nil, ---@type table|nil - primary_name = "", - flow = nil, ---@type table|nil + main = nil, ---@type table|nil + main_name = "", + flow = nil, ---@type table|nil flow_name = "", unit_displays = {}, unit_name_map = {} @@ -121,11 +121,11 @@ function coordinator.load_config() return 2, "Main monitor is not connected." end - monitors.primary = ppm.get_periph(config.MainDisplay) - monitors.primary_name = config.MainDisplay + monitors.main = ppm.get_periph(config.MainDisplay) + monitors.main_name = config.MainDisplay - monitors.primary.setTextScale(0.5) - w, _ = ppm.monitor_block_size(monitors.primary.getSize()) + monitors.main.setTextScale(0.5) + w, _ = ppm.monitor_block_size(monitors.main.getSize()) if w ~= 8 then return 2, util.c("Main monitor width is incorrect (was ", w, ", must be 8).") end diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index c65bbd0..312d194 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -235,7 +235,10 @@ function iocontrol.init(conf, comms, temp_scale) control_state = false, burn_rate_cmd = 0.0, radiation = types.new_zero_radiation_reading(), - sna_prod_rate = 0.0, + + sna_peak_rate = 0.0, + sna_max_rate = 0.0, + sna_out_rate = 0.0, waste_mode = types.WASTE_MODE.MANUAL_PLUTONIUM, waste_product = types.WASTE_PRODUCT.PLUTONIUM, @@ -1064,12 +1067,14 @@ function iocontrol.update_unit_statuses(statuses) -- solar neutron activator status info if type(rtu_statuses.sna) == "table" then unit.num_snas = rtu_statuses.sna[1] ---@type integer - unit.sna_prod_rate = rtu_statuses.sna[2] ---@type number - unit.sna_peak_rate = rtu_statuses.sna[3] ---@type number + unit.sna_peak_rate = rtu_statuses.sna[2] ---@type number + unit.sna_max_rate = rtu_statuses.sna[3] ---@type number + unit.sna_out_rate = rtu_statuses.sna[4] ---@type number unit.unit_ps.publish("sna_count", unit.num_snas) - unit.unit_ps.publish("sna_prod_rate", unit.sna_prod_rate) 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) sna_count_sum = sna_count_sum + unit.num_snas else @@ -1217,7 +1222,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 = util.trinary(not is_pu, math.min(waste_rate, unit.sna_prod_rate), 0.0) + local u_po_rate = unit.sna_out_rate unit.unit_ps.publish("pu_rate", u_pu_rate) unit.unit_ps.publish("po_rate", u_po_rate) @@ -1225,14 +1230,15 @@ function iocontrol.update_unit_statuses(statuses) unit.unit_ps.publish("sna_in", util.trinary(is_pu, 0, burn_rate)) if unit.waste_product == types.WASTE_PRODUCT.POLONIUM then + u_spent_rate = u_po_rate unit.unit_ps.publish("po_pl_rate", u_po_rate) unit.unit_ps.publish("po_am_rate", 0) po_pl_rate = po_pl_rate + u_po_rate elseif unit.waste_product == types.WASTE_PRODUCT.ANTI_MATTER then + u_spent_rate = 0 unit.unit_ps.publish("po_pl_rate", 0) unit.unit_ps.publish("po_am_rate", u_po_rate) po_am_rate = po_am_rate + u_po_rate - u_spent_rate = 0 else unit.unit_ps.publish("po_pl_rate", 0) unit.unit_ps.publish("po_am_rate", 0) diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 4b8085d..c926b2e 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -52,6 +52,16 @@ local function _init_display(monitor) end end +-- print out that the monitor is too small +---@param monitor table monitor +local function _print_too_small(monitor) + monitor.setCursorPos(1, 1) + monitor.setBackgroundColor(colors.black) + monitor.setTextColor(colors.red) + monitor.clear() + monitor.write("monitor too small") +end + -- disable the flow view ---@param disable boolean function renderer.legacy_disable_flow_view(disable) @@ -64,15 +74,15 @@ function renderer.set_displays(monitors) engine.monitors = monitors -- report to front panel as connected - iocontrol.fp_monitor_state("main", engine.monitors.primary ~= nil) + iocontrol.fp_monitor_state("main", engine.monitors.main ~= nil) iocontrol.fp_monitor_state("flow", engine.monitors.flow ~= nil) for i = 1, #engine.monitors.unit_displays do iocontrol.fp_monitor_state(i, true) end end -- init all displays in use by the renderer function renderer.init_displays() - -- init primary and flow monitors - _init_display(engine.monitors.primary) + -- init main and flow monitors + _init_display(engine.monitors.main) if not engine.disable_flow_view then _init_display(engine.monitors.flow) end -- init unit displays @@ -94,8 +104,8 @@ end -- initialize the dmesg output window function renderer.init_dmesg() - local disp_w, disp_h = engine.monitors.primary.getSize() - engine.dmesg_window = window.create(engine.monitors.primary, 1, 1, disp_w, disp_h) + local disp_w, disp_h = engine.monitors.main.getSize() + engine.dmesg_window = window.create(engine.monitors.main, 1, 1, disp_w, disp_h) log.direct_dmesg(engine.dmesg_window) end @@ -166,8 +176,8 @@ function renderer.try_start_ui() status, msg = pcall(function () -- show main view on main monitor - if engine.monitors.primary ~= nil then - engine.ui.main_display = DisplayBox{window=engine.monitors.primary,fg_bg=style.root} + if engine.monitors.main ~= nil then + engine.ui.main_display = DisplayBox{window=engine.monitors.main,fg_bg=style.root} main_view(engine.ui.main_display) end @@ -244,14 +254,14 @@ function renderer.handle_disconnect(device) if not engine.monitors then return false end - if engine.monitors.primary == device then + if engine.monitors.main == device then if engine.ui.main_display ~= nil then -- delete element tree and clear root UI elements engine.ui.main_display.delete() end is_used = true - engine.monitors.primary = nil + engine.monitors.main = nil engine.ui.main_display = nil iocontrol.fp_monitor_state("main", false) @@ -298,9 +308,9 @@ function renderer.handle_reconnect(name, device) -- note: handle_resize is a more adaptive way of re-initializing a connected monitor -- since it can handle a monitor being reconnected that isn't the right size - if engine.monitors.primary_name == name then + if engine.monitors.main_name == name then is_used = true - engine.monitors.primary = device + engine.monitors.main = device renderer.handle_resize(name) elseif engine.monitors.flow_name == name then @@ -334,8 +344,8 @@ function renderer.handle_resize(name) if not engine.monitors then return false, false end - if engine.monitors.primary_name == name and engine.monitors.primary then - local device = engine.monitors.primary ---@type table + if engine.monitors.main_name == name and engine.monitors.main then + local device = engine.monitors.main ---@type table -- this is necessary if the bottom left block was broken and on reconnect _init_display(device) @@ -343,9 +353,9 @@ function renderer.handle_resize(name) is_used = true -- resize dmesg window if needed, but don't make it thinner - local disp_w, disp_h = engine.monitors.primary.getSize() + local disp_w, disp_h = engine.monitors.main.getSize() local dmsg_w, _ = engine.dmesg_window.getSize() - engine.dmesg_window.reposition(1, 1, math.max(disp_w, dmsg_w), disp_h, engine.monitors.primary) + engine.dmesg_window.reposition(1, 1, math.max(disp_w, dmsg_w), disp_h, engine.monitors.main) if ui.main_display then ui.main_display.delete() @@ -368,11 +378,7 @@ function renderer.handle_resize(name) ui.main_display = nil end - device.setCursorPos(1, 1) - device.setBackgroundColor(colors.black) - device.setTextColor(colors.red) - device.clear() - device.write("monitor too small") + _print_too_small(device) iocontrol.fp_monitor_state("main", false) is_ok = false @@ -407,11 +413,7 @@ function renderer.handle_resize(name) ui.flow_display = nil end - device.setCursorPos(1, 1) - device.setBackgroundColor(colors.black) - device.setTextColor(colors.red) - device.clear() - device.write("monitor too small") + _print_too_small(device) iocontrol.fp_monitor_state("flow", false) is_ok = false @@ -448,11 +450,7 @@ function renderer.handle_resize(name) ui.unit_displays[idx] = nil end - device.setCursorPos(1, 1) - device.setBackgroundColor(colors.black) - device.setTextColor(colors.red) - device.clear() - device.write("monitor too small") + _print_too_small(device) iocontrol.fp_monitor_state(idx, false) is_ok = false @@ -474,7 +472,7 @@ function renderer.handle_mouse(event) if engine.fp_ready and event.monitor == "terminal" then engine.ui.front_panel.handle_mouse(event) elseif engine.ui_ready then - if event.monitor == engine.monitors.primary_name then + if event.monitor == engine.monitors.main_name then if engine.ui.main_display then engine.ui.main_display.handle_mouse(event) end elseif event.monitor == engine.monitors.flow_name then if engine.ui.flow_display then engine.ui.flow_display.handle_mouse(event) end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 268bec3..10294f8 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -22,9 +22,9 @@ local sounder = require("coordinator.sounder") local apisessions = require("coordinator.session.apisessions") -local COORDINATOR_VERSION = "v1.2.7" +local COORDINATOR_VERSION = "v1.2.11" -local CHUNK_LOAD_DELAY_S = 20.0 +local CHUNK_LOAD_DELAY_S = 30.0 local println = util.println local println_ts = util.println_ts @@ -42,24 +42,33 @@ local log_crypto = coordinator.log_crypto -- mount connected devices (required for monitor setup) ppm.mount_all() +local wait_on_load = true local loaded, monitors = coordinator.load_config() -- if the computer just started, its chunk may have just loaded (...or the user rebooted) -- if monitor config failed, maybe an adjacent chunk containing all or part of a monitor has not loaded yet, so keep trying -while loaded == 2 and os.clock() < CHUNK_LOAD_DELAY_S do +while wait_on_load and loaded == 2 and os.clock() < CHUNK_LOAD_DELAY_S do term.clear() term.setCursorPos(1, 1) println("There was a monitor configuration problem at boot.\n") println("Startup will keep trying every 2s in case of chunk load delays.\n") println(util.sprintf("The configurator will be started in %ds if all attempts fail.\n", math.max(0, CHUNK_LOAD_DELAY_S - os.clock()))) - println("(exit early with ctrl-t)") + println("(click to skip to the configurator)") ----@diagnostic disable-next-line: undefined-field - os.sleep(2) + local timer_id = util.start_timer(2) - -- remount and re-attempt - ppm.mount_all() - loaded, monitors = coordinator.load_config() + while true do + local event, param1 = util.pull_event() + if event == "timer" and param1 == timer_id then + -- remount and re-attempt + ppm.mount_all() + loaded, monitors = coordinator.load_config() + break + elseif event == "mouse_click" or event == "terminate" then + wait_on_load = false + break + end + end end if loaded ~= 0 then @@ -67,9 +76,13 @@ if loaded ~= 0 then local success, error = configure.configure(loaded, monitors) if success then loaded, monitors = coordinator.load_config() - assert(loaded == 0, util.trinary(loaded == 1, "failed to load valid configuration", "monitor configuration invalid")) + if loaded ~= 0 then + println(util.trinary(loaded == 2, "monitor configuration invalid", "failed to load a valid configuration") .. ", please reconfigure") + return + end else - assert(success, "coordinator configuration error: " .. error) + println("configuration error: " .. error) + return end end diff --git a/coordinator/ui/components/unit_flow.lua b/coordinator/ui/components/unit_flow.lua index e0c1519..9dfd4c1 100644 --- a/coordinator/ui/components/unit_flow.lua +++ b/coordinator/ui/components/unit_flow.lua @@ -187,10 +187,10 @@ local function make(parent, x, y, wide, unit) local waste_rate = DataIndicator{parent=waste,x=1,y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=s_field} local pu_rate = DataIndicator{parent=waste,x=_wide(82,70),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=0,width=12,fg_bg=s_field} - local po_rate = DataIndicator{parent=waste,x=_wide(52,45),y=6,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=0,width=12,fg_bg=s_field} - local popl_rate = DataIndicator{parent=waste,x=_wide(82,70),y=6,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=0,width=12,fg_bg=s_field} - local poam_rate = DataIndicator{parent=waste,x=_wide(82,70),y=10,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=0,width=12,fg_bg=s_field} - local spent_rate = DataIndicator{parent=waste,x=_wide(117,99),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=0,width=12,fg_bg=s_field} + local po_rate = DataIndicator{parent=waste,x=_wide(52,45),y=6,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=s_field} + local popl_rate = DataIndicator{parent=waste,x=_wide(82,70),y=6,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=s_field} + local poam_rate = DataIndicator{parent=waste,x=_wide(82,70),y=10,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=0,width=12,fg_bg=s_field} + local spent_rate = DataIndicator{parent=waste,x=_wide(117,98),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%8.3f",value=0,width=13,fg_bg=s_field} waste_rate.register(unit.unit_ps, "act_burn_rate", waste_rate.update) pu_rate.register(unit.unit_ps, "pu_rate", pu_rate.update) @@ -220,7 +220,7 @@ local function make(parent, x, y, wide, unit) sna_act.register(unit.unit_ps, "po_rate", function (r) sna_act.update(r > 0) end) sna_cnt.register(unit.unit_ps, "sna_count", sna_cnt.update) sna_pk.register(unit.unit_ps, "sna_peak_rate", sna_pk.update) - sna_max.register(unit.unit_ps, "sna_prod_rate", sna_max.update) + sna_max.register(unit.unit_ps, "sna_max_rate", sna_max.update) sna_in.register(unit.unit_ps, "sna_in", sna_in.update) return root diff --git a/coordinator/ui/layout/flow_view.lua b/coordinator/ui/layout/flow_view.lua index 03d14d4..39e51f8 100644 --- a/coordinator/ui/layout/flow_view.lua +++ b/coordinator/ui/layout/flow_view.lua @@ -351,7 +351,7 @@ local function init(main) status.register(facility.sps_ps_tbl[1], "computed_status", status.update) TextBox{parent=sps_box,x=2,y=3,text="Input Rate",height=1,width=10,fg_bg=style.label} - local sps_in = DataIndicator{parent=sps_box,x=2,label="",format="%15.3f",value=0,unit="mB/t",lu_colors=lu_col,width=20,fg_bg=s_field} + local sps_in = DataIndicator{parent=sps_box,x=2,label="",format="%15.2f",value=0,unit="mB/t",lu_colors=lu_col,width=20,fg_bg=s_field} sps_in.register(facility.ps, "po_am_rate", sps_in.update) @@ -373,8 +373,8 @@ local function init(main) TextBox{parent=main,x=145,y=21,text="PROC. WASTE",alignment=ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray} local pr_waste = Rectangle{parent=main,x=145,y=22,border=border(1,colors.gray,true),width=19,height=5,thin=true,fg_bg=s_hi_bright} local pu = DataIndicator{parent=pr_waste,lu_colors=lu_c_d,label="Pu",unit="mB/t",format="%9.3f",value=0,width=17} - local po = DataIndicator{parent=pr_waste,lu_colors=lu_c_d,label="Po",unit="mB/t",format="%9.3f",value=0,width=17} - local popl = DataIndicator{parent=pr_waste,lu_colors=lu_c_d,label="PoPl",unit="mB/t",format="%7.3f",value=0,width=17} + local po = DataIndicator{parent=pr_waste,lu_colors=lu_c_d,label="Po",unit="mB/t",format="%9.2f",value=0,width=17} + local popl = DataIndicator{parent=pr_waste,lu_colors=lu_c_d,label="PoPl",unit="mB/t",format="%7.2f",value=0,width=17} pu.register(facility.ps, "pu_rate", pu.update) po.register(facility.ps, "po_rate", po.update) diff --git a/pocket/startup.lua b/pocket/startup.lua index 13ac7fd..bbd8587 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -18,7 +18,7 @@ local iocontrol = require("pocket.iocontrol") local pocket = require("pocket.pocket") local renderer = require("pocket.renderer") -local POCKET_VERSION = "v0.7.0-alpha" +local POCKET_VERSION = "v0.7.1-alpha" local println = util.println local println_ts = util.println_ts @@ -31,9 +31,13 @@ if not pocket.load_config() then -- try to reconfigure (user action) local success, error = configure.configure(true) if success then - assert(pocket.load_config(), "failed to load valid configuration") + if not pocket.load_config() then + println("failed to load a valid configuration, please reconfigure") + return + end else - assert(success, "pocket configuration error: " .. error) + println("configuration error: " .. error) + return end end diff --git a/reactor-plc/databus.lua b/reactor-plc/databus.lua index ba7a68e..7436dbb 100644 --- a/reactor-plc/databus.lua +++ b/reactor-plc/databus.lua @@ -70,9 +70,9 @@ function databus.tx_link_state(state) end -- transmit reactor enable state across the bus ----@param active boolean reactor active +---@param active any reactor active function databus.tx_reactor_state(active) - databus.ps.publish("reactor_active", active) + databus.ps.publish("reactor_active", active == true) end -- transmit RPS data across the bus diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index c5e6be3..eec1e4d 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -129,6 +129,21 @@ function plc.rps_init(reactor, is_formed) end end + -- check if the result of a peripheral call was OK, handle the failure if not + ---@nodiscard + ---@param result any PPM function call result + ---@return boolean succeeded if the result is OK, false if it was a PPM failure + local function _check_and_handle_ppm_call(result) + if result == ppm.ACCESS_FAULT then + _set_fault() + elseif result == ppm.UNDEFINED_FIELD then + _set_fault() + self.formed = false + else return true end + + return false + end + -- set emergency coolant control (if configured) ---@param state boolean true to enable emergency coolant, false to disable local function _set_emer_cool(state) @@ -167,25 +182,20 @@ function plc.rps_init(reactor, is_formed) -- check if the reactor is formed local function _is_formed() local formed = reactor.isFormed() - if formed == ppm.ACCESS_FAULT then - -- lost the peripheral or terminated, handled later - _set_fault() - else + if _check_and_handle_ppm_call(formed) then self.formed = formed + end - if not self.state[state_keys.sys_fail] then - self.state[state_keys.sys_fail] = not formed - end + -- always update, since some ppm failures constitute not being formed + if not self.state[state_keys.sys_fail] then + self.state[state_keys.sys_fail] = not self.formed end end -- check if the reactor is force disabled local function _is_force_disabled() local disabled = reactor.isForceDisabled() - if disabled == ppm.ACCESS_FAULT then - -- lost the peripheral or terminated, handled later - _set_fault() - else + if _check_and_handle_ppm_call(disabled) then self.force_disabled = disabled if not self.state[state_keys.force_disabled] then @@ -197,22 +207,16 @@ function plc.rps_init(reactor, is_formed) -- check for high damage local function _high_damage() local damage_percent = reactor.getDamagePercent() - if damage_percent == ppm.ACCESS_FAULT then - -- lost the peripheral or terminated, handled later - _set_fault() - elseif not self.state[state_keys.high_dmg] then + if _check_and_handle_ppm_call(damage_percent) and not self.state[state_keys.high_dmg] then self.state[state_keys.high_dmg] = damage_percent >= RPS_LIMITS.MAX_DAMAGE_PERCENT end end -- check if the reactor is at a critically high temperature local function _high_temp() - -- mekanism: MAX_DAMAGE_TEMPERATURE = 1_200 + -- mekanism: MAX_DAMAGE_TEMPERATURE = 1200K local temp = reactor.getTemperature() - if temp == ppm.ACCESS_FAULT then - -- lost the peripheral or terminated, handled later - _set_fault() - elseif not self.state[state_keys.high_temp] then + if _check_and_handle_ppm_call(temp) and not self.state[state_keys.high_temp] then self.state[state_keys.high_temp] = temp >= RPS_LIMITS.MAX_DAMAGE_TEMPERATURE end end @@ -220,10 +224,7 @@ function plc.rps_init(reactor, is_formed) -- check if there is very low coolant local function _low_coolant() local coolant_filled = reactor.getCoolantFilledPercentage() - if coolant_filled == ppm.ACCESS_FAULT then - -- lost the peripheral or terminated, handled later - _set_fault() - elseif not self.state[state_keys.low_coolant] then + if _check_and_handle_ppm_call(coolant_filled) and not self.state[state_keys.low_coolant] then self.state[state_keys.low_coolant] = coolant_filled < RPS_LIMITS.MIN_COOLANT_FILL end end @@ -231,10 +232,7 @@ function plc.rps_init(reactor, is_formed) -- check for excess waste (>80% filled) local function _excess_waste() local w_filled = reactor.getWasteFilledPercentage() - if w_filled == ppm.ACCESS_FAULT then - -- lost the peripheral or terminated, handled later - _set_fault() - elseif not self.state[state_keys.ex_waste] then + if _check_and_handle_ppm_call(w_filled) and not self.state[state_keys.ex_waste] then self.state[state_keys.ex_waste] = w_filled > RPS_LIMITS.MAX_WASTE_FILL end end @@ -242,10 +240,7 @@ function plc.rps_init(reactor, is_formed) -- check for heated coolant backup (>95% filled) local function _excess_heated_coolant() local hc_filled = reactor.getHeatedCoolantFilledPercentage() - if hc_filled == ppm.ACCESS_FAULT then - -- lost the peripheral or terminated, handled later - _set_fault() - elseif not self.state[state_keys.ex_hcoolant] then + if _check_and_handle_ppm_call(hc_filled) and not self.state[state_keys.ex_hcoolant] then self.state[state_keys.ex_hcoolant] = hc_filled > RPS_LIMITS.MAX_HEATED_COLLANT_FILL end end @@ -253,10 +248,7 @@ function plc.rps_init(reactor, is_formed) -- check if there is no fuel local function _insufficient_fuel() local fuel = reactor.getFuelFilledPercentage() - if fuel == ppm.ACCESS_FAULT then - -- lost the peripheral or terminated, handled later - _set_fault() - elseif not self.state[state_keys.no_fuel] then + if _check_and_handle_ppm_call(fuel) and not self.state[state_keys.no_fuel] then self.state[state_keys.no_fuel] = fuel <= RPS_LIMITS.NO_FUEL_FILL end end @@ -477,13 +469,22 @@ function plc.rps_init(reactor, is_formed) self.tripped = false self.trip_cause = RPS_TRIP_CAUSE.OK - for i = 1, #self.state do - self.state[i] = false - end + for i = 1, #self.state do self.state[i] = false end if not quiet then log.info("RPS: reset") end end + -- partial RPS reset that only clears fault and sys_fail + function public.reset_formed() + self.tripped = false + self.trip_cause = RPS_TRIP_CAUSE.OK + + self.state[state_keys.fault] = false + self.state[state_keys.sys_fail] = false + + log.info("RPS: partial reset on formed") + end + -- reset the automatic and timeout trip flags, then clear trip if that was the trip cause function public.auto_reset() self.state[state_keys.automatic] = false diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index e667e46..9c3d382 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.6.11" +local R_PLC_VERSION = "v1.6.14" local println = util.println local println_ts = util.println_ts @@ -31,9 +31,13 @@ if not plc.load_config() then -- try to reconfigure (user action) local success, error = configure.configure(true) if success then - assert(plc.load_config(), "failed to load valid configuration") + if not plc.load_config() then + println("failed to load a valid configuration, please reconfigure") + return + end else - assert(success, "reactor PLC configuration error: " .. error) + println("configuration error: " .. error) + return end end @@ -131,15 +135,22 @@ local function main() -- we need a reactor, can at least do some things even if it isn't formed though if plc_state.no_reactor then - println("init> fission reactor not found"); + println("init> fission reactor not found") log.warning("init> no reactor on startup") plc_state.init_ok = false plc_state.degraded = true elseif not smem_dev.reactor.isFormed() then - println("init> fission reactor not formed"); + println("init> fission reactor is not formed") log.warning("init> reactor logic adapter present, but reactor is not formed") + plc_state.degraded = true + plc_state.reactor_formed = false + elseif smem_dev.reactor.getStatus() == ppm.UNDEFINED_FIELD then + -- reactor formed after ppm.mount_all was called + println("init> fission reactor was not formed") + log.warning("init> reactor reported formed, but multiblock functions are not available") + plc_state.degraded = true plc_state.reactor_formed = false end diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 85e2a86..afe6acb 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -125,9 +125,8 @@ function threads.thread__main(smem, init) plc_comms.reconnect_reactor(plc_dev.reactor) end - -- reset RPS for newly connected reactor - -- without this, is_formed will be out of date and cause it to think its no longer formed again - rps.reset() + -- partial reset of RPS, specific to becoming formed + rps.reset_formed() else -- fully lost the reactor now :( println_ts("reactor lost (failed reconnect)!") @@ -231,9 +230,8 @@ function threads.thread__main(smem, init) plc_comms.reconnect_reactor(plc_dev.reactor) end - -- reset RPS for newly connected reactor - -- without this, is_formed will be out of date and cause it to think its no longer formed again - rps.reset() + -- partial reset of RPS, specific to becoming formed + rps.reset_formed() end elseif networked and type == "modem" then -- note, check init_ok first since nic will be nil if it is false diff --git a/rtu/startup.lua b/rtu/startup.lua index d58639c..1c5a4a2 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.7.13" +local RTU_VERSION = "v1.7.14" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE @@ -47,9 +47,13 @@ if not rtu.load_config() then -- try to reconfigure (user action) local success, error = configure.configure(true) if success then - assert(rtu.load_config(), "failed to load valid configuration") + if not rtu.load_config() then + println("failed to load a valid configuration, please reconfigure") + return + end else - assert(success, "RTU configuration error: " .. error) + println("configuration error: " .. error) + return end end diff --git a/scada-common/comms.lua b/scada-common/comms.lua index eaa7ec4..180e9c8 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -17,7 +17,7 @@ local max_distance = nil local comms = {} -- protocol/data version (protocol/data independent changes tracked by util.lua version) -comms.version = "2.4.4" +comms.version = "2.4.5" ---@enum PROTOCOL local PROTOCOL = { diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua index 6acb7aa..fb98161 100644 --- a/scada-common/ppm.lua +++ b/scada-common/ppm.lua @@ -9,7 +9,7 @@ local util = require("scada-common.util") local ppm = {} local ACCESS_FAULT = nil ---@type nil -local UNDEFINED_FIELD = "undefined field" +local UNDEFINED_FIELD = "__PPM_UNDEF_FIELD__" local VIRTUAL_DEVICE_TYPE = "ppm_vdev" ppm.ACCESS_FAULT = ACCESS_FAULT @@ -155,7 +155,7 @@ local function peri_init(iface) self.fault_counts[key] = self.fault_counts[key] + 1 - return (function () return ACCESS_FAULT end) + return (function () return UNDEFINED_FIELD end) end } @@ -306,7 +306,7 @@ function ppm.log_mounts() log.info(util.c("PPM: had found a ", mount.type, " (", iface, ")")) end - if #ppm_sys.mounts == 0 then + if util.table_len(ppm_sys.mounts) == 0 then log.warning("PPM: no devices had been found") end end diff --git a/scada-common/util.lua b/scada-common/util.lua index 308bb69..c72d62e 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -22,7 +22,7 @@ local t_pack = table.pack local util = {} -- scada-common version -util.version = "1.1.16" +util.version = "1.1.18" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 @@ -284,11 +284,13 @@ function util.cancel_timer(timer) os.cancelTimer(timer) end --#region PARALLELIZATION --- protected sleep call so we still are in charge of catching termination ----@param t integer seconds +-- protected sleep call so we still are in charge of catching termination
+-- returns the result of pcall +---@param t number seconds +---@return boolean success, any result, any ... --- EVENT_CONSUMER: this function consumes events ---@diagnostic disable-next-line: undefined-field -function util.psleep(t) pcall(os.sleep, t) end +function util.psleep(t) return pcall(os.sleep, t) end -- no-op to provide a brief pause (1 tick) to yield
--- EVENT_CONSUMER: this function consumes events diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 94f93b3..43c6cc4 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -152,6 +152,8 @@ function facility.new(num_reactors, cooling_conf) table.insert(self.test_tone_states, false) end + -- PRIVATE FUNCTIONS -- + -- check if all auto-controlled units completed ramping ---@nodiscard local function _all_units_ramped() @@ -228,7 +230,7 @@ function facility.new(num_reactors, cooling_conf) ---@class facility local public = {} - -- ADD/LINK DEVICES -- + --#region Add/Link Devices -- link a redstone RTU session ---@param rs_unit unit_session @@ -268,11 +270,9 @@ function facility.new(num_reactors, cooling_conf) for _, v in pairs(self.rtu_list) do util.filter_table(v, function (s) return s.get_session_id() ~= session end) end end - -- UPDATE -- + --#endregion - -- supervisor sessions reporting the list of active RTU sessions - ---@param rtu_sessions table session list of all connected RTUs - function public.report_rtus(rtu_sessions) self.rtu_conn_count = #rtu_sessions end + --#region Update -- update (iterate) the facility management function public.update() @@ -323,7 +323,7 @@ function facility.new(num_reactors, cooling_conf) -- Run Process Control -- ------------------------- - --#region Process Control + --#region local avg_charge = self.avg_charge.compute() local avg_inflow = self.avg_inflow.compute() @@ -597,7 +597,7 @@ function facility.new(num_reactors, cooling_conf) -- Evaluate Automatic SCRAM -- ------------------------------ - --#region Automatic SCRAM + --#region local astatus = self.ascram_status @@ -727,6 +727,8 @@ function facility.new(num_reactors, cooling_conf) -- Handle Redstone I/O -- ------------------------- + --#region + if #self.redstone > 0 then -- handle facility SCRAM if self.io_ctl.digital_read(IO.F_SCRAM) then @@ -756,10 +758,14 @@ function facility.new(num_reactors, cooling_conf) self.io_ctl.digital_write(IO.F_ALARM_ANY, has_any_alarm) end + --#endregion + ---------------- -- Unit Tasks -- ---------------- + --#region + local insufficent_po_rate = false local need_emcool = false @@ -798,10 +804,14 @@ function facility.new(num_reactors, cooling_conf) end end + --#endregion + ------------------------ -- Update Alarm Tones -- ------------------------ + --#region + local allow_test = self.allow_testing and self.test_tone_set local alarms = { false, false, false, false, false, false, false, false, false, false, false, false } @@ -888,6 +898,8 @@ function facility.new(num_reactors, cooling_conf) self.test_tone_set = false self.test_tone_reset = true end + + --#endregion end -- call the update function of all units in the facility
@@ -900,7 +912,9 @@ function facility.new(num_reactors, cooling_conf) end end - -- COMMANDS -- + --#endregion + + --#region Commands -- SCRAM all reactor units function public.scram_all() @@ -988,7 +1002,9 @@ function facility.new(num_reactors, cooling_conf) } end - -- SETTINGS -- + --#endregion + + --#region Settings -- set the automatic control group of a unit ---@param unit_id integer unit ID @@ -1029,7 +1045,9 @@ function facility.new(num_reactors, cooling_conf) return self.pu_fallback end - -- DIAGNOSTIC TESTING -- + --#endregion + + --#region Diagnostic Testing -- attempt to set a test tone state ---@param id TONE|0 tone ID or 0 to disable all @@ -1069,7 +1087,9 @@ function facility.new(num_reactors, cooling_conf) return self.allow_testing, self.test_alarm_states end - -- READ STATES/PROPERTIES -- + --#endregion + + --#region Read States/Properties -- get current alarm tone on/off states ---@nodiscard @@ -1183,6 +1203,12 @@ function facility.new(num_reactors, cooling_conf) return status end + --#endregion + + -- supervisor sessions reporting the list of active RTU sessions + ---@param rtu_sessions table session list of all connected RTUs + function public.report_rtus(rtu_sessions) self.rtu_conn_count = #rtu_sessions end + -- get the units in this facility ---@nodiscard function public.get_units() return self.units end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 69633fa..72c0c97 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.2.8" +local SUPERVISOR_VERSION = "v1.2.11" local println = util.println local println_ts = util.println_ts @@ -34,9 +34,13 @@ if not supervisor.load_config() then -- try to reconfigure (user action) local success, error = configure.configure(true) if success then - assert(supervisor.load_config(), "failed to load valid configuration") + if not supervisor.load_config() then + println("failed to load a valid configuration, please reconfigure") + return + end else - assert(success, "supervisor configuration error: " .. error) + println("configuration error: " .. error) + return end end diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 8d38222..afdf6f3 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -77,7 +77,6 @@ function unit.new(reactor_id, num_boilers, num_turbines) tanks = {}, snas = {}, envd = {}, - sna_prod_rate = 0, -- redstone control io_ctl = nil, ---@type rs_controller valves = {}, ---@type unit_valves @@ -256,7 +255,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) -- PRIVATE FUNCTIONS -- - --#region time derivative utility functions + --#region Time Derivative Utility Functions -- compute a change with respect to time of the given value ---@param key string value key @@ -331,7 +330,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) --#endregion - --#region redstone I/O + --#region Redstone I/O -- create a generic valve interface ---@nodiscard @@ -398,8 +397,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) ---@class reactor_unit local public = {} - -- ADD/LINK DEVICES -- - --#region + --#region Add/Link Devices -- link the PLC ---@param plc_session plc_session_struct @@ -489,7 +487,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) --#endregion - -- UPDATE SESSION -- + --#region Update Session -- update (iterate) this unit function public.update() @@ -557,8 +555,9 @@ function unit.new(reactor_id, num_boilers, num_turbines) end end - -- AUTO CONTROL OPERATIONS -- - --#region + --#endregion + + --#region Auto Control Operations -- engage automatic control function public.auto_engage() @@ -645,8 +644,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) --#endregion - -- OPERATIONS -- - --#region + --#region Operations -- queue a command to disable the reactor function public.disable() @@ -726,8 +724,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) --#endregion - -- READ STATES/PROPERTIES -- - --#region + --#region Read States/Properties -- check if an alarm of at least a certain priority level is tripped ---@nodiscard @@ -857,13 +854,15 @@ function unit.new(reactor_id, num_boilers, num_turbines) status.tanks[tank.get_device_idx()] = { tank.is_faulted(), db.formed, db.state, db.tanks } end - -- basic SNA statistical information - local total_peak = 0 + -- SNA statistical information + local total_peak, total_avail, total_out = 0, 0, 0 for i = 1, #self.snas do local db = self.snas[i].get_db() ---@type sna_session_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) end - status.sna = { #self.snas, public.get_sna_rate(), total_peak } + status.sna = { #self.snas, total_peak, total_avail, total_out } -- radiation monitors (environment detectors) status.envds = {} @@ -876,7 +875,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) return status end - -- get the current total [max] production rate is + -- get the current total max production rate ---@nodiscard ---@return number total_avail_rate function public.get_sna_rate() diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index e207c44..f43ff03 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -54,9 +54,7 @@ function logic.update_annunciator(self) -- variables for boiler, or reactor if no boilers used local total_boil_rate = 0.0 - ------------- - -- REACTOR -- - ------------- + --#region Reactor annunc.AutoControl = self.auto_engaged @@ -143,9 +141,9 @@ function logic.update_annunciator(self) self.plc_cache.ok = false end - --------------- - -- MISC RTUs -- - --------------- + --#endregion + + --#region Misc RTUs local max_rad, any_faulted = 0, false @@ -170,9 +168,9 @@ function logic.update_annunciator(self) end end - ------------- - -- BOILERS -- - ------------- + --#endregion + + --#region Boilers local boilers_ready = num_boilers == #self.boilers @@ -230,9 +228,9 @@ function logic.update_annunciator(self) boiler_water_dt_sum = _get_dt(DT_KEYS.ReactorCCool) end - --------------------------- - -- COOLANT FEED MISMATCH -- - --------------------------- + --#endregion + + --#region Coolant Feed Mismatch -- check coolant feed mismatch if using boilers, otherwise calculate with reactor local cfmismatch = false @@ -263,9 +261,9 @@ function logic.update_annunciator(self) annunc.CoolantFeedMismatch = cfmismatch - -------------- - -- TURBINES -- - -------------- + --#endregion + + --#region Turbines local turbines_ready = num_turbines == #self.turbines @@ -340,6 +338,8 @@ function logic.update_annunciator(self) annunc.TurbineTrip[idx] = has_steam and db.state.flow_rate == 0 end + --#endregion + -- update auto control ready state for this unit self.db.control.ready = plc_ready and boilers_ready and turbines_ready end