diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 7d8444b..da12a50 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -116,6 +116,8 @@ function iocontrol.init(conf, comms) ALARM_STATE.INACTIVE -- turbine trip }, + annunciator = {}, ---@type annunciator + reactor_ps = psil.create(), reactor_data = {}, ---@type reactor_db @@ -272,12 +274,23 @@ function iocontrol.update_facility_status(status) fac.auto_active = ctl_status[1] > 0 fac.auto_ramping = ctl_status[2] fac.auto_scram = ctl_status[3] - fac.auto_scram_cause = ctl_status[4] + fac.status_line_1 = ctl_status[4] + fac.status_line_2 = ctl_status[5] 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("auto_scram_cause", fac.auto_scram_cause) + 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] + + if (type(group_map) == "table") and (#group_map == fac.num_units) then + local names = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" } + for i = 1, #group_map do + io.units[i].reactor_ps.publish("auto_group", names[group_map[i] + 1]) + end + end else log.debug(log_header .. "control status not a table") end @@ -556,14 +569,14 @@ function iocontrol.update_unit_statuses(statuses) -- annunciator - local annunciator = status[3] ---@type annunciator + unit.annunciator = status[3] - if type(annunciator) ~= "table" then - annunciator = {} + if type(unit.annunciator) ~= "table" then + unit.annunciator = {} log.debug(log_header .. "annunciator state not a table") end - for key, val in pairs(annunciator) do + for key, val in pairs(unit.annunciator) do if key == "TurbineTrip" then -- split up turbine trip table for all turbines and a general OR combination local trips = val diff --git a/coordinator/startup.lua b/coordinator/startup.lua index b41c73b..c1a8232 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.10" +local COORDINATOR_VERSION = "beta-v0.8.11" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index f7f5957..c3950e9 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -160,7 +160,6 @@ local function init(parent, id) local plc_online = IndicatorLight{parent=annunciator,label="PLC Online",colors=cpair(colors.green,colors.red)} local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=cpair(colors.white,colors.gray)} local r_active = IndicatorLight{parent=annunciator,label="Active",colors=cpair(colors.green,colors.gray)} - ---@todo auto control as info sent here local r_auto = IndicatorLight{parent=annunciator,label="Automatic Control",colors=cpair(colors.blue,colors.gray)} annunciator.line_break() @@ -171,6 +170,7 @@ local function init(parent, id) r_ps.subscribe("PLCOnline", plc_online.update) r_ps.subscribe("PLCHeartbeat", plc_hbeat.update) r_ps.subscribe("status", r_active.update) + r_ps.subscribe("AutoControl", r_auto.update) annunciator.line_break() @@ -328,18 +328,18 @@ local function init(parent, id) -- reactor controls -- ---------------------- + local dis_colors = cpair(colors.white, colors.lightGray) + local burn_control = Div{parent=main,x=12,y=28,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} local burn_rate = SpinboxNumeric{parent=burn_control,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} TextBox{parent=burn_control,x=9,y=2,text="mB/t"} local set_burn = function () unit.set_burn(burn_rate.get_value()) end - PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),callback=set_burn} + local set_burn_btn = PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),dis_fg_bg=dis_colors,callback=set_burn} r_ps.subscribe("burn_rate", burn_rate.set_value) r_ps.subscribe("max_burn", burn_rate.set_max) - local dis_colors = cpair(colors.white, colors.lightGray) - local start = HazardButton{parent=main,x=2,y=28,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=hzd_fg_bg} local ack_a = HazardButton{parent=main,x=12,y=32,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=unit.ack_alarms,fg_bg=hzd_fg_bg} local scram = HazardButton{parent=main,x=2,y=32,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=hzd_fg_bg} @@ -352,7 +352,9 @@ local function init(parent, id) local function start_button_en_check() if (unit.reactor_data ~= nil) and (unit.reactor_data.mek_status ~= nil) then - local can_start = (not unit.reactor_data.mek_status.status) and (not unit.reactor_data.rps_tripped) + local can_start = (not unit.reactor_data.mek_status.status) and + (not unit.reactor_data.rps_tripped) and + (not unit.annunciator.AutoControl) if can_start then start.enable() else start.disable() end end end @@ -455,22 +457,55 @@ local function init(parent, id) local ctl_opts = { "Manual", "Primary", "Secondary", "Tertiary", "Backup" } - RadioButton{parent=auto_div,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.blue,colors.white),radio_bg=colors.gray} + local group = RadioButton{parent=auto_div,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.blue,colors.white),radio_bg=colors.gray} auto_div.line_break() - PushButton{parent=auto_div,text="SET",x=4,min_width=5,fg_bg=cpair(colors.black,colors.white),active_fg_bg=cpair(colors.white,colors.gray),callback=function()end} + local function set_group() unit.set_group(group.get_value() - 1) end + + local set_grp_btn = PushButton{parent=auto_div,text="SET",x=4,min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),dis_fg_bg=cpair(colors.gray,colors.white),callback=set_group} auto_div.line_break() TextBox{parent=auto_div,text="Prio. Group",height=1,width=11,fg_bg=style.label} local auto_grp = TextBox{parent=auto_div,text="Manual",height=1,width=11,fg_bg=bw_fg_bg} + r_ps.subscribe("auto_group", auto_grp.set_value) + auto_div.line_break() - local a_prm = IndicatorLight{parent=auto_div,label="Active",x=2,colors=cpair(colors.green,colors.gray)} + 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)} + 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 + end) + + -- enable and disable controls based on auto control state (start button is handled separately) + r_ps.subscribe("AutoControl", function (auto_active) + start_button_en_check() + + if auto_active then + 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) + return main end diff --git a/graphics/elements/controls/push_button.lua b/graphics/elements/controls/push_button.lua index 018b0e9..8cb89c9 100644 --- a/graphics/elements/controls/push_button.lua +++ b/graphics/elements/controls/push_button.lua @@ -10,6 +10,7 @@ local element = require("graphics.element") ---@field callback function function to call on touch ---@field min_width? integer text length + 2 if omitted ---@field active_fg_bg? cpair foreground/background colors when pressed +---@field dis_fg_bg? cpair foreground/background colors when disabled ---@field parent graphics_element ---@field id? string element id ---@field x? integer 1 if omitted @@ -61,8 +62,10 @@ local function push_button(args) -- show as unpressed in 0.25 seconds tcd.dispatch(0.25, function () e.value = false - e.window.setTextColor(e.fg_bg.fgd) - e.window.setBackgroundColor(e.fg_bg.bkg) + if e.enabled then + e.window.setTextColor(e.fg_bg.fgd) + e.window.setBackgroundColor(e.fg_bg.bkg) + end draw() end) end @@ -78,6 +81,26 @@ local function push_button(args) if val then e.handle_touch(core.events.touch("", 1, 1)) end end + -- show butten as enabled + function e.enable() + if args.dis_fg_bg ~= nil then + e.value = false + e.window.setTextColor(e.fg_bg.fgd) + e.window.setBackgroundColor(e.fg_bg.bkg) + draw() + end + end + + -- show button as disabled + function e.disable() + if args.dis_fg_bg ~= nil then + e.value = false + e.window.setTextColor(args.dis_fg_bg.fgd) + e.window.setBackgroundColor(args.dis_fg_bg.bkg) + draw() + end + end + -- initial draw draw() diff --git a/supervisor/session/facility.lua b/supervisor/session/facility.lua index d554fca..7486346 100644 --- a/supervisor/session/facility.lua +++ b/supervisor/session/facility.lua @@ -40,6 +40,7 @@ function facility.new(num_reactors, cooling_conf) units = {}, induction = {}, redstone = {}, + status_text = { "IDLE", "control inactive" }, -- process control mode = PROCESS.INACTIVE, last_mode = PROCESS.INACTIVE, @@ -103,36 +104,42 @@ function facility.new(num_reactors, cooling_conf) local unallocated = math.floor(burn_rate * 10) -- go through alll priority groups - for i = 1, #self.prio_defs and (unallocated > 0) do + for i = 1, #self.prio_defs do local units = self.prio_defs[i] - local split = math.floor(unallocated / #units) - local splits = {} - for u = 1, #units do splits[u] = split end - splits[#units] = splits[#units] + (unallocated % #units) + if #units > 0 then + local split = math.floor(unallocated / #units) - -- go through all reactor units in this group - for u = 1, #units do - local ctl = units[u].get_control_inf() ---@type unit_control - local last = ctl.br10 + local splits = {} + for u = 1, #units do splits[u] = split end + splits[#units] = splits[#units] + (unallocated % #units) - if splits[u] <= ctl.lim_br10 then - ctl.br10 = splits[u] - else - ctl.br10 = ctl.lim_br10 + -- go through all reactor units in this group + for u = 1, #units do + local ctl = units[u].get_control_inf() ---@type unit_control + local last = ctl.br10 - if u < #units then - local remaining = #units - u - split = math.floor(unallocated / remaining) - for x = (u + 1), #units do splits[x] = split end - splits[#units] = splits[#units] + (unallocated % remaining) + if splits[u] <= ctl.lim_br10 then + ctl.br10 = splits[u] + else + ctl.br10 = ctl.lim_br10 + + if u < #units then + local remaining = #units - u + split = math.floor(unallocated / remaining) + for x = (u + 1), #units do splits[x] = split end + splits[#units] = splits[#units] + (unallocated % remaining) + end end + + unallocated = unallocated - ctl.br10 + + if last ~= ctl.br10 then units[u].a_commit_br10(ramp) end end - - unallocated = unallocated - ctl.br10 - - if last ~= ctl.br10 then units[u].a_commit_br10(ramp) end end + + -- stop if fully allocated + if unallocated <= 0 then break end end end @@ -216,19 +223,24 @@ function facility.new(num_reactors, cooling_conf) ) for _, u in pairs(self.prio_defs[i]) do - blade_count = blade_count + u.get_db().blade_count + blade_count = blade_count + u.get_control_inf().blade_count u.a_engage() self.max_burn_combined = self.max_burn_combined + (u.get_control_inf().lim_br10 / 10.0) end end self.charge_conversion = blade_count * POWER_PER_BLADE + + 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 for _, u in pairs(self.prio_defs[i]) do u.a_disengage() end end + + self.status_text = { "IDLE", "control disengaged" } + log.debug("FAC: disengaging auto control (now inactive)") end self.initial_ramp = true @@ -241,30 +253,43 @@ function facility.new(num_reactors, cooling_conf) -- run units at their last configured set point if state_changed then self.time_start = now + ---@todo will still need to ramp? + log.debug(util.c("FAC: SIMPLE mode first call completed")) end elseif self.mode == PROCESS.BURN_RATE then -- a total aggregate burn rate if state_changed then -- nothing special to do + self.status_text = { "BURN RATE MODE", "starting up" } + log.debug(util.c("FAC: BURN_RATE mode first call completed")) elseif self.waiting_on_ramp and _all_units_ramped() then self.waiting_on_ramp = false self.time_start = now + self.status_text = { "BURN RATE MODE", "running" } + log.debug(util.c("FAC: BURN_RATE mode initial ramp completed")) end if not self.waiting_on_ramp then _allocate_burn_rate(self.burn_target, self.initial_ramp) + + if self.initial_ramp then + self.status_text = { "BURN RATE MODE", "ramping reactors" } + self.waiting_on_ramp = true + log.debug(util.c("FAC: BURN_RATE mode allocated initial ramp")) + end end elseif self.mode == PROCESS.CHARGE then -- target a level of charge local error = (self.charge_target - avg_charge) / self.charge_conversion if state_changed then - -- nothing special to do + log.debug(util.c("FAC: CHARGE mode first call completed")) elseif self.waiting_on_ramp and _all_units_ramped() then self.waiting_on_ramp = false self.time_start = now self.accumulator = 0 + log.debug(util.c("FAC: CHARGE mode initial ramp completed")) end if not self.waiting_on_ramp then @@ -311,11 +336,13 @@ function facility.new(num_reactors, cooling_conf) local sp_r = util.round(setpoint * 10.0) / 10.0 _allocate_burn_rate(sp_r, true) + log.debug(util.c("FAC: GEN_RATE mode first call completed")) elseif self.waiting_on_ramp and _all_units_ramped() then self.waiting_on_ramp = false self.time_start = now self.accumulator = 0 + log.debug(util.c("FAC: GEN_RATE mode initial ramp completed")) end if not self.waiting_on_ramp then @@ -346,6 +373,9 @@ function facility.new(num_reactors, cooling_conf) _allocate_burn_rate(sp_c, false) end + elseif self.mode ~= PROCESS.INACTIVE then + log.error(util.c("FAC: unsupported process mode ", self.mode, ", switching to inactive")) + self.mode = PROCESS.INACTIVE end ------------------------------ @@ -389,6 +419,9 @@ function facility.new(num_reactors, cooling_conf) self.ascram = true end end + + -- update last mode + self.last_mode = self.mode end -- call the update function of all units in the facility @@ -429,8 +462,9 @@ function facility.new(num_reactors, cooling_conf) -- only allow changes if not running if self.mode == PROCESS.INACTIVE then - if (type(config.mode) == "number") and (config.mode > PROCESS.INACTIVE) and (config.mode <= PROCESS.SIMPLE) then + if (type(config.mode) == "number") and (config.mode > PROCESS.INACTIVE) and (config.mode <= PROCESS.GEN_RATE) then self.mode_set = config.mode + log.debug("SET MODE " .. config.mode) end if (type(config.burn_target) == "number") and config.burn_target >= 0.1 then @@ -489,7 +523,7 @@ function facility.new(num_reactors, cooling_conf) util.filter_table(self.prio_defs[old_group], function (u) return u.get_id() ~= unit_id end) end - self.group_map[unit] = group + self.group_map[unit_id] = group -- add to group if not independent if group > 0 then @@ -519,7 +553,9 @@ function facility.new(num_reactors, cooling_conf) self.mode, self.waiting_on_ramp, self.ascram, - self.ascram_reason + self.status_text[1], + self.status_text[2], + self.group_map } end diff --git a/supervisor/session/unit.lua b/supervisor/session/unit.lua index e084fed..9ae4277 100644 --- a/supervisor/session/unit.lua +++ b/supervisor/session/unit.lua @@ -387,6 +387,7 @@ function unit.new(for_reactor, num_boilers, num_turbines) self.db.annunciator.AutoControl = false if self.plc_i ~= nil then self.plc_i.auto_lock(false) + self.db.control.br10 = 0 end end diff --git a/supervisor/session/unitlogic.lua b/supervisor/session/unitlogic.lua index 0531038..2924e6f 100644 --- a/supervisor/session/unitlogic.lua +++ b/supervisor/session/unitlogic.lua @@ -194,7 +194,7 @@ function logic.update_annunciator(self) local max_water_return_rate = 0 -- recompute blade count on the chance that it may have changed - self.db.blade_count = 0 + self.db.control.blade_count = 0 -- go through turbines for stats and online for i = 1, #self.turbines do @@ -204,7 +204,7 @@ function logic.update_annunciator(self) 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 - self.db.blade_count = self.db.blade_count + turbine.build.blades + self.db.control.blade_count = self.db.control.blade_count + turbine.build.blades self.db.annunciator.TurbineOnline[session.get_device_idx()] = true end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index b284a35..ce7f377 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.7" +local SUPERVISOR_VERSION = "beta-v0.9.8" local print = util.print local println = util.println