diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index ff81e5d..11287e7 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -364,8 +364,9 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel, -- send a facility command ---@param cmd FAC_COMMAND command - function public.send_fac_command(cmd) - _send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.FAC_CMD, { cmd }) + ---@param option any? optional option options for the optional options (like waste mode) + function public.send_fac_command(cmd, option) + _send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.FAC_CMD, { cmd, option }) end -- send the auto process control configuration with a start command @@ -379,7 +380,7 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel, -- send a unit command ---@param cmd UNIT_COMMAND command ---@param unit integer unit ID - ---@param option any? optional option options for the optional options (like burn rate) (does option still look like a word?) + ---@param option any? optional option options for the optional options (like burn rate) function public.send_unit_command(cmd, unit, option) _send_sv(PROTOCOL.SCADA_CRDN, SCADA_CRDN_TYPE.UNIT_CMD, { cmd, unit, option }) end @@ -563,6 +564,10 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel, end elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then iocontrol.get_db().facility.ack_alarms_ack(ack) + elseif cmd == FAC_COMMAND.SET_WASTE_MODE then + process.waste_ack_handle(packet.data[2]) + elseif cmd == FAC_COMMAND.SET_PU_FB then + process.pu_fb_ack_handle(packet.data[2]) else log.debug(util.c("received facility command ack with unknown command ", cmd)) end diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 24e839c..2eeb5e2 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -52,6 +52,10 @@ function iocontrol.init(conf, comms) gen_fault = false }, + ---@type WASTE_PRODUCT + auto_current_waste_product = types.WASTE_PRODUCT.PLUTONIUM, + auto_pu_fallback_active = false, + radiation = types.new_zero_radiation_reading(), save_cfg_ack = __generic_ack, @@ -65,16 +69,18 @@ function iocontrol.init(conf, comms) induction_ps_tbl = {}, induction_data_tbl = {}, + sps_ps_tbl = {}, + sps_data_tbl = {}, + env_d_ps = psil.create(), env_d_data = {} } - -- create induction tables (currently only 1 is supported) - for _ = 1, conf.num_units do - local data = {} ---@type imatrix_session_db - table.insert(io.facility.induction_ps_tbl, psil.create()) - table.insert(io.facility.induction_data_tbl, data) - end + -- create induction and SPS tables (currently only 1 of each is supported) + table.insert(io.facility.induction_ps_tbl, psil.create()) + table.insert(io.facility.induction_data_tbl, {}) + table.insert(io.facility.sps_ps_tbl, psil.create()) + table.insert(io.facility.sps_data_tbl, {}) io.units = {} for i = 1, conf.num_units do @@ -87,11 +93,15 @@ function iocontrol.init(conf, comms) num_boilers = 0, num_turbines = 0, + num_snas = 0, control_state = false, burn_rate_cmd = 0.0, - waste_control = 0, radiation = types.new_zero_radiation_reading(), + sna_prod_rate = 0.0, + + waste_mode = types.WASTE_MODE.MANUAL_PLUTONIUM, + waste_product = types.WASTE_PRODUCT.PLUTONIUM, -- auto control group a_group = 0, @@ -100,10 +110,10 @@ function iocontrol.init(conf, comms) scram = function () process.scram(i) end, reset_rps = function () process.reset_rps(i) end, ack_alarms = function () process.ack_all_alarms(i) end, - set_burn = function (rate) process.set_rate(i, rate) end, ---@param rate number burn rate - set_waste = function (mode) process.set_waste(i, mode) end, ---@param mode integer waste processing mode + set_burn = function (rate) process.set_rate(i, rate) end, ---@param rate number burn rate + set_waste = function (mode) process.set_unit_waste(i, mode) end, ---@param mode WASTE_MODE waste processing mode - set_group = function (grp) process.set_group(i, grp) end, ---@param grp integer|0 group ID or 0 + set_group = function (grp) process.set_group(i, grp) end, ---@param grp integer|0 group ID or 0 for manual start_ack = __generic_ack, scram_ack = __generic_ack, @@ -206,6 +216,25 @@ function iocontrol.record_facility_builds(build) end end end + + -- SPS + if type(build.sps) == "table" then + for id, sps in pairs(build.sps) do + if type(fac.sps_data_tbl[id]) == "table" then + fac.sps_data_tbl[id].formed = sps[1] ---@type boolean + fac.sps_data_tbl[id].build = sps[2] ---@type table + + fac.sps_ps_tbl[id].publish("formed", sps[1]) + + for key, val in pairs(fac.sps_data_tbl[id].build) do + fac.sps_ps_tbl[id].publish(key, val) + end + else + log.debug(util.c("iocontrol.record_facility_builds: invalid SPS id ", id)) + valid = false + end + end + end else log.debug("facility builds not a table") valid = false @@ -306,7 +335,7 @@ function iocontrol.update_facility_status(status) local ctl_status = status[1] - if type(ctl_status) == "table" and #ctl_status == 14 then + if type(ctl_status) == "table" and #ctl_status == 16 then fac.all_sys_ok = ctl_status[1] fac.auto_ready = ctl_status[2] @@ -354,6 +383,12 @@ function iocontrol.update_facility_status(status) io.units[i].unit_ps.publish("auto_group", names[group_map[i] + 1]) end end + + fac.auto_current_waste_product = ctl_status[15] + fac.auto_pu_fallback_active = ctl_status[16] + + fac.ps.publish("current_waste_product", fac.auto_current_waste_product) + fac.ps.publish("pu_fallback_active", fac.auto_pu_fallback_active) else log.debug(log_header .. "control status not a table or length mismatch") valid = false @@ -430,6 +465,52 @@ function iocontrol.update_facility_status(status) valid = false end + -- SPS statuses + if type(rtu_statuses.sps) == "table" then + for id = 1, #fac.sps_ps_tbl do + if rtu_statuses.sps[id] == nil then + -- disconnected + fac.sps_ps_tbl[id].publish("computed_status", 1) + end + end + + for id, sps in pairs(rtu_statuses.sps) do + if type(fac.sps_data_tbl[id]) == "table" then + local rtu_faulted = sps[1] ---@type boolean + fac.sps_data_tbl[id].formed = sps[2] ---@type boolean + fac.sps_data_tbl[id].state = sps[3] ---@type table + fac.sps_data_tbl[id].tanks = sps[4] ---@type table + + local data = fac.sps_data_tbl[id] ---@type sps_session_db + + fac.sps_ps_tbl[id].publish("formed", data.formed) + fac.sps_ps_tbl[id].publish("faulted", rtu_faulted) + + if data.formed then + if rtu_faulted then + fac.sps_ps_tbl[id].publish("computed_status", 3) -- faulted + elseif data.state.process_rate > 0 then + fac.sps_ps_tbl[id].publish("computed_status", 5) -- active + else + fac.sps_ps_tbl[id].publish("computed_status", 4) -- idle + end + else + fac.sps_ps_tbl[id].publish("computed_status", 2) -- not formed + end + + for key, val in pairs(data.state) do fac.sps_ps_tbl[id].publish(key, val) end + for key, val in pairs(data.tanks) do fac.sps_ps_tbl[id].publish(key, val) end + + io.facility.ps.publish("am_rate", data.state.process_rate * 1000) + else + log.debug(util.c(log_header, "invalid sps id ", id)) + end + end + else + log.debug(log_header .. "sps list not a table") + valid = false + end + -- environment detector status if type(rtu_statuses.rad_mon) == "table" then if #rtu_statuses.rad_mon > 0 then @@ -472,6 +553,9 @@ function iocontrol.update_unit_statuses(statuses) valid = false else local burn_rate_sum = 0.0 + local sna_count_sum = 0 + local pu_rate = 0.0 + local po_rate = 0.0 -- get all unit statuses for i = 1, #statuses do @@ -480,6 +564,8 @@ function iocontrol.update_unit_statuses(statuses) local unit = io.units[i] ---@type ioctl_unit local status = statuses[i] + local burn_rate = 0.0 + if type(status) ~= "table" or #status ~= 5 then log.debug(log_header .. "invalid status entry in unit statuses (not a table or invalid length)") valid = false @@ -515,7 +601,8 @@ function iocontrol.update_unit_statuses(statuses) -- if status hasn't been received, mek_status = {} if type(unit.reactor_data.mek_status.act_burn_rate) == "number" then - burn_rate_sum = burn_rate_sum + unit.reactor_data.mek_status.act_burn_rate + burn_rate = unit.reactor_data.mek_status.act_burn_rate + burn_rate_sum = burn_rate_sum + burn_rate end if unit.reactor_data.mek_status.status then @@ -662,6 +749,19 @@ function iocontrol.update_unit_statuses(statuses) valid = false end + -- solar neutron activator status info + if type(rtu_statuses.sna) == "table" then + unit.num_snas = rtu_statuses.sna[1] ---@type integer + unit.sna_prod_rate = rtu_statuses.sna[2] ---@type number + + unit.unit_ps.publish("sna_prod_rate", unit.sna_prod_rate) + + sna_count_sum = sna_count_sum + unit.num_snas + else + log.debug(log_header .. "sna statistic list not a table") + valid = false + end + -- environment detector status if type(rtu_statuses.rad_mon) == "table" then if #rtu_statuses.rad_mon > 0 then @@ -739,12 +839,17 @@ function iocontrol.update_unit_statuses(statuses) local unit_state = status[5] if type(unit_state) == "table" then - if #unit_state == 5 then + if #unit_state == 6 then + unit.waste_mode = unit_state[5] + unit.waste_product = unit_state[6] + unit.unit_ps.publish("U_StatusLine1", unit_state[1]) unit.unit_ps.publish("U_StatusLine2", unit_state[2]) - unit.unit_ps.publish("U_WasteMode", unit_state[3]) - unit.unit_ps.publish("U_AutoReady", unit_state[4]) - unit.unit_ps.publish("U_AutoDegraded", unit_state[5]) + unit.unit_ps.publish("U_AutoReady", unit_state[3]) + unit.unit_ps.publish("U_AutoDegraded", unit_state[4]) + 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) else log.debug(log_header .. "unit state length mismatch") valid = false @@ -753,10 +858,18 @@ function iocontrol.update_unit_statuses(statuses) log.debug(log_header .. "unit state not a table") valid = false end + + -- determine waste production for this unit, add to statistics + local is_pu = unit.waste_product == types.WASTE_PRODUCT.PLUTONIUM + pu_rate = pu_rate + util.trinary(is_pu, burn_rate / 10.0, 0.0) + po_rate = po_rate + util.trinary(not is_pu, math.min(burn_rate / 10.0, unit.sna_prod_rate), 0.0) end end io.facility.ps.publish("burn_sum", burn_rate_sum) + io.facility.ps.publish("sna_count", sna_count_sum) + io.facility.ps.publish("pu_rate", pu_rate) + io.facility.ps.publish("po_rate", po_rate) -- update alarm sounder sounder.eval(io.units) diff --git a/coordinator/process.lua b/coordinator/process.lua index 1e318ed..ad9c94f 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -11,6 +11,7 @@ local FAC_COMMAND = comms.FAC_COMMAND local UNIT_COMMAND = comms.UNIT_COMMAND local PROCESS = types.PROCESS +local PRODUCT = types.WASTE_PRODUCT ---@class process_controller local process = {} @@ -24,7 +25,9 @@ local self = { burn_target = 0.0, charge_target = 0.0, gen_target = 0.0, - limits = {} + limits = {}, + waste_product = PRODUCT.PLUTONIUM, + pu_fallback = false } } @@ -48,19 +51,23 @@ function process.init(iocontrol, coord_comms) log.error("process.init(): failed to load coordinator settings file") end + -- facility auto control configuration local config = settings.get("PROCESS") ---@type coord_auto_config|nil - if type(config) == "table" then self.config.mode = config.mode self.config.burn_target = config.burn_target self.config.charge_target = config.charge_target self.config.gen_target = config.gen_target self.config.limits = config.limits + self.config.waste_product = config.waste_product + self.config.pu_fallback = config.pu_fallback self.io.facility.ps.publish("process_mode", self.config.mode) self.io.facility.ps.publish("process_burn_target", self.config.burn_target) self.io.facility.ps.publish("process_charge_target", self.config.charge_target) self.io.facility.ps.publish("process_gen_target", self.config.gen_target) + self.io.facility.ps.publish("process_waste_product", self.config.waste_product) + self.io.facility.ps.publish("process_pu_fallback", self.config.pu_fallback) for id = 1, math.min(#self.config.limits, self.io.facility.num_units) do local unit = self.io.units[id] ---@type ioctl_unit @@ -70,18 +77,18 @@ function process.init(iocontrol, coord_comms) log.info("PROCESS: loaded auto control settings from coord.settings") end - local waste_mode = settings.get("WASTE_MODES") ---@type table|nil - - if type(waste_mode) == "table" then - for id, mode in pairs(waste_mode) do + -- unit waste states + local waste_modes = settings.get("WASTE_MODES") ---@type table|nil + if type(waste_modes) == "table" then + for id, mode in pairs(waste_modes) do self.comms.send_unit_command(UNIT_COMMAND.SET_WASTE, id, mode) end - log.info("PROCESS: loaded waste mode settings from coord.settings") + log.info("PROCESS: loaded unit waste mode settings from coord.settings") end + -- unit priority groups local prio_groups = settings.get("PRIORITY_GROUPS") ---@type table|nil - if type(prio_groups) == "table" then for id, group in pairs(prio_groups) do self.comms.send_unit_command(UNIT_COMMAND.SET_GROUP, id, group) @@ -137,7 +144,7 @@ end -- set waste mode ---@param id integer unit ID ---@param mode integer waste mode -function process.set_waste(id, mode) +function process.set_unit_waste(id, mode) -- publish so that if it fails then it gets reset self.io.units[id].unit_ps.publish("U_WasteMode", mode) @@ -153,7 +160,7 @@ function process.set_waste(id, mode) settings.set("WASTE_MODES", waste_mode) if not settings.save("/coord.settings") then - log.error("process.set_waste(): failed to save coordinator settings file") + log.error("process.set_unit_waste(): failed to save coordinator settings file") end end @@ -204,6 +211,24 @@ end -- AUTO PROCESS CONTROL -- -------------------------- +-- write auto process control to config file +local function _write_auto_config() + -- attempt to load settings + if not settings.load("/coord.settings") then + log.warning("process._write_auto_config(): failed to load coordinator settings file") + end + + -- save config + settings.set("PROCESS", self.config) + local saved = settings.save("/coord.settings") + + if not saved then + log.warning("process._write_auto_config(): failed to save coordinator settings file") + end + + return not not saved +end + -- stop automatic process control function process.stop_auto() self.comms.send_fac_command(FAC_COMMAND.STOP) @@ -216,6 +241,30 @@ function process.start_auto() log.debug("PROCESS: START AUTO CTL") end +-- set automatic process control waste mode +---@param product WASTE_PRODUCT waste product for auto control +function process.set_process_waste(product) + self.comms.send_fac_command(FAC_COMMAND.SET_WASTE_MODE, product) + + log.debug(util.c("PROCESS: SET WASTE ", product)) + + -- update config table and save + self.config.waste_product = product + _write_auto_config() +end + +-- set automatic process control plutonium fallback +---@param enabled boolean whether to enable plutonium fallback +function process.set_pu_fallback(enabled) + self.comms.send_fac_command(FAC_COMMAND.SET_PU_FB, enabled) + + log.debug(util.c("PROCESS: SET PU FALLBACK ", enabled)) + + -- update config table and save + self.config.pu_fallback = enabled + _write_auto_config() +end + -- save process control settings ---@param mode PROCESS control mode ---@param burn_target number burn rate target @@ -223,29 +272,17 @@ end ---@param gen_target number generation rate target ---@param limits table unit burn rate limits function process.save(mode, burn_target, charge_target, gen_target, limits) - -- attempt to load settings - if not settings.load("/coord.settings") then - log.warning("process.save(): failed to load coordinator settings file") - end + log.debug("PROCESS: SAVE") - -- config table - self.config = { - mode = mode, - burn_target = burn_target, - charge_target = charge_target, - gen_target = gen_target, - limits = limits - } + -- update config table + self.config.mode = mode + self.config.burn_target = burn_target + self.config.charge_target = charge_target + self.config.gen_target = gen_target + self.config.limits = limits -- save config - settings.set("PROCESS", self.config) - local saved = settings.save("/coord.settings") - - if not saved then - log.warning("process.save(): failed to save coordinator settings file") - end - - self.io.facility.save_cfg_ack(saved) + self.io.facility.save_cfg_ack(_write_auto_config()) end -- handle a start command acknowledgement @@ -258,16 +295,33 @@ function process.start_ack_handle(response) self.config.charge_target = response[4] self.config.gen_target = response[5] - for i = 1, #response[6] do + for i = 1, math.min(#response[6], self.io.facility.num_units) do self.config.limits[i] = response[6][i] + + local unit = self.io.units[i] ---@type ioctl_unit + unit.unit_ps.publish("burn_limit", self.config.limits[i]) end - self.io.facility.ps.publish("auto_mode", self.config.mode) - self.io.facility.ps.publish("burn_target", self.config.burn_target) - self.io.facility.ps.publish("charge_target", self.config.charge_target) - self.io.facility.ps.publish("gen_target", self.config.gen_target) + self.io.facility.ps.publish("process_mode", self.config.mode) + self.io.facility.ps.publish("process_burn_target", self.config.burn_target) + self.io.facility.ps.publish("process_charge_target", self.config.charge_target) + self.io.facility.ps.publish("process_gen_target", self.config.gen_target) self.io.facility.start_ack(ack) end +-- record waste product state after attempting to change it +---@param response WASTE_PRODUCT supervisor waste product state +function process.waste_ack_handle(response) + self.config.waste_product = response + self.io.facility.ps.publish("process_waste_product", response) +end + +-- record plutonium fallback state after attempting to change it +---@param response boolean supervisor plutonium fallback state +function process.pu_fb_ack_handle(response) + self.config.pu_fallback = response + self.io.facility.ps.publish("process_pu_fallback", response) +end + return process diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 8bbc550..f58642d 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -21,7 +21,7 @@ local sounder = require("coordinator.sounder") local apisessions = require("coordinator.session.apisessions") -local COORDINATOR_VERSION = "v0.17.1" +local COORDINATOR_VERSION = "v0.18.0" local println = util.println local println_ts = util.println_ts diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/process_ctl.lua similarity index 68% rename from coordinator/ui/components/processctl.lua rename to coordinator/ui/components/process_ctl.lua index eaeb8ab..0716619 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/process_ctl.lua @@ -15,8 +15,10 @@ local TextBox = require("graphics.elements.textbox") local DataIndicator = require("graphics.elements.indicators.data") local IndicatorLight = require("graphics.elements.indicators.light") local RadIndicator = require("graphics.elements.indicators.rad") +local StateIndicator = require("graphics.elements.indicators.state") local TriIndicatorLight = require("graphics.elements.indicators.trilight") +local Checkbox = require("graphics.elements.controls.checkbox") local HazardButton = require("graphics.elements.controls.hazard_button") local RadioButton = require("graphics.elements.controls.radio_button") local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") @@ -43,7 +45,7 @@ local function new_view(root, x, y) local lu_cpair = cpair(colors.gray, colors.gray) local dis_colors = cpair(colors.white, colors.lightGray) - local main = Div{parent=root,width=104,height=24,x=x,y=y} + local main = Div{parent=root,width=128,height=24,x=x,y=y} local scram = HazardButton{parent=main,x=1,y=1,text="FAC SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=process.fac_scram,fg_bg=hzd_fg_bg} local ack_a = HazardButton{parent=main,x=16,y=1,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=process.fac_ack_alarms,fg_bg=hzd_fg_bg} @@ -52,12 +54,14 @@ local function new_view(root, x, y) facility.ack_alarms_ack = ack_a.on_response local all_ok = IndicatorLight{parent=main,y=5,label="Unit Systems Online",colors=cpair(colors.green,colors.red)} - local ind_mat = IndicatorLight{parent=main,label="Induction Matrix",colors=cpair(colors.green,colors.gray)} local rad_mon = TriIndicatorLight{parent=main,label="Radiation Monitor",c1=colors.gray,c2=colors.yellow,c3=colors.green} + local ind_mat = IndicatorLight{parent=main,label="Induction Matrix",colors=cpair(colors.green,colors.gray)} + local sps = IndicatorLight{parent=main,label="SPS Connected",colors=cpair(colors.green,colors.gray)} all_ok.register(facility.ps, "all_sys_ok", all_ok.update) - ind_mat.register(facility.induction_ps_tbl[1], "computed_status", function (status) ind_mat.update(status > 1) end) rad_mon.register(facility.ps, "rad_computed_status", rad_mon.update) + ind_mat.register(facility.induction_ps_tbl[1], "computed_status", function (status) ind_mat.update(status > 1) end) + sps.register(facility.sps_ps_tbl[1], "computed_status", function (status) sps.update(status > 1) end) main.line_break() @@ -99,7 +103,7 @@ local function new_view(root, x, y) -- process control -- --------------------- - local proc = Div{parent=main,width=78,height=24,x=27,y=1} + local proc = Div{parent=main,width=103,height=24,x=27,y=1} ----------------------------- -- process control targets -- @@ -148,46 +152,77 @@ local function new_view(root, x, y) local rate_limits = {} - for i = 1, facility.num_units do - local unit = units[i] ---@type ioctl_unit + for i = 1, 4 do + local unit + local tag_fg_bg = cpair(colors.gray,colors.white) + local lim_fg_bg = cpair(colors.lightGray,colors.white) + local ctl_fg = colors.lightGray + local cur_fg_bg = cpair(colors.lightGray,colors.white) + local cur_lu = colors.lightGray + + if i <= facility.num_units then + unit = units[i] ---@type ioctl_unit + tag_fg_bg = cpair(colors.black,colors.lightBlue) + lim_fg_bg = bw_fg_bg + ctl_fg = colors.gray + cur_fg_bg = cpair(colors.black,colors.brown) + cur_lu = colors.black + end local _y = ((i - 1) * 5) + 1 - local unit_tag = Div{parent=limit_div,x=1,y=_y,width=8,height=4,fg_bg=cpair(colors.black,colors.lightBlue)} + local unit_tag = Div{parent=limit_div,x=1,y=_y,width=8,height=4,fg_bg=tag_fg_bg} TextBox{parent=unit_tag,x=2,y=2,text="Unit "..i.." Limit",width=7,height=2} - local lim_ctl = Div{parent=limit_div,x=9,y=_y,width=14,height=3,fg_bg=cpair(colors.gray,colors.white)} - rate_limits[i] = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg} + local lim_ctl = Div{parent=limit_div,x=9,y=_y,width=14,height=3,fg_bg=cpair(ctl_fg,colors.white)} + local lim = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=lim_fg_bg} TextBox{parent=lim_ctl,x=9,y=2,text="mB/t",width=4,height=1} - rate_limits[i].register(unit.unit_ps, "max_burn", rate_limits[i].set_max) - rate_limits[i].register(unit.unit_ps, "burn_limit", rate_limits[i].set_value) + local cur_burn = DataIndicator{parent=limit_div,x=9,y=_y+3,label="",format="%7.1f",value=0,unit="mB/t",commas=false,lu_colors=cpair(cur_lu,cur_lu),width=14,fg_bg=cur_fg_bg} - local cur_burn = DataIndicator{parent=limit_div,x=9,y=_y+3,label="",format="%7.1f",value=0,unit="mB/t",commas=false,lu_colors=cpair(colors.black,colors.black),width=14,fg_bg=cpair(colors.black,colors.brown)} + if i <= facility.num_units then + rate_limits[i] = lim + rate_limits[i].register(unit.unit_ps, "max_burn", rate_limits[i].set_max) + rate_limits[i].register(unit.unit_ps, "burn_limit", rate_limits[i].set_value) - cur_burn.register(unit.unit_ps, "act_burn_rate", cur_burn.update) + cur_burn.register(unit.unit_ps, "act_burn_rate", cur_burn.update) + else + lim.disable() + end end ------------------- -- unit statuses -- ------------------- - local stat_div = Div{parent=proc,width=38,height=19,x=57,y=6} + local stat_div = Div{parent=proc,width=22,height=24,x=57,y=6} - for i = 1, facility.num_units do - local unit = units[i] ---@type ioctl_unit + for i = 1, 4 do + local tag_fg_bg = cpair(colors.gray,colors.white) + local ind_fg_bg = cpair(colors.lightGray,colors.white) + local ind_off = colors.lightGray + + if i <= facility.num_units then + tag_fg_bg = cpair(colors.black,colors.cyan) + ind_fg_bg = bw_fg_bg + ind_off = colors.gray + end local _y = ((i - 1) * 5) + 1 - local unit_tag = Div{parent=stat_div,x=1,y=_y,width=8,height=4,fg_bg=cpair(colors.black,colors.lightBlue)} + local unit_tag = Div{parent=stat_div,x=1,y=_y,width=8,height=4,fg_bg=tag_fg_bg} TextBox{parent=unit_tag,x=2,y=2,text="Unit "..i.." Status",width=7,height=2} - local lights = Div{parent=stat_div,x=9,y=_y,width=12,height=4,fg_bg=bw_fg_bg} - local ready = IndicatorLight{parent=lights,x=2,y=2,label="Ready",colors=cpair(colors.green,colors.gray)} - local degraded = IndicatorLight{parent=lights,x=2,y=3,label="Degraded",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local lights = Div{parent=stat_div,x=9,y=_y,width=14,height=4,fg_bg=ind_fg_bg} + local ready = IndicatorLight{parent=lights,x=2,y=2,label="Ready",colors=cpair(colors.green,ind_off)} + local degraded = IndicatorLight{parent=lights,x=2,y=3,label="Degraded",colors=cpair(colors.red,ind_off),flash=true,period=period.BLINK_250_MS} - ready.register(unit.unit_ps, "U_AutoReady", ready.update) - degraded.register(unit.unit_ps, "U_AutoDegraded", degraded.update) + if i <= facility.num_units then + local unit = units[i] ---@type ioctl_unit + + ready.register(unit.unit_ps, "U_AutoReady", ready.update) + degraded.register(unit.unit_ps, "U_AutoDegraded", degraded.update) + end end ------------------------- @@ -195,7 +230,7 @@ local function new_view(root, x, y) ------------------------- local ctl_opts = { "Monitored Max Burn", "Combined Burn Rate", "Charge Level", "Generation Rate" } - local mode = RadioButton{parent=proc,x=34,y=1,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.purple,colors.black),radio_bg=colors.gray} + local mode = RadioButton{parent=proc,x=34,y=1,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.white,colors.black),radio_bg=colors.purple} mode.register(facility.ps, "process_mode", mode.set_value) @@ -261,6 +296,60 @@ local function new_view(root, x, y) for i = 1, #rate_limits do rate_limits[i].enable() end end end) + + ------------------------------ + -- waste production control -- + ------------------------------ + + local waste_status = Div{parent=proc,width=24,height=4,x=57,y=1,} + + for i = 1, facility.num_units do + local unit = units[i] ---@type ioctl_unit + + TextBox{parent=waste_status,y=i,text="U"..i.." Waste",width=8,height=1} + local a_waste = IndicatorLight{parent=waste_status,x=10,y=i,label="Auto",colors=cpair(colors.white,colors.gray)} + local waste_m = StateIndicator{parent=waste_status,x=17,y=i,states=style.waste.states_abbrv,value=1,min_width=6} + + a_waste.register(unit.unit_ps, "U_AutoWaste", a_waste.update) + waste_m.register(unit.unit_ps, "U_WasteProduct", waste_m.update) + end + + local waste_sel = Div{parent=proc,width=21,height=24,x=81,y=1} + + TextBox{parent=waste_sel,text=" ",width=21,height=1,x=1,y=1,fg_bg=cpair(colors.black,colors.brown)} + TextBox{parent=waste_sel,text="WASTE PRODUCTION",alignment=TEXT_ALIGN.CENTER,width=21,height=1,x=1,y=2,fg_bg=cpair(colors.lightGray,colors.brown)} + + local rect = Rectangle{parent=waste_sel,border=border(1,colors.brown,true),width=21,height=22,x=1,y=3} + local status = StateIndicator{parent=rect,x=2,y=1,states=style.waste.states,value=1,min_width=17} + + status.register(facility.ps, "current_waste_product", status.update) + + local waste_prod = RadioButton{parent=rect,x=2,y=3,options=style.waste.options,callback=process.set_process_waste,radio_colors=cpair(colors.white,colors.black),radio_bg=colors.brown} + local pu_fallback = Checkbox{parent=rect,x=2,y=7,label="Pu Fallback",callback=process.set_pu_fallback,box_fg_bg=cpair(colors.green,colors.black)} + + waste_prod.register(facility.ps, "process_waste_product", waste_prod.set_value) + pu_fallback.register(facility.ps, "process_pu_fallback", pu_fallback.set_value) + + local fb_active = IndicatorLight{parent=rect,x=2,y=9,label="Fallback Active",colors=cpair(colors.white,colors.gray)} + + fb_active.register(facility.ps, "pu_fallback_active", fb_active.update) + + TextBox{parent=rect,x=2,y=11,text="Plutonium Rate",height=1,width=17,fg_bg=style.label} + local pu_rate = DataIndicator{parent=rect,x=2,label="",unit="mB/t",format="%12.2f",value=0,lu_colors=lu_cpair,fg_bg=bw_fg_bg,width=17} + + TextBox{parent=rect,x=2,y=14,text="Polonium Rate",height=1,width=17,fg_bg=style.label} + local po_rate = DataIndicator{parent=rect,x=2,label="",unit="mB/t",format="%12.2f",value=0,lu_colors=lu_cpair,fg_bg=bw_fg_bg,width=17} + + TextBox{parent=rect,x=2,y=17,text="Antimatter Rate",height=1,width=17,fg_bg=style.label} + local am_rate = DataIndicator{parent=rect,x=2,label="",unit="\xb5B/t",format="%12.2f",value=0,lu_colors=lu_cpair,fg_bg=bw_fg_bg,width=17} + + pu_rate.register(facility.ps, "pu_rate", pu_rate.update) + po_rate.register(facility.ps, "po_rate", po_rate.update) + am_rate.register(facility.ps, "am_rate", am_rate.update) + + local sna_count = DataIndicator{parent=rect,x=2,y=20,label="Linked SNAs:",format="%4d",value=0,lu_colors=lu_cpair,width=17} + + sna_count.register(facility.ps, "sna_count", sna_count.update) end return new_view diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 30e2044..7d7bc92 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -33,41 +33,21 @@ local border = core.border local period = core.flasher.PERIOD -local waste_opts = { - { - text = "Auto", - fg_bg = cpair(colors.black, colors.lightGray), - active_fg_bg = cpair(colors.white, colors.gray) - }, - { - text = "Pu", - fg_bg = cpair(colors.black, colors.lightGray), - active_fg_bg = cpair(colors.black, colors.green) - }, - { - text = "Po", - fg_bg = cpair(colors.black, colors.lightGray), - active_fg_bg = cpair(colors.black, colors.cyan) - }, - { - text = "AM", - fg_bg = cpair(colors.black, colors.lightGray), - active_fg_bg = cpair(colors.black, colors.purple) - } -} - -- create a unit view ---@param parent graphics_element parent ---@param id integer local function init(parent, id) local unit = iocontrol.get_db().units[id] ---@type ioctl_unit local f_ps = iocontrol.get_db().facility.ps + + local main = Div{parent=parent,x=1,y=1} + + if unit == nil then return main end + local u_ps = unit.unit_ps local b_ps = unit.boiler_ps_tbl local t_ps = unit.turbine_ps_tbl - local main = Div{parent=parent,x=1,y=1} - TextBox{parent=main,text="Reactor Unit #" .. id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} local bw_fg_bg = cpair(colors.black, colors.white) @@ -398,7 +378,7 @@ local function init(parent, id) local waste_proc = Rectangle{parent=main,border=border(1,colors.brown,true),thin=true,width=33,height=3,x=46,y=49} local waste_div = Div{parent=waste_proc,x=2,y=1,width=31,height=1} - local waste_mode = MultiButton{parent=waste_div,x=1,y=1,options=waste_opts,callback=unit.set_waste,min_width=6} + local waste_mode = MultiButton{parent=waste_div,x=1,y=1,options=style.waste.unit_opts,callback=unit.set_waste,min_width=6} waste_mode.register(u_ps, "U_WasteMode", waste_mode.set_value) diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index a7b3c86..a1b3b5b 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -9,7 +9,7 @@ local iocontrol = require("coordinator.iocontrol") local style = require("coordinator.ui.style") local imatrix = require("coordinator.ui.components.imatrix") -local process_ctl = require("coordinator.ui.components.processctl") +local process_ctl = require("coordinator.ui.components.process_ctl") local unit_overview = require("coordinator.ui.components.unit_overview") local core = require("graphics.core") diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index b78fc91..46faf4a 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -151,7 +151,90 @@ style.imatrix = { { 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.black, colors.gray), + text = "IDLE" + }, + { + color = cpair(colors.black, colors.green), + text = "ACTIVE" + } + } +} + +style.waste = { + -- auto waste processing states + states = { + { + color = cpair(colors.black, colors.green), + text = "PLUTONIUM" + }, + { + color = cpair(colors.black, colors.cyan), + text = "POLONIUM" + }, + { + color = cpair(colors.black, colors.purple), + text = "ANTI MATTER" + } + }, + states_abbrv = { + { + color = cpair(colors.black, colors.green), + text = "Pu" + }, + { + color = cpair(colors.black, colors.cyan), + text = "Po" + }, + { + color = cpair(colors.black, colors.purple), + text = "AM" + } + }, + -- process radio button options + options = { "Plutonium", "Polonium", "Antimatter" }, + -- unit waste selection + unit_opts = { + { + text = "Auto", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.white, colors.gray) + }, + { + text = "Pu", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.black, colors.green) + }, + { + text = "Po", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.black, colors.cyan) + }, + { + text = "AM", + fg_bg = cpair(colors.black, colors.lightGray), + active_fg_bg = cpair(colors.black, colors.purple) + } } } diff --git a/graphics/element.lua b/graphics/element.lua index 2cf211b..4630a53 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -20,6 +20,7 @@ local element = {} ---@alias graphics_args graphics_args_generic ---|waiting_args +---|checkbox_args ---|hazard_button_args ---|multi_button_args ---|push_button_args diff --git a/graphics/elements/controls/checkbox.lua b/graphics/elements/controls/checkbox.lua new file mode 100644 index 0000000..2867d8b --- /dev/null +++ b/graphics/elements/controls/checkbox.lua @@ -0,0 +1,85 @@ +-- Checkbox Graphics Element + +local core = require("graphics.core") +local element = require("graphics.element") + +---@class checkbox_args +---@field label string checkbox text +---@field box_fg_bg cpair colors for checkbox +---@field callback function function to call on press +---@field parent graphics_element +---@field id? string element id +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field fg_bg? cpair foreground/background colors +---@field hidden? boolean true to hide on initial draw + +-- new checkbox control +---@param args checkbox_args +---@return graphics_element element, element_id id +local function checkbox(args) + assert(type(args.label) == "string", "graphics.elements.controls.checkbox: label is a required field") + assert(type(args.box_fg_bg) == "table", "graphics.elements.controls.checkbox: box_fg_bg is a required field") + assert(type(args.callback) == "function", "graphics.elements.controls.checkbox: callback is a required field") + + args.height = 1 + args.width = 3 + string.len(args.label) + + -- create new graphics element base object + local e = element.new(args) + + e.value = false + + -- show the button state + local function draw() + e.window.setCursorPos(1, 1) + + if e.value then + -- show as selected + e.window.setTextColor(args.box_fg_bg.bkg) + e.window.setBackgroundColor(args.box_fg_bg.fgd) + e.window.write("\x88") + e.window.setTextColor(args.box_fg_bg.fgd) + e.window.setBackgroundColor(e.fg_bg.bkg) + e.window.write("\x95") + else + -- show as unselected + e.window.setTextColor(e.fg_bg.bkg) + e.window.setBackgroundColor(args.box_fg_bg.bkg) + e.window.write("\x88") + e.window.setTextColor(args.box_fg_bg.bkg) + e.window.setBackgroundColor(e.fg_bg.bkg) + e.window.write("\x95") + end + end + + -- handle mouse interaction + ---@param event mouse_interaction mouse event + function e.handle_mouse(event) + if e.enabled and core.events.was_clicked(event.type) then + e.value = not e.value + draw() + args.callback(e.value) + end + end + + -- set the value + ---@param val integer new value + function e.set_value(val) + e.value = val + draw() + end + + -- write label text + e.window.setCursorPos(3, 1) + e.window.setTextColor(e.fg_bg.fgd) + e.window.setBackgroundColor(e.fg_bg.bkg) + e.window.write(args.label) + + -- initial draw + draw() + + return e.complete() +end + +return checkbox diff --git a/install_manifest.json b/install_manifest.json index b93e2b4..99abbb5 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"installer": "v1.5a", "bootloader": "0.2", "comms": "2.1.0", "graphics": "1.0.0", "lockbox": "1.0", "reactor-plc": "v1.5.0", "rtu": "v1.4.0", "supervisor": "v0.18.0", "coordinator": "v0.17.1", "pocket": "alpha-v0.5.1"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/tcd.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua", "scada-common/network.lua"], "graphics": ["graphics/element.lua", "graphics/events.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/listbox.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/multipane.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/led.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/ledpair.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/indicators/ledrgb.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/controls/tabbar.lua", "graphics/elements/controls/sidebar.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/digest/md5.lua", "lockbox/mac/hmac.lua"], "reactor-plc": ["reactor-plc/renderer.lua", "reactor-plc/threads.lua", "reactor-plc/databus.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua", "reactor-plc/panel/front_panel.lua", "reactor-plc/panel/style.lua"], "rtu": ["rtu/renderer.lua", "rtu/threads.lua", "rtu/rtu.lua", "rtu/databus.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/panel/front_panel.lua", "rtu/panel/style.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/renderer.lua", "supervisor/databus.lua", "supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/panel/pgi.lua", "supervisor/panel/front_panel.lua", "supervisor/panel/style.lua", "supervisor/panel/components/rtu_entry.lua", "supervisor/panel/components/pdg_entry.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/pocket.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/turbine.lua", "coordinator/session/pocket.lua", "coordinator/session/apisessions.lua"], "pocket": ["pocket/pocket.lua", "pocket/renderer.lua", "pocket/config.lua", "pocket/coreio.lua", "pocket/startup.lua", "pocket/ui/main.lua", "pocket/ui/style.lua", "pocket/ui/components/conn_waiting.lua", "pocket/ui/pages/turbine_page.lua", "pocket/ui/pages/reactor_page.lua", "pocket/ui/pages/home_page.lua", "pocket/ui/pages/unit_page.lua", "pocket/ui/pages/boiler_page.lua"]}, "depends": {"reactor-plc": ["system", "common", "graphics", "lockbox"], "rtu": ["system", "common", "graphics", "lockbox"], "supervisor": ["system", "common", "graphics", "lockbox"], "coordinator": ["system", "common", "graphics", "lockbox"], "pocket": ["system", "common", "graphics", "lockbox"]}, "sizes": {"manifest": 5568, "system": 1991, "common": 97109, "graphics": 144556, "lockbox": 34900, "reactor-plc": 97595, "rtu": 102247, "supervisor": 315425, "coordinator": 198188, "pocket": 37633}} \ No newline at end of file +{"versions": {"installer": "v1.5a", "bootloader": "0.2", "comms": "2.1.1", "graphics": "1.0.0", "lockbox": "1.0", "reactor-plc": "v1.5.0", "rtu": "v1.4.0", "supervisor": "v0.19.0", "coordinator": "v0.18.0", "pocket": "alpha-v0.5.1"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/tcd.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua", "scada-common/network.lua"], "graphics": ["graphics/element.lua", "graphics/events.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/listbox.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/multipane.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/led.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/ledpair.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/indicators/ledrgb.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/controls/tabbar.lua", "graphics/elements/controls/checkbox.lua", "graphics/elements/controls/sidebar.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/digest/md5.lua", "lockbox/mac/hmac.lua"], "reactor-plc": ["reactor-plc/renderer.lua", "reactor-plc/threads.lua", "reactor-plc/databus.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua", "reactor-plc/panel/front_panel.lua", "reactor-plc/panel/style.lua"], "rtu": ["rtu/renderer.lua", "rtu/threads.lua", "rtu/rtu.lua", "rtu/databus.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/panel/front_panel.lua", "rtu/panel/style.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/renderer.lua", "supervisor/databus.lua", "supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/panel/pgi.lua", "supervisor/panel/front_panel.lua", "supervisor/panel/style.lua", "supervisor/panel/components/rtu_entry.lua", "supervisor/panel/components/pdg_entry.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/pocket.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/process_ctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/turbine.lua", "coordinator/session/pocket.lua", "coordinator/session/apisessions.lua"], "pocket": ["pocket/pocket.lua", "pocket/renderer.lua", "pocket/config.lua", "pocket/coreio.lua", "pocket/startup.lua", "pocket/ui/main.lua", "pocket/ui/style.lua", "pocket/ui/components/conn_waiting.lua", "pocket/ui/pages/turbine_page.lua", "pocket/ui/pages/reactor_page.lua", "pocket/ui/pages/home_page.lua", "pocket/ui/pages/unit_page.lua", "pocket/ui/pages/boiler_page.lua"]}, "depends": {"reactor-plc": ["system", "common", "graphics", "lockbox"], "rtu": ["system", "common", "graphics", "lockbox"], "supervisor": ["system", "common", "graphics", "lockbox"], "coordinator": ["system", "common", "graphics", "lockbox"], "pocket": ["system", "common", "graphics", "lockbox"]}, "sizes": {"manifest": 5612, "system": 1991, "common": 97476, "graphics": 147195, "lockbox": 34900, "reactor-plc": 97595, "rtu": 102247, "supervisor": 321729, "coordinator": 212109, "pocket": 37633}} \ No newline at end of file diff --git a/scada-common/comms.lua b/scada-common/comms.lua index 88e8631..a9fa5a5 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -14,7 +14,7 @@ local max_distance = nil ---@type number|nil maximum acceptable t ---@class comms local comms = {} -comms.version = "2.1.0" +comms.version = "2.1.1" ---@enum PROTOCOL local PROTOCOL = { @@ -92,9 +92,11 @@ local PLC_AUTO_ACK = { ---@enum FAC_COMMAND local FAC_COMMAND = { SCRAM_ALL = 0, -- SCRAM all reactors - STOP = 1, -- stop automatic control - START = 2, -- start automatic control - ACK_ALL_ALARMS = 3 -- acknowledge all alarms on all units + STOP = 1, -- stop automatic process control + START = 2, -- start automatic process control + ACK_ALL_ALARMS = 3, -- acknowledge all alarms on all units + SET_WASTE_MODE = 4, -- set automatic waste processing mode + SET_PU_FB = 5 -- set plutonium fallback mode } ---@enum UNIT_COMMAND diff --git a/scada-common/types.lua b/scada-common/types.lua index 21429b5..9e68632 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -158,13 +158,26 @@ types.PROCESS_NAMES = { ---@enum WASTE_MODE types.WASTE_MODE = { AUTO = 1, - PLUTONIUM = 2, - POLONIUM = 3, - ANTI_MATTER = 4 + MANUAL_PLUTONIUM = 2, + MANUAL_POLONIUM = 3, + MANUAL_ANTI_MATTER = 4 } types.WASTE_MODE_NAMES = { "AUTO", + "MANUAL_PLUTONIUM", + "MANUAL_POLONIUM", + "MANUAL_ANTI_MATTER" +} + +---@enum WASTE_PRODUCT +types.WASTE_PRODUCT = { + PLUTONIUM = 1, + POLONIUM = 2, + ANTI_MATTER = 3 +} + +types.WASTE_PRODUCT_NAMES = { "PLUTONIUM", "POLONIUM", "ANTI_MATTER" diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 5864ea5..52870fa 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -11,6 +11,9 @@ local rsctl = require("supervisor.session.rsctl") local PROCESS = types.PROCESS local PROCESS_NAMES = types.PROCESS_NAMES local PRIO = types.ALARM_PRIORITY +local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE +local WASTE = types.WASTE_PRODUCT +local WASTE_MODE = types.WASTE_MODE local IO = rsio.IO @@ -61,6 +64,7 @@ function facility.new(num_reactors, cooling_conf) rtu_conn_count = 0, redstone = {}, induction = {}, + sps = {}, envd = {}, -- redstone I/O control io_ctl = nil, ---@type rs_controller @@ -99,6 +103,10 @@ function facility.new(num_reactors, cooling_conf) last_update = 0, last_error = 0.0, last_time = 0.0, + -- waste processing + waste_product = WASTE.PLUTONIUM, + current_waste_product = WASTE.PLUTONIUM, + pu_fallback = false, -- statistics im_stat_init = false, avg_charge = util.mov_avg(3, 0.0), @@ -205,12 +213,18 @@ function facility.new(num_reactors, cooling_conf) table.insert(self.redstone, rs_unit) end - -- link an imatrix RTU session + -- link an induction matrix RTU session ---@param imatrix unit_session function public.add_imatrix(imatrix) table.insert(self.induction, imatrix) end + -- link an SPS RTU session + ---@param sps unit_session + function public.add_sps(sps) + table.insert(self.sps, sps) + end + -- link an environment detector RTU session ---@param envd unit_session function public.add_envd(envd) @@ -222,6 +236,7 @@ function facility.new(num_reactors, cooling_conf) function public.purge_rtu_devices(session) util.filter_table(self.redstone, function (s) return s.get_session_id() ~= session end) util.filter_table(self.induction, function (s) return s.get_session_id() ~= session end) + util.filter_table(self.sps, function (s) return s.get_session_id() ~= session end) util.filter_table(self.envd, function (s) return s.get_session_id() ~= session end) end @@ -238,6 +253,7 @@ function facility.new(num_reactors, cooling_conf) -- unlink RTU unit sessions if they are closed _unlink_disconnected_units(self.redstone) _unlink_disconnected_units(self.induction) + _unlink_disconnected_units(self.sps) _unlink_disconnected_units(self.envd) -- current state for process control @@ -277,6 +293,8 @@ function facility.new(num_reactors, cooling_conf) -- Run Process Control -- ------------------------- + --#region Process Control + local avg_charge = self.avg_charge.compute() local avg_inflow = self.avg_inflow.compute() @@ -542,10 +560,14 @@ function facility.new(num_reactors, cooling_conf) next_mode = PROCESS.INACTIVE end + --#endregion + ------------------------------ -- Evaluate Automatic SCRAM -- ------------------------------ + --#region Automatic SCRAM + local astatus = self.ascram_status if self.induction[1] ~= nil then @@ -659,6 +681,8 @@ function facility.new(num_reactors, cooling_conf) end end + --#endregion + -- update last mode and set next mode self.last_mode = self.mode self.mode = next_mode @@ -692,12 +716,33 @@ function facility.new(num_reactors, cooling_conf) self.io_ctl.digital_write(IO.F_ALARM, has_alarm) end + + ----------------------------- + -- Update Waste Processing -- + ----------------------------- + + local insufficent_po_rate = false + for i = 1, #self.units do + local u = self.units[i] ---@type reactor_unit + if u.get_control_inf().waste_mode == WASTE_MODE.AUTO then + if (u.get_sna_rate() * 10.0) < u.get_burn_rate() then + insufficent_po_rate = true + break + end + end + end + + if self.waste_product == WASTE.PLUTONIUM or (self.pu_fallback and insufficent_po_rate) then + self.current_waste_product = WASTE.PLUTONIUM + else self.current_waste_product = self.waste_product end end - -- call the update function of all units in the facility + -- call the update function of all units in the facility
+ -- additionally sets the requested auto waste mode if applicable function public.update_units() for i = 1, #self.units do local u = self.units[i] ---@type reactor_unit + u.auto_set_waste(self.current_waste_product) u.update() end end @@ -721,15 +766,15 @@ function facility.new(num_reactors, cooling_conf) end -- stop auto control - function public.auto_stop() - self.mode = PROCESS.INACTIVE - end + function public.auto_stop() self.mode = PROCESS.INACTIVE end -- set automatic control configuration and start the process ---@param config coord_auto_config configuration ---@return table response ready state (successfully started) and current configuration (after updating) function public.auto_start(config) - local ready = false + local charge_scaler = 1000000 -- convert MFE to FE + local gen_scaler = 1000 -- convert kFE to FE + local ready = false -- load up current limits local limits = {} @@ -749,11 +794,11 @@ function facility.new(num_reactors, cooling_conf) end if (type(config.charge_target) == "number") and config.charge_target >= 0 then - self.charge_setpoint = config.charge_target * 1000000 -- convert MFE to FE + self.charge_setpoint = config.charge_target * charge_scaler end if (type(config.gen_target) == "number") and config.gen_target >= 0 then - self.gen_rate_setpoint = config.gen_target * 1000 -- convert kFE to FE + self.gen_rate_setpoint = config.gen_target * gen_scaler end if (type(config.limits) == "table") and (#config.limits == num_reactors) then @@ -782,7 +827,14 @@ function facility.new(num_reactors, cooling_conf) if ready then self.mode = self.mode_set end end - return { ready, self.mode_set, self.burn_target, self.charge_setpoint, self.gen_rate_setpoint, limits } + return { + ready, + self.mode_set, + self.burn_target, + self.charge_setpoint / charge_scaler, + self.gen_rate_setpoint / gen_scaler, + limits + } end -- SETTINGS -- @@ -807,15 +859,35 @@ function facility.new(num_reactors, cooling_conf) end end + -- set waste production + ---@param product WASTE_PRODUCT target product + ---@return WASTE_PRODUCT product newly set value, if valid + function public.set_waste_product(product) + if product == WASTE.PLUTONIUM or product == WASTE.POLONIUM or product == WASTE.ANTI_MATTER then + self.waste_product = product + end + + return self.waste_product + end + + -- enable/disable plutonium fallback + ---@param enabled boolean requested state + ---@return boolean enabled newly set value + function public.set_pu_fallback(enabled) + self.pu_fallback = enabled == true + return self.pu_fallback + end + -- READ STATES/PROPERTIES -- - -- get build properties of all machines + -- get build properties of all facility devices ---@nodiscard - ---@param inc_imatrix boolean? true/nil to include induction matrix build, false to exclude - function public.get_build(inc_imatrix) + ---@param type RTU_UNIT_TYPE? type or nil to include only a particular unit type, or to include all if nil + function public.get_build(type) + local all = type == nil local build = {} - if inc_imatrix ~= false then + if all or type == RTU_UNIT_TYPE.IMATRIX then build.induction = {} for i = 1, #self.induction do local matrix = self.induction[i] ---@type unit_session @@ -823,6 +895,14 @@ function facility.new(num_reactors, cooling_conf) end end + if all or type == RTU_UNIT_TYPE.SPS then + build.sps = {} + for i = 1, #self.sps do + local sps = self.sps[i] ---@type unit_session + build.sps[sps.get_device_idx()] = { sps.get_db().formed, sps.get_db().build } + end + end + return build end @@ -844,7 +924,9 @@ function facility.new(num_reactors, cooling_conf) astat.gen_fault or self.mode == PROCESS.GEN_RATE_FAULT_IDLE, self.status_text[1], self.status_text[2], - self.group_map + self.group_map, + self.current_waste_product, + (self.current_waste_product == WASTE.PLUTONIUM) and (self.waste_product ~= WASTE.PLUTONIUM) } end @@ -875,6 +957,18 @@ function facility.new(num_reactors, cooling_conf) } end + -- status of sps + status.sps = {} + for i = 1, #self.sps do + local sps = self.sps[i] ---@type unit_session + status.sps[sps.get_device_idx()] = { + sps.is_faulted(), + sps.get_db().formed, + sps.get_db().state, + sps.get_db().tanks + } + end + -- radiation monitors (environment detectors) status.rad_mon = {} for i = 1, #self.envd do diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 1b75078..eb8935f 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -258,6 +258,18 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil elseif cmd == FAC_COMMAND.ACK_ALL_ALARMS then facility.ack_all() _send(SCADA_CRDN_TYPE.FAC_CMD, { cmd, true }) + elseif cmd == FAC_COMMAND.SET_WASTE_MODE then + if pkt.length == 2 then + _send(SCADA_CRDN_TYPE.FAC_CMD, { cmd, facility.set_waste_product(pkt.data[2]) }) + else + log.debug(log_header .. "CRDN set waste mode packet length mismatch") + end + elseif cmd == FAC_COMMAND.SET_PU_FB then + if pkt.length == 2 then + _send(SCADA_CRDN_TYPE.FAC_CMD, { cmd, facility.set_pu_fallback(pkt.data[2]) }) + else + log.debug(log_header .. "CRDN set pu fallback packet length mismatch") + end else log.debug(log_header .. "CRDN facility command unknown") end @@ -294,9 +306,9 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil end elseif cmd == UNIT_COMMAND.SET_WASTE then if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] > 0) and (pkt.data[3] <= 4) then - unit.set_waste(pkt.data[3]) + unit.set_waste_mode(pkt.data[3]) else - log.debug(log_header .. "CRDN unit command set waste missing option") + log.debug(log_header .. "CRDN unit command set waste missing/invalid option") end elseif cmd == UNIT_COMMAND.ACK_ALL_ALARMS then unit.ack_all() @@ -417,7 +429,7 @@ function coordinator.new_session(id, s_addr, in_queue, out_queue, timeout, facil self.retry_times.f_builds_packet = util.time() + PARTIAL_RETRY_PERIOD self.acks.fac_builds = false - _send(SCADA_CRDN_TYPE.FAC_BUILDS, { facility.get_build(cmd.val.type == RTU_UNIT_TYPE.IMATRIX) }) + _send(SCADA_CRDN_TYPE.FAC_BUILDS, { facility.get_build(cmd.val.type) }) end else log.error(log_header .. "unsupported data command received in in_queue (this is a bug)", true) diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 69ba796..ce5e08d 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -138,6 +138,10 @@ function rtu.new_session(id, s_addr, in_queue, out_queue, timeout, advertisement -- turbine unit = svrs_turbinev.new(id, i, unit_advert, self.modbus_q) if type(unit) ~= "nil" then target_unit.add_turbine(unit) end + elseif u_type == RTU_UNIT_TYPE.SNA then + -- solar neutron activator + unit = svrs_sna.new(id, i, unit_advert, self.modbus_q) + if type(unit) ~= "nil" then target_unit.add_sna(unit) end elseif u_type == RTU_UNIT_TYPE.ENV_DETECTOR then -- environment detector unit = svrs_envd.new(id, i, unit_advert, self.modbus_q) @@ -161,9 +165,7 @@ function rtu.new_session(id, s_addr, in_queue, out_queue, timeout, advertisement elseif u_type == RTU_UNIT_TYPE.SPS then -- super-critical phase shifter unit = svrs_sps.new(id, i, unit_advert, self.modbus_q) - elseif u_type == RTU_UNIT_TYPE.SNA then - -- solar neutron activator - unit = svrs_sna.new(id, i, unit_advert, self.modbus_q) + if type(unit) ~= "nil" then facility.add_sps(unit) end elseif u_type == RTU_UNIT_TYPE.ENV_DETECTOR then -- environment detector unit = svrs_envd.new(id, i, unit_advert, self.modbus_q) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 3dc850e..34f295b 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v0.18.0" +local SUPERVISOR_VERSION = "v0.19.0" local println = util.println local println_ts = util.println_ts diff --git a/supervisor/unit.lua b/supervisor/unit.lua index 0d7246a..beaf495 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -12,6 +12,7 @@ local rsctl = require("supervisor.session.rsctl") local unit = {} local WASTE_MODE = types.WASTE_MODE +local WASTE = types.WASTE_PRODUCT local ALARM = types.ALARM local PRIO = types.ALARM_PRIORITY local ALARM_STATE = types.ALARM_STATE @@ -71,7 +72,9 @@ function unit.new(reactor_id, num_boilers, num_turbines) redstone = {}, boilers = {}, turbines = {}, + sna = {}, envd = {}, + sna_prod_rate = 0, -- redstone control io_ctl = nil, ---@type rs_controller valves = {}, ---@type unit_valves @@ -89,7 +92,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) damage_start = 0, damage_last = 0, damage_est_last = 0, - waste_mode = WASTE_MODE.AUTO, + waste_product = WASTE.PLUTONIUM, ---@type WASTE_PRODUCT status_text = { "UNKNOWN", "awaiting connection..." }, -- logic for alarms had_reactor = false, @@ -221,7 +224,8 @@ function unit.new(reactor_id, num_boilers, num_turbines) degraded = false, blade_count = 0, br100 = 0, - lim_br100 = 0 + lim_br100 = 0, + waste_mode = WASTE_MODE.AUTO ---@type WASTE_MODE } } } @@ -341,6 +345,32 @@ function unit.new(reactor_id, num_boilers, num_turbines) emer_cool = emer_cool } + -- route reactor waste for a given waste product + ---@param product WASTE_PRODUCT waste product to route valves for + local function _set_waste_valves(product) + self.waste_product = product + + if product == WASTE.PLUTONIUM then + -- route through plutonium generation + waste_pu.open() + waste_sna.close() + waste_po.close() + waste_sps.close() + elseif product == WASTE.POLONIUM then + -- route through polonium generation into pellets + waste_pu.close() + waste_sna.open() + waste_po.open() + waste_sps.close() + elseif product == WASTE.ANTI_MATTER then + -- route through polonium generation into SPS + waste_pu.close() + waste_sna.open() + waste_po.close() + waste_sps.open() + end + end + --#endregion -- unlink disconnected units @@ -378,7 +408,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) table.insert(self.redstone, rs_unit) -- send or re-send waste settings - public.set_waste(self.waste_mode) + _set_waste_valves(self.waste_product) end -- link a turbine RTU session @@ -415,6 +445,12 @@ function unit.new(reactor_id, num_boilers, num_turbines) end end + -- link a solar neutron activator RTU session + ---@param sna unit_session + function public.add_sna(sna) + table.insert(self.sna, sna) + end + -- link an environment detector RTU session ---@param envd unit_session function public.add_envd(envd) @@ -427,6 +463,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) util.filter_table(self.redstone, function (s) return s.get_session_id() ~= session end) util.filter_table(self.boilers, function (s) return s.get_session_id() ~= session end) util.filter_table(self.turbines, function (s) return s.get_session_id() ~= session end) + util.filter_table(self.sna, function (s) return s.get_session_id() ~= session end) util.filter_table(self.envd, function (s) return s.get_session_id() ~= session end) end @@ -448,6 +485,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) _unlink_disconnected_units(self.redstone) _unlink_disconnected_units(self.boilers) _unlink_disconnected_units(self.turbines) + _unlink_disconnected_units(self.sna) _unlink_disconnected_units(self.envd) -- update degraded state for auto control @@ -577,6 +615,15 @@ function unit.new(reactor_id, num_boilers, num_turbines) end end + -- set automatic waste product if mode is set to auto + ---@param product WASTE_PRODUCT waste product to generate + function public.auto_set_waste(product) + if self.db.control.waste_mode == WASTE_MODE.AUTO then + self.waste_product = product + _set_waste_valves(product) + end + end + --#endregion -- OPERATIONS -- @@ -621,34 +668,18 @@ function unit.new(reactor_id, num_boilers, num_turbines) end end - -- route reactor waste - ---@param mode WASTE_MODE waste handling mode - function public.set_waste(mode) - if mode == WASTE_MODE.AUTO then - ---@todo automatic waste routing - self.waste_mode = mode - elseif mode == WASTE_MODE.PLUTONIUM then - -- route through plutonium generation - self.waste_mode = mode - waste_pu.open() - waste_sna.close() - waste_po.close() - waste_sps.close() - elseif mode == WASTE_MODE.POLONIUM then - -- route through polonium generation into pellets - self.waste_mode = mode - waste_pu.close() - waste_sna.open() - waste_po.open() - waste_sps.close() - elseif mode == WASTE_MODE.ANTI_MATTER then - -- route through polonium generation into SPS - self.waste_mode = mode - waste_pu.close() - waste_sna.open() - waste_po.close() - waste_sps.open() - else + -- set waste processing mode + ---@param mode WASTE_MODE processing mode + function public.set_waste_mode(mode) + self.db.control.waste_mode = mode + + if mode == WASTE_MODE.MANUAL_PLUTONIUM then + _set_waste_valves(WASTE.PLUTONIUM) + elseif mode == WASTE_MODE.MANUAL_POLONIUM then + _set_waste_valves(WASTE.POLONIUM) + elseif mode == WASTE_MODE.MANUAL_ANTI_MATTER then + _set_waste_valves(WASTE.ANTI_MATTER) + elseif mode > WASTE_MODE.MANUAL_ANTI_MATTER then log.debug(util.c("invalid waste mode setting ", mode)) end end @@ -730,6 +761,14 @@ function unit.new(reactor_id, num_boilers, num_turbines) return status end + -- get the current burn rate (actual rate) + ---@nodiscard + function public.get_burn_rate() + local rate = 0 + if self.plc_i ~= nil then rate = self.plc_i.get_status().act_burn_rate end + return rate or 0 + end + -- get RTU statuses ---@nodiscard function public.get_rtu_statuses() @@ -750,7 +789,7 @@ function unit.new(reactor_id, num_boilers, num_turbines) -- status of turbines (including tanks) status.turbines = {} for i = 1, #self.turbines do - local turbine = self.turbines[i] ---@type unit_session + local turbine = self.turbines[i] ---@type unit_session status.turbines[turbine.get_device_idx()] = { turbine.is_faulted(), turbine.get_db().formed, @@ -759,10 +798,13 @@ function unit.new(reactor_id, num_boilers, num_turbines) } end + -- basic SNA statistical information, don't send everything, it's not necessary + status.sna = { #self.sna, public.get_sna_rate() } + -- radiation monitors (environment detectors) status.rad_mon = {} for i = 1, #self.envd do - local envd = self.envd[i] ---@type unit_session + local envd = self.envd[i] ---@type unit_session status.rad_mon[envd.get_device_idx()] = { envd.is_faulted(), envd.get_db().radiation @@ -772,6 +814,20 @@ function unit.new(reactor_id, num_boilers, num_turbines) return status end + -- get the current total [max] production rate is + ---@nodiscard + ---@return number total_avail_rate + function public.get_sna_rate() + local total_avail_rate = 0 + + for i = 1, #self.sna do + local db = self.sna[i].get_db() ---@type sna_session_db + total_avail_rate = total_avail_rate + db.state.production_rate + end + + return total_avail_rate + end + -- get the annunciator status ---@nodiscard function public.get_annunciator() return self.db.annunciator end @@ -787,7 +843,14 @@ function unit.new(reactor_id, num_boilers, num_turbines) -- get unit state ---@nodiscard function public.get_state() - return { self.status_text[1], self.status_text[2], self.waste_mode, self.db.control.ready, self.db.control.degraded } + return { + self.status_text[1], + self.status_text[2], + self.db.control.ready, + self.db.control.degraded, + self.db.control.waste_mode, + self.waste_product + } end -- get the reactor ID