From 4aad591d3aee24676ae8cc130f4a0ff5d6029f1c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Apr 2023 16:49:54 -0400 Subject: [PATCH] #182 linked up PLC front panel indicators, cleaned up panel display and added flashing to RPS trip --- graphics/element.lua | 1 + graphics/elements/indicators/ledpair.lua | 1 + graphics/elements/indicators/ledrgb.lua | 57 ++++++++++++++++ reactor-plc/databus.lua | 84 ++++++++++++++++++++++++ reactor-plc/panel/front_panel.lua | 83 +++++++++++------------ reactor-plc/panel/style.lua | 3 +- reactor-plc/plc.lua | 32 +++++---- reactor-plc/renderer.lua | 18 ++++- reactor-plc/startup.lua | 47 +++++++------ reactor-plc/threads.lua | 83 +++++++++++++---------- 10 files changed, 293 insertions(+), 116 deletions(-) create mode 100644 graphics/elements/indicators/ledrgb.lua create mode 100644 reactor-plc/databus.lua diff --git a/graphics/element.lua b/graphics/element.lua index 91810ae..5636fd0 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -34,6 +34,7 @@ local element = {} ---|icon_indicator_args ---|indicator_led_args ---|indicator_led_pair_args +---|indicator_led_rgb_args ---|indicator_light_args ---|power_indicator_args ---|rad_indicator_args diff --git a/graphics/elements/indicators/ledpair.lua b/graphics/elements/indicators/ledpair.lua index c56fafd..b05c4e6 100644 --- a/graphics/elements/indicators/ledpair.lua +++ b/graphics/elements/indicators/ledpair.lua @@ -103,6 +103,7 @@ local function indicator_led_pair(args) -- write label and initial indicator light e.on_update(1) + e.window.setCursorPos(3, 1) e.window.write(args.label) return e.get() diff --git a/graphics/elements/indicators/ledrgb.lua b/graphics/elements/indicators/ledrgb.lua new file mode 100644 index 0000000..8529ea8 --- /dev/null +++ b/graphics/elements/indicators/ledrgb.lua @@ -0,0 +1,57 @@ +-- Indicator RGB LED Graphics Element + +local element = require("graphics.element") + +---@class indicator_led_rgb_args +---@field label string indicator label +---@field colors table colors to use +---@field min_label_width? integer label length if omitted +---@field parent graphics_element +---@field id? string element id +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field fg_bg? cpair foreground/background colors + +-- new tri-state indicator light +---@nodiscard +---@param args indicator_led_rgb_args +---@return graphics_element element, element_id id +local function indicator_led_rgb(args) + assert(type(args.label) == "string", "graphics.elements.indicators.ledrgb: label is a required field") + assert(type(args.colors) == "table", "graphics.elements.indicators.ledrgb: colors is a required field") + + -- single line + args.height = 1 + + -- determine width + args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2 + + -- create new graphics element base object + local e = element.new(args) + + -- init value for initial check in on_update + e.value = 1 + + -- on state change + ---@param new_state integer indicator state + function e.on_update(new_state) + e.value = new_state + e.window.setCursorPos(1, 1) + if type(args.colors[new_state]) == "number" then + e.window.blit("\x8c", colors.toBlit(args.colors[new_state]), e.fg_bg.blit_bkg) + end + end + + -- set indicator state + ---@param val integer indicator state + function e.set_value(val) e.on_update(val) end + + -- write label and initial indicator light + e.on_update(1) + e.window.setCursorPos(3, 1) + e.window.write(args.label) + + return e.get() +end + +return indicator_led_rgb diff --git a/reactor-plc/databus.lua b/reactor-plc/databus.lua new file mode 100644 index 0000000..dc66c56 --- /dev/null +++ b/reactor-plc/databus.lua @@ -0,0 +1,84 @@ +-- +-- Data Bus - Central Communication Linking for PLC PSIL +-- + +local psil = require("scada-common.psil") +local util = require("scada-common.util") + +local databus = {} + +local ps = psil.create() + +-- call to toggle heartbeat signal +function databus.heartbeat() + ps.toggle("heartbeat") +end + +-- transmit firmware versions across the bus +---@param plc_v string PLC version +---@param comms_v string comms version +function databus.tx_versions(plc_v, comms_v) + ps.publish("version", plc_v) + ps.publish("comms_version", comms_v) +end + +-- transmit unit ID across the bus +---@param id integer unit ID +function databus.tx_id(id) + ps.publish("unit_id", id) +end + +-- transmit hardware status across the bus +---@param plc_state plc_state +function databus.tx_hw_status(plc_state) + ps.publish("reactor_dev_state", util.trinary(plc_state.no_reactor, 1, util.trinary(plc_state.reactor_formed, 3, 2))) + ps.publish("has_modem", not plc_state.no_modem) + ps.publish("degraded", plc_state.degraded) + ps.publish("init_ok", plc_state.init_ok) +end + +-- transmit thread (routine) statuses +---@param thread string thread name +---@param ok boolean thread state +function databus.tx_rt_status(thread, ok) + ps.publish(util.c("routine__", thread), ok) +end + +-- transmit supervisor link state across the bus +---@param state integer +function databus.tx_link_state(state) + ps.publish("link_state", state) +end + +-- transmit reactor enable state across the bus +---@param active boolean reactor active +function databus.tx_reactor_state(active) + ps.publish("reactor_active", active) +end + +-- transmit RPS data across the bus +---@param tripped boolean RPS tripped +---@param status table RPS status +function databus.tx_rps(tripped, status) + ps.publish("rps_scram", tripped) + ps.publish("rps_damage", status[1]) + ps.publish("rps_high_temp", status[2]) + ps.publish("rps_low_ccool", status[3]) + ps.publish("rps_high_waste", status[4]) + ps.publish("rps_high_hcool", status[5]) + ps.publish("rps_no_fuel", status[6]) + ps.publish("rps_fault", status[7]) + ps.publish("rps_timeout", status[8]) + ps.publish("rps_manual", status[9]) + ps.publish("rps_automatic", status[10]) + ps.publish("rps_sysfail", status[11]) +end + +-- link a function to receive data from the bus +---@param field string field name +---@param func function function to link +function databus.rx_field(field, func) + ps.subscribe(field, func) +end + +return databus diff --git a/reactor-plc/panel/front_panel.lua b/reactor-plc/panel/front_panel.lua index 7408a1c..09ce452 100644 --- a/reactor-plc/panel/front_panel.lua +++ b/reactor-plc/panel/front_panel.lua @@ -4,21 +4,21 @@ local util = require("scada-common.util") +local databus = require("reactor-plc.databus") + local style = require("reactor-plc.panel.style") local core = require("graphics.core") +local flasher = require("graphics.flasher") local DisplayBox = require("graphics.elements.displaybox") local Div = require("graphics.elements.div") local Rectangle = require("graphics.elements.rectangle") local TextBox = require("graphics.elements.textbox") -local ColorMap = require("graphics.elements.colormap") -local PushButton = require("graphics.elements.controls.push_button") - -local DataIndicator = require("graphics.elements.indicators.data") local LED = require("graphics.elements.indicators.led") local LEDPair = require("graphics.elements.indicators.ledpair") +local RGBLED = require("graphics.elements.indicators.ledrgb") local TEXT_ALIGN = core.graphics.TEXT_ALIGN @@ -27,11 +27,11 @@ local border = core.graphics.border -- create new main view ---@param monitor table main viewscreen ----@param fp_ps psil front panel PSIL -local function init(monitor, fp_ps) +local function init(monitor) local panel = DisplayBox{window=monitor,fg_bg=style.root} - local _ = TextBox{parent=panel,y=1,text="REACTOR PLC",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + local header = TextBox{parent=panel,y=1,text="REACTOR PLC - UNIT ?",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} + databus.rx_field("unit_id", function (id) header.set_value(util.c("REACTOR PLC - UNIT ", id)) end) local system = Div{parent=panel,width=14,height=18,x=2,y=3} @@ -39,17 +39,18 @@ local function init(monitor, fp_ps) local heartbeat = LED{parent=system,label="HEARTBEAT",colors=cpair(colors.green,colors.green_off)} system.line_break() - fp_ps.subscribe("init_ok", init_ok.update) - fp_ps.subscribe("heartbeat", heartbeat.update) + databus.rx_field("init_ok", init_ok.update) + databus.rx_field("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} + local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,colors.gray}} + network.update(5) system.line_break() - fp_ps.subscribe("reactor_dev_state", reactor.update) - fp_ps.subscribe("has_modem", modem.update) - fp_ps.subscribe("link_state", network.update) + databus.rx_field("reactor_dev_state", reactor.update) + databus.rx_field("has_modem", modem.update) + databus.rx_field("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)} @@ -58,24 +59,27 @@ local function init(monitor, fp_ps) 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) + databus.rx_field("routine__main", rt_main.update) + databus.rx_field("routine__rps", rt_rps.update) + databus.rx_field("routine__comms_tx", rt_cmtx.update) + databus.rx_field("routine__comms_rx", rt_cmrx.update) + databus.rx_field("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)} + local status = Div{parent=panel,width=16,height=4,x=18,y=3} + + local active = LED{parent=status,label="RCT ACTIVE",colors=cpair(colors.green,colors.green_off)} + local scram = LED{parent=status,label="RPS TRIP",colors=cpair(colors.red,colors.red_off),flash=true,period=flasher.PERIOD.BLINK_250_MS} system.line_break() - fp_ps.subscribe("reactor_active", active.update) - fp_ps.subscribe("rps_scram", scram.update) + databus.rx_field("reactor_active", active.update) + databus.rx_field("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} - -- about.line_break() - -- local _ = TextBox{parent=about,text="SVTT: 10ms",alignment=TEXT_ALIGN.LEFT,height=1} + local about = Rectangle{parent=panel,width=32,height=3,x=2,y=16,border=border(1,colors.ivory),thin=true,fg_bg=cpair(colors.black,colors.white)} + local fw_v = TextBox{parent=about,x=2,y=1,text="FW: v00.00.00",alignment=TEXT_ALIGN.LEFT,height=1} + local comms_v = TextBox{parent=about,x=17,y=1,text="NT: v00.00.00",alignment=TEXT_ALIGN.LEFT,height=1} + + databus.rx_field("version", function (version) fw_v.set_value(util.c("FW: ", version)) end) + databus.rx_field("comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end) 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 rps_man = LED{parent=rps,label="MANUAL",colors=cpair(colors.red,colors.red_off)} @@ -93,20 +97,17 @@ local function init(monitor, fp_ps) 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) + databus.rx_field("rps_manual", rps_man.update) + databus.rx_field("rps_automatic", rps_auto.update) + databus.rx_field("rps_timeout", rps_tmo.update) + databus.rx_field("rps_fault", rps_flt.update) + databus.rx_field("rps_sysfail", rps_fail.update) + databus.rx_field("rps_damage", rps_dmg.update) + databus.rx_field("rps_high_temp", rps_tmp.update) + databus.rx_field("rps_no_fuel", rps_nof.update) + databus.rx_field("rps_high_waste", rps_wst.update) + databus.rx_field("rps_low_ccool", rps_ccl.update) + databus.rx_field("rps_high_hcool", rps_hcl.update) return panel end diff --git a/reactor-plc/panel/style.lua b/reactor-plc/panel/style.lua index 0b55ec3..42e2a02 100644 --- a/reactor-plc/panel/style.lua +++ b/reactor-plc/panel/style.lua @@ -17,12 +17,11 @@ colors.green_off = colors.lime style.root = cpair(colors.black, colors.ivory) style.header = cpair(colors.black, colors.lightGray) -style.label = cpair(colors.gray, colors.lightGray) style.colors = { { c = colors.red, hex = 0xdf4949 }, -- RED ON { c = colors.orange, hex = 0xffb659 }, - { c = colors.yellow, hex = 0xe5e552 }, + { c = colors.yellow, hex = 0xf9fb53 }, { c = colors.lime, hex = 0x16665a }, -- GREEN OFF { c = colors.green, hex = 0x6be551 }, -- GREEN ON { c = colors.cyan, hex = 0x34bac8 }, diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index dce6a73..3045490 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -1,9 +1,10 @@ -local comms = require("scada-common.comms") -local const = require("scada-common.constants") -local log = require("scada-common.log") -local ppm = require("scada-common.ppm") -local types = require("scada-common.types") -local util = require("scada-common.util") +local comms = require("scada-common.comms") +local const = require("scada-common.constants") +local log = require("scada-common.log") +local ppm = require("scada-common.ppm") +local types = require("scada-common.types") +local util = require("scada-common.util") +local databus = require("reactor-plc.databus") local plc = {} @@ -18,11 +19,6 @@ local AUTO_ACK = comms.PLC_AUTO_ACK local RPS_LIMITS = const.RPS_LIMITS -local print = util.print -local println = util.println -local print_ts = util.print_ts -local println_ts = util.println_ts - -- 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 local PCALL_SCRAM_MSG = "pcall: Scram requires the reactor to be active." @@ -348,6 +344,9 @@ function plc.rps_init(reactor, is_formed) end end + -- report RPS status + databus.tx_rps(self.tripped, self.state) + return self.tripped, status, first_trip end @@ -733,6 +732,11 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, ---@param plc_state plc_state PLC state ---@param setpoints setpoints setpoint control table function public.handle_packet(packet, plc_state, setpoints) + -- print a log message to the terminal as long as the UI isn't running + local function println(message) if not plc_state.fp_ok then util.println(message) end end + local function println_ts(message) if not plc_state.fp_ok then util.println_ts(message) end end + + -- handle packets now that we have prints setup if packet.scada_frame.local_port() == local_port then -- check sequence number if self.r_seq_num == nil then @@ -919,6 +923,9 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, -- clear this since this is for something that was unsolicited self.last_est_ack = ESTABLISH_ACK.ALLOW + + -- report link state + databus.tx_link_state(est_ack + 1) else log.debug("SCADA_MGMT establish packet length mismatch") end @@ -982,6 +989,9 @@ function plc.comms(id, version, modem, local_port, server_port, range, reactor, self.linked = est_ack == ESTABLISH_ACK.ALLOW self.last_est_ack = est_ack + + -- report link state + databus.tx_link_state(est_ack + 1) else log.debug("SCADA_MGMT establish packet length mismatch") end diff --git a/reactor-plc/renderer.lua b/reactor-plc/renderer.lua index 8df385a..458c052 100644 --- a/reactor-plc/renderer.lua +++ b/reactor-plc/renderer.lua @@ -5,6 +5,8 @@ local style = require("reactor-plc.panel.style") local panel_view = require("reactor-plc.panel.front_panel") +local flasher = require("graphics.flasher") + local renderer = {} local ui = { @@ -12,9 +14,9 @@ local ui = { } -- start the UI ----@param fp_ps psil front panel PSIL -function renderer.start_ui(fp_ps) +function renderer.start_ui() if ui.view == nil then + -- reset terminal term.setTextColor(colors.white) term.setBackgroundColor(colors.black) term.clear() @@ -25,13 +27,19 @@ function renderer.start_ui(fp_ps) term.setPaletteColor(style.colors[i].c, style.colors[i].hex) end + -- start flasher callback task + flasher.run() + -- init front panel view - ui.view = panel_view(term.current(), fp_ps) + ui.view = panel_view(term.current()) end end -- close out the UI function renderer.close_ui() + -- stop blinking indicators + flasher.clear() + if ui.view ~= nil then -- hide to stop animation callbacks ui.view.hide() @@ -46,7 +54,11 @@ function renderer.close_ui() term.setPaletteColor(style.colors[i].c, r, g, b) end + -- reset terminal + term.setTextColor(colors.white) + term.setBackgroundColor(colors.black) term.clear() + term.setCursorPos(1, 1) end -- is the UI ready? diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 9f13a9a..59c5ced 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -4,19 +4,20 @@ require("/initenv").init_env() -local crash = require("scada-common.crash") -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local ppm = require("scada-common.ppm") -local psil = require("scada-common.psil") -local util = require("scada-common.util") +local comms = require("scada-common.comms") +local crash = require("scada-common.crash") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local ppm = require("scada-common.ppm") +local util = require("scada-common.util") -local config = require("reactor-plc.config") -local plc = require("reactor-plc.plc") +local config = require("reactor-plc.config") +local databus = require("reactor-plc.databus") +local plc = require("reactor-plc.plc") local renderer = require("reactor-plc.renderer") -local threads = require("reactor-plc.threads") +local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v1.1.0" +local R_PLC_VERSION = "v1.1.1" local print = util.print local println = util.println @@ -63,6 +64,10 @@ local function main() -- startup ---------------------------------------- + -- record firmware versions and ID + databus.tx_versions(R_PLC_VERSION, comms.version) + databus.tx_id(config.REACTOR_ID) + -- mount connected devices ppm.mount_all() @@ -109,10 +114,7 @@ local function main() mq_rps = mqueue.new(), mq_comms_tx = mqueue.new(), mq_comms_rx = mqueue.new() - }, - - -- publisher/subscriber interface for front panel - fp_ps = psil.create() + } } local smem_dev = __shared_memory.plc_dev @@ -152,9 +154,7 @@ local function main() 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 + local function _println_no_fp(message) if not plc_state.fp_ok then println(message) end end -- PLC init
--- EVENT_CONSUMER: this function consumes events @@ -167,7 +167,7 @@ local function main() -- front panel time! if not renderer.ui_ready() then local message = nil - plc_state.fp_ok, message = pcall(renderer.start_ui, __shared_memory.fp_ps) + plc_state.fp_ok, message = pcall(renderer.start_ui) if not plc_state.fp_ok then renderer.close_ui() println_ts(util.c("UI error: ", message)) @@ -192,23 +192,20 @@ local function main() config.TRUSTED_RANGE, smem_dev.reactor, smem_sys.rps, smem_sys.conn_watchdog) log.debug("init> comms init") else - _print_no_fp("init> starting in offline mode") + _println_no_fp("init> starting in offline mode") log.info("init> running without networking") end util.push_event("clock_start") - _print_no_fp("init> completed") + _println_no_fp("init> completed") log.info("init> startup completed") else - _print_no_fp("init> system in degraded state, awaiting devices...") + _println_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) + databus.tx_hw_status(plc_state) end ---------------------------------------- diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 3d84773..dc8bad3 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -1,15 +1,13 @@ -local log = require("scada-common.log") -local mqueue = require("scada-common.mqueue") -local ppm = require("scada-common.ppm") -local util = require("scada-common.util") +local log = require("scada-common.log") +local mqueue = require("scada-common.mqueue") +local ppm = require("scada-common.ppm") +local tcallbackdsp = require("scada-common.tcallbackdsp") +local util = require("scada-common.util") + +local databus = require("reactor-plc.databus") local threads = {} -local print = util.print -local println = util.println -local print_ts = util.print_ts -local println_ts = util.println_ts - local MAIN_CLOCK = 0.5 -- (2Hz, 10 ticks) local RPS_SLEEP = 250 -- (250ms, 5 ticks) local COMMS_SLEEP = 150 -- (150ms, 3 ticks) @@ -32,11 +30,16 @@ local MQ__COMM_CMD = { ---@param smem plc_shared_memory ---@param init function function threads.thread__main(smem, init) + -- print a log message to the terminal as long as the UI isn't running + local function println(message) if not smem.plc_state.fp_ok then util.println(message) end end + local function println_ts(message) if not smem.plc_state.fp_ok then util.println_ts(message) end end + ---@class parallel_thread local public = {} -- execute thread function public.exec() + databus.tx_rt_status("main", true) log.debug("main thread init, clock inactive") -- send status updates at 2Hz (every 10 server ticks) (every loop tick) @@ -61,6 +64,9 @@ function threads.thread__main(smem, init) -- handle event if event == "timer" and loop_clock.is_clock(param1) then + -- blink heartbeat indicator + databus.heartbeat() + -- core clock tick if networked then -- start next clock timer @@ -72,7 +78,6 @@ 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 @@ -80,14 +85,6 @@ 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? @@ -144,10 +141,7 @@ function threads.thread__main(smem, init) 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) + databus.tx_hw_status(plc_state) 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) @@ -159,6 +153,9 @@ function threads.thread__main(smem, init) -- haven't heard from server recently? shutdown reactor plc_comms.unlink() smem.q.mq_rps.push_command(MQ__RPS_CMD.TRIP_TIMEOUT) + elseif event == "timer" then + -- notify timer callback dispatcher if no other timer case claimed this event + tcallbackdsp.handle(param1) elseif event == "peripheral_detach" then -- peripheral disconnect local type, device = ppm.handle_unmount(param1) @@ -191,10 +188,7 @@ function threads.thread__main(smem, 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) + databus.tx_hw_status(plc_state) elseif event == "peripheral" then -- peripheral connect local type, device = ppm.mount(param1) @@ -260,10 +254,7 @@ function threads.thread__main(smem, 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) + databus.tx_hw_status(plc_state) elseif event == "clock_start" then -- start loop clock loop_clock.start() @@ -290,6 +281,8 @@ function threads.thread__main(smem, init) log.fatal(util.strval(result)) end + databus.tx_rt_status("main", false) + -- if status is true, then we are probably exiting, so this won't matter -- if not, we need to restart the clock -- this thread cannot be slept because it will miss events (namely "terminate" otherwise) @@ -307,11 +300,16 @@ end ---@nodiscard ---@param smem plc_shared_memory function threads.thread__rps(smem) + -- print a log message to the terminal as long as the UI isn't running + local function println(message) if not smem.plc_state.fp_ok then util.println(message) end end + local function println_ts(message) if not smem.plc_state.fp_ok then util.println_ts(message) end end + ---@class parallel_thread local public = {} -- execute thread function public.exec() + databus.tx_rt_status("rps", true) log.debug("rps thread start") -- load in from shared memory @@ -345,11 +343,17 @@ function threads.thread__rps(smem) was_linked = true end - -- if we tried to SCRAM but failed, keep trying - -- in that case, SCRAM won't be called until it reconnects (this is the expected use of this check) + if (not plc_state.no_reactor) and rps.is_formed() then + -- check reactor status ---@diagnostic disable-next-line: need-check-nil - if (not plc_state.no_reactor) and rps.is_formed() and rps.is_tripped() and reactor.getStatus() then - rps.scram() + local reactor_status = reactor.getStatus() + databus.tx_reactor_state(reactor_status) + + -- if we tried to SCRAM but failed, keep trying + -- in that case, SCRAM won't be called until it reconnects (this is the expected use of this check) + if rps.is_tripped() and reactor_status then + rps.scram() + end end -- if we are in standalone mode, continuously reset RPS @@ -433,6 +437,8 @@ function threads.thread__rps(smem) log.fatal(util.strval(result)) end + databus.tx_rt_status("rps", false) + if not plc_state.shutdown then if plc_state.init_ok then smem.plc_sys.rps.scram() end log.info("rps thread restarting in 5 seconds...") @@ -453,6 +459,7 @@ function threads.thread__comms_tx(smem) -- execute thread function public.exec() + databus.tx_rt_status("comms_tx", true) log.debug("comms tx thread start") -- load in from shared memory @@ -510,6 +517,8 @@ function threads.thread__comms_tx(smem) log.fatal(util.strval(result)) end + databus.tx_rt_status("comms_tx", false) + if not plc_state.shutdown then log.info("comms tx thread restarting in 5 seconds...") util.psleep(5) @@ -529,6 +538,7 @@ function threads.thread__comms_rx(smem) -- execute thread function public.exec() + databus.tx_rt_status("comms_rx", true) log.debug("comms rx thread start") -- load in from shared memory @@ -586,6 +596,8 @@ function threads.thread__comms_rx(smem) log.fatal(util.strval(result)) end + databus.tx_rt_status("comms_rx", false) + if not plc_state.shutdown then log.info("comms rx thread restarting in 5 seconds...") util.psleep(5) @@ -605,6 +617,7 @@ function threads.thread__setpoint_control(smem) -- execute thread function public.exec() + databus.tx_rt_status("spctl", true) log.debug("setpoint control thread start") -- load in from shared memory @@ -719,6 +732,8 @@ function threads.thread__setpoint_control(smem) log.fatal(util.strval(result)) end + databus.tx_rt_status("spctl", false) + if not plc_state.shutdown then log.info("setpoint control thread restarting in 5 seconds...") util.psleep(5)