Merge branch 'devel' into 225-consolidate-network-channels

This commit is contained in:
Mikayla Fischler 2023-06-05 01:18:13 -04:00
commit 9a5fc92c86
95 changed files with 2338 additions and 847 deletions

View File

@ -1,13 +1,13 @@
# Simple workflow for deploying static content to GitHub Pages # Simple workflow for deploying static content to GitHub Pages
name: Deploy Component Versions name: Deploy Installation Manifests and Versions
on: on:
# Runs on pushes targeting the default branch
push:
branches: ["main"]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch: workflow_dispatch:
push:
branches:
- main
- latest
- devel
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions: permissions:
@ -35,13 +35,26 @@ jobs:
uses: actions/configure-pages@v3 uses: actions/configure-pages@v3
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v3.1.3 uses: actions/setup-python@v3.1.3
- run: mkdir shields - name: Extract branch name
- run: python imgen.py shields shell: bash
- name: Upload artifact run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT
id: extract_branch
- name: Create outputs folder
shell: bash
run: mkdir deploy; mkdir deploy/manifests; mkdir deploy/manifests/${{ steps.extract_branch.outputs.branch }}
- name: Generate manifest and shields for main branch
if: github.event.pull_request.base.ref == 'main'
run: python imgen.py gh_actions
- name: Generate only manifest for non-main branches
if: github.event.pull_request.base.ref != 'main'
run: python imgen.py
- name: Move manifest
run: mv install_manifest.json deploy/manifests/${{ steps.extract_branch.outputs.branch }}
- name: Upload artifacts
uses: actions/upload-pages-artifact@v1 uses: actions/upload-pages-artifact@v1
with: with:
# Upload shields JSON # Upload manifest JSON
path: 'shields/' path: 'deploy/'
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@v2 uses: actions/deploy-pages@v2

View File

@ -20,9 +20,10 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
local function println(message) print(tostring(message)) end local function println(message) print(tostring(message)) end
local function print(message) term.write(tostring(message)) end local function print(message) term.write(tostring(message)) end
local CCMSI_VERSION = "v1.0" local CCMSI_VERSION = "v1.2"
local install_dir = "/.install-cache" local install_dir = "/.install-cache"
local manifest_path = "https://mikaylafischler.github.io/cc-mek-scada/manifests/"
local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/" local repo_path = "http://raw.githubusercontent.com/MikaylaFischler/cc-mek-scada/"
local opts = { ... } local opts = { ... }
@ -122,8 +123,8 @@ if mode == "check" then
-- GET REMOTE MANIFEST -- -- GET REMOTE MANIFEST --
------------------------- -------------------------
if opts[2] then repo_path = repo_path .. opts[2] .. "/" else repo_path = repo_path .. "main/" end if opts[2] then manifest_path = manifest_path .. opts[2] .. "/" else manifest_path = manifest_path .. "main/" end
local install_manifest = repo_path .. "install_manifest.json" local install_manifest = manifest_path .. "install_manifest.json"
local response, error = http.get(install_manifest) local response, error = http.get(install_manifest)
@ -203,7 +204,8 @@ elseif mode == "install" or mode == "update" then
------------------------- -------------------------
if opts[3] then repo_path = repo_path .. opts[3] .. "/" else repo_path = repo_path .. "main/" end if opts[3] then repo_path = repo_path .. opts[3] .. "/" else repo_path = repo_path .. "main/" end
local install_manifest = repo_path .. "install_manifest.json" if opts[3] then manifest_path = manifest_path .. opts[3] .. "/" else manifest_path = manifest_path .. "main/" end
local install_manifest = manifest_path .. "install_manifest.json"
local response, error = http.get(install_manifest) local response, error = http.get(install_manifest)

View File

@ -335,12 +335,13 @@ function coordinator.comms(version, modem, sv_port, sv_listen, api_listen, range
tick_dmesg_waiting(math.max(0, timeout_s - (util.time_s() - start))) tick_dmesg_waiting(math.max(0, timeout_s - (util.time_s() - start)))
_send_establish() _send_establish()
clock.start() clock.start()
elseif event == "timer" then
-- keep checking watchdog timers
apisessions.check_all_watchdogs(p1)
elseif event == "modem_message" then elseif event == "modem_message" then
-- handle message -- handle message
local packet = public.parse_packet(p1, p2, p3, p4, p5) local packet = public.parse_packet(p1, p2, p3, p4, p5)
if packet ~= nil and packet.type == SCADA_MGMT_TYPE.ESTABLISH then
public.handle_packet(packet) public.handle_packet(packet)
end
elseif event == "terminate" then elseif event == "terminate" then
terminated = true terminated = true
break break

View File

@ -138,9 +138,9 @@ function renderer.close_ui()
-- stop blinking indicators -- stop blinking indicators
flasher.clear() flasher.clear()
-- hide to stop animation callbacks -- delete element trees
if engine.ui.main_display ~= nil then engine.ui.main_display.hide() end if engine.ui.main_display ~= nil then engine.ui.main_display.delete() end
for _, display in ipairs(engine.ui.unit_displays) do display.hide() end for _, display in ipairs(engine.ui.unit_displays) do display.delete() end
-- report ui as not ready -- report ui as not ready
engine.ui_ready = false engine.ui_ready = false
@ -163,9 +163,9 @@ end
function renderer.ui_ready() return engine.ui_ready end function renderer.ui_ready() return engine.ui_ready end
-- handle a touch event -- handle a touch event
---@param event mouse_interaction ---@param event mouse_interaction|nil
function renderer.handle_mouse(event) 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 if event.monitor == engine.monitors.primary_name then
engine.ui.main_display.handle_mouse(event) engine.ui.main_display.handle_mouse(event)
else else

View File

@ -117,8 +117,6 @@ function api.new_session(id, in_queue, out_queue, timeout)
-- process packet -- process packet
if pkt.scada_frame.protocol() == PROTOCOL.COORD_API then if pkt.scada_frame.protocol() == PROTOCOL.COORD_API then
---@cast pkt capi_frame ---@cast pkt capi_frame
-- feed watchdog
self.conn_watchdog.feed()
-- handle packet by type -- handle packet by type
if pkt.type == nil then if pkt.type == nil then

View File

@ -7,7 +7,7 @@ require("/initenv").init_env()
local crash = require("scada-common.crash") local crash = require("scada-common.crash")
local log = require("scada-common.log") local log = require("scada-common.log")
local ppm = require("scada-common.ppm") local ppm = require("scada-common.ppm")
local tcallbackdsp = require("scada-common.tcallbackdsp") local tcd = require("scada-common.tcd")
local util = require("scada-common.util") local util = require("scada-common.util")
local core = require("graphics.core") local core = require("graphics.core")
@ -20,7 +20,7 @@ local sounder = require("coordinator.sounder")
local apisessions = require("coordinator.session.apisessions") local apisessions = require("coordinator.session.apisessions")
local COORDINATOR_VERSION = "v0.13.8" local COORDINATOR_VERSION = "v0.15.8"
local println = util.println local println = util.println
local println_ts = util.println_ts local println_ts = util.println_ts
@ -50,7 +50,6 @@ cfv.assert_type_num(config.SOUNDER_VOLUME)
cfv.assert_type_bool(config.TIME_24_HOUR) cfv.assert_type_bool(config.TIME_24_HOUR)
cfv.assert_type_str(config.LOG_PATH) cfv.assert_type_str(config.LOG_PATH)
cfv.assert_type_int(config.LOG_MODE) cfv.assert_type_int(config.LOG_MODE)
cfv.assert_type_bool(config.LOG_DEBUG)
assert(cfv.valid(), "bad config file: missing/invalid fields") assert(cfv.valid(), "bad config file: missing/invalid fields")
@ -58,7 +57,7 @@ assert(cfv.valid(), "bad config file: missing/invalid fields")
-- log init -- log init
---------------------------------------- ----------------------------------------
log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG) log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG == true)
log.info("========================================") log.info("========================================")
log.info("BOOTING coordinator.startup " .. COORDINATOR_VERSION) log.info("BOOTING coordinator.startup " .. COORDINATOR_VERSION)
@ -335,7 +334,7 @@ local function main()
apisessions.check_all_watchdogs(param1) apisessions.check_all_watchdogs(param1)
-- notify timer callback dispatcher -- notify timer callback dispatcher
tcallbackdsp.handle(param1) tcd.handle(param1)
end end
elseif event == "modem_message" then elseif event == "modem_message" then
-- got a packet -- got a packet
@ -359,7 +358,7 @@ local function main()
end end
elseif event == "monitor_touch" then elseif event == "monitor_touch" then
-- handle a monitor touch event -- 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 elseif event == "speaker_audio_empty" then
-- handle speaker buffer emptied -- handle speaker buffer emptied
sounder.continue() sounder.continue()

View File

@ -9,8 +9,8 @@ local DataIndicator = require("graphics.elements.indicators.data")
local StateIndicator = require("graphics.elements.indicators.state") local StateIndicator = require("graphics.elements.indicators.state")
local VerticalBar = require("graphics.elements.indicators.vbar") local VerticalBar = require("graphics.elements.indicators.vbar")
local cpair = core.graphics.cpair local cpair = core.cpair
local border = core.graphics.border local border = core.border
-- new boiler view -- new boiler view
---@param root graphics_element parent ---@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 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} 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) status.register(ps, "computed_status", status.update)
ps.subscribe("temperature", temp.update) temp.register(ps, "temperature", temp.update)
ps.subscribe("boil_rate", boil_r.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="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} 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 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} 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) hcool.register(ps, "hcool_fill", hcool.update)
ps.subscribe("water_fill", water.update) water.register(ps, "water_fill", water.update)
ps.subscribe("steam_fill", steam.update) steam.register(ps, "steam_fill", steam.update)
ps.subscribe("ccool_fill", ccool.update) ccool.register(ps, "ccool_fill", ccool.update)
end end
return new_view return new_view

View File

@ -13,10 +13,10 @@ local PowerIndicator = require("graphics.elements.indicators.power")
local StateIndicator = require("graphics.elements.indicators.state") local StateIndicator = require("graphics.elements.indicators.state")
local VerticalBar = require("graphics.elements.indicators.vbar") local VerticalBar = require("graphics.elements.indicators.vbar")
local cpair = core.graphics.cpair local cpair = core.cpair
local border = core.graphics.border local border = core.border
local TEXT_ALIGN = core.graphics.TEXT_ALIGN local TEXT_ALIGN = core.TEXT_ALIGN
-- new induction matrix view -- new induction matrix view
---@param root graphics_element parent ---@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_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} 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) status.register(ps, "computed_status", status.update)
ps.subscribe("energy", function (val) energy.update(util.joules_to_fe(val)) end) energy.register(ps, "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) capacity.register(ps, "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) input.register(ps, "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) output.register(ps, "last_output", function (val) output.update(util.joules_to_fe(val)) end)
ps.subscribe("avg_charge", avg_chg.update) avg_chg.register(ps, "avg_charge", avg_chg.update)
ps.subscribe("avg_inflow", avg_in.update) avg_in.register(ps, "avg_inflow", avg_in.update)
ps.subscribe("avg_outflow", avg_out.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} 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} 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} 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) cells.register(ps, "cells", cells.update)
ps.subscribe("providers", providers.update) providers.register(ps, "providers", providers.update)
ps.subscribe("energy_fill", function (val) fill.update(val * 100) end) fill.register(ps, "energy_fill", function (val) fill.update(val * 100) end)
ps.subscribe("transfer_cap", function (val) trans_cap.update(util.joules_to_fe(val)) 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 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} 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
end end
ps.subscribe("energy_fill", charge.update) charge.register(ps, "energy_fill", charge.update)
ps.subscribe("last_input", function (val) in_cap.update(calc_saturation(val)) end) in_cap.register(ps, "last_input", function (val) in_cap.update(calc_saturation(val)) end)
ps.subscribe("last_output", function (val) out_cap.update(calc_saturation(val)) end) out_cap.register(ps, "last_output", function (val) out_cap.update(calc_saturation(val)) end)
end end
return new_view return new_view

View File

@ -1,4 +1,4 @@
local tcd = require("scada-common.tcallbackdsp") local tcd = require("scada-common.tcd")
local util = require("scada-common.util") local util = require("scada-common.util")
local iocontrol = require("coordinator.iocontrol") local iocontrol = require("coordinator.iocontrol")
@ -21,10 +21,10 @@ local HazardButton = require("graphics.elements.controls.hazard_button")
local RadioButton = require("graphics.elements.controls.radio_button") local RadioButton = require("graphics.elements.controls.radio_button")
local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") 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 cpair = core.cpair
local border = core.graphics.border local border = core.border
local period = core.flasher.PERIOD local period = core.flasher.PERIOD
@ -33,7 +33,7 @@ local period = core.flasher.PERIOD
---@param x integer top left x ---@param x integer top left x
---@param y integer top left y ---@param y integer top left y
local function new_view(root, x, y) local function new_view(root, x, y)
assert(root.height() >= (y + 24), "main display not of sufficient vertical resolution (add an additional row of monitors)") assert(root.get_height() >= (y + 24), "main display not of sufficient vertical resolution (add an additional row of monitors)")
local facility = iocontrol.get_db().facility local facility = iocontrol.get_db().facility
local units = iocontrol.get_db().units local units = iocontrol.get_db().units
@ -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 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} 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) all_ok.register(facility.ps, "all_sys_ok", all_ok.update)
facility.induction_ps_tbl[1].subscribe("computed_status", function (status) ind_mat.update(status > 1) end) ind_mat.register(facility.induction_ps_tbl[1], "computed_status", function (status) ind_mat.update(status > 1) end)
facility.ps.subscribe("rad_computed_status", rad_mon.update) rad_mon.register(facility.ps, "rad_computed_status", rad_mon.update)
main.line_break() 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_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)} 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) auto_ready.register(facility.ps, "auto_ready", auto_ready.update)
facility.ps.subscribe("auto_active", auto_act.update) auto_act.register(facility.ps, "auto_active", auto_act.update)
facility.ps.subscribe("auto_ramping", auto_ramp.update) auto_ramp.register(facility.ps, "auto_ramping", auto_ramp.update)
facility.ps.subscribe("auto_saturated", auto_sat.update) auto_sat.register(facility.ps, "auto_saturated", auto_sat.update)
main.line_break() 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 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} 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) auto_scram.register(facility.ps, "auto_scram", auto_scram.update)
facility.ps.subscribe("as_matrix_dc", matrix_dc.update) matrix_dc.register(facility.ps, "as_matrix_dc", matrix_dc.update)
facility.ps.subscribe("as_matrix_fill", matrix_fill.update) matrix_fill.register(facility.ps, "as_matrix_fill", matrix_fill.update)
facility.ps.subscribe("as_crit_alarm", unit_crit.update) unit_crit.register(facility.ps, "as_crit_alarm", unit_crit.update)
facility.ps.subscribe("as_radiation", fac_rad_h.update) fac_rad_h.register(facility.ps, "as_radiation", fac_rad_h.update)
facility.ps.subscribe("as_gen_fault", gen_fault.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} 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} 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} 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} 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 -- -- process control --
@ -115,8 +115,8 @@ local function new_view(root, x, y)
TextBox{parent=burn_target,x=18,y=2,text="mB/t"} 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)} 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) b_target.register(facility.ps, "process_burn_target", b_target.set_value)
facility.ps.subscribe("burn_sum", burn_sum.update) 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)} 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} 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"} 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)} 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) c_target.register(facility.ps, "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) 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)} 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} 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"} 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)} 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) g_target.register(facility.ps, "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) 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 -- -- 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} 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} 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) rate_limits[i].register(unit.unit_ps, "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, "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)} 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 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 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} 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) ready.register(unit.unit_ps, "U_AutoReady", ready.update)
unit.unit_ps.subscribe("U_AutoDegraded", degraded.update) degraded.register(unit.unit_ps, "U_AutoDegraded", degraded.update)
end 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 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} 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 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_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)} 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) stat_line_1.register(facility.ps, "status_line_1", stat_line_1.set_value)
facility.ps.subscribe("status_line_2", stat_line_2.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)} 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) tcd.dispatch(0.2, function () save.on_response(ack) end)
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 if ready and (not facility.auto_active) then start.enable() else start.disable() end
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 if active then
b_target.disable() b_target.disable()
c_target.disable() c_target.disable()
@ -246,9 +249,7 @@ local function new_view(root, x, y)
mode.disable() mode.disable()
start.disable() start.disable()
for i = 1, #rate_limits do for i = 1, #rate_limits do rate_limits[i].disable() end
rate_limits[i].disable()
end
else else
b_target.enable() b_target.enable()
c_target.enable() c_target.enable()
@ -257,9 +258,7 @@ local function new_view(root, x, y)
mode.enable() mode.enable()
if facility.auto_ready then start.enable() end if facility.auto_ready then start.enable() end
for i = 1, #rate_limits do for i = 1, #rate_limits do rate_limits[i].enable() end
rate_limits[i].enable()
end
end end
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 HorizontalBar = require("graphics.elements.indicators.hbar")
local StateIndicator = require("graphics.elements.indicators.state") local StateIndicator = require("graphics.elements.indicators.state")
local cpair = core.graphics.cpair local cpair = core.cpair
local border = core.graphics.border local border = core.border
-- create new reactor view -- create new reactor view
---@param root graphics_element parent ---@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 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} 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) status.register(ps, "computed_status", status.update)
ps.subscribe("temp", core_temp.update) core_temp.register(ps, "temp", core_temp.update)
ps.subscribe("act_burn_rate", burn_r.update) burn_r.register(ps, "act_burn_rate", burn_r.update)
ps.subscribe("heating_rate", heating_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} 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 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} 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 if type == types.FLUID.SODIUM then
ccool.recolor(cpair(colors.lightBlue, colors.gray)) ccool.recolor(cpair(colors.lightBlue, colors.gray))
else else
@ -55,7 +55,7 @@ local function new_view(root, x, y, ps)
end end
end) end)
ps.subscribe("hcool_type", function (type) hcool.register(ps, "hcool_type", function (type)
if type == types.FLUID.SUPERHEATED_SODIUM then if type == types.FLUID.SUPERHEATED_SODIUM then
hcool.recolor(cpair(colors.orange, colors.gray)) hcool.recolor(cpair(colors.orange, colors.gray))
else else
@ -63,10 +63,10 @@ local function new_view(root, x, y, ps)
end end
end) end)
ps.subscribe("fuel_fill", fuel.update) fuel.register(ps, "fuel_fill", fuel.update)
ps.subscribe("ccool_fill", ccool.update) ccool.register(ps, "ccool_fill", ccool.update)
ps.subscribe("hcool_fill", hcool.update) hcool.register(ps, "hcool_fill", hcool.update)
ps.subscribe("waste_fill", waste.update) waste.register(ps, "waste_fill", waste.update)
end end
return new_view return new_view

View File

@ -12,8 +12,8 @@ local PowerIndicator = require("graphics.elements.indicators.power")
local StateIndicator = require("graphics.elements.indicators.state") local StateIndicator = require("graphics.elements.indicators.state")
local VerticalBar = require("graphics.elements.indicators.vbar") local VerticalBar = require("graphics.elements.indicators.vbar")
local cpair = core.graphics.cpair local cpair = core.cpair
local border = core.graphics.border local border = core.border
-- new turbine view -- new turbine view
---@param root graphics_element parent ---@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 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} 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) status.register(ps, "computed_status", status.update)
ps.subscribe("prod_rate", function (val) prod_rate.update(util.joules_to_fe(val)) end) prod_rate.register(ps, "prod_rate", function (val) prod_rate.update(util.joules_to_fe(val)) end)
ps.subscribe("flow_rate", flow_rate.update) 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 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} 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="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} TextBox{parent=turbine,text="E",x=3,y=5,height=1,width=1,fg_bg=text_fg_bg}
ps.subscribe("steam_fill", steam.update) steam.register(ps, "steam_fill", steam.update)
ps.subscribe("energy_fill", energy.update) energy.register(ps, "energy_fill", energy.update)
end end
return new_view 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 RadioButton = require("graphics.elements.controls.radio_button")
local SpinboxNumeric = require("graphics.elements.controls.spinbox_numeric") 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 cpair = core.cpair
local border = core.graphics.border local border = core.border
local period = core.flasher.PERIOD 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} local core_map = CoreMap{parent=main,x=2,y=3,reactor_l=18,reactor_w=18}
u_ps.subscribe("temp", core_map.update) core_map.register(u_ps, "temp", core_map.update)
u_ps.subscribe("size", function (s) core_map.resize(s[1], s[2]) end) 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} 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} 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} 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} 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="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} 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 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} 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) fuel.register(u_ps, "fuel_fill", fuel.update)
u_ps.subscribe("ccool_fill", ccool.update) ccool.register(u_ps, "ccool_fill", ccool.update)
u_ps.subscribe("hcool_fill", hcool.update) hcool.register(u_ps, "hcool_fill", hcool.update)
u_ps.subscribe("waste_fill", waste.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 if type == "mekanism:sodium" then
ccool.recolor(cpair(colors.lightBlue, colors.gray)) ccool.recolor(cpair(colors.lightBlue, colors.gray))
else else
@ -115,7 +115,7 @@ local function init(parent, id)
end end
end) end)
u_ps.subscribe("hcool_type", function (type) hcool.register(u_ps, "hcool_type", function (type)
if type == "mekanism:superheated_sodium" then if type == "mekanism:superheated_sodium" then
hcool.recolor(cpair(colors.orange, colors.gray)) hcool.recolor(cpair(colors.orange, colors.gray))
else 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} 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} 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} 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} 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} 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} 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} 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} 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 -- -- 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_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)} 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) stat_line_1.register(u_ps, "U_StatusLine1", stat_line_1.set_value)
u_ps.subscribe("U_StatusLine2", stat_line_2.set_value) stat_line_2.register(u_ps, "U_StatusLine2", stat_line_2.set_value)
----------------- -----------------
-- annunciator -- -- 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 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} 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) plc_online.register(u_ps, "PLCOnline", plc_online.update)
u_ps.subscribe("PLCHeartbeat", plc_hbeat.update) plc_hbeat.register(u_ps, "PLCHeartbeat", plc_hbeat.update)
u_ps.subscribe("RadiationMonitor", rad_mon.update) rad_mon.register(u_ps, "RadiationMonitor", rad_mon.update)
annunciator.line_break() 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_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)} local r_auto = IndicatorLight{parent=annunciator,label="Automatic Control",colors=cpair(colors.white,colors.gray)}
u_ps.subscribe("status", r_active.update) r_active.register(u_ps, "status", r_active.update)
u_ps.subscribe("AutoControl", r_auto.update) r_auto.register(u_ps, "AutoControl", r_auto.update)
-- main unit transient/warning annunciator panel -- main unit transient/warning annunciator panel
local r_scram = IndicatorLight{parent=annunciator,label="Reactor SCRAM",colors=cpair(colors.red,colors.gray)} 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_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)} local r_hsrt = IndicatorLight{parent=annunciator,label="Startup Rate High",colors=cpair(colors.yellow,colors.gray)}
u_ps.subscribe("ReactorSCRAM", r_scram.update) r_scram.register(u_ps, "ReactorSCRAM", r_scram.update)
u_ps.subscribe("ManualReactorSCRAM", r_mscrm.update) r_mscrm.register(u_ps, "ManualReactorSCRAM", r_mscrm.update)
u_ps.subscribe("AutoReactorSCRAM", r_ascrm.update) r_ascrm.register(u_ps, "AutoReactorSCRAM", r_ascrm.update)
u_ps.subscribe("RadiationWarning", rad_wrn.update) rad_wrn.register(u_ps, "RadiationWarning", rad_wrn.update)
u_ps.subscribe("RCPTrip", r_rtrip.update) r_rtrip.register(u_ps, "RCPTrip", r_rtrip.update)
u_ps.subscribe("RCSFlowLow", r_cflow.update) r_cflow.register(u_ps, "RCSFlowLow", r_cflow.update)
u_ps.subscribe("CoolantLevelLow", r_clow.update) r_clow.register(u_ps, "CoolantLevelLow", r_clow.update)
u_ps.subscribe("ReactorTempHigh", r_temp.update) r_temp.register(u_ps, "ReactorTempHigh", r_temp.update)
u_ps.subscribe("ReactorHighDeltaT", r_rhdt.update) r_rhdt.register(u_ps, "ReactorHighDeltaT", r_rhdt.update)
u_ps.subscribe("FuelInputRateLow", r_firl.update) r_firl.register(u_ps, "FuelInputRateLow", r_firl.update)
u_ps.subscribe("WasteLineOcclusion", r_wloc.update) r_wloc.register(u_ps, "WasteLineOcclusion", r_wloc.update)
u_ps.subscribe("HighStartupRate", r_hsrt.update) r_hsrt.register(u_ps, "HighStartupRate", r_hsrt.update)
-- RPS annunciator panel -- 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_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} 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) rps_trp.register(u_ps, "rps_tripped", rps_trp.update)
u_ps.subscribe("high_dmg", rps_dmg.update) rps_dmg.register(u_ps, "high_dmg", rps_dmg.update)
u_ps.subscribe("ex_hcool", rps_exh.update) rps_exh.register(u_ps, "ex_hcool", rps_exh.update)
u_ps.subscribe("ex_waste", rps_exw.update) rps_exw.register(u_ps, "ex_waste", rps_exw.update)
u_ps.subscribe("high_temp", rps_tmp.update) rps_tmp.register(u_ps, "high_temp", rps_tmp.update)
u_ps.subscribe("no_fuel", rps_nof.update) rps_nof.register(u_ps, "no_fuel", rps_nof.update)
u_ps.subscribe("low_cool", rps_loc.update) rps_loc.register(u_ps, "low_cool", rps_loc.update)
u_ps.subscribe("fault", rps_flt.update) rps_flt.register(u_ps, "fault", rps_flt.update)
u_ps.subscribe("timeout", rps_tmo.update) rps_tmo.register(u_ps, "timeout", rps_tmo.update)
u_ps.subscribe("sys_fail", rps_sfl.update) rps_sfl.register(u_ps, "sys_fail", rps_sfl.update)
-- cooling annunciator panel -- 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_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)} 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) c_flt.register(u_ps, "RCSFault", c_flt.update)
u_ps.subscribe("EmergencyCoolant", c_emg.update) c_emg.register(u_ps, "EmergencyCoolant", c_emg.update)
u_ps.subscribe("CoolantFeedMismatch", c_cfm.update) c_cfm.register(u_ps, "CoolantFeedMismatch", c_cfm.update)
u_ps.subscribe("BoilRateMismatch", c_brm.update) c_brm.register(u_ps, "BoilRateMismatch", c_brm.update)
u_ps.subscribe("SteamFeedMismatch", c_sfm.update) c_sfm.register(u_ps, "SteamFeedMismatch", c_sfm.update)
u_ps.subscribe("MaxWaterReturnFeed", c_mwrf.update) c_mwrf.register(u_ps, "MaxWaterReturnFeed", c_mwrf.update)
local available_space = 16 - (unit.num_boilers * 2 + unit.num_turbines * 4) 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 if unit.num_boilers > 0 then
TextBox{parent=rcs_tags,x=1,text="B1",width=2,height=1,fg_bg=bw_fg_bg} 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)} 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} 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)} 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 end
if unit.num_boilers > 1 then if unit.num_boilers > 1 then
-- note, can't (shouldn't for sure...) have 0 turbines -- 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} 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)} 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} 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)} 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 end
-- turbine annunciator panels -- 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} 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} 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} 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)} 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} 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} 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} 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} 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 unit.num_turbines > 1 then
if (available_space > 2 and unit.num_turbines == 2) or available_space > 3 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} 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} 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} 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)} 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} 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} 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} 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} 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 end
if unit.num_turbines > 2 then 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} 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} 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} 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)} 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} 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} 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} 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} 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 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 = 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} 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) burn_rate.register(u_ps, "burn_rate", burn_rate.set_value)
u_ps.subscribe("max_burn", burn_rate.set_max) 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 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} 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
end end
u_ps.subscribe("status", start_button_en_check) start.register(u_ps, "status", start_button_en_check)
u_ps.subscribe("rps_tripped", start_button_en_check) start.register(u_ps, "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, "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} 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} 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} 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 -- -- 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_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} 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) a_brc.register(u_ps, "Alarm_1", a_brc.update)
u_ps.subscribe("Alarm_2", a_rad.update) a_rad.register(u_ps, "Alarm_2", a_rad.update)
u_ps.subscribe("Alarm_4", a_dmg.update) a_dmg.register(u_ps, "Alarm_4", a_dmg.update)
u_ps.subscribe("Alarm_3", a_rcl.update) a_rcl.register(u_ps, "Alarm_3", a_rcl.update)
u_ps.subscribe("Alarm_5", a_rcd.update) a_rcd.register(u_ps, "Alarm_5", a_rcd.update)
u_ps.subscribe("Alarm_6", a_rot.update) a_rot.register(u_ps, "Alarm_6", a_rot.update)
u_ps.subscribe("Alarm_7", a_rht.update) a_rht.register(u_ps, "Alarm_7", a_rht.update)
u_ps.subscribe("Alarm_8", a_rwl.update) a_rwl.register(u_ps, "Alarm_8", a_rwl.update)
u_ps.subscribe("Alarm_9", a_rwh.update) a_rwh.register(u_ps, "Alarm_9", a_rwh.update)
u_ps.subscribe("Alarm_10", a_rps.update) a_rps.register(u_ps, "Alarm_10", a_rps.update)
u_ps.subscribe("Alarm_11", a_clt.update) a_clt.register(u_ps, "Alarm_11", a_clt.update)
u_ps.subscribe("Alarm_12", a_tbt.update) a_tbt.register(u_ps, "Alarm_12", a_tbt.update)
-- ack's and resets -- 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} 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() 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} 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} 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() auto_div.line_break()
local a_rdy = IndicatorLight{parent=auto_div,label="Ready",x=2,colors=cpair(colors.green,colors.gray)} 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} 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 -- 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)) a_stb.update(unit.annunciator.AutoControl and (not active))
end) end)
a_stb.register(u_ps, "AutoControl", function (auto_active)
-- 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()
if auto_active then if auto_active then
a_stb.update(unit.reactor_data.mek_status.status == false) a_stb.update(unit.reactor_data.mek_status.status == false)
else a_stb.update(false) end else a_stb.update(false) end
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 -- 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 if auto_active then set_grp_btn.disable() else set_grp_btn.enable() end
end) end)

View File

@ -14,9 +14,9 @@ local Div = require("graphics.elements.div")
local PipeNetwork = require("graphics.elements.pipenet") local PipeNetwork = require("graphics.elements.pipenet")
local TextBox = require("graphics.elements.textbox") 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 -- make a new unit overview window
---@param parent graphics_element parent ---@param parent graphics_element parent
@ -38,7 +38,7 @@ local function make(parent, x, y, unit)
height = 17 height = 17
end end
assert(parent.height() >= (y + height), "main display not of sufficient vertical resolution (add an additional row of monitors)") assert(parent.get_height() >= (y + height), "main display not of sufficient vertical resolution (add an additional row of monitors)")
-- bounding box div -- bounding box div
local root = Div{parent=parent,x=x,y=y,width=80,height=height} local root = Div{parent=parent,x=x,y=y,width=80,height=height}

View File

@ -18,9 +18,9 @@ local TextBox = require("graphics.elements.textbox")
local DataIndicator = require("graphics.elements.indicators.data") 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 -- create new main view
---@param main graphics_element main displaybox ---@param main graphics_element main displaybox
@ -32,10 +32,10 @@ local function init(main)
local header = TextBox{parent=main,y=1,text="Nuclear Generation Facility SCADA Coordinator",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header} local header = TextBox{parent=main,y=1,text="Nuclear Generation Facility SCADA Coordinator",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header}
local ping = DataIndicator{parent=main,x=1,y=1,label="SVTT",format="%d",value=0,unit="ms",lu_colors=cpair(colors.lightGray, colors.white),width=12,fg_bg=style.header} local ping = DataIndicator{parent=main,x=1,y=1,label="SVTT",format="%d",value=0,unit="ms",lu_colors=cpair(colors.lightGray, colors.white),width=12,fg_bg=style.header}
-- max length example: "01:23:45 AM - Wednesday, September 28 2022" -- 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} local datetime = TextBox{parent=main,x=(header.get_width()-42),y=1,text="",alignment=TEXT_ALIGN.RIGHT,width=42,height=1,fg_bg=style.header}
facility.ps.subscribe("sv_ping", ping.update) ping.register(facility.ps, "sv_ping", ping.update)
facility.ps.subscribe("date_time", datetime.set_value) datetime.register(facility.ps, "date_time", datetime.set_value)
local uo_1, uo_2, uo_3, uo_4 ---@type graphics_element local uo_1, uo_2, uo_3, uo_4 ---@type graphics_element
@ -45,12 +45,12 @@ local function init(main)
-- unit overviews -- unit overviews
if facility.num_units >= 1 then if facility.num_units >= 1 then
uo_1 = unit_overview(main, 2, 3, units[1]) uo_1 = unit_overview(main, 2, 3, units[1])
row_1_height = uo_1.height() row_1_height = uo_1.get_height()
end end
if facility.num_units >= 2 then if facility.num_units >= 2 then
uo_2 = unit_overview(main, 84, 3, units[2]) uo_2 = unit_overview(main, 84, 3, units[2])
row_1_height = math.max(row_1_height, uo_2.height()) row_1_height = math.max(row_1_height, uo_2.get_height())
end end
cnc_y_start = cnc_y_start + row_1_height + 1 cnc_y_start = cnc_y_start + row_1_height + 1
@ -60,11 +60,11 @@ local function init(main)
local row_2_offset = cnc_y_start local row_2_offset = cnc_y_start
uo_3 = unit_overview(main, 2, row_2_offset, units[3]) uo_3 = unit_overview(main, 2, row_2_offset, units[3])
cnc_y_start = row_2_offset + uo_3.height() + 1 cnc_y_start = row_2_offset + uo_3.get_height() + 1
if facility.num_units == 4 then if facility.num_units == 4 then
uo_4 = unit_overview(main, 84, row_2_offset, units[4]) uo_4 = unit_overview(main, 84, row_2_offset, units[4])
cnc_y_start = math.max(cnc_y_start, row_2_offset + uo_4.height() + 1) cnc_y_start = math.max(cnc_y_start, row_2_offset + uo_4.get_height() + 1)
end end
end end
@ -73,11 +73,11 @@ local function init(main)
cnc_y_start = cnc_y_start cnc_y_start = cnc_y_start
-- induction matrix and process control interfaces are 24 tall + space needed for divider -- induction matrix and process control interfaces are 24 tall + space needed for divider
local cnc_bottom_align_start = main.height() - 26 local cnc_bottom_align_start = main.get_height() - 26
assert(cnc_bottom_align_start >= cnc_y_start, "main display not of sufficient vertical resolution (add an additional row of monitors)") assert(cnc_bottom_align_start >= cnc_y_start, "main display not of sufficient vertical resolution (add an additional row of monitors)")
TextBox{parent=main,y=cnc_bottom_align_start,text=util.strrep("\x8c", header.width()),alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=cpair(colors.lightGray,colors.gray)} TextBox{parent=main,y=cnc_bottom_align_start,text=util.strrep("\x8c", header.get_width()),alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=cpair(colors.lightGray,colors.gray)}
cnc_bottom_align_start = cnc_bottom_align_start + 2 cnc_bottom_align_start = cnc_bottom_align_start + 2

View File

@ -6,7 +6,7 @@ local core = require("graphics.core")
local style = {} local style = {}
local cpair = core.graphics.cpair local cpair = core.cpair
-- GLOBAL -- -- 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 core = {}
local flasher = require("graphics.flasher")
core.flasher = 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 core.events = events
local graphics = {} -- Core Types
---@enum TEXT_ALIGN ---@enum TEXT_ALIGN
graphics.TEXT_ALIGN = { core.TEXT_ALIGN = {
LEFT = 1, LEFT = 1,
CENTER = 2, CENTER = 2,
RIGHT = 3 RIGHT = 3
@ -109,7 +32,7 @@ graphics.TEXT_ALIGN = {
---@param color color border color ---@param color color border color
---@param even? boolean whether to pad width extra to account for rectangular pixels, defaults to false ---@param even? boolean whether to pad width extra to account for rectangular pixels, defaults to false
---@return graphics_border ---@return graphics_border
function graphics.border(width, color, even) function core.border(width, color, even)
return { return {
width = width, width = width,
color = color, color = color,
@ -130,7 +53,7 @@ end
---@param w integer ---@param w integer
---@param h integer ---@param h integer
---@return graphics_frame ---@return graphics_frame
function graphics.gframe(x, y, w, h) function core.gframe(x, y, w, h)
return { return {
x = x, x = x,
y = y, y = y,
@ -154,7 +77,7 @@ end
---@param a color ---@param a color
---@param b color ---@param b color
---@return cpair ---@return cpair
function graphics.cpair(a, b) function core.cpair(a, b)
return { return {
-- color pairs -- color pairs
color_a = a, color_a = a,
@ -191,7 +114,7 @@ end
---@param thin? boolean true for 1 subpixel, false (default) for 2 ---@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 ---@param align_tr? boolean false to align bottom left (default), true to align top right
---@return pipe ---@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 { return {
x1 = x1, x1 = x1,
y1 = y1, y1 = y1,
@ -205,6 +128,4 @@ function graphics.pipe(x1, y1, x2, y2, color, thin, align_tr)
} }
end end
core.graphics = graphics
return core return core

View File

@ -12,12 +12,11 @@ local element = {}
---@field id? string element id ---@field id? string element id
---@field x? integer 1 if omitted ---@field x? integer 1 if omitted
---@field y? integer next line if omitted ---@field y? integer next line if omitted
---@field offset_x? integer 0 if omitted
---@field offset_y? integer 0 if omitted
---@field width? integer parent width if omitted ---@field width? integer parent width if omitted
---@field height? integer parent height if omitted ---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height ---@field gframe? graphics_frame frame instead of x/y/width/height
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
---@alias graphics_args graphics_args_generic ---@alias graphics_args graphics_args_generic
---|waiting_args ---|waiting_args
@ -28,6 +27,7 @@ local element = {}
---|sidebar_args ---|sidebar_args
---|spinbox_args ---|spinbox_args
---|switch_button_args ---|switch_button_args
---|tabbar_args
---|alarm_indicator_light ---|alarm_indicator_light
---|core_map_args ---|core_map_args
---|data_indicator_args ---|data_indicator_args
@ -45,38 +45,47 @@ local element = {}
---|colormap_args ---|colormap_args
---|displaybox_args ---|displaybox_args
---|div_args ---|div_args
---|listbox_args
---|multipane_args ---|multipane_args
---|pipenet_args ---|pipenet_args
---|rectangle_args ---|rectangle_args
---|textbox_args ---|textbox_args
---|tiling_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 -- a base graphics element, should not be created on its own
---@nodiscard ---@nodiscard
---@param args graphics_args arguments ---@param args graphics_args arguments
function element.new(args) function element.new(args)
local self = { local self = {
id = -1, id = nil, ---@type element_id|nil
elem_type = debug.getinfo(2).name, elem_type = debug.getinfo(2).name,
define_completed = false, define_completed = false,
p_window = nil, ---@type table p_window = nil, ---@type table
position = { x = 1, y = 1 }, position = { x = 1, y = 1 }, ---@type coordinate_2d
child_offset = { x = 0, y = 0 }, bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1 }, ---@class element_bounds
bounds = { x1 = 1, y1 = 1, x2 = 1, y2 = 1},
next_y = 1, next_y = 1,
children = {}, subscriptions = {},
mt = {} mt = {}
} }
---@class graphics_template ---@class graphics_base
local protected = { local protected = {
enabled = true, enabled = true,
value = nil, ---@type any value = nil, ---@type any
window = nil, ---@type table window = nil, ---@type table
fg_bg = core.graphics.cpair(colors.white, colors.black), content_window = nil, ---@type table|nil
frame = core.graphics.gframe(1, 1, 1, 1) fg_bg = core.cpair(colors.white, colors.black),
frame = core.gframe(1, 1, 1, 1),
children = {}
} }
local name_brief = "graphics.element{" .. self.elem_type .. "}: "
-- element as string -- element as string
function self.mt.__tostring() function self.mt.__tostring()
return "graphics.element{" .. self.elem_type .. "} @ " .. tostring(self) return "graphics.element{" .. self.elem_type .. "} @ " .. tostring(self)
@ -92,10 +101,8 @@ function element.new(args)
------------------------- -------------------------
-- prepare the template -- prepare the template
---@param offset_x integer x offset
---@param offset_y integer y offset
---@param next_y integer next line if no y was provided ---@param next_y integer next line if no y was provided
function protected.prepare_template(offset_x, offset_y, next_y) function protected.prepare_template(next_y)
-- get frame coordinates/size -- get frame coordinates/size
if args.gframe ~= nil then if args.gframe ~= nil then
protected.frame.x = args.gframe.x protected.frame.x = args.gframe.x
@ -105,46 +112,28 @@ function element.new(args)
else else
local w, h = self.p_window.getSize() local w, h = self.p_window.getSize()
protected.frame.x = args.x or 1 protected.frame.x = args.x or 1
if args.parent ~= nil then
protected.frame.y = args.y or (next_y - offset_y)
else
protected.frame.y = args.y or next_y protected.frame.y = args.y or next_y
end
protected.frame.w = args.width or w protected.frame.w = args.width or w
protected.frame.h = args.height or h protected.frame.h = args.height or h
end end
-- inner offsets
if args.offset_x ~= nil then self.child_offset.x = args.offset_x end
if args.offset_y ~= nil then self.child_offset.y = args.offset_y end
-- adjust window frame if applicable -- adjust window frame if applicable
local f = protected.frame local f = protected.frame
local x = f.x
local y = f.y
-- apply offsets
if args.parent ~= nil then if args.parent ~= nil then
-- constrain to parent inner width/height -- constrain to parent inner width/height
local w, h = self.p_window.getSize() local w, h = self.p_window.getSize()
f.w = math.min(f.w, w - ((2 * offset_x) + (f.x - 1))) f.w = math.min(f.w, w - (f.x - 1))
f.h = math.min(f.h, h - ((2 * offset_y) + (f.y - 1))) f.h = math.min(f.h, h - (f.y - 1))
-- offset x/y
f.x = x + offset_x
f.y = y + offset_y
end end
-- check frame -- check frame
assert(f.x >= 1, "graphics.element{" .. self.elem_type .. "}: frame x not >= 1") assert(f.x >= 1, name_brief .. "frame x not >= 1")
assert(f.y >= 1, "graphics.element{" .. self.elem_type .. "}: frame y not >= 1") assert(f.y >= 1, name_brief .. "frame y not >= 1")
assert(f.w >= 1, "graphics.element{" .. self.elem_type .. "}: frame width not >= 1") assert(f.w >= 1, name_brief .. "frame width not >= 1")
assert(f.h >= 1, "graphics.element{" .. self.elem_type .. "}: frame height not >= 1") assert(f.h >= 1, name_brief .. "frame height not >= 1")
-- create window -- create window
protected.window = window.create(self.p_window, f.x, f.y, f.w, f.h, true) protected.window = window.create(self.p_window, f.x, f.y, f.w, f.h, args.hidden ~= true)
-- init colors -- init colors
if args.fg_bg ~= nil then if args.fg_bg ~= nil then
@ -168,9 +157,38 @@ function element.new(args)
self.bounds.y2 = self.position.y + f.h - 1 self.bounds.y2 = self.position.y + f.h - 1
end end
-- check if a coordinate relative to the parent is within the bounds of this element
---@param x integer
---@param y integer
function protected.in_window_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
-- check if a coordinate relative to this window is within the bounds of this element
---@param x integer
---@param y integer
function protected.in_frame_bounds(x, y)
local in_x = x >= 1 and x <= protected.frame.w
local in_y = y >= 1 and y <= protected.frame.h
return in_x and in_y
end
-- luacheck: push ignore -- luacheck: push ignore
---@diagnostic disable: unused-local, unused-vararg ---@diagnostic disable: unused-local, unused-vararg
-- handle a child element having been added
---@param id element_id element identifier
---@param child graphics_element child element
function protected.on_added(id, child)
end
-- handle a child element having been removed
---@param id element_id element identifier
function protected.on_removed(id)
end
-- handle a mouse event -- handle a mouse event
---@param event mouse_interaction mouse interaction event ---@param event mouse_interaction mouse interaction event
function protected.handle_mouse(event) function protected.handle_mouse(event)
@ -241,6 +259,14 @@ function element.new(args)
---@return graphics_element element, element_id id ---@return graphics_element element, element_id id
function protected.get() return public, self.id end function protected.get() return public, self.id end
-- report completion of element instantiation and get the public interface
---@nodiscard
---@return graphics_element element, element_id id
function protected.complete()
if args.parent ~= nil then args.parent.__child_ready(self.id, public) end
return public, self.id
end
----------- -----------
-- SETUP -- -- SETUP --
----------- -----------
@ -252,11 +278,12 @@ function element.new(args)
end end
-- check window -- 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 -- prepare the template
if args.parent == nil then if args.parent == nil then
protected.prepare_template(0, 0, 1) self.id = args.id or "__ROOT__"
protected.prepare_template(1)
else else
self.id = args.parent.__add_child(args.id, protected) self.id = args.parent.__add_child(args.id, protected)
end end
@ -267,57 +294,108 @@ function element.new(args)
-- get the window object -- get the window object
---@nodiscard ---@nodiscard
function public.window() return protected.window end function public.window() return protected.content_window or protected.window end
-- CHILD ELEMENTS -- -- delete this element (hide and unsubscribe from PSIL)
function public.delete()
local fg_bg = protected.fg_bg
if args.parent ~= nil then
-- grab parent fg/bg so we can clear cleanly as a child element
fg_bg = args.parent.get_fg_bg()
end
-- clear, hide, and stop animations
protected.window.setBackgroundColor(fg_bg.bkg)
protected.window.setTextColor(fg_bg.fgd)
protected.window.clear()
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(protected.children) do
v.delete()
protected.children[k] = nil
end
if args.parent ~= nil then
-- remove self from parent
args.parent.__remove_child(self.id)
end
end
-- ELEMENT TREE --
-- add a child element -- add a child element
---@nodiscard ---@nodiscard
---@param key string|nil id ---@param key string|nil id
---@param child graphics_template ---@param child graphics_base
---@return integer|string key ---@return integer|string key
function public.__add_child(key, child) function public.__add_child(key, child)
-- offset first automatic placement child.prepare_template(self.next_y)
if self.next_y <= self.child_offset.y then
self.next_y = self.child_offset.y + 1
end
child.prepare_template(self.child_offset.x, self.child_offset.y, self.next_y)
self.next_y = child.frame.y + child.frame.h self.next_y = child.frame.y + child.frame.h
local child_element = child.get() local child_element = child.get()
if key == nil then if key == nil then
table.insert(self.children, child_element) table.insert(protected.children, child_element)
return #self.children return #protected.children
else else
self.children[key] = child_element protected.children[key] = child_element
return key return key
end end
end end
-- remove a child element
---@param key element_id id
function public.__remove_child(key)
if protected.children[key] ~= nil then
protected.on_removed(key)
protected.children[key] = nil
end
end
-- actions to take upon a child element becoming ready (initial draw/construction completed)
---@param key element_id id
---@param child graphics_element
function public.__child_ready(key, child)
protected.on_added(key, child)
end
-- get a child element -- get a child element
---@nodiscard ---@nodiscard
---@param id element_id
---@return graphics_element ---@return graphics_element
function public.get_child(key) return self.children[key] end function public.get_child(id) return protected.children[id] end
-- remove child -- remove a child element
---@param key string|integer ---@param id element_id
function public.remove(key) self.children[key] = nil end function public.remove(id)
if protected.children[id] ~= nil then
protected.children[id].delete()
protected.on_removed(id)
protected.children[id] = nil
end
end
-- attempt to get a child element by ID (does not include this element itself) -- attempt to get a child element by ID (does not include this element itself)
---@nodiscard ---@nodiscard
---@param id element_id ---@param id element_id
---@return graphics_element|nil element ---@return graphics_element|nil element
function public.get_element_by_id(id) function public.get_element_by_id(id)
if self.children[id] == nil then if protected.children[id] == nil then
for _, child in pairs(self.children) do for _, child in pairs(protected.children) do
local elem = child.get_element_by_id(id) local elem = child.get_element_by_id(id)
if elem ~= nil then return elem end if elem ~= nil then return elem end
end end
else else
return self.children[id] return protected.children[id]
end end
return nil return nil
@ -356,14 +434,14 @@ function element.new(args)
-- get element width -- get element width
---@nodiscard ---@nodiscard
---@return integer width ---@return integer width
function public.width() function public.get_width()
return protected.frame.w return protected.frame.w
end end
-- get element height -- get element height
---@nodiscard ---@nodiscard
---@return integer height ---@return integer height
function public.height() function public.get_height()
return protected.frame.h return protected.frame.h
end end
@ -416,22 +494,29 @@ function element.new(args)
protected.resize(...) protected.resize(...)
end 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 -- -- FUNCTION CALLBACKS --
-- handle a monitor touch or mouse click -- handle a monitor touch or mouse click
---@param event mouse_interaction mouse interaction event ---@param event mouse_interaction mouse interaction event
function public.handle_mouse(event) function public.handle_mouse(event)
local in_x = event.x >= self.bounds.x1 and event.x <= self.bounds.x2 local x_ini, y_ini = event.initial.x, event.initial.y
local in_y = event.y >= self.bounds.y1 and event.y <= self.bounds.y2
if in_x and in_y then local ini_in = protected.in_window_bounds(x_ini, y_ini)
local event_T = core.events.mouse_transposed(event, (event.x - self.position.x) + 1, (event.y - self.position.y) + 1)
-- handle the touch event, transformed into the window frame 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) protected.handle_mouse(event_T)
for _, child in pairs(protected.children) do child.handle_mouse(event_T) end
-- pass on touch event to children
for _, val in pairs(self.children) do val.handle_mouse(event_T) end
end end
end end
@ -447,27 +532,71 @@ function element.new(args)
protected.response_callback(result) protected.response_callback(result)
end end
-- VISIBILITY -- -- register a callback with a PSIL, allowing for automatic unregister on delete<br>
-- do not use graphics elements directly with PSIL subscribe()
-- show the element ---@param ps psil PSIL to subscribe to
function public.show() ---@param key string key to subscribe to
protected.window.setVisible(true) ---@param func function function to link
protected.start_anim() function public.register(ps, key, func)
for _, child in pairs(self.children) do child.show() end table.insert(self.subscriptions, { ps = ps, key = key, func = func })
ps.subscribe(key, func)
end end
-- hide the element -- VISIBILITY & ANIMATIONS --
-- show the element and enables animations by default
---@param animate? boolean true (default) to automatically resume animations
function public.show(animate)
protected.window.setVisible(true)
if animate ~= false then public.animate_all() end
end
-- hide the element and disables animations<br>
-- this alone does not cause an element to be fully hidden, it only prevents updates from being shown<br>
---@see graphics_element.content_redraw
function public.hide() function public.hide()
protected.stop_anim() public.freeze_all() -- stop animations for efficiency/performance
for _, child in pairs(self.children) do child.hide() end
protected.window.setVisible(false) protected.window.setVisible(false)
end end
-- start/resume animation(s)
function public.animate()
protected.start_anim()
end
-- start/resume animation(s) for this element and all its children<br>
-- only animates if a window is visible
function public.animate_all()
if protected.window.isVisible() then
public.animate()
for _, child in pairs(protected.children) do child.animate_all() end
end
end
-- freeze animation(s)
function public.freeze()
protected.stop_anim()
end
-- freeze animation(s) for this element and all its children
function public.freeze_all()
public.freeze()
for _, child in pairs(protected.children) do child.freeze_all() end
end
-- re-draw the element -- re-draw the element
function public.redraw() function public.redraw()
protected.window.redraw() protected.window.redraw()
end end
-- if a content window is set, clears it then re-draws all children
function public.content_redraw()
if protected.content_window ~= nil then
protected.content_window.clear()
for _, child in pairs(protected.children) do child.redraw() end
end
end
return protected return protected
end end

View File

@ -1,6 +1,6 @@
-- Loading/Waiting Animation Graphics Element -- Loading/Waiting Animation Graphics Element
local tcd = require("scada-common.tcallbackdsp") local tcd = require("scada-common.tcd")
local element = require("graphics.element") local element = require("graphics.element")
@ -10,6 +10,7 @@ local element = require("graphics.element")
---@field x? integer 1 if omitted ---@field x? integer 1 if omitted
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new waiting animation element -- new waiting animation element
---@param args waiting_args ---@param args waiting_args
@ -102,7 +103,7 @@ local function waiting(args)
e.start_anim() e.start_anim()
return e.get() return e.complete()
end end
return waiting return waiting

View File

@ -9,6 +9,7 @@ local element = require("graphics.element")
---@field id? string element id ---@field id? string element id
---@field x? integer 1 if omitted ---@field x? integer 1 if omitted
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field hidden? boolean true to hide on initial draw
-- new color map -- new color map
---@param args colormap_args ---@param args colormap_args
@ -27,7 +28,7 @@ local function colormap(args)
e.window.setCursorPos(1, 1) e.window.setCursorPos(1, 1)
e.window.blit(spaces, bkg, bkg) e.window.blit(spaces, bkg, bkg)
return e.get() return e.complete()
end end
return colormap return colormap

View File

@ -1,6 +1,6 @@
-- Hazard-bordered Button Graphics Element -- Hazard-bordered Button Graphics Element
local tcd = require("scada-common.tcallbackdsp") local tcd = require("scada-common.tcd")
local util = require("scada-common.util") local util = require("scada-common.util")
local core = require("graphics.core") local core = require("graphics.core")
@ -16,6 +16,7 @@ local element = require("graphics.element")
---@field x? integer 1 if omitted ---@field x? integer 1 if omitted
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new hazard button -- new hazard button
---@param args hazard_button_args ---@param args hazard_button_args
@ -141,8 +142,10 @@ local function hazard_button(args)
end end
-- handle mouse interaction -- handle mouse interaction
function e.handle_mouse(_) ---@param event mouse_interaction mouse event
function e.handle_mouse(event)
if e.enabled then if e.enabled then
if core.events.was_clicked(event.type) then
-- change text color to indicate clicked -- change text color to indicate clicked
e.window.setTextColor(args.accent) e.window.setTextColor(args.accent)
e.window.setCursorPos(3, 2) e.window.setCursorPos(3, 2)
@ -160,23 +163,19 @@ local function hazard_button(args)
args.callback() args.callback()
end end
end end
end
-- callback on request response -- callback on request response
---@param result boolean true for success, false for failure ---@param result boolean true for success, false for failure
function e.response_callback(result) function e.response_callback(result)
tcd.abort(on_timeout) 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 end
-- set the value (true simulates pressing the button) -- set the value (true simulates pressing the button)
---@param val boolean new value ---@param val boolean new value
function e.set_value(val) 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 end
-- show the button as disabled -- show the button as disabled
@ -200,7 +199,7 @@ local function hazard_button(args)
-- initial draw of border -- initial draw of border
draw_border(args.accent) draw_border(args.accent)
return e.get() return e.complete()
end end
return hazard_button return hazard_button

View File

@ -2,13 +2,13 @@
local util = require("scada-common.util") local util = require("scada-common.util")
local core = require("graphics.core")
local element = require("graphics.element") local element = require("graphics.element")
---@class button_option ---@class button_option
---@field text string ---@field text string
---@field fg_bg cpair ---@field fg_bg cpair
---@field active_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 _start_x integer starting touch x range (inclusive)
---@field _end_x integer ending touch x range (inclusive) ---@field _end_x integer ending touch x range (inclusive)
@ -23,6 +23,7 @@ local element = require("graphics.element")
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field height? integer parent height if omitted ---@field height? integer parent height if omitted
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new multi button (latch selection, exclusively one button at a time) -- new multi button (latch selection, exclusively one button at a time)
---@param args multi_button_args ---@param args multi_button_args
@ -62,9 +63,7 @@ local function multi_button(args)
local next_x = 2 local next_x = 2
for i = 1, #args.options do for i = 1, #args.options do
local opt = args.options[i] ---@type button_option 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._start_x = next_x
opt._end_x = next_x + button_width - 1 opt._end_x = next_x + button_width - 1
@ -92,23 +91,35 @@ local function multi_button(args)
end end
end end
-- handle mouse interaction -- check which button a given x is within
---@param event mouse_interaction mouse event ---@return integer|nil button index or nil if not within a button
---@diagnostic disable-next-line: unused-local local function which_button(x)
function e.handle_mouse(event)
-- determine what was pressed
if e.enabled and event.y == 1 then
for i = 1, #args.options do for i = 1, #args.options do
local opt = args.options[i] ---@type button_option local opt = args.options[i] ---@type button_option
if x >= opt._start_x and x <= opt._end_x then return i end
end
if event.x >= opt._start_x and event.x <= opt._end_x then return nil
e.value = i end
-- handle mouse interaction
---@param event mouse_interaction mouse event
function e.handle_mouse(event)
-- 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)
-- 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() draw()
args.callback(e.value) args.callback(e.value)
end end
end end
end end
end
-- set the value -- set the value
---@param val integer new value ---@param val integer new value
@ -120,7 +131,7 @@ local function multi_button(args)
-- initial draw -- initial draw
draw() draw()
return e.get() return e.complete()
end end
return multi_button return multi_button

View File

@ -1,10 +1,12 @@
-- Button Graphics Element -- Button Graphics Element
local tcd = require("scada-common.tcallbackdsp") local tcd = require("scada-common.tcd")
local core = require("graphics.core") local core = require("graphics.core")
local element = require("graphics.element") local element = require("graphics.element")
local CLICK_TYPE = core.events.CLICK_TYPE
---@class push_button_args ---@class push_button_args
---@field text string button text ---@field text string button text
---@field callback function function to call on touch ---@field callback function function to call on touch
@ -17,6 +19,7 @@ local element = require("graphics.element")
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field height? integer parent height if omitted ---@field height? integer parent height if omitted
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new push button -- new push button
---@param args push_button_args ---@param args push_button_args
@ -24,6 +27,8 @@ local element = require("graphics.element")
local function push_button(args) local function push_button(args)
assert(type(args.text) == "string", "graphics.elements.controls.push_button: text is a required field") 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.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) local text_width = string.len(args.text)
@ -47,36 +52,50 @@ local function push_button(args)
e.window.write(args.text) e.window.write(args.text)
end end
-- handle mouse interaction -- draw the button as pressed (if active_fg_bg set)
function e.handle_mouse(_) local function show_pressed()
if e.enabled then if e.enabled and args.active_fg_bg ~= nil then
if args.active_fg_bg ~= nil then
-- show as pressed
e.value = true e.value = true
e.window.setTextColor(args.active_fg_bg.fgd) e.window.setTextColor(args.active_fg_bg.fgd)
e.window.setBackgroundColor(args.active_fg_bg.bkg) e.window.setBackgroundColor(args.active_fg_bg.bkg)
draw() draw()
end
end
-- show as unpressed in 0.25 seconds -- draw the button as unpressed (if active_fg_bg set)
tcd.dispatch(0.25, function () local function show_unpressed()
if e.enabled and args.active_fg_bg ~= nil then
e.value = false e.value = false
if e.enabled then
e.window.setTextColor(e.fg_bg.fgd) e.window.setTextColor(e.fg_bg.fgd)
e.window.setBackgroundColor(e.fg_bg.bkg) e.window.setBackgroundColor(e.fg_bg.bkg)
end
draw() draw()
end) end
end end
-- call the touch callback -- handle mouse interaction
---@param event mouse_interaction mouse event
function e.handle_mouse(event)
if e.enabled then
if event.type == CLICK_TYPE.TAP then
show_pressed()
-- show as unpressed in 0.25 seconds
if args.active_fg_bg ~= nil then tcd.dispatch(0.25, show_unpressed) end
args.callback() args.callback()
elseif event.type == CLICK_TYPE.DOWN then
show_pressed()
elseif event.type == CLICK_TYPE.UP then
show_unpressed()
if e.in_frame_bounds(event.current.x, event.current.y) then
args.callback()
end
end
end end
end end
-- set the value (true simulates pressing the button) -- set the value (true simulates pressing the button)
---@param val boolean new value ---@param val boolean new value
function e.set_value(val) 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 end
-- show butten as enabled -- show butten as enabled
@ -102,7 +121,7 @@ local function push_button(args)
-- initial draw -- initial draw
draw() draw()
return e.get() return e.complete()
end end
return push_button return push_button

View File

@ -1,5 +1,6 @@
-- Radio Button Graphics Element -- Radio Button Graphics Element
local core = require("graphics.core")
local element = require("graphics.element") local element = require("graphics.element")
---@class radio_button_args ---@class radio_button_args
@ -14,6 +15,7 @@ local element = require("graphics.element")
---@field x? integer 1 if omitted ---@field x? integer 1 if omitted
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new radio button list (latch selection, exclusively one button at a time) -- new radio button list (latch selection, exclusively one button at a time)
---@param args radio_button_args ---@param args radio_button_args
@ -82,10 +84,10 @@ local function radio_button(args)
-- handle mouse interaction -- handle mouse interaction
---@param event mouse_interaction mouse event ---@param event mouse_interaction mouse event
function e.handle_mouse(event) function e.handle_mouse(event)
if e.enabled and core.events.was_clicked(event.type) and (event.initial.y == event.current.y) then
-- determine what was pressed -- determine what was pressed
if e.enabled then if args.options[event.current.y] ~= nil then
if args.options[event.y] ~= nil then e.value = event.current.y
e.value = event.y
draw() draw()
args.callback(e.value) args.callback(e.value)
end end
@ -102,7 +104,7 @@ local function radio_button(args)
-- initial draw -- initial draw
draw() draw()
return e.get() return e.complete()
end end
return radio_button return radio_button

View File

@ -1,9 +1,12 @@
-- Sidebar Graphics Element -- Sidebar Graphics Element
local tcd = require("scada-common.tcallbackdsp") local tcd = require("scada-common.tcd")
local core = require("graphics.core")
local element = require("graphics.element") local element = require("graphics.element")
local CLICK_TYPE = core.events.CLICK_TYPE
---@class sidebar_tab ---@class sidebar_tab
---@field char string character identifier ---@field char string character identifier
---@field color cpair tab colors (fg/bg) ---@field color cpair tab colors (fg/bg)
@ -17,6 +20,7 @@ local element = require("graphics.element")
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field height? integer parent height if omitted ---@field height? integer parent height if omitted
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new sidebar tab selector -- new sidebar tab selector
---@param args sidebar_args ---@param args sidebar_args
@ -39,7 +43,10 @@ local function sidebar(args)
-- show the button state -- show the button state
---@param pressed boolean if the currently selected tab should appear as actively pressed ---@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 for i = 1, #args.tabs do
local tab = args.tabs[i] ---@type sidebar_tab local tab = args.tabs[i] ---@type sidebar_tab
@ -47,7 +54,7 @@ local function sidebar(args)
e.window.setCursorPos(1, y) 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.setTextColor(e.fg_bg.fgd)
e.window.setBackgroundColor(e.fg_bg.bkg) e.window.setBackgroundColor(e.fg_bg.bkg)
else else
@ -74,16 +81,27 @@ local function sidebar(args)
function e.handle_mouse(event) function e.handle_mouse(event)
-- determine what was pressed -- determine what was pressed
if e.enabled then 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 if args.tabs[cur_idx] ~= nil then
e.value = idx if event.type == CLICK_TYPE.TAP then
e.value = cur_idx
draw(true) draw(true)
-- show as unpressed in 0.25 seconds -- show as unpressed in 0.25 seconds
tcd.dispatch(0.25, function () draw(false) end) tcd.dispatch(0.25, function () draw(false) end)
args.callback(e.value) 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_frame_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 end
end end
@ -98,7 +116,7 @@ local function sidebar(args)
-- initial draw -- initial draw
draw(false) draw(false)
return e.get() return e.complete()
end end
return sidebar return sidebar

View File

@ -2,6 +2,7 @@
local util = require("scada-common.util") local util = require("scada-common.util")
local core = require("graphics.core")
local element = require("graphics.element") local element = require("graphics.element")
---@class spinbox_args ---@class spinbox_args
@ -17,6 +18,7 @@ local element = require("graphics.element")
---@field x? integer 1 if omitted ---@field x? integer 1 if omitted
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new spinbox control (minimum value is 0) -- new spinbox control (minimum value is 0)
---@param args spinbox_args ---@param args spinbox_args
@ -130,13 +132,15 @@ local function spinbox(args)
---@param event mouse_interaction mouse event ---@param event mouse_interaction mouse event
function e.handle_mouse(event) function e.handle_mouse(event)
-- only handle if on an increment or decrement arrow -- only handle if on an increment or decrement arrow
if e.enabled and event.x ~= dec_point_x then if e.enabled and core.events.was_clicked(event.type) and
local idx = util.trinary(event.x > dec_point_x, event.x - 1, event.x) (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 digits[idx] ~= nil then
if event.y == 1 then if event.current.y == 1 then
-- increment -- increment
digits[idx] = digits[idx] + 1 digits[idx] = digits[idx] + 1
elseif event.y == 3 then elseif event.current.y == 3 then
-- decrement -- decrement
digits[idx] = digits[idx] - 1 digits[idx] = digits[idx] - 1
end end
@ -146,6 +150,7 @@ local function spinbox(args)
end end
end end
end end
end
-- set the value -- set the value
---@param val number number to show ---@param val number number to show
@ -184,7 +189,7 @@ local function spinbox(args)
e.value = 0 e.value = 0
set_digits() set_digits()
return e.get() return e.complete()
end end
return spinbox return spinbox

View File

@ -1,5 +1,6 @@
-- Button Graphics Element -- Button Graphics Element
local core = require("graphics.core")
local element = require("graphics.element") local element = require("graphics.element")
---@class switch_button_args ---@class switch_button_args
@ -14,6 +15,7 @@ local element = require("graphics.element")
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field height? integer parent height if omitted ---@field height? integer parent height if omitted
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new switch button (latch high/low) -- new switch button (latch high/low)
---@param args switch_button_args ---@param args switch_button_args
@ -22,13 +24,15 @@ local function switch_button(args)
assert(type(args.text) == "string", "graphics.elements.controls.switch_button: text is a required field") 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.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.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) 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 -- create new graphics element base object
local e = element.new(args) local e = element.new(args)
@ -63,8 +67,9 @@ local function switch_button(args)
draw_state() draw_state()
-- handle mouse interaction -- handle mouse interaction
function e.handle_mouse(_) ---@param event mouse_interaction mouse event
if e.enabled then function e.handle_mouse(event)
if e.enabled and core.events.was_clicked(event.type) then
-- toggle state -- toggle state
e.value = not e.value e.value = not e.value
draw_state() draw_state()
@ -82,7 +87,7 @@ local function switch_button(args)
draw_state() draw_state()
end end
return e.get() return e.complete()
end end
return switch_button return switch_button

View File

@ -0,0 +1,131 @@
-- 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
---@field hidden? boolean true to hide on initial draw
-- 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.complete()
end
return tabbar

View File

@ -4,19 +4,22 @@ local element = require("graphics.element")
---@class displaybox_args ---@class displaybox_args
---@field window table ---@field window table
---@field id? string element id
---@field x? integer 1 if omitted ---@field x? integer 1 if omitted
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field width? integer parent width if omitted ---@field width? integer parent width if omitted
---@field height? integer parent height if omitted ---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height ---@field gframe? graphics_frame frame instead of x/y/width/height
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new root display box -- new root display box
---@nodiscard ---@nodiscard
---@param args displaybox_args ---@param args displaybox_args
---@return graphics_element element, element_id id
local function displaybox(args) local function displaybox(args)
-- create new graphics element base object -- create new graphics element base object
return element.new(args).get() return element.new(args).complete()
end end
return displaybox return displaybox

View File

@ -11,6 +11,7 @@ local element = require("graphics.element")
---@field height? integer parent height if omitted ---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height ---@field gframe? graphics_frame frame instead of x/y/width/height
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new div element -- new div element
---@nodiscard ---@nodiscard
@ -18,7 +19,7 @@ local element = require("graphics.element")
---@return graphics_element element, element_id id ---@return graphics_element element, element_id id
local function div(args) local function div(args)
-- create new graphics element base object -- create new graphics element base object
return element.new(args).get() return element.new(args).complete()
end end
return div return div

View File

@ -18,6 +18,7 @@ local flasher = require("graphics.flasher")
---@field x? integer 1 if omitted ---@field x? integer 1 if omitted
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new alarm indicator light -- new alarm indicator light
---@nodiscard ---@nodiscard
@ -108,7 +109,7 @@ local function alarm_indicator_light(args)
e.on_update(1) e.on_update(1)
e.window.write(args.label) e.window.write(args.label)
return e.get() return e.complete()
end end
return alarm_indicator_light return alarm_indicator_light

View File

@ -26,7 +26,7 @@ local function core_map(args)
args.height = 18 args.height = 18
-- inherit only foreground color -- 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 -- create new graphics element base object
local e = element.new(args) local e = element.new(args)
@ -163,7 +163,7 @@ local function core_map(args)
-- initial draw -- initial draw
e.on_update(0) e.on_update(0)
return e.get() return e.complete()
end end
return core_map return core_map

View File

@ -17,6 +17,7 @@ local element = require("graphics.element")
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field width integer length ---@field width integer length
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new data indicator -- new data indicator
---@nodiscard ---@nodiscard
@ -43,6 +44,7 @@ local function data(args)
e.window.setCursorPos(1, 1) e.window.setCursorPos(1, 1)
e.window.write(args.label) e.window.write(args.label)
local value_color = e.fg_bg.fgd
local label_len = string.len(args.label) local label_len = string.len(args.label)
local data_start = 1 local data_start = 1
local clear_width = args.width local clear_width = args.width
@ -64,7 +66,7 @@ local function data(args)
-- write data -- write data
local data_str = util.sprintf(args.format, value) local data_str = util.sprintf(args.format, value)
e.window.setCursorPos(data_start, 1) e.window.setCursorPos(data_start, 1)
e.window.setTextColor(e.fg_bg.fgd) e.window.setTextColor(value_color)
if args.commas then if args.commas then
e.window.write(util.comma_format(data_str)) e.window.write(util.comma_format(data_str))
else else
@ -84,10 +86,17 @@ local function data(args)
---@param val any new value ---@param val any new value
function e.set_value(val) e.on_update(val) end function e.set_value(val) e.on_update(val) end
-- change the foreground color of the value, or all text if no label/unit colors provided
---@param c color
function e.recolor(c)
value_color = c
e.on_update(e.value)
end
-- initial value draw -- initial value draw
e.on_update(args.value) e.on_update(args.value)
return e.get() return e.complete()
end end
return data return data

View File

@ -15,6 +15,7 @@ local element = require("graphics.element")
---@field height? integer parent height if omitted ---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height ---@field gframe? graphics_frame frame instead of x/y/width/height
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new horizontal bar -- new horizontal bar
---@nodiscard ---@nodiscard
@ -119,7 +120,7 @@ local function hbar(args)
-- initialize to 0 -- initialize to 0
e.on_update(0) e.on_update(0)
return e.get() return e.complete()
end end
return hbar return hbar

View File

@ -18,6 +18,7 @@ local element = require("graphics.element")
---@field x? integer 1 if omitted ---@field x? integer 1 if omitted
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new icon indicator -- new icon indicator
---@nodiscard ---@nodiscard
@ -68,7 +69,7 @@ local function icon(args)
-- initial icon draw -- initial icon draw
e.on_update(args.value or 1) e.on_update(args.value or 1)
return e.get() return e.complete()
end end
return icon return icon

View File

@ -16,6 +16,7 @@ local flasher = require("graphics.flasher")
---@field x? integer 1 if omitted ---@field x? integer 1 if omitted
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new indicator LED -- new indicator LED
---@nodiscard ---@nodiscard
@ -94,7 +95,7 @@ local function indicator_led(args)
e.window.write(args.label) e.window.write(args.label)
end end
return e.get() return e.complete()
end end
return indicator_led return indicator_led

View File

@ -18,6 +18,7 @@ local flasher = require("graphics.flasher")
---@field x? integer 1 if omitted ---@field x? integer 1 if omitted
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new dual LED indicator light -- new dual LED indicator light
---@nodiscard ---@nodiscard
@ -108,7 +109,7 @@ local function indicator_led_pair(args)
e.window.write(args.label) e.window.write(args.label)
end end
return e.get() return e.complete()
end end
return indicator_led_pair return indicator_led_pair

View File

@ -11,6 +11,7 @@ local element = require("graphics.element")
---@field x? integer 1 if omitted ---@field x? integer 1 if omitted
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new RGB LED indicator light -- new RGB LED indicator light
---@nodiscard ---@nodiscard
@ -53,7 +54,7 @@ local function indicator_led_rgb(args)
e.window.write(args.label) e.window.write(args.label)
end end
return e.get() return e.complete()
end end
return indicator_led_rgb return indicator_led_rgb

View File

@ -16,6 +16,7 @@ local flasher = require("graphics.flasher")
---@field x? integer 1 if omitted ---@field x? integer 1 if omitted
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new indicator light -- new indicator light
---@nodiscard ---@nodiscard
@ -92,7 +93,7 @@ local function indicator_light(args)
e.window.setCursorPos(3, 1) e.window.setCursorPos(3, 1)
e.window.write(args.label) e.window.write(args.label)
return e.get() return e.complete()
end end
return indicator_light return indicator_light

View File

@ -16,6 +16,7 @@ local element = require("graphics.element")
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field width integer length ---@field width integer length
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new power indicator -- new power indicator
---@nodiscard ---@nodiscard
@ -79,7 +80,7 @@ local function power(args)
-- initial value draw -- initial value draw
e.on_update(args.value) e.on_update(args.value)
return e.get() return e.complete()
end end
return power return power

View File

@ -17,6 +17,7 @@ local element = require("graphics.element")
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field width integer length ---@field width integer length
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new radiation indicator -- new radiation indicator
---@nodiscard ---@nodiscard
@ -84,7 +85,7 @@ local function rad(args)
-- initial value draw -- initial value draw
e.on_update(types.new_zero_radiation_reading()) e.on_update(types.new_zero_radiation_reading())
return e.get() return e.complete()
end end
return rad return rad

View File

@ -18,6 +18,7 @@ local element = require("graphics.element")
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field height? integer 1 if omitted, must be an odd number ---@field height? integer 1 if omitted, must be an odd number
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new state indicator -- new state indicator
---@nodiscard ---@nodiscard
@ -74,7 +75,7 @@ local function state_indicator(args)
-- initial draw -- initial draw
e.on_update(args.value or 1) e.on_update(args.value or 1)
return e.get() return e.complete()
end end
return state_indicator return state_indicator

View File

@ -18,6 +18,7 @@ local flasher = require("graphics.flasher")
---@field x? integer 1 if omitted ---@field x? integer 1 if omitted
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new tri-state indicator light -- new tri-state indicator light
---@nodiscard ---@nodiscard
@ -105,7 +106,7 @@ local function tristate_indicator_light(args)
e.on_update(1) e.on_update(1)
e.window.write(args.label) e.window.write(args.label)
return e.get() return e.complete()
end end
return tristate_indicator_light return tristate_indicator_light

View File

@ -13,6 +13,7 @@ local element = require("graphics.element")
---@field height? integer parent height if omitted ---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height ---@field gframe? graphics_frame frame instead of x/y/width/height
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new vertical bar -- new vertical bar
---@nodiscard ---@nodiscard
@ -99,7 +100,7 @@ local function vbar(args)
---@param val number 0.0 to 1.0 ---@param val number 0.0 to 1.0
function e.set_value(val) e.on_update(val) end function e.set_value(val) e.on_update(val) end
return e.get() return e.complete()
end end
return vbar return vbar

View File

@ -0,0 +1,283 @@
-- Scroll-able List Box Display Graphics Element
local tcd = require("scada-common.tcd")
local core = require("graphics.core")
local element = require("graphics.element")
local CLICK_TYPE = core.events.CLICK_TYPE
---@class listbox_args
---@field scroll_height integer height of internal scrolling container (must fit all elements vertically tiled)
---@field item_pad? integer spacing (lines) between items in the list (default 0)
---@field nav_fg_bg? cpair foreground/background colors for scroll arrows and bar area
---@field nav_active? cpair active colors for bar held down or arrow held down
---@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 height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height
---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
---@class listbox_item
---@field id string|integer element ID
---@field e graphics_element element
---@field y integer y position
---@field h integer element height
-- new listbox element
---@nodiscard
---@param args listbox_args
---@return graphics_element element, element_id id
local function listbox(args)
-- create new graphics element base object
local e = element.new(args)
-- create content window for child elements
local scroll_frame = window.create(e.window, 1, 1, e.frame.w - 1, args.scroll_height, false)
e.content_window = scroll_frame
-- item list and scroll management
local list = {}
local item_pad = args.item_pad or 0
local scroll_offset = 0
local content_height = 0
local max_down_scroll = 0
-- bar control/tracking variables
local max_bar_height = e.frame.h - 2
local bar_height = 0 -- full height of bar
local bar_bounds = { 0, 0 } -- top and bottom of bar
local bar_is_scaled = false -- if the scrollbar doesn't have a 1:1 ratio with lines
local holding_bar = false -- bar is being held by mouse
local bar_grip_pos = 0 -- where the bar was gripped by mouse down
local mouse_last_y = 0 -- last reported y coordinate of drag
-- draw scroll bar arrows, optionally showing one of them as pressed
---@param pressed_arrow? 1|0|-1 arrow to show as pressed (1 = scroll up, 0 = neither, -1 = scroll down)
local function draw_arrows(pressed_arrow)
local nav_fg_bg = args.nav_fg_bg or e.fg_bg
local active_fg_bg = args.nav_active or nav_fg_bg
-- draw up/down arrows
if pressed_arrow == 1 then
e.window.setTextColor(active_fg_bg.fgd)
e.window.setBackgroundColor(active_fg_bg.bkg)
e.window.setCursorPos(e.frame.w, 1)
e.window.write("\x1e")
e.window.setTextColor(nav_fg_bg.fgd)
e.window.setBackgroundColor(nav_fg_bg.bkg)
e.window.setCursorPos(e.frame.w, e.frame.h)
e.window.write("\x1f")
elseif pressed_arrow == -1 then
e.window.setTextColor(nav_fg_bg.fgd)
e.window.setBackgroundColor(nav_fg_bg.bkg)
e.window.setCursorPos(e.frame.w, 1)
e.window.write("\x1e")
e.window.setTextColor(active_fg_bg.fgd)
e.window.setBackgroundColor(active_fg_bg.bkg)
e.window.setCursorPos(e.frame.w, e.frame.h)
e.window.write("\x1f")
else
e.window.setTextColor(nav_fg_bg.fgd)
e.window.setBackgroundColor(nav_fg_bg.bkg)
e.window.setCursorPos(e.frame.w, 1)
e.window.write("\x1e")
e.window.setCursorPos(e.frame.w, e.frame.h)
e.window.write("\x1f")
end
e.window.setTextColor(e.fg_bg.fgd)
e.window.setBackgroundColor(e.fg_bg.bkg)
end
-- render the scroll bar and re-cacluate height & bounds
local function draw_bar()
local offset = 2 + math.abs(scroll_offset)
bar_height = math.min(max_bar_height + max_down_scroll, max_bar_height)
if bar_height < 1 then
bar_is_scaled = true
-- can't do a 1:1 ratio
-- use minimum size bar with scaled offset
local scroll_progress = scroll_offset / max_down_scroll
offset = 2 + math.floor(scroll_progress * (max_bar_height - 1))
bar_height = 1
else
bar_is_scaled = false
end
bar_bounds = { offset, (bar_height + offset) - 1 }
for i = 2, e.frame.h - 1 do
if (i >= offset and i < (bar_height + offset)) and (bar_height ~= max_bar_height) then
if args.nav_fg_bg ~= nil then
e.window.setBackgroundColor(args.nav_fg_bg.fgd)
else
e.window.setBackgroundColor(e.fg_bg.fgd)
end
else
if args.nav_fg_bg ~= nil then
e.window.setBackgroundColor(args.nav_fg_bg.bkg)
else
e.window.setBackgroundColor(e.fg_bg.bkg)
end
end
e.window.setCursorPos(e.frame.w, i)
e.window.write(" ")
end
e.window.setBackgroundColor(e.fg_bg.bkg)
end
-- update item y positions and move elements
local function update_positions()
local next_y = 1
scroll_frame.setVisible(false)
scroll_frame.setBackgroundColor(e.fg_bg.bkg)
scroll_frame.setTextColor(e.fg_bg.fgd)
scroll_frame.clear()
for i = 1, #list do
local item = list[i] ---@type listbox_item
item.y = next_y
next_y = next_y + item.h + item_pad
item.e.reposition(1, item.y)
item.e.show()
end
content_height = next_y
max_down_scroll = math.min(-1 * (content_height - (e.frame.h + 1 + item_pad)), 0)
if scroll_offset < max_down_scroll then scroll_offset = max_down_scroll end
scroll_frame.reposition(1, 1 + scroll_offset)
scroll_frame.setVisible(true)
draw_bar()
end
-- determine where to scroll to based on a scrollbar being dragged without a 1:1 relationship
---@param direction -1|1 negative 1 to scroll up by one, positive 1 to scroll down by one
local function scaled_bar_scroll(direction)
local scroll_progress = scroll_offset / max_down_scroll
local bar_position = math.floor(scroll_progress * (max_bar_height - 1))
-- check what moving the scroll bar up or down would mean for the scroll progress
scroll_progress = (bar_position + direction) / (max_bar_height - 1)
return math.max(math.floor(scroll_progress * max_down_scroll), max_down_scroll)
end
-- scroll down the list
local function scroll_down(scaled)
if scroll_offset > max_down_scroll then
if scaled then
scroll_offset = scaled_bar_scroll(1)
else
scroll_offset = scroll_offset - 1
end
update_positions()
end
end
-- scroll up the list
local function scroll_up(scaled)
if scroll_offset < 0 then
if scaled then
scroll_offset = scaled_bar_scroll(-1)
else
scroll_offset = scroll_offset + 1
end
update_positions()
end
end
-- handle a child element having been added to the list
---@param id element_id element identifier
---@param child graphics_element child element
function e.on_added(id, child)
table.insert(list, { id = id, e = child, y = 0, h = child.get_height() })
update_positions()
end
-- handle a child element having been removed from the list
---@param id element_id element identifier
function e.on_removed(id)
for idx, elem in ipairs(list) do
if elem.id == id then
table.remove(list, idx)
update_positions()
return
end
end
end
-- handle mouse interaction
---@param event mouse_interaction mouse event
function e.handle_mouse(event)
if e.enabled then
if event.type == CLICK_TYPE.TAP then
if event.current.x == e.frame.w then
if event.current.y == 1 or event.current.y < bar_bounds[1] then
draw_arrows(1)
scroll_up()
if args.nav_active ~= nil then tcd.dispatch(0.25, function () draw_arrows(0) end) end
elseif event.current.y == e.frame.h or event.current.y > bar_bounds[2] then
draw_arrows(-1)
scroll_down()
if args.nav_active ~= nil then tcd.dispatch(0.25, function () draw_arrows(0) end) end
end
end
elseif event.type == CLICK_TYPE.DOWN then
if event.current.x == e.frame.w then
if event.current.y == 1 or event.current.y < bar_bounds[1] then
draw_arrows(1)
scroll_up()
elseif event.current.y == e.frame.h or event.current.y > bar_bounds[2] then
draw_arrows(-1)
scroll_down()
else
-- clicked on bar
holding_bar = true
bar_grip_pos = event.current.y - bar_bounds[1]
mouse_last_y = event.current.y
end
end
elseif event.type == CLICK_TYPE.UP then
holding_bar = false
draw_arrows(0)
elseif event.type == CLICK_TYPE.DRAG then
if holding_bar then
-- if mouse is within vertical frame, including the grip point
if event.current.y > (1 + bar_grip_pos) and event.current.y <= ((e.frame.h - bar_height) + bar_grip_pos) then
if event.current.y < mouse_last_y then
scroll_up(bar_is_scaled)
elseif event.current.y > mouse_last_y then
scroll_down(bar_is_scaled)
end
mouse_last_y = event.current.y
end
end
elseif event.type == CLICK_TYPE.SCROLL_DOWN then
scroll_down()
elseif event.type == CLICK_TYPE.SCROLL_UP then
scroll_up()
end
end
end
draw_arrows(0)
draw_bar()
return e.complete()
end
return listbox

View File

@ -12,6 +12,7 @@ local element = require("graphics.element")
---@field height? integer parent height if omitted ---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height ---@field gframe? graphics_frame frame instead of x/y/width/height
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new multipane element -- new multipane element
---@nodiscard ---@nodiscard
@ -36,7 +37,7 @@ local function multipane(args)
e.set_value(1) e.set_value(1)
return e.get() return e.complete()
end end
return multipane return multipane

View File

@ -12,6 +12,7 @@ local element = require("graphics.element")
---@field id? string element id ---@field id? string element id
---@field x? integer 1 if omitted ---@field x? integer 1 if omitted
---@field y? integer 1 if omitted ---@field y? integer 1 if omitted
---@field hidden? boolean true to hide on initial draw
-- new pipe network -- new pipe network
---@param args pipenet_args ---@param args pipenet_args
@ -37,7 +38,7 @@ local function pipenet(args)
args.y = args.y or 1 args.y = args.y or 1
if args.bg ~= nil then 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 end
-- create new graphics element base object -- create new graphics element base object
@ -55,7 +56,7 @@ local function pipenet(args)
e.window.setCursorPos(x, y) 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 if pipe.align_tr then
-- cross width then height -- cross width then height
@ -141,7 +142,7 @@ local function pipenet(args)
end end
return e.get() return e.complete()
end end
return pipenet return pipenet

View File

@ -16,6 +16,7 @@ local element = require("graphics.element")
---@field height? integer parent height if omitted ---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height ---@field gframe? graphics_frame frame instead of x/y/width/height
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new rectangle -- new rectangle
---@param args rectangle_args ---@param args rectangle_args
@ -30,27 +31,35 @@ local function rectangle(args)
end end
-- offset children -- offset children
local offset_x = 0
local offset_y = 0
if args.border ~= nil then if args.border ~= nil then
args.offset_x = args.border.width offset_x = args.border.width
args.offset_y = args.border.width offset_y = args.border.width
-- slightly different y offset if the border is set to even -- slightly different y offset if the border is set to even
if args.border.even then if args.border.even then
local width_x2 = (2 * args.border.width) local width_x2 = (2 * args.border.width)
args.offset_y = math.floor(width_x2 / 3) + util.trinary(width_x2 % 3 > 0, 1, 0) offset_y = math.floor(width_x2 / 3) + util.trinary(width_x2 % 3 > 0, 1, 0)
end end
end end
-- create new graphics element base object -- create new graphics element base object
local e = element.new(args) local e = element.new(args)
-- create content window for child elements
e.content_window = window.create(e.window, 1 + offset_x, 1 + offset_y, e.frame.w - (2 * offset_x), e.frame.h - (2 * offset_y))
e.content_window.setBackgroundColor(e.fg_bg.bkg)
e.content_window.setTextColor(e.fg_bg.fgd)
e.content_window.clear()
-- draw bordered box if requested -- draw bordered box if requested
-- element constructor will have drawn basic colored rectangle regardless -- element constructor will have drawn basic colored rectangle regardless
if args.border ~= nil then if args.border ~= nil then
e.window.setCursorPos(1, 1) e.window.setCursorPos(1, 1)
local border_width = args.offset_x local border_width = offset_x
local border_height = args.offset_y local border_height = offset_y
local border_blit = colors.toBlit(args.border.color) local border_blit = colors.toBlit(args.border.color)
local width_x2 = border_width * 2 local width_x2 = border_width * 2
local inner_width = e.frame.w - width_x2 local inner_width = e.frame.w - width_x2
@ -177,7 +186,7 @@ local function rectangle(args)
end end
end end
return e.get() return e.complete()
end end
return rectangle return rectangle

View File

@ -5,7 +5,7 @@ local util = require("scada-common.util")
local core = require("graphics.core") local core = require("graphics.core")
local element = require("graphics.element") local element = require("graphics.element")
local TEXT_ALIGN = core.graphics.TEXT_ALIGN local TEXT_ALIGN = core.TEXT_ALIGN
---@class textbox_args ---@class textbox_args
---@field text string text to show ---@field text string text to show
@ -18,6 +18,7 @@ local TEXT_ALIGN = core.graphics.TEXT_ALIGN
---@field height? integer parent height if omitted ---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height ---@field gframe? graphics_frame frame instead of x/y/width/height
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new text box -- new text box
---@param args textbox_args ---@param args textbox_args
@ -64,7 +65,7 @@ local function textbox(args)
display_text(val) display_text(val)
end end
return e.get() return e.complete()
end end
return textbox return textbox

View File

@ -16,6 +16,7 @@ local element = require("graphics.element")
---@field height? integer parent height if omitted ---@field height? integer parent height if omitted
---@field gframe? graphics_frame frame instead of x/y/width/height ---@field gframe? graphics_frame frame instead of x/y/width/height
---@field fg_bg? cpair foreground/background colors ---@field fg_bg? cpair foreground/background colors
---@field hidden? boolean true to hide on initial draw
-- new tiling box -- new tiling box
---@param args tiling_args ---@param args tiling_args
@ -81,7 +82,7 @@ local function tiling(args)
if inner_width % 2 == 0 then alternator = not alternator end if inner_width % 2 == 0 then alternator = not alternator end
end end
return e.get() return e.complete()
end end
return tiling return tiling

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

View File

@ -2,7 +2,7 @@
-- Indicator Light Flasher -- Indicator Light Flasher
-- --
local tcd = require("scada-common.tcallbackdsp") local tcd = require("scada-common.tcd")
local flasher = {} local flasher = {}

View File

@ -108,10 +108,10 @@ f = open("install_manifest.json", "w")
json.dump(final_manifest, f) json.dump(final_manifest, f)
f.close() f.close()
if len(sys.argv) > 1 and sys.argv[1] == "shields": if len(sys.argv) > 1 and sys.argv[1] == "gh_actions":
# write all the JSON files for shields.io # write all the JSON files for shields.io
for key, version in final_manifest["versions"].items(): for key, version in final_manifest["versions"].items():
f = open("./shields/" + key + ".json", "w") f = open("./deploy/" + key + ".json", "w")
if version.find("alpha") >= 0: if version.find("alpha") >= 0:
color = "yellow" color = "yellow"

File diff suppressed because one or more lines are too long

View File

@ -70,9 +70,9 @@ end
function renderer.ui_ready() return ui.display ~= nil end function renderer.ui_ready() return ui.display ~= nil end
-- handle a mouse event -- handle a mouse event
---@param event mouse_interaction ---@param event mouse_interaction|nil
function renderer.handle_mouse(event) function renderer.handle_mouse(event)
if ui.display ~= nil then if ui.display ~= nil and event ~= nil then
ui.display.handle_mouse(event) ui.display.handle_mouse(event)
end end
end end

View File

@ -7,7 +7,7 @@ require("/initenv").init_env()
local crash = require("scada-common.crash") local crash = require("scada-common.crash")
local log = require("scada-common.log") local log = require("scada-common.log")
local ppm = require("scada-common.ppm") local ppm = require("scada-common.ppm")
local tcallbackdsp = require("scada-common.tcallbackdsp") local tcd = require("scada-common.tcd")
local util = require("scada-common.util") local util = require("scada-common.util")
local core = require("graphics.core") local core = require("graphics.core")
@ -17,7 +17,7 @@ local coreio = require("pocket.coreio")
local pocket = require("pocket.pocket") local pocket = require("pocket.pocket")
local renderer = require("pocket.renderer") local renderer = require("pocket.renderer")
local POCKET_VERSION = "alpha-v0.2.6" local POCKET_VERSION = "alpha-v0.3.7"
local println = util.println local println = util.println
local println_ts = util.println_ts local println_ts = util.println_ts
@ -36,7 +36,6 @@ cfv.assert_type_num(config.COMMS_TIMEOUT)
cfv.assert_min(config.COMMS_TIMEOUT, 2) cfv.assert_min(config.COMMS_TIMEOUT, 2)
cfv.assert_type_str(config.LOG_PATH) cfv.assert_type_str(config.LOG_PATH)
cfv.assert_type_int(config.LOG_MODE) cfv.assert_type_int(config.LOG_MODE)
cfv.assert_type_bool(config.LOG_DEBUG)
assert(cfv.valid(), "bad config file: missing/invalid fields") assert(cfv.valid(), "bad config file: missing/invalid fields")
@ -44,7 +43,7 @@ assert(cfv.valid(), "bad config file: missing/invalid fields")
-- log init -- log init
---------------------------------------- ----------------------------------------
log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG) log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG == true)
log.info("========================================") log.info("========================================")
log.info("BOOTING pocket.startup " .. POCKET_VERSION) log.info("BOOTING pocket.startup " .. POCKET_VERSION)
@ -121,10 +120,9 @@ local function main()
conn_wd.sv.feed() conn_wd.sv.feed()
conn_wd.api.feed() conn_wd.api.feed()
log.debug("startup> conn watchdog started") log.debug("startup> conn watchdog started")
end
-- main event loop -- main event loop
while ui_ok do while true do
local event, param1, param2, param3, param4, param5 = util.pull_event() local event, param1, param2, param3, param4, param5 = util.pull_event()
-- handle event -- handle event
@ -147,15 +145,15 @@ local function main()
else else
-- a non-clock/main watchdog timer event -- a non-clock/main watchdog timer event
-- notify timer callback dispatcher -- notify timer callback dispatcher
tcallbackdsp.handle(param1) tcd.handle(param1)
end end
elseif event == "modem_message" then elseif event == "modem_message" then
-- got a packet -- got a packet
local packet = pocket_comms.parse_packet(param1, param2, param3, param4, param5) local packet = pocket_comms.parse_packet(param1, param2, param3, param4, param5)
pocket_comms.handle_packet(packet) 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 -- 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 end
-- check for termination request -- check for termination request
@ -168,6 +166,7 @@ local function main()
end end
renderer.close_ui() renderer.close_ui()
end
println_ts("exited") println_ts("exited")
log.info("exited") log.info("exited")

View File

@ -11,9 +11,9 @@ local TextBox = require("graphics.elements.textbox")
local WaitingAnim = require("graphics.elements.animations.waiting") 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 -- create a waiting view
---@param parent graphics_element parent ---@param parent graphics_element parent
@ -25,7 +25,7 @@ local function init(parent, y, is_api)
-- bounding box div -- bounding box div
local box = Div{parent=root,x=1,y=y,height=5} local box = Div{parent=root,x=1,y=y,height=5}
local waiting_x = math.floor(parent.width() / 2) - 1 local waiting_x = math.floor(parent.get_width() / 2) - 1
if is_api then if is_api then
WaitingAnim{parent=box,x=waiting_x,y=1,fg_bg=cpair(colors.blue,style.root.bkg)} WaitingAnim{parent=box,x=waiting_x,y=1,fg_bg=cpair(colors.blue,style.root.bkg)}

View File

@ -8,11 +8,11 @@ local style = require("pocket.ui.style")
local conn_waiting = require("pocket.ui.components.conn_waiting") local conn_waiting = require("pocket.ui.components.conn_waiting")
local home_page = require("pocket.ui.components.home_page") local home_page = require("pocket.ui.pages.home_page")
local unit_page = require("pocket.ui.components.unit_page") local unit_page = require("pocket.ui.pages.unit_page")
local reactor_page = require("pocket.ui.components.reactor_page") local reactor_page = require("pocket.ui.pages.reactor_page")
local boiler_page = require("pocket.ui.components.boiler_page") local boiler_page = require("pocket.ui.pages.boiler_page")
local turbine_page = require("pocket.ui.components.turbine_page") local turbine_page = require("pocket.ui.pages.turbine_page")
local core = require("graphics.core") local core = require("graphics.core")
@ -22,9 +22,9 @@ local TextBox = require("graphics.elements.textbox")
local Sidebar = require("graphics.elements.controls.sidebar") 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 -- create new main view
---@param main graphics_element main displaybox ---@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} 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 if state == coreio.LINK_STATE.UNLINKED or state == coreio.LINK_STATE.API_LINK_ONLY then
root_pane.set_value(1) root_pane.set_value(1)
elseif state == coreio.LINK_STATE.SV_LINK_ONLY then elseif state == coreio.LINK_STATE.SV_LINK_ONLY then

View File

@ -5,9 +5,9 @@ local core = require("graphics.core")
local Div = require("graphics.elements.div") local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox") 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 -- new boiler page view
---@param root graphics_element parent ---@param root graphics_element parent

View File

@ -5,9 +5,9 @@ local core = require("graphics.core")
local Div = require("graphics.elements.div") local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox") 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 -- new home page view
---@param root graphics_element parent ---@param root graphics_element parent

View File

@ -5,9 +5,9 @@ local core = require("graphics.core")
local Div = require("graphics.elements.div") local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox") 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 -- new reactor page view
---@param root graphics_element parent ---@param root graphics_element parent

View File

@ -5,9 +5,9 @@ local core = require("graphics.core")
local Div = require("graphics.elements.div") local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox") 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 -- new turbine page view
---@param root graphics_element parent ---@param root graphics_element parent

View File

@ -5,9 +5,9 @@ local core = require("graphics.core")
local Div = require("graphics.elements.div") local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox") 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 -- new unit page view
---@param root graphics_element parent ---@param root graphics_element parent

View File

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

View File

@ -8,14 +8,16 @@ local util = require("scada-common.util")
local databus = {} local databus = {}
-- databus PSIL
databus.ps = psil.create()
local dbus_iface = { local dbus_iface = {
ps = psil.create(),
rps_scram = function () log.debug("DBUS: unset rps_scram() called") end, rps_scram = function () log.debug("DBUS: unset rps_scram() called") end,
rps_reset = function () log.debug("DBUS: unset rps_reset() called") end rps_reset = function () log.debug("DBUS: unset rps_reset() called") end
} }
-- call to toggle heartbeat signal -- 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 -- link RPS command functions
---@param scram function reactor SCRAM function ---@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 plc_v string PLC version
---@param comms_v string comms version ---@param comms_v string comms version
function databus.tx_versions(plc_v, comms_v) function databus.tx_versions(plc_v, comms_v)
dbus_iface.ps.publish("version", plc_v) databus.ps.publish("version", plc_v)
dbus_iface.ps.publish("comms_version", comms_v) databus.ps.publish("comms_version", comms_v)
end end
-- transmit unit ID across the bus -- transmit unit ID across the bus
---@param id integer unit ID ---@param id integer unit ID
function databus.tx_id(id) function databus.tx_id(id)
dbus_iface.ps.publish("unit_id", id) databus.ps.publish("unit_id", id)
end end
-- transmit hardware status across the bus -- transmit hardware status across the bus
---@param plc_state plc_state ---@param plc_state plc_state
function databus.tx_hw_status(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))) databus.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) databus.ps.publish("has_modem", not plc_state.no_modem)
dbus_iface.ps.publish("degraded", plc_state.degraded) databus.ps.publish("degraded", plc_state.degraded)
dbus_iface.ps.publish("init_ok", plc_state.init_ok) databus.ps.publish("init_ok", plc_state.init_ok)
end end
-- transmit thread (routine) statuses -- transmit thread (routine) statuses
---@param thread string thread name ---@param thread string thread name
---@param ok boolean thread state ---@param ok boolean thread state
function databus.tx_rt_status(thread, ok) 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 end
-- transmit supervisor link state across the bus -- transmit supervisor link state across the bus
---@param state integer ---@param state integer
function databus.tx_link_state(state) function databus.tx_link_state(state)
dbus_iface.ps.publish("link_state", state) databus.ps.publish("link_state", state)
end end
-- transmit reactor enable state across the bus -- transmit reactor enable state across the bus
---@param active boolean reactor active ---@param active boolean reactor active
function databus.tx_reactor_state(active) function databus.tx_reactor_state(active)
dbus_iface.ps.publish("reactor_active", active) databus.ps.publish("reactor_active", active)
end end
-- transmit RPS data across the bus -- transmit RPS data across the bus
@ -78,26 +80,26 @@ end
---@param status table RPS status ---@param status table RPS status
---@param emer_cool_active boolean RPS activated the emergency coolant ---@param emer_cool_active boolean RPS activated the emergency coolant
function databus.tx_rps(tripped, status, emer_cool_active) function databus.tx_rps(tripped, status, emer_cool_active)
dbus_iface.ps.publish("rps_scram", tripped) databus.ps.publish("rps_scram", tripped)
dbus_iface.ps.publish("rps_damage", status[1]) databus.ps.publish("rps_damage", status[1])
dbus_iface.ps.publish("rps_high_temp", status[2]) databus.ps.publish("rps_high_temp", status[2])
dbus_iface.ps.publish("rps_low_ccool", status[3]) databus.ps.publish("rps_low_ccool", status[3])
dbus_iface.ps.publish("rps_high_waste", status[4]) databus.ps.publish("rps_high_waste", status[4])
dbus_iface.ps.publish("rps_high_hcool", status[5]) databus.ps.publish("rps_high_hcool", status[5])
dbus_iface.ps.publish("rps_no_fuel", status[6]) databus.ps.publish("rps_no_fuel", status[6])
dbus_iface.ps.publish("rps_fault", status[7]) databus.ps.publish("rps_fault", status[7])
dbus_iface.ps.publish("rps_timeout", status[8]) databus.ps.publish("rps_timeout", status[8])
dbus_iface.ps.publish("rps_manual", status[9]) databus.ps.publish("rps_manual", status[9])
dbus_iface.ps.publish("rps_automatic", status[10]) databus.ps.publish("rps_automatic", status[10])
dbus_iface.ps.publish("rps_sysfail", status[11]) databus.ps.publish("rps_sysfail", status[11])
dbus_iface.ps.publish("emer_cool", emer_cool_active) databus.ps.publish("emer_cool", emer_cool_active)
end end
-- link a function to receive data from the bus -- link a function to receive data from the bus
---@param field string field name ---@param field string field name
---@param func function function to link ---@param func function function to link
function databus.rx_field(field, func) function databus.rx_field(field, func)
dbus_iface.ps.subscribe(field, func) databus.ps.subscribe(field, func)
end end
return databus return databus

View File

@ -22,16 +22,16 @@ local LED = require("graphics.elements.indicators.led")
local LEDPair = require("graphics.elements.indicators.ledpair") local LEDPair = require("graphics.elements.indicators.ledpair")
local RGBLED = require("graphics.elements.indicators.ledrgb") 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 border = core.graphics.border local border = core.border
-- create new main view -- create new main view
---@param panel graphics_element main displaybox ---@param panel graphics_element main displaybox
local function init(panel) 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} 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 -- system indicators
@ -43,8 +43,8 @@ local function init(panel)
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=cpair(colors.green,colors.green_off)} local heartbeat = LED{parent=system,label="HEARTBEAT",colors=cpair(colors.green,colors.green_off)}
system.line_break() system.line_break()
databus.rx_field("init_ok", init_ok.update) init_ok.register(databus.ps, "init_ok", init_ok.update)
databus.rx_field("heartbeat", heartbeat.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 reactor = LEDPair{parent=system,label="REACTOR",off=colors.red,c1=colors.yellow,c2=colors.green}
local modem = LED{parent=system,label="MODEM",colors=cpair(colors.green,colors.green_off)} local modem = LED{parent=system,label="MODEM",colors=cpair(colors.green,colors.green_off)}
@ -52,9 +52,9 @@ local function init(panel)
network.update(5) network.update(5)
system.line_break() system.line_break()
databus.rx_field("reactor_dev_state", reactor.update) reactor.register(databus.ps, "reactor_dev_state", reactor.update)
databus.rx_field("has_modem", modem.update) modem.register(databus.ps, "has_modem", modem.update)
databus.rx_field("link_state", network.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_main = LED{parent=system,label="RT MAIN",colors=cpair(colors.green,colors.green_off)}
local rt_rps = LED{parent=system,label="RT RPS",colors=cpair(colors.green,colors.green_off)} local rt_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)} local rt_sctl = LED{parent=system,label="RT SPCTL",colors=cpair(colors.green,colors.green_off)}
system.line_break() system.line_break()
databus.rx_field("routine__main", rt_main.update) rt_main.register(databus.ps, "routine__main", rt_main.update)
databus.rx_field("routine__rps", rt_rps.update) rt_rps.register(databus.ps, "routine__rps", rt_rps.update)
databus.rx_field("routine__comms_tx", rt_cmtx.update) rt_cmtx.register(databus.ps, "routine__comms_tx", rt_cmtx.update)
databus.rx_field("routine__comms_rx", rt_cmrx.update) rt_cmrx.register(databus.ps, "routine__comms_rx", rt_cmrx.update)
databus.rx_field("routine__spctl", rt_sctl.update) rt_sctl.register(databus.ps, "routine__spctl", rt_sctl.update)
-- --
-- status & controls -- status & controls
@ -80,7 +80,7 @@ local function init(panel)
-- only show emergency coolant LED if emergency coolant is configured for this device -- only show emergency coolant LED if emergency coolant is configured for this device
if type(config.EMERGENCY_COOL) == "table" then 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)} 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 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)} 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=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)} 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) active.register(databus.ps, "reactor_active", active.update)
databus.rx_field("rps_scram", scram.update) scram.register(databus.ps, "rps_scram", scram.update)
-- --
-- about footer -- 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 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} 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) fw_v.register(databus.ps, "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) comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
-- --
-- rps list -- 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_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)} local rps_hcl = LED{parent=rps,label="HI HCOOLANT",colors=cpair(colors.red,colors.red_off)}
databus.rx_field("rps_manual", rps_man.update) rps_man.register(databus.ps, "rps_manual", rps_man.update)
databus.rx_field("rps_automatic", rps_auto.update) rps_auto.register(databus.ps, "rps_automatic", rps_auto.update)
databus.rx_field("rps_timeout", rps_tmo.update) rps_tmo.register(databus.ps, "rps_timeout", rps_tmo.update)
databus.rx_field("rps_fault", rps_flt.update) rps_flt.register(databus.ps, "rps_fault", rps_flt.update)
databus.rx_field("rps_sysfail", rps_fail.update) rps_fail.register(databus.ps, "rps_sysfail", rps_fail.update)
databus.rx_field("rps_damage", rps_dmg.update) rps_dmg.register(databus.ps, "rps_damage", rps_dmg.update)
databus.rx_field("rps_high_temp", rps_tmp.update) rps_tmp.register(databus.ps, "rps_high_temp", rps_tmp.update)
databus.rx_field("rps_no_fuel", rps_nof.update) rps_nof.register(databus.ps, "rps_no_fuel", rps_nof.update)
databus.rx_field("rps_high_waste", rps_wst.update) rps_wst.register(databus.ps, "rps_high_waste", rps_wst.update)
databus.rx_field("rps_low_ccool", rps_ccl.update) rps_ccl.register(databus.ps, "rps_low_ccool", rps_ccl.update)
databus.rx_field("rps_high_hcool", rps_hcl.update) rps_hcl.register(databus.ps, "rps_high_hcool", rps_hcl.update)
end end
return init return init

View File

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

View File

@ -44,10 +44,8 @@ function renderer.close_ui()
-- stop blinking indicators -- stop blinking indicators
flasher.clear() flasher.clear()
-- hide to stop animation callbacks -- delete element tree
ui.display.hide() ui.display.delete()
-- clear root UI elements
ui.display = nil ui.display = nil
-- restore colors -- restore colors
@ -70,9 +68,9 @@ end
function renderer.ui_ready() return ui.display ~= nil end function renderer.ui_ready() return ui.display ~= nil end
-- handle a mouse event -- handle a mouse event
---@param event mouse_interaction ---@param event mouse_interaction|nil
function renderer.handle_mouse(event) function renderer.handle_mouse(event)
if ui.display ~= nil then if ui.display ~= nil and event ~= nil then
ui.display.handle_mouse(event) ui.display.handle_mouse(event)
end end
end end

View File

@ -18,7 +18,7 @@ local plc = require("reactor-plc.plc")
local renderer = require("reactor-plc.renderer") local renderer = require("reactor-plc.renderer")
local threads = require("reactor-plc.threads") local threads = require("reactor-plc.threads")
local R_PLC_VERSION = "v1.3.0" local R_PLC_VERSION = "v1.4.0"
local println = util.println local println = util.println
local println_ts = util.println_ts local println_ts = util.println_ts
@ -38,7 +38,6 @@ cfv.assert_type_num(config.COMMS_TIMEOUT)
cfv.assert_min(config.COMMS_TIMEOUT, 2) cfv.assert_min(config.COMMS_TIMEOUT, 2)
cfv.assert_type_str(config.LOG_PATH) cfv.assert_type_str(config.LOG_PATH)
cfv.assert_type_int(config.LOG_MODE) cfv.assert_type_int(config.LOG_MODE)
cfv.assert_type_bool(config.LOG_DEBUG)
assert(cfv.valid(), "bad config file: missing/invalid fields") assert(cfv.valid(), "bad config file: missing/invalid fields")
@ -55,7 +54,7 @@ end
-- log init -- log init
---------------------------------------- ----------------------------------------
log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG) log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG == true)
log.info("========================================") log.info("========================================")
log.info("BOOTING reactor-plc.startup " .. R_PLC_VERSION) log.info("BOOTING reactor-plc.startup " .. R_PLC_VERSION)

View File

@ -1,7 +1,7 @@
local log = require("scada-common.log") local log = require("scada-common.log")
local mqueue = require("scada-common.mqueue") local mqueue = require("scada-common.mqueue")
local ppm = require("scada-common.ppm") local ppm = require("scada-common.ppm")
local tcallbackdsp = require("scada-common.tcallbackdsp") local tcd = require("scada-common.tcd")
local util = require("scada-common.util") local util = require("scada-common.util")
local databus = require("reactor-plc.databus") local databus = require("reactor-plc.databus")
@ -157,7 +157,7 @@ function threads.thread__main(smem, init)
smem.q.mq_rps.push_command(MQ__RPS_CMD.TRIP_TIMEOUT) smem.q.mq_rps.push_command(MQ__RPS_CMD.TRIP_TIMEOUT)
elseif event == "timer" then elseif event == "timer" then
-- notify timer callback dispatcher if no other timer case claimed this event -- notify timer callback dispatcher if no other timer case claimed this event
tcallbackdsp.handle(param1) tcd.handle(param1)
elseif event == "peripheral_detach" then elseif event == "peripheral_detach" then
-- peripheral disconnect -- peripheral disconnect
local type, device = ppm.handle_unmount(param1) local type, device = ppm.handle_unmount(param1)
@ -257,9 +257,9 @@ function threads.thread__main(smem, init)
-- update indicators -- update indicators
databus.tx_hw_status(plc_state) databus.tx_hw_status(plc_state)
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 -- handle a mouse event
renderer.handle_mouse(core.events.click(param1, param2, param3)) renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
elseif event == "clock_start" then elseif event == "clock_start" then
-- start loop clock -- start loop clock
loop_clock.start() loop_clock.start()

View File

@ -7,9 +7,8 @@ local util = require("scada-common.util")
local databus = {} local databus = {}
local dbus_iface = { -- databus PSIL
ps = psil.create() databus.ps = psil.create()
}
---@enum RTU_UNIT_HW_STATE ---@enum RTU_UNIT_HW_STATE
local 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 databus.RTU_UNIT_HW_STATE = RTU_UNIT_HW_STATE
-- call to toggle heartbeat signal -- 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 -- transmit firmware versions across the bus
---@param rtu_v string RTU version ---@param rtu_v string RTU version
---@param comms_v string comms version ---@param comms_v string comms version
function databus.tx_versions(rtu_v, comms_v) function databus.tx_versions(rtu_v, comms_v)
dbus_iface.ps.publish("version", rtu_v) databus.ps.publish("version", rtu_v)
dbus_iface.ps.publish("comms_version", comms_v) databus.ps.publish("comms_version", comms_v)
end end
-- transmit hardware status for modem connection state -- transmit hardware status for modem connection state
---@param has_modem boolean ---@param has_modem boolean
function databus.tx_hw_modem(has_modem) function databus.tx_hw_modem(has_modem)
dbus_iface.ps.publish("has_modem", has_modem) databus.ps.publish("has_modem", has_modem)
end end
-- transmit unit hardware type across the bus -- transmit unit hardware type across the bus
---@param uid integer unit ID ---@param uid integer unit ID
---@param type RTU_UNIT_TYPE ---@param type RTU_UNIT_TYPE
function databus.tx_unit_hw_type(uid, 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 end
-- transmit unit hardware status across the bus -- transmit unit hardware status across the bus
---@param uid integer unit ID ---@param uid integer unit ID
---@param status RTU_UNIT_HW_STATE ---@param status RTU_UNIT_HW_STATE
function databus.tx_unit_hw_status(uid, status) function databus.tx_unit_hw_status(uid, status)
dbus_iface.ps.publish("unit_hw_" .. uid, status) databus.ps.publish("unit_hw_" .. uid, status)
end end
-- transmit thread (routine) statuses -- transmit thread (routine) statuses
---@param thread string thread name ---@param thread string thread name
---@param ok boolean thread state ---@param ok boolean thread state
function databus.tx_rt_status(thread, ok) 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 end
-- transmit supervisor link state across the bus -- transmit supervisor link state across the bus
---@param state integer ---@param state integer
function databus.tx_link_state(state) function databus.tx_link_state(state)
dbus_iface.ps.publish("link_state", state) databus.ps.publish("link_state", state)
end end
-- link a function to receive data from the bus -- link a function to receive data from the bus
---@param field string field name ---@param field string field name
---@param func function function to link ---@param func function function to link
function databus.rx_field(field, func) function databus.rx_field(field, func)
dbus_iface.ps.subscribe(field, func) databus.ps.subscribe(field, func)
end end
return databus return databus

View File

@ -16,9 +16,9 @@ local TextBox = require("graphics.elements.textbox")
local LED = require("graphics.elements.indicators.led") local LED = require("graphics.elements.indicators.led")
local RGBLED = require("graphics.elements.indicators.ledrgb") 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 = { local UNIT_TYPE_LABELS = {
"UNKNOWN", "UNKNOWN",
@ -44,27 +44,27 @@ local function init(panel, units)
local system = Div{parent=panel,width=14,height=18,x=2,y=3} local system = Div{parent=panel,width=14,height=18,x=2,y=3}
local on = LED{parent=system,label="POWER",colors=cpair(colors.green,colors.red)} local on = LED{parent=system,label="STATUS",colors=cpair(colors.green,colors.red)}
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=cpair(colors.green,colors.green_off)} local heartbeat = LED{parent=system,label="HEARTBEAT",colors=cpair(colors.green,colors.green_off)}
on.update(true) on.update(true)
system.line_break() 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 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}} local network = RGBLED{parent=system,label="NETWORK",colors={colors.green,colors.red,colors.orange,colors.yellow,colors.gray}}
network.update(5) network.update(5)
system.line_break() system.line_break()
databus.rx_field("has_modem", modem.update) modem.register(databus.ps, "has_modem", modem.update)
databus.rx_field("link_state", network.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_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)} local rt_comm = LED{parent=system,label="RT COMMS",colors=cpair(colors.green,colors.green_off)}
system.line_break() system.line_break()
databus.rx_field("routine__main", rt_main.update) rt_main.register(databus.ps, "routine__main", rt_main.update)
databus.rx_field("routine__comms", rt_comm.update) rt_comm.register(databus.ps, "routine__comms", rt_comm.update)
-- --
-- about label -- 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 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} 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) fw_v.register(databus.ps, "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) comms_v.register(databus.ps, "comms_version", function (version) comms_v.set_value(util.c("NT: v", version)) end)
-- --
-- unit status list -- unit status list
@ -90,7 +90,7 @@ local function init(panel, units)
for i = 1, list_length do for i = 1, list_length do
TextBox{parent=threads,x=1,y=i,text=util.sprintf("%02d",i),height=1} 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)} 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 end
local unit_hw_statuses = Div{parent=panel,height=18,x=25,y=3} local unit_hw_statuses = Div{parent=panel,height=18,x=25,y=3}
@ -102,13 +102,13 @@ local function init(panel, units)
-- hardware status -- hardware status
local unit_hw = RGBLED{parent=unit_hw_statuses,y=i,label="",colors={colors.red,colors.orange,colors.yellow,colors.green}} 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) -- unit name identifier (type + index)
local name = util.c(UNIT_TYPE_LABELS[unit.type + 1], " ", unit.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} 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)) name_box.set_value(util.c(UNIT_TYPE_LABELS[t + 1], " ", unit.index))
end) end)

View File

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

View File

@ -45,10 +45,8 @@ function renderer.close_ui()
-- stop blinking indicators -- stop blinking indicators
flasher.clear() flasher.clear()
-- hide to stop animation callbacks -- delete element tree
ui.display.hide() ui.display.delete()
-- clear root UI elements
ui.display = nil ui.display = nil
-- restore colors -- restore colors
@ -71,9 +69,9 @@ end
function renderer.ui_ready() return ui.display ~= nil end function renderer.ui_ready() return ui.display ~= nil end
-- handle a mouse event -- handle a mouse event
---@param event mouse_interaction ---@param event mouse_interaction|nil
function renderer.handle_mouse(event) function renderer.handle_mouse(event)
if ui.display ~= nil then if ui.display ~= nil and event ~= nil then
ui.display.handle_mouse(event) ui.display.handle_mouse(event)
end end
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 sps_rtu = require("rtu.dev.sps_rtu")
local turbinev_rtu = require("rtu.dev.turbinev_rtu") local turbinev_rtu = require("rtu.dev.turbinev_rtu")
local RTU_VERSION = "v1.0.5" local RTU_VERSION = "v1.2.8"
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE local RTU_UNIT_HW_STATE = databus.RTU_UNIT_HW_STATE
@ -49,7 +49,6 @@ cfv.assert_type_num(config.COMMS_TIMEOUT)
cfv.assert_min(config.COMMS_TIMEOUT, 2) cfv.assert_min(config.COMMS_TIMEOUT, 2)
cfv.assert_type_str(config.LOG_PATH) cfv.assert_type_str(config.LOG_PATH)
cfv.assert_type_int(config.LOG_MODE) cfv.assert_type_int(config.LOG_MODE)
cfv.assert_type_bool(config.LOG_DEBUG)
cfv.assert_type_table(config.RTU_DEVICES) cfv.assert_type_table(config.RTU_DEVICES)
cfv.assert_type_table(config.RTU_REDSTONE) cfv.assert_type_table(config.RTU_REDSTONE)
assert(cfv.valid(), "bad config file: missing/invalid fields") assert(cfv.valid(), "bad config file: missing/invalid fields")
@ -58,7 +57,7 @@ assert(cfv.valid(), "bad config file: missing/invalid fields")
-- log init -- log init
---------------------------------------- ----------------------------------------
log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG) log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG == true)
log.info("========================================") log.info("========================================")
log.info("BOOTING rtu.startup " .. RTU_VERSION) log.info("BOOTING rtu.startup " .. RTU_VERSION)
@ -458,9 +457,9 @@ local function main()
if not rtu_state.fp_ok then if not rtu_state.fp_ok then
renderer.close_ui() renderer.close_ui()
println_ts(util.c("UI error: ", message)) println_ts(util.c("UI error: ", message))
println("init> running without front panel") println("startup> running without front panel")
log.error(util.c("GUI crashed with error ", message)) log.error(util.c("GUI crashed with error ", message))
log.info("init> running in headless mode without front panel") log.info("startup> running in headless mode without front panel")
end end
-- start connection watchdog -- start connection watchdog

View File

@ -1,6 +1,7 @@
local log = require("scada-common.log") local log = require("scada-common.log")
local mqueue = require("scada-common.mqueue") local mqueue = require("scada-common.mqueue")
local ppm = require("scada-common.ppm") local ppm = require("scada-common.ppm")
local tcd = require("scada-common.tcd")
local types = require("scada-common.types") local types = require("scada-common.types")
local util = require("scada-common.util") local util = require("scada-common.util")
@ -82,6 +83,9 @@ function threads.thread__main(smem)
elseif event == "timer" and conn_watchdog.is_timer(param1) then elseif event == "timer" and conn_watchdog.is_timer(param1) then
-- haven't heard from server recently? unlink -- haven't heard from server recently? unlink
rtu_comms.unlink(rtu_state) rtu_comms.unlink(rtu_state)
elseif event == "timer" then
-- notify timer callback dispatcher if no other timer case claimed this event
tcd.handle(param1)
elseif event == "peripheral_detach" then elseif event == "peripheral_detach" then
-- handle loss of a device -- handle loss of a device
local type, device = ppm.handle_unmount(param1) local type, device = ppm.handle_unmount(param1)
@ -229,9 +233,9 @@ function threads.thread__main(smem)
end end
end end
end end
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 -- handle a mouse event
renderer.handle_mouse(core.events.click(param1, param2, param3)) renderer.handle_mouse(core.events.new_mouse_event(event, param1, param2, param3))
end end
-- check for termination request -- check for termination request

View File

@ -2,6 +2,8 @@
-- Publisher-Subscriber Interconnect Layer -- Publisher-Subscriber Interconnect Layer
-- --
local util = require("scada-common.util")
local psil = {} local psil = {}
-- instantiate a new PSI layer -- instantiate a new PSI layer
@ -36,6 +38,15 @@ function psil.create()
table.insert(self.ic[key].subscribers, { notify = func }) table.insert(self.ic[key].subscribers, { notify = func })
end 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 -- publish data to a given key, passing it to all subscribers if it has changed
---@param key string data key ---@param key string data key
---@param value any data value ---@param value any data value
@ -64,6 +75,9 @@ function psil.create()
end end
end end
-- clear the contents of the interconnect
function public.purge() self.ic = nil end
return public return public
end end

View File

@ -5,14 +5,14 @@
local log = require("scada-common.log") local log = require("scada-common.log")
local util = require("scada-common.util") local util = require("scada-common.util")
local tcallbackdsp = {} local tcd = {}
local registry = {} local registry = {}
-- request a function to be called after the specified time -- request a function to be called after the specified time
---@param time number seconds ---@param time number seconds
---@param f function callback function ---@param f function callback function
function tcallbackdsp.dispatch(time, f) function tcd.dispatch(time, f)
local timer = util.start_timer(time) local timer = util.start_timer(time)
registry[timer] = { registry[timer] = {
callback = f, callback = f,
@ -24,7 +24,7 @@ end
-- request a function to be called after the specified time, aborting any registered instances of that function reference -- request a function to be called after the specified time, aborting any registered instances of that function reference
---@param time number seconds ---@param time number seconds
---@param f function callback function ---@param f function callback function
function tcallbackdsp.dispatch_unique(time, f) function tcd.dispatch_unique(time, f)
-- cancel if already registered -- cancel if already registered
for timer, entry in pairs(registry) do for timer, entry in pairs(registry) do
if entry.callback == f then if entry.callback == f then
@ -47,7 +47,7 @@ end
-- abort a requested callback -- abort a requested callback
---@param f function callback function ---@param f function callback function
function tcallbackdsp.abort(f) function tcd.abort(f)
for timer, entry in pairs(registry) do for timer, entry in pairs(registry) do
if entry.callback == f then if entry.callback == f then
-- cancel event and remove from registry (even if it fires it won't call) -- cancel event and remove from registry (even if it fires it won't call)
@ -59,7 +59,7 @@ end
-- lookup a timer event and execute the callback if found -- lookup a timer event and execute the callback if found
---@param event integer timer event timer ID ---@param event integer timer event timer ID
function tcallbackdsp.handle(event) function tcd.handle(event)
if registry[event] ~= nil then if registry[event] ~= nil then
local callback = registry[event].callback local callback = registry[event].callback
-- clear first so that dispatch_unique call from inside callback won't throw a debug message -- clear first so that dispatch_unique call from inside callback won't throw a debug message
@ -70,7 +70,7 @@ end
-- identify any overdo callbacks<br> -- identify any overdo callbacks<br>
-- prints to log debug output -- prints to log debug output
function tcallbackdsp.diagnostics() function tcd.diagnostics()
for timer, entry in pairs(registry) do for timer, entry in pairs(registry) do
if entry.expiry < util.time_s() then if entry.expiry < util.time_s() then
local overtime = util.time_s() - entry.expiry local overtime = util.time_s() - entry.expiry
@ -82,4 +82,4 @@ function tcallbackdsp.diagnostics()
end end
end end
return tcallbackdsp return tcd

View File

@ -39,6 +39,10 @@ function types.new_radiation_reading(r, u) return { radiation = r, unit = u } en
---@return radiation_reading ---@return radiation_reading
function types.new_zero_radiation_reading() return { radiation = 0, unit = "nSv" } end function types.new_zero_radiation_reading() return { radiation = 0, unit = "nSv" } end
---@class coordinate_2d
---@field x integer
---@field y integer
---@class coordinate ---@class coordinate
---@field x integer ---@field x integer
---@field y integer ---@field y integer

174
supervisor/databus.lua Normal file
View File

@ -0,0 +1,174 @@
--
-- Data Bus - Central Communication Linking for Supervisor Front Panel
--
local psil = require("scada-common.psil")
local pgi = require("supervisor.panel.pgi")
local databus = {}
-- databus PSIL
databus.ps = psil.create()
-- call to toggle heartbeat signal
function databus.heartbeat() databus.ps.toggle("heartbeat") end
-- transmit firmware versions across the bus
---@param sv_v string supervisor version
---@param comms_v string comms version
function databus.tx_versions(sv_v, comms_v)
databus.ps.publish("version", sv_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)
databus.ps.publish("has_modem", has_modem)
end
-- transmit PLC firmware version and session connection state
---@param reactor_id integer reactor unit ID
---@param fw string firmware version
---@param channel integer PLC remote port
function databus.tx_plc_connected(reactor_id, fw, channel)
databus.ps.publish("plc_" .. reactor_id .. "_fw", fw)
databus.ps.publish("plc_" .. reactor_id .. "_conn", true)
databus.ps.publish("plc_" .. reactor_id .. "_chan", tostring(channel))
end
-- transmit PLC disconnected
---@param reactor_id integer reactor unit ID
function databus.tx_plc_disconnected(reactor_id)
databus.ps.publish("plc_" .. reactor_id .. "_fw", " ------- ")
databus.ps.publish("plc_" .. reactor_id .. "_conn", false)
databus.ps.publish("plc_" .. reactor_id .. "_chan", " --- ")
databus.ps.publish("plc_" .. reactor_id .. "_rtt", 0)
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.lightGray)
end
-- transmit PLC session RTT
---@param reactor_id integer reactor unit ID
---@param rtt integer round trip time
function databus.tx_plc_rtt(reactor_id, rtt)
databus.ps.publish("plc_" .. reactor_id .. "_rtt", rtt)
if rtt > 700 then
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.red)
elseif rtt > 300 then
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.yellow_hc)
else
databus.ps.publish("plc_" .. reactor_id .. "_rtt_color", colors.green)
end
end
-- transmit RTU firmware version and session connection state
---@param session_id integer RTU session
---@param fw string firmware version
---@param channel integer RTU remote port
function databus.tx_rtu_connected(session_id, fw, channel)
databus.ps.publish("rtu_" .. session_id .. "_fw", fw)
databus.ps.publish("rtu_" .. session_id .. "_chan", tostring(channel))
pgi.create_rtu_entry(session_id)
end
-- transmit RTU disconnected
---@param session_id integer RTU session
function databus.tx_rtu_disconnected(session_id)
pgi.delete_rtu_entry(session_id)
end
-- transmit RTU session RTT
---@param session_id integer RTU session
---@param rtt integer round trip time
function databus.tx_rtu_rtt(session_id, rtt)
databus.ps.publish("rtu_" .. session_id .. "_rtt", rtt)
if rtt > 700 then
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.red)
elseif rtt > 300 then
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.yellow_hc)
else
databus.ps.publish("rtu_" .. session_id .. "_rtt_color", colors.green)
end
end
-- transmit RTU session unit count
---@param session_id integer RTU session
---@param units integer unit count
function databus.tx_rtu_units(session_id, units)
databus.ps.publish("rtu_" .. session_id .. "_units", units)
end
-- transmit coordinator firmware version and session connection state
---@param fw string firmware version
---@param channel integer coordinator remote port
function databus.tx_crd_connected(fw, channel)
databus.ps.publish("crd_fw", fw)
databus.ps.publish("crd_conn", true)
databus.ps.publish("crd_chan", tostring(channel))
end
-- transmit coordinator disconnected
function databus.tx_crd_disconnected()
databus.ps.publish("crd_fw", " ------- ")
databus.ps.publish("crd_conn", false)
databus.ps.publish("crd_chan", "---")
databus.ps.publish("crd_rtt", 0)
databus.ps.publish("crd_rtt_color", colors.lightGray)
end
-- transmit coordinator session RTT
---@param rtt integer round trip time
function databus.tx_crd_rtt(rtt)
databus.ps.publish("crd_rtt", rtt)
if rtt > 700 then
databus.ps.publish("crd_rtt_color", colors.red)
elseif rtt > 300 then
databus.ps.publish("crd_rtt_color", colors.yellow_hc)
else
databus.ps.publish("crd_rtt_color", colors.green)
end
end
-- transmit PKT firmware version and PDG session connection state
---@param session_id integer PDG session
---@param fw string firmware version
---@param channel integer PDG remote port
function databus.tx_pdg_connected(session_id, fw, channel)
databus.ps.publish("pdg_" .. session_id .. "_fw", fw)
databus.ps.publish("pdg_" .. session_id .. "_chan", tostring(channel))
pgi.create_pdg_entry(session_id)
end
-- transmit PDG session disconnected
---@param session_id integer PDG session
function databus.tx_pdg_disconnected(session_id)
pgi.delete_pdg_entry(session_id)
end
-- transmit PDG session RTT
---@param session_id integer PDG session
---@param rtt integer round trip time
function databus.tx_pdg_rtt(session_id, rtt)
databus.ps.publish("pdg_" .. session_id .. "_rtt", rtt)
if rtt > 700 then
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.red)
elseif rtt > 300 then
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.yellow_hc)
else
databus.ps.publish("pdg_" .. session_id .. "_rtt_color", colors.green)
end
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)
databus.ps.subscribe(field, func)
end
return databus

View File

@ -128,7 +128,7 @@ function facility.new(num_reactors, cooling_conf)
for i = 1, #self.prio_defs do for i = 1, #self.prio_defs do
local units = self.prio_defs[i] local units = self.prio_defs[i]
for u = 1, #units do for u = 1, #units do
all_ramped = all_ramped and units[u].a_ramp_complete() all_ramped = all_ramped and units[u].auto_ramp_complete()
end end
end end
@ -159,7 +159,7 @@ function facility.new(num_reactors, cooling_conf)
local u = units[id] ---@type reactor_unit local u = units[id] ---@type reactor_unit
local ctl = u.get_control_inf() local ctl = u.get_control_inf()
local lim_br100 = u.a_get_effective_limit() local lim_br100 = u.auto_get_effective_limit()
if abort_on_fault and (lim_br100 ~= ctl.lim_br100) then if abort_on_fault and (lim_br100 ~= ctl.lim_br100) then
-- effective limit differs from set limit, unit is degraded -- effective limit differs from set limit, unit is degraded
@ -183,7 +183,7 @@ function facility.new(num_reactors, cooling_conf)
unallocated = math.max(0, unallocated - ctl.br100) unallocated = math.max(0, unallocated - ctl.br100)
if last ~= ctl.br100 then u.a_commit_br100(ramp) end if last ~= ctl.br100 then u.auto_commit_br100(ramp) end
end end
end end
end end
@ -320,7 +320,7 @@ function facility.new(num_reactors, cooling_conf)
self.start_fail = START_STATUS.BLADE_MISMATCH self.start_fail = START_STATUS.BLADE_MISMATCH
end end
if self.start_fail == START_STATUS.OK then u.a_engage() end if self.start_fail == START_STATUS.OK then u.auto_engage() end
self.max_burn_combined = self.max_burn_combined + (u.get_control_inf().lim_br100 / 100.0) self.max_burn_combined = self.max_burn_combined + (u.get_control_inf().lim_br100 / 100.0)
end end
@ -340,7 +340,7 @@ function facility.new(num_reactors, cooling_conf)
-- use manual SCRAM since inactive was requested, and automatic SCRAM trips an alarm -- use manual SCRAM since inactive was requested, and automatic SCRAM trips an alarm
for _, u in pairs(self.prio_defs[i]) do for _, u in pairs(self.prio_defs[i]) do
u.scram() u.scram()
u.a_disengage() u.auto_disengage()
end end
end end
@ -601,7 +601,7 @@ function facility.new(num_reactors, cooling_conf)
-- SCRAM all units -- SCRAM all units
for i = 1, #self.prio_defs do for i = 1, #self.prio_defs do
for _, u in pairs(self.prio_defs[i]) do for _, u in pairs(self.prio_defs[i]) do
u.a_scram() u.auto_scram()
end end
end end
@ -653,7 +653,7 @@ function facility.new(num_reactors, cooling_conf)
-- reset PLC RPS trips if we should -- reset PLC RPS trips if we should
for i = 1, #self.units do for i = 1, #self.units do
local u = self.units[i] ---@type reactor_unit local u = self.units[i] ---@type reactor_unit
u.a_cond_rps_reset() u.auto_cond_rps_reset()
end end
end end
end end

View File

@ -0,0 +1,48 @@
--
-- Pocket Diagnostics Connection Entry
--
local util = require("scada-common.util")
local databus = require("supervisor.databus")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
local DataIndicator = require("graphics.elements.indicators.data")
local TEXT_ALIGN = core.TEXT_ALIGN
local cpair = core.cpair
-- create a pocket diagnostics list entry
---@param parent graphics_element parent
---@param id integer PDG session ID
local function init(parent, id)
-- root div
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2,hidden=true}
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=cpair(colors.black,colors.white)}
local ps_prefix = "pdg_" .. id .. "_"
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
local pdg_chan = TextBox{parent=entry,x=1,y=2,text=" :00000",alignment=TEXT_ALIGN.CENTER,width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray),nav_active=cpair(colors.gray,colors.black)}
TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
pdg_chan.register(databus.ps, ps_prefix .. "chan", function (channel) pdg_chan.set_value(util.sprintf(" :%05d", channel)) end)
TextBox{parent=entry,x=10,y=2,text="FW:",width=3,height=1}
local pdg_fw_v = TextBox{parent=entry,x=14,y=2,text=" ------- ",width=20,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
pdg_fw_v.register(databus.ps, ps_prefix .. "fw", pdg_fw_v.set_value)
TextBox{parent=entry,x=35,y=2,text="RTT:",width=4,height=1}
local pdg_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=cpair(colors.lightGray,colors.white)}
TextBox{parent=entry,x=46,y=2,text="ms",width=4,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
pdg_rtt.register(databus.ps, ps_prefix .. "rtt", pdg_rtt.update)
pdg_rtt.register(databus.ps, ps_prefix .. "rtt_color", pdg_rtt.recolor)
return root
end
return init

View File

@ -0,0 +1,52 @@
--
-- RTU Connection Entry
--
local util = require("scada-common.util")
local databus = require("supervisor.databus")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local TextBox = require("graphics.elements.textbox")
local DataIndicator = require("graphics.elements.indicators.data")
local TEXT_ALIGN = core.TEXT_ALIGN
local cpair = core.cpair
-- create an RTU list entry
---@param parent graphics_element parent
---@param id integer RTU session ID
local function init(parent, id)
-- root div
local root = Div{parent=parent,x=2,y=2,height=4,width=parent.get_width()-2,hidden=true}
local entry = Div{parent=root,x=2,y=1,height=3,fg_bg=cpair(colors.black,colors.white)}
local ps_prefix = "rtu_" .. id .. "_"
TextBox{parent=entry,x=1,y=1,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
local rtu_chan = TextBox{parent=entry,x=1,y=2,text=" :00000",alignment=TEXT_ALIGN.CENTER,width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray),nav_active=cpair(colors.gray,colors.black)}
TextBox{parent=entry,x=1,y=3,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
rtu_chan.register(databus.ps, ps_prefix .. "chan", function (channel) rtu_chan.set_value(util.sprintf(" :%05d", channel)) end)
TextBox{parent=entry,x=10,y=2,text="UNITS:",width=7,height=1}
local unit_count = DataIndicator{parent=entry,x=17,y=2,label="",unit="",format="%2d",value=0,width=2,fg_bg=cpair(colors.gray,colors.white)}
unit_count.register(databus.ps, ps_prefix .. "units", unit_count.set_value)
TextBox{parent=entry,x=21,y=2,text="FW:",width=3,height=1}
local rtu_fw_v = TextBox{parent=entry,x=25,y=2,text=" ------- ",width=9,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
rtu_fw_v.register(databus.ps, ps_prefix .. "fw", rtu_fw_v.set_value)
TextBox{parent=entry,x=36,y=2,text="RTT:",width=4,height=1}
local rtu_rtt = DataIndicator{parent=entry,x=40,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=cpair(colors.lightGray,colors.white)}
TextBox{parent=entry,x=46,y=2,text="ms",width=4,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
rtu_rtt.register(databus.ps, ps_prefix .. "rtt", rtu_rtt.update)
rtu_rtt.register(databus.ps, ps_prefix .. "rtt_color", rtu_rtt.recolor)
return root
end
return init

View File

@ -0,0 +1,160 @@
--
-- Main SCADA Coordinator GUI
--
local util = require("scada-common.util")
local config = require("supervisor.config")
local databus = require("supervisor.databus")
local pgi = require("supervisor.panel.pgi")
local style = require("supervisor.panel.style")
local pdg_entry = require("supervisor.panel.components.pdg_entry")
local rtu_entry = require("supervisor.panel.components.rtu_entry")
local core = require("graphics.core")
local Div = require("graphics.elements.div")
local ListBox = require("graphics.elements.listbox")
local MultiPane = require("graphics.elements.multipane")
local TextBox = require("graphics.elements.textbox")
local TabBar = require("graphics.elements.controls.tabbar")
local LED = require("graphics.elements.indicators.led")
local DataIndicator = require("graphics.elements.indicators.data")
local TEXT_ALIGN = core.TEXT_ALIGN
local cpair = core.cpair
-- create new main view
---@param panel graphics_element main displaybox
local function init(panel)
TextBox{parent=panel,y=1,text="SCADA SUPERVISOR",alignment=TEXT_ALIGN.CENTER,height=1,fg_bg=style.header}
local page_div = Div{parent=panel,x=1,y=3}
--
-- system indicators
--
local main_page = Div{parent=page_div,x=1,y=1}
local system = Div{parent=main_page,width=14,height=17,x=2,y=2}
local on = LED{parent=system,label="STATUS",colors=cpair(colors.green,colors.red)}
local heartbeat = LED{parent=system,label="HEARTBEAT",colors=cpair(colors.green,colors.green_off)}
on.update(true)
system.line_break()
heartbeat.register(databus.ps, "heartbeat", heartbeat.update)
local modem = LED{parent=system,label="MODEM",colors=cpair(colors.green,colors.green_off)}
system.line_break()
modem.register(databus.ps, "has_modem", modem.update)
--
-- about footer
--
local about = Div{parent=main_page,width=15,height=3,x=1,y=16,fg_bg=cpair(colors.lightGray,colors.ivory)}
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}
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)
--
-- page handling
--
-- plc page
local plc_page = Div{parent=page_div,x=1,y=1,hidden=true}
local plc_list = Div{parent=plc_page,x=2,y=2,width=49}
for i = 1, config.NUM_REACTORS do
local ps_prefix = "plc_" .. i .. "_"
local plc_entry = Div{parent=plc_list,height=3,fg_bg=cpair(colors.black,colors.white)}
TextBox{parent=plc_entry,x=1,y=1,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
TextBox{parent=plc_entry,x=1,y=2,text="UNIT "..i,alignment=TEXT_ALIGN.CENTER,width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
TextBox{parent=plc_entry,x=1,y=3,text="",width=8,height=1,fg_bg=cpair(colors.black,colors.lightGray)}
local conn = LED{parent=plc_entry,x=10,y=2,label="CONN",colors=cpair(colors.green,colors.green_off)}
conn.register(databus.ps, ps_prefix .. "conn", conn.update)
local plc_chan = TextBox{parent=plc_entry,x=17,y=2,text=" --- ",width=5,height=1,fg_bg=cpair(colors.gray,colors.white)}
plc_chan.register(databus.ps, ps_prefix .. "chan", plc_chan.set_value)
TextBox{parent=plc_entry,x=23,y=2,text="FW:",width=3,height=1}
local plc_fw_v = TextBox{parent=plc_entry,x=27,y=2,text=" ------- ",width=9,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
plc_fw_v.register(databus.ps, ps_prefix .. "fw", plc_fw_v.set_value)
TextBox{parent=plc_entry,x=37,y=2,text="RTT:",width=4,height=1}
local plc_rtt = DataIndicator{parent=plc_entry,x=42,y=2,label="",unit="",format="%4d",value=0,width=4,fg_bg=cpair(colors.lightGray,colors.white)}
TextBox{parent=plc_entry,x=47,y=2,text="ms",width=4,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
plc_rtt.register(databus.ps, ps_prefix .. "rtt", plc_rtt.update)
plc_rtt.register(databus.ps, ps_prefix .. "rtt_color", plc_rtt.recolor)
plc_list.line_break()
end
-- rtu page
local rtu_page = Div{parent=page_div,x=1,y=1,hidden=true}
local rtu_list = ListBox{parent=rtu_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=cpair(colors.black,colors.ivory),nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
local _ = Div{parent=rtu_list,height=1,hidden=true} -- padding
-- coordinator page
local crd_page = Div{parent=page_div,x=1,y=1,hidden=true}
local crd_box = Div{parent=crd_page,x=2,y=2,width=49,height=4,fg_bg=cpair(colors.black,colors.white)}
local crd_conn = LED{parent=crd_box,x=2,y=2,label="CONNECTION",colors=cpair(colors.green,colors.green_off)}
crd_conn.register(databus.ps, "crd_conn", crd_conn.update)
TextBox{parent=crd_box,x=4,y=3,text="CHANNEL ",width=8,height=1,fg_bg=cpair(colors.gray,colors.white)}
local crd_chan = TextBox{parent=crd_box,x=12,y=3,text="---",width=5,height=1,fg_bg=cpair(colors.gray,colors.white)}
crd_chan.register(databus.ps, "crd_chan", crd_chan.set_value)
TextBox{parent=crd_box,x=22,y=2,text="FW:",width=3,height=1}
local crd_fw_v = TextBox{parent=crd_box,x=26,y=2,text=" ------- ",width=9,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
crd_fw_v.register(databus.ps, "crd_fw", crd_fw_v.set_value)
TextBox{parent=crd_box,x=36,y=2,text="RTT:",width=4,height=1}
local crd_rtt = DataIndicator{parent=crd_box,x=41,y=2,label="",unit="",format="%5d",value=0,width=5,fg_bg=cpair(colors.lightGray,colors.white)}
TextBox{parent=crd_box,x=47,y=2,text="ms",width=4,height=1,fg_bg=cpair(colors.lightGray,colors.white)}
crd_rtt.register(databus.ps, "crd_rtt", crd_rtt.update)
crd_rtt.register(databus.ps, "crd_rtt_color", crd_rtt.recolor)
-- pocket diagnostics page
local pkt_page = Div{parent=page_div,x=1,y=1,hidden=true}
local pdg_list = ListBox{parent=pkt_page,x=1,y=1,height=17,width=51,scroll_height=1000,fg_bg=cpair(colors.black,colors.ivory),nav_fg_bg=cpair(colors.gray,colors.lightGray),nav_active=cpair(colors.black,colors.gray)}
local _ = Div{parent=pdg_list,height=1,hidden=true} -- padding
-- assemble page panes
local panes = { main_page, plc_page, rtu_page, crd_page, pkt_page }
local page_pane = MultiPane{parent=page_div,x=1,y=1,panes=panes}
local tabs = {
{ name = "SVR", color = cpair(colors.black, colors.ivory) },
{ name = "PLC", color = cpair(colors.black, colors.ivory) },
{ name = "RTU", color = cpair(colors.black, colors.ivory) },
{ name = "CRD", color = cpair(colors.black, colors.ivory) },
{ name = "PKT", color = cpair(colors.black, colors.ivory) },
}
TabBar{parent=panel,y=2,tabs=tabs,min_width=9,callback=page_pane.set_value,fg_bg=cpair(colors.black,colors.white)}
-- link RTU/PDG list management to PGI
pgi.link_elements(rtu_list, rtu_entry, pdg_list, pdg_entry)
end
return init

93
supervisor/panel/pgi.lua Normal file
View File

@ -0,0 +1,93 @@
--
-- Protected Graphics Interface
--
local log = require("scada-common.log")
local util = require("scada-common.util")
local pgi = {}
local data = {
rtu_list = nil, ---@type nil|graphics_element
pdg_list = nil, ---@type nil|graphics_element
rtu_entry = nil, ---@type function
pdg_entry = nil, ---@type function
-- session entries
s_entries = { rtu = {}, pdg = {} }
}
-- link list boxes
---@param rtu_list graphics_element RTU list element
---@param rtu_entry function RTU entry constructor
---@param pdg_list graphics_element pocket diagnostics list element
---@param pdg_entry function pocket diagnostics entry constructor
function pgi.link_elements(rtu_list, rtu_entry, pdg_list, pdg_entry)
data.rtu_list = rtu_list
data.pdg_list = pdg_list
data.rtu_entry = rtu_entry
data.pdg_entry = pdg_entry
end
-- unlink all fields, disabling the PGI
function pgi.unlink()
data.rtu_list = nil
data.pdg_list = nil
data.rtu_entry = nil
data.pdg_entry = nil
end
-- add an RTU entry to the RTU list
---@param session_id integer RTU session
function pgi.create_rtu_entry(session_id)
if data.rtu_list ~= nil and data.rtu_entry ~= nil then
local success, result = pcall(data.rtu_entry, data.rtu_list, session_id)
if success then
data.s_entries.rtu[session_id] = result
else
log.error(util.c("PGI: failed to create RTU entry (", result, ")"), true)
end
end
end
-- delete an RTU entry from the RTU list
---@param session_id integer RTU session
function pgi.delete_rtu_entry(session_id)
if data.s_entries.rtu[session_id] ~= nil then
local success, result = pcall(data.s_entries.rtu[session_id].delete)
data.s_entries.rtu[session_id] = nil
if not success then
log.error(util.c("PGI: failed to delete RTU entry (", result, ")"), true)
end
end
end
-- add a PDG entry to the PDG list
---@param session_id integer pocket diagnostics session
function pgi.create_pdg_entry(session_id)
if data.pdg_list ~= nil and data.pdg_entry ~= nil then
local success, result = pcall(data.pdg_entry, data.pdg_list, session_id)
if success then
data.s_entries.pdg[session_id] = result
else
log.error(util.c("PGI: failed to create PDG entry (", result, ")"), true)
end
end
end
-- delete a PDG entry from the PDG list
---@param session_id integer pocket diagnostics session
function pgi.delete_pdg_entry(session_id)
if data.s_entries.pdg[session_id] ~= nil then
local success, result = pcall(data.s_entries.pdg[session_id].delete)
data.s_entries.pdg[session_id] = nil
if not success then
log.error(util.c("PGI: failed to delete PDG entry (", result, ")"), true)
end
end
end
return pgi

View File

@ -0,0 +1,42 @@
--
-- Graphics Style Options
--
local core = require("graphics.core")
local style = {}
local cpair = core.cpair
-- GLOBAL --
-- remap global colors
colors.ivory = colors.pink
colors.yellow_hc = colors.purple
colors.red_off = colors.brown
colors.yellow_off = colors.magenta
colors.green_off = colors.lime
style.root = cpair(colors.black, colors.ivory)
style.header = cpair(colors.black, colors.lightGray)
style.colors = {
{ c = colors.red, hex = 0xdf4949 }, -- RED ON
{ c = colors.orange, hex = 0xffb659 },
{ c = colors.yellow, hex = 0xf9fb53 }, -- YELLOW ON
{ c = colors.lime, hex = 0x16665a }, -- GREEN OFF
{ c = colors.green, hex = 0x6be551 }, -- GREEN ON
{ c = colors.cyan, hex = 0x34bac8 },
{ c = colors.lightBlue, hex = 0x6cc0f2 },
{ c = colors.blue, hex = 0x0008fe }, -- LCD BLUE
{ c = colors.purple, hex = 0xe3bc2a }, -- YELLOW HIGH CONTRAST
{ c = colors.pink, hex = 0xdcd9ca }, -- IVORY
{ c = colors.magenta, hex = 0x85862c }, -- YELLOW OFF
-- { c = colors.white, hex = 0xdcd9ca },
{ c = colors.lightGray, hex = 0xb1b8b3 },
{ c = colors.gray, hex = 0x575757 },
-- { c = colors.black, hex = 0x191919 },
{ c = colors.brown, hex = 0x672223 } -- RED OFF
}
return style

84
supervisor/renderer.lua Normal file
View File

@ -0,0 +1,84 @@
--
-- Graphics Rendering Control
--
local panel_view = require("supervisor.panel.front_panel")
local pgi = require("supervisor.panel.pgi")
local style = require("supervisor.panel.style")
local flasher = require("graphics.flasher")
local DisplayBox = require("graphics.elements.displaybox")
local renderer = {}
local ui = {
display = nil
}
-- start the UI
function renderer.start_ui()
if ui.display == nil then
-- reset terminal
term.setTextColor(colors.white)
term.setBackgroundColor(colors.black)
term.clear()
term.setCursorPos(1, 1)
-- set overridden colors
for i = 1, #style.colors do
term.setPaletteColor(style.colors[i].c, style.colors[i].hex)
end
-- init front panel view
ui.display = DisplayBox{window=term.current(),fg_bg=style.root}
panel_view(ui.display)
-- start flasher callback task
flasher.run()
end
end
-- close out the UI
function renderer.close_ui()
if ui.display ~= nil then
-- stop blinking indicators
flasher.clear()
-- disable PGI
pgi.unlink()
-- hide to stop animation callbacks
ui.display.hide()
-- clear root UI elements
ui.display = nil
-- restore colors
for i = 1, #style.colors do
local r, g, b = term.nativePaletteColor(style.colors[i].c)
term.setPaletteColor(style.colors[i].c, r, g, b)
end
-- reset terminal
term.setTextColor(colors.white)
term.setBackgroundColor(colors.black)
term.clear()
term.setCursorPos(1, 1)
end
end
-- is the UI ready?
---@nodiscard
---@return boolean ready
function renderer.ui_ready() return ui.display ~= nil end
-- handle a mouse event
---@param event mouse_interaction|nil
function renderer.handle_mouse(event)
if ui.display ~= nil and event ~= nil then
ui.display.handle_mouse(event)
end
end
return renderer

View File

@ -4,6 +4,8 @@ local mqueue = require("scada-common.mqueue")
local types = require("scada-common.types") local types = require("scada-common.types")
local util = require("scada-common.util") local util = require("scada-common.util")
local databus = require("supervisor.databus")
local svqtypes = require("supervisor.session.svqtypes") local svqtypes = require("supervisor.session.svqtypes")
local coordinator = {} local coordinator = {}
@ -18,8 +20,6 @@ local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
local SV_Q_DATA = svqtypes.SV_Q_DATA local SV_Q_DATA = svqtypes.SV_Q_DATA
local println = util.println
-- retry time constants in ms -- retry time constants in ms
-- local INITIAL_WAIT = 1500 -- local INITIAL_WAIT = 1500
local RETRY_PERIOD = 1000 local RETRY_PERIOD = 1000
@ -49,7 +49,11 @@ local PERIODICS = {
---@param out_queue mqueue out message queue ---@param out_queue mqueue out message queue
---@param timeout number communications timeout ---@param timeout number communications timeout
---@param facility facility facility data table ---@param facility facility facility data table
function coordinator.new_session(id, in_queue, out_queue, timeout, facility) ---@param fp_ok boolean if the front panel UI is running
function coordinator.new_session(id, in_queue, out_queue, timeout, facility, fp_ok)
-- print a log message to the terminal as long as the UI isn't running
local function println(message) if not fp_ok then util.println_ts(message) end end
local log_header = "crdn_session(" .. id .. "): " local log_header = "crdn_session(" .. id .. "): "
local self = { local self = {
@ -84,6 +88,7 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility)
local function _close() local function _close()
self.conn_watchdog.cancel() self.conn_watchdog.cancel()
self.connected = false self.connected = false
databus.tx_crd_disconnected()
end end
-- send a CRDN packet -- send a CRDN packet
@ -205,6 +210,8 @@ function coordinator.new_session(id, in_queue, out_queue, timeout, facility)
-- log.debug(log_header .. "COORD RTT = " .. self.last_rtt .. "ms") -- log.debug(log_header .. "COORD RTT = " .. self.last_rtt .. "ms")
-- log.debug(log_header .. "COORD TT = " .. (srv_now - coord_send) .. "ms") -- log.debug(log_header .. "COORD TT = " .. (srv_now - coord_send) .. "ms")
databus.tx_crd_rtt(self.last_rtt)
else else
log.debug(log_header .. "SCADA keep alive packet length mismatch") log.debug(log_header .. "SCADA keep alive packet length mismatch")
end end

View File

@ -4,6 +4,8 @@ local mqueue = require("scada-common.mqueue")
local types = require("scada-common.types") local types = require("scada-common.types")
local util = require("scada-common.util") local util = require("scada-common.util")
local databus = require("supervisor.databus")
local svqtypes = require("supervisor.session.svqtypes") local svqtypes = require("supervisor.session.svqtypes")
local plc = {} local plc = {}
@ -14,8 +16,6 @@ local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
local PLC_AUTO_ACK = comms.PLC_AUTO_ACK local PLC_AUTO_ACK = comms.PLC_AUTO_ACK
local UNIT_COMMAND = comms.UNIT_COMMAND local UNIT_COMMAND = comms.UNIT_COMMAND
local println = util.println
-- retry time constants in ms -- retry time constants in ms
local INITIAL_WAIT = 1500 local INITIAL_WAIT = 1500
local INITIAL_AUTO_WAIT = 1000 local INITIAL_AUTO_WAIT = 1000
@ -49,7 +49,11 @@ local PERIODICS = {
---@param in_queue mqueue in message queue ---@param in_queue mqueue in message queue
---@param out_queue mqueue out message queue ---@param out_queue mqueue out message queue
---@param timeout number communications timeout ---@param timeout number communications timeout
function plc.new_session(id, reactor_id, in_queue, out_queue, timeout) ---@param fp_ok boolean if the front panel UI is running
function plc.new_session(id, reactor_id, in_queue, out_queue, timeout, fp_ok)
-- print a log message to the terminal as long as the UI isn't running
local function println(message) if not fp_ok then util.println_ts(message) end end
local log_header = "plc_session(" .. id .. "): " local log_header = "plc_session(" .. id .. "): "
local self = { local self = {
@ -235,6 +239,7 @@ function plc.new_session(id, reactor_id, in_queue, out_queue, timeout)
local function _close() local function _close()
self.conn_watchdog.cancel() self.conn_watchdog.cancel()
self.connected = false self.connected = false
databus.tx_plc_disconnected(reactor_id)
end end
-- send an RPLC packet -- send an RPLC packet
@ -485,6 +490,8 @@ function plc.new_session(id, reactor_id, in_queue, out_queue, timeout)
-- log.debug(log_header .. "PLC RTT = " .. self.last_rtt .. "ms") -- log.debug(log_header .. "PLC RTT = " .. self.last_rtt .. "ms")
-- log.debug(log_header .. "PLC TT = " .. (srv_now - plc_send) .. "ms") -- log.debug(log_header .. "PLC TT = " .. (srv_now - plc_send) .. "ms")
databus.tx_plc_rtt(reactor_id, self.last_rtt)
else else
log.debug(log_header .. "SCADA keep alive packet length mismatch") log.debug(log_header .. "SCADA keep alive packet length mismatch")
end end

View File

@ -2,14 +2,13 @@ local comms = require("scada-common.comms")
local log = require("scada-common.log") local log = require("scada-common.log")
local mqueue = require("scada-common.mqueue") local mqueue = require("scada-common.mqueue")
local util = require("scada-common.util") local util = require("scada-common.util")
local databus = require("supervisor.databus")
local pocket = {} local pocket = {}
local PROTOCOL = comms.PROTOCOL local PROTOCOL = comms.PROTOCOL
local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
local println = util.println
-- retry time constants in ms -- retry time constants in ms
-- local INITIAL_WAIT = 1500 -- local INITIAL_WAIT = 1500
-- local RETRY_PERIOD = 1000 -- local RETRY_PERIOD = 1000
@ -33,8 +32,12 @@ local PERIODICS = {
---@param in_queue mqueue in message queue ---@param in_queue mqueue in message queue
---@param out_queue mqueue out message queue ---@param out_queue mqueue out message queue
---@param timeout number communications timeout ---@param timeout number communications timeout
function pocket.new_session(id, in_queue, out_queue, timeout) ---@param fp_ok boolean if the front panel UI is running
local log_header = "diag_session(" .. id .. "): " function pocket.new_session(id, in_queue, out_queue, timeout, fp_ok)
-- print a log message to the terminal as long as the UI isn't running
local function println(message) if not fp_ok then util.println_ts(message) end end
local log_header = "pdg_session(" .. id .. "): "
local self = { local self = {
-- connection properties -- connection properties
@ -55,18 +58,19 @@ function pocket.new_session(id, in_queue, out_queue, timeout)
acks = { acks = {
}, },
-- session database -- session database
---@class diag_db ---@class pdg_db
sDB = { sDB = {
} }
} }
---@class diag_session ---@class pdg_session
local public = {} local public = {}
-- mark this diagnostics session as closed, stop watchdog -- mark this diagnostics session as closed, stop watchdog
local function _close() local function _close()
self.conn_watchdog.cancel() self.conn_watchdog.cancel()
self.connected = false self.connected = false
databus.tx_pdg_disconnected(id)
end end
-- send a SCADA management packet -- send a SCADA management packet
@ -106,16 +110,18 @@ function pocket.new_session(id, in_queue, out_queue, timeout)
-- keep alive reply -- keep alive reply
if pkt.length == 2 then if pkt.length == 2 then
local srv_start = pkt.data[1] local srv_start = pkt.data[1]
-- local diag_send = pkt.data[2] -- local pdg_send = pkt.data[2]
local srv_now = util.time() local srv_now = util.time()
self.last_rtt = srv_now - srv_start self.last_rtt = srv_now - srv_start
if self.last_rtt > 750 then if self.last_rtt > 750 then
log.warning(log_header .. "DIAG KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)") log.warning(log_header .. "PDG KEEP_ALIVE round trip time > 750ms (" .. self.last_rtt .. "ms)")
end end
-- log.debug(log_header .. "DIAG RTT = " .. self.last_rtt .. "ms") -- log.debug(log_header .. "PDG RTT = " .. self.last_rtt .. "ms")
-- log.debug(log_header .. "DIAG TT = " .. (srv_now - diag_send) .. "ms") -- log.debug(log_header .. "PDG TT = " .. (srv_now - pdg_send) .. "ms")
databus.tx_pdg_rtt(id, self.last_rtt)
else else
log.debug(log_header .. "SCADA keep alive packet length mismatch") log.debug(log_header .. "SCADA keep alive packet length mismatch")
end end

View File

@ -4,6 +4,8 @@ local mqueue = require("scada-common.mqueue")
local types = require("scada-common.types") local types = require("scada-common.types")
local util = require("scada-common.util") local util = require("scada-common.util")
local databus = require("supervisor.databus")
local svqtypes = require("supervisor.session.svqtypes") local svqtypes = require("supervisor.session.svqtypes")
-- supervisor rtu sessions (svrs) -- supervisor rtu sessions (svrs)
@ -22,8 +24,6 @@ local PROTOCOL = comms.PROTOCOL
local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE local RTU_UNIT_TYPE = types.RTU_UNIT_TYPE
local println = util.println
local PERIODICS = { local PERIODICS = {
KEEP_ALIVE = 2000 KEEP_ALIVE = 2000
} }
@ -36,7 +36,11 @@ local PERIODICS = {
---@param timeout number communications timeout ---@param timeout number communications timeout
---@param advertisement table RTU device advertisement ---@param advertisement table RTU device advertisement
---@param facility facility facility data table ---@param facility facility facility data table
function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facility) ---@param fp_ok boolean if the front panel UI is running
function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facility, fp_ok)
-- print a log message to the terminal as long as the UI isn't running
local function println(message) if not fp_ok then util.println_ts(message) end end
local log_header = "rtu_session(" .. id .. "): " local log_header = "rtu_session(" .. id .. "): "
local self = { local self = {
@ -66,6 +70,8 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
-- parse the recorded advertisement and create unit sub-sessions -- parse the recorded advertisement and create unit sub-sessions
local function _handle_advertisement() local function _handle_advertisement()
local unit_count = 0
_reset_config() _reset_config()
for i = 1, #self.fac_units do for i = 1, #self.fac_units do
@ -171,24 +177,26 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
end end
if unit ~= nil then if unit ~= nil then
table.insert(self.units, unit) self.units[i] = unit
unit_count = unit_count + 1
elseif u_type ~= RTU_UNIT_TYPE.VIRTUAL then elseif u_type ~= RTU_UNIT_TYPE.VIRTUAL then
_reset_config() _reset_config()
log.error(util.c(log_header, "bad advertisement: error occured while creating a unit (type is ", type_string, ")")) log.error(util.c(log_header, "bad advertisement: error occured while creating a unit (type is ", type_string, ")"))
break break
end end
end end
databus.tx_rtu_units(id, unit_count)
end end
-- mark this RTU session as closed, stop watchdog -- mark this RTU session as closed, stop watchdog
local function _close() local function _close()
self.conn_watchdog.cancel() self.conn_watchdog.cancel()
self.connected = false self.connected = false
databus.tx_rtu_disconnected(id)
-- mark all RTU unit sessions as closed so the reactor unit knows -- mark all RTU unit sessions as closed so the reactor unit knows
for i = 1, #self.units do for _, unit in pairs(self.units) do unit.close() end
self.units[i].close()
end
end end
-- send a MODBUS packet -- send a MODBUS packet
@ -256,6 +264,8 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
-- log.debug(log_header .. "RTU RTT = " .. self.last_rtt .. "ms") -- log.debug(log_header .. "RTU RTT = " .. self.last_rtt .. "ms")
-- log.debug(log_header .. "RTU TT = " .. (srv_now - rtu_send) .. "ms") -- log.debug(log_header .. "RTU TT = " .. (srv_now - rtu_send) .. "ms")
databus.tx_rtu_rtt(id, self.last_rtt)
else else
log.debug(log_header .. "SCADA keep alive packet length mismatch") log.debug(log_header .. "SCADA keep alive packet length mismatch")
end end
@ -351,9 +361,7 @@ function rtu.new_session(id, in_queue, out_queue, timeout, advertisement, facili
local time_now = util.time() local time_now = util.time()
for i = 1, #self.units do for _, unit in pairs(self.units) do unit.update(time_now) end
self.units[i].update(time_now)
end
---------------------- ----------------------
-- update periodics -- -- update periodics --

View File

@ -3,6 +3,7 @@ local mqueue = require("scada-common.mqueue")
local util = require("scada-common.util") local util = require("scada-common.util")
local config = require("supervisor.config") local config = require("supervisor.config")
local databus = require("supervisor.databus")
local facility = require("supervisor.facility") local facility = require("supervisor.facility")
local svqtypes = require("supervisor.session.svqtypes") local svqtypes = require("supervisor.session.svqtypes")
@ -22,24 +23,26 @@ local CRD_S_DATA = coordinator.CRD_S_DATA
local svsessions = {} local svsessions = {}
---@enum SESSION_TYPE
local SESSION_TYPE = { local SESSION_TYPE = {
RTU_SESSION = 0, -- RTU gateway RTU_SESSION = 0, -- RTU gateway
PLC_SESSION = 1, -- reactor PLC PLC_SESSION = 1, -- reactor PLC
COORD_SESSION = 2, -- coordinator COORD_SESSION = 2, -- coordinator
DIAG_SESSION = 3 -- pocket diagnostics PDG_SESSION = 3 -- pocket diagnostics
} }
svsessions.SESSION_TYPE = SESSION_TYPE svsessions.SESSION_TYPE = SESSION_TYPE
local self = { local self = {
modem = nil, ---@type table|nil modem = nil, ---@type table|nil
fp_ok = false,
num_reactors = 0, num_reactors = 0,
facility = nil, ---@type facility|nil facility = nil, ---@type facility|nil
sessions = { rtu = {}, plc = {}, coord = {}, diag = {} }, sessions = { rtu = {}, plc = {}, coord = {}, pdg = {} },
next_ids = { rtu = 0, plc = 0, coord = 0, diag = 0 } next_ids = { rtu = 0, plc = 0, coord = 0, pdg = 0 }
} }
---@alias sv_session_structs plc_session_struct|rtu_session_struct|coord_session_struct|diag_session_struct ---@alias sv_session_structs plc_session_struct|rtu_session_struct|coord_session_struct|pdg_session_struct
-- PRIVATE FUNCTIONS -- -- PRIVATE FUNCTIONS --
@ -194,11 +197,13 @@ end
-- PUBLIC FUNCTIONS -- -- PUBLIC FUNCTIONS --
-- initialize svsessions -- initialize svsessions
---@param modem table ---@param modem table modem device
---@param num_reactors integer ---@param fp_ok boolean front panel active
---@param cooling_conf table ---@param num_reactors integer number of reactors
function svsessions.init(modem, num_reactors, cooling_conf) ---@param cooling_conf table cooling configuration definition
function svsessions.init(modem, fp_ok, num_reactors, cooling_conf)
self.modem = modem self.modem = modem
self.fp_ok = fp_ok
self.num_reactors = num_reactors self.num_reactors = num_reactors
self.facility = facility.new(num_reactors, cooling_conf) self.facility = facility.new(num_reactors, cooling_conf)
end end
@ -245,12 +250,11 @@ end
-- find a pocket diagnostics session by the remote port -- find a pocket diagnostics session by the remote port
---@nodiscard ---@nodiscard
---@param remote_port integer ---@param remote_port integer
---@return diag_session_struct|nil ---@return pdg_session_struct|nil
function svsessions.find_pdg_session(remote_port) function svsessions.find_pdg_session(remote_port)
-- check diagnostic sessions -- check diagnostic sessions
local session = _find_session(self.sessions.diag, remote_port) local session = _find_session(self.sessions.diag, remote_port)
---@cast session diag_session_struct|nil ---@cast session pdg_session_struct|nil
return session return session
end end
@ -299,13 +303,17 @@ function svsessions.establish_plc_session(local_port, remote_port, for_reactor,
instance = nil ---@type plc_session instance = nil ---@type plc_session
} }
plc_s.instance = plc.new_session(self.next_ids.plc, for_reactor, plc_s.in_queue, plc_s.out_queue, config.PLC_TIMEOUT) plc_s.instance = plc.new_session(self.next_ids.plc, for_reactor, plc_s.in_queue, plc_s.out_queue,
config.PLC_TIMEOUT, self.fp_ok)
table.insert(self.sessions.plc, plc_s) table.insert(self.sessions.plc, plc_s)
local units = self.facility.get_units() local units = self.facility.get_units()
units[for_reactor].link_plc_session(plc_s) units[for_reactor].link_plc_session(plc_s)
log.debug(util.c("established new PLC session to ", remote_port, " with ID ", self.next_ids.plc, " for reactor ", for_reactor)) log.debug(util.c("established new PLC session to ", remote_port, " with ID ", self.next_ids.plc,
" for reactor ", for_reactor))
databus.tx_plc_connected(for_reactor, version, remote_port)
self.next_ids.plc = self.next_ids.plc + 1 self.next_ids.plc = self.next_ids.plc + 1
@ -337,11 +345,14 @@ function svsessions.establish_rtu_session(local_port, remote_port, advertisement
instance = nil ---@type rtu_session instance = nil ---@type rtu_session
} }
rtu_s.instance = rtu.new_session(self.next_ids.rtu, rtu_s.in_queue, rtu_s.out_queue, config.RTU_TIMEOUT, advertisement, self.facility) rtu_s.instance = rtu.new_session(self.next_ids.rtu, rtu_s.in_queue, rtu_s.out_queue, config.RTU_TIMEOUT, advertisement,
self.facility, self.fp_ok)
table.insert(self.sessions.rtu, rtu_s) table.insert(self.sessions.rtu, rtu_s)
log.debug("established new RTU session to " .. remote_port .. " with ID " .. self.next_ids.rtu) log.debug("established new RTU session to " .. remote_port .. " with ID " .. self.next_ids.rtu)
databus.tx_rtu_connected(self.next_ids.rtu, version, remote_port)
self.next_ids.rtu = self.next_ids.rtu + 1 self.next_ids.rtu = self.next_ids.rtu + 1
-- success -- success
@ -368,11 +379,14 @@ function svsessions.establish_coord_session(local_port, remote_port, version)
instance = nil ---@type coord_session instance = nil ---@type coord_session
} }
coord_s.instance = coordinator.new_session(self.next_ids.coord, coord_s.in_queue, coord_s.out_queue, config.CRD_TIMEOUT, self.facility) coord_s.instance = coordinator.new_session(self.next_ids.coord, coord_s.in_queue, coord_s.out_queue, config.CRD_TIMEOUT,
self.facility, self.fp_ok)
table.insert(self.sessions.coord, coord_s) table.insert(self.sessions.coord, coord_s)
log.debug("established new coordinator session to " .. remote_port .. " with ID " .. self.next_ids.coord) log.debug("established new coordinator session to " .. remote_port .. " with ID " .. self.next_ids.coord)
databus.tx_crd_connected(version, remote_port)
self.next_ids.coord = self.next_ids.coord + 1 self.next_ids.coord = self.next_ids.coord + 1
-- success -- success
@ -389,9 +403,9 @@ end
---@param remote_port integer ---@param remote_port integer
---@param version string ---@param version string
---@return integer|false session_id ---@return integer|false session_id
function svsessions.establish_diag_session(local_port, remote_port, version) function svsessions.establish_pdg_session(local_port, remote_port, version)
---@class diag_session_struct ---@class pdg_session_struct
local diag_s = { local pdg_s = {
s_type = "pkt", s_type = "pkt",
open = true, open = true,
version = version, version = version,
@ -399,18 +413,20 @@ function svsessions.establish_diag_session(local_port, remote_port, version)
r_port = remote_port, r_port = remote_port,
in_queue = mqueue.new(), in_queue = mqueue.new(),
out_queue = mqueue.new(), out_queue = mqueue.new(),
instance = nil ---@type diag_session instance = nil ---@type pdg_session
} }
diag_s.instance = pocket.new_session(self.next_ids.diag, diag_s.in_queue, diag_s.out_queue, config.PKT_TIMEOUT) pdg_s.instance = pocket.new_session(self.next_ids.pdg, pdg_s.in_queue, pdg_s.out_queue, config.PKT_TIMEOUT, self.fp_ok)
table.insert(self.sessions.diag, diag_s) table.insert(self.sessions.pdg, pdg_s)
log.debug("established new pocket diagnostics session to " .. remote_port .. " with ID " .. self.next_ids.diag) log.debug("established new pocket diagnostics session to " .. remote_port .. " with ID " .. self.next_ids.pdg)
self.next_ids.diag = self.next_ids.diag + 1 databus.tx_pdg_connected(self.next_ids.pdg, version, remote_port)
self.next_ids.pdg = self.next_ids.pdg + 1
-- success -- success
return diag_s.instance.get_id() return pdg_s.instance.get_id()
end end
-- attempt to identify which session's watchdog timer fired -- attempt to identify which session's watchdog timer fired

View File

@ -5,16 +5,22 @@
require("/initenv").init_env() require("/initenv").init_env()
local crash = require("scada-common.crash") local crash = require("scada-common.crash")
local comms = require("scada-common.comms")
local log = require("scada-common.log") local log = require("scada-common.log")
local ppm = require("scada-common.ppm") local ppm = require("scada-common.ppm")
local tcd = require("scada-common.tcd")
local util = require("scada-common.util") local util = require("scada-common.util")
local core = require("graphics.core")
local config = require("supervisor.config") local config = require("supervisor.config")
local databus = require("supervisor.databus")
local renderer = require("supervisor.renderer")
local supervisor = require("supervisor.supervisor") local supervisor = require("supervisor.supervisor")
local svsessions = require("supervisor.session.svsessions") local svsessions = require("supervisor.session.svsessions")
local SUPERVISOR_VERSION = "v0.16.0" local SUPERVISOR_VERSION = "v0.17.0"
local println = util.println local println = util.println
local println_ts = util.println_ts local println_ts = util.println_ts
@ -43,7 +49,6 @@ cfv.assert_type_int(config.NUM_REACTORS)
cfv.assert_type_table(config.REACTOR_COOLING) cfv.assert_type_table(config.REACTOR_COOLING)
cfv.assert_type_str(config.LOG_PATH) cfv.assert_type_str(config.LOG_PATH)
cfv.assert_type_int(config.LOG_MODE) cfv.assert_type_int(config.LOG_MODE)
cfv.assert_type_bool(config.LOG_DEBUG)
assert(cfv.valid(), "bad config file: missing/invalid fields") assert(cfv.valid(), "bad config file: missing/invalid fields")
@ -65,7 +70,7 @@ end
-- log init -- log init
---------------------------------------- ----------------------------------------
log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG) log.init(config.LOG_PATH, config.LOG_MODE, config.LOG_DEBUG == true)
log.info("========================================") log.info("========================================")
log.info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION) log.info("BOOTING supervisor.startup " .. SUPERVISOR_VERSION)
@ -83,6 +88,9 @@ local function main()
-- startup -- startup
---------------------------------------- ----------------------------------------
-- record firmware versions and ID
databus.tx_versions(SUPERVISOR_VERSION, comms.version)
-- mount connected devices -- mount connected devices
ppm.mount_all() ppm.mount_all()
@ -93,7 +101,20 @@ local function main()
return return
end end
-- start comms databus.tx_hw_modem(true)
-- start UI
local fp_ok, message = pcall(renderer.start_ui)
if not fp_ok then
renderer.close_ui()
println_ts(util.c("UI error: ", message))
log.error(util.c("GUI crashed with error ", message))
else
-- redefine println_ts local to not print as we have the front panel running
println_ts = function (_) end
end
---@class sv_channel_list ---@class sv_channel_list
local channels = { local channels = {
SVR = config.SVR_CHANNEL, SVR = config.SVR_CHANNEL,
@ -102,8 +123,10 @@ local function main()
CRD = config.CRD_CHANNEL, CRD = config.CRD_CHANNEL,
PKT = config.PKT_CHANNEL PKT = config.PKT_CHANNEL
} }
-- start comms
local superv_comms = supervisor.comms(SUPERVISOR_VERSION, config.NUM_REACTORS, config.REACTOR_COOLING, modem, local superv_comms = supervisor.comms(SUPERVISOR_VERSION, config.NUM_REACTORS, config.REACTOR_COOLING, modem,
channels, config.TRUSTED_RANGE) channels, config.TRUSTED_RANGE, fp_ok)
-- base loop clock (6.67Hz, 3 ticks) -- base loop clock (6.67Hz, 3 ticks)
local MAIN_CLOCK = 0.15 local MAIN_CLOCK = 0.15
@ -112,6 +135,9 @@ local function main()
-- start clock -- start clock
loop_clock.start() loop_clock.start()
-- halve the rate heartbeat LED flash
local heartbeat_toggle = true
-- event loop -- event loop
while true do while true do
local event, param1, param2, param3, param4, param5 = util.pull_event() local event, param1, param2, param3, param4, param5 = util.pull_event()
@ -126,6 +152,7 @@ local function main()
if device == modem then if device == modem then
println_ts("wireless modem disconnected!") println_ts("wireless modem disconnected!")
log.warning("comms modem disconnected") log.warning("comms modem disconnected")
databus.tx_hw_modem(false)
else else
log.warning("non-comms modem disconnected") log.warning("non-comms modem disconnected")
end end
@ -143,6 +170,8 @@ local function main()
println_ts("wireless modem reconnected.") println_ts("wireless modem reconnected.")
log.info("comms modem reconnected") log.info("comms modem reconnected")
databus.tx_hw_modem(true)
else else
log.info("wired modem reconnected") log.info("wired modem reconnected")
end end
@ -151,6 +180,9 @@ local function main()
elseif event == "timer" and loop_clock.is_clock(param1) then elseif event == "timer" and loop_clock.is_clock(param1) then
-- main loop tick -- main loop tick
if heartbeat_toggle then databus.heartbeat() end
heartbeat_toggle = not heartbeat_toggle
-- iterate sessions -- iterate sessions
svsessions.iterate_all() svsessions.iterate_all()
@ -161,10 +193,16 @@ local function main()
elseif event == "timer" then elseif event == "timer" then
-- a non-clock timer event, check watchdogs -- a non-clock timer event, check watchdogs
svsessions.check_all_watchdogs(param1) svsessions.check_all_watchdogs(param1)
-- notify timer callback dispatcher
tcd.handle(param1)
elseif event == "modem_message" then elseif event == "modem_message" then
-- got a packet -- got a packet
local packet = superv_comms.parse_packet(param1, param2, param3, param4, param5) local packet = superv_comms.parse_packet(param1, param2, param3, param4, param5)
superv_comms.handle_packet(packet) superv_comms.handle_packet(packet)
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 end
-- check for termination request -- check for termination request
@ -177,8 +215,15 @@ local function main()
end end
end end
println_ts("exited") renderer.close_ui()
util.println_ts("exited")
log.info("exited") log.info("exited")
end end
if not xpcall(main, crash.handler) then crash.exit() else log.close() end if not xpcall(main, crash.handler) then
pcall(renderer.close_ui)
crash.exit()
else
log.close()
end

View File

@ -11,8 +11,6 @@ local DEVICE_TYPE = comms.DEVICE_TYPE
local ESTABLISH_ACK = comms.ESTABLISH_ACK local ESTABLISH_ACK = comms.ESTABLISH_ACK
local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE local SCADA_MGMT_TYPE = comms.SCADA_MGMT_TYPE
local println = util.println
-- supervisory controller communications -- supervisory controller communications
---@nodiscard ---@nodiscard
---@param _version string supervisor version ---@param _version string supervisor version
@ -21,8 +19,13 @@ local println = util.println
---@param modem table modem device ---@param modem table modem device
---@param channels sv_channel_list network channels ---@param channels sv_channel_list network channels
---@param range integer trusted device connection range ---@param range integer trusted device connection range
---@param fp_ok boolean if the front panel UI is running
---@diagnostic disable-next-line: unused-local ---@diagnostic disable-next-line: unused-local
function supervisor.comms(_version, num_reactors, cooling_conf, modem, channels, range) function supervisor.comms(_version, num_reactors, cooling_conf, modem, channels, range, fp_ok)
-- print a log message to the terminal as long as the UI isn't running
local function println(message) if not fp_ok then util.println_ts(message) end end
-- channel list
local svr_channel = channels.SVR local svr_channel = channels.SVR
local plc_channel = channels.PLC local plc_channel = channels.PLC
local rtu_channel = channels.RTU local rtu_channel = channels.RTU
@ -45,8 +48,8 @@ function supervisor.comms(_version, num_reactors, cooling_conf, modem, channels,
_conf_channels() _conf_channels()
-- link modem to svsessions -- pass modem, status, and config data to svsessions
svsessions.init(modem, num_reactors, cooling_conf) svsessions.init(modem, fp_ok, num_reactors, cooling_conf)
-- send an establish request response -- send an establish request response
---@param packet scada_packet ---@param packet scada_packet
@ -325,7 +328,7 @@ function supervisor.comms(_version, num_reactors, cooling_conf, modem, channels,
end end
elseif dev_type == DEVICE_TYPE.PKT then elseif dev_type == DEVICE_TYPE.PKT then
-- this is an attempt to establish a new pocket diagnostic session -- this is an attempt to establish a new pocket diagnostic session
local s_id = svsessions.establish_diag_session(l_chan, r_chan, firmware_v) local s_id = svsessions.establish_pdg_session(l_chan, r_chan, firmware_v)
println(util.c("PKT (", firmware_v, ") [:", r_chan, "] \xbb connected")) println(util.c("PKT (", firmware_v, ") [:", r_chan, "] \xbb connected"))
log.info(util.c("SVCTL_ESTABLISH: pocket (", firmware_v, ") [:", r_chan, "] connected with session ID ", s_id)) log.info(util.c("SVCTL_ESTABLISH: pocket (", firmware_v, ") [:", r_chan, "] connected with session ID ", s_id))

View File

@ -506,7 +506,7 @@ function unit.new(reactor_id, num_boilers, num_turbines)
--#region --#region
-- engage automatic control -- engage automatic control
function public.a_engage() function public.auto_engage()
self.auto_engaged = true self.auto_engaged = true
if self.plc_i ~= nil then if self.plc_i ~= nil then
self.plc_i.auto_lock(true) self.plc_i.auto_lock(true)
@ -514,7 +514,7 @@ function unit.new(reactor_id, num_boilers, num_turbines)
end end
-- disengage automatic control -- disengage automatic control
function public.a_disengage() function public.auto_disengage()
self.auto_engaged = false self.auto_engaged = false
if self.plc_i ~= nil then if self.plc_i ~= nil then
self.plc_i.auto_lock(false) self.plc_i.auto_lock(false)
@ -526,7 +526,7 @@ function unit.new(reactor_id, num_boilers, num_turbines)
-- if it is degraded or not ready, the limit will be 0 -- if it is degraded or not ready, the limit will be 0
---@nodiscard ---@nodiscard
---@return integer lim_br100 ---@return integer lim_br100
function public.a_get_effective_limit() function public.auto_get_effective_limit()
if (not self.db.control.ready) or self.db.control.degraded or self.plc_cache.rps_trip then if (not self.db.control.ready) or self.db.control.degraded or self.plc_cache.rps_trip then
self.db.control.br100 = 0 self.db.control.br100 = 0
return 0 return 0
@ -537,7 +537,7 @@ function unit.new(reactor_id, num_boilers, num_turbines)
-- set the automatic burn rate based on the last set burn rate in 100ths -- set the automatic burn rate based on the last set burn rate in 100ths
---@param ramp boolean true to ramp to rate, false to set right away ---@param ramp boolean true to ramp to rate, false to set right away
function public.a_commit_br100(ramp) function public.auto_commit_br100(ramp)
if self.auto_engaged then if self.auto_engaged then
if self.plc_i ~= nil then if self.plc_i ~= nil then
self.plc_i.auto_set_burn(self.db.control.br100 / 100, ramp) self.plc_i.auto_set_burn(self.db.control.br100 / 100, ramp)
@ -550,16 +550,16 @@ function unit.new(reactor_id, num_boilers, num_turbines)
-- check if ramping is complete (burn rate is same as target) -- check if ramping is complete (burn rate is same as target)
---@nodiscard ---@nodiscard
---@return boolean complete ---@return boolean complete
function public.a_ramp_complete() function public.auto_ramp_complete()
if self.plc_i ~= nil then if self.plc_i ~= nil then
return self.plc_i.is_ramp_complete() or return self.plc_i.is_ramp_complete() or
(self.plc_i.get_status().act_burn_rate == 0 and self.db.control.br100 == 0) or (self.plc_i.get_status().act_burn_rate == 0 and self.db.control.br100 == 0) or
public.a_get_effective_limit() == 0 public.auto_get_effective_limit() == 0
else return true end else return true end
end end
-- perform an automatic SCRAM -- perform an automatic SCRAM
function public.a_scram() function public.auto_scram()
if self.plc_s ~= nil then if self.plc_s ~= nil then
self.db.control.br100 = 0 self.db.control.br100 = 0
self.plc_s.in_queue.push_command(PLC_S_CMDS.ASCRAM) self.plc_s.in_queue.push_command(PLC_S_CMDS.ASCRAM)
@ -567,7 +567,7 @@ function unit.new(reactor_id, num_boilers, num_turbines)
end end
-- queue a command to clear timeout/auto-scram if set -- queue a command to clear timeout/auto-scram if set
function public.a_cond_rps_reset() function public.auto_cond_rps_reset()
if self.plc_s ~= nil and self.plc_i ~= nil and (not self.auto_was_alarmed) and (not self.emcool_opened) then if self.plc_s ~= nil and self.plc_i ~= nil and (not self.auto_was_alarmed) and (not self.emcool_opened) then
local rps = self.plc_i.get_rps() local rps = self.plc_i.get_rps()
if rps.timeout or rps.automatic then if rps.timeout or rps.automatic then

View File

@ -549,7 +549,7 @@ function logic.update_auto_safety(public, self)
end end
if alarmed and not self.plc_cache.rps_status.automatic then if alarmed and not self.plc_cache.rps_status.automatic then
public.a_scram() public.auto_scram()
end end
self.auto_was_alarmed = alarmed self.auto_was_alarmed = alarmed