diff --git a/coordinator/config/system.lua b/coordinator/config/system.lua index 3925114..bc78cb0 100644 --- a/coordinator/config/system.lua +++ b/coordinator/config/system.lua @@ -550,7 +550,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white)) alternate = not alternate - if string.len(val) > val_max_w then + if (string.len(val) > val_max_w) or string.find(val, "\n") then local lines = util.strwrap(val, inner_width) height = #lines + 1 end diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 36163eb..c0ba2f3 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -380,6 +380,18 @@ function coordinator.comms(version, nic, sv_watchdog) _send_sv(PROTOCOL.SCADA_MGMT, MGMT_TYPE.CLOSE, {}) end + -- send the resume ready state to the supervisor + ---@param mode PROCESS process control mode + ---@param burn_target number burn rate target + ---@param charge_target number charge level target + ---@param gen_target number generation rate target + ---@param limits number[] unit burn rate limits + function public.send_ready(mode, burn_target, charge_target, gen_target, limits) + _send_sv(PROTOCOL.SCADA_CRDN, CRDN_TYPE.PROCESS_READY, { + mode, burn_target, charge_target, gen_target, limits + }) + end + -- send a facility command ---@param cmd FAC_COMMAND command ---@param option any? optional option options for the optional options (like waste mode) diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 95a3e6f..f44becb 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -164,6 +164,7 @@ function iocontrol.init(conf, comms, temp_scale, energy_scale) num_turbines = 0, num_snas = 0, has_tank = conf.cooling.r_cool[i].TankConnection, + aux_coolant = conf.cooling.aux_coolant[i], status_lines = { "", "" }, @@ -1214,7 +1215,7 @@ function iocontrol.update_unit_statuses(statuses) local valve_states = status[6] if type(valve_states) == "table" then - if #valve_states == 5 then + if #valve_states == 6 then unit.unit_ps.publish("V_pu_conn", valve_states[1] > 0) unit.unit_ps.publish("V_pu_state", valve_states[1] == 2) unit.unit_ps.publish("V_po_conn", valve_states[2] > 0) @@ -1225,6 +1226,8 @@ function iocontrol.update_unit_statuses(statuses) unit.unit_ps.publish("V_am_state", valve_states[4] == 2) unit.unit_ps.publish("V_emc_conn", valve_states[5] > 0) unit.unit_ps.publish("V_emc_state", valve_states[5] == 2) + unit.unit_ps.publish("V_aux_conn", valve_states[6] > 0) + unit.unit_ps.publish("V_aux_state", valve_states[6] == 2) else log.debug(log_header .. "valve states length mismatch") valid = false diff --git a/coordinator/process.lua b/coordinator/process.lua index 1866686..0fc7c1a 100644 --- a/coordinator/process.lua +++ b/coordinator/process.lua @@ -139,6 +139,11 @@ function process.init(iocontrol, coord_comms) log.info("PROCESS: loaded priority groups settings") end + + -- report to the supervisor all initial configuration data has been sent + -- startup resume can occur if needed + local p = ctl_proc + pctl.comms.send_ready(p.mode, p.burn_target, p.charge_target, p.gen_target, p.limits) end -- create a handle to process control for usage of commands that get acknowledgements diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index cfa5013..d3ca0cd 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -137,7 +137,7 @@ function renderer.try_start_fp() if not engine.fp_ready then -- show front panel view on terminal status, msg = pcall(function () - engine.ui.front_panel = DisplayBox{window=term.native(),fg_bg=style.fp.root} + engine.ui.front_panel = DisplayBox{window=term.current(),fg_bg=style.fp.root} panel_view(engine.ui.front_panel, #engine.monitors.unit_displays) end) diff --git a/coordinator/startup.lua b/coordinator/startup.lua index d3413e3..3618e2b 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") local threads = require("coordinator.threads") -local COORDINATOR_VERSION = "v1.6.4" +local COORDINATOR_VERSION = "v1.6.11" local CHUNK_LOAD_DELAY_S = 30.0 diff --git a/coordinator/ui/components/pkt_entry.lua b/coordinator/ui/components/pkt_entry.lua index 78084f9..a377d0e 100644 --- a/coordinator/ui/components/pkt_entry.lua +++ b/coordinator/ui/components/pkt_entry.lua @@ -28,6 +28,8 @@ local function init(parent, id) local ps = iocontrol.get_db().fp.ps + local term_w, _ = term.getSize() + -- root div local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2} local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=s_hi_bright} @@ -43,9 +45,9 @@ local function init(parent, id) local pkt_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,fg_bg=label_fg} pkt_fw_v.register(ps, ps_prefix .. "fw", pkt_fw_v.set_value) - TextBox{parent=entry,x=35,y=2,text="RTT:",width=4} - local pkt_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg} - TextBox{parent=entry,x=46,y=2,text="ms",width=4,fg_bg=label_fg} + TextBox{parent=entry,x=term_w-16,y=2,text="RTT:",width=4} + local pkt_rtt = DataIndicator{parent=entry,x=term_w-11,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg} + TextBox{parent=entry,x=term_w-5,y=2,text="ms",width=4,fg_bg=label_fg} pkt_rtt.register(ps, ps_prefix .. "rtt", pkt_rtt.update) pkt_rtt.register(ps, ps_prefix .. "rtt_color", pkt_rtt.recolor) diff --git a/coordinator/ui/components/unit_flow.lua b/coordinator/ui/components/unit_flow.lua index 2c1ed35..eb00fa5 100644 --- a/coordinator/ui/components/unit_flow.lua +++ b/coordinator/ui/components/unit_flow.lua @@ -59,7 +59,7 @@ local function make(parent, x, y, wide, unit_id) local tank_conns = facility.tank_conns local tank_types = facility.tank_fluid_types - local v_start = 1 + ((unit.unit_id - 1) * 5) + local v_start = 1 + ((unit.unit_id - 1) * 6) local prv_start = 1 + ((unit.unit_id - 1) * 3) local v_fields = { "pu", "po", "pl", "am" } local v_names = { @@ -94,11 +94,21 @@ local function make(parent, x, y, wide, unit_id) if unit.num_boilers > 0 then table.insert(rc_pipes, pipe(0, 1, _wide(28, 19), 1, colors.lightBlue, true)) table.insert(rc_pipes, pipe(0, 3, _wide(28, 19), 3, colors.orange, true)) - table.insert(rc_pipes, pipe(_wide(46 ,39), 1, _wide(72,58), 1, colors.blue, true)) - table.insert(rc_pipes, pipe(_wide(46,39), 3, _wide(72,58), 3, colors.white, true)) + table.insert(rc_pipes, pipe(_wide(46, 39), 1, _wide(72, 58), 1, colors.blue, true)) + table.insert(rc_pipes, pipe(_wide(46, 39), 3, _wide(72, 58), 3, colors.white, true)) + + if unit.aux_coolant then + local em_water = facility.tank_fluid_types[facility.tank_conns[unit_id]] == COOLANT_TYPE.WATER + local offset = util.trinary(unit.has_tank and em_water, 3, 0) + table.insert(rc_pipes, pipe(_wide(51, 41) + offset, 0, _wide(51, 41) + offset, 0, colors.blue, true)) + end else - table.insert(rc_pipes, pipe(0, 1, _wide(72,58), 1, colors.blue, true)) - table.insert(rc_pipes, pipe(0, 3, _wide(72,58), 3, colors.white, true)) + table.insert(rc_pipes, pipe(0, 1, _wide(72, 58), 1, colors.blue, true)) + table.insert(rc_pipes, pipe(0, 3, _wide(72, 58), 3, colors.white, true)) + + if unit.aux_coolant then + table.insert(rc_pipes, pipe(8, 0, 8, 0, colors.blue, true)) + end end if unit.has_tank then @@ -222,17 +232,21 @@ local function make(parent, x, y, wide, unit_id) _machine(_wide(116, 94), 6, "SPENT WASTE \x1b") TextBox{parent=waste,x=_wide(30,25),y=3,text="SNAs [Po]",alignment=ALIGN.CENTER,width=19,fg_bg=wh_gray} - local sna_po = Rectangle{parent=waste,x=_wide(30,25),y=4,border=border(1,colors.gray,true),width=19,height=7,thin=true,fg_bg=style.theme.highlight_box_bright} + local sna_po = Rectangle{parent=waste,x=_wide(30,25),y=4,border=border(1,colors.gray,true),width=19,height=8,thin=true,fg_bg=style.theme.highlight_box_bright} local sna_act = IndicatorLight{parent=sna_po,label="ACTIVE",colors=ind_grn} local sna_cnt = DataIndicator{parent=sna_po,x=12,y=1,lu_colors=lu_c_d,label="CNT",unit="",format="%2d",value=0,width=7} - local sna_pk = DataIndicator{parent=sna_po,y=3,lu_colors=lu_c_d,label="PEAK",unit="mB/t",format="%7.2f",value=0,width=17} - local sna_max = DataIndicator{parent=sna_po,lu_colors=lu_c_d,label="MAX",unit="mB/t",format="%8.2f",value=0,width=17} - local sna_in = DataIndicator{parent=sna_po,lu_colors=lu_c_d,label="IN",unit="mB/t",format="%9.2f",value=0,width=17} + TextBox{parent=sna_po,y=3,text="PEAK\x1a",width=5,fg_bg=cpair(style.theme.label_dark,colors._INHERIT)} + TextBox{parent=sna_po,text="MAX \x1a",width=5,fg_bg=cpair(style.theme.label_dark,colors._INHERIT)} + local sna_pk = DataIndicator{parent=sna_po,x=6,y=3,lu_colors=lu_c_d,label="",unit="mB/t",format="%7.2f",value=0,width=17} + local sna_max_o = DataIndicator{parent=sna_po,x=6,lu_colors=lu_c_d,label="",unit="mB/t",format="%7.2f",value=0,width=17} + local sna_max_i = DataIndicator{parent=sna_po,lu_colors=lu_c_d,label="\x1aMAX",unit="mB/t",format="%7.2f",value=0,width=17} + local sna_in = DataIndicator{parent=sna_po,lu_colors=lu_c_d,label="\x1aIN",unit="mB/t",format="%8.2f",value=0,width=17} sna_act.register(unit.unit_ps, "po_rate", function (r) sna_act.update(r > 0) end) sna_cnt.register(unit.unit_ps, "sna_count", sna_cnt.update) sna_pk.register(unit.unit_ps, "sna_peak_rate", sna_pk.update) - sna_max.register(unit.unit_ps, "sna_max_rate", sna_max.update) + sna_max_o.register(unit.unit_ps, "sna_max_rate", sna_max_o.update) + sna_max_i.register(unit.unit_ps, "sna_max_rate", function (r) sna_max_i.update(r * 10) end) sna_in.register(unit.unit_ps, "sna_in", sna_in.update) return root diff --git a/coordinator/ui/layout/flow_view.lua b/coordinator/ui/layout/flow_view.lua index 7b89212..4de5e4d 100644 --- a/coordinator/ui/layout/flow_view.lua +++ b/coordinator/ui/layout/flow_view.lua @@ -286,7 +286,7 @@ local function init(main) TextBox{parent=main,x=12,y=vy,text="\x10\x11",fg_bg=text_col,width=2} - local conn = IndicatorLight{parent=main,x=9,y=vy+1,label=util.sprintf("PV%02d-EMC", i * 5),colors=style.ind_grn} + local conn = IndicatorLight{parent=main,x=9,y=vy+1,label=util.sprintf("PV%02d-EMC", (i * 6) - 1),colors=style.ind_grn} local open = IndicatorLight{parent=main,x=9,y=vy+2,label="OPEN",colors=style.ind_wht} conn.register(units[i].unit_ps, "V_emc_conn", conn.update) @@ -294,6 +294,35 @@ local function init(main) end end + ------------------------------ + -- auxiliary coolant valves -- + ------------------------------ + + for i = 1, facility.num_units do + if units[i].aux_coolant then + local vx + local vy = 3 + y_ofs(i) + + if #emcool_pipes == 0 then + vx = util.trinary(units[i].num_boilers == 0, 36, 79) + else + local em_water = tank_types[tank_conns[i]] == COOLANT_TYPE.WATER + vx = util.trinary(units[i].num_boilers == 0, 58, util.trinary(units[i].has_tank and em_water, 94, 91)) + end + + PipeNetwork{parent=main,x=vx-6,y=vy,pipes={pipe(0,1,9,0,colors.blue,true)},bg=style.theme.bg} + + TextBox{parent=main,x=vx,y=vy,text="\x10\x11",fg_bg=text_col,width=2} + TextBox{parent=main,x=vx+5,y=vy,text="\x1b",fg_bg=cpair(colors.blue,text_col.bkg),width=1} + + local conn = IndicatorLight{parent=main,x=vx-3,y=vy+1,label=util.sprintf("PV%02d-AUX", i * 6),colors=style.ind_grn} + local open = IndicatorLight{parent=main,x=vx-3,y=vy+2,label="OPEN",colors=style.ind_wht} + + conn.register(units[i].unit_ps, "V_aux_conn", conn.update) + open.register(units[i].unit_ps, "V_aux_state", open.update) + end + end + ------------------- -- dynamic tanks -- ------------------- diff --git a/coordinator/ui/layout/front_panel.lua b/coordinator/ui/layout/front_panel.lua index 46d506b..1b32501 100644 --- a/coordinator/ui/layout/front_panel.lua +++ b/coordinator/ui/layout/front_panel.lua @@ -39,6 +39,8 @@ local led_grn = style.led_grn local function init(panel, num_units) local ps = iocontrol.get_db().fp.ps + local term_w, term_h = term.getSize() + TextBox{parent=panel,y=1,text="SCADA COORDINATOR",alignment=ALIGN.CENTER,fg_bg=style.fp_theme.header} local page_div = Div{parent=panel,x=1,y=3} @@ -61,7 +63,7 @@ local function init(panel, num_units) local modem = LED{parent=system,label="MODEM",colors=led_grn} if not style.colorblind then - local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,style.fp_ind_bkg}} + local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.yellow,colors.orange,style.fp_ind_bkg}} network.update(types.PANEL_LINK_STATE.DISCONNECTED) network.register(ps, "link_state", network.update) else @@ -131,9 +133,9 @@ local function init(panel, num_units) -- about footer -- - local about = Div{parent=main_page,width=15,height=3,x=1,y=16,fg_bg=style.fp.disabled_fg} - local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00"} - local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00"} + local about = Div{parent=main_page,width=15,height=2,y=term_h-3,fg_bg=style.fp.disabled_fg} + local fw_v = TextBox{parent=about,text="FW: v00.00.00"} + local comms_v = TextBox{parent=about,text="NT: v00.00.00"} fw_v.register(ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end) comms_v.register(ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end) @@ -145,7 +147,7 @@ local function init(panel, num_units) -- API page local api_page = Div{parent=page_div,x=1,y=1,hidden=true} - local api_list = ListBox{parent=api_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)} + local api_list = ListBox{parent=api_page,y=1,height=term_h-2,width=term_w,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)} local _ = Div{parent=api_list,height=1} -- padding -- assemble page panes diff --git a/pocket/config/system.lua b/pocket/config/system.lua index 4c195ff..321ef59 100644 --- a/pocket/config/system.lua +++ b/pocket/config/system.lua @@ -385,7 +385,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white)) alternate = not alternate - if string.len(val) > val_max_w then + if (string.len(val) > val_max_w) or string.find(val, "\n") then local lines = util.strwrap(val, inner_width) height = #lines + 1 end diff --git a/pocket/startup.lua b/pocket/startup.lua index 636c6ef..4d72797 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -20,7 +20,7 @@ local pocket = require("pocket.pocket") local renderer = require("pocket.renderer") local threads = require("pocket.threads") -local POCKET_VERSION = "v0.13.0-beta" +local POCKET_VERSION = "v0.13.1-beta" local println = util.println local println_ts = util.println_ts diff --git a/reactor-plc/config/system.lua b/reactor-plc/config/system.lua index d6bd734..909dda7 100644 --- a/reactor-plc/config/system.lua +++ b/reactor-plc/config/system.lua @@ -592,7 +592,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, style, exit) local c = util.trinary(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white)) alternate = not alternate - if string.len(val) > val_max_w then + if (string.len(val) > val_max_w) or string.find(val, "\n") then local lines = util.strwrap(val, inner_width) height = #lines + 1 end diff --git a/reactor-plc/panel/front_panel.lua b/reactor-plc/panel/front_panel.lua index 8bb6a0f..7a346df 100644 --- a/reactor-plc/panel/front_panel.lua +++ b/reactor-plc/panel/front_panel.lua @@ -40,6 +40,8 @@ local function init(panel) local disabled_fg = style.fp.disabled_fg + local term_w, term_h = term.getSize() + local header = TextBox{parent=panel,y=1,text="FISSION REACTOR PLC - UNIT ?",alignment=ALIGN.CENTER,fg_bg=style.theme.header} header.register(databus.ps, "unit_id", function (id) header.set_value(util.c("FISSION REACTOR PLC - UNIT ", id)) end) @@ -60,7 +62,7 @@ local function init(panel) local modem = LED{parent=system,label="MODEM",colors=ind_grn} if not style.colorblind then - local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,style.ind_bkg}} + local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.yellow,colors.orange,style.ind_bkg}} network.update(types.PANEL_LINK_STATE.DISCONNECTED) network.register(databus.ps, "link_state", network.update) else @@ -121,7 +123,7 @@ local function init(panel) -- status & controls -- - local status = Div{parent=panel,width=19,height=18,x=17,y=3} + local status = Div{parent=panel,width=term_w-32,height=18,x=17,y=3} local active = LED{parent=status,x=2,width=12,label="RCT ACTIVE",colors=ind_grn} @@ -131,14 +133,15 @@ local function init(panel) emer_cool.register(databus.ps, "emer_cool", emer_cool.update) end - local status_trip_rct = Rectangle{parent=status,width=20,height=3,x=1,border=border(1,s_hi_box.bkg,true),even_inner=true} - local status_trip = Div{parent=status_trip_rct,width=18,height=1,fg_bg=s_hi_box} + local status_trip_rct = Rectangle{parent=status,height=3,x=1,border=border(1,s_hi_box.bkg,true),even_inner=true} + local status_trip = Div{parent=status_trip_rct,height=1,fg_bg=s_hi_box} local scram = LED{parent=status_trip,width=10,label="RPS TRIP",colors=ind_red,flash=true,period=flasher.PERIOD.BLINK_250_MS} - local controls_rct = Rectangle{parent=status,width=17,height=3,x=1,border=border(1,s_hi_box.bkg,true),even_inner=true} - local controls = Div{parent=controls_rct,width=15,height=1,fg_bg=s_hi_box} - PushButton{parent=controls,x=1,y=1,min_width=7,text="SCRAM",callback=databus.rps_scram,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.black,colors.red_off)} - PushButton{parent=controls,x=9,y=1,min_width=7,text="RESET",callback=databus.rps_reset,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.black,colors.yellow_off)} + local controls_rct = Rectangle{parent=status,width=status.get_width()-2,height=3,x=1,border=border(1,s_hi_box.bkg,true),even_inner=true} + local controls = Div{parent=controls_rct,width=controls_rct.get_width()-2,height=1,fg_bg=s_hi_box} + local button_padding = math.floor((controls.get_width() - 14) / 3) + PushButton{parent=controls,x=button_padding+1,y=1,min_width=7,text="SCRAM",callback=databus.rps_scram,fg_bg=cpair(colors.black,colors.red),active_fg_bg=cpair(colors.black,colors.red_off)} + PushButton{parent=controls,x=(2*button_padding)+9,y=1,min_width=7,text="RESET",callback=databus.rps_reset,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.black,colors.yellow_off)} active.register(databus.ps, "reactor_active", active.update) scram.register(databus.ps, "rps_scram", scram.update) @@ -147,9 +150,9 @@ local function init(panel) -- about footer -- - local about = Div{parent=panel,width=15,height=3,x=1,y=18,fg_bg=disabled_fg} - local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00"} - local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00"} + local about = Div{parent=panel,width=15,height=2,y=term_h-1,fg_bg=disabled_fg} + local fw_v = TextBox{parent=about,text="FW: v00.00.00"} + local comms_v = TextBox{parent=about,text="NT: v00.00.00"} fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end) comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end) @@ -158,7 +161,7 @@ local function init(panel) -- rps list -- - local rps = Rectangle{parent=panel,width=16,height=16,x=36,y=3,border=border(1,s_hi_box.bkg),thin=true,fg_bg=s_hi_box} + local rps = Rectangle{parent=panel,width=16,height=16,x=term_w-15,y=3,border=border(1,s_hi_box.bkg),thin=true,fg_bg=s_hi_box} local rps_man = LED{parent=rps,label="MANUAL",colors=ind_red} local rps_auto = LED{parent=rps,label="AUTOMATIC",colors=ind_red} local rps_tmo = LED{parent=rps,label="TIMEOUT",colors=ind_red} diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index a84e55a..73b95ce 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -23,8 +23,7 @@ local AUTO_ACK = comms.PLC_AUTO_ACK local RPS_LIMITS = const.RPS_LIMITS --- I sure hope the devs don't change this error message, not that it would have safety implications --- I wish they didn't change it to be like this +-- specific errors thrown when scram/start is used that still count as success local PCALL_SCRAM_MSG = "Scram requires the reactor to be active." local PCALL_START_MSG = "Reactor is already active." diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 551e6a3..49d60a5 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc") local renderer = require("reactor-plc.renderer") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v1.8.14" +local R_PLC_VERSION = "v1.8.19" local println = util.println local println_ts = util.println_ts @@ -169,12 +169,12 @@ local function main() -- PLC init
--- EVENT_CONSUMER: this function consumes events local function init() - -- just booting up, no fission allowed (neutrons stay put thanks) - if (not plc_state.no_reactor) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then + -- scram on boot if networked, otherwise leave the reactor be + if __shared_memory.networked and (not plc_state.no_reactor) and plc_state.reactor_formed and smem_dev.reactor.getStatus() then smem_dev.reactor.scram() end - -- front panel time! + -- setup front panel if not renderer.ui_ready() then local message plc_state.fp_ok, message = renderer.try_start_ui(config.FrontPanelTheme, config.ColorMode) diff --git a/rtu/config/peripherals.lua b/rtu/config/peripherals.lua index 10ddca3..e214379 100644 --- a/rtu/config/peripherals.lua +++ b/rtu/config/peripherals.lua @@ -149,7 +149,7 @@ function peripherals.create(tool_ctl, main_pane, cfg_sys, peri_cfg, style) reposition("This SNA is for reactor unit # .", 46, 1, 31, 4, 7) self.p_idx.hide() self.p_assign_btn.hide(true) - self.p_desc_ext.set_value("Before adding lots of SNAs: multiply the \"PEAK\" rate on the flow monitor (after connecting at least 1 SNA) by 10 to get the mB/t of waste that they can process. Enough SNAs to provide 2x to 3x of your max burn rate should be a good margin to catch up after night or cloudy weather. Too many devices (such as SNAs) on one RTU can cause lag.") + self.p_desc_ext.set_value("Warning: too many devices on one RTU Gateway can cause lag. Note that 10x the \"PEAK\x1a\" rate on the flow monitor gives you the mB/t of waste that the SNA(s) can process. Enough SNAs to provide 2x to 3x of that unit's max burn rate should be a good margin to catch up after night or cloudy weather.") elseif type == "dynamicValve" then reposition("This is the below system's # dynamic tank.", 29, 4, 17, 6, 8) self.p_assign_btn.show() diff --git a/rtu/config/redstone.lua b/rtu/config/redstone.lua index c38f2ff..2d9e9f5 100644 --- a/rtu/config/redstone.lua +++ b/rtu/config/redstone.lua @@ -74,11 +74,12 @@ local PORT_DESC_MAP = { { IO.R_PLC_FAULT, "RPS PLC Fault" }, { IO.R_PLC_TIMEOUT, "RPS Supervisor Timeout" }, { IO.U_ALARM, "Unit Alarm" }, - { IO.U_EMER_COOL, "Unit Emergency Cool. Valve" } + { IO.U_EMER_COOL, "Unit Emergency Cool. Valve" }, + { IO.U_AUX_COOL, "Unit Auxiliary Cool. Valve" } } -- designation (0 = facility, 1 = unit) -local PORT_DSGN = { [-1] = 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0 } +local PORT_DSGN = { [-1] = 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1 } assert(#PORT_DESC_MAP == rsio.NUM_PORTS) assert(#PORT_DSGN == rsio.NUM_PORTS) diff --git a/rtu/config/system.lua b/rtu/config/system.lua index 5b572e4..d29b1ec 100644 --- a/rtu/config/system.lua +++ b/rtu/config/system.lua @@ -646,7 +646,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, ext, style) local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white)) alternate = not alternate - if string.len(val) > val_max_w then + if (string.len(val) > val_max_w) or string.find(val, "\n") then local lines = util.strwrap(val, inner_width) height = #lines + 1 end diff --git a/rtu/panel/front_panel.lua b/rtu/panel/front_panel.lua index 6fc40e5..51adf30 100644 --- a/rtu/panel/front_panel.lua +++ b/rtu/panel/front_panel.lua @@ -35,13 +35,15 @@ local UNIT_TYPE_LABELS = { "UNKNOWN", "REDSTONE", "BOILER", "TURBINE", "DYNAMIC local function init(panel, units) local disabled_fg = style.fp.disabled_fg + local term_w, term_h = term.getSize() + TextBox{parent=panel,y=1,text="RTU GATEWAY",alignment=ALIGN.CENTER,fg_bg=style.theme.header} -- -- system indicators -- - local system = Div{parent=panel,width=14,height=18,x=2,y=3} + local system = Div{parent=panel,width=14,height=term_h-5,x=2,y=3} local on = LED{parent=system,label="STATUS",colors=cpair(colors.green,colors.red)} local heartbeat = LED{parent=system,label="HEARTBEAT",colors=ind_grn} @@ -53,7 +55,7 @@ local function init(panel, units) local modem = LED{parent=system,label="MODEM",colors=ind_grn} if not style.colorblind then - local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,style.ind_bkg}} + local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.yellow,colors.orange,style.ind_bkg}} network.update(types.PANEL_LINK_STATE.DISCONNECTED) network.register(databus.ps, "link_state", network.update) else @@ -100,17 +102,17 @@ local function init(panel, units) local comp_id = util.sprintf("(%d)", os.getComputerID()) TextBox{parent=system,x=9,y=4,width=6,text=comp_id,fg_bg=disabled_fg} - TextBox{parent=system,x=1,y=14,text="SPEAKERS",width=8,fg_bg=style.fp.text_fg} - local speaker_count = DataIndicator{parent=system,x=10,y=14,label="",format="%3d",value=0,width=3,fg_bg=style.theme.field_box} + TextBox{parent=system,y=term_h-5,text="SPEAKERS",width=8,fg_bg=style.fp.text_fg} + local speaker_count = DataIndicator{parent=system,x=10,y=term_h-5,label="",format="%3d",value=0,width=3,fg_bg=style.theme.field_box} speaker_count.register(databus.ps, "speaker_count", speaker_count.update) -- -- about label -- - local about = Div{parent=panel,width=15,height=3,x=1,y=18,fg_bg=disabled_fg} - local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00"} - local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00"} + local about = Div{parent=panel,width=15,height=2,y=term_h-1,fg_bg=disabled_fg} + local fw_v = TextBox{parent=about,text="FW: v00.00.00"} + local comms_v = TextBox{parent=about,text="NT: v00.00.00"} fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end) comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end) @@ -119,10 +121,10 @@ local function init(panel, units) -- unit status list -- - local threads = Div{parent=panel,width=8,height=18,x=17,y=3} + local threads = Div{parent=panel,width=8,height=term_h-3,x=17,y=3} - -- display up to 16 units - local list_length = math.min(#units, 16) + -- display as many units as we can with 1 line of padding above and below + local list_length = math.min(#units, term_h - 3) -- show routine statuses for i = 1, list_length do @@ -131,7 +133,7 @@ local function init(panel, units) rt_unit.register(databus.ps, "routine__unit_" .. i, rt_unit.update) end - local unit_hw_statuses = Div{parent=panel,height=18,x=25,y=3} + local unit_hw_statuses = Div{parent=panel,height=term_h-3,x=25,y=3} -- show hardware statuses for i = 1, list_length do @@ -150,7 +152,7 @@ local function init(panel, units) -- assignment (unit # or facility) local for_unit = util.trinary(unit.reactor == 0, "\x1a FACIL ", "\x1a UNIT " .. unit.reactor) - TextBox{parent=unit_hw_statuses,y=i,x=19,text=for_unit,fg_bg=disabled_fg} + TextBox{parent=unit_hw_statuses,y=i,x=term_w-32,text=for_unit,fg_bg=disabled_fg} end end diff --git a/rtu/startup.lua b/rtu/startup.lua index c957ac9..dfef1b7 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -31,7 +31,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "v1.11.0" +local RTU_VERSION = "v1.11.6" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_HW_STATE = databus.RTU_HW_STATE diff --git a/scada-common/comms.lua b/scada-common/comms.lua index a8d0014..48cf416 100644 --- a/scada-common/comms.lua +++ b/scada-common/comms.lua @@ -17,7 +17,7 @@ local max_distance = nil local comms = {} -- protocol/data versions (protocol/data independent changes tracked by util.lua version) -comms.version = "3.0.4" +comms.version = "3.0.5" comms.api_version = "0.0.9" ---@enum PROTOCOL @@ -60,18 +60,19 @@ local MGMT_TYPE = { ---@enum CRDN_TYPE local CRDN_TYPE = { INITIAL_BUILDS = 0, -- initial, complete builds packet to the coordinator - FAC_BUILDS = 1, -- facility RTU builds - FAC_STATUS = 2, -- state of facility and facility devices - FAC_CMD = 3, -- faility command - UNIT_BUILDS = 4, -- build of each reactor unit (reactor + RTUs) - UNIT_STATUSES = 5, -- state of each of the reactor units - UNIT_CMD = 6, -- command a reactor unit - API_GET_FAC = 7, -- API: get the facility general data - API_GET_FAC_DTL = 8, -- API: get (detailed) data for the facility app - API_GET_UNIT = 9, -- API: get reactor unit data - API_GET_CTRL = 10, -- API: get data for the control app - API_GET_PROC = 11, -- API: get data for the process app - API_GET_WASTE = 12 -- API: get data for the waste app + PROCESS_READY = 1, -- process init is complete + last set of info for supervisor startup recovery + FAC_BUILDS = 2, -- facility RTU builds + FAC_STATUS = 3, -- state of facility and facility devices + FAC_CMD = 4, -- faility command + UNIT_BUILDS = 5, -- build of each reactor unit (reactor + RTUs) + UNIT_STATUSES = 6, -- state of each of the reactor units + UNIT_CMD = 7, -- command a reactor unit + API_GET_FAC = 8, -- API: get the facility general data + API_GET_FAC_DTL = 9, -- API: get (detailed) data for the facility app + API_GET_UNIT = 10, -- API: get reactor unit data + API_GET_CTRL = 11, -- API: get data for the control app + API_GET_PROC = 12, -- API: get data for the process app + API_GET_WASTE = 13 -- API: get data for the waste app } ---@enum ESTABLISH_ACK diff --git a/scada-common/constants.lua b/scada-common/constants.lua index 0472a6c..11a8929 100644 --- a/scada-common/constants.lua +++ b/scada-common/constants.lua @@ -72,6 +72,8 @@ local rs = {} rs.IMATRIX_CHARGE_LOW = 0.05 -- activation threshold (less than) for F_MATRIX_LOW rs.IMATRIX_CHARGE_HIGH = 0.95 -- activation threshold (greater than) for F_MATRIX_HIGH +rs.AUX_COOL_ENABLE = 0.60 -- actiation threshold (less than or equal) for U_AUX_COOL +rs.AUX_COOL_DISABLE = 1.00 -- deactivation threshold (greater than or equal) for U_AUX_COOL constants.RS_THRESHOLDS = rs diff --git a/scada-common/rsio.lua b/scada-common/rsio.lua index b88180a..9c4d569 100644 --- a/scada-common/rsio.lua +++ b/scada-common/rsio.lua @@ -78,6 +78,7 @@ local IO_PORT = { -- unit outputs U_ALARM = 25, -- active high, unit alarm U_EMER_COOL = 26, -- active low, emergency coolant control + U_AUX_COOL = 30, -- active low, auxiliary coolant control -- analog outputs -- @@ -90,8 +91,8 @@ rsio.IO_DIR = IO_DIR rsio.IO_MODE = IO_MODE rsio.IO = IO_PORT -rsio.NUM_PORTS = 29 -rsio.NUM_DIG_PORTS = 28 +rsio.NUM_PORTS = 30 +rsio.NUM_DIG_PORTS = 29 rsio.NUM_ANA_PORTS = 1 -- self checks @@ -149,6 +150,7 @@ local MODES = { [IO.R_PLC_TIMEOUT] = IO_MODE.DIGITAL_OUT, [IO.U_ALARM] = IO_MODE.DIGITAL_OUT, [IO.U_EMER_COOL] = IO_MODE.DIGITAL_OUT, + [IO.U_AUX_COOL] = IO_MODE.DIGITAL_OUT, [IO.F_MATRIX_CHG] = IO_MODE.ANALOG_OUT } @@ -208,10 +210,11 @@ local RS_DIO_MAP = { [IO.R_PLC_TIMEOUT] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, [IO.U_ALARM] = { _in = _I_ACTIVE_HIGH, _out = _O_ACTIVE_HIGH, mode = IO_DIR.OUT }, - [IO.U_EMER_COOL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT } + [IO.U_EMER_COOL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT }, + [IO.U_AUX_COOL] = { _in = _I_ACTIVE_LOW, _out = _O_ACTIVE_LOW, mode = IO_DIR.OUT } } -assert(rsio.NUM_DIG_PORTS == #RS_DIO_MAP, "RS_DIO_MAP length incorrect") +assert(rsio.NUM_DIG_PORTS == util.table_len(RS_DIO_MAP), "RS_DIO_MAP length incorrect") -- get the I/O direction of a port ---@nodiscard diff --git a/scada-common/util.lua b/scada-common/util.lua index 40a9fe3..a4c7d4e 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -24,7 +24,7 @@ local t_pack = table.pack local util = {} -- scada-common version -util.version = "1.4.10" +util.version = "1.4.12" util.TICK_TIME_S = 0.05 util.TICK_TIME_MS = 50 diff --git a/supervisor/config/facility.lua b/supervisor/config/facility.lua index 13e238c..b67e8f4 100644 --- a/supervisor/config/facility.lua +++ b/supervisor/config/facility.lua @@ -185,8 +185,9 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style) local fac_c_6 = Div{parent=fac_cfg,x=2,y=4,width=49} local fac_c_7 = Div{parent=fac_cfg,x=2,y=4,width=49} local fac_c_8 = Div{parent=fac_cfg,x=2,y=4,width=49} + local fac_c_9 = Div{parent=fac_cfg,x=2,y=4,width=49} - local fac_pane = MultiPane{parent=fac_cfg,x=1,y=4,panes={fac_c_1,fac_c_2,fac_c_3,fac_c_4,fac_c_5,fac_c_6,fac_c_7, fac_c_8}} + local fac_pane = MultiPane{parent=fac_cfg,x=1,y=4,panes={fac_c_1,fac_c_2,fac_c_3,fac_c_4,fac_c_5,fac_c_6,fac_c_7,fac_c_8,fac_c_9}} TextBox{parent=fac_cfg,x=1,y=2,text=" Facility Configuration",fg_bg=cpair(colors.black,colors.yellow)} @@ -205,10 +206,18 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style) nu_error.hide(true) tmp_cfg.UnitCount = count - local confs = tool_ctl.cooling_elems - if count >= 2 then confs[2].line.show() else confs[2].line.hide(true) end - if count >= 3 then confs[3].line.show() else confs[3].line.hide(true) end - if count == 4 then confs[4].line.show() else confs[4].line.hide(true) end + local c_confs = tool_ctl.cooling_elems + local a_confs = tool_ctl.aux_cool_elems + + for i = 2, 4 do + if count >= i then + c_confs[i].line.show() + a_confs[i].line.show() + else + c_confs[i].line.hide(true) + a_confs[i].line.hide(true) + end + end fac_pane.set_value(2) else nu_error.show() end @@ -285,6 +294,14 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style) else elem.div.hide(true) end end + if not any_has_tank then + tmp_cfg.FacilityTankMode = 0 + tmp_cfg.FacilityTankDefs = {} + tmp_cfg.FacilityTankList = {} + tmp_cfg.FacilityTankConns = {} + tmp_cfg.TankFluidTypes = {} + end + if any_has_tank then fac_pane.set_value(3) else main_pane.set_value(3) end end end @@ -672,25 +689,48 @@ function facility.create(tool_ctl, main_pane, cfg_sys, fac_cfg, style) PushButton{parent=fac_c_7,x=1,y=14,text="\x1b Back",callback=back_from_fluids,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} PushButton{parent=fac_c_7,x=44,y=14,text="Next \x1a",callback=submit_tank_fluids,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + --#endregion + --#region Auxiliary Coolant + + TextBox{parent=fac_c_8,height=5,text="Auxiliary water coolant can be enabled for units to provide extra water during turbine ramp-up. For water cooled reactors, this goes to the reactor. For sodium cooled reactors, water goes to the boiler."} + + for i = 1, 4 do + local line = Div{parent=fac_c_8,x=1,y=7+i,height=1} + + TextBox{parent=line,text="Unit "..i.." -",width=8} + local aux_cool = Checkbox{parent=line,x=10,y=1,label="Has Auxiliary Coolant",default=ini_cfg.AuxiliaryCoolant[i],box_fg_bg=cpair(colors.yellow,colors.black)} + + tool_ctl.aux_cool_elems[i] = { line = line, enable = aux_cool } + end + + local function submit_aux_cool() + tmp_cfg.AuxiliaryCoolant = {} + + for i = 1, tmp_cfg.UnitCount do + tmp_cfg.AuxiliaryCoolant[i] = tool_ctl.aux_cool_elems[i].enable.get_value() + end + + fac_pane.set_value(9) + end + + PushButton{parent=fac_c_8,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(7)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=fac_c_8,x=44,y=14,text="Next \x1a",callback=submit_aux_cool,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + --#endregion --#region Extended Idling - TextBox{parent=fac_c_8,height=6,text="Charge control provides automatic control to maintain an induction matrix charge level. In order to have smoother control, reactors that were activated will be held on at 0.01 mB/t for a short period before allowing them to turn off. This minimizes overshooting the charge target."} - TextBox{parent=fac_c_8,y=8,height=3,text="You can extend this to a full minute to minimize reactors flickering on/off, but there may be more overshoot of the target."} + TextBox{parent=fac_c_9,height=6,text="Charge control provides automatic control to maintain an induction matrix charge level. In order to have smoother control, reactors that were activated will be held on at 0.01 mB/t for a short period before allowing them to turn off. This minimizes overshooting the charge target."} + TextBox{parent=fac_c_9,y=8,height=3,text="You can extend this to a full minute to minimize reactors flickering on/off, but there may be more overshoot of the target."} - local ext_idling = Checkbox{parent=fac_c_8,x=1,y=12,label="Enable Extended Idling",default=ini_cfg.ExtChargeIdling,box_fg_bg=cpair(colors.yellow,colors.black)} - - local function back_from_idling() - fac_pane.set_value(tri(tmp_cfg.FacilityTankMode == 0, 3, 7)) - end + local ext_idling = Checkbox{parent=fac_c_9,x=1,y=12,label="Enable Extended Idling",default=ini_cfg.ExtChargeIdling,box_fg_bg=cpair(colors.yellow,colors.black)} local function submit_idling() tmp_cfg.ExtChargeIdling = ext_idling.get_value() main_pane.set_value(3) end - PushButton{parent=fac_c_8,x=1,y=14,text="\x1b Back",callback=back_from_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} - PushButton{parent=fac_c_8,x=44,y=14,text="Next \x1a",callback=submit_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=fac_c_9,x=1,y=14,text="\x1b Back",callback=function()fac_pane.set_value(8)end,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} + PushButton{parent=fac_c_9,x=44,y=14,text="Next \x1a",callback=submit_idling,fg_bg=nav_fg_bg,active_fg_bg=btn_act_fg_bg} --#endregion diff --git a/supervisor/config/system.lua b/supervisor/config/system.lua index 71483f9..48867f5 100644 --- a/supervisor/config/system.lua +++ b/supervisor/config/system.lua @@ -402,6 +402,10 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit try_set(tool_ctl.tank_elems[i].tank_opt, ini_cfg.FacilityTankDefs[i]) end + for i = 1, #ini_cfg.AuxiliaryCoolant do + try_set(tool_ctl.aux_cool_elems[i].enable, ini_cfg.AuxiliaryCoolant[i]) + end + tool_ctl.en_fac_tanks.set_value(ini_cfg.FacilityTankMode > 0) tool_ctl.view_cfg.enable() @@ -588,7 +592,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit end if val == "" then val = "no facility tanks" end - elseif f[1] == "FacilityTankMode" and raw == 0 then val = "0 (n/a, unit mode)" + elseif f[1] == "FacilityTankMode" and raw == 0 then val = "no facility tanks" elseif f[1] == "FacilityTankDefs" and type(cfg.FacilityTankDefs) == "table" then local tank_name_list = { table.unpack(cfg.FacilityTankList) } ---@type (string|integer)[] local next_f = 1 @@ -625,6 +629,13 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit val = "" + local count = 0 + for idx = 1, #tank_list do + if tank_list[idx] > 0 then count = count + 1 end + end + + local bullet = tri(count < 2, "", " \x07 ") + for idx = 1, #tank_list do local prefix = "?" local fluid = "water" @@ -642,11 +653,28 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit fluid = "sodium" end - val = val .. tri(val == "", "", "\n") .. util.sprintf(" \x07 tank %s - %s", prefix, fluid) + val = val .. tri(val == "", "", "\n") .. util.sprintf(bullet .. "tank %s - %s", prefix, fluid) end end if val == "" then val = "no emergency coolant tanks" end + elseif f[1] == "AuxiliaryCoolant" then + val = "" + + local count = 0 + for idx = 1, #cfg.AuxiliaryCoolant do + if cfg.AuxiliaryCoolant[idx] then count = count + 1 end + end + + local bullet = tri(count < 2, "", " \x07 ") + + for idx = 1, #cfg.AuxiliaryCoolant do + if cfg.AuxiliaryCoolant[idx] then + val = val .. tri(val == "", "", "\n") .. util.sprintf(bullet .. "unit %d", idx) + end + end + + if val == "" then val = "no auxiliary coolant" end end if not skip then @@ -655,7 +683,7 @@ function system.create(tool_ctl, main_pane, cfg_sys, divs, fac_pane, style, exit local c = tri(alternate, g_lg_fg_bg, cpair(colors.gray,colors.white)) alternate = not alternate - if string.len(val) > val_max_w then + if (string.len(val) > val_max_w) or string.find(val, "\n") then local lines = util.strwrap(val, inner_width) height = #lines + 1 end diff --git a/supervisor/configure.lua b/supervisor/configure.lua index 6e760bd..b25f891 100644 --- a/supervisor/configure.lua +++ b/supervisor/configure.lua @@ -72,7 +72,8 @@ local tool_ctl = { load_legacy = nil, ---@type function cooling_elems = {}, ---@type { line: Div, turbines: NumberField, boilers: NumberField, tank: Checkbox }[] - tank_elems = {} ---@type { div: Div, tank_opt: Radio2D, no_tank: TextBox }[] + tank_elems = {}, ---@type { div: Div, tank_opt: Radio2D, no_tank: TextBox }[] + aux_cool_elems = {} ---@type { line: Div, enable: Checkbox }[] } ---@class svr_config @@ -84,6 +85,7 @@ local tmp_cfg = { FacilityTankList = {}, ---@type integer[] list of tanks by slot (0 = none or covered by an above tank, 1 = unit tank, 2 = facility tank) FacilityTankConns = {}, ---@type integer[] map of unit tank connections (indicies are units, values are tank indicies in the tank list) TankFluidTypes = {}, ---@type integer[] which type of fluid each tank in the tank list should be containing + AuxiliaryCoolant = {}, ---@type boolean[] if a unit has auxiliary coolant ExtChargeIdling = false, SVR_Channel = nil, ---@type integer PLC_Channel = nil, ---@type integer @@ -117,6 +119,7 @@ local fields = { { "FacilityTankList", "Facility Tank List", {} }, -- hidden { "FacilityTankConns", "Facility Tank Connections", {} }, -- hidden { "TankFluidTypes", "Tank Fluid Types", {} }, + { "AuxiliaryCoolant", "Auxiliary Water Coolant", {} }, { "ExtChargeIdling", "Extended Charge Idling", false }, { "SVR_Channel", "SVR Channel", 16240 }, { "PLC_Channel", "PLC Channel", 16241 }, diff --git a/supervisor/facility.lua b/supervisor/facility.lua index e47842f..f3cf18b 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -31,6 +31,17 @@ local START_STATUS = { BLADE_MISMATCH = 2 } +---@enum RECOVERY_STATE +local RCV_STATE = { + INACTIVE = 0, + PRIMED = 1, + RUNNING = 2, + STOPPED = 3 +} + +local CHARGE_SCALER = 1000000 -- convert MFE to FE +local GEN_SCALER = 1000 -- convert kFE to FE + ---@class facility_management local facility = {} @@ -41,7 +52,7 @@ function facility.new(config) ---@class _facility_self local self = { units = {}, ---@type reactor_unit[] - types = { AUTO_SCRAM = AUTO_SCRAM, START_STATUS = START_STATUS }, + types = { AUTO_SCRAM = AUTO_SCRAM, START_STATUS = START_STATUS, RCV_STATE = RCV_STATE }, status_text = { "START UP", "initializing..." }, all_sys_ok = false, allow_testing = false, @@ -53,7 +64,8 @@ function facility.new(config) fac_tank_defs = config.FacilityTankDefs, fac_tank_list = config.FacilityTankList, fac_tank_conns = config.FacilityTankConns, - tank_fluid_types = config.TankFluidTypes + tank_fluid_types = config.TankFluidTypes, + aux_coolant = config.AuxiliaryCoolant }, -- rtus rtu_gw_conn_count = 0, @@ -66,12 +78,15 @@ function facility.new(config) -- redstone I/O control io_ctl = nil, ---@type rs_controller -- process control + recovery = RCV_STATE.INACTIVE, ---@type RECOVERY_STATE + recovery_boot_state = nil, ---@type sv_boot_state|nil + last_unit_states = {}, ---@type boolean[] units_ready = false, - mode = PROCESS.INACTIVE, - last_mode = PROCESS.INACTIVE, - return_mode = PROCESS.INACTIVE, - mode_set = PROCESS.MAX_BURN, - start_fail = START_STATUS.OK, + mode = PROCESS.INACTIVE, ---@type PROCESS + last_mode = PROCESS.INACTIVE, ---@type PROCESS + return_mode = PROCESS.INACTIVE, ---@type PROCESS + mode_set = PROCESS.MAX_BURN, ---@type PROCESS + start_fail = START_STATUS.OK, ---@type START_STATUS max_burn_combined = 0.0, -- maximum burn rate to clamp at burn_target = 0.1, -- burn rate target for aggregate burn mode charge_setpoint = 0, -- FE charge target setpoint @@ -101,8 +116,8 @@ function facility.new(config) last_error = 0.0, last_time = 0.0, -- waste processing - waste_product = WASTE.PLUTONIUM, - current_waste_product = WASTE.PLUTONIUM, + waste_product = WASTE.PLUTONIUM, ---@type WASTE_PRODUCT + current_waste_product = WASTE.PLUTONIUM, ---@type WASTE_PRODUCT pu_fallback = false, sps_low_power = false, disabled_sps = false, @@ -126,14 +141,16 @@ function facility.new(config) imtx_faulted_times = { 0, 0, 0 } } + --#region SETUP + -- provide self to facility update functions local f_update = fac_update(self) -- create units for i = 1, config.UnitCount do - table.insert(self.units, - unit.new(i, self.cooling_conf.r_cool[i].BoilerCount, self.cooling_conf.r_cool[i].TurbineCount, config.ExtChargeIdling)) + table.insert(self.units, unit.new(i, self.cooling_conf.r_cool[i].BoilerCount, self.cooling_conf.r_cool[i].TurbineCount, config.ExtChargeIdling, self.cooling_conf.aux_coolant[i])) table.insert(self.group_map, AUTO_GROUP.MANUAL) + table.insert(self.last_unit_states, false) end -- list for RTU session management @@ -149,6 +166,70 @@ function facility.new(config) table.insert(self.test_tone_states, false) end + -- init next boot state + settings.set("LastProcessState", PROCESS.INACTIVE) + settings.set("LastUnitStates", self.last_unit_states) + if not settings.save("/supervisor.settings") then + log.warning("FAC: failed to save initial control state into supervisor settings file") + end + + --#endregion + + -- PRIVATE FUNCTIONS -- + + -- check an auto process control configuration and save it if its valid (does not start the process) + ---@param auto_cfg start_auto_config configuration + ---@return boolean ready, number[] unit_limits + local function _auto_check_and_save(auto_cfg) + local ready = false + + -- load up current limits + local limits = {} + for i = 1, config.UnitCount do + limits[i] = self.units[i].get_control_inf().lim_br100 * 100 + end + + -- only allow changes if not running + if self.mode == PROCESS.INACTIVE then + if (type(auto_cfg.mode) == "number") and (auto_cfg.mode > PROCESS.INACTIVE) and (auto_cfg.mode <= PROCESS.GEN_RATE) then + self.mode_set = auto_cfg.mode + end + + if (type(auto_cfg.burn_target) == "number") and auto_cfg.burn_target >= 0.1 then + self.burn_target = auto_cfg.burn_target + end + + if (type(auto_cfg.charge_target) == "number") and auto_cfg.charge_target >= 0 then + self.charge_setpoint = auto_cfg.charge_target * CHARGE_SCALER + end + + if (type(auto_cfg.gen_target) == "number") and auto_cfg.gen_target >= 0 then + self.gen_rate_setpoint = auto_cfg.gen_target * GEN_SCALER + end + + if (type(auto_cfg.limits) == "table") and (#auto_cfg.limits == config.UnitCount) then + for i = 1, config.UnitCount do + local limit = auto_cfg.limits[i] + + if (type(limit) == "number") and (limit >= 0.1) then + limits[i] = limit + self.units[i].set_burn_limit(limit) + end + end + end + + ready = self.mode_set > 0 + + if ((self.mode_set == PROCESS.CHARGE) and (self.charge_setpoint <= 0)) or + ((self.mode_set == PROCESS.GEN_RATE) and (self.gen_rate_setpoint <= 0)) or + ((self.mode_set == PROCESS.BURN_RATE) and (self.burn_target < 0.1)) then + ready = false + end + end + + return ready, limits + end + -- PUBLIC FUNCTIONS -- ---@class facility @@ -239,6 +320,9 @@ function facility.new(config) -- update (iterate) the facility management function public.update() + -- run reboot recovery routine if needed + f_update.boot_recovery() + -- run process control and evaluate automatic SCRAM f_update.pre_auto() f_update.auto_control(config.ExtChargeIdling) @@ -267,6 +351,50 @@ function facility.new(config) --#endregion + --#region Startup Recovery + + -- on exit, use this to clear the boot state so we don't resume when exiting cleanly + function public.clear_boot_state() + settings.unset("LastProcessState") + settings.unset("LastUnitStates") + + if not settings.save("/supervisor.settings") then + log.warning("facility.clear_boot_state(): failed to save supervisor settings file") + else + log.debug("FAC: cleared boot state on exit") + end + end + + -- initialize facility resume boot recovery + ---@param state sv_boot_state|nil + function public.boot_recovery_init(state) + if self.recovery == RCV_STATE.INACTIVE and state then + self.recovery_boot_state = state + self.recovery = RCV_STATE.PRIMED + log.info("FAC: startup resume ready") + end + end + + -- attempt facility resume boot recovery + ---@param auto_cfg start_auto_config configuration + function public.boot_recovery_start(auto_cfg) + if self.recovery == RCV_STATE.PRIMED then + self.recovery = util.trinary(_auto_check_and_save(auto_cfg), RCV_STATE.RUNNING, RCV_STATE.STOPPED) + log.info(util.c("FAC: startup resume ", util.trinary(self.recovery == RCV_STATE.RUNNING, "started", "failed"))) + else self.recovery = RCV_STATE.STOPPED end + end + + -- used on certain coordinator commands to end reboot recovery (remain in current operational state) + function public.cancel_recovery() + if self.recovery == RCV_STATE.RUNNING then + self.recovery = RCV_STATE.STOPPED + self.recovery_boot_state = nil + log.info("FAC: process startup resume cancelled by user operation") + end + end + + --#endregion + --#region Commands -- SCRAM all reactor units @@ -290,59 +418,13 @@ function facility.new(config) function public.auto_stop() self.mode = PROCESS.INACTIVE end -- set automatic control configuration and start the process - ---@param auto_cfg sys_auto_config configuration + ---@param auto_cfg start_auto_config configuration ---@return table response ready state (successfully started) and current configuration (after updating) function public.auto_start(auto_cfg) - local charge_scaler = 1000000 -- convert MFE to FE - local gen_scaler = 1000 -- convert kFE to FE - local ready = false + local ready, limits = _auto_check_and_save(auto_cfg) - -- load up current limits - local limits = {} - for i = 1, config.UnitCount do - limits[i] = self.units[i].get_control_inf().lim_br100 * 100 - end - - -- only allow changes if not running - if self.mode == PROCESS.INACTIVE then - if (type(auto_cfg.mode) == "number") and (auto_cfg.mode > PROCESS.INACTIVE) and (auto_cfg.mode <= PROCESS.GEN_RATE) then - self.mode_set = auto_cfg.mode - end - - if (type(auto_cfg.burn_target) == "number") and auto_cfg.burn_target >= 0.1 then - self.burn_target = auto_cfg.burn_target - end - - if (type(auto_cfg.charge_target) == "number") and auto_cfg.charge_target >= 0 then - self.charge_setpoint = auto_cfg.charge_target * charge_scaler - end - - if (type(auto_cfg.gen_target) == "number") and auto_cfg.gen_target >= 0 then - self.gen_rate_setpoint = auto_cfg.gen_target * gen_scaler - end - - if (type(auto_cfg.limits) == "table") and (#auto_cfg.limits == config.UnitCount) then - for i = 1, config.UnitCount do - local limit = auto_cfg.limits[i] - - if (type(limit) == "number") and (limit >= 0.1) then - limits[i] = limit - self.units[i].set_burn_limit(limit) - end - end - end - - ready = self.mode_set > 0 - - if ((self.mode_set == PROCESS.CHARGE) and (self.charge_setpoint <= 0)) or - ((self.mode_set == PROCESS.GEN_RATE) and (self.gen_rate_setpoint <= 0)) or - ((self.mode_set == PROCESS.BURN_RATE) and (self.burn_target < 0.1)) then - ready = false - end - - ready = ready and self.units_ready - - if ready then self.mode = self.mode_set end + if ready and self.units_ready then + self.mode = self.mode_set end log.debug(util.c("FAC: process start ", util.trinary(ready, "accepted", "rejected"))) @@ -351,8 +433,8 @@ function facility.new(config) ready, self.mode_set, self.burn_target, - self.charge_setpoint / charge_scaler, - self.gen_rate_setpoint / gen_scaler, + self.charge_setpoint / CHARGE_SCALER, + self.gen_rate_setpoint / GEN_SCALER, limits } end diff --git a/supervisor/facility_update.lua b/supervisor/facility_update.lua index 5e6fa07..a127f81 100644 --- a/supervisor/facility_update.lua +++ b/supervisor/facility_update.lua @@ -1,17 +1,21 @@ -local audio = require("scada-common.audio") -local const = require("scada-common.constants") -local log = require("scada-common.log") -local rsio = require("scada-common.rsio") -local types = require("scada-common.types") -local util = require("scada-common.util") +local audio = require("scada-common.audio") +local const = require("scada-common.constants") +local log = require("scada-common.log") +local rsio = require("scada-common.rsio") +local types = require("scada-common.types") +local util = require("scada-common.util") -local qtypes = require("supervisor.session.rtu.qtypes") +local plc = require("supervisor.session.plc") +local svsessions = require("supervisor.session.svsessions") + +local qtypes = require("supervisor.session.rtu.qtypes") local TONE = audio.TONE local ALARM = types.ALARM local PRIO = types.ALARM_PRIORITY local ALARM_STATE = types.ALARM_STATE +local AUTO_GROUP = types.AUTO_GROUP local CONTAINER_MODE = types.CONTAINER_MODE local PROCESS = types.PROCESS local PROCESS_NAMES = types.PROCESS_NAMES @@ -131,6 +135,54 @@ end --#region PUBLIC FUNCTIONS +-- run reboot recovery routine if needed +function update.boot_recovery() + local RCV_STATE = self.types.RCV_STATE + + -- attempt reboot recovery if in progress + if self.recovery == RCV_STATE.RUNNING then + local was_inactive = self.recovery_boot_state.mode == PROCESS.INACTIVE or self.recovery_boot_state.mode == PROCESS.SYSTEM_ALARM_IDLE + + -- try to start auto control + if self.recovery_boot_state.mode ~= nil and self.units_ready then + if not was_inactive then + self.mode = self.mode_set + log.info("FAC: process startup resume initiated") + end + + self.recovery_boot_state.mode = nil + end + + local recovered = self.recovery_boot_state.mode == nil or was_inactive + + -- restore manual control reactors + for i = 1, #self.units do + local u = self.units[i] + + if self.recovery_boot_state.unit_states[i] and self.group_map[i] == AUTO_GROUP.MANUAL then + recovered = false + + if u.get_control_inf().ready then + local plc_s = svsessions.get_reactor_session(i) + if plc_s ~= nil then + plc_s.in_queue.push_command(plc.PLC_S_CMDS.ENABLE) + log.info("FAC: startup resume enabling manually controlled reactor unit #" .. i) + + -- only execute once + self.recovery_boot_state.unit_states[i] = nil + end + end + end + end + + if recovered then + self.recovery = RCV_STATE.STOPPED + self.recovery_boot_state = nil + log.info("FAC: startup resume sequence completed") + end + end +end + -- automatic control pre-update logic function update.pre_auto() -- unlink RTU sessions if they are closed @@ -243,6 +295,11 @@ function update.auto_control(ExtChargeIdling) log.debug(util.c("FAC: state changed from ", PROCESS_NAMES[self.last_mode + 1], " to ", PROCESS_NAMES[self.mode + 1])) + settings.set("LastProcessState", self.mode) + if not settings.save("/supervisor.settings") then + log.warning("facility_update.auto_control(): failed to save supervisor settings file") + end + if (self.last_mode == PROCESS.INACTIVE) or (self.last_mode == PROCESS.GEN_RATE_FAULT_IDLE) then self.start_fail = START_STATUS.OK @@ -642,15 +699,16 @@ function update.auto_safety() self.ascram_reason = AUTO_SCRAM.NONE -- reset PLC RPS trips if we should - for i = 1, #self.units do - local u = self.units[i] - u.auto_cond_rps_reset() + for i = 1, #self.prio_defs do + for _, u in pairs(self.prio_defs[i]) do + u.auto_cond_rps_reset() + end end end end end --- update last mode and set next mode +-- update last mode, set next mode, and update saved state as needed function update.post_auto() self.last_mode = self.mode self.mode = next_mode @@ -792,6 +850,7 @@ end function update.unit_mgmt() local insufficent_po_rate = false local need_emcool = false + local write_state = false for i = 1, #self.units do local u = self.units[i] @@ -807,6 +866,21 @@ function update.unit_mgmt() if (self.cooling_conf.fac_tank_mode > 0) and u.is_emer_cool_tripped() and (self.cooling_conf.fac_tank_defs[i] == 2) then need_emcool = true end + + -- check for enabled state changes to save + if self.last_unit_states[i] ~= u.is_reactor_enabled() then + self.last_unit_states[i] = u.is_reactor_enabled() + write_state = true + end + end + + -- record unit control states + + if write_state then + settings.set("LastUnitStates", self.last_unit_states) + if not settings.save("/supervisor.settings") then + log.warning("facility_update.unit_mgmt(): failed to save supervisor settings file") + end end -- update waste product diff --git a/supervisor/panel/components/pdg_entry.lua b/supervisor/panel/components/pdg_entry.lua index 368f4bc..c9597c2 100644 --- a/supervisor/panel/components/pdg_entry.lua +++ b/supervisor/panel/components/pdg_entry.lua @@ -25,6 +25,8 @@ local function init(parent, id) local label_fg = style.fp.label_fg + local term_w, _ = term.getSize() + -- root div local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2} local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=style.theme.highlight_box_bright} @@ -40,9 +42,9 @@ local function init(parent, id) local pdg_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,fg_bg=label_fg} pdg_fw_v.register(databus.ps, ps_prefix .. "fw", pdg_fw_v.set_value) - TextBox{parent=entry,x=35,y=2,text="RTT:",width=4} - local pdg_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg} - TextBox{parent=entry,x=46,y=2,text="ms",width=4,fg_bg=label_fg} + TextBox{parent=entry,x=term_w-16,y=2,text="RTT:",width=4} + local pdg_rtt = DataIndicator{parent=entry,x=term_w-11,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg} + TextBox{parent=entry,x=term_w-5,y=2,text="ms",width=4,fg_bg=label_fg} pdg_rtt.register(databus.ps, ps_prefix .. "rtt", pdg_rtt.update) pdg_rtt.register(databus.ps, ps_prefix .. "rtt_color", pdg_rtt.recolor) diff --git a/supervisor/panel/components/rtu_entry.lua b/supervisor/panel/components/rtu_entry.lua index 2de0c0f..8038213 100644 --- a/supervisor/panel/components/rtu_entry.lua +++ b/supervisor/panel/components/rtu_entry.lua @@ -25,6 +25,8 @@ local function init(parent, id) local label_fg = style.fp.label_fg + local term_w, _ = term.getSize() + -- root div local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2} local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=style.theme.highlight_box_bright} @@ -40,13 +42,13 @@ local function init(parent, id) local unit_count = DataIndicator{parent=entry,x=17,y=2,label="",unit="",format="%2d",value=0,width=2,fg_bg=style.fp.label_d_fg} unit_count.register(databus.ps, ps_prefix .. "units", unit_count.set_value) - TextBox{parent=entry,x=21,y=2,text="FW:",width=3} - local rtu_fw_v = TextBox{parent=entry,x=25,y=2,text=" ------- ",width=9,fg_bg=label_fg} + TextBox{parent=entry,x=term_w-30,y=2,text="FW:",width=3} + local rtu_fw_v = TextBox{parent=entry,x=term_w-26,y=2,text=" ------- ",width=9,fg_bg=label_fg} rtu_fw_v.register(databus.ps, ps_prefix .. "fw", rtu_fw_v.set_value) - TextBox{parent=entry,x=36,y=2,text="RTT:",width=4} - local rtu_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg} - TextBox{parent=entry,x=46,y=2,text="ms",width=4,fg_bg=label_fg} + TextBox{parent=entry,x=term_w-15,y=2,text="RTT:",width=4} + local rtu_rtt = DataIndicator{parent=entry,x=term_w-11,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg} + TextBox{parent=entry,x=term_w-5,y=2,text="ms",width=4,fg_bg=label_fg} rtu_rtt.register(databus.ps, ps_prefix .. "rtt", rtu_rtt.update) rtu_rtt.register(databus.ps, ps_prefix .. "rtt_color", rtu_rtt.recolor) diff --git a/supervisor/panel/front_panel.lua b/supervisor/panel/front_panel.lua index e31d8bb..786fadf 100644 --- a/supervisor/panel/front_panel.lua +++ b/supervisor/panel/front_panel.lua @@ -41,6 +41,8 @@ local function init(panel) local label_fg = style.fp.label_fg local label_d_fg = style.fp.label_d_fg + local term_w, term_h = term.getSize() + TextBox{parent=panel,y=1,text="SCADA SUPERVISOR",alignment=ALIGN.CENTER,fg_bg=style.theme.header} local page_div = Div{parent=panel,x=1,y=3} @@ -73,9 +75,9 @@ local function init(panel) -- about footer -- - local about = Div{parent=main_page,width=15,height=3,x=1,y=16,fg_bg=style.fp.disabled_fg} - local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00"} - local comms_v = TextBox{parent=about,x=1,y=2,text="NT: v00.00.00"} + local about = Div{parent=main_page,width=15,height=2,y=term_h-3,fg_bg=style.fp.disabled_fg} + local fw_v = TextBox{parent=about,text="FW: v00.00.00"} + local comms_v = TextBox{parent=about,text="NT: v00.00.00"} fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end) comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end) @@ -87,7 +89,7 @@ local function init(panel) -- plc sessions page local plc_page = Div{parent=page_div,x=1,y=1,hidden=true} - local plc_list = Div{parent=plc_page,x=2,y=2,width=49} + local plc_list = Div{parent=plc_page,x=2,y=2,width=term_w-2} for i = 1, supervisor.config.UnitCount do local ps_prefix = "plc_" .. i .. "_" @@ -103,13 +105,13 @@ local function init(panel) local plc_addr = TextBox{parent=plc_entry,x=17,y=2,text=" --- ",width=5,fg_bg=label_d_fg} plc_addr.register(databus.ps, ps_prefix .. "addr", plc_addr.set_value) - TextBox{parent=plc_entry,x=23,y=2,text="FW:",width=3} - local plc_fw_v = TextBox{parent=plc_entry,x=27,y=2,text=" ------- ",width=9,fg_bg=label_fg} + TextBox{parent=plc_entry,x=term_w-28,y=2,text="FW:",width=3} + local plc_fw_v = TextBox{parent=plc_entry,x=term_w-24,y=2,text=" ------- ",width=9,fg_bg=label_fg} plc_fw_v.register(databus.ps, ps_prefix .. "fw", plc_fw_v.set_value) - TextBox{parent=plc_entry,x=37,y=2,text="RTT:",width=4} - local plc_rtt = DataIndicator{parent=plc_entry,x=42,y=2,label="",unit="",format="%4d",value=0,width=4,fg_bg=label_fg} - TextBox{parent=plc_entry,x=47,y=2,text="ms",width=4,fg_bg=label_fg} + TextBox{parent=plc_entry,x=term_w-14,y=2,text="RTT:",width=4} + local plc_rtt = DataIndicator{parent=plc_entry,x=term_w-9,y=2,label="",unit="",format="%4d",value=0,width=4,fg_bg=label_fg} + TextBox{parent=plc_entry,x=term_w-4,y=2,text="ms",width=4,fg_bg=label_fg} plc_rtt.register(databus.ps, ps_prefix .. "rtt", plc_rtt.update) plc_rtt.register(databus.ps, ps_prefix .. "rtt_color", plc_rtt.recolor) @@ -119,13 +121,13 @@ local function init(panel) -- rtu sessions page local rtu_page = Div{parent=page_div,x=1,y=1,hidden=true} - local rtu_list = ListBox{parent=rtu_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=cpair(colors.black,colors.ivory),nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)} + local rtu_list = ListBox{parent=rtu_page,y=1,height=term_h-2,width=term_w,scroll_height=1000,fg_bg=cpair(colors.black,colors.ivory),nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)} local _ = Div{parent=rtu_list,height=1} -- padding -- coordinator session page local crd_page = Div{parent=page_div,x=1,y=1,hidden=true} - local crd_box = Div{parent=crd_page,x=2,y=2,width=49,height=4,fg_bg=s_hi_bright} + local crd_box = Div{parent=crd_page,x=2,y=2,width=term_w-2,height=4,fg_bg=s_hi_bright} local crd_conn = LED{parent=crd_box,x=2,y=2,label="CONNECTION",colors=cpair(colors.green_hc,colors.green_off)} crd_conn.register(databus.ps, "crd_conn", crd_conn.update) @@ -138,27 +140,27 @@ local function init(panel) local crd_fw_v = TextBox{parent=crd_box,x=26,y=2,text=" ------- ",width=9,fg_bg=label_fg} crd_fw_v.register(databus.ps, "crd_fw", crd_fw_v.set_value) - TextBox{parent=crd_box,x=36,y=2,text="RTT:",width=4} - local crd_rtt = DataIndicator{parent=crd_box,x=41,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg} - TextBox{parent=crd_box,x=47,y=2,text="ms",width=4,fg_bg=label_fg} + TextBox{parent=crd_box,x=term_w-15,y=2,text="RTT:",width=4} + local crd_rtt = DataIndicator{parent=crd_box,x=term_w-10,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=label_fg} + TextBox{parent=crd_box,x=term_w-4,y=2,text="ms",width=4,fg_bg=label_fg} crd_rtt.register(databus.ps, "crd_rtt", crd_rtt.update) crd_rtt.register(databus.ps, "crd_rtt_color", crd_rtt.recolor) -- pocket sessions page - local pkt_page = Div{parent=page_div,x=1,y=1,hidden=true} - local pdg_list = ListBox{parent=pkt_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)} + local pkt_page = Div{parent=page_div,y=1,hidden=true} + local pdg_list = ListBox{parent=pkt_page,y=1,height=term_h-2,width=term_w,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)} local _ = Div{parent=pdg_list,height=1} -- padding -- RTU device ID check/diagnostics page - local chk_page = Div{parent=page_div,x=1,y=1,hidden=true} - local chk_list = ListBox{parent=chk_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)} + local chk_page = Div{parent=page_div,y=1,hidden=true} + local chk_list = ListBox{parent=chk_page,y=1,height=term_h-2,width=term_w,scroll_height=1000,fg_bg=style.fp.text_fg,nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)} local _ = Div{parent=chk_list,height=1} -- padding -- info page - local info_page = Div{parent=page_div,x=1,y=1,hidden=true} + local info_page = Div{parent=page_div,y=1,hidden=true} local info = Div{parent=info_page,height=6,x=2,y=2} TextBox{parent=info,text="SVR \x1a Supervisor Status"} @@ -168,7 +170,7 @@ local function init(panel) TextBox{parent=info,text="PKT \x1a Pocket Connections"} TextBox{parent=info,text="DEV \x1a RTU Device/Configuration Alerts"} - local notes = Div{parent=info_page,width=49,height=8,x=2,y=9,fg_bg=style.fp.disabled_fg} + local notes = Div{parent=info_page,width=term_w-2,height=8,x=2,y=9,fg_bg=style.fp.disabled_fg} TextBox{parent=notes,text="The DEV tab will show missing devices and devices that connected with incorrect information. Missing entries will indicate how the configuration should be, duplicate entries will indicate what is a duplicate, and out-of-range entries will indicate the invalid entry. An out-of-range example is a #2 turbine when you should only have 1 turbine for that unit."} diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index b011d44..4887f0f 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -234,6 +234,23 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim if pkt.type == CRDN_TYPE.INITIAL_BUILDS then -- acknowledgement to coordinator receiving builds self.acks.builds = true + elseif pkt.type == CRDN_TYPE.PROCESS_READY then + if pkt.length == 5 then + -- coordinator has sent all initial process data, power-on recovery is now possible + + ---@type start_auto_config + local config = { + mode = pkt.data[1], + burn_target = pkt.data[2], + charge_target = pkt.data[3], + gen_target = pkt.data[4], + limits = pkt.data[5] + } + + facility.boot_recovery_start(config) + else + log.debug(log_tag .. "CRDN process ready packet length mismatch") + end elseif pkt.type == CRDN_TYPE.FAC_BUILDS then -- acknowledgement to coordinator receiving builds self.acks.fac_builds = true @@ -243,8 +260,11 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim if cmd == FAC_COMMAND.SCRAM_ALL then facility.scram_all() + facility.cancel_recovery() _send(CRDN_TYPE.FAC_CMD, { cmd, true }) elseif cmd == FAC_COMMAND.STOP then + facility.cancel_recovery() + local was_active = facility.auto_is_active() if was_active then @@ -253,15 +273,16 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim _send(CRDN_TYPE.FAC_CMD, { cmd, was_active }) elseif cmd == FAC_COMMAND.START then + facility.cancel_recovery() + if pkt.length == 6 then - ---@type sys_auto_config ----@diagnostic disable-next-line: missing-fields + ---@class start_auto_config local config = { - mode = pkt.data[2], - burn_target = pkt.data[3], - charge_target = pkt.data[4], - gen_target = pkt.data[5], - limits = pkt.data[6] + mode = pkt.data[2], ---@type PROCESS + burn_target = pkt.data[3], ---@type number + charge_target = pkt.data[4], ---@type number + gen_target = pkt.data[5], ---@type number + limits = pkt.data[6] ---@type number[] } _send(CRDN_TYPE.FAC_CMD, { cmd, table.unpack(facility.auto_start(config)) }) @@ -313,8 +334,11 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim local manual = facility.get_group(uid) == AUTO_GROUP.MANUAL if cmd == UNIT_COMMAND.SCRAM then + facility.cancel_recovery() out_queue.push_data(SV_Q_DATA.SCRAM, data) elseif cmd == UNIT_COMMAND.START then + facility.cancel_recovery() + if manual then out_queue.push_data(SV_Q_DATA.START, data) else @@ -324,6 +348,8 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim elseif cmd == UNIT_COMMAND.RESET_RPS then out_queue.push_data(SV_Q_DATA.RESET_RPS, data) elseif cmd == UNIT_COMMAND.SET_BURN then + facility.cancel_recovery() + if pkt.length == 3 then if manual then out_queue.push_data(SV_Q_DATA.SET_BURN, data) @@ -354,6 +380,8 @@ function coordinator.new_session(id, s_addr, i_seq_num, in_queue, out_queue, tim log.debug(log_tag .. "CRDN unit command reset alarm missing alarm id") end elseif cmd == UNIT_COMMAND.SET_GROUP then + facility.cancel_recovery() + if (pkt.length == 3) and (type(pkt.data[3]) == "number") and (pkt.data[3] >= AUTO_GROUP.MANUAL) and (pkt.data[3] <= AUTO_GROUP.BACKUP) then facility.set_group(unit.get_id(), pkt.data[3]) diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index bedbd8b..4eea46d 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -53,15 +53,15 @@ local PERIODICS = { ---@param in_queue mqueue in message queue ---@param out_queue mqueue out message queue ---@param timeout number communications timeout +---@param initial_reset boolean[] initial PLC reset on timeout flags, indexed by reactor_id ---@param fp_ok boolean if the front panel UI is running -function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue, timeout, fp_ok) +function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue, timeout, initial_reset, fp_ok) -- print a log message to the terminal as long as the UI isn't running local function println(message) if not fp_ok then util.println_ts(message) end end local log_tag = "plc_session(" .. id .. "): " local self = { - commanded_state = false, commanded_burn_rate = 0.0, auto_cmd_token = 0, ramping_rate = false, @@ -72,6 +72,7 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue, connected = true, received_struct = false, received_status_cache = false, + received_rps_status = false, conn_watchdog = util.new_watchdog(timeout), last_rtt = 0, -- periodic messages @@ -381,6 +382,16 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue, local status = pcall(_copy_rps_status, pkt.data) if status then -- copied in RPS status data OK + self.received_rps_status = true + + -- try initial reset if needed + if initial_reset[reactor_id] then + initial_reset[reactor_id] = false + if self.sDB.rps_trip_cause == "timeout" then + _send(RPLC_TYPE.RPS_AUTO_RESET, {}) + log.debug(log_tag .. "initial RPS reset on timeout status sent") + end + end else -- error copying RPS status data log.error(log_tag .. "failed to parse RPS status packet data") @@ -394,6 +405,16 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue, local status = pcall(_copy_rps_status, { true, table.unpack(pkt.data) }) if status then -- copied in RPS status data OK + self.received_rps_status = true + + -- try initial reset if needed + if initial_reset[reactor_id] then + initial_reset[reactor_id] = false + if self.sDB.rps_trip_cause == "timeout" then + _send(RPLC_TYPE.RPS_AUTO_RESET, {}) + log.debug(log_tag .. "initial RPS reset on timeout alarm sent") + end + end else -- error copying RPS status data log.error(log_tag .. "failed to parse RPS alarm status data") @@ -487,6 +508,10 @@ function plc.new_session(id, s_addr, i_seq_num, reactor_id, in_queue, out_queue, ---@nodiscard function public.get_db() return self.sDB end + -- check if the reactor structure, status, and RPS status have been received + ---@nodiscard + function public.check_received_all_data() return self.received_struct and self.received_status_cache and self.received_rps_status end + -- check if ramping is completed by first verifying auto command token ack ---@nodiscard function public.is_ramp_complete() diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index ac216d6..087fe19 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -45,6 +45,7 @@ local self = { fp_ok = false, config = nil, ---@type svr_config facility = nil, ---@type facility|nil + plc_ini_reset = {}, -- lists of connected sessions ---@diagnostic disable: missing-fields sessions = { @@ -391,6 +392,7 @@ function svsessions.init(nic, fp_ok, config, facility) conns.tanks[1] = true end + self.plc_ini_reset[i] = true self.dev_dbg.connected.units[i] = conns end end @@ -486,7 +488,7 @@ function svsessions.establish_plc_session(source_addr, i_seq_num, for_reactor, v local id = self.next_ids.plc - plc_s.instance = plc.new_session(id, source_addr, i_seq_num, for_reactor, plc_s.in_queue, plc_s.out_queue, self.config.PLC_Timeout, self.fp_ok) + plc_s.instance = plc.new_session(id, source_addr, i_seq_num, for_reactor, plc_s.in_queue, plc_s.out_queue, self.config.PLC_Timeout, self.plc_ini_reset, self.fp_ok) table.insert(self.sessions.plc, plc_s) local units = self.facility.get_units() diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 32651da..d1ee16d 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -10,6 +10,7 @@ local log = require("scada-common.log") local network = require("scada-common.network") local ppm = require("scada-common.ppm") local tcd = require("scada-common.tcd") +local types = require("scada-common.types") local util = require("scada-common.util") local core = require("graphics.core") @@ -22,7 +23,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v1.6.2" +local SUPERVISOR_VERSION = "v1.6.8" local println = util.println local println_ts = util.println_ts @@ -72,6 +73,21 @@ if config.FacilityTankMode > 0 then cfv.assert_type_int(def) cfv.assert_range(def, 0, 2) assert(cfv.valid(), "startup> invalid facility tank definition for reactor unit " .. i) + + local entry = config.FacilityTankList[i] + cfv.assert_type_int(entry) + cfv.assert_range(entry, 0, 2) + assert(cfv.valid(), "startup> invalid facility tank list entry for tank " .. i) + + local conn = config.FacilityTankConns[i] + cfv.assert_type_int(conn) + cfv.assert_range(conn, 0, #config.FacilityTankDefs) + assert(cfv.valid(), "startup> invalid facility tank connection for reactor unit " .. i) + + local type = config.TankFluidTypes[i] + cfv.assert_type_int(type) + cfv.assert_range(type, 0, types.COOLANT_TYPE.SODIUM) + assert(cfv.valid(), "startup> invalid tank fluid type for tank " .. i) end end @@ -147,6 +163,9 @@ local function main() -- halve the rate heartbeat LED flash local heartbeat_toggle = true + -- init startup recovery + sv_facility.boot_recovery_init(supervisor.boot_state) + -- event loop while true do local event, param1, param2, param3, param4, param5 = util.pull_event() @@ -237,6 +256,8 @@ local function main() end end + sv_facility.clear_boot_state() + renderer.close_ui() util.println_ts("exited") diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index fe7b011..b30e218 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -19,10 +19,24 @@ local config = {} supervisor.config = config --- load the supervisor configuration +-- control state from last unexpected shutdown +supervisor.boot_state = nil ---@type sv_boot_state|nil + +-- load the supervisor configuration and startup state function supervisor.load_config() if not settings.load("/supervisor.settings") then return false end + ---@class sv_boot_state + local boot_state = { + mode = settings.get("LastProcessState"), ---@type PROCESS + unit_states = settings.get("LastUnitStates") ---@type boolean[] + } + + -- only record boot state if likely valid + if type(boot_state.mode) == "number" and type(boot_state.unit_states) == "table" then + supervisor.boot_state = boot_state + end + config.UnitCount = settings.get("UnitCount") config.CoolingConfig = settings.get("CoolingConfig") config.FacilityTankMode = settings.get("FacilityTankMode") @@ -30,6 +44,7 @@ function supervisor.load_config() config.FacilityTankList = settings.get("FacilityTankList") config.FacilityTankConns = settings.get("FacilityTankConns") config.TankFluidTypes = settings.get("TankFluidTypes") + config.AuxiliaryCoolant = settings.get("AuxiliaryCoolant") config.ExtChargeIdling = settings.get("ExtChargeIdling") config.SVR_Channel = settings.get("SVR_Channel") @@ -64,6 +79,7 @@ function supervisor.load_config() cfv.assert_type_table(config.FacilityTankList) cfv.assert_type_table(config.FacilityTankConns) cfv.assert_type_table(config.TankFluidTypes) + cfv.assert_type_table(config.AuxiliaryCoolant) cfv.assert_range(config.FacilityTankMode, 0, 8) cfv.assert_type_bool(config.ExtChargeIdling) @@ -246,20 +262,32 @@ function supervisor.comms(_version, nic, fp_ok, facility) -- PLC linking request if packet.length == 4 and type(packet.data[4]) == "number" then local reactor_id = packet.data[4] - local plc_id = svsessions.establish_plc_session(src_addr, i_seq_num, reactor_id, firmware_v) - if plc_id == false then - -- reactor already has a PLC assigned - if last_ack ~= ESTABLISH_ACK.COLLISION then - log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) + -- check ID validity + if reactor_id < 1 or reactor_id > config.UnitCount then + -- reactor index out of range + if last_ack ~= ESTABLISH_ACK.DENY then + log.warning(util.c("PLC_ESTABLISH: denied assignment ", reactor_id, " outside of configured unit count ", config.UnitCount)) end - _send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION) + _send_establish(packet.scada_frame, ESTABLISH_ACK.DENY) else - -- got an ID; assigned to a reactor successfully - println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected")) - log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id)) - _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) + -- try to establish the session + local plc_id = svsessions.establish_plc_session(src_addr, i_seq_num, reactor_id, firmware_v) + + if plc_id == false then + -- reactor already has a PLC assigned + if last_ack ~= ESTABLISH_ACK.COLLISION then + log.warning(util.c("PLC_ESTABLISH: assignment collision with reactor ", reactor_id)) + end + + _send_establish(packet.scada_frame, ESTABLISH_ACK.COLLISION) + else + -- got an ID; assigned to a reactor successfully + println(util.c("PLC (", firmware_v, ") [@", src_addr, "] \xbb reactor ", reactor_id, " connected")) + log.info(util.c("PLC_ESTABLISH: PLC (", firmware_v, ") [@", src_addr, "] reactor unit ", reactor_id, " PLC connected with session ID ", plc_id)) + _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW) + end end else log.debug("PLC_ESTABLISH: packet length mismatch/bad parameter type") diff --git a/supervisor/unit.lua b/supervisor/unit.lua index dc59cff..b448a95 100644 --- a/supervisor/unit.lua +++ b/supervisor/unit.lua @@ -66,7 +66,8 @@ local unit = {} ---@param num_boilers integer number of boilers expected ---@param num_turbines integer number of turbines expected ---@param ext_idle boolean extended idling mode -function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) +---@param aux_coolant boolean if this unit has auxiliary coolant +function unit.new(reactor_id, num_boilers, num_turbines, ext_idle, aux_coolant) -- time (ms) to idle for auto idling local IDLE_TIME = util.trinary(ext_idle, 60000, 10000) @@ -79,6 +80,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) plc_i = nil, ---@type plc_session num_boilers = num_boilers, num_turbines = num_turbines, + aux_coolant = aux_coolant, types = { DT_KEYS = DT_KEYS, AISTATE = AISTATE }, -- rtus rtu_list = {}, ---@type unit_session[][] @@ -92,7 +94,8 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) io_ctl = nil, ---@type rs_controller ---@diagnostic disable-next-line: missing-fields valves = {}, ---@type unit_valves - emcool_opened = false, + em_cool_opened = false, + aux_cool_opened = false, -- auto control auto_engaged = false, auto_idle = false, @@ -111,6 +114,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) damage_est_last = 0, waste_product = WASTE.PLUTONIUM, ---@type WASTE_PRODUCT status_text = { "UNKNOWN", "awaiting connection..." }, + enable_aux_cool = false, -- logic for alarms had_reactor = false, turbine_flow_stable = false, @@ -373,6 +377,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) local waste_po = _make_valve_iface(IO.WASTE_POPL) local waste_sps = _make_valve_iface(IO.WASTE_AM) local emer_cool = _make_valve_iface(IO.U_EMER_COOL) + local aux_cool = _make_valve_iface(IO.U_AUX_COOL) ---@class unit_valves self.valves = { @@ -380,7 +385,8 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) waste_sna = waste_sna, waste_po = waste_po, waste_sps = waste_sps, - emer_cool = emer_cool + emer_cool = emer_cool, + aux_cool = aux_cool } -- route reactor waste for a given waste product @@ -606,7 +612,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) if #self.redstone > 0 then logic.handle_redstone(self) elseif not self.plc_cache.rps_trip then - self.emcool_opened = false + self.em_cool_opened = false end end @@ -724,7 +730,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) -- queue a command to clear timeout/auto-scram if set function public.auto_cond_rps_reset() - if self.plc_s ~= nil and self.plc_i ~= nil and (not self.auto_was_alarmed) and (not self.emcool_opened) then + if self.plc_s ~= nil and self.plc_i ~= nil and (not self.auto_was_alarmed) and (not self.em_cool_opened) then local rps = self.plc_i.get_rps() if rps.timeout or rps.automatic then self.plc_i.auto_lock(true) -- if it timed out/restarted, auto lock was lost, so re-lock it @@ -840,6 +846,12 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) return false end + -- check the active state of the reactor (if connected) + ---@nodiscard + function public.is_reactor_enabled() + if self.plc_i ~= nil then return self.plc_i.get_status().status else return false end + end + -- check if the reactor is connected, is stopped, the RPS is not tripped, and no alarms are active ---@nodiscard function public.is_safe_idle() @@ -859,7 +871,7 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) -- check if emergency coolant activation has been tripped ---@nodiscard - function public.is_emer_cool_tripped() return self.emcool_opened end + function public.is_emer_cool_tripped() return self.em_cool_opened end -- get build properties of machines -- @@ -1053,7 +1065,8 @@ function unit.new(reactor_id, num_boilers, num_turbines, ext_idle) v.waste_sna.check(), v.waste_po.check(), v.waste_sps.check(), - v.emer_cool.check() + v.emer_cool.check(), + v.aux_cool.check() } end diff --git a/supervisor/unitlogic.lua b/supervisor/unitlogic.lua index f0e4c33..6bd7470 100644 --- a/supervisor/unitlogic.lua +++ b/supervisor/unitlogic.lua @@ -35,6 +35,7 @@ local FLOW_STABILITY_DELAY_MS = const.FLOW_STABILITY_DELAY_MS local ANNUNC_LIMS = const.ANNUNCIATOR_LIMITS local ALARM_LIMS = const.ALARM_LIMITS +local RS_THRESH = const.RS_THRESHOLDS ---@class unit_logic_extension local logic = {} @@ -54,6 +55,10 @@ function logic.update_annunciator(self) -- variables for boiler, or reactor if no boilers used local total_boil_rate = 0.0 + -- auxiliary coolant control + local need_aux_cool = false + local dis_aux_cool = true + --#region Reactor annunc.AutoControl = self.auto_engaged @@ -67,11 +72,10 @@ function logic.update_annunciator(self) 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 = plc_db.formed and (not plc_db.no_reactor) and (not plc_db.rps_tripped) and - (next(self.plc_i.get_status()) ~= nil) and (next(self.plc_i.get_struct()) ~= nil) + -- - must be connected to a formed reactor + -- - can't have a tripped RPS + -- - must have received status, struct, and RPS status at least once + plc_ready = plc_db.formed and (not plc_db.no_reactor) and (not plc_db.rps_tripped) and self.plc_i.check_received_all_data() -- update auto control limit if (plc_db.mek_struct.max_burn > 0) and ((self.db.control.lim_br100 / 100) > plc_db.mek_struct.max_burn) then @@ -149,6 +153,9 @@ function logic.update_annunciator(self) -- if no boilers, use reactor heating rate to check for boil rate mismatch if num_boilers == 0 then total_boil_rate = plc_db.mek_status.heating_rate + + need_aux_cool = plc_db.mek_status.ccool_fill <= RS_THRESH.AUX_COOL_ENABLE + dis_aux_cool = plc_db.mek_status.ccool_fill >= RS_THRESH.AUX_COOL_DISABLE end else self.plc_cache.ok = false @@ -216,6 +223,9 @@ function logic.update_annunciator(self) annunc.BoilerOnline[idx] = true annunc.WaterLevelLow[idx] = boiler.tanks.water_fill < ANNUNC_LIMS.WaterLevelLow + + need_aux_cool = need_aux_cool or (boiler.tanks.water_fill <= RS_THRESH.AUX_COOL_ENABLE) + dis_aux_cool = dis_aux_cool and (boiler.tanks.water_fill >= RS_THRESH.AUX_COOL_DISABLE) end -- check heating rate low @@ -342,11 +352,11 @@ function logic.update_annunciator(self) end if rotation_stable then - log.debug(util.c("UNIT ", self.r_id, ": turbine ", idx, " reached rotational stability (", rotation, ")")) + log.debug(util.c("UNIT ", self.r_id, " turbine ", idx, " reached rotational stability (", rotation, ")")) end if flow_stable then - log.debug(util.c("UNIT ", self.r_id, ": turbine ", idx, " reached flow stability (", turbine.state.flow_rate, " mB/t)")) + log.debug(util.c("UNIT ", self.r_id, " turbine ", idx, " reached flow stability (", turbine.state.flow_rate, " mB/t)")) end turbines_stable = turbines_stable and (rotation_stable or flow_stable) @@ -358,7 +368,7 @@ function logic.update_annunciator(self) turbines_stable = false - log.debug(util.c("UNIT ", self.r_id, ": turbine ", idx, " reset stability (new rate ", turbine.state.steam_input_rate, " != ", last.input_rate," mB/t)")) + log.debug(util.c("UNIT ", self.r_id, " turbine ", idx, " reset stability (new rate ", turbine.state.steam_input_rate, " != ", last.input_rate," mB/t)")) end last.input_rate = turbine.state.steam_input_rate @@ -407,6 +417,12 @@ function logic.update_annunciator(self) -- update auto control ready state for this unit self.db.control.ready = plc_ready and boilers_ready and turbines_ready + + -- update auxiliary coolant command + if plc_ready then + self.enable_aux_cool = self.plc_i.get_db().mek_status.status and + (self.enable_aux_cool or need_aux_cool) and not (dis_aux_cool and self.turbine_flow_stable) + else self.enable_aux_cool = false end end -- update an alarm state given conditions @@ -729,7 +745,7 @@ function logic.update_status_text(self) self.status_text = { "RCS TRANSIENT", "check coolant system" } -- elseif is_active(self.alarms.RPSTransient) then -- RPS status handled when checking reactor status - elseif self.emcool_opened then + elseif self.em_cool_opened then self.status_text = { "EMERGENCY COOLANT OPENED", "reset RPS to close valve" } -- connection dependent states elseif self.plc_i ~= nil then @@ -887,7 +903,7 @@ function logic.handle_redstone(self) (annunc.CoolantLevelLow or (boiler_water_low and rps.ex_hcool)) and is_active(self.alarms.ReactorOverTemp)) - if enable_emer_cool and not self.emcool_opened then + if enable_emer_cool and not self.em_cool_opened then log.debug(util.c(">> Emergency Coolant Enable Detail Report (Unit ", self.r_id, ") <<")) log.debug(util.c("| CoolantLevelLow[", annunc.CoolantLevelLow, "] CoolantLevelLowLow[", rps.low_cool, "] ExcessHeatedCoolant[", rps.ex_hcool, "]")) log.debug(util.c("| ReactorOverTemp[", AISTATE_NAMES[self.alarms.ReactorOverTemp.state], "]")) @@ -911,13 +927,13 @@ function logic.handle_redstone(self) end end - if annunc.EmergencyCoolant > 1 and self.emcool_opened then + if annunc.EmergencyCoolant > 1 and self.em_cool_opened then log.info(util.c("UNIT ", self.r_id, " emergency coolant valve closed")) log.info(util.c("UNIT ", self.r_id, " turbines set to not dump steam")) end - self.emcool_opened = false - elseif enable_emer_cool or self.emcool_opened then + self.em_cool_opened = false + elseif enable_emer_cool or self.em_cool_opened then -- set turbines to dump excess steam for i = 1, #self.turbines do local session = self.turbines[i] @@ -938,16 +954,33 @@ function logic.handle_redstone(self) end end - if annunc.EmergencyCoolant > 1 and not self.emcool_opened then + if annunc.EmergencyCoolant > 1 and not self.em_cool_opened then log.info(util.c("UNIT ", self.r_id, " emergency coolant valve opened")) log.info(util.c("UNIT ", self.r_id, " turbines set to dump excess steam")) end - self.emcool_opened = true + self.em_cool_opened = true end -- set valve state always - if self.emcool_opened then self.valves.emer_cool.open() else self.valves.emer_cool.close() end + if self.em_cool_opened then self.valves.emer_cool.open() else self.valves.emer_cool.close() end + + ----------------------- + -- Auxiliary Coolant -- + ----------------------- + + if self.aux_coolant then + if self.enable_aux_cool and (not self.aux_cool_opened) then + log.info(util.c("UNIT ", self.r_id, " auxiliary coolant valve opened")) + self.aux_cool_opened = true + elseif (not self.enable_aux_cool) and self.aux_cool_opened then + log.info(util.c("UNIT ", self.r_id, " auxiliary coolant valve closed")) + self.aux_cool_opened = false + end + + -- set valve state always + if self.aux_cool_opened then self.valves.aux_cool.open() else self.valves.aux_cool.close() end + end end return logic