-- -- I/O Control's Data Receive (Rx) Handlers -- local comms = require("scada-common.comms") local const = require("scada-common.constants") local types = require("scada-common.types") local util = require("scada-common.util") local DEV_TYPE = comms.DEVICE_TYPE 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 MTX_STATE = types.IMATRIX_STATE local SPS_STATE = types.SPS_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) if data.build then for key, val in pairs(data.build) do ps.publish(key, val) end end 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, _ 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.sps_ps_tbl[1].publish("SPSStateStatus", f_data[8]) fac.ps.publish("sps_process_rate", f_data[9]) end -- update facility app with facility and unit data from API_GET_FAC_DTL ---@param data table function iorx.record_fac_detail_data(data) local fac = io.facility local tank_statuses = data[5] local next_t_stat = 1 -- annunciator fac.all_sys_ok = data[1] fac.rtu_count = data[2] fac.auto_scram = data[3] fac.ascram_status = data[4] fac.ps.publish("all_sys_ok", fac.all_sys_ok) fac.ps.publish("rtu_count", fac.rtu_count) 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) -- unit data local units = data[12] for i = 1, io.facility.num_units do local unit = io.units[i] local u_rx = units[i] unit.connected = u_rx[1] unit.annunciator = u_rx[2] unit.reactor_data = u_rx[3] 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) unit.tank_data_tbl = u_rx[4] 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 = tank_statuses[next_t_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 end ps.publish("DynamicTankStatus", tank_status) ps.publish("DynamicTankStateStatus", c_stat) next_t_stat = next_t_stat + 1 end end -- facility dynamic tank data fac.tank_data_tbl = data[6] for id = 1, #fac.tank_data_tbl do local tank = fac.tank_data_tbl[id] local ps = fac.tank_ps_tbl[id] local c_stat = tank_statuses[next_t_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_t_stat = next_t_stat + 1 end -- induction matrix data fac.induction_data_tbl[1] = data[8] local matrix = fac.induction_data_tbl[1] local m_ps = fac.induction_ps_tbl[1] local m_stat = data[7] local mtx_status = 1 if m_stat ~= MTX_STATE.OFFLINE then if m_stat == MTX_STATE.FAULT then mtx_status = 3 elseif matrix.formed then mtx_status = 4 else mtx_status = 2 end _record_multiblock_status(m_stat == MTX_STATE.FAULT, matrix, m_ps) end m_ps.publish("InductionMatrixStatus", mtx_status) m_ps.publish("InductionMatrixStateStatus", m_stat) m_ps.publish("eta_string", data[9][1]) m_ps.publish("avg_charge", data[9][2]) m_ps.publish("avg_inflow", data[9][3]) m_ps.publish("avg_outflow", data[9][4]) m_ps.publish("is_charging", data[9][5]) m_ps.publish("is_discharging", data[9][6]) m_ps.publish("at_max_io", data[9][7]) -- sps data fac.sps_data_tbl[1] = data[11] local sps = fac.sps_data_tbl[1] local s_ps = fac.sps_ps_tbl[1] local s_stat = data[10] local sps_status = 1 if s_stat ~= SPS_STATE.OFFLINE then if s_stat == SPS_STATE.FAULT then sps_status = 3 elseif sps.formed then sps_status = 4 else sps_status = 2 end _record_multiblock_status(s_stat == SPS_STATE.FAULT, sps, s_ps) end s_ps.publish("SPSStatus", sps_status) s_ps.publish("SPSStateStatus", s_stat) end -- update the radiation monitor app with radiation monitor data from API_GET_RAD ---@param data table function iorx.record_radiation_data(data) -- unit radiation monitors for u_id = 1, #io.units do local unit = io.units[u_id] local max_rad = 0 local connected = {} unit.radiation = types.new_zero_radiation_reading() unit.rad_monitors = data[u_id] for id, mon in pairs(unit.rad_monitors) do table.insert(connected, id) unit.unit_ps.publish("radiation@" .. id, mon.radiation) if mon.raw > max_rad then max_rad = mon.raw unit.radiation = mon.radiation end end unit.unit_ps.publish("radiation", unit.radiation) unit.unit_ps.publish("radiation_monitors", textutils.serialize(connected)) end -- facility radiation monitors local fac = io.facility fac.radiation = types.new_zero_radiation_reading() fac.rad_monitors = data[#io.units + 1] local max_rad = 0 local connected = {} for id, mon in pairs(fac.rad_monitors) do table.insert(connected, id) fac.ps.publish("radiation@" .. id, mon.radiation) if mon.raw > max_rad then max_rad = mon.raw fac.radiation = mon.radiation end end fac.ps.publish("radiation", fac.radiation) fac.ps.publish("radiation_monitors", textutils.serialize(connected)) end local comp_record = {} -- update the computers app with the network data from INFO_LIST_CMP ---@param data table function iorx.record_network_data(data) local ps = io.ps local connected = {} local crd_online = false ps.publish("comp_online", #data) -- add/update connected computers for i = 1, #data do local entry = data[i] local type = entry[1] local id = entry[2] local pfx = "comp_" .. id connected[id] = true if type == DEV_TYPE.SVR then ps.publish("comp_svr_addr", id) ps.publish("comp_svr_fw", entry[3]) elseif type == DEV_TYPE.CRD then crd_online = true ps.publish("comp_crd_addr", id) ps.publish("comp_crd_fw", entry[3]) ps.publish("comp_crd_rtt", entry[4]) else ps.publish(pfx .. "_type", entry[1]) ps.publish(pfx .. "_addr", id) ps.publish(pfx .. "_fw", entry[3]) ps.publish(pfx .. "_rtt", entry[4]) if type == DEV_TYPE.PLC then ps.publish(pfx .. "_unit", entry[5]) end if not comp_record[id] then comp_record[id] = true -- trigger the app to create the new element ps.publish("comp_connect", id) end end end -- handle the coordinator being online or not -- no need to worry about the supervisor since this data is from the supervisor, so it has to be 'online' if received ps.publish("comp_crd_online", crd_online) if not crd_online then ps.publish("comp_crd_addr", "---") ps.publish("comp_crd_fw", "---") ps.publish("comp_crd_rtt", "---") end -- reset the published value ps.publish("comp_connect", false) -- remove disconnected computers for id, state in pairs(comp_record) do if state and not connected[id] then comp_record[id] = false -- trigger the app to delete the element ps.publish("comp_disconnect", id) end end -- reset the published value ps.publish("comp_disconnect", false) end -- clear the tracked connected computer record function iorx.clear_comp_record() comp_record = {} end return function (io_obj) io = io_obj return iorx end