Merge pull request #443 from MikaylaFischler/devel

2024.03.08 Release
This commit is contained in:
Mikayla 2024-03-08 22:40:28 -05:00 committed by GitHub
commit 3c7fff28c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 567 additions and 243 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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<br>
-- 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

View File

@ -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

View File

@ -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}

View File

@ -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)

View File

@ -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}

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 = {

View File

@ -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

View File

@ -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

View File

@ -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<br>
-- 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<br>
--- EVENT_CONSUMER: this function consumes events

View File

@ -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<br>
@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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
]]--