commit
451232ce91
21
ccmsi.lua
21
ccmsi.lua
@ -15,7 +15,7 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
]]--
|
||||
|
||||
local CCMSI_VERSION = "v1.20"
|
||||
local CCMSI_VERSION = "v1.21"
|
||||
|
||||
local install_dir = "/.install-cache"
|
||||
local manifest_path = "https://mikaylafischler.github.io/cc-mek-scada/manifests/"
|
||||
@ -321,23 +321,38 @@ if #opts == 0 or opts[1] == "help" then
|
||||
end
|
||||
return
|
||||
else
|
||||
|
||||
mode = get_opt(opts[1], { "check", "install", "update", "uninstall" })
|
||||
if mode == nil then
|
||||
red();println("Unrecognized mode.");white()
|
||||
return
|
||||
end
|
||||
|
||||
app = get_opt(opts[2], { "reactor-plc", "rtu", "supervisor", "coordinator", "pocket", "installer" })
|
||||
local next_opt = 3
|
||||
local apps = { "reactor-plc", "rtu", "supervisor", "coordinator", "pocket", "installer" }
|
||||
app = get_opt(opts[2], apps)
|
||||
if app == nil then
|
||||
for _, a in pairs(apps) do
|
||||
if fs.exists(a) and fs.isDir(a) then
|
||||
app = a
|
||||
next_opt = 2
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if app == nil and mode ~= "check" then
|
||||
red();println("Unrecognized application.");white()
|
||||
return
|
||||
elseif mode == "check" then
|
||||
next_opt = 2
|
||||
elseif app == "installer" and mode ~= "update" then
|
||||
red();println("Installer app only supports 'update' option.");white()
|
||||
return
|
||||
end
|
||||
|
||||
-- determine target
|
||||
if mode == "check" then target = opts[2] else target = opts[3] end
|
||||
target = opts[next_opt]
|
||||
if (target ~= "main") and (target ~= "devel") then
|
||||
if (target and target ~= "") then yellow();println("Unknown target, defaulting to 'main'");white() end
|
||||
target = "main"
|
||||
|
||||
@ -231,7 +231,7 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
||||
TextBox{parent=fac_c_2,x=1,y=1,height=3,text="Please enter the number of reactors you have, also referred to as reactor units or 'units' for short. A maximum of 4 is currently supported."}
|
||||
tool_ctl.num_units = NumberField{parent=fac_c_2,x=1,y=5,width=5,max_chars=2,default=ini_cfg.UnitCount,min=1,max=4,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=fac_c_2,x=7,y=5,text="reactors"}
|
||||
TextBox{parent=fac_c_2,x=1,y=7,height=3,text="This will decide how many monitors you need. If this does not match the supervisor's number of reactor units, the coordinator will not connect.",fg_bg=g_lg_fg_bg}
|
||||
TextBox{parent=fac_c_2,x=1,y=7,height=3,text="This will decide how many monitors you need. If this does not match the supervisor's number of reactor units, the coordinator will not connect.",fg_bg=cpair(colors.yellow,colors._INHERIT)}
|
||||
TextBox{parent=fac_c_2,x=1,y=10,height=3,text="Since you skipped supervisor sync, the main monitor minimum height can't be determined precisely. It is marked with * on the next page.",fg_bg=g_lg_fg_bg}
|
||||
|
||||
local nu_error = TextBox{parent=fac_c_2,x=8,y=14,width=35,text="Please set the number of reactors.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||
|
||||
@ -211,7 +211,7 @@ local function config_view(display)
|
||||
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text=msg,fg_bg=cpair(colors.red,colors.lightGray)}
|
||||
y_start = y_start + 5
|
||||
elseif tool_ctl.start_fail > 0 then
|
||||
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device had no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
||||
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device is not configured for this version of the coordinator. If you previously had a valid config, it's not lost. You may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
||||
y_start = y_start + 5
|
||||
end
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ local LINK_TIMEOUT = 60.0
|
||||
local coordinator = {}
|
||||
|
||||
---@type crd_config
|
||||
---@diagnostic disable-next-line: missing-fields
|
||||
local config = {}
|
||||
|
||||
coordinator.config = config
|
||||
|
||||
@ -20,6 +20,13 @@ local ENERGY_UNITS = types.ENERGY_SCALE_UNITS
|
||||
local TEMP_SCALE = types.TEMP_SCALE
|
||||
local TEMP_UNITS = types.TEMP_SCALE_UNITS
|
||||
|
||||
local RCT_STATE = types.REACTOR_STATE
|
||||
local BLR_STATE = types.BOILER_STATE
|
||||
local TRB_STATE = types.TURBINE_STATE
|
||||
local TNK_STATE = types.TANK_STATE
|
||||
local MTX_STATE = types.IMATRIX_STATE
|
||||
local SPS_STATE = types.SPS_STATE
|
||||
|
||||
-- nominal RTT is ping (0ms to 10ms usually) + 500ms for CRD main loop tick
|
||||
local WARN_RTT = 1000 -- 2x as long as expected w/ 0 ping
|
||||
local HIGH_RTT = 1500 -- 3.33x as long as expected w/ 0 ping
|
||||
@ -81,6 +88,8 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
||||
tank_mode = conf.cooling.fac_tank_mode,
|
||||
tank_defs = conf.cooling.fac_tank_defs,
|
||||
tank_list = conf.cooling.fac_tank_list,
|
||||
tank_conns = conf.cooling.fac_tank_conns,
|
||||
tank_fluid_types = conf.cooling.tank_fluid_types,
|
||||
all_sys_ok = false,
|
||||
rtu_count = 0,
|
||||
|
||||
@ -119,7 +128,6 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
||||
induction_ps_tbl = {}, ---@type psil[]
|
||||
induction_data_tbl = {}, ---@type imatrix_session_db[]
|
||||
|
||||
sps_status = 1,
|
||||
sps_ps_tbl = {}, ---@type psil[]
|
||||
sps_data_tbl = {}, ---@type sps_session_db[]
|
||||
|
||||
@ -151,10 +159,6 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
||||
local entry = {
|
||||
unit_id = i,
|
||||
connected = false,
|
||||
rtu_hw = {
|
||||
boilers = {}, ---@type { connected: boolean, faulted: boolean }[]
|
||||
turbines = {} ---@type { connected: boolean, faulted: boolean }[]
|
||||
},
|
||||
|
||||
num_boilers = 0,
|
||||
num_turbines = 0,
|
||||
@ -224,6 +228,7 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
||||
ALARM_STATE.INACTIVE -- turbine trip
|
||||
},
|
||||
|
||||
---@diagnostic disable-next-line: missing-fields
|
||||
annunciator = {}, ---@type annunciator
|
||||
|
||||
unit_ps = psil.create(),
|
||||
@ -248,14 +253,12 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
|
||||
for _ = 1, conf.cooling.r_cool[i].BoilerCount do
|
||||
table.insert(entry.boiler_ps_tbl, psil.create())
|
||||
table.insert(entry.boiler_data_tbl, {})
|
||||
table.insert(entry.rtu_hw.boilers, { connected = false, faulted = false })
|
||||
end
|
||||
|
||||
-- create turbine tables
|
||||
for _ = 1, conf.cooling.r_cool[i].TurbineCount do
|
||||
table.insert(entry.turbine_ps_tbl, psil.create())
|
||||
table.insert(entry.turbine_data_tbl, {})
|
||||
table.insert(entry.rtu_hw.turbines, { connected = false, faulted = false })
|
||||
end
|
||||
|
||||
-- create tank tables
|
||||
@ -366,6 +369,7 @@ local function _record_multiblock_build(id, entry, data_tbl, ps_tbl, create)
|
||||
if exists or create then
|
||||
if not exists then
|
||||
ps_tbl[id] = psil.create()
|
||||
---@diagnostic disable-next-line: missing-fields
|
||||
data_tbl[id] = {}
|
||||
end
|
||||
|
||||
@ -627,10 +631,12 @@ function iocontrol.update_facility_status(status)
|
||||
|
||||
-- induction matricies statuses
|
||||
if type(rtu_statuses.induction) == "table" then
|
||||
local matrix_status = MTX_STATE.OFFLINE
|
||||
|
||||
for id = 1, #fac.induction_ps_tbl do
|
||||
if rtu_statuses.induction[id] == nil then
|
||||
-- disconnected
|
||||
fac.induction_ps_tbl[id].publish("computed_status", 1)
|
||||
fac.induction_ps_tbl[id].publish("computed_status", matrix_status)
|
||||
end
|
||||
end
|
||||
|
||||
@ -642,18 +648,20 @@ function iocontrol.update_facility_status(status)
|
||||
local rtu_faulted = _record_multiblock_status(matrix, data, ps)
|
||||
|
||||
if rtu_faulted then
|
||||
ps.publish("computed_status", 3) -- faulted
|
||||
matrix_status = MTX_STATE.FAULT
|
||||
elseif data.formed then
|
||||
if data.tanks.energy_fill >= 0.99 then
|
||||
ps.publish("computed_status", 6) -- full
|
||||
matrix_status = MTX_STATE.HIGH_CHARGE
|
||||
elseif data.tanks.energy_fill <= 0.01 then
|
||||
ps.publish("computed_status", 5) -- empty
|
||||
matrix_status = MTX_STATE.LOW_CHARGE
|
||||
else
|
||||
ps.publish("computed_status", 4) -- on-line
|
||||
matrix_status = MTX_STATE.ONLINE
|
||||
end
|
||||
else
|
||||
ps.publish("computed_status", 2) -- not formed
|
||||
matrix_status = MTX_STATE.UNFORMED
|
||||
end
|
||||
|
||||
ps.publish("computed_status", matrix_status)
|
||||
else
|
||||
log.debug(util.c(log_header, "invalid induction matrix id ", id))
|
||||
end
|
||||
@ -665,12 +673,12 @@ function iocontrol.update_facility_status(status)
|
||||
|
||||
-- SPS statuses
|
||||
if type(rtu_statuses.sps) == "table" then
|
||||
local sps_status = 1
|
||||
local sps_status = SPS_STATE.OFFLINE
|
||||
|
||||
for id = 1, #fac.sps_ps_tbl do
|
||||
if rtu_statuses.sps[id] == nil then
|
||||
-- disconnected
|
||||
fac.sps_ps_tbl[id].publish("computed_status", 1)
|
||||
fac.sps_ps_tbl[id].publish("computed_status", sps_status)
|
||||
end
|
||||
end
|
||||
|
||||
@ -682,11 +690,11 @@ function iocontrol.update_facility_status(status)
|
||||
local rtu_faulted = _record_multiblock_status(sps, data, ps)
|
||||
|
||||
if rtu_faulted then
|
||||
sps_status = 3 -- faulted
|
||||
sps_status = SPS_STATE.FAULT
|
||||
elseif data.formed then
|
||||
-- active / idle
|
||||
sps_status = util.trinary(data.state.process_rate > 0, 5, 4)
|
||||
else sps_status = 2 end -- not formed
|
||||
sps_status = util.trinary(data.state.process_rate > 0, SPS_STATE.ACTIVE, SPS_STATE.IDLE)
|
||||
else sps_status = SPS_STATE.UNFORMED end
|
||||
|
||||
ps.publish("computed_status", sps_status)
|
||||
|
||||
@ -695,8 +703,6 @@ function iocontrol.update_facility_status(status)
|
||||
log.debug(util.c(log_header, "invalid sps id ", id))
|
||||
end
|
||||
end
|
||||
|
||||
io.facility.sps_status = sps_status
|
||||
else
|
||||
log.debug(log_header .. "sps list not a table")
|
||||
valid = false
|
||||
@ -704,10 +710,12 @@ function iocontrol.update_facility_status(status)
|
||||
|
||||
-- dynamic tank statuses
|
||||
if type(rtu_statuses.tanks) == "table" then
|
||||
local tank_status = TNK_STATE.OFFLINE
|
||||
|
||||
for id = 1, #fac.tank_ps_tbl do
|
||||
if rtu_statuses.tanks[id] == nil then
|
||||
-- disconnected
|
||||
fac.tank_ps_tbl[id].publish("computed_status", 1)
|
||||
fac.tank_ps_tbl[id].publish("computed_status", tank_status)
|
||||
end
|
||||
end
|
||||
|
||||
@ -719,18 +727,18 @@ function iocontrol.update_facility_status(status)
|
||||
local rtu_faulted = _record_multiblock_status(tank, data, ps)
|
||||
|
||||
if rtu_faulted then
|
||||
ps.publish("computed_status", 3) -- faulted
|
||||
tank_status = TNK_STATE.FAULT
|
||||
elseif data.formed then
|
||||
if data.tanks.fill >= 0.99 then
|
||||
ps.publish("computed_status", 6) -- full
|
||||
tank_status = TNK_STATE.HIGH_FILL
|
||||
elseif data.tanks.fill < 0.20 then
|
||||
ps.publish("computed_status", 5) -- low
|
||||
tank_status = TNK_STATE.LOW_FILL
|
||||
else
|
||||
ps.publish("computed_status", 4) -- on-line
|
||||
end
|
||||
else
|
||||
ps.publish("computed_status", 2) -- not formed
|
||||
tank_status = TNK_STATE.ONLINE
|
||||
end
|
||||
else tank_status = TNK_STATE.UNFORMED end
|
||||
|
||||
ps.publish("computed_status", tank_status)
|
||||
else
|
||||
log.debug(util.c(log_header, "invalid dynamic tank id ", id))
|
||||
end
|
||||
@ -830,9 +838,11 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
log.debug(log_header .. "reactor status not a table")
|
||||
end
|
||||
|
||||
local computed_status = RCT_STATE.OFFLINE
|
||||
|
||||
if #reactor_status == 0 then
|
||||
unit.connected = false
|
||||
unit.unit_ps.publish("computed_status", 1) -- disconnected
|
||||
unit.unit_ps.publish("computed_status", computed_status)
|
||||
elseif #reactor_status == 3 then
|
||||
local mek_status = reactor_status[1]
|
||||
local rps_status = reactor_status[2]
|
||||
@ -871,22 +881,23 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
burn_rate_sum = burn_rate_sum + burn_rate
|
||||
|
||||
if unit.reactor_data.mek_status.status then
|
||||
unit.unit_ps.publish("computed_status", 5) -- running
|
||||
computed_status = RCT_STATE.ACTIVE
|
||||
else
|
||||
if unit.reactor_data.no_reactor then
|
||||
unit.unit_ps.publish("computed_status", 3) -- faulted
|
||||
computed_status = RCT_STATE.FAULT
|
||||
elseif not unit.reactor_data.formed then
|
||||
unit.unit_ps.publish("computed_status", 2) -- multiblock not formed
|
||||
computed_status = RCT_STATE.UNFORMED
|
||||
elseif unit.reactor_data.rps_status.force_dis then
|
||||
unit.unit_ps.publish("computed_status", 7) -- reactor force disabled
|
||||
computed_status = RCT_STATE.FORCE_DISABLED
|
||||
elseif unit.reactor_data.rps_tripped and unit.reactor_data.rps_trip_cause ~= "manual" then
|
||||
unit.unit_ps.publish("computed_status", 6) -- SCRAM
|
||||
computed_status = RCT_STATE.SCRAMMED
|
||||
else
|
||||
unit.unit_ps.publish("computed_status", 4) -- disabled
|
||||
computed_status = RCT_STATE.DISABLED
|
||||
end
|
||||
end
|
||||
|
||||
unit.connected = true
|
||||
unit.unit_ps.publish("computed_status", computed_status)
|
||||
else
|
||||
log.debug(log_header .. "reactor status length mismatch")
|
||||
valid = false
|
||||
@ -900,13 +911,11 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
if type(rtu_statuses.boilers) == "table" then
|
||||
local boil_sum = 0
|
||||
|
||||
for id = 1, #unit.boiler_ps_tbl do
|
||||
local connected = rtu_statuses.boilers[id] ~= nil
|
||||
unit.rtu_hw.boilers[id].connected = connected
|
||||
computed_status = BLR_STATE.OFFLINE
|
||||
|
||||
if not connected then
|
||||
-- disconnected
|
||||
unit.boiler_ps_tbl[id].publish("computed_status", 1)
|
||||
for id = 1, #unit.boiler_ps_tbl do
|
||||
if rtu_statuses.boilers[id] == nil then
|
||||
unit.boiler_ps_tbl[id].publish("computed_status", computed_status)
|
||||
end
|
||||
end
|
||||
|
||||
@ -916,21 +925,15 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
local ps = unit.boiler_ps_tbl[id]
|
||||
|
||||
local rtu_faulted = _record_multiblock_status(boiler, data, ps)
|
||||
unit.rtu_hw.boilers[id].faulted = rtu_faulted
|
||||
|
||||
if rtu_faulted then
|
||||
ps.publish("computed_status", 3) -- faulted
|
||||
computed_status = BLR_STATE.FAULT
|
||||
elseif data.formed then
|
||||
boil_sum = boil_sum + data.state.boil_rate
|
||||
computed_status = util.trinary(data.state.boil_rate > 0, BLR_STATE.ACTIVE, BLR_STATE.IDLE)
|
||||
else computed_status = BLR_STATE.UNFORMED end
|
||||
|
||||
if data.state.boil_rate > 0 then
|
||||
ps.publish("computed_status", 5) -- active
|
||||
else
|
||||
ps.publish("computed_status", 4) -- idle
|
||||
end
|
||||
else
|
||||
ps.publish("computed_status", 2) -- not formed
|
||||
end
|
||||
unit.boiler_ps_tbl[id].publish("computed_status", computed_status)
|
||||
else
|
||||
log.debug(util.c(log_header, "invalid boiler id ", id))
|
||||
valid = false
|
||||
@ -947,13 +950,11 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
if type(rtu_statuses.turbines) == "table" then
|
||||
local flow_sum = 0
|
||||
|
||||
for id = 1, #unit.turbine_ps_tbl do
|
||||
local connected = rtu_statuses.turbines[id] ~= nil
|
||||
unit.rtu_hw.turbines[id].connected = connected
|
||||
computed_status = TRB_STATE.OFFLINE
|
||||
|
||||
if not connected then
|
||||
-- disconnected
|
||||
unit.turbine_ps_tbl[id].publish("computed_status", 1)
|
||||
for id = 1, #unit.turbine_ps_tbl do
|
||||
if rtu_statuses.turbines[id] == nil then
|
||||
unit.turbine_ps_tbl[id].publish("computed_status", computed_status)
|
||||
end
|
||||
end
|
||||
|
||||
@ -963,23 +964,22 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
local ps = unit.turbine_ps_tbl[id]
|
||||
|
||||
local rtu_faulted = _record_multiblock_status(turbine, data, ps)
|
||||
unit.rtu_hw.turbines[id].faulted = rtu_faulted
|
||||
|
||||
if rtu_faulted then
|
||||
ps.publish("computed_status", 3) -- faulted
|
||||
computed_status = TRB_STATE.FAULT
|
||||
elseif data.formed then
|
||||
flow_sum = flow_sum + data.state.flow_rate
|
||||
|
||||
if data.tanks.energy_fill >= 0.99 then
|
||||
ps.publish("computed_status", 6) -- trip
|
||||
computed_status = TRB_STATE.TRIPPED
|
||||
elseif data.state.flow_rate < 100 then
|
||||
ps.publish("computed_status", 4) -- idle
|
||||
computed_status = TRB_STATE.IDLE
|
||||
else
|
||||
ps.publish("computed_status", 5) -- active
|
||||
end
|
||||
else
|
||||
ps.publish("computed_status", 2) -- not formed
|
||||
computed_status = TRB_STATE.ACTIVE
|
||||
end
|
||||
else computed_status = TRB_STATE.UNFORMED end
|
||||
|
||||
unit.turbine_ps_tbl[id].publish("computed_status", computed_status)
|
||||
else
|
||||
log.debug(util.c(log_header, "invalid turbine id ", id))
|
||||
valid = false
|
||||
@ -994,10 +994,11 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
|
||||
-- dynamic tank statuses
|
||||
if type(rtu_statuses.tanks) == "table" then
|
||||
computed_status = TNK_STATE.OFFLINE
|
||||
|
||||
for id = 1, #unit.tank_ps_tbl do
|
||||
if rtu_statuses.tanks[id] == nil then
|
||||
-- disconnected
|
||||
unit.tank_ps_tbl[id].publish("computed_status", 1)
|
||||
unit.tank_ps_tbl[id].publish("computed_status", computed_status)
|
||||
end
|
||||
end
|
||||
|
||||
@ -1009,18 +1010,18 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
local rtu_faulted = _record_multiblock_status(tank, data, ps)
|
||||
|
||||
if rtu_faulted then
|
||||
ps.publish("computed_status", 3) -- faulted
|
||||
computed_status = TNK_STATE.FAULT
|
||||
elseif data.formed then
|
||||
if data.tanks.fill >= 0.99 then
|
||||
ps.publish("computed_status", 6) -- full
|
||||
computed_status = TNK_STATE.HIGH_FILL
|
||||
elseif data.tanks.fill < 0.20 then
|
||||
ps.publish("computed_status", 5) -- low
|
||||
computed_status = TNK_STATE.LOW_FILL
|
||||
else
|
||||
ps.publish("computed_status", 4) -- on-line
|
||||
end
|
||||
else
|
||||
ps.publish("computed_status", 2) -- not formed
|
||||
computed_status = TNK_STATE.ONLINE
|
||||
end
|
||||
else computed_status = TNK_STATE.UNFORMED end
|
||||
|
||||
unit.tank_ps_tbl[id].publish("computed_status", computed_status)
|
||||
else
|
||||
log.debug(util.c(log_header, "invalid dynamic tank id ", id))
|
||||
valid = false
|
||||
@ -1085,6 +1086,7 @@ function iocontrol.update_unit_statuses(statuses)
|
||||
unit.annunciator = status[3]
|
||||
|
||||
if type(unit.annunciator) ~= "table" then
|
||||
---@diagnostic disable-next-line: missing-fields
|
||||
unit.annunciator = {}
|
||||
log.debug(log_header .. "annunciator state not a table")
|
||||
valid = false
|
||||
|
||||
@ -286,6 +286,7 @@ function process.create_handle()
|
||||
handle.unit_ack = {}
|
||||
|
||||
for u = 1, pctl.io.facility.num_units do
|
||||
---@diagnostic disable-next-line: missing-fields
|
||||
handle.unit_ack[u] = {}
|
||||
|
||||
---@class process_unit_ack
|
||||
|
||||
@ -269,11 +269,17 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
||||
if pkt.length == 1 and type(pkt.data[1]) == "number" then
|
||||
local u = db.units[pkt.data[1]]
|
||||
|
||||
local statuses = { u.unit_ps.get("computed_status") }
|
||||
|
||||
for i = 1, #u.boiler_ps_tbl do table.insert(statuses, u.boiler_ps_tbl[i].get("computed_status")) end
|
||||
for i = 1, #u.turbine_ps_tbl do table.insert(statuses, u.turbine_ps_tbl[i].get("computed_status")) end
|
||||
for i = 1, #u.tank_ps_tbl do table.insert(statuses, u.tank_ps_tbl[i].get("computed_status")) end
|
||||
|
||||
if u then
|
||||
local data = {
|
||||
u.unit_id,
|
||||
u.connected,
|
||||
u.rtu_hw,
|
||||
statuses,
|
||||
u.a_group,
|
||||
u.alarms,
|
||||
u.annunciator,
|
||||
@ -375,7 +381,7 @@ function pocket.new_session(id, s_addr, i_seq_num, in_queue, out_queue, timeout)
|
||||
proc.pu_fallback,
|
||||
proc.sps_low_power,
|
||||
fac.waste_stats,
|
||||
fac.sps_status,
|
||||
fac.sps_ps_tbl[1].get("computed_status") or types.SPS_STATE.OFFLINE,
|
||||
process_rate
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer")
|
||||
local sounder = require("coordinator.sounder")
|
||||
local threads = require("coordinator.threads")
|
||||
|
||||
local COORDINATOR_VERSION = "v1.5.16"
|
||||
local COORDINATOR_VERSION = "v1.6.2"
|
||||
|
||||
local CHUNK_LOAD_DELAY_S = 30.0
|
||||
|
||||
|
||||
@ -24,7 +24,8 @@ local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks)
|
||||
local RENDER_SLEEP = 100 -- (100ms, 2 ticks)
|
||||
|
||||
local MQ__RENDER_CMD = {
|
||||
START_MAIN_UI = 1
|
||||
START_MAIN_UI = 1,
|
||||
CLOSE_MAIN_UI = 2
|
||||
}
|
||||
|
||||
local MQ__RENDER_DATA = {
|
||||
@ -81,7 +82,7 @@ function threads.thread__main(smem)
|
||||
nic.connect(other_modem)
|
||||
else
|
||||
-- close out main UI
|
||||
renderer.close_ui()
|
||||
smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI)
|
||||
|
||||
-- alert user to status
|
||||
log_sys("awaiting comms modem reconnect...")
|
||||
@ -167,9 +168,9 @@ function threads.thread__main(smem)
|
||||
-- supervisor watchdog timeout
|
||||
log_comms("supervisor server timeout")
|
||||
|
||||
-- close connection, main UI, and stop sounder
|
||||
-- close main UI, connection, and stop sounder
|
||||
smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI)
|
||||
coord_comms.close()
|
||||
renderer.close_ui()
|
||||
sounder.stop()
|
||||
else
|
||||
-- a non-clock/main watchdog timer event
|
||||
@ -188,9 +189,9 @@ function threads.thread__main(smem)
|
||||
if coord_comms.handle_packet(packet) then
|
||||
log_comms("supervisor closed connection")
|
||||
|
||||
-- close connection, main UI, and stop sounder
|
||||
-- close main UI, connection, and stop sounder
|
||||
smem.q.mq_render.push_command(MQ__RENDER_CMD.CLOSE_MAIN_UI)
|
||||
coord_comms.close()
|
||||
renderer.close_ui()
|
||||
sounder.stop()
|
||||
end
|
||||
elseif event == "monitor_touch" or event == "mouse_click" or event == "mouse_up" or
|
||||
@ -302,6 +303,13 @@ function threads.thread__render(smem)
|
||||
else
|
||||
log_render("main UI draw took " .. (util.time_ms() - draw_start) .. "ms")
|
||||
end
|
||||
elseif msg.message == MQ__RENDER_CMD.CLOSE_MAIN_UI then
|
||||
-- close the main UI if it has been drawn
|
||||
if renderer.ui_ready() then
|
||||
log_render("closing main UI...")
|
||||
renderer.close_ui()
|
||||
log_render("main UI closed")
|
||||
end
|
||||
end
|
||||
elseif msg.qtype == mqueue.TYPE.DATA then
|
||||
-- received data
|
||||
|
||||
@ -2,8 +2,11 @@
|
||||
-- Basic Unit Flow Overview
|
||||
--
|
||||
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local iocontrol = require("coordinator.iocontrol")
|
||||
|
||||
local style = require("coordinator.ui.style")
|
||||
|
||||
local core = require("graphics.core")
|
||||
@ -19,6 +22,8 @@ local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
||||
local IndicatorLight = require("graphics.elements.indicators.IndicatorLight")
|
||||
local TriIndicatorLight = require("graphics.elements.indicators.TriIndicatorLight")
|
||||
|
||||
local COOLANT_TYPE = types.COOLANT_TYPE
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
|
||||
local sprintf = util.sprintf
|
||||
@ -35,8 +40,8 @@ local lg_gray = style.lg_gray
|
||||
---@param x integer top left x
|
||||
---@param y integer top left y
|
||||
---@param wide boolean whether to render wide version
|
||||
---@param unit ioctl_unit unit database entry
|
||||
local function make(parent, x, y, wide, unit)
|
||||
---@param unit_id integer unit index
|
||||
local function make(parent, x, y, wide, unit_id)
|
||||
local s_field = style.theme.field_box
|
||||
|
||||
local text_c = style.text_colors
|
||||
@ -48,6 +53,12 @@ local function make(parent, x, y, wide, unit)
|
||||
|
||||
local height = 16
|
||||
|
||||
local facility = iocontrol.get_db().facility
|
||||
local unit = iocontrol.get_db().units[unit_id]
|
||||
|
||||
local tank_conns = facility.tank_conns
|
||||
local tank_types = facility.tank_fluid_types
|
||||
|
||||
local v_start = 1 + ((unit.unit_id - 1) * 5)
|
||||
local prv_start = 1 + ((unit.unit_id - 1) * 3)
|
||||
local v_fields = { "pu", "po", "pl", "am" }
|
||||
@ -80,21 +91,22 @@ local function make(parent, x, y, wide, unit)
|
||||
|
||||
local rc_pipes = {}
|
||||
|
||||
local emc_x = 42 -- emergency coolant connection x point
|
||||
|
||||
if unit.num_boilers > 0 then
|
||||
table.insert(rc_pipes, pipe(0, 1, _wide(28, 19), 1, colors.lightBlue, true))
|
||||
table.insert(rc_pipes, pipe(0, 3, _wide(28, 19), 3, colors.orange, true))
|
||||
table.insert(rc_pipes, pipe(_wide(46 ,39), 1, _wide(72,58), 1, colors.blue, true))
|
||||
table.insert(rc_pipes, pipe(_wide(46,39), 3, _wide(72,58), 3, colors.white, true))
|
||||
else
|
||||
emc_x = 3
|
||||
table.insert(rc_pipes, pipe(0, 1, _wide(72,58), 1, colors.blue, true))
|
||||
table.insert(rc_pipes, pipe(0, 3, _wide(72,58), 3, colors.white, true))
|
||||
end
|
||||
|
||||
if unit.has_tank then
|
||||
table.insert(rc_pipes, pipe(emc_x, 1, emc_x, 0, colors.blue, true, true))
|
||||
local is_water = tank_types[tank_conns[unit_id]] == COOLANT_TYPE.WATER
|
||||
-- emergency coolant connection x point
|
||||
local emc_x = util.trinary(is_water and (unit.num_boilers > 0), 42, 3)
|
||||
|
||||
table.insert(rc_pipes, pipe(emc_x, 1, emc_x, 0, util.trinary(is_water, colors.blue, colors.lightBlue), true, true))
|
||||
end
|
||||
|
||||
local prv_yo = math.max(3 - unit.num_turbines, 0)
|
||||
|
||||
@ -24,6 +24,7 @@ local IndicatorLight = require("graphics.elements.indicators.IndicatorLight")
|
||||
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
|
||||
|
||||
local CONTAINER_MODE = types.CONTAINER_MODE
|
||||
local COOLANT_TYPE = types.COOLANT_TYPE
|
||||
|
||||
local ALIGN = core.ALIGN
|
||||
|
||||
@ -46,7 +47,9 @@ local function init(main)
|
||||
local units = iocontrol.get_db().units
|
||||
|
||||
local tank_defs = facility.tank_defs
|
||||
local tank_conns = facility.tank_conns
|
||||
local tank_list = facility.tank_list
|
||||
local tank_types = facility.tank_fluid_types
|
||||
|
||||
-- window header message
|
||||
local header = TextBox{parent=main,y=1,text="Facility Coolant and Waste Flow Monitor",alignment=ALIGN.CENTER,fg_bg=style.theme.header}
|
||||
@ -56,12 +59,16 @@ local function init(main)
|
||||
datetime.register(facility.ps, "date_time", datetime.set_value)
|
||||
|
||||
local po_pipes = {}
|
||||
local water_pipes = {}
|
||||
local emcool_pipes = {}
|
||||
|
||||
-- get the y offset for this unit index
|
||||
---@param idx integer unit index
|
||||
local function y_ofs(idx) return ((idx - 1) * 20) end
|
||||
|
||||
-- get the coolant color
|
||||
---@param idx integer tank index
|
||||
local function c_clr(idx) return util.trinary(tank_types[tank_conns[idx]] == COOLANT_TYPE.WATER, colors.blue, colors.lightBlue) end
|
||||
|
||||
-- determinte facility tank start/end from the definitions list
|
||||
---@param start_idx integer start index of table iteration
|
||||
---@param end_idx integer end index of table iteration
|
||||
@ -81,11 +88,13 @@ local function init(main)
|
||||
for i = 1, facility.num_units do
|
||||
if units[i].has_tank then
|
||||
local y = y_ofs(i)
|
||||
table.insert(water_pipes, pipe(2, y, 2, y + 3, colors.blue, true))
|
||||
table.insert(water_pipes, pipe(2, y, 21, y, colors.blue, true))
|
||||
local color = c_clr(i)
|
||||
|
||||
local x = util.trinary(units[i].num_boilers == 0, 45, 84)
|
||||
table.insert(water_pipes, pipe(21, y, x, y + 2, colors.blue, true, true))
|
||||
table.insert(emcool_pipes, pipe(2, y, 2, y + 3, color, true))
|
||||
table.insert(emcool_pipes, pipe(2, y, 21, y, color, true))
|
||||
|
||||
local x = util.trinary((tank_types[tank_conns[i]] == COOLANT_TYPE.SODIUM) or (units[i].num_boilers == 0), 45, 84)
|
||||
table.insert(emcool_pipes, pipe(21, y, x, y + 2, color, true, true))
|
||||
end
|
||||
end
|
||||
else
|
||||
@ -93,16 +102,17 @@ local function init(main)
|
||||
for i = 1, #tank_defs do
|
||||
if tank_defs[i] > 0 then
|
||||
local y = y_ofs(i)
|
||||
local color = c_clr(i)
|
||||
|
||||
if tank_defs[i] == 2 then
|
||||
table.insert(water_pipes, pipe(1, y, 21, y, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(1, y, 21, y, color, true))
|
||||
else
|
||||
table.insert(water_pipes, pipe(2, y, 2, y + 3, colors.blue, true))
|
||||
table.insert(water_pipes, pipe(2, y, 21, y, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(2, y, 2, y + 3, color, true))
|
||||
table.insert(emcool_pipes, pipe(2, y, 21, y, color, true))
|
||||
end
|
||||
|
||||
local x = util.trinary(units[i].num_boilers == 0, 45, 84)
|
||||
table.insert(water_pipes, pipe(21, y, x, y + 2, colors.blue, true, true))
|
||||
local x = util.trinary((tank_types[tank_conns[i]] == COOLANT_TYPE.SODIUM) or (units[i].num_boilers == 0), 45, 84)
|
||||
table.insert(emcool_pipes, pipe(21, y, x, y + 2, color, true, true))
|
||||
end
|
||||
end
|
||||
|
||||
@ -112,13 +122,14 @@ local function init(main)
|
||||
|
||||
for i = 1, #tank_defs do
|
||||
local y = y_ofs(i)
|
||||
|
||||
if i == first_fdef then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, c_clr(i), true))
|
||||
elseif i > first_fdef then
|
||||
if i == last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y - 14, 0, y, c_clr(first_fdef), true))
|
||||
elseif i < last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y + 5, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y - 14, 0, y + 5, c_clr(first_fdef), true))
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -128,17 +139,19 @@ local function init(main)
|
||||
|
||||
for i = 1, #tank_defs do
|
||||
local y = y_ofs(i)
|
||||
local color = c_clr(first_fdef)
|
||||
|
||||
if i == 4 then
|
||||
if tank_defs[i] == 2 then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, c_clr(i), true))
|
||||
end
|
||||
elseif i == first_fdef then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, color, true))
|
||||
elseif i > first_fdef then
|
||||
if i == last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y - 14, 0, y, color, true))
|
||||
elseif i < last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y + 5, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y - 14, 0, y + 5, color, true))
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -147,12 +160,12 @@ local function init(main)
|
||||
for _, a in pairs({ 1, 3 }) do
|
||||
local b = a + 1
|
||||
if tank_defs[a] == 2 then
|
||||
table.insert(water_pipes, pipe(0, y_ofs(a), 1, y_ofs(a) + 6, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y_ofs(a), 1, y_ofs(a) + 6, c_clr(a), true))
|
||||
if tank_defs[b] == 2 then
|
||||
table.insert(water_pipes, pipe(0, y_ofs(b) - 13, 1, y_ofs(b), colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y_ofs(b) - 13, 1, y_ofs(b), c_clr(a), true))
|
||||
end
|
||||
elseif tank_defs[b] == 2 then
|
||||
table.insert(water_pipes, pipe(0, y_ofs(b), 1, y_ofs(b) + 6, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y_ofs(b), 1, y_ofs(b) + 6, c_clr(b), true))
|
||||
end
|
||||
end
|
||||
elseif facility.tank_mode == 4 then
|
||||
@ -161,17 +174,19 @@ local function init(main)
|
||||
|
||||
for i = 1, #tank_defs do
|
||||
local y = y_ofs(i)
|
||||
local color = c_clr(first_fdef)
|
||||
|
||||
if i == 1 then
|
||||
if tank_defs[i] == 2 then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, c_clr(i), true))
|
||||
end
|
||||
elseif i == first_fdef then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, color, true))
|
||||
elseif i > first_fdef then
|
||||
if i == last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y - 14, 0, y, color, true))
|
||||
elseif i < last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y + 5, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y - 14, 0, y + 5, color, true))
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -181,17 +196,19 @@ local function init(main)
|
||||
|
||||
for i = 1, #tank_defs do
|
||||
local y = y_ofs(i)
|
||||
local color = c_clr(first_fdef)
|
||||
|
||||
if i == 3 or i == 4 then
|
||||
if tank_defs[i] == 2 then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, c_clr(i), true))
|
||||
end
|
||||
elseif i == first_fdef then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, color, true))
|
||||
elseif i > first_fdef then
|
||||
if i == last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y - 14, 0, y, color, true))
|
||||
elseif i < last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y + 5, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y - 14, 0, y + 5, color, true))
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -201,17 +218,19 @@ local function init(main)
|
||||
|
||||
for i = 1, #tank_defs do
|
||||
local y = y_ofs(i)
|
||||
local color = c_clr(first_fdef)
|
||||
|
||||
if i == 1 or i == 4 then
|
||||
if tank_defs[i] == 2 then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, c_clr(i), true))
|
||||
end
|
||||
elseif i == first_fdef then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, color, true))
|
||||
elseif i > first_fdef then
|
||||
if i == last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y - 14, 0, y, color, true))
|
||||
elseif i < last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y + 5, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y - 14, 0, y + 5, color, true))
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -221,17 +240,19 @@ local function init(main)
|
||||
|
||||
for i = 1, #tank_defs do
|
||||
local y = y_ofs(i)
|
||||
local color = c_clr(first_fdef)
|
||||
|
||||
if i == 1 or i == 2 then
|
||||
if tank_defs[i] == 2 then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, c_clr(i), true))
|
||||
end
|
||||
elseif i == first_fdef then
|
||||
table.insert(water_pipes, pipe(0, y, 1, y + 5, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y, 1, y + 5, color, true))
|
||||
elseif i > first_fdef then
|
||||
if i == last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y - 14, 0, y, color, true))
|
||||
elseif i < last_fdef then
|
||||
table.insert(water_pipes, pipe(0, y - 14, 0, y + 5, colors.blue, true))
|
||||
table.insert(emcool_pipes, pipe(0, y - 14, 0, y + 5, color, true))
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -239,14 +260,14 @@ local function init(main)
|
||||
end
|
||||
|
||||
local flow_x = 3
|
||||
if #water_pipes > 0 then
|
||||
if #emcool_pipes > 0 then
|
||||
flow_x = 25
|
||||
PipeNetwork{parent=main,x=2,y=3,pipes=water_pipes,bg=style.theme.bg}
|
||||
PipeNetwork{parent=main,x=2,y=3,pipes=emcool_pipes,bg=style.theme.bg}
|
||||
end
|
||||
|
||||
for i = 1, facility.num_units do
|
||||
local y_offset = y_ofs(i)
|
||||
unit_flow(main, flow_x, 5 + y_offset, #water_pipes == 0, units[i])
|
||||
unit_flow(main, flow_x, 5 + y_offset, #emcool_pipes == 0, i)
|
||||
table.insert(po_pipes, pipe(0, 3 + y_offset, 4, 0, colors.cyan, true, true))
|
||||
util.nop()
|
||||
end
|
||||
@ -301,8 +322,10 @@ local function init(main)
|
||||
local tank_pcnt = DataIndicator{parent=tank_box,x=10,y=3,label="",format="%5.2f",value=100,unit="%",lu_colors=lu_col,width=8,fg_bg=text_col}
|
||||
local tank_amnt = DataIndicator{parent=tank_box,x=2,label="",format="%13d",value=0,commas=true,unit="mB",lu_colors=lu_col,width=16,fg_bg=s_field}
|
||||
|
||||
TextBox{parent=tank_box,x=2,y=6,text="Water Level",width=11,fg_bg=style.label}
|
||||
local level = HorizontalBar{parent=tank_box,x=2,y=7,bar_fg_bg=cpair(colors.blue,colors.gray),height=1,width=16}
|
||||
local is_water = tank_types[i] == COOLANT_TYPE.WATER
|
||||
|
||||
TextBox{parent=tank_box,x=2,y=6,text=util.trinary(is_water,"Water","Sodium").." Level",width=12,fg_bg=style.label}
|
||||
local level = HorizontalBar{parent=tank_box,x=2,y=7,bar_fg_bg=cpair(util.trinary(is_water,colors.blue,colors.lightBlue),colors.gray),height=1,width=16}
|
||||
|
||||
TextBox{parent=tank_box,x=2,y=9,text="In/Out Mode",width=11,fg_bg=style.label}
|
||||
local can_fill = IndicatorLight{parent=tank_box,x=2,y=10,label="FILL",colors=style.ind_wht}
|
||||
|
||||
@ -147,235 +147,102 @@ style.gray_white = cpair(colors.gray, colors.white)
|
||||
-- UI COMPONENTS --
|
||||
|
||||
style.reactor = {
|
||||
-- reactor states
|
||||
-- reactor states<br>
|
||||
---@see REACTOR_STATE
|
||||
states = {
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "PLC OFF-LINE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "NOT FORMED"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "PLC FAULT"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.white, colors.gray),
|
||||
text = "DISABLED"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "ACTIVE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.red),
|
||||
text = "SCRAMMED"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.red),
|
||||
text = "FORCE DISABLED"
|
||||
}
|
||||
{ color = cpair(colors.black, colors.yellow), text = "PLC OFF-LINE" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "PLC FAULT" },
|
||||
{ color = cpair(colors.white, colors.gray), text = "DISABLED" },
|
||||
{ color = cpair(colors.black, colors.green), text = "ACTIVE" },
|
||||
{ color = cpair(colors.black, colors.red), text = "SCRAMMED" },
|
||||
{ color = cpair(colors.black, colors.red), text = "FORCE DISABLED" }
|
||||
}
|
||||
}
|
||||
|
||||
style.boiler = {
|
||||
-- boiler states
|
||||
-- boiler states<br>
|
||||
---@see BOILER_STATE
|
||||
states = {
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "OFF-LINE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "NOT FORMED"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "RTU FAULT"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.white, colors.gray),
|
||||
text = "IDLE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "ACTIVE"
|
||||
}
|
||||
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "RTU FAULT" },
|
||||
{ color = cpair(colors.white, colors.gray), text = "IDLE" },
|
||||
{ color = cpair(colors.black, colors.green), text = "ACTIVE" }
|
||||
}
|
||||
}
|
||||
|
||||
style.turbine = {
|
||||
-- turbine states
|
||||
-- turbine states<br>
|
||||
---@see TURBINE_STATE
|
||||
states = {
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "OFF-LINE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "NOT FORMED"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "RTU FAULT"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.white, colors.gray),
|
||||
text = "IDLE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "ACTIVE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.red),
|
||||
text = "TRIP"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
style.imatrix = {
|
||||
-- induction matrix states
|
||||
states = {
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "OFF-LINE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "NOT FORMED"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "RTU FAULT"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "ONLINE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "LOW CHARGE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "HIGH CHARGE"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
style.sps = {
|
||||
-- SPS states
|
||||
states = {
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "OFF-LINE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "NOT FORMED"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "RTU FAULT"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.white, colors.gray),
|
||||
text = "IDLE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "ACTIVE"
|
||||
}
|
||||
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "RTU FAULT" },
|
||||
{ color = cpair(colors.white, colors.gray), text = "IDLE" },
|
||||
{ color = cpair(colors.black, colors.green), text = "ACTIVE" },
|
||||
{ color = cpair(colors.black, colors.red), text = "TRIP" }
|
||||
}
|
||||
}
|
||||
|
||||
style.dtank = {
|
||||
-- dynamic tank states
|
||||
-- dynamic tank states<br>
|
||||
---@see TANK_STATE
|
||||
states = {
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "OFF-LINE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "NOT FORMED"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "RTU FAULT"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "ONLINE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "LOW FILL"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "FILLED"
|
||||
},
|
||||
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "RTU FAULT" },
|
||||
{ color = cpair(colors.black, colors.green), text = "ONLINE" },
|
||||
{ color = cpair(colors.black, colors.yellow), text = "LOW FILL" },
|
||||
{ color = cpair(colors.black, colors.green), text = "FILLED" }
|
||||
}
|
||||
}
|
||||
|
||||
style.imatrix = {
|
||||
-- induction matrix states<br>
|
||||
---@see IMATRIX_STATE
|
||||
states = {
|
||||
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "RTU FAULT" },
|
||||
{ color = cpair(colors.black, colors.green), text = "ONLINE" },
|
||||
{ color = cpair(colors.black, colors.yellow), text = "LOW CHARGE" },
|
||||
{ color = cpair(colors.black, colors.yellow), text = "HIGH CHARGE" }
|
||||
}
|
||||
}
|
||||
|
||||
style.sps = {
|
||||
-- SPS states<br>
|
||||
---@see SPS_STATE
|
||||
states = {
|
||||
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "RTU FAULT" },
|
||||
{ color = cpair(colors.white, colors.gray), text = "IDLE" },
|
||||
{ color = cpair(colors.black, colors.green), text = "ACTIVE" }
|
||||
}
|
||||
}
|
||||
|
||||
style.waste = {
|
||||
-- auto waste processing states
|
||||
states = {
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "PLUTONIUM"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.cyan),
|
||||
text = "POLONIUM"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.purple),
|
||||
text = "ANTI MATTER"
|
||||
}
|
||||
{ color = cpair(colors.black, colors.green), text = "PLUTONIUM" },
|
||||
{ color = cpair(colors.black, colors.cyan), text = "POLONIUM" },
|
||||
{ color = cpair(colors.black, colors.purple), text = "ANTI MATTER" }
|
||||
},
|
||||
states_abbrv = {
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "Pu"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.cyan),
|
||||
text = "Po"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.purple),
|
||||
text = "AM"
|
||||
}
|
||||
{ color = cpair(colors.black, colors.green), text = "Pu" },
|
||||
{ color = cpair(colors.black, colors.cyan), text = "Po" },
|
||||
{ color = cpair(colors.black, colors.purple), text = "AM" }
|
||||
},
|
||||
-- process radio button options
|
||||
options = { "Plutonium", "Polonium", "Antimatter" },
|
||||
-- unit waste selection
|
||||
unit_opts = {
|
||||
{
|
||||
text = "Auto",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.white, colors.gray)
|
||||
},
|
||||
{
|
||||
text = "Pu",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.black, colors.green)
|
||||
},
|
||||
{
|
||||
text = "Po",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.black, colors.cyan)
|
||||
},
|
||||
{
|
||||
text = "AM",
|
||||
fg_bg = cpair(colors.black, colors.lightGray),
|
||||
active_fg_bg = cpair(colors.black, colors.purple)
|
||||
}
|
||||
{ text = "Auto", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.white, colors.gray) },
|
||||
{ text = "Pu", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, colors.green) },
|
||||
{ text = "Po", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, colors.cyan) },
|
||||
{ text = "AM", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, colors.purple) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ local flasher = require("graphics.flasher")
|
||||
|
||||
local core = {}
|
||||
|
||||
core.version = "2.4.6"
|
||||
core.version = "2.4.7"
|
||||
|
||||
core.flasher = flasher
|
||||
core.events = events
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
-- Generic Graphics Element
|
||||
--
|
||||
|
||||
-- local log = require("scada-common.log")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local core = require("graphics.core")
|
||||
@ -476,10 +475,7 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
|
||||
|
||||
if args.parent ~= nil then
|
||||
-- remove self from parent
|
||||
-- log.debug("removing " .. self.id .. " from parent")
|
||||
args.parent.__remove_child(self.id)
|
||||
else
|
||||
-- log.debug("no parent for " .. self.id .. " on delete attempt")
|
||||
end
|
||||
end
|
||||
|
||||
@ -502,9 +498,12 @@ function element.new(args, constraint, child_offset_x, child_offset_y)
|
||||
self.next_id = self.next_id + 1
|
||||
end
|
||||
|
||||
table.insert(protected.children, child)
|
||||
-- see #539 on GitHub
|
||||
-- using #protected.children after inserting may give the wrong index, since if it inserts in a hole that completes the list then
|
||||
-- the length will jump up to the full length of the list, possibly making two map entries point to the same child
|
||||
protected.child_id_map[id] = #protected.children + 1
|
||||
|
||||
protected.child_id_map[id] = #protected.children
|
||||
table.insert(protected.children, child)
|
||||
|
||||
return id
|
||||
end
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
-- Scroll-able List Box Display Graphics Element
|
||||
|
||||
-- local log = require("scada-common.log")
|
||||
local tcd = require("scada-common.tcd")
|
||||
|
||||
local core = require("graphics.core")
|
||||
@ -153,7 +152,6 @@ return function (args)
|
||||
next_y = next_y + item.h + item_pad
|
||||
item.e.reposition(1, item.y)
|
||||
item.e.show()
|
||||
-- log.debug("iterated " .. item.e.get_id())
|
||||
end
|
||||
|
||||
content_height = next_y
|
||||
@ -212,7 +210,6 @@ return function (args)
|
||||
---@param child graphics_element child element
|
||||
function e.on_added(id, child)
|
||||
table.insert(list, { id = id, e = child, y = 0, h = child.get_height() })
|
||||
-- log.debug("added child " .. id .. " into slot " .. #list)
|
||||
update_positions()
|
||||
end
|
||||
|
||||
@ -222,12 +219,10 @@ return function (args)
|
||||
for idx, elem in ipairs(list) do
|
||||
if elem.id == id then
|
||||
table.remove(list, idx)
|
||||
-- log.debug("removed child " .. id .. " from slot " .. idx)
|
||||
update_positions()
|
||||
return
|
||||
end
|
||||
end
|
||||
-- log.debug("failed to remove child " .. id)
|
||||
end
|
||||
|
||||
-- handle focus
|
||||
|
||||
@ -2,11 +2,11 @@
|
||||
-- I/O Control for Pocket Integration with Supervisor & Coordinator
|
||||
--
|
||||
|
||||
local const = require("scada-common.constants")
|
||||
local psil = require("scada-common.psil")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local iorx = require("pocket.iorx")
|
||||
local process = require("pocket.process")
|
||||
|
||||
local ALARM = types.ALARM
|
||||
@ -50,6 +50,8 @@ function iocontrol.init_core(pkt_comms, nav, cfg)
|
||||
comms = pkt_comms
|
||||
config = cfg
|
||||
|
||||
iocontrol.rx = iorx(io)
|
||||
|
||||
io.nav = nav
|
||||
|
||||
---@class pocket_ioctl_diag
|
||||
@ -136,6 +138,9 @@ function iocontrol.init_fac(conf)
|
||||
num_units = conf.num_units,
|
||||
tank_mode = conf.cooling.fac_tank_mode,
|
||||
tank_defs = conf.cooling.fac_tank_defs,
|
||||
tank_list = conf.cooling.fac_tank_list,
|
||||
tank_conns = conf.cooling.fac_tank_conns,
|
||||
tank_fluid_types = conf.cooling.tank_fluid_types,
|
||||
all_sys_ok = false,
|
||||
rtu_count = 0,
|
||||
|
||||
@ -194,8 +199,6 @@ function iocontrol.init_fac(conf)
|
||||
local entry = {
|
||||
unit_id = i,
|
||||
connected = false,
|
||||
---@type { boilers: { connected: boolean, faulted: boolean }[], turbines: { connected: boolean, faulted: boolean }[] }
|
||||
rtu_hw = {},
|
||||
|
||||
num_boilers = 0,
|
||||
num_turbines = 0,
|
||||
@ -239,6 +242,7 @@ function iocontrol.init_fac(conf)
|
||||
---@type { [ALARM]: ALARM_STATE }
|
||||
alarms = { ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE, ALARM_STATE.INACTIVE },
|
||||
|
||||
---@diagnostic disable-next-line: missing-fields
|
||||
annunciator = {}, ---@type annunciator
|
||||
|
||||
unit_ps = psil.create(),
|
||||
@ -347,642 +351,6 @@ function iocontrol.report_crd_tt(trip_time)
|
||||
io.ps.publish("crd_conn_quality", state)
|
||||
end
|
||||
|
||||
-- populate facility data from API_GET_FAC
|
||||
---@param data table
|
||||
---@return boolean valid
|
||||
function iocontrol.record_facility_data(data)
|
||||
local valid = true
|
||||
|
||||
local fac = io.facility
|
||||
|
||||
fac.all_sys_ok = data[1]
|
||||
fac.rtu_count = data[2]
|
||||
fac.radiation = data[3]
|
||||
|
||||
-- auto control
|
||||
if type(data[4]) == "table" and #data[4] == 4 then
|
||||
fac.auto_ready = data[4][1]
|
||||
fac.auto_active = data[4][2]
|
||||
fac.auto_ramping = data[4][3]
|
||||
fac.auto_saturated = data[4][4]
|
||||
end
|
||||
|
||||
-- waste
|
||||
if type(data[5]) == "table" and #data[5] == 2 then
|
||||
fac.auto_current_waste_product = data[5][1]
|
||||
fac.auto_pu_fallback_active = data[5][2]
|
||||
end
|
||||
|
||||
fac.num_tanks = data[6]
|
||||
fac.has_imatrix = data[7]
|
||||
fac.has_sps = data[8]
|
||||
|
||||
return valid
|
||||
end
|
||||
|
||||
local function tripped(state) return state == ALARM_STATE.TRIPPED or state == ALARM_STATE.ACKED end
|
||||
|
||||
local function _record_multiblock_status(faulted, data, ps)
|
||||
ps.publish("formed", data.formed)
|
||||
ps.publish("faulted", faulted)
|
||||
|
||||
for key, val in pairs(data.state) do ps.publish(key, val) end
|
||||
for key, val in pairs(data.tanks) do ps.publish(key, val) end
|
||||
end
|
||||
|
||||
-- update unit status data from API_GET_UNIT
|
||||
---@param data table
|
||||
function iocontrol.record_unit_data(data)
|
||||
local unit = io.units[data[1]]
|
||||
|
||||
unit.connected = data[2]
|
||||
unit.rtu_hw = data[3]
|
||||
unit.a_group = data[4]
|
||||
unit.alarms = data[5]
|
||||
|
||||
unit.unit_ps.publish("auto_group_id", unit.a_group)
|
||||
unit.unit_ps.publish("auto_group", types.AUTO_GROUP_NAMES[unit.a_group + 1])
|
||||
|
||||
--#region Annunciator
|
||||
|
||||
unit.annunciator = data[6]
|
||||
|
||||
local rcs_disconn, rcs_warn, rcs_hazard = false, false, false
|
||||
|
||||
for key, val in pairs(unit.annunciator) do
|
||||
if key == "BoilerOnline" or key == "TurbineOnline" then
|
||||
local every = true
|
||||
|
||||
-- split up online arrays
|
||||
for id = 1, #val do
|
||||
every = every and val[id]
|
||||
|
||||
if key == "BoilerOnline" then
|
||||
unit.boiler_ps_tbl[id].publish(key, val[id])
|
||||
else
|
||||
unit.turbine_ps_tbl[id].publish(key, val[id])
|
||||
end
|
||||
end
|
||||
|
||||
if not every then rcs_disconn = true end
|
||||
|
||||
unit.unit_ps.publish("U_" .. key, every)
|
||||
elseif key == "HeatingRateLow" or key == "WaterLevelLow" then
|
||||
-- split up array for all boilers
|
||||
local any = false
|
||||
for id = 1, #val do
|
||||
any = any or val[id]
|
||||
unit.boiler_ps_tbl[id].publish(key, val[id])
|
||||
end
|
||||
|
||||
if key == "HeatingRateLow" and any then
|
||||
rcs_warn = true
|
||||
elseif key == "WaterLevelLow" and any then
|
||||
rcs_hazard = true
|
||||
end
|
||||
|
||||
unit.unit_ps.publish("U_" .. key, any)
|
||||
elseif key == "SteamDumpOpen" or key == "TurbineOverSpeed" or key == "GeneratorTrip" or key == "TurbineTrip" then
|
||||
-- split up array for all turbines
|
||||
local any = false
|
||||
for id = 1, #val do
|
||||
any = any or val[id]
|
||||
unit.turbine_ps_tbl[id].publish(key, val[id])
|
||||
end
|
||||
|
||||
if key == "GeneratorTrip" and any then
|
||||
rcs_warn = true
|
||||
elseif (key == "TurbineOverSpeed" or key == "TurbineTrip") and any then
|
||||
rcs_hazard = true
|
||||
end
|
||||
|
||||
unit.unit_ps.publish("U_" .. key, any)
|
||||
else
|
||||
-- non-table fields
|
||||
unit.unit_ps.publish(key, val)
|
||||
end
|
||||
end
|
||||
|
||||
local anc = unit.annunciator
|
||||
rcs_hazard = rcs_hazard or anc.RCPTrip
|
||||
rcs_warn = rcs_warn or anc.RCSFlowLow or anc.CoolantLevelLow or anc.RCSFault or anc.MaxWaterReturnFeed or
|
||||
anc.CoolantFeedMismatch or anc.BoilRateMismatch or anc.SteamFeedMismatch
|
||||
|
||||
local rcs_status = 4
|
||||
if rcs_hazard then
|
||||
rcs_status = 2
|
||||
elseif rcs_warn then
|
||||
rcs_status = 3
|
||||
elseif rcs_disconn then
|
||||
rcs_status = 1
|
||||
end
|
||||
|
||||
unit.unit_ps.publish("U_RCS", rcs_status)
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Reactor Data
|
||||
|
||||
unit.reactor_data = data[7]
|
||||
|
||||
local control_status = 1
|
||||
local reactor_status = 1
|
||||
local reactor_state = 1
|
||||
local rps_status = 1
|
||||
|
||||
if unit.connected then
|
||||
-- update RPS status
|
||||
if unit.reactor_data.rps_tripped then
|
||||
control_status = 2
|
||||
|
||||
if unit.reactor_data.rps_trip_cause == "manual" then
|
||||
reactor_state = 4 -- disabled
|
||||
rps_status = 3
|
||||
else
|
||||
reactor_state = 6 -- SCRAM
|
||||
rps_status = 2
|
||||
end
|
||||
else
|
||||
rps_status = 4
|
||||
reactor_state = 4
|
||||
end
|
||||
|
||||
-- update reactor/control status
|
||||
if unit.reactor_data.mek_status.status then
|
||||
reactor_status = 4
|
||||
reactor_state = 5 -- running
|
||||
control_status = util.trinary(unit.annunciator.AutoControl, 4, 3)
|
||||
else
|
||||
if unit.reactor_data.no_reactor then
|
||||
reactor_status = 2
|
||||
reactor_state = 3 -- faulted
|
||||
elseif not unit.reactor_data.formed then
|
||||
reactor_status = 3
|
||||
reactor_state = 2 -- not formed
|
||||
elseif unit.reactor_data.rps_status.force_dis then
|
||||
reactor_status = 3
|
||||
reactor_state = 7 -- force disabled
|
||||
else
|
||||
reactor_status = 4
|
||||
end
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.reactor_data) do
|
||||
if key ~= "rps_status" and key ~= "mek_struct" and key ~= "mek_status" then
|
||||
unit.unit_ps.publish(key, val)
|
||||
end
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.reactor_data.rps_status) do
|
||||
unit.unit_ps.publish(key, val)
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.reactor_data.mek_struct) do
|
||||
unit.unit_ps.publish(key, val)
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.reactor_data.mek_status) do
|
||||
unit.unit_ps.publish(key, val)
|
||||
end
|
||||
end
|
||||
|
||||
unit.unit_ps.publish("U_ControlStatus", control_status)
|
||||
unit.unit_ps.publish("U_ReactorStatus", reactor_status)
|
||||
unit.unit_ps.publish("U_ReactorStateStatus", reactor_state)
|
||||
unit.unit_ps.publish("U_RPS", rps_status)
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region RTU Devices
|
||||
|
||||
unit.boiler_data_tbl = data[8]
|
||||
|
||||
for id = 1, #unit.boiler_data_tbl do
|
||||
local boiler = unit.boiler_data_tbl[id]
|
||||
local ps = unit.boiler_ps_tbl[id]
|
||||
|
||||
local boiler_status = 1
|
||||
local computed_status = 1
|
||||
|
||||
if unit.rtu_hw.boilers[id].connected then
|
||||
if unit.rtu_hw.boilers[id].faulted then
|
||||
boiler_status = 3
|
||||
computed_status = 3
|
||||
elseif boiler.formed then
|
||||
boiler_status = 4
|
||||
|
||||
if boiler.state.boil_rate > 0 then
|
||||
computed_status = 5
|
||||
else
|
||||
computed_status = 4
|
||||
end
|
||||
else
|
||||
boiler_status = 2
|
||||
computed_status = 2
|
||||
end
|
||||
|
||||
_record_multiblock_status(unit.rtu_hw.boilers[id].faulted, boiler, ps)
|
||||
end
|
||||
|
||||
ps.publish("BoilerStatus", boiler_status)
|
||||
ps.publish("BoilerStateStatus", computed_status)
|
||||
end
|
||||
|
||||
unit.turbine_data_tbl = data[9]
|
||||
|
||||
for id = 1, #unit.turbine_data_tbl do
|
||||
local turbine = unit.turbine_data_tbl[id]
|
||||
local ps = unit.turbine_ps_tbl[id]
|
||||
|
||||
local turbine_status = 1
|
||||
local computed_status = 1
|
||||
|
||||
if unit.rtu_hw.turbines[id].connected then
|
||||
if unit.rtu_hw.turbines[id].faulted then
|
||||
turbine_status = 3
|
||||
computed_status = 3
|
||||
elseif turbine.formed then
|
||||
turbine_status = 4
|
||||
|
||||
if turbine.tanks.energy_fill >= 0.99 then
|
||||
computed_status = 6
|
||||
elseif turbine.state.flow_rate < 100 then
|
||||
computed_status = 4
|
||||
else
|
||||
computed_status = 5
|
||||
end
|
||||
else
|
||||
turbine_status = 2
|
||||
computed_status = 2
|
||||
end
|
||||
|
||||
_record_multiblock_status(unit.rtu_hw.turbines[id].faulted, turbine, ps)
|
||||
end
|
||||
|
||||
ps.publish("TurbineStatus", turbine_status)
|
||||
ps.publish("TurbineStateStatus", computed_status)
|
||||
end
|
||||
|
||||
unit.tank_data_tbl = data[10]
|
||||
|
||||
unit.last_rate_change_ms = data[11]
|
||||
unit.turbine_flow_stable = data[12]
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Status Information Display
|
||||
|
||||
local ecam = {} -- aviation reference :)
|
||||
|
||||
-- local function red(text) return { text = text, color = colors.red } end
|
||||
local function white(text) return { text = text, color = colors.white } end
|
||||
local function blue(text) return { text = text, color = colors.blue } end
|
||||
|
||||
-- if unit.reactor_data.rps_status then
|
||||
-- for k, v in pairs(unit.alarms) do
|
||||
-- unit.alarms[k] = ALARM_STATE.TRIPPED
|
||||
-- end
|
||||
-- end
|
||||
|
||||
if tripped(unit.alarms[ALARM.ContainmentBreach]) then
|
||||
local items = { white("REACTOR MELTDOWN"), blue("DON HAZMAT SUIT") }
|
||||
table.insert(ecam, { color = colors.red, text = "CONTAINMENT BREACH", help = "ContainmentBreach", items = items })
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.ContainmentRadiation]) then
|
||||
local items = {
|
||||
white("RADIATION DETECTED"),
|
||||
blue("DON HAZMAT SUIT"),
|
||||
blue("RESOLVE LEAK"),
|
||||
blue("AWAIT SAFE LEVELS")
|
||||
}
|
||||
|
||||
table.insert(ecam, { color = colors.red, text = "RADIATION LEAK", help = "ContainmentRadiation", items = items })
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.CriticalDamage]) then
|
||||
local items = { white("MELTDOWN IMMINENT"), blue("EVACUATE") }
|
||||
table.insert(ecam, { color = colors.red, text = "RCT DAMAGE CRITICAL", help = "CriticalDamage", items = items })
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.ReactorLost]) then
|
||||
local items = { white("REACTOR OFF-LINE"), blue("CHECK PLC") }
|
||||
table.insert(ecam, { color = colors.red, text = "REACTOR CONN LOST", help = "ReactorLost", items = items })
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.ReactorDamage]) then
|
||||
local items = { white("REACTOR DAMAGED"), blue("CHECK RCS"), blue("AWAIT DMG REDUCED") }
|
||||
table.insert(ecam, { color = colors.red, text = "REACTOR DAMAGE", help = "ReactorDamage", items = items })
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.ReactorOverTemp]) then
|
||||
local items = { white("DAMAGING TEMP"), blue("CHECK RCS"), blue("AWAIT COOLDOWN") }
|
||||
table.insert(ecam, { color = colors.red, text = "REACTOR OVER TEMP", help = "ReactorOverTemp", items = items })
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.ReactorHighTemp]) then
|
||||
local items = { white("OVER EXPECTED TEMP"), blue("CHECK RCS") }
|
||||
table.insert(ecam, { color = colors.yellow, text = "REACTOR HIGH TEMP", help = "ReactorHighTemp", items = items})
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.ReactorWasteLeak]) then
|
||||
local items = { white("AT WASTE CAPACITY"), blue("CHECK WASTE OUTPUT"), blue("KEEP RCT DISABLED") }
|
||||
table.insert(ecam, { color = colors.red, text = "REACTOR WASTE LEAK", help = "ReactorWasteLeak", items = items})
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.ReactorHighWaste]) then
|
||||
local items = { blue("CHECK WASTE OUTPUT") }
|
||||
table.insert(ecam, { color = colors.yellow, text = "REACTOR WASTE HIGH", help = "ReactorHighWaste", items = items})
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.RPSTransient]) then
|
||||
local items = {}
|
||||
local stat = unit.reactor_data.rps_status
|
||||
|
||||
-- for k, _ in pairs(stat) do stat[k] = true end
|
||||
|
||||
local function insert(cond, key, text, color) if cond[key] then table.insert(items, { text = text, help = key, color = color }) end end
|
||||
|
||||
table.insert(items, white("REACTOR SCRAMMED"))
|
||||
insert(stat, "high_dmg", "HIGH DAMAGE", colors.red)
|
||||
insert(stat, "high_temp", "HIGH TEMPERATURE", colors.red)
|
||||
insert(stat, "low_cool", "CRIT LOW COOLANT")
|
||||
insert(stat, "ex_waste", "EXCESS WASTE")
|
||||
insert(stat, "ex_hcool", "EXCESS HEATED COOL")
|
||||
insert(stat, "no_fuel", "NO FUEL")
|
||||
insert(stat, "fault", "HARDWARE FAULT")
|
||||
insert(stat, "timeout", "SUPERVISOR DISCONN")
|
||||
insert(stat, "manual", "MANUAL SCRAM", colors.white)
|
||||
insert(stat, "automatic", "AUTOMATIC SCRAM")
|
||||
insert(stat, "sys_fail", "NOT FORMED", colors.red)
|
||||
insert(stat, "force_dis", "FORCE DISABLED", colors.red)
|
||||
table.insert(items, blue("RESOLVE PROBLEM"))
|
||||
table.insert(items, blue("RESET RPS"))
|
||||
|
||||
table.insert(ecam, { color = colors.yellow, text = "RPS TRANSIENT", help = "RPSTransient", items = items})
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.RCSTransient]) then
|
||||
local items = {}
|
||||
local annunc = unit.annunciator
|
||||
|
||||
-- for k, v in pairs(annunc) do
|
||||
-- if type(v) == "boolean" then annunc[k] = true end
|
||||
-- if type(v) == "table" then
|
||||
-- for a, _ in pairs(v) do
|
||||
-- v[a] = true
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
|
||||
local function insert(cond, key, text, color)
|
||||
if cond == true or (type(cond) == "table" and cond[key]) then table.insert(items, { text = text, help = key, color = color }) end
|
||||
end
|
||||
|
||||
table.insert(items, white("COOLANT PROBLEM"))
|
||||
|
||||
insert(annunc, "RCPTrip", "RCP TRIP", colors.red)
|
||||
insert(annunc, "CoolantLevelLow", "LOW COOLANT")
|
||||
|
||||
if unit.num_boilers == 0 then
|
||||
if (util.time_ms() - unit.last_rate_change_ms) > const.FLOW_STABILITY_DELAY_MS then
|
||||
insert(annunc, "BoilRateMismatch", "BOIL RATE MISMATCH")
|
||||
end
|
||||
|
||||
if unit.turbine_flow_stable then
|
||||
insert(annunc, "RCSFlowLow", "RCS FLOW LOW")
|
||||
insert(annunc, "CoolantFeedMismatch", "COOL FEED MISMATCH")
|
||||
insert(annunc, "SteamFeedMismatch", "STM FEED MISMATCH")
|
||||
end
|
||||
else
|
||||
if (util.time_ms() - unit.last_rate_change_ms) > const.FLOW_STABILITY_DELAY_MS then
|
||||
insert(annunc, "RCSFlowLow", "RCS FLOW LOW")
|
||||
insert(annunc, "BoilRateMismatch", "BOIL RATE MISMATCH")
|
||||
insert(annunc, "CoolantFeedMismatch", "COOL FEED MISMATCH")
|
||||
end
|
||||
|
||||
if unit.turbine_flow_stable then
|
||||
insert(annunc, "SteamFeedMismatch", "STM FEED MISMATCH")
|
||||
end
|
||||
end
|
||||
|
||||
insert(annunc, "MaxWaterReturnFeed", "MAX WTR RTRN FEED")
|
||||
|
||||
for k, v in ipairs(annunc.WaterLevelLow) do insert(v, "WaterLevelLow", "BOILER " .. k .. " WTR LOW", colors.red) end
|
||||
for k, v in ipairs(annunc.HeatingRateLow) do insert(v, "HeatingRateLow", "BOILER " .. k .. " HEAT RATE") end
|
||||
for k, v in ipairs(annunc.TurbineOverSpeed) do insert(v, "TurbineOverSpeed", "TURBINE " .. k .. " OVERSPD", colors.red) end
|
||||
for k, v in ipairs(annunc.GeneratorTrip) do insert(v, "GeneratorTrip", "TURBINE " .. k .. " GEN TRIP") end
|
||||
|
||||
table.insert(items, blue("CHECK COOLING SYS"))
|
||||
|
||||
table.insert(ecam, { color = colors.yellow, text = "RCS TRANSIENT", help = "RCSTransient", items = items})
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.TurbineTrip]) then
|
||||
local items = {}
|
||||
|
||||
for k, v in ipairs(unit.annunciator.TurbineTrip) do
|
||||
if v then table.insert(items, { text = "TURBINE " .. k .. " TRIP", help = "TurbineTrip" }) end
|
||||
end
|
||||
|
||||
table.insert(items, blue("CHECK ENERGY OUT"))
|
||||
table.insert(ecam, { color = colors.red, text = "TURBINE TRIP", help = "TurbineTripAlarm", items = items})
|
||||
end
|
||||
|
||||
if not (tripped(unit.alarms[ALARM.ReactorLost]) or unit.connected) then
|
||||
local items = { blue("CHECK PLC") }
|
||||
table.insert(ecam, { color = colors.yellow, text = "REACTOR OFF-LINE", items = items })
|
||||
end
|
||||
|
||||
for k, v in ipairs(unit.annunciator.BoilerOnline) do
|
||||
if not v then
|
||||
local items = { blue("CHECK RTU") }
|
||||
table.insert(ecam, { color = colors.yellow, text = "BOILER " .. k .. " OFF-LINE", items = items})
|
||||
end
|
||||
end
|
||||
|
||||
for k, v in ipairs(unit.annunciator.TurbineOnline) do
|
||||
if not v then
|
||||
local items = { blue("CHECK RTU") }
|
||||
table.insert(ecam, { color = colors.yellow, text = "TURBINE " .. k .. " OFF-LINE", items = items})
|
||||
end
|
||||
end
|
||||
|
||||
-- if no alarms, put some basic status messages in
|
||||
if #ecam == 0 then
|
||||
table.insert(ecam, { color = colors.green, text = "REACTOR " .. util.trinary(unit.reactor_data.mek_status.status, "NOMINAL", "IDLE"), items = {}})
|
||||
|
||||
local plural = util.trinary(unit.num_turbines > 1, "S", "")
|
||||
table.insert(ecam, { color = colors.green, text = "TURBINE" .. plural .. util.trinary(unit.turbine_flow_stable, " STABLE", " STABILIZING"), items = {}})
|
||||
end
|
||||
|
||||
unit.unit_ps.publish("U_ECAM", textutils.serialize(ecam))
|
||||
|
||||
--#endregion
|
||||
end
|
||||
|
||||
-- update control app with unit data from API_GET_CTRL
|
||||
---@param data table
|
||||
function iocontrol.record_control_data(data)
|
||||
for u_id = 1, #data do
|
||||
local unit = io.units[u_id]
|
||||
local u_data = data[u_id]
|
||||
|
||||
unit.connected = u_data[1]
|
||||
|
||||
unit.reactor_data.rps_tripped = u_data[2]
|
||||
unit.unit_ps.publish("rps_tripped", u_data[2])
|
||||
unit.reactor_data.mek_status.status = u_data[3]
|
||||
unit.unit_ps.publish("status", u_data[3])
|
||||
unit.reactor_data.mek_status.temp = u_data[4]
|
||||
unit.unit_ps.publish("temp", u_data[4])
|
||||
unit.reactor_data.mek_status.burn_rate = u_data[5]
|
||||
unit.unit_ps.publish("burn_rate", u_data[5])
|
||||
unit.reactor_data.mek_status.act_burn_rate = u_data[6]
|
||||
unit.unit_ps.publish("act_burn_rate", u_data[6])
|
||||
unit.reactor_data.mek_struct.max_burn = u_data[7]
|
||||
unit.unit_ps.publish("max_burn", u_data[7])
|
||||
|
||||
unit.annunciator.AutoControl = u_data[8]
|
||||
unit.unit_ps.publish("AutoControl", u_data[8])
|
||||
|
||||
unit.a_group = u_data[9]
|
||||
unit.unit_ps.publish("auto_group_id", unit.a_group)
|
||||
unit.unit_ps.publish("auto_group", types.AUTO_GROUP_NAMES[unit.a_group + 1])
|
||||
|
||||
local control_status = 1
|
||||
|
||||
if unit.connected then
|
||||
if unit.reactor_data.rps_tripped then
|
||||
control_status = 2
|
||||
end
|
||||
|
||||
if unit.reactor_data.mek_status.status then
|
||||
control_status = util.trinary(unit.annunciator.AutoControl, 4, 3)
|
||||
end
|
||||
end
|
||||
|
||||
unit.unit_ps.publish("U_ControlStatus", control_status)
|
||||
end
|
||||
end
|
||||
|
||||
-- update process app with unit data from API_GET_PROC
|
||||
---@param data table
|
||||
function iocontrol.record_process_data(data)
|
||||
-- get unit data
|
||||
for u_id = 1, #io.units do
|
||||
local unit = io.units[u_id]
|
||||
local u_data = data[u_id]
|
||||
|
||||
unit.reactor_data.mek_status.status = u_data[1]
|
||||
unit.reactor_data.mek_struct.max_burn = u_data[2]
|
||||
unit.annunciator.AutoControl = u_data[6]
|
||||
unit.a_group = u_data[7]
|
||||
|
||||
unit.unit_ps.publish("status", u_data[1])
|
||||
unit.unit_ps.publish("max_burn", u_data[2])
|
||||
unit.unit_ps.publish("burn_limit", u_data[3])
|
||||
unit.unit_ps.publish("U_AutoReady", u_data[4])
|
||||
unit.unit_ps.publish("U_AutoDegraded", u_data[5])
|
||||
unit.unit_ps.publish("AutoControl", u_data[6])
|
||||
unit.unit_ps.publish("auto_group_id", unit.a_group)
|
||||
unit.unit_ps.publish("auto_group", types.AUTO_GROUP_NAMES[unit.a_group + 1])
|
||||
end
|
||||
|
||||
-- get facility data
|
||||
local fac = io.facility
|
||||
local f_data = data[#io.units + 1]
|
||||
|
||||
fac.status_lines = f_data[1]
|
||||
|
||||
fac.auto_ready = f_data[2][1]
|
||||
fac.auto_active = f_data[2][2]
|
||||
fac.auto_ramping = f_data[2][3]
|
||||
fac.auto_saturated = f_data[2][4]
|
||||
|
||||
fac.auto_scram = f_data[3]
|
||||
fac.ascram_status = f_data[4]
|
||||
|
||||
fac.ps.publish("status_line_1", fac.status_lines[1])
|
||||
fac.ps.publish("status_line_2", fac.status_lines[2])
|
||||
|
||||
fac.ps.publish("auto_ready", fac.auto_ready)
|
||||
fac.ps.publish("auto_active", fac.auto_active)
|
||||
fac.ps.publish("auto_ramping", fac.auto_ramping)
|
||||
fac.ps.publish("auto_saturated", fac.auto_saturated)
|
||||
|
||||
fac.ps.publish("auto_scram", fac.auto_scram)
|
||||
fac.ps.publish("as_matrix_fault", fac.ascram_status.matrix_fault)
|
||||
fac.ps.publish("as_matrix_fill", fac.ascram_status.matrix_fill)
|
||||
fac.ps.publish("as_crit_alarm", fac.ascram_status.crit_alarm)
|
||||
fac.ps.publish("as_radiation", fac.ascram_status.radiation)
|
||||
fac.ps.publish("as_gen_fault", fac.ascram_status.gen_fault)
|
||||
|
||||
fac.ps.publish("process_mode", f_data[5][1])
|
||||
fac.ps.publish("process_burn_target", f_data[5][2])
|
||||
fac.ps.publish("process_charge_target", f_data[5][3])
|
||||
fac.ps.publish("process_gen_target", f_data[5][4])
|
||||
end
|
||||
|
||||
-- update waste app with unit data from API_GET_WASTE
|
||||
---@param data table
|
||||
function iocontrol.record_waste_data(data)
|
||||
-- get unit data
|
||||
for u_id = 1, #io.units do
|
||||
local unit = io.units[u_id]
|
||||
local u_data = data[u_id]
|
||||
|
||||
unit.waste_mode = u_data[1]
|
||||
unit.waste_product = u_data[2]
|
||||
unit.num_snas = u_data[3]
|
||||
unit.sna_peak_rate = u_data[4]
|
||||
unit.sna_max_rate = u_data[5]
|
||||
unit.sna_out_rate = u_data[6]
|
||||
unit.waste_stats = u_data[7]
|
||||
|
||||
unit.unit_ps.publish("U_AutoWaste", unit.waste_mode == types.WASTE_MODE.AUTO)
|
||||
unit.unit_ps.publish("U_WasteMode", unit.waste_mode)
|
||||
unit.unit_ps.publish("U_WasteProduct", unit.waste_product)
|
||||
|
||||
unit.unit_ps.publish("sna_count", unit.num_snas)
|
||||
unit.unit_ps.publish("sna_peak_rate", unit.sna_peak_rate)
|
||||
unit.unit_ps.publish("sna_max_rate", unit.sna_max_rate)
|
||||
unit.unit_ps.publish("sna_out_rate", unit.sna_out_rate)
|
||||
|
||||
unit.unit_ps.publish("pu_rate", unit.waste_stats[1])
|
||||
unit.unit_ps.publish("po_rate", unit.waste_stats[2])
|
||||
unit.unit_ps.publish("po_pl_rate", unit.waste_stats[3])
|
||||
end
|
||||
|
||||
-- get facility data
|
||||
local fac = io.facility
|
||||
local f_data = data[#io.units + 1]
|
||||
|
||||
fac.auto_current_waste_product = f_data[1]
|
||||
fac.auto_pu_fallback_active = f_data[2]
|
||||
fac.auto_sps_disabled = f_data[3]
|
||||
|
||||
fac.ps.publish("current_waste_product", fac.auto_current_waste_product)
|
||||
fac.ps.publish("pu_fallback_active", fac.auto_pu_fallback_active)
|
||||
fac.ps.publish("sps_disabled_low_power", fac.auto_sps_disabled)
|
||||
|
||||
fac.ps.publish("process_waste_product", f_data[4])
|
||||
fac.ps.publish("process_pu_fallback", f_data[5])
|
||||
fac.ps.publish("process_sps_low_power", f_data[6])
|
||||
|
||||
fac.waste_stats = f_data[7]
|
||||
|
||||
fac.ps.publish("burn_sum", fac.waste_stats[1])
|
||||
fac.ps.publish("pu_rate", fac.waste_stats[2])
|
||||
fac.ps.publish("po_rate", fac.waste_stats[3])
|
||||
fac.ps.publish("po_pl_rate", fac.waste_stats[4])
|
||||
fac.ps.publish("po_am_rate", fac.waste_stats[5])
|
||||
fac.ps.publish("spent_waste_rate", fac.waste_stats[6])
|
||||
|
||||
fac.ps.publish("sps_computed_status", f_data[8])
|
||||
fac.ps.publish("sps_process_rate", f_data[9])
|
||||
end
|
||||
|
||||
-- get the IO controller database
|
||||
function iocontrol.get_db() return io end
|
||||
|
||||
|
||||
657
pocket/iorx.lua
Normal file
657
pocket/iorx.lua
Normal file
@ -0,0 +1,657 @@
|
||||
--
|
||||
-- I/O Control's Data Receive (Rx) Handlers
|
||||
--
|
||||
|
||||
local const = require("scada-common.constants")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local ALARM = types.ALARM
|
||||
local ALARM_STATE = types.ALARM_STATE
|
||||
|
||||
local BLR_STATE = types.BOILER_STATE
|
||||
local TRB_STATE = types.TURBINE_STATE
|
||||
local TNK_STATE = types.TANK_STATE
|
||||
|
||||
local io ---@type pocket_ioctl
|
||||
local iorx = {} ---@class iorx
|
||||
|
||||
-- populate facility data from API_GET_FAC
|
||||
---@param data table
|
||||
---@return boolean valid
|
||||
function iorx.record_facility_data(data)
|
||||
local valid = true
|
||||
|
||||
local fac = io.facility
|
||||
|
||||
fac.all_sys_ok = data[1]
|
||||
fac.rtu_count = data[2]
|
||||
fac.radiation = data[3]
|
||||
|
||||
-- auto control
|
||||
if type(data[4]) == "table" and #data[4] == 4 then
|
||||
fac.auto_ready = data[4][1]
|
||||
fac.auto_active = data[4][2]
|
||||
fac.auto_ramping = data[4][3]
|
||||
fac.auto_saturated = data[4][4]
|
||||
end
|
||||
|
||||
-- waste
|
||||
if type(data[5]) == "table" and #data[5] == 2 then
|
||||
fac.auto_current_waste_product = data[5][1]
|
||||
fac.auto_pu_fallback_active = data[5][2]
|
||||
end
|
||||
|
||||
fac.num_tanks = data[6]
|
||||
fac.has_imatrix = data[7]
|
||||
fac.has_sps = data[8]
|
||||
|
||||
return valid
|
||||
end
|
||||
|
||||
local function tripped(state) return state == ALARM_STATE.TRIPPED or state == ALARM_STATE.ACKED end
|
||||
|
||||
local function _record_multiblock_status(faulted, data, ps)
|
||||
ps.publish("formed", data.formed)
|
||||
ps.publish("faulted", faulted)
|
||||
|
||||
for key, val in pairs(data.state) do ps.publish(key, val) end
|
||||
for key, val in pairs(data.tanks) do ps.publish(key, val) end
|
||||
end
|
||||
|
||||
-- update unit status data from API_GET_UNIT
|
||||
---@param data table
|
||||
function iorx.record_unit_data(data)
|
||||
local unit = io.units[data[1]]
|
||||
|
||||
unit.connected = data[2]
|
||||
local comp_statuses = data[3]
|
||||
unit.a_group = data[4]
|
||||
unit.alarms = data[5]
|
||||
|
||||
local next_c_stat = 1
|
||||
|
||||
unit.unit_ps.publish("auto_group_id", unit.a_group)
|
||||
unit.unit_ps.publish("auto_group", types.AUTO_GROUP_NAMES[unit.a_group + 1])
|
||||
|
||||
--#region Annunciator
|
||||
|
||||
unit.annunciator = data[6]
|
||||
|
||||
local rcs_disconn, rcs_warn, rcs_hazard = false, false, false
|
||||
|
||||
for key, val in pairs(unit.annunciator) do
|
||||
if key == "BoilerOnline" or key == "TurbineOnline" then
|
||||
local every = true
|
||||
|
||||
-- split up online arrays
|
||||
for id = 1, #val do
|
||||
every = every and val[id]
|
||||
|
||||
if key == "BoilerOnline" then
|
||||
unit.boiler_ps_tbl[id].publish(key, val[id])
|
||||
else
|
||||
unit.turbine_ps_tbl[id].publish(key, val[id])
|
||||
end
|
||||
end
|
||||
|
||||
if not every then rcs_disconn = true end
|
||||
|
||||
unit.unit_ps.publish("U_" .. key, every)
|
||||
elseif key == "HeatingRateLow" or key == "WaterLevelLow" then
|
||||
-- split up array for all boilers
|
||||
local any = false
|
||||
for id = 1, #val do
|
||||
any = any or val[id]
|
||||
unit.boiler_ps_tbl[id].publish(key, val[id])
|
||||
end
|
||||
|
||||
if key == "HeatingRateLow" and any then
|
||||
rcs_warn = true
|
||||
elseif key == "WaterLevelLow" and any then
|
||||
rcs_hazard = true
|
||||
end
|
||||
|
||||
unit.unit_ps.publish("U_" .. key, any)
|
||||
elseif key == "SteamDumpOpen" or key == "TurbineOverSpeed" or key == "GeneratorTrip" or key == "TurbineTrip" then
|
||||
-- split up array for all turbines
|
||||
local any = false
|
||||
for id = 1, #val do
|
||||
any = any or val[id]
|
||||
unit.turbine_ps_tbl[id].publish(key, val[id])
|
||||
end
|
||||
|
||||
if key == "GeneratorTrip" and any then
|
||||
rcs_warn = true
|
||||
elseif (key == "TurbineOverSpeed" or key == "TurbineTrip") and any then
|
||||
rcs_hazard = true
|
||||
end
|
||||
|
||||
unit.unit_ps.publish("U_" .. key, any)
|
||||
else
|
||||
-- non-table fields
|
||||
unit.unit_ps.publish(key, val)
|
||||
end
|
||||
end
|
||||
|
||||
local anc = unit.annunciator
|
||||
rcs_hazard = rcs_hazard or anc.RCPTrip
|
||||
rcs_warn = rcs_warn or anc.RCSFlowLow or anc.CoolantLevelLow or anc.RCSFault or anc.MaxWaterReturnFeed or
|
||||
anc.CoolantFeedMismatch or anc.BoilRateMismatch or anc.SteamFeedMismatch
|
||||
|
||||
local rcs_status = 4
|
||||
if rcs_hazard then
|
||||
rcs_status = 2
|
||||
elseif rcs_warn then
|
||||
rcs_status = 3
|
||||
elseif rcs_disconn then
|
||||
rcs_status = 1
|
||||
end
|
||||
|
||||
unit.unit_ps.publish("U_RCS", rcs_status)
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Reactor Data
|
||||
|
||||
unit.reactor_data = data[7]
|
||||
|
||||
local control_status = 1
|
||||
local reactor_status = 1
|
||||
local rps_status = 1
|
||||
|
||||
if unit.connected then
|
||||
-- update RPS status
|
||||
if unit.reactor_data.rps_tripped then
|
||||
control_status = 2
|
||||
rps_status = util.trinary(unit.reactor_data.rps_trip_cause == "manual", 3, 2)
|
||||
else
|
||||
rps_status = 4
|
||||
end
|
||||
|
||||
reactor_status = 4 -- ok, until proven otherwise
|
||||
|
||||
-- update reactor/control status
|
||||
if unit.reactor_data.mek_status.status then
|
||||
control_status = util.trinary(unit.annunciator.AutoControl, 4, 3)
|
||||
else
|
||||
if unit.reactor_data.no_reactor then
|
||||
reactor_status = 2
|
||||
elseif (not unit.reactor_data.formed) or unit.reactor_data.rps_status.force_dis then
|
||||
reactor_status = 3
|
||||
end
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.reactor_data) do
|
||||
if key ~= "rps_status" and key ~= "mek_struct" and key ~= "mek_status" then
|
||||
unit.unit_ps.publish(key, val)
|
||||
end
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.reactor_data.rps_status) do
|
||||
unit.unit_ps.publish(key, val)
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.reactor_data.mek_struct) do
|
||||
unit.unit_ps.publish(key, val)
|
||||
end
|
||||
|
||||
for key, val in pairs(unit.reactor_data.mek_status) do
|
||||
unit.unit_ps.publish(key, val)
|
||||
end
|
||||
end
|
||||
|
||||
unit.unit_ps.publish("U_ControlStatus", control_status)
|
||||
unit.unit_ps.publish("U_ReactorStatus", reactor_status)
|
||||
unit.unit_ps.publish("U_ReactorStateStatus", comp_statuses[next_c_stat])
|
||||
unit.unit_ps.publish("U_RPS", rps_status)
|
||||
|
||||
next_c_stat = next_c_stat + 1
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region RTU Devices
|
||||
|
||||
unit.boiler_data_tbl = data[8]
|
||||
|
||||
for id = 1, #unit.boiler_data_tbl do
|
||||
local boiler = unit.boiler_data_tbl[id]
|
||||
local ps = unit.boiler_ps_tbl[id]
|
||||
local c_stat = comp_statuses[next_c_stat]
|
||||
|
||||
local boiler_status = 1
|
||||
|
||||
if c_stat ~= BLR_STATE.OFFLINE then
|
||||
if c_stat == BLR_STATE.FAULT then
|
||||
boiler_status = 3
|
||||
elseif c_stat ~= BLR_STATE.UNFORMED then
|
||||
boiler_status = 4
|
||||
else
|
||||
boiler_status = 2
|
||||
end
|
||||
|
||||
_record_multiblock_status(c_stat == BLR_STATE.FAULT, boiler, ps)
|
||||
end
|
||||
|
||||
ps.publish("BoilerStatus", boiler_status)
|
||||
ps.publish("BoilerStateStatus", c_stat)
|
||||
|
||||
next_c_stat = next_c_stat + 1
|
||||
end
|
||||
|
||||
unit.turbine_data_tbl = data[9]
|
||||
|
||||
for id = 1, #unit.turbine_data_tbl do
|
||||
local turbine = unit.turbine_data_tbl[id]
|
||||
local ps = unit.turbine_ps_tbl[id]
|
||||
local c_stat = comp_statuses[next_c_stat]
|
||||
|
||||
local turbine_status = 1
|
||||
|
||||
if c_stat ~= TRB_STATE.OFFLINE then
|
||||
if c_stat == TRB_STATE.FAULT then
|
||||
turbine_status = 3
|
||||
elseif turbine.formed then
|
||||
turbine_status = 4
|
||||
else
|
||||
turbine_status = 2
|
||||
end
|
||||
|
||||
_record_multiblock_status(c_stat == TRB_STATE.FAULT, turbine, ps)
|
||||
end
|
||||
|
||||
ps.publish("TurbineStatus", turbine_status)
|
||||
ps.publish("TurbineStateStatus", c_stat)
|
||||
|
||||
next_c_stat = next_c_stat + 1
|
||||
end
|
||||
|
||||
unit.tank_data_tbl = data[10]
|
||||
|
||||
for id = 1, #unit.tank_data_tbl do
|
||||
local tank = unit.tank_data_tbl[id]
|
||||
local ps = unit.tank_ps_tbl[id]
|
||||
local c_stat = comp_statuses[next_c_stat]
|
||||
|
||||
local tank_status = 1
|
||||
|
||||
if c_stat ~= TNK_STATE.OFFLINE then
|
||||
if c_stat == TNK_STATE.FAULT then
|
||||
tank_status = 3
|
||||
elseif tank.formed then
|
||||
tank_status = 4
|
||||
else
|
||||
tank_status = 2
|
||||
end
|
||||
|
||||
_record_multiblock_status(c_stat == TNK_STATE.FAULT, tank, ps)
|
||||
end
|
||||
|
||||
ps.publish("DynamicTankStatus", tank_status)
|
||||
ps.publish("DynamicTankStateStatus", c_stat)
|
||||
|
||||
next_c_stat = next_c_stat + 1
|
||||
end
|
||||
|
||||
unit.last_rate_change_ms = data[11]
|
||||
unit.turbine_flow_stable = data[12]
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Status Information Display
|
||||
|
||||
local ecam = {} -- aviation reference :)
|
||||
|
||||
-- local function red(text) return { text = text, color = colors.red } end
|
||||
local function white(text) return { text = text, color = colors.white } end
|
||||
local function blue(text) return { text = text, color = colors.blue } end
|
||||
|
||||
-- if unit.reactor_data.rps_status then
|
||||
-- for k, v in pairs(unit.alarms) do
|
||||
-- unit.alarms[k] = ALARM_STATE.TRIPPED
|
||||
-- end
|
||||
-- end
|
||||
|
||||
if tripped(unit.alarms[ALARM.ContainmentBreach]) then
|
||||
local items = { white("REACTOR MELTDOWN"), blue("DON HAZMAT SUIT") }
|
||||
table.insert(ecam, { color = colors.red, text = "CONTAINMENT BREACH", help = "ContainmentBreach", items = items })
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.ContainmentRadiation]) then
|
||||
local items = {
|
||||
white("RADIATION DETECTED"),
|
||||
blue("DON HAZMAT SUIT"),
|
||||
blue("RESOLVE LEAK"),
|
||||
blue("AWAIT SAFE LEVELS")
|
||||
}
|
||||
|
||||
table.insert(ecam, { color = colors.red, text = "RADIATION LEAK", help = "ContainmentRadiation", items = items })
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.CriticalDamage]) then
|
||||
local items = { white("MELTDOWN IMMINENT"), blue("EVACUATE") }
|
||||
table.insert(ecam, { color = colors.red, text = "RCT DAMAGE CRITICAL", help = "CriticalDamage", items = items })
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.ReactorLost]) then
|
||||
local items = { white("REACTOR OFF-LINE"), blue("CHECK PLC") }
|
||||
table.insert(ecam, { color = colors.red, text = "REACTOR CONN LOST", help = "ReactorLost", items = items })
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.ReactorDamage]) then
|
||||
local items = { white("REACTOR DAMAGED"), blue("CHECK RCS"), blue("AWAIT DMG REDUCED") }
|
||||
table.insert(ecam, { color = colors.red, text = "REACTOR DAMAGE", help = "ReactorDamage", items = items })
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.ReactorOverTemp]) then
|
||||
local items = { white("DAMAGING TEMP"), blue("CHECK RCS"), blue("AWAIT COOLDOWN") }
|
||||
table.insert(ecam, { color = colors.red, text = "REACTOR OVER TEMP", help = "ReactorOverTemp", items = items })
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.ReactorHighTemp]) then
|
||||
local items = { white("OVER EXPECTED TEMP"), blue("CHECK RCS") }
|
||||
table.insert(ecam, { color = colors.yellow, text = "REACTOR HIGH TEMP", help = "ReactorHighTemp", items = items})
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.ReactorWasteLeak]) then
|
||||
local items = { white("AT WASTE CAPACITY"), blue("CHECK WASTE OUTPUT"), blue("KEEP RCT DISABLED") }
|
||||
table.insert(ecam, { color = colors.red, text = "REACTOR WASTE LEAK", help = "ReactorWasteLeak", items = items})
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.ReactorHighWaste]) then
|
||||
local items = { blue("CHECK WASTE OUTPUT") }
|
||||
table.insert(ecam, { color = colors.yellow, text = "REACTOR WASTE HIGH", help = "ReactorHighWaste", items = items})
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.RPSTransient]) then
|
||||
local items = {}
|
||||
local stat = unit.reactor_data.rps_status
|
||||
|
||||
-- for k, _ in pairs(stat) do stat[k] = true end
|
||||
|
||||
local function insert(cond, key, text, color) if cond[key] then table.insert(items, { text = text, help = key, color = color }) end end
|
||||
|
||||
table.insert(items, white("REACTOR SCRAMMED"))
|
||||
insert(stat, "high_dmg", "HIGH DAMAGE", colors.red)
|
||||
insert(stat, "high_temp", "HIGH TEMPERATURE", colors.red)
|
||||
insert(stat, "low_cool", "CRIT LOW COOLANT")
|
||||
insert(stat, "ex_waste", "EXCESS WASTE")
|
||||
insert(stat, "ex_hcool", "EXCESS HEATED COOL")
|
||||
insert(stat, "no_fuel", "NO FUEL")
|
||||
insert(stat, "fault", "HARDWARE FAULT")
|
||||
insert(stat, "timeout", "SUPERVISOR DISCONN")
|
||||
insert(stat, "manual", "MANUAL SCRAM", colors.white)
|
||||
insert(stat, "automatic", "AUTOMATIC SCRAM")
|
||||
insert(stat, "sys_fail", "NOT FORMED", colors.red)
|
||||
insert(stat, "force_dis", "FORCE DISABLED", colors.red)
|
||||
table.insert(items, blue("RESOLVE PROBLEM"))
|
||||
table.insert(items, blue("RESET RPS"))
|
||||
|
||||
table.insert(ecam, { color = colors.yellow, text = "RPS TRANSIENT", help = "RPSTransient", items = items})
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.RCSTransient]) then
|
||||
local items = {}
|
||||
local annunc = unit.annunciator
|
||||
|
||||
-- for k, v in pairs(annunc) do
|
||||
-- if type(v) == "boolean" then annunc[k] = true end
|
||||
-- if type(v) == "table" then
|
||||
-- for a, _ in pairs(v) do
|
||||
-- v[a] = true
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
|
||||
local function insert(cond, key, text, color)
|
||||
if cond == true or (type(cond) == "table" and cond[key]) then table.insert(items, { text = text, help = key, color = color }) end
|
||||
end
|
||||
|
||||
table.insert(items, white("COOLANT PROBLEM"))
|
||||
|
||||
insert(annunc, "RCPTrip", "RCP TRIP", colors.red)
|
||||
insert(annunc, "CoolantLevelLow", "LOW COOLANT")
|
||||
|
||||
if unit.num_boilers == 0 then
|
||||
if (util.time_ms() - unit.last_rate_change_ms) > const.FLOW_STABILITY_DELAY_MS then
|
||||
insert(annunc, "BoilRateMismatch", "BOIL RATE MISMATCH")
|
||||
end
|
||||
|
||||
if unit.turbine_flow_stable then
|
||||
insert(annunc, "RCSFlowLow", "RCS FLOW LOW")
|
||||
insert(annunc, "CoolantFeedMismatch", "COOL FEED MISMATCH")
|
||||
insert(annunc, "SteamFeedMismatch", "STM FEED MISMATCH")
|
||||
end
|
||||
else
|
||||
if (util.time_ms() - unit.last_rate_change_ms) > const.FLOW_STABILITY_DELAY_MS then
|
||||
insert(annunc, "RCSFlowLow", "RCS FLOW LOW")
|
||||
insert(annunc, "BoilRateMismatch", "BOIL RATE MISMATCH")
|
||||
insert(annunc, "CoolantFeedMismatch", "COOL FEED MISMATCH")
|
||||
end
|
||||
|
||||
if unit.turbine_flow_stable then
|
||||
insert(annunc, "SteamFeedMismatch", "STM FEED MISMATCH")
|
||||
end
|
||||
end
|
||||
|
||||
insert(annunc, "MaxWaterReturnFeed", "MAX WTR RTRN FEED")
|
||||
|
||||
for k, v in ipairs(annunc.WaterLevelLow) do insert(v, "WaterLevelLow", "BOILER " .. k .. " WTR LOW", colors.red) end
|
||||
for k, v in ipairs(annunc.HeatingRateLow) do insert(v, "HeatingRateLow", "BOILER " .. k .. " HEAT RATE") end
|
||||
for k, v in ipairs(annunc.TurbineOverSpeed) do insert(v, "TurbineOverSpeed", "TURBINE " .. k .. " OVERSPD", colors.red) end
|
||||
for k, v in ipairs(annunc.GeneratorTrip) do insert(v, "GeneratorTrip", "TURBINE " .. k .. " GEN TRIP") end
|
||||
|
||||
table.insert(items, blue("CHECK COOLING SYS"))
|
||||
|
||||
table.insert(ecam, { color = colors.yellow, text = "RCS TRANSIENT", help = "RCSTransient", items = items})
|
||||
end
|
||||
|
||||
if tripped(unit.alarms[ALARM.TurbineTrip]) then
|
||||
local items = {}
|
||||
|
||||
for k, v in ipairs(unit.annunciator.TurbineTrip) do
|
||||
if v then table.insert(items, { text = "TURBINE " .. k .. " TRIP", help = "TurbineTrip" }) end
|
||||
end
|
||||
|
||||
table.insert(items, blue("CHECK ENERGY OUT"))
|
||||
table.insert(ecam, { color = colors.red, text = "TURBINE TRIP", help = "TurbineTripAlarm", items = items})
|
||||
end
|
||||
|
||||
if not (tripped(unit.alarms[ALARM.ReactorLost]) or unit.connected) then
|
||||
local items = { blue("CHECK PLC") }
|
||||
table.insert(ecam, { color = colors.yellow, text = "REACTOR OFF-LINE", items = items })
|
||||
end
|
||||
|
||||
for k, v in ipairs(unit.annunciator.BoilerOnline) do
|
||||
if not v then
|
||||
local items = { blue("CHECK RTU") }
|
||||
table.insert(ecam, { color = colors.yellow, text = "BOILER " .. k .. " OFF-LINE", items = items})
|
||||
end
|
||||
end
|
||||
|
||||
for k, v in ipairs(unit.annunciator.TurbineOnline) do
|
||||
if not v then
|
||||
local items = { blue("CHECK RTU") }
|
||||
table.insert(ecam, { color = colors.yellow, text = "TURBINE " .. k .. " OFF-LINE", items = items})
|
||||
end
|
||||
end
|
||||
|
||||
-- if no alarms, put some basic status messages in
|
||||
if #ecam == 0 then
|
||||
table.insert(ecam, { color = colors.green, text = "REACTOR " .. util.trinary(unit.reactor_data.mek_status.status, "NOMINAL", "IDLE"), items = {}})
|
||||
|
||||
local plural = util.trinary(unit.num_turbines > 1, "S", "")
|
||||
table.insert(ecam, { color = colors.green, text = "TURBINE" .. plural .. util.trinary(unit.turbine_flow_stable, " STABLE", " STABILIZING"), items = {}})
|
||||
end
|
||||
|
||||
unit.unit_ps.publish("U_ECAM", textutils.serialize(ecam))
|
||||
|
||||
--#endregion
|
||||
end
|
||||
|
||||
-- update control app with unit data from API_GET_CTRL
|
||||
---@param data table
|
||||
function iorx.record_control_data(data)
|
||||
for u_id = 1, #data do
|
||||
local unit = io.units[u_id]
|
||||
local u_data = data[u_id]
|
||||
|
||||
unit.connected = u_data[1]
|
||||
|
||||
unit.reactor_data.rps_tripped = u_data[2]
|
||||
unit.unit_ps.publish("rps_tripped", u_data[2])
|
||||
unit.reactor_data.mek_status.status = u_data[3]
|
||||
unit.unit_ps.publish("status", u_data[3])
|
||||
unit.reactor_data.mek_status.temp = u_data[4]
|
||||
unit.unit_ps.publish("temp", u_data[4])
|
||||
unit.reactor_data.mek_status.burn_rate = u_data[5]
|
||||
unit.unit_ps.publish("burn_rate", u_data[5])
|
||||
unit.reactor_data.mek_status.act_burn_rate = u_data[6]
|
||||
unit.unit_ps.publish("act_burn_rate", u_data[6])
|
||||
unit.reactor_data.mek_struct.max_burn = u_data[7]
|
||||
unit.unit_ps.publish("max_burn", u_data[7])
|
||||
|
||||
unit.annunciator.AutoControl = u_data[8]
|
||||
unit.unit_ps.publish("AutoControl", u_data[8])
|
||||
|
||||
unit.a_group = u_data[9]
|
||||
unit.unit_ps.publish("auto_group_id", unit.a_group)
|
||||
unit.unit_ps.publish("auto_group", types.AUTO_GROUP_NAMES[unit.a_group + 1])
|
||||
|
||||
local control_status = 1
|
||||
|
||||
if unit.connected then
|
||||
if unit.reactor_data.rps_tripped then
|
||||
control_status = 2
|
||||
end
|
||||
|
||||
if unit.reactor_data.mek_status.status then
|
||||
control_status = util.trinary(unit.annunciator.AutoControl, 4, 3)
|
||||
end
|
||||
end
|
||||
|
||||
unit.unit_ps.publish("U_ControlStatus", control_status)
|
||||
end
|
||||
end
|
||||
|
||||
-- update process app with unit data from API_GET_PROC
|
||||
---@param data table
|
||||
function iorx.record_process_data(data)
|
||||
-- get unit data
|
||||
for u_id = 1, #io.units do
|
||||
local unit = io.units[u_id]
|
||||
local u_data = data[u_id]
|
||||
|
||||
unit.reactor_data.mek_status.status = u_data[1]
|
||||
unit.reactor_data.mek_struct.max_burn = u_data[2]
|
||||
unit.annunciator.AutoControl = u_data[6]
|
||||
unit.a_group = u_data[7]
|
||||
|
||||
unit.unit_ps.publish("status", u_data[1])
|
||||
unit.unit_ps.publish("max_burn", u_data[2])
|
||||
unit.unit_ps.publish("burn_limit", u_data[3])
|
||||
unit.unit_ps.publish("U_AutoReady", u_data[4])
|
||||
unit.unit_ps.publish("U_AutoDegraded", u_data[5])
|
||||
unit.unit_ps.publish("AutoControl", u_data[6])
|
||||
unit.unit_ps.publish("auto_group_id", unit.a_group)
|
||||
unit.unit_ps.publish("auto_group", types.AUTO_GROUP_NAMES[unit.a_group + 1])
|
||||
end
|
||||
|
||||
-- get facility data
|
||||
local fac = io.facility
|
||||
local f_data = data[#io.units + 1]
|
||||
|
||||
fac.status_lines = f_data[1]
|
||||
|
||||
fac.auto_ready = f_data[2][1]
|
||||
fac.auto_active = f_data[2][2]
|
||||
fac.auto_ramping = f_data[2][3]
|
||||
fac.auto_saturated = f_data[2][4]
|
||||
|
||||
fac.auto_scram = f_data[3]
|
||||
fac.ascram_status = f_data[4]
|
||||
|
||||
fac.ps.publish("status_line_1", fac.status_lines[1])
|
||||
fac.ps.publish("status_line_2", fac.status_lines[2])
|
||||
|
||||
fac.ps.publish("auto_ready", fac.auto_ready)
|
||||
fac.ps.publish("auto_active", fac.auto_active)
|
||||
fac.ps.publish("auto_ramping", fac.auto_ramping)
|
||||
fac.ps.publish("auto_saturated", fac.auto_saturated)
|
||||
|
||||
fac.ps.publish("auto_scram", fac.auto_scram)
|
||||
fac.ps.publish("as_matrix_fault", fac.ascram_status.matrix_fault)
|
||||
fac.ps.publish("as_matrix_fill", fac.ascram_status.matrix_fill)
|
||||
fac.ps.publish("as_crit_alarm", fac.ascram_status.crit_alarm)
|
||||
fac.ps.publish("as_radiation", fac.ascram_status.radiation)
|
||||
fac.ps.publish("as_gen_fault", fac.ascram_status.gen_fault)
|
||||
|
||||
fac.ps.publish("process_mode", f_data[5][1])
|
||||
fac.ps.publish("process_burn_target", f_data[5][2])
|
||||
fac.ps.publish("process_charge_target", f_data[5][3])
|
||||
fac.ps.publish("process_gen_target", f_data[5][4])
|
||||
end
|
||||
|
||||
-- update waste app with unit data from API_GET_WASTE
|
||||
---@param data table
|
||||
function iorx.record_waste_data(data)
|
||||
-- get unit data
|
||||
for u_id = 1, #io.units do
|
||||
local unit = io.units[u_id]
|
||||
local u_data = data[u_id]
|
||||
|
||||
unit.waste_mode = u_data[1]
|
||||
unit.waste_product = u_data[2]
|
||||
unit.num_snas = u_data[3]
|
||||
unit.sna_peak_rate = u_data[4]
|
||||
unit.sna_max_rate = u_data[5]
|
||||
unit.sna_out_rate = u_data[6]
|
||||
unit.waste_stats = u_data[7]
|
||||
|
||||
unit.unit_ps.publish("U_AutoWaste", unit.waste_mode == types.WASTE_MODE.AUTO)
|
||||
unit.unit_ps.publish("U_WasteMode", unit.waste_mode)
|
||||
unit.unit_ps.publish("U_WasteProduct", unit.waste_product)
|
||||
|
||||
unit.unit_ps.publish("sna_count", unit.num_snas)
|
||||
unit.unit_ps.publish("sna_peak_rate", unit.sna_peak_rate)
|
||||
unit.unit_ps.publish("sna_max_rate", unit.sna_max_rate)
|
||||
unit.unit_ps.publish("sna_out_rate", unit.sna_out_rate)
|
||||
|
||||
unit.unit_ps.publish("pu_rate", unit.waste_stats[1])
|
||||
unit.unit_ps.publish("po_rate", unit.waste_stats[2])
|
||||
unit.unit_ps.publish("po_pl_rate", unit.waste_stats[3])
|
||||
end
|
||||
|
||||
-- get facility data
|
||||
local fac = io.facility
|
||||
local f_data = data[#io.units + 1]
|
||||
|
||||
fac.auto_current_waste_product = f_data[1]
|
||||
fac.auto_pu_fallback_active = f_data[2]
|
||||
fac.auto_sps_disabled = f_data[3]
|
||||
|
||||
fac.ps.publish("current_waste_product", fac.auto_current_waste_product)
|
||||
fac.ps.publish("pu_fallback_active", fac.auto_pu_fallback_active)
|
||||
fac.ps.publish("sps_disabled_low_power", fac.auto_sps_disabled)
|
||||
|
||||
fac.ps.publish("process_waste_product", f_data[4])
|
||||
fac.ps.publish("process_pu_fallback", f_data[5])
|
||||
fac.ps.publish("process_sps_low_power", f_data[6])
|
||||
|
||||
fac.waste_stats = f_data[7]
|
||||
|
||||
fac.ps.publish("burn_sum", fac.waste_stats[1])
|
||||
fac.ps.publish("pu_rate", fac.waste_stats[2])
|
||||
fac.ps.publish("po_rate", fac.waste_stats[3])
|
||||
fac.ps.publish("po_pl_rate", fac.waste_stats[4])
|
||||
fac.ps.publish("po_am_rate", fac.waste_stats[5])
|
||||
fac.ps.publish("spent_waste_rate", fac.waste_stats[6])
|
||||
|
||||
fac.ps.publish("sps_computed_status", f_data[8])
|
||||
fac.ps.publish("sps_process_rate", f_data[9])
|
||||
end
|
||||
|
||||
return function (io_obj)
|
||||
io = io_obj
|
||||
return iorx
|
||||
end
|
||||
@ -29,6 +29,7 @@ pocket.MQ__RENDER_CMD = MQ__RENDER_CMD
|
||||
pocket.MQ__RENDER_DATA = MQ__RENDER_DATA
|
||||
|
||||
---@type pkt_config
|
||||
---@diagnostic disable-next-line: missing-fields
|
||||
local config = {}
|
||||
|
||||
pocket.config = config
|
||||
@ -726,23 +727,23 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
|
||||
end
|
||||
elseif packet.type == CRDN_TYPE.API_GET_FAC then
|
||||
if _check_length(packet, 11) then
|
||||
iocontrol.record_facility_data(packet.data)
|
||||
iocontrol.rx.record_facility_data(packet.data)
|
||||
end
|
||||
elseif packet.type == CRDN_TYPE.API_GET_UNIT then
|
||||
if _check_length(packet, 12) and type(packet.data[1]) == "number" and iocontrol.get_db().units[packet.data[1]] then
|
||||
iocontrol.record_unit_data(packet.data)
|
||||
iocontrol.rx.record_unit_data(packet.data)
|
||||
end
|
||||
elseif packet.type == CRDN_TYPE.API_GET_CTRL then
|
||||
if _check_length(packet, #iocontrol.get_db().units) then
|
||||
iocontrol.record_control_data(packet.data)
|
||||
iocontrol.rx.record_control_data(packet.data)
|
||||
end
|
||||
elseif packet.type == CRDN_TYPE.API_GET_PROC then
|
||||
if _check_length(packet, #iocontrol.get_db().units + 1) then
|
||||
iocontrol.record_process_data(packet.data)
|
||||
iocontrol.rx.record_process_data(packet.data)
|
||||
end
|
||||
elseif packet.type == CRDN_TYPE.API_GET_WASTE then
|
||||
if _check_length(packet, #iocontrol.get_db().units + 1) then
|
||||
iocontrol.record_waste_data(packet.data)
|
||||
iocontrol.rx.record_waste_data(packet.data)
|
||||
end
|
||||
else _fail_type(packet) end
|
||||
else
|
||||
|
||||
@ -20,7 +20,7 @@ local pocket = require("pocket.pocket")
|
||||
local renderer = require("pocket.renderer")
|
||||
local threads = require("pocket.threads")
|
||||
|
||||
local POCKET_VERSION = "v0.12.10-alpha"
|
||||
local POCKET_VERSION = "v0.12.13-alpha"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
|
||||
@ -9,6 +9,7 @@ local pocket = require("pocket.pocket")
|
||||
|
||||
local style = require("pocket.ui.style")
|
||||
|
||||
local dyn_tank = require("pocket.ui.pages.dynamic_tank")
|
||||
local boiler = require("pocket.ui.pages.unit_boiler")
|
||||
local reactor = require("pocket.ui.pages.unit_reactor")
|
||||
local turbine = require("pocket.ui.pages.unit_turbine")
|
||||
@ -92,6 +93,10 @@ local function new_view(root)
|
||||
table.insert(list, { label = "T-" .. i, color = core.cpair(colors.black, colors.lightGray), callback = nav_links[id].turbine[i] })
|
||||
end
|
||||
|
||||
if #unit.tank_data_tbl > 0 then
|
||||
table.insert(list, { label = "DYN", color = core.cpair(colors.black, colors.lightGray), callback = nav_links[id].d_tank })
|
||||
end
|
||||
|
||||
app.set_sidebar(list)
|
||||
end
|
||||
|
||||
@ -363,6 +368,15 @@ local function new_view(root)
|
||||
|
||||
--#endregion
|
||||
|
||||
--#region Dynamic Tank Tab
|
||||
|
||||
if #unit.tank_data_tbl > 0 then
|
||||
local tank_pane = Div{parent=page_div}
|
||||
nav_links[i].d_tank = dyn_tank(app, u_page, panes, tank_pane, i, unit.tank_ps_tbl[1], update)
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
util.nop()
|
||||
end
|
||||
|
||||
|
||||
83
pocket/ui/pages/dynamic_tank.lua
Normal file
83
pocket/ui/pages/dynamic_tank.lua
Normal file
@ -0,0 +1,83 @@
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local iocontrol = require("pocket.iocontrol")
|
||||
|
||||
local style = require("pocket.ui.style")
|
||||
|
||||
local core = require("graphics.core")
|
||||
|
||||
local Div = require("graphics.elements.Div")
|
||||
local TextBox = require("graphics.elements.TextBox")
|
||||
|
||||
local DataIndicator = require("graphics.elements.indicators.DataIndicator")
|
||||
local HorizontalBar = require("graphics.elements.indicators.HorizontalBar")
|
||||
local IconIndicator = require("graphics.elements.indicators.IconIndicator")
|
||||
local StateIndicator = require("graphics.elements.indicators.StateIndicator")
|
||||
|
||||
local CONTAINER_MODE = types.CONTAINER_MODE
|
||||
local COOLANT_TYPE = types.COOLANT_TYPE
|
||||
|
||||
local cpair = core.cpair
|
||||
|
||||
local label = style.label
|
||||
local lu_col = style.label_unit_pair
|
||||
local text_fg = style.text_fg
|
||||
|
||||
local mode_ind_s = {
|
||||
{ color = cpair(colors.black, colors.lightGray), symbol = "-" },
|
||||
{ color = cpair(colors.black, colors.white), symbol = "+" }
|
||||
}
|
||||
|
||||
-- create a dynamic tank view for the unit or facility app
|
||||
---@param app pocket_app
|
||||
---@param page nav_tree_page
|
||||
---@param panes Div[]
|
||||
---@param tank_pane Div
|
||||
---@param tank_id integer global facility tank ID (as used for tank list, etc)
|
||||
---@param ps psil
|
||||
---@param update function
|
||||
return function (app, page, panes, tank_pane, tank_id, ps, update)
|
||||
local fac = iocontrol.get_db().facility
|
||||
|
||||
local tank_div = Div{parent=tank_pane,x=2,width=tank_pane.get_width()-2}
|
||||
table.insert(panes, tank_div)
|
||||
|
||||
local tank_page = app.new_page(page, #panes)
|
||||
tank_page.tasks = { update }
|
||||
|
||||
TextBox{parent=tank_div,y=1,text="Dyn Tank",width=9}
|
||||
local status = StateIndicator{parent=tank_div,x=10,y=1,states=style.dtank.states,value=1,min_width=12}
|
||||
status.register(ps, "DynamicTankStateStatus", status.update)
|
||||
|
||||
TextBox{parent=tank_div,y=3,text="Fill",width=10,fg_bg=label}
|
||||
local tank_pcnt = DataIndicator{parent=tank_div,x=14,y=3,label="",format="%5.2f",value=100,unit="%",lu_colors=lu_col,width=8,fg_bg=text_fg}
|
||||
local tank_amnt = DataIndicator{parent=tank_div,label="",format="%18d",value=0,commas=true,unit="mB",lu_colors=lu_col,width=21,fg_bg=text_fg}
|
||||
|
||||
local is_water = fac.tank_fluid_types[tank_id] == COOLANT_TYPE.WATER
|
||||
|
||||
TextBox{parent=tank_div,y=6,text=util.trinary(is_water,"Water","Sodium").." Level",width=12,fg_bg=label}
|
||||
local level = HorizontalBar{parent=tank_div,y=7,bar_fg_bg=cpair(util.trinary(is_water,colors.blue,colors.lightBlue),colors.gray),height=1,width=21}
|
||||
|
||||
TextBox{parent=tank_div,y=9,text="Tank Fill Mode",width=14,fg_bg=label}
|
||||
local can_fill = IconIndicator{parent=tank_div,y=10,label="Fill",states=mode_ind_s}
|
||||
local can_empty = IconIndicator{parent=tank_div,y=11,label="Empty",states=mode_ind_s}
|
||||
|
||||
local function _can_fill(mode)
|
||||
can_fill.update((mode == CONTAINER_MODE.BOTH) or (mode == CONTAINER_MODE.FILL))
|
||||
end
|
||||
|
||||
local function _can_empty(mode)
|
||||
can_empty.update((mode == CONTAINER_MODE.BOTH) or (mode == CONTAINER_MODE.EMPTY))
|
||||
end
|
||||
|
||||
tank_pcnt.register(ps, "fill", function (f) tank_pcnt.update(f * 100) end)
|
||||
tank_amnt.register(ps, "stored", function (sto) tank_amnt.update(sto.amount) end)
|
||||
|
||||
level.register(ps, "fill", level.update)
|
||||
|
||||
can_fill.register(ps, "container_mode", _can_fill)
|
||||
can_empty.register(ps, "container_mode", _can_empty)
|
||||
|
||||
return tank_page.nav_to
|
||||
end
|
||||
@ -95,180 +95,93 @@ style.icon_states = states
|
||||
-- MAIN LAYOUT --
|
||||
|
||||
style.reactor = {
|
||||
-- reactor states
|
||||
-- reactor states<br>
|
||||
---@see REACTOR_STATE
|
||||
states = {
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "OFF-LINE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "NOT FORMED"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "PLC FAULT"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.white, colors.gray),
|
||||
text = "DISABLED"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "ACTIVE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.red),
|
||||
text = "SCRAMMED"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.red),
|
||||
text = "FORCE DSBL"
|
||||
}
|
||||
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "PLC FAULT" },
|
||||
{ color = cpair(colors.white, colors.gray), text = "DISABLED" },
|
||||
{ color = cpair(colors.black, colors.green), text = "ACTIVE" },
|
||||
{ color = cpair(colors.black, colors.red), text = "SCRAMMED" },
|
||||
{ color = cpair(colors.black, colors.red), text = "FORCE DSBL" }
|
||||
}
|
||||
}
|
||||
|
||||
style.boiler = {
|
||||
-- boiler states
|
||||
-- boiler states<br>
|
||||
---@see BOILER_STATE
|
||||
states = {
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "OFF-LINE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "NOT FORMED"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "RTU FAULT"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.white, colors.gray),
|
||||
text = "IDLE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "ACTIVE"
|
||||
}
|
||||
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "RTU FAULT" },
|
||||
{ color = cpair(colors.white, colors.gray), text = "IDLE" },
|
||||
{ color = cpair(colors.black, colors.green), text = "ACTIVE" }
|
||||
}
|
||||
}
|
||||
|
||||
style.turbine = {
|
||||
-- turbine states
|
||||
-- turbine states<br>
|
||||
---@see TURBINE_STATE
|
||||
states = {
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "OFF-LINE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "NOT FORMED"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "RTU FAULT"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.white, colors.gray),
|
||||
text = "IDLE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "ACTIVE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.red),
|
||||
text = "TRIP"
|
||||
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "RTU FAULT" },
|
||||
{ color = cpair(colors.white, colors.gray), text = "IDLE" },
|
||||
{ color = cpair(colors.black, colors.green), text = "ACTIVE" },
|
||||
{ color = cpair(colors.black, colors.red), text = "TRIP" }
|
||||
}
|
||||
}
|
||||
|
||||
style.dtank = {
|
||||
-- dynamic tank states<br>
|
||||
---@see TANK_STATE
|
||||
states = {
|
||||
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "RTU FAULT" },
|
||||
{ color = cpair(colors.black, colors.green), text = "ONLINE" },
|
||||
{ color = cpair(colors.black, colors.yellow), text = "LOW FILL" },
|
||||
{ color = cpair(colors.black, colors.green), text = "FILLED" }
|
||||
}
|
||||
}
|
||||
|
||||
style.imatrix = {
|
||||
-- induction matrix states
|
||||
-- induction matrix states<br>
|
||||
---@see IMATRIX_STATE
|
||||
states = {
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "OFF-LINE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "NOT FORMED"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "RTU FAULT"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "ONLINE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "LOW CHARGE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "HIGH CHARGE"
|
||||
},
|
||||
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "RTU FAULT" },
|
||||
{ color = cpair(colors.black, colors.green), text = "ONLINE" },
|
||||
{ color = cpair(colors.black, colors.yellow), text = "LOW CHARGE" },
|
||||
{ color = cpair(colors.black, colors.yellow), text = "HIGH CHARGE" }
|
||||
}
|
||||
}
|
||||
|
||||
style.sps = {
|
||||
-- SPS states
|
||||
-- SPS states<br>
|
||||
---@see SPS_STATE
|
||||
states = {
|
||||
{
|
||||
color = cpair(colors.black, colors.yellow),
|
||||
text = "OFF-LINE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "NOT FORMED"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.orange),
|
||||
text = "RTU FAULT"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.white, colors.gray),
|
||||
text = "IDLE"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "ACTIVE"
|
||||
}
|
||||
{ color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
|
||||
{ color = cpair(colors.black, colors.orange), text = "RTU FAULT" },
|
||||
{ color = cpair(colors.white, colors.gray), text = "IDLE" },
|
||||
{ color = cpair(colors.black, colors.green), text = "ACTIVE" }
|
||||
}
|
||||
}
|
||||
|
||||
style.waste = {
|
||||
-- auto waste processing states
|
||||
states = {
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "PLUTONIUM"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.cyan),
|
||||
text = "POLONIUM"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.purple),
|
||||
text = "ANTI MATTER"
|
||||
}
|
||||
{ color = cpair(colors.black, colors.green), text = "PLUTONIUM" },
|
||||
{ color = cpair(colors.black, colors.cyan), text = "POLONIUM" },
|
||||
{ color = cpair(colors.black, colors.purple), text = "ANTI MATTER" }
|
||||
},
|
||||
states_abbrv = {
|
||||
{
|
||||
color = cpair(colors.black, colors.green),
|
||||
text = "Pu"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.cyan),
|
||||
text = "Po"
|
||||
},
|
||||
{
|
||||
color = cpair(colors.black, colors.purple),
|
||||
text = "AM"
|
||||
}
|
||||
{ color = cpair(colors.black, colors.green), text = "Pu" },
|
||||
{ color = cpair(colors.black, colors.cyan), text = "Po" },
|
||||
{ color = cpair(colors.black, colors.purple), text = "AM" }
|
||||
},
|
||||
-- process radio button options
|
||||
options = { "Plutonium", "Polonium", "Antimatter" },
|
||||
|
||||
@ -159,7 +159,7 @@ local function config_view(display)
|
||||
TextBox{parent=main_page,x=2,y=2,height=2,text="Welcome to the Reactor PLC configurator! Please select one of the following options."}
|
||||
|
||||
if tool_ctl.ask_config then
|
||||
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device had no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
||||
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device is not configured for this version of the reactor PLC. If you previously had a valid config, it's not lost. You may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
||||
y_start = y_start + 5
|
||||
end
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ local PCALL_SCRAM_MSG = "Scram requires the reactor to be active."
|
||||
local PCALL_START_MSG = "Reactor is already active."
|
||||
|
||||
---@type plc_config
|
||||
---@diagnostic disable-next-line: missing-fields
|
||||
local config = {}
|
||||
|
||||
plc.config = config
|
||||
|
||||
@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc")
|
||||
local renderer = require("reactor-plc.renderer")
|
||||
local threads = require("reactor-plc.threads")
|
||||
|
||||
local R_PLC_VERSION = "v1.8.13"
|
||||
local R_PLC_VERSION = "v1.8.14"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
|
||||
@ -90,6 +90,10 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style)
|
||||
load_settings(settings_cfg, true)
|
||||
load_settings(ini_cfg)
|
||||
peri_pane.set_value(5)
|
||||
|
||||
-- for return to list from saved screen
|
||||
tmp_cfg.Peripherals = tool_ctl.deep_copy_peri(ini_cfg.Peripherals)
|
||||
tool_ctl.gen_peri_summary()
|
||||
else
|
||||
peri_pane.set_value(6)
|
||||
end
|
||||
|
||||
@ -151,6 +151,10 @@ function redstone.create(tool_ctl, main_pane, cfg_sys, rs_cfg, style)
|
||||
load_settings(settings_cfg, true)
|
||||
load_settings(ini_cfg)
|
||||
rs_pane.set_value(4)
|
||||
|
||||
-- for return to list from saved screen
|
||||
tmp_cfg.Redstone = tool_ctl.deep_copy_rs(ini_cfg.Redstone)
|
||||
tool_ctl.gen_rs_summary()
|
||||
else
|
||||
rs_pane.set_value(5)
|
||||
end
|
||||
|
||||
@ -177,7 +177,7 @@ local function config_view(display)
|
||||
local y_start = 2
|
||||
|
||||
if tool_ctl.ask_config then
|
||||
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device had no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
||||
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device is not configured for this version of the RTU gateway. If you previously had a valid config, it's not lost. You may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
||||
y_start = y_start + 5
|
||||
else
|
||||
TextBox{parent=main_page,x=2,y=2,height=2,text="Welcome to the RTU gateway configurator! Please select one of the following options."}
|
||||
|
||||
@ -19,6 +19,7 @@ local MGMT_TYPE = comms.MGMT_TYPE
|
||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||
|
||||
---@type rtu_config
|
||||
---@diagnostic disable-next-line: missing-fields
|
||||
local config = {}
|
||||
|
||||
rtu.config = config
|
||||
|
||||
@ -31,7 +31,7 @@ local sna_rtu = require("rtu.dev.sna_rtu")
|
||||
local sps_rtu = require("rtu.dev.sps_rtu")
|
||||
local turbinev_rtu = require("rtu.dev.turbinev_rtu")
|
||||
|
||||
local RTU_VERSION = "v1.10.16"
|
||||
local RTU_VERSION = "v1.10.21"
|
||||
|
||||
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
|
||||
local RTU_HW_STATE = databus.RTU_HW_STATE
|
||||
@ -339,8 +339,7 @@ local function main()
|
||||
|
||||
if formed == ppm.ACCESS_FAULT then
|
||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed boiler multiblock"))
|
||||
return false
|
||||
log.warning(util.c("sys_config> failed to check if '", name, "' is a formed boiler multiblock"))
|
||||
end
|
||||
elseif type == "turbineValve" then
|
||||
-- turbine multiblock
|
||||
@ -354,8 +353,7 @@ local function main()
|
||||
|
||||
if formed == ppm.ACCESS_FAULT then
|
||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed turbine multiblock"))
|
||||
return false
|
||||
log.warning(util.c("sys_config> failed to check if '", name, "' is a formed turbine multiblock"))
|
||||
end
|
||||
elseif type == "dynamicValve" then
|
||||
-- dynamic tank multiblock
|
||||
@ -374,8 +372,7 @@ local function main()
|
||||
|
||||
if formed == ppm.ACCESS_FAULT then
|
||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed dynamic tank multiblock"))
|
||||
return false
|
||||
log.warning(util.c("sys_config> failed to check if '", name, "' is a formed dynamic tank multiblock"))
|
||||
end
|
||||
elseif type == "inductionPort" then
|
||||
-- induction matrix multiblock
|
||||
@ -388,8 +385,7 @@ local function main()
|
||||
|
||||
if formed == ppm.ACCESS_FAULT then
|
||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed induction matrix multiblock"))
|
||||
return false
|
||||
log.warning(util.c("sys_config> failed to check if '", name, "' is a formed induction matrix multiblock"))
|
||||
end
|
||||
elseif type == "spsPort" then
|
||||
-- SPS multiblock
|
||||
@ -402,8 +398,7 @@ local function main()
|
||||
|
||||
if formed == ppm.ACCESS_FAULT then
|
||||
println_ts(util.c("sys_config> failed to check if '", name, "' is formed"))
|
||||
log.fatal(util.c("sys_config> failed to check if '", name, "' is a formed SPS multiblock"))
|
||||
return false
|
||||
log.warning(util.c("sys_config> failed to check if '", name, "' is a formed SPS multiblock"))
|
||||
end
|
||||
elseif type == "solarNeutronActivator" then
|
||||
-- SNA
|
||||
@ -431,7 +426,9 @@ local function main()
|
||||
|
||||
if is_multiblock then
|
||||
if not formed then
|
||||
if formed == false then
|
||||
log.info(util.c("sys_config> device '", name, "' is not formed"))
|
||||
else formed = false end
|
||||
elseif faulted then
|
||||
-- sometimes there is a race condition on server boot where it reports formed, but
|
||||
-- the other functions are not yet defined (that's the theory at least). mark as unformed to attempt connection later
|
||||
@ -461,7 +458,7 @@ local function main()
|
||||
|
||||
table.insert(units, rtu_unit)
|
||||
|
||||
local for_message = "facility"
|
||||
local for_message = "the facility"
|
||||
if for_reactor > 0 then
|
||||
for_message = util.c("reactor ", for_reactor)
|
||||
end
|
||||
|
||||
@ -466,6 +466,9 @@ end
|
||||
---@param smem rtu_shared_memory
|
||||
---@param unit rtu_registry_entry
|
||||
function threads.thread__unit_comms(smem, unit)
|
||||
-- print a log message to the terminal as long as the UI isn't running
|
||||
local function println_ts(message) if not smem.rtu_state.fp_ok then util.println_ts(message) end end
|
||||
|
||||
---@class parallel_thread
|
||||
local public = {}
|
||||
|
||||
@ -483,7 +486,9 @@ function threads.thread__unit_comms(smem, unit)
|
||||
|
||||
local last_f_check = 0
|
||||
|
||||
local detail_name = util.c(types.rtu_type_to_string(unit.type), " (", unit.name, ") [", unit.index, "] for reactor ", unit.reactor)
|
||||
local detail_name = util.c(types.rtu_type_to_string(unit.type), " (", unit.name, ") ",
|
||||
util.trinary(unit.index == false, "", util.c("[", unit.index, "] ")), "for ",
|
||||
util.trinary(unit.reactor == 0, "the facility", util.c("reactor ", unit.reactor)))
|
||||
local short_name = util.c(types.rtu_type_to_string(unit.type), " (", unit.name, ")")
|
||||
|
||||
if packet_queue == nil then
|
||||
@ -538,6 +543,15 @@ function threads.thread__unit_comms(smem, unit)
|
||||
rtu_comms.send_remounted(unit.uid)
|
||||
elseif (is_formed == false) and unit.formed then
|
||||
log.warning(util.c(detail_name, " is no longer formed"))
|
||||
elseif (is_formed == nil) and (unit.hw_state ~= RTU_HW_STATE.OFFLINE) then
|
||||
log.error(util.c(detail_name, " failed to check if formed, attempting remount..."))
|
||||
|
||||
local type, dev = ppm.remount(unit.name)
|
||||
if type and dev then
|
||||
handle_unit_mount(smem, println_ts, unit.name, type, dev, unit)
|
||||
else
|
||||
log.error(util.c(detail_name, " failed to remount"))
|
||||
end
|
||||
end
|
||||
|
||||
unit.formed = is_formed
|
||||
|
||||
@ -17,8 +17,8 @@ local max_distance = nil
|
||||
local comms = {}
|
||||
|
||||
-- protocol/data versions (protocol/data independent changes tracked by util.lua version)
|
||||
comms.version = "3.0.2"
|
||||
comms.api_version = "0.0.7"
|
||||
comms.version = "3.0.3"
|
||||
comms.api_version = "0.0.8"
|
||||
|
||||
---@enum PROTOCOL
|
||||
local PROTOCOL = {
|
||||
|
||||
@ -8,6 +8,10 @@ local util = require("scada-common.util")
|
||||
local DBG_TAG, INF_TAG, WRN_TAG, ERR_TAG, FTL_TAG = "[DBG] ", "[INF] ", "[WRN] ", "[ERR] ", "[FTL] "
|
||||
local COLON, FUNC, ARROW = ":", "():", " > "
|
||||
|
||||
local MIN_SPACE = 512
|
||||
local OUT_OF_SPACE = "Out of space"
|
||||
local TIME_FMT = "%F %T "
|
||||
|
||||
---@class logger
|
||||
local log = {}
|
||||
|
||||
@ -34,13 +38,19 @@ local free_space = fs.getFreeSpace
|
||||
-- PRIVATE FUNCTIONS --
|
||||
-----------------------
|
||||
|
||||
-- check if the provided error indicates out of space or if insufficient space available
|
||||
---@param err_msg string|nil error message
|
||||
---@return boolean out_of_space
|
||||
local function check_out_of_space(err_msg)
|
||||
return (free_space(logger.path) < MIN_SPACE) or ((err_msg ~= nil) and (string.find(err_msg, OUT_OF_SPACE) ~= nil))
|
||||
end
|
||||
|
||||
-- private log write function
|
||||
---@param msg_bits any[]
|
||||
local function _log(msg_bits)
|
||||
if logger.not_ready then return end
|
||||
|
||||
local out_of_space = false
|
||||
local time_stamp = os.date("[%c] ")
|
||||
local time_stamp = os.date(TIME_FMT)
|
||||
local stamped = util.c(time_stamp, table.unpack(msg_bits))
|
||||
|
||||
-- attempt to write log
|
||||
@ -50,18 +60,7 @@ local function _log(msg_bits)
|
||||
end)
|
||||
|
||||
-- if we don't have space, we need to create a new log file
|
||||
|
||||
if (not status) and (result ~= nil) then
|
||||
out_of_space = string.find(result, "Out of space") ~= nil
|
||||
|
||||
if out_of_space then
|
||||
-- will delete log file
|
||||
else
|
||||
util.println("unknown error writing to logfile: " .. result)
|
||||
end
|
||||
end
|
||||
|
||||
if out_of_space or (free_space(logger.path) < 512) then
|
||||
if check_out_of_space() then
|
||||
-- delete the old log file before opening a new one
|
||||
logger.file.close()
|
||||
fs.delete(logger.path)
|
||||
@ -69,10 +68,12 @@ local function _log(msg_bits)
|
||||
-- re-init logger and pass dmesg_out so that it doesn't change
|
||||
log.init(logger.path, logger.mode, logger.debug, logger.dmesg_out)
|
||||
|
||||
-- leave a message
|
||||
logger.file.writeLine(time_stamp .. "recycled log file")
|
||||
-- log the message and recycle warning
|
||||
logger.file.writeLine(time_stamp .. WRN_TAG .. "recycled log file")
|
||||
logger.file.writeLine(stamped)
|
||||
logger.file.flush()
|
||||
elseif (not status) and (result ~= nil) then
|
||||
util.println("unexpected error writing to the log file: " .. result)
|
||||
end
|
||||
end
|
||||
|
||||
@ -86,15 +87,12 @@ end
|
||||
---@param include_debug boolean whether or not to include debug logs
|
||||
---@param dmesg_redirect? Redirect terminal/window to direct dmesg to
|
||||
function log.init(path, write_mode, include_debug, dmesg_redirect)
|
||||
local err_msg
|
||||
|
||||
logger.path = path
|
||||
logger.mode = write_mode
|
||||
logger.debug = include_debug
|
||||
|
||||
if logger.mode == MODE.APPEND then
|
||||
logger.file = fs.open(path, "a")
|
||||
else
|
||||
logger.file = fs.open(path, "w")
|
||||
end
|
||||
logger.file, err_msg = fs.open(path, util.trinary(logger.mode == MODE.APPEND, "a", "w"))
|
||||
|
||||
if dmesg_redirect then
|
||||
logger.dmesg_out = dmesg_redirect
|
||||
@ -102,6 +100,25 @@ function log.init(path, write_mode, include_debug, dmesg_redirect)
|
||||
logger.dmesg_out = term.current()
|
||||
end
|
||||
|
||||
-- check for space issues
|
||||
local out_of_space = check_out_of_space(err_msg)
|
||||
|
||||
-- try to handle problems
|
||||
if logger.file == nil or out_of_space then
|
||||
if out_of_space then
|
||||
if fs.exists(logger.path) then
|
||||
fs.delete(logger.path)
|
||||
|
||||
logger.file, err_msg = fs.open(path, util.trinary(logger.mode == MODE.APPEND, "a", "w"))
|
||||
|
||||
if logger.file then
|
||||
logger.file.writeLine(os.date(TIME_FMT) .. WRN_TAG .. "init recycled log file")
|
||||
logger.file.flush()
|
||||
else error("failed to setup the log file: " .. err_msg) end
|
||||
else error("failed to make space for the log file, please delete unused files") end
|
||||
else error("unexpected error setting up the log file: " .. err_msg) end
|
||||
end
|
||||
|
||||
logger.not_ready = false
|
||||
end
|
||||
|
||||
|
||||
@ -242,7 +242,7 @@ function ppm.mount_all()
|
||||
end
|
||||
end
|
||||
|
||||
-- mount a particular device
|
||||
-- mount a specified device
|
||||
---@nodiscard
|
||||
---@param iface string CC peripheral interface
|
||||
---@return string|nil type, table|nil device
|
||||
@ -266,6 +266,33 @@ function ppm.mount(iface)
|
||||
return pm_type, pm_dev
|
||||
end
|
||||
|
||||
-- unmount and remount a specified device
|
||||
---@nodiscard
|
||||
---@param iface string CC peripheral interface
|
||||
---@return string|nil type, table|nil device
|
||||
function ppm.remount(iface)
|
||||
local ifaces = peripheral.getNames()
|
||||
local pm_dev = nil
|
||||
local pm_type = nil
|
||||
|
||||
for i = 1, #ifaces do
|
||||
if iface == ifaces[i] then
|
||||
log.info(util.c("PPM: remount(", iface, ") -> is a ", pm_type))
|
||||
ppm.unmount(ppm_sys.mounts[iface].dev)
|
||||
|
||||
ppm_sys.mounts[iface] = peri_init(iface)
|
||||
|
||||
pm_type = ppm_sys.mounts[iface].type
|
||||
pm_dev = ppm_sys.mounts[iface].dev
|
||||
|
||||
log.info(util.c("PPM: remount(", iface, ") -> remounted a ", pm_type))
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return pm_type, pm_dev
|
||||
end
|
||||
|
||||
-- mount a virtual, placeholder device (specifically designed for RTU startup with missing devices)
|
||||
---@nodiscard
|
||||
---@return string type, table device
|
||||
|
||||
@ -74,6 +74,13 @@ function psil.create()
|
||||
end
|
||||
end
|
||||
|
||||
-- get the currently stored value for a key, or nil if not set
|
||||
---@param key string data key
|
||||
---@return any
|
||||
function public.get(key)
|
||||
if ic[key] ~= nil then return ic[key].value else return nil end
|
||||
end
|
||||
|
||||
-- clear the contents of the interconnect
|
||||
function public.purge() ic = {} end
|
||||
|
||||
|
||||
@ -253,6 +253,61 @@ types.ENERGY_SCALE_UNITS = {
|
||||
"RF"
|
||||
}
|
||||
|
||||
local GENERIC_STATE = {
|
||||
OFFLINE = 1,
|
||||
UNFORMED = 2,
|
||||
FAULT = 3,
|
||||
IDLE = 4,
|
||||
ACTIVE = 5
|
||||
}
|
||||
|
||||
---@enum REACTOR_STATE
|
||||
types.REACTOR_STATE = {
|
||||
OFFLINE = 1,
|
||||
UNFORMED = 2,
|
||||
FAULT = 3,
|
||||
DISABLED = 4,
|
||||
ACTIVE = 5,
|
||||
SCRAMMED = 6,
|
||||
FORCE_DISABLED = 7
|
||||
}
|
||||
|
||||
---@enum BOILER_STATE
|
||||
types.BOILER_STATE = GENERIC_STATE
|
||||
|
||||
---@enum TURBINE_STATE
|
||||
types.TURBINE_STATE = {
|
||||
OFFLINE = 1,
|
||||
UNFORMED = 2,
|
||||
FAULT = 3,
|
||||
IDLE = 4,
|
||||
ACTIVE = 5,
|
||||
TRIPPED = 6
|
||||
}
|
||||
|
||||
---@enum TANK_STATE
|
||||
types.TANK_STATE = {
|
||||
OFFLINE = 1,
|
||||
UNFORMED = 2,
|
||||
FAULT = 3,
|
||||
ONLINE = 4,
|
||||
LOW_FILL = 5,
|
||||
HIGH_FILL = 6
|
||||
}
|
||||
|
||||
---@enum IMATRIX_STATE
|
||||
types.IMATRIX_STATE = {
|
||||
OFFLINE = 1,
|
||||
UNFORMED = 2,
|
||||
FAULT = 3,
|
||||
ONLINE = 4,
|
||||
LOW_CHARGE = 5,
|
||||
HIGH_CHARGE = 6
|
||||
}
|
||||
|
||||
---@enum SPS_STATE
|
||||
types.SPS_STATE = GENERIC_STATE
|
||||
|
||||
---@enum PANEL_LINK_STATE
|
||||
types.PANEL_LINK_STATE = {
|
||||
LINKED = 1,
|
||||
@ -363,6 +418,12 @@ types.AUTO_GROUP_NAMES = {
|
||||
"Backup"
|
||||
}
|
||||
|
||||
---@enum COOLANT_TYPE
|
||||
types.COOLANT_TYPE = {
|
||||
WATER = 1,
|
||||
SODIUM = 2
|
||||
}
|
||||
|
||||
---@enum WASTE_MODE
|
||||
types.WASTE_MODE = {
|
||||
AUTO = 1,
|
||||
|
||||
@ -24,7 +24,7 @@ local t_pack = table.pack
|
||||
local util = {}
|
||||
|
||||
-- scada-common version
|
||||
util.version = "1.4.6"
|
||||
util.version = "1.4.10"
|
||||
|
||||
util.TICK_TIME_S = 0.05
|
||||
util.TICK_TIME_MS = 50
|
||||
|
||||
@ -18,12 +18,148 @@ local tri = util.trinary
|
||||
local cpair = core.cpair
|
||||
|
||||
local self = {
|
||||
tank_fluid_opts = {}, ---@type Radio2D[]
|
||||
|
||||
vis_draw = nil, ---@type function
|
||||
draw_fluid_ops = nil, ---@type function
|
||||
|
||||
vis_ftanks = {}, ---@type { line: Div, pipe_conn?: TextBox, pipe_chain?: TextBox, pipe_direct?: TextBox, label?: TextBox }[]
|
||||
vis_utanks = {} ---@type { line: Div, label: TextBox }[]
|
||||
}
|
||||
|
||||
local facility = {}
|
||||
|
||||
-- generate the tank list and tank connections tables
|
||||
---@param mode integer facility tank mode
|
||||
---@param defs table facility tank definitions
|
||||
---@return table tank_list, table tank_conns
|
||||
function facility.generate_tank_list_and_conns(mode, defs)
|
||||
local tank_mode = mode
|
||||
local tank_defs = defs
|
||||
local tank_list = { table.unpack(tank_defs) }
|
||||
local tank_conns = { table.unpack(tank_defs) }
|
||||
|
||||
local function calc_fdef(start_idx, end_idx)
|
||||
local first = 4
|
||||
for i = start_idx, end_idx do
|
||||
if tank_defs[i] == 2 then
|
||||
if i < first then first = i end
|
||||
end
|
||||
end
|
||||
return first
|
||||
end
|
||||
|
||||
-- set units using their own tanks as connected to their respective unit tank
|
||||
for i = 1, #tank_defs do
|
||||
if tank_defs[i] == 1 then tank_conns[i] = i end
|
||||
end
|
||||
|
||||
if tank_mode == 1 then
|
||||
-- (1) 1 total facility tank (A A A A)
|
||||
local first_fdef = calc_fdef(1, #tank_defs)
|
||||
for i = 1, #tank_defs do
|
||||
if (i >= first_fdef) and (tank_defs[i] == 2) then
|
||||
tank_conns[i] = first_fdef
|
||||
|
||||
if i > first_fdef then tank_list[i] = 0 end
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 2 then
|
||||
-- (2) 2 total facility tanks (A A A B)
|
||||
local first_fdef = calc_fdef(1, math.min(3, #tank_defs))
|
||||
for i = 1, #tank_defs do
|
||||
if (i >= first_fdef) and (tank_defs[i] == 2) then
|
||||
if i == 4 then
|
||||
tank_conns[i] = 4
|
||||
else
|
||||
tank_conns[i] = first_fdef
|
||||
|
||||
if i > first_fdef then tank_list[i] = 0 end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 3 then
|
||||
-- (3) 2 total facility tanks (A A B B)
|
||||
for _, a in pairs({ 1, 3 }) do
|
||||
local b = a + 1
|
||||
|
||||
if tank_defs[a] == 2 then
|
||||
tank_conns[a] = a
|
||||
elseif tank_defs[b] == 2 then
|
||||
tank_conns[b] = b
|
||||
end
|
||||
|
||||
if (tank_defs[a] == 2) and (tank_defs[b] == 2) then
|
||||
tank_list[b] = 0
|
||||
tank_conns[b] = a
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 4 then
|
||||
-- (4) 2 total facility tanks (A B B B)
|
||||
local first_fdef = calc_fdef(2, #tank_defs)
|
||||
for i = 1, #tank_defs do
|
||||
if tank_defs[i] == 2 then
|
||||
if i == 1 then
|
||||
tank_conns[i] = 1
|
||||
elseif i >= first_fdef then
|
||||
tank_conns[i] = first_fdef
|
||||
|
||||
if i > first_fdef then tank_list[i] = 0 end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 5 then
|
||||
-- (5) 3 total facility tanks (A A B C)
|
||||
local first_fdef = calc_fdef(1, math.min(2, #tank_defs))
|
||||
for i = 1, #tank_defs do
|
||||
if (i >= first_fdef) and (tank_defs[i] == 2) then
|
||||
if i == 3 or i == 4 then
|
||||
tank_conns[i] = i
|
||||
elseif i >= first_fdef then
|
||||
tank_conns[i] = first_fdef
|
||||
|
||||
if i > first_fdef then tank_list[i] = 0 end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 6 then
|
||||
-- (6) 3 total facility tanks (A B B C)
|
||||
local first_fdef = calc_fdef(2, math.min(3, #tank_defs))
|
||||
for i = 1, #tank_defs do
|
||||
if tank_defs[i] == 2 then
|
||||
if i == 1 or i == 4 then
|
||||
tank_conns[i] = i
|
||||
elseif i >= first_fdef then
|
||||
tank_conns[i] = first_fdef
|
||||
|
||||
if i > first_fdef then tank_list[i] = 0 end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 7 then
|
||||
-- (7) 3 total facility tanks (A B C C)
|
||||
local first_fdef = calc_fdef(3, #tank_defs)
|
||||
for i = 1, #tank_defs do
|
||||
if tank_defs[i] == 2 then
|
||||
if i == 1 or i == 2 then
|
||||
tank_conns[i] = i
|
||||
elseif i >= first_fdef then
|
||||
tank_conns[i] = first_fdef
|
||||
|
||||
if i > first_fdef then tank_list[i] = 0 end
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 8 then
|
||||
-- (8) 4 total facility tanks (A B C D)
|
||||
for i = 1, #tank_defs do
|
||||
if tank_defs[i] == 2 then tank_conns[i] = i end
|
||||
end
|
||||
end
|
||||
|
||||
return tank_list, tank_conns
|
||||
end
|
||||
|
||||
-- create the facility configuration view
|
||||
---@param tool_ctl _svr_cfg_tool_ctl
|
||||
---@param main_pane MultiPane
|
||||
@ -48,14 +184,18 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
||||
local fac_c_5 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
||||
local fac_c_6 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
||||
local fac_c_7 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
||||
local fac_c_8 = Div{parent=fac_cfg,x=2,y=4,width=49}
|
||||
|
||||
local fac_pane = MultiPane{parent=fac_cfg,x=1,y=4,panes={fac_c_1,fac_c_2,fac_c_3,fac_c_4,fac_c_5,fac_c_6,fac_c_7}}
|
||||
local fac_pane = MultiPane{parent=fac_cfg,x=1,y=4,panes={fac_c_1,fac_c_2,fac_c_3,fac_c_4,fac_c_5,fac_c_6,fac_c_7, fac_c_8}}
|
||||
|
||||
TextBox{parent=fac_cfg,x=1,y=2,text=" Facility Configuration",fg_bg=cpair(colors.black,colors.yellow)}
|
||||
|
||||
--#region Unit Count
|
||||
|
||||
TextBox{parent=fac_c_1,x=1,y=1,height=3,text="Please enter the number of reactors you have, also referred to as reactor units or 'units' for short. A maximum of 4 is currently supported."}
|
||||
tool_ctl.num_units = NumberField{parent=fac_c_1,x=1,y=5,width=5,max_chars=2,default=ini_cfg.UnitCount,min=1,max=4,fg_bg=bw_fg_bg}
|
||||
TextBox{parent=fac_c_1,x=7,y=5,text="reactors"}
|
||||
TextBox{parent=fac_c_1,x=1,y=7,height=3,text="If you already configured your coordinator, make sure you update the coordinator's configured unit count.",fg_bg=cpair(colors.yellow,colors._INHERIT)}
|
||||
|
||||
local nu_error = TextBox{parent=fac_c_1,x=8,y=14,width=35,text="Please set the number of reactors.",fg_bg=cpair(colors.red,colors.lightGray),hidden=true}
|
||||
|
||||
@ -77,6 +217,9 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
||||
PushButton{parent=fac_c_1,x=1,y=14,text="\x1b Back",callback=function()main_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=fac_c_1,x=44,y=14,text="Next \x1a",callback=submit_num_units,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
--#region Cooling Configuration
|
||||
|
||||
TextBox{parent=fac_c_2,x=1,y=1,height=4,text="Please provide the reactor cooling configuration below. This includes the number of turbines, boilers, and if that reactor has a connection to a dynamic tank for emergency coolant."}
|
||||
TextBox{parent=fac_c_2,x=1,y=6,text="UNIT TURBINES BOILERS HAS TANK CONNECTION?",fg_bg=g_lg_fg_bg}
|
||||
|
||||
@ -149,6 +292,9 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
||||
PushButton{parent=fac_c_2,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(1)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=fac_c_2,x=44,y=14,text="Next \x1a",callback=submit_cooling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
--#region Facility Tanks Option
|
||||
|
||||
TextBox{parent=fac_c_3,x=1,y=1,height=6,text="You have set one or more of your units to use dynamic tanks for emergency coolant. You have two paths for configuration. The first is to assign dynamic tanks to reactor units; one tank per reactor, only connected to that reactor. RTU configurations must also assign it as such."}
|
||||
TextBox{parent=fac_c_3,x=1,y=8,height=3,text="Alternatively, you can configure them as facility tanks to connect to multiple reactor units. These can intermingle with unit-specific tanks."}
|
||||
|
||||
@ -161,6 +307,16 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
||||
else
|
||||
tmp_cfg.FacilityTankMode = 0
|
||||
tmp_cfg.FacilityTankDefs = {}
|
||||
|
||||
-- on facility tank mode 0, setup tank defs to match unit tank option
|
||||
for i = 1, tmp_cfg.UnitCount do
|
||||
tmp_cfg.FacilityTankDefs[i] = tri(tmp_cfg.CoolingConfig[i].TankConnection, 1, 0)
|
||||
end
|
||||
|
||||
tmp_cfg.FacilityTankList, tmp_cfg.FacilityTankConns = facility.generate_tank_list_and_conns(tmp_cfg.FacilityTankMode, tmp_cfg.FacilityTankDefs)
|
||||
|
||||
self.draw_fluid_ops()
|
||||
|
||||
fac_pane.set_value(7)
|
||||
end
|
||||
end
|
||||
@ -168,6 +324,9 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
||||
PushButton{parent=fac_c_3,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(2)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=fac_c_3,x=44,y=14,text="Next \x1a",callback=submit_en_fac_tank,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
--#region Facility Tank Connections
|
||||
|
||||
TextBox{parent=fac_c_4,x=1,y=1,height=4,text="Please set unit connections to dynamic tanks, selecting at least one facility tank. The layout for facility tanks will be configured next."}
|
||||
|
||||
for i = 1, 4 do
|
||||
@ -220,7 +379,7 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
||||
self.vis_utanks[i].line.hide(true)
|
||||
end
|
||||
|
||||
tool_ctl.vis_draw(tmp_cfg.FacilityTankMode)
|
||||
self.vis_draw(tmp_cfg.FacilityTankMode)
|
||||
|
||||
if any_fac then
|
||||
tank_err.hide(true)
|
||||
@ -231,6 +390,9 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
||||
PushButton{parent=fac_c_4,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(3)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=fac_c_4,x=44,y=14,text="Next \x1a",callback=submit_tank_defs,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
--#region Facility Tank Mode
|
||||
|
||||
TextBox{parent=fac_c_5,x=1,y=1,text="Please select your dynamic tank layout."}
|
||||
TextBox{parent=fac_c_5,x=12,y=3,text="Facility Tanks Unit Tanks",fg_bg=g_lg_fg_bg}
|
||||
|
||||
@ -269,7 +431,7 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
||||
|
||||
-- draw the pipe visualization
|
||||
---@param mode integer pipe mode
|
||||
function tool_ctl.vis_draw(mode)
|
||||
function self.vis_draw(mode)
|
||||
-- is a facility tank connected to this unit
|
||||
---@param i integer unit 1 - 4
|
||||
---@return boolean connected
|
||||
@ -391,7 +553,7 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
||||
|
||||
local function change_mode(mode)
|
||||
tmp_cfg.FacilityTankMode = mode
|
||||
tool_ctl.vis_draw(mode)
|
||||
self.vis_draw(mode)
|
||||
end
|
||||
|
||||
local tank_modes = { "Mode 1", "Mode 2", "Mode 3", "Mode 4", "Mode 5", "Mode 6", "Mode 7", "Mode 8" }
|
||||
@ -399,24 +561,127 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
||||
|
||||
--#endregion
|
||||
|
||||
local function next_from_tank_mode()
|
||||
-- determine tank list and connections
|
||||
tmp_cfg.FacilityTankList, tmp_cfg.FacilityTankConns = facility.generate_tank_list_and_conns(tmp_cfg.FacilityTankMode, tmp_cfg.FacilityTankDefs)
|
||||
|
||||
self.draw_fluid_ops()
|
||||
|
||||
fac_pane.set_value(7)
|
||||
end
|
||||
|
||||
PushButton{parent=fac_c_5,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(4)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=fac_c_5,x=44,y=14,text="Next \x1a",callback=function()fac_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=fac_c_5,x=44,y=14,text="Next \x1a",callback=next_from_tank_mode,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
PushButton{parent=fac_c_5,x=8,y=14,min_width=7,text="About",callback=function()fac_pane.set_value(6)end,fg_bg=cpair(colors.black,colors.lightBlue),active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
--#region Facility Tank Mode About
|
||||
|
||||
TextBox{parent=fac_c_6,height=3,text="This visualization tool shows the pipe connections required for a particular dynamic tank configuration you have selected."}
|
||||
TextBox{parent=fac_c_6,y=5,height=4,text="Examples: A U2 tank should be configured on an RTU as the dynamic tank for unit #2. An F3 tank should be configured on an RTU as the #3 dynamic tank for the facility."}
|
||||
TextBox{parent=fac_c_6,y=10,height=3,text="Some modes may look the same if you are not using 4 total reactor units. The wiki has details. Modes that look the same will function the same.",fg_bg=g_lg_fg_bg}
|
||||
|
||||
PushButton{parent=fac_c_6,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(5)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
TextBox{parent=fac_c_7,height=6,text="Charge control provides automatic control to maintain an induction matrix charge level. In order to have smoother control, reactors that were activated will be held on at 0.01 mB/t for a short period before allowing them to turn off. This minimizes overshooting the charge target."}
|
||||
TextBox{parent=fac_c_7,y=8,height=3,text="You can extend this to a full minute to minimize reactors flickering on/off, but there may be more overshoot of the target."}
|
||||
--#endregion
|
||||
--#region Dynamic Tank Fluid Types
|
||||
|
||||
local ext_idling = Checkbox{parent=fac_c_7,x=1,y=12,label="Enable Extended Idling",default=ini_cfg.ExtChargeIdling,box_fg_bg=cpair(colors.yellow,colors.black)}
|
||||
TextBox{parent=fac_c_7,height=3,text="Specify each tank's coolant type, for display use only. Water is the only option if one or more of the connected units is water cooled."}
|
||||
|
||||
local tank_fluid_list = Div{parent=fac_c_7,x=1,y=5,height=8}
|
||||
|
||||
function self.draw_fluid_ops()
|
||||
tank_fluid_list.remove_all()
|
||||
|
||||
local tank_list = tmp_cfg.FacilityTankList
|
||||
local tank_conns = tmp_cfg.FacilityTankConns
|
||||
|
||||
local next_f = 1
|
||||
|
||||
for i = 1, #tank_list do
|
||||
local type = tmp_cfg.TankFluidTypes[i]
|
||||
|
||||
if type == 0 then type = 1 end
|
||||
|
||||
self.tank_fluid_opts[i] = nil
|
||||
|
||||
if tank_list[i] == 1 then
|
||||
local row = Div{parent=tank_fluid_list,height=2}
|
||||
|
||||
TextBox{parent=row,width=11,text="Unit Tank "..i}
|
||||
TextBox{parent=row,text="Connected to: Unit "..i,fg_bg=cpair(colors.gray,colors.lightGray)}
|
||||
|
||||
local tank_fluid = Radio2D{parent=row,x=34,y=1,rows=1,columns=2,default=type,options={"Water","Sodium"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.yellow,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
||||
|
||||
if tmp_cfg.CoolingConfig[i].BoilerCount == 0 then
|
||||
tank_fluid.set_value(1)
|
||||
tank_fluid.disable()
|
||||
end
|
||||
|
||||
self.tank_fluid_opts[i] = tank_fluid
|
||||
elseif tank_list[i] == 2 then
|
||||
local row = Div{parent=tank_fluid_list,height=2}
|
||||
|
||||
TextBox{parent=row,width=15,text="Facility Tank "..next_f}
|
||||
|
||||
local conns = ""
|
||||
local any_bwr = false
|
||||
|
||||
for u = 1, #tank_conns do
|
||||
if tank_conns[u] == i then
|
||||
conns = conns .. tri(conns == "", "", ", ") .. "Unit " .. u
|
||||
any_bwr = any_bwr or (tmp_cfg.CoolingConfig[u].BoilerCount == 0)
|
||||
end
|
||||
end
|
||||
|
||||
TextBox{parent=row,text="Connected to: "..conns,fg_bg=cpair(colors.gray,colors.lightGray)}
|
||||
|
||||
local tank_fluid = Radio2D{parent=row,x=34,y=1,rows=1,columns=2,default=type,options={"Water","Sodium"},radio_colors=cpair(colors.lightGray,colors.black),select_color=colors.yellow,disable_color=colors.gray,disable_fg_bg=g_lg_fg_bg}
|
||||
|
||||
if any_bwr then
|
||||
tank_fluid.set_value(1)
|
||||
tank_fluid.disable()
|
||||
end
|
||||
|
||||
self.tank_fluid_opts[i] = tank_fluid
|
||||
|
||||
next_f = next_f + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function back_from_fluids()
|
||||
fac_pane.set_value(tri(tmp_cfg.FacilityTankMode == 0, 3, 5))
|
||||
end
|
||||
|
||||
local function submit_tank_fluids()
|
||||
tmp_cfg.TankFluidTypes = {}
|
||||
|
||||
for i = 1, #tmp_cfg.FacilityTankList do
|
||||
if self.tank_fluid_opts[i] ~= nil then
|
||||
tmp_cfg.TankFluidTypes[i] = self.tank_fluid_opts[i].get_value()
|
||||
else
|
||||
tmp_cfg.TankFluidTypes[i] = 0
|
||||
end
|
||||
end
|
||||
|
||||
fac_pane.set_value(8)
|
||||
end
|
||||
|
||||
PushButton{parent=fac_c_7,x=1,y=14,text="\x1b Back",callback=back_from_fluids,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=fac_c_7,x=44,y=14,text="Next \x1a",callback=submit_tank_fluids,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
--#region Extended Idling
|
||||
|
||||
TextBox{parent=fac_c_8,height=6,text="Charge control provides automatic control to maintain an induction matrix charge level. In order to have smoother control, reactors that were activated will be held on at 0.01 mB/t for a short period before allowing them to turn off. This minimizes overshooting the charge target."}
|
||||
TextBox{parent=fac_c_8,y=8,height=3,text="You can extend this to a full minute to minimize reactors flickering on/off, but there may be more overshoot of the target."}
|
||||
|
||||
local ext_idling = Checkbox{parent=fac_c_8,x=1,y=12,label="Enable Extended Idling",default=ini_cfg.ExtChargeIdling,box_fg_bg=cpair(colors.yellow,colors.black)}
|
||||
|
||||
local function back_from_idling()
|
||||
fac_pane.set_value(tri(tmp_cfg.FacilityTankMode == 0, 3, 5))
|
||||
fac_pane.set_value(tri(tmp_cfg.FacilityTankMode == 0, 3, 7))
|
||||
end
|
||||
|
||||
local function submit_idling()
|
||||
@ -424,8 +689,10 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style)
|
||||
main_pane.set_value(3)
|
||||
end
|
||||
|
||||
PushButton{parent=fac_c_7,x=1,y=14,text="\x1b Back",callback=back_from_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=fac_c_7,x=44,y=14,text="Next \x1a",callback=submit_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=fac_c_8,x=1,y=14,text="\x1b Back",callback=back_from_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
PushButton{parent=fac_c_8,x=44,y=14,text="Next \x1a",callback=submit_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg}
|
||||
|
||||
--#endregion
|
||||
|
||||
--#endregion
|
||||
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
local log = require("scada-common.log")
|
||||
local types = require("scada-common.types")
|
||||
local util = require("scada-common.util")
|
||||
|
||||
local facility = require("supervisor.config.facility")
|
||||
|
||||
local core = require("graphics.core")
|
||||
local themes = require("graphics.themes")
|
||||
|
||||
@ -508,7 +511,14 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
||||
else
|
||||
tmp_cfg.FacilityTankMode = 0
|
||||
tmp_cfg.FacilityTankDefs = {}
|
||||
|
||||
-- on facility tank mode 0, setup tank defs to match unit tank option
|
||||
for i = 1, tmp_cfg.UnitCount do
|
||||
tmp_cfg.FacilityTankDefs[i] = tri(tmp_cfg.CoolingConfig[i].TankConnection, 1, 0)
|
||||
end
|
||||
end
|
||||
|
||||
tmp_cfg.FacilityTankList, tmp_cfg.FacilityTankConns = facility.generate_tank_list_and_conns(tmp_cfg.FacilityTankMode, tmp_cfg.FacilityTankDefs)
|
||||
|
||||
tmp_cfg.SVR_Channel = config.SVR_CHANNEL
|
||||
tmp_cfg.PLC_Channel = config.PLC_CHANNEL
|
||||
@ -557,6 +567,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
||||
local val_max_w = (inner_width - label_w) + 1
|
||||
local raw = cfg[f[1]]
|
||||
local val = util.strval(raw)
|
||||
local skip = false
|
||||
|
||||
if f[1] == "AuthKey" then val = string.rep("*", string.len(val))
|
||||
elseif f[1] == "LogMode" then val = tri(raw == log.MODE.APPEND, "append", "replace")
|
||||
@ -579,22 +590,66 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
||||
if val == "" then val = "no facility tanks" end
|
||||
elseif f[1] == "FacilityTankMode" and raw == 0 then val = "0 (n/a, unit mode)"
|
||||
elseif f[1] == "FacilityTankDefs" and type(cfg.FacilityTankDefs) == "table" then
|
||||
local tank_name_list = { table.unpack(cfg.FacilityTankList) } ---@type (string|integer)[]
|
||||
local next_f = 1
|
||||
|
||||
val = ""
|
||||
|
||||
for idx = 1, #tank_name_list do
|
||||
if tank_name_list[idx] == 1 then
|
||||
tank_name_list[idx] = "U" .. idx
|
||||
elseif tank_name_list[idx] == 2 then
|
||||
tank_name_list[idx] = "F" .. next_f
|
||||
next_f = next_f + 1
|
||||
end
|
||||
end
|
||||
|
||||
for idx = 1, #cfg.FacilityTankDefs do
|
||||
local t_mode = "not connected to a tank"
|
||||
if cfg.FacilityTankDefs[idx] == 1 then
|
||||
t_mode = "connected to its unit tank"
|
||||
t_mode = "connected to its unit tank (" .. tank_name_list[cfg.FacilityTankConns[idx]] .. ")"
|
||||
elseif cfg.FacilityTankDefs[idx] == 2 then
|
||||
t_mode = "connected to a facility tank"
|
||||
t_mode = "connected to facility tank " .. tank_name_list[cfg.FacilityTankConns[idx]]
|
||||
end
|
||||
|
||||
val = val .. tri(idx == 1, "", "\n") .. util.sprintf(" \x07 unit %d - %s", idx, t_mode)
|
||||
end
|
||||
|
||||
if val == "" then val = "no facility tanks" end
|
||||
elseif f[1] == "FacilityTankList" or f[1] == "FacilityTankConns" then
|
||||
-- hide these since this info is available in the FacilityTankDefs list (connections) and TankFluidTypes list (list of tanks)
|
||||
skip = true
|
||||
elseif f[1] == "TankFluidTypes" and type(cfg.TankFluidTypes) == "table" and type(cfg.FacilityTankList) == "table" then
|
||||
local tank_list = cfg.FacilityTankList
|
||||
local next_f = 1
|
||||
|
||||
val = ""
|
||||
|
||||
for idx = 1, #tank_list do
|
||||
local prefix = "?"
|
||||
local fluid = "water"
|
||||
local type = cfg.TankFluidTypes[idx]
|
||||
|
||||
if tank_list[idx] > 0 then
|
||||
if tank_list[idx] == 1 then
|
||||
prefix = "U" .. idx
|
||||
elseif tank_list[idx] == 2 then
|
||||
prefix = "F" .. next_f
|
||||
next_f = next_f + 1
|
||||
end
|
||||
|
||||
if type == types.COOLANT_TYPE.SODIUM then
|
||||
fluid = "sodium"
|
||||
end
|
||||
|
||||
val = val .. tri(val == "", "", "\n") .. util.sprintf(" \x07 tank %s - %s", prefix, fluid)
|
||||
end
|
||||
end
|
||||
|
||||
if val == "" then val = "no emergency coolant tanks" end
|
||||
end
|
||||
|
||||
if not skip then
|
||||
if val == "nil" then val = "<not set>" end
|
||||
|
||||
local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white))
|
||||
@ -618,6 +673,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit
|
||||
if f[1] == "AuthKey" then self.auth_key_textbox = textbox end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--#endregion
|
||||
end
|
||||
|
||||
@ -30,7 +30,8 @@ local CENTER = core.ALIGN.CENTER
|
||||
-- changes to the config data/format to let the user know
|
||||
local changes = {
|
||||
{ "v1.2.12", { "Added front panel UI theme", "Added color accessibility modes" } },
|
||||
{ "v1.3.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } }
|
||||
{ "v1.3.2", { "Added standard with black off state color mode", "Added blue indicator color modes" } },
|
||||
{ "v1.6.0", { "Added sodium emergency coolant option" } }
|
||||
}
|
||||
|
||||
---@class svr_configurator
|
||||
@ -78,8 +79,11 @@ local tool_ctl = {
|
||||
local tmp_cfg = {
|
||||
UnitCount = 1,
|
||||
CoolingConfig = {}, ---@type { TurbineCount: integer, BoilerCount: integer, TankConnection: boolean }[]
|
||||
FacilityTankMode = 0,
|
||||
FacilityTankDefs = {}, ---@type integer[]
|
||||
FacilityTankMode = 0, -- dynamic tank emergency coolant layout
|
||||
FacilityTankDefs = {}, ---@type integer[] each unit's tank connection target (0 = disconnected, 1 = unit, 2 = facility)
|
||||
FacilityTankList = {}, ---@type integer[] list of tanks by slot (0 = none or covered by an above tank, 1 = unit tank, 2 = facility tank)
|
||||
FacilityTankConns = {}, ---@type integer[] map of unit tank connections (indicies are units, values are tank indicies in the tank list)
|
||||
TankFluidTypes = {}, ---@type integer[] which type of fluid each tank in the tank list should be containing
|
||||
ExtChargeIdling = false,
|
||||
SVR_Channel = nil, ---@type integer
|
||||
PLC_Channel = nil, ---@type integer
|
||||
@ -110,6 +114,9 @@ local fields = {
|
||||
{ "CoolingConfig", "Cooling Configuration", {} },
|
||||
{ "FacilityTankMode", "Facility Tank Mode", 0 },
|
||||
{ "FacilityTankDefs", "Facility Tank Definitions", {} },
|
||||
{ "FacilityTankList", "Facility Tank List", {} }, -- hidden
|
||||
{ "FacilityTankConns", "Facility Tank Connections", {} }, -- hidden
|
||||
{ "TankFluidTypes", "Tank Fluid Types", {} },
|
||||
{ "ExtChargeIdling", "Extended Charge Idling", false },
|
||||
{ "SVR_Channel", "SVR Channel", 16240 },
|
||||
{ "PLC_Channel", "PLC Channel", 16241 },
|
||||
@ -176,7 +183,7 @@ local function config_view(display)
|
||||
TextBox{parent=main_page,x=2,y=2,height=2,text="Welcome to the Supervisor configurator! Please select one of the following options."}
|
||||
|
||||
if tool_ctl.ask_config then
|
||||
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device had no valid config so the configurator has been automatically started. If you previously had a valid config, you may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
||||
TextBox{parent=main_page,x=2,y=y_start,height=4,width=49,text="Notice: This device is not configured for this version of the supervisor. If you previously had a valid config, it's not lost. You may want to check the Change Log to see what changed.",fg_bg=cpair(colors.red,colors.lightGray)}
|
||||
y_start = y_start + 5
|
||||
end
|
||||
|
||||
@ -275,6 +282,10 @@ function configurator.configure(ask_config)
|
||||
load_settings(settings_cfg, true)
|
||||
tool_ctl.has_config = load_settings(ini_cfg)
|
||||
|
||||
-- these need to be initialized as they are used before being set
|
||||
tmp_cfg.FacilityTankMode = ini_cfg.FacilityTankMode
|
||||
tmp_cfg.TankFluidTypes = { table.unpack(ini_cfg.TankFluidTypes) }
|
||||
|
||||
reset_term()
|
||||
|
||||
-- set overridden colors
|
||||
|
||||
@ -51,7 +51,9 @@ function facility.new(config)
|
||||
r_cool = config.CoolingConfig,
|
||||
fac_tank_mode = config.FacilityTankMode,
|
||||
fac_tank_defs = config.FacilityTankDefs,
|
||||
fac_tank_list = {} ---@type integer[]
|
||||
fac_tank_list = config.FacilityTankList,
|
||||
fac_tank_conns = config.FacilityTankConns,
|
||||
tank_fluid_types = config.TankFluidTypes
|
||||
},
|
||||
-- rtus
|
||||
rtu_gw_conn_count = 0,
|
||||
@ -147,99 +149,6 @@ function facility.new(config)
|
||||
table.insert(self.test_tone_states, false)
|
||||
end
|
||||
|
||||
--#region decode tank configuration
|
||||
|
||||
local cool_conf = self.cooling_conf
|
||||
|
||||
-- determine tank information
|
||||
if cool_conf.fac_tank_mode == 0 then
|
||||
cool_conf.fac_tank_defs = {}
|
||||
|
||||
-- on facility tank mode 0, setup tank defs to match unit tank option
|
||||
for i = 1, config.UnitCount do
|
||||
cool_conf.fac_tank_defs[i] = util.trinary(cool_conf.r_cool[i].TankConnection, 1, 0)
|
||||
end
|
||||
|
||||
cool_conf.fac_tank_list = { table.unpack(cool_conf.fac_tank_defs) }
|
||||
else
|
||||
-- decode the layout of tanks from the connections definitions
|
||||
local tank_mode = cool_conf.fac_tank_mode
|
||||
local tank_defs = cool_conf.fac_tank_defs
|
||||
local tank_list = { table.unpack(tank_defs) }
|
||||
|
||||
local function calc_fdef(start_idx, end_idx)
|
||||
local first = 4
|
||||
for i = start_idx, end_idx do
|
||||
if tank_defs[i] == 2 then
|
||||
if i < first then first = i end
|
||||
end
|
||||
end
|
||||
return first
|
||||
end
|
||||
|
||||
if tank_mode == 1 then
|
||||
-- (1) 1 total facility tank (A A A A)
|
||||
local first_fdef = calc_fdef(1, #tank_defs)
|
||||
for i = 1, #tank_defs do
|
||||
if i > first_fdef and tank_defs[i] == 2 then
|
||||
tank_list[i] = 0
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 2 then
|
||||
-- (2) 2 total facility tanks (A A A B)
|
||||
local first_fdef = calc_fdef(1, math.min(3, #tank_defs))
|
||||
for i = 1, #tank_defs do
|
||||
if (i ~= 4) and (i > first_fdef) and (tank_defs[i] == 2) then
|
||||
tank_list[i] = 0
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 3 then
|
||||
-- (3) 2 total facility tanks (A A B B)
|
||||
for _, a in pairs({ 1, 3 }) do
|
||||
local b = a + 1
|
||||
if (tank_defs[a] == 2) and (tank_defs[b] == 2) then
|
||||
tank_list[b] = 0
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 4 then
|
||||
-- (4) 2 total facility tanks (A B B B)
|
||||
local first_fdef = calc_fdef(2, #tank_defs)
|
||||
for i = 1, #tank_defs do
|
||||
if (i ~= 1) and (i > first_fdef) and (tank_defs[i] == 2) then
|
||||
tank_list[i] = 0
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 5 then
|
||||
-- (5) 3 total facility tanks (A A B C)
|
||||
local first_fdef = calc_fdef(1, math.min(2, #tank_defs))
|
||||
for i = 1, #tank_defs do
|
||||
if (not (i == 3 or i == 4)) and (i > first_fdef) and (tank_defs[i] == 2) then
|
||||
tank_list[i] = 0
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 6 then
|
||||
-- (6) 3 total facility tanks (A B B C)
|
||||
local first_fdef = calc_fdef(2, math.min(3, #tank_defs))
|
||||
for i = 1, #tank_defs do
|
||||
if (not (i == 1 or i == 4)) and (i > first_fdef) and (tank_defs[i] == 2) then
|
||||
tank_list[i] = 0
|
||||
end
|
||||
end
|
||||
elseif tank_mode == 7 then
|
||||
-- (7) 3 total facility tanks (A B C C)
|
||||
local first_fdef = calc_fdef(3, #tank_defs)
|
||||
for i = 1, #tank_defs do
|
||||
if (not (i == 1 or i == 2)) and (i > first_fdef) and (tank_defs[i] == 2) then
|
||||
tank_list[i] = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
cool_conf.fac_tank_list = tank_list
|
||||
end
|
||||
|
||||
--#endregion
|
||||
|
||||
-- PUBLIC FUNCTIONS --
|
||||
|
||||
---@class facility
|
||||
|
||||
@ -255,6 +255,7 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim
|
||||
elseif cmd == FAC_COMMAND.START then
|
||||
if pkt.length == 6 then
|
||||
---@type sys_auto_config
|
||||
---@diagnostic disable-next-line: missing-fields
|
||||
local config = {
|
||||
mode = pkt.data[2],
|
||||
burn_target = pkt.data[3],
|
||||
|
||||
@ -46,12 +46,14 @@ local self = {
|
||||
config = nil, ---@type svr_config
|
||||
facility = nil, ---@type facility|nil
|
||||
-- lists of connected sessions
|
||||
---@diagnostic disable: missing-fields
|
||||
sessions = {
|
||||
rtu = {}, ---@type rtu_session_struct
|
||||
plc = {}, ---@type plc_session_struct
|
||||
crd = {}, ---@type crd_session_struct
|
||||
pdg = {} ---@type pdg_session_struct
|
||||
},
|
||||
---@diagnostic enable: missing-fields
|
||||
-- next session IDs
|
||||
next_ids = { rtu = 0, plc = 0, crd = 0, pdg = 0 },
|
||||
-- rtu device tracking and invalid assignment detection
|
||||
|
||||
@ -22,7 +22,7 @@ local supervisor = require("supervisor.supervisor")
|
||||
|
||||
local svsessions = require("supervisor.session.svsessions")
|
||||
|
||||
local SUPERVISOR_VERSION = "v1.5.17"
|
||||
local SUPERVISOR_VERSION = "v1.6.1"
|
||||
|
||||
local println = util.println
|
||||
local println_ts = util.println_ts
|
||||
|
||||
@ -14,6 +14,7 @@ local ESTABLISH_ACK = comms.ESTABLISH_ACK
|
||||
local MGMT_TYPE = comms.MGMT_TYPE
|
||||
|
||||
---@type svr_config
|
||||
---@diagnostic disable-next-line: missing-fields
|
||||
local config = {}
|
||||
|
||||
supervisor.config = config
|
||||
@ -26,6 +27,9 @@ function supervisor.load_config()
|
||||
config.CoolingConfig = settings.get("CoolingConfig")
|
||||
config.FacilityTankMode = settings.get("FacilityTankMode")
|
||||
config.FacilityTankDefs = settings.get("FacilityTankDefs")
|
||||
config.FacilityTankList = settings.get("FacilityTankList")
|
||||
config.FacilityTankConns = settings.get("FacilityTankConns")
|
||||
config.TankFluidTypes = settings.get("TankFluidTypes")
|
||||
config.ExtChargeIdling = settings.get("ExtChargeIdling")
|
||||
|
||||
config.SVR_Channel = settings.get("SVR_Channel")
|
||||
@ -55,8 +59,11 @@ function supervisor.load_config()
|
||||
cfv.assert_range(config.UnitCount, 1, 4)
|
||||
|
||||
cfv.assert_type_table(config.CoolingConfig)
|
||||
cfv.assert_type_table(config.FacilityTankDefs)
|
||||
cfv.assert_type_int(config.FacilityTankMode)
|
||||
cfv.assert_type_table(config.FacilityTankDefs)
|
||||
cfv.assert_type_table(config.FacilityTankList)
|
||||
cfv.assert_type_table(config.FacilityTankConns)
|
||||
cfv.assert_type_table(config.TankFluidTypes)
|
||||
cfv.assert_range(config.FacilityTankMode, 0, 8)
|
||||
|
||||
cfv.assert_type_bool(config.ExtChargeIdling)
|
||||
|
||||
@ -90,6 +90,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
||||
envd = {}, ---@type envd_session[]
|
||||
-- redstone control
|
||||
io_ctl = nil, ---@type rs_controller
|
||||
---@diagnostic disable-next-line: missing-fields
|
||||
valves = {}, ---@type unit_valves
|
||||
emcool_opened = false,
|
||||
-- auto control
|
||||
@ -813,7 +814,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
|
||||
if limit > 0 then
|
||||
self.db.control.lim_br100 = math.floor(limit * 100)
|
||||
|
||||
if self.plc_i ~= nil then
|
||||
if (self.plc_i ~= nil) and (type(self.plc_i.get_struct().max_burn) == "number") then
|
||||
if limit > self.plc_i.get_struct().max_burn then
|
||||
self.db.control.lim_br100 = math.floor(self.plc_i.get_struct().max_burn * 100)
|
||||
end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user