diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index da24e11..04db0a0 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -22,11 +22,10 @@ jobs: uses: lunarmodules/luacheck@v1.1.0 with: # Argument Explanations - # -a = Disable warning for unused arguments # -i 121 = Setting a read-only global variable # 512 = Loop can be executed at most once # 542 = An empty if branch # --no-max-line-length = Disable warnings for long line lengths # --exclude-files ... = Exclude lockbox library (external) and config files # --globals ... = Override all globals overridden in .vscode/settings.json AND 'os' since CraftOS 'os' differs from Lua's 'os' - args: . --no-max-line-length -a -i 121 512 542 --exclude-files ./lockbox/* ./*/config.lua --globals os _HOST bit colors fs http parallel periphemu peripheral read rs settings shell term textutils window + args: . --no-max-line-length -i 121 512 542 --exclude-files ./lockbox/* ./*/config.lua --globals os _HOST bit colors fs http parallel periphemu peripheral read rs settings shell term textutils window diff --git a/coordinator/config.lua b/coordinator/config.lua index 8e12c53..3196e80 100644 --- a/coordinator/config.lua +++ b/coordinator/config.lua @@ -28,5 +28,7 @@ config.LOG_PATH = "/log.txt" -- 0 = APPEND (adds to existing file on start) -- 1 = NEW (replaces existing file on start) config.LOG_MODE = 0 +-- true to log verbose debug messages +config.LOG_DEBUG = false return config diff --git a/coordinator/iocontrol.lua b/coordinator/iocontrol.lua index e26546d..24e839c 100644 --- a/coordinator/iocontrol.lua +++ b/coordinator/iocontrol.lua @@ -18,11 +18,15 @@ local iocontrol = {} ---@class ioctl local io = {} +-- luacheck: no unused args + -- placeholder acknowledge function for type hinting ---@param success boolean ---@diagnostic disable-next-line: unused-local local function __generic_ack(success) end +-- luacheck: unused args + -- initialize the coordinator IO controller ---@param conf facility_conf configuration ---@param comms coord_comms comms reference diff --git a/coordinator/renderer.lua b/coordinator/renderer.lua index 4aa0b53..fec6629 100644 --- a/coordinator/renderer.lua +++ b/coordinator/renderer.lua @@ -138,9 +138,9 @@ function renderer.close_ui() -- stop blinking indicators flasher.clear() - -- hide to stop animation callbacks - if engine.ui.main_display ~= nil then engine.ui.main_display.hide() end - for _, display in ipairs(engine.ui.unit_displays) do display.hide() end + -- delete element trees + if engine.ui.main_display ~= nil then engine.ui.main_display.delete() end + for _, display in ipairs(engine.ui.unit_displays) do display.delete() end -- report ui as not ready engine.ui_ready = false @@ -163,9 +163,9 @@ end function renderer.ui_ready() return engine.ui_ready end -- handle a touch event ----@param event mouse_interaction +---@param event mouse_interaction|nil function renderer.handle_mouse(event) - if engine.ui_ready then + if engine.ui_ready and event ~= nil then if event.monitor == engine.monitors.primary_name then engine.ui.main_display.handle_mouse(event) else diff --git a/coordinator/startup.lua b/coordinator/startup.lua index 97408da..eca2bcc 100644 --- a/coordinator/startup.lua +++ b/coordinator/startup.lua @@ -20,7 +20,7 @@ local sounder = require("coordinator.sounder") local apisessions = require("coordinator.session.apisessions") -local COORDINATOR_VERSION = "v0.13.8" +local COORDINATOR_VERSION = "v0.15.1" local println = util.println local println_ts = util.println_ts @@ -57,7 +57,7 @@ assert(cfv.valid(), "bad config file: missing/invalid fields") -- log init ---------------------------------------- -log.init(config.LOG_PATH, config.LOG_MODE) +log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG == true) log.info("========================================") log.info("BOOTING coordinator.startup " .. COORDINATOR_VERSION) @@ -358,7 +358,7 @@ local function main() end elseif event == "monitor_touch" then -- handle a monitor touch event - renderer.handle_mouse(core.events.touch(param1, param2, param3)) + renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3)) elseif event == "speaker_audio_empty" then -- handle speaker buffer emptied sounder.continue() diff --git a/coordinator/ui/components/boiler.lua b/coordinator/ui/components/boiler.lua index c4a433b..114a0bb 100644 --- a/coordinator/ui/components/boiler.lua +++ b/coordinator/ui/components/boiler.lua @@ -9,8 +9,8 @@ local DataIndicator = require("graphics.elements.indicators.data") local StateIndicator = require("graphics.elements.indicators.state") local VerticalBar = require("graphics.elements.indicators.vbar") -local cpair = core.graphics.cpair -local border = core.graphics.border +local cpair = core.cpair +local border = core.border -- new boiler view ---@param root graphics_element parent @@ -27,9 +27,9 @@ local function new_view(root, x, y, ps) local temp = DataIndicator{parent=boiler,x=5,y=3,lu_colors=lu_col,label="Temp:",unit="K",format="%10.2f",value=0,width=22,fg_bg=text_fg_bg} local boil_r = DataIndicator{parent=boiler,x=5,y=4,lu_colors=lu_col,label="Boil:",unit="mB/t",format="%10.0f",value=0,commas=true,width=22,fg_bg=text_fg_bg} - ps.subscribe("computed_status", status.update) - ps.subscribe("temperature", temp.update) - ps.subscribe("boil_rate", boil_r.update) + status.register(ps, "computed_status", status.update) + temp.register(ps, "temperature", temp.update) + boil_r.register(ps, "boil_rate", boil_r.update) TextBox{parent=boiler,text="H",x=2,y=5,height=1,width=1,fg_bg=text_fg_bg} TextBox{parent=boiler,text="W",x=3,y=5,height=1,width=1,fg_bg=text_fg_bg} @@ -41,10 +41,10 @@ local function new_view(root, x, y, ps) local steam = VerticalBar{parent=boiler,x=27,y=1,fg_bg=cpair(colors.white,colors.gray),height=4,width=1} local ccool = VerticalBar{parent=boiler,x=28,y=1,fg_bg=cpair(colors.lightBlue,colors.gray),height=4,width=1} - ps.subscribe("hcool_fill", hcool.update) - ps.subscribe("water_fill", water.update) - ps.subscribe("steam_fill", steam.update) - ps.subscribe("ccool_fill", ccool.update) + hcool.register(ps, "hcool_fill", hcool.update) + water.register(ps, "water_fill", water.update) + steam.register(ps, "steam_fill", steam.update) + ccool.register(ps, "ccool_fill", ccool.update) end return new_view diff --git a/coordinator/ui/components/imatrix.lua b/coordinator/ui/components/imatrix.lua index 2910fbb..a234cbc 100644 --- a/coordinator/ui/components/imatrix.lua +++ b/coordinator/ui/components/imatrix.lua @@ -13,10 +13,10 @@ local PowerIndicator = require("graphics.elements.indicators.power") local StateIndicator = require("graphics.elements.indicators.state") local VerticalBar = require("graphics.elements.indicators.vbar") -local cpair = core.graphics.cpair -local border = core.graphics.border +local cpair = core.cpair +local border = core.border -local TEXT_ALIGN = core.graphics.TEXT_ALIGN +local TEXT_ALIGN = core.TEXT_ALIGN -- new induction matrix view ---@param root graphics_element parent @@ -50,15 +50,15 @@ local function new_view(root, x, y, data, ps, id) local avg_in = PowerIndicator{parent=rect,x=7,y=9,lu_colors=lu_col,label="Avg. In: ",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg_bg} local avg_out = PowerIndicator{parent=rect,x=7,y=10,lu_colors=lu_col,label="Avg. Out:",format="%8.2f",rate=true,value=0,width=26,fg_bg=text_fg_bg} - ps.subscribe("computed_status", status.update) - ps.subscribe("energy", function (val) energy.update(util.joules_to_fe(val)) end) - ps.subscribe("max_energy", function (val) capacity.update(util.joules_to_fe(val)) end) - ps.subscribe("last_input", function (val) input.update(util.joules_to_fe(val)) end) - ps.subscribe("last_output", function (val) output.update(util.joules_to_fe(val)) end) + status.register(ps, "computed_status", status.update) + energy.register(ps, "energy", function (val) energy.update(util.joules_to_fe(val)) end) + capacity.register(ps, "max_energy", function (val) capacity.update(util.joules_to_fe(val)) end) + input.register(ps, "last_input", function (val) input.update(util.joules_to_fe(val)) end) + output.register(ps, "last_output", function (val) output.update(util.joules_to_fe(val)) end) - ps.subscribe("avg_charge", avg_chg.update) - ps.subscribe("avg_inflow", avg_in.update) - ps.subscribe("avg_outflow", avg_out.update) + avg_chg.register(ps, "avg_charge", avg_chg.update) + avg_in.register(ps, "avg_inflow", avg_in.update) + avg_out.register(ps, "avg_outflow", avg_out.update) local fill = DataIndicator{parent=rect,x=11,y=12,lu_colors=lu_col,label="Fill:",unit="%",format="%8.2f",value=0,width=18,fg_bg=text_fg_bg} @@ -68,10 +68,10 @@ local function new_view(root, x, y, data, ps, id) TextBox{parent=rect,text="Transfer Capacity",x=11,y=17,height=1,width=17,fg_bg=label_fg_bg} local trans_cap = PowerIndicator{parent=rect,x=19,y=18,lu_colors=lu_col,label="",format="%5.2f",rate=true,value=0,width=12,fg_bg=text_fg_bg} - ps.subscribe("cells", cells.update) - ps.subscribe("providers", providers.update) - ps.subscribe("energy_fill", function (val) fill.update(val * 100) end) - ps.subscribe("transfer_cap", function (val) trans_cap.update(util.joules_to_fe(val)) end) + cells.register(ps, "cells", cells.update) + providers.register(ps, "providers", providers.update) + fill.register(ps, "energy_fill", function (val) fill.update(val * 100) end) + trans_cap.register(ps, "transfer_cap", function (val) trans_cap.update(util.joules_to_fe(val)) end) local charge = VerticalBar{parent=rect,x=2,y=2,fg_bg=cpair(colors.green,colors.gray),height=17,width=4} local in_cap = VerticalBar{parent=rect,x=7,y=12,fg_bg=cpair(colors.red,colors.gray),height=7,width=1} @@ -88,9 +88,9 @@ local function new_view(root, x, y, data, ps, id) end end - ps.subscribe("energy_fill", charge.update) - ps.subscribe("last_input", function (val) in_cap.update(calc_saturation(val)) end) - ps.subscribe("last_output", function (val) out_cap.update(calc_saturation(val)) end) + charge.register(ps, "energy_fill", charge.update) + in_cap.register(ps, "last_input", function (val) in_cap.update(calc_saturation(val)) end) + out_cap.register(ps, "last_output", function (val) out_cap.update(calc_saturation(val)) end) end return new_view diff --git a/coordinator/ui/components/processctl.lua b/coordinator/ui/components/processctl.lua index 5b8d8ae..a238a8c 100644 --- a/coordinator/ui/components/processctl.lua +++ b/coordinator/ui/components/processctl.lua @@ -21,10 +21,10 @@ local HazardButton = require("graphics.elements.controls.hazard_button") local RadioButton = require("graphics.elements.controls.radio_button") local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") -local TEXT_ALIGN = core.graphics.TEXT_ALIGN +local TEXT_ALIGN = core.TEXT_ALIGN -local cpair = core.graphics.cpair -local border = core.graphics.border +local cpair = core.cpair +local border = core.border local period = core.flasher.PERIOD @@ -55,9 +55,9 @@ local function new_view(root, x, y) local ind_mat = IndicatorLight{parent=main,label="Induction Matrix",colors=cpair(colors.green,colors.gray)} local rad_mon = TriIndicatorLight{parent=main,label="Radiation Monitor",c1=colors.gray,c2=colors.yellow,c3=colors.green} - facility.ps.subscribe("all_sys_ok", all_ok.update) - facility.induction_ps_tbl[1].subscribe("computed_status", function (status) ind_mat.update(status > 1) end) - facility.ps.subscribe("rad_computed_status", rad_mon.update) + all_ok.register(facility.ps, "all_sys_ok", all_ok.update) + ind_mat.register(facility.induction_ps_tbl[1], "computed_status", function (status) ind_mat.update(status > 1) end) + rad_mon.register(facility.ps, "rad_computed_status", rad_mon.update) main.line_break() @@ -66,10 +66,10 @@ local function new_view(root, x, y) local auto_ramp = IndicatorLight{parent=main,label="Process Ramping",colors=cpair(colors.white,colors.gray),flash=true,period=period.BLINK_250_MS} local auto_sat = IndicatorLight{parent=main,label="Min/Max Burn Rate",colors=cpair(colors.yellow,colors.gray)} - facility.ps.subscribe("auto_ready", auto_ready.update) - facility.ps.subscribe("auto_active", auto_act.update) - facility.ps.subscribe("auto_ramping", auto_ramp.update) - facility.ps.subscribe("auto_saturated", auto_sat.update) + auto_ready.register(facility.ps, "auto_ready", auto_ready.update) + auto_act.register(facility.ps, "auto_active", auto_act.update) + auto_ramp.register(facility.ps, "auto_ramping", auto_ramp.update) + auto_sat.register(facility.ps, "auto_saturated", auto_sat.update) main.line_break() @@ -80,20 +80,20 @@ local function new_view(root, x, y) local fac_rad_h = IndicatorLight{parent=main,label="Facility Radiation High",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} local gen_fault = IndicatorLight{parent=main,label="Gen. Control Fault",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} - facility.ps.subscribe("auto_scram", auto_scram.update) - facility.ps.subscribe("as_matrix_dc", matrix_dc.update) - facility.ps.subscribe("as_matrix_fill", matrix_fill.update) - facility.ps.subscribe("as_crit_alarm", unit_crit.update) - facility.ps.subscribe("as_radiation", fac_rad_h.update) - facility.ps.subscribe("as_gen_fault", gen_fault.update) + auto_scram.register(facility.ps, "auto_scram", auto_scram.update) + matrix_dc.register(facility.ps, "as_matrix_dc", matrix_dc.update) + matrix_fill.register(facility.ps, "as_matrix_fill", matrix_fill.update) + unit_crit.register(facility.ps, "as_crit_alarm", unit_crit.update) + fac_rad_h.register(facility.ps, "as_radiation", fac_rad_h.update) + gen_fault.register(facility.ps, "as_gen_fault", gen_fault.update) TextBox{parent=main,y=23,text="Radiation",height=1,width=13,fg_bg=style.label} local radiation = RadIndicator{parent=main,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} - facility.ps.subscribe("radiation", radiation.update) + radiation.register(facility.ps, "radiation", radiation.update) TextBox{parent=main,x=15,y=23,text="Linked RTUs",height=1,width=11,fg_bg=style.label} local rtu_count = DataIndicator{parent=main,x=15,y=24,label="",format="%11d",value=0,lu_colors=lu_cpair,width=11,fg_bg=bw_fg_bg} - facility.ps.subscribe("rtu_count", rtu_count.update) + rtu_count.register(facility.ps, "rtu_count", rtu_count.update) --------------------- -- process control -- @@ -115,8 +115,8 @@ local function new_view(root, x, y) TextBox{parent=burn_target,x=18,y=2,text="mB/t"} local burn_sum = DataIndicator{parent=targets,x=9,y=4,label="",format="%18.1f",value=0,unit="mB/t",commas=true,lu_colors=cpair(colors.black,colors.black),width=23,fg_bg=cpair(colors.black,colors.brown)} - facility.ps.subscribe("process_burn_target", b_target.set_value) - facility.ps.subscribe("burn_sum", burn_sum.update) + b_target.register(facility.ps, "process_burn_target", b_target.set_value) + burn_sum.register(facility.ps, "burn_sum", burn_sum.update) local chg_tag = Div{parent=targets,x=1,y=6,width=8,height=4,fg_bg=cpair(colors.black,colors.purple)} TextBox{parent=chg_tag,x=2,y=2,text="Charge Target",width=7,height=2} @@ -126,8 +126,8 @@ local function new_view(root, x, y) TextBox{parent=chg_target,x=18,y=2,text="MFE"} local cur_charge = DataIndicator{parent=targets,x=9,y=9,label="",format="%19d",value=0,unit="MFE",commas=true,lu_colors=cpair(colors.black,colors.black),width=23,fg_bg=cpair(colors.black,colors.brown)} - facility.ps.subscribe("process_charge_target", c_target.set_value) - facility.induction_ps_tbl[1].subscribe("energy", function (j) cur_charge.update(util.joules_to_fe(j) / 1000000) end) + c_target.register(facility.ps, "process_charge_target", c_target.set_value) + cur_charge.register(facility.induction_ps_tbl[1], "energy", function (j) cur_charge.update(util.joules_to_fe(j) / 1000000) end) local gen_tag = Div{parent=targets,x=1,y=11,width=8,height=4,fg_bg=cpair(colors.black,colors.purple)} TextBox{parent=gen_tag,x=2,y=2,text="Gen. Target",width=7,height=2} @@ -137,8 +137,8 @@ local function new_view(root, x, y) TextBox{parent=gen_target,x=18,y=2,text="kFE/t"} local cur_gen = DataIndicator{parent=targets,x=9,y=14,label="",format="%17d",value=0,unit="kFE/t",commas=true,lu_colors=cpair(colors.black,colors.black),width=23,fg_bg=cpair(colors.black,colors.brown)} - facility.ps.subscribe("process_gen_target", g_target.set_value) - facility.induction_ps_tbl[1].subscribe("last_input", function (j) cur_gen.update(util.round(util.joules_to_fe(j) / 1000)) end) + g_target.register(facility.ps, "process_gen_target", g_target.set_value) + cur_gen.register(facility.induction_ps_tbl[1], "last_input", function (j) cur_gen.update(util.round(util.joules_to_fe(j) / 1000)) end) ----------------- -- unit limits -- @@ -160,12 +160,12 @@ local function new_view(root, x, y) rate_limits[i] = SpinboxNumeric{parent=lim_ctl,x=2,y=1,whole_num_precision=4,fractional_precision=1,min=0.1,arrow_fg_bg=cpair(colors.gray,colors.white),fg_bg=bw_fg_bg} TextBox{parent=lim_ctl,x=9,y=2,text="mB/t",width=4,height=1} - unit.unit_ps.subscribe("max_burn", rate_limits[i].set_max) - unit.unit_ps.subscribe("burn_limit", rate_limits[i].set_value) + rate_limits[i].register(unit.unit_ps, "max_burn", rate_limits[i].set_max) + rate_limits[i].register(unit.unit_ps, "burn_limit", rate_limits[i].set_value) local cur_burn = DataIndicator{parent=limit_div,x=9,y=_y+3,label="",format="%7.1f",value=0,unit="mB/t",commas=false,lu_colors=cpair(colors.black,colors.black),width=14,fg_bg=cpair(colors.black,colors.brown)} - unit.unit_ps.subscribe("act_burn_rate", cur_burn.update) + cur_burn.register(unit.unit_ps, "act_burn_rate", cur_burn.update) end ------------------- @@ -186,8 +186,8 @@ local function new_view(root, x, y) local ready = IndicatorLight{parent=lights,x=2,y=2,label="Ready",colors=cpair(colors.green,colors.gray)} local degraded = IndicatorLight{parent=lights,x=2,y=3,label="Degraded",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} - unit.unit_ps.subscribe("U_AutoReady", ready.update) - unit.unit_ps.subscribe("U_AutoDegraded", degraded.update) + ready.register(unit.unit_ps, "U_AutoReady", ready.update) + degraded.register(unit.unit_ps, "U_AutoDegraded", degraded.update) end ------------------------- @@ -197,14 +197,14 @@ local function new_view(root, x, y) local ctl_opts = { "Monitored Max Burn", "Combined Burn Rate", "Charge Level", "Generation Rate" } local mode = RadioButton{parent=proc,x=34,y=1,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.purple,colors.black),radio_bg=colors.gray} - facility.ps.subscribe("process_mode", mode.set_value) + mode.register(facility.ps, "process_mode", mode.set_value) local u_stat = Rectangle{parent=proc,border=border(1,colors.gray,true),thin=true,width=31,height=4,x=1,y=16,fg_bg=bw_fg_bg} local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=31,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=bw_fg_bg} local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data...",width=31,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} - facility.ps.subscribe("status_line_1", stat_line_1.set_value) - facility.ps.subscribe("status_line_2", stat_line_2.set_value) + stat_line_1.register(facility.ps, "status_line_1", stat_line_1.set_value) + stat_line_2.register(facility.ps, "status_line_2", stat_line_2.set_value) local auto_controls = Div{parent=proc,x=1,y=20,width=31,height=5,fg_bg=cpair(colors.gray,colors.white)} @@ -233,11 +233,14 @@ local function new_view(root, x, y) tcd.dispatch(0.2, function () save.on_response(ack) end) end - facility.ps.subscribe("auto_ready", function (ready) + start.register(facility.ps, "auto_ready", function (ready) if ready and (not facility.auto_active) then start.enable() else start.disable() end end) - facility.ps.subscribe("auto_active", function (active) + -- REGISTER_NOTE: for optimization/brevity, due to not deleting anything but the whole element tree when it comes + -- to the process control display and coordinator GUI as a whole, child elements will not directly be registered here + -- (preventing garbage collection until the parent 'proc' is deleted) + proc.register(facility.ps, "auto_active", function (active) if active then b_target.disable() c_target.disable() @@ -246,9 +249,7 @@ local function new_view(root, x, y) mode.disable() start.disable() - for i = 1, #rate_limits do - rate_limits[i].disable() - end + for i = 1, #rate_limits do rate_limits[i].disable() end else b_target.enable() c_target.enable() @@ -257,9 +258,7 @@ local function new_view(root, x, y) mode.enable() if facility.auto_ready then start.enable() end - for i = 1, #rate_limits do - rate_limits[i].enable() - end + for i = 1, #rate_limits do rate_limits[i].enable() end end end) end diff --git a/coordinator/ui/components/reactor.lua b/coordinator/ui/components/reactor.lua index db75fb1..b4a2c1b 100644 --- a/coordinator/ui/components/reactor.lua +++ b/coordinator/ui/components/reactor.lua @@ -11,8 +11,8 @@ local DataIndicator = require("graphics.elements.indicators.data") local HorizontalBar = require("graphics.elements.indicators.hbar") local StateIndicator = require("graphics.elements.indicators.state") -local cpair = core.graphics.cpair -local border = core.graphics.border +local cpair = core.cpair +local border = core.border -- create new reactor view ---@param root graphics_element parent @@ -30,10 +30,10 @@ local function new_view(root, x, y, ps) local burn_r = DataIndicator{parent=reactor,x=2,y=4,lu_colors=lu_col,label="Burn Rate:",unit="mB/t",format="%10.2f",value=0,width=26,fg_bg=text_fg_bg} local heating_r = DataIndicator{parent=reactor,x=2,y=5,lu_colors=lu_col,label="Heating:",unit="mB/t",format="%12.0f",value=0,commas=true,width=26,fg_bg=text_fg_bg} - ps.subscribe("computed_status", status.update) - ps.subscribe("temp", core_temp.update) - ps.subscribe("act_burn_rate", burn_r.update) - ps.subscribe("heating_rate", heating_r.update) + status.register(ps, "computed_status", status.update) + core_temp.register(ps, "temp", core_temp.update) + burn_r.register(ps, "act_burn_rate", burn_r.update) + heating_r.register(ps, "heating_rate", heating_r.update) local reactor_fills = Rectangle{parent=root,border=border(1, colors.gray, true),width=24,height=7,x=(x + 29),y=y} @@ -47,7 +47,7 @@ local function new_view(root, x, y, ps) local hcool = HorizontalBar{parent=reactor_fills,x=8,y=4,show_percent=true,bar_fg_bg=cpair(colors.white,colors.gray),height=1,width=14} local waste = HorizontalBar{parent=reactor_fills,x=8,y=5,show_percent=true,bar_fg_bg=cpair(colors.brown,colors.gray),height=1,width=14} - ps.subscribe("ccool_type", function (type) + ccool.register(ps, "ccool_type", function (type) if type == types.FLUID.SODIUM then ccool.recolor(cpair(colors.lightBlue, colors.gray)) else @@ -55,7 +55,7 @@ local function new_view(root, x, y, ps) end end) - ps.subscribe("hcool_type", function (type) + hcool.register(ps, "hcool_type", function (type) if type == types.FLUID.SUPERHEATED_SODIUM then hcool.recolor(cpair(colors.orange, colors.gray)) else @@ -63,10 +63,10 @@ local function new_view(root, x, y, ps) end end) - ps.subscribe("fuel_fill", fuel.update) - ps.subscribe("ccool_fill", ccool.update) - ps.subscribe("hcool_fill", hcool.update) - ps.subscribe("waste_fill", waste.update) + fuel.register(ps, "fuel_fill", fuel.update) + ccool.register(ps, "ccool_fill", ccool.update) + hcool.register(ps, "hcool_fill", hcool.update) + waste.register(ps, "waste_fill", waste.update) end return new_view diff --git a/coordinator/ui/components/turbine.lua b/coordinator/ui/components/turbine.lua index e4d6967..0e4cb21 100644 --- a/coordinator/ui/components/turbine.lua +++ b/coordinator/ui/components/turbine.lua @@ -12,8 +12,8 @@ local PowerIndicator = require("graphics.elements.indicators.power") local StateIndicator = require("graphics.elements.indicators.state") local VerticalBar = require("graphics.elements.indicators.vbar") -local cpair = core.graphics.cpair -local border = core.graphics.border +local cpair = core.cpair +local border = core.border -- new turbine view ---@param root graphics_element parent @@ -30,9 +30,9 @@ local function new_view(root, x, y, ps) local prod_rate = PowerIndicator{parent=turbine,x=5,y=3,lu_colors=lu_col,label="",format="%10.2f",value=0,rate=true,width=16,fg_bg=text_fg_bg} local flow_rate = DataIndicator{parent=turbine,x=5,y=4,lu_colors=lu_col,label="",unit="mB/t",format="%10.0f",value=0,commas=true,width=16,fg_bg=text_fg_bg} - ps.subscribe("computed_status", status.update) - ps.subscribe("prod_rate", function (val) prod_rate.update(util.joules_to_fe(val)) end) - ps.subscribe("flow_rate", flow_rate.update) + status.register(ps, "computed_status", status.update) + prod_rate.register(ps, "prod_rate", function (val) prod_rate.update(util.joules_to_fe(val)) end) + flow_rate.register(ps, "flow_rate", flow_rate.update) local steam = VerticalBar{parent=turbine,x=2,y=1,fg_bg=cpair(colors.white,colors.gray),height=4,width=1} local energy = VerticalBar{parent=turbine,x=3,y=1,fg_bg=cpair(colors.green,colors.gray),height=4,width=1} @@ -40,8 +40,8 @@ local function new_view(root, x, y, ps) TextBox{parent=turbine,text="S",x=2,y=5,height=1,width=1,fg_bg=text_fg_bg} TextBox{parent=turbine,text="E",x=3,y=5,height=1,width=1,fg_bg=text_fg_bg} - ps.subscribe("steam_fill", steam.update) - ps.subscribe("energy_fill", energy.update) + steam.register(ps, "steam_fill", steam.update) + energy.register(ps, "energy_fill", energy.update) end return new_view diff --git a/coordinator/ui/components/unit_detail.lua b/coordinator/ui/components/unit_detail.lua index a29dd4b..30e2044 100644 --- a/coordinator/ui/components/unit_detail.lua +++ b/coordinator/ui/components/unit_detail.lua @@ -26,10 +26,10 @@ local PushButton = require("graphics.elements.controls.push_button") local RadioButton = require("graphics.elements.controls.radio_button") local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") -local TEXT_ALIGN = core.graphics.TEXT_ALIGN +local TEXT_ALIGN = core.TEXT_ALIGN -local cpair = core.graphics.cpair -local border = core.graphics.border +local cpair = core.cpair +local border = core.border local period = core.flasher.PERIOD @@ -79,16 +79,16 @@ local function init(parent, id) ----------------------------- local core_map = CoreMap{parent=main,x=2,y=3,reactor_l=18,reactor_w=18} - u_ps.subscribe("temp", core_map.update) - u_ps.subscribe("size", function (s) core_map.resize(s[1], s[2]) end) + core_map.register(u_ps, "temp", core_map.update) + core_map.register(u_ps, "size", function (s) core_map.resize(s[1], s[2]) end) TextBox{parent=main,x=12,y=22,text="Heating Rate",height=1,width=12,fg_bg=style.label} local heating_r = DataIndicator{parent=main,x=12,label="",format="%14.0f",value=0,unit="mB/t",commas=true,lu_colors=lu_cpair,width=19,fg_bg=bw_fg_bg} - u_ps.subscribe("heating_rate", heating_r.update) + heating_r.register(u_ps, "heating_rate", heating_r.update) TextBox{parent=main,x=12,y=25,text="Commanded Burn Rate",height=1,width=19,fg_bg=style.label} local burn_r = DataIndicator{parent=main,x=12,label="",format="%14.2f",value=0,unit="mB/t",lu_colors=lu_cpair,width=19,fg_bg=bw_fg_bg} - u_ps.subscribe("burn_rate", burn_r.update) + burn_r.register(u_ps, "burn_rate", burn_r.update) TextBox{parent=main,text="F",x=2,y=22,width=1,height=1,fg_bg=style.label} TextBox{parent=main,text="C",x=4,y=22,width=1,height=1,fg_bg=style.label} @@ -102,12 +102,12 @@ local function init(parent, id) local hcool = VerticalBar{parent=main,x=8,y=23,fg_bg=cpair(colors.white,colors.gray),height=4,width=1} local waste = VerticalBar{parent=main,x=10,y=23,fg_bg=cpair(colors.brown,colors.gray),height=4,width=1} - u_ps.subscribe("fuel_fill", fuel.update) - u_ps.subscribe("ccool_fill", ccool.update) - u_ps.subscribe("hcool_fill", hcool.update) - u_ps.subscribe("waste_fill", waste.update) + fuel.register(u_ps, "fuel_fill", fuel.update) + ccool.register(u_ps, "ccool_fill", ccool.update) + hcool.register(u_ps, "hcool_fill", hcool.update) + waste.register(u_ps, "waste_fill", waste.update) - u_ps.subscribe("ccool_type", function (type) + ccool.register(u_ps, "ccool_type", function (type) if type == "mekanism:sodium" then ccool.recolor(cpair(colors.lightBlue, colors.gray)) else @@ -115,7 +115,7 @@ local function init(parent, id) end end) - u_ps.subscribe("hcool_type", function (type) + hcool.register(u_ps, "hcool_type", function (type) if type == "mekanism:superheated_sodium" then hcool.recolor(cpair(colors.orange, colors.gray)) else @@ -125,19 +125,19 @@ local function init(parent, id) TextBox{parent=main,x=32,y=22,text="Core Temp",height=1,width=9,fg_bg=style.label} local core_temp = DataIndicator{parent=main,x=32,label="",format="%11.2f",value=0,unit="K",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} - u_ps.subscribe("temp", core_temp.update) + core_temp.register(u_ps, "temp", core_temp.update) TextBox{parent=main,x=32,y=25,text="Burn Rate",height=1,width=9,fg_bg=style.label} local act_burn_r = DataIndicator{parent=main,x=32,label="",format="%8.2f",value=0,unit="mB/t",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} - u_ps.subscribe("act_burn_rate", act_burn_r.update) + act_burn_r.register(u_ps, "act_burn_rate", act_burn_r.update) TextBox{parent=main,x=32,y=28,text="Damage",height=1,width=6,fg_bg=style.label} local damage_p = DataIndicator{parent=main,x=32,label="",format="%11.0f",value=0,unit="%",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} - u_ps.subscribe("damage", damage_p.update) + damage_p.register(u_ps, "damage", damage_p.update) TextBox{parent=main,x=32,y=31,text="Radiation",height=1,width=21,fg_bg=style.label} local radiation = RadIndicator{parent=main,x=32,label="",format="%9.3f",lu_colors=lu_cpair,width=13,fg_bg=bw_fg_bg} - u_ps.subscribe("radiation", radiation.update) + radiation.register(u_ps, "radiation", radiation.update) ------------------- -- system status -- @@ -147,8 +147,8 @@ local function init(parent, id) local stat_line_1 = TextBox{parent=u_stat,x=1,y=1,text="UNKNOWN",width=33,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=bw_fg_bg} local stat_line_2 = TextBox{parent=u_stat,x=1,y=2,text="awaiting data...",width=33,height=1,alignment=TEXT_ALIGN.CENTER,fg_bg=cpair(colors.gray, colors.white)} - u_ps.subscribe("U_StatusLine1", stat_line_1.set_value) - u_ps.subscribe("U_StatusLine2", stat_line_2.set_value) + stat_line_1.register(u_ps, "U_StatusLine1", stat_line_1.set_value) + stat_line_2.register(u_ps, "U_StatusLine2", stat_line_2.set_value) ----------------- -- annunciator -- @@ -163,9 +163,9 @@ local function init(parent, id) local plc_hbeat = IndicatorLight{parent=annunciator,label="PLC Heartbeat",colors=cpair(colors.white,colors.gray)} local rad_mon = TriIndicatorLight{parent=annunciator,label="Radiation Monitor",c1=colors.gray,c2=colors.yellow,c3=colors.green} - u_ps.subscribe("PLCOnline", plc_online.update) - u_ps.subscribe("PLCHeartbeat", plc_hbeat.update) - u_ps.subscribe("RadiationMonitor", rad_mon.update) + plc_online.register(u_ps, "PLCOnline", plc_online.update) + plc_hbeat.register(u_ps, "PLCHeartbeat", plc_hbeat.update) + rad_mon.register(u_ps, "RadiationMonitor", rad_mon.update) annunciator.line_break() @@ -173,8 +173,8 @@ local function init(parent, id) local r_active = IndicatorLight{parent=annunciator,label="Active",colors=cpair(colors.green,colors.gray)} local r_auto = IndicatorLight{parent=annunciator,label="Automatic Control",colors=cpair(colors.white,colors.gray)} - u_ps.subscribe("status", r_active.update) - u_ps.subscribe("AutoControl", r_auto.update) + r_active.register(u_ps, "status", r_active.update) + r_auto.register(u_ps, "AutoControl", r_auto.update) -- main unit transient/warning annunciator panel local r_scram = IndicatorLight{parent=annunciator,label="Reactor SCRAM",colors=cpair(colors.red,colors.gray)} @@ -190,18 +190,18 @@ local function init(parent, id) local r_wloc = IndicatorLight{parent=annunciator,label="Waste Line Occlusion",colors=cpair(colors.yellow,colors.gray)} local r_hsrt = IndicatorLight{parent=annunciator,label="Startup Rate High",colors=cpair(colors.yellow,colors.gray)} - u_ps.subscribe("ReactorSCRAM", r_scram.update) - u_ps.subscribe("ManualReactorSCRAM", r_mscrm.update) - u_ps.subscribe("AutoReactorSCRAM", r_ascrm.update) - u_ps.subscribe("RadiationWarning", rad_wrn.update) - u_ps.subscribe("RCPTrip", r_rtrip.update) - u_ps.subscribe("RCSFlowLow", r_cflow.update) - u_ps.subscribe("CoolantLevelLow", r_clow.update) - u_ps.subscribe("ReactorTempHigh", r_temp.update) - u_ps.subscribe("ReactorHighDeltaT", r_rhdt.update) - u_ps.subscribe("FuelInputRateLow", r_firl.update) - u_ps.subscribe("WasteLineOcclusion", r_wloc.update) - u_ps.subscribe("HighStartupRate", r_hsrt.update) + r_scram.register(u_ps, "ReactorSCRAM", r_scram.update) + r_mscrm.register(u_ps, "ManualReactorSCRAM", r_mscrm.update) + r_ascrm.register(u_ps, "AutoReactorSCRAM", r_ascrm.update) + rad_wrn.register(u_ps, "RadiationWarning", rad_wrn.update) + r_rtrip.register(u_ps, "RCPTrip", r_rtrip.update) + r_cflow.register(u_ps, "RCSFlowLow", r_cflow.update) + r_clow.register(u_ps, "CoolantLevelLow", r_clow.update) + r_temp.register(u_ps, "ReactorTempHigh", r_temp.update) + r_rhdt.register(u_ps, "ReactorHighDeltaT", r_rhdt.update) + r_firl.register(u_ps, "FuelInputRateLow", r_firl.update) + r_wloc.register(u_ps, "WasteLineOcclusion", r_wloc.update) + r_hsrt.register(u_ps, "HighStartupRate", r_hsrt.update) -- RPS annunciator panel @@ -220,16 +220,16 @@ local function init(parent, id) local rps_tmo = IndicatorLight{parent=rps_annunc,label="Connection Timeout",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_500_MS} local rps_sfl = IndicatorLight{parent=rps_annunc,label="System Failure",colors=cpair(colors.orange,colors.gray),flash=true,period=period.BLINK_500_MS} - u_ps.subscribe("rps_tripped", rps_trp.update) - u_ps.subscribe("high_dmg", rps_dmg.update) - u_ps.subscribe("ex_hcool", rps_exh.update) - u_ps.subscribe("ex_waste", rps_exw.update) - u_ps.subscribe("high_temp", rps_tmp.update) - u_ps.subscribe("no_fuel", rps_nof.update) - u_ps.subscribe("low_cool", rps_loc.update) - u_ps.subscribe("fault", rps_flt.update) - u_ps.subscribe("timeout", rps_tmo.update) - u_ps.subscribe("sys_fail", rps_sfl.update) + rps_trp.register(u_ps, "rps_tripped", rps_trp.update) + rps_dmg.register(u_ps, "high_dmg", rps_dmg.update) + rps_exh.register(u_ps, "ex_hcool", rps_exh.update) + rps_exw.register(u_ps, "ex_waste", rps_exw.update) + rps_tmp.register(u_ps, "high_temp", rps_tmp.update) + rps_nof.register(u_ps, "no_fuel", rps_nof.update) + rps_loc.register(u_ps, "low_cool", rps_loc.update) + rps_flt.register(u_ps, "fault", rps_flt.update) + rps_tmo.register(u_ps, "timeout", rps_tmo.update) + rps_sfl.register(u_ps, "sys_fail", rps_sfl.update) -- cooling annunciator panel @@ -245,12 +245,12 @@ local function init(parent, id) local c_sfm = IndicatorLight{parent=rcs_annunc,label="Steam Feed Mismatch",colors=cpair(colors.yellow,colors.gray)} local c_mwrf = IndicatorLight{parent=rcs_annunc,label="Max Water Return Feed",colors=cpair(colors.yellow,colors.gray)} - u_ps.subscribe("RCSFault", c_flt.update) - u_ps.subscribe("EmergencyCoolant", c_emg.update) - u_ps.subscribe("CoolantFeedMismatch", c_cfm.update) - u_ps.subscribe("BoilRateMismatch", c_brm.update) - u_ps.subscribe("SteamFeedMismatch", c_sfm.update) - u_ps.subscribe("MaxWaterReturnFeed", c_mwrf.update) + c_flt.register(u_ps, "RCSFault", c_flt.update) + c_emg.register(u_ps, "EmergencyCoolant", c_emg.update) + c_cfm.register(u_ps, "CoolantFeedMismatch", c_cfm.update) + c_brm.register(u_ps, "BoilRateMismatch", c_brm.update) + c_sfm.register(u_ps, "SteamFeedMismatch", c_sfm.update) + c_mwrf.register(u_ps, "MaxWaterReturnFeed", c_mwrf.update) local available_space = 16 - (unit.num_boilers * 2 + unit.num_turbines * 4) @@ -267,11 +267,11 @@ local function init(parent, id) if unit.num_boilers > 0 then TextBox{parent=rcs_tags,x=1,text="B1",width=2,height=1,fg_bg=bw_fg_bg} local b1_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=cpair(colors.red,colors.gray)} - b_ps[1].subscribe("WasterLevelLow", b1_wll.update) + b1_wll.register(b_ps[1], "WasterLevelLow", b1_wll.update) TextBox{parent=rcs_tags,text="B1",width=2,height=1,fg_bg=bw_fg_bg} local b1_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} - b_ps[1].subscribe("HeatingRateLow", b1_hr.update) + b1_hr.register(b_ps[1], "HeatingRateLow", b1_hr.update) end if unit.num_boilers > 1 then -- note, can't (shouldn't for sure...) have 0 turbines @@ -283,11 +283,11 @@ local function init(parent, id) TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=bw_fg_bg} local b2_wll = IndicatorLight{parent=rcs_annunc,label="Water Level Low",colors=cpair(colors.red,colors.gray)} - b_ps[2].subscribe("WasterLevelLow", b2_wll.update) + b2_wll.register(b_ps[2], "WasterLevelLow", b2_wll.update) TextBox{parent=rcs_tags,text="B2",width=2,height=1,fg_bg=bw_fg_bg} local b2_hr = IndicatorLight{parent=rcs_annunc,label="Heating Rate Low",colors=cpair(colors.yellow,colors.gray)} - b_ps[2].subscribe("HeatingRateLow", b2_hr.update) + b2_hr.register(b_ps[2], "HeatingRateLow", b2_hr.update) end -- turbine annunciator panels @@ -296,19 +296,19 @@ local function init(parent, id) TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg} local t1_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} - t_ps[1].subscribe("SteamDumpOpen", t1_sdo.update) + t1_sdo.register(t_ps[1], "SteamDumpOpen", t1_sdo.update) TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg} local t1_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} - t_ps[1].subscribe("TurbineOverSpeed", t1_tos.update) + t1_tos.register(t_ps[1], "TurbineOverSpeed", t1_tos.update) TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg} local t1_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_250_MS} - t_ps[1].subscribe("GeneratorTrip", t1_gtrp.update) + t1_gtrp.register(t_ps[1], "GeneratorTrip", t1_gtrp.update) TextBox{parent=rcs_tags,text="T1",width=2,height=1,fg_bg=bw_fg_bg} local t1_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} - t_ps[1].subscribe("TurbineTrip", t1_trp.update) + t1_trp.register(t_ps[1], "TurbineTrip", t1_trp.update) if unit.num_turbines > 1 then if (available_space > 2 and unit.num_turbines == 2) or available_space > 3 then @@ -317,19 +317,19 @@ local function init(parent, id) TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg} local t2_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} - t_ps[2].subscribe("SteamDumpOpen", t2_sdo.update) + t2_sdo.register(t_ps[2], "SteamDumpOpen", t2_sdo.update) TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg} local t2_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} - t_ps[2].subscribe("TurbineOverSpeed", t2_tos.update) + t2_tos.register(t_ps[2], "TurbineOverSpeed", t2_tos.update) TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg} local t2_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_250_MS} - t_ps[2].subscribe("GeneratorTrip", t2_gtrp.update) + t2_gtrp.register(t_ps[2], "GeneratorTrip", t2_gtrp.update) TextBox{parent=rcs_tags,text="T2",width=2,height=1,fg_bg=bw_fg_bg} local t2_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} - t_ps[2].subscribe("TurbineTrip", t2_trp.update) + t2_trp.register(t_ps[2], "TurbineTrip", t2_trp.update) end if unit.num_turbines > 2 then @@ -337,19 +337,19 @@ local function init(parent, id) TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg} local t3_sdo = TriIndicatorLight{parent=rcs_annunc,label="Steam Relief Valve Open",c1=colors.gray,c2=colors.yellow,c3=colors.red} - t_ps[3].subscribe("SteamDumpOpen", t3_sdo.update) + t3_sdo.register(t_ps[3], "SteamDumpOpen", t3_sdo.update) TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg} local t3_tos = IndicatorLight{parent=rcs_annunc,label="Turbine Over Speed",colors=cpair(colors.red,colors.gray)} - t_ps[3].subscribe("TurbineOverSpeed", t3_tos.update) + t3_tos.register(t_ps[3], "TurbineOverSpeed", t3_tos.update) TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg} local t3_gtrp = IndicatorLight{parent=rcs_annunc,label="Generator Trip",colors=cpair(colors.yellow,colors.gray),flash=true,period=period.BLINK_250_MS} - t_ps[3].subscribe("GeneratorTrip", t3_gtrp.update) + t3_gtrp.register(t_ps[3], "GeneratorTrip", t3_gtrp.update) TextBox{parent=rcs_tags,text="T3",width=2,height=1,fg_bg=bw_fg_bg} local t3_trp = IndicatorLight{parent=rcs_annunc,label="Turbine Trip",colors=cpair(colors.red,colors.gray),flash=true,period=period.BLINK_250_MS} - t_ps[3].subscribe("TurbineTrip", t3_trp.update) + t3_trp.register(t_ps[3], "TurbineTrip", t3_trp.update) end ---------------------- @@ -365,8 +365,8 @@ local function init(parent, id) local set_burn = function () unit.set_burn(burn_rate.get_value()) end local set_burn_btn = PushButton{parent=burn_control,x=14,y=2,text="SET",min_width=5,fg_bg=cpair(colors.black,colors.yellow),active_fg_bg=cpair(colors.white,colors.gray),dis_fg_bg=dis_colors,callback=set_burn} - u_ps.subscribe("burn_rate", burn_rate.set_value) - u_ps.subscribe("max_burn", burn_rate.set_max) + burn_rate.register(u_ps, "burn_rate", burn_rate.set_value) + burn_rate.register(u_ps, "max_burn", burn_rate.set_max) local start = HazardButton{parent=main,x=2,y=28,text="START",accent=colors.lightBlue,dis_colors=dis_colors,callback=unit.start,fg_bg=hzd_fg_bg} local ack_a = HazardButton{parent=main,x=12,y=32,text="ACK \x13",accent=colors.orange,dis_colors=dis_colors,callback=unit.ack_alarms,fg_bg=hzd_fg_bg} @@ -387,9 +387,12 @@ local function init(parent, id) end end - u_ps.subscribe("status", start_button_en_check) - u_ps.subscribe("rps_tripped", start_button_en_check) - u_ps.subscribe("rps_tripped", function (active) if active then reset.enable() else reset.disable() end end) + start.register(u_ps, "status", start_button_en_check) + start.register(u_ps, "rps_tripped", start_button_en_check) + start.register(u_ps, "auto_group_id", start_button_en_check) + start.register(u_ps, "AutoControl", start_button_en_check) + + reset.register(u_ps, "rps_tripped", function (active) if active then reset.enable() else reset.disable() end end) TextBox{parent=main,text="WASTE PROCESSING",fg_bg=cpair(colors.black,colors.brown),alignment=TEXT_ALIGN.CENTER,width=33,height=1,x=46,y=48} local waste_proc = Rectangle{parent=main,border=border(1,colors.brown,true),thin=true,width=33,height=3,x=46,y=49} @@ -397,7 +400,7 @@ local function init(parent, id) local waste_mode = MultiButton{parent=waste_div,x=1,y=1,options=waste_opts,callback=unit.set_waste,min_width=6} - u_ps.subscribe("U_WasteMode", waste_mode.set_value) + waste_mode.register(u_ps, "U_WasteMode", waste_mode.set_value) ---------------------- -- alarm management -- @@ -420,20 +423,20 @@ local function init(parent, id) local a_clt = AlarmLight{parent=alarm_panel,x=6,label="RCS Transient",c1=colors.gray,c2=colors.yellow,c3=colors.green,flash=true,period=period.BLINK_500_MS} local a_tbt = AlarmLight{parent=alarm_panel,x=6,label="Turbine Trip",c1=colors.gray,c2=colors.red,c3=colors.green,flash=true,period=period.BLINK_250_MS} - u_ps.subscribe("Alarm_1", a_brc.update) - u_ps.subscribe("Alarm_2", a_rad.update) - u_ps.subscribe("Alarm_4", a_dmg.update) + a_brc.register(u_ps, "Alarm_1", a_brc.update) + a_rad.register(u_ps, "Alarm_2", a_rad.update) + a_dmg.register(u_ps, "Alarm_4", a_dmg.update) - u_ps.subscribe("Alarm_3", a_rcl.update) - u_ps.subscribe("Alarm_5", a_rcd.update) - u_ps.subscribe("Alarm_6", a_rot.update) - u_ps.subscribe("Alarm_7", a_rht.update) - u_ps.subscribe("Alarm_8", a_rwl.update) - u_ps.subscribe("Alarm_9", a_rwh.update) + a_rcl.register(u_ps, "Alarm_3", a_rcl.update) + a_rcd.register(u_ps, "Alarm_5", a_rcd.update) + a_rot.register(u_ps, "Alarm_6", a_rot.update) + a_rht.register(u_ps, "Alarm_7", a_rht.update) + a_rwl.register(u_ps, "Alarm_8", a_rwl.update) + a_rwh.register(u_ps, "Alarm_9", a_rwh.update) - u_ps.subscribe("Alarm_10", a_rps.update) - u_ps.subscribe("Alarm_11", a_clt.update) - u_ps.subscribe("Alarm_12", a_tbt.update) + a_rps.register(u_ps, "Alarm_10", a_rps.update) + a_clt.register(u_ps, "Alarm_11", a_clt.update) + a_tbt.register(u_ps, "Alarm_12", a_tbt.update) -- ack's and resets @@ -487,7 +490,7 @@ local function init(parent, id) local group = RadioButton{parent=auto_div,options=ctl_opts,callback=function()end,radio_colors=cpair(colors.blue,colors.white),radio_bg=colors.gray} - u_ps.subscribe("auto_group_id", function (gid) group.set_value(gid + 1) end) + group.register(u_ps, "auto_group_id", function (gid) group.set_value(gid + 1) end) auto_div.line_break() @@ -499,44 +502,35 @@ local function init(parent, id) TextBox{parent=auto_div,text="Prio. Group",height=1,width=11,fg_bg=style.label} local auto_grp = TextBox{parent=auto_div,text="Manual",height=1,width=11,fg_bg=bw_fg_bg} - u_ps.subscribe("auto_group", auto_grp.set_value) + auto_grp.register(u_ps, "auto_group", auto_grp.set_value) auto_div.line_break() local a_rdy = IndicatorLight{parent=auto_div,label="Ready",x=2,colors=cpair(colors.green,colors.gray)} local a_stb = IndicatorLight{parent=auto_div,label="Standby",x=2,colors=cpair(colors.white,colors.gray),flash=true,period=period.BLINK_1000_MS} - u_ps.subscribe("U_AutoReady", a_rdy.update) + a_rdy.register(u_ps, "U_AutoReady", a_rdy.update) -- update standby indicator - u_ps.subscribe("status", function (active) + a_stb.register(u_ps, "status", function (active) a_stb.update(unit.annunciator.AutoControl and (not active)) end) - - -- enable and disable controls based on group assignment - u_ps.subscribe("auto_group_id", function (gid) - start_button_en_check() - - if gid == 0 then - burn_rate.enable() - set_burn_btn.enable() - else - burn_rate.disable() - set_burn_btn.disable() - end - end) - - -- enable and disable controls based on auto control state (start button is handled separately) - u_ps.subscribe("AutoControl", function (auto_active) - start_button_en_check() - + a_stb.register(u_ps, "AutoControl", function (auto_active) if auto_active then a_stb.update(unit.reactor_data.mek_status.status == false) else a_stb.update(false) end end) + -- enable/disable controls based on group assignment (start button is separate) + burn_rate.register(u_ps, "auto_group_id", function (gid) + if gid == 0 then burn_rate.enable() else burn_rate.disable() end + end) + set_burn_btn.register(u_ps, "auto_group_id", function (gid) + if gid == 0 then set_burn_btn.enable() else set_burn_btn.disable() end + end) + -- can't change group if auto is engaged regardless of if this unit is part of auto control - f_ps.subscribe("auto_active", function (auto_active) + set_grp_btn.register(f_ps, "auto_active", function (auto_active) if auto_active then set_grp_btn.disable() else set_grp_btn.enable() end end) diff --git a/coordinator/ui/components/unit_overview.lua b/coordinator/ui/components/unit_overview.lua index 24bc02e..bd341bf 100644 --- a/coordinator/ui/components/unit_overview.lua +++ b/coordinator/ui/components/unit_overview.lua @@ -14,9 +14,9 @@ local Div = require("graphics.elements.div") local PipeNetwork = require("graphics.elements.pipenet") local TextBox = require("graphics.elements.textbox") -local TEXT_ALIGN = core.graphics.TEXT_ALIGN +local TEXT_ALIGN = core.TEXT_ALIGN -local pipe = core.graphics.pipe +local pipe = core.pipe -- make a new unit overview window ---@param parent graphics_element parent diff --git a/coordinator/ui/layout/main_view.lua b/coordinator/ui/layout/main_view.lua index a7f8ae2..a758b24 100644 --- a/coordinator/ui/layout/main_view.lua +++ b/coordinator/ui/layout/main_view.lua @@ -18,9 +18,9 @@ local TextBox = require("graphics.elements.textbox") local DataIndicator = require("graphics.elements.indicators.data") -local TEXT_ALIGN = core.graphics.TEXT_ALIGN +local TEXT_ALIGN = core.TEXT_ALIGN -local cpair = core.graphics.cpair +local cpair = core.cpair -- create new main view ---@param main graphics_element main displaybox @@ -34,8 +34,8 @@ local function init(main) -- max length example: "01:23:45 AM - Wednesday, September 28 2022" local datetime = TextBox{parent=main,x=(header.width()-42),y=1,text="",alignment=TEXT_ALIGN.RIGHT,width=42,height=1,fg_bg=style.header} - facility.ps.subscribe("sv_ping", ping.update) - facility.ps.subscribe("date_time", datetime.set_value) + ping.register(facility.ps, "sv_ping", ping.update) + datetime.register(facility.ps, "date_time", datetime.set_value) local uo_1, uo_2, uo_3, uo_4 ---@type graphics_element diff --git a/coordinator/ui/style.lua b/coordinator/ui/style.lua index 74923f2..b78fc91 100644 --- a/coordinator/ui/style.lua +++ b/coordinator/ui/style.lua @@ -6,7 +6,7 @@ local core = require("graphics.core") local style = {} -local cpair = core.graphics.cpair +local cpair = core.cpair -- GLOBAL -- diff --git a/graphics/core.lua b/graphics/core.lua index d03e551..58b6b8c 100644 --- a/graphics/core.lua +++ b/graphics/core.lua @@ -1,96 +1,19 @@ -- --- Graphics Core Functions and Objects +-- Graphics Core Types, Checks, and Constructors -- +local events = require("graphics.events") +local flasher = require("graphics.flasher") + local core = {} -local flasher = require("graphics.flasher") - core.flasher = flasher - -local events = {} - ----@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 monitor touch mouse interaction event ----@nodiscard ----@param monitor string ----@param x integer ----@param y integer ----@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 - } -end - core.events = events -local graphics = {} +-- Core Types ---@enum TEXT_ALIGN -graphics.TEXT_ALIGN = { +core.TEXT_ALIGN = { LEFT = 1, CENTER = 2, RIGHT = 3 @@ -109,7 +32,7 @@ graphics.TEXT_ALIGN = { ---@param color color border color ---@param even? boolean whether to pad width extra to account for rectangular pixels, defaults to false ---@return graphics_border -function graphics.border(width, color, even) +function core.border(width, color, even) return { width = width, color = color, @@ -130,7 +53,7 @@ end ---@param w integer ---@param h integer ---@return graphics_frame -function graphics.gframe(x, y, w, h) +function core.gframe(x, y, w, h) return { x = x, y = y, @@ -154,7 +77,7 @@ end ---@param a color ---@param b color ---@return cpair -function graphics.cpair(a, b) +function core.cpair(a, b) return { -- color pairs color_a = a, @@ -191,7 +114,7 @@ end ---@param thin? boolean true for 1 subpixel, false (default) for 2 ---@param align_tr? boolean false to align bottom left (default), true to align top right ---@return pipe -function graphics.pipe(x1, y1, x2, y2, color, thin, align_tr) +function core.pipe(x1, y1, x2, y2, color, thin, align_tr) return { x1 = x1, y1 = y1, @@ -205,6 +128,4 @@ function graphics.pipe(x1, y1, x2, y2, color, thin, align_tr) } end -core.graphics = graphics - return core diff --git a/graphics/element.lua b/graphics/element.lua index 2265888..46b9ca1 100644 --- a/graphics/element.lua +++ b/graphics/element.lua @@ -28,6 +28,7 @@ local element = {} ---|sidebar_args ---|spinbox_args ---|switch_button_args +---|tabbar_args ---|alarm_indicator_light ---|core_map_args ---|data_indicator_args @@ -51,6 +52,11 @@ local element = {} ---|textbox_args ---|tiling_args +---@class element_subscription +---@field ps psil ps used +---@field key string data key +---@field func function callback + -- a base graphics element, should not be created on its own ---@nodiscard ---@param args graphics_args arguments @@ -59,12 +65,13 @@ function element.new(args) id = -1, elem_type = debug.getinfo(2).name, define_completed = false, - p_window = nil, ---@type table - position = { x = 1, y = 1 }, + p_window = nil, ---@type table + position = { x = 1, y = 1 }, ---@type coordinate_2d child_offset = { x = 0, y = 0 }, - bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1}, + bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1 }, ---@class element_bounds next_y = 1, children = {}, + subscriptions = {}, mt = {} } @@ -73,10 +80,12 @@ function element.new(args) enabled = true, value = nil, ---@type any window = nil, ---@type table - fg_bg = core.graphics.cpair(colors.white, colors.black), - frame = core.graphics.gframe(1, 1, 1, 1) + fg_bg = core.cpair(colors.white, colors.black), + frame = core.gframe(1, 1, 1, 1) } + local name_brief = "graphics.element{" .. self.elem_type .. "}: " + -- element as string function self.mt.__tostring() return "graphics.element{" .. self.elem_type .. "} @ " .. tostring(self) @@ -138,10 +147,10 @@ function element.new(args) end -- check frame - assert(f.x >= 1, "graphics.element{" .. self.elem_type .. "}: frame x not >= 1") - assert(f.y >= 1, "graphics.element{" .. self.elem_type .. "}: frame y not >= 1") - assert(f.w >= 1, "graphics.element{" .. self.elem_type .. "}: frame width not >= 1") - assert(f.h >= 1, "graphics.element{" .. self.elem_type .. "}: frame height not >= 1") + assert(f.x >= 1, name_brief .. "frame x not >= 1") + assert(f.y >= 1, name_brief .. "frame y not >= 1") + assert(f.w >= 1, name_brief .. "frame width not >= 1") + assert(f.h >= 1, name_brief .. "frame height not >= 1") -- create window protected.window = window.create(self.p_window, f.x, f.y, f.w, f.h, true) @@ -168,8 +177,29 @@ function element.new(args) self.bounds.y2 = self.position.y + f.h - 1 end + -- check if a coordinate is within the bounds of this element + ---@param x integer + ---@param y integer + function protected.in_bounds(x, y) + local in_x = x >= self.bounds.x1 and x <= self.bounds.x2 + local in_y = y >= self.bounds.y1 and y <= self.bounds.y2 + return in_x and in_y + end + +-- luacheck: push ignore ---@diagnostic disable: unused-local, unused-vararg + -- dynamically insert a child element + ---@param id string|integer element identifier + ---@param elem graphics_element element + function protected.insert(id, elem) + end + + -- dynamically remove a child element + ---@param id string|integer element identifier + function protected.remove(id) + end + -- handle a mouse event ---@param event mouse_interaction mouse interaction event function protected.handle_mouse(event) @@ -224,6 +254,7 @@ function element.new(args) function protected.resize(...) end +-- luacheck: pop ---@diagnostic enable: unused-local, unused-vararg -- start animations @@ -250,7 +281,7 @@ function element.new(args) end -- check window - assert(self.p_window, "graphics.element{" .. self.elem_type .. "}: no parent window provided") + assert(self.p_window, name_brief .. "no parent window provided") -- prepare the template if args.parent == nil then @@ -267,7 +298,25 @@ function element.new(args) ---@nodiscard function public.window() return protected.window end - -- CHILD ELEMENTS -- + -- delete this element (hide and unsubscribe from PSIL) + function public.delete() + -- hide + stop animations + public.hide() + + -- unsubscribe from PSIL + for i = 1, #self.subscriptions do + local s = self.subscriptions[i] ---@type element_subscription + s.ps.unsubscribe(s.key, s.func) + end + + -- delete all children + for k, v in pairs(self.children) do + v.delete() + self.children[k] = nil + end + end + + -- ELEMENT TREE -- -- add a child element ---@nodiscard @@ -297,12 +346,18 @@ function element.new(args) -- get a child element ---@nodiscard + ---@param id element_id ---@return graphics_element - function public.get_child(key) return self.children[key] end + function public.get_child(id) return self.children[id] end - -- remove child - ---@param key string|integer - function public.remove(key) self.children[key] = nil end + -- remove a child element + ---@param id element_id + function public.remove(id) + if self.children[id] ~= nil then + self.children[id].delete() + self.children[id] = nil + end + end -- attempt to get a child element by ID (does not include this element itself) ---@nodiscard @@ -321,6 +376,25 @@ function element.new(args) return nil end + -- DYNAMIC CHILD ELEMENTS -- + + -- insert an element as a contained child
+ -- this is intended to be used dynamically, and depends on the target element type.
+ -- not all elements support dynamic children. + ---@param id string|integer element identifier + ---@param elem graphics_element element + function public.insert_element(id, elem) + protected.insert(id, elem) + end + + -- remove an element from contained children
+ -- this is intended to be used dynamically, and depends on the target element type.
+ -- not all elements support dynamic children. + ---@param id string|integer element identifier + function public.remove_element(id) + protected.remove(id) + end + -- AUTO-PLACEMENT -- -- skip a line for automatically placed elements @@ -414,22 +488,29 @@ function element.new(args) protected.resize(...) end + -- reposition the element window
+ -- offsets relative to parent frame are where (1, 1) would be on top of the parent's top left corner + ---@param x integer x position relative to parent frame + ---@param y integer y position relative to parent frame + function public.reposition(x, y) + protected.window.reposition(x, y) + end + -- FUNCTION CALLBACKS -- -- 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 + local x_ini, y_ini = event.initial.x, event.initial.y - if in_x and in_y then - local event_T = core.events.mouse_transposed(event, (event.x - self.position.x) + 1, (event.y - self.position.y) + 1) + local ini_in = protected.in_bounds(x_ini, y_ini) - -- handle the touch event, transformed into the window frame + if ini_in then + local event_T = core.events.mouse_transposed(event, self.position.x, self.position.y) + + -- handle the mouse event then pass to children protected.handle_mouse(event_T) - - -- pass on touch event to children - for _, val in pairs(self.children) do val.handle_mouse(event_T) end + for _, child in pairs(self.children) do child.handle_mouse(event_T) end end end @@ -445,6 +526,16 @@ function element.new(args) protected.response_callback(result) end + -- register a callback with a PSIL, allowing for automatic unregister on delete
+ -- do not use graphics elements directly with PSIL subscribe() + ---@param ps psil PSIL to subscribe to + ---@param key string key to subscribe to + ---@param func function function to link + function public.register(ps, key, func) + table.insert(self.subscriptions, { ps = ps, key = key, func = func }) + ps.subscribe(key, func) + end + -- VISIBILITY -- -- show the element diff --git a/graphics/elements/controls/hazard_button.lua b/graphics/elements/controls/hazard_button.lua index e9f2bf4..4dca5c4 100644 --- a/graphics/elements/controls/hazard_button.lua +++ b/graphics/elements/controls/hazard_button.lua @@ -142,24 +142,25 @@ local function hazard_button(args) -- handle mouse interaction ---@param event mouse_interaction mouse event ----@diagnostic disable-next-line: unused-local function e.handle_mouse(event) if e.enabled then - -- change text color to indicate clicked - e.window.setTextColor(args.accent) - e.window.setCursorPos(3, 2) - e.window.write(args.text) + if core.events.was_clicked(event.type) then + -- change text color to indicate clicked + e.window.setTextColor(args.accent) + e.window.setCursorPos(3, 2) + e.window.write(args.text) - -- abort any other callbacks - tcd.abort(on_timeout) - tcd.abort(on_success) - tcd.abort(on_failure) + -- 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) + -- 1.5 second timeout + tcd.dispatch(1.5, on_timeout) - -- call the touch callback - args.callback() + -- call the touch callback + args.callback() + end end end @@ -167,18 +168,13 @@ local function hazard_button(args) ---@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 + if result then on_success() else on_failure(0) end end -- set the value (true simulates pressing the button) ---@param val boolean new value function e.set_value(val) - if val then e.handle_mouse(core.events.mouse_generic("", core.events.click_type.VIRTUAL, 1, 1)) end + if val then e.handle_mouse(core.events.mouse_generic(core.events.CLICK_TYPE.UP, 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 2549e2b..e44bad0 100644 --- a/graphics/elements/controls/multi_button.lua +++ b/graphics/elements/controls/multi_button.lua @@ -2,13 +2,13 @@ local util = require("scada-common.util") +local core = require("graphics.core") local element = require("graphics.element") ---@class button_option ---@field text string ---@field fg_bg cpair ---@field active_fg_bg cpair ----@field _lpad integer automatically calculated left pad ---@field _start_x integer starting touch x range (inclusive) ---@field _end_x integer ending touch x range (inclusive) @@ -62,9 +62,7 @@ local function multi_button(args) local next_x = 2 for i = 1, #args.options do local opt = args.options[i] ---@type button_option - local w = string.len(opt.text) - opt._lpad = math.floor((e.frame.w - w) / 2) opt._start_x = next_x opt._end_x = next_x + button_width - 1 @@ -92,20 +90,32 @@ local function multi_button(args) end end + -- check which button a given x is within + ---@return integer|nil button index or nil if not within a button + local function which_button(x) + for i = 1, #args.options do + local opt = args.options[i] ---@type button_option + if x >= opt._start_x and x <= opt._end_x then return i end + end + + return nil + end + -- 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 - local opt = args.options[i] ---@type button_option + -- if enabled and the button row was pressed... + if e.enabled and core.events.was_clicked(event.type) then + -- a button may have been pressed, which one was it? + local button_ini = which_button(event.initial.x) + local button_cur = which_button(event.current.x) - if event.x >= opt._start_x and event.x <= opt._end_x then - e.value = i - draw() - args.callback(e.value) - end + -- mouse up must always have started with a mouse down on the same button to count as a click + -- tap always has identical coordinates, so this always passes for taps + if button_ini == button_cur and button_cur ~= nil then + e.value = button_cur + draw() + args.callback(e.value) end end end diff --git a/graphics/elements/controls/push_button.lua b/graphics/elements/controls/push_button.lua index d0c1299..7f91ea5 100644 --- a/graphics/elements/controls/push_button.lua +++ b/graphics/elements/controls/push_button.lua @@ -5,6 +5,8 @@ local tcd = require("scada-common.tcallbackdsp") local core = require("graphics.core") local element = require("graphics.element") +local CLICK_TYPE = core.events.CLICK_TYPE + ---@class push_button_args ---@field text string button text ---@field callback function function to call on touch @@ -24,6 +26,8 @@ local element = require("graphics.element") local function push_button(args) assert(type(args.text) == "string", "graphics.elements.controls.push_button: text is a required field") assert(type(args.callback) == "function", "graphics.elements.controls.push_button: callback is a required field") + assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0), + "graphics.elements.controls.push_button: min_width must be nil or a number > 0") local text_width = string.len(args.text) @@ -47,38 +51,50 @@ local function push_button(args) e.window.write(args.text) end + -- draw the button as pressed (if active_fg_bg set) + local function show_pressed() + if e.enabled and args.active_fg_bg ~= nil then + e.value = true + e.window.setTextColor(args.active_fg_bg.fgd) + e.window.setBackgroundColor(args.active_fg_bg.bkg) + draw() + end + end + + -- draw the button as unpressed (if active_fg_bg set) + local function show_unpressed() + if e.enabled and args.active_fg_bg ~= nil then + e.value = false + e.window.setTextColor(e.fg_bg.fgd) + e.window.setBackgroundColor(e.fg_bg.bkg) + draw() + end + end + -- handle mouse interaction ---@param event mouse_interaction mouse event ----@diagnostic disable-next-line: unused-local function e.handle_mouse(event) if e.enabled then - if args.active_fg_bg ~= nil then - -- show as pressed - e.value = true - e.window.setTextColor(args.active_fg_bg.fgd) - e.window.setBackgroundColor(args.active_fg_bg.bkg) - draw() - + if event.type == CLICK_TYPE.TAP then + show_pressed() -- show as unpressed in 0.25 seconds - tcd.dispatch(0.25, function () - e.value = false - if e.enabled then - e.window.setTextColor(e.fg_bg.fgd) - e.window.setBackgroundColor(e.fg_bg.bkg) - end - draw() - end) + if args.active_fg_bg ~= nil then tcd.dispatch(0.25, show_unpressed) end + args.callback() + elseif event.type == CLICK_TYPE.DOWN then + show_pressed() + elseif event.type == CLICK_TYPE.UP then + show_unpressed() + if e.in_bounds(event.current.x, event.current.y) then + args.callback() + end end - - -- call the touch callback - args.callback() end end -- set the value (true simulates pressing the button) ---@param val boolean new value function e.set_value(val) - if val then e.handle_mouse(core.events.mouse_generic("", core.events.click_type.VIRTUAL, 1, 1)) end + if val then e.handle_mouse(core.events.mouse_generic(core.events.CLICK_TYPE.UP, 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 3b2a593..050bf39 100644 --- a/graphics/elements/controls/radio_button.lua +++ b/graphics/elements/controls/radio_button.lua @@ -1,5 +1,6 @@ -- Radio Button Graphics Element +local core = require("graphics.core") local element = require("graphics.element") ---@class radio_button_args @@ -82,10 +83,10 @@ local function radio_button(args) -- 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 - e.value = event.y + if e.enabled and core.events.was_clicked(event.type) and (event.initial.y == event.current.y) then + -- determine what was pressed + if args.options[event.current.y] ~= nil then + e.value = event.current.y draw() args.callback(e.value) end diff --git a/graphics/elements/controls/sidebar.lua b/graphics/elements/controls/sidebar.lua index 885761d..a20cb72 100644 --- a/graphics/elements/controls/sidebar.lua +++ b/graphics/elements/controls/sidebar.lua @@ -2,8 +2,11 @@ local tcd = require("scada-common.tcallbackdsp") +local core = require("graphics.core") local element = require("graphics.element") +local CLICK_TYPE = core.events.CLICK_TYPE + ---@class sidebar_tab ---@field char string character identifier ---@field color cpair tab colors (fg/bg) @@ -39,7 +42,10 @@ local function sidebar(args) -- show the button state ---@param pressed boolean if the currently selected tab should appear as actively pressed - local function draw(pressed) + ---@param pressed_idx? integer optional index to show as held (that is not yet selected) + local function draw(pressed, pressed_idx) + pressed_idx = pressed_idx or e.value + for i = 1, #args.tabs do local tab = args.tabs[i] ---@type sidebar_tab @@ -47,7 +53,7 @@ local function sidebar(args) e.window.setCursorPos(1, y) - if pressed and e.value == i then + if pressed and i == pressed_idx then e.window.setTextColor(e.fg_bg.fgd) e.window.setBackgroundColor(e.fg_bg.bkg) else @@ -74,16 +80,27 @@ local function sidebar(args) function e.handle_mouse(event) -- determine what was pressed if e.enabled then - local idx = math.ceil(event.y / 3) + local cur_idx = math.ceil(event.current.y / 3) + local ini_idx = math.ceil(event.initial.y / 3) - if args.tabs[idx] ~= nil then - e.value = idx - draw(true) - - -- show as unpressed in 0.25 seconds - tcd.dispatch(0.25, function () draw(false) end) - - args.callback(e.value) + if args.tabs[cur_idx] ~= nil then + if event.type == CLICK_TYPE.TAP then + e.value = cur_idx + draw(true) + -- show as unpressed in 0.25 seconds + tcd.dispatch(0.25, function () draw(false) end) + args.callback(e.value) + elseif event.type == CLICK_TYPE.DOWN then + draw(true, cur_idx) + elseif event.type == CLICK_TYPE.UP then + if cur_idx == ini_idx and e.in_bounds(event.current.x, event.current.y) then + e.value = cur_idx + draw(false) + args.callback(e.value) + else draw(false) end + end + elseif event.type == CLICK_TYPE.UP then + draw(false) end end end diff --git a/graphics/elements/controls/spinbox_numeric.lua b/graphics/elements/controls/spinbox_numeric.lua index 15e0e76..6b88c0e 100644 --- a/graphics/elements/controls/spinbox_numeric.lua +++ b/graphics/elements/controls/spinbox_numeric.lua @@ -2,6 +2,7 @@ local util = require("scada-common.util") +local core = require("graphics.core") local element = require("graphics.element") ---@class spinbox_args @@ -130,19 +131,22 @@ local function spinbox(args) ---@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) - if digits[idx] ~= nil then - if event.y == 1 then - -- increment - digits[idx] = digits[idx] + 1 - elseif event.y == 3 then - -- decrement - digits[idx] = digits[idx] - 1 - end + if e.enabled and core.events.was_clicked(event.type) and + (event.current.x ~= dec_point_x) and (event.current.y ~= 2) then + if event.current.x == event.initial.x and event.current.y == event.initial.y then + local idx = util.trinary(event.current.x > dec_point_x, event.current.x - 1, event.current.x) + if digits[idx] ~= nil then + if event.current.y == 1 then + -- increment + digits[idx] = digits[idx] + 1 + elseif event.current.y == 3 then + -- decrement + digits[idx] = digits[idx] - 1 + end - update_value() - show_num() + update_value() + show_num() + end end end end diff --git a/graphics/elements/controls/switch_button.lua b/graphics/elements/controls/switch_button.lua index 133ea45..645bf8a 100644 --- a/graphics/elements/controls/switch_button.lua +++ b/graphics/elements/controls/switch_button.lua @@ -1,5 +1,6 @@ -- Button Graphics Element +local core = require("graphics.core") local element = require("graphics.element") ---@class switch_button_args @@ -22,13 +23,15 @@ local function switch_button(args) assert(type(args.text) == "string", "graphics.elements.controls.switch_button: text is a required field") assert(type(args.callback) == "function", "graphics.elements.controls.switch_button: callback is a required field") assert(type(args.active_fg_bg) == "table", "graphics.elements.controls.switch_button: active_fg_bg is a required field") + assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0), + "graphics.elements.controls.switch_button: min_width must be nil or a number > 0") - -- single line - args.height = 1 - - -- determine widths local text_width = string.len(args.text) - args.width = math.max(text_width + 2, args.min_width) + + -- single line height, calculate width + args.height = 1 + args.min_width = args.min_width or 0 + args.width = math.max(text_width, args.min_width) -- create new graphics element base object local e = element.new(args) @@ -64,9 +67,8 @@ local function switch_button(args) -- handle mouse interaction ---@param event mouse_interaction mouse event ----@diagnostic disable-next-line: unused-local function e.handle_mouse(event) - if e.enabled then + if e.enabled and core.events.was_clicked(event.type) then -- toggle state e.value = not e.value draw_state() diff --git a/graphics/elements/controls/tabbar.lua b/graphics/elements/controls/tabbar.lua new file mode 100644 index 0000000..6249951 --- /dev/null +++ b/graphics/elements/controls/tabbar.lua @@ -0,0 +1,130 @@ +-- Tab Bar Graphics Element + +local util = require("scada-common.util") + +local core = require("graphics.core") +local element = require("graphics.element") + +---@class tabbar_tab +---@field name string tab name +---@field color cpair tab colors (fg/bg) +---@field _start_x integer starting touch x range (inclusive) +---@field _end_x integer ending touch x range (inclusive) + +---@class tabbar_args +---@field tabs table tab options +---@field callback function function to call on tab change +---@field min_width? integer text length + 2 if omitted +---@field parent graphics_element +---@field id? string element id +---@field x? integer 1 if omitted +---@field y? integer 1 if omitted +---@field width? integer parent width if omitted +---@field fg_bg? cpair foreground/background colors + +-- new tab selector +---@param args tabbar_args +---@return graphics_element element, element_id id +local function tabbar(args) + assert(type(args.tabs) == "table", "graphics.elements.controls.tabbar: tabs is a required field") + assert(#args.tabs > 0, "graphics.elements.controls.tabbar: at least one tab is required") + assert(type(args.callback) == "function", "graphics.elements.controls.tabbar: callback is a required field") + assert(type(args.min_width) == "nil" or (type(args.min_width) == "number" and args.min_width > 0), + "graphics.elements.controls.tabbar: min_width must be nil or a number > 0") + + -- always 1 tall + args.height = 1 + + -- determine widths + local max_width = 1 + for i = 1, #args.tabs do + local opt = args.tabs[i] ---@type tabbar_tab + if string.len(opt.name) > max_width then + max_width = string.len(opt.name) + end + end + + local button_width = math.max(max_width, args.min_width or 0) + + -- create new graphics element base object + local e = element.new(args) + + assert(e.frame.w >= (button_width * #args.tabs), "graphics.elements.controls.tabbar: width insufficent to display all tabs") + + -- default to 1st tab + e.value = 1 + + -- calculate required tab dimension information + local next_x = 1 + for i = 1, #args.tabs do + local tab = args.tabs[i] ---@type tabbar_tab + + tab._start_x = next_x + tab._end_x = next_x + button_width - 1 + + next_x = next_x + button_width + end + + -- show the tab state + local function draw() + for i = 1, #args.tabs do + local tab = args.tabs[i] ---@type tabbar_tab + + e.window.setCursorPos(tab._start_x, 1) + + if e.value == i then + e.window.setTextColor(tab.color.fgd) + e.window.setBackgroundColor(tab.color.bkg) + else + e.window.setTextColor(e.fg_bg.fgd) + e.window.setBackgroundColor(e.fg_bg.bkg) + end + + e.window.write(util.pad(tab.name, button_width)) + end + end + + -- check which tab a given x is within + ---@return integer|nil button index or nil if not within a tab + local function which_tab(x) + for i = 1, #args.tabs do + local tab = args.tabs[i] ---@type tabbar_tab + if x >= tab._start_x and x <= tab._end_x then return i end + end + + return nil + end + + -- handle mouse interaction + ---@param event mouse_interaction mouse event + function e.handle_mouse(event) + -- determine what was pressed + if e.enabled and core.events.was_clicked(event.type) then + -- a button may have been pressed, which one was it? + local tab_ini = which_tab(event.initial.x) + local tab_cur = which_tab(event.current.x) + + -- mouse up must always have started with a mouse down on the same tab to count as a click + -- tap always has identical coordinates, so this always passes for taps + if tab_ini == tab_cur and tab_cur ~= nil then + e.value = tab_cur + draw() + args.callback(e.value) + end + end + end + + -- set the value + ---@param val integer new value + function e.set_value(val) + e.value = val + draw() + end + + -- initial draw + draw() + + return e.get() +end + +return tabbar diff --git a/graphics/elements/indicators/coremap.lua b/graphics/elements/indicators/coremap.lua index 323e17c..05434a3 100644 --- a/graphics/elements/indicators/coremap.lua +++ b/graphics/elements/indicators/coremap.lua @@ -26,7 +26,7 @@ local function core_map(args) args.height = 18 -- inherit only foreground color - args.fg_bg = core.graphics.cpair(args.parent.get_fg_bg().fgd, colors.gray) + args.fg_bg = core.cpair(args.parent.get_fg_bg().fgd, colors.gray) -- create new graphics element base object local e = element.new(args) diff --git a/graphics/elements/pipenet.lua b/graphics/elements/pipenet.lua index 71ee9fd..8a1d29b 100644 --- a/graphics/elements/pipenet.lua +++ b/graphics/elements/pipenet.lua @@ -37,7 +37,7 @@ local function pipenet(args) args.y = args.y or 1 if args.bg ~= nil then - args.fg_bg = core.graphics.cpair(args.bg, args.bg) + args.fg_bg = core.cpair(args.bg, args.bg) end -- create new graphics element base object @@ -55,7 +55,7 @@ local function pipenet(args) e.window.setCursorPos(x, y) - local c = core.graphics.cpair(pipe.color, e.fg_bg.bkg) + local c = core.cpair(pipe.color, e.fg_bg.bkg) if pipe.align_tr then -- cross width then height diff --git a/graphics/elements/textbox.lua b/graphics/elements/textbox.lua index c911677..9066deb 100644 --- a/graphics/elements/textbox.lua +++ b/graphics/elements/textbox.lua @@ -5,7 +5,7 @@ local util = require("scada-common.util") local core = require("graphics.core") local element = require("graphics.element") -local TEXT_ALIGN = core.graphics.TEXT_ALIGN +local TEXT_ALIGN = core.TEXT_ALIGN ---@class textbox_args ---@field text string text to show diff --git a/graphics/events.lua b/graphics/events.lua new file mode 100644 index 0000000..3391a18 --- /dev/null +++ b/graphics/events.lua @@ -0,0 +1,161 @@ +-- +-- Graphics Events and Event Handlers +-- + +local util = require("scada-common.util") + +local events = {} + +---@enum CLICK_BUTTON +events.CLICK_BUTTON = { + GENERIC = 0, + LEFT_BUTTON = 1, + RIGHT_BUTTON = 2, + MID_BUTTON = 3 +} + +---@enum CLICK_TYPE +events.CLICK_TYPE = { + TAP = 1, -- screen tap (complete click) + DOWN = 2, -- button down + UP = 3, -- button up (completed a click) + DRAG = 4, -- mouse dragged + SCROLL_DOWN = 5, -- scroll down + SCROLL_UP = 6 -- scroll up +} + +-- create a new 2D coordinate +---@param x integer +---@param y integer +---@return coordinate_2d +local function _coord2d(x, y) return { x = x, y = y } end + +---@class mouse_interaction +---@field monitor string +---@field button CLICK_BUTTON +---@field type CLICK_TYPE +---@field initial coordinate_2d +---@field current coordinate_2d + +local handler = { + -- left, right, middle button down tracking + button_down = { + _coord2d(0, 0), + _coord2d(0, 0), + _coord2d(0, 0) + } +} + +-- create a new monitor touch mouse interaction event +---@nodiscard +---@param monitor string +---@param x integer +---@param y integer +---@return mouse_interaction +local function _monitor_touch(monitor, x, y) + return { + monitor = monitor, + button = events.CLICK_BUTTON.GENERIC, + type = events.CLICK_TYPE.TAP, + initial = _coord2d(x, y), + current = _coord2d(x, y) + } +end + +-- create a new mouse button mouse interaction event +---@nodiscard +---@param button CLICK_BUTTON mouse button +---@param type CLICK_TYPE click type +---@param x1 integer initial x +---@param y1 integer initial y +---@param x2 integer current x +---@param y2 integer current y +---@return mouse_interaction +local function _mouse_event(button, type, x1, y1, x2, y2) + return { + monitor = "terminal", + button = button, + type = type, + initial = _coord2d(x1, y1), + current = _coord2d(x2, y2) + } +end + +-- create a new generic mouse interaction event +---@nodiscard +---@param type CLICK_TYPE +---@param x integer +---@param y integer +---@return mouse_interaction +function events.mouse_generic(type, x, y) + return { + monitor = "", + button = events.CLICK_BUTTON.GENERIC, + type = type, + initial = _coord2d(x, y), + current = _coord2d(x, y) + } +end + +-- create a new transposed mouse interaction event using the event's monitor/button fields +---@nodiscard +---@param event mouse_interaction +---@param elem_pos_x integer element's x position: new x = (event x - element x) + 1 +---@param elem_pos_y integer element's y position: new y = (event y - element y) + 1 +---@return mouse_interaction +function events.mouse_transposed(event, elem_pos_x, elem_pos_y) + return { + monitor = event.monitor, + button = event.button, + type = event.type, + initial = _coord2d((event.initial.x - elem_pos_x) + 1, (event.initial.y - elem_pos_y) + 1), + current = _coord2d((event.current.x - elem_pos_x) + 1, (event.current.y - elem_pos_y) + 1) + } +end + +-- check if an event qualifies as a click (tap or up) +---@nodiscard +---@param t CLICK_TYPE +function events.was_clicked(t) return t == events.CLICK_TYPE.TAP or t == events.CLICK_TYPE.UP end + +-- create a new mouse event to pass onto graphics renderer
+-- supports: mouse_click, mouse_up, mouse_drag, mouse_scroll, and monitor_touch +---@param event_type os_event OS event to handle +---@param opt integer|string button, scroll direction, or monitor for monitor touch +---@param x integer x coordinate +---@param y integer y coordinate +---@return mouse_interaction|nil +function events.new_mouse_event(event_type, opt, x, y) + if event_type == "mouse_click" then + ---@cast opt 1|2|3 + handler.button_down[opt] = _coord2d(x, y) + return _mouse_event(opt, events.CLICK_TYPE.DOWN, x, y, x, y) + elseif event_type == "mouse_up" then + ---@cast opt 1|2|3 + local initial = handler.button_down[opt] ---@type coordinate_2d + return _mouse_event(opt, events.CLICK_TYPE.UP, initial.x, initial.y, x, y) + elseif event_type == "monitor_touch" then + ---@cast opt string + return _monitor_touch(opt, x, y) + elseif event_type == "mouse_drag" then + ---@cast opt 1|2|3 + local initial = handler.button_down[opt] ---@type coordinate_2d + return _mouse_event(opt, events.CLICK_TYPE.DRAG, initial.x, initial.y, x, y) + elseif event_type == "mouse_scroll" then + ---@cast opt 1|-1 + local scroll_direction = util.trinary(opt == 1, events.CLICK_TYPE.SCROLL_DOWN, events.CLICK_TYPE.SCROLL_UP) + return _mouse_event(events.CLICK_BUTTON.GENERIC, scroll_direction, x, y, x, y) + end +end + +-- create a new key event to pass onto graphics renderer
+-- supports: char, key, and key_up +---@param event_type os_event +function events.new_key_event(event_type) + if event_type == "char" then + elseif event_type == "key" then + elseif event_type == "key_up" then + end +end + +return events diff --git a/install_manifest.json b/install_manifest.json index 91054c0..18c5137 100644 --- a/install_manifest.json +++ b/install_manifest.json @@ -1 +1 @@ -{"versions": {"installer": "v1.0", "bootloader": "0.2", "comms": "1.4.1", "reactor-plc": "v1.1.17", "rtu": "v1.0.5", "supervisor": "v0.15.5", "coordinator": "v0.13.8", "pocket": "alpha-v0.2.6"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/multipane.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/led.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/ledpair.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/indicators/ledrgb.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/controls/sidebar.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/renderer.lua", "reactor-plc/threads.lua", "reactor-plc/databus.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua", "reactor-plc/panel/front_panel.lua", "reactor-plc/panel/style.lua"], "rtu": ["rtu/renderer.lua", "rtu/threads.lua", "rtu/rtu.lua", "rtu/databus.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/panel/front_panel.lua", "rtu/panel/style.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/pocket.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/turbine.lua", "coordinator/session/api.lua", "coordinator/session/apisessions.lua"], "pocket": ["pocket/pocket.lua", "pocket/renderer.lua", "pocket/config.lua", "pocket/coreio.lua", "pocket/startup.lua", "pocket/ui/main.lua", "pocket/ui/style.lua", "pocket/ui/components/turbine_page.lua", "pocket/ui/components/reactor_page.lua", "pocket/ui/components/home_page.lua", "pocket/ui/components/unit_page.lua", "pocket/ui/components/boiler_page.lua", "pocket/ui/components/conn_waiting.lua"]}, "depends": {"reactor-plc": ["system", "common", "graphics"], "rtu": ["system", "common", "graphics"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 5467, "system": 1991, "common": 90337, "graphics": 115781, "lockbox": 100797, "reactor-plc": 95517, "rtu": 100134, "supervisor": 282706, "coordinator": 195981, "pocket": 36123}} \ No newline at end of file +{"versions": {"installer": "v1.0", "bootloader": "0.2", "comms": "1.4.1", "reactor-plc": "v1.3.1", "rtu": "v1.2.1", "supervisor": "v0.15.9", "coordinator": "v0.15.1", "pocket": "alpha-v0.3.1"}, "files": {"system": ["initenv.lua", "startup.lua"], "common": ["scada-common/crypto.lua", "scada-common/ppm.lua", "scada-common/comms.lua", "scada-common/psil.lua", "scada-common/tcallbackdsp.lua", "scada-common/rsio.lua", "scada-common/constants.lua", "scada-common/mqueue.lua", "scada-common/crash.lua", "scada-common/log.lua", "scada-common/types.lua", "scada-common/util.lua"], "graphics": ["graphics/element.lua", "graphics/events.lua", "graphics/flasher.lua", "graphics/core.lua", "graphics/elements/textbox.lua", "graphics/elements/displaybox.lua", "graphics/elements/pipenet.lua", "graphics/elements/rectangle.lua", "graphics/elements/div.lua", "graphics/elements/multipane.lua", "graphics/elements/tiling.lua", "graphics/elements/colormap.lua", "graphics/elements/indicators/alight.lua", "graphics/elements/indicators/icon.lua", "graphics/elements/indicators/power.lua", "graphics/elements/indicators/rad.lua", "graphics/elements/indicators/state.lua", "graphics/elements/indicators/light.lua", "graphics/elements/indicators/vbar.lua", "graphics/elements/indicators/led.lua", "graphics/elements/indicators/coremap.lua", "graphics/elements/indicators/data.lua", "graphics/elements/indicators/ledpair.lua", "graphics/elements/indicators/hbar.lua", "graphics/elements/indicators/trilight.lua", "graphics/elements/indicators/ledrgb.lua", "graphics/elements/controls/switch_button.lua", "graphics/elements/controls/spinbox_numeric.lua", "graphics/elements/controls/hazard_button.lua", "graphics/elements/controls/push_button.lua", "graphics/elements/controls/radio_button.lua", "graphics/elements/controls/multi_button.lua", "graphics/elements/controls/tabbar.lua", "graphics/elements/controls/sidebar.lua", "graphics/elements/animations/waiting.lua"], "lockbox": ["lockbox/init.lua", "lockbox/LICENSE", "lockbox/kdf/pbkdf2.lua", "lockbox/util/bit.lua", "lockbox/util/array.lua", "lockbox/util/stream.lua", "lockbox/util/queue.lua", "lockbox/digest/sha2_224.lua", "lockbox/digest/sha1.lua", "lockbox/digest/sha2_256.lua", "lockbox/cipher/aes128.lua", "lockbox/cipher/aes256.lua", "lockbox/cipher/aes192.lua", "lockbox/cipher/mode/ofb.lua", "lockbox/cipher/mode/cbc.lua", "lockbox/cipher/mode/ctr.lua", "lockbox/cipher/mode/cfb.lua", "lockbox/mac/hmac.lua", "lockbox/padding/ansix923.lua", "lockbox/padding/pkcs7.lua", "lockbox/padding/zero.lua", "lockbox/padding/isoiec7816.lua"], "reactor-plc": ["reactor-plc/renderer.lua", "reactor-plc/threads.lua", "reactor-plc/databus.lua", "reactor-plc/plc.lua", "reactor-plc/config.lua", "reactor-plc/startup.lua", "reactor-plc/panel/front_panel.lua", "reactor-plc/panel/style.lua"], "rtu": ["rtu/renderer.lua", "rtu/threads.lua", "rtu/rtu.lua", "rtu/databus.lua", "rtu/modbus.lua", "rtu/config.lua", "rtu/startup.lua", "rtu/panel/front_panel.lua", "rtu/panel/style.lua", "rtu/dev/sps_rtu.lua", "rtu/dev/envd_rtu.lua", "rtu/dev/boilerv_rtu.lua", "rtu/dev/redstone_rtu.lua", "rtu/dev/sna_rtu.lua", "rtu/dev/imatrix_rtu.lua", "rtu/dev/turbinev_rtu.lua"], "supervisor": ["supervisor/supervisor.lua", "supervisor/unit.lua", "supervisor/config.lua", "supervisor/startup.lua", "supervisor/unitlogic.lua", "supervisor/facility.lua", "supervisor/session/coordinator.lua", "supervisor/session/svqtypes.lua", "supervisor/session/pocket.lua", "supervisor/session/svsessions.lua", "supervisor/session/rtu.lua", "supervisor/session/plc.lua", "supervisor/session/rsctl.lua", "supervisor/session/rtu/boilerv.lua", "supervisor/session/rtu/txnctrl.lua", "supervisor/session/rtu/unit_session.lua", "supervisor/session/rtu/turbinev.lua", "supervisor/session/rtu/envd.lua", "supervisor/session/rtu/imatrix.lua", "supervisor/session/rtu/sps.lua", "supervisor/session/rtu/qtypes.lua", "supervisor/session/rtu/sna.lua", "supervisor/session/rtu/redstone.lua"], "coordinator": ["coordinator/coordinator.lua", "coordinator/renderer.lua", "coordinator/iocontrol.lua", "coordinator/sounder.lua", "coordinator/config.lua", "coordinator/startup.lua", "coordinator/process.lua", "coordinator/ui/dialog.lua", "coordinator/ui/style.lua", "coordinator/ui/layout/main_view.lua", "coordinator/ui/layout/unit_view.lua", "coordinator/ui/components/reactor.lua", "coordinator/ui/components/processctl.lua", "coordinator/ui/components/unit_overview.lua", "coordinator/ui/components/boiler.lua", "coordinator/ui/components/unit_detail.lua", "coordinator/ui/components/imatrix.lua", "coordinator/ui/components/turbine.lua", "coordinator/session/api.lua", "coordinator/session/apisessions.lua"], "pocket": ["pocket/pocket.lua", "pocket/renderer.lua", "pocket/config.lua", "pocket/coreio.lua", "pocket/startup.lua", "pocket/ui/main.lua", "pocket/ui/style.lua", "pocket/ui/components/turbine_page.lua", "pocket/ui/components/reactor_page.lua", "pocket/ui/components/home_page.lua", "pocket/ui/components/unit_page.lua", "pocket/ui/components/boiler_page.lua", "pocket/ui/components/conn_waiting.lua"]}, "depends": {"reactor-plc": ["system", "common", "graphics"], "rtu": ["system", "common", "graphics"], "supervisor": ["system", "common"], "coordinator": ["system", "common", "graphics"], "pocket": ["system", "common", "graphics"]}, "sizes": {"manifest": 5530, "system": 1991, "common": 91102, "graphics": 129080, "lockbox": 100797, "reactor-plc": 95896, "rtu": 100982, "supervisor": 283073, "coordinator": 197508, "pocket": 36200}} \ No newline at end of file diff --git a/pocket/config.lua b/pocket/config.lua index cacd9f1..27e1489 100644 --- a/pocket/config.lua +++ b/pocket/config.lua @@ -17,5 +17,7 @@ config.LOG_PATH = "/log.txt" -- 0 = APPEND (adds to existing file on start) -- 1 = NEW (replaces existing file on start) config.LOG_MODE = 0 +-- true to log verbose debug messages +config.LOG_DEBUG = false return config diff --git a/pocket/renderer.lua b/pocket/renderer.lua index 1ecf3ab..fa25bcd 100644 --- a/pocket/renderer.lua +++ b/pocket/renderer.lua @@ -70,9 +70,9 @@ end function renderer.ui_ready() return ui.display ~= nil end -- handle a mouse event ----@param event mouse_interaction +---@param event mouse_interaction|nil function renderer.handle_mouse(event) - if ui.display ~= nil then + if ui.display ~= nil and event ~= nil then ui.display.handle_mouse(event) end end diff --git a/pocket/startup.lua b/pocket/startup.lua index 9516340..d1c54d0 100644 --- a/pocket/startup.lua +++ b/pocket/startup.lua @@ -17,7 +17,7 @@ local coreio = require("pocket.coreio") local pocket = require("pocket.pocket") local renderer = require("pocket.renderer") -local POCKET_VERSION = "alpha-v0.2.6" +local POCKET_VERSION = "alpha-v0.3.1" local println = util.println local println_ts = util.println_ts @@ -43,7 +43,7 @@ assert(cfv.valid(), "bad config file: missing/invalid fields") -- log init ---------------------------------------- -log.init(config.LOG_PATH, config.LOG_MODE) +log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG == true) log.info("========================================") log.info("BOOTING pocket.startup " .. POCKET_VERSION) @@ -152,9 +152,9 @@ local function main() -- got a packet local packet = pocket_comms.parse_packet(param1, param2, param3, param4, param5) pocket_comms.handle_packet(packet) - elseif event == "mouse_click" then + elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" then -- handle a monitor touch event - renderer.handle_mouse(core.events.touch(param1, param2, param3)) + renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3)) end -- check for termination request diff --git a/pocket/ui/components/boiler_page.lua b/pocket/ui/components/boiler_page.lua index fd0eca1..ec13d59 100644 --- a/pocket/ui/components/boiler_page.lua +++ b/pocket/ui/components/boiler_page.lua @@ -5,9 +5,9 @@ local core = require("graphics.core") local Div = require("graphics.elements.div") local TextBox = require("graphics.elements.textbox") --- local cpair = core.graphics.cpair +-- local cpair = core.cpair -local TEXT_ALIGN = core.graphics.TEXT_ALIGN +local TEXT_ALIGN = core.TEXT_ALIGN -- new boiler page view ---@param root graphics_element parent diff --git a/pocket/ui/components/conn_waiting.lua b/pocket/ui/components/conn_waiting.lua index cd08652..9bbbfc0 100644 --- a/pocket/ui/components/conn_waiting.lua +++ b/pocket/ui/components/conn_waiting.lua @@ -11,9 +11,9 @@ local TextBox = require("graphics.elements.textbox") local WaitingAnim = require("graphics.elements.animations.waiting") -local TEXT_ALIGN = core.graphics.TEXT_ALIGN +local TEXT_ALIGN = core.TEXT_ALIGN -local cpair = core.graphics.cpair +local cpair = core.cpair -- create a waiting view ---@param parent graphics_element parent diff --git a/pocket/ui/components/home_page.lua b/pocket/ui/components/home_page.lua index 5287cac..a31cae8 100644 --- a/pocket/ui/components/home_page.lua +++ b/pocket/ui/components/home_page.lua @@ -5,9 +5,9 @@ local core = require("graphics.core") local Div = require("graphics.elements.div") local TextBox = require("graphics.elements.textbox") --- local cpair = core.graphics.cpair +-- local cpair = core.cpair -local TEXT_ALIGN = core.graphics.TEXT_ALIGN +local TEXT_ALIGN = core.TEXT_ALIGN -- new home page view ---@param root graphics_element parent diff --git a/pocket/ui/components/reactor_page.lua b/pocket/ui/components/reactor_page.lua index 50b1939..c7a2e96 100644 --- a/pocket/ui/components/reactor_page.lua +++ b/pocket/ui/components/reactor_page.lua @@ -5,9 +5,9 @@ local core = require("graphics.core") local Div = require("graphics.elements.div") local TextBox = require("graphics.elements.textbox") --- local cpair = core.graphics.cpair +-- local cpair = core.cpair -local TEXT_ALIGN = core.graphics.TEXT_ALIGN +local TEXT_ALIGN = core.TEXT_ALIGN -- new reactor page view ---@param root graphics_element parent diff --git a/pocket/ui/components/turbine_page.lua b/pocket/ui/components/turbine_page.lua index 9fd7af5..527e419 100644 --- a/pocket/ui/components/turbine_page.lua +++ b/pocket/ui/components/turbine_page.lua @@ -5,9 +5,9 @@ local core = require("graphics.core") local Div = require("graphics.elements.div") local TextBox = require("graphics.elements.textbox") --- local cpair = core.graphics.cpair +-- local cpair = core.cpair -local TEXT_ALIGN = core.graphics.TEXT_ALIGN +local TEXT_ALIGN = core.TEXT_ALIGN -- new turbine page view ---@param root graphics_element parent diff --git a/pocket/ui/components/unit_page.lua b/pocket/ui/components/unit_page.lua index 2e24df3..a0718e6 100644 --- a/pocket/ui/components/unit_page.lua +++ b/pocket/ui/components/unit_page.lua @@ -5,9 +5,9 @@ local core = require("graphics.core") local Div = require("graphics.elements.div") local TextBox = require("graphics.elements.textbox") --- local cpair = core.graphics.cpair +-- local cpair = core.cpair -local TEXT_ALIGN = core.graphics.TEXT_ALIGN +local TEXT_ALIGN = core.TEXT_ALIGN -- new unit page view ---@param root graphics_element parent diff --git a/pocket/ui/main.lua b/pocket/ui/main.lua index 5af9ce8..c143b0b 100644 --- a/pocket/ui/main.lua +++ b/pocket/ui/main.lua @@ -22,9 +22,9 @@ local TextBox = require("graphics.elements.textbox") local Sidebar = require("graphics.elements.controls.sidebar") -local TEXT_ALIGN = core.graphics.TEXT_ALIGN +local TEXT_ALIGN = core.TEXT_ALIGN -local cpair = core.graphics.cpair +local cpair = core.cpair -- create new main view ---@param main graphics_element main displaybox @@ -45,7 +45,7 @@ local function init(main) local root_pane = MultiPane{parent=root_pane_div,x=1,y=1,panes=root_panes} - coreio.core_ps().subscribe("link_state", function (state) + root_pane.register(coreio.core_ps(), "link_state", function (state) if state == coreio.LINK_STATE.UNLINKED or state == coreio.LINK_STATE.API_LINK_ONLY then root_pane.set_value(1) elseif state == coreio.LINK_STATE.SV_LINK_ONLY then diff --git a/pocket/ui/style.lua b/pocket/ui/style.lua index b9a09fc..2fb7526 100644 --- a/pocket/ui/style.lua +++ b/pocket/ui/style.lua @@ -6,7 +6,7 @@ local core = require("graphics.core") local style = {} -local cpair = core.graphics.cpair +local cpair = core.cpair -- GLOBAL -- diff --git a/reactor-plc/config.lua b/reactor-plc/config.lua index e479ede..e402bbb 100644 --- a/reactor-plc/config.lua +++ b/reactor-plc/config.lua @@ -24,5 +24,7 @@ config.LOG_PATH = "/log.txt" -- 0 = APPEND (adds to existing file on start) -- 1 = NEW (replaces existing file on start) config.LOG_MODE = 0 +-- true to log verbose debug messages +config.LOG_DEBUG = false return config diff --git a/reactor-plc/databus.lua b/reactor-plc/databus.lua index beee265..ba7a68e 100644 --- a/reactor-plc/databus.lua +++ b/reactor-plc/databus.lua @@ -8,14 +8,16 @@ local util = require("scada-common.util") local databus = {} +-- databus PSIL +databus.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() dbus_iface.ps.toggle("heartbeat") end +function databus.heartbeat() databus.ps.toggle("heartbeat") end -- link RPS command functions ---@param scram function reactor SCRAM function @@ -35,42 +37,42 @@ function databus.rps_reset() dbus_iface.rps_reset() end ---@param plc_v string PLC version ---@param comms_v string comms version function databus.tx_versions(plc_v, comms_v) - dbus_iface.ps.publish("version", plc_v) - dbus_iface.ps.publish("comms_version", comms_v) + databus.ps.publish("version", plc_v) + databus.ps.publish("comms_version", comms_v) end -- transmit unit ID across the bus ---@param id integer unit ID function databus.tx_id(id) - dbus_iface.ps.publish("unit_id", id) + databus.ps.publish("unit_id", id) end -- transmit hardware status across the bus ---@param plc_state plc_state function databus.tx_hw_status(plc_state) - 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) + databus.ps.publish("reactor_dev_state", util.trinary(plc_state.no_reactor, 1, util.trinary(plc_state.reactor_formed, 3, 2))) + databus.ps.publish("has_modem", not plc_state.no_modem) + databus.ps.publish("degraded", plc_state.degraded) + databus.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) - dbus_iface.ps.publish(util.c("routine__", thread), ok) + databus.ps.publish(util.c("routine__", thread), ok) end -- transmit supervisor link state across the bus ---@param state integer function databus.tx_link_state(state) - dbus_iface.ps.publish("link_state", state) + databus.ps.publish("link_state", state) end -- transmit reactor enable state across the bus ---@param active boolean reactor active function databus.tx_reactor_state(active) - dbus_iface.ps.publish("reactor_active", active) + databus.ps.publish("reactor_active", active) end -- transmit RPS data across the bus @@ -78,26 +80,26 @@ end ---@param status table RPS status ---@param emer_cool_active boolean RPS activated the emergency coolant function databus.tx_rps(tripped, status, emer_cool_active) - 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]) - dbus_iface.ps.publish("emer_cool", emer_cool_active) + databus.ps.publish("rps_scram", tripped) + databus.ps.publish("rps_damage", status[1]) + databus.ps.publish("rps_high_temp", status[2]) + databus.ps.publish("rps_low_ccool", status[3]) + databus.ps.publish("rps_high_waste", status[4]) + databus.ps.publish("rps_high_hcool", status[5]) + databus.ps.publish("rps_no_fuel", status[6]) + databus.ps.publish("rps_fault", status[7]) + databus.ps.publish("rps_timeout", status[8]) + databus.ps.publish("rps_manual", status[9]) + databus.ps.publish("rps_automatic", status[10]) + databus.ps.publish("rps_sysfail", status[11]) + databus.ps.publish("emer_cool", emer_cool_active) 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) - dbus_iface.ps.subscribe(field, func) + databus.ps.subscribe(field, func) end return databus diff --git a/reactor-plc/panel/front_panel.lua b/reactor-plc/panel/front_panel.lua index 70c80ef..8e28a75 100644 --- a/reactor-plc/panel/front_panel.lua +++ b/reactor-plc/panel/front_panel.lua @@ -22,16 +22,16 @@ 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 +local TEXT_ALIGN = core.TEXT_ALIGN -local cpair = core.graphics.cpair -local border = core.graphics.border +local cpair = core.cpair +local border = core.border -- create new main view ---@param panel graphics_element main displaybox local function init(panel) 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) + header.register(databus.ps, "unit_id", function (id) header.set_value(util.c("REACTOR PLC - UNIT ", id)) end) -- -- system indicators @@ -43,8 +43,8 @@ local function init(panel) local heartbeat = LED{parent=system,label="HEARTBEAT",colors=cpair(colors.green,colors.green_off)} system.line_break() - databus.rx_field("init_ok", init_ok.update) - databus.rx_field("heartbeat", heartbeat.update) + init_ok.register(databus.ps, "init_ok", init_ok.update) + heartbeat.register(databus.ps, "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)} @@ -52,9 +52,9 @@ local function init(panel) network.update(5) system.line_break() - databus.rx_field("reactor_dev_state", reactor.update) - databus.rx_field("has_modem", modem.update) - databus.rx_field("link_state", network.update) + reactor.register(databus.ps, "reactor_dev_state", reactor.update) + modem.register(databus.ps, "has_modem", modem.update) + network.register(databus.ps, "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)} @@ -63,11 +63,11 @@ local function init(panel) local rt_sctl = LED{parent=system,label="RT SPCTL",colors=cpair(colors.green,colors.green_off)} system.line_break() - 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) + rt_main.register(databus.ps, "routine__main", rt_main.update) + rt_rps.register(databus.ps, "routine__rps", rt_rps.update) + rt_cmtx.register(databus.ps, "routine__comms_tx", rt_cmtx.update) + rt_cmrx.register(databus.ps, "routine__comms_rx", rt_cmrx.update) + rt_sctl.register(databus.ps, "routine__spctl", rt_sctl.update) -- -- status & controls @@ -80,7 +80,7 @@ local function init(panel) -- only show emergency coolant LED if emergency coolant is configured for this device if type(config.EMERGENCY_COOL) == "table" then local emer_cool = LED{parent=status,x=2,width=14,label="EMER COOLANT",colors=cpair(colors.yellow,colors.yellow_off)} - databus.rx_field("emer_cool", emer_cool.update) + emer_cool.register(databus.ps, "emer_cool", emer_cool.update) end local status_trip_rct = Rectangle{parent=status,width=20,height=3,x=1,border=border(1,colors.lightGray,true),even_inner=true,fg_bg=cpair(colors.black,colors.ivory)} @@ -92,8 +92,8 @@ local function init(panel) 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) + active.register(databus.ps, "reactor_active", active.update) + scram.register(databus.ps, "rps_scram", scram.update) -- -- about footer @@ -103,8 +103,8 @@ local function init(panel) 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) + fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end) + comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end) -- -- rps list @@ -126,17 +126,17 @@ local function init(panel) 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)} - 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) + rps_man.register(databus.ps, "rps_manual", rps_man.update) + rps_auto.register(databus.ps, "rps_automatic", rps_auto.update) + rps_tmo.register(databus.ps, "rps_timeout", rps_tmo.update) + rps_flt.register(databus.ps, "rps_fault", rps_flt.update) + rps_fail.register(databus.ps, "rps_sysfail", rps_fail.update) + rps_dmg.register(databus.ps, "rps_damage", rps_dmg.update) + rps_tmp.register(databus.ps, "rps_high_temp", rps_tmp.update) + rps_nof.register(databus.ps, "rps_no_fuel", rps_nof.update) + rps_wst.register(databus.ps, "rps_high_waste", rps_wst.update) + rps_ccl.register(databus.ps, "rps_low_ccool", rps_ccl.update) + rps_hcl.register(databus.ps, "rps_high_hcool", rps_hcl.update) end return init diff --git a/reactor-plc/panel/style.lua b/reactor-plc/panel/style.lua index 31039d4..996453c 100644 --- a/reactor-plc/panel/style.lua +++ b/reactor-plc/panel/style.lua @@ -6,7 +6,7 @@ local core = require("graphics.core") local style = {} -local cpair = core.graphics.cpair +local cpair = core.cpair -- GLOBAL -- diff --git a/reactor-plc/renderer.lua b/reactor-plc/renderer.lua index 0700ebe..038918b 100644 --- a/reactor-plc/renderer.lua +++ b/reactor-plc/renderer.lua @@ -44,10 +44,8 @@ function renderer.close_ui() -- stop blinking indicators flasher.clear() - -- hide to stop animation callbacks - ui.display.hide() - - -- clear root UI elements + -- delete element tree + ui.display.delete() ui.display = nil -- restore colors @@ -70,9 +68,9 @@ end function renderer.ui_ready() return ui.display ~= nil end -- handle a mouse event ----@param event mouse_interaction +---@param event mouse_interaction|nil function renderer.handle_mouse(event) - if ui.display ~= nil then + if ui.display ~= nil and event ~= nil then ui.display.handle_mouse(event) end end diff --git a/reactor-plc/startup.lua b/reactor-plc/startup.lua index bd40fce..c3b546f 100644 --- a/reactor-plc/startup.lua +++ b/reactor-plc/startup.lua @@ -18,7 +18,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.17" +local R_PLC_VERSION = "v1.3.1" local println = util.println local println_ts = util.println_ts @@ -54,7 +54,7 @@ end -- log init ---------------------------------------- -log.init(config.LOG_PATH, config.LOG_MODE) +log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG == true) log.info("========================================") log.info("BOOTING reactor-plc.startup " .. R_PLC_VERSION) diff --git a/reactor-plc/threads.lua b/reactor-plc/threads.lua index 8470430..8dbea6d 100644 --- a/reactor-plc/threads.lua +++ b/reactor-plc/threads.lua @@ -257,9 +257,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 == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" then + -- handle a mouse event + renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3)) elseif event == "clock_start" then -- start loop clock loop_clock.start() diff --git a/rtu/config.lua b/rtu/config.lua index 96b26ee..1b96bec 100644 --- a/rtu/config.lua +++ b/rtu/config.lua @@ -17,6 +17,8 @@ config.LOG_PATH = "/log.txt" -- 0 = APPEND (adds to existing file on start) -- 1 = NEW (replaces existing file on start) config.LOG_MODE = 0 +-- true to log verbose debug messages +config.LOG_DEBUG = false -- RTU peripheral devices (named: side/network device name) config.RTU_DEVICES = { diff --git a/rtu/databus.lua b/rtu/databus.lua index 8ef720e..3014367 100644 --- a/rtu/databus.lua +++ b/rtu/databus.lua @@ -7,9 +7,8 @@ local util = require("scada-common.util") local databus = {} -local dbus_iface = { - ps = psil.create() -} +-- databus PSIL +databus.ps = psil.create() ---@enum RTU_UNIT_HW_STATE local RTU_UNIT_HW_STATE = { @@ -22,54 +21,54 @@ local RTU_UNIT_HW_STATE = { databus.RTU_UNIT_HW_STATE = RTU_UNIT_HW_STATE -- call to toggle heartbeat signal -function databus.heartbeat() dbus_iface.ps.toggle("heartbeat") end +function databus.heartbeat() databus.ps.toggle("heartbeat") end -- transmit firmware versions across the bus ---@param rtu_v string RTU version ---@param comms_v string comms version function databus.tx_versions(rtu_v, comms_v) - dbus_iface.ps.publish("version", rtu_v) - dbus_iface.ps.publish("comms_version", comms_v) + databus.ps.publish("version", rtu_v) + databus.ps.publish("comms_version", comms_v) end -- transmit hardware status for modem connection state ---@param has_modem boolean function databus.tx_hw_modem(has_modem) - dbus_iface.ps.publish("has_modem", has_modem) + databus.ps.publish("has_modem", has_modem) end -- transmit unit hardware type across the bus ---@param uid integer unit ID ---@param type RTU_UNIT_TYPE function databus.tx_unit_hw_type(uid, type) - dbus_iface.ps.publish("unit_type_" .. uid, type) + databus.ps.publish("unit_type_" .. uid, type) end -- transmit unit hardware status across the bus ---@param uid integer unit ID ---@param status RTU_UNIT_HW_STATE function databus.tx_unit_hw_status(uid, status) - dbus_iface.ps.publish("unit_hw_" .. uid, status) + databus.ps.publish("unit_hw_" .. uid, status) end -- transmit thread (routine) statuses ---@param thread string thread name ---@param ok boolean thread state function databus.tx_rt_status(thread, ok) - dbus_iface.ps.publish(util.c("routine__", thread), ok) + databus.ps.publish(util.c("routine__", thread), ok) end -- transmit supervisor link state across the bus ---@param state integer function databus.tx_link_state(state) - dbus_iface.ps.publish("link_state", state) + databus.ps.publish("link_state", state) 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) - dbus_iface.ps.subscribe(field, func) + databus.ps.subscribe(field, func) end return databus diff --git a/rtu/panel/front_panel.lua b/rtu/panel/front_panel.lua index f6014da..4c4aa78 100644 --- a/rtu/panel/front_panel.lua +++ b/rtu/panel/front_panel.lua @@ -16,9 +16,9 @@ local TextBox = require("graphics.elements.textbox") local LED = require("graphics.elements.indicators.led") local RGBLED = require("graphics.elements.indicators.ledrgb") -local TEXT_ALIGN = core.graphics.TEXT_ALIGN +local TEXT_ALIGN = core.TEXT_ALIGN -local cpair = core.graphics.cpair +local cpair = core.cpair local UNIT_TYPE_LABELS = { "UNKNOWN", @@ -49,22 +49,22 @@ local function init(panel, units) on.update(true) system.line_break() - databus.rx_field("heartbeat", heartbeat.update) + heartbeat.register(databus.ps, "heartbeat", heartbeat.update) local modem = LED{parent=system,label="MODEM",colors=cpair(colors.green,colors.green_off)} local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,colors.gray}} network.update(5) system.line_break() - databus.rx_field("has_modem", modem.update) - databus.rx_field("link_state", network.update) + modem.register(databus.ps, "has_modem", modem.update) + network.register(databus.ps, "link_state", network.update) local rt_main = LED{parent=system,label="RT MAIN",colors=cpair(colors.green,colors.green_off)} local rt_comm = LED{parent=system,label="RT COMMS",colors=cpair(colors.green,colors.green_off)} system.line_break() - databus.rx_field("routine__main", rt_main.update) - databus.rx_field("routine__comms", rt_comm.update) + rt_main.register(databus.ps, "routine__main", rt_main.update) + rt_comm.register(databus.ps, "routine__comms", rt_comm.update) -- -- about label @@ -74,8 +74,8 @@ local function init(panel, units) local fw_v = TextBox{parent=about,x=1,y=1,text="FW: v00.00.00",alignment=TEXT_ALIGN.LEFT,height=1} local comms_v = TextBox{parent=about,x=1,y=2,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) + fw_v.register(databus.ps, "version", function (version) fw_v.set_value(util.c("FW: ", version)) end) + comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end) -- -- unit status list @@ -90,7 +90,7 @@ local function init(panel, units) for i = 1, list_length do TextBox{parent=threads,x=1,y=i,text=util.sprintf("%02d",i),height=1} local rt_unit = LED{parent=threads,x=4,y=i,label="RT",colors=cpair(colors.green,colors.green_off)} - databus.rx_field("routine__unit_" .. i, rt_unit.update) + rt_unit.register(databus.ps, "routine__unit_" .. i, rt_unit.update) end local unit_hw_statuses = Div{parent=panel,height=18,x=25,y=3} @@ -102,13 +102,13 @@ local function init(panel, units) -- hardware status local unit_hw = RGBLED{parent=unit_hw_statuses,y=i,label="",colors={colors.red,colors.orange,colors.yellow,colors.green}} - databus.rx_field("unit_hw_" .. i, unit_hw.update) + unit_hw.register(databus.ps, "unit_hw_" .. i, unit_hw.update) -- unit name identifier (type + index) local name = util.c(UNIT_TYPE_LABELS[unit.type + 1], " ", unit.index) local name_box = TextBox{parent=unit_hw_statuses,y=i,x=3,text=name,height=1} - databus.rx_field("unit_type_" .. i, function (t) + name_box.register(databus.ps, "unit_type_" .. i, function (t) name_box.set_value(util.c(UNIT_TYPE_LABELS[t + 1], " ", unit.index)) end) diff --git a/rtu/panel/style.lua b/rtu/panel/style.lua index 31039d4..996453c 100644 --- a/rtu/panel/style.lua +++ b/rtu/panel/style.lua @@ -6,7 +6,7 @@ local core = require("graphics.core") local style = {} -local cpair = core.graphics.cpair +local cpair = core.cpair -- GLOBAL -- diff --git a/rtu/renderer.lua b/rtu/renderer.lua index f79f19e..17949ce 100644 --- a/rtu/renderer.lua +++ b/rtu/renderer.lua @@ -45,10 +45,8 @@ function renderer.close_ui() -- stop blinking indicators flasher.clear() - -- hide to stop animation callbacks - ui.display.hide() - - -- clear root UI elements + -- delete element tree + ui.display.delete() ui.display = nil -- restore colors @@ -71,9 +69,9 @@ end function renderer.ui_ready() return ui.display ~= nil end -- handle a mouse event ----@param event mouse_interaction +---@param event mouse_interaction|nil function renderer.handle_mouse(event) - if ui.display ~= nil then + if ui.display ~= nil and event ~= nil then ui.display.handle_mouse(event) end end diff --git a/rtu/startup.lua b/rtu/startup.lua index e2d9d94..a217ac3 100644 --- a/rtu/startup.lua +++ b/rtu/startup.lua @@ -28,7 +28,7 @@ local sna_rtu = require("rtu.dev.sna_rtu") local sps_rtu = require("rtu.dev.sps_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu") -local RTU_VERSION = "v1.0.5" +local RTU_VERSION = "v1.2.1" local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE @@ -57,7 +57,7 @@ assert(cfv.valid(), "bad config file: missing/invalid fields") -- log init ---------------------------------------- -log.init(config.LOG_PATH, config.LOG_MODE) +log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG == true) log.info("========================================") log.info("BOOTING rtu.startup " .. RTU_VERSION) diff --git a/rtu/threads.lua b/rtu/threads.lua index 9dac9f1..046525c 100644 --- a/rtu/threads.lua +++ b/rtu/threads.lua @@ -229,9 +229,9 @@ function threads.thread__main(smem) end end end - elseif event == "mouse_click" then - -- handle a monitor touch event - renderer.handle_mouse(core.events.click(param1, param2, param3)) + elseif event == "mouse_click" or event == "mouse_up" or event == "mouse_drag" or event == "mouse_scroll" then + -- handle a mouse event + renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3)) end -- check for termination request diff --git a/scada-common/log.lua b/scada-common/log.lua index 40baef2..7e2d3fd 100644 --- a/scada-common/log.lua +++ b/scada-common/log.lua @@ -15,12 +15,10 @@ local MODE = { log.MODE = MODE --- whether to log debug messages or not -local LOG_DEBUG = true - -local log_sys = { +local logger = { path = "/log.txt", mode = MODE.APPEND, + debug = false, file = nil, dmesg_out = nil } @@ -41,8 +39,8 @@ local function _log(msg) -- attempt to write log local status, result = pcall(function () - log_sys.file.writeLine(stamped) - log_sys.file.flush() + logger.file.writeLine(stamped) + logger.file.flush() end) -- if we don't have space, we need to create a new log file @@ -57,18 +55,18 @@ local function _log(msg) end end - if out_of_space or (free_space(log_sys.path) < 100) then + if out_of_space or (free_space(logger.path) < 100) then -- delete the old log file before opening a new one - log_sys.file.close() - fs.delete(log_sys.path) + logger.file.close() + fs.delete(logger.path) -- re-init logger and pass dmesg_out so that it doesn't change - log.init(log_sys.path, log_sys.mode, log_sys.dmesg_out) + log.init(logger.path, logger.mode, logger.debug, logger.dmesg_out) -- leave a message - log_sys.file.writeLine(time_stamp .. "recycled log file") - log_sys.file.writeLine(stamped) - log_sys.file.flush() + logger.file.writeLine(time_stamp .. "recycled log file") + logger.file.writeLine(stamped) + logger.file.flush() end end @@ -78,33 +76,35 @@ end -- initialize logger ---@param path string file path ----@param write_mode MODE +---@param write_mode MODE file write mode +---@param include_debug boolean whether or not to include debug logs ---@param dmesg_redirect? table terminal/window to direct dmesg to -function log.init(path, write_mode, dmesg_redirect) - log_sys.path = path - log_sys.mode = write_mode +function log.init(path, write_mode, include_debug, dmesg_redirect) + logger.path = path + logger.mode = write_mode + logger.debug = include_debug - if log_sys.mode == MODE.APPEND then - log_sys.file = fs.open(path, "a") + if logger.mode == MODE.APPEND then + logger.file = fs.open(path, "a") else - log_sys.file = fs.open(path, "w") + logger.file = fs.open(path, "w") end if dmesg_redirect then - log_sys.dmesg_out = dmesg_redirect + logger.dmesg_out = dmesg_redirect else - log_sys.dmesg_out = term.current() + logger.dmesg_out = term.current() end end -- close the log file handle function log.close() - log_sys.file.close() + logger.file.close() end -- direct dmesg output to a monitor/window ---@param window table window or terminal reference -function log.direct_dmesg(window) log_sys.dmesg_out = window end +function log.direct_dmesg(window) logger.dmesg_out = window end -- dmesg style logging for boot because I like linux-y things ---@param msg string message @@ -120,7 +120,7 @@ function log.dmesg(msg, tag, tag_color) tag = util.strval(tag) local t_stamp = string.format("%12.2f", os.clock()) - local out = log_sys.dmesg_out + local out = logger.dmesg_out if out ~= nil then local out_w, out_h = out.getSize() @@ -216,7 +216,7 @@ end function log.dmesg_working(msg, tag, tag_color) local ts_coord = log.dmesg(msg, tag, tag_color) - local out = log_sys.dmesg_out + local out = logger.dmesg_out local width = (ts_coord.x2 - ts_coord.x1) + 1 if out ~= nil then @@ -275,7 +275,7 @@ end ---@param msg string message ---@param trace? boolean include file trace function log.debug(msg, trace) - if LOG_DEBUG then + if logger.debug then local dbg_info = "" if trace then diff --git a/scada-common/psil.lua b/scada-common/psil.lua index 664d10d..13dcaa5 100644 --- a/scada-common/psil.lua +++ b/scada-common/psil.lua @@ -2,6 +2,8 @@ -- Publisher-Subscriber Interconnect Layer -- +local util = require("scada-common.util") + local psil = {} -- instantiate a new PSI layer @@ -36,6 +38,15 @@ function psil.create() table.insert(self.ic[key].subscribers, { notify = func }) end + -- unsubscribe a function from a given key + ---@param key string data key + ---@param func function function to unsubscribe + function public.unsubscribe(key, func) + if self.ic[key] ~= nil then + util.filter_table(self.ic[key].subscribers, function (s) return s.notify ~= func end) + end + end + -- publish data to a given key, passing it to all subscribers if it has changed ---@param key string data key ---@param value any data value @@ -64,6 +75,9 @@ function psil.create() end end + -- clear the contents of the interconnect + function public.purge() self.ic = nil end + return public end diff --git a/scada-common/types.lua b/scada-common/types.lua index 9beb1e6..8df01c1 100644 --- a/scada-common/types.lua +++ b/scada-common/types.lua @@ -39,6 +39,10 @@ function types.new_radiation_reading(r, u) return { radiation = r, unit = u } en ---@return radiation_reading function types.new_zero_radiation_reading() return { radiation = 0, unit = "nSv" } end +---@class coordinate_2d +---@field x integer +---@field y integer + ---@class coordinate ---@field x integer ---@field y integer diff --git a/scada-common/util.lua b/scada-common/util.lua index e13f9fc..063143c 100644 --- a/scada-common/util.lua +++ b/scada-common/util.lua @@ -113,10 +113,13 @@ end ---@return table lines function util.strwrap(str, limit) return cc_strings.wrap(str, limit) end +-- luacheck: no unused args + -- concatenation with built-in to string ---@nodiscard ---@vararg any ---@return string +---@diagnostic disable-next-line: unused-vararg function util.concat(...) local str = "" for _, v in ipairs(arg) do str = str .. util.strval(v) end @@ -130,10 +133,13 @@ util.c = util.concat ---@nodiscard ---@param format string ---@vararg any +---@diagnostic disable-next-line: unused-vararg function util.sprintf(format, ...) return string.format(format, table.unpack(arg)) end +-- luacheck: unused args + -- format a number string with commas as the thousands separator
-- subtracts from spaces at the start if present for each comma used ---@nodiscard diff --git a/supervisor/config.lua b/supervisor/config.lua index 0aa26d8..b716d38 100644 --- a/supervisor/config.lua +++ b/supervisor/config.lua @@ -28,5 +28,7 @@ config.LOG_PATH = "/log.txt" -- 0 = APPEND (adds to existing file on start) -- 1 = NEW (replaces existing file on start) config.LOG_MODE = 0 +-- true to log verbose debug messages +config.LOG_DEBUG = false return config diff --git a/supervisor/session/rtu.lua b/supervisor/session/rtu.lua index 96309a0..b494aa6 100644 --- a/supervisor/session/rtu.lua +++ b/supervisor/session/rtu.lua @@ -118,26 +118,31 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili if unit_advert.reactor > 0 then local target_unit = self.fac_units[unit_advert.reactor] ---@type reactor_unit + -- unit RTUs if u_type == RTU_UNIT_TYPE.REDSTONE then -- redstone unit = svrs_redstone.new(id, i, unit_advert, self.modbus_q) if type(unit) ~= "nil" then target_unit.add_redstone(unit) end elseif u_type == RTU_UNIT_TYPE.BOILER_VALVE then - -- boiler (Mekanism 10.1+) + -- boiler unit = svrs_boilerv.new(id, i, unit_advert, self.modbus_q) if type(unit) ~= "nil" then target_unit.add_boiler(unit) end elseif u_type == RTU_UNIT_TYPE.TURBINE_VALVE then - -- turbine (Mekanism 10.1+) + -- turbine unit = svrs_turbinev.new(id, i, unit_advert, self.modbus_q) if type(unit) ~= "nil" then target_unit.add_turbine(unit) end elseif u_type == RTU_UNIT_TYPE.ENV_DETECTOR then -- environment detector unit = svrs_envd.new(id, i, unit_advert, self.modbus_q) if type(unit) ~= "nil" then target_unit.add_envd(unit) end + elseif u_type == RTU_UNIT_TYPE.VIRTUAL then + -- skip virtual units + log.debug(util.c(log_header, "skipping virtual RTU unit #", i)) else log.error(util.c(log_header, "bad advertisement: encountered unsupported reactor-specific RTU type ", type_string)) end else + -- facility RTUs if u_type == RTU_UNIT_TYPE.REDSTONE then -- redstone unit = svrs_redstone.new(id, i, unit_advert, self.modbus_q) @@ -156,6 +161,9 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili -- environment detector unit = svrs_envd.new(id, i, unit_advert, self.modbus_q) if type(unit) ~= "nil" then facility.add_envd(unit) end + elseif u_type == RTU_UNIT_TYPE.VIRTUAL then + -- skip virtual units + log.debug(util.c(log_header, "skipping virtual RTU unit #", i)) else log.error(util.c(log_header, "bad advertisement: encountered unsupported reactor-independent RTU type ", type_string)) end @@ -163,8 +171,8 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili end if unit ~= nil then - table.insert(self.units, unit) - else + self.units[i] = unit + elseif u_type ~= RTU_UNIT_TYPE.VIRTUAL then _reset_config() log.error(util.c(log_header, "bad advertisement: error occured while creating a unit (type is ", type_string, ")")) break @@ -178,9 +186,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili self.connected = false -- mark all RTU unit sessions as closed so the reactor unit knows - for i = 1, #self.units do - self.units[i].close() - end + for _, unit in pairs(self.units) do unit.close() end end -- send a MODBUS packet @@ -343,9 +349,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili local time_now = util.time() - for i = 1, #self.units do - self.units[i].update(time_now) - end + for _, unit in pairs(self.units) do unit.update(time_now) end ---------------------- -- update periodics -- diff --git a/supervisor/session/rtu/redstone.lua b/supervisor/session/rtu/redstone.lua index bc7b81d..25b7284 100644 --- a/supervisor/session/rtu/redstone.lua +++ b/supervisor/session/rtu/redstone.lua @@ -120,9 +120,7 @@ function redstone.new(session_id, unit_id, advert, out_queue) local io_f = { ---@nodiscard read = function () return rsio.digital_is_active(port, self.phy_io.digital_in[port].phy) end, - ---@param active boolean ----@diagnostic disable-next-line: unused-local - write = function (active) end + write = function () end } self.db.io[port] = io_f @@ -155,9 +153,7 @@ function redstone.new(session_id, unit_id, advert, out_queue) ---@nodiscard ---@return integer read = function () return self.phy_io.analog_in[port].phy end, - ---@param value integer ----@diagnostic disable-next-line: unused-local - write = function (value) end + write = function () end } self.db.io[port] = io_f diff --git a/supervisor/session/rtu/unit_session.lua b/supervisor/session/rtu/unit_session.lua index 700f9b1..711fc75 100644 --- a/supervisor/session/rtu/unit_session.lua +++ b/supervisor/session/rtu/unit_session.lua @@ -166,6 +166,8 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t -- PUBLIC TEMPLATE FUNCTIONS -- +-- luacheck: no unused args + -- handle a packet ---@param m_pkt modbus_frame ---@diagnostic disable-next-line: unused-local @@ -180,6 +182,8 @@ function unit_session.new(session_id, unit_id, advert, out_queue, log_tag, txn_t log.debug("template unit_session.update() called", true) end +-- luacheck: unused args + -- invalidate build cache function public.invalidate_cache() log.debug("template unit_session.invalidate_cache() called", true) diff --git a/supervisor/startup.lua b/supervisor/startup.lua index bf27e2e..534a5d9 100644 --- a/supervisor/startup.lua +++ b/supervisor/startup.lua @@ -14,7 +14,7 @@ local supervisor = require("supervisor.supervisor") local svsessions = require("supervisor.session.svsessions") -local SUPERVISOR_VERSION = "v0.15.5" +local SUPERVISOR_VERSION = "v0.15.9" local println = util.println local println_ts = util.println_ts @@ -61,7 +61,7 @@ end -- log init ---------------------------------------- -log.init(config.LOG_PATH, config.LOG_MODE) +log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG == true) log.info("========================================") log.info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION) diff --git a/supervisor/supervisor.lua b/supervisor/supervisor.lua index 49cf482..998c438 100644 --- a/supervisor/supervisor.lua +++ b/supervisor/supervisor.lua @@ -15,7 +15,7 @@ local println = util.println -- supervisory controller communications ---@nodiscard ----@param version string supervisor version +---@param _version string supervisor version ---@param num_reactors integer number of reactors ---@param cooling_conf table cooling configuration table ---@param modem table modem device @@ -23,7 +23,7 @@ local println = util.println ---@param svctl_listen integer listening port for supervisor access ---@param range integer trusted device connection range ---@diagnostic disable-next-line: unused-local -function supervisor.comms(version, num_reactors, cooling_conf, modem, dev_listen, svctl_listen, range) +function supervisor.comms(_version, num_reactors, cooling_conf, modem, dev_listen, svctl_listen, range) local self = { last_est_acks = {} } diff --git a/test/modbustest.lua b/test/modbustest.lua index 40d2b2f..b521e7b 100644 --- a/test/modbustest.lua +++ b/test/modbustest.lua @@ -63,6 +63,7 @@ mbt.test_error__check_request(MODBUS_EXCODE.NEG_ACKNOWLEDGE) println("PASS") print("99 {1,2}: ") +---@diagnostic disable-next-line: param-type-mismatch mbt.pkt_set(99, {1, 2}) mbt.test_error__check_request(MODBUS_EXCODE.ILLEGAL_FUNCTION) println("PASS") diff --git a/test/turbine_modbustest.lua b/test/turbine_modbustest.lua index fe167d7..c8f7801 100644 --- a/test/turbine_modbustest.lua +++ b/test/turbine_modbustest.lua @@ -22,7 +22,7 @@ println("") -- RTU init -- -log.init("/log.txt", log.MODE.NEW) +log.init("/log.txt", log.MODE.NEW, true) print(">>> init turbine RTU: ")