Merge pull request #236 from MikaylaFischler/devel

2023.05.22 Release
This commit is contained in:
Mikayla 2023-05-22 10:06:18 -04:00 committed by GitHub
commit 97cee58e5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 971 additions and 590 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -6,7 +6,7 @@ local core = require("graphics.core")
local style = {}
local cpair = core.graphics.cpair
local cpair = core.cpair
-- GLOBAL --

View File

@ -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

View File

@ -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<br>
-- this is intended to be used dynamically, and depends on the target element type.<br>
-- 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<br>
-- this is intended to be used dynamically, and depends on the target element type.<br>
-- 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<br>
-- 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<br>
-- 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

161
graphics/events.lua Normal file
View File

@ -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<br>
-- 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<br>
-- 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

File diff suppressed because one or more lines are too long

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -6,7 +6,7 @@ local core = require("graphics.core")
local style = {}
local cpair = core.graphics.cpair
local cpair = core.cpair
-- GLOBAL --

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -6,7 +6,7 @@ local core = require("graphics.core")
local style = {}
local cpair = core.graphics.cpair
local cpair = core.cpair
-- GLOBAL --

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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 = {

View File

@ -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

View File

@ -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)

View File

@ -6,7 +6,7 @@ local core = require("graphics.core")
local style = {}
local cpair = core.graphics.cpair
local cpair = core.cpair
-- GLOBAL --

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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<br>
-- subtracts from spaces at the start if present for each comma used
---@nodiscard

View File

@ -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

View File

@ -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 --

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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 = {}
}

View File

@ -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")

View File

@ -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: ")