From 806b217d58da577fcc75159bfb6c19bc04e73378 Mon Sep 17 00:00:00 2001 From: Mikayla Fischler Date: Sun, 6 Nov 2022 18:41:52 -0500 Subject: [PATCH] #100 interactive reactor controls (start, scram, reset) --- coordinator/coordinator.lua | 24 +++- coordinator/iocontrol.lua | 12 ++ coordinator/startup.lua | 2 +- coordinator/ui/components/unit_detail.lua | 30 +++-- graphics/element.lua | 11 ++ graphics/elements/controls/hazard_button.lua | 121 +++++++++++++++++-- scada-common/tcallbackdsp.lua | 14 ++- supervisor/session/coordinator.lua | 7 ++ supervisor/session/plc.lua | 14 --- supervisor/session/svqtypes.lua | 5 + supervisor/session/svsessions.lua | 8 +- supervisor/startup.lua | 2 +- 12 files changed, 211 insertions(+), 39 deletions(-) diff --git a/coordinator/coordinator.lua b/coordinator/coordinator.lua index 13ec9a4..05b8443 100644 --- a/coordinator/coordinator.lua +++ b/coordinator/coordinator.lua @@ -431,15 +431,27 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, sv_wa -- unit command acknowledgement if packet.length == 3 then local cmd = packet.data[1] - local unit = packet.data[2] + local unit_id = packet.data[2] local ack = packet.data[3] - if cmd == CRDN_COMMANDS.SCRAM then - elseif cmd == CRDN_COMMANDS.START then - elseif cmd == CRDN_COMMANDS.RESET_RPS then - elseif cmd == CRDN_COMMANDS.SET_BURN then - elseif cmd == CRDN_COMMANDS.SET_WASTE then + local unit = iocontrol.get_db().units[unit_id] ---@type ioctl_entry + + if unit ~= nil then + if cmd == CRDN_COMMANDS.SCRAM then + unit.scram_ack(ack) + elseif cmd == CRDN_COMMANDS.START then + unit.start_ack(ack) + elseif cmd == CRDN_COMMANDS.RESET_RPS then + unit.reset_rps_ack(ack) + elseif cmd == CRDN_COMMANDS.SET_BURN then + unit.set_burn_ack(ack) + elseif cmd == CRDN_COMMANDS.SET_WASTE then + unit.set_waste_ack(ack) + else + log.debug(util.c("received command ack with unknown command ", cmd)) + end else + log.debug(util.c("received command ack with unknown unit ", unit_id)) end else log.debug("unit command ack packet length mismatch") diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index 26ef62b..0d953ae 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -38,6 +38,13 @@ function iocontrol.init(conf, comms) scram = function () end, reset_rps = function () end, set_burn = function (rate) end, + set_waste = function (mode) end, + + start_ack = function (success) end, + scram_ack = function (success) end, + reset_rps_ack = function (success) end, + set_burn_ack = function (success) end, + set_waste_ack = function (success) end, reactor_ps = psil.create(), reactor_data = {}, ---@type reactor_db @@ -71,6 +78,11 @@ function iocontrol.init(conf, comms) log.debug(util.c("UNIT[", i, "]: SET_BURN = ", rate)) end + function entry.set_waste(mode) + comms.send_command(CRDN_COMMANDS.SET_WASTE, i, mode) + log.debug(util.c("UNIT[", i, "]: SET_WASTE = ", mode)) + end + -- create boiler tables for _ = 1, conf.defs[(i * 2) - 1] do local data = {} ---@type boilerv_session_db diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 0d2a81d..95616d0 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -16,7 +16,7 @@ local config = require("coordinator.config") local coordinator = require("coordinator.coordinator") local renderer = require("coordinator.renderer") -local COORDINATOR_VERSION = "alpha-v0.6.2" +local COORDINATOR_VERSION = "alpha-v0.6.3" local print = util.print local println = util.println diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index 182b773..04fa8ee 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -239,15 +239,32 @@ local function init(parent, id) ---@todo radiation monitor IndicatorLight{parent=annunciator,label="Radiation Monitor",colors=cpair(colors.green,colors.gray)} IndicatorLight{parent=annunciator,label="Radiation Alarm",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} - DataIndicator{parent=main,x=34,y=51,label="",format="%10.1f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=18,fg_bg=stat_fg_bg} + DataIndicator{parent=main,x=22,y=22,label="",format="%3.2f",value=0,unit="mSv/h",lu_colors=lu_cpair,width=11,fg_bg=stat_fg_bg} -- reactor controls -- - HazardButton{parent=main,x=2,y=44,text="START",accent=colors.lightBlue,callback=unit.start,fg_bg=scram_fg_bg} - HazardButton{parent=main,x=12,y=44,text="SCRAM",accent=colors.yellow,callback=unit.scram,fg_bg=scram_fg_bg} - HazardButton{parent=main,x=22,y=44,text="RESET",accent=colors.red,callback=unit.reset_rps,fg_bg=scram_fg_bg} + local dis_colors = cpair(colors.white, colors.lightGray) - local burn_control = Div{parent=main,x=12,y=40,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} + local start = HazardButton{parent=main,x=2,y=26,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=scram_fg_bg} + local scram = HazardButton{parent=main,x=12,y=26,text="SCRAM",accent=colors.yellow,dis_colors=dis_colors,callback=unit.scram,fg_bg=scram_fg_bg} + local reset = HazardButton{parent=main,x=22,y=26,text="RESET",accent=colors.red,dis_colors=dis_colors,callback=unit.reset_rps,fg_bg=scram_fg_bg} + + unit.start_ack = start.on_response + unit.scram_ack = scram.on_response + unit.reset_rps_ack = reset.on_response + + local function start_button_en_check() + if (unit.reactor_data ~= nil) and (unit.reactor_data.mek_status ~= nil) then + local can_start = (not unit.reactor_data.mek_status.status) and (not unit.reactor_data.rps_tripped) + if can_start then start.enable() else start.disable() end + end + end + + r_ps.subscribe("status", start_button_en_check) + r_ps.subscribe("rps_tripped", start_button_en_check) + r_ps.subscribe("rps_tripped", function (active) if active then reset.enable() else reset.disable() end end) + + local burn_control = Div{parent=main,x=2,y=22,width=19,height=3,fg_bg=cpair(colors.gray,colors.white)} local burn_rate = SpinboxNumeric{parent=burn_control,x=2,y=1,whole_num_precision=4,fractional_precision=1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=cpair(colors.black,colors.white)} TextBox{parent=burn_control,x=9,y=2,text="mB/t"} @@ -280,10 +297,9 @@ local function init(parent, id) } ---@todo waste selection - local waste_sel_f = function (s) print("waste: " .. s) end local waste_sel = Div{parent=main,x=2,y=48,width=29,height=2,fg_bg=cpair(colors.black, colors.white)} - MultiButton{parent=waste_sel,x=1,y=1,options=opts,callback=waste_sel_f,min_width=6,fg_bg=cpair(colors.black, colors.white)} + MultiButton{parent=waste_sel,x=1,y=1,options=opts,callback=unit.set_waste,min_width=6,fg_bg=cpair(colors.black, colors.white)} TextBox{parent=waste_sel,text="Waste Processing",alignment=TEXT_ALIGN.CENTER,x=1,y=1,height=1} ---@fixme test code diff --git a/graphics/element.lua b/graphics/element.lua index 178f2d7..33decff 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -163,6 +163,11 @@ function element.new(args) function protected.on_update(...) end + -- callback on control press responses + ---@param result any + function protected.response_callback(result) + end + -- get value function protected.get_value() return protected.value @@ -354,6 +359,12 @@ function element.new(args) protected.on_update(...) end + -- on a control request response + ---@param result any + function public.on_response(result) + protected.response_callback(result) + end + -- VISIBILITY -- -- show the element diff --git a/graphics/elements/controls/hazard_button.lua b/graphics/elements/controls/hazard_button.lua index 5fca7fe..7369866 100644 --- a/graphics/elements/controls/hazard_button.lua +++ b/graphics/elements/controls/hazard_button.lua @@ -8,6 +8,7 @@ local element = require("graphics.element") ---@class hazard_button_args ---@field text string text to show on button ---@field accent color accent color for hazard border +---@field dis_colors? cpair text color and border color when disabled ---@field callback function function to call on touch ---@field parent graphics_element ---@field id? string element id @@ -62,6 +63,82 @@ local function hazard_button(args) e.window.write("\x99\x98\x98\x98\x98\x98\x98\x98\x99") end + -- on request timeout: recursively calls itself to double flash button text + ---@param n integer call count + local function on_timeout(n) + -- start at 0 + if n == nil then n = 0 end + + if n == 0 then + -- go back off + e.window.setTextColor(args.fg_bg.fgd) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + end + + if n >= 4 then + -- done + elseif n % 2 == 0 then + -- toggle text color on after 0.25 seconds + tcd.dispatch(0.25, function () + e.window.setTextColor(args.accent) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + on_timeout(n + 1) + on_timeout(n + 1) + end) + elseif n % 1 then + -- toggle text color off after 0.25 seconds + tcd.dispatch(0.25, function () + e.window.setTextColor(args.fg_bg.fgd) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + on_timeout(n + 1) + end) + end + end + + -- blink routine for success indication + local function on_success() + e.window.setTextColor(args.fg_bg.fgd) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + end + + -- blink routine for failure indication + ---@param n integer call count + local function on_failure(n) + -- start at 0 + if n == nil then n = 0 end + + if n == 0 then + -- go back off + e.window.setTextColor(args.fg_bg.fgd) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + end + + if n >= 2 then + -- done + elseif n % 2 == 0 then + -- toggle text color on after 0.5 seconds + tcd.dispatch(0.5, function () + e.window.setTextColor(args.accent) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + on_failure(n + 1) + end) + elseif n % 1 then + -- toggle text color off after 0.25 seconds + tcd.dispatch(0.25, function () + e.window.setTextColor(args.fg_bg.fgd) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + on_failure(n + 1) + end) + end + end + -- handle touch ---@param event monitor_touch monitor touch event ---@diagnostic disable-next-line: unused-local @@ -75,12 +152,25 @@ local function hazard_button(args) e.window.setCursorPos(3, 2) e.window.write(args.text) - -- restore text color after 1 second - tcd.dispatch(1, function () - e.window.setTextColor(args.fg_bg.fgd) - e.window.setCursorPos(3, 2) - e.window.write(args.text) - end) + -- abort any other callbacks + tcd.abort(on_timeout) + tcd.abort(on_success) + tcd.abort(on_failure) + + -- 1.5 second timeout + tcd.dispatch(1.5, on_timeout) + end + end + + -- callback on request response + ---@param result boolean true for success, false for failure + function e.response_callback(result) + tcd.abort(on_timeout) + + if result then + on_success() + else + on_failure(0) end end @@ -90,8 +180,25 @@ local function hazard_button(args) if val then e.handle_touch(core.events.touch("", 1, 1)) end end + -- show the button as disabled + function e.disable() + if args.dis_colors then + draw_border(args.dis_colors.color_a) + e.window.setTextColor(args.dis_colors.color_b) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + end + end + + -- show the button as enabled + function e.enable() + draw_border(args.accent) + e.window.setTextColor(args.fg_bg.fgd) + e.window.setCursorPos(3, 2) + e.window.write(args.text) + end + -- initial draw of border - ---@todo disabling will change border draw_border(args.accent) return e.get() diff --git a/scada-common/tcallbackdsp.lua b/scada-common/tcallbackdsp.lua index d4926c0..52f55da 100644 --- a/scada-common/tcallbackdsp.lua +++ b/scada-common/tcallbackdsp.lua @@ -27,7 +27,7 @@ end ---@param time number seconds ---@param f function callback function function tcallbackdsp.dispatch_unique(time, f) - -- ignore if already registered + -- cancel if already registered for timer, entry in pairs(registry) do if entry.callback == f then -- found an instance of this function reference, abort it @@ -49,6 +49,18 @@ function tcallbackdsp.dispatch_unique(time, f) -- log.debug(util.c("TCD: queued callback for ", f, " [timer: ", timer, "]")) end +-- abort a requested callback +---@param f function callback function +function tcallbackdsp.abort(f) + for timer, entry in pairs(registry) do + if entry.callback == f then + -- cancel event and remove from registry (even if it fires it won't call) + util.cancel_timer(timer) + registry[timer] = nil + end + end +end + -- lookup a timer event and execute the callback if found ---@param event integer timer event timer ID function tcallbackdsp.handle(event) diff --git a/supervisor/session/coordinator.lua b/supervisor/session/coordinator.lua index 3db1da7..53ba62c 100644 --- a/supervisor/session/coordinator.lua +++ b/supervisor/session/coordinator.lua @@ -29,6 +29,7 @@ local CRD_S_CMDS = { } local CRD_S_DATA = { + CMD_ACK = 1 } coordinator.CRD_S_CMDS = CRD_S_CMDS @@ -271,6 +272,12 @@ function coordinator.new_session(id, in_queue, out_queue, facility_units) end elseif message.qtype == mqueue.TYPE.DATA then -- instruction with body + local cmd = message.message ---@type queue_data + + if cmd.key == CRD_S_DATA.CMD_ACK then + local ack = cmd.val ---@type coord_ack + _send(SCADA_CRDN_TYPES.COMMAND_UNIT, { ack.cmd, ack.unit, ack.ack }) + end end end diff --git a/supervisor/session/plc.lua b/supervisor/session/plc.lua index 6b066ef..4e72a68 100644 --- a/supervisor/session/plc.lua +++ b/supervisor/session/plc.lua @@ -74,14 +74,12 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) struct_req = (util.time() + 500), status_req = (util.time() + 500), scram_req = 0, - enable_req = 0, burn_rate_req = 0, rps_reset_req = 0 }, -- command acknowledgements acks = { scram = true, - enable = true, burn_rate = true, rps_reset = true }, @@ -355,7 +353,6 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) -- enable acknowledgement local ack = _get_ack(pkt) if ack then - self.acks.enable = true self.sDB.control_state = true elseif ack == false then log.debug(log_header .. "enable failed!") @@ -537,8 +534,6 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) local cmd = message.message if cmd == PLC_S_CMDS.ENABLE then -- enable reactor - self.acks.enable = false - self.retry_times.enable_req = util.time() + INITIAL_WAIT _send(RPLC_TYPES.RPS_ENABLE, {}) elseif cmd == PLC_S_CMDS.SCRAM then -- SCRAM reactor @@ -635,15 +630,6 @@ function plc.new_session(id, for_reactor, in_queue, out_queue) end end - -- enable request retry - - if not self.acks.enable then - if rtimes.enable_req - util.time() <= 0 then - _send(RPLC_TYPES.RPS_ENABLE, {}) - rtimes.enable_req = util.time() + RETRY_PERIOD - end - end - -- burn rate request retry if not self.acks.burn_rate then diff --git a/supervisor/session/svqtypes.lua b/supervisor/session/svqtypes.lua index 19f9d54..1b8b131 100644 --- a/supervisor/session/svqtypes.lua +++ b/supervisor/session/svqtypes.lua @@ -14,6 +14,11 @@ local SV_Q_DATA = { CRDN_ACK = 7 } +---@class coord_ack +---@field unit integer +---@field cmd integer +---@field ack boolean + svqtypes.SV_Q_CMDS = SV_Q_CMDS svqtypes.SV_Q_DATA = SV_Q_DATA diff --git a/supervisor/session/svsessions.lua b/supervisor/session/svsessions.lua index de54493..29a3e07 100644 --- a/supervisor/session/svsessions.lua +++ b/supervisor/session/svsessions.lua @@ -17,6 +17,7 @@ local SV_Q_DATA = svqtypes.SV_Q_DATA local PLC_S_CMDS = plc.PLC_S_CMDS local PLC_S_DATA = plc.PLC_S_DATA local CRD_S_CMDS = coordinator.CRD_S_CMDS +local CRD_S_DATA = coordinator.CRD_S_DATA local svsessions = {} @@ -70,7 +71,6 @@ local function _sv_handle_outq(session) if cmd.key < SV_Q_DATA.__END_PLC_CMDS__ then -- PLC commands from coordinator - local crdn_sid = session.instance.get_id() local plc_s = svsessions.get_reactor_session(cmd.val[1]) if plc_s ~= nil then @@ -90,7 +90,11 @@ local function _sv_handle_outq(session) end else if cmd.key == SV_Q_DATA.CRDN_ACK then - ---@todo ack to be sent to coordinator + -- ack to be sent to coordinator + local crd_s = svsessions.get_coord_session() + if crd_s ~= nil then + crd_s.in_queue.push_data(CRD_S_DATA.CMD_ACK, cmd.val) + end end end end diff --git a/supervisor/startup.lua b/supervisor/startup.lua index 2b3412d..afe2745 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -13,7 +13,7 @@ local svsessions = require("supervisor.session.svsessions") local config = require("supervisor.config") local supervisor = require("supervisor.supervisor") -local SUPERVISOR_VERSION = "beta-v0.6.7" +local SUPERVISOR_VERSION = "beta-v0.6.8" local print = util.print local println = util.println