diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index b5c09e6..36636bc 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -243,12 +243,13 @@ end ---@nodiscard ---@param version string coordinator version ---@param nic nic network interface device +---@param num_units integer number of configured units for number of monitors, checked against SV ---@param crd_channel integer port of configured supervisor ---@param svr_channel integer listening port for supervisor replys ---@param pkt_channel integer listening port for pocket API ---@param range integer trusted device connection range ---@param sv_watchdog watchdog -function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel, range, sv_watchdog) +function coordinator.comms(version, nic, num_units, crd_channel, svr_channel, pkt_channel, range, sv_watchdog) local self = { sv_linked = false, sv_addr = comms.BROADCAST, @@ -707,21 +708,16 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel, -- reset to disconnected before validating iocontrol.fp_link_state(types.PANEL_LINK_STATE.DISCONNECTED) - if type(config) == "table" and #config > 1 then + if type(config) == "table" and #config == 2 then -- get configuration ---@class facility_conf local conf = { num_units = config[1], ---@type integer - defs = {} -- boilers and turbines + cooling = config[2] ---@type sv_cooling_conf } - if (#config - 1) == (conf.num_units * 2) then - -- record sequence of pairs of [#boilers, #turbines] per unit - for i = 2, #config do - table.insert(conf.defs, config[i]) - end - + if conf.num_units == num_units then -- init io controller iocontrol.init(conf, public) @@ -733,7 +729,7 @@ function coordinator.comms(version, nic, crd_channel, svr_channel, pkt_channel, iocontrol.fp_link_state(types.PANEL_LINK_STATE.LINKED) else self.sv_config_err = true - log.warning("invalid supervisor configuration definitions received, establish failed") + log.warning("supervisor config's number of units don't match coordinator's config, establish failed") end else log.debug("invalid supervisor configuration table received, establish failed") diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index ce6d667..d2ee8ef 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -38,9 +38,7 @@ local function __generic_ack(success) end ---@param comms_v string comms version function iocontrol.init_fp(firmware_v, comms_v) ---@class ioctl_front_panel - io.fp = { - ps = psil.create() - } + io.fp = { ps = psil.create() } io.fp.ps.publish("version", firmware_v) io.fp.ps.publish("comms_version", comms_v) @@ -52,7 +50,9 @@ end function iocontrol.init(conf, comms) ---@class ioctl_facility io.facility = { - num_units = conf.num_units, ---@type integer + num_units = conf.num_units, + tank_mode = conf.cooling.fac_tank_mode, + tank_defs = conf.cooling.fac_tank_list, all_sys_ok = false, rtu_count = 0, @@ -116,6 +116,7 @@ function iocontrol.init(conf, comms) num_boilers = 0, num_turbines = 0, num_snas = 0, + has_tank = conf.cooling.r_cool[i].TANK, control_state = false, burn_rate_cmd = 0.0, @@ -191,14 +192,14 @@ function iocontrol.init(conf, comms) } -- create boiler tables - for _ = 1, conf.defs[(i * 2) - 1] do + for _ = 1, conf.cooling.r_cool[i].BOILERS do local data = {} ---@type boilerv_session_db table.insert(entry.boiler_ps_tbl, psil.create()) table.insert(entry.boiler_data_tbl, data) end -- create turbine tables - for _ = 1, conf.defs[i * 2] do + for _ = 1, conf.cooling.r_cool[i].TURBINES do local data = {} ---@type turbinev_session_db table.insert(entry.turbine_ps_tbl, psil.create()) table.insert(entry.turbine_data_tbl, data) @@ -210,6 +211,18 @@ function iocontrol.init(conf, comms) table.insert(io.units, entry) end + -- on facility tank mode 0, setup tank list to match unit TANK option + if io.facility.tank_mode == 0 then + for i = 1, #io.units do + io.facility.tank_defs[i] = util.trinary(conf.cooling.r_cool[i].TANK, 1, 0) + end + -- on other facility modes, overwrite unit TANK option with facility tank list + else + for i = 1, #io.units do + io.units[i].has_tank = conf.cooling.fac_tank_list[i] > 0 + end + end + -- pass IO control here since it can't be require'd due to a require loop process.init(io, comms) end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 9951d20..f22326b 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -162,8 +162,8 @@ local function main() -- create network interface then setup comms local nic = network.nic(modem) - local coord_comms = coordinator.comms(COORDINATOR_VERSION, nic, config.CRD_CHANNEL, config.SVR_CHANNEL, - config.PKT_CHANNEL, config.TRUSTED_RANGE, conn_watchdog) + local coord_comms = coordinator.comms(COORDINATOR_VERSION, nic, config.NUM_UNITS, config.CRD_CHANNEL, + config.SVR_CHANNEL, config.PKT_CHANNEL, config.TRUSTED_RANGE, conn_watchdog) log.debug("startup> comms init") log_comms("comms initialized") diff --git a/coordinator/ui/components/unit_flow.lua b/coordinator/ui/components/unit_flow.lua index efef2dc..2c8db41 100644 --- a/coordinator/ui/components/unit_flow.lua +++ b/coordinator/ui/components/unit_flow.lua @@ -38,8 +38,9 @@ local ind_wht = style.ind_wht ---@param parent graphics_element parent ---@param x integer top left x ---@param y integer top left y +---@param wide boolean whether to render wide version ---@param unit ioctl_unit unit database entry -local function make(parent, x, y, unit) +local function make(parent, x, y, wide, unit) local height = 16 local v_start = 1 + ((unit.unit_id - 1) * 4) @@ -56,8 +57,10 @@ local function make(parent, x, y, unit) assert(parent.get_height() >= (y + height), "flow display not of sufficient vertical resolution (add an additional row of monitors) " .. y .. "," .. parent.get_height()) + local function _wide(a, b) return util.trinary(wide, a, b) end + -- bounding box div - local root = Div{parent=parent,x=x,y=y,width=114,height=height} + local root = Div{parent=parent,x=x,y=y,width=_wide(136, 114),height=height} local lg_gray = cpair(colors.lightGray, colors.gray) local wh_gray = cpair(colors.white, colors.gray) @@ -70,46 +73,62 @@ local function make(parent, x, y, unit) TextBox{parent=reactor,y=1,text="FISSION REACTOR",alignment=TEXT_ALIGN.CENTER,height=1} TextBox{parent=reactor,y=3,text="UNIT #"..unit.unit_id,alignment=TEXT_ALIGN.CENTER,height=1} TextBox{parent=root,x=19,y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray} - TextBox{parent=root,x=4,y=5,text="\x19",width=1,height=1,fg_bg=lg_gray} + TextBox{parent=root,x=3,y=5,text="\x19",width=1,height=1,fg_bg=lg_gray} local rc_pipes = {} - table.insert(rc_pipes, pipe(0, 1, 19, 1, colors.lightBlue, true)) - table.insert(rc_pipes, pipe(0, 3, 19, 3, colors.orange, true)) - table.insert(rc_pipes, pipe(39, 1, 58, 1, colors.blue, true)) - table.insert(rc_pipes, pipe(39, 3, 58, 3, colors.white, true)) + local emc_x = 42 -- emergency coolant connection x point - table.insert(rc_pipes, pipe(78, 0, 83, 0, colors.white, true)) - table.insert(rc_pipes, pipe(78, 2, 83, 2, colors.white, true)) - table.insert(rc_pipes, pipe(78, 4, 83, 4, colors.white, true)) + 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)) + else + emc_x = 3 + 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)) + end + + if unit.has_tank then + table.insert(rc_pipes, pipe(emc_x, 1, emc_x, 0, colors.blue, true, true)) + end + + local prv_yo = math.max(3 - unit.num_turbines, 0) + for i = 1, unit.num_turbines do + local py = 2 * (i - 1) + prv_yo + table.insert(rc_pipes, pipe(_wide(92, 78), py, _wide(104, 83), py, colors.white, true)) + end PipeNetwork{parent=root,x=20,y=1,pipes=rc_pipes,bg=colors.lightGray} - local hc_rate = DataIndicator{parent=root,x=22,y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=287000000,commas=true,width=16,fg_bg=bw_fg_bg} - local cc_rate = DataIndicator{parent=root,x=22,y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=287000000,commas=true,width=16,fg_bg=bw_fg_bg} + if unit.num_boilers > 0 then + local hc_rate = DataIndicator{parent=root,x=_wide(25,22),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=287000000,commas=true,width=16,fg_bg=bw_fg_bg} + local cc_rate = DataIndicator{parent=root,x=_wide(25,22),y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=287000000,commas=true,width=16,fg_bg=bw_fg_bg} - local boiler = Rectangle{parent=root,x=40,y=1,border=border(1, colors.gray, true),width=19,height=5,fg_bg=wh_gray} - TextBox{parent=boiler,y=1,text="THERMO-ELECTRIC",alignment=TEXT_ALIGN.CENTER,height=1} - TextBox{parent=boiler,y=3,text="BOILERS",alignment=TEXT_ALIGN.CENTER,height=1} - TextBox{parent=root,x=40,y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray} - TextBox{parent=root,x=58,y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray} + local boiler = Rectangle{parent=root,x=_wide(47,40),y=1,border=border(1, colors.gray, true),width=19,height=5,fg_bg=wh_gray} + TextBox{parent=boiler,y=1,text="THERMO-ELECTRIC",alignment=TEXT_ALIGN.CENTER,height=1} + TextBox{parent=boiler,y=3,text="BOILERS",alignment=TEXT_ALIGN.CENTER,height=1} + TextBox{parent=root,x=_wide(47,40),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray} + TextBox{parent=root,x=_wide(65,58),y=2,text="\x1b \x80 \x1a",width=1,height=3,fg_bg=lg_gray} - local wt_rate = DataIndicator{parent=root,x=61,y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=287000000,commas=true,width=16,fg_bg=bw_fg_bg} - local st_rate = DataIndicator{parent=root,x=61,y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=287000000,commas=true,width=16,fg_bg=bw_fg_bg} - - local turbine = Rectangle{parent=root,x=79,y=1,border=border(1, colors.gray, true),width=19,height=5,fg_bg=wh_gray} - TextBox{parent=turbine,y=1,text="STEAM TURBINE",alignment=TEXT_ALIGN.CENTER,height=1} - TextBox{parent=turbine,y=3,text="GENERATORS",alignment=TEXT_ALIGN.CENTER,height=1} - TextBox{parent=root,x=79,y=2,text="\x1a \x80 \x1b",width=1,height=3,fg_bg=lg_gray} - - local function _relief(rx, ry, name) - TextBox{parent=root,x=rx,y=ry,text="\x10\x11\x7f",fg_bg=text_c,width=3,height=1} - local conn = TriIndicatorLight{parent=root,x=rx+4,y=ry,label=name,c1=colors.gray,c2=colors.yellow,c3=colors.red} + local wt_rate = DataIndicator{parent=root,x=_wide(71,61),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=287000000,commas=true,width=16,fg_bg=bw_fg_bg} + local st_rate = DataIndicator{parent=root,x=_wide(71,61),y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=287000000,commas=true,width=16,fg_bg=bw_fg_bg} + else + local wt_rate = DataIndicator{parent=root,x=28,y=3,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=287000000,commas=true,width=16,fg_bg=bw_fg_bg} + local st_rate = DataIndicator{parent=root,x=28,y=5,lu_colors=lu_c,label="",unit="mB/t",format="%11.0f",value=287000000,commas=true,width=16,fg_bg=bw_fg_bg} end - _relief(103, 1, v_names[5]) - _relief(103, 3, v_names[6]) - _relief(103, 5, v_names[7]) + local turbine = Rectangle{parent=root,x=_wide(93,79),y=1,border=border(1, colors.gray, true),width=19,height=5,fg_bg=wh_gray} + TextBox{parent=turbine,y=1,text="STEAM TURBINE",alignment=TEXT_ALIGN.CENTER,height=1} + TextBox{parent=turbine,y=3,text="GENERATORS",alignment=TEXT_ALIGN.CENTER,height=1} + TextBox{parent=root,x=_wide(93,79),y=2,text="\x1a \x80 \x1b",width=1,height=3,fg_bg=lg_gray} + + for i = 1, unit.num_turbines do + local ry = 1 + (2 * (i - 1)) + prv_yo + TextBox{parent=root,x=_wide(125,103),y=ry,text="\x10\x11\x7f",fg_bg=text_c,width=3,height=1} + local state = TriIndicatorLight{parent=root,x=_wide(129,107),y=ry,label=v_names[i+4],c1=colors.gray,c2=colors.yellow,c3=colors.red} + end ---------------------- -- WASTE PROCESSING -- @@ -118,21 +137,24 @@ local function make(parent, x, y, unit) local waste = Div{parent=root,x=3,y=6} local waste_pipes = { - pipe(0, 0, 16, 1, colors.brown, true), - pipe(12, 1, 16, 5, colors.brown, true), - pipe(18, 1, 44, 1, colors.brown, true), - pipe(18, 5, 23, 5, colors.brown, true), - pipe(52, 1, 80, 1, colors.green, true), - pipe(42, 4, 60, 4, colors.cyan, true), - pipe(56, 4, 60, 8, colors.cyan, true), - pipe(62, 4, 80, 4, colors.cyan, true), - pipe(62, 8, 110, 8, colors.cyan, true), - pipe(93, 1, 94, 3, colors.black, true, true), - pipe(93, 4, 109, 6, colors.black, true, true), - pipe(109, 6, 107, 6, colors.black, true, true) + pipe(0, 0, _wide(19, 16), 1, colors.brown, true), + pipe(_wide(14, 13), 1, _wide(19, 17), 5, colors.brown, true), + pipe(_wide(22, 19), 1, _wide(49, 45), 1, colors.brown, true), + pipe(_wide(22, 19), 5, _wide(28, 24), 5, colors.brown, true), + + pipe(_wide(64, 53), 1, _wide(95, 81), 1, colors.green, true), + + pipe(_wide(48, 43), 4, _wide(71, 61), 4, colors.cyan, true), + pipe(_wide(66, 57), 4, _wide(71, 61), 8, colors.cyan, true), + pipe(_wide(74, 63), 4, _wide(95, 81), 4, colors.cyan, true), + pipe(_wide(74, 63), 8, _wide(133, 111), 8, colors.cyan, true), + + pipe(_wide(108, 94), 1, _wide(132, 110), 6, colors.black, true, true), + pipe(_wide(108, 94), 4, _wide(111, 95), 1, colors.black, true, true), + pipe(_wide(132, 110), 6, _wide(130, 108), 6, colors.black, true, true) } - PipeNetwork{parent=waste,x=2,y=1,pipes=waste_pipes,bg=colors.lightGray} + PipeNetwork{parent=waste,x=1,y=1,pipes=waste_pipes,bg=colors.lightGray} local function _valve(vx, vy, n) TextBox{parent=waste,x=vx,y=vy,text="\x10\x11",fg_bg=text_c,width=2,height=1} @@ -142,29 +164,29 @@ local function make(parent, x, y, unit) local function _machine(mx, my, name) local l = string.len(name) + 2 - TextBox{parent=waste,x=mx,y=my,text=util.strrep("\x8f",l),alignment=TEXT_ALIGN.CENTER,fg_bg=lg_gray,width=l,height=1} + TextBox{parent=waste,x=mx,y=my,text=string.rep("\x8f",l),alignment=TEXT_ALIGN.CENTER,fg_bg=lg_gray,width=l,height=1} TextBox{parent=waste,x=mx,y=my+1,text=name,alignment=TEXT_ALIGN.CENTER,fg_bg=wh_gray,width=l,height=1} end local waste_rate = DataIndicator{parent=waste,x=1,y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.2f",value=1234.56,width=12,fg_bg=bw_fg_bg} - local pu_rate = DataIndicator{parent=waste,x=70,y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=bw_fg_bg} - local po_rate = DataIndicator{parent=waste,x=45,y=6,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=bw_fg_bg} - local popl_rate = DataIndicator{parent=waste,x=70,y=6,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=bw_fg_bg} - local poam_rate = DataIndicator{parent=waste,x=70,y=10,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=bw_fg_bg} - local spent_rate = DataIndicator{parent=waste,x=99,y=4,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=bw_fg_bg} + local pu_rate = DataIndicator{parent=waste,x=_wide(82,70),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=bw_fg_bg} + local po_rate = DataIndicator{parent=waste,x=_wide(52,45),y=6,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=bw_fg_bg} + local popl_rate = DataIndicator{parent=waste,x=_wide(82,70),y=6,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=bw_fg_bg} + local poam_rate = DataIndicator{parent=waste,x=_wide(82,70),y=10,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=bw_fg_bg} + local spent_rate = DataIndicator{parent=waste,x=_wide(117,99),y=3,lu_colors=lu_c,label="",unit="mB/t",format="%7.3f",value=123.456,width=12,fg_bg=bw_fg_bg} - _valve(18, 2, 1) - _valve(18, 6, 2) - _valve(62, 5, 3) - _valve(62, 9, 4) + _valve(_wide(21, 18), 2, 1) + _valve(_wide(21, 18), 6, 2) + _valve(_wide(73, 62), 5, 3) + _valve(_wide(73, 62), 9, 4) - _machine(45, 1, "CENTRIFUGE \x1a"); - _machine(83, 1, "PRC [Pu] \x1a"); - _machine(83, 4, "PRC [Po] \x1a"); - _machine(94, 6, "SPENT WASTE \x1b") + _machine(_wide(51, 45), 1, "CENTRIFUGE \x1a"); + _machine(_wide(97, 83), 1, "PRC [Pu] \x1a"); + _machine(_wide(97, 83), 4, "PRC [Po] \x1a"); + _machine(_wide(116, 94), 6, "SPENT WASTE \x1b") - TextBox{parent=waste,x=25,y=3,text="SNAs [Po]",alignment=TEXT_ALIGN.CENTER,width=19,height=1,fg_bg=wh_gray} - local sna_po = Rectangle{parent=waste,x=25,y=4,border=border(1, colors.gray, true),width=19,height=7,thin=true,fg_bg=bw_fg_bg} + TextBox{parent=waste,x=_wide(30,25),y=3,text="SNAs [Po]",alignment=TEXT_ALIGN.CENTER,width=19,height=1,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=bw_fg_bg} 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,label="CNT",unit="",format="%2d",value=99,width=7} local sna_pk = DataIndicator{parent=sna_po,y=3,lu_colors=lu_c,label="PEAK",unit="mB/t",format="%7.2f",value=1000,width=17} diff --git a/coordinator/ui/layout/flow_view.lua b/coordinator/ui/layout/flow_view.lua index 26a1617..f309b63 100644 --- a/coordinator/ui/layout/flow_view.lua +++ b/coordinator/ui/layout/flow_view.lua @@ -38,6 +38,9 @@ local function init(main) local facility = iocontrol.get_db().facility local units = iocontrol.get_db().units + local tank_defs = facility.tank_defs + local tank_draw = { table.unpack(tank_defs) } + -- window header message local header = TextBox{parent=main,y=1,text="Facility Coolant and Waste Flow Monitor",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} -- max length example: "01:23:45 AM - Wednesday, September 28 2022" @@ -46,52 +49,266 @@ local function init(main) datetime.register(facility.ps, "date_time", datetime.set_value) local po_pipes = {} - local water_pipes = {} - local fac_tanks = true + -- get the y offset for this unit index + local function y_ofs(idx) return ((idx - 1) * 20) end - for i = 1, 4 do - local y = ((i - 1) * 20) - table.insert(water_pipes, pipe(2, y, 2, y + 5, colors.blue, true)) - table.insert(water_pipes, pipe(2, y, 82, y, colors.blue, true)) - table.insert(water_pipes, pipe(82, y, 82, y + 2, colors.blue, true)) - if fac_tanks and i > 1 then table.insert(water_pipes, pipe(21, y - 19, 21, y, colors.blue, true)) end + local function calc_fdef(start_idx, end_idx) + local first, last = 4, 0 + for i = start_idx, end_idx do + if tank_defs[i] == 2 then + last = i + if i < first then first = i end + end + end + return first, last end - PipeNetwork{parent=main,x=2,y=3,pipes=water_pipes,bg=colors.lightGray} + if facility.tank_mode == 0 or facility.tank_mode == 8 then + -- (0) tanks belong to reactor units OR (8) 4 total facility tanks (A B C D) + for i = 1, facility.num_units do + if units[i].has_tank then + local y = y_ofs(i) + table.insert(water_pipes, pipe(2, y, 2, y + 5, colors.blue, true)) + table.insert(water_pipes, pipe(2, y, 21, y, colors.blue, true)) + + local u = units[i] ---@type ioctl_unit + local x = util.trinary(u.num_boilers == 0, 45, 84) + table.insert(water_pipes, pipe(21, y, x, y + 2, colors.blue, true, true)) + end + end + else + -- setup connections for units with emergency coolant, always the same + for i = 1, #tank_defs do + if tank_defs[i] > 0 then + local y = y_ofs(i) + + if tank_defs[i] == 2 then + table.insert(water_pipes, pipe(1, y, 21, y, colors.blue, true)) + else + table.insert(water_pipes, pipe(2, y, 2, y + 5, colors.blue, true)) + table.insert(water_pipes, pipe(2, y, 21, y, colors.blue, true)) + end + + local u = units[i] ---@type ioctl_unit + local x = util.trinary(u.num_boilers == 0, 45, 84) + table.insert(water_pipes, pipe(21, y, x, y + 2, colors.blue, true, true)) + end + end + + if facility.tank_mode == 1 then + -- (1) 1 total facility tank (A A A A) + local first_fdef, last_fdef = calc_fdef(1, #tank_defs) + + for i = 1, #tank_defs do + local y = y_ofs(i) + if i == first_fdef then + table.insert(water_pipes, pipe(0, y, 1, y + 6, colors.blue, true)) + elseif i > first_fdef then + if tank_defs[i] == 2 then tank_draw[i] = 0 end + if i == last_fdef then + table.insert(water_pipes, pipe(0, y - 13, 0, y, colors.blue, true)) + elseif i < last_fdef then + table.insert(water_pipes, pipe(0, y - 13, 0, y + 6, colors.blue, true)) + end + end + end + elseif facility.tank_mode == 2 then + -- (2) 2 total facility tanks (A A A B) + local first_fdef, last_fdef = calc_fdef(1, math.min(3, #tank_defs)) + + for i = 1, #tank_defs do + local y = y_ofs(i) + if i == 4 then + if tank_defs[i] == 2 then + table.insert(water_pipes, pipe(0, y, 1, y + 6, colors.blue, true)) + end + elseif i == first_fdef then + table.insert(water_pipes, pipe(0, y, 1, y + 6, colors.blue, true)) + elseif i > first_fdef then + if tank_defs[i] == 2 then tank_draw[i] = 0 end + if i == last_fdef then + table.insert(water_pipes, pipe(0, y - 13, 0, y, colors.blue, true)) + elseif i < last_fdef then + table.insert(water_pipes, pipe(0, y - 13, 0, y + 6, colors.blue, true)) + end + end + end + elseif facility.tank_mode == 3 then + -- (3) 2 total facility tanks (A A B B) + for _, a in pairs({ 1, 3 }) do + local b = a + 1 + if tank_defs[a] == 2 then + table.insert(water_pipes, pipe(0, y_ofs(a), 1, y_ofs(a) + 6, colors.blue, true)) + if tank_defs[b] == 2 then + table.insert(water_pipes, pipe(0, y_ofs(b) - 13, 1, y_ofs(b), colors.blue, true)) + tank_draw[b] = 0 + end + elseif tank_defs[b] == 2 then + table.insert(water_pipes, pipe(0, y_ofs(b), 1, y_ofs(b) + 6, colors.blue, true)) + end + end + elseif facility.tank_mode == 4 then + -- (4) 2 total facility tanks (A B B B) + local first_fdef, last_fdef = calc_fdef(2, #tank_defs) + + for i = 1, #tank_defs do + local y = y_ofs(i) + if i == 1 then + if tank_defs[i] == 2 then + table.insert(water_pipes, pipe(0, y, 1, y + 6, colors.blue, true)) + end + elseif i == first_fdef then + table.insert(water_pipes, pipe(0, y, 1, y + 6, colors.blue, true)) + elseif i > first_fdef then + if tank_defs[i] == 2 then tank_draw[i] = 0 end + if i == last_fdef then + table.insert(water_pipes, pipe(0, y - 13, 0, y, colors.blue, true)) + elseif i < last_fdef then + table.insert(water_pipes, pipe(0, y - 13, 0, y + 6, colors.blue, true)) + end + end + end + elseif facility.tank_mode == 5 then + -- (5) 3 total facility tanks (A A B C) + local first_fdef, last_fdef = calc_fdef(1, math.min(2, #tank_defs)) + + for i = 1, #tank_defs do + local y = y_ofs(i) + if i == 3 or i == 4 then + if tank_defs[i] == 2 then + table.insert(water_pipes, pipe(0, y, 1, y + 6, colors.blue, true)) + end + elseif i == first_fdef then + table.insert(water_pipes, pipe(0, y, 1, y + 6, colors.blue, true)) + elseif i > first_fdef then + if tank_defs[i] == 2 then tank_draw[i] = 0 end + if i == last_fdef then + table.insert(water_pipes, pipe(0, y - 13, 0, y, colors.blue, true)) + elseif i < last_fdef then + table.insert(water_pipes, pipe(0, y - 13, 0, y + 6, colors.blue, true)) + end + end + end + elseif facility.tank_mode == 6 then + -- (6) 3 total facility tanks (A B B C) + local first_fdef, last_fdef = calc_fdef(2, math.min(3, #tank_defs)) + + for i = 1, #tank_defs do + local y = y_ofs(i) + if i == 1 or i == 4 then + if tank_defs[i] == 2 then + table.insert(water_pipes, pipe(0, y, 1, y + 6, colors.blue, true)) + end + elseif i == first_fdef then + table.insert(water_pipes, pipe(0, y, 1, y + 6, colors.blue, true)) + elseif i > first_fdef then + if tank_defs[i] == 2 then tank_draw[i] = 0 end + if i == last_fdef then + table.insert(water_pipes, pipe(0, y - 13, 0, y, colors.blue, true)) + elseif i < last_fdef then + table.insert(water_pipes, pipe(0, y - 13, 0, y + 6, colors.blue, true)) + end + end + end + elseif facility.tank_mode == 7 then + -- (7) 3 total facility tanks (A B C C) + local first_fdef, last_fdef = calc_fdef(3, #tank_defs) + + for i = 1, #tank_defs do + local y = y_ofs(i) + if i == 1 or i == 2 then + if tank_defs[i] == 2 then + table.insert(water_pipes, pipe(0, y, 1, y + 6, colors.blue, true)) + end + elseif i == first_fdef then + table.insert(water_pipes, pipe(0, y, 1, y + 6, colors.blue, true)) + elseif i > first_fdef then + if tank_defs[i] == 2 then tank_draw[i] = 0 end + if i == last_fdef then + table.insert(water_pipes, pipe(0, y - 13, 0, y, colors.blue, true)) + elseif i < last_fdef then + table.insert(water_pipes, pipe(0, y - 13, 0, y + 6, colors.blue, true)) + end + end + end + end + end + + local flow_x = 3 + if #water_pipes > 0 then + flow_x = 25 + PipeNetwork{parent=main,x=2,y=3,pipes=water_pipes,bg=colors.lightGray} + end for i = 1, facility.num_units do - local y_offset = ((i - 1) * 20) - unit_flow(main, 25, 5 + y_offset, units[i]) + local y_offset = y_ofs(i) + unit_flow(main, flow_x, 5 + y_offset, #water_pipes == 0, units[i]) table.insert(po_pipes, pipe(0, 3 + y_offset, 8, 0, colors.cyan, true, true)) - - local vx, vy = 11, 3 + y_offset - TextBox{parent=main,x=vx,y=vy,text="\x10\x11",fg_bg=cpair(colors.black,colors.lightGray),width=2,height=1} - local conn = IndicatorLight{parent=main,x=vx-3,y=vy+1,label=util.sprintf("PV%02d", i + 13),colors=cpair(colors.green,colors.gray)} - local state = IndicatorLight{parent=main,x=vx-3,y=vy+2,label="STATE",colors=cpair(colors.white,colors.white)} - - local tank = Div{parent=main,x=2,y=8+y_offset,width=20,height=12} - TextBox{parent=tank,text=" ",height=1,x=1,y=1,fg_bg=cpair(colors.lightGray,colors.gray)} - TextBox{parent=tank,text="DYNAMIC TANK "..i,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=cpair(colors.white,colors.gray)} - local tank_box = Rectangle{parent=tank,border=border(1, colors.gray, true),width=20,height=10} - local status = StateIndicator{parent=tank_box,x=3,y=1,states=style.dtank.states,value=1,min_width=14} - TextBox{parent=tank_box,x=2,y=3,text="Fill",height=1,width=10,fg_bg=style.label} - local tank_pcnt = DataIndicator{parent=tank_box,x=10,y=3,label="",format="%5.2f",value=100,unit="%",lu_colors=lu_col,width=8,fg_bg=text_col} - local tank_amnt = DataIndicator{parent=tank_box,x=2,label="",format="%13d",value=0,unit="mB",lu_colors=lu_col,width=16,fg_bg=bw_fg_bg} - TextBox{parent=tank_box,x=2,y=6,text="Water Level",height=1,width=11,fg_bg=style.label} - local ccool = HorizontalBar{parent=tank_box,x=2,y=7,bar_fg_bg=cpair(colors.blue,colors.gray),height=1,width=16} end PipeNetwork{parent=main,x=139,y=15,pipes=po_pipes,bg=colors.lightGray} + -- TANK VALVES -- + + local next_f_id = 1 + + for i = 1, #tank_defs do + if tank_defs[i] > 0 then + local vy = 3 + y_ofs(i) + + TextBox{parent=main,x=12,y=vy,text="\x10\x11",fg_bg=cpair(colors.black,colors.lightGray),width=2,height=1} + + local conn = IndicatorLight{parent=main,x=9,y=vy+1,label=util.sprintf("PV%02d-EMC", i + 13),colors=cpair(colors.green,colors.gray)} + local state = IndicatorLight{parent=main,x=9,y=vy+2,label="STATE",colors=cpair(colors.white,colors.white)} + end + end + + -- DYNAMIC TANKS -- + + for i = 1, #tank_draw do + if tank_draw[i] > 0 then + local id = "U-" .. i + if tank_draw[i] == 2 then + id = "F-" .. next_f_id + next_f_id = next_f_id + 1 + end + + local y_offset = y_ofs(i) + + local tank = Div{parent=main,x=3,y=8+y_offset,width=20,height=12} + + TextBox{parent=tank,text=" ",height=1,x=1,y=1,fg_bg=cpair(colors.lightGray,colors.gray)} + TextBox{parent=tank,text="DYNAMIC TANK "..id,alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=cpair(colors.white,colors.gray)} + + local tank_box = Rectangle{parent=tank,border=border(1, colors.gray, true),width=20,height=10} + + local status = StateIndicator{parent=tank_box,x=3,y=1,states=style.dtank.states,value=1,min_width=14} + + TextBox{parent=tank_box,x=2,y=3,text="Fill",height=1,width=10,fg_bg=style.label} + local tank_pcnt = DataIndicator{parent=tank_box,x=10,y=3,label="",format="%5.2f",value=100,unit="%",lu_colors=lu_col,width=8,fg_bg=text_col} + local tank_amnt = DataIndicator{parent=tank_box,x=2,label="",format="%13d",value=0,unit="mB",lu_colors=lu_col,width=16,fg_bg=bw_fg_bg} + + TextBox{parent=tank_box,x=2,y=6,text="Water Level",height=1,width=11,fg_bg=style.label} + local ccool = HorizontalBar{parent=tank_box,x=2,y=7,bar_fg_bg=cpair(colors.blue,colors.gray),height=1,width=16} + end + end + + -- SPS -- + local sps = Div{parent=main,x=140,y=3,height=12} + TextBox{parent=sps,text=" ",width=24,height=1,x=1,y=1,fg_bg=cpair(colors.lightGray,colors.gray)} TextBox{parent=sps,text="SPS",alignment=TEXT_ALIGN.CENTER,width=24,height=1,fg_bg=cpair(colors.white,colors.gray)} + local sps_box = Rectangle{parent=sps,border=border(1, colors.gray, true),width=24,height=10} + local status = StateIndicator{parent=sps_box,x=5,y=1,states=style.sps.states,value=1,min_width=14} + TextBox{parent=sps_box,x=2,y=3,text="Input Rate",height=1,width=10,fg_bg=style.label} local sps_in = DataIndicator{parent=sps_box,x=2,label="",format="%15.2f",value=0,unit="mB/t",lu_colors=lu_col,width=20,fg_bg=bw_fg_bg} + TextBox{parent=sps_box,x=2,y=6,text="Production Rate",height=1,width=15,fg_bg=style.label} local sps_rate = DataIndicator{parent=sps_box,x=2,label="",format="%15.2f",value=0,unit="\xb5B/t",lu_colors=lu_col,width=20,fg_bg=bw_fg_bg} end diff --git a/supervisor/config.lua b/supervisor/config.lua index 2373f0d..3ea0da8 100644 --- a/supervisor/config.lua +++ b/supervisor/config.lua @@ -10,27 +10,39 @@ config.RTU_CHANNEL = 16242 config.CRD_CHANNEL = 16243 -- pocket comms channel config.PKT_CHANNEL = 16244 --- max trusted modem message distance (0 to disable check) +-- max trusted modem message distance +-- (0 to disable check) config.TRUSTED_RANGE = 0 --- time in seconds (>= 2) before assuming a remote device is no longer active +-- time in seconds (>= 2) before assuming a remote +-- device is no longer active config.PLC_TIMEOUT = 5 config.RTU_TIMEOUT = 5 config.CRD_TIMEOUT = 5 config.PKT_TIMEOUT = 5 --- facility authentication key (do NOT use one of your passwords) +-- facility authentication key +-- (do NOT use one of your passwords) -- this enables verifying that messages are authentic --- all devices on the same network must use the same key +-- all devices on this network must use this key -- config.AUTH_KEY = "SCADAfacility123" -- expected number of reactors config.NUM_REACTORS = 4 --- expected number of boilers/turbines for each reactor +-- expected number of devices for each unit config.REACTOR_COOLING = { - { BOILERS = 1, TURBINES = 1 }, -- reactor unit 1 - { BOILERS = 1, TURBINES = 1 }, -- reactor unit 2 - { BOILERS = 1, TURBINES = 1 }, -- reactor unit 3 - { BOILERS = 1, TURBINES = 1 } -- reactor unit 4 +-- reactor unit 1 +{ BOILERS = 1, TURBINES = 1, TANK = false }, +-- reactor unit 2 +{ BOILERS = 1, TURBINES = 1, TANK = false }, +-- reactor unit 3 +{ BOILERS = 1, TURBINES = 1, TANK = false }, +-- reactor unit 4 +{ BOILERS = 1, TURBINES = 1, TANK = false } } +-- advanced facility dynamic tank configuration +-- (see wiki for details) +-- by default, dynamic tanks are for each unit +config.FAC_TANK_MODE = 0 +config.FAC_TANK_LIST = { 0, 0, 0, 0 } -- log path config.LOG_PATH = "/log.txt" diff --git a/supervisor/facility.lua b/supervisor/facility.lua index 77aa676..d03ad94 100644 --- a/supervisor/facility.lua +++ b/supervisor/facility.lua @@ -54,7 +54,7 @@ local facility = {} -- create a new facility management object ---@nodiscard ---@param num_reactors integer number of reactor units ----@param cooling_conf table cooling configurations of reactor units +---@param cooling_conf sv_cooling_conf cooling configurations of reactor units function facility.new(num_reactors, cooling_conf) local self = { units = {}, @@ -118,7 +118,7 @@ function facility.new(num_reactors, cooling_conf) -- create units for i = 1, num_reactors do - table.insert(self.units, unit.new(i, cooling_conf[i].BOILERS, cooling_conf[i].TURBINES)) + table.insert(self.units, unit.new(i, cooling_conf.r_cool[i].BOILERS, cooling_conf.r_cool[i].TURBINES)) table.insert(self.group_map, 0) end diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index 075f090..e890401 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -198,7 +198,7 @@ end ---@param nic nic network interface device ---@param fp_ok boolean front panel active ---@param num_reactors integer number of reactors ----@param cooling_conf table cooling configuration definition +---@param cooling_conf sv_cooling_conf cooling configuration definition function svsessions.init(nic, fp_ok, num_reactors, cooling_conf) self.nic = nic self.fp_ok = fp_ok diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 1ba2a4f..4da9937 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -21,7 +21,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v0.20.5" +local SUPERVISOR_VERSION = "v1.0.0" local println = util.println local println_ts = util.println_ts @@ -48,11 +48,16 @@ cfv.assert_type_num(config.PKT_TIMEOUT) cfv.assert_min(config.PKT_TIMEOUT, 2) cfv.assert_type_int(config.NUM_REACTORS) cfv.assert_type_table(config.REACTOR_COOLING) +cfv.assert_type_int(config.FAC_TANK_MODE) +cfv.assert_type_table(config.FAC_TANK_LIST) cfv.assert_type_str(config.LOG_PATH) cfv.assert_type_int(config.LOG_MODE) assert(cfv.valid(), "bad config file: missing/invalid fields") +assert((config.FAC_TANK_MODE ~= 0) and (config.NUM_REACTORS == #config.FAC_TANK_LIST), + "bad config file: FAC_TANK_LIST length not equal to NUM_REACTORS") + cfv.assert_eq(#config.REACTOR_COOLING, config.NUM_REACTORS) assert(cfv.valid(), "config: number of cooling configs different than number of units") @@ -61,6 +66,7 @@ for i = 1, config.NUM_REACTORS do assert(cfv.valid(), "config: missing cooling entry for reactor " .. i) cfv.assert_type_int(config.REACTOR_COOLING[i].BOILERS) cfv.assert_type_int(config.REACTOR_COOLING[i].TURBINES) + cfv.assert_type_bool(config.REACTOR_COOLING[i].TANK) assert(cfv.valid(), "config: missing boilers/turbines for reactor " .. i) cfv.assert_min(config.REACTOR_COOLING[i].BOILERS, 0) cfv.assert_min(config.REACTOR_COOLING[i].TURBINES, 1) diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index fb33b06..e0e87e8 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -32,7 +32,8 @@ function supervisor.comms(_version, nic, fp_ok) -- configuration data local num_reactors = config.NUM_REACTORS - local cooling_conf = config.REACTOR_COOLING + ---@class sv_cooling_conf + local cooling_conf = { r_cool = config.REACTOR_COOLING, fac_tank_mode = config.FAC_TANK_MODE, fac_tank_list = config.FAC_TANK_LIST } local self = { last_est_acks = {} @@ -295,16 +296,10 @@ function supervisor.comms(_version, nic, fp_ok) local s_id = svsessions.establish_crd_session(src_addr, firmware_v) if s_id ~= false then - local cfg = { num_reactors } - for i = 1, #cooling_conf do - table.insert(cfg, cooling_conf[i].BOILERS) - table.insert(cfg, cooling_conf[i].TURBINES) - end - println(util.c("CRD (", firmware_v, ") [@", src_addr, "] \xbb connected")) log.info(util.c("CRD_ESTABLISH: coordinator (", firmware_v, ") [@", src_addr, "] connected with session ID ", s_id)) - _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW, cfg) + _send_establish(packet.scada_frame, ESTABLISH_ACK.ALLOW, { num_reactors, cooling_conf }) else if last_ack ~= ESTABLISH_ACK.COLLISION then log.info("CRD_ESTABLISH: denied new coordinator [@" .. src_addr .. "] due to already being connected to another coordinator")