diff --git a/reactor-plc/panel/front_panel.lua b/reactor-plc/panel/front_panel.lua index 455dd15..7408a1c 100644 --- a/reactor-plc/panel/front_panel.lua +++ b/reactor-plc/panel/front_panel.lua @@ -10,15 +10,15 @@ local core = require("graphics.core") local DisplayBox = require("graphics.elements.displaybox") local Div = require("graphics.elements.div") -local Rectangle = require("graphics.elements.rectangle") +local Rectangle = require("graphics.elements.rectangle") local TextBox = require("graphics.elements.textbox") -local ColorMap = require("graphics.elements.colormap") +local ColorMap = require("graphics.elements.colormap") local PushButton = require("graphics.elements.controls.push_button") -local SwitchButton = require("graphics.elements.controls.switch_button") local DataIndicator = require("graphics.elements.indicators.data") local LED = require("graphics.elements.indicators.led") +local LEDPair = require("graphics.elements.indicators.ledpair") local TEXT_ALIGN = core.graphics.TEXT_ALIGN @@ -27,31 +27,50 @@ local border = core.graphics.border -- create new main view ---@param monitor table main viewscreen -local function init(monitor) +---@param fp_ps psil front panel PSIL +local function init(monitor, fp_ps) local panel = DisplayBox{window=monitor,fg_bg=style.root} - -- window header message - local header = TextBox{parent=panel,y=1,text="REACTOR PLC",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + local _ = TextBox{parent=panel,y=1,text="REACTOR PLC",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} local system = Div{parent=panel,width=14,height=18,x=2,y=3} local init_ok = LED{parent=system,label="STATUS",colors=cpair(colors.green,colors.red)} local heartbeat = LED{parent=system,label="HEARTBEAT",colors=cpair(colors.green,colors.green_off)} system.line_break() - local reactor = LED{parent=system,label="REACTOR",colors=cpair(colors.green,colors.red)} - local modem = LED{parent=system,label="MODEM",colors=cpair(colors.green,colors.gray)} - local network = LED{parent=system,label="NETWORK",colors=cpair(colors.green,colors.gray)} + + fp_ps.subscribe("init_ok", init_ok.update) + fp_ps.subscribe("heartbeat", heartbeat.update) + + local reactor = LEDPair{parent=system,label="REACTOR",off=colors.red,c1=colors.yellow,c2=colors.green} + local modem = LED{parent=system,label="MODEM",colors=cpair(colors.green,colors.green_off)} + local network = LEDPair{parent=system,label="NETWORK",off=colors.gray,c1=colors.yellow,c2=colors.green} system.line_break() - local _ = LED{parent=system,label="RT MAIN",colors=cpair(colors.green,colors.gray)} - local _ = LED{parent=system,label="RT RPS",colors=cpair(colors.green,colors.gray)} - local _ = LED{parent=system,label="RT COMMS TX",colors=cpair(colors.green,colors.gray)} - local _ = LED{parent=system,label="RT COMMS RX",colors=cpair(colors.green,colors.gray)} - local _ = LED{parent=system,label="RT SPCTL",colors=cpair(colors.green,colors.gray)} + + fp_ps.subscribe("reactor_dev_state", reactor.update) + fp_ps.subscribe("has_modem", modem.update) + fp_ps.subscribe("link_state", network.update) + + local rt_main = LED{parent=system,label="RT MAIN",colors=cpair(colors.green,colors.green_off)} + local rt_rps = LED{parent=system,label="RT RPS",colors=cpair(colors.green,colors.green_off)} + local rt_cmtx = LED{parent=system,label="RT COMMS TX",colors=cpair(colors.green,colors.green_off)} + local rt_cmrx = LED{parent=system,label="RT COMMS RX",colors=cpair(colors.green,colors.green_off)} + local rt_sctl = LED{parent=system,label="RT SPCTL",colors=cpair(colors.green,colors.green_off)} system.line_break() + + fp_ps.subscribe("routine__main", rt_main.update) + fp_ps.subscribe("routine__rps", rt_rps.update) + fp_ps.subscribe("routine__comms_tx", rt_cmtx.update) + fp_ps.subscribe("routine__comms_rx", rt_cmrx.update) + fp_ps.subscribe("routine__spctl", rt_sctl.update) + local active = LED{parent=system,label="RCT ACTIVE",colors=cpair(colors.green,colors.green_off)} local scram = LED{parent=system,label="RPS TRIP",colors=cpair(colors.red,colors.red_off)} system.line_break() + fp_ps.subscribe("reactor_active", active.update) + fp_ps.subscribe("rps_scram", scram.update) + local about = Rectangle{parent=panel,width=16,height=4,x=18,y=15,border=border(1,colors.white),thin=true,fg_bg=cpair(colors.black,colors.white)} local _ = TextBox{parent=about,text="FW: v1.0.0",alignment=TEXT_ALIGN.LEFT,height=1} local _ = TextBox{parent=about,text="NT: v1.4.0",alignment=TEXT_ALIGN.LEFT,height=1} @@ -59,25 +78,35 @@ local function init(monitor) -- local _ = TextBox{parent=about,text="SVTT: 10ms",alignment=TEXT_ALIGN.LEFT,height=1} local rps = Rectangle{parent=panel,width=16,height=16,x=36,y=3,border=border(1,colors.lightGray),thin=true,fg_bg=cpair(colors.black,colors.lightGray)} - local _ = LED{parent=rps,label="MANUAL",colors=cpair(colors.red,colors.red_off)} - local _ = LED{parent=rps,label="AUTOMATIC",colors=cpair(colors.red,colors.red_off)} - local _ = LED{parent=rps,label="TIMEOUT",colors=cpair(colors.red,colors.red_off)} - local _ = LED{parent=rps,label="PLC FAULT",colors=cpair(colors.red,colors.red_off)} - local _ = LED{parent=rps,label="RCT FAULT",colors=cpair(colors.red,colors.red_off)} + local rps_man = LED{parent=rps,label="MANUAL",colors=cpair(colors.red,colors.red_off)} + local rps_auto = LED{parent=rps,label="AUTOMATIC",colors=cpair(colors.red,colors.red_off)} + local rps_tmo = LED{parent=rps,label="TIMEOUT",colors=cpair(colors.red,colors.red_off)} + local rps_flt = LED{parent=rps,label="PLC FAULT",colors=cpair(colors.red,colors.red_off)} + local rps_fail = LED{parent=rps,label="RCT FAULT",colors=cpair(colors.red,colors.red_off)} rps.line_break() - local _ = LED{parent=rps,label="HI DAMAGE",colors=cpair(colors.red,colors.red_off)} - local _ = LED{parent=rps,label="HI TEMP",colors=cpair(colors.red,colors.red_off)} + local rps_dmg = LED{parent=rps,label="HI DAMAGE",colors=cpair(colors.red,colors.red_off)} + local rps_tmp = LED{parent=rps,label="HI TEMP",colors=cpair(colors.red,colors.red_off)} rps.line_break() - local _ = LED{parent=rps,label="LO FUEL",colors=cpair(colors.red,colors.red_off)} - local _ = LED{parent=rps,label="HI WASTE",colors=cpair(colors.red,colors.red_off)} + local rps_nof = LED{parent=rps,label="LO FUEL",colors=cpair(colors.red,colors.red_off)} + local rps_wst = LED{parent=rps,label="HI WASTE",colors=cpair(colors.red,colors.red_off)} rps.line_break() - local _ = LED{parent=rps,label="LO CCOOLANT",colors=cpair(colors.red,colors.red_off)} - local _ = LED{parent=rps,label="HI HCOOLANT",colors=cpair(colors.red,colors.red_off)} + local rps_ccl = LED{parent=rps,label="LO CCOOLANT",colors=cpair(colors.red,colors.red_off)} + local rps_hcl = LED{parent=rps,label="HI HCOOLANT",colors=cpair(colors.red,colors.red_off)} + fp_ps.subscribe("rps_manual", rps_man.update) + fp_ps.subscribe("rps_automatic", rps_auto.update) + fp_ps.subscribe("rps_timeout", rps_tmo.update) + fp_ps.subscribe("rps_fault", rps_flt.update) + fp_ps.subscribe("rps_sysfail", rps_fail.update) + fp_ps.subscribe("rps_damage", rps_dmg.update) + fp_ps.subscribe("rps_high_temp", rps_tmp.update) + fp_ps.subscribe("rps_no_fuel", rps_nof.update) + fp_ps.subscribe("rps_high_waste", rps_wst.update) + fp_ps.subscribe("rps_low_ccool", rps_ccl.update) + fp_ps.subscribe("rps_high_hcool", rps_hcl.update) ColorMap{parent=panel,x=1,y=19} -- facility.ps.subscribe("sv_ping", ping.update) - -- facility.ps.subscribe("date_time", datetime.set_value) return panel end diff --git a/reactor-plc/renderer.lua b/reactor-plc/renderer.lua index cdda240..8df385a 100644 --- a/reactor-plc/renderer.lua +++ b/reactor-plc/renderer.lua @@ -12,7 +12,8 @@ local ui = { } -- start the UI -function renderer.start_ui() +---@param fp_ps psil front panel PSIL +function renderer.start_ui(fp_ps) if ui.view == nil then term.setTextColor(colors.white) term.setBackgroundColor(colors.black) @@ -25,7 +26,7 @@ function renderer.start_ui() end -- init front panel view - ui.view = panel_view(term.current()) + ui.view = panel_view(term.current(), fp_ps) end end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index b833136..9f13a9a 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -76,6 +76,7 @@ local function main() ---@class plc_state plc_state = { init_ok = true, + fp_ok = false, shutdown = false, degraded = false, reactor_formed = true, @@ -150,18 +151,33 @@ local function main() plc_state.no_modem = true end + -- print a log message to the terminal as long as the UI isn't running + local function _print_no_fp(message) + if not plc_state.fp_ok then println(message) end + end + -- 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 + smem_dev.reactor.scram() + end + -- front panel time! - renderer.start_ui() + if not renderer.ui_ready() then + local message = nil + plc_state.fp_ok, message = pcall(renderer.start_ui, __shared_memory.fp_ps) + if not plc_state.fp_ok then + renderer.close_ui() + println_ts(util.c("UI error: ", message)) + println("init> running without front panel") + log.error(util.c("GUI crashed with error ", message)) + log.info("init> running in headless mode without front panel") + end + end if plc_state.init_ok then - -- just booting up, no fission allowed (neutrons stay put thanks) - if plc_state.reactor_formed and smem_dev.reactor.getStatus() then - smem_dev.reactor.scram() - end - -- init reactor protection system smem_sys.rps = plc.rps_init(smem_dev.reactor, plc_state.reactor_formed) log.debug("init> rps init") @@ -176,18 +192,23 @@ local function main() config.TRUSTED_RANGE, smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog) log.debug("init> comms init") else - println("init> starting in offline mode") + _print_no_fp("init> starting in offline mode") log.info("init> running without networking") end util.push_event("clock_start") - println("init> completed") + _print_no_fp("init> completed") log.info("init> startup completed") else - -- println("init> system in degraded state, awaiting devices...") + _print_no_fp("init> system in degraded state, awaiting devices...") log.warning("init> started in a degraded state, awaiting peripheral connections...") end + + __shared_memory.fp_ps.publish("reactor_dev_state", util.trinary(plc_state.no_reactor, 1, util.trinary(plc_state.reactor_formed, 3, 2))) + __shared_memory.fp_ps.publish("has_modem", not plc_state.no_modem) + __shared_memory.fp_ps.publish("degraded", plc_state.degraded) + __shared_memory.fp_ps.publish("init_ok", plc_state.init_ok) end ---------------------------------------- diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index d2708fd..3d84773 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -72,6 +72,7 @@ function threads.thread__main(smem, init) smem.q.mq_comms_tx.push_command(MQ__COMM_CMD.SEND_STATUS) else if ticks_to_update == 0 then + smem.fp_ps.toggle("heartbeat") plc_comms.send_link_req() ticks_to_update = LINK_TICKS else @@ -79,6 +80,14 @@ function threads.thread__main(smem, init) end end end + else + -- use ticks to update just for heartbeat if not networked + if ticks_to_update == 0 then + smem.fp_ps.toggle("heartbeat") + ticks_to_update = LINK_TICKS + else + ticks_to_update = ticks_to_update - 1 + end end -- are we now formed after waiting to be formed? @@ -133,6 +142,12 @@ function threads.thread__main(smem, init) -- reactor no longer formed plc_state.reactor_formed = false end + + -- update indicators + smem.fp_ps.publish("init_ok", plc_state.init_ok) + smem.fp_ps.publish("reactor_dev_state", not plc_state.no_reactor) + smem.fp_ps.publish("has_modem", not plc_state.no_modem) + smem.fp_ps.publish("degraded", plc_state.degraded) elseif event == "modem_message" and networked and plc_state.init_ok and not plc_state.no_modem then -- got a packet local packet = plc_comms.parse_packet(param1, param2, param3, param4, param5) @@ -174,6 +189,12 @@ function threads.thread__main(smem, init) end end end + + -- update indicators + smem.fp_ps.publish("has_reactor", not plc_state.no_reactor) + smem.fp_ps.publish("has_modem", not plc_state.no_modem) + smem.fp_ps.publish("degraded", plc_state.degraded) + smem.fp_ps.publish("init_ok", plc_state.init_ok) elseif event == "peripheral" then -- peripheral connect local type, device = ppm.mount(param1) @@ -237,6 +258,12 @@ function threads.thread__main(smem, init) plc_state.init_ok = true init() end + + -- update indicators + smem.fp_ps.publish("has_reactor", not plc_state.no_reactor) + smem.fp_ps.publish("has_modem", not plc_state.no_modem) + smem.fp_ps.publish("degraded", plc_state.degraded) + smem.fp_ps.publish("init_ok", plc_state.init_ok) elseif event == "clock_start" then -- start loop clock loop_clock.start() diff --git a/scada-common/psil.lua b/scada-common/psil.lua index c21b2cf..664d10d 100644 --- a/scada-common/psil.lua +++ b/scada-common/psil.lua @@ -51,6 +51,19 @@ function psil.create() self.ic[key].value = value end + -- publish a toggled boolean value to a given key, passing it to all subscribers if it has changed
+ -- this is intended to be used to toggle boolean indicators such as heartbeats without extra state variables + ---@param key string data key + function public.toggle(key) + if self.ic[key] == nil then alloc(key) end + + self.ic[key].value = self.ic[key].value == false + + for i = 1, #self.ic[key].subscribers do + self.ic[key].subscribers[i].notify(self.ic[key].value) + end + end + return public end