Merge pull request #582 from MikaylaFischler/pocket-alpha-dev

Unit Dynamic Tank View
This commit is contained in:
Mikayla 2024-12-12 20:07:54 -05:00 committed by GitHub
commit 33803a1ace
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 1035 additions and 1067 deletions

View File

@ -24,6 +24,7 @@ local LINK_TIMEOUT = 60.0
local coordinator = {} local coordinator = {}
---@type crd_config ---@type crd_config
---@diagnostic disable-next-line: missing-fields
local config = {} local config = {}
coordinator.config = config coordinator.config = config

View File

@ -20,6 +20,13 @@ local ENERGY_UNITS = types.ENERGY_SCALE_UNITS
local TEMP_SCALE = types.TEMP_SCALE local TEMP_SCALE = types.TEMP_SCALE
local TEMP_UNITS = types.TEMP_SCALE_UNITS 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 -- 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 WARN_RTT = 1000 -- 2x as long as expected w/ 0 ping
local HIGH_RTT = 1500 -- 3.33x as long as expected w/ 0 ping local HIGH_RTT = 1500 -- 3.33x as long as expected w/ 0 ping
@ -119,7 +126,6 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
induction_ps_tbl = {}, ---@type psil[] induction_ps_tbl = {}, ---@type psil[]
induction_data_tbl = {}, ---@type imatrix_session_db[] induction_data_tbl = {}, ---@type imatrix_session_db[]
sps_status = 1,
sps_ps_tbl = {}, ---@type psil[] sps_ps_tbl = {}, ---@type psil[]
sps_data_tbl = {}, ---@type sps_session_db[] sps_data_tbl = {}, ---@type sps_session_db[]
@ -151,10 +157,6 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
local entry = { local entry = {
unit_id = i, unit_id = i,
connected = false, connected = false,
rtu_hw = {
boilers = {}, ---@type { connected: boolean, faulted: boolean }[]
turbines = {} ---@type { connected: boolean, faulted: boolean }[]
},
num_boilers = 0, num_boilers = 0,
num_turbines = 0, num_turbines = 0,
@ -224,6 +226,7 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
ALARM_STATE.INACTIVE -- turbine trip ALARM_STATE.INACTIVE -- turbine trip
}, },
---@diagnostic disable-next-line: missing-fields
annunciator = {}, ---@type annunciator annunciator = {}, ---@type annunciator
unit_ps = psil.create(), unit_ps = psil.create(),
@ -248,14 +251,12 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale)
for _ = 1, conf.cooling.r_cool[i].BoilerCount do for _ = 1, conf.cooling.r_cool[i].BoilerCount do
table.insert(entry.boiler_ps_tbl, psil.create()) table.insert(entry.boiler_ps_tbl, psil.create())
table.insert(entry.boiler_data_tbl, {}) table.insert(entry.boiler_data_tbl, {})
table.insert(entry.rtu_hw.boilers, { connected = false, faulted = false })
end end
-- create turbine tables -- create turbine tables
for _ = 1, conf.cooling.r_cool[i].TurbineCount do for _ = 1, conf.cooling.r_cool[i].TurbineCount do
table.insert(entry.turbine_ps_tbl, psil.create()) table.insert(entry.turbine_ps_tbl, psil.create())
table.insert(entry.turbine_data_tbl, {}) table.insert(entry.turbine_data_tbl, {})
table.insert(entry.rtu_hw.turbines, { connected = false, faulted = false })
end end
-- create tank tables -- create tank tables
@ -366,6 +367,7 @@ local function _record_multiblock_build(id, entry, data_tbl, ps_tbl, create)
if exists or create then if exists or create then
if not exists then if not exists then
ps_tbl[id] = psil.create() ps_tbl[id] = psil.create()
---@diagnostic disable-next-line: missing-fields
data_tbl[id] = {} data_tbl[id] = {}
end end
@ -627,10 +629,12 @@ function iocontrol.update_facility_status(status)
-- induction matricies statuses -- induction matricies statuses
if type(rtu_statuses.induction) == "table" then if type(rtu_statuses.induction) == "table" then
local matrix_status = MTX_STATE.OFFLINE
for id = 1, #fac.induction_ps_tbl do for id = 1, #fac.induction_ps_tbl do
if rtu_statuses.induction[id] == nil then if rtu_statuses.induction[id] == nil then
-- disconnected -- disconnected
fac.induction_ps_tbl[id].publish("computed_status", 1) fac.induction_ps_tbl[id].publish("computed_status", matrix_status)
end end
end end
@ -642,18 +646,20 @@ function iocontrol.update_facility_status(status)
local rtu_faulted = _record_multiblock_status(matrix, data, ps) local rtu_faulted = _record_multiblock_status(matrix, data, ps)
if rtu_faulted then if rtu_faulted then
ps.publish("computed_status", 3) -- faulted matrix_status = MTX_STATE.FAULT
elseif data.formed then elseif data.formed then
if data.tanks.energy_fill >= 0.99 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 elseif data.tanks.energy_fill <= 0.01 then
ps.publish("computed_status", 5) -- empty matrix_status = MTX_STATE.LOW_CHARGE
else else
ps.publish("computed_status", 4) -- on-line matrix_status = MTX_STATE.ONLINE
end end
else else
ps.publish("computed_status", 2) -- not formed matrix_status = MTX_STATE.UNFORMED
end end
ps.publish("computed_status", matrix_status)
else else
log.debug(util.c(log_header, "invalid induction matrix id ", id)) log.debug(util.c(log_header, "invalid induction matrix id ", id))
end end
@ -665,12 +671,12 @@ function iocontrol.update_facility_status(status)
-- SPS statuses -- SPS statuses
if type(rtu_statuses.sps) == "table" then 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 for id = 1, #fac.sps_ps_tbl do
if rtu_statuses.sps[id] == nil then if rtu_statuses.sps[id] == nil then
-- disconnected -- disconnected
fac.sps_ps_tbl[id].publish("computed_status", 1) fac.sps_ps_tbl[id].publish("computed_status", sps_status)
end end
end end
@ -682,11 +688,11 @@ function iocontrol.update_facility_status(status)
local rtu_faulted = _record_multiblock_status(sps, data, ps) local rtu_faulted = _record_multiblock_status(sps, data, ps)
if rtu_faulted then if rtu_faulted then
sps_status = 3 -- faulted sps_status = SPS_STATE.FAULT
elseif data.formed then elseif data.formed then
-- active / idle -- active / idle
sps_status = util.trinary(data.state.process_rate > 0, 5, 4) sps_status = util.trinary(data.state.process_rate > 0, SPS_STATE.ACTIVE, SPS_STATE.IDLE)
else sps_status = 2 end -- not formed else sps_status = SPS_STATE.UNFORMED end
ps.publish("computed_status", sps_status) ps.publish("computed_status", sps_status)
@ -695,8 +701,6 @@ function iocontrol.update_facility_status(status)
log.debug(util.c(log_header, "invalid sps id ", id)) log.debug(util.c(log_header, "invalid sps id ", id))
end end
end end
io.facility.sps_status = sps_status
else else
log.debug(log_header .. "sps list not a table") log.debug(log_header .. "sps list not a table")
valid = false valid = false
@ -704,10 +708,12 @@ function iocontrol.update_facility_status(status)
-- dynamic tank statuses -- dynamic tank statuses
if type(rtu_statuses.tanks) == "table" then if type(rtu_statuses.tanks) == "table" then
local tank_status = TNK_STATE.OFFLINE
for id = 1, #fac.tank_ps_tbl do for id = 1, #fac.tank_ps_tbl do
if rtu_statuses.tanks[id] == nil then if rtu_statuses.tanks[id] == nil then
-- disconnected -- disconnected
fac.tank_ps_tbl[id].publish("computed_status", 1) fac.tank_ps_tbl[id].publish("computed_status", tank_status)
end end
end end
@ -719,18 +725,18 @@ function iocontrol.update_facility_status(status)
local rtu_faulted = _record_multiblock_status(tank, data, ps) local rtu_faulted = _record_multiblock_status(tank, data, ps)
if rtu_faulted then if rtu_faulted then
ps.publish("computed_status", 3) -- faulted tank_status = TNK_STATE.FAULT
elseif data.formed then elseif data.formed then
if data.tanks.fill >= 0.99 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 elseif data.tanks.fill < 0.20 then
ps.publish("computed_status", 5) -- low tank_status = TNK_STATE.LOW_FILL
else else
ps.publish("computed_status", 4) -- on-line tank_status = TNK_STATE.ONLINE
end
else
ps.publish("computed_status", 2) -- not formed
end end
else tank_status = TNK_STATE.UNFORMED end
ps.publish("computed_status", tank_status)
else else
log.debug(util.c(log_header, "invalid dynamic tank id ", id)) log.debug(util.c(log_header, "invalid dynamic tank id ", id))
end end
@ -830,9 +836,11 @@ function iocontrol.update_unit_statuses(statuses)
log.debug(log_header .. "reactor status not a table") log.debug(log_header .. "reactor status not a table")
end end
local computed_status = RCT_STATE.OFFLINE
if #reactor_status == 0 then if #reactor_status == 0 then
unit.connected = false unit.connected = false
unit.unit_ps.publish("computed_status", 1) -- disconnected unit.unit_ps.publish("computed_status", computed_status)
elseif #reactor_status == 3 then elseif #reactor_status == 3 then
local mek_status = reactor_status[1] local mek_status = reactor_status[1]
local rps_status = reactor_status[2] local rps_status = reactor_status[2]
@ -871,22 +879,23 @@ function iocontrol.update_unit_statuses(statuses)
burn_rate_sum = burn_rate_sum + burn_rate burn_rate_sum = burn_rate_sum + burn_rate
if unit.reactor_data.mek_status.status then if unit.reactor_data.mek_status.status then
unit.unit_ps.publish("computed_status", 5) -- running computed_status = RCT_STATE.ACTIVE
else else
if unit.reactor_data.no_reactor then 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 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 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 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 else
unit.unit_ps.publish("computed_status", 4) -- disabled computed_status = RCT_STATE.DISABLED
end end
end end
unit.connected = true unit.connected = true
unit.unit_ps.publish("computed_status", computed_status)
else else
log.debug(log_header .. "reactor status length mismatch") log.debug(log_header .. "reactor status length mismatch")
valid = false valid = false
@ -900,13 +909,11 @@ function iocontrol.update_unit_statuses(statuses)
if type(rtu_statuses.boilers) == "table" then if type(rtu_statuses.boilers) == "table" then
local boil_sum = 0 local boil_sum = 0
for id = 1, #unit.boiler_ps_tbl do computed_status = BLR_STATE.OFFLINE
local connected = rtu_statuses.boilers[id] ~= nil
unit.rtu_hw.boilers[id].connected = connected
if not connected then for id = 1, #unit.boiler_ps_tbl do
-- disconnected if rtu_statuses.boilers[id] == nil then
unit.boiler_ps_tbl[id].publish("computed_status", 1) unit.boiler_ps_tbl[id].publish("computed_status", computed_status)
end end
end end
@ -916,21 +923,15 @@ function iocontrol.update_unit_statuses(statuses)
local ps = unit.boiler_ps_tbl[id] local ps = unit.boiler_ps_tbl[id]
local rtu_faulted = _record_multiblock_status(boiler, data, ps) local rtu_faulted = _record_multiblock_status(boiler, data, ps)
unit.rtu_hw.boilers[id].faulted = rtu_faulted
if rtu_faulted then if rtu_faulted then
ps.publish("computed_status", 3) -- faulted computed_status = BLR_STATE.FAULT
elseif data.formed then elseif data.formed then
boil_sum = boil_sum + data.state.boil_rate 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 unit.boiler_ps_tbl[id].publish("computed_status", computed_status)
ps.publish("computed_status", 5) -- active
else
ps.publish("computed_status", 4) -- idle
end
else
ps.publish("computed_status", 2) -- not formed
end
else else
log.debug(util.c(log_header, "invalid boiler id ", id)) log.debug(util.c(log_header, "invalid boiler id ", id))
valid = false valid = false
@ -947,13 +948,11 @@ function iocontrol.update_unit_statuses(statuses)
if type(rtu_statuses.turbines) == "table" then if type(rtu_statuses.turbines) == "table" then
local flow_sum = 0 local flow_sum = 0
for id = 1, #unit.turbine_ps_tbl do computed_status = TRB_STATE.OFFLINE
local connected = rtu_statuses.turbines[id] ~= nil
unit.rtu_hw.turbines[id].connected = connected
if not connected then for id = 1, #unit.turbine_ps_tbl do
-- disconnected if rtu_statuses.turbines[id] == nil then
unit.turbine_ps_tbl[id].publish("computed_status", 1) unit.turbine_ps_tbl[id].publish("computed_status", computed_status)
end end
end end
@ -963,23 +962,22 @@ function iocontrol.update_unit_statuses(statuses)
local ps = unit.turbine_ps_tbl[id] local ps = unit.turbine_ps_tbl[id]
local rtu_faulted = _record_multiblock_status(turbine, data, ps) local rtu_faulted = _record_multiblock_status(turbine, data, ps)
unit.rtu_hw.turbines[id].faulted = rtu_faulted
if rtu_faulted then if rtu_faulted then
ps.publish("computed_status", 3) -- faulted computed_status = TRB_STATE.FAULT
elseif data.formed then elseif data.formed then
flow_sum = flow_sum + data.state.flow_rate flow_sum = flow_sum + data.state.flow_rate
if data.tanks.energy_fill >= 0.99 then 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 elseif data.state.flow_rate < 100 then
ps.publish("computed_status", 4) -- idle computed_status = TRB_STATE.IDLE
else else
ps.publish("computed_status", 5) -- active computed_status = TRB_STATE.ACTIVE
end
else
ps.publish("computed_status", 2) -- not formed
end end
else computed_status = TRB_STATE.UNFORMED end
unit.turbine_ps_tbl[id].publish("computed_status", computed_status)
else else
log.debug(util.c(log_header, "invalid turbine id ", id)) log.debug(util.c(log_header, "invalid turbine id ", id))
valid = false valid = false
@ -994,10 +992,11 @@ function iocontrol.update_unit_statuses(statuses)
-- dynamic tank statuses -- dynamic tank statuses
if type(rtu_statuses.tanks) == "table" then if type(rtu_statuses.tanks) == "table" then
computed_status = TNK_STATE.OFFLINE
for id = 1, #unit.tank_ps_tbl do for id = 1, #unit.tank_ps_tbl do
if rtu_statuses.tanks[id] == nil then if rtu_statuses.tanks[id] == nil then
-- disconnected unit.tank_ps_tbl[id].publish("computed_status", computed_status)
unit.tank_ps_tbl[id].publish("computed_status", 1)
end end
end end
@ -1009,18 +1008,18 @@ function iocontrol.update_unit_statuses(statuses)
local rtu_faulted = _record_multiblock_status(tank, data, ps) local rtu_faulted = _record_multiblock_status(tank, data, ps)
if rtu_faulted then if rtu_faulted then
ps.publish("computed_status", 3) -- faulted computed_status = TNK_STATE.FAULT
elseif data.formed then elseif data.formed then
if data.tanks.fill >= 0.99 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 elseif data.tanks.fill < 0.20 then
ps.publish("computed_status", 5) -- low computed_status = TNK_STATE.LOW_FILL
else else
ps.publish("computed_status", 4) -- on-line computed_status = TNK_STATE.ONLINE
end
else
ps.publish("computed_status", 2) -- not formed
end end
else computed_status = TNK_STATE.UNFORMED end
unit.tank_ps_tbl[id].publish("computed_status", computed_status)
else else
log.debug(util.c(log_header, "invalid dynamic tank id ", id)) log.debug(util.c(log_header, "invalid dynamic tank id ", id))
valid = false valid = false
@ -1085,6 +1084,7 @@ function iocontrol.update_unit_statuses(statuses)
unit.annunciator = status[3] unit.annunciator = status[3]
if type(unit.annunciator) ~= "table" then if type(unit.annunciator) ~= "table" then
---@diagnostic disable-next-line: missing-fields
unit.annunciator = {} unit.annunciator = {}
log.debug(log_header .. "annunciator state not a table") log.debug(log_header .. "annunciator state not a table")
valid = false valid = false

View File

@ -286,6 +286,7 @@ function process.create_handle()
handle.unit_ack = {} handle.unit_ack = {}
for u = 1, pctl.io.facility.num_units do for u = 1, pctl.io.facility.num_units do
---@diagnostic disable-next-line: missing-fields
handle.unit_ack[u] = {} handle.unit_ack[u] = {}
---@class process_unit_ack ---@class process_unit_ack

View File

@ -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 if pkt.length == 1 and type(pkt.data[1]) == "number" then
local u = db.units[pkt.data[1]] 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 if u then
local data = { local data = {
u.unit_id, u.unit_id,
u.connected, u.connected,
u.rtu_hw, statuses,
u.a_group, u.a_group,
u.alarms, u.alarms,
u.annunciator, 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.pu_fallback,
proc.sps_low_power, proc.sps_low_power,
fac.waste_stats, fac.waste_stats,
fac.sps_status, fac.sps_ps_tbl[1].get("computed_status") or types.SPS_STATE.OFFLINE,
process_rate process_rate
} }

View File

@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer")
local sounder = require("coordinator.sounder") local sounder = require("coordinator.sounder")
local threads = require("coordinator.threads") local threads = require("coordinator.threads")
local COORDINATOR_VERSION = "v1.5.16" local COORDINATOR_VERSION = "v1.5.17"
local CHUNK_LOAD_DELAY_S = 30.0 local CHUNK_LOAD_DELAY_S = 30.0

View File

@ -147,235 +147,102 @@ style.gray_white = cpair(colors.gray, colors.white)
-- UI COMPONENTS -- -- UI COMPONENTS --
style.reactor = { style.reactor = {
-- reactor states -- reactor states<br>
---@see REACTOR_STATE
states = { states = {
{ { color = cpair(colors.black, colors.yellow), text = "PLC OFF-LINE" },
color = cpair(colors.black, colors.yellow), { color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
text = "PLC OFF-LINE" { 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.orange), { color = cpair(colors.black, colors.red), text = "SCRAMMED" },
text = "NOT FORMED" { color = cpair(colors.black, colors.red), text = "FORCE DISABLED" }
},
{
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 = { style.boiler = {
-- boiler states -- boiler states<br>
---@see BOILER_STATE
states = { states = {
{ { color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
color = cpair(colors.black, colors.yellow), { color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
text = "OFF-LINE" { 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.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 = { style.turbine = {
-- turbine states -- turbine states<br>
---@see TURBINE_STATE
states = { states = {
{ { color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
color = cpair(colors.black, colors.yellow), { color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
text = "OFF-LINE" { 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.orange), { color = cpair(colors.black, colors.red), text = "TRIP" }
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"
}
} }
} }
style.dtank = { style.dtank = {
-- dynamic tank states -- dynamic tank states<br>
---@see TANK_STATE
states = { states = {
{ { color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
color = cpair(colors.black, colors.yellow), { color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
text = "OFF-LINE" { 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.orange), { color = cpair(colors.black, colors.green), text = "FILLED" }
text = "NOT FORMED" }
}, }
{
color = cpair(colors.black, colors.orange), style.imatrix = {
text = "RTU FAULT" -- induction matrix states<br>
}, ---@see IMATRIX_STATE
{ states = {
color = cpair(colors.black, colors.green), { color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
text = "ONLINE" { 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), { color = cpair(colors.black, colors.yellow), text = "LOW CHARGE" },
text = "LOW FILL" { color = cpair(colors.black, colors.yellow), text = "HIGH CHARGE" }
}, }
{ }
color = cpair(colors.black, colors.green),
text = "FILLED" 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 = { style.waste = {
-- auto waste processing states -- auto waste processing states
states = { states = {
{ { color = cpair(colors.black, colors.green), text = "PLUTONIUM" },
color = cpair(colors.black, colors.green), { color = cpair(colors.black, colors.cyan), text = "POLONIUM" },
text = "PLUTONIUM" { color = cpair(colors.black, colors.purple), text = "ANTI MATTER" }
},
{
color = cpair(colors.black, colors.cyan),
text = "POLONIUM"
},
{
color = cpair(colors.black, colors.purple),
text = "ANTI MATTER"
}
}, },
states_abbrv = { states_abbrv = {
{ { color = cpair(colors.black, colors.green), text = "Pu" },
color = cpair(colors.black, colors.green), { color = cpair(colors.black, colors.cyan), text = "Po" },
text = "Pu" { color = cpair(colors.black, colors.purple), text = "AM" }
},
{
color = cpair(colors.black, colors.cyan),
text = "Po"
},
{
color = cpair(colors.black, colors.purple),
text = "AM"
}
}, },
-- process radio button options -- process radio button options
options = { "Plutonium", "Polonium", "Antimatter" }, options = { "Plutonium", "Polonium", "Antimatter" },
-- unit waste selection -- unit waste selection
unit_opts = { unit_opts = {
{ { text = "Auto", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.white, colors.gray) },
text = "Auto", { text = "Pu", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, colors.green) },
fg_bg = cpair(colors.black, colors.lightGray), { text = "Po", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, colors.cyan) },
active_fg_bg = cpair(colors.white, colors.gray) { text = "AM", fg_bg = cpair(colors.black, colors.lightGray), active_fg_bg = cpair(colors.black, colors.purple) }
},
{
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)
}
} }
} }

View File

@ -2,11 +2,11 @@
-- I/O Control for Pocket Integration with Supervisor & Coordinator -- I/O Control for Pocket Integration with Supervisor & Coordinator
-- --
local const = require("scada-common.constants")
local psil = require("scada-common.psil") local psil = require("scada-common.psil")
local types = require("scada-common.types") local types = require("scada-common.types")
local util = require("scada-common.util") local util = require("scada-common.util")
local iorx = require("pocket.iorx")
local process = require("pocket.process") local process = require("pocket.process")
local ALARM = types.ALARM local ALARM = types.ALARM
@ -50,6 +50,8 @@ function iocontrol.init_core(pkt_comms, nav, cfg)
comms = pkt_comms comms = pkt_comms
config = cfg config = cfg
iocontrol.rx = iorx(io)
io.nav = nav io.nav = nav
---@class pocket_ioctl_diag ---@class pocket_ioctl_diag
@ -194,8 +196,6 @@ function iocontrol.init_fac(conf)
local entry = { local entry = {
unit_id = i, unit_id = i,
connected = false, connected = false,
---@type { boilers: { connected: boolean, faulted: boolean }[], turbines: { connected: boolean, faulted: boolean }[] }
rtu_hw = {},
num_boilers = 0, num_boilers = 0,
num_turbines = 0, num_turbines = 0,
@ -239,6 +239,7 @@ function iocontrol.init_fac(conf)
---@type { [ALARM]: ALARM_STATE } ---@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 }, 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 annunciator = {}, ---@type annunciator
unit_ps = psil.create(), unit_ps = psil.create(),
@ -347,642 +348,6 @@ function iocontrol.report_crd_tt(trip_time)
io.ps.publish("crd_conn_quality", state) io.ps.publish("crd_conn_quality", state)
end 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 -- get the IO controller database
function iocontrol.get_db() return io end function iocontrol.get_db() return io end

657
pocket/iorx.lua Normal file
View 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

View File

@ -29,6 +29,7 @@ pocket.MQ__RENDER_CMD = MQ__RENDER_CMD
pocket.MQ__RENDER_DATA = MQ__RENDER_DATA pocket.MQ__RENDER_DATA = MQ__RENDER_DATA
---@type pkt_config ---@type pkt_config
---@diagnostic disable-next-line: missing-fields
local config = {} local config = {}
pocket.config = config pocket.config = config
@ -726,23 +727,23 @@ function pocket.comms(version, nic, sv_watchdog, api_watchdog, nav)
end end
elseif packet.type == CRDN_TYPE.API_GET_FAC then elseif packet.type == CRDN_TYPE.API_GET_FAC then
if _check_length(packet, 11) then if _check_length(packet, 11) then
iocontrol.record_facility_data(packet.data) iocontrol.rx.record_facility_data(packet.data)
end end
elseif packet.type == CRDN_TYPE.API_GET_UNIT then 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 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 end
elseif packet.type == CRDN_TYPE.API_GET_CTRL then elseif packet.type == CRDN_TYPE.API_GET_CTRL then
if _check_length(packet, #iocontrol.get_db().units) then if _check_length(packet, #iocontrol.get_db().units) then
iocontrol.record_control_data(packet.data) iocontrol.rx.record_control_data(packet.data)
end end
elseif packet.type == CRDN_TYPE.API_GET_PROC then elseif packet.type == CRDN_TYPE.API_GET_PROC then
if _check_length(packet, #iocontrol.get_db().units + 1) 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 end
elseif packet.type == CRDN_TYPE.API_GET_WASTE then elseif packet.type == CRDN_TYPE.API_GET_WASTE then
if _check_length(packet, #iocontrol.get_db().units + 1) 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 end
else _fail_type(packet) end else _fail_type(packet) end
else else

View File

@ -20,7 +20,7 @@ local pocket = require("pocket.pocket")
local renderer = require("pocket.renderer") local renderer = require("pocket.renderer")
local threads = require("pocket.threads") local threads = require("pocket.threads")
local POCKET_VERSION = "v0.12.10-alpha" local POCKET_VERSION = "v0.12.11-alpha"
local println = util.println local println = util.println
local println_ts = util.println_ts local println_ts = util.println_ts

View File

@ -9,6 +9,7 @@ local pocket = require("pocket.pocket")
local style = require("pocket.ui.style") local style = require("pocket.ui.style")
local dyn_tank = require("pocket.ui.pages.dynamic_tank")
local boiler = require("pocket.ui.pages.unit_boiler") local boiler = require("pocket.ui.pages.unit_boiler")
local reactor = require("pocket.ui.pages.unit_reactor") local reactor = require("pocket.ui.pages.unit_reactor")
local turbine = require("pocket.ui.pages.unit_turbine") 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] }) table.insert(list, { label = "T-" .. i, color = core.cpair(colors.black, colors.lightGray), callback = nav_links[id].turbine[i] })
end 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) app.set_sidebar(list)
end end
@ -363,6 +368,15 @@ local function new_view(root)
--#endregion --#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, unit.tank_ps_tbl[1], update)
end
--#endregion
util.nop() util.nop()
end end

View File

@ -0,0 +1,74 @@
local types = require("scada-common.types")
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 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 ps psil
---@param update function
return function (app, page, panes, tank_pane, ps, update)
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}
TextBox{parent=tank_div,y=6,text="Fluid Level",width=11,fg_bg=label}
local level = HorizontalBar{parent=tank_div,y=7,bar_fg_bg=cpair(colors.blue,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

View File

@ -95,180 +95,93 @@ style.icon_states = states
-- MAIN LAYOUT -- -- MAIN LAYOUT --
style.reactor = { style.reactor = {
-- reactor states -- reactor states<br>
---@see REACTOR_STATE
states = { states = {
{ { color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
color = cpair(colors.black, colors.yellow), { color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
text = "OFF-LINE" { 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.orange), { color = cpair(colors.black, colors.red), text = "SCRAMMED" },
text = "NOT FORMED" { color = cpair(colors.black, colors.red), text = "FORCE DSBL" }
},
{
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 = { style.boiler = {
-- boiler states -- boiler states<br>
---@see BOILER_STATE
states = { states = {
{ { color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
color = cpair(colors.black, colors.yellow), { color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
text = "OFF-LINE" { 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.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 = { style.turbine = {
-- turbine states -- turbine states<br>
---@see TURBINE_STATE
states = { states = {
{ { color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
color = cpair(colors.black, colors.yellow), { color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
text = "OFF-LINE" { 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.orange), { color = cpair(colors.black, colors.red), text = "TRIP" }
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 = { style.imatrix = {
-- induction matrix states -- induction matrix states<br>
---@see IMATRIX_STATE
states = { states = {
{ { color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
color = cpair(colors.black, colors.yellow), { color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
text = "OFF-LINE" { 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.orange), { color = cpair(colors.black, colors.yellow), text = "HIGH CHARGE" }
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 = { style.sps = {
-- SPS states -- SPS states<br>
---@see SPS_STATE
states = { states = {
{ { color = cpair(colors.black, colors.yellow), text = "OFF-LINE" },
color = cpair(colors.black, colors.yellow), { color = cpair(colors.black, colors.orange), text = "NOT FORMED" },
text = "OFF-LINE" { 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.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 = { style.waste = {
-- auto waste processing states -- auto waste processing states
states = { states = {
{ { color = cpair(colors.black, colors.green), text = "PLUTONIUM" },
color = cpair(colors.black, colors.green), { color = cpair(colors.black, colors.cyan), text = "POLONIUM" },
text = "PLUTONIUM" { color = cpair(colors.black, colors.purple), text = "ANTI MATTER" }
},
{
color = cpair(colors.black, colors.cyan),
text = "POLONIUM"
},
{
color = cpair(colors.black, colors.purple),
text = "ANTI MATTER"
}
}, },
states_abbrv = { states_abbrv = {
{ { color = cpair(colors.black, colors.green), text = "Pu" },
color = cpair(colors.black, colors.green), { color = cpair(colors.black, colors.cyan), text = "Po" },
text = "Pu" { color = cpair(colors.black, colors.purple), text = "AM" }
},
{
color = cpair(colors.black, colors.cyan),
text = "Po"
},
{
color = cpair(colors.black, colors.purple),
text = "AM"
}
}, },
-- process radio button options -- process radio button options
options = { "Plutonium", "Polonium", "Antimatter" }, options = { "Plutonium", "Polonium", "Antimatter" },

View File

@ -29,6 +29,7 @@ local PCALL_SCRAM_MSG = "Scram requires the reactor to be active."
local PCALL_START_MSG = "Reactor is already active." local PCALL_START_MSG = "Reactor is already active."
---@type plc_config ---@type plc_config
---@diagnostic disable-next-line: missing-fields
local config = {} local config = {}
plc.config = config plc.config = config

View File

@ -19,6 +19,7 @@ local MGMT_TYPE = comms.MGMT_TYPE
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
---@type rtu_config ---@type rtu_config
---@diagnostic disable-next-line: missing-fields
local config = {} local config = {}
rtu.config = config rtu.config = config

View File

@ -74,6 +74,13 @@ function psil.create()
end end
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 -- clear the contents of the interconnect
function public.purge() ic = {} end function public.purge() ic = {} end

View File

@ -253,6 +253,61 @@ types.ENERGY_SCALE_UNITS = {
"RF" "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 ---@enum PANEL_LINK_STATE
types.PANEL_LINK_STATE = { types.PANEL_LINK_STATE = {
LINKED = 1, LINKED = 1,

View File

@ -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 elseif cmd == FAC_COMMAND.START then
if pkt.length == 6 then if pkt.length == 6 then
---@type sys_auto_config ---@type sys_auto_config
---@diagnostic disable-next-line: missing-fields
local config = { local config = {
mode = pkt.data[2], mode = pkt.data[2],
burn_target = pkt.data[3], burn_target = pkt.data[3],

View File

@ -46,12 +46,14 @@ local self = {
config = nil, ---@type svr_config config = nil, ---@type svr_config
facility = nil, ---@type facility|nil facility = nil, ---@type facility|nil
-- lists of connected sessions -- lists of connected sessions
---@diagnostic disable: missing-fields
sessions = { sessions = {
rtu = {}, ---@type rtu_session_struct rtu = {}, ---@type rtu_session_struct
plc = {}, ---@type plc_session_struct plc = {}, ---@type plc_session_struct
crd = {}, ---@type crd_session_struct crd = {}, ---@type crd_session_struct
pdg = {} ---@type pdg_session_struct pdg = {} ---@type pdg_session_struct
}, },
---@diagnostic enable: missing-fields
-- next session IDs -- next session IDs
next_ids = { rtu = 0, plc = 0, crd = 0, pdg = 0 }, next_ids = { rtu = 0, plc = 0, crd = 0, pdg = 0 },
-- rtu device tracking and invalid assignment detection -- rtu device tracking and invalid assignment detection

View File

@ -14,6 +14,7 @@ local ESTABLISH_ACK = comms.ESTABLISH_ACK
local MGMT_TYPE = comms.MGMT_TYPE local MGMT_TYPE = comms.MGMT_TYPE
---@type svr_config ---@type svr_config
---@diagnostic disable-next-line: missing-fields
local config = {} local config = {}
supervisor.config = config supervisor.config = config

View File

@ -90,6 +90,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle)
envd = {}, ---@type envd_session[] envd = {}, ---@type envd_session[]
-- redstone control -- redstone control
io_ctl = nil, ---@type rs_controller io_ctl = nil, ---@type rs_controller
---@diagnostic disable-next-line: missing-fields
valves = {}, ---@type unit_valves valves = {}, ---@type unit_valves
emcool_opened = false, emcool_opened = false,
-- auto control -- auto control