diff --git a/coordinator/configure.lua b/coordinator/configure.lua
index 7abb267..7c3fe58 100644
--- a/coordinator/configure.lua
+++ b/coordinator/configure.lua
@@ -39,7 +39,9 @@ local CENTER = core.ALIGN.CENTER
local RIGHT = core.ALIGN.RIGHT
-- changes to the config data/format to let the user know
-local changes = {}
+local changes = {
+ {"v1.2.4", { "Added temperature scale options" } }
+}
---@class crd_configurator
local configurator = {}
@@ -119,6 +121,7 @@ local tmp_cfg = {
UnitCount = 1,
SpeakerVolume = 1.0,
Time24Hour = true,
+ TempScale = 1,
DisableFlowView = false,
MainDisplay = nil, ---@type string
FlowDisplay = nil, ---@type string
@@ -148,6 +151,7 @@ local fields = {
{ "UnitDisplays", "Unit Monitors", {} },
{ "SpeakerVolume", "Speaker Volume", 1.0 },
{ "Time24Hour", "Use 24-hour Time Format", true },
+ { "TempScale", "Temperature Scale", 1 },
{ "DisableFlowView", "Disable Flow Monitor (legacy, discouraged)", false },
{ "SVR_Channel", "SVR Channel", 16240 },
{ "CRD_Channel", "CRD Channel", 16243 },
@@ -465,7 +469,7 @@ local function config_view(display)
key_err.hide(true)
-- init mac for supervisor connection
- if string.len(v) >= 8 then network.init_mac(tmp_cfg.AuthKey) end
+ if string.len(v) >= 8 then network.init_mac(tmp_cfg.AuthKey) else network.deinit_mac() end
main_pane.set_value(3)
@@ -739,8 +743,12 @@ local function config_view(display)
TextBox{parent=crd_c_1,x=1,y=4,height=1,text="Clock Time Format"}
local clock_fmt = RadioButton{parent=crd_c_1,x=1,y=5,default=util.trinary(ini_cfg.Time24Hour,1,2),options={"24-Hour","12-Hour"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
+ TextBox{parent=crd_c_1,x=1,y=8,height=1,text="Temperature Scale"}
+ local temp_scale = RadioButton{parent=crd_c_1,x=1,y=9,default=ini_cfg.TempScale,options={"Kelvin","Celsius","Fahrenheit","Rankine"},callback=function()end,radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.lime}
+
local function submit_ui_opts()
tmp_cfg.Time24Hour = clock_fmt.get_value() == 1
+ tmp_cfg.TempScale = temp_scale.get_value()
main_pane.set_value(7)
end
@@ -1186,6 +1194,8 @@ local function config_view(display)
if f[1] == "AuthKey" then val = string.rep("*", string.len(val))
elseif f[1] == "LogMode" then val = util.trinary(raw == log.MODE.APPEND, "append", "replace")
+ elseif f[1] == "TempScale" then
+ if raw == 1 then val = "Kelvin" elseif raw == 2 then val = "Celsius" elseif raw == 3 then val = "Fahrenheit" else val = "Rankine" end
elseif f[1] == "UnitDisplays" and type(cfg.UnitDisplays) == "table" then
val = ""
for idx = 1, #cfg.UnitDisplays do
diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua
index ae2ab77..bc97c99 100644
--- a/coordinator/coordinator.lua
+++ b/coordinator/coordinator.lua
@@ -35,6 +35,7 @@ function coordinator.load_config()
config.UnitCount = settings.get("UnitCount")
config.SpeakerVolume = settings.get("SpeakerVolume")
config.Time24Hour = settings.get("Time24Hour")
+ config.TempScale = settings.get("TempScale")
config.DisableFlowView = settings.get("DisableFlowView")
config.MainDisplay = settings.get("MainDisplay")
@@ -58,6 +59,8 @@ function coordinator.load_config()
cfv.assert_type_int(config.UnitCount)
cfv.assert_range(config.UnitCount, 1, 4)
cfv.assert_type_bool(config.Time24Hour)
+ cfv.assert_type_int(config.TempScale)
+ cfv.assert_range(config.TempScale, 1, 4)
cfv.assert_type_bool(config.DisableFlowView)
cfv.assert_type_table(config.UnitDisplays)
@@ -92,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 = {}
@@ -118,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
@@ -299,7 +302,7 @@ function coordinator.comms(version, nic, sv_watchdog)
if not self.sv_linked then
if self.est_tick_waiting == nil then
- self.est_start = util.time_s()
+ self.est_start = os.clock()
self.est_last = self.est_start
self.est_tick_waiting, self.est_task_done =
@@ -307,10 +310,10 @@ function coordinator.comms(version, nic, sv_watchdog)
_send_establish()
else
- self.est_tick_waiting(math.max(0, LINK_TIMEOUT - (util.time_s() - self.est_start)))
+ self.est_tick_waiting(math.max(0, LINK_TIMEOUT - (os.clock() - self.est_start)))
end
- if abort or (util.time_s() - self.est_start) >= LINK_TIMEOUT then
+ if abort or (os.clock() - self.est_start) >= LINK_TIMEOUT then
self.est_task_done(false)
if abort then
@@ -333,9 +336,9 @@ function coordinator.comms(version, nic, sv_watchdog)
elseif self.sv_config_err then
coordinator.log_comms("supervisor unit count does not match coordinator unit count, check configs")
ok = false
- elseif (util.time_s() - self.est_last) > 1.0 then
+ elseif (os.clock() - self.est_last) > 1.0 then
_send_establish()
- self.est_last = util.time_s()
+ self.est_last = os.clock()
end
elseif self.est_tick_waiting ~= nil then
self.est_task_done(true)
@@ -676,7 +679,7 @@ function coordinator.comms(version, nic, sv_watchdog)
if conf.num_units == config.UnitCount then
-- init io controller
- iocontrol.init(conf, public)
+ iocontrol.init(conf, public, config.TempScale)
self.sv_addr = src_addr
self.sv_linked = true
diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua
index 3e32d6d..312d194 100644
--- a/coordinator/iocontrol.lua
+++ b/coordinator/iocontrol.lua
@@ -47,7 +47,23 @@ end
-- initialize the coordinator IO controller
---@param conf facility_conf configuration
---@param comms coord_comms comms reference
-function iocontrol.init(conf, comms)
+---@param temp_scale integer temperature unit (1 = K, 2 = C, 3 = F, 4 = R)
+function iocontrol.init(conf, comms, temp_scale)
+ -- temperature unit label and conversion function (from Kelvin)
+ if temp_scale == 2 then
+ io.temp_label = "\xb0C"
+ io.temp_convert = function (t) return t - 273.15 end
+ elseif temp_scale == 3 then
+ io.temp_label = "\xb0F"
+ io.temp_convert = function (t) return (1.8 * (t - 273.15)) + 32 end
+ elseif temp_scale == 4 then
+ io.temp_label = "\xb0R"
+ io.temp_convert = function (t) return 1.8 * t end
+ else
+ io.temp_label = "K"
+ io.temp_convert = function (t) return t end
+ end
+
-- facility data structure
---@class ioctl_facility
io.facility = {
@@ -219,7 +235,10 @@ function iocontrol.init(conf, comms)
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,
@@ -923,7 +942,7 @@ function iocontrol.update_unit_statuses(statuses)
local boil_sum = 0
for id = 1, #unit.boiler_ps_tbl do
- if rtu_statuses.boilers[i] == nil then
+ if rtu_statuses.boilers[id] == nil then
-- disconnected
unit.boiler_ps_tbl[id].publish("computed_status", 1)
end
@@ -966,7 +985,7 @@ function iocontrol.update_unit_statuses(statuses)
local flow_sum = 0
for id = 1, #unit.turbine_ps_tbl do
- if rtu_statuses.turbines[i] == nil then
+ if rtu_statuses.turbines[id] == nil then
-- disconnected
unit.turbine_ps_tbl[id].publish("computed_status", 1)
end
@@ -1009,7 +1028,7 @@ function iocontrol.update_unit_statuses(statuses)
-- dynamic tank statuses
if type(rtu_statuses.tanks) == "table" then
for id = 1, #unit.tank_ps_tbl do
- if rtu_statuses.tanks[i] == nil then
+ if rtu_statuses.tanks[id] == nil then
-- disconnected
unit.tank_ps_tbl[id].publish("computed_status", 1)
end
@@ -1048,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
@@ -1201,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)
@@ -1209,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 07282c4..541b1a7 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_x, disp_y = engine.monitors.primary.getSize()
- engine.dmesg_window = window.create(engine.monitors.primary, 1, 1, disp_x, disp_y)
+ 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
@@ -242,43 +252,43 @@ function renderer.ui_ready() return engine.ui_ready end
function renderer.handle_disconnect(device)
local is_used = false
- if engine.monitors ~= nil then
- if engine.monitors.primary == device then
- if engine.ui.main_display ~= nil then
- -- delete element tree and clear root UI elements
- engine.ui.main_display.delete()
- end
+ if not engine.monitors then return false end
- is_used = true
- engine.monitors.primary = nil
- engine.ui.main_display = nil
+ 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
- iocontrol.fp_monitor_state("main", false)
- elseif engine.monitors.flow == device then
- if engine.ui.flow_display ~= nil then
- -- delete element tree and clear root UI elements
- engine.ui.flow_display.delete()
- end
+ is_used = true
+ engine.monitors.main = nil
+ engine.ui.main_display = nil
- is_used = true
- engine.monitors.flow = nil
- engine.ui.flow_display = nil
+ iocontrol.fp_monitor_state("main", false)
+ elseif engine.monitors.flow == device then
+ if engine.ui.flow_display ~= nil then
+ -- delete element tree and clear root UI elements
+ engine.ui.flow_display.delete()
+ end
- iocontrol.fp_monitor_state("flow", false)
- else
- for idx, monitor in pairs(engine.monitors.unit_displays) do
- if monitor == device then
- if engine.ui.unit_displays[idx] ~= nil then
- engine.ui.unit_displays[idx].delete()
- end
+ is_used = true
+ engine.monitors.flow = nil
+ engine.ui.flow_display = nil
- is_used = true
- engine.monitors.unit_displays[idx] = nil
- engine.ui.unit_displays[idx] = nil
-
- iocontrol.fp_monitor_state(idx, false)
- break
+ iocontrol.fp_monitor_state("flow", false)
+ else
+ for idx, monitor in pairs(engine.monitors.unit_displays) do
+ if monitor == device then
+ if engine.ui.unit_displays[idx] ~= nil then
+ engine.ui.unit_displays[idx].delete()
end
+
+ is_used = true
+ engine.monitors.unit_displays[idx] = nil
+ engine.ui.unit_displays[idx] = nil
+
+ iocontrol.fp_monitor_state(idx, false)
+ break
end
end
end
@@ -293,52 +303,29 @@ end
function renderer.handle_reconnect(name, device)
local is_used = false
- if engine.monitors ~= nil then
- if engine.monitors.primary_name == name then
- is_used = true
- _init_display(device)
- engine.monitors.primary = device
+ if not engine.monitors then return false end
- local disp_x, disp_y = engine.monitors.primary.getSize()
- engine.dmesg_window.reposition(1, 1, disp_x, disp_y, engine.monitors.primary)
+ -- 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.ui_ready and (engine.ui.main_display == nil) then
- engine.dmesg_window.setVisible(false)
+ if engine.monitors.main_name == name then
+ is_used = true
+ engine.monitors.main = device
- engine.ui.main_display = DisplayBox{window=device,fg_bg=style.root}
- main_view(engine.ui.main_display)
- else
- engine.dmesg_window.setVisible(true)
- engine.dmesg_window.redraw()
- end
+ renderer.handle_resize(name)
+ elseif engine.monitors.flow_name == name then
+ is_used = true
+ engine.monitors.flow = device
- iocontrol.fp_monitor_state("main", true)
- elseif engine.monitors.flow_name == name then
- is_used = true
- _init_display(device)
- engine.monitors.flow = device
+ renderer.handle_resize(name)
+ else
+ for idx, monitor in ipairs(engine.monitors.unit_name_map) do
+ if monitor == name then
+ is_used = true
+ engine.monitors.unit_displays[idx] = device
- if engine.ui_ready and (engine.ui.flow_display == nil) then
- engine.ui.flow_display = DisplayBox{window=device,fg_bg=style.root}
- flow_view(engine.ui.flow_display)
- end
-
- iocontrol.fp_monitor_state("flow", true)
- else
- for idx, monitor in ipairs(engine.monitors.unit_name_map) do
- if monitor == name then
- is_used = true
- _init_display(device)
- engine.monitors.unit_displays[idx] = device
-
- if engine.ui_ready and (engine.ui.unit_displays[idx] == nil) then
- engine.ui.unit_displays[idx] = DisplayBox{window=device,fg_bg=style.root}
- unit_view(engine.ui.unit_displays[idx], idx)
- end
-
- iocontrol.fp_monitor_state(idx, true)
- break
- end
+ renderer.handle_resize(name)
+ break
end
end
end
@@ -346,6 +333,137 @@ function renderer.handle_reconnect(name, device)
return is_used
end
+-- handle a monitor being resized
+-- returns if this monitor is assigned + if the assigned screen still fits
+---@param name string monitor name
+---@return boolean is_used, boolean is_ok
+function renderer.handle_resize(name)
+ local is_used = false
+ local is_ok = true
+ local ui = engine.ui
+
+ if not engine.monitors then return false, false end
+
+ 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)
+
+ is_used = true
+
+ -- resize dmesg window if needed, but don't make it thinner
+ 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.main)
+
+ if ui.main_display then
+ ui.main_display.delete()
+ ui.main_display = nil
+ end
+
+ iocontrol.fp_monitor_state("main", true)
+
+ engine.dmesg_window.setVisible(not engine.ui_ready)
+
+ if engine.ui_ready then
+ local ok = pcall(function ()
+ ui.main_display = DisplayBox{window=device,fg_bg=style.root}
+ main_view(ui.main_display)
+ end)
+
+ if not ok then
+ if ui.main_display then
+ ui.main_display.delete()
+ ui.main_display = nil
+ end
+
+ _print_too_small(device)
+
+ iocontrol.fp_monitor_state("main", false)
+ is_ok = false
+ end
+ else engine.dmesg_window.redraw() end
+ elseif engine.monitors.flow_name == name and engine.monitors.flow then
+ local device = engine.monitors.flow ---@type table
+
+ -- this is necessary if the bottom left block was broken and on reconnect
+ _init_display(device)
+
+ is_used = true
+
+ if ui.flow_display then
+ ui.flow_display.delete()
+ ui.flow_display = nil
+ end
+
+ iocontrol.fp_monitor_state("flow", true)
+
+ if engine.ui_ready then
+ engine.dmesg_window.setVisible(false)
+
+ local ok = pcall(function ()
+ ui.flow_display = DisplayBox{window=device,fg_bg=style.root}
+ flow_view(ui.flow_display)
+ end)
+
+ if not ok then
+ if ui.flow_display then
+ ui.flow_display.delete()
+ ui.flow_display = nil
+ end
+
+ _print_too_small(device)
+
+ iocontrol.fp_monitor_state("flow", false)
+ is_ok = false
+ end
+ end
+ else
+ for idx, monitor in ipairs(engine.monitors.unit_name_map) do
+ local device = engine.monitors.unit_displays[idx]
+
+ if monitor == name and device then
+ -- this is necessary if the bottom left block was broken and on reconnect
+ _init_display(device)
+
+ is_used = true
+
+ if ui.unit_displays[idx] then
+ ui.unit_displays[idx].delete()
+ ui.unit_displays[idx] = nil
+ end
+
+ iocontrol.fp_monitor_state(idx, true)
+
+ if engine.ui_ready then
+ engine.dmesg_window.setVisible(false)
+
+ local ok = pcall(function ()
+ ui.unit_displays[idx] = DisplayBox{window=device,fg_bg=style.root}
+ unit_view(ui.unit_displays[idx], idx)
+ end)
+
+ if not ok then
+ if ui.unit_displays[idx] then
+ ui.unit_displays[idx].delete()
+ ui.unit_displays[idx] = nil
+ end
+
+ _print_too_small(device)
+
+ iocontrol.fp_monitor_state(idx, false)
+ is_ok = false
+ end
+ end
+
+ break
+ end
+ end
+ end
+
+ return is_used, is_ok
+end
-- handle a touch event
---@param event mouse_interaction|nil
@@ -354,16 +472,15 @@ 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
- engine.ui.main_display.handle_mouse(event)
+ 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
- engine.ui.flow_display.handle_mouse(event)
+ if engine.ui.flow_display then engine.ui.flow_display.handle_mouse(event) end
else
for id, monitor in ipairs(engine.monitors.unit_name_map) do
- if event.monitor == monitor then
- local layout = engine.ui.unit_displays[id] ---@type graphics_element
- layout.handle_mouse(event)
- break
+ local display = engine.ui.unit_displays[id]
+ if event.monitor == monitor and display then
+ if display then display.handle_mouse(event) end
end
end
end
diff --git a/coordinator/startup.lua b/coordinator/startup.lua
index 5831b8a..10294f8 100644
--- a/coordinator/startup.lua
+++ b/coordinator/startup.lua
@@ -22,7 +22,9 @@ local sounder = require("coordinator.sounder")
local apisessions = require("coordinator.session.apisessions")
-local COORDINATOR_VERSION = "v1.2.2"
+local COORDINATOR_VERSION = "v1.2.11"
+
+local CHUNK_LOAD_DELAY_S = 30.0
local println = util.println
local println_ts = util.println_ts
@@ -40,15 +42,47 @@ 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 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("(click to skip to the configurator)")
+
+ local timer_id = util.start_timer(2)
+
+ 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
-- try to reconfigure (user action)
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
@@ -79,8 +113,8 @@ local function main()
-- system startup
----------------------------------------
- -- re-mount devices now that logging is ready
- ppm.mount_all()
+ -- log mounts now since mounting was done before logging was ready
+ ppm.log_mounts()
-- report versions/init fp PSIL
iocontrol.init_fp(COORDINATOR_VERSION, comms.version)
@@ -269,6 +303,11 @@ local function main()
iocontrol.fp_has_speaker(true)
end
end
+ elseif event == "monitor_resize" then
+ local is_used, is_ok = renderer.handle_resize(param1)
+ if is_used then
+ log_sys(util.c("configured monitor ", param1, " resized, ", util.trinary(is_ok, "display still fits", "display no longer fits")))
+ end
elseif event == "timer" then
if loop_clock.is_clock(param1) then
-- main loop tick
diff --git a/coordinator/ui/components/boiler.lua b/coordinator/ui/components/boiler.lua
index 0d651d9..ce2f1bc 100644
--- a/coordinator/ui/components/boiler.lua
+++ b/coordinator/ui/components/boiler.lua
@@ -1,5 +1,7 @@
local style = require("coordinator.ui.style")
+local iocontrol = require("coordinator.iocontrol")
+
local core = require("graphics.core")
local Rectangle = require("graphics.elements.rectangle")
@@ -13,6 +15,7 @@ local cpair = core.cpair
local border = core.border
local text_fg_bg = style.text_colors
+local lu_col = style.lu_colors
-- new boiler view
---@param root graphics_element parent
@@ -20,14 +23,16 @@ local text_fg_bg = style.text_colors
---@param y integer top left y
---@param ps psil ps interface
local function new_view(root, x, y, ps)
+ local db = iocontrol.get_db()
+
local boiler = Rectangle{parent=root,border=border(1,colors.gray,true),width=31,height=7,x=x,y=y}
local status = StateIndicator{parent=boiler,x=9,y=1,states=style.boiler.states,value=1,min_width=12}
- local temp = DataIndicator{parent=boiler,x=5,y=3,lu_colors=style.lu_col,label="Temp:",unit="K",format="%10.2f",value=0,width=22,fg_bg=text_fg_bg}
- local boil_r = DataIndicator{parent=boiler,x=5,y=4,lu_colors=style.lu_col,label="Boil:",unit="mB/t",format="%10.0f",value=0,commas=true,width=22,fg_bg=text_fg_bg}
+ local temp = DataIndicator{parent=boiler,x=5,y=3,lu_colors=lu_col,label="Temp:",unit=db.temp_label,format="%10.2f",value=0,commas=true,width=22,fg_bg=text_fg_bg}
+ local boil_r = DataIndicator{parent=boiler,x=5,y=4,lu_colors=lu_col,label="Boil:",unit="mB/t",format="%10.0f",value=0,commas=true,width=22,fg_bg=text_fg_bg}
status.register(ps, "computed_status", status.update)
- temp.register(ps, "temperature", temp.update)
+ temp.register(ps, "temperature", function (t) temp.update(db.temp_convert(t)) end)
boil_r.register(ps, "boil_rate", boil_r.update)
TextBox{parent=boiler,text="H",x=2,y=5,height=1,width=1,fg_bg=text_fg_bg}
diff --git a/coordinator/ui/components/reactor.lua b/coordinator/ui/components/reactor.lua
index c74ab0a..27b033e 100644
--- a/coordinator/ui/components/reactor.lua
+++ b/coordinator/ui/components/reactor.lua
@@ -1,5 +1,7 @@
local types = require("scada-common.types")
+local iocontrol = require("coordinator.iocontrol")
+
local style = require("coordinator.ui.style")
local core = require("graphics.core")
@@ -23,15 +25,17 @@ local lu_col = style.lu_colors
---@param y integer top left y
---@param ps psil ps interface
local function new_view(root, x, y, ps)
+ local db = iocontrol.get_db()
+
local reactor = Rectangle{parent=root,border=border(1, colors.gray, true),width=30,height=7,x=x,y=y}
local status = StateIndicator{parent=reactor,x=6,y=1,states=style.reactor.states,value=1,min_width=16}
- local core_temp = DataIndicator{parent=reactor,x=2,y=3,lu_colors=lu_col,label="Core Temp:",unit="K",format="%10.2f",value=0,width=26,fg_bg=text_fg_bg}
+ local core_temp = DataIndicator{parent=reactor,x=2,y=3,lu_colors=lu_col,label="Core Temp:",unit=db.temp_label,format="%10.2f",value=0,commas=true,width=26,fg_bg=text_fg_bg}
local burn_r = DataIndicator{parent=reactor,x=2,y=4,lu_colors=lu_col,label="Burn Rate:",unit="mB/t",format="%10.2f",value=0,width=26,fg_bg=text_fg_bg}
local heating_r = DataIndicator{parent=reactor,x=2,y=5,lu_colors=lu_col,label="Heating:",unit="mB/t",format="%12.0f",value=0,commas=true,width=26,fg_bg=text_fg_bg}
status.register(ps, "computed_status", status.update)
- core_temp.register(ps, "temp", core_temp.update)
+ core_temp.register(ps, "temp", function (t) core_temp.update(db.temp_convert(t)) end)
burn_r.register(ps, "act_burn_rate", burn_r.update)
heating_r.register(ps, "heating_rate", heating_r.update)
diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua
index 52b633a..64fc1e0 100644
--- a/coordinator/ui/components/unit_detail.lua
+++ b/coordinator/ui/components/unit_detail.lua
@@ -3,6 +3,7 @@
--
local types = require("scada-common.types")
+local util = require("scada-common.util")
local iocontrol = require("coordinator.iocontrol")
@@ -51,8 +52,9 @@ local period = core.flasher.PERIOD
---@param parent graphics_element parent
---@param id integer
local function init(parent, id)
- local unit = iocontrol.get_db().units[id] ---@type ioctl_unit
- local f_ps = iocontrol.get_db().facility.ps
+ local db = iocontrol.get_db()
+ local unit = db.units[id] ---@type ioctl_unit
+ local f_ps = db.facility.ps
local main = Div{parent=parent,x=1,y=1}
@@ -114,8 +116,9 @@ local function init(parent, id)
end)
TextBox{parent=main,x=32,y=22,text="Core Temp",height=1,width=9,fg_bg=style.label}
- local core_temp = DataIndicator{parent=main,x=32,label="",format="%11.2f",value=0,unit="K",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg}
- core_temp.register(u_ps, "temp", core_temp.update)
+ local fmt = util.trinary(string.len(db.temp_label) == 2, "%10.2f", "%11.2f")
+ local core_temp = DataIndicator{parent=main,x=32,label="",format=fmt,value=0,commas=true,unit=db.temp_label,lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg}
+ core_temp.register(u_ps, "temp", function (t) core_temp.update(db.temp_convert(t)) end)
TextBox{parent=main,x=32,y=25,text="Burn Rate",height=1,width=9,fg_bg=style.label}
local act_burn_r = DataIndicator{parent=main,x=32,label="",format="%8.2f",value=0,unit="mB/t",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg}
diff --git a/coordinator/ui/components/unit_flow.lua b/coordinator/ui/components/unit_flow.lua
index f3103e5..0cb64c2 100644
--- a/coordinator/ui/components/unit_flow.lua
+++ b/coordinator/ui/components/unit_flow.lua
@@ -181,10 +181,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=bw_fg_bg}
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=bw_fg_bg}
- 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=bw_fg_bg}
- 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=bw_fg_bg}
- 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=bw_fg_bg}
- 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=bw_fg_bg}
+ 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=bw_fg_bg}
+ 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=bw_fg_bg}
+ 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=bw_fg_bg}
+ 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=bw_fg_bg}
waste_rate.register(unit.unit_ps, "act_burn_rate", waste_rate.update)
pu_rate.register(unit.unit_ps, "pu_rate", pu_rate.update)
@@ -214,7 +214,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 f19dda2..1040a8c 100644
--- a/coordinator/ui/layout/flow_view.lua
+++ b/coordinator/ui/layout/flow_view.lua
@@ -348,7 +348,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=bw_fg_bg}
+ 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=bw_fg_bg}
sps_in.register(facility.ps, "po_am_rate", sps_in.update)
@@ -370,8 +370,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=bw_fg_bg}
local pu = DataIndicator{parent=pr_waste,lu_colors=lu_col,label="Pu",unit="mB/t",format="%9.3f",value=0,width=17}
- local po = DataIndicator{parent=pr_waste,lu_colors=lu_col,label="Po",unit="mB/t",format="%9.3f",value=0,width=17}
- local popl = DataIndicator{parent=pr_waste,lu_colors=lu_col,label="PoPl",unit="mB/t",format="%7.3f",value=0,width=17}
+ local po = DataIndicator{parent=pr_waste,lu_colors=lu_col,label="Po",unit="mB/t",format="%9.2f",value=0,width=17}
+ local popl = DataIndicator{parent=pr_waste,lu_colors=lu_col,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/network.lua b/scada-common/network.lua
index 742f418..bdaf5c3 100644
--- a/scada-common/network.lua
+++ b/scada-common/network.lua
@@ -53,6 +53,11 @@ function network.init_mac(passkey)
return init_time
end
+-- de-initialize message authentication system
+function network.deinit_mac()
+ c_eng.key, c_eng.hmac = nil, nil
+end
+
-- generate HMAC of message
---@nodiscard
---@param message string initial value concatenated with ciphertext
diff --git a/scada-common/ppm.lua b/scada-common/ppm.lua
index df64b68..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
}
@@ -300,6 +300,17 @@ function ppm.handle_unmount(iface)
return pm_type, pm_dev
end
+-- log all mounts, to be used if `ppm.mount_all` is called before logging is ready
+function ppm.log_mounts()
+ for iface, mount in pairs(ppm_sys.mounts) do
+ log.info(util.c("PPM: had found a ", mount.type, " (", iface, ")"))
+ end
+
+ if util.table_len(ppm_sys.mounts) == 0 then
+ log.warning("PPM: no devices had been found")
+ end
+end
+
-- GENERAL ACCESSORS --
-- list all available peripherals
diff --git a/scada-common/util.lua b/scada-common/util.lua
index 10b4202..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.14"
+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
diff --git a/test/watch_psil_allocs.lua b/test/watch_psil_allocs.lua
new file mode 100644
index 0000000..a45685e
--- /dev/null
+++ b/test/watch_psil_allocs.lua
@@ -0,0 +1,56 @@
+-- add this to psil:
+
+--[[
+ -- count the number of subscribers in this PSIL instance
+ ---@return integer count
+ function public.count()
+ local c = 0
+ for _, val in pairs(ic) do
+ for _ = 1, #val.subscribers do c = c + 1 end
+ end
+ return c
+ end
+]]--
+
+
+-- add this to coordinator iocontrol front panel heartbeat function:
+
+--[[
+if io.facility then
+ local count = io.facility.ps.count()
+
+ count = count + io.facility.env_d_ps.count()
+
+ for x = 1, #io.facility.induction_ps_tbl do
+ count = count + io.facility.induction_ps_tbl[x].count()
+ end
+
+ for x = 1, #io.facility.sps_ps_tbl do
+ count = count + io.facility.sps_ps_tbl[x].count()
+ end
+
+ for x = 1, #io.facility.tank_ps_tbl do
+ count = count + io.facility.tank_ps_tbl[x].count()
+ end
+
+ for i = 1, #io.units do
+ local entry = io.units[i] ---@type ioctl_unit
+
+ count = count + entry.unit_ps.count()
+
+ for x = 1, #entry.boiler_ps_tbl do
+ count = count + entry.boiler_ps_tbl[x].count()
+ end
+
+ for x = 1, #entry.turbine_ps_tbl do
+ count = count + entry.turbine_ps_tbl[x].count()
+ end
+
+ for x = 1, #entry.tank_ps_tbl do
+ count = count + entry.tank_ps_tbl[x].count()
+ end
+ end
+
+ log.debug(count)
+end
+]]--