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