From 2e78aa895db9b7f23afec1bc86d9f8076092a68d Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 2 Feb 2023 22:58:51 -0500 Subject: [PATCH] #101 #102 burn rate process mode functional --- coordinator/iocontrol.lua | 22 +++++---- coordinator/startup.lua | 2 +- coordinator/ui/components/processctl.lua | 51 +++++++++++++++++++++ coordinator/ui/components/unit_detail.lua | 17 +++---- coordinator/ui/components/unit_overview.lua | 2 - coordinator/ui/layout/main_view.lua | 11 +++-- supervisor/session/facility.lua | 25 +++++++++- supervisor/session/plc.lua | 1 + supervisor/session/unit.lua | 17 +++++-- supervisor/session/unitlogic.lua | 31 +++++++++++++ supervisor/startup.lua | 2 +- 11 files changed, 148 insertions(+), 33 deletions(-) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index da12a50..ecf677d 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -23,9 +23,11 @@ local io = {} function iocontrol.init(conf, comms) ---@class ioctl_facility io.facility = { + auto_ready = false, auto_active = false, auto_ramping = false, auto_scram = false, + ---@todo not currently used or set auto_scram_cause = "ok", ---@type auto_scram_cause num_units = conf.num_units, ---@type integer @@ -271,19 +273,21 @@ function iocontrol.update_facility_status(status) local ctl_status = status[1] if type(ctl_status) == "table" then - fac.auto_active = ctl_status[1] > 0 - fac.auto_ramping = ctl_status[2] - fac.auto_scram = ctl_status[3] - fac.status_line_1 = ctl_status[4] - fac.status_line_2 = ctl_status[5] + fac.auto_ready = ctl_status[1] + fac.auto_active = ctl_status[2] > 0 + fac.auto_ramping = ctl_status[3] + fac.auto_scram = ctl_status[4] + fac.status_line_1 = ctl_status[5] + fac.status_line_2 = ctl_status[6] + 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_scram", fac.auto_scram) fac.ps.publish("status_line_1", fac.status_line_1) fac.ps.publish("status_line_2", fac.status_line_2) - local group_map = ctl_status[6] + local group_map = ctl_status[7] if (type(group_map) == "table") and (#group_map == fac.num_units) then local names = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" } @@ -634,10 +638,12 @@ function iocontrol.update_unit_statuses(statuses) local unit_state = status[5] if type(unit_state) == "table" then - if #unit_state == 3 then + if #unit_state == 5 then unit.reactor_ps.publish("U_StatusLine1", unit_state[1]) unit.reactor_ps.publish("U_StatusLine2", unit_state[2]) - unit.reactor_ps.publish("U_WasteMode", unit_state[3]) + unit.reactor_ps.publish("U_WasteMode", unit_state[3]) + unit.reactor_ps.publish("U_AutoReady", unit_state[4]) + unit.reactor_ps.publish("U_AutoDegraded", unit_state[5]) else log.debug(log_header .. "unit state length mismatch") end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index c1a8232..0546344 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "beta-v0.8.11" +local COORDINATOR_VERSION = "beta-v0.8.12" local print = util.print local println = util.println diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index 91f888d..9687ccf 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -30,6 +30,8 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN local cpair = core.graphics.cpair local border = core.graphics.border +local period = core.flasher.PERIOD + -- new process control view ---@param root graphics_element parent ---@param x integer top left x @@ -48,6 +50,22 @@ local function new_view(root, x, y) facility.scram_ack = scram.on_response + local auto_act = IndicatorLight{parent=main,y=5,label="Auto Active",colors=cpair(colors.green,colors.gray)} + local auto_ramp = IndicatorLight{parent=main,label="Auto Ramping",colors=cpair(colors.white,colors.gray),flash=true,period=period.BLINK_250_MS} + local auto_scram = IndicatorLight{parent=main,label="Auto SCRAM",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + + facility.ps.subscribe("auto_active", auto_act.update) + facility.ps.subscribe("auto_ramping", auto_ramp.update) + facility.ps.subscribe("auto_scram", auto_scram.update) + + main.line_break() + + local _ = IndicatorLight{parent=main,label="Unit Off-line",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_1000_MS} + local _ = IndicatorLight{parent=main,label="Unit RPS Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local _ = IndicatorLight{parent=main,label="Unit Critical Alarm",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + local _ = IndicatorLight{parent=main,label="High Charge Level",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} + + --------------------- -- process control -- --------------------- @@ -134,6 +152,9 @@ local function new_view(root, x, y) local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=31,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=bw_fg_bg} local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data",width=31,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} + facility.ps.subscribe("status_line_1", stat_line_1.set_value) + facility.ps.subscribe("status_line_2", stat_line_2.set_value) + local auto_controls = Div{parent=proc,x=1,y=20,width=31,height=5,fg_bg=cpair(colors.gray,colors.white)} -- save the automatic process control configuration without starting @@ -160,6 +181,36 @@ local function new_view(root, x, y) function facility.save_cfg_ack(ack) tcd.dispatch(0.2, function () save.on_response(ack) end) end + + facility.ps.subscribe("auto_ready", function (ready) + if ready and (not facility.auto_active) then start.enable() else start.disable() end + end) + + facility.ps.subscribe("auto_active", function (active) + if active then + b_target.disable() + c_target.disable() + g_target.disable() + + mode.disable() + start.disable() + + for i = 1, #rate_limits do + rate_limits[i].disable() + end + else + b_target.enable() + c_target.enable() + g_target.enable() + + mode.enable() + if facility.auto_ready then start.enable() end + + for i = 1, #rate_limits do + rate_limits[i].enable() + end + end + end) end return new_view diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index c3950e9..cb87894 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -474,17 +474,14 @@ local function init(parent, id) auto_div.line_break() - local a_act = IndicatorLight{parent=auto_div,label="Active",x=2,colors=cpair(colors.green,colors.gray)} - local a_stb = IndicatorLight{parent=auto_div,label="Standby",x=2,colors=cpair(colors.white,colors.gray)} + local a_rdy = IndicatorLight{parent=auto_div,label="Ready",x=2,colors=cpair(colors.green,colors.gray)} + local a_stb = IndicatorLight{parent=auto_div,label="Standby",x=2,colors=cpair(colors.white,colors.gray),flash=true,period=period.BLINK_1000_MS} + r_ps.subscribe("U_AutoReady", a_rdy.update) + + -- update standby indicator r_ps.subscribe("status", function (active) - if unit.annunciator.AutoControl then - a_act.update(active) - a_stb.update(not active) - else - a_act.update(false) - a_stb.update(false) - end + a_stb.update(unit.annunciator.AutoControl and (not active)) end) -- enable and disable controls based on auto control state (start button is handled separately) @@ -495,13 +492,11 @@ local function init(parent, id) burn_rate.disable() set_burn_btn.disable() set_grp_btn.disable() - a_act.update(unit.reactor_data.mek_status.status == true) a_stb.update(unit.reactor_data.mek_status.status == false) else burn_rate.enable() set_burn_btn.enable() set_grp_btn.enable() - a_act.update(false) a_stb.update(false) end end) diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index 56166ae..b28b43f 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -16,8 +16,6 @@ local TextBox = require("graphics.elements.textbox") local TEXT_ALIGN = core.graphics.TEXT_ALIGN -local cpair = core.graphics.cpair -local border = core.graphics.border local pipe = core.graphics.pipe -- make a new unit overview window diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index 59ac653..f1b68ad 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -49,26 +49,31 @@ local function init(monitor) local uo_1, uo_2, uo_3, uo_4 ---@type graphics_element local cnc_y_start = 3 + local row_1_height = 0 -- unit overviews if facility.num_units >= 1 then uo_1 = unit_overview(main, 2, 3, units[1]) - cnc_y_start = cnc_y_start + uo_1.height() + 1 + row_1_height = uo_1.height() end if facility.num_units >= 2 then uo_2 = unit_overview(main, 84, 3, units[2]) + row_1_height = math.max(row_1_height, uo_2.height()) end + cnc_y_start = cnc_y_start + row_1_height + 1 + if facility.num_units >= 3 then -- base offset 3, spacing 1, max height of units 1 and 2 - local row_2_offset = 3 + 1 + math.max(uo_1.height(), uo_2.height()) + local row_2_offset = cnc_y_start uo_3 = unit_overview(main, 2, row_2_offset, units[3]) - cnc_y_start = cnc_y_start + uo_3.height() + 1 + cnc_y_start = row_2_offset + uo_3.height() + 1 if facility.num_units == 4 then uo_4 = unit_overview(main, 84, row_2_offset, units[4]) + cnc_y_start = math.max(cnc_y_start, row_2_offset + uo_4.height() + 1) end end diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index 7486346..ebd417a 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -40,8 +40,9 @@ function facility.new(num_reactors, cooling_conf) units = {}, induction = {}, redstone = {}, - status_text = { "IDLE", "control inactive" }, + status_text = { "START UP", "initializing..." }, -- process control + units_ready = false, mode = PROCESS.INACTIVE, last_mode = PROCESS.INACTIVE, mode_set = PROCESS.SIMPLE, @@ -234,7 +235,10 @@ function facility.new(num_reactors, cooling_conf) log.debug(util.c("FAC: starting auto control: chg_conv = ", self.charge_conversion, ", blade_count = ", blade_count, ", max_burn = ", self.max_burn_combined)) elseif self.mode == PROCESS.INACTIVE then for i = 1, #self.prio_defs do + -- SCRAM reactors and disengage auto control + -- use manual SCRAM since inactive was requested, and automatic SCRAM trips an alarm for _, u in pairs(self.prio_defs[i]) do + u.scram() u.a_disengage() end end @@ -249,7 +253,21 @@ function facility.new(num_reactors, cooling_conf) self.initial_ramp = false end - if self.mode == PROCESS.SIMPLE then + if self.mode == PROCESS.INACTIVE then + -- check if we are ready to start when that time comes + self.units_ready = true + for i = 1, #self.prio_defs do + for _, u in pairs(self.prio_defs[i]) do + self.units_ready = self.units_ready and u.get_control_inf().ready + end + end + + if self.units_ready then + self.status_text = { "IDLE", "control disengaged" } + else + self.status_text = { "NOT READY", "assigned units not ready" } + end + elseif self.mode == PROCESS.SIMPLE then -- run units at their last configured set point if state_changed then self.time_start = now @@ -504,6 +522,8 @@ function facility.new(num_reactors, cooling_conf) ready = false end + ready = ready and self.units_ready + if ready then self.mode = self.mode_set end end @@ -550,6 +570,7 @@ function facility.new(num_reactors, cooling_conf) -- get automatic process control status function public.get_control_status() return { + self.units_ready, self.mode, self.waiting_on_ramp, self.ascram, diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index ecd6cc6..c36d449 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -448,6 +448,7 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) self.acks.burn_rate = ack ~= PLC_AUTO_ACK.FAIL + ---@todo implement error handling here if ack == PLC_AUTO_ACK.FAIL then elseif ack == PLC_AUTO_ACK.DIRECT_SET_OK then elseif ack == PLC_AUTO_ACK.RAMP_SET_OK then diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index 9ae4277..742c5ce 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -174,6 +174,8 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- fields for facility control ---@class unit_control control = { + ready = false, + degraded = false, blade_count = 0, br10 = 0, lim_br10 = 0 @@ -398,7 +400,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) if self.plc_i ~= nil then self.plc_i.auto_set_burn(self.db.control.br10 / 10, ramp) - if ramp then self.ramp_target_br10 = self.db.control.br10 / 10 end + if ramp then self.ramp_target_br10 = self.db.control.br10 end end end end @@ -407,8 +409,9 @@ function unit.new(for_reactor, num_boilers, num_turbines) ---@return boolean complete function public.a_ramp_complete() if self.plc_i ~= nil then - return (math.floor(self.plc_i.get_db().mek_status.burn_rate * 10) == self.ramp_target_br10) or (self.ramp_target_br10 == 0) - else return false end + local cur_rate = math.floor(self.plc_i.get_db().mek_status.burn_rate * 10) + return (cur_rate == self.ramp_target_br10) or (self.ramp_target_br10 == 0) + else return true end end -- perform an automatic SCRAM @@ -428,6 +431,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) if self.plc_s ~= nil and not self.plc_s.open then self.plc_s = nil self.plc_i = nil + self.db.control.br10 = 0 self.db.control.lim_br10 = 0 end @@ -436,6 +440,9 @@ function unit.new(for_reactor, num_boilers, num_turbines) _unlink_disconnected_units(self.turbines) _unlink_disconnected_units(self.redstone) + -- update degraded state for auto control + self.db.control.degraded = (#self.boilers ~= num_boilers) or (#self.turbines ~= num_turbines) or (self.plc_i == nil) + -- update deltas _dt__compute_all() @@ -606,9 +613,9 @@ function unit.new(for_reactor, num_boilers, num_turbines) -- get information required for automatic reactor control function public.get_control_inf() return self.db.control end - -- get unit state (currently only waste mode) + -- get unit state function public.get_state() - return { self.status_text[1], self.status_text[2], self.waste_mode } + return { self.status_text[1], self.status_text[2], self.waste_mode, self.db.control.ready, self.db.control.degraded } end -- get the reactor ID diff --git a/supervisor/session/unitlogic.lua b/supervisor/session/unitlogic.lua index 2924e6f..4354fde 100644 --- a/supervisor/session/unitlogic.lua +++ b/supervisor/session/unitlogic.lua @@ -38,9 +38,17 @@ function logic.update_annunciator(self) -- check PLC status self.db.annunciator.PLCOnline = self.plc_i ~= nil + local plc_ready = self.db.annunciator.PLCOnline + if self.db.annunciator.PLCOnline then local plc_db = self.plc_i.get_db() + -- update ready state + -- - can't be tripped + -- - must have received status at least once + -- - must have received struct at least once + plc_ready = (not plc_db.rps_tripped) and (plc_db.last_status_update > 0) and (plc_db.mek_struct.length > 0) + -- update auto control limit if (self.db.control.lim_br10 == 0) or ((self.db.control.lim_br10 / 10) > plc_db.mek_struct.max_burn) then self.db.control.lim_br10 = math.floor(plc_db.mek_struct.max_burn * 10) @@ -106,6 +114,8 @@ function logic.update_annunciator(self) -- BOILERS -- ------------- + local boilers_ready = num_boilers == #self.boilers + -- clear boiler online flags for i = 1, num_boilers do self.db.annunciator.BoilerOnline[i] = false end @@ -119,6 +129,14 @@ function logic.update_annunciator(self) local session = self.boilers[i] ---@type unit_session local boiler = session.get_db() ---@type boilerv_session_db + -- update ready state + -- - must be formed + -- - must have received build, state, and tanks at least once + boilers_ready = boilers_ready and boiler.formed and + (boiler.build.last_update > 0) and + (boiler.state.last_update > 0) and + (boiler.tanks.last_update > 0) + total_boil_rate = total_boil_rate + boiler.state.boil_rate boiler_steam_dt_sum = _get_dt(DT_KEYS.BoilerSteam .. self.boilers[i].get_device_idx()) boiler_water_dt_sum = _get_dt(DT_KEYS.BoilerWater .. self.boilers[i].get_device_idx()) @@ -185,6 +203,8 @@ function logic.update_annunciator(self) -- TURBINES -- -------------- + local turbines_ready = num_turbines == #self.turbines + -- clear turbine online flags for i = 1, num_turbines do self.db.annunciator.TurbineOnline[i] = false end @@ -201,6 +221,14 @@ function logic.update_annunciator(self) local session = self.turbines[i] ---@type unit_session local turbine = session.get_db() ---@type turbinev_session_db + -- update ready state + -- - must be formed + -- - must have received build, state, and tanks at least once + turbines_ready = turbines_ready and turbine.formed and + (turbine.build.last_update > 0) and + (turbine.state.last_update > 0) and + (turbine.tanks.last_update > 0) + total_flow_rate = total_flow_rate + turbine.state.flow_rate total_input_rate = total_input_rate + turbine.state.steam_input_rate max_water_return_rate = max_water_return_rate + turbine.build.max_water_output @@ -257,6 +285,9 @@ function logic.update_annunciator(self) local has_steam = db.state.steam_input_rate > 0 or db.tanks.steam_fill > 0.01 self.db.annunciator.TurbineTrip[turbine.get_device_idx()] = has_steam and db.state.flow_rate == 0 end + + -- update auto control ready state for this unit + self.db.control.ready = plc_ready and boilers_ready and turbines_ready end -- update an alarm state given conditions diff --git a/supervisor/startup.lua b/supervisor/startup.lua index ce7f377..f579ec2 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.9.8" +local SUPERVISOR_VERSION = "beta-v0.9.9" local print = util.print local println = util.println