From 6ea530635fa38d479ed16835fd6275b5d5f69f57 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Thu, 6 Apr 2023 22:10:33 -0400 Subject: [PATCH 1/7] #182 WIP PLC front panel --- graphics/element.lua | 15 ++- graphics/elements/indicators/led.lua | 98 ++++++++++++++++++++ graphics/elements/indicators/ledpair.lua | 111 +++++++++++++++++++++++ imgen.py | 2 +- reactor-plc/panel/front_panel.lua | 85 +++++++++++++++++ reactor-plc/panel/style.lua | 41 +++++++++ reactor-plc/renderer.lua | 62 +++++++++++++ reactor-plc/startup.lua | 16 +++- scada-common/log.lua | 2 +- 9 files changed, 426 insertions(+), 6 deletions(-) create mode 100644 graphics/elements/indicators/led.lua create mode 100644 graphics/elements/indicators/ledpair.lua create mode 100644 reactor-plc/panel/front_panel.lua create mode 100644 reactor-plc/panel/style.lua create mode 100644 reactor-plc/renderer.lua diff --git a/graphics/element.lua b/graphics/element.lua index 8aa3ce9..91810ae 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -32,6 +32,8 @@ local element = {} ---|data_indicator_args ---|hbar_args ---|icon_indicator_args +---|indicator_led_args +---|indicator_led_pair_args ---|indicator_light_args ---|power_indicator_args ---|rad_indicator_args @@ -100,7 +102,13 @@ function element.new(args) else local w, h = self.p_window.getSize() protected.frame.x = args.x or 1 - protected.frame.y = args.y or next_y + + if args.parent ~= nil then + protected.frame.y = args.y or (next_y - offset_y) + else + protected.frame.y = args.y or next_y + end + protected.frame.w = args.width or w protected.frame.h = args.height or h end @@ -260,6 +268,11 @@ function element.new(args) ---@param child graphics_template ---@return integer|string key function public.__add_child(key, child) + -- offset first automatic placement + if self.next_y <= self.child_offset.y then + self.next_y = self.child_offset.y + 1 + end + child.prepare_template(self.child_offset.x, self.child_offset.y, self.next_y) self.next_y = child.frame.y + child.frame.h diff --git a/graphics/elements/indicators/led.lua b/graphics/elements/indicators/led.lua new file mode 100644 index 0000000..7905848 --- /dev/null +++ b/graphics/elements/indicators/led.lua @@ -0,0 +1,98 @@ +-- Indicator "LED" Graphics Element + +local util = require("scada-common.util") + +local element = require("graphics.element") +local flasher = require("graphics.flasher") + +---@class indicator_led_args +---@field label string indicator label +---@field colors cpair on/off colors (a/b respectively) +---@field min_label_width? integer label length if omitted +---@field flash? boolean whether to flash on true rather than stay on +---@field period? PERIOD flash period +---@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 indicator LED +---@nodiscard +---@param args indicator_led_args +---@return graphics_element element, element_id id +local function indicator_led(args) + assert(type(args.label) == "string", "graphics.elements.indicators.led: label is a required field") + assert(type(args.colors) == "table", "graphics.elements.indicators.led: colors is a required field") + + if args.flash then + assert(util.is_int(args.period), "graphics.elements.indicators.led: period is a required field if flash is enabled") + end + + -- single line + args.height = 1 + + -- determine width + args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2 + + -- flasher state + local flash_on = true + + -- create new graphics element base object + local e = element.new(args) + + -- called by flasher when enabled + local function flash_callback() + e.window.setCursorPos(1, 1) + + if flash_on then + e.window.blit("\x8c", args.colors.blit_a, e.fg_bg.blit_bkg) + else + e.window.blit("\x8c", args.colors.blit_b, e.fg_bg.blit_bkg) + end + + flash_on = not flash_on + end + + -- enable light or start flashing + local function enable() + if args.flash then + flash_on = true + flasher.start(flash_callback, args.period) + else + e.window.setCursorPos(1, 1) + e.window.blit("\x8c", args.colors.blit_a, e.fg_bg.blit_bkg) + end + end + + -- disable light or stop flashing + local function disable() + if args.flash then + flash_on = false + flasher.stop(flash_callback) + end + + e.window.setCursorPos(1, 1) + e.window.blit("\x8c", args.colors.blit_b, e.fg_bg.blit_bkg) + end + + -- on state change + ---@param new_state boolean indicator state + function e.on_update(new_state) + e.value = new_state + if new_state then enable() else disable() end + end + + -- set indicator state + ---@param val boolean indicator state + function e.set_value(val) e.on_update(val) end + + -- write label and initial indicator light + e.on_update(false) + e.window.setCursorPos(3, 1) + e.window.write(args.label) + + return e.get() +end + +return indicator_led diff --git a/graphics/elements/indicators/ledpair.lua b/graphics/elements/indicators/ledpair.lua new file mode 100644 index 0000000..c56fafd --- /dev/null +++ b/graphics/elements/indicators/ledpair.lua @@ -0,0 +1,111 @@ +-- Indicator LED Pair Graphics Element (two LEDs provide: off, color_a, color_b) + +local util = require("scada-common.util") + +local element = require("graphics.element") +local flasher = require("graphics.flasher") + +---@class indicator_led_pair_args +---@field label string indicator label +---@field off color color for off +---@field c1 color color for #1 on +---@field c2 color color for #2 on +---@field min_label_width? integer label length if omitted +---@field flash? boolean whether to flash when on rather than stay on +---@field period? PERIOD flash period +---@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_pair_args +---@return graphics_element element, element_id id +local function indicator_led_pair(args) + assert(type(args.label) == "string", "graphics.elements.indicators.ledpair: label is a required field") + assert(type(args.off) == "number", "graphics.elements.indicators.ledpair: off is a required field") + assert(type(args.c1) == "number", "graphics.elements.indicators.ledpair: c1 is a required field") + assert(type(args.c2) == "number", "graphics.elements.indicators.ledpair: c2 is a required field") + + if args.flash then + assert(util.is_int(args.period), "graphics.elements.indicators.ledpair: period is a required field if flash is enabled") + end + + -- single line + args.height = 1 + + -- determine width + args.width = math.max(args.min_label_width or 1, string.len(args.label)) + 2 + + -- flasher state + local flash_on = true + + -- blit translations + local co = colors.toBlit(args.off) + local c1 = colors.toBlit(args.c1) + local c2 = colors.toBlit(args.c2) + + -- create new graphics element base object + local e = element.new(args) + + -- init value for initial check in on_update + e.value = 1 + + -- called by flasher when enabled + local function flash_callback() + e.window.setCursorPos(1, 1) + + if flash_on then + if e.value == 2 then + e.window.blit("\x8c", c1, e.fg_bg.blit_bkg) + elseif e.value == 3 then + e.window.blit("\x8c", c2, e.fg_bg.blit_bkg) + end + else + e.window.blit("\x8c", co, e.fg_bg.blit_bkg) + end + + flash_on = not flash_on + end + + -- on state change + ---@param new_state integer indicator state + function e.on_update(new_state) + local was_off = e.value <= 1 + + e.value = new_state + e.window.setCursorPos(1, 1) + + if args.flash then + if was_off and (new_state > 1) then + flash_on = true + flasher.start(flash_callback, args.period) + elseif new_state <= 1 then + flash_on = false + flasher.stop(flash_callback) + + e.window.blit("\x8c", co, e.fg_bg.blit_bkg) + end + elseif new_state == 2 then + e.window.blit("\x8c", c1, e.fg_bg.blit_bkg) + elseif new_state == 3 then + e.window.blit("\x8c", c2, e.fg_bg.blit_bkg) + else + e.window.blit("\x8c", co, 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.write(args.label) + + return e.get() +end + +return indicator_led_pair diff --git a/imgen.py b/imgen.py index faab46a..1ef46f2 100644 --- a/imgen.py +++ b/imgen.py @@ -68,7 +68,7 @@ def make_manifest(size): "pocket" : list_files("./pocket"), }, "depends" : { - "reactor-plc" : [ "system", "common" ], + "reactor-plc" : [ "system", "common", "graphics" ], "rtu" : [ "system", "common" ], "supervisor" : [ "system", "common" ], "coordinator" : [ "system", "common", "graphics" ], diff --git a/reactor-plc/panel/front_panel.lua b/reactor-plc/panel/front_panel.lua new file mode 100644 index 0000000..455dd15 --- /dev/null +++ b/reactor-plc/panel/front_panel.lua @@ -0,0 +1,85 @@ +-- +-- Main SCADA Coordinator GUI +-- + +local util = require("scada-common.util") + +local style = require("reactor-plc.panel.style") + +local core = require("graphics.core") + +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 SwitchButton = require("graphics.elements.controls.switch_button") + +local DataIndicator = require("graphics.elements.indicators.data") +local LED = require("graphics.elements.indicators.led") + +local TEXT_ALIGN = core.graphics.TEXT_ALIGN + +local cpair = core.graphics.cpair +local border = core.graphics.border + +-- create new main view +---@param monitor table main viewscreen +local function init(monitor) + 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 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)} + 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)} + system.line_break() + 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() + + 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 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)} + 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)} + 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)} + 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)} + + + 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 + +return init diff --git a/reactor-plc/panel/style.lua b/reactor-plc/panel/style.lua new file mode 100644 index 0000000..0b55ec3 --- /dev/null +++ b/reactor-plc/panel/style.lua @@ -0,0 +1,41 @@ +-- +-- Graphics Style Options +-- + +local core = require("graphics.core") + +local style = {} + +local cpair = core.graphics.cpair + +-- GLOBAL -- + +-- remap global colors +colors.ivory = colors.pink +colors.red_off = colors.brown +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.lime, hex = 0x16665a }, -- GREEN OFF + { c = colors.green, hex = 0x6be551 }, -- GREEN ON + { c = colors.cyan, hex = 0x34bac8 }, + { c = colors.lightBlue, hex = 0x6cc0f2 }, + { c = colors.blue, hex = 0x0096ff }, + { c = colors.purple, hex = 0xb156ee }, + { c = colors.pink, hex = 0xdcd9ca }, -- IVORY + { c = colors.magenta, hex = 0xf9488a }, + -- { c = colors.white, hex = 0xdcd9ca }, + { c = colors.lightGray, hex = 0x999f9b }, + { c = colors.gray, hex = 0x575757 }, + -- { c = colors.black, hex = 0x191919 }, + { c = colors.brown, hex = 0x672223 } -- RED OFF +} + +return style diff --git a/reactor-plc/renderer.lua b/reactor-plc/renderer.lua new file mode 100644 index 0000000..cdda240 --- /dev/null +++ b/reactor-plc/renderer.lua @@ -0,0 +1,62 @@ +-- +-- Graphics Rendering Control +-- + +local style = require("reactor-plc.panel.style") +local panel_view = require("reactor-plc.panel.front_panel") + +local renderer = {} + +local ui = { + view = nil +} + +-- start the UI +function renderer.start_ui() + if ui.view == nil then + term.setTextColor(colors.white) + term.setBackgroundColor(colors.black) + term.clear() + term.setCursorPos(1, 1) + + -- set overridden colors + for i = 1, #style.colors do + term.setPaletteColor(style.colors[i].c, style.colors[i].hex) + end + + -- init front panel view + ui.view = panel_view(term.current()) + end +end + +-- close out the UI +function renderer.close_ui() + if ui.view ~= nil then + -- hide to stop animation callbacks + ui.view.hide() + end + + -- clear root UI elements + ui.view = nil + + -- restore colors + for i = 1, #style.colors do + local r, g, b = term.nativePaletteColor(style.colors[i].c) + term.setPaletteColor(style.colors[i].c, r, g, b) + end + + term.clear() +end + +-- is the UI ready? +---@nodiscard +---@return boolean ready +function renderer.ui_ready() return ui.view ~= nil end + +-- handle a touch event +---@param event monitor_touch +function renderer.handle_touch(event) + ui.view.handle_touch(event) +end + +return renderer diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 5ad5ca4..b833136 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -8,13 +8,15 @@ 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 config = require("reactor-plc.config") local plc = require("reactor-plc.plc") +local renderer = require("reactor-plc.renderer") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v1.0.0" +local R_PLC_VERSION = "v1.1.0" local print = util.print local println = util.println @@ -106,7 +108,10 @@ 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 @@ -148,6 +153,9 @@ local function main() -- PLC init
--- EVENT_CONSUMER: this function consumes events local function init() + -- front panel time! + renderer.start_ui() + 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 @@ -177,7 +185,7 @@ local function main() println("init> completed") log.info("init> startup completed") else - println("init> system in degraded state, awaiting devices...") + -- println("init> system in degraded state, awaiting devices...") log.warning("init> started in a degraded state, awaiting peripheral connections...") end end @@ -217,6 +225,8 @@ local function main() parallel.waitForAll(main_thread.p_exec, rps_thread.p_exec) end + renderer.close_ui() + println_ts("exited") log.info("exited") end diff --git a/scada-common/log.lua b/scada-common/log.lua index 424bf55..30f785d 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -16,7 +16,7 @@ local MODE = { log.MODE = MODE -- whether to log debug messages or not -local LOG_DEBUG = false +local LOG_DEBUG = true local log_sys = { path = "/log.txt", From 9bc4f0f7a651fc2bb5d609c873454de3577e8ee7 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Apr 2023 00:38:46 -0400 Subject: [PATCH 2/7] #182 WIP work on PLC PSIL --- reactor-plc/panel/front_panel.lua | 81 +++++++++++++++++++++---------- reactor-plc/renderer.lua | 5 +- reactor-plc/startup.lua | 39 +++++++++++---- reactor-plc/threads.lua | 27 +++++++++++ scada-common/psil.lua | 13 +++++ 5 files changed, 128 insertions(+), 37 deletions(-) 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 From 4aad591d3aee24676ae8cc130f4a0ff5d6029f1c Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Apr 2023 16:49:54 -0400 Subject: [PATCH 3/7] #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) From 67872a10530779ba008d277fa4aeaf05ba9f2dfa Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Apr 2023 21:33:54 -0400 Subject: [PATCH 4/7] updated graphics touch events to be mouse events --- coordinator/renderer.lua | 8 +-- coordinator/startup.lua | 4 +- graphics/core.lua | 62 ++++++++++++++++++- graphics/element.lua | 18 +++--- graphics/elements/controls/hazard_button.lua | 8 +-- graphics/elements/controls/multi_button.lua | 7 ++- graphics/elements/controls/push_button.lua | 10 +-- graphics/elements/controls/radio_button.lua | 6 +- .../elements/controls/spinbox_numeric.lua | 6 +- graphics/elements/controls/switch_button.lua | 6 +- reactor-plc/startup.lua | 2 +- 11 files changed, 97 insertions(+), 40 deletions(-) diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index edf0de8..f0563ca 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -171,15 +171,15 @@ end function renderer.ui_ready() return engine.ui_ready end -- handle a touch event ----@param event monitor_touch -function renderer.handle_touch(event) +---@param event mouse_interaction +function renderer.handle_mouse(event) if event.monitor == engine.monitors.primary_name then - ui.main_layout.handle_touch(event) + ui.main_layout.handle_mouse(event) else for id, monitor in pairs(engine.monitors.unit_name_map) do if event.monitor == monitor then local layout = ui.unit_layouts[id] ---@type graphics_element - layout.handle_touch(event) + layout.handle_mouse(event) end end end diff --git a/coordinator/startup.lua b/coordinator/startup.lua index a6a81f1..95178ad 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -19,7 +19,7 @@ local iocontrol = require("coordinator.iocontrol") local renderer = require("coordinator.renderer") local sounder = require("coordinator.sounder") -local COORDINATOR_VERSION = "v0.12.3" +local COORDINATOR_VERSION = "v0.12.4" local print = util.print local println = util.println @@ -354,7 +354,7 @@ local function main() end elseif event == "monitor_touch" then -- handle a monitor touch event - renderer.handle_touch(core.events.touch(param1, param2, param3)) + renderer.handle_mouse(core.events.touch(param1, param2, param3)) elseif event == "speaker_audio_empty" then -- handle speaker buffer emptied sounder.continue() diff --git a/graphics/core.lua b/graphics/core.lua index 98c8ed5..d03e551 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -10,20 +10,76 @@ core.flasher = flasher local events = {} ----@class monitor_touch +---@enum click_type +events.click_type = { + VIRTUAL = 0, + LEFT_BUTTON = 1, + RIGHT_BUTTON = 2, + MID_BUTTON = 3 +} + +---@class mouse_interaction ---@field monitor string +---@field button integer ---@field x integer ---@field y integer --- create a new touch event definition +-- create a new monitor touch mouse interaction event ---@nodiscard ---@param monitor string ---@param x integer ---@param y integer ----@return monitor_touch +---@return mouse_interaction function events.touch(monitor, x, y) return { monitor = monitor, + button = events.click_type.LEFT_BUTTON, + x = x, + y = y + } +end + +-- create a new mouse click mouse interaction event +---@nodiscard +---@param button click_type +---@param x integer +---@param y integer +---@return mouse_interaction +function events.click(button, x, y) + return { + monitor = "terminal", + button = button, + x = x, + y = y + } +end + +-- create a new transposed mouse interaction event using the event's monitor/button fields +---@nodiscard +---@param event mouse_interaction +---@param new_x integer +---@param new_y integer +---@return mouse_interaction +function events.mouse_transposed(event, new_x, new_y) + return { + monitor = event.monitor, + button = event.button, + x = new_x, + y = new_y + } +end + +-- create a new generic mouse interaction event +---@nodiscard +---@param monitor string +---@param button click_type +---@param x integer +---@param y integer +---@return mouse_interaction +function events.mouse_generic(monitor, button, x, y) + return { + monitor = monitor, + button = button, x = x, y = y } diff --git a/graphics/element.lua b/graphics/element.lua index 5636fd0..d9bc489 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -166,9 +166,9 @@ function element.new(args) self.bounds.y2 = self.position.y + f.h - 1 end - -- handle a touch event - ---@param event table monitor_touch event - function protected.handle_touch(event) + -- handle a mouse event + ---@param event mouse_interaction mouse interaction event + function protected.handle_mouse(event) end -- handle data value changes @@ -410,20 +410,20 @@ function element.new(args) -- FUNCTION CALLBACKS -- - -- handle a monitor touch - ---@param event monitor_touch monitor touch event - function public.handle_touch(event) + -- handle a monitor touch or mouse click + ---@param event mouse_interaction mouse interaction event + function public.handle_mouse(event) local in_x = event.x >= self.bounds.x1 and event.x <= self.bounds.x2 local in_y = event.y >= self.bounds.y1 and event.y <= self.bounds.y2 if in_x and in_y then - local event_T = core.events.touch(event.monitor, (event.x - self.position.x) + 1, (event.y - self.position.y) + 1) + local event_T = core.events.mouse_transposed(event, (event.x - self.position.x) + 1, (event.y - self.position.y) + 1) -- handle the touch event, transformed into the window frame - protected.handle_touch(event_T) + protected.handle_mouse(event_T) -- pass on touch event to children - for _, val in pairs(self.children) do val.handle_touch(event_T) end + for _, val in pairs(self.children) do val.handle_mouse(event_T) end end end diff --git a/graphics/elements/controls/hazard_button.lua b/graphics/elements/controls/hazard_button.lua index 0b59df6..e9f2bf4 100644 --- a/graphics/elements/controls/hazard_button.lua +++ b/graphics/elements/controls/hazard_button.lua @@ -140,10 +140,10 @@ local function hazard_button(args) end end - -- handle touch - ---@param event monitor_touch monitor touch event + -- handle mouse interaction + ---@param event mouse_interaction mouse event ---@diagnostic disable-next-line: unused-local - function e.handle_touch(event) + function e.handle_mouse(event) if e.enabled then -- change text color to indicate clicked e.window.setTextColor(args.accent) @@ -178,7 +178,7 @@ local function hazard_button(args) -- set the value (true simulates pressing the button) ---@param val boolean new value function e.set_value(val) - if val then e.handle_touch(core.events.touch("", 1, 1)) end + if val then e.handle_mouse(core.events.mouse_generic("", core.events.click_type.VIRTUAL, 1, 1)) end end -- show the button as disabled diff --git a/graphics/elements/controls/multi_button.lua b/graphics/elements/controls/multi_button.lua index 2cf583a..2549e2b 100644 --- a/graphics/elements/controls/multi_button.lua +++ b/graphics/elements/controls/multi_button.lua @@ -92,9 +92,10 @@ local function multi_button(args) end end - -- handle touch - ---@param event monitor_touch monitor touch event - function e.handle_touch(event) + -- handle mouse interaction + ---@param event mouse_interaction mouse event +---@diagnostic disable-next-line: unused-local + function e.handle_mouse(event) -- determine what was pressed if e.enabled and event.y == 1 then for i = 1, #args.options do diff --git a/graphics/elements/controls/push_button.lua b/graphics/elements/controls/push_button.lua index 8cb89c9..d0c1299 100644 --- a/graphics/elements/controls/push_button.lua +++ b/graphics/elements/controls/push_button.lua @@ -8,7 +8,7 @@ local element = require("graphics.element") ---@class push_button_args ---@field text string button text ---@field callback function function to call on touch ----@field min_width? integer text length + 2 if omitted +---@field min_width? integer text length if omitted ---@field active_fg_bg? cpair foreground/background colors when pressed ---@field dis_fg_bg? cpair foreground/background colors when disabled ---@field parent graphics_element @@ -47,10 +47,10 @@ local function push_button(args) e.window.write(args.text) end - -- handle touch - ---@param event monitor_touch monitor touch event + -- handle mouse interaction + ---@param event mouse_interaction mouse event ---@diagnostic disable-next-line: unused-local - function e.handle_touch(event) + function e.handle_mouse(event) if e.enabled then if args.active_fg_bg ~= nil then -- show as pressed @@ -78,7 +78,7 @@ local function push_button(args) -- set the value (true simulates pressing the button) ---@param val boolean new value function e.set_value(val) - if val then e.handle_touch(core.events.touch("", 1, 1)) end + if val then e.handle_mouse(core.events.mouse_generic("", core.events.click_type.VIRTUAL, 1, 1)) end end -- show butten as enabled diff --git a/graphics/elements/controls/radio_button.lua b/graphics/elements/controls/radio_button.lua index 025cad1..3b2a593 100644 --- a/graphics/elements/controls/radio_button.lua +++ b/graphics/elements/controls/radio_button.lua @@ -79,9 +79,9 @@ local function radio_button(args) end end - -- handle touch - ---@param event monitor_touch monitor touch event - function e.handle_touch(event) + -- handle mouse interaction + ---@param event mouse_interaction mouse event + function e.handle_mouse(event) -- determine what was pressed if e.enabled then if args.options[event.y] ~= nil then diff --git a/graphics/elements/controls/spinbox_numeric.lua b/graphics/elements/controls/spinbox_numeric.lua index 088d847..ffbd1f8 100644 --- a/graphics/elements/controls/spinbox_numeric.lua +++ b/graphics/elements/controls/spinbox_numeric.lua @@ -127,9 +127,9 @@ local function spinbox(args) -- init with the default value show_num() - -- handle touch - ---@param event monitor_touch monitor touch event - function e.handle_touch(event) + -- handle mouse interaction + ---@param event mouse_interaction mouse event + function e.handle_mouse(event) -- only handle if on an increment or decrement arrow if e.enabled and event.x ~= dec_point_x then local idx = util.trinary(event.x > dec_point_x, event.x - 1, event.x) diff --git a/graphics/elements/controls/switch_button.lua b/graphics/elements/controls/switch_button.lua index bf138f2..133ea45 100644 --- a/graphics/elements/controls/switch_button.lua +++ b/graphics/elements/controls/switch_button.lua @@ -62,10 +62,10 @@ local function switch_button(args) -- initial draw draw_state() - -- handle touch - ---@param event monitor_touch monitor touch event + -- handle mouse interaction + ---@param event mouse_interaction mouse event ---@diagnostic disable-next-line: unused-local - function e.handle_touch(event) + function e.handle_mouse(event) if e.enabled then -- toggle state e.value = not e.value diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 59c5ced..920877c 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -17,7 +17,7 @@ local plc = require("reactor-plc.plc") local renderer = require("reactor-plc.renderer") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v1.1.1" +local R_PLC_VERSION = "v1.1.2" local print = util.print local println = util.println From 6bd7dd0271f506b3f98d13027f0e106e76f3c473 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Apr 2023 21:35:16 -0400 Subject: [PATCH 5/7] improved rectangle graphics element feature set --- graphics/elements/rectangle.lua | 48 ++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/graphics/elements/rectangle.lua b/graphics/elements/rectangle.lua index 6422cbc..a4d8dde 100644 --- a/graphics/elements/rectangle.lua +++ b/graphics/elements/rectangle.lua @@ -7,6 +7,7 @@ local element = require("graphics.element") ---@class rectangle_args ---@field border? graphics_border ---@field thin? boolean true to use extra thin even borders +---@field even_inner? boolean true to make the inner area of a border even ---@field parent graphics_element ---@field id? string element id ---@field x? integer 1 if omitted @@ -66,14 +67,27 @@ local function rectangle(args) local blit_bg_top_bot = util.strrep(border_blit, e.frame.w) -- partial bars - local p_a = util.spaces(border_width) .. util.strrep("\x8f", inner_width) .. util.spaces(border_width) - local p_b = util.spaces(border_width) .. util.strrep("\x83", inner_width) .. util.spaces(border_width) - local p_s = spaces - + local p_a, p_b, p_s if args.thin == true then - p_a = "\x97" .. util.strrep("\x83", inner_width) .. "\x94" - p_b = "\x8a" .. util.strrep("\x8f", inner_width) .. "\x85" + if args.even_inner == true then + p_a = "\x9c" .. util.strrep("\x8c", inner_width) .. "\x93" + p_b = "\x8d" .. util.strrep("\x8c", inner_width) .. "\x8e" + else + p_a = "\x97" .. util.strrep("\x83", inner_width) .. "\x94" + p_b = "\x8a" .. util.strrep("\x8f", inner_width) .. "\x85" + end + p_s = "\x95" .. util.spaces(inner_width) .. "\x95" + else + if args.even_inner == true then + p_a = util.strrep("\x83", inner_width + width_x2) + p_b = util.strrep("\x8f", inner_width + width_x2) + else + p_a = util.spaces(border_width) .. util.strrep("\x83", inner_width) .. util.spaces(border_width) + p_b = util.spaces(border_width) .. util.strrep("\x8f", inner_width) .. util.spaces(border_width) + end + + p_s = spaces end local p_inv_fg = util.strrep(border_blit, border_width) .. util.strrep(e.fg_bg.blit_bkg, inner_width) .. @@ -112,10 +126,13 @@ local function rectangle(args) if args.thin == true then e.window.blit(p_a, p_inv_bg, p_inv_fg) else + local _fg = util.trinary(args.even_inner == true, util.strrep(e.fg_bg.blit_bkg, e.frame.w), p_inv_bg) + local _bg = util.trinary(args.even_inner == true, blit_bg_top_bot, p_inv_fg) + if width_x2 % 3 == 1 then - e.window.blit(p_b, p_inv_bg, p_inv_fg) + e.window.blit(p_b, _fg, _bg) elseif width_x2 % 3 == 2 then - e.window.blit(p_a, p_inv_bg, p_inv_fg) + e.window.blit(p_a, _fg, _bg) else -- skip line e.window.blit(spaces, blit_fg, blit_bg_sides) @@ -129,12 +146,19 @@ local function rectangle(args) -- partial pixel fill if args.border.even and y == ((e.frame.h - border_width) + 1) then if args.thin == true then - e.window.blit(p_b, util.strrep(e.fg_bg.blit_bkg, e.frame.w), blit_bg_top_bot) + if args.even_inner == true then + e.window.blit(p_b, blit_bg_top_bot, util.strrep(e.fg_bg.blit_bkg, e.frame.w)) + else + e.window.blit(p_b, util.strrep(e.fg_bg.blit_bkg, e.frame.w), blit_bg_top_bot) + end else + local _fg = util.trinary(args.even_inner == true, blit_bg_top_bot, p_inv_fg) + local _bg = util.trinary(args.even_inner == true, util.strrep(e.fg_bg.blit_bkg, e.frame.w), blit_bg_top_bot) + if width_x2 % 3 == 1 then - e.window.blit(p_a, p_inv_fg, blit_bg_top_bot) - elseif width_x2 % 3 == 2 or (args.thin == true) then - e.window.blit(p_b, p_inv_fg, blit_bg_top_bot) + e.window.blit(p_a, _fg, _bg) + elseif width_x2 % 3 == 2 then + e.window.blit(p_b, _fg, _bg) else -- skip line e.window.blit(spaces, blit_fg, blit_bg_sides) From 27a697c27e98b651fee2e1799f5deefad3e25870 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Apr 2023 21:35:44 -0400 Subject: [PATCH 6/7] #182 added scram/reset buttons to PLC front panel --- reactor-plc/databus.lua | 71 +++++++++++++++++++------------ reactor-plc/panel/front_panel.lua | 17 ++++++-- reactor-plc/panel/style.lua | 5 ++- reactor-plc/plc.lua | 3 ++ reactor-plc/renderer.lua | 6 +-- reactor-plc/startup.lua | 2 +- reactor-plc/threads.lua | 6 +++ 7 files changed, 73 insertions(+), 37 deletions(-) diff --git a/reactor-plc/databus.lua b/reactor-plc/databus.lua index dc66c56..eeb2260 100644 --- a/reactor-plc/databus.lua +++ b/reactor-plc/databus.lua @@ -1,84 +1,101 @@ -- --- Data Bus - Central Communication Linking for PLC PSIL +-- Data Bus - Central Communication Linking for PLC Front Panel -- +local log = require("scada-common.log") local psil = require("scada-common.psil") local util = require("scada-common.util") local databus = {} -local ps = psil.create() +local dbus_iface = { + ps = psil.create(), + rps_scram = function () log.debug("DBUS: unset rps_scram() called") end, + rps_reset = function () log.debug("DBUS: unset rps_reset() called") end +} -- call to toggle heartbeat signal -function databus.heartbeat() - ps.toggle("heartbeat") +function databus.heartbeat() dbus_iface.ps.toggle("heartbeat") end + +-- link RPS command functions +---@param scram function reactor SCRAM function +---@param reset function RPS reset function +function databus.link_rps(scram, reset) + dbus_iface.rps_scram = scram + dbus_iface.rps_reset = reset end +-- transmit a command to the RPS to SCRAM +function databus.rps_scram() dbus_iface.rps_scram() end + +-- transmit a command to the RPS to reset +function databus.rps_reset() dbus_iface.rps_reset() 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) + dbus_iface.ps.publish("version", plc_v) + dbus_iface.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) + dbus_iface.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) + dbus_iface.ps.publish("reactor_dev_state", util.trinary(plc_state.no_reactor, 1, util.trinary(plc_state.reactor_formed, 3, 2))) + dbus_iface.ps.publish("has_modem", not plc_state.no_modem) + dbus_iface.ps.publish("degraded", plc_state.degraded) + dbus_iface.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) + dbus_iface.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) + dbus_iface.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) + dbus_iface.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]) + dbus_iface.ps.publish("rps_scram", tripped) + dbus_iface.ps.publish("rps_damage", status[1]) + dbus_iface.ps.publish("rps_high_temp", status[2]) + dbus_iface.ps.publish("rps_low_ccool", status[3]) + dbus_iface.ps.publish("rps_high_waste", status[4]) + dbus_iface.ps.publish("rps_high_hcool", status[5]) + dbus_iface.ps.publish("rps_no_fuel", status[6]) + dbus_iface.ps.publish("rps_fault", status[7]) + dbus_iface.ps.publish("rps_timeout", status[8]) + dbus_iface.ps.publish("rps_manual", status[9]) + dbus_iface.ps.publish("rps_automatic", status[10]) + dbus_iface.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) + dbus_iface.ps.subscribe(field, func) end return databus diff --git a/reactor-plc/panel/front_panel.lua b/reactor-plc/panel/front_panel.lua index 09ce452..d7e07b1 100644 --- a/reactor-plc/panel/front_panel.lua +++ b/reactor-plc/panel/front_panel.lua @@ -16,6 +16,8 @@ local Div = require("graphics.elements.div") local Rectangle = require("graphics.elements.rectangle") local TextBox = require("graphics.elements.textbox") +local PushButton = require("graphics.elements.controls.push_button") + local LED = require("graphics.elements.indicators.led") local LEDPair = require("graphics.elements.indicators.ledpair") local RGBLED = require("graphics.elements.indicators.ledrgb") @@ -65,11 +67,18 @@ local function init(monitor) databus.rx_field("routine__comms_rx", rt_cmrx.update) databus.rx_field("routine__spctl", rt_sctl.update) - local status = Div{parent=panel,width=16,height=4,x=18,y=3} + local status = Div{parent=panel,width=19,height=18,x=17,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() + local active = LED{parent=status,x=2,width=12,label="RCT ACTIVE",colors=cpair(colors.green,colors.green_off)} + + local status_trip_rct = Rectangle{parent=status,width=20,height=3,x=1,y=2,border=border(1,colors.lightGray,true),even_inner=true,fg_bg=cpair(colors.black,colors.ivory)} + local status_trip = Div{parent=status_trip_rct,width=18,height=1,fg_bg=cpair(colors.black,colors.lightGray)} + local scram = LED{parent=status_trip,width=10,label="RPS TRIP",colors=cpair(colors.red,colors.red_off),flash=true,period=flasher.PERIOD.BLINK_250_MS} + + local controls_rct = Rectangle{parent=status,width=17,height=3,x=1,y=5,border=border(1,colors.white,true),even_inner=true,fg_bg=cpair(colors.black,colors.ivory)} + local controls = Div{parent=controls_rct,width=15,height=1,fg_bg=cpair(colors.black,colors.white)} + 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)} databus.rx_field("reactor_active", active.update) databus.rx_field("rps_scram", scram.update) diff --git a/reactor-plc/panel/style.lua b/reactor-plc/panel/style.lua index 42e2a02..01b00c9 100644 --- a/reactor-plc/panel/style.lua +++ b/reactor-plc/panel/style.lua @@ -13,6 +13,7 @@ local cpair = core.graphics.cpair -- remap global colors colors.ivory = colors.pink colors.red_off = colors.brown +colors.yellow_off = colors.magenta colors.green_off = colors.lime style.root = cpair(colors.black, colors.ivory) @@ -29,9 +30,9 @@ style.colors = { { c = colors.blue, hex = 0x0096ff }, { c = colors.purple, hex = 0xb156ee }, { c = colors.pink, hex = 0xdcd9ca }, -- IVORY - { c = colors.magenta, hex = 0xf9488a }, + { c = colors.magenta, hex = 0x85862c }, -- YELLOW OFF -- { c = colors.white, hex = 0xdcd9ca }, - { c = colors.lightGray, hex = 0x999f9b }, + { c = colors.lightGray, hex = 0xb1b8b3 }, { c = colors.gray, hex = 0x575757 }, -- { c = colors.black, hex = 0x191919 }, { c = colors.brown, hex = 0x672223 } -- RED OFF diff --git a/reactor-plc/plc.lua b/reactor-plc/plc.lua index 3045490..16bc0b9 100644 --- a/reactor-plc/plc.lua +++ b/reactor-plc/plc.lua @@ -396,6 +396,9 @@ function plc.rps_init(reactor, is_formed) end end + -- link functions with databus + databus.link_rps(public.trip_manual, public.reset) + return public end diff --git a/reactor-plc/renderer.lua b/reactor-plc/renderer.lua index 458c052..5fa2452 100644 --- a/reactor-plc/renderer.lua +++ b/reactor-plc/renderer.lua @@ -67,9 +67,9 @@ end function renderer.ui_ready() return ui.view ~= nil end -- handle a touch event ----@param event monitor_touch -function renderer.handle_touch(event) - ui.view.handle_touch(event) +---@param event mouse_interaction +function renderer.handle_mouse(event) + ui.view.handle_mouse(event) end return renderer diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index 920877c..de83875 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -17,7 +17,7 @@ local plc = require("reactor-plc.plc") local renderer = require("reactor-plc.renderer") local threads = require("reactor-plc.threads") -local R_PLC_VERSION = "v1.1.2" +local R_PLC_VERSION = "v1.1.3" local print = util.print local println = util.println diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index dc8bad3..61879a8 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -5,6 +5,9 @@ local tcallbackdsp = require("scada-common.tcallbackdsp") local util = require("scada-common.util") local databus = require("reactor-plc.databus") +local renderer = require("reactor-plc.renderer") + +local core = require("graphics.core") local threads = {} @@ -255,6 +258,9 @@ function threads.thread__main(smem, init) -- update indicators databus.tx_hw_status(plc_state) + elseif event == "mouse_click" then + -- handle a monitor touch event + renderer.handle_mouse(core.events.click(param1, param2, param3)) elseif event == "clock_start" then -- start loop clock loop_clock.start() From d624690b6b30a4e25fa7c77e561b3827dc2d72b5 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sat, 8 Apr 2023 22:00:51 -0400 Subject: [PATCH 7/7] comment/indentation fixes --- graphics/elements/indicators/ledpair.lua | 2 +- graphics/elements/indicators/ledrgb.lua | 2 +- reactor-plc/panel/front_panel.lua | 26 ++++++++++++------------ reactor-plc/renderer.lua | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/graphics/elements/indicators/ledpair.lua b/graphics/elements/indicators/ledpair.lua index b05c4e6..aaf94ec 100644 --- a/graphics/elements/indicators/ledpair.lua +++ b/graphics/elements/indicators/ledpair.lua @@ -19,7 +19,7 @@ local flasher = require("graphics.flasher") ---@field y? integer 1 if omitted ---@field fg_bg? cpair foreground/background colors --- new tri-state indicator light +-- new dual LED indicator light ---@nodiscard ---@param args indicator_led_pair_args ---@return graphics_element element, element_id id diff --git a/graphics/elements/indicators/ledrgb.lua b/graphics/elements/indicators/ledrgb.lua index 8529ea8..c58b835 100644 --- a/graphics/elements/indicators/ledrgb.lua +++ b/graphics/elements/indicators/ledrgb.lua @@ -12,7 +12,7 @@ local element = require("graphics.element") ---@field y? integer 1 if omitted ---@field fg_bg? cpair foreground/background colors --- new tri-state indicator light +-- new RGB LED indicator light ---@nodiscard ---@param args indicator_led_rgb_args ---@return graphics_element element, element_id id diff --git a/reactor-plc/panel/front_panel.lua b/reactor-plc/panel/front_panel.lua index d7e07b1..f875b5d 100644 --- a/reactor-plc/panel/front_panel.lua +++ b/reactor-plc/panel/front_panel.lua @@ -2,25 +2,25 @@ -- Main SCADA Coordinator GUI -- -local util = require("scada-common.util") +local util = require("scada-common.util") -local databus = require("reactor-plc.databus") +local databus = require("reactor-plc.databus") -local style = require("reactor-plc.panel.style") +local style = require("reactor-plc.panel.style") -local core = require("graphics.core") -local flasher = require("graphics.flasher") +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 DisplayBox = require("graphics.elements.displaybox") +local Div = require("graphics.elements.div") +local Rectangle = require("graphics.elements.rectangle") +local TextBox = require("graphics.elements.textbox") -local PushButton = require("graphics.elements.controls.push_button") +local PushButton = require("graphics.elements.controls.push_button") -local LED = require("graphics.elements.indicators.led") -local LEDPair = require("graphics.elements.indicators.ledpair") -local RGBLED = require("graphics.elements.indicators.ledrgb") +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 diff --git a/reactor-plc/renderer.lua b/reactor-plc/renderer.lua index 5fa2452..ad2bcc0 100644 --- a/reactor-plc/renderer.lua +++ b/reactor-plc/renderer.lua @@ -66,7 +66,7 @@ end ---@return boolean ready function renderer.ui_ready() return ui.view ~= nil end --- handle a touch event +-- handle a mouse event ---@param event mouse_interaction function renderer.handle_mouse(event) ui.view.handle_mouse(event)